zhangmeng
2024-04-19 e3ba120cb766a17e098e58d11c39ffc600a3070c
commit | author | age
e3ba12 1 <template>
Z 2     <view
3         class="u-subsection"
4         ref="u-subsection"
5         :class="[`u-subsection--${mode}`]"
6         :style="[$u.addStyle(customStyle), wrapperStyle]"
7     >
8         <view
9             class="u-subsection__bar"
10             ref="u-subsection__bar"
11             :style="[barStyle]"
12             :class="[
13                 mode === 'button' && 'u-subsection--button__bar',
14                 current === 0 &&
15                     mode === 'subsection' &&
16                     'u-subsection__bar--first',
17                 current > 0 &&
18                     current < list.length - 1 &&
19                     mode === 'subsection' &&
20                     'u-subsection__bar--center',
21                 current === list.length - 1 &&
22                     mode === 'subsection' &&
23                     'u-subsection__bar--last',
24             ]"
25         ></view>
26         <view
27             class="u-subsection__item"
28             :class="[
29                 `u-subsection__item--${index}`,
30                 index < list.length - 1 &&
31                     'u-subsection__item--no-border-right',
32                 index === 0 && 'u-subsection__item--first',
33                 index === list.length - 1 && 'u-subsection__item--last',
34             ]"
35             :ref="`u-subsection__item--${index}`"
36             :style="[itemStyle(index)]"
37             @tap="clickHandler(index)"
38             v-for="(item, index) in list"
39             :key="index"
40         >
41             <text
42                 class="u-subsection__item__text"
43                 :style="[textStyle(index)]"
44                 >{{ getText(item) }}</text
45             >
46         </view>
47     </view>
48 </template>
49
50 <script>
51 // #ifdef APP-NVUE
52 const dom = uni.requireNativePlugin("dom");
53 const animation = uni.requireNativePlugin("animation");
54 // #endif
55 import props from "./props.js";
56 /**
57  * Subsection 分段器
58  * @description 该分段器一般用于用户从几个选项中选择某一个的场景
59  * @tutorial https://www.uviewui.com/components/subsection.html
60  * @property {Array}            list            tab的数据
61  * @property {String | Number}    current            当前活动的tab的index(默认 0 )
62  * @property {String}            activeColor        激活时的颜色(默认 '#3c9cff' )
63  * @property {String}            inactiveColor    未激活时的颜色(默认 '#303133' )
64  * @property {String}            mode            模式选择,mode=button为按钮形式,mode=subsection时为分段模式(默认 'button' )
65  * @property {String | Number}    fontSize        字体大小,单位px(默认 12 )
66  * @property {Boolean}            bold            激活选项的字体是否加粗(默认 true )
67  * @property {String}            bgColor            组件背景颜色,mode为button时有效(默认 '#eeeeef' )
68  * @property {Object}            customStyle        定义需要用到的外部样式
69  * @property {String}            keyName            从`list`元素对象中读取的键名(默认 'name' )
70  *
71  * @event {Function} change        分段器选项发生改变时触发  回调 index:选项的index索引值,从0开始
72  * @example <u-subsection :list="list" :current="curNow" @change="sectionChange"></u-subsection>
73  */
74 export default {
75     name: "u-subsection",
76     mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
77     data() {
78         return {
79             // 组件尺寸
80             itemRect: {
81                 width: 0,
82                 height: 0,
83             },
84         };
85     },
86     watch: {
87         list(newValue, oldValue) {
88             this.init();
89         },
90         current: {
91             immediate: true,
92             handler(n) {
93                 // #ifdef APP-NVUE
94                 // 在安卓nvue上,如果通过translateX进行位移,到最后一个时,会导致右侧无法绘制圆角
95                 // 故用animation模块进行位移
96                 const ref = this.$refs?.["u-subsection__bar"]?.ref;
97                 // 不存在ref的时候(理解为第一次初始化时,需要渲染dom,进行一定延时再获取ref),这里的100ms是经过测试得出的结果(某些安卓需要延时久一点),勿随意修改
98                 uni.$u.sleep(ref ? 0 : 100).then(() => {
99                     animation.transition(this.$refs["u-subsection__bar"].ref, {
100                         styles: {
101                             transform: `translateX(${
102                                 n * this.itemRect.width
103                             }px)`,
104                             transformOrigin: "center center",
105                         },
106                         duration: 300,
107                     });
108                 });
109                 // #endif
110             },
111         },
112     },
113     computed: {
114         wrapperStyle() {
115             const style = {};
116             // button模式时,设置背景色
117             if (this.mode === "button") {
118                 style.backgroundColor = this.bgColor;
119             }
120             return style;
121         },
122         // 滑块的样式
123         barStyle() {
124             const style = {};
125             style.width = `${this.itemRect.width}px`;
126             style.height = `${this.itemRect.height}px`;
127             // 通过translateX移动滑块,其移动的距离为索引*item的宽度
128             // #ifndef APP-NVUE
129             style.transform = `translateX(${
130                 this.current * this.itemRect.width
131             }px)`;
132             // #endif
133             if (this.mode === "subsection") {
134                 // 在subsection模式下,需要动态设置滑块的圆角,因为移动滑块使用的是translateX,无法通过父元素设置overflow: hidden隐藏滑块的直角
135                 style.backgroundColor = this.activeColor;
136             }
137             return style;
138         },
139         // 分段器item的样式
140         itemStyle(index) {
141             return (index) => {
142                 const style = {};
143                 if (this.mode === "subsection") {
144                     // 设置border的样式
145                     style.borderColor = this.activeColor;
146                     style.borderWidth = "1px";
147                     style.borderStyle = "solid";
148                 }
149                 return style;
150             };
151         },
152         // 分段器文字颜色
153         textStyle(index) {
154             return (index) => {
155                 const style = {};
156                 style.fontWeight =
157                     this.bold && this.current === index ? "bold" : "normal";
158                 style.fontSize = uni.$u.addUnit(this.fontSize);
159                 // subsection模式下,激活时默认为白色的文字
160                 if (this.mode === "subsection") {
161                     style.color =
162                         this.current === index ? "#fff" : this.inactiveColor;
163                 } else {
164                     // button模式下,激活时文字颜色默认为activeColor
165                     style.color =
166                         this.current === index
167                             ? this.activeColor
168                             : this.inactiveColor;
169                 }
170                 return style;
171             };
172         },
173     },
174     mounted() {
175         this.init();
176     },
177     methods: {
178         init() {
179             uni.$u.sleep().then(() => this.getRect());
180         },
181         // 判断展示文本
182         getText(item) {
183             return typeof item === 'object' ? item[this.keyName] : item
184         },
185         // 获取组件的尺寸
186         getRect() {
187             // #ifndef APP-NVUE
188             this.$uGetRect(".u-subsection__item--0").then((size) => {
189                 this.itemRect = size;
190             });
191             // #endif
192
193             // #ifdef APP-NVUE
194             const ref = this.$refs["u-subsection__item--0"][0];
195             ref &&
196                 dom.getComponentRect(ref, (res) => {
197                     this.itemRect = res.size;
198                 });
199             // #endif
200         },
201         clickHandler(index) {
202             this.$emit("change", index);
203         },
204     },
205 };
206 </script>
207
208 <style lang="scss" scoped>
209 @import "../../libs/css/components.scss";
210
211 .u-subsection {
212     @include flex;
213     position: relative;
214     overflow: hidden;
215     /* #ifndef APP-NVUE */
216     width: 100%;
217     box-sizing: border-box;
218     /* #endif */
219
220     &--button {
221         height: 32px;
222         background-color: rgb(238, 238, 239);
223         padding: 3px;
224         border-radius: 3px;
225         align-items: stretch;
226
227         &__bar {
228             background-color: #ffffff;
229             border-radius: 3px !important;
230         }
231     }
232
233     &--subsection {
234         height: 30px;
235     }
236
237     &__bar {
238         position: absolute;
239         /* #ifndef APP-NVUE */
240         transition-property: transform, color;
241         transition-duration: 0.3s;
242         transition-timing-function: ease-in-out;
243         /* #endif */
244
245         &--first {
246             border-top-left-radius: 3px;
247             border-bottom-left-radius: 3px;
248             border-top-right-radius: 0px;
249             border-bottom-right-radius: 0px;
250         }
251
252         &--center {
253             border-top-left-radius: 0px;
254             border-bottom-left-radius: 0px;
255             border-top-right-radius: 0px;
256             border-bottom-right-radius: 0px;
257         }
258
259         &--last {
260             border-top-left-radius: 0px;
261             border-bottom-left-radius: 0px;
262             border-top-right-radius: 3px;
263             border-bottom-right-radius: 3px;
264         }
265     }
266
267     &__item {
268         @include flex;
269         flex: 1;
270         justify-content: center;
271         align-items: center;
272         // vue环境下,需要设置相对定位,因为滑块为绝对定位,item需要在滑块的上面
273         position: relative;
274
275         &--no-border-right {
276             border-right-width: 0 !important;
277         }
278
279         &--first {
280             border-top-left-radius: 3px;
281             border-bottom-left-radius: 3px;
282         }
283
284         &--last {
285             border-top-right-radius: 3px;
286             border-bottom-right-radius: 3px;
287         }
288
289         &__text {
290             font-size: 12px;
291             line-height: 12px;
292             @include flex;
293             align-items: center;
294             transition-property: color;
295             transition-duration: 0.3s;
296         }
297     }
298 }
299 </style>