Compare commits

...

133 Commits

Author SHA1 Message Date
yangqiang 016dcf6e09 Merge pull request 'yangjie' (#49) from yangjie into master
Reviewed-on: #49
2025-03-10 14:21:26 +08:00
yj bde5002e1e 更新错误弹窗手动更新 2025-03-06 09:46:45 +08:00
yj bcfc790dd3 下载错误手动下载 2025-03-05 18:15:40 +08:00
yj 55deae9654 注释多余代码 2025-03-05 16:50:28 +08:00
yj a08e0a6ece 更新版本 2025-03-05 15:58:34 +08:00
yj 00c16d44ff 优化 2025-03-05 15:58:18 +08:00
yj e90186e4b9 去除多余图标 2025-03-05 15:46:35 +08:00
yj c9a6984a02 优化 2025-03-05 14:39:53 +08:00
yj 440f19b436 优化 2025-03-05 14:29:43 +08:00
yj f0c79d64cd 打包优化 2025-03-05 13:39:18 +08:00
yj f6aca11a83 优化 2025-03-05 11:37:19 +08:00
yj c69d965183 优化 2025-03-05 11:11:53 +08:00
yj 489d5c8511 环境配置优化 2025-03-05 10:41:50 +08:00
yj 76e968322d 去除多余代码 2025-03-05 09:59:39 +08:00
yj ef55fab17d 优化样式 2025-02-28 16:43:17 +08:00
yj 1f897ad6b3 更新版本号 2025-02-28 14:19:03 +08:00
yj 56e3a19356 修改打包配置 2025-02-27 17:56:06 +08:00
yj a02232a770 修改配置 2025-02-27 17:45:57 +08:00
yj 715ee0be43 语音流畅度 2025-02-27 17:31:30 +08:00
yj d6e9de24bf 优化 2025-02-27 13:52:31 +08:00
yj 860f141782 优化 2025-02-27 13:47:09 +08:00
yj 882bacb2d5 优化 2025-02-27 13:46:29 +08:00
yj 700566fd7e 优化 2025-02-27 13:46:16 +08:00
yj f438a90c76 优化 2025-02-27 12:53:45 +08:00
yj b185bb7d67 优化 2025-02-27 12:43:57 +08:00
yj 06a2e6e4ac 优化 2025-02-27 10:28:21 +08:00
yj 4b59b1a18d 更新版本号 2025-02-27 09:57:57 +08:00
yj ed2c39e7fc 优化更新 2025-02-27 09:47:17 +08:00
yj 1e3107d0e2 优化 2025-02-26 17:14:28 +08:00
yj 37c06ca19f 优化 2025-02-26 15:56:12 +08:00
yj 7093367904 优化 2025-02-26 14:20:44 +08:00
yj 4121283ef1 优化 2025-02-26 12:01:00 +08:00
yj 705e5f1b01 正式环境不允许刷新 2025-02-26 10:59:20 +08:00
yj 864f305acf 优化 2025-02-26 10:10:36 +08:00
yj 2b4a3cdbca 手动更新功能 2025-02-26 09:59:05 +08:00
yj e01c308800 修改图片大小 2025-02-26 09:12:38 +08:00
yj 62846483fd 优化 2025-02-25 17:44:24 +08:00
yj 8094a2669a 优化 2025-02-25 17:20:04 +08:00
yj 034d46b39e 优化 2025-02-25 17:18:16 +08:00
yj f9dc5830aa 优化 2025-02-25 15:55:56 +08:00
yj 13de6c510d 删除多余代码 2025-02-25 15:40:23 +08:00
yj b4d03efec7 优化 2025-02-25 15:29:28 +08:00
yj 550073a73a 优化 2025-02-25 15:20:38 +08:00
yj 40342246cc 优化结束共享观看逻辑 2025-02-25 15:04:36 +08:00
yj d11f437e2d 设置无网络无法打开应用 2025-02-25 11:29:11 +08:00
yj ac00b4b2a9 修改配置 2025-02-25 10:42:36 +08:00
yj 6d6a9756e8 优化 2025-02-25 09:59:38 +08:00
yj b689fa0f76 优化 2025-02-25 09:36:05 +08:00
yj caba67da14 优化 2025-02-24 17:41:56 +08:00
yj fce3209e59 优化 2025-02-24 16:42:56 +08:00
yj 67682defa8 如果账号已在其他地方登录,弹窗是否强制登录&全员观看-活跃用户 2025-02-24 15:47:57 +08:00
yj 32d238fb7f 优化 2025-02-21 16:42:33 +08:00
yj 4520bba95d 优化 2025-02-21 16:12:58 +08:00
yj 32acac493d 添加监测到远端最活跃用户回调 2025-02-21 15:59:03 +08:00
yj 1a98b6e7d4 优化描述 2025-02-21 14:57:56 +08:00
yj aa76736cf4 电脑端摄像头无法正常使用得情况下,限制开启摄像头 2025-02-21 14:38:57 +08:00
yj 79d393ab14 去除多余代码 2025-02-20 17:13:30 +08:00
yj c79c06aaa3 优化 2025-02-20 17:02:28 +08:00
yj 0fbcb32565 优化&bug修复 2025-02-20 15:44:10 +08:00
yj dc9ee39a1e 修改音频场景 2025-02-19 17:25:45 +08:00
yj f709f3db42 摄像头麦克风优化 2025-02-19 17:19:08 +08:00
yj 2b97e5d93b 输出音量获取本地 2025-02-19 16:30:37 +08:00
yj 4776b15026 聊天中,输入内容后,点击上方默认消息,会清空输入框修复 2025-02-18 17:30:50 +08:00
yj 1ec1c1f12f 优化申请发言弹窗改成独立窗体 2025-02-18 11:13:29 +08:00
yj a80ee1efab 调整屏幕采集帧率 2025-02-18 10:28:45 +08:00
yj 3c2356087d 入会时,记录用户当前使用版本 2025-02-18 10:14:41 +08:00
yj 7363635217 发言人5分钟未推音视频流自动下麦 2025-02-17 10:35:25 +08:00
yj 4cf65d50a3 增加提示 2025-02-14 16:15:34 +08:00
yj 0fb2b4bf75 后台静默版本升级 2025-02-14 16:09:40 +08:00
yj 7f6c0bffb4 优化视频框占比 2025-02-14 14:49:50 +08:00
yj 1b62f6a9ff 更新内容格式错误报错修复 2025-02-14 09:57:03 +08:00
yj 0d50925d0b 反馈建议图片地址添加时间戳 2025-02-14 09:49:37 +08:00
yangqiang 2598ecbcea Merge pull request 'yangjie' (#48) from yangjie into master
Reviewed-on: #48
2025-02-13 17:28:41 +08:00
yj fe1ed45d94 更改版本号 2025-02-13 16:27:10 +08:00
yj 52d8ff14b1 优化 2025-02-13 09:47:58 +08:00
yj 7f9e62027a 断网后再次打开升级弹窗 2025-02-12 16:48:31 +08:00
yj e0ac4b2223 修改提示 2025-02-12 16:20:51 +08:00
yj 05c4cfdd95 优化设置发言人提示 2025-02-12 16:00:14 +08:00
yj 369aaa420c 去除首次升级时间5秒 2025-02-11 17:34:59 +08:00
yj cc69c3d864 优化共享小窗口显示 2025-02-11 11:12:16 +08:00
yj f70074aa6d 共享小窗显示优化&会议箭头大于6人显示&共享偶现无提示优化 2025-02-11 10:35:04 +08:00
yj 0c052a6cd0 优化 2025-02-10 09:17:24 +08:00
yj d45cc15b1f 优化 2025-02-06 13:55:01 +08:00
yangqiang f24150af42 Merge pull request 'yangjie' (#47) from yangjie into master
Reviewed-on: #47
2025-01-24 13:43:09 +08:00
yj fb05838a68 打包参数 2025-01-24 11:18:08 +08:00
yj c0b6b7afd1 去除多余代码 2025-01-24 11:12:11 +08:00
yj 7fd79873f3 菜单自定义 2025-01-24 11:11:43 +08:00
yj c71ea10a4b 优化 2025-01-24 10:34:35 +08:00
yj 3c9b3ac48a 优化 2025-01-24 10:22:32 +08:00
yj 7b4cfbeb1d 优化 2025-01-24 09:07:07 +08:00
yj 25e9f16af0 优化 2025-01-23 14:41:09 +08:00
yj fd809034c1 去除多余代码 2025-01-23 12:38:42 +08:00
yj 26ce3707d7 优化 2025-01-23 12:38:13 +08:00
yj 74760e6382 优化 2025-01-23 12:35:43 +08:00
yj 2e2074fda3 优化 2025-01-23 12:34:26 +08:00
yj 0e37a751ea 优化 2025-01-23 11:45:55 +08:00
yj 70c54d71fd 优化 2025-01-23 11:36:49 +08:00
yj a4ae5577d4 优化 2025-01-23 11:14:18 +08:00
yj 2164fcfde4 优化 2025-01-22 17:58:22 +08:00
yj c8dc0e3276 优化 2025-01-22 17:30:50 +08:00
yj 24a174c7b5 优化 2025-01-22 17:11:07 +08:00
yj e5c4b85dc4 优化 2025-01-22 16:35:45 +08:00
yj 8bd09d6f01 重连 2025-01-22 15:20:50 +08:00
yj 54a5b442cd 右下角通知弹窗关闭 2025-01-22 09:58:00 +08:00
yj 1d2f1072ef 小视频滚动条加按钮 2025-01-22 09:41:57 +08:00
yj c2b36c0b3f 优化 2025-01-21 15:05:54 +08:00
yj dae9b35802 优化模式 2025-01-21 12:25:43 +08:00
yj 9b58afece8 优化 2025-01-21 11:43:30 +08:00
yj 75396341eb 修改视图显示模式 2025-01-21 10:52:33 +08:00
yj 678962d1e9 优化 2025-01-21 10:29:43 +08:00
yj dc619db987 优化 2025-01-20 17:46:09 +08:00
yj 982867dfe1 优化 2025-01-20 15:14:05 +08:00
yj 96959a3e6f 优化 2025-01-20 14:04:39 +08:00
yj 2afcef025b 优化 2025-01-20 10:43:12 +08:00
yj 1887bb39bc 优化 2025-01-20 10:26:31 +08:00
yj bfb85e53a9 优化 2025-01-17 17:55:29 +08:00
yj 88cad54e52 优化 2025-01-17 17:50:59 +08:00
yj ad16a0867b 优化 2025-01-17 16:52:07 +08:00
yj 6d98f0fc0f 去除多余代码 2025-01-17 15:08:43 +08:00
yj c6887b2747 修改共享屏幕token 2025-01-17 15:08:27 +08:00
yj 36e1bda441 更新token 2025-01-16 14:50:10 +08:00
yj f993e740d7 阻止息屏 2025-01-16 14:06:25 +08:00
yj a5e2e8b036 优化 2025-01-16 13:59:23 +08:00
yj 9080065f60 新增每30秒刷新一次房间用户&优化应用不自动进入休眠模式 2025-01-16 11:49:08 +08:00
yj 9f045f7531 优化 2025-01-08 10:53:02 +08:00
yj d10fdd1d5c 更新token 2025-01-08 10:30:58 +08:00
yj 3b79abb8ee 优化 2025-01-06 14:30:54 +08:00
yj c299f6f5af 优化 2025-01-06 14:26:24 +08:00
yj 496a4dd28f 优化 2025-01-06 14:26:15 +08:00
yj 956f045fad 优化 2025-01-06 14:08:07 +08:00
yj 2abf010867 修改样式 2025-01-06 14:03:49 +08:00
yj 20604bb28a 修改意见反馈打开时间 2025-01-06 11:19:20 +08:00
yj 9a6a5904a4 意见反馈 2025-01-06 11:18:53 +08:00
69 changed files with 2280 additions and 770 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": {
"oneClick": false,
"installerIcon": "build/install.ico",
"uninstallerIcon": "build/install.ico",
"installerHeaderIcon": "build/install.ico",
"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
"perMachine": true,
"include": "build/install.nsh"
}
}

View File

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

227
main.js
View File

@ -10,6 +10,8 @@ const {
dialog,
crashReporter,
desktopCapturer,
powerSaveBlocker,
net
} = require('electron');
const path = require('node:path')
const updateJs = require('./src/utils/package/update')
@ -17,17 +19,20 @@ const fs = require('fs');
const Registry = require('winreg');
const { autoUpdater, CancellationToken } = require('electron-updater');
const signalR = require('@microsoft/signalr');
const { setTimeout, setInterval, clearTimeout, clearInterval } = require('timers');
const { setTimeout, setInterval } = require('timers');
const cancellationToken = new CancellationToken()
app.allowRendererProcessReuse = false;
let mainWindow = null;
let childWindow = {}
let isMaximized = false;
let env;
let env = 'development'; //development production xy
let regKey;
let connection = null;
let envStr;
let startNumber = 0;
let buildStatus = false; //true 打包开发版本 false 本地开发
powerSaveBlocker.start('prevent-display-sleep')
const id = powerSaveBlocker.start('prevent-display-sleep')
powerSaveBlocker.stop(id)
class AppWindow extends BrowserWindow {
constructor(config) {
@ -48,12 +53,15 @@ class AppWindow extends BrowserWindow {
const finalConfig = { ...basicConfig, ...config };
super(finalConfig);
if (env === 'development') {
// 开发
this.loadURL('http://localhost:3000');
if (buildStatus) {
this.loadURL('http://192.168.2.9:8827/');
} else {
// 测试 | 生产
this.loadFile(path.resolve(__dirname, './dist/index.html'));
this.loadURL('http://localhost:3000');
}
} else {
this.loadURL('https://meeting-api.23544.com/')
}
// this.loadFile(path.resolve(__dirname, './dist/index.html'))
this.once('ready-to-show', () => {
this.show();
});
@ -62,33 +70,21 @@ class AppWindow extends BrowserWindow {
function quit() {
app.quit()
}
let tray;
// 检查网络状态
function checkNetworkStatus() {
if (!net.isOnline()) {
dialog.showErrorBox(`${env === 'xy' ? '湖北襄阳四中教研平台' : '智汇享'}-网络连接错误', '当前无网络连接,请检查您的网络设置。`);
app.quit();
return false;
}
return true;
}
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 tray = new Tray(trayIcon);
const contextMenu = Menu.buildFromTemplate([
{
label: '打开', click: () => {
mainWindow.webContents.send('isOpenWindows');
},
// icon: iconPath,
},
{
label: '最小化到系统托盘', click: () => {
mainWindow.hide();
},
// icon: iconPath,
},
{
label: '退出', click: async () => {
quit()
},
// icon: iconPath,
},
]);
tray.setToolTip(updateJs.getTitle(envStr));
tray.setContextMenu(contextMenu);
tray = new Tray(trayIcon);
tray.setToolTip(updateJs.getTitle(env));
tray.on('click', () => {
mainWindow.webContents.send('isOpenWindows');
});
@ -97,9 +93,21 @@ function createTray() {
function createWindow() {
mainWindow = new AppWindow();
mainWindow.focus();
mainWindow.hookWindowMessage(278, function (e) {
mainWindow.setEnabled(false);//窗口禁用
setTimeout(() => {
mainWindow.setEnabled(true);//窗口启用
}, 100);
return true;
})
}
const additionalData = { myKey: 'myValue' }
app.on('ready', () => {
// 检查网络状态
if (!checkNetworkStatus()) {
return;
}
// const gotTheLock = true
const gotTheLock = app.requestSingleInstanceLock(additionalData)
if (gotTheLock) {
app.getPath('crashDumps')
@ -107,15 +115,14 @@ app.on('ready', () => {
uploadToServer: false,
ignoreSystemCrashHandler: false
})
env = process.argv.find((arg) => arg.startsWith('--env='))?.split('=')[1];
if (env === 'development') {
Object.defineProperty(app, 'isPackaged', {
get() {
return true
}
})
autoUpdater.updateConfigPath = path.join('latest.yml')
}
// if (!buildStatus) {
// Object.defineProperty(app, 'isPackaged', {
// get() {
// return true
// }
// })
// autoUpdater.updateConfigPath = path.join('latest.yml')
// }
createWindow()
regKey = new Registry({
hive: Registry.HKCU,
@ -136,6 +143,12 @@ app.on('ready', () => {
mainWindow.webContents.openDevTools()
}
});
mainWindow.on('focus', () => {
mainWindow.show()
});
mainWindow.on('maximize', () => {
mainWindow.show()
});
// 监听移动
mainWindow.on('move', () => {
// 如果是全屏自动恢复到上次窗口大小
@ -147,17 +160,20 @@ app.on('ready', () => {
isMaximized = true;
}
});
ipcMain.handle('setEnv', (event, str) => {
envStr = str;
ipcMain.handle('startLoad', (event) => {
if (startNumber === 0) {
updateHandle() // 检查更新
setInterval(() => {
updateHandle() // 每一小时检查更新
autoUpdater.checkForUpdates()
}, 1000 * 60 * 60)
createTray()
startNumber++
}
});
// 更新
ipcMain.handle('updateHandle', () => {
autoUpdater.checkForUpdates()
});
// socket
ipcMain.handle('startSignalr', (event, user) => {
startSignalr(user)
@ -182,6 +198,11 @@ app.on('ready', () => {
connection.off('SetDriver');
connection.off('ShowDriverList');
connection.off('ModifyNickName');
connection.off('JoinChannelCallback');
connection.off('ExitSharedScreen');
connection.off('SetSpeaker');
connection.off('ReceivedOperation');
connection.off('StopedSharedScreen');
}
});
ipcMain.handle('onStop', (event) => {
@ -201,8 +222,7 @@ app.on('ready', () => {
await connection.invoke(str, data.roomNum, data.msg)
break;
case 'sendOper':
// 4:屏幕共享
await connection.invoke(str, data.roomNum, data.type)
await connection.invoke(str, data.roomNum, data.contentString)
break;
case 'getDrivers':
// 获取某个人的设备列表
@ -217,13 +237,21 @@ app.on('ready', () => {
await connection.invoke(str, data.uid, data.driversJsonString)
break;
case 'joinChannel':
// 设置某个人的设备列表
// 加入房间
await connection.invoke(str, data.roomNum, data.enableMicr, data.enableCamera, data.isRoomManager || false)
break;
case 'levelChannel':
// 设置某个人的设备列表
// 退出房间
await connection.invoke(str, data.roomNum)
break;
case 'SetSpeakerCallback':
// 发言人设置成功
await connection.invoke(str, data)
break;
case 'sendOper2User':
// 扩展参数
await connection.invoke(str, data.uid, data.contentString)
break;
}
});
ipcMain.handle('onOtherSignalr', (event) => {
@ -254,10 +282,10 @@ app.on('ready', () => {
})
});
// 扩展操作
connection.on("Operation", (type) => {
connection.on("Operation", (contentString) => {
mainWindow.webContents.send('onSignalr', {
key: 'Operation',
type
contentString
})
});
// 移出会议
@ -379,6 +407,40 @@ app.on('ready', () => {
nickName
})
});
// 加入房间回调
connection.on("JoinChannelCallback", (isSuccess) => {
mainWindow.webContents.send('onSignalr', {
key: 'JoinChannelCallback',
isSuccess,
})
});
// 退出共享
connection.on("ExitSharedScreen", () => {
mainWindow.webContents.send('onSignalr', {
key: 'ExitSharedScreen'
})
});
// 设置发言人
connection.on("SetSpeaker", (RoomManagerInputDTO) => {
mainWindow.webContents.send('onSignalr', {
key: 'SetSpeaker',
RoomManagerInputDTO
})
});
// 扩展参数
connection.on("ReceivedOperation", (contentString) => {
mainWindow.webContents.send('onSignalr', {
key: 'ReceivedOperation',
contentString
})
});
// 共享人取消共享屏幕
connection.on("StopedSharedScreen", (ScreenShareId) => {
mainWindow.webContents.send('onSignalr', {
key: 'StopedSharedScreen',
ScreenShareId
})
});
}
});
// 放大缩小退出窗口
@ -420,6 +482,10 @@ app.on('ready', () => {
ipcMain.handle('getVersion', () => {
return app.getVersion();
});
// 获取环境
ipcMain.handle('getEnv', () => {
return env;
});
// 获取窗口是否显示
ipcMain.handle('isVisible', () => {
return mainWindow.isVisible();
@ -455,6 +521,11 @@ app.on('ready', () => {
downloadUpdate()
} else if (data === '2') { // 下载完成 点击安装
quitAndInstall()
} else if (data === '3') { // 打开弹窗
let message = JSON.stringify({
type: '3',
})
sendUpdateMessage(message)
}
});
// 选择文件夹
@ -489,9 +560,32 @@ app.on('ready', () => {
// 设置桌面应用基础属性
ipcMain.handle('setMainWindowSize', (event, config) => {
if (config.width === 250) {
const contextMenu = Menu.buildFromTemplate([
{
label: '退出',
click: () => quit(),
},
]);
tray.setContextMenu(contextMenu);
mainWindow.setSkipTaskbar(true)
mainWindow.setResizable(false)
mainWindow.setAlwaysOnTop(true, 'screen-saver')
} else {
const contextMenu = Menu.buildFromTemplate([
{
label: '打开',
click: () => mainWindow.webContents.send('isOpenWindows'),
},
{
label: '最小化到系统托盘',
click: () => mainWindow.hide(),
},
{
label: '退出',
click: () => quit(),
},
]);
tray.setContextMenu(contextMenu);
}
// 设置最小窗口尺寸
mainWindow.setMinimumSize(config.width, config.height);
@ -549,13 +643,26 @@ app.on('ready', () => {
width: config.width,
height: config.height,
})
if (envStr === 'development') {
if (env === 'development') {
// 开发
if (buildStatus) {
child.loadURL(`http://192.168.2.9:8827/#/${config.key}`);
// child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`);
} else {
child.loadURL(config.url)
}
} 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.setEnabled(false);//窗口禁用
setTimeout(() => {
child.setEnabled(true);//窗口启用
}, 100);
return true;
})
childWindow[config.key] = child
child.once('ready-to-show', () => {
if (config.show) {
@ -649,7 +756,6 @@ app.on('ready', () => {
});
// 检测更新在你想要检查更新的时候执行renderer事件触发后的操作自行编写
function updateHandle() {
autoUpdater.checkForUpdates()
// autoUpdater.checkForUpdatesAndNotify().catch();
const message = {
error: '检查更新出错',
@ -657,23 +763,26 @@ function updateHandle() {
updateAva: '检测到新版本,正在下载……',
updateNotAva: '已经是最新版本,不用更新'
}
autoUpdater.setFeedURL(updateJs.getUpdateUrl(envStr))
autoUpdater.setFeedURL(updateJs.getUpdateUrl(env))
autoUpdater.autoDownload = false // 不自动下载安装包
autoUpdater.autoInstallOnAppQuit = false // 不自动安装
autoUpdater.on('error', function (error) {
sendUpdateMessage(message.error)
sendUpdateMessage(error)
})
autoUpdater.on('checking-for-update', function () {
sendUpdateMessage(message.checking)
})
autoUpdater.on('update-available', function (info) {
let messageStr = JSON.stringify({ type: '0' })
setTimeout(() => {
sendUpdateMessage(messageStr)
}, 5000)
mainWindow.webContents.send('changeLocalStorage', {
isUpdate: true,
});
})
autoUpdater.on('update-not-available', function (info) {
mainWindow.webContents.send('changeLocalStorage', {
isUpdate: false,
});
})
// 更新下载进度事件
autoUpdater.on('download-progress', function (progressObj) {
@ -746,7 +855,7 @@ function mainWindowCenter() {
const startSignalr = async (user) => {
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,
transport: signalR.HttpTransportType.WebSockets,
accessTokenFactory: () => user.token

7
package-lock.json generated
View File

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

View File

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

View File

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

View File

@ -8,20 +8,21 @@ import Login from '@/page/Login/index'
import Meeting from '@/page/Meeting/index'
import NotFound from '@/page/NotFound/index'
import { storage } from '@/utils'
import { message, Spin } from "antd";
import { message, Modal, Spin } from "antd";
import JoinMeetingModal from "@/components/JoinMeetingModal";
import UpdateModal from "@/components/UpdateModal";
import * as CryptoJS from 'crypto-js';
import { PostLogin } from "@/api/Login";
import { GetCheckOnline, PostLogin } from "@/api/Login";
import { agora } from "@/utils/package/agora";
import QuitTips from "@/components/QuitTips";
import { GetLeave } from "@/api/Meeting";
import ShareScreenWindow from "@/page/Meeting/ShareScreenWindow";
import UserListWindow from "@/page/Meeting/UserListWindow";
import ChatSmallWindow from "@/page/Meeting/ChatSmallWindow";
import ChatBigWindow from "@/page/Meeting/ChatBigWindow";
import NoticeWindow from "@/page/Meeting/NoticeWindow";
import { getKeyOpenChildWindow, getTitle, setKeyOpenChildWindow, storageSeeting } from "./utils/package/public";
import { ExclamationCircleFilled } from "@ant-design/icons";
const { confirm } = Modal;
const fs = require('fs').promises;
const { exec } = require('child_process');
const App: React.FC = () => {
@ -41,9 +42,7 @@ const App: React.FC = () => {
useEffect(() => {
let userInfo = JSON.parse(storage.getItem('user') as string)
let loginInfo = JSON.parse(storage.getItem('login') as string)
window.electron.setEnv(import.meta.env.VITE_ENV);
if (userInfo && !userInfo.isAnonymous) {
if (loginInfo && loginInfo.isAutoLogin) {
const login = () => {
PostLogin({
account: loginInfo.account,
pwd: CryptoJS.MD5(loginInfo.password).toString(CryptoJS.enc.Hex)
@ -57,6 +56,31 @@ const App: React.FC = () => {
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 {
toSrc('/login')
}
@ -79,6 +103,7 @@ const App: React.FC = () => {
};
}, []);
useEffect(() => {
window.electron.startLoad();
window.electron.downFile(async (_e: any, data: any) => {
const response = await fetch(data.filePath);
const arrayBuffer = await response.arrayBuffer();
@ -127,9 +152,7 @@ const App: React.FC = () => {
}, [])
useEffect(() => {
window.electron.onUpdate((_e: any, data: any) => {
if (location.hash.indexOf('/meeting') === -1) {
updateModalRef.current.changeModal(data)
}
})
if (!storage.getItem('setting')) {
storage.setItem('setting', JSON.stringify(storageSeeting))
@ -164,16 +187,24 @@ const App: React.FC = () => {
if (location.href.indexOf('/login') !== -1) {
window.electron.onStop()
}
if (location.hash && location.hash.indexOf('/meeting') === -1) {
window.electron.updateHandle()
}
message.destroy('cameraTemporarily')
}, [navigate])
}
useEffect(() => {
document.addEventListener('keydown', (event) => {
if (event.key === 'F11') {
document.addEventListener('keydown', async (event) => {
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();
}
}
});
document.getElementsByTagName('title')[0].innerText = getTitle(import.meta.env.VITE_ENV)
getTitle()
}, [])
const handleResize = (): void => {
setWindowSize({
@ -200,8 +231,10 @@ const App: React.FC = () => {
if (item.msg) {
message.error(item.msg)
}
await leaveChannel(true)
await leaveChannel()
setTimeout(() => {
toSrc('/login')
}, 5000);
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) {
window.electron.closeChildWindow('shareScreenWindow')
setKeyOpenChildWindow('shareScreenWindow', false)
@ -240,11 +273,9 @@ const App: React.FC = () => {
})
})
const data = JSON.parse(localStorage.stateInfo);
if (!bool) {
await GetLeave({
roomNum: data.channelId,
await window.electron.onInvoke('levelChannel', {
roomNum: data.channelId
})
}
await agora.leaveChannel()
}
};
@ -260,6 +291,8 @@ const App: React.FC = () => {
storage.removeItem('user')
navigate('/login')
}
} else if (e.key === 'env') {
}
};
return (

View File

@ -5,6 +5,12 @@ export const GetRoom = (data: { pageIndex: number, pageSize: number }) =>
method: 'get'
})
export const PostFeedback = (data: any) =>
request({
url: `/home/feedback`,
method: 'post',
data,
})
export const PostRoom = (data: any) =>
request({
url: `/home/room`,

View File

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

View File

@ -71,14 +71,20 @@ export const PostRoomManager = (data: any) =>
request({
url: `/room/manager`,
method: 'post',
data
data: {
...data,
SettingUserId: ''
}
})
export const DeleteRoomManager = (data: any) =>
request({
url: `/room/manager`,
method: 'delete',
data
data: {
...data,
SettingUserId: ''
}
})
export const GetRoomKickout = (roomNum: string, kickUid: string) =>
@ -155,3 +161,24 @@ export const PutAlterUname = (data: any) =>
method: 'put',
data
})
export const GetSharedScreen = (roomNum: string) =>
request({
url: `/room/shared-screen?roomNum=${roomNum}`,
method: 'get'
})
export const PostSharedScreen = (roomNum: string) =>
request({
url: `/room/shared-screen?roomNum=${roomNum}`,
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.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
src/assets/icon56.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 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

@ -1,5 +1,5 @@
import styles from '@/components/Avatar/index.module.scss'
import { useState, useImperativeHandle, forwardRef } from "react";
import { memo, useImperativeHandle, forwardRef } from "react";
const Avatar = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
getData: () => {
@ -15,4 +15,4 @@ const Avatar = forwardRef((props: any, ref: any) => {
)
})
export default Avatar
export default memo(Avatar)

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

@ -1,7 +1,7 @@
import styles from '@/components/EquipmentManagement/index.module.scss'
import { getKeyOpenChildWindow } from '@/utils/package/public';
import { Button, Modal, Select, Slider, message } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react";
import { useState, useImperativeHandle, forwardRef, memo } from "react";
const EquipmentManagement = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeModal: async (uid: string, userName: string) => {
@ -116,4 +116,4 @@ const EquipmentManagement = forwardRef((props: any, ref: any) => {
)
})
export default EquipmentManagement
export default memo(EquipmentManagement)

View File

@ -0,0 +1,45 @@
.feedBackModel {
max-height: 80vh;
display: flex;
flex-direction: column;
.feedBackModelContent {
flex-grow: 1;
overflow-y: auto;
margin: 10px 0;
.feedBackModelContentRate {
margin-bottom: 20px;
background-color: #101215;
padding: 10px 20px 30px;
box-sizing: border-box;
}
.feedBackModelContentList {
margin-bottom: 20px;
>div:nth-child(2) {
>div {
background-color: #101215;
margin-bottom: 10px;
cursor: pointer;
color: #7F859B;
font-size: 14px;
padding: 4px 8px;
box-sizing: border-box;
border: 1px transparent solid;
}
.active {
color: white;
border: 1px #495EAD solid;
}
}
}
}
.feedBackModelFooter {
flex-shrink: 0;
display: flex;
justify-content: flex-end;
}
}

View File

@ -0,0 +1,138 @@
import { PostFeedback } from '@/api/Home/Index';
import styles from '@/components/FeedBackModel/index.module.scss'
import { Button, message, Modal, Rate } from 'antd';
import TextArea from 'antd/es/input/TextArea';
import { useState, useImperativeHandle, forwardRef, memo } from "react";
const FeedBackModel = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeModal: () => {
setIsFeedBackModel(true)
},
}))
const [isFeedBackModel, setIsFeedBackModel] = useState(false);
const [feedBackForm, setFeedBackForm] = useState({
rateValue: 0,
otherContent: '',
});
const [feedBackList, setFeedBackList] = useState([
{
text: "软件卡顿",
value: 2,
active: false,
},
{
text: "设计不合理",
value: 3,
active: false,
},
{
text: "功能太少",
value: 4,
active: false,
},
{
text: "通话不流畅",
value: 5,
active: false,
},
{
text: "视频卡顿",
value: 6,
active: false,
},
{
text: "操作麻烦",
value: 7,
active: false,
},
{
text: "其他,需要手动填写",
value: 1,
active: false,
},
]);
return (
<>
<Modal
title="反馈建议评分"
open={isFeedBackModel}
footer={null}
destroyOnClose={true}
onCancel={() => setIsFeedBackModel(false)}
centered
width={'500px'}
>
<div className={styles.feedBackModel}>
<div className={styles.feedBackModelContent}>
<div className={styles.feedBackModelContentRate}>
<div style={{ color: 'white', fontSize: '14px', marginBottom: '4px' }}>:</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Rate value={feedBackForm.rateValue} allowHalf style={{ transform: 'scale(2)' }} allowClear onChange={(e) => {
setFeedBackForm({
...feedBackForm,
rateValue: e
})
}} />
</div>
</div>
<div className={styles.feedBackModelContentList}>
<div style={{ color: 'white', fontSize: '14px', marginBottom: '4px' }}>:</div>
<div>
{
feedBackList.map((item, index) => {
return (
<div key={index} className={item.active ? styles.active : ''} onClick={() => {
const feedBackListTemp = [...feedBackList]
feedBackListTemp[index].active = !feedBackListTemp[index].active
setFeedBackList(feedBackListTemp)
}}>
<span>{item.text}</span>
</div>
)
})
}
</div>
</div>
<div className={styles.feedBackModelContentList} style={{ visibility: feedBackList[feedBackList.length - 1].active ? 'visible' : 'hidden' }}>
<TextArea
placeholder="填写意见"
value={feedBackForm.otherContent}
autoSize={{ minRows: 3, maxRows: 6 }}
onChange={(e) => {
setFeedBackForm({
...feedBackForm,
otherContent: e.target.value
})
}}
/>
</div>
</div>
<div className={styles.feedBackModelFooter}>
<Button type="primary" className='m-ant-btn'
onClick={() => {
let parmes = {
score: feedBackForm.rateValue,
otherContent: feedBackList[feedBackList.length - 1].active ? feedBackForm.otherContent : '',
types: feedBackList.filter(row => row.active).map((item: any) => item.value),
}
if (feedBackForm.rateValue === 0) {
message.error('请选择评分')
return
}
PostFeedback(parmes).then(res => {
if (res.code === 200) {
message.success('提交成功!')
setIsFeedBackModel(false)
}
})
}}></Button>
<Button type="primary" style={{ backgroundColor: 'rgb(16,20,24)', marginLeft: '20px' }}
onClick={() => setIsFeedBackModel(false)}></Button>
</div>
</div>
</Modal>
</>
)
})
export default memo(FeedBackModel)

View File

@ -1,6 +1,6 @@
import styles from '@/components/InvitingPersonnelModal/index.module.scss'
import { Button, Checkbox, Input, Modal, Pagination, Radio, message } from 'antd';
import { useState, useImperativeHandle, forwardRef, useEffect } from "react";
import { useState, useImperativeHandle, forwardRef, useEffect, memo } from "react";
import { SearchOutlined } from '@ant-design/icons';
import { GetUserList } from '@/api/Home/User';
import { useLocation } from 'react-router-dom';
@ -197,4 +197,4 @@ const InvitingPersonnelModal = forwardRef((props: any, ref: any) => {
})
export default InvitingPersonnelModal
export default memo(InvitingPersonnelModal)

View File

@ -1,7 +1,7 @@
import styles from '@/components/JoinMeetingModal/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl';
import { Modal, message } from 'antd';
import { useState, useImperativeHandle, forwardRef, useRef } from "react";
import { useState, useImperativeHandle, forwardRef, useRef, memo } from "react";
import Avatar from '@/components/Avatar';
import JoinSetting from '../JoinSetting';
const JoinMeetingModal = forwardRef((props: any, ref: any) => {
@ -55,4 +55,4 @@ const JoinMeetingModal = forwardRef((props: any, ref: any) => {
)
})
export default JoinMeetingModal
export default memo(JoinMeetingModal)

View File

@ -3,12 +3,13 @@ import { storage } from '@/utils';
import ImageUrl from '@/utils/package/imageUrl';
import { GetCheckoutRoomNum, GetRoomRtcToken, GetRoomInfo } from '@/api/Home/Index';
import { Button, Modal, message } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react";
import { useState, useImperativeHandle, forwardRef, memo } from "react";
import { PostRefresh } from '@/api/Login';
import Avatar from '@/components/Avatar';
import { useNavigate } from 'react-router-dom';
import { agora } from '@/utils/package/agora';
import { role } from '@/config/role';
import { PostHomeVerLog } from '@/api/Meeting';
const { setInterval, clearInterval } = require('timers');
let time = null as any;
const JoinSetting = forwardRef((_props: any, ref: any) => {
@ -170,7 +171,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
setJoinRoomSettingForm(list)
if (index === 1) {
if (list[index].active) {
agora.startPreview('videoPreview', Number(user.screenShareId))
agora.startPreview('videoPreview', Number(user.screenShareId), +new Date())
}
}
} else {
@ -201,6 +202,13 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
GetRoomInfo(roomNumber).then(async (res) => {
if (res.code === 200) {
await agora.release()
await window.electron.getVersion().then(async req => {
await PostHomeVerLog({
version: req,
platformType: 1,
roomNum: roomNumber,
})
})
navigate(`/meeting`, {
state: {
channelId: roomNumber,
@ -236,4 +244,4 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
)
})
export default JoinSetting
export default memo(JoinSetting)

View File

@ -1,6 +1,6 @@
import styles from '@/components/MeetingDisconnected/index.module.scss'
import { InfoCircleOutlined } from '@ant-design/icons';
import { useState, useImperativeHandle, forwardRef } from "react";
import { useState, useImperativeHandle, forwardRef, memo } from "react";
const MeetingDisconnected = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeModal: (bool: boolean) => {
@ -17,4 +17,4 @@ const MeetingDisconnected = forwardRef((props: any, ref: any) => {
)
})
export default MeetingDisconnected
export default memo(MeetingDisconnected)

View File

@ -1,6 +1,6 @@
import styles from '@/components/Operation/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl';
import { useEffect, useState } from "react";
import { useEffect, useState, memo } from "react";
type OperationKeyType = 'minimize' | 'quit' | 'maximize' | 'unmaximize' | 'hide' | 'show';
type OperationType = {
icon: string;
@ -100,4 +100,4 @@ const Operation: React.FC = () => {
</>
)
}
export default Operation
export default memo(Operation)

View File

@ -2,7 +2,7 @@ import styles from '@/components/QuitTips/index.module.scss'
import { storage } from '@/utils';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Button, Checkbox, Modal, Radio } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react";
import { useState, useImperativeHandle, forwardRef, memo } from "react";
type OperationKeyType = 'minimize' | 'quit' | 'maximize' | 'unmaximize' | 'hide' | 'show';
const QuitTips = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
@ -66,4 +66,4 @@ const QuitTips = forwardRef((props: any, ref: any) => {
)
})
export default QuitTips
export default memo(QuitTips)

View File

@ -8,7 +8,7 @@ import {
VerticalAlignBottomOutlined
} from '@ant-design/icons';
import { Button, Input, message, Modal, Pagination, Popconfirm, Progress, Table } from 'antd';
import { forwardRef, useEffect, useImperativeHandle, useState, useRef } from "react";
import { forwardRef, useEffect, useImperativeHandle, useState, useRef, memo } from "react";
import { DeleteRoomFile, GetRoomFile, GetRoomFileDwUrl, GetRoomUpFileurl, GetRoomUserItem, PostRoomFile } from '@/api/Meeting';
import axios from 'axios';
import { useLocation } from 'react-router-dom';
@ -393,4 +393,4 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
)
})
export default SharedFilesModel
export default memo(SharedFilesModel)

View File

@ -2,7 +2,7 @@ import { GetRoomSingnIn, PostRoomSingnIn } from '@/api/Meeting';
import styles from '@/components/SingIn/index.module.scss'
import { storage } from '@/utils';
import { Button, message, Modal } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react";
import { useState, useImperativeHandle, forwardRef, memo } from "react";
const SingIn = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeModal: () => {
@ -85,4 +85,4 @@ const SingIn = forwardRef((props: any, ref: any) => {
)
})
export default SingIn
export default memo(SingIn)

View File

@ -1,6 +1,6 @@
import styles from '@/components/SpeakerModeModal/index.module.scss'
import { Checkbox, Modal } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react";
import { useState, useImperativeHandle, forwardRef, memo } from "react";
import { storage } from '@/utils';
import { useLocation } from 'react-router-dom';
import { GetSyncView } from '@/api/Meeting';
@ -147,4 +147,4 @@ const FourScreenMode: React.FC<Props> = ({ onClick, meetingMode }) => {
)
}
export default SpeakerModeModal
export default memo(SpeakerModeModal)

View File

@ -1,7 +1,7 @@
import styles from '@/components/StupWizard/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl';
import { Button, Checkbox, Empty, Input, message, Modal, Popover, Radio, Select, Slider, Space } from 'antd';
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { forwardRef, useEffect, useImperativeHandle, useState, memo } from "react";
import { agora } from '@/utils/package/agora'
import { CloseOutlined, LoadingOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { storage } from '@/utils';
@ -11,6 +11,7 @@ import { storageSeeting } from '@/utils/package/public';
let meetingUserInfo = '' as any;
const fs = require('fs').promises;
const { exec } = require('child_process');
let c = +new Date();
const StupWizard = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeModal: (index: number = 0, data: any) => {
@ -33,6 +34,9 @@ const StupWizard = forwardRef((_props: any, ref: any) => {
}
}
storage.setItem('setting', JSON.stringify(setting))
},
getStupWizardModal: () => {
return isStupWizard
}
}))
const [list, setList] = useState([
@ -84,6 +88,8 @@ const StupWizard = forwardRef((_props: any, ref: any) => {
{list.map((row: any, index: number) => {
return (
<div key={index} className={`${row.active ? styles.active : ''}`} onClick={async () => {
const userInfo = JSON.parse(storage.getItem('user') as string)
await agora.destroyRendererByConfigPreview(Number(userInfo.screenShareId), c)
const newList = [...list];
newList.forEach(item => item.active = false);
newList[index].active = true;
@ -106,9 +112,11 @@ const StupWizard = forwardRef((_props: any, ref: any) => {
cursor: 'pointer'
}}
onClick={async () => {
const userInfo = JSON.parse(storage.getItem('user') as string)
if (location.hash.indexOf('/meeting') === -1) {
agora.release()
}
await agora.destroyRendererByConfigPreview(Number(userInfo.screenShareId), c)
setIsStupWizard(false)
}}
/>
@ -125,9 +133,11 @@ const StupWizard = forwardRef((_props: any, ref: any) => {
})
const CurrencyComponents = () => {
const [optionsValue, setOperationValue] = useState<'hide' | 'quit'>('hide');
const [voiceStimulation, setVoiceStimulation] = useState(true);
const setting = JSON.parse(storage.getItem('setting') as string)
useEffect(() => {
setOperationValue(setting.closeSetting)
setVoiceStimulation(setting.voiceStimulation)
}, []);
return (
<>
@ -146,6 +156,33 @@ const CurrencyComponents = () => {
<Radio value={'hide'}>退</Radio>
</Radio.Group>
</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>
@ -208,7 +245,8 @@ const VideoComponents = () => {
}, [darkLightEnhancement]);
useEffect(() => {
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) => {
const imagePath = path.join(res, 'src', 'assets', 'virtualBackground', `${virtualBackground.sourceIndex + 1}.png`);
agora.enableVirtualBackground(virtualBackground.isVirtualBackground, {
@ -225,6 +263,7 @@ const VideoComponents = () => {
color: Number(virtualBackground.color),
})
}
})
} else {
agora.enableVirtualBackground(virtualBackground.isVirtualBackground, {
background_source_type: 1,
@ -247,7 +286,7 @@ const VideoComponents = () => {
})
if (setting.videoDeviceId && list.length) {
await agora.setVideoDeviceManager(setting.videoDeviceId)
await agora.startPreview('videoPreview', Number(userInfo.screenShareId))
await agora.startPreview('videoPreview', Number(userInfo.screenShareId), c)
}
})
}
@ -305,7 +344,7 @@ const VideoComponents = () => {
agora.setVideoDeviceManager(e)
if (!setting.videoDeviceId) {
const userInfo = JSON.parse(storage.getItem('user') as string)
await agora.startPreview('videoPreview', Number(userInfo.screenShareId))
await agora.startPreview('videoPreview', Number(userInfo.screenShareId), c)
}
setting.videoDeviceId = e;
storage.setItem('setting', JSON.stringify(setting))
@ -691,7 +730,18 @@ const AudioComponents = () => {
ecordingVolume: e,
})
}} disabled={!audioDeviceManager.ecordingItem} />
{/* || audioDeviceManager.autoEcordingVolume */}
</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>
<Checkbox checked={audioDeviceManager.isAINoiseReduction} onChange={(e) => {
@ -724,16 +774,6 @@ const AudioComponents = () => {
</Radio.Group>
</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 className={styles.audioComponentsSelect}>
@ -964,4 +1004,4 @@ const FileComponents = () => {
)
}
export default StupWizard
export default memo(StupWizard)

View File

@ -1,4 +1,4 @@
import { useEffect } from "react";
import { useEffect, memo } from "react";
import '@/components/TldrawView/index.scss'
import {
Tldraw,
@ -44,4 +44,4 @@ const TldrawView: React.FC = () => {
</>
)
}
export default TldrawView
export default memo(TldrawView)

View File

@ -1,45 +1,106 @@
import styles from '@/components/UpdateModal/index.module.scss'
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 { forwardRef, useImperativeHandle, useState } 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, () => ({
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)
getContent()
if (dataJson.type === '0') { // 打开弹窗
setIsUpdateModal(true)
setProgress(res => {
if (res) {
} else {
window.electron.onDownload('1')
}
return res
})
} else if (dataJson.type === '1') { // 下载中 返回进度值
if (dataJson.value === 100 && location.hash.indexOf('/meeting') === -1) {
setIsUpdateModal(true)
getContent()
}
setProgress(dataJson.value.toFixed(2))
} else if (dataJson.type === '2') { // 下载完成
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 [progress, setProgress] = useState(0); // 下载进度值
const [updateContent, setUpdateContent] = useState('') // 版本更新内容
const [env, setEnv] = useState('')
const [_isError, setIsError] = useState(true)
useEffect(() => {
window.electron.getEnv().then(res => {
setEnv(res)
})
}, [])
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 => {
if (response.status === 200) {
return setUpdateContent(await response.text())
}
throw new Error('Network response was not ok.');
})
.then(textContent => {
.then(_textContent => {
})
.catch(error => {
.catch(_error => {
});
})
}
function closeModal() {
if (progress != 100) {
window.electron.onDownload('0') // 取消下载
}
// if (progress != 100) {
// window.electron.onDownload('0') // 取消下载
// }
setIsUpdateModal(false)
}
@ -49,12 +110,12 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
title=""
open={isUpdateModal}
footer={null}
// onCancel={() => closeModal()}
onCancel={() => closeModal()}
centered
width={'400px'}
className='modal-padding'
maskClosable={false}
closeIcon={false}
closeIcon={progress === 100 ? false : true}
>
<div className={styles.isUpdateModal} style={{ backgroundImage: `url(${ImageUrl.icon7})` }}>
<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' }}
className={`m-ant-btn`}
></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 style={{ margin: '20px 0' }}>
{progress}%
@ -87,4 +148,4 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
)
})
export default UpdateModal
export default memo(UpdateModal)

View File

@ -2,7 +2,7 @@ import { PutAlterUname } from '@/api/Meeting';
import styles from '@/components/UserName/index.module.scss'
import { storage } from '@/utils';
import { Button, Input, message, Modal } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react";
import { useState, useImperativeHandle, forwardRef, memo } from "react";
const UserName = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeModal: (data: any) => {
@ -47,6 +47,10 @@ const UserName = forwardRef((props: any, ref: any) => {
onClick={async () => {
const stateInfo = await JSON.parse(storage.getItem('stateInfo') as string);
if (info.userName) {
if (info.userName.length > 10) {
message.error('用户名称最多10个字')
return
}
PutAlterUname({
nickName: info.userName,
roomNum: stateInfo.channelId,
@ -71,4 +75,4 @@ const UserName = forwardRef((props: any, ref: any) => {
)
})
export default UserName
export default memo(UserName)

View File

@ -3,7 +3,7 @@ import styles from '@/components/UserVideo/index.module.scss'
import { GetPolling } from '@/api/Meeting';
import { agora } from '@/utils/package/agora';
import { Button, Empty, Select, message } from 'antd';
import { useEffect, useState } from "react";
import { useEffect, useState, memo } from "react";
import { useLocation } from 'react-router';
import { VideoStreamType } from 'agora-electron-sdk';
const { setInterval, clearInterval } = require('timers');
@ -154,4 +154,4 @@ const UserVideo: React.FC = () => {
)
}
export default UserVideo
export default memo(UserVideo)

View File

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

View File

@ -2,17 +2,22 @@ import styles from '@/page/Home/Index/index.module.scss'
import { useEffect, useState, useRef } from "react";
import Operation from '@/components/Operation';
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 { ExclamationCircleFilled, ReloadOutlined } from '@ant-design/icons';
import JoinSetting from '@/components/JoinSetting';
import { storage } from '@/utils';
import { PostRefresh } from '@/api/Login';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { role } from '@/config/role';
import dayjs from 'dayjs';
import StupWizard from '@/components/StupWizard';
import { GetSubDpList } from '@/api/Home/User';
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 fs = require('fs').promises;
const { exec } = require('child_process');
@ -20,6 +25,7 @@ const { RangePicker } = DatePicker;
const { confirm } = Modal;
const Index: React.FC = () => {
const navigate = useNavigate();
const { state } = useLocation();
const [list, setList] = useState({
data: [],
total: 0,
@ -37,16 +43,19 @@ const Index: React.FC = () => {
})
const joinSettingRef = useRef<any>();
const stupWizardRef = useRef<any>();
const feedBackModelRef = useRef<any>();
const [user, setUser] = useState<any>({});
const [currentRoomInfo, setCurrentRoomInfo] = useState<any>({});
const [subjectList, setSubjectList] = useState<any>([]);
const [timeData, setTimeData] = useState<any>([]);
const [isCreateRoom, setIsCreateRoom] = useState<boolean>(false);
const [allowAnonymous, setAllowAnonymous] = useState(true);
const [baseImage, setBaseImage] = useState('');
const userInfo = JSON.parse(storage.getItem('user') as string)
useEffect(() => {
setUser(userInfo)
if (state?.currentSeconds >= 600) {
feedBackModelRef.current.changeModal()
}
}, [])
useEffect(() => {
let time = null as any
@ -239,35 +248,7 @@ const Index: React.FC = () => {
<span>{item.roomNum}</span>
<img src={ImageUrl.icon10} alt="" />
</div>
<Popover
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>
<Code roomNum={item.roomNum}></Code>
</div>
<div>
{role.ID.includes(userInfo.roleId) ? <Popover
@ -329,13 +310,26 @@ const Index: React.FC = () => {
<Button type="primary"
iconPosition={'end'}
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)) {
joinSettingRef.current.changeModal(item.roomNum)
} else {
storage.setItem('loading', true)
postRefresh(() => {
getRoomRtcToken(item.roomNum, (options: any) => {
getRoomRtcToken(item.roomNum, async (options: any) => {
if (options) {
await window.electron.getVersion().then(async req => {
await PostHomeVerLog({
version: req,
platformType: 1,
roomNum: item.roomNum,
})
})
navigate(`/meeting`, {
state: {
channelId: item.roomNum,
@ -351,6 +345,8 @@ const Index: React.FC = () => {
})
})
}
}
})
}}
icon={<img src={ImageUrl.icon9} alt="" />}
className='m-ant-btn'>
@ -551,6 +547,7 @@ const Index: React.FC = () => {
</Modal>
<JoinSetting ref={joinSettingRef} />
<StupWizard ref={stupWizardRef} />
<FeedBackModel ref={feedBackModelRef} />
</>
)
}

View File

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

View File

@ -120,8 +120,26 @@
@else if $i ==4 {
flex-shrink: 0;
>div:nth-child(1) {
color: #ccc;
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 {
@ -130,7 +148,7 @@
padding-top: 10px;
margin-top: 10px;
@for $i from 1 through 2 {
@for $i from 1 through 3 {
>div:nth-child(#{$i}) {
display: flex;
align-items: center;
@ -144,13 +162,16 @@
height: 16px;
@if $i ==1 {
background: url('/src/assets/icon16.png') no-repeat center/cover;
background: url('/src/assets/icon56.png') no-repeat center/120%;
}
@else if $i ==2 {
background: url('/src/assets/icon15.png') no-repeat center/cover;
background: url('/src/assets/icon16.png') no-repeat center/cover;
}
@else if $i ==3 {
background: url('/src/assets/icon15.png') no-repeat center/cover;
}
}
>span {
@ -162,10 +183,14 @@
&:hover {
>div {
@if $i ==1 {
background: url('/src/assets/icon16-active.png') no-repeat center/cover;
background: url('/src/assets/icon56-active.png') no-repeat center/120%;
}
@else if $i ==2 {
background: url('/src/assets/icon16-active.png') no-repeat center/cover;
}
@else if $i ==3 {
background: url('/src/assets/icon15-active.png') no-repeat center/cover;
}
}

View File

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

View File

@ -4,11 +4,14 @@ import { useEffect, useState } from "react";
import { useNavigate } from 'react-router-dom';
import { Input, Button, Checkbox, message, Modal } from "antd"
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 ImageUrl from '@/utils/package/imageUrl'
import { v4 as uuidv4 } from 'uuid';
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 navigate = useNavigate();
const [accountPasswordStatus, setAccountPasswordStatus] = useState<boolean>(false);
@ -37,13 +40,16 @@ const Login: React.FC = () => {
roomNum: '',
})
const [nameModal, setNameModal] = useState(false)
const [env, setEnv] = useState('')
useEffect(() => {
window.electron.setMainWindowSize({
width: 752,
height: 520,
key: 'login'
})
window.electron.getEnv().then(res => {
setEnv(res)
})
if (storage.getItem('login')) {
const login = JSON.parse(storage.getItem('login') as string);
const data = {
@ -100,7 +106,32 @@ const Login: React.FC = () => {
}
GetCheckUser(operation.account).then(res => {
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.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 className={styles.loginContent}>
<div>
@ -315,6 +346,15 @@ const Login: React.FC = () => {
if (!anonInfo.nickName) {
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)
PostAnonLogin(anonInfo).then(async (res) => {
if (res.code == 200) {
@ -357,6 +397,8 @@ const Login: React.FC = () => {
}).catch(() => {
storage.setItem('loading', false)
})
}
})
}}></Button>
</div>
</div>

View File

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

View File

@ -206,19 +206,30 @@ const ChatBigWindow: React.FC = () => {
></Button> : null}
</div> : <div style={{ color: 'white' }}></div>
}>
<div>
<div title={item.userName}>
<div><Avatar name={item.userName} /></div>
{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 style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')} </span>{item.userName}</span>
<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>
</Popover> : <div>
</Popover> : <div title={item.userName}>
<div><Avatar name={item.userName} /></div>
{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 style={{ fontSize: '12px', color: '#ccc', marginRight: '4px' }}>{dayjs(item.timestamp).format('HH:mm:ss')} </span>{item.userName}</span>
<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>{item.message}</div>

View File

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

View File

@ -21,7 +21,10 @@ const NoticeWindow: React.FC = () => {
api.open({
message: '',
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'>
<Button
type="primary"
@ -68,6 +71,7 @@ const NoticeWindow: React.FC = () => {
</div>
</div>,
onClose: () => {
setTimeout(() => {
const dom = document.getElementsByClassName('ant-notification')
if (dom.length === 0) {
window.electron.setChildWindowShow({
@ -75,6 +79,7 @@ const NoticeWindow: React.FC = () => {
bool: false
})
}
}, 500);
},
duration: 10,
placement: 'bottomRight',

View File

@ -4,6 +4,7 @@ import styles from '@/page/Meeting/ShareScreenWindow/index.module.scss'
import { storage } from '@/utils';
import ImageUrl from '@/utils/package/imageUrl';
import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons';
import { RtcStats } from 'agora-electron-sdk';
import { Button } from 'antd';
import dayjs from 'dayjs';
import { useEffect, useState } from "react";
@ -50,6 +51,12 @@ const ShareScreenWindow: React.FC = () => {
const [timeStr, setTimeStr] = useState(0)
const [isExpand, setIsExpand] = useState(false)
const [roomUserLists, setRoomUserLists] = useState<any>([])
const [currentEffective, setCurrentEffective] = useState(3)
const [networkOther, setNetworkOther] = useState<RtcStats>({})
const [networkQuality, setNetworkQuality] = useState({
level: '佳',
text: '网络质量极好'
})
const channel = new BroadcastChannel('meeting_channel');
const userInfo = JSON.parse(storage.getItem('user') as string)
let timeout: NodeJS.Timeout;
@ -97,6 +104,11 @@ const ShareScreenWindow: React.FC = () => {
case 'roomUserList':
setRoomUserLists(data.parmes.roomUserList)
break;
case 'nnetworkStatus':
setCurrentEffective(data.parmes.currentEffective)
setNetworkQuality(data.parmes.networkQuality)
setNetworkOther(data.parmes.networkOther)
break;
}
})
return () => {
@ -141,13 +153,19 @@ const ShareScreenWindow: React.FC = () => {
<>
<div className={styles.shareScreenWindow} style={{ width: isExpand ? '100%' : '100%' }}>
<div className={styles.shareScreenWindowTitle}>
<span>{changeCurrentSeconds(timeStr)} </span>
{isExpand ? <span className='drag' onClick={() => {
<span>{changeCurrentSeconds(timeStr)} {!isExpand ? '共享中' : ''}
{networkIcon(currentEffective)}
<span style={{ color: 'white', marginLeft: '30px' }}>
{!isExpand ? <span style={{ marginRight: '10px' }}>{networkQuality.level}</span> : ''}
<span>{networkOther.lastmileDelay}ms</span>
</span>
</span>
{isExpand ? <span className='drag' style={{ flexShrink: 0 }} onClick={() => {
channel.postMessage({
type: 'shareScreenWindowClose',
shareScreenWindowClose: timeStr
});
}}></span> : <span style={{ visibility: 'hidden' }}></span>}
}}></span> : <span style={{ visibility: 'hidden', flexShrink: 0 }}></span>}
</div>
{isExpand ? null : <div className={`${styles.shareScreenWindowContent} drag`}>
<div className={styles.shareScreenWindowContentList}>
@ -184,7 +202,7 @@ const ShareScreenWindow: React.FC = () => {
}}
key={index}>
<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> : ''}
{item.select ? <img src={item.iconSelect} alt="" /> : <img src={item.active ? item.iconActive : item.icon} alt="" />}
</div>
@ -205,7 +223,7 @@ const ShareScreenWindow: React.FC = () => {
<div className={`${styles.shareScreenWindowExpand} drag`} onClick={() => {
setIsExpand(!isExpand)
window.electron.setChildWindow({
width: isExpand ? 440 : 440 / 2,
width: isExpand ? 440 : 440 / 1.6,
key: 'shareScreenWindow',
})
}}>
@ -217,4 +235,60 @@ const ShareScreenWindow: React.FC = () => {
)
}
const networkIcon = (network: number) => {
switch (network) {
case 0:
return <svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg" className='drag' style={{ transform: 'scale(0.5)', position: 'absolute', top: '-4px' }}>
<g clip-path="url(#clip0_21262_3609)">
<path d="M4.44094 32C3.03695 32 1.90039 31.15 1.90039 30.1V15.3C1.90039 14.25 3.03695 13.4 4.44094 13.4C5.84492 13.4 6.98149 14.25 6.98149 15.3V30.1C6.98149 31.15 5.84492 32 4.44094 32ZM17.01 32C15.606 32 14.4694 31.15 14.4694 30.1V8.9C14.4694 7.85 15.606 7 17.01 7C18.4139 7 19.5505 7.85 19.5505 8.9V30.1C19.5505 31.15 18.4139 32 17.01 32ZM29.579 32C28.175 32 27.0384 31.15 27.0384 30.1V1.9C27.0384 0.85 28.175 0 29.579 0C30.983 0 32.1195 0.85 32.1195 1.9V30.1C32.1195 31.15 30.983 32 29.579 32Z" fill="#7C8280" />
<path d="M7.00124 2.11509L5.01758 4.09875L3.03391 2.11509C2.77629 1.85747 2.35122 1.85747 2.0936 2.11509C1.83599 2.37271 1.83599 2.79778 2.0936 3.0554L4.07727 5.03906L2.0936 7.02273C1.83599 7.28035 1.83599 7.70542 2.0936 7.96304C2.35122 8.22065 2.77629 8.22065 3.03391 7.96304L5.01758 5.97937L7.00124 7.96304C7.25886 8.22065 7.68393 8.22065 7.94155 7.96304C8.19917 7.70542 8.19917 7.28035 7.94155 7.02273L5.95789 5.03906L7.94155 3.0554C8.19917 2.79778 8.19917 2.37271 7.94155 2.11509C7.68393 1.85747 7.25886 1.85747 7.00124 2.11509Z" fill="#F90000" />
</g>
<defs>
<clipPath id="clip0_21262_3609">
<rect width="32" height="32" fill="white" transform="translate(0.119141)" />
</clipPath>
</defs>
</svg>
case 1:
return <svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg" className='drag' style={{ transform: 'scale(0.5)', position: 'absolute', top: '-4px' }}>
<g clip-path="url(#clip0_21262_3615)">
<path d="M26.9316 30.1C26.9316 31.15 28.0918 32 29.5249 32C30.9581 32 32.1182 31.15 32.1182 30.1V1.9C32.1182 0.85 30.9581 0 29.5249 0C28.0918 0 26.9316 0.85 26.9316 1.9V30.1Z" fill="#7C8280" />
<path d="M14.1035 30.1C14.1035 31.15 15.2637 32 16.6968 32C18.1299 32 19.2901 31.15 19.2901 30.1V8.9C19.2901 7.85 18.1299 7 16.6968 7C15.2637 7 14.1035 7.85 14.1035 8.9V30.1Z" fill="#7C8280" />
<path d="M1.27344 30.1004C1.27344 31.1504 2.4336 32.0004 3.86673 32.0004C5.29986 32.0004 6.46002 31.1504 6.46002 30.1004V15.3004C6.46002 14.2504 5.29986 13.4004 3.86673 13.4004C2.4336 13.4004 1.27344 14.2504 1.27344 15.3004V30.1004Z" fill="#FF800B" />
</g>
<defs>
<clipPath id="clip0_21262_3615">
<rect width="32" height="32" fill="white" transform="translate(0.119141)" />
</clipPath>
</defs>
</svg>
case 2:
return <svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg" className='drag' style={{ transform: 'scale(0.5)', position: 'absolute', top: '-4px' }}>
<g clip-path="url(#clip0_21262_3621)">
<path d="M26.9316 30.1C26.9316 31.15 28.0918 32 29.5249 32C30.9581 32 32.1182 31.15 32.1182 30.1V1.9C32.1182 0.85 30.9581 0 29.5249 0C28.0918 0 26.9316 0.85 26.9316 1.9V30.1Z" fill="#7C8280" />
<path d="M14.1035 30.1C14.1035 31.15 15.2637 32 16.6968 32C18.1299 32 19.2901 31.15 19.2901 30.1V8.9C19.2901 7.85 18.1299 7 16.6968 7C15.2637 7 14.1035 7.85 14.1035 8.9V30.1Z" fill="#FF800B" />
<path d="M1.27344 30.1004C1.27344 31.1504 2.4336 32.0004 3.86673 32.0004C5.29986 32.0004 6.46002 31.1504 6.46002 30.1004V15.3004C6.46002 14.2504 5.29986 13.4004 3.86673 13.4004C2.4336 13.4004 1.27344 14.2504 1.27344 15.3004V30.1004Z" fill="#FF800B" />
</g>
<defs>
<clipPath id="clip0_21262_3621">
<rect width="32" height="32" fill="white" transform="translate(0.119141)" />
</clipPath>
</defs>
</svg>
case 3:
return <svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg" className='drag' style={{ transform: 'scale(0.5)', position: 'absolute', top: '-4px' }}>
<g clip-path="url(#clip0_21262_3625)">
<path d="M26.9316 30.1C26.9316 31.15 28.0918 32 29.5249 32C30.9581 32 32.1182 31.15 32.1182 30.1V1.9C32.1182 0.85 30.9581 0 29.5249 0C28.0918 0 26.9316 0.85 26.9316 1.9V30.1Z" fill="#02B188" />
<path d="M14.1035 30.1C14.1035 31.15 15.2637 32 16.6968 32C18.1299 32 19.2901 31.15 19.2901 30.1V8.9C19.2901 7.85 18.1299 7 16.6968 7C15.2637 7 14.1035 7.85 14.1035 8.9V30.1Z" fill="#02B188" />
<path d="M1.27344 30.1004C1.27344 31.1504 2.4336 32.0004 3.86673 32.0004C5.29986 32.0004 6.46002 31.1504 6.46002 30.1004V15.3004C6.46002 14.2504 5.29986 13.4004 3.86673 13.4004C2.4336 13.4004 1.27344 14.2504 1.27344 15.3004V30.1004Z" fill="#02B188" />
</g>
<defs>
<clipPath id="clip0_21262_3625">
<rect width="32" height="32" fill="white" transform="translate(0.119141)" />
</clipPath>
</defs>
</svg>
}
}
export default ShareScreenWindow

View File

@ -232,6 +232,11 @@
position: relative;
overflow: hidden;
#videoView {
position: relative;
border: 1px red solid;
}
.standardModeIcon {
position: absolute;
left: 50%;
@ -298,13 +303,17 @@
// 演讲者模式
.meetingContentBodyLeftSpeakerMode {
width: 18%;
width: 270px;
overflow-y: auto;
height: 100%;
.meetingContentSwiperCard {
width: 100%;
}
.meetingContentSwiperCard {
height: 160px;
}
}
// 单画面模式
@ -370,7 +379,7 @@
right: 0;
bottom: 0;
height: 100% !important;
width: calc(100% - 18%) !important;
width: calc(100% - 270px) !important;
z-index: 2;
}
@ -381,7 +390,7 @@
.meetingContentSwiperCard {
height: 160px;
width: calc(100% / 6);
width: 270px;
border-radius: 10px;
overflow: hidden;
position: relative;
@ -427,6 +436,22 @@
}
}
.meetingContentSwiperCaret {
position: absolute;
z-index: 2;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.5);
color: white;
border: 1px white solid;
font-size: 20px;
width: 25px;
height: 25px;
display: flex;
justify-content: center;
align-items: center;
}
.meetingContentBodyLeftBlock {
position: absolute;
background-color: #1F2022;
@ -524,6 +549,8 @@
font-size: 14px;
color: #F3F3F5;
margin-left: 4px;
display: flex;
align-items: center;
}
>div {
@ -635,6 +662,8 @@
font-size: 14px;
color: #F3F3F5;
margin-left: 4px;
display: flex;
align-items: center;
}
}
@ -664,6 +693,8 @@
>span {
font-size: 14px;
color: #F3F3F5;
display: flex;
align-items: center;
}
>div {
@ -773,7 +804,7 @@
}
>img {
height: 50px;
height: 35px;
}
>span {
@ -795,8 +826,8 @@
}
>label {
height: 50px;
height: 50px;
height: 35px;
height: 35px;
cursor: pointer;
position: relative;

File diff suppressed because it is too large Load Diff

4
src/render.d.ts vendored
View File

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

View File

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

View File

@ -9,7 +9,6 @@ import {
AudioAinsMode,
SimulcastStreamMode,
VideoStreamType,
QualityType,
RtcConnection,
RtcStats,
AudioVolumeInfo,
@ -22,7 +21,8 @@ import {
ColorEnhanceOptions,
LowlightEnhanceOptions,
VirtualBackgroundSource,
AudienceLatencyLevelType
AudienceLatencyLevelType,
StreamPublishState
} from "agora-electron-sdk";
import { GetRoomRtcToken, GetAgoraConf } from "@/api/Home/Index";
import { storage } from '@/utils';
@ -32,7 +32,6 @@ const os = require("os");
const option: any = {
appId: '',
token: '',
tokenA: '',
channelId: '',
uid: '',
screenShareId: '',
@ -126,7 +125,8 @@ export const agora = {
if (settingData.darkLightEnhancement) agora.setLowlightEnhanceOptions(settingData.darkLightEnhancement.isDarkLightEnhancement, settingData.darkLightEnhancement)
if (settingData.virtualBackground) {
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) => {
const imagePath = path.join(res, 'src', 'assets', 'virtualBackground', `${settingData.virtualBackground.sourceIndex + 1}.png`);
agora.enableVirtualBackground(settingData.virtualBackground.isVirtualBackground, {
@ -143,6 +143,7 @@ export const agora = {
color: Number(settingData.virtualBackground.color),
})
}
})
} else {
agora.enableVirtualBackground(settingData.virtualBackground.isVirtualBackground, {
background_source_type: 1,
@ -153,7 +154,7 @@ export const agora = {
}, 1000);
},
// 事件回调
registerEventHandler: ({ onJoinChannelSuccess, onUserJoined, onUserOffline, onAudioVolumeIndication, onNetworkQuality, onRtcStats, onConnectionStateChanged, onLocalVideoStateChanged, onConnectionLost }: any) => {
registerEventHandler: ({ onJoinChannelSuccess, onUserJoined, onUserOffline, onAudioVolumeIndication, onRtcStats, onConnectionStateChanged, onLocalVideoStateChanged, onConnectionLost, onTokenPrivilegeWillExpire, onActiveSpeaker, onVideoPublishStateChanged, onAudioPublishStateChanged }: any) => {
rtcEngine.registerEventHandler({
// 监听本地用户加入频道事件
onJoinChannelSuccess: async (connection: RtcConnection, elapsed: number) => {
@ -168,25 +169,17 @@ export const agora = {
await onUserOffline?.(connection, remoteUid, reason)
},
// // 视频发布状态改变回调
// onVideoPublishStateChanged: (source: any, channel: any, oldState: any, newState: any, elapseSinceLastState: any) => {
// if (newState === 1) {
// }
// },
onVideoPublishStateChanged: (source: VideoSourceType, channel: string, oldState: StreamPublishState, newState: StreamPublishState, elapseSinceLastState: number) => {
onVideoPublishStateChanged?.(source, channel, oldState, newState, elapseSinceLastState)
},
// // 音频发布状态改变回调
// onAudioPublishStateChanged: (channel: any, oldState: any, newState: any, elapseSinceLastState: any) => {
// if (newState === 1) {
// }
// },
onAudioPublishStateChanged: (channel: string, oldState: StreamPublishState, newState: StreamPublishState, elapseSinceLastState: number) => {
onAudioPublishStateChanged?.(channel, oldState, newState, elapseSinceLastState)
},
// // 用户音量提示回调。
onAudioVolumeIndication: async (_connection: RtcConnection, speakers: AudioVolumeInfo[], _speakerNumber: number, _totalVolume: number) => {
await onAudioVolumeIndication?.(speakers)
},
//通话中每个用户的网络上下行 last mile 质量报告回调。
onNetworkQuality: async (connection: RtcConnection, remoteUid: number, txQuality: QualityType, rxQuality: QualityType) => {
await onNetworkQuality?.(connection, remoteUid, txQuality, rxQuality)
},
//当前通话相关的统计信息回调。
onRtcStats: async (_connection: RtcConnection, stats: RtcStats) => {
await onRtcStats?.(stats)
@ -202,9 +195,26 @@ export const agora = {
// 网络连接中断,且 SDK 无法在 10 秒内连接服务器回调。
onConnectionLost: (_connection: RtcConnection) => {
onConnectionLost?.()
},
// Token 即将在 30s 内过期回调。
onTokenPrivilegeWillExpire: (connection: RtcConnection, token: string) => {
onTokenPrivilegeWillExpire?.(connection, token)
},
// 监测到远端最活跃用户回调。
onActiveSpeaker: (connection: RtcConnection, uid: number) => {
const setting = JSON.parse(storage.getItem('setting') as string);
if (setting.voiceStimulation) {
onActiveSpeaker?.(connection, uid)
}
}
});
},
// 刷新token
refreshToken: async (data: any) => {
await rtcEngine.updateChannelMediaOptionsEx({
token: data.token,
}, data.connection);
},
// 获取视图模式
getRrenderMode: (uid: number) => {
if (String(uid).length === 9) {
@ -220,7 +230,7 @@ export const agora = {
return
}
await rtcEngine.setupLocalVideo({
renderMode: agora.getRrenderMode(item.uid),
renderMode: item.renderMode || agora.getRrenderMode(item.uid),
sourceType: item.sourceType,
uid: item.uid,
view: item.view,
@ -233,7 +243,7 @@ export const agora = {
if (item.view?.childNodes.length === 1) {
await rtcEngine.setupRemoteVideo(
{
renderMode: agora.getRrenderMode(item.uid),
renderMode: item.renderMode || agora.getRrenderMode(item.uid),
sourceType: VideoSourceType.VideoSourceRemote,
uid: item.uid,
view: item.view,
@ -247,7 +257,7 @@ export const agora = {
if (item.view?.childNodes.length === 1) {
await rtcEngine.setupRemoteVideoEx(
{
renderMode: agora.getRrenderMode(item.uid),
renderMode: item.renderMode || agora.getRrenderMode(item.uid),
sourceType: VideoSourceType.VideoSourceRemote,
uid: item.uid,
view: item.view,
@ -261,7 +271,7 @@ export const agora = {
setupRemoteVideo: async (item: any) => {
await rtcEngine.setupRemoteVideo(
{
renderMode: agora.getRrenderMode(item.uid),
renderMode: item.renderMode || agora.getRrenderMode(item.uid),
sourceType: VideoSourceType.VideoSourceRemote,
uid: item.uid,
view: item.view,
@ -285,7 +295,7 @@ export const agora = {
},
// 加入频道
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.setDualStreamModeEx(
SimulcastStreamMode.EnableSimulcastStream,
@ -298,6 +308,7 @@ export const agora = {
},
{ channelId: option.channelId, localUid: Number(option.uid) }
);
await rtcEngine.setAudioScenario(8)
},
// 更新频道配置
updateChannelMediaOptions: async (bool: boolean) => {
@ -320,10 +331,10 @@ export const agora = {
)
},
// 共享屏幕单独用户
joinChannelEx: async (uid: any) => {
joinChannelEx: async (uid: any, token: string) => {
await agora.leaveChannelEx(uid)
await rtcEngine.joinChannelEx(
option.token,
token,
{ channelId: option.channelId, localUid: Number(uid) },
{
autoSubscribeAudio: false,//设置是否自动订阅所有音频流
@ -336,11 +347,11 @@ export const agora = {
);
},
// 所有用户加入的第二个房间
allJoinChannelEx: async (bool: boolean = false) => {
allJoinChannelEx: async (bool: boolean = false, token: string) => {
const user = await JSON.parse(storage.getItem('user') as string)
await agora.startCameraCapture(true)
await rtcEngine.joinChannelEx(
option.tokenA,
token,
{ channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) },
{
clientRoleType: bool ? ClientRoleType.ClientRoleAudience : ClientRoleType.ClientRoleBroadcaster, //用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众
@ -382,10 +393,14 @@ export const agora = {
},
// 销毁视频渲染dom
destroyRendererByConfig: async (uid: number, channelId?: string) => {
await rtcEngine.destroyRendererByConfig(VideoSourceType.VideoSourceRemote, channelId, uid);
await rtcEngine.destroyRendererByConfig(option.uid === uid ? VideoSourceType.VideoSourceCameraPrimary : VideoSourceType.VideoSourceRemote, channelId, uid);
},
destroyRendererByView: async () => {
let dom = document.getElementById(`meetingAbsoluteVideo`);
destroyRendererByConfigPreview: async (uid: number, channelId: number) => {
await agora.destroyRendererByView('videoPreview')
await rtcEngine.leaveChannelEx({ channelId: `${channelId + uid}`, localUid: Number(uid) })
},
destroyRendererByView: async (key: string) => {
let dom = document.getElementById(key);
if (dom) {
await rtcEngine.destroyRendererByView(dom);
}
@ -433,8 +448,8 @@ export const agora = {
startCameraCapture: async (bool: boolean = false) => {
await rtcEngine.startCameraCapture(VideoSourceType.VideoSourceCamera, {
format: {
width: bool ? 160 : 1280,
height: bool ? 160 : 720,
width: bool ? 160 : 1920,
height: bool ? 160 : 1080,
fps: 15,
}
})
@ -446,7 +461,6 @@ export const agora = {
// 加入频道
setJoinChannel: async (data: any) => {
option.token = data.token;
option.tokenA = data.tokenA;
option.channelId = data.channelId;
option.uid = Number(data.uid);
option.screenShareId = data.screenShareId;
@ -457,7 +471,7 @@ export const agora = {
return await rtcEngine.getScreenCaptureSources(thumbSize, iconSize, includeScreen)
},
// 共享屏幕采集
setDesktopCapturerVideo: async (targetSource: any, isComputerAudio: boolean, isFluencyPriority: boolean) => {
setDesktopCapturerVideo: async (targetSource: any, isComputerAudio: boolean, isFluencyPriority: boolean, token: string) => {
const user = JSON.parse(storage.getItem('user') as string)
agora.stopScreenCapture();
if (isComputerAudio) {
@ -465,7 +479,7 @@ export const agora = {
}
let data = {
frameRate: isFluencyPriority ? 30 : 15,
frameRate: isFluencyPriority ? 15 : 7,
dimensions: {
width: 1920,
height: 1080,
@ -493,7 +507,7 @@ export const agora = {
}
);
}
await agora.joinChannelEx(user.screenShareId)
await agora.joinChannelEx(user.screenShareId, token)
},
// 获取系统中所有的视频设备列表。
getVideoDeviceManager: async (): Promise<any> => {
@ -507,12 +521,12 @@ export const agora = {
await rtcEngine.getVideoDeviceManager().setDevice(deviceIdUTF8)
},
// 开启本地视频预览
startPreview: async (id: string, uid: number): Promise<void> => {
startPreview: async (id: string, uid: number, channelId: number): Promise<void> => {
rtcEngine.enableVideo();
rtcEngine.startPreview();
await GetRoomRtcToken(`${+new Date()}`).then(async (res) => {
await GetRoomRtcToken(`${channelId + uid}`).then(async (res) => {
await rtcEngine.joinChannelEx(res.data, {
channelId: `${+new Date() + uid}`,
channelId: `${channelId + uid}`,
localUid: uid,
}, {
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,

View File

@ -84,6 +84,8 @@ import icon52Select from '@/assets/icon52-select.png'
import icon53 from '@/assets/icon53.png'
import icon54 from '@/assets/icon54.png'
import icon55 from '@/assets/icon55.png'
import icon56 from '@/assets/icon56.png'
import icon56Active from '@/assets/icon56-active.png'
export default {
loading,
icon,
@ -170,5 +172,7 @@ export default {
icon52Select,
icon53,
icon54,
icon55
icon55,
icon56,
icon56Active
}

View File

@ -1,5 +1,7 @@
import path from "path";
import storage from "./storage";
import axios from "axios";
import yaml from 'js-yaml'
export const setKeyOpenChildWindow = async (key: string, bool: boolean) => {
const openChildWindow = await JSON.parse(storage.getItem('openChildWindow') as string)
openChildWindow[key] = bool;
@ -27,6 +29,7 @@ export const storageSeeting: any = {
shareFilesPath: path.resolve(__dirname, '../../Downloads/') + '\\', //共享文件保存路径
isShareSavePath: true, //是否下载钱询问每个文件保存的位置
closeSetting: 'hide', //关闭按钮设置
voiceStimulation: true, //语音激励
isAINoiseReduction: true, //是否开启ai降噪
aINoiseReduction: 1, // 降噪模式
isRecordingTips: true, //是否开启录制提示
@ -59,15 +62,61 @@ export const getUpdateUrl = (env: string) => {
switch (env) {
case 'xy':
return 'https://meeting-api.23544.com/meeting/xysz'
case 'development':
return 'http://192.168.2.9:8827'
default:
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) {
case 'xy':
return '湖北襄阳四中教研平台'
str = '湖北襄阳四中教研平台'
break;
case 'development':
str = '智汇享'
break;
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) {
case 'xy':
return 'https://meeting-api.23544.com/meeting/xysz'
case 'development':
return 'http://192.168.2.9:8827'
default:
return 'https://meeting-api.23544.com/meeting/update'
}
@ -11,6 +13,8 @@ module.exports = {
switch (env) {
case 'xy':
return '湖北襄阳四中教研平台'
case 'development':
return '智汇享'
default:
return '智汇享'
}
@ -19,6 +23,8 @@ module.exports = {
switch (env) {
case 'xy':
return 'icon54'
case 'development':
return 'icon'
default:
return 'icon'
}

View File

@ -1,10 +1,10 @@
import { AxiosRequestConfig, AxiosResponse } from 'axios'
import Request from './request'
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({
baseURL: import.meta.env.VITE_BASE_URL_API,
baseURL,
timeout: constant.CONFIG_REQUEST_TIMEOUT_TIME as number,
interceptors: {
// 请求拦截器
@ -22,5 +22,4 @@ const request = (config: any) => {
}
return req.request<any>(config)
}
export default request

View File

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

View File

@ -405,3 +405,16 @@ $pagination-hover-background-color: #5575F2;
.ant-tabs {
-webkit-app-region: no-drag;
}
.ant-rate .ant-rate-star-first,
.ant-rate .ant-rate-star-second {
color: gray;
}
.hideCancelText {
.ant-modal-confirm-btns {
>button:nth-child(1){
display: none;
}
}
}

View File

@ -64,7 +64,6 @@ export default defineConfig({
AudioAinsMode,
SimulcastStreamMode,
VideoStreamType,
QualityType,
RtcConnection,
RtcStats,
AudioVolumeInfo,
@ -77,7 +76,9 @@ export default defineConfig({
ColorEnhanceOptions,
LowlightEnhanceOptions,
VirtualBackgroundSource,
AudienceLatencyLevelType
AudienceLatencyLevelType,
StreamPublishState,
IMediaEngine
} = require("agora-electron-sdk")
export {
createAgoraRtcEngine,
@ -90,7 +91,6 @@ export default defineConfig({
AudioAinsMode,
SimulcastStreamMode,
VideoStreamType,
QualityType,
RtcConnection,
RtcStats,
AudioVolumeInfo,
@ -103,7 +103,9 @@ export default defineConfig({
ColorEnhanceOptions,
LowlightEnhanceOptions,
VirtualBackgroundSource,
AudienceLatencyLevelType
AudienceLatencyLevelType,
StreamPublishState,
IMediaEngine
}
`,
})