890 lines
30 KiB
Vue
890 lines
30 KiB
Vue
<template>
|
|
<div class="app-container warehouse-page">
|
|
<el-form
|
|
v-show="showSearch"
|
|
ref="queryForm"
|
|
:model="queryParams"
|
|
size="small"
|
|
:inline="true"
|
|
label-width="80px"
|
|
>
|
|
<el-form-item label="关键字">
|
|
<el-input
|
|
v-model="queryParams.keyword"
|
|
placeholder="输入编码/名称关键字"
|
|
clearable
|
|
@keyup.enter.native="handleQuery"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="层级">
|
|
<el-select v-model="queryParams.level" placeholder="全部" clearable @change="handleQuery">
|
|
<el-option label="一级" :value="1" />
|
|
<el-option label="二级" :value="2" />
|
|
<el-option label="三级" :value="3" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<el-row :gutter="12" class="toolbar-row">
|
|
<el-col :xs="24" :sm="16" :md="16" class="toolbar-actions">
|
|
<el-button type="primary" icon="el-icon-plus" size="mini" @click="openQuickDialog()">
|
|
快速新增 1~3 级
|
|
</el-button>
|
|
<el-button type="success" icon="el-icon-plus" size="mini" @click="openCreateDialog()">
|
|
新增单级节点
|
|
</el-button>
|
|
<el-button type="warning" icon="el-icon-download" size="mini" @click="handleDownloadTemplate">
|
|
下载导入模板
|
|
</el-button>
|
|
<el-button type="danger" icon="el-icon-upload2" size="mini" @click="triggerImport">
|
|
导入
|
|
</el-button>
|
|
<el-button type="info" icon="el-icon-sort" size="mini" @click="toggleExpand">
|
|
{{ isExpandAll ? '折叠全部' : '展开全部' }}
|
|
</el-button>
|
|
<input
|
|
ref="importInput"
|
|
type="file"
|
|
accept=".xls,.xlsx"
|
|
style="display: none"
|
|
@change="handleImportChange"
|
|
/>
|
|
</el-col>
|
|
<el-col :xs="24" :sm="8" :md="8" class="toolbar-right">
|
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getTreeData"></right-toolbar>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<el-table
|
|
ref="treeTable"
|
|
v-loading="loading"
|
|
:data="filteredTreeData"
|
|
row-key="actualWarehouseId"
|
|
:default-expand-all="isExpandAll"
|
|
class="warehouse-table"
|
|
:tree-props="{ children: 'children' }"
|
|
>
|
|
<el-table-column label="层级" align="center">
|
|
<template slot-scope="scope">
|
|
<span >{{ getLevelLabel(scope.row.level) }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="编码" prop="actualWarehouseCode" show-overflow-tooltip />
|
|
<el-table-column label="名称" prop="actualWarehouseName" show-overflow-tooltip />
|
|
<el-table-column label="排序号" prop="sortNo" width="100" align="center" />
|
|
<el-table-column label="状态" width="120" align="center">
|
|
<template slot-scope="scope">
|
|
<el-tag :type="scope.row.isEnabled === 1 ? 'success' : 'info'" effect="plain">
|
|
{{ scope.row.isEnabled === 1 ? '未占用' : '已占用' }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="备注" prop="remark" show-overflow-tooltip />
|
|
<el-table-column label="二维码" width="120" align="center">
|
|
<template slot-scope="scope">
|
|
<QRCode
|
|
v-if="scope.row.level === 3"
|
|
:content="scope.row.actualWarehouseId"
|
|
:size="60"
|
|
/>
|
|
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="220" align="center">
|
|
<template slot-scope="scope">
|
|
<el-button type="text" size="mini" @click="openCreateDialog(scope.row)">新增下级</el-button>
|
|
<el-button type="text" size="mini" @click="openEditDialog(scope.row)">编辑</el-button>
|
|
<el-button type="text" size="mini" @click="handleDelete(scope.row)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<el-dialog
|
|
title="批量新增 1-3 级"
|
|
:visible.sync="quickDialog.visible"
|
|
width="860px"
|
|
append-to-body
|
|
custom-class="warehouse-dialog"
|
|
>
|
|
<el-form label-position="top" class="quick-form">
|
|
<div class="level-card">
|
|
<div class="level-card__header">
|
|
<div class="level-card__title">一级节点</div>
|
|
</div>
|
|
<el-row :gutter="12">
|
|
<el-col :span="8">
|
|
<el-form-item label="编码">
|
|
<el-input v-model="quickForm.level1.code" placeholder="请输入一级编码" clearable />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="8">
|
|
<el-form-item label="名称">
|
|
<el-input v-model="quickForm.level1.name" placeholder="请输入一级名称" clearable />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="8">
|
|
<el-form-item label="排序号">
|
|
<el-input-number v-model="quickForm.level1.sortNo" :min="0" :max="9999" controls-position="right" />
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
|
|
<div class="level-card">
|
|
<div class="level-card__header">
|
|
<div class="level-card__title">二级 / 三级节点</div>
|
|
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="addLevel2">新增二级</el-button>
|
|
</div>
|
|
|
|
<div
|
|
class="level-card__item"
|
|
v-for="(level2, index) in quickForm.level2List"
|
|
:key="`level2-${index}`"
|
|
>
|
|
<div class="level-card__item-header">
|
|
<div class="level-card__item-title">二级节点 {{ index + 1 }}</div>
|
|
<div class="level-card__item-actions">
|
|
<el-button
|
|
v-if="quickForm.level2List.length > 1"
|
|
type="text"
|
|
size="mini"
|
|
icon="el-icon-minus"
|
|
@click="removeLevel2(index)"
|
|
>移除</el-button>
|
|
<el-button
|
|
type="text"
|
|
size="mini"
|
|
icon="el-icon-plus"
|
|
@click="addLevel3(level2)"
|
|
>添加三级</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<el-row :gutter="12">
|
|
<el-col :span="8">
|
|
<el-form-item label="二级编码">
|
|
<el-input v-model="level2.code" placeholder="请输入二级编码" clearable />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="8">
|
|
<el-form-item label="二级名称">
|
|
<el-input v-model="level2.name" placeholder="请输入二级名称" clearable />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="8">
|
|
<el-form-item label="排序号">
|
|
<el-input-number v-model="level2.sortNo" :min="0" :max="9999" controls-position="right" />
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<div class="level-card__children" v-if="level2.children.length">
|
|
<div
|
|
class="level-card__child"
|
|
v-for="(child, cIndex) in level2.children"
|
|
:key="`level3-${index}-${cIndex}`"
|
|
>
|
|
<div class="level-card__child-header">
|
|
<span>三级节点 {{ index + 1 }}-{{ cIndex + 1 }}</span>
|
|
<el-button
|
|
type="text"
|
|
size="mini"
|
|
icon="el-icon-delete"
|
|
@click="removeLevel3(level2, cIndex)"
|
|
>移除</el-button>
|
|
</div>
|
|
<el-row :gutter="12">
|
|
<el-col :span="8">
|
|
<el-form-item label="三级编码">
|
|
<el-input v-model="child.code" placeholder="请输入三级编码" clearable />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="8">
|
|
<el-form-item label="三级名称">
|
|
<el-input v-model="child.name" placeholder="请输入三级名称" clearable />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="8">
|
|
<el-form-item label="排序号">
|
|
<el-input-number v-model="child.sortNo" :min="0" :max="9999" controls-position="right" />
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-form>
|
|
<div slot="footer" class="dialog-footer">
|
|
<el-button @click="quickDialog.visible = false">取 消</el-button>
|
|
<el-button type="primary" :loading="quickDialog.loading" @click="submitQuickForm">确 定</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
|
|
<el-dialog
|
|
:title="editDialog.title"
|
|
:visible.sync="editDialog.visible"
|
|
width="520px"
|
|
append-to-body
|
|
custom-class="warehouse-dialog"
|
|
>
|
|
<el-form ref="editForm" :model="editDialog.form" :rules="editDialog.rules" label-width="90px">
|
|
<el-form-item label="层级" prop="actualWarehouseType">
|
|
<el-radio-group v-model="editDialog.form.actualWarehouseType" size="small">
|
|
<el-radio-button :label="1">一级</el-radio-button>
|
|
<el-radio-button :label="2">二级</el-radio-button>
|
|
<el-radio-button :label="3">三级</el-radio-button>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
<el-form-item label="父节点">
|
|
<span>{{ editDialog.parentPath || '最高级' }}</span>
|
|
</el-form-item>
|
|
<el-form-item label="编码" prop="actualWarehouseCode">
|
|
<el-input v-model="editDialog.form.actualWarehouseCode" placeholder="请输入编码" />
|
|
</el-form-item>
|
|
<el-form-item label="名称" prop="actualWarehouseName">
|
|
<el-input v-model="editDialog.form.actualWarehouseName" placeholder="请输入名称" />
|
|
</el-form-item>
|
|
<el-form-item label="排序号" prop="sortNo">
|
|
<el-input-number v-model="editDialog.form.sortNo" :min="0" :max="9999" controls-position="right" />
|
|
</el-form-item>
|
|
<el-form-item label="状态" prop="isEnabled">
|
|
<el-radio-group v-model="editDialog.form.isEnabled">
|
|
<el-radio :label="1">启用</el-radio>
|
|
<el-radio :label="0">停用</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
<el-form-item label="备注">
|
|
<el-input v-model="editDialog.form.remark" type="textarea" rows="2" />
|
|
</el-form-item>
|
|
</el-form>
|
|
<div slot="footer" class="dialog-footer">
|
|
<el-button @click="editDialog.visible = false">取 消</el-button>
|
|
<el-button type="primary" :loading="editDialog.loading" @click="submitEditForm">确 定</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
|
|
<el-dialog
|
|
:title="createDialog.title"
|
|
:visible.sync="createDialog.visible"
|
|
width="520px"
|
|
append-to-body
|
|
custom-class="warehouse-dialog"
|
|
>
|
|
<el-form ref="createForm" :model="createDialog.form" :rules="createDialog.rules" label-width="90px">
|
|
<el-form-item label="父节点">
|
|
<template v-if="createDialog.parentPath">
|
|
<span>{{ createDialog.parentPath }}</span>
|
|
</template>
|
|
<template v-else>
|
|
<ActualWarehouseSelect
|
|
v-model="createDialog.form.parentId"
|
|
placeholder="选择父节点(可留空)"
|
|
:width="240"
|
|
@select="handleCreateParentSelect"
|
|
/>
|
|
</template>
|
|
</el-form-item>
|
|
<el-form-item label="层级" prop="actualWarehouseType">
|
|
<el-radio-group v-model="createDialog.form.actualWarehouseType" size="small" :disabled="!!createDialog.parentNode">
|
|
<el-radio-button :label="1">一级</el-radio-button>
|
|
<el-radio-button :label="2">二级</el-radio-button>
|
|
<el-radio-button :label="3">三级</el-radio-button>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
<el-form-item label="编码" prop="actualWarehouseCode">
|
|
<el-input v-model="createDialog.form.actualWarehouseCode" placeholder="请输入编码" />
|
|
</el-form-item>
|
|
<el-form-item label="名称" prop="actualWarehouseName">
|
|
<el-input v-model="createDialog.form.actualWarehouseName" placeholder="请输入名称" />
|
|
</el-form-item>
|
|
<el-form-item label="排序号" prop="sortNo">
|
|
<el-input-number v-model="createDialog.form.sortNo" :min="0" :max="9999" controls-position="right" />
|
|
</el-form-item>
|
|
<el-form-item label="状态" prop="isEnabled">
|
|
<el-radio-group v-model="createDialog.form.isEnabled">
|
|
<el-radio :label="1">启用</el-radio>
|
|
<el-radio :label="0">停用</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
<el-form-item label="备注">
|
|
<el-input v-model="createDialog.form.remark" type="textarea" rows="2" />
|
|
</el-form-item>
|
|
</el-form>
|
|
<div slot="footer" class="dialog-footer">
|
|
<el-button @click="createDialog.visible = false">取 消</el-button>
|
|
<el-button type="primary" :loading="createDialog.loading" @click="submitCreateForm">确 定</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import {
|
|
listActualWarehouseTree,
|
|
getActualWarehouse,
|
|
delActualWarehouse,
|
|
addActualWarehouse,
|
|
updateActualWarehouse,
|
|
createActualWarehouseHierarchy,
|
|
importActualWarehouse
|
|
} from "@/api/wms/actualWarehouse";
|
|
import QRCode from "../print/components/QRCode.vue";
|
|
import ActualWarehouseSelect from "@/components/KLPService/ActualWarehouseSelect";
|
|
|
|
const LEVELS = [1, 2, 3];
|
|
const LEVEL_LABELS = {
|
|
1: "一级",
|
|
2: "二级",
|
|
3: "三级"
|
|
};
|
|
|
|
export default {
|
|
name: "ActualWarehouse",
|
|
components: { QRCode, ActualWarehouseSelect },
|
|
data() {
|
|
return {
|
|
showSearch: true,
|
|
loading: false,
|
|
isExpandAll: true,
|
|
warehouseTree: [],
|
|
filteredCache: [],
|
|
nodeIndex: {},
|
|
queryParams: {
|
|
keyword: undefined,
|
|
level: undefined
|
|
},
|
|
quickDialog: {
|
|
visible: false,
|
|
loading: false
|
|
},
|
|
quickForm: this.buildQuickForm(),
|
|
editDialog: {
|
|
visible: false,
|
|
loading: false,
|
|
title: "编辑节点",
|
|
parentPath: "",
|
|
form: {
|
|
actualWarehouseId: null,
|
|
actualWarehouseType: 1,
|
|
actualWarehouseCode: "",
|
|
actualWarehouseName: "",
|
|
sortNo: 0,
|
|
isEnabled: 1,
|
|
remark: ""
|
|
},
|
|
rules: {
|
|
actualWarehouseType: [{ required: true, message: "请选择层级", trigger: "change" }],
|
|
actualWarehouseCode: [{ required: true, message: "请输入编码", trigger: "blur" }],
|
|
actualWarehouseName: [{ required: true, message: "请输入名称", trigger: "blur" }],
|
|
sortNo: [{ required: true, type: "number", message: "请输入排序号", trigger: "change" }]
|
|
}
|
|
},
|
|
createDialog: {
|
|
visible: false,
|
|
loading: false,
|
|
title: "新增节点",
|
|
parentNode: null,
|
|
parentPath: "",
|
|
form: {
|
|
parentId: 0,
|
|
actualWarehouseType: 1,
|
|
actualWarehouseCode: "",
|
|
actualWarehouseName: "",
|
|
sortNo: 0,
|
|
isEnabled: 1,
|
|
remark: ""
|
|
},
|
|
rules: {
|
|
actualWarehouseType: [{ required: true, message: "请选择层级", trigger: "change" }],
|
|
actualWarehouseCode: [{ required: true, message: "请输入编码", trigger: "blur" }],
|
|
actualWarehouseName: [{ required: true, message: "请输入名称", trigger: "blur" }],
|
|
sortNo: [{ required: true, type: "number", message: "请输入排序号", trigger: "change" }]
|
|
}
|
|
}
|
|
};
|
|
},
|
|
computed: {
|
|
filteredTreeData() {
|
|
const keyword = (this.queryParams.keyword || "").trim();
|
|
const levelFilter = this.queryParams.level;
|
|
if (!keyword && !levelFilter) {
|
|
return this.warehouseTree;
|
|
}
|
|
return this.filterTree(this.warehouseTree, keyword, levelFilter);
|
|
}
|
|
},
|
|
created() {
|
|
this.getTreeData();
|
|
},
|
|
methods: {
|
|
createLevelNode(level) {
|
|
return {
|
|
level,
|
|
code: "",
|
|
name: "",
|
|
sortNo: level * 10,
|
|
isEnabled: 1,
|
|
remark: "",
|
|
actualWarehouseId: null,
|
|
children: level === 2 ? [] : undefined
|
|
};
|
|
},
|
|
buildQuickForm() {
|
|
return {
|
|
level1: this.createLevelNode(1),
|
|
level2List: [this.createLevelNode(2)]
|
|
};
|
|
},
|
|
getTreeData() {
|
|
this.loading = true;
|
|
listActualWarehouseTree({ actualWarehouseName: this.queryParams.keyword })
|
|
.then(res => {
|
|
const list = res.data || [];
|
|
this.decorateTree(list);
|
|
})
|
|
.finally(() => {
|
|
this.loading = false;
|
|
});
|
|
},
|
|
decorateTree(list) {
|
|
this.nodeIndex = {};
|
|
const walker = (nodes, level = 1, parentId = 0, parentPath = []) => {
|
|
return (nodes || []).map(item => {
|
|
const current = {
|
|
...item,
|
|
level,
|
|
parentId: item.parentId ?? parentId,
|
|
path: [...parentPath, item.actualWarehouseName].filter(Boolean)
|
|
};
|
|
this.nodeIndex[current.actualWarehouseId] = current;
|
|
current.children = walker(item.children || [], level + 1, current.actualWarehouseId, current.path);
|
|
return current;
|
|
});
|
|
};
|
|
this.warehouseTree = walker(list);
|
|
},
|
|
filterTree(nodes, keyword, levelFilter) {
|
|
const result = [];
|
|
nodes.forEach(node => {
|
|
const matchKeyword =
|
|
!keyword ||
|
|
(node.actualWarehouseName && node.actualWarehouseName.includes(keyword)) ||
|
|
(node.actualWarehouseCode && node.actualWarehouseCode.includes(keyword));
|
|
const matchLevel = !levelFilter || node.level === levelFilter;
|
|
const filteredChildren = this.filterTree(node.children || [], keyword, levelFilter);
|
|
if ((matchKeyword && matchLevel) || filteredChildren.length) {
|
|
result.push({
|
|
...node,
|
|
children: filteredChildren.length ? filteredChildren : node.children
|
|
});
|
|
}
|
|
});
|
|
return result;
|
|
},
|
|
handleQuery() {
|
|
this.getTreeData();
|
|
},
|
|
resetQuery() {
|
|
this.queryParams.keyword = undefined;
|
|
this.queryParams.level = undefined;
|
|
this.handleQuery();
|
|
},
|
|
toggleExpand() {
|
|
this.isExpandAll = !this.isExpandAll;
|
|
this.$nextTick(() => {
|
|
this.toggleTreeRows(this.filteredTreeData, this.isExpandAll);
|
|
});
|
|
},
|
|
toggleTreeRows(nodes, expand) {
|
|
const table = this.$refs.treeTable;
|
|
if (!table || !Array.isArray(nodes)) return;
|
|
nodes.forEach(node => {
|
|
table.toggleRowExpansion(node, expand);
|
|
if (node.children && node.children.length) {
|
|
this.toggleTreeRows(node.children, expand);
|
|
}
|
|
});
|
|
},
|
|
handleDownloadTemplate() {
|
|
this.download("wms/actualWarehouse/importTemplate", {}, "实际库区导入模板.xlsx");
|
|
},
|
|
triggerImport() {
|
|
if (this.$refs.importInput) {
|
|
this.$refs.importInput.value = null;
|
|
this.$refs.importInput.click();
|
|
}
|
|
},
|
|
handleImportChange(e) {
|
|
const file = e.target.files && e.target.files[0];
|
|
if (!file) return;
|
|
const formData = new FormData();
|
|
formData.append("file", file);
|
|
const loading = this.$loading({
|
|
lock: true,
|
|
text: "导入中,请稍候...",
|
|
spinner: "el-icon-loading",
|
|
background: "rgba(0, 0, 0, 0.4)"
|
|
});
|
|
importActualWarehouse(formData)
|
|
.then(res => {
|
|
this.$modal.msgSuccess(res.msg || "导入成功");
|
|
this.getTreeData();
|
|
})
|
|
.catch(() => {
|
|
this.$modal.msgError("导入失败,请检查模板数据");
|
|
})
|
|
.finally(() => {
|
|
loading.close();
|
|
if (this.$refs.importInput) {
|
|
this.$refs.importInput.value = null;
|
|
}
|
|
});
|
|
},
|
|
getLevelLabel(level) {
|
|
return LEVEL_LABELS[level] || `第${level}级`;
|
|
},
|
|
openQuickDialog() {
|
|
this.quickDialog.visible = true;
|
|
this.quickDialog.loading = false;
|
|
this.quickForm = this.buildQuickForm();
|
|
},
|
|
addLevel2() {
|
|
this.quickForm.level2List.push(this.createLevelNode(2));
|
|
},
|
|
removeLevel2(index) {
|
|
if (this.quickForm.level2List.length === 1) return;
|
|
this.quickForm.level2List.splice(index, 1);
|
|
},
|
|
addLevel3(level2) {
|
|
if (!Array.isArray(level2.children)) {
|
|
this.$set(level2, "children", []);
|
|
}
|
|
level2.children.push(this.createLevelNode(3));
|
|
},
|
|
removeLevel3(level2, childIndex) {
|
|
level2.children.splice(childIndex, 1);
|
|
},
|
|
submitQuickForm() {
|
|
const level1Payload = this.normalizeNode(this.quickForm.level1);
|
|
if (!level1Payload) {
|
|
this.$modal.msgWarning("请先填写完整的一级编码和名称");
|
|
return;
|
|
}
|
|
const validationError = this.validateQuickForm();
|
|
if (validationError) {
|
|
this.$modal.msgWarning(validationError);
|
|
return;
|
|
}
|
|
const paths = this.buildHierarchyPaths(level1Payload);
|
|
if (!paths.length) {
|
|
this.$modal.msgWarning("请至少填写一个二级或三级节点");
|
|
return;
|
|
}
|
|
this.quickDialog.loading = true;
|
|
const runner = async () => {
|
|
for (const path of paths) {
|
|
await createActualWarehouseHierarchy({ levels: path });
|
|
}
|
|
};
|
|
runner()
|
|
.then(() => {
|
|
this.$modal.msgSuccess("已完成录入");
|
|
this.quickDialog.visible = false;
|
|
this.getTreeData();
|
|
})
|
|
.catch(() => {
|
|
this.$modal.msgError("批量新增失败,请重试");
|
|
})
|
|
.finally(() => {
|
|
this.quickDialog.loading = false;
|
|
});
|
|
},
|
|
validateQuickForm() {
|
|
const level2List = this.quickForm.level2List;
|
|
const hasSecondOrThird = level2List.some(item => {
|
|
const hasSecond = (item.code || "").trim() && (item.name || "").trim();
|
|
const hasThird = (item.children || []).some(child => (child.code || "").trim() || (child.name || "").trim());
|
|
return hasSecond || hasThird;
|
|
});
|
|
if (!hasSecondOrThird) {
|
|
return "请至少填写一个二级或三级节点";
|
|
}
|
|
for (const level2 of level2List) {
|
|
const children = level2.children || [];
|
|
const hasChildren = children.some(child => (child.code || "").trim() || (child.name || "").trim());
|
|
if (hasChildren && (!(level2.code || "").trim() || !(level2.name || "").trim())) {
|
|
return "有三级节点时,所属的二级节点必须填写完整";
|
|
}
|
|
const invalidChild = children.find(child => {
|
|
const code = (child.code || "").trim();
|
|
const name = (child.name || "").trim();
|
|
return (code && !name) || (!code && name);
|
|
});
|
|
if (invalidChild) {
|
|
return "三级节点需同时填写编码与名称";
|
|
}
|
|
if (!hasChildren && ((level2.code || "").trim() && !(level2.name || "").trim())) {
|
|
return "二级节点需同时填写编码与名称";
|
|
}
|
|
}
|
|
return "";
|
|
},
|
|
buildHierarchyPaths(level1Payload) {
|
|
const paths = [];
|
|
this.quickForm.level2List.forEach(level2 => {
|
|
const level2Payload = this.normalizeNode(level2);
|
|
const childNodes = level2.children
|
|
.map(child => this.normalizeNode(child))
|
|
.filter(Boolean);
|
|
if (childNodes.length) {
|
|
if (!level2Payload) return;
|
|
childNodes.forEach(child => {
|
|
paths.push([level1Payload, level2Payload, child]);
|
|
});
|
|
} else if (level2Payload) {
|
|
paths.push([level1Payload, level2Payload]);
|
|
}
|
|
});
|
|
if (!paths.some(path => path.length > 1)) {
|
|
paths.push([level1Payload]);
|
|
}
|
|
return paths;
|
|
},
|
|
normalizeNode(node) {
|
|
if (!node) return null;
|
|
const code = (node.code || "").trim();
|
|
const name = (node.name || "").trim();
|
|
if (!code || !name) return null;
|
|
return {
|
|
level: node.level,
|
|
actualWarehouseId: node.actualWarehouseId || null,
|
|
actualWarehouseCode: code,
|
|
actualWarehouseName: name,
|
|
actualWarehouseType: node.level,
|
|
sortNo: Number(node.sortNo) || 0,
|
|
isEnabled: node.isEnabled ?? 1,
|
|
remark: node.remark
|
|
};
|
|
},
|
|
openEditDialog(row) {
|
|
this.editDialog.visible = true;
|
|
this.editDialog.loading = false;
|
|
getActualWarehouse(row.actualWarehouseId).then(res => {
|
|
const data = res.data || {};
|
|
this.editDialog.form = {
|
|
actualWarehouseId: data.actualWarehouseId,
|
|
actualWarehouseType: data.actualWarehouseType,
|
|
actualWarehouseCode: data.actualWarehouseCode,
|
|
actualWarehouseName: data.actualWarehouseName,
|
|
sortNo: data.sortNo,
|
|
isEnabled: data.isEnabled ?? 1,
|
|
remark: data.remark
|
|
};
|
|
const chain = this.findNodeChain(data.parentId);
|
|
this.editDialog.parentPath = chain.map(item => item.actualWarehouseName).join(" / ");
|
|
});
|
|
},
|
|
openCreateDialog(parentNode) {
|
|
if (parentNode && parentNode.level >= 3) {
|
|
this.$modal.msgWarning("三级节点无法继续新增下级");
|
|
return;
|
|
}
|
|
this.createDialog.visible = true;
|
|
this.createDialog.loading = false;
|
|
this.createDialog.parentNode = parentNode || null;
|
|
this.createDialog.parentPath = "";
|
|
this.createDialog.title = parentNode
|
|
? `在【${parentNode.actualWarehouseName}】下新增`
|
|
: "新增节点";
|
|
const nextLevel = parentNode ? Math.min(parentNode.level + 1, 3) : 1;
|
|
this.createDialog.form = {
|
|
parentId: parentNode ? parentNode.actualWarehouseId : 0,
|
|
actualWarehouseType: nextLevel,
|
|
actualWarehouseCode: "",
|
|
actualWarehouseName: "",
|
|
sortNo: (parentNode?.children?.length || 0) * 10,
|
|
isEnabled: 1,
|
|
remark: ""
|
|
};
|
|
if (parentNode) {
|
|
this.createDialog.parentPath = this.findNodeChain(parentNode.actualWarehouseId)
|
|
.map(item => item.actualWarehouseName)
|
|
.join(" / ");
|
|
}
|
|
},
|
|
handleCreateParentSelect(node) {
|
|
if (!node) {
|
|
this.createDialog.form.parentId = 0;
|
|
this.createDialog.form.actualWarehouseType = 1;
|
|
this.createDialog.parentPath = "";
|
|
return;
|
|
}
|
|
if (node.level >= 3) {
|
|
this.$modal.msgWarning("三级节点无法再新增下级");
|
|
this.createDialog.form.parentId = 0;
|
|
this.createDialog.form.actualWarehouseType = 1;
|
|
return;
|
|
}
|
|
this.createDialog.form.actualWarehouseType = node.level + 1;
|
|
this.createDialog.parentPath = this.findNodeChain(node.actualWarehouseId)
|
|
.map(item => item.actualWarehouseName)
|
|
.join(" / ");
|
|
},
|
|
submitCreateForm() {
|
|
this.$refs.createForm.validate(valid => {
|
|
if (!valid) return;
|
|
const payload = { ...this.createDialog.form };
|
|
this.createDialog.loading = true;
|
|
addActualWarehouse(payload)
|
|
.then(() => {
|
|
this.$modal.msgSuccess("新增成功");
|
|
this.createDialog.visible = false;
|
|
this.getTreeData();
|
|
})
|
|
.finally(() => {
|
|
this.createDialog.loading = false;
|
|
});
|
|
});
|
|
},
|
|
submitEditForm() {
|
|
this.$refs.editForm.validate(valid => {
|
|
if (!valid) return;
|
|
this.editDialog.loading = true;
|
|
updateActualWarehouse(this.editDialog.form)
|
|
.then(() => {
|
|
this.$modal.msgSuccess("修改成功");
|
|
this.editDialog.visible = false;
|
|
this.getTreeData();
|
|
})
|
|
.finally(() => {
|
|
this.editDialog.loading = false;
|
|
});
|
|
});
|
|
},
|
|
handleDelete(row) {
|
|
this.$modal
|
|
.confirm(`确认删除【${row.actualWarehouseName}】吗?`)
|
|
.then(() => delActualWarehouse(row.actualWarehouseId))
|
|
.then(() => {
|
|
this.$modal.msgSuccess("删除成功");
|
|
this.getTreeData();
|
|
})
|
|
.catch(() => {});
|
|
},
|
|
findNodeChain(nodeId) {
|
|
if (!nodeId) return [];
|
|
const chain = [];
|
|
let current = this.nodeIndex[nodeId];
|
|
while (current) {
|
|
chain.unshift(current);
|
|
current = current.parentId ? this.nodeIndex[current.parentId] : null;
|
|
}
|
|
return chain;
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.warehouse-page {
|
|
min-height: 100%;
|
|
}
|
|
|
|
.toolbar-row {
|
|
margin-bottom: 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
.toolbar-actions .el-button + .el-button {
|
|
margin-left: 8px;
|
|
}
|
|
|
|
.toolbar-right {
|
|
text-align: right;
|
|
}
|
|
|
|
.warehouse-table {
|
|
width: 100%;
|
|
}
|
|
|
|
.warehouse-dialog ::v-deep .el-dialog__body {
|
|
max-height: 60vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.quick-form {
|
|
padding-bottom: 8px;
|
|
}
|
|
|
|
.level-card {
|
|
border: 1px solid #ebeef5;
|
|
border-radius: 6px;
|
|
padding: 12px;
|
|
margin-bottom: 12px;
|
|
background: #fff;
|
|
}
|
|
|
|
.level-card__header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.level-card__title {
|
|
font-weight: 600;
|
|
color: #303133;
|
|
}
|
|
|
|
.level-card__item {
|
|
border-top: 1px dashed #ebeef5;
|
|
padding-top: 12px;
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.level-card__item-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.level-card__item-actions .el-button + .el-button {
|
|
margin-left: 6px;
|
|
}
|
|
|
|
.level-card__children {
|
|
margin-top: 12px;
|
|
border-left: 2px solid #f2f6fc;
|
|
padding-left: 12px;
|
|
}
|
|
|
|
.level-card__child {
|
|
border: 1px dashed #dcdfe6;
|
|
border-radius: 4px;
|
|
padding: 10px;
|
|
margin-bottom: 10px;
|
|
background: #fafafa;
|
|
}
|
|
|
|
.level-card__child-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.qr-disabled {
|
|
color: #c0c4cc;
|
|
font-size: 12px;
|
|
}
|
|
</style>
|
|
|