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: ( + + + + ), + }} 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',