329 lines
9.8 KiB
TypeScript
329 lines
9.8 KiB
TypeScript
import Taro from '@tarojs/taro'
|
||
import type { DesignItem, OrderItem, AddressItem, StickerItem } from '../types'
|
||
import { PRODUCT_ICON_MAP } from './productConfig'
|
||
import { assetUrl } from './asset'
|
||
|
||
import { invalidateAuthCache } from './authState'
|
||
// 重新导出类型,供各页面从 store 直接引用(修正原「声明但不导出」的编译错误)
|
||
export type { DesignItem, OrderItem, AddressItem, StickerItem }
|
||
|
||
const THEME_KEY = 'smart_theme'
|
||
const USER_KEY = 'smart_user_info'
|
||
const ACTIVE_USER_KEY = 'smart_active_openid'
|
||
|
||
/** 判断 openid 是否为历史版 mock 残留(形如 mock_xxx) */
|
||
export function isMockOpenid(openid?: string | null): boolean {
|
||
return !!openid && (openid.startsWith('mock_') || openid.startsWith('mock-'))
|
||
}
|
||
|
||
// ---------- 当前用户 openid 管理 ----------
|
||
|
||
function getActiveOpenid(): string {
|
||
const user = getUserInfoRaw()
|
||
const openid = user?.openid || Taro.getStorageSync(ACTIVE_USER_KEY) || '_guest_'
|
||
Taro.setStorageSync(ACTIVE_USER_KEY, openid)
|
||
return openid
|
||
}
|
||
|
||
function key(scope: string): string {
|
||
const prefix = getActiveOpenid()
|
||
return `${scope}_${prefix}`
|
||
}
|
||
|
||
// ---------- 用户数据 ----------
|
||
|
||
const USER_REGISTRY_KEY = 'smart_user_registry'
|
||
const ADMIN_PASSWORD = 'zhihui2024'
|
||
|
||
// ---------- 用户注册表 ----------
|
||
|
||
function getUserRegistry(): string[] {
|
||
try { return Taro.getStorageSync(USER_REGISTRY_KEY) || [] } catch { return [] }
|
||
}
|
||
|
||
function saveToRegistry(openid: string) {
|
||
const list = getUserRegistry()
|
||
if (!list.includes(openid)) {
|
||
list.push(openid)
|
||
Taro.setStorageSync(USER_REGISTRY_KEY, list)
|
||
}
|
||
}
|
||
|
||
function removeFromRegistry(openid: string) {
|
||
const list = getUserRegistry().filter(id => id !== openid)
|
||
Taro.setStorageSync(USER_REGISTRY_KEY, list)
|
||
}
|
||
|
||
// ---------- 用户数据 ----------
|
||
|
||
export function getUserInfoRaw(): any {
|
||
try { return Taro.getStorageSync(USER_KEY) } catch { return null }
|
||
}
|
||
|
||
export function setUserInfoRaw(info: any) {
|
||
Taro.setStorageSync(USER_KEY, info)
|
||
if (info?.openid) {
|
||
Taro.setStorageSync(ACTIVE_USER_KEY, info.openid)
|
||
// 为每个用户备份独立副本,方便切换账号时读取
|
||
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 {
|
||
try {
|
||
const backup = Taro.getStorageSync(`user_info_${openid}`)
|
||
if (backup) return backup
|
||
} catch {}
|
||
const current = getUserInfoRaw()
|
||
if (current?.openid === openid) return current
|
||
return null
|
||
}
|
||
|
||
export function clearUserInfo() {
|
||
// 仅退出登录,不删除用户数据
|
||
Taro.removeStorageSync(USER_KEY)
|
||
Taro.removeStorageSync(ACTIVE_USER_KEY)
|
||
invalidateAuthCache()
|
||
Taro.eventCenter?.trigger('authStateChanged', { openid: '' })
|
||
}
|
||
|
||
export function setUserInfo(info: any) {
|
||
setUserInfoRaw(info)
|
||
}
|
||
|
||
export function getUserInfo() {
|
||
return getUserInfoRaw()
|
||
}
|
||
|
||
// ---------- 用户数据库管理 ----------
|
||
|
||
export function listAllUsers() {
|
||
const registry = getUserRegistry()
|
||
return registry.map(openid => {
|
||
const info = getUserInfoByOpenid(openid)
|
||
const dList = getDesignListFor(openid)
|
||
const oList = getOrderListFor(openid)
|
||
return {
|
||
openid,
|
||
nickName: info?.nickName || '未知用户',
|
||
avatarUrl: info?.avatarUrl || '',
|
||
loginAt: info?.loginAt || 0,
|
||
designCount: dList.length,
|
||
orderCount: oList.length
|
||
}
|
||
})
|
||
}
|
||
|
||
export function createUser(nickName: string, avatarUrl?: string) {
|
||
const openid = 'mock_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 6)
|
||
const info = { openid, nickName: nickName || '微信用户', avatarUrl: avatarUrl || '', loginAt: Date.now() }
|
||
setUserInfoRaw(info)
|
||
return info
|
||
}
|
||
|
||
export function switchUser(openid: string) {
|
||
const info = getUserInfoByOpenid(openid)
|
||
if (!info) return false
|
||
Taro.setStorageSync(USER_KEY, info)
|
||
Taro.setStorageSync(ACTIVE_USER_KEY, openid)
|
||
return true
|
||
}
|
||
|
||
export function deleteUser(openid: string) {
|
||
// 删除该用户的所有数据
|
||
Taro.removeStorageSync(`user_info_${openid}`)
|
||
Taro.removeStorageSync(`design_list_${openid}`)
|
||
Taro.removeStorageSync(`order_list_${openid}`)
|
||
Taro.removeStorageSync(`address_list_${openid}`)
|
||
removeFromRegistry(openid)
|
||
// 如果删的是当前登录用户,清掉登录态
|
||
const current = getUserInfoRaw()
|
||
if (current?.openid === openid) {
|
||
clearUserInfo()
|
||
}
|
||
}
|
||
|
||
export function verifyAdminPassword(password: string): boolean {
|
||
return password === ADMIN_PASSWORD
|
||
}
|
||
|
||
// ---------- 跨用户读取辅助函数 ----------
|
||
|
||
function keyFor(scope: string, openid: string) {
|
||
return `${scope}_${openid}`
|
||
}
|
||
|
||
function getDesignListFor(openid: string): DesignItem[] {
|
||
try { return Taro.getStorageSync(keyFor('design_list', openid)) || [] } catch { return [] }
|
||
}
|
||
|
||
function getOrderListFor(openid: string): OrderItem[] {
|
||
try { return Taro.getStorageSync(keyFor('order_list', openid)) || [] } catch { return [] }
|
||
}
|
||
|
||
// ---------- 设计清单 ----------
|
||
|
||
export function getDesignList(): DesignItem[] {
|
||
try { return Taro.getStorageSync(key('design_list')) || [] } catch { return [] }
|
||
}
|
||
|
||
export function setDesignList(list: DesignItem[]) {
|
||
Taro.setStorageSync(key('design_list'), list)
|
||
}
|
||
|
||
export function addDesign(product: any, count: number): DesignItem {
|
||
const list = getDesignList()
|
||
const iconImg = assetUrl(product?.iconImg || PRODUCT_ICON_MAP[product?.id] || '/icon/四角星.svg')
|
||
const item: DesignItem = {
|
||
id: 'DSG' + Date.now(),
|
||
productId: product.id,
|
||
productName: product.name,
|
||
productIcon: iconImg,
|
||
unitPrice: product.price,
|
||
count,
|
||
status: 'undesigned',
|
||
createdAt: new Date().toISOString().slice(0, 10)
|
||
}
|
||
setDesignList([...list, item])
|
||
return item
|
||
}
|
||
|
||
export function updateDesign(id: string, patch: Partial<DesignItem>) {
|
||
const list = getDesignList()
|
||
const idx = list.findIndex(d => d.id === id)
|
||
if (idx === -1) return
|
||
list[idx] = { ...list[idx], ...patch }
|
||
setDesignList(list)
|
||
}
|
||
|
||
/** 批量删除设计条目 */
|
||
export function removeDesigns(ids: string[]) {
|
||
const list = getDesignList().filter(d => !ids.includes(d.id))
|
||
setDesignList(list)
|
||
}
|
||
|
||
/** 根据ID删除单条设计 */
|
||
export function removeDesign(id: string) {
|
||
const list = getDesignList().filter(d => d.id !== id)
|
||
setDesignList(list)
|
||
}
|
||
|
||
// ---------- 订单 ----------
|
||
|
||
export function getOrderList(): OrderItem[] {
|
||
try { return Taro.getStorageSync(key('order_list')) || [] } catch { return [] }
|
||
}
|
||
|
||
export function setOrderList(list: OrderItem[]) {
|
||
Taro.setStorageSync(key('order_list'), list)
|
||
}
|
||
|
||
export function designToOrder(designId: string): OrderItem | null {
|
||
const dList = getDesignList()
|
||
const oList = getOrderList()
|
||
const design = dList.find(d => d.id === designId)
|
||
if (!design) return null
|
||
|
||
const rawIcon = design.productIcon?.startsWith('/icon/') || /^https?:/i.test(design.productIcon || '')
|
||
? design.productIcon
|
||
: PRODUCT_ICON_MAP[design.productId] || '/icon/四角星.svg'
|
||
|
||
const iconImg = assetUrl(rawIcon)
|
||
|
||
const order: OrderItem = {
|
||
id: 'ORD' + Date.now().toString().slice(-9),
|
||
productName: design.productName,
|
||
productIcon: iconImg,
|
||
statusCode: 'pending',
|
||
date: new Date().toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }).replace(/\//g, '-'),
|
||
price: '¥' + (design.unitPrice * design.count).toFixed(2),
|
||
count: design.count,
|
||
sku: `${design.productName} × ${design.count}`
|
||
}
|
||
|
||
const dIdx = dList.findIndex(d => d.id === designId)
|
||
if (dIdx !== -1) {
|
||
dList[dIdx].status = 'ordered'
|
||
dList[dIdx].orderId = order.id
|
||
}
|
||
|
||
setDesignList(dList)
|
||
setOrderList([order, ...oList])
|
||
return order
|
||
}
|
||
|
||
// ---------- 主题 ----------
|
||
|
||
export type ThemeMode = 'light' | 'dark' | 'auto'
|
||
|
||
export function getTheme(): ThemeMode {
|
||
try { return Taro.getStorageSync(THEME_KEY) || 'auto' } catch { return 'auto' }
|
||
}
|
||
|
||
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') {
|
||
// darkmode 开启后 getAppBaseInfo().theme 能拿到系统主题;缓存仅用于启动时加速
|
||
if (detectedSystemTheme) return detectedSystemTheme
|
||
try {
|
||
const info = Taro.getAppBaseInfo()
|
||
if (info.theme === 'dark' || info.theme === 'light') return info.theme
|
||
} catch { /* ignore */ }
|
||
return 'light'
|
||
}
|
||
return mode
|
||
}
|
||
|
||
// ---------- 收货地址 ----------
|
||
|
||
export function getAddressList(): AddressItem[] {
|
||
try { return Taro.getStorageSync(key('address_list')) || [] } catch { return [] }
|
||
}
|
||
|
||
export function setAddressList(list: AddressItem[]) {
|
||
Taro.setStorageSync(key('address_list'), list)
|
||
}
|
||
|
||
export function addAddress(addr: Omit<AddressItem, 'id'>): AddressItem {
|
||
const list = getAddressList()
|
||
const item: AddressItem = { ...addr, id: 'ADR' + Date.now() }
|
||
// 若设为默认,取消其他默认
|
||
if (item.isDefault) {
|
||
list.forEach(a => { a.isDefault = false })
|
||
}
|
||
setAddressList([item, ...list])
|
||
return item
|
||
}
|
||
|
||
export function updateAddress(id: string, patch: Partial<AddressItem>) {
|
||
const list = getAddressList()
|
||
const idx = list.findIndex(a => a.id === id)
|
||
if (idx === -1) return
|
||
if (patch.isDefault) list.forEach(a => { a.isDefault = false })
|
||
list[idx] = { ...list[idx], ...patch }
|
||
setAddressList(list)
|
||
}
|
||
|
||
export function deleteAddress(id: string) {
|
||
const list = getAddressList().filter(a => a.id !== id)
|
||
setAddressList(list)
|
||
}
|
||
|
||
export function getDefaultAddress(): AddressItem | undefined {
|
||
return getAddressList().find(a => a.isDefault)
|
||
}
|