wd-collapse-item.vue 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <template>
  2. <view :class="`wd-collapse-item ${disabled ? 'is-disabled' : ''} is-border ${customClass}`" :style="customStyle">
  3. <view
  4. :class="`wd-collapse-item__header ${expanded ? 'is-expanded' : ''} ${isFirst ? 'wd-collapse-item__header-first' : ''} ${
  5. $slots.title ? 'is-custom' : ''
  6. }`"
  7. @click="handleClick"
  8. >
  9. <slot name="title" :expanded="expanded" :disabled="disabled" :isFirst="isFirst">
  10. <text class="wd-collapse-item__title">{{ title }}</text>
  11. <wd-icon name="arrow-down" :custom-class="`wd-collapse-item__arrow ${expanded ? 'is-retract' : ''}`" />
  12. </slot>
  13. </view>
  14. <view class="wd-collapse-item__wrapper" :style="contentStyle" @transitionend="handleTransitionEnd">
  15. <view class="wd-collapse-item__body" :class="customBodyClass" :style="customBodyStyle" :id="collapseId">
  16. <slot />
  17. </view>
  18. </view>
  19. </view>
  20. </template>
  21. <script lang="ts">
  22. export default {
  23. name: 'wd-collapse-item',
  24. options: {
  25. addGlobalClass: true,
  26. virtualHost: true,
  27. styleIsolation: 'shared'
  28. }
  29. }
  30. </script>
  31. <script lang="ts" setup>
  32. import wdIcon from '../wd-icon/wd-icon.vue'
  33. import { computed, getCurrentInstance, onMounted, ref, watch, type CSSProperties } from 'vue'
  34. import { addUnit, getRect, isArray, isDef, isPromise, isString, objToStyle, pause, uuid } from '../common/util'
  35. import { useParent } from '../composables/useParent'
  36. import { COLLAPSE_KEY } from '../wd-collapse/types'
  37. import { collapseItemProps, type CollapseItemExpose } from './types'
  38. const collapseId = ref<string>(`collapseId${uuid()}`)
  39. const props = defineProps(collapseItemProps)
  40. const { parent: collapse, index } = useParent(COLLAPSE_KEY)
  41. const height = ref<string | number>('')
  42. const inited = ref<boolean>(false)
  43. const expanded = ref<boolean>(false)
  44. const { proxy } = getCurrentInstance() as any
  45. /**
  46. * 容器样式,(动画)
  47. */
  48. const isFirst = computed(() => {
  49. return index.value === 0
  50. })
  51. /**
  52. * 容器样式,(动画)
  53. */
  54. const contentStyle = computed(() => {
  55. const style: CSSProperties = {}
  56. if (inited.value) {
  57. style.transition = 'height 0.3s ease-in-out'
  58. }
  59. if (!expanded.value) {
  60. style.height = '0px'
  61. } else if (height.value) {
  62. style.height = addUnit(height.value)
  63. }
  64. return objToStyle(style)
  65. })
  66. /**
  67. * 是否选中
  68. */
  69. const isSelected = computed(() => {
  70. const modelValue = collapse ? collapse?.props.modelValue || [] : []
  71. const { name } = props
  72. return (isString(modelValue) && modelValue === name) || (isArray(modelValue) && modelValue.indexOf(name as string) >= 0)
  73. })
  74. watch(
  75. () => isSelected.value,
  76. (newVal) => {
  77. updateExpand(newVal)
  78. }
  79. )
  80. onMounted(() => {
  81. updateExpand(isSelected.value)
  82. })
  83. async function updateExpand(useBeforeExpand: boolean = true) {
  84. try {
  85. if (useBeforeExpand) {
  86. await handleBeforeExpand()
  87. }
  88. initRect()
  89. } catch (error) {
  90. /* empty */
  91. }
  92. }
  93. function initRect() {
  94. getRect(`#${collapseId.value}`, false, proxy).then(async (rect) => {
  95. const { height: rectHeight } = rect
  96. height.value = isDef(rectHeight) ? Number(rectHeight) : ''
  97. await pause()
  98. if (isSelected.value) {
  99. expanded.value = true
  100. } else {
  101. expanded.value = false
  102. }
  103. if (!inited.value) {
  104. inited.value = true
  105. }
  106. })
  107. }
  108. function handleTransitionEnd() {
  109. if (expanded.value) {
  110. height.value = ''
  111. }
  112. }
  113. // 点击子项
  114. async function handleClick() {
  115. if (props.disabled) return
  116. try {
  117. await updateExpand()
  118. const { name } = props
  119. collapse && collapse.toggle(name, !expanded.value)
  120. } catch (error) {
  121. /* empty */
  122. }
  123. }
  124. /**
  125. * 展开前钩子
  126. */
  127. function handleBeforeExpand() {
  128. return new Promise<void>((resolve, reject) => {
  129. const { name } = props
  130. const nextexpanded = !expanded.value
  131. if (nextexpanded && props.beforeExpend) {
  132. const response = props.beforeExpend(name)
  133. if (!response) {
  134. reject()
  135. }
  136. if (isPromise(response)) {
  137. response.then(() => resolve()).catch(reject)
  138. } else {
  139. resolve()
  140. }
  141. } else {
  142. resolve()
  143. }
  144. })
  145. }
  146. function getExpanded() {
  147. return expanded.value
  148. }
  149. defineExpose<CollapseItemExpose>({ getExpanded, updateExpand })
  150. </script>
  151. <style lang="scss" scoped>
  152. @import './index.scss';
  153. </style>