优化物品规则分页加载
This commit is contained in:
@@ -262,8 +262,11 @@ export type WorkOrderStatistics = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ProductRuleListInput = {
|
export type ProductRuleListInput = {
|
||||||
|
page?: number
|
||||||
|
pageSize?: number
|
||||||
enabled?: boolean | null
|
enabled?: boolean | null
|
||||||
keyword?: string
|
keyword?: string
|
||||||
|
categoryId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WalletLedgerListInput = {
|
export type WalletLedgerListInput = {
|
||||||
|
|||||||
@@ -548,8 +548,9 @@ export async function deleteWorkCategory(
|
|||||||
export async function listWorkProductRules({
|
export async function listWorkProductRules({
|
||||||
enabled = null,
|
enabled = null,
|
||||||
keyword = '',
|
keyword = '',
|
||||||
|
categoryId = 0,
|
||||||
}: ProductRuleListInput = {}): Promise<WorkProductRuleRow[]> {
|
}: ProductRuleListInput = {}): Promise<WorkProductRuleRow[]> {
|
||||||
const { whereClause, params } = buildWorkProductRuleWhere({ enabled, keyword })
|
const { whereClause, params } = buildWorkProductRuleWhere({ enabled, keyword, categoryId })
|
||||||
const result = await query<WorkProductRuleRow>(
|
const result = await query<WorkProductRuleRow>(
|
||||||
`${WORK_PRODUCT_RULE_SELECT}
|
`${WORK_PRODUCT_RULE_SELECT}
|
||||||
${whereClause}
|
${whereClause}
|
||||||
@@ -559,6 +560,56 @@ export async function listWorkProductRules({
|
|||||||
return result.rows
|
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> {
|
export async function getWorkProductRuleByKey(ruleKey: string): Promise<WorkProductRuleRow | null> {
|
||||||
const result = await query<WorkProductRuleRow>(
|
const result = await query<WorkProductRuleRow>(
|
||||||
`${WORK_PRODUCT_RULE_SELECT} WHERE wpr.rule_key = $1 LIMIT 1`,
|
`${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 filters: string[] = []
|
||||||
const params: unknown[] = []
|
const params: unknown[] = []
|
||||||
if (enabled !== null && enabled !== undefined) {
|
if (enabled !== null && enabled !== undefined) {
|
||||||
@@ -2818,6 +2873,10 @@ function buildWorkProductRuleWhere({ enabled = null, keyword = '' }: ProductRule
|
|||||||
OR wpr.product_name ILIKE $${params.length}
|
OR wpr.product_name ILIKE $${params.length}
|
||||||
)`)
|
)`)
|
||||||
}
|
}
|
||||||
|
if (categoryId) {
|
||||||
|
params.push(categoryId)
|
||||||
|
filters.push(`wpr.category_id = $${params.length}`)
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
|
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
|
||||||
params,
|
params,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
countWorkerActiveOrders,
|
countWorkerActiveOrders,
|
||||||
countWorkCategoryUsages,
|
countWorkCategoryUsages,
|
||||||
countWorkerLevelUsages,
|
countWorkerLevelUsages,
|
||||||
|
countWorkProductRulesByCategory,
|
||||||
countWorkOrderPendingSharingSubmissions,
|
countWorkOrderPendingSharingSubmissions,
|
||||||
countTimeoutEventsByWorkerIds,
|
countTimeoutEventsByWorkerIds,
|
||||||
createWorkOrder,
|
createWorkOrder,
|
||||||
@@ -31,6 +32,7 @@ import {
|
|||||||
listWorkOrders,
|
listWorkOrders,
|
||||||
listWorkOrderEventsByOrderId,
|
listWorkOrderEventsByOrderId,
|
||||||
listWorkProductRules,
|
listWorkProductRules,
|
||||||
|
listWorkProductRulesPage,
|
||||||
listWorkerLevels,
|
listWorkerLevels,
|
||||||
listWorkerUsers,
|
listWorkerUsers,
|
||||||
listWorkerWithdrawalAccounts,
|
listWorkerWithdrawalAccounts,
|
||||||
@@ -232,15 +234,32 @@ export async function deleteAdminWorkerLevel(levelId: number | string) {
|
|||||||
|
|
||||||
export async function listAdminWorkProductRules(query: JsonObject = {}) {
|
export async function listAdminWorkProductRules(query: JsonObject = {}) {
|
||||||
await ensureWorkerPlatformDefaults()
|
await ensureWorkerPlatformDefaults()
|
||||||
|
const page = normalizePage(query.page)
|
||||||
|
const pageSize = normalizePageSize(query.pageSize)
|
||||||
const enabledValue = String(query.enabled ?? '').trim()
|
const enabledValue = String(query.enabled ?? '').trim()
|
||||||
const enabled = enabledValue
|
const enabled = enabledValue
|
||||||
? ['true', '1', 'enabled', 'active'].includes(enabledValue.toLowerCase())
|
? ['true', '1', 'enabled', 'active'].includes(enabledValue.toLowerCase())
|
||||||
: null
|
: null
|
||||||
const items = await listWorkProductRules({
|
const keyword = String(query.keyword || '').trim()
|
||||||
enabled,
|
const categoryId = normalizeOptionalId(query.categoryId ?? query.category_id) || 0
|
||||||
keyword: String(query.keyword || '').trim(),
|
const [{ items, total }, categoryRows] = await Promise.all([
|
||||||
})
|
listWorkProductRulesPage({
|
||||||
return { items: items.map(mapWorkProductRule) }
|
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 = {}) {
|
export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
saveAdminWorkProductRule,
|
saveAdminWorkProductRule,
|
||||||
} from '@/services/admin'
|
} from '@/services/admin'
|
||||||
import type { CollectField, WorkProductRule } from '@/types/worker-platform'
|
import type { CollectField, WorkProductRule } from '@/types/worker-platform'
|
||||||
|
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||||
import { formatMoney } from './shared'
|
import { formatMoney } from './shared'
|
||||||
|
|
||||||
export default function ProductRulesPanel() {
|
export default function ProductRulesPanel() {
|
||||||
@@ -41,37 +42,40 @@ export default function ProductRulesPanel() {
|
|||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
|
|
||||||
const [editingRule, setEditingRule] = useState<WorkProductRule | null>(null)
|
const [editingRule, setEditingRule] = useState<WorkProductRule | null>(null)
|
||||||
|
const [keywordInput, setKeywordInput] = useState('')
|
||||||
const [keyword, setKeyword] = useState('')
|
const [keyword, setKeyword] = useState('')
|
||||||
const [categoryId, setCategoryId] = useState<number | undefined>()
|
const [categoryId, setCategoryId] = useState<number | undefined>()
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE)
|
||||||
|
|
||||||
const rulesQuery = useQuery({
|
const rulesQuery = useQuery({
|
||||||
queryKey: ['admin-worker-platform-product-rules'],
|
queryKey: ['admin-worker-platform-product-rules', keyword, categoryId, page, pageSize],
|
||||||
queryFn: () => fetchAdminWorkProductRules(),
|
queryFn: () =>
|
||||||
|
fetchAdminWorkProductRules({
|
||||||
|
keyword,
|
||||||
|
categoryId,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
const categoriesQuery = useQuery({
|
const categoriesQuery = useQuery({
|
||||||
queryKey: ['admin-worker-platform-categories'],
|
queryKey: ['admin-worker-platform-categories'],
|
||||||
queryFn: () => fetchAdminWorkCategories(),
|
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) => {
|
function searchRules(value = keywordInput) {
|
||||||
if (categoryId && Number(rule.categoryId || 0) !== categoryId) return false
|
setKeyword(value.trim())
|
||||||
const keywordText = keyword.trim().toLowerCase()
|
setPage(1)
|
||||||
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 resetRuleForm() {
|
function resetRuleForm() {
|
||||||
setEditingRule(null)
|
setEditingRule(null)
|
||||||
@@ -448,18 +452,7 @@ export default function ProductRulesPanel() {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label="规则标识"
|
label="规则标识"
|
||||||
name="ruleKey"
|
name="ruleKey"
|
||||||
rules={[
|
rules={[{ required: true, message: '请输入规则标识' }]}
|
||||||
{ 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()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Input placeholder="skin-training" disabled={Boolean(editingRule)} />
|
<Input placeholder="skin-training" disabled={Boolean(editingRule)} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -652,7 +645,7 @@ export default function ProductRulesPanel() {
|
|||||||
物品规则列表
|
物品规则列表
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<Tag color="blue" style={{ margin: 0 }}>
|
<Tag color="blue" style={{ margin: 0 }}>
|
||||||
{filteredRules.length} 条规则
|
{rulesPagination?.total || 0} 条规则
|
||||||
</Tag>
|
</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
@@ -662,8 +655,13 @@ export default function ProductRulesPanel() {
|
|||||||
allowClear
|
allowClear
|
||||||
placeholder="搜索规则/商品/SKU/店铺"
|
placeholder="搜索规则/商品/SKU/店铺"
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
value={keyword}
|
value={keywordInput}
|
||||||
onChange={(event) => setKeyword(event.target.value)}
|
onChange={(event) => {
|
||||||
|
const value = event.target.value
|
||||||
|
setKeywordInput(value)
|
||||||
|
if (!value) searchRules('')
|
||||||
|
}}
|
||||||
|
onSearch={searchRules}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
icon={<ReloadOutlined />}
|
icon={<ReloadOutlined />}
|
||||||
@@ -683,13 +681,14 @@ export default function ProductRulesPanel() {
|
|||||||
<div className="product-rules-category-tabs-bar">
|
<div className="product-rules-category-tabs-bar">
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={categoryId ? String(categoryId) : 'all'}
|
activeKey={categoryId ? String(categoryId) : 'all'}
|
||||||
onChange={(key) => setCategoryId(key === 'all' ? undefined : Number(key))}
|
onChange={(key) => {
|
||||||
|
setCategoryId(key === 'all' ? undefined : Number(key))
|
||||||
|
setPage(1)
|
||||||
|
}}
|
||||||
items={[
|
items={[
|
||||||
{ key: 'all', label: `全部 (${allRules.length})` },
|
{ key: 'all', label: `全部 (${rulesPagination?.total || 0})` },
|
||||||
...(categoriesQuery.data?.data.items || []).map((item) => {
|
...(categoriesQuery.data?.data.items || []).map((item) => {
|
||||||
const count = allRules.filter(
|
const count = categoryCounts.get(String(item.categoryId)) || 0
|
||||||
(r) => Number(r.categoryId || 0) === item.categoryId,
|
|
||||||
).length
|
|
||||||
return {
|
return {
|
||||||
key: String(item.categoryId),
|
key: String(item.categoryId),
|
||||||
label: `${item.name}${count ? ` (${count})` : ''}`,
|
label: `${item.name}${count ? ` (${count})` : ''}`,
|
||||||
@@ -702,9 +701,8 @@ export default function ProductRulesPanel() {
|
|||||||
<Table<WorkProductRule>
|
<Table<WorkProductRule>
|
||||||
rowKey="ruleId"
|
rowKey="ruleId"
|
||||||
loading={rulesQuery.isLoading}
|
loading={rulesQuery.isLoading}
|
||||||
dataSource={filteredRules}
|
dataSource={rules}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={false}
|
|
||||||
rowClassName={(row) =>
|
rowClassName={(row) =>
|
||||||
editingRule?.ruleId === row.ruleId ? 'product-rule-row-active' : ''
|
editingRule?.ruleId === row.ruleId ? 'product-rule-row-active' : ''
|
||||||
}
|
}
|
||||||
@@ -712,6 +710,15 @@ export default function ProductRulesPanel() {
|
|||||||
onClick: () => editRule(row),
|
onClick: () => editRule(row),
|
||||||
title: '点击编辑此规则',
|
title: '点击编辑此规则',
|
||||||
})}
|
})}
|
||||||
|
pagination={buildAdminTablePagination({
|
||||||
|
current: rulesPagination?.page || page,
|
||||||
|
pageSize: rulesPagination?.pageSize || pageSize,
|
||||||
|
total: rulesPagination?.total || 0,
|
||||||
|
onChange: (nextPage, nextPageSize) => {
|
||||||
|
setPage(nextPage)
|
||||||
|
setPageSize(nextPageSize)
|
||||||
|
},
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -78,7 +78,11 @@ export function fetchAdminWorkerUsers(params?: Record<string, unknown>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fetchAdminWorkProductRules(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: {
|
export function saveAdminWorkProductRule(payload: {
|
||||||
|
|||||||
Reference in New Issue
Block a user