From 5f6a4afcbbad627c389217a7942f7e5e78e8f585 Mon Sep 17 00:00:00 2001 From: yml Date: Sun, 24 May 2026 20:49:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=BB=E5=BA=95=E4=BC=98=E5=8C=96=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/jobs/ordertimeout/job.go | 5 +- backend/internal/model/dispute.go | 2 +- backend/internal/model/listing.go | 3 - backend/internal/model/order.go | 6 +- backend/internal/model/order_checkout.go | 4 +- .../internal/modules/dispute/repository.go | 17 ++- backend/internal/modules/listing/dto.go | 6 - backend/internal/modules/listing/handler.go | 2 + .../internal/modules/listing/repository.go | 76 +--------- backend/internal/modules/listing/service.go | 87 ++++++++++++ .../internal/modules/listing/service_test.go | 42 ++++++ backend/internal/modules/order/dto.go | 5 +- backend/internal/modules/order/repository.go | 109 ++++++++++----- backend/migrations/000001_init.sql | 40 ++++-- .../000002_order_payment_stepwise.sql | 132 ------------------ frontend/src/api/listings.ts | 6 - frontend/src/api/orders.ts | 5 +- .../src/views/account/OrderDetailView.vue | 10 +- .../views/admin/AdminListingDetailView.vue | 2 +- .../views/admin/AdminListingReviewView.vue | 2 +- .../src/views/admin/AdminListingsView.vue | 2 +- .../src/views/admin/AdminOrderDetailView.vue | 7 +- .../mobile/MobileSellerListingCreateView.vue | 7 +- frontend/src/views/mobile/listingDisplay.ts | 2 +- .../src/views/public/ListingDetailView.vue | 2 +- .../views/seller/SellerListingCreateView.vue | 3 - .../src/views/seller/SellerListingsView.vue | 2 +- scripts/dev.sh | 11 +- 28 files changed, 295 insertions(+), 302 deletions(-) create mode 100644 backend/internal/modules/listing/service_test.go delete mode 100644 backend/migrations/000002_order_payment_stepwise.sql diff --git a/backend/internal/jobs/ordertimeout/job.go b/backend/internal/jobs/ordertimeout/job.go index cceb01b..7951200 100644 --- a/backend/internal/jobs/ordertimeout/job.go +++ b/backend/internal/jobs/ordertimeout/job.go @@ -298,10 +298,7 @@ func (j *Job) handleReturnOverdue(ctx context.Context, now time.Time, cfg thresh var rows []model.RentalOrder overdueBefore := now.Add(-time.Duration(cfg.ReturnOverdueGraceMinutes) * time.Minute) err := j.db.WithContext(ctx). - Where(`status = ? AND ( - (rented_at IS NOT NULL AND TIMESTAMPADD(HOUR, COALESCE(NULLIF(estimated_duration_hours, 0), 24), rented_at) <= ?) - OR (rented_at IS NULL AND rent_end_at IS NOT NULL AND rent_end_at <= ?) - )`, "renting", overdueBefore, overdueBefore). + Where(`status = ? AND rented_at IS NOT NULL AND TIMESTAMPADD(HOUR, COALESCE(NULLIF(estimated_duration_hours, 0), 24), rented_at) <= ?`, "renting", overdueBefore). Order("id ASC"). Limit(100). Find(&rows).Error diff --git a/backend/internal/model/dispute.go b/backend/internal/model/dispute.go index aa261f7..44dc1e4 100644 --- a/backend/internal/model/dispute.go +++ b/backend/internal/model/dispute.go @@ -14,7 +14,7 @@ type Dispute struct { Type string `gorm:"size:32;not null" json:"type"` Status string `gorm:"size:32;not null;default:'open'" json:"status"` Description string `json:"description"` - EvidenceURLS datatypes.JSON `json:"evidence_urls"` + EvidenceURLS datatypes.JSON `gorm:"column:evidence_urls" json:"evidence_urls"` ArbitrationResult string `gorm:"size:32;not null;default:''" json:"arbitration_result"` ArbitrationRemark string `json:"arbitration_remark"` HandledBy *uint64 `json:"handled_by"` diff --git a/backend/internal/model/listing.go b/backend/internal/model/listing.go index bf25749..6cdac67 100644 --- a/backend/internal/model/listing.go +++ b/backend/internal/model/listing.go @@ -33,9 +33,6 @@ type RentalListing struct { AccountID uint64 `gorm:"not null;index" json:"account_id"` OwnerID uint64 `gorm:"not null;index" json:"owner_id"` Price float64 `gorm:"type:decimal(12,2);not null;default:0" json:"price"` - PriceHourly float64 `gorm:"type:decimal(12,2);not null;default:0" json:"price_hourly"` - PriceDaily float64 `gorm:"type:decimal(12,2);not null;default:0" json:"price_daily"` - PriceWeekly float64 `gorm:"type:decimal(12,2);not null;default:0" json:"price_weekly"` DepositAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"deposit_amount"` InTransaction bool `gorm:"not null;default:false" json:"in_transaction"` Status string `gorm:"size:32;not null;default:'draft'" json:"status"` diff --git a/backend/internal/model/order.go b/backend/internal/model/order.go index 4822c65..d4d1a59 100644 --- a/backend/internal/model/order.go +++ b/backend/internal/model/order.go @@ -15,14 +15,12 @@ type RentalOrder struct { RenterID uint64 `gorm:"not null;index" json:"renter_id"` RentedAt *time.Time `json:"rented_at"` EstimatedDurationHours int `gorm:"not null;default:24" json:"estimated_duration_hours"` - RentStartAt *time.Time `json:"rent_start_at"` - RentEndAt *time.Time `json:"rent_end_at"` - RentHours int `gorm:"not null" json:"rent_hours"` RentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"rent_amount"` + OwnerRentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"owner_rent_amount"` DepositAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"deposit_amount"` PlatformFee float64 `gorm:"type:decimal(12,2);not null;default:0" json:"platform_fee"` AccountSnapshot datatypes.JSON `json:"account_snapshot"` - Status string `gorm:"size:32;not null;default:'pending_confirm'" json:"status"` + Status string `gorm:"size:32;not null;default:'pending_payment'" json:"status"` HandoffStatus string `gorm:"size:32;not null;default:'none'" json:"handoff_status"` SettlementStatus string `gorm:"size:32;not null;default:'unsettled'" json:"settlement_status"` OwnerSettledAt *time.Time `json:"owner_settled_at"` diff --git a/backend/internal/model/order_checkout.go b/backend/internal/model/order_checkout.go index df6e448..2ef652b 100644 --- a/backend/internal/model/order_checkout.go +++ b/backend/internal/model/order_checkout.go @@ -12,6 +12,8 @@ type OrderCheckout struct { InitiatedBy uint64 `gorm:"not null" json:"initiated_by"` Status string `gorm:"size:32;not null;default:'submitted'" json:"status"` RentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"rent_amount"` + OwnerRentAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"owner_rent_amount"` + PlatformFee float64 `gorm:"type:decimal(12,2);not null;default:0" json:"platform_fee"` DepositAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"deposit_amount"` ConsumableAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"consumable_amount"` CoinConsumedM float64 `gorm:"type:decimal(12,2);not null;default:0" json:"coin_consumed_m"` @@ -20,7 +22,7 @@ type OrderCheckout struct { RenterRefundAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"renter_refund_amount"` OwnerIncomeAmount float64 `gorm:"type:decimal(12,2);not null;default:0" json:"owner_income_amount"` Content string `json:"content"` - EvidenceURLS datatypes.JSON `json:"evidence_urls"` + EvidenceURLS datatypes.JSON `gorm:"column:evidence_urls" json:"evidence_urls"` OwnerAdjustmentReason string `json:"owner_adjustment_reason"` OwnerAdjustedAt *time.Time `json:"owner_adjusted_at"` RenterConfirmedAt *time.Time `json:"renter_confirmed_at"` diff --git a/backend/internal/modules/dispute/repository.go b/backend/internal/modules/dispute/repository.go index 94a7cdc..7982005 100644 --- a/backend/internal/modules/dispute/repository.go +++ b/backend/internal/modules/dispute/repository.go @@ -303,6 +303,10 @@ type arbitrationSettlement struct { func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (arbitrationSettlement, error) { total := order.RentAmount + order.DepositAmount + ownerRentAmount := order.OwnerRentAmount + if ownerRentAmount <= 0 || ownerRentAmount > order.RentAmount { + ownerRentAmount = order.RentAmount + } settlement := arbitrationSettlement{} orderID := order.ID if total > 0 { @@ -359,9 +363,9 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) ( return settlement, ErrInvalidDispute } addRenterRefund(req.Amount, "仲裁部分退款") - addOwnerIncome(total-req.Amount, "仲裁剩余金额结算给号主") + addOwnerIncome(minMoney(total-req.Amount, ownerRentAmount+order.DepositAmount), "仲裁剩余金额结算给号主") case "release_deposit": - addOwnerIncome(order.RentAmount, "仲裁确认订单金额结算给号主") + addOwnerIncome(ownerRentAmount, "仲裁确认订单金额结算给号主") addRenterRefund(order.DepositAmount, "仲裁释放押金给租客") case "deduct_deposit", "compensate_owner": deductAmount := req.Amount @@ -372,7 +376,7 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) ( return settlement, ErrInvalidDispute } settlement.DepositDeductAmount = deductAmount - addOwnerIncome(order.RentAmount+deductAmount, "仲裁订单金额及押金赔付结算给号主") + addOwnerIncome(ownerRentAmount+deductAmount, "仲裁订单金额及押金赔付结算给号主") addRenterRefund(order.DepositAmount-deductAmount, "仲裁退回剩余押金给租客") case "order_close": // Only release frozen funds. No available-balance settlement happens in development mode. @@ -384,6 +388,13 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) ( return settlement, nil } +func minMoney(a float64, b float64) float64 { + if a < b { + return a + } + return b +} + func (r *Repository) baseQuery() *gorm.DB { return r.db.Table("disputes AS d"). Select("d.*, o.order_no, a.title"). diff --git a/backend/internal/modules/listing/dto.go b/backend/internal/modules/listing/dto.go index 9fdd3b4..4063d98 100644 --- a/backend/internal/modules/listing/dto.go +++ b/backend/internal/modules/listing/dto.go @@ -19,9 +19,6 @@ type ListingDTO struct { ScreenshotURLS []string `json:"screenshot_urls"` CoverURL string `json:"cover_url"` Price float64 `json:"price"` - PriceHourly float64 `json:"price_hourly"` - PriceDaily float64 `json:"price_daily"` - PriceWeekly float64 `json:"price_weekly"` DepositAmount float64 `json:"deposit_amount"` IsAccelerated bool `json:"is_accelerated_sale"` InTransaction bool `json:"in_transaction"` @@ -43,9 +40,6 @@ type CreateRequest struct { AssetSummary map[string]any `json:"asset_summary"` ScreenshotURLS []string `json:"screenshot_urls"` Price float64 `json:"price"` - PriceHourly float64 `json:"price_hourly"` - PriceDaily float64 `json:"price_daily"` - PriceWeekly float64 `json:"price_weekly"` DepositAmount float64 `json:"deposit_amount"` } diff --git a/backend/internal/modules/listing/handler.go b/backend/internal/modules/listing/handler.go index 528fbc3..86f97de 100644 --- a/backend/internal/modules/listing/handler.go +++ b/backend/internal/modules/listing/handler.go @@ -364,6 +364,8 @@ func writeListingError(c *gin.Context, err error) { response.BadRequest(c, "发布价格不正确") case errors.Is(err, ErrInvalidDeposit): response.BadRequest(c, "押金不能小于 0") + case errors.Is(err, ErrDepositTooLow): + response.BadRequest(c, "押金必须大于额外消耗品总价值") case errors.Is(err, ErrInvalidHafCoin): response.BadRequest(c, "哈夫币数量不正确") case errors.Is(err, ErrMissingScreenshot): diff --git a/backend/internal/modules/listing/repository.go b/backend/internal/modules/listing/repository.go index cf17f2d..5d8c02f 100644 --- a/backend/internal/modules/listing/repository.go +++ b/backend/internal/modules/listing/repository.go @@ -65,9 +65,6 @@ func (r *Repository) Create(ownerID uint64, req CreateRequest, reviewRequired bo AccountID: account.ID, OwnerID: ownerID, Price: price, - PriceHourly: listingHourlyPrice(req, price), - PriceDaily: listingDailyPrice(req, price), - PriceWeekly: listingWeeklyPrice(req, price), DepositAmount: req.DepositAmount, Status: listingStatus, ReviewStatus: reviewStatus, @@ -112,9 +109,6 @@ func (r *Repository) Update(ownerID uint64, listingID uint64, req UpdateRequest, price := normalizedListingPrice(req) listing.Price = price - listing.PriceHourly = listingHourlyPrice(req, price) - listing.PriceDaily = listingDailyPrice(req, price) - listing.PriceWeekly = listingWeeklyPrice(req, price) listing.DepositAmount = req.DepositAmount listingStatus, reviewStatus, publishedAt := initialPublishState(reviewRequired) listing.Status = listingStatus @@ -489,63 +483,7 @@ func rowsToDTO(rows []listingRow) []ListingDTO { } func normalizedListingPrice(req CreateRequest) float64 { - if req.Price > 0 { - return req.Price - } - if req.PriceDaily > 0 { - return req.PriceDaily - } - if req.PriceHourly > 0 { - return req.PriceHourly * 24 - } - if req.PriceWeekly > 0 { - return req.PriceWeekly / 7 - } - return 0 -} - -func listingHourlyPrice(req CreateRequest, price float64) float64 { - if req.PriceHourly > 0 { - return req.PriceHourly - } - return price / 24 -} - -func listingDailyPrice(req CreateRequest, price float64) float64 { - if req.PriceDaily > 0 { - return req.PriceDaily - } - return price -} - -func listingWeeklyPrice(req CreateRequest, price float64) float64 { - if req.PriceWeekly > 0 { - return req.PriceWeekly - } - return price * 7 -} - -func listingDisplayPrices(price float64, hourly float64, daily float64, weekly float64) (float64, float64, float64, float64) { - if price <= 0 { - switch { - case daily > 0: - price = daily - case hourly > 0: - price = hourly * 24 - case weekly > 0: - price = weekly / 7 - } - } - if daily <= 0 { - daily = price - } - if hourly <= 0 && price > 0 { - hourly = price / 24 - } - if weekly <= 0 && price > 0 { - weekly = price * 7 - } - return price, hourly, daily, weekly + return req.Price } func publicListings(items []ListingDTO) []ListingDTO { @@ -565,7 +503,6 @@ func applyPublicListingURLs(item *ListingDTO) { func (row listingRow) toDTO() ListingDTO { assetSummary := decodeAssetSummary(row.AssetSummary) screenshotURLS := cleanScreenshotURLs(decodeScreenshots(row.ScreenshotURLS)) - price, priceHourly, priceDaily, priceWeekly := listingDisplayPrices(row.Price, row.PriceHourly, row.PriceDaily, row.PriceWeekly) return ListingDTO{ ID: row.ID, AccountID: row.AccountID, @@ -582,10 +519,7 @@ func (row listingRow) toDTO() ListingDTO { AssetSummary: assetSummary, ScreenshotURLS: screenshotURLS, CoverURL: publicCoverURL(row.ID, screenshotURLS, row.Status, row.ReviewStatus), - Price: price, - PriceHourly: priceHourly, - PriceDaily: priceDaily, - PriceWeekly: priceWeekly, + Price: row.Price, DepositAmount: row.DepositAmount, IsAccelerated: isAcceleratedSale(assetSummary), InTransaction: row.InTransaction, @@ -601,7 +535,6 @@ func (row listingRow) toDTO() ListingDTO { func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO { assetSummary := decodeAssetSummary(account.AssetSummary) screenshotURLS := cleanScreenshotURLs(decodeScreenshots(account.ScreenshotURLS)) - price, priceHourly, priceDaily, priceWeekly := listingDisplayPrices(listing.Price, listing.PriceHourly, listing.PriceDaily, listing.PriceWeekly) return &ListingDTO{ ID: listing.ID, AccountID: account.ID, @@ -616,10 +549,7 @@ func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO { AssetSummary: assetSummary, ScreenshotURLS: screenshotURLS, CoverURL: publicCoverURL(listing.ID, screenshotURLS, listing.Status, listing.ReviewStatus), - Price: price, - PriceHourly: priceHourly, - PriceDaily: priceDaily, - PriceWeekly: priceWeekly, + Price: listing.Price, DepositAmount: listing.DepositAmount, IsAccelerated: isAcceleratedSale(assetSummary), InTransaction: listing.InTransaction, diff --git a/backend/internal/modules/listing/service.go b/backend/internal/modules/listing/service.go index 0eb364a..8ee51d2 100644 --- a/backend/internal/modules/listing/service.go +++ b/backend/internal/modules/listing/service.go @@ -3,6 +3,8 @@ package listing import ( "encoding/json" "errors" + "math" + "regexp" "strconv" "strings" ) @@ -15,6 +17,7 @@ var ( ErrMissingServerRegion = errors.New("missing server region") ErrInvalidPrice = errors.New("invalid listing price") ErrInvalidDeposit = errors.New("invalid listing deposit") + ErrDepositTooLow = errors.New("listing deposit too low") ErrInvalidHafCoin = errors.New("invalid haf coin amount") ErrMissingScreenshot = errors.New("missing screenshot") ) @@ -34,6 +37,8 @@ const ( defaultFireLevelMin = 38 ) +var priceNumberPattern = regexp.MustCompile(`(\d+(?:\.\d+)?)`) + type FireLevelTooLowError struct { Min int } @@ -217,6 +222,9 @@ func validateRequest(req CreateRequest, rules publishRules) error { if req.DepositAmount < 0 { return ErrInvalidDeposit } + if consumables := consumableValue(req.AssetSummary); consumables > 0 && req.DepositAmount <= consumables { + return ErrDepositTooLow + } if req.HafCoinAmount < 0 { return ErrInvalidHafCoin } @@ -229,6 +237,85 @@ func validateRequest(req CreateRequest, rules publishRules) error { return nil } +func consumableValue(summary map[string]any) float64 { + if summary == nil { + return 0 + } + rawResources, ok := summary["resources"] + if !ok { + return 0 + } + resources, ok := rawResources.([]any) + if !ok { + return 0 + } + total := 0.0 + for _, raw := range resources { + resource, ok := raw.(map[string]any) + if !ok { + continue + } + mode, _ := resource["mode"].(string) + if strings.TrimSpace(mode) != "收费" { + continue + } + quantity := readSummaryFloat(resource["quantity"]) + if quantity <= 0 { + continue + } + priceText, _ := resource["price"].(string) + total += quantity * readUnitPrice(priceText) + } + return roundMoney(total) +} + +func readSummaryFloat(value any) float64 { + switch current := value.(type) { + case float64: + return current + case int: + return float64(current) + case int64: + return float64(current) + case json.Number: + parsed, err := current.Float64() + if err != nil { + return 0 + } + return parsed + case string: + parsed, err := strconv.ParseFloat(strings.TrimSpace(current), 64) + if err != nil { + return 0 + } + return parsed + default: + return 0 + } +} + +func readUnitPrice(priceText string) float64 { + numbers := priceNumberPattern.FindAllString(priceText, -1) + if len(numbers) == 0 { + return 0 + } + amount, err := strconv.ParseFloat(numbers[0], 64) + if err != nil { + return 0 + } + if len(numbers) >= 2 { + count, err := strconv.ParseFloat(numbers[1], 64) + if err == nil && count > 0 { + return amount / count + } + } + return amount +} + +func roundMoney(value float64) float64 { + return math.Round(value*100) / 100 +} + func readFireLevel(summary map[string]any) (int, bool) { if summary == nil { return 0, false diff --git a/backend/internal/modules/listing/service_test.go b/backend/internal/modules/listing/service_test.go new file mode 100644 index 0000000..74543e2 --- /dev/null +++ b/backend/internal/modules/listing/service_test.go @@ -0,0 +1,42 @@ +package listing + +import "testing" + +func TestConsumableValueOnlyCountsChargedResources(t *testing.T) { + value := consumableValue(map[string]any{ + "resources": []any{ + map[string]any{"mode": "收费", "quantity": float64(5), "price": "0.1元/个"}, + map[string]any{"mode": "赠送", "quantity": float64(3), "price": "0.6元/个"}, + map[string]any{"mode": "收费", "quantity": float64(2), "price": "1元/2个"}, + }, + }) + + if value != 1.5 { + t.Fatalf("expected 1.5, got %.2f", value) + } +} + +func TestValidateRequestRequiresDepositAboveConsumables(t *testing.T) { + req := CreateRequest{ + Title: "测试账号", + ServerRegion: "烽火地带", + Price: 100, + DepositAmount: 1.5, + HafCoinAmount: 1000000, + ScreenshotURLS: []string{"https://example.com/a.png"}, + AssetSummary: map[string]any{ + "resources": []any{ + map[string]any{"mode": "收费", "quantity": float64(2), "price": "1元/个"}, + }, + }, + } + + if err := validateRequest(req, publishRules{}); err != ErrDepositTooLow { + t.Fatalf("expected ErrDepositTooLow, got %v", err) + } + + req.DepositAmount = 2.01 + if err := validateRequest(req, publishRules{}); err != nil { + t.Fatalf("expected valid request, got %v", err) + } +} diff --git a/backend/internal/modules/order/dto.go b/backend/internal/modules/order/dto.go index e434330..dd52fe1 100644 --- a/backend/internal/modules/order/dto.go +++ b/backend/internal/modules/order/dto.go @@ -20,9 +20,8 @@ type OrderDTO struct { LoginPlatform string `json:"login_platform"` RentedAt *time.Time `json:"rented_at"` EstimatedDurationHours int `json:"estimated_duration_hours"` - RentStartAt *time.Time `json:"rent_start_at"` - RentEndAt *time.Time `json:"rent_end_at"` RentAmount float64 `json:"rent_amount"` + OwnerRentAmount float64 `json:"owner_rent_amount"` DepositAmount float64 `json:"deposit_amount"` PlatformFee float64 `json:"platform_fee"` AccountSnapshot datatypes.JSON `json:"account_snapshot"` @@ -90,6 +89,8 @@ type CheckoutDTO struct { InitiatedBy uint64 `json:"initiated_by"` Status string `json:"status"` RentAmount float64 `json:"rent_amount"` + OwnerRentAmount float64 `json:"owner_rent_amount"` + PlatformFee float64 `json:"platform_fee"` DepositAmount float64 `json:"deposit_amount"` ConsumableAmount float64 `json:"consumable_amount"` CoinConsumedM float64 `json:"coin_consumed_m"` diff --git a/backend/internal/modules/order/repository.go b/backend/internal/modules/order/repository.go index 2a19edb..0575959 100644 --- a/backend/internal/modules/order/repository.go +++ b/backend/internal/modules/order/repository.go @@ -28,31 +28,81 @@ func NewRepository(db *gorm.DB) *Repository { return &Repository{db: db} } -func listingOrderPrice(listing model.RentalListing) float64 { - switch { - case listing.Price > 0: - return listing.Price - case listing.PriceDaily > 0: - return listing.PriceDaily - case listing.PriceHourly > 0: - return listing.PriceHourly * float64(internalOrderHours) - case listing.PriceWeekly > 0: - return listing.PriceWeekly / 7 - default: - return 0 - } +type orderPricing struct { + RentAmount float64 + OwnerRentAmount float64 + PlatformFee float64 } func orderDurationHours(order model.RentalOrder) int { if order.EstimatedDurationHours > 0 { return order.EstimatedDurationHours } - if order.RentHours > 0 { - return order.RentHours - } return internalOrderHours } +func buildOrderPricing(listing model.RentalListing, account model.GameAccount) orderPricing { + rentAmount := roundMoney(listing.Price) + ownerRentAmount := readSnapshotPrice(account.AssetSummary, "seller_total_price") + if ownerRentAmount <= 0 || ownerRentAmount > rentAmount { + ownerRentAmount = rentAmount + } + platformFee := readSnapshotPrice(account.AssetSummary, "platform_markup_amount") + if platformFee <= 0 || roundMoney(ownerRentAmount+platformFee) != rentAmount { + platformFee = roundMoney(rentAmount - ownerRentAmount) + } + if platformFee < 0 { + platformFee = 0 + } + return orderPricing{ + RentAmount: rentAmount, + OwnerRentAmount: roundMoney(ownerRentAmount), + PlatformFee: roundMoney(platformFee), + } +} + +func readSnapshotPrice(raw datatypes.JSON, key string) float64 { + if len(raw) == 0 { + return 0 + } + var summary map[string]any + if err := json.Unmarshal(raw, &summary); err != nil { + return 0 + } + breakdown, ok := summary["price_breakdown"].(map[string]any) + if !ok { + return 0 + } + return readJSONNumber(breakdown[key]) +} + +func readJSONNumber(value any) float64 { + switch typed := value.(type) { + case float64: + return typed + case float32: + return float64(typed) + case int: + return float64(typed) + case int64: + return float64(typed) + case json.Number: + number, err := typed.Float64() + if err != nil { + return 0 + } + return number + case string: + number, err := strconv.ParseFloat(typed, 64) + if err != nil { + return 0 + } + return number + default: + return 0 + } +} + func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, error) { var createdID uint64 err := r.db.Transaction(func(tx *gorm.DB) error { @@ -80,6 +130,7 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro return err } rentHours := internalOrderHours + pricing := buildOrderPricing(listing, account) order := model.RentalOrder{ OrderNo: orderNo, ListingID: listing.ID, @@ -87,10 +138,10 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro OwnerID: listing.OwnerID, RenterID: renterID, EstimatedDurationHours: rentHours, - RentHours: rentHours, - RentAmount: listingOrderPrice(listing), + RentAmount: pricing.RentAmount, + OwnerRentAmount: pricing.OwnerRentAmount, DepositAmount: listing.DepositAmount, - PlatformFee: 0, + PlatformFee: pricing.PlatformFee, AccountSnapshot: snapshot, Status: "pending_payment", HandoffStatus: "none", @@ -370,10 +421,7 @@ func (r *Repository) ConfirmReceive(userID uint64, orderID uint64) error { order.HandoffStatus = "received" durationHours := orderDurationHours(order) order.EstimatedDurationHours = durationHours - order.RentStartAt = &now order.RentedAt = &now - rentEnd := now.Add(time.Duration(durationHours) * time.Hour) - order.RentEndAt = &rentEnd orderID := order.ID if err := notification.Append(tx, notification.Entry{ UserID: order.OwnerID, @@ -983,13 +1031,15 @@ func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, c InitiatedBy: initiatedBy, Status: status, RentAmount: order.RentAmount, + OwnerRentAmount: order.OwnerRentAmount, + PlatformFee: order.PlatformFee, DepositAmount: order.DepositAmount, ConsumableAmount: roundMoney(consumableAmount), CoinConsumedM: roundMoney(coinConsumedM), OtherAmount: roundMoney(otherAmount), DepositDeductAmount: roundMoney(deductAmount), RenterRefundAmount: roundMoney(renterRefund), - OwnerIncomeAmount: roundMoney(order.RentAmount + deductAmount), + OwnerIncomeAmount: roundMoney(order.OwnerRentAmount + deductAmount), Content: content, EvidenceURLS: evidence, }, nil @@ -1068,15 +1118,7 @@ type orderRow struct { func (row orderRow) toDTO() OrderDTO { rentedAt := row.RentedAt - if rentedAt == nil { - rentedAt = row.RentStartAt - } durationHours := orderDurationHours(row.RentalOrder) - rentEndAt := row.RentEndAt - if rentEndAt == nil && rentedAt != nil && durationHours > 0 { - calculatedEnd := rentedAt.Add(time.Duration(durationHours) * time.Hour) - rentEndAt = &calculatedEnd - } return OrderDTO{ ID: row.ID, OrderNo: row.OrderNo, @@ -1091,9 +1133,8 @@ func (row orderRow) toDTO() OrderDTO { LoginPlatform: row.LoginPlatform, RentedAt: rentedAt, EstimatedDurationHours: durationHours, - RentStartAt: row.RentStartAt, - RentEndAt: rentEndAt, RentAmount: row.RentAmount, + OwnerRentAmount: row.OwnerRentAmount, DepositAmount: row.DepositAmount, PlatformFee: row.PlatformFee, AccountSnapshot: row.AccountSnapshot, @@ -1126,6 +1167,8 @@ func toCheckoutDTO(checkout model.OrderCheckout) CheckoutDTO { InitiatedBy: checkout.InitiatedBy, Status: checkout.Status, RentAmount: checkout.RentAmount, + OwnerRentAmount: checkout.OwnerRentAmount, + PlatformFee: checkout.PlatformFee, DepositAmount: checkout.DepositAmount, ConsumableAmount: checkout.ConsumableAmount, CoinConsumedM: checkout.CoinConsumedM, diff --git a/backend/migrations/000001_init.sql b/backend/migrations/000001_init.sql index be0fb99..df951a4 100644 --- a/backend/migrations/000001_init.sql +++ b/backend/migrations/000001_init.sql @@ -54,9 +54,6 @@ CREATE TABLE rental_listings ( account_id BIGINT UNSIGNED NOT NULL, owner_id BIGINT UNSIGNED NOT NULL, price DECIMAL(12,2) NOT NULL DEFAULT 0.00, - price_hourly DECIMAL(12,2) NOT NULL DEFAULT 0.00, - price_daily DECIMAL(12,2) NOT NULL DEFAULT 0.00, - price_weekly DECIMAL(12,2) NOT NULL DEFAULT 0.00, deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, in_transaction TINYINT(1) NOT NULL DEFAULT 0, status VARCHAR(32) NOT NULL DEFAULT 'draft', @@ -67,8 +64,7 @@ CREATE TABLE rental_listings ( updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_rental_listings_account_id (account_id), KEY idx_rental_listings_owner_id (owner_id), - KEY idx_rental_listings_filter (status, review_status, price), - KEY idx_rental_listings_legacy_filter (status, review_status, price_hourly) + KEY idx_rental_listings_filter (status, review_status, in_transaction, price) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE rental_orders ( @@ -80,14 +76,12 @@ CREATE TABLE rental_orders ( renter_id BIGINT UNSIGNED NOT NULL, rented_at DATETIME NULL, estimated_duration_hours INT NOT NULL DEFAULT 24, - rent_start_at DATETIME NULL, - rent_end_at DATETIME NULL, - rent_hours INT NOT NULL, rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + owner_rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, platform_fee DECIMAL(12,2) NOT NULL DEFAULT 0.00, account_snapshot JSON NULL, - status VARCHAR(32) NOT NULL DEFAULT 'pending_confirm', + status VARCHAR(32) NOT NULL DEFAULT 'pending_payment', handoff_status VARCHAR(32) NOT NULL DEFAULT 'none', settlement_status VARCHAR(32) NOT NULL DEFAULT 'unsettled', owner_settled_at DATETIME NULL, @@ -101,6 +95,33 @@ CREATE TABLE rental_orders ( KEY idx_rental_orders_rented_at (status, rented_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +CREATE TABLE order_checkouts ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + order_id BIGINT UNSIGNED NOT NULL, + initiated_by BIGINT UNSIGNED NOT NULL, + status VARCHAR(32) NOT NULL DEFAULT 'submitted', + rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + owner_rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + platform_fee DECIMAL(12,2) NOT NULL DEFAULT 0.00, + deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + consumable_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + coin_consumed_m DECIMAL(12,2) NOT NULL DEFAULT 0.00, + other_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + deposit_deduct_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + renter_refund_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + owner_income_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + content TEXT NULL, + evidence_urls JSON NULL, + owner_adjustment_reason TEXT NULL, + owner_adjusted_at DATETIME NULL, + renter_confirmed_at DATETIME NULL, + renter_rejected_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_order_checkouts_order_id (order_id), + KEY idx_order_checkouts_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + CREATE TABLE handoff_records ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, order_id BIGINT UNSIGNED NOT NULL, @@ -141,6 +162,7 @@ CREATE TABLE wallet_ledger ( created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_wallet_ledger_ledger_no (ledger_no), KEY idx_wallet_ledger_user_created (user_id, created_at), + KEY idx_wallet_ledger_order_id (order_id), KEY idx_wallet_ledger_biz (biz_type, biz_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/backend/migrations/000002_order_payment_stepwise.sql b/backend/migrations/000002_order_payment_stepwise.sql deleted file mode 100644 index a24b80d..0000000 --- a/backend/migrations/000002_order_payment_stepwise.sql +++ /dev/null @@ -1,132 +0,0 @@ -SET @listing_in_transaction_exists := ( - SELECT COUNT(*) - FROM information_schema.columns - WHERE table_schema = DATABASE() - AND table_name = 'rental_listings' - AND column_name = 'in_transaction' -); - -SET @add_listing_in_transaction_sql := IF( - @listing_in_transaction_exists = 0, - 'ALTER TABLE rental_listings ADD COLUMN in_transaction TINYINT(1) NOT NULL DEFAULT 0 AFTER deposit_amount', - 'SELECT 1' -); -PREPARE add_listing_in_transaction_stmt FROM @add_listing_in_transaction_sql; -EXECUTE add_listing_in_transaction_stmt; -DEALLOCATE PREPARE add_listing_in_transaction_stmt; - -SET @listing_price_exists := ( - SELECT COUNT(*) - FROM information_schema.columns - WHERE table_schema = DATABASE() - AND table_name = 'rental_listings' - AND column_name = 'price' -); - -SET @add_listing_price_sql := IF( - @listing_price_exists = 0, - 'ALTER TABLE rental_listings ADD COLUMN price DECIMAL(12,2) NOT NULL DEFAULT 0.00 AFTER owner_id', - 'SELECT 1' -); -PREPARE add_listing_price_stmt FROM @add_listing_price_sql; -EXECUTE add_listing_price_stmt; -DEALLOCATE PREPARE add_listing_price_stmt; - -UPDATE rental_listings -SET price = CASE - WHEN price_daily > 0 THEN price_daily - WHEN price_hourly > 0 THEN price_hourly * 24 - WHEN price_weekly > 0 THEN price_weekly / 7 - ELSE price -END -WHERE price <= 0; - -SET @order_rented_at_exists := ( - SELECT COUNT(*) - FROM information_schema.columns - WHERE table_schema = DATABASE() - AND table_name = 'rental_orders' - AND column_name = 'rented_at' -); - -SET @add_order_rented_at_sql := IF( - @order_rented_at_exists = 0, - 'ALTER TABLE rental_orders ADD COLUMN rented_at DATETIME NULL AFTER renter_id', - 'SELECT 1' -); -PREPARE add_order_rented_at_stmt FROM @add_order_rented_at_sql; -EXECUTE add_order_rented_at_stmt; -DEALLOCATE PREPARE add_order_rented_at_stmt; - -SET @order_estimated_duration_exists := ( - SELECT COUNT(*) - FROM information_schema.columns - WHERE table_schema = DATABASE() - AND table_name = 'rental_orders' - AND column_name = 'estimated_duration_hours' -); - -SET @add_order_estimated_duration_sql := IF( - @order_estimated_duration_exists = 0, - 'ALTER TABLE rental_orders ADD COLUMN estimated_duration_hours INT NOT NULL DEFAULT 24 AFTER rented_at', - 'SELECT 1' -); -PREPARE add_order_estimated_duration_stmt FROM @add_order_estimated_duration_sql; -EXECUTE add_order_estimated_duration_stmt; -DEALLOCATE PREPARE add_order_estimated_duration_stmt; - -UPDATE rental_orders -SET - rented_at = COALESCE(rented_at, rent_start_at), - estimated_duration_hours = CASE - WHEN estimated_duration_hours > 0 THEN estimated_duration_hours - WHEN rent_hours > 0 THEN rent_hours - ELSE 24 - END -WHERE rented_at IS NULL OR estimated_duration_hours <= 0; - -SET @order_rented_at_index_exists := ( - SELECT COUNT(*) - FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = 'rental_orders' - AND index_name = 'idx_rental_orders_rented_at' -); - -SET @add_order_rented_at_index_sql := IF( - @order_rented_at_index_exists = 0, - 'ALTER TABLE rental_orders ADD KEY idx_rental_orders_rented_at (status, rented_at)', - 'SELECT 1' -); -PREPARE add_order_rented_at_index_stmt FROM @add_order_rented_at_index_sql; -EXECUTE add_order_rented_at_index_stmt; -DEALLOCATE PREPARE add_order_rented_at_index_stmt; - -INSERT INTO system_configs (`key`, `value`, description) -VALUES ('order.pending_payment_timeout_minutes', '15', '订单待支付超时取消分钟数') -ON DUPLICATE KEY UPDATE `key` = VALUES(`key`); - -CREATE TABLE IF NOT EXISTS order_checkouts ( - id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, - order_id BIGINT UNSIGNED NOT NULL, - initiated_by BIGINT UNSIGNED NOT NULL, - status VARCHAR(32) NOT NULL DEFAULT 'submitted', - rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, - deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, - consumable_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, - coin_consumed_m DECIMAL(12,2) NOT NULL DEFAULT 0.00, - other_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, - deposit_deduct_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, - renter_refund_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, - owner_income_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, - content TEXT NULL, - evidence_urls JSON NULL, - owner_adjustment_reason TEXT NULL, - owner_adjusted_at DATETIME NULL, - renter_confirmed_at DATETIME NULL, - renter_rejected_at DATETIME NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - KEY idx_order_checkouts_order_id (order_id), - KEY idx_order_checkouts_status (status) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/frontend/src/api/listings.ts b/frontend/src/api/listings.ts index e995773..9d42198 100644 --- a/frontend/src/api/listings.ts +++ b/frontend/src/api/listings.ts @@ -17,9 +17,6 @@ export interface Listing { screenshot_urls: string[] cover_url: string price: number - price_hourly: number - price_daily: number - price_weekly: number deposit_amount: number is_accelerated_sale?: boolean in_transaction: boolean @@ -41,9 +38,6 @@ export interface ListingPayload { asset_summary?: Record screenshot_urls: string[] price: number - price_hourly: number - price_daily: number - price_weekly: number deposit_amount: number } diff --git a/frontend/src/api/orders.ts b/frontend/src/api/orders.ts index 2ed2e30..0cf5b21 100644 --- a/frontend/src/api/orders.ts +++ b/frontend/src/api/orders.ts @@ -14,9 +14,8 @@ export interface Order { login_platform: string rented_at?: string estimated_duration_hours: number - rent_start_at?: string - rent_end_at?: string rent_amount: number + owner_rent_amount: number deposit_amount: number platform_fee: number account_snapshot?: Record @@ -34,6 +33,8 @@ export interface Checkout { initiated_by: number status: string rent_amount: number + owner_rent_amount: number + platform_fee: number deposit_amount: number consumable_amount: number coin_consumed_m: number diff --git a/frontend/src/views/account/OrderDetailView.vue b/frontend/src/views/account/OrderDetailView.vue index 19888ba..065ba78 100644 --- a/frontend/src/views/account/OrderDetailView.vue +++ b/frontend/src/views/account/OrderDetailView.vue @@ -308,12 +308,11 @@ function readError(error: unknown, fallback: string) { } function orderRentedAt() { - return order.value?.rented_at || order.value?.rent_start_at + return order.value?.rented_at } function orderEstimatedEndAt() { if (!order.value) return undefined - if (order.value.rent_end_at) return order.value.rent_end_at const rentedAt = orderRentedAt() const durationHours = Number(order.value.estimated_duration_hours || 0) if (!rentedAt || durationHours <= 0) return undefined @@ -466,6 +465,10 @@ function linesToList(value: string) { 订单金额 ¥{{ order.rent_amount }} +
+ 平台费用 + ¥{{ order.platform_fee }} +
押金 ¥{{ order.deposit_amount }} @@ -554,7 +557,8 @@ function linesToList(value: string) {

结账明细

-

租金:¥{{ order.checkout.rent_amount }},押金:¥{{ order.checkout.deposit_amount }}

+

买家租金:¥{{ order.checkout.rent_amount }},号主租金:¥{{ order.checkout.owner_rent_amount }},平台费用:¥{{ order.checkout.platform_fee }}

+

押金:¥{{ order.checkout.deposit_amount }}

扣除:¥{{ order.checkout.deposit_deduct_amount }},退还租客:¥{{ order.checkout.renter_refund_amount }},号主收入:¥{{ order.checkout.owner_income_amount }}

说明:{{ order.checkout.content }}

修正原因:{{ order.checkout.owner_adjustment_reason }}

diff --git a/frontend/src/views/admin/AdminListingDetailView.vue b/frontend/src/views/admin/AdminListingDetailView.vue index e552b74..8015a1e 100644 --- a/frontend/src/views/admin/AdminListingDetailView.vue +++ b/frontend/src/views/admin/AdminListingDetailView.vue @@ -58,7 +58,7 @@ function money(value: number) { } function listingPrice(row: Listing) { - return money(row.price || row.price_daily || row.price_hourly) + return money(row.price) } function extractObjectKey(url: string) { diff --git a/frontend/src/views/admin/AdminListingReviewView.vue b/frontend/src/views/admin/AdminListingReviewView.vue index 63019cc..65de4f1 100644 --- a/frontend/src/views/admin/AdminListingReviewView.vue +++ b/frontend/src/views/admin/AdminListingReviewView.vue @@ -63,7 +63,7 @@ function openEvidence(row: Listing) { } function listingPrice(row: Listing) { - return `¥${Number(row.price || row.price_daily || row.price_hourly || 0).toFixed(2)}` + return `¥${Number(row.price || 0).toFixed(2)}` } function extractObjectKey(url: string) { diff --git a/frontend/src/views/admin/AdminListingsView.vue b/frontend/src/views/admin/AdminListingsView.vue index e144a47..e0e5fdb 100644 --- a/frontend/src/views/admin/AdminListingsView.vue +++ b/frontend/src/views/admin/AdminListingsView.vue @@ -43,7 +43,7 @@ function money(value: number) { } function listingPrice(row: Listing) { - return money(row.price || row.price_daily || row.price_hourly) + return money(row.price) } function ownerName(row: Listing) { diff --git a/frontend/src/views/admin/AdminOrderDetailView.vue b/frontend/src/views/admin/AdminOrderDetailView.vue index 659daae..3428664 100644 --- a/frontend/src/views/admin/AdminOrderDetailView.vue +++ b/frontend/src/views/admin/AdminOrderDetailView.vue @@ -65,12 +65,11 @@ function readError(error: unknown, fallback: string) { } function orderRentedAt() { - return order.value?.rented_at || order.value?.rent_start_at + return order.value?.rented_at } function orderEstimatedEndAt() { if (!order.value) return undefined - if (order.value.rent_end_at) return order.value.rent_end_at const rentedAt = orderRentedAt() const durationHours = Number(order.value.estimated_duration_hours || 0) if (!rentedAt || durationHours <= 0) return undefined @@ -108,6 +107,10 @@ function orderEstimatedEndAt() { 订单金额 ¥{{ order.rent_amount }}
+
+ 平台费用 + ¥{{ order.platform_fee }} +
押金 ¥{{ order.deposit_amount }} diff --git a/frontend/src/views/mobile/MobileSellerListingCreateView.vue b/frontend/src/views/mobile/MobileSellerListingCreateView.vue index 595d584..e594e4d 100644 --- a/frontend/src/views/mobile/MobileSellerListingCreateView.vue +++ b/frontend/src/views/mobile/MobileSellerListingCreateView.vue @@ -428,7 +428,6 @@ async function handleSubmit() { try { const title = `${form.server_region} ${form.rank_level} ${form.haf_coin_amount}M哈夫币`; const dailyPrice = calculatedFinalPrice.value; - const hourlyPrice = Math.max(roundMoney(dailyPrice / 24), 0.01); const listing = await createListing({ title, @@ -440,9 +439,6 @@ async function handleSubmit() { asset_summary: buildAssetSummary(), screenshot_urls: screenshotUrls.value, price: dailyPrice, - price_hourly: hourlyPrice, - price_daily: dailyPrice, - price_weekly: roundMoney(dailyPrice * 7), deposit_amount: Number(form.deposit_amount), }); localStorage.removeItem(draftKey); @@ -493,6 +489,9 @@ function validateForm() { if (!Number.isFinite(Number(form.deposit_amount))) { return "押金格式不正确"; } + if (calculatedConsumablePrice.value > 0 && Number(form.deposit_amount) <= calculatedConsumablePrice.value) { + return `押金必须大于额外消耗品总价值 ¥${calculatedConsumablePrice.value}`; + } if (!calculatedFinalPrice.value) { return "请完善币数、保险、体力和负重后再发布"; } diff --git a/frontend/src/views/mobile/listingDisplay.ts b/frontend/src/views/mobile/listingDisplay.ts index 9ba93c2..4de9708 100644 --- a/frontend/src/views/mobile/listingDisplay.ts +++ b/frontend/src/views/mobile/listingDisplay.ts @@ -27,7 +27,7 @@ export function formatHafCoinM(amountWan: number) { } export function getListingDisplayPrice(item: Listing) { - return Number(item.price_daily || item.price_hourly || 0); + return Number(item.price || 0); } export function getRatioValue(item: Listing) { diff --git a/frontend/src/views/public/ListingDetailView.vue b/frontend/src/views/public/ListingDetailView.vue index f3e0ab2..1368259 100644 --- a/frontend/src/views/public/ListingDetailView.vue +++ b/frontend/src/views/public/ListingDetailView.vue @@ -45,7 +45,7 @@ function readError(error: unknown, fallback: string) { } function listingPrice(item: Listing) { - return Number(item.price || item.price_daily || item.price_hourly || 0).toFixed(2); + return Number(item.price || 0).toFixed(2); } diff --git a/frontend/src/views/seller/SellerListingCreateView.vue b/frontend/src/views/seller/SellerListingCreateView.vue index 8875f61..46e5311 100644 --- a/frontend/src/views/seller/SellerListingCreateView.vue +++ b/frontend/src/views/seller/SellerListingCreateView.vue @@ -28,9 +28,6 @@ async function handleSubmit() { const listing = await createListing({ ...form, price, - price_hourly: Math.max(Math.round((price / 24) * 100) / 100, 0.01), - price_daily: price, - price_weekly: Math.round(price * 7 * 100) / 100, screenshot_urls: screenshotUrls.value, }) ElMessage.success( diff --git a/frontend/src/views/seller/SellerListingsView.vue b/frontend/src/views/seller/SellerListingsView.vue index eae7c23..aa319df 100644 --- a/frontend/src/views/seller/SellerListingsView.vue +++ b/frontend/src/views/seller/SellerListingsView.vue @@ -45,7 +45,7 @@ function readSellerPrice(row: Listing) { const price = Number((breakdown as Record).seller_total_price) if (Number.isFinite(price) && price > 0) return price } - return Number(row.price || row.price_daily || row.price_hourly || 0) + return Number(row.price || 0) } diff --git a/scripts/dev.sh b/scripts/dev.sh index 616144a..f1efdaf 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -57,6 +57,12 @@ wait_container_healthy() { } init_database() { + if [[ "${DEV_RESET_DB:-0}" == "1" ]]; then + log "DEV_RESET_DB=1,重建开发数据库..." + docker exec hfb-mysql mysql -uroot -prootsecret -e \ + "DROP DATABASE IF EXISTS hfb_sys; CREATE DATABASE hfb_sys CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" + fi + local table_count table_count="$( docker exec hfb-mysql mysql -uhfb -psecret -N -s -e \ @@ -69,11 +75,6 @@ init_database() { else log "数据库结构已存在,跳过迁移" fi - - if [[ -f "${ROOT_DIR}/backend/migrations/000002_order_payment_stepwise.sql" ]]; then - log "应用订单支付增量迁移..." - docker exec -i hfb-mysql mysql -uhfb -psecret hfb_sys < "${ROOT_DIR}/backend/migrations/000002_order_payment_stepwise.sql" - fi } load_env_file() {