tui-tab.vue 7.5 KB

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