From 359b8a83454d04535dc3c042622f46ff553d4c46 Mon Sep 17 00:00:00 2001 From: dxc Date: Fri, 6 Mar 2026 14:33:13 +0800 Subject: [PATCH] =?UTF-8?q?"feat:=201-=E5=AE=9E=E7=8E=B0=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=B1=82=E7=BA=A7=E6=A0=91=E5=BD=A2=E5=BA=93=E4=BD=8D=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD=20=20=20=20=20=202=20-=20=E9=A6=96?= =?UTF-8?q?=E9=A1=B5=E6=96=B0=E5=A2=9E=E5=BA=93=E4=BD=8D=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E5=92=8C=E6=A0=91=E5=BD=A2=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=20=20=20=20=20=203=20-=20=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20SysWarehouseLocation=20=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=92=8C=20CRUD=20API=20=20=20=20=20=204=20-=20=E6=A0=91?= =?UTF-8?q?=E5=BD=A2=E7=BB=93=E6=9E=84=E6=94=AF=E6=8C=81=E6=97=A0=E9=99=90?= =?UTF-8?q?=E5=B1=82=E7=BA=A7=EF=BC=8C=E8=87=AA=E5=8A=A8=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=20full=5Fpath=20=20=20=20=20=205=20-=20=E4=BF=AE=E5=A4=8D=20pr?= =?UTF-8?q?oduct.vue=20=E4=B8=AD=20defaultColumns=20=E6=9C=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inventory-backend/app/__init__.py | 15 +- inventory-backend/app/models/system.py | 37 ++- inventory-web/src/views/dashboard/index.vue | 223 +++++++++++++++++- .../src/views/stock/inbound/product.vue | 2 +- 4 files changed, 272 insertions(+), 5 deletions(-) diff --git a/inventory-backend/app/__init__.py b/inventory-backend/app/__init__.py index 4a273cd..fd45320 100644 --- a/inventory-backend/app/__init__.py +++ b/inventory-backend/app/__init__.py @@ -130,6 +130,19 @@ def create_app(): except ImportError as e: print(f"❌ 错误: Permission 模块导入失败 (请检查 app/api/v1/permission.py 是否存在): {e}") + # ----------------------------------------------------- + # 2.8 注册库位管理模块 (Warehouse) + # ----------------------------------------------------- + try: + from app.api.v1.warehouse import warehouse_bp + # 标准: /api/v1/warehouse/tree + app.register_blueprint(warehouse_bp, url_prefix='/api/v1/warehouse') + # 兼容: /api/warehouse/tree + app.register_blueprint(warehouse_bp, url_prefix='/api/warehouse', name='warehouse_legacy') + print("✅ Warehouse 模块注册成功") + except ImportError as e: + print(f"❌ 错误: Warehouse 模块导入失败: {e}") + # ========================================================= # 3. 预加载数据模型 # ========================================================= @@ -145,7 +158,7 @@ def create_app(): from app.models.outbound import TransOutbound # 系统与业务模型 (SysRolePermission 等在 models.system 中) - from app.models.system import SysUser, SysLog, SysMenu, SysElement, SysRolePermission + from app.models.system import SysUser, SysLog, SysMenu, SysElement, SysRolePermission, SysWarehouseLocation # 确保借还模型被加载 from app.models.transaction import TransBorrow, TransRepair, TransScrap diff --git a/inventory-backend/app/models/system.py b/inventory-backend/app/models/system.py index f0b77e5..f3ecbb4 100644 --- a/inventory-backend/app/models/system.py +++ b/inventory-backend/app/models/system.py @@ -148,4 +148,39 @@ class SysRolePermission(db.Model): id = db.Column(db.Integer, primary_key=True) role_code = db.Column(db.String(50), nullable=False) target_code = db.Column(db.String(100), nullable=False) # menu_code 或 element_code - type = db.Column(db.String(20), nullable=False) # 'menu' 或 'element' \ No newline at end of file + type = db.Column(db.String(20), nullable=False) # 'menu' 或 'element' + + +# ========================================== +# 4. 库位管理模型 +# ========================================== +class SysWarehouseLocation(db.Model): + """ + 库位字典表(支持无限层级树形结构) + 对应数据库: sys_warehouse_location + """ + __tablename__ = 'sys_warehouse_location' + + id = db.Column(db.Integer, primary_key=True) + parent_id = db.Column(db.Integer, db.ForeignKey('sys_warehouse_location.id'), nullable=True) + name = db.Column(db.String(100), nullable=False) + full_path = db.Column(db.String(500)) # 完整路径,如 "A区/货架1/第3层" + level = db.Column(db.Integer, default=0) # 层级深度,顶级为0 + is_enabled = db.Column(db.Boolean, default=True) + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) + + # 自关联 + children = db.relationship('SysWarehouseLocation', backref=db.backref('parent', remote_side=[id]), lazy='dynamic') + + def to_dict(self): + return { + 'id': self.id, + 'parent_id': self.parent_id, + 'name': self.name, + 'full_path': self.full_path, + 'level': self.level, + 'is_enabled': self.is_enabled, + 'created_at': self.created_at.isoformat() if self.created_at else None, + 'updated_at': self.updated_at.isoformat() if self.updated_at else None + } \ No newline at end of file diff --git a/inventory-web/src/views/dashboard/index.vue b/inventory-web/src/views/dashboard/index.vue index f4eacfa..ef2c4d0 100644 --- a/inventory-web/src/views/dashboard/index.vue +++ b/inventory-web/src/views/dashboard/index.vue @@ -5,6 +5,9 @@
👋 欢迎回来,{{ userStore.username }}
+ + 库位设置 + 系统运行正常 打印设置 @@ -36,6 +39,7 @@
+ @@ -59,6 +63,63 @@ + + +
+
+ + 新增顶级区域 + +
+ + + + + +
+
+ + + + + + + + + + + + + + + + +
@@ -68,9 +129,10 @@ import { useRouter } from 'vue-router' // 1. 引入 User Store import { useUserStore } from '@/stores/user' // 引入需要的图标 -import { Box, TrendCharts, ShoppingCart, Operation, Setting } from '@element-plus/icons-vue' +import { Box, TrendCharts, ShoppingCart, Operation, Setting, Location, Plus, Edit, Delete } from '@element-plus/icons-vue' import { getPrinterConfig, updatePrinterConfig } from '@/api/common/print' -import { ElMessage } from 'element-plus' +import { ElMessage, ElMessageBox } from 'element-plus' +import { getWarehouseTree, createWarehouse, updateWarehouse, deleteWarehouse } from '@/api/common/warehouse' const router = useRouter() // 2. 实例化 store @@ -132,6 +194,141 @@ const savePrinterConfig = async () => { } } +// ==================== 库位管理相关 ==================== +const warehouseDialogVisible = ref(false) +const treeRef = ref() +const treeData = ref([]) +const treeProps = { + label: 'name', + children: 'children' +} + +// 库位表单 +const locationFormVisible = ref(false) +const locationFormTitle = ref('') +const locationForm = reactive({ + id: undefined as number | undefined, + parent_id: null as number | null, + parentName: '无(顶级)', + name: '', + is_enabled: true +}) +const locationLoading = ref(false) + +// 打开库位管理弹窗 +const openWarehouseDialog = async () => { + await loadWarehouseTree() + warehouseDialogVisible.value = true +} + +// 加载库位树 +const loadWarehouseTree = async () => { + try { + const res = await getWarehouseTree() + if (res.code === 200) { + treeData.value = res.data || [] + } + } catch (e) { + console.error(e) + } +} + +// 新增顶级区域 +const handleAddTopLevel = () => { + locationForm.id = undefined + locationForm.parent_id = null + locationForm.parentName = '无(顶级)' + locationForm.name = '' + locationForm.is_enabled = true + locationFormTitle.value = '新增顶级库位' + locationFormVisible.value = true +} + +// 新增下级 +const handleAddChild = (data: any) => { + locationForm.id = undefined + locationForm.parent_id = data.id + locationForm.parentName = data.full_path || data.name + locationForm.name = '' + locationForm.is_enabled = true + locationFormTitle.value = `新增下级库位:${data.name}` + locationFormVisible.value = true +} + +// 编辑 +const handleEdit = (data: any) => { + locationForm.id = data.id + locationForm.parent_id = data.parent_id + locationForm.parentName = data.parent_id ? data.parent_full_path || '无(顶级)' : '无(顶级)' + locationForm.name = data.name + locationForm.is_enabled = data.is_enabled + locationFormTitle.value = `编辑库位:${data.name}` + locationFormVisible.value = true +} + +// 删除 +const handleDelete = async (data: any) => { + try { + await ElMessageBox.confirm( + `确定要删除库位「${data.name}」吗?${data.children && data.children.length > 0 ? '该操作将同时删除所有子库位。' : ''}`, + '警告', + { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' } + ) + + const res = await deleteWarehouse(data.id) + if (res.code === 200) { + ElMessage.success('删除成功') + await loadWarehouseTree() + } else { + ElMessage.error(res.msg || '删除失败') + } + } catch (e) { + // 用户取消 + } +} + +// 保存库位 +const saveLocation = async () => { + if (!locationForm.name.trim()) { + ElMessage.warning('请输入库位名称') + return + } + + try { + locationLoading.value = true + const payload: any = { + name: locationForm.name.trim(), + is_enabled: locationForm.is_enabled + } + + if (locationForm.parent_id) { + payload.parent_id = locationForm.parent_id + } + + let res + if (locationForm.id) { + // 编辑 + payload.id = locationForm.id + res = await updateWarehouse(payload) + } else { + // 新增 + res = await createWarehouse(payload) + } + + if (res.code === 200) { + ElMessage.success('保存成功') + locationFormVisible.value = false + await loadWarehouseTree() + } else { + ElMessage.error(res.msg || '保存失败') + } + } catch (e) { + ElMessage.error('请求异常') + } finally { + locationLoading.value = false + } +} + // 统一跳转函数 const handleNav = (path: string) => { router.push(path) @@ -197,4 +394,26 @@ const handleNav = (path: string) => { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } + +/* 库位管理弹窗样式 */ +.warehouse-dialog { + padding: 10px 0; +} +.warehouse-header { + margin-bottom: 15px; +} +.tree-node { + flex: 1; + display: flex; + align-items: center; + justify-content: space-between; + padding-right: 10px; +} +.node-label { + font-size: 14px; +} +.node-actions { + display: flex; + gap: 5px; +} diff --git a/inventory-web/src/views/stock/inbound/product.vue b/inventory-web/src/views/stock/inbound/product.vue index f6cd06e..ab16788 100644 --- a/inventory-web/src/views/stock/inbound/product.vue +++ b/inventory-web/src/views/stock/inbound/product.vue @@ -716,7 +716,7 @@ const initColumnPermissions = () => { } // 如果没有保存的配置,使用默认列 - visibleColumnProps.value = defaultColumns + visibleColumnProps.value = defaultVisibleCols } // 检查列权限(移除权限限制,始终返回 true)