feat(weibo): 区分充值资金来源并完善协作单记录
This commit is contained in:
@@ -79,6 +79,24 @@ export interface WeiboCollabAccountOption {
|
||||
ck_check_status: string;
|
||||
}
|
||||
|
||||
export interface WeiboRechargeRecord {
|
||||
id: number;
|
||||
order_id: string;
|
||||
pay_id: string;
|
||||
amount_yuan: number;
|
||||
single_reward: number;
|
||||
status: string;
|
||||
status_label: string;
|
||||
sub_status: string;
|
||||
is_paid: boolean;
|
||||
paid_at: string | null;
|
||||
status_checked_at: string | null;
|
||||
created_by: number | null;
|
||||
created_by_name: string;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface WeiboCollabOrder {
|
||||
id: number;
|
||||
order_no: string;
|
||||
@@ -103,6 +121,10 @@ export interface WeiboCollabOrder {
|
||||
remark: string;
|
||||
status: WeiboCollabStatus;
|
||||
status_label: string;
|
||||
funding_source: string;
|
||||
funding_source_label: string;
|
||||
funding_source_detail: string;
|
||||
funding_recharge_record_id: number | null;
|
||||
next_status: WeiboCollabStatus | null;
|
||||
missing_fields: string[];
|
||||
weidou_balance: number | null;
|
||||
@@ -147,6 +169,7 @@ export interface WeiboCollabOrder {
|
||||
} | null;
|
||||
last_metric_at: string | null;
|
||||
last_metric_message: string;
|
||||
recharge_records: WeiboRechargeRecord[];
|
||||
}
|
||||
|
||||
export interface WeiboCollabOrderList {
|
||||
|
||||
@@ -71,7 +71,7 @@ const { TextArea } = Input;
|
||||
const STATUS_META: Record<WeiboCollabStatus, { label: string; color: string }> = {
|
||||
incomplete: { label: '待补资料', color: 'default' },
|
||||
to_recharge: { label: '待充值', color: 'blue' },
|
||||
recharged: { label: '已充值', color: 'cyan' },
|
||||
recharged: { label: '余额已核账', color: 'cyan' },
|
||||
scripted: { label: '已上脚本', color: 'purple' },
|
||||
done: { label: '兑换成功', color: 'success' },
|
||||
weidou_error: { label: '微豆异常', color: 'orange' },
|
||||
@@ -241,6 +241,13 @@ function balanceMeta(reason: string) {
|
||||
return { label: '未核账', color: 'default' };
|
||||
}
|
||||
|
||||
function fundingSourceColor(source: string) {
|
||||
if (source === 'web_recharge') return 'success';
|
||||
if (source === 'legacy_web_recharge') return 'warning';
|
||||
if (source === 'unknown') return 'default';
|
||||
return 'blue';
|
||||
}
|
||||
|
||||
interface OrderFormValues {
|
||||
order_no: string;
|
||||
shop?: string;
|
||||
@@ -307,6 +314,9 @@ export default function WeiboCollabPage() {
|
||||
const [statusRollback, setStatusRollback] = useState(false);
|
||||
const [statusIds, setStatusIds] = useState<number[]>([]);
|
||||
const [statusSaving, setStatusSaving] = useState(false);
|
||||
const [noteTarget, setNoteTarget] = useState<WeiboCollabOrder | null>(null);
|
||||
const [noteValue, setNoteValue] = useState('');
|
||||
const [noteSaving, setNoteSaving] = useState(false);
|
||||
|
||||
const [shopOpen, setShopOpen] = useState(false);
|
||||
const [goodsOpen, setGoodsOpen] = useState(false);
|
||||
@@ -534,6 +544,33 @@ export default function WeiboCollabPage() {
|
||||
setEditOpen(true);
|
||||
};
|
||||
|
||||
const openNote = (order: WeiboCollabOrder) => {
|
||||
setNoteTarget(order);
|
||||
setNoteValue(order.remark || '');
|
||||
};
|
||||
|
||||
const saveNote = async () => {
|
||||
if (!noteTarget || !canEdit) return;
|
||||
setNoteSaving(true);
|
||||
try {
|
||||
const saved = await weiboCollabApi.updateOrder(noteTarget.id, {
|
||||
remark: noteValue.trim(),
|
||||
expected_version: noteTarget.version,
|
||||
});
|
||||
setOrders((current) => current.map((row) => (
|
||||
row.id === saved.id ? saved : row
|
||||
)));
|
||||
setDetail((current) => (current?.id === saved.id ? saved : current));
|
||||
setNoteTarget(null);
|
||||
setNoteValue('');
|
||||
message.success(noteValue.trim() ? '备注已保存' : '备注已清空');
|
||||
} catch (error) {
|
||||
message.error(getErrorMessage(error));
|
||||
} finally {
|
||||
setNoteSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoodsChange = (value: string) => {
|
||||
const selected = goodsOptions.find((item) => item.value === value);
|
||||
if (!selected) return;
|
||||
@@ -980,11 +1017,40 @@ export default function WeiboCollabPage() {
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 130,
|
||||
width: 190,
|
||||
render: (value: WeiboCollabStatus, row) => (
|
||||
<Space size={4} wrap>
|
||||
<Tag color={STATUS_META[value]?.color}>{row.status_label || value}</Tag>
|
||||
{row.exchange_verdict === 'unknown' && <Tag color="warning">待确认</Tag>}
|
||||
<Space orientation="vertical" size={2} style={{ width: '100%' }}>
|
||||
<Space size={4} wrap>
|
||||
<Tag color={STATUS_META[value]?.color}>{row.status_label || value}</Tag>
|
||||
{row.exchange_verdict === 'unknown' && <Tag color="warning">待确认</Tag>}
|
||||
{row.funding_source_label && (
|
||||
<Tag color={fundingSourceColor(row.funding_source)}>
|
||||
{row.funding_source_label}
|
||||
</Tag>
|
||||
)}
|
||||
</Space>
|
||||
<div
|
||||
role={canEdit && !row.is_archived ? 'button' : undefined}
|
||||
tabIndex={canEdit && !row.is_archived ? 0 : undefined}
|
||||
title={row.remark || (canEdit && !row.is_archived ? '点击添加备注' : undefined)}
|
||||
onClick={() => {
|
||||
if (canEdit && !row.is_archived) openNote(row);
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (canEdit && !row.is_archived && (event.key === 'Enter' || event.key === ' ')) {
|
||||
event.preventDefault();
|
||||
openNote(row);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
cursor: canEdit && !row.is_archived ? 'text' : 'default',
|
||||
minHeight: 22,
|
||||
wordBreak: 'break-word',
|
||||
lineHeight: 1.35,
|
||||
}}
|
||||
>
|
||||
{row.remark || <Text type="secondary">+ 备注</Text>}
|
||||
</div>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
@@ -1569,6 +1635,29 @@ export default function WeiboCollabPage() {
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="订单备注"
|
||||
open={noteTarget !== null}
|
||||
confirmLoading={noteSaving}
|
||||
okText="保存备注"
|
||||
cancelText="取消"
|
||||
onOk={() => void saveNote()}
|
||||
onCancel={() => {
|
||||
setNoteTarget(null);
|
||||
setNoteValue('');
|
||||
}}
|
||||
destroyOnHidden
|
||||
>
|
||||
<TextArea
|
||||
rows={5}
|
||||
maxLength={512}
|
||||
showCount
|
||||
value={noteValue}
|
||||
onChange={(event) => setNoteValue(event.target.value)}
|
||||
placeholder="填写订单备注,保存后会记录到审计日志"
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={`修改状态${statusIds.length > 1 ? `(${statusIds.length} 单)` : ''}`}
|
||||
open={statusOpen}
|
||||
@@ -1751,6 +1840,23 @@ export default function WeiboCollabPage() {
|
||||
label: '最近核账',
|
||||
children: formatTime(detail.weidou_checked_at),
|
||||
},
|
||||
{
|
||||
key: 'funding_source',
|
||||
label: '余额来源',
|
||||
children: detail.funding_source_label ? (
|
||||
<Tag color={fundingSourceColor(detail.funding_source)}>
|
||||
{detail.funding_source_label}
|
||||
</Tag>
|
||||
) : (
|
||||
'未识别'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'funding_detail',
|
||||
label: '来源依据',
|
||||
span: 2,
|
||||
children: detail.funding_source_detail || '暂无来源依据',
|
||||
},
|
||||
{
|
||||
key: 'scripted',
|
||||
label: '上脚本时间',
|
||||
@@ -1809,6 +1915,70 @@ export default function WeiboCollabPage() {
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
<Title level={5}>Web 充值记录</Title>
|
||||
<Table
|
||||
rowKey="id"
|
||||
size="small"
|
||||
pagination={false}
|
||||
locale={{ emptyText: '该账号暂无系统 Web 充值下单记录' }}
|
||||
dataSource={detail.recharge_records}
|
||||
rowClassName={(row) => (
|
||||
row.id === detail.funding_recharge_record_id ? 'ant-table-row-selected' : ''
|
||||
)}
|
||||
columns={[
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 170,
|
||||
render: formatTime,
|
||||
},
|
||||
{
|
||||
title: '充值单号',
|
||||
dataIndex: 'order_id',
|
||||
render: (value: string) => (
|
||||
<Text code copyable={{ text: value }}>
|
||||
{value}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '金额',
|
||||
dataIndex: 'amount_yuan',
|
||||
width: 90,
|
||||
align: 'right',
|
||||
render: (value: number) => `${value} 元`,
|
||||
},
|
||||
{
|
||||
title: '赠送微豆',
|
||||
dataIndex: 'single_reward',
|
||||
width: 110,
|
||||
align: 'right',
|
||||
render: (value: number) => value.toLocaleString(),
|
||||
},
|
||||
{
|
||||
title: '支付状态',
|
||||
dataIndex: 'status',
|
||||
width: 110,
|
||||
render: (value: string, row) => (
|
||||
<Tag color={value === 'paid' ? 'success' : value === 'failed' ? 'error' : 'default'}>
|
||||
{row.status_label || value}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '支付时间',
|
||||
dataIndex: 'paid_at',
|
||||
width: 170,
|
||||
render: formatTime,
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'created_by_name',
|
||||
width: 110,
|
||||
render: (value: string) => value || '-',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Space wrap>
|
||||
{canEdit && !detail.is_archived && (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user