index.js 6.1 KB

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