DynamicFormControl.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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 === 'sign'">
  226. <view>
  227. <SignatureField
  228. ref="itemRef"
  229. :modelValue="model"
  230. v-bind="params"
  231. @update:modelValue="(e: any) => onValueChanged(e)"
  232. />
  233. </view>
  234. </template>
  235. <template v-else-if="item.type === 'button'">
  236. <Button
  237. ref="itemRef"
  238. :disabled="disabled || readonly"
  239. v-bind="params"
  240. />
  241. </template>
  242. <template v-else-if="item.type === 'image'">
  243. <Image
  244. ref="itemRef"
  245. v-bind="params"
  246. />
  247. </template>
  248. <template v-else-if="item.type === 'alert'">
  249. <Alert
  250. ref="itemRef"
  251. v-bind="params"
  252. />
  253. </template>
  254. <ComponentRender v-else
  255. ref="itemRef"
  256. :modelValue="model"
  257. @update:modelValue="onValueChanged"
  258. :params="params"
  259. :item="item"
  260. :name="name"
  261. :isLast="isLast"
  262. :disabled="disabled"
  263. :readonly="readonly"
  264. />
  265. </slot>
  266. </Field>
  267. </template>
  268. <script setup lang="ts">
  269. import { computed, inject, onBeforeUnmount, onMounted, ref, type PropType, type Ref } from 'vue';
  270. import type { IDynamicFormItem, IDynamicFormItemCallback, IDynamicFormMessageCenter, IDynamicFormObject, IDynamicFormOptions, IDynamicFormRef } from '.';
  271. import Field from '../form/Field.vue';
  272. import Stepper from '../form/Stepper.vue';
  273. import NaPickerField, { type PickerFieldProps } from '../form/PickerField.vue';
  274. import CheckBox from '../form/CheckBox.vue';
  275. import Switch from '../form/Switch.vue';
  276. import RadioValue from './wrappers/RadioValue.vue';
  277. import PickerIdField from './wrappers/PickerIdField.vue';
  278. import CheckBoxList from './wrappers/CheckBoxList.vue';
  279. import CheckBoxToInt from './wrappers/CheckBoxToInt.vue';
  280. import type { RadioValueProps } from './wrappers/RadioValue';
  281. import type { PickerIdFieldProps } from './wrappers/PickerIdField';
  282. import PickerCityField from './wrappers/PickerCityField.vue';
  283. import PickerLonlat from './wrappers/PickerLonlat.vue';
  284. import DateTimePickerField from '../form/DateTimePickerField.vue';
  285. import TimePickerField from '../form/TimePickerField.vue';
  286. import DatePickerField from '../form/DatePickerField.vue';
  287. import UploaderField, { type UploaderFieldProps } from '../form/UploaderField.vue';
  288. import RadioIdField from './wrappers/RadioIdField.vue';
  289. import type { RadioIdFieldProps } from './wrappers/RadioIdField';
  290. import Rate, { type RateProps } from '../form/Rate.vue';
  291. import ComponentConfigs from '@/common/components/dynamicf/ComponentConfigs';
  292. import ComponentRender from '@/common/components/dynamicf/ComponentRender.vue';
  293. import type { Rules } from 'async-validator';
  294. import PickerAddressField from './wrappers/PickerAddressField.vue';
  295. import Button from '../basic/Button.vue';
  296. import Alert from '../feedback/Alert.vue';
  297. import Image from '../basic/Image.vue';
  298. import CheckBoxTreeList, { type CheckBoxTreeListProps } from './wrappers/CheckBoxTreeList.vue';
  299. import { useInjectFormContext, useInjectFormItemContext } from '../form/FormContext';
  300. import SignatureField from '../form/SignatureField.vue';
  301. export interface FormCeilProps {
  302. model: unknown,
  303. rawModel: unknown,
  304. parent?: IDynamicFormItem,
  305. parentModel: unknown,
  306. onModelUpdate: (v: unknown) => void,
  307. item: IDynamicFormItem,
  308. name: string,
  309. disabled: boolean,
  310. additionalProps: Record<string, unknown>,
  311. }
  312. const filedInternalTypes = [
  313. 'text',
  314. 'textarea',
  315. 'text-tag',
  316. 'password',
  317. ]
  318. const props = defineProps({
  319. item: {
  320. type: Object as PropType<IDynamicFormItem>,
  321. required: true,
  322. },
  323. name: {
  324. type: String,
  325. default: ''
  326. },
  327. parent: {
  328. type: Object as PropType<IDynamicFormItem>,
  329. default: null
  330. },
  331. disabled: {
  332. type: Boolean,
  333. default: false
  334. },
  335. model: {
  336. type: null
  337. },
  338. parentModel: {
  339. type: null
  340. },
  341. rawModel: {
  342. type: Object as PropType<Record<string, unknown>>,
  343. default: null
  344. },
  345. noLabel: {
  346. type: Boolean,
  347. default: false
  348. },
  349. formWrapperColDefault: {
  350. type: Object,
  351. default: null
  352. },
  353. formLabelColDefault: {
  354. type: Object,
  355. default: null
  356. },
  357. isFirst: {
  358. type: Boolean,
  359. default: false,
  360. },
  361. isLast: {
  362. type: Boolean,
  363. default: false,
  364. },
  365. });
  366. const emit = defineEmits([ 'update:model' ]);
  367. const formItemRef = ref();
  368. const finalOptions = inject<Ref<IDynamicFormOptions>>('finalOptions');
  369. const globalParams = inject<Ref<IDynamicFormObject>>('globalParams');
  370. const formRef = inject<IDynamicFormRef>('formRef');
  371. const formName = inject('formName', '');
  372. const context = useInjectFormItemContext();
  373. const formContext = useInjectFormContext();
  374. const disabled = computed(() => props.disabled || formContext?.disabled.value || context?.disabled.value);
  375. const readonly = computed(() => formContext?.readonly.value || context?.readonly.value);
  376. function evaluateCallback(val: unknown|IDynamicFormItemCallback<unknown>) {
  377. if (typeof val === 'object' && typeof (val as IDynamicFormItemCallback<unknown>).callback === 'function')
  378. return (val as IDynamicFormItemCallback<unknown>).callback(
  379. props.model,
  380. props.rawModel,
  381. props.parentModel,
  382. {
  383. item: props.item,
  384. parent: props.parent,
  385. form: formRef!,
  386. formGlobalParams: globalParams?.value || {},
  387. formRules: (finalOptions?.value.formRules ?? {}) as Record<string, Rules>,
  388. isFirst: props.isFirst,
  389. isLast: props.isLast,
  390. }
  391. );
  392. return val as unknown;
  393. }
  394. function evaluateCallbackObj(val: Record<string, unknown|IDynamicFormItemCallback<unknown>>) {
  395. const newObj = {} as Record<string, unknown>;
  396. for (const key in val) {
  397. if (Object.prototype.hasOwnProperty.call(val, key))
  398. newObj[key] = evaluateCallback(val[key]);
  399. }
  400. return newObj;
  401. }
  402. const extraDefine = computed(() => ComponentConfigs.find((item) => item.name === props.item.type))
  403. const params = computed(() => {
  404. return {
  405. ...extraDefine.value?.props || {},
  406. ...evaluateCallbackObj(props.item.additionalProps as any)
  407. } as Record<string, unknown>
  408. })
  409. const label = computed(() => evaluateCallback(props.item.label) as string)
  410. const data = computed<FormCeilProps>(() => {
  411. return {
  412. name: props.name,
  413. item: props.item,
  414. model: props.model,
  415. onModelUpdate: onValueChanged,
  416. rawModel: props.rawModel,
  417. parentModel: props.parentModel,
  418. parent: props.parent,
  419. rules: props.item.rules,
  420. disabled: props.disabled,
  421. additionalProps: props.item.additionalProps as Record<string, unknown>,
  422. }
  423. })
  424. const itemRef = ref();
  425. const messageCenter = inject<IDynamicFormMessageCenter>('messageCenter');
  426. function onValueChanged(v: any) {
  427. props.item.watch?.(props.model, v, props.rawModel, getComponentRef());
  428. emit('update:model', v);
  429. }
  430. function getComponentRef() {
  431. if (typeof itemRef.value.getItemRef === 'function')
  432. return itemRef.value.getItemRef();
  433. return itemRef.value;
  434. }
  435. onMounted(() => {
  436. props.item.mounted?.(props.model, props.rawModel, getComponentRef());
  437. messageCenter?.addWidgetRef(props.item.name, props.item.type ?? '', getComponentRef);
  438. })
  439. onBeforeUnmount(() => {
  440. props.item.beforeUnmount?.(props.model, props.rawModel, getComponentRef());
  441. messageCenter?.removeWidgetRef(props.item.name, props.item.type ?? '', getComponentRef);
  442. })
  443. defineOptions({
  444. options: {
  445. virtualHost: true,
  446. }
  447. })
  448. </script>