fix:Rock Token列表ID合并与列宽优化
This commit is contained in:
@@ -275,7 +275,7 @@ export function RockTokenTable({
|
||||
columns={columns}
|
||||
dataSource={tokens}
|
||||
loading={loadingTokens}
|
||||
scroll={{ x: 1700 }}
|
||||
scroll={{ x: 1100 }}
|
||||
rowSelection={{
|
||||
selectedRowKeys: selectedTokenIds,
|
||||
preserveSelectedRowKeys: true,
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { ColumnsType } from 'antd/es/table'
|
||||
import { UndoOutlined } from '@ant-design/icons'
|
||||
import { rockApi, type RockTokenItem, type RockTokenRestoreResult } from '../../api'
|
||||
import { formatDateTime } from '../../utils/time'
|
||||
import { TokenInfoCell } from './tokenColumns'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
@@ -181,43 +182,11 @@ export function TokenRecycleBinDrawer({
|
||||
).length
|
||||
|
||||
const columns: ColumnsType<RockTokenItem> = [
|
||||
{ title: 'ID', dataIndex: 'id', width: 80 },
|
||||
{
|
||||
title: '0666 编号 / 兑换码',
|
||||
dataIndex: 'upstream_token_id',
|
||||
width: 360,
|
||||
onCell: () => ({ style: { whiteSpace: 'nowrap' } }),
|
||||
render: (_, record) => {
|
||||
const text = `${record.upstream_token_id || '-'}/${record.redeem_code || '-'}`
|
||||
// 复制按钮只复制兑换码,编号不参与复制
|
||||
const copyText = (record.redeem_code || '').trim()
|
||||
return (
|
||||
<Text
|
||||
copyable={copyText ? { text: copyText } : false}
|
||||
style={{ fontSize: 12, fontFamily: 'monospace', fontWeight: 600, whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '包ID / 提取码',
|
||||
width: 260,
|
||||
render: (_, record) => {
|
||||
const packageText = record.package_id ? String(record.package_id) : '-'
|
||||
const text = `${packageText}/${record.token || '-'}`
|
||||
// 复制按钮只复制提取码,包 ID 不参与复制
|
||||
const copyText = (record.token || '').trim()
|
||||
return (
|
||||
<Text
|
||||
copyable={copyText ? { text: copyText } : false}
|
||||
style={{ fontSize: 12, fontFamily: 'monospace', fontWeight: 600, wordBreak: 'break-all' }}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
title: 'ID / Token(编号/兑换码 · 包ID/提取码)',
|
||||
key: 'token_info',
|
||||
width: 330,
|
||||
render: (_, record) => <TokenInfoCell record={record} />,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
@@ -230,19 +199,19 @@ export function TokenRecycleBinDrawer({
|
||||
{
|
||||
title: '订单号',
|
||||
dataIndex: 'assigned_order_no',
|
||||
width: 200,
|
||||
width: 180,
|
||||
render: (value?: string) => value || '-',
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'actual_role_name',
|
||||
width: 140,
|
||||
width: 130,
|
||||
render: (value?: string) => value || '-',
|
||||
},
|
||||
{
|
||||
title: '删除时间',
|
||||
dataIndex: 'deleted_at',
|
||||
width: 170,
|
||||
width: 160,
|
||||
render: (value?: string | null) => formatDateTime(value),
|
||||
},
|
||||
{
|
||||
@@ -347,7 +316,7 @@ export function TokenRecycleBinDrawer({
|
||||
columns={columns}
|
||||
dataSource={rows}
|
||||
loading={loading}
|
||||
scroll={{ x: 1560 }}
|
||||
scroll={{ x: 1160 }}
|
||||
rowSelection={{
|
||||
selectedRowKeys: selectedIds,
|
||||
onChange: (keys, selected) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
MessageOutlined,
|
||||
StopOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import type { RockTokenItem } from '../../api'
|
||||
@@ -16,9 +17,16 @@ export interface TokenRemarkCellProps {
|
||||
record: RockTokenItem
|
||||
onUpdateRemark: (id: number, remark: string, status?: string) => void
|
||||
onMarkAbnormal: (id: number, remark?: string) => void
|
||||
// 紧凑模式:表格里只渲染一个图标按钮,备注内容通过悬浮提示和弹层查看
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
export function TokenRemarkCell({ record, onUpdateRemark, onMarkAbnormal }: TokenRemarkCellProps) {
|
||||
export function TokenRemarkCell({
|
||||
record,
|
||||
onUpdateRemark,
|
||||
onMarkAbnormal,
|
||||
compact,
|
||||
}: TokenRemarkCellProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [inputValue, setInputValue] = useState(record.remark || '')
|
||||
|
||||
@@ -165,6 +173,31 @@ export function TokenRemarkCell({ record, onUpdateRemark, onMarkAbnormal }: Toke
|
||||
const isAbnormalOrReported =
|
||||
record.status === 'abnormal' || (record.remark && record.remark.includes('上报'))
|
||||
|
||||
if (compact) {
|
||||
const hasRemark = Boolean(record.remark)
|
||||
return (
|
||||
<Popover
|
||||
content={content}
|
||||
trigger="click"
|
||||
open={open}
|
||||
onOpenChange={(v) => {
|
||||
setOpen(v)
|
||||
if (v) setInputValue(record.remark || '')
|
||||
}}
|
||||
placement="bottomLeft"
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type={hasRemark ? 'primary' : 'text'}
|
||||
danger={hasRemark && Boolean(isAbnormalOrReported)}
|
||||
icon={hasRemark ? <MessageOutlined /> : <EditOutlined />}
|
||||
title={hasRemark ? `备注:${record.remark}(点击修改)` : '添加备注'}
|
||||
style={{ width: 26, height: 26, padding: 0, fontSize: 12 }}
|
||||
/>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
if (!record.remark) {
|
||||
return (
|
||||
<Popover
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { Button, Popconfirm, Space, Tag, Tooltip, Typography } from 'antd'
|
||||
import { CopyOutlined, ExportOutlined, EyeOutlined, StopOutlined, SyncOutlined, UndoOutlined } from '@ant-design/icons'
|
||||
import { Button, Dropdown, Popconfirm, Space, Spin, Tag, Tooltip, Typography } from 'antd'
|
||||
import {
|
||||
CopyOutlined,
|
||||
ExportOutlined,
|
||||
EyeOutlined,
|
||||
FileTextOutlined,
|
||||
StopOutlined,
|
||||
SyncOutlined,
|
||||
UndoOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import type { RockTokenItem } from '../../api'
|
||||
import { formatDateTime } from '../../utils/time'
|
||||
@@ -19,6 +27,48 @@ function usingElapsedDays(usingAt?: string): number | null {
|
||||
return diff > 0 ? diff / MS_PER_DAY : 0
|
||||
}
|
||||
|
||||
// Token 主信息:编号/兑换码 与 包ID/提取码 上下两行展示,
|
||||
// 两个复制按钮各自只复制兑换码 / 提取码,编号与包 ID 不参与复制。
|
||||
export function TokenInfoCell({ record }: { record: RockTokenItem }) {
|
||||
const redeemCode = (record.redeem_code || '').trim()
|
||||
const extractCode = (record.token || '').trim()
|
||||
const tokenNo = record.upstream_token_id || '-'
|
||||
const packageNo = record.package_id ? String(record.package_id) : '-'
|
||||
return (
|
||||
<Space direction="vertical" size={1} style={{ width: '100%', lineHeight: 1.45 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, minWidth: 0 }}>
|
||||
<span style={{ flexShrink: 0, fontSize: 10, color: '#94a3b8' }}>#{record.id}</span>
|
||||
<Text
|
||||
copyable={redeemCode ? { text: redeemCode, tooltips: ['复制兑换码', '已复制兑换码'] } : false}
|
||||
style={{
|
||||
fontSize: 11,
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 600,
|
||||
color: '#0f172a',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{tokenNo}/{redeemCode || '-'}
|
||||
</Text>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 4, minWidth: 0 }}>
|
||||
<span style={{ flexShrink: 0, fontSize: 10, color: '#94a3b8', lineHeight: '18px' }}>包ID</span>
|
||||
<Text
|
||||
copyable={extractCode ? { text: extractCode, tooltips: ['复制提取码', '已复制提取码'] } : false}
|
||||
style={{
|
||||
fontSize: 11,
|
||||
fontFamily: 'monospace',
|
||||
color: '#64748b',
|
||||
wordBreak: 'break-all',
|
||||
}}
|
||||
>
|
||||
{packageNo}/{extractCode || '-'}
|
||||
</Text>
|
||||
</div>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
|
||||
export interface TokenColumnHandlers {
|
||||
copyingVoucherId: number | null
|
||||
usingWarnDays: number
|
||||
@@ -56,62 +106,25 @@ export function buildTokenColumns({
|
||||
}: TokenColumnHandlers): ColumnsType<RockTokenItem> {
|
||||
return [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 80,
|
||||
align: 'right',
|
||||
render: (id: number) => (
|
||||
<Text style={{ fontSize: 12, fontFamily: 'monospace', color: '#64748b' }}>{id}</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '0666 编号 / 兑换码',
|
||||
dataIndex: 'upstream_token_id',
|
||||
width: 360,
|
||||
onCell: () => ({ style: { whiteSpace: 'nowrap' } }),
|
||||
render: (_, record) => {
|
||||
const text = `${record.upstream_token_id || '-'}/${record.redeem_code || '-'}`
|
||||
// 复制按钮只复制兑换码,编号不参与复制
|
||||
const copyText = (record.redeem_code || '').trim()
|
||||
return (
|
||||
<Text
|
||||
copyable={copyText ? { text: copyText } : false}
|
||||
style={{ fontSize: 12, fontFamily: 'monospace', fontWeight: 600, whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '包ID / 提取码',
|
||||
width: 260,
|
||||
render: (_, record) => {
|
||||
const packageText = record.package_id ? String(record.package_id) : '-'
|
||||
const text = `${packageText}/${record.token || '-'}`
|
||||
// 复制按钮只复制提取码,包 ID 不参与复制
|
||||
const copyText = (record.token || '').trim()
|
||||
return (
|
||||
<Text
|
||||
copyable={copyText ? { text: copyText } : false}
|
||||
style={{ fontSize: 12, fontFamily: 'monospace', fontWeight: 600, wordBreak: 'break-all' }}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
)
|
||||
},
|
||||
title: 'ID / Token(编号/兑换码 · 包ID/提取码)',
|
||||
key: 'token_info',
|
||||
width: 330,
|
||||
render: (_, record) => <TokenInfoCell record={record} />,
|
||||
},
|
||||
{
|
||||
title: '规格与适用范围',
|
||||
dataIndex: 'color',
|
||||
width: 150,
|
||||
width: 120,
|
||||
render: (_, record) => (
|
||||
<Space direction="vertical" size={2}>
|
||||
<Tag color="geekblue" style={{ borderRadius: 4, fontWeight: 500 }}>
|
||||
<Tag
|
||||
color="geekblue"
|
||||
style={{ borderRadius: 4, fontWeight: 500, fontSize: 11, padding: '0 4px', margin: 0 }}
|
||||
>
|
||||
通用黑色 Token
|
||||
</Tag>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{record.region || 'Q/V区通用 · 自动分发'}
|
||||
<Text type="secondary" style={{ fontSize: 11 }}>
|
||||
{record.region || 'Q/V区通用'}
|
||||
</Text>
|
||||
</Space>
|
||||
),
|
||||
@@ -119,7 +132,7 @@ export function buildTokenColumns({
|
||||
{
|
||||
title: '库存状态',
|
||||
dataIndex: 'status',
|
||||
width: 170,
|
||||
width: 165,
|
||||
render: (status, record) => {
|
||||
switch (status) {
|
||||
case 'available':
|
||||
@@ -193,7 +206,7 @@ export function buildTokenColumns({
|
||||
},
|
||||
{
|
||||
title: '流转时间',
|
||||
width: 210,
|
||||
width: 180,
|
||||
render: (_, record) => {
|
||||
const usingDays = record.status === 'using' ? usingElapsedDays(record.using_at) : null
|
||||
const usingColor =
|
||||
@@ -248,7 +261,7 @@ export function buildTokenColumns({
|
||||
{
|
||||
title: '关联业务工单',
|
||||
dataIndex: 'assigned_order_no',
|
||||
width: 220,
|
||||
width: 168,
|
||||
render: (orderNo, record) => {
|
||||
if (!orderNo) {
|
||||
return <Text type="secondary">-</Text>
|
||||
@@ -286,7 +299,7 @@ export function buildTokenColumns({
|
||||
},
|
||||
{
|
||||
title: '预期 UID vs 扫码角色 (风控门禁)',
|
||||
width: 250,
|
||||
width: 255,
|
||||
render: (_, record) => {
|
||||
const isMismatch = record.status === 'mismatch'
|
||||
return (
|
||||
@@ -323,19 +336,22 @@ export function buildTokenColumns({
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
width: 200,
|
||||
width: 44,
|
||||
align: 'center',
|
||||
render: (_, record) => (
|
||||
<TokenRemarkCell
|
||||
record={record}
|
||||
onUpdateRemark={onUpdateRemark}
|
||||
onMarkAbnormal={onMarkAbnormal}
|
||||
compact
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '发货结果图片',
|
||||
title: '凭证',
|
||||
key: 'voucher',
|
||||
width: 240,
|
||||
width: 56,
|
||||
align: 'center',
|
||||
render: (_, record) => {
|
||||
if (record.status !== 'redeemed' && !record.upstream_fulfill_id) {
|
||||
return <Text type="secondary">-</Text>
|
||||
@@ -346,91 +362,63 @@ export function buildTokenColumns({
|
||||
const imgSrc = `/images/products/${sku}.webp`
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
{/* 商品缩略图:鼠标悬浮微放、点击快速预览高清大图凭据 */}
|
||||
<Tooltip title="点击预览深海蓝发货凭据大图">
|
||||
<div
|
||||
onClick={() => onPreviewVoucher(record)}
|
||||
style={{
|
||||
width: 44,
|
||||
height: 44,
|
||||
flexShrink: 0,
|
||||
borderRadius: 8,
|
||||
border: '1px solid #e2e8f0',
|
||||
background: '#f8fafc',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.06)',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'scale(1.08)'
|
||||
e.currentTarget.style.borderColor = '#93c5fd'
|
||||
e.currentTarget.style.boxShadow = '0 3px 8px rgba(37,99,235,0.2)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'scale(1)'
|
||||
e.currentTarget.style.borderColor = '#e2e8f0'
|
||||
e.currentTarget.style.boxShadow = '0 1px 3px rgba(0,0,0,0.06)'
|
||||
}}
|
||||
>
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
menu={{
|
||||
items: [
|
||||
{ key: 'copy', icon: <CopyOutlined />, label: '复制发货结果图' },
|
||||
{ key: 'preview', icon: <EyeOutlined />, label: '预览高清大图' },
|
||||
],
|
||||
onClick: ({ key }) => {
|
||||
if (key === 'copy') onCopyVoucher(record)
|
||||
if (key === 'preview') onPreviewVoucher(record)
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div
|
||||
title={`${record.target_skin || '黑色ROCK长袖T恤'} · 点击复制或预览发货凭据`}
|
||||
style={{
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: 8,
|
||||
border: '1px solid #e2e8f0',
|
||||
background: '#f8fafc',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
overflow: 'hidden',
|
||||
margin: '0 auto',
|
||||
}}
|
||||
>
|
||||
{isCopying ? (
|
||||
<Spin size="small" />
|
||||
) : (
|
||||
<img
|
||||
src={imgSrc}
|
||||
alt={record.target_skin || '道具'}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain', padding: 2 }}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{/* 操作按钮与商品徽章 */}
|
||||
<Space direction="vertical" size={3}>
|
||||
<Space size={4}>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon={<CopyOutlined />}
|
||||
loading={isCopying}
|
||||
onClick={() => onCopyVoucher(record)}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #059669 0%, #10b981 100%)',
|
||||
borderColor: '#059669',
|
||||
boxShadow: '0 1px 2px rgba(16, 185, 129, 0.2)',
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
padding: '0 8px',
|
||||
}}
|
||||
>
|
||||
复制结果图
|
||||
</Button>
|
||||
<Tooltip title="查看高清发货凭据大图">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => onPreviewVoucher(record)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
<div>
|
||||
<Tag color="cyan" style={{ fontSize: 11, margin: 0 }}>
|
||||
{record.target_skin || '黑色ROCK长袖T恤'}
|
||||
</Tag>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Dropdown>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '原始数据',
|
||||
width: 100,
|
||||
title: '原始',
|
||||
width: 44,
|
||||
align: 'center',
|
||||
render: (_, record) =>
|
||||
record.raw_data ? (
|
||||
<Button size="small" onClick={() => onViewRawData(record)}>
|
||||
查看
|
||||
</Button>
|
||||
<Tooltip title="查看原始数据">
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<FileTextOutlined />}
|
||||
onClick={() => onViewRawData(record)}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Text type="secondary">-</Text>
|
||||
),
|
||||
@@ -438,7 +426,7 @@ export function buildTokenColumns({
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
width: 140,
|
||||
fixed: 'right',
|
||||
render: (_, record) => {
|
||||
const refreshButton = (
|
||||
|
||||
Reference in New Issue
Block a user