feat:跟新livekit房间创建
This commit is contained in:
@@ -7,6 +7,7 @@ VITE_APP_PORT = 80
|
||||
|
||||
# 应用模板管理后台/开发环境
|
||||
VITE_APP_BASE_API = '/dev-api'
|
||||
VITE_APP_BASE_API_livekit = '/livekit-api'
|
||||
|
||||
# 后端实际地址(可选,用于proxy.target)
|
||||
VITE_API_TARGET = 'https://xsynergy.gxtech.ltd'
|
||||
|
||||
99
package-lock.json
generated
99
package-lock.json
generated
@@ -16,6 +16,7 @@
|
||||
"code-inspector-plugin": "^0.20.12",
|
||||
"element-plus": "^2.2.27",
|
||||
"js-cookie": "^3.0.1",
|
||||
"livekit-client": "^2.7.5",
|
||||
"mitt": "^3.0.0",
|
||||
"mqtt": "^5.14.0",
|
||||
"pinia": "^2.0.22",
|
||||
@@ -94,6 +95,11 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmmirror.com/@bufbuild/protobuf/-/protobuf-1.10.1.tgz",
|
||||
"integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ=="
|
||||
},
|
||||
"node_modules/@ctrl/tinycolor": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||
@@ -603,6 +609,19 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/mutex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@livekit/mutex/-/mutex-1.0.0.tgz",
|
||||
"integrity": "sha512-aiUhoThBNF9UyGTxEURFzJLhhPLIVTnQiEVMjRhPnfHNKLfo2JY9xovHKIus7B78UD5hsP6DlgpmAsjrz4U0Iw=="
|
||||
},
|
||||
"node_modules/@livekit/protocol": {
|
||||
"version": "1.29.4",
|
||||
"resolved": "https://registry.npmmirror.com/@livekit/protocol/-/protocol-1.29.4.tgz",
|
||||
"integrity": "sha512-dsqxvABHilrMA0BU5m1w8cMWSVeDjV2ZUIUDClNQZju3c30DLMfEYDHU5nmXDfaaHjNIgoRbYR7upJMozG8JJg==",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@msgpack/msgpack": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@msgpack/msgpack/-/msgpack-3.1.2.tgz",
|
||||
@@ -2279,6 +2298,27 @@
|
||||
"resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||
},
|
||||
"node_modules/livekit-client": {
|
||||
"version": "2.7.5",
|
||||
"resolved": "https://registry.npmmirror.com/livekit-client/-/livekit-client-2.7.5.tgz",
|
||||
"integrity": "sha512-sPhHYwXvG75y1LDC50dDC9k6Z49L2vc/HcMRhzhi7yBca6ofPEebpB0bmPOry4ovrnFA+a8TL1pFR2mko1/clw==",
|
||||
"dependencies": {
|
||||
"@livekit/mutex": "1.0.0",
|
||||
"@livekit/protocol": "1.29.4",
|
||||
"events": "^3.3.0",
|
||||
"loglevel": "^1.8.0",
|
||||
"sdp-transform": "^2.14.1",
|
||||
"ts-debounce": "^4.0.0",
|
||||
"tslib": "2.7.0",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"webrtc-adapter": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/livekit-client/node_modules/tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
|
||||
},
|
||||
"node_modules/local-pkg": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz",
|
||||
@@ -2316,6 +2356,18 @@
|
||||
"lodash-es": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/loglevel": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmmirror.com/loglevel/-/loglevel-1.9.2.tgz",
|
||||
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/loglevel"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
@@ -3021,6 +3073,15 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@@ -3063,6 +3124,19 @@
|
||||
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sdp": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/sdp/-/sdp-3.2.1.tgz",
|
||||
"integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw=="
|
||||
},
|
||||
"node_modules/sdp-transform": {
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmmirror.com/sdp-transform/-/sdp-transform-2.15.0.tgz",
|
||||
"integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==",
|
||||
"bin": {
|
||||
"sdp-verify": "checker.js"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -3375,6 +3449,11 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-debounce": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/ts-debounce/-/ts-debounce-4.0.0.tgz",
|
||||
"integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg=="
|
||||
},
|
||||
"node_modules/ts-interface-checker": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
@@ -3385,6 +3464,14 @@
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
},
|
||||
"node_modules/typed-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/typed-emitter/-/typed-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
|
||||
"optionalDependencies": {
|
||||
"rxjs": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz",
|
||||
@@ -3751,6 +3838,18 @@
|
||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webrtc-adapter": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/webrtc-adapter/-/webrtc-adapter-9.0.3.tgz",
|
||||
"integrity": "sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==",
|
||||
"dependencies": {
|
||||
"sdp": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0",
|
||||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"code-inspector-plugin": "^0.20.12",
|
||||
"element-plus": "^2.2.27",
|
||||
"js-cookie": "^3.0.1",
|
||||
"livekit-client": "^2.7.5",
|
||||
"mitt": "^3.0.0",
|
||||
"mqtt": "^5.14.0",
|
||||
"pinia": "^2.0.22",
|
||||
|
||||
28
src/api/conferencingRoom.js
Normal file
28
src/api/conferencingRoom.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import request from '@/views/conferencingRoom/utils/request.js'
|
||||
import { tansParams } from "@/utils/ruoyi";
|
||||
|
||||
// 获取roomtoken
|
||||
export function getRoomToken(data) {
|
||||
return request({
|
||||
url: `/room/token`,
|
||||
method: 'post',
|
||||
data: tansParams(data),
|
||||
})
|
||||
}
|
||||
|
||||
// 创建房间
|
||||
export function createRoom(data) {
|
||||
return request({
|
||||
url: `/room/`,
|
||||
method: 'post',
|
||||
data: tansParams(data),
|
||||
})
|
||||
}
|
||||
|
||||
//获取所有房间列表
|
||||
export function getRoomList() {
|
||||
return request({
|
||||
url: `/room/ `,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
@@ -71,8 +71,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
height: 100%;
|
||||
.el-dialog {
|
||||
margin: 0 auto !important;
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
@import "./ruoyi.scss";
|
||||
@import 'element-plus/theme-chalk/index.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
// @tailwind base;
|
||||
// @tailwind components;
|
||||
// @tailwind utilities;
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
|
||||
@@ -30,6 +30,18 @@ const router = createRouter({
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/conferencingRoom",
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: "Coordinate",
|
||||
component: () => import("@/views/conferencingRoom/index.vue")
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
// 错误页面路由
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
|
||||
@@ -127,9 +127,10 @@ service.interceptors.response.use(
|
||||
|
||||
case 401:
|
||||
console.log('未授权', responseData)
|
||||
return handleUnauthorized().then(() => {
|
||||
return Promise.reject({ code: 401, message: '未授权' });
|
||||
});
|
||||
return Promise.resolve(responseData);
|
||||
// return handleUnauthorized().then(() => {
|
||||
// return Promise.reject({ code: 401, message: '未授权' });
|
||||
// });
|
||||
|
||||
case 500:
|
||||
const serverErrorMsg = responseData.meta?.message || '服务器内部错误';
|
||||
@@ -142,18 +143,9 @@ service.interceptors.response.use(
|
||||
return Promise.reject({ code: businessCode, message: errorMsg });
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.error('请求错误:', error);
|
||||
(error) => {
|
||||
let { message } = error;
|
||||
let code = error?.response?.status || -1;
|
||||
console.log(code,'code')
|
||||
|
||||
if(code == 401) {
|
||||
return handleUnauthorized().then(() => {
|
||||
return Promise.reject({ code: 401, message: '未授权' });
|
||||
});
|
||||
}
|
||||
|
||||
let code = error?.response?.status || -1;
|
||||
if (message == 'Network Error') {
|
||||
message = '后端接口连接异常';
|
||||
ElMessage({ message, type: 'error', duration: 5 * 1000 });
|
||||
|
||||
683
src/views/conferencingRoom/index.vue
Normal file
683
src/views/conferencingRoom/index.vue
Normal file
@@ -0,0 +1,683 @@
|
||||
<template>
|
||||
<div id="audio"></div>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<el-table :data="rooms.tableData" style="width: 100%">
|
||||
<el-table-column prop="name" label="房间名称" />
|
||||
<el-table-column prop="empty_timeout" label="空房间关闭时间" />
|
||||
<el-table-column prop="creation_time" label="创建时间" />
|
||||
<el-table-column prop="turn_password" label="口令" />
|
||||
</el-table>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="form" v-show="status">
|
||||
<el-form :model="formModel" label-position="right" :label-width="90">
|
||||
<el-form-item label="房间名称:">
|
||||
<el-input v-model="formModel.room" placeholder="输入房间名称" style="width: 300px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户编码:">
|
||||
<el-input v-model="formModel.uid" placeholder="输入用户编码" style="width: 300px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="选项:">
|
||||
<el-radio-group v-model="formModel.creatRoom">
|
||||
<el-radio label="0">
|
||||
<span>创建房间</span>
|
||||
</el-radio>
|
||||
<el-radio label="1">
|
||||
<span>加入房间</span>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="joinRoomBtn">
|
||||
{{ formModel.creatRoom === "0" ? "创建房间" : "加入房间" }}
|
||||
</el-button>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div v-if="!status" class="form">
|
||||
<el-result icon="success" :title="formModel.room" sub-title="已加入房间" />
|
||||
<el-input v-model="msgDate.some" placeholder="输入要发送的消息"></el-input>
|
||||
<el-button @click="postMessage" type="success">发送文本消息</el-button>
|
||||
|
||||
<!-- 视频容器 -->
|
||||
<div class="video-container">
|
||||
<!-- 本地视频 -->
|
||||
<div class="video-wrapper">
|
||||
<h3>我的视频 ({{ formModel.uid }})</h3>
|
||||
<video ref="localVideo" autoplay muted playsinline class="video-element"></video>
|
||||
<div class="video-controls">
|
||||
<el-button @click="toggleCamera" :type="cameraEnabled ? 'danger' : 'success'" size="small">
|
||||
{{ cameraEnabled ? '关闭摄像头' : '开启摄像头' }}
|
||||
</el-button>
|
||||
<el-button @click="toggleMicrophone" :type="microphoneEnabled ? 'danger' : 'success'" size="small">
|
||||
{{ microphoneEnabled ? '关闭麦克风' : '开启麦克风' }}
|
||||
</el-button>
|
||||
<el-button @click="toggleScreenShare" :type="isScreenSharing ? 'danger' : 'primary'" size="small">
|
||||
{{ isScreenSharing ? '停止共享' : '共享屏幕' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 远程视频 -->
|
||||
<div class="video-wrapper" v-for="participant in remoteParticipants" :key="participant.identity">
|
||||
<h3>{{ participant.identity }}</h3>
|
||||
<div class="video-tracks">
|
||||
<video
|
||||
v-for="videoTrack in participant.videoTracks"
|
||||
:key="videoTrack.sid"
|
||||
:ref="el => setRemoteVideo(el, videoTrack)"
|
||||
autoplay
|
||||
playsinline
|
||||
class="video-element">
|
||||
</video>
|
||||
</div>
|
||||
<div class="participant-info">
|
||||
<span>音频: {{ participant.audioEnabled ? '开启' : '关闭' }}</span>
|
||||
<span>视频: {{ participant.videoEnabled ? '开启' : '关闭' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 显示连接状态 -->
|
||||
<div v-if="connectionStatus" :class="['status', connectionStatus.type]">
|
||||
{{ connectionStatus.message }}
|
||||
</div>
|
||||
|
||||
<!-- 房间信息 -->
|
||||
<div class="room-info" v-if="!status">
|
||||
<h4>房间信息</h4>
|
||||
<p>房间名称: {{ room.name }}</p>
|
||||
<p>参与者数量: {{ participantCount }}</p>
|
||||
<p>连接状态: {{ room.connectionState }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, onUnmounted, nextTick, computed } from "vue";
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getRoomToken, getRoomList } from "@/api/conferencingRoom.js"
|
||||
import { Room, RoomEvent, ParticipantEvent, Track } from "livekit-client";
|
||||
|
||||
// LiveKit 服务器配置 - 确保使用正确的服务器地址
|
||||
const wsURL = "wss://meeting.cnsdt.com:443";
|
||||
|
||||
// 响应式数据
|
||||
const formModel = reactive({
|
||||
room: 'thehome',
|
||||
uid: 'xtqxk',
|
||||
creatRoom: '0'
|
||||
});
|
||||
|
||||
const status = ref(true);
|
||||
const connectionStatus = ref(null);
|
||||
const rooms = reactive({ tableData: [] });
|
||||
const msgDate = reactive({ some: '' });
|
||||
|
||||
// 视频相关引用和数据
|
||||
const localVideo = ref(null);
|
||||
const cameraEnabled = ref(false);
|
||||
const microphoneEnabled = ref(false);
|
||||
const isScreenSharing = ref(false);
|
||||
const remoteParticipants = ref([]);
|
||||
|
||||
// 创建 Room 实例
|
||||
const room = new Room({
|
||||
// 启用自适应流,根据网络状况自动调整视频质量
|
||||
adaptiveStream: true,
|
||||
// 启用动态投射,只传输当前可见的视频流以节省带宽
|
||||
dynacast: true,
|
||||
// 配置视频捕获默认值
|
||||
videoCaptureDefaults: {
|
||||
resolution: { width: 1280, height: 720 }
|
||||
},
|
||||
// 配置发布选项
|
||||
publishDefaults: {
|
||||
// 配置屏幕分享
|
||||
screenShareEncoding: {
|
||||
maxBitrate: 3_000_000,// 最大比特率 3 Mbps
|
||||
maxFramerate: 30, // 最大帧率 30 fps
|
||||
},
|
||||
// 配置视频编码
|
||||
videoEncoding: {
|
||||
maxBitrate: 2_500_000,
|
||||
maxFramerate: 30,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// 计算属性
|
||||
const participantCount = computed(() => {
|
||||
return remoteParticipants.value.length + 1; // 包括自己
|
||||
});
|
||||
|
||||
// 设置事件监听器
|
||||
function setupRoomListeners() {
|
||||
// 清除现有监听器
|
||||
room.removeAllListeners();
|
||||
|
||||
room
|
||||
.on(RoomEvent.Connected, handleConnected) // 连接成功
|
||||
.on(RoomEvent.Disconnected, handleDisconnected)// 连接断开
|
||||
.on(RoomEvent.Reconnected, handleReconnected)// 重连成功
|
||||
.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)// 订阅远程轨道
|
||||
.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)// 取消订阅远程轨道
|
||||
.on(RoomEvent.ActiveSpeakersChanged, handleActiveSpeakersChanged)// 活跃说话者变化
|
||||
.on(RoomEvent.DataReceived, handleDataReceived) // 接收到自定义数据
|
||||
.on(RoomEvent.ConnectionStateChanged, handleConnectionStateChanged)// 连接状态变化
|
||||
.on(RoomEvent.ParticipantConnected, handleParticipantConnected)// 新参与者加入
|
||||
.on(RoomEvent.ParticipantDisconnected, handleParticipantDisconnected)// 参与者离开
|
||||
.on(RoomEvent.LocalTrackPublished, handleLocalTrackPublished)// 本地轨道发布成功
|
||||
.on(RoomEvent.LocalTrackUnpublished, handleLocalTrackUnpublished)// 本地轨道取消发布
|
||||
.on(RoomEvent.TrackMuted, handleTrackMuted) // 轨道静音
|
||||
.on(RoomEvent.TrackUnmuted, handleTrackUnmuted);// 轨道取消静音
|
||||
}
|
||||
|
||||
// 事件处理函数
|
||||
async function handleConnected() {
|
||||
console.log("成功连接到房间:", room.name);
|
||||
status.value = false;
|
||||
connectionStatus.value = {
|
||||
type: 'success',
|
||||
message: `已成功连接到房间: ${room.name}`
|
||||
};
|
||||
ElMessage.success('已成功连接到房间');
|
||||
|
||||
// 初始化远程参与者列表
|
||||
updateRemoteParticipants();
|
||||
|
||||
// 自动开启摄像头(仅创建房间时)
|
||||
if (formModel.creatRoom === "0") {
|
||||
try {
|
||||
await enableCamera();
|
||||
ElMessage.success('摄像头已自动开启');
|
||||
} catch (error) {
|
||||
console.warn('自动开启摄像头失败:', error);
|
||||
ElMessage.warning('自动开启摄像头失败,请手动开启');
|
||||
}
|
||||
}
|
||||
|
||||
// 监听所有现有远程参与者
|
||||
room.remoteParticipants.forEach(participant => {
|
||||
setupParticipantListeners(participant);
|
||||
});
|
||||
}
|
||||
|
||||
function handleDisconnected(reason) {
|
||||
console.log("断开连接:", reason);
|
||||
status.value = true;
|
||||
cameraEnabled.value = false;
|
||||
microphoneEnabled.value = false;
|
||||
isScreenSharing.value = false;
|
||||
remoteParticipants.value = [];
|
||||
connectionStatus.value = {
|
||||
type: 'error',
|
||||
message: `连接已断开: ${reason}`
|
||||
};
|
||||
ElMessage.error('连接已断开');
|
||||
}
|
||||
|
||||
function handleReconnected() {
|
||||
console.log("已重新连接");
|
||||
connectionStatus.value = {
|
||||
type: 'success',
|
||||
message: '已重新连接到房间'
|
||||
};
|
||||
updateRemoteParticipants();
|
||||
ElMessage.success('已重新连接到房间');
|
||||
}
|
||||
|
||||
// 处理轨道订阅事件
|
||||
function handleTrackSubscribed(track, publication, participant) {
|
||||
console.log("轨道已订阅:", track.kind, "来自:", participant.identity);
|
||||
|
||||
if (track.kind === Track.Kind.Video || track.kind === Track.Kind.Audio) {
|
||||
updateRemoteParticipants();
|
||||
|
||||
nextTick(() => {
|
||||
if (track.kind === Track.Kind.Video) {
|
||||
attachVideoTrack(track, participant.identity);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleTrackUnsubscribed(track, publication, participant) {
|
||||
console.log("轨道取消订阅:", track.kind, participant.identity);
|
||||
updateRemoteParticipants();
|
||||
}
|
||||
|
||||
function handleParticipantConnected(participant) {
|
||||
console.log("参与者连接:", participant.identity);
|
||||
console.log("参与者连接+++:", participant);
|
||||
setupParticipantListeners(participant);
|
||||
updateRemoteParticipants();
|
||||
}
|
||||
|
||||
function handleParticipantDisconnected(participant) {
|
||||
console.log("参与者断开连接:", participant.identity);
|
||||
|
||||
updateRemoteParticipants();
|
||||
}
|
||||
|
||||
function handleLocalTrackPublished(publication) {
|
||||
console.log("本地轨道发布:", publication.kind);
|
||||
if (publication.kind === Track.Kind.Video && publication.track) {
|
||||
attachLocalVideoTrack(publication.track);
|
||||
}
|
||||
}
|
||||
|
||||
function handleLocalTrackUnpublished(publication) {
|
||||
console.log("本地轨道取消发布:", publication.kind);
|
||||
if (publication.kind === Track.Kind.Video) {
|
||||
cameraEnabled.value = false;
|
||||
} else if (publication.kind === Track.Kind.Audio) {
|
||||
microphoneEnabled.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleTrackMuted(publication, participant) {
|
||||
console.log("轨道静音:", publication.kind, participant.identity);
|
||||
updateRemoteParticipants();
|
||||
}
|
||||
|
||||
function handleTrackUnmuted(publication, participant) {
|
||||
console.log("轨道取消静音:", publication.kind, participant.identity);
|
||||
updateRemoteParticipants();
|
||||
}
|
||||
|
||||
function setupParticipantListeners(participant) {
|
||||
participant
|
||||
.on(ParticipantEvent.TrackSubscribed, (track, publication) => {
|
||||
handleTrackSubscribed(track, publication, participant);
|
||||
})
|
||||
.on(ParticipantEvent.TrackUnsubscribed, (track, publication) => {
|
||||
handleTrackUnsubscribed(track, publication, participant);
|
||||
})
|
||||
.on(ParticipantEvent.TrackMuted, (publication) => {
|
||||
handleTrackMuted(publication, participant);
|
||||
})
|
||||
.on(ParticipantEvent.TrackUnmuted, (publication) => {
|
||||
handleTrackUnmuted(publication, participant);
|
||||
});
|
||||
}
|
||||
|
||||
function handleActiveSpeakersChanged(speakers) {
|
||||
console.log("活跃说话者变化:", speakers.map(s => s.identity));
|
||||
}
|
||||
|
||||
function handleDataReceived(payload, participant, kind) {
|
||||
try {
|
||||
const decoder = new TextDecoder();
|
||||
const strData = decoder.decode(payload);
|
||||
console.log("接收到消息:", strData, "来自:", participant.identity);
|
||||
ElMessage.info(`收到消息 from ${participant.identity}: ${strData}`);
|
||||
} catch (error) {
|
||||
console.error('处理接收消息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleConnectionStateChanged(state) {
|
||||
console.log("连接状态变化:", state);
|
||||
connectionStatus.value = {
|
||||
type: 'info',
|
||||
message: `连接状态: ${state}`
|
||||
};
|
||||
}
|
||||
|
||||
// 更新远程参与者列表
|
||||
function updateRemoteParticipants() {
|
||||
try {
|
||||
const participants = [];
|
||||
|
||||
if (room && room.remoteParticipants) {
|
||||
console.log(room.remoteParticipants,'room.remoteParticipants+++++')
|
||||
// 使用更安全的方式遍历远程参与者
|
||||
for (const [identity, participant] of Object.entries(room.remoteParticipants)) {
|
||||
if (participant && participant.identity !== room.localParticipant?.identity) {
|
||||
const videoTracks = [];
|
||||
|
||||
// 获取视频轨道
|
||||
if (participant.videoTracks) {
|
||||
for (const [trackId, publication] of Object.entries(participant.videoTracks)) {
|
||||
if (publication && publication.track) {
|
||||
videoTracks.push({
|
||||
sid: publication.trackSid,
|
||||
track: publication.track,
|
||||
kind: publication.kind
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
participants.push({
|
||||
identity: participant.identity,
|
||||
videoTracks: videoTracks,
|
||||
audioEnabled: !participant.isMicrophoneEnabled,
|
||||
videoEnabled: !participant.isCameraEnabled
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remoteParticipants.value = participants;
|
||||
console.log(participants,'participants--+++--')
|
||||
} catch (error) {
|
||||
console.error('更新远程参与者列表失败:', error);
|
||||
remoteParticipants.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 附加本地视频轨道
|
||||
function attachLocalVideoTrack(track) {
|
||||
if (localVideo.value && track) {
|
||||
try {
|
||||
const element = track.attach();
|
||||
if (element && element.srcObject) {
|
||||
localVideo.value.srcObject = element.srcObject;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('附加本地视频轨道失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 附加远程视频轨道
|
||||
function attachVideoTrack(track, participantIdentity) {
|
||||
if (track && track.kind === Track.Kind.Video) {
|
||||
try {
|
||||
const element = track.attach();
|
||||
console.log('远程视频轨道附加成功:', participantIdentity);
|
||||
} catch (error) {
|
||||
console.error('附加远程视频轨道失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置远程视频引用
|
||||
function setRemoteVideo(el, videoTrack) {
|
||||
if (el && videoTrack && videoTrack.track) {
|
||||
try {
|
||||
const element = videoTrack.track.attach();
|
||||
if (element && element.srcObject) {
|
||||
el.srcObject = element.srcObject;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('设置远程视频失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开启摄像头
|
||||
async function enableCamera() {
|
||||
try {
|
||||
await room.localParticipant.setCameraEnabled(true);
|
||||
cameraEnabled.value = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('开启摄像头失败:', error);
|
||||
cameraEnabled.value = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 切换摄像头状态
|
||||
async function toggleCamera() {
|
||||
try {
|
||||
if (cameraEnabled.value) {
|
||||
await room.localParticipant.setCameraEnabled(false);
|
||||
cameraEnabled.value = false;
|
||||
ElMessage.info('摄像头已关闭');
|
||||
} else {
|
||||
await room.localParticipant.setCameraEnabled(true);
|
||||
cameraEnabled.value = true;
|
||||
ElMessage.success('摄像头已开启');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换摄像头失败:', error);
|
||||
ElMessage.error('切换摄像头失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 切换麦克风状态
|
||||
async function toggleMicrophone() {
|
||||
try {
|
||||
if (microphoneEnabled.value) {
|
||||
await room.localParticipant.setMicrophoneEnabled(false);
|
||||
microphoneEnabled.value = false;
|
||||
ElMessage.info('麦克风已关闭');
|
||||
} else {
|
||||
await room.localParticipant.setMicrophoneEnabled(true);
|
||||
microphoneEnabled.value = true;
|
||||
ElMessage.success('麦克风已开启');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换麦克风失败:', error);
|
||||
ElMessage.error('切换麦克风失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 切换屏幕共享
|
||||
async function toggleScreenShare() {
|
||||
try {
|
||||
if (isScreenSharing.value) {
|
||||
await room.localParticipant.setScreenShareEnabled(false);
|
||||
isScreenSharing.value = false;
|
||||
ElMessage.info('屏幕共享已停止');
|
||||
} else {
|
||||
await room.localParticipant.setScreenShareEnabled(true);
|
||||
isScreenSharing.value = true;
|
||||
ElMessage.success('屏幕共享已开始');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换屏幕共享失败:', error);
|
||||
ElMessage.error('切换屏幕共享失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取房间列表
|
||||
async function getRoomsList() {
|
||||
try {
|
||||
const res = await getRoomList();
|
||||
console.log('房间列表:', res);
|
||||
if (res.data.rooms) {
|
||||
rooms.tableData = res.data.rooms;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取房间列表失败:', error);
|
||||
ElMessage.error('获取房间列表失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 加入房间
|
||||
async function joinRoomBtn() {
|
||||
try {
|
||||
// 验证输入
|
||||
if (!formModel.room.trim() || !formModel.uid.trim()) {
|
||||
ElMessage.error('请填写房间名称和用户编码');
|
||||
return;
|
||||
}
|
||||
|
||||
connectionStatus.value = { type: 'info', message: '正在获取 token...' };
|
||||
|
||||
// 获取 token
|
||||
const res = await getRoomToken(formModel);
|
||||
const token = res.data.access_token;
|
||||
|
||||
if (!token) {
|
||||
throw new Error('获取 token 失败');
|
||||
}
|
||||
|
||||
connectionStatus.value = { type: 'info', message: '正在连接房间...' };
|
||||
|
||||
// 设置事件监听
|
||||
setupRoomListeners();
|
||||
|
||||
// 连接房间 - 添加更详细的配置
|
||||
await room.connect(wsURL, token, {
|
||||
// 自动订阅所有轨道
|
||||
autoSubscribe: true,
|
||||
});
|
||||
|
||||
// 连接成功后获取最新房间列表
|
||||
await getRoomsList();
|
||||
|
||||
} catch (error) {
|
||||
console.error('连接失败:', error);
|
||||
connectionStatus.value = {
|
||||
type: 'error',
|
||||
message: `连接失败: ${error.message}`
|
||||
};
|
||||
ElMessage.error(`连接失败: ${error.message}`);
|
||||
|
||||
// 重置状态
|
||||
status.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
function postMessage() {
|
||||
try {
|
||||
if (!msgDate.some.trim()) {
|
||||
ElMessage.warning('请输入消息内容');
|
||||
return;
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(JSON.stringify({
|
||||
message: msgDate.some,
|
||||
timestamp: new Date().toISOString(),
|
||||
from: formModel.uid
|
||||
}));
|
||||
|
||||
room.localParticipant.publishData(data, { reliable: true });
|
||||
ElMessage.success('消息发送成功');
|
||||
msgDate.some = '';
|
||||
|
||||
} catch (error) {
|
||||
console.error('发送消息失败:', error);
|
||||
ElMessage.error('发送消息失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 组件卸载时断开连接
|
||||
onUnmounted(() => {
|
||||
if (room) {
|
||||
room.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getRoomsList();
|
||||
setupRoomListeners();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.form {
|
||||
margin: 20px auto;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-wrapper {
|
||||
border: 2px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
background: #f5f7fa;
|
||||
min-width: 320px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.video-wrapper h3 {
|
||||
margin: 0 0 10px 0;
|
||||
text-align: center;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.video-element {
|
||||
width: 100%;
|
||||
height: 225px;
|
||||
background: #000;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.video-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.participant-info {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.video-tracks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status.success {
|
||||
background-color: #f0f9ff;
|
||||
color: #67c23a;
|
||||
border: 1px solid #b3e19d;
|
||||
}
|
||||
|
||||
.status.error {
|
||||
background-color: #fef0f0;
|
||||
color: #f56c6c;
|
||||
border: 1px solid #fbc4c4;
|
||||
}
|
||||
|
||||
.status.info {
|
||||
background-color: #f4f4f5;
|
||||
color: #909399;
|
||||
border: 1px solid #d3d4d6;
|
||||
}
|
||||
|
||||
.room-info {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #409eff;
|
||||
}
|
||||
|
||||
.room-info h4 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.room-info p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
186
src/views/conferencingRoom/utils/request.js
Normal file
186
src/views/conferencingRoom/utils/request.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import axios from "axios";
|
||||
import {
|
||||
ElNotification,
|
||||
ElMessageBox,
|
||||
ElMessage,
|
||||
} from "element-plus";
|
||||
import { tansParams } from "@/utils/ruoyi";
|
||||
import cache from "@/plugins/cache";
|
||||
import { getToken, removeToken } from "@/utils/auth";
|
||||
import router from '@/router';
|
||||
import { useMeterStore } from '@/stores/modules/meter'
|
||||
|
||||
axios.defaults.headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||||
const meterStore = useMeterStore()
|
||||
meterStore.initUdid()
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
// axios中请求配置有baseURL选项,表示请求URL公共部分
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API_livekit,
|
||||
// 超时
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// request拦截器
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
// 是否需要防止数据重复提交
|
||||
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
|
||||
if (meterStore.getSudid()) {
|
||||
config.headers["X-User-Agent"] = `gxtech/web 1.0.0: c=GxTech, udid=${meterStore.getSudid()}, sv=15.4.1, app=stt`;
|
||||
}
|
||||
// get请求映射params参数
|
||||
if (config.method === "get" && config.params) {
|
||||
let url = config.url + "?" + tansParams(config.params);
|
||||
url = url.slice(0, -1);
|
||||
config.params = {};
|
||||
config.url = url;
|
||||
}
|
||||
if (
|
||||
!isRepeatSubmit &&
|
||||
(config.method === "post" || config.method === "put")
|
||||
) {
|
||||
const requestObj = {
|
||||
url: config.url,
|
||||
data:
|
||||
typeof config.data === "object"
|
||||
? JSON.stringify(config.data)
|
||||
: config.data,
|
||||
time: new Date().getTime(),
|
||||
};
|
||||
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
|
||||
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
|
||||
if (requestSize >= limitSize) {
|
||||
console.warn(
|
||||
`[${config.url}]: ` +
|
||||
"请求数据大小超出允许的5M限制,无法进行防重复提交验证。"
|
||||
);
|
||||
return config;
|
||||
}
|
||||
const sessionObj = cache.session.getJSON("sessionObj");
|
||||
if (
|
||||
sessionObj === undefined ||
|
||||
sessionObj === null ||
|
||||
sessionObj === ""
|
||||
) {
|
||||
cache.session.setJSON("sessionObj", requestObj);
|
||||
} else {
|
||||
const s_url = sessionObj.url; // 请求地址
|
||||
const s_data = sessionObj.data; // 请求数据
|
||||
const s_time = sessionObj.time; // 请求时间
|
||||
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
|
||||
if (
|
||||
s_data === requestObj.data &&
|
||||
requestObj.time - s_time < interval &&
|
||||
s_url === requestObj.url
|
||||
) {
|
||||
const message = "数据正在处理,请勿重复提交";
|
||||
console.warn(`[${s_url}]: ` + message);
|
||||
return Promise.reject(new Error(message));
|
||||
} else {
|
||||
cache.session.setJSON("sessionObj", requestObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
// 1. 检查响应是否存在
|
||||
if (!response) {
|
||||
ElMessage.error('无响应数据');
|
||||
return Promise.reject(new Error('无响应数据'));
|
||||
}
|
||||
// 2. 安全获取响应数据和状态码
|
||||
const responseData = response.data || {};
|
||||
const statusCode = response.status;
|
||||
const businessCode = responseData.meta?.code || statusCode;
|
||||
|
||||
// 3. 二进制数据直接返回
|
||||
if (
|
||||
response.request.responseType === 'blob' ||
|
||||
response.request.responseType === 'arraybuffer'
|
||||
) {
|
||||
return responseData;
|
||||
}
|
||||
|
||||
// 4. 根据业务码处理不同情况
|
||||
switch (businessCode) {
|
||||
case 200:
|
||||
case 201:
|
||||
return Promise.resolve(responseData);
|
||||
|
||||
case 401:
|
||||
console.log('未授权', responseData)
|
||||
return Promise.resolve(responseData);
|
||||
// return handleUnauthorized().then(() => {
|
||||
// return Promise.reject({ code: 401, message: '未授权' });
|
||||
// });
|
||||
|
||||
case 500:
|
||||
const serverErrorMsg = responseData.meta?.message || '服务器内部错误';
|
||||
ElMessage({ message: serverErrorMsg, type: 'error' });
|
||||
return Promise.reject({ code: 500, message: serverErrorMsg });
|
||||
|
||||
default:
|
||||
const errorMsg = responseData.meta?.message || `业务错误 (${businessCode})`;
|
||||
ElNotification.error({ title: errorMsg });
|
||||
return Promise.reject({ code: businessCode, message: errorMsg });
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
let { message } = error;
|
||||
let code = error?.response?.status || -1;
|
||||
if (message == 'Network Error') {
|
||||
message = '后端接口连接异常';
|
||||
ElMessage({ message, type: 'error', duration: 5 * 1000 });
|
||||
} else if (message.includes('timeout')) {
|
||||
message = '系统接口请求超时';
|
||||
ElMessage({ message, type: 'error', duration: 5 * 1000 });
|
||||
} else if (message.includes('Request failed with status code')) {
|
||||
// message = '系统接口' + message.substr(message.length - 3) + '异常';
|
||||
}
|
||||
|
||||
// 返回结构化错误
|
||||
return Promise.reject({
|
||||
code,
|
||||
message,
|
||||
raw: error // 保留原始 error
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// 单独处理401未授权
|
||||
function handleUnauthorized() {
|
||||
return ElMessageBox.confirm(
|
||||
'认证信息已失效,您可以继续留在该页面,或者重新登录',
|
||||
'系统提示',
|
||||
{
|
||||
confirmButtonText: '重新登录',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
removeToken()
|
||||
if (router.currentRoute.path !== '/login') {
|
||||
router.push({
|
||||
path: '/login',
|
||||
query: { redirect: router.currentRoute.fullPath }
|
||||
});
|
||||
} else {
|
||||
// 如果在登录页,强制刷新以清除残留状态
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
return Promise.reject('用户取消操作');
|
||||
});
|
||||
}
|
||||
|
||||
export default service;
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- v-loading="leftListLoading || loading" -->
|
||||
<div class="left-list" v-loading="leftListLoading || loading">
|
||||
<div class="left-list" >
|
||||
<div class="list-tab">
|
||||
<div
|
||||
:class="'list-tab-item ' + (leftTab == 1 ? 'pitch-on' : '')"
|
||||
@@ -314,7 +314,7 @@ const HandleLoadNode = async (node, resolve) => {
|
||||
|
||||
const loadNode = async(resolve,id)=>{
|
||||
try {
|
||||
state.leftListLoading = true
|
||||
// state.leftListLoading = true
|
||||
if(!id){
|
||||
let res = await getDirectories({level:1})
|
||||
resolve(res.data)
|
||||
@@ -322,10 +322,10 @@ const loadNode = async(resolve,id)=>{
|
||||
let res = await getDirectoriesUsers(id,{directory_uuid:id})
|
||||
resolve(res.data)
|
||||
}
|
||||
state.leftListLoading = false
|
||||
// state.leftListLoading = false
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
state.leftListLoading = false
|
||||
// state.leftListLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,6 +543,8 @@ const {
|
||||
@extend .flex;
|
||||
justify-content: flex-start;
|
||||
height: 45px;
|
||||
font-size: 18px;
|
||||
|
||||
|
||||
.tree-item-img1 {
|
||||
width: 22px;
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
<span>所属部门</span>
|
||||
</div>
|
||||
<div class="user-information-text">
|
||||
{{ detail.dept?.deptName || '暂无' }}
|
||||
{{ detail.organization || '暂无' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -217,7 +217,7 @@
|
||||
<el-button type="primary" @click="clickInitiate">
|
||||
发起协作
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="message-null">
|
||||
@@ -322,6 +322,7 @@ const clickDetail = (item) => {
|
||||
|
||||
/** 发起协作邀请 */
|
||||
const clickInitiate = () => {
|
||||
console.log('发起协作邀请')
|
||||
// state.loadText = '初始化协作房间中'
|
||||
// state.load = true
|
||||
// getUserStatus({ userIds: state.detail.userId })
|
||||
|
||||
@@ -118,15 +118,16 @@ function getCanvasSize(container) {
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await getInfo("self");
|
||||
showLogin.value = false;
|
||||
} catch (err) {
|
||||
if (err.code === 401) {
|
||||
const res = await getInfo("self");
|
||||
console.log(res, "用户信息校验")
|
||||
if (res.meta.code === 401) {
|
||||
showLogin.value = true;
|
||||
} else {
|
||||
showLogin.value = false;
|
||||
console.warn("⚠️ 用户信息校验失败:", err);
|
||||
showLogin.value = false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("⚠️ 用户信息校验失败:", err);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -112,21 +112,19 @@ function requestNotificationPermission() {
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loginView.value = true
|
||||
const arr = await getInfo("self");
|
||||
console.log(arr,'身份信息')
|
||||
const res = await getInfo("self");
|
||||
showLogin.value = false;
|
||||
router.push({
|
||||
if (res.meta.code === 401) {
|
||||
showLogin.value = true;
|
||||
} else {
|
||||
router.push({
|
||||
path: '/whiteboard',
|
||||
query: { room_uid: 'nxst-ok4j' }
|
||||
})
|
||||
})
|
||||
}
|
||||
loginView.value = false
|
||||
} catch (err) {
|
||||
if (err.code === 401) {
|
||||
showLogin.value = true;
|
||||
} else {
|
||||
showLogin.value = false;
|
||||
console.warn("⚠️ 用户信息校验失败:", err);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("⚠️ 用户信息校验失败:", err);
|
||||
} finally {
|
||||
loginView.value = false
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export default defineConfig({
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
host: '0.0.0.0', // 关键配置,允许局域网访问
|
||||
port: 3000,
|
||||
open: true,
|
||||
hmr: { overlay: false },
|
||||
@@ -24,6 +25,13 @@ export default defineConfig({
|
||||
ws: true,
|
||||
rewrite: (path) =>
|
||||
path.replace(new RegExp(`^/dev-api`), '')
|
||||
},
|
||||
'/livekit-api': {
|
||||
target: 'https://meeting.cnsdt.com/api/v1', // 从环境变量读取
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: (path) =>
|
||||
path.replace(new RegExp(`^/livekit-api`), '')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user