DynamicFormControl.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. <template>
  2. <text
  3. v-if="item.type === 'static-text' "
  4. class="form-static-text"
  5. :style="(params.style as any)"
  6. :class="(params.class as any)"
  7. >
  8. {{ params?.text ?? model ?? null }}
  9. </text>
  10. <Field
  11. ref="formItemRef"
  12. v-else-if="item.type && filedInternalTypes.includes(item.type)"
  13. :label="label"
  14. :multiline="item.type === 'textarea'"
  15. :tags="item.type.endsWith('-tag')"
  16. :name="item.name"
  17. :modelValue="model"
  18. @update:modelValue="onValueChanged"
  19. :maxlength="260"
  20. :showBottomBorder="!isLast"
  21. :required="Boolean(item.rules?.length)"
  22. :rules="item.rules"
  23. :disabled="disabled"
  24. :readonly="readonly"
  25. :type="item.type === 'password' ? 'password' : 'text'"
  26. v-bind="{
  27. ...params,
  28. ...extraDefine?.itemProps || {},
  29. ...item.formProps,
  30. }"
  31. />
  32. <Field
  33. v-else
  34. ref="formItemRef"
  35. :label="label"
  36. :name="item.name"
  37. :required="Boolean(item.rules?.length)"
  38. :showRightArrow="extraDefine?.needArrow"
  39. :showBottomBorder="!isLast"
  40. :requireChildRef="() => itemRef"
  41. :rules="item.rules"
  42. :disabled="disabled"
  43. :readonly="readonly"
  44. v-bind="{
  45. ...extraDefine?.itemProps || {},
  46. ...item.formProps,
  47. }"
  48. >
  49. <!-- <text>fullName: {{item.name}}</text> -->
  50. <slot name="insertion">
  51. <template v-if="item.type === 'custom'">
  52. <slot name="formCeil" :data="data" />
  53. </template>
  54. <template v-else-if="item.type === 'number'">
  55. <Stepper
  56. ref="itemRef"
  57. :modelValue="model"
  58. :disabled="disabled || readonly"
  59. @update:modelValue="onValueChanged"
  60. v-bind="params"
  61. />
  62. </template>
  63. <template v-else-if="item.type === 'switch'">
  64. <Switch
  65. ref="itemRef"
  66. :modelValue="model"
  67. :disabled="disabled || readonly"
  68. @update:modelValue="onValueChanged"
  69. v-bind="params"
  70. />
  71. </template>
  72. <template v-else-if="item.type === 'radio-value'">
  73. <RadioValue
  74. ref="itemRef"
  75. :modelValue="model"
  76. :disabled="disabled || readonly"
  77. @update:modelValue="onValueChanged"
  78. v-bind="(params as any as RadioValueProps)"
  79. />
  80. </template>
  81. <template v-else-if="item.type === 'radio-id'">
  82. <RadioIdField
  83. ref="itemRef"
  84. :modelValue="model"
  85. :disabled="disabled || readonly"
  86. @update:modelValue="onValueChanged"
  87. v-bind="(params as any as RadioIdFieldProps)"
  88. />
  89. </template>
  90. <template v-else-if="item.type === 'select'">
  91. <view>
  92. <NaPickerField
  93. ref="itemRef"
  94. :modelValue="model"
  95. :disabled="disabled || readonly"
  96. @update:modelValue="onValueChanged"
  97. v-bind="(params as any as PickerFieldProps)"
  98. />
  99. </view>
  100. </template>
  101. <template v-else-if="item.type === 'rate'">
  102. <Rate
  103. ref="itemRef"
  104. :modelValue="model"
  105. :disabled="disabled || readonly"
  106. @update:modelValue="onValueChanged"
  107. v-bind="(params as any as RateProps)"
  108. />
  109. </template>
  110. <template v-else-if="item.type === 'uploader'">
  111. <UploaderField
  112. ref="itemRef"
  113. :modelValue="model"
  114. :disabled="disabled"
  115. :readonly="readonly"
  116. @update:modelValue="onValueChanged"
  117. v-bind="(params as any as UploaderFieldProps)"
  118. />
  119. </template>
  120. <template v-else-if="item.type === 'select-id'">
  121. <PickerIdField
  122. ref="itemRef"
  123. :modelValue="model"
  124. :disabled="disabled"
  125. :readonly="readonly"
  126. @update:modelValue="onValueChanged"
  127. v-bind="(params as any as PickerIdFieldProps)"
  128. />
  129. </template>
  130. <template v-else-if="item.type === 'select-city'">
  131. <PickerCityField
  132. ref="itemRef"
  133. :modelValue="model"
  134. :disabled="disabled"
  135. :readonly="readonly"
  136. @update:modelValue="onValueChanged"
  137. v-bind="(params as any)"
  138. />
  139. </template>
  140. <template v-else-if="item.type === 'select-address'">
  141. <PickerAddressField
  142. ref="itemRef"
  143. :modelValue="model"
  144. :disabled="disabled"
  145. :readonly="readonly"
  146. @update:modelValue="onValueChanged"
  147. v-bind="(params as any)"
  148. />
  149. </template>
  150. <template v-else-if="item.type === 'select-lonlat'">
  151. <PickerLonlat
  152. ref="itemRef"
  153. :modelValue="model"
  154. :disabled="disabled || readonly"
  155. @update:modelValue="(v:any) => onValueChanged(v)"
  156. v-bind="params"
  157. />
  158. </template>
  159. <template v-else-if="item.type === 'check-box'">
  160. <CheckBox
  161. ref="itemRef"
  162. :modelValue="model"
  163. :disabled="disabled || readonly"
  164. @update:modelValue="onValueChanged"
  165. v-bind="params"
  166. />
  167. </template>
  168. <template v-else-if="item.type === 'check-box-list'">
  169. <CheckBoxList
  170. ref="itemRef"
  171. :modelValue="model"
  172. :disabled="disabled || readonly"
  173. @update:modelValue="onValueChanged"
  174. v-bind="(params)"
  175. />
  176. </template>
  177. <template v-else-if="item.type === 'check-box-tree'">
  178. <CheckBoxTreeList
  179. ref="itemRef"
  180. :modelValue="model"
  181. :disabled="disabled || readonly"
  182. @update:modelValue="onValueChanged"
  183. v-bind="(params as any as CheckBoxTreeListProps)"
  184. />
  185. </template>
  186. <template v-else-if="item.type === 'check-box-int'">
  187. <CheckBoxToInt
  188. ref="itemRef"
  189. :modelValue="model"
  190. :disabled="disabled || readonly"
  191. @update:modelValue="onValueChanged"
  192. v-bind="params"
  193. />
  194. </template>
  195. <template v-else-if="item.type === 'datetime'">
  196. <view>
  197. <DateTimePickerField
  198. ref="itemRef"
  199. :modelValue="model"
  200. v-bind="params"
  201. @update:modelValue="(e: any) => onValueChanged(e)"
  202. />
  203. </view>
  204. </template>
  205. <template v-else-if="item.type === 'time'">
  206. <view>
  207. <TimePickerField
  208. ref="itemRef"
  209. :modelValue="model"
  210. v-bind="params"
  211. @update:modelValue="(e: any) => onValueChanged(e)"
  212. />
  213. </view>
  214. </template>
  215. <template v-else-if="item.type === 'date'">
  216. <view>
  217. <DatePickerField
  218. ref="itemRef"
  219. :modelValue="model"
  220. v-bind="params"
  221. @update:modelValue="(e: any) => onValueChanged(e)"
  222. />
  223. </view>
  224. </template>
  225. <template v-else-if="item.type === 'button'">
  226. <Button
  227. ref="itemRef"
  228. :disabled="disabled || readonly"
  229. v-bind="params"
  230. />
  231. </template>
  232. <template v-else-if="item.type === 'image'">
  233. <Image
  234. ref="itemRef"
  235. v-bind="params"
  236. />
  237. </template>
  238. <template v-else-if="item.type === 'alert'">
  239. <Alert
  240. ref="itemRef"
  241. v-bind="params"
  242. />
  243. </template>
  244. <ComponentRender v-else
  245. ref="itemRef"
  246. :modelValue="model"
  247. @update:modelValue="onValueChanged"
  248. :params="params"
  249. :item="item"
  250. :name="name"
  251. :isLast="isLast"
  252. :disabled="disabled"
  253. :readonly="readonly"
  254. />
  255. </slot>
  256. </Field>
  257. </template>
  258. <script setup lang="ts">
  259. import { computed, inject, onBeforeUnmount, onMounted, ref, type PropType, type Ref } from 'vue';
  260. import type { IDynamicFormItem, IDynamicFormItemCallback, IDynamicFormMessageCenter, IDynamicFormObject, IDynamicFormOptions, IDynamicFormRef } from '.';
  261. import Field from '../form/Field.vue';
  262. import Stepper from '../form/Stepper.vue';
  263. import NaPickerField, { type PickerFieldProps } from '../form/PickerField.vue';
  264. import CheckBox from '../form/CheckBox.vue';
  265. import Switch from '../form/Switch.vue';
  266. import RadioValue from './wrappers/RadioValue.vue';
  267. import PickerIdField from './wrappers/PickerIdField.vue';
  268. import CheckBoxList from './wrappers/CheckBoxList.vue';
  269. import CheckBoxToInt from './wrappers/CheckBoxToInt.vue';
  270. import type { RadioValueProps } from './wrappers/RadioValue';
  271. import type { PickerIdFieldProps } from './wrappers/PickerIdField';
  272. import PickerCityField from './wrappers/PickerCityField.vue';
  273. import PickerLonlat from './wrappers/PickerLonlat.vue';
  274. import DateTimePickerField from '../form/DateTimePickerField.vue';
  275. import TimePickerField from '../form/TimePickerField.vue';
  276. import DatePickerField from '../form/DatePickerField.vue';
  277. import UploaderField, { type UploaderFieldProps } from '../form/UploaderField.vue';
  278. import RadioIdField from './wrappers/RadioIdField.vue';
  279. import type { RadioIdFieldProps } from './wrappers/RadioIdField';
  280. import Rate, { type RateProps } from '../form/Rate.vue';
  281. import ComponentConfigs from '@/common/components/dynamicf/ComponentConfigs';
  282. import ComponentRender from '@/common/components/dynamicf/ComponentRender.vue';
  283. import type { Rules } from 'async-validator';
  284. import PickerAddressField from './wrappers/PickerAddressField.vue';
  285. import Button from '../basic/Button.vue';
  286. import Alert from '../feedback/Alert.vue';
  287. import Image from '../basic/Image.vue';
  288. import CheckBoxTreeList, { type CheckBoxTreeListProps } from './wrappers/CheckBoxTreeList.vue';
  289. import { useInjectFormContext, useInjectFormItemContext } from '../form/FormContext';
  290. export interface FormCeilProps {
  291. model: unknown,
  292. rawModel: unknown,
  293. parent?: IDynamicFormItem,
  294. parentModel: unknown,
  295. onModelUpdate: (v: unknown) => void,
  296. item: IDynamicFormItem,
  297. name: string,
  298. disabled: boolean,
  299. additionalProps: Record<string, unknown>,
  300. }
  301. const filedInternalTypes = [
  302. 'text',
  303. 'textarea',
  304. 'text-tag',
  305. 'password',
  306. ]
  307. const props = defineProps({
  308. item: {
  309. type: Object as PropType<IDynamicFormItem>,
  310. required: true,
  311. },
  312. name: {
  313. type: String,
  314. default: ''
  315. },
  316. parent: {
  317. type: Object as PropType<IDynamicFormItem>,
  318. default: null
  319. },
  320. disabled: {
  321. type: Boolean,
  322. default: false
  323. },
  324. model: {
  325. type: null
  326. },
  327. parentModel: {
  328. type: null
  329. },
  330. rawModel: {
  331. type: Object as PropType<Record<string, unknown>>,
  332. default: null
  333. },
  334. noLabel: {
  335. type: Boolean,
  336. default: false
  337. },
  338. formWrapperColDefault: {
  339. type: Object,
  340. default: null
  341. },
  342. formLabelColDefault: {
  343. type: Object,
  344. default: null
  345. },
  346. isFirst: {
  347. type: Boolean,
  348. default: false,
  349. },
  350. isLast: {
  351. type: Boolean,
  352. default: false,
  353. },
  354. });
  355. const emit = defineEmits([ 'update:model' ]);
  356. const formItemRef = ref();
  357. const finalOptions = inject<Ref<IDynamicFormOptions>>('finalOptions');
  358. const globalParams = inject<Ref<IDynamicFormObject>>('globalParams');
  359. const formRef = inject<IDynamicFormRef>('formRef');
  360. const formName = inject('formName', '');
  361. const context = useInjectFormItemContext();
  362. const formContext = useInjectFormContext();
  363. const disabled = computed(() => props.disabled || formContext?.disabled.value || context?.disabled.value);
  364. const readonly = computed(() => formContext?.readonly.value || context?.readonly.value);
  365. function evaluateCallback(val: unknown|IDynamicFormItemCallback<unknown>) {
  366. if (typeof val === 'object' && typeof (val as IDynamicFormItemCallback<unknown>).callback === 'function')
  367. return (val as IDynamicFormItemCallback<unknown>).callback(
  368. props.model,
  369. props.rawModel,
  370. props.parentModel,
  371. {
  372. item: props.item,
  373. parent: props.parent,
  374. form: formRef!,
  375. formGlobalParams: globalParams?.value || {},
  376. formRules: (finalOptions?.value.formRules ?? {}) as Record<string, Rules>,
  377. isFirst: props.isFirst,
  378. isLast: props.isLast,
  379. }
  380. );
  381. return val as unknown;
  382. }
  383. function evaluateCallbackObj(val: Record<string, unknown|IDynamicFormItemCallback<unknown>>) {
  384. const newObj = {} as Record<string, unknown>;
  385. for (const key in val) {
  386. if (Object.prototype.hasOwnProperty.call(val, key))
  387. newObj[key] = evaluateCallback(val[key]);
  388. }
  389. return newObj;
  390. }
  391. const extraDefine = computed(() => ComponentConfigs.find((item) => item.name === props.item.type))
  392. const params = computed(() => {
  393. return {
  394. ...extraDefine.value?.props || {},
  395. ...evaluateCallbackObj(props.item.additionalProps as any)
  396. } as Record<string, unknown>
  397. })
  398. const label = computed(() => evaluateCallback(props.item.label) as string)
  399. const data = computed<FormCeilProps>(() => {
  400. return {
  401. name: props.name,
  402. item: props.item,
  403. model: props.model,
  404. onModelUpdate: onValueChanged,
  405. rawModel: props.rawModel,
  406. parentModel: props.parentModel,
  407. parent: props.parent,
  408. rules: props.item.rules,
  409. disabled: props.disabled,
  410. additionalProps: props.item.additionalProps as Record<string, unknown>,
  411. }
  412. })
  413. const itemRef = ref();
  414. const messageCenter = inject<IDynamicFormMessageCenter>('messageCenter');
  415. function onValueChanged(v: any) {
  416. props.item.watch?.(props.model, v, props.rawModel, getComponentRef());
  417. emit('update:model', v);
  418. }
  419. function getComponentRef() {
  420. if (typeof itemRef.value.getItemRef === 'function')
  421. return itemRef.value.getItemRef();
  422. return itemRef.value;
  423. }
  424. onMounted(() => {
  425. props.item.mounted?.(props.model, props.rawModel, getComponentRef());
  426. messageCenter?.addWidgetRef(props.item.name, props.item.type ?? '', getComponentRef);
  427. })
  428. onBeforeUnmount(() => {
  429. props.item.beforeUnmount?.(props.model, props.rawModel, getComponentRef());
  430. messageCenter?.removeWidgetRef(props.item.name, props.item.type ?? '', getComponentRef);
  431. })
  432. defineOptions({
  433. options: {
  434. virtualHost: true,
  435. }
  436. })
  437. </script>