From 0a42392d5e05650a2e79627cb7d957cb228e3c67 Mon Sep 17 00:00:00 2001 From: yml Date: Wed, 17 Jun 2026 20:59:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8F=91=E5=B8=83=E7=BE=A4?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=B8=8E=E4=BA=8C=E7=BB=B4=E7=A0=81=E6=B1=A0?= =?UTF-8?q?=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/chat/handler_quick_reply.go | 20 ++ .../internal/modules/chat/listing_group.go | 9 +- backend/internal/modules/chat/quick_reply.go | 27 ++- backend/internal/modules/chat/service.go | 14 ++ .../modules/systemconfig/repository.go | 37 +++ backend/internal/router/router.go | 2 + .../000010_listing_group_welcome_config.sql | 20 ++ .../admin/components/AutoWelcomeConfig.vue | 45 +++- .../admin/views/AdminQrCodePoolView.vue | 221 +++++++++++++++--- .../admin/views/AdminSystemConfigsView.vue | 52 ++++- frontend/src/features/chats/api/chats.ts | 15 ++ .../src/features/chats/views/ChatView.vue | 34 ++- .../features/chats/views/MobileChatView.vue | 33 ++- .../shared/components/business/AuthImage.vue | 5 + 14 files changed, 460 insertions(+), 74 deletions(-) create mode 100644 backend/migrations/000010_listing_group_welcome_config.sql diff --git a/backend/internal/modules/chat/handler_quick_reply.go b/backend/internal/modules/chat/handler_quick_reply.go index 9f704f5..ca24791 100644 --- a/backend/internal/modules/chat/handler_quick_reply.go +++ b/backend/internal/modules/chat/handler_quick_reply.go @@ -97,3 +97,23 @@ func (h *Handler) AdminUpdateAutoWelcome(c *gin.Context) { } response.OK(c, gin.H{"updated": true}) } + +func (h *Handler) AdminGetListingGroupWelcome(c *gin.Context) { + message := h.service.GetListingGroupWelcomeMessage(c.Request.Context()) + response.OK(c, gin.H{"message": message}) +} + +func (h *Handler) AdminUpdateListingGroupWelcome(c *gin.Context) { + var req struct { + Message string `json:"message" binding:"required"` + } + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "欢迎语内容不能为空") + return + } + if err := h.service.UpdateListingGroupWelcomeMessage(c.Request.Context(), req.Message); err != nil { + writeChatError(c, err) + return + } + response.OK(c, gin.H{"updated": true}) +} diff --git a/backend/internal/modules/chat/listing_group.go b/backend/internal/modules/chat/listing_group.go index f3985de..6ae9683 100644 --- a/backend/internal/modules/chat/listing_group.go +++ b/backend/internal/modules/chat/listing_group.go @@ -189,14 +189,12 @@ func listingConversationTitle(listing model.RentalListing) string { } func getListingGroupWelcomeMessage(tx *gorm.DB) string { - defaultMsg := "欢迎加入账号群!请号主扫描下方二维码加入企业微信群,方便客服与您及时联系。" - var cfg model.SystemConfig - if err := tx.Where("`key` = ?", "chat.listing_group_welcome").First(&cfg).Error; err == nil && cfg.Value != "" { + if err := tx.Where("`key` = ?", listingGroupWelcomeConfigKey).First(&cfg).Error; err == nil && cfg.Value != "" { return cfg.Value } - return defaultMsg + return defaultListingGroupWelcome } func sendSystemMessage(tx *gorm.DB, conversationID uint64, content string) error { @@ -218,7 +216,6 @@ func sendSystemMessage(tx *gorm.DB, conversationID uint64, content string) error } func sendQrCodeImage(tx *gorm.DB, conversationID uint64, imageURL string) error { - attachmentURLs := `["` + imageURL + `"]` content := "👇 请扫码加入企业微信群" message := model.ChatMessage{ @@ -227,7 +224,7 @@ func sendQrCodeImage(tx *gorm.DB, conversationID uint64, imageURL string) error SenderRole: "system", ContentType: "image", Content: content, - AttachmentURLS: []byte(attachmentURLs), + AttachmentURLS: encodeStringList([]string{imageURL}), } if err := tx.Create(&message).Error; err != nil { diff --git a/backend/internal/modules/chat/quick_reply.go b/backend/internal/modules/chat/quick_reply.go index 9b2d117..de1abcb 100644 --- a/backend/internal/modules/chat/quick_reply.go +++ b/backend/internal/modules/chat/quick_reply.go @@ -5,6 +5,13 @@ import ( "hfb_sys/backend/internal/model" ) +const ( + autoWelcomeConfigKey = "chat.auto_welcome_message" + defaultAutoWelcomeMessage = "欢迎加入订单群聊!如有任何问题,请随时沟通。" + listingGroupWelcomeConfigKey = "chat.listing_group_welcome" + defaultListingGroupWelcome = "欢迎加入账号群!请号主扫描下方二维码加入企业微信群,方便客服与您及时联系。" +) + func (r *Repository) UpdateRemark(ctx context.Context, principal Principal, conversationID uint64, remark string) error { return r.db.WithContext(ctx).Model(&model.ChatParticipant{}). Where("conversation_id = ? AND participant_type = ? AND participant_id = ?", conversationID, principal.Type, principal.ID). @@ -77,13 +84,27 @@ func (r *Repository) DeleteQuickReply(ctx context.Context, adminID uint64, reply } func (r *Repository) GetAutoWelcomeMessage(ctx context.Context) string { var cfg model.SystemConfig - if err := r.db.WithContext(ctx).Where("`key` = ?", "chat.auto_welcome_message").First(&cfg).Error; err != nil { - return "欢迎加入订单群聊!如有任何问题,请随时沟通。" + if err := r.db.WithContext(ctx).Where("`key` = ?", autoWelcomeConfigKey).First(&cfg).Error; err != nil { + return defaultAutoWelcomeMessage } return cfg.Value } func (r *Repository) UpdateAutoWelcomeMessage(ctx context.Context, message string) error { return r.db.WithContext(ctx).Model(&model.SystemConfig{}). - Where("`key` = ?", "chat.auto_welcome_message"). + Where("`key` = ?", autoWelcomeConfigKey). + Update("value", message).Error +} + +func (r *Repository) GetListingGroupWelcomeMessage(ctx context.Context) string { + var cfg model.SystemConfig + if err := r.db.WithContext(ctx).Where("`key` = ?", listingGroupWelcomeConfigKey).First(&cfg).Error; err != nil { + return defaultListingGroupWelcome + } + return cfg.Value +} + +func (r *Repository) UpdateListingGroupWelcomeMessage(ctx context.Context, message string) error { + return r.db.WithContext(ctx).Model(&model.SystemConfig{}). + Where("`key` = ?", listingGroupWelcomeConfigKey). Update("value", message).Error } diff --git a/backend/internal/modules/chat/service.go b/backend/internal/modules/chat/service.go index 37c7ce7..7442201 100644 --- a/backend/internal/modules/chat/service.go +++ b/backend/internal/modules/chat/service.go @@ -208,3 +208,17 @@ func (s *Service) UpdateAutoWelcomeMessage(ctx context.Context, message string) } return s.repo.UpdateAutoWelcomeMessage(ctx, message) } + +func (s *Service) GetListingGroupWelcomeMessage(ctx context.Context) string { + if s.repo == nil { + return "" + } + return s.repo.GetListingGroupWelcomeMessage(ctx) +} + +func (s *Service) UpdateListingGroupWelcomeMessage(ctx context.Context, message string) error { + if s.repo == nil { + return ErrDependencyUnavailable + } + return s.repo.UpdateListingGroupWelcomeMessage(ctx, message) +} diff --git a/backend/internal/modules/systemconfig/repository.go b/backend/internal/modules/systemconfig/repository.go index e88ba1f..24c7db4 100644 --- a/backend/internal/modules/systemconfig/repository.go +++ b/backend/internal/modules/systemconfig/repository.go @@ -37,6 +37,7 @@ var defaultConfigs = []defaultConfig{ {Key: postRentalNoticeConfigKey, Value: defaultPostRentalNoticeConfigValue(), Description: "租后须知配置 JSON"}, {Key: "chat.default_support_admin_id", Value: "2", Description: "默认客服 ID(必须是启用状态的客服角色)"}, {Key: "chat.auto_welcome_message", Value: "欢迎加入订单群聊!如有任何问题,请随时沟通。", Description: "建群后自动发送的欢迎话术"}, + {Key: "chat.listing_group_welcome", Value: "欢迎加入账号群!请号主扫描下方二维码加入企业微信群,方便客服与您及时联系。", Description: "发布群创建后自动发送的欢迎语"}, {Key: homeAnnouncementsConfigKey, Value: defaultHomeAnnouncementsConfigValue(), Description: "移动端首页公告 JSON 数组"}, {Key: homeBannersConfigKey, Value: defaultHomeBannersConfigValue(), Description: "移动端首页轮播图 JSON 数组"}, {Key: publishOptionsConfigKey, Value: defaultPublishOptionsConfigValue(), Description: "发布页选项配置 JSON"}, @@ -46,6 +47,7 @@ var defaultConfigs = []defaultConfig{ var adminVisibleConfigKeys = []string{ "chat.auto_welcome_message", "chat.default_support_admin_id", + "chat.listing_group_welcome", "handoff.owner_return_confirm_timeout_minutes", "handoff.owner_submit_timeout_minutes", "handoff.renter_confirm_timeout_minutes", @@ -155,6 +157,11 @@ func (r *Repository) ensureDefaults(ctx context.Context) error { return err } } + if item.Key == "chat.listing_group_welcome" { + if err := updateLegacyListingGroupWelcome(tx, item); err != nil { + return err + } + } if item.Key == publishOptionsConfigKey { if err := updateLegacyPublishOptions(tx, item); err != nil { return err @@ -165,6 +172,26 @@ func (r *Repository) ensureDefaults(ctx context.Context) error { }) } +func updateLegacyListingGroupWelcome(tx *gorm.DB, item defaultConfig) error { + var row model.SystemConfig + if err := tx.Where("`key` = ?", item.Key).First(&row).Error; err != nil { + return err + } + changed := false + if isMojibakeText(row.Value) { + row.Value = item.Value + changed = true + } + if row.Description != item.Description { + row.Description = item.Description + changed = true + } + if !changed { + return nil + } + return tx.Save(&row).Error +} + func updateDefaultSupportConfig(tx *gorm.DB, item defaultConfig) error { var row model.SystemConfig if err := tx.Where("`key` = ?", item.Key).First(&row).Error; err != nil { @@ -260,6 +287,16 @@ func isLegacyPublishOptionsValue(value string) bool { return false } +func isMojibakeText(value string) bool { + mojibakeMarkers := []string{"å", "æ", "ç", "è", "é", "ä", "ï¼", "ã€", "â"} + for _, marker := range mojibakeMarkers { + if strings.Contains(value, marker) { + return true + } + } + return false +} + func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizID uint64, meta AuditMeta, detail map[string]any) error { return auditlog.Append(tx, auditlog.Entry{ ActorType: "admin", diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index 36add85..d6586af 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -550,6 +550,8 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine { adminRoutes.DELETE("/chats/quick-replies/:id", requirePerm("chat:send"), chatHandler.AdminDeleteQuickReply) adminRoutes.GET("/chats/auto-welcome", requirePerm("system_config:view"), chatHandler.AdminGetAutoWelcome) adminRoutes.PUT("/chats/auto-welcome", requirePerm("system_config:update"), chatHandler.AdminUpdateAutoWelcome) + adminRoutes.GET("/chats/listing-group-welcome", requirePerm("system_config:view"), chatHandler.AdminGetListingGroupWelcome) + adminRoutes.PUT("/chats/listing-group-welcome", requirePerm("system_config:update"), chatHandler.AdminUpdateListingGroupWelcome) // 二维码池管理 adminRoutes.POST("/chats/qrcodes", requirePerm("chat:manage"), chatHandler.CreateQrCodeHandler) diff --git a/backend/migrations/000010_listing_group_welcome_config.sql b/backend/migrations/000010_listing_group_welcome_config.sql new file mode 100644 index 0000000..0054e64 --- /dev/null +++ b/backend/migrations/000010_listing_group_welcome_config.sql @@ -0,0 +1,20 @@ +-- +goose Up +-- +goose StatementBegin + +INSERT INTO system_configs (`key`, `value`, description) VALUES + ('chat.listing_group_welcome', '欢迎加入账号群!请号主扫描下方二维码加入企业微信群,方便客服与您及时联系。', '发布群创建后自动发送的欢迎语') +ON DUPLICATE KEY UPDATE + `value` = CASE + WHEN `value` LIKE '%å%' OR `value` LIKE '%æ%' OR `value` LIKE '%ç%' OR `value` LIKE '%ä%' OR `value` LIKE '%ï¼%' THEN VALUES(`value`) + ELSE `value` + END, + description = VALUES(description); + +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin + +DELETE FROM system_configs WHERE `key` = 'chat.listing_group_welcome'; + +-- +goose StatementEnd diff --git a/frontend/src/features/admin/components/AutoWelcomeConfig.vue b/frontend/src/features/admin/components/AutoWelcomeConfig.vue index bb6fcb6..b729c31 100644 --- a/frontend/src/features/admin/components/AutoWelcomeConfig.vue +++ b/frontend/src/features/admin/components/AutoWelcomeConfig.vue @@ -1,13 +1,28 @@