mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-06 13:05:09 +00:00
fix(webui): 修复 Toast 提示信息过长导致 UI 溢出的问题 (#1595)
- 新增路径截断工具函数,支持 Windows/Linux 长路径处理 - 创建 toast 包装器,自动截断错误信息中的长路径 - 为 Toaster 组件添加 maxWidth 和 word-break 样式防止溢出 - 更新插件配置弹窗使用新的 toast 工具
This commit is contained in:
@@ -12,6 +12,8 @@ export const Toaster = () => {
|
||||
borderRadius: '20px',
|
||||
background: isDark ? '#333' : '#fff',
|
||||
color: isDark ? '#fff' : '#333',
|
||||
maxWidth: '400px',
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Input } from '@heroui/input';
|
||||
import { Select, SelectItem } from '@heroui/select';
|
||||
import { Switch } from '@heroui/switch';
|
||||
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import toast from '@/utils/toast';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import PluginManager, { PluginConfigSchemaItem } from '@/controllers/plugin_manager';
|
||||
import key from '@/const/key';
|
||||
|
||||
60
packages/napcat-webui-frontend/src/utils/toast.ts
Normal file
60
packages/napcat-webui-frontend/src/utils/toast.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Toast 工具模块
|
||||
* 包装 react-hot-toast,自动截断长路径避免溢出
|
||||
*/
|
||||
import hotToast, { ToastOptions, Renderable, ValueOrFunction, Toast } from 'react-hot-toast';
|
||||
import { truncateErrorMessage } from './truncate';
|
||||
|
||||
type Message = ValueOrFunction<Renderable, Toast>;
|
||||
|
||||
/**
|
||||
* 包装后的 toast 对象
|
||||
* 对 error 类型的 toast 自动应用路径截断
|
||||
*/
|
||||
const toast = {
|
||||
/**
|
||||
* 显示错误 toast,自动截断长路径
|
||||
*/
|
||||
error: (message: Message, options?: ToastOptions) => {
|
||||
const truncatedMessage = typeof message === 'string'
|
||||
? truncateErrorMessage(message)
|
||||
: message;
|
||||
return hotToast.error(truncatedMessage, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示成功 toast
|
||||
*/
|
||||
success: (message: Message, options?: ToastOptions) => {
|
||||
return hotToast.success(message, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示加载中 toast
|
||||
*/
|
||||
loading: (message: Message, options?: ToastOptions) => {
|
||||
return hotToast.loading(message, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示普通 toast
|
||||
*/
|
||||
custom: hotToast.custom,
|
||||
|
||||
/**
|
||||
* 关闭 toast
|
||||
*/
|
||||
dismiss: hotToast.dismiss,
|
||||
|
||||
/**
|
||||
* 移除 toast
|
||||
*/
|
||||
remove: hotToast.remove,
|
||||
|
||||
/**
|
||||
* Promise toast
|
||||
*/
|
||||
promise: hotToast.promise,
|
||||
};
|
||||
|
||||
export default toast;
|
||||
141
packages/napcat-webui-frontend/src/utils/truncate.ts
Normal file
141
packages/napcat-webui-frontend/src/utils/truncate.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 路径截断工具函数
|
||||
*
|
||||
* 用于解决前端提示框中长路径导致内容溢出的问题。
|
||||
* 当错误消息包含过长的文件路径时,会导致提示框显示异常。
|
||||
*
|
||||
* 使用场景:
|
||||
* - Toast 消息中包含文件路径
|
||||
* - 错误提示中包含配置文件路径
|
||||
* - 任何可能因路径过长导致 UI 溢出的场景
|
||||
*
|
||||
* 兼容性:
|
||||
* - Windows 路径:D:\folder\subfolder\file (使用 \ 作为分隔符)
|
||||
* - Linux/Unix 路径:/home/user/folder/file (使用 / 作为分隔符)
|
||||
*
|
||||
* 示例:
|
||||
* - Windows: D:\NapCat.Shell-1\NapCat.Shell-2\...\data → D:\NapCat.Shell-1\...\napcat-plugin-builtin\data
|
||||
* - Linux: /home/user/projects/napcat/plugins/data → /home/user/...\plugins/data
|
||||
*/
|
||||
|
||||
/**
|
||||
* 截断长路径,保留开头和结尾部分
|
||||
*
|
||||
* @param path - 需要截断的路径(支持 Windows 和 Linux 路径格式)
|
||||
* @param maxLength - 最大允许长度,默认 60 字符
|
||||
* @returns 截断后的路径,中间用 ... 替代
|
||||
*
|
||||
* @example
|
||||
* // Windows 路径
|
||||
* truncatePath('D:\\folder1\\folder2\\folder3\\file.txt', 30)
|
||||
* // 返回: 'D:\\...\\folder3\\file.txt'
|
||||
*
|
||||
* @example
|
||||
* // Linux 路径
|
||||
* truncatePath('/home/user/projects/deep/nested/file.txt', 30)
|
||||
* // 返回: '/home/user/.../nested/file.txt'
|
||||
*/
|
||||
export function truncatePath (path: string, maxLength: number = 60): string {
|
||||
if (path.length <= maxLength) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// 自动检测路径分隔符,兼容 Windows (\) 和 Linux/Unix (/)
|
||||
const separator = path.includes('\\') ? '\\' : '/';
|
||||
const parts = path.split(separator);
|
||||
|
||||
if (parts.length <= 3) {
|
||||
// 如果路径段太少(如 D:\folder\file),直接尾部截断
|
||||
return path.substring(0, maxLength - 3) + '...';
|
||||
}
|
||||
|
||||
// 保留第一段(Windows 驱动器号如 D: 或 Linux 根目录)和最后两段(父目录+文件名)
|
||||
const firstPart = parts[0];
|
||||
const lastParts = parts.slice(-2).join(separator);
|
||||
|
||||
const truncated = `${firstPart}${separator}...${separator}${lastParts}`;
|
||||
|
||||
// 如果截断后仍然超长,回退到简单的尾部截断
|
||||
if (truncated.length > maxLength) {
|
||||
return path.substring(0, maxLength - 3) + '...';
|
||||
}
|
||||
|
||||
return truncated;
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能截断消息文本,特别处理包含路径的错误消息
|
||||
*
|
||||
* 此函数会自动检测消息中的文件路径(Windows 和 Linux 格式)并截断过长的路径,
|
||||
* 以防止 UI 组件(如 Toast、Alert)因内容过长而溢出。
|
||||
*
|
||||
* @param message - 需要处理的消息文本
|
||||
* @param maxLength - 最终消息的最大长度,默认 100 字符
|
||||
* @returns 处理后的消息,路径被截断,整体长度受限
|
||||
*
|
||||
* @example
|
||||
* // 处理包含 Windows 路径的错误消息
|
||||
* truncateErrorMessage("Save failed: Error updating config: EPERM: operation not permitted, open 'D:\\very\\long\\path\\config.json'")
|
||||
* // 返回: "Save failed: Error updating config: EPERM: operation not permitted, open 'D:\\...\\path\\config.json'"
|
||||
*
|
||||
* @example
|
||||
* // 处理包含 Linux 路径的错误消息
|
||||
* truncateErrorMessage("Failed to read /home/user/projects/napcat/very/deep/nested/config.json")
|
||||
* // 返回: "Failed to read /home/user/.../nested/config.json"
|
||||
*/
|
||||
export function truncateErrorMessage (message: string, maxLength: number = 100): string {
|
||||
if (message.length <= maxLength) {
|
||||
return message;
|
||||
}
|
||||
|
||||
// Windows 路径正则:匹配 盘符:\路径 格式,如 D:\folder\file.txt
|
||||
// 排除空白字符和引号,避免匹配到路径外的内容
|
||||
const windowsPathRegex = /[A-Za-z]:\\[^\s'"]+/g;
|
||||
|
||||
// Linux/Unix 路径正则:匹配 /开头的多级路径,如 /home/user/file
|
||||
// 要求至少有两级目录,避免匹配单独的 /
|
||||
const unixPathRegex = /\/[^\s'"]+(?:\/[^\s'"]+)+/g;
|
||||
|
||||
let result = message;
|
||||
|
||||
// 处理 Windows 路径
|
||||
const windowsPaths = message.match(windowsPathRegex);
|
||||
if (windowsPaths) {
|
||||
for (const path of windowsPaths) {
|
||||
if (path.length > 40) {
|
||||
result = result.replace(path, truncatePath(path, 40));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 Unix 路径
|
||||
const unixPaths = message.match(unixPathRegex);
|
||||
if (unixPaths) {
|
||||
for (const path of unixPaths) {
|
||||
if (path.length > 40) {
|
||||
result = result.replace(path, truncatePath(path, 40));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果处理路径后消息仍然超长,直接尾部截断
|
||||
if (result.length > maxLength) {
|
||||
return result.substring(0, maxLength - 3) + '...';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 截断普通文本(简单截断,不做路径检测)
|
||||
*
|
||||
* @param text - 需要截断的文本
|
||||
* @param maxLength - 最大长度,默认 50 字符
|
||||
* @returns 截断后的文本,超长部分用 ... 替代
|
||||
*/
|
||||
export function truncateText (text: string, maxLength: number = 50): string {
|
||||
if (text.length <= maxLength) {
|
||||
return text;
|
||||
}
|
||||
return text.substring(0, maxLength - 3) + '...';
|
||||
}
|
||||
Reference in New Issue
Block a user