Compare commits

...

114 Commits

Author SHA1 Message Date
yj 3b03d6a8c3 代码格式化并添加类型 2024-09-22 09:17:17 +08:00
youngq 1ecec388ce 修复录屏麦克风问题 2024-09-21 01:34:53 +08:00
yj 27d3a59438 恢复轮流接流代码 2024-09-20 17:20:31 +08:00
yj 09aaae8274 导出参会记录 2024-09-20 17:12:07 +08:00
yj 86ccca642d 删除会议室 2024-09-20 16:16:18 +08:00
yj b746a50d1a 修改阈值 2024-09-20 15:46:30 +08:00
yj 5fabfd7308 优化接流限制 2024-09-20 15:15:32 +08:00
yj adf86ef62e 每一小时检查更新 2024-09-20 10:17:07 +08:00
yj 6f882d030c 优化缩放倍率 2024-09-18 15:38:00 +08:00
yj 1c5a7aa4a9 音频优化 2024-09-18 15:11:47 +08:00
yj 9ab13ca2b4 关闭声音不关闭屏幕共享的音频 2024-09-18 14:50:25 +08:00
yj ef8e9641ed 修改默认降噪模式 2024-09-18 12:44:03 +08:00
yj d77782916c 角色权限优化 2024-09-14 09:32:25 +08:00
yj 092d78bbb9 还原用户角色 2024-09-14 09:15:18 +08:00
yj cc2cc4c9e5 新增用户角色 2024-09-13 17:00:26 +08:00
yj 2d63cdefdb 优化 2024-09-13 11:46:13 +08:00
yj 3eebab8192 声音显示优化 2024-09-13 11:39:21 +08:00
yj 0f164b9cca appid修改为动态设置 2024-09-13 10:42:37 +08:00
yj 90a0a5f0d0 优化了文件不存在 可以点击前往设置 2024-09-12 14:19:31 +08:00
yj 41c111ec66 优化 2024-09-12 11:35:14 +08:00
yj 2102ce9c32 优化 2024-09-12 11:27:05 +08:00
yj 65ad327f29 优化 2024-09-12 11:26:20 +08:00
yj 28d8b9d4dc 增加输入音量设置 2024-09-11 14:00:31 +08:00
yj 5a20b9603e 更换图标 2024-09-10 14:50:16 +08:00
yj ba9d02f9fa 视频订阅优化 2024-09-10 14:31:27 +08:00
yj 32308e006b 账号限制个数改为30个 2024-09-10 14:26:26 +08:00
yj 8d9a018c19 去掉账号只能输入数字 2024-09-10 14:09:36 +08:00
yj ac689b9548 顺序优化 2024-09-10 14:04:12 +08:00
yj 7875e37bd1 会议监控未打开不加入房间 2024-09-10 13:59:34 +08:00
yj 96d17e21a0 共享屏幕在未观看时不接流 2024-09-10 13:50:46 +08:00
yj 0702536b3c 优化非管理员加入第二个房间什么流都不能接 2024-09-10 11:41:52 +08:00
yj 193c99cbfa 设备远程控制 2024-09-10 11:38:01 +08:00
yj edf7ce3629 远程设备控制 2024-09-10 11:33:18 +08:00
yj e40ff8c931 设备管理初始化 2024-09-10 10:11:54 +08:00
yj 36becdc6c4 增加设备管理 2024-09-10 09:25:48 +08:00
yj 92dea100e8 优化 2024-09-09 16:26:33 +08:00
yj b6ba3c8bf6 优化 2024-09-09 16:10:33 +08:00
yj 6b6d895ee3 优化 2024-09-09 15:59:00 +08:00
yj e2dc0a58e0 优化 2024-09-09 15:02:00 +08:00
yj 954da4a220 优化 2024-09-09 15:01:42 +08:00
yj b4618bbf38 视频大小流测试 2024-09-09 14:41:36 +08:00
yj 0cda0e7842 样式优化 2024-09-09 14:02:57 +08:00
yj 4e9bcb813e 导入用户提示 2024-09-09 13:42:58 +08:00
yj b07ff2e2f1 操作优化 2024-09-09 13:38:47 +08:00
yj 953749867b f12弹出控制台开启 2024-09-09 11:46:17 +08:00
yj 5a8678a3a1 优化 2024-09-09 11:34:52 +08:00
yj 051481b631 打包后下载模版报错 2024-09-09 11:34:21 +08:00
yj 939c21cf67 修改打包报错 2024-09-09 11:09:21 +08:00
yj e322314761 优化 2024-09-09 11:07:03 +08:00
yj 5822a3b08c 批量导入用户 2024-09-06 16:37:57 +08:00
yj 98a15550c0 优化样式 2024-09-05 18:03:10 +08:00
yj b65cd93602 优化下载页面样式 2024-09-05 17:50:31 +08:00
yj c659442856 优化 2024-09-05 17:35:35 +08:00
yj 414d7d1f97 减短时间 2024-09-05 16:32:10 +08:00
youngq e3303ad903 1 2024-09-05 16:21:50 +08:00
yj d130da43f0 优化时长 2024-09-05 16:14:14 +08:00
yj 2b79c917a1 删除多余代码 2024-09-05 16:11:43 +08:00
yj 4774f875a3 优化 2024-09-05 16:11:02 +08:00
yj 83efd746e2 删除多余代码 2024-09-05 14:33:48 +08:00
yj 895ee285d3 优化 2024-09-05 14:14:11 +08:00
yj 6babcdbfd0 添加延时 2024-09-05 14:07:20 +08:00
yj cb2940249c 优化 2024-09-05 14:00:35 +08:00
yj 55bc137c79 还原离开加入 2024-09-05 13:47:23 +08:00
yj cd1f274644 还原永久加入房间 2024-09-05 13:34:07 +08:00
yj eee2c11336 还原配置 2024-09-05 11:43:39 +08:00
yj d397b27d49 优化 2024-09-05 10:52:19 +08:00
yj fa309a9985 修改默认加入房间 2024-09-05 10:41:15 +08:00
yj 98602ad6be 优化 2024-09-05 09:00:19 +08:00
yj 583794ff13 去除1000跳转登录 2024-09-04 14:50:10 +08:00
yj 22c655174c 退出跳转bug修复 2024-09-04 14:33:12 +08:00
yj b27b338e39 修改图标大小 2024-09-04 10:17:32 +08:00
yj 7d58420b77 新增ai降噪 2024-09-04 09:27:26 +08:00
yj e645c13158 优化第二个房间号名称 2024-09-03 17:30:36 +08:00
yj eeccf9a462 更新地址 2024-09-03 16:57:29 +08:00
youngq 2d7103f7ae 1 2024-09-03 16:03:17 +08:00
youngq ee3725ded7 1 2024-09-03 14:53:13 +08:00
yj 86d4e19bc7 修改描述 2024-09-03 11:33:47 +08:00
yj 1f71c35920 解决录制问题 2024-09-03 10:48:15 +08:00
yj 7d7490dc48 增加权限 2024-09-02 17:59:08 +08:00
yj 16d4b3e73f 共享桌面失败 2024-09-02 17:28:14 +08:00
yj 9e1860c722 更改版本 2024-09-02 16:18:56 +08:00
yj 91896b8b6b 修改配置 2024-09-02 16:04:14 +08:00
yj c713507845 优化 2024-08-30 14:44:33 +08:00
yj ff491384be 添加提示 2024-08-30 14:30:41 +08:00
yj c6c80119c4 版本号更新 2024-08-30 13:52:47 +08:00
yj 2034dbed21 去除console 2024-08-30 12:20:26 +08:00
yj 697b2521d5 优化 2024-08-30 12:19:07 +08:00
yj eb555d6d66 优化 2024-08-30 12:14:52 +08:00
yj 1d67a9c88d 优化设置看谁 2024-08-30 11:04:21 +08:00
yj 2a4f72b621 去除无用组件 2024-08-30 10:36:24 +08:00
yj ed4106d6e0 优化 2024-08-30 10:32:54 +08:00
yj c28481e3ba 更新版本 2024-08-30 09:50:57 +08:00
向望 a4678aca3b 优化 2024-08-30 09:48:52 +08:00
yj 3bb365f248 优化 2024-08-30 09:43:05 +08:00
yj d39906d0b0 优化 2024-08-30 09:37:30 +08:00
yj af97c97f6a 优化 2024-08-30 09:30:02 +08:00
yj 1d857aa7cc 样式优化 2024-08-30 09:19:50 +08:00
yj 5e210ce2cc 样式优化 2024-08-29 18:01:30 +08:00
yj 8ee9d82e5f 样式优化 2024-08-29 17:48:12 +08:00
yj 8a159704da 优化 2024-08-29 17:43:09 +08:00
yj c2d9e2cbea 优化 2024-08-29 17:41:50 +08:00
yj 712b3cfed6 优化 2024-08-29 17:25:21 +08:00
yj d1dad93ecd 第二频道不采集音频 2024-08-29 14:32:42 +08:00
yj 016f1a8f2f 还原 2024-08-29 14:26:01 +08:00
yj daf96cd4d5 测试2 2024-08-29 14:17:59 +08:00
yj 8c4ae7532a 测试 2024-08-29 14:09:29 +08:00
yj 308eef3704 优化 2024-08-29 13:51:00 +08:00
yj 47c4626171 会议监控 2024-08-29 13:38:41 +08:00
yj 3998bed6ff 恢复 2024-08-28 16:49:17 +08:00
yj b9d967365e 优化 2024-08-28 15:44:02 +08:00
yj 5a30d69cc8 优化 2024-08-28 15:01:46 +08:00
yj f6539d30f8 优化 2024-08-28 14:31:36 +08:00
yj 75a142d3be 隐藏会议监控 2024-08-28 13:45:40 +08:00
yj 1c0a2a9034 Merge pull request 'yj' (#1) from yj into master
Reviewed-on: #1
2024-08-28 13:40:03 +08:00
57 changed files with 1502 additions and 668 deletions

View File

@ -1,5 +1,5 @@
#基础API 绝对的 #基础API 绝对的
VITE_BASE_URL_API = 'http://192.168.2.9:5192' VITE_BASE_URL_API = 'https://meeting-api.23544.com/pc'
VITE_BASE_URL_DRAW_API = 'http://192.168.2.9:6555' VITE_BASE_URL_DRAW_API = 'http://192.168.2.9:6555'
#当前IP 相对的 #当前IP 相对的
VITE_BASE_CURRENT_API = '.' VITE_BASE_CURRENT_API = '.'

62
main.js
View File

@ -16,7 +16,6 @@ const { autoUpdater, CancellationToken } = require('electron-updater');
const cancellationToken = new CancellationToken() const cancellationToken = new CancellationToken()
app.allowRendererProcessReuse = false; app.allowRendererProcessReuse = false;
let mainWindow = null; let mainWindow = null;
let newWindow = null;
let isMaximized = false; let isMaximized = false;
let env; let env;
@ -130,6 +129,9 @@ app.on('ready', () => {
} }
createWindow() createWindow()
updateHandle() // 检查更新 updateHandle() // 检查更新
setInterval(() => {
updateHandle() // 每一小时检查更新
}, 1000 * 60 * 60)
createTray() createTray()
// 获取当前脚本所在目录的绝对路径 // 获取当前脚本所在目录的绝对路径
const currentDirectory = __dirname; const currentDirectory = __dirname;
@ -142,11 +144,11 @@ app.on('ready', () => {
} }
// 监听f12打开控制台 // 监听f12打开控制台
mainWindow.webContents.on('before-input-event', (event, input) => { mainWindow.webContents.on('before-input-event', (event, input) => {
if (env === 'development') { // if (env === 'development') {
if (input.key === 'F12') { if (input.key === 'F12') {
mainWindow.webContents.openDevTools() mainWindow.webContents.openDevTools()
}
} }
// }
}); });
// 监听移动 // 监听移动
mainWindow.on('move', () => { mainWindow.on('move', () => {
@ -243,6 +245,12 @@ app.on('ready', () => {
} }
} }
}); });
// 获取桌面大小
ipcMain.handle('getWindowSize', (event, config) => {
const primaryDisplay = screen.getPrimaryDisplay()
const { width, height } = primaryDisplay.workAreaSize
return { width, height }
});
// 设置桌面应用基础属性 // 设置桌面应用基础属性
ipcMain.handle('setMainWindowSize', (event, config) => { ipcMain.handle('setMainWindowSize', (event, config) => {
// 设置最小窗口尺寸 // 设置最小窗口尺寸
@ -263,46 +271,6 @@ app.on('ready', () => {
const y = Math.round((display.workArea.height - mainWindow.getSize()[1]) / 2); const y = Math.round((display.workArea.height - mainWindow.getSize()[1]) / 2);
mainWindow.setPosition(x, y); mainWindow.setPosition(x, y);
}); });
// 打开新窗口
ipcMain.handle('oepnWindow', (event, data) => {
if (newWindow) {
newWindow.focus();
} else {
newWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 1200,
minHeight: 800,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
enableRemoteModule: true,
nodeIntegrationInWorker: true,
allowMediaDevices: true,
preload: path.join(__dirname, 'preload.js')
},
frame: false,
backgroundColor: '#00000000',
transparent: true,
});
newWindow.loadURL(data.url);
newWindow.focus();
newWindow.webContents.on('before-input-event', (event, input) => {
if (env === 'development') {
if (input.key === 'F12') {
newWindow.webContents.openDevTools()
}
}
});
}
});
// 关闭会议监控窗口
ipcMain.handle('closeMonitorWindow', () => {
if (newWindow) {
newWindow.close();
newWindow = null
}
});
} }
}); });
// 检测更新在你想要检查更新的时候执行renderer事件触发后的操作自行编写 // 检测更新在你想要检查更新的时候执行renderer事件触发后的操作自行编写
@ -313,9 +281,9 @@ function updateHandle() {
error: '检查更新出错', error: '检查更新出错',
checking: '正在检查更新……', checking: '正在检查更新……',
updateAva: '检测到新版本,正在下载……', updateAva: '检测到新版本,正在下载……',
updateNotAva: '现在使用的就是最新版本,不用更新' updateNotAva: '已经是最新版本,不用更新'
} }
autoUpdater.setFeedURL('https://update.23544.com/metting') autoUpdater.setFeedURL('https://meeting-api.23544.com/meeting/update')
autoUpdater.autoDownload = false // 不自动下载安装包 autoUpdater.autoDownload = false // 不自动下载安装包
autoUpdater.autoInstallOnAppQuit = false // 不自动安装 autoUpdater.autoInstallOnAppQuit = false // 不自动安装
autoUpdater.on('error', function (error) { autoUpdater.on('error', function (error) {

View File

@ -1,10 +1,10 @@
{ {
"name": "WGShare.Metting", "name": "WGShare.Metting",
"private": true, "private": true,
"version": "0.0.1", "version": "0.1.14",
"main": "main.js", "main": "main.js",
"authors": "yj", "authors": "yj",
"description": "test", "description": "智汇享",
"scripts": { "scripts": {
"dev": "concurrently \"electron . --env=development\" \"cross-env BROWSER=none vite\"", "dev": "concurrently \"electron . --env=development\" \"cross-env BROWSER=none vite\"",
"test": "concurrently \"electron . --env=test\" \"cross-env BROWSER=none vite\"", "test": "concurrently \"electron . --env=test\" \"cross-env BROWSER=none vite\"",
@ -69,7 +69,7 @@
"publish": [ "publish": [
{ {
"provider": "generic", "provider": "generic",
"url": "https://update.23544.com/metting" "url": "https://meeting-api.23544.com/meeting/update"
} }
], ],
"files": [ "files": [
@ -77,6 +77,7 @@
], ],
"win": { "win": {
"icon": "build/start.ico", "icon": "build/start.ico",
"requestedExecutionLevel": "highestAvailable",
"target": [ "target": [
{ {
"target": "nsis", "target": "nsis",
@ -99,7 +100,9 @@
"createDesktopShortcut": true, "createDesktopShortcut": true,
"createStartMenuShortcut": true, "createStartMenuShortcut": true,
"deleteAppDataOnUninstall": true, "deleteAppDataOnUninstall": true,
"shortcutName": "智汇享" "shortcutName": "智汇享",
"allowElevation": true,
"perMachine":true
} }
} }
} }

View File

@ -5,6 +5,10 @@ window.electron = {
setMainWindowSize: (config) => { setMainWindowSize: (config) => {
ipcRenderer.invoke('setMainWindowSize', { ...config }) ipcRenderer.invoke('setMainWindowSize', { ...config })
}, },
// 获取窗口大小
getWindowSize: (config) => {
return ipcRenderer.invoke('getWindowSize')
},
// 设置窗口状态 // 设置窗口状态
setViewStatus: (status) => { setViewStatus: (status) => {
ipcRenderer.invoke('setViewStatus', status) ipcRenderer.invoke('setViewStatus', status)
@ -60,13 +64,5 @@ window.electron = {
// 下载文件 // 下载文件
downFile: (callback) => { downFile: (callback) => {
ipcRenderer.on('downFile', callback) ipcRenderer.on('downFile', callback)
},
// 打开新窗口
oepnWindow: (data) => {
ipcRenderer.invoke('oepnWindow', data)
},
// 关闭会议监控窗口
closeMonitorWindow: () => {
ipcRenderer.invoke('closeMonitorWindow')
} }
} }

View File

@ -17,7 +17,6 @@ import { PostLogin } from "@/api/Login";
import { agora } from "@/utils/package/agora"; import { agora } from "@/utils/package/agora";
import QuitTips from "@/components/QuitTips"; import QuitTips from "@/components/QuitTips";
import { GetLeave } from "@/api/Meeting"; import { GetLeave } from "@/api/Meeting";
import UserVideo from "./page/UserVideo";
import path from "path"; import path from "path";
const fs = require('fs').promises; const fs = require('fs').promises;
const { exec } = require('child_process'); const { exec } = require('child_process');
@ -33,128 +32,126 @@ const App: React.FC = () => {
}); });
const [spinning, setSpinning] = useState(false); const [spinning, setSpinning] = useState(false);
const [isState, setIsState] = useState(true); const [isState, setIsState] = useState(true);
if (location.href.indexOf('/userVideo') === -1) { useEffect(() => {
useEffect(() => { let userInfo = JSON.parse(storage.getItem('user') as string)
let userInfo = JSON.parse(storage.getItem('user') as string) let loginInfo = JSON.parse(storage.getItem('login') as string)
let loginInfo = JSON.parse(storage.getItem('login') as string) if (userInfo) {
if (userInfo) { if (loginInfo && loginInfo.isAutoLogin) {
if (loginInfo && loginInfo.isAutoLogin) { PostLogin({
PostLogin({ account: loginInfo.account,
account: loginInfo.account, pwd: CryptoJS.MD5(loginInfo.password).toString(CryptoJS.enc.Hex)
pwd: CryptoJS.MD5(loginInfo.password).toString(CryptoJS.enc.Hex) }).then(async (res) => {
}).then(async (res) => { if (res.code === 200) {
if (res.code === 200) { storage.setItem('user', JSON.stringify(res.data))
storage.setItem('user', JSON.stringify(res.data)) storage.setItem('userLogin', true)
toSrc('/home') toSrc('/home')
await startSignalr() await startSignalr()
} else { } else {
toSrc('/login') toSrc('/login')
} }
}) })
} else {
toSrc('/login')
}
} else { } else {
toSrc('/login') toSrc('/login')
} }
window.addEventListener('resize', handleResize); } else {
window.addEventListener('online', handleNetworkChange); toSrc('/login')
window.addEventListener('offline', handleNetworkChange); }
const originalSetItem = window.localStorage.setItem; window.addEventListener('resize', handleResize);
window.localStorage.setItem = function (key, value) { window.addEventListener('online', handleNetworkChange);
originalSetItem.call(this, key, value); window.addEventListener('offline', handleNetworkChange);
const event = new Event('customStorageChange') as any; const originalSetItem = window.localStorage.setItem;
event.key = key window.localStorage.setItem = function (key, value) {
event.value = value originalSetItem.call(this, key, value);
window.dispatchEvent(event); const event = new Event('customStorageChange') as any;
}; event.key = key
window.addEventListener('customStorageChange', handleCustomStorageChange); event.value = value
return () => { window.dispatchEvent(event);
window.removeEventListener('resize', handleResize); };
window.removeEventListener('customStorageChange', handleCustomStorageChange); window.addEventListener('customStorageChange', handleCustomStorageChange);
window.removeEventListener('online', handleNetworkChange); return () => {
window.removeEventListener('offline', handleNetworkChange); window.removeEventListener('resize', handleResize);
}; window.removeEventListener('customStorageChange', handleCustomStorageChange);
}, []); window.removeEventListener('online', handleNetworkChange);
useEffect(() => { window.removeEventListener('offline', handleNetworkChange);
window.electron.downFile(async (_e: any, data: any) => { };
const response = await fetch(data.filePath); }, []);
const arrayBuffer = await response.arrayBuffer(); useEffect(() => {
const buffer = Buffer.from(arrayBuffer); window.electron.downFile(async (_e: any, data: any) => {
fs.writeFile(`${data.downFilePaths}${data.fileName}`, buffer, {}); const response = await fetch(data.filePath);
message.success(`下载成功!文件已保存至:${data.downFilePaths}`) const arrayBuffer = await response.arrayBuffer();
await fs.access(data.downFilePaths, fs.constants.F_OK); const buffer = Buffer.from(arrayBuffer);
if (process.platform === 'win32') { fs.writeFile(`${data.downFilePaths}${data.fileName}`, buffer, {});
exec(`explorer "${data.downFilePaths}"`); message.success(`下载成功!文件已保存至:${data.downFilePaths}`)
} else if (process.platform === 'darwin') { await fs.access(data.downFilePaths, fs.constants.F_OK);
exec(`open "${data.downFilePaths}"`); if (process.platform === 'win32') {
} exec(`explorer "${data.downFilePaths}"`);
}) } else if (process.platform === 'darwin') {
window.electron.onFilePath(async (_e: any, filePath: string, key: string) => { exec(`open "${data.downFilePaths}"`);
const setting = await JSON.parse(storage.getItem('setting') as string) }
if (key === 'recordingFilesPath') { })
setting.recordingFilesPath = filePath window.electron.onFilePath(async (_e: any, filePath: string, key: string) => {
const setting = await JSON.parse(storage.getItem('setting') as string)
if (key === 'recordingFilesPath') {
setting.recordingFilesPath = filePath
} else {
setting.shareFilesPath = filePath
}
storage.setItem('setting', JSON.stringify(setting))
})
window.electron.quitAndInstall(async (_e: any) => {
leaveChannel()
})
}, [])
useEffect(() => {
window.electron.onUpdate((_e: any, data: any) => {
if (location.hash.indexOf('/meeting') === -1) {
updateModalRef.current.changeModal(data)
}
})
if (!storage.getItem('setting')) {
storage.setItem('setting', JSON.stringify({
videoDeviceId: '', //摄像头id
ecordingDeviceId: "", //输入设备id
playBackDeviceId: "", //输出设备id
ecordingVolume: '', //输入音量
playBackVolume: '', //输出音量
autoEcordingVolume: true, //是否自动调整麦克风音量
recordingFilesPath: path.resolve(__dirname, '../../Downloads') + '\\', //本地录制保存路径
shareFilesPath: path.resolve(__dirname, '../../Downloads/') + '\\', //共享文件保存路径
isShareSavePath: true, //是否下载钱询问每个文件保存的位置
closeSetting: 'hide', //关闭按钮设置
isAINoiseReduction: true, //是否开启ai降噪
aINoiseReduction: 1, // 降噪模式
}))
}
}, [])
useEffect(() => {
if (isState) {
setIsState(false)
window.electron.onQuit(async () => {
if (location.hash.indexOf('/login') === 1) {
window.electron.quit()
} else { } else {
setting.shareFilesPath = filePath if (storage.getItem('isTips') === 'true') {
} const setting = JSON.parse(storage.getItem('setting') as string)
storage.setItem('setting', JSON.stringify(setting)) if (setting.closeSetting === 'hide') {
}) window.electron.setViewStatus(setting.closeSetting)
window.electron.quitAndInstall(async (_e: any) => {
leaveChannel()
})
}, [])
useEffect(() => {
window.electron.onUpdate((_e: any, data: any) => {
if (location.hash.indexOf('/meeting') === -1) {
updateModalRef.current.changeModal(data)
}
})
if (!storage.getItem('setting')) {
storage.setItem('setting', JSON.stringify({
videoDeviceId: '', //摄像头id
ecordingDeviceId: "", //输入设备id
playBackDeviceId: "", //输出设备id
ecordingVolume: '', //输入音量
playBackVolume: '', //输出音量
autoEcordingVolume: true, //是否自动调整麦克风音量
recordingFilesPath: path.resolve(__dirname, '../../Downloads') + '\\', //本地录制保存路径
shareFilesPath: path.resolve(__dirname, '../../Downloads/') + '\\', //共享文件保存路径
isShareSavePath: true, //是否下载钱询问每个文件保存的位置
closeSetting: 'hide', //关闭按钮设置
}))
}
}, [])
useEffect(() => {
if (isState) {
setIsState(false)
window.electron.onQuit(async () => {
if (location.hash.indexOf('/login') === 1) {
window.electron.quit()
} else {
if (storage.getItem('isTips') === 'true') {
const setting = JSON.parse(storage.getItem('setting') as string)
if (setting.closeSetting === 'hide') {
window.electron.setViewStatus(setting.closeSetting)
} else {
window.electron.quit()
}
} else { } else {
quitTipsRef.current.changeModal() window.electron.quit()
} }
} else {
quitTipsRef.current.changeModal()
} }
}) }
} })
storage.setItem('stateInfo', JSON.stringify(state)) }
}, [state]) storage.setItem('stateInfo', JSON.stringify(state))
useEffect(() => { }, [state])
if (location.href.indexOf('/login') !== -1) { useEffect(() => {
onStop() if (location.href.indexOf('/login') !== -1) {
} onStop()
if (location.href.indexOf('/meeting') === -1) { }
window.electron.closeMonitorWindow() }, [navigate])
}
}, [navigate])
}
useEffect(() => { useEffect(() => {
document.addEventListener('keydown', (event) => { document.addEventListener('keydown', (event) => {
if (event.key === 'F11') { if (event.key === 'F11') {
@ -200,20 +197,22 @@ const App: React.FC = () => {
}) })
} }
const toSrc = (path: string): void => { const toSrc = (path: string): void => {
switch (path) { window.electron.getWindowSize().then((res: any) => {
case '/login': switch (path) {
storage.removeItem('user') case '/login':
navigate('/login') storage.removeItem('user')
break; storage.setItem('userLogin', false)
case '/home': navigate('/login')
window.electron.setMainWindowSize({ break;
width: 1200, case '/home':
height: 800, window.electron.setMainWindowSize({
}) width: Math.ceil(res.width / 1.5),
navigate('/home') height: Math.ceil(res.height / 1.3),
break; })
navigate('/home')
} break;
}
})
}; };
const leaveChannel = async (bool?: boolean): Promise<void> => { const leaveChannel = async (bool?: boolean): Promise<void> => {
if (location.hash.indexOf('/meeting') === 1) { if (location.hash.indexOf('/meeting') === 1) {
@ -233,6 +232,10 @@ const App: React.FC = () => {
if (Boolean(e.value)) { if (Boolean(e.value)) {
onEventSignalr() onEventSignalr()
} }
} else if (e.key === 'userLogin') {
if (!Boolean(e.value)) {
navigate('/login')
}
} }
}; };
@ -259,7 +262,6 @@ const App: React.FC = () => {
</Route> </Route>
<Route path='/login' element={<Login />} /> <Route path='/login' element={<Login />} />
<Route path='/meeting' element={<Meeting />} /> <Route path='/meeting' element={<Meeting />} />
<Route path='/userVideo' element={<UserVideo />} />
<Route path='*' element={<NotFound />} /> <Route path='*' element={<NotFound />} />
</Routes> </Routes>
<Spin spinning={spinning} fullscreen /> <Spin spinning={spinning} fullscreen />

View File

@ -5,12 +5,17 @@ export const GetRoom = (data: { pageIndex: number, pageSize: number }) =>
method: 'get' method: 'get'
}) })
export const PostRomm = (data: any) => export const PostRoom = (data: any) =>
request({ request({
url: `/home/room`, url: `/home/room`,
method: 'post', method: 'post',
data, data,
}) })
export const DeleteRoom = (roomId: string) =>
request({
url: `/home/room?roomId=${roomId}`,
method: 'delete',
})
export const GetCheckoutRoomNum = (roomNum: string) => export const GetCheckoutRoomNum = (roomNum: string) =>
request({ request({
@ -30,3 +35,15 @@ export const GetRoomInfo = (roomNum: string) =>
method: 'get', method: 'get',
}) })
export const GetAgoraConf = () =>
request({
url: `/home/agora-conf`,
method: 'get',
})
export const GetRecord = (beginTimestamp: number, endTimestamp: number, roomNum: string) =>
request({
url: `/home/record?beginTimestamp=${beginTimestamp}&endTimestamp=${endTimestamp}&roomNum=${roomNum}`,
method: 'get',
})

View File

@ -38,3 +38,10 @@ export const GetRoleDpList = () =>
url: `/pub/role-dp-list`, url: `/pub/role-dp-list`,
method: 'get', method: 'get',
}) })
export const PostUserImport = (data: any) =>
request({
url: `/user/import`,
method: 'post',
data
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 622 B

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 B

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 530 B

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 B

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 977 B

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 760 B

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 892 B

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 769 B

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 973 B

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 641 B

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 963 B

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 700 B

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
src/assets/icon49.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1,23 @@
.equipmentManagement {
padding: 10px;
box-sizing: border-box;
>div:nth-child(1) {
>div {
display: flex;
align-items: center;
margin-bottom: 10px;
>span {
color: #EEEEEE;
font-size: 16px;
margin-right: 10px;
}
}
}
>div:nth-child(2) {
display: flex;
justify-content: center;
margin-top: 20px;
}
}

View File

@ -0,0 +1,103 @@
import styles from '@/components/EquipmentManagement/index.module.scss'
import { onInvoke } from '@/utils/package/signalr';
import { Button, Modal, Select, Slider, message } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react";
const EquipmentManagement = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeModal: async (uid: string, userName: string) => {
setCallerUid(uid)
setDeviceInfo({})
await onInvoke('getDrivers', {
uid
})
setUserName(userName)
setEquipmentManagementModal(true)
},
setData: (data: any) => {
setDeviceInfo(data)
}
}))
const [equipmentManagementModal, setEquipmentManagementModal] = useState(false)
const [callerUid, setCallerUid] = useState('')
const [deviceInfo, setDeviceInfo] = useState<any>({})
const [userName, setUserName] = useState<any>({})
return (
<>
<Modal
title={`设备管理(${userName})`}
open={equipmentManagementModal}
footer={null}
centered
width={'500px'}
onCancel={() => {
setEquipmentManagementModal(false)
}}>
<div className={styles.equipmentManagement}>
<div>
<div>
<span></span>
<Select
placeholder={deviceInfo?.videoList?.length ? '请选择设备' : '未检测到摄像头'}
options={deviceInfo?.videoList} style={{ flexGrow: 1 }}
value={deviceInfo?.videoDeviceId} onChange={async (e) => {
setDeviceInfo({
...deviceInfo,
videoDeviceId: e
})
}} />
</div>
<div>
<span></span>
<Select
placeholder={deviceInfo?.ecordingList?.length ? '请选择设备' : '未检测到麦克风'}
options={deviceInfo?.ecordingList} style={{ flexGrow: 1 }}
value={deviceInfo?.ecordingDeviceId} onChange={async (e) => {
setDeviceInfo({
...deviceInfo,
ecordingDeviceId: e
})
}} />;
</div>
<div>
<span></span>
<Slider value={deviceInfo.ecordingVolume} style={{ flexGrow: 1 }} max={255}
onChange={async (e) => {
setDeviceInfo({
...deviceInfo,
ecordingVolume: e
})
}} disabled={!deviceInfo.ecordingDeviceId} />
</div>
<div>
<span></span>
<Select
placeholder={deviceInfo?.playBackList?.length ? '请选择设备' : '未检测到麦克风'}
options={deviceInfo?.playBackList} style={{ flexGrow: 1 }}
value={deviceInfo?.playBackDeviceId} onChange={async (e) => {
setDeviceInfo({
...deviceInfo,
playBackDeviceId: e
})
}} />;
</div>
</div>
<div>
<Button type="primary" className='m-ant-btn' onClick={async () => {
await onInvoke('setDrivers', {
uid: callerUid,
driversJsonString: JSON.stringify(deviceInfo)
})
setEquipmentManagementModal(false)
message.success('设置成功')
}}></Button>
<Button type="primary"
style={{ backgroundColor: '#31353A', marginLeft: '10px' }}
onClick={() => setEquipmentManagementModal(false)}></Button>
</div>
</div>
</Modal >
</>
)
})
export default EquipmentManagement

View File

@ -8,6 +8,7 @@ import { PostRefresh } from '@/api/Login';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { agora } from '@/utils/package/agora'; import { agora } from '@/utils/package/agora';
import { role } from '@/config/role';
let time = null as any; let time = null as any;
const JoinSetting = forwardRef((_props: any, ref: any) => { const JoinSetting = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
@ -71,7 +72,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
}) })
} }
const getRoomRtcToken = async (roomNum: string, callBack: Function): Promise<void> => { const getRoomRtcToken = async (roomNum: string, callBack: Function): Promise<void> => {
Promise.all([GetRoomRtcToken(roomNum), GetRoomRtcToken(roomNum + '1')]).then(res => { Promise.all([GetRoomRtcToken(roomNum), GetRoomRtcToken(roomNum + 'a')]).then(res => {
if (res[0].code === 200 && res[1].code === 200) { if (res[0].code === 200 && res[1].code === 200) {
callBack({ callBack({
token: res[0].data, token: res[0].data,
@ -84,6 +85,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
await PostRefresh(user.refresh_token).then(res => { await PostRefresh(user.refresh_token).then(res => {
if (res.code === 200) { if (res.code === 200) {
storage.setItem('user', JSON.stringify(res.data)) storage.setItem('user', JSON.stringify(res.data))
storage.setItem('userLogin', true)
callBack(res.data) callBack(res.data)
} }
}) })
@ -126,7 +128,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
{ {
joinRoomSettingForm.map((item, index) => { joinRoomSettingForm.map((item, index) => {
return <div key={index} onClick={async () => { return <div key={index} onClick={async () => {
if (user.roleId === '1') { if (role.ID.includes(user.roleId)) {
let msg = ''; let msg = '';
if (index === 0) { if (index === 0) {
await agora.getAudioMediaList().then(res => { await agora.getAudioMediaList().then(res => {

View File

@ -7,14 +7,15 @@ import {
VerticalAlignBottomOutlined VerticalAlignBottomOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Button, Input, message, Modal, Pagination, Progress, Table } from 'antd'; import { Button, Input, message, Modal, Pagination, Progress, Table } from 'antd';
import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; import { forwardRef, useEffect, useImperativeHandle, useState, useRef } from "react";
import { DeleteRoomFile, GetRoomFile, GetRoomFileDwUrl, GetRoomUpFileurl, GetRoomUserItem, PostRoomFile } from '@/api/Meeting'; import { DeleteRoomFile, GetRoomFile, GetRoomFileDwUrl, GetRoomUpFileurl, GetRoomUserItem, PostRoomFile } from '@/api/Meeting';
import axios from 'axios'; import axios from 'axios';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { storage } from '@/utils'; import { storage } from '@/utils';
import StupWizard from '../StupWizard';
import { role } from '@/config/role';
const fs = require('fs').promises; const fs = require('fs').promises;
const { exec } = require('child_process'); const { exec } = require('child_process');
const { Column } = Table const { Column } = Table
const SharedFilesModel = forwardRef((props: any, ref: any) => { const SharedFilesModel = forwardRef((props: any, ref: any) => {
@ -26,8 +27,10 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
setRoomUserItem(res.data) setRoomUserItem(res.data)
} }
}) })
getRoomFile()
} }
})) }))
const stupWizardRef = useRef<any>();
const { state } = useLocation(); const { state } = useLocation();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [roomUserItem, setRoomUserItem] = useState<any>({}) const [roomUserItem, setRoomUserItem] = useState<any>({})
@ -126,7 +129,7 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
}) })
} }
}} /> }} />
{roomUserItem && roomUserItem.roleId === '1' || roomUserItem.isRoomManager ? <ProfileOutlined title={showRowSelection ? '取消框选' : '显示框选'} onClick={() => { {roomUserItem && role.ID.includes(roomUserItem.roleId) || roomUserItem.isRoomManager ? <ProfileOutlined title={showRowSelection ? '取消框选' : '显示框选'} onClick={() => {
setShowRowSelection(!showRowSelection) setShowRowSelection(!showRowSelection)
}} style={{ color: showRowSelection ? '#5575F2' : 'white' }} /> : null} }} style={{ color: showRowSelection ? '#5575F2' : 'white' }} /> : null}
{showRowSelection ? <DeleteOutlined title='删除' onClick={() => { {showRowSelection ? <DeleteOutlined title='删除' onClick={() => {
@ -310,7 +313,11 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
} }
} catch (error: any) { } catch (error: any) {
if (error.code === 'ENOENT') { if (error.code === 'ENOENT') {
message.error('文件夹不存在!') message.error({
content: <div> <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
stupWizardRef.current.changeModal(4)
}}></span></div>,
})
return return
} else { } else {
message.error(error) message.error(error)
@ -323,7 +330,6 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
}) })
} }
}) })
}} /> }} />
{/* <FolderOutlined title='文件' style={{ color: '#FFA000', cursor: 'pointer', marginLeft: '10px' }} /> */} {/* <FolderOutlined title='文件' style={{ color: '#FFA000', cursor: 'pointer', marginLeft: '10px' }} /> */}
</> </>
@ -344,6 +350,7 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
</div> </div>
</div> </div>
</Modal> </Modal>
<StupWizard ref={stupWizardRef} />
</> </>
) )
}) })

View File

@ -1,6 +1,6 @@
import styles from '@/components/StupWizard/index.module.scss' import styles from '@/components/StupWizard/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl'; import ImageUrl from '@/utils/package/imageUrl';
import { Button, Checkbox, Empty, Input, message, Modal, Popover, Radio, Select, Slider } from 'antd'; import { Button, Checkbox, Empty, Input, message, Modal, Popover, Radio, Select, Slider, Space } from 'antd';
import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { agora } from '@/utils/package/agora' import { agora } from '@/utils/package/agora'
import { CloseOutlined, LoadingOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import { CloseOutlined, LoadingOutlined, QuestionCircleOutlined } from '@ant-design/icons';
@ -11,7 +11,7 @@ const fs = require('fs').promises;
const { exec } = require('child_process'); const { exec } = require('child_process');
const StupWizard = forwardRef((props: any, ref: any) => { const StupWizard = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeModal: () => { changeModal: (index: number = 0) => {
if (location.hash.indexOf('/meeting') === -1) { if (location.hash.indexOf('/meeting') === -1) {
agora.init() agora.init()
} }
@ -19,7 +19,7 @@ const StupWizard = forwardRef((props: any, ref: any) => {
res.forEach((item: any) => { res.forEach((item: any) => {
item.active = false item.active = false
}); });
res[0].active = true; res[index].active = true;
return res return res
}) })
setIsStupWizard(true) setIsStupWizard(true)
@ -243,6 +243,8 @@ const AudioComponents = () => {
ecordingActive: false, ecordingActive: false,
ecordingVolume: 0, ecordingVolume: 0,
autoEcordingVolume: true, autoEcordingVolume: true,
isAINoiseReduction: true,
aINoiseReduction: 1
}); });
const setting = JSON.parse(storage.getItem('setting') as string) const setting = JSON.parse(storage.getItem('setting') as string)
useEffect(() => { useEffect(() => {
@ -288,7 +290,9 @@ const AudioComponents = () => {
ecordingItem: setting.ecordingDeviceId, ecordingItem: setting.ecordingDeviceId,
ecordingVolume: setting.ecordingVolume, ecordingVolume: setting.ecordingVolume,
playBackVolume: setting.playBackVolume, playBackVolume: setting.playBackVolume,
autoEcordingVolume: setting.autoEcordingVolume autoEcordingVolume: setting.autoEcordingVolume,
isAINoiseReduction: setting.isAINoiseReduction,
aINoiseReduction: setting.aINoiseReduction,
}) })
} }
return ( return (
@ -350,6 +354,38 @@ const AudioComponents = () => {
}) })
}} disabled={!audioDeviceManager.ecordingItem} /> }} disabled={!audioDeviceManager.ecordingItem} />
</div> </div>
<div>
<div>
<Checkbox checked={audioDeviceManager.isAINoiseReduction} onChange={(e) => {
setting.isAINoiseReduction = e.target.checked;
storage.setItem('setting', JSON.stringify(setting))
setAudioDeviceManager({
...audioDeviceManager,
isAINoiseReduction: e.target.checked
})
agora.setAINSMode(e.target.checked, audioDeviceManager.aINoiseReduction)
}}>
AI降噪
</Checkbox>
</div>
<div style={{ margin: '10px 0 0 20px' }}>
<Radio.Group onChange={(e) => {
setting.aINoiseReduction = e.target.value;
storage.setItem('setting', JSON.stringify(setting))
setAudioDeviceManager({
...audioDeviceManager,
aINoiseReduction: e.target.value
})
agora.setAINSMode(audioDeviceManager.isAINoiseReduction, e.target.value)
}} disabled={!audioDeviceManager.isAINoiseReduction} value={audioDeviceManager.aINoiseReduction}>
<Space direction="vertical">
<Radio value={0}></Radio>
<Radio value={1}></Radio>
<Radio value={2}></Radio>
</Space>
</Radio.Group>
</div>
</div>
{/* <div> {/* <div>
<Checkbox onChange={async (e) => { <Checkbox onChange={async (e) => {
setting.autoEcordingVolume = e.target.checked; setting.autoEcordingVolume = e.target.checked;

View File

@ -22,7 +22,7 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
const [updateContent, setUpdateContent] = useState('') // 版本更新内容 const [updateContent, setUpdateContent] = useState('') // 版本更新内容
function getContent() { function getContent() {
fetch(`https://update.23544.com/metting/update.txt?t=${+new Date()}`) // 配置服务器地址 fetch(`https://meeting-api.23544.com/meeting/update/update.txt?t=${+new Date()}`) // 配置服务器地址
.then(async response => { .then(async response => {
if (response.status === 200) { if (response.status === 200) {
return setUpdateContent(await response.text()) return setUpdateContent(await response.text())
@ -67,7 +67,7 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
></Button> ></Button>
<div className={styles.button2} onClick={() => setIsUpdateModal(false)}></div> <div className={styles.button2} onClick={() => setIsUpdateModal(false)}></div>
</div> : progress < 100 ? </div> : progress < 100 ?
<div style={{ marginTop: '20px' }}> <div style={{ margin: '20px 0' }}>
{progress}% {progress}%
<Flex gap="small" vertical> <Flex gap="small" vertical>
<Progress percent={progress} showInfo={false} /> <Progress percent={progress} showInfo={false} />
@ -75,7 +75,7 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
</div> : </div> :
<Button type="primary" <Button type="primary"
onClick={() => window.electron.onDownload('2')} onClick={() => window.electron.onDownload('2')}
style={{ width: '100%', height: '40px', marginTop: '20px' }} style={{ width: '100%', height: '40px', margin: '20px 0' }}
className={`m-ant-btn`} className={`m-ant-btn`}
></Button> ></Button>
} }

View File

@ -2,40 +2,24 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: rgb(7, 9, 11); background-color: rgb(7, 9, 11);
padding: 20px;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.userVideoTitle {
text-align: center;
color: white;
font-size: 20px;
flex-shrink: 0;
position: relative;
>span {
cursor: pointer;
position: absolute;
right: 20px;
top: 50%;
transform: translate(0, -50%);
}
}
.userVideoContent { .userVideoContent {
flex-grow: 1; flex-grow: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 20px 40px 0;
.userVideoContentHeader { .userVideoContentHeader {
flex-shrink: 0;
margin-bottom: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
flex-shrink: 0; padding: 10px;
margin-bottom: 10px; box-sizing: border-box;
padding: 0 10px;
>div:nth-child(1) { >div:nth-child(1) {
display: flex; display: flex;
@ -45,12 +29,16 @@
background-color: #181A1D; background-color: #181A1D;
border-radius: 4px; border-radius: 4px;
padding: 4px 10px; padding: 4px 10px;
box-sizing: border-box;
margin: 0 10px 0 0; margin: 0 10px 0 0;
display: flex;
align-items: center;
>span { >span {
color: #EEEEEE; color: #EEEEEE;
font-size: 14px; font-size: 12px;
margin-right: 10px; white-space: nowrap;
margin-right: 4px;
} }
} }
} }
@ -61,8 +49,8 @@
>span { >span {
color: #EEEEEE; color: #EEEEEE;
font-size: 14px; font-size: 12px;
margin-right: 10px; margin-right: 4px;
} }
} }
} }
@ -77,9 +65,9 @@
height: 0; height: 0;
.userVideoContentListItem { .userVideoContentListItem {
height: 20%; height: 32%;
width: calc(100% / 4 - 20px); width: calc(100% / 4 - 8px);
padding: 10px; padding: 4px;
.userVideoContentListItemVideo { .userVideoContentListItemVideo {
background-color: #101317; background-color: #101317;
@ -87,6 +75,18 @@
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative;
>div:nth-child(1) {
background-color: rgb(253, 194, 41);
color: black;
position: absolute;
left: 4px;
bottom: 4px;
font-size: 12px;
z-index: 1;
padding: 2px 4px;
}
} }
} }
} }

View File

@ -1,14 +1,13 @@
import styles from '@/components/UserVideo/index.module.scss'
import { GetPolling } from '@/api/Meeting'; import { GetPolling } from '@/api/Meeting';
import styles from '@/page/UserVideo/index.module.scss'
import { storage } from '@/utils';
import { agora } from '@/utils/package/agora'; import { agora } from '@/utils/package/agora';
import { CloseOutlined } from '@ant-design/icons';
import { Button, Empty, Select, message } from 'antd'; import { Button, Empty, Select, message } from 'antd';
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useLocation } from 'react-router';
import { VideoStreamType } from 'agora-electron-sdk';
const UserVideo: React.FC = () => { const UserVideo: React.FC = () => {
let userInfo = JSON.parse(storage.getItem('user') as string) const { state } = useLocation();
const [user, setUser] = useState<any>({});
const [from, setFrom] = useState<any>({ const [from, setFrom] = useState<any>({
cycleIntervalList: [ cycleIntervalList: [
{ value: 30, label: '30秒' }, { value: 30, label: '30秒' },
@ -18,19 +17,16 @@ const UserVideo: React.FC = () => {
], ],
cycleIntervalValue: 30, cycleIntervalValue: 30,
viewPeople: [ viewPeople: [
{ value: 4, label: '4人' }, { value: 6, label: '6人' },
{ value: 8, label: '8人' },
{ value: 12, label: '12人' }, { value: 12, label: '12人' },
{ value: 16, label: '16人' }, // { value: 20, label: '20人' },
{ value: 20, label: '20人' },
], ],
viewPeopleValue: 4, viewPeopleValue: 6,
}) })
const [timeNumber, setTimeNumber] = useState(30); const [timeNumber, setTimeNumber] = useState(30);
const [timeStatus, setTimeStatus] = useState(false); const [timeStatus, setTimeStatus] = useState(false);
const [userList, setUserList] = useState([]); const [userList, setUserList] = useState([]);
useEffect(() => { useEffect(() => {
setUser(userInfo)
window.addEventListener('customStorageChange', handleCustomStorageChange); window.addEventListener('customStorageChange', handleCustomStorageChange);
return () => { return () => {
window.removeEventListener('customStorageChange', handleCustomStorageChange); window.removeEventListener('customStorageChange', handleCustomStorageChange);
@ -63,12 +59,14 @@ const UserVideo: React.FC = () => {
}, [from.viewPeopleValue]) }, [from.viewPeopleValue])
useEffect(() => { useEffect(() => {
userList.forEach((item: any) => { userList.forEach(async (item: any) => {
// agora.meetingMonitoringSetupRemoteVideoJoin({ await agora.destroyRendererByConfig(Number('1' + item.screenShareId))
// uid: Number('1' + item.screenShareId), await agora.setupRemoteVideoEx({
// view: document.getElementById(`video-${item.uid}`), uid: Number('1' + item.screenShareId),
// channelId: getQueryParameterRegex('channelId'), view: document.getElementById(`video-${item.screenShareId}`),
// }) channelId: state.channelId + 'a',
})
await agora.setRemoteVideoStreamType(Number('1' + item.screenShareId), VideoStreamType.VideoStreamLow, false)
}) })
}, [userList]) }, [userList])
// 监听缓存变化 // 监听缓存变化
@ -77,30 +75,22 @@ const UserVideo: React.FC = () => {
} }
}; };
// 获取地址栏参数
const getQueryParameterRegex = (name: string): string | null => {
const reg = new RegExp(`[?&]${name}=([^&#]*)`);
const results = window.location.href.match(reg);
return results === null ? null : results[1];
}
// 获取轮训用户 // 获取轮训用户
const getPolling = async (): Promise<void> => { const getPolling = async (): Promise<void> => {
GetPolling(getQueryParameterRegex('channelId')?.split('1')[0] as string, from.viewPeopleValue).then((res: any) => { setUserList([])
if (res.code === 200) { setFrom((res: any) => {
setUserList(res.data) GetPolling(state.channelId?.split('a')[0] as string, res.viewPeopleValue).then((res: any) => {
} if (res.code === 200) {
setUserList(res.data)
}
})
return res
}) })
}; };
return ( return (
<> <>
<div className={styles.userVideo}> <div className={styles.userVideo}>
<div className={styles.userVideoTitle}> <div className={`${styles.userVideoContent}`}>
<CloseOutlined className='drag' onClick={() => {
window.electron.closeMonitorWindow()
}} />
</div>
<div className={`${styles.userVideoContent} drag`}>
<div className={styles.userVideoContentHeader}> <div className={styles.userVideoContentHeader}>
<div> <div>
<div> <div>
@ -108,6 +98,7 @@ const UserVideo: React.FC = () => {
<Select <Select
placeholder='请选择循环间隔' placeholder='请选择循环间隔'
options={from.cycleIntervalList} options={from.cycleIntervalList}
size={'small'}
value={from.cycleIntervalValue} onChange={(e) => { value={from.cycleIntervalValue} onChange={(e) => {
setFrom({ ...from, cycleIntervalValue: e }) setFrom({ ...from, cycleIntervalValue: e })
setTimeNumber(e) setTimeNumber(e)
@ -118,6 +109,7 @@ const UserVideo: React.FC = () => {
<Select <Select
placeholder='请选择查看人数' placeholder='请选择查看人数'
options={from.viewPeople} options={from.viewPeople}
size={'small'}
value={from.viewPeopleValue} onChange={(e) => { value={from.viewPeopleValue} onChange={(e) => {
setFrom({ ...from, viewPeopleValue: e }) setFrom({ ...from, viewPeopleValue: e })
}} /> }} />
@ -127,6 +119,7 @@ const UserVideo: React.FC = () => {
<span>{timeNumber}</span> <span>{timeNumber}</span>
{timeStatus ? <Button {timeStatus ? <Button
type="primary" type="primary"
size={'small'}
onClick={() => { onClick={() => {
setTimeStatus(!timeStatus) setTimeStatus(!timeStatus)
}} }}
@ -135,6 +128,7 @@ const UserVideo: React.FC = () => {
<Button <Button
type="primary" type="primary"
className='m-ant-btn' className='m-ant-btn'
size={'small'}
onClick={() => { onClick={() => {
setTimeStatus(!timeStatus) setTimeStatus(!timeStatus)
}} }}
@ -145,8 +139,8 @@ const UserVideo: React.FC = () => {
{ {
userList.map((item: any, index: number) => { userList.map((item: any, index: number) => {
return <div className={styles.userVideoContentListItem} key={index}> return <div className={styles.userVideoContentListItem} key={index}>
<div className={styles.userVideoContentListItemVideo} id={`video-${item.uid}`}> <div className={styles.userVideoContentListItemVideo} id={`video-${item.screenShareId}`}>
<div>{item.userName}{item.isRoomManager ? '(发言中)' : ''}</div>
</div> </div>
</div> </div>
}) })

3
src/config/role.ts Normal file
View File

@ -0,0 +1,3 @@
export const role = {
ID: ['1', '3']
}

View File

@ -1,14 +1,21 @@
import styles from '@/page/Home/Index/index.module.scss' import styles from '@/page/Home/Index/index.module.scss'
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import Operation from '@/components/Operation'; import Operation from '@/components/Operation';
import { Button, Input, Modal, Pagination, Empty, message } from "antd"; import { Button, Input, Modal, Pagination, Empty, message, Popover, Popconfirm, DatePicker } from "antd";
import { GetRoom, PostRomm, GetCheckoutRoomNum, GetRoomRtcToken } from '@/api/Home/Index'; import { GetRoom, PostRoom, GetCheckoutRoomNum, GetRoomRtcToken, DeleteRoom, GetRecord } from '@/api/Home/Index';
import ImageUrl from '@/utils/package/imageUrl' import ImageUrl from '@/utils/package/imageUrl'
import { ReloadOutlined } from '@ant-design/icons'; import { ExclamationCircleFilled, ReloadOutlined } from '@ant-design/icons';
import JoinSetting from '@/components/JoinSetting'; import JoinSetting from '@/components/JoinSetting';
import { storage } from '@/utils'; import { storage } from '@/utils';
import { PostRefresh } from '@/api/Login'; import { PostRefresh } from '@/api/Login';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { role } from '@/config/role';
import dayjs from 'dayjs';
import StupWizard from '@/components/StupWizard';
const fs = require('fs').promises;
const { exec } = require('child_process');
const { RangePicker } = DatePicker;
const { confirm } = Modal;
const Index: React.FC = () => { const Index: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [list, setList] = useState({ const [list, setList] = useState({
@ -18,12 +25,15 @@ const Index: React.FC = () => {
pageSize: 12, pageSize: 12,
}) })
const [createRoomModal, setCreateRoomModal] = useState(false) const [createRoomModal, setCreateRoomModal] = useState(false)
const [timeSelectModal, setTimeSelectModal] = useState(false)
const [createRoomFrom, setCreateRoomFrom] = useState<{ roomName: string, roomNum: string }>({ const [createRoomFrom, setCreateRoomFrom] = useState<{ roomName: string, roomNum: string }>({
roomName: "", roomName: "",
roomNum: "" roomNum: ""
}) })
const joinSettingRef = useRef<any>(); const joinSettingRef = useRef<any>();
const stupWizardRef = useRef<any>();
const [user, setUser] = useState<any>({}); const [user, setUser] = useState<any>({});
const [currentRoomInfo, setCurrentRoomInfo] = useState<any>({});
const userInfo = JSON.parse(storage.getItem('user') as string) const userInfo = JSON.parse(storage.getItem('user') as string)
useEffect(() => { useEffect(() => {
setUser(userInfo) setUser(userInfo)
@ -70,7 +80,7 @@ const Index: React.FC = () => {
}) })
} }
const getRoomRtcToken = async (roomNum: string, callBack: Function): Promise<void> => { const getRoomRtcToken = async (roomNum: string, callBack: Function): Promise<void> => {
Promise.all([GetRoomRtcToken(roomNum), GetRoomRtcToken(roomNum + '1')]).then(res => { Promise.all([GetRoomRtcToken(roomNum), GetRoomRtcToken(roomNum + 'a')]).then(res => {
if (res[0].code === 200 && res[1].code === 200) { if (res[0].code === 200 && res[1].code === 200) {
callBack({ callBack({
token: res[0].data, token: res[0].data,
@ -83,10 +93,59 @@ const Index: React.FC = () => {
await PostRefresh(user.refresh_token).then(res => { await PostRefresh(user.refresh_token).then(res => {
if (res.code === 200) { if (res.code === 200) {
storage.setItem('user', JSON.stringify(res.data)) storage.setItem('user', JSON.stringify(res.data))
storage.setItem('userLogin', true)
callBack(res.data) callBack(res.data)
} }
}) })
} }
const changeOpen = (index: number, bool: boolean): void => {
const newList = [...list.data] as any;
newList[index].open = bool
setList({
...list,
data: newList
})
}
const fileUpLoad = async (data: { url: string, content: string, fileName: string }): Promise<void> => {
const setting = await JSON.parse(storage.getItem('setting') as string)
try {
const response = await fetch(data.url);
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
await fs.writeFile(`${setting.shareFilesPath}\\${data.fileName}`, buffer, {});
confirm({
title: '提示',
icon: <ExclamationCircleFilled />,
content: data.content,
centered: true,
okText: '打开文件夹',
cancelText: '关闭',
async onOk() {
await fs.access(setting.shareFilesPath, fs.constants.F_OK);
if (process.platform === 'win32') {
exec(`explorer "${setting.shareFilesPath}"`);
} else if (process.platform === 'darwin') {
exec(`open "${setting.shareFilesPath}"`);
}
},
onCancel() {
}
})
} catch (error: any) {
if (error.code === 'ENOENT') {
message.error({
content: <div> <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
stupWizardRef.current.changeModal(4)
}}></span></div>
})
return
} else {
message.error(error)
}
}
}
return ( return (
<> <>
<div className={styles.index}> <div className={styles.index}>
@ -147,12 +206,52 @@ const Index: React.FC = () => {
<img src={ImageUrl.icon10} alt="" /> <img src={ImageUrl.icon10} alt="" />
</div> </div>
<div> <div>
<Popover
{/* <Button type="primary" danger>设置</Button> */} content={
<div className='meetingContentFooterPopover'>
<Popconfirm
title="提示"
description={`确定删除该会议吗`}
onConfirm={async () => {
DeleteRoom(item.id).then((res) => {
if (res.code === 200) {
message.success('删除成功')
changeOpen(index, false)
getRoomList()
}
})
}}
onCancel={() => {
changeOpen(index, false)
}}
okText="确定"
cancelText="取消"
>
<div></div>
</Popconfirm>
<div onClick={() => {
changeOpen(index, false)
setTimeSelectModal(true)
}}></div>
<div onClick={() => {
changeOpen(index, false)
}}></div>
</div>
}
title=""
trigger="click"
open={item.open}
onOpenChange={() => {
setCurrentRoomInfo(list.data[index])
changeOpen(index, true)
}}
>
<Button type="primary" danger></Button>
</Popover>
<Button type="primary" <Button type="primary"
iconPosition={'end'} iconPosition={'end'}
onClick={async () => { onClick={async () => {
if (userInfo.roleId === '1') { if (role.ID.includes(userInfo.roleId)) {
joinSettingRef.current.changeModal(item.roomNum) joinSettingRef.current.changeModal(item.roomNum)
} else { } else {
postRefresh(() => { postRefresh(() => {
@ -267,7 +366,7 @@ const Index: React.FC = () => {
if (bool) { if (bool) {
message.error('房间号已存在!') message.error('房间号已存在!')
} else { } else {
PostRomm(createRoomFrom).then(res => { PostRoom(createRoomFrom).then(res => {
if (res.code === 200) { if (res.code === 200) {
message.success('创建成功!') message.success('创建成功!')
setCreateRoomModal(false) setCreateRoomModal(false)
@ -280,7 +379,32 @@ const Index: React.FC = () => {
</div> </div>
</div> </div>
</Modal> </Modal>
<Modal title="选择时间段" destroyOnClose={true} open={timeSelectModal} footer={null} onCancel={() => setTimeSelectModal(false)} centered width={'400px'}>
<div>
<RangePicker
showTime={{ format: 'YYYY-MM-DD HH:mm:ss' }}
format="YYYY-MM-DD HH:mm:ss"
onChange={(_value, dateString) => {
const setting = JSON.parse(storage.getItem('setting') as string)
if (dateString.length === 2) {
GetRecord(dayjs(dateString[0]).unix(), dayjs(dateString[1]).unix(), currentRoomInfo.roomNum).then(res => {
if (res.code === 200) {
const fileName = res.data.split('/').pop().split('?')[0];
fileUpLoad({
url: res.data,
content: `下载参会记录成功!文件已保存至:${setting.shareFilesPath}`,
fileName
})
}
setTimeSelectModal(false)
})
}
}}
/>
</div>
</Modal>
<JoinSetting ref={joinSettingRef} /> <JoinSetting ref={joinSettingRef} />
<StupWizard ref={stupWizardRef} />
</> </>
) )
} }

View File

@ -21,11 +21,14 @@
display: flex; display: flex;
align-items: center; align-items: center;
>button {
margin-right: 22px;
}
.userBtnsDel { .userBtnsDel {
background-color: #3A1457; background-color: #3A1457;
box-shadow: none; box-shadow: none;
color: white; color: white;
margin-left: 22px;
&:hover { &:hover {
background-color: lighten(#3A1457, 5%) !important; background-color: lighten(#3A1457, 5%) !important;

View File

@ -1,13 +1,19 @@
import styles from '@/page/Home/User/index.module.scss' import styles from '@/page/Home/User/index.module.scss'
import { useEffect, useState } from "react"; import { useEffect, useState, useRef } from "react";
import Operation from '@/components/Operation'; import Operation from '@/components/Operation';
import { Button, Input, Table, Pagination, Modal, message, Select } from "antd"; import { Button, Input, Table, Pagination, Modal, message, Select } from "antd";
import { SearchOutlined } from '@ant-design/icons'; import { ExclamationCircleFilled, SearchOutlined } from '@ant-design/icons';
import { GetUserList, PostUser, PutUser, DeleteUser, PutUserPwd, GetRoleDpList } from '@/api/Home/User'; import { GetUserList, PostUser, PutUser, DeleteUser, PutUserPwd, GetRoleDpList, PostUserImport } from '@/api/Home/User';
import * as CryptoJS from 'crypto-js'; import * as CryptoJS from 'crypto-js';
import ImageUrl from '@/utils/package/imageUrl'; import ImageUrl from '@/utils/package/imageUrl';
import { storage } from '@/utils';
import StupWizard from '@/components/StupWizard';
const { Column } = Table const { Column } = Table
const { confirm } = Modal;
const { exec } = require('child_process');
const fs = require('fs').promises;
const User: React.FC = () => { const User: React.FC = () => {
const stupWizardRef = useRef<any>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [isCreateUser, setIsCreateUser] = useState(false); const [isCreateUser, setIsCreateUser] = useState(false);
const [list, setList] = useState({ const [list, setList] = useState({
@ -27,6 +33,7 @@ const User: React.FC = () => {
UserName: "" UserName: ""
}) })
const [changeUserPawModal, setChangeUserPawModal] = useState(false) const [changeUserPawModal, setChangeUserPawModal] = useState(false)
const [changeImportModal, setChangeImportModal] = useState(false)
const [changeUserPawFrom, setChangeUserPawFrom] = useState({ const [changeUserPawFrom, setChangeUserPawFrom] = useState({
Pwd: "", Pwd: "",
newPwd: '', newPwd: '',
@ -72,6 +79,46 @@ const User: React.FC = () => {
} }
}) })
} }
const fileUpLoad = async (data: { url: string, content: string, fileName: string }): Promise<void> => {
const setting = await JSON.parse(storage.getItem('setting') as string)
try {
const response = await fetch(data.url);
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
await fs.writeFile(`${setting.shareFilesPath}\\${data.fileName}`, buffer, {});
setChangeImportModal(false)
confirm({
title: '提示',
icon: <ExclamationCircleFilled />,
content: data.content,
centered: true,
okText: '打开文件夹',
cancelText: '关闭',
async onOk() {
await fs.access(setting.shareFilesPath, fs.constants.F_OK);
if (process.platform === 'win32') {
exec(`explorer "${setting.shareFilesPath}"`);
} else if (process.platform === 'darwin') {
exec(`open "${setting.shareFilesPath}"`);
}
},
onCancel() {
}
})
} catch (error: any) {
if (error.code === 'ENOENT') {
message.error({
content: <div> <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
stupWizardRef.current.changeModal(4)
}}></span></div>
})
return
} else {
message.error(error)
}
}
}
return ( return (
<> <>
<div className={styles.user}> <div className={styles.user}>
@ -100,6 +147,13 @@ const User: React.FC = () => {
className='m-ant-btn'> className='m-ant-btn'>
</Button> </Button>
<Button type="primary"
onClick={() => {
setChangeImportModal(true)
}}
className='m-ant-btn'>
</Button>
<Button type="primary" <Button type="primary"
icon={<img src={ImageUrl.icon21} alt="" />} icon={<img src={ImageUrl.icon21} alt="" />}
className={styles.userBtnsDel} className={styles.userBtnsDel}
@ -221,17 +275,14 @@ const User: React.FC = () => {
<Input <Input
style={{ flexGrow: 1 }} style={{ flexGrow: 1 }}
placeholder="请输入账号" placeholder="请输入账号"
maxLength={11} maxLength={30}
showCount={true} showCount={true}
value={addUserFrom.Account} value={addUserFrom.Account}
onChange={(e) => { onChange={(e) => {
const regex = /^[0-9]*$/; setAddUserFrom({
if (regex.test(e.target.value)) { ...addUserFrom,
setAddUserFrom({ Account: e.target.value,
...addUserFrom, });
Account: e.target.value,
});
}
}} }}
/> />
</div> </div>
@ -400,6 +451,69 @@ const User: React.FC = () => {
</div> </div>
</div> </div>
</Modal> </Modal>
<Modal title='批量导入用户' open={changeImportModal} onCancel={() => setChangeImportModal(false)} footer={null} centered width={'300px'}>
<div>
<div>
<Button type="primary" className='m-ant-btn' style={{ width: '100%', marginBottom: '10px' }}
onClick={async () => {
const setting = await JSON.parse(storage.getItem('setting') as string)
await fileUpLoad({
url: 'https://wgshare.oss-cn-chengdu.aliyuncs.com/%E7%94%A8%E6%88%B7%E6%89%B9%E9%87%8F%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF%E8%A1%A8.xlsx',
content: `下载导入模板成功!文件已保存至:${setting.shareFilesPath}`,
fileName: `用户批量导入模板表_${+new Date()}.xlsx`
})
}}
>
</Button>
</div>
<div>
<Button type="primary" className='m-ant-btn' style={{ width: '100%' }}
onClick={() => {
const file = document.createElement("input") as any;
file.type = "file";
file.accept = ".xls,.xlsx";
file.onchange = async () => {
const setting = await JSON.parse(storage.getItem('setting') as string)
const fileInfo = file.files[0];
const formData = new FormData();
formData.append("file", fileInfo);
await PostUserImport(formData).then(res => {
if (res.code === 200) {
if (res.data.item1) {
if (list.pageIndex === 1) {
getUserList()
} else {
setList({
...list,
pageIndex: 1
})
}
message.success(res.data.item3)
} else {
if (res.data.item2) {
const fileName = res.data.item2.split('/').pop().split('?')[0];
fileUpLoad({
url: res.data.item2,
content: `导入模板失败!失败文件已保存至:${setting.shareFilesPath}`,
fileName
})
}
message.error(res.data.item3)
}
}
})
setChangeImportModal(false)
};
file.click();
}}
>
</Button>
</div>
</div>
</Modal>
<StupWizard ref={stupWizardRef} />
</> </>
) )
} }

View File

@ -139,8 +139,8 @@ const Home: React.FC = () => {
title="提示" title="提示"
description="确认退出吗?" description="确认退出吗?"
onConfirm={() => { onConfirm={() => {
navigate('/login')
storage.removeItem('user') storage.removeItem('user')
storage.setItem('userLogin', false)
}} }}
onCancel={() => { onCancel={() => {

View File

@ -140,10 +140,13 @@ const Login: React.FC = () => {
optionsValue: operation.optionsValue, optionsValue: operation.optionsValue,
})) }))
storage.setItem('user', JSON.stringify(res.data)) storage.setItem('user', JSON.stringify(res.data))
storage.setItem('userLogin', true)
try { try {
window.electron.setMainWindowSize({ window.electron.getWindowSize().then((res: any) => {
width: 1200, window.electron.setMainWindowSize({
height: 800, width: Math.ceil(res.width / 1.5),
height: Math.ceil(res.height / 1.3),
})
}) })
} catch { } catch {

View File

@ -31,7 +31,30 @@
>img { >img {
width: 18px; width: 18px;
margin-right: 4px; }
>label {
height: 18px;
width: 18px;
position: relative;
>img {
width: 100%;
height: 100%;
margin-right: 4px;
}
>div {
position: absolute;
left: 0;
bottom: 0;
height: 0%;
width: 100%;
overflow: hidden;
background-repeat: no-repeat;
background-position: bottom center;
background-size: cover;
}
} }
>span { >span {
@ -160,6 +183,7 @@
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
overflow: hidden;
.standardModeIcon { .standardModeIcon {
position: absolute; position: absolute;
@ -209,6 +233,7 @@
} }
} }
// 自由者模式 // 自由者模式
.meetingContentBodyLeftFreedomMode { .meetingContentBodyLeftFreedomMode {
width: 100%; width: 100%;
@ -282,7 +307,7 @@
position: absolute !important; position: absolute !important;
bottom: 0; bottom: 0;
left: 0; left: 0;
height: calc(100% - 160px) !important; height: calc(100% - 170px) !important;
width: 100% !important; width: 100% !important;
z-index: 2; z-index: 2;
} }
@ -343,6 +368,7 @@
} }
} }
.meetingContentSwiperCardFullScreenIcon { .meetingContentSwiperCardFullScreenIcon {
position: absolute; position: absolute;
z-index: 3; z-index: 3;
@ -394,33 +420,14 @@
padding: 10px 20px; padding: 10px 20px;
white-space: nowrap; white-space: nowrap;
} }
.meetingContentSwiperCardBorder {
position: absolute;
height: 3px;
width: calc(100% - 20px);
background-color: #2C2C2C;
overflow: hidden;
left: 10px;
z-index: 1;
bottom: 9px;
>div {
background-color: #5575F2;
position: absolute;
height: 3px;
left: 0;
top: 0;
}
}
} }
.meetingContentBodyRight { .meetingContentBodyRight {
width: 300px;
flex-shrink: 0; flex-shrink: 0;
height: 100%; height: 100%;
.meetingUserList { .meetingUserList {
width: 300px;
height: 100%; height: 100%;
padding: 10px 0 20px; padding: 10px 0 20px;
box-sizing: border-box; box-sizing: border-box;
@ -535,6 +542,7 @@
} }
.meetingUserChat { .meetingUserChat {
width: 300px;
height: 100%; height: 100%;
background-color: #16191E; background-color: #16191E;
display: flex; display: flex;
@ -653,6 +661,39 @@
border-top: 1px solid #23272E; border-top: 1px solid #23272E;
} }
} }
.meetingUserVideoList {
padding: 10px 10px 10px;
box-sizing: border-box;
height: 100%;
background-color: #16191E;
display: flex;
flex-direction: column;
.meetingUserVideoListTitle {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
>span {
color: #EEEEEE;
font-size: 18px;
}
>img {
cursor: pointer;
}
}
.meetingUserVideoListContent {
flex-grow: 1;
height: 0px;
overflow-y: auto;
box-sizing: border-box;
}
}
} }
} }
@ -684,7 +725,7 @@
} }
>img { >img {
height: 30px; height: 50px;
} }
>span { >span {
@ -705,6 +746,31 @@
right: -10px; right: -10px;
} }
>label {
height: 50px;
height: 50px;
cursor: pointer;
position: relative;
>img {
width: 100%;
height: 100%;
margin-right: 4px;
}
>div {
position: absolute;
left: 0;
bottom: 0;
height: 0%;
width: 100%;
overflow: hidden;
background-repeat: no-repeat;
background-position: bottom center;
background-size: cover;
}
}
&:hover { &:hover {
background-color: #161A29; background-color: #161A29;
} }

File diff suppressed because it is too large Load Diff

4
src/render.d.ts vendored
View File

@ -1,6 +1,7 @@
// electron-env.d.ts // electron-env.d.ts
export interface IElectronAPI { export interface IElectronAPI {
setMainWindowSize: (config: any) => void; setMainWindowSize: (config: any) => void;
getWindowSize: () => any;
setViewStatus: (status: 'quit' | 'maximize' | 'minimize' | 'unmaximize' | 'hide') => void; setViewStatus: (status: 'quit' | 'maximize' | 'minimize' | 'unmaximize' | 'hide') => void;
getIsMaximized: () => Promise<boolean>; getIsMaximized: () => Promise<boolean>;
setWriteText: (text: string) => void; setWriteText: (text: string) => void;
@ -15,9 +16,6 @@ export interface IElectronAPI {
downFile: (callBack: Function) => void; downFile: (callBack: Function) => void;
quitAndInstall: (callBack: Function) => void; quitAndInstall: (callBack: Function) => void;
getVersion: () => Promise<string>; getVersion: () => Promise<string>;
oepnWindow: (data: any) => any;
closeMonitorWindow: () => void
} }
declare global { declare global {

View File

@ -5,12 +5,16 @@ import {
VideoViewSetupMode, VideoViewSetupMode,
ScreenCaptureSourceType, ScreenCaptureSourceType,
RenderModeType, RenderModeType,
ChannelProfileType ChannelProfileType,
AudioAinsMode,
SimulcastStreamMode,
VideoStreamType
} from "agora-electron-sdk"; } from "agora-electron-sdk";
import { GetRoomRtcToken } from "@/api/Home/Index"; import { GetRoomRtcToken, GetAgoraConf } from "@/api/Home/Index";
import { storage } from '@/utils'; import { storage } from '@/utils';
import { role } from "@/config/role";
const option: any = { const option: any = {
appId: 'dcfc466a6ecb4a1f972630065dfb1e75', appId: '',
token: '', token: '',
tokenA: '', tokenA: '',
channelId: '', channelId: '',
@ -21,11 +25,14 @@ let rtcEngine: any = '';
export const agora = { export const agora = {
// 初始化 // 初始化
init: async (bool: boolean = false) => { init: async (bool: boolean = false) => {
rtcEngine = createAgoraRtcEngine(); const { data } = await GetAgoraConf();
await rtcEngine.initialize({ if (data) {
appId: option.appId, rtcEngine = createAgoraRtcEngine();
}); await rtcEngine.initialize({
await agora.setDeviceManager(bool) appId: data,
});
await agora.setDeviceManager(bool)
}
}, },
// 获取rtcEngine // 获取rtcEngine
getRtcEngine: () => { getRtcEngine: () => {
@ -92,6 +99,7 @@ export const agora = {
if (setting.playBackVolume) agora.setPlaybackDeviceVolume(setting.playBackVolume) // 设置播放设备音量 if (setting.playBackVolume) agora.setPlaybackDeviceVolume(setting.playBackVolume) // 设置播放设备音量
if (setting.ecordingDeviceId) agora.setRecordingDevice(setting.ecordingDeviceId) // 设置音频采集设备 if (setting.ecordingDeviceId) agora.setRecordingDevice(setting.ecordingDeviceId) // 设置音频采集设备
if (setting.ecordingVolume) agora.setRecordingDeviceVolume(setting.ecordingVolume) // 设置音频设备音量 if (setting.ecordingVolume) agora.setRecordingDeviceVolume(setting.ecordingVolume) // 设置音频设备音量
if (setting.isAINoiseReduction) agora.setAINSMode(setting.isAINoiseReduction, setting.aINoiseReduction) // 设置ai降噪
} }
}, 1000); }, 1000);
}, },
@ -163,6 +171,20 @@ export const agora = {
); );
} }
}, },
setupRemoteVideoEx: async (item: any) => {
if (item.view?.childNodes.length === 1) {
await rtcEngine.setupRemoteVideoEx(
{
renderMode: agora.getRrenderMode(item.uid),
sourceType: VideoSourceType.VideoSourceRemote,
uid: item.uid,
view: item.view,
setupMode: VideoViewSetupMode.VideoViewSetupAdd,
},
{ channelId: item.channelId },
);
}
},
// 退出 // 退出
setupRemoteVideo: async (item: any) => { setupRemoteVideo: async (item: any) => {
await rtcEngine.setupRemoteVideo( await rtcEngine.setupRemoteVideo(
@ -193,6 +215,17 @@ export const agora = {
joinChannel: async () => { joinChannel: async () => {
await rtcEngine.enableAudioVolumeIndication(100, 1, true) await rtcEngine.enableAudioVolumeIndication(100, 1, true)
await rtcEngine.joinChannel(option.token, option.channelId, option.uid); await rtcEngine.joinChannel(option.token, option.channelId, option.uid);
await rtcEngine.setDualStreamModeEx(
SimulcastStreamMode.EnableSimulcastStream,
{
dimensions: {
width: 320,
height: 180
},
framerate: 5,
},
{ channelId: option.channelId, localUid: Number(option.uid) }
);
}, },
// 更新频道配置 // 更新频道配置
updateChannelMediaOptions: async (bool: boolean) => { updateChannelMediaOptions: async (bool: boolean) => {
@ -205,6 +238,14 @@ export const agora = {
publishScreenTrack: false,//设置是否发布屏幕采集的视频 publishScreenTrack: false,//设置是否发布屏幕采集的视频
}) })
}, },
// 设置接收大小流
setRemoteVideoStreamType: async (uid: number, type: VideoStreamType, bool: boolean) => {
await rtcEngine.setRemoteVideoStreamTypeEx(
Number(uid),
type,
bool ? { channelId: option.channelId, localUid: Number(option.uid) } : { channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) }
)
},
// 共享屏幕单独用户 // 共享屏幕单独用户
joinChannelEx: async (uid: any) => { joinChannelEx: async (uid: any) => {
await agora.leaveChannelEx(uid) await agora.leaveChannelEx(uid)
@ -222,26 +263,53 @@ export const agora = {
); );
}, },
// 所有用户加入的第二个房间 // 所有用户加入的第二个房间
allJoinChannelEx: async () => { allJoinChannelEx: async (bool: boolean = false) => {
const user = await JSON.parse(storage.getItem('user') as string)
await agora.startCameraCapture(true)
await rtcEngine.joinChannelEx( await rtcEngine.joinChannelEx(
option.tokenA, option.tokenA,
{ channelId: option.channelId + '1', localUid: Number('1' + option.screenShareId) }, { channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) },
{} {
clientRoleType: bool ? ClientRoleType.ClientRoleAudience : ClientRoleType.ClientRoleBroadcaster, //用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众
autoSubscribeAudio: false,//设置是否自动订阅所有音频流
autoSubscribeVideo: role.ID.includes(user.roleId) ? true : false,//设置是否自动订阅所有视频流
publishMicrophoneTrack: false,//设置是否发布麦克风采集到的音频
publishCameraTrack: true,//设置是否发布摄像头采集的视频
publishScreenTrack: false,//设置是否发布屏幕采集的视频
}
);
await rtcEngine.setDualStreamModeEx(
SimulcastStreamMode.EnableSimulcastStream,
{
dimensions: {
width: 320,
height: 180
},
framerate: 5,
},
{ channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) }
); );
await agora.updateChannelMediaOptionsEx(false)
}, },
updateChannelMediaOptionsEx: async (bool: boolean) => { // 退出第二个房间
rtcEngine.updateChannelMediaOptionsEx({ allLeaveChannelEx: async () => {
clientRoleType: bool ? ClientRoleType.ClientRoleBroadcaster : ClientRoleType.ClientRoleAudience, //用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众 await agora.stopCameraCapture();
autoSubscribeAudio: false,//设置是否自动订阅所有音频流 await rtcEngine.leaveChannelEx({ channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) })
autoSubscribeVideo: true,//设置是否自动订阅所有视频流 },
publishMicrophoneTrack: true,//设置是否发布麦克风采集到的音频 // 停止/恢复接收指定的视频流。
publishCameraTrack: true,//设置是否发布摄像头采集的视频 muteRemoteVideoStreamEx: async (uid: number, mute: boolean) => {
publishScreenTrack: false,//设置是否发布屏幕采集的视频 await rtcEngine.muteRemoteVideoStreamEx(uid, mute, { channelId: option.channelId, localUid: Number(option.uid) })
}, { },
channelId: option.channelId + '1', // 取消或恢复订阅指定远端用户的音频流
localUid: Number('1' + option.screenShareId) muteRemoteVideoStream: async (uid: number, mute: boolean) => {
}) rtcEngine.muteRemoteVideoStream(uid, mute)
},
// 销毁视频渲染dom
destroyRendererByConfig: async (uid: number) => {
await rtcEngine.destroyRendererByConfig(VideoSourceType.VideoSourceRemote, option.channelId + 'a', uid);
},
// ai降噪
setAINSMode: async (enabled: boolean, mode: AudioAinsMode) => {
rtcEngine.setAINSMode(enabled, mode)
}, },
// 离开共享屏幕频道 // 离开共享屏幕频道
leaveChannelEx: async (uid: any) => { leaveChannelEx: async (uid: any) => {
@ -253,19 +321,27 @@ export const agora = {
rtcEngine.enableLoopbackRecording(false) rtcEngine.enableLoopbackRecording(false)
}, },
// 取消或恢复发布本地音频流 // 取消或恢复发布本地音频流
muteLocalAudioStream: async (mute: any) => { muteLocalAudioStream: async (data: any, mute: any) => {
await rtcEngine.muteLocalAudioStream(mute) // await rtcEngine.muteLocalAudioStream(mute)
await rtcEngine.updateChannelMediaOptions({
clientRoleType: data ? ClientRoleType.ClientRoleBroadcaster : ClientRoleType.ClientRoleAudience, //用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众
autoSubscribeAudio: true,//设置是否自动订阅所有音频流
autoSubscribeVideo: true,//设置是否自动订阅所有视频流
publishMicrophoneTrack: mute,//设置是否发布麦克风采集到的音频
publishCameraTrack: true,//设置是否发布摄像头采集的视频
publishScreenTrack: false,//设置是否发布屏幕采集的视频
})
}, },
// 取消或恢复发布本地视频流 // 取消或恢复发布本地视频流
muteLocalVideoStream: async (mute: any) => { muteLocalVideoStream: async (mute: any) => {
await rtcEngine.muteLocalVideoStream(mute) await rtcEngine.muteLocalVideoStream(mute)
}, },
// 摄像头采集 // 摄像头采集
startCameraCapture: async () => { startCameraCapture: async (bool: boolean = false) => {
await rtcEngine.startCameraCapture(VideoSourceType.VideoSourceCamera, { await rtcEngine.startCameraCapture(VideoSourceType.VideoSourceCamera, {
format: { format: {
width: 640, width: bool ? 160 : 1280,
height: 360, height: bool ? 160 : 720,
fps: 15, fps: 15,
} }
}) })
@ -282,13 +358,10 @@ export const agora = {
option.uid = Number(data.uid); option.uid = Number(data.uid);
option.screenShareId = data.screenShareId; option.screenShareId = data.screenShareId;
await agora.joinChannel() await agora.joinChannel()
if (data.tokenA) {
await agora.allJoinChannelEx()
}
}, },
// 桌面捕获音频和视频的媒体源的信息 // 桌面捕获音频和视频的媒体源的信息
getDesktopCapturerVideo: async () => { getDesktopCapturerVideo: async (thumbSize: any, iconSize: any, includeScreen: boolean) => {
return rtcEngine.getScreenCaptureSources({ width: 300, height: 300 }, { width: 300, height: 300 }, true); return await rtcEngine.getScreenCaptureSources(thumbSize, iconSize, includeScreen).filter((item: any) => item.type === 1)
}, },
// 共享屏幕采集 // 共享屏幕采集
setDesktopCapturerVideo: async (targetSource: any, isComputerAudio: boolean, isFluencyPriority: boolean) => { setDesktopCapturerVideo: async (targetSource: any, isComputerAudio: boolean, isFluencyPriority: boolean) => {

View File

@ -70,6 +70,7 @@ import icon47 from '@/assets/icon47.png'
import icon47Active from '@/assets/icon47-active.png' import icon47Active from '@/assets/icon47-active.png'
import icon48 from '@/assets/icon48.png' import icon48 from '@/assets/icon48.png'
import icon48Select from '@/assets/icon48-select.png' import icon48Select from '@/assets/icon48-select.png'
import icon49 from '@/assets/icon49.png'
export default { export default {
loading, loading,
icon, icon,
@ -142,5 +143,6 @@ export default {
icon47, icon47,
icon47Active, icon47Active,
icon48, icon48,
icon48Select icon48Select,
icon49,
} }

View File

@ -160,6 +160,27 @@ export const onSignalr = (callBack: Function) => {
watchUids watchUids
}) })
}); });
// 设备列表
connection.on("DriverList", (callerUid: string) => {
callBack({
key: 'DriverList',
callerUid
})
});
// 设置设备
connection.on("SaveDriver", (driver: string) => {
callBack({
key: 'SaveDriver',
driver
})
});
// 显示设备列表
connection.on("ShowDriverList", (driversJsonString: string) => {
callBack({
key: 'ShowDriverList',
driversJsonString
})
});
} }
} }
export const offSignalr = () => { export const offSignalr = () => {
@ -178,6 +199,9 @@ export const offSignalr = () => {
connection.off('ManagerRefresh'); connection.off('ManagerRefresh');
connection.off('ApplyToSpeak'); connection.off('ApplyToSpeak');
connection.off('Watch'); connection.off('Watch');
connection.off('DriverList');
connection.off('SetDriver');
connection.off('ShowDriverList');
} }
} }
export const onInvoke = async (str: string, data: any) => { export const onInvoke = async (str: string, data: any) => {
@ -189,6 +213,18 @@ export const onInvoke = async (str: string, data: any) => {
// 4:屏幕共享 // 4:屏幕共享
await connection.invoke(str, data.roomNum, data.type) await connection.invoke(str, data.roomNum, data.type)
break; break;
case 'getDrivers':
// 获取某个人的设备列表
await connection.invoke(str, data.uid)
break;
case 'sendDrivers':
// 发送设备列表给某个人
await connection.invoke(str, data.uid, data.driversJsonString)
break;
case 'setDrivers':
// 设置某个人的设备列表
await connection.invoke(str, data.uid, data.driversJsonString)
break;
} }
} }
export const onStop = async () => { export const onStop = async () => {

View File

@ -1,5 +1,5 @@
class LocalStorage { class LocalStorage {
private constructor() {} private constructor() { }
private static instance: LocalStorage | null = null private static instance: LocalStorage | null = null

View File

@ -60,7 +60,7 @@ class Request {
message.error(resData.message) message.error(resData.message)
} }
} }
if (resData.code === 1403 || resData.code === 1000) { if (resData.code === 1403) {
toLogin() toLogin()
} }
return resData return resData
@ -113,7 +113,7 @@ class Request {
} }
function toLogin() { function toLogin() {
storage.removeItem('user') storage.removeItem('user')
location.href = location.origin + '/#/login' storage.setItem('userLogin', false)
} }
function updatePostRefresh() { function updatePostRefresh() {
let user = JSON.parse(storage.getItem('user') as string); let user = JSON.parse(storage.getItem('user') as string);
@ -121,6 +121,7 @@ function updatePostRefresh() {
PostRefresh(user.refresh_token).then((res) => { PostRefresh(user.refresh_token).then((res) => {
if (res.code == 200) { if (res.code == 200) {
storage.setItem('user', JSON.stringify(res.data)) storage.setItem('user', JSON.stringify(res.data))
storage.setItem('userLogin', true)
} else { } else {
toLogin() toLogin()
} }

View File

@ -343,11 +343,16 @@ $pagination-hover-background-color: #5575F2;
} }
.ant-radio-checked .ant-radio-inner { .ant-radio-checked .ant-radio-inner {
border-color: $btn-background-color;
background-color: $btn-background-color; background-color: $btn-background-color;
border-color: $btn-background-color;
} }
} }
:where(.css-dev-only-do-not-override-98ntnt).ant-radio-wrapper .ant-radio-disabled .ant-radio-inner {
background-color: #28282C;
border-color: #28282C;
}
// ant-notification // ant-notification
.ant-notification { .ant-notification {
.ant-notification-notice-wrapper { .ant-notification-notice-wrapper {
@ -371,3 +376,8 @@ $pagination-hover-background-color: #5575F2;
} }
} }
} }
// ant-message
.ant-message {
-webkit-app-region: no-drag;
}

View File

@ -60,7 +60,10 @@ export default defineConfig({
RenderModeType, RenderModeType,
ScreenCaptureSourceType, ScreenCaptureSourceType,
VideoSourceType, VideoSourceType,
VideoViewSetupMode VideoViewSetupMode,
AudioAinsMode,
SimulcastStreamMode,
VideoStreamType
} = require("agora-electron-sdk") } = require("agora-electron-sdk")
export { export {
createAgoraRtcEngine, createAgoraRtcEngine,
@ -69,7 +72,10 @@ export default defineConfig({
RenderModeType, RenderModeType,
ScreenCaptureSourceType, ScreenCaptureSourceType,
VideoSourceType, VideoSourceType,
VideoViewSetupMode VideoViewSetupMode,
AudioAinsMode,
SimulcastStreamMode,
VideoStreamType
} }
`, `,
}) })