删掉租期相关错误东西

This commit is contained in:
yml2213
2026-05-23 14:44:32 +08:00
parent e6cc9f1255
commit a6a1afb879
41 changed files with 127 additions and 312 deletions
+2 -2
View File
@@ -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,
},
-2
View File
@@ -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.
-4
View File
@@ -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 -1
View File
@@ -1,3 +1,3 @@
# Order Module
订单状态机、账号交接、租期归还、超时处理。
订单状态机、账号交接、归还、超时处理。
-2
View File
@@ -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 {
+1 -1
View File
@@ -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):
+8 -11
View File
@@ -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,
+3 -1
View File
@@ -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 -1
View File
@@ -1,3 +1,3 @@
# Wallet Module
押金冻结、租金结算、不可变资金流水。
押金冻结、订单金额结算、不可变资金流水。
-2
View File
@@ -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 '',
+10 -10
View File
@@ -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)
-20
View File
@@ -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
-4
View File
@@ -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> {
+1 -3
View File
@@ -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>
+2 -3
View File
@@ -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">
+2 -2
View File
@@ -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">
+7 -23
View File
@@ -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);
}
+11 -27
View File
@@ -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
+10 -9
View File
@@ -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" />