feat:更新协作信息
This commit is contained in:
@@ -8,7 +8,7 @@ onMounted(() => {
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('App.vue: Pinia 初始化中...', error)
|
console.warn('App.vue: Pinia 初始化中...', error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -128,4 +128,13 @@ export function getParticipantsApi(roomId) {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 获取当前房间中视频
|
||||||
|
export function getvideoUrlApi(room_uid) {
|
||||||
|
return request({
|
||||||
|
url: `/api/v1/room/${ room_uid }/recordings`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,4 +16,23 @@ export function getDirectoriesUsers(directory_uuid,data) {
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
params:data
|
params:data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//获取参与者历史参会记录
|
||||||
|
export function getParticipantsHistoryApi(userId,data) {
|
||||||
|
return request({
|
||||||
|
url: `/api/v1/rooms/${ userId }/participants/history`,
|
||||||
|
method: 'get',
|
||||||
|
params:data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户详细信息
|
||||||
|
export function getInfo(userUid,type) {
|
||||||
|
return request({
|
||||||
|
url: `/api/v1/auth/users/${userUid}`,
|
||||||
|
method: 'get',
|
||||||
|
params:type
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
import iframeToggle from "./IframeToggle/index.vue";
|
import iframeToggle from "./IframeToggle/index.vue";
|
||||||
import useTagsViewStore from "@/stores/modules/tagsView.js";
|
import useTagsViewStore from "@/stores/modules/tagsView.js";
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore();
|
const tagsViewStore = useTagsViewStore();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
149
src/layout/components/InviteJoin/index.vue
Normal file
149
src/layout/components/InviteJoin/index.vue
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="inviteDialog"
|
||||||
|
title="远程协作"
|
||||||
|
width="400px"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:show-close="false"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div style="width: 100%; margin-bottom: 30px; font-size: 20px">
|
||||||
|
"
|
||||||
|
{{
|
||||||
|
socketInformation.room_name
|
||||||
|
? socketInformation.room_name
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
" 邀请您参加远程协作
|
||||||
|
</div>
|
||||||
|
<div style="text-align: center">
|
||||||
|
<el-button
|
||||||
|
size="large"
|
||||||
|
type="danger"
|
||||||
|
style="font-size: 16px"
|
||||||
|
@click="clickRefuseJoin"
|
||||||
|
>
|
||||||
|
拒 绝
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
size="large"
|
||||||
|
type="primary"
|
||||||
|
style="font-size: 16px"
|
||||||
|
@click="clickJoin"
|
||||||
|
>
|
||||||
|
加 入
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref ,onMounted} from 'vue'
|
||||||
|
import { getStatusApi } from '@/api/conferencingRoom.js'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { mqttClient } from "@/utils/mqtt.js";
|
||||||
|
import { useUserStore } from '@/stores/modules/user.js'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const inviteDialog = ref(false)
|
||||||
|
const socketInformation = ref(null)
|
||||||
|
|
||||||
|
|
||||||
|
/** 拒绝加入 */
|
||||||
|
const clickRefuseJoin = async () => {
|
||||||
|
//status 1: 同意加入, 5: 拒绝加入
|
||||||
|
try{
|
||||||
|
const res = await getStatusApi(socketInformation.value.room_uid,{status:5})
|
||||||
|
if(res.meta.code == 200){
|
||||||
|
ElMessage({
|
||||||
|
message: '已拒绝加入该协作',
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
inviteDialog.value = false
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error,'error')
|
||||||
|
inviteDialog.value = false
|
||||||
|
} finally {
|
||||||
|
inviteDialog.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clickJoin = async () => {
|
||||||
|
const res = await getStatusApi(socketInformation.value.room_uid,{status:1})
|
||||||
|
if(res.meta.code == 200){
|
||||||
|
ElMessage({
|
||||||
|
message: '成功加入该协作',
|
||||||
|
type: 'success',
|
||||||
|
})
|
||||||
|
inviteDialog.value = false
|
||||||
|
router.push({
|
||||||
|
path: '/conferencingRoom',
|
||||||
|
query:{
|
||||||
|
type:2,//创建房间,加入房间 2
|
||||||
|
room_uid:socketInformation.value.room_uid
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
inviteDialog.value = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 浏览器通知 */
|
||||||
|
const showNotification = (data) => {
|
||||||
|
if ('Notification' in window) {
|
||||||
|
Notification.requestPermission().then((permission) => {
|
||||||
|
if (permission === 'granted') {
|
||||||
|
const notification = new Notification('协作邀请', {
|
||||||
|
// body: String(data.room_name) + '邀请您参加远程协作'
|
||||||
|
body: '远程协作有新的邀请'
|
||||||
|
// icon: logo,
|
||||||
|
})
|
||||||
|
notification.onclick = () => { clickJoin() }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理加入房间和拒接房间 mqtt 消息 */
|
||||||
|
const processingSocket = (message) => {
|
||||||
|
const res = JSON.parse(message)
|
||||||
|
console.log(res,'收到用户信息 邀请')
|
||||||
|
if (!res?.status) {
|
||||||
|
socketInformation.value = res
|
||||||
|
inviteDialog.value = true
|
||||||
|
showNotification(socketInformation.value)
|
||||||
|
}else if(res.status == 5){
|
||||||
|
ElMessage({
|
||||||
|
message: `${res?.display_name}拒绝加入该协作`,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
processingSocket,
|
||||||
|
});
|
||||||
|
|
||||||
|
// onMounted(async () => {
|
||||||
|
// await mqttClient.connect(`room${Math.random().toString(16).substr(2, 8)}`);
|
||||||
|
// const res = await userStore.getInfo()
|
||||||
|
// const topic = `xSynergy/ROOM/+/rooms/${res.uid}`;
|
||||||
|
// mqttClient.subscribe(topic, async (shapeData) => {
|
||||||
|
// // console.log(shapeData.toString(),'shapeData发送邀请')
|
||||||
|
// processingSocket(shapeData.toString())
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -2,4 +2,5 @@ export { default as AppMain } from './AppMain.vue'
|
|||||||
export { default as Navbar } from './Navbar.vue'
|
export { default as Navbar } from './Navbar.vue'
|
||||||
export { default as Settings } from './Settings/index.vue'
|
export { default as Settings } from './Settings/index.vue'
|
||||||
export { default as TagsView } from './TagsView/index.vue'
|
export { default as TagsView } from './TagsView/index.vue'
|
||||||
export { default as ResetPwd } from './ResetPwd/index.vue'
|
export { default as ResetPwd } from './ResetPwd/index.vue'
|
||||||
|
export { default as InviteJoin } from './InviteJoin/index.vue'
|
||||||
@@ -37,23 +37,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ResetPwd ref="resetPwdRef"/>
|
<ResetPwd ref="resetPwdRef"/>
|
||||||
|
<InviteJoin ref="inviteJoinRef"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ElMessageBox } from 'element-plus'
|
import { ElMessageBox ,ElMessage} from 'element-plus'
|
||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } from '@vueuse/core'
|
||||||
import Sidebar from './components/Sidebar/index.vue'
|
import Sidebar from './components/Sidebar/index.vue'
|
||||||
import { AppMain, TagsView ,ResetPwd} from './components/index.js'
|
import { AppMain, TagsView ,ResetPwd,InviteJoin} from './components/index.js'
|
||||||
import { useAppStore } from '@/stores/modules/app.js'
|
import { useAppStore } from '@/stores/modules/app.js'
|
||||||
import { useSettingsStore } from '@/stores/modules/settings.js'
|
import { useSettingsStore } from '@/stores/modules/settings.js'
|
||||||
import { useUserStore } from '@/stores/modules/user.js'
|
import { useUserStore } from '@/stores/modules/user.js'
|
||||||
import { removeToken } from '@/utils/auth.js'
|
import { removeToken } from '@/utils/auth.js'
|
||||||
|
import { onMounted ,ref} from 'vue'
|
||||||
|
import { mqttClient } from "@/utils/mqtt.js";
|
||||||
|
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const useAppStoreStore = useAppStore()
|
const useAppStoreStore = useAppStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const inviteJoinRef = ref(null)
|
||||||
|
|
||||||
const theme = computed(() => settingsStore.theme)
|
const theme = computed(() => settingsStore.theme)
|
||||||
const sidebar = computed(() => useAppStoreStore.sidebar)
|
const sidebar = computed(() => useAppStoreStore.sidebar)
|
||||||
@@ -136,6 +141,18 @@ function handleClickOutside() {
|
|||||||
useAppStoreStore.closeSideBar({ withoutAnimation: false })
|
useAppStoreStore.closeSideBar({ withoutAnimation: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await mqttClient.connect(`room${Math.random().toString(16).substr(2, 8)}`);
|
||||||
|
const res = await userStore.getInfo()
|
||||||
|
const topic = `xSynergy/ROOM/+/rooms/${res.uid}`;
|
||||||
|
mqttClient.subscribe(topic, async (shapeData) => {
|
||||||
|
if(inviteJoinRef.value){
|
||||||
|
inviteJoinRef.value.processingSocket(shapeData.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -68,6 +68,12 @@ export const constantRoutes = [
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
meta: { title: "401未授权" }
|
meta: { title: "401未授权" }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/assistWx',
|
||||||
|
component: () => import('@/views/coordinate/personnelList/components/assistWx/index.vue'),
|
||||||
|
meta: { title: "白板" },
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const dynamicRoutes = [
|
export const dynamicRoutes = [
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export const useRoomStore = defineStore('room', {
|
|||||||
userUid: '',
|
userUid: '',
|
||||||
//邀请进入房间的用户uid
|
//邀请进入房间的用户uid
|
||||||
detailUid: '',
|
detailUid: '',
|
||||||
|
//邀请用户名称
|
||||||
|
detailName: '',
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
setUserUid(data) {
|
setUserUid(data) {
|
||||||
@@ -14,6 +16,9 @@ export const useRoomStore = defineStore('room', {
|
|||||||
},
|
},
|
||||||
setDetailUid(data) {
|
setDetailUid(data) {
|
||||||
this.detailUid = data
|
this.detailUid = data
|
||||||
|
},
|
||||||
|
setDetailName(data) {
|
||||||
|
this.detailName = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,8 @@ export const useUserStore = defineStore(
|
|||||||
async login(userInfo) {
|
async login(userInfo) {
|
||||||
try {
|
try {
|
||||||
const { username, password } = userInfo;
|
const { username, password } = userInfo;
|
||||||
const trimmedUsername = username.trim();
|
const trimmedUsername = username.trim();
|
||||||
|
const res = await login(trimmedUsername, password);
|
||||||
const res = await login(trimmedUsername, password);
|
|
||||||
if (res.meta.code !== 200) {
|
if (res.meta.code !== 200) {
|
||||||
ElMessage({ message: res.meta?.message || '登录失败', type: 'error' });
|
ElMessage({ message: res.meta?.message || '登录失败', type: 'error' });
|
||||||
return Promise.reject(res);
|
return Promise.reject(res);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
import router from '@/router';
|
||||||
const TokenKey = "token";
|
const TokenKey = "token";
|
||||||
|
|
||||||
export function getToken() {
|
export function getToken() {
|
||||||
@@ -18,3 +18,54 @@ export function removeToken() {
|
|||||||
return sessionStorage.removeItem(TokenKey);
|
return sessionStorage.removeItem(TokenKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//获取用户信息
|
||||||
|
export function getUserInfo() {
|
||||||
|
try {
|
||||||
|
const userData = sessionStorage.getItem("userData");
|
||||||
|
|
||||||
|
// 如果userData不存在,执行未授权处理
|
||||||
|
if (!userData) {
|
||||||
|
handleUnauthorized();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试解析JSON数据
|
||||||
|
try {
|
||||||
|
const parsedData = JSON.parse(userData);
|
||||||
|
return parsedData;
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error('用户数据格式错误,无法解析JSON:', parseError);
|
||||||
|
// 数据格式错误也视为未登录
|
||||||
|
sessionStorage.removeItem("userData");
|
||||||
|
handleUnauthorized();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取用户信息时发生错误:', error);
|
||||||
|
handleUnauthorized();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleUnauthorized() {
|
||||||
|
removeToken();
|
||||||
|
|
||||||
|
// 使用 nextTick 确保路由状态已更新
|
||||||
|
import('vue').then(({ nextTick }) => {
|
||||||
|
nextTick(() => {
|
||||||
|
const currentPath = router.currentRoute.value.fullPath;
|
||||||
|
if (router.currentRoute.value.path !== '/login') {
|
||||||
|
router.push({
|
||||||
|
path: '/login',
|
||||||
|
query: {
|
||||||
|
redirect: currentPath !== '/login' ? currentPath : undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -156,9 +156,10 @@ service.interceptors.response.use(
|
|||||||
// if(currentPath == 'ConferencingRoom'){
|
// if(currentPath == 'ConferencingRoom'){
|
||||||
// return Promise.resolve(responseData);
|
// return Promise.resolve(responseData);
|
||||||
// }else{
|
// }else{
|
||||||
return handleUnauthorized().then(() => {
|
return handleUnauthorized()
|
||||||
return Promise.reject({ code: 401, message: '未授权' });
|
// .then(() => {
|
||||||
});
|
// return Promise.reject({ code: 401, message: '未授权' });
|
||||||
|
// });
|
||||||
// }
|
// }
|
||||||
case 500:
|
case 500:
|
||||||
const serverErrorMsg = responseData.meta?.message || '服务器内部错误';
|
const serverErrorMsg = responseData.meta?.message || '服务器内部错误';
|
||||||
@@ -221,7 +222,7 @@ service.interceptors.response.use(
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
function handleUnauthorized() {
|
function handleUnauthorized() {
|
||||||
removeToken();
|
removeToken();
|
||||||
|
|
||||||
// 使用 nextTick 确保路由状态已更新
|
// 使用 nextTick 确保路由状态已更新
|
||||||
|
|||||||
@@ -716,3 +716,26 @@ export function removeDuplicate(arr) {
|
|||||||
});
|
});
|
||||||
return newArr; // 返回一个新数组
|
return newArr; // 返回一个新数组
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function createThrottle(func, delay){
|
||||||
|
let timeoutId;
|
||||||
|
let lastExecTime = 0;
|
||||||
|
|
||||||
|
return (...args) => {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
|
||||||
|
// 立即执行第一次
|
||||||
|
if (currentTime - lastExecTime > delay) {
|
||||||
|
func.apply(this, args);
|
||||||
|
lastExecTime = currentTime;
|
||||||
|
} else {
|
||||||
|
// 清除之前的定时器,设置新的定时器
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
func.apply(this, args);
|
||||||
|
lastExecTime = Date.now();
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -157,4 +157,4 @@ function isValidBox(x1, y1, x2, y2, imgWidth, imgHeight) {
|
|||||||
x2 > x1 && y2 > y1 &&
|
x2 > x1 && y2 > y1 &&
|
||||||
x2 <= imgWidth && y2 <= imgHeight
|
x2 <= imgWidth && y2 <= imgHeight
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ElMessage } from 'element-plus';
|
|||||||
export function errorHandling(error,type) {
|
export function errorHandling(error,type) {
|
||||||
switch (error.name) {
|
switch (error.name) {
|
||||||
case 'NotAllowedError':
|
case 'NotAllowedError':
|
||||||
ElMessage.error('用户拒绝了权限请求,请允许此网站使用摄像头');
|
ElMessage.error(`用户拒绝了权限请求,请允许此网站使用${type}权限`);
|
||||||
break;
|
break;
|
||||||
case 'NotFoundError':
|
case 'NotFoundError':
|
||||||
ElMessage.error(`未检测到可用的${type}设备,请检查${type}是否已正确连接`);
|
ElMessage.error(`未检测到可用的${type}设备,请检查${type}是否已正确连接`);
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="showLogin">
|
<div v-if="showLogin">
|
||||||
<!-- 登录界面 -->
|
<!-- 登录界面 -->
|
||||||
<Login @loginSuccess="handleLoginSuccess" />
|
<Login @loginSuccess="handleLoginSuccess" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="loading" class="loading-container">
|
||||||
|
<div class="loading-content">
|
||||||
|
<el-icon class="loading-icon"><Loading /></el-icon>
|
||||||
|
<p>正在创建房间,请稍候...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 音频元素 -->
|
<!-- 音频元素 -->
|
||||||
<audio ref="localAudio" autoplay muted class="audio-element"></audio>
|
<audio ref="localAudio" autoplay muted class="audio-element"></audio>
|
||||||
<div id="audio"></div>
|
<div id="audio"></div>
|
||||||
@@ -100,8 +108,7 @@
|
|||||||
:ref="el => setScreenShareVideoRef(el)"
|
:ref="el => setScreenShareVideoRef(el)"
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
class="screen-share-element"
|
class="screen-share-element"
|
||||||
style='border:1px solid red'
|
|
||||||
@loadedmetadata="handleScreenShareLoaded">
|
@loadedmetadata="handleScreenShareLoaded">
|
||||||
</video>
|
</video>
|
||||||
<!-- 如果没有屏幕共享,显示提示 -->
|
<!-- 如果没有屏幕共享,显示提示 -->
|
||||||
@@ -445,7 +452,7 @@ const enlargedLaserPointerCanvas = ref(null); // 放大视频激光笔 Canvas
|
|||||||
const enlargedLaserPointerContext = ref(null); // 放大视频激光笔上下文
|
const enlargedLaserPointerContext = ref(null); // 放大视频激光笔上下文
|
||||||
|
|
||||||
const lastActivatedLayer = ref(''); // 记录最后激活的图层:'screenVideo' 或 'whiteboard'
|
const lastActivatedLayer = ref(''); // 记录最后激活的图层:'screenVideo' 或 'whiteboard'
|
||||||
|
const loading = ref(false);
|
||||||
// 路径更新节流处理
|
// 路径更新节流处理
|
||||||
let lastPublishTime = 0;
|
let lastPublishTime = 0;
|
||||||
const publishThrottleTime = 100; // 100ms 节流
|
const publishThrottleTime = 100; // 100ms 节流
|
||||||
@@ -1934,9 +1941,12 @@ async function handleConfirmSelection(userInfo){
|
|||||||
if(userInfo.length < 0){
|
if(userInfo.length < 0){
|
||||||
ElMessage.error('请选择加入房间的人员')
|
ElMessage.error('请选择加入房间的人员')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const joinUserIds = userInfo.map(item => item.uid)
|
const joinUserInfo = userInfo.map(item => ({
|
||||||
await getInvite(room.name,{user_uids:joinUserIds, participant_role: "participant"})
|
user_uid: item.uid,
|
||||||
|
display_name: item.name
|
||||||
|
}));
|
||||||
|
await getInvite(room.name,{participants:joinUserInfo, participant_role: "participant"})
|
||||||
}
|
}
|
||||||
|
|
||||||
function publishWhiteboardMessage(type, payload = {}) {
|
function publishWhiteboardMessage(type, payload = {}) {
|
||||||
@@ -2832,9 +2842,13 @@ function handleScreenShareEnded() {
|
|||||||
|
|
||||||
async function joinRoomBtn() {
|
async function joinRoomBtn() {
|
||||||
try {
|
try {
|
||||||
|
loading.value = true; // 开始加载
|
||||||
|
status.value = true; // 确保显示加载状态
|
||||||
|
|
||||||
const res = await getRoomToken({max_participants: 20});
|
const res = await getRoomToken({max_participants: 20});
|
||||||
if(res.meta.code != 200){
|
if(res.meta.code != 200){
|
||||||
ElMessage.error(res.meta.message);
|
ElMessage.error(res.meta.message);
|
||||||
|
loading.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const token = res.data.access_token;
|
const token = res.data.access_token;
|
||||||
@@ -2849,6 +2863,8 @@ async function joinRoomBtn() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(`连接失败: ${error.message}`);
|
ElMessage.error(`连接失败: ${error.message}`);
|
||||||
status.value = true;
|
status.value = true;
|
||||||
|
} finally {
|
||||||
|
loading.value = false; // 无论成功失败都结束加载
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3094,7 +3110,7 @@ onMounted(async () => {
|
|||||||
hostUid.value = roomStore.userUid
|
hostUid.value = roomStore.userUid
|
||||||
// 邀请用户参与房间
|
// 邀请用户参与房间
|
||||||
if(room.name){
|
if(room.name){
|
||||||
await getInvite(room.name,{user_uids:[roomStore.detailUid], participant_role: "participant"})
|
await getInvite(room.name,{participants:[{user_uid:roomStore.detailUid,display_name:roomStore.detailName}], participant_role: "participant"})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const res = await getTokenApi(route.query.room_uid)
|
const res = await getTokenApi(route.query.room_uid)
|
||||||
@@ -3115,6 +3131,42 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.loading-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(15, 15, 26, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-content {
|
||||||
|
text-align: center;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #409eff;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-content p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #a0a0b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
/* 内容层级容器 */
|
/* 内容层级容器 */
|
||||||
.content-layers-container {
|
.content-layers-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -3145,7 +3197,7 @@ onMounted(async () => {
|
|||||||
.enlarged-video-wrapper,
|
.enlarged-video-wrapper,
|
||||||
.screen-share-wrapper {
|
.screen-share-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enlarged-video-element,
|
.enlarged-video-element,
|
||||||
@@ -3438,7 +3490,7 @@ body {
|
|||||||
}
|
}
|
||||||
.screen-share-element {
|
.screen-share-element {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 96%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
background: #000;
|
background: #000;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -3733,7 +3785,7 @@ body {
|
|||||||
}
|
}
|
||||||
.microphone-control-group .dropdown-btn {
|
.microphone-control-group .dropdown-btn {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
.controls-container {
|
.controls-container {
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
|||||||
@@ -1108,7 +1108,7 @@ async function handleConfirmSelection(userInfo){
|
|||||||
ElMessage.error('请选择加入房间的人员')
|
ElMessage.error('请选择加入房间的人员')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const joinUserIds = userInfo.map(item => item.uid)
|
const joinUserIds = userInfo.map(item => item.uid)
|
||||||
await getInvite(room.name,{user_uids:joinUserIds, participant_role: "participant"})
|
await getInvite(room.name,{user_uids:joinUserIds, participant_role: "participant"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,236 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wrapper-content">
|
<div>
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogFormVisible"
|
||||||
|
width="80%"
|
||||||
|
:show-close="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:modal="false"
|
||||||
|
:lock-scroll="true"
|
||||||
|
:before-close="handleClose"
|
||||||
|
class="call-dialog"
|
||||||
|
>
|
||||||
|
<div class="call-wrapper">
|
||||||
|
<!-- 上方头像与状态 -->
|
||||||
|
<div class="avatar-section">
|
||||||
|
<img class="avatar" :src="avatarUrl" alt="头像" />
|
||||||
|
<div class="user-name">{{ userName }}</div>
|
||||||
|
<div class="status-text">{{ statusText }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部操作按钮 -->
|
||||||
|
<div class="control-section">
|
||||||
|
<!-- 呼叫模式 -->
|
||||||
|
<template v-if="mode === 'call'">
|
||||||
|
<el-button
|
||||||
|
v-if="callStatus === 'calling'"
|
||||||
|
type="danger"
|
||||||
|
round
|
||||||
|
class="control-btn hangup"
|
||||||
|
@click="hangup"
|
||||||
|
>
|
||||||
|
挂断
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-else
|
||||||
|
type="primary"
|
||||||
|
round
|
||||||
|
class="control-btn call"
|
||||||
|
@click="startCall"
|
||||||
|
>
|
||||||
|
呼叫中...
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 接听模式 -->
|
||||||
|
<template v-else>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
round
|
||||||
|
class="control-btn accept"
|
||||||
|
@click="acceptCall"
|
||||||
|
>
|
||||||
|
接听
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
round
|
||||||
|
class="control-btn hangup"
|
||||||
|
@click="hangup"
|
||||||
|
>
|
||||||
|
挂断
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, nextTick, onUnmounted, onMounted } from "vue";
|
|
||||||
import { ElLoading, ElMessage } from "element-plus";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import { mqttClient } from "@/utils/mqtt";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
roomId: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
|
|
||||||
})
|
<script setup>
|
||||||
const route = useRoute();
|
import { ref, computed } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
onMounted(async () => {
|
const props = defineProps({
|
||||||
|
mode: { type: String, default: "call" }, // call: 呼叫模式, receive: 接听模式
|
||||||
});
|
avatarUrl: {
|
||||||
|
type: String,
|
||||||
onUnmounted(() => {
|
default: "https://cdn-icons-png.flaticon.com/512/1946/1946429.png",
|
||||||
|
},
|
||||||
});
|
userName: { type: String, default: "对方用户" },
|
||||||
</script>
|
});
|
||||||
|
|
||||||
<style scoped>
|
const dialogFormVisible = ref(false);
|
||||||
|
const callStatus = ref("calling"); // calling | active | ended
|
||||||
</style>
|
|
||||||
|
const statusText = computed(() => {
|
||||||
|
if (props.mode === "call") {
|
||||||
|
if (callStatus.value === "calling") return "正在呼叫对方...";
|
||||||
|
if (callStatus.value === "active") return "通话中";
|
||||||
|
return "通话结束";
|
||||||
|
} else {
|
||||||
|
return callStatus.value === "active" ? "通话中" : "对方来电...";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function startCall() {
|
||||||
|
callStatus.value = "active";
|
||||||
|
ElMessage.success("开始通话");
|
||||||
|
}
|
||||||
|
|
||||||
|
function acceptCall() {
|
||||||
|
callStatus.value = "active";
|
||||||
|
ElMessage.success("已接听");
|
||||||
|
}
|
||||||
|
|
||||||
|
function hangup() {
|
||||||
|
callStatus.value = "ended";
|
||||||
|
ElMessage.error("通话已结束");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
dialogFormVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
dialogFormVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* ::v-deep .el-dialog__header{
|
||||||
|
background-color:red;
|
||||||
|
} */
|
||||||
|
/* 去除 el-dialog 默认样式,让它全屏覆盖且透明 */
|
||||||
|
.call-dialog :deep(.el-dialog) {
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
height: calc(100vh - 30vh);
|
||||||
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-dialog :deep(.el-dialog__body) {
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通话主背景 */
|
||||||
|
.call-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: calc(100vh - 30vh);
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(180deg, #0f2027, #203a43, #2c5364);
|
||||||
|
color: #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 80px 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头像部分 */
|
||||||
|
.avatar-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 6px solid rgba(255, 255, 255, 0.25);
|
||||||
|
box-shadow: 0 0 40px rgba(0, 0, 0, 0.4);
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
margin-top: 24px;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 控制按钮部分 */
|
||||||
|
.control-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn {
|
||||||
|
width: 160px;
|
||||||
|
height: 60px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn.accept {
|
||||||
|
background: linear-gradient(135deg, #00c853, #4caf50);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn.hangup {
|
||||||
|
background: linear-gradient(135deg, #ff1744, #d50000);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn.call {
|
||||||
|
background: linear-gradient(135deg, #2196f3, #1976d2);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -19,14 +19,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-content">
|
<div class="list-content">
|
||||||
<div class="content-top-input">
|
<div class="content-top-input" v-if="leftTab == 1">
|
||||||
|
<!-- <el-input
|
||||||
|
v-model="queryFrom.nickName"
|
||||||
|
placeholder="搜索成员"
|
||||||
|
type="text"
|
||||||
|
prefix-icon="Search"
|
||||||
|
@change="searchList"
|
||||||
|
/> -->
|
||||||
|
<el-select
|
||||||
|
v-model="participant_user"
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
remote
|
||||||
|
reserve-keyword
|
||||||
|
placeholder="搜索成员"
|
||||||
|
:remote-method="remoteMethod"
|
||||||
|
:loading="loading"
|
||||||
|
collapse-tags
|
||||||
|
collapse-tags-tooltip
|
||||||
|
style="width: 100%"
|
||||||
|
@change="searchList"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in userList"
|
||||||
|
:key="item.uid"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.uid"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="content-top-input" v-if="leftTab == 2">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryFrom.nickName"
|
v-model="queryFrom.nickName"
|
||||||
placeholder="搜索成员"
|
placeholder="搜索成员"
|
||||||
type="text"
|
type="text"
|
||||||
prefix-icon="Search"
|
prefix-icon="Search"
|
||||||
@change="searchList"
|
@change="searchList"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-datapicker" v-if="leftTab == 1">
|
<div class="content-datapicker" v-if="leftTab == 1">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
@@ -47,49 +78,50 @@
|
|||||||
v-infinite-scroll="infinite"
|
v-infinite-scroll="infinite"
|
||||||
v-if="dataList?.length"
|
v-if="dataList?.length"
|
||||||
>
|
>
|
||||||
|
<!-- @click="updateDetail(item)" -->
|
||||||
<div
|
<div
|
||||||
v-for="(item, index) in dataList"
|
v-for="(item, index) in dataList"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="content-list-item"
|
class="content-list-item"
|
||||||
@click="updateDetail(item)"
|
@click="updateDetail(item)"
|
||||||
:style="
|
:style="
|
||||||
item.assistanceId == assistanceId
|
item.id == assistanceId
|
||||||
? 'border-color: #409EFF; '
|
? 'border-color: #409EFF; '
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div class="list-item-top">
|
<div class="list-item-top">
|
||||||
<span>
|
<span>
|
||||||
{{ parseTime(item.beginTime, '{m}月{d}日') }}
|
{{ parseTime(item.created_at, '{m}月{d}日') }}
|
||||||
{{ weekName[new Date(item.beginTime).getDay()] }}
|
{{ weekName[new Date(item.created_at).getDay()] }}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
{{ parseTime(item.beginTime, '{y}年') }}
|
{{ parseTime(item.created_at, '{y}年') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-item-content">
|
<div class="list-item-content">
|
||||||
<div class="list-item-content-text">
|
<div class="list-item-content-text">
|
||||||
<div style="display: flex; flex-wrap: wrap">
|
<div style="display: flex; flex-wrap: wrap">
|
||||||
<span
|
<span
|
||||||
v-for="(items, indexs) in item.assistanceMemberList"
|
v-for="(items, indexs) in item.all_participants"
|
||||||
:key="indexs"
|
:key="indexs"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
items.nickName +
|
items.display_name +
|
||||||
(indexs + 1 == item.assistanceMemberList.length
|
(indexs + 1 == item.all_participants.length
|
||||||
? ''
|
? ''
|
||||||
: '、')
|
: '、')
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span>
|
<span>
|
||||||
发起人:{{ item.initiatorName ? item.initiatorName : '' }}
|
发起人:{{ item.all_participants.find(item => item.participant_role == 'moderator')?.display_name || ''}}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
时间:{{
|
时间:{{
|
||||||
parseTime(item.beginTime, '{h}:{i}') +
|
parseTime(item.created_at, '{h}:{i}') +
|
||||||
' ~ ' +
|
' ~ ' +
|
||||||
(item.endTime ? parseTime(item.endTime, '{h}:{i}') : '')
|
(item.updated_at ? parseTime(item.updated_at, '{h}:{i}') : '')
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,10 +170,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
// getAssistanceList,
|
// getAssistanceList,
|
||||||
|
getParticipantsHistoryApi,
|
||||||
getDirectories,
|
getDirectories,
|
||||||
getDirectoriesUsers
|
getDirectoriesUsers,
|
||||||
|
getInfo,
|
||||||
} from '@/api/coordinate.js'
|
} from '@/api/coordinate.js'
|
||||||
import { nextTick, reactive, toRefs, watch, onMounted } from 'vue'
|
import { nextTick, reactive, toRefs, watch, onMounted } from 'vue'
|
||||||
|
import { deepClone,parseTime ,createThrottle} from '@/utils/ruoyi.js'
|
||||||
|
import { getUserInfo } from '@/utils/auth.js'
|
||||||
|
|
||||||
// 接收 props
|
// 接收 props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -157,13 +193,15 @@ const emit = defineEmits(['updateDetail', 'updateTab'])
|
|||||||
// state
|
// state
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
isFirst: true,
|
isFirst: true,
|
||||||
leftTab: 2,
|
leftTab: 1,
|
||||||
queryFrom: {
|
queryFrom: {
|
||||||
pageNum: 1,
|
page_size: 10,
|
||||||
pageSize: 10,
|
page: 1,
|
||||||
nickName: '',
|
nickName: '',
|
||||||
leftDatePicker: null,
|
leftDatePicker: null,
|
||||||
|
participant_user_uids:''
|
||||||
},
|
},
|
||||||
|
participant_user:[],
|
||||||
leftListLoading: true,
|
leftListLoading: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
dataList: [],
|
dataList: [],
|
||||||
@@ -216,7 +254,22 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
assistanceId: '',
|
assistanceId: '',
|
||||||
})
|
userList:[],
|
||||||
|
})
|
||||||
|
// state.loading = true;
|
||||||
|
const remoteMethod = createThrottle(async (query) => {
|
||||||
|
if (!query || query.trim() === '') {
|
||||||
|
state.userList = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await getInfo(query, { search_type: 'name' });
|
||||||
|
state.userList = res.data || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('搜索用户失败:', error);
|
||||||
|
state.userList = [];
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 树状列表筛选
|
* 树状列表筛选
|
||||||
@@ -231,11 +284,11 @@ const filterNode = (value, data) => {
|
|||||||
*/
|
*/
|
||||||
const searchList = () => {
|
const searchList = () => {
|
||||||
if (state.leftTab == 1) {
|
if (state.leftTab == 1) {
|
||||||
state.queryFrom.pageNum = 1
|
state.queryFrom.page = 1
|
||||||
state.dataList = []
|
state.dataList = []
|
||||||
|
state.queryFrom.participant_user_uids = state.participant_user.join(',')
|
||||||
getList()
|
getList()
|
||||||
} else {
|
} else {
|
||||||
// console.log('treeRef.filter',state.treeRef)
|
|
||||||
state.treeRef.filter(state.queryFrom.nickName)
|
state.treeRef.filter(state.queryFrom.nickName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,8 +297,8 @@ const searchList = () => {
|
|||||||
* 变更详情
|
* 变更详情
|
||||||
*/
|
*/
|
||||||
const updateDetail = (item) => {
|
const updateDetail = (item) => {
|
||||||
if (state.leftTab == 1) {
|
if (state.leftTab == 1) {
|
||||||
state.assistanceId = item.assistanceId
|
state.assistanceId = item.id
|
||||||
emit('updateDetail', item)
|
emit('updateDetail', item)
|
||||||
} else {
|
} else {
|
||||||
if (item.uid) {
|
if (item.uid) {
|
||||||
@@ -257,43 +310,63 @@ const updateDetail = (item) => {
|
|||||||
/**
|
/**
|
||||||
* 触底加载
|
* 触底加载
|
||||||
*/
|
*/
|
||||||
// const infinite = () => {
|
const infinite = () => {
|
||||||
// if (state.more) {
|
if (state.more) {
|
||||||
// state.queryFrom.pageNum++
|
state.queryFrom.page++
|
||||||
// getList()
|
getList()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 协作记录
|
* 协作记录
|
||||||
*/
|
*/
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
try {
|
try {
|
||||||
state.leftListLoading = true
|
state.leftListLoading = true
|
||||||
|
let query = deepClone(state.queryFrom)
|
||||||
|
// if (query.leftDatePicker?.length) {
|
||||||
|
// query.beginSignTime = query.leftDatePicker[0]
|
||||||
|
// query.endSignTime = query.leftDatePicker[1]
|
||||||
|
// }
|
||||||
|
|
||||||
let query = structuredClone(state.queryFrom)
|
if (query.leftDatePicker?.length) {
|
||||||
if (query.leftDatePicker?.length) {
|
const startTime = new Date(query.leftDatePicker[0]);
|
||||||
query.beginSignTime = query.leftDatePicker[0]
|
const endTime = new Date(query.leftDatePicker[1]);
|
||||||
query.endSignTime = query.leftDatePicker[1]
|
|
||||||
|
// 添加日期有效性验证
|
||||||
|
if (!isNaN(startTime.getTime())) {
|
||||||
|
query.start_time = Math.floor(startTime.getTime());
|
||||||
|
} else {
|
||||||
|
console.error('开始时间格式无效:', query.leftDatePicker[0]);
|
||||||
|
query.start_time = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNaN(endTime.getTime())) {
|
||||||
|
query.end_time = Math.floor(endTime.getTime());
|
||||||
|
} else {
|
||||||
|
console.error('结束时间格式无效:', query.leftDatePicker[1]);
|
||||||
|
query.end_time = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delete query.leftDatePicker
|
|
||||||
|
|
||||||
let infoData = await getAssistanceList({ ...query })
|
delete query.leftDatePicker
|
||||||
|
const userData = await getUserInfo()
|
||||||
state.dataList = infoData.rows.length
|
if(!userData) return
|
||||||
? state.dataList.concat(infoData.rows)
|
let infoData = await getParticipantsHistoryApi( userData?.uid ,{ ...query })
|
||||||
|
state.dataList = infoData.data.history?.length
|
||||||
|
? state.dataList.concat(infoData.data.history)
|
||||||
: []
|
: []
|
||||||
|
|
||||||
if (state.isFirst) {
|
if (state.isFirst) {
|
||||||
emit('updateDetail', state.dataList.length ? state.dataList[0] : null)
|
emit('updateDetail', state.dataList?.length ? state.dataList[0] : null)
|
||||||
state.assistanceId = state.dataList.length
|
state.assistanceId = state.dataList?.length
|
||||||
? state.dataList[0].assistanceId
|
? state.dataList[0].id
|
||||||
: ''
|
: ''
|
||||||
state.isFirst = false
|
state.isFirst = false
|
||||||
}
|
}
|
||||||
|
|
||||||
state.more = state.dataList.length < infoData.total
|
state.more = state.dataList?.length < infoData.data.total
|
||||||
state.isShow = Boolean(state.dataList.length)
|
state.isShow = Boolean(state.dataList?.length)
|
||||||
state.leftListLoading = false
|
state.leftListLoading = false
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
@@ -350,20 +423,7 @@ const loadUserNode = async(resolve,id,level)=>{
|
|||||||
console.log(error)
|
console.log(error)
|
||||||
state.leftListLoading = false
|
state.leftListLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// try {
|
|
||||||
// if (userList.data.sub_units?.length) {
|
|
||||||
// state.dataList = userList.data.sub_units
|
|
||||||
// let user = getUser(state.dataList)
|
|
||||||
// emit('updateDetail', user)
|
|
||||||
// } else {
|
|
||||||
// emit('updateDetail', null)
|
|
||||||
// }
|
|
||||||
// state.leftListLoading = false
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getUser = (list) => {
|
const getUser = (list) => {
|
||||||
for (let i = 0; i < list.length; i++) {
|
for (let i = 0; i < list.length; i++) {
|
||||||
@@ -398,15 +458,16 @@ watch(
|
|||||||
state.dataList = []
|
state.dataList = []
|
||||||
state.isFirst = true
|
state.isFirst = true
|
||||||
state.queryFrom = {
|
state.queryFrom = {
|
||||||
pageNum: 1,
|
page_size: 10,
|
||||||
pageSize: 10,
|
page: 1,
|
||||||
nickName: '',
|
nickName: '',
|
||||||
|
participant_user_uids:'',
|
||||||
leftDatePicker: null,
|
leftDatePicker: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue == 1) {
|
if (newValue == 1) {
|
||||||
state.isShow = false
|
state.isShow = false
|
||||||
// getList()
|
getList()
|
||||||
} else {
|
} else {
|
||||||
HandleLoadNode()
|
HandleLoadNode()
|
||||||
}
|
}
|
||||||
@@ -416,7 +477,7 @@ watch(
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
state.dataList = []
|
state.dataList = []
|
||||||
state.isFirst = true
|
state.isFirst = true
|
||||||
// getList()
|
getList()
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -425,7 +486,7 @@ onMounted(() => {
|
|||||||
const {
|
const {
|
||||||
isFirst, leftTab, queryFrom, leftListLoading, loading,
|
isFirst, leftTab, queryFrom, leftListLoading, loading,
|
||||||
dataList, more, isShow, shortcuts, weekName,
|
dataList, more, isShow, shortcuts, weekName,
|
||||||
treeRef, treeProps, assistanceId
|
treeRef, treeProps, assistanceId,userList,participant_user
|
||||||
} = toRefs(state)
|
} = toRefs(state)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,278 +1,278 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="app-container" v-loading="load" :element-loading-text="loadText">
|
<div class="app-container" v-loading="load" :element-loading-text="loadText">
|
||||||
<el-row :gutter="6" style="padding: 0 10px; background: #fefefe">
|
<el-row :gutter="6" style="padding: 0 10px; background: #fefefe">
|
||||||
<el-col :xs="24" :sm="24" :md="8" :lg="6">
|
<el-col :xs="24" :sm="24" :md="8" :lg="6">
|
||||||
<leftTab
|
<leftTab
|
||||||
@updateDetail="updateDetail"
|
@updateDetail="updateDetail"
|
||||||
@updateTab="updateTab"
|
@updateTab="updateTab"
|
||||||
:loading="!detail?.appId && !detail?.userId && isShow"
|
:loading="!detail?.appId && !detail?.userId && isShow"
|
||||||
/>
|
/>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :xs="24" :sm="24" :md="16" :lg="18">
|
<el-col :xs="24" :sm="24" :md="16" :lg="18">
|
||||||
<div
|
|
||||||
class="right-content"
|
|
||||||
|
|
||||||
>
|
|
||||||
<!-- v-loading="!detail?.appId && !detail?.userId && isShow" -->
|
|
||||||
<div class="right-content-title">
|
|
||||||
{{ tabValue == 1 ? '协作信息' : '员工信息' }}
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="agency-detail-massage-cont right-content-message"
|
class="right-content"
|
||||||
v-if="isShow && tabValue == 1"
|
v-loading='isShowLoading'
|
||||||
>
|
>
|
||||||
<div class="agency-detail-cont-item">
|
<div class="right-content-title">
|
||||||
<span class="agency-detail-item-title">发起人</span>
|
{{ tabValue == 1 ? '协作信息' : '员工信息' }}
|
||||||
<span class="agency-detail-item-content">
|
</div>
|
||||||
{{ detail.initiatorName }}
|
<div
|
||||||
</span>
|
class="agency-detail-massage-cont right-content-message"
|
||||||
</div>
|
v-if="isShow && tabValue == 1"
|
||||||
<div class="agency-detail-cont-item">
|
|
||||||
<span class="agency-detail-item-title">协作时间</span>
|
|
||||||
<span class="agency-detail-item-content">
|
|
||||||
<!-- {{
|
|
||||||
parseTime(detail.beginTime, '{y}年{m}月{d}日') +
|
|
||||||
' ' +
|
|
||||||
weekName[new Date(detail.beginTime).getDay()] +
|
|
||||||
' ' +
|
|
||||||
parseTime(detail.beginTime, '{h}:{i}')
|
|
||||||
}} -->
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="agency-detail-cont-item">
|
|
||||||
<span class="agency-detail-item-title">成员</span>
|
|
||||||
<span
|
|
||||||
class="agency-detail-item-content"
|
|
||||||
v-if="detail?.assistanceMemberList?.length"
|
|
||||||
style="display: flex; flex-wrap: wrap"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-for="(items, indexs) in detail.assistanceMemberList"
|
|
||||||
:key="indexs"
|
|
||||||
>
|
>
|
||||||
|
<div class="agency-detail-cont-item">
|
||||||
|
<span class="agency-detail-item-title">发起人</span>
|
||||||
|
<span class="agency-detail-item-content">
|
||||||
|
{{ detail.initiator }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="agency-detail-cont-item">
|
||||||
|
<span class="agency-detail-item-title">协作时间</span>
|
||||||
|
<span class="agency-detail-item-content">
|
||||||
{{
|
{{
|
||||||
items.nickName +
|
detail.created_at ?
|
||||||
(indexs + 1 == detail.assistanceMemberList.length
|
parseTime(detail.created_at, '{y}年{m}月{d}日') +
|
||||||
? ''
|
' ' +
|
||||||
: '、')
|
weekName[new Date(detail.created_at).getDay()] +
|
||||||
|
' ' +
|
||||||
|
parseTime(detail.created_at, '{h}:{i}')
|
||||||
|
: '暂无'
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="agency-detail-cont-item">
|
|
||||||
<span class="agency-detail-item-title">协作时长</span>
|
|
||||||
<span class="agency-detail-item-content">
|
|
||||||
<!-- {{ getTime() }} -->
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right-content-file" v-if="isShow && tabValue == 1">
|
|
||||||
<el-row :gutter="15">
|
|
||||||
<el-col :xs="24" :sm="24" :md="16" :lg="18">
|
|
||||||
<div class="content-file-video">
|
|
||||||
<div class="file-top">协作视频</div>
|
|
||||||
<div class="file-video-bottom">
|
|
||||||
<!-- autoplay="autoplay" -->
|
|
||||||
<video
|
|
||||||
v-if="
|
|
||||||
detail.remoteVideoFile?.prefix &&
|
|
||||||
detail.remoteVideoFile.path
|
|
||||||
"
|
|
||||||
:src="
|
|
||||||
detail.remoteVideoFile.prefix +
|
|
||||||
detail.remoteVideoFile.path
|
|
||||||
"
|
|
||||||
id="videoPlayer"
|
|
||||||
loop
|
|
||||||
controls
|
|
||||||
></video>
|
|
||||||
<div v-else class="video-null">暂无视频</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
<div class="agency-detail-cont-item">
|
||||||
<el-col :xs="24" :sm="24" :md="8" :lg="6">
|
<span class="agency-detail-item-title">成员</span>
|
||||||
<div>
|
<span
|
||||||
<div class="file-top">
|
class="agency-detail-item-content"
|
||||||
附件({{
|
v-if="detail?.all_participants?.length"
|
||||||
detail?.fileList?.length ? detail.fileList.length : 0
|
style="display: flex; flex-wrap: wrap"
|
||||||
}})
|
|
||||||
</div>
|
|
||||||
<div class="content-file-list">
|
|
||||||
<el-scrollbar
|
|
||||||
class="file-list"
|
|
||||||
height="calc(100vh - 380px)"
|
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
class="file-list-content"
|
v-for="(items, indexs) in detail.all_participants"
|
||||||
v-if="detail?.fileList?.length"
|
:key="indexs"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
items.display_name +
|
||||||
|
(indexs + 1 == detail.all_participants.length
|
||||||
|
? ''
|
||||||
|
: '、')
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="agency-detail-cont-item">
|
||||||
|
<span class="agency-detail-item-title">协作时长</span>
|
||||||
|
<span class="agency-detail-item-content">
|
||||||
|
{{ getTime() }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-content-file" v-if="isShow && tabValue == 1">
|
||||||
|
<el-row :gutter="15">
|
||||||
|
<el-col :xs="24" :sm="24" :md="16" :lg="18">
|
||||||
|
<div class="content-file-video">
|
||||||
|
<div class="file-top">协作视频</div>
|
||||||
|
<div class="file-video-bottom">
|
||||||
|
<!-- autoplay="autoplay" -->
|
||||||
|
<video
|
||||||
|
v-if="detail.remoteVideoFile?.storage_url"
|
||||||
|
:src="detail.remoteVideoFile.storage_url"
|
||||||
|
id="videoPlayer"
|
||||||
|
loop
|
||||||
|
autoplay
|
||||||
|
controls
|
||||||
|
></video>
|
||||||
|
<div v-else class="video-null">暂无视频</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :xs="24" :sm="24" :md="8" :lg="6">
|
||||||
|
<div>
|
||||||
|
<div class="file-top">
|
||||||
|
附件({{
|
||||||
|
detail?.fileList?.length ? detail.fileList.length : 0
|
||||||
|
}})
|
||||||
|
</div>
|
||||||
|
<div class="content-file-list">
|
||||||
|
<el-scrollbar
|
||||||
|
class="file-list"
|
||||||
|
height="calc(100vh - 380px)"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="file-list-item"
|
class="file-list-content"
|
||||||
v-for="(item, index) in detail.fileList"
|
v-if="detail?.fileList?.length"
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<div class="file-list-item-icon"></div>
|
|
||||||
<div class="file-list-item-text">
|
|
||||||
<div class="list-item-text text-out-of-hiding-1">
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
<el-link
|
|
||||||
:href="item.prefix + item.path"
|
|
||||||
type="primary"
|
|
||||||
target="_blank"
|
|
||||||
:underline="false"
|
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="file-list-item"
|
||||||
|
v-for="(item, index) in detail.fileList"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div class="file-list-item-icon"></div>
|
||||||
|
<div class="file-list-item-text">
|
||||||
|
<div class="list-item-text text-out-of-hiding-1">
|
||||||
|
{{ item.file_name }}
|
||||||
|
</div>
|
||||||
<el-icon
|
<el-icon
|
||||||
:size="18"
|
:size="18"
|
||||||
color="#0d74ff"
|
color="#0d74ff"
|
||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
>
|
@click="handlePreview(item)"
|
||||||
<Download />
|
>
|
||||||
|
<View />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-link>
|
<el-link
|
||||||
<el-icon
|
:href="item.source_url"
|
||||||
:size="18"
|
type="primary"
|
||||||
color="#FF4646"
|
target="_blank"
|
||||||
style="cursor: pointer"
|
:underline="false"
|
||||||
@click="clickDeleteFile(item)"
|
>
|
||||||
>
|
<el-icon
|
||||||
<Delete />
|
:size="18"
|
||||||
</el-icon>
|
color="#0d74ff"
|
||||||
|
style="cursor: pointer"
|
||||||
|
>
|
||||||
|
<Download />
|
||||||
|
</el-icon>
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="message-user"
|
||||||
|
v-else-if="isShow && tabValue == 2"
|
||||||
|
style="height: calc(100vh - 90px)"
|
||||||
|
v-loading="userLoading"
|
||||||
|
>
|
||||||
|
<div class="message-user-card">
|
||||||
|
<div class="user-card-nickName">
|
||||||
|
<img v-if="detail.avatar" :src="detail.avatar" />
|
||||||
|
<img v-else src="@/assets/images/profile.jpg" />
|
||||||
|
<span>{{ detail.nickName || detail.name || '暂无信息' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="user-card-information">
|
||||||
|
<div class="user-information-item">
|
||||||
|
<div class="user-information-title">
|
||||||
|
<img src="@/assets/images/user-information1.png" alt="" />
|
||||||
|
<span>性别</span>
|
||||||
|
</div>
|
||||||
|
<div class="user-information-text">
|
||||||
|
<!-- <dict-tag
|
||||||
|
v-if="detail.sex"
|
||||||
|
:options="sys_user_sex"
|
||||||
|
:value="detail.sex"
|
||||||
|
/>
|
||||||
|
<div v-else>
|
||||||
|
{{ '暂无' }}
|
||||||
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="user-information-item">
|
||||||
|
<div class="user-information-title">
|
||||||
|
<img src="@/assets/images/user-information2.png" alt="" />
|
||||||
|
<span>手机号</span>
|
||||||
|
</div>
|
||||||
|
<div class="user-information-text">
|
||||||
|
{{ detail.phonenumber || '暂无' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-information-item">
|
||||||
|
<div class="user-information-title">
|
||||||
|
<img src="@/assets/images/user-information3.png" alt="" />
|
||||||
|
<span>邮箱</span>
|
||||||
|
</div>
|
||||||
|
<div class="user-information-text">
|
||||||
|
{{ detail.email || '暂无' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-information-item">
|
||||||
|
<div class="user-information-title">
|
||||||
|
<img src="@/assets/images/user-information4.png" alt="" />
|
||||||
|
<span>所属部门</span>
|
||||||
|
</div>
|
||||||
|
<div class="user-information-text">
|
||||||
|
{{ detail.organization || '暂无' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="user-card-btn">
|
||||||
|
<el-button type="info" @click="clickInitiate">
|
||||||
|
发起协作
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="message-null">
|
||||||
|
<el-empty description="暂无内容" />
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
</el-col>
|
||||||
class="message-user"
|
</el-row>
|
||||||
v-else-if="isShow && tabValue == 2"
|
|
||||||
style="height: calc(100vh - 90px)"
|
|
||||||
v-loading="userLoading"
|
|
||||||
>
|
|
||||||
<div class="message-user-card">
|
|
||||||
<div class="user-card-nickName">
|
|
||||||
<img v-if="detail.avatar" :src="detail.avatar" />
|
|
||||||
<img v-else src="@/assets/images/profile.jpg" />
|
|
||||||
<span>{{ detail.nickName || detail.name || '暂无信息' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="user-card-information">
|
|
||||||
<div class="user-information-item">
|
|
||||||
<div class="user-information-title">
|
|
||||||
<img src="@/assets/images/user-information1.png" alt="" />
|
|
||||||
<span>性别</span>
|
|
||||||
</div>
|
|
||||||
<div class="user-information-text">
|
|
||||||
<!-- <dict-tag
|
|
||||||
v-if="detail.sex"
|
|
||||||
:options="sys_user_sex"
|
|
||||||
:value="detail.sex"
|
|
||||||
/>
|
|
||||||
<div v-else>
|
|
||||||
{{ '暂无' }}
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="user-information-item">
|
|
||||||
<div class="user-information-title">
|
|
||||||
<img src="@/assets/images/user-information2.png" alt="" />
|
|
||||||
<span>手机号</span>
|
|
||||||
</div>
|
|
||||||
<div class="user-information-text">
|
|
||||||
{{ detail.phonenumber || '暂无' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="user-information-item">
|
|
||||||
<div class="user-information-title">
|
|
||||||
<img src="@/assets/images/user-information3.png" alt="" />
|
|
||||||
<span>邮箱</span>
|
|
||||||
</div>
|
|
||||||
<div class="user-information-text">
|
|
||||||
{{ detail.email || '暂无' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="user-information-item">
|
|
||||||
<div class="user-information-title">
|
|
||||||
<img src="@/assets/images/user-information4.png" alt="" />
|
|
||||||
<span>所属部门</span>
|
|
||||||
</div>
|
|
||||||
<div class="user-information-text">
|
|
||||||
{{ detail.organization || '暂无' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="user-card-btn">
|
|
||||||
<el-button type="info" @click="clickInitiate">
|
|
||||||
发起协作
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="message-null">
|
|
||||||
<el-empty description="暂无内容" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-dialog
|
<!-- <el-dialog
|
||||||
v-model="inviteDialog"
|
v-model="inviteDialog"
|
||||||
title="远程协作"
|
title="远程协作"
|
||||||
width="400px"
|
width="400px"
|
||||||
:close-on-press-escape="false"
|
:close-on-press-escape="false"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
>
|
>
|
||||||
<div>
|
|
||||||
<div style="width: 100%; margin-bottom: 30px; font-size: 20px">
|
<div style="width: 100%; margin-bottom: 30px; font-size: 20px">
|
||||||
"
|
"
|
||||||
{{
|
{{
|
||||||
socketInformation.room_name
|
socketInformation.room_name
|
||||||
? socketInformation.room_name
|
? socketInformation.room_name
|
||||||
: ''
|
: ''
|
||||||
}}
|
}}
|
||||||
" 邀请您参加远程协作
|
" 邀请您参加远程协作
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: center">
|
<div style="text-align: center">
|
||||||
<el-button
|
<el-button
|
||||||
size="large"
|
size="large"
|
||||||
type="danger"
|
type="danger"
|
||||||
style="font-size: 16px"
|
style="font-size: 16px"
|
||||||
@click="clickRefuseJoin"
|
@click="clickRefuseJoin"
|
||||||
>
|
>
|
||||||
拒 绝
|
拒 绝
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
size="large"
|
size="large"
|
||||||
type="primary"
|
type="primary"
|
||||||
style="font-size: 16px"
|
style="font-size: 16px"
|
||||||
@click="clickJoin"
|
@click="clickJoin"
|
||||||
>
|
>
|
||||||
加 入
|
加 入
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</el-dialog> -->
|
||||||
</el-dialog>
|
</div>
|
||||||
</div>
|
<!-- 文件预览 -->
|
||||||
</div>
|
<BrowseFile ref="browseFileRef" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onActivated, onMounted, reactive, toRefs, watch, getCurrentInstance ,ref} from 'vue'
|
import { onActivated, onMounted, reactive, toRefs, watch, getCurrentInstance ,ref} from 'vue'
|
||||||
import leftTab from './components/leftTab/index.vue'
|
import leftTab from './components/leftTab/index.vue'
|
||||||
|
import AssistWx from './components/assistWx/index.vue'
|
||||||
|
import BrowseFile from '@/views/conferencingRoom/components/fileUpload/browseFile.vue'
|
||||||
import { getInfo } from '@/api/login.js'
|
import { getInfo } from '@/api/login.js'
|
||||||
import { getStatusApi } from '@/api/conferencingRoom.js'
|
import { getStatusApi ,getFileListApi ,getvideoUrlApi} from '@/api/conferencingRoom.js'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useRoomStore } from '@/stores/modules/room'
|
import { useRoomStore } from '@/stores/modules/room'
|
||||||
import { useUserStore } from '@/stores/modules/user.js'
|
import { useUserStore } from '@/stores/modules/user.js'
|
||||||
import { mqttClient } from "@/utils/mqtt.js";
|
import { mqttClient } from "@/utils/mqtt.js";
|
||||||
import { getToken } from '@/utils/auth.js'
|
import { getToken ,getUserInfo} from '@/utils/auth.js';
|
||||||
|
import { deepClone,parseTime } from '@/utils/ruoyi.js'
|
||||||
const roomStore = useRoomStore()
|
const roomStore = useRoomStore()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -280,8 +280,9 @@ const { proxy } = getCurrentInstance()
|
|||||||
const state = reactive({
|
const state = reactive({
|
||||||
detail: {},
|
detail: {},
|
||||||
weekName: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
|
weekName: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
|
||||||
tabValue: 2,
|
tabValue: 1,
|
||||||
isShow: true,
|
isShow: true,
|
||||||
|
isShowLoading: false,
|
||||||
load: false,
|
load: false,
|
||||||
loadText: '数据加载中',
|
loadText: '数据加载中',
|
||||||
isLinkKnow: 'F',
|
isLinkKnow: 'F',
|
||||||
@@ -289,7 +290,9 @@ const state = reactive({
|
|||||||
inviteDialog: false,
|
inviteDialog: false,
|
||||||
cooperation: import.meta.env.VITE_APP_COOPERATION_TYPE,
|
cooperation: import.meta.env.VITE_APP_COOPERATION_TYPE,
|
||||||
})
|
})
|
||||||
const userLoading = ref(false); // 用户信息加载状态
|
const userLoading = ref(false); // 用户信息加载状态
|
||||||
|
//文件预览
|
||||||
|
const browseFileRef = ref(null);
|
||||||
|
|
||||||
const isEmptyObject = (obj) => {
|
const isEmptyObject = (obj) => {
|
||||||
return !obj || Object.keys(obj).length === 0
|
return !obj || Object.keys(obj).length === 0
|
||||||
@@ -302,7 +305,9 @@ const clickInitiate = () => {
|
|||||||
userData = JSON.parse(sessionStorage.getItem('userData')) || null
|
userData = JSON.parse(sessionStorage.getItem('userData')) || null
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析 userData 失败:', e)
|
console.error('解析 userData 失败:', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (isEmptyObject(state.detail)) {
|
if (isEmptyObject(state.detail)) {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: '请先选择人员',
|
message: '请先选择人员',
|
||||||
@@ -318,13 +323,14 @@ const clickInitiate = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
roomStore.setUserUid(userData.uid)
|
roomStore.setUserUid(userData.uid)
|
||||||
roomStore.setDetailUid(state.detail.uid)
|
roomStore.setDetailUid(state.detail.uid)
|
||||||
|
roomStore.setDetailName(state.detail.name)
|
||||||
router.push({
|
router.push({
|
||||||
path: '/conferencingRoom',
|
path: '/conferencingRoom',
|
||||||
query:{
|
query:{
|
||||||
type:1//创建房间,加入房间 2
|
type:1//创建房间,加入房间 2
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改展示列表 */
|
/** 修改展示列表 */
|
||||||
@@ -334,33 +340,49 @@ const updateTab = (newValue) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 修改展示区内容 */
|
/** 修改展示区内容 */
|
||||||
const updateDetail = async (details) => {
|
const updateDetail = async (details) => {
|
||||||
userLoading.value = true
|
userLoading.value = true
|
||||||
if (details) {
|
if (details) {
|
||||||
state.detail = {}
|
state.detail = {}
|
||||||
if (state.tabValue == 1) {
|
if (state.tabValue == 1) {
|
||||||
state.isShow = true
|
state.isShowLoading = true
|
||||||
|
getTheFileList(details)
|
||||||
} else {
|
} else {
|
||||||
const res = await getInfo(details.uid)
|
const res = await getInfo(details.uid)
|
||||||
state.detail = res.data
|
state.detail = res.data
|
||||||
userLoading.value = false
|
userLoading.value = false
|
||||||
// getInfo(details.uid)
|
}
|
||||||
// .then((res) => {
|
}
|
||||||
// console.log(res,'人员详细信息')
|
userLoading.value = false
|
||||||
// })
|
}
|
||||||
// .finally(() => { state.isShow = true })
|
|
||||||
|
const getTheFileList = async (details) => {
|
||||||
|
try {
|
||||||
|
let detail = deepClone(details);
|
||||||
|
const [fileResponse, videoResponse] = await Promise.all([
|
||||||
|
getFileListApi(details.room_uid),
|
||||||
|
getvideoUrlApi(details.room_uid)
|
||||||
|
]);
|
||||||
|
const processedDetail = {
|
||||||
|
...details,
|
||||||
|
fileList: fileResponse.data?.files || [],
|
||||||
|
remoteVideoFile: videoResponse.data?.recordings?.[0] || {},
|
||||||
|
initiator: details.all_participants?.find(item =>
|
||||||
|
item.participant_role === 'moderator' // 使用严格相等
|
||||||
|
)?.display_name || '未知发起人'
|
||||||
|
};
|
||||||
|
state.detail = processedDetail;
|
||||||
|
state.isShowLoading = false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取文件列表失败:', error);
|
||||||
|
// 可以根据需要添加错误处理逻辑
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
state.isShow = false
|
|
||||||
}
|
|
||||||
userLoading.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取通话时长 */
|
/** 获取通话时长 */
|
||||||
const getTime = () => {
|
const getTime = () => {
|
||||||
let begin = new Date(state.detail.beginTime).getTime()
|
let begin = new Date(state.detail.created_at).getTime()
|
||||||
let end = new Date(state.detail.endTime).getTime()
|
let end = new Date(state.detail.updated_at).getTime()
|
||||||
if (begin && end) {
|
if (begin && end) {
|
||||||
let diff = end - begin
|
let diff = end - begin
|
||||||
const h = Math.floor(diff / (1000 * 60 * 60))
|
const h = Math.floor(diff / (1000 * 60 * 60))
|
||||||
@@ -374,83 +396,92 @@ const getTime = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加入会议 */
|
//文件预览
|
||||||
const clickJoin = async () => {
|
function handlePreview(file) {
|
||||||
const res = await getStatusApi(state.socketInformation.room_uid,{status:1})
|
if (!file.preview_url) {
|
||||||
if(res.meta.code == 200){
|
ElMessage.error('文件链接无效');
|
||||||
ElMessage({
|
return;
|
||||||
message: '成功加入该协作',
|
}
|
||||||
type: 'success',
|
browseFileRef.value.showEdit(file)
|
||||||
})
|
|
||||||
state.inviteDialog = false
|
|
||||||
router.push({
|
|
||||||
path: '/conferencingRoom',
|
|
||||||
query:{
|
|
||||||
type:2,//创建房间,加入房间 2
|
|
||||||
room_uid:state.socketInformation.room_uid
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
state.inviteDialog = false
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 加入会议 */
|
||||||
|
// const clickJoin = async () => {
|
||||||
|
// const res = await getStatusApi(state.socketInformation.room_uid,{status:1})
|
||||||
|
// if(res.meta.code == 200){
|
||||||
|
// ElMessage({
|
||||||
|
// message: '成功加入该协作',
|
||||||
|
// type: 'success',
|
||||||
|
// })
|
||||||
|
// state.inviteDialog = false
|
||||||
|
// router.push({
|
||||||
|
// path: '/conferencingRoom',
|
||||||
|
// query:{
|
||||||
|
// type:2,//创建房间,加入房间 2
|
||||||
|
// room_uid:state.socketInformation.room_uid
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// state.inviteDialog = false
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
/** 拒绝加入 */
|
/** 拒绝加入 */
|
||||||
const clickRefuseJoin = async () => {
|
// const clickRefuseJoin = async () => {
|
||||||
//status 1: 同意加入, 5: 拒绝加入
|
// //status 1: 同意加入, 5: 拒绝加入
|
||||||
const res = await getStatusApi(state.socketInformation.room_uid,{status:5})
|
// const res = await getStatusApi(state.socketInformation.room_uid,{status:5})
|
||||||
if(res.meta.code == 200){
|
// if(res.meta.code == 200){
|
||||||
ElMessage({
|
// ElMessage({
|
||||||
message: '已拒绝加入该协作',
|
// message: '已拒绝加入该协作',
|
||||||
type: 'error',
|
// type: 'error',
|
||||||
})
|
// })
|
||||||
state.inviteDialog = false
|
// state.inviteDialog = false
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/** 处理加入房间和拒接房间 mqtt 消息 */
|
/** 处理加入房间和拒接房间 mqtt 消息 */
|
||||||
const processingSocket = (message) => {
|
// const processingSocket = (message) => {
|
||||||
const res = JSON.parse(message)
|
// const res = JSON.parse(message)
|
||||||
if (!res?.status) {
|
// if (!res?.status) {
|
||||||
state.socketInformation = res
|
// state.socketInformation = res
|
||||||
state.inviteDialog = true
|
// state.inviteDialog = true
|
||||||
showNotification(state.socketInformation)
|
// showNotification(state.socketInformation)
|
||||||
}else if(res.status == 5){
|
// }else if(res.status == 5){
|
||||||
ElMessage({
|
// ElMessage({
|
||||||
message: `${res?.display_name}拒绝加入该协作`,
|
// message: `${res?.display_name}拒绝加入该协作`,
|
||||||
type: 'error',
|
// type: 'error',
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/** 浏览器通知 */
|
/** 浏览器通知 */
|
||||||
const showNotification = (data) => {
|
// const showNotification = (data) => {
|
||||||
if ('Notification' in window) {
|
// if ('Notification' in window) {
|
||||||
Notification.requestPermission().then((permission) => {
|
// Notification.requestPermission().then((permission) => {
|
||||||
if (permission === 'granted') {
|
// if (permission === 'granted') {
|
||||||
const notification = new Notification('协作邀请', {
|
// const notification = new Notification('协作邀请', {
|
||||||
// body: String(data.room_name) + '邀请您参加远程协作'
|
// // body: String(data.room_name) + '邀请您参加远程协作'
|
||||||
body: '远程协作有新的邀请'
|
// body: '远程协作有新的邀请'
|
||||||
// icon: logo,
|
// // icon: logo,
|
||||||
})
|
// })
|
||||||
notification.onclick = () => { clickJoin() }
|
// notification.onclick = () => { clickJoin() }
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
// 暴露给模板
|
// 暴露给模板
|
||||||
const { detail, weekName, tabValue, isShow, load, loadText, isLinkKnow, socketInformation, inviteDialog, cooperation } = toRefs(state)
|
const { detail, weekName, tabValue, isShow, load, loadText, isLinkKnow, socketInformation, inviteDialog, cooperation,isShowLoading } = toRefs(state)
|
||||||
onMounted(async () => {
|
// onMounted(async () => {
|
||||||
await mqttClient.connect(`room${Math.random().toString(16).substr(2, 8)}`);
|
// await mqttClient.connect(`room${Math.random().toString(16).substr(2, 8)}`);
|
||||||
const res = await userStore.getInfo()
|
// const res = await userStore.getInfo()
|
||||||
const topic = `xSynergy/ROOM/+/rooms/${res.uid}`;
|
// const topic = `xSynergy/ROOM/+/rooms/${res.uid}`;
|
||||||
mqttClient.subscribe(topic, async (shapeData) => {
|
// mqttClient.subscribe(topic, async (shapeData) => {
|
||||||
// console.log(shapeData.toString(),'shapeData发送邀请')
|
// // console.log(shapeData.toString(),'shapeData发送邀请')
|
||||||
processingSocket(shapeData.toString())
|
// processingSocket(shapeData.toString())
|
||||||
});
|
// });
|
||||||
})
|
// })
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ const loading = ref(false)
|
|||||||
password: ciphertext,
|
password: ciphertext,
|
||||||
username: loginForm.value.username,
|
username: loginForm.value.username,
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
const userInfo = JSON.parse(sessionStorage.getItem('userData'))
|
const userInfo = JSON.parse(sessionStorage.getItem('userData'))
|
||||||
await handleLoginSuccess();
|
await handleLoginSuccess();
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user