u-subsection.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <template>
  2. <view class="u-subsection" :style="[subsectionStyle]">
  3. <view class="u-item u-line-1" :style="[itemStyle(index)]" @tap="click(index)" :class="[noBorderRight(index), 'u-item-' + index]"
  4. v-for="(item, index) in listInfo" :key="index">
  5. <view :style="[textStyle(index)]" class="u-item-text u-line-1">
  6. {{ item.name }}
  7. </view>
  8. </view>
  9. <view class="u-item-bg" :style="[itemBarStyle]"></view>
  10. </view>
  11. </template>
  12. <script>
  13. /**
  14. * subsection 分段器
  15. * @description 该分段器一般用于用户从几个选项中选择某一个的场景
  16. * @tutorial https://www.uviewui.com/components/subsection.html
  17. * @property {Array} list 选项的数组,形式见上方"基本使用"
  18. * @property {String Number} current 初始化时默认选中的选项索引值(默认0)
  19. * @property {String} active-color 激活时的颜色,mode为subsection时固定为白色(默认#303133)
  20. * @property {String} inactive-color 未激活时字体的颜色,mode为subsection时无效(默认#606266)
  21. * @property {String} mode 模式选择,见官网"模式选择"说明(默认button)
  22. * @property {String Number} font-size 字体大小,单位rpx(默认28)
  23. * @property {String Number} height 组件高度,单位rpx(默认70)
  24. * @property {Boolean} animation 是否开启动画效果,见上方说明(默认true)
  25. * @property {Boolean} bold 激活选项的字体是否加粗(默认true)
  26. * @property {String} bg-color 组件背景颜色,mode为button时有效(默认#eeeeef)
  27. * @property {String} button-color 按钮背景颜色,mode为button时有效(默认#ffffff)
  28. * @event {Function} change 分段器选项发生改变时触发
  29. * @example <u-subsection active-color="#ff9900"></u-subsection>
  30. */
  31. export default {
  32. name: "u-subsection",
  33. props: {
  34. // tab的数据
  35. list: {
  36. type: Array,
  37. default () {
  38. return [];
  39. }
  40. },
  41. // 当前活动的tab的index
  42. current: {
  43. type: [Number, String],
  44. default: 0
  45. },
  46. // 激活的颜色
  47. activeColor: {
  48. type: String,
  49. default: '#303133'
  50. },
  51. // 未激活的颜色
  52. inactiveColor: {
  53. type: String,
  54. default: '#606266'
  55. },
  56. // 模式选择,mode=button为按钮形式,mode=subsection时为分段模式
  57. mode: {
  58. type: String,
  59. default: 'button'
  60. },
  61. // 字体大小,单位rpx
  62. fontSize: {
  63. type: [Number, String],
  64. default: 28
  65. },
  66. // 是否开启动画效果
  67. animation: {
  68. type: Boolean,
  69. default: true
  70. },
  71. // 组件的高度,单位rpx
  72. height: {
  73. type: [Number, String],
  74. default: 70
  75. },
  76. // 激活tab的字体是否加粗
  77. bold: {
  78. type: Boolean,
  79. default: true
  80. },
  81. // mode=button时,组件背景颜色
  82. bgColor: {
  83. type: String,
  84. default: '#eeeeef'
  85. },
  86. // mode = button时,滑块背景颜色
  87. buttonColor: {
  88. type: String,
  89. default: '#ffffff'
  90. },
  91. // 在切换分段器的时候,是否让设备震一下
  92. vibrateShort: {
  93. type: Boolean,
  94. default: false
  95. }
  96. },
  97. data() {
  98. return {
  99. listInfo: [],
  100. itemBgStyle: {
  101. width: 0,
  102. left: 0,
  103. backgroundColor: '#ffffff',
  104. height: '100%',
  105. transition: ''
  106. },
  107. currentIndex: this.current,
  108. buttonPadding: 3, // mode = button 时,组件的内边距
  109. borderRadius: 5, // 圆角值
  110. firstTimeVibrateShort: true // 组件初始化时,会触发current变化,此时不应震动
  111. };
  112. },
  113. watch: {
  114. current: {
  115. immediate: true,
  116. handler(nVal) {
  117. this.currentIndex = nVal;
  118. this.changeSectionStatus(nVal);
  119. }
  120. }
  121. },
  122. created() {
  123. // 将list的数据,传入listInfo数组,因为不能修改props传递的list值
  124. // 可以接受直接数组形式,或者数组元素为对象的形式,如:['简介', '评论'],或者[{name: '简介'}, {name: '评论'}]
  125. this.listInfo = this.list.map((val, index) => {
  126. if (typeof val != 'object') {
  127. let obj = {
  128. width: 0,
  129. name: val
  130. };
  131. return obj;
  132. } else {
  133. val.width = 0;
  134. return val;
  135. }
  136. });
  137. },
  138. computed: {
  139. // 设置mode=subsection时,滑块特有的样式
  140. noBorderRight() {
  141. return index => {
  142. if (this.mode != 'subsection') return;
  143. let classs = '';
  144. // 不显示右边的边框
  145. if (index < this.list.length - 1) classs += ' u-none-border-right';
  146. // 显示整个组件的左右边圆角
  147. if (index == 0) classs += ' u-item-first';
  148. if (index == this.list.length - 1) classs += ' u-item-last';
  149. return classs;
  150. };
  151. },
  152. // 文字的样式
  153. textStyle() {
  154. return index => {
  155. let style = {};
  156. // 设置字体颜色
  157. if (this.mode == 'subsection') {
  158. if (index == this.currentIndex) {
  159. style.color = '#ffffff';
  160. } else {
  161. style.color = this.activeColor;
  162. }
  163. } else {
  164. if (index == this.currentIndex) {
  165. style.color = this.activeColor;
  166. } else {
  167. style.color = this.inactiveColor;
  168. }
  169. }
  170. // 字体加粗
  171. if (index == this.currentIndex && this.bold) style.fontWeight = 'bold';
  172. // 文字大小
  173. style.fontSize = this.fontSize + 'rpx';
  174. return style;
  175. };
  176. },
  177. // 每个分段器item的样式
  178. itemStyle() {
  179. return index => {
  180. let style = {};
  181. if (this.mode == 'subsection') {
  182. // 设置border的样式
  183. style.borderColor = this.activeColor;
  184. style.borderWidth = '1px';
  185. style.borderStyle = 'solid';
  186. }
  187. return style;
  188. };
  189. },
  190. // mode=button时,外层view的样式
  191. subsectionStyle() {
  192. let style = {};
  193. style.height = uni.upx2px(this.height) + 'px';
  194. if (this.mode == 'button') {
  195. style.backgroundColor = this.bgColor;
  196. style.padding = `${this.buttonPadding}px`;
  197. style.borderRadius = `${this.borderRadius}px`;
  198. }
  199. return style;
  200. },
  201. // 滑块的样式
  202. itemBarStyle() {
  203. let style = {};
  204. style.backgroundColor = this.activeColor;
  205. style.zIndex = 1;
  206. if (this.mode == 'button') {
  207. style.backgroundColor = this.buttonColor;
  208. style.borderRadius = `${this.borderRadius}px`;
  209. style.bottom = `${this.buttonPadding}px`;
  210. style.height = uni.upx2px(this.height) - this.buttonPadding * 2 + 'px';
  211. style.zIndex = 0;
  212. }
  213. return Object.assign(this.itemBgStyle, style);
  214. }
  215. },
  216. mounted() {
  217. setTimeout(() => {
  218. this.getTabsInfo();
  219. }, 100);
  220. },
  221. methods: {
  222. // 改变滑块的样式
  223. changeSectionStatus(nVal) {
  224. if (this.mode == 'subsection') {
  225. // 根据滑块在最左边和最右边时,显示左边和右边的圆角
  226. if (nVal == this.list.length - 1) {
  227. this.itemBgStyle.borderRadius = `0 ${this.buttonPadding}px ${this.buttonPadding}px 0`;
  228. }
  229. if (nVal == 0) {
  230. this.itemBgStyle.borderRadius = `${this.buttonPadding}px 0 0 ${this.buttonPadding}px`;
  231. }
  232. if (nVal > 0 && nVal < this.list.length - 1) {
  233. this.itemBgStyle.borderRadius = '0';
  234. }
  235. }
  236. // 更新滑块的位置
  237. setTimeout(() => {
  238. this.itemBgLeft();
  239. }, 10);
  240. if (this.vibrateShort && !this.firstTimeVibrateShort) {
  241. // 使手机产生短促震动,微信小程序有效,APP(HX 2.6.8)和H5无效
  242. // #ifndef H5
  243. uni.vibrateShort();
  244. // #endif
  245. }
  246. // 第一次过后,设置firstTimeVibrateShort为false,让其下一次可以震动(如果允许震动的话)
  247. this.firstTimeVibrateShort = false;
  248. },
  249. click(index) {
  250. // 不允许点击当前激活选项
  251. if (index == this.currentIndex) {
  252. this.$emit('change', Number(index));
  253. return
  254. };
  255. this.currentIndex = index;
  256. this.changeSectionStatus(index);
  257. this.$emit('change', Number(index));
  258. },
  259. // 获取各个tab的节点信息
  260. getTabsInfo() {
  261. let view = uni.createSelectorQuery().in(this);
  262. for (let i = 0; i < this.list.length; i++) {
  263. view.select('.u-item-' + i).boundingClientRect();
  264. }
  265. view.exec(res => {
  266. if (!res.length) {
  267. setTimeout(() => {
  268. this.getTabsInfo();
  269. return;
  270. }, 10);
  271. }
  272. // 将分段器每个item的宽度,放入listInfo数组
  273. res.map((val, index) => {
  274. this.listInfo[index].width = val.width;
  275. });
  276. // 初始化滑块的宽度
  277. if (this.mode == 'subsection') {
  278. this.itemBgStyle.width = this.listInfo[0].width + 'px';
  279. } else if (this.mode == 'button') {
  280. this.itemBgStyle.width = this.listInfo[0].width + 'px';
  281. }
  282. // 初始化滑块的位置
  283. this.itemBgLeft();
  284. });
  285. },
  286. itemBgLeft() {
  287. // 根据是否开启动画效果,
  288. if (this.animation) {
  289. this.itemBgStyle.transition = 'all 0.35s';
  290. } else {
  291. this.itemBgStyle.transition = 'all 0s';
  292. }
  293. let left = 0;
  294. // 计算当前活跃item到组件左边的距离
  295. this.listInfo.map((val, index) => {
  296. if (index < this.currentIndex) left += val.width;
  297. });
  298. // 根据mode不同模式,计算滑块需要移动的距离
  299. if (this.mode == 'subsection') {
  300. this.itemBgStyle.left = left + 'px';
  301. } else if (this.mode == 'button') {
  302. this.itemBgStyle.left = left + this.buttonPadding + 'px';
  303. }
  304. }
  305. }
  306. };
  307. </script>
  308. <style lang="scss" scoped>
  309. @import "../../libs/css/style.components.scss";
  310. .u-subsection {
  311. @include vue-flex;
  312. align-items: center;
  313. overflow: hidden;
  314. position: relative;
  315. }
  316. .u-item {
  317. flex: 1;
  318. text-align: center;
  319. font-size: 26rpx;
  320. height: 100%;
  321. @include vue-flex;
  322. align-items: center;
  323. justify-content: center;
  324. color: $u-main-color;
  325. padding: 0 6rpx;
  326. }
  327. .u-item-bg {
  328. background-color: $u-type-primary;
  329. position: absolute;
  330. z-index: -1;
  331. }
  332. .u-none-border-right {
  333. border-right: none !important;
  334. }
  335. .u-item-first {
  336. border-top-left-radius: 8rpx;
  337. border-bottom-left-radius: 8rpx;
  338. }
  339. .u-item-last {
  340. border-top-right-radius: 8rpx;
  341. border-bottom-right-radius: 8rpx;
  342. }
  343. .u-item-text {
  344. transition: all 0.35s;
  345. color: $u-main-color;
  346. @include vue-flex;
  347. align-items: center;
  348. position: relative;
  349. z-index: 3;
  350. }
  351. </style>