diff --git a/.gitignore b/.gitignore index 846d202..3b810ce 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /CloudBuilder.Topshelf/bin /CloudBuilder.Topshelf/obj /uniapp/demo +/uniapp/youjiala/src/.qoder diff --git a/CloudBuilder/CloudBuilder.Security.Service/SecurityGeneratorNumberService.cs b/CloudBuilder/CloudBuilder.Security.Service/SecurityGeneratorNumberService.cs index 4fa35d5..51a9f3f 100644 --- a/CloudBuilder/CloudBuilder.Security.Service/SecurityGeneratorNumberService.cs +++ b/CloudBuilder/CloudBuilder.Security.Service/SecurityGeneratorNumberService.cs @@ -9,6 +9,7 @@ using CloudBuilder.Security.Policy; using CloudBuilder.Security.SearchCriteria; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; namespace CloudBuilder.Security.Service @@ -153,13 +154,76 @@ namespace CloudBuilder.Security.Service return GetNo(systemId, subsystemId, prefix, null, null); } + //public string GetNo(string systemId, string subsystemId, string prefix, string refreshPeriod, string format) + //{ + // SecurityGeneratorNumberEntity number = null; + + // if (!string.IsNullOrEmpty(systemId) && !string.IsNullOrEmpty(subsystemId)) + // { + // SecurityGeneratorNumberEntity[] tps = repository.Entities.Where(x => x.SystemId == systemId && x.SubsystemId == subsystemId).ToArray(); + + // if (tps != null && tps.Length > 0 && !string.IsNullOrEmpty(prefix)) + // { + // tps = tps.Where(x => x.Prefix == prefix).ToArray(); + // } + + // if (tps != null && tps.Length > 0) number = tps[0]; + // } + + // if (number == null) + // { + // number = new SecurityGeneratorNumberEntity(); + + // number.SystemId = string.IsNullOrEmpty(systemId) ? string.Empty : systemId; + // number.SubsystemId = string.IsNullOrEmpty(subsystemId) ? string.Empty : subsystemId; + + // number.Prefix = string.IsNullOrEmpty(prefix) ? string.Empty : prefix; + // number.RefreshPeriod = string.IsNullOrEmpty(refreshPeriod) ? YesNoPolicy.YES : refreshPeriod; + // number.Format = string.IsNullOrEmpty(format) ? "yyMMdd000000" : format; + // number.LastGeneratorDate = DateTime.Now; + // number.NextId = 2; + + // repository.InsertNow(number); + // } + // else + // { + // number.NextId += 1; + + // number.LastGeneratorDate = DateTime.Now; + // repository.UpdateNow(number); + // } + + // format = number.Format; + + // if (number.LastGeneratorDate.Date.Subtract(DateTime.Now.Date).TotalDays != 0 && number.RefreshPeriod == YesNoPolicy.YES) + // { + // number.NextId = 1; + // } + + // string temp = format.Replace("0", "").Replace("yyyy", DateTime.Now.ToString("yyyy"), StringComparison.OrdinalIgnoreCase).Replace("yy", DateTime.Now.ToString("yy"), StringComparison.OrdinalIgnoreCase) + // .Replace("MM", DateTime.Now.ToString("MM"), StringComparison.OrdinalIgnoreCase).Replace("dd", DateTime.Now.ToString("dd")); + + // string zero = format.Replace("y", "").Replace("Y", "").Replace("M", "").Replace("m", "").Replace("d", "").Replace("D", ""); + // string code = number.Prefix + temp + string.Format("{0:" + zero + "}", number.NextId - 1); + + // Int64 max = Convert.ToInt64(zero.Replace("0", "9")); + // if (max == number.NextId) + // { + // throw new Exception("系统编号已达到最大值."); + // } + + // return code; + //} + public string GetNo(string systemId, string subsystemId, string prefix, string refreshPeriod, string format) { SecurityGeneratorNumberEntity number = null; if (!string.IsNullOrEmpty(systemId) && !string.IsNullOrEmpty(subsystemId)) { - SecurityGeneratorNumberEntity[] tps = repository.Entities.Where(x => x.SystemId == systemId && x.SubsystemId == subsystemId).ToArray(); + // 这里只用来「定位计数行 + 读取 format/prefix 等静态配置」;自增后的取值不再依赖这份内存拷贝, + // 用 DetachedEntities 避免跟踪实例干扰后面回读到的最新 next_id。 + SecurityGeneratorNumberEntity[] tps = repository.DetachedEntities.Where(x => x.SystemId == systemId && x.SubsystemId == subsystemId).ToArray(); if (tps != null && tps.Length > 0 && !string.IsNullOrEmpty(prefix)) { @@ -169,8 +233,12 @@ namespace CloudBuilder.Security.Service if (tps != null && tps.Length > 0) number = tps[0]; } + // pointer 等价于旧实现里「自增/建行之后、用于发号与最大值校验」的 number.NextId + int pointer; + if (number == null) { + // 首次建行:NextId=2,本次发号 = pointer-1 = 1(与旧实现完全一致,建行分支不参与原子自增) number = new SecurityGeneratorNumberEntity(); number.SystemId = string.IsNullOrEmpty(systemId) ? string.Empty : systemId; @@ -183,30 +251,37 @@ namespace CloudBuilder.Security.Service number.NextId = 2; repository.InsertNow(number); + + pointer = number.NextId; } else { - number.NextId += 1; + // 治本:把旧的「读旧值→内存+1→写回」换成数据库端单条原子 UPDATE(next_id = next_id + 1 由 DB 计算)。 + // 并发事务会在该行的排他锁上排队,后到者只能在前者提交后基于新值再加,彻底杜绝丢失更新/重号。 + // 走 EF 的 ExecuteSqlRaw 而非裸 Connection,确保自动登记当前 [UnitOfWork] 事务(否则 SqlServer 报"需要事务")。 + string providerName = repository.GetDbContext().Database.ProviderName; + string nowFunc = providerName.Contains("MySql") ? "NOW()" : "GETDATE()"; + string incrementSql = $"UPDATE {SecurityGeneratorNumberEntity.DB_NAME_SECURITY_GENERATOR_NUMBER} " + + $"SET {SecurityGeneratorNumberEntity.DB_NEXT_ID} = {SecurityGeneratorNumberEntity.DB_NEXT_ID} + 1, " + + $"{SecurityGeneratorNumberEntity.DB_LAST_GENERATOR_DATE} = {nowFunc} " + + $"WHERE {SecurityGeneratorNumberEntity.DB_GENERATOR_ID} = {number.GeneratorId}"; - number.LastGeneratorDate = DateTime.Now; - repository.UpdateNow(number); + repository.GetDbContext().Database.ExecuteSqlRaw(incrementSql); + + // 回读自增后的值:同一事务能读到自己的写;此刻并发事务正阻塞在上面那条 UPDATE 的行锁上 + pointer = repository.DetachedEntities.Where(x => x.GeneratorId == number.GeneratorId).Select(x => x.NextId).First(); } format = number.Format; - if (number.LastGeneratorDate.Date.Subtract(DateTime.Now.Date).TotalDays != 0 && number.RefreshPeriod == YesNoPolicy.YES) - { - number.NextId = 1; - } - string temp = format.Replace("0", "").Replace("yyyy", DateTime.Now.ToString("yyyy"), StringComparison.OrdinalIgnoreCase).Replace("yy", DateTime.Now.ToString("yy"), StringComparison.OrdinalIgnoreCase) .Replace("MM", DateTime.Now.ToString("MM"), StringComparison.OrdinalIgnoreCase).Replace("dd", DateTime.Now.ToString("dd")); string zero = format.Replace("y", "").Replace("Y", "").Replace("M", "").Replace("m", "").Replace("d", "").Replace("D", ""); - string code = number.Prefix + temp + string.Format("{0:" + zero + "}", number.NextId - 1); + string code = number.Prefix + temp + string.Format("{0:" + zero + "}", pointer - 1); Int64 max = Convert.ToInt64(zero.Replace("0", "9")); - if (max == number.NextId) + if (max == pointer) { throw new Exception("系统编号已达到最大值."); } @@ -229,6 +304,61 @@ namespace CloudBuilder.Security.Service return GetNextId(systemId, subsystemId, prefix, format, 1); } + //public int GetNextId(string systemId, string subsystemId, string prefix, string format, int length) + //{ + // SecurityGeneratorNumberEntity number = null; + + // if (!string.IsNullOrEmpty(systemId) && !string.IsNullOrEmpty(subsystemId)) + // { + // SecurityGeneratorNumberEntity[] tps = repository.DetachedEntities.Where(x => x.SystemId == systemId && x.SubsystemId == subsystemId).ToArray(); + + // if (tps != null && tps.Length > 0 && !string.IsNullOrEmpty(prefix)) + // { + // tps = tps.Where(x => x.Prefix == prefix).ToArray(); + // } + + // if (tps != null && tps.Length > 0) number = tps[0]; + // } + + // if (number == null) + // { + // number = new SecurityGeneratorNumberEntity(); + + // number.SystemId = string.IsNullOrEmpty(systemId) ? string.Empty : systemId; + // number.SubsystemId = string.IsNullOrEmpty(subsystemId) ? string.Empty : subsystemId; + + // number.Prefix = string.IsNullOrEmpty(prefix) ? string.Empty : prefix; + // number.Format = string.IsNullOrEmpty(format) ? "yyMMdd000000" : format; + // number.LastGeneratorDate = DateTime.Now; + // number.NextId = 1 + length; + // number.RefreshPeriod = YesNoPolicy.NO; + + // repository.Insert(number); + // } + // else + // { + + // number.NextId = number.NextId + length; + + // if (number.LastGeneratorDate.Date.Subtract(DateTime.Now.Date).TotalDays != 0 && number.RefreshPeriod == YesNoPolicy.YES) + // { + // number.NextId = 1 + length; + // } + + // number.LastGeneratorDate = DateTime.Now; + // repository.Update(number); + // } + + // string zero = number.Format.Replace("y", "").Replace("Y", "").Replace("M", "").Replace("m", "").Replace("d", "").Replace("D", ""); + + // Int64 max = Convert.ToInt64(zero.Replace("0", "9")); + // if (max == number.NextId - length) + // { + // throw new Exception("系统编号已达到最大值."); + // } + + // return number.NextId - length - 1; + //} public int GetNextId(string systemId, string subsystemId, string prefix, string format, int length) { SecurityGeneratorNumberEntity number = null; @@ -245,6 +375,9 @@ namespace CloudBuilder.Security.Service if (tps != null && tps.Length > 0) number = tps[0]; } + // pointer 等价于旧实现里「自增/建行/重置之后、用于发号与最大值校验」的 number.NextId + int pointer; + if (number == null) { number = new SecurityGeneratorNumberEntity(); @@ -259,31 +392,42 @@ namespace CloudBuilder.Security.Service number.RefreshPeriod = YesNoPolicy.NO; repository.Insert(number); + + pointer = number.NextId; } else { + // 与 GetNo 同款治本:把「读旧值→内存+length→(可能重置)→写回」整体交给数据库端一条原子 UPDATE。 + // 注意与 GetNo 的差别:GetNextId 的每日重置不是死代码(它在比较之后才把 last_generator_date 置为 now), + // 所以这里把「跨天且 refresh_period=YES 时重置为 1+length」并入 CASE,靠该行排他锁串行化, + // 后到事务读到的是前者提交后的新值,既杜绝丢失更新,也顺带修好「同一天首个号被并发重复发放」的隐患。 + string providerName = repository.GetDbContext().Database.ProviderName; + bool isMySql = providerName.Contains("MySql"); + string nowFunc = isMySql ? "NOW()" : "GETDATE()"; + string dateChanged = isMySql + ? $"DATE({SecurityGeneratorNumberEntity.DB_LAST_GENERATOR_DATE}) <> DATE(NOW())" + : $"CAST({SecurityGeneratorNumberEntity.DB_LAST_GENERATOR_DATE} AS date) <> CAST(GETDATE() AS date)"; + string incrementSql = $"UPDATE {SecurityGeneratorNumberEntity.DB_NAME_SECURITY_GENERATOR_NUMBER} " + + $"SET {SecurityGeneratorNumberEntity.DB_NEXT_ID} = CASE WHEN {SecurityGeneratorNumberEntity.DB_REFRESH_PERIOD} = '{YesNoPolicy.YES}' AND {dateChanged} " + + $"THEN 1 + {length} ELSE {SecurityGeneratorNumberEntity.DB_NEXT_ID} + {length} END, " + + $"{SecurityGeneratorNumberEntity.DB_LAST_GENERATOR_DATE} = {nowFunc} " + + $"WHERE {SecurityGeneratorNumberEntity.DB_GENERATOR_ID} = {number.GeneratorId}"; - number.NextId = number.NextId + length; + repository.GetDbContext().Database.ExecuteSqlRaw(incrementSql); - if (number.LastGeneratorDate.Date.Subtract(DateTime.Now.Date).TotalDays != 0 && number.RefreshPeriod == YesNoPolicy.YES) - { - number.NextId = 1 + length; - } - - number.LastGeneratorDate = DateTime.Now; - repository.Update(number); + // 回读自增后的值:同一事务能读到自己的写;此刻并发事务正阻塞在上面那条 UPDATE 的行锁上 + pointer = repository.DetachedEntities.Where(x => x.GeneratorId == number.GeneratorId).Select(x => x.NextId).First(); } string zero = number.Format.Replace("y", "").Replace("Y", "").Replace("M", "").Replace("m", "").Replace("d", "").Replace("D", ""); Int64 max = Convert.ToInt64(zero.Replace("0", "9")); - if (max == number.NextId - length) + if (max == pointer - length) { throw new Exception("系统编号已达到最大值."); } - return number.NextId - length - 1; + return pointer - length - 1; } - } } diff --git a/CloudBuilder/CloudBuilder.Web.Entry/appsettings.json b/CloudBuilder/CloudBuilder.Web.Entry/appsettings.json index d796595..bb904b3 100644 --- a/CloudBuilder/CloudBuilder.Web.Entry/appsettings.json +++ b/CloudBuilder/CloudBuilder.Web.Entry/appsettings.json @@ -10,7 +10,7 @@ "DbContexts": { "MasterDbContext": { "DbContextTypeName": "MasterDbContext", - "ConnectionString": "server=nas.youjiala.com;port=3307;database=builder;uid=root;pwd=Root@1237890;CharSet=utf8mb4;", + "ConnectionString": "server=nas.youjiala.com;port=3307;database=Furion;uid=root;pwd=Root@1237890;CharSet=utf8mb4;", "DatabaseType": "MySql", "LazyLoadingProxiesEnabled": true, "AuditEntityEnabled": true, diff --git a/uniapp/youjiala/package-lock.json b/uniapp/youjiala/package-lock.json index d5b7d85..0d50f62 100644 --- a/uniapp/youjiala/package-lock.json +++ b/uniapp/youjiala/package-lock.json @@ -26,6 +26,7 @@ "@dcloudio/uni-quickapp-webview": "3.0.0-4080720251210001", "crypto-js": "^4.1.1", "dayjs": "^1.11.23", + "hls.js": "^1.7.3", "pinia": "2.2.4", "pinia-plugin-persistedstate": "3.2.1", "uview-pro": "^0.6.15", @@ -61,7 +62,7 @@ }, "engines": { "node": ">=18.0.0", - "pnpm": ">=10.0.0" + "npm": ">=9.0.0" } }, "node_modules/@ampproject/remapping": { @@ -5469,34 +5470,6 @@ "url": "https://opencollective.com/pkgr" } }, - "node_modules/@quansync/fs": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/@quansync/fs/-/fs-1.1.0.tgz", - "integrity": "sha512-qAPG/t3HqML1TlN7sY/pTbEjzFVAKsMjNNMGheyDosro+kT4iw2KCUoHcVdmliwWjorm4elZbgNQyU2eD97eDg==", - "dev": true, - "dependencies": { - "quansync": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/@quansync/fs/node_modules/quansync": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/quansync/-/quansync-1.0.0.tgz", - "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ] - }, "node_modules/@rollup/pluginutils": { "version": "5.1.0", "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", @@ -7479,6 +7452,36 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@unocss/config/node_modules/@quansync/fs": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/@quansync/fs/-/fs-0.1.6.tgz", + "integrity": "sha512-zoA8SqQO11qH9H8FCBR7NIbowYARIPmBz3nKjgAaOUDi/xPAAu1uAgebtV7KXHTc6CDZJVRZ1u4wIGvY5CWYaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^0.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/@unocss/config/node_modules/@quansync/fs/node_modules/quansync": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.3.0.tgz", + "integrity": "sha512-dr5GyvHkdDbrAeXyl0MGi/jWKM6+/lZbNFVe+Ff7ivJi4RVry7O091VfXT/wuAVcF3FwNr86nwZVdxx8nELb2w==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/@unocss/config/node_modules/@unocss/core": { "version": "66.10.1", "resolved": "https://registry.npmmirror.com/@unocss/core/-/core-66.10.1.tgz", @@ -7488,6 +7491,22 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@unocss/config/node_modules/unconfig": { + "version": "7.3.2", + "resolved": "https://registry.npmmirror.com/unconfig/-/unconfig-7.3.2.tgz", + "integrity": "sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^0.1.1", + "defu": "^6.1.4", + "jiti": "^2.4.2", + "quansync": "^0.2.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@unocss/core": { "version": "66.10.5", "resolved": "https://registry.npmmirror.com/@unocss/core/-/core-66.10.5.tgz", @@ -12302,6 +12321,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hls.js": { + "version": "1.7.3", + "resolved": "https://registry.npmmirror.com/hls.js/-/hls.js-1.7.3.tgz", + "integrity": "sha512-MsPlx6yVW4Qv4C7mEVou4/gk/5cN2dVxOTnrNjPSmyH2f0Ln+/UkG10Ax1PE6OvrmTVPKgQYozTtBaPOuCzq7A==", + "license": "Apache-2.0" + }, "node_modules/html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -18828,67 +18853,6 @@ "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz", "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==" }, - "node_modules/unconfig": { - "version": "7.5.0", - "resolved": "https://registry.npmmirror.com/unconfig/-/unconfig-7.5.0.tgz", - "integrity": "sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==", - "dev": true, - "dependencies": { - "@quansync/fs": "^1.0.0", - "defu": "^6.1.4", - "jiti": "^2.6.1", - "quansync": "^1.0.0", - "unconfig-core": "7.5.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/unconfig-core": { - "version": "7.5.0", - "resolved": "https://registry.npmmirror.com/unconfig-core/-/unconfig-core-7.5.0.tgz", - "integrity": "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==", - "dev": true, - "dependencies": { - "@quansync/fs": "^1.0.0", - "quansync": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/unconfig-core/node_modules/quansync": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/quansync/-/quansync-1.0.0.tgz", - "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ] - }, - "node_modules/unconfig/node_modules/quansync": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/quansync/-/quansync-1.0.0.tgz", - "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ] - }, "node_modules/undici-types": { "version": "7.24.6", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.24.6.tgz", diff --git a/uniapp/youjiala/package.json b/uniapp/youjiala/package.json index a2f9214..4c0be08 100644 --- a/uniapp/youjiala/package.json +++ b/uniapp/youjiala/package.json @@ -74,6 +74,7 @@ "@dcloudio/uni-quickapp-webview": "3.0.0-4080720251210001", "crypto-js": "^4.1.1", "dayjs": "^1.11.23", + "hls.js": "^1.7.3", "pinia": "2.2.4", "pinia-plugin-persistedstate": "3.2.1", "uview-pro": "^0.6.15", diff --git a/uniapp/youjiala/smoke.out b/uniapp/youjiala/smoke.out deleted file mode 100644 index e69de29..0000000 diff --git a/uniapp/youjiala/src/common/constant.ts b/uniapp/youjiala/src/common/constant.ts index 04c8c78..27fe77f 100644 --- a/uniapp/youjiala/src/common/constant.ts +++ b/uniapp/youjiala/src/common/constant.ts @@ -1,12 +1,11 @@ export const APP_INFO: { name: string; version: string; - buildTime: string; platform: string; } = { - name: "uViewPro(跨平台UI组件库)", - version: "1.0.9", - buildTime: "2025-12-23", + // 版本号需与 src/manifest.json 的 versionName / package.json 的 version 保持一致(改版本时三处同步) + name: "有家相册", + version: "1.0.0", platform: getPlatform(), }; diff --git a/uniapp/youjiala/src/components/admin-list/admin-list.vue b/uniapp/youjiala/src/components/admin-list/admin-list.vue index 6682c16..fe5753e 100644 --- a/uniapp/youjiala/src/components/admin-list/admin-list.vue +++ b/uniapp/youjiala/src/components/admin-list/admin-list.vue @@ -63,6 +63,11 @@ const props = defineProps({ * 否则未登录时会先打一发必然 401 的请求,错误弹窗和跳转一起出现。 */ auto: { type: Boolean, default: false }, + /** + * 选择器模式下重新拉第一页(搜索/筛选)时是否保留已勾选: + * true=多选挑选中不清勾选(挑完再搜也不怕丢已选),false=普通列表照旧清。 + */ + keepSelectionOnReload: { type: Boolean, default: false }, }); const emit = defineEmits<{ @@ -81,6 +86,8 @@ const paging = ref(null); const rows = ref([]); const selectedKeys = ref([]); const selectMode = ref(false); +// 记住被勾选行的完整对象(按 rowKey 存):搜索/翻页后当前 rows 变了也能凭 key 还原已选,供选择器一次性回传 +const selectedPool = ref>({}); const keywordModel = computed({ get: () => props.keyword, @@ -100,7 +107,7 @@ function isSelected(row: AdminRow): boolean { } const selectedRows = computed(() => - rows.value.filter(row => selectedKeys.value.includes(keyOf(row))), + selectedKeys.value.map(key => selectedPool.value[key]).filter(Boolean), ); const allSelected = computed( @@ -109,6 +116,7 @@ const allSelected = computed( function clearSelection(): void { selectedKeys.value = []; + selectedPool.value = {}; selectMode.value = false; emit("selection-change", []); } @@ -116,12 +124,18 @@ function clearSelection(): void { function toggleSelect(row: AdminRow): void { const key = keyOf(row); const next = selectedKeys.value.filter(item => item !== key); - // filter 后长度没变说明原本没选中,那就加上 + const pool = { ...selectedPool.value }; + // filter 后长度没变说明原本没选中,那就加上(同时存下行对象) if (next.length === selectedKeys.value.length) { next.push(key); + pool[key] = row; + } + else { + delete pool[key]; } selectedKeys.value = next; - emit("selection-change", rows.value.filter(item => next.includes(keyOf(item)))); + selectedPool.value = pool; + emit("selection-change", next.map(item => pool[item]).filter(Boolean)); } function toggleSelectAll(): void { @@ -130,7 +144,12 @@ function toggleSelectAll(): void { return; } selectedKeys.value = rows.value.map(row => keyOf(row)); - emit("selection-change", [...rows.value]); + const pool = { ...selectedPool.value }; + for (const row of rows.value) { + pool[keyOf(row)] = row; + } + selectedPool.value = pool; + emit("selection-change", selectedKeys.value.map(key => pool[key]).filter(Boolean)); } function onLongPress(row: AdminRow): void { @@ -144,7 +163,8 @@ function onLongPress(row: AdminRow): void { async function onQuery(page: number, pageSize: number): Promise { try { const result = await props.loader(page, pageSize); - if (page === 1) { + // 回到第 1 页默认清勾选;选择器正在多选时保留,避免"先勾后搜"丢失已选 + if (page === 1 && !(selectMode.value && props.keepSelectionOnReload)) { clearSelection(); } paging.value?.completeByTotal(result.rows, result.total); diff --git a/uniapp/youjiala/src/locale/lang/en-US.json b/uniapp/youjiala/src/locale/lang/en-US.json index c33e78d..a02c63f 100644 --- a/uniapp/youjiala/src/locale/lang/en-US.json +++ b/uniapp/youjiala/src/locale/lang/en-US.json @@ -16,13 +16,221 @@ }, "MinePage": { "title": "Me", + "myTheme": "My Theme", + "mySecurity": "Security", + "currentTheme": "Current Theme", + "aboutTitle": "About", + "appName": "App", + "versionLabel": "Version", + "platformLabel": "Platform" + }, + "ThemePage": { + "title": "My Theme", "themeTitle": "Theme Color", "themeHint": "Component colors update instantly on switch and the choice is remembered", "darkTitle": "Dark Mode", "darkAuto": "Follow System", "darkLight": "Light", - "darkDark": "Dark", - "currentTheme": "Current Theme" + "darkDark": "Dark" + }, + "SecurityPage": { + "title": "Security", + "pageHint": "Permission system and basic settings, admin only", + "pendingTag": "Pending", + "pendingTip": "Not migrated from the legacy system yet, coming soon", + "user": "Users", + "userDesc": "Accounts, login users and status", + "actor": "Roles", + "actorDesc": "Security roles and membership", + "group": "User Groups", + "groupDesc": "Groups and group permission assignment", + "function": "Functions", + "functionDesc": "System functions and menu definitions", + "permission": "Permissions", + "permissionDesc": "Function grants and permission dependencies", + "generator": "Number Generators", + "generatorDesc": "Document number rules and sequences", + "master": "Master Data", + "masterDesc": "Master data and base dictionary settings", + "helpFunction": "Help Functions", + "helpFunctionDesc": "Function help documentation", + "helpFeature": "Help Features", + "helpFeatureDesc": "Help topics and grouping maintenance" + }, + "GeneratorPage": { + "title": "Number Generators", + "detailTitle": "Number Rule Detail", + "systemId": "System Id", + "subsystemId": "Subsystem Id", + "prefix": "Prefix", + "refreshPeriod": "Refresh Period", + "format": "Format", + "nextId": "Next Id", + "lastGeneratorDate": "Last Generated At", + "yes": "Yes", + "no": "No", + "formatRequired": "Please enter the format" + }, + "GroupPage": { + "title": "User Groups", + "detailTitle": "User Group Detail", + "groupName": "Group Name", + "groupLocalName": "Local Name", + "groupEngName": "English Name", + "createdDatetime": "Created At", + "nameRequired": "Please enter the group name", + "localNameRequired": "Please enter the local name", + "actionView": "View", + "actionCreate": "Create", + "actionAmend": "Amend", + "actionDelete": "Delete", + "permissionTitle": "Permissions", + "addPermissionBut": "Add Permission", + "permissionEmpty": "No permissions yet", + "unnamedPermission": "Unnamed permission", + "dependentLabel": "Dependent", + "dependentViewTitle": "Dependent Permissions", + "dependentViewEmpty": "No dependent permissions yet", + "unnamedDependent": "Unnamed dependent", + "actorTitle": "Roles", + "addActorBut": "Add Role", + "actorEmpty": "No roles yet", + "unnamedActor": "Unnamed role" + }, + "FunctionPage": { + "title": "Functions", + "detailTitle": "Function Detail", + "functionName": "Function Name", + "functionLocalName": "Local Name", + "functionEngName": "English Name", + "functionIndc": "Menu?", + "icon": "Icon", + "permissionName": "Permission Name", + "pageUrl": "Page URL", + "apiRoute": "API Route", + "pageName": "Page Name", + "remark": "Remark", + "yes": "Yes", + "no": "No", + "menuItem": "Menu item", + "nameRequired": "Please enter the function name", + "localNameRequired": "Please enter the local name", + "pickPermissionBut": "Pick Permission", + "parentTitle": "Parent Directory", + "addParentBut": "Add Parent", + "parentEmpty": "No parent directory", + "unnamedParent": "Unnamed parent", + "sonTitle": "Child Menus", + "addSonBut": "Add Child", + "sonEmpty": "No child menus", + "unnamedSon": "Unnamed child" + }, + "PermissionPage": { + "title": "Permissions", + "detailTitle": "Permission Detail", + "permissionName": "Permission Name", + "description": "Description", + "dependent": "Dependent", + "createdDatetime": "Created At", + "delegateIndc": "Delegable?", + "yes": "Yes", + "no": "No", + "nameRequired": "Please enter the permission name", + "descriptionRequired": "Please enter the description", + "dependentNameRequired": "Please enter the dependent name", + "dependentDescRequired": "Please enter the dependent local name", + "dependentTitle": "Dependent Permissions", + "addDependentBut": "Add Dependent", + "dependentEmpty": "No dependent permissions yet", + "dependentDetailName": "Dependent Name", + "dependentDetailDescription": "Dependent Local Name", + "dependentNamePlaceholder": "Name of the dependent permission", + "dependentDescPlaceholder": "Local description of the dependent permission" + }, + "MasterPage": { + "title": "Master Data", + "detailTitle": "Master Data Detail", + "masterCategory": "Category", + "masterSubcategory": "Subcategory", + "masterCodeItem": "Code Item", + "localDescription": "Local Description", + "engDescription": "English Description", + "value": "Value", + "orderBy": "Order", + "validIndc": "Valid?", + "remark": "Remark", + "yes": "Yes", + "no": "No", + "categoryRequired": "Please enter the category", + "subcategoryRequired": "Please enter the subcategory", + "codeItemRequired": "Please enter the code item", + "localDescriptionRequired": "Please enter the local description" + }, + "ActorPage": { + "detailTitle": "Role Detail", + "actorName": "Role Name", + "actorLocalName": "Local Name", + "actorEngName": "English Name", + "actorNameRequired": "Please enter the role name", + "localNameRequired": "Please enter the local name", + "groupTitle": "Groups", + "addGroupBut": "Add Group", + "groupEmpty": "No groups yet", + "unnamedGroup": "Unnamed group", + "userTitle": "Member Users", + "addUserBut": "Add User", + "userEmpty": "No member users yet", + "unnamedUser": "Unnamed user" + }, + "HelpFunctionPage": { + "title": "Help Functions", + "detailTitle": "Help Function Detail", + "functionName": "Function Name", + "intro": "Introduction", + "enabledIndc": "Enabled?", + "yes": "Yes", + "no": "No", + "helpFunctionId": "Id", + "enabledTag": "Enabled", + "nameRequired": "Please pick a function name", + "pickFunctionBut": "Pick the function to document", + "stepTitle": "Steps", + "addStepBut": "Add Step", + "stepEmpty": "No steps yet", + "unnamedGroup": "No feature group selected", + "stepName": "Title", + "stepDescription": "Description", + "stepTips": "Tips", + "stepIcon": "Icon", + "stepTitlePlaceholder": "What to do in this step", + "stepDescPlaceholder": "How to do this step", + "stepTipsPlaceholder": "Extra tips, optional", + "stepIconPlaceholder": "Icon name on the page", + "featureTitle": "Referenced Feature Groups", + "addFeatureBut": "Reference Feature Group", + "featureEmpty": "No feature group referenced" + }, + "HelpFeaturePage": { + "title": "Help Features", + "detailTitle": "Feature Group Detail", + "name": "Feature Name", + "description": "Description", + "icon": "Icon", + "color": "Color", + "groupCode": "Group Code", + "groupName": "Group Name", + "featureCount": "Features", + "groupTitle": "Feature Group", + "groupCodeRequired": "Please enter the group code", + "nameRequired": "Please enter the feature name", + "featureTitle": "Features In Group", + "addFeatureBut": "Add Feature", + "featureEmpty": "No features yet", + "namePlaceholder": "Usually the text on the button", + "descPlaceholder": "What this button / icon does", + "iconPlaceholder": "e.g. DeleteOutlined", + "colorPlaceholder": "e.g. #faad14 / red", + "noGroupTip": "This feature has no owning group" }, "FilePage": { "title": "Files", @@ -262,7 +470,7 @@ "viewBut": "View", "amendBut": "Edit", "createBut": "New", - "deleteBut": "Invalidate", + "deleteBut": "Delete", "saveBut": "Save", "backBut": "Back", "pickDoneBut": "Done", @@ -270,8 +478,8 @@ "notExistTip": "Record not found", "notSetTip": "Not set", "savedTip": "Saved", - "deletedTip": "Records invalidated", - "confirmDeleteTitle": "Invalidate the selected records?", + "deletedTip": "Records deleted", + "confirmDeleteTitle": "Delete the selected records?", "noPermissionText": "No accessible admin function", "menuLoading": "Loading menu…", "menuRetryBut": "Reload", diff --git a/uniapp/youjiala/src/locale/lang/zh-CN.json b/uniapp/youjiala/src/locale/lang/zh-CN.json index 9cea14c..936da73 100644 --- a/uniapp/youjiala/src/locale/lang/zh-CN.json +++ b/uniapp/youjiala/src/locale/lang/zh-CN.json @@ -16,13 +16,221 @@ }, "MinePage": { "title": "我的", + "myTheme": "我的主题", + "mySecurity": "安全管理", + "currentTheme": "当前主题", + "aboutTitle": "关于", + "appName": "应用名称", + "versionLabel": "当前版本", + "platformLabel": "运行平台" + }, + "ThemePage": { + "title": "我的主题", "themeTitle": "主题颜色", "themeHint": "切换后组件配色即时更新,选择会被记住", "darkTitle": "深色模式", "darkAuto": "跟随系统", "darkLight": "浅色", - "darkDark": "深色", - "currentTheme": "当前主题" + "darkDark": "深色" + }, + "SecurityPage": { + "title": "安全管理", + "pageHint": "权限体系与基础设定,仅管理员可操作", + "pendingTag": "待迁移", + "pendingTip": "该功能尚未从旧系统迁移,敬请期待", + "user": "用户管理", + "userDesc": "账号、登录用户与状态", + "actor": "角色管理", + "actorDesc": "安全角色与成员归属", + "group": "用户组管理", + "groupDesc": "用户组与组权限分配", + "function": "功能管理", + "functionDesc": "系统功能与菜单定义", + "permission": "权限管理", + "permissionDesc": "功能授权与权限依赖", + "generator": "编号生成器", + "generatorDesc": "单据编号规则与流水号", + "master": "主档管理", + "masterDesc": "主数据与基础字典设定", + "helpFunction": "帮助功能", + "helpFunctionDesc": "功能帮助说明维护", + "helpFeature": "帮助专题", + "helpFeatureDesc": "帮助专题与分组维护" + }, + "GeneratorPage": { + "title": "编号生成器", + "detailTitle": "编号规则详情", + "systemId": "项目编号", + "subsystemId": "子编号", + "prefix": "前缀", + "refreshPeriod": "重新刷新", + "format": "格式", + "nextId": "编号", + "lastGeneratorDate": "上次发号时间", + "yes": "是", + "no": "否", + "formatRequired": "请输入格式" + }, + "GroupPage": { + "title": "用户组查询", + "detailTitle": "用户组详情", + "groupName": "组别名称", + "groupLocalName": "中文名称", + "groupEngName": "英文名称", + "createdDatetime": "创建日期", + "nameRequired": "请输入组别名称", + "localNameRequired": "请输入中文名称", + "actionView": "查看", + "actionCreate": "新增", + "actionAmend": "修改", + "actionDelete": "删除", + "permissionTitle": "权限明细", + "addPermissionBut": "添加权限", + "permissionEmpty": "暂无权限明细", + "unnamedPermission": "未命名权限", + "dependentLabel": "扩展", + "dependentViewTitle": "扩展权限明细", + "dependentViewEmpty": "暂无扩展权限明细", + "unnamedDependent": "未命名扩展", + "actorTitle": "组内角色", + "addActorBut": "添加角色", + "actorEmpty": "暂无组内角色", + "unnamedActor": "未命名角色" + }, + "FunctionPage": { + "title": "功能查询", + "detailTitle": "功能详情", + "functionName": "功能名称", + "functionLocalName": "中文名称", + "functionEngName": "英文名称", + "functionIndc": "菜单?", + "icon": "图标", + "permissionName": "权限名称", + "pageUrl": "页面地址", + "apiRoute": "接口路由", + "pageName": "页面名称", + "remark": "备注", + "yes": "是", + "no": "否", + "menuItem": "菜单项", + "nameRequired": "请输入功能名称", + "localNameRequired": "请输入中文名称", + "pickPermissionBut": "选择权限", + "parentTitle": "上级目录", + "addParentBut": "添加上级", + "parentEmpty": "暂无上级目录", + "unnamedParent": "未命名上级", + "sonTitle": "下级菜单", + "addSonBut": "添加下级", + "sonEmpty": "暂无下级菜单", + "unnamedSon": "未命名下级" + }, + "PermissionPage": { + "title": "权限查询", + "detailTitle": "权限详情", + "permissionName": "权限名称", + "description": "说明", + "dependent": "扩展", + "createdDatetime": "创建日期", + "delegateIndc": "允许代理?", + "yes": "是", + "no": "否", + "nameRequired": "请输入权限名称", + "descriptionRequired": "请输入说明", + "dependentNameRequired": "请输入扩展名称", + "dependentDescRequired": "请输入扩展中文名称", + "dependentTitle": "扩展权限明细", + "addDependentBut": "添加扩展", + "dependentEmpty": "暂无扩展权限明细", + "dependentDetailName": "扩展名称", + "dependentDetailDescription": "扩展中文名称", + "dependentNamePlaceholder": "扩展权限的名称", + "dependentDescPlaceholder": "扩展权限的中文说明" + }, + "MasterPage": { + "title": "主档查询", + "detailTitle": "主档详情", + "masterCategory": "主档类别", + "masterSubcategory": "子类别", + "masterCodeItem": "代码项", + "localDescription": "中文描述", + "engDescription": "英文描述", + "value": "取值", + "orderBy": "排序", + "validIndc": "有效?", + "remark": "备注", + "yes": "是", + "no": "否", + "categoryRequired": "请输入主档类别", + "subcategoryRequired": "请输入子类别", + "codeItemRequired": "请输入代码项", + "localDescriptionRequired": "请输入中文描述" + }, + "ActorPage": { + "detailTitle": "角色详情", + "actorName": "角色名称", + "actorLocalName": "中文名称", + "actorEngName": "英文名称", + "actorNameRequired": "请输入角色名称", + "localNameRequired": "请输入中文名称", + "groupTitle": "所属组别", + "addGroupBut": "添加组别", + "groupEmpty": "暂无所属组别", + "unnamedGroup": "未命名组别", + "userTitle": "成员用户", + "addUserBut": "添加用户", + "userEmpty": "暂无成员用户", + "unnamedUser": "未命名用户" + }, + "HelpFunctionPage": { + "title": "帮助功能查询", + "detailTitle": "帮助功能详情", + "functionName": "功能名称", + "intro": "功能说明", + "enabledIndc": "启用?", + "yes": "是", + "no": "否", + "helpFunctionId": "编号", + "enabledTag": "启用", + "nameRequired": "请选择功能名称", + "pickFunctionBut": "选择要编写帮助的功能", + "stepTitle": "操作步骤", + "addStepBut": "新增步骤", + "stepEmpty": "暂无操作步骤", + "unnamedGroup": "未选择功能点组", + "stepName": "标题", + "stepDescription": "说明", + "stepTips": "提示", + "stepIcon": "图标", + "stepTitlePlaceholder": "这一步先做什么", + "stepDescPlaceholder": "这一步具体怎么做", + "stepTipsPlaceholder": "补充说明,可留空", + "stepIconPlaceholder": "页面上的图标名", + "featureTitle": "引用的功能点组", + "addFeatureBut": "引用功能点组", + "featureEmpty": "暂未引用功能点组" + }, + "HelpFeaturePage": { + "title": "帮助功能点查询", + "detailTitle": "功能点组详情", + "name": "功能点名称", + "description": "作用说明", + "icon": "图标", + "color": "颜色", + "groupCode": "组编码", + "groupName": "组名称", + "featureCount": "功能点", + "groupTitle": "功能点组", + "groupCodeRequired": "请输入组编码", + "nameRequired": "请输入功能点名称", + "featureTitle": "组内功能点", + "addFeatureBut": "新增功能点", + "featureEmpty": "暂无功能点", + "namePlaceholder": "一般就是按钮上的字", + "descPlaceholder": "这个按钮 / 图标的作用", + "iconPlaceholder": "如 DeleteOutlined", + "colorPlaceholder": "如 #faad14 / red", + "noGroupTip": "该功能点没有所属组" }, "FilePage": { "title": "文件", @@ -262,7 +470,7 @@ "viewBut": "查看", "amendBut": "修改", "createBut": "新增", - "deleteBut": "无效", + "deleteBut": "删除", "saveBut": "保存", "backBut": "返回", "pickDoneBut": "完成选择", @@ -271,7 +479,7 @@ "notSetTip": "未设置", "savedTip": "保存成功", "deletedTip": "记录删除成功", - "confirmDeleteTitle": "确定要作废选中的记录吗?", + "confirmDeleteTitle": "确定要删除选中的记录吗?", "noPermissionText": "没有可访问的管理功能", "menuLoading": "菜单加载中…", "menuRetryBut": "重新加载", diff --git a/uniapp/youjiala/src/pages.json b/uniapp/youjiala/src/pages.json index a5f4b1a..3da21d9 100644 --- a/uniapp/youjiala/src/pages.json +++ b/uniapp/youjiala/src/pages.json @@ -22,6 +22,10 @@ }, // 管理端 security 域(旧 WebVue views/security 的第一批移植): // user-enquiry 列表 / user-operation 详情(?id=&mode=view|create|amend)/ actor-enquiry 角色查询(兼作选择器) + // 安全管理枢纽页(短地址 /security):从「我的」页跳入,聚合权限体系基础设定入口 + { + "path": "pages/security/security" + }, { "path": "pages/security/user-enquiry" }, @@ -31,6 +35,62 @@ { "path": "pages/security/actor-enquiry" }, + // 编号生成器(短地址 /generator-enquiry 列表 + /generator-operation 详情):旧 GeneratorNumberEnquiry/Modal 迁移 + { + "path": "pages/security/generator-enquiry" + }, + { + "path": "pages/security/generator-operation" + }, + // 角色详情(短地址 /actor-operation):旧 ActorOperation 迁移,group/user 双子表选择器 + { + "path": "pages/security/actor-operation" + }, + // 组别(/group-enquiry 列表+选择器、/group-operation 详情):旧 GroupEnquiry/Operation 迁移 + { + "path": "pages/security/group-enquiry" + }, + { + "path": "pages/security/group-operation" + }, + // 功能(/function-enquiry、/function-operation):旧 FunctionEnquiry/Operation 迁移,父子选择器+重排 + { + "path": "pages/security/function-enquiry" + }, + { + "path": "pages/security/function-operation" + }, + // 权限(/permission-enquiry、/permission-operation、/permission-dependent-enquiry):旧 Permission 三页迁移 + { + "path": "pages/security/permission-enquiry" + }, + { + "path": "pages/security/permission-operation" + }, + { + "path": "pages/security/permission-dependent-enquiry" + }, + // 数据字典(/master-enquiry、/master-operation):旧 Master 两页迁移,复合主键 cat|sub|item + { + "path": "pages/security/master-enquiry" + }, + { + "path": "pages/security/master-operation" + }, + // 帮助功能(/help-function-enquiry、/help-function-operation):旧 SecurityHelpFunction 两页迁移 + { + "path": "pages/security/help-function-enquiry" + }, + { + "path": "pages/security/help-function-operation" + }, + // 帮助功能点组(/help-feature-enquiry、/help-feature-operation):旧 SecurityHelpFeature 两页迁移 + { + "path": "pages/security/help-feature-enquiry" + }, + { + "path": "pages/security/help-feature-operation" + }, // 底部导航测试页(首页复用上面的 dashboard):文件/多媒体/共享/我的 { "path": "pages/tab/file" @@ -44,6 +104,10 @@ { "path": "pages/tab/mine" }, + // 我的主题(短地址 /theme):从「我的」页跳入,主题配色与深色模式设置 + { + "path": "pages/theme/theme" + }, // 传输列表:uploadQueue store 的视图页,选文件上传后自动跳入 { "path": "pages/transport/transport" diff --git a/uniapp/youjiala/src/pages/security/actor-enquiry.vue b/uniapp/youjiala/src/pages/security/actor-enquiry.vue index 75fe2d8..3f4aed4 100644 --- a/uniapp/youjiala/src/pages/security/actor-enquiry.vue +++ b/uniapp/youjiala/src/pages/security/actor-enquiry.vue @@ -3,7 +3,7 @@ * 角色查询(ActorEnquiry):旧 WebVue views/security/ActorEnquiry.vue 的移动端等价物 * * 它有两种打开方式,同一套列表: - * 1. 从菜单进(无 channel):普通查询页,点行看详情(本批暂未接详情,仅回传/返回); + * 1. 从菜单进(无 channel):普通查询页(点行看详情,右上可新增、长按多选可批量删除); * 2. 被 user-operation 当"角色选择器"跳进来(带 ?channel=&multiple=1): * 选完 uni.$emit 回传再 navigateBack(见 useAdminPick / useAdminPickTarget 协议)。 * @@ -20,102 +20,160 @@ import { SecurityActorEntity } from "@/data/auto/securityActorEntity"; import { SecurityActorSearchCriteria } from "@/data/auto/securityActorSearchCriteria"; import { SearchFilterOperatorPolicy } from "@/data/custom/searchFilterOperatorPolicy"; import { SearchFilterTypePolicy } from "@/data/custom/searchFilterTypePolicy"; +import { usePagePermission } from "@/permission/permission-context"; +import { pageUrl } from "@/router/routes"; import securityActorService from "@/service/security/securityActorService"; const { t } = useLocale(); const pickTarget = useAdminPickTarget(); +const perm = usePagePermission("actorEnquiry"); + +/** 选择器模式:带 channel 进来时点行回传;否则作为普通菜单页点行进详情 */ +const isPick = ref(false); const bindOptions = reactive([ - makeFilterOption( - SecurityActorEntity.DB_ACTOR_LOCAL_NAME, - t("UserPage.actorLocalName"), - SearchFilterTypePolicy.TEXT, - SearchFilterOperatorPolicy.CONTAIN, - ), + makeFilterOption( + SecurityActorEntity.DB_ACTOR_LOCAL_NAME, + t("UserPage.actorLocalName"), + SearchFilterTypePolicy.TEXT, + SearchFilterOperatorPolicy.CONTAIN, + ), ]); async function fetchData(query: AdminQuery) { - const criteria = new SecurityActorSearchCriteria(); - criteria.GridColumns = []; - fillCriteria(criteria, query.filters, query); - const data = await securityActorService.FindBySearchCriteria(criteria); - return { rows: data?.Datas ?? [], total: data?.Total ?? 0 }; + const criteria = new SecurityActorSearchCriteria(); + criteria.GridColumns = []; + fillCriteria(criteria, query.filters, query); + const data = await securityActorService.FindBySearchCriteria(criteria); + return { rows: data?.Datas ?? [], total: data?.Total ?? 0 }; } const state = useAdminList({ bindOptions, loader: fetchData }); const { filterCount, filterVisible, draft, searchToken } = state; const columns = computed(() => [ - { - label: t("UserPage.actorLocalName"), - prop: SecurityActorEntity.ACTOR_LOCAL_NAME, - }, - { label: t("UserPage.actorName"), prop: SecurityActorEntity.ACTOR_NAME }, + { + label: t("UserPage.actorLocalName"), + prop: SecurityActorEntity.ACTOR_LOCAL_NAME, + }, + { label: t("UserPage.actorName"), prop: SecurityActorEntity.ACTOR_NAME }, ]); const keyword = ref(""); const listRef = ref(null); function onSearch(): void { - const option = bindOptions.find( - item => item.BindValue === SecurityActorEntity.DB_ACTOR_LOCAL_NAME, - ); - if (option) { - option.Value = keyword.value; - } - state.reload(); + const option = bindOptions.find( + item => item.BindValue === SecurityActorEntity.DB_ACTOR_LOCAL_NAME, + ); + if (option) { + option.Value = keyword.value; + } + state.reload(); } function onRowTap(row: AdminRow): void { - // 多选态交给勾选,不在这里回传;单选态点一行即选中 + // 选择器模式:多选态交给勾选,单选态点一行即选中回传 + if (isPick.value) { if (pickTarget.multiple.value) { - return; + return; } pickTarget.deliverOne(row); + return; + } + // 菜单模式:进角色详情(旧页面双击行开编辑弹窗,移动端改点行进详情页) + uni.navigateTo({ + url: `${pageUrl("actorOperation")}?id=${row.ActorName}&mode=view`, + }); } function onConfirm(rows: AdminRow[]): void { - if (!rows.length) { - uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" }); + if (!rows.length) { + uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" }); + return; + } + pickTarget.deliver(rows); +} + +function onCreate(): void { + uni.navigateTo({ url: `${pageUrl("actorOperation")}?mode=create` }); +} + +async function onDelete(rows: AdminRow[]): Promise { + if (!rows.length) { + uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" }); + return; + } + uni.showModal({ + title: t("AdminPage.confirmDeleteTitle"), + success: async (res: any) => { + if (!res.confirm) { return; - } - pickTarget.deliver(rows); + } + // 旧页面也是循环单条 Delete(非 Deletes),这里保持一致 + for (const row of rows) { + await securityActorService.Delete(String(row.ActorName ?? "")); + } + listRef.value?.clearSelection(); + state.reload(); + uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" }); + }, + }); } onLoad((options) => { - pickTarget.readOptions(options); + pickTarget.readOptions(options); + isPick.value = String(options?.channel ?? "").trim().length > 0; }); -let booted = false; useAuthGuard(() => { - if (booted) { - return; - } - booted = true; - nextTick(() => listRef.value?.refresh()); + // 每次显示都重拉第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面) + nextTick(() => listRef.value?.refresh()); }); diff --git a/uniapp/youjiala/src/pages/security/actor-operation.vue b/uniapp/youjiala/src/pages/security/actor-operation.vue new file mode 100644 index 0000000..538f210 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/actor-operation.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/function-enquiry.vue b/uniapp/youjiala/src/pages/security/function-enquiry.vue new file mode 100644 index 0000000..25a9aa5 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/function-enquiry.vue @@ -0,0 +1,246 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/function-operation.vue b/uniapp/youjiala/src/pages/security/function-operation.vue new file mode 100644 index 0000000..787b3ae --- /dev/null +++ b/uniapp/youjiala/src/pages/security/function-operation.vue @@ -0,0 +1,495 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/generator-enquiry.vue b/uniapp/youjiala/src/pages/security/generator-enquiry.vue new file mode 100644 index 0000000..f26aaa5 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/generator-enquiry.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/generator-operation.vue b/uniapp/youjiala/src/pages/security/generator-operation.vue new file mode 100644 index 0000000..c805a7d --- /dev/null +++ b/uniapp/youjiala/src/pages/security/generator-operation.vue @@ -0,0 +1,145 @@ + + + diff --git a/uniapp/youjiala/src/pages/security/group-enquiry.vue b/uniapp/youjiala/src/pages/security/group-enquiry.vue new file mode 100644 index 0000000..ceedef7 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/group-enquiry.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/group-operation.vue b/uniapp/youjiala/src/pages/security/group-operation.vue new file mode 100644 index 0000000..cb54cc2 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/group-operation.vue @@ -0,0 +1,596 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/help-feature-enquiry.vue b/uniapp/youjiala/src/pages/security/help-feature-enquiry.vue new file mode 100644 index 0000000..470f70b --- /dev/null +++ b/uniapp/youjiala/src/pages/security/help-feature-enquiry.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/help-feature-operation.vue b/uniapp/youjiala/src/pages/security/help-feature-operation.vue new file mode 100644 index 0000000..bf134d9 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/help-feature-operation.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/help-function-enquiry.vue b/uniapp/youjiala/src/pages/security/help-function-enquiry.vue new file mode 100644 index 0000000..0835367 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/help-function-enquiry.vue @@ -0,0 +1,210 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/help-function-operation.vue b/uniapp/youjiala/src/pages/security/help-function-operation.vue new file mode 100644 index 0000000..b9c0c1a --- /dev/null +++ b/uniapp/youjiala/src/pages/security/help-function-operation.vue @@ -0,0 +1,578 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/master-enquiry.vue b/uniapp/youjiala/src/pages/security/master-enquiry.vue new file mode 100644 index 0000000..7d88ad0 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/master-enquiry.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/master-operation.vue b/uniapp/youjiala/src/pages/security/master-operation.vue new file mode 100644 index 0000000..588b309 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/master-operation.vue @@ -0,0 +1,202 @@ + + + diff --git a/uniapp/youjiala/src/pages/security/permission-dependent-enquiry.vue b/uniapp/youjiala/src/pages/security/permission-dependent-enquiry.vue new file mode 100644 index 0000000..dd3b013 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/permission-dependent-enquiry.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/permission-enquiry.vue b/uniapp/youjiala/src/pages/security/permission-enquiry.vue new file mode 100644 index 0000000..aec7b81 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/permission-enquiry.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/permission-operation.vue b/uniapp/youjiala/src/pages/security/permission-operation.vue new file mode 100644 index 0000000..b6e944a --- /dev/null +++ b/uniapp/youjiala/src/pages/security/permission-operation.vue @@ -0,0 +1,355 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/security.vue b/uniapp/youjiala/src/pages/security/security.vue new file mode 100644 index 0000000..4420fe0 --- /dev/null +++ b/uniapp/youjiala/src/pages/security/security.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/uniapp/youjiala/src/pages/security/user-enquiry.vue b/uniapp/youjiala/src/pages/security/user-enquiry.vue index cca7337..9f8180e 100644 --- a/uniapp/youjiala/src/pages/security/user-enquiry.vue +++ b/uniapp/youjiala/src/pages/security/user-enquiry.vue @@ -9,10 +9,11 @@ * 页面只组数据与路由,列表/筛选/分页的机制全在 admin-list + useAdminList 里。 */ import type { AdminColumn, AdminQuery, AdminRow } from "@/admin/admin-types"; +import { onLoad } from "@dcloudio/uni-app"; import { useLocale } from "uview-pro"; import { computed, nextTick, reactive, ref, watch } from "vue"; import { fillCriteria, makeFilterOption } from "@/admin/admin-filter"; -import { useAdminList, useAuthGuard } from "@/composables"; +import { useAdminList, useAdminPickTarget, useAuthGuard } from "@/composables"; import { SecurityUserEntity } from "@/data/auto/securityUserEntity"; import { SecurityUserSearchCriteria } from "@/data/auto/securityUserSearchCriteria"; import { SearchFilterOperatorPolicy } from "@/data/custom/searchFilterOperatorPolicy"; @@ -23,6 +24,10 @@ import securityUserService from "@/service/security/securityUserService"; const { t, currentLocale } = useLocale(); const perm = usePagePermission("userEnquiry"); +const pickTarget = useAdminPickTarget(); + +/** 选择器模式:带 channel 进来(如 actor-operation 选用户)时点行回传而非跳详情 */ +const isPick = ref(false); /** 列名 -> i18n key:抽屉标签、卡片行标签都靠它,切换语言时就地刷新而不重建条件 */ const LABEL_KEYS: Record = { @@ -102,6 +107,13 @@ function onSearch(): void { } function onRowTap(row: AdminRow): void { + if (isPick.value) { + if (pickTarget.multiple.value) { + return; + } + pickTarget.deliverOne(row); + return; + } // 与旧页面一致:id 用 UserName,mode 决定详情页是只读还是编辑 // 不做 encodeURIComponent:见 composables/useAdminPick 的说明(H5 二次编码坑) uni.navigateTo({ @@ -109,6 +121,14 @@ function onRowTap(row: AdminRow): void { }); } +function onConfirm(rows: AdminRow[]): void { + if (!rows.length) { + uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" }); + return; + } + pickTarget.deliver(rows); +} + function onCreate(): void { uni.navigateTo({ url: `${pageUrl("userOperation")}?mode=create` }); } @@ -145,33 +165,37 @@ watch(currentLocale, () => { } }); +onLoad((options) => { + pickTarget.readOptions(options); + isPick.value = String(options?.channel ?? "").trim().length > 0; +}); + // 取数排在登录守卫之后:admin-list 的 auto 默认 false,首屏由这里手动触发一次 -let booted = false; useAuthGuard(() => { - if (booted) { - return; - } - booted = true; + // 每次显示都重拉第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面) nextTick(() => listRef.value?.refresh()); });