优化物品规则分页加载

This commit is contained in:
yml2213
2026-08-17 16:20:54 +08:00
parent ecac7b63db
commit be7c124d65
5 changed files with 142 additions and 50 deletions
@@ -262,8 +262,11 @@ export type WorkOrderStatistics = {
}
export type ProductRuleListInput = {
page?: number
pageSize?: number
enabled?: boolean | null
keyword?: string
categoryId?: number
}
export type WalletLedgerListInput = {
@@ -548,8 +548,9 @@ export async function deleteWorkCategory(
export async function listWorkProductRules({
enabled = null,
keyword = '',
categoryId = 0,
}: ProductRuleListInput = {}): Promise<WorkProductRuleRow[]> {
const { whereClause, params } = buildWorkProductRuleWhere({ enabled, keyword })
const { whereClause, params } = buildWorkProductRuleWhere({ enabled, keyword, categoryId })
const result = await query<WorkProductRuleRow>(
`${WORK_PRODUCT_RULE_SELECT}
${whereClause}
@@ -559,6 +560,56 @@ export async function listWorkProductRules({
return result.rows
}
export async function listWorkProductRulesPage({
page = 1,
pageSize = 20,
enabled = null,
keyword = '',
categoryId = 0,
}: ProductRuleListInput): Promise<{ items: WorkProductRuleRow[]; total: number }> {
const { whereClause, params } = buildWorkProductRuleWhere({ enabled, keyword, categoryId })
const totalResult = await query<{ total: number }>(
`
SELECT COUNT(*)::int AS total
FROM work_product_rules wpr
${whereClause}
`,
params,
)
const offset = (page - 1) * pageSize
const listParams = [...params, pageSize, offset]
const result = await query<WorkProductRuleRow>(
`${WORK_PRODUCT_RULE_SELECT}
${whereClause}
ORDER BY wpr.sort_order ASC, wpr.id DESC
LIMIT $${listParams.length - 1} OFFSET $${listParams.length}`,
listParams,
)
return { items: result.rows, total: Number(totalResult.rows[0]?.total || 0) }
}
export async function countWorkProductRulesByCategory(
input: {
enabled?: boolean | null
keyword?: string
} = {},
): Promise<Array<{ categoryId: number | null; total: number }>> {
const { whereClause, params } = buildWorkProductRuleWhere(input)
const result = await query<{ category_id: number | null; total: number }>(
`
SELECT wpr.category_id, COUNT(*)::int AS total
FROM work_product_rules wpr
${whereClause}
GROUP BY wpr.category_id
`,
params,
)
return result.rows.map((row) => ({
categoryId: row.category_id ? Number(row.category_id) : null,
total: Number(row.total || 0),
}))
}
export async function getWorkProductRuleByKey(ruleKey: string): Promise<WorkProductRuleRow | null> {
const result = await query<WorkProductRuleRow>(
`${WORK_PRODUCT_RULE_SELECT} WHERE wpr.rule_key = $1 LIMIT 1`,
@@ -2803,7 +2854,11 @@ function buildWorkOrderWhere({
}
}
function buildWorkProductRuleWhere({ enabled = null, keyword = '' }: ProductRuleListInput) {
function buildWorkProductRuleWhere({
enabled = null,
keyword = '',
categoryId = 0,
}: ProductRuleListInput) {
const filters: string[] = []
const params: unknown[] = []
if (enabled !== null && enabled !== undefined) {
@@ -2818,6 +2873,10 @@ function buildWorkProductRuleWhere({ enabled = null, keyword = '' }: ProductRule
OR wpr.product_name ILIKE $${params.length}
)`)
}
if (categoryId) {
params.push(categoryId)
filters.push(`wpr.category_id = $${params.length}`)
}
return {
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
params,
@@ -9,6 +9,7 @@ import {
countWorkerActiveOrders,
countWorkCategoryUsages,
countWorkerLevelUsages,
countWorkProductRulesByCategory,
countWorkOrderPendingSharingSubmissions,
countTimeoutEventsByWorkerIds,
createWorkOrder,
@@ -31,6 +32,7 @@ import {
listWorkOrders,
listWorkOrderEventsByOrderId,
listWorkProductRules,
listWorkProductRulesPage,
listWorkerLevels,
listWorkerUsers,
listWorkerWithdrawalAccounts,
@@ -232,15 +234,32 @@ export async function deleteAdminWorkerLevel(levelId: number | string) {
export async function listAdminWorkProductRules(query: JsonObject = {}) {
await ensureWorkerPlatformDefaults()
const page = normalizePage(query.page)
const pageSize = normalizePageSize(query.pageSize)
const enabledValue = String(query.enabled ?? '').trim()
const enabled = enabledValue
? ['true', '1', 'enabled', 'active'].includes(enabledValue.toLowerCase())
: null
const items = await listWorkProductRules({
enabled,
keyword: String(query.keyword || '').trim(),
})
return { items: items.map(mapWorkProductRule) }
const keyword = String(query.keyword || '').trim()
const categoryId = normalizeOptionalId(query.categoryId ?? query.category_id) || 0
const [{ items, total }, categoryRows] = await Promise.all([
listWorkProductRulesPage({
page,
pageSize,
enabled,
keyword,
categoryId,
}),
countWorkProductRulesByCategory({ enabled, keyword }),
])
return {
items: items.map(mapWorkProductRule),
pagination: { page, pageSize, total },
categoryCounts: categoryRows.map((row) => ({
categoryId: row.categoryId,
total: row.total,
})),
}
}
export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
@@ -33,6 +33,7 @@ import {
saveAdminWorkProductRule,
} from '@/services/admin'
import type { CollectField, WorkProductRule } from '@/types/worker-platform'
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
import { formatMoney } from './shared'
export default function ProductRulesPanel() {
@@ -41,37 +42,40 @@ export default function ProductRulesPanel() {
const [form] = Form.useForm()
const [editingRule, setEditingRule] = useState<WorkProductRule | null>(null)
const [keywordInput, setKeywordInput] = useState('')
const [keyword, setKeyword] = useState('')
const [categoryId, setCategoryId] = useState<number | undefined>()
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
const rulesQuery = useQuery({
queryKey: ['admin-worker-platform-product-rules'],
queryFn: () => fetchAdminWorkProductRules(),
queryKey: ['admin-worker-platform-product-rules', keyword, categoryId, page, pageSize],
queryFn: () =>
fetchAdminWorkProductRules({
keyword,
categoryId,
page,
pageSize,
}),
})
const categoriesQuery = useQuery({
queryKey: ['admin-worker-platform-categories'],
queryFn: () => fetchAdminWorkCategories(),
})
const allRules = rulesQuery.data?.data.items || []
const rules = rulesQuery.data?.data.items || []
const rulesPagination = rulesQuery.data?.data.pagination
const categoryCounts = new Map(
(rulesQuery.data?.data.categoryCounts || []).map((item) => [
item.categoryId === null ? 'uncategorized' : String(item.categoryId),
item.total,
]),
)
const filteredRules = allRules.filter((rule) => {
if (categoryId && Number(rule.categoryId || 0) !== categoryId) return false
const keywordText = keyword.trim().toLowerCase()
if (!keywordText) return true
return [
rule.ruleKey,
rule.productName,
rule.skuCode,
rule.provider,
rule.shopId,
rule.categoryName,
].some((value) =>
String(value || '')
.toLowerCase()
.includes(keywordText),
)
})
function searchRules(value = keywordInput) {
setKeyword(value.trim())
setPage(1)
}
function resetRuleForm() {
setEditingRule(null)
@@ -448,18 +452,7 @@ export default function ProductRulesPanel() {
<Form.Item
label="规则标识"
name="ruleKey"
rules={[
{ required: true, message: '请输入规则标识' },
{
validator: (_, value) => {
const key = String(value || '').trim()
if (!editingRule && allRules.some((rule) => rule.ruleKey === key)) {
return Promise.reject(new Error('标识已存在'))
}
return Promise.resolve()
},
},
]}
rules={[{ required: true, message: '请输入规则标识' }]}
>
<Input placeholder="skin-training" disabled={Boolean(editingRule)} />
</Form.Item>
@@ -652,7 +645,7 @@ export default function ProductRulesPanel() {
</Typography.Text>
<Tag color="blue" style={{ margin: 0 }}>
{filteredRules.length}
{rulesPagination?.total || 0}
</Tag>
</Space>
}
@@ -662,8 +655,13 @@ export default function ProductRulesPanel() {
allowClear
placeholder="搜索规则/商品/SKU/店铺"
style={{ width: 200 }}
value={keyword}
onChange={(event) => setKeyword(event.target.value)}
value={keywordInput}
onChange={(event) => {
const value = event.target.value
setKeywordInput(value)
if (!value) searchRules('')
}}
onSearch={searchRules}
/>
<Button
icon={<ReloadOutlined />}
@@ -683,13 +681,14 @@ export default function ProductRulesPanel() {
<div className="product-rules-category-tabs-bar">
<Tabs
activeKey={categoryId ? String(categoryId) : 'all'}
onChange={(key) => setCategoryId(key === 'all' ? undefined : Number(key))}
onChange={(key) => {
setCategoryId(key === 'all' ? undefined : Number(key))
setPage(1)
}}
items={[
{ key: 'all', label: `全部 (${allRules.length})` },
{ key: 'all', label: `全部 (${rulesPagination?.total || 0})` },
...(categoriesQuery.data?.data.items || []).map((item) => {
const count = allRules.filter(
(r) => Number(r.categoryId || 0) === item.categoryId,
).length
const count = categoryCounts.get(String(item.categoryId)) || 0
return {
key: String(item.categoryId),
label: `${item.name}${count ? ` (${count})` : ''}`,
@@ -702,9 +701,8 @@ export default function ProductRulesPanel() {
<Table<WorkProductRule>
rowKey="ruleId"
loading={rulesQuery.isLoading}
dataSource={filteredRules}
dataSource={rules}
columns={columns}
pagination={false}
rowClassName={(row) =>
editingRule?.ruleId === row.ruleId ? 'product-rule-row-active' : ''
}
@@ -712,6 +710,15 @@ export default function ProductRulesPanel() {
onClick: () => editRule(row),
title: '点击编辑此规则',
})}
pagination={buildAdminTablePagination({
current: rulesPagination?.page || page,
pageSize: rulesPagination?.pageSize || pageSize,
total: rulesPagination?.total || 0,
onChange: (nextPage, nextPageSize) => {
setPage(nextPage)
setPageSize(nextPageSize)
},
})}
/>
</Card>
</div>
@@ -78,7 +78,11 @@ export function fetchAdminWorkerUsers(params?: Record<string, unknown>) {
}
export function fetchAdminWorkProductRules(params?: Record<string, unknown>) {
return apiGet<{ items: WorkProductRule[] }>('/api/v1/admin/worker-platform/product-rules', params)
return apiGet<{
items: WorkProductRule[]
pagination: { page: number; pageSize: number; total: number }
categoryCounts: Array<{ categoryId: number | null; total: number }>
}>('/api/v1/admin/worker-platform/product-rules', params)
}
export function saveAdminWorkProductRule(payload: {