index.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <template>
  2. <view
  3. :class="[
  4. 'nut-collapse-item',
  5. { 'nut-collapse-item-left': classDirection == 'left' },
  6. { 'nut-collapse-item-icon': icon && icon != 'none' }
  7. ]"
  8. >
  9. <view
  10. :class="[
  11. 'collapse-item',
  12. { 'item-expanded': openExpanded },
  13. { 'nut-collapse-item-disabled': disabled }
  14. ]"
  15. @click="toggleOpen"
  16. >
  17. <view class="collapse-title">
  18. <view>
  19. <img
  20. v-if="
  21. titleIcon != '' &&
  22. titleIcon != 'none' &&
  23. titleIconPosition == 'left'
  24. "
  25. :src="titleIcon"
  26. :style="titleIconWH"
  27. class="titleIconLeft"
  28. />
  29. <view v-html="title"></view>
  30. <img
  31. v-if="
  32. titleIcon != '' &&
  33. titleIcon != 'none' &&
  34. titleIconPosition == 'right'
  35. "
  36. :src="titleIcon"
  37. :style="titleIconWH"
  38. class="titleIconRight"
  39. />
  40. </view>
  41. </view>
  42. <view v-if="subTitle" v-html="subTitle" class="subTitle"></view>
  43. <i
  44. v-if="icon && icon != 'none'"
  45. :class="[
  46. 'collapse-icon',
  47. { 'col-expanded': openExpanded },
  48. { 'collapse-icon-disabled': disabled }
  49. ]"
  50. :style="iconStyle"
  51. ></i>
  52. <i
  53. v-else-if="icon != 'none'"
  54. :class="[
  55. 'collapse-icon',
  56. { 'col-expanded': openExpanded },
  57. { 'collapse-icon-disabled': disabled }
  58. ]"
  59. ></i>
  60. </view>
  61. <view class="collapse-wrapper" ref="wrapperRef">
  62. <view class="collapse-content" ref="contentRef">
  63. <slot></slot>
  64. </view>
  65. </view>
  66. </view>
  67. </template>
  68. <script lang="ts">
  69. import {
  70. reactive,
  71. inject,
  72. toRefs,
  73. onMounted,
  74. ref,
  75. nextTick,
  76. computed,
  77. watch,
  78. getCurrentInstance,
  79. ComponentInternalInstance
  80. } from 'vue';
  81. import { createComponent } from '@/utils/create';
  82. const { create } = createComponent('collapse-item');
  83. export default create({
  84. props: {
  85. title: {
  86. type: String,
  87. default: ''
  88. },
  89. subTitle: {
  90. type: String,
  91. default: ''
  92. },
  93. disabled: {
  94. type: Boolean,
  95. default: false
  96. },
  97. name: {
  98. type: [Number, String],
  99. default: -1,
  100. required: true
  101. },
  102. collapseRef: {
  103. type: Object
  104. }
  105. },
  106. setup(props) {
  107. const collapse: any = inject('collapseParent');
  108. const parent: any = reactive(collapse);
  109. const relation = (child: ComponentInternalInstance): void => {
  110. if (child.proxy) {
  111. parent.children.push(child.proxy);
  112. }
  113. };
  114. relation(getCurrentInstance() as ComponentInternalInstance);
  115. const proxyData = reactive({
  116. openExpanded: false,
  117. classDirection: 'right',
  118. iconStyle: {
  119. width: '20px',
  120. height: '20px',
  121. 'background-image':
  122. 'url(https://img10.360buyimg.com/imagetools/jfs/t1/111306/10/17422/341/5f58aa0eEe9218dd6/28d76a42db334e31.png)',
  123. 'background-repeat': 'no-repeat',
  124. 'background-size': '100% 100%',
  125. transform: 'rotate(0deg)'
  126. }
  127. });
  128. const titleIconStyle = reactive({
  129. titleIcon: parent.titleIcon,
  130. titleIconPosition: parent.titleIconPosition,
  131. titleIconWH: {
  132. width: '13px',
  133. height: '13px'
  134. }
  135. });
  136. // 获取 Dom 元素
  137. const wrapperRef: any = ref(null);
  138. const contentRef: any = ref(null);
  139. // 清除 willChange 减少性能浪费
  140. const onTransitionEnd = () => {
  141. const wrapperRefEle: any = document.getElementsByClassName(
  142. 'collapse-wrapper'
  143. )[0];
  144. wrapperRefEle.style.willChange = 'auto';
  145. };
  146. // 手风琴模式
  147. const animation = () => {
  148. const wrapperRefEle: any = wrapperRef.value;
  149. const contentRefEle: any = contentRef.value;
  150. if (!wrapperRefEle || !contentRefEle) {
  151. return;
  152. }
  153. const offsetHeight = contentRefEle.offsetHeight;
  154. if (offsetHeight) {
  155. const contentHeight = `${offsetHeight}px`;
  156. wrapperRefEle.style.willChange = 'height';
  157. wrapperRefEle.style.height = !proxyData.openExpanded
  158. ? 0
  159. : contentHeight;
  160. if (parent.icon && parent.icon != 'none' && !proxyData.openExpanded) {
  161. proxyData.iconStyle['transform'] = 'rotate(0deg)';
  162. } else {
  163. proxyData.iconStyle['transform'] = 'rotate(' + parent.rotate + 'deg)';
  164. }
  165. }
  166. if (!proxyData.openExpanded) {
  167. onTransitionEnd();
  168. }
  169. };
  170. const open = () => {
  171. proxyData.openExpanded = !proxyData.openExpanded;
  172. animation();
  173. };
  174. const defaultOpen = () => {
  175. open();
  176. if (parent.icon && parent.icon != 'none') {
  177. proxyData['iconStyle']['transform'] =
  178. 'rotate(' + parent.rotate + 'deg)';
  179. }
  180. };
  181. const currentName = computed(() => props.name ?? index.value);
  182. const toggleOpen = () => {
  183. if (parent.accordion) {
  184. parent.children.forEach((item: any, index: number) => {
  185. if (currentName.value == item.name) {
  186. item.changeOpen(!item.openExpanded);
  187. } else {
  188. item.changeOpen(false);
  189. item.animation();
  190. }
  191. });
  192. nextTick(() => {
  193. parent.changeVal(currentName.value, !proxyData.openExpanded);
  194. animation();
  195. });
  196. } else {
  197. parent.changeValAry(props.name);
  198. open();
  199. }
  200. };
  201. // 更改子组件展示
  202. const changeOpen = (bol: boolean) => {
  203. proxyData.openExpanded = bol;
  204. };
  205. const expanded = computed(() => {
  206. if (parent) {
  207. return parent.isExpanded(props.name);
  208. }
  209. return null;
  210. });
  211. watch(expanded, (value, oldValue) => {
  212. if (value) {
  213. proxyData.openExpanded = true;
  214. }
  215. });
  216. onMounted(() => {
  217. const { name } = props;
  218. const active = parent && parent.value;
  219. if (typeof active == 'number' || typeof active == 'string') {
  220. if (name == active) {
  221. defaultOpen();
  222. }
  223. } else if (Object.values(active) instanceof Array) {
  224. const f = Object.values(active).filter(item => item == name);
  225. if (f.length > 0) {
  226. defaultOpen();
  227. }
  228. }
  229. proxyData.classDirection = parent.expandIconPosition;
  230. if (parent.icon && parent.icon != 'none') {
  231. proxyData.iconStyle['background-image'] = 'url(' + parent.icon + ')';
  232. }
  233. if (parent.iconWidth && parent.icon != 'none') {
  234. proxyData.iconStyle['width'] = parent.conWidth;
  235. }
  236. if (parent.iconHeght && parent.icon != 'none') {
  237. proxyData.iconStyle['height'] = parent.iconHeight;
  238. }
  239. });
  240. return {
  241. ...toRefs(proxyData),
  242. ...toRefs(parent),
  243. ...toRefs(titleIconStyle),
  244. wrapperRef,
  245. contentRef,
  246. open,
  247. toggleOpen,
  248. changeOpen,
  249. animation
  250. };
  251. }
  252. });
  253. </script>
  254. <style lang="scss" scoped>
  255. @import './index.scss';
  256. </style>