97 lines
2.5 KiB
TypeScript
97 lines
2.5 KiB
TypeScript
'use client';
|
||
import { useControllableValue } from 'ahooks';
|
||
import React, { useState } from 'react';
|
||
import { cn } from '@/lib';
|
||
import Image from 'next/image';
|
||
|
||
type RateProps = {
|
||
readonly?: boolean;
|
||
// 0 - 10
|
||
value?: number;
|
||
onChange?: (value: number) => void;
|
||
} & React.HTMLAttributes<HTMLDivElement>;
|
||
export default function Rate(props: RateProps) {
|
||
const { readonly = false, ...others } = props;
|
||
const [value = 0, onChange] = useControllableValue(props);
|
||
const [hoveredValue, setHoveredValue] = useState(0);
|
||
|
||
const readonlyRender = () => {
|
||
// value 范围 0-10,每颗星代表 2 分
|
||
const fullCounts = Math.floor(value / 2); // 满星数量
|
||
const halfCount = value % 2; // 半星数量(0 或 1)
|
||
const emptyCount = 5 - fullCounts - halfCount; // 空星数量
|
||
|
||
const starDoms = [
|
||
// 满星
|
||
...Array.from({ length: fullCounts }, (_, i) => (
|
||
<Image
|
||
key={`full-${i}`}
|
||
src={'/component/star_full.svg'}
|
||
width={20}
|
||
height={20}
|
||
alt="full star"
|
||
/>
|
||
)),
|
||
// 半星
|
||
...Array.from({ length: halfCount }, (_, i) => (
|
||
<Image
|
||
key={`half-${i}`}
|
||
src={'/component/star_half.svg'}
|
||
width={20}
|
||
height={20}
|
||
alt="half star"
|
||
/>
|
||
)),
|
||
// 空星
|
||
];
|
||
return (
|
||
<>
|
||
{starDoms}
|
||
<span className="font-normal" style={{ color: 'rgba(255, 225, 0, 1)' }}>
|
||
{value}
|
||
</span>
|
||
</>
|
||
);
|
||
};
|
||
|
||
const editRender = () => {
|
||
const finalValue = hoveredValue || value;
|
||
// 将 0-10 的值转换为 0-5 的星级
|
||
const starValue = finalValue / 2;
|
||
|
||
return Array.from({ length: 5 }, (_, index) => {
|
||
let src = '/component/star_empty.svg';
|
||
// 判断当前星星应该显示什么状态
|
||
if (index < Math.floor(starValue)) {
|
||
// 满星
|
||
src = '/component/star_full.svg';
|
||
}
|
||
|
||
return (
|
||
<Image
|
||
key={index}
|
||
src={src}
|
||
width={20}
|
||
height={20}
|
||
alt="star"
|
||
className="cursor-pointer"
|
||
onClick={() => onChange?.((index + 1) * 2)}
|
||
onMouseEnter={() => setHoveredValue((index + 1) * 2)}
|
||
/>
|
||
);
|
||
});
|
||
};
|
||
|
||
const render = readonly ? readonlyRender : editRender;
|
||
|
||
return (
|
||
<div
|
||
{...others}
|
||
onMouseLeave={() => setHoveredValue(0)}
|
||
className={cn('flex items-center gap-1', props.className)}
|
||
>
|
||
{render()}
|
||
</div>
|
||
);
|
||
}
|