113 lines
3.2 KiB
TypeScript
113 lines
3.2 KiB
TypeScript
import { getToken } from '@/utils/auth';
|
||
import { ElNotification } from 'element-plus';
|
||
import { useNoticeStore } from '@/store/modules/notice';
|
||
import { useEventSource } from '@vueuse/core';
|
||
import { watch } from 'vue';
|
||
|
||
// 全局单例保存SSE实例,防止多次创建
|
||
let eventSourceInstance: ReturnType<typeof useEventSource> | null = null;
|
||
let stopWatchError: (() => void) | null = null;
|
||
let stopWatchData: (() => void) | null = null;
|
||
|
||
// 关闭SSE、清理监听
|
||
export const closeSSE = () => {
|
||
if (eventSourceInstance) {
|
||
eventSourceInstance.close();
|
||
eventSourceInstance = null;
|
||
}
|
||
// 销毁watch
|
||
stopWatchError?.();
|
||
stopWatchData?.();
|
||
stopWatchError = null;
|
||
stopWatchData = null;
|
||
};
|
||
|
||
// 初始化SSE
|
||
export const initSSE = (url: string) => {
|
||
if (import.meta.env.VITE_APP_SSE === 'false') {
|
||
return;
|
||
}
|
||
// 关键:新建之前先关闭旧连接!
|
||
closeSSE();
|
||
|
||
const token = getToken();
|
||
const clientId = import.meta.env.VITE_APP_CLIENT_ID;
|
||
const sseUrl = url + '?Authorization=Bearer ' + token + '&clientid=' + clientId;
|
||
|
||
eventSourceInstance = useEventSource(sseUrl, [], {
|
||
autoReconnect: {
|
||
retries: 5,
|
||
delay: 5000,
|
||
onFailed() {
|
||
console.log('Failed to connect after 5 retries');
|
||
}
|
||
}
|
||
});
|
||
|
||
const { data, error } = eventSourceInstance;
|
||
|
||
stopWatchError = watch(error, () => {
|
||
console.log('SSE connection error:', error.value);
|
||
// error.value = null; // 不要手动覆盖,会干扰useEventSource内部状态
|
||
});
|
||
|
||
stopWatchData = watch(data, () => {
|
||
if (!data.value) return;
|
||
const notice = tryParseJson(data.value as string);
|
||
if (typeof notice === 'string') {
|
||
useNoticeStore().addNotice({
|
||
message: notice,
|
||
read: false,
|
||
time: new Date().toLocaleString()
|
||
});
|
||
ElNotification({
|
||
title: '消息',
|
||
message: notice,
|
||
type: 'success',
|
||
duration: 3000
|
||
});
|
||
data.value = null;
|
||
return;
|
||
} else {
|
||
const title = typeof notice.title === 'string' ? notice.title : '消息';
|
||
const message = typeof notice.message === 'string' ? notice.message : '';
|
||
const type = typeof notice.type === 'string' ? notice.type : 'info';
|
||
useNoticeStore().addNotice({
|
||
message,
|
||
read: false,
|
||
time: new Date().toLocaleString()
|
||
});
|
||
ElNotification({
|
||
title,
|
||
message,
|
||
type: type as 'success' | 'warning' | 'info' | 'error',
|
||
duration: 3000
|
||
});
|
||
data.value = null;
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 尝试解析后端返回的字符串
|
||
* 如果是合法JSON对象 → 返回 Record<string, unknown>
|
||
* 否则(数组/null/基础值/非JSON字符串) → 返回原字符串
|
||
* @param str 后端传过来的字符串
|
||
*/
|
||
function tryParseJson(str: string): string | Record<string, unknown> {
|
||
if (typeof str !== 'string' || str === '') return str;
|
||
|
||
const trimStr = str.trim();
|
||
if (trimStr === '') return str;
|
||
try {
|
||
const parsed = JSON.parse(trimStr);
|
||
// 只有纯对象才返回对象,数组/null/基础值等统一当作字符串处理
|
||
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||
return parsed as Record<string, unknown>;
|
||
}
|
||
return str;
|
||
} catch {
|
||
return str;
|
||
}
|
||
}
|