feat(release): v3.4.13 experience optimizations across admin, mini-user, and H5 login
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,11 +13,16 @@ type Row = {
|
||||
settleAmount: number;
|
||||
channel?: RedeemChannel;
|
||||
createdAt: string;
|
||||
user?: { userNo: string; phone: string | null };
|
||||
user?: { userNo: string; phone: string | null; nickname?: string | null };
|
||||
store?: { name: string; cityName: string };
|
||||
coupon?: { couponNo: string };
|
||||
};
|
||||
|
||||
function maskPhone(phone: string | null | undefined) {
|
||||
if (!phone || phone.length < 7) return phone ?? '—';
|
||||
return `${phone.slice(0, 3)}****${phone.slice(-4)}`;
|
||||
}
|
||||
|
||||
export default function RedeemRecordsPage() {
|
||||
const [form] = Form.useForm();
|
||||
const [filters, setFilters] = useState<Record<string, string>>({});
|
||||
@@ -50,7 +55,19 @@ export default function RedeemRecordsPage() {
|
||||
);
|
||||
},
|
||||
},
|
||||
{ title: '用户', dataIndex: ['user', 'userNo'], width: 110 },
|
||||
{ title: '用户编号', dataIndex: ['user', 'userNo'], width: 110 },
|
||||
{
|
||||
title: '用户昵称',
|
||||
dataIndex: ['user', 'nickname'],
|
||||
width: 100,
|
||||
render: (v: string | null | undefined) => v || '—',
|
||||
},
|
||||
{
|
||||
title: '用户手机',
|
||||
dataIndex: ['user', 'phone'],
|
||||
width: 120,
|
||||
render: (v: string | null | undefined) => maskPhone(v),
|
||||
},
|
||||
{ title: '门店', dataIndex: ['store', 'name'] },
|
||||
{ title: '核销额', dataIndex: 'amount', width: 90, render: (v) => `¥${v}` },
|
||||
{ title: '结算额', dataIndex: 'settleAmount', width: 90, render: (v) => `¥${v}` },
|
||||
@@ -113,7 +130,7 @@ export default function RedeemRecordsPage() {
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={data?.items ?? []}
|
||||
scroll={{ x: 1100 }}
|
||||
scroll={{ x: 1300 }}
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize,
|
||||
@@ -138,6 +155,19 @@ export default function RedeemRecordsPage() {
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="核销额">¥{String(detail.amount)}</Descriptions.Item>
|
||||
<Descriptions.Item label="结算额">¥{String(detail.settleAmount)}</Descriptions.Item>
|
||||
{detail.user && typeof detail.user === 'object' ? (
|
||||
<>
|
||||
<Descriptions.Item label="用户编号">
|
||||
{String((detail.user as { userNo?: string }).userNo ?? '—')}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="用户昵称">
|
||||
{String((detail.user as { nickname?: string | null }).nickname ?? '—')}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="用户手机">
|
||||
{maskPhone((detail.user as { phone?: string | null }).phone)}
|
||||
</Descriptions.Item>
|
||||
</>
|
||||
) : null}
|
||||
<Descriptions.Item label="时间">{fmtTime(String(detail.createdAt))}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
)}
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
DEV_PLAN_TASK_TYPE_LABELS,
|
||||
SUPPORT_TICKET_STATUS_LABELS,
|
||||
SUPPORT_TICKET_TYPE_LABELS,
|
||||
SUPPORT_TICKET_PRIORITY_LABELS,
|
||||
mapSupportTicketTypeToDevPlanTask,
|
||||
type BatchReviewPreviewItem,
|
||||
type BatchReviewPreviewResponse,
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
type DevPlanVersionDto,
|
||||
type SupportTicketDto,
|
||||
type SupportTicketLinkedTaskDto,
|
||||
type SupportTicketPriorityDto,
|
||||
type SupportTicketStatusDto,
|
||||
type SupportTicketTypeDto,
|
||||
} from '@dukang/shared-types';
|
||||
@@ -64,6 +66,17 @@ const STATUS_OPTIONS = (Object.keys(SUPPORT_TICKET_STATUS_LABELS) as SupportTick
|
||||
(value) => ({ value, label: SUPPORT_TICKET_STATUS_LABELS[value] }),
|
||||
);
|
||||
|
||||
const PRIORITY_COLOR: Record<SupportTicketPriorityDto, string> = {
|
||||
LOW: 'default',
|
||||
NORMAL: 'blue',
|
||||
HIGH: 'orange',
|
||||
URGENT: 'red',
|
||||
};
|
||||
|
||||
const PRIORITY_OPTIONS = (Object.keys(SUPPORT_TICKET_PRIORITY_LABELS) as SupportTicketPriorityDto[]).map(
|
||||
(value) => ({ value, label: SUPPORT_TICKET_PRIORITY_LABELS[value] }),
|
||||
);
|
||||
|
||||
const TASK_TYPE_OPTIONS = (Object.keys(DEV_PLAN_TASK_TYPE_LABELS) as DevPlanTaskTypeDto[]).map(
|
||||
(v) => ({ value: v, label: DEV_PLAN_TASK_TYPE_LABELS[v] }),
|
||||
);
|
||||
@@ -80,6 +93,7 @@ export default function SupportTicketsPage() {
|
||||
const qs = new URLSearchParams();
|
||||
if (filters.ticketType) qs.set('ticketType', filters.ticketType);
|
||||
if (filters.status) qs.set('status', filters.status);
|
||||
if (filters.priority) qs.set('priority', filters.priority);
|
||||
return qs;
|
||||
},
|
||||
[filters],
|
||||
@@ -154,6 +168,7 @@ export default function SupportTicketsPage() {
|
||||
title: values.title.trim(),
|
||||
content: values.content?.trim() || undefined,
|
||||
remark: values.remark?.trim() || undefined,
|
||||
priority: values.priority,
|
||||
attachmentUrls: (values.attachmentUrls ?? []).map((u: string) => u?.trim()).filter(Boolean),
|
||||
}),
|
||||
});
|
||||
@@ -217,6 +232,7 @@ export default function SupportTicketsPage() {
|
||||
title: detail.title,
|
||||
content: detail.content || '',
|
||||
remark: detail.remark || '',
|
||||
priority: detail.priority ?? 'NORMAL',
|
||||
attachmentUrls: detail.attachmentUrls?.length ? detail.attachmentUrls : [''],
|
||||
});
|
||||
setEditOpen(true);
|
||||
@@ -233,6 +249,7 @@ export default function SupportTicketsPage() {
|
||||
title: values.title.trim(),
|
||||
content: values.content?.trim() || undefined,
|
||||
remark: values.remark?.trim() || undefined,
|
||||
priority: values.priority,
|
||||
attachmentUrls: (values.attachmentUrls ?? []).map((u: string) => u?.trim()).filter(Boolean),
|
||||
}),
|
||||
});
|
||||
@@ -451,6 +468,16 @@ export default function SupportTicketsPage() {
|
||||
width: 90,
|
||||
render: (t: SupportTicketTypeDto) => SUPPORT_TICKET_TYPE_LABELS[t] ?? t,
|
||||
},
|
||||
{
|
||||
title: '优先级',
|
||||
dataIndex: 'priority',
|
||||
width: 90,
|
||||
render: (p: SupportTicketPriorityDto) => (
|
||||
<Tag color={PRIORITY_COLOR[p] ?? 'default'}>
|
||||
{SUPPORT_TICKET_PRIORITY_LABELS[p] ?? p ?? '普通'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
@@ -562,6 +589,9 @@ export default function SupportTicketsPage() {
|
||||
<Form.Item name="status" label="状态">
|
||||
<Select allowClear style={{ width: 120 }} options={STATUS_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item name="priority" label="优先级">
|
||||
<Select allowClear style={{ width: 120 }} options={PRIORITY_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
筛选
|
||||
</Button>
|
||||
@@ -603,6 +633,9 @@ export default function SupportTicketsPage() {
|
||||
{detail.ticketNo}
|
||||
</Typography.Text>
|
||||
<Tag>{SUPPORT_TICKET_TYPE_LABELS[detail.ticketType] ?? detail.ticketType}</Tag>
|
||||
<Tag color={PRIORITY_COLOR[detail.priority ?? 'NORMAL']}>
|
||||
{SUPPORT_TICKET_PRIORITY_LABELS[detail.priority ?? 'NORMAL']}
|
||||
</Tag>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
@@ -861,10 +894,13 @@ export default function SupportTicketsPage() {
|
||||
</Drawer>
|
||||
|
||||
<Modal title="创建技术支持工单" open={createOpen} onCancel={() => setCreateOpen(false)} onOk={() => void submitCreate()} confirmLoading={creating} destroyOnClose okText="提交">
|
||||
<Form form={createForm} layout="vertical" initialValues={{ ticketType: 'BUG', attachmentUrls: [''] }}>
|
||||
<Form form={createForm} layout="vertical" initialValues={{ ticketType: 'BUG', priority: 'NORMAL', attachmentUrls: [''] }}>
|
||||
<Form.Item name="ticketType" label="类型" rules={[{ required: true }]}>
|
||||
<Select options={TYPE_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item name="priority" label="优先级" rules={[{ required: true }]}>
|
||||
<Select options={PRIORITY_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item name="title" label="标题" rules={[{ required: true }]}>
|
||||
<Input maxLength={128} showCount />
|
||||
</Form.Item>
|
||||
@@ -968,6 +1004,9 @@ export default function SupportTicketsPage() {
|
||||
<Form.Item name="ticketType" label="类型" rules={[{ required: true }]}>
|
||||
<Select options={TYPE_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item name="priority" label="优先级" rules={[{ required: true }]}>
|
||||
<Select options={PRIORITY_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item name="title" label="标题" rules={[{ required: true }]}>
|
||||
<Input maxLength={128} showCount />
|
||||
</Form.Item>
|
||||
|
||||
@@ -212,6 +212,14 @@ export default function PromoCodeDetailPage() {
|
||||
<Statistic title="扫码进入次数" value={stats?.scanCount ?? detail.scanCount} />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title="归因用户数"
|
||||
value={stats?.attributionCount ?? 0}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
|
||||
Reference in New Issue
Block a user