优化操作区域

This commit is contained in:
yml2213
2026-07-23 11:44:36 +08:00
parent d7f9918d06
commit 66ef033b47
2 changed files with 364 additions and 96 deletions
@@ -120,6 +120,8 @@ export default function AdminKuaishouIndustryPage() {
const [actionLoading, setActionLoading] = useState('')
const [consumingVoucherId, setConsumingVoucherId] = useState<number | null>(null)
const [vouchers, setVouchers] = useState<AdminKuaishouIndustryVoucher[]>([])
const [selectedVoucher, setSelectedVoucher] =
useState<AdminKuaishouIndustryVoucher | null>(null)
const [voucherTotal, setVoucherTotal] = useState(0)
const [voucherFilters, setVoucherFilters] = useState<VoucherFilterState>({
oid: '',
@@ -341,15 +343,21 @@ export default function AdminKuaishouIndustryPage() {
)
}
const isSelected = selectedVoucher?.id === row.id
return (
<Button size="small" type="primary" ghost onClick={() => selectVoucher(row)}>
<Button
size="small"
type="primary"
ghost={!isSelected}
onClick={() => selectVoucher(row)}
>
{isSelected ? '已选' : '选择'}
</Button>
)
},
},
],
[consumingVoucherId, isSupportOnly, shopNameBySellerId],
[consumingVoucherId, isSupportOnly, selectedVoucher?.id, shopNameBySellerId],
)
const refundColumns = useMemo<TableColumnsType<Record<string, unknown>>>(
@@ -425,7 +433,14 @@ export default function AdminKuaishouIndustryPage() {
page: filters.page,
pageSize: filters.pageSize,
})
setVouchers(response.data.items)
const items = response.data.items
setVouchers(items)
setSelectedVoucher((current) => {
if (!current) {
return null
}
return items.find((item) => item.id === current.id) || current
})
setVoucherTotal(response.data.pagination.total)
} catch (error) {
showError(error instanceof Error ? error.message : '查询电子凭证失败')
@@ -435,18 +450,8 @@ export default function AdminKuaishouIndustryPage() {
}
function selectVoucher(row: AdminKuaishouIndustryVoucher) {
setToolForm((current) => ({
...current,
sellerId: row.sellerId,
oid: row.oid,
orderId: row.oid,
taskId: row.taskId || '',
voucherCode: row.voucherCode,
eticketType: row.eticketType || current.eticketType || '',
serialNum: row.consumeSerialNum || current.serialNum,
reason: current.reason || 'USER_APPLY_REFUND',
}))
showSuccess('已填入券码操作区')
setSelectedVoucher(row)
showSuccess(`已选择券码 ${row.voucherCode}`)
}
async function runCheckAvailable() {
@@ -688,9 +693,17 @@ export default function AdminKuaishouIndustryPage() {
}
function buildVoucherToolPayload(): AdminKuaishouIndustryVoucherToolPayload {
const voucherCode = String(toolForm.voucherCode || '').trim()
const voucherCode = String(selectedVoucher?.voucherCode || toolForm.voucherCode || '').trim()
const oid = String(selectedVoucher?.oid || toolForm.oid || toolForm.orderId || '').trim()
return {
...toolForm,
sellerId: selectedVoucher?.sellerId || toolForm.sellerId,
oid,
orderId: oid,
taskId: selectedVoucher?.taskId || toolForm.taskId,
voucherCode,
eticketType: selectedVoucher?.eticketType || toolForm.eticketType,
serialNum: toolForm.serialNum || selectedVoucher?.consumeSerialNum,
etickets: voucherCode ? [{ id: voucherCode, code: voucherCode, num: 1 }] : undefined,
}
}
@@ -878,6 +891,9 @@ export default function AdminKuaishouIndustryPage() {
columns={columns}
dataSource={vouchers}
loading={voucherLoading}
rowClassName={(row) =>
selectedVoucher?.id === row.id ? 'kuaishou-industry-row-selected' : ''
}
// 列已压缩;仅在极窄容器时横向滚动
scroll={{ x: scrollX || 1064 }}
pagination={buildAdminTablePagination({
@@ -900,6 +916,18 @@ export default function AdminKuaishouIndustryPage() {
}
function renderVoucherTab() {
const hasVoucherTarget = Boolean(
selectedVoucher ||
String(toolForm.voucherCode || '').trim() ||
String(toolForm.taskId || '').trim() ||
String(toolForm.oid || toolForm.orderId || '').trim(),
)
const selectedShopName = selectedVoucher
? selectedVoucher.shopName ||
shopNameBySellerId.get(String(selectedVoucher.sellerId || '').trim()) ||
'未命名店铺'
: ''
return (
<div className="page-stack">
{renderVoucherFilters()}
@@ -909,84 +937,200 @@ export default function AdminKuaishouIndustryPage() {
{renderVoucherTable(1064)}
</Card>
<Card title="接口操作">
<div className="kuaishou-industry-tool-grid">
<Input
placeholder="店铺 ID"
value={toolForm.sellerId}
onChange={(event) => updateToolForm({ sellerId: event.target.value })}
/>
<Input
placeholder="订单号 oid"
value={toolForm.oid}
onChange={(event) =>
updateToolForm({
oid: event.target.value,
orderId: event.target.value,
})
}
/>
<Input
placeholder="任务 ID"
value={String(toolForm.taskId || '')}
onChange={(event) => updateToolForm({ taskId: event.target.value })}
/>
<Input
placeholder="券码"
value={toolForm.voucherCode}
onChange={(event) => updateToolForm({ voucherCode: event.target.value })}
/>
<Input
placeholder="电子凭证类型(选中券码自动填入)"
value={toolForm.eticketType}
onChange={(event) => updateToolForm({ eticketType: event.target.value })}
/>
<Input
placeholder="核销序列号"
value={toolForm.serialNum}
onChange={(event) => updateToolForm({ serialNum: event.target.value })}
/>
<Input
placeholder="核销类型"
value={toolForm.consumeType}
onChange={(event) => updateToolForm({ consumeType: event.target.value })}
/>
<Select
allowClear
placeholder="销毁/冲正原因"
value={toolForm.reason || undefined}
onChange={(value) => updateToolForm({ reason: value || '' })}
options={[
{ label: 'USER_APPLY_REFUND(用户退款)', value: 'USER_APPLY_REFUND' },
{ label: 'SYS_ADMIN_DESTROY(后台作废)', value: 'SYS_ADMIN_DESTROY' },
{ label: 'SUPPLY_DESTROY(供应商作废)', value: 'SUPPLY_DESTROY' },
{ label: 'ETICKET_EXPIRED(凭证过期)', value: 'ETICKET_EXPIRED' },
]}
style={{ minWidth: 220 }}
/>
<Input
placeholder="门店名称"
value={toolForm.storeName}
onChange={(event) => updateToolForm({ storeName: event.target.value })}
/>
<Input
placeholder="门店地址"
value={toolForm.storeAddress}
onChange={(event) => updateToolForm({ storeAddress: event.target.value })}
/>
<Card title="券码操作" className="kuaishou-industry-tool-card">
<div className="kuaishou-industry-tool-section">
<div className="kuaishou-industry-tool-section-title"></div>
{selectedVoucher ? (
<div className="kuaishou-industry-selected-voucher">
<div className="kuaishou-industry-selected-header">
<Typography.Text
strong
copyable={{ text: selectedVoucher.voucherCode }}
className="kuaishou-industry-selected-code"
>
{selectedVoucher.voucherCode}
</Typography.Text>
<Space size={4}>
<Tag color={getVoucherStatusColor(selectedVoucher.status)}>
{getVoucherStatusLabel(selectedVoucher.status)}
</Tag>
<Button type="link" size="small" onClick={() => setSelectedVoucher(null)}>
</Button>
</Space>
</div>
<div className="kuaishou-industry-selected-meta">
<div>
<span></span>
<Typography.Text copyable={{ text: selectedVoucher.oid }}>
{selectedVoucher.oid || '-'}
</Typography.Text>
</div>
<div>
<span></span>
<strong>{selectedVoucher.taskNo || selectedVoucher.taskId || '-'}</strong>
</div>
<div>
<span></span>
<strong>{selectedShopName}</strong>
<small>{selectedVoucher.sellerId || '-'}</small>
</div>
<div>
<span></span>
<strong title={selectedVoucher.eticketType || undefined}>
{selectedVoucher.eticketType || '-'}
</strong>
</div>
</div>
</div>
) : (
<div className="kuaishou-industry-selected-empty">
<strong></strong>
<span></span>
</div>
)}
</div>
<Space wrap className="kuaishou-industry-actions">
<div className="kuaishou-industry-tool-section">
<div className="kuaishou-industry-tool-section-title"></div>
<div className="kuaishou-industry-tool-grid">
<label className="kuaishou-industry-tool-field">
<span></span>
<Input
placeholder="consume"
value={toolForm.consumeType}
onChange={(event) => updateToolForm({ consumeType: event.target.value })}
/>
</label>
<label className="kuaishou-industry-tool-field">
<span></span>
<Input
placeholder={selectedVoucher?.consumeSerialNum || '默认使用卡券记录'}
value={toolForm.serialNum}
onChange={(event) => updateToolForm({ serialNum: event.target.value })}
/>
</label>
<label className="kuaishou-industry-tool-field kuaishou-industry-tool-field-wide">
<span></span>
<Select
value={toolForm.reason || undefined}
onChange={(value) => updateToolForm({ reason: value || '' })}
options={[
{ label: '用户申请退款', value: 'USER_APPLY_REFUND' },
{ label: '后台作废', value: 'SYS_ADMIN_DESTROY' },
{ label: '供应商作废', value: 'SUPPLY_DESTROY' },
{ label: '凭证过期', value: 'ETICKET_EXPIRED' },
]}
/>
</label>
<label className="kuaishou-industry-tool-field">
<span></span>
<Input
placeholder="用于核销明细"
value={toolForm.storeName}
onChange={(event) => updateToolForm({ storeName: event.target.value })}
/>
</label>
<label className="kuaishou-industry-tool-field">
<span></span>
<Input
placeholder="用于核销明细"
value={toolForm.storeAddress}
onChange={(event) => updateToolForm({ storeAddress: event.target.value })}
/>
</label>
</div>
</div>
<Collapse
ghost
size="small"
className="kuaishou-industry-manual-target"
items={[
{
key: 'manual-target',
label: selectedVoucher
? '手动指定卡券(清除当前选择后可用)'
: '手动指定卡券',
children: (
<div className="kuaishou-industry-tool-grid">
<label className="kuaishou-industry-tool-field">
<span> ID</span>
<Input
disabled={Boolean(selectedVoucher)}
value={toolForm.sellerId}
onChange={(event) => updateToolForm({ sellerId: event.target.value })}
/>
</label>
<label className="kuaishou-industry-tool-field">
<span> oid</span>
<Input
disabled={Boolean(selectedVoucher)}
value={toolForm.oid}
onChange={(event) =>
updateToolForm({
oid: event.target.value,
orderId: event.target.value,
})
}
/>
</label>
<label className="kuaishou-industry-tool-field">
<span> ID</span>
<Input
disabled={Boolean(selectedVoucher)}
value={String(toolForm.taskId || '')}
onChange={(event) => updateToolForm({ taskId: event.target.value })}
/>
</label>
<label className="kuaishou-industry-tool-field">
<span></span>
<Input
disabled={Boolean(selectedVoucher)}
value={toolForm.voucherCode}
onChange={(event) =>
updateToolForm({ voucherCode: event.target.value })
}
/>
</label>
<label className="kuaishou-industry-tool-field kuaishou-industry-tool-field-wide">
<span></span>
<Input
disabled={Boolean(selectedVoucher)}
value={toolForm.eticketType}
onChange={(event) =>
updateToolForm({ eticketType: event.target.value })
}
/>
</label>
</div>
),
},
]}
/>
<div className="kuaishou-industry-actions">
<Button
icon={<SyncOutlined />}
loading={actionLoading === 'check'}
disabled={!hasVoucherTarget}
onClick={runCheckAvailable}
>
</Button>
<Button
icon={<SendOutlined />}
loading={actionLoading === 'resend'}
disabled={!hasVoucherTarget}
onClick={runResendCode}
>
</Button>
<Button
type="primary"
icon={<CheckCircleOutlined />}
loading={actionLoading === 'consume'}
disabled={!hasVoucherTarget}
onClick={runConsume}
>
@@ -995,6 +1139,7 @@ export default function AdminKuaishouIndustryPage() {
danger
icon={<RollbackOutlined />}
loading={actionLoading === 'reverse'}
disabled={!hasVoucherTarget}
onClick={runReverse}
>
@@ -1004,6 +1149,7 @@ export default function AdminKuaishouIndustryPage() {
description="将向快手发起销毁回调,并关闭关联任务。已销毁券会重试回调。"
okText="销毁"
cancelText="取消"
disabled={!canManageRefund || !hasVoucherTarget}
onConfirm={() => runDestroy()}
>
<Button
@@ -1011,19 +1157,12 @@ export default function AdminKuaishouIndustryPage() {
type="primary"
icon={<DeleteOutlined />}
loading={actionLoading === 'destroy'}
disabled={!canManageRefund}
disabled={!canManageRefund || !hasVoucherTarget}
>
</Button>
</Popconfirm>
<Button
icon={<SendOutlined />}
loading={actionLoading === 'resend'}
onClick={runResendCode}
>
</Button>
</Space>
</div>
{renderInlineActionResult('vouchers')}
</Card>
</div>
+133 -4
View File
@@ -338,15 +338,144 @@ select {
vertical-align: top;
}
.kuaishou-industry-row-selected > td {
background: #eef6ff !important;
}
.kuaishou-industry-row-selected:hover > td {
background: #e5f0ff !important;
}
.kuaishou-industry-tool-card .ant-card-body {
padding: 16px;
}
.kuaishou-industry-tool-section + .kuaishou-industry-tool-section {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #edf0f5;
}
.kuaishou-industry-tool-section-title {
margin-bottom: 10px;
color: #1f2937;
font-size: 14px;
font-weight: 700;
}
.kuaishou-industry-selected-voucher {
min-width: 0;
}
.kuaishou-industry-selected-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
min-width: 0;
}
.kuaishou-industry-selected-code {
min-width: 0;
overflow-wrap: anywhere;
word-break: break-all;
}
.kuaishou-industry-selected-meta {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px 16px;
margin-top: 12px;
}
.kuaishou-industry-selected-meta > div {
display: grid;
gap: 2px;
min-width: 0;
}
.kuaishou-industry-selected-meta span,
.kuaishou-industry-selected-meta small {
color: #6b7280;
font-size: 12px;
}
.kuaishou-industry-selected-meta strong,
.kuaishou-industry-selected-meta .ant-typography {
min-width: 0;
overflow-wrap: anywhere;
word-break: break-all;
}
.kuaishou-industry-selected-empty {
display: grid;
gap: 4px;
padding: 4px 0;
color: #6b7280;
font-size: 13px;
}
.kuaishou-industry-selected-empty strong {
color: #374151;
}
.kuaishou-industry-tool-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 10px;
margin-bottom: 14px;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.kuaishou-industry-tool-field {
display: grid;
gap: 6px;
min-width: 0;
}
.kuaishou-industry-tool-field > span {
color: #4b5563;
font-size: 12px;
line-height: 1.3;
}
.kuaishou-industry-tool-field-wide {
grid-column: 1 / -1;
}
.kuaishou-industry-tool-field .ant-select {
width: 100%;
}
.kuaishou-industry-manual-target {
margin-top: 14px;
border-top: 1px solid #edf0f5;
}
.kuaishou-industry-manual-target .ant-collapse-header {
padding: 12px 0 8px;
color: #6b7280;
font-size: 12px;
}
.kuaishou-industry-manual-target .ant-collapse-content-box {
padding: 4px 0 0;
}
.kuaishou-industry-actions {
margin-top: 2px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #edf0f5;
}
.kuaishou-industry-actions .ant-btn {
width: 100%;
min-width: 0;
}
.kuaishou-industry-actions > :last-child {
grid-column: 1 / -1;
}
.kuaishou-industry-action-result {