重构: 前端DRY收口+后端测试类型对齐
- 新增 useToast composable: 消除 AiChat/Settings/Projects 4处 toast 重复 统一默认3000ms(Projects原4000ms为操作类提示保留参数覆盖) - 新增 utils/json.ts parseJsonArray: 消除 parseStack/parseTags/ModuleNode 3处JSON字符串数组解析重复 - 新增 utils/html.ts escapeHtml: 消除 useMarkdown/FilePreview 2处重复 - ProjectDetail score-bar 内联三元改用 scoreTier(消除最后一处阈值硬编码) - ConversationSidebar 删除 formatTime 透传包装(直接用 formatRelative) - 清理死代码: parseTs/stringifyError/ErrorSink/_Unused 改私有或删除 wrapNakedDiff 改私有(无外部 import) - ModuleNode shortPath 改名 truncatedPath(与 useToolCard.shortPath 语义不同)
This commit is contained in:
@@ -166,9 +166,7 @@ export function useRendered(getText: () => string): {
|
||||
return { rendered, ensureLoaded }
|
||||
}
|
||||
|
||||
function escapeHtml(text: string): string {
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
}
|
||||
import { escapeHtml } from '@/utils/html'
|
||||
|
||||
/// marked 未就绪时的兜底渲染:HTML 转义 + 换行转 <br>(纯文本安全降级)
|
||||
function escapeFallback(text: string): string {
|
||||
@@ -217,7 +215,7 @@ const _DIFF_LINE_RE = /^[ \t]*[+-][^\n]*$/ // 行首可选空格/tab + +/- + 任
|
||||
const _PLUS_LINE_RE = /^[ \t]*\+[^\n]*$/ // 含 + 的行(+ 必须含的判定)
|
||||
const _FENCE_OPEN_RE = /^[ \t]{0,3}(```|~~~)/ // marked 围栏开头(行首 ≤3 空格 + 3+ 反引号/波浪)
|
||||
|
||||
export function wrapNakedDiff(text: string): string {
|
||||
function wrapNakedDiff(text: string): string {
|
||||
if (!text) return text
|
||||
// 快速短路:全文无 +/- 行首特征直接返回原文(避免大文本白跑切段)
|
||||
if (!/[+-]/.test(text)) return text
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
* 注:带 seq 竞态守卫的场景调用方需在 fn 内部判断,不在本工具职责范围。
|
||||
*/
|
||||
|
||||
import type { Ref } from 'vue'
|
||||
// (Ref import 已移除,_Unused 类型已删除)
|
||||
|
||||
/** 带 error 字段的 store state 形状(仅约束 error,其他字段任意)。 */
|
||||
export interface ErrorSink {
|
||||
/** 带 error 字段的 store state 形状(仅约束 error,其他字段任意)。仅本模块内部使用。 */
|
||||
interface ErrorSink {
|
||||
error: string | null
|
||||
}
|
||||
|
||||
@@ -40,12 +40,8 @@ export async function runWithCatch<T>(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 unknown 错误转为可读字符串(无则返 undefined,由调用方兜底 i18n)。
|
||||
*
|
||||
* Tauri invoke 抛出的通常是 string 或 Error 实例。
|
||||
*/
|
||||
export function stringifyError(e: unknown): string | undefined {
|
||||
// stringifyError 仅本模块内部使用(runWithCatch/runWithCatchGuarded 调用),不导出。
|
||||
function stringifyError(e: unknown): string | undefined {
|
||||
if (typeof e === 'string') return e
|
||||
if (e instanceof Error) return e.message || e.toString()
|
||||
if (e && typeof e === 'object' && 'toString' in e) {
|
||||
@@ -81,5 +77,4 @@ export async function runWithCatchGuarded<T>(
|
||||
}
|
||||
}
|
||||
|
||||
// 显式标记 Ref 未使用(避免未来用上时改导入)
|
||||
export type _Unused = Ref<unknown>
|
||||
// _Unused 类型从未被外部 import,已删除(同时移除上方 Ref import)。
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 轻量 toast 提示 composable — 消除 AiChat / Settings / Projects / TaskDetail 4 处重复。
|
||||
*
|
||||
* 原 4 处各自 reactive/ref + _toastTimer + showToast + onUnmounted 清理,
|
||||
* 且 Projects 用 4000ms 其余 3000ms(体验不一致 bug)。
|
||||
* 本 composable 统一默认 3000ms,支持调用方按需传 durationMs。
|
||||
*
|
||||
* 用法:
|
||||
* const { toast, showToast } = useToast()
|
||||
* showToast('保存成功')
|
||||
* showToast('导入失败', 'error', 4000)
|
||||
* // template: <div v-if="toast.visible" class="toast ...">{{ toast.msg }}</div>
|
||||
*/
|
||||
import { reactive, onUnmounted } from 'vue'
|
||||
|
||||
export type ToastType = 'info' | 'error' | 'warning'
|
||||
|
||||
export interface ToastState {
|
||||
visible: boolean
|
||||
msg: string
|
||||
type: ToastType
|
||||
}
|
||||
|
||||
let _timer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
export function useToast(defaultDurationMs = 3000) {
|
||||
const toast = reactive<ToastState>({
|
||||
visible: false,
|
||||
msg: '',
|
||||
type: 'info',
|
||||
})
|
||||
|
||||
function showToast(msg: string, type: ToastType = 'info', durationMs?: number) {
|
||||
toast.msg = msg
|
||||
toast.type = type
|
||||
toast.visible = true
|
||||
if (_timer) clearTimeout(_timer)
|
||||
_timer = setTimeout(() => {
|
||||
toast.visible = false
|
||||
}, durationMs ?? defaultDurationMs)
|
||||
}
|
||||
|
||||
function hideToast() {
|
||||
if (_timer) {
|
||||
clearTimeout(_timer)
|
||||
_timer = null
|
||||
}
|
||||
toast.visible = false
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (_timer) {
|
||||
clearTimeout(_timer)
|
||||
_timer = null
|
||||
}
|
||||
})
|
||||
|
||||
return { toast, showToast, hideToast }
|
||||
}
|
||||
Reference in New Issue
Block a user