Merge pull request 'yangjie' (#49) from yangjie into master

Reviewed-on: #49
This commit is contained in:
yangqiang 2025-03-10 14:21:26 +08:00
commit 016dcf6e09
50 changed files with 1085 additions and 442 deletions

View File

@ -1,6 +0,0 @@
#基础API 绝对的
VITE_BASE_URL_API = 'http://192.168.2.9:5192'
#当前IP 相对的
VITE_BASE_CURRENT_API = '.'
#开发环境
VITE_ENV = 'development'

View File

@ -1,6 +0,0 @@
#基础API 绝对的
VITE_BASE_URL_API = 'https://meeting-api.23544.com/pc'
#当前IP 相对的
VITE_BASE_CURRENT_API = '.'
#生产环境
VITE_ENV = 'production'

View File

@ -1,6 +0,0 @@
#基础API 绝对的
VITE_BASE_URL_API = 'https://meeting-api.23544.com/pc'
#当前IP 相对的
VITE_BASE_CURRENT_API = '.'
#测试环境
VITE_ENV = 'xy'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

16
build/install.nsh Normal file
View File

@ -0,0 +1,16 @@
!macro customFinishPage
AutoCloseWindow true
Function StartApp
${if} ${isUpdated}
StrCpy $1 "--updated"
${else}
StrCpy $1 ""
${endif}
${StdUtils.ExecShellAsUser} $0 "$launchLink" "open" "$1"
FunctionEnd
Function .onInstSuccess
Call StartApp
FunctionEnd
!macroend

57
config/development.json Normal file
View File

@ -0,0 +1,57 @@
{
"appId": "agora.io.ElectronApiExample",
"asar": true,
"asarUnpack": [
"node_modules/agora-electron-sdk"
],
"buildDependenciesFromSource": true,
"compression": "normal",
"productName": "智汇享",
"publish": [
{
"provider": "generic",
"url": "http://192.168.2.9:8827"
}
],
"files": [
"!*.log"
],
"win": {
"icon": "build/start.ico",
"requestedExecutionLevel": "highestAvailable",
"target": [
{
"target": "nsis",
"arch": [
"ia32"
]
}
]
},
"directories": {
"output": "electron"
},
"extraResources": [
{
"from": "src/assets/virtualBackground",
"to": "images",
"filter": [
"**/*"
]
}
],
"nsis": {
"oneClick": false,
"installerIcon": "build/start.ico",
"uninstallerIcon": "build/start.ico",
"installerHeaderIcon": "build/start.ico",
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"deleteAppDataOnUninstall": true,
"shortcutName": "智汇享",
"allowElevation": true,
"perMachine": true,
"include": "build/install.nsh"
}
}

View File

@ -42,15 +42,16 @@
], ],
"nsis": { "nsis": {
"oneClick": false, "oneClick": false,
"installerIcon": "build/install.ico", "installerIcon": "build/start.ico",
"uninstallerIcon": "build/install.ico", "uninstallerIcon": "build/start.ico",
"installerHeaderIcon": "build/install.ico", "installerHeaderIcon": "build/start.ico",
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true, "createDesktopShortcut": true,
"createStartMenuShortcut": true, "createStartMenuShortcut": true,
"deleteAppDataOnUninstall": true, "deleteAppDataOnUninstall": true,
"shortcutName": "智汇享", "shortcutName": "智汇享",
"allowElevation": true, "allowElevation": true,
"perMachine": true "perMachine": true,
"include": "build/install.nsh"
} }
} }

View File

@ -51,6 +51,7 @@
"deleteAppDataOnUninstall": true, "deleteAppDataOnUninstall": true,
"shortcutName": "湖北襄阳四中教研平台", "shortcutName": "湖北襄阳四中教研平台",
"allowElevation": true, "allowElevation": true,
"perMachine": true "perMachine": true,
"include": "build/install.nsh"
} }
} }

111
main.js
View File

@ -11,6 +11,7 @@ const {
crashReporter, crashReporter,
desktopCapturer, desktopCapturer,
powerSaveBlocker, powerSaveBlocker,
net
} = require('electron'); } = require('electron');
const path = require('node:path') const path = require('node:path')
const updateJs = require('./src/utils/package/update') const updateJs = require('./src/utils/package/update')
@ -18,16 +19,15 @@ const fs = require('fs');
const Registry = require('winreg'); const Registry = require('winreg');
const { autoUpdater, CancellationToken } = require('electron-updater'); const { autoUpdater, CancellationToken } = require('electron-updater');
const signalR = require('@microsoft/signalr'); const signalR = require('@microsoft/signalr');
const { setTimeout, setInterval, clearTimeout, clearInterval } = require('timers'); const { setTimeout, setInterval } = require('timers');
const cancellationToken = new CancellationToken() const cancellationToken = new CancellationToken()
app.allowRendererProcessReuse = false; app.allowRendererProcessReuse = false;
let mainWindow = null; let mainWindow = null;
let childWindow = {} let childWindow = {}
let isMaximized = false; let isMaximized = false;
let env; let env = 'development'; //development production xy
let regKey; let regKey;
let connection = null; let connection = null;
let envStr;
let startNumber = 0; let startNumber = 0;
let buildStatus = false; //true 打包开发版本 false 本地开发 let buildStatus = false; //true 打包开发版本 false 本地开发
powerSaveBlocker.start('prevent-display-sleep') powerSaveBlocker.start('prevent-display-sleep')
@ -53,16 +53,15 @@ class AppWindow extends BrowserWindow {
const finalConfig = { ...basicConfig, ...config }; const finalConfig = { ...basicConfig, ...config };
super(finalConfig); super(finalConfig);
if (env === 'development') { if (env === 'development') {
// 开发
if (buildStatus) { if (buildStatus) {
this.loadFile(path.resolve(__dirname, './dist/index.html')); this.loadURL('http://192.168.2.9:8827/');
} else { } else {
this.loadURL('http://localhost:3000'); this.loadURL('http://localhost:3000');
} }
} else { } else {
// 测试 | 生产 this.loadURL('https://meeting-api.23544.com/')
this.loadFile(path.resolve(__dirname, './dist/index.html'));
} }
// this.loadFile(path.resolve(__dirname, './dist/index.html'))
this.once('ready-to-show', () => { this.once('ready-to-show', () => {
this.show(); this.show();
}); });
@ -72,11 +71,20 @@ function quit() {
app.quit() app.quit()
} }
let tray; let tray;
// 检查网络状态
function checkNetworkStatus() {
if (!net.isOnline()) {
dialog.showErrorBox(`${env === 'xy' ? '湖北襄阳四中教研平台' : '智汇享'}-网络连接错误', '当前无网络连接,请检查您的网络设置。`);
app.quit();
return false;
}
return true;
}
function createTray() { function createTray() {
const iconPath = `${__dirname}/src/assets/${updateJs.getIcon(envStr)}.png`; const iconPath = `${__dirname}/src/assets/${updateJs.getIcon(env)}.png`;
const trayIcon = nativeImage.createFromPath(iconPath); const trayIcon = nativeImage.createFromPath(iconPath);
tray = new Tray(trayIcon); tray = new Tray(trayIcon);
tray.setToolTip(updateJs.getTitle(envStr)); tray.setToolTip(updateJs.getTitle(env));
tray.on('click', () => { tray.on('click', () => {
mainWindow.webContents.send('isOpenWindows'); mainWindow.webContents.send('isOpenWindows');
}); });
@ -95,6 +103,10 @@ function createWindow() {
} }
const additionalData = { myKey: 'myValue' } const additionalData = { myKey: 'myValue' }
app.on('ready', () => { app.on('ready', () => {
// 检查网络状态
if (!checkNetworkStatus()) {
return;
}
// const gotTheLock = true // const gotTheLock = true
const gotTheLock = app.requestSingleInstanceLock(additionalData) const gotTheLock = app.requestSingleInstanceLock(additionalData)
if (gotTheLock) { if (gotTheLock) {
@ -103,15 +115,14 @@ app.on('ready', () => {
uploadToServer: false, uploadToServer: false,
ignoreSystemCrashHandler: false ignoreSystemCrashHandler: false
}) })
env = process.argv.find((arg) => arg.startsWith('--env='))?.split('=')[1]; // if (!buildStatus) {
if (env === 'development') { // Object.defineProperty(app, 'isPackaged', {
Object.defineProperty(app, 'isPackaged', { // get() {
get() { // return true
return true // }
} // })
}) // autoUpdater.updateConfigPath = path.join('latest.yml')
autoUpdater.updateConfigPath = path.join('latest.yml') // }
}
createWindow() createWindow()
regKey = new Registry({ regKey = new Registry({
hive: Registry.HKCU, hive: Registry.HKCU,
@ -149,12 +160,11 @@ app.on('ready', () => {
isMaximized = true; isMaximized = true;
} }
}); });
ipcMain.handle('setEnv', (event, str) => { ipcMain.handle('startLoad', (event) => {
envStr = str;
if (startNumber === 0) { if (startNumber === 0) {
updateHandle() // 检查更新 updateHandle() // 检查更新
setInterval(() => { setInterval(() => {
updateHandle() // 每一小时检查更新 autoUpdater.checkForUpdates()
}, 1000 * 60 * 60) }, 1000 * 60 * 60)
createTray() createTray()
startNumber++ startNumber++
@ -162,7 +172,7 @@ app.on('ready', () => {
}); });
// 更新 // 更新
ipcMain.handle('updateHandle', () => { ipcMain.handle('updateHandle', () => {
updateHandle() autoUpdater.checkForUpdates()
}); });
// socket // socket
ipcMain.handle('startSignalr', (event, user) => { ipcMain.handle('startSignalr', (event, user) => {
@ -191,6 +201,8 @@ app.on('ready', () => {
connection.off('JoinChannelCallback'); connection.off('JoinChannelCallback');
connection.off('ExitSharedScreen'); connection.off('ExitSharedScreen');
connection.off('SetSpeaker'); connection.off('SetSpeaker');
connection.off('ReceivedOperation');
connection.off('StopedSharedScreen');
} }
}); });
ipcMain.handle('onStop', (event) => { ipcMain.handle('onStop', (event) => {
@ -210,7 +222,7 @@ app.on('ready', () => {
await connection.invoke(str, data.roomNum, data.msg) await connection.invoke(str, data.roomNum, data.msg)
break; break;
case 'sendOper': case 'sendOper':
await connection.invoke(str, data.roomNum, data.type) await connection.invoke(str, data.roomNum, data.contentString)
break; break;
case 'getDrivers': case 'getDrivers':
// 获取某个人的设备列表 // 获取某个人的设备列表
@ -236,6 +248,10 @@ app.on('ready', () => {
// 发言人设置成功 // 发言人设置成功
await connection.invoke(str, data) await connection.invoke(str, data)
break; break;
case 'sendOper2User':
// 扩展参数
await connection.invoke(str, data.uid, data.contentString)
break;
} }
}); });
ipcMain.handle('onOtherSignalr', (event) => { ipcMain.handle('onOtherSignalr', (event) => {
@ -266,10 +282,10 @@ app.on('ready', () => {
}) })
}); });
// 扩展操作 // 扩展操作
connection.on("Operation", (type) => { connection.on("Operation", (contentString) => {
mainWindow.webContents.send('onSignalr', { mainWindow.webContents.send('onSignalr', {
key: 'Operation', key: 'Operation',
type contentString
}) })
}); });
// 移出会议 // 移出会议
@ -411,6 +427,20 @@ app.on('ready', () => {
RoomManagerInputDTO RoomManagerInputDTO
}) })
}); });
// 扩展参数
connection.on("ReceivedOperation", (contentString) => {
mainWindow.webContents.send('onSignalr', {
key: 'ReceivedOperation',
contentString
})
});
// 共享人取消共享屏幕
connection.on("StopedSharedScreen", (ScreenShareId) => {
mainWindow.webContents.send('onSignalr', {
key: 'StopedSharedScreen',
ScreenShareId
})
});
} }
}); });
// 放大缩小退出窗口 // 放大缩小退出窗口
@ -452,6 +482,10 @@ app.on('ready', () => {
ipcMain.handle('getVersion', () => { ipcMain.handle('getVersion', () => {
return app.getVersion(); return app.getVersion();
}); });
// 获取环境
ipcMain.handle('getEnv', () => {
return env;
});
// 获取窗口是否显示 // 获取窗口是否显示
ipcMain.handle('isVisible', () => { ipcMain.handle('isVisible', () => {
return mainWindow.isVisible(); return mainWindow.isVisible();
@ -487,6 +521,11 @@ app.on('ready', () => {
downloadUpdate() downloadUpdate()
} else if (data === '2') { // 下载完成 点击安装 } else if (data === '2') { // 下载完成 点击安装
quitAndInstall() quitAndInstall()
} else if (data === '3') { // 打开弹窗
let message = JSON.stringify({
type: '3',
})
sendUpdateMessage(message)
} }
}); });
// 选择文件夹 // 选择文件夹
@ -604,16 +643,18 @@ app.on('ready', () => {
width: config.width, width: config.width,
height: config.height, height: config.height,
}) })
if (envStr === 'development') { if (env === 'development') {
// 开发 // 开发
if (buildStatus) { if (buildStatus) {
child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`); child.loadURL(`http://192.168.2.9:8827/#/${config.key}`);
// child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`);
} else { } else {
child.loadURL(config.url) child.loadURL(config.url)
} }
} else { } else {
// 测试 | 生产 // 测试 | 生产
child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`); child.loadURL(`https://meeting-api.23544.com/#/${config.key}`);
// child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`);
} }
child.hookWindowMessage(278, function (e) { child.hookWindowMessage(278, function (e) {
child.setEnabled(false);//窗口禁用 child.setEnabled(false);//窗口禁用
@ -715,7 +756,6 @@ app.on('ready', () => {
}); });
// 检测更新在你想要检查更新的时候执行renderer事件触发后的操作自行编写 // 检测更新在你想要检查更新的时候执行renderer事件触发后的操作自行编写
function updateHandle() { function updateHandle() {
autoUpdater.checkForUpdates()
// autoUpdater.checkForUpdatesAndNotify().catch(); // autoUpdater.checkForUpdatesAndNotify().catch();
const message = { const message = {
error: '检查更新出错', error: '检查更新出错',
@ -723,11 +763,11 @@ function updateHandle() {
updateAva: '检测到新版本,正在下载……', updateAva: '检测到新版本,正在下载……',
updateNotAva: '已经是最新版本,不用更新' updateNotAva: '已经是最新版本,不用更新'
} }
autoUpdater.setFeedURL(updateJs.getUpdateUrl(envStr)) autoUpdater.setFeedURL(updateJs.getUpdateUrl(env))
autoUpdater.autoDownload = false // 不自动下载安装包 autoUpdater.autoDownload = false // 不自动下载安装包
autoUpdater.autoInstallOnAppQuit = false // 不自动安装 autoUpdater.autoInstallOnAppQuit = false // 不自动安装
autoUpdater.on('error', function (error) { autoUpdater.on('error', function (error) {
sendUpdateMessage(message.error) sendUpdateMessage(error)
}) })
autoUpdater.on('checking-for-update', function () { autoUpdater.on('checking-for-update', function () {
sendUpdateMessage(message.checking) sendUpdateMessage(message.checking)
@ -735,9 +775,14 @@ function updateHandle() {
autoUpdater.on('update-available', function (info) { autoUpdater.on('update-available', function (info) {
let messageStr = JSON.stringify({ type: '0' }) let messageStr = JSON.stringify({ type: '0' })
sendUpdateMessage(messageStr) sendUpdateMessage(messageStr)
mainWindow.webContents.send('changeLocalStorage', {
isUpdate: true,
});
}) })
autoUpdater.on('update-not-available', function (info) { autoUpdater.on('update-not-available', function (info) {
mainWindow.webContents.send('changeLocalStorage', {
isUpdate: false,
});
}) })
// 更新下载进度事件 // 更新下载进度事件
autoUpdater.on('download-progress', function (progressObj) { autoUpdater.on('download-progress', function (progressObj) {
@ -810,7 +855,7 @@ function mainWindowCenter() {
const startSignalr = async (user) => { const startSignalr = async (user) => {
connection = new signalR.HubConnectionBuilder() connection = new signalR.HubConnectionBuilder()
.withUrl(`${envStr === 'development' ? 'http://192.168.2.9:5192' : 'https://meeting-api.23544.com/pc'}/session-manage`, { .withUrl(`${env === 'development' ? 'http://192.168.2.9:5192' : 'https://meeting-api.23544.com/pc'}/session-manage`, {
skipNegotiation: true, skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets, transport: signalR.HttpTransportType.WebSockets,
accessTokenFactory: () => user.token accessTokenFactory: () => user.token

7
package-lock.json generated
View File

@ -1,17 +1,17 @@
{ {
"name": "WGShare.Metting", "name": "WGShare.Metting",
"version": "0.4.8", "version": "0.7.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "WGShare.Metting", "name": "WGShare.Metting",
"version": "0.4.8", "version": "0.7.1",
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.3.7", "@ant-design/icons": "^5.3.7",
"@microsoft/signalr": "^8.0.0", "@microsoft/signalr": "^8.0.0",
"@types/node": "^20.14.9", "@types/node": "^20.14.9",
"agora-electron-sdk": "^4.4.0", "agora-electron-sdk": "4.4.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"antd": "^5.18.2", "antd": "^5.18.2",
"axios": "^1.7.2", "axios": "^1.7.2",
@ -19,6 +19,7 @@
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"electron-squirrel-startup": "^1.0.1", "electron-squirrel-startup": "^1.0.1",
"electron-updater": "^6.2.1", "electron-updater": "^6.2.1",
"js-yaml": "^4.1.0",
"os": "^0.1.2", "os": "^0.1.2",
"path": "^0.12.7", "path": "^0.12.7",
"postcss-px-to-viewport-8-plugin": "^1.2.5", "postcss-px-to-viewport-8-plugin": "^1.2.5",

View File

@ -1,21 +1,17 @@
{ {
"name": "WGShare.Metting", "name": "WGShare.Metting",
"private": true, "private": true,
"version": "0.7.1", "version": "0.8.0",
"main": "main.js", "main": "main.js",
"authors": "yj", "authors": "yj",
"description": "智汇享", "description": "智汇享",
"scripts": { "scripts": {
"dev": "concurrently \"electron . --env=development\" \"cross-env BROWSER=none vite\"", "dev": "concurrently \"electron .\" \"cross-env BROWSER=none vite\"",
"prod": "concurrently \"electron . --env=production\" \"cross-env BROWSER=none vite\"", "build": "vite build",
"xy": "concurrently \"electron . --env=xy\" \"cross-env BROWSER=none vite\"",
"build": "vite build --mode development",
"build:prod": "vite build --mode production",
"build:xy": "vite build --mode xy",
"preview": "vite preview", "preview": "vite preview",
"build:dev-win": "vite build --mode development & electron-builder -w --config=./config/build.json", "build:dev": "vite build & electron-builder -w --config=./config/development.json",
"build:prod-win": "vite build --mode production & electron-builder -w --config=./config/build.json", "build:prod": "vite build & electron-builder -w --config=./config/production.json",
"build:prod-win-xy": "vite build --mode xy & electron-builder -w --config=./config/xy.json" "build:xy": "vite build & electron-builder -w --config=./config/xy.json"
}, },
"agora_electron": { "agora_electron": {
"platform": "win32", "platform": "win32",
@ -34,6 +30,7 @@
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"electron-squirrel-startup": "^1.0.1", "electron-squirrel-startup": "^1.0.1",
"electron-updater": "^6.2.1", "electron-updater": "^6.2.1",
"js-yaml": "^4.1.0",
"os": "^0.1.2", "os": "^0.1.2",
"path": "^0.12.7", "path": "^0.12.7",
"postcss-px-to-viewport-8-plugin": "^1.2.5", "postcss-px-to-viewport-8-plugin": "^1.2.5",

View File

@ -51,6 +51,10 @@ window.electron = {
getVersion: () => { getVersion: () => {
return ipcRenderer.invoke('getVersion') return ipcRenderer.invoke('getVersion')
}, },
// 获取环境
getEnv: () => {
return ipcRenderer.invoke('getEnv')
},
// 获取窗口是否显示 // 获取窗口是否显示
isVisible: () => { isVisible: () => {
return ipcRenderer.invoke('isVisible') return ipcRenderer.invoke('isVisible')
@ -87,9 +91,9 @@ window.electron = {
isOpenWindows: (callback) => { isOpenWindows: (callback) => {
ipcRenderer.on('isOpenWindows', callback) ipcRenderer.on('isOpenWindows', callback)
}, },
// 设置环境变量 // 首次加载
setEnv: (str) => { startLoad: () => {
ipcRenderer.invoke('setEnv', str) ipcRenderer.invoke('startLoad')
}, },
// 更新 // 更新
updateHandle: () => { updateHandle: () => {
@ -134,7 +138,7 @@ window.electron = {
ipcRenderer.invoke('createChildWindow', { ipcRenderer.invoke('createChildWindow', {
url: location.origin + `/#/noticeWindow`, url: location.origin + `/#/noticeWindow`,
width: 388, width: 388,
height: 150, height: 180,
key: 'noticeWindow', key: 'noticeWindow',
}) })
ipcRenderer.invoke('createChildWindow', { ipcRenderer.invoke('createChildWindow', {

View File

@ -8,20 +8,21 @@ import Login from '@/page/Login/index'
import Meeting from '@/page/Meeting/index' import Meeting from '@/page/Meeting/index'
import NotFound from '@/page/NotFound/index' import NotFound from '@/page/NotFound/index'
import { storage } from '@/utils' import { storage } from '@/utils'
import { message, Spin } from "antd"; import { message, Modal, Spin } from "antd";
import JoinMeetingModal from "@/components/JoinMeetingModal"; import JoinMeetingModal from "@/components/JoinMeetingModal";
import UpdateModal from "@/components/UpdateModal"; import UpdateModal from "@/components/UpdateModal";
import * as CryptoJS from 'crypto-js'; import * as CryptoJS from 'crypto-js';
import { PostLogin } from "@/api/Login"; import { GetCheckOnline, 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 ShareScreenWindow from "@/page/Meeting/ShareScreenWindow"; import ShareScreenWindow from "@/page/Meeting/ShareScreenWindow";
import UserListWindow from "@/page/Meeting/UserListWindow"; import UserListWindow from "@/page/Meeting/UserListWindow";
import ChatSmallWindow from "@/page/Meeting/ChatSmallWindow"; import ChatSmallWindow from "@/page/Meeting/ChatSmallWindow";
import ChatBigWindow from "@/page/Meeting/ChatBigWindow"; import ChatBigWindow from "@/page/Meeting/ChatBigWindow";
import NoticeWindow from "@/page/Meeting/NoticeWindow"; import NoticeWindow from "@/page/Meeting/NoticeWindow";
import { getKeyOpenChildWindow, getTitle, setKeyOpenChildWindow, storageSeeting } from "./utils/package/public"; import { getKeyOpenChildWindow, getTitle, setKeyOpenChildWindow, storageSeeting } from "./utils/package/public";
import { ExclamationCircleFilled } from "@ant-design/icons";
const { confirm } = Modal;
const fs = require('fs').promises; const fs = require('fs').promises;
const { exec } = require('child_process'); const { exec } = require('child_process');
const App: React.FC = () => { const App: React.FC = () => {
@ -41,9 +42,7 @@ const App: React.FC = () => {
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)
window.electron.setEnv(import.meta.env.VITE_ENV); const login = () => {
if (userInfo && !userInfo.isAnonymous) {
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)
@ -57,6 +56,31 @@ const App: React.FC = () => {
toSrc('/login') toSrc('/login')
} }
}) })
}
if (userInfo && !userInfo.isAnonymous) {
if (loginInfo && loginInfo.isAutoLogin) {
GetCheckOnline(loginInfo.account).then(req => {
if (req.code === 200) {
if (req.data) {
confirm({
title: '提示',
icon: <ExclamationCircleFilled />,
content: `账号已在其他地方登录,是否强制登陆?`,
centered: true,
okText: '确定',
cancelText: '取消',
async onOk() {
login()
},
onCancel() {
toSrc('/login')
}
})
} else {
login()
}
}
})
} else { } else {
toSrc('/login') toSrc('/login')
} }
@ -79,6 +103,7 @@ const App: React.FC = () => {
}; };
}, []); }, []);
useEffect(() => { useEffect(() => {
window.electron.startLoad();
window.electron.downFile(async (_e: any, data: any) => { window.electron.downFile(async (_e: any, data: any) => {
const response = await fetch(data.filePath); const response = await fetch(data.filePath);
const arrayBuffer = await response.arrayBuffer(); const arrayBuffer = await response.arrayBuffer();
@ -127,9 +152,7 @@ const App: React.FC = () => {
}, []) }, [])
useEffect(() => { useEffect(() => {
window.electron.onUpdate((_e: any, data: any) => { window.electron.onUpdate((_e: any, data: any) => {
if (location.hash.indexOf('/meeting') === -1) {
updateModalRef.current.changeModal(data) updateModalRef.current.changeModal(data)
}
}) })
if (!storage.getItem('setting')) { if (!storage.getItem('setting')) {
storage.setItem('setting', JSON.stringify(storageSeeting)) storage.setItem('setting', JSON.stringify(storageSeeting))
@ -164,16 +187,24 @@ const App: React.FC = () => {
if (location.href.indexOf('/login') !== -1) { if (location.href.indexOf('/login') !== -1) {
window.electron.onStop() window.electron.onStop()
} }
if (location.hash && location.hash.indexOf('/meeting') === -1) {
window.electron.updateHandle()
}
message.destroy('cameraTemporarily') message.destroy('cameraTemporarily')
}, [navigate]) }, [navigate])
} }
useEffect(() => { useEffect(() => {
document.addEventListener('keydown', (event) => { document.addEventListener('keydown', async (event) => {
if (event.key === 'F11') { if (event.keyCode == 122) {
event.preventDefault();
} else if (((event.ctrlKey && event.keyCode == 82) || event.keyCode == 116)) {
let env = await window.electron.getEnv()
if (env !== 'development') {
event.preventDefault(); event.preventDefault();
} }
}
}); });
document.getElementsByTagName('title')[0].innerText = getTitle(import.meta.env.VITE_ENV) getTitle()
}, []) }, [])
const handleResize = (): void => { const handleResize = (): void => {
setWindowSize({ setWindowSize({
@ -200,8 +231,10 @@ const App: React.FC = () => {
if (item.msg) { if (item.msg) {
message.error(item.msg) message.error(item.msg)
} }
await leaveChannel(true) await leaveChannel()
setTimeout(() => {
toSrc('/login') toSrc('/login')
}, 5000);
break; break;
} }
}) })
@ -223,7 +256,7 @@ const App: React.FC = () => {
} }
}) })
}; };
const leaveChannel = async (bool?: boolean): Promise<void> => { const leaveChannel = async (): Promise<void> => {
if (location.hash.indexOf('/meeting') === 1) { if (location.hash.indexOf('/meeting') === 1) {
window.electron.closeChildWindow('shareScreenWindow') window.electron.closeChildWindow('shareScreenWindow')
setKeyOpenChildWindow('shareScreenWindow', false) setKeyOpenChildWindow('shareScreenWindow', false)
@ -240,11 +273,9 @@ const App: React.FC = () => {
}) })
}) })
const data = JSON.parse(localStorage.stateInfo); const data = JSON.parse(localStorage.stateInfo);
if (!bool) { await window.electron.onInvoke('levelChannel', {
await GetLeave({ roomNum: data.channelId
roomNum: data.channelId,
}) })
}
await agora.leaveChannel() await agora.leaveChannel()
} }
}; };
@ -260,12 +291,8 @@ const App: React.FC = () => {
storage.removeItem('user') storage.removeItem('user')
navigate('/login') navigate('/login')
} }
} else if (e.key === 'reconnect') { } else if (e.key === 'env') {
if (e.value == true) {
if (location.hash.indexOf('/meeting') === -1) {
window.electron.updateHandle()
}
}
} }
}; };
return ( return (

View File

@ -23,3 +23,9 @@ export const PostAnonLogin = (data: any) =>
method: 'post', method: 'post',
data, data,
}) })
export const GetCheckOnline = (account: string) =>
request({
url: `/auth/check-online?account=${account}`,
method: 'get'
})

View File

@ -171,3 +171,14 @@ export const PostSharedScreen = (roomNum: string) =>
url: `/room/shared-screen?roomNum=${roomNum}`, url: `/room/shared-screen?roomNum=${roomNum}`,
method: 'post' method: 'post'
}) })
export const PostStopSharedScreen = (roomNum: string) =>
request({
url: `/room/stop-shared-screen?roomNum=${roomNum}`,
method: 'post'
})
export const PostHomeVerLog = (data: any) =>
request({
url: `/home/ver-log`,
method: 'post',
data
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 838 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 921 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

View File

@ -0,0 +1,48 @@
import ImageUrl from '@/utils/package/imageUrl';
import { Empty, Popover } from 'antd';
import { GetQrcode } from '@/api/Home/Index';
import { memo, useImperativeHandle, forwardRef, useState } from "react";
const Code = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
getData: () => {
}
}))
const [baseImage, setBaseImage] = useState('');
const [roomNum, setRoomNum] = useState(props.roomNum);
return (
<>
<Popover
placement="bottom"
onOpenChange={async (e: boolean) => {
setBaseImage('')
if (e) {
let env = await window.electron.getEnv()
GetQrcode(roomNum, env === 'development' ? 'trial' : 'release').then(res => {
if (res.code === 200) {
setBaseImage(res.data)
}
})
}
}}
content={
baseImage ? <div>
<img style={{ width: '200px', margin: '0 auto' }} src={`data:image/png;base64,${baseImage}`} alt="" />
<div style={{ color: 'white', textAlign: 'center', fontSize: '16px', marginTop: '10px' }}>
<span></span><br />
<span></span>
</div>
</div> : <div>
<Empty description={'二维码加载中,请稍后'} />
</div>
}
>
<div title='小程序'>
<img src={ImageUrl.icon55} alt="" style={{ width: '16px' }} />
</div>
</Popover>
</>
)
})
export default memo(Code)

View File

@ -9,6 +9,7 @@ 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'; import { role } from '@/config/role';
import { PostHomeVerLog } from '@/api/Meeting';
const { setInterval, clearInterval } = require('timers'); const { setInterval, clearInterval } = require('timers');
let time = null as any; let time = null as any;
const JoinSetting = forwardRef((_props: any, ref: any) => { const JoinSetting = forwardRef((_props: any, ref: any) => {
@ -201,6 +202,13 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
GetRoomInfo(roomNumber).then(async (res) => { GetRoomInfo(roomNumber).then(async (res) => {
if (res.code === 200) { if (res.code === 200) {
await agora.release() await agora.release()
await window.electron.getVersion().then(async req => {
await PostHomeVerLog({
version: req,
platformType: 1,
roomNum: roomNumber,
})
})
navigate(`/meeting`, { navigate(`/meeting`, {
state: { state: {
channelId: roomNumber, channelId: roomNumber,

View File

@ -34,6 +34,9 @@ const StupWizard = forwardRef((_props: any, ref: any) => {
} }
} }
storage.setItem('setting', JSON.stringify(setting)) storage.setItem('setting', JSON.stringify(setting))
},
getStupWizardModal: () => {
return isStupWizard
} }
})) }))
const [list, setList] = useState([ const [list, setList] = useState([
@ -130,9 +133,11 @@ const StupWizard = forwardRef((_props: any, ref: any) => {
}) })
const CurrencyComponents = () => { const CurrencyComponents = () => {
const [optionsValue, setOperationValue] = useState<'hide' | 'quit'>('hide'); const [optionsValue, setOperationValue] = useState<'hide' | 'quit'>('hide');
const [voiceStimulation, setVoiceStimulation] = useState(true);
const setting = JSON.parse(storage.getItem('setting') as string) const setting = JSON.parse(storage.getItem('setting') as string)
useEffect(() => { useEffect(() => {
setOperationValue(setting.closeSetting) setOperationValue(setting.closeSetting)
setVoiceStimulation(setting.voiceStimulation)
}, []); }, []);
return ( return (
<> <>
@ -151,6 +156,33 @@ const CurrencyComponents = () => {
<Radio value={'hide'}>退</Radio> <Radio value={'hide'}>退</Radio>
</Radio.Group> </Radio.Group>
</div> </div>
<div>
<span> <Popover
content={
<span
style={{
color: 'white'
}}>
</span>
}
title=""
>
<QuestionCircleOutlined style={{
color: 'white',
cursor: 'pointer',
marginRight: '10px'
}} />
</Popover></span>
<Radio.Group onChange={(e: any) => {
setting.voiceStimulation = e.target.value;
storage.setItem('setting', JSON.stringify(setting))
setVoiceStimulation(e.target.value)
}} style={{ flexShrink: 0, margin: '10px 0' }} value={voiceStimulation}>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -213,7 +245,8 @@ const VideoComponents = () => {
}, [darkLightEnhancement]); }, [darkLightEnhancement]);
useEffect(() => { useEffect(() => {
if (typeof virtualBackground.sourceIndex === 'number') { if (typeof virtualBackground.sourceIndex === 'number') {
if (import.meta.env.VITE_ENV === 'development') { window.electron.getEnv().then(res=>{
if (res === 'development') {
window.electron.getAppPath().then((res: string) => { window.electron.getAppPath().then((res: string) => {
const imagePath = path.join(res, 'src', 'assets', 'virtualBackground', `${virtualBackground.sourceIndex + 1}.png`); const imagePath = path.join(res, 'src', 'assets', 'virtualBackground', `${virtualBackground.sourceIndex + 1}.png`);
agora.enableVirtualBackground(virtualBackground.isVirtualBackground, { agora.enableVirtualBackground(virtualBackground.isVirtualBackground, {
@ -230,6 +263,7 @@ const VideoComponents = () => {
color: Number(virtualBackground.color), color: Number(virtualBackground.color),
}) })
} }
})
} else { } else {
agora.enableVirtualBackground(virtualBackground.isVirtualBackground, { agora.enableVirtualBackground(virtualBackground.isVirtualBackground, {
background_source_type: 1, background_source_type: 1,
@ -696,7 +730,18 @@ const AudioComponents = () => {
ecordingVolume: e, ecordingVolume: e,
}) })
}} disabled={!audioDeviceManager.ecordingItem} /> }} disabled={!audioDeviceManager.ecordingItem} />
{/* || audioDeviceManager.autoEcordingVolume */}
</div> </div>
{/* <div style={{ marginBottom: '10px' }}>
<Checkbox onChange={async (e) => {
setting.autoEcordingVolume = e.target.checked;
storage.setItem('setting', JSON.stringify(setting))
setAudioDeviceManager({
...audioDeviceManager,
autoEcordingVolume: e.target.checked
})
}} checked={audioDeviceManager.autoEcordingVolume}></Checkbox>
</div> */}
<div> <div>
<div> <div>
<Checkbox checked={audioDeviceManager.isAINoiseReduction} onChange={(e) => { <Checkbox checked={audioDeviceManager.isAINoiseReduction} onChange={(e) => {
@ -729,16 +774,6 @@ const AudioComponents = () => {
</Radio.Group> </Radio.Group>
</div> </div>
</div> </div>
{/* <div>
<Checkbox onChange={async (e) => {
setting.autoEcordingVolume = e.target.checked;
storage.setItem('setting', JSON.stringify(setting))
setAudioDeviceManager({
...audioDeviceManager,
autoEcordingVolume: e.target.checked
})
}} checked={audioDeviceManager.autoEcordingVolume}></Checkbox>
</div> */}
</div> </div>
<div> <div>
<div className={styles.audioComponentsSelect}> <div className={styles.audioComponentsSelect}>

View File

@ -1,45 +1,106 @@
import styles from '@/components/UpdateModal/index.module.scss' import styles from '@/components/UpdateModal/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl'; import ImageUrl from '@/utils/package/imageUrl';
import { getUpdateUrl } from '@/utils/package/public'; import { getUpdateUrl, isVersion } from '@/utils/package/public';
import { ExclamationCircleFilled } from '@ant-design/icons';
import { Button, Flex, Modal, Progress } from 'antd'; import { Button, Flex, Modal, Progress } from 'antd';
import { forwardRef, useImperativeHandle, useState, memo } from "react"; import { forwardRef, useImperativeHandle, useState, memo, useEffect } from "react";
const { confirm } = Modal;
const UpdateModal = forwardRef((props: any, ref: any) => { const UpdateModal = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeModal: (data: any) => { changeModal: (data: any) => {
if (JSON.stringify(data).includes('Error') || JSON.stringify(data).includes('{}')) {
setIsError(res => {
if (res) {
setIsError(false)
confirm({
keyboard: false,
title: '提示',
icon: <ExclamationCircleFilled />,
content: `更新失败,请尝试手动更新!`,
centered: true,
okText: '立即更新',
wrapClassName: 'hideCancelText',
cancelText: '',
async onOk() {
isVersion((bool: boolean, req: any) => {
if (bool && req) {
window.electron.getEnv().then(res => {
location.href = `${getUpdateUrl(res)}/${req.path}`
})
}
})
},
})
}
return res
})
} else {
try {
let dataJson = JSON.parse(data) let dataJson = JSON.parse(data)
getContent()
if (dataJson.type === '0') { // 打开弹窗 if (dataJson.type === '0') { // 打开弹窗
setIsUpdateModal(true) setProgress(res => {
if (res) {
} else {
window.electron.onDownload('1')
}
return res
})
} else if (dataJson.type === '1') { // 下载中 返回进度值 } else if (dataJson.type === '1') { // 下载中 返回进度值
if (dataJson.value === 100 && location.hash.indexOf('/meeting') === -1) {
setIsUpdateModal(true)
getContent()
}
setProgress(dataJson.value.toFixed(2)) setProgress(dataJson.value.toFixed(2))
} else if (dataJson.type === '2') { // 下载完成 } else if (dataJson.type === '2') { // 下载完成
setProgress(100) setProgress(100)
if (location.hash.indexOf('/meeting') === -1) {
setIsUpdateModal(true)
getContent()
}
} else if (dataJson.type === '3') {
if (location.hash.indexOf('/meeting') === -1) {
setIsUpdateModal(true)
getContent()
}
}
} catch (error) {
}
} }
} }
})) }))
const [isUpdateModal, setIsUpdateModal] = useState(false); const [isUpdateModal, setIsUpdateModal] = useState(false);
const [progress, setProgress] = useState(0); // 下载进度值 const [progress, setProgress] = useState(0); // 下载进度值
const [updateContent, setUpdateContent] = useState('') // 版本更新内容 const [updateContent, setUpdateContent] = useState('') // 版本更新内容
const [env, setEnv] = useState('')
const [_isError, setIsError] = useState(true)
useEffect(() => {
window.electron.getEnv().then(res => {
setEnv(res)
})
}, [])
function getContent() { function getContent() {
fetch(`${getUpdateUrl(import.meta.env.VITE_ENV)}/update.txt?t=${+new Date()}`) // 配置服务器地址 window.electron.getEnv().then(res => {
fetch(`${getUpdateUrl(res)}/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())
} }
throw new Error('Network response was not ok.'); throw new Error('Network response was not ok.');
}) })
.then(textContent => { .then(_textContent => {
}) })
.catch(error => { .catch(_error => {
}); });
})
} }
function closeModal() { function closeModal() {
if (progress != 100) { // if (progress != 100) {
window.electron.onDownload('0') // 取消下载 // window.electron.onDownload('0') // 取消下载
} // }
setIsUpdateModal(false) setIsUpdateModal(false)
} }
@ -49,12 +110,12 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
title="" title=""
open={isUpdateModal} open={isUpdateModal}
footer={null} footer={null}
// onCancel={() => closeModal()} onCancel={() => closeModal()}
centered centered
width={'400px'} width={'400px'}
className='modal-padding' className='modal-padding'
maskClosable={false} maskClosable={false}
closeIcon={false} closeIcon={progress === 100 ? false : true}
> >
<div className={styles.isUpdateModal} style={{ backgroundImage: `url(${ImageUrl.icon7})` }}> <div className={styles.isUpdateModal} style={{ backgroundImage: `url(${ImageUrl.icon7})` }}>
<div className={styles.remarks} dangerouslySetInnerHTML={{ __html: updateContent }}> <div className={styles.remarks} dangerouslySetInnerHTML={{ __html: updateContent }}>
@ -67,7 +128,7 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
style={{ width: '100%', height: '40px', marginBottom: '10px' }} style={{ width: '100%', height: '40px', marginBottom: '10px' }}
className={`m-ant-btn`} className={`m-ant-btn`}
></Button> ></Button>
{import.meta.env.VITE_ENV === "development" ? <div className={styles.button2} onClick={() => setIsUpdateModal(false)}></div> : null} {env === "development" ? <div className={styles.button2} onClick={() => setIsUpdateModal(false)}></div> : null}
</div> : progress < 100 ? </div> : progress < 100 ?
<div style={{ margin: '20px 0' }}> <div style={{ margin: '20px 0' }}>
{progress}% {progress}%

View File

@ -47,6 +47,10 @@ const UserName = forwardRef((props: any, ref: any) => {
onClick={async () => { onClick={async () => {
const stateInfo = await JSON.parse(storage.getItem('stateInfo') as string); const stateInfo = await JSON.parse(storage.getItem('stateInfo') as string);
if (info.userName) { if (info.userName) {
if (info.userName.length > 10) {
message.error('用户名称最多10个字')
return
}
PutAlterUname({ PutAlterUname({
nickName: info.userName, nickName: info.userName,
roomNum: stateInfo.channelId, roomNum: stateInfo.channelId,

View File

@ -121,14 +121,6 @@
font-size: 14px; font-size: 14px;
} }
} }
>div:nth-child(2) {
cursor: pointer;
>img {
width: 16px;
}
}
} }
>div:nth-child(2) { >div:nth-child(2) {

View File

@ -2,7 +2,7 @@ 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, Popover, Popconfirm, DatePicker, Select, Radio } from "antd"; import { Button, Input, Modal, Pagination, Empty, message, Popover, Popconfirm, DatePicker, Select, Radio } from "antd";
import { GetRoom, PostRoom, GetCheckoutRoomNum, GetRoomRtcToken, DeleteRoom, GetRecord, PostRoomInfo, GetQrcode } from '@/api/Home/Index'; import { GetRoom, PostRoom, GetCheckoutRoomNum, GetRoomRtcToken, DeleteRoom, GetRecord, PostRoomInfo } from '@/api/Home/Index';
import ImageUrl from '@/utils/package/imageUrl' import ImageUrl from '@/utils/package/imageUrl'
import { ExclamationCircleFilled, ReloadOutlined } from '@ant-design/icons'; import { ExclamationCircleFilled, ReloadOutlined } from '@ant-design/icons';
import JoinSetting from '@/components/JoinSetting'; import JoinSetting from '@/components/JoinSetting';
@ -14,6 +14,10 @@ import dayjs from 'dayjs';
import StupWizard from '@/components/StupWizard'; import StupWizard from '@/components/StupWizard';
import { GetSubDpList } from '@/api/Home/User'; import { GetSubDpList } from '@/api/Home/User';
import FeedBackModel from '@/components/FeedBackModel'; import FeedBackModel from '@/components/FeedBackModel';
import { PostHomeVerLog } from '@/api/Meeting';
import Code from '@/components/Code';
import { isVersion } from "@/utils/package/public";
const { setInterval, clearInterval } = require('timers'); const { setInterval, clearInterval } = require('timers');
const fs = require('fs').promises; const fs = require('fs').promises;
const { exec } = require('child_process'); const { exec } = require('child_process');
@ -46,7 +50,6 @@ const Index: React.FC = () => {
const [timeData, setTimeData] = useState<any>([]); const [timeData, setTimeData] = useState<any>([]);
const [isCreateRoom, setIsCreateRoom] = useState<boolean>(false); const [isCreateRoom, setIsCreateRoom] = useState<boolean>(false);
const [allowAnonymous, setAllowAnonymous] = useState(true); const [allowAnonymous, setAllowAnonymous] = useState(true);
const [baseImage, setBaseImage] = useState('');
const userInfo = JSON.parse(storage.getItem('user') as string) const userInfo = JSON.parse(storage.getItem('user') as string)
useEffect(() => { useEffect(() => {
setUser(userInfo) setUser(userInfo)
@ -245,35 +248,7 @@ const Index: React.FC = () => {
<span>{item.roomNum}</span> <span>{item.roomNum}</span>
<img src={ImageUrl.icon10} alt="" /> <img src={ImageUrl.icon10} alt="" />
</div> </div>
<Popover <Code roomNum={item.roomNum}></Code>
placement="bottom"
onOpenChange={(e: boolean) => {
setBaseImage('')
if (e) {
GetQrcode(item.roomNum, import.meta.env.VITE_ENV === 'development' ? 'trial' : 'release').then(res => {
if (res.code === 200) {
setBaseImage(res.data)
}
})
}
}}
content={
baseImage ? <div>
<img style={{ width: '200px', margin: '0 auto' }} src={`data:image/png;base64,${baseImage}`} alt="" />
<div style={{ color: 'white', textAlign: 'center', fontSize: '16px', marginTop: '10px' }}>
<span></span><br />
<span></span>
</div>
</div> : <div>
<Empty description={'二维码加载中,请稍后'} />
</div>
}
>
<div title='小程序'>
<img src={ImageUrl.icon55} alt="" />
</div>
</Popover>
</div> </div>
<div> <div>
{role.ID.includes(userInfo.roleId) ? <Popover {role.ID.includes(userInfo.roleId) ? <Popover
@ -335,13 +310,26 @@ const Index: React.FC = () => {
<Button type="primary" <Button type="primary"
iconPosition={'end'} iconPosition={'end'}
onClick={async () => { onClick={async () => {
storage.setItem('loading', true)
isVersion((bool: boolean) => {
storage.setItem('loading', false)
if (bool) {
window.electron.onDownload('3')
} else {
if (role.ID.includes(userInfo.roleId)) { if (role.ID.includes(userInfo.roleId)) {
joinSettingRef.current.changeModal(item.roomNum) joinSettingRef.current.changeModal(item.roomNum)
} else { } else {
storage.setItem('loading', true) storage.setItem('loading', true)
postRefresh(() => { postRefresh(() => {
getRoomRtcToken(item.roomNum, (options: any) => { getRoomRtcToken(item.roomNum, async (options: any) => {
if (options) { if (options) {
await window.electron.getVersion().then(async req => {
await PostHomeVerLog({
version: req,
platformType: 1,
roomNum: item.roomNum,
})
})
navigate(`/meeting`, { navigate(`/meeting`, {
state: { state: {
channelId: item.roomNum, channelId: item.roomNum,
@ -357,6 +345,8 @@ const Index: React.FC = () => {
}) })
}) })
} }
}
})
}} }}
icon={<img src={ImageUrl.icon9} alt="" />} icon={<img src={ImageUrl.icon9} alt="" />}
className='m-ant-btn'> className='m-ant-btn'>

View File

@ -458,6 +458,9 @@ const User: React.FC = () => {
if (!addUserFrom.UserName && isCreateUser !== 'batch') { if (!addUserFrom.UserName && isCreateUser !== 'batch') {
return message.error('请输入用户名称!') return message.error('请输入用户名称!')
} }
if (addUserFrom.UserName.length > 10) {
return message.error('用户名称最多10个字')
}
if (addUserFrom.year === '') { if (addUserFrom.year === '') {
return message.error('请输入届!') return message.error('请输入届!')
} }

View File

@ -120,8 +120,26 @@
@else if $i ==4 { @else if $i ==4 {
flex-shrink: 0; flex-shrink: 0;
>div:nth-child(1) {
color: #ccc; color: #ccc;
font-size: 16px; font-size: 16px;
display: flex;
align-items: center;
>span:nth-child(2) {
background-color: red;
font-size: 12px;
padding: 0px 4px;
border-radius: 10px;
margin-left: 4px;
}
}
>div:nth-child(2) {
width: 100%;
margin-top: 10px;
}
} }
@else if $i ==5 { @else if $i ==5 {

View File

@ -1,7 +1,7 @@
import styles from '@/page/Home/index.module.scss' import styles from '@/page/Home/index.module.scss'
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import { Outlet, useNavigate } from 'react-router-dom'; import { Outlet, useNavigate } from 'react-router-dom';
import { Popconfirm, Popover } from 'antd'; import { Button, Popconfirm, Popover } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn' import 'dayjs/locale/zh-cn'
import { storage } from '@/utils'; import { storage } from '@/utils';
@ -45,6 +45,7 @@ const Home: React.FC = () => {
]); ]);
const [userInfo, setUserInfo] = useState<any>({}) const [userInfo, setUserInfo] = useState<any>({})
const [version, setVersion] = useState<string>('') const [version, setVersion] = useState<string>('')
const [update, setUpdate] = useState(false)
const [dateInfo, setDateInfo] = useState<{ const [dateInfo, setDateInfo] = useState<{
work: string; work: string;
time: string; time: string;
@ -67,11 +68,18 @@ const Home: React.FC = () => {
}) })
}; };
const timer = setInterval(updateTime, 1000); const timer = setInterval(updateTime, 1000);
window.addEventListener('customStorageChange', handleCustomStorageChange);
return () => { return () => {
window.removeEventListener('customStorageChange', handleCustomStorageChange);
clearInterval(timer); clearInterval(timer);
}; };
}, []); }, []);
const handleCustomStorageChange = (e: any): void => {
if (e.key === 'isUpdate') {
setUpdate(e.value)
}
};
const changtNavList = (index: number, bool?: boolean): void => { const changtNavList = (index: number, bool?: boolean): void => {
const newNavList = [...navList]; const newNavList = [...navList];
if (typeof bool === 'boolean') { if (typeof bool === 'boolean') {
@ -126,14 +134,22 @@ const Home: React.FC = () => {
) )
})} })}
</div> </div>
<div className='drag'>
<div> <div>
{version} <span>:{version}</span>
{update ? <span>new</span> : null}
</div>
{update ? <div>
<Button type="primary" className='m-ant-btn' style={{ width: '100%' }} onClick={() => {
window.electron.onDownload('3')
}}></Button>
</div> : null}
</div> </div>
<div> <div>
<Popover <Popover
placement="right" placement="right"
content={ content={
<img style={{ width: '400px' }} src={'https://meeting-api.23544.com/meeting/update/ddq.png'} alt="" /> <img style={{ width: '400px' }} src={`https://meeting-api.23544.com/meeting/update/ddq.png?t=${+new Date()}`} alt="" />
} }
> >
<div className='drag' title='反馈建议'> <div className='drag' title='反馈建议'>

View File

@ -4,11 +4,14 @@ import { useEffect, useState } from "react";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Input, Button, Checkbox, message, Modal } from "antd" import { Input, Button, Checkbox, message, Modal } from "antd"
import { storage } from '@/utils' import { storage } from '@/utils'
import { GetCheckUser, PostAnonLogin, PostLogin } from '@/api/Login' import { GetCheckOnline, GetCheckUser, PostAnonLogin, PostLogin } from '@/api/Login'
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 { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { GetCheckoutRoomNum, GetRoomInfo, GetRoomRtcToken } from '@/api/Home/Index'; import { GetCheckoutRoomNum, GetRoomInfo, GetRoomRtcToken } from '@/api/Home/Index';
import { ExclamationCircleFilled } from '@ant-design/icons';
import { isVersion } from '@/utils/package/public';
const { confirm } = Modal;
const Login: React.FC = () => { const Login: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [accountPasswordStatus, setAccountPasswordStatus] = useState<boolean>(false); const [accountPasswordStatus, setAccountPasswordStatus] = useState<boolean>(false);
@ -37,13 +40,16 @@ const Login: React.FC = () => {
roomNum: '', roomNum: '',
}) })
const [nameModal, setNameModal] = useState(false) const [nameModal, setNameModal] = useState(false)
const [env, setEnv] = useState('')
useEffect(() => { useEffect(() => {
window.electron.setMainWindowSize({ window.electron.setMainWindowSize({
width: 752, width: 752,
height: 520, height: 520,
key: 'login' key: 'login'
}) })
window.electron.getEnv().then(res => {
setEnv(res)
})
if (storage.getItem('login')) { if (storage.getItem('login')) {
const login = JSON.parse(storage.getItem('login') as string); const login = JSON.parse(storage.getItem('login') as string);
const data = { const data = {
@ -100,7 +106,32 @@ const Login: React.FC = () => {
} }
GetCheckUser(operation.account).then(res => { GetCheckUser(operation.account).then(res => {
if (res.code === 200) { if (res.code === 200) {
res.data ? setAccountPasswordStatus(true) : message.error('账号不存在!') if (res.data) {
GetCheckOnline(operation.account).then(req => {
if (req.code === 200) {
if (req.data) {
confirm({
title: '提示',
icon: <ExclamationCircleFilled />,
content: `账号已在其他地方登录,是否强制登陆?`,
centered: true,
okText: '确定',
cancelText: '取消',
async onOk() {
setAccountPasswordStatus(true)
},
onCancel() {
}
})
} else {
setAccountPasswordStatus(true)
}
}
})
} else {
message.error('账号不存在!')
}
} }
}) })
} }
@ -173,7 +204,7 @@ const Login: React.FC = () => {
<> <>
<div className={styles.login}> <div className={styles.login}>
<div className={styles.loginBg}> <div className={styles.loginBg}>
<img src={import.meta.env.VITE_ENV === 'xy' ? ImageUrl.icon53 : ImageUrl.icon1} alt="" /> {env ? <img src={env === 'xy' ? ImageUrl.icon53 : ImageUrl.icon1} alt="" /> : null}
</div> </div>
<div className={styles.loginContent}> <div className={styles.loginContent}>
<div> <div>
@ -315,6 +346,15 @@ const Login: React.FC = () => {
if (!anonInfo.nickName) { if (!anonInfo.nickName) {
return message.error('请输入参会昵称!') return message.error('请输入参会昵称!')
} }
if (anonInfo.nickName.length > 10) {
return message.error('参会昵称最多10个字')
}
storage.setItem('loading', true)
isVersion((bool: boolean) => {
storage.setItem('loading', false)
if (bool) {
window.electron.onDownload('3')
} else {
storage.setItem('loading', true) storage.setItem('loading', true)
PostAnonLogin(anonInfo).then(async (res) => { PostAnonLogin(anonInfo).then(async (res) => {
if (res.code == 200) { if (res.code == 200) {
@ -357,6 +397,8 @@ const Login: React.FC = () => {
}).catch(() => { }).catch(() => {
storage.setItem('loading', false) storage.setItem('loading', false)
}) })
}
})
}}></Button> }}></Button>
</div> </div>
</div> </div>

View File

@ -46,6 +46,8 @@
font-size: 14px; font-size: 14px;
color: #F3F3F5; color: #F3F3F5;
margin-left: 4px; margin-left: 4px;
display: flex;
align-items: center;
} }
>div {} >div {}
@ -77,6 +79,8 @@
>span { >span {
font-size: 14px; font-size: 14px;
color: #F3F3F5; color: #F3F3F5;
display: flex;
align-items: center;
} }
>div { >div {

View File

@ -206,19 +206,30 @@ const ChatBigWindow: React.FC = () => {
></Button> : null} ></Button> : null}
</div> : <div style={{ color: 'white' }}></div> </div> : <div style={{ color: 'white' }}></div>
}> }>
<div> <div title={item.userName}>
<div><Avatar name={item.userName} /></div> <div><Avatar name={item.userName} /></div>
{item.uid !== user.uid ? {item.uid !== user.uid ?
<span>{item.userName} <span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span></span> : <span>
<span> <span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')} </span>{item.userName}</span> <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '150px' }}>{item.userName}</span>
<span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span>
</span> :
<span>
<span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span>
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '150px' }}>{item.userName}</span>
</span>
} }
</div> </div>
</Popover> : <div> </Popover> : <div title={item.userName}>
<div><Avatar name={item.userName} /></div> <div><Avatar name={item.userName} /></div>
{item.uid !== user.uid ? {item.uid !== user.uid ?
<span>{item.userName}<span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span></span> : <span>
<span><span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')} </span>{item.userName}</span> <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '150px' }}>{item.userName}</span>
<span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span>
</span> :
<span>
<span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span>
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '150px' }}>{item.userName}</span>
</span>
} }
</div>} </div>}
<div>{item.message}</div> <div>{item.message}</div>

View File

@ -31,7 +31,7 @@
display: flex; display: flex;
>span:nth-child(1) { >span:nth-child(1) {
white-space: nowrap; word-break: break-all;
color: #ff970f; color: #ff970f;
} }

View File

@ -21,7 +21,10 @@ const NoticeWindow: React.FC = () => {
api.open({ api.open({
message: '', message: '',
description: <div> description: <div>
<span style={{ fontSize: '16px' }}>{noticeItem.uname}</span> <div style={{ fontSize: '16px' }}>
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={noticeItem.uname}>{noticeItem.uname}</div>
<div></div>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end' }} className='drag'> <div style={{ display: 'flex', justifyContent: 'flex-end' }} className='drag'>
<Button <Button
type="primary" type="primary"

View File

@ -202,7 +202,7 @@ const ShareScreenWindow: React.FC = () => {
}} }}
key={index}> key={index}>
<div> <div>
{!item.active ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})` }} id={`micr-item-${userInfo.uid}`}> {!item.active ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})`, transition: '0.18s' }} id={`micr-item-${userInfo.uid}`}>
</div> : ''} </div> : ''}
{item.select ? <img src={item.iconSelect} alt="" /> : <img src={item.active ? item.iconActive : item.icon} alt="" />} {item.select ? <img src={item.iconSelect} alt="" /> : <img src={item.active ? item.iconActive : item.icon} alt="" />}
</div> </div>

View File

@ -303,7 +303,7 @@
// 演讲者模式 // 演讲者模式
.meetingContentBodyLeftSpeakerMode { .meetingContentBodyLeftSpeakerMode {
width: 18%; width: 270px;
overflow-y: auto; overflow-y: auto;
height: 100%; height: 100%;
@ -312,7 +312,7 @@
} }
.meetingContentSwiperCard { .meetingContentSwiperCard {
height: calc(100% / 6); height: 160px;
} }
} }
@ -379,7 +379,7 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
height: 100% !important; height: 100% !important;
width: calc(100% - 18%) !important; width: calc(100% - 270px) !important;
z-index: 2; z-index: 2;
} }
@ -390,7 +390,7 @@
.meetingContentSwiperCard { .meetingContentSwiperCard {
height: 160px; height: 160px;
width: calc(100% / 6); width: 270px;
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
@ -444,8 +444,8 @@
color: white; color: white;
border: 1px white solid; border: 1px white solid;
font-size: 20px; font-size: 20px;
width: 30px; width: 25px;
height: 30px; height: 25px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -549,6 +549,8 @@
font-size: 14px; font-size: 14px;
color: #F3F3F5; color: #F3F3F5;
margin-left: 4px; margin-left: 4px;
display: flex;
align-items: center;
} }
>div { >div {
@ -660,6 +662,8 @@
font-size: 14px; font-size: 14px;
color: #F3F3F5; color: #F3F3F5;
margin-left: 4px; margin-left: 4px;
display: flex;
align-items: center;
} }
} }
@ -689,6 +693,8 @@
>span { >span {
font-size: 14px; font-size: 14px;
color: #F3F3F5; color: #F3F3F5;
display: flex;
align-items: center;
} }
>div { >div {
@ -798,7 +804,7 @@
} }
>img { >img {
height: 50px; height: 35px;
} }
>span { >span {
@ -820,8 +826,8 @@
} }
>label { >label {
height: 50px; height: 35px;
height: 50px; height: 35px;
cursor: pointer; cursor: pointer;
position: relative; position: relative;

View File

@ -8,12 +8,12 @@ import { SearchOutlined, EllipsisOutlined, ExclamationCircleFilled, FullscreenEx
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { thumbImageBufferToBase64 } from '@/utils/package/base64' import { thumbImageBufferToBase64 } from '@/utils/package/base64'
import { storage } from '@/utils'; import { storage } from '@/utils';
import { GetRoomUser, PostOpenMicr, GetSharedScreen, PostOpenCamera, GetLeaveAll, PostRoomManager, DeleteRoomManager, GetRoomKickout, GetShowUser, PostShowUser, PostMuteAll, GetRoomUserItem, GetApplySpeak, PostSharedScreen } from '@/api/Meeting'; import { GetRoomUser, PostOpenMicr, GetSharedScreen, PostOpenCamera, GetLeaveAll, PostRoomManager, DeleteRoomManager, GetRoomKickout, GetShowUser, PostShowUser, PostMuteAll, GetRoomUserItem, GetApplySpeak, PostSharedScreen, PostStopSharedScreen } from '@/api/Meeting';
import ImageUrl from '@/utils/package/imageUrl' import ImageUrl from '@/utils/package/imageUrl'
import { agora } from '@/utils/package/agora' import { agora } from '@/utils/package/agora'
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import durationPlugin from 'dayjs/plugin/duration'; import durationPlugin from 'dayjs/plugin/duration';
import { AudioVolumeInfo, ConnectionChangedReasonType, ConnectionStateType, LocalVideoStreamReason, LocalVideoStreamState, RenderModeType, RtcConnection, RtcStats, UserOfflineReasonType, VideoSourceType, VideoStreamType } from 'agora-electron-sdk'; import { AudioVolumeInfo, ConnectionChangedReasonType, ConnectionStateType, LocalVideoStreamReason, LocalVideoStreamState, RenderModeType, RtcConnection, RtcStats, StreamPublishState, UserOfflineReasonType, VideoSourceType, VideoStreamType } from 'agora-electron-sdk';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import SharedFilesModel from '@/components/SharedFilesModel'; import SharedFilesModel from '@/components/SharedFilesModel';
import StupWizard from '@/components/StupWizard'; import StupWizard from '@/components/StupWizard';
@ -26,6 +26,7 @@ import MeetingDisconnected from '@/components/MeetingDisconnected';
import SingIn from '@/components/SingIn'; import SingIn from '@/components/SingIn';
import UserName from '@/components/UserName'; import UserName from '@/components/UserName';
import { GetRoomRtcToken } from '@/api/Home/Index'; import { GetRoomRtcToken } from '@/api/Home/Index';
import Code from '@/components/Code';
const { setTimeout, setInterval, clearTimeout, clearInterval } = require('timers'); const { setTimeout, setInterval, clearTimeout, clearInterval } = require('timers');
const { confirm } = Modal; const { confirm } = Modal;
const { exec } = require('child_process'); const { exec } = require('child_process');
@ -158,6 +159,8 @@ const Meeting: React.FC = () => {
itemIndex: 0, itemIndex: 0,
rowIndex: 0, rowIndex: 0,
}); });
const [_audioStatus, setAudioStatus] = useState<StreamPublishState>(1);
const [_videoStatus, setVideoStatus] = useState<StreamPublishState>(1);
const [roomUserList, setRoomUserList] = useState<any>([]) const [roomUserList, setRoomUserList] = useState<any>([])
const [_speackUid, setSpeackUid] = useState<any>([]) const [_speackUid, setSpeackUid] = useState<any>([])
const [currentSpeakUser, setCurrentSpeakUser] = useState<any>([]) const [currentSpeakUser, setCurrentSpeakUser] = useState<any>([])
@ -215,6 +218,7 @@ const Meeting: React.FC = () => {
}); });
const [isVideoFullScreen, setIsVideoFullScreen] = useState<boolean>(false) const [isVideoFullScreen, setIsVideoFullScreen] = useState<boolean>(false)
const [observer, setObserver] = useState<IntersectionObserver>() const [observer, setObserver] = useState<IntersectionObserver>()
const [_activeSpeaker, setActiveSpeaker] = useState('')
let userInfo = JSON.parse(storage.getItem('user') as string) let userInfo = JSON.parse(storage.getItem('user') as string)
const msgTips = '您不是管理员或发言人,无法开启此功能!' const msgTips = '您不是管理员或发言人,无法开启此功能!'
const channel = new BroadcastChannel('meeting_channel'); const channel = new BroadcastChannel('meeting_channel');
@ -272,7 +276,7 @@ const Meeting: React.FC = () => {
case 'shareScreenWindowClose': case 'shareScreenWindowClose':
setCurrentSeconds(shareScreenWindowClose) setCurrentSeconds(shareScreenWindowClose)
await stopScreenCapture() await stopScreenCapture()
await allUserLook(userInfo.uid, userInfo.userName) await allUserLook(userInfo.uid, userInfo.userName, true)
break; break;
case 'shareScreenWindowfooterListsTitle': case 'shareScreenWindowfooterListsTitle':
switch (shareScreenWindowfooterListsTitle) { switch (shareScreenWindowfooterListsTitle) {
@ -719,7 +723,7 @@ const Meeting: React.FC = () => {
break; break;
// 扩展操作 // 扩展操作
case 'Operation': case 'Operation':
switch (item.type) { switch (item.contentString) {
} }
break; break;
@ -782,6 +786,14 @@ const Meeting: React.FC = () => {
break; break;
// 发言人用户信息刷新 // 发言人用户信息刷新
case 'ManagerRefresh': case 'ManagerRefresh':
if (!item.user.isRoomManager) {
setCurrentVideoId((res: any) => {
if (res === String(item.user.uid)) {
getShowUser()
}
return res
})
}
setAllUserListData('ManagerRefresh', item, async () => { setAllUserListData('ManagerRefresh', item, async () => {
if (item.user.uid === item.uid) { if (item.user.uid === item.uid) {
if (item.user.uid === userInfo.uid) { if (item.user.uid === userInfo.uid) {
@ -821,8 +833,6 @@ const Meeting: React.FC = () => {
break; break;
// 申请发言 // 申请发言
case 'ApplyToSpeak': case 'ApplyToSpeak':
setIsScreenCapture(bool => {
if (bool) {
window.electron.setChildWindowShow({ window.electron.setChildWindowShow({
key: 'noticeWindow', key: 'noticeWindow',
bool: true bool: true
@ -831,63 +841,6 @@ const Meeting: React.FC = () => {
type: 'noticeItem', type: 'noticeItem',
noticeItem: item noticeItem: item
}); });
} else {
api.open({
message: '',
description: <div>
<span style={{ fontSize: '16px' }}>{item.uname}</span>
<div style={{ display: 'flex', justifyContent: 'flex-end' }} className='drag'>
<Button
type="primary"
className='m-ant-btn'
onClick={async (e: any) => {
let i = e.nativeEvent.path;
if (i) {
i.forEach((i: any) => {
if (i.className === 'ant-notification-notice ant-notification-notice-closable') {
i.childNodes.forEach((row: any) => {
if (row.className === 'ant-notification-notice-close') {
row.click()
postRoomManager({
roomId: state.roomId,
roomNum: state.channelId,
userId: item.uid
})
}
})
}
})
}
}}
></Button>
<Button
type="primary"
onClick={(e: any) => {
let item = e.nativeEvent.path;
if (item) {
item.forEach((item: any) => {
if (item.className === 'ant-notification-notice ant-notification-notice-closable') {
item.childNodes.forEach((row: any) => {
if (row.className === 'ant-notification-notice-close') {
row.click()
}
})
}
})
}
}}
style={{ backgroundColor: '#EC3C3C', marginLeft: '14px' }}
></Button>
</div>
</div>,
duration: 10,
placement: 'bottomRight',
showProgress: true,
pauseOnHover: false,
});
}
return bool
})
break; break;
// 管理员查看随机用户 // 管理员查看随机用户
case 'Watch': case 'Watch':
@ -932,7 +885,7 @@ const Meeting: React.FC = () => {
} }
}), }),
playBackDeviceId: res[1].playBackList.find((row: any) => row.deviceId === setting.playBackDeviceId) ? setting.playBackDeviceId : res[1].playBackList.length ? res[1].playBackList[0].deviceId : null, playBackDeviceId: res[1].playBackList.find((row: any) => row.deviceId === setting.playBackDeviceId) ? setting.playBackDeviceId : res[1].playBackList.length ? res[1].playBackList[0].deviceId : null,
ecordingVolume: res[1].ecordingVolume ecordingVolume: setting.ecordingVolume
} }
window.electron.onInvoke('sendDrivers', { window.electron.onInvoke('sendDrivers', {
uid: item.callerUid, uid: item.callerUid,
@ -1023,10 +976,35 @@ const Meeting: React.FC = () => {
return res return res
}) })
break; break;
// 共享 // 设置发言人
case 'SetSpeaker': case 'SetSpeaker':
window.electron.onInvoke('SetSpeakerCallback', item.RoomManagerInputDTO) window.electron.onInvoke('SetSpeakerCallback', item.RoomManagerInputDTO)
break; break;
// 扩展参数
case 'ReceivedOperation':
try {
const temp = JSON.parse(item.contentString)
if (temp.type === 'audio') {
await PostOpenMicr({
roomNum: temp.roomNum,
uid: temp.uid,
enableMicr: temp.enableMicr
})
} else {
await PostOpenCamera({
roomNum: temp.roomNum,
uid: temp.uid,
enableCamera: temp.enableCamera
})
}
} catch (error) {
}
break;
// 共享人取消共享屏幕
case 'StopedSharedScreen':
setVideoUser()
break;
} }
}) })
return () => { return () => {
@ -1182,6 +1160,61 @@ const Meeting: React.FC = () => {
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [isClickedMediaSteam]); }, [isClickedMediaSteam]);
// useEffect(() => {
// let timer: NodeJS.Timeout | string = '';
// if (audioStatus === 1 && videoStatus === 1) {
// if (timer) {
// clearTimeout(timer)
// timer = ''
// }
// timer = setTimeout(() => {
// setIsShare((req: any) => {
// if (!req) {
// setRoomUserList((res: any) => {
// let userItem = res.find((item: any) => item.uid === userInfo.uid)
// if (!role.ID.includes(userInfo.roleId) && userItem && userItem.isRoomManager) {
// DeleteRoomManager({
// roomId: state.roomId,
// roomNum: state.channelId,
// userId: userInfo.uid
// })
// confirm({
// title: '提示',
// icon: <ExclamationCircleFilled />,
// content: `由于您长时间未发言,已自动取消发言权限,是否重新申请发言?`,
// centered: true,
// okText: '确定',
// cancelText: '取消',
// async onOk() {
// GetApplySpeak(state.channelId).then(res => {
// if (res.code === 200) {
// setIsClicked(true);
// message.success('申请发言成功')
// }
// })
// },
// onCancel() {
// }
// })
// }
// clearTimeout(timer)
// timer = ''
// return res
// })
// }
// return req
// })
// }, 1000 * 60 * 5);
// } else {
// if (timer) {
// clearTimeout(timer)
// timer = ''
// }
// }
// return () => timer ? clearTimeout(timer) : '';
// }, [audioStatus, videoStatus]);
useEffect(() => { useEffect(() => {
let timer: NodeJS.Timeout | undefined; let timer: NodeJS.Timeout | undefined;
if (timer) { if (timer) {
@ -1313,6 +1346,32 @@ const Meeting: React.FC = () => {
channelId: connection.channelId, channelId: connection.channelId,
renderMode: RenderModeType.RenderModeFit renderMode: RenderModeType.RenderModeFit
}) })
setCurrentVideoId((res: any) => {
if (res === String(remoteUid)) {
let dom: any;
setCurrentLookUserStatus(req => {
switch (req) {
case 1:
dom = document.getElementById(`video-source-camera-primary`) as HTMLElement
break;
case 2:
dom = document.getElementById(`video-source-screen`) as HTMLElement;
break;
case 3:
dom = document.getElementById(`video-source-remote-screen`) as HTMLElement
break;
case 4:
dom = document.getElementById(`video-source-remote-camera`) as HTMLElement
break;
}
if (dom && dom.childNodes.length === 1) {
renderVideo(String(remoteUid))
}
return req
})
}
return res
})
}, 1000); }, 1000);
} }
} }
@ -1369,6 +1428,20 @@ const Meeting: React.FC = () => {
} else if (state === 3) { } else if (state === 3) {
meetingDisconnectedRef.current.changeModal(false) meetingDisconnectedRef.current.changeModal(false)
setIsAgoraDisconnected(false) setIsAgoraDisconnected(false)
} else if (state === 5) {
confirm({
keyboard: false,
title: '提示',
icon: <ExclamationCircleFilled />,
content: `重连失败,请退出房间重试!`,
centered: true,
okText: '退出',
wrapClassName: 'hideCancelText',
cancelText: '',
async onOk() {
leaveChannel()
},
})
} }
}, },
onConnectionLost: () => { onConnectionLost: () => {
@ -1388,18 +1461,33 @@ const Meeting: React.FC = () => {
if (bool) { if (bool) {
stopScreenCapture() stopScreenCapture()
setSharedScreenItem('') setSharedScreenItem('')
allUserLook(userInfo.uid, userInfo.userName) allUserLook(userInfo.uid, userInfo.userName, true)
} }
return bool return bool
}) })
} else if (reason === 3 || reason === 4) { } else if (reason === 3 || reason === 4) {
message.error({ message.error({
content: <div><span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => { content: <div><span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
stupWizardRef.current.changeModal(1); stupWizardRef.current.changeModal(1);
}}></span></div>, }}></span></div>,
duration: 60, duration: 15,
key: 'cameraTemporarily' key: 'cameraTemporarily'
}); });
} else if (_state === 3) {
if (!stupWizardRef.current.getStupWizardModal()) {
await PostOpenCamera({
roomNum: state.channelId,
uid: userInfo.uid,
enableCamera: false
})
message.error({
content: <div><span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
stupWizardRef.current.changeModal(1);
}}></span></div>,
duration: 15,
key: 'cameraTemporarily'
});
}
} }
}, },
onTokenPrivilegeWillExpire: async (connection: RtcConnection, _token: string) => { onTokenPrivilegeWillExpire: async (connection: RtcConnection, _token: string) => {
@ -1411,6 +1499,23 @@ const Meeting: React.FC = () => {
}) })
} }
}) })
},
onVideoPublishStateChanged: (_source: VideoSourceType, _channel: string, _oldState: StreamPublishState, newState: StreamPublishState, _elapseSinceLastState: number) => {
setVideoStatus(newState)
},
onAudioPublishStateChanged: (_channel: string, _oldState: StreamPublishState, newState: StreamPublishState, _elapseSinceLastState: number) => {
setAudioStatus(newState)
},
onActiveSpeaker: (_connection: RtcConnection, uid: number) => {
if (String(uid).length !== 9) {
setActiveSpeaker(String(uid))
}
setIsShare((res: any) => {
if (!res && String(uid).length !== 9) {
renderVideo(String(uid))
}
return res
})
} }
}) })
if (state.enableCamera) { if (state.enableCamera) {
@ -1664,6 +1769,20 @@ const Meeting: React.FC = () => {
}) })
} }
} }
const setVideoUser = () => {
setRoomUserList((newChatList: any) => {
setActiveSpeaker(res => {
let item = newChatList.find((item: any) => item.uid === res)
if (item && item.isRoom && item.isAdmin) {
renderVideo(res)
} else {
getShowUser()
}
return res
})
return newChatList
})
}
// 加入房间时间 // 加入房间时间
const changeCurrentSeconds = (): string => { const changeCurrentSeconds = (): string => {
const duration = dayjs.duration(currentSeconds, 'seconds'); const duration = dayjs.duration(currentSeconds, 'seconds');
@ -1731,8 +1850,8 @@ const Meeting: React.FC = () => {
if (res) { if (res) {
GetSharedScreen(state.channelId).then(req => { GetSharedScreen(state.channelId).then(req => {
if (req.code === 200) { if (req.code === 200) {
if (res.data) { if (req.data) {
setIsShare(res.data) setIsShare(req.data)
} }
getDesktopCapturerVideo() getDesktopCapturerVideo()
setIsSharedScreenModal(true) setIsSharedScreenModal(true)
@ -1753,7 +1872,7 @@ const Meeting: React.FC = () => {
} }
}) })
if (row.title === '停止共享') { if (row.title === '停止共享') {
await allUserLook(userInfo.uid, userInfo.userName) await allUserLook(userInfo.uid, userInfo.userName, true)
} }
break; break;
case '静音': case '静音':
@ -2020,9 +2139,13 @@ const Meeting: React.FC = () => {
}) })
}; };
// 设置全员看谁 // 设置全员看谁
const allUserLook = async (uid: string, name: string): Promise<void> => { const allUserLook = async (uid: string, name: string, bool?: boolean): Promise<void> => {
if (bool) {
await PostStopSharedScreen(state.channelId)
} else {
await PostShowUser(state.channelId, uid, name) await PostShowUser(state.channelId, uid, name)
} }
}
// 设置发言人 // 设置发言人
const postRoomManager = async (data: any): Promise<void> => { const postRoomManager = async (data: any): Promise<void> => {
if (isAdmin >= 20) { if (isAdmin >= 20) {
@ -2083,7 +2206,25 @@ const Meeting: React.FC = () => {
item.isRoom = true; item.isRoom = true;
item.isAdmin = role.ID.includes(item.roleId) || item.isRoomManager item.isAdmin = role.ID.includes(item.roleId) || item.isRoomManager
}) })
setRoomUserList(res.data) setRoomUserList((req: any) => {
if (req.length) {
let arr: any = []
res.data.forEach((item: any) => {
let userItem = req.find((row: any) => row.uid == item.uid);
if (userItem) {
userItem.enableCamera = item.enableCamera;
userItem.enableMicr = item.enableMicr;
userItem.isRoomManager = item.isRoomManager;
userItem.isAdmin = role.ID.includes(item.roleId) || item.isRoomManager;
} else {
arr.push(item)
}
});
return [...req, ...arr]
} else {
return res.data
}
})
getUserRoomInfo().then(async (res) => { getUserRoomInfo().then(async (res) => {
await agora.updateChannelMediaOptions(res ? true : false) await agora.updateChannelMediaOptions(res ? true : false)
changeAgoraDevice() changeAgoraDevice()
@ -2115,7 +2256,7 @@ const Meeting: React.FC = () => {
enableCamera: !storeDevice[0][1].active, enableCamera: !storeDevice[0][1].active,
isRoomManager: userItem ? userItem.isRoomManager : false, isRoomManager: userItem ? userItem.isRoomManager : false,
}) })
await getShowUser() setVideoUser()
if (userItem.isRoomManager) { if (userItem.isRoomManager) {
await postOpenMicr(!storeDevice[0][0].active, userInfo.uid) await postOpenMicr(!storeDevice[0][0].active, userInfo.uid)
await postOpenCamera(!storeDevice[0][1].active, userInfo.uid) await postOpenCamera(!storeDevice[0][1].active, userInfo.uid)
@ -2147,13 +2288,15 @@ const Meeting: React.FC = () => {
const sendMsg = (text?: string): void => { const sendMsg = (text?: string): void => {
let msg = text ? text : textMsg; let msg = text ? text : textMsg;
if (msg) { if (msg) {
setRoomUserList((res: any) => {
let row = res.find((item: any) => item.uid === userInfo.uid)
window.electron.onInvoke('sendChannelMsg', { window.electron.onInvoke('sendChannelMsg', {
roomNum: state.channelId, roomNum: state.channelId,
msg: msg, msg: msg,
}) })
let item = { let item = {
uid: userInfo.uid, uid: userInfo.uid,
userName: userInfo.userName, userName: row.userName,
message: msg, message: msg,
timestamp: +new Date() timestamp: +new Date()
} }
@ -2164,8 +2307,9 @@ const Meeting: React.FC = () => {
chatListIten: item, chatListIten: item,
} }
}) })
setTextMsg('');
chatScrollBotton() chatScrollBotton()
return res
})
} else { } else {
message.error('请输入内容!') message.error('请输入内容!')
} }
@ -2213,8 +2357,6 @@ const Meeting: React.FC = () => {
} }
}) })
} }
// 开关麦克风 // 开关麦克风
const postOpenMicrApi = async (enableMicr: boolean, uid: string, isAll: boolean, isMessage: boolean = false): Promise<void> => { const postOpenMicrApi = async (enableMicr: boolean, uid: string, isAll: boolean, isMessage: boolean = false): Promise<void> => {
if (isAll) { if (isAll) {
@ -2223,11 +2365,20 @@ const Meeting: React.FC = () => {
enableMicr enableMicr
}) })
} else { } else {
await PostOpenMicr({ await window.electron.onInvoke('sendOper2User', {
uid,
contentString: JSON.stringify({
roomNum: state.channelId, roomNum: state.channelId,
uid, uid,
enableMicr enableMicr,
type: 'audio'
}) })
})
// await PostOpenMicr({
// roomNum: state.channelId,
// uid,
// enableMicr
// })
} }
if (isMessage) { if (isMessage) {
// message.success('操作成功') // message.success('操作成功')
@ -2268,11 +2419,20 @@ const Meeting: React.FC = () => {
} else { } else {
await agora.stopCameraCapture(); await agora.stopCameraCapture();
} }
await PostOpenCamera({ await window.electron.onInvoke('sendOper2User', {
uid,
contentString: JSON.stringify({
roomNum: state.channelId, roomNum: state.channelId,
uid, uid,
enableCamera enableCamera,
type: 'video'
}) })
})
// await PostOpenCamera({
// roomNum: state.channelId,
// uid,
// enableCamera
// })
if (isMessage) { if (isMessage) {
// message.success('操作成功') // message.success('操作成功')
} }
@ -2376,7 +2536,7 @@ const Meeting: React.FC = () => {
</svg> </svg>
</div> //右 </div> //右
} else { } else {
return <div className={`${styles.speakerModeIcon} drag`} style={{ left: 'calc(18% - 4px)' }} title='收起' onClick={() => setIsVideoFullScreen(true)}> return <div className={`${styles.speakerModeIcon} drag`} style={{ left: 'calc(270px - 4px)' }} title='收起' onClick={() => setIsVideoFullScreen(true)}>
<svg width="12" height="22" viewBox="0 0 12 22" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="12" height="22" viewBox="0 0 12 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.9355 1.45949L10.7759 0.299805L0.0636029 11.0143L10.7759 21.7275L11.9355 20.5679L2.38107 11.0136L11.9355 1.45949Z" /> <path d="M11.9355 1.45949L10.7759 0.299805L0.0636029 11.0143L10.7759 21.7275L11.9355 20.5679L2.38107 11.0136L11.9355 1.45949Z" />
</svg> </svg>
@ -2415,6 +2575,14 @@ const Meeting: React.FC = () => {
} }
message.success('操作成功') message.success('操作成功')
} }
// 判断是否出现滚动条
const hasScrollbar = () => {
let element = document.getElementById('videoView') as HTMLDivElement
if (element) {
return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
}
return false
}
// 移出房间 // 移出房间
const getRoomKickout = async (channelId: string, uid: string, userName: string): Promise<void> => { const getRoomKickout = async (channelId: string, uid: string, userName: string): Promise<void> => {
confirm({ confirm({
@ -2601,7 +2769,9 @@ const Meeting: React.FC = () => {
</Popover>} </Popover>}
<div>{changeCurrentSeconds()}</div> <div>{changeCurrentSeconds()}</div>
</div> </div>
<div>{state.channelId} {state.roomName}</div> <div style={{ display: 'flex', alignItems: 'center' }}>{state.channelId} {state.roomName}
<span className='drag' style={{ marginTop: '2px', marginLeft: '4px' }}><Code roomNum={state.channelId}></Code></span>
</div>
<div className='drag'> <div className='drag'>
<Popover <Popover
content={ content={
@ -2769,13 +2939,13 @@ const Meeting: React.FC = () => {
</div> : null) </div> : null)
} }
)} )}
{isAdmin > 6 ? <div> {hasScrollbar() ? <div>
{meetingMode === "StandardMode" ? <div className={`${styles.meetingContentSwiperCaret}`} style={{ left: '20px', top: '66px' }} onClick={() => { {meetingMode === "StandardMode" ? <div className={`${styles.meetingContentSwiperCaret}`} style={{ left: '20px', top: '66px' }} onClick={() => {
const container = document.getElementById('videoView') as HTMLElement; const container = document.getElementById('videoView') as HTMLElement;
container.scrollLeft -= 100 container.scrollLeft -= 100
}}> }}>
<CaretLeftOutlined /> <CaretLeftOutlined />
</div> : <div className={`${styles.meetingContentSwiperCaret}`} style={{ left: '8.2%', top: '20px' }} onClick={() => { </div> : <div className={`${styles.meetingContentSwiperCaret}`} style={{ left: '115px', top: '20px' }} onClick={() => {
const container = document.getElementById('videoView') as HTMLElement; const container = document.getElementById('videoView') as HTMLElement;
container.scrollTop -= 100 container.scrollTop -= 100
}}> }}>
@ -2786,7 +2956,7 @@ const Meeting: React.FC = () => {
container.scrollLeft += 100 container.scrollLeft += 100
}}> }}>
<CaretRightOutlined /> <CaretRightOutlined />
</div> : <div className={`${styles.meetingContentSwiperCaret}`} style={{ left: '8.2%', bottom: '20px' }} onClick={() => { </div> : <div className={`${styles.meetingContentSwiperCaret}`} style={{ left: '115px', bottom: '20px' }} onClick={() => {
const container = document.getElementById('videoView') as HTMLElement; const container = document.getElementById('videoView') as HTMLElement;
container.scrollTop += 100 container.scrollTop += 100
}}> }}>
@ -2900,7 +3070,7 @@ const Meeting: React.FC = () => {
<div> <div>
<div><Avatar name={item.userName} /></div> <div><Avatar name={item.userName} /></div>
<span> <span>
{item.userName}{item.uid === user.uid ? '(我)' : ''} <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: role.ID.includes(item.roleId) || item.isRoomManager ? '50px' : '80px' }} title={item.userName}>{item.userName}</span>{item.uid === user.uid ? '(我)' : ''}
{role.ID.includes(item.roleId) || item.isRoomManager ? {role.ID.includes(item.roleId) || item.isRoomManager ?
<span style={{ color: '#02B188', marginLeft: '4px' }}> <span style={{ color: '#02B188', marginLeft: '4px' }}>
{role.ID.includes(item.roleId) ? '管理员' : '发言人'} {role.ID.includes(item.roleId) ? '管理员' : '发言人'}
@ -3120,19 +3290,30 @@ const Meeting: React.FC = () => {
></Button> : null} ></Button> : null}
</div> : <div style={{ color: 'white' }}></div> </div> : <div style={{ color: 'white' }}></div>
}> }>
<div> <div title={item.userName}>
<div><Avatar name={item.userName} /></div> <div><Avatar name={item.userName} /></div>
{item.uid !== user.uid ? {item.uid !== user.uid ?
<span>{item.userName} <span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span></span> : <span>
<span> <span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')} </span>{item.userName}</span> <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '150px' }}>{item.userName}</span>
<span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span>
</span> :
<span>
<span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span>
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '150px' }}>{item.userName}</span>
</span>
} }
</div> </div>
</Popover> : <div> </Popover> : <div title={item.userName}>
<div><Avatar name={item.userName} /></div> <div><Avatar name={item.userName} /></div>
{item.uid !== user.uid ? {item.uid !== user.uid ?
<span>{item.userName}<span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span></span> : <span>
<span><span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')} </span>{item.userName}</span> <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '150px' }}>{item.userName}</span>
<span style={{ fontSize: '12px', color: '#ccc', marginLeft: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span>
</span> :
<span>
<span style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')}</span>
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '150px' }}>{item.userName}</span>
</span>
} }
</div>} </div>}
<div>{item.message}</div> <div>{item.message}</div>
@ -3157,7 +3338,10 @@ const Meeting: React.FC = () => {
<Input.TextArea placeholder="请输入消息" value={textMsg} style={{ flexGrow: 1 }} onChange={(e) => { <Input.TextArea placeholder="请输入消息" value={textMsg} style={{ flexGrow: 1 }} onChange={(e) => {
setTextMsg(e.target.value) setTextMsg(e.target.value)
}}></Input.TextArea> }}></Input.TextArea>
<Button type="primary" className='m-ant-btn' style={{ flexShrink: 0, marginTop: '4px' }} onClick={() => sendMsg()}></Button> <Button type="primary" className='m-ant-btn' style={{ flexShrink: 0, marginTop: '4px' }} onClick={() => {
sendMsg()
setTextMsg('');
}}></Button>
</div> </div>
</div> </div>
: :
@ -3327,7 +3511,7 @@ const Meeting: React.FC = () => {
key={rowIndex}> key={rowIndex}>
<label> <label>
<img src={row.active ? row.iconActive : row.icon} alt="" /> <img src={row.active ? row.iconActive : row.icon} alt="" />
{!row.active ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})` }} id={`micr-item-${userInfo.uid}`}> {!row.active ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})`, transition: '0.18s' }} id={`micr-item-${userInfo.uid}`}>
</div> : ''} </div> : ''}
</label> </label>
<span>{row.title}</span> <span>{row.title}</span>
@ -3494,7 +3678,7 @@ const meetingContentUser = (item: any, bool?: boolean) => {
<> <>
<div className={styles.meetingContentUser}> <div className={styles.meetingContentUser}>
<div className={styles.meetingContentUserName}> <div className={styles.meetingContentUserName}>
{role.ID.includes(item.roleId) || item.isRoomManager ? {role.ID.includes(item.roleId) ?
<div style={{ background: role.ID.includes(item.roleId) ? '#FDC229' : '#3F51B5' }}> <div style={{ background: role.ID.includes(item.roleId) ? '#FDC229' : '#3F51B5' }}>
<img src={ImageUrl.icon32} alt="" /> <img src={ImageUrl.icon32} alt="" />
</div> : null} </div> : null}
@ -3502,7 +3686,7 @@ const meetingContentUser = (item: any, bool?: boolean) => {
bool ? !item.enableMicr ? <img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" /> : '' : bool ? !item.enableMicr ? <img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" /> : '' :
<label> <label>
<img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" /> <img src={item.enableMicr ? ImageUrl.icon22 : ImageUrl.icon22Active} alt="" />
{item.enableMicr ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})` }} id={`micr-${item.uid}`}> {item.enableMicr ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})`, transition: '0.18s' }} id={`micr-${item.uid}`}>
</div> : ''} </div> : ''}
</label> </label>
} }

3
src/render.d.ts vendored
View File

@ -24,9 +24,10 @@ export interface IElectronAPI {
downFile: (callBack: Function) => void; downFile: (callBack: Function) => void;
quitAndInstall: (callBack: Function) => void; quitAndInstall: (callBack: Function) => void;
isOpenWindows: (callBack: Function) => void; isOpenWindows: (callBack: Function) => void;
setEnv: (str: string) => any; startLoad: () => any;
updateHandle: () => any; updateHandle: () => any;
getVersion: () => Promise<string>; getVersion: () => Promise<string>;
getEnv: () => Promise<string>;
isVisible: () => Promise<string>; isVisible: () => Promise<string>;
setRegistry: (uuid: string) => any; setRegistry: (uuid: string) => any;
getRegistry: () => any; getRegistry: () => any;

View File

@ -1,3 +1,4 @@
declare module 'react-dom/client'; declare module 'react-dom/client';
declare module 'crypto-js'; declare module 'crypto-js';
declare module 'js-yaml';
declare module 'uuid'; declare module 'uuid';

View File

@ -21,7 +21,8 @@ import {
ColorEnhanceOptions, ColorEnhanceOptions,
LowlightEnhanceOptions, LowlightEnhanceOptions,
VirtualBackgroundSource, VirtualBackgroundSource,
AudienceLatencyLevelType AudienceLatencyLevelType,
StreamPublishState
} from "agora-electron-sdk"; } from "agora-electron-sdk";
import { GetRoomRtcToken, GetAgoraConf } from "@/api/Home/Index"; import { GetRoomRtcToken, GetAgoraConf } from "@/api/Home/Index";
import { storage } from '@/utils'; import { storage } from '@/utils';
@ -124,7 +125,8 @@ export const agora = {
if (settingData.darkLightEnhancement) agora.setLowlightEnhanceOptions(settingData.darkLightEnhancement.isDarkLightEnhancement, settingData.darkLightEnhancement) if (settingData.darkLightEnhancement) agora.setLowlightEnhanceOptions(settingData.darkLightEnhancement.isDarkLightEnhancement, settingData.darkLightEnhancement)
if (settingData.virtualBackground) { if (settingData.virtualBackground) {
if (typeof settingData.virtualBackground.sourceIndex === 'number') { if (typeof settingData.virtualBackground.sourceIndex === 'number') {
if (import.meta.env.VITE_ENV === 'development') { window.electron.getEnv().then(res => {
if (res === 'development') {
window.electron.getAppPath().then((res: string) => { window.electron.getAppPath().then((res: string) => {
const imagePath = path.join(res, 'src', 'assets', 'virtualBackground', `${settingData.virtualBackground.sourceIndex + 1}.png`); const imagePath = path.join(res, 'src', 'assets', 'virtualBackground', `${settingData.virtualBackground.sourceIndex + 1}.png`);
agora.enableVirtualBackground(settingData.virtualBackground.isVirtualBackground, { agora.enableVirtualBackground(settingData.virtualBackground.isVirtualBackground, {
@ -141,6 +143,7 @@ export const agora = {
color: Number(settingData.virtualBackground.color), color: Number(settingData.virtualBackground.color),
}) })
} }
})
} else { } else {
agora.enableVirtualBackground(settingData.virtualBackground.isVirtualBackground, { agora.enableVirtualBackground(settingData.virtualBackground.isVirtualBackground, {
background_source_type: 1, background_source_type: 1,
@ -151,7 +154,7 @@ export const agora = {
}, 1000); }, 1000);
}, },
// 事件回调 // 事件回调
registerEventHandler: ({ onJoinChannelSuccess, onUserJoined, onUserOffline, onAudioVolumeIndication, onRtcStats, onConnectionStateChanged, onLocalVideoStateChanged, onConnectionLost, onTokenPrivilegeWillExpire }: any) => { registerEventHandler: ({ onJoinChannelSuccess, onUserJoined, onUserOffline, onAudioVolumeIndication, onRtcStats, onConnectionStateChanged, onLocalVideoStateChanged, onConnectionLost, onTokenPrivilegeWillExpire, onActiveSpeaker, onVideoPublishStateChanged, onAudioPublishStateChanged }: any) => {
rtcEngine.registerEventHandler({ rtcEngine.registerEventHandler({
// 监听本地用户加入频道事件 // 监听本地用户加入频道事件
onJoinChannelSuccess: async (connection: RtcConnection, elapsed: number) => { onJoinChannelSuccess: async (connection: RtcConnection, elapsed: number) => {
@ -166,17 +169,13 @@ export const agora = {
await onUserOffline?.(connection, remoteUid, reason) await onUserOffline?.(connection, remoteUid, reason)
}, },
// // 视频发布状态改变回调 // // 视频发布状态改变回调
// onVideoPublishStateChanged: (source: any, channel: any, oldState: any, newState: any, elapseSinceLastState: any) => { onVideoPublishStateChanged: (source: VideoSourceType, channel: string, oldState: StreamPublishState, newState: StreamPublishState, elapseSinceLastState: number) => {
// if (newState === 1) { onVideoPublishStateChanged?.(source, channel, oldState, newState, elapseSinceLastState)
},
// }
// },
// // 音频发布状态改变回调 // // 音频发布状态改变回调
// onAudioPublishStateChanged: (channel: any, oldState: any, newState: any, elapseSinceLastState: any) => { onAudioPublishStateChanged: (channel: string, oldState: StreamPublishState, newState: StreamPublishState, elapseSinceLastState: number) => {
// if (newState === 1) { onAudioPublishStateChanged?.(channel, oldState, newState, elapseSinceLastState)
},
// }
// },
// // 用户音量提示回调。 // // 用户音量提示回调。
onAudioVolumeIndication: async (_connection: RtcConnection, speakers: AudioVolumeInfo[], _speakerNumber: number, _totalVolume: number) => { onAudioVolumeIndication: async (_connection: RtcConnection, speakers: AudioVolumeInfo[], _speakerNumber: number, _totalVolume: number) => {
await onAudioVolumeIndication?.(speakers) await onAudioVolumeIndication?.(speakers)
@ -200,6 +199,13 @@ export const agora = {
// Token 即将在 30s 内过期回调。 // Token 即将在 30s 内过期回调。
onTokenPrivilegeWillExpire: (connection: RtcConnection, token: string) => { onTokenPrivilegeWillExpire: (connection: RtcConnection, token: string) => {
onTokenPrivilegeWillExpire?.(connection, token) onTokenPrivilegeWillExpire?.(connection, token)
},
// 监测到远端最活跃用户回调。
onActiveSpeaker: (connection: RtcConnection, uid: number) => {
const setting = JSON.parse(storage.getItem('setting') as string);
if (setting.voiceStimulation) {
onActiveSpeaker?.(connection, uid)
}
} }
}); });
}, },
@ -289,7 +295,7 @@ export const agora = {
}, },
// 加入频道 // 加入频道
joinChannel: async () => { joinChannel: async () => {
await rtcEngine.enableAudioVolumeIndication(100, 3, true) await rtcEngine.enableAudioVolumeIndication(200, 3, true)
await rtcEngine.joinChannel(option.token, option.channelId, option.uid); await rtcEngine.joinChannel(option.token, option.channelId, option.uid);
await rtcEngine.setDualStreamModeEx( await rtcEngine.setDualStreamModeEx(
SimulcastStreamMode.EnableSimulcastStream, SimulcastStreamMode.EnableSimulcastStream,
@ -302,6 +308,7 @@ export const agora = {
}, },
{ channelId: option.channelId, localUid: Number(option.uid) } { channelId: option.channelId, localUid: Number(option.uid) }
); );
await rtcEngine.setAudioScenario(8)
}, },
// 更新频道配置 // 更新频道配置
updateChannelMediaOptions: async (bool: boolean) => { updateChannelMediaOptions: async (bool: boolean) => {
@ -441,8 +448,8 @@ export const agora = {
startCameraCapture: async (bool: boolean = false) => { startCameraCapture: async (bool: boolean = false) => {
await rtcEngine.startCameraCapture(VideoSourceType.VideoSourceCamera, { await rtcEngine.startCameraCapture(VideoSourceType.VideoSourceCamera, {
format: { format: {
width: bool ? 160 : 1280, width: bool ? 160 : 1920,
height: bool ? 160 : 720, height: bool ? 160 : 1080,
fps: 15, fps: 15,
} }
}) })
@ -472,7 +479,7 @@ export const agora = {
} }
let data = { let data = {
frameRate: isFluencyPriority ? 30 : 15, frameRate: isFluencyPriority ? 15 : 7,
dimensions: { dimensions: {
width: 1920, width: 1920,
height: 1080, height: 1080,

View File

@ -1,5 +1,7 @@
import path from "path"; import path from "path";
import storage from "./storage"; import storage from "./storage";
import axios from "axios";
import yaml from 'js-yaml'
export const setKeyOpenChildWindow = async (key: string, bool: boolean) => { export const setKeyOpenChildWindow = async (key: string, bool: boolean) => {
const openChildWindow = await JSON.parse(storage.getItem('openChildWindow') as string) const openChildWindow = await JSON.parse(storage.getItem('openChildWindow') as string)
openChildWindow[key] = bool; openChildWindow[key] = bool;
@ -27,6 +29,7 @@ export const storageSeeting: any = {
shareFilesPath: path.resolve(__dirname, '../../Downloads/') + '\\', //共享文件保存路径 shareFilesPath: path.resolve(__dirname, '../../Downloads/') + '\\', //共享文件保存路径
isShareSavePath: true, //是否下载钱询问每个文件保存的位置 isShareSavePath: true, //是否下载钱询问每个文件保存的位置
closeSetting: 'hide', //关闭按钮设置 closeSetting: 'hide', //关闭按钮设置
voiceStimulation: true, //语音激励
isAINoiseReduction: true, //是否开启ai降噪 isAINoiseReduction: true, //是否开启ai降噪
aINoiseReduction: 1, // 降噪模式 aINoiseReduction: 1, // 降噪模式
isRecordingTips: true, //是否开启录制提示 isRecordingTips: true, //是否开启录制提示
@ -59,15 +62,61 @@ export const getUpdateUrl = (env: string) => {
switch (env) { switch (env) {
case 'xy': case 'xy':
return 'https://meeting-api.23544.com/meeting/xysz' return 'https://meeting-api.23544.com/meeting/xysz'
case 'development':
return 'http://192.168.2.9:8827'
default: default:
return 'https://meeting-api.23544.com/meeting/update' return 'https://meeting-api.23544.com/meeting/update'
} }
} }
export const getTitle = (env: string) => { export const getTitle = async () => {
let env = await window.electron.getEnv()
let str;
switch (env) { switch (env) {
case 'xy': case 'xy':
return '湖北襄阳四中教研平台' str = '湖北襄阳四中教研平台'
break;
case 'development':
str = '智汇享'
break;
default: default:
return '智汇享' str = '智汇享'
break;
} }
document.getElementsByTagName('title')[0].innerText = str
}
export const compareVersions = (version1: string, version2: string): number => {
const v1Parts = version1.split('.').map(Number);
const v2Parts = version2.split('.').map(Number);
const maxLength = Math.max(v1Parts.length, v2Parts.length);
for (let i = 0; i < maxLength; i++) {
const v1 = v1Parts[i] || 0;
const v2 = v2Parts[i] || 0;
if (v1 > v2) {
return 1;
} else if (v1 < v2) {
return -1;
}
}
return 0;
}
export const isVersion = (callBack: Function) => {
window.electron.getEnv().then(res => {
axios.get(`${getUpdateUrl(res)}/latest.yml`).then(res => {
if (res.status === 200 && res.data) {
const data = yaml.load(res.data); // 解析 YAML 内容
window.electron.getVersion().then(req => {
if (compareVersions(data.version, req) == 1) {
callBack(true, data)
} else {
callBack(false, data)
}
})
} else {
callBack(false)
}
}).catch(() => {
callBack(false)
})
})
} }

View File

@ -3,6 +3,8 @@ module.exports = {
switch (env) { switch (env) {
case 'xy': case 'xy':
return 'https://meeting-api.23544.com/meeting/xysz' return 'https://meeting-api.23544.com/meeting/xysz'
case 'development':
return 'http://192.168.2.9:8827'
default: default:
return 'https://meeting-api.23544.com/meeting/update' return 'https://meeting-api.23544.com/meeting/update'
} }
@ -11,6 +13,8 @@ module.exports = {
switch (env) { switch (env) {
case 'xy': case 'xy':
return '湖北襄阳四中教研平台' return '湖北襄阳四中教研平台'
case 'development':
return '智汇享'
default: default:
return '智汇享' return '智汇享'
} }
@ -19,6 +23,8 @@ module.exports = {
switch (env) { switch (env) {
case 'xy': case 'xy':
return 'icon54' return 'icon54'
case 'development':
return 'icon'
default: default:
return 'icon' return 'icon'
} }

View File

@ -1,10 +1,10 @@
import { AxiosRequestConfig, AxiosResponse } from 'axios' import { AxiosRequestConfig, AxiosResponse } from 'axios'
import Request from './request' import Request from './request'
import { constant } from '@/config' import { constant } from '@/config'
let baseURL = !location.hostname.includes('meeting-api.23544.com') ? 'http://192.168.2.9:5192' : 'https://meeting-api.23544.com/pc'
// 实例化 // 实例化
const req = new Request({ const req = new Request({
baseURL: import.meta.env.VITE_BASE_URL_API, baseURL,
timeout: constant.CONFIG_REQUEST_TIMEOUT_TIME as number, timeout: constant.CONFIG_REQUEST_TIMEOUT_TIME as number,
interceptors: { interceptors: {
// 请求拦截器 // 请求拦截器
@ -22,5 +22,4 @@ const request = (config: any) => {
} }
return req.request<any>(config) return req.request<any>(config)
} }
export default request export default request

View File

@ -77,6 +77,9 @@ class Request {
case 403: case 403:
updatePostRefresh() updatePostRefresh()
break break
case 502:
message.error('网络已断开,请检查网络状态')
break
default: default:
message.error(err.message) message.error(err.message)
break; break;

View File

@ -76,7 +76,9 @@ export default defineConfig({
ColorEnhanceOptions, ColorEnhanceOptions,
LowlightEnhanceOptions, LowlightEnhanceOptions,
VirtualBackgroundSource, VirtualBackgroundSource,
AudienceLatencyLevelType AudienceLatencyLevelType,
StreamPublishState,
IMediaEngine
} = require("agora-electron-sdk") } = require("agora-electron-sdk")
export { export {
createAgoraRtcEngine, createAgoraRtcEngine,
@ -101,7 +103,9 @@ export default defineConfig({
ColorEnhanceOptions, ColorEnhanceOptions,
LowlightEnhanceOptions, LowlightEnhanceOptions,
VirtualBackgroundSource, VirtualBackgroundSource,
AudienceLatencyLevelType AudienceLatencyLevelType,
StreamPublishState,
IMediaEngine
} }
`, `,
}) })