z-tabs.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. <!-- z-tabs v0.2.5 by-ZXLee -->
  2. <!-- github地址:https://github.com/SmileZXLee/uni-z-tabs -->
  3. <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?name=z-tabs -->
  4. <!-- 反馈QQ群:790460711 -->
  5. <template name="z-tabs">
  6. <view class="z-tabs-conatiner" :style="[{background:bgColor}, tabsStyle]">
  7. <view class="z-tabs-left">
  8. <slot name="left" />
  9. </view>
  10. <view ref="z-tabs-scroll-view-conatiner" class="z-tabs-scroll-view-conatiner">
  11. <scroll-view ref="z-tabs-scroll-view" class="z-tabs-scroll-view" :scroll-x="true" :scroll-left="scrollLeft"
  12. :show-scrollbar="false" :scroll-with-animation="isFirstLoaded" @scroll="scroll">
  13. <view class="z-tabs-list-container" :style="[tabsListStyle]">
  14. <view class="z-tabs-list" :style="[tabsListStyle, {marginTop: -finalBottomSpace+'px'}]"
  15. style="align-items: center;">
  16. <view :ref="`z-tabs-item-${index}`" :id="`z-tabs-item-${index}`" class="z-tabs-item"
  17. :style="[tabStyle]" v-for="(item,index) in list" :key="index"
  18. @click="tabsClick(index,item)">
  19. <view v-if="item.is_dot" :style="{background:item.dot_color}"
  20. style="width: 14rpx;height: 14rpx;margin-right: 10rpx;border-radius: 50%;">
  21. </view>
  22. <view class="z-tabs-item-title-container">
  23. <text :class="{'z-tabs-item-title':true,'z-tabs-item-title-disabled':item.disabled}"
  24. :style="[{color:item.disabled?disabledColor:(currentIndex===index?activeColor:inactiveColor)},item.disabled?disabledStyle:(currentIndex===index?activeStyle:inactiveStyle)]">
  25. {{item[nameKey]||item}}
  26. </text>
  27. <text v-if="item.badge&&_formatCount(item.badge.count).length" class="z-tabs-item-badge"
  28. :style="[badgeStyle]">{{_formatCount(item.badge.count)}}</text>
  29. </view>
  30. </view>
  31. </view>
  32. <view class="z-tabs-bottom"
  33. :style="[{width: tabsContainerWidth+'px', bottom: finalBottomSpace+'px'}]">
  34. <view ref="z-tabs-bottom-dot" class="z-tabs-bottom-dot" <!-- #ifndef APP-NVUE -->
  35. :style="[{transform:`translateX(${bottomDotX}px)`,transition:dotTransition,background:activeColor},finalDotStyle]"
  36. <!-- #endif -->
  37. <!-- #ifdef APP-NVUE -->
  38. :style="[{background:activeColor},finalDotStyle]"
  39. <!-- #endif -->
  40. />
  41. </view>
  42. </view>
  43. </scroll-view>
  44. </view>
  45. <view class="z-tabs-right">
  46. <slot name="right" />
  47. </view>
  48. </view>
  49. </template>
  50. <script>
  51. // #ifdef APP-NVUE
  52. const weexDom = weex.requireModule('dom');
  53. const weexAnimation = weex.requireModule('animation');
  54. // #endif
  55. import zTabsConfig from './config/index'
  56. //获取默认配置信息
  57. function _gc(key, defaultValue) {
  58. let config = null;
  59. if (zTabsConfig && Object.keys(zTabsConfig).length) {
  60. config = zTabsConfig;
  61. } else {
  62. return defaultValue;
  63. }
  64. const value = config[_toKebab(key)];
  65. return value === undefined ? defaultValue : value;
  66. }
  67. //驼峰转短横线
  68. function _toKebab(value) {
  69. return value.replace(/([A-Z])/g, "-$1").toLowerCase();
  70. }
  71. /**
  72. * z-tabs 标签
  73. * @description 一个简单轻量的tabs标签,全平台兼容,支持nvue、vue3
  74. * @tutorial https://ext.dcloud.net.cn/plugin?name=z-tabs
  75. * @property {Array} list 数据源数组,支持形如['tab1','tab2']的格式或[{name:'tab1',value:1}]的格式
  76. * @property {Number|String} current 当前选中的index,默认为0
  77. * @property {Number|String} scroll-count list数组长度超过scrollCount时滚动显示(不自动铺满全屏),默认为5
  78. * @property {Number|String} tab-width 自定义每个tab的宽度,默认为0,即代表根据内容自动撑开,单位rpx,支持传100、"100px"或"100rpx"
  79. * @property {Number|String} bar-width 滑块宽度,单位rpx,支持传100、"100px"或"100rpx"
  80. * @property {Number|String} bar-height 滑块高度,单位rpx,支持传100、"100px"或"100rpx"
  81. * @property {Object} bar-style 滑块样式,其中的width和height将被bar-width和bar-height覆盖
  82. * @property {Number|String} bottom-space tabs与底部的间距,单位rpx,支持传100、"100px"或"100rpx"
  83. * @property {String} bar-animate-mode 切换tab时滑块动画模式,与swiper联动时有效,点击切换tab时无效,必须调用setDx。默认为line,即切换tab时滑块宽度保持不变,线性运动。可选值为worm,即为类似毛毛虫蠕动效果
  84. * @property {String} name-key list中item的name(标题)的key,默认为name
  85. * @property {String} value-key list中item的value的key,默认为value
  86. * @property {String} active-color 激活状态tab的颜色
  87. * @property {String} inactive-color 未激活状态tab的颜色
  88. * @property {String} disabled-color 禁用状态tab的颜色
  89. * @property {Object} active-style 激活状态tab的样式
  90. * @property {Object} inactive-style 未激活状态tab的样式
  91. * @property {Object} disabled-style 禁用状态tab的样式
  92. * @property {Number|String} badge-max-count 徽标数最大数字限制,超过这个数字将变成badge-max-count+,默认为99
  93. * @property {Object} badge-style 徽标样式,例如可自定义背景色,字体等等
  94. * @property {String} bg-color z-tabs背景色
  95. * @property {Object} tabs-style z-tabs样式
  96. * @property {Boolean} init-trigger-change 初始化时是否自动触发change事件
  97. * @event {Function(index,value)} change tabs改变时触发,index:当前切换到的index;value:当前切换到的value
  98. * @example <z-tabs :list="list"></z-tabs>
  99. */
  100. export default {
  101. name: 'z-tabs',
  102. data() {
  103. return {
  104. currentIndex: 0,
  105. currentSwiperIndex: 0,
  106. bottomDotX: -1,
  107. bottomDotXForIndex: 0,
  108. showBottomDot: false,
  109. shouldSetDx: true,
  110. barCalcedWidth: 0,
  111. pxBarWidth: 0,
  112. scrollLeft: 0,
  113. tabsSuperWidth: uni.upx2px(750),
  114. tabsWidth: uni.upx2px(750),
  115. tabsHeight: uni.upx2px(80),
  116. tabsLeft: 0,
  117. tabsContainerWidth: 0,
  118. itemNodeInfos: [],
  119. isFirstLoaded: false,
  120. currentScrollLeft: 0,
  121. changeTriggerFailed: false,
  122. currentChanged: false
  123. };
  124. },
  125. props: {
  126. //数据源数组,支持形如['tab1','tab2']的格式或[{name:'tab1',value:1}]的格式
  127. list: {
  128. type: Array,
  129. default: function() {
  130. return [];
  131. }
  132. },
  133. //当前选中的index
  134. current: {
  135. type: [Number, String],
  136. default: _gc('current', 0)
  137. },
  138. //list数组长度超过scrollCount时滚动显示(不自动铺满全屏)
  139. scrollCount: {
  140. type: [Number, String],
  141. default: _gc('scrollCount', 5)
  142. },
  143. //z-tabs样式
  144. tabsStyle: {
  145. type: Object,
  146. default: function() {
  147. return _gc('tabsStyle', {})
  148. }
  149. },
  150. //自定义每个tab的宽度,默认为0,即代表根据内容自动撑开,单位rpx,支持传100、"100px"或"100rpx"
  151. tabWidth: {
  152. type: [Number, String],
  153. default: _gc('tabWidth', 0)
  154. },
  155. //滑块宽度,单位rpx,支持传100、"100px"或"100rpx"
  156. barWidth: {
  157. type: [Number, String],
  158. default: _gc('barWidth', 45)
  159. },
  160. //滑块高度,单位rpx,支持传100、"100px"或"100rpx"
  161. barHeight: {
  162. type: [Number, String],
  163. default: _gc('barHeight', 8)
  164. },
  165. //滑块样式,其中的width和height将被barWidth和barHeight覆盖
  166. barStyle: {
  167. type: Object,
  168. default: function() {
  169. return _gc('barStyle', {});
  170. }
  171. },
  172. //tabs与底部的间距,单位rpx,支持传100、"100px"或"100rpx"
  173. bottomSpace: {
  174. type: [Number, String],
  175. default: _gc('bottomSpace', 8)
  176. },
  177. //切换tab时滑块动画模式,与swiper联动时有效,点击切换tab时无效,必须调用setDx。默认为line,即切换tab时滑块宽度保持不变,线性运动。可选值为worm,即为类似毛毛虫蠕动效果
  178. barAnimateMode: {
  179. type: String,
  180. default: _gc('barAnimateMode', 'line')
  181. },
  182. //list中item的name(标题)的key
  183. nameKey: {
  184. type: String,
  185. default: _gc('nameKey', 'name')
  186. },
  187. //list中item的value的key
  188. valueKey: {
  189. type: String,
  190. default: _gc('valueKey', 'value')
  191. },
  192. //激活状态tab的颜色
  193. activeColor: {
  194. type: String,
  195. default: _gc('activeColor', '#007AFF')
  196. },
  197. //未激活状态tab的颜色
  198. inactiveColor: {
  199. type: String,
  200. default: _gc('inactiveColor', '#333333')
  201. },
  202. //禁用状态tab的颜色
  203. disabledColor: {
  204. type: String,
  205. default: _gc('disabledColor', '#bbbbbb')
  206. },
  207. //激活状态tab的样式
  208. activeStyle: {
  209. type: Object,
  210. default: function() {
  211. return _gc('activeStyle', {});
  212. }
  213. },
  214. //未激活状态tab的样式
  215. inactiveStyle: {
  216. type: Object,
  217. default: function() {
  218. return _gc('inactiveStyle', {});
  219. }
  220. },
  221. //禁用状态tab的样式
  222. disabledStyle: {
  223. type: Object,
  224. default: function() {
  225. return _gc('disabledStyle', {});
  226. }
  227. },
  228. //z-tabs背景色
  229. bgColor: {
  230. type: String,
  231. default: ''
  232. },
  233. //徽标数最大数字限制,超过这个数字将变成badgeMaxCount+
  234. badgeMaxCount: {
  235. type: [Number, String],
  236. default: _gc('badgeMaxCount', 99)
  237. },
  238. //徽标样式,例如可自定义背景色,字体等等
  239. badgeStyle: {
  240. type: Object,
  241. default: function() {
  242. return _gc('badgeStyle', {})
  243. }
  244. },
  245. //初始化时是否自动触发change事件
  246. initTriggerChange: {
  247. type: Boolean,
  248. default: _gc('initTriggerChange', false)
  249. },
  250. dot_color: {
  251. type: String,
  252. default: ''
  253. },
  254. is_dot: {
  255. type: Boolean,
  256. default: false
  257. },
  258. },
  259. mounted() {
  260. this.updateSubviewLayout();
  261. },
  262. watch: {
  263. current: {
  264. handler(newVal) {
  265. this.currentChanged && this._lockDx();
  266. this.currentIndex = newVal;
  267. this._preUpdateDotPosition(this.currentIndex);
  268. if (this.initTriggerChange) {
  269. if (newVal < this.list.length) {
  270. this.$emit('change', newVal, this.list[newVal][this.valueKey]);
  271. } else {
  272. this.changeTriggerFailed = true;
  273. }
  274. }
  275. this.currentChanged = true;
  276. },
  277. immediate: true
  278. },
  279. list: {
  280. handler(newVal) {
  281. this._handleListChange(newVal);
  282. },
  283. immediate: false
  284. },
  285. bottomDotX(newVal) {
  286. if (newVal >= 0) {
  287. // #ifndef APP-NVUE
  288. this.showBottomDot = true;
  289. // #endif
  290. this.$nextTick(() => {
  291. // #ifdef APP-NVUE
  292. weexAnimation.transition(this.$refs['z-tabs-bottom-dot'], {
  293. styles: {
  294. transform: `translateX(${newVal}px)`
  295. },
  296. duration: this.showAnimate ? 200 : 0,
  297. delay: 0
  298. })
  299. setTimeout(() => {
  300. this.showBottomDot = true;
  301. }, 10)
  302. // #endif
  303. })
  304. }
  305. },
  306. finalBarWidth: {
  307. handler(newVal) {
  308. this.barCalcedWidth = newVal;
  309. this.pxBarWidth = this.barCalcedWidth;
  310. },
  311. immediate: true
  312. },
  313. currentIndex: {
  314. handler(newVal) {
  315. this.currentSwiperIndex = newVal;
  316. },
  317. immediate: true
  318. }
  319. },
  320. computed: {
  321. shouldScroll() {
  322. return this.list.length > this.scrollCount;
  323. },
  324. finalTabsHeight() {
  325. return this.tabsHeight;
  326. },
  327. tabStyle() {
  328. const stl = this.shouldScroll ? {
  329. 'flex-shrink': 0
  330. } : {
  331. 'flex': 1
  332. };
  333. if (this.finalTabWidth > 0) {
  334. stl['width'] = this.finalTabWidth + 'px';
  335. } else {
  336. delete stl.width;
  337. }
  338. return stl;
  339. },
  340. tabsListStyle() {
  341. return this.shouldScroll ? {} : {
  342. 'flex': 1
  343. };
  344. },
  345. showAnimate() {
  346. return this.isFirstLoaded && !this.shouldSetDx;
  347. },
  348. dotTransition() {
  349. return this.showAnimate ? 'transform .2s linear' : 'none';
  350. },
  351. finalDotStyle() {
  352. return {
  353. ...this.barStyle,
  354. width: this.barCalcedWidth + 'px',
  355. height: this.finalBarHeight + 'px',
  356. opacity: this.showBottomDot ? 1 : 0
  357. };
  358. },
  359. finalTabWidth() {
  360. return this._convertTextToPx(this.tabWidth);
  361. },
  362. finalBarWidth() {
  363. return this._convertTextToPx(this.barWidth);
  364. },
  365. finalBarHeight() {
  366. return this._convertTextToPx(this.barHeight);
  367. },
  368. finalBottomSpace() {
  369. return this._convertTextToPx(this.bottomSpace);
  370. }
  371. },
  372. methods: {
  373. //根据swiper的@transition实时更新底部dot位置
  374. setDx(dx) {
  375. if (!this.shouldSetDx) return;
  376. const isLineMode = this.barAnimateMode === 'line';
  377. const isWormMode = this.barAnimateMode === 'worm';
  378. let dxRate = dx / this.tabsSuperWidth;
  379. this.currentSwiperIndex = this.currentIndex + parseInt(dxRate);
  380. const isRight = dxRate > 0;
  381. const barWidth = this.pxBarWidth;
  382. if (this.currentSwiperIndex !== this.currentIndex) {
  383. dxRate = dxRate - (this.currentSwiperIndex - this.currentIndex);
  384. const currentNode = this.itemNodeInfos[this.currentSwiperIndex];
  385. if (!!currentNode) {
  386. this.bottomDotXForIndex = this._getBottomDotX(currentNode, barWidth);
  387. }
  388. }
  389. const currentIndex = this.currentSwiperIndex;
  390. let nextIndex = currentIndex + (isRight ? 1 : -1);
  391. nextIndex = Math.max(0, nextIndex);
  392. nextIndex = Math.min(nextIndex, this.itemNodeInfos.length - 1);
  393. const currentNodeInfo = this.itemNodeInfos[currentIndex];
  394. const nextNodeInfo = this.itemNodeInfos[nextIndex];
  395. const nextBottomX = this._getBottomDotX(nextNodeInfo, barWidth);
  396. if (isLineMode) {
  397. this.bottomDotX = this.bottomDotXForIndex + (nextBottomX - this.bottomDotXForIndex) * Math.abs(dxRate);
  398. } else if (isWormMode) {
  399. if ((isRight && currentIndex >= this.itemNodeInfos.length - 1) || (!isRight && currentIndex <= 0))
  400. return;
  401. const spaceOffset = isRight ? nextNodeInfo.right - currentNodeInfo.left : currentNodeInfo.right -
  402. nextNodeInfo.left;
  403. let barCalcedWidth = barWidth + spaceOffset * Math.abs(dxRate);
  404. if (isRight) {
  405. if (barCalcedWidth > nextBottomX - this.bottomDotX + barWidth) {
  406. const barMinusWidth = barWidth + spaceOffset * (1 - dxRate);
  407. this.bottomDotX = this.bottomDotXForIndex + (barCalcedWidth - barMinusWidth) / 2;
  408. barCalcedWidth = barMinusWidth;
  409. }
  410. } else if (!isRight) {
  411. if (barCalcedWidth > this.bottomDotXForIndex + barWidth - nextBottomX) {
  412. const barMinusWidth = barWidth + spaceOffset * (1 + dxRate);
  413. barCalcedWidth = barMinusWidth;
  414. this.bottomDotX = nextBottomX;
  415. } else {
  416. this.bottomDotX = this.bottomDotXForIndex - (barCalcedWidth - barWidth);
  417. }
  418. }
  419. barCalcedWidth = Math.max(barCalcedWidth, barWidth);
  420. this.barCalcedWidth = barCalcedWidth;
  421. }
  422. },
  423. //在swiper的@animationfinish中通知z-tabs结束多setDx的锁定,若在父组件中调用了setDx,则必须调用unlockDx
  424. unlockDx() {
  425. this.$nextTick(() => {
  426. setTimeout(() => {
  427. this.shouldSetDx = true;
  428. }, 10)
  429. })
  430. },
  431. //更新z-tabs内部布局
  432. updateSubviewLayout(tryCount = 0) {
  433. this.$nextTick(() => {
  434. let delayTime = 10;
  435. // #ifdef APP-NVUE || MP-BAIDU
  436. delayTime = 50;
  437. // #endif
  438. setTimeout(() => {
  439. this._getNodeClientRect('.z-tabs-scroll-view-conatiner').then(res => {
  440. if (res) {
  441. if (!res[0].width && tryCount < 10) {
  442. setTimeout(() => {
  443. tryCount++;
  444. this.updateSubviewLayout(tryCount);
  445. }, 50);
  446. return;
  447. }
  448. this.tabsWidth = res[0].width;
  449. this.tabsHeight = res[0].height;
  450. this.tabsLeft = res[0].left;
  451. this._handleListChange(this.list);
  452. }
  453. })
  454. this._getNodeClientRect('.z-tabs-conatiner').then(res => {
  455. if (res && res[0].width) {
  456. this.tabsSuperWidth = res[0].width;
  457. }
  458. })
  459. }, delayTime)
  460. })
  461. },
  462. //点击了tabs
  463. tabsClick(index, item) {
  464. if (item.disabled) return;
  465. if (this.currentIndex != index) {
  466. this.shouldSetDx = false;
  467. this.$emit('change', index, item[this.valueKey]);
  468. this.currentIndex = index;
  469. this._preUpdateDotPosition(index);
  470. } else {
  471. this.$emit('secondClick', index, item[this.valueKey]);
  472. }
  473. },
  474. //scroll-view滚动
  475. scroll(e) {
  476. this.currentScrollLeft = e.detail.scrollLeft;
  477. },
  478. //锁定dx,用于避免在swiper被动触发滚动时候执行setDx中的代码
  479. _lockDx() {
  480. this.shouldSetDx = false;
  481. },
  482. //更新底部dot位置之前的预处理
  483. _preUpdateDotPosition(index) {
  484. // #ifndef APP-NVUE
  485. this.$nextTick(() => {
  486. uni.createSelectorQuery().in(this).select(".z-tabs-scroll-view").fields({
  487. scrollOffset: true
  488. }, data => {
  489. if (data) {
  490. this.currentScrollLeft = data.scrollLeft;
  491. this._updateDotPosition(index);
  492. } else {
  493. this._updateDotPosition(index);
  494. }
  495. }).exec();
  496. })
  497. // #endif
  498. // #ifdef APP-NVUE
  499. this._updateDotPosition(index);
  500. // #endif
  501. },
  502. //更新底部dot位置
  503. _updateDotPosition(index) {
  504. if (index >= this.itemNodeInfos.length) return;
  505. this.$nextTick(async () => {
  506. let node = this.itemNodeInfos[index];
  507. let offset = 0;
  508. let tabsContainerWidth = this.tabsContainerWidth;
  509. if (JSON.stringify(this.activeStyle) !== '{}') {
  510. const nodeRes = await this._getNodeClientRect(`#z-tabs-item-${index}`, true);
  511. if (nodeRes) {
  512. node = nodeRes[0];
  513. offset = this.currentScrollLeft;
  514. this.tabsHeight = Math.max(node.height + uni.upx2px(28), this.tabsHeight);
  515. tabsContainerWidth = 0;
  516. for (let i = 0; i < this.itemNodeInfos.length; i++) {
  517. let oldNode = this.itemNodeInfos[i];
  518. tabsContainerWidth += i === index ? node.width : oldNode.width;
  519. }
  520. }
  521. }
  522. this.bottomDotX = this._getBottomDotX(node, this.finalBarWidth, offset);
  523. this.bottomDotXForIndex = this.bottomDotX;
  524. if (this.tabsWidth) {
  525. setTimeout(() => {
  526. let scrollLeft = this.bottomDotX - this.tabsWidth / 2 + this
  527. .finalBarWidth / 2;
  528. scrollLeft = Math.max(0, scrollLeft);
  529. if (tabsContainerWidth) {
  530. scrollLeft = Math.min(scrollLeft, tabsContainerWidth - this.tabsWidth +
  531. 10);
  532. }
  533. if (this.shouldScroll && tabsContainerWidth > this.tabsWidth) {
  534. this.scrollLeft = scrollLeft;
  535. }
  536. this.$nextTick(() => {
  537. this.isFirstLoaded = true;
  538. })
  539. }, 200)
  540. }
  541. })
  542. },
  543. // 处理list改变
  544. _handleListChange(newVal) {
  545. this.$nextTick(async () => {
  546. if (newVal.length) {
  547. let itemNodeInfos = [];
  548. let tabsContainerWidth = 0;
  549. let delayTime = 0;
  550. // #ifdef MP-BAIDU
  551. delayTime = 100;
  552. // #endif
  553. setTimeout(async () => {
  554. for (let i = 0; i < newVal.length; i++) {
  555. const nodeRes = await this._getNodeClientRect(`#z-tabs-item-${i}`,
  556. true);
  557. if (nodeRes) {
  558. const node = nodeRes[0];
  559. node.left += this.currentScrollLeft;
  560. itemNodeInfos.push(node);
  561. tabsContainerWidth += node.width;
  562. }
  563. if (i === this.currentIndex) {
  564. this.itemNodeInfos = itemNodeInfos;
  565. this.tabsContainerWidth = tabsContainerWidth;
  566. this._updateDotPosition(this.currentIndex);
  567. }
  568. }
  569. this.itemNodeInfos = itemNodeInfos;
  570. this.tabsContainerWidth = tabsContainerWidth;
  571. this._updateDotPosition(this.currentIndex);
  572. }, delayTime)
  573. }
  574. })
  575. if (this.initTriggerChange && this.changeTriggerFailed && newVal.length) {
  576. if (this.current < newVal.length) {
  577. this.$emit('change', this.current, newVal[this.current][this.valueKey]);
  578. }
  579. }
  580. },
  581. //根据node获取bottomX
  582. _getBottomDotX(node, barWidth = this.finalBarWidth, offset = 0) {
  583. return node.left + node.width / 2 - barWidth / 2 + offset - this.tabsLeft;
  584. },
  585. //获取节点信息
  586. _getNodeClientRect(select, withRefArr = false) {
  587. // #ifdef APP-NVUE
  588. select = select.replace('.', '').replace('#', '');
  589. const ref = withRefArr ? this.$refs[select][0] : this.$refs[select];
  590. return new Promise((resolve, reject) => {
  591. if (ref) {
  592. weexDom.getComponentRect(ref, option => {
  593. if (option && option.result) {
  594. resolve([option.size]);
  595. } else resolve(false);
  596. })
  597. } else resolve(false);
  598. });
  599. return;
  600. // #endif
  601. const res = uni.createSelectorQuery().in(this);
  602. res.select(select).boundingClientRect();
  603. return new Promise((resolve, reject) => {
  604. res.exec(data => {
  605. resolve((data && data != '' && data != undefined && data.length) ? data : false);
  606. });
  607. });
  608. },
  609. //格式化badge中的count
  610. _formatCount(count) {
  611. if (!count) return '';
  612. if (count > this.badgeMaxCount) {
  613. return this.badgeMaxCount + '+';
  614. }
  615. return count.toString();
  616. },
  617. //将文本的px或者rpx转为px的值
  618. _convertTextToPx(text) {
  619. const dataType = Object.prototype.toString.call(text);
  620. if (dataType === '[object Number]') {
  621. return uni.upx2px(text);
  622. }
  623. let isRpx = false;
  624. if (text.indexOf('rpx') !== -1 || text.indexOf('upx') !== -1) {
  625. text = text.replace('rpx', '').replace('upx', '');
  626. isRpx = true;
  627. } else if (text.indexOf('px') !== -1) {
  628. text = text.replace('px', '');
  629. } else {
  630. text = uni.upx2px(text);
  631. }
  632. if (!isNaN(text)) {
  633. if (isRpx) return Number(uni.upx2px(text));
  634. return Number(text);
  635. }
  636. return 0;
  637. }
  638. }
  639. }
  640. </script>
  641. <style scoped>
  642. .z-tabs-conatiner {
  643. /* #ifndef APP-NVUE */
  644. overflow: hidden;
  645. display: flex;
  646. width: 100%;
  647. /* #endif */
  648. /* #ifdef APP-NVUE */
  649. width: 750rpx;
  650. /* #endif */
  651. flex-direction: row;
  652. height: 80rpx;
  653. }
  654. .z-tabs-scroll-view-conatiner {
  655. flex: 1;
  656. position: relative;
  657. /* #ifndef APP-NVUE */
  658. display: flex;
  659. height: 100%;
  660. width: 100%;
  661. /* #endif */
  662. flex-direction: row;
  663. }
  664. /* #ifndef APP-NVUE */
  665. .z-tabs-scroll-view ::-webkit-scrollbar {
  666. display: none;
  667. -webkit-appearance: none;
  668. width: 0 !important;
  669. height: 0 !important;
  670. background: transparent;
  671. }
  672. /* #endif */
  673. .z-tabs-scroll-view {
  674. flex-direction: row;
  675. position: absolute;
  676. left: 0;
  677. top: 0;
  678. right: 0;
  679. bottom: 0;
  680. /* #ifndef APP-NVUE */
  681. width: 100%;
  682. height: 100%;
  683. /* #endif */
  684. flex: 1;
  685. }
  686. .z-tabs-list-container {
  687. position: relative;
  688. /* #ifndef APP-NVUE */
  689. height: 100%;
  690. /* #endif */
  691. }
  692. .z-tabs-list,
  693. .z-tabs-list-container {
  694. /* #ifndef APP-NVUE */
  695. display: flex;
  696. /* #endif */
  697. flex-direction: row;
  698. }
  699. .z-tabs-item {
  700. /* #ifndef APP-NVUE */
  701. display: flex;
  702. /* #endif */
  703. flex-direction: row;
  704. justify-content: center;
  705. align-items: center;
  706. padding: 0px 20rpx;
  707. }
  708. .z-tabs-item-title-container {
  709. /* #ifndef APP-NVUE */
  710. display: flex;
  711. /* #endif */
  712. flex-direction: row;
  713. align-items: center;
  714. }
  715. .z-tabs-item-title {
  716. font-size: 30rpx;
  717. }
  718. .z-tabs-item-title-disabled {
  719. /* #ifndef APP-NVUE */
  720. cursor: not-allowed;
  721. /* #endif */
  722. }
  723. .z-tabs-item-badge {
  724. margin-left: 8rpx;
  725. background-color: #ec5b56;
  726. color: white;
  727. font-size: 22rpx;
  728. border-radius: 100px;
  729. padding: 0rpx 10rpx;
  730. }
  731. .z-tabs-bottom {
  732. position: absolute;
  733. bottom: 0;
  734. left: 0;
  735. right: 0;
  736. }
  737. .z-tabs-bottom-dot {
  738. border-radius: 100px;
  739. }
  740. .z-tabs-left,
  741. .z-tabs-right {
  742. /* #ifndef APP-NVUE */
  743. display: flex;
  744. /* #endif */
  745. flex-direction: row;
  746. align-items: center;
  747. }
  748. </style>