You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
9.7 KiB

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