diff --git a/.env b/.env index b8ccc52c..14a254b0 100644 --- a/.env +++ b/.env @@ -18,3 +18,8 @@ VITE_APP_DOCALERT_ENABLE=false # 百度统计 VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc + +# 默认账户密码 +VITE_APP_DEFAULT_LOGIN_TENANT = 芋道源码 +VITE_APP_DEFAULT_LOGIN_USERNAME = admin +VITE_APP_DEFAULT_LOGIN_PASSWORD = admin123 diff --git a/.image/common/ai-feature.png b/.image/common/ai-feature.png new file mode 100644 index 00000000..b4a55f54 Binary files /dev/null and b/.image/common/ai-feature.png differ diff --git a/.image/common/ai-preview.gif b/.image/common/ai-preview.gif new file mode 100644 index 00000000..5f13ac4f Binary files /dev/null and b/.image/common/ai-preview.gif differ diff --git a/README.md b/README.md index 7a6c2f1a..6623cbc4 100644 --- a/README.md +++ b/README.md @@ -18,17 +18,16 @@ 推荐 VS Code 开发,配合插件如下: -| 插件名 | 功能 | -|-------------------------------|--------------------------| -| TypeScript Vue Plugin (Volar) | 用于 TypeScript 的 Vue 插件 | -| Vue Language Features (Volar) | Vue3.0 语法支持 | -| unocss | unocss for vscode | -| Iconify IntelliSense | Iconify 预览和搜索 | -| i18n Ally | 国际化智能提示 | -| Stylelint | Css 格式化 | -| Prettier | 代码格式化 | -| ESLint | 脚本代码检查 | -| DotENV | env 文件高亮 | +| 插件名 | 功能 | +|-------------------------------|---------------------| +| Vue - Official | Vue 与 TypeScript 支持 | +| unocss | unocss for vscode | +| Iconify IntelliSense | Iconify 预览和搜索 | +| i18n Ally | 国际化智能提示 | +| Stylelint | Css 格式化 | +| Prettier | 代码格式化 | +| ESLint | 脚本代码检查 | +| DotENV | env 文件高亮 | ### 提交规范 diff --git a/package.json b/package.json index 1fa88646..18be2c2e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "private": false, "scripts": { "i": "pnpm install", - "dev": "vite", + "dev": "vite --mode env.local", "dev-server": "vite --mode dev", "ts:check": "vue-tsc --noEmit", "build:local": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build", @@ -29,6 +29,7 @@ "@form-create/designer": "^3.1.3", "@form-create/element-ui": "^3.1.24", "@iconify/iconify": "^3.1.1", + "@microsoft/fetch-event-source": "^2.0.1", "@videojs-player/vue": "^1.0.0", "@vueuse/core": "^10.9.0", "@wangeditor/editor": "^5.1.23", @@ -46,11 +47,12 @@ "driver.js": "^1.3.1", "echarts": "^5.5.0", "echarts-wordcloud": "^2.1.0", - "element-plus": "2.6.1", + "element-plus": "2.7.0", "fast-xml-parser": "^4.3.2", "highlight.js": "^11.9.0", "jsencrypt": "^3.3.2", "lodash-es": "^4.17.21", + "marked": "^12.0.2", "min-dash": "^4.1.1", "mitt": "^3.0.1", "nprogress": "^0.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c2351e0..55dceb8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ dependencies: '@iconify/iconify': specifier: ^3.1.1 version: 3.1.1 + '@microsoft/fetch-event-source': + specifier: ^2.0.1 + version: 2.0.1 '@videojs-player/vue': specifier: ^1.0.0 version: 1.0.0(@types/video.js@7.3.58)(video.js@7.21.5)(vue@3.4.21) @@ -69,8 +72,8 @@ dependencies: specifier: ^2.1.0 version: 2.1.0(echarts@5.5.0) element-plus: - specifier: 2.6.1 - version: 2.6.1(vue@3.4.21) + specifier: 2.7.0 + version: 2.7.0(vue@3.4.21) fast-xml-parser: specifier: ^4.3.2 version: 4.3.6 @@ -83,6 +86,9 @@ dependencies: lodash-es: specifier: ^4.17.21 version: 4.17.21 + marked: + specifier: ^12.0.2 + version: 12.0.2 min-dash: specifier: ^4.1.1 version: 4.2.1 @@ -2360,6 +2366,10 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@microsoft/fetch-event-source@2.0.1: + resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==, tarball: https://registry.npmmirror.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz} + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, tarball: https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz} engines: {node: '>= 8'} @@ -5082,8 +5092,8 @@ packages: resolution: {integrity: sha512-9ItEpeu15hW5m8jKdriL+BQrgwDTXEL9pn4SkillWFu73ZNNNQ2BKKLS+ZHv2vC9UkNhosAeyfxOf/5OSeTCPA==, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.750.tgz} dev: true - /element-plus@2.6.1(vue@3.4.21): - resolution: {integrity: sha512-6VRpLjwtIVdtUuITJPPKtpOH1NM6nuAkRE3q5O4Lrx0N1bYMhTkiqb2Jy7zfQuDPbOIkkF2OABTzegpNnzgsnQ==, tarball: https://registry.npmmirror.com/element-plus/-/element-plus-2.6.1.tgz} + /element-plus@2.7.0(vue@3.4.21): + resolution: {integrity: sha512-WAiaFLavuWFxof9qwkC27jvkh9nRcNnB506g1vvJSiVaVqjCBWUFCIyJKeN11M1qcv2cS5VV5PfSLjTIkrw87A==, tarball: https://registry.npmmirror.com/element-plus/-/element-plus-2.7.0.tgz} peerDependencies: vue: ^3.2.0 dependencies: @@ -6917,6 +6927,12 @@ packages: object-visit: 1.0.1 dev: true + /marked@12.0.2: + resolution: {integrity: sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==, tarball: https://registry.npmmirror.com/marked/-/marked-12.0.2.tgz} + engines: {node: '>= 18'} + hasBin: true + dev: false + /matches-selector@1.2.0: resolution: {integrity: sha512-c4vLwYWyl+Ji+U43eU/G5FwxWd4ZH0ePUsFs5y0uwD9HUEFBXUQ1zUUan+78IpRD+y4pUfG0nAzNM292K7ItvA==, tarball: https://registry.npmmirror.com/matches-selector/-/matches-selector-1.2.0.tgz} dev: true diff --git a/src/App.vue b/src/App.vue index 7407d97a..1f5f36d7 100644 --- a/src/App.vue +++ b/src/App.vue @@ -54,4 +54,8 @@ body { .#{$prefix-cls}-grey-mode { filter: grayscale(100%); } + +.scrollbar__view { + height: 99%!important; +} diff --git a/src/api/ai/chat/conversation/index.ts b/src/api/ai/chat/conversation/index.ts new file mode 100644 index 00000000..6ce4482f --- /dev/null +++ b/src/api/ai/chat/conversation/index.ts @@ -0,0 +1,65 @@ +import request from '@/config/axios' + +// AI 聊天对话 VO +export interface ChatConversationVO { + id: number // ID 编号 + userId: number // 用户编号 + title: string // 对话标题 + pinned: boolean // 是否置顶 + roleId: number // 角色编号 + modelId: number // 模型编号 + model: string // 模型标志 + temperature: number // 温度参数 + maxTokens: number // 单条回复的最大 Token 数量 + maxContexts: number // 上下文的最大 Message 数量 + createTime?: Date // 创建时间 + // 额外字段 + systemMessage?: string // 角色设定 + modelName?: string // 模型名字 + roleAvatar?: string // 角色头像 + modelMaxTokens?: string // 模型的单条回复的最大 Token 数量 + modelMaxContexts?: string // 模型的上下文的最大 Message 数量 +} + +// AI 聊天对话 API +export const ChatConversationApi = { + // 获得【我的】聊天对话 + getChatConversationMy: async (id: number) => { + return await request.get({ url: `/ai/chat/conversation/get-my?id=${id}` }) + }, + + // 新增【我的】聊天对话 + createChatConversationMy: async (data?: ChatConversationVO) => { + return await request.post({ url: `/ai/chat/conversation/create-my`, data }) + }, + + // 更新【我的】聊天对话 + updateChatConversationMy: async (data: ChatConversationVO) => { + return await request.put({ url: `/ai/chat/conversation/update-my`, data }) + }, + + // 删除【我的】聊天对话 + deleteChatConversationMy: async (id: string) => { + return await request.delete({ url: `/ai/chat/conversation/delete-my?id=${id}` }) + }, + + // 删除【我的】所有对话,置顶除外 + deleteChatConversationMyByUnpinned: async () => { + return await request.delete({ url: `/ai/chat/conversation/delete-by-unpinned` }) + }, + + // 获得【我的】聊天对话列表 + getChatConversationMyList: async () => { + return await request.get({ url: `/ai/chat/conversation/my-list` }) + }, + + // 获得对话分页 + getChatConversationPage: async (params: any) => { + return await request.get({ url: `/ai/chat/conversation/page`, params }) + }, + + // 管理员删除消息 + deleteChatConversationByAdmin: async (id: number) => { + return await request.delete({ url: `/ai/chat/conversation/delete-by-admin?id=${id}` }) + } +} diff --git a/src/api/ai/chat/message/index.ts b/src/api/ai/chat/message/index.ts new file mode 100644 index 00000000..ef1196ac --- /dev/null +++ b/src/api/ai/chat/message/index.ts @@ -0,0 +1,83 @@ +import request from '@/config/axios' +import { fetchEventSource } from '@microsoft/fetch-event-source' +import { getAccessToken } from '@/utils/auth' +import { config } from '@/config/axios/config' + +// 聊天VO +export interface ChatMessageVO { + id: number // 编号 + conversationId: number // 对话编号 + type: string // 消息类型 + userId: string // 用户编号 + roleId: string // 角色编号 + model: number // 模型标志 + modelId: number // 模型编号 + content: string // 聊天内容 + tokens: number // 消耗 Token 数量 + createTime: Date // 创建时间 + roleAvatar: string // 角色头像 + userAvatar: string // 创建时间 +} + +// AI chat 聊天 +export const ChatMessageApi = { + // 消息列表 + getChatMessageListByConversationId: async (conversationId: number | null) => { + return await request.get({ + url: `/ai/chat/message/list-by-conversation-id?conversationId=${conversationId}` + }) + }, + + // 发送 Stream 消息 + // 为什么不用 axios 呢?因为它不支持 SSE 调用 + sendChatMessageStream: async ( + conversationId: number, + content: string, + ctrl, + enableContext: boolean, + onMessage, + onError, + onClose + ) => { + const token = getAccessToken() + return fetchEventSource(`${config.base_url}/ai/chat/message/send-stream`, { + method: 'post', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + openWhenHidden: true, + body: JSON.stringify({ + conversationId, + content, + useContext: enableContext + }), + onmessage: onMessage, + onerror: onError, + onclose: onClose, + signal: ctrl.signal + }) + }, + + // 删除消息 + deleteChatMessage: async (id: string) => { + return await request.delete({ url: `/ai/chat/message/delete?id=${id}` }) + }, + + // 删除指定对话的消息 + deleteByConversationId: async (conversationId: number) => { + return await request.delete({ + url: `/ai/chat/message/delete-by-conversation-id?conversationId=${conversationId}` + }) + }, + + // 获得消息分页 + getChatMessagePage: async (params: any) => { + return await request.get({ url: '/ai/chat/message/page', params }) + }, + + // 管理员删除消息 + deleteChatMessageByAdmin: async (id: number) => { + return await request.delete({ url: `/ai/chat/message/delete-by-admin?id=${id}` }) + } +} diff --git a/src/api/ai/image/index.ts b/src/api/ai/image/index.ts new file mode 100644 index 00000000..5f8f13ca --- /dev/null +++ b/src/api/ai/image/index.ts @@ -0,0 +1,107 @@ +import request from '@/config/axios' + +// AI 绘图 VO +export interface ImageVO { + id: number // 编号 + platform: string // 平台 + model: string // 模型 + prompt: string // 提示词 + width: number // 图片宽度 + height: number // 图片高度 + status: number // 状态 + publicStatus: boolean // 公开状态 + picUrl: string // 任务地址 + errorMessage: string // 错误信息 + options: any // 配置 Map + taskId: number // 任务编号 + buttons: ImageMidjourneyButtonsVO[] // mj 操作按钮 + createTime: Date // 创建时间 + finishTime: Date // 完成时间 +} + +export interface ImageDrawReqVO { + platform: string // 平台 + prompt: string // 提示词 + model: string // 模型 + style: string // 图像生成的风格 + width: string // 图片宽度 + height: string // 图片高度 + options: object // 绘制参数,Map +} + +export interface ImageMidjourneyImagineReqVO { + prompt: string // 提示词 + model: string // 模型 mj nijj + base64Array: string[] // size不能为空 + width: string // 图片宽度 + height: string // 图片高度 + version: string // 版本 +} + +export interface ImageMidjourneyActionVO { + id: number // 图片编号 + customId: string // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识 +} + +export interface ImageMidjourneyButtonsVO { + customId: string // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识 + emoji: string // 图标 emoji + label: string // Make Variations 文本 + style: number // 样式: 2(Primary)、3(Green) +} + +// AI 图片 API +export const ImageApi = { + // 获取【我的】绘图分页 + getImagePageMy: async (params: PageParam) => { + return await request.get({ url: `/ai/image/my-page`, params }) + }, + // 获取公开的绘图记录 + getImagePagePublic: async (params: PageParam) => { + return await request.get({ url: `/ai/image/public-page`, params }) + }, + // 获取【我的】绘图记录 + getImageMy: async (id: number) => { + return await request.get({ url: `/ai/image/get-my?id=${id}` }) + }, + // 获取【我的】绘图记录列表 + getImageListMyByIds: async (ids: number[]) => { + return await request.get({ url: `/ai/image/my-list-by-ids`, params: { ids: ids.join(',') } }) + }, + // 生成图片 + drawImage: async (data: ImageDrawReqVO) => { + return await request.post({ url: `/ai/image/draw`, data }) + }, + // 删除【我的】绘画记录 + deleteImageMy: async (id: number) => { + return await request.delete({ url: `/ai/image/delete-my?id=${id}` }) + }, + + // ================ midjourney 专属 ================ + + // 【Midjourney】生成图片 + midjourneyImagine: async (data: ImageMidjourneyImagineReqVO) => { + return await request.post({ url: `/ai/image/midjourney/imagine`, data }) + }, + // 【Midjourney】Action 操作(二次生成图片) + midjourneyAction: async (data: ImageMidjourneyActionVO) => { + return await request.post({ url: `/ai/image/midjourney/action`, data }) + }, + + // ================ 绘图管理 ================ + + // 查询绘画分页 + getImagePage: async (params: any) => { + return await request.get({ url: `/ai/image/page`, params }) + }, + + // 更新绘画发布状态 + updateImage: async (data: any) => { + return await request.put({ url: '/ai/image/update-public-status', data }) + }, + + // 删除绘画 + deleteImage: async (id: number) => { + return await request.delete({ url: `/ai/image/delete?id=` + id }) + } +} diff --git a/src/api/ai/model/apiKey/index.ts b/src/api/ai/model/apiKey/index.ts new file mode 100644 index 00000000..ed94836e --- /dev/null +++ b/src/api/ai/model/apiKey/index.ts @@ -0,0 +1,44 @@ +import request from '@/config/axios' + +// AI API 密钥 VO +export interface ApiKeyVO { + id: number // 编号 + name: string // 名称 + apiKey: string // 密钥 + platform: string // 平台 + url: string // 自定义 API 地址 + status: number // 状态 +} + +// AI API 密钥 API +export const ApiKeyApi = { + // 查询 API 密钥分页 + getApiKeyPage: async (params: any) => { + return await request.get({ url: `/ai/api-key/page`, params }) + }, + + // 获得 API 密钥列表 + getApiKeySimpleList: async () => { + return await request.get({ url: `/ai/api-key/simple-list` }) + }, + + // 查询 API 密钥详情 + getApiKey: async (id: number) => { + return await request.get({ url: `/ai/api-key/get?id=` + id }) + }, + + // 新增 API 密钥 + createApiKey: async (data: ApiKeyVO) => { + return await request.post({ url: `/ai/api-key/create`, data }) + }, + + // 修改 API 密钥 + updateApiKey: async (data: ApiKeyVO) => { + return await request.put({ url: `/ai/api-key/update`, data }) + }, + + // 删除 API 密钥 + deleteApiKey: async (id: number) => { + return await request.delete({ url: `/ai/api-key/delete?id=` + id }) + } +} diff --git a/src/api/ai/model/chatModel/index.ts b/src/api/ai/model/chatModel/index.ts new file mode 100644 index 00000000..c2ef4c8d --- /dev/null +++ b/src/api/ai/model/chatModel/index.ts @@ -0,0 +1,53 @@ +import request from '@/config/axios' + +// AI 聊天模型 VO +export interface ChatModelVO { + id: number // 编号 + keyId: number // API 秘钥编号 + name: string // 模型名字 + model: string // 模型标识 + platform: string // 模型平台 + sort: number // 排序 + status: number // 状态 + temperature: number // 温度参数 + maxTokens: number // 单条回复的最大 Token 数量 + maxContexts: number // 上下文的最大 Message 数量 +} + +// AI 聊天模型 API +export const ChatModelApi = { + // 查询聊天模型分页 + getChatModelPage: async (params: any) => { + return await request.get({ url: `/ai/chat-model/page`, params }) + }, + + // 获得聊天模型列表 + getChatModelSimpleList: async (status?: number) => { + return await request.get({ + url: `/ai/chat-model/simple-list`, + params: { + status + } + }) + }, + + // 查询聊天模型详情 + getChatModel: async (id: number) => { + return await request.get({ url: `/ai/chat-model/get?id=` + id }) + }, + + // 新增聊天模型 + createChatModel: async (data: ChatModelVO) => { + return await request.post({ url: `/ai/chat-model/create`, data }) + }, + + // 修改聊天模型 + updateChatModel: async (data: ChatModelVO) => { + return await request.put({ url: `/ai/chat-model/update`, data }) + }, + + // 删除聊天模型 + deleteChatModel: async (id: number) => { + return await request.delete({ url: `/ai/chat-model/delete?id=` + id }) + } +} diff --git a/src/api/ai/model/chatRole/index.ts b/src/api/ai/model/chatRole/index.ts new file mode 100644 index 00000000..a9fce13c --- /dev/null +++ b/src/api/ai/model/chatRole/index.ts @@ -0,0 +1,80 @@ +import request from '@/config/axios' + +// AI 聊天角色 VO +export interface ChatRoleVO { + id: number // 角色编号 + modelId: number // 模型编号 + name: string // 角色名称 + avatar: string // 角色头像 + category: string // 角色类别 + sort: number // 角色排序 + description: string // 角色描述 + systemMessage: string // 角色设定 + welcomeMessage: string // 角色设定 + publicStatus: boolean // 是否公开 + status: number // 状态 +} + +// AI 聊天角色 分页请求 vo +export interface ChatRolePageReqVO { + name?: string // 角色名称 + category?: string // 角色类别 + publicStatus: boolean // 是否公开 + pageNo: number // 是否公开 + pageSize: number // 是否公开 +} + +// AI 聊天角色 API +export const ChatRoleApi = { + // 查询聊天角色分页 + getChatRolePage: async (params: any) => { + return await request.get({ url: `/ai/chat-role/page`, params }) + }, + + // 查询聊天角色详情 + getChatRole: async (id: number) => { + return await request.get({ url: `/ai/chat-role/get?id=` + id }) + }, + + // 新增聊天角色 + createChatRole: async (data: ChatRoleVO) => { + return await request.post({ url: `/ai/chat-role/create`, data }) + }, + + // 修改聊天角色 + updateChatRole: async (data: ChatRoleVO) => { + return await request.put({ url: `/ai/chat-role/update`, data }) + }, + + // 删除聊天角色 + deleteChatRole: async (id: number) => { + return await request.delete({ url: `/ai/chat-role/delete?id=` + id }) + }, + + // ======= chat 聊天 + + // 获取 my role + getMyPage: async (params: ChatRolePageReqVO) => { + return await request.get({ url: `/ai/chat-role/my-page`, params}) + }, + + // 获取角色分类 + getCategoryList: async () => { + return await request.get({ url: `/ai/chat-role/category-list`}) + }, + + // 创建角色 + createMy: async (data: ChatRoleVO) => { + return await request.post({ url: `/ai/chat-role/create-my`, data}) + }, + + // 更新角色 + updateMy: async (data: ChatRoleVO) => { + return await request.put({ url: `/ai/chat-role/update-my`, data}) + }, + + // 删除角色 my + deleteMy: async (id: number) => { + return await request.delete({ url: `/ai/chat-role/delete-my?id=` + id }) + }, +} diff --git a/src/api/ai/music/index.ts b/src/api/ai/music/index.ts new file mode 100644 index 00000000..74b85268 --- /dev/null +++ b/src/api/ai/music/index.ts @@ -0,0 +1,41 @@ +import request from '@/config/axios' + +// AI 音乐 VO +export interface MusicVO { + id: number // 编号 + userId: number // 用户编号 + title: string // 音乐名称 + lyric: string // 歌词 + imageUrl: string // 图片地址 + audioUrl: string // 音频地址 + videoUrl: string // 视频地址 + status: number // 音乐状态 + gptDescriptionPrompt: string // 描述词 + prompt: string // 提示词 + platform: string // 模型平台 + model: string // 模型 + generateMode: number // 生成模式 + tags: string // 音乐风格标签 + duration: number // 音乐时长 + publicStatus: boolean // 是否发布 + taskId: string // 任务id + errorMessage: string // 错误信息 +} + +// AI 音乐 API +export const MusicApi = { + // 查询音乐分页 + getMusicPage: async (params: any) => { + return await request.get({ url: `/ai/music/page`, params }) + }, + + // 更新音乐 + updateMusic: async (data: any) => { + return await request.put({ url: '/ai/music/update', data }) + }, + + // 删除音乐 + deleteMusic: async (id: number) => { + return await request.delete({ url: `/ai/music/delete?id=` + id }) + } +} diff --git a/src/api/ai/write/index.ts b/src/api/ai/write/index.ts new file mode 100644 index 00000000..013f998f --- /dev/null +++ b/src/api/ai/write/index.ts @@ -0,0 +1,85 @@ +import { fetchEventSource } from '@microsoft/fetch-event-source' + +import { getAccessToken } from '@/utils/auth' +import { config } from '@/config/axios/config' +import { AiWriteTypeEnum } from '@/views/ai/utils/constants' +import request from '@/config/axios' + +export interface WriteVO { + type: AiWriteTypeEnum.WRITING | AiWriteTypeEnum.REPLY // 1:撰写 2:回复 + prompt: string // 写作内容提示 1。撰写 2回复 + originalContent: string // 原文 + length: number // 长度 + format: number // 格式 + tone: number // 语气 + language: number // 语言 + userId?: number // 用户编号 + platform?: string // 平台 + model?: string // 模型 + generatedContent?: string // 生成的内容 + errorMessage?: string // 错误信息 + createTime?: Date // 创建时间 +} + +export interface AiWritePageReqVO extends PageParam { + userId?: number // 用户编号 + type?: AiWriteTypeEnum // 写作类型 + platform?: string // 平台 + createTime?: [string, string] // 创建时间 +} + +export interface AiWriteRespVo { + id: number + userId: number + type: number + platform: string + model: string + prompt: string + generatedContent: string + originalContent: string + length: number + format: number + tone: number + language: number + errorMessage: string + createTime: string +} + +export const WriteApi = { + writeStream: ({ + data, + onClose, + onMessage, + onError, + ctrl + }: { + data: WriteVO + onMessage?: (res: any) => void + onError?: (...args: any[]) => void + onClose?: (...args: any[]) => void + ctrl: AbortController + }) => { + const token = getAccessToken() + return fetchEventSource(`${config.base_url}/ai/write/generate-stream`, { + method: 'post', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + openWhenHidden: true, + body: JSON.stringify(data), + onmessage: onMessage, + onerror: onError, + onclose: onClose, + signal: ctrl.signal + }) + }, + // 获取写作列表 + getWritePage: (params: AiWritePageReqVO) => { + return request.get>({ url: `/ai/write/page`, params }) + }, + // 删除写作 + deleteWrite(id: number) { + return request.delete({ url: `/ai/write/delete`, params: { id } }) + } +} diff --git a/src/api/infra/jobLog/index.ts b/src/api/infra/jobLog/index.ts index dc80f1d9..ed54761c 100644 --- a/src/api/infra/jobLog/index.ts +++ b/src/api/infra/jobLog/index.ts @@ -12,6 +12,7 @@ export interface JobLogVO { duration: string status: number createTime: string + result: string } // 任务日志列表 diff --git a/src/api/mall/promotion/kefu/conversation/index.ts b/src/api/mall/promotion/kefu/conversation/index.ts new file mode 100644 index 00000000..2dbf3316 --- /dev/null +++ b/src/api/mall/promotion/kefu/conversation/index.ts @@ -0,0 +1,35 @@ +import request from '@/config/axios' + +export interface KeFuConversationRespVO { + id: number // 编号 + userId: number // 会话所属用户 + userAvatar: string // 会话所属用户头像 + userNickname: string // 会话所属用户昵称 + lastMessageTime: Date // 最后聊天时间 + lastMessageContent: string // 最后聊天内容 + lastMessageContentType: number // 最后发送的消息类型 + adminPinned: boolean // 管理端置顶 + userDeleted: boolean // 用户是否可见 + adminDeleted: boolean // 管理员是否可见 + adminUnreadMessageCount: number // 管理员未读消息数 + createTime?: string // 创建时间 +} + +// 客服会话 API +export const KeFuConversationApi = { + // 获得客服会话列表 + getConversationList: async () => { + return await request.get({ url: '/promotion/kefu-conversation/list' }) + }, + // 客服会话置顶 + updateConversationPinned: async (data: any) => { + return await request.put({ + url: '/promotion/kefu-conversation/update-conversation-pinned', + data + }) + }, + // 删除客服会话 + deleteConversation: async (id: number) => { + return await request.get({ url: '/promotion/kefu-conversation/delete?id' + id }) + } +} diff --git a/src/api/mall/promotion/kefu/message/index.ts b/src/api/mall/promotion/kefu/message/index.ts new file mode 100644 index 00000000..a12167fa --- /dev/null +++ b/src/api/mall/promotion/kefu/message/index.ts @@ -0,0 +1,36 @@ +import request from '@/config/axios' + +export interface KeFuMessageRespVO { + id: number // 编号 + conversationId: number // 会话编号 + senderId: number // 发送人编号 + senderAvatar: string // 发送人头像 + senderType: number // 发送人类型 + receiverId: number // 接收人编号 + receiverType: number // 接收人类型 + contentType: number // 消息类型 + content: string // 消息 + readStatus: boolean // 是否已读 + createTime: Date // 创建时间 +} + +// 客服会话 API +export const KeFuMessageApi = { + // 发送客服消息 + sendKeFuMessage: async (data: any) => { + return await request.post({ + url: '/promotion/kefu-message/send', + data + }) + }, + // 更新客服消息已读状态 + updateKeFuMessageReadStatus: async (conversationId: number) => { + return await request.put({ + url: '/promotion/kefu-message/update-read-status?conversationId=' + conversationId + }) + }, + // 获得消息分页数据 + getKeFuMessagePage: async (params: any) => { + return await request.get({ url: '/promotion/kefu-message/page', params }) + } +} diff --git a/src/assets/ai/copy-style2.svg b/src/assets/ai/copy-style2.svg new file mode 100644 index 00000000..2d56a87f --- /dev/null +++ b/src/assets/ai/copy-style2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/ai/copy.svg b/src/assets/ai/copy.svg new file mode 100644 index 00000000..f51f8d81 --- /dev/null +++ b/src/assets/ai/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/ai/dall2.jpg b/src/assets/ai/dall2.jpg new file mode 100644 index 00000000..c07374dc Binary files /dev/null and b/src/assets/ai/dall2.jpg differ diff --git a/src/assets/ai/dall3.jpg b/src/assets/ai/dall3.jpg new file mode 100644 index 00000000..7f45803b Binary files /dev/null and b/src/assets/ai/dall3.jpg differ diff --git a/src/assets/ai/delete.svg b/src/assets/ai/delete.svg new file mode 100644 index 00000000..d2ee18ed --- /dev/null +++ b/src/assets/ai/delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/ai/gpt.svg b/src/assets/ai/gpt.svg new file mode 100644 index 00000000..603e2e95 --- /dev/null +++ b/src/assets/ai/gpt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/ai/qingxi.jpg b/src/assets/ai/qingxi.jpg new file mode 100644 index 00000000..d76b8156 Binary files /dev/null and b/src/assets/ai/qingxi.jpg differ diff --git a/src/assets/ai/ziran.jpg b/src/assets/ai/ziran.jpg new file mode 100644 index 00000000..62907242 Binary files /dev/null and b/src/assets/ai/ziran.jpg differ diff --git a/src/components/ContentWrap/src/ContentWrap.vue b/src/components/ContentWrap/src/ContentWrap.vue index 454e95c9..c75e4b71 100644 --- a/src/components/ContentWrap/src/ContentWrap.vue +++ b/src/components/ContentWrap/src/ContentWrap.vue @@ -10,12 +10,13 @@ const prefixCls = getPrefixCls('content-wrap') defineProps({ title: propTypes.string.def(''), - message: propTypes.string.def('') + message: propTypes.string.def(''), + bodyStyle: propTypes.object.def({ padding: '20px' }) }) diff --git a/src/components/FormCreate/src/components/useApiSelect.tsx b/src/components/FormCreate/src/components/useApiSelect.tsx index a67e6f68..29cd3027 100644 --- a/src/components/FormCreate/src/components/useApiSelect.tsx +++ b/src/components/FormCreate/src/components/useApiSelect.tsx @@ -27,6 +27,11 @@ export const useApiSelect = (option: ApiSelectProps) => { type: String, default: 'GET' }, + // 选项解析函数 + parseFunc: { + type: String, + default: '' + }, // 请求参数 data: { type: String, @@ -41,35 +46,121 @@ export const useApiSelect = (option: ApiSelectProps) => { multiple: { type: Boolean, default: false + }, + // 是否远程搜索 + remote: { + type: Boolean, + default: false + }, + // 远程搜索时携带的参数 + remoteField: { + type: String, + default: 'label' } }, setup(props) { const attrs = useAttrs() const options = ref([]) // 下拉数据 + const loading = ref(false) // 是否正在从远程获取数据 + const queryParam = ref() // 当前输入的值 const getOptions = async () => { options.value = [] // 接口选择器 if (isEmpty(props.url)) { return } - let data = [] switch (props.method) { case 'GET': - data = await request.get({ url: props.url }) + let url: string = props.url + if (props.remote) { + url = `${url}?${props.remoteField}=${queryParam.value}` + } + parseOptions(await request.get({ url: url })) break case 'POST': - data = await request.post({ url: props.url, data: jsonParse(props.data) }) + const data: any = jsonParse(props.data) + if (props.remote) { + data[props.remoteField] = queryParam.value + } + parseOptions(await request.post({ url: props.url, data: data })) break } + } + function parseOptions(data: any) { + // 情况一:如果有自定义解析函数优先使用自定义解析 + if (!isEmpty(props.parseFunc)) { + options.value = parseFunc()?.(data) + return + } + // 情况二:返回的直接是一个列表 + if (Array.isArray(data)) { + parseOptions0(data) + return + } + // 情况二:返回的是分页数据,尝试读取 list + data = data.list + if (!!data && Array.isArray(data)) { + parseOptions0(data) + return + } + // 情况三:不是 yudao-vue-pro 标准返回 + console.warn( + `接口[${props.url}] 返回结果不是 yudao-vue-pro 标准返回建议采用自定义解析函数处理` + ) + } + + function parseOptions0(data: any[]) { if (Array.isArray(data)) { options.value = data.map((item: any) => ({ - label: item[props.labelField], - value: item[props.valueField] + label: parseExpression(item, props.labelField), + value: parseExpression(item, props.valueField) })) return } - console.error(`接口[${props.url}] 返回结果不是一个数组`) + console.warn(`接口[${props.url}] 返回结果不是一个数组`) + } + + function parseFunc() { + let parse: any = null + if (!!props.parseFunc) { + // 解析字符串函数 + parse = new Function(`return ${props.parseFunc}`)() + } + return parse + } + + function parseExpression(data: any, template: string) { + // 检测是否使用了表达式 + if (template.indexOf('${') === -1) { + return data[template] + } + // 正则表达式匹配模板字符串中的 ${...} + const pattern = /\$\{([^}]*)}/g + // 使用replace函数配合正则表达式和回调函数来进行替换 + return template.replace(pattern, (_, expr) => { + // expr 是匹配到的 ${} 内的表达式(这里是属性名),从 data 中获取对应的值 + const result = data[expr.trim()] // 去除前后空白,以防用户输入带空格的属性名 + if (!result) { + console.warn( + `接口选择器选项模版[${template}][${expr.trim()}] 解析值失败结果为[${result}], 请检查属性名称是否存在于接口返回值中,存在则忽略此条!!!` + ) + } + return result + }) + } + + const remoteMethod = async (query: any) => { + if (!query) { + return + } + loading.value = true + try { + queryParam.value = query + await getOptions() + } finally { + loading.value = false + } } onMounted(async () => { @@ -80,15 +171,29 @@ export const useApiSelect = (option: ApiSelectProps) => { if (props.multiple) { // fix:多写此步是为了解决 multiple 属性问题 return ( - + {options.value.map((item, index) => ( ))} ) } + debugger return ( - + {options.value.map((item, index) => ( ))} diff --git a/src/components/FormCreate/src/config/selectRule.ts b/src/components/FormCreate/src/config/selectRule.ts index 281d3739..a6f3841f 100644 --- a/src/components/FormCreate/src/config/selectRule.ts +++ b/src/components/FormCreate/src/config/selectRule.ts @@ -13,12 +13,30 @@ const selectRule = [ control: [ { value: 'select', - condition: '=', + condition: '==', method: 'hidden', - rule: ['multiple'] + rule: [ + 'multiple', + 'clearable', + 'collapseTags', + 'multipleLimit', + 'allowCreate', + 'filterable', + 'noMatchText', + 'remote', + 'remoteMethod', + 'reserveKeyword', + 'defaultFirstOption', + 'automaticDropdown' + ] } ] }, + { + type: 'switch', + field: 'filterable', + title: '是否可搜索' + }, { type: 'switch', field: 'multiple', title: '是否多选' }, { type: 'switch', @@ -43,27 +61,12 @@ const selectRule = [ title: 'autocomplete 属性' }, { type: 'input', field: 'placeholder', title: '占位符' }, - { - type: 'switch', - field: 'filterable', - title: '是否可搜索' - }, { type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' }, { type: 'input', field: 'noMatchText', title: '搜索条件无匹配时显示的文字' }, - { - type: 'switch', - field: 'remote', - title: '其中的选项是否从服务器远程加载' - }, - { - type: 'Struct', - field: 'remoteMethod', - title: '自定义远程搜索方法' - }, { type: 'input', field: 'noDataText', title: '选项为空时显示的文字' }, { type: 'switch', @@ -130,6 +133,7 @@ const apiSelectRule = [ type: 'input', field: 'labelField', title: 'label 属性', + info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}', props: { placeholder: 'nickname' } @@ -138,9 +142,39 @@ const apiSelectRule = [ type: 'input', field: 'valueField', title: 'value 属性', + info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}', props: { placeholder: 'id' } + }, + { + type: 'input', + field: 'parseFunc', + title: '选项解析函数', + info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表 + (data: any)=>{ label: string; value: any }[]`, + props: { + autosize: true, + rows: { minRows: 2, maxRows: 6 }, + type: 'textarea', + placeholder: ` + function (data) { + console.log(data) + return data.list.map(item=> ({label: item.nickname,value: item.id})) + }` + } + }, + { + type: 'switch', + field: 'remote', + info: '是否可搜索', + title: '其中的选项是否从服务器远程加载' + }, + { + type: 'input', + field: 'remoteField', + title: '请求参数', + info: '远程请求时请求携带的参数名称,如:name' } ] diff --git a/src/components/FormCreate/src/config/useDictSelectRule.ts b/src/components/FormCreate/src/config/useDictSelectRule.ts index 3db630bc..5c5e8cad 100644 --- a/src/components/FormCreate/src/config/useDictSelectRule.ts +++ b/src/components/FormCreate/src/config/useDictSelectRule.ts @@ -2,6 +2,7 @@ import { generateUUID } from '@/utils' import * as DictDataApi from '@/api/system/dict/dict.type' import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils' import { selectRule } from '@/components/FormCreate/src/config/selectRule' +import { cloneDeep } from 'lodash-es' /** * 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule @@ -9,6 +10,7 @@ import { selectRule } from '@/components/FormCreate/src/config/selectRule' export const useDictSelectRule = () => { const label = '字典选择器' const name = 'DictSelect' + const rules = cloneDeep(selectRule) const dictOptions = ref<{ label: string; value: string }[]>([]) // 字典类型下拉数据 onMounted(async () => { const data = await DictDataApi.getSimpleDictTypeList() @@ -55,7 +57,7 @@ export const useDictSelectRule = () => { { label: '布尔值', value: 'bool' } ] }, - ...selectRule + ...rules ]) } } diff --git a/src/components/FormCreate/src/config/useSelectRule.ts b/src/components/FormCreate/src/config/useSelectRule.ts index bf20588d..ff21a223 100644 --- a/src/components/FormCreate/src/config/useSelectRule.ts +++ b/src/components/FormCreate/src/config/useSelectRule.ts @@ -2,6 +2,7 @@ import { generateUUID } from '@/utils' import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils' import { selectRule } from '@/components/FormCreate/src/config/selectRule' import { SelectRuleOption } from '@/components/FormCreate/src/type' +import { cloneDeep } from 'lodash-es' /** * 通用选择器规则 hook @@ -11,6 +12,7 @@ import { SelectRuleOption } from '@/components/FormCreate/src/type' export const useSelectRule = (option: SelectRuleOption) => { const label = option.label const name = option.name + const rules = cloneDeep(selectRule) return { icon: option.icon, label, @@ -28,7 +30,7 @@ export const useSelectRule = (option: SelectRuleOption) => { if (!option.props) { option.props = [] } - return localeProps(t, name + '.props', [makeRequiredRule(), ...option.props, ...selectRule]) + return localeProps(t, name + '.props', [makeRequiredRule(), ...option.props, ...rules]) } } } diff --git a/src/components/FormCreate/src/useFormCreateDesigner.ts b/src/components/FormCreate/src/useFormCreateDesigner.ts index 69c8f314..53fee78e 100644 --- a/src/components/FormCreate/src/useFormCreateDesigner.ts +++ b/src/components/FormCreate/src/useFormCreateDesigner.ts @@ -71,9 +71,9 @@ export const useFormCreateDesigner = async (designer: Ref) => { */ const buildSystemMenu = () => { // 移除自带的下拉选择器组件,使用 currencySelectRule 替代 - designer.value?.removeMenuItem('select') - designer.value?.removeMenuItem('radio') - designer.value?.removeMenuItem('checkbox') + // designer.value?.removeMenuItem('select') + // designer.value?.removeMenuItem('radio') + // designer.value?.removeMenuItem('checkbox') const components = [userSelectRule, deptSelectRule, dictSelectRule, apiSelectRule0] const menu: Menu = { name: 'system', diff --git a/src/components/FormCreate/src/utils/index.ts b/src/components/FormCreate/src/utils/index.ts index e5480981..2d4a6fd7 100644 --- a/src/components/FormCreate/src/utils/index.ts +++ b/src/components/FormCreate/src/utils/index.ts @@ -1,4 +1,3 @@ -// TODO puhui999: 借鉴一下 form-create-designer utils 方法 🤣 (导入不了只能先 copy 过来用下) export function makeRequiredRule() { return { type: 'Required', @@ -17,63 +16,3 @@ export const localeProps = (t, prefix, rules) => { return rule }) } - -export function upper(str) { - return str.replace(str[0], str[0].toLocaleUpperCase()) -} - -export function makeOptionsRule(t, to, userOptions) { - console.log(userOptions[0]) - const options = [ - { label: t('props.optionsType.struct'), value: 0 }, - { label: t('props.optionsType.json'), value: 1 }, - { label: '用户数据', value: 2 } - ] - - const control = [ - { - value: 0, - rule: [ - { - type: 'TableOptions', - field: 'formCreate' + upper(to).replace('.', '>'), - props: { defaultValue: [] } - } - ] - }, - { - value: 1, - rule: [ - { - type: 'Struct', - field: 'formCreate' + upper(to).replace('.', '>'), - props: { defaultValue: [] } - } - ] - }, - { - value: 2, - rule: [ - { - type: 'TableOptions', - field: 'formCreate' + upper(to).replace('.', '>'), - props: { modelValue: [] } - } - ] - } - ] - options.splice(0, 0) - control.push() - - return { - type: 'radio', - title: t('props.options'), - field: '_optionType', - value: 0, - options, - props: { - type: 'button' - }, - control - } -} diff --git a/src/components/Icon/src/Icon.vue b/src/components/Icon/src/Icon.vue index 4246539f..a90bb37e 100644 --- a/src/components/Icon/src/Icon.vue +++ b/src/components/Icon/src/Icon.vue @@ -22,7 +22,7 @@ const props = defineProps({ const elRef = ref(null) -const isLocal = computed(() => props.icon.startsWith('svg-icon:')) +const isLocal = computed(() => props.icon?.startsWith('svg-icon:')) const symbolId = computed(() => { return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon diff --git a/src/components/MarkdownView/index.vue b/src/components/MarkdownView/index.vue new file mode 100644 index 00000000..55166ff4 --- /dev/null +++ b/src/components/MarkdownView/index.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue b/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue index 1715d73b..304630d9 100644 --- a/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue +++ b/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue @@ -129,7 +129,7 @@ const updateFlowType = (flowType) => { conditionExpression: null }) bpmnInstances().modeling.updateProperties(toRaw(bpmnElementSource.value), { - default: bpmnElement.value + default: toRaw(bpmnElement.value) }) return } diff --git a/src/config/axios/service.ts b/src/config/axios/service.ts index 25936068..aed40908 100644 --- a/src/config/axios/service.ts +++ b/src/config/axios/service.ts @@ -174,6 +174,7 @@ service.interceptors.response.use( if (msg === '无效的刷新令牌') { // hard coding:忽略这个提示,直接登出 console.log(msg) + return handleAuthorized() } else { ElNotification.error({ title: msg }) } diff --git a/src/layout/Layout.vue b/src/layout/Layout.vue index 43f9b69d..af519707 100644 --- a/src/layout/Layout.vue +++ b/src/layout/Layout.vue @@ -72,7 +72,7 @@ $prefix-cls: #{$namespace}-layout; .#{$prefix-cls} { background-color: var(--app-content-bg-color); :deep(.#{$elNamespace}-scrollbar__view) { - height: 100% !important; + height: 99% !important; } } diff --git a/src/permission.ts b/src/permission.ts index d538303b..b04bc3c1 100644 --- a/src/permission.ts +++ b/src/permission.ts @@ -83,7 +83,7 @@ router.beforeEach(async (to, from, next) => { const redirectPath = from.query.redirect || to.path // 修复跳转时不带参数的问题 const redirect = decodeURIComponent(redirectPath as string) - const { basePath, paramsObject: query } = parseURL(redirect) + const { paramsObject: query } = parseURL(redirect) const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect, query } next(nextData) } else { diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index cecb3686..a8e5e0c3 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -70,6 +70,26 @@ const remainingRouter: AppRouteRecordRaw[] = [ } ] }, + // { + // path: '/ai/music', + // component: Layout, + // redirect: '/index', + // name: 'AIMusic', + // meta: {}, + // children: [ + // { + // path: 'index', + // component: () => import('@/views/ai/music/components/index.vue'), + // name: 'AIMusicIndex', + // meta: { + // title: 'AI 音乐', + // icon: 'ep:home-filled', + // noCache: false, + // affix: true + // } + // } + // ] + // }, { path: '/user', component: Layout, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index f4d67b4e..cfa785b0 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -109,6 +109,14 @@ export const PayChannelEnum = { code: 'wx_app', name: '微信 APP 支付' }, + WX_NATIVE: { + code: 'wx_native', + name: '微信 Native 支付' + }, + WX_WAP: { + code: 'wx_wap', + name: '微信 WAP 网站支付' + }, WX_BAR: { code: 'wx_bar', name: '微信条码支付' diff --git a/src/utils/dict.ts b/src/utils/dict.ts index 80043bf4..13b18588 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -1,8 +1,8 @@ /** * 数据字典工具类 */ -import {useDictStoreWithOut} from '@/store/modules/dict' -import {ElementPlusInfoType} from '@/types/elementPlus' +import { useDictStoreWithOut } from '@/store/modules/dict' +import { ElementPlusInfoType } from '@/types/elementPlus' const dictStore = useDictStoreWithOut() @@ -24,6 +24,10 @@ export interface NumberDictDataType extends DictDataType { value: number } +export interface StringDictDataType extends DictDataType { + value: string +} + export const getDictOptions = (dictType: string) => { return dictStore.getDictByType(dictType) || [] } @@ -44,8 +48,11 @@ export const getIntDictOptions = (dictType: string): NumberDictDataType[] => { } export const getStrDictOptions = (dictType: string) => { - const dictOption: DictDataType[] = [] + // 获得通用的 DictDataType 列表 const dictOptions: DictDataType[] = getDictOptions(dictType) + // 转换成 string 类型的 StringDictDataType 类型 + // why 需要特殊转换:避免 IDEA 在 v-for="dict in getStrDictOptions(...)" 时,el-option 的 key 会告警 + const dictOption: StringDictDataType[] = [] dictOptions.forEach((dict: DictDataType) => { dictOption.push({ ...dict, @@ -211,6 +218,18 @@ export enum DICT_TYPE { ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态 ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type', // 库存明细的业务类型 + // ========== AI - 人工智能模块 ========== + AI_PLATFORM = 'ai_platform', // AI 平台 + AI_IMAGE_STATUS = 'ai_image_status', // AI 图片状态 + AI_MUSIC_STATUS = 'ai_music_status', // AI 音乐状态 + AI_GENERATE_MODE = 'ai_generate_mode', // AI 生成模式 + AI_WRITE_TYPE = 'ai_write_type', // AI 写作类型 + AI_WRITE_LENGTH = 'ai_write_length', // AI 写作长度 + AI_WRITE_FORMAT = 'ai_write_format', // AI 写作格式 + AI_WRITE_TONE = 'ai_write_tone', // AI 写作语气 + AI_WRITE_LANGUAGE = 'ai_write_language' // AI 写作语言 + ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type', // 库存明细的业务类型 + // ============== PMS ================= PROJECT_TYPE = 'project_type', // 项目类型 ENTRUST_METHOD = 'entrust_method', // 委托方式 diff --git a/src/utils/download.ts b/src/utils/download.ts index ab200149..1d07484b 100644 --- a/src/utils/download.ts +++ b/src/utils/download.ts @@ -32,6 +32,24 @@ const download = { // 下载 Markdown 方法 markdown: (data: Blob, fileName: string) => { download0(data, fileName, 'text/markdown') + }, + // 下载图片(允许跨域) + image: (url: string) => { + const image = new Image() + image.setAttribute('crossOrigin', 'anonymous') + image.src = url + image.onload = () => { + const canvas = document.createElement('canvas') + canvas.width = image.width + canvas.height = image.height + const ctx = canvas.getContext('2d') as CanvasDrawImage + ctx.drawImage(image, 0, 0, image.width, image.height) + const url = canvas.toDataURL('image/png') + const a = document.createElement('a') + a.href = url + a.download = 'image.png' + a.click() + } } } diff --git a/src/utils/index.ts b/src/utils/index.ts index 2590bce8..2c2fbbd0 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -313,7 +313,7 @@ export const fenToYuan = (price: string | number): string => { */ export const calculateRelativeRate = (value?: number, reference?: number) => { // 防止除0 - if (!reference) return 0 + if (!reference || reference == 0) return 0 return ((100 * ((value || 0) - reference)) / reference).toFixed(0) } diff --git a/src/views/Login/components/LoginForm.vue b/src/views/Login/components/LoginForm.vue index edc834fa..9d416cf6 100644 --- a/src/views/Login/components/LoginForm.vue +++ b/src/views/Login/components/LoginForm.vue @@ -184,9 +184,9 @@ const loginData = reactive({ captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE, tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE, loginForm: { - tenantName: '设计院', - username: 'admin', - password: 'admin123', + tenantName: import.meta.env.VITE_APP_DEFAULT_LOGIN_TENANT || '', + username: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '', + password: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '', captchaVerification: '', rememberMe: true // 默认记录我。如果不需要,可手动修改 } diff --git a/src/views/ai/chat/index.vue b/src/views/ai/chat/index.vue deleted file mode 100644 index bc846a3d..00000000 --- a/src/views/ai/chat/index.vue +++ /dev/null @@ -1,166 +0,0 @@ - - - diff --git a/src/views/ai/chat/index/components/conversation/ConversationList.vue b/src/views/ai/chat/index/components/conversation/ConversationList.vue new file mode 100644 index 00000000..54940f82 --- /dev/null +++ b/src/views/ai/chat/index/components/conversation/ConversationList.vue @@ -0,0 +1,472 @@ + + + + + + diff --git a/src/views/ai/chat/index/components/conversation/ConversationUpdateForm.vue b/src/views/ai/chat/index/components/conversation/ConversationUpdateForm.vue new file mode 100644 index 00000000..bff094fb --- /dev/null +++ b/src/views/ai/chat/index/components/conversation/ConversationUpdateForm.vue @@ -0,0 +1,145 @@ + + diff --git a/src/views/ai/chat/index/components/message/MessageList.vue b/src/views/ai/chat/index/components/message/MessageList.vue new file mode 100644 index 00000000..2cc84079 --- /dev/null +++ b/src/views/ai/chat/index/components/message/MessageList.vue @@ -0,0 +1,282 @@ + + + + diff --git a/src/views/ai/chat/index/components/message/MessageListEmpty.vue b/src/views/ai/chat/index/components/message/MessageListEmpty.vue new file mode 100644 index 00000000..b042fd66 --- /dev/null +++ b/src/views/ai/chat/index/components/message/MessageListEmpty.vue @@ -0,0 +1,83 @@ + + + + diff --git a/src/views/ai/chat/index/components/message/MessageLoading.vue b/src/views/ai/chat/index/components/message/MessageLoading.vue new file mode 100644 index 00000000..f3198cbd --- /dev/null +++ b/src/views/ai/chat/index/components/message/MessageLoading.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/src/views/ai/chat/index/components/message/MessageNewConversation.vue b/src/views/ai/chat/index/components/message/MessageNewConversation.vue new file mode 100644 index 00000000..40c3107a --- /dev/null +++ b/src/views/ai/chat/index/components/message/MessageNewConversation.vue @@ -0,0 +1,46 @@ + + + + diff --git a/src/views/ai/chat/index/components/role/RoleCategoryList.vue b/src/views/ai/chat/index/components/role/RoleCategoryList.vue new file mode 100644 index 00000000..c02126d5 --- /dev/null +++ b/src/views/ai/chat/index/components/role/RoleCategoryList.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/views/ai/chat/index/components/role/RoleHeader.vue b/src/views/ai/chat/index/components/role/RoleHeader.vue new file mode 100644 index 00000000..17b1693b --- /dev/null +++ b/src/views/ai/chat/index/components/role/RoleHeader.vue @@ -0,0 +1,48 @@ + + + + + + diff --git a/src/views/ai/chat/index/components/role/RoleList.vue b/src/views/ai/chat/index/components/role/RoleList.vue new file mode 100644 index 00000000..b148b220 --- /dev/null +++ b/src/views/ai/chat/index/components/role/RoleList.vue @@ -0,0 +1,174 @@ + + + + + + diff --git a/src/views/ai/chat/index/components/role/RoleRepository.vue b/src/views/ai/chat/index/components/role/RoleRepository.vue new file mode 100644 index 00000000..246dcb4f --- /dev/null +++ b/src/views/ai/chat/index/components/role/RoleRepository.vue @@ -0,0 +1,289 @@ + + + + + + + + diff --git a/src/views/ai/chat/index/index.vue b/src/views/ai/chat/index/index.vue new file mode 100644 index 00000000..229b8959 --- /dev/null +++ b/src/views/ai/chat/index/index.vue @@ -0,0 +1,773 @@ + + + + + diff --git a/src/views/ai/chat/manager/ChatConversationList.vue b/src/views/ai/chat/manager/ChatConversationList.vue new file mode 100644 index 00000000..23933f01 --- /dev/null +++ b/src/views/ai/chat/manager/ChatConversationList.vue @@ -0,0 +1,163 @@ + + + diff --git a/src/views/ai/chat/manager/ChatMessageList.vue b/src/views/ai/chat/manager/ChatMessageList.vue new file mode 100644 index 00000000..0d841840 --- /dev/null +++ b/src/views/ai/chat/manager/ChatMessageList.vue @@ -0,0 +1,175 @@ + + + diff --git a/src/views/ai/chat/manager/index.vue b/src/views/ai/chat/manager/index.vue new file mode 100644 index 00000000..ca2d0924 --- /dev/null +++ b/src/views/ai/chat/manager/index.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/views/ai/image/index/components/ImageCard.vue b/src/views/ai/image/index/components/ImageCard.vue new file mode 100644 index 00000000..4ba78cac --- /dev/null +++ b/src/views/ai/image/index/components/ImageCard.vue @@ -0,0 +1,162 @@ + + + + diff --git a/src/views/ai/image/index/components/ImageDetail.vue b/src/views/ai/image/index/components/ImageDetail.vue new file mode 100644 index 00000000..ad15aa8d --- /dev/null +++ b/src/views/ai/image/index/components/ImageDetail.vue @@ -0,0 +1,224 @@ + + + + diff --git a/src/views/ai/image/index/components/ImageList.vue b/src/views/ai/image/index/components/ImageList.vue new file mode 100644 index 00000000..cdd1e208 --- /dev/null +++ b/src/views/ai/image/index/components/ImageList.vue @@ -0,0 +1,233 @@ + + + diff --git a/src/views/ai/image/index/components/dall3/index.vue b/src/views/ai/image/index/components/dall3/index.vue new file mode 100644 index 00000000..5c891ab9 --- /dev/null +++ b/src/views/ai/image/index/components/dall3/index.vue @@ -0,0 +1,320 @@ + + + + diff --git a/src/views/ai/image/index/components/midjourney/index.vue b/src/views/ai/image/index/components/midjourney/index.vue new file mode 100644 index 00000000..1d7fda18 --- /dev/null +++ b/src/views/ai/image/index/components/midjourney/index.vue @@ -0,0 +1,326 @@ + + + + diff --git a/src/views/ai/image/index/components/other/index.vue b/src/views/ai/image/index/components/other/index.vue new file mode 100644 index 00000000..a688be18 --- /dev/null +++ b/src/views/ai/image/index/components/other/index.vue @@ -0,0 +1,216 @@ + + + + diff --git a/src/views/ai/image/index/components/stableDiffusion/index.vue b/src/views/ai/image/index/components/stableDiffusion/index.vue new file mode 100644 index 00000000..169938f4 --- /dev/null +++ b/src/views/ai/image/index/components/stableDiffusion/index.vue @@ -0,0 +1,272 @@ + + + + diff --git a/src/views/ai/image/index/index.vue b/src/views/ai/image/index/index.vue new file mode 100644 index 00000000..1217e793 --- /dev/null +++ b/src/views/ai/image/index/index.vue @@ -0,0 +1,141 @@ + + + + + + diff --git a/src/views/ai/image/manager/index.vue b/src/views/ai/image/manager/index.vue new file mode 100644 index 00000000..84403f35 --- /dev/null +++ b/src/views/ai/image/manager/index.vue @@ -0,0 +1,251 @@ + + + diff --git a/src/views/ai/image/square/index.vue b/src/views/ai/image/square/index.vue new file mode 100644 index 00000000..81643d19 --- /dev/null +++ b/src/views/ai/image/square/index.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/views/ai/model/apiKey/ApiKeyForm.vue b/src/views/ai/model/apiKey/ApiKeyForm.vue new file mode 100644 index 00000000..a8fc0128 --- /dev/null +++ b/src/views/ai/model/apiKey/ApiKeyForm.vue @@ -0,0 +1,132 @@ + + diff --git a/src/views/ai/model/apiKey/index.vue b/src/views/ai/model/apiKey/index.vue new file mode 100644 index 00000000..6daf6a7d --- /dev/null +++ b/src/views/ai/model/apiKey/index.vue @@ -0,0 +1,180 @@ + + + diff --git a/src/views/ai/model/chatModel/ChatModelForm.vue b/src/views/ai/model/chatModel/ChatModelForm.vue new file mode 100644 index 00000000..e3f785c0 --- /dev/null +++ b/src/views/ai/model/chatModel/ChatModelForm.vue @@ -0,0 +1,181 @@ + + diff --git a/src/views/ai/model/chatModel/index.vue b/src/views/ai/model/chatModel/index.vue new file mode 100644 index 00000000..c5506746 --- /dev/null +++ b/src/views/ai/model/chatModel/index.vue @@ -0,0 +1,185 @@ + + + diff --git a/src/views/ai/model/chatRole/ChatRoleForm.vue b/src/views/ai/model/chatRole/ChatRoleForm.vue new file mode 100644 index 00000000..3c49e8ed --- /dev/null +++ b/src/views/ai/model/chatRole/ChatRoleForm.vue @@ -0,0 +1,183 @@ + + diff --git a/src/views/ai/model/chatRole/index.vue b/src/views/ai/model/chatRole/index.vue new file mode 100644 index 00000000..e870a556 --- /dev/null +++ b/src/views/ai/model/chatRole/index.vue @@ -0,0 +1,187 @@ + + + diff --git a/src/views/ai/music/components/index.vue b/src/views/ai/music/components/index.vue new file mode 100644 index 00000000..e1395b56 --- /dev/null +++ b/src/views/ai/music/components/index.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/views/ai/music/components/list/audioBar/index.vue b/src/views/ai/music/components/list/audioBar/index.vue new file mode 100644 index 00000000..412d7fef --- /dev/null +++ b/src/views/ai/music/components/list/audioBar/index.vue @@ -0,0 +1,68 @@ + + + diff --git a/src/views/ai/music/components/list/index.vue b/src/views/ai/music/components/list/index.vue new file mode 100644 index 00000000..1b8a46e5 --- /dev/null +++ b/src/views/ai/music/components/list/index.vue @@ -0,0 +1,94 @@ + + + + + + diff --git a/src/views/ai/music/components/list/songCard/index.vue b/src/views/ai/music/components/list/songCard/index.vue new file mode 100644 index 00000000..dc1ffa8b --- /dev/null +++ b/src/views/ai/music/components/list/songCard/index.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/views/ai/music/components/list/songInfo/index.vue b/src/views/ai/music/components/list/songInfo/index.vue new file mode 100644 index 00000000..4832bfcb --- /dev/null +++ b/src/views/ai/music/components/list/songInfo/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/views/ai/music/components/mode/desc.vue b/src/views/ai/music/components/mode/desc.vue new file mode 100644 index 00000000..4488461e --- /dev/null +++ b/src/views/ai/music/components/mode/desc.vue @@ -0,0 +1,55 @@ + + + diff --git a/src/views/ai/music/components/mode/index.vue b/src/views/ai/music/components/mode/index.vue new file mode 100644 index 00000000..61cf238e --- /dev/null +++ b/src/views/ai/music/components/mode/index.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/views/ai/music/components/mode/lyric.vue b/src/views/ai/music/components/mode/lyric.vue new file mode 100644 index 00000000..f774003a --- /dev/null +++ b/src/views/ai/music/components/mode/lyric.vue @@ -0,0 +1,83 @@ + + + diff --git a/src/views/ai/music/components/title/index.vue b/src/views/ai/music/components/title/index.vue new file mode 100644 index 00000000..a0658027 --- /dev/null +++ b/src/views/ai/music/components/title/index.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/views/ai/music/manager/index.vue b/src/views/ai/music/manager/index.vue new file mode 100644 index 00000000..462a88d2 --- /dev/null +++ b/src/views/ai/music/manager/index.vue @@ -0,0 +1,292 @@ + + + diff --git a/src/views/ai/utils/constants.ts b/src/views/ai/utils/constants.ts new file mode 100644 index 00000000..4fee2dc6 --- /dev/null +++ b/src/views/ai/utils/constants.ts @@ -0,0 +1,416 @@ +/** + * Created by 芋道源码 + * + * AI 枚举类 + * + * 问题:为什么不放在 src/utils/constants.ts 呢? + * 回答:主要 AI 是可选模块,考虑到独立、解耦,所以放在了 /views/ai/utils/constants.ts + */ + +/** + * AI 平台的枚举 + */ +export const AiPlatformEnum = { + TONG_YI: 'TongYi', // 阿里 + YI_YAN: 'YiYan', // 百度 + DEEP_SEEK: 'DeepSeek', // DeepSeek + ZHI_PU: 'ZhiPu', // 智谱 AI + XING_HUO: 'XingHuo', // 讯飞 + OPENAI: 'OpenAI', + Ollama: 'Ollama', + STABLE_DIFFUSION: 'StableDiffusion', // Stability AI + MIDJOURNEY: 'Midjourney', // Midjourney + SUNO: 'Suno' // Suno AI +} + +export const OtherPlatformEnum: ImageModelVO[] = [ + { + key: AiPlatformEnum.TONG_YI, + name: '通义万相' + }, + { + key: AiPlatformEnum.YI_YAN, + name: '百度千帆' + }, + { + key: AiPlatformEnum.ZHI_PU, + name: '智谱 AI' + } +] + +/** + * AI 图像生成状态的枚举 + */ +export const AiImageStatusEnum = { + IN_PROGRESS: 10, // 进行中 + SUCCESS: 20, // 已完成 + FAIL: 30 // 已失败 +} + +/** + * AI 音乐生成状态的枚举 + */ +export const AiMusicStatusEnum = { + IN_PROGRESS: 10, // 进行中 + SUCCESS: 20, // 已完成 + FAIL: 30 // 已失败 +} + +/** + * AI 写作类型的枚举 + */ +export enum AiWriteTypeEnum { + WRITING = 1, // 撰写 + REPLY // 回复 +} + +// 表格展示对照map +export const AiWriteTypeTableRender = { + [AiWriteTypeEnum.WRITING]: '撰写', + [AiWriteTypeEnum.REPLY]: '回复', +} + +// ========== 【图片 UI】相关的枚举 ========== +export const ImageHotWords = [ + '中国旗袍', + '古装美女', + '卡通头像', + '机甲战士', + '童话小屋', + '中国长城' +] // 图片热词 + +export const ImageHotEnglishWords = [ + 'Chinese Cheongsam', + 'Ancient Beauty', + 'Cartoon Avatar', + 'Mech Warrior', + 'Fairy Tale Cottage', + 'The Great Wall of China' +] // 图片热词(英文) + +export interface ImageModelVO { + key: string + name: string + image?: string +} + +export const StableDiffusionSamplers: ImageModelVO[] = [ + { + key: 'DDIM', + name: 'DDIM' + }, + { + key: 'DDPM', + name: 'DDPM' + }, + { + key: 'K_DPMPP_2M', + name: 'K_DPMPP_2M' + }, + { + key: 'K_DPMPP_2S_ANCESTRAL', + name: 'K_DPMPP_2S_ANCESTRAL' + }, + { + key: 'K_DPM_2', + name: 'K_DPM_2' + }, + { + key: 'K_DPM_2_ANCESTRAL', + name: 'K_DPM_2_ANCESTRAL' + }, + { + key: 'K_EULER', + name: 'K_EULER' + }, + { + key: 'K_EULER_ANCESTRAL', + name: 'K_EULER_ANCESTRAL' + }, + { + key: 'K_HEUN', + name: 'K_HEUN' + }, + { + key: 'K_LMS', + name: 'K_LMS' + } +] + +export const StableDiffusionStylePresets: ImageModelVO[] = [ + { + key: '3d-model', + name: '3d-model' + }, + { + key: 'analog-film', + name: 'analog-film' + }, + { + key: 'anime', + name: 'anime' + }, + { + key: 'cinematic', + name: 'cinematic' + }, + { + key: 'comic-book', + name: 'comic-book' + }, + { + key: 'digital-art', + name: 'digital-art' + }, + { + key: 'enhance', + name: 'enhance' + }, + { + key: 'fantasy-art', + name: 'fantasy-art' + }, + { + key: 'isometric', + name: 'isometric' + }, + { + key: 'line-art', + name: 'line-art' + }, + { + key: 'low-poly', + name: 'low-poly' + }, + { + key: 'modeling-compound', + name: 'modeling-compound' + }, + // neon-punk origami photographic pixel-art tile-texture + { + key: 'neon-punk', + name: 'neon-punk' + }, + { + key: 'origami', + name: 'origami' + }, + { + key: 'photographic', + name: 'photographic' + }, + { + key: 'pixel-art', + name: 'pixel-art' + }, + { + key: 'tile-texture', + name: 'tile-texture' + } +] + +export const TongYiWanXiangModels: ImageModelVO[] = [ + { + key: 'wanx-v1', + name: 'wanx-v1' + }, + { + key: 'wanx-sketch-to-image-v1', + name: 'wanx-sketch-to-image-v1' + } +] + +export const QianFanModels: ImageModelVO[] = [ + { + key: 'sd_xl', + name: 'sd_xl' + } +] + +export const ChatGlmModels: ImageModelVO[] = [ + { + key: 'cogview-3', + name: 'cogview-3' + } +] + +export const StableDiffusionClipGuidancePresets: ImageModelVO[] = [ + { + key: 'NONE', + name: 'NONE' + }, + { + key: 'FAST_BLUE', + name: 'FAST_BLUE' + }, + { + key: 'FAST_GREEN', + name: 'FAST_GREEN' + }, + { + key: 'SIMPLE', + name: 'SIMPLE' + }, + { + key: 'SLOW', + name: 'SLOW' + }, + { + key: 'SLOWER', + name: 'SLOWER' + }, + { + key: 'SLOWEST', + name: 'SLOWEST' + } +] + +export const Dall3Models: ImageModelVO[] = [ + { + key: 'dall-e-3', + name: 'DALL·E 3', + image: `/src/assets/ai/dall2.jpg` + }, + { + key: 'dall-e-2', + name: 'DALL·E 2', + image: `/src/assets/ai/dall3.jpg` + } +] + +export const Dall3StyleList: ImageModelVO[] = [ + { + key: 'vivid', + name: '清晰', + image: `/src/assets/ai/qingxi.jpg` + }, + { + key: 'natural', + name: '自然', + image: `/src/assets/ai/ziran.jpg` + } +] + +export interface ImageSizeVO { + key: string + name?: string + style: string + width: string + height: string +} + +export const Dall3SizeList: ImageSizeVO[] = [ + { + key: '1024x1024', + name: '1:1', + width: '1024', + height: '1024', + style: 'width: 30px; height: 30px;background-color: #dcdcdc;' + }, + { + key: '1024x1792', + name: '3:5', + width: '1024', + height: '1792', + style: 'width: 30px; height: 50px;background-color: #dcdcdc;' + }, + { + key: '1792x1024', + name: '5:3', + width: '1792', + height: '1024', + style: 'width: 50px; height: 30px;background-color: #dcdcdc;' + } +] + +export const MidjourneyModels: ImageModelVO[] = [ + { + key: 'midjourney', + name: 'MJ', + image: 'https://bigpt8.com/pc/_nuxt/mj.34a61377.png' + }, + { + key: 'niji', + name: 'NIJI', + image: 'https://bigpt8.com/pc/_nuxt/nj.ca79b143.png' + } +] + +export const MidjourneySizeList: ImageSizeVO[] = [ + { + key: '1:1', + width: '1', + height: '1', + style: 'width: 30px; height: 30px;background-color: #dcdcdc;' + }, + { + key: '3:4', + width: '3', + height: '4', + style: 'width: 30px; height: 40px;background-color: #dcdcdc;' + }, + { + key: '4:3', + width: '4', + height: '3', + style: 'width: 40px; height: 30px;background-color: #dcdcdc;' + }, + { + key: '9:16', + width: '9', + height: '16', + style: 'width: 30px; height: 50px;background-color: #dcdcdc;' + }, + { + key: '16:9', + width: '16', + height: '9', + style: 'width: 50px; height: 30px;background-color: #dcdcdc;' + } +] + +export const MidjourneyVersions = [ + { + value: '6.0', + label: 'v6.0' + }, + { + value: '5.2', + label: 'v5.2' + }, + { + value: '5.1', + label: 'v5.1' + }, + { + value: '5.0', + label: 'v5.0' + }, + { + value: '4.0', + label: 'v4.0' + } +] + +export const NijiVersionList = [ + { + value: '5', + label: 'v5' + } +] + +// ========== 【写作 UI】相关的枚举 ========== + +/** 写作点击示例时的数据 **/ +export const WriteExample = { + write: { + prompt: 'vue', + data: 'Vue.js 是一种用于构建用户界面的渐进式 JavaScript 框架。它的核心库只关注视图层,易于上手,同时也便于与其他库或已有项目整合。\n\nVue.js 的特点包括:\n- 响应式的数据绑定:Vue.js 会自动将数据与 DOM 同步,使得状态管理变得更加简单。\n- 组件化:Vue.js 允许开发者通过小型、独立和通常可复用的组件构建大型应用。\n- 虚拟 DOM:Vue.js 使用虚拟 DOM 实现快速渲染,提高了性能。\n\n在 Vue.js 中,一个典型的应用结构可能包括:\n1. 根实例:每个 Vue 应用都需要一个根实例作为入口点。\n2. 组件系统:可以创建自定义的可复用组件。\n3. 指令:特殊的带有前缀 v- 的属性,为 DOM 元素提供特殊的行为。\n4. 插值:用于文本内容,将数据动态地插入到 HTML。\n5. 计算属性和侦听器:用于处理数据的复杂逻辑和响应数据变化。\n6. 条件渲染:根据条件决定元素的渲染。\n7. 列表渲染:用于显示列表数据。\n8. 事件处理:响应用户交互。\n9. 表单输入绑定:处理表单输入和验证。\n10. 组件生命周期钩子:在组件的不同阶段执行特定的函数。\n\nVue.js 还提供了官方的路由器 Vue Router 和状态管理库 Vuex,以支持构建复杂的单页应用(SPA)。\n\n在开发过程中,开发者通常会使用 Vue CLI,这是一个强大的命令行工具,用于快速生成 Vue 项目脚手架,集成了诸如 Babel、Webpack 等现代前端工具,以及热重载、代码检测等开发体验优化功能。\n\nVue.js 的生态系统还包括大量的第三方库和插件,如 Vuetify(UI 组件库)、Vue Test Utils(测试工具)等,这些都极大地丰富了 Vue.js 的开发生态。\n\n总的来说,Vue.js 是一个灵活、高效的前端框架,适合从小型项目到大型企业级应用的开发。它的易用性、灵活性和强大的社区支持使其成为许多开发者的首选框架之一。' + }, + reply: { + originalContent: '领导,我想请假', + prompt: '不批', + data: '您的请假申请已收悉,经核实和考虑,暂时无法批准您的请假申请。\n\n如有特殊情况或紧急事务,请及时与我联系。\n\n祝工作顺利。\n\n谢谢。' + } +} diff --git a/src/views/ai/utils/utils.ts b/src/views/ai/utils/utils.ts new file mode 100644 index 00000000..ab45ae18 --- /dev/null +++ b/src/views/ai/utils/utils.ts @@ -0,0 +1,13 @@ +/** + * Created by 芋道源码 + * + * AI 枚举类 + * + * 问题:为什么不放在 src/utils/common-utils.ts 呢? + * 回答:主要 AI 是可选模块,考虑到独立、解耦,所以放在了 /views/ai/utils/common-utils.ts + */ + +/** 判断字符串是否包含中文 */ +export const hasChinese = (str: string) => { + return /[\u4e00-\u9fa5]/.test(str) +} diff --git a/src/views/ai/write/index/components/Left.vue b/src/views/ai/write/index/components/Left.vue new file mode 100644 index 00000000..05cc04a5 --- /dev/null +++ b/src/views/ai/write/index/components/Left.vue @@ -0,0 +1,213 @@ + + + diff --git a/src/views/ai/write/index/components/Right.vue b/src/views/ai/write/index/components/Right.vue new file mode 100644 index 00000000..d0aada5d --- /dev/null +++ b/src/views/ai/write/index/components/Right.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/src/views/ai/write/index/components/Tag.vue b/src/views/ai/write/index/components/Tag.vue new file mode 100644 index 00000000..3d616bed --- /dev/null +++ b/src/views/ai/write/index/components/Tag.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/views/ai/write/index/index.vue b/src/views/ai/write/index/index.vue new file mode 100644 index 00000000..0dfda742 --- /dev/null +++ b/src/views/ai/write/index/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/views/ai/write/manager/index.vue b/src/views/ai/write/manager/index.vue new file mode 100644 index 00000000..ddd1400a --- /dev/null +++ b/src/views/ai/write/manager/index.vue @@ -0,0 +1,256 @@ + + + diff --git a/src/views/bpm/category/index.vue b/src/views/bpm/category/index.vue index 46fa6cf1..085b3715 100644 --- a/src/views/bpm/category/index.vue +++ b/src/views/bpm/category/index.vue @@ -126,7 +126,6 @@ diff --git a/src/views/mall/promotion/kefu/components/KeFuConversationList.vue b/src/views/mall/promotion/kefu/components/KeFuConversationList.vue new file mode 100644 index 00000000..2a651672 --- /dev/null +++ b/src/views/mall/promotion/kefu/components/KeFuConversationList.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/src/views/mall/promotion/kefu/components/KeFuMessageList.vue b/src/views/mall/promotion/kefu/components/KeFuMessageList.vue new file mode 100644 index 00000000..a43ab0ec --- /dev/null +++ b/src/views/mall/promotion/kefu/components/KeFuMessageList.vue @@ -0,0 +1,399 @@ + + + + + diff --git a/src/views/mall/promotion/kefu/components/asserts/a.png b/src/views/mall/promotion/kefu/components/asserts/a.png new file mode 100644 index 00000000..32939004 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/a.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/aini.png b/src/views/mall/promotion/kefu/components/asserts/aini.png new file mode 100644 index 00000000..02cf5c49 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/aini.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/aixin.png b/src/views/mall/promotion/kefu/components/asserts/aixin.png new file mode 100644 index 00000000..25e64223 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/aixin.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/baiyan.png b/src/views/mall/promotion/kefu/components/asserts/baiyan.png new file mode 100644 index 00000000..d16260af Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/baiyan.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/bizui.png b/src/views/mall/promotion/kefu/components/asserts/bizui.png new file mode 100644 index 00000000..a3b18002 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/bizui.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/buhaoyisi.png b/src/views/mall/promotion/kefu/components/asserts/buhaoyisi.png new file mode 100644 index 00000000..54c4b3f7 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/buhaoyisi.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/bukesiyi.png b/src/views/mall/promotion/kefu/components/asserts/bukesiyi.png new file mode 100644 index 00000000..5f272e3e Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/bukesiyi.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/dajing.png b/src/views/mall/promotion/kefu/components/asserts/dajing.png new file mode 100644 index 00000000..8649727e Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/dajing.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/danao.png b/src/views/mall/promotion/kefu/components/asserts/danao.png new file mode 100644 index 00000000..aa85a294 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/danao.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/daxiao.png b/src/views/mall/promotion/kefu/components/asserts/daxiao.png new file mode 100644 index 00000000..26206bc0 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/daxiao.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/dianzan.png b/src/views/mall/promotion/kefu/components/asserts/dianzan.png new file mode 100644 index 00000000..2e7f00eb Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/dianzan.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/emo.png b/src/views/mall/promotion/kefu/components/asserts/emo.png new file mode 100644 index 00000000..9c845516 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/emo.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/esi.png b/src/views/mall/promotion/kefu/components/asserts/esi.png new file mode 100644 index 00000000..84e9726f Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/esi.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/fadai.png b/src/views/mall/promotion/kefu/components/asserts/fadai.png new file mode 100644 index 00000000..0772de26 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/fadai.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/fankun.png b/src/views/mall/promotion/kefu/components/asserts/fankun.png new file mode 100644 index 00000000..6e18dac3 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/fankun.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/feiwen.png b/src/views/mall/promotion/kefu/components/asserts/feiwen.png new file mode 100644 index 00000000..be976165 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/feiwen.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/fennu.png b/src/views/mall/promotion/kefu/components/asserts/fennu.png new file mode 100644 index 00000000..20c57338 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/fennu.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/ganga.png b/src/views/mall/promotion/kefu/components/asserts/ganga.png new file mode 100644 index 00000000..30ec329d Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/ganga.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/ganmao.png b/src/views/mall/promotion/kefu/components/asserts/ganmao.png new file mode 100644 index 00000000..35bbb89f Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/ganmao.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/hanyan.png b/src/views/mall/promotion/kefu/components/asserts/hanyan.png new file mode 100644 index 00000000..a0bc838b Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/hanyan.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/haochi.png b/src/views/mall/promotion/kefu/components/asserts/haochi.png new file mode 100644 index 00000000..2e52b6be Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/haochi.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/hongxin.png b/src/views/mall/promotion/kefu/components/asserts/hongxin.png new file mode 100644 index 00000000..65b5de8f Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/hongxin.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/huaixiao.png b/src/views/mall/promotion/kefu/components/asserts/huaixiao.png new file mode 100644 index 00000000..bc0e76c4 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/huaixiao.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/jingkong.png b/src/views/mall/promotion/kefu/components/asserts/jingkong.png new file mode 100644 index 00000000..7aa65845 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/jingkong.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/jingshu.png b/src/views/mall/promotion/kefu/components/asserts/jingshu.png new file mode 100644 index 00000000..0e984d68 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/jingshu.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/jingya.png b/src/views/mall/promotion/kefu/components/asserts/jingya.png new file mode 100644 index 00000000..9ba6bab3 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/jingya.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/kaixin.png b/src/views/mall/promotion/kefu/components/asserts/kaixin.png new file mode 100644 index 00000000..29c9f5dd Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/kaixin.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/keai.png b/src/views/mall/promotion/kefu/components/asserts/keai.png new file mode 100644 index 00000000..d3b582c6 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/keai.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/keshui.png b/src/views/mall/promotion/kefu/components/asserts/keshui.png new file mode 100644 index 00000000..cef489ea Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/keshui.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/kun.png b/src/views/mall/promotion/kefu/components/asserts/kun.png new file mode 100644 index 00000000..1ddc388a Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/kun.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/lengku.png b/src/views/mall/promotion/kefu/components/asserts/lengku.png new file mode 100644 index 00000000..c5c6feeb Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/lengku.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/liuhan.png b/src/views/mall/promotion/kefu/components/asserts/liuhan.png new file mode 100644 index 00000000..e6ddc6f4 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/liuhan.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/liukoushui.png b/src/views/mall/promotion/kefu/components/asserts/liukoushui.png new file mode 100644 index 00000000..3e2fba65 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/liukoushui.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/liulei.png b/src/views/mall/promotion/kefu/components/asserts/liulei.png new file mode 100644 index 00000000..dbf82040 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/liulei.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/mengbi.png b/src/views/mall/promotion/kefu/components/asserts/mengbi.png new file mode 100644 index 00000000..a4206eef Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/mengbi.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/mianwubiaoqing.png b/src/views/mall/promotion/kefu/components/asserts/mianwubiaoqing.png new file mode 100644 index 00000000..6f315b98 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/mianwubiaoqing.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/nanguo.png b/src/views/mall/promotion/kefu/components/asserts/nanguo.png new file mode 100644 index 00000000..19b9fb94 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/nanguo.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/outu.png b/src/views/mall/promotion/kefu/components/asserts/outu.png new file mode 100644 index 00000000..2f9a06d6 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/outu.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/picture.svg b/src/views/mall/promotion/kefu/components/asserts/picture.svg new file mode 100644 index 00000000..8811d495 --- /dev/null +++ b/src/views/mall/promotion/kefu/components/asserts/picture.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/views/mall/promotion/kefu/components/asserts/shengqi.png b/src/views/mall/promotion/kefu/components/asserts/shengqi.png new file mode 100644 index 00000000..7dce41dc Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/shengqi.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/shuizhuo.png b/src/views/mall/promotion/kefu/components/asserts/shuizhuo.png new file mode 100644 index 00000000..97d0f0a6 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/shuizhuo.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/tianshi.png b/src/views/mall/promotion/kefu/components/asserts/tianshi.png new file mode 100644 index 00000000..eb922dd7 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/tianshi.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/xiaodiaoya.png b/src/views/mall/promotion/kefu/components/asserts/xiaodiaoya.png new file mode 100644 index 00000000..29fbc0e1 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/xiaodiaoya.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/xiaoku.png b/src/views/mall/promotion/kefu/components/asserts/xiaoku.png new file mode 100644 index 00000000..88a169d4 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/xiaoku.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/xinsui.png b/src/views/mall/promotion/kefu/components/asserts/xinsui.png new file mode 100644 index 00000000..a0f572a1 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/xinsui.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/xiong.png b/src/views/mall/promotion/kefu/components/asserts/xiong.png new file mode 100644 index 00000000..43dfd709 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/xiong.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/yiwen.png b/src/views/mall/promotion/kefu/components/asserts/yiwen.png new file mode 100644 index 00000000..4c0da709 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/yiwen.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/yun.png b/src/views/mall/promotion/kefu/components/asserts/yun.png new file mode 100644 index 00000000..56e5d021 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/yun.png differ diff --git a/src/views/mall/promotion/kefu/components/asserts/ziya.png b/src/views/mall/promotion/kefu/components/asserts/ziya.png new file mode 100644 index 00000000..593ef5e6 Binary files /dev/null and b/src/views/mall/promotion/kefu/components/asserts/ziya.png differ diff --git a/src/views/mall/promotion/kefu/components/index.ts b/src/views/mall/promotion/kefu/components/index.ts new file mode 100644 index 00000000..88e52832 --- /dev/null +++ b/src/views/mall/promotion/kefu/components/index.ts @@ -0,0 +1,4 @@ +import KeFuConversationList from './KeFuConversationList.vue' +import KeFuMessageList from './KeFuMessageList.vue' + +export { KeFuConversationList, KeFuMessageList } diff --git a/src/views/mall/promotion/kefu/components/message/ImageMessageItem.vue b/src/views/mall/promotion/kefu/components/message/ImageMessageItem.vue new file mode 100644 index 00000000..6b60651b --- /dev/null +++ b/src/views/mall/promotion/kefu/components/message/ImageMessageItem.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/views/mall/promotion/kefu/components/message/OrderMessageItem.vue b/src/views/mall/promotion/kefu/components/message/OrderMessageItem.vue new file mode 100644 index 00000000..61c81e47 --- /dev/null +++ b/src/views/mall/promotion/kefu/components/message/OrderMessageItem.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/src/views/mall/promotion/kefu/components/message/ProductItem.vue b/src/views/mall/promotion/kefu/components/message/ProductItem.vue new file mode 100644 index 00000000..39233deb --- /dev/null +++ b/src/views/mall/promotion/kefu/components/message/ProductItem.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/src/views/mall/promotion/kefu/components/message/ProductMessageItem.vue b/src/views/mall/promotion/kefu/components/message/ProductMessageItem.vue new file mode 100644 index 00000000..ea51d089 --- /dev/null +++ b/src/views/mall/promotion/kefu/components/message/ProductMessageItem.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/views/mall/promotion/kefu/components/message/TextMessageItem.vue b/src/views/mall/promotion/kefu/components/message/TextMessageItem.vue new file mode 100644 index 00000000..fd4b6edb --- /dev/null +++ b/src/views/mall/promotion/kefu/components/message/TextMessageItem.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/views/mall/promotion/kefu/components/tools/EmojiSelectPopover.vue b/src/views/mall/promotion/kefu/components/tools/EmojiSelectPopover.vue new file mode 100644 index 00000000..43c096db --- /dev/null +++ b/src/views/mall/promotion/kefu/components/tools/EmojiSelectPopover.vue @@ -0,0 +1,42 @@ + + + + diff --git a/src/views/mall/promotion/kefu/components/tools/PictureSelectUpload.vue b/src/views/mall/promotion/kefu/components/tools/PictureSelectUpload.vue new file mode 100644 index 00000000..9742353a --- /dev/null +++ b/src/views/mall/promotion/kefu/components/tools/PictureSelectUpload.vue @@ -0,0 +1,93 @@ + + + + + + diff --git a/src/views/mall/promotion/kefu/components/tools/constants.ts b/src/views/mall/promotion/kefu/components/tools/constants.ts new file mode 100644 index 00000000..750e7f55 --- /dev/null +++ b/src/views/mall/promotion/kefu/components/tools/constants.ts @@ -0,0 +1,17 @@ +// 客服消息类型枚举类 +export const KeFuMessageContentTypeEnum = { + TEXT: 1, // 文本消息 + IMAGE: 2, // 图片消息 + VOICE: 3, // 语音消息 + VIDEO: 4, // 视频消息 + SYSTEM: 5, // 系统消息 + // ========== 商城特殊消息 ========== + PRODUCT: 10, // 商品消息 + ORDER: 11 // 订单消息" +} + +// Promotion 的 WebSocket 消息类型枚举类 +export const WebSocketMessageTypeConstants = { + KEFU_MESSAGE_TYPE: 'kefu_message_type', // 客服消息类型 + KEFU_MESSAGE_ADMIN_READ: 'kefu_message_read_status_change' // 客服消息管理员已读 +} diff --git a/src/views/mall/promotion/kefu/components/tools/emoji.ts b/src/views/mall/promotion/kefu/components/tools/emoji.ts new file mode 100644 index 00000000..7e3f19be --- /dev/null +++ b/src/views/mall/promotion/kefu/components/tools/emoji.ts @@ -0,0 +1,126 @@ +import { isEmpty } from '@/utils/is' + +const emojiList = [ + { name: '[笑掉牙]', file: 'xiaodiaoya.png' }, + { name: '[可爱]', file: 'keai.png' }, + { name: '[冷酷]', file: 'lengku.png' }, + { name: '[闭嘴]', file: 'bizui.png' }, + { name: '[生气]', file: 'shengqi.png' }, + { name: '[惊恐]', file: 'jingkong.png' }, + { name: '[瞌睡]', file: 'keshui.png' }, + { name: '[大笑]', file: 'daxiao.png' }, + { name: '[爱心]', file: 'aixin.png' }, + { name: '[坏笑]', file: 'huaixiao.png' }, + { name: '[飞吻]', file: 'feiwen.png' }, + { name: '[疑问]', file: 'yiwen.png' }, + { name: '[开心]', file: 'kaixin.png' }, + { name: '[发呆]', file: 'fadai.png' }, + { name: '[流泪]', file: 'liulei.png' }, + { name: '[汗颜]', file: 'hanyan.png' }, + { name: '[惊悚]', file: 'jingshu.png' }, + { name: '[困~]', file: 'kun.png' }, + { name: '[心碎]', file: 'xinsui.png' }, + { name: '[天使]', file: 'tianshi.png' }, + { name: '[晕]', file: 'yun.png' }, + { name: '[啊]', file: 'a.png' }, + { name: '[愤怒]', file: 'fennu.png' }, + { name: '[睡着]', file: 'shuizhuo.png' }, + { name: '[面无表情]', file: 'mianwubiaoqing.png' }, + { name: '[难过]', file: 'nanguo.png' }, + { name: '[犯困]', file: 'fankun.png' }, + { name: '[好吃]', file: 'haochi.png' }, + { name: '[呕吐]', file: 'outu.png' }, + { name: '[龇牙]', file: 'ziya.png' }, + { name: '[懵比]', file: 'mengbi.png' }, + { name: '[白眼]', file: 'baiyan.png' }, + { name: '[饿死]', file: 'esi.png' }, + { name: '[凶]', file: 'xiong.png' }, + { name: '[感冒]', file: 'ganmao.png' }, + { name: '[流汗]', file: 'liuhan.png' }, + { name: '[笑哭]', file: 'xiaoku.png' }, + { name: '[流口水]', file: 'liukoushui.png' }, + { name: '[尴尬]', file: 'ganga.png' }, + { name: '[惊讶]', file: 'jingya.png' }, + { name: '[大惊]', file: 'dajing.png' }, + { name: '[不好意思]', file: 'buhaoyisi.png' }, + { name: '[大闹]', file: 'danao.png' }, + { name: '[不可思议]', file: 'bukesiyi.png' }, + { name: '[爱你]', file: 'aini.png' }, + { name: '[红心]', file: 'hongxin.png' }, + { name: '[点赞]', file: 'dianzan.png' }, + { name: '[恶魔]', file: 'emo.png' } +] + +export interface Emoji { + name: string + url: string +} + +export const useEmoji = () => { + const emojiPathList = ref([]) + + /** 加载本地图片 */ + const initStaticEmoji = async () => { + const pathList = import.meta.glob( + '@/views/mall/promotion/kefu/components/asserts/*.{png,jpg,jpeg,svg}' + ) + for (const path in pathList) { + const imageModule: any = await pathList[path]() + emojiPathList.value.push(imageModule.default) + } + } + + /** 初始化 */ + onMounted(async () => { + if (isEmpty(emojiPathList.value)) { + await initStaticEmoji() + } + }) + + /** + * 将文本中的表情替换成图片 + * + * @param data 文本 + * @return 替换后的文本 + */ + const replaceEmoji = (content: string) => { + let newData = content + if (typeof newData !== 'object') { + const reg = /\[(.+?)]/g // [] 中括号 + const zhEmojiName = newData.match(reg) + if (zhEmojiName) { + zhEmojiName.forEach((item) => { + const emojiFile = getEmojiFileByName(item) + newData = newData.replace( + item, + `` + ) + }) + } + } + return newData + } + + /** + * 获得所有表情 + * + * @return 表情列表 + */ + function getEmojiList(): Emoji[] { + return emojiList.map((item) => ({ + url: getEmojiFileByName(item.name), + name: item.name + })) as Emoji[] + } + + function getEmojiFileByName(name: string) { + for (const emoji of emojiList) { + if (emoji.name === name) { + return emojiPathList.value.find((item: string) => item.indexOf(emoji.file) > -1) + } + } + return false + } + + return { replaceEmoji, getEmojiList } +} diff --git a/src/views/mall/promotion/kefu/index.vue b/src/views/mall/promotion/kefu/index.vue new file mode 100644 index 00000000..fae88d71 --- /dev/null +++ b/src/views/mall/promotion/kefu/index.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/views/mall/trade/afterSale/index.vue b/src/views/mall/trade/afterSale/index.vue index 23ff967f..52051c35 100644 --- a/src/views/mall/trade/afterSale/index.vue +++ b/src/views/mall/trade/afterSale/index.vue @@ -246,7 +246,7 @@ const openAfterSaleDetail = (id: number) => { /** 查看订单详情 */ const openOrderDetail = (id: number) => { - push({ name: 'TradeOrderDetail', params: { orderId: id } }) + push({ name: 'TradeOrderDetail', params: { id } }) } /** 商品图预览 */ diff --git a/src/views/member/user/detail/UserOrderList.vue b/src/views/member/user/detail/UserOrderList.vue index b6870bc3..bae0bf0d 100644 --- a/src/views/member/user/detail/UserOrderList.vue +++ b/src/views/member/user/detail/UserOrderList.vue @@ -267,7 +267,7 @@ const getList = async () => { /** 查看订单详情 */ const openDetail = (id: number) => { - push({ name: 'TradeOrderDetail', params: { orderId: id } }) + push({ name: 'TradeOrderDetail', params: { id } }) } /** 初始化 **/ diff --git a/src/views/pay/app/components/channel/WeixinChannelForm.vue b/src/views/pay/app/components/channel/WeixinChannelForm.vue index 34e92c69..cfd46bc0 100644 --- a/src/views/pay/app/components/channel/WeixinChannelForm.vue +++ b/src/views/pay/app/components/channel/WeixinChannelForm.vue @@ -80,7 +80,8 @@ :http-request="keyContentUpload" > - 点击上传 + + 点击上传 @@ -120,7 +121,8 @@ :http-request="privateKeyContentUpload" > - 点击上传 + + 点击上传 @@ -148,7 +150,8 @@ :http-request="privateCertContentUpload" > - 点击上传 + + 点击上传 @@ -310,7 +313,7 @@ const pemFileBeforeUpload = (file) => { /** * 读取 apiclient_key.pem 到 privateKeyContent 字段 */ -const privateKeyContentUpload = (event) => { +const privateKeyContentUpload = async (event) => { const readFile = new FileReader() readFile.onload = (e: any) => { formData.value.config.privateKeyContent = e.target.result @@ -321,7 +324,7 @@ const privateKeyContentUpload = (event) => { /** * 读取 apiclient_cert.pem 到 privateCertContent 字段 */ -const privateCertContentUpload = (event) => { +const privateCertContentUpload = async (event) => { const readFile = new FileReader() readFile.onload = (e: any) => { formData.value.config.privateCertContent = e.target.result @@ -332,7 +335,7 @@ const privateCertContentUpload = (event) => { /** * 读取 apiclient_cert.p12 到 keyContent 字段 */ -const keyContentUpload = (event) => { +const keyContentUpload = async (event) => { const readFile = new FileReader() readFile.onload = (e: any) => { formData.value.config.keyContent = e.target.result.split(',')[1] diff --git a/src/views/pay/app/index.vue b/src/views/pay/app/index.vue index 2f4a9c1e..6b60d9b1 100644 --- a/src/views/pay/app/index.vue +++ b/src/views/pay/app/index.vue @@ -45,10 +45,17 @@ /> - 搜索 - 重置 + + + 搜索 + + + + 重置 + - 新增 + + 新增 @@ -70,12 +77,17 @@ - + - - - - - - - - - - - -