diff --git a/backend/internal/modules/qualityowner/dto.go b/backend/internal/modules/qualityowner/dto.go
index 9263ac12..a72d2de2 100644
--- a/backend/internal/modules/qualityowner/dto.go
+++ b/backend/internal/modules/qualityowner/dto.go
@@ -3,26 +3,33 @@ package qualityowner
import (
"encoding/json"
"errors"
+ "strings"
"time"
)
const (
- ConfigKey = "quality_owner.rules"
- StatusNone = "none"
- StatusActive = "active"
- DepositSourcePaid = "paid"
- DepositSourceManual = "manual"
- defaultDepositCent = int64(20_000)
- defaultInsuranceBps = 800
- defaultPremiumRateBps = 400
+ ConfigKey = "quality_owner.rules"
+ StatusNone = "none"
+ StatusActive = "active"
+ DepositSourcePaid = "paid"
+ DepositSourceManual = "manual"
+ defaultDepositCent = int64(20_000)
+ defaultInsuranceBps = 800
+ defaultPremiumRateBps = 400
+ defaultAgreementReadSeconds = 15
+ defaultAgreementTitle = "优质号主服务协议"
+ MaxAgreementReadSeconds = 300
)
var ErrInvalidRules = errors.New("invalid quality owner rules")
type Rules struct {
- DepositAmountCent int64 `json:"deposit_amount_cent"`
- InsuranceFeeBps int `json:"insurance_fee_bps"`
- PremiumInsuranceFeeBps int `json:"premium_insurance_fee_bps"`
+ DepositAmountCent int64 `json:"deposit_amount_cent"`
+ InsuranceFeeBps int `json:"insurance_fee_bps"`
+ PremiumInsuranceFeeBps int `json:"premium_insurance_fee_bps"`
+ AgreementTitle string `json:"agreement_title"`
+ AgreementContent string `json:"agreement_content"`
+ AgreementReadSeconds int `json:"agreement_read_seconds"`
}
func DefaultRules() Rules {
@@ -30,6 +37,156 @@ func DefaultRules() Rules {
DepositAmountCent: defaultDepositCent,
InsuranceFeeBps: defaultInsuranceBps,
PremiumInsuranceFeeBps: defaultPremiumRateBps,
+ AgreementTitle: defaultAgreementTitle,
+ AgreementReadSeconds: defaultAgreementReadSeconds,
+ AgreementContent: `本协议由大锤商行平台(以下简称“平台”)与申请成为“优质号主”的用户(以下简称“号主”)共同签署,自号主勾选同意并完成信用保证金冻结之日起生效。
+
+一、总则
+
+1.1 为保障平台账号租赁交易的秩序与安全,平台推出「优质号主」认证计划,旨在筛选信誉良好、服务高效的号主,为其提供专属权益与曝光资源,包括将账号发布至「极品专区」并享有专项收益比例等。
+
+1.2 号主自愿申请成为优质号主,并承诺遵守本协议全部条款。
+
+二、定义
+
+| 术语 | 定义 |
+|---|---|
+| 优质号主 | 通过平台认证、完成200元信用保证金冻结并遵守本协议的高信誉号主 |
+| 《极品专区》 | 优质号主可选择发布的专属渠道,仅限大锤商行至尊SVIP用户租用;优质号主在该渠道发布账号可享有专项收益比例及专属标识 |
+| 《大锤商行》 | 优质号主可选择发布的公开渠道,面向全平台注册用户租用 |
+| 信用保证金(押金) | 号主为获得优质号主发布资格而授权平台冻结的200元信用保证金,仅冻结、不扣除 |
+| 大锤商行至尊SVIP | 平台最高等级VIP用户,须同时满足租客免押资格方可租用《极品专区》渠道账号 |
+
+三、成为优质号主的条件
+
+3.1 信用保证金标准
+
+本计划仅设一个信用保证金档位,所有申请者统一适用:
+
+| 申请类型 | 信用保证金 | 备注 |
+|---|---:|---|
+| 所有申请者 | 200元 | 仅冻结,不扣除;不设其他金额档位或免押档位 |
+
+3.2 强制前置条件
+
+所有申请者须强制阅读《账号出租号主须知》满15秒,方可继续申请流程。
+
+3.3 信用保证金特别约定
+
+3.3.1 保证金性质:200元为信用保证金,仅用于信用展示及发布资格核验,不属于租赁费用、平台服务收入、违约金或处罚金。平台不扣除、不没收该保证金。
+
+3.3.2 冻结方式:号主申请成为优质号主时,授权平台冻结200元信用保证金。冻结期间,该保证金不参与扣罚。
+
+3.3.3 解冻条件:号主在无正在进行中的订单且无未解决的纠纷时,可随时向平台申请解冻全部信用保证金。平台审核通过后,按原支付路径或平台规则允许的方式解冻退还。
+
+3.3.4 校验机制:每次发布账号时,系统自动核验信用保证金是否足额冻结:
+
+- 已足额冻结 → 可正常发布;
+- 未冻结或冻结不足 → 不可发布,系统弹窗提示“请先冻结200元信用保证金”。
+
+3.3.5 违规处理与保证金:号主单方面违规的,平台有权依据本协议及平台规则采取警告、下架账号、限制发布、取消优质号主资格等措施,但信用保证金不扣除。如存在未完成订单或未解决纠纷,信用保证金可继续冻结至相关事项处理完毕。
+
+四、优质号主的权利
+
+成功成为优质号主后,享有以下权益:
+
+| 序号 | 权益内容 | 具体说明 |
+|---|---|---|
+| 4.1 | 专项收益比例 | 优质号主将账号发布至「极品专区」的,享有专项收益比例:号主到手金额比例较普通渠道上浮1个百分点(即平台服务费比例相应下调1个百分点,具体以发布页面公示为准);该权益可与VIP权益及优惠券叠加使用 |
+| 4.2 | 发布渠道选择 | 发布时可自由选择投放至「极品专区」或「大锤商行」(详见第五条) |
+| 4.3 | 特殊曝光标识 | 优质号主发布的账号显示“优质号主”专属标识;发布至「极品专区」的账号同时展示“极品专区”专属标签 |
+| 4.4 | 包赔手续费减半 | 购买全额包赔服务,手续费由8%降至4% |
+| 4.5 | 平台兜底保障 | 非因号主明显过错导致的问题(如租客恶意损坏账号等),由平台按规则负责兜底赔偿 |
+
+五、发布渠道规则
+
+优质号主在每次发布账号时,必须从以下两个渠道中二选一:
+
+5.1 渠道对比
+
+| 对比项 | 《极品专区》 | 《大锤商行》 |
+|---|---|---|
+| 租用资格 | 仅限大锤商行至尊SVIP,且须具备租客免押资格 | 全平台注册用户 |
+| 租客筛选标准 | 累计消费≥1万元 + 无纠纷记录 + 无封禁记录 | 无特殊限制 |
+| 适用场景 | 高客单价、极致安全、专项收益比例 | 高曝光、快速出单 |
+
+5.2 关于《极品专区》渠道
+
+5.2.1 选择该渠道的账号,将展示“极品专区”专属标签。
+
+5.2.2 非大锤商行至尊SVIP用户,或虽为大锤商行至尊SVIP但无租客免押资格的,不可查看及租用该渠道账号。
+
+5.2.3 该渠道租客均为平台严选的优质租客,号主可享受“基本绝对安全”的出租体验。
+
+5.2.4 优质号主在该渠道发布账号,可按本协议第4.1款享有专项收益比例。
+
+5.3 关于《大锤商行》渠道
+
+5.3.1 选择该渠道的账号,面向全体平台注册用户开放租用。
+
+5.3.2 号主仍享有平台兜底保障权益(详见第4.5款)。
+
+六、号主的义务与违规处理
+
+6.1 号主的基本义务
+
+优质号主在交易中应当:
+
+6.1.1 按照约定时间交付账号,不得无故毁约、爽约;
+
+6.1.2 在租客使用过程中,及时配合上号验证;
+
+6.1.3 在需要时,积极配合完成人脸识别解除等操作;
+
+6.1.4 不得在出租期间擅自顶号、修改密码或通过其他方式收回账号;
+
+6.1.5 与租客保持友善沟通,不得出现辱骂、威胁等不文明行为。
+
+6.2 违规行为与处罚
+
+平台对违规行为采取警告、下架、限制发布、取消资格等措施;信用保证金不扣除。
+
+| 违规行为 | 处理措施 |
+|---|---|
+| 顶号 | 警告、立即下架、限制发布;情节严重者取消优质号主资格 |
+| 毁约不出租 | 警告、下架、限制发布;情节严重者取消优质号主资格 |
+| 无法及时配合上号 | 警告、限制发布;多次违规者取消资格 |
+| 无法配合解除人脸 | 警告、下架、限制发布;情节严重者取消资格 |
+| 其他违反平台规则的行为 | 依据平台规则处理 |
+
+上述处理均不扣除信用保证金;如存在未完成订单或未解决纠纷,信用保证金可继续冻结至相关事项处理完毕。
+
+七、收益与结算
+
+7.1 号主通过本平台获得的租赁收入,平台将按对应渠道的结算规则进行结算。
+
+7.2 优质号主将账号发布至「极品专区」的,按本协议第4.1款享有专项收益比例;该权益可与VIP权益及优惠券叠加使用。
+
+7.3 平台将在租客确认收货后将收益划转至号主账户余额,号主可随时申请提现。
+
+八、免责条款
+
+8.1 因不可抗力(包括但不限于自然灾害、战争、罢工、政府行为、网络故障、系统维护等)导致协议无法履行的,双方互不承担责任。
+
+8.2 平台有权根据法律法规变化或业务运营需要,对本协议进行修订,修订后的协议将在平台公示。如号主继续使用优质号主服务,视为接受修订内容。
+
+九、协议终止
+
+9.1 号主可随时申请退出优质号主计划。满足信用保证金解冻条件的,200元信用保证金予以解冻退还,优质号主资格即时取消。
+
+9.2 号主严重违反本协议或平台规则的,平台有权单方面取消其优质号主资格。信用保证金不扣除;如存在未完成订单或未解决纠纷,可继续冻结至相关事项处理完毕。
+
+9.3 优质号主资格取消后,对应的权益(特殊曝光、包赔手续费减半、发布渠道选择、极品专区专项收益比例等)同步终止。
+
+十、附则
+
+10.1 本协议的解释权归大锤商行平台所有。
+
+10.2 本协议自号主勾选同意并完成200元信用保证金冻结之日起生效。
+
+10.3 如本协议与平台其他规则存在冲突,以本协议为准。
+
+本人已详细阅读并充分理解本协议全部内容,同意遵守上述条款。`,
}
}
@@ -55,6 +212,15 @@ func ParseConfigValue(value string) (Rules, error) {
if rules.PremiumInsuranceFeeBps < 0 || rules.PremiumInsuranceFeeBps > rules.InsuranceFeeBps {
return Rules{}, ErrInvalidRules
}
+ rules.AgreementTitle = strings.TrimSpace(rules.AgreementTitle)
+ rules.AgreementContent = strings.TrimSpace(rules.AgreementContent)
+ if rules.AgreementTitle == "" || rules.AgreementContent == "" {
+ return Rules{}, ErrInvalidRules
+ }
+ if rules.AgreementReadSeconds < defaultAgreementReadSeconds ||
+ rules.AgreementReadSeconds > MaxAgreementReadSeconds {
+ return Rules{}, ErrInvalidRules
+ }
return rules, nil
}
@@ -79,6 +245,9 @@ type StatusDTO struct {
RequiredDepositCent int64 `json:"required_deposit_cent"`
InsuranceFeeBps int `json:"insurance_fee_bps"`
PremiumInsuranceFeeBps int `json:"premium_insurance_fee_bps"`
+ AgreementTitle string `json:"agreement_title"`
+ AgreementContent string `json:"agreement_content"`
+ AgreementReadSeconds int `json:"agreement_read_seconds"`
}
type AdminSetRequest struct {
diff --git a/backend/internal/modules/qualityowner/dto_test.go b/backend/internal/modules/qualityowner/dto_test.go
index 110355fe..3a3b8688 100644
--- a/backend/internal/modules/qualityowner/dto_test.go
+++ b/backend/internal/modules/qualityowner/dto_test.go
@@ -10,6 +10,11 @@ func TestParseConfigValueDefaultsAndValidation(t *testing.T) {
if rules.DepositAmountCent != defaultDepositCent || rules.InsuranceFeeBps != 800 || rules.PremiumInsuranceFeeBps != 400 {
t.Fatalf("unexpected rules: %+v", rules)
}
+ if rules.AgreementTitle != defaultAgreementTitle ||
+ rules.AgreementReadSeconds != defaultAgreementReadSeconds ||
+ rules.AgreementContent == "" {
+ t.Fatalf("unexpected agreement defaults: %+v", rules)
+ }
if _, err := ParseConfigValue(`{"deposit_amount_cent":20000,"insurance_fee_bps":400,"premium_insurance_fee_bps":800}`); err == nil {
t.Fatal("expected premium rate above normal rate to be rejected")
@@ -17,6 +22,9 @@ func TestParseConfigValueDefaultsAndValidation(t *testing.T) {
if _, err := ParseConfigValue(`{"deposit_amount_cent":0}`); err == nil {
t.Fatal("expected zero deposit to be rejected")
}
+ if _, err := ParseConfigValue(`{"agreement_read_seconds":14}`); err == nil {
+ t.Fatal("expected agreement reading time below 15 seconds to be rejected")
+ }
}
func TestDefaultConfigValueCanBeParsed(t *testing.T) {
@@ -27,4 +35,7 @@ func TestDefaultConfigValueCanBeParsed(t *testing.T) {
if rules.DepositAmountCent != 20000 || rules.InsuranceFeeBps != 800 || rules.PremiumInsuranceFeeBps != 400 {
t.Fatalf("unexpected default rules: %+v", rules)
}
+ if rules.AgreementReadSeconds != 15 || rules.AgreementTitle == "" || rules.AgreementContent == "" {
+ t.Fatalf("unexpected default agreement: %+v", rules)
+ }
}
diff --git a/backend/internal/modules/qualityowner/repository.go b/backend/internal/modules/qualityowner/repository.go
index c1d4d04a..ee7a22d7 100644
--- a/backend/internal/modules/qualityowner/repository.go
+++ b/backend/internal/modules/qualityowner/repository.go
@@ -468,5 +468,8 @@ func toStatusDTO(user model.User, account model.WalletAccount, rules Rules, acti
RequiredDepositCent: rules.DepositAmountCent,
InsuranceFeeBps: rules.InsuranceFeeBps,
PremiumInsuranceFeeBps: rules.PremiumInsuranceFeeBps,
+ AgreementTitle: rules.AgreementTitle,
+ AgreementContent: rules.AgreementContent,
+ AgreementReadSeconds: rules.AgreementReadSeconds,
}
}
diff --git a/backend/internal/modules/systemconfig/repository.go b/backend/internal/modules/systemconfig/repository.go
index 37fc41a2..bad3547e 100644
--- a/backend/internal/modules/systemconfig/repository.go
+++ b/backend/internal/modules/systemconfig/repository.go
@@ -40,7 +40,7 @@ var defaultConfigs = []defaultConfig{
{Key: "order.return_overdue_grace_minutes", Value: "10", Description: "预计截止后结账宽限分钟数"},
{Key: "listing.review_required", Value: "false", Description: "发布账号是否需要后台人工审核"},
{Key: "listing.publish_cooldown_minutes", Value: "5", Description: "同一用户两次新增发布账号的最小间隔分钟数(0 表示关闭)"},
- {Key: qualityowner.ConfigKey, Value: qualityowner.DefaultConfigValue(), Description: "优质号主保证金与无忧租号保险费率配置 JSON"},
+ {Key: qualityowner.ConfigKey, Value: qualityowner.DefaultConfigValue(), Description: "优质号主保证金、开通协议与无忧租号保险费率配置 JSON"},
{Key: listingPublishAgreementsConfigKey, Value: defaultListingPublishAgreementsConfigValue(), Description: "发布账号前协议配置 JSON"},
{Key: orderAgreementsConfigKey, Value: defaultOrderAgreementsConfigValue(), Description: "下单前协议配置 JSON"},
{Key: postRentalNoticeConfigKey, Value: defaultPostRentalNoticeConfigValue(), Description: "租后须知配置 JSON"},
@@ -134,6 +134,12 @@ func (r *Repository) List(ctx context.Context) ([]ConfigDTO, error) {
}
items := make([]ConfigDTO, 0, len(rows))
for _, row := range rows {
+ if row.Key == qualityowner.ConfigKey {
+ rules, err := qualityowner.ParseConfigValue(row.Value)
+ if err == nil {
+ row.Value, _ = qualityowner.MarshalConfig(rules)
+ }
+ }
items = append(items, toDTO(row))
}
return items, nil
diff --git a/frontend/src/features/admin/components/QualityOwnerConfigDialog.vue b/frontend/src/features/admin/components/QualityOwnerConfigDialog.vue
index 7b31daf0..5365b6d0 100644
--- a/frontend/src/features/admin/components/QualityOwnerConfigDialog.vue
+++ b/frontend/src/features/admin/components/QualityOwnerConfigDialog.vue
@@ -19,6 +19,9 @@ const form = reactive({
depositYuan: 200,
insurancePercent: 8,
premiumInsurancePercent: 4,
+ agreementTitle: '',
+ agreementContent: '',
+ agreementReadSeconds: 15,
description: '',
saving: false,
})
@@ -32,10 +35,18 @@ watch(
form.depositYuan = Number(parsed.deposit_amount_cent || 20000) / 100
form.insurancePercent = Number(parsed.insurance_fee_bps || 800) / 100
form.premiumInsurancePercent = Number(parsed.premium_insurance_fee_bps || 400) / 100
+ form.agreementTitle =
+ typeof parsed.agreement_title === 'string' ? parsed.agreement_title : '优质号主服务协议'
+ form.agreementContent =
+ typeof parsed.agreement_content === 'string' ? parsed.agreement_content : ''
+ form.agreementReadSeconds = Math.max(15, Number(parsed.agreement_read_seconds || 15))
} catch {
form.depositYuan = 200
form.insurancePercent = 8
form.premiumInsurancePercent = 4
+ form.agreementTitle = '优质号主服务协议'
+ form.agreementContent = ''
+ form.agreementReadSeconds = 15
}
form.description = props.config.description || ''
},
@@ -55,6 +66,14 @@ async function save() {
ElMessage.warning('极品专区费率不能高于普通保险费率')
return
}
+ if (!form.agreementTitle.trim() || !form.agreementContent.trim()) {
+ ElMessage.warning('请填写优质号主协议标题和正文')
+ return
+ }
+ if (form.agreementReadSeconds < 15 || form.agreementReadSeconds > 300) {
+ ElMessage.warning('强制阅读时长必须在 15-300 秒之间')
+ return
+ }
form.saving = true
try {
await updateSystemConfig(props.config.key, {
@@ -62,6 +81,9 @@ async function save() {
deposit_amount_cent: Math.round(form.depositYuan * 100),
insurance_fee_bps: Math.round(form.insurancePercent * 100),
premium_insurance_fee_bps: Math.round(form.premiumInsurancePercent * 100),
+ agreement_title: form.agreementTitle.trim(),
+ agreement_content: form.agreementContent.trim(),
+ agreement_read_seconds: Math.round(form.agreementReadSeconds),
}),
description: form.description,
})
@@ -80,7 +102,8 @@ async function save() {
Premium Owner
请完整阅读后勾选同意并继续开通
+