import { View, Text, Image, Input, Button } from '@tarojs/components' import Taro, { useDidShow } from '@tarojs/taro' import { useState } from 'react' import './index.scss' import { getOrderList, getUserInfo, setUserInfo, getDesignList } from '../../utils/store' import { useThemeContext } from '../../context/ThemeContext' import { useSafeArea } from '../../hooks/useSafeArea' import { useStatusBar } from '../../hooks/useStatusBar' import ThemedPageMeta from '../../components/ThemedPageMeta' import ScrollTopMask from '../../components/ScrollTopMask' import { login, getMe, updateProfile, getToken } from '../../utils/api' import { safeHideLoading } from '../../utils/request' import { assetUrl } from '../../utils/asset' import { fetchOrders } from '../../utils/api/order' const ICON_MAP: Record = { '我的设计': assetUrl('/icon/调色盘.png'), '词云生成': assetUrl('/icon/词云生成.png'), '收货地址': assetUrl('/icon/地址.png'), '联系客服': assetUrl('/icon/电话.png'), '使用帮助': assetUrl('/icon/使用帮助.png'), '设置': assetUrl('/icon/设置.png') } const STATUS_MAP = [ { key: 'toDesign', label: '待设计', icon: assetUrl('/icon/三角尺.png') }, { key: 'pending', label: '待付款', icon: assetUrl('/icon/四角星.svg') }, { key: 'paid', label: '待发货', icon: assetUrl('/icon/包裹.png') }, { key: 'shipping', label: '待收货', icon: assetUrl('/icon/杯子.png') }, { key: 'done', label: '已完成', icon: assetUrl('/icon/书本.png') } ] const MENU_ITEMS = [ { label: '我的设计', path: '/pages/designList/index' }, { label: '词云生成', path: '/pages/wordcloud/index' }, { label: '收货地址', path: '/pages/address/index' }, { label: '联系客服', path: '/pages/service/index' }, { label: '使用帮助', path: '/pages/agreement/index' }, { label: '设置', path: '/pages/settings/index' } ] // Stable no-op: Taro runtime crashes when an event handler is removed from a // Text/Image element (pure-text is missing from componentsAlias). Keeping // onTap as a function at all times avoids the removeEventListener side-effect // that triggers the crash. const noop = () => {} export default function ProfilePage() { const { theme, resolvedTheme } = useThemeContext() const safe = useSafeArea() useStatusBar(resolvedTheme) const [isLoggedIn, setIsLoggedIn] = useState(false) const [userInfo, setUserInfoState] = useState({ nickName: '', avatarUrl: '' }) const [showLogin, setShowLogin] = useState(false) // loginStep: 'entry' 显示登录按钮;'fill' 新用户补全资料 const [loginStep, setLoginStep] = useState<'entry' | 'fill'>('entry') const [loginNickName, setLoginNickName] = useState('') const [loginAvatarUrl, setLoginAvatarUrl] = useState('') const [loginLoading, setLoginLoading] = useState(false) const [orderStats, setOrderStats] = useState({ toDesign: 0, pending: 0, paid: 0, shipping: 0, done: 0 }) const loadStats = async () => { const designs = getDesignList() try { const remote = (await fetchOrders({ page: 1, pageSize: 100 })).list setOrderStats({ toDesign: designs.filter(d => d.status === 'undesigned' || d.status === 'designing').length, pending: remote.filter(o => o.status === 'PENDING').length, paid: remote.filter(o => o.status === 'PAID' || o.status === 'PROCESSING').length, shipping: remote.filter(o => o.status === 'SHIPPED').length, done: remote.filter(o => o.status === 'COMPLETED').length }) } catch { const orders = getOrderList() setOrderStats({ toDesign: designs.filter(d => d.status === 'undesigned' || d.status === 'designing').length, pending: orders.filter(o => o.statusCode === 'pending').length, paid: orders.filter(o => o.statusCode === 'paid').length, shipping: orders.filter(o => o.statusCode === 'shipping').length, done: orders.filter(o => o.statusCode === 'done').length }) } } const sync = () => { const info = getUserInfo() if (info && info.openid) { setIsLoggedIn(true) setUserInfoState({ nickName: info.nickName || '', avatarUrl: info.avatarUrl || '' }) } else { setIsLoggedIn(false) setUserInfoState({ nickName: '', avatarUrl: '' }) } loadStats() } useDidShow(() => { sync() }) const handleOpenLogin = () => { setLoginStep('entry') setLoginNickName('') setLoginAvatarUrl('') setShowLogin(true) } const handleCloseLogin = () => setShowLogin(false) const loginFail = (msg: string) => { setLoginLoading(false) safeHideLoading() Taro.showToast({ title: msg, icon: 'none' }) } const loginFinish = (accessToken: string, openid: string, nickName: string, avatarUrl: string) => { const info = { openid, nickName: nickName || '微信用户', avatarUrl: avatarUrl || '', accessToken, loginAt: Date.now() } setUserInfo(info) setUserInfoState({ nickName: info.nickName, avatarUrl: info.avatarUrl }) setIsLoggedIn(true) setShowLogin(false) setLoginStep('entry') setLoginLoading(false) safeHideLoading() Taro.showToast({ title: '登录成功', icon: 'success' }) } /** * 登录:wx.login 拿 code -> 后端判断新老用户 * - 老用户(isNewUser=false) -> 直接用库里已有的昵称/头像完成,免设置 * - 新用户(isNewUser=true) -> 切到 'fill' 步骤补全资料 */ const handleLogin = async () => { if (loginLoading) return console.log('[Profile] 登录按钮被点击,开始登录流程') setLoginLoading(true) Taro.showLoading({ title: '登录中', mask: true }) try { const wxLogin = await Taro.login() console.log('[Profile] wx.login 成功,code=', wxLogin.code) const result = await login(wxLogin.code) console.log('[Profile] 后端 /auth/login 返回:', JSON.stringify(result)) if (!result || !result.accessToken) { loginFail('登录失败,未拿到 token') return } // 取 openid 落本地 let openid = '' try { const me = await getMe() openid = (me && me.openid) || '' } catch { openid = '' } if (!openid) { loginFail('登录异常,请重试') return } // 资料不完整(新用户,或老用户从未设置过资料)都引导补填 const needProfile = result.isNewUser || !result.nickname || !result.avatar if (needProfile) { // 预填已有值(若老用户有一部分已设置) setLoginNickName(result.nickname || '') setLoginAvatarUrl(result.avatar || '') safeHideLoading() setLoginLoading(false) setLoginStep('fill') Taro.showToast({ title: '请设置头像和昵称', icon: 'none' }) return } // 资料完整的老用户:直接用库里已有资料 loginFinish(result.accessToken, openid, result.nickname || '', result.avatar || '') } catch (e) { console.error('[Profile] 登录流程出错:', e) loginFail((e && (e as Error).message) || '后端连接失败,请检查网络') } } /** 新用户补全资料后保存到后端并完成登录 */ const handleSaveProfile = async () => { if (loginLoading) return setLoginLoading(true) Taro.showLoading({ title: '保存中', mask: true }) try { // 把昵称/头像存到后端(如需真实 openid 可再 getMe) await updateProfile({ nickname: loginNickName || undefined, avatar: loginAvatarUrl || undefined }) let openid = '' try { const me = await getMe() openid = (me && me.openid) || '' } catch { openid = '' } if (!openid) { loginFail('保存异常,请重试') return } const stored = getUserInfo() loginFinish( (stored && stored.accessToken) || getToken(), openid, loginNickName, loginAvatarUrl ) } catch (e) { console.error('[Profile] 保存资料出错:', e) loginFail((e && (e as Error).message) || '保存失败') } } const onChooseAvatar = (e: any) => { setLoginAvatarUrl(e.detail.avatarUrl || '') } const handleQuickClick = (type: string) => { switch (type) { case 'toDesign': Taro.setStorageSync('designList:filter', 'undesigned') Taro.switchTab({ url: '/pages/designList/index' }) Taro.eventCenter.trigger('tabBarChange', '/pages/designList/index') break case 'pending': Taro.setStorageSync('orders:filter', 'pending') Taro.switchTab({ url: '/pages/orders/index' }) Taro.eventCenter.trigger('tabBarChange', '/pages/orders/index') break case 'paid': Taro.setStorageSync('orders:filter', 'paid') Taro.switchTab({ url: '/pages/orders/index' }) break case 'shipping': Taro.setStorageSync('orders:filter', 'shipping') Taro.switchTab({ url: '/pages/orders/index' }) break case 'done': Taro.setStorageSync('orders:filter', 'done') Taro.switchTab({ url: '/pages/orders/index' }) break } } const handleMenuClick = (path: string, label: string) => { // Clear any lingering toast that could intercept the subsequent navigateTo Taro.hideToast() if (!isLoggedIn && (path.includes('designList') || path.includes('orders'))) { Taro.showToast({ title: '请先登录', icon: 'none' }) setShowLogin(true) return } if (label === '联系客服') { Taro.navigateTo({ url: '/pages/service/index', fail: (err: any) => { console.error('navigateTo service fail', err) Taro.showToast({ title: '跳转失败', icon: 'none' }) } }) return } if (path.startsWith('/pages/')) { if (path.includes('designList') || path.includes('orders')) { Taro.switchTab({ url: path, fail: (err: any) => { console.error('switchTab fail', err) Taro.showToast({ title: '跳转失败', icon: 'none' }) } }) } else { Taro.navigateTo({ url: path, fail: (err: any) => { console.error('navigateTo fail', err) Taro.showToast({ title: '跳转失败', icon: 'none' }) } }) } } } return ( {/* 用户信息头部 */} {isLoggedIn && userInfo.avatarUrl ? ( ) : ( )} {isLoggedIn ? userInfo.nickName : '点击登录'} {isLoggedIn ? '智绘微刻会员' : '授权微信,开启专属定制'} {/* 5 状态快捷入口 */} {STATUS_MAP.slice(0, 2).map((s) => ( handleQuickClick(s.key)}> {orderStats[s.key as keyof typeof orderStats]} {s.label} ))} {STATUS_MAP.slice(2).map((s, idx) => ( handleQuickClick(s.key)}> {orderStats[s.key as keyof typeof orderStats]} {s.label} {idx < 2 && } ))} {/* 功能菜单 6宫格 */} 常用功能 {MENU_ITEMS.map((item, idx) => ( handleMenuClick(item.path, item.label)}> {item.label} ))} {/* 企业定制入口 */} 企业批量定制 年会礼品、入职纪念、团建伴手礼 立即咨询 {/* 登录弹窗 */} {showLogin && ( 登录智绘微刻 {loginStep === 'entry' ? ( <> 登录后同步您的设计清单和订单 取消 微信登录 ) : ( <> 请设置您的头像和昵称 setLoginNickName(e.detail.value)} /> 取消 保存 )} )} ) }