DynamicForm.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <template>
  2. <Form
  3. ref="formEditor"
  4. :name="name"
  5. v-bind="finalOptions.formAdditionaProps"
  6. :model="model || {}"
  7. :rules="finalOptions.formRules"
  8. :disabled="finalOptions.disabled"
  9. :readonly="finalOptions.readonly"
  10. @submit="(e) => emit('submit', e)"
  11. @submitFailed="() => emit('finishFailed')"
  12. >
  13. <!--空显示-->
  14. <slot name="empty" v-if="options.formItems?.length == 0 || !model">
  15. <div v-if="options.emptyText" class="dynamic-form-item-empty">{{ options.emptyText }}</div>
  16. </slot>
  17. <Alert
  18. v-else-if="(typeof model !== 'object' && !options.suppressRootError)"
  19. type="warning"
  20. message="DynamicForm: model is not a object!"
  21. :description="`At form ${name || 'unnamed'} Root`"
  22. />
  23. <template v-else>
  24. <template v-for="(item, index) in options.formItems" :key="item.name">
  25. <template v-if="item.type === 'insertion'">
  26. <slot name="insertion" :data="item" />
  27. </template>
  28. <!--表单条目渲染核心-->
  29. <DynamicFormItemContainer
  30. v-else
  31. :item="item"
  32. :name="item.name"
  33. :rawModel="finalModel"
  34. :model="finalModel[item.name]"
  35. :parentModel="finalModel"
  36. :isFirst="index === 0"
  37. :isLast="index === options.formItems.length - 1"
  38. @update:model="(v: unknown) => finalModel[item.name] = v"
  39. :disabled="options.disabled"
  40. >
  41. <template #arrayButtonAdd="props">
  42. <slot name="formArrayButtonAdd" :onClick="props.onClick" />
  43. </template>
  44. <template #arrayButtons="props">
  45. <slot name="formArrayButtons"
  46. :onDeleteClick="props.onDeleteClick"
  47. :onUpClick="props.onUpClick"
  48. :onDownClick="props.onDownClick"
  49. />
  50. </template>
  51. <template #formCeil="values">
  52. <slot name="formCeil" :data="values.data" />
  53. </template>
  54. </DynamicFormItemContainer>
  55. </template>
  56. <slot name="endButton" />
  57. </template>
  58. </Form>
  59. </template>
  60. <script setup lang="ts">
  61. import { computed, onMounted, provide, ref, toRef, toRefs, type PropType } from 'vue';
  62. import Form, { type FormInstance } from '../form/Form.vue';
  63. import {
  64. type IDynamicFormOptions, type IDynamicFormItem, type IDynamicFormRef,
  65. type IDynamicFormObject, defaultDynamicFormOptions,
  66. type IDynamicFormMessageCenter,
  67. type IDynamicFormMessageCenterCallback,
  68. MESSAGE_RELOAD,
  69. type IDynamicFormWidgetRef
  70. } from '.';
  71. import DynamicFormItemContainer from './nest/DynamicFormItemContainer.vue';
  72. import Alert from '@/components/feedback/Alert.vue';
  73. const props = defineProps({
  74. /**
  75. * 动态表单选项
  76. */
  77. options: {
  78. type: Object as PropType<IDynamicFormOptions>,
  79. default: null
  80. },
  81. /**
  82. * 表单数据模型
  83. */
  84. model: {
  85. type: Object,
  86. default: null
  87. },
  88. /**
  89. * 表单名称, 设置到表单组件上。
  90. */
  91. name: {
  92. type: String,
  93. default: ''
  94. },
  95. /**
  96. * 全局参数。用于向每个表单项的参数中添加额外的参数,可以在回调中的 formGlobalParams 中访问。
  97. */
  98. globalParams: {
  99. type: Object as PropType<IDynamicFormObject>,
  100. default: null
  101. },
  102. });
  103. const emit = defineEmits(['ready', 'submit', 'finish', 'finishFailed']);
  104. const { options, model, name } = toRefs(props);
  105. const finalOptions = computed<IDynamicFormOptions>(() => ({
  106. ...defaultDynamicFormOptions,
  107. ...options.value,
  108. }));
  109. const finalModel = computed(() => {
  110. if (typeof props.model !== 'object')
  111. return {};
  112. return props.model;
  113. });
  114. provide('rawModel', model);
  115. provide('globalParams', toRef(props, 'globalParams'));
  116. provide('finalOptions', finalOptions);
  117. const formEditor = ref<FormInstance>();
  118. const widgetsRefMap = new Map<string, IDynamicFormWidgetRef>();
  119. const widgetsRefTypesMap = new Map<string, IDynamicFormWidgetRef[]>();
  120. const messageCenterMap = new Map<string, IDynamicFormMessageCenterCallback>();
  121. provide('messageCenter', {
  122. addInstance: (name: string, fn: IDynamicFormMessageCenterCallback) => messageCenterMap.set(name, fn),
  123. removeInstance: (name: string) => messageCenterMap.delete(name),
  124. addWidgetRef: (name: string, type: string, ref: IDynamicFormWidgetRef) => {
  125. const refs = widgetsRefTypesMap.get(name) || [];
  126. refs.push(ref);
  127. widgetsRefTypesMap.set(type, refs);
  128. },
  129. removeWidgetRef: (name: string, type: string, ref: IDynamicFormWidgetRef) => {
  130. const refs = widgetsRefTypesMap.get(name) || [];
  131. widgetsRefTypesMap.set(type, refs.filter((r) => r !== ref));
  132. },
  133. } as IDynamicFormMessageCenter);
  134. //获取组件引用
  135. function getFormItemControlRef(key: string) {
  136. return widgetsRefMap.get(key)?.();
  137. }
  138. //获取组件引用组
  139. function getFormItemControlRefsByType(type: string) {
  140. return (widgetsRefTypesMap.get(type) || []).map((ref) => ref());
  141. }
  142. //通过路径访问
  143. function accessFormModel(keyName: string, isSet: boolean, setValue: unknown) : unknown {
  144. const keys = keyName.split('.');
  145. let ret : unknown = undefined;
  146. let obj = model.value as Record<string, unknown>;
  147. let keyIndex = 0;
  148. let key = keys[keyIndex];
  149. while (obj) {
  150. const leftIndex = key.indexOf('[');
  151. if (leftIndex > 0 && key.endsWith(']')) {
  152. const arr = obj[key.substring(0, leftIndex)] as Record<string, unknown>[];
  153. const index = parseInt(key.substring(leftIndex + 1, key.length - 1))
  154. obj = arr[index];
  155. if (keyIndex >= keys.length - 1) {
  156. ret = obj;
  157. if (isSet) arr[index] = setValue as Record<string, unknown>;
  158. }
  159. } else {
  160. const newObj = obj[key] as Record<string, unknown>;
  161. if (keyIndex >= keys.length - 1) {
  162. ret = newObj;
  163. if (isSet)
  164. obj[key] = setValue as Record<string, unknown>;
  165. }
  166. obj = newObj;
  167. }
  168. if (keyIndex < keys.length - 1)
  169. key = keys[++keyIndex];
  170. else
  171. break;
  172. }
  173. return ret;
  174. }
  175. //发送通知消息
  176. function dispatchMessage(messageName: string, data?: unknown, receiveFilter?: RegExp) {
  177. for (const iterator of messageCenterMap) {
  178. if (!receiveFilter || receiveFilter.test(iterator[0]))
  179. iterator[1](messageName, data);
  180. }
  181. }
  182. //发送重新加载消息
  183. function dispatchReload() {
  184. dispatchMessage(MESSAGE_RELOAD);
  185. }
  186. //初始化默认值到模型
  187. function initDefaultValuesToModel() {
  188. function loopItems(key: string, parentKey: string, type: string, items: IDynamicFormItem[]) {
  189. let i = 0;
  190. for (const item of items) {
  191. let currentKey = key;
  192. switch (type) {
  193. case 'flat-simple':
  194. case 'flat-group':
  195. currentKey = (parentKey ? parentKey + '.' : '') + item.name;
  196. break;
  197. default:
  198. case 'object':
  199. case 'object-group':
  200. currentKey = (key ? key + '.' : '') + item.name;
  201. break
  202. case 'array':
  203. currentKey = (parentKey ? parentKey + '.' : '') + `[${i}]`;
  204. break;
  205. case 'array-object':
  206. currentKey = (parentKey ? parentKey + '.' : '') + `[${i}]` + item.name;
  207. break;
  208. }
  209. if (item.children) {
  210. loopItems(currentKey, key, item.type || '', item.children);
  211. }
  212. //console.log(currentKey);
  213. if (item.defaultValue !== undefined) {
  214. const oldValue = accessFormModel(currentKey, false, undefined);
  215. if (oldValue !== undefined && oldValue !== null)
  216. continue;
  217. accessFormModel(currentKey, true, typeof item.defaultValue === 'function' ? item.defaultValue() : item.defaultValue);
  218. }
  219. i++;
  220. }
  221. }
  222. loopItems('', '', '', finalOptions.value.formItems);
  223. }
  224. //获取当前表单中可见的所有字段名
  225. function getVisibleFormNames() {
  226. return Array.from(messageCenterMap.keys());
  227. }
  228. onMounted(() => {
  229. setTimeout(() => {
  230. emit('ready');
  231. }, 400);
  232. });
  233. const formRef : IDynamicFormRef = {
  234. initDefaultValuesToModel,
  235. getVisibleFormNames,
  236. getFormRef() {
  237. if (!formEditor.value)
  238. throw new Error('Form instance is not create.');
  239. return formEditor.value
  240. },
  241. getFormItemControlRefsByType: getFormItemControlRefsByType as any,
  242. getFormItemControlRef: getFormItemControlRef as any,
  243. submit() { return this.getFormRef().validate(); },
  244. validate() { return this.getFormRef().validate(); },
  245. setValueByPath: (path: string|string[], value: unknown) => {
  246. if (Array.isArray(path))
  247. path = path.join('.');
  248. return accessFormModel(path, true, value);
  249. },
  250. getValueByPath: (path: string|string[]) => {
  251. if (Array.isArray(path))
  252. path = path.join('.');
  253. return accessFormModel(path, false, undefined);
  254. },
  255. dispatchMessage,
  256. dispatchReload,
  257. };
  258. provide('formRef', formRef);
  259. provide('formName', name.value || 'unnamed');
  260. defineExpose(formRef);
  261. </script>