feat: 添加租后须知功能并优化发布默认设置

新增功能:
- 添加租后须知查看功能(PC端+移动端)
- 管理后台支持配置租后须知内容
- 默认提供完整的租后须知模板

功能实现:
- 后端:新增 PostRentalNotice API 接口和配置管理
- PC端:在用户下拉菜单添加租后须知入口,独立页面展示
- 移动端:在个人中心实名认证下方添加入口,弹窗展示
- 管理端:新增租后须知配置弹窗,支持编辑和恢复默认

优化改进:
- 修复发布页面默认选中"参考比例"而非"自定比例"
- 移除错误的迁移测试脚本(与 MySQL 8.0 不兼容)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
yml
2026-06-05 09:55:11 +08:00
co-authored by Claude Opus 4.7
parent 0b9b0b5ea7
commit 5d5bd5af77
15 changed files with 563 additions and 201 deletions
@@ -32,6 +32,11 @@ type OrderAgreementsDTO struct {
RenterAgreement AgreementContentDTO `json:"renter_agreement"`
}
type PostRentalNoticeDTO struct {
Title string `json:"title"`
Content string `json:"content"`
}
type AgreementContentDTO struct {
Title string `json:"title"`
Content string `json:"content"`
@@ -54,6 +54,15 @@ func (h *Handler) OrderAgreements(c *gin.Context) {
response.OK(c, agreements)
}
func (h *Handler) PostRentalNotice(c *gin.Context) {
notice, err := h.service.PostRentalNotice()
if err != nil {
writeConfigError(c, err)
return
}
response.OK(c, notice)
}
func (h *Handler) HomeAnnouncements(c *gin.Context) {
announcements, err := h.service.HomeAnnouncements()
if err != nil {
@@ -0,0 +1,52 @@
package systemconfig
import "encoding/json"
const postRentalNoticeConfigKey = "profile.post_rental_notice"
func defaultPostRentalNoticeConfigValue() string {
raw, err := json.Marshal(DefaultPostRentalNotice())
if err != nil {
return "{}"
}
return string(raw)
}
func DefaultPostRentalNotice() PostRentalNoticeDTO {
return PostRentalNoticeDTO{
Title: "租后须知(重要!)",
Content: `一、上号前须知
1. 租期说明
租期内无法退租(除非发现不可抗力),如遇游戏的风控策略调整导致账号异常,如账号主动提交过人脸识别认证(按交易协议比例赔偿)。
2. 账号信息不齐
租客流需在首次上号后10分钟完成验号,超时则单次以租期已开始计算,此后中断与异常问题由租客自行承担。
仅以在首次上号10分钟之前完成查验;
未上号马号主与关联等,如果出现异常抢拍后经过时,除押金全额返还后,可能会影响结账比例规则判定。
二、账号使用规范
3. 切勿使用规制
账号内绑"绑币"外,其它物品谨慎消耗,送意开货时用品、子弹包、资源包、全能包、合金包。
活动赠送或抽奖券及二次充值。
若账号因租客违规操作、使用外挂、恶意消耗、转移资产或违反游戏规则造成损失,可能从押金中扣除或进入争议处理,根本扣除额按按违规严重程度而定。
4. 禁止转租
不可转租、转借或用于任何非订单目的。
号主无法输入人脸验证;按交接规则卫按级号号不得修改账号绑定信息或任何影响号主取回账号的内容。
三、封禁处理规则
5. 账号封禁
账号封禁判责规则按"按照订单记录、截图证据、聊天记录和双方说明"进行判断,若租客操作符合规则但仍封禁,须按封禁情况审核后按实际损失或押金比例约40%补偿号主。
不可转按换现金或按时归还后不予退租,按照平台规则处理退款和赔付。`,
}
}
@@ -32,6 +32,7 @@ var defaultConfigs = []defaultConfig{
{Key: "order.return_overdue_grace_minutes", Value: "10", Description: "预计截止后结账宽限分钟数"},
{Key: "listing.review_required", Value: "false", Description: "发布账号是否需要后台人工审核"},
{Key: orderAgreementsConfigKey, Value: defaultOrderAgreementsConfigValue(), Description: "下单前协议配置 JSON"},
{Key: postRentalNoticeConfigKey, Value: defaultPostRentalNoticeConfigValue(), Description: "租后须知配置 JSON"},
{Key: "chat.default_support_admin_id", Value: "1", Description: "订单群聊回退客服 ID(仅当无可用客服时使用)"},
{Key: "chat.auto_welcome_message", Value: "欢迎加入订单群聊!如有任何问题,请随时沟通。", Description: "建群后自动发送的欢迎话术"},
{Key: homeAnnouncementsConfigKey, Value: defaultHomeAnnouncementsConfigValue(), Description: "移动端首页公告 JSON 数组"},
@@ -54,6 +55,7 @@ var adminVisibleConfigKeys = []string{
"order.agreements",
"order.pending_payment_timeout_minutes",
"order.return_overdue_grace_minutes",
"profile.post_rental_notice",
}
func NewRepository(db *gorm.DB) *Repository {
@@ -59,6 +59,17 @@ func (s *Service) OrderAgreements() (*OrderAgreementsDTO, error) {
return &agreements, nil
}
func (s *Service) PostRentalNotice() (*PostRentalNoticeDTO, error) {
if s.repo == nil {
return nil, ErrDependencyUnavailable
}
notice, err := s.postRentalNotice()
if err != nil {
return nil, err
}
return &notice, nil
}
func (s *Service) publishOptions() (PublishOptionsDTO, error) {
value, err := s.repo.FindValue(publishOptionsConfigKey)
if err != nil {
@@ -98,6 +109,19 @@ func (s *Service) orderAgreements() (OrderAgreementsDTO, error) {
return agreements, nil
}
func (s *Service) postRentalNotice() (PostRentalNoticeDTO, error) {
value, err := s.repo.FindValue(postRentalNoticeConfigKey)
if err != nil {
return PostRentalNoticeDTO{}, err
}
notice := DefaultPostRentalNotice()
if err := json.Unmarshal([]byte(value), &notice); err != nil {
notice = DefaultPostRentalNotice()
}
normalizePostRentalNotice(&notice)
return notice, nil
}
func (s *Service) HomeAnnouncements() (*HomeAnnouncementsDTO, error) {
if s.repo == nil {
return nil, ErrDependencyUnavailable
@@ -223,6 +247,18 @@ func normalizeOrderAgreements(agreements *OrderAgreementsDTO) {
}
}
func normalizePostRentalNotice(notice *PostRentalNoticeDTO) {
defaults := DefaultPostRentalNotice()
notice.Title = strings.TrimSpace(notice.Title)
notice.Content = strings.TrimSpace(notice.Content)
if notice.Title == "" {
notice.Title = defaults.Title
}
if notice.Content == "" {
notice.Content = defaults.Content
}
}
func normalizePublishOptions(options *PublishOptionsDTO) {
defaults := DefaultPublishOptions()
if len(options.ServerOptions) == 0 {