index.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. <template>
  2. <component :is="'el-form'" v-bind="_formOptions" ref="proFormRef" :model="formModel">
  3. <el-row :gutter="5">
  4. <template v-for="item in itemsOptions" :key="item.prop">
  5. <el-col :span="item.span || 24" g>
  6. <component :is="'el-form-item'" v-bind="item" v-if="show(item.show)">
  7. <template #label>
  8. <el-space :size="4">
  9. <span class="label-span">{{ `${item.label}` }}</span>
  10. <!-- <el-tooltip :content="item.formItem.label" placement="top" :disabled="isShowTooltip">
  11. <span class="label-span" @mouseenter="visibleTooltip">{{ `${item.formItem.label}` }}</span>
  12. </el-tooltip> -->
  13. <el-tooltip v-if="item?.tooltip" effect="dark" :content="item?.tooltip" placement="top">
  14. <i :class="'iconfont icon-yiwen'"></i>
  15. </el-tooltip>
  16. </el-space>
  17. <span v-if="item.hideLabelSuffix">{{ `${item.hideLabelSuffix ? '' : ':'}` }}</span>
  18. <span v-else>{{ `${_formOptions.labelSuffix}` }}</span>
  19. </template>
  20. <!-- <template v-if="item.compOptions.elTagName === 'slot'"> -->
  21. <slot :name="item.prop" :form-model="formModel"></slot>
  22. <slot name="default" :form-model="formModel"></slot>
  23. <!-- </template> -->
  24. <template v-if="item.compOptions.elTagName === 'icon'">
  25. <SelectIcon v-model:icon-value="formModel[item.prop]" />
  26. </template>
  27. <template v-else-if="item.compOptions.elTagName === 'file-upload'">
  28. <FileUpload v-model:model-value="formModel[item.prop]" />
  29. </template>
  30. <template v-else-if="item.compOptions.elTagName === 'img-upload'">
  31. <Imgs v-model="formModel[item.prop]" v-bind="$attrs" />
  32. </template>
  33. <template v-else-if="item.compOptions.elTagName === 'file-upload-s3'">
  34. <FileUploadS3 v-model:model-value="formModel[item.prop]" />
  35. </template>
  36. <template v-else-if="item.compOptions.elTagName === 'img-upload-s3'">
  37. <ImgsS3 v-model="formModel[item.prop]" v-bind="$attrs" />
  38. </template>
  39. <Item v-else :item="item" :form-model="formModel" />
  40. </component>
  41. </el-col>
  42. </template>
  43. </el-row>
  44. <el-form-item v-if="_formOptions.hasFooter">
  45. <slot name="operation" :model="formModel" :pro-form-ref="proFormRef">
  46. <el-button type="primary" @click="onSubmit(proFormRef)">{{ _formOptions.submitButtonText }}</el-button>
  47. <el-button v-if="_formOptions.showResetButton" type="info" @click="resetForm(proFormRef)">
  48. {{ _formOptions.resetButtonText }}
  49. </el-button>
  50. <el-button v-if="_formOptions.showCancelButton" @click="emits('cancel')">
  51. {{ _formOptions.cancelButtonText }}
  52. </el-button>
  53. </slot>
  54. </el-form-item>
  55. </component>
  56. </template>
  57. <script setup lang="ts" name="ProForm">
  58. import { ref, computed, ComputedRef, watch, unref, provide } from 'vue'
  59. import { ElMessage, type FormInstance } from 'element-plus'
  60. import Item from '@/components/ProForm/components/Item.vue'
  61. import SelectIcon from '@/components/SelectIcon/index.vue'
  62. import FileUpload from '@/components/Upload/File.vue'
  63. import Imgs from '@/components/Upload/Imgs.vue'
  64. import FileUploadS3 from '@/components/Upload/FileS3.vue'
  65. import ImgsS3 from '@/components/Upload/ImgsS3.vue'
  66. // import { handleProp } from '@/utils'
  67. // 表单整体配置项
  68. export interface ProFormProps {
  69. formOptions?: ProForm.FormOptions
  70. itemsOptions: ProForm.ItemsOptions[]
  71. model?: Record<ProForm.FormItem['prop'], ProForm.FormItem['value']>
  72. api?: (params: any) => Promise<any> // 表单提交api
  73. }
  74. // 表单的数据
  75. const formModel = ref<Record<string, any>>({})
  76. // 表单ref
  77. const proFormRef = ref<FormInstance>()
  78. // 默认值
  79. const props = withDefaults(defineProps<ProFormProps>(), {
  80. items: () => [],
  81. model: () => ({})
  82. })
  83. const show = (showFunction: any) => {
  84. if (!showFunction) return true
  85. if (typeof showFunction == 'function') {
  86. // 直接调用 showFunction 函数,并传入 formModel.value
  87. return showFunction(formModel.value)
  88. }
  89. // 如果 showFunction 不是函数,直接返回 true 显示该表单项
  90. return true
  91. }
  92. // 设置option默认值,如果传入自定义的配置则合并form配置项
  93. const _formOptions: ComputedRef<ProForm.FormOptions> = computed(() => {
  94. const form = {
  95. inline: false,
  96. labelPosition: 'right',
  97. labelWidth: 120,
  98. disabled: false,
  99. hasFooter: true,
  100. labelSuffix: ':',
  101. submitButtonText: '提交',
  102. resetButtonText: '重置',
  103. cancelButtonText: '取消'
  104. }
  105. return Object.assign(form, props?.formOptions)
  106. })
  107. // 事件传递定义
  108. interface EmitEvent {
  109. (e: 'submit', params: any): void
  110. (e: 'reset'): void
  111. (e: 'cancel'): void
  112. }
  113. const enumMap = ref(new Map<string, { [key: string]: any }[]>())
  114. const setEnumMap = async ({ prop, compOptions }: ProForm.ItemsOptions) => {
  115. const props = compOptions?.enumKey || prop
  116. const enumValue = compOptions?.enum
  117. if (!enumValue) return
  118. // 如果当前 enumMap 存在相同的值 return
  119. if (enumMap.value.has(props!) && (typeof enumValue === 'function' || enumMap.value.get(props!) === enumValue)) return
  120. // 当前 enum 为静态数据,则直接存储到 enumMap
  121. if (typeof enumValue !== 'function') return enumMap.value.set(props!, unref(enumValue!))
  122. // 为了防止接口执行慢,而存储慢,导致重复请求,所以预先存储为[],接口返回后再二次存储
  123. enumMap.value.set(props!, [])
  124. // 当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap
  125. let { data } = await enumValue()
  126. enumMap.value.set(props!, data)
  127. }
  128. // 注入 enumMap
  129. provide('enumMap', enumMap)
  130. // 处理表单项需要的参数
  131. props.itemsOptions.forEach(async item => {
  132. await setEnumMap(item)
  133. })
  134. const emits = defineEmits<EmitEvent>()
  135. // 根据items初始化model, 如果items有传值就用传递的model数据模型,否则就给上面声明的formModel设置相应的(key,value) [item.prop], item.value是表单的默认值(选填)
  136. watch(
  137. () => props.model,
  138. () => {
  139. props.itemsOptions.map((item: ProForm.ItemsOptions) => {
  140. // 如果类型为checkbox,默认值需要设置一个空数组
  141. let value = ['checkbox', 'transfer'].includes(item.compOptions.elTagName!) ? [] : props.model[item.prop]
  142. props.model[item.prop] ? (formModel.value = props.model) : (formModel.value[item.prop] = item.compOptions.value || value)
  143. })
  144. },
  145. { immediate: true }
  146. )
  147. // 提交按钮
  148. const onSubmit = (formEl: FormInstance | undefined) => {
  149. console.log('表单提交数据', formModel.value)
  150. if (!formEl) return
  151. formEl.validate(valid => {
  152. if (valid) {
  153. if (props.api) emits('submit', formModel.value)
  154. props.api!({ ...formModel }).then(res => {
  155. if (res.code == 200) {
  156. resetForm(formEl)
  157. ElMessage.success('操作成功')
  158. } else {
  159. console.log('message', res.message)
  160. }
  161. })
  162. } else {
  163. console.log('校验失败')
  164. }
  165. })
  166. }
  167. // 重置
  168. const resetForm = (formEl: FormInstance | undefined) => {
  169. if (!formEl) return
  170. formEl.resetFields()
  171. }
  172. // let isShowTooltip = ref(false)
  173. // const visibleTooltip = e => {
  174. // console.log('e', e)
  175. // isShowTooltip.value = e.target.offsetWidth + 18 - e.fromElement.clientWidth > 0 ? false : true //18为required *号引起的偏差。
  176. // console.log('isShowTooltip', isShowTooltip.value)
  177. // }
  178. // 暴露方法给父组件使用
  179. defineExpose({
  180. proFormRef,
  181. formModel,
  182. resetForm,
  183. onSubmit
  184. })
  185. </script>
  186. <style scoped lang="scss">
  187. @import './index.scss';
  188. </style>