jobsys-explore 4.6.21 → 4.7.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/.eslintignore +3 -3
- package/CHANGELOG.md +542 -542
- package/README.md +41 -41
- package/TODOs.md +8 -8
- package/business-components/survey/ExSurvey.jsx +193 -193
- package/business-components/survey/index.js +5 -5
- package/business-components/survey/index.less +36 -36
- package/components/button/ExButton.jsx +120 -120
- package/components/button/index.js +4 -4
- package/components/button/index.less +7 -7
- package/components/decorator/ExDecorator.jsx +31 -31
- package/components/decorator/index.js +5 -5
- package/components/decorator/index.less +76 -76
- package/components/form/ExAddress.jsx +195 -195
- package/components/form/ExCascader.jsx +171 -171
- package/components/form/ExCheckbox.jsx +59 -59
- package/components/form/ExDate.jsx +143 -143
- package/components/form/ExDatetime.jsx +166 -166
- package/components/form/ExField.jsx +138 -138
- package/components/form/ExFieldUploader.jsx +50 -50
- package/components/form/ExForm.jsx +544 -544
- package/components/form/ExMatrixCheckbox.jsx +99 -99
- package/components/form/ExMatrixRadio.jsx +86 -86
- package/components/form/ExMatrixScale.jsx +97 -97
- package/components/form/ExNumber.jsx +51 -51
- package/components/form/ExRadio.jsx +58 -58
- package/components/form/ExRate.jsx +51 -51
- package/components/form/ExSelect.jsx +251 -251
- package/components/form/ExSlider.jsx +55 -55
- package/components/form/ExSwitch.jsx +51 -51
- package/components/form/ExTime.jsx +99 -99
- package/components/form/FormItem.jsx +307 -307
- package/components/form/PickerWrapper.jsx +120 -120
- package/components/form/index.js +46 -46
- package/components/form/index.less +178 -178
- package/components/form/utils.js +62 -62
- package/components/grid/ExGrid.jsx +53 -53
- package/components/grid/index.js +4 -4
- package/components/grid/index.less +2 -2
- package/components/index.js +12 -12
- package/components/pagination/ExPagination.jsx +457 -440
- package/components/pagination/index.js +5 -5
- package/components/pagination/index.less +3 -3
- package/components/provider/ExProvider.jsx +173 -173
- package/components/qrcode/ExQrcode.jsx +86 -86
- package/components/qrcode/index.js +5 -5
- package/components/qrcode/index.less +8 -8
- package/components/result/ExResult.jsx +122 -122
- package/components/result/index.js +5 -5
- package/components/result/index.less +59 -59
- package/components/search/ExSearch.jsx +370 -326
- package/components/search/components/Expand.jsx +77 -77
- package/components/search/components/Field.jsx +27 -27
- package/components/search/components/Quick.jsx +57 -57
- package/components/search/components/index.js +5 -5
- package/components/search/index.js +5 -5
- package/components/search/index.less +118 -118
- package/components/search/utils.js +30 -30
- package/components/sector/ExSector.jsx +52 -52
- package/components/sector/README.md +26 -26
- package/components/sector/index.js +5 -5
- package/components/sector/index.less +122 -122
- package/components/theme/ExTheme.jsx +10 -10
- package/components/theme/index.js +4 -4
- package/components/theme/index.less +98 -98
- package/components/uploader/ExUploader.jsx +293 -293
- package/components/uploader/index.js +5 -5
- package/components/utils.js +187 -187
- package/directives/auth.js +113 -113
- package/directives/index.js +4 -4
- package/dist/cipher-98df1050.cjs.map +1 -1
- package/dist/cipher-f2ed5ee6.js.map +1 -1
- package/dist/directives.cjs.map +1 -1
- package/dist/directives.js.map +1 -1
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.js.map +1 -1
- package/dist/jobsys-explore.cjs +6 -6
- package/dist/jobsys-explore.cjs.map +1 -1
- package/dist/jobsys-explore.js +464 -407
- package/dist/jobsys-explore.js.map +1 -1
- package/docgen.config.js +15 -15
- package/docs/.vuepress/.cache/deps/_metadata.json +52 -52
- package/docs/.vuepress/.cache/deps/lodash-es.js +8442 -8442
- package/docs/.vuepress/.cache/deps/lodash-es.js.map +7 -7
- package/docs/.vuepress/.temp/internal/clientConfigs.js +17 -17
- package/docs/.vuepress/.temp/internal/pagesComponents.js +24 -24
- package/docs/.vuepress/.temp/internal/pagesData.js +22 -22
- package/docs/.vuepress/.temp/internal/pagesRoutes.js +12 -12
- package/docs/.vuepress/.temp/internal/themeData.js +1 -1
- package/docs/.vuepress/.temp/pages/components/decorator/ExDecorator.html.js +1 -1
- package/docs/.vuepress/.temp/pages/components/decorator/ExDecorator.html.vue +37 -37
- package/docs/.vuepress/.temp/pages/components/sector/ExSector.html.js +1 -1
- package/docs/.vuepress/.temp/pages/components/sector/ExSector.html.vue +71 -71
- package/docs/.vuepress/.temp/styles/index.scss +1 -1
- package/docs/.vuepress/config.js +61 -61
- package/docs/.vuepress/dist/404.html +33 -33
- package/docs/.vuepress/dist/assets/404.html-a0ce2184.js +1 -1
- package/docs/.vuepress/dist/assets/ExButton.html-ad283101.js +1 -1
- package/docs/.vuepress/dist/assets/ExDecorator.html-42d09114.js +1 -1
- package/docs/.vuepress/dist/assets/ExDecorator.html-c82c5fe8.js +1 -1
- package/docs/.vuepress/dist/assets/ExForm.html-9e3f8000.js +1 -1
- package/docs/.vuepress/dist/assets/ExProvider.html-78fdc6cd.js +1 -1
- package/docs/.vuepress/dist/assets/ExSearch.html-103f6f34.js +1 -1
- package/docs/.vuepress/dist/assets/ExSector.html-a1e24c3a.js +7 -7
- package/docs/.vuepress/dist/assets/ExSector.html-cff3fefd.js +1 -1
- package/docs/.vuepress/dist/assets/ExUploader.html-8310e424.js +1 -1
- package/docs/.vuepress/dist/assets/app-29fe8d1e.js +10 -10
- package/docs/.vuepress/dist/assets/hooks.html-90ccbc1a.js +1 -1
- package/docs/.vuepress/dist/assets/index.html-85b79c97.js +43 -43
- package/docs/.vuepress/dist/assets/style-46d7e227.css +1 -1
- package/docs/.vuepress/dist/components/button/ExButton.html +33 -33
- package/docs/.vuepress/dist/components/decorator/ExDecorator.html +33 -33
- package/docs/.vuepress/dist/components/form/ExForm.html +33 -33
- package/docs/.vuepress/dist/components/provider/ExProvider.html +33 -33
- package/docs/.vuepress/dist/components/search/ExSearch.html +33 -33
- package/docs/.vuepress/dist/components/sector/ExSector.html +39 -39
- package/docs/.vuepress/dist/components/uploader/ExUploader.html +33 -33
- package/docs/.vuepress/dist/hooks.html +33 -33
- package/docs/.vuepress/dist/index.html +75 -75
- package/docs/.vuepress/styles/index.scss +7 -7
- package/docs/components/decorator/ExDecorator.md +14 -14
- package/docs/components/sector/ExSector.md +43 -43
- package/docs/index.md +82 -82
- package/hooks/cipher.js +44 -44
- package/hooks/datetime.js +69 -69
- package/hooks/form.js +188 -188
- package/hooks/utils.js +282 -282
- package/index.html +17 -17
- package/package.json +1 -1
- package/playground/App.vue +191 -191
- package/playground/TestButton.vue +61 -61
- package/playground/TestCascader.vue +2442 -2442
- package/playground/TestDecorator.vue +14 -14
- package/playground/TestForm.vue +429 -429
- package/playground/TestFormItem.vue +110 -110
- package/playground/TestGrid.vue +22 -22
- package/playground/TestPagination.vue +1250 -1248
- package/playground/TestQrcode.vue +7 -7
- package/playground/TestResult.vue +12 -12
- package/playground/TestSearch.vue +115 -115
- package/playground/TestSector.vue +15 -15
- package/playground/TestSurvey.vue +27 -27
- package/playground/TestUploader.vue +14 -14
- package/playground/main.js +22 -22
- package/utils/style.js +13 -13
- package/vite.config.js +54 -54
- package/.changeset/blue-spiders-roll.md +0 -5
- package/.changeset/cyan-monkeys-draw.md +0 -5
- package/.changeset/dry-feet-float.md +0 -5
- package/.changeset/empty-mice-share.md +0 -5
- package/.changeset/famous-yaks-doubt.md +0 -5
- package/.changeset/five-fans-type.md +0 -5
- package/.changeset/funny-hats-drop.md +0 -5
- package/.changeset/khaki-cobras-bathe.md +0 -5
- package/.changeset/khaki-forks-shave.md +0 -5
- package/.changeset/lazy-yaks-crash.md +0 -5
- package/.changeset/light-cycles-flow.md +0 -5
- package/.changeset/loud-mirrors-explain.md +0 -5
- package/.changeset/lovely-balloons-protect.md +0 -5
- package/.changeset/mean-pens-travel.md +0 -5
- package/.changeset/moody-doors-grow.md +0 -5
- package/.changeset/moody-laws-change.md +0 -5
- package/.changeset/nasty-goats-joke.md +0 -5
- package/.changeset/odd-forks-drop.md +0 -5
- package/.changeset/olive-windows-suffer.md +0 -5
- package/.changeset/popular-carpets-jog.md +0 -5
- package/.changeset/popular-planets-play.md +0 -5
- package/.changeset/rare-gorillas-boil.md +0 -5
- package/.changeset/rare-moose-teach.md +0 -5
- package/.changeset/sharp-tools-hope.md +0 -5
- package/.changeset/slimy-sloths-refuse.md +0 -5
- package/.changeset/slow-boats-search.md +0 -5
- package/.changeset/small-experts-bake.md +0 -5
- package/.changeset/smooth-horses-tie.md +0 -5
- package/.changeset/tame-feet-reply.md +0 -5
- package/.changeset/tidy-items-reflect.md +0 -5
- package/.changeset/weak-chicken-admire.md +0 -5
- package/.changeset/weak-rockets-compare.md +0 -5
- package/.changeset/wild-glasses-bathe.md +0 -5
- package/.changeset/wise-ears-turn.md +0 -5
|
@@ -1,544 +1,544 @@
|
|
|
1
|
-
import { computed, defineComponent, inject, nextTick, onMounted, reactive, ref, watch } from "vue"
|
|
2
|
-
import { EX_UPLOADER, EX_FORM } from "../provider/ExProvider.jsx"
|
|
3
|
-
import { cloneDeep, every, isEqual, isFunction, isObject, isString, pick } from "lodash-es"
|
|
4
|
-
import { initItemDefaultValue } from "./utils"
|
|
5
|
-
import { useCache, useFetch, useFormFail, useFormFormat, useProcessStatusSuccess } from "../../hooks"
|
|
6
|
-
import { CellGroup, Form, showConfirmDialog, showSuccessToast, Skeleton } from "vant"
|
|
7
|
-
import createFormItem from "./FormItem.jsx"
|
|
8
|
-
import ExButton from "../button/ExButton.jsx"
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* ExForm 表单
|
|
12
|
-
*
|
|
13
|
-
* @version 1.0.0
|
|
14
|
-
*/
|
|
15
|
-
export default defineComponent({
|
|
16
|
-
name: "ExForm",
|
|
17
|
-
props: {
|
|
18
|
-
/**
|
|
19
|
-
* 表单标题
|
|
20
|
-
*/
|
|
21
|
-
title: { type: String, default: "" },
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 表单项 label 宽度,默认单位为px
|
|
25
|
-
*/
|
|
26
|
-
labelWidth: { type: [String, Number], default: "6.2em" },
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 是否在 label 后面添加冒号
|
|
30
|
-
*/
|
|
31
|
-
colon: { type: Boolean, default: false },
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 是否使用卡片模式
|
|
35
|
-
*/
|
|
36
|
-
inset: { type: Boolean, default: false },
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 表单数据,用于初始化表单,并会进行 Watch
|
|
40
|
-
*/
|
|
41
|
-
data: { type: [Object, String], default: "" },
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 是否自动加载
|
|
45
|
-
* true: 表示自动加载数据
|
|
46
|
-
* Array,String: 表示会对 `extraData` 数据中的相关字段进行非空验证,不为空再加载数据
|
|
47
|
-
*/
|
|
48
|
-
autoLoad: {
|
|
49
|
-
//自动加载数据,在fetchData里找
|
|
50
|
-
type: [Boolean, Array, String],
|
|
51
|
-
default: true,
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 获取表单数据的URL
|
|
56
|
-
*/
|
|
57
|
-
fetchUrl: { type: String, default: "" },
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 额外的数据,在提交时会合并到表单数据中并一起提交
|
|
61
|
-
*/
|
|
62
|
-
extraData: { type: Object, default: () => ({}) },
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* 是否禁用表单
|
|
66
|
-
*/
|
|
67
|
-
disabled: { type: Boolean, default: false },
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 是否只读
|
|
71
|
-
*/
|
|
72
|
-
readonly: { type: Boolean, default: false },
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* submit button 是否固定在底部
|
|
76
|
-
*/
|
|
77
|
-
fixed: { type: Boolean, default: false },
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 提交数据URL
|
|
81
|
-
*/
|
|
82
|
-
submitUrl: { type: String, default: "" },
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* 提交按钮文字
|
|
86
|
-
*/
|
|
87
|
-
submitButtonText: { type: String, default: "保存" },
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 提交确认提示内容
|
|
91
|
-
*/
|
|
92
|
-
submitConfirmText: { type: String, default: "" },
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* 是否禁用提交按钮
|
|
96
|
-
*/
|
|
97
|
-
submitDisabled: { type: Boolean, default: false },
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 是否显示关闭按钮
|
|
101
|
-
*/
|
|
102
|
-
closable: { type: Boolean, default: false },
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* 取消按钮文字
|
|
106
|
-
*/
|
|
107
|
-
cancelButtonText: { type: String, default: "取消" },
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 点击关闭时的动作
|
|
111
|
-
*/
|
|
112
|
-
close: { type: Function, default: null },
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* 当有分割线时,分割线的配置
|
|
116
|
-
*/
|
|
117
|
-
dividerProps: { type: Object, default: () => ({}) },
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 是否缓存表单数据
|
|
121
|
-
* 开启后,该数据会缓存在 localStorage
|
|
122
|
-
*/
|
|
123
|
-
cacheable: { type: String, default: "" },
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
*
|
|
127
|
-
* 表单配置
|
|
128
|
-
*
|
|
129
|
-
* @typedef {Object} FormItemConfig
|
|
130
|
-
* @property {string} key 数据库关联名称
|
|
131
|
-
* @property {string} title 显示的名字
|
|
132
|
-
* @property {string} [type] 类型,默认是input
|
|
133
|
-
* @property {array|Function} [options] 组件选项
|
|
134
|
-
* @property {array|Function} [rows] 矩阵组件行标题
|
|
135
|
-
* @property {string} [placeholder] 组件里的提示
|
|
136
|
-
* @property {string|Function} [help] ExField 里的提示
|
|
137
|
-
* @property {string|Function} [append] ExField 外的提示
|
|
138
|
-
* @property {array} [rules] 验证规则
|
|
139
|
-
* @property {boolean} [isLink] 是否展示右侧箭头并开启点击反馈
|
|
140
|
-
* @property {boolean} [readonly] 是否只读
|
|
141
|
-
* @property {boolean|Function} [required] 是否必填,默认是false
|
|
142
|
-
* @property {boolean|Function} [disabled] 组件不可编辑状态,默认是false
|
|
143
|
-
* @property {boolean|Function} [hidden] 组件是否隐藏
|
|
144
|
-
* @property {Function} [match] 支持根据条件返回不同的配置进行动态渲染
|
|
145
|
-
* @property {Function} [init] 初始化函数,用于初始化表单项的值
|
|
146
|
-
* @property {Function} [beforeSubmit] 在提交前修改表单项的值,该函数会在 ExForm 的 beforeSubmit 之前调用
|
|
147
|
-
* @property {boolean|string} [break] 新起一行,默认为false,如果为 String 则以 Divider 分割
|
|
148
|
-
* @property {Object} [fieldProps] ExField 的原生配置
|
|
149
|
-
* @property {Object} [exProps] Ex组件配置
|
|
150
|
-
* @property {Object} [defaultProps] 原生 Vant 组件的配置
|
|
151
|
-
* @property {Object} [defaultSlots] 混合 Field 和 input slot 组件的 slots 组合
|
|
152
|
-
* @property {*} [defaultValue] 默认值,默认是空字符串
|
|
153
|
-
* @property {*} [_temp] 临时数据,内部使用
|
|
154
|
-
*/
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* 表单配置,[见下表](#form-表单配置)
|
|
158
|
-
*/
|
|
159
|
-
form: {
|
|
160
|
-
type: Array,
|
|
161
|
-
default() {
|
|
162
|
-
return []
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* fetch 返回数据处理函数
|
|
168
|
-
* @return {Object} 返回处理后的数据,将用于初始化表单
|
|
169
|
-
*/
|
|
170
|
-
afterFetched: { type: Function, default: null },
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
*
|
|
174
|
-
* @typedef {Object} ExposedFormData
|
|
175
|
-
* @property {Object} formatForm Format后的表单数据
|
|
176
|
-
* @property {Object} originalForm 原生的表单数据
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* 提交数据处理函数
|
|
180
|
-
* @param {ExposedFormData} data
|
|
181
|
-
* @return {Boolean|Object} return false会阻止提交操作,return Object会替换提交的数据
|
|
182
|
-
*
|
|
183
|
-
*/
|
|
184
|
-
beforeSubmit: { type: Function, default: null },
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* 提交成功后的回调
|
|
188
|
-
*/
|
|
189
|
-
afterSubmit: { type: Function, default: null },
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* [原生配置](https://vant-contrib.gitee.io/vant/#/zh-CN/form)
|
|
193
|
-
*/
|
|
194
|
-
formProps: { type: Object, default: () => ({}) },
|
|
195
|
-
},
|
|
196
|
-
emits: ["success"],
|
|
197
|
-
|
|
198
|
-
setup(props, { expose, emit, slots }) {
|
|
199
|
-
const formRef = ref(null) //表单容器
|
|
200
|
-
|
|
201
|
-
const state = reactive({
|
|
202
|
-
temporary: {}, // 用于存放一些临时数据
|
|
203
|
-
submitFetcher: {
|
|
204
|
-
loading: false,
|
|
205
|
-
},
|
|
206
|
-
isInitializing: true, //是否正在初始化
|
|
207
|
-
rules: {},
|
|
208
|
-
submitForm: {}, //提交表单,初始化数据后会生成
|
|
209
|
-
submitFormBackup: {}, //初始化后的表单数据备份,用于重置表单以及脏数据判断
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
const formItems = computed(() => props.form)
|
|
213
|
-
|
|
214
|
-
const uploaderProvider = inject(EX_UPLOADER, () => ({}))
|
|
215
|
-
const formProvider = inject(EX_FORM, () => ({}))
|
|
216
|
-
|
|
217
|
-
watch(
|
|
218
|
-
() => props.data,
|
|
219
|
-
(newV) => {
|
|
220
|
-
initFormData(newV || false)
|
|
221
|
-
},
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
watch(
|
|
225
|
-
() => state.submitForm,
|
|
226
|
-
() => {
|
|
227
|
-
if (props.cacheable) {
|
|
228
|
-
useCache(props.cacheable, localStorage).set(state.submitForm)
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
{ deep: true },
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
watch(
|
|
235
|
-
() => formItems.value,
|
|
236
|
-
() => {
|
|
237
|
-
initFormData(props.data || false)
|
|
238
|
-
},
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
const init = (data) => {
|
|
242
|
-
state.isInitializing = false
|
|
243
|
-
initFormData(data || props.data || false)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
onMounted(() => {
|
|
247
|
-
if (props.autoLoad && props.fetchUrl) {
|
|
248
|
-
let auto = true
|
|
249
|
-
if (props.autoLoad && isObject(props.autoLoad)) {
|
|
250
|
-
auto = every(Object.values(pick(props.extraData, Object.keys(props.autoLoad))))
|
|
251
|
-
} else if (props.autoLoad && isString(props.autoLoad)) {
|
|
252
|
-
auto = !!props.extraData[props.autoLoad]
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (auto) {
|
|
256
|
-
fetchItem()
|
|
257
|
-
} else {
|
|
258
|
-
init()
|
|
259
|
-
}
|
|
260
|
-
} else {
|
|
261
|
-
if (props.cacheable) {
|
|
262
|
-
const cachedData = useCache(props.cacheable, localStorage).get()
|
|
263
|
-
if (cachedData) {
|
|
264
|
-
showConfirmDialog({ message: "存在未提交的数据,是否恢复?", lockScroll: false })
|
|
265
|
-
.then(() => {
|
|
266
|
-
init(cachedData)
|
|
267
|
-
})
|
|
268
|
-
.catch(() => {
|
|
269
|
-
useCache(props.cacheable, localStorage).remove()
|
|
270
|
-
init()
|
|
271
|
-
})
|
|
272
|
-
} else {
|
|
273
|
-
init()
|
|
274
|
-
}
|
|
275
|
-
} else {
|
|
276
|
-
init()
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* 初始化表单数据
|
|
283
|
-
*
|
|
284
|
-
* @param {Object} formData 表单数据
|
|
285
|
-
*/
|
|
286
|
-
const initFormData = (formData) => {
|
|
287
|
-
// 如果有 FormData, 则从中提取 FormItem 中有定义的数据,并进行初始化后存放于 extractFormData
|
|
288
|
-
// 无 FromData 则直接使用 FormItem 的 defaultValue
|
|
289
|
-
let extractFormData = {}
|
|
290
|
-
let existingData = formData ? cloneDeep(formData) : false
|
|
291
|
-
formItems.value.forEach((item) => {
|
|
292
|
-
extractFormData[item.key] = initItemDefaultValue(item, existingData, state.submitForm, { uploaderProvider })
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
if (existingData) {
|
|
296
|
-
// 将初始化后的数据覆盖原有数据,并保留不在 FormItems 中的数据
|
|
297
|
-
extractFormData = { ...existingData, ...extractFormData }
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
state.submitForm = extractFormData
|
|
301
|
-
state.submitFormBackup = cloneDeep(extractFormData)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
//远程拿数据模式
|
|
305
|
-
const fetchItem = () => {
|
|
306
|
-
if (props.fetchUrl) {
|
|
307
|
-
state.isInitializing = false
|
|
308
|
-
useFetch()
|
|
309
|
-
.get(props.fetchUrl, { params: props.extraData })
|
|
310
|
-
.then((res) => {
|
|
311
|
-
state.isInitializing = true
|
|
312
|
-
useProcessStatusSuccess(res, () => {
|
|
313
|
-
if (props.afterFetched && isFunction(props.afterFetched)) {
|
|
314
|
-
res = props.afterFetched(res)
|
|
315
|
-
} else if (formProvider.afterFetched && isFunction(formProvider.afterFetched)) {
|
|
316
|
-
res = formProvider.afterFetched(res)
|
|
317
|
-
}
|
|
318
|
-
//有可能出现 isInitializing 未生效 skeleton 还未关闭的情况
|
|
319
|
-
nextTick(() => {
|
|
320
|
-
initFormData(res)
|
|
321
|
-
})
|
|
322
|
-
})
|
|
323
|
-
})
|
|
324
|
-
.finally(() => {
|
|
325
|
-
state.isInitializing = false
|
|
326
|
-
})
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const onSubmit = async () => {
|
|
331
|
-
await formRef.value.validate()
|
|
332
|
-
|
|
333
|
-
if (props.submitConfirmText) {
|
|
334
|
-
try {
|
|
335
|
-
await showConfirmDialog({ message: props.submitConfirmText })
|
|
336
|
-
} catch (e) {
|
|
337
|
-
return
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// 为了在 item 中也能定制 beforeSubmit 这里和下面的 useFormFormat 会重复 copy 一次 submitForm
|
|
342
|
-
let form = cloneDeep(state.submitForm)
|
|
343
|
-
|
|
344
|
-
const itemsWithBeforeSubmit = formItems.value
|
|
345
|
-
.map((item) => {
|
|
346
|
-
if (item.match) {
|
|
347
|
-
// match 的属性需要在这里处理
|
|
348
|
-
return { ...item, ...item.match(form) }
|
|
349
|
-
}
|
|
350
|
-
return item
|
|
351
|
-
})
|
|
352
|
-
.filter((item) => item.beforeSubmit && isFunction(item.beforeSubmit))
|
|
353
|
-
|
|
354
|
-
for (const item of itemsWithBeforeSubmit) {
|
|
355
|
-
form[item.key] = await item.beforeSubmit({
|
|
356
|
-
value: form[item.key],
|
|
357
|
-
submitForm: form, //改成将 form 传出去,这样可以在 form 中添加参数
|
|
358
|
-
})
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
form = useFormFormat(form, formProvider.format || {})
|
|
362
|
-
|
|
363
|
-
if (props.beforeSubmit && isFunction(props.beforeSubmit)) {
|
|
364
|
-
form = await props.beforeSubmit({ formatForm: form, originalForm: state.submitForm })
|
|
365
|
-
if (form === false) {
|
|
366
|
-
return
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
let res = await useFetch(state.submitFetcher).post(props.submitUrl, form)
|
|
372
|
-
|
|
373
|
-
//提交后再次备份表单数据,isDirty 检测即为 false
|
|
374
|
-
state.submitFormBackup = cloneDeep(state.submitForm)
|
|
375
|
-
|
|
376
|
-
if (props.cacheable) {
|
|
377
|
-
useCache(props.cacheable, localStorage).remove()
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (props.afterSubmit) {
|
|
381
|
-
props.afterSubmit(res)
|
|
382
|
-
} else {
|
|
383
|
-
useProcessStatusSuccess(res, () => {
|
|
384
|
-
showSuccessToast(`${props.submitButtonText}成功`)
|
|
385
|
-
emit("success", res)
|
|
386
|
-
})
|
|
387
|
-
}
|
|
388
|
-
} catch (e) {
|
|
389
|
-
useFormFail(e)
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/********** exposes **********/
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* 获取复制的表单数据
|
|
397
|
-
* @return {*}
|
|
398
|
-
*/
|
|
399
|
-
const getFormStandalone = () => cloneDeep(state.submitForm)
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* 获取表单实时数据,慎用,会改变内部的值
|
|
403
|
-
* @return {*}
|
|
404
|
-
*/
|
|
405
|
-
const getFormRealtime = () => state.submitForm
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* 获取表单的字段值
|
|
409
|
-
* @param {String} key
|
|
410
|
-
* @return {*}
|
|
411
|
-
*/
|
|
412
|
-
const getField = (key) => cloneDeep(state.submitForm[key])
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
*
|
|
416
|
-
* 设置表单数据
|
|
417
|
-
* @param {Object} fields
|
|
418
|
-
*/
|
|
419
|
-
const setForm = (fields) => {
|
|
420
|
-
Object.keys(fields).forEach((key) => {
|
|
421
|
-
state.submitForm[key] = fields[key]
|
|
422
|
-
})
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* 判断表单是否被修改
|
|
427
|
-
* @return {boolean}
|
|
428
|
-
*/
|
|
429
|
-
const isDirty = () => {
|
|
430
|
-
return !isEqual(state.submitForm, state.submitFormBackup)
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* 重置表单
|
|
435
|
-
* @param {Object} formData 表单数据
|
|
436
|
-
*/
|
|
437
|
-
const reset = (formData) => {
|
|
438
|
-
initFormData(formData)
|
|
439
|
-
nextTick(() => {
|
|
440
|
-
formRef.value.resetValidation()
|
|
441
|
-
})
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
expose({
|
|
445
|
-
getForm: getFormStandalone,
|
|
446
|
-
getFormStandalone,
|
|
447
|
-
getFormRealtime,
|
|
448
|
-
getField,
|
|
449
|
-
setForm,
|
|
450
|
-
isDirty,
|
|
451
|
-
fetchItem,
|
|
452
|
-
reset,
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
/********** render **********/
|
|
456
|
-
|
|
457
|
-
const formItemElems = () =>
|
|
458
|
-
formItems.value.map((formItem) =>
|
|
459
|
-
createFormItem(formItem, state.submitForm, {
|
|
460
|
-
props,
|
|
461
|
-
slots,
|
|
462
|
-
}),
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
const skeletonElem = () => (
|
|
466
|
-
<Skeleton row={10} title loading={state.isInitializing}>
|
|
467
|
-
{{
|
|
468
|
-
default: () => formItemElems(),
|
|
469
|
-
}}
|
|
470
|
-
</Skeleton>
|
|
471
|
-
)
|
|
472
|
-
|
|
473
|
-
const footerElem = () => {
|
|
474
|
-
if (state.isInitializing) {
|
|
475
|
-
return null
|
|
476
|
-
}
|
|
477
|
-
if (slots.footer) {
|
|
478
|
-
return <div class={"ex-form__footer"}>{slots.footer()}</div>
|
|
479
|
-
}
|
|
480
|
-
return null
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const buttonsElem = () => {
|
|
484
|
-
if (state.isInitializing) {
|
|
485
|
-
return null
|
|
486
|
-
}
|
|
487
|
-
if (props.readonly) {
|
|
488
|
-
return null
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const cancelBtn = (
|
|
492
|
-
<ExButton
|
|
493
|
-
class={"ex-form__cancel-btn"}
|
|
494
|
-
type={"default"}
|
|
495
|
-
plain
|
|
496
|
-
onClick={() => {
|
|
497
|
-
if (props.close && isFunction(props.close)) {
|
|
498
|
-
props.close()
|
|
499
|
-
}
|
|
500
|
-
}}
|
|
501
|
-
>
|
|
502
|
-
{() => props.cancelButtonText}
|
|
503
|
-
</ExButton>
|
|
504
|
-
)
|
|
505
|
-
|
|
506
|
-
const submitBtn = (
|
|
507
|
-
<ExButton disabled={props.submitDisabled} type={"primary"} fetcher={state.submitFetcher} buttonProps={{ nativeType: "submit" }}>
|
|
508
|
-
{() => props.submitButtonText}
|
|
509
|
-
</ExButton>
|
|
510
|
-
)
|
|
511
|
-
|
|
512
|
-
if (props.fixed) {
|
|
513
|
-
return <div class={"ex-form__btn-wrapper-fixed van-hairline--top"}>{[props.closable ? cancelBtn : null, submitBtn]}</div>
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
return <div class={"ex-form__btn-wrapper"}>{[props.closable ? cancelBtn : null, submitBtn]}</div>
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return () => (
|
|
520
|
-
<Form
|
|
521
|
-
ref={formRef}
|
|
522
|
-
labelWidth={props.labelWidth}
|
|
523
|
-
colon={props.colon}
|
|
524
|
-
class={`ex-form ${props.fixed ? "ex-form__fixed" : ""}`}
|
|
525
|
-
disabled={props.disabled}
|
|
526
|
-
readonly={props.readonly}
|
|
527
|
-
scrollToError={true}
|
|
528
|
-
validateFirst={true}
|
|
529
|
-
onSubmit={onSubmit}
|
|
530
|
-
{...props.formProps}
|
|
531
|
-
>
|
|
532
|
-
{{
|
|
533
|
-
default: () => [
|
|
534
|
-
<CellGroup inset={props.inset} title={props.title}>
|
|
535
|
-
{{ default: () => skeletonElem() }}
|
|
536
|
-
</CellGroup>,
|
|
537
|
-
footerElem(),
|
|
538
|
-
buttonsElem(),
|
|
539
|
-
],
|
|
540
|
-
}}
|
|
541
|
-
</Form>
|
|
542
|
-
)
|
|
543
|
-
},
|
|
544
|
-
})
|
|
1
|
+
import { computed, defineComponent, inject, nextTick, onMounted, reactive, ref, watch } from "vue"
|
|
2
|
+
import { EX_UPLOADER, EX_FORM } from "../provider/ExProvider.jsx"
|
|
3
|
+
import { cloneDeep, every, isEqual, isFunction, isObject, isString, pick } from "lodash-es"
|
|
4
|
+
import { initItemDefaultValue } from "./utils"
|
|
5
|
+
import { useCache, useFetch, useFormFail, useFormFormat, useProcessStatusSuccess } from "../../hooks"
|
|
6
|
+
import { CellGroup, Form, showConfirmDialog, showSuccessToast, Skeleton } from "vant"
|
|
7
|
+
import createFormItem from "./FormItem.jsx"
|
|
8
|
+
import ExButton from "../button/ExButton.jsx"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ExForm 表单
|
|
12
|
+
*
|
|
13
|
+
* @version 1.0.0
|
|
14
|
+
*/
|
|
15
|
+
export default defineComponent({
|
|
16
|
+
name: "ExForm",
|
|
17
|
+
props: {
|
|
18
|
+
/**
|
|
19
|
+
* 表单标题
|
|
20
|
+
*/
|
|
21
|
+
title: { type: String, default: "" },
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 表单项 label 宽度,默认单位为px
|
|
25
|
+
*/
|
|
26
|
+
labelWidth: { type: [String, Number], default: "6.2em" },
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 是否在 label 后面添加冒号
|
|
30
|
+
*/
|
|
31
|
+
colon: { type: Boolean, default: false },
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 是否使用卡片模式
|
|
35
|
+
*/
|
|
36
|
+
inset: { type: Boolean, default: false },
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 表单数据,用于初始化表单,并会进行 Watch
|
|
40
|
+
*/
|
|
41
|
+
data: { type: [Object, String], default: "" },
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 是否自动加载
|
|
45
|
+
* true: 表示自动加载数据
|
|
46
|
+
* Array,String: 表示会对 `extraData` 数据中的相关字段进行非空验证,不为空再加载数据
|
|
47
|
+
*/
|
|
48
|
+
autoLoad: {
|
|
49
|
+
//自动加载数据,在fetchData里找
|
|
50
|
+
type: [Boolean, Array, String],
|
|
51
|
+
default: true,
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 获取表单数据的URL
|
|
56
|
+
*/
|
|
57
|
+
fetchUrl: { type: String, default: "" },
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 额外的数据,在提交时会合并到表单数据中并一起提交
|
|
61
|
+
*/
|
|
62
|
+
extraData: { type: Object, default: () => ({}) },
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 是否禁用表单
|
|
66
|
+
*/
|
|
67
|
+
disabled: { type: Boolean, default: false },
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 是否只读
|
|
71
|
+
*/
|
|
72
|
+
readonly: { type: Boolean, default: false },
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* submit button 是否固定在底部
|
|
76
|
+
*/
|
|
77
|
+
fixed: { type: Boolean, default: false },
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 提交数据URL
|
|
81
|
+
*/
|
|
82
|
+
submitUrl: { type: String, default: "" },
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 提交按钮文字
|
|
86
|
+
*/
|
|
87
|
+
submitButtonText: { type: String, default: "保存" },
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 提交确认提示内容
|
|
91
|
+
*/
|
|
92
|
+
submitConfirmText: { type: String, default: "" },
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 是否禁用提交按钮
|
|
96
|
+
*/
|
|
97
|
+
submitDisabled: { type: Boolean, default: false },
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 是否显示关闭按钮
|
|
101
|
+
*/
|
|
102
|
+
closable: { type: Boolean, default: false },
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 取消按钮文字
|
|
106
|
+
*/
|
|
107
|
+
cancelButtonText: { type: String, default: "取消" },
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 点击关闭时的动作
|
|
111
|
+
*/
|
|
112
|
+
close: { type: Function, default: null },
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 当有分割线时,分割线的配置
|
|
116
|
+
*/
|
|
117
|
+
dividerProps: { type: Object, default: () => ({}) },
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 是否缓存表单数据
|
|
121
|
+
* 开启后,该数据会缓存在 localStorage
|
|
122
|
+
*/
|
|
123
|
+
cacheable: { type: String, default: "" },
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
*
|
|
127
|
+
* 表单配置
|
|
128
|
+
*
|
|
129
|
+
* @typedef {Object} FormItemConfig
|
|
130
|
+
* @property {string} key 数据库关联名称
|
|
131
|
+
* @property {string} title 显示的名字
|
|
132
|
+
* @property {string} [type] 类型,默认是input
|
|
133
|
+
* @property {array|Function} [options] 组件选项
|
|
134
|
+
* @property {array|Function} [rows] 矩阵组件行标题
|
|
135
|
+
* @property {string} [placeholder] 组件里的提示
|
|
136
|
+
* @property {string|Function} [help] ExField 里的提示
|
|
137
|
+
* @property {string|Function} [append] ExField 外的提示
|
|
138
|
+
* @property {array} [rules] 验证规则
|
|
139
|
+
* @property {boolean} [isLink] 是否展示右侧箭头并开启点击反馈
|
|
140
|
+
* @property {boolean} [readonly] 是否只读
|
|
141
|
+
* @property {boolean|Function} [required] 是否必填,默认是false
|
|
142
|
+
* @property {boolean|Function} [disabled] 组件不可编辑状态,默认是false
|
|
143
|
+
* @property {boolean|Function} [hidden] 组件是否隐藏
|
|
144
|
+
* @property {Function} [match] 支持根据条件返回不同的配置进行动态渲染
|
|
145
|
+
* @property {Function} [init] 初始化函数,用于初始化表单项的值
|
|
146
|
+
* @property {Function} [beforeSubmit] 在提交前修改表单项的值,该函数会在 ExForm 的 beforeSubmit 之前调用
|
|
147
|
+
* @property {boolean|string} [break] 新起一行,默认为false,如果为 String 则以 Divider 分割
|
|
148
|
+
* @property {Object} [fieldProps] ExField 的原生配置
|
|
149
|
+
* @property {Object} [exProps] Ex组件配置
|
|
150
|
+
* @property {Object} [defaultProps] 原生 Vant 组件的配置
|
|
151
|
+
* @property {Object} [defaultSlots] 混合 Field 和 input slot 组件的 slots 组合
|
|
152
|
+
* @property {*} [defaultValue] 默认值,默认是空字符串
|
|
153
|
+
* @property {*} [_temp] 临时数据,内部使用
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 表单配置,[见下表](#form-表单配置)
|
|
158
|
+
*/
|
|
159
|
+
form: {
|
|
160
|
+
type: Array,
|
|
161
|
+
default() {
|
|
162
|
+
return []
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* fetch 返回数据处理函数
|
|
168
|
+
* @return {Object} 返回处理后的数据,将用于初始化表单
|
|
169
|
+
*/
|
|
170
|
+
afterFetched: { type: Function, default: null },
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
*
|
|
174
|
+
* @typedef {Object} ExposedFormData
|
|
175
|
+
* @property {Object} formatForm Format后的表单数据
|
|
176
|
+
* @property {Object} originalForm 原生的表单数据
|
|
177
|
+
*
|
|
178
|
+
*
|
|
179
|
+
* 提交数据处理函数
|
|
180
|
+
* @param {ExposedFormData} data
|
|
181
|
+
* @return {Boolean|Object} return false会阻止提交操作,return Object会替换提交的数据
|
|
182
|
+
*
|
|
183
|
+
*/
|
|
184
|
+
beforeSubmit: { type: Function, default: null },
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 提交成功后的回调
|
|
188
|
+
*/
|
|
189
|
+
afterSubmit: { type: Function, default: null },
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* [原生配置](https://vant-contrib.gitee.io/vant/#/zh-CN/form)
|
|
193
|
+
*/
|
|
194
|
+
formProps: { type: Object, default: () => ({}) },
|
|
195
|
+
},
|
|
196
|
+
emits: ["success"],
|
|
197
|
+
|
|
198
|
+
setup(props, { expose, emit, slots }) {
|
|
199
|
+
const formRef = ref(null) //表单容器
|
|
200
|
+
|
|
201
|
+
const state = reactive({
|
|
202
|
+
temporary: {}, // 用于存放一些临时数据
|
|
203
|
+
submitFetcher: {
|
|
204
|
+
loading: false,
|
|
205
|
+
},
|
|
206
|
+
isInitializing: true, //是否正在初始化
|
|
207
|
+
rules: {},
|
|
208
|
+
submitForm: {}, //提交表单,初始化数据后会生成
|
|
209
|
+
submitFormBackup: {}, //初始化后的表单数据备份,用于重置表单以及脏数据判断
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const formItems = computed(() => props.form)
|
|
213
|
+
|
|
214
|
+
const uploaderProvider = inject(EX_UPLOADER, () => ({}))
|
|
215
|
+
const formProvider = inject(EX_FORM, () => ({}))
|
|
216
|
+
|
|
217
|
+
watch(
|
|
218
|
+
() => props.data,
|
|
219
|
+
(newV) => {
|
|
220
|
+
initFormData(newV || false)
|
|
221
|
+
},
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
watch(
|
|
225
|
+
() => state.submitForm,
|
|
226
|
+
() => {
|
|
227
|
+
if (props.cacheable) {
|
|
228
|
+
useCache(props.cacheable, localStorage).set(state.submitForm)
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
{ deep: true },
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
watch(
|
|
235
|
+
() => formItems.value,
|
|
236
|
+
() => {
|
|
237
|
+
initFormData(props.data || false)
|
|
238
|
+
},
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
const init = (data) => {
|
|
242
|
+
state.isInitializing = false
|
|
243
|
+
initFormData(data || props.data || false)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
onMounted(() => {
|
|
247
|
+
if (props.autoLoad && props.fetchUrl) {
|
|
248
|
+
let auto = true
|
|
249
|
+
if (props.autoLoad && isObject(props.autoLoad)) {
|
|
250
|
+
auto = every(Object.values(pick(props.extraData, Object.keys(props.autoLoad))))
|
|
251
|
+
} else if (props.autoLoad && isString(props.autoLoad)) {
|
|
252
|
+
auto = !!props.extraData[props.autoLoad]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (auto) {
|
|
256
|
+
fetchItem()
|
|
257
|
+
} else {
|
|
258
|
+
init()
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
if (props.cacheable) {
|
|
262
|
+
const cachedData = useCache(props.cacheable, localStorage).get()
|
|
263
|
+
if (cachedData) {
|
|
264
|
+
showConfirmDialog({ message: "存在未提交的数据,是否恢复?", lockScroll: false })
|
|
265
|
+
.then(() => {
|
|
266
|
+
init(cachedData)
|
|
267
|
+
})
|
|
268
|
+
.catch(() => {
|
|
269
|
+
useCache(props.cacheable, localStorage).remove()
|
|
270
|
+
init()
|
|
271
|
+
})
|
|
272
|
+
} else {
|
|
273
|
+
init()
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
init()
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 初始化表单数据
|
|
283
|
+
*
|
|
284
|
+
* @param {Object} formData 表单数据
|
|
285
|
+
*/
|
|
286
|
+
const initFormData = (formData) => {
|
|
287
|
+
// 如果有 FormData, 则从中提取 FormItem 中有定义的数据,并进行初始化后存放于 extractFormData
|
|
288
|
+
// 无 FromData 则直接使用 FormItem 的 defaultValue
|
|
289
|
+
let extractFormData = {}
|
|
290
|
+
let existingData = formData ? cloneDeep(formData) : false
|
|
291
|
+
formItems.value.forEach((item) => {
|
|
292
|
+
extractFormData[item.key] = initItemDefaultValue(item, existingData, state.submitForm, { uploaderProvider })
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
if (existingData) {
|
|
296
|
+
// 将初始化后的数据覆盖原有数据,并保留不在 FormItems 中的数据
|
|
297
|
+
extractFormData = { ...existingData, ...extractFormData }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
state.submitForm = extractFormData
|
|
301
|
+
state.submitFormBackup = cloneDeep(extractFormData)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
//远程拿数据模式
|
|
305
|
+
const fetchItem = () => {
|
|
306
|
+
if (props.fetchUrl) {
|
|
307
|
+
state.isInitializing = false
|
|
308
|
+
useFetch()
|
|
309
|
+
.get(props.fetchUrl, { params: props.extraData })
|
|
310
|
+
.then((res) => {
|
|
311
|
+
state.isInitializing = true
|
|
312
|
+
useProcessStatusSuccess(res, () => {
|
|
313
|
+
if (props.afterFetched && isFunction(props.afterFetched)) {
|
|
314
|
+
res = props.afterFetched(res)
|
|
315
|
+
} else if (formProvider.afterFetched && isFunction(formProvider.afterFetched)) {
|
|
316
|
+
res = formProvider.afterFetched(res)
|
|
317
|
+
}
|
|
318
|
+
//有可能出现 isInitializing 未生效 skeleton 还未关闭的情况
|
|
319
|
+
nextTick(() => {
|
|
320
|
+
initFormData(res)
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
})
|
|
324
|
+
.finally(() => {
|
|
325
|
+
state.isInitializing = false
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const onSubmit = async () => {
|
|
331
|
+
await formRef.value.validate()
|
|
332
|
+
|
|
333
|
+
if (props.submitConfirmText) {
|
|
334
|
+
try {
|
|
335
|
+
await showConfirmDialog({ message: props.submitConfirmText })
|
|
336
|
+
} catch (e) {
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 为了在 item 中也能定制 beforeSubmit 这里和下面的 useFormFormat 会重复 copy 一次 submitForm
|
|
342
|
+
let form = cloneDeep(state.submitForm)
|
|
343
|
+
|
|
344
|
+
const itemsWithBeforeSubmit = formItems.value
|
|
345
|
+
.map((item) => {
|
|
346
|
+
if (item.match) {
|
|
347
|
+
// match 的属性需要在这里处理
|
|
348
|
+
return { ...item, ...item.match(form) }
|
|
349
|
+
}
|
|
350
|
+
return item
|
|
351
|
+
})
|
|
352
|
+
.filter((item) => item.beforeSubmit && isFunction(item.beforeSubmit))
|
|
353
|
+
|
|
354
|
+
for (const item of itemsWithBeforeSubmit) {
|
|
355
|
+
form[item.key] = await item.beforeSubmit({
|
|
356
|
+
value: form[item.key],
|
|
357
|
+
submitForm: form, //改成将 form 传出去,这样可以在 form 中添加参数
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
form = useFormFormat(form, formProvider.format || {})
|
|
362
|
+
|
|
363
|
+
if (props.beforeSubmit && isFunction(props.beforeSubmit)) {
|
|
364
|
+
form = await props.beforeSubmit({ formatForm: form, originalForm: state.submitForm })
|
|
365
|
+
if (form === false) {
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
let res = await useFetch(state.submitFetcher).post(props.submitUrl, form)
|
|
372
|
+
|
|
373
|
+
//提交后再次备份表单数据,isDirty 检测即为 false
|
|
374
|
+
state.submitFormBackup = cloneDeep(state.submitForm)
|
|
375
|
+
|
|
376
|
+
if (props.cacheable) {
|
|
377
|
+
useCache(props.cacheable, localStorage).remove()
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (props.afterSubmit) {
|
|
381
|
+
props.afterSubmit(res)
|
|
382
|
+
} else {
|
|
383
|
+
useProcessStatusSuccess(res, () => {
|
|
384
|
+
showSuccessToast(`${props.submitButtonText}成功`)
|
|
385
|
+
emit("success", res)
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
} catch (e) {
|
|
389
|
+
useFormFail(e)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/********** exposes **********/
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* 获取复制的表单数据
|
|
397
|
+
* @return {*}
|
|
398
|
+
*/
|
|
399
|
+
const getFormStandalone = () => cloneDeep(state.submitForm)
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* 获取表单实时数据,慎用,会改变内部的值
|
|
403
|
+
* @return {*}
|
|
404
|
+
*/
|
|
405
|
+
const getFormRealtime = () => state.submitForm
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* 获取表单的字段值
|
|
409
|
+
* @param {String} key
|
|
410
|
+
* @return {*}
|
|
411
|
+
*/
|
|
412
|
+
const getField = (key) => cloneDeep(state.submitForm[key])
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
*
|
|
416
|
+
* 设置表单数据
|
|
417
|
+
* @param {Object} fields
|
|
418
|
+
*/
|
|
419
|
+
const setForm = (fields) => {
|
|
420
|
+
Object.keys(fields).forEach((key) => {
|
|
421
|
+
state.submitForm[key] = fields[key]
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 判断表单是否被修改
|
|
427
|
+
* @return {boolean}
|
|
428
|
+
*/
|
|
429
|
+
const isDirty = () => {
|
|
430
|
+
return !isEqual(state.submitForm, state.submitFormBackup)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* 重置表单
|
|
435
|
+
* @param {Object} formData 表单数据
|
|
436
|
+
*/
|
|
437
|
+
const reset = (formData) => {
|
|
438
|
+
initFormData(formData)
|
|
439
|
+
nextTick(() => {
|
|
440
|
+
formRef.value.resetValidation()
|
|
441
|
+
})
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
expose({
|
|
445
|
+
getForm: getFormStandalone,
|
|
446
|
+
getFormStandalone,
|
|
447
|
+
getFormRealtime,
|
|
448
|
+
getField,
|
|
449
|
+
setForm,
|
|
450
|
+
isDirty,
|
|
451
|
+
fetchItem,
|
|
452
|
+
reset,
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
/********** render **********/
|
|
456
|
+
|
|
457
|
+
const formItemElems = () =>
|
|
458
|
+
formItems.value.map((formItem) =>
|
|
459
|
+
createFormItem(formItem, state.submitForm, {
|
|
460
|
+
props,
|
|
461
|
+
slots,
|
|
462
|
+
}),
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
const skeletonElem = () => (
|
|
466
|
+
<Skeleton row={10} title loading={state.isInitializing}>
|
|
467
|
+
{{
|
|
468
|
+
default: () => formItemElems(),
|
|
469
|
+
}}
|
|
470
|
+
</Skeleton>
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
const footerElem = () => {
|
|
474
|
+
if (state.isInitializing) {
|
|
475
|
+
return null
|
|
476
|
+
}
|
|
477
|
+
if (slots.footer) {
|
|
478
|
+
return <div class={"ex-form__footer"}>{slots.footer()}</div>
|
|
479
|
+
}
|
|
480
|
+
return null
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const buttonsElem = () => {
|
|
484
|
+
if (state.isInitializing) {
|
|
485
|
+
return null
|
|
486
|
+
}
|
|
487
|
+
if (props.readonly) {
|
|
488
|
+
return null
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const cancelBtn = (
|
|
492
|
+
<ExButton
|
|
493
|
+
class={"ex-form__cancel-btn"}
|
|
494
|
+
type={"default"}
|
|
495
|
+
plain
|
|
496
|
+
onClick={() => {
|
|
497
|
+
if (props.close && isFunction(props.close)) {
|
|
498
|
+
props.close()
|
|
499
|
+
}
|
|
500
|
+
}}
|
|
501
|
+
>
|
|
502
|
+
{() => props.cancelButtonText}
|
|
503
|
+
</ExButton>
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
const submitBtn = (
|
|
507
|
+
<ExButton disabled={props.submitDisabled} type={"primary"} fetcher={state.submitFetcher} buttonProps={{ nativeType: "submit" }}>
|
|
508
|
+
{() => props.submitButtonText}
|
|
509
|
+
</ExButton>
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
if (props.fixed) {
|
|
513
|
+
return <div class={"ex-form__btn-wrapper-fixed van-hairline--top"}>{[props.closable ? cancelBtn : null, submitBtn]}</div>
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return <div class={"ex-form__btn-wrapper"}>{[props.closable ? cancelBtn : null, submitBtn]}</div>
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return () => (
|
|
520
|
+
<Form
|
|
521
|
+
ref={formRef}
|
|
522
|
+
labelWidth={props.labelWidth}
|
|
523
|
+
colon={props.colon}
|
|
524
|
+
class={`ex-form ${props.fixed ? "ex-form__fixed" : ""}`}
|
|
525
|
+
disabled={props.disabled}
|
|
526
|
+
readonly={props.readonly}
|
|
527
|
+
scrollToError={true}
|
|
528
|
+
validateFirst={true}
|
|
529
|
+
onSubmit={onSubmit}
|
|
530
|
+
{...props.formProps}
|
|
531
|
+
>
|
|
532
|
+
{{
|
|
533
|
+
default: () => [
|
|
534
|
+
<CellGroup inset={props.inset} title={props.title}>
|
|
535
|
+
{{ default: () => skeletonElem() }}
|
|
536
|
+
</CellGroup>,
|
|
537
|
+
footerElem(),
|
|
538
|
+
buttonsElem(),
|
|
539
|
+
],
|
|
540
|
+
}}
|
|
541
|
+
</Form>
|
|
542
|
+
)
|
|
543
|
+
},
|
|
544
|
+
})
|