feat:产品物料信息-添加树状结构

main
黄伟杰 2 days ago
parent 73dd73a1f4
commit d9a1b07f07

@ -13,6 +13,11 @@ export interface ProductCategoryVO {
// ERP 产品分类 API
export const ProductCategoryApi = {
// 查询产品分类分组树
getProductCategoryGroupTree: async () => {
return await request.get({ url: `/erp/product-category/group-tree` })
},
// 查询产品分类列表
getProductCategoryList: async (type?: number) => {
return await request.get({ url: `/erp/product-category/list`, params: { type } })

@ -2058,7 +2058,10 @@ export default {
validatorCategoryRequired: 'Product category id can not be empty',
validatorCategoryTypeRequired: 'Type can not be empty',
validatorUnitRequired: 'Unit id can not be empty',
validatorStatusRequired: 'Product status can not be empty'
validatorStatusRequired: 'Product status can not be empty',
categoryTree: 'Category Tree',
addCategory: 'Add Category',
refreshTree: 'Refresh'
},
AutocodeRule: {
moduleName: 'Code Rules',

@ -3209,7 +3209,10 @@ export default {
validatorCategoryRequired: '产品分类编号不能为空',
validatorCategoryTypeRequired: '类型不能为空',
validatorUnitRequired: '单位编号不能为空',
validatorStatusRequired: '产品状态不能为空'
validatorStatusRequired: '产品状态不能为空',
categoryTree: '产品分类树',
addCategory: '新增分类',
refreshTree: '刷新'
},
AutocodeRule: {
moduleName: '编码规则',

@ -92,12 +92,17 @@ const formRef = ref() // 表单 Ref
const productCategoryTree = ref() //
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
const open = async (type: string, id?: number, defaultData?: any) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
if (!id) {
//
if (defaultData) {
if (defaultData.type !== undefined) formData.value.type = defaultData.type
if (defaultData.parentId !== undefined) formData.value.parentId = defaultData.parentId
}
if (!id && !defaultData) {
const typeOptions = getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE)
if (typeOptions.length > 0) {
formData.value.type = typeOptions[0].value

@ -32,52 +32,96 @@
</el-form>
</ContentWrap>
<ContentWrap>
<el-tabs v-model="activeName" @tab-change="handleTabChange">
<el-tab-pane
v-for="dict in getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE)"
:key="dict.value"
:label="dict.label"
:name="String(dict.value)"
/>
</el-tabs>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('FactoryModeling.ProductInformation.tableBarCodeColumn')" align="center" prop="barCode"
sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableNameColumn')" align="left" prop="name"
width="220px" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStandardColumn')" align="center"
prop="standard" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCategoryColumn')" align="center"
prop="subCategoryName" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableUnitColumn')" align="center" prop="unitName"
sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStatusColumn')" align="center" prop="status"
sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCreateTimeColumn')" align="center"
prop="createTime" :formatter="dateFormatter" width="180px" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableOperateColumn')" align="center" width="150px">
<template #default="scope">
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:product:update']">
{{ t('FactoryModeling.ProductInformation.tableEditAction') }}
<div class="product-page-layout">
<!-- 左侧分类树 -->
<ContentWrap class="product-page-left">
<div class="tree-header">
<span class="tree-title">{{ t('FactoryModeling.ProductInformation.categoryTree') }}</span>
<div class="tree-header-actions">
<el-button link type="primary" size="small" @click="fetchGroupTree">
<Icon icon="ep:refresh" /> {{ t('FactoryModeling.ProductInformation.refreshTree') }}
</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:product:delete']">
{{ t('FactoryModeling.ProductInformation.tableDeleteAction') }}
<el-button link type="primary" size="small" @click="openCategoryForm('create')" v-hasPermi="['erp:product-category:create']">
<Icon icon="ep:plus" /> {{ t('FactoryModeling.ProductInformation.addCategory') }}
</el-button>
</div>
</div>
<el-tree
ref="treeRef"
:data="treeData"
:props="{ children: 'children', label: 'name' }"
node-key="nodeKey"
highlight-current
default-expand-all
@node-click="handleTreeNodeClick"
>
<template #default="{ data }">
<span class="custom-tree-node">
<span class="tree-node-label">{{ data.name }}</span>
<span class="tree-node-actions">
<el-button link type="primary" size="small" @click.stop="openCategoryForm('create', data.isType ? { type: data.type } : { type: data.type, parentId: data.id })"
v-hasPermi="['erp:product-category:create']">
<Icon icon="ep:plus" />
</el-button>
<template v-if="!data.isType">
<el-button link type="primary" size="small" @click.stop="openCategoryForm('update', data)"
v-hasPermi="['erp:product-category:update']">
<Icon icon="ep:edit" />
</el-button>
<el-button link type="danger" size="small" @click.stop="handleCategoryDelete(data)"
v-hasPermi="['erp:product-category:delete']">
<Icon icon="ep:delete" />
</el-button>
</template>
</span>
</span>
</template>
</el-table-column>
</el-table>
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</ContentWrap>
</el-tree>
</ContentWrap>
<!-- 右侧产品表格 -->
<ContentWrap class="product-page-right">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('FactoryModeling.ProductInformation.tableBarCodeColumn')" align="center" prop="barCode"
sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableNameColumn')" align="left" prop="name"
width="220px" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStandardColumn')" align="center"
prop="standard" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCategoryColumn')" align="center"
prop="subCategoryName" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableUnitColumn')" align="center" prop="unitName"
sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableStatusColumn')" align="center" prop="status"
sortable>
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column :label="t('FactoryModeling.ProductInformation.tableCreateTimeColumn')" align="center"
prop="createTime" :formatter="dateFormatter" width="180px" sortable />
<el-table-column :label="t('FactoryModeling.ProductInformation.tableOperateColumn')" align="center" width="150px">
<template #default="scope">
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:product:update']">
{{ t('FactoryModeling.ProductInformation.tableEditAction') }}
</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:product:delete']">
{{ t('FactoryModeling.ProductInformation.tableDeleteAction') }}
</el-button>
</template>
</el-table-column>
</el-table>
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</ContentWrap>
</div>
</template>
<ProductForm v-else-if="formType === 'product'" ref="formRef" @success="getList" @closed="handleFormClosed" />
<BomForm v-else-if="formType === 'bom'" ref="bomFormRef" @success="getList" @closed="handleFormClosed" />
<!-- 分类表单弹窗 -->
<ProductCategoryForm ref="categoryFormRef" @success="handleCategorySuccess" />
</div>
</template>
@ -85,7 +129,9 @@
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { ProductCategoryApi } from '@/api/erp/product/category'
import ProductForm from './ProductForm.vue'
import ProductCategoryForm from '../category/ProductCategoryForm.vue'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import BomForm from "@/views/mes/bom/BomForm.vue";
@ -111,6 +157,69 @@ const exportLoading = ref(false)
const formVisible = ref(false)
const formType = ref('')
/** 分类树数据 */
const groupTreeData = ref<any[]>([])
const treeRef = ref()
const typeDict = computed(() => getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE))
const treeData = computed(() => {
return (groupTreeData.value || []).map((group: any) => {
const dictItem = typeDict.value.find((d: any) => d.value === group.type)
const name = dictItem?.label || String(group.type)
return {
nodeKey: `type_${group.type}`,
name,
type: group.type,
isType: true,
children: (group.children || []).map((child: any) => ({
...child,
nodeKey: `cat_${child.id}`,
isType: false
}))
}
})
})
const fetchGroupTree = async () => {
const res = await ProductCategoryApi.getProductCategoryGroupTree()
groupTreeData.value = res
}
/** 树节点点击 - 筛选产品 */
const handleTreeNodeClick = (data: any) => {
if (data.isType) {
queryParams.categoryType = data.type
queryParams.categoryId = undefined
} else {
queryParams.categoryType = data.type
queryParams.categoryId = data.id
}
handleQuery()
}
/** 分类增删改 */
const categoryFormRef = ref()
const openCategoryForm = (type: string, data?: any) => {
const treeData = type === 'update' ? data : undefined
const defaultData = data && !treeData ? { ...data } : undefined
nextTick(() => {
categoryFormRef.value.open(type, treeData?.id, defaultData)
})
}
const handleCategoryDelete = async (data: any) => {
try {
await message.delConfirm()
await ProductCategoryApi.deleteProductCategory(data.id)
message.success(t('common.delSuccess'))
await fetchGroupTree()
} catch {}
}
const handleCategorySuccess = () => {
fetchGroupTree()
}
const getList = async () => {
loading.value = true
try {
@ -163,20 +272,14 @@ const handleExport = async () => {
}
onMounted(async () => {
await fetchGroupTree()
const typeOptions = getIntDictOptions(DICT_TYPE.MATERIAL_CLASSIFICATION_TYPE)
if (typeOptions.length > 0) {
activeName = String(typeOptions[0].value)
queryParams.categoryType = typeOptions[0].value
}
await getList()
})
let activeName = ''
const handleTabChange = (name: string) => {
queryParams.categoryType = name ? Number(name) : undefined
handleQuery()
}
const bomFormRef = ref()
const openBomForm = (type: string, id?: number) => {
formVisible.value = true
@ -191,3 +294,66 @@ const handleFormClosed = () => {
formType.value = ''
}
</script>
<style lang="scss" scoped>
.product-page-layout {
display: flex;
gap: 12px;
}
.product-page-left {
width: 260px;
flex: 0 0 auto;
}
.product-page-right {
flex: 1;
min-width: 0;
}
.tree-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
border-bottom: 1px solid var(--el-border-color-lighter);
margin-bottom: 4px;
.tree-title {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.tree-header-actions {
display: flex;
align-items: center;
gap: 8px;
}
}
.custom-tree-node {
display: flex;
align-items: center;
justify-content: space-between;
flex: 1;
padding-right: 8px;
.tree-node-label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tree-node-actions {
display: none;
flex-shrink: 0;
margin-left: 8px;
}
&:hover .tree-node-actions {
display: flex;
}
}
</style>

Loading…
Cancel
Save