Files
WAI_Project_UNIX/uni_modules/cool-ui/components/cl-rate/cl-rate.uvue
2025-08-06 16:30:49 +08:00

197 lines
3.6 KiB
Plaintext

<template>
<view class="cl-rate" :class="[{ 'cl-rate--disabled': isDisabled }, pt.className]">
<view
v-for="(item, index) in max"
:key="index"
class="cl-rate__item"
:class="[
{
'cl-rate__item--active': item <= modelValue
},
pt.item?.className
]"
@touchstart="onTap(index)"
>
<cl-icon
:name="voidIcon"
:color="voidColor"
:size="size"
:pt="{
className: `${pt.icon?.className}`
}"
></cl-icon>
<cl-icon
v-if="getIconActiveWidth(item) > 0"
:name="icon"
:color="color"
:size="size"
:width="getIconActiveWidth(item)"
:pt="{
className: `absolute left-0 ${pt.icon?.className}`
}"
></cl-icon>
</view>
<cl-text
v-if="showScore"
:pt="{
className: parseClass(['cl-rate__score ml-2 font-bold', pt.score?.className])
}"
>{{ modelValue }}</cl-text
>
</view>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { parseClass, parsePt } from "@/cool";
import type { PassThroughProps } from "../../types";
import type { ClIconProps } from "../cl-icon/props";
import { useForm } from "../../hooks";
defineOptions({
name: "cl-rate"
});
// 组件属性定义
const props = defineProps({
// 样式穿透
pt: {
type: Object,
default: () => ({})
},
// 评分值
modelValue: {
type: Number,
default: 0
},
// 最大评分
max: {
type: Number,
default: 5
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 是否允许半星
allowHalf: {
type: Boolean,
default: false
},
// 是否显示分数
showScore: {
type: Boolean,
default: false
},
// 组件尺寸
size: {
type: Number,
default: 40
},
// 图标名称
icon: {
type: String,
default: "star-fill"
},
// 未激活图标
voidIcon: {
type: String,
default: "star-fill"
},
// 激活颜色
color: {
type: String,
default: "primary"
},
// 默认颜色
voidColor: {
type: String,
default: "#dddddd"
}
});
// 定义事件
const emit = defineEmits(["update:modelValue", "change"]);
// 透传样式类型定义
type PassThrough = {
className?: string;
item?: PassThroughProps;
icon?: ClIconProps;
score?: PassThroughProps;
};
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// cl-form 上下文
const { disabled } = useForm();
// 是否禁用
const isDisabled = computed(() => props.disabled || disabled.value);
// 获取图标激活宽度
function getIconActiveWidth(item: number) {
// 如果评分值大于等于当前项,返回null表示完全填充
if (props.modelValue >= item) {
return props.size;
}
// 如果评分值的整数部分小于当前项,返回0表示不填充
if (Math.floor(props.modelValue) < item - 1) {
return 0;
}
// 处理小数部分填充
return Math.floor((props.modelValue % 1) * props.size);
}
// 点击事件处理
function onTap(index: number) {
if (isDisabled.value) {
return;
}
let value: number;
if (props.allowHalf) {
// 半星逻辑:点击同一位置切换半星和整星
const currentValue = index + 1;
if (props.modelValue == currentValue) {
value = index + 0.5;
} else if (props.modelValue == index + 0.5) {
value = index;
} else {
value = currentValue;
}
} else {
value = index + 1;
}
// 确保值在有效范围内
value = Math.max(0, Math.min(value, props.max));
emit("update:modelValue", value);
emit("change", value);
}
</script>
<style lang="scss" scoped>
.cl-rate {
@apply flex flex-row items-center;
&--disabled {
@apply opacity-50;
}
&__item {
@apply flex items-center justify-center relative duration-200 overflow-hidden;
transition-property: color;
margin-right: 6rpx;
}
}
</style>