综合办公系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. <!-- tab组件: <me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs> -->
  2. <template>
  3. <view class="me-tabs" :class="{'tabs-fixed': fixed}" :style="{height: tabHeightVal}">
  4. <scroll-view v-if="tabs.length" :id="viewId" :scroll-left="scrollLeft" scroll-x scroll-with-animation :scroll-animation-duration="300">
  5. <view class="tabs-item" :class="{'tabs-flex':!isScroll, 'tabs-scroll':isScroll}">
  6. <!-- tab -->
  7. <view class="tab-item" :style="{width: tabWidthVal, height: tabHeightVal, 'line-height':tabHeightVal}" v-for="(tab, i) in tabs" :class="{'active': value===i}" :key="i" @click="tabClick(i)">
  8. {{getTabName(tab)}}
  9. </view>
  10. <!-- 下划线 -->
  11. <view class="tabs-line" :style="{left:lineLeft}"></view>
  12. </view>
  13. </scroll-view>
  14. </view>
  15. </template>
  16. <script>
  17. export default {
  18. props:{
  19. tabs: { // 支持格式: ['全部', '待付款'] 或 [{name:'全部'}, {name:'待付款'}]
  20. type: Array,
  21. default(){
  22. return []
  23. }
  24. },
  25. nameKey: { // 取name的字段
  26. type: String,
  27. default: 'name'
  28. },
  29. value: { // 当前显示的下标 (使用v-model语法糖: 1.props需为value; 2.需回调input事件)
  30. type: [String, Number],
  31. default: 0
  32. },
  33. fixed: Boolean, // 是否悬浮,默认false
  34. tabWidth: Number, // 每个tab的宽度,默认不设置值,为flex平均分配; 如果指定宽度,则不使用flex,每个tab居左,超过则水平滑动(单位默认rpx)
  35. height: { // 高度,单位rpx
  36. type: Number,
  37. default: 64
  38. }
  39. },
  40. data() {
  41. return {
  42. viewId: 'id_' + Math.random().toString(36).substr(2,16),
  43. scrollLeft: 0
  44. }
  45. },
  46. computed: {
  47. isScroll(){
  48. return this.tabWidth && this.tabs.length // 指定了tabWidth的宽度,则支持水平滑动
  49. },
  50. tabHeightPx(){
  51. return uni.upx2px(this.height)
  52. },
  53. tabHeightVal(){
  54. return this.tabHeightPx+'px'
  55. },
  56. tabWidthPx(){
  57. return uni.upx2px(this.tabWidth)
  58. },
  59. tabWidthVal(){
  60. return this.isScroll ? this.tabWidthPx+'px' : ''
  61. },
  62. lineLeft() {
  63. if (this.isScroll) {
  64. return this.tabWidthPx * this.value + this.tabWidthPx/2 + 'px' // 需转为px (用rpx的话iOS真机显示有误差)
  65. } else{
  66. return 100/this.tabs.length*(this.value + 1) - 100/(this.tabs.length*2) + '%'
  67. }
  68. }
  69. },
  70. watch: {
  71. tabs() {
  72. this.warpWidth = null; // 重新计算容器宽度
  73. this.scrollCenter(); // 水平滚动到中间
  74. },
  75. value() {
  76. this.scrollCenter(); // 水平滚动到中间
  77. }
  78. },
  79. methods: {
  80. getTabName(tab){
  81. return typeof tab === "object" ? tab[this.nameKey] : tab
  82. },
  83. tabClick(i){
  84. if(this.value!=i){
  85. this.$emit("input",i);
  86. this.$emit("change",i);
  87. }
  88. },
  89. async scrollCenter(){
  90. if(!this.isScroll) return;
  91. if(!this.warpWidth){ // tabs容器的宽度
  92. let rect = await this.initWarpRect()
  93. this.warpWidth = rect ? rect.width : uni.getSystemInfoSync().windowWidth; // 某些情况下取不到宽度,暂时取屏幕宽度
  94. }
  95. let tabLeft = this.tabWidthPx * this.value + this.tabWidthPx/2; // 当前tab中心点到左边的距离
  96. let diff = tabLeft - this.warpWidth/2 // 如果超过tabs容器的一半,则滚动差值
  97. this.scrollLeft = diff;
  98. // #ifdef MP-TOUTIAO
  99. this.scrollTimer && clearTimeout(this.scrollTimer)
  100. this.scrollTimer = setTimeout(()=>{ // 字节跳动小程序,需延时再次设置scrollLeft,否则tab切换跨度较大时不生效
  101. this.scrollLeft = Math.ceil(diff)
  102. },400)
  103. // #endif
  104. },
  105. initWarpRect(){
  106. return new Promise(resolve=>{
  107. setTimeout(()=>{ // 延时确保dom已渲染, 不使用$nextclick
  108. let query = uni.createSelectorQuery();
  109. // #ifndef MP-ALIPAY
  110. query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
  111. // #endif
  112. query.select('#'+this.viewId).boundingClientRect(data => {
  113. resolve(data)
  114. }).exec();
  115. },20)
  116. })
  117. }
  118. },
  119. mounted() {
  120. this.scrollCenter() // 滚动到当前下标
  121. }
  122. }
  123. </script>
  124. <style lang="scss">
  125. .me-tabs{
  126. position: relative;
  127. font-size: 24rpx;
  128. background-color: #fff;
  129. border-bottom: 1rpx solid #eee;
  130. box-sizing: border-box;
  131. overflow-y: hidden;
  132. background-color: #fff;
  133. &.tabs-fixed{
  134. z-index: 990;
  135. position: fixed;
  136. top: var(--window-top);
  137. left: 0;
  138. width: 100%;
  139. }
  140. .tabs-item{
  141. position: relative;
  142. white-space: nowrap;
  143. padding-bottom: 30rpx; // 撑开高度,再配合me-tabs的overflow-y: hidden,以达到隐藏滚动条的目的
  144. box-sizing: border-box;
  145. .tab-item{
  146. position: relative;
  147. text-align: center;
  148. box-sizing: border-box;
  149. &.active{
  150. font-weight: bold;
  151. color: red;
  152. }
  153. }
  154. }
  155. // 平分的方式显示item
  156. .tabs-flex{
  157. display: flex;
  158. .tab-item{
  159. flex: 1;
  160. }
  161. }
  162. // 居左显示item,支持水平滑动
  163. .tabs-scroll{
  164. .tab-item{
  165. display: inline-block;
  166. }
  167. }
  168. // 选中tab的线
  169. .tabs-line{
  170. z-index: 1;
  171. position: absolute;
  172. bottom: 30rpx; // 至少与.tabs-item的padding-bottom一致,才能保证在底部边缘
  173. width: 50rpx;
  174. height: 6rpx;
  175. transform: translateX(-50%);
  176. border-radius: 4rpx;
  177. transition: left .3s;
  178. background: red;
  179. }
  180. }
  181. </style>