v-tabs-column.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <template>
  2. <view class="v-tabs-column">
  3. <view class="v-tabs-column__bar" :style="getStyle">
  4. <scroll-view scroll-y scroll-with-animation style="height: 100%; overflow: hidden" :scroll-top="scrollTop">
  5. <view
  6. class="v-tabs-column__bar-item"
  7. v-for="(v, i) in tabs"
  8. :key="i"
  9. :style="i == current ? getActiveStyle : getInActiveStyle"
  10. :class="{ active: i == current }"
  11. @click="chooseItem(i)"
  12. >
  13. {{ field ? v[field] : v }}
  14. </view>
  15. </scroll-view>
  16. </view>
  17. <view class="v-tabs-column__container" :style="getWidth">
  18. <slot />
  19. </view>
  20. </view>
  21. </template>
  22. <script>
  23. /**
  24. * 常用于分类列表页面,展示分类导航使用
  25. * @description 常用于分类列表页面,展示分类导航使用
  26. * @tutorial https://ext.dcloud.net.cn/plugin?id=3275
  27. * @version 1.0.0
  28. * @property {String} width = '240rpx' 宽度
  29. * @property {String} height 高度
  30. * @property {String} bg-color = '#f3f3f3' 背景颜色
  31. * @property {Array} tabs 数据列表
  32. * @property {Boolean} is-tabbar = [true | false] 当有 tabbar 的时候需要设置为 true
  33. * @property {String} position = ['left' | 'right'] 位置
  34. * @property {String} field 如果使用对象,设置键名
  35. * @property {Object, String} inactiveStyle 默认选项样式
  36. * @property {Object, String} activeStyle 选中的样式
  37. * @property {String} activeColor 选中的文字颜色
  38. * @property {String} inactiveColor 默认的文字颜色
  39. * @property {String} activeFontSize 选中的文字大小
  40. * @property {String} inactiveFontSize 默认的文字大小
  41. * @property {String} padding = '30rpx' 内边距
  42. * @property {String, Number} bold = 500 选中文字加粗效果
  43. *
  44. */
  45. export default {
  46. props: {
  47. width: {
  48. type: String,
  49. default: '240rpx'
  50. },
  51. height: {
  52. type: String,
  53. default: ''
  54. },
  55. bgColor: {
  56. type: String,
  57. default: '#f3f3f3'
  58. },
  59. activeBgColor: {
  60. type: String,
  61. default: '#fff'
  62. },
  63. tabs: {
  64. type: Array,
  65. default() {
  66. return []
  67. }
  68. },
  69. isTabbar: {
  70. type: Boolean,
  71. default: false
  72. },
  73. position: {
  74. type: String,
  75. default: 'left'
  76. },
  77. field: {
  78. type: String,
  79. default: ''
  80. },
  81. inactiveStyle: {
  82. type: [Object, String],
  83. default() {
  84. return {}
  85. }
  86. },
  87. activeStyle: {
  88. type: [Object, String],
  89. default() {
  90. return {}
  91. }
  92. },
  93. activeColor: {
  94. type: String,
  95. default: '#f00'
  96. },
  97. inactiveColor: {
  98. type: String,
  99. default: '#333'
  100. },
  101. activeFontSize: {
  102. tyep: String,
  103. default: '32rpx'
  104. },
  105. inactiveFontSize: {
  106. type: String,
  107. default: '28rpx'
  108. },
  109. padding: {
  110. type: String,
  111. default: '30rpx'
  112. },
  113. bold: {
  114. type: [String, Number],
  115. default: 500
  116. },
  117. value: {
  118. type: [String, Number],
  119. default: 0
  120. },
  121. newCurrent: {
  122. type: [String, Number],
  123. default: 0
  124. },
  125. },
  126. watch: {
  127. newCurrent(newVal, oldVal) {
  128. this.current = newVal
  129. },
  130. current(newVal, oldVal) {
  131. this.$emit('input', newVal)
  132. }
  133. },
  134. data() {
  135. return {
  136. current: this.value,
  137. scrollTop: 0,
  138. containerHeight: 0,
  139. items: [] // 缓存
  140. }
  141. },
  142. methods: {
  143. getHeight() {
  144. if (this.height) {
  145. return this.height
  146. }
  147. let height = ''
  148. // 判断是否是 H5
  149. // #ifdef H5
  150. height = this.isTabbar ? 'calc(100vh - 44px - 50px)' : 'calc(100vh - 44px)'
  151. // #endif
  152. // #ifndef H5
  153. height = this.isTabbar ? 'calc(100vh - 44px)' : '100vh'
  154. // #endif
  155. return height
  156. },
  157. chooseItem(index) {
  158. if (this.current !== index) {
  159. this.current = index
  160. this.$emit('change', index)
  161. this.setPosition()
  162. }
  163. },
  164. setPosition() {
  165. const query = uni
  166. .createSelectorQuery()
  167. // #ifndef MP-ALIPAY
  168. .in(this)
  169. // #endif
  170. // 如果容器的高度已经存在,不需要再循环
  171. if (!this.containerHeight) {
  172. query
  173. .select('.v-tabs-column__bar')
  174. .boundingClientRect((data) => {
  175. // 判断属性值存在
  176. if (data && data.height) {
  177. this.containerHeight = data.height
  178. }
  179. })
  180. .exec()
  181. }
  182. // 缓存有属性,不必每次循环节点
  183. if (this.items.length <= 0) {
  184. query
  185. .selectAll('.v-tabs-column__bar-item')
  186. .boundingClientRect((data) => {
  187. this.items = data
  188. this.calcScrollTop()
  189. })
  190. .exec()
  191. } else {
  192. this.calcScrollTop()
  193. }
  194. },
  195. calcScrollTop() {
  196. if (this.items) {
  197. // 每一个 item 的高度都相等,随便取一个即可
  198. const currentHeight = (this.current + 1) * this.items[0].height
  199. // 计算滚动条距离顶部的位置
  200. this.scrollTop = currentHeight - this.containerHeight / 2
  201. }
  202. },
  203. // 将对象转换成字符串: {'font-size': '24rpx'} ====> 'font-size: 24rpx'
  204. objToString(obj) {
  205. let result = ''
  206. for (let [key, val] of Object.entries(obj)) {
  207. result += `${key}: ${val};`
  208. }
  209. return result
  210. }
  211. },
  212. computed: {
  213. // 计算 bar 的样式
  214. getStyle() {
  215. // 判断是否传入了 height,如果没有,计算一下 height(除去 navbar 和 tabbar)
  216. return this.objToString({
  217. width: this.width,
  218. height: this.getHeight(),
  219. background: this.bgColor,
  220. left: this.position === 'left' ? 0 : 'auto',
  221. right: this.position === 'right' ? 0 : 'auto'
  222. })
  223. },
  224. // 计算 item 激活样式
  225. getActiveStyle() {
  226. return this.objToString(
  227. Object.assign(
  228. {
  229. color: this.activeColor,
  230. padding: this.padding,
  231. background: this.activeBgColor,
  232. 'font-weight': this.bold,
  233. 'font-size': this.activeFontSize
  234. },
  235. this.activeStyle
  236. )
  237. )
  238. },
  239. // 计算 item 未激活样式
  240. getInActiveStyle() {
  241. return this.objToString(
  242. Object.assign(
  243. {
  244. color: this.inactiveColor,
  245. padding: this.padding,
  246. 'font-size': this.inactiveFontSize
  247. },
  248. this.inactiveStyle
  249. )
  250. )
  251. },
  252. // 计算容器区域宽度和内边距
  253. getWidth() {
  254. return this.objToString({
  255. width: `calc(100vw - ${this.width})`,
  256. 'padding-left': this.position == 'left' ? this.width : 0
  257. })
  258. }
  259. }
  260. }
  261. </script>
  262. <style lang="scss" scoped>
  263. .v-tabs-column {
  264. position: relative;
  265. ::-webkit-scrollbar {
  266. display: none;
  267. }
  268. &__bar {
  269. position: fixed;
  270. &-item {
  271. position: relative;
  272. transition: all 0.3s;
  273. text-overflow: ellipsis;
  274. overflow: hidden;
  275. white-space: nowrap;
  276. }
  277. }
  278. }
  279. </style>