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:
yml2213
2026-10-01 09:24:56 +08:00
parent 53b40f8fad
commit 329d0f75bb
22 changed files with 358 additions and 89 deletions
+14 -3
View File
@@ -6,10 +6,12 @@ import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { fetchAuthCaptcha, sendSmsCode, type AuthCaptcha } from '@/features/auth/api/auth'
import { useDistributionSiteStore } from '@/stores/distributionSite'
import { useSessionStore } from '@/stores/session'
const router = useRouter()
const session = useSessionStore()
const siteStore = useDistributionSiteStore()
const loading = ref(false)
const sending = ref(false)
const captchaLoading = ref(false)
@@ -177,8 +179,9 @@ const modeSubtitle = computed(() =>
<div class="login-card">
<div class="login-left">
<div class="brand-lockup">
<div class="brand-logo">锤</div>
<strong>大锤商行</strong>
<img v-if="siteStore.logoUrl" :src="siteStore.logoUrl" alt="" class="brand-logo-img" />
<div v-else class="brand-logo">{{ siteStore.brandMark }}</div>
<strong>{{ siteStore.siteName }}</strong>
</div>
<div class="login-hero">
<h2>安全租号<br />畅享游戏</h2>
@@ -188,7 +191,7 @@ const modeSubtitle = computed(() =>
</p>
</div>
<div class="login-footer">
<span>© 哈夫币租号平台</span>
<span>© {{ siteStore.isDistribution ? siteStore.siteName : '哈夫币租号平台' }}</span>
</div>
</div>
@@ -488,6 +491,14 @@ const modeSubtitle = computed(() =>
font-weight: 900;
}
.brand-logo-img {
width: 36px;
height: 36px;
border-radius: 10px;
object-fit: cover;
box-shadow: 0 8px 24px rgba(23, 35, 61, 0.12);
}
.brand-lockup strong {
color: #0f172a;
font-size: 15px;
@@ -5,12 +5,14 @@ import { showToast, showDialog } from 'vant'
import { fetchAuthCaptcha, type AuthCaptcha } from '@/features/auth/api/auth'
import { useSessionStore } from '@/stores/session'
import { useDistributionSiteStore } from '@/stores/distributionSite'
import { useSmsCountdown } from '@/shared/composables/useSmsCountdown'
import { readError } from '@/shared/utils/error'
const router = useRouter()
const route = useRoute()
const session = useSessionStore()
const siteStore = useDistributionSiteStore()
const loading = ref(false)
const agreed = ref(false)
const captchaLoading = ref(false)
@@ -116,8 +118,9 @@ async function handlePasswordLogin() {
<section class="auth-body">
<div class="brand-lockup">
<div class="brand-logo">锤</div>
<strong>大锤商行</strong>
<img v-if="siteStore.logoUrl" :src="siteStore.logoUrl" alt="" class="brand-logo-img" />
<div v-else class="brand-logo">{{ siteStore.brandMark }}</div>
<strong>{{ siteStore.siteName }}</strong>
</div>
<div class="form-section">
@@ -293,6 +296,14 @@ async function handlePasswordLogin() {
font-weight: 900;
}
.brand-logo-img {
width: 44px;
height: 44px;
border-radius: 12px;
object-fit: cover;
box-shadow: 0 10px 28px rgba(23, 35, 61, 0.12);
}
.brand-lockup strong {
color: #0f172a;
font-size: 20px;
@@ -359,7 +359,7 @@ function resolveAvatarURL(url: string | undefined | null) {
<section v-if="siteStore.isDistribution" class="growth-card is-distribution">
<div>
<span>所属商城</span>
<strong>{{ siteStore.siteName || '专属分销商城' }}</strong>
<strong>{{ siteStore.siteName || '官方商城' }}</strong>
</div>
<div>
<span>会员特权</span>
@@ -5,12 +5,14 @@ import { showToast } from 'vant'
import { fetchAuthCaptcha, type AuthCaptcha } from '@/features/auth/api/auth'
import { useSessionStore } from '@/stores/session'
import { useDistributionSiteStore } from '@/stores/distributionSite'
import { useSmsCountdown } from '@/shared/composables/useSmsCountdown'
import { readError } from '@/shared/utils/error'
const router = useRouter()
const route = useRoute()
const session = useSessionStore()
const siteStore = useDistributionSiteStore()
const loading = ref(false)
const agreed = ref(false)
const captchaLoading = ref(false)
@@ -95,8 +97,9 @@ async function handleRegister() {
<section class="auth-body">
<div class="brand-lockup">
<div class="brand-logo">锤</div>
<strong>大锤商行</strong>
<img v-if="siteStore.logoUrl" :src="siteStore.logoUrl" alt="" class="brand-logo-img" />
<div v-else class="brand-logo">{{ siteStore.brandMark }}</div>
<strong>{{ siteStore.siteName }}</strong>
</div>
<div class="form-section">
@@ -267,6 +270,14 @@ async function handleRegister() {
font-weight: 900;
}
.brand-logo-img {
width: 46px;
height: 46px;
border-radius: 14px;
object-fit: cover;
box-shadow: 0 14px 32px rgba(23, 35, 61, 0.12);
}
.brand-lockup strong {
display: block;
color: #111827;