Commit d8ea5d52 by luoqi

refactor(web): 精简成功 toast(仅留无其它反馈的)+ 模型下拉加风格标签

- 删冗余成功 toast:单条认领/回收、登录/登出、归档、点赞点踩、召回反馈(界面已就地反映);
  保留:表单保存、批量计数、异步生成完成、"为何无动作"类、占位跳转。错误 toast 全保留。
- 重生成模型下拉加风格标签:慢·精细 / 快·均衡 / 快·流畅 / 极快·简洁。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 70b70ce0
......@@ -74,10 +74,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
try {
const r = await mockLogin({ tenant: cs.tenant, role, userExternalId: cs.externalId });
applyTokens(r);
const tName = cs.tenant === 'ruier' ? '瑞尔' : '瑞泰';
toast.success(`已登录:${cs.name} · ${tName} · ${roleNameZh(role)}`, {
description: cs.clinics.map((c) => c.name).join(' / ') || '(模拟身份)',
});
// 登录成功不弹 toast —— applyTokens 后直接进入应用,页面切换本身即反馈
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
toast.error('登录失败', { description: msg.slice(0, 120) });
......@@ -93,10 +90,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
try {
const r = await mockLogin({ tenant, role: roleKey });
applyTokens(r);
const tName = TENANTS.find((t) => t.slug === tenant)?.nameZh ?? tenant;
toast.success(`已登录:${tName} · ${roleNameZh(roleKey)}`, {
description: '(模拟身份,真生产环境会接宿主 SSO)',
});
// 登录成功不弹 toast —— 直接进入应用即反馈
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
toast.error('登录失败', { description: msg.slice(0, 120) });
......
......@@ -273,17 +273,13 @@ export function PlanDetailApp({
contactAttempts: result.contactAttempts,
});
// 触达熔断是用户应知的"非预期"事件 → 保留 toast;普通归档不弹(setPlanOverride 已就地更新状态徽标即反馈)
if (result.breakerTripped) {
showToast(
'amber',
'已达触达上限,自动归档',
`本次为第 ${result.contactAttempts} 次,Plan 自动 abandoned`,
);
} else {
const statusLabel =
({ completed: '已完成', abandoned: '已放弃' } as Record<string, string>)[result.planStatus] ??
'维持当前(等下次跟进)';
showToast('emerald', '已归档', `Plan → ${statusLabel}`);
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
......@@ -442,11 +438,7 @@ export function PlanDetailApp({
showToast('slate', '本话术暂无 AI 调用记录', '兜底模板话术无法评价');
return;
}
showToast(
v === 'up' ? 'emerald' : 'amber',
v === 'up' ? '感谢反馈 · 已标记为有用' : '感谢反馈 · 已标记为待改进',
'用于后续 AI 话术优化',
);
// 成功不弹 toast —— 按钮已变色填充 + 禁用(chosen 态),本身即反馈
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
showToast('rose', '反馈提交失败', msg.slice(0, 100));
......@@ -582,7 +574,7 @@ function RecallFeedbackControl({
await plansApi.submitRecallFeedback(planId, 'up');
setFeedback('up');
setNote('');
showToast?.('emerald', '已记录召回反馈', '标记为"召回准 / 有用"');
// 成功不弹 toast —— setFeedback 已把按钮切到"已选"态,本身即反馈
} catch (e) {
showToast?.('rose', '反馈失败', e instanceof Error ? e.message.slice(0, 80) : String(e));
} finally {
......@@ -597,7 +589,7 @@ function RecallFeedbackControl({
await plansApi.submitRecallFeedback(planId, 'down', note.trim() || undefined);
setFeedback('down');
setOpen(false);
showToast?.('amber', '已记录召回反馈', note.trim() ? '问题已提交,谢谢反馈' : '已标记为"有问题"');
// 成功不弹 toast —— setFeedback 已把按钮切到"已选"态 + 弹层收起,本身即反馈
} catch (e) {
showToast?.('rose', '反馈失败', e instanceof Error ? e.message.slice(0, 80) : String(e));
} finally {
......@@ -1663,10 +1655,10 @@ function PersonaTagCloud({ features }: { features: typeof mockPersona.features }
// 话术生成模型选项(具体型号,直传后端)
const SCRIPT_MODELS: { key: ScriptModel; label: string }[] = [
{ key: 'deepseek-v4-pro', label: 'DeepSeek V4 Pro' },
{ key: 'deepseek-v4-flash', label: 'DeepSeek V4 Flash' },
{ key: 'gemini-3.5-flash', label: 'Gemini 3.5 Flash' },
{ key: 'qwen3.7-max', label: '通义千问 Max' },
{ key: 'deepseek-v4-pro', label: '慢 · 精细(DeepSeek V4 Pro)' },
{ key: 'deepseek-v4-flash', label: '快 · 均衡(DeepSeek V4 Flash)' },
{ key: 'gemini-3.5-flash', label: '快 · 流畅(Gemini 3.5 Flash)' },
{ key: 'qwen3.7-max', label: '极快 · 简洁(Qwen 3.7 Max)' },
];
// 投入档选项(跟模型并列,直传后端 tier)
......
......@@ -168,7 +168,7 @@ export function PlansListApp() {
if (!user) return;
try {
await plansApi.assign(plan.id, user.sub);
toast.success('已认领', { description: plan.patient.name ?? plan.patient.nameMasked ?? plan.patient.externalId });
// 成功不弹 toast —— refreshAll 后该行就地变为"已认领",列表本身即反馈
refreshAll();
} catch (e) {
toast.error('认领失败', { description: errMsg(e) });
......@@ -177,7 +177,7 @@ export function PlansListApp() {
const handleRecycleOne = async (plan: PlanListItem) => {
try {
await plansApi.recycle(plan.id);
toast.success('已回收到池', { description: plan.patient.name ?? plan.patient.nameMasked ?? plan.patient.externalId });
// 成功不弹 toast —— refreshAll 后该行回到池中,列表本身即反馈
refreshAll();
} catch (e) {
toast.error('回收失败', { description: errMsg(e) });
......@@ -376,7 +376,7 @@ function PageHeader({
aria-label="退出登录"
onClick={() => {
useAuthStore.getState().clear(); // 清 token → AuthGate 自动弹回登录(mock/SSO 同此口径)
toast.success('已退出登录');
// 不弹 toast —— 立即跳回登录页本身即反馈
}}
>
<LogOut className="h-4 w-4" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment