me-tabs.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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" v-for="(tab, i) in tabs" :id="'tabitem'+i" :ref="'tabitem'+i" :class="{'active': value===i}" :style="{ height: tabHeightVal, 'line-height':tabHeightVal,'color':value===i?actColor:norColor }" :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: {
  20. type: Array,
  21. default() {
  22. return []
  23. }
  24. },
  25. nameKey: {
  26. type: String,
  27. default: 'dictLabel'
  28. },
  29. value: {
  30. type: [String, Number],
  31. default: 0
  32. },
  33. fixed: Boolean,
  34. tabWidth: Number,
  35. height: {
  36. type: Number,
  37. default: 64
  38. },
  39. norColor: {
  40. type: String,
  41. default: '#333333'
  42. },
  43. actColor: {
  44. type: String,
  45. default: '#FF5C03'
  46. },
  47. },
  48. data() {
  49. return {
  50. viewId: 'id_' + Math.random().toString(36).substr(2, 16),
  51. scrollLeft: 0,
  52. tabListSize: [],
  53. lineLeft: '0px',
  54. }
  55. },
  56. computed: {
  57. isScroll() {
  58. return this.tabWidth && this.tabs.length;
  59. },
  60. tabHeightPx() {
  61. return uni.upx2px(this.height);
  62. },
  63. tabHeightVal() {
  64. return this.tabHeightPx + 'px';
  65. },
  66. tabWidthPx() {
  67. return uni.upx2px(this.tabWidth);
  68. },
  69. tabWidthVal() {
  70. return this.isScroll ? this.tabWidthPx + 'px' : '';
  71. }
  72. },
  73. watch: {
  74. tabs() {
  75. this.warpWidth = null;
  76. this.updateTabs();
  77. },
  78. value() {
  79. console.log("qxj value changed");
  80. this.updateTabs();
  81. }
  82. },
  83. methods: {
  84. getTabName(tab) {
  85. return typeof tab === "object" ? tab[this.nameKey] : tab;
  86. },
  87. tabClick(i) {
  88. if (this.value !== i) {
  89. this.$emit("input", i);
  90. this.$emit("change", i);
  91. }
  92. },
  93. async updateTabs() {
  94. await this.selectorQuery();
  95. this.scrollCenter();
  96. this.updateLineLeft();
  97. },
  98. async scrollCenter() {
  99. if (!this.isScroll) return;
  100. if (!this.warpWidth) {
  101. let rect = await this.initWarpRect();
  102. this.warpWidth = rect ? rect.width : uni.getSystemInfoSync().windowWidth;
  103. }
  104. let tabLeft = this.tabWidthPx * this.value + this.tabWidthPx / 2;
  105. let diff = tabLeft - this.warpWidth / 2;
  106. this.scrollLeft = diff;
  107. // #ifdef MP-TOUTIAO
  108. this.scrollTimer && clearTimeout(this.scrollTimer);
  109. this.scrollTimer = setTimeout(() => {
  110. this.scrollLeft = Math.ceil(diff);
  111. }, 400);
  112. // #endif
  113. },
  114. initWarpRect() {
  115. return new Promise(resolve => {
  116. setTimeout(() => {
  117. let query = uni.createSelectorQuery();
  118. // #ifndef MP-ALIPAY
  119. query = query.in(this);
  120. // #endif
  121. query.select('#' + this.viewId).boundingClientRect(data => {
  122. resolve(data);
  123. }).exec();
  124. }, 20);
  125. });
  126. },
  127. selectorQuery() {
  128. return new Promise(resolve => {
  129. if (this.tabs.length === 0) {
  130. resolve();
  131. return;
  132. }
  133. uni.createSelectorQuery().in(this).select('.tabs-item').fields({
  134. dataset: true,
  135. size: true,
  136. }, res => {
  137. if (res) {
  138. this.swiperWidth = res.width;
  139. }
  140. }).exec();
  141. uni.createSelectorQuery().in(this).selectAll('.tab-item').boundingClientRect(rects => {
  142. this.tabListSize = rects;
  143. resolve();
  144. }).exec();
  145. });
  146. },
  147. updateLineLeft() {
  148. if (this.isScroll && this.tabListSize.length > 0) {
  149. let currentSize = this.tabListSize[this.value];
  150. let tabWid = currentSize.left + currentSize.width / 2.0;
  151. this.lineLeft = tabWid + 'px';
  152. console.log("qxj isScroll updateLineLeft value:"+this.lineLeft);
  153. } else {
  154. this.lineLeft = 100 / this.tabs.length * (this.value + 1) - 100 / (this.tabs.length * 2) + '%';
  155. console.log("qxj updateLineLeft value:"+this.lineLeft);
  156. }
  157. }
  158. },
  159. mounted() {
  160. this.updateTabs();
  161. }
  162. }
  163. </script>
  164. <style lang="scss">
  165. .me-tabs{
  166. position: relative;
  167. font-size: 28rpx;
  168. background-color: #fff;
  169. border-bottom: 0rpx solid #eee;
  170. box-sizing: border-box;
  171. overflow-y: hidden;
  172. background-color: #fff;
  173. &.tabs-fixed{
  174. z-index: 990;
  175. position: fixed;
  176. top: var(--window-top);
  177. left: 0;
  178. width: 100%;
  179. }
  180. &.tabs-fixed1{
  181. z-index: 990;
  182. position: fixed;
  183. top: 80;
  184. left: 0;
  185. width: 100%;
  186. }
  187. .tabs-item{
  188. position: relative;
  189. white-space: nowrap;
  190. padding-bottom: 30rpx; // 撑开高度,再配合me-tabs的overflow-y: hidden,以达到隐藏滚动条的目的
  191. box-sizing: border-box;
  192. .tab-item{
  193. position: relative;
  194. text-align: center;
  195. box-sizing: border-box;
  196. padding-left: 20rpx;
  197. padding-right:20rpx;
  198. color:#333;
  199. font-size: 32rpx;
  200. &.active{
  201. font-weight: bold;
  202. color: #FF5C03;
  203. }
  204. }
  205. }
  206. // 平分的方式显示item
  207. .tabs-flex{
  208. display: flex;
  209. .tab-item{
  210. flex: 1;
  211. }
  212. }
  213. // 居左显示item,支持水平滑动
  214. .tabs-scroll{
  215. .tab-item{
  216. display: inline-block;
  217. }
  218. }
  219. // 选中tab的线
  220. .tabs-line{
  221. z-index: 1;
  222. position: absolute;
  223. bottom: 32rpx; // 至少与.tabs-item的padding-bottom一致,才能保证在底部边缘
  224. width: 50rpx;
  225. height: 6rpx;
  226. transform: translateX(-50%);
  227. border-radius: 4rpx;
  228. transition: left .3s;
  229. background: #FF5C03;
  230. }
  231. }
  232. </style>