diff --git a/inventory-backend/app/api/v1/bom.py b/inventory-backend/app/api/v1/bom.py index d174967..c83c01f 100644 --- a/inventory-backend/app/api/v1/bom.py +++ b/inventory-backend/app/api/v1/bom.py @@ -3,15 +3,61 @@ from app.services.bom_service import BomService from app.models.base import MaterialBase from app.models.bom import BomTable from app.extensions import db -from flask_jwt_extended import jwt_required +from flask_jwt_extended import jwt_required, get_jwt +from app.utils.decorators import permission_required +from app.services.auth_service import AuthService bom_bp = Blueprint('bom', __name__) +# ============================================================================== +# 辅助函数:获取当前用户的完整权限列表(基于角色查询) +# ============================================================================== +def get_current_user_permissions(): + """ + 返回当前用户拥有的所有权限码列表(包括菜单和元素) + 此函数根据角色查询数据库得到权限。 + """ + claims = get_jwt() + user_role = claims.get('role') + if not user_role: + return [] + # 超级管理员返回所有字段权限 + if user_role == 'super_admin': + return ['bom_manage:*'] + perm_dict = AuthService.get_user_permissions(user_role) + # 合并菜单和元素权限 + perms = perm_dict.get('menus', []) + perm_dict.get('elements', []) + return perms + + +def filter_item_by_permissions(item_dict, user_permissions): + """ + 根据用户权限过滤 item 字典,无权限的字段值置为 None + """ + # 字段名到权限码的映射(与前端 permissionMap 保持一致) + field_to_perm = { + 'bom_no': 'bom_manage:bom_no', + 'parent_name': 'bom_manage:parent_name', + 'parent_spec': 'bom_manage:parent_spec', + 'version': 'bom_manage:version', + 'is_enabled': 'bom_manage:status', + 'child_count': 'bom_manage:child_count', + } + # 如果用户是超级管理员且有 'bom_manage:*',则不过滤 + if 'bom_manage:*' in user_permissions: + return item_dict + for field, perm_code in field_to_perm.items(): + if field in item_dict and perm_code not in user_permissions: + item_dict[field] = None + return item_dict + + # ==================== 新版 BOM 接口(基于 bom_no) ==================== @bom_bp.route('/list', methods=['GET']) @jwt_required() +@permission_required('bom_manage') def get_bom_list(): """获取所有 BOM 配方列表,支持 keyword 搜索和 active_only 过滤""" try: @@ -20,6 +66,10 @@ def get_bom_list(): active_only = request.args.get('active_only', 'false').lower() == 'true' data = BomService.get_bom_list(keyword=keyword, active_only=active_only) + # 字段级脱敏 + user_permissions = get_current_user_permissions() + if isinstance(data, list): + data = [filter_item_by_permissions(item, user_permissions) for item in data] return jsonify({ 'code': 200, 'msg': 'success', @@ -32,6 +82,7 @@ def get_bom_list(): @bom_bp.route('/detail/', methods=['GET']) @jwt_required() +@permission_required('bom_manage') def get_bom_detail(bom_no): """ 根据 BOM 编号获取配方详情 @@ -42,6 +93,9 @@ def get_bom_detail(bom_no): data = BomService.get_bom_detail(bom_no, version=version) if not data: return jsonify({'code': 404, 'msg': 'BOM 不存在'}), 404 + # 字段级脱敏 + user_permissions = get_current_user_permissions() + data = filter_item_by_permissions(data, user_permissions) return jsonify({ 'code': 200, 'msg': 'success', @@ -54,6 +108,7 @@ def get_bom_detail(bom_no): @bom_bp.route('/save', methods=['POST']) @jwt_required() +@permission_required('bom_manage:operation') def save_bom(): """保存或更新 BOM 配方(支持自定义 bom_no 和 多版本)""" try: @@ -81,12 +136,16 @@ def save_bom(): @bom_bp.route('/stock/', methods=['GET']) @jwt_required() +@permission_required('bom_manage') def get_bom_with_stock_by_no(bom_no): """根据 BOM 编号获取配方详情及库存信息""" try: data = BomService.get_bom_with_stock_by_bom_no(bom_no) if not data: return jsonify({'code': 404, 'msg': 'BOM 不存在'}), 404 + # 字段级脱敏 + user_permissions = get_current_user_permissions() + data = filter_item_by_permissions(data, user_permissions) return jsonify({ 'code': 200, 'msg': 'success', @@ -101,6 +160,7 @@ def get_bom_with_stock_by_no(bom_no): @bom_bp.route('/', methods=['DELETE']) @jwt_required() +@permission_required('bom_manage:operation') def delete_bom(bom_no): """ 根据 BOM 编号删除 @@ -133,9 +193,13 @@ def delete_bom(bom_no): @bom_bp.route('/', methods=['GET']) @jwt_required() +@permission_required('bom_manage') def get_bom(parent_id): try: data = BomService.get_bom_with_stock(parent_id) + # 字段级脱敏 + user_permissions = get_current_user_permissions() + data = filter_item_by_permissions(data, user_permissions) return jsonify({ 'code': 200, 'msg': 'success', @@ -148,6 +212,7 @@ def get_bom(parent_id): @bom_bp.route('', methods=['POST']) @jwt_required() +@permission_required('bom_manage:operation') def save_bom_legacy(): try: req_data = request.get_json() @@ -169,11 +234,14 @@ def save_bom_legacy(): @bom_bp.route('/base/list', methods=['GET']) @jwt_required() +@permission_required('bom_manage') def get_material_base_list(): """获取所有基础物料列表,用于前端下拉框""" try: materials = MaterialBase.query.filter_by(is_enabled=True).order_by(MaterialBase.id.desc()).all() data = [item.to_dict() for item in materials] + # 字段级脱敏 (如果需要,但此接口通常用于下拉选择,可能不需要脱敏) + # 保持原样 return jsonify({ 'code': 200, 'msg': 'success', @@ -186,12 +254,16 @@ def get_material_base_list(): @bom_bp.route('/parents', methods=['GET']) @jwt_required() +@permission_required('bom_manage') def get_bom_parents(): """获取所有已定义BOM的父件物料列表(兼容旧版)""" try: subq = db.session.query(BomTable.parent_id).distinct().subquery() parents = MaterialBase.query.join(subq, MaterialBase.id == subq.c.parent_id).all() data = [item.to_dict() for item in parents] + # 字段级脱敏 (如果需要) + user_permissions = get_current_user_permissions() + data = [filter_item_by_permissions(item, user_permissions) for item in data] return jsonify({ 'code': 200, 'msg': 'success', @@ -199,4 +271,4 @@ def get_bom_parents(): }) except Exception as e: current_app.logger.error(f'获取BOM父件列表失败: {str(e)}') - return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500 \ No newline at end of file + return jsonify({'code': 500, 'msg': '内部服务器错误'}), 500 diff --git a/inventory-web/src/views/bom/BomManage.vue b/inventory-web/src/views/bom/BomManage.vue index 7a809d9..7095d71 100644 --- a/inventory-web/src/views/bom/BomManage.vue +++ b/inventory-web/src/views/bom/BomManage.vue @@ -17,29 +17,29 @@ - 新建 BOM + 新建 BOM - - - - + + + + - + - - + +