| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- <template>
- <div class="app-container">
- <el-card shadow="never">
- <!-- 搜索表单 -->
- <el-form class="-mb-10px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px">
- <TenantSelect v-model="queryParams.tenantIds" placeholder="请选择机构名称" prop="tenantIds" />
- <el-form-item>
- <el-button type="primary" @click="handleQuery">搜索</el-button>
- <el-button @click="resetQuery">重置</el-button>
- </el-form-item>
- </el-form>
- <template #header>
- <div class="card-header">
- <span>物资分类信息</span>
- <div class="right-actions">
- <el-button v-hasPermi="['warehouses:classifyMessage:add']" type="primary" @click="handleAddRoot"><Icon icon="ep:plus" class="mr-5px" />新增根节点</el-button>
- <!-- <el-button type="success" @click="openImport"><Icon icon="ep:download" class="mr-5px" />导入</el-button> -->
- </div>
- </div>
- </template>
- <div class="main-layout">
- <!-- 左侧树 -->
- <div class="left-tree">
- <el-tree
- ref="treeRef"
- :data="treeData"
- node-key="id"
- :props="treeProps"
- default-expand-all
- highlight-current
- :expand-on-click-node="false"
- @node-click="handleTreeNodeClick"
- >
- <template #default="{ node, data }">
- <span class="tree-node">
- <Icon icon="ep:folder" v-if="data.childrenList && data.childrenList.length" class="mr-5px" />
- <Icon icon="ep:document" v-else class="mr-5px" />
- <span>{{ node.label }}</span>
- </span>
- </template>
- </el-tree>
- </div>
- <!-- 右侧树表格 -->
- <div class="right-table">
- <el-table
- :data="tableData"
- row-key="id"
- border
- default-expand-all
- :tree-props="{ children: 'childrenList', hasChildren: 'hasChildren' }"
- :header-cell-style="tableHeaderColor"
- >
- <el-table-column prop="categoryName" label="类别名称" min-width="300" />
- <el-table-column label="操作" width="360" align="center">
- <template #default="{ row }">
- <el-button v-hasPermi="['warehouses:classifyMessage:edit']" link type="primary" @click="handleEdit(row)">
- <Icon icon="ep:edit" class="mr-5px" />编辑
- </el-button>
- <el-button v-hasPermi="['warehouses:classifyMessage:add']" link type="primary" @click="handleAddChild(row)">
- <Icon icon="ep:plus" class="mr-5px" />添加下级节点
- </el-button>
- <el-button v-hasPermi="['warehouses:classifyMessage:delete']" link type="danger" @click="handleDelete(row)">
- <Icon icon="ep:delete" class="mr-5px" />删除
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </div>
- <!-- 新增/编辑弹窗 -->
- <Dialog v-model="dialog.visible" :title="dialog.title" width="600px" @close="handleDialogClose">
- <el-form ref="formRef" :model="dialog.form" :rules="rules" label-width="90px">
- <el-form-item label="上级节点">
- <el-input v-model="dialog.form.parentName" disabled />
- </el-form-item>
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="分类编号" prop="categoryNumber">
- <el-input v-model="dialog.form.categoryNumber" placeholder="请输入分类编号" />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="分类名称" prop="categoryName">
- <el-input v-model="dialog.form.categoryName" placeholder="请输入分类名称" />
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- <template #footer>
- <el-button @click="dialog.visible = false">关闭</el-button>
- <el-button type="primary" :loading="dialog.loading" @click="handleSubmit">保存</el-button>
- </template>
- </Dialog>
- <!-- 导入弹窗(复用 ImportFile 组件) -->
- <Import ref="importRef" :config="importConfig" @success="handleImportSuccess" />
- </el-card>
- </div>
- </template>
- <script setup>
- import { onMounted, reactive, ref } from 'vue'
- import { ElMessage } from 'element-plus'
- import Import from '@/components/ImportFile/index.vue'
- import {
- getMaterialCategoryTree,
- createMaterialCategory,
- updateMaterialCategory,
- getMaterialCategory,
- deleteMaterialCategory
- } from '@/api/material/classifyMessage'
- import { ElMessageBox } from 'element-plus'
- import { useUserStore } from '@/store/modules/user'
- const userStore = useUserStore()
- const queryFormRef = ref()
- const queryParams = reactive({
- tenantIds: userStore.orgTenantId,
- })
- // -------------------- 左侧树 --------------------
- const treeRef = ref()
- const treeData = ref([])
- const treeProps = { children: 'childrenList', label: 'categoryName' }
- const currentNodeId = ref(null)
- const handleTreeNodeClick = (data) => {
- currentNodeId.value = data.id
- tableData.value = [JSON.parse(JSON.stringify(data))]
- }
- // -------------------- 右侧树表格 --------------------
- const tableData = ref([])
- const tableHeaderColor = ({ rowIndex }) => {
- if (rowIndex === 0) {
- return {
- backgroundColor: '#f8f8f9',
- color: '#666666',
- fontWeight: 'bold'
- }
- }
- }
- // -------------------- API:获取树 --------------------
- const getTree = async () => {
- try {
- const res = await getMaterialCategoryTree({
- tenantIds: queryParams.tenantIds?.length ? queryParams.tenantIds : undefined
- })
- const data = res?.data ?? res
- treeData.value = Array.isArray(data) ? data : []
- // 默认选中根节点
- currentNodeId.value = treeData.value?.[0]?.id ?? null
- if (currentNodeId.value) {
- treeRef.value?.setCurrentKey?.(currentNodeId.value)
- tableData.value = [JSON.parse(JSON.stringify(treeData.value[0]))]
- } else {
- tableData.value = []
- }
- } catch (e) {
- console.error(e)
- ElMessage.error(e?.message || '获取分类树失败')
- treeData.value = []
- tableData.value = []
- }
- }
- const handleQuery = () => {
- if (queryParams.tenantIds.length == 0) {
- ElMessage.error('请选择机构')
- return
- }
- getTree()
- }
- const resetQuery = () => {
- queryParams.tenantIds = userStore.orgTenantId
- getTree()
- }
- // -------------------- 弹窗:新增/编辑 --------------------
- const dialog = reactive({
- visible: false,
- mode: 'add', // add/edit
- loading: false,
- form: {
- id: null,
- superior: 0,
- parentName: '',
- categoryNumber: '',
- categoryName: ''
- },
- title: ''
- })
- const formRef = ref()
- const rules = {
- categoryNumber: [{ required: true, message: '请输入分类编号', trigger: 'blur' }],
- categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' }]
- }
- const handleDialogClose = () => {
- formRef.value?.resetFields?.()
- }
- // 通用:树查找(childrenList)
- const findNodeById = (nodes, id) => {
- if (!Array.isArray(nodes)) return null
- for (const n of nodes) {
- if (n.id === id) return n
- if (n.childrenList?.length) {
- const found = findNodeById(n.childrenList, id)
- if (found) return found
- }
- }
- return null
- }
- const openDialog = async ({ mode, parentNode, row }) => {
- // 新增/编辑:只能选择一个院区(参考 check-in-request 的判断风格)
- if (queryParams.tenantIds.length == 0 || (queryParams.tenantIds.length > 1 && !row?.tenantId)) {
- ElMessage.error(mode === 'add' ? '新增只能选择一个机构' : '编辑只能选择一个机构')
- return
- }
- dialog.mode = mode
- dialog.visible = true
- dialog.loading = false
- dialog.title = mode === 'add' ? '物资类别-新增' : '物资类别-编辑'
- if (mode === 'add') {
- dialog.form = {
- id: null,
- superior: parentNode?.id ?? 0,
- parentName: parentNode?.categoryName ?? '',
- categoryNumber: '',
- categoryName: ''
- }
- } else {
- // 编辑:拉详情再赋值
- try {
- const res = await getMaterialCategory(row.id)
- const data = res?.data ?? res
- dialog.form = {
- id: data?.id,
- superior: data?.superior ?? 0,
- parentName: findNodeById(treeData.value, data?.superior)?.categoryName || '',
- categoryNumber: data?.categoryNumber ?? '',
- categoryName: data?.categoryName ?? ''
- }
- } catch (e) {
- console.error(e)
- ElMessage.error(e?.message || '获取详情失败')
- dialog.visible = false
- }
- }
- }
- const handleAddRoot = () => {
- // 新增根节点:只能选择一个院区
- if (queryParams.tenantIds.length == 0 || queryParams.tenantIds.length > 1) {
- ElMessage.error('新增只能选择一个机构')
- return
- }
- openDialog({ mode: 'add', parentNode: { id: 0, categoryName: '根节点' } })
- }
- const handleAddChild = (row) => {
- openDialog({ mode: 'add', parentNode: row })
- }
- const handleEdit = (row) => {
- openDialog({ mode: 'edit', row })
- }
- const handleSubmit = async () => {
- await formRef.value?.validate?.()
- // 新增/编辑提交:必须选且只能选一个机构
- if (queryParams.tenantIds.length == 0 || queryParams.tenantIds.length > 1) {
- ElMessage.error('只能选择一个机构进行操作')
- return
- }
- dialog.loading = true
- try {
- const tenantId = queryParams.tenantIds[0]
- const payload = {
- id: dialog.form.id || undefined,
- tenantId,
- superior: dialog.form.superior,
- categoryNumber: dialog.form.categoryNumber,
- categoryName: dialog.form.categoryName
- }
- if (dialog.mode === 'add') {
- delete payload.id
- await createMaterialCategory(payload)
- ElMessage.success('新增成功')
- } else {
- if (!payload.id) {
- ElMessage.error('缺少 id,无法编辑')
- return
- }
- await updateMaterialCategory(payload)
- ElMessage.success('编辑成功')
- }
- dialog.visible = false
- await getTree()
- } catch (e) {
- console.error(e)
- ElMessage.error(e?.message || '保存失败')
- } finally {
- dialog.loading = false
- }
- }
- // 删除
- const handleDelete = async (row) => {
- // 删除:必须选且只能选一个机构;并且如果多选机构,要求当前行存在 tenantId
- if (queryParams.tenantIds.length == 0 || (queryParams.tenantIds.length > 1 && !row?.tenantId)) {
- ElMessage.error('删除只能选择一个机构')
- return
- }
- try {
- await ElMessageBox.confirm(
- `确定要删除分类【${row?.categoryName || '-'}】吗?删除后该分类下的所有子分类以及所属物资信息将一并删除,此操作不可恢复!`,
- '删除确认',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }
- )
- await deleteMaterialCategory(row.id)
- ElMessage.success('删除成功')
- await getTree()
- } catch (e) {
- // 用户取消不提示错误
- if (e !== 'cancel' && e !== 'close') {
- console.error(e)
- ElMessage.error(e?.message || '删除失败')
- }
- }
- }
- // -------------------- 导入 --------------------
- const importRef = ref()
- const importConfig = reactive({
- title: '物资分类信息导入',
- importUrl: '/warehouses/classify/import',
- excelTempName: '物资分类信息导入模板',
- downloadUrl: '/warehouses/classify/template',
- desc: '物资分类信息',
- failExportUrl: ''
- })
- const openImport = () => {
- importRef.value?.open?.()
- }
- const handleImportSuccess = () => {
- ElMessage.success('导入成功')
- getTree()
- }
- // -------------------- init --------------------
- onMounted(() => {
- getTree()
- })
- </script>
- <style scoped>
- .card-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .right-actions {
- display: flex;
- gap: 10px;
- }
- .main-layout {
- display: flex;
- height: calc(100vh - 240px);
- min-height: 520px;
- }
- .left-tree {
- width: 342px;
- /* border-right: 1px solid #ebeef5; */
- padding: 10px;
- overflow: auto;
- }
- .right-table {
- flex: 1;
- padding: 10px;
- overflow: auto;
- }
- .tree-node {
- display: inline-flex;
- align-items: center;
- }
- .mr-5px {
- margin-right: 5px;
- }
- </style>
|