import { getActiveLayout, getActiveSchemes, getActiveVars, getThemeFeatures, getThemeMeta, loadAndApplyTheme, } from '@/utils/theme' import { resolvePageTabBar, syncTabBarByRoute } from '@/utils/tabbar' import { PC_PAGE_MAX_WIDTH, catColsByWidth, getWindowWidth, goodsColsByWidth, isPcWeixin, watchWindowWidth, } from '@/utils/device' /** * 页面主题 mixin:暴露色板 + layout + 进入动效 class * 严禁按 themeMeta.code 特判业务结构 */ export default { data() { return { themeVars: getActiveVars(), themeLayout: getActiveLayout(), themeMeta: getThemeMeta(), themeSchemes: getActiveSchemes(), themeFeatures: getThemeFeatures(), pcWindowWidth: getWindowWidth(), } }, computed: { isPcWeixin() { return isPcWeixin() }, /** 首页 / 列表商品列数,随窗口宽度变(不限 PC) */ pcGoodsCols() { return goodsColsByWidth(this.pcWindowWidth) }, /** 首页分类宫格列数,格子更小所以更早加列 */ pcCatCols() { return catColsByWidth(this.pcWindowWidth) }, themeEffect() { return (this.themeLayout && this.themeLayout.effect) || {} }, themePageStyle() { const v = this.themeVars || {} const style = { backgroundColor: v['--color-bg'] || '#F7F8FA', color: v['--color-text'] || '#1a1a1a', } // PC 大窗不要把手机版拉成一条超宽带,内容限宽居中 if (isPcWeixin()) { style.maxWidth = PC_PAGE_MAX_WIDTH + 'px' style.marginLeft = 'auto' style.marginRight = 'auto' style.width = '100%' style.boxSizing = 'border-box' } return style }, themePrimary() { return (this.themeVars && this.themeVars['--color-primary']) || '#B08D57' }, themeAccent() { return (this.themeVars && this.themeVars['--color-accent']) || this.themePrimary }, themeSurface() { return (this.themeVars && this.themeVars['--color-surface']) || '#ffffff' }, themeSurfaceSoft() { return (this.themeVars && this.themeVars['--color-surface-soft']) || this.themeSurface }, themeText() { return (this.themeVars && this.themeVars['--color-text']) || '#1a1a1a' }, themeTextSoft() { return (this.themeVars && this.themeVars['--color-text-soft']) || '#666666' }, themeBorder() { return (this.themeVars && this.themeVars['--color-border']) || '#eeeeee' }, themePrice() { return (this.themeVars && this.themeVars['--color-price']) || this.themePrimary }, themeRadius() { return (this.themeVars && this.themeVars['--radius-md']) || '16rpx' }, themeRadiusLg() { return (this.themeVars && this.themeVars['--radius-lg']) || '20rpx' }, themeShadow() { return (this.themeVars && this.themeVars['--shadow-md']) || 'none' }, themeIconColor() { return this.themePrimary }, themeCardMode() { return this.themeEffect.card || 'elevated' }, themeIconMode() { return this.themeEffect.icon || 'line' }, themeEnterClass() { const t = this.themeEffect.transition || 'fade' return `theme-enter theme-enter--${t}` }, homeHero() { return this.sectionVariant( 'home', 'hero', (this.themeLayout.home && this.themeLayout.home.hero) || 'carousel', ) }, homeCategory() { return this.sectionVariant( 'home', 'category', (this.themeLayout.home && this.themeLayout.home.category) || 'grid', ) }, homeProduct() { return this.sectionVariant( 'home', 'product', (this.themeLayout.home && this.themeLayout.home.product) || 'grid', ) }, homePackage() { return this.sectionVariant( 'home', 'package', (this.themeLayout.home && this.themeLayout.home.package) || 'card', ) }, packageList() { return this.sectionVariant( 'package', 'list', (this.themeLayout.package && this.themeLayout.package.list) || 'card', ) }, /** 套餐页搜索:优先本页 search 区块,没有则复用首页搜索样式 */ packageSearch() { const own = this.sectionVariant('package', 'search', '') if (own) return own return this.homeSearch }, packageEnabled() { return !!(this.themeFeatures && this.themeFeatures.package_enabled) }, productGallery() { return this.sectionVariant( 'product', 'gallery', (this.themeLayout.product && this.themeLayout.product.gallery) || 'swiper', ) }, productPrice() { return this.sectionVariant( 'product', 'price', (this.themeLayout.product && this.themeLayout.product.price) || 'inline', ) }, productAction() { return this.sectionVariant( 'product', 'action', (this.themeLayout.product && this.themeLayout.product.action) || 'fixed', ) }, catalogFilter() { return this.sectionVariant('catalog', 'filter', 'chip') }, catalogList() { return this.sectionVariant('catalog', 'list', 'waterfall') }, searchList() { return this.sectionVariant('search', 'list', 'waterfall') }, listStyle() { return this.sectionVariant( 'cart', 'list', (this.themeLayout.list && this.themeLayout.list.style) || 'card', ) }, mineHeader() { return this.sectionVariant( 'mine', 'header', (this.themeLayout.mine && this.themeLayout.mine.header) || 'gradient', ) }, mineMenu() { return this.sectionVariant( 'mine', 'menu', (this.themeLayout.mine && this.themeLayout.mine.menu) || 'list', ) }, themeSkeleton() { return this.themeEffect.skeleton || 'shimmer' }, themeCardScheme() { return this.themeLayout.card_scheme || 'champagne-foil' }, pageCardScheme() { const meta = this.pageMeta(this.currentThemePage || '') return (meta && meta.card_scheme) || this.themeCardScheme }, pageCardCaption() { const meta = this.pageMeta(this.currentThemePage || '') return (meta && meta.card_caption) || '' }, themeChrome() { return (this.themeLayout && this.themeLayout.chrome) || {} }, themeTabbar() { return this.themeChrome.tabbar || 'plain' }, themeNavbar() { return this.themeChrome.navbar || 'solid' }, homeSections() { return this.pageSections('home') }, homeSearch() { return this.sectionVariant('home', 'search', 'bar') }, /** 当前页皮肤:先读本页 pages.xxx.skin,没有再回落到首页 */ pageSkin() { const page = this.pageMeta(this.currentThemePage || 'home') const home = this.pageMeta('home') return page.skin || home.skin || 'shop' }, pageDensity() { const page = this.pageMeta(this.currentThemePage || 'home') const home = this.pageMeta('home') return page.density || home.density || 'regular' }, pageFrame() { const page = this.pageMeta(this.currentThemePage || 'home') const home = this.pageMeta('home') return page.frame || home.frame || 'none' }, productInfo() { return this.sectionVariant('product', 'info', 'plain') }, productSpec() { return this.sectionVariant('product', 'spec', 'plain') }, }, methods: { pageMeta(page) { const pages = (this.themeLayout && this.themeLayout.pages) || {} return pages[page] || {} }, pagePreset(page) { const map = (this.themeLayout && this.themeLayout.page_presets) || {} return map[page] || '' }, pageSections(page) { const pages = (this.themeLayout && this.themeLayout.pages) || {} const list = pages[page] && pages[page].sections if (Array.isArray(list) && list.length) { return list } return [] }, sectionVisible(page, type) { const list = this.pageSections(page) if (!list.length) { return !(page === 'home' && type === 'product') } const found = list.find((s) => s.type === type) if (!found) return !(page === 'home' && type === 'product') return found.visible !== false }, sectionOrder(page, type) { const list = this.pageSections(page) const index = list.findIndex((s) => s.type === type) return index < 0 ? 50 : index }, sectionVariant(page, type, fallback) { const list = this.pageSections(page) const found = list.find((s) => s.type === type) return (found && found.variant) || fallback }, /** * 拉主题并按当前路由对齐底栏。 * 页面自己写了 onShow 时可能盖掉 mixin 钩子,所以抽成方法让页面显式调用。 */ unbindWindowWidth() { if (typeof this._unwatchWindowWidth === 'function') { this._unwatchWindowWidth() this._unwatchWindowWidth = null } }, async applyThemeOnShow() { // 先对齐当前页底栏,不要等主题接口,否则邻项互切时 pending 还在栏已经画死 syncTabBarByRoute(this) try { const theme = await loadAndApplyTheme() this.themeVars = (theme && theme.css_vars) || getActiveVars() this.themeLayout = (theme && theme.layout) || getActiveLayout() this.themeSchemes = (theme && theme.card_schemes) || getActiveSchemes() this.themeMeta = { code: (theme && theme.code) || '', name: (theme && theme.name) || '', style_tag: (theme && theme.style_tag) || '', fallback: !!(theme && theme.fallback), } this.themeFeatures = (theme && theme.features) || getThemeFeatures() } catch (e) { this.themeVars = getActiveVars() this.themeLayout = getActiveLayout() this.themeMeta = getThemeMeta() this.themeFeatures = getThemeFeatures() } const bar = resolvePageTabBar(this) if (bar && typeof bar.pullTheme === 'function') bar.pullTheme() }, }, created() { this._unwatchWindowWidth = watchWindowWidth((width) => { this.pcWindowWidth = width }) }, beforeDestroy() { this.unbindWindowWidth() }, beforeUnmount() { this.unbindWindowWidth() }, async onShow() { await this.applyThemeOnShow() }, }