Skip to content

Commit 1333bda

Browse files
feat: ✨ Rate 支持清空评分(#1302)
✅ Closes: #1293
1 parent 41dd417 commit 1333bda

8 files changed

Lines changed: 273 additions & 12 deletions

File tree

‎docs/component/rate.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ function changeValue({ value }) {
6767
<wd-rate v-model="value" allow-half />
6868
```
6969

70+
## 允许清空评分
71+
72+
设置 `clearable` 属性,当 clearable 属性设置为 true,再次点击相同的值时,可以将值重置为 0。
73+
74+
```html
75+
<wd-rate v-model="value" clearable />
76+
```
77+
7078
## Attributes
7179

7280
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
@@ -83,6 +91,7 @@ function changeValue({ value }) {
8391
| disabled | 是否禁用 | boolean | - | false | - |
8492
| disabled-color | 禁用的图标颜色 | string | - | linear-gradient(315deg, rgba(177,177,177,1) 0%,rgba(199,199,199,1) 100%) | - |
8593
| allow-half | 是否允许半选 | boolean | - | false | 1.7.0 |
94+
| clearable | 是否允许再次点击后清除 | boolean | - | false | $LOWEST_VERSION$ |
8695

8796
## Events
8897

‎docs/en-US/component/rate.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ Set the `allowHalf` property.
6767
<wd-rate v-model="value" allow-half />
6868
```
6969

70+
## Allow Clear Rating
71+
72+
Set the `clearable` property. When clearable is set to true, clicking the same value again can reset the value to 0.
73+
74+
```html
75+
<wd-rate v-model="value" clearable />
76+
```
77+
7078
## Attributes
7179

7280
| Parameter | Description | Type | Options | Default | Version |
@@ -83,6 +91,7 @@ Set the `allowHalf` property.
8391
| disabled | Whether it's disabled | boolean | - | false | - |
8492
| disabled-color | Disabled icon color | string | - | linear-gradient(315deg, rgba(177,177,177,1) 0%,rgba(199,199,199,1) 100%) | - |
8593
| allow-half | Whether to allow half selection | boolean | - | false | 1.7.0 |
94+
| clearable | Whether to allow clear rating | boolean | - | false | $LOWEST_VERSION$ |
8695

8796
## Events
8897

‎src/locale/en-US.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,7 @@
14511451
"yue-xiu-qu": "Yuexiu",
14521452
"yue-xuan-ze": "Monthly selection",
14531453
"yun-xu-ban-xuan": "Allow half selection",
1454+
"yun-xu-qing-kong-ping-fen": "Allow clear rating",
14541455
"yun-xu-kong-zhi-bing-she-zhi-placeholder": "Allow null values and set placeholder",
14551456
"yy-nian-mm-yue-dd-ri": "YY year MM month DD day",
14561457
"yy-nian-mm-yue-dd-ri-0": "YY year MM month DD day",

‎src/locale/zh-CN.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,7 @@
14511451
"yue-xiu-qu": "越秀区",
14521452
"yue-xuan-ze": "月选择",
14531453
"yun-xu-ban-xuan": "允许半选",
1454+
"yun-xu-qing-kong-ping-fen": "允许清空评分",
14541455
"yun-xu-kong-zhi-bing-she-zhi-placeholder": "允许空值,并设置 placeholder",
14551456
"yy-nian-mm-yue-dd-ri": "YY年MM月DD日",
14561457
"yy-nian-mm-yue-dd-ri-0": "YY年MM月DD日",

‎src/subPages/rate/Index.vue‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@
3636
<demo-block :title="$t('yun-xu-ban-xuan')">
3737
<wd-rate v-model="value8" allow-half />
3838
</demo-block>
39+
40+
<demo-block :title="$t('yun-xu-qing-kong-ping-fen')">
41+
<view style="margin-bottom: 24rpx">
42+
<wd-rate v-model="value9" clearable />
43+
</view>
44+
<view>
45+
<wd-rate v-model="value10" clearable allow-half />
46+
</view>
47+
</demo-block>
3948
</page-wraper>
4049
</template>
4150
<script lang="ts" setup>
@@ -49,6 +58,8 @@ const value5 = ref<number>(4)
4958
const value6 = ref<number>(3)
5059
const value7 = ref<number>(5)
5160
const value8 = ref<number>(2.5)
61+
const value9 = ref<number>(3)
62+
const value10 = ref<number>(3.5)
5263
5364
function changeValue1({ value }: any) {
5465
console.log(value)

‎src/uni_modules/wot-design-uni/components/wd-rate/types.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,12 @@ export const rateProps = {
9292
* 类型: boolean
9393
* 默认值: false
9494
*/
95-
allowHalf: makeBooleanProp(false)
95+
allowHalf: makeBooleanProp(false),
96+
97+
/**
98+
* 当 clearable 属性设置为 true,再次点击相同的值时,可以将值重置为 0。
99+
* 类型: boolean
100+
* 默认值: false
101+
*/
102+
clearable: makeBooleanProp(false)
96103
}

‎src/uni_modules/wot-design-uni/components/wd-rate/wd-rate.vue‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
:name="isActive(rate) ? activeIcon : icon"
1313
:size="size"
1414
:custom-style="rate === '100%' ? iconActiveStyle : iconStyle"
15-
@click="changeRate(index, false)"
15+
@click="handleClick(index, false)"
1616
/>
17-
<view v-if="props.allowHalf" class="wd-rate__item-half" @click.stop="changeRate(index, true)">
17+
<view v-if="props.allowHalf" class="wd-rate__item-half" @click.stop="handleClick(index, true)">
1818
<wd-icon
1919
custom-class="wd-rate__item-star"
2020
:name="isActive(rate) ? activeIcon : icon"
@@ -125,12 +125,30 @@ function computeActiveValue() {
125125
}
126126
activeValue.value = tempActiveValue
127127
}
128+
129+
/**
130+
* @description 处理点击事件
131+
* @param index 点击的索引
132+
* @param isHalf 是否为半星
133+
*/
134+
function handleClick(index: number, isHalf: boolean) {
135+
const { readonly, disabled, clearable, allowHalf, modelValue } = props
136+
if (readonly || disabled) return
137+
let value = isHalf ? index + 0.5 : index + 1
138+
// 点击清空逻辑:当点击的值与当前modelValue相等且等于最小值时允许清空
139+
if (clearable) {
140+
const minValue = allowHalf ? 0.5 : 1
141+
if (value === modelValue && value === minValue) {
142+
value = 0
143+
}
144+
}
145+
updateValue(value)
146+
}
147+
128148
/**
129-
* @description 点击icon触发组件的change事件
149+
* @description 设置评分值并触发事件
130150
*/
131-
function changeRate(index: number, isHalf: boolean) {
132-
if (props.readonly || props.disabled) return
133-
const value = isHalf ? index + 0.5 : index + 1
151+
function updateValue(value: number) {
134152
emit('update:modelValue', value)
135153
emit('change', {
136154
value
@@ -149,7 +167,8 @@ async function onTouchMove(event: TouchEvent) {
149167
const isHalf = props.allowHalf && clientX - target.left! < itemWidth / 2
150168
const value = isHalf ? targetIndex + 0.5 : targetIndex + 1
151169
if (value >= 0.5) {
152-
changeRate(targetIndex, isHalf)
170+
const value = isHalf ? targetIndex + 0.5 : targetIndex + 1
171+
updateValue(value)
153172
}
154173
}
155174
}

‎tests/components/wd-rate.test.ts‎

Lines changed: 208 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,9 @@ describe('评分组件', () => {
302302

303303
await nextTick()
304304

305-
// 直接调用组件的 changeRate 方法,模拟点击第一个星星
305+
// 直接调用组件的 handleClick 方法,模拟点击第一个星星
306306
// 这比模拟触摸事件更可靠,因为触摸事件依赖于 DOM 元素的位置
307-
await (wrapper.vm as any).changeRate(0, false)
307+
await (wrapper.vm as any).handleClick(0, false)
308308

309309
// 验证事件
310310
const emitted = wrapper.emitted() as Record<string, any[]>
@@ -326,9 +326,9 @@ describe('评分组件', () => {
326326

327327
await nextTick()
328328

329-
// 直接调用组件的 changeRate 方法,模拟点击第一个半星
329+
// 直接调用组件的 handleClick 方法,模拟点击第一个半星
330330
// 这比模拟触摸事件更可靠,因为触摸事件依赖于 DOM 元素的位置
331-
await (wrapper.vm as any).changeRate(0, true)
331+
await (wrapper.vm as any).handleClick(0, true)
332332

333333
// 验证事件
334334
const emitted = wrapper.emitted() as Record<string, any[]>
@@ -413,4 +413,208 @@ describe('评分组件', () => {
413413

414414
expect(wrapper.attributes('style')).toBe(customStyle)
415415
})
416+
417+
// 测试清空功能
418+
describe('clearable 功能测试', () => {
419+
// 测试基本清空功能
420+
test('clearable 为 true 时,点击相同的最小值可以清空', async () => {
421+
const wrapper = mount(WdRate, {
422+
props: {
423+
modelValue: 1,
424+
clearable: true
425+
}
426+
})
427+
428+
await nextTick()
429+
430+
// 调用 handleClick 方法,模拟点击第一个星星(当前值为1,最小值为1)
431+
await (wrapper.vm as any).handleClick(0, false)
432+
433+
// 验证清空事件
434+
const emitted = wrapper.emitted() as Record<string, any[]>
435+
expect(emitted['update:modelValue']).toBeTruthy()
436+
expect(emitted['update:modelValue'][0][0]).toBe(0)
437+
438+
expect(emitted['change']).toBeTruthy()
439+
expect(emitted['change'][0][0]).toEqual({ value: 0 })
440+
})
441+
442+
test('clearable 为 false 时,点击相同值不会清空', async () => {
443+
const wrapper = mount(WdRate, {
444+
props: {
445+
modelValue: 1,
446+
clearable: false
447+
}
448+
})
449+
450+
await nextTick()
451+
452+
// 调用 handleClick 方法,模拟点击第一个星星
453+
await (wrapper.vm as any).handleClick(0, false)
454+
455+
// 验证不会清空,而是设置为1
456+
const emitted = wrapper.emitted() as Record<string, any[]>
457+
expect(emitted['update:modelValue']).toBeTruthy()
458+
expect(emitted['update:modelValue'][0][0]).toBe(1)
459+
460+
expect(emitted['change']).toBeTruthy()
461+
expect(emitted['change'][0][0]).toEqual({ value: 1 })
462+
})
463+
464+
// 测试半星模式下的清空
465+
test('clearable + allowHalf 组合下,点击 0.5 可以清空', async () => {
466+
const wrapper = mount(WdRate, {
467+
props: {
468+
modelValue: 0.5,
469+
clearable: true,
470+
allowHalf: true
471+
}
472+
})
473+
474+
await nextTick()
475+
476+
// 调用 handleClick 方法,模拟点击第一个半星(当前值为0.5,最小值为0.5)
477+
await (wrapper.vm as any).handleClick(0, true)
478+
479+
// 验证清空事件
480+
const emitted = wrapper.emitted() as Record<string, any[]>
481+
expect(emitted['update:modelValue']).toBeTruthy()
482+
expect(emitted['update:modelValue'][0][0]).toBe(0)
483+
484+
expect(emitted['change']).toBeTruthy()
485+
expect(emitted['change'][0][0]).toEqual({ value: 0 })
486+
})
487+
488+
test('非最小值时点击不会清空', async () => {
489+
const wrapper = mount(WdRate, {
490+
props: {
491+
modelValue: 2,
492+
clearable: true
493+
}
494+
})
495+
496+
await nextTick()
497+
498+
// 调用 handleClick 方法,模拟点击第一个星星(当前值为2,点击值为1,不等于当前值)
499+
await (wrapper.vm as any).handleClick(0, false)
500+
501+
// 验证不会清空,而是设置为1
502+
const emitted = wrapper.emitted() as Record<string, any[]>
503+
expect(emitted['update:modelValue']).toBeTruthy()
504+
expect(emitted['update:modelValue'][0][0]).toBe(1)
505+
506+
expect(emitted['change']).toBeTruthy()
507+
expect(emitted['change'][0][0]).toEqual({ value: 1 })
508+
})
509+
510+
// 测试边界情况
511+
test('当前值为 0 时点击不会触发清空', async () => {
512+
const wrapper = mount(WdRate, {
513+
props: {
514+
modelValue: 0,
515+
clearable: true
516+
}
517+
})
518+
519+
await nextTick()
520+
521+
// 调用 handleClick 方法,模拟点击第一个星星
522+
await (wrapper.vm as any).handleClick(0, false)
523+
524+
// 验证设置为1,而不是保持0
525+
const emitted = wrapper.emitted() as Record<string, any[]>
526+
expect(emitted['update:modelValue']).toBeTruthy()
527+
expect(emitted['update:modelValue'][0][0]).toBe(1)
528+
529+
expect(emitted['change']).toBeTruthy()
530+
expect(emitted['change'][0][0]).toEqual({ value: 1 })
531+
})
532+
533+
test('点击非最小值时不会清空', async () => {
534+
const wrapper = mount(WdRate, {
535+
props: {
536+
modelValue: 3,
537+
clearable: true
538+
}
539+
})
540+
541+
await nextTick()
542+
543+
// 调用 handleClick 方法,模拟点击第三个星星(当前值为3,点击值为3,但不是最小值)
544+
await (wrapper.vm as any).handleClick(2, false)
545+
546+
// 验证不会清空,而是保持为3
547+
const emitted = wrapper.emitted() as Record<string, any[]>
548+
expect(emitted['update:modelValue']).toBeTruthy()
549+
expect(emitted['update:modelValue'][0][0]).toBe(3)
550+
551+
expect(emitted['change']).toBeTruthy()
552+
expect(emitted['change'][0][0]).toEqual({ value: 3 })
553+
})
554+
555+
// 测试与其他状态的组合
556+
test('readonly + clearable 时不会清空', async () => {
557+
const wrapper = mount(WdRate, {
558+
props: {
559+
modelValue: 1,
560+
clearable: true,
561+
readonly: true
562+
}
563+
})
564+
565+
await nextTick()
566+
567+
// 调用 handleClick 方法,模拟点击第一个星星
568+
await (wrapper.vm as any).handleClick(0, false)
569+
570+
// 验证在只读状态下不会触发任何事件
571+
const emitted = wrapper.emitted() as Record<string, any[]>
572+
expect(emitted['update:modelValue']).toBeFalsy()
573+
expect(emitted['change']).toBeFalsy()
574+
})
575+
576+
test('disabled + clearable 时不会清空', async () => {
577+
const wrapper = mount(WdRate, {
578+
props: {
579+
modelValue: 1,
580+
clearable: true,
581+
disabled: true
582+
}
583+
})
584+
585+
await nextTick()
586+
587+
// 调用 handleClick 方法,模拟点击第一个星星
588+
await (wrapper.vm as any).handleClick(0, false)
589+
590+
// 验证在禁用状态下不会触发任何事件
591+
const emitted = wrapper.emitted() as Record<string, any[]>
592+
expect(emitted['update:modelValue']).toBeFalsy()
593+
expect(emitted['change']).toBeFalsy()
594+
})
595+
596+
// 测试半星模式下非最小值不会清空
597+
test('半星模式下点击非最小值不会清空', async () => {
598+
const wrapper = mount(WdRate, {
599+
props: {
600+
modelValue: 1.5,
601+
clearable: true,
602+
allowHalf: true
603+
}
604+
})
605+
606+
await nextTick()
607+
608+
// 调用 handleClick 方法,模拟点击第二个半星(当前值为1.5,点击值为1.5,但不是最小值0.5)
609+
await (wrapper.vm as any).handleClick(1, true)
610+
611+
// 验证不会清空,而是保持为1.5
612+
const emitted = wrapper.emitted() as Record<string, any[]>
613+
expect(emitted['update:modelValue']).toBeTruthy()
614+
expect(emitted['update:modelValue'][0][0]).toBe(1.5)
615+
616+
expect(emitted['change']).toBeTruthy()
617+
expect(emitted['change'][0][0]).toEqual({ value: 1.5 })
618+
})
619+
})
416620
})

0 commit comments

Comments
 (0)