uni-collapse-item.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. <template>
  2. <view class="uni-collapse-item">
  3. <!-- onClick(!isOpen) -->
  4. <view
  5. @click="onClick(!isOpen)"
  6. class="uni-collapse-item__title"
  7. :class="{
  8. 'is-open': isOpen && titleBorder === 'auto',
  9. 'uni-collapse-item-border': titleBorder !== 'none',
  10. }"
  11. >
  12. <view class="uni-collapse-item__title-wrap">
  13. <slot name="title">
  14. <view
  15. class="uni-collapse-item__title-box"
  16. :class="{ 'is-disabled': disabled }"
  17. :style="{
  18. paddingLeft: `${leave * 5 * 2 + 10}rpx`,
  19. }"
  20. >
  21. <image
  22. v-if="thumb"
  23. :src="thumb"
  24. class="uni-collapse-item__title-img"
  25. />
  26. <text class="uni-collapse-item__title-text">{{ title }}</text>
  27. </view>
  28. </slot>
  29. </view>
  30. <view
  31. v-if="showArrow"
  32. :class="{
  33. 'uni-collapse-item__title-arrow-active': isOpen,
  34. 'uni-collapse-item--animation': showAnimation === true,
  35. }"
  36. class="uni-collapse-item__title-arrow"
  37. >
  38. <uni-icons
  39. :color="disabled ? '#ddd' : '#bbb'"
  40. size="14"
  41. type="bottom"
  42. />
  43. </view>
  44. </view>
  45. <view
  46. class="uni-collapse-item__wrap"
  47. :class="{ 'is--transition': showAnimation }"
  48. :style="{ height: isOpen ? 'auto' : 0 }"
  49. >
  50. <view
  51. :id="elId"
  52. ref="collapse--hook"
  53. class="uni-collapse-item__wrap-content"
  54. :class="{
  55. open: isheight,
  56. 'uni-collapse-item--border': border && isOpen,
  57. }"
  58. >
  59. <slot></slot>
  60. </view>
  61. </view>
  62. </view>
  63. </template>
  64. <script>
  65. // #ifdef APP-NVUE
  66. const dom = weex.requireModule("dom");
  67. // #endif
  68. /**
  69. * CollapseItem 折叠面板子组件
  70. * @description 折叠面板子组件
  71. * @property {String} title 标题文字
  72. * @property {String} thumb 标题左侧缩略图
  73. * @property {String} name 唯一标志符
  74. * @property {Boolean} open = [true|false] 是否展开组件
  75. * @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
  76. * @property {String} border = ['auto'|'show'|'none'] 是否显示分隔线
  77. * @property {Boolean} disabled = [true|false] 是否展开面板
  78. * @property {Boolean} showAnimation = [true|false] 开启动画
  79. * @property {Boolean} showArrow = [true|false] 是否显示右侧箭头
  80. */
  81. export default {
  82. name: "uniCollapseItem",
  83. props: {
  84. // 列表标题
  85. title: {
  86. type: String,
  87. default: "",
  88. },
  89. name: {
  90. type: [Number, String],
  91. default: "",
  92. },
  93. // 是否禁用
  94. disabled: {
  95. type: Boolean,
  96. default: false,
  97. },
  98. // #ifdef APP-PLUS
  99. // 是否显示动画,app 端默认不开启动画,卡顿严重
  100. showAnimation: {
  101. type: Boolean,
  102. default: false,
  103. },
  104. // #endif
  105. // #ifndef APP-PLUS
  106. // 是否显示动画
  107. showAnimation: {
  108. type: Boolean,
  109. default: true,
  110. },
  111. // #endif
  112. // 是否展开
  113. open: {
  114. type: Boolean,
  115. default: false,
  116. },
  117. // 缩略图
  118. thumb: {
  119. type: String,
  120. default: "",
  121. },
  122. // 标题分隔线显示类型
  123. titleBorder: {
  124. type: String,
  125. default: "auto",
  126. },
  127. border: {
  128. type: Boolean,
  129. default: true,
  130. },
  131. showArrow: {
  132. type: Boolean,
  133. default: true,
  134. },
  135. leave: {
  136. type: Number,
  137. default: 0,
  138. },
  139. },
  140. data() {
  141. // TODO 随机生生元素ID,解决百度小程序获取同一个元素位置信息的bug
  142. const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`;
  143. return {
  144. isOpen: false,
  145. isheight: null,
  146. height: 0,
  147. elId,
  148. nameSync: 0,
  149. };
  150. },
  151. watch: {
  152. open(val) {
  153. this.isOpen = val;
  154. this.onClick(val, "init");
  155. },
  156. },
  157. updated(e) {
  158. this.$nextTick(() => {
  159. this.init(true);
  160. });
  161. },
  162. created() {
  163. this.collapse = this.getCollapse();
  164. this.oldHeight = 0;
  165. this.onClick(this.open, "init");
  166. },
  167. // #ifndef VUE3
  168. // TODO vue2
  169. destroyed() {
  170. if (this.__isUnmounted) return;
  171. this.uninstall();
  172. },
  173. // #endif
  174. // #ifdef VUE3
  175. // TODO vue3
  176. unmounted() {
  177. this.__isUnmounted = true;
  178. this.uninstall();
  179. },
  180. // #endif
  181. mounted() {
  182. if (!this.collapse) return;
  183. if (this.name !== "") {
  184. this.nameSync = this.name;
  185. } else {
  186. this.nameSync = this.collapse.childrens.length + "";
  187. }
  188. if (this.collapse.names.indexOf(this.nameSync) === -1) {
  189. this.collapse.names.push(this.nameSync);
  190. } else {
  191. console.warn(`name 值 ${this.nameSync} 重复`);
  192. }
  193. if (this.collapse.childrens.indexOf(this) === -1) {
  194. this.collapse.childrens.push(this);
  195. }
  196. this.init();
  197. },
  198. methods: {
  199. init(type) {
  200. // #ifndef APP-NVUE
  201. this.getCollapseHeight(type);
  202. // #endif
  203. // #ifdef APP-NVUE
  204. this.getNvueHwight(type);
  205. // #endif
  206. },
  207. uninstall() {
  208. if (this.collapse) {
  209. this.collapse.childrens.forEach((item, index) => {
  210. if (item === this) {
  211. this.collapse.childrens.splice(index, 1);
  212. }
  213. });
  214. this.collapse.names.forEach((item, index) => {
  215. if (item === this.nameSync) {
  216. this.collapse.names.splice(index, 1);
  217. }
  218. });
  219. }
  220. },
  221. onClick(isOpen, type) {
  222. if (this.disabled) return;
  223. this.isOpen = isOpen;
  224. if (this.isOpen && this.collapse) {
  225. this.collapse.setAccordion(this);
  226. }
  227. if (type !== "init") {
  228. this.collapse.onChange(isOpen, this);
  229. }
  230. },
  231. getCollapseHeight(type, index = 0) {
  232. const views = uni.createSelectorQuery().in(this);
  233. views
  234. .select(`#${this.elId}`)
  235. .fields(
  236. {
  237. size: true,
  238. },
  239. (data) => {
  240. // TODO 百度中可能获取不到节点信息 ,需要循环获取
  241. if (index >= 10) return;
  242. if (!data) {
  243. index++;
  244. this.getCollapseHeight(false, index);
  245. return;
  246. }
  247. // #ifdef APP-NVUE
  248. this.height = data.height + 1;
  249. // #endif
  250. // #ifndef APP-NVUE
  251. this.height = data.height;
  252. // #endif
  253. this.isheight = true;
  254. if (type) return;
  255. this.onClick(this.isOpen, "init");
  256. }
  257. )
  258. .exec();
  259. },
  260. getNvueHwight(type) {
  261. const result = dom.getComponentRect(
  262. this.$refs["collapse--hook"],
  263. (option) => {
  264. if (option && option.result && option.size) {
  265. // #ifdef APP-NVUE
  266. this.height = option.size.height + 1;
  267. // #endif
  268. // #ifndef APP-NVUE
  269. this.height = option.size.height;
  270. // #endif
  271. this.isheight = true;
  272. if (type) return;
  273. this.onClick(this.open, "init");
  274. }
  275. }
  276. );
  277. },
  278. /**
  279. * 获取父元素实例
  280. */
  281. getCollapse(name = "uniCollapse") {
  282. let parent = this.$parent;
  283. let parentName = parent.$options.name;
  284. while (parentName !== name) {
  285. parent = parent.$parent;
  286. if (!parent) return false;
  287. parentName = parent.$options.name;
  288. }
  289. return parent;
  290. },
  291. },
  292. };
  293. </script>
  294. <style lang="scss">
  295. .uni-collapse-item {
  296. /* #ifndef APP-NVUE */
  297. box-sizing: border-box;
  298. /* #endif */
  299. &__title {
  300. /* #ifndef APP-NVUE */
  301. display: flex;
  302. width: 100%;
  303. box-sizing: border-box;
  304. /* #endif */
  305. flex-direction: row;
  306. align-items: center;
  307. transition: border-bottom-color 0.3s;
  308. // transition-property: border-bottom-color;
  309. // transition-duration: 5s;
  310. &-wrap {
  311. width: 100%;
  312. flex: 1;
  313. }
  314. &-box {
  315. padding: 20rpx 0;
  316. /* #ifndef APP-NVUE */
  317. display: flex;
  318. width: 100%;
  319. box-sizing: border-box;
  320. /* #endif */
  321. flex-direction: row;
  322. justify-content: space-between;
  323. align-items: center;
  324. background-color: #fff;
  325. color: #303133;
  326. font-size: 13px;
  327. font-weight: 500;
  328. /* #ifdef H5 */
  329. cursor: pointer;
  330. outline: none;
  331. /* #endif */
  332. &.is-disabled {
  333. .uni-collapse-item__title-text {
  334. color: #999;
  335. }
  336. }
  337. }
  338. &.is-open {
  339. border-bottom-color: transparent;
  340. }
  341. &-img {
  342. height: 22px;
  343. width: 22px;
  344. margin-right: 10px;
  345. }
  346. &-text {
  347. flex: 1;
  348. font-size: 14px;
  349. /* #ifndef APP-NVUE */
  350. white-space: nowrap;
  351. color: inherit;
  352. /* #endif */
  353. /* #ifdef APP-NVUE */
  354. lines: 1;
  355. /* #endif */
  356. overflow: hidden;
  357. text-overflow: ellipsis;
  358. }
  359. &-arrow {
  360. /* #ifndef APP-NVUE */
  361. display: flex;
  362. box-sizing: border-box;
  363. /* #endif */
  364. align-items: center;
  365. justify-content: center;
  366. width: 20px;
  367. height: 20px;
  368. margin-right: 10px;
  369. transform: rotate(0deg);
  370. &-active {
  371. transform: rotate(-180deg);
  372. }
  373. }
  374. }
  375. &__wrap {
  376. /* #ifndef APP-NVUE */
  377. will-change: height;
  378. box-sizing: border-box;
  379. /* #endif */
  380. background-color: #fff;
  381. overflow: hidden;
  382. position: relative;
  383. height: 0;
  384. &.is--transition {
  385. // transition: all 0.3s;
  386. transition-property: height, border-bottom-width;
  387. transition-duration: 0.3s;
  388. /* #ifndef APP-NVUE */
  389. will-change: height;
  390. /* #endif */
  391. }
  392. &-content {
  393. position: absolute;
  394. font-size: 13px;
  395. color: #303133;
  396. // transition: height 0.3s;
  397. border-bottom-color: transparent;
  398. border-bottom-style: solid;
  399. border-bottom-width: 0;
  400. &.open {
  401. position: relative;
  402. }
  403. }
  404. }
  405. &--animation {
  406. transition-property: transform;
  407. transition-duration: 0.3s;
  408. transition-timing-function: ease;
  409. }
  410. }
  411. </style>