优化: 批A前端(timer所有权+会话族+审批conv-scoped+FilePreview+失败反馈)
A1-B1 timer所有权:新建 useTimerOwnership composable(每实例独立ref+onUnmounted清理)+ useToast _timer 下沉 + Ideas debounce 补清理(治跨实例串扰) A1-B2 FilePreview:reqSeq 双计数器守卫(loadFile/loadDiff 最新seq才写,防乱序覆盖)+ mermaid securityLevel strict + filePath 比对(防跨文件SVG注入) A1-B3 会话族:load_more 滚顶加载接线(switch透传has_more/earliest_seq+prepend去重+scrollTop恢复)+ switch失败保留视图+报错(仅对话不存在才create-new)+ new/delete失败反馈(withConvOp helper收敛)+ delete清ai_messages孤儿 A1-B5前端契约:isToolFailure 三处加 success===false 判定(useToolCard/ToolResultBody/ToolCard,git只读失败不再绿框) A2-B10 审批conv-scoped:pendingApprovals 补conversationId + cleanup 按convId filter + 移除全局清(AiError不再误清其他conv)+ :676 dir auth filter方向修 usage打标前端:is_estimated 字段+事件透传+MessageList『估算』角标(详情面板说明) A2-B9前端:clearChat try/catch 错误气泡
This commit is contained in:
+13
-13
@@ -11,7 +11,8 @@
|
||||
* showToast('导入失败', 'error', 4000)
|
||||
* // template: <div v-if="toast.visible" class="toast ...">{{ toast.msg }}</div>
|
||||
*/
|
||||
import { reactive, onUnmounted } from 'vue'
|
||||
import { reactive } from 'vue'
|
||||
import { useTimerOwnership } from './useTimerOwnership'
|
||||
|
||||
// P0-2: 加 'success'(保存成功用绿色 toast,此前成功/中性都用 info 致反馈不明确)
|
||||
export type ToastType = 'info' | 'error' | 'warning' | 'success'
|
||||
@@ -22,8 +23,6 @@ export interface ToastState {
|
||||
type: ToastType
|
||||
}
|
||||
|
||||
let _timer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
export function useToast(defaultDurationMs = 3000) {
|
||||
const toast = reactive<ToastState>({
|
||||
visible: false,
|
||||
@@ -31,30 +30,31 @@ export function useToast(defaultDurationMs = 3000) {
|
||||
type: 'info',
|
||||
})
|
||||
|
||||
// G5.4:原模块级 `let _timer` 单例被所有 useToast() 实例共享 —— 组件 B showToast/onUnmounted
|
||||
// 会清掉组件 A 的 timer,致 A 的 toast 永不隐藏(跨实例 bug)。
|
||||
// 已下沉为「每实例独立 timer」:useTimerOwnership 每实例注册表 + onUnmounted 只清自己。
|
||||
// 不再保留模块级兜底 —— 模块级共享正是串扰根源,每实例自清理即正确语义。
|
||||
const { setOwnTimeout, clearOwn } = useTimerOwnership()
|
||||
let _timer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
function showToast(msg: string, type: ToastType = 'info', durationMs?: number) {
|
||||
toast.msg = msg
|
||||
toast.type = type
|
||||
toast.visible = true
|
||||
if (_timer) clearTimeout(_timer)
|
||||
_timer = setTimeout(() => {
|
||||
if (_timer) clearOwn(_timer)
|
||||
_timer = setOwnTimeout(() => {
|
||||
_timer = null
|
||||
toast.visible = false
|
||||
}, durationMs ?? defaultDurationMs)
|
||||
}
|
||||
|
||||
function hideToast() {
|
||||
if (_timer) {
|
||||
clearTimeout(_timer)
|
||||
clearOwn(_timer)
|
||||
_timer = null
|
||||
}
|
||||
toast.visible = false
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (_timer) {
|
||||
clearTimeout(_timer)
|
||||
_timer = null
|
||||
}
|
||||
})
|
||||
|
||||
return { toast, showToast, hideToast }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user