index.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import { ElFormItem, ElTooltip } from 'element-plus'
  2. import { h, toRef, computed, ref, unref, onErrorCaptured } from 'vue'
  3. import { getInputComponent, getViewComponent, getH5InputComponent } from '../cip-form-input/util'
  4. import { isEmpty, isEmptyObject, isInputEmpty } from '@cip/utils/util'
  5. import { useWatchFieldDepend } from './hooks/use-field-depend'
  6. import { useFieldValue, useSteamUpdateValues } from './hooks/use-model-change'
  7. import { useRules } from './hooks/use-field-rules'
  8. import { isHideLabel, getLabelWidth, UpdateModelQueue } from './util'
  9. import './index.less'
  10. import { useFormInject, useElFormInject } from '../hooks/use-form'
  11. import { useCipConfig } from '@cip/components/hooks/use-cip-config'
  12. export default {
  13. name: 'CipFormItem',
  14. props: {
  15. config: Object, // 字段配置信息
  16. fieldKey: String, // 字段名
  17. model: { // 字段所属model
  18. type: Object,
  19. default: () => ({})
  20. },
  21. readonly: Boolean, // 是否只读-即查看模式
  22. customSlots: Function,
  23. showTemplate: { // 是否展示模版值[注:表单设计中使用]
  24. type: Boolean,
  25. default: false
  26. },
  27. tableDependOnValues: Object,
  28. inTable: {
  29. type: Boolean,
  30. default: false
  31. },
  32. componentKey: String,
  33. grid: [Number, Boolean],
  34. tableData: Array,
  35. onSearch: Function // 供CipSearchForm使用
  36. },
  37. emits: ['update:model'],
  38. setup (props, context) {
  39. onErrorCaptured((e) => {
  40. // el-form-item 在onBeforeUnmount会访问el.value.firstElementChild 由于切换较快的原因会抱错
  41. process.env.NODE_ENV === 'development' && console.log(e)
  42. return false
  43. })
  44. // elForm组件实例
  45. const cipConfig = useCipConfig()
  46. const elForm = useElFormInject()
  47. const cipForm = useFormInject()
  48. const equipment = computed(() => {
  49. return unref(cipForm.equipment) || 'pc'
  50. })
  51. // 一般处于cip-table直接使用的情况下
  52. const notInForm = computed(() => {
  53. return isEmptyObject(elForm)
  54. })
  55. const updateModel = (value) => {
  56. // console.log('updateModel', value)
  57. // if (!notInForm.value) {
  58. // // 因为延迟执行的问题,可能导致数据无法及时有效验证手动调用
  59. // elForm.validateField([formItemConfig.value.ruleKey || props.fieldKey])
  60. // }
  61. context.emit('update:model', value)
  62. }
  63. const updateModelQueue = new UpdateModelQueue(() => props.model, updateModel)
  64. // FormItem及Input组件渲染模式 ['hidden','read','read-write']
  65. const status = computed(() => {
  66. // 为设置则默认开始可读可写模式
  67. const config = formItemConfig.value
  68. if (props.readonly) {
  69. if (config.readable === false) return 'hidden'
  70. return 'read'
  71. }
  72. if (isEmpty(config.readable) && isEmpty(config.writable)) {
  73. return 'read-write'
  74. }
  75. if (config.writable) {
  76. return 'read-write'
  77. } else if (config.readable) {
  78. return 'read'
  79. } else {
  80. return 'hidden'
  81. }
  82. })
  83. // Input组件实际使用的配置
  84. const formItemConfig = computed(() => {
  85. return runningConfig.value || props.config // handleFormConfig()
  86. })
  87. const model = toRef(props, 'model')
  88. const fieldKey = toRef(props, 'fieldKey')
  89. // 仅正对modelValue生效
  90. const changeEffect = computed(() => {
  91. return formItemConfig.value.changeEffect
  92. })
  93. // 3种数据更新方式并行
  94. // modelValue
  95. const [modelValue, updateModelValue] = useFieldValue(fieldKey, model, updateModelQueue, changeEffect)
  96. const otherKey = computed(() => formItemConfig.value?.otherKey)
  97. // otherValue
  98. const [otherValue, updateOtherValue] = useFieldValue(otherKey, model, updateModelQueue)
  99. const { values, streamUpdateModel, clearValues } = useSteamUpdateValues(fieldKey, otherKey, model, updateModel, changeEffect)
  100. // 监听依赖 触发响应事件
  101. const { changeCount, dependOnValues, outDependOnValues, runningConfig } = useWatchFieldDepend(props, context, { updateModelValue, updateOtherValue, clearValues })
  102. const readonly = toRef(props, 'readonly')
  103. // rules
  104. const { usingRules, rules } = useRules(formItemConfig, readonly, status, otherValue, dependOnValues, outDependOnValues)
  105. // Input组件是否展示标记(需要控制form-item内置的margin-bottom)
  106. const childStatus = ref(true)
  107. // FormItem label
  108. const formItemLabel = () => {
  109. // 隐藏label或者label为空时直接返回空字符串
  110. if (formItemConfig.value.hideLabel === true || isEmpty(formItemConfig.value.label)) return ''
  111. // 表单为只读模式下展示样式控制
  112. const labelId = formItemConfig.value.directory ? props.fieldKey : undefined
  113. const result = [h('span', { class: { 'is-readonly': props.readonly }, id: labelId }, [formItemConfig.value.label])]
  114. // 存在说明
  115. if (formItemConfig.value.description) {
  116. const descriptionComp = (
  117. <ElTooltip effect={formItemConfig.value.descriptionEffect || 'light'} placement={'top'}>
  118. {{
  119. content: () => formItemConfig.value.description,
  120. default: () => <i class={'el-icon-question'} style={'margin-left:2px;line-height: inherit;'} />
  121. }}
  122. </ElTooltip>
  123. )
  124. result.push(descriptionComp)
  125. }
  126. // 仅在正在使用rules的input中且required为true时展示必填标记
  127. if (usingRules.value && formItemConfig.value.required) {
  128. const requiredAsterisk = (<span class={'cip-danger-color'}>*</span>)
  129. result.unshift(requiredAsterisk)
  130. }
  131. // 有标签后缀的增加标签后缀
  132. if (elForm.labelSuffix) {
  133. result.push(elForm.labelSuffix)
  134. }
  135. return h('div', { style: { color: '#333333', ...formItemConfig.value.labelStyle } }, result) // result
  136. }
  137. const inlineErrorMessage = computed(() => {
  138. return props.inTable || cipForm.equipment === 'mobile'
  139. })
  140. // FormItem ErrorMessage
  141. const errorMessageNode = ({ error }) => {
  142. if (!inlineErrorMessage.value) return null
  143. return (
  144. <ElTooltip content={error}>
  145. <i class={'el-icon-warning cip-danger-color '} style={{ outline: 'none', border: 'none' }} />
  146. </ElTooltip>
  147. )
  148. }
  149. const labelPosition = computed(() => {
  150. // 依赖elForm实例数据总是在变换、故修改为有父组件cip-form下发数据
  151. if (!isEmpty(formItemConfig.value.labelPosition)) {
  152. return formItemConfig.value.labelPosition === 'top'
  153. }
  154. return unref(cipForm.labelPosition) === 'top'
  155. })
  156. const renderItemInput = () => {
  157. if (props.customSlots) {
  158. return props.customSlots({
  159. fieldKey: props.fieldKey,
  160. modelValue: modelValue.value,
  161. updateModel: updateModelValue, // 即将废弃,此处名字与实际功能不符合
  162. updateModelValue
  163. })
  164. }
  165. const type = formItemConfig.value.type || 'default'
  166. const componentProps = {
  167. key: props.componentKey,
  168. id: props.fieldKey,
  169. fieldKey: props.fieldKey,
  170. modelValue: modelValue.value,
  171. otherValue: otherValue.value,
  172. values: values.value,
  173. // model: model.value, // 即将废弃
  174. changeCount: changeCount.value,
  175. config: formItemConfig.value,
  176. usingRules: usingRules.value,
  177. rules: rules.value,
  178. dependOnValues: dependOnValues.value,
  179. outDependOnValues: outDependOnValues.value,
  180. disabled: formItemConfig.value.importantDisabled !== undefined
  181. ? formItemConfig.value.importantDisabled
  182. : formItemConfig.value.disabled,
  183. showTemplate: props.showTemplate,
  184. tableData: props.tableData,
  185. class: 'cip-form-item__input',
  186. onStatusChange: (status) => { // 子组件触发事件控制父组件是否显示
  187. childStatus.value = status
  188. },
  189. onSearch: props.onSearch
  190. }
  191. if (status.value === 'read-write') {
  192. const inputComponentProps = {
  193. ...componentProps,
  194. // 'onUpdate:modelValue': updateModelValue,
  195. // 'onUpdate:otherValue': updateOtherValue,
  196. 'onStreamUpdate:model': streamUpdateModel
  197. }
  198. if (unref(cipForm.equipment) === 'mobile') {
  199. return h(getH5InputComponent(type), inputComponentProps)
  200. } else {
  201. return h(getInputComponent(type), inputComponentProps)
  202. }
  203. } else {
  204. // 根据cip-config配置给view的modelValue添加默认值
  205. if (isInputEmpty(componentProps.modelValue)) {
  206. if (props.inTable && cipConfig.table.defaultViewValue) {
  207. componentProps.modelValue = cipConfig.table.defaultViewValue
  208. }
  209. if (cipConfig.defaultViewValue) {
  210. componentProps.modelValue = cipConfig.defaultViewValue
  211. }
  212. }
  213. return h(getViewComponent(type), componentProps)
  214. }
  215. }
  216. // FormItem
  217. const formItem = () => {
  218. return h(ElFormItem, {
  219. class: {
  220. 'pos-top': labelPosition.value,
  221. // 'pos-top--padding': labelPositionTopPadding, 暂时关闭position === top时内容的缩进
  222. 'hide-label': isHideLabel(formItemConfig.value),
  223. 'content--end': formItemConfig.value.contentEnd
  224. },
  225. prop: formItemConfig.value.ruleKey || props.fieldKey, // 子表单内的输入框会生成一个ruleKey
  226. rules: rules.value,
  227. labelWidth: getLabelWidth(formItemConfig.value),
  228. inlineMessage: inlineErrorMessage.value
  229. }, {
  230. label: formItemLabel,
  231. error: errorMessageNode,
  232. default: renderItemInput
  233. })
  234. }
  235. const cipFormStyle = computed(() => {
  236. if (props.grid) {
  237. return { gridColumn: `span ${formItemConfig.value.span || 1}`, ...formItemConfig.value.itemStyle }
  238. } else {
  239. return (elForm.inline && !props.inTable) ? formItemConfig.value.style : formItemConfig.value.itemStyle
  240. }
  241. })
  242. return () => {
  243. if (status.value === 'hidden') return null
  244. if (props.inTable && status.value === 'read') return renderItemInput()
  245. // 父组件不存在form状态下的渲染方式 目前经为cip-table下直接渲染使用
  246. if (notInForm.value) return renderItemInput()
  247. return (
  248. <div
  249. style={cipFormStyle.value}
  250. class={[
  251. 'cip-form-item',
  252. 'el-form-item__wrapper',
  253. 'ep-form-item__wrapper', // 支持namespace ep
  254. `cip-form-item--${equipment.value}`,
  255. {
  256. 'cip-form-item--label-position-top': labelPosition.value,
  257. 'cip-form-item--hidden': !childStatus.value || formItemConfig.value.hideItem,
  258. 'cip-form-item--in-table': props.inTable
  259. }
  260. ]}>
  261. {formItem()}
  262. </div>
  263. )
  264. }
  265. }
  266. }