import { useCallback, useEffect, useState } from 'react'; import { View, Text, Input, Switch, Image } from '@tarojs/components'; import '../../styles/invoice.css'; import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro'; import PageShell from '../../components/PageShell'; import SubPageHeader from '../../components/SubPageHeader'; import { request, toast } from '../../lib/api'; import { usePageView } from '../../lib/usePageView'; import { INVOICE_TITLE_TYPE_LABELS, type InvoiceTitleType, type UpsertInvoiceTitleRequest, type UserInvoiceTitleDto, } from '@dukang/shared-types'; import iconEdit from '../../assets/icons/编辑.png'; import iconDefault from '../../assets/icons/默认.png'; import iconDelete from '../../assets/icons/删除.png'; const isH5 = process.env.TARO_ENV === 'h5'; const ACTION_ICONS = { edit: iconEdit, star: iconDefault, trash: iconDelete, } as const; type EditDraft = UpsertInvoiceTitleRequest & { id?: string }; function TitleActionIcon({ kind, label, onClick, }: { kind: keyof typeof ACTION_ICONS; label: string; onClick: () => void; }) { return ( ); } const EMPTY_DRAFT: EditDraft = { titleType: 'PERSONAL', titleName: '', taxNo: '', email: '', phone: '', addressPhone: '', bankAccount: '', isDefault: false, }; export default function InvoiceTitlesPage() { usePageView('user_invoice_titles_view'); const [titles, setTitles] = useState([]); const [loading, setLoading] = useState(true); const [sheetOpen, setSheetOpen] = useState(false); const [draft, setDraft] = useState(EMPTY_DRAFT); const [saving, setSaving] = useState(false); const load = useCallback(() => { setLoading(true); return request('/trade/invoice-titles') .then((data) => setTitles(Array.isArray(data) ? data : [])) .catch((e) => { toast(e instanceof Error ? e.message : '加载失败'); setTitles([]); }) .finally(() => setLoading(false)); }, []); useEffect(() => { void load(); }, [load]); useDidShow(() => { void load(); }); usePullDownRefresh(() => { void load().finally(() => Taro.stopPullDownRefresh()); }); function openCreate() { setDraft({ ...EMPTY_DRAFT }); setSheetOpen(true); } function openEdit(t: UserInvoiceTitleDto) { setDraft({ id: t.id, titleType: t.titleType, titleName: t.titleName, taxNo: t.taxNo ?? '', email: t.email ?? '', phone: t.phone ?? '', addressPhone: t.addressPhone ?? '', bankAccount: t.bankAccount ?? '', isDefault: t.isDefault, }); setSheetOpen(true); } async function save() { if (saving) return; const titleName = draft.titleName.trim(); if (!titleName) { toast('请填写发票抬头名称'); return; } if (draft.titleType === 'ENTERPRISE' && !draft.taxNo?.trim()) { toast('企业抬头须填写税号'); return; } setSaving(true); try { const payload: UpsertInvoiceTitleRequest = { titleType: draft.titleType, titleName, taxNo: draft.taxNo?.trim() || null, email: draft.email?.trim() || null, phone: draft.phone?.trim() || null, addressPhone: draft.addressPhone?.trim() || null, bankAccount: draft.bankAccount?.trim() || null, isDefault: !!draft.isDefault, }; if (draft.id) { await request(`/trade/invoice-titles/${draft.id}`, { method: 'PUT', data: payload, }); toast('已更新', 'success'); } else { await request('/trade/invoice-titles', { method: 'POST', data: payload }); toast('已添加', 'success'); } setSheetOpen(false); await load(); } catch (e) { toast(e instanceof Error ? e.message : '保存失败'); } finally { setSaving(false); } } async function setDefault(t: UserInvoiceTitleDto) { try { await request(`/trade/invoice-titles/${t.id}`, { method: 'PUT', data: { titleType: t.titleType, titleName: t.titleName, taxNo: t.taxNo, email: t.email, phone: t.phone, addressPhone: t.addressPhone, bankAccount: t.bankAccount, isDefault: true, }, }); toast('已设为默认', 'success'); await load(); } catch (e) { toast(e instanceof Error ? e.message : '设置失败'); } } async function remove(id: string) { const ok = await Taro.showModal({ title: '提示', content: '确认删除该发票抬头?' }); if (!ok.confirm) return; try { await request(`/trade/invoice-titles/${id}`, { method: 'DELETE' }); toast('已删除', 'success'); await load(); } catch (e) { toast(e instanceof Error ? e.message : '删除失败'); } } return ( {isH5 ? ( Taro.navigateBack().catch(() => Taro.switchTab({ url: '/pages/mine/index' }))} /> ) : null} {loading ? 加载中… : null} {!loading && titles.length === 0 ? ( 暂无发票抬头,点击右上角添加 ) : null} {!loading && titles.map((t) => ( {t.titleName} {INVOICE_TITLE_TYPE_LABELS[t.titleType] ?? t.titleType} {t.isDefault ? 默认 : null} {t.taxNo ? 税号:{t.taxNo} : null} {t.email ? 邮箱:{t.email} : null} {t.phone ? 电话:{t.phone} : null} {t.addressPhone ? ( 地址电话:{t.addressPhone} ) : null} {t.bankAccount ? ( 开户行账号:{t.bankAccount} ) : null} openEdit(t)} /> {!t.isDefault ? ( void setDefault(t)} /> ) : null} void remove(t.id)} /> ))} + 添加抬头 {sheetOpen ? ( !saving && setSheetOpen(false)} /> {draft.id ? '编辑发票抬头' : '添加发票抬头'} 抬头类型 {(['PERSONAL', 'ENTERPRISE'] as InvoiceTitleType[]).map((tp) => ( setDraft({ ...draft, titleType: tp })} > {INVOICE_TITLE_TYPE_LABELS[tp]} ))} 抬头名称 setDraft({ ...draft, titleName: e.detail.value })} /> {draft.titleType === 'ENTERPRISE' ? ( 税号 setDraft({ ...draft, taxNo: e.detail.value })} /> ) : null} 接收邮箱 setDraft({ ...draft, email: e.detail.value })} /> 联系电话 setDraft({ ...draft, phone: e.detail.value })} /> {draft.titleType === 'ENTERPRISE' ? ( <> 地址电话 setDraft({ ...draft, addressPhone: e.detail.value })} /> 开户行账号 setDraft({ ...draft, bankAccount: e.detail.value })} /> ) : null} 设为默认抬头 setDraft({ ...draft, isDefault: !!e.detail.value })} /> !saving && setSheetOpen(false)} > 取消 { if (!saving) void save(); }} > {saving ? '保存中…' : '保存'} ) : null} ); }