fix: 顶栏颜色跟随小程序主题并保留跟随系统
This commit is contained in:
+9
-1
@@ -1,4 +1,6 @@
|
||||
export default defineAppConfig({
|
||||
// darkmode 仅作为“跟随系统”的探测通道(onThemeChange/getAppBaseInfo().theme 需要它),
|
||||
// 顶部/窗口颜色不直接依赖它;每个页面用 PageMeta + NavigationBar 按小程序内设置强覆盖。
|
||||
darkmode: true,
|
||||
themeLocation: 'theme.json',
|
||||
pages: [
|
||||
@@ -23,7 +25,13 @@ export default defineAppConfig({
|
||||
window: {
|
||||
backgroundTextStyle: 'dark',
|
||||
navigationStyle: 'custom',
|
||||
// 应用启动时先保证浅色页面的 iOS 状态栏为黑字;页面 Hook 会在主题变化时覆盖它。
|
||||
// 顶部/窗口颜色先显式固定为小程序浅色主题,避免 iOS 顶部区域跟随系统深色;
|
||||
// 应用启动后页面 Hook 会按小程序内主题实时覆盖上面的值。
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
backgroundColor: '#ffffff',
|
||||
backgroundColorTop: '#ffffff',
|
||||
backgroundColorBottom: '#ffffff',
|
||||
// 状态栏文字默认黑字;各页面 useStatusBar 会按小程序主题覆盖它。
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
tabBar: {
|
||||
|
||||
@@ -256,3 +256,23 @@ page {
|
||||
border-top: var(--line-card);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ==========================================================
|
||||
系统主题探测器(跟随系统模式)
|
||||
以官方 onThemeChange/getAppBaseInfo 为主,这里是 CSS 兜底通道。
|
||||
========================================================== */
|
||||
.system-theme-detector {
|
||||
position: fixed;
|
||||
left: -9999px;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.system-theme-detector {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +1,52 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import { getMe, logout } from '../../utils/api'
|
||||
import { getUserInfoRaw, clearUserInfo, isMockOpenid } from '../../utils/store'
|
||||
import { isAuthVerified } from '../../utils/authState'
|
||||
import { THEME_CHANGE_EVENT } from '../../context/ThemeContext'
|
||||
|
||||
type GuardState = 'checking' | 'ok' | 'denied'
|
||||
|
||||
/**
|
||||
* 登录守卫:不仅看本地是否有 openid,还要求本地用户与云端校验一致。
|
||||
* - 本地 openid 是历史 mock 残留 -> 判定不一致,清掉并引导重新登录
|
||||
* - 本地有 openid 但后端 getMe 校验失败/用户不存在 -> 判定不一致,清掉并引导重新登录
|
||||
* - 本地与云端 ID/openid 一致 -> 放行
|
||||
* 登录守卫:首次进入做一次云端校验并缓存结果,
|
||||
* 之后同一 openid 直接读取缓存放行,不再重复请求后端。
|
||||
* 每次 Tab 切换/重新显示时再核对一次,避免登录完成后还停留在旧的“未登录”态。
|
||||
*/
|
||||
export default function LoginGuard(props: { children: React.ReactNode }) {
|
||||
const [state, setState] = useState<GuardState>('checking')
|
||||
|
||||
useEffect(() => {
|
||||
const localUser = getUserInfoRaw()
|
||||
|
||||
// 本地根本没登录信息
|
||||
if (!localUser?.openid) {
|
||||
setState('denied')
|
||||
return
|
||||
}
|
||||
|
||||
// 本地是旧版 mock 残留:与真实云端不一致,强制重新登录
|
||||
if (isMockOpenid(localUser.openid)) {
|
||||
clearUserInfo()
|
||||
logout()
|
||||
setState('denied')
|
||||
return
|
||||
}
|
||||
|
||||
// 本地有真实 openid:到后端校验是否与云端一致
|
||||
getMe()
|
||||
.then((me) => {
|
||||
const consistent = !!me && !!me.id && me.openid === localUser.openid
|
||||
if (consistent) {
|
||||
setState('ok')
|
||||
} else {
|
||||
clearUserInfo()
|
||||
logout()
|
||||
setState('denied')
|
||||
}
|
||||
const verify = useCallback(() => {
|
||||
let cancelled = false
|
||||
isAuthVerified()
|
||||
.then((ok) => {
|
||||
if (!cancelled) setState(ok ? 'ok' : 'denied')
|
||||
})
|
||||
.catch(() => {
|
||||
// 后端不可用时的谨慎处理:不静默放行,也不抹掉本地,
|
||||
// 返回 denied 让用户尝试重新登录(后端恢复后即可通过)
|
||||
setState('denied')
|
||||
if (!cancelled) setState('denied')
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const cancel = verify()
|
||||
const onTab = () => {
|
||||
verify()
|
||||
}
|
||||
const onTheme = () => {
|
||||
verify()
|
||||
}
|
||||
Taro.eventCenter.on('tabBarChange', onTab)
|
||||
Taro.eventCenter.on(THEME_CHANGE_EVENT, onTheme)
|
||||
Taro.eventCenter.on('authStateChanged', onTab)
|
||||
return () => {
|
||||
cancel()
|
||||
Taro.eventCenter.off('tabBarChange', onTab)
|
||||
Taro.eventCenter.off(THEME_CHANGE_EVENT, onTheme)
|
||||
Taro.eventCenter.off('authStateChanged', onTab)
|
||||
}
|
||||
}, [verify])
|
||||
|
||||
if (state === 'checking') {
|
||||
return (
|
||||
<View className='login-guard'>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { PageMeta, NavigationBar } from '@tarojs/components'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
|
||||
interface ThemedPageMetaProps {
|
||||
/** 强制顶部使用深色背景(用于浅色主题但顶部是深色大图的页面) */
|
||||
darkTop?: boolean
|
||||
}
|
||||
|
||||
export default function ThemedPageMeta({ darkTop = false }: ThemedPageMetaProps) {
|
||||
const { resolvedTheme } = useThemeContext()
|
||||
const isDark = resolvedTheme === 'dark' || darkTop
|
||||
const bg = isDark ? '#191919' : '#ffffff'
|
||||
const frontColor = isDark ? '#ffffff' : '#000000'
|
||||
|
||||
return (
|
||||
<PageMeta
|
||||
backgroundColor={bg}
|
||||
backgroundColorTop={bg}
|
||||
backgroundColorBottom={bg}
|
||||
backgroundTextStyle={isDark ? 'light' : 'dark'}
|
||||
rootBackgroundColor={bg}
|
||||
>
|
||||
<NavigationBar frontColor={frontColor} backgroundColor={bg} />
|
||||
</PageMeta>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createContext, useContext, useState, useCallback, useEffect } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { getTheme, setTheme as saveTheme, resolveTheme, type ThemeMode } from '../utils/store'
|
||||
import { View } from '@tarojs/components'
|
||||
import { getTheme, setTheme as saveTheme, resolveTheme, setDetectedSystemTheme, type ThemeMode } from '../utils/store'
|
||||
import { applyPageBackground } from '../utils/themeBackground'
|
||||
|
||||
interface ThemeContextValue {
|
||||
@@ -19,16 +20,14 @@ const ThemeContext = createContext<ThemeContextValue>({
|
||||
|
||||
export { ThemeContext }
|
||||
|
||||
type ThemeChangeListener = (result: { theme?: string }) => void
|
||||
|
||||
type ThemeChangeApi = {
|
||||
onThemeChange?: (listener: ThemeChangeListener) => void
|
||||
offThemeChange?: (listener: ThemeChangeListener) => void
|
||||
}
|
||||
|
||||
/** 主题广播事件名:自定义 tabBar 等无法继承 React Context 的独立组件通过它同步主题 */
|
||||
export const THEME_CHANGE_EVENT = 'themeChange'
|
||||
|
||||
type ThemeChangeApi = {
|
||||
onThemeChange?: (listener: (res: { theme?: string }) => void) => void
|
||||
offThemeChange?: (listener: (res: { theme?: string }) => void) => void
|
||||
}
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [theme, set] = useState<ThemeMode>(getTheme())
|
||||
const [resolvedTheme, setResolved] = useState<'light' | 'dark'>(resolveTheme(getTheme()))
|
||||
@@ -48,6 +47,57 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
setTheme(next)
|
||||
}, [resolvedTheme, setTheme])
|
||||
|
||||
/**
|
||||
* darkmode 只作为系统主题探测通道;选中“跟随系统”时才跟着系统切换,
|
||||
* 固定浅色/深色时由 setTheme 直接决定,不被动绑定系统。
|
||||
*/
|
||||
const updateFromSystem = useCallback((t: 'light' | 'dark') => {
|
||||
setDetectedSystemTheme(t)
|
||||
if (getTheme() === 'auto') {
|
||||
setResolved(t)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const detectSystemTheme = useCallback(() => {
|
||||
try {
|
||||
const info = Taro.getAppBaseInfo()
|
||||
updateFromSystem(info.theme === 'dark' ? 'dark' : 'light')
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
// CSS 探测器作为兜底:个别机型 getAppBaseInfo().theme 延迟时再读一次
|
||||
try {
|
||||
Taro.createSelectorQuery()
|
||||
.select('.system-theme-detector')
|
||||
.fields({ computedStyle: ['opacity'] })
|
||||
.exec((res: any) => {
|
||||
const opacity = res && res[0] && res[0].computedStyle && res[0].computedStyle.opacity
|
||||
updateFromSystem(opacity === '1' ? 'dark' : 'light')
|
||||
})
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}, [updateFromSystem])
|
||||
|
||||
useEffect(() => {
|
||||
detectSystemTheme()
|
||||
}, [detectSystemTheme])
|
||||
|
||||
// 系统实时切换深浅色时更新“跟随系统”模式
|
||||
useEffect(() => {
|
||||
const listener = (res: { theme?: string }) => {
|
||||
if (res && (res.theme === 'dark' || res.theme === 'light')) {
|
||||
updateFromSystem(res.theme)
|
||||
}
|
||||
}
|
||||
const themeApi = Taro as typeof Taro & ThemeChangeApi
|
||||
themeApi.onThemeChange?.(listener)
|
||||
return () => {
|
||||
themeApi.offThemeChange?.(listener)
|
||||
}
|
||||
}, [updateFromSystem])
|
||||
|
||||
// 广播实际生效主题,供 custom-tab-bar 等独立组件订阅
|
||||
useEffect(() => {
|
||||
Taro.eventCenter.trigger(THEME_CHANGE_EVENT, resolvedTheme)
|
||||
@@ -59,35 +109,20 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
applyPageBackground(resolvedTheme)
|
||||
}, [resolvedTheme])
|
||||
|
||||
// 页面每次显示时(从小程序后台切回前台)重新检测系统主题
|
||||
// 页面每次显示时(从小程序后台切回前台)重新探测系统主题
|
||||
useDidShow(() => {
|
||||
if (theme === 'auto') {
|
||||
setResolved(resolveTheme('auto'))
|
||||
detectSystemTheme()
|
||||
}
|
||||
})
|
||||
|
||||
// auto模式下监听系统主题变化
|
||||
useEffect(() => {
|
||||
if (theme === 'auto') {
|
||||
// 先同步一次当前系统主题
|
||||
setResolved(resolveTheme('auto'))
|
||||
const listener: ThemeChangeListener = (res) => {
|
||||
setResolved(res.theme === 'dark' ? 'dark' : 'light')
|
||||
}
|
||||
const themeApi = Taro as typeof Taro & ThemeChangeApi
|
||||
themeApi.onThemeChange?.(listener)
|
||||
return () => {
|
||||
themeApi.offThemeChange?.(listener)
|
||||
}
|
||||
} else {
|
||||
updateResolved(theme)
|
||||
}
|
||||
}, [theme, updateResolved])
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, resolvedTheme, toggleTheme, setTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
<>
|
||||
<View className='system-theme-detector' />
|
||||
<ThemeContext.Provider value={{ theme, resolvedTheme, toggleTheme, setTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120px;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
height: calc(96px + constant(safe-area-inset-bottom));
|
||||
height: calc(96px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 -8px 20px rgba(0, 0, 0, 0.08);
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
z-index: 1000;
|
||||
@@ -18,10 +20,10 @@
|
||||
|
||||
/* 自定义组件样式隔离:两套主题配色在组件内自包含,不依赖全局 app.wxss */
|
||||
.custom-tab-bar.theme-dark {
|
||||
background: rgba(25, 25, 25, 0.75);
|
||||
background: rgba(25, 25, 25, 0.78);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.5);
|
||||
box-shadow: 0 -8px 20px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
@@ -29,28 +31,31 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
padding: 0 12px;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
max-width: 160px;
|
||||
}
|
||||
|
||||
.tab-icon-img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-bottom: 4px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-bottom: 2px;
|
||||
transition: transform 0.2s;
|
||||
opacity: 0.6;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-icon-img {
|
||||
transform: scale(1.15);
|
||||
transform: scale(1.08);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
font-size: 20px;
|
||||
color: #b08d8d;
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
transition: color 0.2s;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.custom-tab-bar.theme-dark .tab-label {
|
||||
@@ -59,7 +64,6 @@
|
||||
|
||||
.tab-item.active .tab-label {
|
||||
color: #ff9a9e;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.custom-tab-bar.theme-dark .tab-item.active .tab-label {
|
||||
|
||||
@@ -7,18 +7,28 @@ import { applyPageBackground } from '../utils/themeBackground'
|
||||
import './index.scss'
|
||||
|
||||
const TABS = [
|
||||
{ pagePath: '/pages/index/index', text: '首页', icon: '/icon/首页.svg', iconActive: '/icon/首页-fill.svg' },
|
||||
{ pagePath: '/pages/shop/index', text: '商品', icon: '/icon/商品.svg', iconActive: '/icon/商品-fill.svg' },
|
||||
{ pagePath: '/pages/designList/index', text: '设计清单', icon: '/icon/调色盘.svg', iconActive: '/icon/调色盘-fill.svg' },
|
||||
{ pagePath: '/pages/orders/index', text: '订单', icon: '/icon/包裹.svg', iconActive: '/icon/包裹-fill.svg' },
|
||||
{ pagePath: '/pages/profile/index', text: '我的', icon: '/icon/个人.svg', iconActive: '/icon/个人-fill.svg' }
|
||||
{ pagePath: '/pages/index/index', label: '首页', icon: '/icon/首页.svg', iconActive: '/icon/首页-fill.svg' },
|
||||
{ pagePath: '/pages/shop/index', label: '商品', icon: '/icon/商品.svg', iconActive: '/icon/商品-fill.svg' },
|
||||
{ pagePath: '/pages/designList/index', label: '设计清单', icon: '/icon/调色盘.svg', iconActive: '/icon/调色盘-fill.svg' },
|
||||
{ pagePath: '/pages/orders/index', label: '订单', icon: '/icon/包裹.svg', iconActive: '/icon/包裹-fill.svg' },
|
||||
{ pagePath: '/pages/profile/index', label: '我的', icon: '/icon/个人.svg', iconActive: '/icon/个人-fill.svg' }
|
||||
]
|
||||
|
||||
function matchTab(path: string): string {
|
||||
const idx = TABS.findIndex((t) => path.endsWith(t.pagePath))
|
||||
return TABS[idx >= 0 ? idx : 0].pagePath
|
||||
}
|
||||
|
||||
function currentTabPath(): string {
|
||||
const path = Taro.getCurrentInstance().router?.path || 'pages/index/index'
|
||||
return matchTab(path)
|
||||
}
|
||||
|
||||
export default function CustomTabBar() {
|
||||
// 自定义 tabBar 是独立组件,无法继承页面里的 React Context,
|
||||
// 初始值从本地主题配置读取,之后通过 eventCenter 跟随主题切换
|
||||
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>(resolveTheme(getTheme()))
|
||||
const currentPath = `/${Taro.getCurrentInstance().router?.path || 'pages/index/index'}`
|
||||
|
||||
// 微信自定义 tabBar 不会自动感知 page 路由变化,用 state 保持选中态响应式
|
||||
const [activeTab, setActiveTab] = useState<string>(currentTabPath)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (t: 'light' | 'dark') => setResolvedTheme(t)
|
||||
@@ -28,6 +38,17 @@ export default function CustomTabBar() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 其它入口(如页面内部调用 switchTab)也会回到 tabBar,收到事件后重新同步选中态
|
||||
useEffect(() => {
|
||||
const handler = (path?: string) => {
|
||||
setActiveTab(matchTab(path || Taro.getCurrentInstance().router?.path || 'pages/index/index'))
|
||||
}
|
||||
Taro.eventCenter.on('tabBarChange', handler)
|
||||
return () => {
|
||||
Taro.eventCenter.off('tabBarChange', handler)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// tabBar 是 tab 页切换时必定会挂载/更新的组件:在这里也刷新一次页面背景,
|
||||
// 兜底处理“登录后切到受保护页面”时自定义导航区背景,避免残留白色
|
||||
useEffect(() => {
|
||||
@@ -35,13 +56,15 @@ export default function CustomTabBar() {
|
||||
}, [resolvedTheme])
|
||||
|
||||
const switchTab = (url: string) => {
|
||||
setActiveTab(matchTab(url))
|
||||
Taro.switchTab({ url })
|
||||
Taro.eventCenter?.trigger('tabBarChange', url)
|
||||
}
|
||||
|
||||
return (
|
||||
<View className={`custom-tab-bar theme-${resolvedTheme}`}>
|
||||
{TABS.map((tab) => {
|
||||
const isActive = currentPath === tab.pagePath
|
||||
const isActive = tab.pagePath === activeTab
|
||||
return (
|
||||
<View
|
||||
key={tab.pagePath}
|
||||
@@ -49,7 +72,7 @@ export default function CustomTabBar() {
|
||||
onTap={() => switchTab(tab.pagePath)}
|
||||
>
|
||||
<Image className={`tab-icon-img ${isActive ? 'active' : ''}`} src={isActive ? tab.iconActive : tab.icon} mode='aspectFit' />
|
||||
<Text className='tab-label'>{tab.text}</Text>
|
||||
<Text className='tab-label'>{tab.label}</Text>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '收货地址'
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { AddressItem } from '../../types'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
|
||||
export default function AddressPage() {
|
||||
const { theme, resolvedTheme } = useThemeContext()
|
||||
@@ -93,6 +94,7 @@ export default function AddressPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='address-page'>
|
||||
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '定制协议'
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import './index.scss'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
|
||||
export default function AgreementPage() {
|
||||
const { theme, resolvedTheme } = useThemeContext()
|
||||
@@ -34,6 +35,7 @@ export default function AgreementPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='agreement-page'>
|
||||
<View className='agreement-content' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
<Text className='agreement-title'>定制协议</Text>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '确认下单'
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getDesignList, designToOrder, getDefaultAddress, getAddressList, type A
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
|
||||
export default function CheckoutPage() {
|
||||
const { theme, resolvedTheme } = useThemeContext()
|
||||
@@ -59,6 +60,7 @@ export default function CheckoutPage() {
|
||||
if (!design) {
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='checkout-page'>
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
<Text className='page-title'>设计不存在</Text>
|
||||
@@ -79,6 +81,7 @@ export default function CheckoutPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='checkout-page'>
|
||||
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '设计清单'
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import LoginGuard from '../../components/LoginGuard'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
|
||||
const STATUS_TABS = [
|
||||
{ code: 'all', label: '全部' },
|
||||
@@ -115,6 +116,7 @@ export default function DesignListPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<LoginGuard>
|
||||
<View className='design-page'>
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: 'DIY工作台'
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getDesignList, setDesignList, updateDesign, type DesignItem, type Stick
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
|
||||
export default function DIYPage() {
|
||||
const { theme, resolvedTheme } = useThemeContext()
|
||||
@@ -257,6 +258,7 @@ export default function DIYPage() {
|
||||
if (!category) {
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='diy-page'>
|
||||
<Text className='page-title'>加载中...</Text>
|
||||
</View>
|
||||
@@ -266,6 +268,7 @@ export default function DIYPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='diy-page'>
|
||||
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '编辑贴纸'
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getDesignList, setDesignList, updateDesign, type DesignItem, type Stick
|
||||
import { useThemeContext } from '../../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../../components/ThemedPageMeta'
|
||||
|
||||
/* ============================================================
|
||||
使用 Canvas 实现贴纸编辑(亮度/色相/线稿)
|
||||
@@ -304,6 +305,7 @@ export default function StickerEditPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='sticker-edit-page'>
|
||||
{/* Header */}
|
||||
<View className='edit-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '智绘微刻'
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CATEGORIES } from '../../utils/productConfig'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
|
||||
// 成品展示配图(从 img 里取对应实物图)
|
||||
const SHOWCASE_LIST = [
|
||||
@@ -48,6 +49,7 @@ export default function Index() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='index-page'>
|
||||
|
||||
{/* 顶部标题栏 */}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '订单详情'
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import './index.scss'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
import { getOrderList, getDefaultAddress, getAddressList, type AddressItem, type OrderItem } from '../../utils/store'
|
||||
import { PRODUCT_ICON_MAP } from '../../utils/productConfig'
|
||||
|
||||
@@ -51,6 +52,7 @@ export default function OrderDetailPage() {
|
||||
if (!order) {
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='order-detail-page'>
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
<Text className='page-title'>订单不存在</Text>
|
||||
@@ -62,6 +64,7 @@ export default function OrderDetailPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='order-detail-page'>
|
||||
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '订单列表'
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import LoginGuard from '../../components/LoginGuard'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
|
||||
const STATUS_TABS = [
|
||||
{ code: 'all', label: '全部' },
|
||||
@@ -75,6 +76,7 @@ export default function OrdersPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<LoginGuard>
|
||||
<View className='orders-page'>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '商品详情'
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import { addDesign } from '../../utils/store'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
import { getProductIconImg } from '../../utils/productConfig'
|
||||
|
||||
const HERO_COLORS = ['#FFE4EC', '#FFF0F5', '#FCE4EC']
|
||||
@@ -25,6 +26,7 @@ export default function ProductPage() {
|
||||
if (!product) {
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='product-page'>
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
<Text className='page-title'>商品未找到</Text>
|
||||
@@ -58,6 +60,7 @@ export default function ProductPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='product-page'>
|
||||
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '个人中心'
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getOrderList, getUserInfo, setUserInfo, getDesignList } from '../../uti
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
import { login, getMe, updateProfile, getToken } from '../../utils/api'
|
||||
import { safeHideLoading } from '../../utils/request'
|
||||
|
||||
@@ -276,6 +277,7 @@ export default function ProfilePage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`} style={{ minHeight: '100vh' }}>
|
||||
<ThemedPageMeta />
|
||||
<View className='profile-page'>
|
||||
|
||||
{/* 用户信息头部 */}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '客服中心'
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import './index.scss'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
|
||||
const FAQ_ITEMS = [
|
||||
{ q: '定制周期需要多久?', a: '通常下单后3-5个工作日内发货,批量订单(50件以上)可能需要7-10个工作日。' },
|
||||
@@ -19,6 +20,7 @@ export default function ServicePage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`} style={{ minHeight: '100vh' }}>
|
||||
<ThemedPageMeta />
|
||||
<View className='service-page'>
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
<View className='star-badge' />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '设置'
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import './index.scss'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
import { getUserInfo, setUserInfo, clearUserInfo, getAddressList, getDefaultAddress, type AddressItem } from '../../utils/store'
|
||||
|
||||
const THEME_OPTIONS: { label: string; value: 'light' | 'dark' | 'auto' }[] = [
|
||||
@@ -66,6 +67,7 @@ export default function SettingsPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`} style={{ minHeight: '100vh' }}>
|
||||
<ThemedPageMeta />
|
||||
<View className='settings-page'>
|
||||
<View className='settings-area'>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
// 顶部为深色商品大图,状态栏文字设为白色,确保 iOS 时间/电量可见
|
||||
navigationBarTextStyle: 'white'
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ProductCategory } from '../../../types'
|
||||
import { useSafeArea } from '../../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../../hooks/useStatusBar'
|
||||
import { useThemeContext } from '../../../context/ThemeContext'
|
||||
import ThemedPageMeta from '../../../components/ThemedPageMeta'
|
||||
|
||||
const clamp = (v: number, min: number, max: number) => Math.min(Math.max(v, min), max)
|
||||
const lerp = (a: number, b: number, t: number) => a + (b - a) * t
|
||||
@@ -107,6 +108,7 @@ export default function ShopDetailPage() {
|
||||
if (!product) {
|
||||
return (
|
||||
<View className="shop-detail-page">
|
||||
<ThemedPageMeta darkTop />
|
||||
<Text style={{ color: '#fff', padding: '40rpx' }}>商品不存在</Text>
|
||||
</View>
|
||||
)
|
||||
@@ -114,6 +116,7 @@ export default function ShopDetailPage() {
|
||||
|
||||
return (
|
||||
<View className="shop-detail-page">
|
||||
<ThemedPageMeta darkTop />
|
||||
{/* Fixed Hero */}
|
||||
<View className="shop-hero-fixed">
|
||||
<View
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom'
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import Taro from '@tarojs/taro'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
import './index.scss'
|
||||
import { PRODUCTS } from '../../utils/productConfig'
|
||||
|
||||
@@ -17,6 +18,7 @@ export default function ShopPage() {
|
||||
|
||||
return (
|
||||
<View className={`shop-page theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className="shop-list">
|
||||
<View className="shop-header" style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
|
||||
<Text className="shop-header-title">智绘精选</Text>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '用户数据库'
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
|
||||
export default function UserDatabasePage() {
|
||||
const { theme, resolvedTheme } = useThemeContext()
|
||||
@@ -82,6 +83,7 @@ export default function UserDatabasePage() {
|
||||
if (!isUnlocked) {
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='udb-page'>
|
||||
<View className='udb-lock-card dashed-card mt-20'>
|
||||
<View className='star-badge' />
|
||||
@@ -110,6 +112,7 @@ export default function UserDatabasePage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='udb-page'>
|
||||
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
enablePageMeta: true,
|
||||
navigationBarTitleText: '词云生成'
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import './index.scss'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||
|
||||
export default function WordCloudPage() {
|
||||
const { theme, resolvedTheme } = useThemeContext()
|
||||
@@ -61,6 +62,7 @@ export default function WordCloudPage() {
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<ThemedPageMeta />
|
||||
<View className='wordcloud-page'>
|
||||
|
||||
<View className='page-header flex-between' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import { getMe, logout, getToken } from './api'
|
||||
import { getUserInfoRaw, clearUserInfo, isMockOpenid } from './store'
|
||||
|
||||
const CACHE_KEY = 'smart_auth_verified'
|
||||
const TTL = 30 * 60 * 1000
|
||||
|
||||
interface CacheEntry {
|
||||
openid: string
|
||||
ok: boolean
|
||||
at: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录态全局缓存:首次校验通过后用内存+本地双缓存记住结果,
|
||||
* 之后进设计清单/订单等受保护页面不再重复请求后端。
|
||||
* 登录、退出登录、切换账号都会通过 invalidateAuthCache() 清掉缓存。
|
||||
*/
|
||||
let verifiedCache: CacheEntry | null = null
|
||||
let inflight: Promise<boolean> | null = null
|
||||
|
||||
export function invalidateAuthCache(): void {
|
||||
verifiedCache = null
|
||||
inflight = null
|
||||
try {
|
||||
Taro.removeStorageSync(CACHE_KEY)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function readCache(openid: string): boolean | null {
|
||||
const entry = verifiedCache || (() => {
|
||||
try {
|
||||
const raw = Taro.getStorageSync(CACHE_KEY)
|
||||
return raw && typeof raw === 'object' ? raw as CacheEntry : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})()
|
||||
if (!entry || entry.openid !== openid) return null
|
||||
if (Date.now() - entry.at > TTL) return null
|
||||
verifiedCache = entry
|
||||
return entry.ok
|
||||
}
|
||||
|
||||
function writeCache(openid: string, ok: boolean): void {
|
||||
const entry: CacheEntry = { openid, ok, at: Date.now() }
|
||||
verifiedCache = entry
|
||||
try {
|
||||
Taro.setStorageSync(CACHE_KEY, entry)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地已有真实 openid + token 时视为“本地已登录”。
|
||||
* 后端校验失败(网络抖动/服务端暂时不可用)不直接锁死页面,
|
||||
* 由后续 401 或主动登出再重置状态。
|
||||
*/
|
||||
function hasLocalSession(): boolean {
|
||||
const user = getUserInfoRaw()
|
||||
return !!user?.openid && !isMockOpenid(user.openid) && !!user.accessToken && !!getToken()
|
||||
}
|
||||
|
||||
export async function isAuthVerified(): Promise<boolean> {
|
||||
const localUser = getUserInfoRaw()
|
||||
if (!localUser?.openid) {
|
||||
console.log('[AuthGuard] 无本地用户,判定未登录')
|
||||
return false
|
||||
}
|
||||
console.log('[AuthGuard] 本地用户 openid=', localUser.openid)
|
||||
if (isMockOpenid(localUser.openid)) {
|
||||
clearUserInfo()
|
||||
logout()
|
||||
invalidateAuthCache()
|
||||
return false
|
||||
}
|
||||
|
||||
const cached = readCache(localUser.openid)
|
||||
if (cached !== null) {
|
||||
console.log('[AuthGuard] 命中本地缓存 ok=', cached)
|
||||
return cached
|
||||
}
|
||||
|
||||
if (!inflight) {
|
||||
inflight = getMe()
|
||||
.then((me) => {
|
||||
console.log('[AuthGuard] getMe 返回:', JSON.stringify(me))
|
||||
const consistent = !!me && !!me.id && me.openid === localUser.openid
|
||||
console.log('[AuthGuard] 一致性=', consistent)
|
||||
if (consistent) {
|
||||
writeCache(localUser.openid, true)
|
||||
return true
|
||||
}
|
||||
clearUserInfo()
|
||||
logout()
|
||||
invalidateAuthCache()
|
||||
return false
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('[AuthGuard] getMe 失败,走本地会话兜底:', e)
|
||||
return hasLocalSession()
|
||||
})
|
||||
.finally(() => {
|
||||
inflight = null
|
||||
})
|
||||
}
|
||||
|
||||
return inflight
|
||||
}
|
||||
+17
-6
@@ -2,6 +2,7 @@ import Taro from '@tarojs/taro'
|
||||
import type { DesignItem, OrderItem, AddressItem, StickerItem } from '../types'
|
||||
import { PRODUCT_ICON_MAP } from './productConfig'
|
||||
|
||||
import { invalidateAuthCache } from './authState'
|
||||
// 重新导出类型,供各页面从 store 直接引用(修正原「声明但不导出」的编译错误)
|
||||
export type { DesignItem, OrderItem, AddressItem, StickerItem }
|
||||
|
||||
@@ -66,6 +67,8 @@ export function setUserInfoRaw(info: any) {
|
||||
Taro.setStorageSync(`user_info_${info.openid}`, info)
|
||||
saveToRegistry(info.openid)
|
||||
}
|
||||
invalidateAuthCache()
|
||||
Taro.eventCenter?.trigger('authStateChanged', { openid: info?.openid || '' })
|
||||
}
|
||||
|
||||
export function getUserInfoByOpenid(openid: string): any | null {
|
||||
@@ -82,6 +85,8 @@ export function clearUserInfo() {
|
||||
// 仅退出登录,不删除用户数据
|
||||
Taro.removeStorageSync(USER_KEY)
|
||||
Taro.removeStorageSync(ACTIVE_USER_KEY)
|
||||
invalidateAuthCache()
|
||||
Taro.eventCenter?.trigger('authStateChanged', { openid: '' })
|
||||
}
|
||||
|
||||
export function setUserInfo(info: any) {
|
||||
@@ -259,17 +264,23 @@ export function setTheme(theme: ThemeMode) {
|
||||
Taro.setStorageSync(THEME_KEY, theme)
|
||||
}
|
||||
|
||||
// 跟随系统模式下,系统主题由 onThemeChange/getAppBaseInfo 写入缓存,供 resolveTheme 使用
|
||||
let detectedSystemTheme: 'light' | 'dark' | null = null
|
||||
|
||||
export function setDetectedSystemTheme(t: 'light' | 'dark'): void {
|
||||
detectedSystemTheme = t
|
||||
}
|
||||
|
||||
/** 根据自动模式解析实际生效主题 */
|
||||
export function resolveTheme(mode: ThemeMode): 'light' | 'dark' {
|
||||
if (mode === 'auto') {
|
||||
// 需 app.json 开启 darkmode 后 theme 字段才有效;getSystemInfoSync 已废弃,改用 getAppBaseInfo
|
||||
// darkmode 开启后 getAppBaseInfo().theme 能拿到系统主题;缓存仅用于启动时加速
|
||||
if (detectedSystemTheme) return detectedSystemTheme
|
||||
try {
|
||||
const info = Taro.getAppBaseInfo()
|
||||
return info.theme === 'dark' ? 'dark' : 'light'
|
||||
} catch {
|
||||
const info = Taro.getSystemInfoSync()
|
||||
return info.theme === 'dark' ? 'dark' : 'light'
|
||||
}
|
||||
if (info.theme === 'dark' || info.theme === 'light') return info.theme
|
||||
} catch { /* ignore */ }
|
||||
return 'light'
|
||||
}
|
||||
return mode
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user