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