diff --git a/Yi.Ai.Vue3/.build/plugins/fontawesome.ts b/Yi.Ai.Vue3/.build/plugins/fontawesome.ts new file mode 100644 index 00000000..22207eda --- /dev/null +++ b/Yi.Ai.Vue3/.build/plugins/fontawesome.ts @@ -0,0 +1,23 @@ +import type { Plugin } from 'vite'; +import { config, library } from '@fortawesome/fontawesome-svg-core'; +import { fas } from '@fortawesome/free-solid-svg-icons'; + +/** + * Vite 插件:配置 FontAwesome + * 预注册所有图标,避免运行时重复注册 + */ +export default function fontAwesomePlugin(): Plugin { + // 在模块加载时配置 FontAwesome + library.add(fas); + + return { + name: 'vite-plugin-fontawesome', + config() { + return { + define: { + // 确保 FontAwesome 在客户端正确初始化 + }, + }; + }, + }; +} diff --git a/Yi.Ai.Vue3/.build/plugins/index.ts b/Yi.Ai.Vue3/.build/plugins/index.ts index 122c0fe2..bed77970 100644 --- a/Yi.Ai.Vue3/.build/plugins/index.ts +++ b/Yi.Ai.Vue3/.build/plugins/index.ts @@ -10,15 +10,21 @@ import Components from 'unplugin-vue-components/vite'; import viteCompression from 'vite-plugin-compression'; import envTyped from 'vite-plugin-env-typed'; +import fontAwesomePlugin from './fontawesome'; import gitHashPlugin from './git-hash'; +import preloadPlugin from './preload'; import createSvgIcon from './svg-icon'; +import versionHtmlPlugin from './version-html'; const root = path.resolve(__dirname, '../../'); function plugins({ mode, command }: ConfigEnv): PluginOption[] { return [ + versionHtmlPlugin(), // 最先处理 HTML 版本号 gitHashPlugin(), + preloadPlugin(), UnoCSS(), + fontAwesomePlugin(), envTyped({ mode, envDir: root, @@ -35,7 +41,18 @@ function plugins({ mode, command }: ConfigEnv): PluginOption[] { dts: path.join(root, 'types', 'auto-imports.d.ts'), }), Components({ - resolvers: [ElementPlusResolver()], + resolvers: [ + ElementPlusResolver(), + // 自动导入 FontAwesomeIcon 组件 + (componentName) => { + if (componentName === 'FontAwesomeIcon') { + return { + name: 'FontAwesomeIcon', + from: '@/components/FontAwesomeIcon/index.vue', + }; + } + }, + ], dts: path.join(root, 'types', 'components.d.ts'), }), createSvgIcon(command === 'build'), diff --git a/Yi.Ai.Vue3/.build/plugins/preload.ts b/Yi.Ai.Vue3/.build/plugins/preload.ts new file mode 100644 index 00000000..4bccc738 --- /dev/null +++ b/Yi.Ai.Vue3/.build/plugins/preload.ts @@ -0,0 +1,47 @@ +import type { Plugin } from 'vite'; + +/** + * Vite 插件:资源预加载优化 + * 自动添加 Link 标签预加载关键资源 + */ +export default function preloadPlugin(): Plugin { + return { + name: 'vite-plugin-preload-optimization', + apply: 'build', + transformIndexHtml(html, context) { + // 只在生产环境添加预加载 + if (process.env.NODE_ENV === 'development') { + return html; + } + + const bundle = context.bundle || {}; + const preloadLinks: string[] = []; + + // 收集关键资源 + const criticalChunks = ['vue-vendor', 'element-plus', 'pinia']; + const criticalAssets: string[] = []; + + Object.entries(bundle).forEach(([fileName, chunk]) => { + if (chunk.type === 'chunk' && criticalChunks.some(name => fileName.includes(name))) { + criticalAssets.push(`/${fileName}`); + } + }); + + // 生成预加载标签 + criticalAssets.forEach(href => { + if (href.endsWith('.js')) { + preloadLinks.push(``); + } else if (href.endsWith('.css')) { + preloadLinks.push(``); + } + }); + + // 将预加载标签插入到 之前 + if (preloadLinks.length > 0) { + return html.replace('', `${preloadLinks.join('\n ')}\n`); + } + + return html; + }, + }; +} diff --git a/Yi.Ai.Vue3/.build/plugins/version-html.ts b/Yi.Ai.Vue3/.build/plugins/version-html.ts new file mode 100644 index 00000000..674dd304 --- /dev/null +++ b/Yi.Ai.Vue3/.build/plugins/version-html.ts @@ -0,0 +1,20 @@ +import type { Plugin } from 'vite'; +import { APP_VERSION, APP_NAME } from '../../src/config/version'; + +/** + * Vite 插件:在 HTML 中注入版本号 + * 替换 HTML 中的占位符为实际版本号 + */ +export default function versionHtmlPlugin(): Plugin { + return { + name: 'vite-plugin-version-html', + enforce: 'pre', + transformIndexHtml(html) { + // 替换 HTML 中的版本占位符 + return html + .replace(/%APP_NAME%/g, APP_NAME) + .replace(/%APP_VERSION%/g, APP_VERSION) + .replace(/%APP_FULL_NAME%/g, `${APP_NAME} ${APP_VERSION}`); + }, + }; +} diff --git a/Yi.Ai.Vue3/FONTAWESOME_MIGRATION.md b/Yi.Ai.Vue3/FONTAWESOME_MIGRATION.md new file mode 100644 index 00000000..b5ef1146 --- /dev/null +++ b/Yi.Ai.Vue3/FONTAWESOME_MIGRATION.md @@ -0,0 +1,133 @@ +# FontAwesome 图标迁移指南 + +## 迁移步骤 + +### 1. 在组件中使用 FontAwesomeIcon + +```vue + + + + +``` + +```vue + + + + + +``` + +### 2. 自动映射工具 + +使用 `getFontAwesomeIcon` 函数自动映射图标名: + +```typescript +import { getFontAwesomeIcon } from '@/utils/icon-mapping'; + +// 将 Element Plus 图标名转换为 FontAwesome 图标名 +const faIcon = getFontAwesomeIcon('Check'); // 返回 'check' +const faIcon2 = getFontAwesomeIcon('ArrowLeft'); // 返回 'arrow-left' +``` + +### 3. Props 说明 + +| Prop | 类型 | 可选值 | 说明 | +|------|------|--------|------| +| icon | string | 任意 FontAwesome 图标名 | 图标名称(不含 fa- 前缀) | +| size | string | xs, sm, lg, xl, 2x, 3x, 4x, 5x | 图标大小 | +| spin | boolean | true/false | 是否旋转 | +| pulse | boolean | true/false | 是否脉冲动画 | +| rotation | number | 0, 90, 180, 270 | 旋转角度 | + +### 4. 常用图标对照表 + +| Element Plus | FontAwesome | +|--------------|-------------| +| Check | check | +| Close | xmark | +| Delete | trash | +| Edit | pen-to-square | +| Plus | plus | +| Search | magnifying-glass | +| Refresh | rotate-right | +| Loading | spinner | +| Download | download | +| ArrowLeft | arrow-left | +| ArrowRight | arrow-right | +| User | user | +| Setting | gear | +| View | eye | +| Hide | eye-slash | +| Lock | lock | +| Share | share-nodes | +| Heart | heart | +| Star | star | +| Clock | clock | +| Calendar | calendar | +| ChatLineRound | comment | +| Bell | bell | +| Document | file | +| Picture | image | + +### 5. 批量迁移示例 + +```vue + + + + + + + + + +``` + +## 注意事项 + +1. **无需手动导入**:FontAwesomeIcon 组件已在 `vite.config.ts` 中配置为自动导入 +2. **图标名格式**:使用小写、带连字符的图标名(如 `magnifying-glass`) +3. **完整图标列表**:访问 [FontAwesome 官网](https://fontawesome.com/search?o=r&m=free) 查看所有可用图标 +4. **渐进步骤**:可以逐步迁移,Element Plus 图标和 FontAwesome 可以共存 + +## 优化建议 + +1. **减少包体积**:迁移后可以移除 `@element-plus/icons-vue` 依赖 +2. **统一图标风格**:FontAwesome 图标风格更统一 +3. **更好的性能**:FontAwesome 按需加载,不会加载未使用的图标 + +## 查找图标 + +- [Solid Icons 搜索](https://fontawesome.com/search?o=r&m=free) +- 图标名格式:`fa-solid fa-icon-name` +- 在代码中使用时只需:`icon="icon-name"` diff --git a/Yi.Ai.Vue3/index.html b/Yi.Ai.Vue3/index.html index 02766bc4..f62b1dec 100644 --- a/Yi.Ai.Vue3/index.html +++ b/Yi.Ai.Vue3/index.html @@ -17,6 +17,14 @@ + + + + + + + + diff --git a/Yi.Ai.Vue3/src/components/FontAwesomeIcon/index.ts b/Yi.Ai.Vue3/src/components/FontAwesomeIcon/index.ts new file mode 100644 index 00000000..7e1e0dc6 --- /dev/null +++ b/Yi.Ai.Vue3/src/components/FontAwesomeIcon/index.ts @@ -0,0 +1,3 @@ +export { default as FontAwesomeIcon } from './index.vue'; +export { default as FontAwesomeDemo } from './demo.vue'; +export { getFontAwesomeIcon, iconMapping } from '@/utils/icon-mapping'; diff --git a/Yi.Ai.Vue3/src/components/FontAwesomeIcon/index.vue b/Yi.Ai.Vue3/src/components/FontAwesomeIcon/index.vue new file mode 100644 index 00000000..2bb285bd --- /dev/null +++ b/Yi.Ai.Vue3/src/components/FontAwesomeIcon/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/Yi.Ai.Vue3/src/components/MarkedMarkdown/index.vue b/Yi.Ai.Vue3/src/components/MarkedMarkdown/index.vue index b457c6ad..6d0a078b 100644 --- a/Yi.Ai.Vue3/src/components/MarkedMarkdown/index.vue +++ b/Yi.Ai.Vue3/src/components/MarkedMarkdown/index.vue @@ -1,7 +1,7 @@ diff --git a/Yi.Ai.Vue3/src/components/ProductPackage/PackageTab.vue b/Yi.Ai.Vue3/src/components/ProductPackage/PackageTab.vue index 2bf3b79a..100f7ffa 100644 --- a/Yi.Ai.Vue3/src/components/ProductPackage/PackageTab.vue +++ b/Yi.Ai.Vue3/src/components/ProductPackage/PackageTab.vue @@ -1,10 +1,13 @@ diff --git a/Yi.Ai.Vue3/src/components/SupportModelProducts/indexl.vue b/Yi.Ai.Vue3/src/components/SupportModelProducts/indexl.vue index df88ebd9..c73acda0 100644 --- a/Yi.Ai.Vue3/src/components/SupportModelProducts/indexl.vue +++ b/Yi.Ai.Vue3/src/components/SupportModelProducts/indexl.vue @@ -1,4 +1,8 @@ diff --git a/Yi.Ai.Vue3/src/config/index.ts b/Yi.Ai.Vue3/src/config/index.ts index 7d60d8fc..c13f4f1c 100644 --- a/Yi.Ai.Vue3/src/config/index.ts +++ b/Yi.Ai.Vue3/src/config/index.ts @@ -12,4 +12,18 @@ export const COLLAPSE_THRESHOLD: number = 600; export const SIDE_BAR_WIDTH: number = 280; // 路由白名单地址[本地存在的路由 staticRouter.ts 中] -export const ROUTER_WHITE_LIST: string[] = ['/chat', '/chat/conversation', '/chat/image', '/chat/video', '/model-library', '/403', '/404']; +// 包含所有无需登录即可访问的公开路径 +export const ROUTER_WHITE_LIST: string[] = [ + '/chat', + '/chat/conversation', + '/chat/image', + '/chat/video', + '/chat/agent', + '/model-library', + '/products', + '/pay-result', + '/activity/:id', + '/announcement/:id', + '/403', + '/404', +]; diff --git a/Yi.Ai.Vue3/src/config/version.ts b/Yi.Ai.Vue3/src/config/version.ts new file mode 100644 index 00000000..f95b2300 --- /dev/null +++ b/Yi.Ai.Vue3/src/config/version.ts @@ -0,0 +1,61 @@ +/** + * 应用版本配置 + * 集中管理应用版本信息 + * + * ⚠️ 注意:修改此处版本号即可,vite.config.ts 会自动读取 + */ + +// 主版本号 - 修改此处即可同步更新所有地方的版本显示 +export const APP_VERSION = '3.6.0'; + +// 应用名称 +export const APP_NAME = '意心AI'; + +// 完整名称(名称 + 版本) +export const APP_FULL_NAME = `${APP_NAME} ${APP_VERSION}`; + +// 构建信息(由 vite 注入) +declare const __GIT_BRANCH__: string; +declare const __GIT_HASH__: string; +declare const __GIT_DATE__: string; +declare const __BUILD_TIME__: string; + +// 版本信息(由 vite 注入) +declare const __APP_VERSION__: string; +declare const __APP_NAME__: string; + +export interface BuildInfo { + version: string; + name: string; + gitBranch: string; + gitHash: string; + gitDate: string; + buildTime: string; +} + +// 获取完整构建信息 +export function getBuildInfo(): BuildInfo { + return { + version: APP_VERSION, + name: APP_NAME, + gitBranch: typeof __GIT_BRANCH__ !== 'undefined' ? __GIT_BRANCH__ : 'unknown', + gitHash: typeof __GIT_HASH__ !== 'undefined' ? __GIT_HASH__ : 'unknown', + gitDate: typeof __GIT_DATE__ !== 'undefined' ? __GIT_DATE__ : 'unknown', + buildTime: typeof __BUILD_TIME__ !== 'undefined' ? __BUILD_TIME__ : new Date().toISOString(), + }; +} + +// 在控制台输出构建信息 +export function logBuildInfo(): void { + console.log( + `%c ${APP_NAME} ${APP_VERSION} %c Build Info `, + 'background:#35495e; padding: 4px; border-radius: 3px 0 0 3px; color: #fff', + 'background:#41b883; padding: 4px; border-radius: 0 3px 3px 0; color: #fff', + ); + const info = getBuildInfo(); + console.log(`🔹 Version: ${info.version}`); + // console.log(`🔹 Git Branch: ${info.gitBranch}`); + console.log(`🔹 Git Commit: ${info.gitHash}`); + // console.log(`🔹 Commit Date: ${info.gitDate}`); + // console.log(`🔹 Build Time: ${info.buildTime}`); +} diff --git a/Yi.Ai.Vue3/src/layouts/components/ChatAside/index.vue b/Yi.Ai.Vue3/src/layouts/components/ChatAside/index.vue index 083e9496..46671a91 100644 --- a/Yi.Ai.Vue3/src/layouts/components/ChatAside/index.vue +++ b/Yi.Ai.Vue3/src/layouts/components/ChatAside/index.vue @@ -20,12 +20,29 @@ const isCollapsed = computed(() => designStore.isCollapseConversationList); // 判断是否为新建对话状态(没有选中任何会话) const isNewChatState = computed(() => !sessionStore.currentSession); +const isLoading = ref(false); -onMounted(async () => { - await sessionStore.requestSessionList(); - if (conversationsList.value.length > 0 && sessionId.value) { - const currentSessionRes = await get_session(`${sessionId.value}`); - sessionStore.setCurrentSession(currentSessionRes.data); +onMounted(() => { + // 使用 requestIdleCallback 或 setTimeout 延迟加载数据 + // 避免阻塞首屏渲染 + const loadData = async () => { + isLoading.value = true; + try { + await sessionStore.requestSessionList(); + if (conversationsList.value.length > 0 && sessionId.value) { + const currentSessionRes = await get_session(`${sessionId.value}`); + sessionStore.setCurrentSession(currentSessionRes.data); + } + } finally { + isLoading.value = false; + } + }; + + // 优先使用 requestIdleCallback,如果不支持则使用 setTimeout + if (typeof requestIdleCallback !== 'undefined') { + requestIdleCallback(() => loadData(), { timeout: 1000 }); + } else { + setTimeout(loadData, 100); } }); diff --git a/Yi.Ai.Vue3/src/layouts/index.vue b/Yi.Ai.Vue3/src/layouts/index.vue index c37bc699..1a92f92d 100644 --- a/Yi.Ai.Vue3/src/layouts/index.vue +++ b/Yi.Ai.Vue3/src/layouts/index.vue @@ -35,30 +35,6 @@ const layout = computed((): LayoutType | 'mobile' => { // 否则使用全局设置的 layout return designStore.layout; }); - -onMounted(() => { - // 更好的做法是等待所有资源加载 - window.addEventListener('load', () => { - const loader = document.getElementById('yixinai-loader'); - if (loader) { - loader.style.opacity = '0'; - setTimeout(() => { - loader.style.display = 'none'; - }, 500); // 匹配过渡时间 - } - }); - - // 设置超时作为兜底 - setTimeout(() => { - const loader = document.getElementById('yixinai-loader'); - if (loader) { - loader.style.opacity = '0'; - setTimeout(() => { - loader.style.display = 'none'; - }, 500); - } - }, 500); // 最多显示0.5秒 -});