diff --git a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/BjxxService.cs b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/BjxxService.cs index db6a404..1a1566f 100644 --- a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/BjxxService.cs +++ b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/BjxxService.cs @@ -192,11 +192,15 @@ namespace NCC.Extend.Bjxx .Any()); } - // 关联产品筛选(通过 sssb 关联到资料管理,再通过资料管理的 fl 字段关联到产品;支持按产品 Id 或 Cpid 匹配) + // 关联产品筛选:支持 (1) bjxx.sssb 直接为产品 Id/Cpid (2) bjxx.sssb 为 zlgl.Id,zlgl.Fl 关联产品 var flFilter = string.IsNullOrWhiteSpace(input.fl) ? null : input.fl.Trim(); if (!string.IsNullOrEmpty(flFilter)) { - query = query.Where(it => + query = query.Where(it => + it.Sssb == flFilter || + SqlFunc.Subqueryable() + .Where(cp => (cp.Id == flFilter || cp.Cpid == flFilter) && (cp.Id == it.Sssb || cp.Cpid == it.Sssb)) + .Any() || SqlFunc.Subqueryable() .Where(zlgl => zlgl.Id == it.Sssb && ( zlgl.Fl == flFilter || @@ -230,9 +234,8 @@ namespace NCC.Extend.Bjxx sssb = it.Sssb, sbmc = SqlFunc.Subqueryable().Where(z => z.Id == it.Sssb).Select(z => z.Zlm), glcpmc = SqlFunc.Subqueryable() - .Where(cpgl => SqlFunc.Subqueryable() - .Where(zlgl => zlgl.Id == it.Sssb && zlgl.Fl == cpgl.Id) - .Any()) + .Where(cpgl => cpgl.Id == it.Sssb || cpgl.Cpid == it.Sssb || + SqlFunc.Subqueryable().Where(zlgl => zlgl.Id == it.Sssb && zlgl.Fl == cpgl.Id).Any()) .Select(cpgl => cpgl.Cpmc), bjqk = it.Bjqk, bjjj = it.Bjjj, diff --git a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/KhsbPermissionHelper.cs b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/KhsbPermissionHelper.cs index 933d58d..7f25be6 100644 --- a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/KhsbPermissionHelper.cs +++ b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/KhsbPermissionHelper.cs @@ -15,9 +15,9 @@ namespace NCC.Extend public static class KhsbPermissionHelper { /// - /// 获取当前用户可查看的所属客户ID集合 + /// 获取当前用户可查看的所属客户ID集合(用于备件、资料、故障等移动端权限) /// 管理员:返回 null 表示不限制 - /// 非管理员:返回 [userId, 所属部门id, 所属组织id及其祖先] 的并集 + /// 非管理员:返回 [userId, 所属组织id],不包含上级组织,避免越权看到其他组织的设备数据 /// public static async Task> GetUserAllowedSskhbhIdsAsync(IUserManager userManager, SqlSugarScope db) { @@ -33,11 +33,7 @@ namespace NCC.Extend allowedIds.Add(userInfo.userId); if (!string.IsNullOrEmpty(userInfo.organizeId)) - { allowedIds.Add(userInfo.organizeId); - var ancestorIds = await GetAncestorOrganizeIdsAsync(db, userInfo.organizeId); - foreach (var id in ancestorIds) allowedIds.Add(id); - } return allowedIds.ToList(); } @@ -48,8 +44,9 @@ namespace NCC.Extend } /// - /// 获取用户可查看的产品ID集合(cpgl.F_Id) - /// 通过 khsb.dysbbh = cpgl.cpid 关联,用户所属设备(sskhbh 匹配)对应的产品 + /// 获取用户可查看的产品ID集合(cpgl.Id) + /// 来源:用户可查看设备(sskhbh 匹配)的 (1) khsb.Fl 直接关联产品 (2) khsb.Dysbbh=cpgl.Cpid 关联产品 + /// 与 ZlglService 逻辑一致,确保备件、资料等模块权限统一 /// 管理员返回 null 表示不限制 /// public static async Task> GetUserAllowedProductIdsAsync(IUserManager userManager, SqlSugarScope db) @@ -58,19 +55,29 @@ namespace NCC.Extend if (allowedSskhbhIds == null) return null; // 管理员 if (allowedSskhbhIds.Count == 0) return new List(); - var productIds = await db.Queryable() + var fromFl = await db.Queryable() + .Where(k => k.Sskhbh != null && allowedSskhbhIds.Contains(k.Sskhbh) && k.Fl != null && k.Fl != "") + .Select(k => k.Fl) + .Distinct() + .ToListAsync(); + var fromDysbbh = await db.Queryable() .InnerJoin((k, c) => k.Dysbbh != null && k.Dysbbh != "" && k.Dysbbh == c.Cpid) .Where((k, c) => k.Sskhbh != null && allowedSskhbhIds.Contains(k.Sskhbh)) .Select((k, c) => c.Id) .Distinct() .ToListAsync(); - return productIds ?? new List(); + var productIds = (fromFl ?? new List()) + .Union(fromDysbbh ?? new List()) + .Distinct() + .ToList(); + return productIds; } /// /// 获取用户可查看的备件ID集合 - /// 来源1:khsb.bjxx(设备直接关联的备件) - /// 来源2:bjxx.sssb 关联 zlgl,zlgl.fl 关联产品,产品通过 khsb.dysbbh=cpgl.cpid 匹配用户设备 + /// 来源1:khsb.bjxx(设备直接关联的备件),且备件所属产品须在 allowedProductIds 内,避免设备误关联他产品备件导致越权 + /// 来源2:bjxx.sssb 关联 zlgl,zlgl.fl 关联产品,产品通过 khsb 匹配用户设备 + /// 来源3:bjxx.sssb 直接为 cpgl.Id 或 cpgl.Cpid,产品在用户可查看范围内 /// 管理员返回 null 表示不限制 /// public static async Task> GetUserAllowedBjxxIdsAsync(IUserManager userManager, SqlSugarScope db) @@ -80,23 +87,36 @@ namespace NCC.Extend if (allowedSskhbhIds.Count == 0) return new List(); var ids = new HashSet(StringComparer.OrdinalIgnoreCase); + var allowedProductIds = await GetUserAllowedProductIdsAsync(userManager, db); - // 来源1:khsb.bjxx + // 来源1:khsb.bjxx,仅保留备件所属产品在 allowedProductIds 内的,防止设备误关联他产品备件导致越权 var bjxxStrList = await db.Queryable() .Where(k => k.Sskhbh != null && allowedSskhbhIds.Contains(k.Sskhbh) && k.Bjxx != null && k.Bjxx != "") .Select(k => k.Bjxx) .ToListAsync(); + var rawBjxxIds = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var s in bjxxStrList ?? new List()) { foreach (var id in s.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x))) + rawBjxxIds.Add(id); + } + if (rawBjxxIds.Count > 0 && allowedProductIds != null && allowedProductIds.Count > 0) + { + var bjxxList = rawBjxxIds.ToList(); + var bjxxInScope = await db.Queryable() + .Where(b => bjxxList.Contains(b.Id) && b.Sssb != null && b.Sssb != "") + .Where(b => allowedProductIds.Contains(b.Sssb) || + SqlFunc.Subqueryable().Where(c => (c.Id == b.Sssb || c.Cpid == b.Sssb) && allowedProductIds.Contains(c.Id)).Any() || + SqlFunc.Subqueryable().Where(z => z.Id == b.Sssb && z.Fl != null && allowedProductIds.Contains(z.Fl)).Any()) + .Select(b => b.Id) + .ToListAsync(); + foreach (var id in bjxxInScope ?? new List()) ids.Add(id); } - - // 来源2:通过产品关联(khsb.dysbbh=cpgl.cpid -> zlgl.fl=cpgl.Id -> bjxx.sssb=zlgl.F_Id) - var allowedProductIds = await GetUserAllowedProductIdsAsync(userManager, db); if (allowedProductIds != null && allowedProductIds.Count > 0) { + // 来源2:bjxx.sssb = zlgl.F_Id,且 zlgl.fl 在允许产品内 var zlglIds = await db.Queryable() .Where(z => z.Fl != null && allowedProductIds.Contains(z.Fl)) .Select(z => z.Id) @@ -110,32 +130,20 @@ namespace NCC.Extend foreach (var id in bjxxIdsFromZlgl ?? new List()) ids.Add(id); } + + // 来源3:bjxx.sssb 直接为 cpgl.Id 或 cpgl.Cpid + var bjxxIdsFromCpgl = await db.Queryable() + .Where(b => b.Sssb != null && b.Sssb != "") + .Where(b => SqlFunc.Subqueryable() + .Where(c => (c.Id == b.Sssb || c.Cpid == b.Sssb) && allowedProductIds.Contains(c.Id)) + .Any()) + .Select(b => b.Id) + .ToListAsync(); + foreach (var id in bjxxIdsFromCpgl ?? new List()) + ids.Add(id); } return ids.ToList(); } - - private static async Task> GetAncestorOrganizeIdsAsync(SqlSugarScope db, string organizeId) - { - if (string.IsNullOrEmpty(organizeId)) return new List(); - var result = new List(); - try - { - var currentId = organizeId; - for (int i = 0; i < 50; i++) - { - var org = await db.Queryable() - .Where(o => o.Id == currentId && o.DeleteMark == null) - .Select(o => new { o.ParentId }) - .FirstAsync(); - if (org == null || string.IsNullOrEmpty(org.ParentId) || org.ParentId == "-1") - break; - result.Add(org.ParentId); - currentId = org.ParentId; - } - } - catch { } - return result; - } } } diff --git a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/ZskService.cs b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/ZskService.cs index c7575ba..602ac4b 100644 --- a/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/ZskService.cs +++ b/机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/ZskService.cs @@ -82,18 +82,26 @@ namespace NCC.Extend.Zsk .WhereIF(!string.IsNullOrEmpty(input.sssb), p => p.Sssb.Equals(input.sssb)); // 客户权限:仅显示该客户在"我的设备"中已有设备关联产品的知识库(传入 sskhbh 时生效;管理员不传则看全部) + // 使用 KhsbPermissionHelper 与备件、资料等模块一致的权限逻辑:[userId, organizeId],且支持 khsb.Fl + Dysbbh 关联产品 if (!string.IsNullOrEmpty(input.sskhbh)) { - var allowedProductIds = await _db.Queryable() - .Where(k => k.Sskhbh != null && k.Sskhbh.Contains(input.sskhbh.Trim())) - .Where(k => k.Fl != null && k.Fl != "") - .Select(k => k.Fl) - .Distinct() - .ToListAsync(); - if (allowedProductIds == null || allowedProductIds.Count == 0) + var allowedSskhbhIds = await KhsbPermissionHelper.GetUserAllowedSskhbhIdsAsync(_userManager, _db); + if (allowedSskhbhIds == null) + { + // 管理员:不限制 + } + else if (allowedSskhbhIds.Count == 0) + { query = query.Where(it => false); + } else - query = query.Where(it => allowedProductIds.Contains(it.Sssb)); + { + var allowedProductIds = await KhsbPermissionHelper.GetUserAllowedProductIdsAsync(_userManager, _db); + if (allowedProductIds == null || allowedProductIds.Count == 0) + query = query.Where(it => false); + else + query = query.Where(it => it.Sssb != null && allowedProductIds.Contains(it.Sssb)); + } } var data = await query @@ -150,16 +158,18 @@ namespace NCC.Extend.Zsk if (!string.IsNullOrEmpty(input.sskhbh)) { - var allowedProductIds = await _db.Queryable() - .Where(k => k.Sskhbh != null && k.Sskhbh.Contains(input.sskhbh.Trim())) - .Where(k => k.Fl != null && k.Fl != "") - .Select(k => k.Fl) - .Distinct() - .ToListAsync(); - if (allowedProductIds == null || allowedProductIds.Count == 0) + var allowedSskhbhIds = await KhsbPermissionHelper.GetUserAllowedSskhbhIdsAsync(_userManager, _db); + if (allowedSskhbhIds == null) { } + else if (allowedSskhbhIds.Count == 0) query = query.Where(it => false); else - query = query.Where(it => allowedProductIds.Contains(it.Sssb)); + { + var allowedProductIds = await KhsbPermissionHelper.GetUserAllowedProductIdsAsync(_userManager, _db); + if (allowedProductIds == null || allowedProductIds.Count == 0) + query = query.Where(it => false); + else + query = query.Where(it => it.Sssb != null && allowedProductIds.Contains(it.Sssb)); + } } var data = await query diff --git a/机具(管理端)/src/views/khsb/Form.vue b/机具(管理端)/src/views/khsb/Form.vue index dc9f13c..f7eea1f 100644 --- a/机具(管理端)/src/views/khsb/Form.vue +++ b/机具(管理端)/src/views/khsb/Form.vue @@ -94,7 +94,7 @@ - + @@ -236,7 +236,6 @@ created() { this.getUserTreeData(); this.getCpglOptions(); - this.getBjxxOptions(); }, mounted() { }, @@ -263,11 +262,16 @@ return Promise.reject(err) }) }, - getBjxxOptions() { + // 按产品筛选备件,仅返回对应产品的备件(productId 为 cpgl.id) + getBjxxOptions(productId) { + if (!productId) { + this.bjxxOptions = [] + return Promise.resolve() + } return request({ url: '/api/Extend/Bjxx', method: 'GET', - data: { currentPage: 1, pageSize: 1000 } + data: { currentPage: 1, pageSize: 2000, sssb: productId } }).then(res => { this.bjxxOptions = res.data.list || [] return Promise.resolve() @@ -295,7 +299,9 @@ } }, handleDysbChange(val) { - // 选择产品后,自动填充产品编号、产品名称、设备名称,并将设备id保存到fl字段 + // 选择产品后,自动填充产品编号、产品名称、设备名称,并将设备id保存到fl字段;按产品加载备件列表 + this.bjxxValue = []; + this.dataForm.bjxx = ''; if (val) { const product = this.cpglOptions.find(item => item.id === val); if (product) { @@ -303,13 +309,14 @@ this.dataForm.dysbbh = product.cpid || product.id || ''; this.dataForm.dysb = product.cpmc || ''; this.dataForm.sbmc = product.cpmc || ''; + this.getBjxxOptions(val); } } else { this.dataForm.fl = ''; - this.dataForm.fl = ''; - this.dataForm.dysb = ''; - this.dataForm.dysbbh = ''; - this.dataForm.sbmc = ''; + this.dataForm.dysb = ''; + this.dataForm.dysbbh = ''; + this.dataForm.sbmc = ''; + this.bjxxOptions = []; } }, handleKhxsryChange(val) { @@ -380,7 +387,7 @@ if (this.dataForm.sskhbh) { this.sskhValue = this.dataForm.sskhbh; } - // 优先用 fl(设备id)还原关联产品选择,否则用 dysbbh 查找 + // 优先用 fl 还原关联产品选择,否则用 dysbbh 查找;再按产品加载备件 const resolveDysbValue = () => { if (this.dataForm.fl) { this.dysbValue = this.dataForm.fl; @@ -395,20 +402,32 @@ } } }; + const loadBjxxAndRestore = () => { + const productId = this.dataForm.fl || this.dysbValue; + if (productId) { + this.getBjxxOptions(productId).then(() => { + if (this.dataForm.bjxx && typeof this.dataForm.bjxx === 'string') { + this.bjxxValue = this.dataForm.bjxx.split(',').filter(item => item); + } else { + this.bjxxValue = []; + } + }); + } else { + this.bjxxValue = []; + } + }; if (this.cpglOptions.length === 0) { - this.getCpglOptions().then(() => resolveDysbValue()); + this.getCpglOptions().then(() => { + resolveDysbValue(); + loadBjxxAndRestore(); + }); } else { resolveDysbValue(); + loadBjxxAndRestore(); } if (this.dataForm.khxsrybh) { this.khxsryValue = this.dataForm.khxsrybh; } - // 处理备件信息(从逗号分隔的字符串转换为数组) - if (this.dataForm.bjxx && typeof this.dataForm.bjxx === 'string') { - this.bjxxValue = this.dataForm.bjxx.split(',').filter(item => item); - } else { - this.bjxxValue = []; - } }) } else { // 新建时清空所有字段