feat(distribution): 支持分销上传来源追踪、主站审核展示与收售结算模式自动判定

This commit is contained in:
yml2213
2026-09-08 15:00:35 +08:00
parent a61828c6ca
commit 0fcb6a513f
14 changed files with 301 additions and 69 deletions
+2
View File
@@ -43,6 +43,8 @@ func MigrateListingLifecycleTestSchema(db *gorm.DB) error {
&model.RentalListing{},
&model.ListingUpload{},
&model.ListingStatusEvent{},
&model.DistributionSite{},
&model.DistributionSiteAccount{},
)
}
+6 -5
View File
@@ -40,11 +40,12 @@ type RentalListing struct {
ReviewStatus string `gorm:"size:32;not null;default:'none'" json:"review_status"`
HandoffMode string `gorm:"size:16;not null;default:'owner';index" json:"handoff_mode"`
SettlementMode string `gorm:"size:32;not null;default:'owner_wallet'" json:"settlement_mode"`
ManagedAdminID *uint64 `gorm:"index" json:"managed_admin_id"`
ReviewReason string `gorm:"size:255;not null;default:''" json:"review_reason"`
PublishedAt *time.Time `json:"published_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ManagedAdminID *uint64 `gorm:"index" json:"managed_admin_id"`
DistributionSiteID *uint64 `gorm:"column:distribution_site_id;index" json:"distribution_site_id,omitempty"`
ReviewReason string `gorm:"size:255;not null;default:''" json:"review_reason"`
PublishedAt *time.Time `json:"published_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (RentalListing) TableName() string {
@@ -24,28 +24,66 @@ func distributionTablesPresent(tx *gorm.DB) bool {
return distributionTablePresent
}
// ResolveOrderSourceTx 校验站点、账号展示关系和生效规则,供正常订单创建事务写入快照引用。
// 第一期仅启用 two_party_distributor_receive 模式,其他模式规则不会命中(见设计文档实施顺序)。
// ResolveOrderSourceTx 校验收号方站点与售出方站点、账号展示关系和生效规则,供正常订单创建事务写入快照引用。
func ResolveOrderSourceTx(tx *gorm.DB, siteCode string, listingID, accountID uint64, at time.Time) (*model.DistributionSite, *model.DistributionRuleVersion, error) {
if siteCode == "" {
var listing model.RentalListing
var listingSiteID *uint64
if listingID > 0 {
if err := tx.Select("id, distribution_site_id").First(&listing, listingID).Error; err == nil {
listingSiteID = listing.DistributionSiteID
}
}
// 1. 两方均无分销站:纯主站交易
if siteCode == "" && listingSiteID == nil {
return nil, nil, nil
}
var site model.DistributionSite
if err := tx.Where("site_code = ? AND status = 'active' AND display_account_enabled = ?", siteCode, true).First(&site).Error; err != nil {
// 2. 模式一:分销网收号、主站卖出 (ModeTwoPartyDistributorReceive)
if siteCode == "" && listingSiteID != nil {
var site model.DistributionSite
if err := tx.Where("id = ? AND status = 'active'", *listingSiteID).First(&site).Error; err != nil {
return nil, nil, ErrSiteNotFound
}
var rule model.DistributionRuleVersion
query := tx.Where("mode = ? AND status = 'active' AND effective_from <= ? AND (effective_to IS NULL OR effective_to > ?)", ModeTwoPartyDistributorReceive, at, at).
Where("site_id = ? OR site_id IS NULL", site.ID).
Order("CASE WHEN site_id IS NULL THEN 1 ELSE 0 END, effective_from DESC, id DESC")
if err := query.First(&rule).Error; err != nil {
return nil, nil, ErrRuleNotFound
}
return &site, &rule, nil
}
// 3. 买家在分销站下单:校验售出站点状态与账号展示关系
var sellSite model.DistributionSite
if err := tx.Where("site_code = ? AND status = 'active' AND display_account_enabled = ?", siteCode, true).First(&sellSite).Error; err != nil {
return nil, nil, ErrSiteNotFound
}
var relation model.DistributionSiteAccount
if err := tx.Where("site_id = ? AND listing_id = ? AND account_id = ? AND display_status = 'enabled'", site.ID, listingID, accountID).First(&relation).Error; err != nil {
if err := tx.Where("site_id = ? AND listing_id = ? AND account_id = ? AND display_status = 'enabled'", sellSite.ID, listingID, accountID).First(&relation).Error; err != nil {
return nil, nil, ErrAccountDisplayNotFound
}
// 4. 解析生效规则:优先匹配有效规则版本
targetMode := ModeTwoPartyDistributorReceive
if listingSiteID != nil && *listingSiteID != sellSite.ID {
targetMode = ModeThreeParty
}
var rule model.DistributionRuleVersion
query := tx.Where("mode = ? AND status = 'active' AND effective_from <= ? AND (effective_to IS NULL OR effective_to > ?)", ModeTwoPartyDistributorReceive, at, at).
Where("site_id = ? OR site_id IS NULL", site.ID).
Order("CASE WHEN site_id IS NULL THEN 1 ELSE 0 END, effective_from DESC, id DESC")
query := tx.Where("status = 'active' AND effective_from <= ? AND (effective_to IS NULL OR effective_to > ?)", at, at).
Where("site_id = ? OR site_id IS NULL", sellSite.ID)
if targetMode == ModeThreeParty {
query = query.Where("mode = ?", ModeThreeParty)
} else {
query = query.Where("mode IN (?, ?)", ModeTwoPartyDistributorReceive, ModeTwoPartyPlatformReceive).
Order("CASE WHEN mode = '" + ModeTwoPartyDistributorReceive + "' THEN 0 ELSE 1 END")
}
query = query.Order("CASE WHEN site_id IS NULL THEN 1 ELSE 0 END, effective_from DESC, id DESC")
if err := query.First(&rule).Error; err != nil {
return nil, nil, ErrRuleNotFound
}
return &site, &rule, nil
return &sellSite, &rule, nil
}
// calculateForRule 按规则快照计算手续费、服务费和分成基数。
@@ -99,6 +137,12 @@ func SnapshotPaidOrderTx(tx *gorm.DB, orderID uint64, grossPaidCent int64) error
partyID := *order.DistributionSiteID
if partyType == "platform" {
partyID = 0
} else if split.PartyRole == "distributor_a" {
// 三方模式下 distributor_a 为商品收号站
var listing model.RentalListing
if err := tx.Select("distribution_site_id").First(&listing, order.ListingID).Error; err == nil && listing.DistributionSiteID != nil {
partyID = *listing.DistributionSiteID
}
}
parties = append(parties, model.DistributionOrderParty{OrderID: order.ID, SiteID: *order.DistributionSiteID, PartyRole: split.PartyRole, PartyType: partyType, PartyID: partyID, Ratio: split.Ratio, IncomeAmountCent: split.AmountCent, ProfitAmountCent: split.AmountCent})
}
+3
View File
@@ -38,6 +38,9 @@ type ListingDTO struct {
ManagedAdminID *uint64 `json:"managed_admin_id,omitempty"`
ReviewReason string `json:"review_reason"`
PublishedAt *time.Time `json:"published_at"`
DistributionSiteID *uint64 `json:"distribution_site_id,omitempty"`
DistributionSiteCode string `json:"distribution_site_code,omitempty"`
DistributionSiteName string `json:"distribution_site_name,omitempty"`
ListingGroupConversationID uint64 `json:"listing_group_conversation_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
@@ -18,7 +18,8 @@ func (h *Handler) Create(c *gin.Context) {
response.BadRequest(c, listingBindErrorMessage(err))
return
}
item, err := h.service.Create(c.Request.Context(), ownerID, req)
siteCode := strings.TrimSpace(c.GetHeader("X-Distribution-Site-Code"))
item, err := h.service.Create(c.Request.Context(), ownerID, req, siteCode)
if err != nil {
writeListingError(c, err)
return
+39 -9
View File
@@ -15,7 +15,7 @@ import (
"gorm.io/gorm/clause"
)
func (r *Repository) Create(ctx context.Context, ownerID uint64, req CreateRequest, reviewRequired bool, publishCooldown time.Duration) (*ListingDTO, error) {
func (r *Repository) Create(ctx context.Context, ownerID uint64, req CreateRequest, reviewRequired bool, publishCooldown time.Duration, siteCode ...string) (*ListingDTO, error) {
var dto *ListingDTO
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
now := time.Now()
@@ -51,20 +51,44 @@ func (r *Repository) Create(ctx context.Context, ownerID uint64, req CreateReque
if err := tx.Create(&account).Error; err != nil {
return err
}
var site model.DistributionSite
var siteID *uint64
if len(siteCode) > 0 && strings.TrimSpace(siteCode[0]) != "" {
trimmedSiteCode := strings.TrimSpace(siteCode[0])
if err := tx.Where("site_code = ? AND status = 'active'", trimmedSiteCode).First(&site).Error; err == nil {
siteID = &site.ID
}
}
priceCent := normalizedListingPriceCent(req)
listing := model.RentalListing{
ListingNo: listingNo,
AccountID: account.ID,
OwnerID: ownerID,
PriceCent: priceCent,
DepositAmountCent: req.DepositAmountCent,
Status: listingStatus,
ReviewStatus: reviewStatus,
PublishedAt: publishedAt,
ListingNo: listingNo,
AccountID: account.ID,
OwnerID: ownerID,
PriceCent: priceCent,
DepositAmountCent: req.DepositAmountCent,
Status: listingStatus,
ReviewStatus: reviewStatus,
PublishedAt: publishedAt,
DistributionSiteID: siteID,
}
if err := tx.Create(&listing).Error; err != nil {
return err
}
if siteID != nil {
dsa := model.DistributionSiteAccount{
SiteID: *siteID,
AccountID: account.ID,
ListingID: listing.ID,
DisplayStatus: "enabled",
}
if err := tx.Create(&dsa).Error; err != nil {
return err
}
}
if err := listingstatus.AppendTransition(tx, &listing, "", listingstatus.SourceSeller, listingstatus.ActorUser, ownerID, "发布上架"); err != nil {
return err
}
@@ -80,6 +104,12 @@ func (r *Repository) Create(ctx context.Context, ownerID uint64, req CreateReque
}
dto = toDTO(account, listing)
if siteID != nil {
dto.DistributionSiteID = siteID
dto.DistributionSiteCode = site.SiteCode
dto.DistributionSiteName = site.SiteName
dto.SourceChannel = fmt.Sprintf("分销站 - %s (%s)", site.SiteName, site.SiteCode)
}
dto.ListingGroupConversationID = conversationID
return nil
})
+40 -34
View File
@@ -18,8 +18,10 @@ type listingRow struct {
Title string
OwnerPhone string
OwnerNickname string
SourceChannel string `gorm:"column:source_channel"`
IsExternalUpload bool `gorm:"column:is_external_upload"`
SourceChannel string `gorm:"column:source_channel"`
IsExternalUpload bool `gorm:"column:is_external_upload"`
DistributionSiteCode string `gorm:"column:distribution_site_code"`
DistributionSiteName string `gorm:"column:distribution_site_name"`
Description string
GameName string
ServerRegion string
@@ -189,11 +191,14 @@ func (row listingRow) toDTO() ListingDTO {
ReviewStatus: reviewStatus,
HandoffMode: row.HandoffMode,
SettlementMode: row.SettlementMode,
ManagedAdminID: row.ManagedAdminID,
ReviewReason: reviewReason,
PublishedAt: row.PublishedAt,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
ManagedAdminID: row.ManagedAdminID,
DistributionSiteID: row.DistributionSiteID,
DistributionSiteCode: row.DistributionSiteCode,
DistributionSiteName: row.DistributionSiteName,
ReviewReason: reviewReason,
PublishedAt: row.PublishedAt,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
}
@@ -202,33 +207,34 @@ func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
screenshotURLS := cleanScreenshotURLs(decodeScreenshots(account.ScreenshotURLS))
reviewStatus, reviewReason := normalizedReviewState(listing.Status, listing.ReviewStatus, listing.ReviewReason)
return &ListingDTO{
ID: listing.ID,
ListingNo: listing.ListingNo,
AccountID: account.ID,
OwnerID: listing.OwnerID,
Title: account.Title,
Description: account.Description,
GameName: account.GameName,
ServerRegion: account.ServerRegion,
LoginPlatform: account.LoginPlatform,
RankLevel: account.RankLevel,
HafCoinAmount: account.HafCoinAmount,
AssetSummary: assetSummary,
ScreenshotURLS: screenshotURLS,
CoverURL: publicCoverURL(listing.ID, screenshotURLS, listing.Status, listing.ReviewStatus),
PriceCent: listing.PriceCent,
DepositAmountCent: listing.DepositAmountCent,
IsAccelerated: isAcceleratedSale(assetSummary),
InTransaction: listing.InTransaction,
Status: listing.Status,
ReviewStatus: reviewStatus,
HandoffMode: listing.HandoffMode,
SettlementMode: listing.SettlementMode,
ManagedAdminID: listing.ManagedAdminID,
ReviewReason: reviewReason,
PublishedAt: listing.PublishedAt,
CreatedAt: listing.CreatedAt,
UpdatedAt: listing.UpdatedAt,
ID: listing.ID,
ListingNo: listing.ListingNo,
AccountID: account.ID,
OwnerID: listing.OwnerID,
Title: account.Title,
Description: account.Description,
GameName: account.GameName,
ServerRegion: account.ServerRegion,
LoginPlatform: account.LoginPlatform,
RankLevel: account.RankLevel,
HafCoinAmount: account.HafCoinAmount,
AssetSummary: assetSummary,
ScreenshotURLS: screenshotURLS,
CoverURL: publicCoverURL(listing.ID, screenshotURLS, listing.Status, listing.ReviewStatus),
PriceCent: listing.PriceCent,
DepositAmountCent: listing.DepositAmountCent,
IsAccelerated: isAcceleratedSale(assetSummary),
InTransaction: listing.InTransaction,
Status: listing.Status,
ReviewStatus: reviewStatus,
HandoffMode: listing.HandoffMode,
SettlementMode: listing.SettlementMode,
ManagedAdminID: listing.ManagedAdminID,
DistributionSiteID: listing.DistributionSiteID,
ReviewReason: reviewReason,
PublishedAt: listing.PublishedAt,
CreatedAt: listing.CreatedAt,
UpdatedAt: listing.UpdatedAt,
}
}
@@ -220,13 +220,17 @@ func (r *Repository) baseQuery(ctx context.Context) *gorm.DB {
Select(`l.*, a.title, a.description, a.game_name, a.server_region, a.login_platform, a.rank_level,
a.haf_coin_amount, a.asset_summary, a.screenshot_urls, COALESCE(u.phone, '') AS owner_phone, COALESCE(u.nickname, '') AS owner_nickname,
CASE WHEN lu.id IS NULL THEN 0 ELSE 1 END AS is_external_upload,
COALESCE(src_site.site_code, '') AS distribution_site_code,
COALESCE(src_site.site_name, '') AS distribution_site_name,
CASE
WHEN l.distribution_site_id IS NOT NULL AND src_site.id IS NOT NULL THEN CONCAT('分销站 - ', src_site.site_name, ' (', src_site.site_code, ')')
WHEN lu.id IS NULL THEN ?
WHEN COALESCE(lu.source_channel, '') = '' THEN ?
ELSE lu.source_channel
END AS source_channel`, sourceChannelWebsite, sourceChannelExternalUnknown).
Joins("JOIN game_accounts AS a ON a.id = l.account_id").
Joins("LEFT JOIN users AS u ON u.id = l.owner_id").
Joins("LEFT JOIN distribution_sites AS src_site ON src_site.id = l.distribution_site_id").
// 逐商品按 listing_id 索引定位最后一次导入,避免每次列表查询都对全量导入记录分组。
Joins(`LEFT JOIN listing_uploads AS lu ON lu.id = (
SELECT MAX(lu_latest.id) FROM listing_uploads AS lu_latest WHERE lu_latest.listing_id = l.id
@@ -4,7 +4,7 @@ import (
"context"
)
func (s *Service) Create(ctx context.Context, ownerID uint64, req CreateRequest) (*ListingDTO, error) {
func (s *Service) Create(ctx context.Context, ownerID uint64, req CreateRequest, siteCode ...string) (*ListingDTO, error) {
if s.repo == nil {
return nil, ErrDependencyUnavailable
}
@@ -29,7 +29,11 @@ func (s *Service) Create(ctx context.Context, ownerID uint64, req CreateRequest)
if err != nil {
return nil, err
}
return s.repo.Create(ctx, ownerID, req, reviewRequired, cooldown)
effectiveSiteCode := ""
if len(siteCode) > 0 {
effectiveSiteCode = siteCode[0]
}
return s.repo.Create(ctx, ownerID, req, reviewRequired, cooldown, effectiveSiteCode)
}
func (s *Service) Update(ctx context.Context, ownerID uint64, id uint64, req UpdateRequest) (*ListingDTO, error) {
@@ -820,3 +820,71 @@ func TestUpdateScreenshots(t *testing.T) {
t.Fatalf("unexpected coin screenshots: %#v", coinList)
}
}
func TestCreateListingWithDistributionSiteCode(t *testing.T) {
db := database.NewTestDB()
if err := database.MigrateListingLifecycleTestSchema(db); err != nil {
t.Fatalf("migrate err: %v", err)
}
repo := NewRepository(db, nil)
user := model.User{Phone: "18800000001", Nickname: "分销卖家", RealnameStatus: "verified", Status: "active"}
db.Create(&user)
site := model.DistributionSite{
SiteCode: "dist_test_1",
SiteName: "测试分销商1",
Domain: "d1.com",
Status: "active",
DisplayAccountEnabled: true,
}
db.Create(&site)
req := CreateRequest{
Title: "分销上架商品",
ServerRegion: "QQ",
LoginPlatform: "PC",
PriceCent: 8800,
}
dto, err := repo.Create(context.Background(), user.ID, req, true, 0, "dist_test_1")
if err != nil {
t.Fatalf("repo.Create failed: %v", err)
}
if dto.DistributionSiteID == nil || *dto.DistributionSiteID != site.ID {
t.Fatalf("expected DistributionSiteID=%d, got %v", site.ID, dto.DistributionSiteID)
}
if dto.DistributionSiteCode != "dist_test_1" {
t.Fatalf("expected dist_test_1, got %s", dto.DistributionSiteCode)
}
if dto.DistributionSiteName != "测试分销商1" {
t.Fatalf("expected 测试分销商1, got %s", dto.DistributionSiteName)
}
expectedSource := "分销站 - 测试分销商1 (dist_test_1)"
if dto.SourceChannel != expectedSource {
t.Fatalf("expected %s, got %s", expectedSource, dto.SourceChannel)
}
// 验证 distribution_site_accounts 自动创建并启用
var dsa model.DistributionSiteAccount
if err := db.Where("site_id = ? AND listing_id = ?", site.ID, dto.ID).First(&dsa).Error; err != nil {
t.Fatalf("failed to find distribution_site_account: %v", err)
}
if dsa.DisplayStatus != "enabled" {
t.Fatalf("expected enabled, got %s", dsa.DisplayStatus)
}
// 验证审核工作台列表展示分销站来源
pending, err := repo.ListPendingReview(context.Background())
if err != nil {
t.Fatalf("ListPendingReview failed: %v", err)
}
if len(pending) != 1 {
t.Fatalf("expected 1 pending item, got len=%d", len(pending))
}
if pending[0].SourceChannel != expectedSource {
t.Fatalf("expected pending SourceChannel=%s, got %s", expectedSource, pending[0].SourceChannel)
}
}
+15 -4
View File
@@ -3,6 +3,8 @@ package order
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"hfb_sys/backend/internal/model"
@@ -146,13 +148,22 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
func orderAccountSourceSnapshot(tx *gorm.DB, listingID uint64) (string, string, error) {
var upload model.ListingUpload
err := tx.Where("listing_id = ?", listingID).Order("id DESC").First(&upload).Error
if err == gorm.ErrRecordNotFound {
return "internal", "", nil
if err == nil {
return "external", upload.SourceChannel, nil
}
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return "", "", err
}
return "external", upload.SourceChannel, nil
var listing model.RentalListing
if err := tx.Select("distribution_site_id").First(&listing, listingID).Error; err == nil && listing.DistributionSiteID != nil {
var site model.DistributionSite
if err := tx.Select("site_code, site_name").First(&site, *listing.DistributionSiteID).Error; err == nil {
return "distribution", fmt.Sprintf("分销站 - %s (%s)", site.SiteName, site.SiteCode), nil
}
}
return "internal", "", nil
}
func listingHandoffMode(listing model.RentalListing) string {
@@ -0,0 +1,46 @@
-- +goose Up
-- 1. rental_listings 表新增 distribution_site_id 并添加索引
SET @sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'rental_listings' AND COLUMN_NAME = 'distribution_site_id') = 0,
'ALTER TABLE rental_listings ADD COLUMN distribution_site_id BIGINT UNSIGNED NULL AFTER managed_admin_id',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'rental_listings' AND INDEX_NAME = 'idx_rental_listings_distribution_site') = 0,
'ALTER TABLE rental_listings ADD KEY idx_rental_listings_distribution_site (distribution_site_id, status)',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 2. 回填历史分销账号的归属站点
UPDATE rental_listings l
JOIN distribution_site_accounts dsa ON dsa.listing_id = l.id
SET l.distribution_site_id = dsa.site_id
WHERE l.distribution_site_id IS NULL;
-- +goose Down
SET @sql = IF(
(SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'rental_listings' AND INDEX_NAME = 'idx_rental_listings_distribution_site') > 0,
'ALTER TABLE rental_listings DROP KEY idx_rental_listings_distribution_site',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = IF(
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'rental_listings' AND COLUMN_NAME = 'distribution_site_id') > 0,
'ALTER TABLE rental_listings DROP COLUMN distribution_site_id',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
@@ -61,7 +61,7 @@ import {
} from '@/shared/utils/listingDisplay'
import { formatDateTime } from '@/shared/utils/time'
type RiskLevel = 'danger' | 'warning' | 'info'
type RiskLevel = 'danger' | 'warning' | 'info' | 'success'
interface RiskItem {
label: string
@@ -598,6 +598,10 @@ function sourceChannel(row: Listing) {
return isExternalUpload(row) ? '未填写' : '站内发布'
}
function isDistributionListing(row: Listing) {
return Boolean(row.distribution_site_id || sourceChannel(row).startsWith('分销站'))
}
function ownerOnlineText(row: Listing) {
const text = row.asset_summary?.online_time_text
if (typeof text === 'string' && text.trim()) return text
@@ -674,6 +678,8 @@ function riskItems(row: Listing): RiskItem[] {
level: 'info',
type: 'general',
})
} else if (isDistributionListing(row)) {
items.push({ label: `来源渠道:${sourceChannel(row)}`, level: 'success', type: 'general' })
} else {
items.push({ label: `来源渠道:${sourceChannel(row)}`, level: 'info', type: 'general' })
}
@@ -712,6 +718,7 @@ function riskItems(row: Listing): RiskItem[] {
function riskTagType(level: RiskLevel) {
if (level === 'danger') return 'danger'
if (level === 'warning') return 'warning'
if (level === 'success') return 'success'
return 'info'
}
@@ -1114,9 +1121,11 @@ async function handleScreenshotSaved(payload: { oldUrl: string; newUrl: string }
<span class="queue-deposit">押 ¥{{ formatCent(item.deposit_amount_cent) }}</span>
</div>
<div class="queue-tags">
<el-tag size="small" type="primary" effect="plain">{{
sourceChannel(item)
}}</el-tag>
<el-tag
size="small"
:type="isDistributionListing(item) ? 'success' : 'primary'"
effect="plain"
>{{ sourceChannel(item) }}</el-tag>
<el-tag v-if="isExternalUpload(item)" size="small" type="info">{{
uploaderName(item)
}}</el-tag>
@@ -30,6 +30,9 @@ export interface Listing {
review_status: ListingReviewStatus
review_reason: string
published_at?: string
distribution_site_id?: number
distribution_site_code?: string
distribution_site_name?: string
listing_group_conversation_id?: number
created_at: string
updated_at: string