feat(weibo): 优化协作单详情并增加已上脚本状态
This commit is contained in:
@@ -4,6 +4,7 @@ export type WeiboCollabStatus =
|
||||
| 'incomplete'
|
||||
| 'to_recharge'
|
||||
| 'recharged'
|
||||
| 'scripted'
|
||||
| 'done'
|
||||
| 'weidou_error'
|
||||
| 'failed'
|
||||
@@ -119,6 +120,7 @@ export interface WeiboCollabOrder {
|
||||
exchange_verdict: WeiboCollabVerdict;
|
||||
exchange_message: string;
|
||||
recharged_at: string | null;
|
||||
scripted_at: string | null;
|
||||
done_at: string | null;
|
||||
void_reason: string;
|
||||
is_archived: boolean;
|
||||
|
||||
@@ -72,6 +72,7 @@ const STATUS_META: Record<WeiboCollabStatus, { label: string; color: string }> =
|
||||
incomplete: { label: '待补资料', color: 'default' },
|
||||
to_recharge: { label: '待充值', color: 'blue' },
|
||||
recharged: { label: '已充值', color: 'cyan' },
|
||||
scripted: { label: '已上脚本', color: 'purple' },
|
||||
done: { label: '兑换成功', color: 'success' },
|
||||
weidou_error: { label: '微豆异常', color: 'orange' },
|
||||
failed: { label: '兑换失败', color: 'error' },
|
||||
@@ -84,10 +85,17 @@ const ACCOUNT_CK_LABELS: Record<string, string> = {
|
||||
error: '查询失败',
|
||||
};
|
||||
|
||||
const ACCOUNT_CK_COLORS: Record<string, string> = {
|
||||
valid: 'success',
|
||||
invalid: 'error',
|
||||
error: 'warning',
|
||||
};
|
||||
|
||||
const STATUS_ORDER: WeiboCollabStatus[] = [
|
||||
'incomplete',
|
||||
'to_recharge',
|
||||
'recharged',
|
||||
'scripted',
|
||||
'done',
|
||||
'weidou_error',
|
||||
'failed',
|
||||
@@ -115,6 +123,117 @@ const VERDICT_META: Record<string, { label: string; color: string }> = {
|
||||
'': { label: '未判定', color: 'default' },
|
||||
};
|
||||
|
||||
const TIMELINE_ACTION_LABELS: Record<string, string> = {
|
||||
'weibo:collab:create': '新建协作单',
|
||||
'weibo:collab:update': '编辑资料',
|
||||
'weibo:collab:verify': '校验账号',
|
||||
'weibo:collab:status': '修改状态',
|
||||
'weibo:collab:status_auto': '自动流转',
|
||||
'weibo:collab:balance_check': '充值核账',
|
||||
'weibo:collab:exchange_check': '查询兑换记录',
|
||||
'weibo:collab:void': '作废订单',
|
||||
'weibo:collab:archive': '归档订单',
|
||||
'weibo:collab:unarchive': '取消归档',
|
||||
};
|
||||
|
||||
const TIMELINE_FIELD_LABELS: Record<string, string> = {
|
||||
from: '原状态',
|
||||
to: '新状态',
|
||||
reason: '原因',
|
||||
account_id: '账号 ID',
|
||||
uid: 'UID',
|
||||
role: '绑定角色',
|
||||
ck_status: 'CK 状态',
|
||||
balance: '余额',
|
||||
required_total: '需求合计',
|
||||
surplus: '结余',
|
||||
threshold: '阈值',
|
||||
goods_name: '商品',
|
||||
required_weidou: '所需微豆',
|
||||
shop: '店铺',
|
||||
draft: '草稿',
|
||||
order_no: '订单号',
|
||||
records_total: '兑换记录数',
|
||||
records_matched: '匹配数',
|
||||
verdict: '兑换结论',
|
||||
message: '说明',
|
||||
};
|
||||
|
||||
function statusLabel(value: unknown): string {
|
||||
const key = String(value || '');
|
||||
return STATUS_META[key as WeiboCollabStatus]?.label || key || '-';
|
||||
}
|
||||
|
||||
function metricSourceLabel(source: string): string {
|
||||
if (source === 'balance_check') return '充值核账';
|
||||
if (source === 'exchange_records') return '兑换记录';
|
||||
return source || '其他查询';
|
||||
}
|
||||
|
||||
function timelineActionLabel(action: string): string {
|
||||
return TIMELINE_ACTION_LABELS[action] || action || '其他操作';
|
||||
}
|
||||
|
||||
function timelineDetailText(action: string, detail: string): string {
|
||||
let data: Record<string, unknown>;
|
||||
try {
|
||||
const parsed = JSON.parse(detail) as unknown;
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
return detail || '-';
|
||||
}
|
||||
data = parsed as Record<string, unknown>;
|
||||
} catch {
|
||||
return detail || '-';
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
if (data.from !== undefined || data.to !== undefined) {
|
||||
parts.push(`${statusLabel(data.from)} → ${statusLabel(data.to)}`);
|
||||
}
|
||||
if (action === 'weibo:collab:balance_check') {
|
||||
parts.push(
|
||||
`余额 ${Number(data.balance ?? 0).toLocaleString()} / 需求 ${Number(data.required_total ?? 0).toLocaleString()} / 结余 ${Number(data.surplus ?? 0).toLocaleString()}`,
|
||||
);
|
||||
}
|
||||
if (action === 'weibo:collab:exchange_check') {
|
||||
parts.push(
|
||||
`匹配 ${data.records_matched ?? 0}/${data.records_total ?? 0} 条兑换记录`,
|
||||
);
|
||||
}
|
||||
const skipKeys = new Set([
|
||||
'from',
|
||||
'to',
|
||||
'balance',
|
||||
'required_total',
|
||||
'surplus',
|
||||
'records_matched',
|
||||
'records_total',
|
||||
'account_id',
|
||||
'order_ids',
|
||||
'group_key',
|
||||
]);
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
if (skipKeys.has(key) || value === null || value === undefined || value === '') {
|
||||
return;
|
||||
}
|
||||
const label = TIMELINE_FIELD_LABELS[key] || '详情';
|
||||
const rendered =
|
||||
typeof value === 'boolean'
|
||||
? value
|
||||
? '是'
|
||||
: '否'
|
||||
: typeof value === 'object'
|
||||
? value &&
|
||||
'old' in value &&
|
||||
'new' in value
|
||||
? `${String((value as { old?: unknown }).old ?? '-')} → ${String((value as { new?: unknown }).new ?? '-')}`
|
||||
: '已更新'
|
||||
: String(value);
|
||||
parts.push(`${label}:${rendered}`);
|
||||
});
|
||||
return parts.join(';') || '无附加信息';
|
||||
}
|
||||
|
||||
function balanceMeta(reason: string) {
|
||||
if (reason === 'ok') return { label: '余额正常', color: 'success' };
|
||||
if (reason === 'shortage') return { label: '微豆不足', color: 'error' };
|
||||
@@ -813,6 +932,36 @@ export default function WeiboCollabPage() {
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'CK 状态',
|
||||
dataIndex: 'account_ck_status',
|
||||
width: 140,
|
||||
render: (value: string, row) => {
|
||||
const label = ACCOUNT_CK_LABELS[value] || value || '未检测';
|
||||
return (
|
||||
<Space orientation="vertical" size={2} style={{ width: '100%' }}>
|
||||
<Tag color={ACCOUNT_CK_COLORS[value] || 'default'}>{label}</Tag>
|
||||
{row.account_ck_message && (
|
||||
<Tooltip title={row.account_ck_message}>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
display: 'block',
|
||||
maxWidth: 120,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{row.account_ck_message}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '商品 / 微豆',
|
||||
width: 190,
|
||||
@@ -1225,7 +1374,7 @@ export default function WeiboCollabPage() {
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
dataSource={orders}
|
||||
scroll={{ x: 1500 }}
|
||||
scroll={{ x: 1650 }}
|
||||
rowSelection={{
|
||||
selectedRowKeys: selectedIds,
|
||||
onChange: (keys) => setSelectedIds(keys.map(Number)),
|
||||
@@ -1439,19 +1588,99 @@ export default function WeiboCollabPage() {
|
||||
</Modal>
|
||||
|
||||
<Drawer
|
||||
title={detail ? `协作单详情 · ${detail.order_no}` : '协作单详情'}
|
||||
title={
|
||||
detail ? (
|
||||
<Space size={10} wrap>
|
||||
<span>协作单详情</span>
|
||||
<Text code copyable={{ text: detail.order_no }}>
|
||||
{detail.order_no}
|
||||
</Text>
|
||||
<Tag color={STATUS_META[detail.status]?.color}>
|
||||
{detail.status_label}
|
||||
</Tag>
|
||||
</Space>
|
||||
) : (
|
||||
'协作单详情'
|
||||
)
|
||||
}
|
||||
open={detailOpen}
|
||||
onClose={() => setDetailOpen(false)}
|
||||
size={880}
|
||||
size="min(1180px, 96vw)"
|
||||
loading={detailLoading}
|
||||
>
|
||||
{detail && (
|
||||
<Space direction="vertical" size={16} style={{ width: '100%' }}>
|
||||
<Row gutter={[12, 12]}>
|
||||
{[
|
||||
{
|
||||
title: '账号余额',
|
||||
value: detail.weidou_balance,
|
||||
suffix: '微豆',
|
||||
color: undefined,
|
||||
},
|
||||
{
|
||||
title: '礼包需求',
|
||||
value: detail.weidou_required_total ?? detail.required_weidou,
|
||||
suffix: '微豆',
|
||||
color: undefined,
|
||||
},
|
||||
{
|
||||
title: '当前结余',
|
||||
value: detail.weidou_surplus,
|
||||
suffix: '微豆',
|
||||
color:
|
||||
detail.weidou_surplus != null &&
|
||||
(detail.weidou_surplus < 0 ||
|
||||
(detail.weidou_threshold != null &&
|
||||
detail.weidou_surplus > detail.weidou_threshold))
|
||||
? token.colorError
|
||||
: '#389e0d',
|
||||
},
|
||||
{
|
||||
title: '允许结余',
|
||||
value: detail.weidou_threshold,
|
||||
suffix: '微豆',
|
||||
color: undefined,
|
||||
},
|
||||
].map((item) => (
|
||||
<Col xs={12} md={6} key={item.title}>
|
||||
<div
|
||||
style={{
|
||||
border: `1px solid ${token.colorBorderSecondary}`,
|
||||
borderRadius: 6,
|
||||
background: token.colorBgContainer,
|
||||
padding: '10px 12px',
|
||||
}}
|
||||
>
|
||||
<Statistic
|
||||
title={item.title}
|
||||
value={item.value ?? '-'}
|
||||
suffix={item.value == null ? undefined : item.suffix}
|
||||
valueStyle={{
|
||||
fontSize: 22,
|
||||
color: item.color,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Descriptions
|
||||
size="small"
|
||||
column={2}
|
||||
bordered
|
||||
column={3}
|
||||
items={[
|
||||
{ key: 'shop', label: '店铺 / 负责人', children: `${detail.shop || '-'} / ${detail.owner_name || '-'}` },
|
||||
{ key: 'shop', label: '店铺', children: detail.shop || '-' },
|
||||
{ key: 'owner', label: '负责人', children: detail.owner_name || '-' },
|
||||
{
|
||||
key: 'status',
|
||||
label: '业务状态',
|
||||
children: (
|
||||
<Tag color={STATUS_META[detail.status]?.color}>
|
||||
{detail.status_label}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'account',
|
||||
label: '微博账号',
|
||||
@@ -1487,21 +1716,84 @@ export default function WeiboCollabPage() {
|
||||
),
|
||||
},
|
||||
{ key: 'role', label: '绑定角色', children: detail.game_role_name || '-' },
|
||||
{ key: 'goods', label: '商品 / 所需微豆', children: `${detail.goods_name || '-'} / ${detail.required_weidou ?? '-'}` },
|
||||
{ key: 'status', label: '业务状态', children: <Tag color={STATUS_META[detail.status]?.color}>{detail.status_label}</Tag> },
|
||||
{ key: 'balance', label: '最近核账', children: detail.weidou_check_message || '-' },
|
||||
{ key: 'exchange', label: '兑换确认', children: detail.exchange_message || '-' },
|
||||
{ key: 'updated', label: '最后修改', children: `${detail.updated_by_name || '-'} · ${formatTime(detail.updated_at)}` },
|
||||
{
|
||||
key: 'ck',
|
||||
label: 'CK 状态',
|
||||
children:
|
||||
ACCOUNT_CK_LABELS[detail.account_ck_status] ||
|
||||
detail.account_ck_status ||
|
||||
'未检测',
|
||||
},
|
||||
{
|
||||
key: 'goods',
|
||||
label: '商品',
|
||||
children: detail.goods_name || '-',
|
||||
},
|
||||
{
|
||||
key: 'required',
|
||||
label: '所需微豆',
|
||||
children:
|
||||
detail.required_weidou?.toLocaleString() ?? '-',
|
||||
},
|
||||
{
|
||||
key: 'checked',
|
||||
label: '最近核账',
|
||||
children: formatTime(detail.weidou_checked_at),
|
||||
},
|
||||
{
|
||||
key: 'scripted',
|
||||
label: '上脚本时间',
|
||||
children: formatTime(detail.scripted_at),
|
||||
},
|
||||
{
|
||||
key: 'balance_message',
|
||||
label: '核账结论',
|
||||
span: 2,
|
||||
children: detail.weidou_check_message || '尚未核账',
|
||||
},
|
||||
{
|
||||
key: 'exchange',
|
||||
label: '兑换确认',
|
||||
span: 3,
|
||||
children: detail.exchange_message || '尚未查询兑换记录',
|
||||
},
|
||||
{
|
||||
key: 'created',
|
||||
label: '创建记录',
|
||||
children: `${detail.created_by_name || '-'} · ${formatTime(detail.created_at)}`,
|
||||
},
|
||||
{
|
||||
key: 'updated',
|
||||
label: '最后修改',
|
||||
span: 2,
|
||||
children: `${detail.updated_by_name || '-'} · ${formatTime(detail.updated_at)}`,
|
||||
},
|
||||
...(detail.remark
|
||||
? [
|
||||
{
|
||||
key: 'remark',
|
||||
label: '备注',
|
||||
span: 3,
|
||||
children: detail.remark,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
{detail.home_images.length > 0 && (
|
||||
<Space wrap>
|
||||
<Space wrap size={10}>
|
||||
{detail.home_images.map((key) => (
|
||||
<img
|
||||
key={key}
|
||||
src={`/api/files/object?key=${encodeURIComponent(key)}&variant=thumb`}
|
||||
alt="主页截图"
|
||||
style={{ width: 96, height: 96, objectFit: 'cover', borderRadius: 4 }}
|
||||
style={{
|
||||
width: 112,
|
||||
height: 112,
|
||||
objectFit: 'cover',
|
||||
borderRadius: 6,
|
||||
border: `1px solid ${token.colorBorderSecondary}`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Space>
|
||||
@@ -1526,23 +1818,70 @@ export default function WeiboCollabPage() {
|
||||
rowKey="id"
|
||||
size="small"
|
||||
pagination={false}
|
||||
scroll={{ x: 980 }}
|
||||
locale={{ emptyText: '暂无核账或兑换记录' }}
|
||||
dataSource={detailMetrics}
|
||||
columns={[
|
||||
{ title: '时间', dataIndex: 'created_at', width: 170, render: formatTime },
|
||||
{ title: '来源', dataIndex: 'source', width: 130 },
|
||||
{
|
||||
title: '核账',
|
||||
width: 220,
|
||||
render: (_, row) =>
|
||||
`余额 ${row.weidou_balance ?? '-'} / 合计 ${row.weidou_required_total ?? '-'} / 结余 ${row.weidou_surplus ?? '-'}`,
|
||||
title: '查询时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 170,
|
||||
render: formatTime,
|
||||
},
|
||||
{
|
||||
title: '兑换',
|
||||
width: 220,
|
||||
render: (_, row) =>
|
||||
`${row.verdict_label || '未判定'} · ${row.records_matched}/${row.records_total}`,
|
||||
title: '查询类型',
|
||||
dataIndex: 'source',
|
||||
width: 110,
|
||||
render: (value: string) => metricSourceLabel(value),
|
||||
},
|
||||
{
|
||||
title: '账号余额',
|
||||
dataIndex: 'weidou_balance',
|
||||
width: 110,
|
||||
align: 'right',
|
||||
render: (value: number | null) =>
|
||||
value?.toLocaleString() ?? '-',
|
||||
},
|
||||
{
|
||||
title: '需求合计',
|
||||
dataIndex: 'weidou_required_total',
|
||||
width: 110,
|
||||
align: 'right',
|
||||
render: (value: number | null) =>
|
||||
value?.toLocaleString() ?? '-',
|
||||
},
|
||||
{
|
||||
title: '结余',
|
||||
dataIndex: 'weidou_surplus',
|
||||
width: 110,
|
||||
align: 'right',
|
||||
render: (value: number | null) => (
|
||||
<Text type={value != null && value < 0 ? 'danger' : undefined}>
|
||||
{value?.toLocaleString() ?? '-'}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '兑换匹配',
|
||||
width: 120,
|
||||
render: (_, row) => (
|
||||
<Space size={4}>
|
||||
<Tag color={VERDICT_META[row.verdict]?.color}>
|
||||
{row.verdict_label ||
|
||||
VERDICT_META[row.verdict]?.label ||
|
||||
'未判定'}
|
||||
</Tag>
|
||||
<Text type="secondary">
|
||||
{row.records_matched}/{row.records_total}
|
||||
</Text>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '结论',
|
||||
dataIndex: 'message',
|
||||
render: (value: string) => value || '-',
|
||||
},
|
||||
{ title: '结论', dataIndex: 'message' },
|
||||
]}
|
||||
/>
|
||||
<Title level={5}>操作时间线</Title>
|
||||
@@ -1550,12 +1889,38 @@ export default function WeiboCollabPage() {
|
||||
rowKey="id"
|
||||
size="small"
|
||||
pagination={false}
|
||||
scroll={{ x: 980 }}
|
||||
locale={{ emptyText: '暂无操作记录' }}
|
||||
dataSource={detailTimeline}
|
||||
columns={[
|
||||
{ title: '时间', dataIndex: 'created_at', width: 170, render: formatTime },
|
||||
{ title: '操作人', dataIndex: 'username', width: 120 },
|
||||
{ title: '动作', dataIndex: 'action', width: 190 },
|
||||
{ title: '详情', dataIndex: 'detail' },
|
||||
{
|
||||
title: '操作时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 170,
|
||||
render: formatTime,
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'username',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '动作',
|
||||
dataIndex: 'action',
|
||||
width: 140,
|
||||
render: (value: string) => (
|
||||
<Tag color="blue">{timelineActionLabel(value)}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '详情',
|
||||
dataIndex: 'detail',
|
||||
render: (value: string, row) => (
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
{timelineDetailText(row.action, value)}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
|
||||
Reference in New Issue
Block a user