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

feat:添加env环境变量配置

parent 86790086
......@@ -9,6 +9,9 @@ const instance = axios.create({
}
})
// 防止重复跳转的标志
let isNavigatingToLogin = false
// 请求拦截器 - 添加认证信息
instance.interceptors.request.use(
(config) => {
......@@ -44,11 +47,24 @@ instance.interceptors.response.use(
// 处理401未授权错误
if (status === 401) {
// 检查是否已经在跳转过程中,防止无限循环
if (isNavigatingToLogin) {
return Promise.reject(new Error('正在跳转到登录页面'))
}
// 设置跳转标志
isNavigatingToLogin = true
// 清除本地存储的用户信息
localStorage.removeItem('wechat_user')
// 使用全局路由管理器跳转
navigateToLogin()
// 3秒后重置跳转标志,防止长时间锁定
setTimeout(() => {
isNavigatingToLogin = false
}, 3000)
return Promise.reject(new Error('未授权访问,请重新登录'))
}
......
......@@ -14,22 +14,34 @@
import AiChat from './components/AiChat.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
const userInfo = localStorage.getItem('wechat_user')
const {extMap = {}} = JSON.parse(userInfo || '{}')
const userToken = extMap.sessionId;
// 添加APP_CODE配置
const appCode = 'ped.qywx';
// 使用环境变量代替硬编码
const appCode = import.meta.env.VITE_APP_CODE || 'ped.qywx';
const chatParams = {
appId: '83b2664019a945d0a438abe6339758d8',
stage: 'wechat-demo',
};
// const dialogSessionId = '20251028143404893-00045166';
const dialogSessionId = '';
const dialogSessionId = '20251028143404893-00045166';
// const dialogSessionId = '';
const detailData = ref({
title: '国械小智',
});
......
......@@ -87,38 +87,58 @@
import { EventSourcePolyfill } from 'event-source-polyfill';
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
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 { SendOutlined, UserOutlined } from '@ant-design/icons-vue';
import defaultAvatar from '@/assets/logo.png';
import ChartComponent from './ChartComponent.vue'; // 导入独立的图表组件
import VoiceRecognition from './VoiceRecognition.vue'; // 导入语音识别组件
// 组件属性
const props = withDefaults(
defineProps<{
params?: ChatParams;
dialogSessionId?: string;
detailData?: {
title?: string;
};
apiBaseUrl?: string;
token?: string;
appCode?: string;
logoUrl?: string;
onMessageSend?: (message: string) => Promise<any>;
onGetChatRecord?: (dialogSessionId: string) => Promise<any>;
customClass?: string;
}>(),
{
params: () => ({
// 定义组件属性
const props = defineProps({
// 对话会话ID
dialogSessionId: {
type: String,
default: ''
},
// API基础URL
apiBaseUrl: {
type: String,
default: import.meta.env.VITE_API_BASE_PATH || '/pedapi'
},
// 应用代码
appCode: {
type: String,
default: import.meta.env.VITE_APP_CODE || 'ped.qywx'
},
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',
stage: 'wechat-demo',
}),
apiBaseUrl: '/pedapi',
logoUrl: '',
},
);
})
}
});
// 内容模板生成器 - 简化版本,表格功能已抽离
const contentTemplates = {
......@@ -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 {
message: any;
status: number | string;
......@@ -340,7 +353,6 @@ const sendAudioMessage = async (audioUrl: string) => {
await props.onMessageSend(audioUrl);
} else {
// 默认的API调用逻辑 - 使用与sendMessage相同的逻辑,只是参数不同
console.log('默认音频API调用逻辑');
const response = await post(`${props.apiBaseUrl}/aiService/ask/app/${props.params?.appId}`, {
questionLocalAudioFilePath: audioUrl,
...props.params,
......@@ -349,7 +361,6 @@ const sendAudioMessage = async (audioUrl: string) => {
Token: props.token || '',
'x-session-id': props.token || '',
'x-app-code': props.appCode || '',
'Content-Type': 'multipart/form-data',
}
});
......@@ -370,76 +381,6 @@ const startConversation = () => {
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 () => {
loading.value = true;
......@@ -480,13 +421,6 @@ const sendMessage = async () => {
try {
messageText.value = '';
// 可选:在发送消息后模拟选项数据返回
// 取消注释以下代码来启用模拟
setTimeout(() => {
simulateOptionData();
loading.value = false;
}, 1000);
// 调用外部传入的消息发送函数
if (props.onMessageSend) {
console.log('调用外部消息发送函数', message);
......@@ -555,7 +489,7 @@ const processSSEMessage = (
case 3: // 图表数据
if (updatedResponse) {
switch (contentType) {
case 'table': // 表格数据
case 2: // 表格数据
const { rows } = messageContent;
// 表格数据处理
updatedResponse.contentBlocks.push({
......@@ -574,7 +508,7 @@ const processSSEMessage = (
chartType: 3,
});
break;
case 'option': // 选项数据
case 3: // 选项数据
const { tips, options } = messageContent;
if (options?.length) {
if (options?.length === 1) {
......@@ -609,7 +543,7 @@ const processSSEMessage = (
break;
}
}
break;
break;
case 10: // 思考开始
updatedIsThinking = true;
if (updatedBlockIndex === -1 && updatedResponse) {
......@@ -883,30 +817,25 @@ onBeforeUnmount(() => {
isInThinkingMode.value = false;
});
// 处理历史记录数据
const processHistoryData = (data: any): Message[] => {
const result: Message[] = [];
const date = dayjs(data.createTime).format('HH:mm');
// 处理问题消息
if (data.question) {
// 处理历史记录数据
const processHistoryData = (dataArray: any[]) => {
const result: Message[] = [];
dataArray.forEach((data) => {
let date = dayjs(data.startTime).format('YYYY-MM-DD HH:mm:ss');
// 处理问题消息
if (data.question || data.audioPath) {
let questionContent = '';
// 检查是否为音频消息
if (isAudioMessage(data)) {
if (data.audioPath) {
// 处理音频消息
const audioData = data.question;
questionContent = contentTemplates.audio({
audioUrl: audioData.audioUrl,
audioBlob: audioData.audioBlob
audioUrl: data.audioPath
});
} else {
// 处理文本消息
questionContent = contentTemplates.text(
typeof data.question === 'string' ? data.question : data.question.content || ''
);
questionContent = contentTemplates.text(data.question);
}
result.push({
messageType: 'sent',
avatar: '',
......@@ -925,8 +854,7 @@ const processHistoryData = (data: any): Message[] => {
],
});
}
// 处理AI回答消息
// 处理AI回答消息
if (data.answerInfoList && Array.isArray(data.answerInfoList)) {
const aiMessage: Message = {
messageType: 'received',
......@@ -970,9 +898,10 @@ const processHistoryData = (data: any): Message[] => {
result.push(aiMessage);
}
}
});
return result;
};
return result;
};
// 获取历史会话消息
const getChatRecord = async (dialogSessionId: string) => {
......@@ -983,7 +912,7 @@ const getChatRecord = async (dialogSessionId: string) => {
messages.value = [...recordList];
}
} else {
const response = await post(`${props.apiBaseUrl}/aiService/ask/list/chat/${dialogSessionId}`, {}, {
const response = await get(`${props.apiBaseUrl}/aiService/ask/list/chat/${dialogSessionId}`, {
headers: {
Token: props.token || '',
'x-session-id': props.token || '',
......@@ -1082,26 +1011,28 @@ const setupAudioPlayers = () => {
const audioPlayers = document.querySelectorAll('.audio-player');
audioPlayers.forEach((player) => {
// 移除之前的事件监听器,避免重复绑定
const newPlayer = player.cloneNode(true);
player.parentNode.replaceChild(newPlayer, player);
// 检查是否已经绑定过事件监听器
if (player.hasAttribute('data-audio-bound')) {
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');
// 修复:在DOM替换后重新获取音频元素
const audioElement = audioId ? document.getElementById(audioId) : null;
if (!audioElement) {
console.warn('未找到音频元素,audioId:', audioId);
return;
}
console.log('音频元素:', audioElement);
// 音频播放结束,重置为总时长 - 已移除
audioElement.addEventListener('ended', () => {
newPlayer.classList.remove('playing');
const playIcon = newPlayer.querySelector('.play-icon');
const pauseIcon = newPlayer.querySelector('.pause-icon');
player.classList.remove('playing');
const playIcon = player.querySelector('.play-icon');
const pauseIcon = player.querySelector('.pause-icon');
if (playIcon && pauseIcon) {
playIcon.style.display = 'inline';
pauseIcon.style.display = 'none';
......@@ -1109,7 +1040,7 @@ const setupAudioPlayers = () => {
});
// 设置播放/暂停事件
newPlayer.addEventListener('click', (e) => {
player.addEventListener('click', (e) => {
e.stopPropagation();
if (audioElement.paused) {
......@@ -1118,18 +1049,18 @@ const setupAudioPlayers = () => {
audioElement.play().catch(error => {
console.error('播放音频失败:', error);
});
newPlayer.classList.add('playing');
player.classList.add('playing');
} else {
audioElement.pause();
newPlayer.classList.remove('playing');
player.classList.remove('playing');
}
});
// 音频播放事件
audioElement.addEventListener('play', () => {
newPlayer.classList.add('playing');
const playIcon = newPlayer.querySelector('.play-icon');
const pauseIcon = newPlayer.querySelector('.pause-icon');
player.classList.add('playing');
const playIcon = player.querySelector('.play-icon');
const pauseIcon = player.querySelector('.pause-icon');
if (playIcon && pauseIcon) {
playIcon.style.display = 'none';
pauseIcon.style.display = 'inline';
......@@ -1138,9 +1069,9 @@ const setupAudioPlayers = () => {
// 音频暂停事件
audioElement.addEventListener('pause', () => {
newPlayer.classList.remove('playing');
const playIcon = newPlayer.querySelector('.play-icon');
const pauseIcon = newPlayer.querySelector('.pause-icon');
player.classList.remove('playing');
const playIcon = player.querySelector('.play-icon');
const pauseIcon = player.querySelector('.pause-icon');
if (playIcon && pauseIcon) {
playIcon.style.display = 'inline';
pauseIcon.style.display = 'none';
......
......@@ -241,14 +241,13 @@ const uploadAudioFile = async (audioBlob: Blob): Promise<string> => {
formData.append('fileFolder', 'AI_TEMP');
// 使用项目中的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: {
'Content-Type': 'multipart/form-data',
'x-app-code': 'ped.qywx'
}
});
console.log('上传接口返回数据:', result);
if (result.data.code === 0) {
const filePath = result.data.data.filePath;
return filePath;
......@@ -275,7 +274,6 @@ const sendRecordedAudio = async () => {
// 先调用上传接口获取URL
const audioUrl = await uploadAudioFile(audioBlob);
console.log('上传接口返回的filePath:', audioUrl);
// 上传成功后触发audio事件,传递URL和Blob
emit('audio', audioUrl, audioBlob);
......
import { defineConfig } from 'vite'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path';
import fs from 'fs';
export default defineConfig({
base: '/ai/', // 添加基础路径前缀
plugins: [vue()],
server: {
host: '0.0.0.0',
port: 3000,
https: {
key: fs.readFileSync('./localhost-key.pem'),
cert: fs.readFileSync('./localhost.pem')
},
// 添加history fallback配置
historyApiFallback: {
rewrites: [
{ from: /^\/ai\/.*$/, to: '/ai/index.html' }
]
},
proxy: {
'/pedapi': {
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);
});
}
export default defineConfig(({ mode }) => {
// 加载环境变量
const env = loadEnv(mode, process.cwd(), '')
// 清理API基础URL,移除末尾的斜杠
const apiBaseUrl = (env.VITE_API_BASE_URL).replace(/\/+$/, '');
const basePath = env.VITE_BASE_PATH;
return {
base: basePath,
plugins: [vue()],
server: {
host: '0.0.0.0',
port: 3000,
https: {
key: fs.readFileSync('./localhost-key.pem'),
cert: fs.readFileSync('./localhost.pem')
},
'/ifile': {
target: 'http://peddev.cmic.com.cn',
changeOrigin: true, // 解决跨域问题
secure: false, // 允许不安全的SSL连接
// 添加history fallback配置
historyApiFallback: {
rewrites: [
{ from: /^\/ai\/.*$/, to: '/ai/index.html' }
]
},
'/WeChatOauth2': {
target: 'http://peddev.cmic.com.cn',
changeOrigin: true, // 解决跨域问题
secure: false, // 允许不安全的SSL连接
proxy: {
// 同时配置两种路径的代理
'/pedapi': {
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