feat: 技术支持工单/企微权限/开发版本管理/消息推送等迭代
This commit is contained in:
@@ -1,310 +0,0 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { PartnerBillDto } from '@dukang/shared-types';
|
||||
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
|
||||
import PullToRefresh from '@dukang/shared-ui/PullToRefresh';
|
||||
import { request } from '../lib/api';
|
||||
import { usePartnerSession } from '../contexts/PartnerSessionContext';
|
||||
import { contactSupport } from '../lib/contact';
|
||||
import { isPrimaryAccount } from '../lib/partnerAccess';
|
||||
import { listPartnerStaff } from '../lib/staff';
|
||||
import { toastError, toastSuccess } from '../lib/toast';
|
||||
import {
|
||||
authorizePartnerWechat,
|
||||
fetchClientConfig,
|
||||
handlePartnerWechatLoginResult,
|
||||
} from '../lib/wechat-auth';
|
||||
import { isWechatEnv } from '../lib/weixin';
|
||||
|
||||
type CenterPageProps = {
|
||||
/** 子账号个人中心复用 */
|
||||
variant?: 'primary' | 'sub';
|
||||
roleLabel?: string;
|
||||
};
|
||||
|
||||
function fmtMoney(n: number) {
|
||||
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
export default function CenterPage({ variant = 'primary', roleLabel }: CenterPageProps) {
|
||||
const { account, logout, refresh, applySession } = usePartnerSession();
|
||||
const isPrimary = variant === 'primary' && isPrimaryAccount(account);
|
||||
const [authorizing, setAuthorizing] = useState(false);
|
||||
const [bills, setBills] = useState<PartnerBillDto[]>([]);
|
||||
const [staffCount, setStaffCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = isPrimary ? '合伙人中心' : '个人中心';
|
||||
}, [isPrimary]);
|
||||
|
||||
const loadCenter = useCallback(() => {
|
||||
const tasks: Promise<unknown>[] = [Promise.resolve(refresh())];
|
||||
if (isPrimary) {
|
||||
tasks.push(
|
||||
request<PartnerBillDto[]>('PARTNER_H5', '/partner/settlement/bills')
|
||||
.then(setBills)
|
||||
.catch(() => setBills([])),
|
||||
listPartnerStaff()
|
||||
.then((list) => setStaffCount(list.length))
|
||||
.catch(() => setStaffCount(0)),
|
||||
);
|
||||
}
|
||||
return Promise.all(tasks).then(() => undefined);
|
||||
}, [isPrimary, refresh]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadCenter();
|
||||
}, [loadCenter]);
|
||||
|
||||
const finance = useMemo(() => {
|
||||
const pending = bills.filter((b) => b.status === 'AWAITING_CONFIRM');
|
||||
const settled = bills.filter((b) => b.status === 'PAID');
|
||||
const paidCount = settled.length;
|
||||
const rejectedCount = bills.filter((b) => b.status === 'REJECTED').length;
|
||||
return {
|
||||
pendingBillCount: pending.length,
|
||||
rejectedCount,
|
||||
pendingTotal: bills
|
||||
.filter((b) => b.status === 'AWAITING_CONFIRM' || b.status === 'UNPAID' || b.status === 'REJECTED')
|
||||
.reduce((s, b) => s + Number(b.totalAmount || 0), 0),
|
||||
settledTotal: settled.reduce((s, b) => s + Number(b.totalAmount || 0), 0),
|
||||
balance: settled.reduce((s, b) => s + Number(b.totalAmount || 0), 0),
|
||||
paidCount,
|
||||
};
|
||||
}, [bills]);
|
||||
|
||||
function handleContactPartner() {
|
||||
if (isPrimary) {
|
||||
if (!contactSupport()) toastError('暂无客服电话');
|
||||
return;
|
||||
}
|
||||
if (!account?.primaryPhone) {
|
||||
toastError('暂无合伙人联系方式');
|
||||
return;
|
||||
}
|
||||
window.location.href = `tel:${account.primaryPhone}`;
|
||||
}
|
||||
|
||||
async function handleAvatarAuth() {
|
||||
if (authorizing) return;
|
||||
if (!isWechatEnv()) {
|
||||
toastError('请在微信内打开以授权头像昵称');
|
||||
return;
|
||||
}
|
||||
setAuthorizing(true);
|
||||
try {
|
||||
const config = await fetchClientConfig();
|
||||
if (!isWxAuthorizeEnabled(config)) {
|
||||
toastError('微信授权暂未开启');
|
||||
return;
|
||||
}
|
||||
const result = await authorizePartnerWechat();
|
||||
if (result) {
|
||||
const session = handlePartnerWechatLoginResult(result);
|
||||
if (session) applySession(session);
|
||||
await refresh();
|
||||
toastSuccess('微信授权成功');
|
||||
}
|
||||
// result 为空表示已跳转 OAuth,无需额外处理
|
||||
} catch (e) {
|
||||
toastError(e instanceof Error ? e.message : '微信授权失败');
|
||||
} finally {
|
||||
setAuthorizing(false);
|
||||
}
|
||||
}
|
||||
|
||||
const locationLabel = [account?.companyName].filter(Boolean).join(' · ') || '—';
|
||||
const displayName = account?.wxNickname?.trim() || account?.name || '合伙人';
|
||||
const avatarUrl = account?.wxAvatarUrl?.trim() || '';
|
||||
const badgeLabel = roleLabel || (isPrimary ? '城市合伙人' : '拓店员');
|
||||
|
||||
return (
|
||||
<PullToRefresh onRefresh={loadCenter} className="page partner-center-page partner-home--flush-top">
|
||||
<section className="partner-profile-card">
|
||||
<button
|
||||
type="button"
|
||||
className="partner-profile-avatar"
|
||||
onClick={() => void handleAvatarAuth()}
|
||||
disabled={authorizing}
|
||||
aria-label={avatarUrl ? '更新微信头像' : '微信授权获取头像'}
|
||||
>
|
||||
{avatarUrl ? (
|
||||
<img src={avatarUrl} alt="" className="partner-profile-avatar-img" referrerPolicy="no-referrer" />
|
||||
) : (
|
||||
<span className="material-symbols-outlined">person</span>
|
||||
)}
|
||||
{!avatarUrl && (
|
||||
<span className="partner-profile-avatar-hint">授权</span>
|
||||
)}
|
||||
</button>
|
||||
<div className="partner-profile-main">
|
||||
<div className="partner-profile-title-row">
|
||||
<h2 className="partner-profile-name">{displayName}</h2>
|
||||
<span className="partner-role-badge">{badgeLabel}</span>
|
||||
</div>
|
||||
<div className="partner-profile-location">
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>location_on</span>
|
||||
<span className="body-md line-2-clamp">{locationLabel}</span>
|
||||
</div>
|
||||
{!isPrimary && (
|
||||
<p className="label-md text-muted" style={{ marginTop: 4 }}>{account?.phone || ''}</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{isPrimary && (
|
||||
<>
|
||||
<section className="partner-assets-section">
|
||||
<div className="partner-section-head">
|
||||
<h3 className="headline-md">资产概览</h3>
|
||||
<Link to="/center/settlement" className="label-md text-primary" style={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
明细 <span className="material-symbols-outlined" style={{ fontSize: 14 }}>chevron_right</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{finance.pendingBillCount > 0 && (
|
||||
<Link to="/center/bills" className="partner-bills-banner">
|
||||
<div className="partner-bills-banner-left">
|
||||
<div className="partner-bills-icon">
|
||||
<span className="material-symbols-outlined">receipt_long</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="body-md text-primary" style={{ fontWeight: 600 }}>
|
||||
{finance.rejectedCount > 0 ? '待处理账单' : '待确认账单'}
|
||||
</p>
|
||||
<p className="label-md text-primary" style={{ opacity: 0.85 }}>
|
||||
{finance.rejectedCount > 0
|
||||
? `您有 ${finance.pendingBillCount} 笔账单待处理(含 ${finance.rejectedCount} 笔已驳回)`
|
||||
: `您有 ${finance.pendingBillCount} 笔账单待确认`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-primary">chevron_right</span>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<div className="partner-finance-grid">
|
||||
<div className="partner-finance-card partner-finance-card--hero">
|
||||
<p className="partner-finance-hero-label">账户余额 (元)</p>
|
||||
<p className="partner-finance-hero-amount">{fmtMoney(finance.balance)}</p>
|
||||
</div>
|
||||
<Link to="/center/settlement" className="partner-finance-card">
|
||||
<p className="label-md text-muted">待结算</p>
|
||||
<p className="partner-finance-value text-primary">¥ {fmtMoney(finance.pendingTotal)}</p>
|
||||
</Link>
|
||||
<Link to="/center/settlement" className="partner-finance-card">
|
||||
<p className="label-md text-muted" style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
提现记录
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 14 }}>history</span>
|
||||
</p>
|
||||
<p className="partner-finance-value">{finance.paidCount} 笔</p>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="partner-menu-section">
|
||||
<div className="partner-section-head">
|
||||
<h3 className="headline-md">运营管理</h3>
|
||||
</div>
|
||||
<div className="partner-menu-card">
|
||||
<Link to="/center/staff" className="partner-menu-item">
|
||||
<div className="partner-menu-item-left">
|
||||
<div className="partner-menu-icon">
|
||||
<span className="material-symbols-outlined">badge</span>
|
||||
</div>
|
||||
<span className="body-md" style={{ fontSize: 16 }}>我的员工</span>
|
||||
</div>
|
||||
<div className="partner-menu-item-right">
|
||||
{staffCount > 0 && <span className="partner-menu-badge">{staffCount}人</span>}
|
||||
<span className="material-symbols-outlined text-muted">chevron_right</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Link to="/center/proxy-orders" className="partner-menu-item">
|
||||
<div className="partner-menu-item-left">
|
||||
<div className="partner-menu-icon">
|
||||
<span className="material-symbols-outlined">receipt_long</span>
|
||||
</div>
|
||||
<span className="body-md" style={{ fontSize: 16 }}>代下单列表</span>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-muted">chevron_right</span>
|
||||
</Link>
|
||||
<Link to="/stores?audit=pending" className="partner-menu-item">
|
||||
<div className="partner-menu-item-left">
|
||||
<div className="partner-menu-icon">
|
||||
<span className="material-symbols-outlined">storefront</span>
|
||||
</div>
|
||||
<span className="body-md" style={{ fontSize: 16 }}>门店审核记录</span>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-muted">chevron_right</span>
|
||||
</Link>
|
||||
<Link to="/stores" className="partner-menu-item">
|
||||
<div className="partner-menu-item-left">
|
||||
<div className="partner-menu-icon">
|
||||
<span className="material-symbols-outlined">description</span>
|
||||
</div>
|
||||
<span className="body-md" style={{ fontSize: 16 }}>合同管理</span>
|
||||
</div>
|
||||
<div className="partner-menu-item-right">
|
||||
<span className="partner-menu-status">待续约</span>
|
||||
<span className="material-symbols-outlined text-muted">chevron_right</span>
|
||||
</div>
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
className="partner-menu-item"
|
||||
onClick={() => { if (!contactSupport()) toastError('暂无客服电话'); }}
|
||||
>
|
||||
<div className="partner-menu-item-left">
|
||||
<div className="partner-menu-icon">
|
||||
<span className="material-symbols-outlined">support_agent</span>
|
||||
</div>
|
||||
<span className="body-md" style={{ fontSize: 16 }}>联系客服</span>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-muted">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isPrimary && (
|
||||
<section className="partner-menu-section">
|
||||
<div className="partner-menu-card">
|
||||
<Link to="/leaderboard" className="partner-menu-item">
|
||||
<div className="partner-menu-item-left">
|
||||
<div className="partner-menu-icon">
|
||||
<span className="material-symbols-outlined">emoji_events</span>
|
||||
</div>
|
||||
<span className="body-md" style={{ fontSize: 16 }}>团队贡献榜</span>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-muted">chevron_right</span>
|
||||
</Link>
|
||||
<button type="button" className="partner-menu-item" onClick={() => { if (!contactSupport()) toastError('暂无客服电话'); }}>
|
||||
<div className="partner-menu-item-left">
|
||||
<div className="partner-menu-icon"><span className="material-symbols-outlined">support_agent</span></div>
|
||||
<span className="body-md" style={{ fontSize: 16 }}>联系客服</span>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-muted">chevron_right</span>
|
||||
</button>
|
||||
<button type="button" className="partner-menu-item" onClick={handleContactPartner}>
|
||||
<div className="partner-menu-item-left">
|
||||
<div className="partner-menu-icon"><span className="material-symbols-outlined">call</span></div>
|
||||
<span className="body-md" style={{ fontSize: 16 }}>联系合伙人</span>
|
||||
</div>
|
||||
<span className="material-symbols-outlined text-muted">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<button type="button" className="partner-logout-btn" onClick={logout}>
|
||||
<span className="material-symbols-outlined">logout</span>
|
||||
退出登录
|
||||
</button>
|
||||
|
||||
<div className="partner-center-footer">
|
||||
<p className="label-md text-muted">传承千年 · 杜康好客</p>
|
||||
</div>
|
||||
</PullToRefresh>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user