index.jsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import { h, ref, toRefs } from 'vue'
  2. import { ElForm } from 'element-plus'
  3. import { useFormProvide } from '../hooks/use-form'
  4. import CipFormItem from '../cip-form-item'
  5. import CipFormDirectory from './form-directory'
  6. import CipFormLayout from '../cip-form-layout'
  7. import { DRender } from '../helper/d-render'
  8. import { toUpperFirstCase, getFieldValue } from '@cip/utils/util'
  9. import CipMessage from '../cip-message'
  10. import './index.less'
  11. const dRender = new DRender()
  12. export default {
  13. name: 'CipForm',
  14. props: {
  15. model: Object,
  16. fieldList: Array,
  17. showOnly: Boolean,
  18. modelKey: {
  19. type: [String, Function]
  20. },
  21. grid: { type: [Number, Boolean] }, // 是否开启grid布局
  22. useDirectory: Boolean,
  23. labelPosition: String,
  24. scrollToError: {
  25. type: Boolean,
  26. default: true
  27. },
  28. equipment: {
  29. type: String,
  30. default: 'pc',
  31. validate: (val) => ['pc', 'mobile'].includes(val)
  32. },
  33. border: Boolean, // showOnly + border 将出现边框
  34. enterHandler: Function, // 回车触发回调
  35. options: Object
  36. },
  37. emits: ['update:model', 'submit', 'cancel'],
  38. setup (props, context) {
  39. // 下发属性
  40. const uploadQueue = ref({})
  41. useFormProvide(props, uploadQueue)
  42. const directoryConfig = ref([])
  43. const { model, fieldList } = toRefs(props)
  44. const cipFormRef = ref()
  45. // 修改model的值
  46. const updateModel = (val) => {
  47. context.emit('update:model', val)
  48. }
  49. const generateComponentKey = (key) => {
  50. if (props.modelKey) {
  51. const appendKey = toUpperFirstCase(key)
  52. if (typeof props.modelKey === 'function') {
  53. return `${props.modelKey(props.model)}${appendKey}`
  54. } else {
  55. const value = getFieldValue(props.model, props.modelKey)
  56. return `${value || ''}${appendKey}`
  57. }
  58. } else {
  59. return key
  60. }
  61. }
  62. // 获取layout及item组件需要的props
  63. const getComponentProps = (key, config) => {
  64. const componentKey = generateComponentKey(key)
  65. const componentProps = {
  66. key: componentKey,
  67. componentKey: componentKey,
  68. model,
  69. fieldKey: key,
  70. config,
  71. readonly: props.showOnly,
  72. grid: props.grid,
  73. formLabelPosition: props.labelPosition,
  74. 'onUpdate:model': (val) => {
  75. if (componentKey === generateComponentKey(key)) {
  76. updateModel(val)
  77. }
  78. }
  79. }
  80. if (props.enterHandler) {
  81. componentProps.onKeyup = (e) => {
  82. const { keyCode } = e
  83. if (keyCode === 13) {
  84. props.enterHandler()
  85. }
  86. }
  87. }
  88. return componentProps
  89. }
  90. // 布局字段渲染方式
  91. const getFormLayout = (componentProps) => {
  92. return h(CipFormLayout, {
  93. ...componentProps,
  94. onValidate: (cb) => {
  95. validate(cb)
  96. },
  97. onSubmit: () => {
  98. context.emit('submit')
  99. },
  100. onCancel: () => {
  101. context.emit('cancel')
  102. }
  103. }, {
  104. item: ({ children = [], isShow } = {}) => {
  105. return children.map((v) => getFormDefaultSlot(v, isShow))
  106. }
  107. })
  108. }
  109. // 输入字段渲染方式
  110. const getFormItem = (componentProps) => {
  111. return h(CipFormItem, componentProps)
  112. }
  113. // 渲染单个字段
  114. const getFormDefaultSlot = ({ key, config } = {}, isShow) => {
  115. // 若存在字段key值的插槽覆盖则配置整个ElFormItem
  116. config._isGrid = props.grid
  117. config._isShow = isShow
  118. if (context.slots[key]) {
  119. return context.slots[key]({ key, config })
  120. }
  121. const componentProps = getComponentProps(key, config)
  122. // 若存在字段key值+Input的插槽覆盖则配置ElFormItem内的Input
  123. if (context.slots[`${key}Input`]) {
  124. return h(CipFormItem, {
  125. ...componentProps,
  126. customSlots: context.slots[`${key}Input`]
  127. })
  128. }
  129. // 判断是否为布局类型的字段
  130. if (dRender.isLayoutType(config.type)) {
  131. // layout类型字段
  132. return getFormLayout(componentProps)
  133. } else {
  134. // input类型字段
  135. // 如果需要表单目录导航则添加
  136. if (config.directory) {
  137. directoryConfig.value[key] = { label: config.staticInfo || config.label, level: config.directory }
  138. }
  139. return getFormItem(componentProps)
  140. }
  141. }
  142. // 渲染表单
  143. const getFormDefaultSlots = () => {
  144. if (props.useDirectory) {
  145. return fieldList.value.map((v) => getFormDefaultSlot(v)).concat(
  146. [h(CipFormDirectory, { directory: directoryConfig.value })]
  147. )
  148. } else {
  149. return fieldList.value.map((v) => getFormDefaultSlot(v))
  150. }
  151. }
  152. /** start父组件通过ref调用方法 **/
  153. const validateUpload = () => {
  154. return new Promise((resolve, reject) => {
  155. const keys = Object.keys(uploadQueue.value)
  156. for (let i = 0; i < keys.length; i++) {
  157. const key = keys[i]
  158. if (uploadQueue.value[key]) {
  159. CipMessage.error('请等待文件上传', '提示')
  160. resolve(false)
  161. break
  162. }
  163. }
  164. resolve(true)
  165. })
  166. }
  167. const validateField = (props, cb) => {
  168. return cipFormRef.value.validateField(props, cb)
  169. }
  170. const validate = async (cb = () => {}) => {
  171. const isUpload = await validateUpload()
  172. if (!isUpload) {
  173. // eslint-disable-next-line standard/no-callback-literal
  174. cb(false)
  175. throw new Error('请等待文件上传')
  176. } else {
  177. // const res = await cipFormRef.value.validate() // 此方式返回的res为 true or false
  178. return new Promise((resolve, reject) => {
  179. cipFormRef.value.validate(async (isValid, invalidFields) => {
  180. if (typeof cb === 'function') cb(isValid, invalidFields)
  181. isValid ? resolve(isValid) : reject(isValid)
  182. })
  183. })
  184. }
  185. }
  186. const clearValidate = () => {
  187. return cipFormRef.value?.clearValidate()
  188. }
  189. context.expose({
  190. validateUpload,
  191. validateField,
  192. validate,
  193. clearValidate
  194. })
  195. /** end父组件通过ref调用方法 **/
  196. return () => h(ElForm, {
  197. ...context.attrs,
  198. ref: cipFormRef,
  199. hideRequiredAsterisk: true,
  200. model: model, // 待进行测试 使用model.value后数据是否正常
  201. class: [
  202. 'cip-form',
  203. `cip-form--${props.equipment}`,
  204. {
  205. 'cip-form--grid': props.grid,
  206. 'cip-form--border': props.border && props.showOnly,
  207. 'cip-form--readonly': props.showOnly
  208. }
  209. ],
  210. style: { gridTemplateColumns: `repeat(${typeof props.grid === 'number' ? props.grid : 3},1fr)` },
  211. size: 'default',
  212. labelPosition: props.labelPosition,
  213. scrollToError: props.scrollToError,
  214. onSubmit: ev => { ev.preventDefault() }
  215. }, { default: () => [getFormDefaultSlots(), context.slots.default?.()] })
  216. }
  217. }