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:
yml2213
2026-09-05 14:40:14 +08:00
parent 10d2e2ed3d
commit 6304f97bc3
27 changed files with 3051 additions and 2312 deletions
+112
View File
@@ -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>
)
}