+
navigate(-1)} />
- {orderNo && (
-
- )}
+
+
杜康好客客服
+
点击下方按钮,在微信内进入在线客服会话
-
- {messages.map((m) => {
- if (m.role === 'system') {
- return (
-
- {m.text}
-
- );
- }
- const isUser = m.role === 'user';
- return (
-
- {!isUser && (
-
- support_agent
-
- )}
-
{m.text}
-
- );
- })}
-
-
-
- {QUICK_QUESTIONS.map((q) => (
-
- ))}
-
-
-
+
+
+ call
+ 或拨打客服电话 {CUSTOMER_SERVICE_PHONE}
+
+
);
}
diff --git a/apps/h5-user/src/pages/InvoiceApplyPage.tsx b/apps/h5-user/src/pages/InvoiceApplyPage.tsx
new file mode 100644
index 0000000..9239bca
--- /dev/null
+++ b/apps/h5-user/src/pages/InvoiceApplyPage.tsx
@@ -0,0 +1,158 @@
+import { useEffect, useState } from 'react';
+import { useNavigate, useSearchParams } from 'react-router-dom';
+import {
+ INVOICE_KIND_LABELS,
+ INVOICE_TITLE_TYPE_LABELS,
+ type InvoiceKind,
+ type InvoiceTitleType,
+} from '@dukang/shared-types';
+import SubPageHeader from '../components/SubPageHeader';
+import { request } from '../lib/api';
+
+type OrderRow = { id: string; orderNo: string; payAmount: number | string; productName?: string };
+
+export default function InvoiceApplyPage() {
+ const navigate = useNavigate();
+ const [params] = useSearchParams();
+ const presetOrderId = params.get('orderId') || '';
+
+ const [orders, setOrders] = useState
([]);
+ const [orderId, setOrderId] = useState(presetOrderId);
+ const [titleType, setTitleType] = useState('PERSONAL');
+ const [invoiceKind, setInvoiceKind] = useState('NORMAL');
+ const [titleName, setTitleName] = useState('');
+ const [taxNo, setTaxNo] = useState('');
+ const [addressPhone, setAddressPhone] = useState('');
+ const [bankAccount, setBankAccount] = useState('');
+ const [email, setEmail] = useState('');
+ const [phone, setPhone] = useState('');
+ const [submitting, setSubmitting] = useState(false);
+
+ useEffect(() => {
+ request<{ list?: OrderRow[] }>('USER_H5', '/trade/orders?tab=completed&pageSize=50')
+ .then((res) => setOrders(res.list ?? []))
+ .catch(() => setOrders([]));
+ }, []);
+
+ useEffect(() => {
+ if (invoiceKind === 'SPECIAL') setTitleType('ENTERPRISE');
+ }, [invoiceKind]);
+
+ async function submit() {
+ if (!orderId) {
+ window.alert('请选择订单');
+ return;
+ }
+ if (!titleName.trim() || !email.trim() || !phone.trim()) {
+ window.alert('请填写抬头名称、邮箱与手机号');
+ return;
+ }
+ setSubmitting(true);
+ try {
+ await request('USER_H5', `/trade/orders/${orderId}/invoices`, {
+ method: 'POST',
+ body: JSON.stringify({
+ titleType,
+ invoiceKind,
+ titleName: titleName.trim(),
+ taxNo: taxNo.trim() || undefined,
+ addressPhone: addressPhone.trim() || undefined,
+ bankAccount: bankAccount.trim() || undefined,
+ email: email.trim(),
+ phone: phone.trim(),
+ }),
+ });
+ window.alert('发票申请已提交,总部将在 2 个工作日内开具');
+ navigate('/invoices');
+ } catch (e) {
+ window.alert(e instanceof Error ? e.message : '申请失败');
+ } finally {
+ setSubmitting(false);
+ }
+ }
+
+ return (
+
+ );
+}
diff --git a/apps/h5-user/src/pages/InvoiceListPage.tsx b/apps/h5-user/src/pages/InvoiceListPage.tsx
new file mode 100644
index 0000000..9e3e99b
--- /dev/null
+++ b/apps/h5-user/src/pages/InvoiceListPage.tsx
@@ -0,0 +1,68 @@
+import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import {
+ INVOICE_KIND_LABELS,
+ INVOICE_STATUS_LABELS,
+ INVOICE_TITLE_TYPE_LABELS,
+ type InvoiceDto,
+} from '@dukang/shared-types';
+import SubPageHeader from '../components/SubPageHeader';
+import { request } from '../lib/api';
+
+export default function InvoiceListPage() {
+ const navigate = useNavigate();
+ const [items, setItems] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ request<{ items: InvoiceDto[] }>('USER_H5', '/trade/invoices?pageSize=50')
+ .then((res) => setItems(res.items ?? []))
+ .catch(() => setItems([]))
+ .finally(() => setLoading(false));
+ }, []);
+
+ return (
+
+
navigate(-1)} />
+
+ {loading ? (
+ 加载中…
+ ) : items.length === 0 ? (
+
+
暂无发票申请
+
+
+ ) : (
+
+ {items.map((inv) => (
+
+
+
+ {INVOICE_TITLE_TYPE_LABELS[inv.titleType]} · {INVOICE_KIND_LABELS[inv.invoiceKind]}
+
+
+ {INVOICE_STATUS_LABELS[inv.status]}
+
+
+
{inv.titleName}
+
+ {inv.invoiceNo} · 订单 {inv.orderNo ?? inv.orderId}
+
+ {inv.status === 'ISSUED' && inv.fileUrl ? (
+
+ 查看/下载发票
+
+ ) : null}
+
+ ))}
+
+
+ )}
+
+
+ );
+}
diff --git a/apps/h5-user/src/pages/MinePage.tsx b/apps/h5-user/src/pages/MinePage.tsx
index 129831c..0e47b22 100644
--- a/apps/h5-user/src/pages/MinePage.tsx
+++ b/apps/h5-user/src/pages/MinePage.tsx
@@ -17,6 +17,8 @@ const ORDER_SHORTCUTS = [
const SERVICES = [
{ icon: 'location_on', label: '地址管理', to: '/addresses' },
+ { icon: 'assignment_return', label: '售后工单', to: '/after-sale/list' },
+ { icon: 'receipt_long', label: '我的发票', to: '/invoices' },
{ icon: 'storefront', label: '可用门店', to: '/stores' },
{ icon: 'headset_mic', label: '联系客服', badge: '在线中', action: 'cs' as const },
{ icon: 'info', label: '关于我们', action: 'about' as const },
diff --git a/apps/h5-user/src/pages/OrderDetailPage.tsx b/apps/h5-user/src/pages/OrderDetailPage.tsx
index 53304d4..3b3cc5f 100644
--- a/apps/h5-user/src/pages/OrderDetailPage.tsx
+++ b/apps/h5-user/src/pages/OrderDetailPage.tsx
@@ -178,6 +178,7 @@ export default function OrderDetailPage() {
order &&
!canPay &&
['PENDING_SHIP', 'PENDING_RECEIVE', 'COMPLETED', 'OUT_WAREHOUSE', 'SHIPPING'].includes(order.status);
+ const canInvoice = order?.status === 'COMPLETED';
const productTotal = Number(order?.productAmount ?? order?.payAmount ?? 0);
const freightTotal = Number(order?.freightAmount ?? 0);
@@ -207,22 +208,6 @@ export default function OrderDetailPage() {
}
}
- async function requestRefund() {
- if (!id || !canRefund) return;
- setConfirming(true);
- try {
- await request('USER_H5', `/trade/orders/${id}/refund-requests`, {
- method: 'POST',
- body: JSON.stringify({ remark: '用户申请退款' }),
- });
- await loadOrder();
- } catch (e) {
- window.alert(e instanceof Error ? e.message : '申请退款失败');
- } finally {
- setConfirming(false);
- }
- }
-
if (!order) return 加载中...
;
return (
@@ -470,8 +455,21 @@ export default function OrderDetailPage() {
联系客服
{canRefund && order?.status !== 'REFUNDING' && order?.status !== 'REFUNDED' && (
-