feat(admin): 增加履约渠道批量删除并统一分页配置

This commit is contained in:
yml2213
2026-11-02 17:31:55 +08:00
parent 735a791a2e
commit d871b93fde
14 changed files with 184 additions and 46 deletions
@@ -12,6 +12,7 @@ import {
assignChannelBySku, assignChannelBySku,
assignChannelToOrderItems, assignChannelToOrderItems,
deleteChannelRule, deleteChannelRule,
deleteChannelRules,
listChannelRules, listChannelRules,
listPendingChannelGroups, listPendingChannelGroups,
switchChannelForOrderItems, switchChannelForOrderItems,
@@ -234,6 +235,22 @@ router.delete(
}), }),
) )
router.post(
'/fulfillment/rules/batch-delete',
requireAdminPermission('finance.config'),
createJsonHandler((req) => deleteChannelRules(Array.isArray(req.body?.ids) ? req.body.ids : []), {
successMessage: '渠道规则已批量删除',
errorMessage: '批量删除渠道规则失败',
scope: '[admin/fulfillment/rules/batch-delete]',
audit: (_req, data) => ({
action: 'fulfillment.batch_delete_rules',
targetType: 'internal_sku_channel',
targetId: '',
data: data as Record<string, unknown>,
}),
}),
)
router.get( router.get(
'/fulfillment/pipeline/status', '/fulfillment/pipeline/status',
requireAdminPermission('finance.view'), requireAdminPermission('finance.view'),
@@ -25,6 +25,7 @@ import {
} from './channel-types.js' } from './channel-types.js'
import { import {
deleteInternalSkuChannelById, deleteInternalSkuChannelById,
deleteInternalSkuChannelsByIds,
planInternalSkuChannels, planInternalSkuChannels,
setInternalSkuPrimaryChannel, setInternalSkuPrimaryChannel,
upsertInternalSkuChannel, upsertInternalSkuChannel,
@@ -1119,6 +1120,21 @@ export async function deleteChannelRule(ruleId: number): Promise<{ deleted: bool
return { deleted: await deleteInternalSkuChannelById(id) } return { deleted: await deleteInternalSkuChannelById(id) }
} }
export async function deleteChannelRules(idsInput: number[]): Promise<{
deletedCount: number
deletedIds: number[]
missingIds: number[]
}> {
if (!Array.isArray(idsInput) || idsInput.length === 0) {
throw createHttpError('请选择要删除的渠道规则', {
statusCode: 400,
errorCode: 'channel_rule_ids_required',
})
}
const result = await deleteInternalSkuChannelsByIds(idsInput)
return { deletedCount: result.deletedIds.length, ...result }
}
async function loadEnabledRules(): Promise<ChannelRule[]> { async function loadEnabledRules(): Promise<ChannelRule[]> {
const rows = await query<ChannelRule>( const rows = await query<ChannelRule>(
` `
@@ -20,7 +20,7 @@ test(
const { closeDb, query } = await import('../../db/client.js') const { closeDb, query } = await import('../../db/client.js')
const { const {
assignChannelByInternalSku, assignChannelByInternalSku,
deleteChannelRule, deleteChannelRules,
ensureVoucherRuleForSku, ensureVoucherRuleForSku,
listChannelRules, listChannelRules,
switchChannelForOrderItems, switchChannelForOrderItems,
@@ -171,7 +171,9 @@ test(
assert.ok( assert.ok(
listedRules.some((rule) => rule.id === savedRule.id && rule.match_key === voucherSku), listedRules.some((rule) => rule.id === savedRule.id && rule.match_key === voucherSku),
) )
assert.equal((await deleteChannelRule(savedRule.id)).deleted, true) const deletedRules = await deleteChannelRules([savedRule.id])
assert.equal(deletedRules.deletedCount, 1)
assert.deepEqual(deletedRules.deletedIds, [savedRule.id])
} finally { } finally {
if (orderItemIds.length > 0) { if (orderItemIds.length > 0) {
await query(`DELETE FROM cost_exception_items WHERE order_item_id = ANY($1::bigint[])`, [ await query(`DELETE FROM cost_exception_items WHERE order_item_id = ANY($1::bigint[])`, [
@@ -203,3 +203,21 @@ export async function deleteInternalSkuChannelById(channelId: number): Promise<b
const result = await query(`DELETE FROM internal_sku_channels WHERE id = $1`, [id]) const result = await query(`DELETE FROM internal_sku_channels WHERE id = $1`, [id])
return (result.rowCount || 0) > 0 return (result.rowCount || 0) > 0
} }
export async function deleteInternalSkuChannelsByIds(idsInput: number[]): Promise<{
deletedIds: number[]
missingIds: number[]
}> {
const ids = [...new Set(idsInput.map(Number).filter((id) => Number.isInteger(id) && id > 0))]
if (ids.length === 0) return { deletedIds: [], missingIds: [] }
const result = await query<{ id: number }>(
`DELETE FROM internal_sku_channels WHERE id = ANY($1::bigint[]) RETURNING id`,
[ids],
)
const deletedIds = result.rows.map((row) => Number(row.id))
const deleted = new Set(deletedIds)
return {
deletedIds,
missingIds: ids.filter((id) => !deleted.has(id)),
}
}
@@ -27,6 +27,7 @@ import type { Dayjs } from 'dayjs'
import { AdminRangePicker } from '@/components/admin/AdminDatePicker' import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
import FinanceSubNav from '@/components/admin/FinanceSubNav' import FinanceSubNav from '@/components/admin/FinanceSubNav'
import PercentTable from '@/components/common/PercentTable' import PercentTable from '@/components/common/PercentTable'
import { buildAdminTablePagination } from '@/utils/admin-pagination'
import dayjs from '@/lib/dayjs' import dayjs from '@/lib/dayjs'
import { import {
closeFinancePeriod, closeFinancePeriod,
@@ -509,16 +510,15 @@ export default function AdminFinanceLedgerPage() {
dataSource={entriesQuery.data?.data.items || []} dataSource={entriesQuery.data?.data.items || []}
columns={entryColumns} columns={entryColumns}
onRow={(row) => ({ onClick: () => setEntryDetail(row) })} onRow={(row) => ({ onClick: () => setEntryDetail(row) })}
pagination={{ pagination={buildAdminTablePagination({
current: entryPage, current: entryPage,
pageSize: entryPageSize, pageSize: entryPageSize,
total: entriesQuery.data?.data.pagination.total || 0, total: entriesQuery.data?.data.pagination.total || 0,
showSizeChanger: true,
onChange: (page, size) => { onChange: (page, size) => {
setEntryPage(page) setEntryPage(page)
setEntryPageSize(size) setEntryPageSize(size)
}, },
}} })}
/> />
</Card> </Card>
</Space> </Space>
@@ -566,16 +566,15 @@ export default function AdminFinanceLedgerPage() {
loading={reconciliationsQuery.isLoading} loading={reconciliationsQuery.isLoading}
dataSource={reconciliationsQuery.data?.data.items || []} dataSource={reconciliationsQuery.data?.data.items || []}
columns={reconColumns} columns={reconColumns}
pagination={{ pagination={buildAdminTablePagination({
current: reconPage, current: reconPage,
pageSize: reconPageSize, pageSize: reconPageSize,
total: reconciliationsQuery.data?.data.pagination.total || 0, total: reconciliationsQuery.data?.data.pagination.total || 0,
showSizeChanger: true,
onChange: (page, size) => { onChange: (page, size) => {
setReconPage(page) setReconPage(page)
setReconPageSize(size) setReconPageSize(size)
}, },
}} })}
/> />
</Card> </Card>
), ),
@@ -6,7 +6,12 @@
* · 没有命中规则的新单落在「待分类」,收入进未分类桶,不产生成本噪音; * · 没有命中规则的新单落在「待分类」,收入进未分类桶,不产生成本噪音;
* · 第三方卡券是「只统计」渠道:不建任务、不发领取页。 * · 第三方卡券是「只统计」渠道:不建任务、不发领取页。
*/ */
import { PlusOutlined, ReloadOutlined, ThunderboltOutlined } from '@ant-design/icons' import {
DeleteOutlined,
PlusOutlined,
ReloadOutlined,
ThunderboltOutlined,
} from '@ant-design/icons'
import { import {
Alert, Alert,
Button, Button,
@@ -30,7 +35,7 @@ import PercentTable from '@/components/common/PercentTable'
import FinanceSubNav from '@/components/admin/FinanceSubNav' import FinanceSubNav from '@/components/admin/FinanceSubNav'
import { import {
assignChannelBySku, assignChannelBySku,
deleteChannelRule, deleteChannelRules,
fetchChannelExceptions, fetchChannelExceptions,
fetchChannelRules, fetchChannelRules,
fetchPendingChannelGroups, fetchPendingChannelGroups,
@@ -43,6 +48,11 @@ import {
} from '@/services/admin' } from '@/services/admin'
import { formatRedeemAmount } from '@/utils/redeem' import { formatRedeemAmount } from '@/utils/redeem'
import { formatDateTime } from '@/utils/date-time' import { formatDateTime } from '@/utils/date-time'
import {
ADMIN_DEFAULT_PAGE_SIZE,
buildAdminLocalTablePagination,
buildAdminTablePagination,
} from '@/utils/admin-pagination'
const CHANNEL_OPTIONS = [ const CHANNEL_OPTIONS = [
{ value: 'work_order', label: '接单平台' }, { value: 'work_order', label: '接单平台' },
@@ -80,11 +90,13 @@ export default function AdminFulfillmentChannelPage() {
function PendingPanel() { function PendingPanel() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [keyword, setKeyword] = useState('') const [keyword, setKeyword] = useState('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
const [selections, setSelections] = useState<Record<string, string>>({}) const [selections, setSelections] = useState<Record<string, string>>({})
const query = useQuery({ const query = useQuery({
queryKey: ['fulfillment-pending', keyword], queryKey: ['fulfillment-pending', keyword, page, pageSize],
queryFn: () => fetchPendingChannelGroups({ page: 1, pageSize: 100, keyword }), queryFn: () => fetchPendingChannelGroups({ page, pageSize, keyword }),
}) })
const assignMutation = useMutation({ const assignMutation = useMutation({
@@ -191,7 +203,10 @@ function PendingPanel() {
allowClear allowClear
placeholder="SKU / 商品名" placeholder="SKU / 商品名"
style={{ width: 220 }} style={{ width: 220 }}
onSearch={(value) => setKeyword(value)} onSearch={(value) => {
setKeyword(value)
setPage(1)
}}
/> />
<Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}> <Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}>
刷新 刷新
@@ -218,7 +233,15 @@ function PendingPanel() {
loading={query.isFetching} loading={query.isFetching}
dataSource={query.data?.data?.items || []} dataSource={query.data?.data?.items || []}
columns={columns} columns={columns}
pagination={{ pageSize: 20, showSizeChanger: false }} pagination={buildAdminTablePagination({
current: query.data?.data?.page || page,
pageSize: query.data?.data?.pageSize || pageSize,
total: query.data?.data?.total || 0,
onChange: (nextPage, nextPageSize) => {
setPage(nextPageSize !== pageSize ? 1 : nextPage)
setPageSize(nextPageSize)
},
})}
/> />
</Card> </Card>
) )
@@ -227,6 +250,7 @@ function PendingPanel() {
function RulesPanel() { function RulesPanel() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [selectedRuleIds, setSelectedRuleIds] = useState<number[]>([])
const [form] = Form.useForm() const [form] = Form.useForm()
const query = useQuery({ const query = useQuery({
@@ -246,14 +270,31 @@ function RulesPanel() {
}) })
const deleteMutation = useMutation({ const deleteMutation = useMutation({
mutationFn: (id: number) => deleteChannelRule(id), mutationFn: (ids: number[]) => deleteChannelRules(ids),
onSuccess: () => { onSuccess: (result) => {
message.success('规则已删除') const missingHint = result.data.missingIds.length
? `,${result.data.missingIds.length} 条已不存在`
: ''
message.success(`已删除 ${result.data.deletedCount} 条渠道规则${missingHint}`)
setSelectedRuleIds([])
void queryClient.invalidateQueries({ queryKey: ['fulfillment-rules'] }) void queryClient.invalidateQueries({ queryKey: ['fulfillment-rules'] })
void queryClient.invalidateQueries({ queryKey: ['fulfillment-pending'] })
}, },
onError: () => message.error('删除规则失败'), onError: () => message.error('删除规则失败'),
}) })
function confirmDeleteRules(ids: number[]) {
if (ids.length === 0) return
Modal.confirm({
title: ids.length === 1 ? '删除渠道规则' : `批量删除 ${ids.length} 条渠道规则`,
content: '删除后会立即影响新订单的渠道判定;历史订单和成本数据不会被删除。',
okText: '确认删除',
okButtonProps: { danger: true },
cancelText: '取消',
onOk: () => deleteMutation.mutateAsync(ids),
})
}
const columns = [ const columns = [
{ title: '作用范围', width: 110, render: () => <Tag color="blue">全局</Tag> }, { title: '作用范围', width: 110, render: () => <Tag color="blue">全局</Tag> },
{ {
@@ -288,7 +329,7 @@ function RulesPanel() {
danger danger
size="small" size="small"
loading={deleteMutation.isPending} loading={deleteMutation.isPending}
onClick={() => deleteMutation.mutate(row.id)} onClick={() => confirmDeleteRules([row.id])}
> >
删除 删除
</Button> </Button>
@@ -301,6 +342,15 @@ function RulesPanel() {
title={`统一渠道配置(${query.data?.data?.length ?? 0})`} title={`统一渠道配置(${query.data?.data?.length ?? 0})`}
extra={ extra={
<Space> <Space>
<Button
danger
icon={<DeleteOutlined />}
disabled={selectedRuleIds.length === 0}
loading={deleteMutation.isPending}
onClick={() => confirmDeleteRules(selectedRuleIds)}
>
批量删除{selectedRuleIds.length ? ` (${selectedRuleIds.length})` : ''}
</Button>
<Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}> <Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}>
刷新 刷新
</Button> </Button>
@@ -320,7 +370,11 @@ function RulesPanel() {
loading={query.isFetching} loading={query.isFetching}
dataSource={query.data?.data || []} dataSource={query.data?.data || []}
columns={columns} columns={columns}
pagination={{ pageSize: 20, showSizeChanger: false }} rowSelection={{
selectedRowKeys: selectedRuleIds,
onChange: (keys) => setSelectedRuleIds(keys.map(Number)),
}}
pagination={buildAdminLocalTablePagination()}
/> />
<Modal <Modal
@@ -366,9 +420,11 @@ function RulesPanel() {
function ExceptionsPanel() { function ExceptionsPanel() {
const [reason, setReason] = useState('') const [reason, setReason] = useState('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
const query = useQuery({ const query = useQuery({
queryKey: ['fulfillment-exceptions', reason], queryKey: ['fulfillment-exceptions', reason, page, pageSize],
queryFn: () => fetchChannelExceptions({ reason, status: 'open', page: 1, pageSize: 100 }), queryFn: () => fetchChannelExceptions({ reason, status: 'open', page, pageSize }),
}) })
const REASON_OPTIONS = [ const REASON_OPTIONS = [
@@ -408,7 +464,10 @@ function ExceptionsPanel() {
style={{ width: 180 }} style={{ width: 180 }}
value={reason} value={reason}
options={REASON_OPTIONS} options={REASON_OPTIONS}
onChange={setReason} onChange={(value) => {
setReason(value)
setPage(1)
}}
/> />
<Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}> <Button icon={<ReloadOutlined />} onClick={() => void query.refetch()}>
刷新 刷新
@@ -422,7 +481,15 @@ function ExceptionsPanel() {
loading={query.isFetching} loading={query.isFetching}
dataSource={query.data?.data?.items || []} dataSource={query.data?.data?.items || []}
columns={columns} columns={columns}
pagination={{ pageSize: 20, showSizeChanger: false }} pagination={buildAdminTablePagination({
current: query.data?.data?.page || page,
pageSize: query.data?.data?.pageSize || pageSize,
total: query.data?.data?.total || 0,
onChange: (nextPage, nextPageSize) => {
setPage(nextPageSize !== pageSize ? 1 : nextPage)
setPageSize(nextPageSize)
},
})}
/> />
</Card> </Card>
) )
@@ -27,25 +27,25 @@ import {
setAdminProductPrice, setAdminProductPrice,
} from '@/services/admin' } from '@/services/admin'
import type { ProductCatalogItem } from '@/types/admin' import type { ProductCatalogItem } from '@/types/admin'
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
import { useCostCenter } from './CostCenterContext' import { useCostCenter } from './CostCenterContext'
const PAGE_SIZE = 100
export default function VoucherSkuPanel() { export default function VoucherSkuPanel() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const { canConfig, refreshAll } = useCostCenter() const { canConfig, refreshAll } = useCostCenter()
const [page] = useState(1) const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
const [selected, setSelected] = useState<ProductCatalogItem | null>(null) const [selected, setSelected] = useState<ProductCatalogItem | null>(null)
const [ensuringRules, setEnsuringRules] = useState(false) const [ensuringRules, setEnsuringRules] = useState(false)
const [form] = Form.useForm<{ amountFen: number; changeReason?: string }>() const [form] = Form.useForm<{ amountFen: number; changeReason?: string }>()
const query = useQuery({ const query = useQuery({
queryKey: ['cost-center-voucher-products', page], queryKey: ['cost-center-voucher-products', page, pageSize],
queryFn: () => queryFn: () =>
fetchAdminProductCatalog({ fetchAdminProductCatalog({
carrierType: 'third_party_voucher', carrierType: 'third_party_voucher',
page, page,
pageSize: PAGE_SIZE, pageSize,
}), }),
}) })
@@ -154,7 +154,15 @@ export default function VoucherSkuPanel() {
loading={query.isFetching} loading={query.isFetching}
dataSource={items} dataSource={items}
columns={columns} columns={columns}
pagination={{ pageSize: 20, showSizeChanger: false }} pagination={buildAdminTablePagination({
current: page,
pageSize,
total,
onChange: (nextPage, nextPageSize) => {
setPage(nextPageSize !== pageSize ? 1 : nextPage)
setPageSize(nextPageSize)
},
})}
/> />
<Modal <Modal
@@ -41,6 +41,7 @@ import {
import type { PlatformCreditAccount, PlatformCreditEntry, PlatformCreditOwner } from '@/types/admin' import type { PlatformCreditAccount, PlatformCreditEntry, PlatformCreditOwner } from '@/types/admin'
import { hasAdminPermission } from '@/utils/admin-auth' import { hasAdminPermission } from '@/utils/admin-auth'
import { formatAdminDateTime } from '@/utils/admin-time' import { formatAdminDateTime } from '@/utils/admin-time'
import { buildAdminLocalTablePagination } from '@/utils/admin-pagination'
const PLATFORM_TAG_COLORS: Record<string, string> = { const PLATFORM_TAG_COLORS: Record<string, string> = {
nanhang: 'blue', nanhang: 'blue',
@@ -406,7 +407,7 @@ export function PlatformCreditPanel() {
dataSource={visibleAccounts} dataSource={visibleAccounts}
loading={accountsQuery.isLoading} loading={accountsQuery.isLoading}
scroll={{ x: 1080 }} scroll={{ x: 1080 }}
pagination={{ pageSize: 20, showSizeChanger: true }} pagination={buildAdminLocalTablePagination()}
locale={{ emptyText: '还没有客服积分账户' }} locale={{ emptyText: '还没有客服积分账户' }}
/> />
@@ -29,6 +29,7 @@ import {
Typography, Typography,
} from 'antd' } from 'antd'
import PercentTable from '@/components/common/PercentTable' import PercentTable from '@/components/common/PercentTable'
import { buildAdminTablePagination } from '@/utils/admin-pagination'
import type { TableColumnsType } from 'antd' import type { TableColumnsType } from 'antd'
import { useState } from 'react' import { useState } from 'react'
@@ -880,18 +881,16 @@ export default function ConsignmentInventoryPanel() {
dataSource={items} dataSource={items}
loading={inventoriesQuery.isLoading} loading={inventoriesQuery.isLoading}
scroll={{ x: 1590 }} scroll={{ x: 1590 }}
pagination={{ pagination={buildAdminTablePagination({
current: page, current: page,
pageSize, pageSize,
total, total,
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
showTotal: (t) => `共 ${t} 件物品`, showTotal: (t) => `共 ${t} 件物品`,
onChange: (p, ps) => { onChange: (p, ps) => {
setPage(p) setPage(p)
setPageSize(ps) setPageSize(ps)
}, },
}} })}
/> />
</Card> </Card>
@@ -26,6 +26,7 @@ import {
App, App,
} from 'antd' } from 'antd'
import PercentTable from '@/components/common/PercentTable' import PercentTable from '@/components/common/PercentTable'
import { buildAdminTablePagination } from '@/utils/admin-pagination'
import type { TableColumnsType } from 'antd' import type { TableColumnsType } from 'antd'
import { useState } from 'react' import { useState } from 'react'
@@ -474,17 +475,16 @@ export default function ConsignmentSubmissionsPanel() {
dataSource={submissions} dataSource={submissions}
loading={submissionsQuery.isLoading} loading={submissionsQuery.isLoading}
scroll={{ x: 1490 }} scroll={{ x: 1490 }}
pagination={{ pagination={buildAdminTablePagination({
current: submissionPage, current: submissionPage,
pageSize: submissionPageSize, pageSize: submissionPageSize,
total: submissionTotal, total: submissionTotal,
showSizeChanger: true,
showTotal: (t) => `共 ${t} 条提报记录`, showTotal: (t) => `共 ${t} 条提报记录`,
onChange: (p, ps) => { onChange: (p, ps) => {
setSubmissionPage(p) setSubmissionPage(p)
setSubmissionPageSize(ps) setSubmissionPageSize(ps)
}, },
}} })}
/> />
</Card> </Card>
@@ -44,7 +44,11 @@ import {
import type { KuaishouMatchSource, WorkProductMatchLog } from '@/types/worker-platform' import type { KuaishouMatchSource, WorkProductMatchLog } from '@/types/worker-platform'
import type { AdminKuaishouIndustryShopOption } from '@/types/admin' import type { AdminKuaishouIndustryShopOption } from '@/types/admin'
import { formatAdminDateTime } from '@/utils/admin-time' import { formatAdminDateTime } from '@/utils/admin-time'
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination' import {
ADMIN_DEFAULT_PAGE_SIZE,
buildAdminLocalTablePagination,
buildAdminTablePagination,
} from '@/utils/admin-pagination'
import { formatMoney } from './shared' import { formatMoney } from './shared'
export default function ProductMatchPanel() { export default function ProductMatchPanel() {
@@ -366,7 +370,7 @@ export default function ProductMatchPanel() {
loading={sourcesQuery.isLoading} loading={sourcesQuery.isLoading}
columns={sourceColumns} columns={sourceColumns}
dataSource={sources} dataSource={sources}
pagination={{ pageSize: 10, showSizeChanger: false }} pagination={buildAdminLocalTablePagination()}
size="small" size="small"
/> />
</div> </div>
@@ -14,6 +14,7 @@ import {
Tag, Tag,
} from 'antd' } from 'antd'
import PercentTable from '@/components/common/PercentTable' import PercentTable from '@/components/common/PercentTable'
import { buildAdminTablePagination } from '@/utils/admin-pagination'
import type { ColumnsType } from 'antd/es/table' import type { ColumnsType } from 'antd/es/table'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
@@ -635,16 +636,15 @@ export default function RiskControlPanel() {
dataSource={bans?.items || []} dataSource={bans?.items || []}
loading={bansQuery.isLoading} loading={bansQuery.isLoading}
locale={{ emptyText: '暂无封禁记录' }} locale={{ emptyText: '暂无封禁记录' }}
pagination={{ pagination={buildAdminTablePagination({
current: banPage, current: banPage,
pageSize: banPageSize, pageSize: banPageSize,
total: bans?.total || 0, total: bans?.total || 0,
showSizeChanger: true,
onChange: (nextPage, nextPageSize) => { onChange: (nextPage, nextPageSize) => {
setBanPage(nextPage) setBanPage(nextPage)
setBanPageSize(nextPageSize) setBanPageSize(nextPageSize)
}, },
}} })}
/> />
</Card> </Card>
@@ -690,16 +690,15 @@ export default function RiskControlPanel() {
columns={eventColumns} columns={eventColumns}
dataSource={events?.items || []} dataSource={events?.items || []}
loading={eventsQuery.isLoading} loading={eventsQuery.isLoading}
pagination={{ pagination={buildAdminTablePagination({
current: page, current: page,
pageSize, pageSize,
total: events?.total || 0, total: events?.total || 0,
showSizeChanger: true,
onChange: (nextPage, nextPageSize) => { onChange: (nextPage, nextPageSize) => {
setPage(nextPage) setPage(nextPage)
setPageSize(nextPageSize) setPageSize(nextPageSize)
}, },
}} })}
/> />
</Card> </Card>
</Space> </Space>
@@ -1697,7 +1697,7 @@ function AffiliateDashPlatformPanel() {
<PercentTable<SkuMappingRow> <PercentTable<SkuMappingRow>
rowKey={(row) => row.productNo} rowKey={(row) => row.productNo}
size="small" size="small"
pagination={{ pageSize: 10, showSizeChanger: false }} pagination={buildAdminLocalTablePagination()}
dataSource={builtinSkuRows} dataSource={builtinSkuRows}
columns={[ columns={[
{ title: '91 中文编码', dataIndex: 'productNo', key: 'productNo' }, { title: '91 中文编码', dataIndex: 'productNo', key: 'productNo' },
@@ -147,6 +147,14 @@ export function deleteChannelRule(ruleId: number) {
return apiDelete<{ deleted: boolean }>(`/api/v1/admin/fulfillment/rules/${ruleId}`) return apiDelete<{ deleted: boolean }>(`/api/v1/admin/fulfillment/rules/${ruleId}`)
} }
export function deleteChannelRules(ids: number[]) {
return apiPost<{
deletedCount: number
deletedIds: number[]
missingIds: number[]
}>('/api/v1/admin/fulfillment/rules/batch-delete', { ids })
}
export function fetchPipelineStatus() { export function fetchPipelineStatus() {
return apiGet<FinancePipelineStatus[]>('/api/v1/admin/fulfillment/pipeline/status') return apiGet<FinancePipelineStatus[]>('/api/v1/admin/fulfillment/pipeline/status')
} }