feat(listing): 增加押金说明配置与悬浮提示

This commit is contained in:
yml2213
2026-11-05 11:44:49 +08:00
parent 6355ca21b0
commit 15030237c4
11 changed files with 195 additions and 7 deletions
@@ -166,6 +166,7 @@ type PublishScreenshotSlot struct {
type PublishPriceConfig struct {
DepositPlaceholder string `json:"deposit_placeholder"`
DepositHint string `json:"deposit_hint"`
DepositTooltip string `json:"deposit_tooltip"`
PricePlaceholder string `json:"price_placeholder"`
RatioDescription string `json:"ratio_description"`
}
@@ -231,6 +231,9 @@ func normalizePublishOptions(options *PublishOptionsDTO) {
if options.PriceConfig.DepositHint == "" {
options.PriceConfig.DepositHint = defaults.PriceConfig.DepositHint
}
if options.PriceConfig.DepositTooltip == "" {
options.PriceConfig.DepositTooltip = defaults.PriceConfig.DepositTooltip
}
if options.PriceConfig.PricePlaceholder == "" {
options.PriceConfig.PricePlaceholder = defaults.PriceConfig.PricePlaceholder
}
@@ -93,8 +93,30 @@ func DefaultPublishOptions() PublishOptionsDTO {
PriceConfig: PublishPriceConfig{
DepositPlaceholder: "温馨提示:本押金用于保障账号安全。若买家在租用期间,使用非纯币类道具/资源(如消耗品、材料等)或导致账号被封禁,相关损失将直接从押金中扣除进行赔付。押金过高会延长出租等待时间(请自行衡量)感谢理解。",
DepositHint: "租客缴纳,封号必赔付",
PricePlaceholder: "填写币数、保险、体力和负重后自动计算;发布总价=纯币基础价+额外消耗品",
RatioDescription: "1:xx 表示 1 元人民币(RMB)等价兑换 xx 万哈夫币",
DepositTooltip: `1. 系统推荐押金(基础覆盖)
系统将根据您填写的出租皮肤、特勤处等级等信息,自动计算并生成一个推荐押金额度。该额度已覆盖上述主要资产的合理市场价值,可作为押金设置的基础参考。
2. 系统推荐押金未覆盖项目(需自行评估)
请注意,系统推荐押金暂不包含以下资产类型,您需要根据实际情况额外评估其价值:
- 收藏室内的稀有物品
- 其他具有个人纪念意义或情怀价值的虚拟资产(如限定称号、绝版道具等)
3. 未出租价值折算规则(重点)
请在「收藏室及不出租部分哈弗币价值」中填写收藏室等不出租资产折合的哈弗币。系统按 1元=50万 计算参考值,并自动更新推荐押金:低于参考值时覆盖为参考值,否则追加参考值的 50%。
示例:基础推荐押金 200 元、收藏室价值 200M → 参考值 400 元,200<400,推荐押金更新为 400 元。
目的:该举能够有效覆盖租客可能出现的洗号、违规操作导致账号封禁等风险带来的损失,为您提供充分的风险缓冲。
4. 额外保障选项
若您在设置足额押金后仍感不放心,可选择购买平台提供的《全额包赔服务》。一旦发生封号损失,将获得账号市场价值的全额赔付,让您的出租过程后顾无忧。`,
PricePlaceholder: "填写币数、保险、体力和负重后自动计算;发布总价=纯币基础价+额外消耗品",
RatioDescription: "1:xx 表示 1 元人民币(RMB)等价兑换 xx 万哈夫币",
},
DepositRecommend: PublishDepositRecommend{
BaseAmount: 50,
@@ -136,6 +136,20 @@ func TestNormalizePublishOptionsValueDefaultsQualityOwnerRatioDiscount(t *testin
}
}
func TestNormalizePublishOptionsValueDefaultsDepositTooltip(t *testing.T) {
normalized, err := normalizePublishOptionsValue(`{"schema_version":2}`)
if err != nil {
t.Fatalf("规范化失败: %v", err)
}
var options PublishOptionsDTO
if err := json.Unmarshal([]byte(normalized), &options); err != nil {
t.Fatalf("解析规范化结果失败: %v", err)
}
if !strings.Contains(options.PriceConfig.DepositTooltip, "系统推荐押金(基础覆盖)") {
t.Fatalf("押金说明默认文案缺失: %q", options.PriceConfig.DepositTooltip)
}
}
func TestNormalizePublishOptionsValuePreservesExplicitZeroPremiumReviewRatioMarkup(t *testing.T) {
options := DefaultPublishOptions()
options.RatioConfig.PremiumReviewRatioMarkup = 0
@@ -336,6 +336,14 @@ async function handleSave() {
placeholder="例如:租客缴纳,封号必赔付"
/>
</el-form-item>
<el-form-item label="押金悬浮说明">
<el-input
v-model="publishOptionsDraft.price_config.deposit_tooltip"
type="textarea"
:rows="12"
placeholder="支持换行,发布页点击或悬停押金旁的说明图标时展示"
/>
</el-form-item>
<el-form-item label="价格提示">
<el-input
v-model="publishOptionsDraft.price_config.price_placeholder"
@@ -0,0 +1,19 @@
import { describe, expect, it } from 'vitest'
import { mergeListingPublishOptions } from './listingOptions'
describe('mergeListingPublishOptions', () => {
it('读取后台配置的押金说明', () => {
const options = mergeListingPublishOptions({
price_config: {
deposit_placeholder: '',
deposit_hint: '',
deposit_tooltip: '自定义押金说明',
price_placeholder: '',
ratio_description: '',
},
})
expect(options.price_config.deposit_tooltip).toBe('自定义押金说明')
})
})
@@ -32,6 +32,7 @@ export interface PublishScreenshotSlot {
export interface PublishPriceConfig {
deposit_placeholder: string
deposit_hint: string
deposit_tooltip: string
price_placeholder: string
ratio_description: string
}
@@ -122,6 +123,29 @@ export interface ListingPublishOptions {
ratio_config: PublishRatioConfig
}
const defaultDepositTooltip = `1. 系统推荐押金(基础覆盖)
系统将根据您填写的出租皮肤、特勤处等级等信息,自动计算并生成一个推荐押金额度。该额度已覆盖上述主要资产的合理市场价值,可作为押金设置的基础参考。
2. 系统推荐押金未覆盖项目(需自行评估)
请注意,系统推荐押金暂不包含以下资产类型,您需要根据实际情况额外评估其价值:
- 收藏室内的稀有物品
- 其他具有个人纪念意义或情怀价值的虚拟资产(如限定称号、绝版道具等)
3. 未出租价值折算规则(重点)
请在「收藏室及不出租部分哈弗币价值」中填写收藏室等不出租资产折合的哈弗币。系统按 1元=50万 计算参考值,并自动更新推荐押金:低于参考值时覆盖为参考值,否则追加参考值的 50%。
示例:基础推荐押金 200 元、收藏室价值 200M → 参考值 400 元,200<400,推荐押金更新为 400 元。
目的:该举能够有效覆盖租客可能出现的洗号、违规操作导致账号封禁等风险带来的损失,为您提供充分的风险缓冲。
4. 额外保障选项
若您在设置足额押金后仍感不放心,可选择购买平台提供的《全额包赔服务》。一旦发生封号损失,将获得账号市场价值的全额赔付,让您的出租过程后顾无忧。`
export const emptyListingPublishOptions: ListingPublishOptions = {
unlock_demulan_enabled: false,
next_season_pass_enabled: false,
@@ -141,6 +165,7 @@ export const emptyListingPublishOptions: ListingPublishOptions = {
price_config: {
deposit_placeholder: '',
deposit_hint: '租客缴纳,封号必赔付',
deposit_tooltip: defaultDepositTooltip,
price_placeholder: '',
ratio_description: '',
},
@@ -325,6 +350,10 @@ function normalizePriceConfig(value?: unknown): PublishPriceConfig {
typeof row.deposit_hint === 'string' && row.deposit_hint.trim()
? row.deposit_hint.trim()
: emptyListingPublishOptions.price_config.deposit_hint,
deposit_tooltip:
typeof row.deposit_tooltip === 'string' && row.deposit_tooltip.trim()
? row.deposit_tooltip.trim()
: emptyListingPublishOptions.price_config.deposit_tooltip,
price_placeholder:
typeof row.price_placeholder === 'string' ? row.price_placeholder.trim() : '',
ratio_description:
@@ -837,6 +837,22 @@
min-width: 0;
}
.deposit-heading .deposit-heading-title {
gap: 4px;
}
.deposit-guide-trigger {
display: inline-grid;
width: 22px;
height: 22px;
padding: 0;
place-items: center;
border: 0;
background: transparent;
color: #f97316;
font-size: 17px;
}
.deposit-heading b {
color: #f04452;
font-size: 13px;
@@ -129,6 +129,16 @@ function openPublishAgreement(agreement: AgreementContent) {
agreementPopupVisible.value = true
}
function showDepositGuide() {
if (!priceConfig.value.deposit_tooltip) return
void showDialog({
title: '押金说明',
message: priceConfig.value.deposit_tooltip,
messageAlign: 'left',
confirmButtonText: '我知道了',
})
}
function selectRadio<T extends string>(value: T, setter: (value: T) => void) {
setter(value)
}
@@ -689,7 +699,18 @@ const screenshotGuides: Record<string, string> = {
<h2 class="section-title">押金与价格</h2>
<div class="deposit-card">
<label class="deposit-heading">
<span>押金<b>*</b></span>
<span class="deposit-heading-title">
押金<b>*</b>
<button
v-if="priceConfig.deposit_tooltip"
type="button"
class="deposit-guide-trigger"
aria-label="查看押金说明"
@click.prevent.stop="showDepositGuide"
>
<van-icon name="info-o" />
</button>
</span>
<em v-if="priceConfig.deposit_hint">{{ priceConfig.deposit_hint }}</em>
</label>
<div class="deposit-input-wrap">
@@ -164,6 +164,44 @@
align-items: center;
}
.deposit-label-title {
display: inline-flex;
align-items: center;
gap: 3px;
}
.deposit-guide-trigger {
display: inline-grid;
width: 18px;
height: 18px;
padding: 0;
place-items: center;
border: 0;
background: transparent;
color: var(--color-orange-primary);
cursor: help;
}
.deposit-guide-trigger:hover,
.deposit-guide-trigger:focus-visible {
color: var(--color-orange-dark);
outline: none;
}
.deposit-guide-content {
max-height: min(62vh, 560px);
overflow-y: auto;
color: #3f4753;
font-size: 13px;
line-height: 1.75;
white-space: pre-wrap;
}
:global(.deposit-guide-popper.el-popover) {
max-width: min(560px, calc(100vw - 32px));
padding: 14px 16px;
}
.deposit-label em {
display: inline-flex;
align-items: center;
@@ -3,6 +3,7 @@ import {
ArrowRight,
Delete,
DocumentChecked,
InfoFilled,
Picture,
RefreshRight,
UploadFilled,
@@ -544,10 +545,26 @@ function selectDailyLoss(value: string | number) {
<PublishSection title="押金与价格" description="系统按资料自动计算售价">
<div class="compact-grid two">
<label class="input-block">
<span class="deposit-label"
>押金<b>*</b
><em v-if="priceConfig.deposit_hint">{{ priceConfig.deposit_hint }}</em></span
>
<span class="deposit-label">
<span class="deposit-label-title">
押金<b>*</b>
<el-popover
v-if="priceConfig.deposit_tooltip"
placement="top-start"
:width="560"
trigger="hover"
popper-class="deposit-guide-popper"
>
<template #reference>
<button type="button" class="deposit-guide-trigger" aria-label="查看押金说明">
<el-icon><InfoFilled /></el-icon>
</button>
</template>
<div class="deposit-guide-content">{{ priceConfig.deposit_tooltip }}</div>
</el-popover>
</span>
<em v-if="priceConfig.deposit_hint">{{ priceConfig.deposit_hint }}</em>
</span>
<div class="deposit-control">
<el-input-number
v-model="form.depositAmountYuan"