Merge branch 'main' into dev_match

This commit is contained in:
2026-11-06 10:14:43 +08:00
63 changed files with 7027 additions and 954 deletions
@@ -6,6 +6,7 @@ import {
ThunderboltOutlined,
} from '@ant-design/icons'
import { useQuery } from '@tanstack/react-query'
import dayjs from 'dayjs'
import {
Alert,
App,
@@ -29,7 +30,9 @@ import {
Tooltip,
Typography,
} from 'antd'
import { AdminRangePicker } from '@/components/admin/AdminDatePicker'
import PercentTable from '@/components/common/PercentTable'
import { ADMIN_DATE_FORMAT } from '@/lib/dayjs'
import { buildAdminTablePagination } from '@/utils/admin-pagination'
import type { TableColumnsType } from 'antd'
import { useState } from 'react'
@@ -149,6 +152,7 @@ export default function ConsignmentInventoryPanel() {
const [series, setSeries] = useState<string>('')
const [diamondLevel, setDiamondLevel] = useState<number>(0)
const [allowMatching, setAllowMatching] = useState<string>('')
const [inboundRange, setInboundRange] = useState<[string, string] | null>(null)
const [keyword, setKeyword] = useState<string>('')
const [page, setPage] = useState<number>(1)
const [pageSize, setPageSize] = useState<number>(20)
@@ -206,6 +210,8 @@ export default function ConsignmentInventoryPanel() {
series,
diamondLevel,
allowMatching,
inboundRange?.[0],
inboundRange?.[1],
keyword,
page,
pageSize,
@@ -218,6 +224,8 @@ export default function ConsignmentInventoryPanel() {
series: series || undefined,
diamondLevel: diamondLevel > 0 ? diamondLevel : undefined,
allowMatching: allowMatching === 'yes' ? true : allowMatching === 'no' ? false : undefined,
dateFrom: inboundRange?.[0],
dateTo: inboundRange?.[1],
keyword: keyword || undefined,
page,
pageSize,
@@ -854,6 +862,21 @@ export default function ConsignmentInventoryPanel() {
]}
/>
<AdminRangePicker
allowClear
style={{ width: 250 }}
placeholder={['入库开始日期', '入库结束日期']}
value={inboundRange ? [dayjs(inboundRange[0]), dayjs(inboundRange[1])] : null}
onChange={(dates) => {
setInboundRange(
dates?.[0] && dates[1]
? [dates[0].format(ADMIN_DATE_FORMAT), dates[1].format(ADMIN_DATE_FORMAT)]
: null,
)
setPage(1)
}}
/>
<Input.Search
placeholder="搜索物品名/编号/收购人/店铺/卖家"
allowClear
@@ -1,7 +1,8 @@
import { ReloadOutlined, SaveOutlined } from '@ant-design/icons'
import { ReloadOutlined, SaveOutlined, SearchOutlined } from '@ant-design/icons'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import {
Button,
Input,
InputNumber,
Select,
Space,
@@ -27,6 +28,7 @@ import {
type VoucherConsumePolicy,
type VoucherConsumeProvider,
} from '@/services/admin'
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
import { formatDateTime } from '@/utils/date-time'
const PROVIDER_LABEL: Record<VoucherConsumeProvider, string> = {
@@ -90,16 +92,63 @@ const JOB_STATUS: Record<string, { color: string; label: string }> = {
cancelled: { color: 'default', label: '已取消' },
}
const PROVIDER_FILTER_OPTIONS: Array<{ value: VoucherConsumeProvider; label: string }> = [
{ value: 'kuaishou_industry', label: PROVIDER_LABEL.kuaishou_industry },
{ value: 'douyin_mashida', label: PROVIDER_LABEL.douyin_mashida },
]
const CHANNEL_FILTER_OPTIONS: Array<{ value: VoucherConsumeChannel; label: string }> = [
{ value: 'affiliate_dash', label: CHANNEL_LABEL.affiliate_dash },
{ value: 'work_order', label: CHANNEL_LABEL.work_order },
{ value: 'manual', label: CHANNEL_LABEL.manual },
{ value: 'third_party_voucher', label: CHANNEL_LABEL.third_party_voucher },
{ value: 'on_account', label: CHANNEL_LABEL.on_account },
]
const JOB_STATUS_FILTER_OPTIONS: Array<{
value: VoucherConsumeJob['status']
label: string
}> = [
{ value: 'pending', label: JOB_STATUS.pending!.label },
{ value: 'processing', label: JOB_STATUS.processing!.label },
{ value: 'succeeded', label: JOB_STATUS.succeeded!.label },
{ value: 'failed', label: JOB_STATUS.failed!.label },
{ value: 'cancelled', label: JOB_STATUS.cancelled!.label },
]
export default function VoucherConsumePolicyPanel() {
const queryClient = useQueryClient()
const [draft, setDraft] = useState<VoucherConsumePolicy | null>(null)
const [keywordDraft, setKeywordDraft] = useState('')
const [keyword, setKeyword] = useState('')
const [providerFilter, setProviderFilter] = useState<VoucherConsumeProvider | ''>('')
const [channelFilter, setChannelFilter] = useState<VoucherConsumeChannel | ''>('')
const [statusFilter, setStatusFilter] = useState<VoucherConsumeJob['status'] | ''>('')
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
const policyQuery = useQuery({
queryKey: ['voucher-consume-policy'],
queryFn: fetchVoucherConsumePolicy,
})
const jobsQuery = useQuery({
queryKey: ['voucher-consume-jobs'],
queryFn: () => fetchVoucherConsumeJobs({ page: 1, pageSize: 20 }),
queryKey: [
'voucher-consume-jobs',
keyword,
providerFilter,
channelFilter,
statusFilter,
page,
pageSize,
],
queryFn: () =>
fetchVoucherConsumeJobs({
keyword: keyword || undefined,
provider: providerFilter || undefined,
channel: channelFilter || undefined,
status: statusFilter || undefined,
page,
pageSize,
}),
refetchInterval: 10_000,
})
@@ -134,6 +183,16 @@ export default function VoucherConsumePolicyPanel() {
})
const rows = useMemo(() => buildPolicyRows(draft), [draft])
const hasJobFilters = Boolean(keyword || providerFilter || channelFilter || statusFilter)
function resetJobFilters() {
setKeywordDraft('')
setKeyword('')
setProviderFilter('')
setChannelFilter('')
setStatusFilter('')
setPage(1)
}
function updateChannel(
provider: VoucherConsumeProvider,
@@ -348,25 +407,92 @@ export default function VoucherConsumePolicyPanel() {
loading={policyQuery.isLoading}
/>
<Space className="full-width" style={{ justifyContent: 'space-between' }}>
<Space className="full-width" style={{ justifyContent: 'space-between' }} wrap>
<Typography.Title level={5} style={{ margin: 0 }}>
最近核销任务
</Typography.Title>
<Button
icon={<ReloadOutlined />}
onClick={() => void queryClient.invalidateQueries({ queryKey: ['voucher-consume-jobs'] })}
>
刷新
</Button>
<Space wrap>
<Input.Search
allowClear
prefix={<SearchOutlined />}
placeholder="任务号 / 订单号 / 平台单号"
value={keywordDraft}
onChange={(event) => {
const value = event.target.value
setKeywordDraft(value)
if (!value) {
setKeyword('')
setPage(1)
}
}}
onSearch={(value) => {
setKeyword(String(value || '').trim())
setPage(1)
}}
style={{ width: 260 }}
/>
<Select
allowClear
placeholder="全部平台"
value={providerFilter || undefined}
options={PROVIDER_FILTER_OPTIONS}
onChange={(value: VoucherConsumeProvider | undefined) => {
setProviderFilter(value || '')
setPage(1)
}}
style={{ width: 150 }}
/>
<Select
allowClear
placeholder="全部渠道"
value={channelFilter || undefined}
options={CHANNEL_FILTER_OPTIONS}
onChange={(value: VoucherConsumeChannel | undefined) => {
setChannelFilter(value || '')
setPage(1)
}}
style={{ width: 150 }}
/>
<Select
allowClear
placeholder="全部状态"
value={statusFilter || undefined}
options={JOB_STATUS_FILTER_OPTIONS}
onChange={(value: VoucherConsumeJob['status'] | undefined) => {
setStatusFilter(value || '')
setPage(1)
}}
style={{ width: 130 }}
/>
<Button disabled={!hasJobFilters} onClick={resetJobFilters}>
重置
</Button>
<Button
icon={<ReloadOutlined />}
onClick={() =>
void queryClient.invalidateQueries({ queryKey: ['voucher-consume-jobs'] })
}
>
刷新
</Button>
</Space>
</Space>
<PercentTable
rowKey="id"
size="small"
columns={jobColumns}
dataSource={jobsQuery.data?.data.items || []}
loading={jobsQuery.isLoading}
pagination={false}
scroll={{ x: 1100 }}
loading={jobsQuery.isFetching}
pagination={buildAdminTablePagination({
current: jobsQuery.data?.data.page || page,
pageSize: jobsQuery.data?.data.pageSize || pageSize,
total: jobsQuery.data?.data.total || 0,
onChange: (nextPage, nextPageSize) => {
setPage(nextPageSize !== pageSize ? 1 : nextPage)
setPageSize(nextPageSize)
},
})}
scroll={{ x: 1200 }}
/>
</Space>
)