data-form.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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. onShow(() => {
  110. console.log('App Show', modelValue)
  111. })
  112. defineExpose({
  113. validate,
  114. submitDisabled,
  115. })
  116. </script>
  117. <template>
  118. <wd-config-provider :theme-vars="themeVars">
  119. <wd-form ref="form" error-type="toast" :rules="rules" :model="modelValue">
  120. <!-- <wd-cell-group border> -->
  121. <template
  122. v-for="(
  123. [prop, { type, label, labelWidth, hiddenLabel, existing, required, props, maxlength }],
  124. index
  125. ) in Object.entries(schema)"
  126. :key="index"
  127. >
  128. <div
  129. v-if="existing ?? true"
  130. class="grid mb-4"
  131. :class="[direction === 'horizontal' ? 'items-start' : '']"
  132. :style="
  133. direction === 'horizontal'
  134. ? { 'grid-template-columns': `${addUnit(labelWidth || 100)} auto` }
  135. : {}
  136. "
  137. >
  138. <label
  139. v-if="type !== 'Submit' && !hiddenLabel"
  140. class="text-sm font-normal leading-relaxed"
  141. :class="[
  142. direction === 'horizontal'
  143. ? 'text-black/60 h-10 flex items-center'
  144. : 'mb-1 text-black/40',
  145. ]"
  146. :for="prop"
  147. >
  148. <span
  149. class="text-[#ef4343] text-base font-normal font-['PingFang_SC'] leading-normal"
  150. :class="required ? 'visible' : 'invisible'"
  151. >
  152. *
  153. </span>
  154. {{ label || prop }}
  155. </label>
  156. <wd-input
  157. v-if="type === 'TextField'"
  158. v-bind="{
  159. ...(direction === 'vertical'
  160. ? verticalDefaultProps[type]
  161. : horizontalDefaultProps[type]),
  162. ...omit(props, []),
  163. }"
  164. v-model="modelValue[prop]"
  165. ></wd-input>
  166. <wd-datetime-picker
  167. v-model="modelValue[prop]"
  168. v-if="type === 'TimePick'"
  169. :minDate="315514870000"
  170. v-bind="{
  171. ...(direction === 'vertical'
  172. ? verticalDefaultProps[type]
  173. : horizontalDefaultProps[type]),
  174. cell: false,
  175. ...props,
  176. }"
  177. />
  178. <wd-textarea
  179. v-if="type === 'Textarea'"
  180. v-model="modelValue[prop]"
  181. class="h-[20px]"
  182. :maxlength="maxlength"
  183. show-word-limit
  184. v-bind="{
  185. ...(direction === 'vertical'
  186. ? verticalDefaultProps[type]
  187. : horizontalDefaultProps[type]),
  188. cell: false,
  189. ...props,
  190. }"
  191. />
  192. <wd-picker
  193. v-if="type === 'Select'"
  194. v-bind="{
  195. ...(direction === 'vertical'
  196. ? verticalDefaultProps[type]
  197. : horizontalDefaultProps[type]),
  198. cell: false,
  199. ...props,
  200. }"
  201. v-model="modelValue[prop]"
  202. ></wd-picker>
  203. <wd-radio-group
  204. v-if="type === 'Radio'"
  205. v-bind="{
  206. ...(direction === 'vertical'
  207. ? verticalDefaultProps[type]
  208. : horizontalDefaultProps[type]),
  209. ...props,
  210. cell: true,
  211. shape: 'button',
  212. }"
  213. v-model="modelValue[prop]"
  214. >
  215. <template v-for="{ label, value } of props.columns" :key="value">
  216. <wd-radio :value="value">{{ label }}</wd-radio>
  217. </template>
  218. </wd-radio-group>
  219. <wd-checkbox-group
  220. v-if="type === 'Checkbox'"
  221. v-bind="{
  222. ...(direction === 'vertical'
  223. ? verticalDefaultProps[type]
  224. : horizontalDefaultProps[type]),
  225. ...props,
  226. cell: true,
  227. shape: 'button',
  228. }"
  229. v-model="modelValue[prop]"
  230. >
  231. <template v-for="{ label, value } of props.columns" :key="value">
  232. <wd-checkbox custom-class="mr-4!" :modelValue="value">{{ label }}</wd-checkbox>
  233. </template>
  234. </wd-checkbox-group>
  235. <wd-upload
  236. v-if="type === 'ImageUploader'"
  237. v-bind="{
  238. ...(direction === 'vertical'
  239. ? verticalDefaultProps[type]
  240. : horizontalDefaultProps[type]),
  241. ...props,
  242. action,
  243. fileList:
  244. (modelValue[prop] ?? '') === ''
  245. ? []
  246. : modelValue[prop]?.split(',').map((it) => ({ url: it })),
  247. }"
  248. @change="
  249. ({ fileList }) => {
  250. const newUrls = fileList
  251. .map(({ response }) => {
  252. try {
  253. return JSON.parse(response).data
  254. } catch (e) {
  255. return null
  256. }
  257. })
  258. .filter(Boolean) // 过滤掉无效的 null 值
  259. const existingUrls = modelValue[prop]?.split(',') ?? []
  260. modelValue[prop] = [...existingUrls, ...newUrls].join(',')
  261. }
  262. "
  263. :before-remove="
  264. ({ file, fileList, resolve }) => {
  265. resolve(true)
  266. modelValue[prop] = fileList.map(({ url }) => url).join(',')
  267. }
  268. "
  269. ></wd-upload>
  270. <wd-button
  271. v-if="type === 'Submit'"
  272. v-bind="{
  273. ...(direction === 'vertical' ? verticalDefaultProps[type] : {}),
  274. ...omit(props, []),
  275. formType: 'submit',
  276. }"
  277. @click="submit"
  278. >
  279. <span v-if="type === 'Submit'">提交</span>
  280. </wd-button>
  281. </div>
  282. </template>
  283. <!-- </wd-cell-group> -->
  284. </wd-form>
  285. </wd-config-provider>
  286. </template>
  287. <style lang="less" scoped></style>