删掉租期相关错误东西
This commit is contained in:
@@ -254,7 +254,7 @@ func (j *Job) handleReturnOverdue(ctx context.Context, now time.Time, cfg thresh
|
||||
UserID: order.RenterID,
|
||||
Type: "timeout",
|
||||
Title: "订单已逾期未归还",
|
||||
Content: "租期已超时,请尽快提交归还说明,避免进入申诉处理。",
|
||||
Content: "订单已超过预计截止时间,请尽快提交归还说明,避免进入申诉处理。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
@@ -262,7 +262,7 @@ func (j *Job) handleReturnOverdue(ctx context.Context, now time.Time, cfg thresh
|
||||
UserID: order.OwnerID,
|
||||
Type: "timeout",
|
||||
Title: "租客逾期未归还",
|
||||
Content: "租客未在租期结束后及时归还,你可以发起申诉或等待客服处理。",
|
||||
Content: "租客未在预计截止后及时归还,你可以发起申诉或等待客服处理。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
|
||||
@@ -36,8 +36,6 @@ type RentalListing struct {
|
||||
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"`
|
||||
MinRentHours int `gorm:"not null;default:1" json:"min_rent_hours"`
|
||||
MaxRentHours int `gorm:"not null;default:168" json:"max_rent_hours"`
|
||||
Status string `gorm:"size:32;not null;default:'draft'" json:"status"`
|
||||
ReviewStatus string `gorm:"size:32;not null;default:'none'" json:"review_status"`
|
||||
ReviewReason string `gorm:"size:255;not null;default:''" json:"review_reason"`
|
||||
|
||||
@@ -320,7 +320,7 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (
|
||||
addRenterRefund(req.Amount, "仲裁部分退款")
|
||||
addOwnerIncome(total-req.Amount, "仲裁剩余金额结算给号主")
|
||||
case "release_deposit":
|
||||
addOwnerIncome(order.RentAmount, "仲裁确认租金结算给号主")
|
||||
addOwnerIncome(order.RentAmount, "仲裁确认订单金额结算给号主")
|
||||
addRenterRefund(order.DepositAmount, "仲裁释放押金给租客")
|
||||
case "deduct_deposit", "compensate_owner":
|
||||
deductAmount := req.Amount
|
||||
@@ -331,7 +331,7 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (
|
||||
return settlement, ErrInvalidDispute
|
||||
}
|
||||
settlement.DepositDeductAmount = deductAmount
|
||||
addOwnerIncome(order.RentAmount+deductAmount, "仲裁租金及押金赔付结算给号主")
|
||||
addOwnerIncome(order.RentAmount+deductAmount, "仲裁订单金额及押金赔付结算给号主")
|
||||
addRenterRefund(order.DepositAmount-deductAmount, "仲裁退回剩余押金给租客")
|
||||
case "order_close":
|
||||
// Only release frozen funds. No available-balance settlement happens in development mode.
|
||||
|
||||
@@ -22,8 +22,6 @@ type ListingDTO struct {
|
||||
PriceDaily float64 `json:"price_daily"`
|
||||
PriceWeekly float64 `json:"price_weekly"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
MinRentHours int `json:"min_rent_hours"`
|
||||
MaxRentHours int `json:"max_rent_hours"`
|
||||
Status string `json:"status"`
|
||||
ReviewStatus string `json:"review_status"`
|
||||
ReviewReason string `json:"review_reason"`
|
||||
@@ -45,8 +43,6 @@ type CreateRequest struct {
|
||||
PriceDaily float64 `json:"price_daily"`
|
||||
PriceWeekly float64 `json:"price_weekly"`
|
||||
DepositAmount float64 `json:"deposit_amount" binding:"required"`
|
||||
MinRentHours int `json:"min_rent_hours" binding:"required"`
|
||||
MaxRentHours int `json:"max_rent_hours" binding:"required"`
|
||||
}
|
||||
|
||||
type UpdateRequest = CreateRequest
|
||||
|
||||
@@ -56,8 +56,6 @@ func (r *Repository) Create(ownerID uint64, req CreateRequest) (*ListingDTO, err
|
||||
PriceDaily: req.PriceDaily,
|
||||
PriceWeekly: req.PriceWeekly,
|
||||
DepositAmount: req.DepositAmount,
|
||||
MinRentHours: req.MinRentHours,
|
||||
MaxRentHours: req.MaxRentHours,
|
||||
Status: "draft",
|
||||
ReviewStatus: "none",
|
||||
}
|
||||
@@ -105,8 +103,6 @@ func (r *Repository) Update(ownerID uint64, listingID uint64, req UpdateRequest)
|
||||
listing.PriceDaily = req.PriceDaily
|
||||
listing.PriceWeekly = req.PriceWeekly
|
||||
listing.DepositAmount = req.DepositAmount
|
||||
listing.MinRentHours = req.MinRentHours
|
||||
listing.MaxRentHours = req.MaxRentHours
|
||||
listing.Status = "draft"
|
||||
listing.ReviewStatus = "none"
|
||||
listing.ReviewReason = ""
|
||||
@@ -470,8 +466,6 @@ func (row listingRow) toDTO() ListingDTO {
|
||||
PriceDaily: row.PriceDaily,
|
||||
PriceWeekly: row.PriceWeekly,
|
||||
DepositAmount: row.DepositAmount,
|
||||
MinRentHours: row.MinRentHours,
|
||||
MaxRentHours: row.MaxRentHours,
|
||||
Status: row.Status,
|
||||
ReviewStatus: row.ReviewStatus,
|
||||
ReviewReason: row.ReviewReason,
|
||||
@@ -502,8 +496,6 @@ func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
|
||||
PriceDaily: listing.PriceDaily,
|
||||
PriceWeekly: listing.PriceWeekly,
|
||||
DepositAmount: listing.DepositAmount,
|
||||
MinRentHours: listing.MinRentHours,
|
||||
MaxRentHours: listing.MaxRentHours,
|
||||
Status: listing.Status,
|
||||
ReviewStatus: listing.ReviewStatus,
|
||||
ReviewReason: listing.ReviewReason,
|
||||
|
||||
@@ -146,9 +146,6 @@ func validateRequest(req CreateRequest) error {
|
||||
if req.PriceHourly <= 0 || req.DepositAmount < 0 {
|
||||
return ErrInvalidInput
|
||||
}
|
||||
if req.MinRentHours <= 0 || req.MaxRentHours < req.MinRentHours {
|
||||
return ErrInvalidInput
|
||||
}
|
||||
if req.HafCoinAmount < 0 {
|
||||
return ErrInvalidInput
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Order Module
|
||||
|
||||
订单状态机、账号交接、租期归还、超时处理。
|
||||
订单状态机、账号交接、归还、超时处理。
|
||||
|
||||
@@ -20,7 +20,6 @@ type OrderDTO struct {
|
||||
LoginPlatform string `json:"login_platform"`
|
||||
RentStartAt *time.Time `json:"rent_start_at"`
|
||||
RentEndAt *time.Time `json:"rent_end_at"`
|
||||
RentHours int `json:"rent_hours"`
|
||||
RentAmount float64 `json:"rent_amount"`
|
||||
DepositAmount float64 `json:"deposit_amount"`
|
||||
PlatformFee float64 `json:"platform_fee"`
|
||||
@@ -34,7 +33,6 @@ type OrderDTO struct {
|
||||
|
||||
type CreateRequest struct {
|
||||
ListingID uint64 `json:"listing_id" binding:"required"`
|
||||
RentHours int `json:"rent_hours" binding:"required"`
|
||||
}
|
||||
|
||||
type SubmitHandoffRequest struct {
|
||||
|
||||
@@ -282,7 +282,7 @@ func writeOrderError(c *gin.Context, err error) {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
response.ServiceUnavailable(c, "数据库未连接")
|
||||
case errors.Is(err, ErrInvalidRentHours):
|
||||
response.BadRequest(c, "租期不符合规则")
|
||||
response.BadRequest(c, "订单信息不符合规则")
|
||||
case errors.Is(err, ErrListingUnavailable):
|
||||
response.Error(c, http.StatusConflict, "listing_unavailable", "该账号暂不可租")
|
||||
case errors.Is(err, ErrCannotRentOwnListing):
|
||||
|
||||
@@ -38,9 +38,6 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
if listing.OwnerID == renterID {
|
||||
return ErrCannotRentOwnListing
|
||||
}
|
||||
if req.RentHours < listing.MinRentHours || req.RentHours > listing.MaxRentHours {
|
||||
return ErrInvalidRentHours
|
||||
}
|
||||
|
||||
var account model.GameAccount
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, listing.AccountID).Error; err != nil {
|
||||
@@ -55,7 +52,8 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
rentEnd := now.Add(time.Duration(req.RentHours) * time.Hour)
|
||||
rentHours := internalOrderHours
|
||||
rentEnd := now.Add(time.Duration(rentHours) * time.Hour)
|
||||
order := model.RentalOrder{
|
||||
OrderNo: orderNo,
|
||||
ListingID: listing.ID,
|
||||
@@ -64,8 +62,8 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
RenterID: renterID,
|
||||
RentStartAt: &now,
|
||||
RentEndAt: &rentEnd,
|
||||
RentHours: req.RentHours,
|
||||
RentAmount: listing.PriceHourly * float64(req.RentHours),
|
||||
RentHours: rentHours,
|
||||
RentAmount: listing.PriceHourly * float64(rentHours),
|
||||
DepositAmount: listing.DepositAmount,
|
||||
PlatformFee: 0,
|
||||
AccountSnapshot: snapshot,
|
||||
@@ -86,7 +84,7 @@ func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, erro
|
||||
BalanceType: "frozen",
|
||||
BizType: "order_lock",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "开发态模拟冻结租金和押金",
|
||||
Remark: "开发态模拟冻结订单金额和押金",
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
@@ -272,7 +270,7 @@ func (r *Repository) ConfirmReceive(userID uint64, orderID uint64) error {
|
||||
UserID: order.OwnerID,
|
||||
Type: "handoff",
|
||||
Title: "租客已确认收号",
|
||||
Content: "订单已进入租赁中。",
|
||||
Content: "订单已进入使用中。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
}); err != nil {
|
||||
@@ -399,7 +397,7 @@ func (r *Repository) ConfirmReturn(userID uint64, orderID uint64) error {
|
||||
BalanceType: "available",
|
||||
BizType: "owner_income",
|
||||
BizNo: order.OrderNo,
|
||||
Remark: "订单完成模拟结算租金",
|
||||
Remark: "订单完成模拟结算订单金额",
|
||||
},
|
||||
wallet.Entry{
|
||||
UserID: order.RenterID,
|
||||
@@ -427,7 +425,7 @@ func (r *Repository) ConfirmReturn(userID uint64, orderID uint64) error {
|
||||
UserID: order.OwnerID,
|
||||
Type: "settlement",
|
||||
Title: "订单已完成",
|
||||
Content: "订单已完成,模拟租金已入账。",
|
||||
Content: "订单已完成,模拟订单金额已入账。",
|
||||
BizType: "order",
|
||||
BizID: &orderID,
|
||||
},
|
||||
@@ -727,7 +725,6 @@ func (row orderRow) toDTO() OrderDTO {
|
||||
LoginPlatform: row.LoginPlatform,
|
||||
RentStartAt: row.RentStartAt,
|
||||
RentEndAt: row.RentEndAt,
|
||||
RentHours: row.RentHours,
|
||||
RentAmount: row.RentAmount,
|
||||
DepositAmount: row.DepositAmount,
|
||||
PlatformFee: row.PlatformFee,
|
||||
|
||||
@@ -15,6 +15,8 @@ var (
|
||||
ErrPermissionDenied = errors.New("permission denied")
|
||||
)
|
||||
|
||||
const internalOrderHours = 24
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
}
|
||||
@@ -27,7 +29,7 @@ func (s *Service) Create(userID uint64, req CreateRequest) (*OrderDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if req.ListingID == 0 || req.RentHours <= 0 {
|
||||
if req.ListingID == 0 {
|
||||
return nil, ErrInvalidRentHours
|
||||
}
|
||||
return s.repo.Create(userID, req)
|
||||
|
||||
@@ -83,7 +83,6 @@ type PublishRatioConfig struct {
|
||||
InsuranceBaseRatios []PublishInsuranceBaseRatio `json:"insurance_base_ratios"`
|
||||
ConfigItems []PublishRatioConfigItem `json:"config_items"`
|
||||
CoinCorrections []PublishCoinCorrection `json:"coin_corrections"`
|
||||
RentRules []PublishRentRule `json:"rent_rules"`
|
||||
}
|
||||
|
||||
type PublishInsuranceBaseRatio struct {
|
||||
@@ -103,8 +102,3 @@ type PublishCoinCorrection struct {
|
||||
ThresholdM float64 `json:"threshold_m"`
|
||||
Correction float64 `json:"correction"`
|
||||
}
|
||||
|
||||
type PublishRentRule struct {
|
||||
ThresholdM float64 `json:"threshold_m"`
|
||||
Days int `json:"days"`
|
||||
}
|
||||
|
||||
@@ -106,11 +106,6 @@ func DefaultPublishOptions() PublishOptionsDTO {
|
||||
{ThresholdM: 380, Correction: 4},
|
||||
{ThresholdM: 530, Correction: 5},
|
||||
},
|
||||
RentRules: []PublishRentRule{
|
||||
{ThresholdM: 300, Days: 7},
|
||||
{ThresholdM: 100, Days: 3},
|
||||
{ThresholdM: 0, Days: 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ var defaultConfigs = []defaultConfig{
|
||||
{Key: "handoff.owner_submit_timeout_minutes", Value: "30", Description: "号主待交接超时分钟数"},
|
||||
{Key: "handoff.renter_confirm_timeout_minutes", Value: "30", Description: "租客待确认收号超时分钟数"},
|
||||
{Key: "handoff.owner_return_confirm_timeout_minutes", Value: "120", Description: "号主待确认归还超时分钟数"},
|
||||
{Key: "order.return_overdue_grace_minutes", Value: "10", Description: "租期到期后归还宽限分钟数"},
|
||||
{Key: "order.return_overdue_grace_minutes", Value: "10", Description: "预计截止后归还宽限分钟数"},
|
||||
{Key: "deposit.min_amount", Value: "50", Description: "发布租号最低押金"},
|
||||
{Key: "risk.sms_limit_per_phone_hour", Value: "5", Description: "单手机号每小时短信验证码次数"},
|
||||
{Key: "risk.sms_limit_per_ip_hour", Value: "20", Description: "单 IP 每小时短信验证码次数"},
|
||||
|
||||
@@ -211,7 +211,4 @@ func normalizePublishOptions(options *PublishOptionsDTO) {
|
||||
if len(options.RatioConfig.CoinCorrections) == 0 {
|
||||
options.RatioConfig.CoinCorrections = defaults.RatioConfig.CoinCorrections
|
||||
}
|
||||
if len(options.RatioConfig.RentRules) == 0 {
|
||||
options.RatioConfig.RentRules = defaults.RatioConfig.RentRules
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Wallet Module
|
||||
|
||||
押金冻结、租金结算、不可变资金流水。
|
||||
押金冻结、订单金额结算、不可变资金流水。
|
||||
|
||||
@@ -57,8 +57,6 @@ CREATE TABLE rental_listings (
|
||||
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,
|
||||
min_rent_hours INT NOT NULL DEFAULT 1,
|
||||
max_rent_hours INT NOT NULL DEFAULT 168,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'draft',
|
||||
review_status VARCHAR(32) NOT NULL DEFAULT 'none',
|
||||
review_reason VARCHAR(255) NOT NULL DEFAULT '',
|
||||
|
||||
@@ -214,7 +214,7 @@ VALUES
|
||||
(
|
||||
@owner_c, '三角洲行动', '微信', '扫码',
|
||||
'纯币180M/6格/6体/5负/80发AWM',
|
||||
'扫码交接,资源适中,适合三日租。',
|
||||
'扫码交接,资源适中,适合短期使用。',
|
||||
'黑鹰', 180000000,
|
||||
JSON_OBJECT(
|
||||
'face_owner', '否',
|
||||
@@ -361,29 +361,29 @@ VALUES
|
||||
);
|
||||
|
||||
INSERT INTO rental_listings
|
||||
(account_id, owner_id, price_hourly, price_daily, price_weekly, deposit_amount, min_rent_hours, max_rent_hours, status, review_status, published_at, created_at, updated_at)
|
||||
SELECT id, owner_id, 29.76, 714.29, 5000.00, 120.00, 24, 168, 'published', 'approved', NOW() - INTERVAL 80 MINUTE, created_at, updated_at
|
||||
(account_id, owner_id, price_hourly, price_daily, price_weekly, deposit_amount, status, review_status, published_at, created_at, updated_at)
|
||||
SELECT id, owner_id, 29.76, 714.29, 5000.00, 120.00, 'published', 'approved', NOW() - INTERVAL 80 MINUTE, created_at, updated_at
|
||||
FROM game_accounts WHERE title = '纯币300M/6格/6体/6负/4六甲/6六头'
|
||||
UNION ALL
|
||||
SELECT id, owner_id, 33.33, 800.00, 2400.00, 60.00, 24, 72, 'published', 'approved', NOW() - INTERVAL 70 MINUTE, created_at, updated_at
|
||||
SELECT id, owner_id, 33.33, 800.00, 2400.00, 60.00, 'published', 'approved', NOW() - INTERVAL 70 MINUTE, created_at, updated_at
|
||||
FROM game_accounts WHERE title = '纯币120M/4格/5体/5负/2六头'
|
||||
UNION ALL
|
||||
SELECT id, owner_id, 62.50, 1500.00, 1500.00, 80.00, 8, 24, 'published', 'approved', NOW() - INTERVAL 60 MINUTE, created_at, updated_at
|
||||
SELECT id, owner_id, 62.50, 1500.00, 1500.00, 80.00, 'published', 'approved', NOW() - INTERVAL 60 MINUTE, created_at, updated_at
|
||||
FROM game_accounts WHERE title = '纯币60M/2格/4体/4负/1体验卡'
|
||||
UNION ALL
|
||||
SELECT id, owner_id, 42.52, 1020.41, 7142.86, 180.00, 24, 168, 'published', 'approved', NOW() - INTERVAL 50 MINUTE, created_at, updated_at
|
||||
SELECT id, owner_id, 42.52, 1020.41, 7142.86, 180.00, 'published', 'approved', NOW() - INTERVAL 50 MINUTE, created_at, updated_at
|
||||
FROM game_accounts WHERE title = '纯币500M/9格/7体/7负/10六头'
|
||||
UNION ALL
|
||||
SELECT id, owner_id, 50.00, 1200.00, 3600.00, 100.00, 24, 72, 'published', 'approved', NOW() - INTERVAL 40 MINUTE, created_at, updated_at
|
||||
SELECT id, owner_id, 50.00, 1200.00, 3600.00, 100.00, 'published', 'approved', NOW() - INTERVAL 40 MINUTE, created_at, updated_at
|
||||
FROM game_accounts WHERE title = '纯币180M/6格/6体/5负/80发AWM'
|
||||
UNION ALL
|
||||
SELECT id, owner_id, 31.25, 750.00, 750.00, 50.00, 8, 24, 'published', 'approved', NOW() - INTERVAL 30 MINUTE, created_at, updated_at
|
||||
SELECT id, owner_id, 31.25, 750.00, 750.00, 50.00, 'published', 'approved', NOW() - INTERVAL 30 MINUTE, created_at, updated_at
|
||||
FROM game_accounts WHERE title = '纯币90M/4格/5体/4负/3五级套'
|
||||
UNION ALL
|
||||
SELECT id, owner_id, 25.79, 619.05, 4333.33, 120.00, 24, 168, 'published', 'approved', NOW() - INTERVAL 20 MINUTE, created_at, updated_at
|
||||
SELECT id, owner_id, 25.79, 619.05, 4333.33, 120.00, 'published', 'approved', NOW() - INTERVAL 20 MINUTE, created_at, updated_at
|
||||
FROM game_accounts WHERE title = '纯币260M/6格/6体/6负/皮肤多'
|
||||
UNION ALL
|
||||
SELECT id, owner_id, 35.71, 857.14, 6000.00, 160.00, 24, 168, 'published', 'approved', NOW() - INTERVAL 10 MINUTE, created_at, updated_at
|
||||
SELECT id, owner_id, 35.71, 857.14, 6000.00, 160.00, 'published', 'approved', NOW() - INTERVAL 10 MINUTE, created_at, updated_at
|
||||
FROM game_accounts WHERE title = '纯币360M/9格/7体/6负/赠送六级弹';
|
||||
|
||||
INSERT INTO notifications (user_id, type, title, content, biz_type, biz_id, created_at)
|
||||
|
||||
@@ -52,16 +52,10 @@ export interface PublishCoinCorrection {
|
||||
correction: number
|
||||
}
|
||||
|
||||
export interface PublishRentRule {
|
||||
threshold_m: number
|
||||
days: number
|
||||
}
|
||||
|
||||
export interface PublishRatioConfig {
|
||||
insurance_base_ratios: PublishInsuranceBaseRatio[]
|
||||
config_items: PublishRatioConfigItem[]
|
||||
coin_corrections: PublishCoinCorrection[]
|
||||
rent_rules: PublishRentRule[]
|
||||
}
|
||||
|
||||
export interface ListingPublishOptions {
|
||||
@@ -109,7 +103,6 @@ export const emptyListingPublishOptions: ListingPublishOptions = {
|
||||
insurance_base_ratios: [],
|
||||
config_items: [],
|
||||
coin_corrections: [],
|
||||
rent_rules: [],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -208,7 +201,6 @@ function normalizeRatioConfig(value?: unknown): PublishRatioConfig {
|
||||
coin_corrections: normalizeCoinCorrections(
|
||||
Array.isArray(row.coin_corrections) ? row.coin_corrections : [],
|
||||
),
|
||||
rent_rules: normalizeRentRules(Array.isArray(row.rent_rules) ? row.rent_rules : []),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,18 +243,6 @@ function normalizeCoinCorrections(values: unknown[]): PublishCoinCorrection[] {
|
||||
.filter((item) => item.threshold_m >= 0 && item.correction > 0)
|
||||
}
|
||||
|
||||
function normalizeRentRules(values: unknown[]): PublishRentRule[] {
|
||||
return values
|
||||
.map((item) => {
|
||||
const row = isRecord(item) ? item : {}
|
||||
return {
|
||||
threshold_m: readNumber(row.threshold_m),
|
||||
days: Math.trunc(readNumber(row.days)),
|
||||
}
|
||||
})
|
||||
.filter((item) => item.threshold_m >= 0 && item.days > 0)
|
||||
}
|
||||
|
||||
function readNumber(value: unknown) {
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) ? parsed : 0
|
||||
|
||||
@@ -20,8 +20,6 @@ export interface Listing {
|
||||
price_daily: number
|
||||
price_weekly: number
|
||||
deposit_amount: number
|
||||
min_rent_hours: number
|
||||
max_rent_hours: number
|
||||
status: string
|
||||
review_status: string
|
||||
review_reason: string
|
||||
@@ -43,8 +41,6 @@ export interface ListingPayload {
|
||||
price_daily: number
|
||||
price_weekly: number
|
||||
deposit_amount: number
|
||||
min_rent_hours: number
|
||||
max_rent_hours: number
|
||||
}
|
||||
|
||||
interface ApiResponse<T> {
|
||||
|
||||
@@ -14,7 +14,6 @@ export interface Order {
|
||||
login_platform: string
|
||||
rent_start_at?: string
|
||||
rent_end_at?: string
|
||||
rent_hours: number
|
||||
rent_amount: number
|
||||
deposit_amount: number
|
||||
platform_fee: number
|
||||
@@ -44,10 +43,9 @@ interface ApiResponse<T> {
|
||||
data: T
|
||||
}
|
||||
|
||||
export async function createOrder(listingId: number, rentHours: number) {
|
||||
export async function createOrder(listingId: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<Order>>('/orders', {
|
||||
listing_id: listingId,
|
||||
rent_hours: rentHours,
|
||||
})
|
||||
return data.data
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="page-header">
|
||||
<p class="eyebrow">Create Order</p>
|
||||
<h1>创建订单</h1>
|
||||
<p>确认租期、租金、押金和账号资产快照。</p>
|
||||
<p>确认价格、押金和账号资产快照。</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -91,7 +91,7 @@ async function handleConfirmReceive() {
|
||||
confirming.value = true
|
||||
try {
|
||||
await confirmReceive(order.value.id)
|
||||
ElMessage.success('已确认收号,订单进入租赁中')
|
||||
ElMessage.success('已确认收号,订单进入使用中')
|
||||
await loadOrder()
|
||||
} catch (error) {
|
||||
ElMessage.error(readError(error, '确认收号失败'))
|
||||
@@ -197,7 +197,7 @@ function readError(error: unknown, fallback: string) {
|
||||
<strong>{{ order.handoff_status }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>租金</span>
|
||||
<span>订单金额</span>
|
||||
<strong>¥{{ order.rent_amount }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
@@ -207,9 +207,8 @@ function readError(error: unknown, fallback: string) {
|
||||
</div>
|
||||
|
||||
<div v-if="order" class="order-panel">
|
||||
<p>租期:{{ order.rent_hours }} 小时</p>
|
||||
<p>开始:{{ formatDateTime(order.rent_start_at, '未开始') }}</p>
|
||||
<p>结束:{{ formatDateTime(order.rent_end_at, '未设置') }}</p>
|
||||
<p>预计截止:{{ formatDateTime(order.rent_end_at, '未设置') }}</p>
|
||||
<el-button v-if="order.status === 'pending_handoff'" type="danger" :loading="cancelling" @click="handleCancel">
|
||||
取消订单并释放账号
|
||||
</el-button>
|
||||
@@ -236,7 +235,7 @@ function readError(error: unknown, fallback: string) {
|
||||
class="order-panel"
|
||||
>
|
||||
<h2>确认收号</h2>
|
||||
<p>确认账号可以正常登录后,订单会进入租赁中并重新计算租期结束时间。</p>
|
||||
<p>确认账号可以正常登录后,订单会进入使用中并重新计算预计截止时间。</p>
|
||||
<el-button type="primary" :loading="confirming" @click="handleConfirmReceive">确认已收到账号</el-button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,14 +23,13 @@ async function loadOrders() {
|
||||
<div class="page-header">
|
||||
<p class="eyebrow">Orders</p>
|
||||
<h1>我的订单</h1>
|
||||
<p>跟踪待交接、租赁中、待归还、申诉中和已完成订单。</p>
|
||||
<p>跟踪待交接、使用中、待归还、申诉中和已完成订单。</p>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" class="table-panel" :data="orders">
|
||||
<el-table-column prop="order_no" label="订单号" min-width="210" />
|
||||
<el-table-column prop="title" label="账号" min-width="180" />
|
||||
<el-table-column prop="rent_hours" label="租期" width="90" />
|
||||
<el-table-column prop="rent_amount" label="租金" width="100" />
|
||||
<el-table-column prop="rent_amount" label="订单金额" width="100" />
|
||||
<el-table-column prop="deposit_amount" label="押金" width="100" />
|
||||
<el-table-column prop="status" label="状态" width="140" />
|
||||
<el-table-column label="操作" width="100">
|
||||
|
||||
@@ -56,7 +56,7 @@ function money(value?: number) {
|
||||
<strong>{{ dashboard.metrics.total_orders }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>租赁中</span>
|
||||
<span>使用中</span>
|
||||
<strong>{{ dashboard.metrics.renting_orders }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
@@ -119,7 +119,7 @@ function money(value?: number) {
|
||||
<el-table-column prop="order_no" label="最近订单" min-width="210" />
|
||||
<el-table-column prop="title" label="账号" min-width="170" />
|
||||
<el-table-column prop="status" label="状态" width="140" />
|
||||
<el-table-column prop="rent_amount" label="租金" width="100" />
|
||||
<el-table-column prop="rent_amount" label="订单金额" width="100" />
|
||||
<el-table-column prop="deposit_amount" label="押金" width="100" />
|
||||
<el-table-column label="创建时间" min-width="180">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
|
||||
@@ -56,6 +56,10 @@ function money(value: number) {
|
||||
return `¥${Number(value || 0).toFixed(2)}`
|
||||
}
|
||||
|
||||
function listingPrice(row: Listing) {
|
||||
return money(row.price_daily || row.price_hourly)
|
||||
}
|
||||
|
||||
function extractObjectKey(url: string) {
|
||||
try {
|
||||
const parsed = new URL(url, window.location.origin)
|
||||
@@ -110,8 +114,8 @@ function readError(error: unknown, fallback: string) {
|
||||
<strong>{{ listing.review_status }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>时租</span>
|
||||
<strong>{{ money(listing.price_hourly) }}</strong>
|
||||
<span>价格</span>
|
||||
<strong>{{ listingPrice(listing) }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>押金</span>
|
||||
@@ -132,13 +136,11 @@ function readError(error: unknown, fallback: string) {
|
||||
</div>
|
||||
|
||||
<div class="order-panel dashboard-panel">
|
||||
<h2>号主与租期</h2>
|
||||
<h2>号主与价格</h2>
|
||||
<p>号主:{{ listing.owner_phone || listing.owner_nickname || listing.owner_id }}</p>
|
||||
<p>号主 ID:{{ listing.owner_id }}</p>
|
||||
<p>最短租期:{{ listing.min_rent_hours }} 小时</p>
|
||||
<p>最长租期:{{ listing.max_rent_hours }} 小时</p>
|
||||
<p>日租:{{ money(listing.price_daily) }}</p>
|
||||
<p>周租:{{ money(listing.price_weekly) }}</p>
|
||||
<p>价格:{{ listingPrice(listing) }}</p>
|
||||
<p>押金:{{ money(listing.deposit_amount) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -62,6 +62,10 @@ function openEvidence(row: Listing) {
|
||||
evidenceListing.value = row
|
||||
}
|
||||
|
||||
function listingPrice(row: Listing) {
|
||||
return `¥${Number(row.price_daily || row.price_hourly || 0).toFixed(2)}`
|
||||
}
|
||||
|
||||
function extractObjectKey(url: string) {
|
||||
try {
|
||||
const parsed = new URL(url, window.location.origin)
|
||||
@@ -95,7 +99,7 @@ function readError(error: unknown, fallback: string) {
|
||||
<div class="page-header">
|
||||
<p class="eyebrow">Review</p>
|
||||
<h1>商品审核</h1>
|
||||
<p>审核号主提交的账号资产、价格、押金和租期规则。</p>
|
||||
<p>审核号主提交的账号资产、价格和押金。</p>
|
||||
</div>
|
||||
<el-button @click="loadListings">刷新</el-button>
|
||||
</div>
|
||||
@@ -107,7 +111,9 @@ function readError(error: unknown, fallback: string) {
|
||||
<el-table-column prop="login_platform" label="平台" width="120" />
|
||||
<el-table-column prop="haf_coin_amount" label="哈夫币" width="110" />
|
||||
<el-table-column prop="rank_level" label="段位" width="110" />
|
||||
<el-table-column prop="price_hourly" label="时租" width="90" />
|
||||
<el-table-column label="价格" width="90">
|
||||
<template #default="{ row }">{{ listingPrice(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deposit_amount" label="押金" width="90" />
|
||||
<el-table-column label="截图" width="90">
|
||||
<template #default="{ row }">
|
||||
|
||||
@@ -41,6 +41,10 @@ function money(value: number) {
|
||||
return `¥${Number(value || 0).toFixed(2)}`
|
||||
}
|
||||
|
||||
function listingPrice(row: Listing) {
|
||||
return money(row.price_daily || row.price_hourly)
|
||||
}
|
||||
|
||||
function ownerName(row: Listing) {
|
||||
return row.owner_phone || row.owner_nickname || `号主 ${row.owner_id}`
|
||||
}
|
||||
@@ -84,7 +88,7 @@ function reviewType(status: string) {
|
||||
<strong>{{ publishedCount }} 个</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>租赁中</span>
|
||||
<span>已锁定</span>
|
||||
<strong>{{ rentedCount }} 个</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
@@ -101,7 +105,7 @@ function reviewType(status: string) {
|
||||
<el-select v-model="filters.status" clearable placeholder="全部状态" class="full-control">
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="已上架" value="published" />
|
||||
<el-option label="租赁中" value="rented" />
|
||||
<el-option label="已锁定" value="rented" />
|
||||
<el-option label="已下架" value="offline" />
|
||||
<el-option label="异常" value="abnormal" />
|
||||
</el-select>
|
||||
@@ -137,8 +141,8 @@ function reviewType(status: string) {
|
||||
<el-table-column prop="login_platform" label="平台" width="120" />
|
||||
<el-table-column prop="rank_level" label="段位" width="110" />
|
||||
<el-table-column prop="haf_coin_amount" label="哈夫币" width="110" />
|
||||
<el-table-column label="时租" width="100">
|
||||
<template #default="{ row }">{{ money(row.price_hourly) }}</template>
|
||||
<el-table-column label="价格" width="100">
|
||||
<template #default="{ row }">{{ listingPrice(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="押金" width="100">
|
||||
<template #default="{ row }">{{ money(row.deposit_amount) }}</template>
|
||||
|
||||
@@ -91,7 +91,7 @@ function readError(error: unknown, fallback: string) {
|
||||
<strong>{{ order.handoff_status }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>租金</span>
|
||||
<span>订单金额</span>
|
||||
<strong>¥{{ order.rent_amount }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
@@ -105,9 +105,8 @@ function readError(error: unknown, fallback: string) {
|
||||
<h2>用户信息</h2>
|
||||
<p>租客:{{ order.renter_phone || order.renter_id }}</p>
|
||||
<p>号主:{{ order.owner_phone || order.owner_id }}</p>
|
||||
<p>租期:{{ order.rent_hours }} 小时</p>
|
||||
<p>开始:{{ formatDateTime(order.rent_start_at, '未开始') }}</p>
|
||||
<p>结束:{{ formatDateTime(order.rent_end_at, '未设置') }}</p>
|
||||
<p>预计截止:{{ formatDateTime(order.rent_end_at, '未设置') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="order-panel dashboard-panel">
|
||||
|
||||
@@ -36,7 +36,7 @@ async function loadOrders() {
|
||||
<div class="toolbar-actions">
|
||||
<el-select v-model="status" clearable placeholder="订单状态" style="width: 180px">
|
||||
<el-option label="待交接" value="pending_handoff" />
|
||||
<el-option label="租赁中" value="renting" />
|
||||
<el-option label="使用中" value="renting" />
|
||||
<el-option label="逾期中" value="overdue" />
|
||||
<el-option label="待归还确认" value="pending_return_confirm" />
|
||||
<el-option label="申诉中" value="disputing" />
|
||||
@@ -57,7 +57,7 @@ async function loadOrders() {
|
||||
<el-table-column prop="status" label="状态" width="140" />
|
||||
<el-table-column prop="handoff_status" label="交接" width="180" />
|
||||
<el-table-column prop="settlement_status" label="结算" width="120" />
|
||||
<el-table-column prop="rent_amount" label="租金" width="100" />
|
||||
<el-table-column prop="rent_amount" label="订单金额" width="100" />
|
||||
<el-table-column prop="deposit_amount" label="押金" width="100" />
|
||||
<el-table-column label="创建时间" min-width="180">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
|
||||
@@ -281,17 +281,6 @@ function removeCoinCorrection(index: number) {
|
||||
publishOptionsDraft.value.ratio_config.coin_corrections.splice(index, 1)
|
||||
}
|
||||
|
||||
function addRentRule() {
|
||||
publishOptionsDraft.value.ratio_config.rent_rules.push({
|
||||
threshold_m: 0,
|
||||
days: 1,
|
||||
})
|
||||
}
|
||||
|
||||
function removeRentRule(index: number) {
|
||||
publishOptionsDraft.value.ratio_config.rent_rules.splice(index, 1)
|
||||
}
|
||||
|
||||
function addHomeBanner() {
|
||||
homeBannersDraft.value.push({
|
||||
eyebrow: '首页推荐',
|
||||
@@ -642,23 +631,6 @@ function readError(error: unknown, fallback: string) {
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="editor-block-title subtle-title">
|
||||
<span>租期规则</span>
|
||||
<el-button size="small" @click="addRentRule">添加租期</el-button>
|
||||
</div>
|
||||
<el-table :data="publishOptionsDraft.ratio_config.rent_rules" size="small" border>
|
||||
<el-table-column label="大于等于 M" min-width="130">
|
||||
<template #default="{ row }"><el-input-number v-model="row.threshold_m" :min="0" :step="10" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="租期天数" min-width="130">
|
||||
<template #default="{ row }"><el-input-number v-model="row.days" :min="1" :step="1" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90">
|
||||
<template #default="{ $index }">
|
||||
<el-button size="small" type="danger" plain @click="removeRentRule($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="editor-block">
|
||||
|
||||
@@ -62,7 +62,7 @@ function directionLabel(direction: string) {
|
||||
<div class="page-header">
|
||||
<p class="eyebrow">Wallet Ledger</p>
|
||||
<h1>资金流水</h1>
|
||||
<p>查看租金、押金、冻结、解冻、退款和结算流水。当前为开发态账务记录,不代表真实支付余额。</p>
|
||||
<p>查看订单金额、押金、冻结、解冻、退款和结算流水。当前为开发态账务记录,不代表真实支付余额。</p>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button @click="resetFilters">重置</el-button>
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
getListingSubtitle,
|
||||
getListingTitle,
|
||||
getLoginMethod,
|
||||
getRentDays,
|
||||
getServerRegion,
|
||||
readAssetNumber,
|
||||
readAssetString,
|
||||
@@ -29,14 +28,12 @@ const router = useRouter();
|
||||
const session = useSessionStore();
|
||||
const loading = ref(false);
|
||||
const ordering = ref(false);
|
||||
const rentHours = ref(1);
|
||||
const listing = ref<Listing | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
listing.value = await fetchListing(String(route.params.id));
|
||||
rentHours.value = Math.max(listing.value.min_rent_hours || 1, 1);
|
||||
} catch {
|
||||
showToast({ message: "加载失败", icon: "warning-o" });
|
||||
} finally {
|
||||
@@ -44,10 +41,9 @@ onMounted(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
/* 预计租金 */
|
||||
const totalRent = computed(() => {
|
||||
const orderTotal = computed(() => {
|
||||
if (!listing.value) return "0.00";
|
||||
return (listing.value.price_hourly * rentHours.value).toFixed(2);
|
||||
return getListingDisplayPrice(listing.value).toFixed(2);
|
||||
});
|
||||
|
||||
const detailMetrics = computed(() => {
|
||||
@@ -136,7 +132,7 @@ async function handleCreateOrder() {
|
||||
|
||||
ordering.value = true;
|
||||
try {
|
||||
const order = await createOrder(listing.value.id, rentHours.value);
|
||||
await createOrder(listing.value.id);
|
||||
showToast({ message: "订单已创建,账号已锁定", icon: "passed" });
|
||||
await router.push(`/m/orders`);
|
||||
} catch (error) {
|
||||
@@ -255,10 +251,6 @@ function isNavActive(path: string) {
|
||||
<span class="info-label">M单价</span>
|
||||
<span class="info-text">{{ formatRatio(listing) }}</span>
|
||||
</div>
|
||||
<div class="info-line">
|
||||
<span class="info-label">租期</span>
|
||||
<span class="info-text">{{ getRentDays(listing) }}天</span>
|
||||
</div>
|
||||
<div class="info-line">
|
||||
<span class="info-label">常用登录地</span>
|
||||
<span class="info-text">{{ assetRegions(listing).join("、") || "--" }}</span>
|
||||
@@ -316,26 +308,9 @@ function isNavActive(path: string) {
|
||||
<!-- 底部下单栏(固定) -->
|
||||
<div class="order-bar">
|
||||
<div class="order-bar-left">
|
||||
<div class="rent-control">
|
||||
<button
|
||||
class="rent-btn"
|
||||
:disabled="rentHours <= listing.min_rent_hours"
|
||||
@click="rentHours--"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<span class="rent-value">{{ rentHours }}小时</span>
|
||||
<button
|
||||
class="rent-btn"
|
||||
:disabled="rentHours >= listing.max_rent_hours"
|
||||
@click="rentHours++"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
<div class="order-price">
|
||||
<span class="price-label">租金</span>
|
||||
<span class="price-amount">¥{{ totalRent }}</span>
|
||||
<span class="price-label">价格</span>
|
||||
<span class="price-amount">¥{{ orderTotal }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<van-button
|
||||
@@ -528,7 +503,7 @@ function isNavActive(path: string) {
|
||||
color: #ff5f00;
|
||||
}
|
||||
|
||||
/* ========== 租期信息 ========== */
|
||||
/* ========== 账号资料 ========== */
|
||||
.info-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -676,38 +651,6 @@ function isNavActive(path: string) {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.rent-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.rent-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
background: #f5f7fa;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.rent-btn:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.rent-value {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
min-width: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.order-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
@@ -35,7 +35,7 @@ async function loadOrders() {
|
||||
const statusTabs = [
|
||||
{ key: "all", label: "全部" },
|
||||
{ key: "pending_handoff", label: "待交接" },
|
||||
{ key: "renting", label: "租赁中" },
|
||||
{ key: "renting", label: "使用中" },
|
||||
{ key: "pending_return_confirm", label: "待归还" },
|
||||
{ key: "completed", label: "已完成" },
|
||||
];
|
||||
@@ -64,7 +64,7 @@ function statusColor(status: string) {
|
||||
function statusLabel(status: string) {
|
||||
const map: Record<string, string> = {
|
||||
pending_handoff: "待交接",
|
||||
renting: "租赁中",
|
||||
renting: "使用中",
|
||||
overdue: "已逾期",
|
||||
pending_return_confirm: "待归还",
|
||||
completed: "已完成",
|
||||
@@ -138,11 +138,7 @@ function goDetail(id: number) {
|
||||
<h3 class="order-title">{{ order.title }}</h3>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">租期</span>
|
||||
<span class="info-value">{{ order.rent_hours }}小时</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">租金</span>
|
||||
<span class="info-label">订单金额</span>
|
||||
<span class="info-value price">¥{{ order.rent_amount }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
|
||||
@@ -133,9 +133,6 @@ const calculatedFinalPrice = computed(() =>
|
||||
const calculatedRatioText = computed(() =>
|
||||
calculatedRatio.value > 0 ? `1:${formatNumber(calculatedRatio.value)}` : ""
|
||||
);
|
||||
const calculatedRentDays = computed(() =>
|
||||
coinMAmount.value > 0 ? calculateRentDays(coinMAmount.value) : 0
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
restoreDraft();
|
||||
@@ -356,8 +353,7 @@ async function handleSubmit() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const title = `${form.server_region} ${form.rank_level} ${form.haf_coin_amount}M哈夫币`;
|
||||
const rentDays = Math.max(calculatedRentDays.value, 1);
|
||||
const dailyPrice = roundMoney(calculatedFinalPrice.value / rentDays);
|
||||
const dailyPrice = calculatedFinalPrice.value;
|
||||
const hourlyPrice = Math.max(roundMoney(dailyPrice / 24), 0.01);
|
||||
|
||||
await createListing({
|
||||
@@ -373,8 +369,6 @@ async function handleSubmit() {
|
||||
price_daily: dailyPrice,
|
||||
price_weekly: roundMoney(dailyPrice * 7),
|
||||
deposit_amount: Number(form.deposit_amount),
|
||||
min_rent_hours: 24,
|
||||
max_rent_hours: rentDays * 24,
|
||||
});
|
||||
localStorage.removeItem(draftKey);
|
||||
showToast({ message: "发布成功", icon: "passed" });
|
||||
@@ -402,7 +396,7 @@ function validateForm() {
|
||||
if (form.deposit_amount === "" || Number(form.deposit_amount) < 0) {
|
||||
return "请填写押金";
|
||||
}
|
||||
if (!calculatedFinalPrice.value || !calculatedRentDays.value) {
|
||||
if (!calculatedFinalPrice.value) {
|
||||
return "请完善币数、保险、体力和负重后再发布";
|
||||
}
|
||||
for (const item of screenshotSlots.value) {
|
||||
@@ -426,6 +420,7 @@ function buildAssetSummary() {
|
||||
face_owner: form.face_owner,
|
||||
secret_kd: form.secret_kd,
|
||||
fire_level: Number(form.fire_level),
|
||||
publish_ratio: calculatedRatio.value,
|
||||
season_insurance: form.season_insurance,
|
||||
stamina_level: form.stamina_level,
|
||||
load_level: form.load_level,
|
||||
@@ -535,12 +530,6 @@ function getCoinCorrection(coinM: number) {
|
||||
.find((item) => coinM > item.threshold_m)?.correction || 0;
|
||||
}
|
||||
|
||||
function calculateRentDays(coinM: number) {
|
||||
return [...ratioConfig.value.rent_rules]
|
||||
.sort((a, b) => b.threshold_m - a.threshold_m)
|
||||
.find((item) => coinM >= item.threshold_m)?.days || 1;
|
||||
}
|
||||
|
||||
function formatNumber(value: number) {
|
||||
return Number.isInteger(value) ? `${value}` : `${Math.round(value * 10) / 10}`;
|
||||
}
|
||||
@@ -795,7 +784,7 @@ function readError(error: unknown, fallback: string) {
|
||||
/>
|
||||
</div>
|
||||
<p class="field-hint">
|
||||
此在线时间指的是百分百能够联系上您的时间,若是在此期间联系不上您导致无法上号会扣除您的部分租金或上架押金,在线时长太短可能无法上架,请预留充足时间用于扫码以及冻结人脸,请谨慎填写
|
||||
此在线时间指的是百分百能够联系上您的时间,若是在此期间联系不上您导致无法上号会扣除您的部分订单金额或上架押金,在线时长太短可能无法上架,请预留充足时间用于扫码以及冻结人脸,请谨慎填写
|
||||
</p>
|
||||
|
||||
<van-field label="封禁记录" required class="publish-field">
|
||||
|
||||
@@ -27,38 +27,24 @@ export function formatHafCoinM(amountWan: number) {
|
||||
}
|
||||
|
||||
export function getListingDisplayPrice(item: Listing) {
|
||||
const rentDays = getRentDays(item);
|
||||
if (rentDays > 0 && item.price_daily > 0) {
|
||||
return roundMoney(item.price_daily * rentDays);
|
||||
}
|
||||
return Number(item.price_daily || item.price_hourly || 0);
|
||||
}
|
||||
|
||||
export function getRentDays(item: Listing) {
|
||||
if (item.max_rent_hours >= 24) return Math.max(Math.round(item.max_rent_hours / 24), 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
export function getRatioValue(item: Listing) {
|
||||
const ratio = readAssetNumber(item, "publish_ratio");
|
||||
if (ratio > 0) return ratio;
|
||||
const price = getListingDisplayPrice(item);
|
||||
if (price <= 0) return 0;
|
||||
return getCoinWan(item) / price;
|
||||
}
|
||||
|
||||
export function formatRatio(item: Listing) {
|
||||
const pricePerM = getPricePerM(item);
|
||||
return pricePerM > 0 ? `1M=¥${formatMoney(pricePerM)}` : "--";
|
||||
const ratio = getRatioValue(item);
|
||||
return ratio > 0 ? `1:${formatRatioNumber(ratio)}` : "--";
|
||||
}
|
||||
|
||||
export function getValuePerYuanText(item: Listing) {
|
||||
const pricePerM = getPricePerM(item);
|
||||
return pricePerM > 0 ? `1M=¥${formatMoney(pricePerM)}` : "";
|
||||
}
|
||||
|
||||
export function getPricePerM(item: Listing) {
|
||||
const coinM = getCoinM(item);
|
||||
if (coinM <= 0) return 0;
|
||||
return roundMoney(getListingDisplayPrice(item) / coinM);
|
||||
return formatRatio(item);
|
||||
}
|
||||
|
||||
export function getLoginMethod(item: Listing) {
|
||||
@@ -83,9 +69,7 @@ export function getListingTitle(item: Listing) {
|
||||
}
|
||||
|
||||
export function getListingSubtitle(item: Listing) {
|
||||
return [getValuePerYuanText(item), `租期${getRentDays(item)}天`]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
return getValuePerYuanText(item);
|
||||
}
|
||||
|
||||
export function getListingChips(item: Listing): ListingDisplayChip[] {
|
||||
@@ -225,7 +209,7 @@ function roundMoney(value: number) {
|
||||
return Math.round(value * 100) / 100;
|
||||
}
|
||||
|
||||
function formatMoney(value: number) {
|
||||
function formatRatioNumber(value: number) {
|
||||
const rounded = roundMoney(value);
|
||||
return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(2);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const ordering = ref(false);
|
||||
const rentHours = ref(1);
|
||||
const listing = ref<Listing | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -26,7 +25,7 @@ async function handleCreateOrder() {
|
||||
if (!listing.value) return;
|
||||
ordering.value = true;
|
||||
try {
|
||||
const order = await createOrder(listing.value.id, rentHours.value);
|
||||
const order = await createOrder(listing.value.id);
|
||||
ElMessage.success("订单已创建,账号已锁定");
|
||||
await router.push(`/orders/${order.id}`);
|
||||
} catch (error) {
|
||||
@@ -44,6 +43,10 @@ function readError(error: unknown, fallback: string) {
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function listingPrice(item: Listing) {
|
||||
return Number(item.price_daily || item.price_hourly || 0).toFixed(2);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -78,12 +81,8 @@ function readError(error: unknown, fallback: string) {
|
||||
<strong>{{ listing.haf_coin_amount }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>时租</span>
|
||||
<strong>¥{{ listing.price_hourly }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>日租</span>
|
||||
<strong>¥{{ listing.price_daily }}</strong>
|
||||
<span>价格</span>
|
||||
<strong>¥{{ listingPrice(listing) }}</strong>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>押金</span>
|
||||
@@ -93,27 +92,12 @@ function readError(error: unknown, fallback: string) {
|
||||
</section>
|
||||
|
||||
<aside class="order-panel pc-order-card">
|
||||
<h2>立即租用</h2>
|
||||
<p class="order-safe-text">
|
||||
平台托管订单与押金,租期 {{ listing.min_rent_hours }}-{{
|
||||
listing.max_rent_hours
|
||||
}}
|
||||
小时。
|
||||
</p>
|
||||
<h2>立即下单</h2>
|
||||
<p class="order-safe-text">平台托管订单与押金,按平台交接流程完成账号使用。</p>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="租用时长">
|
||||
<el-input-number
|
||||
v-model="rentHours"
|
||||
:min="listing.min_rent_hours"
|
||||
:max="listing.max_rent_hours"
|
||||
class="full-control"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div class="order-total-box">
|
||||
<span>预计租金</span>
|
||||
<strong
|
||||
>¥{{ (listing.price_hourly * rentHours).toFixed(2) }}</strong
|
||||
>
|
||||
<span>价格</span>
|
||||
<strong>¥{{ listingPrice(listing) }}</strong>
|
||||
<em>押金 ¥{{ listing.deposit_amount }}</em>
|
||||
</div>
|
||||
<el-button
|
||||
|
||||
@@ -36,20 +36,20 @@ const filteredListings = computed(() => {
|
||||
const matchCoin =
|
||||
!filters.minCoin || item.haf_coin_amount >= filters.minCoin;
|
||||
const matchPrice =
|
||||
!filters.maxPrice || item.price_hourly <= filters.maxPrice;
|
||||
!filters.maxPrice || listingPrice(item) <= filters.maxPrice;
|
||||
return (
|
||||
matchKeyword && matchRegion && matchPlatform && matchCoin && matchPrice
|
||||
);
|
||||
});
|
||||
|
||||
return [...result].sort((a, b) => {
|
||||
if (sortBy.value === "price-asc") return a.price_hourly - b.price_hourly;
|
||||
if (sortBy.value === "price-asc") return listingPrice(a) - listingPrice(b);
|
||||
if (sortBy.value === "coin-desc")
|
||||
return b.haf_coin_amount - a.haf_coin_amount;
|
||||
if (sortBy.value === "deposit-asc")
|
||||
return a.deposit_amount - b.deposit_amount;
|
||||
return (
|
||||
b.haf_coin_amount - a.haf_coin_amount || a.price_hourly - b.price_hourly
|
||||
b.haf_coin_amount - a.haf_coin_amount || listingPrice(a) - listingPrice(b)
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -77,6 +77,10 @@ function resetFilters() {
|
||||
function uniqueOptions(values: string[]) {
|
||||
return [...new Set(values.filter(Boolean))];
|
||||
}
|
||||
|
||||
function listingPrice(item: Listing) {
|
||||
return Number(item.price_daily || item.price_hourly || 0);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -145,7 +149,7 @@ function uniqueOptions(values: string[]) {
|
||||
class="full-control"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="最高时租">
|
||||
<el-form-item label="最高价格">
|
||||
<el-input-number
|
||||
v-model="filters.maxPrice"
|
||||
:min="0"
|
||||
@@ -203,12 +207,9 @@ function uniqueOptions(values: string[]) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-price-box">
|
||||
<span>时租</span>
|
||||
<strong>¥{{ item.price_hourly }}</strong>
|
||||
<span>价格</span>
|
||||
<strong>¥{{ listingPrice(item).toFixed(2) }}</strong>
|
||||
<em>押金 ¥{{ item.deposit_amount }}</em>
|
||||
<small
|
||||
>{{ item.min_rent_hours }}-{{ item.max_rent_hours }} 小时</small
|
||||
>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="page-header">
|
||||
<p class="eyebrow">Earnings</p>
|
||||
<h1>收益流水</h1>
|
||||
<p>查看租金、平台抽成、结算和冻结金额。</p>
|
||||
<p>查看订单金额、平台抽成、结算和冻结金额。</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -21,14 +21,19 @@ const form = reactive({
|
||||
price_daily: 50,
|
||||
price_weekly: 300,
|
||||
deposit_amount: 100,
|
||||
min_rent_hours: 1,
|
||||
max_rent_hours: 24,
|
||||
})
|
||||
|
||||
async function handleSubmit() {
|
||||
loading.value = true
|
||||
try {
|
||||
await createListing({ ...form, screenshot_urls: screenshotUrls.value })
|
||||
const price = Number(form.price_daily || 0)
|
||||
await createListing({
|
||||
...form,
|
||||
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('发布已创建')
|
||||
await router.push('/seller/listings')
|
||||
} catch (error) {
|
||||
@@ -115,24 +120,12 @@ function readError(error: unknown, fallback: string) {
|
||||
<el-form-item label="哈夫币数量">
|
||||
<el-input-number v-model="form.haf_coin_amount" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时租">
|
||||
<el-input-number v-model="form.price_hourly" :min="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日租">
|
||||
<el-form-item label="价格">
|
||||
<el-input-number v-model="form.price_daily" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="周租">
|
||||
<el-input-number v-model="form.price_weekly" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="押金">
|
||||
<el-input-number v-model="form.deposit_amount" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最短租期(小时)">
|
||||
<el-input-number v-model="form.min_rent_hours" :min="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最长租期(小时)">
|
||||
<el-input-number v-model="form.max_rent_hours" :min="form.min_rent_hours" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-button type="primary" :loading="loading" @click="handleSubmit">保存发布</el-button>
|
||||
</el-form>
|
||||
|
||||
@@ -29,6 +29,10 @@ async function offline(id: number) {
|
||||
ElMessage.success('已下架')
|
||||
await loadListings()
|
||||
}
|
||||
|
||||
function listingPrice(row: Listing) {
|
||||
return `¥${Number(row.price_daily || row.price_hourly || 0).toFixed(2)}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -48,7 +52,9 @@ async function offline(id: number) {
|
||||
<el-table-column prop="title" label="标题" min-width="180" />
|
||||
<el-table-column prop="server_region" label="区服" width="120" />
|
||||
<el-table-column prop="haf_coin_amount" label="哈夫币" width="120" />
|
||||
<el-table-column prop="price_hourly" label="时租" width="100" />
|
||||
<el-table-column label="价格" width="100">
|
||||
<template #default="{ row }">{{ listingPrice(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deposit_amount" label="押金" width="100" />
|
||||
<el-table-column prop="status" label="状态" width="110" />
|
||||
<el-table-column prop="review_status" label="审核" width="110" />
|
||||
|
||||
Reference in New Issue
Block a user