Files
dukang/apps/h5-user/src/pages/MinePage.tsx
T
jacy c43defca3f 用户端调整
2.首页商品详情增加点击区域
3.浓香型 酱香型 点击增加提示:暂未开放
5.首页餐券2字更换为“好客权益”
6.门店页面地址筛选功能 (三级菜单,省市区)
7.好客权益:去使用点击交互调整为核销页面,单张好客权益点击核销页面带参数和金额
7.核销金额限制为可用余额最大数,不是500
9.收货地址电话号码增加校验,长度限制,地址或者未填提示,地区调整为省市区三级菜单
10.微信授权位置调整在手机验证码下侧
2026-07-01 00:19:08 +08:00

214 lines
8.2 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import TabMainHeader from '../components/TabMainHeader';
import AppImage from '@dukang/shared-ui/AppImage';
import { clearAuth, request } from '../lib/api';
const DEFAULT_AVATAR =
'https://lh3.googleusercontent.com/aida-public/AB6AXuAz_9Pnpk_Md4sEU6PXkeybus8oLZO9e-3pOpLuSwBX0jm_Z0JCfX1w2oZxz1VZayTh0PKUPjwjSuxJVX410fjtWFGR_f55f-nWppXWUweHRnEC7WyIWEqx4AyVHt-k02OhyaSGQfvY5cHG5IuRe9EqdcHy47gBQ82_cxGgX-DrKV4oYcwLoNRynAV0_xv2p1GOhisnQVulHwZcQClUJcP8q4nTY0Y3DR1w4ioa0DYTHePE43mLDJptjZcQqS7V8LihJdn4ze6fvQA';
const ORDER_SHORTCUTS = [
{ tab: 'pending_pay', icon: 'payments', label: '待付款' },
{ tab: 'pending_ship', icon: 'package_2', label: '待发货' },
{ tab: 'pending_receive', icon: 'local_shipping', label: '配送中' },
{ tab: 'completed', icon: 'task_alt', label: '已完成' },
] as const;
const SERVICES = [
{ icon: 'location_on', label: '地址管理', to: '/addresses' },
{ icon: 'storefront', label: '可用门店', to: '/stores' },
{ icon: 'headset_mic', label: '联系客服', badge: '在线中', action: 'cs' as const },
{ icon: 'info', label: '关于我们', action: 'about' as const },
] as const;
function formatMoney(amount: number) {
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
export default function MinePage() {
const navigate = useNavigate();
const [profile, setProfile] = useState<Record<string, unknown> | null>(null);
const [benefitBalance, setBenefitBalance] = useState(0);
const [orderCounts, setOrderCounts] = useState<Record<string, number>>({});
const [toast, setToast] = useState('');
useEffect(() => {
Promise.all([
request<Record<string, unknown>>('USER_H5', '/auth/me'),
request<Array<Record<string, unknown>>>('USER_H5', '/benefit/coupons'),
...ORDER_SHORTCUTS.map((s) =>
request<{ total: number }>('USER_H5', `/trade/orders?tab=${s.tab}&pageSize=1`),
),
])
.then(([me, coupons, ...totals]) => {
setProfile(me);
const balance = coupons.reduce((sum, c) => {
if (String(c.status) === 'ACTIVE') return sum + Number(c.balance || 0);
return sum;
}, 0);
setBenefitBalance(balance);
const counts: Record<string, number> = {};
ORDER_SHORTCUTS.forEach((s, i) => {
counts[s.tab] = totals[i]?.total ?? 0;
});
setOrderCounts(counts);
})
.catch(() => {});
}, []);
function showToast(msg: string) {
setToast(msg);
window.setTimeout(() => setToast(''), 2200);
}
function handleService(item: (typeof SERVICES)[number]) {
if ('to' in item && item.to) return;
if (item.action === 'cs') showToast('preV1:在线客服即将开放');
if (item.action === 'about') showToast('杜康好客 · 传承千年酒文化');
}
function logout() {
clearAuth();
navigate('/login');
}
const nickname = String(profile?.nickname || '用户');
const userNo = String(profile?.userNo || '');
const avatar = String(profile?.avatarUrl || DEFAULT_AVATAR);
return (
<div className="mine-page">
<TabMainHeader title="我的" />
<header className="mine-header">
<div className="mine-header-texture" aria-hidden />
<div className="mine-profile">
<div className="mine-avatar-wrap">
<AppImage src={avatar} alt="" wrapperClassName="mine-avatar app-image--fill" />
<span className="mine-avatar-badge" aria-hidden>
<span className="material-symbols-outlined mine-fill-icon">verified</span>
</span>
</div>
<div className="mine-profile-info">
<h1 className="mine-profile-name">{nickname}</h1>
<div className="mine-profile-meta">
{userNo && <span className="mine-profile-id">ID: {userNo}</span>}
<span className="mine-member-tag">好客会员</span>
</div>
</div>
<button
type="button"
className="mine-settings-btn"
aria-label="设置"
onClick={() => showToast('preV1:账号设置即将开放')}
>
<span className="material-symbols-outlined">settings</span>
</button>
</div>
<div className="mine-header-glow" aria-hidden />
</header>
<main className="mine-main">
<section className="mine-card">
<div className="mine-card-head">
<h2 className="mine-card-title">
<span className="material-symbols-outlined mine-card-title-icon">account_balance_wallet</span>
我的资产
</h2>
<Link to="/benefit" className="mine-card-link">
查看明细
<span className="material-symbols-outlined">chevron_right</span>
</Link>
</div>
<div className="mine-asset-panel">
<div className="mine-asset-notch" aria-hidden />
<div className="mine-asset-notch-line" aria-hidden />
<div>
<p className="mine-asset-label">好客权益余额</p>
<div className="mine-asset-amount">
<span className="mine-asset-currency">¥</span>
<span className="mine-asset-value">{formatMoney(benefitBalance)}</span>
</div>
</div>
<Link to="/redeem" className="mine-asset-cta">
去使用
</Link>
</div>
</section>
<section className="mine-card">
<div className="mine-card-head mine-card-head-orders">
<h2 className="mine-card-title">我的订单</h2>
<Link to="/orders" className="mine-card-link">
全部订单
<span className="material-symbols-outlined">chevron_right</span>
</Link>
</div>
<div className="mine-order-grid">
{ORDER_SHORTCUTS.map((item) => {
const count = orderCounts[item.tab] ?? 0;
return (
<Link key={item.tab} to={`/orders?tab=${item.tab}`} className="mine-order-item">
<div className="mine-order-icon-wrap">
<span className="material-symbols-outlined mine-order-icon">{item.icon}</span>
{count > 0 && (
<span className="mine-order-badge">{count > 99 ? '99+' : count}</span>
)}
</div>
<span className="mine-order-label">{item.label}</span>
</Link>
);
})}
</div>
</section>
<section className="mine-card mine-services">
{SERVICES.map((item, index) => {
const inner = (
<>
<div className="mine-service-left">
<span className="mine-service-icon-wrap">
<span className="material-symbols-outlined">{item.icon}</span>
</span>
<span className="mine-service-label">{item.label}</span>
</div>
<div className="mine-service-right">
{'badge' in item && item.badge && (
<span className="mine-service-badge">{item.badge}</span>
)}
<span className="material-symbols-outlined mine-service-chevron">chevron_right</span>
</div>
</>
);
if ('to' in item && item.to) {
return (
<Link key={item.label} to={item.to} className="mine-service-item">
{inner}
</Link>
);
}
return (
<button
key={item.label}
type="button"
className="mine-service-item"
onClick={() => handleService(item)}
>
{inner}
</button>
);
})}
</section>
<div className="mine-footer">
<p className="mine-version">杜康好客 V2.4.0</p>
<button type="button" className="mine-logout" onClick={logout}>
退出当前账号
</button>
</div>
</main>
{toast && <div className="mine-toast">{toast}</div>}
</div>
);
}