v-tabs.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. <template>
  2. <view :id="elId" class="v-tabs">
  3. <scroll-view
  4. id="scrollContainer"
  5. :scroll-x="scroll"
  6. :scroll-left="scroll ? scrollLeft : 0"
  7. :scroll-with-animation="scroll"
  8. :style="{ position: fixed ? 'fixed' : 'relative', zIndex: 1993 }"
  9. >
  10. <view
  11. class="v-tabs__container"
  12. :style="{
  13. display: scroll ? 'inline-flex' : 'flex',
  14. whiteSpace: scroll ? 'nowrap' : 'normal',
  15. background: bgColor,
  16. height,
  17. padding
  18. }"
  19. >
  20. <view
  21. class="v-tabs__container-item"
  22. v-for="(v, i) in tabs"
  23. :key="i"
  24. :style="{
  25. color: current == i ? activeColor : color,
  26. fontSize: current == i ? fontSize : fontSize,
  27. fontWeight: bold && current == i ? 'bold' : '',
  28. justifyContent: !scroll ? 'center' : '',
  29. flex: scroll ? '' : 1,
  30. padding: paddingItem
  31. }"
  32. @click="change(i)"
  33. >
  34. {{ field ? v[field] : v }}
  35. </view>
  36. <view
  37. v-if="!pills"
  38. class="v-tabs__container-line"
  39. :style="{
  40. background: lineColor,
  41. width: lineWidth + 'px',
  42. height: lineHeight,
  43. borderRadius: lineRadius,
  44. left: lineLeft + 'px',
  45. transform: `translateX(-${lineWidth / 2}px)`
  46. }"
  47. ></view>
  48. <view
  49. v-else
  50. class="v-tabs__container-pills"
  51. :style="{
  52. background: pillsColor,
  53. borderRadius: pillsBorderRadius,
  54. left: pillsLeft + 'px',
  55. width: currentWidth + 'px',
  56. height
  57. }"
  58. ></view>
  59. </view>
  60. </scroll-view>
  61. <view
  62. class="v-tabs__placeholder"
  63. :style="{
  64. height: fixed ? height : '0',
  65. padding
  66. }"
  67. ></view>
  68. </view>
  69. </template>
  70. <script>
  71. /**
  72. * v-tabs
  73. * @property {Number} value 选中的下标
  74. * @property {Array} tabs tabs 列表
  75. * @property {String} bgColor = '#fff' 背景颜色
  76. * @property {String} color = '#333' 默认颜色
  77. * @property {String} activeColor = '#2979ff' 选中文字颜色
  78. * @property {String} fontSize = '28rpx' 默认文字大小
  79. * @property {String} activeFontSize = '28rpx' 选中文字大小
  80. * @property {Boolean} bold = [true | false] 选中文字是否加粗
  81. * @property {Boolean} scroll = [true | false] 是否滚动
  82. * @property {String} height = '60rpx' tab 的高度
  83. * @property {String} lineHeight = '10rpx' 下划线的高度
  84. * @property {String} lineColor = '#2979ff' 下划线的颜色
  85. * @property {Number} lineScale = 0.5 下划线的宽度缩放比例
  86. * @property {String} lineRadius = '10rpx' 下划线圆角
  87. * @property {Boolean} pills = [true | false] 是否胶囊样式
  88. * @property {String} pillsColor = '#2979ff' 胶囊背景色
  89. * @property {String} pillsBorderRadius = '10rpx' 胶囊圆角大小
  90. * @property {String} field 如果是对象,显示的键名
  91. * @property {Boolean} fixed = [true | false] 是否固定
  92. * @property {String} paddingItem = '0 22rpx' 选项的边距
  93. *
  94. * @event {Function(current)} change 改变标签触发
  95. */
  96. export default {
  97. props: {
  98. value: {
  99. type: Number,
  100. default: 0
  101. },
  102. tabs: {
  103. type: Array,
  104. default() {
  105. return []
  106. }
  107. },
  108. bgColor: {
  109. type: String,
  110. default: '#fff'
  111. },
  112. padding: {
  113. type: String,
  114. default: '0'
  115. },
  116. color: {
  117. type: String,
  118. default: '#333'
  119. },
  120. activeColor: {
  121. type: String,
  122. default: '#2979ff'
  123. },
  124. fontSize: {
  125. type: String,
  126. default: '28rpx'
  127. },
  128. activeFontSize: {
  129. type: String,
  130. default: '32rpx'
  131. },
  132. bold: {
  133. type: Boolean,
  134. default: true
  135. },
  136. scroll: {
  137. type: Boolean,
  138. default: true
  139. },
  140. height: {
  141. type: String,
  142. default: '70rpx'
  143. },
  144. lineColor: {
  145. type: String,
  146. default: '#2979ff'
  147. },
  148. lineHeight: {
  149. type: String,
  150. default: '10rpx'
  151. },
  152. lineScale: {
  153. type: Number,
  154. default: 0.5
  155. },
  156. lineRadius: {
  157. type: String,
  158. default: '10rpx'
  159. },
  160. pills: {
  161. type: Boolean,
  162. deafult: false
  163. },
  164. pillsColor: {
  165. type: String,
  166. default: '#2979ff'
  167. },
  168. pillsBorderRadius: {
  169. type: String,
  170. default: '10rpx'
  171. },
  172. field: {
  173. type: String,
  174. default: ''
  175. },
  176. fixed: {
  177. type: Boolean,
  178. default: false
  179. },
  180. paddingItem: {
  181. type: String,
  182. default: '0 22rpx'
  183. }
  184. },
  185. data() {
  186. return {
  187. elId: '',
  188. lineWidth: 30,
  189. currentWidth: 0, // 当前选项的宽度
  190. lineLeft: 0, // 滑块距离左侧的位置
  191. pillsLeft: 0, // 胶囊距离左侧的位置
  192. scrollLeft: 0, // 距离左边的位置
  193. containerWidth: 0, // 容器的宽度
  194. current: 0 // 当前选中项
  195. }
  196. },
  197. watch: {
  198. value(newVal) {
  199. this.current = newVal
  200. this.$nextTick(() => {
  201. this.getTabItemWidth()
  202. })
  203. },
  204. current(newVal) {
  205. this.$emit('input', newVal)
  206. },
  207. tabs(newVal) {
  208. this.$nextTick(() => {
  209. this.getTabItemWidth()
  210. })
  211. }
  212. },
  213. methods: {
  214. // 产生随机字符串
  215. randomString(len) {
  216. len = len || 32
  217. let $chars =
  218. 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
  219. let maxPos = $chars.length
  220. let pwd = ''
  221. for (let i = 0; i < len; i++) {
  222. pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
  223. }
  224. return pwd
  225. },
  226. // 切换事件
  227. change(index) {
  228. if (this.current !== index) {
  229. this.current = index
  230. this.$emit('change', index)
  231. }
  232. },
  233. // 获取左移动位置
  234. getTabItemWidth() {
  235. let query = uni
  236. .createSelectorQuery()
  237. // #ifndef MP-ALIPAY
  238. .in(this)
  239. // #endif
  240. // 获取容器的宽度
  241. query
  242. .select(`#scrollContainer`)
  243. .boundingClientRect((data) => {
  244. if (!this.containerWidth && data) {
  245. this.containerWidth = data.width
  246. }
  247. })
  248. .exec()
  249. // 获取所有的 tab-item 的宽度
  250. query
  251. .selectAll('.v-tabs__container-item')
  252. .boundingClientRect((data) => {
  253. if (!data) {
  254. return
  255. }
  256. let lineLeft = 0
  257. let currentWidth = 0
  258. if (data) {
  259. for (let i = 0; i < data.length; i++) {
  260. if (i < this.current) {
  261. lineLeft += data[i].width
  262. } else if (i == this.current) {
  263. currentWidth = data[i].width
  264. } else {
  265. break
  266. }
  267. }
  268. }
  269. // 当前滑块的宽度
  270. this.currentWidth = currentWidth
  271. // 缩放后的滑块宽度
  272. this.lineWidth = currentWidth * this.lineScale * 1
  273. // 滑块作移动的位置
  274. this.lineLeft = lineLeft + currentWidth / 2
  275. // 胶囊距离左侧的位置
  276. this.pillsLeft = lineLeft
  277. // 计算滚动的距离左侧的位置
  278. if (this.scroll) {
  279. this.scrollLeft = this.lineLeft - this.containerWidth / 2
  280. }
  281. })
  282. .exec()
  283. }
  284. },
  285. mounted() {
  286. this.elId = 'xfjpeter_' + this.randomString()
  287. this.current = this.value
  288. this.$nextTick(() => {
  289. this.getTabItemWidth()
  290. })
  291. }
  292. }
  293. </script>
  294. <style lang="scss" scoped>
  295. .v-tabs {
  296. width: 100%;
  297. box-sizing: border-box;
  298. overflow: hidden;
  299. ::-webkit-scrollbar {
  300. display: none;
  301. }
  302. &__container {
  303. min-width: 100%;
  304. position: relative;
  305. display: inline-flex;
  306. align-items: center;
  307. white-space: nowrap;
  308. overflow: hidden;
  309. &-item {
  310. display: flex;
  311. align-items: center;
  312. height: 100%;
  313. position: relative;
  314. z-index: 10;
  315. // padding: 0 11px;
  316. transition: all 0.3s;
  317. white-space: nowrap;
  318. }
  319. &-line {
  320. position: absolute;
  321. bottom: 0;
  322. transition: all 0.3s linear;
  323. }
  324. &-pills {
  325. position: absolute;
  326. transition: all 0.3s linear;
  327. z-index: 9;
  328. }
  329. }
  330. }
  331. </style>