优化资产分类管理功能,移除类型字段并新增自定义属性配置功能

This commit is contained in:
WUSIJIAN
2025-12-15 15:11:02 +08:00
parent 7bdc961d4b
commit 948bc2b791
2 changed files with 191 additions and 23 deletions

View File

@@ -1,6 +1,6 @@
<template>
<div class="assets-edit-category-container">
<el-dialog :title="isEdit ? '修改分类' : '添加分类'" v-model="isShowDialog" width="500px">
<el-dialog :title="isEdit ? '修改分类' : '添加分类'" v-model="isShowDialog" width="700px">
<el-form ref="formRef" :model="ruleForm" :rules="rules" size="default" label-width="90px">
<el-form-item label="上级分类">
<el-cascader
@@ -9,22 +9,17 @@
:props="{ checkStrictly: true, emitPath: false, value: 'id', label: 'name', children: 'children' }"
placeholder="请选择上级分类"
clearable
:disabled="isEdit || isAddChild"
class="w100"
/>
</el-form-item>
<el-form-item label="分类名称" prop="name">
<el-input v-model="ruleForm.name" placeholder="请输入分类名称" clearable />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="ruleForm.type" placeholder="请选择类型" class="w100">
<el-option label="商品" value="product" />
<el-option label="服务" value="service" />
</el-select>
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
<el-form-item label="状态">
<el-form-item label="状态" v-if="!isEdit">
<el-switch
v-model="ruleForm.status"
active-value="1"
@@ -34,6 +29,64 @@
inactive-text="隐藏"
/>
</el-form-item>
<!-- 自定义属性 -->
<el-form-item label="自定义属性">
<div class="custom-attrs-container">
<div v-for="(attr, index) in ruleForm.attrs" :key="index" class="custom-attr-item">
<el-input
v-if="attr.type !== 'select' && attr.type !== 'multi_select'"
v-model="attr.name"
placeholder="属性名称"
style="width: 120px"
/>
<el-select v-model="attr.type" placeholder="属性类型" style="width: 120px" @change="onAttrTypeChange(attr)">
<el-option label="文本" value="text" />
<el-option label="数字" value="number" />
<el-option label="日期" value="date" />
<el-option label="单选" value="select" />
<el-option label="多选" value="multi_select" />
<el-option label="布尔" value="boolean" />
<el-option label="图片" value="image" />
</el-select>
<!-- 单选/多选时显示字典类型选择 -->
<el-select
v-if="attr.type === 'select' || attr.type === 'multi_select'"
v-model="attr.dictKey"
placeholder="选择字典类型"
style="width: 120px"
:loading="dictLoading"
@change="onDictKeyChange(attr)"
>
<el-option
v-for="(item, idx) in dictTypeOptions"
:key="idx"
:label="item.name || ''"
:value="item.name || ''"
/>
</el-select>
<!-- 选择字典类型后显示字典值选择 -->
<el-select
v-if="attr.dictKey && (attr.type === 'select' || attr.type === 'multi_select')"
v-model="attr.dictValues"
multiple
placeholder="选择字典值"
style="width: 150px"
collapse-tags
collapse-tags-tooltip
:max-collapse-tags="2"
>
<el-option
v-for="(item, idx) in getDictValuesByType(attr.dictKey)"
:key="idx"
:label="item.key"
:value="item.key"
/>
</el-select>
<el-button type="danger" :icon="Delete" circle @click="removeAttr(index)" />
</div>
<el-button type="primary" :icon="Plus" @click="addAttr">添加属性</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
@@ -54,7 +107,9 @@ export default {
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { Plus, Delete } from '@element-plus/icons-vue';
import { getCategoryTree, getCategory, addCategory, updateCategory } from '/@/api/assets/category';
import { getDicts } from '/@/api/system/dict/data';
interface CategoryRow {
id: string;
@@ -66,13 +121,37 @@ interface CategoryRow {
children?: CategoryRow[] | null;
}
interface CustomAttr {
name: string;
type: string;
required?: boolean;
multiple?: boolean;
options?: string;
description?: string;
sort?: number;
dictKey?: string;
dictValues?: string[];
}
interface DictInfo {
name: string;
remark: string;
}
interface DictValue {
key: string;
value: string;
isDefault: number;
remark: string;
}
interface RuleForm {
id: string;
parentId: string;
name: string;
type: string;
sort: number;
status: string;
attrs: CustomAttr[];
}
const emit = defineEmits(['getCategoryList']);
@@ -80,21 +159,96 @@ const emit = defineEmits(['getCategoryList']);
const formRef = ref();
const isShowDialog = ref(false);
const isEdit = ref(false);
const isAddChild = ref(false);
const submitLoading = ref(false);
const categoryData = ref<CategoryRow[]>([]);
const dictTypeOptions = ref<DictInfo[]>([]);
const dictValueOptions = ref<DictValue[]>([]);
const dictLoading = ref(false);
const ruleForm = reactive<RuleForm>({
id: '',
parentId: '',
name: '',
type: 'product',
sort: 0,
status: 'enabled',
attrs: [],
});
const rules = {
name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
type: [{ required: true, message: '请选择类型', trigger: 'change' }],
};
// 获取字典类型数据
const fetchDictTypeOptions = () => {
dictLoading.value = true;
getDicts('assets')
.then((res: any) => {
console.log('字典接口返回数据:', res);
const info = res.data?.info;
let infoList: DictInfo[] = [];
// 处理info可能是对象或数组的情况
if (Array.isArray(info)) {
infoList = info;
} else if (info && typeof info === 'object') {
infoList = [info];
}
// 过滤掉name为空的项
dictTypeOptions.value = infoList.filter((item: DictInfo) => item && item.name);
// 保存字典值列表
dictValueOptions.value = res.data?.values ?? [];
console.log('字典类型选项:', dictTypeOptions.value);
console.log('字典值选项:', dictValueOptions.value);
})
.catch((err: any) => {
console.error('获取字典数据失败:', err);
dictTypeOptions.value = [];
dictValueOptions.value = [];
})
.finally(() => {
dictLoading.value = false;
});
};
// 根据字典类型获取对应的字典值
const getDictValuesByType = (dictKey: string) => {
if (!dictKey) return [];
// 暂时返回所有字典值,后续可根据实际数据结构调整过滤逻辑
// 如果需要按类型过滤,可以根据后端返回的数据结构调整
return dictValueOptions.value;
};
// 字典类型选择变化时
const onDictKeyChange = (attr: CustomAttr) => {
// 清空已选的字典值
attr.dictValues = [];
};
// 添加自定义属性
const addAttr = () => {
ruleForm.attrs.push({
name: '',
type: 'text',
dictKey: '',
});
};
// 删除自定义属性
const removeAttr = (index: number) => {
ruleForm.attrs.splice(index, 1);
};
// 属性类型变化时
const onAttrTypeChange = (attr: CustomAttr) => {
if (attr.type === 'select' || attr.type === 'multi_select') {
// 如果字典类型数据还没加载,则加载
if (dictTypeOptions.value.length === 0) {
fetchDictTypeOptions();
}
attr.dictKey = '';
} else {
attr.dictKey = undefined;
}
};
// 重置表单
@@ -102,15 +256,16 @@ const resetForm = () => {
ruleForm.id = '';
ruleForm.parentId = '';
ruleForm.name = '';
ruleForm.type = 'product';
ruleForm.sort = 0;
ruleForm.status = 'enabled';
ruleForm.attrs = [];
};
// 打开弹窗
const openDialog = (row?: CategoryRow | string, edit?: boolean) => {
resetForm();
isEdit.value = edit || false;
isAddChild.value = false;
// 获取分类树数据
getCategoryTree().then((res: any) => {
@@ -124,13 +279,18 @@ const openDialog = (row?: CategoryRow | string, edit?: boolean) => {
ruleForm.id = data.id || '';
ruleForm.parentId = data.parentId || '';
ruleForm.name = data.name || '';
ruleForm.type = data.type || 'product';
ruleForm.sort = data.sort || 0;
ruleForm.status = data.status || 'enabled';
ruleForm.attrs = data.attrs || [];
// 如果有单选/多选属性,预加载字典类型数据
if (ruleForm.attrs.some((attr: CustomAttr) => attr.type === 'select' || attr.type === 'multi_select')) {
fetchDictTypeOptions();
}
});
} else if (row && typeof row === 'string') {
// 新增子分类模式设置父级ID
ruleForm.parentId = row;
isAddChild.value = true;
}
isShowDialog.value = true;
@@ -190,4 +350,21 @@ defineExpose({
.w100 {
width: 100%;
}
.custom-attrs-container {
width: 100%;
.custom-attr-item {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
.el-button.is-circle {
:deep(.el-icon) {
margin-right: 0 !important;
}
}
}
}
</style>

View File

@@ -6,9 +6,6 @@
<el-form-item label="名称">
<el-input size="default" v-model="tableData.param.name" placeholder="请输入分类名称" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="分类">
<el-input size="default" v-model="tableData.param.type" placeholder="请输入分类" clearable style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button size="default" type="primary" @click="getCategoryList">
<el-icon><ele-Search /></el-icon>
@@ -30,13 +27,7 @@
v-loading="tableData.loading"
>
<!-- <el-table-column type="index" label="序号" width="80" /> -->
<el-table-column prop="name" label="分类名称" show-overflow-tooltip min-width="180" />
<el-table-column prop="type" label="类型" width="100">
<template #default="scope">
<el-tag v-if="scope.row.type === 'product'" type="primary">商品</el-tag>
<el-tag v-else-if="scope.row.type === 'service'" type="success">服务</el-tag>
</template>
</el-table-column>
<el-table-column prop="name" label="分类名称" show-overflow-tooltip min-width="100" />
<el-table-column prop="sort" label="排序" width="80" />
<el-table-column prop="createdAt" label="创建时间" width="180" show-overflow-tooltip />
<el-table-column prop="updatedAt" label="修改时间" width="180" show-overflow-tooltip />