tm-tabs.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. <template>
  2. <view class="tm-tabs "
  3. :class="[bgColor == 'white' ? (black_tmeme ? 'bk grey-darken-4' : bgColor) : bgColor, 'shadow-' + bgColor + '-' + shadow, black_tmeme ? 'bk' : '']">
  4. <scroll-view scroll-with-animation :scroll-into-view="toTargetId" @scroll="scrollViesw" scroll-x
  5. class="tm-tabs-con ">
  6. <view class="tm-tabs-wk " :class="{
  7. 'text-align-left': align == 'left',
  8. 'text-align-right': align == 'right',
  9. 'text-align-center': align == 'center',
  10. 'flex-between': align == 'split'
  11. }">
  12. <view @click.stop.prevent="acitveItemClick(index, true, $event)"
  13. class="tm-tabs-con-item d-inline-block " :class="[
  14. `tm-tabs-con-item-${index}`,
  15. model == 'rect' ? 'border-' + color_tmeme + '-a-1' : '',
  16. index !== list.length - 1 && model == 'rect' ? 'tm-tabs-con-item-rborder' : '',
  17. active == index && model == 'rect' ? color_tmeme : ''
  18. ]" :style="{
  19. height: barheight + 'px',
  20. lineHeight: barheight + 'px'
  21. }" v-for="(item, index) in list" :key="index" :id="guid + '_' + index">
  22. <view class="tm-tabs-con-item-text px-24" :style="{
  23. fontSize: active == index ? active_font_size : font_size
  24. }" :class="[
  25. (model == 'line' || model == 'none') && active == index ? 'text-' + color_tmeme : 'text-'+fontColor,
  26. (model == 'line' || model == 'none') && active == index ? 'text-weight-b' : '',
  27. model == 'fill' && active == index ? color_tmeme: '',
  28. ]">
  29. <slot name="default" :data="item">{{ item[rangeKey] || item }}</slot>
  30. </view>
  31. </view>
  32. </view>
  33. <view v-if="model == 'line'" class="tm-tabs-con-item-border"
  34. :class="[borderColor, `shadow-${color_tmeme}-4`, isOnecLoad == false ? 'tm-tabs-con-item-border-trans' : '']"
  35. :style="{
  36. transform: `translateX(${activePos.left})`,
  37. width: activePos.width
  38. }"></view>
  39. </scroll-view>
  40. </view>
  41. </template>
  42. <script>
  43. /**
  44. * 选项卡切换
  45. * @property {String} model = [line|rect|fill] 默认:line,样式,线和框两种
  46. * @property {String} color = [] 默认:primary,主题文字颜色。
  47. * @property {String} active-border-color = [] 默认:'',底下指示线的颜色主题。
  48. * @property {String} bg-color = [] 默认:white,主题背景颜色。
  49. * @property {Number} value = [] 默认:0,当前激活的项。双向绑定使用value.sync或者v-model
  50. * @property {Number} font-size = [] 默认:28,默认字体大小,单位upx
  51. * @property {Number} font-color = [] 默认:'',默认文字颜色,默认为空,使用主题自动匹配文字色。
  52. * @property {Number} active-font-size = [] 默认:28,激活后字体大小,单位upx
  53. * @property {String} align = [center|left|right|split] 默认:center,居中,左,右,均分对齐
  54. * @property {String|Number} height = [90|100] 默认:90,高度。单位 upx
  55. * @property {Array} list = [] 默认:[],数据数组,可以是字符串数组,也可以是对象数组,需要提供rangKey
  56. * @property {String} range-key = [] 默认:'',数据数组,需要提供rangKey以显示文本。
  57. * @property {Function} change 返回当前选中的index值同v-model一样的值
  58. * @property {String} active-key-value = [] 默认:'',当前激活项(和value一样的功能),如果提供对象数组,则可以提供当前选项list[index][activeKey]的对象数据来自动解析当前选择的index项
  59. */
  60. export default {
  61. name: 'tm-tabs',
  62. model: {
  63. prop: 'value',
  64. event: 'input'
  65. },
  66. props: {
  67. // 样式,
  68. model: {
  69. type: String,
  70. default: 'line' //line|rect|fill
  71. },
  72. // 主题色包括文字颜色
  73. color: {
  74. type: String,
  75. default: 'primary'
  76. },
  77. activeBorderColor: {
  78. type: String,
  79. default: ''
  80. },
  81. // 背景颜色。
  82. bgColor: {
  83. type: String,
  84. default: 'white'
  85. },
  86. // 当前激活的项。
  87. value: {
  88. type: Number,
  89. default: 0
  90. },
  91. // 项目对齐方式。
  92. align: {
  93. type: String,
  94. default: 'center' // center|left|right|split
  95. },
  96. // 单位为upx
  97. height: {
  98. type: String | Number,
  99. default: 90
  100. },
  101. black: {
  102. type: Boolean | String,
  103. default: null
  104. },
  105. // 投影。
  106. shadow: {
  107. type: String | Number,
  108. default: 3
  109. },
  110. list: {
  111. type: Array,
  112. default: () => {
  113. // { title: '标签1', value: '' }, { title: '标签2标签标签', value: '' }, { title: '标签3', value: '' }
  114. return [];
  115. }
  116. },
  117. rangeKey: {
  118. type: String,
  119. default: ''
  120. },
  121. // 当前激活项,如果提供对象数组,则可以提供当前选项的对象数据来自动解析当前选择的index项
  122. activeKeyValue: {
  123. type: String,
  124. default: ''
  125. },
  126. fontSize: {
  127. type: Number,
  128. default: 28
  129. },
  130. //默认文字颜色,默认为空,使用主题自动匹配文字色。
  131. fontColor: {
  132. type: String,
  133. default: ''
  134. },
  135. activeFontSize: {
  136. type: Number,
  137. default: 28
  138. },
  139. // 跟随主题色的改变而改变。
  140. fllowTheme: {
  141. type: Boolean | String,
  142. default: true
  143. }
  144. },
  145. watch: {
  146. activeKeyValue: function() {
  147. this.setActiveIndex();
  148. },
  149. value: async function(val) {
  150. this.active = val;
  151. this.acitveItemClick(val, false);
  152. },
  153. active: async function(val) {
  154. this.$emit('input', val);
  155. this.$emit('update:value', val);
  156. this.$emit('change', val);
  157. },
  158. list: {
  159. deep: true,
  160. async handler() {
  161. await this.inits();
  162. }
  163. }
  164. },
  165. computed: {
  166. font_size: function() {
  167. return uni.upx2px(this.fontSize) + 'px';
  168. },
  169. active_font_size: function() {
  170. return uni.upx2px(this.activeFontSize) + 'px';
  171. },
  172. black_tmeme: function() {
  173. if (this.black !== null) return this.black;
  174. return this.$tm.vx.state().tmVuetify.black;
  175. },
  176. color_tmeme: function() {
  177. if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
  178. .fllowTheme) {
  179. return this.$tm.vx.state().tmVuetify.color;
  180. }
  181. return this.color;
  182. },
  183. borderColor: function() {
  184. if (this.$tm.vx.state().tmVuetify.color !== null && this.$tm.vx.state().tmVuetify.color && this
  185. .fllowTheme) {
  186. return this.$tm.vx.state().tmVuetify.color;
  187. }
  188. return this.activeBorderColor || this.color;
  189. },
  190. barheight: function() {
  191. let h = parseInt(this.height);
  192. if (isNaN(h) || !h) h = 90;
  193. return uni.upx2px(h);
  194. }
  195. },
  196. data() {
  197. return {
  198. active: 0,
  199. old_active: 0,
  200. guid: '',
  201. scrollObj: null,
  202. activePos: {
  203. left: 0,
  204. width: 0
  205. },
  206. preantObjinfo: null,
  207. tid: 88855565656,
  208. isOnecLoad: true,
  209. toTargetId: ''
  210. };
  211. },
  212. created() {
  213. this.guid = uni.$tm.guid();
  214. this.active = this.value;
  215. },
  216. mounted() {
  217. let t = this;
  218. uni.$tm.sleep(50).then(() => {
  219. t.inits();
  220. })
  221. },
  222. methods: {
  223. inits() {
  224. let t = this;
  225. this.setActiveIndex(this.active);
  226. let pqu = uni.createSelectorQuery().in(t)
  227. pqu.select('.tm-tabs')
  228. .boundingClientRect().exec(function(pd) {
  229. t.preantObjinfo = pd[0];
  230. t.$nextTick(function() {
  231. t.acitveItemClick(t.active, false);
  232. });
  233. })
  234. },
  235. scrollViesw(e) {
  236. this.scrollObj = e;
  237. },
  238. setLabelLeft(indexObj_now, callback) {
  239. let t = this;
  240. let e = this.scrollObj;
  241. let escroolet = 0;
  242. if (e) {
  243. escroolet = e.detail.scrollLeft;
  244. }
  245. let pqu = uni.createSelectorQuery().in(t)
  246. let ychi = this.activeFontSize == this.fontSize ? 0 : 160;
  247. uni.$tm.sleep(ychi).then(fs => {
  248. pqu.select(`.tm-tabs-con-item-${indexObj_now}`)
  249. .boundingClientRect().select(`.tm-tabs-con-item-0`).boundingClientRect().exec(
  250. function(res) {
  251. let now_Item_obj = res[0];
  252. let now_Item_one = res[1];
  253. if (now_Item_obj.id == now_Item_one.id) {
  254. // now_Item_obj.right = Math.abs(now_Item_one.left)+now_Item_one.right;
  255. // now_Item_one.right = Math.abs(now_Item_one.left)+now_Item_one.right;
  256. // now_Item_obj.left=0;
  257. // now_Item_one.left=0;
  258. }
  259. let nowId = t.guid + '_' + t.active;
  260. let dleft = now_Item_obj.left;
  261. let preventLeft = t.preantObjinfo.left;
  262. let acLeft = 0;
  263. let lftc = 0;
  264. let ch = (now_Item_obj.width - 24 - uni.upx2px(24) * 2) / 2;
  265. if (dleft <= 0) {
  266. dleft = escroolet + now_Item_obj.left;
  267. if (now_Item_obj.left == 0 && escroolet == 0) {
  268. lftc = (now_Item_obj.width - 24 - uni.upx2px(24) * 2) / 2 + 12 + 'px';
  269. } else {
  270. lftc = dleft + ch + 12 + 'px';
  271. if (now_Item_obj.id == now_Item_one.id) {
  272. let ptch = (now_Item_obj.width) / 2;
  273. lftc = ptch - 12 + 'px'
  274. }
  275. }
  276. } else {
  277. acLeft = Math.abs(now_Item_one.left >= 0 ? 0 : now_Item_one.left) + Math.abs(
  278. dleft);
  279. lftc = acLeft + uni.upx2px(24) - (now_Item_one.left >= 0 ? t.preantObjinfo
  280. .left : 0) + ch + 'px';
  281. }
  282. t.activePos = {
  283. left: lftc,
  284. // left:nowPage_x + itemObj.width + 'px',
  285. width: 24 + 'px'
  286. };
  287. t.old_active = t.active;
  288. callback();
  289. })
  290. })
  291. },
  292. setActiveIndex() {
  293. let t = this;
  294. if (typeof this.list[0] === 'object' && this.rangeKey) {
  295. let index = this.list.findIndex(item => {
  296. return item[t.rangeKey] == t.activeKeyValue;
  297. });
  298. if (index > -1) {
  299. this.active = index;
  300. }
  301. }
  302. },
  303. acitveItemClick(indx, etype, e) {
  304. let t = this;
  305. if (etype !== false) {
  306. this.isOnecLoad = false;
  307. }
  308. if (this.list.length <= 0) return;
  309. if (typeof this.list[indx] == 'undefined') return;
  310. t.active = indx;
  311. t.setLabelLeft(indx, function() {
  312. let nowScrollToid = '';
  313. let pqu = uni.createSelectorQuery().in(t)
  314. pqu.select('#' + t.guid + '_' + indx)
  315. .boundingClientRect().exec(function(pd) {
  316. let itemObj = pd[0];
  317. if (itemObj.left <= 0) {
  318. t.toTargetId = itemObj.id;
  319. } else if (itemObj.right > t.preantObjinfo.right) {
  320. t.toTargetId = itemObj.id;
  321. } else {
  322. t.toTargetId = null;
  323. }
  324. })
  325. });
  326. }
  327. }
  328. };
  329. </script>
  330. <style lang="scss" scoped>
  331. .tm-tabs {
  332. .tm-tabs-con {
  333. position: relative;
  334. width: 100%;
  335. .tm-tabs-con-item-border {
  336. height: 4px;
  337. border-radius: 4px;
  338. position: absolute;
  339. margin-top: -4px;
  340. width: 10px;
  341. &.tm-tabs-con-item-border-trans {
  342. transition: all 0.15s linear;
  343. }
  344. }
  345. .tm-tabs-wk {
  346. position: relative;
  347. left: 0;
  348. white-space: nowrap;
  349. width: 100%;
  350. .tm-tabs-con-item {
  351. flex-shrink: 0;
  352. display: inline-block;
  353. .tm-tabs-con-item-text {
  354. // transition: all 0.1s;
  355. }
  356. &.tm-tabs-con-item-rborder {
  357. border-right: 0;
  358. }
  359. }
  360. }
  361. }
  362. }
  363. </style>