tui-tab.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. <template>
  2. <scroll-view class="tui-scroll__view" :class="[isFixed && !isSticky?'tui-tabs__fixed':'',isSticky?'tui-tabs__sticky':'']"
  3. :style="{height: height+'rpx',background:backgroundColor,top: isFixed || isSticky ? top + 'px' : 'auto',zIndex:isFixed || isSticky?zIndex:'auto'}"
  4. :scroll-x="scrolling" :scroll-with-animation="scrolling" :scroll-left="scrollLeft">
  5. <view class="tui-tabs__wrap">
  6. <view class="tui-tabs__list" :class="[scroll ? 'tui-tabs__scroll' : '']" :style="{height: height+'rpx'}">
  7. <view class="tui-tabs__item" :style="{height: height+'rpx'}" v-for="(item,index) in tabs" :key="index" @tap="handleClick"
  8. :data-index="index">
  9. <view class="tui-item__child" :style="{ color: currentTab == index ? selectedColor : color,fontSize: size + 'rpx',lineHeight: size + 'rpx',fontWeight: bold && currentTab == index ? 'bold' : 'normal'}">{{item}}</view>
  10. </view>
  11. <view class="tui-tabs__line" :class="[needTransition ? 'tui-transition' : '']" :style="{background: sliderBgColor,height:sliderHeight,borderRadius: sliderRadius,bottom: bottom,width: lineWidth+'px',transform: `translateX(${translateX}px)`}"></view>
  12. </view>
  13. </view>
  14. </scroll-view>
  15. </template>
  16. <script>
  17. export default {
  18. name: 'tuiTab',
  19. options: {
  20. virtualHost: true
  21. },
  22. props: {
  23. // 标签页数据源
  24. tabs: {
  25. type: Array,
  26. default () {
  27. return []
  28. }
  29. },
  30. // 当前选项卡
  31. current: {
  32. type: Number,
  33. default: 0
  34. },
  35. // 是否可以滚动
  36. scroll: {
  37. type: Boolean,
  38. default: false
  39. },
  40. // tab高度 rpx
  41. height: {
  42. type: Number,
  43. default: 80
  44. },
  45. backgroundColor: {
  46. type: String,
  47. default: '#fff'
  48. },
  49. //字体大小
  50. size: {
  51. type: Number,
  52. default: 28
  53. },
  54. //字体颜色
  55. color: {
  56. type: String,
  57. default: '#666'
  58. },
  59. //选中后字体颜色
  60. selectedColor: {
  61. type: String,
  62. default: '#5677fc'
  63. },
  64. //选中后 是否加粗 ,未选中则无效
  65. bold: {
  66. type: Boolean,
  67. default: false
  68. },
  69. //滑块高度
  70. sliderHeight: {
  71. type: String,
  72. default: '2px'
  73. },
  74. //滑块背景颜色
  75. sliderBgColor: {
  76. type: String,
  77. default: '#5677fc'
  78. },
  79. //滑块 radius
  80. sliderRadius: {
  81. type: String,
  82. default: '2px'
  83. },
  84. //滑块bottom
  85. bottom: {
  86. type: String,
  87. default: '0'
  88. },
  89. //是否固定
  90. isFixed: {
  91. type: Boolean,
  92. default: false
  93. },
  94. //吸顶效果,为true时isFixed失效
  95. isSticky: {
  96. type: Boolean,
  97. default: false
  98. },
  99. //isFixed=true时,tab top值 px
  100. top: {
  101. type: Number,
  102. // #ifndef H5
  103. default: 0,
  104. // #endif
  105. // #ifdef H5
  106. default: 44
  107. // #endif
  108. },
  109. zIndex: {
  110. type: [Number, String],
  111. default: 996
  112. }
  113. },
  114. watch: {
  115. /**
  116. * 监听数据变化, 如果改变重新初始化参数
  117. */
  118. tabs(newVal, oldVal) {
  119. this.scrolling = false
  120. // 异步加载数据时候, 延迟执行 init 方法, 防止无法正确获取 dom 信息
  121. setTimeout(() => this.init(), 0);
  122. },
  123. /**
  124. * 监听 currentTab 变化, 做对应处理
  125. */
  126. current(newVal, oldVal) {
  127. this.scrollByIndex(newVal);
  128. }
  129. },
  130. created() {
  131. this.currentTab = this.current;
  132. },
  133. mounted() {
  134. this.init()
  135. },
  136. data() {
  137. return {
  138. /* 未渲染数据 */
  139. windowWidth: 0, // 屏幕宽度
  140. tabItems: [], // 所有 tab 节点信息
  141. /* 渲染数据 */
  142. scrolling: true, // 控制 scroll-view 滚动以在异步加载数据的时候能正确获得 dom 信息
  143. needTransition: false, // 下划线是否需要过渡动画
  144. translateX: 0, // 下划 line 的左边距离
  145. lineWidth: 0, // 下划 line 宽度
  146. scrollLeft: 0, // scroll-view 左边滚动距离
  147. currentTab: 0
  148. };
  149. },
  150. methods: {
  151. /**
  152. * 切换菜单
  153. */
  154. handleClick(e) {
  155. let index = Number(e.currentTarget.dataset.index)
  156. this.$emit('change', {
  157. index: index
  158. });
  159. this.scrollByIndex(index);
  160. },
  161. /**
  162. * 滑动到指定位置
  163. * @param tabCur: 当前激活的tabItem的索引
  164. * @param needTransition: 下划线是否需要过渡动画, 第一次进来应设置为false
  165. */
  166. scrollByIndex(tabCur, needTransition = true) {
  167. let item = this.tabItems[tabCur];
  168. if (!item) return;
  169. let itemWidth = item.width || 0,
  170. itemLeft = item.left || 0;
  171. this.needTransition = needTransition;
  172. this.currentTab = tabCur;
  173. // 超出滚动的情况
  174. if (this.scroll) {
  175. // 保持滚动后当前 item '尽可能' 在屏幕中间
  176. let scrollLeft = itemLeft - (this.windowWidth - itemWidth) / 2;
  177. this.scrollLeft = scrollLeft;
  178. this.translateX = itemLeft;
  179. this.lineWidth = itemWidth
  180. } else {
  181. // 不超出滚动的情况
  182. this.translateX = itemLeft;
  183. this.lineWidth = itemWidth
  184. }
  185. },
  186. /**
  187. * 初始化函数
  188. */
  189. init() {
  190. const {
  191. windowWidth
  192. } = uni.getSystemInfoSync();
  193. this.windowWidth = windowWidth || 375
  194. const query = uni.createSelectorQuery().in(this);
  195. query.selectAll(".tui-item__child").boundingClientRect((res) => {
  196. this.scrolling = true;
  197. this.tabItems = res;
  198. this.scrollByIndex(this.currentTab, false);
  199. }).exec();
  200. }
  201. }
  202. };
  203. </script>
  204. <style scoped>
  205. .tui-scroll__view {
  206. width: 100%;
  207. height: 80rpx;
  208. overflow: hidden;
  209. }
  210. .tui-tabs__fixed {
  211. position: fixed;
  212. left: 0;
  213. }
  214. .tui-tabs__sticky {
  215. position: sticky;
  216. left: 0;
  217. }
  218. .tui-tabs__wrap {
  219. padding-bottom: 20rpx;
  220. }
  221. .tui-tabs__list {
  222. position: relative;
  223. height: 80rpx;
  224. display: flex;
  225. }
  226. .tui-tabs__scroll {
  227. white-space: nowrap !important;
  228. display: block !important;
  229. }
  230. .tui-tabs__scroll .tui-tabs__item {
  231. padding: 0 30rpx;
  232. display: inline-flex;
  233. }
  234. .tui-tabs__scroll .tui-item__child {
  235. display: block !important;
  236. }
  237. .tui-tabs__item {
  238. flex: 1;
  239. text-align: center;
  240. padding: 0 10rpx;
  241. box-sizing: border-box;
  242. transition: color 0.3s ease-in-out;
  243. display: flex;
  244. align-items: center;
  245. justify-content: center;
  246. }
  247. .tui-item__child {
  248. display: inline-block;
  249. }
  250. .tui-tabs__line {
  251. position: absolute;
  252. left: 0;
  253. width: 0;
  254. display: inline-block;
  255. }
  256. .tui-tabs__line.tui-transition {
  257. transition: width 0.3s, transform 0.3s;
  258. }
  259. </style>