smart-admin-web 10.14.0 → 10.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/README.md +5 -5
  2. package/dist/api/index.d.ts +37 -0
  3. package/dist/api/index.js +35 -14
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/schema.d.ts +381 -3
  6. package/dist/assets/icons/ph-subset.json.js +3 -3
  7. package/dist/assets/smart-logo.svg.js +5 -0
  8. package/dist/assets/smart-logo.svg.js.map +1 -0
  9. package/dist/components/SmartLogo.vue.d.ts +3 -0
  10. package/dist/components/SmartLogo.vue.js +1 -1
  11. package/dist/components/SmartLogo2.vue.js +22 -64
  12. package/dist/components/SmartLogo2.vue.js.map +1 -1
  13. package/dist/composables/useLoginHero.d.ts +2 -0
  14. package/dist/composables/useLoginHero.js +22 -0
  15. package/dist/composables/useLoginHero.js.map +1 -0
  16. package/dist/composables/usePageTitle.js +16 -14
  17. package/dist/composables/usePageTitle.js.map +1 -1
  18. package/dist/composables/useSite.d.ts +5 -0
  19. package/dist/composables/useSite.js +16 -13
  20. package/dist/composables/useSite.js.map +1 -1
  21. package/dist/index.js +92 -91
  22. package/dist/lib/favicon.d.ts +1 -0
  23. package/dist/lib/favicon.js +12 -0
  24. package/dist/lib/favicon.js.map +1 -0
  25. package/dist/lib/loginHero.d.ts +16 -0
  26. package/dist/lib/loginHero.js +33 -0
  27. package/dist/lib/loginHero.js.map +1 -0
  28. package/dist/lib/logoCrop.d.ts +25 -0
  29. package/dist/lib/logoCrop.js +22 -0
  30. package/dist/lib/logoCrop.js.map +1 -0
  31. package/dist/lib/runtime.d.ts +2 -0
  32. package/dist/lib/runtime.js +1 -1
  33. package/dist/lib/runtime.js.map +1 -1
  34. package/dist/lib/securityRules.d.ts +26 -0
  35. package/dist/lib/securityRules.js +86 -0
  36. package/dist/lib/securityRules.js.map +1 -0
  37. package/dist/locales/en-US.d.ts +242 -28
  38. package/dist/locales/en-US.js +262 -58
  39. package/dist/locales/en-US.js.map +1 -1
  40. package/dist/locales/zh-CN.d.ts +242 -28
  41. package/dist/locales/zh-CN.js +257 -58
  42. package/dist/locales/zh-CN.js.map +1 -1
  43. package/dist/router/viewRegistry.js +10 -10
  44. package/dist/router/viewRegistry.js.map +1 -1
  45. package/dist/style.css +1 -1
  46. package/dist/utils/oauthBrand.d.ts +34 -9
  47. package/dist/utils/oauthBrand.js +70 -74
  48. package/dist/utils/oauthBrand.js.map +1 -1
  49. package/dist/views/login/components/LoginHeroPanel.vue.d.ts +18 -0
  50. package/dist/views/login/components/LoginHeroPanel.vue.js +8 -0
  51. package/dist/views/login/components/LoginHeroPanel.vue.js.map +1 -0
  52. package/dist/views/login/components/LoginHeroPanel2.vue.js +66 -0
  53. package/dist/views/login/components/LoginHeroPanel2.vue.js.map +1 -0
  54. package/dist/views/login/skins/SplitPanel.vue.js +1 -1
  55. package/dist/views/login/skins/SplitPanel2.vue.js +39 -59
  56. package/dist/views/login/skins/SplitPanel2.vue.js.map +1 -1
  57. package/dist/views/system/config/components/AdminMock.vue.js +8 -0
  58. package/dist/views/system/config/components/AdminMock.vue.js.map +1 -0
  59. package/dist/views/system/config/components/AdminMock2.vue.js +76 -0
  60. package/dist/views/system/config/components/AdminMock2.vue.js.map +1 -0
  61. package/dist/views/system/config/components/AdvancedSection.vue.js +5 -0
  62. package/dist/views/system/config/components/AdvancedSection.vue.js.map +1 -0
  63. package/dist/views/system/config/components/AdvancedSection2.vue.js +135 -0
  64. package/dist/views/system/config/components/AdvancedSection2.vue.js.map +1 -0
  65. package/dist/views/system/config/components/BrandSection.vue.d.ts +7 -0
  66. package/dist/views/system/config/components/BrandSection.vue.js +8 -0
  67. package/dist/views/system/config/components/BrandSection.vue.js.map +1 -0
  68. package/dist/views/system/config/components/BrandSection2.vue.js +264 -0
  69. package/dist/views/system/config/components/BrandSection2.vue.js.map +1 -0
  70. package/dist/views/system/config/components/CfgGroup.vue.d.ts +18 -0
  71. package/dist/views/system/config/components/CfgGroup.vue.js +8 -0
  72. package/dist/views/system/config/components/CfgGroup.vue.js.map +1 -0
  73. package/dist/views/system/config/components/CfgGroup2.vue.js +22 -0
  74. package/dist/views/system/config/components/CfgGroup2.vue.js.map +1 -0
  75. package/dist/views/system/config/components/CfgRow.vue.d.ts +26 -0
  76. package/dist/views/system/config/components/CfgRow.vue.js +9 -0
  77. package/dist/views/system/config/components/CfgRow.vue.js.map +1 -0
  78. package/dist/views/system/config/components/CfgRow2.vue.js +53 -0
  79. package/dist/views/system/config/components/CfgRow2.vue.js.map +1 -0
  80. package/dist/views/system/config/components/CfgSegment.vue.d.ts +28 -0
  81. package/dist/views/system/config/components/CfgSegment.vue.js +8 -0
  82. package/dist/views/system/config/components/CfgSegment.vue.js.map +1 -0
  83. package/dist/views/system/config/components/CfgSegment2.vue.js +44 -0
  84. package/dist/views/system/config/components/CfgSegment2.vue.js.map +1 -0
  85. package/dist/views/system/config/components/ConfigFormModal.vue.js +5 -2
  86. package/dist/views/system/config/components/ConfigFormModal.vue.js.map +1 -1
  87. package/dist/views/system/config/components/ConfigFormModal2.vue.js +66 -56
  88. package/dist/views/system/config/components/ConfigFormModal2.vue.js.map +1 -1
  89. package/dist/views/system/config/components/JobSection.vue.js +8 -0
  90. package/dist/views/system/config/components/JobSection.vue.js.map +1 -0
  91. package/dist/views/system/config/components/JobSection2.vue.js +138 -0
  92. package/dist/views/system/config/components/JobSection2.vue.js.map +1 -0
  93. package/dist/views/system/config/components/{GroupExtraConfig.vue.d.ts → LoginMock.vue.d.ts} +3 -4
  94. package/dist/views/system/config/components/LoginMock.vue.js +8 -0
  95. package/dist/views/system/config/components/LoginMock.vue.js.map +1 -0
  96. package/dist/views/system/config/components/LoginMock2.vue.js +121 -0
  97. package/dist/views/system/config/components/LoginMock2.vue.js.map +1 -0
  98. package/dist/views/system/config/components/LogoCropper.vue.d.ts +10 -0
  99. package/dist/views/system/config/components/LogoCropper.vue.js +8 -0
  100. package/dist/views/system/config/components/LogoCropper.vue.js.map +1 -0
  101. package/dist/views/system/config/components/LogoCropper2.vue.js +122 -0
  102. package/dist/views/system/config/components/LogoCropper2.vue.js.map +1 -0
  103. package/dist/views/system/config/components/LogoEditor.vue.js +8 -0
  104. package/dist/views/system/config/components/LogoEditor.vue.js.map +1 -0
  105. package/dist/views/system/config/components/LogoEditor2.vue.js +163 -0
  106. package/dist/views/system/config/components/LogoEditor2.vue.js.map +1 -0
  107. package/dist/views/system/config/components/ProviderSheet.vue.d.ts +17 -0
  108. package/dist/views/system/config/components/ProviderSheet.vue.js +8 -0
  109. package/dist/views/system/config/components/ProviderSheet.vue.js.map +1 -0
  110. package/dist/views/system/config/components/ProviderSheet2.vue.js +343 -0
  111. package/dist/views/system/config/components/ProviderSheet2.vue.js.map +1 -0
  112. package/dist/views/system/config/components/ScaleToFit.vue.d.ts +17 -0
  113. package/dist/views/system/config/components/ScaleToFit.vue.js +8 -0
  114. package/dist/views/system/config/components/ScaleToFit.vue.js.map +1 -0
  115. package/dist/views/system/config/components/ScaleToFit2.vue.js +41 -0
  116. package/dist/views/system/config/components/ScaleToFit2.vue.js.map +1 -0
  117. package/dist/views/system/config/components/SectionLayout.vue.d.ts +28 -0
  118. package/dist/views/system/config/components/SectionLayout.vue.js +8 -0
  119. package/dist/views/system/config/components/SectionLayout.vue.js.map +1 -0
  120. package/dist/views/system/config/components/SectionLayout2.vue.js +45 -0
  121. package/dist/views/system/config/components/SectionLayout2.vue.js.map +1 -0
  122. package/dist/views/system/config/components/SecuritySection.vue.js +8 -0
  123. package/dist/views/system/config/components/SecuritySection.vue.js.map +1 -0
  124. package/dist/views/system/config/components/SecuritySection2.vue.js +416 -0
  125. package/dist/views/system/config/components/SecuritySection2.vue.js.map +1 -0
  126. package/dist/views/system/config/components/SensitiveSection.vue.js +8 -0
  127. package/dist/views/system/config/components/SensitiveSection.vue.js.map +1 -0
  128. package/dist/views/system/config/components/SensitiveSection2.vue.js +209 -0
  129. package/dist/views/system/config/components/SensitiveSection2.vue.js.map +1 -0
  130. package/dist/views/system/config/components/SigninSection.vue.js +8 -0
  131. package/dist/views/system/config/components/SigninSection.vue.js.map +1 -0
  132. package/dist/views/system/config/components/SigninSection2.vue.js +201 -0
  133. package/dist/views/system/config/components/SigninSection2.vue.js.map +1 -0
  134. package/dist/views/system/config/components/UploadSection.vue.d.ts +3 -0
  135. package/dist/views/system/config/components/UploadSection.vue.js +8 -0
  136. package/dist/views/system/config/components/UploadSection.vue.js.map +1 -0
  137. package/dist/views/system/config/components/UploadSection2.vue.js +167 -0
  138. package/dist/views/system/config/components/UploadSection2.vue.js.map +1 -0
  139. package/dist/views/system/config/draft.d.ts +148 -0
  140. package/dist/views/system/config/draft.js +170 -0
  141. package/dist/views/system/config/draft.js.map +1 -0
  142. package/dist/views/system/config/flash.d.ts +2 -0
  143. package/dist/views/system/config/flash.js +9 -0
  144. package/dist/views/system/config/flash.js.map +1 -0
  145. package/dist/views/system/config/groups.d.ts +35 -7
  146. package/dist/views/system/config/groups.js +99 -14
  147. package/dist/views/system/config/groups.js.map +1 -1
  148. package/dist/views/system/config/index.vue.js +4 -1
  149. package/dist/views/system/config/index.vue.js.map +1 -1
  150. package/dist/views/system/config/index2.vue.js +180 -115
  151. package/dist/views/system/config/index2.vue.js.map +1 -1
  152. package/dist/views/system/config/providerForm.d.ts +33 -0
  153. package/dist/views/system/config/providerForm.js +63 -0
  154. package/dist/views/system/config/providerForm.js.map +1 -0
  155. package/package.json +4 -4
  156. package/dist/views/system/config/components/ExternalAuthConfig.vue.js +0 -8
  157. package/dist/views/system/config/components/ExternalAuthConfig.vue.js.map +0 -1
  158. package/dist/views/system/config/components/ExternalAuthConfig2.vue.js +0 -218
  159. package/dist/views/system/config/components/ExternalAuthConfig2.vue.js.map +0 -1
  160. package/dist/views/system/config/components/GroupExtraConfig.vue.js +0 -8
  161. package/dist/views/system/config/components/GroupExtraConfig.vue.js.map +0 -1
  162. package/dist/views/system/config/components/GroupExtraConfig2.vue.js +0 -108
  163. package/dist/views/system/config/components/GroupExtraConfig2.vue.js.map +0 -1
  164. package/dist/views/system/config/components/HighSensConfig.vue.js +0 -5
  165. package/dist/views/system/config/components/HighSensConfig.vue.js.map +0 -1
  166. package/dist/views/system/config/components/HighSensConfig2.vue.js +0 -134
  167. package/dist/views/system/config/components/HighSensConfig2.vue.js.map +0 -1
  168. package/dist/views/system/config/components/JobConfig.vue.js +0 -5
  169. package/dist/views/system/config/components/JobConfig.vue.js.map +0 -1
  170. package/dist/views/system/config/components/JobConfig2.vue.js +0 -116
  171. package/dist/views/system/config/components/JobConfig2.vue.js.map +0 -1
  172. package/dist/views/system/config/components/OtherConfig.vue.js +0 -5
  173. package/dist/views/system/config/components/OtherConfig.vue.js.map +0 -1
  174. package/dist/views/system/config/components/OtherConfig2.vue.js +0 -121
  175. package/dist/views/system/config/components/OtherConfig2.vue.js.map +0 -1
  176. package/dist/views/system/config/components/SecurityConfig.vue.js +0 -8
  177. package/dist/views/system/config/components/SecurityConfig.vue.js.map +0 -1
  178. package/dist/views/system/config/components/SecurityConfig2.vue.js +0 -456
  179. package/dist/views/system/config/components/SecurityConfig2.vue.js.map +0 -1
  180. package/dist/views/system/config/components/SysBaseConfig.vue.js +0 -5
  181. package/dist/views/system/config/components/SysBaseConfig.vue.js.map +0 -1
  182. package/dist/views/system/config/components/SysBaseConfig2.vue.js +0 -107
  183. package/dist/views/system/config/components/SysBaseConfig2.vue.js.map +0 -1
  184. package/dist/views/system/config/components/UploadConfig.vue.js +0 -5
  185. package/dist/views/system/config/components/UploadConfig.vue.js.map +0 -1
  186. package/dist/views/system/config/components/UploadConfig2.vue.js +0 -135
  187. package/dist/views/system/config/components/UploadConfig2.vue.js.map +0 -1
  188. /package/dist/views/system/config/components/{ExternalAuthConfig.vue.d.ts → AdminMock.vue.d.ts} +0 -0
  189. /package/dist/views/system/config/components/{HighSensConfig.vue.d.ts → AdvancedSection.vue.d.ts} +0 -0
  190. /package/dist/views/system/config/components/{JobConfig.vue.d.ts → JobSection.vue.d.ts} +0 -0
  191. /package/dist/views/system/config/components/{OtherConfig.vue.d.ts → LogoEditor.vue.d.ts} +0 -0
  192. /package/dist/views/system/config/components/{SecurityConfig.vue.d.ts → SecuritySection.vue.d.ts} +0 -0
  193. /package/dist/views/system/config/components/{SysBaseConfig.vue.d.ts → SensitiveSection.vue.d.ts} +0 -0
  194. /package/dist/views/system/config/components/{UploadConfig.vue.d.ts → SigninSection.vue.d.ts} +0 -0
@@ -1,18 +1,103 @@
1
- import { ref as o } from "vue";
2
- const e = {
3
- sys: "base",
4
- security: "security",
5
- externalauth: "externalAuth",
6
- upload: "upload",
7
- job: "job"
8
- }, s = Object.keys(e), t = o(0);
9
- function i() {
10
- t.value++;
1
+ import { ref as e } from "vue";
2
+ const L = [
3
+ "brand",
4
+ "security",
5
+ "signin",
6
+ "sensitive",
7
+ "upload",
8
+ "job",
9
+ "advanced"
10
+ ], O = [
11
+ ["brand", "security", "signin", "sensitive"],
12
+ ["upload", "job", "advanced"]
13
+ ], T = {
14
+ brand: "ph:browser",
15
+ security: "ph:shield-check",
16
+ signin: "ph:sign-in",
17
+ sensitive: "ph:lock-key",
18
+ upload: "ph:cloud-arrow-up",
19
+ job: "ph:clock",
20
+ advanced: "ph:sliders-horizontal"
21
+ }, i = "sys.site.logo", r = [
22
+ "sys.site.title",
23
+ i,
24
+ "sys.site.copyright",
25
+ "sys.site.copyrightUrl"
26
+ ], o = ["zh-CN", "en-US"], n = (s) => ({
27
+ headline: `sys.login.hero.headline.${s}`,
28
+ highlight: `sys.login.hero.highlight.${s}`,
29
+ features: `sys.login.hero.features.${s}`
30
+ }), c = "sys.site.subtitle", y = "sys.login.hero.showFeatures", a = [
31
+ c,
32
+ y,
33
+ ...o.flatMap((s) => Object.values(n(s)))
34
+ ], u = {
35
+ "sys.security.loginLock.maxFailCount": 0,
36
+ "sys.security.loginLock.lockMinutes": 1,
37
+ "sys.security.password.minLength": 1,
38
+ "sys.security.password.expireDays": 0,
39
+ "sys.security.password.historyCount": 0,
40
+ "sys.security.session.accessMinutes": 1,
41
+ "sys.security.session.refreshMinutes": 1,
42
+ "sys.security.rateLimit.windowSeconds": 1,
43
+ "sys.security.rateLimit.permitPerWindow": 0,
44
+ "sys.security.rateLimit.authPermitPerWindow": 0
45
+ }, t = "sys.security.smsLogin.enabled", d = [
46
+ "sys.security.password.requireUpper",
47
+ "sys.security.password.requireLower",
48
+ "sys.security.password.requireDigit",
49
+ "sys.security.password.requireSpecial",
50
+ "sys.security.password.forceChangeOnFirstLogin",
51
+ "sys.security.captcha.enabled",
52
+ "sys.security.totp.enabled",
53
+ "sys.security.totp.requireForSuperAdmin",
54
+ "sys.security.mfa.enabled",
55
+ "sys.security.rateLimit.enabled"
56
+ ], l = "sys.security.captcha.type", h = [
57
+ ...Object.keys(u),
58
+ ...d,
59
+ l,
60
+ t
61
+ ], p = "sys.upload.maxSizeMb", E = "sys.upload.allowedExtensions", g = "sys.job.logRetentionDays", S = "sys.job.alertEmails", f = [
62
+ ...r,
63
+ ...a,
64
+ ...h,
65
+ p,
66
+ E,
67
+ g,
68
+ S
69
+ ], w = ["sys", "login", "security", "upload", "job"];
70
+ function C(s) {
71
+ return s.startsWith("sys.site.") || s.startsWith("sys.login.") ? "brand" : s === t || s.startsWith("sys.externalauth.") ? "signin" : s.startsWith("sys.security.") ? "security" : s.startsWith("sys.upload.") ? "upload" : s.startsWith("sys.job.") ? "job" : "advanced";
72
+ }
73
+ const _ = e(0);
74
+ function Y() {
75
+ _.value++;
11
76
  }
12
77
  export {
13
- s as STRUCTURED_GROUPS,
14
- e as STRUCTURED_GROUP_TABS,
15
- i as bumpConfigRevision,
16
- t as configRevision
78
+ r as BRAND_KEYS,
79
+ l as CAPTCHA_TYPE_KEY,
80
+ L as CONFIG_TABS,
81
+ S as JOB_ALERT_KEY,
82
+ g as JOB_RETENTION_KEY,
83
+ a as LOGIN_KEYS,
84
+ o as LOGIN_LOCALES,
85
+ i as LOGO_KEY,
86
+ h as SECURITY_KEYS,
87
+ u as SECURITY_NUMBERS,
88
+ d as SECURITY_SWITCHES,
89
+ y as SHOW_FEATURES_KEY,
90
+ t as SMS_LOGIN_KEY,
91
+ f as STATIC_CLAIMED_KEYS,
92
+ w as STRUCTURED_GROUPS,
93
+ c as SUBTITLE_KEY,
94
+ O as TAB_GROUPS,
95
+ T as TAB_ICONS,
96
+ E as UPLOAD_EXTS_KEY,
97
+ p as UPLOAD_MAX_KEY,
98
+ Y as bumpConfigRevision,
99
+ _ as configRevision,
100
+ n as heroKeys,
101
+ C as tabOfKey
17
102
  };
18
103
  //# sourceMappingURL=groups.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"groups.js","sources":["../../../../src/views/system/config/groups.ts"],"sourcesContent":["// 分类配置中心的分组归属:某分组由某结构化 Tab 管理,该组的行都显示在那个 Tab 里\n// (Tab 认领的键渲染成字段,其余在「本组其它配置」);「其他配置」只列不归任何结构化 Tab 的分组。\nimport { ref } from 'vue'\n\n/** 结构化分组 → 管它的 Tab(值是 config.tab.* 的 i18n 子键)。 */\nexport const STRUCTURED_GROUP_TABS: Readonly<Record<string, string>> = {\n sys: 'base',\n security: 'security',\n externalauth: 'externalAuth',\n upload: 'upload',\n job: 'job',\n}\n\nexport const STRUCTURED_GROUPS = Object.keys(STRUCTURED_GROUP_TABS)\n\n/**\n * 配置行增删改的版本号。各 Tab 以 show:lazy 常驻,切回来不会重新挂载;\n * 行被改动后 bump 一次,「本组其它配置」据此重拉,不用刷新整页。\n */\nexport const configRevision = ref(0)\n\nexport function bumpConfigRevision() {\n configRevision.value++\n}\n"],"names":["STRUCTURED_GROUP_TABS","STRUCTURED_GROUPS","configRevision","ref","bumpConfigRevision"],"mappings":";AAKO,MAAMA,IAA0D;AAAA,EACrE,KAAK;AAAA,EACL,UAAU;AAAA,EACV,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,KAAK;AACP,GAEaC,IAAoB,OAAO,KAAKD,CAAqB,GAMrDE,IAAiBC,EAAI,CAAC;AAE5B,SAASC,IAAqB;AACnC,EAAAF,EAAe;AACjB;"}
1
+ {"version":3,"file":"groups.js","sources":["../../../../src/views/system/config/groups.ts"],"sourcesContent":["// 配置中心的分类与键归属。顶部导航每一项是一个分类,分类名就是它管的那一类东西;\n// 结构化表单认领的键由这里集中声明,\n// 草稿只装这些键,「高级」页据此排除它们(同一个键不会在两处都能改)。\nimport { ref } from 'vue'\n\nexport const CONFIG_TABS = [\n 'brand',\n 'security',\n 'signin',\n 'sensitive',\n 'upload',\n 'job',\n 'advanced',\n] as const\nexport type ConfigTab = (typeof CONFIG_TABS)[number]\n\n/** 顶部导航分组:站点与安全 | 系统,组间留空。 */\nexport const TAB_GROUPS: readonly (readonly ConfigTab[])[] = [\n ['brand', 'security', 'signin', 'sensitive'],\n ['upload', 'job', 'advanced'],\n]\n\n/** 导航图标 */\nexport const TAB_ICONS: Record<ConfigTab, string> = {\n brand: 'ph:browser',\n security: 'ph:shield-check',\n signin: 'ph:sign-in',\n sensitive: 'ph:lock-key',\n upload: 'ph:cloud-arrow-up',\n job: 'ph:clock',\n advanced: 'ph:sliders-horizontal',\n}\n\nexport const LOGO_KEY = 'sys.site.logo'\nexport const BRAND_KEYS = [\n 'sys.site.title',\n LOGO_KEY,\n 'sys.site.copyright',\n 'sys.site.copyrightUrl',\n] as const\n\nexport const LOGIN_LOCALES = ['zh-CN', 'en-US'] as const\nexport type LoginLocale = (typeof LOGIN_LOCALES)[number]\nexport const heroKeys = (locale: LoginLocale) => ({\n headline: `sys.login.hero.headline.${locale}`,\n highlight: `sys.login.hero.highlight.${locale}`,\n features: `sys.login.hero.features.${locale}`,\n})\nexport const SUBTITLE_KEY = 'sys.site.subtitle'\nexport const SHOW_FEATURES_KEY = 'sys.login.hero.showFeatures'\nexport const LOGIN_KEYS = [\n SUBTITLE_KEY,\n SHOW_FEATURES_KEY,\n ...LOGIN_LOCALES.flatMap(l => Object.values(heroKeys(l))),\n]\n\n// 数值项的下限:锁定阈值不为负、时长至少 1 分钟,避免存 0 变成永不过期/永久锁定。\n// 0 在少数项上是合法的\"关闭\"语义(永不过期、不防重用、不限流)。\nexport const SECURITY_NUMBERS: Readonly<Record<string, number>> = {\n 'sys.security.loginLock.maxFailCount': 0,\n 'sys.security.loginLock.lockMinutes': 1,\n 'sys.security.password.minLength': 1,\n 'sys.security.password.expireDays': 0,\n 'sys.security.password.historyCount': 0,\n 'sys.security.session.accessMinutes': 1,\n 'sys.security.session.refreshMinutes': 1,\n 'sys.security.rateLimit.windowSeconds': 1,\n 'sys.security.rateLimit.permitPerWindow': 0,\n 'sys.security.rateLimit.authPermitPerWindow': 0,\n}\n/** 短信验证码登录是一种登录方式,在「登录方式」页,不在安全策略 */\nexport const SMS_LOGIN_KEY = 'sys.security.smsLogin.enabled'\nexport const SECURITY_SWITCHES = [\n 'sys.security.password.requireUpper',\n 'sys.security.password.requireLower',\n 'sys.security.password.requireDigit',\n 'sys.security.password.requireSpecial',\n 'sys.security.password.forceChangeOnFirstLogin',\n 'sys.security.captcha.enabled',\n 'sys.security.totp.enabled',\n 'sys.security.totp.requireForSuperAdmin',\n 'sys.security.mfa.enabled',\n 'sys.security.rateLimit.enabled',\n] as const\nexport const CAPTCHA_TYPE_KEY = 'sys.security.captcha.type'\nexport const SECURITY_KEYS = [\n ...Object.keys(SECURITY_NUMBERS),\n ...SECURITY_SWITCHES,\n CAPTCHA_TYPE_KEY,\n SMS_LOGIN_KEY,\n]\n\nexport const UPLOAD_MAX_KEY = 'sys.upload.maxSizeMb'\nexport const UPLOAD_EXTS_KEY = 'sys.upload.allowedExtensions'\nexport const JOB_RETENTION_KEY = 'sys.job.logRetentionDays'\nexport const JOB_ALERT_KEY = 'sys.job.alertEmails'\n\n/** 固定认领的键(第三方登录的键按已注册的方式动态认领,见 draft.ts)。 */\nexport const STATIC_CLAIMED_KEYS: readonly string[] = [\n ...BRAND_KEYS,\n ...LOGIN_KEYS,\n ...SECURITY_KEYS,\n UPLOAD_MAX_KEY,\n UPLOAD_EXTS_KEY,\n JOB_RETENTION_KEY,\n JOB_ALERT_KEY,\n]\n\n/** 结构化表单从这些分组取值。 */\nexport const STRUCTURED_GROUPS = ['sys', 'login', 'security', 'upload', 'job'] as const\n\n/** 键 → 所属分类,用于导航上的\"有未保存改动\"标记。 */\nexport function tabOfKey(key: string): ConfigTab {\n if (key.startsWith('sys.site.') || key.startsWith('sys.login.')) return 'brand'\n if (key === SMS_LOGIN_KEY || key.startsWith('sys.externalauth.')) return 'signin'\n if (key.startsWith('sys.security.')) return 'security'\n if (key.startsWith('sys.upload.')) return 'upload'\n if (key.startsWith('sys.job.')) return 'job'\n return 'advanced'\n}\n\n/** 配置行增删改的版本号:保存或在「高级」页改了行之后 bump,「高级」表据此重拉。 */\nexport const configRevision = ref(0)\n\nexport function bumpConfigRevision() {\n configRevision.value++\n}\n"],"names":["CONFIG_TABS","TAB_GROUPS","TAB_ICONS","LOGO_KEY","BRAND_KEYS","LOGIN_LOCALES","heroKeys","locale","SUBTITLE_KEY","SHOW_FEATURES_KEY","LOGIN_KEYS","l","SECURITY_NUMBERS","SMS_LOGIN_KEY","SECURITY_SWITCHES","CAPTCHA_TYPE_KEY","SECURITY_KEYS","UPLOAD_MAX_KEY","UPLOAD_EXTS_KEY","JOB_RETENTION_KEY","JOB_ALERT_KEY","STATIC_CLAIMED_KEYS","STRUCTURED_GROUPS","tabOfKey","key","configRevision","ref","bumpConfigRevision"],"mappings":";AAKO,MAAMA,IAAc;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAIaC,IAAgD;AAAA,EAC3D,CAAC,SAAS,YAAY,UAAU,WAAW;AAAA,EAC3C,CAAC,UAAU,OAAO,UAAU;AAC9B,GAGaC,IAAuC;AAAA,EAClD,OAAO;AAAA,EACP,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,UAAU;AACZ,GAEaC,IAAW,iBACXC,IAAa;AAAA,EACxB;AAAA,EACAD;AAAA,EACA;AAAA,EACA;AACF,GAEaE,IAAgB,CAAC,SAAS,OAAO,GAEjCC,IAAW,CAACC,OAAyB;AAAA,EAChD,UAAU,2BAA2BA,CAAM;AAAA,EAC3C,WAAW,4BAA4BA,CAAM;AAAA,EAC7C,UAAU,2BAA2BA,CAAM;AAC7C,IACaC,IAAe,qBACfC,IAAoB,+BACpBC,IAAa;AAAA,EACxBF;AAAA,EACAC;AAAA,EACA,GAAGJ,EAAc,QAAQ,CAAAM,MAAK,OAAO,OAAOL,EAASK,CAAC,CAAC,CAAC;AAC1D,GAIaC,IAAqD;AAAA,EAChE,uCAAuC;AAAA,EACvC,sCAAsC;AAAA,EACtC,mCAAmC;AAAA,EACnC,oCAAoC;AAAA,EACpC,sCAAsC;AAAA,EACtC,sCAAsC;AAAA,EACtC,uCAAuC;AAAA,EACvC,wCAAwC;AAAA,EACxC,0CAA0C;AAAA,EAC1C,8CAA8C;AAChD,GAEaC,IAAgB,iCAChBC,IAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GACaC,IAAmB,6BACnBC,IAAgB;AAAA,EAC3B,GAAG,OAAO,KAAKJ,CAAgB;AAAA,EAC/B,GAAGE;AAAA,EACHC;AAAA,EACAF;AACF,GAEaI,IAAiB,wBACjBC,IAAkB,gCAClBC,IAAoB,4BACpBC,IAAgB,uBAGhBC,IAAyC;AAAA,EACpD,GAAGjB;AAAA,EACH,GAAGM;AAAA,EACH,GAAGM;AAAA,EACHC;AAAA,EACAC;AAAA,EACAC;AAAA,EACAC;AACF,GAGaE,IAAoB,CAAC,OAAO,SAAS,YAAY,UAAU,KAAK;AAGtE,SAASC,EAASC,GAAwB;AAC/C,SAAIA,EAAI,WAAW,WAAW,KAAKA,EAAI,WAAW,YAAY,IAAU,UACpEA,MAAQX,KAAiBW,EAAI,WAAW,mBAAmB,IAAU,WACrEA,EAAI,WAAW,eAAe,IAAU,aACxCA,EAAI,WAAW,aAAa,IAAU,WACtCA,EAAI,WAAW,UAAU,IAAU,QAChC;AACT;AAGO,MAAMC,IAAiBC,EAAI,CAAC;AAE5B,SAASC,IAAqB;AACnC,EAAAF,EAAe;AACjB;"}
@@ -1,4 +1,7 @@
1
- import f from "./index2.vue.js";
1
+ import o from "./index2.vue.js";
2
+ /* empty css */
3
+ import t from "../../../_virtual/_plugin-vue_export-helper.js";
4
+ const f = /* @__PURE__ */ t(o, [["__scopeId", "data-v-8ea68f7d"]]);
2
5
  export {
3
6
  f as default
4
7
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
1
+ {"version":3,"file":"index.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
@@ -1,127 +1,192 @@
1
- import { defineComponent as l, openBlock as f, createBlock as d, unref as t, withCtx as a, createVNode as e } from "vue";
2
- import { NTabs as n, NTabPane as o, NCard as r } from "naive-ui";
3
- import { useI18n as b } from "vue-i18n";
4
- import c from "./components/SysBaseConfig2.vue.js";
5
- import s from "./components/SecurityConfig.vue.js";
6
- import m from "./components/ExternalAuthConfig.vue.js";
7
- import u from "./components/UploadConfig2.vue.js";
8
- import _ from "./components/JobConfig2.vue.js";
9
- import p from "./components/OtherConfig2.vue.js";
10
- const k = /* @__PURE__ */ l({
1
+ import { defineComponent as U, computed as k, ref as O, onMounted as V, onBeforeUnmount as I, openBlock as a, createElementBlock as c, createElementVNode as f, unref as t, Fragment as y, renderList as N, createCommentVNode as v, normalizeClass as K, createVNode as u, toDisplayString as p, withDirectives as _, vShow as b, createBlock as C, Transition as P, withCtx as w, createTextVNode as T, nextTick as F } from "vue";
2
+ import { useRoute as G, useRouter as M, onBeforeRouteLeave as j } from "vue-router";
3
+ import { useMessage as H, NSpin as J, NButton as $ } from "naive-ui";
4
+ import { useI18n as Z } from "vue-i18n";
5
+ import Q from "../../../components/AppIcon.vue.js";
6
+ import { useConfirm as W } from "../../../composables/useConfirm.js";
7
+ import { useAuthStore as X } from "../../../stores/auth.js";
8
+ import { translateError as D } from "../../../utils/error.js";
9
+ import { CONFIG_TABS as d, TAB_GROUPS as Y, TAB_ICONS as ee } from "./groups.js";
10
+ import { createConfigDraft as te, provideConfigDraft as ne, checkDraft as oe } from "./draft.js";
11
+ import ae from "./components/BrandSection.vue.js";
12
+ import re from "./components/SecuritySection.vue.js";
13
+ import ie from "./components/SigninSection.vue.js";
14
+ import se from "./components/SensitiveSection.vue.js";
15
+ import le from "./components/UploadSection.vue.js";
16
+ import ce from "./components/JobSection.vue.js";
17
+ import ue from "./components/AdvancedSection2.vue.js";
18
+ const de = { class: "config-center fill-page" }, fe = ["aria-label"], ve = {
19
+ key: 0,
20
+ class: "gap",
21
+ "aria-hidden": "true"
22
+ }, me = ["aria-selected", "tabindex", "data-tab", "onClick"], ge = ["title"], pe = { class: "sr-only" }, _e = {
23
+ key: 0,
24
+ class: "cfg-loading"
25
+ }, be = {
26
+ key: 1,
27
+ class: "cfg-body"
28
+ }, he = {
29
+ key: 0,
30
+ class: "savebar",
31
+ "data-testid": "config-savebar"
32
+ }, ye = {
33
+ class: "msg",
34
+ role: "status",
35
+ "aria-live": "polite"
36
+ }, Ie = /* @__PURE__ */ U({
11
37
  __name: "index",
12
- setup(y) {
13
- const { t: i } = b();
14
- return (h, g) => (f(), d(t(n), {
15
- type: "line",
16
- animated: ""
17
- }, {
18
- default: a(() => [
19
- e(t(o), {
20
- name: "base",
21
- tab: t(i)("config.tab.base"),
22
- "display-directive": "show:lazy"
23
- }, {
24
- default: a(() => [
25
- e(t(r), {
26
- bordered: !1,
27
- title: t(i)("config.tab.base")
38
+ setup(Se) {
39
+ const { t: o } = Z(), m = H(), { ask: E } = W(), A = X(), S = G(), L = M(), i = te();
40
+ ne(i);
41
+ const q = k(() => A.hasPerm("PUT:/api/v1/sys/config/batch")), h = k(() => i.dirtyKeys.value.length), B = O(null);
42
+ function z(e) {
43
+ const l = d.indexOf(n.value), s = e.key === "ArrowRight" ? (l + 1) % d.length : e.key === "ArrowLeft" ? (l - 1 + d.length) % d.length : e.key === "Home" ? 0 : e.key === "End" ? d.length - 1 : -1;
44
+ s < 0 || (e.preventDefault(), n.value = d[s], F(
45
+ () => {
46
+ var g, r;
47
+ return (r = (g = e.currentTarget) == null ? void 0 : g.querySelector(`[data-tab="${d[s]}"]`)) == null ? void 0 : r.focus();
48
+ }
49
+ ));
50
+ }
51
+ const n = k({
52
+ get: () => {
53
+ const e = S.query.tab;
54
+ return d.includes(e) ? e : "brand";
55
+ },
56
+ set: (e) => {
57
+ L.replace({ query: { ...S.query, tab: e } });
58
+ }
59
+ });
60
+ V(async () => {
61
+ try {
62
+ await i.load();
63
+ } catch (e) {
64
+ m.error(D(e));
65
+ }
66
+ });
67
+ async function R() {
68
+ var l;
69
+ const e = oe(i.values);
70
+ if (e.error) {
71
+ n.value = e.error.tab, e.error.locale && ((l = B.value) == null || l.setLocale(e.error.locale)), m.error(o(e.error.message));
72
+ return;
73
+ }
74
+ for (const s of e.warnings)
75
+ m.warning(
76
+ `${o(`config.login.locale${s.locale === "zh-CN" ? "Zh" : "En"}`)}: ${o(s.message)}`
77
+ );
78
+ try {
79
+ await i.save(), m.success(o("config.saved"));
80
+ } catch (s) {
81
+ m.error(D(s));
82
+ }
83
+ }
84
+ j(async (e) => !h.value || e.path === S.path ? !0 : E({ title: o("config.leaveTitle"), content: o("config.leaveConfirm") }));
85
+ function x(e) {
86
+ h.value && e.preventDefault();
87
+ }
88
+ return window.addEventListener("beforeunload", x), I(() => {
89
+ window.removeEventListener("beforeunload", x), i.discard();
90
+ }), (e, l) => (a(), c("div", de, [
91
+ f("nav", {
92
+ class: "cfg-nav",
93
+ role: "tablist",
94
+ "aria-label": t(o)("config.title"),
95
+ "data-testid": "config-nav",
96
+ onKeydown: z
97
+ }, [
98
+ (a(!0), c(y, null, N(t(Y), (s, g) => (a(), c(y, { key: g }, [
99
+ g ? (a(), c("span", ve)) : v("", !0),
100
+ (a(!0), c(y, null, N(s, (r) => (a(), c("button", {
101
+ key: r,
102
+ type: "button",
103
+ role: "tab",
104
+ "aria-selected": n.value === r,
105
+ tabindex: n.value === r ? 0 : -1,
106
+ class: K(["nav-item", { on: n.value === r }]),
107
+ "data-tab": r,
108
+ onClick: (ke) => n.value = r
109
+ }, [
110
+ u(Q, {
111
+ icon: t(ee)[r],
112
+ size: 22
113
+ }, null, 8, ["icon"]),
114
+ f("span", null, p(t(o)(`config.tab.${r}`)), 1),
115
+ t(i).dirtyTabs.value.has(r) ? (a(), c(y, { key: 0 }, [
116
+ f("i", {
117
+ class: "dot",
118
+ "aria-hidden": "true",
119
+ title: t(o)("config.unsavedTab")
120
+ }, null, 8, ge),
121
+ f("span", pe, p(t(o)("config.unsavedTab")), 1)
122
+ ], 64)) : v("", !0)
123
+ ], 10, me))), 128))
124
+ ], 64))), 128))
125
+ ], 40, fe),
126
+ t(i).loading.value ? (a(), c("div", _e, [
127
+ u(t(J))
128
+ ])) : (a(), c("div", be, [
129
+ _(u(ae, {
130
+ ref_key: "brandSection",
131
+ ref: B
132
+ }, null, 512), [
133
+ [b, n.value === "brand"]
134
+ ]),
135
+ _(u(re, null, null, 512), [
136
+ [b, n.value === "security"]
137
+ ]),
138
+ _(u(ie, null, null, 512), [
139
+ [b, n.value === "signin"]
140
+ ]),
141
+ n.value === "sensitive" ? (a(), C(se, { key: 0 })) : v("", !0),
142
+ _(u(le, null, null, 512), [
143
+ [b, n.value === "upload"]
144
+ ]),
145
+ _(u(ce, null, null, 512), [
146
+ [b, n.value === "job"]
147
+ ]),
148
+ n.value === "advanced" ? (a(), C(ue, { key: 1 })) : v("", !0)
149
+ ])),
150
+ u(P, { name: "savebar" }, {
151
+ default: w(() => [
152
+ h.value ? (a(), c("div", he, [
153
+ f("span", ye, [
154
+ l[1] || (l[1] = f("i", {
155
+ class: "dot",
156
+ "aria-hidden": "true"
157
+ }, null, -1)),
158
+ T(" " + p(t(o)("config.unsaved", { n: h.value })), 1)
159
+ ]),
160
+ u(t($), {
161
+ size: "small",
162
+ disabled: t(i).saving.value,
163
+ onClick: l[0] || (l[0] = (s) => t(i).discard())
28
164
  }, {
29
- default: a(() => [
30
- e(c)
165
+ default: w(() => [
166
+ T(p(t(o)("config.discard")), 1)
31
167
  ]),
32
168
  _: 1
33
- }, 8, ["title"])
34
- ]),
35
- _: 1
36
- }, 8, ["tab"]),
37
- e(t(o), {
38
- name: "security",
39
- tab: t(i)("config.tab.security"),
40
- "display-directive": "show:lazy"
41
- }, {
42
- default: a(() => [
43
- e(t(r), {
44
- bordered: !1,
45
- title: t(i)("config.tab.security")
169
+ }, 8, ["disabled"]),
170
+ q.value ? (a(), C(t($), {
171
+ key: 0,
172
+ size: "small",
173
+ type: "primary",
174
+ loading: t(i).saving.value,
175
+ onClick: R
46
176
  }, {
47
- default: a(() => [
48
- e(s)
177
+ default: w(() => [
178
+ T(p(t(o)("common.save")), 1)
49
179
  ]),
50
180
  _: 1
51
- }, 8, ["title"])
52
- ]),
53
- _: 1
54
- }, 8, ["tab"]),
55
- e(t(o), {
56
- name: "externalAuth",
57
- tab: t(i)("config.tab.externalAuth"),
58
- "display-directive": "show:lazy"
59
- }, {
60
- default: a(() => [
61
- e(t(r), {
62
- bordered: !1,
63
- title: t(i)("config.tab.externalAuth")
64
- }, {
65
- default: a(() => [
66
- e(m)
67
- ]),
68
- _: 1
69
- }, 8, ["title"])
70
- ]),
71
- _: 1
72
- }, 8, ["tab"]),
73
- e(t(o), {
74
- name: "upload",
75
- tab: t(i)("config.tab.upload"),
76
- "display-directive": "show:lazy"
77
- }, {
78
- default: a(() => [
79
- e(t(r), {
80
- bordered: !1,
81
- title: t(i)("config.tab.upload")
82
- }, {
83
- default: a(() => [
84
- e(u)
85
- ]),
86
- _: 1
87
- }, 8, ["title"])
88
- ]),
89
- _: 1
90
- }, 8, ["tab"]),
91
- e(t(o), {
92
- name: "job",
93
- tab: t(i)("config.tab.job"),
94
- "display-directive": "show:lazy"
95
- }, {
96
- default: a(() => [
97
- e(t(r), {
98
- bordered: !1,
99
- title: t(i)("config.tab.job")
100
- }, {
101
- default: a(() => [
102
- e(_)
103
- ]),
104
- _: 1
105
- }, 8, ["title"])
106
- ]),
107
- _: 1
108
- }, 8, ["tab"]),
109
- e(t(o), {
110
- name: "other",
111
- tab: t(i)("config.tab.other"),
112
- "display-directive": "show:lazy"
113
- }, {
114
- default: a(() => [
115
- e(p)
116
- ]),
117
- _: 1
118
- }, 8, ["tab"])
119
- ]),
120
- _: 1
121
- }));
181
+ }, 8, ["loading"])) : v("", !0)
182
+ ])) : v("", !0)
183
+ ]),
184
+ _: 1
185
+ })
186
+ ]));
122
187
  }
123
188
  });
124
189
  export {
125
- k as default
190
+ Ie as default
126
191
  };
127
192
  //# sourceMappingURL=index2.vue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index2.vue.js","sources":["../../../../src/views/system/config/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 分类配置中心:按分类 Tab 组织,一个结构化 Tab 管一个分组(见 groups.ts)。系统基础=结构化表单(字段级 UI);\n// 安全策略=锁定/密码/会话结构化表单(后端 ISecurityPolicyProvider 强制执行,改值即时生效);\n// 结构化 Tab 下方的「本组其它配置」列同组里没被字段认领的行;其他配置=不归任何结构化 Tab 的分组的扁平 CRUD 兜底。\nimport { NTabs, NTabPane, NCard } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport SysBaseConfig from './components/SysBaseConfig.vue'\nimport SecurityConfig from './components/SecurityConfig.vue'\nimport ExternalAuthConfig from './components/ExternalAuthConfig.vue'\nimport UploadConfig from './components/UploadConfig.vue'\nimport JobConfig from './components/JobConfig.vue'\nimport OtherConfig from './components/OtherConfig.vue'\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <!-- display-directive=\"show:lazy\":每个面板首次访问才挂载(保住懒加载),之后常驻、切走只是 v-show 隐藏。\n naive 的默认值是 'if' —— 面板一离场就销毁,而四个子面板的未保存改动全在各自的组件本地 state 里,\n 于是\"改了值 → 切个 Tab 看一眼 → 切回来\"就把改动无声吃掉了(回来还会重新拉一遍后端旧值,让人以为没改过)。\n 注意 display-directive 是 n-tab-pane 的 prop,不是 n-tabs 的,四个都得写。 -->\n <n-tabs type=\"line\" animated>\n <n-tab-pane name=\"base\" :tab=\"t('config.tab.base')\" display-directive=\"show:lazy\">\n <n-card :bordered=\"false\" :title=\"t('config.tab.base')\">\n <sys-base-config />\n </n-card>\n </n-tab-pane>\n <n-tab-pane name=\"security\" :tab=\"t('config.tab.security')\" display-directive=\"show:lazy\">\n <n-card :bordered=\"false\" :title=\"t('config.tab.security')\">\n <security-config />\n </n-card>\n </n-tab-pane>\n <n-tab-pane\n name=\"externalAuth\"\n :tab=\"t('config.tab.externalAuth')\"\n display-directive=\"show:lazy\"\n >\n <n-card :bordered=\"false\" :title=\"t('config.tab.externalAuth')\">\n <external-auth-config />\n </n-card>\n </n-tab-pane>\n <n-tab-pane name=\"upload\" :tab=\"t('config.tab.upload')\" display-directive=\"show:lazy\">\n <n-card :bordered=\"false\" :title=\"t('config.tab.upload')\">\n <upload-config />\n </n-card>\n </n-tab-pane>\n <n-tab-pane name=\"job\" :tab=\"t('config.tab.job')\" display-directive=\"show:lazy\">\n <n-card :bordered=\"false\" :title=\"t('config.tab.job')\">\n <job-config />\n </n-card>\n </n-tab-pane>\n <n-tab-pane name=\"other\" :tab=\"t('config.tab.other')\" display-directive=\"show:lazy\">\n <other-config />\n </n-tab-pane>\n </n-tabs>\n</template>\n"],"names":["t","useI18n","_createBlock","_unref","NTabs","_createVNode","NTabPane","NCard","SysBaseConfig","SecurityConfig","ExternalAuthConfig","UploadConfig","JobConfig","OtherConfig"],"mappings":";;;;;;;;;;;;AAaA,UAAM,EAAE,GAAAA,EAAA,IAAMC,EAAA;2BAQZC,EAiCSC,EAAAC,CAAA,GAAA;AAAA,MAjCD,MAAK;AAAA,MAAO,UAAA;AAAA,IAAA;iBAClB,MAIa;AAAA,QAJbC,EAIaF,EAAAG,CAAA,GAAA;AAAA,UAJD,MAAK;AAAA,UAAQ,KAAKH,EAAAH,CAAA,EAAC,iBAAA;AAAA,UAAqB,qBAAkB;AAAA,QAAA;qBACpE,MAES;AAAA,YAFTK,EAESF,EAAAI,CAAA,GAAA;AAAA,cAFA,UAAU;AAAA,cAAQ,OAAOJ,EAAAH,CAAA,EAAC,iBAAA;AAAA,YAAA;yBACjC,MAAmB;AAAA,gBAAnBK,EAAmBG,CAAA;AAAA,cAAA;;;;;;QAGvBH,EAIaF,EAAAG,CAAA,GAAA;AAAA,UAJD,MAAK;AAAA,UAAY,KAAKH,EAAAH,CAAA,EAAC,qBAAA;AAAA,UAAyB,qBAAkB;AAAA,QAAA;qBAC5E,MAES;AAAA,YAFTK,EAESF,EAAAI,CAAA,GAAA;AAAA,cAFA,UAAU;AAAA,cAAQ,OAAOJ,EAAAH,CAAA,EAAC,qBAAA;AAAA,YAAA;yBACjC,MAAmB;AAAA,gBAAnBK,EAAmBI,CAAA;AAAA,cAAA;;;;;;QAGvBJ,EAQaF,EAAAG,CAAA,GAAA;AAAA,UAPX,MAAK;AAAA,UACJ,KAAKH,EAAAH,CAAA,EAAC,yBAAA;AAAA,UACP,qBAAkB;AAAA,QAAA;qBAElB,MAES;AAAA,YAFTK,EAESF,EAAAI,CAAA,GAAA;AAAA,cAFA,UAAU;AAAA,cAAQ,OAAOJ,EAAAH,CAAA,EAAC,yBAAA;AAAA,YAAA;yBACjC,MAAwB;AAAA,gBAAxBK,EAAwBK,CAAA;AAAA,cAAA;;;;;;QAG5BL,EAIaF,EAAAG,CAAA,GAAA;AAAA,UAJD,MAAK;AAAA,UAAU,KAAKH,EAAAH,CAAA,EAAC,mBAAA;AAAA,UAAuB,qBAAkB;AAAA,QAAA;qBACxE,MAES;AAAA,YAFTK,EAESF,EAAAI,CAAA,GAAA;AAAA,cAFA,UAAU;AAAA,cAAQ,OAAOJ,EAAAH,CAAA,EAAC,mBAAA;AAAA,YAAA;yBACjC,MAAiB;AAAA,gBAAjBK,EAAiBM,CAAA;AAAA,cAAA;;;;;;QAGrBN,EAIaF,EAAAG,CAAA,GAAA;AAAA,UAJD,MAAK;AAAA,UAAO,KAAKH,EAAAH,CAAA,EAAC,gBAAA;AAAA,UAAoB,qBAAkB;AAAA,QAAA;qBAClE,MAES;AAAA,YAFTK,EAESF,EAAAI,CAAA,GAAA;AAAA,cAFA,UAAU;AAAA,cAAQ,OAAOJ,EAAAH,CAAA,EAAC,gBAAA;AAAA,YAAA;yBACjC,MAAc;AAAA,gBAAdK,EAAcO,CAAA;AAAA,cAAA;;;;;;QAGlBP,EAEaF,EAAAG,CAAA,GAAA;AAAA,UAFD,MAAK;AAAA,UAAS,KAAKH,EAAAH,CAAA,EAAC,kBAAA;AAAA,UAAsB,qBAAkB;AAAA,QAAA;qBACtE,MAAgB;AAAA,YAAhBK,EAAgBQ,CAAA;AAAA,UAAA;;;;;;;;"}
1
+ {"version":3,"file":"index2.vue.js","sources":["../../../../src/views/system/config/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 配置中心:仿 macOS「系统设置」。顶部图标页签,每个分类一屏放下、「左边设置、右边预览」\n// (没有可预览效果的分类不放预览)。结构化表单共用一份页面级草稿(draft.ts),改动由底部浮出的\n// 保存条统一保存;改过的行和页签带橙点。当前分类写进 ?tab=,刷新和分享链接能回到同一处。\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport { NButton, NSpin, useMessage } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport AppIcon from '#/components/AppIcon.vue'\nimport { useConfirm } from '#/composables/useConfirm'\nimport { useAuthStore } from '#/stores/auth'\nimport { translateError } from '#/utils/error'\nimport { CONFIG_TABS, TAB_GROUPS, TAB_ICONS, type ConfigTab } from './groups'\nimport { checkDraft, createConfigDraft, provideConfigDraft } from './draft'\nimport BrandSection from './components/BrandSection.vue'\nimport SecuritySection from './components/SecuritySection.vue'\nimport SigninSection from './components/SigninSection.vue'\nimport SensitiveSection from './components/SensitiveSection.vue'\nimport UploadSection from './components/UploadSection.vue'\nimport JobSection from './components/JobSection.vue'\nimport AdvancedSection from './components/AdvancedSection.vue'\n\nconst { t } = useI18n()\nconst message = useMessage()\nconst { ask } = useConfirm()\nconst auth = useAuthStore()\nconst route = useRoute()\nconst router = useRouter()\n\nconst draft = createConfigDraft()\nprovideConfigDraft(draft)\nconst canSave = computed(() => auth.hasPerm('PUT:/api/v1/sys/config/batch'))\nconst dirtyCount = computed(() => draft.dirtyKeys.value.length)\nconst brandSection = ref<InstanceType<typeof BrandSection> | null>(null)\n\n/** 方向键在页签间移动并切换,焦点跟过去 */\nfunction onNavKey(e: KeyboardEvent) {\n const i = CONFIG_TABS.indexOf(tab.value)\n const next =\n e.key === 'ArrowRight'\n ? (i + 1) % CONFIG_TABS.length\n : e.key === 'ArrowLeft'\n ? (i - 1 + CONFIG_TABS.length) % CONFIG_TABS.length\n : e.key === 'Home'\n ? 0\n : e.key === 'End'\n ? CONFIG_TABS.length - 1\n : -1\n if (next < 0) return\n e.preventDefault()\n tab.value = CONFIG_TABS[next]\n void nextTick(() =>\n (e.currentTarget as HTMLElement | null)\n ?.querySelector<HTMLElement>(`[data-tab=\"${CONFIG_TABS[next]}\"]`)\n ?.focus(),\n )\n}\n\nconst tab = computed<ConfigTab>({\n get: () => {\n const q = route.query.tab\n return CONFIG_TABS.includes(q as ConfigTab) ? (q as ConfigTab) : 'brand'\n },\n set: v => {\n void router.replace({ query: { ...route.query, tab: v } })\n },\n})\n\nonMounted(async () => {\n try {\n await draft.load()\n } catch (e) {\n message.error(translateError(e))\n }\n})\n\nasync function save() {\n const check = checkDraft(draft.values)\n if (check.error) {\n tab.value = check.error.tab\n if (check.error.locale) brandSection.value?.setLocale(check.error.locale)\n message.error(t(check.error.message))\n return\n }\n for (const w of check.warnings)\n message.warning(\n `${t(`config.login.locale${w.locale === 'zh-CN' ? 'Zh' : 'En'}`)}: ${t(w.message)}`,\n )\n try {\n await draft.save()\n message.success(t('config.saved'))\n } catch (e) {\n message.error(translateError(e))\n }\n}\n\n// 有未保存改动时离开要确认;关标签页/刷新交给浏览器自己的提示\nonBeforeRouteLeave(async to => {\n if (!dirtyCount.value || to.path === route.path) return true\n return ask({ title: t('config.leaveTitle'), content: t('config.leaveConfirm') })\n})\nfunction onBeforeUnload(e: BeforeUnloadEvent) {\n if (dirtyCount.value) e.preventDefault()\n}\nwindow.addEventListener('beforeunload', onBeforeUnload)\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', onBeforeUnload)\n draft.discard() // 释放预览用的 blob: 地址\n})\n</script>\n\n<template>\n <!-- .fill-page:整页吃满一屏(内核 styles/layout.css 的高度链),每个分类在 .cfg-body 里一屏放下 -->\n <div class=\"config-center fill-page\">\n <!-- 页签导航:role=tablist + 漫游 tabindex,方向键 / Home / End 切换(WAI-ARIA Tabs 模式) -->\n <nav\n class=\"cfg-nav\"\n role=\"tablist\"\n :aria-label=\"t('config.title')\"\n data-testid=\"config-nav\"\n @keydown=\"onNavKey\"\n >\n <template v-for=\"(group, gi) in TAB_GROUPS\" :key=\"gi\">\n <span v-if=\"gi\" class=\"gap\" aria-hidden=\"true\" />\n <button\n v-for=\"key in group\"\n :key=\"key\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"tab === key\"\n :tabindex=\"tab === key ? 0 : -1\"\n :class=\"['nav-item', { on: tab === key }]\"\n :data-tab=\"key\"\n @click=\"tab = key\"\n >\n <AppIcon :icon=\"TAB_ICONS[key]\" :size=\"22\" />\n <span>{{ t(`config.tab.${key}`) }}</span>\n <template v-if=\"draft.dirtyTabs.value.has(key)\">\n <i class=\"dot\" aria-hidden=\"true\" :title=\"t('config.unsavedTab')\" />\n <span class=\"sr-only\">{{ t('config.unsavedTab') }}</span>\n </template>\n </button>\n </template>\n </nav>\n\n <div v-if=\"draft.loading.value\" class=\"cfg-loading\"><n-spin /></div>\n <div v-else class=\"cfg-body\">\n <BrandSection v-show=\"tab === 'brand'\" ref=\"brandSection\" />\n <SecuritySection v-show=\"tab === 'security'\" />\n <SigninSection v-show=\"tab === 'signin'\" />\n <SensitiveSection v-if=\"tab === 'sensitive'\" />\n <UploadSection v-show=\"tab === 'upload'\" />\n <JobSection v-show=\"tab === 'job'\" />\n <AdvancedSection v-if=\"tab === 'advanced'\" />\n </div>\n\n <!-- 保存条浮在底部正中(毛玻璃胶囊),有改动才出现 -->\n <transition name=\"savebar\">\n <div v-if=\"dirtyCount\" class=\"savebar\" data-testid=\"config-savebar\">\n <span class=\"msg\" role=\"status\" aria-live=\"polite\">\n <i class=\"dot\" aria-hidden=\"true\" />\n {{ t('config.unsaved', { n: dirtyCount }) }}\n </span>\n <n-button size=\"small\" :disabled=\"draft.saving.value\" @click=\"draft.discard()\">\n {{ t('config.discard') }}\n </n-button>\n <n-button\n v-if=\"canSave\"\n size=\"small\"\n type=\"primary\"\n :loading=\"draft.saving.value\"\n @click=\"save\"\n >\n {{ t('common.save') }}\n </n-button>\n </div>\n </transition>\n </div>\n</template>\n\n<style scoped>\n.config-center {\n position: relative;\n overflow: hidden;\n border-radius: var(--radius-lg);\n background: var(--color-bg-body);\n}\n.cfg-nav {\n display: flex;\n flex: none;\n flex-wrap: wrap;\n justify-content: center;\n gap: 2px;\n padding: 8px 16px;\n border-bottom: 0.5px solid var(--color-border);\n background: var(--color-bg-container);\n}\n.gap {\n width: 14px;\n}\n.nav-item {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n gap: 3px;\n min-width: 76px;\n min-height: 52px;\n padding: 6px 10px 5px;\n border: 0;\n border-radius: 8px;\n background: none;\n font: inherit;\n font-size: var(--font-size-sm);\n color: var(--color-text-secondary);\n cursor: pointer;\n user-select: none;\n}\n.nav-item:hover {\n background: var(--color-fill-hover);\n}\n.nav-item.on {\n background: var(--color-fill);\n color: var(--color-primary);\n}\n.nav-item.on span {\n color: var(--color-text-primary);\n font-weight: 500;\n}\n.nav-item:focus-visible {\n outline: 2px solid var(--color-primary);\n outline-offset: -2px;\n}\n.nav-item .dot {\n position: absolute;\n top: 6px;\n right: 16px;\n box-shadow: 0 0 0 2px var(--color-bg-container);\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n.dot {\n display: inline-block;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--color-warning);\n}\n.cfg-loading {\n display: grid;\n flex: 1;\n place-items: center;\n}\n/* 内容区吃掉导航之外的全部高度;每个分类自己保证一屏放下(SectionLayout) */\n.cfg-body {\n flex: 1;\n min-height: 0;\n padding: 16px 24px 20px;\n}\n.cfg-body > :deep(section) {\n height: 100%;\n}\n.savebar {\n position: absolute;\n left: 50%;\n bottom: 18px;\n z-index: 10;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 7px 7px 16px;\n border-radius: 14px;\n background: color-mix(in srgb, var(--color-bg-container) 88%, transparent);\n backdrop-filter: blur(20px);\n border: 1px solid var(--color-border);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.14);\n transform: translateX(-50%);\n}\n.savebar .msg {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-right: 4px;\n font-size: var(--font-size-base);\n color: var(--color-text-secondary);\n white-space: nowrap;\n}\n.savebar-enter-active,\n.savebar-leave-active {\n transition:\n transform 0.25s cubic-bezier(0.2, 0.9, 0.3, 1.2),\n opacity 0.2s ease;\n}\n.savebar-enter-from,\n.savebar-leave-to {\n transform: translate(-50%, 140%);\n opacity: 0;\n}\n/* 系统开了「减少动态效果」就只淡入淡出,不做位移 */\n@media (prefers-reduced-motion: reduce) {\n .savebar-enter-active,\n .savebar-leave-active {\n transition: opacity 0.15s linear;\n }\n .savebar-enter-from,\n .savebar-leave-to {\n transform: translateX(-50%);\n }\n}\n/* 矮屏:收紧留白 */\n@media (max-height: 899px) {\n .cfg-nav {\n padding: 6px 16px;\n }\n .nav-item {\n min-height: 48px;\n padding: 4px 10px 3px;\n }\n .cfg-body {\n padding: 12px 20px 14px;\n }\n}\n</style>\n"],"names":["t","useI18n","message","useMessage","ask","useConfirm","auth","useAuthStore","route","useRoute","router","useRouter","draft","createConfigDraft","provideConfigDraft","canSave","computed","dirtyCount","brandSection","ref","onNavKey","i","CONFIG_TABS","tab","next","nextTick","_b","_a","q","v","onMounted","translateError","save","check","checkDraft","w","e","onBeforeRouteLeave","to","onBeforeUnload","onBeforeUnmount","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_unref","_Fragment","_renderList","TAB_GROUPS","group","gi","_hoisted_3","key","_normalizeClass","$event","_createVNode","AppIcon","TAB_ICONS","_toDisplayString","_hoisted_6","_hoisted_7","NSpin","_hoisted_8","BrandSection","SecuritySection","SigninSection","_createBlock","SensitiveSection","UploadSection","JobSection","AdvancedSection","_Transition","_hoisted_9","_hoisted_10","NButton","_cache"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { ExternalAuthField, ExternalAuthProvider, ExternalAuthProviderSaveInput, ExternalAuthProviderTestInput } from '../../../api';
2
+ /** 新增 OIDC 时标识的写法:小写字母开头,字母数字与连字符,2–32 位。与后端规则一致。 */
3
+ export declare const CODE_PATTERN: RegExp;
4
+ export interface ProviderForm {
5
+ /** 新增(标识可填)还是编辑(标识固定) */
6
+ isNew: boolean;
7
+ code: string;
8
+ displayName: string;
9
+ /** 非机密字段当前输入 */
10
+ values: Record<string, string>;
11
+ /** 机密字段当前输入;空串 = 不改 */
12
+ secrets: Record<string, string>;
13
+ }
14
+ /** 用目录里已有的配置(或类型缺省值)起一份表单;`identity` 是还没存过时的标识与展示名。 */
15
+ export declare function createForm(fields: readonly ExternalAuthField[], saved: ExternalAuthProvider | undefined, isNew: boolean, identity: {
16
+ code: string;
17
+ displayName: string;
18
+ }): ProviderForm;
19
+ /** 改了「决定请求发往哪里」的字段(如 OIDC 的 Authority,忽略末尾 `/`)。新增不算。 */
20
+ export declare function endpointChanged(fields: readonly ExternalAuthField[], form: ProviderForm, saved: ExternalAuthProvider | undefined): boolean;
21
+ /** 这个机密字段现在必须填吗:还没配过,或者改了端点(防止把已保存的密钥发到新地址)。 */
22
+ export declare function secretRequired(field: ExternalAuthField, saved: ExternalAuthProvider | undefined, endpointMoved: boolean): boolean;
23
+ /** 缺哪些必填项(字段名列表);空数组 = 可以保存。 */
24
+ export declare function missingFields(fields: readonly ExternalAuthField[], form: ProviderForm, saved: ExternalAuthProvider | undefined): string[];
25
+ /** 保存入参:非机密进 values,机密进 secrets(后端按名字对操作日志打码,别混放)。 */
26
+ export declare function buildSaveBody(type: string, form: ProviderForm): ExternalAuthProviderSaveInput;
27
+ /** 测试入参:用表单里的值;库里已有这条时带上 code,机密留空则取已保存的。 */
28
+ export declare function buildTestBody(type: string, form: ProviderForm, saved: ExternalAuthProvider | undefined): ExternalAuthProviderTestInput;
29
+ /**
30
+ * 要填到厂商后台的回调地址:后端给的模板里把 `{code}` 换成标识(新增时标识没填,保留占位)。
31
+ * 没配回调基址时模板为空串,返回空串,由页面提示。
32
+ */
33
+ export declare function callbackFor(template: string, code: string): string;
@@ -0,0 +1,63 @@
1
+ const a = /^[a-z][a-z0-9-]{1,31}$/;
2
+ function f(n, e, t, c) {
3
+ const i = {}, u = {};
4
+ for (const s of n)
5
+ s.secret ? u[s.name] = "" : i[s.name] = (e == null ? void 0 : e.values[s.name]) ?? s.default ?? "";
6
+ return {
7
+ isNew: t,
8
+ code: (e == null ? void 0 : e.code) ?? c.code,
9
+ displayName: (e == null ? void 0 : e.displayName) ?? c.displayName,
10
+ values: i,
11
+ secrets: u
12
+ };
13
+ }
14
+ const o = (n) => (n ?? "").trim().replace(/\/+$/, "");
15
+ function m(n, e, t) {
16
+ return e.isNew || !t ? !1 : n.some(
17
+ (c) => c.definesEndpoint && o(e.values[c.name]) !== o(t.values[c.name])
18
+ );
19
+ }
20
+ function p(n, e, t) {
21
+ var c;
22
+ return n.secret ? !((c = e == null ? void 0 : e.secrets[n.name]) != null && c.hasValue) || t : !1;
23
+ }
24
+ function N(n, e, t) {
25
+ var u, s;
26
+ const c = m(n, e, t), i = [];
27
+ e.isNew && (e.displayName.trim() || i.push("displayName"), a.test(e.code) || i.push("code"));
28
+ for (const r of n)
29
+ r.secret ? p(r, t, c) && !((u = e.secrets[r.name]) != null && u.trim()) && i.push(r.name) : r.required && !((s = e.values[r.name]) != null && s.trim()) && i.push(r.name);
30
+ return i;
31
+ }
32
+ const l = (n) => Object.fromEntries(Object.entries(n).map(([e, t]) => [e, t.trim()]));
33
+ function d(n, e) {
34
+ return {
35
+ type: n,
36
+ displayName: e.displayName.trim() || null,
37
+ icon: null,
38
+ values: l(e.values),
39
+ secrets: l(e.secrets)
40
+ };
41
+ }
42
+ function y(n, e, t) {
43
+ return {
44
+ code: t ? e.code : null,
45
+ type: n,
46
+ values: l(e.values),
47
+ secrets: l(e.secrets)
48
+ };
49
+ }
50
+ function h(n, e) {
51
+ return n.replace("{code}", e || "{code}");
52
+ }
53
+ export {
54
+ a as CODE_PATTERN,
55
+ d as buildSaveBody,
56
+ y as buildTestBody,
57
+ h as callbackFor,
58
+ f as createForm,
59
+ m as endpointChanged,
60
+ N as missingFields,
61
+ p as secretRequired
62
+ };
63
+ //# sourceMappingURL=providerForm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providerForm.js","sources":["../../../../src/views/system/config/providerForm.ts"],"sourcesContent":["// 第三方登录设置面板的表单规则(纯函数,vitest 直接驱动)。\n// 字段清单来自后端类型目录:非机密字段回显,机密字段只有「已配置 + 尾四位」,留空表示不改。\nimport type {\n ExternalAuthField,\n ExternalAuthProvider,\n ExternalAuthProviderSaveInput,\n ExternalAuthProviderTestInput,\n} from '#/api'\n\n/** 新增 OIDC 时标识的写法:小写字母开头,字母数字与连字符,2–32 位。与后端规则一致。 */\nexport const CODE_PATTERN = /^[a-z][a-z0-9-]{1,31}$/\n\nexport interface ProviderForm {\n /** 新增(标识可填)还是编辑(标识固定) */\n isNew: boolean\n code: string\n displayName: string\n /** 非机密字段当前输入 */\n values: Record<string, string>\n /** 机密字段当前输入;空串 = 不改 */\n secrets: Record<string, string>\n}\n\n/** 用目录里已有的配置(或类型缺省值)起一份表单;`identity` 是还没存过时的标识与展示名。 */\nexport function createForm(\n fields: readonly ExternalAuthField[],\n saved: ExternalAuthProvider | undefined,\n isNew: boolean,\n identity: { code: string; displayName: string },\n): ProviderForm {\n const values: Record<string, string> = {}\n const secrets: Record<string, string> = {}\n for (const f of fields) {\n if (f.secret) secrets[f.name] = ''\n else values[f.name] = saved?.values[f.name] ?? f.default ?? ''\n }\n return {\n isNew,\n code: saved?.code ?? identity.code,\n displayName: saved?.displayName ?? identity.displayName,\n values,\n secrets,\n }\n}\n\nconst normEndpoint = (v: string | undefined) => (v ?? '').trim().replace(/\\/+$/, '')\n\n/** 改了「决定请求发往哪里」的字段(如 OIDC 的 Authority,忽略末尾 `/`)。新增不算。 */\nexport function endpointChanged(\n fields: readonly ExternalAuthField[],\n form: ProviderForm,\n saved: ExternalAuthProvider | undefined,\n): boolean {\n if (form.isNew || !saved) return false\n return fields.some(\n f =>\n f.definesEndpoint && normEndpoint(form.values[f.name]) !== normEndpoint(saved.values[f.name]),\n )\n}\n\n/** 这个机密字段现在必须填吗:还没配过,或者改了端点(防止把已保存的密钥发到新地址)。 */\nexport function secretRequired(\n field: ExternalAuthField,\n saved: ExternalAuthProvider | undefined,\n endpointMoved: boolean,\n): boolean {\n if (!field.secret) return false\n return !saved?.secrets[field.name]?.hasValue || endpointMoved\n}\n\n/** 缺哪些必填项(字段名列表);空数组 = 可以保存。 */\nexport function missingFields(\n fields: readonly ExternalAuthField[],\n form: ProviderForm,\n saved: ExternalAuthProvider | undefined,\n): string[] {\n const moved = endpointChanged(fields, form, saved)\n const missing: string[] = []\n if (form.isNew) {\n if (!form.displayName.trim()) missing.push('displayName')\n if (!CODE_PATTERN.test(form.code)) missing.push('code')\n }\n for (const f of fields) {\n if (f.secret) {\n if (secretRequired(f, saved, moved) && !form.secrets[f.name]?.trim()) missing.push(f.name)\n } else if (f.required && !form.values[f.name]?.trim()) {\n missing.push(f.name)\n }\n }\n return missing\n}\n\nconst trimmed = (src: Record<string, string>) =>\n Object.fromEntries(Object.entries(src).map(([k, v]) => [k, v.trim()]))\n\n/** 保存入参:非机密进 values,机密进 secrets(后端按名字对操作日志打码,别混放)。 */\nexport function buildSaveBody(type: string, form: ProviderForm): ExternalAuthProviderSaveInput {\n return {\n type,\n displayName: form.displayName.trim() || null,\n icon: null,\n values: trimmed(form.values),\n secrets: trimmed(form.secrets),\n }\n}\n\n/** 测试入参:用表单里的值;库里已有这条时带上 code,机密留空则取已保存的。 */\nexport function buildTestBody(\n type: string,\n form: ProviderForm,\n saved: ExternalAuthProvider | undefined,\n): ExternalAuthProviderTestInput {\n return {\n code: saved ? form.code : null,\n type,\n values: trimmed(form.values),\n secrets: trimmed(form.secrets),\n }\n}\n\n/**\n * 要填到厂商后台的回调地址:后端给的模板里把 `{code}` 换成标识(新增时标识没填,保留占位)。\n * 没配回调基址时模板为空串,返回空串,由页面提示。\n */\nexport function callbackFor(template: string, code: string): string {\n return template.replace('{code}', code || '{code}')\n}\n"],"names":["CODE_PATTERN","createForm","fields","saved","isNew","identity","values","secrets","f","normEndpoint","v","endpointChanged","form","secretRequired","field","endpointMoved","_a","missingFields","_b","moved","missing","trimmed","src","k","buildSaveBody","type","buildTestBody","callbackFor","template","code"],"mappings":"AAUO,MAAMA,IAAe;AAcrB,SAASC,EACdC,GACAC,GACAC,GACAC,GACc;AACd,QAAMC,IAAiC,CAAA,GACjCC,IAAkC,CAAA;AACxC,aAAWC,KAAKN;AACd,IAAIM,EAAE,SAAQD,EAAQC,EAAE,IAAI,IAAI,KAC3BF,EAAOE,EAAE,IAAI,KAAIL,KAAA,gBAAAA,EAAO,OAAOK,EAAE,UAASA,EAAE,WAAW;AAE9D,SAAO;AAAA,IACL,OAAAJ;AAAA,IACA,OAAMD,KAAA,gBAAAA,EAAO,SAAQE,EAAS;AAAA,IAC9B,cAAaF,KAAA,gBAAAA,EAAO,gBAAeE,EAAS;AAAA,IAC5C,QAAAC;AAAA,IACA,SAAAC;AAAA,EAAA;AAEJ;AAEA,MAAME,IAAe,CAACC,OAA2BA,KAAK,IAAI,OAAO,QAAQ,QAAQ,EAAE;AAG5E,SAASC,EACdT,GACAU,GACAT,GACS;AACT,SAAIS,EAAK,SAAS,CAACT,IAAc,KAC1BD,EAAO;AAAA,IACZ,CAAAM,MACEA,EAAE,mBAAmBC,EAAaG,EAAK,OAAOJ,EAAE,IAAI,CAAC,MAAMC,EAAaN,EAAM,OAAOK,EAAE,IAAI,CAAC;AAAA,EAAA;AAElG;AAGO,SAASK,EACdC,GACAX,GACAY,GACS;AAvDJ,MAAAC;AAwDL,SAAKF,EAAM,SACJ,GAACE,IAAAb,KAAA,gBAAAA,EAAO,QAAQW,EAAM,UAArB,QAAAE,EAA4B,aAAYD,IADtB;AAE5B;AAGO,SAASE,EACdf,GACAU,GACAT,GACU;AAjEL,MAAAa,GAAAE;AAkEL,QAAMC,IAAQR,EAAgBT,GAAQU,GAAMT,CAAK,GAC3CiB,IAAoB,CAAA;AAC1B,EAAIR,EAAK,UACFA,EAAK,YAAY,UAAQQ,EAAQ,KAAK,aAAa,GACnDpB,EAAa,KAAKY,EAAK,IAAI,KAAGQ,EAAQ,KAAK,MAAM;AAExD,aAAWZ,KAAKN;AACd,IAAIM,EAAE,SACAK,EAAeL,GAAGL,GAAOgB,CAAK,KAAK,GAACH,IAAAJ,EAAK,QAAQJ,EAAE,IAAI,MAAnB,QAAAQ,EAAsB,WAAQI,EAAQ,KAAKZ,EAAE,IAAI,IAChFA,EAAE,YAAY,GAACU,IAAAN,EAAK,OAAOJ,EAAE,IAAI,MAAlB,QAAAU,EAAqB,WAC7CE,EAAQ,KAAKZ,EAAE,IAAI;AAGvB,SAAOY;AACT;AAEA,MAAMC,IAAU,CAACC,MACf,OAAO,YAAY,OAAO,QAAQA,CAAG,EAAE,IAAI,CAAC,CAACC,GAAGb,CAAC,MAAM,CAACa,GAAGb,EAAE,KAAA,CAAM,CAAC,CAAC;AAGhE,SAASc,EAAcC,GAAcb,GAAmD;AAC7F,SAAO;AAAA,IACL,MAAAa;AAAA,IACA,aAAab,EAAK,YAAY,KAAA,KAAU;AAAA,IACxC,MAAM;AAAA,IACN,QAAQS,EAAQT,EAAK,MAAM;AAAA,IAC3B,SAASS,EAAQT,EAAK,OAAO;AAAA,EAAA;AAEjC;AAGO,SAASc,EACdD,GACAb,GACAT,GAC+B;AAC/B,SAAO;AAAA,IACL,MAAMA,IAAQS,EAAK,OAAO;AAAA,IAC1B,MAAAa;AAAA,IACA,QAAQJ,EAAQT,EAAK,MAAM;AAAA,IAC3B,SAASS,EAAQT,EAAK,OAAO;AAAA,EAAA;AAEjC;AAMO,SAASe,EAAYC,GAAkBC,GAAsB;AAClE,SAAOD,EAAS,QAAQ,UAAUC,KAAQ,QAAQ;AACpD;"}