commit | author | age
|
e3ba12
|
1 |
<template> |
Z |
2 |
<view |
|
3 |
class="u-rate" |
|
4 |
:id="elId" |
|
5 |
ref="u-rate" |
|
6 |
:style="[$u.addStyle(customStyle)]" |
|
7 |
> |
|
8 |
<view |
|
9 |
class="u-rate__content" |
|
10 |
@touchmove.stop="touchMove" |
|
11 |
@touchend.stop="touchEnd" |
|
12 |
> |
|
13 |
<view |
|
14 |
class="u-rate__content__item" |
|
15 |
v-for="(item, index) in Number(count)" |
|
16 |
:key="index" |
|
17 |
:class="[elClass]" |
|
18 |
> |
|
19 |
<view |
|
20 |
class="u-rate__content__item__icon-wrap" |
|
21 |
ref="u-rate__content__item__icon-wrap" |
|
22 |
@tap.stop="clickHandler($event, index + 1)" |
|
23 |
> |
|
24 |
<u-icon |
|
25 |
:name=" |
|
26 |
Math.floor(activeIndex) > index |
|
27 |
? activeIcon |
|
28 |
: inactiveIcon |
|
29 |
" |
|
30 |
:color=" |
|
31 |
disabled |
|
32 |
? '#c8c9cc' |
|
33 |
: Math.floor(activeIndex) > index |
|
34 |
? activeColor |
|
35 |
: inactiveColor |
|
36 |
" |
|
37 |
:custom-style="{ |
|
38 |
padding: `0 ${$u.addUnit(gutter / 2)}`, |
|
39 |
}" |
|
40 |
:size="size" |
|
41 |
></u-icon> |
|
42 |
</view> |
|
43 |
<view |
|
44 |
v-if="allowHalf" |
|
45 |
@tap.stop="clickHandler($event, index + 1)" |
|
46 |
class="u-rate__content__item__icon-wrap u-rate__content__item__icon-wrap--half" |
|
47 |
:style="[{ |
|
48 |
width: $u.addUnit(rateWidth / 2), |
|
49 |
}]" |
|
50 |
ref="u-rate__content__item__icon-wrap" |
|
51 |
> |
|
52 |
<u-icon |
|
53 |
:name=" |
|
54 |
Math.ceil(activeIndex) > index |
|
55 |
? activeIcon |
|
56 |
: inactiveIcon |
|
57 |
" |
|
58 |
:color=" |
|
59 |
disabled |
|
60 |
? '#c8c9cc' |
|
61 |
: Math.ceil(activeIndex) > index |
|
62 |
? activeColor |
|
63 |
: inactiveColor |
|
64 |
" |
|
65 |
:custom-style="{ |
|
66 |
padding: `0 ${$u.addUnit(gutter / 2)}` |
|
67 |
}" |
|
68 |
:size="size" |
|
69 |
></u-icon> |
|
70 |
</view> |
|
71 |
</view> |
|
72 |
</view> |
|
73 |
</view> |
|
74 |
</template> |
|
75 |
|
|
76 |
<script> |
|
77 |
import props from './props.js'; |
|
78 |
|
|
79 |
// #ifdef APP-NVUE |
|
80 |
const dom = weex.requireModule("dom"); |
|
81 |
// #endif |
|
82 |
/** |
|
83 |
* rate 评分 |
|
84 |
* @description 该组件一般用于满意度调查,星型评分的场景 |
|
85 |
* @tutorial https://www.uviewui.com/components/rate.html |
|
86 |
* @property {String | Number} value 用于v-model双向绑定选中的星星数量 (默认 1 ) |
|
87 |
* @property {String | Number} count 最多可选的星星数量 (默认 5 ) |
|
88 |
* @property {Boolean} disabled 是否禁止用户操作 (默认 false ) |
|
89 |
* @property {Boolean} readonly 是否只读 (默认 false ) |
|
90 |
* @property {String | Number} size 星星的大小,单位px (默认 18 ) |
|
91 |
* @property {String} inactiveColor 未选中星星的颜色 (默认 '#b2b2b2' ) |
|
92 |
* @property {String} activeColor 选中的星星颜色 (默认 '#FA3534' ) |
|
93 |
* @property {String | Number} gutter 星星之间的距离 (默认 4 ) |
|
94 |
* @property {String | Number} minCount 最少选中星星的个数 (默认 1 ) |
|
95 |
* @property {Boolean} allowHalf 是否允许半星选择 (默认 false ) |
|
96 |
* @property {String} activeIcon 选中时的图标名,只能为uView的内置图标 (默认 'star-fill' ) |
|
97 |
* @property {String} inactiveIcon 未选中时的图标名,只能为uView的内置图标 (默认 'star' ) |
|
98 |
* @property {Boolean} touchable 是否可以通过滑动手势选择评分 (默认 'true' ) |
|
99 |
* @property {Object} customStyle 组件的样式,对象形式 |
|
100 |
* @event {Function} change 选中的星星发生变化时触发 |
|
101 |
* @example <u-rate :count="count" :value="2"></u-rate> |
|
102 |
*/ |
|
103 |
export default { |
|
104 |
name: "u-rate", |
|
105 |
mixins: [uni.$u.mpMixin, uni.$u.mixin,props], |
|
106 |
data() { |
|
107 |
return { |
|
108 |
// 生成一个唯一id,否则一个页面多个评分组件,会造成冲突 |
|
109 |
elId: uni.$u.guid(), |
|
110 |
elClass: uni.$u.guid(), |
|
111 |
rateBoxLeft: 0, // 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离 |
|
112 |
activeIndex: this.value, |
|
113 |
rateWidth: 0, // 每个星星的宽度 |
|
114 |
// 标识是否正在滑动,由于iOS事件上touch比click先触发,导致快速滑动结束后,接着触发click,导致事件混乱而出错 |
|
115 |
moving: false, |
|
116 |
}; |
|
117 |
}, |
|
118 |
watch: { |
|
119 |
value(val) { |
|
120 |
this.activeIndex = val; |
|
121 |
}, |
|
122 |
activeIndex: 'emitEvent' |
|
123 |
}, |
|
124 |
methods: { |
|
125 |
init() { |
|
126 |
uni.$u.sleep().then(() => { |
|
127 |
this.getRateItemRect(); |
|
128 |
this.getRateIconWrapRect(); |
|
129 |
}) |
|
130 |
}, |
|
131 |
// 获取评分组件盒子的布局信息 |
|
132 |
async getRateItemRect() { |
|
133 |
await uni.$u.sleep(); |
|
134 |
// uView封装的获取节点的方法,详见文档 |
|
135 |
// #ifndef APP-NVUE |
|
136 |
this.$uGetRect("#" + this.elId).then((res) => { |
|
137 |
this.rateBoxLeft = res.left; |
|
138 |
}); |
|
139 |
// #endif |
|
140 |
// #ifdef APP-NVUE |
|
141 |
dom.getComponentRect(this.$refs["u-rate"], (res) => { |
|
142 |
this.rateBoxLeft = res.size.left; |
|
143 |
}); |
|
144 |
// #endif |
|
145 |
}, |
|
146 |
// 获取单个星星的尺寸 |
|
147 |
getRateIconWrapRect() { |
|
148 |
// uView封装的获取节点的方法,详见文档 |
|
149 |
// #ifndef APP-NVUE |
|
150 |
this.$uGetRect("." + this.elClass).then((res) => { |
|
151 |
this.rateWidth = res.width; |
|
152 |
}); |
|
153 |
// #endif |
|
154 |
// #ifdef APP-NVUE |
|
155 |
dom.getComponentRect( |
|
156 |
this.$refs["u-rate__content__item__icon-wrap"][0], |
|
157 |
(res) => { |
|
158 |
this.rateWidth = res.size.width; |
|
159 |
} |
|
160 |
); |
|
161 |
// #endif |
|
162 |
}, |
|
163 |
// 手指滑动 |
|
164 |
touchMove(e) { |
|
165 |
// 如果禁止通过手动滑动选择,返回 |
|
166 |
if (!this.touchable) { |
|
167 |
return; |
|
168 |
} |
|
169 |
this.preventEvent(e); |
|
170 |
const x = e.changedTouches[0].pageX; |
|
171 |
this.getActiveIndex(x); |
|
172 |
}, |
|
173 |
// 停止滑动 |
|
174 |
touchEnd(e) { |
|
175 |
// 如果禁止通过手动滑动选择,返回 |
|
176 |
if (!this.touchable) { |
|
177 |
return; |
|
178 |
} |
|
179 |
this.preventEvent(e); |
|
180 |
const x = e.changedTouches[0].pageX; |
|
181 |
this.getActiveIndex(x); |
|
182 |
}, |
|
183 |
// 通过点击,直接选中 |
|
184 |
clickHandler(e, index) { |
|
185 |
// ios上,moving状态取消事件触发 |
|
186 |
if (uni.$u.os() === "ios" && this.moving) { |
|
187 |
return; |
|
188 |
} |
|
189 |
this.preventEvent(e); |
|
190 |
let x = 0; |
|
191 |
// 点击时,在nvue上,无法获得点击的坐标,所以无法实现点击半星选择 |
|
192 |
// #ifndef APP-NVUE |
|
193 |
x = e.changedTouches[0].pageX; |
|
194 |
// #endif |
|
195 |
// #ifdef APP-NVUE |
|
196 |
// nvue下,无法通过点击获得坐标信息,这里通过元素的位置尺寸值模拟坐标 |
|
197 |
x = index * this.rateWidth + this.rateBoxLeft; |
|
198 |
// #endif |
|
199 |
this.getActiveIndex(x,true); |
|
200 |
}, |
|
201 |
// 发出事件 |
|
202 |
emitEvent() { |
|
203 |
// 发出change事件 |
|
204 |
this.$emit("change", this.activeIndex); |
|
205 |
// 同时修改双向绑定的value的值 |
|
206 |
this.$emit("input", this.activeIndex); |
|
207 |
}, |
|
208 |
// 获取当前激活的评分图标 |
|
209 |
getActiveIndex(x,isClick = false) { |
|
210 |
if (this.disabled || this.readonly) { |
|
211 |
return; |
|
212 |
} |
|
213 |
// 判断当前操作的点的x坐标值,是否在允许的边界范围内 |
|
214 |
const allRateWidth = this.rateWidth * this.count + this.rateBoxLeft; |
|
215 |
// 如果小于第一个图标的左边界,设置为最小值,如果大于所有图标的宽度,则设置为最大值 |
|
216 |
x = uni.$u.range(this.rateBoxLeft, allRateWidth, x) - this.rateBoxLeft |
|
217 |
// 滑动点相对于评分盒子左边的距离 |
|
218 |
const distance = x; |
|
219 |
// 滑动的距离,相当于多少颗星星 |
|
220 |
let index; |
|
221 |
// 判断是否允许半星 |
|
222 |
if (this.allowHalf) { |
|
223 |
index = Math.floor(distance / this.rateWidth); |
|
224 |
// 取余,判断小数的区间范围 |
|
225 |
const decimal = distance % this.rateWidth; |
|
226 |
if (decimal <= this.rateWidth / 2 && decimal > 0) { |
|
227 |
index += 0.5; |
|
228 |
} else if (decimal > this.rateWidth / 2) { |
|
229 |
index++; |
|
230 |
} |
|
231 |
} else { |
|
232 |
index = Math.floor(distance / this.rateWidth); |
|
233 |
// 取余,判断小数的区间范围 |
|
234 |
const decimal = distance % this.rateWidth; |
|
235 |
// 非半星时,只有超过了图标的一半距离,才认为是选择了这颗星 |
|
236 |
if (isClick){ |
|
237 |
if (decimal > 0) index++; |
|
238 |
} else { |
|
239 |
if (decimal > this.rateWidth / 2) index++; |
|
240 |
} |
|
241 |
|
|
242 |
} |
|
243 |
this.activeIndex = Math.min(index, this.count); |
|
244 |
// 对最少颗星星的限制 |
|
245 |
if (this.activeIndex < this.minCount) { |
|
246 |
this.activeIndex = this.minCount; |
|
247 |
} |
|
248 |
|
|
249 |
// 设置延时为了让click事件在touchmove之前触发 |
|
250 |
setTimeout(() => { |
|
251 |
this.moving = true; |
|
252 |
}, 10); |
|
253 |
// 一定时间后,取消标识为移动中状态,是为了让click事件无效 |
|
254 |
setTimeout(() => { |
|
255 |
this.moving = false; |
|
256 |
}, 10); |
|
257 |
}, |
|
258 |
}, |
|
259 |
mounted() { |
|
260 |
this.init(); |
|
261 |
}, |
|
262 |
}; |
|
263 |
</script> |
|
264 |
|
|
265 |
<style lang="scss" scoped> |
|
266 |
@import "../../libs/css/components.scss"; |
|
267 |
$u-rate-margin: 0 !default; |
|
268 |
$u-rate-padding: 0 !default; |
|
269 |
$u-rate-item-icon-wrap-half-top: 0 !default; |
|
270 |
$u-rate-item-icon-wrap-half-left: 0 !default; |
|
271 |
|
|
272 |
.u-rate { |
|
273 |
@include flex; |
|
274 |
align-items: center; |
|
275 |
margin: $u-rate-margin; |
|
276 |
padding: $u-rate-padding; |
|
277 |
/* #ifndef APP-NVUE */ |
|
278 |
touch-action: none; |
|
279 |
/* #endif */ |
|
280 |
|
|
281 |
&__content { |
|
282 |
@include flex; |
|
283 |
|
|
284 |
&__item { |
|
285 |
position: relative; |
|
286 |
|
|
287 |
&__icon-wrap { |
|
288 |
&--half { |
|
289 |
position: absolute; |
|
290 |
overflow: hidden; |
|
291 |
top: $u-rate-item-icon-wrap-half-top; |
|
292 |
left: $u-rate-item-icon-wrap-half-left; |
|
293 |
} |
|
294 |
} |
|
295 |
} |
|
296 |
} |
|
297 |
} |
|
298 |
|
|
299 |
.u-icon { |
|
300 |
/* #ifndef APP-NVUE */ |
|
301 |
box-sizing: border-box; |
|
302 |
/* #endif */ |
|
303 |
} |
|
304 |
</style> |