feat(listings): 订单和账号详情展示可租时长与每日消耗

租客在订单列表和详情标题能看到每个号的租期、消耗。账号详情把这两项拆成独立格子。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-10-05 16:32:08 +08:00
co-authored by Cursor
parent c559287ee4
commit f2060c19cf
18 changed files with 393 additions and 71 deletions
+2
View File
@@ -96,6 +96,8 @@ type UserOrderListItemDTO struct {
Title string `json:"title"`
ServerRegion string `json:"server_region"`
LoginPlatform string `json:"login_platform"`
RentalDays int `json:"rental_days,omitempty"`
DailyLossM float64 `json:"daily_loss_m,omitempty"`
DisplayAmountCent int64 `json:"display_amount_cent"`
DepositAmountCent int64 `json:"deposit_amount_cent"`
DepositWaivedAmountCent int64 `json:"deposit_waived_amount_cent"`
@@ -179,6 +179,7 @@ func (row orderRow) toUserListItem(userID uint64, paymentDeadlineAt *time.Time)
if role == "owner" && row.OwnerRentAmountCent > 0 {
displayAmountCent = row.OwnerRentAmountCent
}
rentalDays, dailyLossM := orderListingTerms(row.AccountSnapshot)
return UserOrderListItemDTO{
ID: row.ID,
OrderNo: row.OrderNo,
@@ -188,6 +189,8 @@ func (row orderRow) toUserListItem(userID uint64, paymentDeadlineAt *time.Time)
Title: row.Title,
ServerRegion: row.ServerRegion,
LoginPlatform: row.LoginPlatform,
RentalDays: rentalDays,
DailyLossM: dailyLossM,
DisplayAmountCent: displayAmountCent,
DepositAmountCent: row.DepositAmountCent,
DepositWaivedAmountCent: row.DepositWaivedAmountCent,
@@ -7,6 +7,8 @@ import (
"time"
"hfb_sys/backend/internal/model"
"gorm.io/datatypes"
)
func TestOrderDTOForUserHidesDepositHoldFields(t *testing.T) {
@@ -72,6 +74,44 @@ func TestUserOrderListItemDoesNotSerializeDetailFields(t *testing.T) {
}
}
func TestUserOrderListItemIncludesRentalTerms(t *testing.T) {
row := orderRow{
RentalOrder: model.RentalOrder{
AccountSnapshot: datatypes.JSON([]byte(`{"haf_coin_amount":100000000,"asset_summary":{"daily_loss_m":10}}`)),
},
Title: "微信 三角洲巅峰 100M哈夫币",
}
item := row.toUserListItem(20, nil)
if item.RentalDays != 10 {
t.Fatalf("rental days = %d, want 10", item.RentalDays)
}
if item.DailyLossM != 10 {
t.Fatalf("daily loss = %v, want 10", item.DailyLossM)
}
raw, err := json.Marshal(item)
if err != nil {
t.Fatalf("Marshal() error = %v", err)
}
text := string(raw)
if !strings.Contains(text, `"rental_days":10`) || !strings.Contains(text, `"daily_loss_m":10`) {
t.Fatalf("list response missing rental terms: %s", text)
}
}
func TestUserOrderListItemOmitsRentalTermsWithoutSnapshot(t *testing.T) {
item := orderRow{Title: "测试账号"}.toUserListItem(20, nil)
raw, err := json.Marshal(item)
if err != nil {
t.Fatalf("Marshal() error = %v", err)
}
text := string(raw)
if strings.Contains(text, "rental_days") || strings.Contains(text, "daily_loss_m") {
t.Fatalf("empty snapshot should omit rental terms: %s", text)
}
}
func TestAdminOrderListItemShowsPhonesAndOmitsSnapshot(t *testing.T) {
row := orderRow{
RentalOrder: model.RentalOrder{ID: 1, OrderNo: "ORD-001", Status: orderStatusPendingPayment},
+14
View File
@@ -124,6 +124,20 @@ func orderEstimatedEndAt(order model.RentalOrder) *time.Time {
return &endAt
}
// orderListingTerms 用下单快照里的哈夫币和日损耗估算租期,和首页「可租 / 消耗」同一口径。
func orderListingTerms(snapshot datatypes.JSON) (days int, dailyLossM float64) {
dailyLossM = readOrderSnapshotAssetNumber(snapshot, "daily_loss_m")
coinM := readOrderSnapshotCoinM(snapshot)
if dailyLossM <= 0 || coinM <= 0 {
return 0, 0
}
days = int(math.Ceil(coinM / dailyLossM))
if days < 1 {
days = 1
}
return days, roundQuantity(dailyLossM)
}
func estimateOrderDurationHours(snapshot datatypes.JSON) int {
hafCoinM := readOrderSnapshotCoinM(snapshot)
if hafCoinM <= 0 {