Files
gym-manage/品牌方案小程序更新说明.md
2026-07-23 18:56:35 +08:00

13 KiB
Raw Permalink Blame History

品牌方案 — 小程序端更新说明

概述

后台管理系统配置的品牌方案(Logo、主色调、辅助色、背景图、品牌名、口号)通过 REST API 实时同步至会员端和教练端 UniApp 小程序。小程序冷启动、页面显示、下拉刷新时自动拉取最新配置。


架构总览

┌──────────────────────┐        ┌──────────────────────┐
│   后台管理端 (web)     │        │   小程序端 (uniapp)    │
│                      │        │                      │
│  BrandManagement.vue │        │  App.vue onLaunch     │
│  保存品牌配置 ──────────┐      │    │                  │
│                      │      │    │  brandStore        │
│  POST /api/brand/logo│      │    │  .fetchConfig()    │
│  PUT  /api/brand/color     │    │    │                  │
│  PUT  /api/brand/info│      │    │  GET /api/brand    │
│       ↓              │      │    │  /config (JWT)     │
│  ┌─────────────┐     │      │    │    │                  │
│  │  PostgreSQL │◄────┼──────┼────┼────┘                  │
│  │ brand_config│     │      │    │  brandStore.config   │
│  └─────────────┘     │      │    │  → 各页面动态绑定     │
│                      │      │    │                      │
└──────────────────────┘      │    └──────────────────────┘

API 接口

GET /api/brand/config

获取当前租户的品牌配置。

项目 说明
方法 GET
路径 /api/brand/config
认证 需要 JWT TokenAuthorization: Bearer <token>
租户识别 从 JWT Claims 中的 tenantId 自动提取
签名 需要签名头(项目统一签名机制)

响应数据结构

{
  "id": 1,
  "tenantId": "default",
  "logoUrl": "http://localhost:8084/api/files/preview/brand/logo/logo_xxx.jpeg",
  "backgroundImageUrl": null,
  "primaryColor": "#00E676",
  "primaryColorRgb": "0,230,118",
  "secondaryColor": "#1A1A1A",
  "secondaryColorRgb": "26,26,26",
  "fontFamily": null,
  "brandName": "Novalon健身房",
  "slogan": "专业健身,从这里开始",
  "createdAt": "2026-07-23T17:37:36",
  "updatedAt": "2026-07-23T17:37:59"
}

字段说明

字段 类型 说明
id Long 记录 ID
tenantId String 租户 ID
logoUrl String Logo 图片完整 URL
backgroundImageUrl String 背景图完整 URL(可为 null
primaryColor String 主色调 HEX 值,如 #00E676
primaryColorRgb String 主色调 RGB 值,如 0,230,118
secondaryColor String 辅助色 HEX 值,如 #1A1A1A
secondaryColorRgb String 辅助色 RGB 值,如 26,26,26
fontFamily String 字体族(预留)
brandName String 品牌名称,最长 100 字符
slogan String 品牌口号,最长 200 字符
createdAt String 创建时间(ISO 格式)
updatedAt String 最后更新时间(ISO 格式),用作版本号

小程序端接入指南

文件结构

两个小程序项目结构相同:

项目根目录/
├── api/
│   └── brand.js           ← 品牌配置 API 层
├── store/
│   └── brand.js           ← 品牌配置 Store(含缓存逻辑)
├── App.vue                ← 入口,冷启动/前台切换时拉取配置
└── pages/
    ├── index/index.vue    ← 首页
    ├── login/login.vue    ← 登录页
    ├── profile/profile.vue← 个人中心
    └── ...

1. api/brand.js — API 调用层

const request = require('../utils/request')

module.exports = {
  getBrandConfig() {
    return request('/brand/config', { method: 'GET' })
  }
}

说明

  • 复用项目现有的 luch-request 封装,自动注入 JWT Token 和签名头
  • 返回 Promise<Object>,即 BrandConfig 对象

2. store/brand.js — 品牌配置 Store

const brandApi = require('../api/brand')

const BRAND_CACHE_KEY = 'brand_config_cache'

const DEFAULT_CONFIG = {
  brandName: 'Novalon健身房',
  slogan: '专业健身,从这里开始',
  primaryColor: '#00E676',
  primaryColorRgb: '0,230,118',
  secondaryColor: '#1A1A1A',
  secondaryColorRgb: '26,26,26',
  logoUrl: '',
  backgroundImageUrl: '',
  updatedAt: ''
}

const brandStore = {
  _config: null,
  _loaded: false,

  get config() {
    if (this._config) return this._config
    try {
      const cached = uni.getStorageSync(BRAND_CACHE_KEY)
      if (cached && cached.primaryColor) {
        this._config = cached
        return cached
      }
    } catch (e) {}
    return { ...DEFAULT_CONFIG }
  },

  async fetchConfig() {
    let cached = null
    try { cached = uni.getStorageSync(BRAND_CACHE_KEY) } catch (e) {}

    try {
      const res = await brandApi.getBrandConfig()
      if (res) {
        const newConfig = {
          brandName: res.brandName || DEFAULT_CONFIG.brandName,
          slogan: res.slogan || DEFAULT_CONFIG.slogan,
          primaryColor: res.primaryColor || DEFAULT_CONFIG.primaryColor,
          primaryColorRgb: res.primaryColorRgb || DEFAULT_CONFIG.primaryColorRgb,
          secondaryColor: res.secondaryColor || DEFAULT_CONFIG.secondaryColor,
          secondaryColorRgb: res.secondaryColorRgb || DEFAULT_CONFIG.secondaryColorRgb,
          logoUrl: res.logoUrl || '',
          backgroundImageUrl: res.backgroundImageUrl || '',
          updatedAt: res.updatedAt || ''
        }

        if (!this._config || this._config.updatedAt !== newConfig.updatedAt) {
          this._config = newConfig
          uni.setStorageSync(BRAND_CACHE_KEY, newConfig)
          this._loaded = true
          return true
        }
      }
    } catch (e) {
      console.error('[BrandStore] 获取品牌配置失败:', e)
    }
    this._loaded = true
    return false
  },

  applyTabBar() {
    const cfg = this.config
    uni.setTabBarStyle({
      color: '#7A7E84',
      selectedColor: cfg.primaryColor,
      backgroundColor: cfg.secondaryColor,
      borderStyle: 'black'
    })
  }
}

module.exports = brandStore

3. App.vue — 应用入口

<script>
  const brandStore = require('./store/brand')

  export default {
    onLaunch: function() {
      console.log('App Launch')
      brandStore.fetchConfig().then(changed => {
        if (changed) brandStore.applyTabBar()
      })
      setTimeout(() => { brandStore.applyTabBar() }, 300)
    },
    onShow: function() {
      console.log('App Show')
      brandStore.fetchConfig().then(changed => {
        if (changed) brandStore.applyTabBar()
      })
    }
  }
</script>

4. 页面模板接入示例

以首页为例,将硬编码颜色替换为动态绑定:

<!-- 导航栏:辅助色背景 -->
<view class="header-wrap" :style="{ backgroundColor: brandConfig.secondaryColor }">
  <view class="nav-bar" :style="{ backgroundColor: brandConfig.secondaryColor }">
    <text class="nav-title">✦ 今日训练</text>
  </view>
</view>

<!-- 品牌展示:动态名称/Slogan/Logo -->
<view class="brand-section">
  <image class="brand-logo" :src="brandConfig.logoUrl || '/static/logo.png'" mode="aspectFit" />
  <view class="brand-text">
    <text class="brand-name">{{ brandConfig.brandName }}</text>
    <text class="brand-slogan">{{ brandConfig.slogan }}</text>
  </view>
</view>

<!-- 问候卡片:辅助色背景 + 背景图 -->
<view class="greeting-card" :style="{
  backgroundColor: brandConfig.secondaryColor,
  backgroundImage: brandConfig.backgroundImageUrl ? 'url(' + brandConfig.backgroundImageUrl + ')' : 'none',
  backgroundSize: 'cover',
  backgroundPosition: 'center'
}">
  <!-- ... -->
</view>

<!-- 主色调元素 -->
<view class="checkin-badge" :style="{
  backgroundColor: 'rgba(' + brandConfig.primaryColorRgb + ',0.2)',
  color: brandConfig.primaryColor
}">
  ✓ 今日已签到
</view>

<text class="stat-number" :style="{ color: brandConfig.primaryColor }">128</text>

对应的 script 部分:

const brandStore = require('../../store/brand')

export default {
  computed: {
    brandConfig() { return brandStore.config }
  },
  onShow() {
    brandStore.fetchConfig()
    // ... other data loading
  },
  onPullDownRefresh() {
    brandStore.fetchConfig().finally(() => { uni.stopPullDownRefresh() })
  }
}

品牌字段与页面元素映射表

品牌字段 应用的元素
primaryColor 按钮背景色、选中标签背景色、价格文字、难度星星高亮、签到成功标记、功能入口图标
primaryColorRgb 半透明遮罩背景(如签到徽章 rgba(xxx, 0.2))、功能入口圆形容器背景
secondaryColor 自定义导航栏背景、问候卡片背景、统计卡片背景
secondaryColorRgb 半透明副色遮罩
logoUrl 首页品牌展示 Logo 图片 src 属性
backgroundImageUrl 问候卡片背景图(CSS background-image
brandName 首页品牌名称文本、登录页应用名称
slogan 首页品牌口号文本、登录页口号文本

更新时机

时机 触发方式 说明
冷启动 App.vue → onLaunch 先读本地缓存立即渲染,后台异步拉取,对比 updatedAt 决定是否更新
切回前台 App.vue → onShow 用户从后台切回时拉取最新配置
下拉刷新 各页面 onPullDownRefresh 手动刷新时同步拉取品牌配置
TabBar 切换 App.vue → onShow + 各页面 onShow Tab 页切换时所在页触发

缓存策略

本地缓存 Key: brand_config_cache

存储数据: { brandName, slogan, primaryColor, primaryColorRgb,
            secondaryColor, secondaryColorRgb, logoUrl,
            backgroundImageUrl, updatedAt }

更新策略:
1. 启动时读取缓存 → 立即渲染
2. 后台调 API GET /api/brand/config
3. 对比 API 返回的 updatedAt 与缓存的 updatedAt
   - 相同 → 不更新,节省渲染开销
   - 不同 → 更新内存 + 本地缓存,触发 UI 刷新
4. 如果 API 调用失败 → 保持缓存值不变,不影响用户体验

涉及的页面清单

会员端 (gym-manage-uniapp)

页面 文件 动态元素
首页 pages/index/index.vue 品牌展示(名称/口号/Logo)、导航栏背景、问候卡背景/背景图、签到徽章、统计数据、课程卡片价格、查看全部链接
团课搜索 pages/search/search.vue 导航栏背景、搜索区背景、时间段筛选、类型筛选、排序激活态、预约按钮、课程价格
我的课程 pages/my-courses/my-courses.vue 导航栏背景、空状态跳转链接
课程详情 pages/course-detail/course-detail.vue 导航栏背景、封面渐变、预约按钮
个人中心 pages/profile/profile.vue 导航栏背景、统计卡片背景、签到数字、会员标签、签到标记、保存按钮
登录 pages/login/login.vue 品牌名称/口号、Logo 图标、微信登录按钮、协议链接

教练端 (gym-manage-coach-uniapp)

页面 文件 动态元素
首页 pages/index/index.vue 导航栏背景、欢迎卡片、教练名、统计数字、功能入口图标
课程列表 pages/course-list/course-list.vue 导航栏背景、Tab 筛选、状态标签
课程详情 pages/course-detail/course-detail.vue 导航栏背景、封面渐变、开课按钮、价格文字
个人中心 pages/profile/profile.vue 导航栏背景、头像容器、标签颜色、统计卡片、评分徽章、公式按钮
登录 pages/login/login.vue 品牌名称/口号、Logo 图标、登录按钮

Q&A

Q: 后端不可用时怎么办?

A: brandStore 内置默认配置(与原硬编码值完全一致),API 失败时自动回退到默认值,用户无感知。

Q: 如何确认品牌配置已生效?

A: 后台管理端修改配色/Logo/名称并保存后,小程序重新打开或下拉刷新即可看到变化。冷启动首次渲染使用缓存值(上次成功拉取的),后台静默更新。

Q: 不同租户会互相影响吗?

A: 不会。GET /api/brand/config 从 JWT Token 中提取 tenantId,只返回当前租户的配置。数据库层面也有唯一索引 UNIQUE(tenant_id) WHERE deleted_at IS NULL 保证隔离。