feat(worker-platform): 分类标识改为系统自动生成并在前端UI隐藏
This commit is contained in:
@@ -33,6 +33,66 @@ export async function listAllWorkCategories(): Promise<WorkCategoryRow[]> {
|
||||
return result.rows
|
||||
}
|
||||
|
||||
export async function createWorkCategory(input: {
|
||||
categoryKey: string
|
||||
name: string
|
||||
parentId?: number | null
|
||||
iconUrl?: string | null
|
||||
sortOrder: number
|
||||
status: string
|
||||
now: string
|
||||
}): Promise<WorkCategoryRow | null> {
|
||||
const result = await query<WorkCategoryRow>(
|
||||
`
|
||||
INSERT INTO work_categories (category_key, name, parent_id, icon_url, sort_order, status, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING *
|
||||
`,
|
||||
[
|
||||
input.categoryKey,
|
||||
input.name,
|
||||
input.parentId ?? null,
|
||||
input.iconUrl ?? null,
|
||||
input.sortOrder,
|
||||
input.status,
|
||||
input.now,
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function updateWorkCategoryById(
|
||||
id: number,
|
||||
input: {
|
||||
name: string
|
||||
parentId?: number | null
|
||||
iconUrl?: string | null
|
||||
sortOrder: number
|
||||
status: string
|
||||
now: string
|
||||
},
|
||||
): Promise<WorkCategoryRow | null> {
|
||||
const result = await query<WorkCategoryRow>(
|
||||
`
|
||||
UPDATE work_categories
|
||||
SET name = $1, parent_id = $2, icon_url = $3, sort_order = $4, status = $5, updated_at = $6
|
||||
WHERE id = $7
|
||||
RETURNING *
|
||||
`,
|
||||
[
|
||||
input.name,
|
||||
input.parentId ?? null,
|
||||
input.iconUrl ?? null,
|
||||
input.sortOrder,
|
||||
input.status,
|
||||
input.now,
|
||||
id,
|
||||
],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function upsertWorkCategory(input: {
|
||||
categoryKey: string
|
||||
name: string
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import {
|
||||
countWorkCategoryUsages,
|
||||
createWorkCategory,
|
||||
deleteWorkCategory,
|
||||
getWorkCategoryById,
|
||||
getWorkCategoryByKey,
|
||||
listAllWorkCategories,
|
||||
updateWorkCategoryById,
|
||||
upsertWorkCategory,
|
||||
} from '../../repositories/worker-platform/work-category-repo.js'
|
||||
import {
|
||||
@@ -69,12 +71,25 @@ export async function listAdminWorkCategories() {
|
||||
return { items: (await listAllWorkCategories()).map(mapWorkCategory) }
|
||||
}
|
||||
|
||||
async function generateUniqueCategoryKey(): Promise<string> {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const key = `cat_${Date.now().toString(36)}_${randomId('', 3)}`
|
||||
const existing = await getWorkCategoryByKey(key)
|
||||
if (!existing) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return `cat_${Date.now().toString(36)}_${randomId('', 6)}`
|
||||
}
|
||||
|
||||
export async function saveAdminWorkCategory(payload: JsonObject = {}) {
|
||||
const now = nowIso()
|
||||
const categoryKey = normalizeSlugKey(
|
||||
payload.categoryKey || payload.category_key || payload.name,
|
||||
DEFAULT_CATEGORY_KEY,
|
||||
)
|
||||
const rawCategoryId = payload.categoryId ?? payload.category_id ?? payload.id
|
||||
const categoryId =
|
||||
rawCategoryId !== undefined && rawCategoryId !== null && String(rawCategoryId).trim() !== ''
|
||||
? Number(rawCategoryId)
|
||||
: null
|
||||
|
||||
const name = String(payload.name || '').trim() || '默认分类'
|
||||
const rawParentId = payload.parentId ?? payload.parent_id
|
||||
const parentId =
|
||||
@@ -87,6 +102,8 @@ export async function saveAdminWorkCategory(payload: JsonObject = {}) {
|
||||
: payload.icon_url !== undefined
|
||||
? String(payload.icon_url || '').trim() || null
|
||||
: null
|
||||
const sortOrder = normalizeInteger(payload.sortOrder ?? payload.sort_order, 100)
|
||||
const status = normalizeEnabledStatus(payload.status)
|
||||
|
||||
if (parentId) {
|
||||
const parent = await getWorkCategoryById(parentId)
|
||||
@@ -96,7 +113,7 @@ export async function saveAdminWorkCategory(payload: JsonObject = {}) {
|
||||
errorCode: 'parent_category_not_found',
|
||||
})
|
||||
}
|
||||
if (parent.category_key === categoryKey) {
|
||||
if (categoryId && parent.id === categoryId) {
|
||||
throw createHttpError('上级分类不能是当前分类自身', {
|
||||
statusCode: 400,
|
||||
errorCode: 'invalid_parent_category',
|
||||
@@ -110,26 +127,80 @@ export async function saveAdminWorkCategory(payload: JsonObject = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const existing = await getWorkCategoryByKey(categoryKey)
|
||||
if (existing && parentId) {
|
||||
const usages = await countWorkCategoryUsages(existing.id)
|
||||
if (usages.childCategoryCount > 0) {
|
||||
throw createHttpError('该分类下已有子分类,无法再作为二级分类', {
|
||||
statusCode: 400,
|
||||
errorCode: 'category_has_children',
|
||||
// 编辑已有分类
|
||||
if (categoryId) {
|
||||
const existing = await getWorkCategoryById(categoryId)
|
||||
if (!existing) {
|
||||
throw createHttpError('分类不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'work_category_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
if (parentId) {
|
||||
const usages = await countWorkCategoryUsages(existing.id)
|
||||
if (usages.childCategoryCount > 0) {
|
||||
throw createHttpError('该分类下已有子分类,无法再作为二级分类', {
|
||||
statusCode: 400,
|
||||
errorCode: 'category_has_children',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const category = await updateWorkCategoryById(categoryId, {
|
||||
name,
|
||||
parentId,
|
||||
iconUrl,
|
||||
sortOrder,
|
||||
status,
|
||||
now,
|
||||
})
|
||||
if (!category) {
|
||||
throw createHttpError('分类更新失败', {
|
||||
statusCode: 500,
|
||||
errorCode: 'category_update_failed',
|
||||
})
|
||||
}
|
||||
return { category: mapWorkCategory(category) }
|
||||
}
|
||||
|
||||
// 创建新分类:系统自动生成全局唯一 categoryKey,无需用户手动填写
|
||||
let categoryKey = ''
|
||||
const providedKey = String(payload.categoryKey || payload.category_key || '').trim()
|
||||
if (providedKey) {
|
||||
const normalizedKey = normalizeSlugKey(providedKey, '')
|
||||
if (normalizedKey) {
|
||||
const existingWithKey = await getWorkCategoryByKey(normalizedKey)
|
||||
if (existingWithKey) {
|
||||
throw createHttpError('分类标识已存在,请勿重复创建', {
|
||||
statusCode: 400,
|
||||
errorCode: 'category_key_conflict',
|
||||
})
|
||||
}
|
||||
categoryKey = normalizedKey
|
||||
}
|
||||
}
|
||||
|
||||
const category = await upsertWorkCategory({
|
||||
if (!categoryKey) {
|
||||
categoryKey = await generateUniqueCategoryKey()
|
||||
}
|
||||
|
||||
const category = await createWorkCategory({
|
||||
categoryKey,
|
||||
name,
|
||||
parentId,
|
||||
iconUrl,
|
||||
sortOrder: normalizeInteger(payload.sortOrder ?? payload.sort_order, 100),
|
||||
status: normalizeEnabledStatus(payload.status),
|
||||
sortOrder,
|
||||
status,
|
||||
now,
|
||||
})
|
||||
if (!category) {
|
||||
throw createHttpError('创建分类失败', {
|
||||
statusCode: 500,
|
||||
errorCode: 'category_create_failed',
|
||||
})
|
||||
}
|
||||
|
||||
return { category: mapWorkCategory(category) }
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import { uploadFile } from '@/services/files'
|
||||
import type { WorkCategory } from '@/types/worker-platform'
|
||||
|
||||
type CategoryFormValues = {
|
||||
categoryKey: string
|
||||
name: string
|
||||
parentId?: number
|
||||
iconUrl?: string
|
||||
@@ -63,7 +62,7 @@ export default function CategoriesPanel() {
|
||||
{ value: 0, label: '无(作为一级分类)' },
|
||||
...level1Categories.map((c) => ({
|
||||
value: c.categoryId,
|
||||
label: `${c.name} (${c.categoryKey})`,
|
||||
label: c.name,
|
||||
})),
|
||||
]
|
||||
}, [rawCategories, editingCategory])
|
||||
@@ -98,7 +97,6 @@ export default function CategoriesPanel() {
|
||||
function startEdit(category: WorkCategory) {
|
||||
setEditingCategory(category)
|
||||
form.setFieldsValue({
|
||||
categoryKey: category.categoryKey,
|
||||
name: category.name,
|
||||
parentId: category.parentId || 0,
|
||||
iconUrl: category.iconUrl || '',
|
||||
@@ -129,7 +127,7 @@ export default function CategoriesPanel() {
|
||||
async function saveCategory(values: CategoryFormValues) {
|
||||
try {
|
||||
await saveAdminWorkCategory({
|
||||
categoryKey: values.categoryKey,
|
||||
categoryId: editingCategory ? editingCategory.categoryId : undefined,
|
||||
name: values.name,
|
||||
parentId: values.parentId ? Number(values.parentId) : null,
|
||||
iconUrl: values.iconUrl ? String(values.iconUrl).trim() : null,
|
||||
@@ -147,7 +145,7 @@ export default function CategoriesPanel() {
|
||||
async function toggleCategory(category: WorkCategory) {
|
||||
try {
|
||||
await saveAdminWorkCategory({
|
||||
categoryKey: category.categoryKey,
|
||||
categoryId: category.categoryId,
|
||||
name: category.name,
|
||||
parentId: category.parentId || null,
|
||||
iconUrl: category.iconUrl || null,
|
||||
@@ -219,7 +217,6 @@ export default function CategoriesPanel() {
|
||||
return <Tag color="geekblue">{parent ? parent.name : `ID: ${row.parentId}`}</Tag>
|
||||
},
|
||||
},
|
||||
{ title: '标识', dataIndex: 'categoryKey', width: 150 },
|
||||
{ title: '排序', dataIndex: 'sortOrder', width: 90 },
|
||||
{
|
||||
title: '状态',
|
||||
@@ -275,13 +272,6 @@ export default function CategoriesPanel() {
|
||||
>
|
||||
<Select options={parentOptions} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="标识"
|
||||
name="categoryKey"
|
||||
rules={[{ required: true, message: '请输入标识' }]}
|
||||
>
|
||||
<Input placeholder="如 hpjy、dtts" />
|
||||
</Form.Item>
|
||||
<Form.Item label="名称" name="name" rules={[{ required: true, message: '请输入名称' }]}>
|
||||
<Input placeholder="如 和平精英、地铁装备" />
|
||||
</Form.Item>
|
||||
|
||||
@@ -147,7 +147,8 @@ export function fetchAdminWorkCategories() {
|
||||
}
|
||||
|
||||
export function saveAdminWorkCategory(payload: {
|
||||
categoryKey: string
|
||||
categoryId?: number
|
||||
categoryKey?: string
|
||||
name: string
|
||||
parentId?: number | null
|
||||
iconUrl?: string | null
|
||||
|
||||
Reference in New Issue
Block a user