"""字段级权限:字段清单单一来源 + 权限解析 + 后端强制过滤。 设计目标(对应「字段级权限」优化): 1. 字段清单单一来源:FIELD_REGISTRY 定义每个页面的字段(key=中文显示名,与前端列定义、 现有 user_permissions 存储保持一致),sensitive 标记敏感字段。 2. 权限存储复用现有表结构,无需迁移: - 角色级字段权限:role_operation_permissions,operation = 'field:<字段>:view' / 'field:<字段>:edit' - 用户级字段权限:user_permissions,operation 同上 3. 敏感字段默认隐藏:敏感字段未显式授权时,默认拒绝查看/编辑(安全优先)。 4. 后端强制过滤:filter_rows / filter_row 按有效字段权限剔除无权查看的字段。 权限解析优先级:管理员全权限 → 用户级明确值 → 角色级明确值 → 敏感字段默认拒绝 / 非敏感默认继承页面权限。 """ from services.db import query from services.permission_model import ensure_position_permissions, user_position_code # ─── 字段清单单一来源 ─── # key 统一使用中文显示名(与前端列定义一致),sensitive=True 表示敏感字段(默认隐藏,需显式授权)。 FIELD_REGISTRY = { # 计划预测(与 PLAN_COLUMNS 对齐) 'planning': [ {'key': '产品类型', 'sensitive': False}, {'key': '产品种类', 'sensitive': False}, {'key': '货品编号', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '品牌', 'sensitive': False}, {'key': '批注', 'sensitive': False}, {'key': '新旧品', 'sensitive': False}, {'key': '下单清单', 'sensitive': False}, {'key': '产品分级', 'sensitive': False}, {'key': '昨日销量', 'sensitive': False}, {'key': '云仓昨日销量', 'sensitive': False}, {'key': '外仓昨日销量', 'sensitive': False}, {'key': '销售趋势', 'sensitive': False}, {'key': '总库存', 'sensitive': False}, {'key': '库存', 'sensitive': False}, {'key': '总在途库存', 'sensitive': False}, {'key': '云仓库存', 'sensitive': False}, {'key': '外仓库存', 'sensitive': False}, {'key': '加工厂库存', 'sensitive': False}, {'key': '云仓可分配库存', 'sensitive': False}, {'key': '外仓可分配库存', 'sensitive': False}, {'key': '是否调拨', 'sensitive': False}, {'key': '生产在途', 'sensitive': False}, {'key': '采购在途', 'sensitive': False}, {'key': '日销量预测', 'sensitive': False}, {'key': '云仓日销量预测', 'sensitive': False}, {'key': '外仓日销量预测', 'sensitive': False}, {'key': '建议下单数量', 'sensitive': False}, {'key': '实际下单数量', 'sensitive': False}, {'key': '正式下单数量', 'sensitive': False}, {'key': '库存健康指数', 'sensitive': False}, {'key': '风险把控', 'sensitive': False}, {'key': '供应商名称', 'sensitive': False}, {'key': '采购跟单', 'sensitive': False}, {'key': '采购计划', 'sensitive': False}, {'key': '开发专员', 'sensitive': False}, {'key': '归属平台', 'sensitive': False}, {'key': '运营专员', 'sensitive': False}, {'key': '成本价', 'sensitive': True}, {'key': '3天平均销量', 'sensitive': False}, {'key': '云仓3天平均销量', 'sensitive': False}, {'key': '外仓3天平均销量', 'sensitive': False}, {'key': '7天平均销量', 'sensitive': False}, {'key': '云仓7天平均销量', 'sensitive': False}, {'key': '外仓7天平均销量', 'sensitive': False}, {'key': '14天平均销量', 'sensitive': False}, {'key': '云仓14天平均销量', 'sensitive': False}, {'key': '外仓14天平均销量', 'sensitive': False}, {'key': '库存可支撑天数', 'sensitive': False}, {'key': '现货采购在途', 'sensitive': False}, {'key': '加工厂库存支撑', 'sensitive': False}, {'key': '定制品在途', 'sensitive': False}, {'key': '总支撑天数', 'sensitive': False}, {'key': '云仓支撑天数', 'sensitive': False}, {'key': '外仓支撑天数', 'sensitive': False}, {'key': '建议回货数量', 'sensitive': False}, {'key': '产品规格', 'sensitive': False}, {'key': '30天平均销量', 'sensitive': False}, {'key': '60天平均销量', 'sensitive': False}, {'key': '生产货期', 'sensitive': False}, {'key': '物流时间', 'sensitive': False}, {'key': '最低起订量', 'sensitive': False}, {'key': '创建时间', 'sensitive': False}, {'key': '是否新品', 'sensitive': False}, {'key': '一级类目', 'sensitive': False}, {'key': '二级类目', 'sensitive': False}, {'key': '三级类目', 'sensitive': False}, ], # 数据概览 'data_summary': [ {'key': '货品编号', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '品牌', 'sensitive': False}, {'key': '仓库', 'sensitive': False}, {'key': '店铺', 'sensitive': False}, {'key': '总数量', 'sensitive': False}, {'key': '总成本', 'sensitive': True}, {'key': '日期', 'sensitive': False}, {'key': '数据来源', 'sensitive': False}, ], # 数据明细 'data_detail': [ {'key': '货品编号', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '品牌', 'sensitive': False}, {'key': '仓库', 'sensitive': False}, {'key': '店铺', 'sensitive': False}, {'key': '一级类目', 'sensitive': False}, {'key': '二级类目', 'sensitive': False}, {'key': '三级类目', 'sensitive': False}, {'key': '货品数量', 'sensitive': False}, {'key': '货品成本', 'sensitive': True}, {'key': '日期', 'sensitive': False}, {'key': '数据来源', 'sensitive': False}, ], # 库存-成品 'inventory_finished': [ {'key': '货品编号', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '品牌', 'sensitive': False}, {'key': '仓库', 'sensitive': False}, {'key': '可用库存', 'sensitive': False}, {'key': '生产在途', 'sensitive': False}, {'key': '采购在途', 'sensitive': False}, {'key': '供应商', 'sensitive': False}, {'key': '库存日期', 'sensitive': False}, {'key': '采购跟单', 'sensitive': False}, {'key': '采购计划', 'sensitive': False}, ], # 库存-包材 'inventory_packaging': [ {'key': '货品编号', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '品牌', 'sensitive': False}, {'key': '仓库', 'sensitive': False}, {'key': '可用库存', 'sensitive': False}, {'key': '生产在途', 'sensitive': False}, {'key': '采购在途', 'sensitive': False}, {'key': '供应商', 'sensitive': False}, {'key': '库存日期', 'sensitive': False}, ], # 采购待下单 'purchase_pending': [ {'key': '产品类型', 'sensitive': False}, {'key': '货品编号', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '确认下单', 'sensitive': False}, {'key': '产品分级', 'sensitive': False}, {'key': '供应商名称', 'sensitive': False}, {'key': '建议下单数量', 'sensitive': False}, {'key': '正式下单数量', 'sensitive': False}, {'key': '下单备注', 'sensitive': False}, {'key': '货期', 'sensitive': False}, {'key': '物流', 'sensitive': False}, {'key': '总支撑天数', 'sensitive': False}, {'key': '平台', 'sensitive': False}, {'key': '运营', 'sensitive': False}, {'key': '审核价格', 'sensitive': True}, {'key': '下单人员', 'sensitive': False}, {'key': '下单时间', 'sensitive': False}, {'key': '采购跟单', 'sensitive': False}, {'key': '采购计划', 'sensitive': False}, {'key': '加入时间', 'sensitive': False}, ], # 采购已下单 'purchase_backup': [ {'key': '产品类型', 'sensitive': False}, {'key': '货品编号', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '下单时间', 'sensitive': False}, {'key': '下单人', 'sensitive': False}, {'key': '生产单号', 'sensitive': False}, {'key': '建议下单数量', 'sensitive': False}, {'key': '正式下单数量', 'sensitive': False}, {'key': '产品分级', 'sensitive': False}, {'key': '供应商名称', 'sensitive': False}, {'key': '下单备注', 'sensitive': False}, {'key': '货期', 'sensitive': False}, {'key': '物流', 'sensitive': False}, {'key': '总支撑天数', 'sensitive': False}, {'key': '采购跟单', 'sensitive': False}, {'key': '采购计划', 'sensitive': False}, {'key': '平台', 'sensitive': False}, {'key': '运营', 'sensitive': False}, {'key': '审核价格', 'sensitive': True}, ], # 国内订单 'order_domestic': [ {'key': '日期', 'sensitive': False}, {'key': '计划下单时间', 'sensitive': False}, {'key': '新旧品', 'sensitive': False}, {'key': '货品编号', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '货品类型', 'sensitive': False}, {'key': '下单备注', 'sensitive': False}, {'key': '单价', 'sensitive': True}, {'key': '审核价格', 'sensitive': True}, {'key': '数量', 'sensitive': False}, {'key': '金额', 'sensitive': True}, {'key': '供货商', 'sensitive': False}, {'key': '仓库', 'sensitive': False}, {'key': '金蝶采购单', 'sensitive': False}, {'key': '采购员', 'sensitive': False}, {'key': '下单人', 'sensitive': False}, {'key': '进度', 'sensitive': False}, {'key': '旺店通单号', 'sensitive': False}, {'key': '采购开发', 'sensitive': False}, {'key': '预计到货日期', 'sensitive': False}, {'key': '金蝶入库', 'sensitive': False}, ], # 海外订单 'order_overseas': [ {'key': '产品编码', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '货品类型', 'sensitive': False}, {'key': '日期', 'sensitive': False}, {'key': '计划下单时间', 'sensitive': False}, {'key': '单价', 'sensitive': True}, {'key': '审核价格', 'sensitive': True}, {'key': '数量', 'sensitive': False}, {'key': '金额', 'sensitive': True}, {'key': '供应商', 'sensitive': False}, {'key': '仓库', 'sensitive': False}, {'key': '新旧品', 'sensitive': False}, {'key': '下单备注', 'sensitive': False}, {'key': '采购员', 'sensitive': False}, {'key': '下单人', 'sensitive': False}, {'key': '进度', 'sensitive': False}, {'key': '旺店通单号', 'sensitive': False}, {'key': '采购开发', 'sensitive': False}, {'key': '预计到货日期', 'sensitive': False}, ], # 包材订单 'order_packaging': [ {'key': '日期', 'sensitive': False}, {'key': '计划下单时间', 'sensitive': False}, {'key': '产品编码', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '货品类型', 'sensitive': False}, {'key': '单价', 'sensitive': True}, {'key': '审核价格', 'sensitive': True}, {'key': '数量', 'sensitive': False}, {'key': '金额', 'sensitive': True}, {'key': '供应商', 'sensitive': False}, {'key': '成品供应商', 'sensitive': False}, {'key': '仓库', 'sensitive': False}, {'key': '新旧品', 'sensitive': False}, {'key': '采购员', 'sensitive': False}, {'key': '下单人', 'sensitive': False}, {'key': '进度', 'sensitive': False}, {'key': '下单备注', 'sensitive': False}, {'key': '采购开发', 'sensitive': False}, {'key': '旺店通单号', 'sensitive': False}, ], # 成品订单 'order_finished': [ {'key': '品类', 'sensitive': False}, {'key': '货品编号', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '货品类型', 'sensitive': False}, {'key': '供应商', 'sensitive': False}, {'key': '可用库存', 'sensitive': False}, {'key': '昨日销量', 'sensitive': False}, {'key': '3天均销', 'sensitive': False}, {'key': '7天均销', 'sensitive': False}, {'key': '15天均销', 'sensitive': False}, {'key': '支撑天数', 'sensitive': False}, {'key': '采购总数量', 'sensitive': False}, {'key': '生产在途', 'sensitive': False}, {'key': '采购在途', 'sensitive': False}, {'key': '产品进度', 'sensitive': False}, {'key': '加工厂库存', 'sensitive': False}, {'key': '跟单小组', 'sensitive': False}, {'key': '采购计划', 'sensitive': False}, {'key': '备注', 'sensitive': False}, ], # 产品基础信息 'products': [ {'key': '货品编号', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '货品类型', 'sensitive': False}, {'key': '品牌', 'sensitive': False}, {'key': '供应商编码', 'sensitive': False}, {'key': '供应商名称', 'sensitive': False}, {'key': '成本价', 'sensitive': True}, {'key': '货期', 'sensitive': False}, {'key': '物流时间', 'sensitive': False}, {'key': '最低起订量', 'sensitive': False}, {'key': '采购跟单', 'sensitive': False}, {'key': '采购计划', 'sensitive': False}, {'key': '平台', 'sensitive': False}, {'key': '运营', 'sensitive': False}, {'key': '开发专员', 'sensitive': False}, {'key': '创建时间', 'sensitive': False}, {'key': '导入日期', 'sensitive': False}, ], # 新品管理 'product_new': [ {'key': '产品编码', 'sensitive': False}, {'key': '产品名称', 'sensitive': False}, {'key': '品牌', 'sensitive': False}, {'key': '条形码', 'sensitive': False}, {'key': '容量', 'sensitive': False}, {'key': '成品备注', 'sensitive': False}, {'key': '产品总成本', 'sensitive': True}, {'key': '产品总货款', 'sensitive': True}, {'key': '是否含运', 'sensitive': False}, {'key': '产品开发', 'sensitive': False}, {'key': '包材开发', 'sensitive': False}, {'key': '设计师', 'sensitive': False}, {'key': '负责运营', 'sensitive': False}, ], # 新品包材管理(独立于新品成品管理;字段来自 new_product_bom) 'product_packaging': [ {'key': '成品编码', 'sensitive': False}, {'key': '成品名称', 'sensitive': False}, {'key': '物料编码', 'sensitive': False}, {'key': '物料名称', 'sensitive': False}, {'key': '用量', 'sensitive': False}, {'key': '供应商编码', 'sensitive': False}, {'key': '供应商', 'sensitive': False}, {'key': '货期/天', 'sensitive': False}, {'key': '税点', 'sensitive': True}, {'key': '是否含税', 'sensitive': False}, {'key': '单价/元', 'sensitive': True}, {'key': '数量', 'sensitive': False}, {'key': '货款/元', 'sensitive': True}, {'key': '结款方式', 'sensitive': False}, {'key': '是否可下单', 'sensitive': False}, {'key': '下单备注', 'sensitive': False}, {'key': '通用物料编码', 'sensitive': False}, {'key': '通用物料名称', 'sensitive': False}, {'key': '下单日期', 'sensitive': False}, ], # BOM 'bom': [ {'key': '成品编号', 'sensitive': False}, {'key': '成品名称', 'sensitive': False}, {'key': '物料编号', 'sensitive': False}, {'key': '物料名称', 'sensitive': False}, {'key': '用量', 'sensitive': False}, {'key': '供应商', 'sensitive': False}, {'key': '成本价', 'sensitive': True}, {'key': '创建时间', 'sensitive': False}, ], # 仓库 'warehouse': [ {'key': '仓库编号', 'sensitive': False}, {'key': '仓库名称', 'sensitive': False}, {'key': '仓库类型', 'sensitive': False}, {'key': '地址', 'sensitive': False}, {'key': '联系人', 'sensitive': False}, {'key': '联系电话', 'sensitive': False}, {'key': '创建时间', 'sensitive': False}, ], # 供应商 'suppliers': [ {'key': '供应商编码', 'sensitive': False}, {'key': '供应商名称', 'sensitive': False}, {'key': '联系人', 'sensitive': False}, {'key': '联系电话', 'sensitive': False}, {'key': '地址', 'sensitive': False}, {'key': '合作状态', 'sensitive': False}, {'key': '创建时间', 'sensitive': False}, ], # 现货销售 'spot_sales': [ {'key': '商家编码', 'sensitive': False}, {'key': '货品名称', 'sensitive': False}, {'key': '平台', 'sensitive': False}, {'key': '类目', 'sensitive': False}, {'key': '首批成本价', 'sensitive': True}, ], # AI 分析:金额类数据源单独控制(敏感,默认不开放) 'ai_analysis': [ {'key': '库存成本汇总', 'sensitive': True}, {'key': '商品库存金额', 'sensitive': True}, {'key': '品牌汇总', 'sensitive': True}, ], } # 运行页面ID → 标准权限页面ID 回退映射 _FIELD_REGISTRY_ALIASES = { 'purchase': 'purchase_pending', 'purchase_ready': 'purchase_backup', 'orders_domestic': 'order_domestic', 'orders_overseas': 'order_overseas', 'orders_packaging': 'order_packaging', 'orders_finished': 'order_finished', } # 标准页面ID → 存储/历史别名集合(与 routes/auth.py 的 PERMISSION_PAGE_ALIASES 保持一致) _PAGE_ALIASES = { 'purchase_pending': ['purchase_pending', 'purchase'], 'purchase_backup': ['purchase_backup', 'purchase_ready', 'purchase'], 'order_domestic': ['order_domestic', 'orders_domestic', 'orders'], 'order_overseas': ['order_overseas', 'orders_overseas', 'orders'], 'order_packaging': ['order_packaging', 'orders_packaging', 'orders'], 'order_finished': ['order_finished', 'orders_finished', 'orders'], 'data_summary': ['data_summary', 'data'], 'data_detail': ['data_detail', 'data'], 'inventory_finished': ['inventory_finished', 'inventory'], 'inventory_packaging': ['inventory_packaging', 'inventory'], 'product_new': ['product_new'], 'product_packaging': ['product_packaging'], 'spot_sales': ['spot_sales', 'spot'], } # 标准页面ID → 前端运行页面ID(注入前端时使用) _RUNTIME_PAGE = { 'purchase_pending': 'purchase', 'purchase_backup': 'purchase_ready', 'order_domestic': 'orders_domestic', 'order_overseas': 'orders_overseas', 'order_packaging': 'orders_packaging', 'order_finished': 'orders_finished', } # 敏感字段的响应 key 映射(后端过滤时按实际返回字段名剔除)。 # 未列出的字段默认按 key(中文显示名)匹配响应字段。 FIELD_RESP_KEYS = { # 采购待下单/已下单接口使用英文键名,统一映射到权限字段中文名。 ('purchase_pending', '产品类型'): ['product_type'], ('purchase_pending', '货品编号'): ['product_code'], ('purchase_pending', '货品名称'): ['product_name'], ('purchase_pending', '产品分级'): ['product_grade'], ('purchase_pending', '供应商名称'): ['supplier_name'], ('purchase_pending', '建议下单数量'): ['suggested_qty'], ('purchase_pending', '正式下单数量'): ['formal_order_qty'], ('purchase_pending', '下单备注'): ['remark'], ('purchase_pending', '货期'): ['lead_time'], ('purchase_pending', '物流'): ['logistics_time'], ('purchase_pending', '总支撑天数'): ['total_support_days'], ('purchase_pending', '平台'): ['platform'], ('purchase_pending', '运营'): ['operator'], ('purchase_pending', '审核价格'): ['audit_price'], ('purchase_pending', '下单人员'): ['created_by'], ('purchase_pending', '下单时间'): ['created_at'], ('purchase_pending', '采购跟单'): ['procurement_follower'], ('purchase_pending', '采购计划'): ['procurement_planner'], ('purchase_pending', '加入时间'): ['created_at'], ('purchase_backup', '产品类型'): ['product_type'], ('purchase_backup', '货品编号'): ['product_code'], ('purchase_backup', '货品名称'): ['product_name'], ('purchase_backup', '供应商名称'): ['supplier_name'], ('purchase_backup', '建议下单数量'): ['suggested_qty'], ('purchase_backup', '正式下单数量'): ['formal_order_qty'], ('purchase_backup', '下单备注'): ['remark'], ('purchase_backup', '货期'): ['lead_time'], ('purchase_backup', '物流'): ['logistics_time'], ('purchase_backup', '总支撑天数'): ['total_support_days'], ('purchase_backup', '平台'): ['platform'], ('purchase_backup', '运营'): ['operator'], ('purchase_backup', '审核价格'): ['audit_price'], ('purchase_backup', '下单人员'): ['created_by'], ('purchase_backup', '下单时间'): ['ordered_at'], ('purchase_backup', '采购跟单'): ['procurement_follower'], ('purchase_backup', '采购计划'): ['procurement_planner'], ('purchase_backup', '生产单号'): ['production_order_no'], # 包材订单接口字段与权限字段名称不同。 ('order_packaging', '产品编码'): ['编码'], ('order_packaging', '货品名称'): ['名称'], ('order_packaging', '金额'): ['合计'], ('order_packaging', '供应商'): ['供应商'], ('order_packaging', '旺店通单号'): ['旺店通编号'], ('product_packaging', '成品编码'): ['finished_product_code'], ('product_packaging', '成品名称'): ['finished_product_name'], ('product_packaging', '物料编码'): ['material_code'], ('product_packaging', '物料名称'): ['material_name'], ('product_packaging', '供应商编码'): ['supplier_code'], ('product_packaging', '供应商'): ['supplier'], ('product_packaging', '货期/天'): ['lead_time'], ('product_packaging', '税点'): ['tax_rate'], ('product_packaging', '是否含税'): ['tax_included'], ('product_packaging', '单价/元'): ['unit_price'], ('product_packaging', '数量'): ['quantity'], ('product_packaging', '货款/元'): ['amount'], ('product_packaging', '结款方式'): ['payment_method'], ('product_packaging', '是否可下单'): ['can_order'], ('product_packaging', '下单备注'): ['remark'], ('product_packaging', '通用物料编码'): ['common_code'], ('product_packaging', '通用物料名称'): ['common_name'], ('product_packaging', '下单日期'): ['order_date'], ('planning', '成本价'): ['cost_price'], ('planning', '新旧品'): ['planning_new_old'], ('data_summary', '总成本'): ['total_cost'], ('products', '成本价'): ['cost_price'], ('product_new', '产品总成本'): ['total_cost'], ('product_new', '产品总货款'): ['total_payment'], ('product_new', '是否含运'): ['freight_included'], ('bom', '成本价'): ['cost_price'], } def _field_resp_keys(page, key): """返回某字段应剔除的响应 key 列表(含 key 本身)。""" canonical = _canonical_page(page) resp = FIELD_RESP_KEYS.get((canonical, key), []) keys = [key] + [r for r in resp if r != key] return keys def _canonical_page(page): return _FIELD_REGISTRY_ALIASES.get(page, page) def _page_aliases(page): canonical = _canonical_page(page) return _PAGE_ALIASES.get(canonical, [canonical]) def _runtime_page(page): canonical = _canonical_page(page) return _RUNTIME_PAGE.get(canonical, canonical) def get_field_list(page): """返回页面字段清单(list of {key, sensitive}),未注册的页面返回空列表。""" return FIELD_REGISTRY.get(_canonical_page(page), []) def is_admin_role(role): return role in ('admin', 'super_admin') def _load_field_config(user_id, role, page): """读取角色、岗位、用户三级字段权限配置。""" aliases = _page_aliases(page) placeholders = ','.join(['%s'] * len(aliases)) role_perm, position_perm, user_perm = {}, {}, {} explicit = set() role_rows = query( f"SELECT operation, permission FROM role_operation_permissions " f"WHERE role=%s AND page IN ({placeholders}) AND operation LIKE 'field:%%'", tuple(['viewer' if role == '查看者' else role] + aliases) ) for row in role_rows: parts = row['operation'].split(':', 2) if len(parts) == 3: role_perm.setdefault(parts[1], {})[parts[2]] = int(row['permission']) explicit.add(parts[1]) ensure_position_permissions() position = user_position_code(user_id) if position: position_rows = query( f"SELECT operation, permission FROM position_operation_permissions " f"WHERE position_code=%s AND page IN ({placeholders}) AND operation LIKE 'field:%%'", tuple([position] + aliases) ) for row in position_rows: parts = row['operation'].split(':', 2) if len(parts) == 3: position_perm.setdefault(parts[1], {})[parts[2]] = int(row['permission']) explicit.add(parts[1]) user_rows = query( f"SELECT operation, permission FROM user_permissions " f"WHERE user_id=%s AND page IN ({placeholders}) AND operation LIKE 'field:%%'", tuple([user_id] + aliases) ) for row in user_rows: parts = row['operation'].split(':', 2) if len(parts) == 3: user_perm.setdefault(parts[1], {})[parts[2]] = int(row['permission']) explicit.add(parts[1]) return role_perm, position_perm, user_perm, explicit def _resolve_field_value(user_perm, position_perm, role_perm, field_key, operation, default_allow): """字段权限优先级:用户例外 > 岗位权限 > 角色权限 > 页面默认。""" user_value = user_perm.get(field_key, {}).get(operation) if user_value is not None: return bool(user_value) position_value = position_perm.get(field_key, {}).get(operation) if position_value is not None: return bool(position_value) role_value = role_perm.get(field_key, {}).get(operation) if role_value is not None: return bool(role_value) return bool(default_allow) def field_perms_for_page(user_id, role, page, page_view=True, page_edit=False): """返回合并角色、岗位、用户后的字段权限。""" fields = get_field_list(page) if not fields: return {} if is_admin_role(role): return {field['key']: {'view': True, 'edit': True} for field in fields} role_perm, position_perm, user_perm, _ = _load_field_config(user_id, role, page) result = {} for field in fields: key = field['key'] result[key] = { 'view': _resolve_field_value(user_perm, position_perm, role_perm, key, 'view', page_view), 'edit': _resolve_field_value(user_perm, position_perm, role_perm, key, 'edit', page_edit), } return result def role_field_perms_for_page(role, page): """仅角色级字段权限,用于角色权限矩阵。""" fields = get_field_list(page) if not fields: return {} if is_admin_role(role): return {field['key']: {'view': True, 'edit': True} for field in fields} aliases = _page_aliases(page) placeholders = ','.join(['%s'] * len(aliases)) role_perm = {} rows = query( f"SELECT operation, permission FROM role_operation_permissions " f"WHERE role=%s AND page IN ({placeholders}) AND operation LIKE 'field:%%'", tuple(['viewer' if role == '查看者' else role] + aliases) ) for row in rows: parts = row['operation'].split(':', 2) if len(parts) == 3: role_perm.setdefault(parts[1], {})[parts[2]] = int(row['permission']) return { field['key']: { 'view': _resolve_field_value({}, {}, role_perm, field['key'], 'view', True), 'edit': _resolve_field_value({}, {}, role_perm, field['key'], 'edit', False), } for field in fields } def inherited_field_perms_for_page(user_id, role, page): """角色+岗位的字段继承值,不包含用户个人覆盖。""" fields = get_field_list(page) if not fields: return {} if is_admin_role(role): return {field['key']: {'view': True, 'edit': True} for field in fields} role_perm, position_perm, _, _ = _load_field_config(user_id, role, page) return { field['key']: { 'view': _resolve_field_value({}, position_perm, role_perm, field['key'], 'view', True), 'edit': _resolve_field_value({}, position_perm, role_perm, field['key'], 'edit', False), } for field in fields } def filter_row(page, row, user_id, role, page_view=True, page_edit=False): """对单个 dict 按字段 view 权限剔除无权查看的字段(原地修改并返回)。""" if row is None or is_admin_role(role): return row perms = field_perms_for_page(user_id, role, page, page_view, page_edit) if not perms: return row for key in list(row.keys()): # 响应字段可能用中文显示名,也可能用英文 DB 列名;统一按映射剔除 matched = None for fkey, fperm in perms.items(): if key in _field_resp_keys(page, fkey): matched = (fkey, fperm) break if matched and not matched[1]['view']: del row[key] return row def filter_rows(page, rows, user_id, role, page_view=True, page_edit=False): """对 dict 列表按字段 view 权限剔除字段(原地修改并返回)。""" if not rows or is_admin_role(role): return rows perms = field_perms_for_page(user_id, role, page, page_view, page_edit) if not perms: return rows # 预先构建 响应key -> 是否可查看 的映射,避免逐行重复计算 resp_visibility = {} for fkey, fperm in perms.items(): for rkey in _field_resp_keys(page, fkey): resp_visibility[rkey] = fperm['view'] for row in rows: if not isinstance(row, dict): continue for key in list(row.keys()): if key in resp_visibility and not resp_visibility[key]: del row[key] return rows def effective_field_overrides(user_id, role, page_view_by_page=None, page_edit_by_page=None): """返回需要注入前端的字段级权限显式值(用于敏感字段默认隐藏 + 显式配置)。 仅返回「敏感字段」和「有显式配置」的字段;其余字段由前端按页面权限默认继承。 返回结构:{runtime_page: {field_key: {'view': 0/1, 'edit': 0/1}}}。 """ if is_admin_role(role): return {} page_view_by_page = page_view_by_page or {} page_edit_by_page = page_edit_by_page or {} overrides = {} for page, fields in FIELD_REGISTRY.items(): role_perm, position_perm, user_perm, explicit = _load_field_config(user_id, role, page) pv = page_view_by_page.get(page, True) pe = page_edit_by_page.get(page, False) page_overrides = {} for f in fields: key = f['key'] sensitive = bool(f.get('sensitive')) if key not in explicit: continue page_overrides[key] = { 'view': 1 if _resolve_field_value(user_perm, position_perm, role_perm, key, 'view', pv) else 0, 'edit': 1 if _resolve_field_value(user_perm, position_perm, role_perm, key, 'edit', pe) else 0, } if page_overrides: overrides[_runtime_page(page)] = page_overrides return overrides