index.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <template>
  2. <view :class="classes">
  3. <view :class="['nut-collapse-item__title', { 'nut-collapse-item__title--disabled': disabled }]" @click="toggleOpen">
  4. <view class="nut-collapse-item__title-main">
  5. <view class="nut-collapse-item__title-main-value">
  6. <slot v-if="$slots.title" name="title"></slot>
  7. <template v-else>
  8. <view v-html="title" class="nut-collapse-item__title-mtitle"></view>
  9. </template>
  10. <view class="nut-collapse-item__title-label" v-if="label">{{ label }}</view>
  11. </view>
  12. </view>
  13. <view v-if="$slots.value" class="nut-collapse-item__title-sub">
  14. <slot name="value"></slot>
  15. </view>
  16. <view v-else v-html="value" class="nut-collapse-item__title-sub"></view>
  17. <view
  18. :class="['nut-collapse-item__title-icon', { 'nut-collapse-item__title-icon--expanded': openExpanded }]"
  19. :style="{ transform: 'rotate(' + (openExpanded ? rotate : 0) + 'deg)' }"
  20. >
  21. <component :is="renderIcon(icon)"></component>
  22. </view>
  23. </view>
  24. <view v-if="$slots.extra" class="nut-collapse__item-extraWrapper">
  25. <div class="nut-collapse__item-extraWrapper__extraRender">
  26. <slot name="extra"></slot>
  27. </div>
  28. </view>
  29. <view class="nut-collapse__item-wrapper" ref="wrapperRef">
  30. <view :class="['nut-collapse__item-wrapper__content', emptyContent]" ref="contentRef">
  31. <slot></slot>
  32. </view>
  33. </view>
  34. </view>
  35. </template>
  36. <script lang="ts">
  37. import {
  38. reactive,
  39. inject,
  40. toRefs,
  41. onMounted,
  42. ref,
  43. nextTick,
  44. computed,
  45. watch,
  46. getCurrentInstance,
  47. ComponentInternalInstance
  48. } from 'vue';
  49. import { DownArrow } from '@nutui/icons-vue';
  50. import { createComponent, renderIcon } from '@/packages/utils/create';
  51. const { create, componentName } = createComponent('collapse-item');
  52. export default create({
  53. props: {
  54. collapseRef: {
  55. type: Object
  56. },
  57. title: {
  58. type: String,
  59. default: ''
  60. },
  61. value: {
  62. type: String,
  63. default: ''
  64. },
  65. label: {
  66. type: String,
  67. default: ''
  68. },
  69. disabled: {
  70. type: Boolean,
  71. default: false
  72. },
  73. name: {
  74. type: [Number, String],
  75. default: -1,
  76. required: true
  77. },
  78. border: {
  79. type: Boolean,
  80. default: true
  81. },
  82. icon: {
  83. type: Object,
  84. default: () => DownArrow
  85. },
  86. rotate: {
  87. type: [String, Number],
  88. default: 180
  89. }
  90. },
  91. // components: { DownArrow },
  92. setup(props, ctx: any) {
  93. const collapse: any = inject('collapseParent');
  94. const parent: any = reactive(collapse);
  95. const classes = computed(() => {
  96. const prefixCls = componentName;
  97. return {
  98. [prefixCls]: true,
  99. [prefixCls + '__border']: props.border
  100. };
  101. });
  102. const relation = (child: ComponentInternalInstance): void => {
  103. if (child.proxy) {
  104. parent.children.push(child.proxy);
  105. }
  106. };
  107. relation(getCurrentInstance() as ComponentInternalInstance);
  108. const proxyData = reactive({
  109. openExpanded: false
  110. });
  111. // 获取 Dom 元素
  112. const wrapperRef: any = ref(null);
  113. const contentRef: any = ref(null);
  114. // 清除 willChange 减少性能浪费
  115. const onTransitionEnd = () => {
  116. const wrapperRefEle: any = document.getElementsByClassName('nut-collapse__item-wrapper')[0];
  117. if (wrapperRefEle) {
  118. wrapperRefEle.style.willChange = 'auto';
  119. }
  120. // const query = wx.createSelectorQuery();
  121. // query.select('#productServe').boundingClientRect();
  122. };
  123. // 手风琴模式
  124. const animation = () => {
  125. const wrapperRefEle: any = wrapperRef.value;
  126. const contentRefEle: any = contentRef.value;
  127. if (!wrapperRefEle || !contentRefEle) {
  128. return;
  129. }
  130. const offsetHeight = contentRefEle.offsetHeight || 'auto';
  131. if (offsetHeight) {
  132. const contentHeight = `${offsetHeight}px`;
  133. wrapperRefEle.style.willChange = 'height';
  134. wrapperRefEle.style.height = !proxyData.openExpanded ? 0 : contentHeight;
  135. }
  136. if (!proxyData.openExpanded) {
  137. onTransitionEnd();
  138. }
  139. };
  140. const open = () => {
  141. proxyData.openExpanded = !proxyData.openExpanded;
  142. animation();
  143. };
  144. const defaultOpen = () => {
  145. open();
  146. };
  147. const currentName = computed(() => props.name);
  148. const toggleOpen = () => {
  149. if (parent.props.accordion) {
  150. nextTick(() => {
  151. if (currentName.value == parent.props.modelValue) {
  152. open();
  153. } else {
  154. parent.changeVal(currentName.value);
  155. }
  156. });
  157. } else {
  158. parent.changeValAry(String(props.name));
  159. open();
  160. }
  161. };
  162. // 更改子组件展示
  163. const changeOpen = (bol: boolean) => {
  164. proxyData.openExpanded = bol;
  165. };
  166. const expanded = computed(() => {
  167. if (parent) {
  168. return parent.isExpanded(props.name);
  169. }
  170. return null;
  171. });
  172. watch(expanded, (value, oldValue) => {
  173. if (value) {
  174. proxyData.openExpanded = true;
  175. }
  176. });
  177. const init = () => {
  178. const { name } = props;
  179. const active = parent && parent.props.modelValue;
  180. nextTick(() => {
  181. if (typeof active == 'number' || typeof active == 'string') {
  182. if (name == active) {
  183. defaultOpen();
  184. }
  185. } else if (Object.values(active) instanceof Array) {
  186. const f = Object.values(active).filter((item) => item == name);
  187. if (f.length > 0) {
  188. defaultOpen();
  189. }
  190. }
  191. });
  192. };
  193. onMounted(() => {
  194. // const MutationObserver: any =
  195. // window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  196. var observer = new MutationObserver(() => {
  197. animation();
  198. });
  199. const ele = document.getElementsByClassName('nut-collapse__item-wrapper')[0];
  200. if (ele) {
  201. observer.observe(ele, {
  202. childList: true,
  203. subtree: true
  204. });
  205. }
  206. init();
  207. });
  208. const emptyContent = computed(() => {
  209. let ele = contentRef.value;
  210. let _class = '';
  211. if (!ele?.innerText) {
  212. _class = 'nut-collapse__item-wrapper__content--empty';
  213. }
  214. return _class;
  215. });
  216. return {
  217. classes,
  218. emptyContent,
  219. ...toRefs(proxyData),
  220. renderIcon,
  221. wrapperRef,
  222. contentRef,
  223. open,
  224. toggleOpen,
  225. changeOpen,
  226. animation
  227. };
  228. }
  229. });
  230. </script>