diff --git a/main.js b/main.js index 345ce0f..0415f85 100644 --- a/main.js +++ b/main.js @@ -181,6 +181,7 @@ app.on('ready', () => { connection.off('DriverList'); connection.off('SetDriver'); connection.off('ShowDriverList'); + connection.off('ModifyNickName'); } }); ipcMain.handle('onStop', (event) => { @@ -370,6 +371,14 @@ app.on('ready', () => { driversJsonString }) }); + // 修改用户名称 + connection.on("ModifyNickName", (uid, nickName) => { + mainWindow.webContents.send('onSignalr', { + key: 'ModifyNickName', + uid, + nickName + }) + }); } }); // 放大缩小退出窗口 diff --git a/package.json b/package.json index b7a531b..ce35f53 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "WGShare.Metting", "private": true, - "version": "0.6.0", + "version": "0.6.3", "main": "main.js", "authors": "yj", "description": "智汇享", diff --git a/src/api/Meeting/index.ts b/src/api/Meeting/index.ts index 3733fae..22db4c8 100644 --- a/src/api/Meeting/index.ts +++ b/src/api/Meeting/index.ts @@ -147,4 +147,11 @@ export const PostRoomSingnIn = (data: any) => url: `/room/sign-in`, method: 'post', data + }) + +export const PutAlterUname = (data: any) => + request({ + url: `/room/alter-uname`, + method: 'put', + data }) \ No newline at end of file diff --git a/src/components/JoinSetting/index.tsx b/src/components/JoinSetting/index.tsx index 18065db..e8be5b5 100644 --- a/src/components/JoinSetting/index.tsx +++ b/src/components/JoinSetting/index.tsx @@ -20,18 +20,17 @@ const JoinSetting = forwardRef((_props: any, ref: any) => { if (location.hash.indexOf('/meeting') === -1) { await agora.init() } - setJoinRoomSettingForm((res: any) => { - res.forEach(async (item: any, index: number) => { - if (index === 0 && role.ID.includes(userInfo.roleId)) { - await agora.getAudioMediaList().then(res => { - item.active = res.ecordingList.length ? true : false - }) - } else { - item.active = false - } - }); - return res - }) + const list = [...joinRoomSettingForm] + list.forEach(async (item: any, index: number) => { + if (index === 0 && role.ID.includes(userInfo.roleId)) { + await agora.getAudioMediaList().then(res => { + item.active = res.ecordingList.length ? true : false + }) + } else { + item.active = false + } + }); + setJoinRoomSettingForm(list) setRoomNumber(roomNum) getDeviceList() } diff --git a/src/components/SharedFilesModel/index.tsx b/src/components/SharedFilesModel/index.tsx index 4ce71d2..2eb8029 100644 --- a/src/components/SharedFilesModel/index.tsx +++ b/src/components/SharedFilesModel/index.tsx @@ -1,12 +1,13 @@ import styles from '@/components/SharedFilesModel/index.module.scss' import { DeleteOutlined, + LoadingOutlined, ProfileOutlined, ReloadOutlined, SearchOutlined, VerticalAlignBottomOutlined } 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 { DeleteRoomFile, GetRoomFile, GetRoomFileDwUrl, GetRoomUpFileurl, GetRoomUserItem, PostRoomFile } from '@/api/Meeting'; import axios from 'axios'; @@ -100,10 +101,10 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => {
} - onChange={(e) => { + onPressEnter={(e: any) => { if (fileList.pageIndex === 1) { setFileList({ ...fileList, @@ -116,8 +117,30 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => { pageIndex: 1 }) } - }} + onBlur={(e) => { + if (fileList.pageIndex === 1) { + setFileList({ + ...fileList, + keyword: e.target.value, + }) + } else { + setFileList({ + ...fileList, + keyword: e.target.value, + pageIndex: 1 + }) + } + }} + suffix={ + { + getRoomFile() + }} + >搜索 + + } /> { if (fileList.pageIndex === 1) { @@ -129,29 +152,41 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => { }) } }} /> - {roomUserItem && role.ID.includes(roomUserItem.roleId) || roomUserItem.isRoomManager ? { + {roomUserItem && (role.ID.includes(roomUserItem.roleId) || roomUserItem.isRoomManager) && fileList.data.length ? { setShowRowSelection(!showRowSelection) }} style={{ color: showRowSelection ? '#5575F2' : 'white' }} /> : null} - {showRowSelection ? { - if (selectedRowKeys.length) { - DeleteRoomFile(selectedRowKeys).then(res => { - if (res.code === 200) { - message.success('删除成功!') - if (fileList.pageIndex === 1) { - getRoomFile() - } else { - setFileList({ - ...fileList, - pageIndex: 1 - }) - } + {showRowSelection && fileList.data.length && selectedRowKeys.length ? + { + if (selectedRowKeys.length) { + DeleteRoomFile(selectedRowKeys).then(res => { + if (res.code === 200) { + message.success('删除成功!') + if (fileList.pageIndex === 1) { + getRoomFile() + } else { + setFileList({ + ...fileList, + pageIndex: 1 + }) + } + } + }) + } else { + message.error('请选择文件!') } - }) - } else { - message.error('请选择文件!') - } - }} /> : null} - + >上传 : null}
@@ -258,9 +293,10 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => { /> ( <> - { + storage.setItem('loading', true) GetRoomFileDwUrl(item.fileUrl, item.id).then(res => { if (res.code === 200) { axios({ @@ -329,8 +365,10 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => { }, 2000) }) } + }).finally(() => { + storage.setItem('loading', false) }) - }} /> + }} /> : } {/* */} )} /> @@ -341,7 +379,7 @@ const SharedFilesModel = forwardRef((props: any, ref: any) => { ...fileList, pageIndex: e }) - }} pageSize={fileList.pageSize} current={fileList.pageIndex} hideOnSinglePage={true} showSizeChanger={false}/> + }} pageSize={fileList.pageSize} current={fileList.pageIndex} hideOnSinglePage={true} showSizeChanger={false} />
diff --git a/src/components/UserName/index.module.scss b/src/components/UserName/index.module.scss new file mode 100644 index 0000000..07f2ea1 --- /dev/null +++ b/src/components/UserName/index.module.scss @@ -0,0 +1,8 @@ +.userNameModal { + + .userNameModalFooter { + margin-top: 10px; + display: flex; + justify-content: center; + } +} \ No newline at end of file diff --git a/src/components/UserName/index.tsx b/src/components/UserName/index.tsx new file mode 100644 index 0000000..1bc10d7 --- /dev/null +++ b/src/components/UserName/index.tsx @@ -0,0 +1,74 @@ +import { PutAlterUname } from '@/api/Meeting'; +import styles from '@/components/UserName/index.module.scss' +import { storage } from '@/utils'; +import { Button, Input, message, Modal } from 'antd'; +import { useState, useImperativeHandle, forwardRef } from "react"; +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 ( + <> + setUserNameModal(false)} + centered + width={'300px'} + > +
+
+ { + setInfo({ + ...info, + userName: e.target.value + }) + }} + /> +
+
+ + +
+
+
+ + ) +}) + +export default UserName \ No newline at end of file diff --git a/src/page/Home/Index/index.tsx b/src/page/Home/Index/index.tsx index 2c3caeb..270763b 100644 --- a/src/page/Home/Index/index.tsx +++ b/src/page/Home/Index/index.tsx @@ -1,7 +1,7 @@ import styles from '@/page/Home/Index/index.module.scss' import { useEffect, useState, useRef } from "react"; import Operation from '@/components/Operation'; -import { Button, Input, Modal, Pagination, Empty, message, Popover, Popconfirm, DatePicker, Select } from "antd"; +import { Button, Input, Modal, Pagination, Empty, message, Popover, Popconfirm, DatePicker, Select, Radio } from "antd"; import { GetRoom, PostRoom, GetCheckoutRoomNum, GetRoomRtcToken, DeleteRoom, GetRecord, PostRoomInfo, GetQrcode } from '@/api/Home/Index'; import ImageUrl from '@/utils/package/imageUrl' import { ExclamationCircleFilled, ReloadOutlined } from '@ant-design/icons'; @@ -42,6 +42,7 @@ const Index: React.FC = () => { const [subjectList, setSubjectList] = useState([]); const [timeData, setTimeData] = useState([]); const [isCreateRoom, setIsCreateRoom] = useState(false); + const [allowAnonymous, setAllowAnonymous] = useState(true); const [baseImage, setBaseImage] = useState(''); const userInfo = JSON.parse(storage.getItem('user') as string) useEffect(() => { @@ -305,6 +306,7 @@ const Index: React.FC = () => { year: item.year, id: item.id, }) + setAllowAnonymous(item.allowAnonymous) getSubDpList() setIsCreateRoom(false) setCreateRoomModal(true) @@ -385,7 +387,6 @@ const Index: React.FC = () => { placeholder="请输入房间号" style={{ flexGrow: 1 }} className={styles.letterSpacing} - showCount maxLength={8} value={createRoomFrom.roomNum} onChange={(e) => { @@ -422,7 +423,6 @@ const Index: React.FC = () => { { @@ -437,6 +437,7 @@ const Index: React.FC = () => { 届: { @@ -463,6 +464,15 @@ const Index: React.FC = () => { }) }} /> +
+ 游客入会: + { + setAllowAnonymous(e.target.value); + }} value={allowAnonymous}> + 开启 + 关闭 + +
{ if (bool) { message.error('房间号已存在!') } else { - PostRoom(createRoomFrom).then(res => { + PostRoom({ ...createRoomFrom, allowAnonymous }).then(res => { if (res.code === 200) { message.success('创建成功!') setCreateRoomModal(false) + setAllowAnonymous(true) getRoomList() } }) } }) } else { - PostRoomInfo(createRoomFrom).then(res => { + PostRoomInfo({ ...createRoomFrom, allowAnonymous }).then(res => { if (res.code === 200) { message.success('更新成功!') setCreateRoomModal(false) + setAllowAnonymous(true) getRoomList() } }) diff --git a/src/page/Home/User/index.tsx b/src/page/Home/User/index.tsx index b83d7d7..e297033 100644 --- a/src/page/Home/User/index.tsx +++ b/src/page/Home/User/index.tsx @@ -221,7 +221,7 @@ const User: React.FC = () => { > 删除用户 - +
{ { const regex = /^[0-9]*$/; if (regex.test(e.target.value)) { @@ -602,7 +603,7 @@ const User: React.FC = () => { onClick={() => { const file = document.createElement("input") as any; file.type = "file"; - // file.accept = ".xls,.xlsx"; + file.accept = ".xls,.xlsx"; file.onchange = async () => { const setting = await JSON.parse(storage.getItem('setting') as string) const fileInfo = file.files[0]; @@ -669,7 +670,7 @@ const User: React.FC = () => { onClick={() => { const file = document.createElement("input") as any; file.type = "file"; - // file.accept = ".xls,.xlsx"; + file.accept = ".xls,.xlsx"; file.onchange = async () => { const setting = await JSON.parse(storage.getItem('setting') as string) const fileInfo = file.files[0]; @@ -762,12 +763,17 @@ const User: React.FC = () => {
diff --git a/src/page/Meeting/UserListWindow/index.tsx b/src/page/Meeting/UserListWindow/index.tsx index 14fd1d9..1630667 100644 --- a/src/page/Meeting/UserListWindow/index.tsx +++ b/src/page/Meeting/UserListWindow/index.tsx @@ -7,6 +7,7 @@ import Avatar from '@/components/Avatar'; import { useEffect, useState, useRef } from "react"; import { storage } from '@/utils'; import EquipmentManagement from '@/components/EquipmentManagement'; +import UserName from '@/components/UserName'; const { confirm } = Modal; const UserListWindow: React.FC = () => { @@ -14,6 +15,7 @@ const UserListWindow: React.FC = () => { const [user, setUser] = useState({}); const [roomUserList, setRoomUserList] = useState([]) const equipmentManagementRef = useRef(); + const userNameRef = useRef(); const channel = new BroadcastChannel('meeting_channel'); const userInfo = JSON.parse(storage.getItem('user') as string) useEffect(() => { @@ -149,6 +151,18 @@ const UserListWindow: React.FC = () => { }); }} >移出会议 + }> { }}>全员静音 + { channel.postMessage({ type: 'userListWindowEquipmentManagement', diff --git a/src/page/Meeting/index.tsx b/src/page/Meeting/index.tsx index 07c2de6..ccf0a00 100644 --- a/src/page/Meeting/index.tsx +++ b/src/page/Meeting/index.tsx @@ -24,6 +24,7 @@ import { fixWebmDuration } from "webm-duration-fix-buffer"; import { getKeyOpenChildWindow, setKeyOpenChildWindow } from '@/utils/package/public'; import MeetingDisconnected from '@/components/MeetingDisconnected'; import SingIn from '@/components/SingIn'; +import UserName from '@/components/UserName'; const { setTimeout, setInterval, clearTimeout, clearInterval } = require('timers'); const { confirm } = Modal; const { exec } = require('child_process'); @@ -39,7 +40,9 @@ const Meeting: React.FC = () => { const equipmentManagementRef = useRef(); const meetingDisconnectedRef = useRef(); const singInRef = useRef(); + const userNameRef = useRef(); const [isClicked, setIsClicked] = useState(false); + const [isClickedMediaSteam, setIsClickedMediaSteam] = useState(false); const [statusList, setStatusList] = useState({ userList: false, userChatList: false, @@ -431,7 +434,7 @@ const Meeting: React.FC = () => { setTimeout(async () => { const setting = await JSON.parse(storage.getItem('setting') as string); const stateInfo = await JSON.parse(storage.getItem('stateInfo') as string); - if (stateInfo && setting.isRecordingTips) { + if (stateInfo && setting.isRecordingTips && location.href.indexOf('/meeting') !== -1) { setRecorder((data: any) => { if (!data) { setIsScreenCapture(bool => { @@ -855,6 +858,10 @@ const Meeting: React.FC = () => { } } break; + // 修改用户名称 + case 'ModifyNickName': + setAllUserListData('ModifyNickName', item) + break; } }) return () => { @@ -912,6 +919,18 @@ const Meeting: React.FC = () => { return () => clearTimeout(timer); }, [isClicked]); + useEffect(() => { + let timer: NodeJS.Timeout; + + if (isClickedMediaSteam) { + timer = setTimeout(() => { + setIsClickedMediaSteam(false) + }, 3000); + } + + return () => clearTimeout(timer); + }, [isClickedMediaSteam]); + useEffect(() => { const elements = document.querySelectorAll('.intersectionObserver-view'); if (elements.length && currentVideoId) { @@ -1276,6 +1295,15 @@ const Meeting: React.FC = () => { return res }) break; + case 'ModifyNickName': + setRoomUserList((res: any) => { + let userItem = res.find((row: any) => row.uid == item.uid) + if (userItem) { + userItem.userName = item.nickName + } + return res + }) + break; } changeAgoraDevice() } @@ -1506,52 +1534,57 @@ const Meeting: React.FC = () => { case '录制': const setting = await JSON.parse(storage.getItem('setting') as string); try { - await fs.access(setting.recordingFilesPath, fs.constants.F_OK); - footerListTemplate[itemIndex][rowIndex].title = '录制中'; - footerListTemplate[itemIndex][rowIndex].active = true; - setFooterList(footerListTemplate); - window.electron.getSources().then(async (sources: any) => { - const screenId = sources[0].id; - const stream = await navigator.mediaDevices.getUserMedia({ - audio: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: screenId, - } - } as any, - video: { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: screenId, - } - } as any + if (!isClickedMediaSteam) { + setIsClickedMediaSteam(true) + await fs.access(setting.recordingFilesPath, fs.constants.F_OK); + footerListTemplate[itemIndex][rowIndex].title = '录制中'; + footerListTemplate[itemIndex][rowIndex].active = true; + setFooterList(footerListTemplate); + window.electron.getSources().then(async (sources: any) => { + const screenId = sources[0].id; + const stream = await navigator.mediaDevices.getUserMedia({ + audio: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: screenId, + } + } as any, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: screenId, + } + } as any + }); + // 获取所有音频输入设备 + const devices = await navigator.mediaDevices.enumerateDevices(); + const audioInputDevices = devices.filter(device => device.kind === 'audioinput' && + device.deviceId !== 'default' && + device.deviceId !== 'communications'); + // 使用Web Audio API来捕获系统声音和麦克风声音,将它们合并到同一个MediaStream中。 + const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)(); + const systemSoundSource = audioCtx.createMediaStreamSource(stream); + const systemSoundDestination = audioCtx.createMediaStreamDestination(); + systemSoundSource.connect(systemSoundDestination); + // 录制所有音频输入设备 + audioInputDevices.forEach(async device => { + const micStream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: device.deviceId } } }); + setMediaStream((res: any) => [...res, micStream]); + const micSoundSource = audioCtx.createMediaStreamSource(micStream); + micSoundSource.connect(systemSoundDestination); + }) + // 合并音频流与视频流 + const combinedSource = new MediaStream([...stream.getVideoTracks(), ...systemSoundDestination.stream.getAudioTracks()]); + // 开始录制 + const mediaRecorder = new MediaRecorder(combinedSource, { + mimeType: 'video/webm;codecs=vp9,opus', + videoBitsPerSecond: 1.5e6, + }); + setRecorder(mediaRecorder); }); - // 获取所有音频输入设备 - const devices = await navigator.mediaDevices.enumerateDevices(); - const audioInputDevices = devices.filter(device => device.kind === 'audioinput' && - device.deviceId !== 'default' && - device.deviceId !== 'communications'); - // 使用Web Audio API来捕获系统声音和麦克风声音,将它们合并到同一个MediaStream中。 - const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)(); - const systemSoundSource = audioCtx.createMediaStreamSource(stream); - const systemSoundDestination = audioCtx.createMediaStreamDestination(); - systemSoundSource.connect(systemSoundDestination); - // 录制所有音频输入设备 - audioInputDevices.forEach(async device => { - const micStream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: device.deviceId } } }); - setMediaStream((res: any) => [...res, micStream]); - const micSoundSource = audioCtx.createMediaStreamSource(micStream); - micSoundSource.connect(systemSoundDestination); - }) - // 合并音频流与视频流 - const combinedSource = new MediaStream([...stream.getVideoTracks(), ...systemSoundDestination.stream.getAudioTracks()]); - // 开始录制 - const mediaRecorder = new MediaRecorder(combinedSource, { - mimeType: 'video/webm;codecs=vp9,opus', - videoBitsPerSecond: 1.5e6, - }); - setRecorder(mediaRecorder); - }); + } else { + message.error('录制太频繁了,请稍后重试!'); + } } catch (error: any) { if (error.code === 'ENOENT') { message.error({ @@ -1567,10 +1600,14 @@ const Meeting: React.FC = () => { break; case '录制中': - footerListTemplate[itemIndex][rowIndex].title = '录制' - footerListTemplate[itemIndex][rowIndex].active = false - setFooterList(footerListTemplate) - stopRecorderMedia() + if (isClickedMediaSteam) { + message.error('录制时长不足3秒,请稍后重试!'); + } else { + footerListTemplate[itemIndex][rowIndex].title = '录制' + footerListTemplate[itemIndex][rowIndex].active = false + setFooterList(footerListTemplate) + stopRecorderMedia() + } break; case '共享文件': sharedFilesModelRef.current.getData() @@ -2329,6 +2366,17 @@ const Meeting: React.FC = () => { getRoomKickout(state.channelId, item.uid, item.userName) }} >移出会议 : null} + {item.uid !== user.uid ? : null} }>
@@ -2487,6 +2535,18 @@ const Meeting: React.FC = () => { getRoomKickout(state.channelId, item.uid, item.userName) }} >移出会议 +
}> @@ -2995,6 +3055,7 @@ const Meeting: React.FC = () => { + ) }