refactor(web): 拆分 Dashboard/Settings/VisitorChat 超大组件为子模块
- Dashboard 2591→1944 行,抽出 dashboard/ 纯函数、客户资料字段与 5 个状态自持弹窗 - Settings 1602→478 行,抽出 settings/ 16 个分区模块(设置卡片、弹窗与工具函数) - VisitorChat 1343→1045 行,抽出 visitor/ 头部、消息区、输入区与评价浮层 - 均为纯代码搬移:行为、路由入口与导出不变;tsc -b 与 vite build 通过
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user