From 0a5448b3f5a9261b6b26caf8f0428c5d6050ed77 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 9 Jul 2026 23:31:44 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BA=BF=E4=B8=8B=E6=8F=90=E5=8F=B7=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=B7=E6=A0=BC=E6=8B=86=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/modules/pickup/dto.go | 29 +++--- backend/internal/modules/pickup/repository.go | 97 +++++++++++++------ .../modules/pickup/repository_test.go | 70 +++++++++++++ .../src/features/admin/api/adminPickup.ts | 3 + .../features/admin/views/AdminPickupView.vue | 33 ++++++- 5 files changed, 189 insertions(+), 43 deletions(-) create mode 100644 backend/internal/modules/pickup/repository_test.go diff --git a/backend/internal/modules/pickup/dto.go b/backend/internal/modules/pickup/dto.go index 6d401cf..087b596 100644 --- a/backend/internal/modules/pickup/dto.go +++ b/backend/internal/modules/pickup/dto.go @@ -84,19 +84,22 @@ type SellerPickupQuery struct { } type AvailableListingDTO struct { - ID uint64 `json:"id"` - ListingNo string `json:"listing_no"` - AccountID uint64 `json:"account_id"` - AccountTitle string `json:"account_title"` - ServerRegion string `json:"server_region"` - LoginPlatform string `json:"login_platform"` - OwnerID uint64 `json:"owner_id"` - OwnerPhone string `json:"owner_phone"` - ListingPriceCent int64 `json:"listing_price_cent"` - OwnerPriceCent int64 `json:"owner_price_cent"` - WebsiteProfitCent int64 `json:"website_profit_cent"` - SellerRatio float64 `json:"seller_ratio"` - BuyerRatio float64 `json:"buyer_ratio"` + ID uint64 `json:"id"` + ListingNo string `json:"listing_no"` + AccountID uint64 `json:"account_id"` + AccountTitle string `json:"account_title"` + ServerRegion string `json:"server_region"` + LoginPlatform string `json:"login_platform"` + OwnerID uint64 `json:"owner_id"` + OwnerPhone string `json:"owner_phone"` + ListingPriceCent int64 `json:"listing_price_cent"` + OwnerPriceCent int64 `json:"owner_price_cent"` + OwnerPurePriceCent int64 `json:"owner_pure_price_cent"` + OwnerExtraItemPriceCent int64 `json:"owner_extra_item_price_cent"` + OwnerTotalPriceCent int64 `json:"owner_total_price_cent"` + WebsiteProfitCent int64 `json:"website_profit_cent"` + SellerRatio float64 `json:"seller_ratio"` + BuyerRatio float64 `json:"buyer_ratio"` } type PaginatedResult struct { diff --git a/backend/internal/modules/pickup/repository.go b/backend/internal/modules/pickup/repository.go index 3a9a685..7670771 100644 --- a/backend/internal/modules/pickup/repository.go +++ b/backend/internal/modules/pickup/repository.go @@ -452,19 +452,22 @@ func (row availableListingRow) toDTO() AvailableListingDTO { model.GameAccount{HafCoinAmount: row.HafCoinAmount, AssetSummary: row.AssetSummary}, ) return AvailableListingDTO{ - ID: row.ID, - ListingNo: row.ListingNo, - AccountID: row.AccountID, - AccountTitle: row.AccountTitle, - ServerRegion: row.ServerRegion, - LoginPlatform: row.LoginPlatform, - OwnerID: row.OwnerID, - OwnerPhone: row.OwnerPhone, - ListingPriceCent: snapshot.ListingPriceCent, - OwnerPriceCent: snapshot.OwnerPriceCent, - WebsiteProfitCent: snapshot.WebsiteProfitCent, - SellerRatio: snapshot.SellerRatio, - BuyerRatio: snapshot.BuyerRatio, + ID: row.ID, + ListingNo: row.ListingNo, + AccountID: row.AccountID, + AccountTitle: row.AccountTitle, + ServerRegion: row.ServerRegion, + LoginPlatform: row.LoginPlatform, + OwnerID: row.OwnerID, + OwnerPhone: row.OwnerPhone, + ListingPriceCent: snapshot.ListingPriceCent, + OwnerPriceCent: snapshot.OwnerPriceCent, + OwnerPurePriceCent: snapshot.OwnerPurePriceCent, + OwnerExtraItemPriceCent: snapshot.OwnerExtraItemPriceCent, + OwnerTotalPriceCent: snapshot.OwnerTotalPriceCent, + WebsiteProfitCent: snapshot.WebsiteProfitCent, + SellerRatio: snapshot.SellerRatio, + BuyerRatio: snapshot.BuyerRatio, } } @@ -506,24 +509,37 @@ func isEmptyPickupAccountSnapshot(raw datatypes.JSON) bool { } type pickupPriceSnapshot struct { - ListingPriceCent int64 - OwnerPriceCent int64 - WebsiteProfitCent int64 - SellerRatio float64 - BuyerRatio float64 + ListingPriceCent int64 + OwnerPriceCent int64 + OwnerPurePriceCent int64 + OwnerExtraItemPriceCent int64 + OwnerTotalPriceCent int64 + WebsiteProfitCent int64 + SellerRatio float64 + BuyerRatio float64 } func buildPickupPriceSnapshot(listing model.RentalListing, account model.GameAccount) pickupPriceSnapshot { summary := decodePickupAssetSummary(account.AssetSummary) breakdown := pickupPriceBreakdown(summary) listingPriceCent := maxPickupCent(listing.PriceCent, 0) - ownerPriceCent := yuanToPickupCent(readPickupNumber(breakdown["seller_total_price"])) - if ownerPriceCent <= 0 || (listingPriceCent > 0 && ownerPriceCent > listingPriceCent) { - ownerPriceCent = listingPriceCent + ownerPurePriceCent := yuanToPickupCent(readPickupNumber(breakdown["seller_coin_base_price"])) + ownerExtraItemPriceCent := yuanToPickupCent(readPickupNumber(breakdown["consumable_price"])) + ownerTotalPriceCent := yuanToPickupCent(readPickupNumber(breakdown["seller_total_price"])) + if ownerTotalPriceCent <= 0 && (ownerPurePriceCent > 0 || ownerExtraItemPriceCent > 0) { + ownerTotalPriceCent = ownerPurePriceCent + ownerExtraItemPriceCent } + if ownerTotalPriceCent <= 0 || (listingPriceCent > 0 && ownerTotalPriceCent > listingPriceCent) { + ownerTotalPriceCent = listingPriceCent + } + ownerPurePriceCent, ownerExtraItemPriceCent = normalizePickupOwnerPriceSplit( + ownerTotalPriceCent, + ownerPurePriceCent, + ownerExtraItemPriceCent, + ) websiteProfitCent := yuanToPickupCent(readPickupNumber(breakdown["platform_markup_amount"])) if websiteProfitCent <= 0 { - websiteProfitCent = listingPriceCent - ownerPriceCent + websiteProfitCent = listingPriceCent - ownerTotalPriceCent } if websiteProfitCent < 0 { websiteProfitCent = 0 @@ -540,14 +556,41 @@ func buildPickupPriceSnapshot(listing model.RentalListing, account model.GameAcc buyerRatio = ratioFromCoin(account.HafCoinAmount, readPickupNumber(breakdown["buyer_coin_base_price"])) } return pickupPriceSnapshot{ - ListingPriceCent: listingPriceCent, - OwnerPriceCent: ownerPriceCent, - WebsiteProfitCent: websiteProfitCent, - SellerRatio: roundPickupRatio(sellerRatio), - BuyerRatio: roundPickupRatio(buyerRatio), + ListingPriceCent: listingPriceCent, + OwnerPriceCent: ownerTotalPriceCent, + OwnerPurePriceCent: ownerPurePriceCent, + OwnerExtraItemPriceCent: ownerExtraItemPriceCent, + OwnerTotalPriceCent: ownerTotalPriceCent, + WebsiteProfitCent: websiteProfitCent, + SellerRatio: roundPickupRatio(sellerRatio), + BuyerRatio: roundPickupRatio(buyerRatio), } } +func normalizePickupOwnerPriceSplit(totalCent, pureCent, extraCent int64) (int64, int64) { + if totalCent <= 0 { + return 0, 0 + } + pureCent = maxPickupCent(pureCent, 0) + extraCent = maxPickupCent(extraCent, 0) + switch { + case pureCent == 0 && extraCent == 0: + pureCent = totalCent + case pureCent == 0: + pureCent = maxPickupCent(totalCent-extraCent, 0) + case extraCent == 0: + extraCent = maxPickupCent(totalCent-pureCent, 0) + case pureCent+extraCent != totalCent: + if pureCent > totalCent { + pureCent = totalCent + extraCent = 0 + } else { + extraCent = totalCent - pureCent + } + } + return pureCent, extraCent +} + func decodePickupAssetSummary(raw datatypes.JSON) map[string]any { if len(raw) == 0 { return map[string]any{} diff --git a/backend/internal/modules/pickup/repository_test.go b/backend/internal/modules/pickup/repository_test.go new file mode 100644 index 0000000..cca4038 --- /dev/null +++ b/backend/internal/modules/pickup/repository_test.go @@ -0,0 +1,70 @@ +package pickup + +import ( + "encoding/json" + "testing" + + "hfb_sys/backend/internal/model" + + "gorm.io/datatypes" +) + +func TestBuildPickupPriceSnapshotSplitsOwnerPrice(t *testing.T) { + raw, err := json.Marshal(map[string]any{ + "price_breakdown": map[string]any{ + "seller_coin_base_price": 233, + "seller_total_price": 353, + "platform_markup_amount": 30, + "consumable_price": 120, + "seller_ratio": 42.9, + "buyer_ratio": 35.1, + }, + }) + if err != nil { + t.Fatalf("构造资产快照失败: %v", err) + } + + snapshot := buildPickupPriceSnapshot( + model.RentalListing{PriceCent: 38300}, + model.GameAccount{HafCoinAmount: 100000000, AssetSummary: datatypes.JSON(raw)}, + ) + + assertInt64(t, "网站售价", snapshot.ListingPriceCent, 38300) + assertInt64(t, "号主纯比价格", snapshot.OwnerPurePriceCent, 23300) + assertInt64(t, "额外物品价格", snapshot.OwnerExtraItemPriceCent, 12000) + assertInt64(t, "号主总价", snapshot.OwnerTotalPriceCent, 35300) + assertInt64(t, "兼容号主价", snapshot.OwnerPriceCent, 35300) + assertInt64(t, "网站加价", snapshot.WebsiteProfitCent, 3000) + if snapshot.SellerRatio != 42.9 || snapshot.BuyerRatio != 35.1 { + t.Fatalf("比例 = %.2f/%.2f, want 42.90/35.10", snapshot.SellerRatio, snapshot.BuyerRatio) + } +} + +func TestBuildPickupPriceSnapshotFallsBackToSplitSum(t *testing.T) { + raw, err := json.Marshal(map[string]any{ + "price_breakdown": map[string]any{ + "seller_coin_base_price": 200, + "consumable_price": 50, + }, + }) + if err != nil { + t.Fatalf("构造资产快照失败: %v", err) + } + + snapshot := buildPickupPriceSnapshot( + model.RentalListing{PriceCent: 30000}, + model.GameAccount{AssetSummary: datatypes.JSON(raw)}, + ) + + assertInt64(t, "拆分合计总价", snapshot.OwnerTotalPriceCent, 25000) + assertInt64(t, "纯比价格", snapshot.OwnerPurePriceCent, 20000) + assertInt64(t, "额外物品价格", snapshot.OwnerExtraItemPriceCent, 5000) + assertInt64(t, "网站加价兜底", snapshot.WebsiteProfitCent, 5000) +} + +func assertInt64(t *testing.T, name string, got, want int64) { + t.Helper() + if got != want { + t.Fatalf("%s = %d, want %d", name, got, want) + } +} diff --git a/frontend/src/features/admin/api/adminPickup.ts b/frontend/src/features/admin/api/adminPickup.ts index b8e6220..8d25025 100644 --- a/frontend/src/features/admin/api/adminPickup.ts +++ b/frontend/src/features/admin/api/adminPickup.ts @@ -41,6 +41,9 @@ export interface AvailableListing { owner_phone: string listing_price_cent: number owner_price_cent: number + owner_pure_price_cent: number + owner_extra_item_price_cent: number + owner_total_price_cent: number website_profit_cent: number seller_ratio: number buyer_ratio: number diff --git a/frontend/src/features/admin/views/AdminPickupView.vue b/frontend/src/features/admin/views/AdminPickupView.vue index 1fb3f54..af21b0c 100644 --- a/frontend/src/features/admin/views/AdminPickupView.vue +++ b/frontend/src/features/admin/views/AdminPickupView.vue @@ -38,6 +38,21 @@ const listingLoading = ref(false) const selectedListing = computed( () => listingOptions.value.find(item => item.id === createForm.listing_id) || null ) +const selectedPriceSnapshot = computed(() => { + const item = selectedListing.value + if (!item) return null + const totalCent = normalizeCent(item.owner_total_price_cent || item.owner_price_cent) + let pureCent = normalizeCent(item.owner_pure_price_cent) + let extraCent = normalizeCent(item.owner_extra_item_price_cent) + if (pureCent <= 0 && extraCent <= 0) { + pureCent = totalCent + } else if (pureCent <= 0) { + pureCent = Math.max(totalCent - extraCent, 0) + } else if (extraCent <= 0) { + extraCent = Math.max(totalCent - pureCent, 0) + } + return { pureCent, extraCent, totalCent } +}) onMounted(loadList) @@ -193,6 +208,16 @@ function ratioText(value: number) { const rounded = Math.round(ratio * 100) / 100 return `1:${Number.isInteger(rounded) ? rounded : rounded.toFixed(2)}` } + +function normalizeCent(value: number | undefined | null) { + const cent = Number(value || 0) + if (!Number.isFinite(cent)) return 0 + return Math.max(Math.round(cent), 0) +} + +function listingOwnerTotalCent(item: AvailableListing) { + return normalizeCent(item.owner_total_price_cent || item.owner_price_cent) +}