feat: 支持三级分类结构 - 一级(顶级)/二级(设置部门)/三级(设置分值)

This commit is contained in:
Your Name 2026-01-05 19:53:38 +08:00
parent 6c85f4ca4d
commit 874abf35cf

View File

@ -90,20 +90,24 @@
<div class="section-header">
<h2 class="section-title">需求类型管理</h2>
<a-space>
<a-button type="primary" @click="showDemandTypeModal(null, null)">
<a-button type="primary" @click="showDemandTypeModal(null, 'level1')">
<template #icon><Icon name="plus" size="sm" /></template>
添加大分类
添加一级分类
</a-button>
<a-button @click="showDemandTypeModal(null, 'sub')" :disabled="parentDemandTypes.length === 0">
<a-button @click="showDemandTypeModal(null, 'level2')" :disabled="level1DemandTypes.length === 0">
<template #icon><Icon name="plus" size="sm" /></template>
添加子分类
添加二级分类
</a-button>
<a-button @click="showDemandTypeModal(null, 'level3')" :disabled="level2DemandTypes.length === 0">
<template #icon><Icon name="plus" size="sm" /></template>
添加三级分类
</a-button>
</a-space>
</div>
<a-alert type="info" show-icon style="margin-bottom: 16px;">
<template #message>
<span>大分类用于设置可接单部门限制,子分类继承大分类的部门设置。单价分值支持小数(如0.35)</span>
<span>支持三级分类:一级分类(顶级) → 二级分类(设置接单部门) → 三级分类(设置单价分值)。三级分类继承二级分类的部门设置。</span>
</template>
</a-alert>
@ -115,32 +119,38 @@
:pagination="{ pageSize: 20 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'parentName'">
<template v-if="column.key === 'level'">
<a-tag v-if="getDemandTypeLevel(record) === 1" color="blue">一级</a-tag>
<a-tag v-else-if="getDemandTypeLevel(record) === 2" color="green">二级</a-tag>
<a-tag v-else color="orange">三级</a-tag>
</template>
<template v-else-if="column.key === 'parentName'">
<span v-if="record.parentId">{{ record.parentName || '--' }}</span>
<a-tag v-else color="blue">大分类</a-tag>
<span v-else class="text-muted">--</span>
</template>
<template v-else-if="column.key === 'name'">
<span :style="{ paddingLeft: record.parentId ? '20px' : '0' }">
<span v-if="record.parentId">└ </span>
<span :style="{ paddingLeft: (getDemandTypeLevel(record) - 1) * 20 + 'px' }">
<span v-if="getDemandTypeLevel(record) > 1">└ </span>
{{ record.name || '--' }}
</span>
</template>
<template v-else-if="column.key === 'unitScore'">
<span v-if="record.parentId">{{ record.unitScore ?? '--' }}</span>
<span v-if="getDemandTypeLevel(record) === 3">{{ record.unitScore ?? '--' }}</span>
<span v-else class="text-muted">--</span>
</template>
<template v-else-if="column.key === 'estimatedHours'">
<span v-if="record.parentId">{{ record.estimatedHours ?? '--' }}</span>
<span v-if="getDemandTypeLevel(record) === 3">{{ record.estimatedHours ?? '--' }}</span>
<span v-else class="text-muted">--</span>
</template>
<template v-else-if="column.key === 'allowedDepts'">
<template v-if="!record.parentId">
<template v-if="getDemandTypeLevel(record) === 2">
<span v-if="record.allowedDeptIds">
{{ getDeptNamesFromIds(record.allowedDeptIds) }}
</span>
<span v-else class="text-muted">所有接单部门</span>
</template>
<span v-else class="text-muted">继承大分类</span>
<span v-else-if="getDemandTypeLevel(record) === 3" class="text-muted">继承二级</span>
<span v-else class="text-muted">--</span>
</template>
<template v-else-if="column.key === 'status'">
<a-switch
@ -152,15 +162,23 @@
<a-space>
<a-button type="link" size="small" @click="showDemandTypeModal(record)">编辑</a-button>
<a-button
v-if="!record.parentId"
v-if="getDemandTypeLevel(record) === 1"
type="link"
size="small"
@click="showDemandTypeModal(null, 'sub', record.id)"
@click="showDemandTypeModal(null, 'level2', record.id)"
>
添加子分类
添加二级
</a-button>
<a-button
v-if="getDemandTypeLevel(record) === 2"
type="link"
size="small"
@click="showDemandTypeModal(null, 'level3', record.id)"
>
添加三级
</a-button>
<a-popconfirm
:title="record.parentId ? '确定删除此子分类?' : '确定删除此大分类及其所有子分类?'"
:title="getDemandTypeLevel(record) === 1 ? '确定删除?将同时删除所有下级分类' : getDemandTypeLevel(record) === 2 ? '确定删除?将同时删除所有三级分类' : '确定删除此分类?'"
@confirm="deleteItem('demandType', record.id)"
>
<a-button type="link" danger size="small">删除</a-button>
@ -1169,15 +1187,15 @@
width="600px"
>
<a-form :model="formData.demandType" layout="vertical">
<!-- 选择所属大分类(仅子分类显示) -->
<a-form-item v-if="formData.demandType.isSubType" label="所属大分类" required>
<!-- 选择所属一级分类(二级分类显示) -->
<a-form-item v-if="formData.demandType.addLevel === 'level2'" label="所属一级分类" required>
<a-select
v-model:value="formData.demandType.parentId"
placeholder="请选择所属大分类"
placeholder="请选择所属一级分类"
:disabled="!!editingItem"
>
<a-select-option
v-for="parent in parentDemandTypes"
v-for="parent in level1DemandTypes"
:key="parent.id"
:value="parent.id"
>
@ -1186,12 +1204,29 @@
</a-select>
</a-form-item>
<!-- 选择所属二级分类(三级分类显示) -->
<a-form-item v-if="formData.demandType.addLevel === 'level3'" label="所属二级分类" required>
<a-select
v-model:value="formData.demandType.parentId"
placeholder="请选择所属二级分类"
:disabled="!!editingItem"
>
<a-select-option
v-for="parent in level2DemandTypes"
:key="parent.id"
:value="parent.id"
>
{{ getLevel2DisplayName(parent) }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="类型名称" required>
<a-input v-model:value="formData.demandType.name" placeholder="请输入类型名称" />
</a-form-item>
<!-- 单价分值(仅子分类显示) -->
<a-form-item v-if="formData.demandType.isSubType" label="单价分值" required>
<!-- 单价分值(仅三级分类显示) -->
<a-form-item v-if="formData.demandType.addLevel === 'level3'" label="单价分值" required>
<a-input-number
v-model:value="formData.demandType.unitScore"
:min="0"
@ -1202,8 +1237,8 @@
/>
</a-form-item>
<!-- 预计时长(仅子分类显示) -->
<a-form-item v-if="formData.demandType.isSubType" label="预计时长(小时)">
<!-- 预计时长(仅三级分类显示) -->
<a-form-item v-if="formData.demandType.addLevel === 'level3'" label="预计时长(小时)">
<a-input-number
v-model:value="formData.demandType.estimatedHours"
:min="0"
@ -1217,8 +1252,8 @@
<a-textarea v-model:value="formData.demandType.description" :rows="3" placeholder="请输入描述" />
</a-form-item>
<!-- 可接单部门(仅大分类显示) -->
<a-form-item v-if="!formData.demandType.isSubType" label="可接单部门">
<!-- 可接单部门(仅二级分类显示) -->
<a-form-item v-if="formData.demandType.addLevel === 'level2'" label="可接单部门">
<a-select
v-model:value="formData.demandType.departmentIds"
mode="multiple"
@ -1242,14 +1277,21 @@
</a-select>
<div class="form-tip">
<Icon name="info" size="sm" color="#6B6B6B" style="margin-right: 4px;" />
选择父级部门会自动包含所有子部门。子分类会继承大分类的部门设置。
选择父级部门会自动包含所有子部门。三级分类会继承此设置。
</div>
</a-form-item>
<!-- 子分类提示 -->
<a-alert v-if="formData.demandType.isSubType" type="info" show-icon>
<!-- 一级分类提示 -->
<a-alert v-if="formData.demandType.addLevel === 'level1'" type="info" show-icon>
<template #message>
<span>子分类将继承大分类的可接单部门设置</span>
<span>一级分类是顶级分类,用于组织二级分类</span>
</template>
</a-alert>
<!-- 三级分类提示 -->
<a-alert v-if="formData.demandType.addLevel === 'level3'" type="info" show-icon>
<template #message>
<span>三级分类将继承二级分类的可接单部门设置</span>
</template>
</a-alert>
</a-form>
@ -1950,7 +1992,7 @@ const defaultDepartmentForm = (): DepartmentForm => ({
})
const formData = reactive({
demandType: {} as Partial<DemandType> & { departmentIds?: number[], isSubType?: boolean },
demandType: {} as Partial<DemandType> & { departmentIds?: number[], addLevel?: string },
taskSubType: {} as Partial<TaskSubType>,
brand: {} as Partial<Brand>,
platform: {} as Partial<Platform>,
@ -2069,11 +2111,35 @@ const receivingDepartments = computed(() => {
return departments.value.filter(dept => dept.deptType === 'receiving')
})
// 大分类列表(parentId为null的需求类型)
const parentDemandTypes = computed(() => {
// 一级分类列表(parentId为null的需求类型)
const level1DemandTypes = computed(() => {
return demandTypes.value.filter(dt => !dt.parentId)
})
// 二级分类列表(父级是一级分类的)
const level2DemandTypes = computed(() => {
const level1Ids = level1DemandTypes.value.map(dt => dt.id)
return demandTypes.value.filter(dt => dt.parentId && level1Ids.includes(dt.parentId))
})
// 三级分类列表(父级是二级分类的)
const level3DemandTypes = computed(() => {
const level2Ids = level2DemandTypes.value.map(dt => dt.id)
return demandTypes.value.filter(dt => dt.parentId && level2Ids.includes(dt.parentId))
})
// 兼容旧代码
const parentDemandTypes = level2DemandTypes
// 获取需求类型的层级
const getDemandTypeLevel = (record: DemandType): number => {
if (!record.parentId) return 1
const parent = demandTypes.value.find(dt => dt.id === record.parentId)
if (!parent) return 1
if (!parent.parentId) return 2
return 3
}
// 扁平化的部门选项(带层级显示)
const flatDepartmentOptions = computed(() => {
const receiving = departments.value.filter(dept => dept.deptType === 'receiving')
@ -2142,9 +2208,20 @@ const getDeptNamesFromIds = (idsStr: string) => {
// 获取需求类型弹窗标题
const getDemandTypeModalTitle = () => {
if (editingItem.value) {
return formData.demandType.parentId ? '编辑子分类' : '编辑大分类'
const level = getDemandTypeLevel(editingItem.value as DemandType)
if (level === 1) return '编辑一级分类'
if (level === 2) return '编辑二级分类'
return '编辑三级分类'
}
return formData.demandType.isSubType ? '添加子分类' : '添加大分类'
if (formData.demandType.addLevel === 'level1') return '添加一级分类'
if (formData.demandType.addLevel === 'level2') return '添加二级分类'
return '添加三级分类'
}
// 获取二级分类的显示名称(包含一级分类)
const getLevel2DisplayName = (level2: DemandType) => {
const level1 = level1DemandTypes.value.find(dt => dt.id === level2.parentId)
return level1 ? `${level1.name} / ${level2.name}` : level2.name
}
const leaderOptions = computed(() =>
@ -2176,14 +2253,15 @@ const getLimitPeriodLabel = (value?: string) => {
// 表格列定义
const demandTypeColumns = [
{ title: 'ID', dataIndex: 'id', key: 'id', width: 60 },
{ title: '分类', key: 'parentName', width: 100 },
{ title: '级别', key: 'level', width: 70 },
{ title: '上级', key: 'parentName', width: 100 },
{ title: '类型名称', dataIndex: 'name', key: 'name' },
{ title: '单价分值', dataIndex: 'unitScore', key: 'unitScore', width: 100 },
{ title: '预计时长(h)', dataIndex: 'estimatedHours', key: 'estimatedHours', width: 110 },
{ title: '预计时长(h)', dataIndex: 'estimatedHours', key: 'estimatedHours', width: 100 },
{ title: '可接单部门', key: 'allowedDepts', ellipsis: true },
{ title: '排序', dataIndex: 'sortOrder', key: 'sortOrder', width: 60 },
{ title: '状态', key: 'status', width: 80 },
{ title: '操作', key: 'actions', width: 200 }
{ title: '状态', key: 'status', width: 70 },
{ title: '操作', key: 'actions', width: 220 }
]
const taskSubTypeColumns = [
@ -2535,20 +2613,20 @@ const showDemandTypeModal = async (item?: DemandType | null, type?: string | nul
const departmentIds = item.allowedDeptIds
? item.allowedDeptIds.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id))
: []
const level = getDemandTypeLevel(item)
formData.demandType = {
...item,
departmentIds,
isSubType: !!item.parentId
addLevel: level === 1 ? 'level1' : level === 2 ? 'level2' : 'level3'
}
} else {
// 新建模式
const isSubType = type === 'sub'
formData.demandType = {
sortOrder: 0,
isEnabled: 1,
departmentIds: [],
isSubType,
parentId: isSubType ? (defaultParentId || null) : null
addLevel: type || 'level1',
parentId: defaultParentId || null
}
}
@ -2627,14 +2705,16 @@ const closeModal = (type: string) => {
// 保存操作
const saveDemandType = async () => {
try {
// 子分类必须选择父级
if (formData.demandType.isSubType && !formData.demandType.parentId) {
message.error('请选择所属大分类')
const addLevel = formData.demandType.addLevel || 'level1'
// 二级、三级分类必须选择父级
if ((addLevel === 'level2' || addLevel === 'level3') && !formData.demandType.parentId) {
message.error(addLevel === 'level2' ? '请选择所属一级分类' : '请选择所属二级分类')
return
}
// 子分类必须填写单价分值
if (formData.demandType.isSubType && (formData.demandType.unitScore === null || formData.demandType.unitScore === undefined)) {
// 三级分类必须填写单价分值
if (addLevel === 'level3' && (formData.demandType.unitScore === null || formData.demandType.unitScore === undefined)) {
message.error('请输入单价分值')
return
}
@ -2642,16 +2722,16 @@ const saveDemandType = async () => {
// 构建保存数据,确保字段名正确
const saveData: Partial<DemandType> = {
name: formData.demandType.name,
// 大分类不设置单价,子分类才设置
unitScore: formData.demandType.isSubType ? formData.demandType.unitScore : 0,
estimatedHours: formData.demandType.isSubType ? formData.demandType.estimatedHours : null,
// 只有三级分类设置单价
unitScore: addLevel === 'level3' ? formData.demandType.unitScore : 0,
estimatedHours: addLevel === 'level3' ? formData.demandType.estimatedHours : null,
description: formData.demandType.description,
sortOrder: formData.demandType.sortOrder ?? 0,
isEnabled: formData.demandType.isEnabled ?? 1,
// 父级ID(子分类需要设置)
parentId: formData.demandType.isSubType ? formData.demandType.parentId : null,
// 将部门ID数组转换为逗号分隔的字符串(仅大分类需要设置)
allowedDeptIds: !formData.demandType.isSubType && formData.demandType.departmentIds?.length > 0
// 父级ID(二级、三级分类需要设置)
parentId: (addLevel === 'level2' || addLevel === 'level3') ? formData.demandType.parentId : null,
// 将部门ID数组转换为逗号分隔的字符串(仅二级分类需要设置)
allowedDeptIds: addLevel === 'level2' && formData.demandType.departmentIds?.length > 0
? formData.demandType.departmentIds.join(',')
: null
}