优化快手电子凭证:客服核销页、店铺展示与筛选

- 客服角色仅展示「券码核销」Tab,本地列表直接核销
- 卖家列改为店铺名+ID 展示,并返回 shopName
- 新增轻量店铺列表接口,支持按店铺下拉筛选
- 修复 asRecord 空值导致的前端构建类型错误
This commit is contained in:
yml2213
2026-07-10 17:46:40 +08:00
parent 7aeaafbe75
commit 36fb994acd
6 changed files with 414 additions and 139 deletions
@@ -5,6 +5,7 @@ import {
consumeAdminKuaishouIndustryVoucherByCode, consumeAdminKuaishouIndustryVoucherByCode,
disagreeAdminKuaishouIndustryRefund, disagreeAdminKuaishouIndustryRefund,
listAdminKuaishouIndustryRefunds, listAdminKuaishouIndustryRefunds,
listAdminKuaishouIndustryShops,
listAdminKuaishouIndustryVouchers, listAdminKuaishouIndustryVouchers,
resendAdminKuaishouIndustryVoucherCode, resendAdminKuaishouIndustryVoucherCode,
reverseAdminKuaishouIndustryVoucher, reverseAdminKuaishouIndustryVoucher,
@@ -35,6 +36,16 @@ router.get(
), ),
) )
router.get(
'/kuaishou-industry/shops',
requireAdminRoles(['admin', 'operator', 'support']),
createJsonHandler(() => listAdminKuaishouIndustryShops(), {
successMessage: 'ok',
errorMessage: '查询快手行业店铺失败',
scope: '[admin/kuaishou-industry/shops]',
}),
)
router.post( router.post(
'/kuaishou-industry/refunds/list', '/kuaishou-industry/refunds/list',
requireAdminRoles(['admin', 'operator']), requireAdminRoles(['admin', 'operator']),
@@ -32,9 +32,12 @@ export async function listAdminKuaishouIndustryVouchers(
input: KuaishouIndustryVoucherAdminListQuery = {}, input: KuaishouIndustryVoucherAdminListQuery = {},
) { ) {
const result = await listKuaishouIndustryVouchersForAdmin(input) const result = await listKuaishouIndustryVouchersForAdmin(input)
const shopNameBySellerId = buildKuaishouIndustryShopNameMap()
return { return {
items: result.items.map(mapAdminKuaishouIndustryVoucher), items: result.items.map((voucher) =>
mapAdminKuaishouIndustryVoucher(voucher, shopNameBySellerId),
),
pagination: { pagination: {
page: result.page, page: result.page,
pageSize: result.pageSize, pageSize: result.pageSize,
@@ -43,6 +46,27 @@ export async function listAdminKuaishouIndustryVouchers(
} }
} }
/** 轻量店铺选项:仅展示名与 ID,供筛选/下拉使用(不含 token 等敏感字段) */
export function listAdminKuaishouIndustryShops() {
const shops = listKuaishouIndustryShopConfigs(getKuaishouIndustrySourceConfig())
return {
shops: shops
.map((shop) => {
const sellerId = String(shop.sellerId || '').trim()
const shopId = String(shop.shopId || '').trim()
const shopName = String(shop.customShopName || shop.shopName || '').trim()
return {
sellerId,
shopId,
shopName,
enabled: shop.enabled !== false,
}
})
.filter((shop) => shop.sellerId || shop.shopId),
}
}
export async function listAdminKuaishouIndustryRefunds(input: JsonObject = {}) { export async function listAdminKuaishouIndustryRefunds(input: JsonObject = {}) {
assertRequired(input.beginTime, '开始时间未填写') assertRequired(input.beginTime, '开始时间未填写')
assertRequired(input.endTime, '结束时间未填写') assertRequired(input.endTime, '结束时间未填写')
@@ -370,7 +394,11 @@ function sanitizeOpenApiRequest(request: JsonObject | undefined): JsonObject | n
} }
} }
function mapAdminKuaishouIndustryVoucher(voucher: KuaishouIndustryVoucherRow) { function mapAdminKuaishouIndustryVoucher(
voucher: KuaishouIndustryVoucherRow,
shopNameBySellerId: Map<string, string> = buildKuaishouIndustryShopNameMap(),
) {
const sellerId = String(voucher.seller_id || '').trim()
return { return {
id: voucher.id, id: voucher.id,
voucherCode: voucher.voucher_code, voucherCode: voucher.voucher_code,
@@ -378,7 +406,8 @@ function mapAdminKuaishouIndustryVoucher(voucher: KuaishouIndustryVoucherRow) {
orderId: voucher.order_id, orderId: voucher.order_id,
taskId: voucher.task_id, taskId: voucher.task_id,
unitIndex: voucher.unit_index, unitIndex: voucher.unit_index,
sellerId: voucher.seller_id, sellerId,
shopName: shopNameBySellerId.get(sellerId) || '',
tokenMasked: maskSecret(voucher.token), tokenMasked: maskSecret(voucher.token),
status: voucher.status, status: voucher.status,
validStartTime: Number(voucher.valid_start_time || 0) || 0, validStartTime: Number(voucher.valid_start_time || 0) || 0,
@@ -397,6 +426,29 @@ function mapAdminKuaishouIndustryVoucher(voucher: KuaishouIndustryVoucherRow) {
} }
} }
function buildKuaishouIndustryShopNameMap(): Map<string, string> {
const map = new Map<string, string>()
const shops = listKuaishouIndustryShopConfigs(getKuaishouIndustrySourceConfig())
for (const shop of shops) {
const name = String(shop.customShopName || shop.shopName || '').trim()
if (!name) {
continue
}
const sellerId = String(shop.sellerId || '').trim()
const shopId = String(shop.shopId || '').trim()
if (sellerId) {
map.set(sellerId, name)
}
if (shopId && shopId !== sellerId) {
map.set(shopId, name)
}
}
return map
}
function mapTaskReference(task: TaskRow) { function mapTaskReference(task: TaskRow) {
return { return {
taskId: task.id, taskId: task.id,
@@ -14,6 +14,7 @@ import {
Descriptions, Descriptions,
Input, Input,
InputNumber, InputNumber,
Popconfirm,
Select, Select,
Space, Space,
Table, Table,
@@ -33,7 +34,7 @@ import {
checkAdminKuaishouIndustryVoucherAvailable, checkAdminKuaishouIndustryVoucherAvailable,
consumeAdminKuaishouIndustryVoucher, consumeAdminKuaishouIndustryVoucher,
disagreeAdminKuaishouIndustryRefund, disagreeAdminKuaishouIndustryRefund,
fetchAdminKuaishouIndustrySourceConfig, fetchAdminKuaishouIndustryShops,
fetchAdminKuaishouIndustryVouchers, fetchAdminKuaishouIndustryVouchers,
listAdminKuaishouIndustryRefunds, listAdminKuaishouIndustryRefunds,
resendAdminKuaishouIndustryVoucherCode, resendAdminKuaishouIndustryVoucherCode,
@@ -44,11 +45,11 @@ import type {
AdminKuaishouIndustryRefundApprovePayload, AdminKuaishouIndustryRefundApprovePayload,
AdminKuaishouIndustryRefundDisagreePayload, AdminKuaishouIndustryRefundDisagreePayload,
AdminKuaishouIndustryRefundListPayload, AdminKuaishouIndustryRefundListPayload,
AdminKuaishouIndustryShopConfig, AdminKuaishouIndustryShopOption,
AdminKuaishouIndustryVoucher, AdminKuaishouIndustryVoucher,
AdminKuaishouIndustryVoucherToolPayload, AdminKuaishouIndustryVoucherToolPayload,
} from '@/types/admin' } from '@/types/admin'
import { hasAdminRole } from '@/utils/admin-auth' import { getAdminRole, hasAdminRole } from '@/utils/admin-auth'
import { import {
ADMIN_DEFAULT_PAGE_SIZE, ADMIN_DEFAULT_PAGE_SIZE,
buildAdminLocalTablePagination, buildAdminLocalTablePagination,
@@ -58,7 +59,7 @@ import { formatAdminDateTime } from '@/utils/admin-time'
import { stringifyDisplayJson } from '@/utils/date-time' import { stringifyDisplayJson } from '@/utils/date-time'
type DateRangeValue = [Dayjs | null, Dayjs | null] | null type DateRangeValue = [Dayjs | null, Dayjs | null] | null
type IndustryTab = 'vouchers' | 'refunds' | 'result' type IndustryTab = 'support-consume' | 'vouchers' | 'refunds' | 'result'
type IndustryActionScope = 'vouchers' | 'refunds' type IndustryActionScope = 'vouchers' | 'refunds'
type IndustryActionKind = type IndustryActionKind =
| 'check' | 'check'
@@ -109,10 +110,14 @@ type RefundListState = {
const DEFAULT_ETICKET_TYPE = 'DINING_OPEN_TICKET' const DEFAULT_ETICKET_TYPE = 'DINING_OPEN_TICKET'
export default function AdminKuaishouIndustryPage() { export default function AdminKuaishouIndustryPage() {
const [activeTab, setActiveTab] = useState<IndustryTab>('vouchers') const isSupportOnly = getAdminRole() === 'support'
const canManageRefund = hasAdminRole('operator') const canManageRefund = hasAdminRole('operator')
const [activeTab, setActiveTab] = useState<IndustryTab>(
isSupportOnly ? 'support-consume' : 'vouchers',
)
const [voucherLoading, setVoucherLoading] = useState(false) const [voucherLoading, setVoucherLoading] = useState(false)
const [actionLoading, setActionLoading] = useState('') const [actionLoading, setActionLoading] = useState('')
const [consumingVoucherId, setConsumingVoucherId] = useState<number | null>(null)
const [vouchers, setVouchers] = useState<AdminKuaishouIndustryVoucher[]>([]) const [vouchers, setVouchers] = useState<AdminKuaishouIndustryVoucher[]>([])
const [voucherTotal, setVoucherTotal] = useState(0) const [voucherTotal, setVoucherTotal] = useState(0)
const [voucherFilters, setVoucherFilters] = useState<VoucherFilterState>({ const [voucherFilters, setVoucherFilters] = useState<VoucherFilterState>({
@@ -158,7 +163,17 @@ export default function AdminKuaishouIndustryPage() {
}) })
const [lastActionResult, setLastActionResult] = useState<IndustryActionResultView | null>(null) const [lastActionResult, setLastActionResult] = useState<IndustryActionResultView | null>(null)
const [refundRows, setRefundRows] = useState<Array<Record<string, unknown>>>([]) const [refundRows, setRefundRows] = useState<Array<Record<string, unknown>>>([])
const [industryShops, setIndustryShops] = useState<AdminKuaishouIndustryShopConfig[]>([]) const [industryShops, setIndustryShops] = useState<AdminKuaishouIndustryShopOption[]>([])
const shopFilterOptions = useMemo(
() =>
industryShops
.filter((shop) => shop.sellerId)
.map((shop) => ({
label: formatShopOptionLabel(shop),
value: shop.sellerId,
})),
[industryShops],
)
const refundShopOptions = useMemo( const refundShopOptions = useMemo(
() => () =>
industryShops industryShops
@@ -169,6 +184,24 @@ export default function AdminKuaishouIndustryPage() {
})), })),
[industryShops], [industryShops],
) )
const shopNameBySellerId = useMemo(() => {
const map = new Map<string, string>()
for (const shop of industryShops) {
const name = String(shop.shopName || '').trim()
if (!name) {
continue
}
const sellerId = String(shop.sellerId || '').trim()
const shopId = String(shop.shopId || '').trim()
if (sellerId) {
map.set(sellerId, name)
}
if (shopId) {
map.set(shopId, name)
}
}
return map
}, [industryShops])
const columns = useMemo<TableColumnsType<AdminKuaishouIndustryVoucher>>( const columns = useMemo<TableColumnsType<AdminKuaishouIndustryVoucher>>(
() => [ () => [
@@ -209,14 +242,14 @@ export default function AdminKuaishouIndustryPage() {
), ),
}, },
{ {
title: '卖家', title: '店铺',
minWidth: 160, minWidth: 180,
render: (_, row) => ( render: (_, row) =>
<div className="cell-stack"> renderShopCell(
<span>{row.sellerId || '-'}</span> row.shopName || shopNameBySellerId.get(String(row.sellerId || '').trim()) || '',
<span className="muted">Token{row.tokenMasked || '-'}</span> row.sellerId,
</div> isSupportOnly ? '' : row.tokenMasked,
), ),
}, },
{ {
title: '核销', title: '核销',
@@ -228,30 +261,69 @@ export default function AdminKuaishouIndustryPage() {
</div> </div>
), ),
}, },
{ ...(isSupportOnly
title: '更新时间', ? []
width: 170, : [
render: (_, row) => formatAdminDateTime(row.updatedAt), {
}, title: '更新时间',
width: 170,
render: (_: unknown, row: AdminKuaishouIndustryVoucher) =>
formatAdminDateTime(row.updatedAt),
},
]),
{ {
title: '操作', title: '操作',
fixed: 'right', fixed: 'right' as const,
width: 120, width: isSupportOnly ? 100 : 120,
render: (_, row) => ( render: (_: unknown, row: AdminKuaishouIndustryVoucher) => {
<Button size="small" type="primary" ghost onClick={() => selectVoucher(row)}> if (isSupportOnly) {
const canConsume = row.status === 'UNUSED'
</Button> return (
), <Popconfirm
title="确认核销该券码?"
description={row.voucherCode}
okText="核销"
cancelText="取消"
disabled={!canConsume}
onConfirm={() => consumeVoucherRow(row)}
>
<Button
size="small"
type="primary"
icon={<CheckCircleOutlined />}
disabled={!canConsume}
loading={consumingVoucherId === row.id}
>
</Button>
</Popconfirm>
)
}
return (
<Button size="small" type="primary" ghost onClick={() => selectVoucher(row)}>
</Button>
)
},
}, },
], ],
[], [consumingVoucherId, isSupportOnly, shopNameBySellerId],
) )
const refundColumns = useMemo<TableColumnsType<Record<string, unknown>>>( const refundColumns = useMemo<TableColumnsType<Record<string, unknown>>>(
() => [ () => [
{ title: '售后单', dataIndex: 'refundId', minWidth: 160 }, { title: '售后单', dataIndex: 'refundId', minWidth: 160 },
{ title: '订单', dataIndex: 'oid', minWidth: 160 }, { title: '订单', dataIndex: 'oid', minWidth: 160 },
{ title: '卖家', dataIndex: 'sellerId', minWidth: 140 }, {
title: '店铺',
dataIndex: 'sellerId',
minWidth: 180,
render: (_, row) => {
const sellerId = String(row.sellerId || '').trim()
return renderShopCell(shopNameBySellerId.get(sellerId) || '', sellerId)
},
},
{ title: '状态', dataIndex: 'status', width: 100 }, { title: '状态', dataIndex: 'status', width: 100 },
{ title: '协商', dataIndex: 'negotiateStatus', width: 100 }, { title: '协商', dataIndex: 'negotiateStatus', width: 100 },
{ title: '金额', dataIndex: 'refundFee', width: 100 }, { title: '金额', dataIndex: 'refundFee', width: 100 },
@@ -271,18 +343,18 @@ export default function AdminKuaishouIndustryPage() {
), ),
}, },
], ],
[], [shopNameBySellerId],
) )
useEffect(() => { useEffect(() => {
void loadVouchers()
void loadIndustryShops() void loadIndustryShops()
void loadVouchers()
}, []) }, [])
async function loadIndustryShops() { async function loadIndustryShops() {
try { try {
const response = await fetchAdminKuaishouIndustrySourceConfig() const response = await fetchAdminKuaishouIndustryShops()
const shops = response.data.source.shops || [] const shops = response.data.shops || []
setIndustryShops(shops) setIndustryShops(shops)
const enabledSellerIds = shops const enabledSellerIds = shops
@@ -292,7 +364,8 @@ export default function AdminKuaishouIndustryPage() {
applyDefaultSellerId(enabledSellerIds[0] as string) applyDefaultSellerId(enabledSellerIds[0] as string)
} }
} catch (error) { } catch (error) {
showError(error instanceof Error ? error.message : '读取快手授权店铺失败') // 店铺下拉失败不阻断列表,仅回退为手动输入店铺 ID
console.warn(error instanceof Error ? error.message : '读取店铺列表失败')
} }
} }
@@ -372,6 +445,50 @@ export default function AdminKuaishouIndustryPage() {
} }
} }
async function consumeVoucherRow(row: AdminKuaishouIndustryVoucher) {
if (row.status !== 'UNUSED') {
showError('仅未使用券码可核销')
return
}
setConsumingVoucherId(row.id)
setActionLoading('consume')
try {
const response = await consumeAdminKuaishouIndustryVoucher({
sellerId: row.sellerId,
oid: row.oid,
orderId: row.oid,
taskId: row.taskId || '',
voucherCode: row.voucherCode,
eticketType: DEFAULT_ETICKET_TYPE,
consumeType: 'consume',
serialNum: row.consumeSerialNum || undefined,
etickets: [{ id: row.voucherCode, code: row.voucherCode, num: 1 }],
})
if (!isSupportOnly) {
publishActionResult(
buildIndustryActionResultView('consume', '手动核销', response.data),
)
}
showSuccess(`券码 ${row.voucherCode} 已核销`)
await loadVouchers()
} catch (error) {
const message = error instanceof Error ? error.message : '核销失败'
if (!isSupportOnly) {
publishActionResult(
buildIndustryActionResultView('consume', '手动核销', {
success: false,
error: message,
}),
)
}
showError(message)
} finally {
setConsumingVoucherId(null)
setActionLoading('')
}
}
async function runResendCode() { async function runResendCode() {
setActionLoading('resend') setActionLoading('resend')
try { try {
@@ -403,7 +520,7 @@ export default function AdminKuaishouIndustryPage() {
const [begin, end] = refundDateRange || [] const [begin, end] = refundDateRange || []
const sellerId = resolveRefundSellerId(refundListForm.sellerId) const sellerId = resolveRefundSellerId(refundListForm.sellerId)
if (!sellerId && refundShopOptions.length > 1) { if (!sellerId && refundShopOptions.length > 1) {
showError('请先选择快手售后退款店铺 sellerId') showError('请先选择快手售后退款店铺')
return return
} }
@@ -429,7 +546,7 @@ export default function AdminKuaishouIndustryPage() {
async function runApproveRefund() { async function runApproveRefund() {
const sellerId = resolveRefundSellerId(approveForm.sellerId) const sellerId = resolveRefundSellerId(approveForm.sellerId)
if (!sellerId && refundShopOptions.length > 1) { if (!sellerId && refundShopOptions.length > 1) {
showError('请先选择快手售后退款店铺 sellerId') showError('请先选择快手售后退款店铺')
return return
} }
@@ -445,7 +562,7 @@ export default function AdminKuaishouIndustryPage() {
async function runDisagreeRefund() { async function runDisagreeRefund() {
const sellerId = resolveRefundSellerId(disagreeForm.sellerId) const sellerId = resolveRefundSellerId(disagreeForm.sellerId)
if (!sellerId && refundShopOptions.length > 1) { if (!sellerId && refundShopOptions.length > 1) {
showError('请先选择快手售后退款店铺 sellerId') showError('请先选择快手售后退款店铺')
return return
} }
@@ -537,33 +654,47 @@ export default function AdminKuaishouIndustryPage() {
showSuccess('已填入退款操作区') showSuccess('已填入退款操作区')
} }
const tabItems: TabsProps['items'] = [ const tabItems: TabsProps['items'] = isSupportOnly
{ ? [
key: 'vouchers', {
label: '券码操作', key: 'support-consume',
children: renderVoucherTab(), label: '券码核销',
}, children: renderSupportConsumeTab(),
...(canManageRefund },
? [ ]
{ : [
key: 'refunds', {
label: '售后退款', key: 'vouchers',
children: renderRefundTab(), label: '券码操作',
}, children: renderVoucherTab(),
] },
: []), ...(canManageRefund
{ ? [
key: 'result', {
label: lastActionResult ? `接口结果 · ${lastActionResult.title}` : '接口结果', key: 'refunds',
children: renderResultTab(), label: '售后退款',
}, children: renderRefundTab(),
] },
]
: []),
{
key: 'result',
label: lastActionResult ? `接口结果 · ${lastActionResult.title}` : '接口结果',
children: renderResultTab(),
},
]
return ( return (
<div className="page-stack kuaishou-industry-page"> <div className="page-stack kuaishou-industry-page">
<PageHeader <PageHeader
title="快手电子凭证" title="快手电子凭证"
description={canManageRefund ? '行业电子凭证接口工具与售后处理' : '行业电子凭证接口工具'} description={
isSupportOnly
? '客服核销:查询本地券码并执行核销'
: canManageRefund
? '行业电子凭证接口工具与售后处理'
: '行业电子凭证接口工具'
}
/> />
<Tabs <Tabs
@@ -574,47 +705,63 @@ export default function AdminKuaishouIndustryPage() {
</div> </div>
) )
function renderVoucherTab() { function renderVoucherFilters() {
return ( return (
<div className="page-stack"> <Card>
<Card> <Space wrap className="filter-form">
<Space wrap className="filter-form"> <Input
<Input allowClear
placeholder="订单号 oid"
value={voucherFilters.oid}
onChange={(event) =>
setVoucherFilters({
...voucherFilters,
oid: event.target.value,
})
}
/>
<Input
allowClear
placeholder="券码"
value={voucherFilters.voucherCode}
onChange={(event) =>
setVoucherFilters({
...voucherFilters,
voucherCode: event.target.value,
})
}
/>
<Input
allowClear
placeholder="任务 ID"
value={voucherFilters.taskId}
onChange={(event) =>
setVoucherFilters({
...voucherFilters,
taskId: event.target.value,
})
}
/>
{shopFilterOptions.length > 0 ? (
<Select
allowClear allowClear
placeholder="订单号 oid" showSearch
value={voucherFilters.oid} optionFilterProp="label"
onChange={(event) => placeholder="按店铺筛选"
setVoucherFilters({ style={{ minWidth: 240 }}
...voucherFilters, value={voucherFilters.sellerId || undefined}
oid: event.target.value, options={shopFilterOptions}
onChange={(value) => {
void loadVouchers({
page: 1,
sellerId: String(value || '').trim(),
}) })
} }}
/> />
) : (
<Input <Input
allowClear allowClear
placeholder="券码" placeholder="店铺 ID"
value={voucherFilters.voucherCode}
onChange={(event) =>
setVoucherFilters({
...voucherFilters,
voucherCode: event.target.value,
})
}
/>
<Input
allowClear
placeholder="任务 ID"
value={voucherFilters.taskId}
onChange={(event) =>
setVoucherFilters({
...voucherFilters,
taskId: event.target.value,
})
}
/>
<Input
allowClear
placeholder="卖家 ID"
value={voucherFilters.sellerId} value={voucherFilters.sellerId}
onChange={(event) => onChange={(event) =>
setVoucherFilters({ setVoucherFilters({
@@ -623,50 +770,71 @@ export default function AdminKuaishouIndustryPage() {
}) })
} }
/> />
<Select )}
allowClear <Select
placeholder="券码状态" allowClear
style={{ width: 132 }} placeholder="券码状态"
value={voucherFilters.status || undefined} style={{ width: 132 }}
options={[ value={voucherFilters.status || undefined}
{ label: '未使用', value: 'UNUSED' }, options={[
{ label: '已核销', value: 'CONSUMED' }, { label: '未使用', value: 'UNUSED' },
{ label: '已销', value: 'DESTROYED' }, { label: '已销', value: 'CONSUMED' },
]} { label: '已销毁', value: 'DESTROYED' },
onChange={(value) => setVoucherFilters({ ...voucherFilters, status: value || '' })} ]}
/> onChange={(value) => setVoucherFilters({ ...voucherFilters, status: value || '' })}
<Button />
type="primary" <Button
icon={<SearchOutlined />} type="primary"
loading={voucherLoading} icon={<SearchOutlined />}
onClick={() => loadVouchers({ page: 1 })} loading={voucherLoading}
> onClick={() => loadVouchers({ page: 1 })}
>
</Button>
</Space> </Button>
</Card> </Space>
</Card>
)
}
function renderVoucherTable(scrollX = 1180) {
return (
<Table
rowKey="id"
columns={columns}
dataSource={vouchers}
loading={voucherLoading}
scroll={{ x: scrollX }}
pagination={buildAdminTablePagination({
current: voucherFilters.page,
pageSize: voucherFilters.pageSize,
total: voucherTotal,
onChange: (page, pageSize) => loadVouchers({ page, pageSize }),
})}
/>
)
}
function renderSupportConsumeTab() {
return (
<div className="page-stack">
{renderVoucherFilters()}
<Card title="本地券码">{renderVoucherTable(1080)}</Card>
</div>
)
}
function renderVoucherTab() {
return (
<div className="page-stack">
{renderVoucherFilters()}
<div className="kuaishou-industry-split"> <div className="kuaishou-industry-split">
<Card title="本地券码"> <Card title="本地券码">{renderVoucherTable()}</Card>
<Table
rowKey="id"
columns={columns}
dataSource={vouchers}
loading={voucherLoading}
scroll={{ x: 1180 }}
pagination={buildAdminTablePagination({
current: voucherFilters.page,
pageSize: voucherFilters.pageSize,
total: voucherTotal,
onChange: (page, pageSize) => loadVouchers({ page, pageSize }),
})}
/>
</Card>
<Card title="接口操作"> <Card title="接口操作">
<div className="kuaishou-industry-tool-grid"> <div className="kuaishou-industry-tool-grid">
<Input <Input
placeholder="卖家 ID" placeholder="店铺 ID"
value={toolForm.sellerId} value={toolForm.sellerId}
onChange={(event) => updateToolForm({ sellerId: event.target.value })} onChange={(event) => updateToolForm({ sellerId: event.target.value })}
/> />
@@ -1115,7 +1283,7 @@ export default function AdminKuaishouIndustryPage() {
return ( return (
<Input <Input
allowClear allowClear
placeholder="卖家 ID" placeholder="店铺 ID"
value={normalizedValue} value={normalizedValue}
onChange={(event) => onChange(event.target.value)} onChange={(event) => onChange(event.target.value)}
/> />
@@ -1123,6 +1291,28 @@ export default function AdminKuaishouIndustryPage() {
} }
} }
function renderShopCell(
shopName?: string | null,
sellerId?: string | null,
tokenMasked?: string | null,
) {
const name = String(shopName || '').trim()
const id = String(sellerId || '').trim()
const token = String(tokenMasked || '').trim()
if (!name && !id) {
return <span>-</span>
}
return (
<div className="cell-stack">
<span>{name || id}</span>
{name && id ? <span className="muted">ID{id}</span> : null}
{token ? <span className="muted">Token{token}</span> : null}
</div>
)
}
function extractRefundRows( function extractRefundRows(
response: Record<string, unknown> | null, response: Record<string, unknown> | null,
): Array<Record<string, unknown>> { ): Array<Record<string, unknown>> {
@@ -1409,11 +1599,12 @@ function getVoucherStatusLabel(status: string) {
return status || '-' return status || '-'
} }
function formatShopOptionLabel(shop: AdminKuaishouIndustryShopConfig) { function formatShopOptionLabel(shop: AdminKuaishouIndustryShopOption) {
const name = shop.customShopName || shop.shopName || '未命名店铺' const name = String(shop.shopName || '').trim() || '未命名店铺'
const tokenStatus = shop.hasAccessToken ? '' : ' / 未授权' const id = shop.sellerId || shop.shopId || ''
const disabledSuffix = shop.enabled === false ? ' · 已停用' : ''
return `${name} / ${shop.sellerId}${tokenStatus}` return id ? `${name}${id}${disabledSuffix}` : `${name}${disabledSuffix}`
} }
function formatTimestamp(value: unknown) { function formatTimestamp(value: unknown) {
@@ -4,6 +4,7 @@ import type {
AdminKuaishouIndustryRefundApprovePayload, AdminKuaishouIndustryRefundApprovePayload,
AdminKuaishouIndustryRefundDisagreePayload, AdminKuaishouIndustryRefundDisagreePayload,
AdminKuaishouIndustryRefundListPayload, AdminKuaishouIndustryRefundListPayload,
AdminKuaishouIndustryShopListResult,
AdminKuaishouIndustryVoucherConsumeResult, AdminKuaishouIndustryVoucherConsumeResult,
AdminKuaishouIndustryVoucherListResult, AdminKuaishouIndustryVoucherListResult,
AdminKuaishouIndustryVoucherResendResult, AdminKuaishouIndustryVoucherResendResult,
@@ -17,6 +18,10 @@ export function fetchAdminKuaishouIndustryVouchers(params?: Record<string, unkno
) )
} }
export function fetchAdminKuaishouIndustryShops() {
return apiGet<AdminKuaishouIndustryShopListResult>('/api/v1/admin/kuaishou-industry/shops')
}
export function listAdminKuaishouIndustryRefunds( export function listAdminKuaishouIndustryRefunds(
payload: AdminKuaishouIndustryRefundListPayload, payload: AdminKuaishouIndustryRefundListPayload,
) { ) {
+2
View File
@@ -32,6 +32,8 @@ export type {
AdminKuaishouIndustryOpenApiResult, AdminKuaishouIndustryOpenApiResult,
AdminKuaishouIndustryVoucher, AdminKuaishouIndustryVoucher,
AdminKuaishouIndustryVoucherListResult, AdminKuaishouIndustryVoucherListResult,
AdminKuaishouIndustryShopOption,
AdminKuaishouIndustryShopListResult,
AdminKuaishouIndustryVoucherConsumeResult, AdminKuaishouIndustryVoucherConsumeResult,
AdminKuaishouIndustryVoucherResendResult, AdminKuaishouIndustryVoucherResendResult,
AdminKuaishouIndustryRefundListPayload, AdminKuaishouIndustryRefundListPayload,
@@ -19,6 +19,8 @@ export interface AdminKuaishouIndustryVoucher {
taskId: number | null taskId: number | null
unitIndex: number unitIndex: number
sellerId: string sellerId: string
/** 来自行业店铺配置的展示名(customShopName 优先) */
shopName: string
tokenMasked: string tokenMasked: string
status: string status: string
validStartTime: number validStartTime: number
@@ -41,6 +43,18 @@ export interface AdminKuaishouIndustryVoucherListResult {
pagination: AdminPagination pagination: AdminPagination
} }
/** 电子凭证页筛选用的轻量店铺选项(无 token 等敏感字段) */
export interface AdminKuaishouIndustryShopOption {
sellerId: string
shopId: string
shopName: string
enabled: boolean
}
export interface AdminKuaishouIndustryShopListResult {
shops: AdminKuaishouIndustryShopOption[]
}
export interface AdminKuaishouIndustryVoucherConsumeResult { export interface AdminKuaishouIndustryVoucherConsumeResult {
success: boolean success: boolean
voucher: AdminKuaishouIndustryVoucher voucher: AdminKuaishouIndustryVoucher