接单平台等级 CRUD 完善,修复默认分类停用被重置
- 等级面板支持编辑、启用/停用切换、删除(默认等级与使用中等级受保护) - 新增等级删除接口及使用量校验 - 修复默认分类停用后被 ensureWorkerPlatformDefaults 重置为启用的问题,默认分类仅不存在时创建 - 分类操作后改为显式刷新列表
This commit is contained in:
@@ -323,6 +323,22 @@ export async function upsertWorkerLevel(input: {
|
|||||||
return result.rows[0] || null
|
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> {
|
export async function getWorkerUserByUsername(username: string): Promise<WorkerUserRow | null> {
|
||||||
const result = await query<WorkerUserRow>(
|
const result = await query<WorkerUserRow>(
|
||||||
`${WORKER_USER_SELECT} WHERE wu.username = $1 LIMIT 1`,
|
`${WORKER_USER_SELECT} WHERE wu.username = $1 LIMIT 1`,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
creditAdminWorkerWallet,
|
creditAdminWorkerWallet,
|
||||||
createAdminMockWorkOrder,
|
createAdminMockWorkOrder,
|
||||||
deleteAdminWorkCategory,
|
deleteAdminWorkCategory,
|
||||||
|
deleteAdminWorkerLevel,
|
||||||
getAdminWorkerFinanceConfig,
|
getAdminWorkerFinanceConfig,
|
||||||
getAdminWorkerPlatformSummary,
|
getAdminWorkerPlatformSummary,
|
||||||
listAdminWorkerFinanceRequests,
|
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(
|
router.get(
|
||||||
'/worker-platform/categories',
|
'/worker-platform/categories',
|
||||||
requireAdminRoles(['admin', 'operator', 'support']),
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
|
|||||||
@@ -10,14 +10,17 @@ import {
|
|||||||
countWorkerAcceptedOrders,
|
countWorkerAcceptedOrders,
|
||||||
countWorkerActiveOrders,
|
countWorkerActiveOrders,
|
||||||
countWorkCategoryUsages,
|
countWorkCategoryUsages,
|
||||||
|
countWorkerLevelUsages,
|
||||||
createWorkOrder,
|
createWorkOrder,
|
||||||
createWorkOrderEvent,
|
createWorkOrderEvent,
|
||||||
createWorkerFinanceRequest,
|
createWorkerFinanceRequest,
|
||||||
createWorkerUser,
|
createWorkerUser,
|
||||||
deleteWorkCategory,
|
deleteWorkCategory,
|
||||||
|
deleteWorkerLevel,
|
||||||
getWorkerFinanceRequestById,
|
getWorkerFinanceRequestById,
|
||||||
getWorkerFinanceRequestSummary,
|
getWorkerFinanceRequestSummary,
|
||||||
getWorkCategoryById,
|
getWorkCategoryById,
|
||||||
|
getWorkCategoryByKey,
|
||||||
getWorkerLevelById,
|
getWorkerLevelById,
|
||||||
getWorkerLevelByKey,
|
getWorkerLevelByKey,
|
||||||
getWorkerUserByInviteCode,
|
getWorkerUserByInviteCode,
|
||||||
@@ -110,13 +113,16 @@ export async function ensureWorkerPlatformDefaults() {
|
|||||||
}),
|
}),
|
||||||
now,
|
now,
|
||||||
}))
|
}))
|
||||||
const category = await upsertWorkCategory({
|
const existingCategory = await getWorkCategoryByKey(DEFAULT_CATEGORY_KEY)
|
||||||
|
const category =
|
||||||
|
existingCategory ||
|
||||||
|
(await upsertWorkCategory({
|
||||||
categoryKey: DEFAULT_CATEGORY_KEY,
|
categoryKey: DEFAULT_CATEGORY_KEY,
|
||||||
name: '默认分类',
|
name: '默认分类',
|
||||||
sortOrder: 100,
|
sortOrder: 100,
|
||||||
status: 'active',
|
status: 'active',
|
||||||
now,
|
now,
|
||||||
})
|
}))
|
||||||
return { level, category }
|
return { level, category }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -933,6 +939,31 @@ export async function saveAdminWorkerLevel(payload: JsonObject = {}) {
|
|||||||
return { level: mapWorkerLevel(level) }
|
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 = {}) {
|
export async function listAdminWorkProductRules(query: JsonObject = {}) {
|
||||||
await ensureWorkerPlatformDefaults()
|
await ensureWorkerPlatformDefaults()
|
||||||
const enabledValue = String(query.enabled ?? '').trim()
|
const enabledValue = String(query.enabled ?? '').trim()
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
createAdminMockWorkOrder,
|
createAdminMockWorkOrder,
|
||||||
creditAdminWorkerWallet,
|
creditAdminWorkerWallet,
|
||||||
deleteAdminWorkCategory,
|
deleteAdminWorkCategory,
|
||||||
|
deleteAdminWorkerLevel,
|
||||||
fetchAdminWorkerFinanceConfig,
|
fetchAdminWorkerFinanceConfig,
|
||||||
fetchAdminWorkerFinanceRequests,
|
fetchAdminWorkerFinanceRequests,
|
||||||
fetchAdminWorkCategories,
|
fetchAdminWorkCategories,
|
||||||
@@ -1043,9 +1044,7 @@ function CategoriesPanel() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function refreshCategories() {
|
async function refreshCategories() {
|
||||||
await queryClient.invalidateQueries({
|
await categoriesQuery.refetch()
|
||||||
queryKey: ['admin-worker-platform-categories'],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function startEdit(category: WorkCategory) {
|
function startEdit(category: WorkCategory) {
|
||||||
@@ -1880,6 +1879,28 @@ function LevelsPanel() {
|
|||||||
queryFn: () => fetchAdminWorkerLevels(),
|
queryFn: () => fetchAdminWorkerLevels(),
|
||||||
})
|
})
|
||||||
const [form] = Form.useForm()
|
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: {
|
async function saveLevel(values: {
|
||||||
levelKey: string
|
levelKey: string
|
||||||
@@ -1890,15 +1911,47 @@ function LevelsPanel() {
|
|||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
await saveAdminWorkerLevel(values)
|
await saveAdminWorkerLevel(values)
|
||||||
message.success('等级已保存')
|
message.success(editingLevel ? '等级已更新' : '等级已保存')
|
||||||
await queryClient.invalidateQueries({
|
cancelEdit()
|
||||||
queryKey: ['admin-worker-platform-levels'],
|
await refreshLevels()
|
||||||
})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error instanceof Error ? error.message : '保存失败')
|
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> = [
|
const columns: TableColumnsType<WorkerLevel> = [
|
||||||
{ title: '等级', dataIndex: 'name' },
|
{ title: '等级', dataIndex: 'name' },
|
||||||
{ title: '标识', dataIndex: 'levelKey' },
|
{ title: '标识', dataIndex: 'levelKey' },
|
||||||
@@ -1908,12 +1961,40 @@ function LevelsPanel() {
|
|||||||
},
|
},
|
||||||
{ title: '最大同时接单', dataIndex: 'maxActiveOrders' },
|
{ title: '最大同时接单', dataIndex: 'maxActiveOrders' },
|
||||||
{ title: '升级所需累计完成', dataIndex: 'upgradeThreshold' },
|
{ 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 (
|
return (
|
||||||
<section className="platform-panel-stack">
|
<section className="platform-panel-stack">
|
||||||
<Card title="保存等级" bordered={false}>
|
<Card
|
||||||
|
title={editingLevel ? `编辑等级:${editingLevel.name}` : '保存等级'}
|
||||||
|
bordered={false}
|
||||||
|
>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
layout="inline"
|
layout="inline"
|
||||||
@@ -1954,9 +2035,12 @@ function LevelsPanel() {
|
|||||||
<InputNumber min={0} max={100000} />
|
<InputNumber min={0} max={100000} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
|
<Space>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
|
{editingLevel ? <Button onClick={cancelEdit}>取消</Button> : null}
|
||||||
|
</Space>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -1969,6 +2053,19 @@ function LevelsPanel() {
|
|||||||
pagination={false}
|
pagination={false}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
<Modal
|
||||||
|
title="删除等级"
|
||||||
|
open={Boolean(deletingLevel)}
|
||||||
|
onOk={submitDelete}
|
||||||
|
onCancel={() => setDeletingLevel(null)}
|
||||||
|
okButtonProps={{ danger: true }}
|
||||||
|
okText="删除"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<Typography.Text>
|
||||||
|
确定删除等级「{deletingLevel?.name}」?正在使用该等级的打手将无法删除,需先调整。
|
||||||
|
</Typography.Text>
|
||||||
|
</Modal>
|
||||||
</section>
|
</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: {
|
export function saveAdminWorkerLevel(payload: {
|
||||||
levelKey: string
|
levelKey: string
|
||||||
name: string
|
name: string
|
||||||
depositFreeAmount?: number
|
depositFreeAmount?: number
|
||||||
maxActiveOrders?: number
|
maxActiveOrders?: number
|
||||||
upgradeThreshold?: number
|
upgradeThreshold?: number
|
||||||
|
status?: string
|
||||||
}) {
|
}) {
|
||||||
return apiPost<{ level: WorkerLevel }>(
|
return apiPost<{ level: WorkerLevel }>(
|
||||||
'/api/v1/admin/worker-platform/levels',
|
'/api/v1/admin/worker-platform/levels',
|
||||||
|
|||||||
Reference in New Issue
Block a user