Commit e32996f4 authored by 水玉婷's avatar 水玉婷
Browse files

feat:添加env环境变量配置

parent 86790086
...@@ -9,6 +9,9 @@ const instance = axios.create({ ...@@ -9,6 +9,9 @@ const instance = axios.create({
} }
}) })
// 防止重复跳转的标志
let isNavigatingToLogin = false
// 请求拦截器 - 添加认证信息 // 请求拦截器 - 添加认证信息
instance.interceptors.request.use( instance.interceptors.request.use(
(config) => { (config) => {
...@@ -44,11 +47,24 @@ instance.interceptors.response.use( ...@@ -44,11 +47,24 @@ instance.interceptors.response.use(
// 处理401未授权错误 // 处理401未授权错误
if (status === 401) { if (status === 401) {
// 检查是否已经在跳转过程中,防止无限循环
if (isNavigatingToLogin) {
return Promise.reject(new Error('正在跳转到登录页面'))
}
// 设置跳转标志
isNavigatingToLogin = true
// 清除本地存储的用户信息 // 清除本地存储的用户信息
localStorage.removeItem('wechat_user') localStorage.removeItem('wechat_user')
// 使用全局路由管理器跳转 // 使用全局路由管理器跳转
navigateToLogin() navigateToLogin()
// 3秒后重置跳转标志,防止长时间锁定
setTimeout(() => {
isNavigatingToLogin = false
}, 3000)
return Promise.reject(new Error('未授权访问,请重新登录')) return Promise.reject(new Error('未授权访问,请重新登录'))
} }
......
...@@ -14,22 +14,34 @@ ...@@ -14,22 +14,34 @@
import AiChat from './components/AiChat.vue'; import AiChat from './components/AiChat.vue';
import { ref } from 'vue'; import { ref } from 'vue';
const apiBaseUrl = '/pedapi'; // 动态处理API基础路径
const getApiBaseUrl = () => {
const basePath = import.meta.env.VITE_API_BASE_PATH;
// 如果设置为'/',则返回空字符串,避免双斜杠
if (basePath === '/') {
return '';
}
return basePath;
};
const apiBaseUrl = getApiBaseUrl();
// 获取token // 获取token
const userInfo = localStorage.getItem('wechat_user') const userInfo = localStorage.getItem('wechat_user')
const {extMap = {}} = JSON.parse(userInfo || '{}') const {extMap = {}} = JSON.parse(userInfo || '{}')
const userToken = extMap.sessionId; const userToken = extMap.sessionId;
// 添加APP_CODE配置 // 使用环境变量代替硬编码
const appCode = 'ped.qywx'; const appCode = import.meta.env.VITE_APP_CODE || 'ped.qywx';
const chatParams = { const chatParams = {
appId: '83b2664019a945d0a438abe6339758d8', appId: '83b2664019a945d0a438abe6339758d8',
stage: 'wechat-demo', stage: 'wechat-demo',
}; };
// const dialogSessionId = '20251028143404893-00045166'; const dialogSessionId = '20251028143404893-00045166';
const dialogSessionId = ''; // const dialogSessionId = '';
const detailData = ref({ const detailData = ref({
title: '国械小智', title: '国械小智',
}); });
......
...@@ -87,38 +87,58 @@ ...@@ -87,38 +87,58 @@
import { EventSourcePolyfill } from 'event-source-polyfill'; import { EventSourcePolyfill } from 'event-source-polyfill';
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { post } from '@/utils/axios.js'; // 导入axios的post方法 import { post,get} from '@/utils/axios.js'; // 导入axios的post方法
import tableTemplate from './tableTemplate'; import tableTemplate from './tableTemplate';
import { SendOutlined, UserOutlined } from '@ant-design/icons-vue'; import { SendOutlined, UserOutlined } from '@ant-design/icons-vue';
import defaultAvatar from '@/assets/logo.png'; import defaultAvatar from '@/assets/logo.png';
import ChartComponent from './ChartComponent.vue'; // 导入独立的图表组件 import ChartComponent from './ChartComponent.vue'; // 导入独立的图表组件
import VoiceRecognition from './VoiceRecognition.vue'; // 导入语音识别组件 import VoiceRecognition from './VoiceRecognition.vue'; // 导入语音识别组件
// 组件属性 // 定义组件属性
const props = withDefaults( const props = defineProps({
defineProps<{ // 对话会话ID
params?: ChatParams; dialogSessionId: {
dialogSessionId?: string; type: String,
detailData?: { default: ''
title?: string; },
}; // API基础URL
apiBaseUrl?: string; apiBaseUrl: {
token?: string; type: String,
appCode?: string; default: import.meta.env.VITE_API_BASE_PATH || '/pedapi'
logoUrl?: string; },
onMessageSend?: (message: string) => Promise<any>; // 应用代码
onGetChatRecord?: (dialogSessionId: string) => Promise<any>; appCode: {
customClass?: string; type: String,
}>(), default: import.meta.env.VITE_APP_CODE || 'ped.qywx'
{ },
params: () => ({ token: {
type: String,
default: ''
},
logoUrl: {
type: String,
default: ''
},
onMessageSend: {
type: Function,
default: undefined
},
onGetChatRecord: {
type: Function,
default: undefined
},
customClass: {
type: String,
default: ''
},
params: {
type: Object,
default: () => ({
appId: 'app-test', appId: 'app-test',
stage: 'wechat-demo', stage: 'wechat-demo',
}), })
apiBaseUrl: '/pedapi', }
logoUrl: '', });
},
);
// 内容模板生成器 - 简化版本,表格功能已抽离 // 内容模板生成器 - 简化版本,表格功能已抽离
const contentTemplates = { const contentTemplates = {
...@@ -251,13 +271,6 @@ interface Message { ...@@ -251,13 +271,6 @@ interface Message {
}[]; }[];
} }
// 检查是否为音频消息的辅助函数
const isAudioMessage = (messageData: any): boolean => {
return messageData.questionType === 'audio' ||
(messageData.question && typeof messageData.question === 'object' &&
(messageData.question.audioUrl || messageData.question.audioData));
};
interface SSEData { interface SSEData {
message: any; message: any;
status: number | string; status: number | string;
...@@ -340,7 +353,6 @@ const sendAudioMessage = async (audioUrl: string) => { ...@@ -340,7 +353,6 @@ const sendAudioMessage = async (audioUrl: string) => {
await props.onMessageSend(audioUrl); await props.onMessageSend(audioUrl);
} else { } else {
// 默认的API调用逻辑 - 使用与sendMessage相同的逻辑,只是参数不同 // 默认的API调用逻辑 - 使用与sendMessage相同的逻辑,只是参数不同
console.log('默认音频API调用逻辑');
const response = await post(`${props.apiBaseUrl}/aiService/ask/app/${props.params?.appId}`, { const response = await post(`${props.apiBaseUrl}/aiService/ask/app/${props.params?.appId}`, {
questionLocalAudioFilePath: audioUrl, questionLocalAudioFilePath: audioUrl,
...props.params, ...props.params,
...@@ -349,7 +361,6 @@ const sendAudioMessage = async (audioUrl: string) => { ...@@ -349,7 +361,6 @@ const sendAudioMessage = async (audioUrl: string) => {
Token: props.token || '', Token: props.token || '',
'x-session-id': props.token || '', 'x-session-id': props.token || '',
'x-app-code': props.appCode || '', 'x-app-code': props.appCode || '',
'Content-Type': 'multipart/form-data',
} }
}); });
...@@ -370,76 +381,6 @@ const startConversation = () => { ...@@ -370,76 +381,6 @@ const startConversation = () => {
hasStartedConversation.value = true; hasStartedConversation.value = true;
}; };
// 模拟status为3、type为options的数据返回
const simulateOptionData = () => {
// 第一个消息:展示三个options
const firstOptionData = {
status: 3,
type: 'option',
message: {
tips: "以下是三个可选的报表链接:",
options: [
{ title: "月度销售报表", url: "https://example.com/report/monthly" },
{ title: "季度业绩分析", url: "https://example.com/report/quarterly" },
{ title: "年度总结报告", url: "https://example.com/report/annual" }
]
}
};
// 第二个消息:展示一个options(会走iframe逻辑)
const secondOptionData = {
status: 3,
type: 'option',
message: {
tips: "这是单个报表的预览:",
options: [
{
title: "销售指标看板",
url: "/WeChatOauth2/MobileReport_Monthly/MonthlyReport_index.aspx?postage=384b67414b334f2f693177644246313756704a724d513d3d&company=器械整体&typename=整体指标"
}
]
}
};
// 创建AI响应消息
const aiMessage = {
messageType: 'received',
avatar: 'AI',
recordId: 'simulate-001',
promptTokens: 100,
completionTokens: 50,
totalTokens: 150,
date: dayjs().format('HH:mm'),
contentBlocks: []
};
// 处理第一个选项数据(三个options)
const firstResult = processSSEMessage(
firstOptionData,
aiMessage,
false,
-1,
false
);
// 处理第二个选项数据(一个options)
const secondResult = processSSEMessage(
secondOptionData,
firstResult.updatedResponse,
false,
-1,
false
);
// 将处理后的消息添加到消息列表
if (secondResult.updatedResponse) {
messages.value.push(secondResult.updatedResponse);
nextTick(() => {
scrollToBottom();
});
}
};
// 发送消息 // 发送消息
const sendMessage = async () => { const sendMessage = async () => {
loading.value = true; loading.value = true;
...@@ -480,13 +421,6 @@ const sendMessage = async () => { ...@@ -480,13 +421,6 @@ const sendMessage = async () => {
try { try {
messageText.value = ''; messageText.value = '';
// 可选:在发送消息后模拟选项数据返回
// 取消注释以下代码来启用模拟
setTimeout(() => {
simulateOptionData();
loading.value = false;
}, 1000);
// 调用外部传入的消息发送函数 // 调用外部传入的消息发送函数
if (props.onMessageSend) { if (props.onMessageSend) {
console.log('调用外部消息发送函数', message); console.log('调用外部消息发送函数', message);
...@@ -555,7 +489,7 @@ const processSSEMessage = ( ...@@ -555,7 +489,7 @@ const processSSEMessage = (
case 3: // 图表数据 case 3: // 图表数据
if (updatedResponse) { if (updatedResponse) {
switch (contentType) { switch (contentType) {
case 'table': // 表格数据 case 2: // 表格数据
const { rows } = messageContent; const { rows } = messageContent;
// 表格数据处理 // 表格数据处理
updatedResponse.contentBlocks.push({ updatedResponse.contentBlocks.push({
...@@ -574,7 +508,7 @@ const processSSEMessage = ( ...@@ -574,7 +508,7 @@ const processSSEMessage = (
chartType: 3, chartType: 3,
}); });
break; break;
case 'option': // 选项数据 case 3: // 选项数据
const { tips, options } = messageContent; const { tips, options } = messageContent;
if (options?.length) { if (options?.length) {
if (options?.length === 1) { if (options?.length === 1) {
...@@ -609,7 +543,7 @@ const processSSEMessage = ( ...@@ -609,7 +543,7 @@ const processSSEMessage = (
break; break;
} }
} }
break; break;
case 10: // 思考开始 case 10: // 思考开始
updatedIsThinking = true; updatedIsThinking = true;
if (updatedBlockIndex === -1 && updatedResponse) { if (updatedBlockIndex === -1 && updatedResponse) {
...@@ -883,30 +817,25 @@ onBeforeUnmount(() => { ...@@ -883,30 +817,25 @@ onBeforeUnmount(() => {
isInThinkingMode.value = false; isInThinkingMode.value = false;
}); });
// 处理历史记录数据 // 处理历史记录数据
const processHistoryData = (data: any): Message[] => { const processHistoryData = (dataArray: any[]) => {
const result: Message[] = []; const result: Message[] = [];
const date = dayjs(data.createTime).format('HH:mm'); dataArray.forEach((data) => {
let date = dayjs(data.startTime).format('YYYY-MM-DD HH:mm:ss');
// 处理问题消息 // 处理问题消息
if (data.question) { if (data.question || data.audioPath) {
let questionContent = ''; let questionContent = '';
// 检查是否为音频消息 // 检查是否为音频消息
if (isAudioMessage(data)) { if (data.audioPath) {
// 处理音频消息 // 处理音频消息
const audioData = data.question;
questionContent = contentTemplates.audio({ questionContent = contentTemplates.audio({
audioUrl: audioData.audioUrl, audioUrl: data.audioPath
audioBlob: audioData.audioBlob
}); });
} else { } else {
// 处理文本消息 // 处理文本消息
questionContent = contentTemplates.text( questionContent = contentTemplates.text(data.question);
typeof data.question === 'string' ? data.question : data.question.content || ''
);
} }
result.push({ result.push({
messageType: 'sent', messageType: 'sent',
avatar: '', avatar: '',
...@@ -925,8 +854,7 @@ const processHistoryData = (data: any): Message[] => { ...@@ -925,8 +854,7 @@ const processHistoryData = (data: any): Message[] => {
], ],
}); });
} }
// 处理AI回答消息
// 处理AI回答消息
if (data.answerInfoList && Array.isArray(data.answerInfoList)) { if (data.answerInfoList && Array.isArray(data.answerInfoList)) {
const aiMessage: Message = { const aiMessage: Message = {
messageType: 'received', messageType: 'received',
...@@ -970,9 +898,10 @@ const processHistoryData = (data: any): Message[] => { ...@@ -970,9 +898,10 @@ const processHistoryData = (data: any): Message[] => {
result.push(aiMessage); result.push(aiMessage);
} }
} }
});
return result; return result;
}; };
// 获取历史会话消息 // 获取历史会话消息
const getChatRecord = async (dialogSessionId: string) => { const getChatRecord = async (dialogSessionId: string) => {
...@@ -983,7 +912,7 @@ const getChatRecord = async (dialogSessionId: string) => { ...@@ -983,7 +912,7 @@ const getChatRecord = async (dialogSessionId: string) => {
messages.value = [...recordList]; messages.value = [...recordList];
} }
} else { } else {
const response = await post(`${props.apiBaseUrl}/aiService/ask/list/chat/${dialogSessionId}`, {}, { const response = await get(`${props.apiBaseUrl}/aiService/ask/list/chat/${dialogSessionId}`, {
headers: { headers: {
Token: props.token || '', Token: props.token || '',
'x-session-id': props.token || '', 'x-session-id': props.token || '',
...@@ -1082,26 +1011,28 @@ const setupAudioPlayers = () => { ...@@ -1082,26 +1011,28 @@ const setupAudioPlayers = () => {
const audioPlayers = document.querySelectorAll('.audio-player'); const audioPlayers = document.querySelectorAll('.audio-player');
audioPlayers.forEach((player) => { audioPlayers.forEach((player) => {
// 移除之前的事件监听器,避免重复绑定 // 检查是否已经绑定过事件监听器
const newPlayer = player.cloneNode(true); if (player.hasAttribute('data-audio-bound')) {
player.parentNode.replaceChild(newPlayer, player); return; // 如果已经绑定过,跳过
}
const audioMessage = newPlayer.closest('.audio-message'); // 标记为已绑定
player.setAttribute('data-audio-bound', 'true');
const audioMessage = player.closest('.audio-message');
const audioId = audioMessage?.getAttribute('data-audio-id'); const audioId = audioMessage?.getAttribute('data-audio-id');
// 修复:在DOM替换后重新获取音频元素
const audioElement = audioId ? document.getElementById(audioId) : null; const audioElement = audioId ? document.getElementById(audioId) : null;
if (!audioElement) { if (!audioElement) {
console.warn('未找到音频元素,audioId:', audioId); console.warn('未找到音频元素,audioId:', audioId);
return; return;
} }
console.log('音频元素:', audioElement);
// 音频播放结束,重置为总时长 - 已移除 // 音频播放结束,重置为总时长 - 已移除
audioElement.addEventListener('ended', () => { audioElement.addEventListener('ended', () => {
newPlayer.classList.remove('playing'); player.classList.remove('playing');
const playIcon = newPlayer.querySelector('.play-icon'); const playIcon = player.querySelector('.play-icon');
const pauseIcon = newPlayer.querySelector('.pause-icon'); const pauseIcon = player.querySelector('.pause-icon');
if (playIcon && pauseIcon) { if (playIcon && pauseIcon) {
playIcon.style.display = 'inline'; playIcon.style.display = 'inline';
pauseIcon.style.display = 'none'; pauseIcon.style.display = 'none';
...@@ -1109,7 +1040,7 @@ const setupAudioPlayers = () => { ...@@ -1109,7 +1040,7 @@ const setupAudioPlayers = () => {
}); });
// 设置播放/暂停事件 // 设置播放/暂停事件
newPlayer.addEventListener('click', (e) => { player.addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
if (audioElement.paused) { if (audioElement.paused) {
...@@ -1118,18 +1049,18 @@ const setupAudioPlayers = () => { ...@@ -1118,18 +1049,18 @@ const setupAudioPlayers = () => {
audioElement.play().catch(error => { audioElement.play().catch(error => {
console.error('播放音频失败:', error); console.error('播放音频失败:', error);
}); });
newPlayer.classList.add('playing'); player.classList.add('playing');
} else { } else {
audioElement.pause(); audioElement.pause();
newPlayer.classList.remove('playing'); player.classList.remove('playing');
} }
}); });
// 音频播放事件 // 音频播放事件
audioElement.addEventListener('play', () => { audioElement.addEventListener('play', () => {
newPlayer.classList.add('playing'); player.classList.add('playing');
const playIcon = newPlayer.querySelector('.play-icon'); const playIcon = player.querySelector('.play-icon');
const pauseIcon = newPlayer.querySelector('.pause-icon'); const pauseIcon = player.querySelector('.pause-icon');
if (playIcon && pauseIcon) { if (playIcon && pauseIcon) {
playIcon.style.display = 'none'; playIcon.style.display = 'none';
pauseIcon.style.display = 'inline'; pauseIcon.style.display = 'inline';
...@@ -1138,9 +1069,9 @@ const setupAudioPlayers = () => { ...@@ -1138,9 +1069,9 @@ const setupAudioPlayers = () => {
// 音频暂停事件 // 音频暂停事件
audioElement.addEventListener('pause', () => { audioElement.addEventListener('pause', () => {
newPlayer.classList.remove('playing'); player.classList.remove('playing');
const playIcon = newPlayer.querySelector('.play-icon'); const playIcon = player.querySelector('.play-icon');
const pauseIcon = newPlayer.querySelector('.pause-icon'); const pauseIcon = player.querySelector('.pause-icon');
if (playIcon && pauseIcon) { if (playIcon && pauseIcon) {
playIcon.style.display = 'inline'; playIcon.style.display = 'inline';
pauseIcon.style.display = 'none'; pauseIcon.style.display = 'none';
......
...@@ -241,14 +241,13 @@ const uploadAudioFile = async (audioBlob: Blob): Promise<string> => { ...@@ -241,14 +241,13 @@ const uploadAudioFile = async (audioBlob: Blob): Promise<string> => {
formData.append('fileFolder', 'AI_TEMP'); formData.append('fileFolder', 'AI_TEMP');
// 使用项目中的axios post方法调用上传接口 // 使用项目中的axios post方法调用上传接口
const result = await post('/pedapi/platformService/upload/v2', formData, { const result = await post(`${import.meta.env.VITE_API_BASE_PATH}/platformService/upload/v2`, formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
'x-app-code': 'ped.qywx' 'x-app-code': 'ped.qywx'
} }
}); });
console.log('上传接口返回数据:', result);
if (result.data.code === 0) { if (result.data.code === 0) {
const filePath = result.data.data.filePath; const filePath = result.data.data.filePath;
return filePath; return filePath;
...@@ -275,7 +274,6 @@ const sendRecordedAudio = async () => { ...@@ -275,7 +274,6 @@ const sendRecordedAudio = async () => {
// 先调用上传接口获取URL // 先调用上传接口获取URL
const audioUrl = await uploadAudioFile(audioBlob); const audioUrl = await uploadAudioFile(audioBlob);
console.log('上传接口返回的filePath:', audioUrl);
// 上传成功后触发audio事件,传递URL和Blob // 上传成功后触发audio事件,传递URL和Blob
emit('audio', audioUrl, audioBlob); emit('audio', audioUrl, audioBlob);
......
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'; import { resolve } from 'path';
import fs from 'fs'; import fs from 'fs';
export default defineConfig({
base: '/ai/', // 添加基础路径前缀 export default defineConfig(({ mode }) => {
plugins: [vue()], // 加载环境变量
server: { const env = loadEnv(mode, process.cwd(), '')
host: '0.0.0.0',
port: 3000, // 清理API基础URL,移除末尾的斜杠
https: { const apiBaseUrl = (env.VITE_API_BASE_URL).replace(/\/+$/, '');
key: fs.readFileSync('./localhost-key.pem'), const basePath = env.VITE_BASE_PATH;
cert: fs.readFileSync('./localhost.pem')
}, return {
// 添加history fallback配置 base: basePath,
historyApiFallback: { plugins: [vue()],
rewrites: [ server: {
{ from: /^\/ai\/.*$/, to: '/ai/index.html' } host: '0.0.0.0',
] port: 3000,
}, https: {
proxy: { key: fs.readFileSync('./localhost-key.pem'),
'/pedapi': { cert: fs.readFileSync('./localhost.pem')
target: 'http://peddev.cmic.com.cn',
changeOrigin: true, // 解决跨域问题
secure: false, // 允许不安全的SSL连接
configure: (proxy, options) => {
proxy.on('error', (err, req, res) => {
console.log('代理错误:', err);
});
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('发送请求到:', options.target);
});
}
}, },
'/ifile': { // 添加history fallback配置
target: 'http://peddev.cmic.com.cn', historyApiFallback: {
changeOrigin: true, // 解决跨域问题 rewrites: [
secure: false, // 允许不安全的SSL连接 { from: /^\/ai\/.*$/, to: '/ai/index.html' }
]
}, },
'/WeChatOauth2': { proxy: {
target: 'http://peddev.cmic.com.cn', // 同时配置两种路径的代理
changeOrigin: true, // 解决跨域问题 '/pedapi': {
secure: false, // 允许不安全的SSL连接 target: apiBaseUrl,
changeOrigin: true,
secure: false,
configure: (proxy, options) => {
proxy.on('error', (err, req, res) => {
console.log('代理错误:', err);
});
proxy.on('proxyReq', (proxyReq, req, res) => {
console.log('发送请求到:', options.target);
});
}
},
'/aiService': {
target: apiBaseUrl,
changeOrigin: true,
secure: false,
},
'/ifile': {
target: apiBaseUrl,
changeOrigin: true,
secure: false,
},
'/WeChatOauth2': {
target: apiBaseUrl,
changeOrigin: true,
secure: false,
}
},
},
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'echarts': 'echarts/dist/echarts.esm.js'
} }
}, },
}, }
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'echarts': 'echarts/dist/echarts.esm.js'
}
},
}) })
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment