导出方法为sse

This commit is contained in:
龙运模 2025-07-26 17:09:04 +08:00
parent 199dd3ddb4
commit 800c93602e
9 changed files with 180 additions and 36 deletions

View File

@ -13,6 +13,9 @@ VUE_APP_API_DEV = https://dev.api.linkwing.com/api/v1
VUE_APP_WS_URL = wss://dev.api.linkwing.com/wss VUE_APP_WS_URL = wss://dev.api.linkwing.com/wss
VUE_APP_WSS_URL = wss://dev.api.linkwing.com/wss VUE_APP_WSS_URL = wss://dev.api.linkwing.com/wss
# SSE地址
VUE_APP_SSE_URL = https://dev.api.linkwing.com/sse/stream
# 本地端口 # 本地端口
VUE_APP_PORT = 2801 VUE_APP_PORT = 2801

View File

@ -5,15 +5,15 @@ NODE_ENV = production
VUE_APP_TITLE = 象纬云科 VUE_APP_TITLE = 象纬云科
# 测试环境 # 测试环境
VUE_APP_API_BASEURL = https://dev.api.linkwing.com/api/v1 # VUE_APP_API_BASEURL = https://dev.api.linkwing.com/api/v1
VUE_APP_API_DEV = https://dev.api.linkwing.com/api/v1 # VUE_APP_API_DEV = https://dev.api.linkwing.com/api/v1
VUE_APP_WS_URL = wss://dev.api.linkwing.com/wss # VUE_APP_WS_URL = wss://dev.api.linkwing.com/wss
VUE_APP_WSS_URL = wss://dev.api.linkwing.com/wss # VUE_APP_WSS_URL = wss://dev.api.linkwing.com/wss
# 线上环境 # 线上环境
# 接口地址 # WS地址 # 接口地址 # WS地址
# VUE_APP_API_BASEURL = https://prod.api.linkwing.com/api/v1 VUE_APP_API_BASEURL = https://prod.api.linkwing.com/api/v1
# VUE_APP_API_DEV = https://prod.api.linkwing.com/api/v1 VUE_APP_API_DEV = https://prod.api.linkwing.com/api/v1
# VUE_APP_WS_URL = wss://prod.api.linkwing.com/wss VUE_APP_WS_URL = wss://prod.api.linkwing.com/wss
# VUE_APP_WSS_URL = wss://prod.api.linkwing.com/wss VUE_APP_WSS_URL = wss://prod.api.linkwing.com/wss

View File

@ -16,7 +16,11 @@
<div :class="list.length-1==index?'':'exportItem'" v-if="list.length>0 && type == item.type"> <div :class="list.length-1==index?'':'exportItem'" v-if="list.length>0 && type == item.type">
<div class="exportHeader"> <div class="exportHeader">
<div class="name">{{item.type_desc}}</div> <div class="name">{{item.type_desc}}</div>
<div class="status" v-if="item.status==0">{{item.msg}}</div> <div class="status" v-if="item.status==0">
<el-tooltip effect="dark" :content="item.msg">
<span size="mini">{{item.msg}}</span>
</el-tooltip>
</div>
<div class="finish" v-if="item.status==1"><i class="icon"><sc-icon-Finish/></i> 文件生成完成</div> <div class="finish" v-if="item.status==1"><i class="icon"><sc-icon-Finish/></i> 文件生成完成</div>
</div> </div>
<el-progress class="exportPopover" :text-inside="true" :stroke-width="12" :percentage="item.rate" /> <el-progress class="exportPopover" :text-inside="true" :stroke-width="12" :percentage="item.rate" />
@ -24,27 +28,6 @@
</div> </div>
</template> </template>
</el-popover> </el-popover>
<!-- <div class="customerPopover">-->
<!-- <div class="btnBox">-->
<!-- <slot></slot>-->
<!-- </div>-->
<!-- <div class="contentPopover" v-if="show">-->
<!-- <div class="bodyMain">-->
<!-- <span class="arrow"></span>-->
<!-- <div v-for="(item,index) in list" :key="item">-->
<!-- <div :class="list.length-1==index?'':'exportItem'" v-if="list.length>0 && type == item.type">-->
<!-- <div class="exportHeader">-->
<!-- <div class="name">{{item.type_desc}}</div>-->
<!-- <div class="status" v-if="item.status==0">{{item.msg}}</div>-->
<!-- <div class="finish" v-if="item.status==1"><i class="icon"><sc-icon-Finish/></i> 文件生成完成</div>-->
<!-- </div>-->
<!-- <el-progress class="exportPopover" :text-inside="true" :stroke-width="12" :percentage="item.rate" />-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</template> </template>
<script> <script>
@ -77,10 +60,14 @@ export default {
}, },
mounted() { mounted() {
// //
eventBus.$on('sockBack', this.getWsResult); // eventBus.$on('sockBack', this.getWsResult);
eventBus.$on('sseBack', this.getWsResult);
}, },
unmounted() { unmounted() {
eventBus.$off('sockBack', this.getWsResult); // eventBus.$off('sockBack', this.getWsResult);
eventBus.$off('sseBack', this.getWsResult);
}, },
methods:{ methods:{
getWsResult(res){ getWsResult(res){

View File

@ -10,7 +10,11 @@
<div v-if="list.length>0 && type == item.type"> <div v-if="list.length>0 && type == item.type">
<div class="exportHeader"> <div class="exportHeader">
<div class="exportName">{{item.type_desc}}</div> <div class="exportName">{{item.type_desc}}</div>
<div class="status" v-if="item.status==0">{{item.msg}}</div> <div class="status" v-if="item.status==0">
<el-tooltip effect="dark" :content="item.msg">
<span size="mini">{{item.msg}}</span>
</el-tooltip>
</div>
<div class="finish" v-if="item.status==1"><i class="icon"><sc-icon-Finish/></i> 文件生成完成</div> <div class="finish" v-if="item.status==1"><i class="icon"><sc-icon-Finish/></i> 文件生成完成</div>
</div> </div>
<el-progress class="exportPopover" :text-inside="true" :stroke-width="12" :percentage="item.rate" /> <el-progress class="exportPopover" :text-inside="true" :stroke-width="12" :percentage="item.rate" />
@ -40,10 +44,14 @@ export default {
}, },
mounted() { mounted() {
// //
eventBus.$on('sockBack', this.getWsResult); // eventBus.$on('sockBack', this.getWsResult);
// sse
eventBus.$on('sseBack', this.getWsResult);
}, },
unmounted() { unmounted() {
eventBus.$off('sockBack', this.getWsResult); // eventBus.$off('sockBack', this.getWsResult);
eventBus.$off('sseBack', this.getWsResult);
}, },
methods:{ methods:{
getWsResult(res){ getWsResult(res){

View File

@ -107,9 +107,13 @@ export default {
}, },
mounted() { mounted() {
eventBus.$on('sockBack', this.getWsResult); eventBus.$on('sockBack', this.getWsResult);
// eventBus.$on('sseBack', this.getWsResult);
}, },
unmounted() { unmounted() {
eventBus.$off('sockBack', this.getWsResult); eventBus.$off('sockBack', this.getWsResult);
// eventBus.$off('sseBack', this.getWsResult);
}, },
methods:{ methods:{
getWsResult(res){ getWsResult(res){

View File

@ -23,6 +23,8 @@ const DEFAULT_CONFIG = {
? process.env.VUE_APP_WS_URL ? process.env.VUE_APP_WS_URL
: process.env.VUE_APP_WSS_URL, : process.env.VUE_APP_WSS_URL,
// SSE接口地址
SSE_URL:process.env.VUE_APP_SSE_URL,
//请求超时 //请求超时
TIMEOUT: 10000000, TIMEOUT: 10000000,

View File

@ -262,9 +262,14 @@
} }
}, },
mounted() { mounted() {
let token = this.$TOOL.cookie.get('TOKEN');
if(token && token !== null){
this.$seeApi.default.connect();
}
}, },
unmounted() { unmounted() {
this.$seeApi.default.close();
eventBus.$off('sockBack', this.getWsResult); eventBus.$off('sockBack', this.getWsResult);
}, },
watch: { watch: {

View File

@ -51,6 +51,7 @@ import errorHandler from './utils/errorHandler'
import * as elIcons from '@element-plus/icons-vue' import * as elIcons from '@element-plus/icons-vue'
import * as scIcons from './assets/icons' import * as scIcons from './assets/icons'
import * as socketApi from "@/utils/websocket"; import * as socketApi from "@/utils/websocket";
import * as sseApi from "@/utils/sseService";
export default { export default {
install(app) { install(app) {
@ -62,6 +63,7 @@ export default {
app.config.globalProperties.$AUTH = permission; app.config.globalProperties.$AUTH = permission;
app.config.globalProperties.$ROLE = rolePermission; app.config.globalProperties.$ROLE = rolePermission;
app.config.globalProperties.$socketApi = socketApi; app.config.globalProperties.$socketApi = socketApi;
app.config.globalProperties.$seeApi = sseApi;
//注册全局组件 //注册全局组件
app.component('scTable', scTable); app.component('scTable', scTable);

133
src/utils/sseService.js Normal file
View File

@ -0,0 +1,133 @@
// src/utils/sse.js
// import store from "../store";
import tool from "@/utils/tool";
import systemConfig from '@/config';
import {eventBus} from "@/utils/eventBus";
let eventSource = null;
let reconnectTimer = null;
const reconnectDelay = 5000;
let isConnected = false;
let lockReconnect = false;
const callbacks = new Map();
const url = systemConfig.SSE_URL;
// 改进的连接管理
function createSSE(token) {
if (isConnected && eventSource) return;
closeSSE();
try {
let reToken = token === undefined ? tool.cookie.get('TOKEN') : token;
if(!reToken) return;
eventSource = new EventSource(`${url}?token=${reToken}`);
// 使用具名函数以便于移除监听器
eventSource.onopen = onOpen;
eventSource.onmessage = onMessage;
eventSource.onerror = onError;
} catch (error) {
console.error('创建SSE连接失败:', error);
handleDisconnect();
}
}
// 具名事件处理函数
function onOpen() {
isConnected = true;
console.log('SSE连接已建立');
}
function onMessage(event) {
try {
const res = JSON.parse(event.data);
// store.commit('set_sse_msg', data);
if(res && res.data){
eventBus.$emit('sseBack',res.data);
}
} catch (error) {
console.error('解析SSE数据失败:', error);
}
}
function onError() {
// error
// console.error('SSE连接错误:', error);
handleDisconnect();
}
// 改进的注册方法
function registerCallback(callback, identifier = 'default') {
// 清理同名的旧回调
if (callbacks.has(identifier)) {
callbacks.delete(identifier);
}
callbacks.set(identifier, {
fn: callback,
// 存储调用栈信息便于调试
_debug: new Error().stack.split('\n').slice(1, 4).join('\n')
});
return () => {
callbacks.delete(identifier);
console.log(`SSE回调 ${identifier} 已取消注册`);
};
}
function handleDisconnect() {
if (!isConnected) return;
isConnected = false;
// console.log('SSE连接断开尝试重连...');
if (!lockReconnect) {
lockReconnect = true;
clearTimeout(reconnectTimer);
reconnectTimer = setTimeout(() => {
lockReconnect = false;
// 重连时保留现有回调
createSSE();
}, reconnectDelay);
}
}
function restartSSE(token) {
closeSSE();
createSSE(token);
}
function closeSSE() {
if (eventSource) {
// 移除所有监听器
eventSource.onopen = null;
eventSource.onmessage = null;
eventSource.onerror = null;
eventSource.close();
eventSource = null;
}
isConnected = false;
clearTimeout(reconnectTimer);
console.log('SSE连接已关闭');
}
// 添加调试方法
function debugCallbacks() {
callbacks.forEach((value, key) => {
console.log(`回调标识: ${key}`);
});
console.groupEnd();
}
export default {
connect: createSSE,
onMessage: registerCallback,
close: closeSSE,
restart: restartSSE,
debug: debugCallbacks,
get isConnected() {
return isConnected;
}
};