¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="u-tabs"> |
| | | <view class="u-tabs__wrapper"> |
| | | <slot name="left" /> |
| | | <view class="u-tabs__wrapper__scroll-view-wrapper"> |
| | | <scroll-view |
| | | :scroll-x="scrollable" |
| | | :scroll-left="scrollLeft" |
| | | scroll-with-animation |
| | | class="u-tabs__wrapper__scroll-view" |
| | | :show-scrollbar="false" |
| | | ref="u-tabs__wrapper__scroll-view" |
| | | > |
| | | <view |
| | | class="u-tabs__wrapper__nav" |
| | | ref="u-tabs__wrapper__nav" |
| | | > |
| | | <view |
| | | class="u-tabs__wrapper__nav__item" |
| | | v-for="(item, index) in list" |
| | | :key="index" |
| | | @tap="clickHandler(item, index)" |
| | | :ref="`u-tabs__wrapper__nav__item-${index}`" |
| | | :style="[$u.addStyle(itemStyle), {flex: scrollable ? '' : 1}]" |
| | | :class="[`u-tabs__wrapper__nav__item-${index}`, item.disabled && 'u-tabs__wrapper__nav__item--disabled']" |
| | | > |
| | | <text |
| | | :class="[item.disabled && 'u-tabs__wrapper__nav__item__text--disabled']" |
| | | class="u-tabs__wrapper__nav__item__text" |
| | | :style="[textStyle(index)]" |
| | | >{{ item[keyName] }}</text> |
| | | <u-badge |
| | | :show="!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))" |
| | | :isDot="item.badge && item.badge.isDot || propsBadge.isDot" |
| | | :value="item.badge && item.badge.value || propsBadge.value" |
| | | :max="item.badge && item.badge.max || propsBadge.max" |
| | | :type="item.badge && item.badge.type || propsBadge.type" |
| | | :showZero="item.badge && item.badge.showZero || propsBadge.showZero" |
| | | :bgColor="item.badge && item.badge.bgColor || propsBadge.bgColor" |
| | | :color="item.badge && item.badge.color || propsBadge.color" |
| | | :shape="item.badge && item.badge.shape || propsBadge.shape" |
| | | :numberType="item.badge && item.badge.numberType || propsBadge.numberType" |
| | | :inverted="item.badge && item.badge.inverted || propsBadge.inverted" |
| | | customStyle="margin-left: 4px;" |
| | | ></u-badge> |
| | | </view> |
| | | <!-- #ifdef APP-NVUE --> |
| | | <view |
| | | class="u-tabs__wrapper__nav__line" |
| | | ref="u-tabs__wrapper__nav__line" |
| | | :style="[{ |
| | | width: $u.addUnit(lineWidth), |
| | | height: $u.addUnit(lineHeight), |
| | | background: lineColor, |
| | | backgroundSize: lineBgSize, |
| | | }]" |
| | | > |
| | | <!-- #endif --> |
| | | <!-- #ifndef APP-NVUE --> |
| | | <view |
| | | class="u-tabs__wrapper__nav__line" |
| | | ref="u-tabs__wrapper__nav__line" |
| | | :style="[{ |
| | | width: $u.addUnit(lineWidth), |
| | | transform: `translate(${lineOffsetLeft}px)`, |
| | | transitionDuration: `${firstTime ? 0 : duration}ms`, |
| | | height: $u.addUnit(lineHeight), |
| | | background: lineColor, |
| | | backgroundSize: lineBgSize, |
| | | }]" |
| | | > |
| | | <!-- #endif --> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | <slot name="right" /> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | // #ifdef APP-NVUE |
| | | const animation = uni.requireNativePlugin('animation') |
| | | const dom = uni.requireNativePlugin('dom') |
| | | // #endif |
| | | import props from './props.js'; |
| | | /** |
| | | * Tabs æ ç¾ |
| | | * @description tabsæ ç¾ç»ä»¶ï¼å¨æ ç¾å¤çæ¶åï¼å¯ä»¥é
ç½®ä¸ºå·¦å³æ»å¨ï¼æ ç¾å°çæ¶åï¼å¯ä»¥ç¦æ¢æ»å¨ã 该ç»ä»¶çä¸ä¸ªç¹ç¹æ¯é
置为æ»å¨æ¨¡å¼æ¶ï¼æ¿æ´»çtabä¼èªå¨ç§»å¨å°ç»ä»¶çä¸é´ä½ç½®ã |
| | | * @tutorial https://www.uviewui.com/components/tabs.html |
| | | * @property {String | Number} duration æ»åç§»å¨ä¸æ¬¡æéçæ¶é´ï¼åä½ç§ï¼é»è®¤ 200 ï¼ |
| | | * @property {String | Number} swierWidth swiperç宽度ï¼é»è®¤ '750rpx' ï¼ |
| | | * @property {String} keyName ä»`list`å
ç´ å¯¹è±¡ä¸è¯»åçé®åï¼é»è®¤ 'name' ï¼ |
| | | * @event {Function(index)} change æ ç¾æ¹åæ¶è§¦å index: ç¹å»äºç¬¬å 个tabï¼ç´¢å¼ä»0å¼å§ |
| | | * @event {Function(index)} click ç¹å»æ ç¾æ¶è§¦å index: ç¹å»äºç¬¬å 个tabï¼ç´¢å¼ä»0å¼å§ |
| | | * @example <u-tabs :list="list" :is-scroll="false" :current="current" @change="change"></u-tabs> |
| | | */ |
| | | export default { |
| | | name: 'u-tabs', |
| | | mixins: [uni.$u.mpMixin, uni.$u.mixin, props], |
| | | data() { |
| | | return { |
| | | firstTime: true, |
| | | scrollLeft: 0, |
| | | scrollViewWidth: 0, |
| | | lineOffsetLeft: 0, |
| | | tabsRect: { |
| | | left: 0 |
| | | }, |
| | | innerCurrent: 0, |
| | | moving: false, |
| | | } |
| | | }, |
| | | watch: { |
| | | current: { |
| | | immediate: true, |
| | | handler (newValue, oldValue) { |
| | | // å
å¤é¨å¼ä¸ç¸çæ¶ï¼æå°è¯ç§»å¨æ»å |
| | | if (newValue !== this.innerCurrent) { |
| | | this.innerCurrent = newValue |
| | | this.$nextTick(() => { |
| | | this.resize() |
| | | }) |
| | | } |
| | | } |
| | | }, |
| | | // listååæ¶ï¼éæ°æ¸²æliståé¡¹ä¿¡æ¯ |
| | | list() { |
| | | this.$nextTick(() => { |
| | | this.resize() |
| | | }) |
| | | } |
| | | }, |
| | | computed: { |
| | | textStyle() { |
| | | return index => { |
| | | const style = {} |
| | | // å彿æ¯å¦æ¿æ´»çæ ·å¼ |
| | | const customeStyle = index === this.innerCurrent ? uni.$u.addStyle(this.activeStyle) : uni.$u |
| | | .addStyle( |
| | | this.inactiveStyle) |
| | | // 妿å½åèå被ç¦ç¨ï¼åå ä¸å¯¹åºé¢è²ï¼éè¦å¨æ¤åå¤çï¼æ¯å 为nvueä¸ï¼æ æ³å¨styleæ ·å¼ä¸éè¿!importè¦çæ ç¾çå
èæ ·å¼ |
| | | if (this.list[index].disabled) { |
| | | style.color = '#c8c9cc' |
| | | } |
| | | return uni.$u.deepMerge(customeStyle, style) |
| | | } |
| | | }, |
| | | propsBadge() { |
| | | return uni.$u.props.badge |
| | | } |
| | | }, |
| | | async mounted() { |
| | | this.init() |
| | | }, |
| | | methods: { |
| | | setLineLeft() { |
| | | const tabItem = this.list[this.innerCurrent]; |
| | | if (!tabItem) { |
| | | return; |
| | | } |
| | | // è·åæ»å该移å¨çä½ç½® |
| | | let lineOffsetLeft = this.list |
| | | .slice(0, this.innerCurrent) |
| | | .reduce((total, curr) => total + curr.rect.width, 0); |
| | | // è·åä¸åçº¿çæ°å¼pxè¡¨ç¤ºæ³ |
| | | const lineWidth = uni.$u.getPx(this.lineWidth); |
| | | this.lineOffsetLeft = lineOffsetLeft + (tabItem.rect.width - lineWidth) / 2 |
| | | // #ifdef APP-NVUE |
| | | // ç¬¬ä¸æ¬¡ç§»å¨æ»åï¼æ éè¿æ¸¡æ¶é´ |
| | | this.animation(this.lineOffsetLeft, this.firstTime ? 0 : parseInt(this.duration)) |
| | | // #endif |
| | | |
| | | // 妿æ¯ç¬¬ä¸æ¬¡æ§è¡æ¤æ¹æ³ï¼è®©æ»åå¨åå§åæ¶ï¼ç¬é´æ»å¨å°ç¬¬ä¸ä¸ªtab itemçä¸é´ |
| | | // è¿ééè¦ä¸ä¸ªå®æ¶å¨ï¼å 为å¨énvueä¸ï¼æ¯ç´æ¥éè¿styleç»å®è¿æ¸¡æ¶é´ï¼éè¦çå
¶è¿æ¸¡å®æåï¼å设置为false(éç¬¬ä¸æ¬¡ç§»å¨æ»å) |
| | | if (this.firstTime) { |
| | | setTimeout(() => { |
| | | this.firstTime = false |
| | | }, 10); |
| | | } |
| | | }, |
| | | // nvueä¸è®¾ç½®æ»åçä½ç½® |
| | | animation(x, duration = 0) { |
| | | // #ifdef APP-NVUE |
| | | const ref = this.$refs['u-tabs__wrapper__nav__line'] |
| | | animation.transition(ref, { |
| | | styles: { |
| | | transform: `translateX(${x}px)` |
| | | }, |
| | | duration |
| | | }) |
| | | // #endif |
| | | }, |
| | | // ç¹å»æä¸ä¸ªæ ç¾ |
| | | clickHandler(item, index) { |
| | | // å 为æ ç¾å¯è½ä¸ºdisabledç¶æï¼æä»¥clickæ¯ä¸å®ä¼ååºçï¼ä½æ¯changeäºä»¶æ¯éè¦å¯ç¨çç¶ææååº |
| | | this.$emit('click', { |
| | | ...item, |
| | | index |
| | | }) |
| | | // 妿disabledç¶æï¼è¿å |
| | | if (item.disabled) return |
| | | this.innerCurrent = index |
| | | this.resize() |
| | | this.$emit('change', { |
| | | ...item, |
| | | index |
| | | }) |
| | | }, |
| | | init() { |
| | | uni.$u.sleep().then(() => { |
| | | this.resize() |
| | | }) |
| | | }, |
| | | setScrollLeft() { |
| | | // å½åæ´»å¨tabçå¸å±ä¿¡æ¯ï¼ætabèåçwidthåleft(为å
ç´ å·¦è¾¹çå°ç¶å
ç´ å·¦è¾¹ççè·ç¦»)çä¿¡æ¯ |
| | | const tabRect = this.list[this.innerCurrent] |
| | | // ç´¯å å¾å°å½åitemå°å·¦è¾¹çè·ç¦» |
| | | const offsetLeft = this.list |
| | | .slice(0, this.innerCurrent) |
| | | .reduce((total, curr) => { |
| | | return total + curr.rect.width |
| | | }, 0) |
| | | // æ¤å¤ä¸ºå±å¹å®½åº¦ |
| | | const windowWidth = uni.$u.sys().windowWidth |
| | | // å°æ´»å¨çtabs-itemç§»å¨å°å±å¹æ£ä¸é´ï¼å®é
䏿¯å¯¹scroll-viewçç§»å¨ |
| | | let scrollLeft = offsetLeft - (this.tabsRect.width - tabRect.rect.width) / 2 - (windowWidth - this.tabsRect |
| | | .right) / 2 + this.tabsRect.left / 2 |
| | | // è¿éåä¸ä¸ªéå¶ï¼éå¶scrollLeftçæå¤§å¼ä¸ºæ´ä¸ªscroll-view宽度åå»tabsç»ä»¶ç宽度 |
| | | scrollLeft = Math.min(scrollLeft, this.scrollViewWidth - this.tabsRect.width) |
| | | this.scrollLeft = Math.max(0, scrollLeft) |
| | | }, |
| | | // è·åæææ ç¾ç尺寸 |
| | | resize() { |
| | | // 妿ä¸åå¨listï¼åä¸å¤ç |
| | | if(this.list.length === 0) { |
| | | return |
| | | } |
| | | Promise.all([this.getTabsRect(), this.getAllItemRect()]).then(([tabsRect, itemRect = []]) => { |
| | | this.tabsRect = tabsRect |
| | | this.scrollViewWidth = 0 |
| | | itemRect.map((item, index) => { |
| | | // 计ç®scroll-viewç宽度ï¼è¿é |
| | | this.scrollViewWidth += item.width |
| | | // å¦å¤è®¡ç®æ¯ä¸ä¸ªitemçä¸å¿ç¹Xè½´åæ |
| | | this.list[index].rect = item |
| | | }) |
| | | // è·åäºtabsç尺寸ä¹åï¼è®¾ç½®æ»åçä½ç½® |
| | | this.setLineLeft() |
| | | this.setScrollLeft() |
| | | }) |
| | | }, |
| | | // è·å导èªèåç尺寸 |
| | | getTabsRect() { |
| | | return new Promise(resolve => { |
| | | this.queryRect('u-tabs__wrapper__scroll-view').then(size => resolve(size)) |
| | | }) |
| | | }, |
| | | // è·åæææ ç¾ç尺寸 |
| | | getAllItemRect() { |
| | | return new Promise(resolve => { |
| | | const promiseAllArr = this.list.map((item, index) => this.queryRect( |
| | | `u-tabs__wrapper__nav__item-${index}`, true)) |
| | | Promise.all(promiseAllArr).then(sizes => resolve(sizes)) |
| | | }) |
| | | }, |
| | | // è·åå个æ ç¾ç尺寸 |
| | | queryRect(el, item) { |
| | | // #ifndef APP-NVUE |
| | | // $uGetRect为uViewèªå¸¦çèç¹æ¥è¯¢ç®åæ¹æ³ï¼è¯¦è§ææ¡£ä»ç»ï¼https://www.uviewui.com/js/getRect.html |
| | | // ç»ä»¶å
é¨ä¸è¬ç¨this.$uGetRectï¼å¯¹å¤ç为uni.$u.getRectï¼äºè
åè½ä¸è´ï¼åç§°ä¸å |
| | | return new Promise(resolve => { |
| | | this.$uGetRect(`.${el}`).then(size => { |
| | | resolve(size) |
| | | }) |
| | | }) |
| | | // #endif |
| | | |
| | | // #ifdef APP-NVUE |
| | | // nvueä¸ï¼ä½¿ç¨domæ¨¡åæ¥è¯¢å
ç´ é«åº¦ |
| | | // è¿åä¸ä¸ªpromiseï¼è®©è°ç¨æ¤æ¹æ³ç主ä½è½ä½¿ç¨thenåè° |
| | | return new Promise(resolve => { |
| | | dom.getComponentRect(item ? this.$refs[el][0] : this.$refs[el], res => { |
| | | resolve(res.size) |
| | | }) |
| | | }) |
| | | // #endif |
| | | }, |
| | | }, |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @import "../../libs/css/components.scss"; |
| | | |
| | | .u-tabs { |
| | | |
| | | &__wrapper { |
| | | @include flex; |
| | | align-items: center; |
| | | |
| | | &__scroll-view-wrapper { |
| | | flex: 1; |
| | | /* #ifndef APP-NVUE */ |
| | | overflow: auto hidden; |
| | | /* #endif */ |
| | | } |
| | | |
| | | &__scroll-view { |
| | | @include flex; |
| | | flex: 1; |
| | | } |
| | | |
| | | &__nav { |
| | | @include flex; |
| | | position: relative; |
| | | |
| | | &__item { |
| | | padding: 0 11px; |
| | | @include flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | &--disabled { |
| | | /* #ifndef APP-NVUE */ |
| | | cursor: not-allowed; |
| | | /* #endif */ |
| | | } |
| | | |
| | | &__text { |
| | | font-size: 15px; |
| | | color: $u-content-color; |
| | | |
| | | &--disabled { |
| | | color: $u-disabled-color !important; |
| | | } |
| | | } |
| | | } |
| | | |
| | | &__line { |
| | | height: 3px; |
| | | background: $u-primary; |
| | | width: 30px; |
| | | position: absolute; |
| | | bottom: 2px; |
| | | border-radius: 100px; |
| | | transition-property: transform; |
| | | transition-duration: 300ms; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |