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.
Files changed (180) hide show
  1. package/.eslintignore +3 -3
  2. package/CHANGELOG.md +542 -542
  3. package/README.md +41 -41
  4. package/TODOs.md +8 -8
  5. package/business-components/survey/ExSurvey.jsx +193 -193
  6. package/business-components/survey/index.js +5 -5
  7. package/business-components/survey/index.less +36 -36
  8. package/components/button/ExButton.jsx +120 -120
  9. package/components/button/index.js +4 -4
  10. package/components/button/index.less +7 -7
  11. package/components/decorator/ExDecorator.jsx +31 -31
  12. package/components/decorator/index.js +5 -5
  13. package/components/decorator/index.less +76 -76
  14. package/components/form/ExAddress.jsx +195 -195
  15. package/components/form/ExCascader.jsx +171 -171
  16. package/components/form/ExCheckbox.jsx +59 -59
  17. package/components/form/ExDate.jsx +143 -143
  18. package/components/form/ExDatetime.jsx +166 -166
  19. package/components/form/ExField.jsx +138 -138
  20. package/components/form/ExFieldUploader.jsx +50 -50
  21. package/components/form/ExForm.jsx +544 -544
  22. package/components/form/ExMatrixCheckbox.jsx +99 -99
  23. package/components/form/ExMatrixRadio.jsx +86 -86
  24. package/components/form/ExMatrixScale.jsx +97 -97
  25. package/components/form/ExNumber.jsx +51 -51
  26. package/components/form/ExRadio.jsx +58 -58
  27. package/components/form/ExRate.jsx +51 -51
  28. package/components/form/ExSelect.jsx +251 -251
  29. package/components/form/ExSlider.jsx +55 -55
  30. package/components/form/ExSwitch.jsx +51 -51
  31. package/components/form/ExTime.jsx +99 -99
  32. package/components/form/FormItem.jsx +307 -307
  33. package/components/form/PickerWrapper.jsx +120 -120
  34. package/components/form/index.js +46 -46
  35. package/components/form/index.less +178 -178
  36. package/components/form/utils.js +62 -62
  37. package/components/grid/ExGrid.jsx +53 -53
  38. package/components/grid/index.js +4 -4
  39. package/components/grid/index.less +2 -2
  40. package/components/index.js +12 -12
  41. package/components/pagination/ExPagination.jsx +457 -440
  42. package/components/pagination/index.js +5 -5
  43. package/components/pagination/index.less +3 -3
  44. package/components/provider/ExProvider.jsx +173 -173
  45. package/components/qrcode/ExQrcode.jsx +86 -86
  46. package/components/qrcode/index.js +5 -5
  47. package/components/qrcode/index.less +8 -8
  48. package/components/result/ExResult.jsx +122 -122
  49. package/components/result/index.js +5 -5
  50. package/components/result/index.less +59 -59
  51. package/components/search/ExSearch.jsx +370 -326
  52. package/components/search/components/Expand.jsx +77 -77
  53. package/components/search/components/Field.jsx +27 -27
  54. package/components/search/components/Quick.jsx +57 -57
  55. package/components/search/components/index.js +5 -5
  56. package/components/search/index.js +5 -5
  57. package/components/search/index.less +118 -118
  58. package/components/search/utils.js +30 -30
  59. package/components/sector/ExSector.jsx +52 -52
  60. package/components/sector/README.md +26 -26
  61. package/components/sector/index.js +5 -5
  62. package/components/sector/index.less +122 -122
  63. package/components/theme/ExTheme.jsx +10 -10
  64. package/components/theme/index.js +4 -4
  65. package/components/theme/index.less +98 -98
  66. package/components/uploader/ExUploader.jsx +293 -293
  67. package/components/uploader/index.js +5 -5
  68. package/components/utils.js +187 -187
  69. package/directives/auth.js +113 -113
  70. package/directives/index.js +4 -4
  71. package/dist/cipher-98df1050.cjs.map +1 -1
  72. package/dist/cipher-f2ed5ee6.js.map +1 -1
  73. package/dist/directives.cjs.map +1 -1
  74. package/dist/directives.js.map +1 -1
  75. package/dist/hooks.cjs.map +1 -1
  76. package/dist/hooks.js.map +1 -1
  77. package/dist/jobsys-explore.cjs +6 -6
  78. package/dist/jobsys-explore.cjs.map +1 -1
  79. package/dist/jobsys-explore.js +464 -407
  80. package/dist/jobsys-explore.js.map +1 -1
  81. package/docgen.config.js +15 -15
  82. package/docs/.vuepress/.cache/deps/_metadata.json +52 -52
  83. package/docs/.vuepress/.cache/deps/lodash-es.js +8442 -8442
  84. package/docs/.vuepress/.cache/deps/lodash-es.js.map +7 -7
  85. package/docs/.vuepress/.temp/internal/clientConfigs.js +17 -17
  86. package/docs/.vuepress/.temp/internal/pagesComponents.js +24 -24
  87. package/docs/.vuepress/.temp/internal/pagesData.js +22 -22
  88. package/docs/.vuepress/.temp/internal/pagesRoutes.js +12 -12
  89. package/docs/.vuepress/.temp/internal/themeData.js +1 -1
  90. package/docs/.vuepress/.temp/pages/components/decorator/ExDecorator.html.js +1 -1
  91. package/docs/.vuepress/.temp/pages/components/decorator/ExDecorator.html.vue +37 -37
  92. package/docs/.vuepress/.temp/pages/components/sector/ExSector.html.js +1 -1
  93. package/docs/.vuepress/.temp/pages/components/sector/ExSector.html.vue +71 -71
  94. package/docs/.vuepress/.temp/styles/index.scss +1 -1
  95. package/docs/.vuepress/config.js +61 -61
  96. package/docs/.vuepress/dist/404.html +33 -33
  97. package/docs/.vuepress/dist/assets/404.html-a0ce2184.js +1 -1
  98. package/docs/.vuepress/dist/assets/ExButton.html-ad283101.js +1 -1
  99. package/docs/.vuepress/dist/assets/ExDecorator.html-42d09114.js +1 -1
  100. package/docs/.vuepress/dist/assets/ExDecorator.html-c82c5fe8.js +1 -1
  101. package/docs/.vuepress/dist/assets/ExForm.html-9e3f8000.js +1 -1
  102. package/docs/.vuepress/dist/assets/ExProvider.html-78fdc6cd.js +1 -1
  103. package/docs/.vuepress/dist/assets/ExSearch.html-103f6f34.js +1 -1
  104. package/docs/.vuepress/dist/assets/ExSector.html-a1e24c3a.js +7 -7
  105. package/docs/.vuepress/dist/assets/ExSector.html-cff3fefd.js +1 -1
  106. package/docs/.vuepress/dist/assets/ExUploader.html-8310e424.js +1 -1
  107. package/docs/.vuepress/dist/assets/app-29fe8d1e.js +10 -10
  108. package/docs/.vuepress/dist/assets/hooks.html-90ccbc1a.js +1 -1
  109. package/docs/.vuepress/dist/assets/index.html-85b79c97.js +43 -43
  110. package/docs/.vuepress/dist/assets/style-46d7e227.css +1 -1
  111. package/docs/.vuepress/dist/components/button/ExButton.html +33 -33
  112. package/docs/.vuepress/dist/components/decorator/ExDecorator.html +33 -33
  113. package/docs/.vuepress/dist/components/form/ExForm.html +33 -33
  114. package/docs/.vuepress/dist/components/provider/ExProvider.html +33 -33
  115. package/docs/.vuepress/dist/components/search/ExSearch.html +33 -33
  116. package/docs/.vuepress/dist/components/sector/ExSector.html +39 -39
  117. package/docs/.vuepress/dist/components/uploader/ExUploader.html +33 -33
  118. package/docs/.vuepress/dist/hooks.html +33 -33
  119. package/docs/.vuepress/dist/index.html +75 -75
  120. package/docs/.vuepress/styles/index.scss +7 -7
  121. package/docs/components/decorator/ExDecorator.md +14 -14
  122. package/docs/components/sector/ExSector.md +43 -43
  123. package/docs/index.md +82 -82
  124. package/hooks/cipher.js +44 -44
  125. package/hooks/datetime.js +69 -69
  126. package/hooks/form.js +188 -188
  127. package/hooks/utils.js +282 -282
  128. package/index.html +17 -17
  129. package/package.json +1 -1
  130. package/playground/App.vue +191 -191
  131. package/playground/TestButton.vue +61 -61
  132. package/playground/TestCascader.vue +2442 -2442
  133. package/playground/TestDecorator.vue +14 -14
  134. package/playground/TestForm.vue +429 -429
  135. package/playground/TestFormItem.vue +110 -110
  136. package/playground/TestGrid.vue +22 -22
  137. package/playground/TestPagination.vue +1250 -1248
  138. package/playground/TestQrcode.vue +7 -7
  139. package/playground/TestResult.vue +12 -12
  140. package/playground/TestSearch.vue +115 -115
  141. package/playground/TestSector.vue +15 -15
  142. package/playground/TestSurvey.vue +27 -27
  143. package/playground/TestUploader.vue +14 -14
  144. package/playground/main.js +22 -22
  145. package/utils/style.js +13 -13
  146. package/vite.config.js +54 -54
  147. package/.changeset/blue-spiders-roll.md +0 -5
  148. package/.changeset/cyan-monkeys-draw.md +0 -5
  149. package/.changeset/dry-feet-float.md +0 -5
  150. package/.changeset/empty-mice-share.md +0 -5
  151. package/.changeset/famous-yaks-doubt.md +0 -5
  152. package/.changeset/five-fans-type.md +0 -5
  153. package/.changeset/funny-hats-drop.md +0 -5
  154. package/.changeset/khaki-cobras-bathe.md +0 -5
  155. package/.changeset/khaki-forks-shave.md +0 -5
  156. package/.changeset/lazy-yaks-crash.md +0 -5
  157. package/.changeset/light-cycles-flow.md +0 -5
  158. package/.changeset/loud-mirrors-explain.md +0 -5
  159. package/.changeset/lovely-balloons-protect.md +0 -5
  160. package/.changeset/mean-pens-travel.md +0 -5
  161. package/.changeset/moody-doors-grow.md +0 -5
  162. package/.changeset/moody-laws-change.md +0 -5
  163. package/.changeset/nasty-goats-joke.md +0 -5
  164. package/.changeset/odd-forks-drop.md +0 -5
  165. package/.changeset/olive-windows-suffer.md +0 -5
  166. package/.changeset/popular-carpets-jog.md +0 -5
  167. package/.changeset/popular-planets-play.md +0 -5
  168. package/.changeset/rare-gorillas-boil.md +0 -5
  169. package/.changeset/rare-moose-teach.md +0 -5
  170. package/.changeset/sharp-tools-hope.md +0 -5
  171. package/.changeset/slimy-sloths-refuse.md +0 -5
  172. package/.changeset/slow-boats-search.md +0 -5
  173. package/.changeset/small-experts-bake.md +0 -5
  174. package/.changeset/smooth-horses-tie.md +0 -5
  175. package/.changeset/tame-feet-reply.md +0 -5
  176. package/.changeset/tidy-items-reflect.md +0 -5
  177. package/.changeset/weak-chicken-admire.md +0 -5
  178. package/.changeset/weak-rockets-compare.md +0 -5
  179. package/.changeset/wild-glasses-bathe.md +0 -5
  180. 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
+ })