DynamicForm.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. import { forwardRef, memo, useEffect, useImperativeHandle } from 'react'
  2. import useUserStore from '@/store/userStore'
  3. import { dictMapping, useNoAuthDict } from '@/hooks/useDict'
  4. import { DatePicker, Form, Input, InputNumber, Select, Upload } from 'antd'
  5. import { PlusOutlined } from '@ant-design/icons'
  6. const { RangePicker } = DatePicker
  7. export interface DynamicFormRef {
  8. onSubmit: () => void
  9. }
  10. interface DynamicFormProps {
  11. formItems?: API.TmplItem[]
  12. formData?: Record<string, any>
  13. formRule?: any
  14. disabled?: boolean
  15. setFormData: (data: Record<string, any>) => void
  16. }
  17. const DynamicForm = memo(
  18. forwardRef<DynamicFormRef, DynamicFormProps>(
  19. ({ formItems = [], formData = {}, formRule = {}, disabled = false, setFormData }, ref) => {
  20. useImperativeHandle(ref, () => {
  21. return {
  22. onSubmit
  23. }
  24. })
  25. console.log(formRule)
  26. const { dictArray: inputUnitTypeOptions, dictRequestFn: getInputUnitTypeOptions } =
  27. useNoAuthDict('input_unit_type')
  28. useEffect(() => {
  29. getInputUnitTypeOptions()
  30. }, [])
  31. useEffect(() => {
  32. console.log(inputUnitTypeOptions)
  33. }, [inputUnitTypeOptions])
  34. const [form] = Form.useForm()
  35. const onSubmit = () => {
  36. form.submit()
  37. }
  38. const onFinish = (values: any) => {
  39. console.log('Finish:', values)
  40. }
  41. const onFinishFailed = (errorInfo: any) => {
  42. console.log('Failed:', errorInfo)
  43. }
  44. console.log('formData', formData)
  45. console.log(formItems)
  46. console.log(setFormData)
  47. // 表单项生成器
  48. const FieldWrapper = (props: { children: React.ReactNode }) => {
  49. return (
  50. <div className='px-[20px] py-[16px] mb-[16px] rounded-[10px] bg-[#ecf5ff]'>
  51. {props.children}
  52. </div>
  53. )
  54. }
  55. const renderField = (field: API.TmplItem) => {
  56. const options = field.itemStruct
  57. const { type, label, desc, placeholder, visible, expand, widgetId } = options!
  58. if (!visible) return null // 条件渲染
  59. const labelContent = (
  60. <div>
  61. <div>{label.text}</div>
  62. {desc && (
  63. <div className='text-text-secondary mt-[4px]' style={{ fontSize: '12px' }}>
  64. {desc}
  65. </div>
  66. )}
  67. </div>
  68. )
  69. switch (type) {
  70. case 'INPUT': {
  71. const expandVal = expand as API.FD_COM['INPUT']
  72. return (
  73. <FieldWrapper key={widgetId}>
  74. <Form.Item
  75. name={widgetId}
  76. label={labelContent}
  77. rules={formRule[widgetId!]}
  78. hasFeedback
  79. style={{ marginBottom: 0 }}>
  80. <Input
  81. placeholder={placeholder}
  82. prefix={expandVal.prefix}
  83. suffix={expandVal.suffix}
  84. showCount
  85. allowClear
  86. />
  87. </Form.Item>
  88. </FieldWrapper>
  89. )
  90. }
  91. case 'NUMBER_INPUT': {
  92. const expandVal = expand as API.FD_COM['NUMBER_INPUT']
  93. const min = typeof expandVal.min === 'number' ? expandVal.min : undefined
  94. const max = typeof expandVal.max === 'number' ? expandVal.max : undefined
  95. const decimalDigits = expandVal.decimalPlacesNumber || 0
  96. return (
  97. <FieldWrapper key={widgetId}>
  98. <Form.Item name={widgetId} label={labelContent} style={{ marginBottom: 0 }}>
  99. <InputNumber
  100. className='!w-full'
  101. placeholder={`${min !== undefined ? '最小值:' + min.toFixed(decimalDigits) + ';' : ''}${
  102. max !== undefined ? '最大值:' + max.toFixed(decimalDigits) + ';' : ''
  103. }${'小数位数:' + decimalDigits + ';'}`}
  104. maxLength={parseInt(expandVal.maxChars)}
  105. prefix={expandVal.prefix}
  106. suffix={
  107. (expandVal.suffix ||
  108. (expandVal.featureType === 'NUMBER' && expandVal.unit)) &&
  109. (expandVal.featureType === 'NUMBER' && expandVal.unit !== 'NONE'
  110. ? expandVal.unit
  111. : expandVal.suffix)
  112. }
  113. min={min}
  114. max={max}
  115. precision={decimalDigits}
  116. />
  117. </Form.Item>
  118. </FieldWrapper>
  119. )
  120. }
  121. case 'AMOUNT_INPUT': {
  122. const expandVal = expand as API.FD_COM['NUMBER_INPUT']
  123. const min = typeof expandVal.min === 'number' ? expandVal.min : undefined
  124. const max = typeof expandVal.max === 'number' ? expandVal.max : undefined
  125. const decimalDigits = expandVal.decimalPlacesNumber || 0
  126. return (
  127. <FieldWrapper key={widgetId}>
  128. <Form.Item name={widgetId} label={labelContent} style={{ marginBottom: 0 }}>
  129. <InputNumber
  130. className='!w-full'
  131. placeholder={`${min !== undefined ? '最小值:' + min.toFixed(decimalDigits) + ';' : ''}${
  132. max !== undefined ? '最大值:' + max.toFixed(decimalDigits) + ';' : ''
  133. }${'小数位数:' + decimalDigits + ';'}`}
  134. maxLength={parseInt(expandVal.maxChars)}
  135. prefix={expandVal.prefix}
  136. suffix={
  137. (expandVal.suffix || expandVal.unit) &&
  138. (expandVal.unit
  139. ? dictMapping({ unit: expandVal.unit }, 'unit', inputUnitTypeOptions)
  140. : expandVal.suffix)
  141. }
  142. min={min}
  143. max={max}
  144. precision={decimalDigits}
  145. />
  146. </Form.Item>
  147. </FieldWrapper>
  148. )
  149. }
  150. case 'SELECT': {
  151. const expandVal = expand as API.FD_COM['SELECT']
  152. return (
  153. <FieldWrapper key={widgetId}>
  154. <Form.Item name={widgetId} label={labelContent} style={{ marginBottom: 0 }}>
  155. <Select
  156. mode={expandVal.multiple ? 'multiple' : undefined}
  157. placeholder={placeholder}
  158. options={expandVal.userInput}
  159. allowClear
  160. />
  161. </Form.Item>
  162. </FieldWrapper>
  163. )
  164. }
  165. case 'DATETIME_PICKER': {
  166. const expandVal = expand as API.FD_COM['DATETIME_PICKER']
  167. const datePickerType = expandVal.dateType.toLowerCase()
  168. const datePickerTypeMap = {
  169. year: { picker: 'year', showTime: false, dateFormat: 'YYYY' },
  170. month: { picker: 'month', showTime: false, dateFormat: 'YYYY-MM' },
  171. date: { picker: undefined, showTime: false, dateFormat: 'YYYY-MM-DD' },
  172. week: { picker: 'week', showTime: false, dateFormat: 'YYYY-WW' },
  173. datetime: { picker: undefined, showTime: true, dateFormat: 'YYYY-MM-DD HH:mm:ss' },
  174. daterange: { picker: 'range', showTime: false, dateFormat: 'YYYY-MM-DD' },
  175. datetimerange: { picker: 'range', showTime: true, dateFormat: 'YYYY-MM-DD HH:mm:ss' }
  176. }
  177. if (!datePickerType.includes('range')) {
  178. return (
  179. <FieldWrapper key={widgetId}>
  180. <Form.Item name={widgetId} label={labelContent} style={{ marginBottom: 0 }}>
  181. <DatePicker
  182. className='w-full'
  183. picker={
  184. datePickerTypeMap[datePickerType as keyof typeof datePickerTypeMap]
  185. .picker as any
  186. }
  187. showTime={
  188. datePickerTypeMap[datePickerType as keyof typeof datePickerTypeMap].showTime
  189. }
  190. format={
  191. datePickerTypeMap[datePickerType as keyof typeof datePickerTypeMap]
  192. .dateFormat
  193. }
  194. />
  195. </Form.Item>
  196. </FieldWrapper>
  197. )
  198. } else {
  199. return (
  200. <FieldWrapper key={widgetId}>
  201. <Form.Item name={widgetId} label={labelContent} style={{ marginBottom: 0 }}>
  202. <RangePicker
  203. className='w-full'
  204. picker={
  205. datePickerTypeMap[datePickerType as keyof typeof datePickerTypeMap]
  206. .picker as any
  207. }
  208. showTime={
  209. datePickerTypeMap[datePickerType as keyof typeof datePickerTypeMap].showTime
  210. }
  211. format={
  212. datePickerTypeMap[datePickerType as keyof typeof datePickerTypeMap]
  213. .dateFormat
  214. }
  215. />
  216. </Form.Item>
  217. </FieldWrapper>
  218. )
  219. }
  220. }
  221. case 'FILE_PICKER': {
  222. const expandVal = expand as API.FD_COM['FILE_PICKER']
  223. const uploadUrl = '/cms/file/submit'
  224. const userStore = useUserStore()
  225. const headers = {
  226. Authorization: 'Bearer ' + userStore.access_token,
  227. 'X-Tenant-Id': String(userStore.userInfo.tenantId),
  228. 'X-Target-Folder': 'DECLARATION',
  229. 'X-Client-Type': 'TO_B',
  230. 'X-Terminal-Type': ''
  231. }
  232. const limitType = (() => {
  233. const accept = expandVal.accept || ''
  234. const multiAcceptList = expandVal.multiAcceptList || []
  235. if (accept && multiAcceptList.length !== 0) {
  236. if (multiAcceptList[0] === 'ATTACHMENT') {
  237. return undefined
  238. } else {
  239. return multiAcceptList.join(',')
  240. }
  241. } else if (accept) {
  242. if (accept === 'ATTACHMENT') {
  243. return undefined
  244. } else {
  245. return accept
  246. }
  247. } else if (multiAcceptList.length !== 0) {
  248. if (multiAcceptList[0] === 'ATTACHMENT') {
  249. return undefined
  250. } else {
  251. return multiAcceptList.join(',')
  252. }
  253. } else {
  254. return undefined
  255. }
  256. })()
  257. const normFile = (e: any) => {
  258. console.log(e)
  259. if (Array.isArray(e)) return e
  260. return e?.fileList || []
  261. }
  262. console.log(limitType)
  263. const handlePreview = (file: any) => {
  264. console.log(file)
  265. // window.open(file.url)
  266. }
  267. const handleChange = (file: any) => {
  268. console.log(file)
  269. }
  270. const uploadButton = (
  271. <button style={{ border: 0, background: 'none' }} type='button'>
  272. <PlusOutlined />
  273. <div style={{ marginTop: 8 }}>点击上传</div>
  274. </button>
  275. )
  276. return (
  277. <FieldWrapper key={widgetId}>
  278. <Form.Item
  279. name={widgetId}
  280. label={labelContent}
  281. style={{ marginBottom: 0 }}
  282. valuePropName='fileList'
  283. getValueFromEvent={normFile}>
  284. <Upload
  285. action={import.meta.env.VITE_APP_ENV === 'dev' ? '/api' + uploadUrl : uploadUrl}
  286. headers={headers}
  287. listType='picture-card'
  288. accept={limitType}
  289. maxCount={expandVal.limit}
  290. onPreview={handlePreview}
  291. onChange={handleChange}>
  292. {uploadButton}
  293. </Upload>
  294. </Form.Item>
  295. </FieldWrapper>
  296. )
  297. }
  298. // 其他组件类型...
  299. default:
  300. return null
  301. }
  302. }
  303. return (
  304. <Form
  305. form={form}
  306. layout='vertical'
  307. initialValues={formData}
  308. disabled={disabled}
  309. onFinish={onFinish}
  310. onFinishFailed={onFinishFailed}>
  311. {formItems.map(renderField)}
  312. </Form>
  313. )
  314. }
  315. )
  316. )
  317. export default DynamicForm