feat(distribution): 分站前台按独立商城呈现并支持首页文案自定义
分站前台原本对顾客暴露分销身份,且登录等页面写死了主站品牌, 与「一手独立商城」的定位不符;同时首页文案全部硬编码,站长无法按自己口径调整。 去分销身份(顾客可见处): - 头部品牌区、首页主视觉、移动端认证卡去掉「官方分销/官方分销认证/官方分销站」标识 - 会员徽标「分销专享」→「会员专享」,「所属商城」兜底「专属分销站/商城」→「官方商城」 - 订单页说明改为「当前为【X】商城,仅展示在本站产生的订单记录」,保留订单按站隔离的必要说明 - 无公告时的兜底跑马灯、轮播角标去掉分销措辞 - 清理随之失效的 pc-brand-badge / mobile-brand-badge 样式 品牌跟随站点配置(即可自定义): - PC/移动登录页、移动注册页、加桌面引导页的品牌改为 站点名称 + Logo(无 Logo 取站名首字), 不再写死主站「大锤商行」;主站行为不变(新增 store getter brandMark:分站取站名首字,主站仍为「锤」) 首页文案可配置: - 新增迁移 000076 与 distribution_sites.storefront_text_json (tagline 头部副标题 / hero_badge 主视觉徽标 / hero_desc 主视觉描述 / card_sub 移动端卡片副标题) - 分销后台「品牌装修 → 首页文案」可编辑,占位提示即默认值,留空或整段清空回落内置默认 - 贯通 UpdateSiteBranding → 公开站点信息接口(含 Redis 缓存失效)→ 前台 store 逐字段合并默认值 - 装修预览同步展示站长填写的副标题与徽标 测试: - 后端补装修字段写入/公开接口透出/清空回落断言 - 前端新增 storefrontText 单测(默认合并、覆盖、空白与非法类型回落)
This commit is contained in:
@@ -4,29 +4,31 @@ import "time"
|
||||
|
||||
// DistributionSite 表示一个独立分销网站主体。
|
||||
type DistributionSite struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
SiteCode string `gorm:"size:64;not null;uniqueIndex" json:"site_code"`
|
||||
SiteName string `gorm:"size:128;not null" json:"site_name"`
|
||||
Domain string `gorm:"size:255;not null;uniqueIndex" json:"domain"`
|
||||
Status string `gorm:"size:16;not null;default:'active';index" json:"status"`
|
||||
DisplayAccountEnabled bool `gorm:"not null;default:true" json:"display_account_enabled"`
|
||||
LogoURL string `gorm:"size:512;not null;default:''" json:"logo_url"`
|
||||
FaviconURL string `gorm:"size:512;not null;default:''" json:"favicon_url"`
|
||||
BannersJSON string `gorm:"type:text" json:"banners_json"`
|
||||
AnnouncementsJSON string `gorm:"type:text" json:"announcements_json"`
|
||||
FooterInfoJSON string `gorm:"type:text" json:"footer_info_json"`
|
||||
AWRecycleJSON string `gorm:"type:text" json:"aw_recycle_json"`
|
||||
CooperationFeedbackJSON string `gorm:"type:text" json:"cooperation_feedback_json"`
|
||||
DefaultSupportGroupID *uint64 `gorm:"index" json:"default_support_group_id,omitempty"`
|
||||
ListingQrcodeURL string `gorm:"size:512;not null;default:''" json:"listing_qrcode_url"`
|
||||
SMSConfigJSON string `gorm:"type:text" json:"-"`
|
||||
ReceiveRatioDelta float64 `gorm:"type:decimal(10,2);not null;default:0" json:"receive_ratio_delta"`
|
||||
ReceiveCostDelta float64 `gorm:"type:decimal(10,2);not null;default:0" json:"receive_cost_delta"`
|
||||
TaxEnabled bool `gorm:"not null;default:false" json:"tax_enabled"`
|
||||
TaxRateBPS int64 `gorm:"not null;default:100" json:"tax_rate_bps"`
|
||||
SettlementHoldDays int `gorm:"not null;default:7" json:"settlement_hold_days"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
SiteCode string `gorm:"size:64;not null;uniqueIndex" json:"site_code"`
|
||||
SiteName string `gorm:"size:128;not null" json:"site_name"`
|
||||
Domain string `gorm:"size:255;not null;uniqueIndex" json:"domain"`
|
||||
Status string `gorm:"size:16;not null;default:'active';index" json:"status"`
|
||||
DisplayAccountEnabled bool `gorm:"not null;default:true" json:"display_account_enabled"`
|
||||
LogoURL string `gorm:"size:512;not null;default:''" json:"logo_url"`
|
||||
FaviconURL string `gorm:"size:512;not null;default:''" json:"favicon_url"`
|
||||
BannersJSON string `gorm:"type:text" json:"banners_json"`
|
||||
AnnouncementsJSON string `gorm:"type:text" json:"announcements_json"`
|
||||
FooterInfoJSON string `gorm:"type:text" json:"footer_info_json"`
|
||||
AWRecycleJSON string `gorm:"type:text" json:"aw_recycle_json"`
|
||||
CooperationFeedbackJSON string `gorm:"type:text" json:"cooperation_feedback_json"`
|
||||
// StorefrontTextJSON 首页文案(tagline/hero_badge/hero_desc/card_sub),空字段继承前台默认。
|
||||
StorefrontTextJSON string `gorm:"type:text" json:"storefront_text_json"`
|
||||
DefaultSupportGroupID *uint64 `gorm:"index" json:"default_support_group_id,omitempty"`
|
||||
ListingQrcodeURL string `gorm:"size:512;not null;default:''" json:"listing_qrcode_url"`
|
||||
SMSConfigJSON string `gorm:"type:text" json:"-"`
|
||||
ReceiveRatioDelta float64 `gorm:"type:decimal(10,2);not null;default:0" json:"receive_ratio_delta"`
|
||||
ReceiveCostDelta float64 `gorm:"type:decimal(10,2);not null;default:0" json:"receive_cost_delta"`
|
||||
TaxEnabled bool `gorm:"not null;default:false" json:"tax_enabled"`
|
||||
TaxRateBPS int64 `gorm:"not null;default:100" json:"tax_rate_bps"`
|
||||
SettlementHoldDays int `gorm:"not null;default:7" json:"settlement_hold_days"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (DistributionSite) TableName() string { return "distribution_sites" }
|
||||
|
||||
@@ -16,6 +16,7 @@ type SiteDTO struct {
|
||||
FooterInfoJSON string `json:"footer_info_json"`
|
||||
AWRecycleJSON string `json:"aw_recycle_json"`
|
||||
CooperationFeedbackJSON string `json:"cooperation_feedback_json"`
|
||||
StorefrontTextJSON string `json:"storefront_text_json"`
|
||||
DefaultSupportGroupID *uint64 `json:"default_support_group_id,omitempty"`
|
||||
ListingQrcodeURL string `json:"listing_qrcode_url"`
|
||||
ReceiveRatioDelta float64 `json:"receive_ratio_delta"`
|
||||
@@ -260,6 +261,7 @@ type PublicSiteInfoDTO struct {
|
||||
BannersJSON string `json:"banners_json"`
|
||||
AnnouncementsJSON string `json:"announcements_json"`
|
||||
FooterInfoJSON string `json:"footer_info_json"`
|
||||
StorefrontTextJSON string `json:"storefront_text_json"`
|
||||
DefaultSupportGroupID *uint64 `json:"default_support_group_id,omitempty"`
|
||||
ReceiveRatioDelta float64 `json:"receive_ratio_delta"`
|
||||
ReceiveCostDelta float64 `json:"receive_cost_delta"`
|
||||
@@ -428,6 +430,7 @@ type UpdateSiteBrandingRequest struct {
|
||||
FooterInfoJSON *string `json:"footer_info_json"`
|
||||
AWRecycleJSON *string `json:"aw_recycle_json"`
|
||||
CooperationFeedbackJSON *string `json:"cooperation_feedback_json"`
|
||||
StorefrontTextJSON *string `json:"storefront_text_json"`
|
||||
ListingQrcodeURL *string `json:"listing_qrcode_url"`
|
||||
}
|
||||
|
||||
|
||||
@@ -887,6 +887,7 @@ func siteDTO(row model.DistributionSite) SiteDTO {
|
||||
FooterInfoJSON: row.FooterInfoJSON,
|
||||
AWRecycleJSON: row.AWRecycleJSON,
|
||||
CooperationFeedbackJSON: row.CooperationFeedbackJSON,
|
||||
StorefrontTextJSON: row.StorefrontTextJSON,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
}
|
||||
@@ -2657,6 +2658,7 @@ func (r *Repository) GetPublicSiteInfo(ctx context.Context, code, domain string)
|
||||
BannersJSON: site.BannersJSON,
|
||||
AnnouncementsJSON: site.AnnouncementsJSON,
|
||||
FooterInfoJSON: site.FooterInfoJSON,
|
||||
StorefrontTextJSON: site.StorefrontTextJSON,
|
||||
ReceiveRatioDelta: site.ReceiveRatioDelta,
|
||||
ReceiveCostDelta: site.ReceiveCostDelta,
|
||||
}
|
||||
@@ -2744,6 +2746,10 @@ func (r *Repository) UpdateSiteBranding(ctx context.Context, adminID, siteID uin
|
||||
site.CooperationFeedbackJSON = strings.TrimSpace(*req.CooperationFeedbackJSON)
|
||||
updates["cooperation_feedback_json"] = site.CooperationFeedbackJSON
|
||||
}
|
||||
if req.StorefrontTextJSON != nil {
|
||||
site.StorefrontTextJSON = strings.TrimSpace(*req.StorefrontTextJSON)
|
||||
updates["storefront_text_json"] = site.StorefrontTextJSON
|
||||
}
|
||||
if req.ListingQrcodeURL != nil {
|
||||
site.ListingQrcodeURL = strings.TrimSpace(*req.ListingQrcodeURL)
|
||||
updates["listing_qrcode_url"] = site.ListingQrcodeURL
|
||||
|
||||
@@ -637,12 +637,14 @@ func TestDistributionBrandingAndMarkup(t *testing.T) {
|
||||
banners := `[{"image_url":"https://cdn.example.com/b1.jpg"}]`
|
||||
announcements := `[{"title":"开业大吉"}]`
|
||||
footer := `{"copyright":"2026 Brand"}`
|
||||
storefrontText := `{"tagline":"天域商行 · 专注回收","hero_badge":"正品保障"}`
|
||||
updated, err := repo.UpdateSiteBranding(t.Context(), 0, site.ID, UpdateSiteBrandingRequest{
|
||||
LogoURL: &logo,
|
||||
FaviconURL: &favicon,
|
||||
BannersJSON: &banners,
|
||||
AnnouncementsJSON: &announcements,
|
||||
FooterInfoJSON: &footer,
|
||||
LogoURL: &logo,
|
||||
FaviconURL: &favicon,
|
||||
BannersJSON: &banners,
|
||||
AnnouncementsJSON: &announcements,
|
||||
FooterInfoJSON: &footer,
|
||||
StorefrontTextJSON: &storefrontText,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateSiteBranding failed: %v", err)
|
||||
@@ -650,6 +652,30 @@ func TestDistributionBrandingAndMarkup(t *testing.T) {
|
||||
if updated.LogoURL != "https://cdn.example.com/logo.png" || updated.FaviconURL != "https://cdn.example.com/favicon.ico" {
|
||||
t.Fatalf("unexpected branding: %+v", updated)
|
||||
}
|
||||
if updated.StorefrontTextJSON != storefrontText {
|
||||
t.Fatalf("storefront_text_json = %q, want %q", updated.StorefrontTextJSON, storefrontText)
|
||||
}
|
||||
|
||||
// 公开站点信息必须透出首页文案,前台才能读到站长自定义内容。
|
||||
publicInfo, err := repo.GetPublicSiteInfo(t.Context(), site.SiteCode, "")
|
||||
if err != nil {
|
||||
t.Fatalf("GetPublicSiteInfo failed: %v", err)
|
||||
}
|
||||
if publicInfo.StorefrontTextJSON != storefrontText {
|
||||
t.Fatalf("public storefront_text_json = %q, want %q", publicInfo.StorefrontTextJSON, storefrontText)
|
||||
}
|
||||
|
||||
// 传空字符串表示清除自定义文案,前台回落内置默认值。
|
||||
emptyText := ""
|
||||
textCleared, err := repo.UpdateSiteBranding(t.Context(), 0, site.ID, UpdateSiteBrandingRequest{
|
||||
StorefrontTextJSON: &emptyText,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("clearing storefront text failed: %v", err)
|
||||
}
|
||||
if textCleared.StorefrontTextJSON != "" {
|
||||
t.Fatalf("storefront_text_json should be cleared, got %q", textCleared.StorefrontTextJSON)
|
||||
}
|
||||
|
||||
qrURL := "/api/files/object?key=qrcode/brand-site.png"
|
||||
qrUpdated, err := repo.UpdateSite(t.Context(), 0, site.ID, UpdateSiteRequest{ListingQrcodeURL: &qrURL})
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- +goose Up
|
||||
|
||||
-- 分站首页文案(站长可在分销后台「品牌装修」自定义)。JSON 字段:
|
||||
-- tagline 头部品牌区副标题(PC + 移动)
|
||||
-- hero_badge 首页主视觉徽标(PC Hero + 移动认证卡片)
|
||||
-- hero_desc 首页主视觉描述(PC)
|
||||
-- card_sub 移动端认证卡片副标题
|
||||
-- 空值或空字符串表示沿用前台内置默认文案,因此不影响存量站点。
|
||||
ALTER TABLE distribution_sites
|
||||
ADD COLUMN storefront_text_json TEXT NULL COMMENT '分站首页文案 JSON(tagline/hero_badge/hero_desc/card_sub),空字段继承默认' AFTER cooperation_feedback_json;
|
||||
|
||||
-- +goose Down
|
||||
|
||||
ALTER TABLE distribution_sites
|
||||
DROP COLUMN storefront_text_json;
|
||||
Reference in New Issue
Block a user