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. uni.$tm.sleep(30).then(()=>{
  192. t.setInits();
  193. })
  194. },
  195. methods: {
  196. setInits() {
  197. let t = this;
  198. this.width_item = this.width;
  199. this.$nextTick(function() {
  200. uni.createSelectorQuery().in(t).select('.tm-segTabs-wkbody')
  201. .boundingClientRect().select('#tm-segTabs-item-' + this.active).boundingClientRect()
  202. .exec(function(tx) {
  203. let p = tx[0]
  204. if (!p) return;
  205. t.body_height = p.height;
  206. t.preventLeft = p.left;
  207. let p1 = tx[1]
  208. if (!p1) return;
  209. t.active_barHeight = p1.height;
  210. let left = 0;
  211. if (t.width == 0) {
  212. t.active_barWidth = p1.width;
  213. left = p1.left;
  214. } else {
  215. t.active_barWidth = t.width_item
  216. left = t.preventLeft + t.width_item * t.active;
  217. }
  218. let lsl = Math.floor((t.gutter / 2))
  219. t.left = left - t.preventLeft - uni.upx2px(lsl);
  220. t.aniOn = true;
  221. })
  222. });
  223. },
  224. returnKeyValue(item) {
  225. if (typeof item == 'string') {
  226. return item;
  227. }
  228. if (typeof item == 'object') {
  229. return item[this.rangKey];
  230. }
  231. },
  232. setDefaultPos() {
  233. let t = this;
  234. uni.createSelectorQuery().in(t).select('#tm-segTabs-item-' + this.active)
  235. .boundingClientRect().exec(
  236. function(p1) {
  237. if (!p1[0]) return;
  238. t.active_barHeight = p1[0].height;
  239. t.active_barWidth = p1[0].width;
  240. let lsl = Math.floor((t.gutter / 2))
  241. t.left = p1[0].left - t.preventLeft - uni.upx2px(lsl);
  242. })
  243. },
  244. clickItem(index, item) {
  245. this.active_index = index;
  246. this.$emit('change', index);
  247. this.$nextTick(function() {
  248. this.setDefaultPos();
  249. });
  250. }
  251. }
  252. };
  253. </script>
  254. <style lang="scss">
  255. .tm-segTabs {
  256. .tm-segTabs-wkbody {
  257. z-index: 2;
  258. .tm-segTabs-item {
  259. transition: all 0.2s linear;
  260. }
  261. }
  262. .tm-segTabs-bg {
  263. .tm-segTabs-bg-bar {
  264. &.aniOn {
  265. transition: all 0.2s ease-in-out;
  266. }
  267. }
  268. box-shadow: 0 0 3px 2px rgba(0, 0, 0, 0.02) inset;
  269. }
  270. }
  271. </style>