diff --git a/backend/internal/modules/distribution/dto.go b/backend/internal/modules/distribution/dto.go
index 9d8290f..c3eb05c 100644
--- a/backend/internal/modules/distribution/dto.go
+++ b/backend/internal/modules/distribution/dto.go
@@ -152,6 +152,8 @@ type AccountDisplayDTO struct {
DepositAmountCent int64 `json:"deposit_amount_cent,omitempty"`
ListingStatus string `json:"listing_status,omitempty"`
InTransaction bool `json:"in_transaction,omitempty"`
+ ListingSiteID *uint64 `json:"listing_site_id,omitempty"`
+ SourceChannel string `json:"source_channel,omitempty"`
}
type SyncSiteAccountsRequest struct {
diff --git a/backend/internal/modules/distribution/repository.go b/backend/internal/modules/distribution/repository.go
index 9248ce2..3b2e37f 100644
--- a/backend/internal/modules/distribution/repository.go
+++ b/backend/internal/modules/distribution/repository.go
@@ -480,6 +480,8 @@ func (r *Repository) ListAccountDisplays(ctx context.Context, siteID uint64, sta
DepositAmountCent int64 `gorm:"column:deposit_amount_cent"`
ListingStatus string `gorm:"column:listing_status"`
InTransaction bool `gorm:"column:in_transaction"`
+ ListingSiteID *uint64 `gorm:"column:listing_site_id"`
+ SourceChannel string `gorm:"column:source_channel"`
}
var rows []accountDisplayRow
@@ -494,6 +496,8 @@ func (r *Repository) ListAccountDisplays(ctx context.Context, siteID uint64, sta
COALESCE(dsa.markup_amount_cent, 0) AS markup_amount_cent,
COALESCE(dsa.shelf_ratio_delta, 0) AS shelf_ratio_delta,
COALESCE(l.listing_no, '') AS listing_no,
+ l.distribution_site_id AS listing_site_id,
+ COALESCE(l.source_channel, '') AS source_channel,
COALESCE(ga.title, '') AS title,
COALESCE(ga.game_name, '') AS game_name,
COALESCE(ga.server_region, '') AS server_region,
@@ -540,6 +544,8 @@ func (r *Repository) ListAccountDisplays(ctx context.Context, siteID uint64, sta
DepositAmountCent: row.DepositAmountCent,
ListingStatus: row.ListingStatus,
InTransaction: row.InTransaction,
+ ListingSiteID: row.ListingSiteID,
+ SourceChannel: row.SourceChannel,
})
}
return items, nil
@@ -627,13 +633,6 @@ func (r *Repository) SyncSiteAccounts(ctx context.Context, adminID, siteID uint6
}
var candidates []model.RentalListing
- if err := r.db.WithContext(ctx).
- Where("distribution_site_id = ? AND status = ? AND review_status = ?", siteID, "published", "approved").
- Order("id ASC").
- Find(&candidates).Error; err != nil {
- return nil, err
- }
-
if len(selectedIDs) > 0 {
var selected []model.RentalListing
if err := r.db.WithContext(ctx).Where("id IN ?", selectedIDs).Find(&selected).Error; err != nil {
@@ -642,16 +641,14 @@ func (r *Repository) SyncSiteAccounts(ctx context.Context, adminID, siteID uint6
if len(selected) != len(selectedIDs) {
return nil, ErrSyncListingNotFound
}
- seenListing := make(map[uint64]bool, len(candidates)+len(selected))
- for _, l := range candidates {
- seenListing[l.ID] = true
- }
- for _, l := range selected {
- if seenListing[l.ID] {
- continue
- }
- seenListing[l.ID] = true
- candidates = append(candidates, l)
+ candidates = selected
+ } else {
+ // 未显式指定 listingIDs 时,默认将主站自营及全网审核通过的在售商品全部同步至本分站货架
+ if err := r.db.WithContext(ctx).
+ Where("status = ? AND review_status = ?", "published", "approved").
+ Order("id ASC").
+ Find(&candidates).Error; err != nil {
+ return nil, err
}
}
diff --git a/backend/internal/modules/distribution/repository_test.go b/backend/internal/modules/distribution/repository_test.go
index 3d8381c..0b121f8 100644
--- a/backend/internal/modules/distribution/repository_test.go
+++ b/backend/internal/modules/distribution/repository_test.go
@@ -318,20 +318,20 @@ func TestSyncSiteAccountsAndPublicListings(t *testing.T) {
t.Fatal(err)
}
- // 默认同步不得灌入主站全量目录,只收本站上传货
+ // 默认同步全网合规商品(包含主站商品与本站上传货)
resMain, err := repo.SyncSiteAccounts(t.Context(), 0, site.ID, nil)
if err != nil {
- t.Fatalf("SyncSiteAccounts own-only err = %v", err)
+ t.Fatalf("SyncSiteAccounts err = %v", err)
}
- if resMain.SyncedCount != 1 || resMain.SkippedCount != 0 || resMain.TotalActive != 1 {
- t.Fatalf("unexpected own-only res: %+v", resMain)
+ if resMain.SyncedCount != 2 || resMain.SkippedCount != 0 || resMain.TotalActive != 2 {
+ t.Fatalf("unexpected sync res: %+v", resMain)
}
res2, err := repo.SyncSiteAccounts(t.Context(), 0, site.ID, nil)
if err != nil {
t.Fatalf("SyncSiteAccounts retry err = %v", err)
}
- if res2.SyncedCount != 0 || res2.SkippedCount != 1 || res2.TotalActive != 1 {
+ if res2.SyncedCount != 0 || res2.SkippedCount != 2 || res2.TotalActive != 2 {
t.Fatalf("unexpected res2: %+v", res2)
}
@@ -343,7 +343,7 @@ func TestSyncSiteAccountsAndPublicListings(t *testing.T) {
if err != nil {
t.Fatalf("explicit select err = %v", err)
}
- if resSelect.SyncedCount != 1 || resSelect.SkippedCount != 1 || resSelect.TotalActive != 2 {
+ if resSelect.SyncedCount != 0 || resSelect.SkippedCount != 1 || resSelect.TotalActive != 1 {
t.Fatalf("unexpected explicit select res: %+v", resSelect)
}
diff --git a/distribution-admin/src/api.ts b/distribution-admin/src/api.ts
index 2093456..4644db4 100644
--- a/distribution-admin/src/api.ts
+++ b/distribution-admin/src/api.ts
@@ -191,6 +191,8 @@ export interface AccountDisplay {
deposit_amount_cent?: number
listing_status?: string
in_transaction?: boolean
+ listing_site_id?: number | null
+ source_channel?: string
}
export interface SyncSiteAccountsResult {
diff --git a/distribution-admin/src/pages/Accounts.tsx b/distribution-admin/src/pages/Accounts.tsx
index cba6411..e3eb5e6 100644
--- a/distribution-admin/src/pages/Accounts.tsx
+++ b/distribution-admin/src/pages/Accounts.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState, useMemo } from 'react'
-import { Card, Table, Typography, Space, Select, Tag, message, Button, Input, Modal, InputNumber } from 'antd'
+import { Card, Table, Typography, Space, Select, Tag, message, Button, Input, Modal, InputNumber, Empty } from 'antd'
import { SyncOutlined, SearchOutlined, CheckCircleOutlined, StopOutlined, EditOutlined, DollarCircleOutlined } from '@ant-design/icons'
import {
listAccountDisplays,
@@ -69,13 +69,13 @@ export default function Accounts() {
try {
const res = await syncSiteAccounts(siteID)
message.success(
- `同步完成!新增本站上传货 ${res.synced_count} 个,已存在 ${res.skipped_count} 个(本次候选 ${res.total_active} 个)`,
+ `同步完成!成功同步新增商品 ${res.synced_count} 个,已在货架 ${res.skipped_count} 个(平台在售共 ${res.total_active} 个)`,
5,
)
await load(siteID, status)
} catch (error: unknown) {
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
- message.error(respMessage ?? '同步本站上传商品失败')
+ message.error(respMessage ?? '同步主站商品失败')
} finally {
setSyncing(false)
}
@@ -189,7 +189,7 @@ export default function Accounts() {
商品与账号展示
- 管理当前分销网站对外展示的商品。默认同步本站上传货,主站货需显式勾选,不会灌入全站目录。
+ 管理当前分销网站对外展示的商品货架。所有账号源均来自主站及平台共享池,支持一键同步主站商品,并进行自主提价(调小比例)销售。
@@ -279,6 +279,23 @@ export default function Accounts() {
rowKey="id"
loading={loading}
dataSource={filteredItems}
+ locale={{
+ emptyText: (
+
+ }
+ loading={syncing}
+ onClick={handleSync}
+ >
+ 一键同步主站商品
+
+
+ ),
+ }}
rowSelection={{
selectedRowKeys,
onChange: setSelectedRowKeys,
@@ -302,6 +319,19 @@ export default function Accounts() {
),
},
+ {
+ title: '货源归属',
+ width: 110,
+ render: (_: unknown, row: AccountDisplay) => {
+ if (row.listing_site_id === null || row.listing_site_id === undefined) {
+ return 主站自营
+ }
+ if (row.listing_site_id === siteID) {
+ return 本站收号
+ }
+ return 合作分站
+ },
+ },
{
title: '账号信息',
dataIndex: 'title',