Compare commits

...

203 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
yangqiang e820c3e53c Merge pull request 'yangjie' (#46) from yangjie into master
Reviewed-on: #46
2025-01-03 10:43:26 +08:00
yj 92cfef8f91 添加退出房间 2025-01-03 09:16:11 +08:00
yj 1c6f551a3b 修改会议监控关闭时摄像头卡住 2025-01-03 09:12:51 +08:00
yj d0ca3c9321 优化缩略图大小 2025-01-02 16:29:02 +08:00
yj 5c5780d150 优化 2025-01-02 15:46:56 +08:00
yj d51d1f9844 优化 2025-01-02 13:45:29 +08:00
yj 488724aa62 优化 2025-01-02 11:42:29 +08:00
yj d2d20d3efd 去除多余代码 2024-12-30 13:34:19 +08:00
yj 3723873d71 优化频繁切换用户身份系统卡顿 2024-12-27 16:49:44 +08:00
yj c07de8d2a1 停止采集摄像头参数修改 2024-12-27 15:25:59 +08:00
yj 3bc5994506 优化进入房间快速退出提示无效房间号 2024-12-26 15:15:36 +08:00
yj 1ad41eaf26 修改录制文件比特率 2024-12-20 10:03:12 +08:00
yj 2284b01d89 优化 2024-12-19 16:13:53 +08:00
yj c9ca37d323 优化 2024-12-19 15:43:08 +08:00
yj e26d8f0c3f 优化提升 2024-12-19 15:33:24 +08:00
yj 5d6c43e27a 优化 2024-12-18 15:21:48 +08:00
yangqiang f47681d2f1 Merge pull request 'yangjie' (#45) from yangjie into master
Reviewed-on: #45
2024-12-18 13:52:10 +08:00
yj d867305ca4 防止录制重复点击 2024-12-17 15:08:20 +08:00
yj 3df6dc6add 优化 2024-12-17 14:47:57 +08:00
yj 12eaca27f9 优化 2024-12-17 14:05:56 +08:00
yj db568f46eb Merge branch 'yangjie' of https://gitea.23544.com/marking/WGShare.Client.Electron into yangjie 2024-12-17 11:14:47 +08:00
yj bc78d3a692 优化共享文件操作 2024-12-17 11:14:42 +08:00
youngq e5ca4e6f3a 修改匿名入会为游客登录 2024-12-16 16:49:46 +08:00
yj 63914c1fb7 修改进入会议状态 2024-12-16 14:27:13 +08:00
yj d0387662a6 人员届 2024-12-16 14:09:25 +08:00
yj 2501267340 更新版本 2024-12-13 15:10:39 +08:00
yj 3632bd4d07 优化 2024-12-13 15:03:55 +08:00
yj 6fce7478c6 优化 2024-12-13 14:35:47 +08:00
yj 9f154c558e 修改类型 2024-12-13 14:33:25 +08:00
yj 20e25fa96d 优化 2024-12-13 14:09:28 +08:00
yj 631dbf26b1 共享屏幕添加修改用户名 2024-12-13 13:37:07 +08:00
yj c557fed280 小窗添加按钮 2024-12-13 13:32:36 +08:00
yj 7b7049681a 优化 2024-12-13 12:18:22 +08:00
yj bfd629f264 优化 2024-12-13 12:17:18 +08:00
yj 8176c9b5d7 修改用户名 2024-12-13 11:44:15 +08:00
yj ec5edabaf0 优化 2024-12-13 10:27:53 +08:00
yangqiang 782350dc77 Merge pull request 'yangjie' (#43) from yangjie into master
Reviewed-on: #43
2024-12-12 14:23:47 +08:00
yj 41e9742610 修改bug 2024-12-12 11:21:59 +08:00
yj c8d8db27d7 优化 2024-12-12 11:13:38 +08:00
yj 97abad8231 增加提示 2024-12-12 10:38:21 +08:00
yj aa8809e03c 修改房间号不存在loading取消 2024-12-12 10:13:39 +08:00
yangqiang 39010986d2 Merge pull request '样式' (#42) from yangjie into master
Reviewed-on: #42
2024-12-11 15:22:03 +08:00
yj d30b6ed762 样式 2024-12-11 15:20:58 +08:00
yangqiang ae8ba8cbe2 Merge pull request 'yangjie' (#41) from yangjie into master
Reviewed-on: #41
2024-12-11 14:52:08 +08:00
yj 7aa8bb4a8f 共享时录屏按钮不显示 2024-12-11 14:41:30 +08:00
yj bafa7e3a70 设置黑名单 2024-12-10 18:00:10 +08:00
yj 4f462e818b 停止接取共享流 2024-12-10 17:36:19 +08:00
yj c99ec3668c 优化 2024-12-10 17:15:32 +08:00
yj 9a5ef75322 样式&全员退出分享人优化 2024-12-10 17:04:28 +08:00
yj 0781ebc3af 更换定时器方法 2024-12-10 15:04:54 +08:00
yj cb1593a76f 优化 2024-12-10 14:49:43 +08:00
yj 79b1d8c5c2 添加参数 2024-12-10 13:47:45 +08:00
yj 0b57e83e29 修改延迟参数 2024-12-10 13:45:29 +08:00
yj cc2759b242 Merge branch 'master' of https://gitea.23544.com/marking/WGShare.Client.Electron into yangjie 2024-12-10 09:52:58 +08:00
yj b314a2c25f 恢复会议监控尺寸 2024-12-09 14:50:15 +08:00
yj 5fb95d6a15 优化 2024-12-09 13:58:11 +08:00
yj 4fbcf0e319 添加错误日志 2024-12-09 11:51:41 +08:00
yj a20c833de0 统一日志名字 2024-12-09 11:20:47 +08:00
yj 234665d08d 日志 2024-12-09 11:09:27 +08:00
yj 4528696ec7 日志显示 2024-12-09 11:01:13 +08:00
yj 9619230cf8 优化 2024-12-06 18:00:24 +08:00
yj 844547d853 优化 2024-12-06 17:26:02 +08:00
yj b267f8bf4c Merge branch 'yangjie' of https://gitea.23544.com/marking/WGShare.Client.Electron into yangjie 2024-12-06 17:25:44 +08:00
yj a29784bb26 防止出现多个任务 2024-12-06 17:25:28 +08:00
yangqiang 3498dc9a61 Merge pull request '1' (#40) from yangjie into master
Reviewed-on: #40
2024-12-03 13:57:32 +08:00
youngq e81f89fd88 1 2024-12-03 13:57:21 +08:00
yangqiang 954b6d38e5 Merge pull request 'yangjie' (#39) from yangjie into master
Reviewed-on: #39
2024-12-03 13:56:25 +08:00
yj a390572d99 优化二维码加载显示 2024-12-03 09:37:51 +08:00
yj 605b10e5a5 优化样式 2024-11-29 17:05:57 +08:00
yj 3c0faea343 首页新增小程序二维码入口 2024-11-29 17:02:10 +08:00
73 changed files with 2884 additions and 928 deletions

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

16
build/install.nsh Normal file
View File

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

57
config/development.json Normal file
View File

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

View File

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

View File

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

263
main.js
View File

@ -8,7 +8,10 @@ const {
ipcMain, ipcMain,
clipboard, clipboard,
dialog, dialog,
crashReporter,
desktopCapturer, desktopCapturer,
powerSaveBlocker,
net
} = require('electron'); } = require('electron');
const path = require('node:path') const path = require('node:path')
const updateJs = require('./src/utils/package/update') const updateJs = require('./src/utils/package/update')
@ -16,15 +19,20 @@ const fs = require('fs');
const Registry = require('winreg'); const Registry = require('winreg');
const { autoUpdater, CancellationToken } = require('electron-updater'); const { autoUpdater, CancellationToken } = require('electron-updater');
const signalR = require('@microsoft/signalr'); const signalR = require('@microsoft/signalr');
const { setTimeout, setInterval } = require('timers');
const cancellationToken = new CancellationToken() const cancellationToken = new CancellationToken()
app.allowRendererProcessReuse = false; app.allowRendererProcessReuse = false;
let mainWindow = null; let mainWindow = null;
let childWindow = {} let childWindow = {}
let isMaximized = false; let isMaximized = false;
let env; let env = 'development'; //development production xy
let regKey; let regKey;
let connection = null; let connection = null;
let envStr; let startNumber = 0;
let buildStatus = false; //true 打包开发版本 false 本地开发
powerSaveBlocker.start('prevent-display-sleep')
const id = powerSaveBlocker.start('prevent-display-sleep')
powerSaveBlocker.stop(id)
class AppWindow extends BrowserWindow { class AppWindow extends BrowserWindow {
constructor(config) { constructor(config) {
@ -44,13 +52,16 @@ class AppWindow extends BrowserWindow {
}; };
const finalConfig = { ...basicConfig, ...config }; const finalConfig = { ...basicConfig, ...config };
super(finalConfig); super(finalConfig);
if (envStr === 'development') { if (env === 'development') {
// 开发 if (buildStatus) {
this.loadURL('http://localhost:3000'); this.loadURL('http://192.168.2.9:8827/');
} else { } else {
// 测试 | 生产 this.loadURL('http://localhost:3000');
this.loadFile(path.resolve(__dirname, './dist/index.html'));
} }
} else {
this.loadURL('https://meeting-api.23544.com/')
}
// this.loadFile(path.resolve(__dirname, './dist/index.html'))
this.once('ready-to-show', () => { this.once('ready-to-show', () => {
this.show(); this.show();
}); });
@ -59,33 +70,21 @@ class AppWindow extends BrowserWindow {
function quit() { function quit() {
app.quit() app.quit()
} }
let tray;
// 检查网络状态
function checkNetworkStatus() {
if (!net.isOnline()) {
dialog.showErrorBox(`${env === 'xy' ? '湖北襄阳四中教研平台' : '智汇享'}-网络连接错误', '当前无网络连接,请检查您的网络设置。`);
app.quit();
return false;
}
return true;
}
function createTray() { function createTray() {
const iconPath = `${__dirname}/src/assets/${updateJs.getIcon(envStr)}.png`; const iconPath = `${__dirname}/src/assets/${updateJs.getIcon(env)}.png`;
const trayIcon = nativeImage.createFromPath(iconPath); const trayIcon = nativeImage.createFromPath(iconPath);
const tray = new Tray(trayIcon); tray = new Tray(trayIcon);
const contextMenu = Menu.buildFromTemplate([ tray.setToolTip(updateJs.getTitle(env));
{
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.on('click', () => { tray.on('click', () => {
mainWindow.webContents.send('isOpenWindows'); mainWindow.webContents.send('isOpenWindows');
}); });
@ -94,20 +93,36 @@ function createTray() {
function createWindow() { function createWindow() {
mainWindow = new AppWindow(); mainWindow = new AppWindow();
mainWindow.focus(); mainWindow.focus();
mainWindow.hookWindowMessage(278, function (e) {
mainWindow.setEnabled(false);//窗口禁用
setTimeout(() => {
mainWindow.setEnabled(true);//窗口启用
}, 100);
return true;
})
} }
const additionalData = { myKey: 'myValue' } const additionalData = { myKey: 'myValue' }
app.on('ready', () => { app.on('ready', () => {
// 检查网络状态
if (!checkNetworkStatus()) {
return;
}
// const gotTheLock = true
const gotTheLock = app.requestSingleInstanceLock(additionalData) const gotTheLock = app.requestSingleInstanceLock(additionalData)
if (gotTheLock) { if (gotTheLock) {
env = process.argv.find((arg) => arg.startsWith('--env='))?.split('=')[1]; app.getPath('crashDumps')
if (env === 'development') { crashReporter.start({
Object.defineProperty(app, 'isPackaged', { uploadToServer: false,
get() { ignoreSystemCrashHandler: false
return true
}
}) })
autoUpdater.updateConfigPath = path.join('latest.yml') // if (!buildStatus) {
} // Object.defineProperty(app, 'isPackaged', {
// get() {
// return true
// }
// })
// autoUpdater.updateConfigPath = path.join('latest.yml')
// }
createWindow() createWindow()
regKey = new Registry({ regKey = new Registry({
hive: Registry.HKCU, hive: Registry.HKCU,
@ -128,6 +143,12 @@ app.on('ready', () => {
mainWindow.webContents.openDevTools() mainWindow.webContents.openDevTools()
} }
}); });
mainWindow.on('focus', () => {
mainWindow.show()
});
mainWindow.on('maximize', () => {
mainWindow.show()
});
// 监听移动 // 监听移动
mainWindow.on('move', () => { mainWindow.on('move', () => {
// 如果是全屏自动恢复到上次窗口大小 // 如果是全屏自动恢复到上次窗口大小
@ -139,13 +160,19 @@ app.on('ready', () => {
isMaximized = true; isMaximized = true;
} }
}); });
ipcMain.handle('setEnv', (event, str) => { ipcMain.handle('startLoad', (event) => {
envStr = str; if (startNumber === 0) {
updateHandle() // 检查更新 updateHandle() // 检查更新
setInterval(() => { setInterval(() => {
updateHandle() // 每一小时检查更新 autoUpdater.checkForUpdates()
}, 1000 * 60 * 60) }, 1000 * 60 * 60)
createTray() createTray()
startNumber++
}
});
// 更新
ipcMain.handle('updateHandle', () => {
autoUpdater.checkForUpdates()
}); });
// socket // socket
ipcMain.handle('startSignalr', (event, user) => { ipcMain.handle('startSignalr', (event, user) => {
@ -170,6 +197,12 @@ app.on('ready', () => {
connection.off('DriverList'); connection.off('DriverList');
connection.off('SetDriver'); connection.off('SetDriver');
connection.off('ShowDriverList'); 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) => { ipcMain.handle('onStop', (event) => {
@ -189,8 +222,7 @@ app.on('ready', () => {
await connection.invoke(str, data.roomNum, data.msg) await connection.invoke(str, data.roomNum, data.msg)
break; break;
case 'sendOper': case 'sendOper':
// 4:屏幕共享 await connection.invoke(str, data.roomNum, data.contentString)
await connection.invoke(str, data.roomNum, data.type)
break; break;
case 'getDrivers': case 'getDrivers':
// 获取某个人的设备列表 // 获取某个人的设备列表
@ -205,13 +237,21 @@ app.on('ready', () => {
await connection.invoke(str, data.uid, data.driversJsonString) await connection.invoke(str, data.uid, data.driversJsonString)
break; break;
case 'joinChannel': case 'joinChannel':
// 设置某个人的设备列表 // 加入房间
await connection.invoke(str, data.roomNum, data.enableMicr, data.enableCamera, data.isRoomManager || false) await connection.invoke(str, data.roomNum, data.enableMicr, data.enableCamera, data.isRoomManager || false)
break; break;
case 'levelChannel': case 'levelChannel':
// 设置某个人的设备列表 // 退出房间
await connection.invoke(str, data.roomNum) await connection.invoke(str, data.roomNum)
break; break;
case 'SetSpeakerCallback':
// 发言人设置成功
await connection.invoke(str, data)
break;
case 'sendOper2User':
// 扩展参数
await connection.invoke(str, data.uid, data.contentString)
break;
} }
}); });
ipcMain.handle('onOtherSignalr', (event) => { ipcMain.handle('onOtherSignalr', (event) => {
@ -242,10 +282,10 @@ app.on('ready', () => {
}) })
}); });
// 扩展操作 // 扩展操作
connection.on("Operation", (type) => { connection.on("Operation", (contentString) => {
mainWindow.webContents.send('onSignalr', { mainWindow.webContents.send('onSignalr', {
key: 'Operation', key: 'Operation',
type contentString
}) })
}); });
// 移出会议 // 移出会议
@ -359,6 +399,48 @@ app.on('ready', () => {
driversJsonString driversJsonString
}) })
}); });
// 修改用户名称
connection.on("ModifyNickName", (uid, nickName) => {
mainWindow.webContents.send('onSignalr', {
key: 'ModifyNickName',
uid,
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
})
});
} }
}); });
// 放大缩小退出窗口 // 放大缩小退出窗口
@ -382,6 +464,9 @@ app.on('ready', () => {
case 'show': case 'show':
mainWindow.show() mainWindow.show()
mainWindow.focus(); mainWindow.focus();
mainWindow.setSkipTaskbar(false)
mainWindow.setResizable(true)
mainWindow.setAlwaysOnTop(false)
break; break;
} }
}); });
@ -397,6 +482,10 @@ app.on('ready', () => {
ipcMain.handle('getVersion', () => { ipcMain.handle('getVersion', () => {
return app.getVersion(); return app.getVersion();
}); });
// 获取环境
ipcMain.handle('getEnv', () => {
return env;
});
// 获取窗口是否显示 // 获取窗口是否显示
ipcMain.handle('isVisible', () => { ipcMain.handle('isVisible', () => {
return mainWindow.isVisible(); return mainWindow.isVisible();
@ -432,6 +521,11 @@ app.on('ready', () => {
downloadUpdate() downloadUpdate()
} else if (data === '2') { // 下载完成 点击安装 } else if (data === '2') { // 下载完成 点击安装
quitAndInstall() quitAndInstall()
} else if (data === '3') { // 打开弹窗
let message = JSON.stringify({
type: '3',
})
sendUpdateMessage(message)
} }
}); });
// 选择文件夹 // 选择文件夹
@ -465,6 +559,34 @@ app.on('ready', () => {
}); });
// 设置桌面应用基础属性 // 设置桌面应用基础属性
ipcMain.handle('setMainWindowSize', (event, config) => { 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); mainWindow.setMinimumSize(config.width, config.height);
// 设置最大尺寸 // 设置最大尺寸
@ -521,13 +643,26 @@ app.on('ready', () => {
width: config.width, width: config.width,
height: config.height, 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) child.loadURL(config.url)
}
} else { } else {
// 测试 | 生产 // 测试 | 生产
child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`); child.loadURL(`https://meeting-api.23544.com/#/${config.key}`);
// child.loadURL(`file://${path.join(__dirname, './dist/index.html')}#/${config.key}`);
} }
child.hookWindowMessage(278, function (e) {
child.setEnabled(false);//窗口禁用
setTimeout(() => {
child.setEnabled(true);//窗口启用
}, 100);
return true;
})
childWindow[config.key] = child childWindow[config.key] = child
child.once('ready-to-show', () => { child.once('ready-to-show', () => {
if (config.show) { if (config.show) {
@ -552,9 +687,6 @@ app.on('ready', () => {
childWindow[k] = "" childWindow[k] = ""
} }
} }
mainWindow.setSkipTaskbar(false)
mainWindow.setResizable(true)
mainWindow.setAlwaysOnTop(false)
} else { } else {
childWindow[key].close() childWindow[key].close()
childWindow[key] = "" childWindow[key] = ""
@ -589,6 +721,9 @@ app.on('ready', () => {
childWindow[config.key].hide() childWindow[config.key].hide()
} }
} }
} else {
if (config.bool) {
childWindow[config.key].hide()
} else { } else {
if (childWindow[config.key].isVisible()) { if (childWindow[config.key].isVisible()) {
childWindow[config.key].hide() childWindow[config.key].hide()
@ -596,6 +731,7 @@ app.on('ready', () => {
childWindow[config.key].show() childWindow[config.key].show()
} }
} }
}
}); });
// 定位主窗口 // 定位主窗口
ipcMain.handle('setPosition', (event, data) => { ipcMain.handle('setPosition', (event, data) => {
@ -620,7 +756,6 @@ app.on('ready', () => {
}); });
// 检测更新在你想要检查更新的时候执行renderer事件触发后的操作自行编写 // 检测更新在你想要检查更新的时候执行renderer事件触发后的操作自行编写
function updateHandle() { function updateHandle() {
autoUpdater.checkForUpdates()
// autoUpdater.checkForUpdatesAndNotify().catch(); // autoUpdater.checkForUpdatesAndNotify().catch();
const message = { const message = {
error: '检查更新出错', error: '检查更新出错',
@ -628,23 +763,26 @@ function updateHandle() {
updateAva: '检测到新版本,正在下载……', updateAva: '检测到新版本,正在下载……',
updateNotAva: '已经是最新版本,不用更新' updateNotAva: '已经是最新版本,不用更新'
} }
autoUpdater.setFeedURL(updateJs.getUpdateUrl(envStr)) autoUpdater.setFeedURL(updateJs.getUpdateUrl(env))
autoUpdater.autoDownload = false // 不自动下载安装包 autoUpdater.autoDownload = false // 不自动下载安装包
autoUpdater.autoInstallOnAppQuit = false // 不自动安装 autoUpdater.autoInstallOnAppQuit = false // 不自动安装
autoUpdater.on('error', function (error) { autoUpdater.on('error', function (error) {
sendUpdateMessage(message.error) sendUpdateMessage(error)
}) })
autoUpdater.on('checking-for-update', function () { autoUpdater.on('checking-for-update', function () {
sendUpdateMessage(message.checking) sendUpdateMessage(message.checking)
}) })
autoUpdater.on('update-available', function (info) { autoUpdater.on('update-available', function (info) {
let messageStr = JSON.stringify({ type: '0' }) let messageStr = JSON.stringify({ type: '0' })
setTimeout(() => {
sendUpdateMessage(messageStr) sendUpdateMessage(messageStr)
}, 5000) mainWindow.webContents.send('changeLocalStorage', {
isUpdate: true,
});
}) })
autoUpdater.on('update-not-available', function (info) { autoUpdater.on('update-not-available', function (info) {
mainWindow.webContents.send('changeLocalStorage', {
isUpdate: false,
});
}) })
// 更新下载进度事件 // 更新下载进度事件
autoUpdater.on('download-progress', function (progressObj) { autoUpdater.on('download-progress', function (progressObj) {
@ -695,9 +833,6 @@ function windowOperation(config) {
case 'shareScreenWindow': case 'shareScreenWindow':
x = Math.round((display.workArea.width - child.getSize()[0]) / 2); x = Math.round((display.workArea.width - child.getSize()[0]) / 2);
child.setPosition(x, 0); child.setPosition(x, 0);
mainWindow.setSkipTaskbar(true)
mainWindow.setResizable(false)
mainWindow.setAlwaysOnTop(true, 'screen-saver')
break; break;
case 'chatSmallWindow': case 'chatSmallWindow':
y = height - child.getSize()[1]; y = height - child.getSize()[1];
@ -720,7 +855,7 @@ function mainWindowCenter() {
const startSignalr = async (user) => { const startSignalr = async (user) => {
connection = new signalR.HubConnectionBuilder() connection = new signalR.HubConnectionBuilder()
.withUrl(`${envStr === 'development' ? 'http://192.168.2.9:5192' : 'https://meeting-api.23544.com/pc'}/session-manage`, { .withUrl(`${env === 'development' ? 'http://192.168.2.9:5192' : 'https://meeting-api.23544.com/pc'}/session-manage`, {
skipNegotiation: true, skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets, transport: signalR.HttpTransportType.WebSockets,
accessTokenFactory: () => user.token accessTokenFactory: () => user.token

7
package-lock.json generated
View File

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

View File

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

View File

@ -51,6 +51,10 @@ window.electron = {
getVersion: () => { getVersion: () => {
return ipcRenderer.invoke('getVersion') return ipcRenderer.invoke('getVersion')
}, },
// 获取环境
getEnv: () => {
return ipcRenderer.invoke('getEnv')
},
// 获取窗口是否显示 // 获取窗口是否显示
isVisible: () => { isVisible: () => {
return ipcRenderer.invoke('isVisible') return ipcRenderer.invoke('isVisible')
@ -87,9 +91,13 @@ window.electron = {
isOpenWindows: (callback) => { isOpenWindows: (callback) => {
ipcRenderer.on('isOpenWindows', callback) ipcRenderer.on('isOpenWindows', callback)
}, },
// 设置环境变量 // 首次加载
setEnv: (str) => { startLoad: () => {
ipcRenderer.invoke('setEnv', str) ipcRenderer.invoke('startLoad')
},
// 更新
updateHandle: () => {
ipcRenderer.invoke('updateHandle')
}, },
// 通知下载最新的包 // 通知下载最新的包
onDownload: (type) => { onDownload: (type) => {
@ -116,8 +124,59 @@ window.electron = {
ipcRenderer.invoke('setRegistry', uuid) ipcRenderer.invoke('setRegistry', uuid)
}, },
// 创建子窗口 // 创建子窗口
createChildWindow: (config) => { createChildWindow: (str) => {
ipcRenderer.invoke('createChildWindow', config) switch (str) {
case 'show':
ipcRenderer.invoke('setChildWindowShow', {
key: 'shareScreenWindow',
})
ipcRenderer.invoke('setChildWindowShow', {
key: 'chatSmallWindow',
})
break;
case 'hide':
ipcRenderer.invoke('createChildWindow', {
url: location.origin + `/#/noticeWindow`,
width: 388,
height: 180,
key: 'noticeWindow',
})
ipcRenderer.invoke('createChildWindow', {
url: location.origin + `/#/shareScreenWindow`,
width: 400,
height: 80,
key: 'shareScreenWindow',
})
ipcRenderer.invoke('createChildWindow', {
url: location.origin + `/#/chatSmallWindow`,
width: 200,
height: 150,
key: 'chatSmallWindow',
})
ipcRenderer.invoke('createChildWindow', {
url: location.origin + `/#/chatBigWindow`,
width: 540,
height: 640,
key: 'chatBigWindow',
})
ipcRenderer.invoke('createChildWindow', {
url: location.origin + `/#/userListWindow`,
width: 440,
height: 540,
key: 'userListWindow',
})
break;
case 'stop':
ipcRenderer.invoke('setChildWindowShow', {
key: 'shareScreenWindow',
bool: true
})
ipcRenderer.invoke('setChildWindowShow', {
key: 'chatSmallWindow',
bool: true
})
break;
}
}, },
// 关闭子窗口 // 关闭子窗口
closeChildWindow: (key) => { closeChildWindow: (key) => {

View File

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

View File

@ -5,6 +5,12 @@ export const GetRoom = (data: { pageIndex: number, pageSize: number }) =>
method: 'get' method: 'get'
}) })
export const PostFeedback = (data: any) =>
request({
url: `/home/feedback`,
method: 'post',
data,
})
export const PostRoom = (data: any) => export const PostRoom = (data: any) =>
request({ request({
url: `/home/room`, url: `/home/room`,
@ -46,6 +52,11 @@ export const GetAgoraConf = () =>
url: `/home/agora-conf`, url: `/home/agora-conf`,
method: 'get', method: 'get',
}) })
export const GetQrcode = (roomNum: string, env: string) =>
request({
url: `/home/r-qrcode?roomNum=${roomNum}&env=${env}`,
method: 'get',
})
export const GetRecord = (beginTimestamp: number, endTimestamp: number, roomNum: string) => export const GetRecord = (beginTimestamp: number, endTimestamp: number, roomNum: string) =>
request({ request({

View File

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

View File

@ -71,14 +71,20 @@ export const PostRoomManager = (data: any) =>
request({ request({
url: `/room/manager`, url: `/room/manager`,
method: 'post', method: 'post',
data data: {
...data,
SettingUserId: ''
}
}) })
export const DeleteRoomManager = (data: any) => export const DeleteRoomManager = (data: any) =>
request({ request({
url: `/room/manager`, url: `/room/manager`,
method: 'delete', method: 'delete',
data data: {
...data,
SettingUserId: ''
}
}) })
export const GetRoomKickout = (roomNum: string, kickUid: string) => export const GetRoomKickout = (roomNum: string, kickUid: string) =>
@ -148,3 +154,31 @@ export const PostRoomSingnIn = (data: any) =>
method: 'post', method: 'post',
data data
}) })
export const PutAlterUname = (data: any) =>
request({
url: `/room/alter-uname`,
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

BIN
src/assets/icon55.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 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 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) => { const Avatar = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
getData: () => { 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 styles from '@/components/EquipmentManagement/index.module.scss'
import { getKeyOpenChildWindow } from '@/utils/package/public'; import { getKeyOpenChildWindow } from '@/utils/package/public';
import { Button, Modal, Select, Slider, message } from 'antd'; 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) => { const EquipmentManagement = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeModal: async (uid: string, userName: string) => { 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 styles from '@/components/InvitingPersonnelModal/index.module.scss'
import { Button, Checkbox, Input, Modal, Pagination, Radio, message } from 'antd'; 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 { SearchOutlined } from '@ant-design/icons';
import { GetUserList } from '@/api/Home/User'; import { GetUserList } from '@/api/Home/User';
import { useLocation } from 'react-router-dom'; 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 styles from '@/components/JoinMeetingModal/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl'; import ImageUrl from '@/utils/package/imageUrl';
import { Modal, message } from 'antd'; 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 Avatar from '@/components/Avatar';
import JoinSetting from '../JoinSetting'; import JoinSetting from '../JoinSetting';
const JoinMeetingModal = forwardRef((props: any, ref: any) => { 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,14 @@ import { storage } from '@/utils';
import ImageUrl from '@/utils/package/imageUrl'; import ImageUrl from '@/utils/package/imageUrl';
import { GetCheckoutRoomNum, GetRoomRtcToken, GetRoomInfo } from '@/api/Home/Index'; import { GetCheckoutRoomNum, GetRoomRtcToken, GetRoomInfo } from '@/api/Home/Index';
import { Button, Modal, message } from 'antd'; 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 { PostRefresh } from '@/api/Login';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { agora } from '@/utils/package/agora'; import { agora } from '@/utils/package/agora';
import { role } from '@/config/role'; import { role } from '@/config/role';
import { PostHomeVerLog } from '@/api/Meeting';
const { setInterval, clearInterval } = require('timers');
let time = null as any; let time = null as any;
const JoinSetting = forwardRef((_props: any, ref: any) => { const JoinSetting = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
@ -19,8 +21,8 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
if (location.hash.indexOf('/meeting') === -1) { if (location.hash.indexOf('/meeting') === -1) {
await agora.init() await agora.init()
} }
setJoinRoomSettingForm((res: any) => { const list = [...joinRoomSettingForm]
res.forEach(async (item: any, index: number) => { list.forEach(async (item: any, index: number) => {
if (index === 0 && role.ID.includes(userInfo.roleId)) { if (index === 0 && role.ID.includes(userInfo.roleId)) {
await agora.getAudioMediaList().then(res => { await agora.getAudioMediaList().then(res => {
item.active = res.ecordingList.length ? true : false item.active = res.ecordingList.length ? true : false
@ -29,8 +31,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
item.active = false item.active = false
} }
}); });
return res setJoinRoomSettingForm(list)
})
setRoomNumber(roomNum) setRoomNumber(roomNum)
getDeviceList() getDeviceList()
} }
@ -170,7 +171,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
setJoinRoomSettingForm(list) setJoinRoomSettingForm(list)
if (index === 1) { if (index === 1) {
if (list[index].active) { if (list[index].active) {
agora.startPreview('videoPreview', Number(user.screenShareId)) agora.startPreview('videoPreview', Number(user.screenShareId), +new Date())
} }
} }
} else { } else {
@ -201,6 +202,13 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
GetRoomInfo(roomNumber).then(async (res) => { GetRoomInfo(roomNumber).then(async (res) => {
if (res.code === 200) { if (res.code === 200) {
await agora.release() await agora.release()
await window.electron.getVersion().then(async req => {
await PostHomeVerLog({
version: req,
platformType: 1,
roomNum: roomNumber,
})
})
navigate(`/meeting`, { navigate(`/meeting`, {
state: { state: {
channelId: roomNumber, channelId: roomNumber,
@ -221,6 +229,7 @@ const JoinSetting = forwardRef((_props: any, ref: any) => {
}) })
} else { } else {
message.error('房间号不存在!') message.error('房间号不存在!')
storage.setItem('loading', false)
} }
}) })
}} }}
@ -235,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 styles from '@/components/MeetingDisconnected/index.module.scss'
import { InfoCircleOutlined } from '@ant-design/icons'; 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) => { const MeetingDisconnected = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeModal: (bool: boolean) => { 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 styles from '@/components/Operation/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl'; 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 OperationKeyType = 'minimize' | 'quit' | 'maximize' | 'unmaximize' | 'hide' | 'show';
type OperationType = { type OperationType = {
icon: string; 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 { storage } from '@/utils';
import { InfoCircleOutlined } from '@ant-design/icons'; import { InfoCircleOutlined } from '@ant-design/icons';
import { Button, Checkbox, Modal, Radio } from 'antd'; 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'; type OperationKeyType = 'minimize' | 'quit' | 'maximize' | 'unmaximize' | 'hide' | 'show';
const QuitTips = forwardRef((props: any, ref: any) => { const QuitTips = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
@ -66,4 +66,4 @@ const QuitTips = forwardRef((props: any, ref: any) => {
) )
}) })
export default QuitTips export default memo(QuitTips)

View File

@ -1,13 +1,14 @@
import styles from '@/components/SharedFilesModel/index.module.scss' import styles from '@/components/SharedFilesModel/index.module.scss'
import { import {
DeleteOutlined, DeleteOutlined,
LoadingOutlined,
ProfileOutlined, ProfileOutlined,
ReloadOutlined, ReloadOutlined,
SearchOutlined, SearchOutlined,
VerticalAlignBottomOutlined VerticalAlignBottomOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Button, Input, message, Modal, Pagination, Progress, Table } from 'antd'; import { Button, Input, message, Modal, Pagination, 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 { DeleteRoomFile, GetRoomFile, GetRoomFileDwUrl, GetRoomUpFileurl, GetRoomUserItem, PostRoomFile } from '@/api/Meeting';
import axios from 'axios'; import axios from 'axios';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
@ -100,10 +101,10 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
</div> </div>
<div style={{ color: 'white' }}> <div style={{ color: 'white' }}>
<Input <Input
placeholder="搜索" placeholder="请输入文件名"
style={{ width: '200px' }} style={{ width: '200px' }}
prefix={<SearchOutlined style={{ color: 'white' }} />} prefix={<SearchOutlined style={{ color: 'white' }} />}
onChange={(e) => { onPressEnter={(e: any) => {
if (fileList.pageIndex === 1) { if (fileList.pageIndex === 1) {
setFileList({ setFileList({
...fileList, ...fileList,
@ -116,8 +117,30 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
pageIndex: 1 pageIndex: 1
}) })
} }
}} }}
onBlur={(e) => {
if (fileList.pageIndex === 1) {
setFileList({
...fileList,
keyword: e.target.value,
})
} else {
setFileList({
...fileList,
keyword: e.target.value,
pageIndex: 1
})
}
}}
suffix={
<span
style={{ color: '#47D3D0', cursor: 'pointer' }}
onClick={() => {
getRoomFile()
}}
>
</span>
}
/> />
<ReloadOutlined title='刷新' onClick={() => { <ReloadOutlined title='刷新' onClick={() => {
if (fileList.pageIndex === 1) { if (fileList.pageIndex === 1) {
@ -129,10 +152,14 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
}) })
} }
}} /> }} />
{roomUserItem && role.ID.includes(roomUserItem.roleId) || roomUserItem.isRoomManager ? <ProfileOutlined title={showRowSelection ? '取消框选' : '显示框选'} onClick={() => { {roomUserItem && (role.ID.includes(roomUserItem.roleId) || roomUserItem.isRoomManager) && fileList.data.length ? <ProfileOutlined title={showRowSelection ? '取消框选' : '显示框选'} onClick={() => {
setShowRowSelection(!showRowSelection) setShowRowSelection(!showRowSelection)
}} style={{ color: showRowSelection ? '#5575F2' : 'white' }} /> : null} }} style={{ color: showRowSelection ? '#5575F2' : 'white' }} /> : null}
{showRowSelection ? <DeleteOutlined title='删除' onClick={() => { {showRowSelection && fileList.data.length && selectedRowKeys.length ?
<Popconfirm
title="提示"
description="确认删除吗?"
onConfirm={() => {
if (selectedRowKeys.length) { if (selectedRowKeys.length) {
DeleteRoomFile(selectedRowKeys).then(res => { DeleteRoomFile(selectedRowKeys).then(res => {
if (res.code === 200) { if (res.code === 200) {
@ -150,8 +177,16 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
} else { } else {
message.error('请选择文件!') message.error('请选择文件!')
} }
}} /> : null} }}
<Button type="primary" style={{ backgroundColor: '#31353A' }} onCancel={() => {
}}
okText="确认"
cancelText="取消"
>
<DeleteOutlined title='删除' />
</Popconfirm> : null}
{roomUserItem && role.ID.includes(roomUserItem.roleId) || roomUserItem.isRoomManager ? <Button type="primary" style={{ backgroundColor: '#31353A' }}
onClick={() => { onClick={() => {
if (isUpFile) { if (isUpFile) {
message.error('文件上传中,请稍后上传。') message.error('文件上传中,请稍后上传。')
@ -211,7 +246,7 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
}; };
file.click(); file.click();
}} }}
></Button> ></Button> : null}
</div> </div>
</div> </div>
<div> <div>
@ -258,9 +293,10 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
/> />
<Column title="操作" render={(item) => ( <Column title="操作" render={(item) => (
<> <>
<VerticalAlignBottomOutlined title='下载' {!item.showPercentComplete ? <VerticalAlignBottomOutlined title='下载'
style={{ color: '#5575F2', cursor: 'pointer' }} style={{ color: '#5575F2', cursor: 'pointer' }}
onClick={async () => { onClick={async () => {
storage.setItem('loading', true)
GetRoomFileDwUrl(item.fileUrl, item.id).then(res => { GetRoomFileDwUrl(item.fileUrl, item.id).then(res => {
if (res.code === 200) { if (res.code === 200) {
axios({ axios({
@ -329,8 +365,10 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
}, 2000) }, 2000)
}) })
} }
}).finally(() => {
storage.setItem('loading', false)
}) })
}} /> }} /> : <LoadingOutlined />}
{/* <FolderOutlined title='文件' style={{ color: '#FFA000', cursor: 'pointer', marginLeft: '10px' }} /> */} {/* <FolderOutlined title='文件' style={{ color: '#FFA000', cursor: 'pointer', marginLeft: '10px' }} /> */}
</> </>
)} /> )} />
@ -355,4 +393,4 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
) )
}) })
export default SharedFilesModel export default memo(SharedFilesModel)

View File

@ -2,13 +2,14 @@ import { GetRoomSingnIn, PostRoomSingnIn } from '@/api/Meeting';
import styles from '@/components/SingIn/index.module.scss' import styles from '@/components/SingIn/index.module.scss'
import { storage } from '@/utils'; import { storage } from '@/utils';
import { Button, message, Modal } from 'antd'; 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) => { const SingIn = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeModal: () => { changeModal: () => {
getRoomSingnIn() getRoomSingnIn()
}, },
getModal: () => { getModal: () => {
setIsMessage(true)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setSingInModal(bool => { setSingInModal(bool => {
resolve(bool) resolve(bool)
@ -18,12 +19,20 @@ const SingIn = forwardRef((props: any, ref: any) => {
}, },
})) }))
const [singInModal, setSingInModal] = useState(false); const [singInModal, setSingInModal] = useState(false);
const [isMessage, setIsMessage] = useState(false);
const [singInList, setSingInList] = useState([]); const [singInList, setSingInList] = useState([]);
const getRoomSingnIn = async (): Promise<void> => { const getRoomSingnIn = async (): Promise<void> => {
await GetRoomSingnIn().then(res => { await GetRoomSingnIn().then(res => {
if (res.code === 200) { if (res.code === 200) {
if (res.data.length) { if (res.data.length) {
setSingInModal(true) setSingInModal(true)
} else {
setIsMessage(bool => {
if (!bool) {
message.error('暂未绑定签到人,请联系管理员添加后签到!')
}
return false
})
} }
setSingInList(res.data.map((item: any) => { setSingInList(res.data.map((item: any) => {
return { return {
@ -76,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 styles from '@/components/SpeakerModeModal/index.module.scss'
import { Checkbox, Modal } from 'antd'; import { Checkbox, Modal } from 'antd';
import { useState, useImperativeHandle, forwardRef } from "react"; import { useState, useImperativeHandle, forwardRef, memo } from "react";
import { storage } from '@/utils'; import { storage } from '@/utils';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { GetSyncView } from '@/api/Meeting'; 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 styles from '@/components/StupWizard/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl'; import ImageUrl from '@/utils/package/imageUrl';
import { Button, Checkbox, Empty, Input, message, Modal, Popover, Radio, Select, Slider, Space } from 'antd'; import { Button, Checkbox, Empty, Input, message, Modal, Popover, Radio, Select, Slider, Space } from 'antd';
import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; import { forwardRef, useEffect, useImperativeHandle, useState, memo } from "react";
import { agora } from '@/utils/package/agora' import { agora } from '@/utils/package/agora'
import { CloseOutlined, LoadingOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import { CloseOutlined, LoadingOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { storage } from '@/utils'; import { storage } from '@/utils';
@ -11,6 +11,7 @@ import { storageSeeting } from '@/utils/package/public';
let meetingUserInfo = '' as any; let meetingUserInfo = '' as any;
const fs = require('fs').promises; const fs = require('fs').promises;
const { exec } = require('child_process'); const { exec } = require('child_process');
let c = +new Date();
const StupWizard = forwardRef((_props: any, ref: any) => { const StupWizard = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeModal: (index: number = 0, data: any) => { changeModal: (index: number = 0, data: any) => {
@ -33,6 +34,9 @@ const StupWizard = forwardRef((_props: any, ref: any) => {
} }
} }
storage.setItem('setting', JSON.stringify(setting)) storage.setItem('setting', JSON.stringify(setting))
},
getStupWizardModal: () => {
return isStupWizard
} }
})) }))
const [list, setList] = useState([ const [list, setList] = useState([
@ -84,6 +88,8 @@ const StupWizard = forwardRef((_props: any, ref: any) => {
{list.map((row: any, index: number) => { {list.map((row: any, index: number) => {
return ( return (
<div key={index} className={`${row.active ? styles.active : ''}`} onClick={async () => { <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]; const newList = [...list];
newList.forEach(item => item.active = false); newList.forEach(item => item.active = false);
newList[index].active = true; newList[index].active = true;
@ -106,9 +112,11 @@ const StupWizard = forwardRef((_props: any, ref: any) => {
cursor: 'pointer' cursor: 'pointer'
}} }}
onClick={async () => { onClick={async () => {
const userInfo = JSON.parse(storage.getItem('user') as string)
if (location.hash.indexOf('/meeting') === -1) { if (location.hash.indexOf('/meeting') === -1) {
agora.release() agora.release()
} }
await agora.destroyRendererByConfigPreview(Number(userInfo.screenShareId), c)
setIsStupWizard(false) setIsStupWizard(false)
}} }}
/> />
@ -125,9 +133,11 @@ const StupWizard = forwardRef((_props: any, ref: any) => {
}) })
const CurrencyComponents = () => { const CurrencyComponents = () => {
const [optionsValue, setOperationValue] = useState<'hide' | 'quit'>('hide'); const [optionsValue, setOperationValue] = useState<'hide' | 'quit'>('hide');
const [voiceStimulation, setVoiceStimulation] = useState(true);
const setting = JSON.parse(storage.getItem('setting') as string) const setting = JSON.parse(storage.getItem('setting') as string)
useEffect(() => { useEffect(() => {
setOperationValue(setting.closeSetting) setOperationValue(setting.closeSetting)
setVoiceStimulation(setting.voiceStimulation)
}, []); }, []);
return ( return (
<> <>
@ -146,6 +156,33 @@ const CurrencyComponents = () => {
<Radio value={'hide'}>退</Radio> <Radio value={'hide'}>退</Radio>
</Radio.Group> </Radio.Group>
</div> </div>
<div>
<span> <Popover
content={
<span
style={{
color: 'white'
}}>
</span>
}
title=""
>
<QuestionCircleOutlined style={{
color: 'white',
cursor: 'pointer',
marginRight: '10px'
}} />
</Popover></span>
<Radio.Group onChange={(e: any) => {
setting.voiceStimulation = e.target.value;
storage.setItem('setting', JSON.stringify(setting))
setVoiceStimulation(e.target.value)
}} style={{ flexShrink: 0, margin: '10px 0' }} value={voiceStimulation}>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -208,7 +245,8 @@ const VideoComponents = () => {
}, [darkLightEnhancement]); }, [darkLightEnhancement]);
useEffect(() => { useEffect(() => {
if (typeof virtualBackground.sourceIndex === 'number') { if (typeof virtualBackground.sourceIndex === 'number') {
if (import.meta.env.VITE_ENV === 'development') { window.electron.getEnv().then(res=>{
if (res === 'development') {
window.electron.getAppPath().then((res: string) => { window.electron.getAppPath().then((res: string) => {
const imagePath = path.join(res, 'src', 'assets', 'virtualBackground', `${virtualBackground.sourceIndex + 1}.png`); const imagePath = path.join(res, 'src', 'assets', 'virtualBackground', `${virtualBackground.sourceIndex + 1}.png`);
agora.enableVirtualBackground(virtualBackground.isVirtualBackground, { agora.enableVirtualBackground(virtualBackground.isVirtualBackground, {
@ -225,6 +263,7 @@ const VideoComponents = () => {
color: Number(virtualBackground.color), color: Number(virtualBackground.color),
}) })
} }
})
} else { } else {
agora.enableVirtualBackground(virtualBackground.isVirtualBackground, { agora.enableVirtualBackground(virtualBackground.isVirtualBackground, {
background_source_type: 1, background_source_type: 1,
@ -247,7 +286,7 @@ const VideoComponents = () => {
}) })
if (setting.videoDeviceId && list.length) { if (setting.videoDeviceId && list.length) {
await agora.setVideoDeviceManager(setting.videoDeviceId) 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) agora.setVideoDeviceManager(e)
if (!setting.videoDeviceId) { if (!setting.videoDeviceId) {
const userInfo = JSON.parse(storage.getItem('user') as string) 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; setting.videoDeviceId = e;
storage.setItem('setting', JSON.stringify(setting)) storage.setItem('setting', JSON.stringify(setting))
@ -691,7 +730,18 @@ const AudioComponents = () => {
ecordingVolume: e, ecordingVolume: e,
}) })
}} disabled={!audioDeviceManager.ecordingItem} /> }} disabled={!audioDeviceManager.ecordingItem} />
{/* || audioDeviceManager.autoEcordingVolume */}
</div> </div>
{/* <div style={{ marginBottom: '10px' }}>
<Checkbox onChange={async (e) => {
setting.autoEcordingVolume = e.target.checked;
storage.setItem('setting', JSON.stringify(setting))
setAudioDeviceManager({
...audioDeviceManager,
autoEcordingVolume: e.target.checked
})
}} checked={audioDeviceManager.autoEcordingVolume}></Checkbox>
</div> */}
<div> <div>
<div> <div>
<Checkbox checked={audioDeviceManager.isAINoiseReduction} onChange={(e) => { <Checkbox checked={audioDeviceManager.isAINoiseReduction} onChange={(e) => {
@ -724,16 +774,6 @@ const AudioComponents = () => {
</Radio.Group> </Radio.Group>
</div> </div>
</div> </div>
{/* <div>
<Checkbox onChange={async (e) => {
setting.autoEcordingVolume = e.target.checked;
storage.setItem('setting', JSON.stringify(setting))
setAudioDeviceManager({
...audioDeviceManager,
autoEcordingVolume: e.target.checked
})
}} checked={audioDeviceManager.autoEcordingVolume}></Checkbox>
</div> */}
</div> </div>
<div> <div>
<div className={styles.audioComponentsSelect}> <div className={styles.audioComponentsSelect}>
@ -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 '@/components/TldrawView/index.scss'
import { import {
Tldraw, 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 styles from '@/components/UpdateModal/index.module.scss'
import ImageUrl from '@/utils/package/imageUrl'; import ImageUrl from '@/utils/package/imageUrl';
import { getUpdateUrl } from '@/utils/package/public'; import { getUpdateUrl, isVersion } from '@/utils/package/public';
import { ExclamationCircleFilled } from '@ant-design/icons';
import { Button, Flex, Modal, Progress } from 'antd'; import { Button, Flex, Modal, Progress } from 'antd';
import { forwardRef, useImperativeHandle, useState } from "react"; import { forwardRef, useImperativeHandle, useState, memo, useEffect } from "react";
const { confirm } = Modal;
const UpdateModal = forwardRef((props: any, ref: any) => { const UpdateModal = forwardRef((_props: any, ref: any) => {
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
changeModal: (data: any) => { changeModal: (data: any) => {
if (JSON.stringify(data).includes('Error') || JSON.stringify(data).includes('{}')) {
setIsError(res => {
if (res) {
setIsError(false)
confirm({
keyboard: false,
title: '提示',
icon: <ExclamationCircleFilled />,
content: `更新失败,请尝试手动更新!`,
centered: true,
okText: '立即更新',
wrapClassName: 'hideCancelText',
cancelText: '',
async onOk() {
isVersion((bool: boolean, req: any) => {
if (bool && req) {
window.electron.getEnv().then(res => {
location.href = `${getUpdateUrl(res)}/${req.path}`
})
}
})
},
})
}
return res
})
} else {
try {
let dataJson = JSON.parse(data) let dataJson = JSON.parse(data)
getContent()
if (dataJson.type === '0') { // 打开弹窗 if (dataJson.type === '0') { // 打开弹窗
setIsUpdateModal(true) setProgress(res => {
if (res) {
} else {
window.electron.onDownload('1')
}
return res
})
} else if (dataJson.type === '1') { // 下载中 返回进度值 } else if (dataJson.type === '1') { // 下载中 返回进度值
if (dataJson.value === 100 && location.hash.indexOf('/meeting') === -1) {
setIsUpdateModal(true)
getContent()
}
setProgress(dataJson.value.toFixed(2)) setProgress(dataJson.value.toFixed(2))
} else if (dataJson.type === '2') { // 下载完成 } else if (dataJson.type === '2') { // 下载完成
setProgress(100) setProgress(100)
if (location.hash.indexOf('/meeting') === -1) {
setIsUpdateModal(true)
getContent()
}
} else if (dataJson.type === '3') {
if (location.hash.indexOf('/meeting') === -1) {
setIsUpdateModal(true)
getContent()
}
}
} catch (error) {
}
} }
} }
})) }))
const [isUpdateModal, setIsUpdateModal] = useState(false); const [isUpdateModal, setIsUpdateModal] = useState(false);
const [progress, setProgress] = useState(0); // 下载进度值 const [progress, setProgress] = useState(0); // 下载进度值
const [updateContent, setUpdateContent] = useState('') // 版本更新内容 const [updateContent, setUpdateContent] = useState('') // 版本更新内容
const [env, setEnv] = useState('')
const [_isError, setIsError] = useState(true)
useEffect(() => {
window.electron.getEnv().then(res => {
setEnv(res)
})
}, [])
function getContent() { function getContent() {
fetch(`${getUpdateUrl(import.meta.env.VITE_ENV)}/update.txt?t=${+new Date()}`) // 配置服务器地址 window.electron.getEnv().then(res => {
fetch(`${getUpdateUrl(res)}/update.txt?t=${+new Date()}`) // 配置服务器地址
.then(async response => { .then(async response => {
if (response.status === 200) { if (response.status === 200) {
return setUpdateContent(await response.text()) return setUpdateContent(await response.text())
} }
throw new Error('Network response was not ok.'); throw new Error('Network response was not ok.');
}) })
.then(textContent => { .then(_textContent => {
}) })
.catch(error => { .catch(_error => {
}); });
})
} }
function closeModal() { function closeModal() {
if (progress != 100) { // if (progress != 100) {
window.electron.onDownload('0') // 取消下载 // window.electron.onDownload('0') // 取消下载
} // }
setIsUpdateModal(false) setIsUpdateModal(false)
} }
@ -49,12 +110,12 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
title="" title=""
open={isUpdateModal} open={isUpdateModal}
footer={null} footer={null}
// onCancel={() => closeModal()} onCancel={() => closeModal()}
centered centered
width={'400px'} width={'400px'}
className='modal-padding' className='modal-padding'
maskClosable={false} maskClosable={false}
closeIcon={false} closeIcon={progress === 100 ? false : true}
> >
<div className={styles.isUpdateModal} style={{ backgroundImage: `url(${ImageUrl.icon7})` }}> <div className={styles.isUpdateModal} style={{ backgroundImage: `url(${ImageUrl.icon7})` }}>
<div className={styles.remarks} dangerouslySetInnerHTML={{ __html: updateContent }}> <div className={styles.remarks} dangerouslySetInnerHTML={{ __html: updateContent }}>
@ -67,7 +128,7 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
style={{ width: '100%', height: '40px', marginBottom: '10px' }} style={{ width: '100%', height: '40px', marginBottom: '10px' }}
className={`m-ant-btn`} className={`m-ant-btn`}
></Button> ></Button>
{import.meta.env.VITE_ENV === "development" ? <div className={styles.button2} onClick={() => setIsUpdateModal(false)}></div> : null} {env === "development" ? <div className={styles.button2} onClick={() => setIsUpdateModal(false)}></div> : null}
</div> : progress < 100 ? </div> : progress < 100 ?
<div style={{ margin: '20px 0' }}> <div style={{ margin: '20px 0' }}>
{progress}% {progress}%
@ -87,4 +148,4 @@ const UpdateModal = forwardRef((props: any, ref: any) => {
) )
}) })
export default UpdateModal export default memo(UpdateModal)

View File

@ -0,0 +1,8 @@
.userNameModal {
.userNameModalFooter {
margin-top: 10px;
display: flex;
justify-content: center;
}
}

View File

@ -0,0 +1,78 @@
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, memo } from "react";
const UserName = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
changeModal: (data: any) => {
setInfo(data)
setUserNameModal(true)
}
}))
const [userNameModal, setUserNameModal] = useState(false);
const [info, setInfo] = useState({
userName: '',
uid: ''
});
return (
<>
<Modal
title="修改用户名"
open={userNameModal}
footer={null}
destroyOnClose={true}
onCancel={() => setUserNameModal(false)}
centered
width={'300px'}
>
<div className={styles.userNameModal}>
<div className={styles.userNameModalContent}>
<Input
placeholder="请输入用户名称"
style={{ width: '100%' }}
value={info.userName}
onChange={(e) => {
setInfo({
...info,
userName: e.target.value
})
}}
/>
</div>
<div className={styles.userNameModalFooter}>
<Button type="primary" style={{ backgroundColor: 'rgb(16,20,24)', marginRight: '14px' }}
onClick={() => setUserNameModal(false)}></Button>
<Button type="primary"
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,
uid: info.uid,
}).then((res) => {
if (res.code === 200) {
message.success('修改成功')
setUserNameModal(false)
}
})
} else {
message.error('请输入用户名')
}
}}
className='m-ant-btn'>
</Button>
</div>
</div>
</Modal>
</>
)
})
export default memo(UserName)

View File

@ -65,8 +65,8 @@
height: 0; height: 0;
.userVideoContentListItem { .userVideoContentListItem {
height: 32%; height: calc(100% / 6 - 8px);
width: calc(100% / 4 - 8px); width: calc(100% / 2 - 8px);
padding: 4px; padding: 4px;
.userVideoContentListItemVideo { .userVideoContentListItemVideo {

View File

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

View File

@ -108,14 +108,20 @@
>div:nth-child(1) { >div:nth-child(1) {
display: flex; display: flex;
align-items: center; align-items: center;
>div:nth-child(1) {
margin-right: 6px;
display: flex;
align-items: center;
cursor: pointer; cursor: pointer;
>span { >span {
color: #767676; color: #767676;
margin-right: 4px; margin-right: 6px;
font-size: 14px; font-size: 14px;
} }
} }
}
>div:nth-child(2) { >div:nth-child(2) {
display: flex; display: flex;

View File

@ -1,24 +1,31 @@
import styles from '@/page/Home/Index/index.module.scss' import styles from '@/page/Home/Index/index.module.scss'
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import Operation from '@/components/Operation'; import Operation from '@/components/Operation';
import { Button, Input, Modal, Pagination, Empty, message, Popover, Popconfirm, DatePicker, Select } from "antd"; import { Button, Input, Modal, Pagination, Empty, message, Popover, Popconfirm, DatePicker, Select, Radio } from "antd";
import { GetRoom, PostRoom, GetCheckoutRoomNum, GetRoomRtcToken, DeleteRoom, GetRecord, PostRoomInfo } from '@/api/Home/Index'; import { GetRoom, PostRoom, GetCheckoutRoomNum, GetRoomRtcToken, DeleteRoom, GetRecord, PostRoomInfo } from '@/api/Home/Index';
import ImageUrl from '@/utils/package/imageUrl' import ImageUrl from '@/utils/package/imageUrl'
import { ExclamationCircleFilled, ReloadOutlined } from '@ant-design/icons'; import { ExclamationCircleFilled, ReloadOutlined } from '@ant-design/icons';
import JoinSetting from '@/components/JoinSetting'; import JoinSetting from '@/components/JoinSetting';
import { storage } from '@/utils'; import { storage } from '@/utils';
import { PostRefresh } from '@/api/Login'; import { PostRefresh } from '@/api/Login';
import { useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { role } from '@/config/role'; import { role } from '@/config/role';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import StupWizard from '@/components/StupWizard'; import StupWizard from '@/components/StupWizard';
import { GetSubDpList } from '@/api/Home/User'; import { GetSubDpList } from '@/api/Home/User';
import FeedBackModel from '@/components/FeedBackModel';
import { 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 fs = require('fs').promises;
const { exec } = require('child_process'); const { exec } = require('child_process');
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
const { confirm } = Modal; const { confirm } = Modal;
const Index: React.FC = () => { const Index: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { state } = useLocation();
const [list, setList] = useState({ const [list, setList] = useState({
data: [], data: [],
total: 0, total: 0,
@ -36,14 +43,19 @@ const Index: React.FC = () => {
}) })
const joinSettingRef = useRef<any>(); const joinSettingRef = useRef<any>();
const stupWizardRef = useRef<any>(); const stupWizardRef = useRef<any>();
const feedBackModelRef = useRef<any>();
const [user, setUser] = useState<any>({}); const [user, setUser] = useState<any>({});
const [currentRoomInfo, setCurrentRoomInfo] = useState<any>({}); const [currentRoomInfo, setCurrentRoomInfo] = useState<any>({});
const [subjectList, setSubjectList] = useState<any>([]); const [subjectList, setSubjectList] = useState<any>([]);
const [timeData, setTimeData] = useState<any>([]); const [timeData, setTimeData] = useState<any>([]);
const [isCreateRoom, setIsCreateRoom] = useState<boolean>(false); const [isCreateRoom, setIsCreateRoom] = useState<boolean>(false);
const [allowAnonymous, setAllowAnonymous] = useState(true);
const userInfo = JSON.parse(storage.getItem('user') as string) const userInfo = JSON.parse(storage.getItem('user') as string)
useEffect(() => { useEffect(() => {
setUser(userInfo) setUser(userInfo)
if (state?.currentSeconds >= 600) {
feedBackModelRef.current.changeModal()
}
}, []) }, [])
useEffect(() => { useEffect(() => {
let time = null as any let time = null as any
@ -230,11 +242,14 @@ const Index: React.FC = () => {
<span>{item.onlineUserCount}</span> <span>{item.onlineUserCount}</span>
</div> </div>
</div> </div>
<div>
<div> <div>
<div onClick={() => copyRoomNum(item.roomNum)} title='复制房间号'> <div onClick={() => copyRoomNum(item.roomNum)} title='复制房间号'>
<span>{item.roomNum}</span> <span>{item.roomNum}</span>
<img src={ImageUrl.icon10} alt="" /> <img src={ImageUrl.icon10} alt="" />
</div> </div>
<Code roomNum={item.roomNum}></Code>
</div>
<div> <div>
{role.ID.includes(userInfo.roleId) ? <Popover {role.ID.includes(userInfo.roleId) ? <Popover
content={ content={
@ -272,6 +287,7 @@ const Index: React.FC = () => {
year: item.year, year: item.year,
id: item.id, id: item.id,
}) })
setAllowAnonymous(item.allowAnonymous)
getSubDpList() getSubDpList()
setIsCreateRoom(false) setIsCreateRoom(false)
setCreateRoomModal(true) setCreateRoomModal(true)
@ -294,13 +310,26 @@ const Index: React.FC = () => {
<Button type="primary" <Button type="primary"
iconPosition={'end'} iconPosition={'end'}
onClick={async () => { onClick={async () => {
storage.setItem('loading', true)
isVersion((bool: boolean) => {
storage.setItem('loading', false)
if (bool) {
window.electron.onDownload('3')
} else {
if (role.ID.includes(userInfo.roleId)) { if (role.ID.includes(userInfo.roleId)) {
joinSettingRef.current.changeModal(item.roomNum) joinSettingRef.current.changeModal(item.roomNum)
} else { } else {
storage.setItem('loading', true) storage.setItem('loading', true)
postRefresh(() => { postRefresh(() => {
getRoomRtcToken(item.roomNum, (options: any) => { getRoomRtcToken(item.roomNum, async (options: any) => {
if (options) { if (options) {
await window.electron.getVersion().then(async req => {
await PostHomeVerLog({
version: req,
platformType: 1,
roomNum: item.roomNum,
})
})
navigate(`/meeting`, { navigate(`/meeting`, {
state: { state: {
channelId: item.roomNum, channelId: item.roomNum,
@ -316,6 +345,8 @@ const Index: React.FC = () => {
}) })
}) })
} }
}
})
}} }}
icon={<img src={ImageUrl.icon9} alt="" />} icon={<img src={ImageUrl.icon9} alt="" />}
className='m-ant-btn'> className='m-ant-btn'>
@ -352,7 +383,6 @@ const Index: React.FC = () => {
placeholder="请输入房间号" placeholder="请输入房间号"
style={{ flexGrow: 1 }} style={{ flexGrow: 1 }}
className={styles.letterSpacing} className={styles.letterSpacing}
showCount
maxLength={8} maxLength={8}
value={createRoomFrom.roomNum} value={createRoomFrom.roomNum}
onChange={(e) => { onChange={(e) => {
@ -389,7 +419,6 @@ const Index: React.FC = () => {
<Input.TextArea <Input.TextArea
placeholder="请输入房间名字" placeholder="请输入房间名字"
style={{ flexGrow: 1 }} style={{ flexGrow: 1 }}
showCount
maxLength={30} maxLength={30}
value={createRoomFrom.roomName} value={createRoomFrom.roomName}
onChange={(e) => { onChange={(e) => {
@ -404,6 +433,7 @@ const Index: React.FC = () => {
<span></span> <span></span>
<Input <Input
placeholder="请输入届" placeholder="请输入届"
maxLength={4}
style={{ flexGrow: 1 }} style={{ flexGrow: 1 }}
value={createRoomFrom.year} value={createRoomFrom.year}
onChange={(e) => { onChange={(e) => {
@ -430,6 +460,15 @@ const Index: React.FC = () => {
}) })
}} /> }} />
</div> </div>
<div>
<span></span>
<Radio.Group onChange={(e) => {
setAllowAnonymous(e.target.value);
}} value={allowAnonymous}>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
</div>
</div> </div>
<div style={{ <div style={{
display: 'flex', justifyContent: 'center' display: 'flex', justifyContent: 'center'
@ -442,7 +481,7 @@ const Index: React.FC = () => {
if (!createRoomFrom.roomNum) { if (!createRoomFrom.roomNum) {
return message.error('请输入房间号!') return message.error('请输入房间号!')
} }
if (!createRoomFrom.year) { if (createRoomFrom.year === "") {
return message.error('请输入届!') return message.error('请输入届!')
} }
if (isCreateRoom) { if (isCreateRoom) {
@ -450,20 +489,22 @@ const Index: React.FC = () => {
if (bool) { if (bool) {
message.error('房间号已存在!') message.error('房间号已存在!')
} else { } else {
PostRoom(createRoomFrom).then(res => { PostRoom({ ...createRoomFrom, allowAnonymous }).then(res => {
if (res.code === 200) { if (res.code === 200) {
message.success('创建成功!') message.success('创建成功!')
setCreateRoomModal(false) setCreateRoomModal(false)
setAllowAnonymous(true)
getRoomList() getRoomList()
} }
}) })
} }
}) })
} else { } else {
PostRoomInfo(createRoomFrom).then(res => { PostRoomInfo({ ...createRoomFrom, allowAnonymous }).then(res => {
if (res.code === 200) { if (res.code === 200) {
message.success('更新成功!') message.success('更新成功!')
setCreateRoomModal(false) setCreateRoomModal(false)
setAllowAnonymous(true)
getRoomList() getRoomList()
} }
}) })
@ -506,6 +547,7 @@ const Index: React.FC = () => {
</Modal> </Modal>
<JoinSetting ref={joinSettingRef} /> <JoinSetting ref={joinSettingRef} />
<StupWizard ref={stupWizardRef} /> <StupWizard ref={stupWizardRef} />
<FeedBackModel ref={feedBackModelRef} />
</> </>
) )
} }

View File

@ -415,6 +415,7 @@ const User: React.FC = () => {
<Input <Input
placeholder="请输入届" placeholder="请输入届"
value={addUserFrom.year} value={addUserFrom.year}
maxLength={4}
onChange={(e) => { onChange={(e) => {
const regex = /^[0-9]*$/; const regex = /^[0-9]*$/;
if (regex.test(e.target.value)) { if (regex.test(e.target.value)) {
@ -457,6 +458,9 @@ const User: React.FC = () => {
if (!addUserFrom.UserName && isCreateUser !== 'batch') { if (!addUserFrom.UserName && isCreateUser !== 'batch') {
return message.error('请输入用户名称!') return message.error('请输入用户名称!')
} }
if (addUserFrom.UserName.length > 10) {
return message.error('用户名称最多10个字')
}
if (addUserFrom.year === '') { if (addUserFrom.year === '') {
return message.error('请输入届!') return message.error('请输入届!')
} }
@ -602,7 +606,7 @@ const User: React.FC = () => {
onClick={() => { onClick={() => {
const file = document.createElement("input") as any; const file = document.createElement("input") as any;
file.type = "file"; file.type = "file";
// file.accept = ".xls,.xlsx"; file.accept = ".xls,.xlsx";
file.onchange = async () => { file.onchange = async () => {
const setting = await JSON.parse(storage.getItem('setting') as string) const setting = await JSON.parse(storage.getItem('setting') as string)
const fileInfo = file.files[0]; const fileInfo = file.files[0];
@ -669,7 +673,7 @@ const User: React.FC = () => {
onClick={() => { onClick={() => {
const file = document.createElement("input") as any; const file = document.createElement("input") as any;
file.type = "file"; file.type = "file";
// file.accept = ".xls,.xlsx"; file.accept = ".xls,.xlsx";
file.onchange = async () => { file.onchange = async () => {
const setting = await JSON.parse(storage.getItem('setting') as string) const setting = await JSON.parse(storage.getItem('setting') as string)
const fileInfo = file.files[0]; const fileInfo = file.files[0];
@ -762,12 +766,17 @@ const User: React.FC = () => {
<Button type="primary" style={{ backgroundColor: '#31353A', marginRight: '14px' }} <Button type="primary" style={{ backgroundColor: '#31353A', marginRight: '14px' }}
onClick={() => setSignInUserModal(false)}></Button> onClick={() => setSignInUserModal(false)}></Button>
<Button type="primary" className='m-ant-btn' onClick={() => { <Button type="primary" className='m-ant-btn' onClick={() => {
let signInNameNull = signInUserForm.currentUserList.find((item: any) => !item.signInName)
if (signInNameNull) {
message.error('请输入用户名称');
} else {
PutSigns(signInUserForm.uid, signInUserForm.currentUserList).then(res => { PutSigns(signInUserForm.uid, signInUserForm.currentUserList).then(res => {
if (res.code === 200) { if (res.code === 200) {
message.success('签到绑定成功') message.success('签到绑定成功')
setSignInUserModal(false) setSignInUserModal(false)
} }
}) })
}
}}></Button> }}></Button>
</div> </div>
</div> </div>

View File

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

View File

@ -1,13 +1,14 @@
import styles from '@/page/Home/index.module.scss' import styles from '@/page/Home/index.module.scss'
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import { Outlet, useNavigate } from 'react-router-dom'; import { Outlet, useNavigate } from 'react-router-dom';
import { Popconfirm } from 'antd'; import { Button, Popconfirm, Popover } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn' import 'dayjs/locale/zh-cn'
import { storage } from '@/utils'; import { storage } from '@/utils';
import ImageUrl from '@/utils/package/imageUrl' import ImageUrl from '@/utils/package/imageUrl'
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import StupWizard from '@/components/StupWizard'; import StupWizard from '@/components/StupWizard';
const { setInterval, clearInterval } = require('timers');
dayjs.locale('zh-cn'); dayjs.locale('zh-cn');
type navListType = { type navListType = {
title: string; title: string;
@ -44,6 +45,7 @@ const Home: React.FC = () => {
]); ]);
const [userInfo, setUserInfo] = useState<any>({}) const [userInfo, setUserInfo] = useState<any>({})
const [version, setVersion] = useState<string>('') const [version, setVersion] = useState<string>('')
const [update, setUpdate] = useState(false)
const [dateInfo, setDateInfo] = useState<{ const [dateInfo, setDateInfo] = useState<{
work: string; work: string;
time: string; time: string;
@ -66,11 +68,18 @@ const Home: React.FC = () => {
}) })
}; };
const timer = setInterval(updateTime, 1000); const timer = setInterval(updateTime, 1000);
window.addEventListener('customStorageChange', handleCustomStorageChange);
return () => { return () => {
window.removeEventListener('customStorageChange', handleCustomStorageChange);
clearInterval(timer); clearInterval(timer);
}; };
}, []); }, []);
const handleCustomStorageChange = (e: any): void => {
if (e.key === 'isUpdate') {
setUpdate(e.value)
}
};
const changtNavList = (index: number, bool?: boolean): void => { const changtNavList = (index: number, bool?: boolean): void => {
const newNavList = [...navList]; const newNavList = [...navList];
if (typeof bool === 'boolean') { if (typeof bool === 'boolean') {
@ -125,10 +134,29 @@ const Home: React.FC = () => {
) )
})} })}
</div> </div>
<div className='drag'>
<div> <div>
{version} <span>:{version}</span>
{update ? <span>new</span> : null}
</div>
{update ? <div>
<Button type="primary" className='m-ant-btn' style={{ width: '100%' }} onClick={() => {
window.electron.onDownload('3')
}}></Button>
</div> : null}
</div> </div>
<div> <div>
<Popover
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={() => { <div className='drag' title='设置' onClick={() => {
stupWizardRef.current.changeModal() stupWizardRef.current.changeModal()
}}> }}>

View File

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

View File

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

View File

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

View File

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

View File

@ -21,7 +21,10 @@ const NoticeWindow: React.FC = () => {
api.open({ api.open({
message: '', message: '',
description: <div> description: <div>
<span style={{ fontSize: '16px' }}>{noticeItem.uname}</span> <div style={{ fontSize: '16px' }}>
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={noticeItem.uname}>{noticeItem.uname}</div>
<div></div>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end' }} className='drag'> <div style={{ display: 'flex', justifyContent: 'flex-end' }} className='drag'>
<Button <Button
type="primary" type="primary"
@ -68,6 +71,7 @@ const NoticeWindow: React.FC = () => {
</div> </div>
</div>, </div>,
onClose: () => { onClose: () => {
setTimeout(() => {
const dom = document.getElementsByClassName('ant-notification') const dom = document.getElementsByClassName('ant-notification')
if (dom.length === 0) { if (dom.length === 0) {
window.electron.setChildWindowShow({ window.electron.setChildWindowShow({
@ -75,6 +79,7 @@ const NoticeWindow: React.FC = () => {
bool: false bool: false
}) })
} }
}, 500);
}, },
duration: 10, duration: 10,
placement: 'bottomRight', placement: 'bottomRight',

View File

@ -4,9 +4,11 @@ import styles from '@/page/Meeting/ShareScreenWindow/index.module.scss'
import { storage } from '@/utils'; import { storage } from '@/utils';
import ImageUrl from '@/utils/package/imageUrl'; import ImageUrl from '@/utils/package/imageUrl';
import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons'; import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons';
import { RtcStats } from 'agora-electron-sdk';
import { Button } from 'antd'; import { Button } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
const { setInterval, clearInterval } = require('timers');
const ShareScreenWindow: React.FC = () => { const ShareScreenWindow: React.FC = () => {
const [footerLists, setFooterLists] = useState<any>([ const [footerLists, setFooterLists] = useState<any>([
{ {
@ -49,11 +51,22 @@ const ShareScreenWindow: React.FC = () => {
const [timeStr, setTimeStr] = useState(0) const [timeStr, setTimeStr] = useState(0)
const [isExpand, setIsExpand] = useState(false) const [isExpand, setIsExpand] = useState(false)
const [roomUserLists, setRoomUserLists] = useState<any>([]) 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 channel = new BroadcastChannel('meeting_channel');
const userInfo = JSON.parse(storage.getItem('user') as string) const userInfo = JSON.parse(storage.getItem('user') as string)
let timeout: NodeJS.Timeout; let timeout: NodeJS.Timeout;
useEffect(() => { useEffect(() => {
getRoomUser() getRoomUser()
if (!role.ID.includes(userInfo.roleId)) {
setFooterLists((res: any) => {
return res.splice(4, 1)
})
}
channel.onmessage = function (event) { channel.onmessage = function (event) {
let { type, time } = event.data; let { type, time } = event.data;
switch (type) { switch (type) {
@ -82,13 +95,20 @@ const ShareScreenWindow: React.FC = () => {
footerListTemplate[0].active = data.parmes.footerList[0][0].active; footerListTemplate[0].active = data.parmes.footerList[0][0].active;
footerListTemplate[1].title = data.parmes.footerList[0][1].active ? '开启视频' : '关闭视频'; footerListTemplate[1].title = data.parmes.footerList[0][1].active ? '开启视频' : '关闭视频';
footerListTemplate[1].active = data.parmes.footerList[0][1].active; footerListTemplate[1].active = data.parmes.footerList[0][1].active;
if (role.ID.includes(userInfo.roleId)) {
footerListTemplate[4].title = data.parmes.footerList[1][3].active ? '录制中' : '录制'; footerListTemplate[4].title = data.parmes.footerList[1][3].active ? '录制中' : '录制';
footerListTemplate[4].active = data.parmes.footerList[1][3].active; footerListTemplate[4].active = data.parmes.footerList[1][3].active;
}
setFooterLists(footerListTemplate) setFooterLists(footerListTemplate)
break; break;
case 'roomUserList': case 'roomUserList':
setRoomUserLists(data.parmes.roomUserList) setRoomUserLists(data.parmes.roomUserList)
break; break;
case 'nnetworkStatus':
setCurrentEffective(data.parmes.currentEffective)
setNetworkQuality(data.parmes.networkQuality)
setNetworkOther(data.parmes.networkOther)
break;
} }
}) })
return () => { return () => {
@ -133,13 +153,19 @@ const ShareScreenWindow: React.FC = () => {
<> <>
<div className={styles.shareScreenWindow} style={{ width: isExpand ? '100%' : '100%' }}> <div className={styles.shareScreenWindow} style={{ width: isExpand ? '100%' : '100%' }}>
<div className={styles.shareScreenWindowTitle}> <div className={styles.shareScreenWindowTitle}>
<span>{changeCurrentSeconds(timeStr)} </span> <span>{changeCurrentSeconds(timeStr)} {!isExpand ? '共享中' : ''}
{isExpand ? <span className='drag' onClick={() => { {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({ channel.postMessage({
type: 'shareScreenWindowClose', type: 'shareScreenWindowClose',
shareScreenWindowClose: timeStr shareScreenWindowClose: timeStr
}); });
}}></span> : <span style={{ visibility: 'hidden' }}></span>} }}></span> : <span style={{ visibility: 'hidden', flexShrink: 0 }}></span>}
</div> </div>
{isExpand ? null : <div className={`${styles.shareScreenWindowContent} drag`}> {isExpand ? null : <div className={`${styles.shareScreenWindowContent} drag`}>
<div className={styles.shareScreenWindowContentList}> <div className={styles.shareScreenWindowContentList}>
@ -176,7 +202,7 @@ const ShareScreenWindow: React.FC = () => {
}} }}
key={index}> key={index}>
<div> <div>
{!item.active ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})` }} id={`micr-item-${userInfo.uid}`}> {!item.active ? <div style={{ backgroundImage: `url(${ImageUrl.icon49})`, transition: '0.18s' }} id={`micr-item-${userInfo.uid}`}>
</div> : ''} </div> : ''}
{item.select ? <img src={item.iconSelect} alt="" /> : <img src={item.active ? item.iconActive : item.icon} alt="" />} {item.select ? <img src={item.iconSelect} alt="" /> : <img src={item.active ? item.iconActive : item.icon} alt="" />}
</div> </div>
@ -197,7 +223,7 @@ const ShareScreenWindow: React.FC = () => {
<div className={`${styles.shareScreenWindowExpand} drag`} onClick={() => { <div className={`${styles.shareScreenWindowExpand} drag`} onClick={() => {
setIsExpand(!isExpand) setIsExpand(!isExpand)
window.electron.setChildWindow({ window.electron.setChildWindow({
width: isExpand ? 440 : 440 / 2, width: isExpand ? 440 : 440 / 1.6,
key: 'shareScreenWindow', key: 'shareScreenWindow',
}) })
}}> }}>
@ -209,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 export default ShareScreenWindow

View File

@ -7,6 +7,7 @@ import Avatar from '@/components/Avatar';
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import { storage } from '@/utils'; import { storage } from '@/utils';
import EquipmentManagement from '@/components/EquipmentManagement'; import EquipmentManagement from '@/components/EquipmentManagement';
import UserName from '@/components/UserName';
const { confirm } = Modal; const { confirm } = Modal;
const UserListWindow: React.FC = () => { const UserListWindow: React.FC = () => {
@ -14,6 +15,7 @@ const UserListWindow: React.FC = () => {
const [user, setUser] = useState<any>({}); const [user, setUser] = useState<any>({});
const [roomUserList, setRoomUserList] = useState<any>([]) const [roomUserList, setRoomUserList] = useState<any>([])
const equipmentManagementRef = useRef<any>(); const equipmentManagementRef = useRef<any>();
const userNameRef = useRef<any>();
const channel = new BroadcastChannel('meeting_channel'); const channel = new BroadcastChannel('meeting_channel');
const userInfo = JSON.parse(storage.getItem('user') as string) const userInfo = JSON.parse(storage.getItem('user') as string)
useEffect(() => { useEffect(() => {
@ -149,6 +151,18 @@ const UserListWindow: React.FC = () => {
}); });
}} }}
></Button> ></Button>
<Button
type="primary"
className='m-ant-btn'
style={{ width: '100%', marginTop: '10px' }}
size={'small'}
onClick={() => {
userNameRef.current.changeModal({
userName: item.userName,
uid: item.uid
})
}}
></Button>
</div> </div>
}> }>
<EllipsisOutlined style={{ <EllipsisOutlined style={{
@ -192,6 +206,7 @@ const UserListWindow: React.FC = () => {
}}></div> }}></div>
</div> </div>
</div> </div>
<UserName ref={userNameRef} />
<EquipmentManagement ref={equipmentManagementRef} getDriver={(uid: string) => { <EquipmentManagement ref={equipmentManagementRef} getDriver={(uid: string) => {
channel.postMessage({ channel.postMessage({
type: 'userListWindowEquipmentManagement', type: 'userListWindowEquipmentManagement',

View File

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

File diff suppressed because it is too large Load Diff

6
src/render.d.ts vendored
View File

@ -24,12 +24,14 @@ export interface IElectronAPI {
downFile: (callBack: Function) => void; downFile: (callBack: Function) => void;
quitAndInstall: (callBack: Function) => void; quitAndInstall: (callBack: Function) => void;
isOpenWindows: (callBack: Function) => void; isOpenWindows: (callBack: Function) => void;
setEnv: (str: string) => any; startLoad: () => any;
updateHandle: () => any;
getVersion: () => Promise<string>; getVersion: () => Promise<string>;
getEnv: () => Promise<string>;
isVisible: () => Promise<string>; isVisible: () => Promise<string>;
setRegistry: (uuid: string) => any; setRegistry: (uuid: string) => any;
getRegistry: () => any; getRegistry: () => any;
createChildWindow: (config: any) => void; createChildWindow: (str: string) => void;
setChildWindow: (config: any) => void; setChildWindow: (config: any) => void;
setChildWindowShow: (config: any) => void; setChildWindowShow: (config: any) => void;
closeChildWindow: (key: string) => void; closeChildWindow: (key: string) => void;

View File

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

View File

@ -9,7 +9,6 @@ import {
AudioAinsMode, AudioAinsMode,
SimulcastStreamMode, SimulcastStreamMode,
VideoStreamType, VideoStreamType,
QualityType,
RtcConnection, RtcConnection,
RtcStats, RtcStats,
AudioVolumeInfo, AudioVolumeInfo,
@ -21,16 +20,18 @@ import {
BeautyOptions, BeautyOptions,
ColorEnhanceOptions, ColorEnhanceOptions,
LowlightEnhanceOptions, LowlightEnhanceOptions,
VirtualBackgroundSource VirtualBackgroundSource,
AudienceLatencyLevelType,
StreamPublishState
} from "agora-electron-sdk"; } from "agora-electron-sdk";
import { GetRoomRtcToken, GetAgoraConf } from "@/api/Home/Index"; import { GetRoomRtcToken, GetAgoraConf } from "@/api/Home/Index";
import { storage } from '@/utils'; import { storage } from '@/utils';
import { role } from "@/config/role"; import { role } from "@/config/role";
import path from "path"; import path from "path";
const os = require("os");
const option: any = { const option: any = {
appId: '', appId: '',
token: '', token: '',
tokenA: '',
channelId: '', channelId: '',
uid: '', uid: '',
screenShareId: '', screenShareId: '',
@ -44,6 +45,7 @@ export const agora = {
rtcEngine = createAgoraRtcEngine(); rtcEngine = createAgoraRtcEngine();
await rtcEngine.initialize({ await rtcEngine.initialize({
appId: data, appId: data,
logConfig: { filePath: path.resolve(os.homedir(), "./agorasdk.log") }
}); });
if (bool) { if (bool) {
await agora.setDeviceManager() await agora.setDeviceManager()
@ -123,7 +125,8 @@ export const agora = {
if (settingData.darkLightEnhancement) agora.setLowlightEnhanceOptions(settingData.darkLightEnhancement.isDarkLightEnhancement, settingData.darkLightEnhancement) if (settingData.darkLightEnhancement) agora.setLowlightEnhanceOptions(settingData.darkLightEnhancement.isDarkLightEnhancement, settingData.darkLightEnhancement)
if (settingData.virtualBackground) { if (settingData.virtualBackground) {
if (typeof settingData.virtualBackground.sourceIndex === 'number') { if (typeof settingData.virtualBackground.sourceIndex === 'number') {
if (import.meta.env.VITE_ENV === 'development') { window.electron.getEnv().then(res => {
if (res === 'development') {
window.electron.getAppPath().then((res: string) => { window.electron.getAppPath().then((res: string) => {
const imagePath = path.join(res, 'src', 'assets', 'virtualBackground', `${settingData.virtualBackground.sourceIndex + 1}.png`); const imagePath = path.join(res, 'src', 'assets', 'virtualBackground', `${settingData.virtualBackground.sourceIndex + 1}.png`);
agora.enableVirtualBackground(settingData.virtualBackground.isVirtualBackground, { agora.enableVirtualBackground(settingData.virtualBackground.isVirtualBackground, {
@ -140,6 +143,7 @@ export const agora = {
color: Number(settingData.virtualBackground.color), color: Number(settingData.virtualBackground.color),
}) })
} }
})
} else { } else {
agora.enableVirtualBackground(settingData.virtualBackground.isVirtualBackground, { agora.enableVirtualBackground(settingData.virtualBackground.isVirtualBackground, {
background_source_type: 1, background_source_type: 1,
@ -150,7 +154,7 @@ export const agora = {
}, 1000); }, 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({ rtcEngine.registerEventHandler({
// 监听本地用户加入频道事件 // 监听本地用户加入频道事件
onJoinChannelSuccess: async (connection: RtcConnection, elapsed: number) => { onJoinChannelSuccess: async (connection: RtcConnection, elapsed: number) => {
@ -165,25 +169,17 @@ export const agora = {
await onUserOffline?.(connection, remoteUid, reason) await onUserOffline?.(connection, remoteUid, reason)
}, },
// // 视频发布状态改变回调 // // 视频发布状态改变回调
// onVideoPublishStateChanged: (source: any, channel: any, oldState: any, newState: any, elapseSinceLastState: any) => { onVideoPublishStateChanged: (source: VideoSourceType, channel: string, oldState: StreamPublishState, newState: StreamPublishState, elapseSinceLastState: number) => {
// if (newState === 1) { onVideoPublishStateChanged?.(source, channel, oldState, newState, elapseSinceLastState)
},
// }
// },
// // 音频发布状态改变回调 // // 音频发布状态改变回调
// onAudioPublishStateChanged: (channel: any, oldState: any, newState: any, elapseSinceLastState: any) => { onAudioPublishStateChanged: (channel: string, oldState: StreamPublishState, newState: StreamPublishState, elapseSinceLastState: number) => {
// if (newState === 1) { onAudioPublishStateChanged?.(channel, oldState, newState, elapseSinceLastState)
},
// }
// },
// // 用户音量提示回调。 // // 用户音量提示回调。
onAudioVolumeIndication: async (_connection: RtcConnection, speakers: AudioVolumeInfo[], _speakerNumber: number, _totalVolume: number) => { onAudioVolumeIndication: async (_connection: RtcConnection, speakers: AudioVolumeInfo[], _speakerNumber: number, _totalVolume: number) => {
await onAudioVolumeIndication?.(speakers) await onAudioVolumeIndication?.(speakers)
}, },
//通话中每个用户的网络上下行 last mile 质量报告回调。
onNetworkQuality: async (connection: RtcConnection, remoteUid: number, txQuality: QualityType, rxQuality: QualityType) => {
await onNetworkQuality?.(connection, remoteUid, txQuality, rxQuality)
},
//当前通话相关的统计信息回调。 //当前通话相关的统计信息回调。
onRtcStats: async (_connection: RtcConnection, stats: RtcStats) => { onRtcStats: async (_connection: RtcConnection, stats: RtcStats) => {
await onRtcStats?.(stats) await onRtcStats?.(stats)
@ -199,9 +195,26 @@ export const agora = {
// 网络连接中断,且 SDK 无法在 10 秒内连接服务器回调。 // 网络连接中断,且 SDK 无法在 10 秒内连接服务器回调。
onConnectionLost: (_connection: RtcConnection) => { onConnectionLost: (_connection: RtcConnection) => {
onConnectionLost?.() 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) => { getRrenderMode: (uid: number) => {
if (String(uid).length === 9) { if (String(uid).length === 9) {
@ -213,8 +226,11 @@ export const agora = {
// 本地加入 // 本地加入
setupLocalVideo: async (item: any) => { setupLocalVideo: async (item: any) => {
if (item.view?.childNodes.length === 1 || item.type) { if (item.view?.childNodes.length === 1 || item.type) {
if (String(item.uid).length === 9) {
return
}
await rtcEngine.setupLocalVideo({ await rtcEngine.setupLocalVideo({
renderMode: agora.getRrenderMode(item.uid), renderMode: item.renderMode || agora.getRrenderMode(item.uid),
sourceType: item.sourceType, sourceType: item.sourceType,
uid: item.uid, uid: item.uid,
view: item.view, view: item.view,
@ -227,7 +243,7 @@ export const agora = {
if (item.view?.childNodes.length === 1) { if (item.view?.childNodes.length === 1) {
await rtcEngine.setupRemoteVideo( await rtcEngine.setupRemoteVideo(
{ {
renderMode: agora.getRrenderMode(item.uid), renderMode: item.renderMode || agora.getRrenderMode(item.uid),
sourceType: VideoSourceType.VideoSourceRemote, sourceType: VideoSourceType.VideoSourceRemote,
uid: item.uid, uid: item.uid,
view: item.view, view: item.view,
@ -241,7 +257,7 @@ export const agora = {
if (item.view?.childNodes.length === 1) { if (item.view?.childNodes.length === 1) {
await rtcEngine.setupRemoteVideoEx( await rtcEngine.setupRemoteVideoEx(
{ {
renderMode: agora.getRrenderMode(item.uid), renderMode: item.renderMode || agora.getRrenderMode(item.uid),
sourceType: VideoSourceType.VideoSourceRemote, sourceType: VideoSourceType.VideoSourceRemote,
uid: item.uid, uid: item.uid,
view: item.view, view: item.view,
@ -255,7 +271,7 @@ export const agora = {
setupRemoteVideo: async (item: any) => { setupRemoteVideo: async (item: any) => {
await rtcEngine.setupRemoteVideo( await rtcEngine.setupRemoteVideo(
{ {
renderMode: agora.getRrenderMode(item.uid), renderMode: item.renderMode || agora.getRrenderMode(item.uid),
sourceType: VideoSourceType.VideoSourceRemote, sourceType: VideoSourceType.VideoSourceRemote,
uid: item.uid, uid: item.uid,
view: item.view, view: item.view,
@ -279,7 +295,7 @@ export const agora = {
}, },
// 加入频道 // 加入频道
joinChannel: async () => { joinChannel: async () => {
await rtcEngine.enableAudioVolumeIndication(100, 3, true) await rtcEngine.enableAudioVolumeIndication(200, 3, true)
await rtcEngine.joinChannel(option.token, option.channelId, option.uid); await rtcEngine.joinChannel(option.token, option.channelId, option.uid);
await rtcEngine.setDualStreamModeEx( await rtcEngine.setDualStreamModeEx(
SimulcastStreamMode.EnableSimulcastStream, SimulcastStreamMode.EnableSimulcastStream,
@ -292,6 +308,7 @@ export const agora = {
}, },
{ channelId: option.channelId, localUid: Number(option.uid) } { channelId: option.channelId, localUid: Number(option.uid) }
); );
await rtcEngine.setAudioScenario(8)
}, },
// 更新频道配置 // 更新频道配置
updateChannelMediaOptions: async (bool: boolean) => { updateChannelMediaOptions: async (bool: boolean) => {
@ -302,6 +319,7 @@ export const agora = {
publishMicrophoneTrack: true,//设置是否发布麦克风采集到的音频 publishMicrophoneTrack: true,//设置是否发布麦克风采集到的音频
publishCameraTrack: true,//设置是否发布摄像头采集的视频 publishCameraTrack: true,//设置是否发布摄像头采集的视频
publishScreenTrack: false,//设置是否发布屏幕采集的视频 publishScreenTrack: false,//设置是否发布屏幕采集的视频
audienceLatencyLevel: bool ? AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency : AudienceLatencyLevelType.AudienceLatencyLevelLowLatency,
}) })
}, },
// 设置接收大小流 // 设置接收大小流
@ -313,10 +331,10 @@ export const agora = {
) )
}, },
// 共享屏幕单独用户 // 共享屏幕单独用户
joinChannelEx: async (uid: any) => { joinChannelEx: async (uid: any, token: string) => {
await agora.leaveChannelEx(uid) await agora.leaveChannelEx(uid)
await rtcEngine.joinChannelEx( await rtcEngine.joinChannelEx(
option.token, token,
{ channelId: option.channelId, localUid: Number(uid) }, { channelId: option.channelId, localUid: Number(uid) },
{ {
autoSubscribeAudio: false,//设置是否自动订阅所有音频流 autoSubscribeAudio: false,//设置是否自动订阅所有音频流
@ -329,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) const user = await JSON.parse(storage.getItem('user') as string)
await agora.startCameraCapture(true) await agora.startCameraCapture(true)
await rtcEngine.joinChannelEx( await rtcEngine.joinChannelEx(
option.tokenA, token,
{ channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) }, { channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) },
{ {
clientRoleType: bool ? ClientRoleType.ClientRoleAudience : ClientRoleType.ClientRoleBroadcaster, //用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众 clientRoleType: bool ? ClientRoleType.ClientRoleAudience : ClientRoleType.ClientRoleBroadcaster, //用户角色 ClientRoleBroadcaster 主播 ClientRoleAudience 观众
@ -342,6 +360,7 @@ export const agora = {
publishMicrophoneTrack: false,//设置是否发布麦克风采集到的音频 publishMicrophoneTrack: false,//设置是否发布麦克风采集到的音频
publishCameraTrack: true,//设置是否发布摄像头采集的视频 publishCameraTrack: true,//设置是否发布摄像头采集的视频
publishScreenTrack: false,//设置是否发布屏幕采集的视频 publishScreenTrack: false,//设置是否发布屏幕采集的视频
audienceLatencyLevel: bool ? AudienceLatencyLevelType.AudienceLatencyLevelLowLatency : AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency,
} }
); );
await rtcEngine.setDualStreamModeEx( await rtcEngine.setDualStreamModeEx(
@ -358,20 +377,33 @@ export const agora = {
}, },
// 退出第二个房间 // 退出第二个房间
allLeaveChannelEx: async () => { allLeaveChannelEx: async () => {
await agora.stopCameraCapture();
await rtcEngine.leaveChannelEx({ channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) }) await rtcEngine.leaveChannelEx({ channelId: option.channelId + 'a', localUid: Number('1' + option.screenShareId) })
}, },
// 停止/恢复接收指定的视频流。 // 停止/恢复接收指定的视频流。
muteRemoteVideoStreamEx: async (uid: number, mute: boolean) => { muteRemoteVideoStreamEx: async (uid: number, mute: boolean) => {
await rtcEngine.muteRemoteVideoStreamEx(uid, mute, { channelId: option.channelId, localUid: Number(option.uid) }) await rtcEngine.muteRemoteVideoStreamEx(uid, mute, { channelId: option.channelId, localUid: Number(option.uid) })
}, },
// 设置视频订阅黑名单。
setSubscribeVideoBlocklist: async (uidList: number[], uidNumber: number) => {
await rtcEngine.setSubscribeVideoBlocklist(uidList, uidNumber)
},
// 取消或恢复订阅指定远端用户的音频流 // 取消或恢复订阅指定远端用户的音频流
muteRemoteVideoStream: async (uid: number, mute: boolean) => { muteRemoteVideoStream: async (uid: number, mute: boolean) => {
rtcEngine.muteRemoteVideoStream(uid, mute) rtcEngine.muteRemoteVideoStream(uid, mute)
}, },
// 销毁视频渲染dom // 销毁视频渲染dom
destroyRendererByConfig: async (uid: number, channelId?: string) => { 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);
},
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);
}
}, },
// ai降噪 // ai降噪
setAINSMode: async (enabled: boolean, mode: AudioAinsMode) => { setAINSMode: async (enabled: boolean, mode: AudioAinsMode) => {
@ -396,6 +428,7 @@ export const agora = {
publishMicrophoneTrack: publishMicrophoneTrack,//设置是否发布麦克风采集到的音频 publishMicrophoneTrack: publishMicrophoneTrack,//设置是否发布麦克风采集到的音频
publishCameraTrack: publishCameraTrack,//设置是否发布摄像头采集的视频 publishCameraTrack: publishCameraTrack,//设置是否发布摄像头采集的视频
publishScreenTrack: false,//设置是否发布屏幕采集的视频 publishScreenTrack: false,//设置是否发布屏幕采集的视频
audienceLatencyLevel: data ? AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency : AudienceLatencyLevelType.AudienceLatencyLevelLowLatency,
}) })
}, },
// 取消或恢复发布本地视频流 // 取消或恢复发布本地视频流
@ -408,26 +441,26 @@ export const agora = {
publishMicrophoneTrack: publishMicrophoneTrack,//设置是否发布麦克风采集到的音频 publishMicrophoneTrack: publishMicrophoneTrack,//设置是否发布麦克风采集到的音频
publishCameraTrack: publishCameraTrack,//设置是否发布摄像头采集的视频 publishCameraTrack: publishCameraTrack,//设置是否发布摄像头采集的视频
publishScreenTrack: false,//设置是否发布屏幕采集的视频 publishScreenTrack: false,//设置是否发布屏幕采集的视频
audienceLatencyLevel: data ? AudienceLatencyLevelType.AudienceLatencyLevelUltraLowLatency : AudienceLatencyLevelType.AudienceLatencyLevelLowLatency,
}) })
}, },
// 摄像头采集 // 摄像头采集
startCameraCapture: async (bool: boolean = false) => { startCameraCapture: async (bool: boolean = false) => {
await rtcEngine.startCameraCapture(VideoSourceType.VideoSourceCamera, { await rtcEngine.startCameraCapture(VideoSourceType.VideoSourceCamera, {
format: { format: {
width: bool ? 160 : 1280, width: bool ? 160 : 1920,
height: bool ? 160 : 720, height: bool ? 160 : 1080,
fps: 15, fps: 15,
} }
}) })
}, },
// 停止采集摄像头 // 停止采集摄像头
stopCameraCapture: async () => { stopCameraCapture: async () => {
await rtcEngine.stopCameraCapture() await rtcEngine.stopCameraCapture(VideoSourceType.VideoSourceCamera)
}, },
// 加入频道 // 加入频道
setJoinChannel: async (data: any) => { setJoinChannel: async (data: any) => {
option.token = data.token; option.token = data.token;
option.tokenA = data.tokenA;
option.channelId = data.channelId; option.channelId = data.channelId;
option.uid = Number(data.uid); option.uid = Number(data.uid);
option.screenShareId = data.screenShareId; option.screenShareId = data.screenShareId;
@ -438,7 +471,7 @@ export const agora = {
return await rtcEngine.getScreenCaptureSources(thumbSize, iconSize, includeScreen) 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) const user = JSON.parse(storage.getItem('user') as string)
agora.stopScreenCapture(); agora.stopScreenCapture();
if (isComputerAudio) { if (isComputerAudio) {
@ -446,7 +479,7 @@ export const agora = {
} }
let data = { let data = {
frameRate: isFluencyPriority ? 30 : 15, frameRate: isFluencyPriority ? 15 : 7,
dimensions: { dimensions: {
width: 1920, width: 1920,
height: 1080, height: 1080,
@ -474,7 +507,7 @@ export const agora = {
} }
); );
} }
await agora.joinChannelEx(user.screenShareId) await agora.joinChannelEx(user.screenShareId, token)
}, },
// 获取系统中所有的视频设备列表。 // 获取系统中所有的视频设备列表。
getVideoDeviceManager: async (): Promise<any> => { getVideoDeviceManager: async (): Promise<any> => {
@ -488,12 +521,12 @@ export const agora = {
await rtcEngine.getVideoDeviceManager().setDevice(deviceIdUTF8) 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.enableVideo();
rtcEngine.startPreview(); rtcEngine.startPreview();
await GetRoomRtcToken(`${+new Date()}`).then(async (res) => { await GetRoomRtcToken(`${channelId + uid}`).then(async (res) => {
await rtcEngine.joinChannelEx(res.data, { await rtcEngine.joinChannelEx(res.data, {
channelId: `${+new Date() + uid}`, channelId: `${channelId + uid}`,
localUid: uid, localUid: uid,
}, { }, {
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting, channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,

View File

@ -82,6 +82,10 @@ import virtualBackground6 from '@/assets/virtualBackground/6.png'
import icon52 from '@/assets/icon52.png' import icon52 from '@/assets/icon52.png'
import icon52Select from '@/assets/icon52-select.png' import icon52Select from '@/assets/icon52-select.png'
import icon53 from '@/assets/icon53.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 { export default {
loading, loading,
icon, icon,
@ -166,5 +170,9 @@ export default {
virtualBackground6, virtualBackground6,
icon52, icon52,
icon52Select, icon52Select,
icon53 icon53,
icon54,
icon55,
icon56,
icon56Active
} }

View File

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

View File

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

View File

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

View File

@ -77,12 +77,25 @@ class Request {
case 403: case 403:
updatePostRefresh() updatePostRefresh()
break break
case 502:
message.error('网络已断开,请检查网络状态')
break
default: default:
message.error(err.message) message.error(err.message)
break; break;
} }
} else { } else {
switch (err.code) {
case "ECONNABORTED":
message.error('网络连接超时,请检查网络状态')
break;
case "ERR_NETWORK":
message.error('网络已断开,请检查网络状态')
break;
default:
message.error(err.message) message.error(err.message)
break;
}
} }
} }
) )

View File

@ -405,3 +405,16 @@ $pagination-hover-background-color: #5575F2;
.ant-tabs { .ant-tabs {
-webkit-app-region: no-drag; -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, AudioAinsMode,
SimulcastStreamMode, SimulcastStreamMode,
VideoStreamType, VideoStreamType,
QualityType,
RtcConnection, RtcConnection,
RtcStats, RtcStats,
AudioVolumeInfo, AudioVolumeInfo,
@ -76,7 +75,10 @@ export default defineConfig({
BeautyOptions, BeautyOptions,
ColorEnhanceOptions, ColorEnhanceOptions,
LowlightEnhanceOptions, LowlightEnhanceOptions,
VirtualBackgroundSource VirtualBackgroundSource,
AudienceLatencyLevelType,
StreamPublishState,
IMediaEngine
} = require("agora-electron-sdk") } = require("agora-electron-sdk")
export { export {
createAgoraRtcEngine, createAgoraRtcEngine,
@ -89,7 +91,6 @@ export default defineConfig({
AudioAinsMode, AudioAinsMode,
SimulcastStreamMode, SimulcastStreamMode,
VideoStreamType, VideoStreamType,
QualityType,
RtcConnection, RtcConnection,
RtcStats, RtcStats,
AudioVolumeInfo, AudioVolumeInfo,
@ -101,7 +102,10 @@ export default defineConfig({
BeautyOptions, BeautyOptions,
ColorEnhanceOptions, ColorEnhanceOptions,
LowlightEnhanceOptions, LowlightEnhanceOptions,
VirtualBackgroundSource VirtualBackgroundSource,
AudienceLatencyLevelType,
StreamPublishState,
IMediaEngine
} }
`, `,
}) })