index.jsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import { defineComponent, markRaw, ref, computed } from 'vue'
  2. import { ElDialog, ElButton } from 'element-plus'
  3. import { getUsingConfig } from '@cip/utils/util'
  4. import { useCipConfig } from '@cip/components/hooks/use-cip-config'
  5. import './dialog.less'
  6. export default defineComponent({
  7. name: 'CipDialog',
  8. props: {
  9. title: String,
  10. modelValue: { default: false }, // 是否显示dialog
  11. onConfirm: { type: Function }, // Function(resolve,reject)
  12. beforeConfirm: { type: Function, default: () => {} }, // 触发表单验证前的值设置
  13. width: { // 设置dialog宽度
  14. default: '870px'
  15. },
  16. top: { default: '15vh' },
  17. closeOnClickModal: { // 点击遮罩层是否关闭dialog
  18. type: Boolean,
  19. default: false
  20. },
  21. showOnly: {
  22. default: false
  23. },
  24. buttonSize: { // 设置footer按钮大小
  25. default: 'small'
  26. },
  27. confirmText: { // 触发on-confirm方法的按钮文字
  28. default: '确认'
  29. },
  30. cancelText: { // 触发关闭dialog方法按钮的文字
  31. default: '取消'
  32. },
  33. showCancel: {
  34. type: Boolean,
  35. default: true
  36. },
  37. destroyOnClose: { // 默认关闭时销毁dialog
  38. type: Boolean,
  39. default: true
  40. },
  41. closeOnPressEscape: {},
  42. maxDepth: { // 查找form的深度
  43. type: Number,
  44. default: 5
  45. },
  46. fullscreen: Boolean
  47. },
  48. emits: ['update:modelValue', 'cancel', 'close'],
  49. setup (props, { slots, emit }) {
  50. const cipConfig = useCipConfig()
  51. // esc弹窗
  52. const closeOnPressEscape = computed(() => {
  53. return getUsingConfig(props.closeOnPressEscape, cipConfig.dialog?.closeOnPressEscape)
  54. })
  55. const defaultSlot = ref([])
  56. const isComponent = vnode => !!vnode.component
  57. const isFormComponent = (vnode) => {
  58. if (!isComponent(vnode)) return false
  59. const componentName = (vnode.type?.name ?? '').toLocaleLowerCase()
  60. return componentName.indexOf('form') > -1
  61. }
  62. const getComponent = vnode => vnode?.component
  63. const getMethodByComponent = (component = {}, method) => component.ctx?.[method] ?? component.exposed?.[method]
  64. const clearValidate = (vnodeList = []) => {
  65. // eslint-disable-next-line no-unused-expressions
  66. const handler = (component) => {
  67. const clearValidate = getMethodByComponent(component, 'clearValidate')
  68. // eslint-disable-next-line no-unused-expressions
  69. clearValidate?.()
  70. }
  71. findFormAndHandler(vnodeList, handler).then(() => {})
  72. }
  73. const validForms = async (vnodeList = []) => {
  74. const validList = []
  75. const handler = async (component) => {
  76. try {
  77. const validate = getMethodByComponent(component, 'validate')
  78. await validate?.()
  79. validList.push(true)
  80. } catch (e) {
  81. validList.push(e)
  82. }
  83. }
  84. try {
  85. await findFormAndHandler(vnodeList, handler)
  86. } catch (e) {
  87. console.log('findFormAndHandler', e)
  88. }
  89. return validList
  90. }
  91. const findFormAndHandler = async (vnodeList = [], handler = () => {}, depth = 1) => {
  92. for (let i = 0; i < vnodeList.length; i++) {
  93. const vnode = vnodeList[i]
  94. if (isFormComponent(vnode)) {
  95. const component = getComponent(vnode)
  96. await handler(component)
  97. } else {
  98. if (depth > props.maxDepth) return false
  99. // 使用defineAsyncComponent 是 subTree 及为下个组件
  100. const childVnodeList = isComponent(vnode)
  101. ? (vnode.component.subTree?.children ?? [vnode.component.subTree])
  102. : vnode.children
  103. // 如果是文字节点则跳过不处理
  104. if (typeof childVnodeList === 'string') {
  105. continue
  106. }
  107. if (childVnodeList?.length > 0) {
  108. await findFormAndHandler(childVnodeList, handler, ++depth) // 此处++必须在前
  109. }
  110. }
  111. }
  112. }
  113. const waiting = ref(false)
  114. // 默认使用props.onConfirm当入参为函数时使用函数
  115. const confirm = async (cb) => {
  116. // 防止cb为e导致的错误
  117. if (typeof cb !== 'function') cb = props.onConfirm
  118. try {
  119. waiting.value = true
  120. const validList = await validForms(defaultSlot.value)
  121. if (!validList.some(valid => valid !== true)) {
  122. const res = await new Promise((resolve, reject) => {
  123. if (typeof cb === 'function') {
  124. cb(resolve, reject)
  125. } else {
  126. reject(new TypeError('onConfirm is not a function'))
  127. }
  128. })
  129. updateVisible(false)
  130. return res ?? true
  131. } else {
  132. // 存在未通过验证的表单
  133. throw new Error('未通过表单验证')
  134. }
  135. // eslint-disable-next-line no-useless-catch
  136. } catch (e) {
  137. // 跑出错误
  138. throw e
  139. } finally {
  140. waiting.value = false
  141. }
  142. }
  143. const cancel = () => {
  144. updateVisible(false)
  145. emit('cancel')
  146. }
  147. const openHandler = () => {
  148. clearValidate(defaultSlot.value)
  149. }
  150. const closeHandler = () => {
  151. emit('close')
  152. }
  153. const updateVisible = (val) => {
  154. emit('update:modelValue', val)
  155. }
  156. const dialogSlots = {
  157. title: () => <>
  158. <div className="el-dialog__mainTitle">{props.title || slots.mainTitle?.() }</div>
  159. <div class="el-dialog__subTitle">{slots.subTitle?.()}</div>
  160. </>,
  161. default: () => {
  162. const slot = slots.default?.()
  163. if (slot) {
  164. defaultSlot.value = markRaw(slot)
  165. }
  166. return slot
  167. },
  168. footer: () => {
  169. if (!props.showOnly) {
  170. const defaultFooter = <div>
  171. {
  172. !waiting.value && props.showCancel &&
  173. <ElButton
  174. onClick={() => cancel()}
  175. size={props.buttonSize}>
  176. {props.cancelText}
  177. </ElButton>
  178. }
  179. <ElButton
  180. onClick={() => confirm()}
  181. size={props.buttonSize}
  182. type={'primary'}
  183. loading={waiting.value} >
  184. {props.confirmText}
  185. </ElButton>
  186. </div>
  187. const footerSlot = slots.footer?.({ confirm, loading: waiting.value, cancel })
  188. return slots.footer ? footerSlot : defaultFooter
  189. } else {
  190. return slots.footer?.()
  191. }
  192. }
  193. }
  194. return () => (
  195. <ElDialog
  196. customClass={'cip-dialog__wrapper'}
  197. modelValue={props.modelValue}
  198. onUpdate:modelValue={updateVisible}
  199. title={props.title}
  200. width={props.width}
  201. closeOnClickModal={props.closeOnClickModal}
  202. top={props.top}
  203. destroyOnClose={props.destroyOnClose}
  204. appendToBody={true}
  205. close-on-press-escape={closeOnPressEscape.value}
  206. onClose={() => closeHandler()}
  207. onOpen={() => openHandler()}
  208. fullscreen={props.fullscreen}
  209. v-slots={dialogSlots}/>
  210. )
  211. }
  212. })