From 7a38526ab332131183fe899dfb5d6eb3db8b1c40 Mon Sep 17 00:00:00 2001 From: ccnetcore Date: Sat, 31 Jan 2026 16:07:30 +0800 Subject: [PATCH 01/15] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=B6=88=E6=81=AF=E6=8E=A5=E5=8F=A3=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 DeleteAsync 方法的参数绑定由 FromBody 调整为 FromQuery,避免在删除消息时参数无法正确接收的问题 --- .../Services/Chat/MessageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/MessageService.cs b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/MessageService.cs index 56101112..378fbb7b 100644 --- a/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/MessageService.cs +++ b/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Application/Services/Chat/MessageService.cs @@ -46,7 +46,7 @@ public class MessageService : ApplicationService /// /// 删除参数,包含消息Id列表和是否删除后续消息的开关 [Authorize] - public async Task DeleteAsync([FromBody] MessageDeleteInput input) + public async Task DeleteAsync([FromQuery] MessageDeleteInput input) { var userId = CurrentUser.GetId(); From ec382995b46570fc8ea102081ef1aee399198938 Mon Sep 17 00:00:00 2001 From: Gsh <15170702455@163.com> Date: Sat, 31 Jan 2026 17:39:23 +0800 Subject: [PATCH 02/15] =?UTF-8?q?feat:=20=E5=AF=B9=E8=AF=9D=E4=B8=AD?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E7=BC=96=E8=BE=91=E4=B8=8E=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E7=94=9F=E6=88=90=E4=B8=8E=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Yi.Ai.Vue3/src/api/chat/index.ts | 16 +- Yi.Ai.Vue3/src/api/chat/types.ts | 4 +- .../src/components/ProductPackage/index.vue | 4 +- .../components/CardFlipActivity.vue | 135 ++- Yi.Ai.Vue3/src/config/constants.ts | 2 +- .../pages/chat/layouts/chatWithId/index.vue | 863 ++++++++++++++++-- Yi.Ai.Vue3/src/pages/modelLibrary/index.vue | 5 +- Yi.Ai.Vue3/src/stores/modules/chat.ts | 14 +- Yi.Ai.Vue3/types/components.d.ts | 2 + 9 files changed, 943 insertions(+), 102 deletions(-) diff --git a/Yi.Ai.Vue3/src/api/chat/index.ts b/Yi.Ai.Vue3/src/api/chat/index.ts index aa9cdbba..ca793d68 100644 --- a/Yi.Ai.Vue3/src/api/chat/index.ts +++ b/Yi.Ai.Vue3/src/api/chat/index.ts @@ -1,5 +1,19 @@ import type { ChatMessageVo, GetChatListParams, SendDTO } from './types'; -import { get, post } from '@/utils/request'; +import { del, get, post } from '@/utils/request'; + +// 删除消息接口 +export interface DeleteMessageParams { + ids: (number | string)[]; + isDeleteSubsequent?: boolean; +} + +export function deleteMessages(data: DeleteMessageParams) { + const idsQuery = data.ids.map(id => `ids=${encodeURIComponent(id)}`).join('&'); + const subsequentQuery = data.isDeleteSubsequent !== undefined ? `isDeleteSubsequent=${data.isDeleteSubsequent}` : ''; + const query = [idsQuery, subsequentQuery].filter(Boolean).join('&'); + const url = `/message${query ? `?${query}` : ''}`; + return del(url).json(); +} // 发送消息(旧接口) export function send(data: SendDTO) { diff --git a/Yi.Ai.Vue3/src/api/chat/types.ts b/Yi.Ai.Vue3/src/api/chat/types.ts index d94d1ae4..3afcd16a 100644 --- a/Yi.Ai.Vue3/src/api/chat/types.ts +++ b/Yi.Ai.Vue3/src/api/chat/types.ts @@ -125,7 +125,7 @@ export interface GetChatListParams { /** * 主键 */ - id?: number; + id?: number | string; /** * 排序的方向desc或者asc */ @@ -195,7 +195,7 @@ export interface ChatMessageVo { /** * 主键 */ - id?: number; + id?: number | string; /** * 模型名称 */ diff --git a/Yi.Ai.Vue3/src/components/ProductPackage/index.vue b/Yi.Ai.Vue3/src/components/ProductPackage/index.vue index 20ef964f..7dc5d663 100644 --- a/Yi.Ai.Vue3/src/components/ProductPackage/index.vue +++ b/Yi.Ai.Vue3/src/components/ProductPackage/index.vue @@ -6,8 +6,8 @@ import { createOrder, getOrderStatus } from '@/api'; import { getGoodsList, GoodsCategoryType } from '@/api/pay'; import ProductPage from '@/pages/products/index.vue'; import { useUserStore } from '@/stores'; -import NewbieGuide from './NewbieGuide.vue'; import ActivationGuide from './ActivationGuide.vue'; +import NewbieGuide from './NewbieGuide.vue'; import PackageTab from './PackageTab.vue'; const emit = defineEmits(['close']); @@ -171,7 +171,7 @@ const benefitsData2 = { qy: [ { name: '需先成为意心会员后方可购买使用', value: '' }, { name: '意心会员过期后,尊享Token包会临时冻结', value: '' }, - { name: '可重复购买,将自动累积Token,在个人中心查看', value: '' }, + { name: '尊享Token = 实际消耗Token * 当前模型倍率,模型倍率可前往【模型库】查看', value: '' }, { name: 'Token长期有效,无限流限制', value: '' }, { name: '几乎是全网最低价,让人人用的起Agent', value: '' }, { name: '附带claude code独家教程,手把手对接', value: '' }, diff --git a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue index 14e47109..b360aa19 100644 --- a/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue +++ b/Yi.Ai.Vue3/src/components/userPersonalCenter/components/CardFlipActivity.vue @@ -1,4 +1,4 @@ - + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/components/ChatSender.vue b/Yi.Ai.Vue3/src/pages/chat/components/ChatSender.vue new file mode 100644 index 00000000..09a9a22d --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/components/ChatSender.vue @@ -0,0 +1,206 @@ + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/components/DeleteModeToolbar.vue b/Yi.Ai.Vue3/src/pages/chat/components/DeleteModeToolbar.vue new file mode 100644 index 00000000..a4b5780a --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/components/DeleteModeToolbar.vue @@ -0,0 +1,90 @@ + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/components/MessageItem.vue b/Yi.Ai.Vue3/src/pages/chat/components/MessageItem.vue new file mode 100644 index 00000000..19340571 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/components/MessageItem.vue @@ -0,0 +1,555 @@ + + + + + + diff --git a/Yi.Ai.Vue3/src/pages/chat/components/index.ts b/Yi.Ai.Vue3/src/pages/chat/components/index.ts new file mode 100644 index 00000000..306d5172 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/components/index.ts @@ -0,0 +1,6 @@ +// Chat 页面组件统一导出 + +export { default as ChatHeader } from './ChatHeader.vue'; +export { default as ChatSender } from './ChatSender.vue'; +export { default as MessageItem } from './MessageItem.vue'; +export { default as DeleteModeToolbar } from './DeleteModeToolbar.vue'; diff --git a/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue b/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue index 3eefcc93..02e29158 100644 --- a/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue +++ b/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue @@ -1,61 +1,77 @@ diff --git a/Yi.Ai.Vue3/src/pages/chat/styles/bubble.scss b/Yi.Ai.Vue3/src/pages/chat/styles/bubble.scss new file mode 100644 index 00000000..d78e5482 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/styles/bubble.scss @@ -0,0 +1,169 @@ +// 气泡列表相关样式 (需要 :deep 穿透) + +// 基础气泡列表样式 +@mixin bubble-list-base { + :deep(.el-bubble-list) { + padding-top: 24px; + + @media (max-width: 768px) { + padding-top: 16px; + } + } +} + +// 气泡基础样式 +@mixin bubble-base { + :deep(.el-bubble) { + padding: 0 12px 24px; + + // 隐藏头像 + .el-avatar { + display: none !important; + } + + // 用户消息样式 + &[class*="end"] { + width: 100%; + max-width: 100%; + + .el-bubble-content-wrapper { + flex: none; + max-width: fit-content; + } + + .el-bubble-content { + width: fit-content; + max-width: 100%; + } + } + + @media (max-width: 768px) { + padding: 0 8px 16px; + } + + @media (max-width: 480px) { + padding: 0 6px; + padding-bottom: 12px; + } + } +} + +// AI消息样式 +@mixin bubble-ai-style { + :deep(.el-bubble[class*="start"]) { + width: 100%; + max-width: 100%; + + .el-bubble-content-wrapper { + flex: auto; + } + + .el-bubble-content { + width: 100%; + max-width: 100%; + padding: 0; + background: transparent !important; + border: none !important; + box-shadow: none !important; + } + } +} + +// 用户编辑模式样式 +@mixin bubble-edit-mode { + :deep(.el-bubble[class*="end"]) { + &:has(.edit-message-wrapper-full) { + .el-bubble-content-wrapper { + flex: auto; + max-width: 100%; + } + + .el-bubble-content { + width: 100%; + max-width: 100%; + } + } + } +} + +// 删除模式气泡样式 +@mixin bubble-delete-mode { + :deep(.el-bubble-list.delete-mode) { + .el-bubble { + &[class*="end"] .el-bubble-content-wrapper { + flex: auto; + max-width: 100%; + } + + .el-bubble-content { + position: relative; + min-height: 44px; + padding: 12px 16px; + background-color: #f5f7fa; + border: 1px solid transparent; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; + + &:hover { + background-color: #e8f0fe; + border-color: #c6dafc; + } + } + + &:has(.el-checkbox.is-checked) .el-bubble-content { + background-color: #d2e3fc; + border-color: #8ab4f8; + } + } + + .delete-checkbox-inline { + position: absolute; + left: 12px; + top: 12px; + z-index: 2; + + :deep(.el-checkbox) { + --el-checkbox-input-height: 20px; + --el-checkbox-input-width: 20px; + } + } + + .ai-content-wrapper, + .user-content-wrapper { + margin-left: 36px; + width: calc(100% - 36px); + } + + .user-content-wrapper { + align-items: flex-start; + + .edit-message-wrapper-full { + width: 100%; + max-width: 100%; + } + } + } +} + +// Typewriter 样式 +@mixin typewriter-style { + :deep(.el-typewriter) { + overflow: hidden; + border-radius: 12px; + } +} + +// Markdown 容器样式 +@mixin markdown-container { + :deep(.elx-xmarkdown-container) { + padding: 8px 4px; + } +} + +// 代码块头部样式 +@mixin code-header { + :deep(.markdown-elxLanguage-header-div) { + top: -25px !important; + } +} diff --git a/Yi.Ai.Vue3/src/pages/chat/styles/index.scss b/Yi.Ai.Vue3/src/pages/chat/styles/index.scss new file mode 100644 index 00000000..b8306d20 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/styles/index.scss @@ -0,0 +1,5 @@ +// Chat 页面公共样式统一导入 + +@forward './variables'; +@forward './mixins'; +@forward './bubble'; diff --git a/Yi.Ai.Vue3/src/pages/chat/styles/mixins.scss b/Yi.Ai.Vue3/src/pages/chat/styles/mixins.scss new file mode 100644 index 00000000..f80b6586 --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/styles/mixins.scss @@ -0,0 +1,102 @@ +// 聊天页面公共 mixins + +// 响应式 +@mixin respond-to($breakpoint) { + @if $breakpoint == tablet { + @media (max-width: 768px) { + @content; + } + } @else if $breakpoint == mobile { + @media (max-width: 480px) { + @content; + } + } +} + +// 弹性布局 +@mixin flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +@mixin flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +@mixin flex-column { + display: flex; + flex-direction: column; +} + +// 文本省略 +@mixin text-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// 多行文本省略 +@mixin text-ellipsis-multi($lines: 2) { + display: -webkit-box; + -webkit-line-clamp: $lines; + -webkit-box-orient: vertical; + overflow: hidden; +} + +// 滚动按钮样式 +@mixin scroll-btn { + position: absolute; + top: 50%; + transform: translateY(-50%); + @include flex-center; + width: 22px; + height: 22px; + border-radius: 8px; + border: 1px solid rgba(0, 0, 0, 0.08); + color: rgba(0, 0, 0, 0.4); + background-color: #fff; + font-size: 10px; + cursor: pointer; + z-index: 10; + transition: all 0.2s ease; + + &:hover { + background-color: #f3f4f6; + border-color: rgba(0, 0, 0, 0.15); + color: rgba(0, 0, 0, 0.6); + } +} + +// 操作按钮样式 +@mixin action-btn { + width: 24px; + height: 24px; + padding: 0; + font-size: 13px; + color: #555; + background: transparent; + border: none; + border-radius: 4px; + transition: all 0.2s ease; + + &:hover { + color: #409eff; + background: #f0f7ff; + } + + &:active { + background: #e6f2ff; + } + + &[disabled] { + color: #bbb; + background: transparent; + } + + .el-icon { + font-size: 13px; + } +} diff --git a/Yi.Ai.Vue3/src/pages/chat/styles/variables.scss b/Yi.Ai.Vue3/src/pages/chat/styles/variables.scss new file mode 100644 index 00000000..ef70f4ae --- /dev/null +++ b/Yi.Ai.Vue3/src/pages/chat/styles/variables.scss @@ -0,0 +1,56 @@ +// 聊天页面公共样式变量 + +// 布局 +$chat-header-height: 60px; +$chat-header-height-mobile: 50px; +$chat-header-height-small: 48px; + +$chat-max-width: 1000px; +$chat-padding: 20px; +$chat-padding-mobile: 12px; +$chat-padding-small: 8px; + +// 气泡列表 +$bubble-padding-y: 24px; +$bubble-padding-x: 12px; +$bubble-gap: 24px; + +$bubble-padding-y-mobile: 16px; +$bubble-padding-x-mobile: 8px; +$bubble-gap-mobile: 16px; + +// 用户消息 +$user-image-max-size: 200px; +$user-image-max-size-mobile: 150px; +$user-image-max-size-small: 120px; + +// 颜色 +$color-text-primary: #333; +$color-text-secondary: #888; +$color-text-tertiary: #bbb; + +$color-border: rgba(0, 0, 0, 0.08); +$color-border-hover: rgba(0, 0, 0, 0.15); + +$color-bg-hover: #f3f4f6; +$color-bg-light: #f5f7fa; +$color-bg-lighter: #e8f0fe; +$color-bg-selected: #d2e3fc; +$color-border-selected: #8ab4f8; + +$color-primary: #409eff; +$color-primary-light: #f0f7ff; +$color-primary-lighter: #e6f2ff; + +// 删除模式 +$color-delete-bg: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%); +$color-delete-border: #fed7aa; +$color-delete-text: #ea580c; + +// 动画 +$transition-fast: 0.2s ease; +$transition-normal: 0.3s ease; + +// 响应式断点 +$breakpoint-tablet: 768px; +$breakpoint-mobile: 480px; diff --git a/Yi.Ai.Vue3/src/stores/index.ts b/Yi.Ai.Vue3/src/stores/index.ts index cc1a71eb..a7041c70 100644 --- a/Yi.Ai.Vue3/src/stores/index.ts +++ b/Yi.Ai.Vue3/src/stores/index.ts @@ -8,7 +8,7 @@ store.use(piniaPluginPersistedstate); export default store; export * from './modules/announcement' -// export * from './modules/chat'; +export * from './modules/chat'; export * from './modules/design'; export * from './modules/user'; export * from './modules/guideTour'; From 11cbb1b61224d77656fca4c19f12ee7a246e112b Mon Sep 17 00:00:00 2001 From: Gsh <15170702455@163.com> Date: Sun, 1 Feb 2026 00:30:44 +0800 Subject: [PATCH 08/15] =?UTF-8?q?feat:=20=E9=A1=B9=E7=9B=AE=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Yi.Ai.Vue3/.build/plugins/fontawesome.ts | 23 ++ Yi.Ai.Vue3/.build/plugins/index.ts | 19 +- Yi.Ai.Vue3/.build/plugins/preload.ts | 47 +++ Yi.Ai.Vue3/.build/plugins/version-html.ts | 20 + Yi.Ai.Vue3/FONTAWESOME_MIGRATION.md | 133 +++++++ Yi.Ai.Vue3/index.html | 168 +++++++- Yi.Ai.Vue3/package.json | 3 + Yi.Ai.Vue3/pnpm-lock.yaml | 374 +++++++++--------- .../src/components/FontAwesomeIcon/demo.vue | 250 ++++++++++++ .../src/components/FontAwesomeIcon/index.ts | 3 + .../src/components/FontAwesomeIcon/index.vue | 33 ++ .../src/components/MarkedMarkdown/index.vue | 5 +- .../src/components/ModelSelect/index.vue | 4 +- .../ProductPackage/ActivationGuide.vue | 8 +- .../components/ProductPackage/PackageTab.vue | 5 +- .../src/components/ProductPackage/index.vue | 8 +- .../SupportModelProducts/indexl.vue | 6 +- Yi.Ai.Vue3/src/config/index.ts | 16 +- Yi.Ai.Vue3/src/config/version.ts | 61 +++ .../layouts/components/ChatAside/index.vue | 27 +- Yi.Ai.Vue3/src/layouts/index.vue | 24 -- Yi.Ai.Vue3/src/main.ts | 83 +++- Yi.Ai.Vue3/src/pages/test/fontawesome.vue | 29 ++ Yi.Ai.Vue3/src/routers/index.ts | 146 ++++--- .../src/routers/modules/staticRouter.ts | 40 +- Yi.Ai.Vue3/src/stores/modules/user.ts | 5 +- Yi.Ai.Vue3/src/utils/icon-mapping.ts | 123 ++++++ Yi.Ai.Vue3/types/import_meta.d.ts | 1 + Yi.Ai.Vue3/vite.config.ts | 125 +++++- 29 files changed, 1490 insertions(+), 299 deletions(-) create mode 100644 Yi.Ai.Vue3/.build/plugins/fontawesome.ts create mode 100644 Yi.Ai.Vue3/.build/plugins/preload.ts create mode 100644 Yi.Ai.Vue3/.build/plugins/version-html.ts create mode 100644 Yi.Ai.Vue3/FONTAWESOME_MIGRATION.md create mode 100644 Yi.Ai.Vue3/src/components/FontAwesomeIcon/demo.vue create mode 100644 Yi.Ai.Vue3/src/components/FontAwesomeIcon/index.ts create mode 100644 Yi.Ai.Vue3/src/components/FontAwesomeIcon/index.vue create mode 100644 Yi.Ai.Vue3/src/config/version.ts create mode 100644 Yi.Ai.Vue3/src/pages/test/fontawesome.vue create mode 100644 Yi.Ai.Vue3/src/utils/icon-mapping.ts 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秒 -});