weaver-e10-builder 1.5.13 → 1.5.14

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 (308) hide show
  1. package/dist/commands/actionflow.d.ts.map +1 -1
  2. package/dist/commands/actionflow.js +1 -0
  3. package/dist/commands/actionflow.js.map +1 -1
  4. package/dist/commands/designer-setters.d.ts.map +1 -1
  5. package/dist/commands/designer-setters.js +148 -47
  6. package/dist/commands/designer-setters.js.map +1 -1
  7. package/dist/commands/form-add-detail-table.d.ts.map +1 -1
  8. package/dist/commands/form-add-detail-table.js +53 -7
  9. package/dist/commands/form-add-detail-table.js.map +1 -1
  10. package/dist/commands/form-browser-definition.d.ts +14 -0
  11. package/dist/commands/form-browser-definition.d.ts.map +1 -0
  12. package/dist/commands/form-browser-definition.js +493 -0
  13. package/dist/commands/form-browser-definition.js.map +1 -0
  14. package/dist/commands/form-code.d.ts +5 -0
  15. package/dist/commands/form-code.d.ts.map +1 -0
  16. package/dist/commands/form-code.js +276 -0
  17. package/dist/commands/form-code.js.map +1 -0
  18. package/dist/commands/form-engine-linkage.d.ts +3 -0
  19. package/dist/commands/form-engine-linkage.d.ts.map +1 -0
  20. package/dist/commands/form-engine-linkage.js +199 -0
  21. package/dist/commands/form-engine-linkage.js.map +1 -0
  22. package/dist/commands/form-formula.d.ts.map +1 -1
  23. package/dist/commands/form-formula.js +7 -19
  24. package/dist/commands/form-formula.js.map +1 -1
  25. package/dist/commands/form-layout-template-advanced.d.ts +3 -0
  26. package/dist/commands/form-layout-template-advanced.d.ts.map +1 -0
  27. package/dist/commands/form-layout-template-advanced.js +115 -0
  28. package/dist/commands/form-layout-template-advanced.js.map +1 -0
  29. package/dist/commands/form-layout-template-easy.d.ts +3 -0
  30. package/dist/commands/form-layout-template-easy.d.ts.map +1 -0
  31. package/dist/commands/form-layout-template-easy.js +59 -0
  32. package/dist/commands/form-layout-template-easy.js.map +1 -0
  33. package/dist/commands/form-layout.d.ts.map +1 -1
  34. package/dist/commands/form-layout.js +77 -0
  35. package/dist/commands/form-layout.js.map +1 -1
  36. package/dist/commands/form-list-filter.js +1 -1
  37. package/dist/commands/form-list-filter.js.map +1 -1
  38. package/dist/commands/form-list-utils.d.ts +5 -0
  39. package/dist/commands/form-list-utils.d.ts.map +1 -1
  40. package/dist/commands/form-list-utils.js +23 -3
  41. package/dist/commands/form-list-utils.js.map +1 -1
  42. package/dist/commands/form-permission.d.ts.map +1 -1
  43. package/dist/commands/form-permission.js +55 -19
  44. package/dist/commands/form-permission.js.map +1 -1
  45. package/dist/commands/form-physical-table.d.ts.map +1 -1
  46. package/dist/commands/form-physical-table.js +8 -2
  47. package/dist/commands/form-physical-table.js.map +1 -1
  48. package/dist/commands/form.d.ts.map +1 -1
  49. package/dist/commands/form.js +143 -5
  50. package/dist/commands/form.js.map +1 -1
  51. package/dist/commands/menu.d.ts +68 -2
  52. package/dist/commands/menu.d.ts.map +1 -1
  53. package/dist/commands/menu.js +186 -41
  54. package/dist/commands/menu.js.map +1 -1
  55. package/dist/commands/page.d.ts.map +1 -1
  56. package/dist/commands/page.js +29 -6
  57. package/dist/commands/page.js.map +1 -1
  58. package/dist/commands/special.d.ts.map +1 -1
  59. package/dist/commands/special.js +3 -2
  60. package/dist/commands/special.js.map +1 -1
  61. package/dist/commands/wf-form.d.ts.map +1 -1
  62. package/dist/commands/wf-form.js +76 -31
  63. package/dist/commands/wf-form.js.map +1 -1
  64. package/dist/commands/workflow-auth-resp.js +2 -2
  65. package/dist/commands/workflow-auth-resp.js.map +1 -1
  66. package/dist/commands/workflow-base-settings.d.ts +3 -0
  67. package/dist/commands/workflow-base-settings.d.ts.map +1 -0
  68. package/dist/commands/workflow-base-settings.js +73 -0
  69. package/dist/commands/workflow-base-settings.js.map +1 -0
  70. package/dist/commands/workflow-condition.d.ts.map +1 -1
  71. package/dist/commands/workflow-condition.js +59 -450
  72. package/dist/commands/workflow-condition.js.map +1 -1
  73. package/dist/commands/workflow-export.d.ts +9 -0
  74. package/dist/commands/workflow-export.d.ts.map +1 -0
  75. package/dist/commands/workflow-export.js +121 -0
  76. package/dist/commands/workflow-export.js.map +1 -0
  77. package/dist/commands/workflow-node.d.ts.map +1 -1
  78. package/dist/commands/workflow-node.js +28 -53
  79. package/dist/commands/workflow-node.js.map +1 -1
  80. package/dist/commands/workflow-operator-batch.d.ts +3 -0
  81. package/dist/commands/workflow-operator-batch.d.ts.map +1 -0
  82. package/dist/commands/workflow-operator-batch.js +35 -0
  83. package/dist/commands/workflow-operator-batch.js.map +1 -0
  84. package/dist/commands/workflow-subprocess.d.ts.map +1 -1
  85. package/dist/commands/workflow-subprocess.js +21 -33
  86. package/dist/commands/workflow-subprocess.js.map +1 -1
  87. package/dist/commands/workflow.d.ts.map +1 -1
  88. package/dist/commands/workflow.js +18 -33
  89. package/dist/commands/workflow.js.map +1 -1
  90. package/dist/index.js +18 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/services/actionflow/browser_source.d.ts +9 -0
  93. package/dist/services/actionflow/browser_source.d.ts.map +1 -0
  94. package/dist/services/actionflow/browser_source.js +12 -0
  95. package/dist/services/actionflow/browser_source.js.map +1 -0
  96. package/dist/services/actionflow/components/date_trigger.js +1 -1
  97. package/dist/services/actionflow/components/date_trigger.js.map +1 -1
  98. package/dist/services/actionflow/components/document_actions.d.ts.map +1 -1
  99. package/dist/services/actionflow/components/document_actions.js +178 -25
  100. package/dist/services/actionflow/components/document_actions.js.map +1 -1
  101. package/dist/services/actionflow/components/enquiry_form.js +1 -1
  102. package/dist/services/actionflow/components/enquiry_form.js.map +1 -1
  103. package/dist/services/actionflow/components/logic_flow_set.d.ts.map +1 -1
  104. package/dist/services/actionflow/components/logic_flow_set.js +80 -8
  105. package/dist/services/actionflow/components/logic_flow_set.js.map +1 -1
  106. package/dist/services/actionflow/components/meeting_create.d.ts.map +1 -1
  107. package/dist/services/actionflow/components/meeting_create.js +1 -0
  108. package/dist/services/actionflow/components/meeting_create.js.map +1 -1
  109. package/dist/services/actionflow/components/schedule_create.d.ts.map +1 -1
  110. package/dist/services/actionflow/components/schedule_create.js +2 -0
  111. package/dist/services/actionflow/components/schedule_create.js.map +1 -1
  112. package/dist/services/actionflow/components/time_batch_trigger.js +1 -1
  113. package/dist/services/actionflow/components/time_batch_trigger.js.map +1 -1
  114. package/dist/services/actionflow/components/workflow_actions.d.ts.map +1 -1
  115. package/dist/services/actionflow/components/workflow_actions.js +6 -2
  116. package/dist/services/actionflow/components/workflow_actions.js.map +1 -1
  117. package/dist/services/actionflow/context_preflight.d.ts.map +1 -1
  118. package/dist/services/actionflow/context_preflight.js +103 -6
  119. package/dist/services/actionflow/context_preflight.js.map +1 -1
  120. package/dist/services/actionflow/form_source.d.ts +2 -0
  121. package/dist/services/actionflow/form_source.d.ts.map +1 -1
  122. package/dist/services/actionflow/form_source.js +8 -0
  123. package/dist/services/actionflow/form_source.js.map +1 -1
  124. package/dist/services/actionflow/formula_service.d.ts.map +1 -1
  125. package/dist/services/actionflow/formula_service.js +35 -19
  126. package/dist/services/actionflow/formula_service.js.map +1 -1
  127. package/dist/services/actionflow/logicflow_source.d.ts +5 -0
  128. package/dist/services/actionflow/logicflow_source.d.ts.map +1 -0
  129. package/dist/services/actionflow/logicflow_source.js +15 -0
  130. package/dist/services/actionflow/logicflow_source.js.map +1 -0
  131. package/dist/services/actionflow/preflight_service.d.ts +15 -0
  132. package/dist/services/actionflow/preflight_service.d.ts.map +1 -1
  133. package/dist/services/actionflow/preflight_service.js +558 -26
  134. package/dist/services/actionflow/preflight_service.js.map +1 -1
  135. package/dist/services/actionflow/runtime_metadata.d.ts +8 -0
  136. package/dist/services/actionflow/runtime_metadata.d.ts.map +1 -1
  137. package/dist/services/actionflow/runtime_metadata.js +25 -0
  138. package/dist/services/actionflow/runtime_metadata.js.map +1 -1
  139. package/dist/services/actionflow/workflow_compiler.d.ts.map +1 -1
  140. package/dist/services/actionflow/workflow_compiler.js +50 -10
  141. package/dist/services/actionflow/workflow_compiler.js.map +1 -1
  142. package/dist/services/actionflow/workflow_source.d.ts +2 -0
  143. package/dist/services/actionflow/workflow_source.d.ts.map +1 -1
  144. package/dist/services/actionflow/workflow_source.js +7 -0
  145. package/dist/services/actionflow/workflow_source.js.map +1 -1
  146. package/dist/services/auth-resp-condition.d.ts +7 -3
  147. package/dist/services/auth-resp-condition.d.ts.map +1 -1
  148. package/dist/services/auth-resp-condition.js +58 -63
  149. package/dist/services/auth-resp-condition.js.map +1 -1
  150. package/dist/services/browser.d.ts +5 -0
  151. package/dist/services/browser.d.ts.map +1 -1
  152. package/dist/services/browser.js +16 -4
  153. package/dist/services/browser.js.map +1 -1
  154. package/dist/services/condition-rules.d.ts +2 -30
  155. package/dist/services/condition-rules.d.ts.map +1 -1
  156. package/dist/services/condition-rules.js +3 -219
  157. package/dist/services/condition-rules.js.map +1 -1
  158. package/dist/services/designer-dataset.d.ts +45 -0
  159. package/dist/services/designer-dataset.d.ts.map +1 -0
  160. package/dist/services/designer-dataset.js +156 -0
  161. package/dist/services/designer-dataset.js.map +1 -0
  162. package/dist/services/form-association.d.ts +7 -0
  163. package/dist/services/form-association.d.ts.map +1 -1
  164. package/dist/services/form-association.js +22 -1
  165. package/dist/services/form-association.js.map +1 -1
  166. package/dist/services/form-code.d.ts +53 -0
  167. package/dist/services/form-code.d.ts.map +1 -0
  168. package/dist/services/form-code.js +370 -0
  169. package/dist/services/form-code.js.map +1 -0
  170. package/dist/services/form-create.d.ts +20 -0
  171. package/dist/services/form-create.d.ts.map +1 -1
  172. package/dist/services/form-create.js +73 -1
  173. package/dist/services/form-create.js.map +1 -1
  174. package/dist/services/form-field-build.d.ts +1 -1
  175. package/dist/services/form-field-build.d.ts.map +1 -1
  176. package/dist/services/form-field-build.js +20 -1
  177. package/dist/services/form-field-build.js.map +1 -1
  178. package/dist/services/form-field-drop.d.ts +2 -0
  179. package/dist/services/form-field-drop.d.ts.map +1 -1
  180. package/dist/services/form-field-drop.js +2 -0
  181. package/dist/services/form-field-drop.js.map +1 -1
  182. package/dist/services/form-layout-detail-insert.d.ts +58 -0
  183. package/dist/services/form-layout-detail-insert.d.ts.map +1 -0
  184. package/dist/services/form-layout-detail-insert.js +293 -0
  185. package/dist/services/form-layout-detail-insert.js.map +1 -0
  186. package/dist/services/form-layout-insert.d.ts.map +1 -1
  187. package/dist/services/form-layout-insert.js +34 -2
  188. package/dist/services/form-layout-insert.js.map +1 -1
  189. package/dist/services/form-layout-source/compile.d.ts.map +1 -1
  190. package/dist/services/form-layout-source/compile.js +11 -4
  191. package/dist/services/form-layout-source/compile.js.map +1 -1
  192. package/dist/services/form-layout-template-advanced.d.ts +330 -0
  193. package/dist/services/form-layout-template-advanced.d.ts.map +1 -0
  194. package/dist/services/form-layout-template-advanced.js +1437 -0
  195. package/dist/services/form-layout-template-advanced.js.map +1 -0
  196. package/dist/services/form-layout-template-easy.d.ts +34 -0
  197. package/dist/services/form-layout-template-easy.d.ts.map +1 -0
  198. package/dist/services/form-layout-template-easy.js +159 -0
  199. package/dist/services/form-layout-template-easy.js.map +1 -0
  200. package/dist/services/form-layout-template-flow.d.ts +23 -0
  201. package/dist/services/form-layout-template-flow.d.ts.map +1 -0
  202. package/dist/services/form-layout-template-flow.js +99 -0
  203. package/dist/services/form-layout-template-flow.js.map +1 -0
  204. package/dist/services/form-permission-archive.d.ts +23 -1
  205. package/dist/services/form-permission-archive.d.ts.map +1 -1
  206. package/dist/services/form-permission-archive.js +54 -2
  207. package/dist/services/form-permission-archive.js.map +1 -1
  208. package/dist/services/form-physical-table.d.ts +10 -0
  209. package/dist/services/form-physical-table.d.ts.map +1 -1
  210. package/dist/services/form-physical-table.js +20 -0
  211. package/dist/services/form-physical-table.js.map +1 -1
  212. package/dist/services/form-view-linkage-functions.d.ts +19 -0
  213. package/dist/services/form-view-linkage-functions.d.ts.map +1 -0
  214. package/dist/services/form-view-linkage-functions.js +90 -0
  215. package/dist/services/form-view-linkage-functions.js.map +1 -0
  216. package/dist/services/matrix-condition.d.ts +32 -1
  217. package/dist/services/matrix-condition.d.ts.map +1 -1
  218. package/dist/services/matrix-condition.js +169 -6
  219. package/dist/services/matrix-condition.js.map +1 -1
  220. package/dist/services/menu-catalog.d.ts +24 -1
  221. package/dist/services/menu-catalog.d.ts.map +1 -1
  222. package/dist/services/menu-catalog.js +49 -2
  223. package/dist/services/menu-catalog.js.map +1 -1
  224. package/dist/services/workflow-base-settings.d.ts +117 -0
  225. package/dist/services/workflow-base-settings.d.ts.map +1 -0
  226. package/dist/services/workflow-base-settings.js +504 -0
  227. package/dist/services/workflow-base-settings.js.map +1 -0
  228. package/dist/services/workflow-condition-core.d.ts +48 -0
  229. package/dist/services/workflow-condition-core.d.ts.map +1 -0
  230. package/dist/services/workflow-condition-core.js +316 -0
  231. package/dist/services/workflow-condition-core.js.map +1 -0
  232. package/dist/services/workflow-context.d.ts +10 -0
  233. package/dist/services/workflow-context.d.ts.map +1 -0
  234. package/dist/services/workflow-context.js +15 -0
  235. package/dist/services/workflow-context.js.map +1 -0
  236. package/dist/services/workflow-export.d.ts +80 -0
  237. package/dist/services/workflow-export.d.ts.map +1 -0
  238. package/dist/services/workflow-export.js +150 -0
  239. package/dist/services/workflow-export.js.map +1 -0
  240. package/dist/services/workflow-field-permission-route.d.ts +75 -0
  241. package/dist/services/workflow-field-permission-route.d.ts.map +1 -0
  242. package/dist/services/workflow-field-permission-route.js +52 -0
  243. package/dist/services/workflow-field-permission-route.js.map +1 -0
  244. package/dist/services/workflow-field-permission.d.ts +18 -0
  245. package/dist/services/workflow-field-permission.d.ts.map +1 -0
  246. package/dist/services/workflow-field-permission.js +40 -0
  247. package/dist/services/workflow-field-permission.js.map +1 -0
  248. package/dist/services/workflow-node-layout.d.ts +11 -10
  249. package/dist/services/workflow-node-layout.d.ts.map +1 -1
  250. package/dist/services/workflow-node-layout.js +24 -62
  251. package/dist/services/workflow-node-layout.js.map +1 -1
  252. package/dist/services/workflow-operator-batch-condition.d.ts +23 -0
  253. package/dist/services/workflow-operator-batch-condition.d.ts.map +1 -0
  254. package/dist/services/workflow-operator-batch-condition.js +56 -0
  255. package/dist/services/workflow-operator-batch-condition.js.map +1 -0
  256. package/package.json +1 -1
  257. package/skill-docs/SKILL.md +155 -154
  258. package/skill-docs/_catalog.md +9 -2
  259. package/skill-docs/actionflows/references/components/document/GetDocumentInfo.md +17 -17
  260. package/skill-docs/actionflows/references/components/logic/LogicFlowSet.md +23 -17
  261. package/skill-docs/assets/form-template/style-templates-advanced-base-example.json +1420 -0
  262. package/skill-docs/assets/form-template/style-templates-advanced-base.css +33 -0
  263. package/skill-docs/assets/form-template/style-templates-advanced-blue-crystal-example.json +1094 -0
  264. package/skill-docs/assets/form-template/style-templates-advanced-document-example.json +1251 -0
  265. package/skill-docs/assets/form-template/style-templates-advanced-grid-example.json +39 -0
  266. package/skill-docs/assets/form-template/style-templates-advanced-page-example.json +38 -0
  267. package/skill-docs/assets/form-template/style-templates-advanced-placements-example.json +30 -0
  268. package/skill-docs/assets/form-template/style-templates-advanced-standard-example.json +53 -0
  269. package/skill-docs/assets/form-template/style-templates-advanced-structure-example.json +25 -0
  270. package/skill-docs/assets/form-template/style-templates-advanced-table-example.json +1420 -0
  271. package/skill-docs/references/code-block/guide-code-block.md +141 -0
  272. package/skill-docs/references/code-block/guide-layout-css.md +107 -0
  273. package/skill-docs/references/code-block/ref-controls.md +60 -0
  274. package/skill-docs/references/code-block/ref-coverage.md +32 -0
  275. package/skill-docs/references/code-block/ref-details.md +57 -0
  276. package/skill-docs/references/code-block/ref-events-validation.md +42 -0
  277. package/skill-docs/references/code-block/ref-foundations-fields.md +56 -0
  278. package/skill-docs/references/code-block/ref-recipes.md +164 -0
  279. package/skill-docs/references/code-block/ref-troubleshooting.md +49 -0
  280. package/skill-docs/references/code-block/ref-ui-global.md +47 -0
  281. package/skill-docs/references/common/guide-plan-template.md +168 -167
  282. package/skill-docs/references/common/map.md +2 -1
  283. package/skill-docs/references/common/ref-cli-commands.md +54 -15
  284. package/skill-docs/references/ebuilder-designer/guide-designer-factory.md +8 -4
  285. package/skill-docs/references/form/form-template/style-templates-advanced-css-guide.md +167 -0
  286. package/skill-docs/references/form/form-template/style-templates-advanced.md +315 -0
  287. package/skill-docs/references/form/form-template/style-templates-easy.md +55 -0
  288. package/skill-docs/references/form/form-template/style-templates-flow.md +27 -0
  289. package/skill-docs/references/form/form-template/style-templates.md +29 -0
  290. package/skill-docs/references/form/guide-browser-definition.md +148 -0
  291. package/skill-docs/references/form/guide-create-form.md +258 -250
  292. package/skill-docs/references/form/guide-form-archive-permission.md +29 -6
  293. package/skill-docs/references/form/guide-form-formula-functions.md +354 -354
  294. package/skill-docs/references/form/guide-form-layout-incremental.md +28 -0
  295. package/skill-docs/references/form/guide-form-layout-planning.md +11 -0
  296. package/skill-docs/references/form/guide-form-layout-source.md +4 -0
  297. package/skill-docs/references/form/guide-form-linkage.md +295 -259
  298. package/skill-docs/references/form/guide-form-physical-table.md +2 -0
  299. package/skill-docs/references/form/guide-form-view.md +3 -0
  300. package/skill-docs/references/form/ref-form-fields.md +301 -293
  301. package/skill-docs/references/form/ref-list-filter-date-params.md +83 -0
  302. package/skill-docs/references/menu/system-menus.md +12 -0
  303. package/skill-docs/references/workflow/condition-core-contract.md +63 -0
  304. package/skill-docs/references/workflow/guide-base-auth-share-remind.md +126 -0
  305. package/skill-docs/references/workflow/guide-workflow-condition.md +476 -447
  306. package/skill-docs/references/workflow/guide-workflow-node-layout.md +6 -0
  307. package/skill-docs/references/workflow/guide-workflow.md +28 -2
  308. package/skill-docs/references/form/style-templates.md +0 -97
@@ -0,0 +1,1437 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { isDeepStrictEqual } from 'node:util';
3
+ import { assertDesignerReadableLayoutCode, decodeLayoutCode, encodeLayoutCode, updateCode } from './form-code.js';
4
+ const ROOT = '/api/form/form/layout/template';
5
+ const MODULE = 'form';
6
+ export function validateAdvancedLayoutEditPatch(input) {
7
+ const patch = object(input, '布局修改配置');
8
+ keys(patch, ['edits'], '布局修改配置');
9
+ if (!Array.isArray(patch.edits) || patch.edits.length === 0)
10
+ throw new Error('edits 必须是非空数组');
11
+ for (const edit of patch.edits) {
12
+ const item = object(edit, 'edits 项');
13
+ keys(item, ['op', 'path', 'value'], 'edits 项');
14
+ if (item.op !== 'set' && item.op !== 'remove')
15
+ throw new Error('edits.op 只能是 set 或 remove');
16
+ parseLayoutPath(item.path);
17
+ if (item.op === 'set' && !Object.hasOwn(item, 'value'))
18
+ throw new Error('set 操作必须提供 value');
19
+ if (item.op === 'remove' && Object.hasOwn(item, 'value'))
20
+ throw new Error('remove 操作不能提供 value');
21
+ }
22
+ return patch;
23
+ }
24
+ export function validateAdvancedTemplateCreateConfig(input) {
25
+ const config = object(input, '模板新建配置');
26
+ if (config.sourceTemplateId === undefined && config.layoutInfo === undefined && config.edits === undefined)
27
+ return validateMinimalAdvancedDesign(input);
28
+ keys(config, ['templateName', 'sourceTemplateId', 'layoutInfo', 'edits', 'layoutStyle', 'grid', 'coverFileId', 'cssSource'], '模板新建配置');
29
+ if (typeof config.templateName !== 'string' || !config.templateName.trim() || config.templateName.length > 100)
30
+ throw new Error('templateName 必须是 1–100 字的名称');
31
+ if ((config.sourceTemplateId === undefined) === (config.layoutInfo === undefined))
32
+ throw new Error('sourceTemplateId 与 layoutInfo 必须且只能提供一个');
33
+ if (config.sourceTemplateId !== undefined && (typeof config.sourceTemplateId !== 'string' || !/^\d+$/.test(config.sourceTemplateId)))
34
+ throw new Error('sourceTemplateId 必须是模板 ID');
35
+ if (config.layoutInfo !== undefined)
36
+ object(config.layoutInfo, 'layoutInfo');
37
+ if (config.edits !== undefined)
38
+ validateAdvancedLayoutEditPatch({ edits: config.edits });
39
+ if (config.layoutStyle !== undefined)
40
+ validateAdvancedLayoutStylePatch(config.layoutStyle);
41
+ if (config.grid !== undefined)
42
+ validateAdvancedGridPatch(config.grid);
43
+ if (config.coverFileId !== undefined && typeof config.coverFileId !== 'string')
44
+ throw new Error('coverFileId 必须是字符串');
45
+ if (config.cssSource !== undefined && typeof config.cssSource !== 'string')
46
+ throw new Error('cssSource 必须是 CSS 文字');
47
+ return config;
48
+ }
49
+ function parseLayoutPath(path) {
50
+ if (typeof path !== 'string' || !path.startsWith('/') || path === '/')
51
+ throw new Error('edits.path 必须是指向 layoutInfo 内属性的 JSON Pointer');
52
+ const parts = path.slice(1).split('/').map(part => {
53
+ if (/~(?![01])/.test(part))
54
+ throw new Error('edits.path 转义无效');
55
+ return part.replace(/~1/g, '/').replace(/~0/g, '~');
56
+ });
57
+ if (parts.some(part => !part || ['__proto__', 'prototype', 'constructor'].includes(part)))
58
+ throw new Error('edits.path 包含不允许的路径');
59
+ return parts;
60
+ }
61
+ export function applyAdvancedLayoutEdits(layoutInfo, edits) {
62
+ for (const edit of edits || []) {
63
+ const parts = parseLayoutPath(edit.path);
64
+ let parent = layoutInfo;
65
+ for (const part of parts.slice(0, -1)) {
66
+ if (!plain(parent) && !Array.isArray(parent))
67
+ throw new Error(`路径不存在:${edit.path}`);
68
+ parent = parent[part];
69
+ if (parent === undefined)
70
+ throw new Error(`路径不存在:${edit.path}`);
71
+ }
72
+ const leaf = parts.at(-1);
73
+ if (!plain(parent) && !Array.isArray(parent))
74
+ throw new Error(`路径不存在:${edit.path}`);
75
+ if (Array.isArray(parent) && (!/^\d+$/.test(leaf) || Number(leaf) >= parent.length))
76
+ throw new Error(`数组位置不存在:${edit.path}`);
77
+ if (edit.op === 'remove') {
78
+ if (Array.isArray(parent) || !Object.hasOwn(parent, leaf))
79
+ throw new Error(`不能删除该路径:${edit.path}`);
80
+ delete parent[leaf];
81
+ }
82
+ else
83
+ parent[leaf] = structuredClone(edit.value);
84
+ }
85
+ }
86
+ function verifyAdvancedLayoutEdits(expectedLayout, savedLayout, edits, id) {
87
+ const normalize = (value) => {
88
+ if (Array.isArray(value))
89
+ return value.map(normalize);
90
+ if (!plain(value))
91
+ return value;
92
+ const result = {};
93
+ for (const [key, item] of Object.entries(value)) {
94
+ if ((key === 'id' && value.type && value.config) || (key === 'comId' && value.allowOverflow !== undefined && value.type))
95
+ continue;
96
+ result[key] = normalize(item);
97
+ }
98
+ return result;
99
+ };
100
+ for (const edit of edits || []) {
101
+ const parts = parseLayoutPath(edit.path);
102
+ const read = (obj, path) => path.reduce((value, part) => value?.[part], obj);
103
+ const expected = read(expectedLayout, parts);
104
+ let actual = read(savedLayout, parts);
105
+ if (actual === undefined && parts[0] === 'pageLayouts' && parts[2] === 'config' && parts[3] === 'workbook' && parts[4] !== 'Sheet1') {
106
+ const index = expectedLayout.pageLayouts?.[Number(parts[1])]?.comps?.findIndex((comp) => comp.id === parts[4]);
107
+ const savedKey = savedLayout.pageLayouts?.[Number(parts[1])]?.comps?.[index]?.id;
108
+ if (savedKey)
109
+ actual = read(savedLayout, [...parts.slice(0, 4), savedKey, ...parts.slice(5)]);
110
+ }
111
+ if (!isDeepStrictEqual(normalize(actual), normalize(expected)))
112
+ throw new Error(`模板 ${id} 的 ${edit.path} 回读不一致`);
113
+ }
114
+ }
115
+ const plain = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
116
+ const cssColor = (value, label) => {
117
+ if (typeof value !== 'string' || !/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value))
118
+ throw new Error(`${label} 必须是 #RGB、#RRGGBB 或 #RRGGBBAA`);
119
+ return value;
120
+ };
121
+ const borderColor = (value, label) => value === 'transparent' ? 'transparent' : cssColor(value, label);
122
+ const pixel = (value, label) => {
123
+ if (!Number.isInteger(value) || Number(value) < 0 || Number(value) > 2000)
124
+ throw new Error(`${label} 必须是 0–2000 的整数`);
125
+ return Number(value);
126
+ };
127
+ function keys(value, allowed, label) {
128
+ for (const key of Object.keys(value))
129
+ if (!allowed.includes(key))
130
+ throw new Error(`${label} 不支持 ${key}`);
131
+ }
132
+ function object(value, label) {
133
+ if (!plain(value))
134
+ throw new Error(`${label} 必须是对象`);
135
+ return value;
136
+ }
137
+ function dimension(value, label) {
138
+ if (typeof value !== 'string' || !/^(100|[1-9]?\d)%$|^[1-9]\d{0,3}px$/.test(value))
139
+ throw new Error(`${label} 必须是百分比或正整数 px`);
140
+ return value;
141
+ }
142
+ function spacing(value, allowed, label, signed = false) {
143
+ const item = object(value, label);
144
+ keys(item, allowed, label);
145
+ for (const [key, val] of Object.entries(item)) {
146
+ if (signed && key.startsWith('margin') && Number.isInteger(val) && val >= -2000 && val <= 2000)
147
+ continue;
148
+ pixel(val, `${label}.${key}`);
149
+ }
150
+ return item;
151
+ }
152
+ function solidBorder(value, label) {
153
+ const border = object(value, label);
154
+ keys(border, ['widthPx', 'color'], label);
155
+ if (border.widthPx === undefined || border.color === undefined)
156
+ throw new Error(`${label} 需要 widthPx 和 color`);
157
+ pixel(border.widthPx, `${label}.widthPx`);
158
+ cssColor(border.color, `${label}.color`);
159
+ }
160
+ function noBorder(value, label) {
161
+ if (value !== 'none')
162
+ throw new Error(`${label} 目前只支持 none`);
163
+ }
164
+ function validateInputStyle(value, label, widthKey) {
165
+ const item = object(value, label);
166
+ keys(item, [...(widthKey ? [widthKey] : []), 'backgroundColor', 'border'], label);
167
+ if (widthKey && item[widthKey] !== undefined)
168
+ dimension(item[widthKey], `${label}.${widthKey}`);
169
+ if (item.backgroundColor !== undefined)
170
+ cssColor(item.backgroundColor, `${label}.backgroundColor`);
171
+ if (item.border !== undefined)
172
+ noBorder(item.border, `${label}.border`);
173
+ }
174
+ function validatePageRegion(value, label) {
175
+ const region = object(value, label);
176
+ keys(region, ['backgroundColor', 'borders', 'radiusPx', 'cornerRadius'], label);
177
+ if (region.backgroundColor !== undefined)
178
+ cssColor(region.backgroundColor, `${label}.backgroundColor`);
179
+ if (region.radiusPx !== undefined)
180
+ pixel(region.radiusPx, `${label}.radiusPx`);
181
+ if (region.cornerRadius !== undefined)
182
+ spacing(region.cornerRadius, ['topLeftPx', 'topRightPx', 'bottomLeftPx', 'bottomRightPx'], `${label}.cornerRadius`);
183
+ if (region.borders !== undefined) {
184
+ const borders = object(region.borders, `${label}.borders`);
185
+ keys(borders, ['all', 'top', 'right', 'bottom', 'left'], `${label}.borders`);
186
+ for (const [side, raw] of Object.entries(borders)) {
187
+ const border = object(raw, `${label}.borders.${side}`);
188
+ keys(border, ['widthPx', 'line', 'color'], `${label}.borders.${side}`);
189
+ pixel(border.widthPx, `${label}.borders.${side}.widthPx`);
190
+ if (!['none', 'solid', 'dashed', 'dotted'].includes(border.line))
191
+ throw new Error(`${label}.borders.${side}.line 无效`);
192
+ borderColor(border.color, `${label}.borders.${side}.color`);
193
+ }
194
+ }
195
+ }
196
+ export function validateAdvancedGridPatch(value) {
197
+ const patch = object(value, 'grid');
198
+ keys(patch, ['main', 'detail'], 'grid');
199
+ if (!Object.keys(patch).length)
200
+ throw new Error('grid 至少需要 main 或 detail');
201
+ for (const [scope, raw] of Object.entries(patch)) {
202
+ const sheet = object(raw, `grid.${scope}`);
203
+ keys(sheet, ['rowHeights', 'columns', 'cells', 'merge', 'split'], `grid.${scope}`);
204
+ for (const kind of ['rowHeights', 'columns', 'cells', 'merge', 'split'])
205
+ if (sheet[kind] !== undefined && !Array.isArray(sheet[kind]))
206
+ throw new Error(`grid.${scope}.${kind} 必须是数组`);
207
+ for (const item of sheet.rowHeights || []) {
208
+ const row = object(item, `${scope}.rowHeights`);
209
+ keys(row, ['row', 'px'], `${scope}.rowHeights`);
210
+ pixel(row.row, 'rowHeights.row');
211
+ pixel(row.px, 'rowHeights.px');
212
+ if (row.px === 0)
213
+ throw new Error('rowHeights.px 必须大于 0');
214
+ }
215
+ for (const item of sheet.columns || []) {
216
+ const col = object(item, `${scope}.columns`);
217
+ keys(col, ['col', 'unit', 'value'], `${scope}.columns`);
218
+ pixel(col.col, 'columns.col');
219
+ if (!['percent', 'fixed'].includes(col.unit))
220
+ throw new Error('columns.unit 只能是 percent 或 fixed');
221
+ pixel(col.value, 'columns.value');
222
+ if (col.value === 0)
223
+ throw new Error('columns.value 必须大于 0');
224
+ if (col.unit === 'percent' && col.value > 100)
225
+ throw new Error('百分比列宽不能大于 100');
226
+ }
227
+ for (const item of sheet.cells || []) {
228
+ const cell = object(item, `${scope}.cells`);
229
+ keys(cell, ['row', 'col', 'text', 'systemParameter', 'backgroundColor', 'textColor', 'font', 'fontFamily', 'fontSizePx',
230
+ 'bold', 'italic', 'underline', 'horizontalAlign', 'verticalAlign', 'wordWrap', 'textIndent', 'borders'], `${scope}.cells`);
231
+ pixel(cell.row, 'cells.row');
232
+ pixel(cell.col, 'cells.col');
233
+ if (cell.text !== undefined && (typeof cell.text !== 'string' || cell.text.length > 10000))
234
+ throw new Error('cells.text 必须是长度不超过 10000 的文字');
235
+ if (cell.systemParameter !== undefined && cell.systemParameter !== 'formTitle')
236
+ throw new Error('cells.systemParameter 目前只支持 formTitle');
237
+ if (cell.text !== undefined && cell.systemParameter !== undefined)
238
+ throw new Error('cells.text 与 systemParameter 不能同时设置');
239
+ if (cell.backgroundColor !== undefined)
240
+ cssColor(cell.backgroundColor, 'cells.backgroundColor');
241
+ if (cell.textColor !== undefined)
242
+ cssColor(cell.textColor, 'cells.textColor');
243
+ if (cell.font !== undefined && (typeof cell.font !== 'string' || !/^(?:italic )?(?:[1-9]00 )?\d{1,3}px [\p{L}\p{N} _-]+$/u.test(cell.font)))
244
+ throw new Error('cells.font 格式如 italic 700 14px 宋体');
245
+ if (cell.font !== undefined && ['fontFamily', 'fontSizePx', 'bold', 'italic'].some(key => cell[key] !== undefined))
246
+ throw new Error('cells.font 不能与独立字体设置同时使用');
247
+ if (cell.fontFamily !== undefined && (typeof cell.fontFamily !== 'string' || !/^[\p{L}\p{N} _-]{1,100}$/u.test(cell.fontFamily)))
248
+ throw new Error('cells.fontFamily 格式无效');
249
+ if (cell.fontSizePx !== undefined && (pixel(cell.fontSizePx, 'cells.fontSizePx') < 1 || cell.fontSizePx > 999))
250
+ throw new Error('cells.fontSizePx 必须是 1–999 的整数');
251
+ for (const key of ['bold', 'italic', 'underline', 'wordWrap'])
252
+ if (cell[key] !== undefined && typeof cell[key] !== 'boolean')
253
+ throw new Error(`cells.${key} 必须是布尔值`);
254
+ if (cell.horizontalAlign !== undefined && !['left', 'center', 'right'].includes(cell.horizontalAlign))
255
+ throw new Error('cells.horizontalAlign 无效');
256
+ if (cell.verticalAlign !== undefined && !['top', 'middle', 'bottom'].includes(cell.verticalAlign))
257
+ throw new Error('cells.verticalAlign 无效');
258
+ if (cell.textIndent !== undefined)
259
+ pixel(cell.textIndent, 'cells.textIndent');
260
+ if (cell.borders !== undefined) {
261
+ const borders = object(cell.borders, 'cells.borders');
262
+ keys(borders, ['top', 'right', 'bottom', 'left'], 'cells.borders');
263
+ for (const [side, rawBorder] of Object.entries(borders)) {
264
+ const border = object(rawBorder, `cells.borders.${side}`);
265
+ keys(border, ['color', 'line'], `cells.borders.${side}`);
266
+ if (!['none', 'thin', 'medium', 'thick', 'dashed', 'dotted', 'double'].includes(border.line))
267
+ throw new Error(`cells.borders.${side}.line 无效`);
268
+ if (border.line !== 'none')
269
+ cssColor(border.color, `cells.borders.${side}.color`);
270
+ else if (border.color !== undefined)
271
+ cssColor(border.color, `cells.borders.${side}.color`);
272
+ }
273
+ }
274
+ }
275
+ for (const item of sheet.merge || []) {
276
+ const merge = object(item, `${scope}.merge`);
277
+ keys(merge, ['row', 'col', 'rowCount', 'colCount'], `${scope}.merge`);
278
+ for (const key of ['row', 'col', 'rowCount', 'colCount'])
279
+ pixel(merge[key], `merge.${key}`);
280
+ if (!merge.rowCount || !merge.colCount || (merge.rowCount === 1 && merge.colCount === 1))
281
+ throw new Error('merge 至少覆盖两个单元格');
282
+ }
283
+ for (const item of sheet.split || []) {
284
+ const split = object(item, `${scope}.split`);
285
+ keys(split, ['row', 'col'], `${scope}.split`);
286
+ pixel(split.row, 'split.row');
287
+ pixel(split.col, 'split.col');
288
+ }
289
+ }
290
+ return patch;
291
+ }
292
+ export function applyAdvancedGridPatch(layoutInfo, value) {
293
+ const patch = validateAdvancedGridPatch(value);
294
+ const workbook = layoutInfo.pageLayouts?.[0]?.config?.workbook;
295
+ if (!plain(workbook) || !workbook.Sheet1)
296
+ throw new Error('模板缺少 PC 表格式主网格');
297
+ for (const scope of ['main', 'detail']) {
298
+ const item = patch[scope];
299
+ if (!item)
300
+ continue;
301
+ const sheetName = scope === 'main' ? 'Sheet1' : Object.keys(workbook).find(key => key !== 'Sheet1');
302
+ const sheet = sheetName && workbook[sheetName];
303
+ if (!sheet)
304
+ throw new Error(`模板没有${scope === 'main' ? '主表' : '明细表'}网格`);
305
+ for (const row of item.rowHeights || []) {
306
+ if (!sheet.rows[row.row])
307
+ throw new Error(`${scope} 第 ${row.row} 行不存在`);
308
+ sheet.rows[row.row].size = row.px;
309
+ }
310
+ for (const col of item.columns || []) {
311
+ if (!sheet.columns[col.col])
312
+ throw new Error(`${scope} 第 ${col.col} 列不存在`);
313
+ sheet.columns[col.col] = { ...sheet.columns[col.col], type: col.unit, size: col.value };
314
+ }
315
+ if (sheet.columns.every((col) => col.type === 'percent') && sheet.columns.reduce((n, col) => n + col.size, 0) !== 100)
316
+ throw new Error(`${scope} 百分比列宽之和必须为 100`);
317
+ const cellsIn = (span) => sheet.cells.filter((c) => Number(c.row) >= span.row && Number(c.row) < span.row + span.rowCount &&
318
+ Number(c.col) >= span.col && Number(c.col) < span.col + span.colCount);
319
+ const overlap = (a, b) => a.row < b.row + b.rowCount && a.row + a.rowCount > b.row &&
320
+ a.col < b.col + b.colCount && a.col + a.colCount > b.col;
321
+ for (const change of item.split || []) {
322
+ const index = sheet.spans.findIndex((span) => span.row === change.row && span.col === change.col);
323
+ if (index < 0)
324
+ throw new Error(`${scope} ${change.row},${change.col} 不是合并区域的左上角`);
325
+ const span = sheet.spans[index];
326
+ if (cellsIn(span).some((cell) => cell.style?.cellType))
327
+ throw new Error('不能拆分包含字段组件或重复标记的区域');
328
+ sheet.spans.splice(index, 1);
329
+ }
330
+ for (const span of item.merge || []) {
331
+ if (span.row + span.rowCount > sheet.rows.length || span.col + span.colCount > sheet.columns.length)
332
+ throw new Error(`${scope} 合并范围超出网格`);
333
+ if (sheet.spans.some((existing) => overlap(existing, span)))
334
+ throw new Error(`${scope} 合并范围与已有合并区域重叠`);
335
+ const covered = cellsIn(span);
336
+ if (covered.some((cell) => cell.style?.cellType ||
337
+ ((Number(cell.row) !== span.row || Number(cell.col) !== span.col) && cell.value !== undefined && cell.value !== null && cell.value !== '')))
338
+ throw new Error('合并范围包含字段组件、重复标记或非左上角文字');
339
+ sheet.spans.push({ row: span.row, rowCount: span.rowCount, col: span.col, colCount: span.colCount });
340
+ }
341
+ for (const change of item.cells || []) {
342
+ if (!sheet.rows[change.row] || !sheet.columns[change.col])
343
+ throw new Error(`${scope} 单元格 ${change.row},${change.col} 超出网格`);
344
+ let cell = sheet.cells.find((c) => Number(c.row) === change.row && Number(c.col) === change.col);
345
+ if (!cell) {
346
+ cell = { row: String(change.row), col: String(change.col), style: {} };
347
+ sheet.cells.push(cell);
348
+ }
349
+ const style = cell.style ??= {};
350
+ if (change.text !== undefined || change.systemParameter !== undefined) {
351
+ if (style.cellType)
352
+ throw new Error('不能将静态文字或系统参数写入字段组件单元格');
353
+ if (sheet.spans.some((span) => change.row >= span.row && change.row < span.row + span.rowCount &&
354
+ change.col >= span.col && change.col < span.col + span.colCount && (change.row !== span.row || change.col !== span.col)))
355
+ throw new Error('合并区域中的文字只能写在左上角单元格');
356
+ cell.value = change.systemParameter === 'formTitle' ? '#[formTitle]#' : change.text;
357
+ }
358
+ if (change.backgroundColor !== undefined)
359
+ style.backColor = change.backgroundColor;
360
+ if (change.textColor !== undefined)
361
+ style.foreColor = change.textColor;
362
+ if (change.font !== undefined)
363
+ style.font = change.font;
364
+ if (['fontFamily', 'fontSizePx', 'bold', 'italic'].some(key => change[key] !== undefined)) {
365
+ const old = /^(italic )?(?:([1-9]00) )?(\d{1,3})px (.+)$/u.exec(style.font || '12px Arial');
366
+ if (!old)
367
+ throw new Error('原字体格式未知,需同时指定完整 font');
368
+ const italic = change.italic ?? Boolean(old[1]);
369
+ const weight = change.bold === undefined ? old[2] : change.bold ? '700' : undefined;
370
+ const size = change.fontSizePx ?? Number(old[3]);
371
+ const family = change.fontFamily ?? old[4];
372
+ style.font = `${italic ? 'italic ' : ''}${weight ? `${weight} ` : ''}${size}px ${family}`;
373
+ }
374
+ if (change.underline !== undefined)
375
+ style.textDecoration = change.underline ? 1 : 0;
376
+ if (change.horizontalAlign !== undefined)
377
+ style.hAlign = { center: 1, right: 2, left: 3 }[change.horizontalAlign];
378
+ if (change.verticalAlign !== undefined)
379
+ style.vAlign = { top: 0, middle: 1, bottom: 2 }[change.verticalAlign];
380
+ if (change.wordWrap !== undefined)
381
+ style.wordWrap = change.wordWrap;
382
+ if (change.textIndent !== undefined)
383
+ style.textIndent = change.textIndent;
384
+ const lineCodes = { thin: 1, medium: 2, thick: 5, dashed: 3, dotted: 4, double: 6 };
385
+ for (const [side, border] of Object.entries(change.borders || {})) {
386
+ const key = `border${side[0].toUpperCase()}${side.slice(1)}`;
387
+ if (border.line === 'none')
388
+ delete style[key];
389
+ else
390
+ style[key] = { color: border.color, style: lineCodes[border.line] };
391
+ }
392
+ }
393
+ }
394
+ return layoutInfo;
395
+ }
396
+ export function validateAdvancedLayoutStylePatch(value) {
397
+ const style = object(value, 'layoutStyle');
398
+ keys(style, ['page', 'field', 'detailTable', 'detailFields', 'group'], 'layoutStyle');
399
+ if (style.page !== undefined) {
400
+ const page = object(style.page, 'page');
401
+ keys(page, ['backgroundColor', 'showReadOnlyPlaceholder', 'showReadOnlyBackground', 'readOnlyType', 'tabStyleConfigId', 'outer', 'form', 'spacing'], 'page');
402
+ if (page.backgroundColor !== undefined)
403
+ cssColor(page.backgroundColor, 'page.backgroundColor');
404
+ for (const key of ['showReadOnlyPlaceholder', 'showReadOnlyBackground'])
405
+ if (page[key] !== undefined && typeof page[key] !== 'boolean')
406
+ throw new Error(`page.${key} 必须是布尔值`);
407
+ if (page.readOnlyType !== undefined && page.readOnlyType !== 'disabled')
408
+ throw new Error('page.readOnlyType 目前只支持 disabled');
409
+ if (page.tabStyleConfigId !== undefined && (typeof page.tabStyleConfigId !== 'string' || !/^\d+$/.test(page.tabStyleConfigId)))
410
+ throw new Error('page.tabStyleConfigId 必须是样式 ID');
411
+ if (page.outer !== undefined)
412
+ validatePageRegion(page.outer, 'page.outer');
413
+ if (page.form !== undefined)
414
+ validatePageRegion(page.form, 'page.form');
415
+ if (page.spacing !== undefined)
416
+ spacing(page.spacing, ['marginTopPx', 'marginRightPx', 'marginBottomPx', 'marginLeftPx', 'paddingTopPx', 'paddingRightPx', 'paddingBottomPx', 'paddingLeftPx'], 'page.spacing');
417
+ }
418
+ if (style.field !== undefined) {
419
+ const field = object(style.field, 'field');
420
+ keys(field, ['contentRightMarginPx', 'spacingBottomPx', 'content', 'input', 'browser'], 'field');
421
+ if (field.contentRightMarginPx !== undefined)
422
+ pixel(field.contentRightMarginPx, 'field.contentRightMarginPx');
423
+ if (field.spacingBottomPx !== undefined)
424
+ pixel(field.spacingBottomPx, 'field.spacingBottomPx');
425
+ if (field.content !== undefined) {
426
+ const item = object(field.content, 'field.content');
427
+ keys(item, ['border', 'radiusPx', 'paddingLeftPx', 'paddingRightPx', 'paddingTopPx', 'paddingBottomPx'], 'field.content');
428
+ if (item.border !== undefined)
429
+ solidBorder(item.border, 'field.content.border');
430
+ for (const [key, val] of Object.entries(item))
431
+ if (key !== 'border')
432
+ pixel(val, `field.content.${key}`);
433
+ }
434
+ if (field.input !== undefined)
435
+ validateInputStyle(field.input, 'field.input', 'width');
436
+ if (field.browser !== undefined)
437
+ validateInputStyle(field.browser, 'field.browser', 'minWidth');
438
+ }
439
+ if (style.detailTable !== undefined)
440
+ spacing(style.detailTable, ['paddingTopPx', 'paddingBottomPx', 'paddingLeftPx', 'paddingRightPx'], 'detailTable');
441
+ if (style.detailFields !== undefined) {
442
+ const detail = object(style.detailFields, 'detailFields');
443
+ keys(detail, ['contentRightMarginPx', 'spacingRightPx', 'spacingBottomPx', 'content', 'input', 'browser'], 'detailFields');
444
+ for (const key of ['contentRightMarginPx', 'spacingRightPx', 'spacingBottomPx'])
445
+ if (detail[key] !== undefined)
446
+ pixel(detail[key], `detailFields.${key}`);
447
+ for (const kind of ['input', 'browser'])
448
+ if (detail[kind] !== undefined)
449
+ validateInputStyle(detail[kind], `detailFields.${kind}`);
450
+ if (detail.content !== undefined) {
451
+ const content = object(detail.content, 'detailFields.content');
452
+ keys(content, ['paddingRightPx', 'border'], 'detailFields.content');
453
+ if (content.paddingRightPx !== undefined)
454
+ pixel(content.paddingRightPx, 'detailFields.content.paddingRightPx');
455
+ if (content.border !== undefined)
456
+ noBorder(content.border, 'detailFields.content.border');
457
+ }
458
+ }
459
+ if (style.group !== undefined) {
460
+ const group = object(style.group, 'group');
461
+ keys(group, ['title', 'icon', 'background', 'content', 'spacing'], 'group');
462
+ if (group.title !== undefined) {
463
+ const title = object(group.title, 'group.title');
464
+ keys(title, ['fontWeight', 'backgroundColor', 'leftBorder', 'bottomBorder', 'paddingTopPx', 'paddingBottomPx', 'paddingLeftPx', 'marginBottomPx'], 'group.title');
465
+ for (const [key, val] of Object.entries(title)) {
466
+ if (key === 'fontWeight') {
467
+ if (!Number.isInteger(val) || val < 100 || val > 900 || val % 100)
468
+ throw new Error('group.title.fontWeight 必须是 100–900 的百位整数');
469
+ }
470
+ else if (key === 'backgroundColor')
471
+ cssColor(val, `group.title.${key}`);
472
+ else if (key === 'leftBorder' || key === 'bottomBorder')
473
+ solidBorder(val, `group.title.${key}`);
474
+ else
475
+ pixel(val, `group.title.${key}`);
476
+ }
477
+ }
478
+ if (group.icon !== undefined) {
479
+ const icon = object(group.icon, 'group.icon');
480
+ keys(icon, ['path', 'fontSizePx', 'color', 'radiusPx', 'fillType', 'borderColor', 'backgroundColor', 'displayType'], 'group.icon');
481
+ if (typeof icon.path !== 'string' || !/^#Icon-[\w-]+$/.test(icon.path))
482
+ throw new Error('group.icon.path 格式无效');
483
+ for (const key of ['fontSizePx', 'radiusPx'])
484
+ if (icon[key] !== undefined)
485
+ pixel(icon[key], `group.icon.${key}`);
486
+ for (const key of ['color', 'borderColor', 'backgroundColor'])
487
+ if (icon[key] !== undefined)
488
+ cssColor(icon[key], `group.icon.${key}`);
489
+ if (icon.fillType !== undefined && icon.fillType !== 'none')
490
+ throw new Error('group.icon.fillType 目前只支持 none');
491
+ if (icon.displayType !== undefined && icon.displayType !== 'suffer')
492
+ throw new Error('group.icon.displayType 目前只支持 suffer');
493
+ }
494
+ if (group.background !== undefined) {
495
+ const bg = object(group.background, 'group.background');
496
+ keys(bg, ['color', 'radiusPx'], 'group.background');
497
+ if (bg.color !== undefined)
498
+ cssColor(bg.color, 'group.background.color');
499
+ if (bg.radiusPx !== undefined)
500
+ pixel(bg.radiusPx, 'group.background.radiusPx');
501
+ }
502
+ if (group.content !== undefined)
503
+ spacing(group.content, ['marginLeftPx', 'marginRightPx', 'paddingTopPx', 'paddingBottomPx'], 'group.content', true);
504
+ if (group.spacing !== undefined)
505
+ spacing(group.spacing, ['marginTopPx', 'marginBottomPx', 'paddingTopPx', 'paddingLeftPx', 'paddingRightPx'], 'group.spacing', true);
506
+ }
507
+ return style;
508
+ }
509
+ const set = (root, path, value) => {
510
+ let node = root;
511
+ for (const key of path.slice(0, -1))
512
+ node = node[key] ??= {};
513
+ node[path.at(-1)] = value;
514
+ };
515
+ const spacingNames = { marginLeftPx: 'margin-left', marginRightPx: 'margin-right',
516
+ marginTopPx: 'margin-top', marginBottomPx: 'margin-bottom', paddingLeftPx: 'padding-left',
517
+ paddingRightPx: 'padding-right', paddingTopPx: 'padding-top', paddingBottomPx: 'padding-bottom' };
518
+ function applySpacing(style, path, values) {
519
+ if (!values)
520
+ return;
521
+ for (const [key, value] of Object.entries(values))
522
+ set(style, [...path, 'spacing', spacingNames[key]], `${value}px`);
523
+ }
524
+ function applyNoBorder(style, path) {
525
+ for (const key of ['border', 'borderTop', 'borderBottom', 'borderLeft', 'borderRight'])
526
+ set(style, [...path, 'border', key], '0px none transparent');
527
+ }
528
+ function applySolidBorder(style, path, border) {
529
+ for (const key of ['border', 'borderTop', 'borderBottom', 'borderLeft', 'borderRight'])
530
+ set(style, [...path, 'border', key], `${border.widthPx}px solid ${border.color}`);
531
+ }
532
+ function applyInputStyle(style, path, item) {
533
+ if (!item)
534
+ return;
535
+ if (item.width !== undefined)
536
+ set(style, [...path, 'width'], item.width);
537
+ if (item.minWidth !== undefined)
538
+ set(style, [...path, 'min-width'], item.minWidth);
539
+ if (item.backgroundColor !== undefined)
540
+ set(style, [...path, 'background-color'], item.backgroundColor);
541
+ if (item.border === 'none')
542
+ applyNoBorder(style, path);
543
+ }
544
+ function applyPageRegion(style, path, region) {
545
+ if (!region)
546
+ return;
547
+ if (region.backgroundColor !== undefined)
548
+ set(style, [...path, 'background-color'], region.backgroundColor);
549
+ const borderKeys = { all: 'border', top: 'borderTop', right: 'borderRight', bottom: 'borderBottom', left: 'borderLeft' };
550
+ for (const [side, border] of Object.entries(region.borders || {}))
551
+ set(style, [...path, 'border', borderKeys[side]], `${border.widthPx}px ${border.line} ${border.color}`);
552
+ if (region.radiusPx !== undefined)
553
+ set(style, [...path, 'border-radius', 'borderRadius'], `${region.radiusPx}px`);
554
+ const cornerKeys = { topLeftPx: 'borderTopLeftRadius', topRightPx: 'borderTopRightRadius',
555
+ bottomLeftPx: 'borderBottomLeftRadius', bottomRightPx: 'borderBottomRightRadius' };
556
+ for (const [key, value] of Object.entries(region.cornerRadius || {}))
557
+ set(style, [...path, 'border-radius', cornerKeys[key]], `${value}px`);
558
+ }
559
+ export function applyAdvancedLayoutStyle(layoutInfo, value) {
560
+ const patch = validateAdvancedLayoutStylePatch(value);
561
+ const existing = layoutInfo.formStyle?.styleConfig;
562
+ const style = existing ? JSON.parse(existing) : {};
563
+ if (!plain(style))
564
+ throw new Error('模板布局样式格式未知');
565
+ const page = patch.page;
566
+ if (page?.backgroundColor !== undefined)
567
+ set(style, ['formLayout', 'background', 'background-color'], page.backgroundColor);
568
+ if (page?.showReadOnlyPlaceholder !== undefined)
569
+ set(style, ['formLayout', 'typeSetting', 'showReadOnlyPlaceholder'], page.showReadOnlyPlaceholder);
570
+ if (page?.showReadOnlyBackground !== undefined)
571
+ set(style, ['formLayout', 'typeSetting', 'showReadOnlyBackground'], page.showReadOnlyBackground);
572
+ if (page?.readOnlyType !== undefined)
573
+ set(style, ['formLayout', 'typeSetting', 'readOnlyType'], page.readOnlyType);
574
+ if (page?.tabStyleConfigId !== undefined)
575
+ set(style, ['formLayout', 'typeSetting', 'tabStyleConfigId'], page.tabStyleConfigId);
576
+ applyPageRegion(style, ['formLayout', 'background'], page?.outer);
577
+ applyPageRegion(style, ['formLayout', 'formBackground'], page?.form);
578
+ applySpacing(style, ['formLayout', 'layoutSpacing'], page?.spacing);
579
+ const field = patch.field;
580
+ if (field?.contentRightMarginPx !== undefined)
581
+ set(style, ['field', 'content', 'spacing', 'margin-right'], `${field.contentRightMarginPx}px`);
582
+ if (field?.spacingBottomPx !== undefined)
583
+ set(style, ['field', 'fieldSpacing', 'spacing', 'margin-bottom'], `${field.spacingBottomPx}px`);
584
+ if (field?.content?.border)
585
+ applySolidBorder(style, ['field', 'content'], field.content.border);
586
+ if (field?.content?.radiusPx !== undefined)
587
+ set(style, ['field', 'content', 'border-radius', 'borderRadius'], `${field.content.radiusPx}px`);
588
+ for (const key of ['paddingLeftPx', 'paddingRightPx', 'paddingTopPx', 'paddingBottomPx'])
589
+ if (field?.content?.[key] !== undefined)
590
+ set(style, ['field', 'content', 'spacing', spacingNames[key]], `${field.content[key]}px`);
591
+ applyInputStyle(style, ['field', 'inputStyle'], field?.input);
592
+ applyInputStyle(style, ['field', 'browserStyle'], field?.browser);
593
+ applySpacing(style, ['layout', 'detailTable', 'fieldSpacing'], patch.detailTable);
594
+ if (patch.detailFields?.contentRightMarginPx !== undefined)
595
+ set(style, ['layout', 'detailTableFields', 'content', 'spacing', 'margin-right'], `${patch.detailFields.contentRightMarginPx}px`);
596
+ if (patch.detailFields?.spacingRightPx !== undefined)
597
+ set(style, ['layout', 'detailTableFields', 'fieldSpacing', 'spacing', 'margin-right'], `${patch.detailFields.spacingRightPx}px`);
598
+ if (patch.detailFields?.spacingBottomPx !== undefined)
599
+ set(style, ['layout', 'detailTableFields', 'fieldSpacing', 'spacing', 'margin-bottom'], `${patch.detailFields.spacingBottomPx}px`);
600
+ if (patch.detailFields?.content?.paddingRightPx !== undefined)
601
+ set(style, ['layout', 'detailTableFields', 'content', 'spacing', 'padding-right'], `${patch.detailFields.content.paddingRightPx}px`);
602
+ if (patch.detailFields?.content?.border === 'none')
603
+ applyNoBorder(style, ['layout', 'detailTableFields', 'content']);
604
+ applyInputStyle(style, ['layout', 'detailTableFields', 'inputStyle'], patch.detailFields?.input);
605
+ applyInputStyle(style, ['layout', 'detailTableFields', 'browserStyle'], patch.detailFields?.browser);
606
+ const title = patch.group?.title;
607
+ if (title?.fontWeight !== undefined)
608
+ set(style, ['layout', 'collapse', 'title', 'font-weight'], String(title.fontWeight));
609
+ if (title?.backgroundColor !== undefined)
610
+ set(style, ['layout', 'collapse', 'title', 'background-color'], title.backgroundColor);
611
+ if (title?.leftBorder)
612
+ set(style, ['layout', 'collapse', 'title', 'border', 'borderLeft'], `${title.leftBorder.widthPx}px solid ${title.leftBorder.color}`);
613
+ if (title?.bottomBorder)
614
+ set(style, ['layout', 'collapse', 'title', 'border', 'borderBottom'], `${title.bottomBorder.widthPx}px solid ${title.bottomBorder.color}`);
615
+ for (const [key, native] of [['paddingTopPx', 'padding-top'], ['paddingBottomPx', 'padding-bottom'], ['paddingLeftPx', 'padding-left'], ['marginBottomPx', 'margin-bottom']]) {
616
+ if (title?.[key] !== undefined)
617
+ set(style, ['layout', 'collapse', 'title', 'spacing', native], `${title[key]}px`);
618
+ }
619
+ const icon = patch.group?.icon;
620
+ if (icon) {
621
+ const base = ['layout', 'collapse', 'groupIconSetting', 'groupIcon'];
622
+ set(style, [...base, 'path'], icon.path);
623
+ if (icon.fontSizePx !== undefined)
624
+ set(style, [...base, 'style', 'fontSize'], icon.fontSizePx);
625
+ if (icon.color !== undefined)
626
+ set(style, [...base, 'style', 'color'], icon.color);
627
+ if (icon.radiusPx !== undefined)
628
+ set(style, [...base, 'style', 'radius'], `${icon.radiusPx}px`);
629
+ if (icon.fillType !== undefined)
630
+ set(style, [...base, 'style', 'fillType'], icon.fillType);
631
+ if (icon.borderColor !== undefined)
632
+ set(style, [...base, 'style', 'borderColor'], icon.borderColor);
633
+ if (icon.backgroundColor !== undefined)
634
+ set(style, [...base, 'style', 'bgColor'], icon.backgroundColor);
635
+ if (icon.displayType !== undefined)
636
+ set(style, ['layout', 'collapse', 'groupIconSetting', 'iconDisplayType'], icon.displayType);
637
+ }
638
+ if (patch.group?.background?.color !== undefined)
639
+ set(style, ['layout', 'collapse', 'background', 'background-color'], patch.group.background.color);
640
+ if (patch.group?.background?.radiusPx !== undefined)
641
+ set(style, ['layout', 'collapse', 'background', 'border-radius', 'borderRadius'], `${patch.group.background.radiusPx}px`);
642
+ applySpacing(style, ['layout', 'collapse', 'content'], patch.group?.content);
643
+ applySpacing(style, ['layout', 'collapse', 'fieldSpacing'], patch.group?.spacing);
644
+ layoutInfo.formStyle ??= {};
645
+ layoutInfo.formStyle.styleConfig = JSON.stringify(style);
646
+ return layoutInfo;
647
+ }
648
+ function result(response, action) {
649
+ if (response?.code !== 200 || response?.status !== true)
650
+ throw new Error(`${action}失败:${response?.msg || '接口返回异常'}`);
651
+ return response.data;
652
+ }
653
+ function size(value, label, fallback) {
654
+ const actual = value === undefined ? fallback : Number(value);
655
+ if (!Number.isInteger(actual) || actual < 1 || actual > 2000)
656
+ throw new Error(`${label} 必须是 1–2000 的整数`);
657
+ return actual;
658
+ }
659
+ export function validateMinimalAdvancedDesign(value) {
660
+ if (!value || typeof value !== 'object' || Array.isArray(value))
661
+ throw new Error('高级模板设计必须是 JSON 对象');
662
+ const input = value;
663
+ const allowed = new Set(['templateName', 'fieldPairsPerRow', 'fieldRows', 'fieldPlacements', 'headerTitle', 'headerTitlePlacement', 'sectionTitle', 'group', 'detailTable', 'columnWidthsPercent',
664
+ 'rowHeight', 'titleWidthPx', 'fieldWidthPx', 'coverFileId', 'layoutStyle', 'grid', 'cssSource']);
665
+ for (const key of Object.keys(input))
666
+ if (!allowed.has(key))
667
+ throw new Error(`最小高级模板暂不支持 ${key}`);
668
+ const templateName = String(input.templateName ?? '').trim();
669
+ if (!templateName || templateName.length > 150)
670
+ throw new Error('templateName 必填且不能超过 150 个字符');
671
+ const fieldPairsPerRow = input.fieldPairsPerRow === undefined ? 1 : input.fieldPairsPerRow;
672
+ if (fieldPairsPerRow !== 1 && fieldPairsPerRow !== 2)
673
+ throw new Error('fieldPairsPerRow 只能是 1 或 2');
674
+ if (input.fieldPlacements !== undefined) {
675
+ if (input.fieldRows !== undefined || input.fieldPairsPerRow !== undefined || !Array.isArray(input.fieldPlacements) ||
676
+ input.fieldPlacements.length < 1 || input.fieldPlacements.length > 4)
677
+ throw new Error('fieldPlacements 需要 1–4 组,并与 fieldRows、fieldPairsPerRow 三选一');
678
+ const pairs = input.fieldPlacements.map(item => object(item, 'fieldPlacements 项').pair);
679
+ if (new Set(pairs).size !== pairs.length || pairs.some((_, index) => !pairs.includes(index + 1)))
680
+ throw new Error('fieldPlacements 须使用从 1 开始的标题字段对,且不能重复或跳号');
681
+ for (const item of input.fieldPlacements) {
682
+ const entry = object(item, 'fieldPlacements 项');
683
+ keys(entry, ['pair', 'title', 'field'], 'fieldPlacements 项');
684
+ for (const key of ['title', 'field']) {
685
+ const p = object(entry[key], `fieldPlacements.${key}`);
686
+ keys(p, ['row', 'col', 'colSpan'], `fieldPlacements.${key}`);
687
+ if (!Number.isInteger(p.row) || p.row < 0 || p.row > 19 ||
688
+ !Number.isInteger(p.col) || p.col < 0 || p.col > 19 ||
689
+ (p.colSpan !== undefined && (!Number.isInteger(p.colSpan) || p.colSpan < 1 || p.colSpan > 20)))
690
+ throw new Error('fieldPlacements 行列及跨列须为有效正整数');
691
+ }
692
+ }
693
+ const occupied = new Set();
694
+ for (const item of input.fieldPlacements)
695
+ for (const p of [item.title, item.field]) {
696
+ if (!p)
697
+ throw new Error('fieldPlacements 缺少 title 或 field 位置');
698
+ for (let col = p.col; col < p.col + (p.colSpan || 1); col++) {
699
+ const key = `${p.row},${col}`;
700
+ if (occupied.has(key))
701
+ throw new Error(`fieldPlacements 单元格或跨列范围重叠:${key}`);
702
+ occupied.add(key);
703
+ }
704
+ }
705
+ }
706
+ if (input.fieldRows !== undefined) {
707
+ if (input.fieldPairsPerRow !== undefined || input.fieldPlacements !== undefined || !Array.isArray(input.fieldRows) || !input.fieldRows.length ||
708
+ input.fieldRows.length > 4 || input.fieldRows.some(row => !Array.isArray(row) || row.length < 1 || row.length > 4 ||
709
+ row.some(pair => !Number.isInteger(pair) || pair < 1 || pair > 4)))
710
+ throw new Error('fieldRows 需要 1–4 行,每行 1–4 个字段对,且不能与 fieldPairsPerRow 同时使用');
711
+ const pairs = input.fieldRows.flat();
712
+ if (new Set(pairs).size !== pairs.length || pairs.some((pair, index) => !pairs.includes(index + 1)))
713
+ throw new Error('fieldRows 须使用从 1 开始的标题字段对,且不能重复或跳号');
714
+ }
715
+ const maxSlots = input.fieldRows ? Math.max(...input.fieldRows.map(row => row.length)) : fieldPairsPerRow;
716
+ const explicit = input.fieldPlacements;
717
+ if (input.headerTitle !== undefined && input.headerTitle !== 'formTitle')
718
+ throw new Error('headerTitle 目前只支持 formTitle');
719
+ if (input.headerTitlePlacement !== undefined && !['full', 'content'].includes(input.headerTitlePlacement))
720
+ throw new Error('headerTitlePlacement 只能是 full 或 content');
721
+ if (input.headerTitlePlacement !== undefined && input.headerTitle === undefined)
722
+ throw new Error('headerTitlePlacement 需要同时设置 headerTitle');
723
+ if (input.sectionTitle !== undefined && (typeof input.sectionTitle !== 'string' || !input.sectionTitle.trim() || input.sectionTitle.length > 100))
724
+ throw new Error('sectionTitle 必须是 1–100 字的文字');
725
+ if (input.sectionTitle !== undefined && maxSlots < 2 && !explicit)
726
+ throw new Error('sectionTitle 目前只用于有两列以上字段的布局');
727
+ if (input.group !== undefined) {
728
+ const group = object(input.group, 'group');
729
+ keys(group, ['enabled'], 'group');
730
+ if (group.enabled !== true)
731
+ throw new Error('group.enabled 只能为 true;不需要分组时省略 group');
732
+ }
733
+ if (input.detailTable !== undefined) {
734
+ const detail = object(input.detailTable, 'detailTable');
735
+ keys(detail, ['enabled', 'title', 'rowHeightPx', 'columnWidthsPx', 'fieldRepeatDirection',
736
+ 'fieldTitlePosition', 'fieldRepeatPosition', 'controlPositions'], 'detailTable');
737
+ if (detail.enabled !== true)
738
+ throw new Error('detailTable.enabled 只能为 true;不需要明细表时省略 detailTable');
739
+ if (detail.title !== undefined && (typeof detail.title !== 'string' || !detail.title.trim() || detail.title.length > 100))
740
+ throw new Error('detailTable.title 必须是 1–100 字的文字');
741
+ if (detail.rowHeightPx !== undefined)
742
+ size(detail.rowHeightPx, 'detailTable.rowHeightPx', 30);
743
+ if (detail.columnWidthsPx !== undefined && (!Array.isArray(detail.columnWidthsPx) || detail.columnWidthsPx.length < 3 || detail.columnWidthsPx.length > 20))
744
+ throw new Error('detailTable.columnWidthsPx 必须提供 3–20 个 px 宽度');
745
+ for (const width of detail.columnWidthsPx || [])
746
+ size(width, 'detailTable.columnWidthsPx', 180);
747
+ if (detail.fieldRepeatDirection !== undefined && !['horizontal', 'vertical'].includes(String(detail.fieldRepeatDirection)))
748
+ throw new Error('detailTable.fieldRepeatDirection 只能是 horizontal 或 vertical');
749
+ const point = (value, label) => {
750
+ const p = object(value, label);
751
+ keys(p, ['row', 'col'], label);
752
+ if (!Number.isInteger(p.row) || !Number.isInteger(p.col) || p.row < 0 || p.col < 0 ||
753
+ p.row > 39 || p.col > 19)
754
+ throw new Error(`${label} 行列须为有效的非负整数`);
755
+ };
756
+ if (detail.fieldTitlePosition !== undefined)
757
+ point(detail.fieldTitlePosition, 'fieldTitlePosition');
758
+ if (detail.fieldRepeatPosition !== undefined)
759
+ point(detail.fieldRepeatPosition, 'fieldRepeatPosition');
760
+ if (detail.controlPositions !== undefined) {
761
+ const controls = object(detail.controlPositions, 'controlPositions');
762
+ keys(controls, ['de_title', 'de_tail', 'de_titletext', 'de_btn', 'de_checkall',
763
+ 'de_checksingle', 'de_serialtitle', 'de_serialnum'], 'controlPositions');
764
+ for (const [name, value] of Object.entries(controls))
765
+ point(value, `controlPositions.${name}`);
766
+ }
767
+ }
768
+ if (input.columnWidthsPercent !== undefined) {
769
+ const expectedColumns = explicit ? input.columnWidthsPercent instanceof Array ? input.columnWidthsPercent.length : 0 : maxSlots * 2 + 2;
770
+ if ((maxSlots < 2 && !input.fieldRows && !explicit) || !Array.isArray(input.columnWidthsPercent) ||
771
+ input.columnWidthsPercent.length !== expectedColumns ||
772
+ input.columnWidthsPercent.some(v => !Number.isInteger(v) || v <= 0 || v > 100) ||
773
+ input.columnWidthsPercent.reduce((n, v) => n + v, 0) !== 100)
774
+ throw new Error(`columnWidthsPercent 需提供 ${expectedColumns} 个正整数,合计 100`);
775
+ }
776
+ if (explicit && (!Array.isArray(input.columnWidthsPercent) || input.columnWidthsPercent.length < 3 ||
777
+ input.columnWidthsPercent.length > 20 || explicit.some(item => [item.title, item.field].some(p => p.col + (p.colSpan || 1) > input.columnWidthsPercent.length))))
778
+ throw new Error('fieldPlacements 必须配合足够列数的 columnWidthsPercent');
779
+ if (input.coverFileId !== undefined && !/^\d+$/.test(String(input.coverFileId)))
780
+ throw new Error('coverFileId 必须是上传文件返回的 ID');
781
+ return { templateName,
782
+ ...(input.fieldRows === undefined && input.fieldPlacements === undefined ? { fieldPairsPerRow } : {}),
783
+ ...(input.fieldRows !== undefined ? { fieldRows: input.fieldRows } : {}),
784
+ ...(input.fieldPlacements !== undefined ? { fieldPlacements: input.fieldPlacements } : {}),
785
+ ...(input.headerTitle !== undefined ? { headerTitle: 'formTitle' } : {}),
786
+ ...(input.headerTitlePlacement !== undefined ? { headerTitlePlacement: input.headerTitlePlacement } : {}),
787
+ ...(input.sectionTitle !== undefined ? { sectionTitle: input.sectionTitle.trim() } : {}),
788
+ ...(input.group !== undefined ? { group: { enabled: true } } : {}),
789
+ ...(input.detailTable !== undefined ? { detailTable: { enabled: true,
790
+ ...(input.detailTable.title !== undefined ? { title: input.detailTable.title.trim() } : {}),
791
+ ...(input.detailTable.rowHeightPx !== undefined ? { rowHeightPx: input.detailTable.rowHeightPx } : {}),
792
+ ...(input.detailTable.columnWidthsPx !== undefined ? { columnWidthsPx: input.detailTable.columnWidthsPx } : {}),
793
+ ...(input.detailTable.fieldRepeatDirection !== undefined ? { fieldRepeatDirection: input.detailTable.fieldRepeatDirection } : {}),
794
+ ...(input.detailTable.fieldTitlePosition !== undefined ? { fieldTitlePosition: input.detailTable.fieldTitlePosition } : {}),
795
+ ...(input.detailTable.fieldRepeatPosition !== undefined ? { fieldRepeatPosition: input.detailTable.fieldRepeatPosition } : {}),
796
+ ...(input.detailTable.controlPositions !== undefined ? { controlPositions: input.detailTable.controlPositions } : {}) } } : {}),
797
+ ...(input.columnWidthsPercent !== undefined ? { columnWidthsPercent: input.columnWidthsPercent } : {}),
798
+ rowHeight: size(input.rowHeight, 'rowHeight', 30),
799
+ titleWidthPx: size(input.titleWidthPx, 'titleWidthPx', 180),
800
+ fieldWidthPx: size(input.fieldWidthPx, 'fieldWidthPx', 180),
801
+ ...(input.coverFileId !== undefined ? { coverFileId: String(input.coverFileId) } : {}),
802
+ ...(input.cssSource !== undefined ? { cssSource: input.cssSource } : {}),
803
+ ...(input.layoutStyle !== undefined ? { layoutStyle: validateAdvancedLayoutStylePatch(input.layoutStyle) } : {}),
804
+ ...(input.grid !== undefined ? { grid: validateAdvancedGridPatch(input.grid) } : {}) };
805
+ }
806
+ const hex = () => randomBytes(16).toString('hex');
807
+ const nodeId = () => randomBytes(9).toString('base64url');
808
+ /** 只取设计器提供的主表占位字段身份,不继承任何现成模板的样式。 */
809
+ export function buildMinimalAdvancedLayout(scaffold, input) {
810
+ const design = validateMinimalAdvancedDesign(input);
811
+ const page = scaffold?.layoutInfo?.pageLayouts?.[0];
812
+ const palette = scaffold?.fields?.flatMap((group) => group.comps || []) || [];
813
+ const field = palette.find((comp) => comp.tcomId === 'main_field1' && comp.type === 'FakeField');
814
+ const fieldRows = design.fieldRows || [Array.from({ length: design.fieldPairsPerRow || 1 }, (_, i) => (i + 1))];
815
+ const pairCount = design.fieldPlacements?.length || fieldRows.flat().length;
816
+ const maxSlots = Math.max(...fieldRows.map(row => row.length));
817
+ for (let i = 2; i <= pairCount; i++)
818
+ if (!palette.some((comp) => comp.tcomId === `main_field${i}` &&
819
+ comp.type === 'FakeField' && comp.associatedId === `main_field${i}`))
820
+ throw new Error(`平台未提供第 ${i} 组主表字段占位符`);
821
+ const groupField = palette.find((comp) => comp.tcomId === 'group_name' && comp.type === 'FakeField');
822
+ const detailField = palette.find((comp) => comp.tcomId === 'main_detailarea' && comp.type === 'DetailTable');
823
+ if (page?.layoutType !== 'EXCEL' || page?.terminalType !== 'PC' || !field || field.associatedId !== 'main_field1') {
824
+ throw new Error('平台未提供预期的 PC 表格式新建设计器底稿');
825
+ }
826
+ if (design.group && !groupField)
827
+ throw new Error('平台未提供分组名称占位符');
828
+ if (design.detailTable && !detailField)
829
+ throw new Error('平台未提供明细表区域占位符');
830
+ const position = (row, col) => ({ row, col, parent: 'ROOT', nodeId: nodeId(), nodes: [], linkedNodes: {} });
831
+ const core = () => ({ refCompId: '', id: hex(), category: String(field.category || '1'),
832
+ package: String(field.package || '@weapp/formbuilder'), isCover: 0 });
833
+ const markComp = (id, name, row) => ({
834
+ ...core(), type: 'RepeatMark', applyStyle: '1',
835
+ config: { title: name, tcomId: id, type: 'RepeatMark', name,
836
+ layout: { hidden: false, wrapped: false }, excel: position(row, 0) },
837
+ });
838
+ const percentMode = maxSlots >= 2 || Boolean(design.fieldRows) || Boolean(design.fieldPlacements);
839
+ const lastCol = design.fieldPlacements ? design.columnWidthsPercent.length : percentMode ? maxSlots * 2 + 2 : 10;
840
+ const mainStartRow = design.group ? 4 : 2;
841
+ const fieldRow = mainStartRow + 1;
842
+ const endRow = design.fieldPlacements ? fieldRow + Math.max(...design.fieldPlacements.flatMap(item => [item.title.row, item.field.row])) + 1 : design.fieldRows ? fieldRow + fieldRows.length : mainStartRow + (maxSlots === 2 ? 2 : 3);
843
+ const groupStart = design.group ? markComp('group_start', '分组开始标识', 2) : undefined;
844
+ const groupName = design.group ? { ...core(), type: 'FakeField', applyStyle: '1',
845
+ config: { tcomId: 'group_name', comId: 'group_name', title: '分组名称', type: 'FakeField', name: '分组名称',
846
+ layout: { hidden: false, wrapped: false }, excel: position(3, 1) } } : undefined;
847
+ const groupEndRow = design.group ? endRow + 1 : undefined;
848
+ const groupEnd = design.group ? markComp('group_end', '分组结束标识', groupEndRow) : undefined;
849
+ const start = markComp('main_repeat_start', '主表字段重复开始标识', mainStartRow);
850
+ const fieldComps = [];
851
+ const placements = design.fieldPlacements || fieldRows.flatMap((row, rowOffset) => row.map((pair, slot) => ({ pair,
852
+ title: { row: rowOffset, col: percentMode ? slot * 2 + 1 : 0 },
853
+ field: { row: rowOffset, col: percentMode ? slot * 2 + 2 : 1 } })));
854
+ for (const placement of placements) {
855
+ const i = placement.pair;
856
+ const associatedId = `main_field${i}`;
857
+ fieldComps.push({ ...core(), type: 'InternalTitle',
858
+ config: { type: 'InternalTitle', name: `主表字段标题${i}`, title: `主表字段标题${i}`, associatedId,
859
+ layout: { hidden: false, wrapped: false }, excel: position(fieldRow + placement.title.row, placement.title.col) } });
860
+ fieldComps.push({ ...core(), type: 'FakeField', applyStyle: '1',
861
+ config: { tcomId: associatedId, title: `主表字段${i}`, type: 'FakeField', name: `主表字段${i}`,
862
+ associatedId, layout: { hidden: false, wrapped: false }, excel: position(fieldRow + placement.field.row, placement.field.col) } });
863
+ }
864
+ const end = markComp('main_repeat_end', '主表字段重复结束标识', endRow);
865
+ const detailStartRow = design.detailTable ? (groupEndRow ?? endRow) + 2 : undefined;
866
+ const detailRow = detailStartRow === undefined ? undefined : detailStartRow + 1;
867
+ const detailEndRow = detailRow === undefined ? undefined : detailRow + 1;
868
+ const detailStart = design.detailTable ? markComp('detail_repeat_start', '明细表重复开始标识', detailStartRow) : undefined;
869
+ const detailEnd = design.detailTable ? markComp('detail_repeat_end', '明细表重复结束标识', detailEndRow) : undefined;
870
+ const defaultControls = {
871
+ de_titletext: { row: 0, col: 0 }, de_btn: { row: 0, col: 2 },
872
+ de_checkall: { row: 1, col: 0 }, de_serialtitle: { row: 1, col: 1 },
873
+ de_title: { row: 2, col: 0 }, de_checksingle: { row: 3, col: 0 },
874
+ de_serialnum: { row: 3, col: 1 }, de_tail: { row: 4, col: 0 },
875
+ };
876
+ const controls = { ...defaultControls, ...(design.detailTable?.controlPositions || {}) };
877
+ const detailTitlePoint = design.detailTable?.fieldTitlePosition || { row: 1, col: 2 };
878
+ const detailRepeatPoint = design.detailTable?.fieldRepeatPosition || { row: 3, col: 2 };
879
+ const detailPoints = [...Object.entries(controls).map(([name, point]) => ({ name, ...point })),
880
+ { name: 'detail_field_title', ...detailTitlePoint }, { name: 'detail_field_repeat', ...detailRepeatPoint }];
881
+ if (design.detailTable && new Set(detailPoints.map(p => `${p.row},${p.col}`)).size !== detailPoints.length)
882
+ throw new Error('明细子网格的系统控件、字段标题和字段迭代不能占用同一单元格');
883
+ const detailColCount = design.detailTable?.columnWidthsPx?.length ||
884
+ Math.max(3, ...detailPoints.map(point => point.col + 1));
885
+ if (design.detailTable && detailPoints.some(point => point.col >= detailColCount))
886
+ throw new Error('明细子网格中的位置超出 columnWidthsPx 列数');
887
+ if (design.detailTable && (controls.de_title.row === controls.de_tail.row ||
888
+ detailPoints.some(point => !['de_title', 'de_tail'].includes(point.name) &&
889
+ [controls.de_title.row, controls.de_tail.row].includes(point.row))))
890
+ throw new Error('明细表头、表尾各占独立的一整行');
891
+ const detailComp = design.detailTable ? { ...core(), type: 'DetailTable', applyStyle: '1',
892
+ config: { defaultPageSize: '20', duplicatedField: [], showSerial: true, defaultRows: '0', allowAdd: true,
893
+ totalLine: [], type: 'DetailTable', title: design.detailTable.title || '明细表', required: false,
894
+ mergedFields: [], tcomId: 'main_detailarea', thArray: [233, 233], maxRows: '', isLayOut: true,
895
+ mobileScroll: true, allowEdit: true, comId: 'main_detailarea', layoutDetail: ['0', '1'],
896
+ componentKey: 'DataTable', isImport: false, allowDelete: true, display: true, enableCardEdit: true,
897
+ frozenColumn: '', enablePaging: false, layout: { hidden: false, wrapped: false },
898
+ excel: { ...position(detailRow, 1), excelTable: Object.entries(controls).map(([type, point]) => ({ ...point, type })) },
899
+ requiredRows: '1', pcScroll: true, name: '明细表区域', allowCopy: true, describe: '' } } : undefined;
900
+ const detailTitle = detailComp ? { ...core(), type: 'FakeField', applyStyle: '1',
901
+ config: { layout: { hidden: false, wrapped: false }, excel: { ...position(detailTitlePoint.row, detailTitlePoint.col), parent: detailComp.config.excel.nodeId },
902
+ tcomId: 'detail_field_title', name: '明细表字段标题', title: '明细表字段标题', type: 'FakeField' } } : undefined;
903
+ const detailRepeat = detailComp ? { ...core(), type: 'FakeField', applyStyle: '1',
904
+ config: { layout: { hidden: false, wrapped: false }, excel: { ...position(detailRepeatPoint.row, detailRepeatPoint.col), parent: detailComp.config.excel.nodeId },
905
+ tcomId: design.detailTable?.fieldRepeatDirection === 'vertical' ? 'detail_field_repeat_y' : 'detail_field_repeat_x',
906
+ t_Type: 'd_repeat', name: design.detailTable?.fieldRepeatDirection === 'vertical' ? '明细表字段纵向迭代' : '明细表字段横向迭代',
907
+ title: design.detailTable?.fieldRepeatDirection === 'vertical' ? '明细表字段纵向迭代' : '明细表字段横向迭代', type: 'FakeField' } } : undefined;
908
+ const cell = (row, col, comp) => ({ row: String(row), col: String(col),
909
+ style: { ...(row >= fieldRow && row < endRow && comp.type === 'FakeField' ? { font: '12px Arial' } : {}),
910
+ cellType: { allowOverflow: false, comId: comp.id, type: comp.type } } });
911
+ const sheet = { rows: Array.from({ length: Math.max(11, (detailEndRow ?? groupEndRow ?? endRow) + 1) }, () => ({ size: design.rowHeight, visible: true })),
912
+ columns: percentMode ? (design.columnWidthsPercent || (maxSlots === 1 ? [4, 18, 74, 4] :
913
+ maxSlots === 2 ? [4, 12, 34, 12, 34, 4] : maxSlots === 3 ? [2, 10, 21, 10, 21, 10, 21, 5] :
914
+ [2, 8, 15, 8, 15, 8, 15, 8, 15, 6])).map(size => ({ type: 'percent', size, visible: true }))
915
+ : Array.from({ length: 10 }, (_, col) => ({ type: 'fixed', size: col === 0 ? design.titleWidthPx : col === 1 ? design.fieldWidthPx : 180, visible: true })),
916
+ cells: [
917
+ ...(groupStart ? [cell(2, 0, groupStart), cell(3, 1, groupName)] : []),
918
+ cell(mainStartRow, 0, start), ...fieldComps.map(comp => cell(comp.config.excel.row, comp.config.excel.col, comp)), cell(endRow, 0, end),
919
+ ...(groupEnd ? [cell(groupEndRow, 0, groupEnd)] : []),
920
+ ...(detailStart ? [cell(detailStartRow, 0, detailStart), cell(detailRow, 1, detailComp), cell(detailEndRow, 0, detailEnd)] : [])
921
+ ],
922
+ rowCount: 39, columnCount: lastCol,
923
+ spans: [
924
+ ...(groupStart ? [{ row: 2, rowCount: 1, col: 0, colCount: lastCol }, { row: groupEndRow, rowCount: 1, col: 0, colCount: lastCol }] : []),
925
+ { row: mainStartRow, rowCount: 1, col: 0, colCount: lastCol }, { row: endRow, rowCount: 1, col: 0, colCount: lastCol },
926
+ ...(groupStart ? [{ row: 3, rowCount: 1, col: 1, colCount: lastCol - 2 }] : []),
927
+ ...placements.flatMap(placement => [placement.title, placement.field].filter(p => 'colSpan' in p &&
928
+ typeof p.colSpan === 'number' && p.colSpan > 1).map(p => ({ row: fieldRow + p.row,
929
+ rowCount: 1, col: p.col, colCount: p.colSpan }))),
930
+ ...(detailStart ? [{ row: detailStartRow, rowCount: 1, col: 0, colCount: lastCol },
931
+ { row: detailRow, rowCount: 1, col: 1, colCount: lastCol - 2 },
932
+ { row: detailEndRow, rowCount: 1, col: 0, colCount: lastCol }] : [])
933
+ ] };
934
+ if (design.headerTitle) {
935
+ sheet.rows[0].size = 72;
936
+ const titleCol = design.headerTitlePlacement === 'content' ? 1 : 0;
937
+ const titleColCount = design.headerTitlePlacement === 'content' ? lastCol - 2 : lastCol;
938
+ sheet.cells.unshift({ row: '0', col: String(titleCol), value: '#[formTitle]#',
939
+ style: { font: '700 20px Arial', hAlign: 1, vAlign: 1 } });
940
+ sheet.spans.push({ row: 0, rowCount: 1, col: titleCol, colCount: titleColCount });
941
+ }
942
+ if (design.sectionTitle) {
943
+ sheet.rows[1].size = 42;
944
+ sheet.cells.push({ row: '1', col: '1', value: design.sectionTitle, style: { font: '700 14px Arial' } });
945
+ sheet.spans.push({ row: 1, rowCount: 1, col: 1, colCount: lastCol - 2 });
946
+ }
947
+ const mark = (row, id, name, icon) => ({ row, col: 0, item: { id, name, icon } });
948
+ const workbook = { Sheet1: sheet };
949
+ if (detailComp && detailTitle && detailRepeat) {
950
+ const height = design.detailTable?.rowHeightPx || 30;
951
+ const detailRowCount = Math.max(5, ...detailPoints.map(point => point.row + 1));
952
+ workbook[detailComp.id] = { rows: Array.from({ length: detailRowCount }, () => ({ size: height, visible: true })),
953
+ columns: (design.detailTable?.columnWidthsPx || Array.from({ length: detailColCount }, () => 180)).map(width => ({ type: 'fixed', size: width, visible: true })),
954
+ cells: [
955
+ ...Object.entries(controls).map(([type, point]) => ({
956
+ row: String(point.row), col: String(point.col), value: null,
957
+ style: { cellType: { allowOverflow: false, type } }
958
+ })),
959
+ cell(detailTitlePoint.row, detailTitlePoint.col, detailTitle),
960
+ cell(detailRepeatPoint.row, detailRepeatPoint.col, detailRepeat)
961
+ ],
962
+ rowCount: Math.max(40, detailRowCount), columnCount: detailColCount,
963
+ spans: [...(controls.de_titletext.row === controls.de_btn.row && controls.de_btn.col > controls.de_titletext.col + 1 &&
964
+ !detailPoints.some(point => !['de_titletext', 'de_btn'].includes(point.name) &&
965
+ point.row === controls.de_titletext.row && point.col > controls.de_titletext.col && point.col < controls.de_btn.col) ?
966
+ [{ row: controls.de_titletext.row, rowCount: 1, col: controls.de_titletext.col,
967
+ colCount: controls.de_btn.col - controls.de_titletext.col }] : []),
968
+ { row: controls.de_title.row, rowCount: 1, col: controls.de_title.col, colCount: detailColCount - controls.de_title.col },
969
+ { row: controls.de_tail.row, rowCount: 1, col: controls.de_tail.col, colCount: detailColCount - controls.de_tail.col }] };
970
+ }
971
+ return { pageLayouts: [{ ...page, comps: [
972
+ ...(groupStart ? [groupStart, groupName] : []), start, ...fieldComps, end,
973
+ ...(groupEnd ? [groupEnd] : []), ...(detailStart ? [detailStart, detailComp, detailEnd, detailTitle, detailRepeat] : [])
974
+ ],
975
+ config: { workbook, ebCodeFileTypes: [], dataset: [] } }],
976
+ marks: {
977
+ ...(groupStart ? { group_start: mark(2, 'group_start', '分组开始标识', 'Icon-bd-groupstart'),
978
+ group_end: mark(groupEndRow, 'group_end', '分组结束标识', 'Icon-bd-groupend') } : {}),
979
+ main_repeat_start: mark(mainStartRow, 'main_repeat_start', '主表字段重复开始标识', 'Icon-bd-formstart'),
980
+ main_repeat_end: mark(endRow, 'main_repeat_end', '主表字段重复结束标识', 'Icon-bd-formend'),
981
+ ...(detailStart ? { detail_repeat_start: mark(detailStartRow, 'detail_repeat_start', '明细表重复开始标识', 'Icon-bd-liststart'),
982
+ detail_repeat_end: mark(detailEndRow, 'detail_repeat_end', '明细表重复结束标识', 'Icon-bd-listend') } : {}),
983
+ }, formStyle: { styleConfig: JSON.stringify({ formLayout: { typeSetting: { readOnlyType: 'disabled' } } }) } };
984
+ }
985
+ export function verifyMinimalAdvancedLayout(layout) {
986
+ const page = layout?.pageLayouts?.[0], sheet = page?.config?.workbook?.Sheet1;
987
+ if (page?.layoutType !== 'EXCEL' || page?.terminalType !== 'PC' || !sheet || !Array.isArray(page.comps))
988
+ return false;
989
+ const marks = layout.marks || {};
990
+ if (!marks.main_repeat_start || !marks.main_repeat_end)
991
+ return false;
992
+ const group = Boolean(marks.group_start || marks.group_end);
993
+ const detail = Boolean(marks.detail_repeat_start || marks.detail_repeat_end);
994
+ if (group && (!marks.group_start || !marks.group_end))
995
+ return false;
996
+ if (detail && (!marks.detail_repeat_start || !marks.detail_repeat_end))
997
+ return false;
998
+ if (Object.keys(marks).length !== 2 + (group ? 2 : 0) + (detail ? 2 : 0))
999
+ return false;
1000
+ const kinds = page.comps.map((c) => `${c.type}:${c.config?.tcomId || c.config?.associatedId || ''}`);
1001
+ const fieldComps = page.comps.filter((comp) => comp.type === 'InternalTitle' ||
1002
+ (comp.type === 'FakeField' && /^main_field[1-4]$/.test(comp.config?.tcomId || '')));
1003
+ const pairIds = fieldComps.filter((comp) => comp.type === 'InternalTitle').map((comp) => comp.config?.associatedId);
1004
+ if (!pairIds.length || pairIds.length > 4 || new Set(pairIds).size !== pairIds.length ||
1005
+ pairIds.some((_, index) => !pairIds.includes(`main_field${index + 1}`)))
1006
+ return false;
1007
+ const expected = [...(group ? ['RepeatMark:group_start', 'FakeField:group_name'] : []), 'RepeatMark:main_repeat_start'];
1008
+ for (const id of pairIds)
1009
+ expected.push(`InternalTitle:${id}`, `FakeField:${id}`);
1010
+ expected.push('RepeatMark:main_repeat_end');
1011
+ if (group)
1012
+ expected.push('RepeatMark:group_end');
1013
+ if (detail)
1014
+ expected.push('RepeatMark:detail_repeat_start', 'DetailTable:main_detailarea', 'RepeatMark:detail_repeat_end', 'FakeField:detail_field_title', kinds.includes('FakeField:detail_field_repeat_y') ? 'FakeField:detail_field_repeat_y' : 'FakeField:detail_field_repeat_x');
1015
+ if (kinds.join('|') !== expected.join('|'))
1016
+ return false;
1017
+ for (const comp of page.comps) {
1018
+ const target = comp.config?.excel?.parent === 'ROOT' ? sheet : page.config.workbook[page.comps.find((item) => item.type === 'DetailTable' && item.config?.excel?.nodeId === comp.config?.excel?.parent)?.id];
1019
+ const cell = target?.cells?.find((c) => Number(c.row) === Number(comp.config.excel.row) && Number(c.col) === Number(comp.config.excel.col));
1020
+ if (!cell || cell.style?.cellType?.comId !== comp.id)
1021
+ return false;
1022
+ }
1023
+ return true;
1024
+ }
1025
+ export class AdvancedTemplateService {
1026
+ client;
1027
+ baseUrl;
1028
+ constructor(client, baseUrl) {
1029
+ this.client = client;
1030
+ this.baseUrl = baseUrl;
1031
+ }
1032
+ url(path) { return `${this.baseUrl}${ROOT}/${path}`; }
1033
+ async scaffold() {
1034
+ return result(await this.client.post(this.url('getTemplateAdvancedDesigner'), { id: '-1', module: MODULE }), '读取高级模板新建设计器');
1035
+ }
1036
+ async list() {
1037
+ const rows = [];
1038
+ let pageNo = 1;
1039
+ while (true) {
1040
+ const data = result(await this.client.post(this.url('listTemplateAdvancedByPage'), {
1041
+ pageNo, pageSize: 100, clientType: '0', module: MODULE,
1042
+ }), '查询高级模板');
1043
+ if (!Array.isArray(data?.result))
1044
+ throw new Error('高级模板列表格式未知');
1045
+ rows.push(...data.result);
1046
+ if (rows.length >= Number(data.totalCount || 0))
1047
+ break;
1048
+ if (!data.result.length || ++pageNo > 1000)
1049
+ throw new Error('高级模板分页提前结束');
1050
+ }
1051
+ return rows.map(row => ({ id: String(row.id), templateName: String(row.templateName || ''),
1052
+ system: String(row.isSys) === '1', ...(row.templateStatus === undefined ? {} : { status: Number(row.templateStatus) }) }));
1053
+ }
1054
+ async customTargets(ids) {
1055
+ if (!ids.length || ids.some(id => !/^[1-9]\d*$/.test(id)) || new Set(ids).size !== ids.length)
1056
+ throw new Error('请提供不重复的高级模板 ID');
1057
+ const all = await this.list();
1058
+ return ids.map(id => {
1059
+ const item = all.find(row => row.id === id);
1060
+ if (!item)
1061
+ throw new Error(`高级模板 ${id} 不存在于当前模板列表`);
1062
+ if (item.system)
1063
+ throw new Error(`系统高级模板 ${id} 不能修改或删除`);
1064
+ return item;
1065
+ });
1066
+ }
1067
+ async setStatus(ids, status) {
1068
+ if (status !== 0 && status !== 1)
1069
+ throw new Error('templateStatus 只能是 0 或 1');
1070
+ await this.customTargets(ids);
1071
+ const response = result(await this.client.post(this.url('batchUpdateAdvancedStatus'), {
1072
+ templateIdList: ids, templateStatus: status,
1073
+ }), '更新高级模板状态');
1074
+ if (Number(response?.executeNum) !== ids.length)
1075
+ throw new Error('模板状态操作数量与请求不一致,请重新查询');
1076
+ const after = await this.list();
1077
+ if (ids.some(id => after.find(row => row.id === id)?.status !== status))
1078
+ throw new Error('模板状态回读不一致,请重新查询');
1079
+ return { ids, status, verified: true };
1080
+ }
1081
+ async delete(ids) {
1082
+ await this.customTargets(ids);
1083
+ const check = result(await this.client.post(this.url('batchDeleteAdvancedCheck'), { templateIdList: ids }), '检查高级模板删除');
1084
+ if (!check || typeof check.check !== 'string' || !/可以删除|可删除/.test(check.check) || /不可以删除|不能删除|不可删除/.test(check.check))
1085
+ throw new Error(`高级模板未通过删除检查:${check?.check || '结果未知'}`);
1086
+ result(await this.client.post(this.url('batchDeleteTemplateAdvanced'), { templateIdList: ids }), '删除高级模板');
1087
+ const after = await this.list();
1088
+ if (ids.some(id => after.some(row => row.id === id)))
1089
+ throw new Error('高级模板删除后仍出现在列表中');
1090
+ return { ids, verified: true };
1091
+ }
1092
+ async get(id) {
1093
+ const data = result(await this.client.post(this.url('getTemplateAdvancedDesigner'), { id, module: MODULE }), '读取高级模板');
1094
+ if (String(data?.id) !== id)
1095
+ throw new Error('高级模板回读 ID 不一致');
1096
+ return data;
1097
+ }
1098
+ async showCss(id) {
1099
+ const data = await this.get(id);
1100
+ const code = decodeLayoutCode(data.layoutInfo?.pageLayouts?.[0]?.sourceCode?.ecode);
1101
+ return { id, cssSource: code.css.content, compiledCss: code.css.compiledContent };
1102
+ }
1103
+ async copySaved(sourceId, templateName) {
1104
+ const source = await this.get(sourceId);
1105
+ if (source.layoutInfo?.pageLayouts?.[0]?.layoutType !== 'EXCEL' ||
1106
+ source.layoutInfo?.pageLayouts?.[0]?.terminalType !== 'PC')
1107
+ throw new Error('只能复制高级 PC 表格式模板');
1108
+ const nameOk = result(await this.client.post(this.url('checkTemplateAdvancedName'), {
1109
+ id: sourceId, templateName, templateLabelObj: '', clientType: '0', module: MODULE,
1110
+ }), '校验高级模板名称');
1111
+ if (nameOk !== true)
1112
+ throw new Error(`模板名称不可用:${templateName}`);
1113
+ const copied = result(await this.client.post(this.url('copyTemplateAdvanced'), {
1114
+ id: sourceId, showOrder: source.showOrder ?? 999999, templateName,
1115
+ templateStatus: source.templateStatus ?? 1, templateImgDef: source.templateImgDef || '',
1116
+ clientType: '0', src: 'copy', module: MODULE,
1117
+ }), '复制高级模板');
1118
+ if (copied !== true)
1119
+ throw new Error('复制结果不确定;先按名称查询,避免重复创建');
1120
+ const matches = (await this.list()).filter(item => item.templateName === templateName && item.id !== sourceId);
1121
+ if (matches.length !== 1)
1122
+ throw new Error(`无法唯一确定复制模板 ${templateName} 的 ID;请按名称查询`);
1123
+ const id = matches[0].id;
1124
+ const clone = await this.get(id);
1125
+ if (clone.layoutInfo?.pageLayouts?.[0]?.comps?.length !== source.layoutInfo.pageLayouts[0].comps.length)
1126
+ throw new Error(`复制模板 ${id} 的组件结构回读不一致`);
1127
+ const css = decodeLayoutCode(source.layoutInfo.pageLayouts[0].sourceCode?.ecode).css.content;
1128
+ if (css)
1129
+ await this.saveCss(id, css);
1130
+ return id;
1131
+ }
1132
+ async copy(id, templateName) {
1133
+ if (typeof templateName !== 'string' || !templateName.trim() || templateName.length > 100)
1134
+ throw new Error('templateName 必须是 1–100 字的名称');
1135
+ const newId = await this.copySaved(id, templateName.trim());
1136
+ const preview = result(await this.client.post(`${this.baseUrl}/api/form/local/initLayout`, {
1137
+ initMode: 0, styleTemplateType: 1, styleTemplateId: newId, generateCollapse: false, layoutType: 'EXCEL', module: MODULE,
1138
+ }), '生成高级模板布局预览');
1139
+ if (!Array.isArray(preview?.pageLayouts))
1140
+ throw new Error(`模板 ${newId} 已复制,但应用预览格式未知`);
1141
+ return { id: newId, verified: true, preview: { persisted: false, pageCount: preview.pageLayouts.length } };
1142
+ }
1143
+ async saveCss(id, cssSource) {
1144
+ const source = await this.get(id);
1145
+ if (String(source.isSys) === '1')
1146
+ throw new Error('系统模板不能修改');
1147
+ const layoutInfo = structuredClone(source.layoutInfo);
1148
+ const page = layoutInfo?.pageLayouts?.[0];
1149
+ if (page?.layoutType !== 'EXCEL' || page?.terminalType !== 'PC')
1150
+ throw new Error('CSS 源码仅适用于高级 PC 表格式模板');
1151
+ const code = updateCode(decodeLayoutCode(page.sourceCode?.ecode), { css: cssSource }, 'layout', id);
1152
+ if (code.js.pageId !== undefined)
1153
+ code.js.pageId = `if (${id} == String(props.pageId))`;
1154
+ page.sourceCode = { ...(page.sourceCode || {}), ecode: encodeLayoutCode(code) };
1155
+ page.config.ebCodeFileTypes = [...new Set([...(page.config.ebCodeFileTypes || []), 'css'])];
1156
+ const savedId = String(result(await this.client.post(this.url('saveTemplateAdvanced'), {
1157
+ templateName: source.templateName, labelObj: source.labelObj || '{}', clientType: String(source.clientType ?? '0'),
1158
+ ...(source.showOrder !== undefined ? { showOrder: source.showOrder } : {}),
1159
+ ...(source.templateStatus !== undefined ? { templateStatus: source.templateStatus } : {}),
1160
+ ...(source.templateImg !== undefined ? { templateImg: source.templateImg } : {}),
1161
+ layoutInfo, id, module: MODULE,
1162
+ }), '保存高级模板 CSS 源码') || '');
1163
+ if (savedId !== id)
1164
+ throw new Error(`保存返回 ID ${savedId} 与目标模板 ${id} 不一致`);
1165
+ const saved = await this.get(id);
1166
+ const encoded = saved.layoutInfo?.pageLayouts?.[0]?.sourceCode?.ecode;
1167
+ assertDesignerReadableLayoutCode(encoded);
1168
+ const readback = decodeLayoutCode(encoded);
1169
+ if (readback.css.content.replace(/\r\n?/g, '\n') !== code.css.content.replace(/\r\n?/g, '\n') ||
1170
+ readback.css.compiledContent.replace(/\r\n?/g, '\n') !== code.css.compiledContent.replace(/\r\n?/g, '\n'))
1171
+ throw new Error(`模板 ${id} 保存后 CSS 源码回读不一致`);
1172
+ }
1173
+ async updateCss(id, cssSource) {
1174
+ if (typeof cssSource !== 'string')
1175
+ throw new Error('CSS 源码必须是文字');
1176
+ await this.saveCss(id, cssSource);
1177
+ const preview = result(await this.client.post(`${this.baseUrl}/api/form/local/initLayout`, {
1178
+ initMode: 0, styleTemplateType: 1, styleTemplateId: id, generateCollapse: false, layoutType: 'EXCEL', module: MODULE,
1179
+ }), '生成高级模板布局预览');
1180
+ if (!Array.isArray(preview?.pageLayouts))
1181
+ throw new Error(`模板 ${id} 已保存,但应用预览格式未知`);
1182
+ return { id, verified: true, preview: { persisted: false, pageCount: preview.pageLayouts.length } };
1183
+ }
1184
+ async createConfigured(input) {
1185
+ const config = validateAdvancedTemplateCreateConfig(input);
1186
+ if (!('sourceTemplateId' in config) && !('layoutInfo' in config))
1187
+ return this.createMinimal(config);
1188
+ if (config.sourceTemplateId) {
1189
+ const id = await this.copySaved(config.sourceTemplateId, config.templateName);
1190
+ if (config.edits || config.layoutStyle || config.grid || config.coverFileId !== undefined) {
1191
+ const copied = await this.get(id);
1192
+ const layoutInfo = structuredClone(copied.layoutInfo);
1193
+ applyAdvancedLayoutEdits(layoutInfo, config.edits);
1194
+ if (config.layoutStyle)
1195
+ applyAdvancedLayoutStyle(layoutInfo, config.layoutStyle);
1196
+ if (config.grid)
1197
+ applyAdvancedGridPatch(layoutInfo, config.grid);
1198
+ const savedId = String(result(await this.client.post(this.url('saveTemplateAdvanced'), {
1199
+ templateName: copied.templateName, labelObj: copied.labelObj || '{}', clientType: String(copied.clientType ?? '0'),
1200
+ ...(copied.showOrder !== undefined ? { showOrder: copied.showOrder } : {}),
1201
+ ...(copied.templateStatus !== undefined ? { templateStatus: copied.templateStatus } : {}),
1202
+ ...((config.coverFileId ?? copied.templateImg) !== undefined ? { templateImg: config.coverFileId ?? copied.templateImg } : {}),
1203
+ layoutInfo, id, module: MODULE,
1204
+ }), '保存复制后的高级模板') || '');
1205
+ if (savedId !== id)
1206
+ throw new Error(`保存返回 ID ${savedId} 与目标模板 ${id} 不一致`);
1207
+ const saved = await this.get(id);
1208
+ if (config.layoutStyle)
1209
+ verifyAdvancedStylePatch(layoutInfo, saved.layoutInfo);
1210
+ if (config.grid)
1211
+ verifyAdvancedGridChanges(layoutInfo, saved.layoutInfo, config.grid);
1212
+ verifyAdvancedLayoutEdits(layoutInfo, saved.layoutInfo, config.edits, id);
1213
+ }
1214
+ if (config.cssSource !== undefined)
1215
+ await this.saveCss(id, config.cssSource);
1216
+ const preview = result(await this.client.post(`${this.baseUrl}/api/form/local/initLayout`, {
1217
+ initMode: 0, styleTemplateType: 1, styleTemplateId: id, generateCollapse: false, layoutType: 'EXCEL', module: MODULE,
1218
+ }), '生成高级模板布局预览');
1219
+ if (!Array.isArray(preview?.pageLayouts))
1220
+ throw new Error(`模板 ${id} 已复制,但应用预览格式未知`);
1221
+ return { id, verified: true, preview: { persisted: false, pageCount: preview.pageLayouts.length } };
1222
+ }
1223
+ const source = config.sourceTemplateId ? await this.get(config.sourceTemplateId) : undefined;
1224
+ const layoutInfo = structuredClone(config.layoutInfo || source?.layoutInfo);
1225
+ if (!layoutInfo)
1226
+ throw new Error('模板布局数据缺失');
1227
+ if (plain(layoutInfo.formStyle?.styleConfig))
1228
+ layoutInfo.formStyle.styleConfig = JSON.stringify(layoutInfo.formStyle.styleConfig);
1229
+ applyAdvancedLayoutEdits(layoutInfo, config.edits);
1230
+ if (config.layoutStyle)
1231
+ applyAdvancedLayoutStyle(layoutInfo, config.layoutStyle);
1232
+ if (config.grid)
1233
+ applyAdvancedGridPatch(layoutInfo, config.grid);
1234
+ const pages = layoutInfo.pageLayouts;
1235
+ if (!Array.isArray(pages) || pages.length !== 1 || pages[0]?.layoutType !== 'EXCEL' || pages[0]?.terminalType !== 'PC' ||
1236
+ !pages[0]?.config?.workbook?.Sheet1 || !Array.isArray(pages[0]?.comps))
1237
+ throw new Error('layoutInfo 必须是完整的高级 PC 表格式布局');
1238
+ const nameOk = result(await this.client.post(this.url('checkTemplateAdvancedName'), {
1239
+ id: '-1', templateName: config.templateName, templateLabelObj: {}, clientType: 0, module: MODULE,
1240
+ }), '校验高级模板名称');
1241
+ if (nameOk !== true)
1242
+ throw new Error(`模板名称不可用:${config.templateName}`);
1243
+ const id = String(result(await this.client.post(this.url('saveTemplateAdvanced'), {
1244
+ templateName: config.templateName, labelObj: '{}', clientType: 0, layoutInfo, id: '-1',
1245
+ templateImg: config.coverFileId ?? source?.templateImg ?? '', module: MODULE,
1246
+ }), '保存高级模板') || '');
1247
+ if (!id || id === '-1')
1248
+ throw new Error('保存结果未返回模板 ID;先按名称查询,避免重复创建');
1249
+ const saved = await this.get(id);
1250
+ if (saved.templateName !== config.templateName || saved.layoutInfo?.pageLayouts?.[0]?.comps?.length !== pages[0].comps.length)
1251
+ throw new Error(`模板 ${id} 保存后基本结构回读不一致`);
1252
+ if (config.layoutStyle)
1253
+ verifyAdvancedStylePatch(layoutInfo, saved.layoutInfo);
1254
+ if (config.grid)
1255
+ verifyAdvancedGridChanges(layoutInfo, saved.layoutInfo, config.grid);
1256
+ verifyAdvancedLayoutEdits(layoutInfo, saved.layoutInfo, config.edits, id);
1257
+ const existingCss = decodeLayoutCode(pages[0].sourceCode?.ecode).css.content;
1258
+ if (config.cssSource !== undefined || existingCss)
1259
+ await this.saveCss(id, config.cssSource ?? existingCss);
1260
+ const preview = result(await this.client.post(`${this.baseUrl}/api/form/local/initLayout`, {
1261
+ initMode: 0, styleTemplateType: 1, styleTemplateId: id, generateCollapse: false, layoutType: 'EXCEL', module: MODULE,
1262
+ }), '生成高级模板布局预览');
1263
+ if (!Array.isArray(preview?.pageLayouts) || preview.pageLayouts.length < 1)
1264
+ throw new Error(`模板 ${id} 已保存,但应用预览格式未知`);
1265
+ return { id, verified: true, preview: { persisted: false, pageCount: preview.pageLayouts.length,
1266
+ componentCount: preview.pageLayouts.reduce((n, page) => n + (page.comps?.length || 0), 0) } };
1267
+ }
1268
+ async createMinimal(input) {
1269
+ const design = validateMinimalAdvancedDesign(input);
1270
+ const layoutInfo = buildMinimalAdvancedLayout(await this.scaffold(), design);
1271
+ if (design.layoutStyle)
1272
+ applyAdvancedLayoutStyle(layoutInfo, design.layoutStyle);
1273
+ if (design.grid)
1274
+ applyAdvancedGridPatch(layoutInfo, design.grid);
1275
+ if (!verifyMinimalAdvancedLayout(layoutInfo))
1276
+ throw new Error('最小高级模板结构校验失败');
1277
+ const nameOk = result(await this.client.post(this.url('checkTemplateAdvancedName'), {
1278
+ id: '-1', templateName: design.templateName, templateLabelObj: {}, clientType: 0, module: MODULE,
1279
+ }), '校验高级模板名称');
1280
+ if (nameOk !== true)
1281
+ throw new Error(`模板名称不可用:${design.templateName}`);
1282
+ const id = String(result(await this.client.post(this.url('saveTemplateAdvanced'), {
1283
+ templateName: design.templateName, labelObj: '{}', clientType: 0, layoutInfo, id: '-1', templateImg: design.coverFileId || '', module: MODULE,
1284
+ }), '保存高级模板') || '');
1285
+ if (!id || id === '-1')
1286
+ throw new Error('保存结果未返回模板 ID;先按名称查询,避免重复创建');
1287
+ const saved = await this.get(id);
1288
+ if (!verifyMinimalAdvancedLayout(saved.layoutInfo))
1289
+ throw new Error(`模板 ${id} 保存后结构回读不一致`);
1290
+ if (design.layoutStyle)
1291
+ verifyAdvancedStylePatch(layoutInfo, saved.layoutInfo);
1292
+ if (design.grid)
1293
+ verifyAdvancedGridChanges(layoutInfo, saved.layoutInfo, design.grid);
1294
+ if (design.cssSource !== undefined)
1295
+ await this.saveCss(id, design.cssSource);
1296
+ const preview = result(await this.client.post(`${this.baseUrl}/api/form/local/initLayout`, {
1297
+ initMode: 0, styleTemplateType: 1, styleTemplateId: id, generateCollapse: false, layoutType: 'EXCEL', module: MODULE,
1298
+ }), '生成高级模板布局预览');
1299
+ if (!Array.isArray(preview?.pageLayouts) || preview.pageLayouts.length < 1)
1300
+ throw new Error(`模板 ${id} 已保存,但应用预览格式未知`);
1301
+ return { id, verified: true, preview: { persisted: false, pageCount: preview.pageLayouts.length,
1302
+ componentCount: preview.pageLayouts.reduce((n, page) => n + (page.comps?.length || 0), 0) } };
1303
+ }
1304
+ async updateLayout(id, input) {
1305
+ const patch = validateAdvancedLayoutEditPatch(input);
1306
+ const source = await this.get(id);
1307
+ if (String(source.isSys) === '1')
1308
+ throw new Error('系统模板不能修改');
1309
+ const layoutInfo = structuredClone(source.layoutInfo);
1310
+ applyAdvancedLayoutEdits(layoutInfo, patch.edits);
1311
+ const page = layoutInfo.pageLayouts?.[0];
1312
+ if (page?.layoutType !== 'EXCEL' || page?.terminalType !== 'PC' || !page.config?.workbook?.Sheet1)
1313
+ throw new Error('修改后的布局必须保留高级 PC 表格式主网格');
1314
+ const savedId = String(result(await this.client.post(this.url('saveTemplateAdvanced'), {
1315
+ templateName: source.templateName, labelObj: source.labelObj || '{}', clientType: String(source.clientType ?? '0'),
1316
+ ...(source.showOrder !== undefined ? { showOrder: source.showOrder } : {}),
1317
+ ...(source.templateStatus !== undefined ? { templateStatus: source.templateStatus } : {}),
1318
+ ...(source.templateImg !== undefined ? { templateImg: source.templateImg } : {}),
1319
+ layoutInfo, id, module: MODULE,
1320
+ }), '保存高级模板布局') || '');
1321
+ if (savedId !== id)
1322
+ throw new Error(`保存返回 ID ${savedId} 与目标模板 ${id} 不一致`);
1323
+ const saved = await this.get(id);
1324
+ verifyAdvancedLayoutEdits(layoutInfo, saved.layoutInfo, patch.edits, id);
1325
+ const preview = result(await this.client.post(`${this.baseUrl}/api/form/local/initLayout`, {
1326
+ initMode: 0, styleTemplateType: 1, styleTemplateId: id, generateCollapse: false, layoutType: 'EXCEL', module: MODULE,
1327
+ }), '生成高级模板布局预览');
1328
+ if (!Array.isArray(preview?.pageLayouts) || preview.pageLayouts.length < 1)
1329
+ throw new Error(`模板 ${id} 已保存,但应用预览格式未知`);
1330
+ return { id, verified: true, preview: { persisted: false, pageCount: preview.pageLayouts.length,
1331
+ componentCount: preview.pageLayouts.reduce((n, item) => n + (item.comps?.length || 0), 0) } };
1332
+ }
1333
+ async updateLayoutStyle(id, patch) {
1334
+ validateAdvancedLayoutStylePatch(patch);
1335
+ const source = await this.get(id);
1336
+ if (String(source.isSys) === '1')
1337
+ throw new Error('系统模板不能修改');
1338
+ const layoutInfo = structuredClone(source.layoutInfo);
1339
+ applyAdvancedLayoutStyle(layoutInfo, patch);
1340
+ const savedId = String(result(await this.client.post(this.url('saveTemplateAdvanced'), {
1341
+ templateName: source.templateName, labelObj: source.labelObj || '{}', clientType: String(source.clientType ?? '0'),
1342
+ ...(source.showOrder !== undefined ? { showOrder: source.showOrder } : {}),
1343
+ ...(source.templateStatus !== undefined ? { templateStatus: source.templateStatus } : {}),
1344
+ ...(source.templateImg !== undefined ? { templateImg: source.templateImg } : {}),
1345
+ layoutInfo, id, module: MODULE,
1346
+ }), '保存高级模板布局样式') || '');
1347
+ if (savedId !== id)
1348
+ throw new Error(`保存返回 ID ${savedId} 与目标模板 ${id} 不一致`);
1349
+ const saved = await this.get(id);
1350
+ verifyAdvancedStylePatch(layoutInfo, saved.layoutInfo);
1351
+ const preview = result(await this.client.post(`${this.baseUrl}/api/form/local/initLayout`, {
1352
+ initMode: 0, styleTemplateType: 1, styleTemplateId: id, generateCollapse: false, layoutType: 'EXCEL', module: MODULE,
1353
+ }), '生成高级模板布局预览');
1354
+ if (!Array.isArray(preview?.pageLayouts))
1355
+ throw new Error(`模板 ${id} 已保存,但应用预览格式未知`);
1356
+ return { id, verified: true, preview: { persisted: false, pageCount: preview.pageLayouts.length } };
1357
+ }
1358
+ async updateGrid(id, patch) {
1359
+ validateAdvancedGridPatch(patch);
1360
+ const source = await this.get(id);
1361
+ if (String(source.isSys) === '1')
1362
+ throw new Error('系统模板不能修改');
1363
+ const layoutInfo = structuredClone(source.layoutInfo);
1364
+ applyAdvancedGridPatch(layoutInfo, patch);
1365
+ const savedId = String(result(await this.client.post(this.url('saveTemplateAdvanced'), {
1366
+ templateName: source.templateName, labelObj: source.labelObj || '{}', clientType: String(source.clientType ?? '0'),
1367
+ ...(source.showOrder !== undefined ? { showOrder: source.showOrder } : {}),
1368
+ ...(source.templateStatus !== undefined ? { templateStatus: source.templateStatus } : {}),
1369
+ ...(source.templateImg !== undefined ? { templateImg: source.templateImg } : {}),
1370
+ layoutInfo, id, module: MODULE,
1371
+ }), '保存高级模板网格样式') || '');
1372
+ if (savedId !== id)
1373
+ throw new Error(`保存返回 ID ${savedId} 与目标模板 ${id} 不一致`);
1374
+ const saved = await this.get(id);
1375
+ verifyAdvancedGridChanges(layoutInfo, saved.layoutInfo, patch);
1376
+ const preview = result(await this.client.post(`${this.baseUrl}/api/form/local/initLayout`, {
1377
+ initMode: 0, styleTemplateType: 1, styleTemplateId: id, generateCollapse: false, layoutType: 'EXCEL', module: MODULE,
1378
+ }), '生成高级模板布局预览');
1379
+ if (!Array.isArray(preview?.pageLayouts))
1380
+ throw new Error(`模板 ${id} 已保存,但应用预览格式未知`);
1381
+ return { id, verified: true, preview: { persisted: false, pageCount: preview.pageLayouts.length } };
1382
+ }
1383
+ }
1384
+ function verifyAdvancedGridChanges(expectedLayout, savedLayout, patch) {
1385
+ for (const scope of ['main', 'detail']) {
1386
+ if (!patch[scope])
1387
+ continue;
1388
+ const expectedBook = expectedLayout.pageLayouts[0].config.workbook;
1389
+ const savedBook = savedLayout.pageLayouts[0].config.workbook;
1390
+ const expectedKey = scope === 'main' ? 'Sheet1' : Object.keys(expectedBook).find(name => name !== 'Sheet1');
1391
+ const savedKey = scope === 'main' ? 'Sheet1' : Object.keys(savedBook).find(name => name !== 'Sheet1');
1392
+ const a = expectedBook[expectedKey], b = savedBook[savedKey];
1393
+ if (!b)
1394
+ throw new Error(`${scope} 网格回读缺失`);
1395
+ for (const row of patch[scope].rowHeights || [])
1396
+ if (b.rows?.[row.row]?.size !== row.px)
1397
+ throw new Error(`${scope} 第 ${row.row} 行高回读不一致`);
1398
+ for (const col of patch[scope].columns || [])
1399
+ if (b.columns?.[col.col]?.type !== col.unit || b.columns?.[col.col]?.size !== col.value)
1400
+ throw new Error(`${scope} 第 ${col.col} 列宽回读不一致`);
1401
+ for (const span of patch[scope].merge || [])
1402
+ if (!b.spans?.some((actual) => actual.row === span.row && actual.col === span.col &&
1403
+ actual.rowCount === span.rowCount && actual.colCount === span.colCount))
1404
+ throw new Error(`${scope} 合并范围回读缺失`);
1405
+ for (const span of patch[scope].split || [])
1406
+ if (b.spans?.some((actual) => actual.row === span.row && actual.col === span.col))
1407
+ throw new Error(`${scope} 拆分范围回读未生效`);
1408
+ for (const change of patch[scope].cells || []) {
1409
+ const expected = a.cells.find((c) => Number(c.row) === change.row && Number(c.col) === change.col);
1410
+ const actual = b.cells.find((c) => Number(c.row) === change.row && Number(c.col) === change.col);
1411
+ if (!actual)
1412
+ throw new Error(`${scope} 单元格 ${change.row},${change.col} 回读缺失`);
1413
+ if ((change.text !== undefined || change.systemParameter !== undefined) && actual.value !== expected.value)
1414
+ throw new Error(`${scope} 单元格 ${change.row},${change.col} 的文字回读不一致`);
1415
+ for (const [name, val] of Object.entries(expected.style))
1416
+ if (name !== 'cellType' && JSON.stringify(actual.style?.[name]) !== JSON.stringify(val))
1417
+ throw new Error(`${scope} 单元格 ${change.row},${change.col} 的 ${name} 回读不一致`);
1418
+ for (const [side, border] of Object.entries(change.borders || {}))
1419
+ if (border.line === 'none' &&
1420
+ actual.style?.[`border${side[0].toUpperCase()}${side.slice(1)}`] !== undefined)
1421
+ throw new Error(`${scope} 单元格 ${change.row},${change.col} 的 ${side} 边框清除未生效`);
1422
+ }
1423
+ }
1424
+ }
1425
+ function verifyAdvancedStylePatch(expectedLayout, savedLayout) {
1426
+ const expected = JSON.parse(expectedLayout.formStyle.styleConfig);
1427
+ const saved = JSON.parse(savedLayout.formStyle?.styleConfig || '{}');
1428
+ const visit = (a, b, path) => {
1429
+ if (plain(a))
1430
+ for (const [key, value] of Object.entries(a))
1431
+ visit(value, b?.[key], `${path}.${key}`);
1432
+ else if (a !== b)
1433
+ throw new Error(`布局样式回读不一致:${path}`);
1434
+ };
1435
+ visit(expected, saved, 'styleConfig');
1436
+ }
1437
+ //# sourceMappingURL=form-layout-template-advanced.js.map