yui-tabs(1.0.6).vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  1. <template>
  2. <view class="yui-tabs" :class="[tabsClass]">
  3. <!-- 依赖元素,用于处理滚动吸顶所需 -->
  4. <view class="depend-wrap"></view>
  5. <!-- 标签区域 -->
  6. <view class="yui-tabs__wrap" :style="[innerWrapStyle,wrapStyle]">
  7. <!-- scrollX为true,表示允许横向滚动 -->
  8. <scroll-view class="yui-tabs__scroll" :class="[scrollX?'enable-sroll':'']" :scroll-x="scrollX"
  9. :scroll-anchoring="true" enable-flex :scroll-left="scrollLeft"
  10. :scroll-into-view="!scrollToCenter?scrollId:''" scroll-with-animation :style="[scrollStyle]">
  11. <view class="yui-tabs__nav" :class="[navClass]" :style=[navStyle]>
  12. <view class="yui-tab" v-for="(tab,index) in tabList" :key="index" @tap.stop="handleClick(index)"
  13. :id="`tab_${index}`" :class="[tabClass(index, tab)]" :style="[tabStyle(tab)]">
  14. <view class="yui-tab__text">
  15. <slot :name="tab.titleSlot">{{tab.label}}</slot>
  16. <text :class="[infoClass(tab)]" v-if="tab.badge || tab.dot">{{tab.badge}}</text>
  17. </view>
  18. </view>
  19. <view v-if="isLine" class="yui-tabs__line" :style="[lineStyle,lineAnimatedStyle]"></view>
  20. </view>
  21. </scroll-view>
  22. <!-- 标签栏额外内容 -->
  23. <view class="yui-tabs__extra">
  24. <slot name="extra"></slot>
  25. </view>
  26. </view>
  27. <!-- 标签内容 -->
  28. <view class="yui-tabs__content" :class="{'yui-tabs__content--animated':animated}">
  29. <view class="yui-tabs__track" :style="[trackStyle]">
  30. <view class="yui-tab__pane" :class="[paneClass(index,tab)]" v-for="(tab,index) in tabList" :key="index"
  31. :style="[tab.paneStyle]" @touchstart="touchStart" @touchmove="touchMove($event,index)"
  32. @touchend="touchEnd($event,index)">
  33. <view v-if="tab.rendered ? true :value == index">
  34. <slot :name="tab.slot"></slot>
  35. </view>
  36. </view>
  37. </view>
  38. </view>
  39. </view>
  40. </template>
  41. <script>
  42. // 1.优化滑动切换与上下滚动互相影响的bug。
  43. // 2.考虑是否增加滚动导航
  44. import {
  45. isNull,
  46. addUnit,
  47. isDef,
  48. isObject,
  49. getDirection
  50. } from "../yui-tabs/utils/uitls.js"
  51. export default {
  52. name: "yui-tabs",
  53. emits: ['input', 'change', 'click', 'rendered', 'scroll'],
  54. // uni-app自定义v-model需要按照如下的规范,直接用value和input,否则在微信小程序上会失效
  55. model: {
  56. prop: 'value',
  57. event: 'input'
  58. },
  59. props: {
  60. color: String, //标签主题色, 默认值为"#0022AB"
  61. background: String, //标签栏背景色,默认值为"#fff"
  62. lineWidth: [Number, String], //底部条宽度,默认单位为px, 默认值为20px
  63. lineHeight: [Number, String], //底部条高度,默认单位为px,默认值为3px
  64. titleActiveColor: String, //标题选中态颜色
  65. titleInactiveColor: String, //标题默认态颜色
  66. // 标签栏样式
  67. wrapStyle: {
  68. type: [Object, null],
  69. default: () => {}
  70. },
  71. // 动画时间,单位秒
  72. duration: {
  73. type: [Number, String],
  74. default: 0.3,
  75. },
  76. // 样式风格类型,可选值为 card
  77. type: {
  78. type: String,
  79. default: "line"
  80. },
  81. // v-model绑定属性,绑定当前选中标签的标识符(标签的下标)
  82. value: {
  83. type: Number,
  84. default: -1
  85. },
  86. // 标签页数据,支持字符串类型与对象类型的数组结构
  87. // 对象类型需符合{label:'标签1',slot:'slotName'}这样的格式,slot为自定义的标签内容插槽名,否则插槽名默认为"pane"+tab下标的命名
  88. tabs: {
  89. type: Array,
  90. default: () => []
  91. },
  92. // 是否开启延迟渲染(首次切换到标签时才触发内容渲染)
  93. isLazyRender: {
  94. type: Boolean,
  95. default: true,
  96. },
  97. // 是否开启切换标签内容时的转场动画
  98. animated: {
  99. type: Boolean,
  100. default: false
  101. },
  102. // 保证组件的可见性,主要用于处理选中标签的底部线条位置
  103. visible: {
  104. type: Boolean,
  105. default: true
  106. },
  107. // 标签页是否滚动吸顶
  108. fixed: Boolean,
  109. // 滚动吸顶下与顶部的最小距离,默认 px
  110. offsetTop: {
  111. type: Number,
  112. default: 0
  113. },
  114. // 滚动吸顶/粘性布局下,标签栏的z-index值
  115. zIndex: {
  116. type: Number,
  117. default: 99
  118. },
  119. // 是否使用粘性定位布局
  120. sticky: Boolean,
  121. // 粘性布局的判断阈值
  122. stickyThreshold: {
  123. type: Number,
  124. default: 0
  125. },
  126. // 标签栏滚动时当前标签居中
  127. scrollToCenter: {
  128. type: Boolean,
  129. default: true,
  130. },
  131. // 标签栏的滚动阈值(仅在ellipsis="false"且type不为"card"下时有效),标签数量超过阈值且总宽度超过标签栏宽度时开始横向滚动(切换时会自动将当前标签居中)
  132. scrollThreshold: {
  133. type: [Number, String],
  134. default: 5
  135. },
  136. // 是否省略过长的标题文字
  137. ellipsis: {
  138. type: Boolean,
  139. default: true,
  140. },
  141. // 是否开启手势滑动切换
  142. swipeable: {
  143. type: Boolean,
  144. default: false,
  145. },
  146. // 是否开启标签内容的拖动动画(该属性依赖于swipeable、is-lazy-render的开启;该属性开启时考虑给包裹内容的容器增加一个min-height,因为开启该属性后,其他未显示出来的标签内容会沿用当前显示的高度,拖动切换后由于高度不一致会有回弹)
  147. swipeAnimated: {
  148. type: Boolean,
  149. default: false,
  150. },
  151. // 滑动切换的滑动距离阈值,手指滑动页面触发切换的阈值,单位为px,表示横向滑动整个可视区域的多少px时才切换标签内容
  152. swipeThreshold: {
  153. type: [Number, String],
  154. default: 50,
  155. },
  156. },
  157. data() {
  158. return {
  159. tabList: [], //标签页数据
  160. scrollId: 'tab_0', //值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素
  161. scrollLeft: 0, //设置横向滚动条位置
  162. extraWidth: 0, //标签栏右侧额外区域宽度
  163. contentWidth: 0, //标签内容宽度
  164. trackStyle: null, //标签内容滑动轨道样式
  165. touchInfo: {
  166. inited: false, //标记左右滑动时的初始化状态
  167. startX: null, //记录touch位置的横坐标
  168. startY: null, //记录touch位置的纵坐标
  169. moved: false, //用来判断是否是一次移动
  170. deltaX: 0, //记录拖动的横坐标距离
  171. isLeftSide: false, //标记是否为左滑
  172. },
  173. // 标签栏底部线条动画相关
  174. lineAnimated: false, //是否开启标签栏底部线条动画(首次不开启)
  175. lineAnimatedStyle: {
  176. transform: `translateX(-100%) translateX(-50%)`,
  177. transitionDuration: `0s`
  178. }, //标签栏底部线条动画样式
  179. isFixed: false, //是否吸顶
  180. }
  181. },
  182. computed: {
  183. // 样式风格是否为line
  184. isLine() {
  185. return this.type === "line"
  186. },
  187. // 标签页容器class
  188. tabsClass() {
  189. return `yui-tabs--${this.type} ${this.visible?'yui-tabs--visible':''} ${this.fixed || this.isFixed?'yui-tabs--fixed':''} `
  190. },
  191. // 标签栏class
  192. navClass() {
  193. return `yui-tabs__nav--${this.type}`
  194. },
  195. // 标签栏style
  196. navStyle() {
  197. const style = {}
  198. if (this.type === "card") style.borderColor = this.color
  199. return style
  200. },
  201. // 标签栏包裹层样式
  202. innerWrapStyle() {
  203. const style = {
  204. background: this.background,
  205. }
  206. // 滚动吸顶下
  207. if (this.fixed || this.isFixed) {
  208. style.top = this.offsetTop + "px"
  209. style.zIndex = this.zIndex
  210. }
  211. return style
  212. },
  213. // 滚动区域样式
  214. scrollStyle() {
  215. return {
  216. width: `calc(100% - ${this.extraWidth}px)`
  217. }
  218. },
  219. // 标签栏底部线条样式
  220. lineStyle() {
  221. const {
  222. lineWidth,
  223. lineHeight,
  224. duration
  225. } = this;
  226. const lineStyle = {
  227. width: addUnit(lineWidth),
  228. backgroundColor: this.color,
  229. }
  230. if (isDef(lineHeight)) {
  231. const height = addUnit(lineHeight);
  232. lineStyle.height = height;
  233. lineStyle.borderRadius = height;
  234. }
  235. return lineStyle
  236. },
  237. // 是否允许横向滚动
  238. scrollX() {
  239. return !this.ellipsis && this.type !== "card" && this.tabs.length > this.scrollThreshold
  240. },
  241. dataLen() {
  242. return this.tabList.length
  243. }
  244. },
  245. watch: {
  246. // 监听选中标识符变化
  247. value: {
  248. handler(val, oldVal) {
  249. this.tabChange(val, oldVal) //标签切换
  250. this.changeStyle() // 样式切换
  251. }
  252. },
  253. // 监听tabs变化,重新初始化tabList
  254. tabs: {
  255. handler(val) {
  256. this.updateTabList(); //更新tabList
  257. },
  258. deep: true
  259. },
  260. },
  261. created() {
  262. this.initTabList() // 初始化tabList
  263. },
  264. mounted() {
  265. this.init() //初始化操作
  266. this.listenEvent(); //监听事件
  267. },
  268. methods: {
  269. // 获取元素位置信息
  270. getRect(select) {
  271. return new Promise((res, rej) => {
  272. if (!select) rej('Parameter is empty');
  273. let query
  274. // #ifdef MP-ALIPAY
  275. query = uni.createSelectorQuery()
  276. // #endif
  277. // #ifndef MP-ALIPAY
  278. query = uni.createSelectorQuery().in(this)
  279. // #endif
  280. query.select(select).boundingClientRect(rect => res(rect)).exec();
  281. })
  282. },
  283. // 标签项class
  284. tabClass(index, tab) {
  285. return `yui-tab_${index} ${tab.active?'yui-tab--active':''} ${this.ellipsis && !this.scrollX?'yui-tab__ellipsis':''}`
  286. },
  287. // 标签内容class
  288. paneClass(index, tab) {
  289. return `yui-tab_pane${index} ${tab.active?'yui-pane--active':''}`
  290. },
  291. // 标签项style
  292. tabStyle(tab) {
  293. let activeColor = this.titleActiveColor
  294. let inactiveColor = this.titleInactiveColor
  295. let background = ""
  296. // type="text" 时,选中时使用主题色
  297. if (this.type === "text" && isNull(activeColor)) {
  298. activeColor = this.color
  299. }
  300. // type="card" 时,未选中则使用主题色
  301. if (this.type === "card") {
  302. background = this.color
  303. if (isNull(inactiveColor)) inactiveColor = this.color
  304. }
  305. // type="button" 时
  306. if (this.type === "button") {
  307. background = this.color
  308. }
  309. return {
  310. color: tab.active ? activeColor : inactiveColor,
  311. background: tab.active ? background : "",
  312. }
  313. },
  314. // 标题右上角信息class
  315. infoClass(tab) {
  316. return ` yui-tab__info ${tab.dot?'yui-tab__info--dot':''}`
  317. },
  318. // 监听事件
  319. listenEvent() {
  320. const that = this
  321. // 粘性定位布局下的吸顶处理
  322. if (this.sticky) {
  323. uni.$on('onPageScroll', function(e) {
  324. that.getRect('.depend-wrap').then(rect => {
  325. that.isFixed = rect.bottom - that.stickyThreshold <= that.offsetTop
  326. // 滚动时触发,仅在 sticky 模式下生效,{ scrollTop: 距离顶部位置, isFixed: 是否吸顶 }
  327. that.$emit("scroll", {
  328. scrollTop: e.scrollTop,
  329. isFixed: that.isFixed
  330. })
  331. })
  332. })
  333. }
  334. },
  335. // 初始化操作
  336. async init() {
  337. //获取额外区域的宽度
  338. let rect = await this.getRect('.yui-tabs__extra')
  339. this.extraWidth = rect ? rect.width : 0
  340. //获取标签内容区域的宽度
  341. rect = await this.getRect('.yui-tabs__content')
  342. this.contentWidth = rect ? rect.width : 0
  343. //获取标签容器距离视口左侧的left值
  344. rect = await this.getRect('.yui-tabs')
  345. const parentLeft = rect ? rect.left : 0
  346. // 保存每个tab的translateX
  347. this.tabList.forEach(async (tab, index) => {
  348. const rect = await this.getRect('.yui-tab_' + index);
  349. tab.translateX = rect ? rect.left + rect.width / 2 - parentLeft : 0
  350. tab.scrollLeft = tab.translateX - this.contentWidth / 2
  351. if (tab.scrollLeft < 0) tab.scrollLeft = 0
  352. if (index === this.value) {
  353. this.tabChange(this.value, -1) //标签切换
  354. this.changeStyle(); //样式切换
  355. }
  356. })
  357. },
  358. // 初始化tabList
  359. initTabList() {
  360. const tabs = this.tabs.filter(o => !isNull(o))
  361. this.tabList = tabs.map((item, index) => {
  362. const isCurr = this.value == index
  363. const tab = {
  364. label: '', //标签名称
  365. slot: 'pane' + index, //标签内容的插槽名称,默认以"pane"+标签下标命名
  366. titleSlot: 'title' + index, //标签标题的插槽名称,默认以"title"+标签下标命名
  367. disabled: false, //是否禁用标签
  368. active: false, //是否选中
  369. rendered: !this.isLazyRender, //标记是否渲染过
  370. show: false, // 是否显示内容
  371. dot: false, //是否在标题右上角显示小红点
  372. translateX: 0, //底部线条translateX值,
  373. scrollLeft: 0, //当前标签对应的横向滚动条位置
  374. }
  375. tab.paneStyle = this.animated ? {
  376. visibility: tab.show ? 'visible' : 'visible',
  377. height: tab.show ? 'auto' : '0px'
  378. } : {
  379. display: tab.show ? 'block' : 'none'
  380. };
  381. // 读取标签对象值
  382. if (isObject(item)) {
  383. tab.label = item.label
  384. tab.slot = isNull(item.slot) ? tab.slot : item.slot
  385. tab.titleSlot = isNull(item.titleSlot) ? tab.titleSlot : item.titleSlot
  386. tab.dot = isNull(item.dot) ? tab.dot : item.dot
  387. tab.badge = !isNull(item.badge) && !tab.dot ? item.badge : ""
  388. } else {
  389. tab.label = item
  390. }
  391. return tab
  392. })
  393. },
  394. // 更新tabList
  395. updateTabList() {
  396. // 如果长度有变化,表示tabs有删除或新增,重新init一次
  397. if (this.tabs.length != this.dataLen) {
  398. this.initTabList() //初始化tabList
  399. } else {
  400. // 否则仅仅更改label,badge,dot值
  401. this.tabs.forEach((item, i) => {
  402. const tab = this.tabList[i]
  403. // 读取标签对象值
  404. if (isObject(item)) {
  405. this.$set(tab, "label", item.label)
  406. this.$set(tab, "dot", isNull(item.dot) ? tab.dot : item.dot)
  407. this.$set(tab, "badge", !isNull(item.badge) && !tab.dot ? item.badge : "")
  408. } else {
  409. this.$set(tab, "label", item)
  410. }
  411. })
  412. }
  413. this.$nextTick(() => {
  414. this.init() //初始化操作
  415. })
  416. },
  417. // 标签点击事件
  418. handleClick(index) {
  419. // if (this.tabList[index].disabled) return //禁用时不允许切换
  420. this.$emit('click', index, this.tabs[index]) // 标签点击事件
  421. if (this.value == index) return //不允许重复切换同一标签
  422. const oldValue = this.value //获取旧的index
  423. //更新v-model绑定的值
  424. this.$emit('input', index) //更新v-model绑定的值
  425. },
  426. // 标签切换
  427. tabChange(value, oldValue) {
  428. const oldTab = this.tabList[oldValue] || {} //上一个tab
  429. const currTab = this.tabList[value] //当前tab
  430. // 设置选中态
  431. oldTab.active = false
  432. currTab.active = true
  433. // 触发rendered事件
  434. if (this.isLazyRender && !currTab.rendered) {
  435. this.$emit('rendered', value, this.tabs[value])
  436. }
  437. currTab.rendered = true //标记渲染过
  438. oldTab.show = false //隐藏旧内容区域
  439. currTab.show = true //显示当前tab对应的内容区域
  440. // 触发change事件
  441. if (oldValue !== -1) this.$emit('change', value, this.tabs[value])
  442. },
  443. // 样式切换
  444. changeStyle() {
  445. // 标签栏允许滚动
  446. if (this.scrollX) {
  447. if (this.scrollToCenter) {
  448. // 设置横向滚动条位置,当前标签滚动到中心位置
  449. this.scrollLeft = this.tabList[this.value].scrollLeft
  450. console.log(this.scrollLeft);
  451. } else {
  452. //设置scroll-into-view
  453. this.scrollId = `tab_${this.value-1}`;
  454. }
  455. }
  456. this.changeLineStyle() //改变标签栏底部线条位置
  457. this.changeTrackStyle(false, this.duration) //改变标签内容滑动轨道样式
  458. this.changePaneStyle() //改变标签内容样式
  459. },
  460. // 改变标签栏底部线条位置
  461. changeLineStyle() {
  462. // 仅在 type="line" 时有效
  463. if (!this.isLine) return
  464. const val = this.tabList[this.value].translateX
  465. const transform = `translateX(${isDef(val) ? val + "px" : '-100%'}) translateX(-50%)`
  466. const duration = `${this.lineAnimated?this.duration:'0'}s`
  467. this.$set(this.lineAnimatedStyle, 'transform', transform)
  468. this.$set(this.lineAnimatedStyle, 'transitionDuration', duration)
  469. this.$nextTick(() => {
  470. this.lineAnimated = true //是否开启标签栏动画
  471. })
  472. },
  473. // 改变标签内容滑动轨道样式
  474. changeTrackStyle(isSlide = false, duration = 0, offsetWidth = 0) {
  475. if (!this.animated) return
  476. // isSlide为true,表示左右滑动;false表示点击标签的转场动画
  477. this.trackStyle = {
  478. 'transform': isSlide ? `translate3d(${offsetWidth}px,0,0)` : `translateX(${-100 * this.value}%)`,
  479. 'transition': `transform ${duration}s ease-in-out`
  480. }
  481. },
  482. // 改变标签内容样式
  483. changePaneStyle() {
  484. this.getRect('.yui-tab__pane' + this.value).then(rect => {
  485. // 有拖动动画时,隐藏的标签内容高度取显示的标签内容高度
  486. const height = rect && this.swipeAnimated ? rect.height : 0
  487. this.tabList.forEach(tab => {
  488. const paneStyle = this.animated ? {
  489. // 有拖动动画时或指定标签内容显示时,为visible
  490. visibility: this.swipeAnimated || tab.show ? 'visible' : 'hidden',
  491. height: tab.show ? 'auto' : height + 'px'
  492. } : {
  493. display: tab.show ? 'block' : 'none'
  494. };
  495. this.$set(tab, "paneStyle", paneStyle)
  496. })
  497. })
  498. },
  499. touchStart(e) {
  500. // 禁止滑动
  501. if (!this.swipeable) return
  502. this.touchInfo.inited = true //touch开始时,将touchInfo对象设置为已初始化状态
  503. const touch = e.touches[0];
  504. // 记录touch位置的横坐标与纵坐标
  505. this.touchInfo.startX = touch.pageX
  506. this.touchInfo.startY = touch.pageY
  507. this.touchInfo.moved = false //用来判断是否是一次移动
  508. },
  509. touchMove(e, index) {
  510. if (!this.touchInfo.inited) return
  511. const {
  512. pageX,
  513. pageY
  514. } = e.changedTouches[0];
  515. const {
  516. startX,
  517. startY
  518. } = this.touchInfo || {}
  519. // 滑动方向不为左右时阻止
  520. const direction = getDirection(startX, startY, pageX, pageY)
  521. if (direction != 3 && direction != 4) return
  522. e.stopPropagation()
  523. // 横坐标偏移量
  524. const deltaX = pageX - startX
  525. // 标记是左滑还是右滑
  526. const isLeftSide = deltaX >= 0
  527. // 如果当前为第一页内容,则不允许左滑;最后一页内容,则不允许右滑
  528. if ((isLeftSide && index == 0) || (!isLeftSide && index == this.dataLen - 1)) {
  529. return
  530. }
  531. this.touchInfo.isLeftSide = isLeftSide
  532. this.touchInfo.moved = true
  533. this.touchInfo.deltaX = Math.abs(deltaX)
  534. // 改变标签内容的样式,模拟拖动动画效果
  535. if (this.swipeAnimated) {
  536. const offsetWidth = this.contentWidth * this.value * -1 + deltaX
  537. this.changeTrackStyle(true, 0, offsetWidth)
  538. }
  539. },
  540. touchEnd(e, index) {
  541. if (!this.touchInfo.moved) return
  542. const {
  543. isLeftSide,
  544. deltaX
  545. } = this.touchInfo || {}
  546. // 移动的横坐标偏移量大于指定的滚动阈值时,则切换显示状态,否则还原
  547. if (Math.abs(deltaX) > Number(this.swipeThreshold)) {
  548. // 根据是否为左滑查找需要滑动到的标签内容页下标,切换标签内容
  549. index = index + (isLeftSide ? -1 : 1)
  550. if (index > -1 && index < this.dataLen) this.handleClick(index)
  551. } else {
  552. this.changeTrackStyle(false, this.duration)
  553. }
  554. // 一次touch完成后,重置touchInfo对象尚未初始化状态
  555. this.touchInfo.inited = false
  556. },
  557. // 外层元素大小或组件显示状态变化时,可以调用此方法来触发重绘
  558. resize() {
  559. this.init()
  560. },
  561. }
  562. }
  563. </script>
  564. <style lang="less" scoped>
  565. @bgColor: #fff; //背景色
  566. @themeColor: #0022AB; //主题色
  567. @inactiveColor: #646566; //标题未选中颜色
  568. @activeColor: #323233; //标题选中颜色
  569. @cardActiveColor: #fff; //type=="card"下的标题选中颜色
  570. @disabledColor: #c8c9cc; //禁用颜色
  571. @dotColor: #e53935; //小红点、徽标背景色
  572. @badgeColor: #fff; //徽标内容颜色
  573. .yui-tabs {
  574. position: relative;
  575. width: 100%;
  576. .depend-wrap {
  577. position: absolute;
  578. top: 0;
  579. }
  580. // 开启粘性定位布局
  581. &--fixed {
  582. // 导航区域包裹层
  583. .yui-tabs__wrap {
  584. position: fixed;
  585. top: 0;
  586. right: 0;
  587. left: 0;
  588. z-index: 99;
  589. }
  590. }
  591. // 不显示滚动条
  592. ::-webkit-scrollbar {
  593. display: none;
  594. width: 0;
  595. height: 0;
  596. -webkit-appearance: none;
  597. background: transparent;
  598. color: transparent;
  599. }
  600. // 导航区域包裹层
  601. &__wrap {
  602. position: relative;
  603. display: flex;
  604. align-items: center;
  605. overflow: hidden;
  606. visibility: hidden;
  607. height: 0;
  608. background: @bgColor;
  609. }
  610. // 标签页可见
  611. &--visible {
  612. // 导航区域包裹层
  613. .yui-tabs__wrap {
  614. visibility: visible;
  615. height: auto;
  616. }
  617. }
  618. // 卡片风格
  619. &--card {
  620. // 导航区域包裹层
  621. .yui-tabs__wrap {
  622. margin: 0 32rpx;
  623. border-radius: 8rpx;
  624. }
  625. }
  626. // 按钮风格
  627. &--button {
  628. // 导航区域包裹层
  629. .yui-tabs__wrap {
  630. margin: 0 32rpx;
  631. }
  632. }
  633. // scroll-view组件样式
  634. &__scroll {
  635. position: relative;
  636. width: 100%;
  637. height: 80rpx;
  638. // 允许滚动
  639. &.enable-sroll {
  640. white-space: nowrap; // 使用横向滚动时,需要给<scroll-view>添加white-space: nowrap;样式
  641. }
  642. }
  643. // 导航区域
  644. &__nav {
  645. position: relative;
  646. box-sizing: content-box;
  647. user-select: none;
  648. height: 80rpx;
  649. flex: 1;
  650. display: flex;
  651. // 导航标签
  652. .yui-tab {
  653. position: relative;
  654. display: inline-block;
  655. line-height: 80rpx;
  656. font-size: 28rpx;
  657. color: @inactiveColor;
  658. text-align: center;
  659. padding: 0 8rpx;
  660. flex: 1;
  661. cursor: pointer;
  662. -webkit-tap-highlight-color: transparent;
  663. transition-property: color background-color;
  664. transition-duration: 0.1s;
  665. // 选中状态
  666. &--active {
  667. color: @activeColor;
  668. font-weight: 500;
  669. }
  670. // 禁用状态
  671. &--disabled {
  672. color: @disabledColor;
  673. cursor: not-allowed;
  674. }
  675. // 标题文字
  676. &__text {
  677. position: relative;
  678. display: inline;
  679. }
  680. // 文字省略
  681. &__ellipsis {
  682. display: -webkit-box; //定义为盒子显示
  683. overflow: hidden;
  684. text-overflow: ellipsis; //文本溢出隐藏为省略号
  685. -webkit-line-clamp: 1; // 限制一个块元素显示的文本行数
  686. -webkit-box-orient: vertical; //盒模型子元素排列: vertical(竖排)orhorizontal(横排)
  687. }
  688. // 标签文字右上角徽标的内容
  689. &__info {
  690. display: inline-block;
  691. position: absolute;
  692. top: 0;
  693. right: 0;
  694. box-sizing: border-box;
  695. min-width: 36rpx;
  696. padding: 0 4rpx;
  697. color: @badgeColor;
  698. font-weight: 500;
  699. font-size: 18rpx;
  700. line-height: 26rpx;
  701. text-align: center;
  702. background-color: @dotColor;
  703. border-radius: 36rpx;
  704. transform: translate(50%, -50%);
  705. transform-origin: 100%;
  706. text-align: center;
  707. }
  708. &__info--dot {
  709. line-height: unset;
  710. padding: 0;
  711. width: 12rpx;
  712. min-width: 0;
  713. height: 12rpx;
  714. background-color: @dotColor;
  715. border-radius: 100%;
  716. }
  717. }
  718. // 文本风格
  719. &--text {
  720. .yui-tab {
  721. &--active {
  722. color: @themeColor;
  723. }
  724. }
  725. }
  726. // 卡片风格
  727. &--card {
  728. box-sizing: border-box;
  729. border: 2rpx solid @themeColor;
  730. border-radius: 8rpx;
  731. .yui-tab {
  732. color: @themeColor;
  733. &--active {
  734. background-color: @themeColor;
  735. color: @cardActiveColor;
  736. }
  737. }
  738. }
  739. // 按钮风格
  740. &--button {
  741. .yui-tab {
  742. height: 40rpx;
  743. line-height: 40rpx;
  744. margin-top: 20rpx;
  745. border-radius: 4rpx;
  746. flex: unset;
  747. &--active {
  748. background-color: @themeColor;
  749. color: @cardActiveColor;
  750. }
  751. }
  752. }
  753. }
  754. // 标签右侧的补充区域
  755. &__extra {
  756. position: relative;
  757. display: inline-flex;
  758. white-space: nowrap;
  759. }
  760. // 底部线条
  761. &__line {
  762. position: absolute;
  763. bottom: 6rpx;
  764. left: 0;
  765. width: 40rpx;
  766. height: 6rpx;
  767. background-color: @themeColor;
  768. border-radius: 6rpx;
  769. transform: translateX(-100%) translateX(-50%);
  770. // transition-duration: 0.3s;
  771. }
  772. // 标签内容的滑动轨道容器
  773. &__track {
  774. position: relative;
  775. display: flex;
  776. width: 100%;
  777. height: unset;
  778. will-change: left;
  779. background-color: @bgColor;
  780. }
  781. // 标签内容
  782. &__content {
  783. background-color: @bgColor;
  784. overflow: hidden;
  785. .yui-tab__pane {
  786. flex-shrink: 0;
  787. box-sizing: border-box;
  788. width: 100%;
  789. }
  790. }
  791. // 标签内容转场动画样式
  792. &__content--animated {
  793. overflow: hidden;
  794. .yui-tab__pane {
  795. transition-duration: 0.3s;
  796. }
  797. }
  798. }
  799. </style>