misp-ui-library-test 0.0.1

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 (373) hide show
  1. package/README.md +113 -0
  2. package/dist/theme-chalk/alert/index.scss +152 -0
  3. package/dist/theme-chalk/avatar/index.scss +51 -0
  4. package/dist/theme-chalk/backtop/index.scss +38 -0
  5. package/dist/theme-chalk/badge/index.scss +63 -0
  6. package/dist/theme-chalk/breadcrumb/index.scss +36 -0
  7. package/dist/theme-chalk/breadcrumb-item/index.scss +36 -0
  8. package/dist/theme-chalk/button/index.scss +157 -0
  9. package/dist/theme-chalk/card/index.scss +38 -0
  10. package/dist/theme-chalk/chart/index.scss +21 -0
  11. package/dist/theme-chalk/checkbox/index.scss +358 -0
  12. package/dist/theme-chalk/col/index.scss +131 -0
  13. package/dist/theme-chalk/container/index.scss +40 -0
  14. package/dist/theme-chalk/css/alert.css +484 -0
  15. package/dist/theme-chalk/css/avatar.css +409 -0
  16. package/dist/theme-chalk/css/backtop.css +401 -0
  17. package/dist/theme-chalk/css/badge.css +419 -0
  18. package/dist/theme-chalk/css/breadcrumb-item.css +395 -0
  19. package/dist/theme-chalk/css/breadcrumb.css +395 -0
  20. package/dist/theme-chalk/css/button.css +587 -0
  21. package/dist/theme-chalk/css/card.css +397 -0
  22. package/dist/theme-chalk/css/chart.css +386 -0
  23. package/dist/theme-chalk/css/checkbox.css +648 -0
  24. package/dist/theme-chalk/css/col.css +2111 -0
  25. package/dist/theme-chalk/css/container.css +404 -0
  26. package/dist/theme-chalk/css/dialog.css +505 -0
  27. package/dist/theme-chalk/css/divider.css +405 -0
  28. package/dist/theme-chalk/css/drawer.css +546 -0
  29. package/dist/theme-chalk/css/empty.css +394 -0
  30. package/dist/theme-chalk/css/form.css +561 -0
  31. package/dist/theme-chalk/css/icon.css +1534 -0
  32. package/dist/theme-chalk/css/image.css +400 -0
  33. package/dist/theme-chalk/css/input-number.css +424 -0
  34. package/dist/theme-chalk/css/input.css +721 -0
  35. package/dist/theme-chalk/css/link.css +424 -0
  36. package/dist/theme-chalk/css/loading.css +483 -0
  37. package/dist/theme-chalk/css/message-box.css +479 -0
  38. package/dist/theme-chalk/css/message.css +456 -0
  39. package/dist/theme-chalk/css/no-data.css +397 -0
  40. package/dist/theme-chalk/css/notification.css +431 -0
  41. package/dist/theme-chalk/css/page-header.css +396 -0
  42. package/dist/theme-chalk/css/pagination.css +565 -0
  43. package/dist/theme-chalk/css/popover.css +421 -0
  44. package/dist/theme-chalk/css/popper.css +454 -0
  45. package/dist/theme-chalk/css/progress.css +451 -0
  46. package/dist/theme-chalk/css/radio.css +626 -0
  47. package/dist/theme-chalk/css/rate.css +414 -0
  48. package/dist/theme-chalk/css/result.css +403 -0
  49. package/dist/theme-chalk/css/row.css +433 -0
  50. package/dist/theme-chalk/css/scrollbar.css +422 -0
  51. package/dist/theme-chalk/css/select.css +1897 -0
  52. package/dist/theme-chalk/css/skeleton-item.css +407 -0
  53. package/dist/theme-chalk/css/skeleton.css +390 -0
  54. package/dist/theme-chalk/css/slider.css +460 -0
  55. package/dist/theme-chalk/css/spinner.css +403 -0
  56. package/dist/theme-chalk/css/statistic.css +396 -0
  57. package/dist/theme-chalk/css/status-timeline-chart.css +388 -0
  58. package/dist/theme-chalk/css/step.css +496 -0
  59. package/dist/theme-chalk/css/steps.css +496 -0
  60. package/dist/theme-chalk/css/switch.css +507 -0
  61. package/dist/theme-chalk/css/tab-pane.css +457 -0
  62. package/dist/theme-chalk/css/table.css +461 -0
  63. package/dist/theme-chalk/css/tabs-navigation.css +925 -0
  64. package/dist/theme-chalk/css/tabs.css +457 -0
  65. package/dist/theme-chalk/css/tag.css +599 -0
  66. package/dist/theme-chalk/css/time-picker.css +683 -0
  67. package/dist/theme-chalk/css/timeline-item.css +459 -0
  68. package/dist/theme-chalk/css/timeline.css +459 -0
  69. package/dist/theme-chalk/css/tooltip.css +485 -0
  70. package/dist/theme-chalk/css/tree.css +473 -0
  71. package/dist/theme-chalk/css/upload.css +665 -0
  72. package/dist/theme-chalk/dialog/index.scss +168 -0
  73. package/dist/theme-chalk/divider/index.scss +46 -0
  74. package/dist/theme-chalk/drawer/index.scss +172 -0
  75. package/dist/theme-chalk/empty/index.scss +36 -0
  76. package/dist/theme-chalk/fonts/element-icons.ttf +0 -0
  77. package/dist/theme-chalk/fonts/element-icons.woff +0 -0
  78. package/dist/theme-chalk/form/index.scss +220 -0
  79. package/dist/theme-chalk/icon/index.scss +1171 -0
  80. package/dist/theme-chalk/image/index.scss +39 -0
  81. package/dist/theme-chalk/index.scss +127 -0
  82. package/dist/theme-chalk/input/index.scss +363 -0
  83. package/dist/theme-chalk/input-number/index.scss +71 -0
  84. package/dist/theme-chalk/link/index.scss +75 -0
  85. package/dist/theme-chalk/loading/index.scss +131 -0
  86. package/dist/theme-chalk/message/index.scss +100 -0
  87. package/dist/theme-chalk/message-box/index.scss +131 -0
  88. package/dist/theme-chalk/mixins.scss +203 -0
  89. package/dist/theme-chalk/no-data/index.scss +36 -0
  90. package/dist/theme-chalk/notification/index.scss +76 -0
  91. package/dist/theme-chalk/page-header/index.scss +36 -0
  92. package/dist/theme-chalk/pagination/index.scss +225 -0
  93. package/dist/theme-chalk/popover/index.scss +56 -0
  94. package/dist/theme-chalk/popper/index.scss +89 -0
  95. package/dist/theme-chalk/progress/index.scss +105 -0
  96. package/dist/theme-chalk/radio/index.scss +328 -0
  97. package/dist/theme-chalk/rate/index.scss +56 -0
  98. package/dist/theme-chalk/reset.scss +262 -0
  99. package/dist/theme-chalk/result/index.scss +46 -0
  100. package/dist/theme-chalk/row/index.scss +88 -0
  101. package/dist/theme-chalk/scrollbar/index.scss +67 -0
  102. package/dist/theme-chalk/select/index.scss +223 -0
  103. package/dist/theme-chalk/skeleton/index.scss +30 -0
  104. package/dist/theme-chalk/skeleton-item/index.scss +49 -0
  105. package/dist/theme-chalk/slider/index.scss +113 -0
  106. package/dist/theme-chalk/spinner/index.scss +40 -0
  107. package/dist/theme-chalk/statistic/index.scss +38 -0
  108. package/dist/theme-chalk/status-timeline-chart/index.scss +23 -0
  109. package/dist/theme-chalk/step/index.scss +160 -0
  110. package/dist/theme-chalk/steps/index.scss +160 -0
  111. package/dist/theme-chalk/switch/index.scss +153 -0
  112. package/dist/theme-chalk/tab-pane/index.scss +112 -0
  113. package/dist/theme-chalk/table/index.scss +110 -0
  114. package/dist/theme-chalk/tabs/index.scss +112 -0
  115. package/dist/theme-chalk/tabs-navigation/index.scss +631 -0
  116. package/dist/theme-chalk/tag/index.scss +138 -0
  117. package/dist/theme-chalk/themes/blue.scss +76 -0
  118. package/dist/theme-chalk/themes/dark.scss +99 -0
  119. package/dist/theme-chalk/themes/index.scss +260 -0
  120. package/dist/theme-chalk/themes/light.scss +99 -0
  121. package/dist/theme-chalk/time-picker/index.scss +332 -0
  122. package/dist/theme-chalk/timeline/index.scss +119 -0
  123. package/dist/theme-chalk/timeline-item/index.scss +119 -0
  124. package/dist/theme-chalk/tooltip/index.scss +122 -0
  125. package/dist/theme-chalk/tree/index.scss +125 -0
  126. package/dist/theme-chalk/upload/index.scss +348 -0
  127. package/dist/theme-chalk/variables.scss +93 -0
  128. package/dist/vue2-ui-library.cjs.temp.js.map +1 -0
  129. package/dist/vue2-ui-library.common.js +28 -0
  130. package/dist/vue2-ui-library.common.js.map +1 -0
  131. package/dist/vue2-ui-library.esm.js +29 -0
  132. package/dist/vue2-ui-library.umd.js +27 -0
  133. package/package.json +161 -0
  134. package/src/directives/clickoutside/index.js +111 -0
  135. package/src/directives/init/index.js +50 -0
  136. package/src/directives/resize/index.js +69 -0
  137. package/src/index.js +174 -0
  138. package/src/mixins/emitter.js +47 -0
  139. package/src/mixins/focus.js +42 -0
  140. package/src/mixins/locale.js +24 -0
  141. package/src/mixins/migrating.js +46 -0
  142. package/src/packages/alert/index.js +12 -0
  143. package/src/packages/alert/src/main.vue +98 -0
  144. package/src/packages/aside/index.js +12 -0
  145. package/src/packages/aside/src/main.vue +20 -0
  146. package/src/packages/auto-grid-layout/index.js +18 -0
  147. package/src/packages/auto-grid-layout/prop.js +92 -0
  148. package/src/packages/auto-grid-layout/src/components/GridItem.vue +759 -0
  149. package/src/packages/auto-grid-layout/src/components/GridLayout.vue +367 -0
  150. package/src/packages/auto-grid-layout/src/main.vue +287 -0
  151. package/src/packages/auto-grid-layout/src/utils/DOM.js +46 -0
  152. package/src/packages/auto-grid-layout/src/utils/interact.js +333 -0
  153. package/src/packages/auto-grid-layout/src/utils/responsiveUtils.js +75 -0
  154. package/src/packages/auto-grid-layout/src/utils/utils.js +339 -0
  155. package/src/packages/autocomplete/index.js +12 -0
  156. package/src/packages/autocomplete/src/main.vue +170 -0
  157. package/src/packages/avatar/index.js +12 -0
  158. package/src/packages/avatar/src/main.vue +87 -0
  159. package/src/packages/backtop/index.js +12 -0
  160. package/src/packages/backtop/src/main.vue +120 -0
  161. package/src/packages/badge/index.js +12 -0
  162. package/src/packages/badge/src/main.vue +53 -0
  163. package/src/packages/breadcrumb/index.js +12 -0
  164. package/src/packages/breadcrumb/src/main.vue +34 -0
  165. package/src/packages/breadcrumb-item/index.js +12 -0
  166. package/src/packages/breadcrumb-item/src/main.vue +41 -0
  167. package/src/packages/button/index.js +13 -0
  168. package/src/packages/button/src/main.vue +106 -0
  169. package/src/packages/calendar/index.js +12 -0
  170. package/src/packages/calendar/src/main.vue +173 -0
  171. package/src/packages/card/index.js +12 -0
  172. package/src/packages/card/src/main.vue +26 -0
  173. package/src/packages/carousel/index.js +12 -0
  174. package/src/packages/carousel/src/main.vue +186 -0
  175. package/src/packages/carousel-item/index.js +12 -0
  176. package/src/packages/carousel-item/src/main.vue +34 -0
  177. package/src/packages/cascader/index.js +12 -0
  178. package/src/packages/cascader/src/main.vue +232 -0
  179. package/src/packages/chart/index.js +7 -0
  180. package/src/packages/chart/src/axis-chart.js +700 -0
  181. package/src/packages/chart/src/main.vue +828 -0
  182. package/src/packages/chart/src/utils.js +21 -0
  183. package/src/packages/checkbox/index.js +44 -0
  184. package/src/packages/checkbox/src/main.vue +312 -0
  185. package/src/packages/col/index.js +12 -0
  186. package/src/packages/col/src/main.vue +85 -0
  187. package/src/packages/collapse/index.js +12 -0
  188. package/src/packages/collapse/src/main.vue +69 -0
  189. package/src/packages/collapse-item/index.js +12 -0
  190. package/src/packages/collapse-item/src/main.vue +75 -0
  191. package/src/packages/color-picker/index.js +12 -0
  192. package/src/packages/color-picker/src/main.vue +206 -0
  193. package/src/packages/container/index.js +12 -0
  194. package/src/packages/container/src/main.vue +33 -0
  195. package/src/packages/date-picker/index.js +12 -0
  196. package/src/packages/date-picker/src/main.vue +246 -0
  197. package/src/packages/descriptions/index.js +12 -0
  198. package/src/packages/descriptions/src/main.vue +89 -0
  199. package/src/packages/descriptions-item/index.js +12 -0
  200. package/src/packages/descriptions-item/src/main.vue +26 -0
  201. package/src/packages/dialog/index.js +12 -0
  202. package/src/packages/dialog/src/main.vue +336 -0
  203. package/src/packages/divider/index.js +12 -0
  204. package/src/packages/divider/src/main.vue +37 -0
  205. package/src/packages/drawer/index.js +7 -0
  206. package/src/packages/drawer/src/main.vue +310 -0
  207. package/src/packages/dropdown/index.js +12 -0
  208. package/src/packages/dropdown/src/main.vue +82 -0
  209. package/src/packages/dropdown-menu-item/index.js +12 -0
  210. package/src/packages/dropdown-menu-item/src/main.vue +29 -0
  211. package/src/packages/empty/index.js +12 -0
  212. package/src/packages/empty/src/img-empty.vue +12 -0
  213. package/src/packages/empty/src/main.vue +49 -0
  214. package/src/packages/footer/index.js +12 -0
  215. package/src/packages/footer/src/main.vue +20 -0
  216. package/src/packages/form/index.js +13 -0
  217. package/src/packages/form/src/form-field.vue +790 -0
  218. package/src/packages/form/src/form-item.vue +464 -0
  219. package/src/packages/form/src/label-wrap.vue +127 -0
  220. package/src/packages/form/src/main.vue +442 -0
  221. package/src/packages/form-item/index.js +11 -0
  222. package/src/packages/header/index.js +12 -0
  223. package/src/packages/header/src/main.vue +20 -0
  224. package/src/packages/icon/index.js +12 -0
  225. package/src/packages/icon/src/main.vue +48 -0
  226. package/src/packages/image/index.js +12 -0
  227. package/src/packages/image/src/main.vue +224 -0
  228. package/src/packages/input/index.js +13 -0
  229. package/src/packages/input/src/calcTextareaHeight.js +123 -0
  230. package/src/packages/input/src/main.vue +510 -0
  231. package/src/packages/input-number/index.js +12 -0
  232. package/src/packages/input-number/src/main.vue +121 -0
  233. package/src/packages/link/index.js +12 -0
  234. package/src/packages/link/src/main.vue +53 -0
  235. package/src/packages/loading/index.js +17 -0
  236. package/src/packages/loading/src/directive.js +54 -0
  237. package/src/packages/loading/src/main.vue +38 -0
  238. package/src/packages/loading/src/service.js +63 -0
  239. package/src/packages/main/index.js +12 -0
  240. package/src/packages/main/src/main.vue +12 -0
  241. package/src/packages/menu/index.js +12 -0
  242. package/src/packages/menu/src/main.vue +117 -0
  243. package/src/packages/menu-item/index.js +12 -0
  244. package/src/packages/menu-item/src/main.vue +61 -0
  245. package/src/packages/message/index.js +95 -0
  246. package/src/packages/message/src/main.vue +131 -0
  247. package/src/packages/message-box/index.js +75 -0
  248. package/src/packages/message-box/src/main.vue +207 -0
  249. package/src/packages/no-data/index.js +8 -0
  250. package/src/packages/no-data/src/main.vue +23 -0
  251. package/src/packages/notification/index.js +72 -0
  252. package/src/packages/notification/src/main.vue +139 -0
  253. package/src/packages/option/index.js +13 -0
  254. package/src/packages/page-header/index.js +12 -0
  255. package/src/packages/page-header/src/main.vue +27 -0
  256. package/src/packages/pagination/index.js +12 -0
  257. package/src/packages/pagination/src/main.vue +255 -0
  258. package/src/packages/popconfirm/index.js +12 -0
  259. package/src/packages/popconfirm/src/main.vue +89 -0
  260. package/src/packages/popover/index.js +12 -0
  261. package/src/packages/popover/src/main.vue +452 -0
  262. package/src/packages/progress/index.js +12 -0
  263. package/src/packages/progress/src/main.vue +236 -0
  264. package/src/packages/radio/index.js +44 -0
  265. package/src/packages/radio/src/main.vue +291 -0
  266. package/src/packages/rate/index.js +12 -0
  267. package/src/packages/rate/src/main.vue +129 -0
  268. package/src/packages/result/index.js +12 -0
  269. package/src/packages/result/src/main.vue +52 -0
  270. package/src/packages/row/index.js +12 -0
  271. package/src/packages/row/src/main.vue +57 -0
  272. package/src/packages/scrollbar/index.js +12 -0
  273. package/src/packages/scrollbar/src/bar.vue +116 -0
  274. package/src/packages/scrollbar/src/main.vue +124 -0
  275. package/src/packages/select/index.js +15 -0
  276. package/src/packages/select/src/clickoutside.js +20 -0
  277. package/src/packages/select/src/main.vue +1055 -0
  278. package/src/packages/select/src/navigation-mixin.js +49 -0
  279. package/src/packages/select/src/option.vue +249 -0
  280. package/src/packages/select/src/select-dropdown.vue +95 -0
  281. package/src/packages/select/src/utils.js +5 -0
  282. package/src/packages/skeleton/index.js +12 -0
  283. package/src/packages/skeleton/src/main.vue +76 -0
  284. package/src/packages/skeleton-item/index.js +12 -0
  285. package/src/packages/skeleton-item/src/img-placeholder.vue +13 -0
  286. package/src/packages/skeleton-item/src/main.vue +22 -0
  287. package/src/packages/slider/index.js +12 -0
  288. package/src/packages/slider/src/main.vue +176 -0
  289. package/src/packages/spinner/index.js +12 -0
  290. package/src/packages/spinner/src/main.vue +27 -0
  291. package/src/packages/statistic/index.js +12 -0
  292. package/src/packages/statistic/src/main.vue +100 -0
  293. package/src/packages/status-timeline-chart/index.js +7 -0
  294. package/src/packages/status-timeline-chart/src/constants.js +20 -0
  295. package/src/packages/status-timeline-chart/src/main.vue +499 -0
  296. package/src/packages/status-timeline-chart/src/option-builder.js +475 -0
  297. package/src/packages/step/index.js +12 -0
  298. package/src/packages/step/src/main.vue +183 -0
  299. package/src/packages/steps/index.js +12 -0
  300. package/src/packages/steps/src/main.vue +57 -0
  301. package/src/packages/submenu/index.js +12 -0
  302. package/src/packages/submenu/src/main.vue +68 -0
  303. package/src/packages/switch/index.js +12 -0
  304. package/src/packages/switch/src/main.vue +166 -0
  305. package/src/packages/tab-pane/index.js +12 -0
  306. package/src/packages/tab-pane/src/main.vue +74 -0
  307. package/src/packages/table/index.js +8 -0
  308. package/src/packages/table/src/components/PagePagination.vue +82 -0
  309. package/src/packages/table/src/main.vue +153 -0
  310. package/src/packages/tabs/index.js +12 -0
  311. package/src/packages/tabs/src/main.vue +167 -0
  312. package/src/packages/tabs-navigation/index.js +7 -0
  313. package/src/packages/tabs-navigation/src/main.vue +462 -0
  314. package/src/packages/tag/index.js +12 -0
  315. package/src/packages/tag/src/main.vue +194 -0
  316. package/src/packages/time-picker/index.js +17 -0
  317. package/src/packages/time-picker/src/date-utils.js +455 -0
  318. package/src/packages/time-picker/src/main.vue +321 -0
  319. package/src/packages/time-picker/src/pane/date-range.vue +193 -0
  320. package/src/packages/time-picker/src/pane/date-time-range.vue +362 -0
  321. package/src/packages/time-picker/src/pane/date-time.vue +362 -0
  322. package/src/packages/time-picker/src/pane/date.vue +441 -0
  323. package/src/packages/time-picker/src/pane/time.vue +260 -0
  324. package/src/packages/time-picker/src/time-input.vue +203 -0
  325. package/src/packages/time-picker/src/time-range-input.vue +170 -0
  326. package/src/packages/timeline/index.js +12 -0
  327. package/src/packages/timeline/src/main.vue +24 -0
  328. package/src/packages/timeline-item/index.js +12 -0
  329. package/src/packages/timeline-item/src/main.vue +78 -0
  330. package/src/packages/tooltip/index.js +12 -0
  331. package/src/packages/tooltip/src/main.js +367 -0
  332. package/src/packages/transfer/index.js +8 -0
  333. package/src/packages/transfer/src/main.vue +229 -0
  334. package/src/packages/transfer/src/transfer-panel.vue +245 -0
  335. package/src/packages/transitions/collapse-transition.js +75 -0
  336. package/src/packages/tree/index.js +12 -0
  337. package/src/packages/tree/src/main.vue +322 -0
  338. package/src/packages/tree/src/model/node.js +348 -0
  339. package/src/packages/tree/src/model/tree-store.js +354 -0
  340. package/src/packages/tree/src/model/util.js +10 -0
  341. package/src/packages/tree/src/tree-node.vue +262 -0
  342. package/src/packages/upload/index.js +9 -0
  343. package/src/packages/upload/src/main.vue +415 -0
  344. package/src/playground/mixins/element.js +98 -0
  345. package/src/playground/types/element.js +197 -0
  346. package/src/playground/types/layout.js +80 -0
  347. package/src/plugins/i18n.js +125 -0
  348. package/src/styles/fonts/element-icons.ttf +0 -0
  349. package/src/styles/fonts/element-icons.woff +0 -0
  350. package/src/styles/index.scss +138 -0
  351. package/src/styles/reset.scss +257 -0
  352. package/src/styles/themes/blue.scss +75 -0
  353. package/src/styles/themes/dark.scss +98 -0
  354. package/src/styles/themes/index.scss +34 -0
  355. package/src/styles/themes/light.scss +98 -0
  356. package/src/styles/themes/variables.scss +233 -0
  357. package/src/styles/variables.scss +146 -0
  358. package/src/utils/async-validator/index.js +659 -0
  359. package/src/utils/browser/index.js +39 -0
  360. package/src/utils/color.js +76 -0
  361. package/src/utils/dom/index.js +421 -0
  362. package/src/utils/dom/scrollbar-width.js +49 -0
  363. package/src/utils/draggable/core.js +379 -0
  364. package/src/utils/draggable/draggable.js +478 -0
  365. package/src/utils/draggable/index.js +61 -0
  366. package/src/utils/draggable/sortable.js +751 -0
  367. package/src/utils/lodash/index.js +2395 -0
  368. package/src/utils/moment/index.js +909 -0
  369. package/src/utils/playground/index.js +37 -0
  370. package/src/utils/popper/index.js +308 -0
  371. package/src/utils/popper/popper.js +1132 -0
  372. package/src/utils/popper/popup-manager.js +288 -0
  373. package/src/utils/popper/popup.js +344 -0
@@ -0,0 +1,790 @@
1
+ <script>
2
+ import { SlotContentProp, FormFieldProp } from './main.vue'
3
+
4
+ import UiFormItem from './form-item.vue'
5
+ import UiInput from '../../input/index.js'
6
+ import UiSelect from '../../select/index.js'
7
+ import { RadioGroup as UiRadioGroup } from '../../radio/index.js'
8
+ import UiIcon from '../../icon/index.js'
9
+ import UiButton from '../../button/index.js'
10
+
11
+ import { get, isArray, isEmpty, isString } from '../../../utils/lodash/index.js'
12
+
13
+ /**
14
+ * 内置表单控件映射
15
+ */
16
+ const COMPONENT_MAP = {
17
+ input: UiInput,
18
+ select: UiSelect,
19
+ radio: UiRadioGroup,
20
+ icon: UiIcon,
21
+ button: UiButton,
22
+ }
23
+
24
+ /**
25
+ * 选项化渲染配置
26
+ */
27
+ const OPTIONAL_RENDER_CONFIG = {
28
+ select: { componentName: 'UiOption' },
29
+ radio: { componentName: 'UiRadio' },
30
+ }
31
+
32
+ const isThenable = (value) => value && typeof value.then === 'function'
33
+
34
+ const nativeHtmlTag = ['div', 'span', 'p', 'i', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'img', 'input', 'button', 'section', 'article', 'header', 'footer', 'nav', 'main', 'aside', 'ul', 'ol', 'li', 'table', 'tr', 'td', 'th', 'thead', 'tbody', 'form', 'label']
35
+ const isNativeTag = type => typeof type === 'string' && nativeHtmlTag.includes(type.toLowerCase())
36
+
37
+ const normalizeOptions = (data) => Array.isArray(data) ? data : []
38
+
39
+ const getFieldDefaultValue = field => field.multiple || (isArray(field.children) && !isEmpty(field.children)) ? [] : undefined
40
+
41
+ export default {
42
+ name: 'UiFormField',
43
+ components: {
44
+ UiFormItem,
45
+ UiFormField: () => import('./form-field.vue'),
46
+ },
47
+ props: {
48
+ /**
49
+ * @type {Array<FormFieldProp>}
50
+ */
51
+ fields: {
52
+ type: Array,
53
+ default: () => [],
54
+ },
55
+ },
56
+ inject: ['uiForm'],
57
+ data() {
58
+ return {
59
+ resolvedOptionsMap: {},
60
+ dynamicOptionsFields: {},
61
+ dynamicOptionRegistrations: {},
62
+ rowUidMap: {},
63
+ }
64
+ },
65
+ computed: {
66
+ /**
67
+ * 获取表单模型数据
68
+ * @returns {Object} 表单绑定的 model 对象
69
+ */
70
+ modelValue() {
71
+ return this.uiForm?.model || {}
72
+ },
73
+ },
74
+ created() {
75
+ this.initOptions()
76
+ },
77
+ watch: {
78
+ fields: {
79
+ handler(newFields) {
80
+ if (!newFields) return
81
+ // 重新解析所有带 options 的字段(包括嵌套)
82
+ this.initOptions()
83
+ },
84
+ deep: true,
85
+ },
86
+ modelValue: {
87
+ handler() {
88
+ this.refreshAllDynamicOptions()
89
+ },
90
+ deep: true, // 深度监听,确保嵌套对象变化也能捕获
91
+ },
92
+ },
93
+ methods: {
94
+ /**
95
+ * 统一解析并存储字段的 options 数据
96
+ * 支持同步数组、异步 Promise 以及返回数组/ Promise 的函数
97
+ * @param {Object} field 字段配置对象
98
+ * @param {String} key 用于存储的唯一 Key,默认为 field.prop
99
+ * @param {Object} [contextData] 上下文数据,包含 rowData 和 modelValue
100
+ */
101
+ resolveAndStoreOptions(field, key, contextData = {}) {
102
+ if (!field || !key) return
103
+
104
+ const rawOptions = field.options
105
+ const isDynamicFunction = typeof rawOptions === 'function'
106
+ const updateState = (data) => this.$set(this.resolvedOptionsMap, key, normalizeOptions(data))
107
+
108
+ // 对函数型 options 进行监听注册
109
+ if (isDynamicFunction) {
110
+ // 使用 key 作为唯一标识
111
+ this.$set(this.dynamicOptionsFields, key, {
112
+ keyPrefix: key,
113
+ fieldConfig: field, // 保存原始配置引用
114
+ })
115
+ } else {
116
+ // 如果不是函数(例如静态数组或 Promise),且之前注册过动态字段,则移除动态监听
117
+ if (this.dynamicOptionsFields[key]) {
118
+ this.$delete(this.dynamicOptionsFields, key)
119
+ }
120
+ }
121
+
122
+ try {
123
+ if (isThenable(rawOptions)) {
124
+ this.handleAsyncOptions(rawOptions, key, updateState)
125
+ return
126
+ }
127
+ if (Array.isArray(rawOptions)) {
128
+ updateState(rawOptions)
129
+ return
130
+ }
131
+ if (isDynamicFunction) {
132
+ const result = rawOptions(contextData.rowData, contextData.modelValue)
133
+ if (isThenable(result)) {
134
+ this.handleAsyncOptions(result, key, updateState)
135
+ } else {
136
+ updateState(result)
137
+ }
138
+ return
139
+ }
140
+ // 其他情况初始化为空数组
141
+ updateState([])
142
+ } catch (error) {
143
+ console.error(`[UiFormField] Error resolving options for key "${key}":`, error)
144
+ updateState([])
145
+ }
146
+ },
147
+
148
+ /**
149
+ * 获取或初始化指定路径的 UID 列表
150
+ * @param {String} path - 数据路径
151
+ * @param {Number} length - 当前数据列表的长度
152
+ */
153
+ getOrCreateUids(path, length) {
154
+ !this.rowUidMap[path] && this.$set(this.rowUidMap, path, [])
155
+ const uids = this.rowUidMap[path]
156
+ // 如果 UID 数量少于数据长度,补充缺失的 UID
157
+ if (uids.length < length) {
158
+ for (let i = uids.length; i < length; i++) {
159
+ uids.push(Math.floor(Date.now() + Math.random() * 1000))
160
+ }
161
+ }
162
+ // 如果 UID 数量多于数据长度(通常发生在删除后未同步,或外部替换了更短的数组),截断
163
+ if (uids.length > length) {
164
+ uids.splice(length)
165
+ }
166
+ return uids
167
+ },
168
+
169
+ /**
170
+ * 计算字段是否可见
171
+ * @param {FormFieldProp} field - 字段配置
172
+ * @param {Object} rowData - 当前行数据或表单数据
173
+ * @returns {Boolean} 是否可见
174
+ */
175
+ isVisible(field, rowData) {
176
+ // 默认可见
177
+ if (field.visible === undefined || field.visible === null) return true
178
+ if (typeof field.visible === 'function') {
179
+ // 传入 rowData 和完整表单模型,供用户进行复杂判断
180
+ return !!field.visible(rowData, this.modelValue)
181
+ }
182
+ return !!field.visible
183
+ },
184
+
185
+ /**
186
+ * 递归解析字段配置树中的 options,支持无限层级嵌套
187
+ * 解析后的 options 会根据路径生成唯一 Key 存入 resolvedOptionsMap
188
+ * @param {Array} fields - 字段配置列表
189
+ * @param {String} [parentKeyPrefix] - 父级 Key 前缀,用于构建唯一 Key,防止嵌套字段 Key 冲突
190
+ */
191
+ parseFieldsForOptions(fields, parentKeyPrefix = '') {
192
+ if (!fields || !fields.length) return
193
+
194
+ fields.forEach((field) => {
195
+ // 处理当前字段的 options
196
+ if (field.options && field.prop) {
197
+ const currentKey = parentKeyPrefix ? `${parentKeyPrefix}__${field.prop}` : field.prop
198
+ if (typeof field.options === 'function') {
199
+ this.$set(this.dynamicOptionRegistrations, currentKey, {
200
+ fieldConfig: field,
201
+ keyPrefix: parentKeyPrefix // 用于后续构建 rowData 路径
202
+ })
203
+ } else {
204
+ // 静态数据,直接解析一次即可,或者也可以存入 map 供统一读取
205
+ this.resolveAndStoreOptions(field, currentKey, {})
206
+ }
207
+ }
208
+ // 处理当前字段 slots 中的 options
209
+ if (field.slots && field.prop) {
210
+ this.parseSlotsForOptions(field.slots, field.prop)
211
+ }
212
+ // 递归处理 children
213
+ if (field.children && field.children.length) {
214
+ const nextPrefix = field.prop ? (parentKeyPrefix ? `${parentKeyPrefix}__${field.prop}` : field.prop) : parentKeyPrefix
215
+ this.parseFieldsForOptions(field.children, nextPrefix)
216
+ }
217
+ })
218
+ },
219
+ /**
220
+ * 根据当前表单状态刷新所有动态 Options
221
+ */
222
+ refreshAllDynamicOptions() {
223
+ const model = this.modelValue
224
+ if (!model || Object.keys(this.dynamicOptionRegistrations).length === 0) return
225
+
226
+ Object.keys(this.dynamicOptionRegistrations).forEach(key => {
227
+ const { fieldConfig, keyPrefix } = this.dynamicOptionRegistrations[key]
228
+
229
+ // 关键:我们需要找到这个 key 对应的具体 rowData
230
+ // keyPrefix 类似于 "users__0" 或 "cycle__1"
231
+ // 我们需要将其转换回数据路径,例如 "users.0"
232
+
233
+ let rowData = model
234
+ if (keyPrefix) {
235
+ // 将 __ 替换为 . 以获取路径
236
+ const path = keyPrefix.replace(/__/g, '.')
237
+ // 使用 get 工具函数获取当前行/对象数据
238
+ rowData = get(model, path) || {}
239
+ }
240
+
241
+ // 重新执行 options 函数,传入最新的 rowData 和 model
242
+ this.resolveAndStoreOptions(fieldConfig, key, {
243
+ rowData: rowData,
244
+ modelValue: model
245
+ })
246
+ })
247
+ },
248
+ /**
249
+ * 选项数据初始化,统一接口
250
+ */
251
+ initOptions() {
252
+ this.dynamicOptionRegistrations = {} // 清空旧注册
253
+ this.parseFieldsForOptions(this.fields)
254
+ // 初始化时也执行一次刷新,确保初始数据正确
255
+ this.refreshAllDynamicOptions()
256
+ },
257
+
258
+ /**
259
+ * 递归解析 slots 配置中的 options
260
+ * @param {Object} slotsConfig - 插槽配置对象 { slotName: config | config[] }
261
+ * @param {String} parentKeyPrefix - 父级 Key 前缀 (通常是 field.prop)
262
+ */
263
+ parseSlotsForOptions(slotsConfig, parentKeyPrefix) {
264
+ if (!slotsConfig || !parentKeyPrefix) return
265
+
266
+ Object.keys(slotsConfig).forEach(slotName => {
267
+ const slotContent = slotsConfig[slotName]
268
+ const configs = Array.isArray(slotContent) ? slotContent : [slotContent]
269
+
270
+ configs.forEach((config, index) => {
271
+ this.parseSlotNodeForOptions(config, `${parentKeyPrefix}__slot__${slotName}__${index}`)
272
+ })
273
+ })
274
+ },
275
+ /**
276
+ * 【新增】递归解析单个 Slot 节点配置
277
+ * @param {Object} config - 节点配置
278
+ * @param {String} currentKeyPrefix - 当前层级的 Key 前缀
279
+ */
280
+ parseSlotNodeForOptions(config, currentKeyPrefix) {
281
+ if (!config || typeof config !== 'object') return
282
+
283
+ // 1. 如果当前节点直接配置了 options,则解析
284
+ // 注意:插槽内的组件通常没有 prop,所以我们直接使用传入的 currentKeyPrefix 作为存储 Key
285
+ if (config.options) {
286
+ this.resolveAndStoreOptions({ options: config.options }, currentKeyPrefix)
287
+ // 将生成的 Key 挂载到配置对象上,供渲染时使用
288
+ // 使用 Object.defineProperty 避免影响 JSON.stringify 或不必要的响应式追踪
289
+ Object.defineProperty(config, '__resolvedOptionsKey__', {
290
+ value: currentKeyPrefix,
291
+ enumerable: false,
292
+ writable: false
293
+ })
294
+ }
295
+
296
+ // 2. 递归处理子节点 (slots.default 或 children)
297
+ // 处理 slots 嵌套
298
+ if (config.slots) {
299
+ // 对于插槽内的嵌套 slots,我们继续追加路径,防止 key 冲突
300
+ Object.keys(config.slots).forEach(subSlotName => {
301
+ const subContent = config.slots[subSlotName]
302
+ const subConfigs = Array.isArray(subContent) ? subContent : [subContent]
303
+ subConfigs.forEach((subConfig, idx) => {
304
+ this.parseSlotNodeForOptions(subConfig, `${currentKeyPrefix}__subSlot__${subSlotName}__${idx}`)
305
+ })
306
+ })
307
+ }
308
+
309
+ // 处理可能的 children 数组 (如果配置结构支持)
310
+ if (config.children && Array.isArray(config.children)) {
311
+ config.children.forEach((child, idx) => {
312
+ this.parseSlotNodeForOptions(child, `${currentKeyPrefix}__child__${idx}`)
313
+ })
314
+ }
315
+ },
316
+
317
+ /**
318
+ * 处理异步 options 数据加载
319
+ * @param {Promise} promise - 返回 options 数据的 Promise 对象
320
+ * @param {String} key - 存储 Key
321
+ * @param {Function} updateState - 状态更新回调函数
322
+ */
323
+ handleAsyncOptions(promise, key, updateState) {
324
+ promise
325
+ .then((res) => updateState(res))
326
+ .catch((err) => {
327
+ console.warn(`[UiFormField] Failed to load options for key "${key}":`, err)
328
+ updateState([])
329
+ })
330
+ },
331
+
332
+ /**
333
+ * 根据类型字符串或组件定义获取对应的组件构造器
334
+ * @param {String | Function | Object} type - 组件类型标识或组件定义
335
+ * @returns {Function | String} 组件构造器或组件名称字符串
336
+ */
337
+ getComponent(type) {
338
+ if (!type) return 'UiInput'
339
+ if (typeof type === 'string') return COMPONENT_MAP[type] || type
340
+ return type
341
+ },
342
+
343
+ /**
344
+ * 获取特定组件类型的可选渲染配置
345
+ * @param {String|Function|Object} type - 组件类型标识
346
+ * @returns {Object|null} 渲染配置对象,如果不存在则返回 null
347
+ */
348
+ getOptionalConfig(type) {
349
+ return typeof type === 'string' ? OPTIONAL_RENDER_CONFIG[type] : null
350
+ },
351
+
352
+ /**
353
+ * 将插槽配置转换为 Vue 的作用域插槽对象
354
+ * @param {Object} slotsConfig - 插槽配置对象
355
+ * @returns {Object} 转换后的作用域插槽对象
356
+ */
357
+ buildScopedSlots(slotsConfig, rowData) {
358
+ if (!slotsConfig) return {}
359
+ const scopedSlots = {}
360
+ Object.keys(slotsConfig).forEach((slotName) => {
361
+ scopedSlots[slotName] = () => this.renderSlotContent(slotsConfig[slotName], rowData)
362
+ })
363
+ return scopedSlots
364
+ },
365
+
366
+ /**
367
+ * 递归渲染插槽内容
368
+ * 支持文本、HTML 字符串以及嵌套组件渲染
369
+ * @param {Object | Array} config - 插槽内容配置
370
+ * @returns {Array<VNode>} 渲染后的 VNode 数组
371
+ */
372
+ renderSlotContent(config, rowData) {
373
+ if (!config) return []
374
+ const configs = Array.isArray(config) ? config : [config]
375
+ return configs.map((item) => this.renderConfigNode(item, rowData)).filter(Boolean)
376
+ },
377
+
378
+ /**
379
+ * 将 options 数据渲染为 VNode 节点列表
380
+ * @param {Array} options - 选项数据数组
381
+ * @param {String} componentName - 选项组件的名称(如 'UiOption')
382
+ * @returns {Array<VNode>} 渲染后的选项 VNode 数组
383
+ */
384
+ renderOptionsAsVNodes(options, componentName) {
385
+ if (!options || !options.length) return []
386
+ const Comp = this.getComponent(componentName)
387
+ return options.map((opt, index) => {
388
+ const props = typeof opt === 'object' && opt !== null ? { ...opt } : { label: opt, value: opt }
389
+ return (
390
+ <Comp
391
+ key={index}
392
+ props={props}
393
+ />
394
+ )
395
+ })
396
+ },
397
+
398
+ /**
399
+ * 渲染复合表单项(支持递归嵌套的多行表单结构)
400
+ * 处理数组类型的字段,提供新增、删除行以及内部字段的递归渲染
401
+ * @param {FormFieldProp} field - 当前复合字段配置
402
+ * @param {Number} index - 当前字段在父级列表中的索引
403
+ * @param {String} [parentPropPath] - 父级属性路径前缀 (例如: "users.0")
404
+ * @param {String} [optionsKeyPrefix] - 父级 Options Key 前缀 (例如: "users__0")
405
+ * @returns {VNode} 渲染后的复合表单项 VNode
406
+ */
407
+ renderComplexField(field, index, parentPropPath = '', optionsKeyPrefix = '') {
408
+ const prop = field.prop
409
+ // 计算当前层级的完整路径前缀
410
+ const currentPathPrefix = parentPropPath ? `${parentPropPath}.${prop}` : prop
411
+ // 计算当前层级的 Options Key 前缀
412
+ const currentOptionsKeyPrefix = optionsKeyPrefix ? `${optionsKeyPrefix}__${prop}` : prop
413
+ // 获取当前字段绑定的数据列表
414
+ let list = get(this.modelValue, currentPathPrefix)
415
+ // 确保是数组
416
+ if (!Array.isArray(list)) {
417
+ list = []
418
+ this.ensureArrayModel(currentPathPrefix)
419
+ list = get(this.modelValue, currentPathPrefix)
420
+ }
421
+
422
+ // 【关键修改】获取当前路径对应的 UID 列表,并确保长度一致
423
+ const uids = this.getOrCreateUids(currentPathPrefix, list.length)
424
+
425
+ // 新增按钮逻辑
426
+ const handleAdd = () => {
427
+ const newItem = {}
428
+
429
+ // 初始化子字段默认值
430
+ if (field.children) {
431
+ field.children.forEach(child => {
432
+ newItem[child.prop] = getFieldDefaultValue(child)
433
+ })
434
+ }
435
+ list.push(newItem)
436
+
437
+ // 生成新 UID
438
+ const newUid = Math.floor(Date.now() + Math.random() * 1000)
439
+ uids.push(newUid)
440
+ this.$set(this.rowUidMap, currentPathPrefix, [...uids])
441
+ }
442
+
443
+ const rows = list.map((item, itemIndex) => {
444
+ // 当前行的路径前缀: e.g., "users.0"
445
+ const rowPathPrefix = `${currentPathPrefix}.${itemIndex}`
446
+ // 渲染该行内的所有子字段
447
+ const childrenNodes = this.renderFieldItems(
448
+ field.children,
449
+ item,
450
+ rowPathPrefix,
451
+ currentOptionsKeyPrefix,
452
+ field.labelWidth
453
+ )
454
+
455
+ // 删除按钮
456
+ const deleteBtn = <UiButton {...{
457
+ class: 'ui-form-complex__delete-btn',
458
+ props: { icon: 'ui-icon-delete', type: 'danger', textual: true, plain: true, },
459
+ on: {
460
+ // 删除时同步移除 UID
461
+ click: () => {
462
+ list.splice(itemIndex, 1)
463
+ uids.splice(itemIndex, 1)
464
+ // 触发响应式更新
465
+ this.$set(this.rowUidMap, currentPathPrefix, [...uids])
466
+ }
467
+ }
468
+ }}/>
469
+
470
+ // 使用 uid 作为 key,如果没有则 fallback 到 index
471
+ const uniqueKey = uids[itemIndex] || itemIndex
472
+
473
+ return <div {...{
474
+ class: 'ui-form-complex--item',
475
+ key: uniqueKey,
476
+ }}>
477
+ <div {...{ class: 'ui-form-complex--item__inner' }}>{childrenNodes}</div>
478
+ <div {...{ class: 'ui-form-complex--item__delete' }}>{deleteBtn}</div>
479
+ </div>
480
+ })
481
+
482
+ // 新增按钮 UI
483
+ const addBtn = <UiButton {...{
484
+ class: 'ui-form-complex__add-btn',
485
+ props: { label: '新增', icon: 'ui-icon-plus', type: 'primary', textual: true, plain: true },
486
+ on: { click: handleAdd }
487
+ }} />
488
+
489
+ // 外层容器 UiFormItem
490
+ const complexKey = optionsKeyPrefix ? `complex-${optionsKeyPrefix}-${index}` : `complex-${prop}-${index}`
491
+
492
+ return <UiFormItem {...{
493
+ class: 'ui-form-complex--field',
494
+ key: complexKey,
495
+ props: {
496
+ label: field.label,
497
+ prop: currentPathPrefix,
498
+ rules: field.rules,
499
+ labelWidth: field.labelWidth,
500
+ error: field.error,
501
+ showMessage: field.showMessage,
502
+ }
503
+ }}>
504
+ <div {...{ slot: 'label', class: 'ui-form-complex--field__add' }}>
505
+ <div {...{ class: 'field-label' }}>{field.label}</div>
506
+ <div>{addBtn}</div>
507
+ </div>
508
+ <div {...{ class: 'ui-form-complex--field__inner' }}>{rows}</div>
509
+ </UiFormItem>
510
+ },
511
+
512
+ /**
513
+ * 辅助方法:确保模型中指定路径的值为数组
514
+ * 如果路径中间节点不存在,会自动创建空对象;如果终点节点不存在,会初始化为空数组
515
+ * @param {String} path - 目标路径,以点号分隔 (e.g., "a.b.c")
516
+ */
517
+ ensureArrayModel(path) {
518
+ const keys = path.split('.')
519
+ let current = this.modelValue
520
+ for (let i = 0; i < keys.length; i++) {
521
+ const key = keys[i]
522
+ if (i === keys.length - 1) {
523
+ // 最后一层,如果是 undefined 则初始化为数组
524
+ if (current[key] === undefined || current[key] === null) {
525
+ this.$set(current, key, [])
526
+ }
527
+ } else {
528
+ // 中间层,如果是 undefined 则初始化为对象
529
+ if (current[key] === undefined || current[key] === null) {
530
+ this.$set(current, key, {})
531
+ }
532
+ current = current[key]
533
+ }
534
+ }
535
+ },
536
+
537
+ /**
538
+ * 辅助方法:渲染一组字段项(支持递归处理复合字段)
539
+ * @param {Array} fields - 字段配置列表
540
+ * @param {Object} rowData - 当前行的数据对象引用
541
+ * @param {String} rowPathPrefix - 当前行的数据路径前缀 (e.g., "users.0")
542
+ * @param {String} optionsKeyPrefix - 当前层级的 Options Key 前缀 (e.g., "users")
543
+ * @param {String|Number} parentLabelWidth - 父级 labelWidth,用于继承
544
+ * @returns {Array<VNode>} 渲染后的表单项 VNode 数组
545
+ */
546
+ renderFieldItems(fields, rowData, rowPathPrefix, optionsKeyPrefix, parentLabelWidth) {
547
+ if (!fields || !fields.length) return []
548
+
549
+ return fields.map((childField, index) => {
550
+ // 1. 判断可见性
551
+ // 对于嵌套字段,rowData 是当前行的数据对象
552
+ if (!this.isVisible(childField, rowData)) {
553
+ return null
554
+ }
555
+ // 如果子字段本身也是复合字段 (有 children),递归调用 renderComplexField
556
+ if (childField.children && childField.children.length) {
557
+ return this.renderComplexField(
558
+ childField,
559
+ index,
560
+ rowPathPrefix, // 父级数据路径
561
+ optionsKeyPrefix // 父级 options key 前缀
562
+ )
563
+ }
564
+
565
+ // 普通字段渲染
566
+ const nestedProp = `${rowPathPrefix}.${childField.prop}`
567
+ if (childField.options && typeof childField.options === 'function') {
568
+ const currentOptionsKey = optionsKeyPrefix ? `${optionsKeyPrefix}__${childField.prop}` : childField.prop
569
+ this.resolveAndStoreOptions(
570
+ childField,
571
+ currentOptionsKey,
572
+ { rowData: rowData, modelValue: this.modelValue }
573
+ )
574
+ }
575
+ // 如果 itemRender 返回空(虽然目前不会,但以防万一),则不渲染
576
+ const dynamicComponent = this.itemRender(childField, rowData, optionsKeyPrefix)
577
+ if (!dynamicComponent) return null
578
+
579
+ return <UiFormItem {...{
580
+ key: nestedProp, // 使用完整路径作为 key 更稳定
581
+ props: {
582
+ label: childField.label,
583
+ prop: nestedProp,
584
+ rules: childField.rules,
585
+ showMessage: childField.showMessage,
586
+ labelWidth: childField.labelWidth || parentLabelWidth,
587
+ },
588
+ }}>{ dynamicComponent }</UiFormItem>
589
+ }).filter(Boolean) // 过滤掉 null
590
+ },
591
+
592
+ /**
593
+ * 通用节点渲染器
594
+ * 根据配置对象渲染单个 VNode,支持文本、HTML、原生标签、组件
595
+ * @param {SlotContentProp | String} config - 配置对象或字符串
596
+ * @param {Object} rowData - 当前行数据(用于动态属性计算,如果需要)
597
+ * @returns {VNode | null}
598
+ */
599
+ renderConfigNode(config, rowData = {}) {
600
+ if (!config) return null
601
+
602
+ if (typeof config === 'string') {
603
+ return config
604
+ }
605
+
606
+ if (typeof config === 'object') {
607
+ const tagName = config.type || config.component
608
+ const Comp = this.getComponent(tagName)
609
+ const isNative = isNativeTag(Comp)
610
+ if (Comp) {
611
+ // 构建属性
612
+ const componentData = {
613
+ props: config.props || {},
614
+ on: config.on || {},
615
+ style: config.style || {},
616
+ class: config.class || {},
617
+ }
618
+
619
+ // 递归渲染子插槽/子内容
620
+ const children = []
621
+ if (config.text) {
622
+ children.push(config.text)
623
+ }
624
+ // 从 slots.default 获取子内容
625
+ if (config.slots && config.slots.default) {
626
+ const subConfigs = Array.isArray(config.slots.default) ? config.slots.default : [config.slots.default]
627
+ children.push(...subConfigs.map(c => this.renderConfigNode(c, rowData)).filter(Boolean))
628
+ }
629
+
630
+ // 注入 Options
631
+ const optionalConfig = this.getOptionalConfig(tagName)
632
+ const hasOptionsCapability = !!optionalConfig // 例如 select 有 UiOption 配置
633
+ if (hasOptionsCapability) {
634
+ let optionsToRender = []
635
+ if (config.__resolvedOptionsKey__) {
636
+ optionsToRender = this.resolvedOptionsMap[config.__resolvedOptionsKey__] || []
637
+ } else if (config.options) {
638
+ optionsToRender = normalizeOptions(config.options)
639
+ }
640
+ if (optionsToRender.length > 0) {
641
+ if (optionalConfig?.componentName) {
642
+ const optionNodes = this.renderOptionsAsVNodes(optionsToRender, optionalConfig.componentName)
643
+ children.unshift(...optionNodes)
644
+ }
645
+ }
646
+ }
647
+
648
+ if (isNative) {
649
+ // 原生标签
650
+ return <Comp {...componentData}>{children}</Comp>
651
+ } else {
652
+ // 自定义组件
653
+ const scopedSlots = config.slots ? this.buildScopedSlotsFromConfig(config.slots, rowData) : {}
654
+ return <Comp {...componentData} scopedSlots={scopedSlots}>{children}</Comp>
655
+ }
656
+ }
657
+ }
658
+ return null
659
+ },
660
+ /**
661
+ * 构建插槽
662
+ */
663
+ buildScopedSlotsFromConfig(slotsConfig, rowData) {
664
+ if (!slotsConfig) return {}
665
+ const scopedSlots = {}
666
+ // 排除 default 插槽,这在 renderConfigNode 方法中已经处理
667
+ Object.keys(slotsConfig).filter(sn => sn !== 'default').forEach((slotName) => {
668
+ scopedSlots[slotName] = () => {
669
+ const content = slotsConfig[slotName]
670
+ const configs = Array.isArray(content) ? content : [content]
671
+ return configs.map(c => this.renderConfigNode(c, rowData)).filter(Boolean)
672
+ }
673
+ })
674
+ return scopedSlots
675
+ },
676
+
677
+ /**
678
+ * 渲染单个表单项的配置生成器
679
+ * 根据字段配置生成对应的组件定义和数据绑定对象,支持处理带有动态 options 的组件(如 Select)。
680
+ * @param {FormFieldProp} field - 当前字段的配置对象
681
+ * @param {Object} rowData - 当前行或表单的数据对象,用于获取字段值
682
+ * @param {String} [optionsKeyPrefix=''] - 选项数据的关键字前缀,用于在 resolvedOptionsMap 中查找对应的 options 数据
683
+ * @returns {{ ComponentDef: Function|String, componentData: Object }} 返回包含组件定义和组件数据配置的对象
684
+ */
685
+ itemRender(field, rowData, optionsKeyPrefix = '') {
686
+ const componentType = field.type
687
+ const ComponentDef = this.getComponent(componentType)
688
+ const optionalConfig = this.getOptionalConfig(componentType)
689
+
690
+ // 计算 disabled 状态
691
+ // 支持 boolean 或 function(rowData) => boolean
692
+ let isDisabled = this.uiForm?.disabled // 全局表单禁用优先
693
+ if (!isDisabled && field.disabled != null) {
694
+ if (typeof field.disabled === 'function') {
695
+ // 如果是函数,传入当前行数据计算
696
+ isDisabled = !!field.disabled(rowData, this.modelValue)
697
+ } else {
698
+ // 如果是布尔值,直接使用
699
+ isDisabled = !!field.disabled
700
+ }
701
+ }
702
+
703
+ if (isNativeTag(ComponentDef)) {
704
+ // 构造一个标准的 SlotContentProp 对象
705
+ const nativeConfig = {
706
+ component: componentType,
707
+ props: field.props || {},
708
+ on: field.on || {},
709
+ style: field.style || {},
710
+ class: field.class || {},
711
+ text: field.text,
712
+ slots: {},
713
+ }
714
+
715
+ // 处理 context,将内容作为 default 插槽传入
716
+ if (field.context) {
717
+ if (typeof field.context === 'string') {
718
+ nativeConfig.slots.default = field.context
719
+ } else if (Array.isArray(field.context)) {
720
+ nativeConfig.slots.default = field.context.map(item => typeof item === 'string' ? { text: item } : item)
721
+ }
722
+ }
723
+
724
+ // 使用通用节点渲染器
725
+ return this.renderConfigNode(nativeConfig, rowData, optionsKeyPrefix)
726
+ }
727
+
728
+ const componentData = {
729
+ props: {
730
+ value: rowData[field.prop],
731
+ ...(field.props || {}),
732
+ disabled: isDisabled,
733
+ },
734
+ on: {
735
+ ...(field.on || {}),
736
+ input: (val) => {
737
+ this.$set(rowData, field.prop, val)
738
+ if (typeof field.on?.input === 'function') {
739
+ field.on.input(val)
740
+ }
741
+ },
742
+ },
743
+ scopedSlots: this.buildScopedSlots(field.slots, rowData),
744
+ }
745
+
746
+ if (optionalConfig?.componentName && field.options != null) {
747
+ const optionsKey = optionsKeyPrefix ? `${optionsKeyPrefix}__${field.prop}` : field.prop
748
+ const currentOptions = this.resolvedOptionsMap[optionsKey] || []
749
+ const originalDefaultSlot = componentData.scopedSlots.default
750
+
751
+ componentData.scopedSlots.default = () => {
752
+ const autoNodes = this.renderOptionsAsVNodes(currentOptions, optionalConfig.componentName)
753
+ const userNodes = originalDefaultSlot ? originalDefaultSlot() : []
754
+ return [...autoNodes, ...userNodes]
755
+ }
756
+ }
757
+
758
+ return <ComponentDef { ...componentData }/>
759
+ }
760
+ },
761
+ render(h) {
762
+ if (!this.fields || !this.fields.length) return null
763
+
764
+ const fieldNodes = this.fields.map((field, index) => {
765
+ // 1. 判断可见性:顶级字段的 rowData 即为表单模型 this.modelValue
766
+ if (!this.isVisible(field, this.modelValue)) return null
767
+ // 嵌套/复合表单 Children
768
+ if (field.children && field.children.length) return this.renderComplexField(field, index)
769
+
770
+ // 单一表单项
771
+ const dynamicComponent = this.itemRender(field, this.modelValue)
772
+ if (!dynamicComponent) return null
773
+
774
+ return h('ui-form-item', {
775
+ key: field.prop || index,
776
+ props: {
777
+ label: field.label,
778
+ prop: field.prop,
779
+ rules: field.rules,
780
+ labelWidth: field.labelWidth,
781
+ error: field.error,
782
+ showMessage: field.showMessage,
783
+ },
784
+ }, [dynamicComponent])
785
+ }).filter(Boolean) // 过滤掉 null
786
+
787
+ return h('div', { class: 'ui-form-field' }, fieldNodes)
788
+ },
789
+ }
790
+ </script>