DynamicForm.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. <DynamicFormRoot
  14. :options="finalOptions"
  15. :model="model"
  16. :name="name"
  17. />
  18. </Form>
  19. </template>
  20. <script setup lang="ts">
  21. import { computed, onMounted, provide, ref, shallowRef, toRef, toRefs, type PropType } from 'vue';
  22. import Form, { type FormInstance } from '../form/Form.vue';
  23. import {
  24. type IDynamicFormOptions, type IDynamicFormItem, type IDynamicFormRef,
  25. type IDynamicFormObject, defaultDynamicFormOptions,
  26. type IDynamicFormMessageCenter,
  27. type IDynamicFormMessageCenterCallback,
  28. MESSAGE_RELOAD,
  29. type IDynamicFormWidgetRef
  30. } from '.';
  31. import DynamicFormRoot from './nest/DynamicFormRoot.vue';
  32. const props = defineProps({
  33. /**
  34. * 动态表单选项
  35. */
  36. options: {
  37. type: Object as PropType<IDynamicFormOptions>,
  38. default: null
  39. },
  40. /**
  41. * 表单数据模型
  42. */
  43. model: {
  44. type: Object,
  45. default: null
  46. },
  47. /**
  48. * 表单名称, 设置到表单组件上。
  49. */
  50. name: {
  51. type: String,
  52. default: ''
  53. },
  54. /**
  55. * 全局参数。用于向每个表单项的参数中添加额外的参数,可以在回调中的 formGlobalParams 中访问。
  56. */
  57. globalParams: {
  58. type: Object as PropType<IDynamicFormObject>,
  59. default: null
  60. },
  61. });
  62. const emit = defineEmits(['ready', 'submit', 'finish', 'finishFailed']);
  63. const { options, model, name } = toRefs(props);
  64. const finalOptions = computed<IDynamicFormOptions>(() => ({
  65. ...defaultDynamicFormOptions,
  66. ...options.value,
  67. }));
  68. const formRules = computed(() => finalOptions.value.formRules);
  69. const nestObjectMargin = computed(() => finalOptions.value.nestObjectMargin);
  70. const suppressEmptyError = computed(() => finalOptions.value.suppressEmptyError);
  71. provide('rawModel', model);
  72. provide('globalParams', toRef(props, 'globalParams'));
  73. provide('formRules', formRules);
  74. provide('nestObjectMargin', nestObjectMargin);
  75. provide('suppressEmptyError', suppressEmptyError);
  76. const formEditor = ref<FormInstance>();
  77. const widgetsRefMap = new Map<string, IDynamicFormWidgetRef>();
  78. const widgetsRefTypesMap = new Map<string, IDynamicFormWidgetRef[]>();
  79. const messageCenterMap = new Map<string, IDynamicFormMessageCenterCallback>();
  80. provide('messageCenter', {
  81. addInstance: (name: string, fn: IDynamicFormMessageCenterCallback) => messageCenterMap.set(name, fn),
  82. removeInstance: (name: string) => messageCenterMap.delete(name),
  83. addWidgetRef: (name: string, type: string, ref: IDynamicFormWidgetRef) => {
  84. const refs = widgetsRefTypesMap.get(name) || [];
  85. refs.push(ref);
  86. widgetsRefTypesMap.set(type, refs);
  87. },
  88. removeWidgetRef: (name: string, type: string, ref: IDynamicFormWidgetRef) => {
  89. const refs = widgetsRefTypesMap.get(name) || [];
  90. widgetsRefTypesMap.set(type, refs.filter((r) => r !== ref));
  91. },
  92. } as IDynamicFormMessageCenter);
  93. //获取组件引用
  94. function getFormItemControlRef(key: string) {
  95. return widgetsRefMap.get(key)?.();
  96. }
  97. //获取组件引用组
  98. function getFormItemControlRefsByType(type: string) {
  99. return (widgetsRefTypesMap.get(type) || []).map((ref) => ref());
  100. }
  101. //通过路径访问
  102. function accessFormModel(keyName: string, isSet: boolean, setValue: unknown) : unknown {
  103. const keys = keyName.split('.');
  104. let ret : unknown = undefined;
  105. let obj = model.value as Record<string, unknown>;
  106. let keyIndex = 0;
  107. let key = keys[keyIndex];
  108. while (obj) {
  109. const leftIndex = key.indexOf('[');
  110. if (leftIndex > 0 && key.endsWith(']')) {
  111. const arr = obj[key.substring(0, leftIndex)] as Record<string, unknown>[];
  112. const index = parseInt(key.substring(leftIndex + 1, key.length - 1))
  113. obj = arr[index];
  114. if (keyIndex >= keys.length - 1) {
  115. ret = obj;
  116. if (isSet) arr[index] = setValue as Record<string, unknown>;
  117. }
  118. } else {
  119. const newObj = obj[key] as Record<string, unknown>;
  120. if (keyIndex >= keys.length - 1) {
  121. ret = newObj;
  122. if (isSet)
  123. obj[key] = setValue as Record<string, unknown>;
  124. }
  125. obj = newObj;
  126. }
  127. if (keyIndex < keys.length - 1)
  128. key = keys[++keyIndex];
  129. else
  130. break;
  131. }
  132. return ret;
  133. }
  134. //发送通知消息
  135. function dispatchMessage(messageName: string, data?: unknown, receiveFilter?: RegExp) {
  136. for (const iterator of messageCenterMap) {
  137. if (!receiveFilter || receiveFilter.test(iterator[0]))
  138. iterator[1](messageName, data);
  139. }
  140. }
  141. //发送重新加载消息
  142. function dispatchReload() {
  143. dispatchMessage(MESSAGE_RELOAD);
  144. }
  145. /**
  146. * 初始化默认值到模型
  147. *
  148. * currentKey 递归规则
  149. * * flat-simple/flat-group 忽略本级key继承父级:
  150. * 例如: 以下结构应推断路径为 user.district
  151. object (name: "user")
  152. └─ flat-simple (name: "group1")
  153. └─ flat-simple (name: "group2")
  154. └─ dropdown1 (name: "district", defaultValue: "Beijing")
  155. * * object/object-group 父级key.子级key
  156. * 例如: 以下结构应推断路径为 user.info.district
  157. object (name: "user")
  158. └─ object (name: "info")
  159. └─ dropdown1 (name: "district", defaultValue: "Beijing")
  160. * * array 父级key.[子级索引]
  161. * 例如: 以下结构应推断路径为 user.activityList[${index}]
  162. object (name: "user")
  163. └─ array (name: "activityList")
  164. └─ text (name: "self", defaultValue: "Hello")
  165. * * array-object 父级key.[子级索引]子级key
  166. * 例如: 以下结构应推断路径为 user.activityList[${index}].name
  167. object (name: "user")
  168. └─ array-object (name: "activityList")
  169. └─ text (name: "name", defaultValue: "Hello")
  170. */
  171. function initDefaultValuesToModel() {
  172. function loopItems(key: string, parentKey: string, type: string, items: IDynamicFormItem[]) {
  173. let i = 0;
  174. for (const item of items) {
  175. let currentKey = key;
  176. switch (type) {
  177. case 'flat-simple':
  178. case 'flat-group':
  179. currentKey = (parentKey ? parentKey + '.' : '') + item.name;
  180. break;
  181. default:
  182. case 'object':
  183. case 'object-group':
  184. currentKey = (key ? key + '.' : '') + item.name;
  185. break
  186. case 'array':
  187. currentKey = (parentKey ? parentKey : '') + `[${i}]`;
  188. break;
  189. case 'array-object':
  190. currentKey = (parentKey ? parentKey : '') + `[${i}].` + item.name;
  191. break;
  192. }
  193. if (item.children) {
  194. const childParentKey = (item.type === 'flat-simple' || item.type === 'flat-group') ? parentKey : currentKey;
  195. loopItems(currentKey, childParentKey, item.type || '', item.children);
  196. }
  197. if (item.defaultValue !== undefined) {
  198. const oldValue = accessFormModel(currentKey, false, undefined);
  199. if (oldValue !== undefined && oldValue !== null)
  200. continue;
  201. accessFormModel(currentKey, true, typeof item.defaultValue === 'function' ? item.defaultValue() : item.defaultValue);
  202. }
  203. i++;
  204. }
  205. }
  206. loopItems('', '', '', finalOptions.value.formItems);
  207. }
  208. //获取当前表单中可见的所有字段名
  209. function getVisibleFormNames() {
  210. return Array.from(messageCenterMap.keys());
  211. }
  212. onMounted(() => {
  213. setTimeout(() => {
  214. emit('ready');
  215. }, 400);
  216. });
  217. const formRef : IDynamicFormRef = {
  218. initDefaultValuesToModel,
  219. getVisibleFormNames,
  220. getFormRef() {
  221. if (!formEditor.value)
  222. throw new Error('Form instance is not create.');
  223. return formEditor.value
  224. },
  225. getGlobalParams() {
  226. return props.globalParams;
  227. },
  228. getFormItemControlRefsByType: getFormItemControlRefsByType as any,
  229. getFormItemControlRef: getFormItemControlRef as any,
  230. submit() { return this.getFormRef().validate(); },
  231. validate() { return this.getFormRef().validate(); },
  232. setValueByPath: (path: string|string[], value: unknown) => {
  233. if (Array.isArray(path))
  234. path = path.join('.');
  235. return accessFormModel(path, true, value);
  236. },
  237. getValueByPath: (path: string|string[]) => {
  238. if (Array.isArray(path))
  239. path = path.join('.');
  240. return accessFormModel(path, false, undefined);
  241. },
  242. dispatchMessage,
  243. dispatchReload,
  244. emitMessage: (m, ...p) => emit(m as any, ...p),
  245. };
  246. provide('formRef', formRef);
  247. provide('formName', name.value || 'unnamed');
  248. defineExpose(formRef);
  249. </script>