data-form.vue 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <script setup lang="ts">
  2. import WdButton from 'wot-design-uni/components/wd-button/wd-button.vue'
  3. import WdInput from 'wot-design-uni/components/wd-input/wd-input.vue'
  4. import WdPicker from 'wot-design-uni/components/wd-picker/wd-picker.vue'
  5. import {ConfigProviderThemeVars} from 'wot-design-uni'
  6. import {DataFormProps, DataFormSchema} from './data-form'
  7. import {addUnit} from 'wot-design-uni/components/common/util'
  8. import {omit} from 'radash'
  9. import WdForm from 'wot-design-uni/components/wd-form/wd-form.vue'
  10. const types = {
  11. TextField: WdInput,
  12. Submit: WdButton,
  13. Select: WdPicker,
  14. // Radio: WdRadioGroup,
  15. }
  16. const defaultProps = {
  17. TextField: {
  18. noBorder: true,
  19. style: {},
  20. customClass: 'rounded border border-[#e1e1e1] border-solid p-1',
  21. placeholder: ' ',
  22. },
  23. Submit: {
  24. customClass: 'w-full! rounded-lg! my-4!',
  25. block: true,
  26. },
  27. }
  28. const verticalDefaultProps = {
  29. TextField: {
  30. noBorder: true,
  31. style: {},
  32. // customClass: 'rounded border border-[#e1e1e1] border-solid p-1',
  33. customClass: 'text-red! bg-[#f5f7f9]! py-2 px-4 rounded-lg',
  34. placeholder: ' ',
  35. },
  36. Textarea: {
  37. customClass: 'bg-[#f5f7f9]! rounded-lg',
  38. },
  39. Submit: {
  40. customClass: 'w-full! rounded-lg! my-4!',
  41. block: true,
  42. },
  43. }
  44. const horizontalDefaultProps = {
  45. TextField: {
  46. customClass: 'text-red! bg-[#f5f7f9]! py-2 px-4 rounded-lg',
  47. placeholderClass: 'text-black/30',
  48. noBorder: true,
  49. },
  50. Select: {
  51. customClass: 'text-black/30! bg-[#f5f7f9]! py-.75 px-4 rounded-lg!',
  52. noBorder: true,
  53. cell: false,
  54. },
  55. Radio: {
  56. customClass: 'my--4!',
  57. },
  58. Checkbox: {
  59. customClass: 'my--4!',
  60. },
  61. TimePick: {
  62. customClass: 'm-0! bg-[#f5f7f9]! py-.75 px-4 rounded-lg!',
  63. },
  64. Textarea: {
  65. customClass: 'bg-[#f5f7f9]! rounded-lg',
  66. },
  67. }
  68. const themeVars: ConfigProviderThemeVars = {
  69. cellPadding: '0',
  70. cellWrapperPadding: '10rpx',
  71. radioButtonRadius: '8rpx',
  72. radioButtonBg: 'transparent',
  73. checkboxButtonRadius: '8rpx',
  74. checkboxButtonBg: 'transparent',
  75. textareaBg: 'transparent',
  76. }
  77. const modelValue = defineModel({
  78. type: Object,
  79. default: () => ({}),
  80. })
  81. const props = withDefaults(
  82. defineProps<{
  83. schema: DataFormSchema
  84. rules?: { [key: string]: { required: boolean; message: string }[] }
  85. direction?: 'horizontal' | 'vertical'
  86. }>(),
  87. {direction: 'vertical', labelShow: true},
  88. )
  89. const emits = defineEmits(['submit'])
  90. const form = ref<InstanceType<typeof WdForm>>()
  91. const action = ref(`${import.meta.env.VITE_SERVER_BASEURL}/app-api/infra/file/upload`)
  92. const submitDisabled = computed(() => {
  93. // console.log(Object.values(modelValue.value).some((it) => !it))
  94. return Object.values(
  95. omit(
  96. modelValue.value,
  97. Object.entries(props.schema)
  98. .filter(([_key, {required}]) => !required)
  99. .map(([key]) => key),
  100. ),
  101. ).some((it) => !it)
  102. })
  103. const submit = () => {
  104. emits('submit', modelValue)
  105. }
  106. const validate = async (): Promise<{ valid: boolean; errors: any[] }> => {
  107. return await form.value!.validate()
  108. }
  109. defineExpose({
  110. validate,
  111. submitDisabled,
  112. })
  113. </script>
  114. <template>
  115. <wd-config-provider :theme-vars="themeVars">
  116. <wd-form ref="form" error-type="toast" :rules="rules" :model="modelValue">
  117. <!-- <wd-cell-group border> -->
  118. <template
  119. v-for="(
  120. [prop, { type, label, labelWidth, hiddenLabel, existing, required, props, maxlength }],
  121. index
  122. ) in Object.entries(schema)"
  123. :key="index"
  124. >
  125. <div
  126. v-if="existing ?? true"
  127. class="grid mb-4"
  128. :class="[direction === 'horizontal' ? 'items-start' : '']"
  129. :style="
  130. direction === 'horizontal'
  131. ? { 'grid-template-columns': `${addUnit(labelWidth || 100)} auto` }
  132. : {}
  133. "
  134. >
  135. <label
  136. v-if="type !== 'Submit' && !hiddenLabel"
  137. class="text-sm font-normal leading-relaxed"
  138. :class="[
  139. direction === 'horizontal'
  140. ? 'text-black/60 h-10 flex items-center'
  141. : 'mb-1 text-black/40',
  142. ]"
  143. :for="prop"
  144. >
  145. <span
  146. class="text-[#ef4343] text-base font-normal font-['PingFang_SC'] leading-normal"
  147. :class="required ? 'visible' : 'invisible'"
  148. >
  149. *
  150. </span>
  151. {{ label || prop }}
  152. </label>
  153. <wd-input
  154. v-if="type === 'TextField'"
  155. v-bind="{
  156. ...(direction === 'vertical'
  157. ? verticalDefaultProps[type]
  158. : horizontalDefaultProps[type]),
  159. ...omit(props, []),
  160. }"
  161. v-model="modelValue[prop]"
  162. ></wd-input>
  163. <wd-datetime-picker
  164. v-model="modelValue[prop]"
  165. v-if="type === 'TimePick'"
  166. v-bind="{
  167. ...(direction === 'vertical'
  168. ? verticalDefaultProps[type]
  169. : horizontalDefaultProps[type]),
  170. cell: false,
  171. ...props,
  172. }"
  173. />
  174. <wd-textarea
  175. v-if="type === 'Textarea'"
  176. v-model="modelValue[prop]"
  177. class="h-[20px]"
  178. :maxlength="maxlength"
  179. show-word-limit
  180. v-bind="{
  181. ...(direction === 'vertical'
  182. ? verticalDefaultProps[type]
  183. : horizontalDefaultProps[type]),
  184. cell: false,
  185. ...props,
  186. }"
  187. />
  188. <wd-picker
  189. v-if="type === 'Select'"
  190. v-bind="{
  191. ...(direction === 'vertical'
  192. ? verticalDefaultProps[type]
  193. : horizontalDefaultProps[type]),
  194. cell: false,
  195. ...props,
  196. }"
  197. v-model="modelValue[prop]"
  198. ></wd-picker>
  199. <wd-radio-group
  200. v-if="type === 'Radio'"
  201. v-bind="{
  202. ...(direction === 'vertical'
  203. ? verticalDefaultProps[type]
  204. : horizontalDefaultProps[type]),
  205. ...props,
  206. cell: true,
  207. shape: 'button',
  208. }"
  209. v-model="modelValue[prop]"
  210. >
  211. <template v-for="{ label, value } of props.columns" :key="value">
  212. <wd-radio :value="value">{{ label }}</wd-radio>
  213. </template>
  214. </wd-radio-group>
  215. <wd-checkbox-group
  216. v-if="type === 'Checkbox'"
  217. v-bind="{
  218. ...(direction === 'vertical'
  219. ? verticalDefaultProps[type]
  220. : horizontalDefaultProps[type]),
  221. ...props,
  222. cell: true,
  223. shape: 'button',
  224. }"
  225. v-model="modelValue[prop]"
  226. >
  227. <template v-for="{ label, value } of props.columns" :key="value">
  228. <wd-checkbox custom-class="mr-4!" :modelValue="value">{{ label }}</wd-checkbox>
  229. </template>
  230. </wd-checkbox-group>
  231. <wd-upload
  232. v-if="type === 'ImageUploader'"
  233. v-bind="{
  234. ...(direction === 'vertical'
  235. ? verticalDefaultProps[type]
  236. : horizontalDefaultProps[type]),
  237. ...props,
  238. action,
  239. fileList:
  240. (modelValue[prop] ?? '') === ''
  241. ? []
  242. : modelValue[prop]?.split(',').map((it) => ({ url: it })),
  243. }"
  244. @change="({ fileList }) => {
  245. const newUrls = fileList.map(({ response }) => {
  246. try {
  247. return JSON.parse(response).data;
  248. } catch (e) {
  249. return null;
  250. }
  251. }).filter(Boolean); // 过滤掉无效的 null 值
  252. const existingUrls = modelValue[prop]?.split(',') ?? [];
  253. modelValue[prop] = [...existingUrls, ...newUrls].join(',');
  254. }"
  255. ></wd-upload>
  256. <wd-button
  257. v-if="type === 'Submit'"
  258. v-bind="{
  259. ...(direction === 'vertical' ? verticalDefaultProps[type] : {}),
  260. ...omit(props, []),
  261. formType: 'submit',
  262. }"
  263. @click="submit"
  264. >
  265. <span v-if="type === 'Submit'">提交</span>
  266. </wd-button>
  267. </div>
  268. </template>
  269. <!-- </wd-cell-group> -->
  270. </wd-form>
  271. </wd-config-provider>
  272. </template>
  273. <style lang="less" scoped></style>