@robot-admin/naive-ui-components 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +257 -0
- package/dist/C_ActionBar-DWN-woTc.css.map +1 -0
- package/dist/C_ActionBar.cjs +5 -0
- package/dist/C_ActionBar.d.cts +2 -0
- package/dist/C_ActionBar.d.ts +2 -0
- package/dist/C_ActionBar.js +4 -0
- package/dist/C_ActionBar2.js +196 -0
- package/dist/C_ActionBar2.js.map +1 -0
- package/dist/C_AntV-AFKyK6hH.css.map +1 -0
- package/dist/C_AntV.cjs +8 -0
- package/dist/C_AntV.d.cts +2 -0
- package/dist/C_AntV.d.ts +2 -0
- package/dist/C_AntV.js +4 -0
- package/dist/C_AntV2.js +3150 -0
- package/dist/C_AntV2.js.map +1 -0
- package/dist/C_Barcode-P_EFj8dC.css.map +1 -0
- package/dist/C_Barcode.cjs +4 -0
- package/dist/C_Barcode.d.cts +2 -0
- package/dist/C_Barcode.d.ts +2 -0
- package/dist/C_Barcode.js +3 -0
- package/dist/C_Barcode2.js +68 -0
- package/dist/C_Barcode2.js.map +1 -0
- package/dist/C_Captcha-C-ef41xw.css.map +1 -0
- package/dist/C_Captcha.cjs +4 -0
- package/dist/C_Captcha.d.cts +2 -0
- package/dist/C_Captcha.d.ts +2 -0
- package/dist/C_Captcha.js +3 -0
- package/dist/C_Captcha2.js +155 -0
- package/dist/C_Captcha2.js.map +1 -0
- package/dist/C_Cascade-D9kNsjsV.css.map +1 -0
- package/dist/C_Cascade.cjs +4 -0
- package/dist/C_Cascade.d.cts +2 -0
- package/dist/C_Cascade.d.ts +2 -0
- package/dist/C_Cascade.js +3 -0
- package/dist/C_Cascade2.js +103 -0
- package/dist/C_Cascade2.js.map +1 -0
- package/dist/C_City-BCQ4ipiK.css.map +1 -0
- package/dist/C_City.cjs +4 -0
- package/dist/C_City.d.cts +2 -0
- package/dist/C_City.d.ts +2 -0
- package/dist/C_City.js +3 -0
- package/dist/C_City2.js +841 -0
- package/dist/C_City2.js.map +1 -0
- package/dist/C_Code-C9kvvEmO.css.map +1 -0
- package/dist/C_Code.cjs +5 -0
- package/dist/C_Code.d.cts +2 -0
- package/dist/C_Code.d.ts +2 -0
- package/dist/C_Code.js +4 -0
- package/dist/C_Code2.js +346 -0
- package/dist/C_Code2.js.map +1 -0
- package/dist/C_CollapsePanel-BUJHuYcU.css.map +1 -0
- package/dist/C_CollapsePanel.cjs +6 -0
- package/dist/C_CollapsePanel.d.cts +2 -0
- package/dist/C_CollapsePanel.d.ts +2 -0
- package/dist/C_CollapsePanel.js +4 -0
- package/dist/C_CollapsePanel2.js +319 -0
- package/dist/C_CollapsePanel2.js.map +1 -0
- package/dist/C_Cron-yx2Ob4Jl.css.map +1 -0
- package/dist/C_Cron.cjs +15 -0
- package/dist/C_Cron.d.cts +2 -0
- package/dist/C_Cron.d.ts +2 -0
- package/dist/C_Cron.js +4 -0
- package/dist/C_Cron2.js +1209 -0
- package/dist/C_Cron2.js.map +1 -0
- package/dist/C_Date.cjs +4 -0
- package/dist/C_Date.d.cts +2 -0
- package/dist/C_Date.d.ts +2 -0
- package/dist/C_Date.js +3 -0
- package/dist/C_Date2.js +219 -0
- package/dist/C_Date2.js.map +1 -0
- package/dist/C_Draggable-C483syRC.css.map +1 -0
- package/dist/C_Draggable.cjs +5 -0
- package/dist/C_Draggable.d.cts +2 -0
- package/dist/C_Draggable.d.ts +2 -0
- package/dist/C_Draggable.js +3 -0
- package/dist/C_Draggable2.js +295 -0
- package/dist/C_Draggable2.js.map +1 -0
- package/dist/C_Editor-Bp0SyIEw.css.map +1 -0
- package/dist/C_Editor.cjs +4 -0
- package/dist/C_Editor.d.cts +2 -0
- package/dist/C_Editor.d.ts +2 -0
- package/dist/C_Editor.js +3 -0
- package/dist/C_Editor2.js +160 -0
- package/dist/C_Editor2.js.map +1 -0
- package/dist/C_FilePreview-CPqvhoCy.css.map +1 -0
- package/dist/C_FilePreview.cjs +6 -0
- package/dist/C_FilePreview.d.cts +2 -0
- package/dist/C_FilePreview.d.ts +2 -0
- package/dist/C_FilePreview.js +3 -0
- package/dist/C_FilePreview2.js +1031 -0
- package/dist/C_FilePreview2.js.map +1 -0
- package/dist/C_Form-Jx7PY3sT.css.map +1 -0
- package/dist/C_Form.cjs +15 -0
- package/dist/C_Form.d.cts +2 -0
- package/dist/C_Form.d.ts +2 -0
- package/dist/C_Form.js +4 -0
- package/dist/C_Form2.js +2510 -0
- package/dist/C_Form2.js.map +1 -0
- package/dist/C_FormSearch-DvRgxlRn.css.map +1 -0
- package/dist/C_FormSearch.cjs +6 -0
- package/dist/C_FormSearch.d.cts +2 -0
- package/dist/C_FormSearch.d.ts +2 -0
- package/dist/C_FormSearch.js +3 -0
- package/dist/C_FormSearch2.js +356 -0
- package/dist/C_FormSearch2.js.map +1 -0
- package/dist/C_FormulaEditor-DtGkt4T_.css.map +1 -0
- package/dist/C_FormulaEditor.cjs +13 -0
- package/dist/C_FormulaEditor.d.cts +2 -0
- package/dist/C_FormulaEditor.d.ts +2 -0
- package/dist/C_FormulaEditor.js +4 -0
- package/dist/C_FormulaEditor2.js +1433 -0
- package/dist/C_FormulaEditor2.js.map +1 -0
- package/dist/C_FullCalendar-BF7H0YIx.css.map +1 -0
- package/dist/C_FullCalendar.cjs +9 -0
- package/dist/C_FullCalendar.d.cts +2 -0
- package/dist/C_FullCalendar.d.ts +2 -0
- package/dist/C_FullCalendar.js +3 -0
- package/dist/C_FullCalendar2.js +377 -0
- package/dist/C_FullCalendar2.js.map +1 -0
- package/dist/C_Guide.cjs +4 -0
- package/dist/C_Guide.d.cts +2 -0
- package/dist/C_Guide.d.ts +2 -0
- package/dist/C_Guide.js +3 -0
- package/dist/C_Guide2.js +58 -0
- package/dist/C_Guide2.js.map +1 -0
- package/dist/C_Icon.cjs +4 -0
- package/dist/C_Icon.d.cts +2 -0
- package/dist/C_Icon.d.ts +2 -0
- package/dist/C_Icon.js +3 -0
- package/dist/C_Icon2.js +286 -0
- package/dist/C_Icon2.js.map +1 -0
- package/dist/C_ImageCropper-BVJfUufl.css.map +1 -0
- package/dist/C_ImageCropper.cjs +6 -0
- package/dist/C_ImageCropper.d.cts +2 -0
- package/dist/C_ImageCropper.d.ts +2 -0
- package/dist/C_ImageCropper.js +4 -0
- package/dist/C_ImageCropper2.js +723 -0
- package/dist/C_ImageCropper2.js.map +1 -0
- package/dist/C_Language.cjs +4 -0
- package/dist/C_Language.d.cts +2 -0
- package/dist/C_Language.d.ts +2 -0
- package/dist/C_Language.js +3 -0
- package/dist/C_Language2.js +72 -0
- package/dist/C_Language2.js.map +1 -0
- package/dist/C_Map-DpzeuWdX.css.map +1 -0
- package/dist/C_Map.cjs +7 -0
- package/dist/C_Map.d.cts +2 -0
- package/dist/C_Map.d.ts +2 -0
- package/dist/C_Map.js +3 -0
- package/dist/C_Map2.js +199 -0
- package/dist/C_Map2.js.map +1 -0
- package/dist/C_Markdown-BEjxknqd.css.map +1 -0
- package/dist/C_Markdown.cjs +4 -0
- package/dist/C_Markdown.d.cts +2 -0
- package/dist/C_Markdown.d.ts +2 -0
- package/dist/C_Markdown.js +3 -0
- package/dist/C_Markdown2.js +186 -0
- package/dist/C_Markdown2.js.map +1 -0
- package/dist/C_NotificationCenter-0l3TY2Gn.css.map +1 -0
- package/dist/C_NotificationCenter.cjs +20 -0
- package/dist/C_NotificationCenter.d.cts +2 -0
- package/dist/C_NotificationCenter.d.ts +2 -0
- package/dist/C_NotificationCenter.js +4 -0
- package/dist/C_NotificationCenter2.js +1383 -0
- package/dist/C_NotificationCenter2.js.map +1 -0
- package/dist/C_Progress.cjs +4 -0
- package/dist/C_Progress.d.cts +2 -0
- package/dist/C_Progress.d.ts +2 -0
- package/dist/C_Progress.js +3 -0
- package/dist/C_Progress2.js +103 -0
- package/dist/C_Progress2.js.map +1 -0
- package/dist/C_QRCode-DbdiAIPg.css.map +1 -0
- package/dist/C_QRCode.cjs +5 -0
- package/dist/C_QRCode.d.cts +2 -0
- package/dist/C_QRCode.d.ts +2 -0
- package/dist/C_QRCode.js +3 -0
- package/dist/C_QRCode2.js +218 -0
- package/dist/C_QRCode2.js.map +1 -0
- package/dist/C_Signature-zhHCbra9.css.map +1 -0
- package/dist/C_Signature.cjs +8 -0
- package/dist/C_Signature.d.cts +2 -0
- package/dist/C_Signature.d.ts +2 -0
- package/dist/C_Signature.js +4 -0
- package/dist/C_Signature2.js +618 -0
- package/dist/C_Signature2.js.map +1 -0
- package/dist/C_SplitPane-C6sBsfKY.css.map +1 -0
- package/dist/C_SplitPane.cjs +6 -0
- package/dist/C_SplitPane.d.cts +2 -0
- package/dist/C_SplitPane.d.ts +2 -0
- package/dist/C_SplitPane.js +4 -0
- package/dist/C_SplitPane2.js +356 -0
- package/dist/C_SplitPane2.js.map +1 -0
- package/dist/C_Steps-CODHN5Hs.css.map +1 -0
- package/dist/C_Steps.cjs +4 -0
- package/dist/C_Steps.d.cts +2 -0
- package/dist/C_Steps.d.ts +2 -0
- package/dist/C_Steps.js +3 -0
- package/dist/C_Steps2.js +82 -0
- package/dist/C_Steps2.js.map +1 -0
- package/dist/C_Table-DSNsntmT.css.map +1 -0
- package/dist/C_Table.cjs +19 -0
- package/dist/C_Table.d.cts +2 -0
- package/dist/C_Table.d.ts +2 -0
- package/dist/C_Table.js +5 -0
- package/dist/C_Table2.js +3009 -0
- package/dist/C_Table2.js.map +1 -0
- package/dist/C_Theme.cjs +4 -0
- package/dist/C_Theme.d.cts +2 -0
- package/dist/C_Theme.d.ts +2 -0
- package/dist/C_Theme.js +3 -0
- package/dist/C_Theme2.js +60 -0
- package/dist/C_Theme2.js.map +1 -0
- package/dist/C_Time-BvZLYraL.css.map +1 -0
- package/dist/C_Time.cjs +5 -0
- package/dist/C_Time.d.cts +2 -0
- package/dist/C_Time.d.ts +2 -0
- package/dist/C_Time.js +3 -0
- package/dist/C_Time2.js +199 -0
- package/dist/C_Time2.js.map +1 -0
- package/dist/C_Tree-0GDv--jX.css.map +1 -0
- package/dist/C_Tree.cjs +7 -0
- package/dist/C_Tree.d.cts +2 -0
- package/dist/C_Tree.d.ts +2 -0
- package/dist/C_Tree.js +4 -0
- package/dist/C_Tree2.js +441 -0
- package/dist/C_Tree2.js.map +1 -0
- package/dist/C_Upload-BXd3YYLx.css.map +1 -0
- package/dist/C_Upload.cjs +12 -0
- package/dist/C_Upload.d.cts +2 -0
- package/dist/C_Upload.d.ts +2 -0
- package/dist/C_Upload.js +4 -0
- package/dist/C_Upload2.js +1388 -0
- package/dist/C_Upload2.js.map +1 -0
- package/dist/C_VideoPlayer-DYG3RL0Q.css.map +1 -0
- package/dist/C_VideoPlayer.cjs +23 -0
- package/dist/C_VideoPlayer.d.cts +2 -0
- package/dist/C_VideoPlayer.d.ts +2 -0
- package/dist/C_VideoPlayer.js +3 -0
- package/dist/C_VideoPlayer2.js +1932 -0
- package/dist/C_VideoPlayer2.js.map +1 -0
- package/dist/C_VtableGantt-fhItIiHE.css.map +1 -0
- package/dist/C_VtableGantt.cjs +6 -0
- package/dist/C_VtableGantt.d.cts +2 -0
- package/dist/C_VtableGantt.d.ts +2 -0
- package/dist/C_VtableGantt.js +4 -0
- package/dist/C_VtableGantt2.js +873 -0
- package/dist/C_VtableGantt2.js.map +1 -0
- package/dist/C_WaterFall-8sQDFXKg.css.map +1 -0
- package/dist/C_WaterFall.cjs +13 -0
- package/dist/C_WaterFall.d.cts +2 -0
- package/dist/C_WaterFall.d.ts +2 -0
- package/dist/C_WaterFall.js +3 -0
- package/dist/C_WaterFall2.js +365 -0
- package/dist/C_WaterFall2.js.map +1 -0
- package/dist/C_WorkFlow-J-dyIuh9.css.map +1 -0
- package/dist/C_WorkFlow.cjs +8 -0
- package/dist/C_WorkFlow.d.cts +2 -0
- package/dist/C_WorkFlow.d.ts +2 -0
- package/dist/C_WorkFlow.js +4 -0
- package/dist/C_WorkFlow2.js +1984 -0
- package/dist/C_WorkFlow2.js.map +1 -0
- package/dist/chunk.js +22 -0
- package/dist/city.js +4817 -0
- package/dist/city.js.map +1 -0
- package/dist/constants.d.ts +273 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants2.d.ts +178 -0
- package/dist/constants2.d.ts.map +1 -0
- package/dist/constants3.d.ts +475 -0
- package/dist/constants3.d.ts.map +1 -0
- package/dist/constants4.d.ts +430 -0
- package/dist/constants4.d.ts.map +1 -0
- package/dist/constants5.d.ts +4283 -0
- package/dist/constants5.d.ts.map +1 -0
- package/dist/data.d.ts +67 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/export-helper.js +9 -0
- package/dist/index.cjs +409 -0
- package/dist/index.d.cts +96 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +230 -0
- package/dist/index.js.map +1 -0
- package/dist/index.vue.d.ts +80 -0
- package/dist/index.vue.d.ts.map +1 -0
- package/dist/index10.vue.d.ts +72 -0
- package/dist/index10.vue.d.ts.map +1 -0
- package/dist/index11.vue.d.ts +26 -0
- package/dist/index11.vue.d.ts.map +1 -0
- package/dist/index12.vue.d.ts +81 -0
- package/dist/index12.vue.d.ts.map +1 -0
- package/dist/index13.vue.d.ts +55 -0
- package/dist/index13.vue.d.ts.map +1 -0
- package/dist/index14.vue.d.ts +33 -0
- package/dist/index14.vue.d.ts.map +1 -0
- package/dist/index15.vue.d.ts +18 -0
- package/dist/index15.vue.d.ts.map +1 -0
- package/dist/index16.vue.d.ts +662 -0
- package/dist/index16.vue.d.ts.map +1 -0
- package/dist/index2.vue.d.ts +38 -0
- package/dist/index2.vue.d.ts.map +1 -0
- package/dist/index3.vue.d.ts +45 -0
- package/dist/index3.vue.d.ts.map +1 -0
- package/dist/index4.vue.d.ts +31 -0
- package/dist/index4.vue.d.ts.map +1 -0
- package/dist/index5.vue.d.ts +35 -0
- package/dist/index5.vue.d.ts.map +1 -0
- package/dist/index6.vue.d.ts +48 -0
- package/dist/index6.vue.d.ts.map +1 -0
- package/dist/index7.vue.d.ts +56 -0
- package/dist/index7.vue.d.ts.map +1 -0
- package/dist/index8.vue.d.ts +41 -0
- package/dist/index8.vue.d.ts.map +1 -0
- package/dist/index9.vue.d.ts +30 -0
- package/dist/index9.vue.d.ts.map +1 -0
- package/dist/storage.js +31 -0
- package/dist/storage.js.map +1 -0
- package/dist/style.css +7725 -0
- package/dist/useCalendarEvents.d.ts +148 -0
- package/dist/useCalendarEvents.d.ts.map +1 -0
- package/dist/useCollapsePanel.d.ts +132 -0
- package/dist/useCollapsePanel.d.ts.map +1 -0
- package/dist/useCropperCore.d.ts +102 -0
- package/dist/useCropperCore.d.ts.map +1 -0
- package/dist/useDraggableLayout.d.ts +194 -0
- package/dist/useDraggableLayout.d.ts.map +1 -0
- package/dist/useDynamicFormState.d.ts +4248 -0
- package/dist/useDynamicFormState.d.ts.map +1 -0
- package/dist/useEdgeInteraction.d.ts +7614 -0
- package/dist/useEdgeInteraction.d.ts.map +1 -0
- package/dist/useFullscreen.d.ts +166 -0
- package/dist/useFullscreen.d.ts.map +1 -0
- package/dist/useInfiniteScroll.d.ts +169 -0
- package/dist/useInfiniteScroll.d.ts.map +1 -0
- package/dist/useModalEdit.d.ts +960 -0
- package/dist/useModalEdit.d.ts.map +1 -0
- package/dist/useQRCode.d.ts +87 -0
- package/dist/useQRCode.d.ts.map +1 -0
- package/dist/useSearchState.d.ts +180 -0
- package/dist/useSearchState.d.ts.map +1 -0
- package/dist/useSignatureHistory.d.ts +189 -0
- package/dist/useSignatureHistory.d.ts.map +1 -0
- package/dist/useSplitResize.d.ts +158 -0
- package/dist/useSplitResize.d.ts.map +1 -0
- package/dist/useTimeSelection.d.ts +105 -0
- package/dist/useTimeSelection.d.ts.map +1 -0
- package/dist/useTreeOperations.d.ts +183 -0
- package/dist/useTreeOperations.d.ts.map +1 -0
- package/dist/useWorkflowValidation.d.ts +1052 -0
- package/dist/useWorkflowValidation.d.ts.map +1 -0
- package/package.json +342 -0
package/dist/C_Form2.js
ADDED
|
@@ -0,0 +1,2510 @@
|
|
|
1
|
+
import { t as C_Icon_default } from "./C_Icon2.js";
|
|
2
|
+
import { t as export_helper_default } from "./export-helper.js";
|
|
3
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, getCurrentInstance, h, inject, mergeProps, nextTick, normalizeClass, normalizeStyle, onMounted, openBlock, reactive, readonly, ref, renderList, renderSlot, resolveComponent, resolveDynamicComponent, toDisplayString, toRaw, unref, vShow, watch, watchEffect, withCtx, withDirectives, withKeys } from "vue";
|
|
4
|
+
import { NButton, NForm, NFormItem, NSpace } from "naive-ui";
|
|
5
|
+
import { mergeRules } from "@robot-admin/form-validate";
|
|
6
|
+
|
|
7
|
+
//#region src/components/C_Form/composables/useFormConfig.ts
|
|
8
|
+
const FORM_DEFAULTS = {
|
|
9
|
+
layout: "default",
|
|
10
|
+
labelPlacement: "left",
|
|
11
|
+
labelWidth: "auto",
|
|
12
|
+
size: "medium",
|
|
13
|
+
disabled: false,
|
|
14
|
+
readonly: false,
|
|
15
|
+
showActions: true,
|
|
16
|
+
validateOnChange: false
|
|
17
|
+
};
|
|
18
|
+
/** 拥有自身控制按钮的布局(不显示默认操作按钮) */
|
|
19
|
+
const LAYOUTS_WITH_OWN_CONTROLS = ["steps", "custom"];
|
|
20
|
+
/**
|
|
21
|
+
* 解析 FormConfig,合并默认值
|
|
22
|
+
* @param config - 用户传入的配置(可选)
|
|
23
|
+
* @returns 具有完整默认值的 ResolvedFormConfig
|
|
24
|
+
*/
|
|
25
|
+
function resolveFormConfig(config) {
|
|
26
|
+
return {
|
|
27
|
+
...FORM_DEFAULTS,
|
|
28
|
+
...config
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 计算是否显示默认操作按钮
|
|
33
|
+
* @param resolved - 已解析的配置
|
|
34
|
+
*/
|
|
35
|
+
function shouldShowActions(resolved) {
|
|
36
|
+
if (resolved.showActions === false) return false;
|
|
37
|
+
if (LAYOUTS_WITH_OWN_CONTROLS.includes(resolved.layout)) return false;
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/components/C_Form/composables/useFormState.ts
|
|
43
|
+
/** 各控件类型的默认空值 */
|
|
44
|
+
const DEFAULT_VALUES = {
|
|
45
|
+
input: "",
|
|
46
|
+
textarea: "",
|
|
47
|
+
editor: "",
|
|
48
|
+
select: null,
|
|
49
|
+
datePicker: null,
|
|
50
|
+
daterange: null,
|
|
51
|
+
timePicker: null,
|
|
52
|
+
cascader: null,
|
|
53
|
+
colorPicker: null,
|
|
54
|
+
checkbox: null,
|
|
55
|
+
upload: [],
|
|
56
|
+
radio: "",
|
|
57
|
+
inputNumber: null,
|
|
58
|
+
slider: null,
|
|
59
|
+
rate: null,
|
|
60
|
+
switch: null
|
|
61
|
+
};
|
|
62
|
+
const getDefaultValue = (type) => {
|
|
63
|
+
return DEFAULT_VALUES[type] ?? null;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* C_Form 状态引擎 — 管理表单数据模型、校验规则、验证 API、字段操作
|
|
67
|
+
* @param options - 表单配置项(响应式)
|
|
68
|
+
* @param config - 解析后的表单配置(响应式)
|
|
69
|
+
* @param formRef - NForm 实例引用
|
|
70
|
+
* @param emit - 组件事件发射器
|
|
71
|
+
*/
|
|
72
|
+
function useFormState(options, config, formRef, emit) {
|
|
73
|
+
const formModel = reactive({});
|
|
74
|
+
const formRules = reactive({});
|
|
75
|
+
const visibleOptions = computed(() => options.value.filter((item) => item.show !== false));
|
|
76
|
+
const initialize = () => {
|
|
77
|
+
try {
|
|
78
|
+
Object.keys(formRules).forEach((key) => delete formRules[key]);
|
|
79
|
+
options.value.forEach((item) => {
|
|
80
|
+
if (!(item.prop in formModel)) formModel[item.prop] = item.value !== void 0 ? item.value : getDefaultValue(item.type);
|
|
81
|
+
if (item.rules?.length) {
|
|
82
|
+
const allHaveValidator = item.rules.every((r) => typeof r.validator === "function");
|
|
83
|
+
formRules[item.prop] = allHaveValidator ? mergeRules(item.rules) : item.rules;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error("[C_Form] 初始化失败:", error);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const handleFieldChange = (field) => {
|
|
91
|
+
if (config.value.validateOnChange) nextTick(() => {
|
|
92
|
+
validateField(field).catch(() => {});
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
const validate = async () => {
|
|
96
|
+
if (!formRef.value) throw new Error("[C_Form] 表单引用不存在");
|
|
97
|
+
try {
|
|
98
|
+
await formRef.value.validate();
|
|
99
|
+
emit("validate-success", getModel());
|
|
100
|
+
} catch (errors) {
|
|
101
|
+
emit("validate-error", errors);
|
|
102
|
+
throw errors;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const validateField = async (field) => {
|
|
106
|
+
if (!formRef.value) throw new Error("[C_Form] 表单引用不存在");
|
|
107
|
+
const fields = Array.isArray(field) ? field : [field];
|
|
108
|
+
try {
|
|
109
|
+
await formRef.value.validate();
|
|
110
|
+
} catch (allErrors) {
|
|
111
|
+
if (!Array.isArray(allErrors)) throw allErrors;
|
|
112
|
+
const targetErrors = allErrors.filter((errorGroup) => errorGroup?.some((err) => {
|
|
113
|
+
const e = err;
|
|
114
|
+
return typeof e.field === "string" && fields.includes(e.field);
|
|
115
|
+
}));
|
|
116
|
+
if (targetErrors.length > 0) throw targetErrors;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const clearValidation = (field) => {
|
|
120
|
+
if (!formRef.value) return;
|
|
121
|
+
if (field) (Array.isArray(field) ? field : [field]).forEach((fieldName) => {
|
|
122
|
+
if (formModel[fieldName] !== void 0) formModel[fieldName] = formModel[fieldName];
|
|
123
|
+
});
|
|
124
|
+
else formRef.value.restoreValidation();
|
|
125
|
+
};
|
|
126
|
+
const validateByFilter = async (filterFn, context) => {
|
|
127
|
+
try {
|
|
128
|
+
const fields = options.value.filter(filterFn).map((option) => option.prop);
|
|
129
|
+
if (fields.length === 0) return true;
|
|
130
|
+
await validateField(fields);
|
|
131
|
+
return true;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.warn(`[C_Form] ${context}验证失败:`, error);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const validateStep = async (stepIndex) => {
|
|
138
|
+
const stepKey = config.value.steps?.steps?.[stepIndex]?.key;
|
|
139
|
+
if (!stepKey) return true;
|
|
140
|
+
return validateByFilter((option) => option.layout?.step === stepKey, `步骤 ${stepIndex} `);
|
|
141
|
+
};
|
|
142
|
+
const validateTab = async (tabKey) => {
|
|
143
|
+
return validateByFilter((option) => option.layout?.tab === tabKey, `标签页 ${tabKey} `);
|
|
144
|
+
};
|
|
145
|
+
const validateDynamicFields = async () => {
|
|
146
|
+
return validateByFilter((option) => Boolean(option.layout?.dynamic), "动态字段 ");
|
|
147
|
+
};
|
|
148
|
+
const validateCustomGroup = async (groupKey) => {
|
|
149
|
+
return validateByFilter((option) => option.layout?.group === groupKey, `自定义分组 ${groupKey} `);
|
|
150
|
+
};
|
|
151
|
+
const getModel = () => ({ ...formModel });
|
|
152
|
+
const setFields = (fields) => {
|
|
153
|
+
Object.assign(formModel, fields);
|
|
154
|
+
};
|
|
155
|
+
const resetFields = () => {
|
|
156
|
+
try {
|
|
157
|
+
clearValidation();
|
|
158
|
+
options.value.forEach((item) => {
|
|
159
|
+
const defaultValue = item.value !== void 0 ? item.value : getDefaultValue(item.type);
|
|
160
|
+
formModel[item.prop] = defaultValue;
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error("[C_Form] 重置表单失败:", error);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const setFieldValue = async (field, value, shouldValidate = false) => {
|
|
167
|
+
formModel[field] = value;
|
|
168
|
+
if (shouldValidate) await validateField(field);
|
|
169
|
+
};
|
|
170
|
+
const getFieldValue = (field) => formModel[field];
|
|
171
|
+
const setFieldsValue = async (fields, shouldValidate = false) => {
|
|
172
|
+
Object.assign(formModel, fields);
|
|
173
|
+
if (shouldValidate) await validate();
|
|
174
|
+
};
|
|
175
|
+
const handleSubmit = async () => {
|
|
176
|
+
try {
|
|
177
|
+
await validate();
|
|
178
|
+
emit("submit", {
|
|
179
|
+
model: getModel(),
|
|
180
|
+
form: formRef.value
|
|
181
|
+
});
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.warn("[C_Form] 表单验证失败:", error);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
onMounted(() => {
|
|
187
|
+
initialize();
|
|
188
|
+
watch(() => options.value, () => initialize(), { deep: true });
|
|
189
|
+
watch(() => formModel, (val) => emit("update:modelValue", { ...val }), { deep: true });
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
formModel,
|
|
193
|
+
formRules,
|
|
194
|
+
visibleOptions,
|
|
195
|
+
initialize,
|
|
196
|
+
handleFieldChange,
|
|
197
|
+
validate,
|
|
198
|
+
validateField,
|
|
199
|
+
validateStep,
|
|
200
|
+
validateTab,
|
|
201
|
+
validateDynamicFields,
|
|
202
|
+
validateCustomGroup,
|
|
203
|
+
clearValidation,
|
|
204
|
+
getModel,
|
|
205
|
+
setFields,
|
|
206
|
+
resetFields,
|
|
207
|
+
setFieldValue,
|
|
208
|
+
getFieldValue,
|
|
209
|
+
setFieldsValue,
|
|
210
|
+
handleSubmit,
|
|
211
|
+
handleReset: resetFields
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/components/C_Form/composables/useFormRenderer.ts
|
|
217
|
+
/**
|
|
218
|
+
* 构建渲染器注册表
|
|
219
|
+
* @param C - 组件映射表(由 C_Form 的 <script setup> 解析并注入)
|
|
220
|
+
*
|
|
221
|
+
* 为什么不在 .ts 文件中直接 resolveComponent?
|
|
222
|
+
* unplugin-vue-components 只转换 .vue SFC 中的 resolveComponent 调用,
|
|
223
|
+
* .ts 文件中的 resolveComponent 不会被转换,运行时无法找到组件。
|
|
224
|
+
*/
|
|
225
|
+
function buildRenderers(C) {
|
|
226
|
+
return {
|
|
227
|
+
input: (props) => h(C.NInput, { ...props }),
|
|
228
|
+
textarea: (props) => h(C.NInput, {
|
|
229
|
+
...props,
|
|
230
|
+
type: "textarea"
|
|
231
|
+
}),
|
|
232
|
+
inputNumber: (props) => h(C.NInputNumber, { ...props }),
|
|
233
|
+
switch: (props) => h(C.NSwitch, { ...props }),
|
|
234
|
+
slider: (props) => h(C.NSlider, { ...props }),
|
|
235
|
+
rate: (props) => h(C.NRate, { ...props }),
|
|
236
|
+
datePicker: (props) => h(C.NDatePicker, { ...props }),
|
|
237
|
+
daterange: (props) => h(C.NDatePicker, {
|
|
238
|
+
...props,
|
|
239
|
+
type: "daterange"
|
|
240
|
+
}),
|
|
241
|
+
timePicker: (props) => h(C.NTimePicker, { ...props }),
|
|
242
|
+
cascader: (props) => h(C.NCascader, { ...props }),
|
|
243
|
+
colorPicker: (props) => h(C.NColorPicker, { ...props }),
|
|
244
|
+
select: (baseProps, item) => h(C.NSelect, {
|
|
245
|
+
...baseProps,
|
|
246
|
+
options: item.children?.map((child) => ({
|
|
247
|
+
value: child.value,
|
|
248
|
+
label: child.label,
|
|
249
|
+
disabled: child.disabled
|
|
250
|
+
})) || []
|
|
251
|
+
}),
|
|
252
|
+
checkbox: (baseProps, item) => h(C.NCheckboxGroup, { ...baseProps }, { default: () => h(C.NSpace, {}, { default: () => item.children?.map((child) => h(C.NCheckbox, {
|
|
253
|
+
value: child.value,
|
|
254
|
+
label: child.label,
|
|
255
|
+
disabled: child.disabled,
|
|
256
|
+
key: String(child.value)
|
|
257
|
+
})) || [] }) }),
|
|
258
|
+
radio: (baseProps, item) => h(C.NRadioGroup, { ...baseProps }, { default: () => h(C.NSpace, {}, { default: () => item.children?.map((child) => h(C.NRadio, {
|
|
259
|
+
value: child.value,
|
|
260
|
+
disabled: child.disabled,
|
|
261
|
+
key: String(child.value)
|
|
262
|
+
}, { default: () => child.label })) || [] }) }),
|
|
263
|
+
upload: (baseProps, item, _config, ctx) => h(C.NUpload, {
|
|
264
|
+
fileList: baseProps.value || [],
|
|
265
|
+
"onUpdate:fileList": (fileList) => {
|
|
266
|
+
baseProps["onUpdate:value"]?.(fileList);
|
|
267
|
+
},
|
|
268
|
+
...item.attrs
|
|
269
|
+
}, {
|
|
270
|
+
trigger: () => ctx?.slots?.["uploadClick"]?.() || h(C.NButton, { type: "primary" }, { default: () => "选择文件" }),
|
|
271
|
+
tip: () => ctx?.slots?.["uploadTip"]?.() || null
|
|
272
|
+
}),
|
|
273
|
+
editor: (baseProps, item, config) => h(C.C_Editor, {
|
|
274
|
+
editorId: `editor-${item.prop}`,
|
|
275
|
+
modelValue: baseProps.value || "",
|
|
276
|
+
placeholder: item.placeholder,
|
|
277
|
+
disabled: config.disabled,
|
|
278
|
+
readonly: config.readonly,
|
|
279
|
+
"onUpdate:modelValue": (value) => {
|
|
280
|
+
baseProps["onUpdate:value"]?.(value);
|
|
281
|
+
},
|
|
282
|
+
...item.attrs
|
|
283
|
+
})
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/** 自定义渲染器扩展存储 */
|
|
287
|
+
const customRenderers = {};
|
|
288
|
+
/**
|
|
289
|
+
* 运行时注册自定义渲染器 — 开闭原则
|
|
290
|
+
* @param type - 控件类型名
|
|
291
|
+
* @param renderer - 渲染函数
|
|
292
|
+
*/
|
|
293
|
+
function registerRenderer(type, renderer) {
|
|
294
|
+
customRenderers[type] = renderer;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* 渲染引擎 Composable — 生成 formItems VNode 数组
|
|
298
|
+
* @param componentMap - 已解析的组件映射表(由 C_Form 的 <script setup> 提供)
|
|
299
|
+
*/
|
|
300
|
+
function useFormRenderer(formModel, visibleOptions, config, handleFieldChange, componentMap, instanceSlots) {
|
|
301
|
+
const renderers = {
|
|
302
|
+
...buildRenderers(componentMap),
|
|
303
|
+
...customRenderers
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* 为指定表单项生成基础 props(双向绑定 + 占位符)
|
|
307
|
+
*/
|
|
308
|
+
const getBaseProps = (item) => {
|
|
309
|
+
const baseProps = {
|
|
310
|
+
value: formModel[item.prop],
|
|
311
|
+
"onUpdate:value": (value) => {
|
|
312
|
+
formModel[item.prop] = value;
|
|
313
|
+
handleFieldChange(item.prop);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
if (item.type === "textarea") baseProps.type = "textarea";
|
|
317
|
+
if (item.placeholder) baseProps.placeholder = item.placeholder;
|
|
318
|
+
return baseProps;
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* 渲染单个表单项控件
|
|
322
|
+
*/
|
|
323
|
+
const renderFormItem = (item) => {
|
|
324
|
+
try {
|
|
325
|
+
const renderer = renderers[item.type];
|
|
326
|
+
if (!renderer) {
|
|
327
|
+
console.warn(`[C_Form] 未支持的组件类型: ${item.type}`);
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
return renderer({
|
|
331
|
+
...getBaseProps(item),
|
|
332
|
+
...item.attrs
|
|
333
|
+
}, item, config.value, {
|
|
334
|
+
slots: instanceSlots,
|
|
335
|
+
components: componentMap
|
|
336
|
+
});
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error(`[C_Form] 渲染表单项失败:`, error, item);
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
return {
|
|
343
|
+
renderFormItem,
|
|
344
|
+
formItems: computed(() => visibleOptions.value.map((item) => h(componentMap.NFormItem, {
|
|
345
|
+
label: item.label,
|
|
346
|
+
path: item.prop,
|
|
347
|
+
key: item.prop,
|
|
348
|
+
required: !!item.rules?.length
|
|
349
|
+
}, { default: () => renderFormItem(item) }))),
|
|
350
|
+
getBaseProps
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/components/C_Form/layouts/Default/index.vue?vue&type=script&setup=true&lang.ts
|
|
356
|
+
const _hoisted_1$6 = { class: "c-form-default" };
|
|
357
|
+
var index_vue_vue_type_script_setup_true_lang_default$8 = /* @__PURE__ */ defineComponent({
|
|
358
|
+
__name: "index",
|
|
359
|
+
props: {
|
|
360
|
+
formItems: {},
|
|
361
|
+
layoutConfig: {},
|
|
362
|
+
options: {}
|
|
363
|
+
},
|
|
364
|
+
setup(__props) {
|
|
365
|
+
return (_ctx, _cache) => {
|
|
366
|
+
return openBlock(), createElementBlock("div", _hoisted_1$6, [(openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.formItems, (item) => {
|
|
367
|
+
return openBlock(), createBlock(resolveDynamicComponent(item), { key: item.key });
|
|
368
|
+
}), 128))]);
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/components/C_Form/layouts/Default/index.vue
|
|
375
|
+
var Default_default = /* @__PURE__ */ export_helper_default(index_vue_vue_type_script_setup_true_lang_default$8, [["__scopeId", "data-v-3422ba86"]]);
|
|
376
|
+
|
|
377
|
+
//#endregion
|
|
378
|
+
//#region src/components/C_Form/layouts/Inline/index.vue?vue&type=script&setup=true&lang.ts
|
|
379
|
+
var index_vue_vue_type_script_setup_true_lang_default$7 = /* @__PURE__ */ defineComponent({
|
|
380
|
+
__name: "index",
|
|
381
|
+
props: {
|
|
382
|
+
formItems: {},
|
|
383
|
+
layoutConfig: { default: () => ({}) },
|
|
384
|
+
options: { default: () => [] }
|
|
385
|
+
},
|
|
386
|
+
setup(__props) {
|
|
387
|
+
const props = __props;
|
|
388
|
+
/**
|
|
389
|
+
* 获取统一项目宽度
|
|
390
|
+
*/
|
|
391
|
+
const itemWidth = computed(() => 280);
|
|
392
|
+
/**
|
|
393
|
+
* 获取项目间距
|
|
394
|
+
*/
|
|
395
|
+
const gap = computed(() => {
|
|
396
|
+
return props.layoutConfig?.inline?.gap ?? 16;
|
|
397
|
+
});
|
|
398
|
+
/**
|
|
399
|
+
* 获取行间距
|
|
400
|
+
*/
|
|
401
|
+
const rowGap = computed(() => 16);
|
|
402
|
+
/**
|
|
403
|
+
* 获取对齐方式 - 既控制垂直对齐也控制水平对齐
|
|
404
|
+
*/
|
|
405
|
+
const align = computed(() => {
|
|
406
|
+
const alignValue = props.layoutConfig?.inline?.align;
|
|
407
|
+
return alignValue === "start" || alignValue === "end" ? alignValue : "center";
|
|
408
|
+
});
|
|
409
|
+
/**
|
|
410
|
+
* 获取垂直对齐样式
|
|
411
|
+
*/
|
|
412
|
+
const alignItems = computed(() => {
|
|
413
|
+
switch (align.value) {
|
|
414
|
+
case "start": return "flex-start";
|
|
415
|
+
case "end": return "flex-end";
|
|
416
|
+
default: return "center";
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
/**
|
|
420
|
+
* 获取水平对齐样式
|
|
421
|
+
*/
|
|
422
|
+
const justifyContent = computed(() => {
|
|
423
|
+
switch (align.value) {
|
|
424
|
+
case "start": return "flex-start";
|
|
425
|
+
case "end": return "flex-end";
|
|
426
|
+
case "center": return "center";
|
|
427
|
+
default: return "flex-start";
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
/**
|
|
431
|
+
* 容器样式计算
|
|
432
|
+
*/
|
|
433
|
+
const containerStyle = computed(() => {
|
|
434
|
+
return {
|
|
435
|
+
display: "flex",
|
|
436
|
+
flexWrap: "wrap",
|
|
437
|
+
alignItems: alignItems.value,
|
|
438
|
+
justifyContent: justifyContent.value,
|
|
439
|
+
gap: `${rowGap.value}px ${gap.value}px`,
|
|
440
|
+
width: "100%"
|
|
441
|
+
};
|
|
442
|
+
});
|
|
443
|
+
/**
|
|
444
|
+
* 获取表单项的唯一key
|
|
445
|
+
*/
|
|
446
|
+
const getItemKey = (item, index) => {
|
|
447
|
+
if (item.key != null) return String(item.key);
|
|
448
|
+
const itemProps = item.props;
|
|
449
|
+
if (itemProps?.path) return itemProps.path;
|
|
450
|
+
return `form-item-${index}`;
|
|
451
|
+
};
|
|
452
|
+
/**
|
|
453
|
+
* 获取表单项样式
|
|
454
|
+
*/
|
|
455
|
+
const getItemStyle = (index) => {
|
|
456
|
+
const layoutConfig = (props.options?.[index])?.layout;
|
|
457
|
+
const baseStyle = {
|
|
458
|
+
width: `${itemWidth.value}px`,
|
|
459
|
+
flexShrink: 0,
|
|
460
|
+
display: "flex",
|
|
461
|
+
flexDirection: "column"
|
|
462
|
+
};
|
|
463
|
+
if (layoutConfig?.width !== void 0) baseStyle.width = typeof layoutConfig.width === "number" ? `${layoutConfig.width}px` : layoutConfig.width;
|
|
464
|
+
if (layoutConfig?.style) Object.assign(baseStyle, layoutConfig.style);
|
|
465
|
+
return baseStyle;
|
|
466
|
+
};
|
|
467
|
+
return (_ctx, _cache) => {
|
|
468
|
+
return openBlock(), createElementBlock("div", {
|
|
469
|
+
class: "c-form-inline",
|
|
470
|
+
style: normalizeStyle(containerStyle.value)
|
|
471
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.formItems, (item, index) => {
|
|
472
|
+
return openBlock(), createElementBlock("div", {
|
|
473
|
+
key: getItemKey(item, index),
|
|
474
|
+
class: "c-form-inline-item",
|
|
475
|
+
style: normalizeStyle(getItemStyle(index))
|
|
476
|
+
}, [(openBlock(), createBlock(resolveDynamicComponent(item)))], 4);
|
|
477
|
+
}), 128))], 4);
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region src/components/C_Form/layouts/Inline/index.vue
|
|
484
|
+
var Inline_default = /* @__PURE__ */ export_helper_default(index_vue_vue_type_script_setup_true_lang_default$7, [["__scopeId", "data-v-c4648e30"]]);
|
|
485
|
+
|
|
486
|
+
//#endregion
|
|
487
|
+
//#region src/components/C_Form/layouts/Grid/index.vue?vue&type=script&setup=true&lang.ts
|
|
488
|
+
const _hoisted_1$5 = { class: "c-grid-layout" };
|
|
489
|
+
const _hoisted_2$5 = { class: "config-header" };
|
|
490
|
+
const _hoisted_3$5 = { class: "config-controls" };
|
|
491
|
+
const _hoisted_4$5 = { class: "config-label" };
|
|
492
|
+
const _hoisted_5$5 = { class: "item-wrapper" };
|
|
493
|
+
var index_vue_vue_type_script_setup_true_lang_default$6 = /* @__PURE__ */ defineComponent({
|
|
494
|
+
__name: "index",
|
|
495
|
+
props: {
|
|
496
|
+
formItems: {},
|
|
497
|
+
layoutConfig: { default: () => ({}) },
|
|
498
|
+
options: { default: () => [] }
|
|
499
|
+
},
|
|
500
|
+
setup(__props, { expose: __expose }) {
|
|
501
|
+
const props = __props;
|
|
502
|
+
const isDev = ref(typeof import.meta !== "undefined" && !!import.meta.env?.DEV);
|
|
503
|
+
const internalConfig = reactive({
|
|
504
|
+
cols: 24,
|
|
505
|
+
gutter: 16,
|
|
506
|
+
responsive: true
|
|
507
|
+
});
|
|
508
|
+
const gridConfig = computed(() => props.layoutConfig?.grid || {});
|
|
509
|
+
const showConfigPanel = computed(() => gridConfig.value.showConfigPanel);
|
|
510
|
+
const showStats = computed(() => gridConfig.value.showStats);
|
|
511
|
+
const effectiveConfig = computed(() => ({
|
|
512
|
+
cols: showConfigPanel.value ? internalConfig.cols : gridConfig.value.cols ?? 24,
|
|
513
|
+
gutter: showConfigPanel.value ? internalConfig.gutter : gridConfig.value.gutter ?? 16,
|
|
514
|
+
responsive: showConfigPanel.value ? internalConfig.responsive : gridConfig.value.responsive ?? true
|
|
515
|
+
}));
|
|
516
|
+
const gridProps = computed(() => ({
|
|
517
|
+
cols: effectiveConfig.value.cols,
|
|
518
|
+
xGap: effectiveConfig.value.gutter,
|
|
519
|
+
yGap: effectiveConfig.value.gutter,
|
|
520
|
+
responsive: effectiveConfig.value.responsive ? "screen" : "self"
|
|
521
|
+
}));
|
|
522
|
+
const configControls = computed(() => [
|
|
523
|
+
{
|
|
524
|
+
key: "cols",
|
|
525
|
+
label: "列数",
|
|
526
|
+
component: "NInputNumber",
|
|
527
|
+
value: computed({
|
|
528
|
+
get: () => internalConfig.cols,
|
|
529
|
+
set: (val) => internalConfig.cols = val
|
|
530
|
+
}),
|
|
531
|
+
props: {
|
|
532
|
+
min: 1,
|
|
533
|
+
max: 48,
|
|
534
|
+
size: "small"
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
key: "gutter",
|
|
539
|
+
label: "间距",
|
|
540
|
+
component: "NInputNumber",
|
|
541
|
+
value: computed({
|
|
542
|
+
get: () => internalConfig.gutter,
|
|
543
|
+
set: (val) => internalConfig.gutter = val
|
|
544
|
+
}),
|
|
545
|
+
props: {
|
|
546
|
+
min: 0,
|
|
547
|
+
max: 48,
|
|
548
|
+
size: "small"
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
key: "responsive",
|
|
553
|
+
label: "响应式",
|
|
554
|
+
component: "NSwitch",
|
|
555
|
+
value: computed({
|
|
556
|
+
get: () => internalConfig.responsive,
|
|
557
|
+
set: (val) => internalConfig.responsive = val
|
|
558
|
+
}),
|
|
559
|
+
props: { size: "small" }
|
|
560
|
+
}
|
|
561
|
+
]);
|
|
562
|
+
const statsText = computed(() => `列数: ${effectiveConfig.value.cols} | 间距: ${effectiveConfig.value.gutter}px | 项目: ${props.formItems.length}个`);
|
|
563
|
+
/**
|
|
564
|
+
* 获取表单项key
|
|
565
|
+
*/
|
|
566
|
+
const getItemKey = (item, index) => String(item.key) || item.props?.path || `grid-item-${index}`;
|
|
567
|
+
/**
|
|
568
|
+
* 获取布局属性值
|
|
569
|
+
*/
|
|
570
|
+
const getLayoutValue = (index, key) => {
|
|
571
|
+
const option = props.options?.[index]?.layout;
|
|
572
|
+
return option?.[key] ?? option?.grid?.[key];
|
|
573
|
+
};
|
|
574
|
+
/**
|
|
575
|
+
* 获取默认span值
|
|
576
|
+
*/
|
|
577
|
+
const getDefaultSpan = () => {
|
|
578
|
+
const { cols } = effectiveConfig.value;
|
|
579
|
+
return cols <= 12 ? cols : cols <= 24 ? 12 : 8;
|
|
580
|
+
};
|
|
581
|
+
/**
|
|
582
|
+
* 获取网格项属性
|
|
583
|
+
*/
|
|
584
|
+
const getItemProps = (index) => {
|
|
585
|
+
const span = getLayoutValue(index, "span");
|
|
586
|
+
const offset = getLayoutValue(index, "offset");
|
|
587
|
+
const suffix = getLayoutValue(index, "suffix");
|
|
588
|
+
return {
|
|
589
|
+
span: typeof span === "number" && span > 0 && span <= effectiveConfig.value.cols ? span : getDefaultSpan(),
|
|
590
|
+
offset: typeof offset === "number" && offset >= 0 && offset < effectiveConfig.value.cols ? offset : 0,
|
|
591
|
+
suffix: Boolean(suffix)
|
|
592
|
+
};
|
|
593
|
+
};
|
|
594
|
+
/**
|
|
595
|
+
* 配置变更事件
|
|
596
|
+
*/
|
|
597
|
+
const emitConfigChange = () => {
|
|
598
|
+
if (isDev.value) console.log("Grid config updated:", toRaw(internalConfig));
|
|
599
|
+
};
|
|
600
|
+
watch(gridConfig, (config) => {
|
|
601
|
+
if (config.cols !== void 0) internalConfig.cols = config.cols;
|
|
602
|
+
if (config.gutter !== void 0) internalConfig.gutter = config.gutter;
|
|
603
|
+
if (config.responsive !== void 0) internalConfig.responsive = config.responsive;
|
|
604
|
+
}, { immediate: true });
|
|
605
|
+
if (isDev.value) watchEffect(() => {
|
|
606
|
+
const optionsCount = props.options.length;
|
|
607
|
+
const itemsCount = props.formItems.length;
|
|
608
|
+
if (optionsCount > 0 && optionsCount !== itemsCount) console.warn(`[GridLayout] 配置数量(${optionsCount})与表单项数量(${itemsCount})不匹配`);
|
|
609
|
+
});
|
|
610
|
+
__expose({
|
|
611
|
+
updateGridConfig: (config) => Object.assign(internalConfig, config),
|
|
612
|
+
getCurrentConfig: () => ({ ...effectiveConfig.value }),
|
|
613
|
+
getGridInfo: () => ({
|
|
614
|
+
...effectiveConfig.value,
|
|
615
|
+
itemCount: props.formItems.length
|
|
616
|
+
})
|
|
617
|
+
});
|
|
618
|
+
return (_ctx, _cache) => {
|
|
619
|
+
const _component_NCard = resolveComponent("NCard");
|
|
620
|
+
const _component_NGridItem = resolveComponent("NGridItem");
|
|
621
|
+
const _component_NGrid = resolveComponent("NGrid");
|
|
622
|
+
const _component_NAlert = resolveComponent("NAlert");
|
|
623
|
+
return openBlock(), createElementBlock("div", _hoisted_1$5, [
|
|
624
|
+
createCommentVNode(" 配置面板 "),
|
|
625
|
+
showConfigPanel.value ? (openBlock(), createBlock(_component_NCard, {
|
|
626
|
+
key: 0,
|
|
627
|
+
size: "small",
|
|
628
|
+
bordered: false,
|
|
629
|
+
class: "config-panel"
|
|
630
|
+
}, {
|
|
631
|
+
header: withCtx(() => [createElementVNode("div", _hoisted_2$5, [createVNode(C_Icon_default, {
|
|
632
|
+
name: "mdi:view-grid",
|
|
633
|
+
size: 18
|
|
634
|
+
}), _cache[0] || (_cache[0] = createElementVNode("span", null, "网格配置", -1))])]),
|
|
635
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_3$5, [(openBlock(true), createElementBlock(Fragment, null, renderList(configControls.value, (control) => {
|
|
636
|
+
return openBlock(), createElementBlock("div", {
|
|
637
|
+
key: control.key,
|
|
638
|
+
class: "config-item"
|
|
639
|
+
}, [createElementVNode("span", _hoisted_4$5, toDisplayString(control.label) + ":", 1), (openBlock(), createBlock(resolveDynamicComponent(control.component), mergeProps({
|
|
640
|
+
value: control.value,
|
|
641
|
+
"onUpdate:value": ($event) => control.value = $event
|
|
642
|
+
}, { ref_for: true }, control.props, { "onUpdate:value": emitConfigChange }), null, 16, ["value", "onUpdate:value"]))]);
|
|
643
|
+
}), 128))])]),
|
|
644
|
+
_: 1
|
|
645
|
+
})) : createCommentVNode("v-if", true),
|
|
646
|
+
createCommentVNode(" 网格容器 "),
|
|
647
|
+
createVNode(_component_NGrid, mergeProps(gridProps.value, { class: "grid-container" }), {
|
|
648
|
+
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.formItems, (item, index) => {
|
|
649
|
+
return openBlock(), createBlock(_component_NGridItem, mergeProps({ key: getItemKey(item, index) }, { ref_for: true }, getItemProps(index), { class: "grid-item" }), {
|
|
650
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_5$5, [(openBlock(), createBlock(resolveDynamicComponent(item)))])]),
|
|
651
|
+
_: 2
|
|
652
|
+
}, 1040);
|
|
653
|
+
}), 128))]),
|
|
654
|
+
_: 1
|
|
655
|
+
}, 16),
|
|
656
|
+
createCommentVNode(" 统计信息 "),
|
|
657
|
+
showStats.value && isDev.value ? (openBlock(), createBlock(_component_NAlert, {
|
|
658
|
+
key: 1,
|
|
659
|
+
type: "info",
|
|
660
|
+
"show-icon": false,
|
|
661
|
+
size: "small",
|
|
662
|
+
class: "grid-stats"
|
|
663
|
+
}, {
|
|
664
|
+
header: withCtx(() => [createVNode(C_Icon_default, {
|
|
665
|
+
name: "mdi:information-outline",
|
|
666
|
+
size: 16
|
|
667
|
+
}), _cache[1] || (_cache[1] = createTextVNode(" 网格信息 ", -1))]),
|
|
668
|
+
default: withCtx(() => [createTextVNode(" " + toDisplayString(statsText.value), 1)]),
|
|
669
|
+
_: 1
|
|
670
|
+
})) : createCommentVNode("v-if", true)
|
|
671
|
+
]);
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
//#endregion
|
|
677
|
+
//#region src/components/C_Form/layouts/Grid/index.vue
|
|
678
|
+
var Grid_default = /* @__PURE__ */ export_helper_default(index_vue_vue_type_script_setup_true_lang_default$6, [["__scopeId", "data-v-ec7e9532"]]);
|
|
679
|
+
|
|
680
|
+
//#endregion
|
|
681
|
+
//#region src/components/C_Form/layouts/Card/index.vue?vue&type=script&setup=true&lang.ts
|
|
682
|
+
const _hoisted_1$4 = { class: "c-form-card" };
|
|
683
|
+
const _hoisted_2$4 = { class: "flex items-center gap-2" };
|
|
684
|
+
const _hoisted_3$4 = { class: "config-controls" };
|
|
685
|
+
const _hoisted_4$4 = { class: "config-item" };
|
|
686
|
+
const _hoisted_5$4 = { class: "flex items-center gap-2" };
|
|
687
|
+
const _hoisted_6$4 = { class: "text-xs min-w-12" };
|
|
688
|
+
const _hoisted_7$3 = { class: "config-item" };
|
|
689
|
+
const _hoisted_8$2 = { class: "config-item" };
|
|
690
|
+
const _hoisted_9$1 = { class: "config-item" };
|
|
691
|
+
const _hoisted_10$1 = { class: "flex items-center gap-3" };
|
|
692
|
+
const _hoisted_11$1 = { class: "card-header" };
|
|
693
|
+
const _hoisted_12$1 = { class: "header-info" };
|
|
694
|
+
const _hoisted_13$1 = { class: "header-text" };
|
|
695
|
+
const _hoisted_14$1 = { key: 0 };
|
|
696
|
+
const _hoisted_15$1 = { class: "header-actions" };
|
|
697
|
+
const _hoisted_16$1 = { class: "field-stats" };
|
|
698
|
+
const _hoisted_17$1 = { class: "card-content" };
|
|
699
|
+
const _hoisted_18$1 = {
|
|
700
|
+
key: 0,
|
|
701
|
+
class: "progress-section"
|
|
702
|
+
};
|
|
703
|
+
const _hoisted_19$1 = { class: "action-content" };
|
|
704
|
+
const _hoisted_20$1 = { class: "status-summary" };
|
|
705
|
+
const _hoisted_21$1 = { class: "status-item" };
|
|
706
|
+
const _hoisted_22$1 = { class: "progress-display" };
|
|
707
|
+
const _hoisted_23$1 = { class: "text-sm min-w-12" };
|
|
708
|
+
const _hoisted_24$1 = { class: "action-buttons" };
|
|
709
|
+
var index_vue_vue_type_script_setup_true_lang_default$5 = /* @__PURE__ */ defineComponent({
|
|
710
|
+
__name: "index",
|
|
711
|
+
props: {
|
|
712
|
+
formItems: {},
|
|
713
|
+
layoutConfig: { default: () => ({}) },
|
|
714
|
+
options: { default: () => [] },
|
|
715
|
+
formData: { default: () => ({}) }
|
|
716
|
+
},
|
|
717
|
+
setup(__props) {
|
|
718
|
+
const props = __props;
|
|
719
|
+
const cardGap = ref(20);
|
|
720
|
+
const showIcons = ref(true);
|
|
721
|
+
const collapsible = ref(true);
|
|
722
|
+
const showProgress = ref(true);
|
|
723
|
+
const currentDirection = ref("vertical");
|
|
724
|
+
const collapsedGroups = ref({});
|
|
725
|
+
const groups = computed(() => {
|
|
726
|
+
return props.layoutConfig?.card?.groups || [];
|
|
727
|
+
});
|
|
728
|
+
const hasGroups = computed(() => {
|
|
729
|
+
return groups.value.length > 0;
|
|
730
|
+
});
|
|
731
|
+
const showLayoutConfig = computed(() => {
|
|
732
|
+
return props.layoutConfig?.card?.showLayoutConfig ?? true;
|
|
733
|
+
});
|
|
734
|
+
const showActionPanel = computed(() => {
|
|
735
|
+
return props.layoutConfig?.card?.showActionPanel ?? true;
|
|
736
|
+
});
|
|
737
|
+
const layoutClass = computed(() => {
|
|
738
|
+
if (!hasGroups.value) return "layout-single";
|
|
739
|
+
return `layout-${currentDirection.value}`;
|
|
740
|
+
});
|
|
741
|
+
const groupsWithItems = computed(() => {
|
|
742
|
+
if (!hasGroups.value) return [];
|
|
743
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
744
|
+
groups.value.forEach((group) => {
|
|
745
|
+
groupMap.set(group.key, []);
|
|
746
|
+
});
|
|
747
|
+
props.formItems.forEach((item, index) => {
|
|
748
|
+
const groupKey = (props.options?.[index])?.layout?.group || groups.value[0]?.key || "default";
|
|
749
|
+
if (!groupMap.has(groupKey)) groupMap.set(groupKey, []);
|
|
750
|
+
groupMap.get(groupKey).push(item);
|
|
751
|
+
});
|
|
752
|
+
return groups.value.map((groupConfig) => ({
|
|
753
|
+
config: groupConfig,
|
|
754
|
+
items: groupMap.get(groupConfig.key) || []
|
|
755
|
+
})).filter((group) => group.items.length > 0);
|
|
756
|
+
});
|
|
757
|
+
const allCollapsed = computed(() => {
|
|
758
|
+
const groupKeys = Object.keys(collapsedGroups.value);
|
|
759
|
+
return groupKeys.length > 0 && groupKeys.every((key) => collapsedGroups.value[key]);
|
|
760
|
+
});
|
|
761
|
+
const totalProgress = computed(() => {
|
|
762
|
+
if (!props.options || props.options.length === 0) return 0;
|
|
763
|
+
const filledCount = props.options.filter((option) => {
|
|
764
|
+
const value = props.formData?.[option.prop || ""];
|
|
765
|
+
return isFieldFilled(value);
|
|
766
|
+
}).length;
|
|
767
|
+
return Math.round(filledCount / props.options.length * 100);
|
|
768
|
+
});
|
|
769
|
+
const isFieldFilled = (value) => {
|
|
770
|
+
if (value === null || value === void 0) return false;
|
|
771
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
772
|
+
if (typeof value === "string") return value.trim() !== "";
|
|
773
|
+
return true;
|
|
774
|
+
};
|
|
775
|
+
const getFilledCount = (group) => {
|
|
776
|
+
if (!props.options) return 0;
|
|
777
|
+
return props.options.filter((option) => option.layout?.group === group.config.key).filter((option) => {
|
|
778
|
+
const value = props.formData?.[option.prop || ""];
|
|
779
|
+
return isFieldFilled(value);
|
|
780
|
+
}).length;
|
|
781
|
+
};
|
|
782
|
+
const getGroupProgress = (group) => {
|
|
783
|
+
if (group.items.length === 0) return 0;
|
|
784
|
+
const filledCount = getFilledCount(group);
|
|
785
|
+
return Math.round(filledCount / group.items.length * 100);
|
|
786
|
+
};
|
|
787
|
+
const getDefaultIcon = (groupKey) => {
|
|
788
|
+
const iconMap = {
|
|
789
|
+
basic: "mdi:account",
|
|
790
|
+
contact: "mdi:phone",
|
|
791
|
+
preferences: "mdi:cog",
|
|
792
|
+
settings: "mdi:cog",
|
|
793
|
+
info: "mdi:information",
|
|
794
|
+
default: "mdi:form-select"
|
|
795
|
+
};
|
|
796
|
+
return iconMap[groupKey] || iconMap.default;
|
|
797
|
+
};
|
|
798
|
+
const toggleGroup = (groupKey) => {
|
|
799
|
+
collapsedGroups.value[groupKey] = !collapsedGroups.value[groupKey];
|
|
800
|
+
};
|
|
801
|
+
const toggleAllGroups = () => {
|
|
802
|
+
const shouldCollapse = !allCollapsed.value;
|
|
803
|
+
groups.value.forEach((group) => {
|
|
804
|
+
collapsedGroups.value[group.key] = shouldCollapse;
|
|
805
|
+
});
|
|
806
|
+
};
|
|
807
|
+
onMounted(() => {
|
|
808
|
+
const config = props.layoutConfig?.card;
|
|
809
|
+
if (config?.direction) currentDirection.value = config.direction;
|
|
810
|
+
if (config?.showProgress !== void 0) showProgress.value = config.showProgress;
|
|
811
|
+
groups.value.forEach((group) => {
|
|
812
|
+
collapsedGroups.value[group.key] = false;
|
|
813
|
+
});
|
|
814
|
+
});
|
|
815
|
+
return (_ctx, _cache) => {
|
|
816
|
+
const _component_NSlider = resolveComponent("NSlider");
|
|
817
|
+
const _component_NSwitch = resolveComponent("NSwitch");
|
|
818
|
+
const _component_NRadio = resolveComponent("NRadio");
|
|
819
|
+
const _component_NRadioGroup = resolveComponent("NRadioGroup");
|
|
820
|
+
const _component_NCard = resolveComponent("NCard");
|
|
821
|
+
const _component_NBadge = resolveComponent("NBadge");
|
|
822
|
+
const _component_NButton = resolveComponent("NButton");
|
|
823
|
+
const _component_NProgress = resolveComponent("NProgress");
|
|
824
|
+
return openBlock(), createElementBlock("div", _hoisted_1$4, [
|
|
825
|
+
createCommentVNode(" 布局配置面板 "),
|
|
826
|
+
hasGroups.value && showLayoutConfig.value ? (openBlock(), createBlock(_component_NCard, {
|
|
827
|
+
key: 0,
|
|
828
|
+
class: "layout-config-panel",
|
|
829
|
+
bordered: false
|
|
830
|
+
}, {
|
|
831
|
+
header: withCtx(() => [createElementVNode("div", _hoisted_2$4, [createVNode(C_Icon_default, {
|
|
832
|
+
name: "mdi:cog",
|
|
833
|
+
size: 18
|
|
834
|
+
}), _cache[4] || (_cache[4] = createElementVNode("span", null, "布局配置", -1))])]),
|
|
835
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_3$4, [
|
|
836
|
+
createElementVNode("div", _hoisted_4$4, [_cache[5] || (_cache[5] = createElementVNode("span", null, "卡片间距", -1)), createElementVNode("div", _hoisted_5$4, [createVNode(_component_NSlider, {
|
|
837
|
+
value: cardGap.value,
|
|
838
|
+
"onUpdate:value": _cache[0] || (_cache[0] = ($event) => cardGap.value = $event),
|
|
839
|
+
min: 12,
|
|
840
|
+
max: 32,
|
|
841
|
+
step: 4,
|
|
842
|
+
class: "w-24"
|
|
843
|
+
}, null, 8, ["value"]), createElementVNode("span", _hoisted_6$4, toDisplayString(cardGap.value) + "px", 1)])]),
|
|
844
|
+
createElementVNode("div", _hoisted_7$3, [_cache[6] || (_cache[6] = createElementVNode("span", null, "显示图标", -1)), createVNode(_component_NSwitch, {
|
|
845
|
+
value: showIcons.value,
|
|
846
|
+
"onUpdate:value": _cache[1] || (_cache[1] = ($event) => showIcons.value = $event),
|
|
847
|
+
size: "small"
|
|
848
|
+
}, null, 8, ["value"])]),
|
|
849
|
+
createElementVNode("div", _hoisted_8$2, [_cache[7] || (_cache[7] = createElementVNode("span", null, "可折叠", -1)), createVNode(_component_NSwitch, {
|
|
850
|
+
value: collapsible.value,
|
|
851
|
+
"onUpdate:value": _cache[2] || (_cache[2] = ($event) => collapsible.value = $event),
|
|
852
|
+
size: "small"
|
|
853
|
+
}, null, 8, ["value"])]),
|
|
854
|
+
createElementVNode("div", _hoisted_9$1, [_cache[10] || (_cache[10] = createElementVNode("span", null, "布局方向", -1)), createVNode(_component_NRadioGroup, {
|
|
855
|
+
value: currentDirection.value,
|
|
856
|
+
"onUpdate:value": _cache[3] || (_cache[3] = ($event) => currentDirection.value = $event),
|
|
857
|
+
size: "small"
|
|
858
|
+
}, {
|
|
859
|
+
default: withCtx(() => [createVNode(_component_NRadio, { value: "vertical" }, {
|
|
860
|
+
default: withCtx(() => _cache[8] || (_cache[8] = [createTextVNode("垂直", -1)])),
|
|
861
|
+
_: 1,
|
|
862
|
+
__: [8]
|
|
863
|
+
}), createVNode(_component_NRadio, { value: "horizontal" }, {
|
|
864
|
+
default: withCtx(() => _cache[9] || (_cache[9] = [createTextVNode("水平", -1)])),
|
|
865
|
+
_: 1,
|
|
866
|
+
__: [9]
|
|
867
|
+
})]),
|
|
868
|
+
_: 1
|
|
869
|
+
}, 8, ["value"])])
|
|
870
|
+
])]),
|
|
871
|
+
_: 1
|
|
872
|
+
})) : createCommentVNode("v-if", true),
|
|
873
|
+
createCommentVNode(" 表单内容区域 "),
|
|
874
|
+
createElementVNode("div", {
|
|
875
|
+
class: normalizeClass(["form-content", layoutClass.value]),
|
|
876
|
+
style: normalizeStyle({ gap: `${cardGap.value}px` })
|
|
877
|
+
}, [createCommentVNode(" 无分组时的单卡片模式 "), !hasGroups.value ? (openBlock(), createBlock(_component_NCard, {
|
|
878
|
+
key: 0,
|
|
879
|
+
class: "single-card",
|
|
880
|
+
hoverable: ""
|
|
881
|
+
}, {
|
|
882
|
+
header: withCtx(() => [createElementVNode("div", _hoisted_10$1, [showIcons.value ? (openBlock(), createBlock(C_Icon_default, {
|
|
883
|
+
key: 0,
|
|
884
|
+
name: "mdi:form-select",
|
|
885
|
+
size: 18,
|
|
886
|
+
style: { "color": "#3b82f6" }
|
|
887
|
+
})) : createCommentVNode("v-if", true), _cache[11] || (_cache[11] = createElementVNode("span", null, "表单信息", -1))])]),
|
|
888
|
+
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.formItems, (item) => {
|
|
889
|
+
return openBlock(), createBlock(resolveDynamicComponent(item), { key: item.key });
|
|
890
|
+
}), 128))]),
|
|
891
|
+
_: 1
|
|
892
|
+
})) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" 有分组时的多卡片模式 "), (openBlock(true), createElementBlock(Fragment, null, renderList(groupsWithItems.value, (group) => {
|
|
893
|
+
return openBlock(), createBlock(_component_NCard, {
|
|
894
|
+
key: group.config.key,
|
|
895
|
+
class: normalizeClass(["group-card", [`${group.config.key}-card`, { collapsed: collapsible.value && collapsedGroups.value[group.config.key] }]]),
|
|
896
|
+
hoverable: ""
|
|
897
|
+
}, {
|
|
898
|
+
header: withCtx(() => [createElementVNode("div", _hoisted_11$1, [createElementVNode("div", _hoisted_12$1, [showIcons.value && group.config.icon ? (openBlock(), createBlock(C_Icon_default, {
|
|
899
|
+
key: 0,
|
|
900
|
+
name: group.config.icon,
|
|
901
|
+
size: 20,
|
|
902
|
+
class: "card-icon"
|
|
903
|
+
}, null, 8, ["name"])) : showIcons.value ? (openBlock(), createBlock(C_Icon_default, {
|
|
904
|
+
key: 1,
|
|
905
|
+
name: getDefaultIcon(group.config.key),
|
|
906
|
+
size: 20,
|
|
907
|
+
class: "card-icon"
|
|
908
|
+
}, null, 8, ["name"])) : createCommentVNode("v-if", true), createElementVNode("div", _hoisted_13$1, [createElementVNode("h3", null, toDisplayString(group.config.title), 1), group.config.description ? (openBlock(), createElementBlock("p", _hoisted_14$1, toDisplayString(group.config.description), 1)) : createCommentVNode("v-if", true)])]), createElementVNode("div", _hoisted_15$1, [
|
|
909
|
+
createCommentVNode(" 统计信息 "),
|
|
910
|
+
createElementVNode("div", _hoisted_16$1, [createVNode(_component_NBadge, {
|
|
911
|
+
value: group.items.length,
|
|
912
|
+
type: "info",
|
|
913
|
+
"show-zero": ""
|
|
914
|
+
}, null, 8, ["value"]), createVNode(_component_NBadge, {
|
|
915
|
+
value: `${getFilledCount(group)}/${group.items.length}`,
|
|
916
|
+
type: getFilledCount(group) === group.items.length ? "success" : "warning"
|
|
917
|
+
}, null, 8, ["value", "type"])]),
|
|
918
|
+
createCommentVNode(" 折叠按钮 "),
|
|
919
|
+
collapsible.value ? (openBlock(), createBlock(_component_NButton, {
|
|
920
|
+
key: 0,
|
|
921
|
+
quaternary: "",
|
|
922
|
+
circle: "",
|
|
923
|
+
size: "small",
|
|
924
|
+
onClick: ($event) => toggleGroup(group.config.key)
|
|
925
|
+
}, {
|
|
926
|
+
icon: withCtx(() => [createVNode(C_Icon_default, {
|
|
927
|
+
name: collapsedGroups.value[group.config.key] ? "mdi:chevron-down" : "mdi:chevron-up",
|
|
928
|
+
size: 18
|
|
929
|
+
}, null, 8, ["name"])]),
|
|
930
|
+
_: 2
|
|
931
|
+
}, 1032, ["onClick"])) : createCommentVNode("v-if", true)
|
|
932
|
+
])])]),
|
|
933
|
+
default: withCtx(() => [withDirectives(createElementVNode("div", _hoisted_17$1, [
|
|
934
|
+
createCommentVNode(" 进度指示器 "),
|
|
935
|
+
showProgress.value ? (openBlock(), createElementBlock("div", _hoisted_18$1, [createVNode(_component_NProgress, {
|
|
936
|
+
percentage: getGroupProgress(group),
|
|
937
|
+
color: getGroupProgress(group) === 100 ? "#52c41a" : "#1890ff",
|
|
938
|
+
"show-indicator": false,
|
|
939
|
+
class: "mb-4"
|
|
940
|
+
}, null, 8, ["percentage", "color"])])) : createCommentVNode("v-if", true),
|
|
941
|
+
createCommentVNode(" 表单项 "),
|
|
942
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(group.items, (item) => {
|
|
943
|
+
return openBlock(), createBlock(resolveDynamicComponent(item), { key: item.key });
|
|
944
|
+
}), 128))
|
|
945
|
+
], 512), [[vShow, !collapsedGroups.value[group.config.key]]])]),
|
|
946
|
+
_: 2
|
|
947
|
+
}, 1032, ["class"]);
|
|
948
|
+
}), 128))], 64))], 6),
|
|
949
|
+
createCommentVNode(" 统一操作面板 "),
|
|
950
|
+
hasGroups.value && showActionPanel.value ? (openBlock(), createBlock(_component_NCard, {
|
|
951
|
+
key: 1,
|
|
952
|
+
class: "action-panel",
|
|
953
|
+
bordered: false
|
|
954
|
+
}, {
|
|
955
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_19$1, [createElementVNode("div", _hoisted_20$1, [createElementVNode("div", _hoisted_21$1, [_cache[12] || (_cache[12] = createElementVNode("span", { class: "label" }, "完成进度:", -1)), createElementVNode("div", _hoisted_22$1, [createVNode(_component_NProgress, {
|
|
956
|
+
percentage: totalProgress.value,
|
|
957
|
+
"show-indicator": false,
|
|
958
|
+
color: totalProgress.value === 100 ? "#52c41a" : "#1890ff",
|
|
959
|
+
class: "flex-1"
|
|
960
|
+
}, null, 8, ["percentage", "color"]), createElementVNode("span", _hoisted_23$1, toDisplayString(totalProgress.value) + "%", 1)])])]), createElementVNode("div", _hoisted_24$1, [collapsible.value ? (openBlock(), createBlock(_component_NButton, {
|
|
961
|
+
key: 0,
|
|
962
|
+
onClick: toggleAllGroups
|
|
963
|
+
}, {
|
|
964
|
+
icon: withCtx(() => [createVNode(C_Icon_default, {
|
|
965
|
+
name: allCollapsed.value ? "mdi:unfold-more-horizontal" : "mdi:unfold-less-horizontal",
|
|
966
|
+
size: 18
|
|
967
|
+
}, null, 8, ["name"])]),
|
|
968
|
+
default: withCtx(() => [createTextVNode(" " + toDisplayString(allCollapsed.value ? "展开全部" : "折叠全部"), 1)]),
|
|
969
|
+
_: 1
|
|
970
|
+
})) : createCommentVNode("v-if", true)])])]),
|
|
971
|
+
_: 1
|
|
972
|
+
})) : createCommentVNode("v-if", true)
|
|
973
|
+
]);
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
//#endregion
|
|
979
|
+
//#region src/components/C_Form/layouts/Card/index.vue
|
|
980
|
+
var Card_default = /* @__PURE__ */ export_helper_default(index_vue_vue_type_script_setup_true_lang_default$5, [["__scopeId", "data-v-fff86989"]]);
|
|
981
|
+
|
|
982
|
+
//#endregion
|
|
983
|
+
//#region src/components/C_Form/layouts/Tabs/index.vue?vue&type=script&setup=true&lang.ts
|
|
984
|
+
const _hoisted_1$3 = { class: "c-form-tabs" };
|
|
985
|
+
const _hoisted_2$3 = {
|
|
986
|
+
key: 0,
|
|
987
|
+
class: "single-panel"
|
|
988
|
+
};
|
|
989
|
+
const _hoisted_3$3 = { class: "tabs-container" };
|
|
990
|
+
const _hoisted_4$3 = {
|
|
991
|
+
key: 0,
|
|
992
|
+
class: "tab-header"
|
|
993
|
+
};
|
|
994
|
+
const _hoisted_5$3 = { class: "tab-description" };
|
|
995
|
+
const _hoisted_6$3 = { class: "tab-form-items" };
|
|
996
|
+
const _hoisted_7$2 = {
|
|
997
|
+
key: 0,
|
|
998
|
+
class: "tabs-actions"
|
|
999
|
+
};
|
|
1000
|
+
var index_vue_vue_type_script_setup_true_lang_default$4 = /* @__PURE__ */ defineComponent({
|
|
1001
|
+
__name: "index",
|
|
1002
|
+
props: {
|
|
1003
|
+
formItems: {},
|
|
1004
|
+
layoutConfig: { default: () => ({}) },
|
|
1005
|
+
options: { default: () => [] }
|
|
1006
|
+
},
|
|
1007
|
+
emits: [
|
|
1008
|
+
"tab-change",
|
|
1009
|
+
"tab-before-change",
|
|
1010
|
+
"tab-validate",
|
|
1011
|
+
"tab-close",
|
|
1012
|
+
"tab-add"
|
|
1013
|
+
],
|
|
1014
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
1015
|
+
const props = __props;
|
|
1016
|
+
const emit = __emit;
|
|
1017
|
+
const currentTab = ref("");
|
|
1018
|
+
const tabValidationStatus = reactive({});
|
|
1019
|
+
const getDefaultTabsConfig = () => ({
|
|
1020
|
+
tabs: [],
|
|
1021
|
+
type: "line",
|
|
1022
|
+
size: "medium",
|
|
1023
|
+
placement: "top",
|
|
1024
|
+
animated: true,
|
|
1025
|
+
closable: false,
|
|
1026
|
+
addable: false,
|
|
1027
|
+
showTabHeader: true,
|
|
1028
|
+
showActions: false,
|
|
1029
|
+
showCount: false,
|
|
1030
|
+
validateBeforeSwitch: false,
|
|
1031
|
+
defaultTab: ""
|
|
1032
|
+
});
|
|
1033
|
+
const tabsConfig = computed(() => {
|
|
1034
|
+
const defaultConfig = getDefaultTabsConfig();
|
|
1035
|
+
const userConfig = props.layoutConfig?.tabs || {};
|
|
1036
|
+
return {
|
|
1037
|
+
...defaultConfig,
|
|
1038
|
+
...userConfig
|
|
1039
|
+
};
|
|
1040
|
+
});
|
|
1041
|
+
const hasTabs = computed(() => {
|
|
1042
|
+
return tabsConfig.value.tabs.length > 0;
|
|
1043
|
+
});
|
|
1044
|
+
const tabsWithItems = computed(() => {
|
|
1045
|
+
if (!hasTabs.value) return [];
|
|
1046
|
+
const tabMap = /* @__PURE__ */ new Map();
|
|
1047
|
+
tabsConfig.value.tabs.forEach((tab) => {
|
|
1048
|
+
tabMap.set(tab.key, []);
|
|
1049
|
+
});
|
|
1050
|
+
props.formItems.forEach((item, index) => {
|
|
1051
|
+
const tabKey = (props.options?.[index])?.layout?.tab || tabsConfig.value.tabs[0]?.key || "default";
|
|
1052
|
+
if (!tabMap.has(tabKey)) tabMap.set(tabKey, []);
|
|
1053
|
+
tabMap.get(tabKey).push(item);
|
|
1054
|
+
});
|
|
1055
|
+
return tabsConfig.value.tabs.map((tabConfig) => ({
|
|
1056
|
+
config: tabConfig,
|
|
1057
|
+
items: tabMap.get(tabConfig.key) || []
|
|
1058
|
+
}));
|
|
1059
|
+
});
|
|
1060
|
+
const getItemKey = (item, index) => {
|
|
1061
|
+
if (item.key != null) return String(item.key);
|
|
1062
|
+
const itemProps = item.props;
|
|
1063
|
+
if (itemProps?.path) return itemProps.path;
|
|
1064
|
+
return `tab-item-${index}`;
|
|
1065
|
+
};
|
|
1066
|
+
const validateCurrentTab = async () => {
|
|
1067
|
+
if (!currentTab.value) return true;
|
|
1068
|
+
try {
|
|
1069
|
+
emit("tab-validate", currentTab.value);
|
|
1070
|
+
const valid = true;
|
|
1071
|
+
tabValidationStatus[currentTab.value] = valid;
|
|
1072
|
+
return valid;
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
console.error("[Tabs Layout] 标签验证失败:", error);
|
|
1075
|
+
tabValidationStatus[currentTab.value] = false;
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
const switchToTab = async (targetTab) => {
|
|
1080
|
+
if (!targetTab || targetTab === currentTab.value) return true;
|
|
1081
|
+
if (!tabsWithItems.value.some((tab) => tab.config.key === targetTab)) return false;
|
|
1082
|
+
try {
|
|
1083
|
+
if (tabsConfig.value.validateBeforeSwitch && currentTab.value) {
|
|
1084
|
+
if (!await validateCurrentTab()) return false;
|
|
1085
|
+
}
|
|
1086
|
+
if (currentTab.value) emit("tab-before-change", currentTab.value, targetTab);
|
|
1087
|
+
currentTab.value = targetTab;
|
|
1088
|
+
emit("tab-change", targetTab, tabsWithItems.value.findIndex((tab) => tab.config.key === targetTab));
|
|
1089
|
+
return true;
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
console.error("[Tabs Layout] 标签切换失败:", error);
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
const handleTabChange = (tabKey) => {
|
|
1096
|
+
switchToTab(tabKey);
|
|
1097
|
+
};
|
|
1098
|
+
const handleTabClose = (tabKey) => {
|
|
1099
|
+
emit("tab-close", tabKey);
|
|
1100
|
+
};
|
|
1101
|
+
const handleTabAdd = () => {
|
|
1102
|
+
emit("tab-add");
|
|
1103
|
+
};
|
|
1104
|
+
const initializeCurrentTab = () => {
|
|
1105
|
+
if (!hasTabs.value || tabsWithItems.value.length === 0) return;
|
|
1106
|
+
const { defaultTab } = tabsConfig.value;
|
|
1107
|
+
const targetTab = defaultTab || tabsWithItems.value[0]?.config.key;
|
|
1108
|
+
if (targetTab && targetTab !== currentTab.value) {
|
|
1109
|
+
currentTab.value = targetTab;
|
|
1110
|
+
nextTick(() => {
|
|
1111
|
+
const tabIndex = tabsWithItems.value.findIndex((tab) => tab.config.key === targetTab);
|
|
1112
|
+
if (tabIndex >= 0) emit("tab-change", targetTab, tabIndex);
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
onMounted(() => {
|
|
1117
|
+
initializeCurrentTab();
|
|
1118
|
+
});
|
|
1119
|
+
watch(computed(() => tabsConfig.value.tabs.map((t) => t.key).join(",")), () => {
|
|
1120
|
+
if (currentTab.value && !tabsConfig.value.tabs.some((tab) => tab.key === currentTab.value)) initializeCurrentTab();
|
|
1121
|
+
});
|
|
1122
|
+
watch(() => tabsConfig.value.defaultTab, (newDefaultTab) => {
|
|
1123
|
+
if (newDefaultTab && newDefaultTab !== currentTab.value) switchToTab(newDefaultTab);
|
|
1124
|
+
});
|
|
1125
|
+
__expose({
|
|
1126
|
+
switchToTab,
|
|
1127
|
+
validateCurrentTab,
|
|
1128
|
+
currentTab: readonly(currentTab),
|
|
1129
|
+
totalTabs: computed(() => tabsWithItems.value.length),
|
|
1130
|
+
tabsWithItems: readonly(tabsWithItems)
|
|
1131
|
+
});
|
|
1132
|
+
return (_ctx, _cache) => {
|
|
1133
|
+
const _component_NBadge = resolveComponent("NBadge");
|
|
1134
|
+
const _component_NSpace = resolveComponent("NSpace");
|
|
1135
|
+
const _component_NEmpty = resolveComponent("NEmpty");
|
|
1136
|
+
const _component_NTabPane = resolveComponent("NTabPane");
|
|
1137
|
+
const _component_NTabs = resolveComponent("NTabs");
|
|
1138
|
+
const _component_NButton = resolveComponent("NButton");
|
|
1139
|
+
return openBlock(), createElementBlock("div", _hoisted_1$3, [createCommentVNode(" 无标签配置时的单一面板模式 "), !hasTabs.value ? (openBlock(), createElementBlock("div", _hoisted_2$3, [(openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.formItems, (item, index) => {
|
|
1140
|
+
return openBlock(), createBlock(resolveDynamicComponent(item), { key: getItemKey(item, index) });
|
|
1141
|
+
}), 128))])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" 有标签配置时的分标签模式 "), createElementVNode("div", _hoisted_3$3, [
|
|
1142
|
+
createVNode(_component_NTabs, {
|
|
1143
|
+
value: currentTab.value,
|
|
1144
|
+
"onUpdate:value": [_cache[0] || (_cache[0] = ($event) => currentTab.value = $event), handleTabChange],
|
|
1145
|
+
type: tabsConfig.value.type,
|
|
1146
|
+
size: tabsConfig.value.size,
|
|
1147
|
+
placement: tabsConfig.value.placement,
|
|
1148
|
+
animated: tabsConfig.value.animated,
|
|
1149
|
+
closable: tabsConfig.value.closable,
|
|
1150
|
+
addable: tabsConfig.value.addable,
|
|
1151
|
+
class: "form-tabs",
|
|
1152
|
+
onClose: handleTabClose,
|
|
1153
|
+
onAdd: handleTabAdd
|
|
1154
|
+
}, {
|
|
1155
|
+
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(tabsWithItems.value, (tab) => {
|
|
1156
|
+
return openBlock(), createBlock(_component_NTabPane, {
|
|
1157
|
+
key: tab.config.key,
|
|
1158
|
+
name: tab.config.key,
|
|
1159
|
+
tab: tab.config.title,
|
|
1160
|
+
disabled: tab.config.disabled,
|
|
1161
|
+
closable: tab.config.closable
|
|
1162
|
+
}, {
|
|
1163
|
+
tab: withCtx(() => [createVNode(_component_NSpace, {
|
|
1164
|
+
align: "center",
|
|
1165
|
+
size: 8
|
|
1166
|
+
}, {
|
|
1167
|
+
default: withCtx(() => [
|
|
1168
|
+
tab.config.icon ? (openBlock(), createBlock(C_Icon_default, {
|
|
1169
|
+
key: 0,
|
|
1170
|
+
name: tab.config.icon,
|
|
1171
|
+
size: 16,
|
|
1172
|
+
class: "tab-icon"
|
|
1173
|
+
}, null, 8, ["name"])) : createCommentVNode("v-if", true),
|
|
1174
|
+
createElementVNode("span", null, toDisplayString(tab.config.title), 1),
|
|
1175
|
+
tabsConfig.value.showCount ? (openBlock(), createBlock(_component_NBadge, {
|
|
1176
|
+
key: 1,
|
|
1177
|
+
value: tab.items.length,
|
|
1178
|
+
max: 99,
|
|
1179
|
+
show: tab.items.length > 0,
|
|
1180
|
+
type: "info"
|
|
1181
|
+
}, null, 8, ["value", "show"])) : createCommentVNode("v-if", true)
|
|
1182
|
+
]),
|
|
1183
|
+
_: 2
|
|
1184
|
+
}, 1024)]),
|
|
1185
|
+
default: withCtx(() => [
|
|
1186
|
+
tabsConfig.value.showTabHeader && tab.config.description ? (openBlock(), createElementBlock("div", _hoisted_4$3, [createElementVNode("p", _hoisted_5$3, toDisplayString(tab.config.description), 1)])) : createCommentVNode("v-if", true),
|
|
1187
|
+
createElementVNode("div", _hoisted_6$3, [(openBlock(true), createElementBlock(Fragment, null, renderList(tab.items, (item, itemIndex) => {
|
|
1188
|
+
return openBlock(), createBlock(resolveDynamicComponent(item), { key: getItemKey(item, itemIndex) });
|
|
1189
|
+
}), 128))]),
|
|
1190
|
+
tab.items.length === 0 ? (openBlock(), createBlock(_component_NEmpty, {
|
|
1191
|
+
key: 1,
|
|
1192
|
+
description: "暂无表单项",
|
|
1193
|
+
class: "tab-empty"
|
|
1194
|
+
})) : createCommentVNode("v-if", true)
|
|
1195
|
+
]),
|
|
1196
|
+
_: 2
|
|
1197
|
+
}, 1032, [
|
|
1198
|
+
"name",
|
|
1199
|
+
"tab",
|
|
1200
|
+
"disabled",
|
|
1201
|
+
"closable"
|
|
1202
|
+
]);
|
|
1203
|
+
}), 128))]),
|
|
1204
|
+
_: 1
|
|
1205
|
+
}, 8, [
|
|
1206
|
+
"value",
|
|
1207
|
+
"type",
|
|
1208
|
+
"size",
|
|
1209
|
+
"placement",
|
|
1210
|
+
"animated",
|
|
1211
|
+
"closable",
|
|
1212
|
+
"addable"
|
|
1213
|
+
]),
|
|
1214
|
+
createCommentVNode(" 标签页操作按钮 "),
|
|
1215
|
+
tabsConfig.value.showActions ? (openBlock(), createElementBlock("div", _hoisted_7$2, [createVNode(_component_NSpace, { justify: "space-between" }, {
|
|
1216
|
+
default: withCtx(() => [createVNode(_component_NSpace, null, {
|
|
1217
|
+
default: withCtx(() => [tabsConfig.value.validateBeforeSwitch ? (openBlock(), createBlock(_component_NButton, {
|
|
1218
|
+
key: 0,
|
|
1219
|
+
type: "primary",
|
|
1220
|
+
size: "small",
|
|
1221
|
+
onClick: validateCurrentTab
|
|
1222
|
+
}, {
|
|
1223
|
+
default: withCtx(() => [createVNode(C_Icon_default, {
|
|
1224
|
+
name: "carbon:star-check",
|
|
1225
|
+
size: 14,
|
|
1226
|
+
style: { "margin-right": "4px" }
|
|
1227
|
+
}), _cache[1] || (_cache[1] = createTextVNode(" 验证当前标签 ", -1))]),
|
|
1228
|
+
_: 1,
|
|
1229
|
+
__: [1]
|
|
1230
|
+
})) : createCommentVNode("v-if", true)]),
|
|
1231
|
+
_: 1
|
|
1232
|
+
}), createVNode(_component_NSpace, null, {
|
|
1233
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "tab-actions", {
|
|
1234
|
+
currentTab: currentTab.value,
|
|
1235
|
+
totalTabs: tabsWithItems.value.length,
|
|
1236
|
+
validateTab: validateCurrentTab,
|
|
1237
|
+
switchToTab
|
|
1238
|
+
}, void 0, true)]),
|
|
1239
|
+
_: 3
|
|
1240
|
+
})]),
|
|
1241
|
+
_: 3
|
|
1242
|
+
})])) : createCommentVNode("v-if", true)
|
|
1243
|
+
])], 2112))]);
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
//#endregion
|
|
1249
|
+
//#region src/components/C_Form/layouts/Tabs/index.vue
|
|
1250
|
+
var Tabs_default = /* @__PURE__ */ export_helper_default(index_vue_vue_type_script_setup_true_lang_default$4, [["__scopeId", "data-v-5a6ef2b7"]]);
|
|
1251
|
+
|
|
1252
|
+
//#endregion
|
|
1253
|
+
//#region src/components/C_Form/layouts/Steps/index.vue?vue&type=script&setup=true&lang.ts
|
|
1254
|
+
const _hoisted_1$2 = { class: "c-form-steps" };
|
|
1255
|
+
const _hoisted_2$2 = {
|
|
1256
|
+
key: 0,
|
|
1257
|
+
class: "single-panel"
|
|
1258
|
+
};
|
|
1259
|
+
const _hoisted_3$2 = { class: "steps-container" };
|
|
1260
|
+
const _hoisted_4$2 = {
|
|
1261
|
+
key: 0,
|
|
1262
|
+
class: "step-header"
|
|
1263
|
+
};
|
|
1264
|
+
const _hoisted_5$2 = { class: "step-title" };
|
|
1265
|
+
const _hoisted_6$2 = {
|
|
1266
|
+
key: 0,
|
|
1267
|
+
class: "step-description"
|
|
1268
|
+
};
|
|
1269
|
+
const _hoisted_7$1 = { class: "step-form-items" };
|
|
1270
|
+
const _hoisted_8$1 = { class: "steps-actions" };
|
|
1271
|
+
var index_vue_vue_type_script_setup_true_lang_default$3 = /* @__PURE__ */ defineComponent({
|
|
1272
|
+
__name: "index",
|
|
1273
|
+
props: {
|
|
1274
|
+
formItems: {},
|
|
1275
|
+
layoutConfig: { default: () => ({}) },
|
|
1276
|
+
options: { default: () => [] }
|
|
1277
|
+
},
|
|
1278
|
+
emits: [
|
|
1279
|
+
"step-change",
|
|
1280
|
+
"step-before-change",
|
|
1281
|
+
"step-validate"
|
|
1282
|
+
],
|
|
1283
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
1284
|
+
const props = __props;
|
|
1285
|
+
const emit = __emit;
|
|
1286
|
+
const currentStep = ref(0);
|
|
1287
|
+
const loading = ref(false);
|
|
1288
|
+
const stepValidationStatus = reactive({});
|
|
1289
|
+
const stepsConfig = computed(() => {
|
|
1290
|
+
const config = props.layoutConfig?.steps || {};
|
|
1291
|
+
return {
|
|
1292
|
+
steps: config.steps || [],
|
|
1293
|
+
vertical: config.vertical || false,
|
|
1294
|
+
size: config.size || "medium",
|
|
1295
|
+
showStepHeader: config.showStepHeader !== false,
|
|
1296
|
+
validateBeforeNext: config.validateBeforeNext || false,
|
|
1297
|
+
prevButtonText: config.prevButtonText || "上一步",
|
|
1298
|
+
nextButtonText: config.nextButtonText || "下一步",
|
|
1299
|
+
defaultStep: config.defaultStep || 0
|
|
1300
|
+
};
|
|
1301
|
+
});
|
|
1302
|
+
const hasSteps = computed(() => {
|
|
1303
|
+
return stepsConfig.value.steps.length > 0;
|
|
1304
|
+
});
|
|
1305
|
+
const stepsWithItems = computed(() => {
|
|
1306
|
+
if (!hasSteps.value) return [];
|
|
1307
|
+
const stepMap = /* @__PURE__ */ new Map();
|
|
1308
|
+
stepsConfig.value.steps.forEach((step) => {
|
|
1309
|
+
stepMap.set(step.key, []);
|
|
1310
|
+
});
|
|
1311
|
+
props.formItems.forEach((item, index) => {
|
|
1312
|
+
const stepKey = (props.options?.[index])?.layout?.step || stepsConfig.value.steps[0]?.key || "default";
|
|
1313
|
+
if (!stepMap.has(stepKey)) stepMap.set(stepKey, []);
|
|
1314
|
+
stepMap.get(stepKey).push(item);
|
|
1315
|
+
});
|
|
1316
|
+
return stepsConfig.value.steps.map((stepConfig) => ({
|
|
1317
|
+
config: stepConfig,
|
|
1318
|
+
items: stepMap.get(stepConfig.key) || []
|
|
1319
|
+
})).filter((step) => step.items.length > 0);
|
|
1320
|
+
});
|
|
1321
|
+
const stepStatus = computed(() => {
|
|
1322
|
+
for (let i = 0; i <= currentStep.value; i++) if (stepValidationStatus[i] === false) return "error";
|
|
1323
|
+
return "process";
|
|
1324
|
+
});
|
|
1325
|
+
const isFirstStep = computed(() => {
|
|
1326
|
+
return currentStep.value === 0;
|
|
1327
|
+
});
|
|
1328
|
+
const isLastStep = computed(() => {
|
|
1329
|
+
return currentStep.value === stepsWithItems.value.length - 1;
|
|
1330
|
+
});
|
|
1331
|
+
const getItemKey = (item, index) => {
|
|
1332
|
+
if (item.key != null) return String(item.key);
|
|
1333
|
+
const itemProps = item.props;
|
|
1334
|
+
if (itemProps?.path) return itemProps.path;
|
|
1335
|
+
return `step-item-${index}`;
|
|
1336
|
+
};
|
|
1337
|
+
const validateCurrentStep = async () => {
|
|
1338
|
+
try {
|
|
1339
|
+
const valid = await Promise.resolve(emit("step-validate", currentStep.value)) !== false;
|
|
1340
|
+
stepValidationStatus[currentStep.value] = valid;
|
|
1341
|
+
return valid;
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
console.error("[Steps Layout] 步骤验证失败:", error);
|
|
1344
|
+
stepValidationStatus[currentStep.value] = false;
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
const switchToStep = async (targetStep, needValidation = false) => {
|
|
1349
|
+
if (targetStep < 0 || targetStep >= stepsWithItems.value.length) return false;
|
|
1350
|
+
if (targetStep === currentStep.value) return true;
|
|
1351
|
+
try {
|
|
1352
|
+
loading.value = true;
|
|
1353
|
+
if (needValidation && stepsConfig.value.validateBeforeNext) {
|
|
1354
|
+
if (!await validateCurrentStep()) return false;
|
|
1355
|
+
}
|
|
1356
|
+
await emit("step-before-change", currentStep.value, targetStep);
|
|
1357
|
+
currentStep.value = targetStep;
|
|
1358
|
+
emit("step-change", currentStep.value, stepsWithItems.value[currentStep.value].config.key);
|
|
1359
|
+
return true;
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
console.error("[Steps Layout] 步骤切换失败:", error);
|
|
1362
|
+
return false;
|
|
1363
|
+
} finally {
|
|
1364
|
+
loading.value = false;
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
const handleNextStep = async () => {
|
|
1368
|
+
await switchToStep(currentStep.value + 1, true);
|
|
1369
|
+
};
|
|
1370
|
+
const handlePreviousStep = () => {
|
|
1371
|
+
switchToStep(currentStep.value - 1);
|
|
1372
|
+
};
|
|
1373
|
+
const goToStep = async (stepIndex) => {
|
|
1374
|
+
if (stepsWithItems.value[stepIndex]?.config.disabled) return;
|
|
1375
|
+
await switchToStep(stepIndex, stepIndex > currentStep.value);
|
|
1376
|
+
};
|
|
1377
|
+
const initializeCurrentStep = () => {
|
|
1378
|
+
if (!hasSteps.value || stepsWithItems.value.length === 0) return;
|
|
1379
|
+
const { defaultStep } = stepsConfig.value;
|
|
1380
|
+
currentStep.value = defaultStep >= 0 && defaultStep < stepsWithItems.value.length && !stepsWithItems.value[defaultStep]?.config.disabled ? defaultStep : 0;
|
|
1381
|
+
};
|
|
1382
|
+
watch(computed(() => stepsConfig.value.steps.map((s) => s.key).join(",")), () => {
|
|
1383
|
+
initializeCurrentStep();
|
|
1384
|
+
});
|
|
1385
|
+
onMounted(() => {
|
|
1386
|
+
initializeCurrentStep();
|
|
1387
|
+
});
|
|
1388
|
+
__expose({
|
|
1389
|
+
nextStep: handleNextStep,
|
|
1390
|
+
previousStep: handlePreviousStep,
|
|
1391
|
+
goToStep,
|
|
1392
|
+
validateCurrentStep,
|
|
1393
|
+
currentStep: readonly(currentStep),
|
|
1394
|
+
totalSteps: computed(() => stepsWithItems.value.length)
|
|
1395
|
+
});
|
|
1396
|
+
return (_ctx, _cache) => {
|
|
1397
|
+
const _component_NStep = resolveComponent("NStep");
|
|
1398
|
+
const _component_NSteps = resolveComponent("NSteps");
|
|
1399
|
+
const _component_NCard = resolveComponent("NCard");
|
|
1400
|
+
const _component_NButton = resolveComponent("NButton");
|
|
1401
|
+
const _component_NSpace = resolveComponent("NSpace");
|
|
1402
|
+
return openBlock(), createElementBlock("div", _hoisted_1$2, [createCommentVNode(" 无步骤配置时的单一面板模式 "), !hasSteps.value ? (openBlock(), createElementBlock("div", _hoisted_2$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.formItems, (item, index) => {
|
|
1403
|
+
return openBlock(), createBlock(resolveDynamicComponent(item), { key: getItemKey(item, index) });
|
|
1404
|
+
}), 128))])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" 有步骤配置时的分步骤模式 "), createElementVNode("div", _hoisted_3$2, [
|
|
1405
|
+
createCommentVNode(" 步骤指示器 "),
|
|
1406
|
+
createVNode(_component_NSteps, {
|
|
1407
|
+
current: currentStep.value + 1,
|
|
1408
|
+
status: stepStatus.value,
|
|
1409
|
+
size: stepsConfig.value.size,
|
|
1410
|
+
vertical: stepsConfig.value.vertical,
|
|
1411
|
+
class: "steps-indicator"
|
|
1412
|
+
}, {
|
|
1413
|
+
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(stepsWithItems.value, (step) => {
|
|
1414
|
+
return openBlock(), createBlock(_component_NStep, {
|
|
1415
|
+
key: step.config.key,
|
|
1416
|
+
title: step.config.title,
|
|
1417
|
+
description: step.config.description,
|
|
1418
|
+
disabled: step.config.disabled
|
|
1419
|
+
}, null, 8, [
|
|
1420
|
+
"title",
|
|
1421
|
+
"description",
|
|
1422
|
+
"disabled"
|
|
1423
|
+
]);
|
|
1424
|
+
}), 128))]),
|
|
1425
|
+
_: 1
|
|
1426
|
+
}, 8, [
|
|
1427
|
+
"current",
|
|
1428
|
+
"status",
|
|
1429
|
+
"size",
|
|
1430
|
+
"vertical"
|
|
1431
|
+
]),
|
|
1432
|
+
createCommentVNode(" 步骤内容区域 "),
|
|
1433
|
+
createVNode(_component_NCard, {
|
|
1434
|
+
class: "steps-content",
|
|
1435
|
+
bordered: false
|
|
1436
|
+
}, {
|
|
1437
|
+
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(stepsWithItems.value, (step, index) => {
|
|
1438
|
+
return withDirectives((openBlock(), createElementBlock("div", {
|
|
1439
|
+
key: step.config.key,
|
|
1440
|
+
class: "step-panel"
|
|
1441
|
+
}, [
|
|
1442
|
+
createCommentVNode(" 步骤标题和描述 "),
|
|
1443
|
+
stepsConfig.value.showStepHeader ? (openBlock(), createElementBlock("div", _hoisted_4$2, [createElementVNode("h3", _hoisted_5$2, toDisplayString(step.config.title), 1), step.config.description ? (openBlock(), createElementBlock("p", _hoisted_6$2, toDisplayString(step.config.description), 1)) : createCommentVNode("v-if", true)])) : createCommentVNode("v-if", true),
|
|
1444
|
+
createCommentVNode(" 步骤内的表单项 "),
|
|
1445
|
+
createElementVNode("div", _hoisted_7$1, [(openBlock(true), createElementBlock(Fragment, null, renderList(step.items, (item, itemIndex) => {
|
|
1446
|
+
return openBlock(), createBlock(resolveDynamicComponent(item), { key: getItemKey(item, itemIndex) });
|
|
1447
|
+
}), 128))])
|
|
1448
|
+
])), [[vShow, currentStep.value === index]]);
|
|
1449
|
+
}), 128))]),
|
|
1450
|
+
_: 1
|
|
1451
|
+
}),
|
|
1452
|
+
createCommentVNode(" 步骤操作按钮 "),
|
|
1453
|
+
createElementVNode("div", _hoisted_8$1, [createVNode(_component_NSpace, { justify: "space-between" }, {
|
|
1454
|
+
default: withCtx(() => [
|
|
1455
|
+
currentStep.value > 0 ? (openBlock(), createBlock(_component_NButton, {
|
|
1456
|
+
key: 0,
|
|
1457
|
+
disabled: loading.value,
|
|
1458
|
+
onClick: handlePreviousStep
|
|
1459
|
+
}, {
|
|
1460
|
+
default: withCtx(() => [createVNode(C_Icon_default, {
|
|
1461
|
+
name: "mdi:chevron-left-first",
|
|
1462
|
+
size: 16,
|
|
1463
|
+
style: { "margin-right": "4px" }
|
|
1464
|
+
}), createTextVNode(" " + toDisplayString(stepsConfig.value.prevButtonText), 1)]),
|
|
1465
|
+
_: 1
|
|
1466
|
+
}, 8, ["disabled"])) : createCommentVNode("v-if", true),
|
|
1467
|
+
_cache[0] || (_cache[0] = createElementVNode("div", null, null, -1)),
|
|
1468
|
+
createVNode(_component_NSpace, null, {
|
|
1469
|
+
default: withCtx(() => [currentStep.value < stepsWithItems.value.length - 1 ? (openBlock(), createBlock(_component_NButton, {
|
|
1470
|
+
key: 0,
|
|
1471
|
+
type: "primary",
|
|
1472
|
+
loading: loading.value,
|
|
1473
|
+
onClick: handleNextStep
|
|
1474
|
+
}, {
|
|
1475
|
+
default: withCtx(() => [createTextVNode(toDisplayString(stepsConfig.value.nextButtonText) + " ", 1), createVNode(C_Icon_default, {
|
|
1476
|
+
name: "mdi:chevron-right-last",
|
|
1477
|
+
size: 16,
|
|
1478
|
+
style: { "margin-left": "4px" }
|
|
1479
|
+
})]),
|
|
1480
|
+
_: 1
|
|
1481
|
+
}, 8, ["loading"])) : createCommentVNode("v-if", true), renderSlot(_ctx.$slots, "step-actions", {
|
|
1482
|
+
currentStep: currentStep.value,
|
|
1483
|
+
totalSteps: stepsWithItems.value.length,
|
|
1484
|
+
isFirstStep: isFirstStep.value,
|
|
1485
|
+
isLastStep: isLastStep.value,
|
|
1486
|
+
nextStep: handleNextStep,
|
|
1487
|
+
previousStep: handlePreviousStep,
|
|
1488
|
+
goToStep
|
|
1489
|
+
}, void 0, true)]),
|
|
1490
|
+
_: 3
|
|
1491
|
+
})
|
|
1492
|
+
]),
|
|
1493
|
+
_: 3,
|
|
1494
|
+
__: [0]
|
|
1495
|
+
})])
|
|
1496
|
+
])], 2112))]);
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
//#endregion
|
|
1502
|
+
//#region src/components/C_Form/layouts/Steps/index.vue
|
|
1503
|
+
var Steps_default = /* @__PURE__ */ export_helper_default(index_vue_vue_type_script_setup_true_lang_default$3, [["__scopeId", "data-v-c0439a30"]]);
|
|
1504
|
+
|
|
1505
|
+
//#endregion
|
|
1506
|
+
//#region src/components/C_Form/composables/useDynamicFormState.ts
|
|
1507
|
+
/**
|
|
1508
|
+
* @description 动态表单状态管理组合式函数
|
|
1509
|
+
* @module useDynamicFormState
|
|
1510
|
+
* @Migration: naive-ui-components 组件库迁移版本
|
|
1511
|
+
* @returns 包含动态表单状态和操作方法的对象
|
|
1512
|
+
*/
|
|
1513
|
+
/**
|
|
1514
|
+
* @description 默认表单配置
|
|
1515
|
+
*/
|
|
1516
|
+
const DEFAULT_CONFIG = {
|
|
1517
|
+
maxFields: 20,
|
|
1518
|
+
autoSave: false,
|
|
1519
|
+
enableSort: true,
|
|
1520
|
+
showControls: true,
|
|
1521
|
+
showItemControls: true
|
|
1522
|
+
};
|
|
1523
|
+
/**
|
|
1524
|
+
* @description 可用的字段类型选项
|
|
1525
|
+
*/
|
|
1526
|
+
const FIELD_TYPE_OPTIONS = [
|
|
1527
|
+
{
|
|
1528
|
+
label: "文本输入",
|
|
1529
|
+
value: "input"
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
label: "数字输入",
|
|
1533
|
+
value: "inputNumber"
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
label: "多行文本",
|
|
1537
|
+
value: "textarea"
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
label: "下拉选择",
|
|
1541
|
+
value: "select"
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
label: "开关切换",
|
|
1545
|
+
value: "switch"
|
|
1546
|
+
},
|
|
1547
|
+
{
|
|
1548
|
+
label: "评分组件",
|
|
1549
|
+
value: "rate"
|
|
1550
|
+
}
|
|
1551
|
+
];
|
|
1552
|
+
/**
|
|
1553
|
+
* @description 创建和管理动态表单状态
|
|
1554
|
+
* @returns 包含状态和方法的对象
|
|
1555
|
+
*/
|
|
1556
|
+
const useDynamicFormState = () => {
|
|
1557
|
+
/**
|
|
1558
|
+
* @description 响应式表单状态
|
|
1559
|
+
*/
|
|
1560
|
+
const state = reactive({
|
|
1561
|
+
config: { ...DEFAULT_CONFIG },
|
|
1562
|
+
baseFields: [],
|
|
1563
|
+
dynamicFields: [],
|
|
1564
|
+
hiddenFieldIds: /* @__PURE__ */ new Set(),
|
|
1565
|
+
fieldCounter: 0,
|
|
1566
|
+
isInitialized: false
|
|
1567
|
+
});
|
|
1568
|
+
/**
|
|
1569
|
+
* @description 计算所有字段(基础字段+动态字段)
|
|
1570
|
+
*/
|
|
1571
|
+
const allFields = computed(() => [...state.baseFields, ...state.dynamicFields.map((field) => ({
|
|
1572
|
+
...field,
|
|
1573
|
+
show: !state.hiddenFieldIds.has(field.prop)
|
|
1574
|
+
}))]);
|
|
1575
|
+
/**
|
|
1576
|
+
* @description 计算可见字段
|
|
1577
|
+
*/
|
|
1578
|
+
const visibleFields = computed(() => allFields.value.filter((field) => field.show !== false));
|
|
1579
|
+
/**
|
|
1580
|
+
* @description 计算动态字段数量
|
|
1581
|
+
*/
|
|
1582
|
+
const dynamicFieldsCount = computed(() => state.dynamicFields.length);
|
|
1583
|
+
/**
|
|
1584
|
+
* @description 计算隐藏字段数量
|
|
1585
|
+
*/
|
|
1586
|
+
const hiddenFieldsCount = computed(() => state.hiddenFieldIds.size);
|
|
1587
|
+
/**
|
|
1588
|
+
* @description 是否可以添加更多字段
|
|
1589
|
+
*/
|
|
1590
|
+
const canAddMoreFields = computed(() => state.dynamicFields.length < state.config.maxFields);
|
|
1591
|
+
/**
|
|
1592
|
+
* @description 是否所有字段都可见
|
|
1593
|
+
*/
|
|
1594
|
+
const allVisible = computed(() => state.hiddenFieldIds.size === 0);
|
|
1595
|
+
/**
|
|
1596
|
+
* @description 添加动态字段
|
|
1597
|
+
* @param config - 字段配置
|
|
1598
|
+
*/
|
|
1599
|
+
const addField = (config = {}) => {
|
|
1600
|
+
if (!canAddMoreFields.value) {
|
|
1601
|
+
console.warn("[useDynamicFormState] 已达到最大字段数量限制");
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
state.fieldCounter++;
|
|
1605
|
+
const defaultType = config.type || FIELD_TYPE_OPTIONS[Math.floor(Math.random() * FIELD_TYPE_OPTIONS.length)].value;
|
|
1606
|
+
const newField = {
|
|
1607
|
+
id: `dynamic_field_${state.fieldCounter}`,
|
|
1608
|
+
type: defaultType,
|
|
1609
|
+
prop: config.prop || `dynamic_${state.fieldCounter}`,
|
|
1610
|
+
label: config.label || `动态字段 ${state.fieldCounter}`,
|
|
1611
|
+
placeholder: config.placeholder || `请输入${config.label || "内容"}`,
|
|
1612
|
+
visible: true,
|
|
1613
|
+
removable: true,
|
|
1614
|
+
created: Date.now(),
|
|
1615
|
+
layout: config.layout || { span: 12 },
|
|
1616
|
+
...config
|
|
1617
|
+
};
|
|
1618
|
+
state.dynamicFields.push(newField);
|
|
1619
|
+
console.log("[useDynamicFormState] 添加字段:", newField);
|
|
1620
|
+
};
|
|
1621
|
+
/**
|
|
1622
|
+
* @description 移除动态字段
|
|
1623
|
+
* @param index - 可选,要移除的字段索引,默认移除最后一个
|
|
1624
|
+
*/
|
|
1625
|
+
const removeField = (index) => {
|
|
1626
|
+
if (state.dynamicFields.length === 0) {
|
|
1627
|
+
console.warn("[useDynamicFormState] 没有可移除的动态字段");
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
const targetIndex = index ?? state.dynamicFields.length - 1;
|
|
1631
|
+
if (targetIndex < 0 || targetIndex >= state.dynamicFields.length) {
|
|
1632
|
+
console.warn("[useDynamicFormState] 字段索引超出范围");
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
const removed = state.dynamicFields.splice(targetIndex, 1)[0];
|
|
1636
|
+
if (removed) {
|
|
1637
|
+
state.hiddenFieldIds.delete(removed.prop);
|
|
1638
|
+
console.log("[useDynamicFormState] 移除字段:", removed.prop);
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
/**
|
|
1642
|
+
* @description 清空所有动态字段
|
|
1643
|
+
*/
|
|
1644
|
+
const clearDynamicFields = () => {
|
|
1645
|
+
console.log("[useDynamicFormState] 清空动态字段:", state.dynamicFields.length);
|
|
1646
|
+
state.dynamicFields.forEach((field) => state.hiddenFieldIds.delete(field.prop));
|
|
1647
|
+
state.dynamicFields.length = 0;
|
|
1648
|
+
state.fieldCounter = 0;
|
|
1649
|
+
};
|
|
1650
|
+
/**
|
|
1651
|
+
* @description 切换字段可见性
|
|
1652
|
+
* @param fieldId - 字段ID
|
|
1653
|
+
*/
|
|
1654
|
+
const toggleFieldVisibility = (fieldId) => {
|
|
1655
|
+
if (state.hiddenFieldIds.has(fieldId)) {
|
|
1656
|
+
state.hiddenFieldIds.delete(fieldId);
|
|
1657
|
+
console.log("[useDynamicFormState] 显示字段:", fieldId);
|
|
1658
|
+
} else {
|
|
1659
|
+
state.hiddenFieldIds.add(fieldId);
|
|
1660
|
+
console.log("[useDynamicFormState] 隐藏字段:", fieldId);
|
|
1661
|
+
}
|
|
1662
|
+
};
|
|
1663
|
+
/**
|
|
1664
|
+
* @description 切换所有字段可见性
|
|
1665
|
+
*/
|
|
1666
|
+
const toggleAllVisibility = () => {
|
|
1667
|
+
if (allVisible.value) {
|
|
1668
|
+
state.dynamicFields.forEach((field) => {
|
|
1669
|
+
state.hiddenFieldIds.add(field.prop);
|
|
1670
|
+
});
|
|
1671
|
+
console.log("[useDynamicFormState] 隐藏所有动态字段");
|
|
1672
|
+
} else {
|
|
1673
|
+
state.hiddenFieldIds.clear();
|
|
1674
|
+
console.log("[useDynamicFormState] 显示所有字段");
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
/**
|
|
1678
|
+
* @description 更新表单配置
|
|
1679
|
+
* @param newConfig - 新的配置对象
|
|
1680
|
+
*/
|
|
1681
|
+
const updateConfig = (newConfig) => {
|
|
1682
|
+
Object.assign(state.config, newConfig);
|
|
1683
|
+
console.log("[useDynamicFormState] 更新配置:", newConfig);
|
|
1684
|
+
};
|
|
1685
|
+
/**
|
|
1686
|
+
* @description 导出当前表单配置
|
|
1687
|
+
* @returns JSON格式的配置字符串
|
|
1688
|
+
*/
|
|
1689
|
+
const exportConfig = () => {
|
|
1690
|
+
const config = {
|
|
1691
|
+
baseFields: state.baseFields,
|
|
1692
|
+
dynamicFields: state.dynamicFields,
|
|
1693
|
+
config: state.config,
|
|
1694
|
+
hiddenFields: Array.from(state.hiddenFieldIds),
|
|
1695
|
+
timestamp: Date.now()
|
|
1696
|
+
};
|
|
1697
|
+
return JSON.stringify(config, null, 2);
|
|
1698
|
+
};
|
|
1699
|
+
/**
|
|
1700
|
+
* @description 初始化表单状态
|
|
1701
|
+
* @param baseFields - 基础字段配置
|
|
1702
|
+
* @param config - 可选,表单配置
|
|
1703
|
+
*/
|
|
1704
|
+
const initialize = (baseFields, config = {}) => {
|
|
1705
|
+
console.log("[useDynamicFormState] 初始化状态:", {
|
|
1706
|
+
baseFieldsCount: baseFields.length,
|
|
1707
|
+
config
|
|
1708
|
+
});
|
|
1709
|
+
state.baseFields = [...baseFields];
|
|
1710
|
+
Object.assign(state.config, config);
|
|
1711
|
+
state.isInitialized = true;
|
|
1712
|
+
console.log("[useDynamicFormState] 初始化完成");
|
|
1713
|
+
};
|
|
1714
|
+
return {
|
|
1715
|
+
state: readonly(state),
|
|
1716
|
+
allFields,
|
|
1717
|
+
visibleFields,
|
|
1718
|
+
dynamicFieldsCount,
|
|
1719
|
+
hiddenFieldsCount,
|
|
1720
|
+
canAddMoreFields,
|
|
1721
|
+
allVisible,
|
|
1722
|
+
FIELD_TYPE_OPTIONS,
|
|
1723
|
+
addField,
|
|
1724
|
+
removeField,
|
|
1725
|
+
clearDynamicFields,
|
|
1726
|
+
toggleFieldVisibility,
|
|
1727
|
+
toggleAllVisibility,
|
|
1728
|
+
updateConfig,
|
|
1729
|
+
exportConfig,
|
|
1730
|
+
initialize
|
|
1731
|
+
};
|
|
1732
|
+
};
|
|
1733
|
+
/**
|
|
1734
|
+
* @description 动态表单状态注入键
|
|
1735
|
+
*/
|
|
1736
|
+
const DYNAMIC_FORM_STATE_KEY = Symbol("dynamicFormState");
|
|
1737
|
+
|
|
1738
|
+
//#endregion
|
|
1739
|
+
//#region src/components/C_Form/layouts/Dynamic/index.vue?vue&type=script&setup=true&lang.ts
|
|
1740
|
+
const _hoisted_1$1 = { class: "c-form-dynamic" };
|
|
1741
|
+
const _hoisted_2$1 = {
|
|
1742
|
+
key: 0,
|
|
1743
|
+
class: "dynamic-controls"
|
|
1744
|
+
};
|
|
1745
|
+
const _hoisted_3$1 = { class: "max-fields-config" };
|
|
1746
|
+
const _hoisted_4$1 = { class: "dynamic-stats" };
|
|
1747
|
+
const _hoisted_5$1 = { class: "dynamic-controls-fallback" };
|
|
1748
|
+
const _hoisted_6$1 = { class: "dynamic-form-items" };
|
|
1749
|
+
var index_vue_vue_type_script_setup_true_lang_default$2 = /* @__PURE__ */ defineComponent({
|
|
1750
|
+
__name: "index",
|
|
1751
|
+
props: {
|
|
1752
|
+
formItems: {},
|
|
1753
|
+
layoutConfig: { default: () => ({}) },
|
|
1754
|
+
options: { default: () => [] },
|
|
1755
|
+
dynamicFormState: { default: null }
|
|
1756
|
+
},
|
|
1757
|
+
emits: [
|
|
1758
|
+
"field-add",
|
|
1759
|
+
"field-remove",
|
|
1760
|
+
"fields-clear"
|
|
1761
|
+
],
|
|
1762
|
+
setup(__props, { expose: __expose }) {
|
|
1763
|
+
const props = __props;
|
|
1764
|
+
const injectedDynamicState = inject(DYNAMIC_FORM_STATE_KEY, null);
|
|
1765
|
+
const dynamicState = computed(() => props.dynamicFormState || injectedDynamicState);
|
|
1766
|
+
const isDynamicStateAvailable = computed(() => !!dynamicState.value);
|
|
1767
|
+
const gridCols = computed(() => props.layoutConfig?.dynamic?.grid?.cols ?? 24);
|
|
1768
|
+
const gridGutter = computed(() => props.layoutConfig?.dynamic?.grid?.gutter ?? 16);
|
|
1769
|
+
const showControls = computed(() => props.layoutConfig?.dynamic?.controls?.showControls !== false);
|
|
1770
|
+
const maxFields = computed(() => {
|
|
1771
|
+
return dynamicState.value?.state.config.maxFields ?? props.layoutConfig?.dynamic?.dynamic?.maxFields ?? 50;
|
|
1772
|
+
});
|
|
1773
|
+
const dynamicFieldsCount = computed(() => dynamicState.value?.dynamicFieldsCount.value ?? 0);
|
|
1774
|
+
const canAddMoreFields = computed(() => dynamicState.value?.canAddMoreFields.value ?? false);
|
|
1775
|
+
const totalFieldsCount = computed(() => props.formItems.length);
|
|
1776
|
+
const getItemKey = (item, index) => {
|
|
1777
|
+
return item.key?.toString() || item.props?.path || `dynamic-item-${index}`;
|
|
1778
|
+
};
|
|
1779
|
+
const getItemSpan = (index) => {
|
|
1780
|
+
const span = props.options?.[index]?.layout?.span;
|
|
1781
|
+
return typeof span === "number" && span > 0 && span <= gridCols.value ? span : Math.min(12, gridCols.value);
|
|
1782
|
+
};
|
|
1783
|
+
const isDynamicField = (item) => {
|
|
1784
|
+
if (!dynamicState.value) return false;
|
|
1785
|
+
const fieldId = item.props?.path || item.key?.toString() || "";
|
|
1786
|
+
return dynamicState.value.state.dynamicFields.some((field) => field.prop === fieldId);
|
|
1787
|
+
};
|
|
1788
|
+
__expose({
|
|
1789
|
+
addField: () => dynamicState.value?.addField(),
|
|
1790
|
+
removeField: () => dynamicState.value?.removeField(),
|
|
1791
|
+
clearAllDynamic: () => dynamicState.value?.clearDynamicFields(),
|
|
1792
|
+
isDynamicStateAvailable,
|
|
1793
|
+
dynamicState
|
|
1794
|
+
});
|
|
1795
|
+
if (typeof import.meta !== "undefined" && import.meta.env?.DEV) watchEffect(() => {
|
|
1796
|
+
const { options, formItems } = props;
|
|
1797
|
+
if (options && options.length !== formItems.length) console.warn(`[Dynamic Layout] 配置项数量(${options.length})与表单项数量(${formItems.length})不匹配`);
|
|
1798
|
+
console.log("[Dynamic Layout]", isDynamicStateAvailable.value ? "动态模式已启用" : "运行在静态模式", {
|
|
1799
|
+
totalFields: totalFieldsCount.value,
|
|
1800
|
+
dynamicFields: dynamicFieldsCount.value,
|
|
1801
|
+
stateSource: props.dynamicFormState ? "props透传" : "inject注入"
|
|
1802
|
+
});
|
|
1803
|
+
});
|
|
1804
|
+
return (_ctx, _cache) => {
|
|
1805
|
+
const _component_NBadge = resolveComponent("NBadge");
|
|
1806
|
+
const _component_NButton = resolveComponent("NButton");
|
|
1807
|
+
const _component_NSpace = resolveComponent("NSpace");
|
|
1808
|
+
const _component_NInputNumber = resolveComponent("NInputNumber");
|
|
1809
|
+
const _component_NTag = resolveComponent("NTag");
|
|
1810
|
+
const _component_NCard = resolveComponent("NCard");
|
|
1811
|
+
const _component_NAlert = resolveComponent("NAlert");
|
|
1812
|
+
const _component_NGridItem = resolveComponent("NGridItem");
|
|
1813
|
+
const _component_NGrid = resolveComponent("NGrid");
|
|
1814
|
+
return openBlock(), createElementBlock("div", _hoisted_1$1, [
|
|
1815
|
+
createCommentVNode(" 动态控制面板 "),
|
|
1816
|
+
showControls.value && isDynamicStateAvailable.value ? (openBlock(), createElementBlock("div", _hoisted_2$1, [createVNode(_component_NCard, {
|
|
1817
|
+
size: "small",
|
|
1818
|
+
title: "动态表单控制",
|
|
1819
|
+
bordered: false
|
|
1820
|
+
}, {
|
|
1821
|
+
"header-extra": withCtx(() => [createVNode(_component_NBadge, {
|
|
1822
|
+
value: dynamicFieldsCount.value,
|
|
1823
|
+
type: "warning"
|
|
1824
|
+
}, {
|
|
1825
|
+
default: withCtx(() => [createVNode(C_Icon_default, {
|
|
1826
|
+
name: "mdi:layers",
|
|
1827
|
+
size: 16
|
|
1828
|
+
})]),
|
|
1829
|
+
_: 1
|
|
1830
|
+
}, 8, ["value"])]),
|
|
1831
|
+
default: withCtx(() => [createVNode(_component_NSpace, {
|
|
1832
|
+
justify: "space-between",
|
|
1833
|
+
align: "center"
|
|
1834
|
+
}, {
|
|
1835
|
+
default: withCtx(() => [
|
|
1836
|
+
createVNode(_component_NSpace, null, {
|
|
1837
|
+
default: withCtx(() => [
|
|
1838
|
+
createVNode(_component_NButton, {
|
|
1839
|
+
size: "small",
|
|
1840
|
+
type: "primary",
|
|
1841
|
+
disabled: !canAddMoreFields.value,
|
|
1842
|
+
onClick: _cache[0] || (_cache[0] = ($event) => dynamicState.value.addField())
|
|
1843
|
+
}, {
|
|
1844
|
+
icon: withCtx(() => [createVNode(C_Icon_default, {
|
|
1845
|
+
name: "mdi:plus",
|
|
1846
|
+
size: 14
|
|
1847
|
+
})]),
|
|
1848
|
+
default: withCtx(() => [createTextVNode(" 添加字段 (" + toDisplayString(dynamicFieldsCount.value) + "/" + toDisplayString(maxFields.value) + ") ", 1)]),
|
|
1849
|
+
_: 1
|
|
1850
|
+
}, 8, ["disabled"]),
|
|
1851
|
+
createVNode(_component_NButton, {
|
|
1852
|
+
size: "small",
|
|
1853
|
+
type: "warning",
|
|
1854
|
+
disabled: dynamicFieldsCount.value === 0,
|
|
1855
|
+
onClick: _cache[1] || (_cache[1] = ($event) => dynamicState.value.removeField())
|
|
1856
|
+
}, {
|
|
1857
|
+
icon: withCtx(() => [createVNode(C_Icon_default, {
|
|
1858
|
+
name: "mdi:minus",
|
|
1859
|
+
size: 14
|
|
1860
|
+
})]),
|
|
1861
|
+
default: withCtx(() => [_cache[4] || (_cache[4] = createTextVNode(" 移除字段 ", -1))]),
|
|
1862
|
+
_: 1,
|
|
1863
|
+
__: [4]
|
|
1864
|
+
}, 8, ["disabled"]),
|
|
1865
|
+
createVNode(_component_NButton, {
|
|
1866
|
+
size: "small",
|
|
1867
|
+
type: "error",
|
|
1868
|
+
disabled: dynamicFieldsCount.value === 0,
|
|
1869
|
+
onClick: _cache[2] || (_cache[2] = ($event) => dynamicState.value.clearDynamicFields())
|
|
1870
|
+
}, {
|
|
1871
|
+
icon: withCtx(() => [createVNode(C_Icon_default, {
|
|
1872
|
+
name: "mdi:delete-sweep",
|
|
1873
|
+
size: 14
|
|
1874
|
+
})]),
|
|
1875
|
+
default: withCtx(() => [_cache[5] || (_cache[5] = createTextVNode(" 清空动态 ", -1))]),
|
|
1876
|
+
_: 1,
|
|
1877
|
+
__: [5]
|
|
1878
|
+
}, 8, ["disabled"])
|
|
1879
|
+
]),
|
|
1880
|
+
_: 1
|
|
1881
|
+
}),
|
|
1882
|
+
createCommentVNode(" 最大字段数配置 "),
|
|
1883
|
+
createElementVNode("div", _hoisted_3$1, [_cache[6] || (_cache[6] = createElementVNode("span", null, "最大字段数:", -1)), createVNode(_component_NInputNumber, {
|
|
1884
|
+
value: maxFields.value,
|
|
1885
|
+
"onUpdate:value": _cache[3] || (_cache[3] = (v) => v && dynamicState.value.updateConfig({ maxFields: v })),
|
|
1886
|
+
min: 5,
|
|
1887
|
+
max: 50,
|
|
1888
|
+
size: "small",
|
|
1889
|
+
style: { "width": "100px" }
|
|
1890
|
+
}, null, 8, ["value"])])
|
|
1891
|
+
]),
|
|
1892
|
+
_: 1
|
|
1893
|
+
}), createElementVNode("div", _hoisted_4$1, [createVNode(_component_NSpace, null, {
|
|
1894
|
+
default: withCtx(() => [createVNode(_component_NTag, { type: "info" }, {
|
|
1895
|
+
default: withCtx(() => [createTextVNode("总字段: " + toDisplayString(totalFieldsCount.value), 1)]),
|
|
1896
|
+
_: 1
|
|
1897
|
+
}), createVNode(_component_NTag, { type: "warning" }, {
|
|
1898
|
+
default: withCtx(() => [createTextVNode("动态: " + toDisplayString(dynamicFieldsCount.value), 1)]),
|
|
1899
|
+
_: 1
|
|
1900
|
+
})]),
|
|
1901
|
+
_: 1
|
|
1902
|
+
})])]),
|
|
1903
|
+
_: 1
|
|
1904
|
+
})])) : showControls.value && !isDynamicStateAvailable.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" 状态不可用时的提示 "), createElementVNode("div", _hoisted_5$1, [createVNode(_component_NCard, {
|
|
1905
|
+
size: "small",
|
|
1906
|
+
title: "动态表单控制 (静态模式)",
|
|
1907
|
+
bordered: false
|
|
1908
|
+
}, {
|
|
1909
|
+
default: withCtx(() => [createVNode(_component_NAlert, {
|
|
1910
|
+
type: "info",
|
|
1911
|
+
"show-icon": ""
|
|
1912
|
+
}, {
|
|
1913
|
+
icon: withCtx(() => [createVNode(C_Icon_default, {
|
|
1914
|
+
name: "mdi:information-outline",
|
|
1915
|
+
size: 16
|
|
1916
|
+
})]),
|
|
1917
|
+
default: withCtx(() => [_cache[7] || (_cache[7] = createTextVNode(" 当前为静态模式,如需动态功能请在父组件中提供动态表单状态。 ", -1))]),
|
|
1918
|
+
_: 1,
|
|
1919
|
+
__: [7]
|
|
1920
|
+
})]),
|
|
1921
|
+
_: 1
|
|
1922
|
+
})])], 2112)) : createCommentVNode("v-if", true),
|
|
1923
|
+
createCommentVNode(" 表单项渲染区域 "),
|
|
1924
|
+
createElementVNode("div", _hoisted_6$1, [createVNode(_component_NGrid, {
|
|
1925
|
+
cols: gridCols.value,
|
|
1926
|
+
"x-gap": gridGutter.value,
|
|
1927
|
+
"y-gap": gridGutter.value
|
|
1928
|
+
}, {
|
|
1929
|
+
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.formItems, (item, index) => {
|
|
1930
|
+
return openBlock(), createBlock(_component_NGridItem, {
|
|
1931
|
+
key: getItemKey(item, index),
|
|
1932
|
+
span: getItemSpan(index)
|
|
1933
|
+
}, {
|
|
1934
|
+
default: withCtx(() => [createElementVNode("div", { class: normalizeClass(["dynamic-item-wrapper", { "is-dynamic-field": isDynamicField(item) }]) }, [(openBlock(), createBlock(resolveDynamicComponent(item)))], 2)]),
|
|
1935
|
+
_: 2
|
|
1936
|
+
}, 1032, ["span"]);
|
|
1937
|
+
}), 128))]),
|
|
1938
|
+
_: 1
|
|
1939
|
+
}, 8, [
|
|
1940
|
+
"cols",
|
|
1941
|
+
"x-gap",
|
|
1942
|
+
"y-gap"
|
|
1943
|
+
])])
|
|
1944
|
+
]);
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
});
|
|
1948
|
+
|
|
1949
|
+
//#endregion
|
|
1950
|
+
//#region src/components/C_Form/layouts/Dynamic/index.vue
|
|
1951
|
+
var Dynamic_default = /* @__PURE__ */ export_helper_default(index_vue_vue_type_script_setup_true_lang_default$2, [["__scopeId", "data-v-3f0739c2"]]);
|
|
1952
|
+
|
|
1953
|
+
//#endregion
|
|
1954
|
+
//#region src/components/C_Form/layouts/Custom/index.vue?vue&type=script&setup=true&lang.ts
|
|
1955
|
+
const _hoisted_1 = { class: "custom-layout" };
|
|
1956
|
+
const _hoisted_2 = { class: "toolbar-content" };
|
|
1957
|
+
const _hoisted_3 = { class: "mode-section" };
|
|
1958
|
+
const _hoisted_4 = { class: "stats-section" };
|
|
1959
|
+
const _hoisted_5 = { class: "stat-item" };
|
|
1960
|
+
const _hoisted_6 = { class: "stat-value" };
|
|
1961
|
+
const _hoisted_7 = { class: "stat-item" };
|
|
1962
|
+
const _hoisted_8 = { class: "stat-value" };
|
|
1963
|
+
const _hoisted_9 = { class: "actions-section" };
|
|
1964
|
+
const _hoisted_10 = { class: "design-tools" };
|
|
1965
|
+
const _hoisted_11 = { class: "tool-group" };
|
|
1966
|
+
const _hoisted_12 = {
|
|
1967
|
+
key: 0,
|
|
1968
|
+
class: "canvas-hint"
|
|
1969
|
+
};
|
|
1970
|
+
const _hoisted_13 = { class: "areas-container" };
|
|
1971
|
+
const _hoisted_14 = { class: "area-header" };
|
|
1972
|
+
const _hoisted_15 = { class: "area-info" };
|
|
1973
|
+
const _hoisted_16 = ["onClick"];
|
|
1974
|
+
const _hoisted_17 = { class: "area-controls" };
|
|
1975
|
+
const _hoisted_18 = { class: "area-fields" };
|
|
1976
|
+
const _hoisted_19 = { class: "field-preview" };
|
|
1977
|
+
const _hoisted_20 = { class: "field-label" };
|
|
1978
|
+
const _hoisted_21 = { class: "field-type" };
|
|
1979
|
+
const _hoisted_22 = {
|
|
1980
|
+
key: 0,
|
|
1981
|
+
class: "area-drop-zone"
|
|
1982
|
+
};
|
|
1983
|
+
const _hoisted_23 = { class: "pool-fields-grid" };
|
|
1984
|
+
const _hoisted_24 = { class: "field-name" };
|
|
1985
|
+
const _hoisted_25 = { class: "field-type-tag" };
|
|
1986
|
+
const _hoisted_26 = {
|
|
1987
|
+
key: 0,
|
|
1988
|
+
class: "empty-layout"
|
|
1989
|
+
};
|
|
1990
|
+
const _hoisted_27 = {
|
|
1991
|
+
key: 1,
|
|
1992
|
+
class: "form-container"
|
|
1993
|
+
};
|
|
1994
|
+
const _hoisted_28 = { class: "form-areas" };
|
|
1995
|
+
var index_vue_vue_type_script_setup_true_lang_default$1 = /* @__PURE__ */ defineComponent({
|
|
1996
|
+
__name: "index",
|
|
1997
|
+
props: {
|
|
1998
|
+
options: { default: () => [] },
|
|
1999
|
+
formItems: { default: () => [] },
|
|
2000
|
+
formData: { default: () => ({}) }
|
|
2001
|
+
},
|
|
2002
|
+
emits: ["fields-change", "export-data"],
|
|
2003
|
+
setup(__props, { emit: __emit }) {
|
|
2004
|
+
const props = __props;
|
|
2005
|
+
const emit = __emit;
|
|
2006
|
+
const isDesignMode = ref(true);
|
|
2007
|
+
const customAreas = ref([]);
|
|
2008
|
+
const availableFields = ref([]);
|
|
2009
|
+
const editingTitleId = ref("");
|
|
2010
|
+
const allFormOptions = computed(() => {
|
|
2011
|
+
if (props.options?.length > 0) return props.options;
|
|
2012
|
+
return props.formItems?.map((item) => {
|
|
2013
|
+
const itemProps = item.props;
|
|
2014
|
+
return {
|
|
2015
|
+
prop: itemProps?.path || "",
|
|
2016
|
+
label: itemProps?.label || itemProps?.path || "",
|
|
2017
|
+
type: "input",
|
|
2018
|
+
show: true
|
|
2019
|
+
};
|
|
2020
|
+
}).filter((option) => option.prop) || [];
|
|
2021
|
+
});
|
|
2022
|
+
const totalFieldsCount = computed(() => customAreas.value.reduce((total, area) => total + area.fields.length, 0));
|
|
2023
|
+
const generateId = () => `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
2024
|
+
const getFieldTypeName = (type) => {
|
|
2025
|
+
return {
|
|
2026
|
+
input: "输入框",
|
|
2027
|
+
select: "下拉框",
|
|
2028
|
+
radio: "单选框",
|
|
2029
|
+
checkbox: "复选框",
|
|
2030
|
+
textarea: "文本域",
|
|
2031
|
+
date: "日期",
|
|
2032
|
+
number: "数字"
|
|
2033
|
+
}[type] || type;
|
|
2034
|
+
};
|
|
2035
|
+
const getFormItemForField = (field) => {
|
|
2036
|
+
return props.formItems?.find((item) => {
|
|
2037
|
+
return item.props?.path === field.prop;
|
|
2038
|
+
}) || null;
|
|
2039
|
+
};
|
|
2040
|
+
const addArea = (type) => {
|
|
2041
|
+
const area = {
|
|
2042
|
+
id: generateId(),
|
|
2043
|
+
title: `${type === "horizontal" ? "水平" : type === "vertical" ? "垂直" : "网格"}区域`,
|
|
2044
|
+
type,
|
|
2045
|
+
fields: []
|
|
2046
|
+
};
|
|
2047
|
+
customAreas.value.push(area);
|
|
2048
|
+
};
|
|
2049
|
+
const deleteArea = (areaId) => {
|
|
2050
|
+
const index = customAreas.value.findIndex((area) => area.id === areaId);
|
|
2051
|
+
if (index !== -1) customAreas.value.splice(index, 1);
|
|
2052
|
+
};
|
|
2053
|
+
const handleSaveLayout = () => {
|
|
2054
|
+
const config = JSON.stringify(customAreas.value, null, 2);
|
|
2055
|
+
const blob = new Blob([config], { type: "application/json" });
|
|
2056
|
+
const url = URL.createObjectURL(blob);
|
|
2057
|
+
const link = document.createElement("a");
|
|
2058
|
+
link.href = url;
|
|
2059
|
+
link.download = `layout-config-${Date.now()}.json`;
|
|
2060
|
+
document.body.appendChild(link);
|
|
2061
|
+
link.click();
|
|
2062
|
+
document.body.removeChild(link);
|
|
2063
|
+
URL.revokeObjectURL(url);
|
|
2064
|
+
};
|
|
2065
|
+
const handleResetLayout = () => {
|
|
2066
|
+
customAreas.value = [];
|
|
2067
|
+
};
|
|
2068
|
+
const handleExportData = () => {
|
|
2069
|
+
emit("export-data", {
|
|
2070
|
+
layout: customAreas.value,
|
|
2071
|
+
formData: props.formData,
|
|
2072
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2073
|
+
});
|
|
2074
|
+
};
|
|
2075
|
+
watchEffect(() => {
|
|
2076
|
+
const usedProps = new Set(customAreas.value.flatMap((area) => area.fields.map((field) => field.prop)));
|
|
2077
|
+
availableFields.value = allFormOptions.value.filter((field) => !usedProps.has(field.prop)).map((field) => ({
|
|
2078
|
+
...field,
|
|
2079
|
+
id: field.prop
|
|
2080
|
+
}));
|
|
2081
|
+
});
|
|
2082
|
+
watch(() => customAreas.value, () => {
|
|
2083
|
+
emit("fields-change", customAreas.value.flatMap((area) => area.fields.map((field) => ({
|
|
2084
|
+
...field,
|
|
2085
|
+
id: void 0
|
|
2086
|
+
}))));
|
|
2087
|
+
}, { deep: true });
|
|
2088
|
+
return (_ctx, _cache) => {
|
|
2089
|
+
const _component_NButton = resolveComponent("NButton");
|
|
2090
|
+
const _component_NButtonGroup = resolveComponent("NButtonGroup");
|
|
2091
|
+
const _component_NCard = resolveComponent("NCard");
|
|
2092
|
+
const _component_NInput = resolveComponent("NInput");
|
|
2093
|
+
const _component_NTag = resolveComponent("NTag");
|
|
2094
|
+
const _component_NEmpty = resolveComponent("NEmpty");
|
|
2095
|
+
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
2096
|
+
createCommentVNode(" 顶部工具栏 "),
|
|
2097
|
+
createVNode(_component_NCard, {
|
|
2098
|
+
bordered: false,
|
|
2099
|
+
class: "toolbar-card"
|
|
2100
|
+
}, {
|
|
2101
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_2, [
|
|
2102
|
+
createCommentVNode(" 模式切换 "),
|
|
2103
|
+
createElementVNode("div", _hoisted_3, [_cache[11] || (_cache[11] = createElementVNode("span", { class: "section-label" }, "自定义模式:", -1)), createVNode(_component_NButtonGroup, null, {
|
|
2104
|
+
default: withCtx(() => [createVNode(_component_NButton, {
|
|
2105
|
+
type: isDesignMode.value ? "primary" : "default",
|
|
2106
|
+
onClick: _cache[0] || (_cache[0] = ($event) => isDesignMode.value = true),
|
|
2107
|
+
size: "small"
|
|
2108
|
+
}, {
|
|
2109
|
+
default: withCtx(() => _cache[9] || (_cache[9] = [createTextVNode(" 🎨 设计模式 ", -1)])),
|
|
2110
|
+
_: 1,
|
|
2111
|
+
__: [9]
|
|
2112
|
+
}, 8, ["type"]), createVNode(_component_NButton, {
|
|
2113
|
+
type: !isDesignMode.value ? "primary" : "default",
|
|
2114
|
+
onClick: _cache[1] || (_cache[1] = ($event) => isDesignMode.value = false),
|
|
2115
|
+
size: "small"
|
|
2116
|
+
}, {
|
|
2117
|
+
default: withCtx(() => _cache[10] || (_cache[10] = [createTextVNode(" 📝 填写模式 ", -1)])),
|
|
2118
|
+
_: 1,
|
|
2119
|
+
__: [10]
|
|
2120
|
+
}, 8, ["type"])]),
|
|
2121
|
+
_: 1
|
|
2122
|
+
})]),
|
|
2123
|
+
createCommentVNode(" 统计信息 "),
|
|
2124
|
+
createElementVNode("div", _hoisted_4, [createElementVNode("div", _hoisted_5, [createElementVNode("div", _hoisted_6, toDisplayString(customAreas.value.length), 1), _cache[12] || (_cache[12] = createElementVNode("div", { class: "stat-label" }, "自定义区域", -1))]), createElementVNode("div", _hoisted_7, [createElementVNode("div", _hoisted_8, toDisplayString(totalFieldsCount.value), 1), _cache[13] || (_cache[13] = createElementVNode("div", { class: "stat-label" }, "字段总数", -1))])]),
|
|
2125
|
+
createCommentVNode(" 操作按钮 "),
|
|
2126
|
+
createElementVNode("div", _hoisted_9, [isDesignMode.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode(_component_NButton, {
|
|
2127
|
+
secondary: "",
|
|
2128
|
+
onClick: handleSaveLayout,
|
|
2129
|
+
size: "small"
|
|
2130
|
+
}, {
|
|
2131
|
+
default: withCtx(() => _cache[14] || (_cache[14] = [createTextVNode(" 💾 保存布局 ", -1)])),
|
|
2132
|
+
_: 1,
|
|
2133
|
+
__: [14]
|
|
2134
|
+
}), createVNode(_component_NButton, {
|
|
2135
|
+
secondary: "",
|
|
2136
|
+
onClick: handleResetLayout,
|
|
2137
|
+
size: "small"
|
|
2138
|
+
}, {
|
|
2139
|
+
default: withCtx(() => _cache[15] || (_cache[15] = [createTextVNode(" 🔄 重置布局 ", -1)])),
|
|
2140
|
+
_: 1,
|
|
2141
|
+
__: [15]
|
|
2142
|
+
})], 64)) : (openBlock(), createBlock(_component_NButton, {
|
|
2143
|
+
key: 1,
|
|
2144
|
+
secondary: "",
|
|
2145
|
+
onClick: handleExportData,
|
|
2146
|
+
size: "small"
|
|
2147
|
+
}, {
|
|
2148
|
+
default: withCtx(() => _cache[16] || (_cache[16] = [createTextVNode(" 📊 导出数据 ", -1)])),
|
|
2149
|
+
_: 1,
|
|
2150
|
+
__: [16]
|
|
2151
|
+
}))])
|
|
2152
|
+
])]),
|
|
2153
|
+
_: 1
|
|
2154
|
+
}),
|
|
2155
|
+
createCommentVNode(" 设计模式工具面板 "),
|
|
2156
|
+
isDesignMode.value ? (openBlock(), createBlock(_component_NCard, {
|
|
2157
|
+
key: 0,
|
|
2158
|
+
class: "design-panel",
|
|
2159
|
+
title: "🎨 设计工具"
|
|
2160
|
+
}, {
|
|
2161
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_10, [createElementVNode("div", _hoisted_11, [
|
|
2162
|
+
_cache[20] || (_cache[20] = createElementVNode("span", { class: "group-label" }, "添加区域:", -1)),
|
|
2163
|
+
createVNode(_component_NButton, {
|
|
2164
|
+
onClick: _cache[2] || (_cache[2] = ($event) => addArea("horizontal")),
|
|
2165
|
+
size: "small"
|
|
2166
|
+
}, {
|
|
2167
|
+
default: withCtx(() => _cache[17] || (_cache[17] = [createTextVNode(" ➡️ 水平区域 ", -1)])),
|
|
2168
|
+
_: 1,
|
|
2169
|
+
__: [17]
|
|
2170
|
+
}),
|
|
2171
|
+
createVNode(_component_NButton, {
|
|
2172
|
+
onClick: _cache[3] || (_cache[3] = ($event) => addArea("vertical")),
|
|
2173
|
+
size: "small"
|
|
2174
|
+
}, {
|
|
2175
|
+
default: withCtx(() => _cache[18] || (_cache[18] = [createTextVNode(" ⬇️ 垂直区域 ", -1)])),
|
|
2176
|
+
_: 1,
|
|
2177
|
+
__: [18]
|
|
2178
|
+
}),
|
|
2179
|
+
createVNode(_component_NButton, {
|
|
2180
|
+
onClick: _cache[4] || (_cache[4] = ($event) => addArea("grid")),
|
|
2181
|
+
size: "small"
|
|
2182
|
+
}, {
|
|
2183
|
+
default: withCtx(() => _cache[19] || (_cache[19] = [createTextVNode(" ⚏ 网格区域 ", -1)])),
|
|
2184
|
+
_: 1,
|
|
2185
|
+
__: [19]
|
|
2186
|
+
})
|
|
2187
|
+
])])]),
|
|
2188
|
+
_: 1
|
|
2189
|
+
})) : createCommentVNode("v-if", true),
|
|
2190
|
+
createCommentVNode(" 主画布区域 "),
|
|
2191
|
+
createElementVNode("div", { class: normalizeClass(["layout-canvas", { "design-mode": isDesignMode.value }]) }, [createCommentVNode(" 设计模式 "), isDesignMode.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
|
|
2192
|
+
customAreas.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_12, _cache[21] || (_cache[21] = [createElementVNode("div", { class: "hint-content" }, [createElementVNode("h3", null, "🎨 开始自定义你的布局"), createElementVNode("p", null, "点击上方按钮添加区域")], -1)]))) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" 区域列表 "), createElementVNode("div", _hoisted_13, [(openBlock(true), createElementBlock(Fragment, null, renderList(customAreas.value, (area) => {
|
|
2193
|
+
return openBlock(), createElementBlock("div", {
|
|
2194
|
+
key: area.id,
|
|
2195
|
+
class: normalizeClass(["custom-area", `area-${area.type}`])
|
|
2196
|
+
}, [
|
|
2197
|
+
createElementVNode("div", _hoisted_14, [createElementVNode("div", _hoisted_15, [editingTitleId.value === area.id ? (openBlock(), createBlock(_component_NInput, {
|
|
2198
|
+
key: 0,
|
|
2199
|
+
value: area.title,
|
|
2200
|
+
"onUpdate:value": ($event) => area.title = $event,
|
|
2201
|
+
size: "small",
|
|
2202
|
+
onBlur: _cache[5] || (_cache[5] = ($event) => editingTitleId.value = ""),
|
|
2203
|
+
onKeyup: _cache[6] || (_cache[6] = withKeys(($event) => editingTitleId.value = "", ["enter"])),
|
|
2204
|
+
class: "title-input"
|
|
2205
|
+
}, null, 8, ["value", "onUpdate:value"])) : (openBlock(), createElementBlock("span", {
|
|
2206
|
+
key: 1,
|
|
2207
|
+
class: "area-title",
|
|
2208
|
+
onClick: ($event) => editingTitleId.value = area.id
|
|
2209
|
+
}, toDisplayString(area.title), 9, _hoisted_16)), createVNode(_component_NTag, { size: "small" }, {
|
|
2210
|
+
default: withCtx(() => [createTextVNode(toDisplayString(area.fields.length) + " 字段", 1)]),
|
|
2211
|
+
_: 2
|
|
2212
|
+
}, 1024)]), createElementVNode("div", _hoisted_17, [createVNode(_component_NButton, {
|
|
2213
|
+
text: "",
|
|
2214
|
+
onClick: ($event) => deleteArea(area.id),
|
|
2215
|
+
size: "tiny",
|
|
2216
|
+
type: "error",
|
|
2217
|
+
title: "删除区域"
|
|
2218
|
+
}, {
|
|
2219
|
+
default: withCtx(() => _cache[22] || (_cache[22] = [createTextVNode(" 🗑️ ", -1)])),
|
|
2220
|
+
_: 2,
|
|
2221
|
+
__: [22]
|
|
2222
|
+
}, 1032, ["onClick"])])]),
|
|
2223
|
+
createCommentVNode(" 字段容器 "),
|
|
2224
|
+
createElementVNode("div", _hoisted_18, [(openBlock(true), createElementBlock(Fragment, null, renderList(area.fields, (field) => {
|
|
2225
|
+
return openBlock(), createElementBlock("div", {
|
|
2226
|
+
key: field.id,
|
|
2227
|
+
class: "field-item"
|
|
2228
|
+
}, [createElementVNode("div", _hoisted_19, [createElementVNode("span", _hoisted_20, toDisplayString(field.label || field.prop), 1), createElementVNode("span", _hoisted_21, toDisplayString(getFieldTypeName(field.type)), 1)])]);
|
|
2229
|
+
}), 128))]),
|
|
2230
|
+
area.fields.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_22, " 拖拽字段到这里 ")) : createCommentVNode("v-if", true)
|
|
2231
|
+
], 2);
|
|
2232
|
+
}), 128))])], 2112)),
|
|
2233
|
+
createCommentVNode(" 字段池 "),
|
|
2234
|
+
createVNode(_component_NCard, {
|
|
2235
|
+
class: "field-pool",
|
|
2236
|
+
title: "📦 可用字段"
|
|
2237
|
+
}, {
|
|
2238
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_23, [(openBlock(true), createElementBlock(Fragment, null, renderList(availableFields.value, (field) => {
|
|
2239
|
+
return openBlock(), createElementBlock("div", {
|
|
2240
|
+
key: field.id,
|
|
2241
|
+
class: "pool-field"
|
|
2242
|
+
}, [createElementVNode("span", _hoisted_24, toDisplayString(field.label || field.prop), 1), createElementVNode("span", _hoisted_25, toDisplayString(getFieldTypeName(field.type)), 1)]);
|
|
2243
|
+
}), 128))])]),
|
|
2244
|
+
_: 1
|
|
2245
|
+
})
|
|
2246
|
+
], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" 填写模式 "), customAreas.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_26, [createVNode(_component_NEmpty, { description: "尚未设计布局" }, {
|
|
2247
|
+
extra: withCtx(() => [createVNode(_component_NButton, {
|
|
2248
|
+
onClick: _cache[7] || (_cache[7] = ($event) => isDesignMode.value = true),
|
|
2249
|
+
type: "primary"
|
|
2250
|
+
}, {
|
|
2251
|
+
default: withCtx(() => _cache[23] || (_cache[23] = [createTextVNode(" 🎨 开始设计 ", -1)])),
|
|
2252
|
+
_: 1,
|
|
2253
|
+
__: [23]
|
|
2254
|
+
})]),
|
|
2255
|
+
_: 1
|
|
2256
|
+
})])) : (openBlock(), createElementBlock("div", _hoisted_27, [createElementVNode("div", _hoisted_28, [(openBlock(true), createElementBlock(Fragment, null, renderList(customAreas.value, (area) => {
|
|
2257
|
+
return openBlock(), createElementBlock("div", {
|
|
2258
|
+
key: area.id,
|
|
2259
|
+
class: normalizeClass(["form-area", `area-${area.type}`])
|
|
2260
|
+
}, [area.fields.length > 0 ? (openBlock(), createBlock(_component_NCard, {
|
|
2261
|
+
key: 0,
|
|
2262
|
+
title: area.title
|
|
2263
|
+
}, {
|
|
2264
|
+
default: withCtx(() => [createElementVNode("div", { class: normalizeClass(["area-form-items", `layout-${area.type}`]) }, [(openBlock(true), createElementBlock(Fragment, null, renderList(area.fields, (field) => {
|
|
2265
|
+
return openBlock(), createBlock(resolveDynamicComponent(getFormItemForField(field)), { key: field.prop });
|
|
2266
|
+
}), 128))], 2)]),
|
|
2267
|
+
_: 2
|
|
2268
|
+
}, 1032, ["title"])) : (openBlock(), createBlock(_component_NEmpty, {
|
|
2269
|
+
key: 1,
|
|
2270
|
+
description: "此区域暂无字段",
|
|
2271
|
+
size: "small"
|
|
2272
|
+
}, {
|
|
2273
|
+
extra: withCtx(() => [createVNode(_component_NButton, {
|
|
2274
|
+
onClick: _cache[8] || (_cache[8] = ($event) => isDesignMode.value = true),
|
|
2275
|
+
size: "small",
|
|
2276
|
+
secondary: ""
|
|
2277
|
+
}, {
|
|
2278
|
+
default: withCtx(() => _cache[24] || (_cache[24] = [createTextVNode(" 🎨 添加字段 ", -1)])),
|
|
2279
|
+
_: 1,
|
|
2280
|
+
__: [24]
|
|
2281
|
+
})]),
|
|
2282
|
+
_: 1
|
|
2283
|
+
}))], 2);
|
|
2284
|
+
}), 128))])]))], 64))], 2)
|
|
2285
|
+
]);
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
|
|
2290
|
+
//#endregion
|
|
2291
|
+
//#region src/components/C_Form/layouts/Custom/index.vue
|
|
2292
|
+
var Custom_default = /* @__PURE__ */ export_helper_default(index_vue_vue_type_script_setup_true_lang_default$1, [["__scopeId", "data-v-a21051df"]]);
|
|
2293
|
+
|
|
2294
|
+
//#endregion
|
|
2295
|
+
//#region src/components/C_Form/index.vue?vue&type=script&setup=true&lang.ts
|
|
2296
|
+
var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
2297
|
+
name: "C_Form",
|
|
2298
|
+
__name: "index",
|
|
2299
|
+
props: {
|
|
2300
|
+
options: {},
|
|
2301
|
+
modelValue: {},
|
|
2302
|
+
config: { default: () => ({}) }
|
|
2303
|
+
},
|
|
2304
|
+
emits: [
|
|
2305
|
+
"submit",
|
|
2306
|
+
"update:modelValue",
|
|
2307
|
+
"validate-success",
|
|
2308
|
+
"validate-error"
|
|
2309
|
+
],
|
|
2310
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
2311
|
+
const LAYOUT_MAP = {
|
|
2312
|
+
default: Default_default,
|
|
2313
|
+
inline: Inline_default,
|
|
2314
|
+
grid: Grid_default,
|
|
2315
|
+
card: Card_default,
|
|
2316
|
+
tabs: Tabs_default,
|
|
2317
|
+
steps: Steps_default,
|
|
2318
|
+
dynamic: Dynamic_default,
|
|
2319
|
+
custom: Custom_default
|
|
2320
|
+
};
|
|
2321
|
+
const COMPONENT_MAP = {
|
|
2322
|
+
NFormItem: resolveComponent("NFormItem"),
|
|
2323
|
+
NInput: resolveComponent("NInput"),
|
|
2324
|
+
NInputNumber: resolveComponent("NInputNumber"),
|
|
2325
|
+
NSwitch: resolveComponent("NSwitch"),
|
|
2326
|
+
NSlider: resolveComponent("NSlider"),
|
|
2327
|
+
NRate: resolveComponent("NRate"),
|
|
2328
|
+
NDatePicker: resolveComponent("NDatePicker"),
|
|
2329
|
+
NTimePicker: resolveComponent("NTimePicker"),
|
|
2330
|
+
NCascader: resolveComponent("NCascader"),
|
|
2331
|
+
NColorPicker: resolveComponent("NColorPicker"),
|
|
2332
|
+
NSelect: resolveComponent("NSelect"),
|
|
2333
|
+
NCheckboxGroup: resolveComponent("NCheckboxGroup"),
|
|
2334
|
+
NCheckbox: resolveComponent("NCheckbox"),
|
|
2335
|
+
NRadioGroup: resolveComponent("NRadioGroup"),
|
|
2336
|
+
NRadio: resolveComponent("NRadio"),
|
|
2337
|
+
NUpload: resolveComponent("NUpload"),
|
|
2338
|
+
NButton: resolveComponent("NButton"),
|
|
2339
|
+
NSpace: resolveComponent("NSpace"),
|
|
2340
|
+
C_Editor: resolveComponent("C_Editor")
|
|
2341
|
+
};
|
|
2342
|
+
const props = __props;
|
|
2343
|
+
const emit = __emit;
|
|
2344
|
+
const resolved = computed(() => resolveFormConfig(props.config));
|
|
2345
|
+
const formRef = ref(null);
|
|
2346
|
+
const { formModel, formRules, visibleOptions, initialize, handleFieldChange, validate, validateField, validateStep, validateTab, validateDynamicFields, validateCustomGroup, clearValidation, getModel, setFields, resetFields, setFieldValue, getFieldValue, setFieldsValue, handleSubmit, handleReset } = useFormState(computed(() => props.options), resolved, formRef, emit);
|
|
2347
|
+
const { formItems } = useFormRenderer(formModel, visibleOptions, resolved, handleFieldChange, COMPONENT_MAP, getCurrentInstance()?.slots);
|
|
2348
|
+
const layoutComponent = computed(() => LAYOUT_MAP[resolved.value.layout] || LAYOUT_MAP.default);
|
|
2349
|
+
const mergedLayoutConfig = computed(() => ({
|
|
2350
|
+
type: resolved.value.layout,
|
|
2351
|
+
grid: resolved.value.grid,
|
|
2352
|
+
inline: resolved.value.inline,
|
|
2353
|
+
card: resolved.value.card,
|
|
2354
|
+
tabs: resolved.value.tabs,
|
|
2355
|
+
steps: resolved.value.steps,
|
|
2356
|
+
dynamic: resolved.value.dynamic,
|
|
2357
|
+
custom: resolved.value.custom
|
|
2358
|
+
}));
|
|
2359
|
+
const showActions = computed(() => shouldShowActions(resolved.value));
|
|
2360
|
+
/** 通用布局事件桥接 */
|
|
2361
|
+
const handleLayoutEvent = (callbackName, ...args) => {
|
|
2362
|
+
const callback = resolved.value[callbackName];
|
|
2363
|
+
callback?.(...args);
|
|
2364
|
+
};
|
|
2365
|
+
/** 字段变化事件(保留回调通道) */
|
|
2366
|
+
const handleFieldsChange = (fields) => {
|
|
2367
|
+
resolved.value.onFieldsChange?.(fields);
|
|
2368
|
+
};
|
|
2369
|
+
/** Steps 布局事件 — 需要多参数特殊处理 */
|
|
2370
|
+
const handleStepChange = (stepIndex, stepKey) => {
|
|
2371
|
+
resolved.value.onStepChange?.(stepIndex, stepKey);
|
|
2372
|
+
};
|
|
2373
|
+
const handleStepBeforeChange = async (currentStep, targetStep) => {
|
|
2374
|
+
resolved.value.onStepBeforeChange?.(currentStep, targetStep);
|
|
2375
|
+
return true;
|
|
2376
|
+
};
|
|
2377
|
+
const handleStepValidate = async (stepIndex) => {
|
|
2378
|
+
try {
|
|
2379
|
+
const currentStepKey = resolved.value.steps?.steps?.[stepIndex]?.key;
|
|
2380
|
+
if (!currentStepKey) return true;
|
|
2381
|
+
const stepFields = props.options.filter((option) => option.layout?.step === currentStepKey).map((option) => option.prop);
|
|
2382
|
+
if (stepFields.length === 0) return true;
|
|
2383
|
+
await validateField(stepFields);
|
|
2384
|
+
resolved.value.onStepValidate?.(stepIndex);
|
|
2385
|
+
return true;
|
|
2386
|
+
} catch (error) {
|
|
2387
|
+
console.warn(`[C_Form] 步骤 ${stepIndex} 验证失败:`, error);
|
|
2388
|
+
return false;
|
|
2389
|
+
}
|
|
2390
|
+
};
|
|
2391
|
+
watch(() => props.modelValue, (val) => {
|
|
2392
|
+
if (val) Object.assign(formModel, val);
|
|
2393
|
+
}, {
|
|
2394
|
+
immediate: true,
|
|
2395
|
+
deep: true
|
|
2396
|
+
});
|
|
2397
|
+
__expose({
|
|
2398
|
+
validate,
|
|
2399
|
+
validateField,
|
|
2400
|
+
validateStep,
|
|
2401
|
+
validateTab,
|
|
2402
|
+
validateDynamicFields,
|
|
2403
|
+
validateCustomGroup,
|
|
2404
|
+
clearValidation,
|
|
2405
|
+
getModel,
|
|
2406
|
+
setFields,
|
|
2407
|
+
resetFields,
|
|
2408
|
+
setFieldValue,
|
|
2409
|
+
getFieldValue,
|
|
2410
|
+
setFieldsValue,
|
|
2411
|
+
formRef,
|
|
2412
|
+
formModel,
|
|
2413
|
+
initialize,
|
|
2414
|
+
layoutType: computed(() => resolved.value.layout),
|
|
2415
|
+
shouldShowDefaultActions: showActions
|
|
2416
|
+
});
|
|
2417
|
+
return (_ctx, _cache) => {
|
|
2418
|
+
return openBlock(), createBlock(unref(NForm), mergeProps({
|
|
2419
|
+
ref_key: "formRef",
|
|
2420
|
+
ref: formRef,
|
|
2421
|
+
model: unref(formModel),
|
|
2422
|
+
rules: unref(formRules),
|
|
2423
|
+
"validate-on-rule-change": false,
|
|
2424
|
+
"label-placement": resolved.value.labelPlacement,
|
|
2425
|
+
"label-width": resolved.value.labelWidth,
|
|
2426
|
+
size: resolved.value.size,
|
|
2427
|
+
disabled: resolved.value.disabled,
|
|
2428
|
+
readonly: resolved.value.readonly
|
|
2429
|
+
}, _ctx.$attrs), {
|
|
2430
|
+
default: withCtx(() => [
|
|
2431
|
+
createCommentVNode(" 布局组件渲染 "),
|
|
2432
|
+
(openBlock(), createBlock(resolveDynamicComponent(layoutComponent.value), {
|
|
2433
|
+
"form-items": unref(formItems),
|
|
2434
|
+
"layout-config": mergedLayoutConfig.value,
|
|
2435
|
+
options: unref(visibleOptions),
|
|
2436
|
+
"form-data": unref(formModel),
|
|
2437
|
+
onTabChange: _cache[0] || (_cache[0] = ($event) => handleLayoutEvent("onTabChange", $event)),
|
|
2438
|
+
onTabValidate: _cache[1] || (_cache[1] = ($event) => handleLayoutEvent("onTabValidate", $event)),
|
|
2439
|
+
onStepChange: handleStepChange,
|
|
2440
|
+
onStepBeforeChange: handleStepBeforeChange,
|
|
2441
|
+
onStepValidate: handleStepValidate,
|
|
2442
|
+
onFieldAdd: _cache[2] || (_cache[2] = ($event) => handleLayoutEvent("onFieldAdd", $event)),
|
|
2443
|
+
onFieldRemove: _cache[3] || (_cache[3] = ($event) => handleLayoutEvent("onFieldRemove", $event)),
|
|
2444
|
+
onFieldToggle: _cache[4] || (_cache[4] = (id, visible) => resolved.value.onFieldToggle?.(id, visible)),
|
|
2445
|
+
onFieldsClear: _cache[5] || (_cache[5] = ($event) => handleLayoutEvent("onFieldsClear")),
|
|
2446
|
+
onRenderModeChange: _cache[6] || (_cache[6] = ($event) => handleLayoutEvent("onRenderModeChange", $event)),
|
|
2447
|
+
onGroupToggle: _cache[7] || (_cache[7] = (key, collapsed) => resolved.value.onGroupToggle?.(key, collapsed)),
|
|
2448
|
+
onGroupReset: _cache[8] || (_cache[8] = ($event) => handleLayoutEvent("onGroupReset", $event)),
|
|
2449
|
+
onValidateSuccess: _cache[9] || (_cache[9] = (model) => emit("validate-success", model)),
|
|
2450
|
+
onValidateError: _cache[10] || (_cache[10] = (errors) => emit("validate-error", errors)),
|
|
2451
|
+
onFieldsChange: handleFieldsChange
|
|
2452
|
+
}, null, 40, [
|
|
2453
|
+
"form-items",
|
|
2454
|
+
"layout-config",
|
|
2455
|
+
"options",
|
|
2456
|
+
"form-data"
|
|
2457
|
+
])),
|
|
2458
|
+
createCommentVNode(" 表单操作按钮区域(只在特定布局中显示) "),
|
|
2459
|
+
showActions.value ? (openBlock(), createBlock(unref(NFormItem), {
|
|
2460
|
+
key: 0,
|
|
2461
|
+
style: { "margin-top": "20px" }
|
|
2462
|
+
}, {
|
|
2463
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "action", {
|
|
2464
|
+
form: formRef.value,
|
|
2465
|
+
model: unref(formModel),
|
|
2466
|
+
validate: unref(validate),
|
|
2467
|
+
validateField: unref(validateField),
|
|
2468
|
+
reset: unref(resetFields),
|
|
2469
|
+
setFields: unref(setFields),
|
|
2470
|
+
getModel: unref(getModel),
|
|
2471
|
+
clearValidation: unref(clearValidation)
|
|
2472
|
+
}, () => [createVNode(unref(NSpace), null, {
|
|
2473
|
+
default: withCtx(() => [createVNode(unref(NButton), {
|
|
2474
|
+
type: "primary",
|
|
2475
|
+
onClick: unref(handleSubmit)
|
|
2476
|
+
}, {
|
|
2477
|
+
default: withCtx(() => _cache[11] || (_cache[11] = [createTextVNode("提交", -1)])),
|
|
2478
|
+
_: 1,
|
|
2479
|
+
__: [11]
|
|
2480
|
+
}, 8, ["onClick"]), createVNode(unref(NButton), { onClick: unref(handleReset) }, {
|
|
2481
|
+
default: withCtx(() => _cache[12] || (_cache[12] = [createTextVNode("重置", -1)])),
|
|
2482
|
+
_: 1,
|
|
2483
|
+
__: [12]
|
|
2484
|
+
}, 8, ["onClick"])]),
|
|
2485
|
+
_: 1
|
|
2486
|
+
})])]),
|
|
2487
|
+
_: 3
|
|
2488
|
+
})) : createCommentVNode("v-if", true)
|
|
2489
|
+
]),
|
|
2490
|
+
_: 3
|
|
2491
|
+
}, 16, [
|
|
2492
|
+
"model",
|
|
2493
|
+
"rules",
|
|
2494
|
+
"label-placement",
|
|
2495
|
+
"label-width",
|
|
2496
|
+
"size",
|
|
2497
|
+
"disabled",
|
|
2498
|
+
"readonly"
|
|
2499
|
+
]);
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
//#endregion
|
|
2505
|
+
//#region src/components/C_Form/index.vue
|
|
2506
|
+
var C_Form_default = index_vue_vue_type_script_setup_true_lang_default;
|
|
2507
|
+
|
|
2508
|
+
//#endregion
|
|
2509
|
+
export { registerRenderer as a, FORM_DEFAULTS as c, shouldShowActions as d, useDynamicFormState as i, LAYOUTS_WITH_OWN_CONTROLS as l, DYNAMIC_FORM_STATE_KEY as n, useFormRenderer as o, FIELD_TYPE_OPTIONS as r, useFormState as s, C_Form_default as t, resolveFormConfig as u };
|
|
2510
|
+
//# sourceMappingURL=C_Form2.js.map
|