Commit 040963e1725d15c8c4e6c1239b739e8295f56ea5

Authored by “wangming”
1 parent aa96451e

等待一堆修改

机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/BjxxService.cs
... ... @@ -192,11 +192,15 @@ namespace NCC.Extend.Bjxx
192 192 .Any());
193 193 }
194 194  
195   - // 关联产品筛选(通过 sssb 关联到资料管理,再通过资料管理的 fl 字段关联到产品;支持按产品 Id 或 Cpid 匹配)
  195 + // 关联产品筛选:支持 (1) bjxx.sssb 直接为产品 Id/Cpid (2) bjxx.sssb 为 zlgl.Id,zlgl.Fl 关联产品
196 196 var flFilter = string.IsNullOrWhiteSpace(input.fl) ? null : input.fl.Trim();
197 197 if (!string.IsNullOrEmpty(flFilter))
198 198 {
199   - query = query.Where(it =>
  199 + query = query.Where(it =>
  200 + it.Sssb == flFilter ||
  201 + SqlFunc.Subqueryable<CpglEntity>()
  202 + .Where(cp => (cp.Id == flFilter || cp.Cpid == flFilter) && (cp.Id == it.Sssb || cp.Cpid == it.Sssb))
  203 + .Any() ||
200 204 SqlFunc.Subqueryable<ZlglEntity>()
201 205 .Where(zlgl => zlgl.Id == it.Sssb && (
202 206 zlgl.Fl == flFilter ||
... ... @@ -230,9 +234,8 @@ namespace NCC.Extend.Bjxx
230 234 sssb = it.Sssb,
231 235 sbmc = SqlFunc.Subqueryable<ZlglEntity>().Where(z => z.Id == it.Sssb).Select(z => z.Zlm),
232 236 glcpmc = SqlFunc.Subqueryable<CpglEntity>()
233   - .Where(cpgl => SqlFunc.Subqueryable<ZlglEntity>()
234   - .Where(zlgl => zlgl.Id == it.Sssb && zlgl.Fl == cpgl.Id)
235   - .Any())
  237 + .Where(cpgl => cpgl.Id == it.Sssb || cpgl.Cpid == it.Sssb ||
  238 + SqlFunc.Subqueryable<ZlglEntity>().Where(zlgl => zlgl.Id == it.Sssb && zlgl.Fl == cpgl.Id).Any())
236 239 .Select(cpgl => cpgl.Cpmc),
237 240 bjqk = it.Bjqk,
238 241 bjjj = it.Bjjj,
... ...
机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/KhsbPermissionHelper.cs
... ... @@ -15,9 +15,9 @@ namespace NCC.Extend
15 15 public static class KhsbPermissionHelper
16 16 {
17 17 /// <summary>
18   - /// 获取当前用户可查看的所属客户ID集合
  18 + /// 获取当前用户可查看的所属客户ID集合(用于备件、资料、故障等移动端权限)
19 19 /// 管理员:返回 null 表示不限制
20   - /// 非管理员:返回 [userId, 所属部门id, 所属组织id及其祖先] 的并集
  20 + /// 非管理员:返回 [userId, 所属组织id],不包含上级组织,避免越权看到其他组织的设备数据
21 21 /// </summary>
22 22 public static async Task<List<string>> GetUserAllowedSskhbhIdsAsync(IUserManager userManager, SqlSugarScope db)
23 23 {
... ... @@ -33,11 +33,7 @@ namespace NCC.Extend
33 33 allowedIds.Add(userInfo.userId);
34 34  
35 35 if (!string.IsNullOrEmpty(userInfo.organizeId))
36   - {
37 36 allowedIds.Add(userInfo.organizeId);
38   - var ancestorIds = await GetAncestorOrganizeIdsAsync(db, userInfo.organizeId);
39   - foreach (var id in ancestorIds) allowedIds.Add(id);
40   - }
41 37  
42 38 return allowedIds.ToList();
43 39 }
... ... @@ -48,8 +44,9 @@ namespace NCC.Extend
48 44 }
49 45  
50 46 /// <summary>
51   - /// 获取用户可查看的产品ID集合(cpgl.F_Id)
52   - /// 通过 khsb.dysbbh = cpgl.cpid 关联,用户所属设备(sskhbh 匹配)对应的产品
  47 + /// 获取用户可查看的产品ID集合(cpgl.Id)
  48 + /// 来源:用户可查看设备(sskhbh 匹配)的 (1) khsb.Fl 直接关联产品 (2) khsb.Dysbbh=cpgl.Cpid 关联产品
  49 + /// 与 ZlglService 逻辑一致,确保备件、资料等模块权限统一
53 50 /// 管理员返回 null 表示不限制
54 51 /// </summary>
55 52 public static async Task<List<string>> GetUserAllowedProductIdsAsync(IUserManager userManager, SqlSugarScope db)
... ... @@ -58,19 +55,29 @@ namespace NCC.Extend
58 55 if (allowedSskhbhIds == null) return null; // 管理员
59 56 if (allowedSskhbhIds.Count == 0) return new List<string>();
60 57  
61   - var productIds = await db.Queryable<KhsbEntity>()
  58 + var fromFl = await db.Queryable<KhsbEntity>()
  59 + .Where(k => k.Sskhbh != null && allowedSskhbhIds.Contains(k.Sskhbh) && k.Fl != null && k.Fl != "")
  60 + .Select(k => k.Fl)
  61 + .Distinct()
  62 + .ToListAsync();
  63 + var fromDysbbh = await db.Queryable<KhsbEntity>()
62 64 .InnerJoin<CpglEntity>((k, c) => k.Dysbbh != null && k.Dysbbh != "" && k.Dysbbh == c.Cpid)
63 65 .Where((k, c) => k.Sskhbh != null && allowedSskhbhIds.Contains(k.Sskhbh))
64 66 .Select((k, c) => c.Id)
65 67 .Distinct()
66 68 .ToListAsync();
67   - return productIds ?? new List<string>();
  69 + var productIds = (fromFl ?? new List<string>())
  70 + .Union(fromDysbbh ?? new List<string>())
  71 + .Distinct()
  72 + .ToList();
  73 + return productIds;
68 74 }
69 75  
70 76 /// <summary>
71 77 /// 获取用户可查看的备件ID集合
72   - /// 来源1:khsb.bjxx(设备直接关联的备件)
73   - /// 来源2:bjxx.sssb 关联 zlgl,zlgl.fl 关联产品,产品通过 khsb.dysbbh=cpgl.cpid 匹配用户设备
  78 + /// 来源1:khsb.bjxx(设备直接关联的备件),且备件所属产品须在 allowedProductIds 内,避免设备误关联他产品备件导致越权
  79 + /// 来源2:bjxx.sssb 关联 zlgl,zlgl.fl 关联产品,产品通过 khsb 匹配用户设备
  80 + /// 来源3:bjxx.sssb 直接为 cpgl.Id 或 cpgl.Cpid,产品在用户可查看范围内
74 81 /// 管理员返回 null 表示不限制
75 82 /// </summary>
76 83 public static async Task<List<string>> GetUserAllowedBjxxIdsAsync(IUserManager userManager, SqlSugarScope db)
... ... @@ -80,23 +87,36 @@ namespace NCC.Extend
80 87 if (allowedSskhbhIds.Count == 0) return new List<string>();
81 88  
82 89 var ids = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  90 + var allowedProductIds = await GetUserAllowedProductIdsAsync(userManager, db);
83 91  
84   - // 来源1:khsb.bjxx
  92 + // 来源1:khsb.bjxx,仅保留备件所属产品在 allowedProductIds 内的,防止设备误关联他产品备件导致越权
85 93 var bjxxStrList = await db.Queryable<KhsbEntity>()
86 94 .Where(k => k.Sskhbh != null && allowedSskhbhIds.Contains(k.Sskhbh) && k.Bjxx != null && k.Bjxx != "")
87 95 .Select(k => k.Bjxx)
88 96 .ToListAsync();
  97 + var rawBjxxIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
89 98 foreach (var s in bjxxStrList ?? new List<string>())
90 99 {
91 100 foreach (var id in s.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
92 101 .Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)))
  102 + rawBjxxIds.Add(id);
  103 + }
  104 + if (rawBjxxIds.Count > 0 && allowedProductIds != null && allowedProductIds.Count > 0)
  105 + {
  106 + var bjxxList = rawBjxxIds.ToList();
  107 + var bjxxInScope = await db.Queryable<BjxxEntity>()
  108 + .Where(b => bjxxList.Contains(b.Id) && b.Sssb != null && b.Sssb != "")
  109 + .Where(b => allowedProductIds.Contains(b.Sssb) ||
  110 + SqlFunc.Subqueryable<CpglEntity>().Where(c => (c.Id == b.Sssb || c.Cpid == b.Sssb) && allowedProductIds.Contains(c.Id)).Any() ||
  111 + SqlFunc.Subqueryable<ZlglEntity>().Where(z => z.Id == b.Sssb && z.Fl != null && allowedProductIds.Contains(z.Fl)).Any())
  112 + .Select(b => b.Id)
  113 + .ToListAsync();
  114 + foreach (var id in bjxxInScope ?? new List<string>())
93 115 ids.Add(id);
94 116 }
95   -
96   - // 来源2:通过产品关联(khsb.dysbbh=cpgl.cpid -> zlgl.fl=cpgl.Id -> bjxx.sssb=zlgl.F_Id)
97   - var allowedProductIds = await GetUserAllowedProductIdsAsync(userManager, db);
98 117 if (allowedProductIds != null && allowedProductIds.Count > 0)
99 118 {
  119 + // 来源2:bjxx.sssb = zlgl.F_Id,且 zlgl.fl 在允许产品内
100 120 var zlglIds = await db.Queryable<ZlglEntity>()
101 121 .Where(z => z.Fl != null && allowedProductIds.Contains(z.Fl))
102 122 .Select(z => z.Id)
... ... @@ -110,32 +130,20 @@ namespace NCC.Extend
110 130 foreach (var id in bjxxIdsFromZlgl ?? new List<string>())
111 131 ids.Add(id);
112 132 }
  133 +
  134 + // 来源3:bjxx.sssb 直接为 cpgl.Id 或 cpgl.Cpid
  135 + var bjxxIdsFromCpgl = await db.Queryable<BjxxEntity>()
  136 + .Where(b => b.Sssb != null && b.Sssb != "")
  137 + .Where(b => SqlFunc.Subqueryable<CpglEntity>()
  138 + .Where(c => (c.Id == b.Sssb || c.Cpid == b.Sssb) && allowedProductIds.Contains(c.Id))
  139 + .Any())
  140 + .Select(b => b.Id)
  141 + .ToListAsync();
  142 + foreach (var id in bjxxIdsFromCpgl ?? new List<string>())
  143 + ids.Add(id);
113 144 }
114 145  
115 146 return ids.ToList();
116 147 }
117   -
118   - private static async Task<List<string>> GetAncestorOrganizeIdsAsync(SqlSugarScope db, string organizeId)
119   - {
120   - if (string.IsNullOrEmpty(organizeId)) return new List<string>();
121   - var result = new List<string>();
122   - try
123   - {
124   - var currentId = organizeId;
125   - for (int i = 0; i < 50; i++)
126   - {
127   - var org = await db.Queryable<OrganizeEntity>()
128   - .Where(o => o.Id == currentId && o.DeleteMark == null)
129   - .Select(o => new { o.ParentId })
130   - .FirstAsync();
131   - if (org == null || string.IsNullOrEmpty(org.ParentId) || org.ParentId == "-1")
132   - break;
133   - result.Add(org.ParentId);
134   - currentId = org.ParentId;
135   - }
136   - }
137   - catch { }
138   - return result;
139   - }
140 148 }
141 149 }
... ...
机具(服务端)/netcore/src/Modularity/Extend/NCC.Extend/ZskService.cs
... ... @@ -82,18 +82,26 @@ namespace NCC.Extend.Zsk
82 82 .WhereIF(!string.IsNullOrEmpty(input.sssb), p => p.Sssb.Equals(input.sssb));
83 83  
84 84 // 客户权限:仅显示该客户在"我的设备"中已有设备关联产品的知识库(传入 sskhbh 时生效;管理员不传则看全部)
  85 + // 使用 KhsbPermissionHelper 与备件、资料等模块一致的权限逻辑:[userId, organizeId],且支持 khsb.Fl + Dysbbh 关联产品
85 86 if (!string.IsNullOrEmpty(input.sskhbh))
86 87 {
87   - var allowedProductIds = await _db.Queryable<KhsbEntity>()
88   - .Where(k => k.Sskhbh != null && k.Sskhbh.Contains(input.sskhbh.Trim()))
89   - .Where(k => k.Fl != null && k.Fl != "")
90   - .Select(k => k.Fl)
91   - .Distinct()
92   - .ToListAsync();
93   - if (allowedProductIds == null || allowedProductIds.Count == 0)
  88 + var allowedSskhbhIds = await KhsbPermissionHelper.GetUserAllowedSskhbhIdsAsync(_userManager, _db);
  89 + if (allowedSskhbhIds == null)
  90 + {
  91 + // 管理员:不限制
  92 + }
  93 + else if (allowedSskhbhIds.Count == 0)
  94 + {
94 95 query = query.Where(it => false);
  96 + }
95 97 else
96   - query = query.Where(it => allowedProductIds.Contains(it.Sssb));
  98 + {
  99 + var allowedProductIds = await KhsbPermissionHelper.GetUserAllowedProductIdsAsync(_userManager, _db);
  100 + if (allowedProductIds == null || allowedProductIds.Count == 0)
  101 + query = query.Where(it => false);
  102 + else
  103 + query = query.Where(it => it.Sssb != null && allowedProductIds.Contains(it.Sssb));
  104 + }
97 105 }
98 106  
99 107 var data = await query
... ... @@ -150,16 +158,18 @@ namespace NCC.Extend.Zsk
150 158  
151 159 if (!string.IsNullOrEmpty(input.sskhbh))
152 160 {
153   - var allowedProductIds = await _db.Queryable<KhsbEntity>()
154   - .Where(k => k.Sskhbh != null && k.Sskhbh.Contains(input.sskhbh.Trim()))
155   - .Where(k => k.Fl != null && k.Fl != "")
156   - .Select(k => k.Fl)
157   - .Distinct()
158   - .ToListAsync();
159   - if (allowedProductIds == null || allowedProductIds.Count == 0)
  161 + var allowedSskhbhIds = await KhsbPermissionHelper.GetUserAllowedSskhbhIdsAsync(_userManager, _db);
  162 + if (allowedSskhbhIds == null) { }
  163 + else if (allowedSskhbhIds.Count == 0)
160 164 query = query.Where(it => false);
161 165 else
162   - query = query.Where(it => allowedProductIds.Contains(it.Sssb));
  166 + {
  167 + var allowedProductIds = await KhsbPermissionHelper.GetUserAllowedProductIdsAsync(_userManager, _db);
  168 + if (allowedProductIds == null || allowedProductIds.Count == 0)
  169 + query = query.Where(it => false);
  170 + else
  171 + query = query.Where(it => it.Sssb != null && allowedProductIds.Contains(it.Sssb));
  172 + }
163 173 }
164 174  
165 175 var data = await query
... ...
机具(管理端)/src/views/khsb/Form.vue
... ... @@ -94,7 +94,7 @@
94 94 </el-col>
95 95 <el-col :span="24">
96 96 <el-form-item label="备件信息" prop="bjxx">
97   - <el-select v-model="bjxxValue" placeholder="请选择备件信息" multiple clearable filterable :style='{"width":"100%"}' @change="handleBjxxChange">
  97 + <el-select v-model="bjxxValue" placeholder="请先选择关联产品后再选择备件" multiple clearable filterable :style='{"width":"100%"}' :disabled="!dysbValue" @change="handleBjxxChange">
98 98 <el-option v-for="item in bjxxOptions" :key="item.id" :label="item.mc + ' (' + item.bh + ')'" :value="item.id" ></el-option>
99 99 </el-select>
100 100 </el-form-item>
... ... @@ -236,7 +236,6 @@
236 236 created() {
237 237 this.getUserTreeData();
238 238 this.getCpglOptions();
239   - this.getBjxxOptions();
240 239 },
241 240 mounted() {
242 241 },
... ... @@ -263,11 +262,16 @@
263 262 return Promise.reject(err)
264 263 })
265 264 },
266   - getBjxxOptions() {
  265 + // 按产品筛选备件,仅返回对应产品的备件(productId 为 cpgl.id)
  266 + getBjxxOptions(productId) {
  267 + if (!productId) {
  268 + this.bjxxOptions = []
  269 + return Promise.resolve()
  270 + }
267 271 return request({
268 272 url: '/api/Extend/Bjxx',
269 273 method: 'GET',
270   - data: { currentPage: 1, pageSize: 1000 }
  274 + data: { currentPage: 1, pageSize: 2000, sssb: productId }
271 275 }).then(res => {
272 276 this.bjxxOptions = res.data.list || []
273 277 return Promise.resolve()
... ... @@ -295,7 +299,9 @@
295 299 }
296 300 },
297 301 handleDysbChange(val) {
298   - // 选择产品后,自动填充产品编号、产品名称、设备名称,并将设备id保存到fl字段
  302 + // 选择产品后,自动填充产品编号、产品名称、设备名称,并将设备id保存到fl字段;按产品加载备件列表
  303 + this.bjxxValue = [];
  304 + this.dataForm.bjxx = '';
299 305 if (val) {
300 306 const product = this.cpglOptions.find(item => item.id === val);
301 307 if (product) {
... ... @@ -303,13 +309,14 @@
303 309 this.dataForm.dysbbh = product.cpid || product.id || '';
304 310 this.dataForm.dysb = product.cpmc || '';
305 311 this.dataForm.sbmc = product.cpmc || '';
  312 + this.getBjxxOptions(val);
306 313 }
307 314 } else {
308 315 this.dataForm.fl = '';
309   - this.dataForm.fl = '';
310   - this.dataForm.dysb = '';
311   - this.dataForm.dysbbh = '';
312   - this.dataForm.sbmc = '';
  316 + this.dataForm.dysb = '';
  317 + this.dataForm.dysbbh = '';
  318 + this.dataForm.sbmc = '';
  319 + this.bjxxOptions = [];
313 320 }
314 321 },
315 322 handleKhxsryChange(val) {
... ... @@ -380,7 +387,7 @@
380 387 if (this.dataForm.sskhbh) {
381 388 this.sskhValue = this.dataForm.sskhbh;
382 389 }
383   - // 优先用 fl(设备id)还原关联产品选择,否则用 dysbbh 查找
  390 + // 优先用 fl 还原关联产品选择,否则用 dysbbh 查找;再按产品加载备件
384 391 const resolveDysbValue = () => {
385 392 if (this.dataForm.fl) {
386 393 this.dysbValue = this.dataForm.fl;
... ... @@ -395,20 +402,32 @@
395 402 }
396 403 }
397 404 };
  405 + const loadBjxxAndRestore = () => {
  406 + const productId = this.dataForm.fl || this.dysbValue;
  407 + if (productId) {
  408 + this.getBjxxOptions(productId).then(() => {
  409 + if (this.dataForm.bjxx && typeof this.dataForm.bjxx === 'string') {
  410 + this.bjxxValue = this.dataForm.bjxx.split(',').filter(item => item);
  411 + } else {
  412 + this.bjxxValue = [];
  413 + }
  414 + });
  415 + } else {
  416 + this.bjxxValue = [];
  417 + }
  418 + };
398 419 if (this.cpglOptions.length === 0) {
399   - this.getCpglOptions().then(() => resolveDysbValue());
  420 + this.getCpglOptions().then(() => {
  421 + resolveDysbValue();
  422 + loadBjxxAndRestore();
  423 + });
400 424 } else {
401 425 resolveDysbValue();
  426 + loadBjxxAndRestore();
402 427 }
403 428 if (this.dataForm.khxsrybh) {
404 429 this.khxsryValue = this.dataForm.khxsrybh;
405 430 }
406   - // 处理备件信息(从逗号分隔的字符串转换为数组)
407   - if (this.dataForm.bjxx && typeof this.dataForm.bjxx === 'string') {
408   - this.bjxxValue = this.dataForm.bjxx.split(',').filter(item => item);
409   - } else {
410   - this.bjxxValue = [];
411   - }
412 431 })
413 432 } else {
414 433 // 新建时清空所有字段
... ...