tm-segTabs.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <template>
  2. <view class="tm-segTabs d-inline-block relative ">
  3. <view class="tm-segTabs-wkbody fulled flex-start relative" :class="['pa-'+gutter]"
  4. :style="{width:width>0?width+'rpx':'auto'}">
  5. <view @click="clickItem(index, item)" :class="[
  6. `px-${margin[0]} py-${margin[1]}`,
  7. `text-size-${fontSize}`,
  8. `round-${round}`,
  9. active_index == index
  10. ? 'text-weight-b ' + (black_tmeme ? `text-grey-lighten-3 ` : `text-${color_tmeme}`)
  11. : black_tmeme
  12. ? `text-grey bk`
  13. : `text-${color}`
  14. ]" :id="'tm-segTabs-item-' + index" v-for="(item, index) in listData" :key="index"
  15. class="tm-segTabs-item flex-shrink flex-center" :style="{
  16. width:width_item+'px'
  17. }">
  18. <slot name="default"
  19. :item="{color:color_tmeme, data: item, index: index, isActive: active_index == index }">
  20. {{ returnKeyValue(item) }}
  21. </slot>
  22. </view>
  23. </view>
  24. <view :class="[black_tmeme ? 'grey-darken-5' : bgColor, `round-${round}`]"
  25. class="tm-segTabs-bg absolute l-0 t-0 fulled " :style="{ height: body_height + 'px' }">
  26. <view
  27. :class="[`shadow-${activeColor}-${shadow}`,black_tmeme ? 'grey-darken-3' : activeColor, `round-${round}`,aniOn?'aniOn':'',`mt-${gutter} ml-${gutter/2}`]"
  28. class="tm-segTabs-bg-bar relative"
  29. :style="{ width: `${active_barWidth}px`, height: `${active_barHeight}px`,transform: `translateX(${left}px)` }">
  30. </view>
  31. </view>
  32. </view>
  33. </template>
  34. <script>
  35. /**
  36. * 分段器选项卡
  37. * @property {Number} value = [] 默认0,当前激活的选项.
  38. * @property {Array} list = [] 默认数据,对象数组或者字符串数组
  39. * @property {String} rang-key = [] 默认text,list对象数组时取文本的字段名称
  40. * @property {String} color = [] 默认black,默认的文字颜色
  41. * @property {String} bg-color = [] 默认grey-lighten-4,默认的背景色
  42. * @property {String} active-font-color = [] 默认black,激活的文本色
  43. * @property {String} active-color = [] 默认black,激活项的背景色
  44. * @property {String} font-size = [] 默认 n,字号,xxs,xs,s,n,g,lg,xl
  45. * @property {Array} margin = [] 默认 [24,10],左右和上下的间距,调整它可以控制宽度和高度。
  46. * @property {Number} round = [] 默认4, 圆角
  47. * @property {Number} shadow = [] 默认4, 投影
  48. * @property {Number} gutter = [] 默认4, 四边的间隙
  49. * @property {Number} width = [] 默认0, 整体的宽度,默认不自动宽度,提供了后,项目内的宽度为均分此宽度。
  50. * @property {Boolean} black = [] 默认false, 是否暗黑模式
  51. * @property {Boolean} fllow-theme = [] 默认true, 是否跟随主题切换主色。
  52. */
  53. export default {
  54. name: 'tm-segTabs',
  55. props: {
  56. value: {
  57. type: Number,
  58. defalut: 0
  59. },
  60. list: {
  61. type: Array,
  62. default: () => []
  63. },
  64. //整体的宽度,不设置使用默认计算的宽度。
  65. width: {
  66. type: Number,
  67. default: 0
  68. },
  69. // 四周的间隙
  70. gutter: {
  71. type: Number,
  72. default: 4
  73. },
  74. shadow: {
  75. type: Number,
  76. default: 4
  77. },
  78. margin: {
  79. type: Array,
  80. default: () => [24, 10]
  81. },
  82. rangKey: {
  83. type: String,
  84. default: 'text'
  85. },
  86. color: {
  87. type: String,
  88. default: 'black'
  89. },
  90. bgColor: {
  91. type: String,
  92. default: 'grey-lighten-4'
  93. },
  94. activeFontColor: {
  95. type: String,
  96. default: 'black'
  97. },
  98. activeColor: {
  99. type: String,
  100. default: 'white'
  101. },
  102. fontSize: {
  103. type: String,
  104. default: 'n'
  105. },
  106. round: {
  107. type: String | Number,
  108. default: 4
  109. },
  110. // 跟随主题色的改变而改变。
  111. fllowTheme: {
  112. type: Boolean | String,
  113. default: true
  114. },
  115. black: {
  116. type: Boolean | String,
  117. default: null
  118. }
  119. },
  120. data() {
  121. return {
  122. body_height: 0,
  123. active_barHeight: 0,
  124. active_barWidth: 0,
  125. aniOn: false,
  126. left: 0,
  127. preventLeft: 0,
  128. width_item_w: 0,
  129. };
  130. },
  131. watch: {
  132. value(newValue, oldValue) {
  133. this.active_index = newValue;
  134. },
  135. list: {
  136. deep: true,
  137. async handler() {
  138. this.width_item = this.width;
  139. await this.setInits();
  140. }
  141. }
  142. },
  143. computed: {
  144. width_item: {
  145. get: function() {
  146. return this.width_item_w;
  147. },
  148. set: function(val) {
  149. if (val == 0) {
  150. this.width_item_w = 'auto'
  151. } else {
  152. this.width_item_w = (uni.upx2px(val) / this.list.length)
  153. }
  154. }
  155. },
  156. black_tmeme: function() {
  157. if (this.black !== null) return this.black;
  158. return this.$tm.vx.state().tmVuetify.black;
  159. },
  160. color_tmeme: function() {
  161. if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
  162. .fllowTheme) {
  163. return this.$tm.vx.state().tmVuetify.color;
  164. }
  165. return this.activeFontColor;
  166. },
  167. listData: function() {
  168. return this.list;
  169. },
  170. active_index: {
  171. get: function() {
  172. return this.value;
  173. },
  174. set: function(val) {
  175. this.active = val;
  176. this.$emit('input', val);
  177. this.$emit('update:value', val);
  178. // this.$emit('change', val);
  179. this.$nextTick(function() {
  180. this.setDefaultPos();
  181. });
  182. }
  183. }
  184. },
  185. created() {
  186. this.active_index = this.value;
  187. this.width_item = this.width;
  188. },
  189. mounted() {
  190. let t = this;
  191. t.setInits();
  192. },
  193. updated() {
  194. this.setInits();
  195. },
  196. methods: {
  197. setInits() {
  198. let t = this;
  199. this.width_item = this.width;
  200. this.$nextTick(function() {
  201. uni.createSelectorQuery().in(t).select('.tm-segTabs-wkbody')
  202. .boundingClientRect().select('#tm-segTabs-item-' + this.active).boundingClientRect()
  203. .exec(function(tx) {
  204. let p = tx[0]
  205. if (!p) return;
  206. t.body_height = p.height;
  207. t.preventLeft = p.left;
  208. let p1 = tx[1]
  209. if (!p1) return;
  210. t.active_barHeight = p1.height;
  211. let left = 0;
  212. if (t.width == 0) {
  213. t.active_barWidth = p1.width;
  214. left = p1.left;
  215. } else {
  216. t.active_barWidth = t.width_item
  217. left = t.preventLeft + t.width_item * t.active;
  218. }
  219. let lsl = Math.floor((t.gutter / 2))
  220. t.left = left - t.preventLeft - uni.upx2px(lsl);
  221. t.aniOn = true;
  222. })
  223. });
  224. },
  225. returnKeyValue(item) {
  226. if (typeof item == 'string') {
  227. return item;
  228. }
  229. if (typeof item == 'object') {
  230. return item[this.rangKey];
  231. }
  232. },
  233. setDefaultPos() {
  234. let t = this;
  235. uni.createSelectorQuery().in(t).select('#tm-segTabs-item-' + this.active)
  236. .boundingClientRect().exec(
  237. function(p1) {
  238. if (!p1[0]) return;
  239. t.active_barHeight = p1[0].height;
  240. t.active_barWidth = p1[0].width;
  241. let lsl = Math.floor((t.gutter / 2))
  242. t.left = p1[0].left - t.preventLeft - uni.upx2px(lsl);
  243. })
  244. },
  245. clickItem(index, item) {
  246. this.active_index = index;
  247. this.$emit('change', index);
  248. this.$nextTick(function() {
  249. this.setDefaultPos();
  250. });
  251. }
  252. }
  253. };
  254. </script>
  255. <style lang="scss">
  256. .tm-segTabs {
  257. .tm-segTabs-wkbody {
  258. z-index: 2;
  259. .tm-segTabs-item {
  260. transition: all 0.2s linear;
  261. }
  262. }
  263. .tm-segTabs-bg {
  264. .tm-segTabs-bg-bar {
  265. &.aniOn {
  266. transition: all 0.2s ease-in-out;
  267. }
  268. }
  269. box-shadow: 0 0 3px 2px rgba(0, 0, 0, 0.02) inset;
  270. }
  271. }
  272. </style>