jobsys-explore 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.eslintignore +4 -0
  2. package/.eslintrc.cjs +38 -0
  3. package/.prettierrc.cjs +38 -0
  4. package/README.md +42 -0
  5. package/components/button/ExButton.jsx +119 -0
  6. package/components/button/index.js +4 -0
  7. package/components/button/index.less +8 -0
  8. package/components/form/ExAddress.jsx +125 -0
  9. package/components/form/ExCascader.jsx +98 -0
  10. package/components/form/ExCheckbox.jsx +59 -0
  11. package/components/form/ExDate.jsx +130 -0
  12. package/components/form/ExDatetime.jsx +152 -0
  13. package/components/form/ExField.jsx +123 -0
  14. package/components/form/ExFieldUploader.jsx +44 -0
  15. package/components/form/ExForm.jsx +395 -0
  16. package/components/form/ExNumber.jsx +49 -0
  17. package/components/form/ExRadio.jsx +58 -0
  18. package/components/form/ExRate.jsx +47 -0
  19. package/components/form/ExSelect.jsx +151 -0
  20. package/components/form/ExSlider.jsx +53 -0
  21. package/components/form/ExSwitch.jsx +49 -0
  22. package/components/form/ExTime.jsx +104 -0
  23. package/components/form/FormItem.jsx +229 -0
  24. package/components/form/PickerWrapper.jsx +111 -0
  25. package/components/form/addressData.json +1 -0
  26. package/components/form/index.js +37 -0
  27. package/components/form/index.less +108 -0
  28. package/components/form/utils.js +47 -0
  29. package/components/grid/ExGrid.jsx +53 -0
  30. package/components/grid/index.js +4 -0
  31. package/components/grid/index.less +3 -0
  32. package/components/index.js +9 -0
  33. package/components/pagination/ExPagination.jsx +148 -0
  34. package/components/pagination/index.js +5 -0
  35. package/components/pagination/index.less +15 -0
  36. package/components/provider/ExProvider.jsx +91 -0
  37. package/components/provider/index.js +4 -0
  38. package/components/qrcode/ExQrcode.jsx +86 -0
  39. package/components/qrcode/index.js +5 -0
  40. package/components/qrcode/index.less +8 -0
  41. package/components/result/ExResult.jsx +122 -0
  42. package/components/result/index.js +5 -0
  43. package/components/result/index.less +60 -0
  44. package/components/search/ExSearch.jsx +252 -0
  45. package/components/search/components/Expand.jsx +77 -0
  46. package/components/search/components/Field.jsx +27 -0
  47. package/components/search/components/Quick.jsx +53 -0
  48. package/components/search/components/index.js +5 -0
  49. package/components/search/index.js +5 -0
  50. package/components/search/index.less +119 -0
  51. package/components/search/utils.js +30 -0
  52. package/components/theme/ExTheme.jsx +10 -0
  53. package/components/theme/index.js +4 -0
  54. package/components/theme/index.less +97 -0
  55. package/components/uploader/ExUploader.jsx +248 -0
  56. package/components/uploader/index.js +5 -0
  57. package/components/utils.js +150 -0
  58. package/dist/__vite-browser-external-2447137e.mjs +5 -0
  59. package/dist/__vite-browser-external-2447137e.mjs.map +1 -0
  60. package/dist/__vite-browser-external-b3701507.js +2 -0
  61. package/dist/__vite-browser-external-b3701507.js.map +1 -0
  62. package/dist/hooks.cjs +2 -0
  63. package/dist/hooks.cjs.map +1 -0
  64. package/dist/hooks.js +35 -0
  65. package/dist/hooks.js.map +1 -0
  66. package/dist/index-82d74e5f.mjs +2120 -0
  67. package/dist/index-82d74e5f.mjs.map +1 -0
  68. package/dist/index-b8729555.js +2 -0
  69. package/dist/index-b8729555.js.map +1 -0
  70. package/dist/jobsys-explore.cjs +9 -0
  71. package/dist/jobsys-explore.cjs.map +1 -0
  72. package/dist/jobsys-explore.js +18673 -0
  73. package/dist/jobsys-explore.js.map +1 -0
  74. package/dist/style.css +1 -0
  75. package/docgen.config.cjs +9 -0
  76. package/docs/.vitepress/cache/deps/_metadata.json +19 -0
  77. package/docs/.vitepress/cache/deps/package.json +3 -0
  78. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +162 -0
  79. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  80. package/docs/.vitepress/cache/deps/vue.js +10795 -0
  81. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  82. package/docs/.vitepress/config.mjs +43 -0
  83. package/docs/.vitepress/dist/404.html +19 -0
  84. package/docs/.vitepress/dist/assets/app.e0eb4814.js +1 -0
  85. package/docs/.vitepress/dist/assets/chunks/framework.7a64ad8e.js +2 -0
  86. package/docs/.vitepress/dist/assets/chunks/theme.ffc5f35c.js +7 -0
  87. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.33bd5a8e.woff2 +0 -0
  88. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.ea42a392.woff2 +0 -0
  89. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.4fbe9427.woff2 +0 -0
  90. package/docs/.vitepress/dist/assets/inter-italic-greek.8f4463c4.woff2 +0 -0
  91. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.bd8920cc.woff2 +0 -0
  92. package/docs/.vitepress/dist/assets/inter-italic-latin.bd3b6f56.woff2 +0 -0
  93. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.6ce511fb.woff2 +0 -0
  94. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.e75737ce.woff2 +0 -0
  95. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.5f2c6c8c.woff2 +0 -0
  96. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.ab0619bc.woff2 +0 -0
  97. package/docs/.vitepress/dist/assets/inter-roman-greek.d5a6d92a.woff2 +0 -0
  98. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.0030eebd.woff2 +0 -0
  99. package/docs/.vitepress/dist/assets/inter-roman-latin.2ed14f66.woff2 +0 -0
  100. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.14ce25a6.woff2 +0 -0
  101. package/docs/.vitepress/dist/assets/style.6747f984.css +1 -0
  102. package/docs/.vitepress/dist/hashmap.json +1 -0
  103. package/docs/.vitepress/theme/index.css +0 -0
  104. package/docs/.vitepress/theme/index.js +12 -0
  105. package/hooks/cipher.js +40 -0
  106. package/hooks/form.js +176 -0
  107. package/hooks/index.js +4 -0
  108. package/hooks/network.js +153 -0
  109. package/hooks/utils.js +61 -0
  110. package/index.html +17 -0
  111. package/index.js +10 -0
  112. package/package.json +75 -0
  113. package/playground/App.vue +45 -0
  114. package/playground/TestButton.vue +15 -0
  115. package/playground/TestForm.vue +271 -0
  116. package/playground/TestFormItem.vue +110 -0
  117. package/playground/TestGrid.vue +22 -0
  118. package/playground/TestPagination.vue +89 -0
  119. package/playground/TestQrcode.vue +7 -0
  120. package/playground/TestResult.vue +12 -0
  121. package/playground/TestSearch.vue +84 -0
  122. package/playground/http.js +23 -0
  123. package/playground/main.js +12 -0
  124. package/postcss.config.cjs +8 -0
  125. package/utils/style.js +13 -0
  126. package/utils/withInstall.js +7 -0
  127. package/vite.config.js +52 -0
@@ -0,0 +1,152 @@
1
+ import PickerWrapper, { pickerSlots } from "./PickerWrapper.jsx"
2
+ import { ref, defineComponent, computed, watch } from "vue"
3
+ import { Calendar, Collapse, CollapseItem, TimePicker } from "vant"
4
+ import ExButton from "../button/ExButton.jsx"
5
+ import { padZero } from "vant/es/utils"
6
+ import { defaultFieldProps } from "../utils"
7
+
8
+ /**
9
+ * ExDatetime 日期时间选择器
10
+ * @version 1.0.0
11
+ */
12
+ export default defineComponent({
13
+ name: "ExDatetime",
14
+ props: {
15
+ ...defaultFieldProps,
16
+
17
+ modelValue: { type: Date, default: () => null },
18
+
19
+ /**
20
+ * 原生[Calendar 配置](https://vant-contrib.gitee.io/vant/#/zh-CN/calendar)
21
+ */
22
+ calendarProps: { type: Object, default: () => ({}) },
23
+
24
+ /**
25
+ * 原生[TimePicker 配置](https://vant-contrib.gitee.io/vant/#/zh-CN/time-picker)
26
+ */
27
+ timeProps: { type: Object, default: () => ({}) },
28
+ },
29
+ emits: ["update:modelValue"],
30
+ setup(props, { slots, emit, expose }) {
31
+ const dateValue = ref(null)
32
+ const timeValue = ref(null)
33
+
34
+ /**
35
+ *
36
+ * @param {Date|null} date
37
+ */
38
+ const prepareDate = (date) => {
39
+ if (date) {
40
+ dateValue.value = date
41
+ timeValue.value = `${padZero(date.getHours(), 2)}:${padZero(date.getMinutes(), 2)}`
42
+ }
43
+ }
44
+
45
+ prepareDate(props.modelValue)
46
+
47
+ watch(
48
+ () => props.modelValue,
49
+ () => prepareDate(props.modelValue),
50
+ )
51
+
52
+ const pickerRef = ref()
53
+
54
+ const activeName = ref("date")
55
+
56
+ const minDate = new Date(2010, 0, 1)
57
+ const maxDate = new Date(2045, 11, 31)
58
+
59
+ const weekdays = ["日", "一", "二", "三", "四", "五", "六"]
60
+
61
+ const dateText = computed(() => {
62
+ if (dateValue.value) {
63
+ return `${dateValue.value.getFullYear()}年${dateValue.value.getMonth() + 1}月${dateValue.value.getDate()}日 星期${
64
+ weekdays[dateValue.value.getDay()]
65
+ }`
66
+ }
67
+ return "请选择日期"
68
+ })
69
+ const timeText = computed(() => {
70
+ if (timeValue.value) {
71
+ return timeValue.value
72
+ }
73
+ return "请选择时间"
74
+ })
75
+
76
+ const displayText = computed(() => (dateValue.value && timeValue.value ? `${dateText.value} ${timeText.value}` : ""))
77
+
78
+ expose({ displayText })
79
+
80
+ const onSelectDate = (date) => {
81
+ dateValue.value = date
82
+ }
83
+
84
+ const onTimeChange = ({ selectedValues }) => {
85
+ timeValue.value = selectedValues.join(":")
86
+ }
87
+
88
+ const onConfirm = () => {
89
+ let dateString = `${dateValue.value.getFullYear()}/${dateValue.value.getMonth() + 1}/${dateValue.value.getDate()}`
90
+
91
+ if (!timeValue.value) {
92
+ dateString += ` 00:00:00`
93
+ } else {
94
+ dateString += ` ${timeValue.value}`
95
+ }
96
+
97
+ pickerRef.value.close()
98
+
99
+ emit("update:modelValue", new Date(dateString))
100
+ }
101
+
102
+ return () => {
103
+ return (
104
+ <PickerWrapper ref={pickerRef} inset={true}>
105
+ {{
106
+ ...pickerSlots(slots, props),
107
+ default: () => [
108
+ <Collapse accordion={true} v-model={activeName.value} border={false} class={"ex-datetime_collapse"}>
109
+ {{
110
+ default: () => [
111
+ <CollapseItem name={"date"} icon={"calendar-o"} title={"日期"} value={dateText.value} isLink={false}>
112
+ {{
113
+ default: () => (
114
+ <div style={{ height: "300px" }}>
115
+ <Calendar
116
+ minDate={minDate}
117
+ maxDate={maxDate}
118
+ showSubtitle={false}
119
+ showConfirm={false}
120
+ poppable={false}
121
+ showTitle={false}
122
+ lazyRender={true}
123
+ rowHeight={36}
124
+ onSelect={onSelectDate}
125
+ {...props.calendarProps}
126
+ ></Calendar>
127
+ </div>
128
+ ),
129
+ }}
130
+ </CollapseItem>,
131
+ <CollapseItem name={"time"} icon={"clock-o"} title={"时间"} isLink={false} value={timeText.value}>
132
+ {{
133
+ default: () => (
134
+ <div>
135
+ <TimePicker showToolbar={false} onChange={onTimeChange} {...props.timeProps}></TimePicker>
136
+ </div>
137
+ ),
138
+ }}
139
+ </CollapseItem>,
140
+ ],
141
+ }}
142
+ </Collapse>,
143
+ <ExButton type={"primary"} style={{ margin: "20px 0" }} onClick={onConfirm}>
144
+ 确定
145
+ </ExButton>,
146
+ ],
147
+ }}
148
+ </PickerWrapper>
149
+ )
150
+ }
151
+ },
152
+ })
@@ -0,0 +1,123 @@
1
+ import { computed, defineComponent, getCurrentInstance, reactive, watch } from "vue"
2
+ import { Field, Icon, Popover } from "vant"
3
+ import { defaultFieldProps, defaultFieldSlots } from "../utils"
4
+ import { omit, pick } from "lodash-es"
5
+
6
+ /**
7
+ * ExField 输入框
8
+ * @version 1.0.0
9
+ */
10
+ export default defineComponent({
11
+ name: "ExField",
12
+ props: {
13
+ ...defaultFieldProps,
14
+
15
+ /**
16
+ * 输入框内容
17
+ */
18
+ modelValue: { type: [String, Number, Array, Object, Boolean], default: () => "" },
19
+
20
+ /**
21
+ * 输入框类型, 支持原生 input 标签的所有
22
+ */
23
+ type: { type: String, default: "" },
24
+
25
+ /**
26
+ * 是否作为 Picker 的 Trigger
27
+ */
28
+ mask: { type: Boolean, default: false },
29
+ },
30
+ emits: ["update:modelValue", "click"],
31
+ setup(props, { slots, emit }) {
32
+ const instance = getCurrentInstance()
33
+ const displayText = instance.parent?.parent?.exposed?.displayText
34
+
35
+ const state = reactive({
36
+ value: props.mask ? displayText : props.modelValue,
37
+ showHelp: false,
38
+ })
39
+
40
+ watch(
41
+ () => props.modelValue,
42
+ () => {
43
+ state.value = props.modelValue
44
+ },
45
+ )
46
+
47
+ const isLink = computed(() => (props.mask ? true : props.isLink))
48
+ const isReadonly = computed(() => (props.mask ? true : props.readonly))
49
+
50
+ const onUpdateValue = (value) => {
51
+ emit("update:modelValue", value)
52
+ }
53
+
54
+ /****************** render **********************/
55
+
56
+ const labelElem = () => (
57
+ <div class={"ex-field__label"}>
58
+ <span>{props.label || slots.label?.()}</span>
59
+
60
+ <Popover v-model:show={state.showHelp} theme={"dark"}>
61
+ {{
62
+ default: () => <div class={"ex-field__help"}>{props.help}</div>,
63
+ reference: () =>
64
+ props.help ? (
65
+ <span class={"ex-field__help-handler"}>
66
+ <Icon name={"warning-o"}></Icon>
67
+ </span>
68
+ ) : null,
69
+ }}
70
+ </Popover>
71
+ </div>
72
+ )
73
+
74
+ const inputElem = () => {
75
+ let inputSlots = omit(slots, Object.keys(defaultFieldSlots))
76
+ let InputElem = slots.input ? slots.input() : null
77
+
78
+ if (Object.keys(inputSlots).length) {
79
+ if (InputElem.length > 1) {
80
+ console.warn("More than one root element wrapped in ExField with input slots!")
81
+ }
82
+ InputElem = InputElem[0]
83
+ return <InputElem>{inputSlots}</InputElem>
84
+ }
85
+ return InputElem
86
+ }
87
+
88
+ return () => {
89
+ let fieldSlots = pick(slots, Object.keys(defaultFieldSlots))
90
+
91
+ if (!fieldSlots.label) {
92
+ fieldSlots.label = labelElem
93
+ }
94
+
95
+ if (fieldSlots.input) {
96
+ fieldSlots.input = inputElem
97
+ }
98
+
99
+ return (
100
+ <div class={`ex-field`}>
101
+ <Field
102
+ v-model={state.value}
103
+ type={props.type}
104
+ name={props.name}
105
+ placeholder={props.placeholder}
106
+ readonly={isReadonly.value}
107
+ disabled={props.disabled}
108
+ required={props.required}
109
+ isLink={isLink.value}
110
+ rules={props.rules}
111
+ onClickInput={() => emit("click")}
112
+ onUpdate:modelValue={onUpdateValue}
113
+ {...props.fieldProps}
114
+ >
115
+ {{
116
+ ...fieldSlots,
117
+ }}
118
+ </Field>
119
+ </div>
120
+ )
121
+ }
122
+ },
123
+ })
@@ -0,0 +1,44 @@
1
+ import { defineComponent, ref, watch } from "vue"
2
+ import { defaultFieldProps } from "../utils"
3
+ import ExField from "./ExField.jsx"
4
+ import { pick } from "lodash-es"
5
+ import ExUploader from "../uploader/ExUploader.jsx"
6
+
7
+ /**
8
+ * ExFieldUploader Uploader in form
9
+ * @version 1.0.0
10
+ */
11
+ export default defineComponent({
12
+ name: "ExFieldUploader",
13
+ props: {
14
+ ...defaultFieldProps,
15
+
16
+ modelValue: { type: [Array, Object], default: () => [] },
17
+
18
+ exUploaderProps: { type: Object, default: () => ({}) },
19
+ },
20
+ emits: ["update:modelValue"],
21
+ setup(props, { emit, slots }) {
22
+ const componentValue = ref(props.modelValue)
23
+
24
+ watch(
25
+ () => props.modelValue,
26
+ () => (componentValue.value = props.modelValue),
27
+ )
28
+
29
+ const onChange = (value) => {
30
+ emit("update:modelValue", value)
31
+ }
32
+
33
+ const fieldProps = pick(props, Object.keys(defaultFieldProps))
34
+
35
+ return () => (
36
+ <ExField {...fieldProps}>
37
+ {{
38
+ ...slots,
39
+ input: () => <ExUploader v-model={componentValue.value} {...props.exUploaderProps} onChange={onChange}></ExUploader>,
40
+ }}
41
+ </ExField>
42
+ )
43
+ },
44
+ })
@@ -0,0 +1,395 @@
1
+ import { computed, defineComponent, inject, 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 { 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
+ * submit button 是否固定在底部
71
+ */
72
+ fixed: { type: Boolean, default: false },
73
+
74
+ /**
75
+ * 提交数据URL
76
+ */
77
+ submitUrl: { type: String, default: "" },
78
+
79
+ /**
80
+ * 提交按钮文字
81
+ */
82
+ submitButtonText: { type: String, default: "保存" },
83
+
84
+ /**
85
+ * 提交确认提示内容
86
+ */
87
+ submitConfirmText: { type: String, default: "" },
88
+
89
+ /**
90
+ * 是否禁用提交按钮
91
+ */
92
+ submitDisabled: { type: Boolean, default: false },
93
+
94
+ /**
95
+ * 当有分割线时,分割线的配置
96
+ */
97
+ dividerProps: { type: Object, default: () => ({}) },
98
+
99
+ /**
100
+ *
101
+ * 表单配置
102
+ *
103
+ * @typedef {Object} FormItemConfig
104
+ * @property {string} key 数据库关联名称
105
+ * @property {string} title 显示的名字
106
+ * @property {string} [type] 类型,默认是input
107
+ * @property {array|Function} [options] 组件选项
108
+ * @property {string} [placeholder] 组件里的提示
109
+ * @property {string|Function} [help] ExField 里的提示
110
+ * @property {array} [rules] 验证规则
111
+ * @property {boolean} [is-link] 是否展示右侧箭头并开启点击反馈
112
+ * @property {boolean} [readonly] 是否只读
113
+ * @property {boolean} [required] 是否必填,默认是false
114
+ * @property {boolean} [disabled] 组件不可编辑状态,默认是false
115
+ * @property {boolean|string} [break] 新起一行,默认为false,如果为 String 则以 Divider 分割
116
+ * @property {Object} [fieldProps] ExField 的原生配置
117
+ * @property {Object} [defaultProps] 组件的配置
118
+ * @property {Object} [slots] 混合 Field 和 input slot 组件的 slots 组合
119
+ * @property {*} [defaultValue] 默认值,默认是空字符串
120
+ * @property {*} [_temp] 临时数据,内部使用
121
+ */
122
+
123
+ /**
124
+ * 表单配置,[见下表](#form-表单配置)
125
+ */
126
+ form: {
127
+ type: Array,
128
+ default() {
129
+ return []
130
+ },
131
+ },
132
+
133
+ /**
134
+ * fetch 返回数据处理函数
135
+ * @return {Object} 返回处理后的数据,将用于初始化表单
136
+ */
137
+ afterFetched: { type: Function, default: null },
138
+
139
+ /**
140
+ *
141
+ * @typedef {Object} ExposedFormData
142
+ * @property {Object} formatForm Format后的表单数据
143
+ * @property {Object} originalForm 原生的表单数据
144
+ *
145
+ *
146
+ * 提交数据处理函数
147
+ * @param {ExposedFormData} data
148
+ * @return {Boolean|Object} return false会阻止提交操作,return Object会替换提交的数据
149
+ *
150
+ */
151
+ beforeSubmit: { type: Function, default: null },
152
+
153
+ /**
154
+ * 提交成功后的回调
155
+ */
156
+ afterSubmit: { type: Function, default: null },
157
+
158
+ /**
159
+ * [原生配置](https://vant-contrib.gitee.io/vant/#/zh-CN/form)
160
+ */
161
+ formProps: { type: Object, default: () => ({}) },
162
+ },
163
+ emits: ["success"],
164
+
165
+ setup(props, { expose, emit, slots }) {
166
+ const formRef = ref(null) //表单容器
167
+
168
+ const state = reactive({
169
+ temporary: {}, // 用于存放一些临时数据
170
+ submitFetcher: {
171
+ loading: false,
172
+ },
173
+ isInitializing: true, //是否正在初始化
174
+ rules: {},
175
+ submitForm: {}, //提交表单,初始化数据后会生成
176
+ submitFormBackup: {}, //初始化后的表单数据备份,用于重置表单以及脏数据判断
177
+ })
178
+
179
+ const formItems = computed(() => props.form)
180
+
181
+ const uploaderProvider = inject(EX_UPLOADER, () => ({}))
182
+ const formProvider = inject(EX_FORM, () => ({}))
183
+
184
+ watch(
185
+ () => props.data,
186
+ (newV) => {
187
+ initFormData(newV || "")
188
+ },
189
+ )
190
+
191
+ const init = () => {
192
+ state.isInitializing = false
193
+ initFormData(props.data || "")
194
+ }
195
+
196
+ onMounted(() => {
197
+ init()
198
+ })
199
+
200
+ /**
201
+ * 初始化表单数据
202
+ *
203
+ * @param {Object} formData 表单数据
204
+ */
205
+ const initFormData = (formData) => {
206
+ // 如果有 FormData, 则从中提取 FormItem 中有定义的数据,并进行初始化后存放于 extractFormData
207
+ // 无 FromData 则直接使用 FormItem 的 defaultValue
208
+ let extractFormData = {}
209
+ let existingData = formData ? cloneDeep(formData) : false
210
+ formItems.value.forEach((item) => {
211
+ extractFormData[item.key] = initItemDefaultValue(item, existingData, state.submitForm, { uploaderProvider })
212
+ })
213
+
214
+ if (existingData) {
215
+ // 将初始化后的数据覆盖原有数据,并保留不在 FormItems 中的数据
216
+ extractFormData = { ...existingData, ...extractFormData }
217
+ }
218
+
219
+ state.submitForm = extractFormData
220
+ state.submitFormBackup = cloneDeep(extractFormData)
221
+ }
222
+
223
+ //远程拿数据模式
224
+ const fetchItem = () => {
225
+ if (props.fetchUrl) {
226
+ state.isInitializing = true
227
+ useFetch()
228
+ .get(props.fetchUrl, { params: props.extraData })
229
+ .then((res) => {
230
+ state.isInitializing = false
231
+ useProcessStatusSuccess(res, () => {
232
+ if (props.afterFetched && isFunction(props.afterFetched)) {
233
+ res = props.afterFetched(res)
234
+ } else if (formProvider.afterFetched && isFunction(formProvider.afterFetched)) {
235
+ res = formProvider.afterFetched(res)
236
+ }
237
+ initFormData(res)
238
+ })
239
+ })
240
+ .finally(() => {
241
+ state.isInitializing = false
242
+ })
243
+ }
244
+ }
245
+
246
+ if (props.autoLoad) {
247
+ let auto = true
248
+ if (props.autoLoad && isObject(props.autoLoad)) {
249
+ auto = every(Object.values(pick(props.extraData, Object.keys(props.autoLoad))))
250
+ } else if (props.autoLoad && isString(props.autoLoad)) {
251
+ auto = !!props.extraData[props.autoLoad]
252
+ }
253
+
254
+ if (auto) {
255
+ fetchItem()
256
+ }
257
+ }
258
+
259
+ const onSubmit = async () => {
260
+ await formRef.value.validate()
261
+
262
+ if (props.submitConfirmText) {
263
+ try {
264
+ await showConfirmDialog({ message: props.submitConfirmText })
265
+ } catch (e) {
266
+ return
267
+ }
268
+ }
269
+
270
+ let form = useFormFormat(state.submitForm, formProvider.format || {})
271
+
272
+ if (props.beforeSubmit && isFunction(props.beforeSubmit)) {
273
+ form = await props.beforeSubmit({ formatForm: form, originalForm: state.submitForm })
274
+ if (form === false) {
275
+ return
276
+ }
277
+ }
278
+
279
+ try {
280
+ let res = await useFetch(state.submitFetcher).post(props.submitUrl, form)
281
+
282
+ //提交后再次备份表单数据,isDirty 检测即为 false
283
+ state.submitFormBackup = cloneDeep(state.submitForm)
284
+
285
+ if (props.afterSubmit) {
286
+ props.afterSubmit(res)
287
+ } else {
288
+ useProcessStatusSuccess(res, () => {
289
+ showSuccessToast(`${props.submitButtonText}成功`)
290
+ emit("success", res)
291
+ })
292
+ }
293
+ } catch (e) {
294
+ useFormFail(e)
295
+ }
296
+ }
297
+
298
+ /********** exposes **********/
299
+
300
+ /**
301
+ * 获取复制的表单数据
302
+ * @return {*}
303
+ */
304
+ const getFormStandalone = () => cloneDeep(state.submitForm)
305
+
306
+ /**
307
+ * 获取表单实时数据
308
+ * @return {*}
309
+ */
310
+ const getForm = () => state.submitForm
311
+
312
+ /**
313
+ *
314
+ * 设置表单数据
315
+ * @param {Object} fields
316
+ */
317
+ const setForm = (fields) => {
318
+ Object.keys(fields).forEach((key) => {
319
+ state.submitForm[key] = fields[key]
320
+ })
321
+ }
322
+
323
+ /**
324
+ * 判断表单是否被修改
325
+ * @return {boolean}
326
+ */
327
+ const isDirty = () => {
328
+ return !isEqual(state.submitForm, state.submitFormBackup)
329
+ }
330
+
331
+ expose({ getForm, getFormStandalone, setForm, isDirty })
332
+
333
+ /********** render **********/
334
+
335
+ const formItemElems = () =>
336
+ formItems.value.map((formItem) =>
337
+ createFormItem(formItem, state.submitForm, {
338
+ props,
339
+ slots,
340
+ }),
341
+ )
342
+
343
+ const skeletonElem = () => (
344
+ <Skeleton rows={10} loading={state.isInitializing}>
345
+ {{
346
+ default: () => formItemElems(),
347
+ }}
348
+ </Skeleton>
349
+ )
350
+
351
+ const footerElem = () => {
352
+ if (slots.footer) {
353
+ return <div class={"ex-form__footer"}>{slots.footer()}</div>
354
+ }
355
+ return null
356
+ }
357
+
358
+ const submitBtnElem = () => {
359
+ const submitBtn = (
360
+ <ExButton disabled={props.submitDisabled} type={"primary"} fetcher={state.submitFetcher} buttonProps={{ nativeType: "submit" }}>
361
+ {{
362
+ default: () => props.submitButtonText || "提交",
363
+ }}
364
+ </ExButton>
365
+ )
366
+
367
+ if (props.fixed) {
368
+ return <div class={"ex-form__submit-btn-fixed van-hairline--top"}>{submitBtn}</div>
369
+ }
370
+
371
+ return <div class={"ex-form__submit-btn"}>{submitBtn}</div>
372
+ }
373
+
374
+ return () => (
375
+ <Form
376
+ ref={formRef}
377
+ scrollToError={true}
378
+ labelWidth={props.labelWidth}
379
+ colon={props.colon}
380
+ class={`ex-form ${props.fixed ? "ex-form__fixed" : ""}`}
381
+ onSubmit={onSubmit}
382
+ >
383
+ {{
384
+ default: () => [
385
+ <CellGroup inset={props.inset} title={props.title}>
386
+ {{ default: () => skeletonElem() }}
387
+ </CellGroup>,
388
+ footerElem(),
389
+ submitBtnElem(),
390
+ ],
391
+ }}
392
+ </Form>
393
+ )
394
+ },
395
+ })