- Dashboard 2591→1944 行,抽出 dashboard/ 纯函数、客户资料字段与 5 个状态自持弹窗 - Settings 1602→478 行,抽出 settings/ 16 个分区模块(设置卡片、弹窗与工具函数) - VisitorChat 1343→1045 行,抽出 visitor/ 头部、消息区、输入区与评价浮层 - 均为纯代码搬移:行为、路由入口与导出不变;tsc -b 与 vite build 通过
113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
import { StarFilled } from '@ant-design/icons'
|
||
|
||
/** 评价浮层:星级 + 快捷评语 + 提交/跳过 */
|
||
export function RatingOverlay({
|
||
score,
|
||
hoverStar,
|
||
text,
|
||
submitting,
|
||
sending,
|
||
presets,
|
||
onScore,
|
||
onHover,
|
||
onText,
|
||
onSubmit,
|
||
onSkip,
|
||
onSkipAndRestart,
|
||
onTogglePreset,
|
||
}: {
|
||
score: number
|
||
hoverStar: number
|
||
text: string
|
||
submitting: boolean
|
||
sending: boolean
|
||
presets: string[]
|
||
onScore: (star: number) => void
|
||
onHover: (star: number) => void
|
||
onText: (next: string) => void
|
||
onSubmit: () => void
|
||
onSkip: () => void
|
||
onSkipAndRestart: () => void
|
||
/** 快捷评语:重复点击同一标签取消选中 */
|
||
onTogglePreset: (preset: string) => void
|
||
}) {
|
||
return (
|
||
<div className="absolute inset-0 bg-black/40 flex items-center justify-center z-10 p-4">
|
||
<div className="bg-white rounded-xl p-5 w-full max-w-xs text-center shadow-lg">
|
||
<div className="text-lg font-semibold text-neutral-800 mb-1">本次服务如何?</div>
|
||
<div className="text-sm text-neutral-400 mb-3">先点亮星级,可补充感受后再提交</div>
|
||
<div className="flex justify-center gap-1.5 mb-1">
|
||
{[1, 2, 3, 4, 5].map(star => {
|
||
const active = star <= (hoverStar || score)
|
||
return (
|
||
<StarFilled
|
||
key={star}
|
||
className="text-2xl cursor-pointer transition-colors"
|
||
style={{ color: active ? '#facc15' : '#e2e8f0' }}
|
||
onMouseEnter={() => onHover(star)}
|
||
onMouseLeave={() => onHover(0)}
|
||
onClick={() => onScore(star)}
|
||
/>
|
||
)
|
||
})}
|
||
</div>
|
||
<div className="text-xs text-neutral-400 mb-3 h-4">
|
||
{score > 0 ? `${score} 星` : '请选择 1~5 星'}
|
||
</div>
|
||
<div className="flex flex-wrap gap-1.5 justify-center mb-3">
|
||
{presets.map(preset => {
|
||
const selected = text === preset
|
||
return (
|
||
<button
|
||
key={preset}
|
||
type="button"
|
||
onClick={() => onTogglePreset(preset)}
|
||
className={`px-2 py-1 rounded-full text-[11px] border cursor-pointer transition-colors ${
|
||
selected
|
||
? 'bg-[#2563eb] border-[#2563eb] text-white'
|
||
: 'bg-neutral-50 border-neutral-200 text-neutral-600 hover:border-blue-300 hover:text-blue-600'
|
||
}`}
|
||
>
|
||
{preset}
|
||
</button>
|
||
)
|
||
})}
|
||
</div>
|
||
<textarea
|
||
className="w-full rounded-lg border border-neutral-200 p-2 text-sm outline-none focus:border-blue-400 mb-3 resize-none text-left"
|
||
rows={3}
|
||
maxLength={500}
|
||
value={text}
|
||
onChange={e => onText(e.target.value)}
|
||
placeholder="可选:点击上方标签快速填入,或自行输入"
|
||
/>
|
||
<button
|
||
type="button"
|
||
disabled={submitting || score < 1}
|
||
onClick={onSubmit}
|
||
className="w-full h-10 rounded-xl border-0 bg-[#2563eb] hover:bg-[#1d4ed8] disabled:opacity-40 cursor-pointer text-white text-sm font-medium mb-2"
|
||
>
|
||
{submitting ? '提交中...' : '提交评价'}
|
||
</button>
|
||
<div className="flex flex-col gap-1.5">
|
||
<button
|
||
type="button"
|
||
onClick={onSkip}
|
||
className="text-sm text-neutral-400 hover:text-neutral-600 border-0 bg-transparent cursor-pointer"
|
||
>
|
||
跳过
|
||
</button>
|
||
<button
|
||
type="button"
|
||
disabled={sending || submitting}
|
||
onClick={onSkipAndRestart}
|
||
className="text-sm text-[#2563eb] hover:text-[#1d4ed8] border-0 bg-transparent cursor-pointer disabled:opacity-50"
|
||
>
|
||
跳过并重新咨询
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|