接单平台等级 CRUD 完善,修复默认分类停用被重置

- 等级面板支持编辑、启用/停用切换、删除(默认等级与使用中等级受保护)
- 新增等级删除接口及使用量校验
- 修复默认分类停用后被 ensureWorkerPlatformDefaults 重置为启用的问题,默认分类仅不存在时创建
- 分类操作后改为显式刷新列表
This commit is contained in:
yml2213
2026-08-01 15:11:02 +08:00
parent 0369186b18
commit d9db2f5dce
5 changed files with 187 additions and 19 deletions
@@ -323,6 +323,22 @@ export async function upsertWorkerLevel(input: {
return result.rows[0] || null
}
export async function countWorkerLevelUsages(levelId: number | string): Promise<number> {
const result = await query<{ total: number }>(
'SELECT COUNT(*)::int AS total FROM worker_users WHERE level_id = $1',
[Number(levelId)],
)
return Number(result.rows[0]?.total || 0)
}
export async function deleteWorkerLevel(levelId: number | string): Promise<{ deleted: boolean }> {
const result = await query<{ id: number }>(
'DELETE FROM worker_levels WHERE id = $1 RETURNING id',
[Number(levelId)],
)
return { deleted: Boolean(result.rows[0]) }
}
export async function getWorkerUserByUsername(username: string): Promise<WorkerUserRow | null> {
const result = await query<WorkerUserRow>(
`${WORKER_USER_SELECT} WHERE wu.username = $1 LIMIT 1`,
@@ -5,6 +5,7 @@ import {
creditAdminWorkerWallet,
createAdminMockWorkOrder,
deleteAdminWorkCategory,
deleteAdminWorkerLevel,
getAdminWorkerFinanceConfig,
getAdminWorkerPlatformSummary,
listAdminWorkerFinanceRequests,
@@ -65,6 +66,22 @@ router.post(
}),
)
router.delete(
'/worker-platform/levels/:levelId',
requireAdminRoles(['admin']),
createJsonHandler((req) => deleteAdminWorkerLevel(String(req.params.levelId || '')), {
successMessage: '接单等级已删除',
errorMessage: '删除接单等级失败',
scope: '[admin/worker-platform/levels/:levelId]',
audit: (_req, data) => ({
action: 'worker_level_deleted',
targetType: 'worker_level',
targetId: String(_req.params.levelId || ''),
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
}),
}),
)
router.get(
'/worker-platform/categories',
requireAdminRoles(['admin', 'operator', 'support']),
@@ -10,14 +10,17 @@ import {
countWorkerAcceptedOrders,
countWorkerActiveOrders,
countWorkCategoryUsages,
countWorkerLevelUsages,
createWorkOrder,
createWorkOrderEvent,
createWorkerFinanceRequest,
createWorkerUser,
deleteWorkCategory,
deleteWorkerLevel,
getWorkerFinanceRequestById,
getWorkerFinanceRequestSummary,
getWorkCategoryById,
getWorkCategoryByKey,
getWorkerLevelById,
getWorkerLevelByKey,
getWorkerUserByInviteCode,
@@ -110,13 +113,16 @@ export async function ensureWorkerPlatformDefaults() {
}),
now,
}))
const category = await upsertWorkCategory({
categoryKey: DEFAULT_CATEGORY_KEY,
name: '默认分类',
sortOrder: 100,
status: 'active',
now,
})
const existingCategory = await getWorkCategoryByKey(DEFAULT_CATEGORY_KEY)
const category =
existingCategory ||
(await upsertWorkCategory({
categoryKey: DEFAULT_CATEGORY_KEY,
name: '默认分类',
sortOrder: 100,
status: 'active',
now,
}))
return { level, category }
}
@@ -933,6 +939,31 @@ export async function saveAdminWorkerLevel(payload: JsonObject = {}) {
return { level: mapWorkerLevel(level) }
}
export async function deleteAdminWorkerLevel(levelId: number | string) {
const level = await getWorkerLevelById(Number(levelId))
if (!level) {
throw createHttpError('等级不存在', {
statusCode: 404,
errorCode: 'worker_level_not_found',
})
}
if (level.level_key === DEFAULT_LEVEL_KEY) {
throw createHttpError('默认等级(VIP1)不可删除', {
statusCode: 409,
errorCode: 'worker_level_default_protected',
})
}
const usageCount = await countWorkerLevelUsages(Number(levelId))
if (usageCount > 0) {
throw createHttpError(`该等级正在被 ${usageCount} 名打手使用,请先调整打手等级后再删除`, {
statusCode: 409,
errorCode: 'worker_level_in_use',
})
}
const { deleted } = await deleteWorkerLevel(Number(levelId))
return { deleted }
}
export async function listAdminWorkProductRules(query: JsonObject = {}) {
await ensureWorkerPlatformDefaults()
const enabledValue = String(query.enabled ?? '').trim()
@@ -40,6 +40,7 @@ import {
createAdminMockWorkOrder,
creditAdminWorkerWallet,
deleteAdminWorkCategory,
deleteAdminWorkerLevel,
fetchAdminWorkerFinanceConfig,
fetchAdminWorkerFinanceRequests,
fetchAdminWorkCategories,
@@ -1043,9 +1044,7 @@ function CategoriesPanel() {
})
async function refreshCategories() {
await queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-categories'],
})
await categoriesQuery.refetch()
}
function startEdit(category: WorkCategory) {
@@ -1880,6 +1879,28 @@ function LevelsPanel() {
queryFn: () => fetchAdminWorkerLevels(),
})
const [form] = Form.useForm()
const [editingLevel, setEditingLevel] = useState<WorkerLevel | null>(null)
const [deletingLevel, setDeletingLevel] = useState<WorkerLevel | null>(null)
async function refreshLevels() {
await levelsQuery.refetch()
}
function startEdit(level: WorkerLevel) {
setEditingLevel(level)
form.setFieldsValue({
levelKey: level.levelKey,
name: level.name,
depositFreeAmount: level.depositFreeAmount,
maxActiveOrders: level.maxActiveOrders,
upgradeThreshold: level.upgradeThreshold,
})
}
function cancelEdit() {
setEditingLevel(null)
form.resetFields()
}
async function saveLevel(values: {
levelKey: string
@@ -1890,15 +1911,47 @@ function LevelsPanel() {
}) {
try {
await saveAdminWorkerLevel(values)
message.success('等级已保存')
await queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-levels'],
})
message.success(editingLevel ? '等级已更新' : '等级已保存')
cancelEdit()
await refreshLevels()
} catch (error) {
message.error(error instanceof Error ? error.message : '保存失败')
}
}
async function toggleLevel(level: WorkerLevel) {
try {
await saveAdminWorkerLevel({
levelKey: level.levelKey,
name: level.name,
depositFreeAmount: level.depositFreeAmount,
maxActiveOrders: level.maxActiveOrders,
upgradeThreshold: level.upgradeThreshold,
status: level.status === 'active' ? 'disabled' : 'active',
})
message.success(level.status === 'active' ? '等级已停用' : '等级已启用')
await refreshLevels()
} catch (error) {
message.error(error instanceof Error ? error.message : '操作失败')
}
}
async function submitDelete() {
if (!deletingLevel) return
try {
await deleteAdminWorkerLevel(deletingLevel.levelId)
message.success('等级已删除')
setDeletingLevel(null)
await refreshLevels()
await queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-workers'],
})
} catch (error) {
message.error(error instanceof Error ? error.message : '删除失败')
setDeletingLevel(null)
}
}
const columns: TableColumnsType<WorkerLevel> = [
{ title: '等级', dataIndex: 'name' },
{ title: '标识', dataIndex: 'levelKey' },
@@ -1908,12 +1961,40 @@ function LevelsPanel() {
},
{ title: '最大同时接单', dataIndex: 'maxActiveOrders' },
{ title: '升级所需累计完成', dataIndex: 'upgradeThreshold' },
{ title: '状态', dataIndex: 'status' },
{
title: '状态',
width: 110,
render: (_, row) => (
<Tag color={row.status === 'active' ? 'green' : 'default'}>
{row.status === 'active' ? '启用' : '停用'}
</Tag>
),
},
{
title: '操作',
width: 240,
render: (_, row) => (
<Space wrap>
<Button size="small" onClick={() => startEdit(row)}>
</Button>
<Button size="small" onClick={() => toggleLevel(row)}>
{row.status === 'active' ? '停用' : '启用'}
</Button>
<Button size="small" danger onClick={() => setDeletingLevel(row)}>
</Button>
</Space>
),
},
]
return (
<section className="platform-panel-stack">
<Card title="保存等级" bordered={false}>
<Card
title={editingLevel ? `编辑等级:${editingLevel.name}` : '保存等级'}
bordered={false}
>
<Form
form={form}
layout="inline"
@@ -1954,9 +2035,12 @@ function LevelsPanel() {
<InputNumber min={0} max={100000} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
</Button>
<Space>
<Button type="primary" htmlType="submit">
</Button>
{editingLevel ? <Button onClick={cancelEdit}></Button> : null}
</Space>
</Form.Item>
</Form>
</Card>
@@ -1969,6 +2053,19 @@ function LevelsPanel() {
pagination={false}
/>
</Card>
<Modal
title="删除等级"
open={Boolean(deletingLevel)}
onOk={submitDelete}
onCancel={() => setDeletingLevel(null)}
okButtonProps={{ danger: true }}
okText="删除"
cancelText="取消"
>
<Typography.Text>
{deletingLevel?.name}使
</Typography.Text>
</Modal>
</section>
)
}
@@ -49,12 +49,19 @@ export function deleteAdminWorkCategory(categoryId: number) {
)
}
export function deleteAdminWorkerLevel(levelId: number) {
return apiDelete<{ deleted: boolean }>(
`/api/v1/admin/worker-platform/levels/${levelId}`,
)
}
export function saveAdminWorkerLevel(payload: {
levelKey: string
name: string
depositFreeAmount?: number
maxActiveOrders?: number
upgradeThreshold?: number
status?: string
}) {
return apiPost<{ level: WorkerLevel }>(
'/api/v1/admin/worker-platform/levels',