Files
lgp-wx-plus/utils/theme.js
2026-08-20 19:15:41 +08:00

192 lines
5.8 KiB
JavaScript

import { getCache, setCache } from '@/utils/cache'
import { getThemeApi } from '@/api/page/theme.js'
/**
* 主题运行时:缓存 css_vars + tokens + layout
* 页面通过 mixin / Theme* 组件消费,禁止按 template.code 特判
*/
const STORAGE_KEY = 'theme_tokens'
const STORAGE_VARS_KEY = 'theme_css_vars'
const STORAGE_LAYOUT_KEY = 'theme_layout'
const STORAGE_META_KEY = 'theme_meta'
const STORAGE_SCHEMES_KEY = 'theme_card_schemes'
const STORAGE_FEATURES_KEY = 'theme_features'
export const FALLBACK_VARS = {
'--color-primary': '#B08D57',
'--color-primary-soft': '#d9b97f',
'--color-accent': '#8C6A3F',
'--color-bg': '#FAF7F2',
'--color-bg-soft': '#f5f2ec',
'--color-surface': '#ffffff',
'--color-surface-soft': '#fafafa',
'--color-text': '#1C1917',
'--color-text-soft': '#666666',
'--color-text-muted': '#999999',
'--color-border': '#E7DFD3',
'--color-price': '#B08D57',
'--radius-md': '16rpx',
'--radius-lg': '20rpx',
'--shadow-md': '0 8rpx 24rpx rgba(0,0,0,0.06)',
'--motion-base': '320ms',
'--motion-easing': 'cubic-bezier(0.22,1,0.36,1)',
}
export const FALLBACK_LAYOUT = {
home: { hero: 'carousel', category: 'grid', product: 'grid' },
product: { gallery: 'swiper', price: 'inline', action: 'fixed' },
list: { style: 'card' },
mine: { header: 'gradient', menu: 'list' },
package: { list: 'card' },
effect: {
transition: 'fade',
skeleton: 'shimmer',
card: 'elevated',
icon: 'line',
},
card_scheme: 'champagne-foil',
chrome: { tabbar: 'plain', navbar: 'solid', tabbar_anim: 'slide' },
page_presets: {},
pages: {},
}
/** 拉取并落地主题 */
export async function loadAndApplyTheme() {
let theme = null
try {
// eslint-disable-next-line no-console
console.log('[theme] GET theme …')
theme = await getThemeApi()
// eslint-disable-next-line no-console
console.log('[theme] ok', theme && theme.code, theme && theme.fallback)
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[theme] request failed', e)
}
if (!theme || !theme.css_vars) {
theme = {
code: '',
name: '',
css_vars: getCache(STORAGE_VARS_KEY) || FALLBACK_VARS,
tokens: getCache(STORAGE_KEY) || {},
layout: getCache(STORAGE_LAYOUT_KEY) || FALLBACK_LAYOUT,
card_schemes: getCache(STORAGE_SCHEMES_KEY) || {},
features: getThemeFeatures(),
fallback: true,
}
}
applyTheme(theme)
return theme
}
export function applyTheme(theme) {
if (!theme) return
const vars = { ...FALLBACK_VARS, ...(theme.css_vars || {}) }
const layout = mergeLayout(theme.layout)
setCache(STORAGE_VARS_KEY, vars)
setCache(STORAGE_LAYOUT_KEY, layout)
if (theme.tokens) {
setCache(STORAGE_KEY, theme.tokens)
}
setCache(STORAGE_META_KEY, {
code: theme.code || '',
name: theme.name || '',
style_tag: theme.style_tag || '',
fallback: !!theme.fallback,
})
setCache(STORAGE_SCHEMES_KEY, theme.card_schemes || {})
setCache(STORAGE_FEATURES_KEY, {
package_enabled: !!(theme.features && theme.features.package_enabled),
subscribe_pay_tpl: (theme.features && theme.features.subscribe_pay_tpl) || '',
subscribe_ship_tpl: (theme.features && theme.features.subscribe_ship_tpl) || '',
})
applyVarsToDom(vars)
}
function mergeLayout(layout) {
const src = layout && typeof layout === 'object' ? layout : {}
const chrome = src.chrome && typeof src.chrome === 'object' ? src.chrome : {}
return {
home: { ...FALLBACK_LAYOUT.home, ...(src.home || {}) },
product: { ...FALLBACK_LAYOUT.product, ...(src.product || {}) },
list: { ...FALLBACK_LAYOUT.list, ...(src.list || {}) },
mine: { ...FALLBACK_LAYOUT.mine, ...(src.mine || {}) },
package: { ...FALLBACK_LAYOUT.package, ...(src.package || {}) },
effect: { ...FALLBACK_LAYOUT.effect, ...(src.effect || {}) },
card_scheme: src.card_scheme || FALLBACK_LAYOUT.card_scheme,
chrome: {
tabbar: chrome.tabbar || 'plain',
navbar: chrome.navbar || 'solid',
tabbar_anim: chrome.tabbar_anim || 'slide',
tabbar_color: chrome.tabbar_color || '',
tabbar_color_on: chrome.tabbar_color_on || '',
},
page_presets: src.page_presets && typeof src.page_presets === 'object' ? src.page_presets : {},
pages: src.pages && typeof src.pages === 'object' ? src.pages : {},
}
}
export function applyVarsToDom(vars) {
if (!vars) return
// #ifdef H5
try {
const root = document.documentElement
Object.entries(vars).forEach(([k, v]) => {
root.style.setProperty(k, String(v))
})
} catch (e) {
// ignore
}
// #endif
}
export function getActiveVars() {
return getCache(STORAGE_VARS_KEY) || FALLBACK_VARS
}
export function getActiveLayout() {
return mergeLayout(getCache(STORAGE_LAYOUT_KEY) || FALLBACK_LAYOUT)
}
export function getActiveTokens() {
return getCache(STORAGE_KEY) || {}
}
export function getThemeMeta() {
return getCache(STORAGE_META_KEY) || { code: '', name: '', style_tag: '', fallback: true }
}
/** 完整主题快照(页面调试 / 组件用) */
export function getActiveSchemes() {
return getCache(STORAGE_SCHEMES_KEY) || {}
}
/** 应用级开关,跟主题一起缓存,底栏/首页热门套餐都读这里 */
export function getThemeFeatures() {
const cached = getCache(STORAGE_FEATURES_KEY) || {}
return {
package_enabled: !!cached.package_enabled,
subscribe_pay_tpl: cached.subscribe_pay_tpl || '',
subscribe_ship_tpl: cached.subscribe_ship_tpl || '',
}
}
export function getTheme() {
return {
...getThemeMeta(),
css_vars: getActiveVars(),
tokens: getActiveTokens(),
layout: getActiveLayout(),
card_schemes: getActiveSchemes(),
features: getThemeFeatures(),
}
}
export function varsToStyleString(vars) {
const obj = vars || getActiveVars()
return Object.entries(obj)
.map(([k, v]) => `${k}: ${v}`)
.join('; ')
}