Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
393 lines
10 KiB
TypeScript
393 lines
10 KiB
TypeScript
import { message } from 'ant-design-vue';
|
||
import axios from 'axios';
|
||
|
||
import { useAccessStore } from '@vben/stores';
|
||
|
||
// 创建 Axios 实例
|
||
const service = axios.create({
|
||
baseURL: '/im-api/',
|
||
timeout: 15_000,
|
||
});
|
||
|
||
// 请求拦截器
|
||
service.interceptors.request.use(
|
||
(config) => {
|
||
const token = localStorage.getItem('token');
|
||
if (token) {
|
||
config.headers.Authorization = `Bearer ${token}`;
|
||
}
|
||
return config;
|
||
},
|
||
(error) => {
|
||
console.error('Request error:', error);
|
||
return Promise.reject(error);
|
||
},
|
||
);
|
||
|
||
// 响应拦截器
|
||
service.interceptors.response.use(
|
||
(response) => {
|
||
const { code, result, message: msg } = response.data;
|
||
if (code === 0) {
|
||
return result;
|
||
} else {
|
||
if (code === 401) {
|
||
message.error(msg);
|
||
localStorage.removeItem('token');
|
||
localStorage.removeItem('chatUser');
|
||
window.location.href = '/login';
|
||
return Promise.reject(new Error(msg));
|
||
}
|
||
message.error(msg);
|
||
return Promise.reject(new Error(msg));
|
||
}
|
||
},
|
||
(error) => {
|
||
console.error('Response error:', error);
|
||
|
||
// 详细的错误处理
|
||
if (error.code === 'ECONNABORTED') {
|
||
console.log('请求超时,请检查网络连接');
|
||
} else if (error.response?.status === 0 || error.message.includes('CORS')) {
|
||
console.log('CORS错误检测到,尝试备用方案...');
|
||
handleCORSError(error.config);
|
||
} else if (error.response?.status >= 400 && error.response?.status < 500) {
|
||
console.log('客户端错误:', error.response.status, error.response.data);
|
||
} else if (error.response?.status >= 500) {
|
||
console.log('服务器错误:', error.response.status);
|
||
} else {
|
||
console.log('网络错误:', error.message);
|
||
}
|
||
|
||
message.error('网络请求失败');
|
||
return Promise.reject(error);
|
||
},
|
||
);
|
||
|
||
// CORS错误的备用处理方案
|
||
const handleCORSError = (config) => {
|
||
console.log('执行CORS错误备用方案...');
|
||
const apiUrl = config.url.replace('/api', '');
|
||
|
||
return fetch(apiUrl, {
|
||
method: config.method.toUpperCase(),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...config.headers,
|
||
},
|
||
mode: 'cors',
|
||
body: config.data ? JSON.stringify(config.data) : undefined,
|
||
})
|
||
.then((response) => {
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
return response.json();
|
||
})
|
||
.catch((error) => {
|
||
console.error('备用方案也失败:', error);
|
||
throw error;
|
||
});
|
||
};
|
||
|
||
// 封装请求方法
|
||
export const get = (url, params = {}) => {
|
||
return service.get(url, { params });
|
||
};
|
||
|
||
export const post = (url, data = {}) => {
|
||
return service.post(url, data);
|
||
};
|
||
|
||
export const upload = (url, file) => {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
return service.post(url, formData, {
|
||
headers: {
|
||
'Content-Type': 'multipart/form-data',
|
||
},
|
||
});
|
||
};
|
||
|
||
// 发送普通消息API
|
||
export const sendMessage = (data) => {
|
||
const apiUrl = '/im-api/send-to-user';
|
||
|
||
// 消息类型映射 - 根据用户提供的完整映射更新
|
||
const messageTypeMap: any = {
|
||
text: 0,
|
||
image: 1,
|
||
audio: 2,
|
||
video: 3,
|
||
prescription: 4,
|
||
'medical-record': 5,
|
||
'video-call': 6,
|
||
'audio-call': 7,
|
||
file: 8,
|
||
register: 10,
|
||
'patient-experience': 11,
|
||
'product-card': 12,
|
||
'end-consultation': 13,
|
||
};
|
||
|
||
// 确保 receiver_user_id 有 user- 前缀(如果是患者端)
|
||
const receiverId = data.receiverId.toString();
|
||
const normalizedReceiverId = receiverId.startsWith('user-') ? receiverId : `user-${receiverId}`;
|
||
|
||
// 确保 sender_user_id:小助手保持 doctor_assistant;医生端补 doctor- 前缀
|
||
const senderId = data.senderId.toString();
|
||
const normalizedSenderId =
|
||
senderId === 'doctor_assistant' || senderId.startsWith('doctor-')
|
||
? senderId
|
||
: `doctor-${senderId}`;
|
||
|
||
const requestData = {
|
||
room_id: data.roomId,
|
||
sender_user_id: normalizedSenderId,
|
||
receiver_user_id: normalizedReceiverId,
|
||
message_type: messageTypeMap[data.type] || 0,
|
||
message_content: data.content || '',
|
||
duration: data.duration || 0,
|
||
};
|
||
|
||
// 如果包含通话信令,添加call_id和call_status
|
||
if (data.callId) {
|
||
requestData.call_id = data.callId;
|
||
}
|
||
if (data.callStatus) {
|
||
requestData.call_status = data.callStatus;
|
||
}
|
||
|
||
// 从 accessStore 获取 Token
|
||
const accessStore = useAccessStore();
|
||
const token = accessStore.accessToken;
|
||
|
||
return axios
|
||
.post(apiUrl, requestData, {
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': token ? (token.startsWith('Bearer ') ? token : `Bearer ${token}`) : '',
|
||
},
|
||
timeout: 15_000,
|
||
withCredentials: false,
|
||
})
|
||
.then((response) => {
|
||
console.log('消息发送成功:', response.data);
|
||
return response.data;
|
||
})
|
||
.catch((error) => {
|
||
console.error('消息发送失败:', error);
|
||
throw error;
|
||
});
|
||
};
|
||
|
||
// 专门的通话信令发送方法
|
||
export const sendCallSignal = (signalData) => {
|
||
const apiUrl = '/api/send-to-user';
|
||
|
||
// 验证必要参数
|
||
if (!signalData.sender_user_id) {
|
||
throw new Error('缺少发送方用户ID');
|
||
}
|
||
if (!signalData.receiver_user_id) {
|
||
throw new Error('缺少接收方用户ID');
|
||
}
|
||
if (!signalData.call_id) {
|
||
throw new Error('缺少通话ID');
|
||
}
|
||
if (!signalData.call_status) {
|
||
throw new Error('缺少通话状态');
|
||
}
|
||
|
||
// 确保 receiver_user_id 有 user- 前缀(如果是患者端)
|
||
const receiverId = signalData.receiver_user_id;
|
||
const normalizedReceiverId = receiverId.startsWith('user-') || receiverId.startsWith('doctor-')
|
||
? receiverId
|
||
: `user-${receiverId}`;
|
||
|
||
// 构建通话信令请求数据
|
||
const requestData = {
|
||
sender_user_id: signalData.sender_user_id,
|
||
receiver_user_id: normalizedReceiverId,
|
||
message_type: signalData.message_type || 6, // 默认视频通话
|
||
message_content:
|
||
signalData.message_content || JSON.stringify(signalData.data || {}),
|
||
call_id: signalData.call_id,
|
||
call_status: signalData.call_status,
|
||
// 添加来源标记
|
||
isFromHttp: true,
|
||
};
|
||
|
||
console.log('发送通话信令到API:', apiUrl, requestData);
|
||
|
||
// 从 accessStore 获取 Token
|
||
const accessStore = useAccessStore();
|
||
const token = accessStore.accessToken;
|
||
|
||
return axios
|
||
.post(apiUrl, requestData, {
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': token ? (token.startsWith('Bearer ') ? token : `Bearer ${token}`) : '',
|
||
},
|
||
timeout: 15_000,
|
||
withCredentials: false,
|
||
})
|
||
.then((response) => {
|
||
console.log('通话信令发送成功:', response.data);
|
||
return response.data;
|
||
})
|
||
.catch((error) => {
|
||
console.error('通话信令发送失败:', error);
|
||
|
||
// 提供更详细的错误信息
|
||
if (error.response) {
|
||
console.error('响应错误:', error.response.data);
|
||
console.error('响应状态:', error.response.status);
|
||
console.error('响应头:', error.response.headers);
|
||
} else if (error.request) {
|
||
console.error('请求错误:', error.request);
|
||
} else {
|
||
console.error('配置错误:', error.message);
|
||
}
|
||
|
||
throw error;
|
||
});
|
||
};
|
||
|
||
// 通话相关的便捷方法
|
||
export const callAPI = {
|
||
// 发起通话邀请
|
||
invite: (senderUserId, receiverUserId, callId, callType = 6) => {
|
||
return sendCallSignal({
|
||
sender_user_id: senderUserId,
|
||
receiver_user_id: receiverUserId,
|
||
call_id: callId,
|
||
call_status: 'invite',
|
||
message_type: callType, // 6=视频, 7=语音
|
||
});
|
||
},
|
||
|
||
// 接受通话
|
||
accept: (senderUserId, receiverUserId, callId, callType = 6) => {
|
||
return sendCallSignal({
|
||
sender_user_id: senderUserId,
|
||
receiver_user_id: receiverUserId,
|
||
call_id: callId,
|
||
call_status: 'accepted',
|
||
message_type: callType,
|
||
});
|
||
},
|
||
|
||
// 拒绝通话
|
||
reject: (senderUserId, receiverUserId, callId, callType = 6) => {
|
||
return sendCallSignal({
|
||
sender_user_id: senderUserId,
|
||
receiver_user_id: receiverUserId,
|
||
call_id: callId,
|
||
call_status: 'rejected',
|
||
message_type: callType,
|
||
});
|
||
},
|
||
|
||
// 结束通话
|
||
end: (senderUserId, receiverUserId, callId, callType = 6) => {
|
||
return sendCallSignal({
|
||
sender_user_id: senderUserId,
|
||
receiver_user_id: receiverUserId,
|
||
call_id: callId,
|
||
call_status: 'ended',
|
||
message_type: callType,
|
||
});
|
||
},
|
||
|
||
// 发送WebRTC Offer
|
||
sendOffer: (
|
||
senderUserId,
|
||
receiverUserId,
|
||
callId,
|
||
offerData,
|
||
callType = 6,
|
||
) => {
|
||
return sendCallSignal({
|
||
sender_user_id: senderUserId,
|
||
receiver_user_id: receiverUserId,
|
||
call_id: callId,
|
||
call_status: 'offer',
|
||
message_type: callType,
|
||
data: offerData,
|
||
});
|
||
},
|
||
|
||
// 发送WebRTC Answer
|
||
sendAnswer: (
|
||
senderUserId,
|
||
receiverUserId,
|
||
callId,
|
||
answerData,
|
||
callType = 6,
|
||
) => {
|
||
return sendCallSignal({
|
||
sender_user_id: senderUserId,
|
||
receiver_user_id: receiverUserId,
|
||
call_id: callId,
|
||
call_status: 'answer',
|
||
message_type: callType,
|
||
data: answerData,
|
||
});
|
||
},
|
||
|
||
// 发送ICE候选
|
||
sendCandidate: (
|
||
senderUserId,
|
||
receiverUserId,
|
||
callId,
|
||
candidateData,
|
||
callType = 6,
|
||
) => {
|
||
return sendCallSignal({
|
||
sender_user_id: senderUserId,
|
||
receiver_user_id: receiverUserId,
|
||
call_id: callId,
|
||
call_status: 'candidate',
|
||
message_type: callType,
|
||
data: candidateData,
|
||
});
|
||
},
|
||
|
||
// 发送忙线状态
|
||
sendBusy: (senderUserId, receiverUserId, callId, callType = 6) => {
|
||
return sendCallSignal({
|
||
sender_user_id: senderUserId,
|
||
receiver_user_id: receiverUserId,
|
||
call_id: callId,
|
||
call_status: 'busy',
|
||
message_type: callType,
|
||
});
|
||
},
|
||
|
||
// 发送无人接听
|
||
sendNoAnswer: (senderUserId, receiverUserId, callId, callType = 6) => {
|
||
return sendCallSignal({
|
||
sender_user_id: senderUserId,
|
||
receiver_user_id: receiverUserId,
|
||
call_id: callId,
|
||
call_status: 'no-answer',
|
||
message_type: callType,
|
||
});
|
||
},
|
||
|
||
// 发送通话失败
|
||
sendFailed: (senderUserId, receiverUserId, callId, callType = 6) => {
|
||
return sendCallSignal({
|
||
sender_user_id: senderUserId,
|
||
receiver_user_id: receiverUserId,
|
||
call_id: callId,
|
||
call_status: 'failed',
|
||
message_type: callType,
|
||
});
|
||
},
|
||
};
|
||
|
||
export default service;
|