tm-tabs.vue 10 KB

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