554 lines
21 KiB
TypeScript
554 lines
21 KiB
TypeScript
import styles from '@/page/Home/Index/index.module.scss'
|
||
import { useEffect, useState, useRef } from "react";
|
||
import Operation from '@/components/Operation';
|
||
import { Button, Input, Modal, Pagination, Empty, message, Popover, Popconfirm, DatePicker, Select, Radio } from "antd";
|
||
import { GetRoom, PostRoom, GetCheckoutRoomNum, GetRoomRtcToken, DeleteRoom, GetRecord, PostRoomInfo } from '@/api/Home/Index';
|
||
import ImageUrl from '@/utils/package/imageUrl'
|
||
import { ExclamationCircleFilled, ReloadOutlined } from '@ant-design/icons';
|
||
import JoinSetting from '@/components/JoinSetting';
|
||
import { storage } from '@/utils';
|
||
import { PostRefresh } from '@/api/Login';
|
||
import { useLocation, useNavigate } from 'react-router-dom';
|
||
import { role } from '@/config/role';
|
||
import dayjs from 'dayjs';
|
||
import StupWizard from '@/components/StupWizard';
|
||
import { GetSubDpList } from '@/api/Home/User';
|
||
import FeedBackModel from '@/components/FeedBackModel';
|
||
import { PostHomeVerLog } from '@/api/Meeting';
|
||
import Code from '@/components/Code';
|
||
import { isVersion } from "@/utils/package/public";
|
||
|
||
const { setInterval, clearInterval } = require('timers');
|
||
const fs = require('fs').promises;
|
||
const { exec } = require('child_process');
|
||
const { RangePicker } = DatePicker;
|
||
const { confirm } = Modal;
|
||
const Index: React.FC = () => {
|
||
const navigate = useNavigate();
|
||
const { state } = useLocation();
|
||
const [list, setList] = useState({
|
||
data: [],
|
||
total: 0,
|
||
pageIndex: 1,
|
||
pageSize: 12,
|
||
})
|
||
const [createRoomModal, setCreateRoomModal] = useState(false)
|
||
const [timeSelectModal, setTimeSelectModal] = useState(false)
|
||
const [createRoomFrom, setCreateRoomFrom] = useState<{ id: string, roomName: string, roomNum: string, subject: number, year: string }>({
|
||
id: "",
|
||
roomName: "",
|
||
roomNum: "",
|
||
subject: 0,
|
||
year: "0"
|
||
})
|
||
const joinSettingRef = useRef<any>();
|
||
const stupWizardRef = useRef<any>();
|
||
const feedBackModelRef = useRef<any>();
|
||
const [user, setUser] = useState<any>({});
|
||
const [currentRoomInfo, setCurrentRoomInfo] = useState<any>({});
|
||
const [subjectList, setSubjectList] = useState<any>([]);
|
||
const [timeData, setTimeData] = useState<any>([]);
|
||
const [isCreateRoom, setIsCreateRoom] = useState<boolean>(false);
|
||
const [allowAnonymous, setAllowAnonymous] = useState(true);
|
||
const userInfo = JSON.parse(storage.getItem('user') as string)
|
||
useEffect(() => {
|
||
setUser(userInfo)
|
||
if (state?.currentSeconds >= 600) {
|
||
feedBackModelRef.current.changeModal()
|
||
}
|
||
}, [])
|
||
useEffect(() => {
|
||
let time = null as any
|
||
if (time) {
|
||
clearInterval(time)
|
||
} else {
|
||
time = setInterval(() => {
|
||
getRoomList()
|
||
}, 1000 * 30)
|
||
}
|
||
getRoomList()
|
||
return () => {
|
||
clearInterval(time)
|
||
}
|
||
}, [list.pageIndex]);
|
||
const getRoomList = async (): Promise<void> => {
|
||
await GetRoom({
|
||
pageIndex: list.pageIndex,
|
||
pageSize: list.pageSize,
|
||
}).then(res => {
|
||
if (res.code === 200) {
|
||
setList({
|
||
...list,
|
||
total: res.data.total,
|
||
data: res.data.items.map((item: any) => {
|
||
return {
|
||
...item,
|
||
open: false
|
||
}
|
||
}),
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
const copyRoomNum = (roomNum: string): void => {
|
||
window.electron.setWriteText(roomNum)
|
||
message.success('复制成功')
|
||
}
|
||
|
||
const isGetCheckoutRoomNum = async (roomNum: string, callBack: Function): Promise<void> => {
|
||
await GetCheckoutRoomNum(roomNum).then(res => {
|
||
if (res.code === 200) {
|
||
callBack(res.data)
|
||
}
|
||
})
|
||
}
|
||
const getSubDpList = async (): Promise<void> => {
|
||
await GetSubDpList().then(res => {
|
||
if (res.code === 200) {
|
||
setSubjectList(res.data.map((item: any) => { return { value: item.value, label: item.name } }))
|
||
}
|
||
})
|
||
}
|
||
const getRoomRtcToken = async (roomNum: string, callBack: Function): Promise<void> => {
|
||
GetRoomRtcToken(roomNum).then(res => {
|
||
if (res.code === 200) {
|
||
callBack({
|
||
token: res.data,
|
||
})
|
||
}
|
||
}).finally(() => {
|
||
storage.setItem('loading', false)
|
||
})
|
||
}
|
||
const postRefresh = async (callBack: Function): Promise<void> => {
|
||
await PostRefresh(user.refresh_token).then(res => {
|
||
if (res.code === 200) {
|
||
storage.setItem('user', JSON.stringify(res.data))
|
||
storage.setItem('userLogin', true)
|
||
callBack(res.data)
|
||
} else {
|
||
storage.setItem('loading', false)
|
||
}
|
||
}).catch(() => {
|
||
storage.setItem('loading', false)
|
||
})
|
||
}
|
||
|
||
const changeOpen = (index: number, bool: boolean): void => {
|
||
const newList = [...list.data] as any;
|
||
newList[index].open = bool
|
||
setList({
|
||
...list,
|
||
data: newList
|
||
})
|
||
}
|
||
|
||
const fileUpLoad = async (data: { url: string, content: string, fileName: string }): Promise<void> => {
|
||
const setting = await JSON.parse(storage.getItem('setting') as string)
|
||
try {
|
||
const response = await fetch(data.url);
|
||
const arrayBuffer = await response.arrayBuffer();
|
||
const buffer = Buffer.from(arrayBuffer);
|
||
await fs.writeFile(`${setting.shareFilesPath}\\${data.fileName}`, buffer, {});
|
||
confirm({
|
||
title: '提示',
|
||
icon: <ExclamationCircleFilled />,
|
||
content: data.content,
|
||
centered: true,
|
||
okText: '打开文件夹',
|
||
cancelText: '关闭',
|
||
async onOk() {
|
||
await fs.access(setting.shareFilesPath, fs.constants.F_OK);
|
||
if (process.platform === 'win32') {
|
||
exec(`explorer "${setting.shareFilesPath}"`);
|
||
} else if (process.platform === 'darwin') {
|
||
exec(`open "${setting.shareFilesPath}"`);
|
||
}
|
||
},
|
||
onCancel() {
|
||
}
|
||
})
|
||
} catch (error: any) {
|
||
if (error.code === 'ENOENT') {
|
||
message.error({
|
||
content: <div>文件夹不存在 <span style={{ color: '#606fc7', cursor: 'pointer' }} onClick={() => {
|
||
stupWizardRef.current.changeModal(4)
|
||
}}>前往设置</span></div>
|
||
})
|
||
return
|
||
} else {
|
||
message.error(error)
|
||
}
|
||
}
|
||
}
|
||
return (
|
||
<>
|
||
<div className={styles.index}>
|
||
<div className={styles.indexOperation}>
|
||
<Operation></Operation>
|
||
</div>
|
||
<div className={styles.indexBtns}>
|
||
{user?.roleId === '1' ? <Button type="primary"
|
||
icon={<img src={ImageUrl.icon8} alt="" />}
|
||
className='m-ant-btn drag'
|
||
onClick={() => {
|
||
setCreateRoomFrom({
|
||
roomName: "",
|
||
roomNum: "",
|
||
subject: 0,
|
||
year: "0",
|
||
id: "",
|
||
})
|
||
getSubDpList()
|
||
setIsCreateRoom(true)
|
||
setCreateRoomModal(true)
|
||
}}
|
||
style={{ marginRight: '22px' }}
|
||
>
|
||
新建会议室
|
||
</Button> : null}
|
||
<Button type="primary" onClick={() => {
|
||
joinSettingRef.current.changeModal()
|
||
}}
|
||
icon={<img src={ImageUrl.icon8} alt="" />}
|
||
className={`${styles.indexBtnsJoin} drag`}>
|
||
加入会议
|
||
</Button>
|
||
</div>
|
||
<div className={styles.indexContent}>
|
||
<div className={`drag ${styles.indexContentTitle}`}>
|
||
<span>会议室列表</span>
|
||
<ReloadOutlined
|
||
title='刷新'
|
||
style={{
|
||
cursor: 'pointer',
|
||
}}
|
||
onClick={() => {
|
||
message.success('刷新成功')
|
||
getRoomList()
|
||
}}
|
||
/>
|
||
</div>
|
||
{list.data.length ? <div className={`drag ${styles.indexContentList}`}>
|
||
{list.data.map((item: any, index: number) => {
|
||
return (
|
||
<div className={`${styles.indexContentListItem}`} key={index}>
|
||
<div>
|
||
<div>{item.roomName}</div>
|
||
<div>
|
||
<img src={ImageUrl.icon11} alt="" />
|
||
<span>{item.onlineUserCount}人</span>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div>
|
||
<div onClick={() => copyRoomNum(item.roomNum)} title='复制房间号'>
|
||
<span>{item.roomNum}</span>
|
||
<img src={ImageUrl.icon10} alt="" />
|
||
</div>
|
||
<Code roomNum={item.roomNum}></Code>
|
||
</div>
|
||
<div>
|
||
{role.ID.includes(userInfo.roleId) ? <Popover
|
||
content={
|
||
<div className='meetingContentFooterPopover'>
|
||
{userInfo.roleId === '1' ? <Popconfirm
|
||
title="提示"
|
||
description={`确定删除该会议吗`}
|
||
onConfirm={async () => {
|
||
DeleteRoom(item.id).then((res) => {
|
||
if (res.code === 200) {
|
||
message.success('删除成功')
|
||
changeOpen(index, false)
|
||
getRoomList()
|
||
}
|
||
})
|
||
}}
|
||
onCancel={() => {
|
||
changeOpen(index, false)
|
||
}}
|
||
okText="确定"
|
||
cancelText="取消"
|
||
>
|
||
<div className='meetingContentFooterPopoverDel'>删除会议室</div>
|
||
</Popconfirm> : null}
|
||
<div className='meetingContentFooterPopoverDefault' onClick={() => {
|
||
changeOpen(index, false)
|
||
setTimeSelectModal(true)
|
||
}}>导出参会记录</div>
|
||
<div className='meetingContentFooterPopoverDefault' onClick={() => {
|
||
changeOpen(index, false)
|
||
setCreateRoomFrom({
|
||
roomName: item.roomName,
|
||
roomNum: item.roomNum,
|
||
subject: item.subject,
|
||
year: item.year,
|
||
id: item.id,
|
||
})
|
||
setAllowAnonymous(item.allowAnonymous)
|
||
getSubDpList()
|
||
setIsCreateRoom(false)
|
||
setCreateRoomModal(true)
|
||
}}>修改设置</div>
|
||
<div className='meetingContentFooterPopoverCancel' onClick={() => {
|
||
changeOpen(index, false)
|
||
}}>取消</div>
|
||
</div>
|
||
}
|
||
title=""
|
||
trigger="click"
|
||
open={item.open}
|
||
onOpenChange={() => {
|
||
setCurrentRoomInfo(list.data[index])
|
||
changeOpen(index, true)
|
||
}}
|
||
>
|
||
<Button type="primary" danger>更多</Button>
|
||
</Popover> : null}
|
||
<Button type="primary"
|
||
iconPosition={'end'}
|
||
onClick={async () => {
|
||
storage.setItem('loading', true)
|
||
isVersion((bool: boolean) => {
|
||
storage.setItem('loading', false)
|
||
if (bool) {
|
||
window.electron.onDownload('3')
|
||
} else {
|
||
if (role.ID.includes(userInfo.roleId)) {
|
||
joinSettingRef.current.changeModal(item.roomNum)
|
||
} else {
|
||
storage.setItem('loading', true)
|
||
postRefresh(() => {
|
||
getRoomRtcToken(item.roomNum, async (options: any) => {
|
||
if (options) {
|
||
await window.electron.getVersion().then(async req => {
|
||
await PostHomeVerLog({
|
||
version: req,
|
||
platformType: 1,
|
||
roomNum: item.roomNum,
|
||
})
|
||
})
|
||
navigate(`/meeting`, {
|
||
state: {
|
||
channelId: item.roomNum,
|
||
token: options.token,
|
||
roomId: item.id,
|
||
roomName: item.roomName,
|
||
enableMicr: false,
|
||
enableCamera: false,
|
||
}
|
||
})
|
||
}
|
||
})
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}}
|
||
icon={<img src={ImageUrl.icon9} alt="" />}
|
||
className='m-ant-btn'>
|
||
进入
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
})}
|
||
<div style={{ visibility: 'hidden', margin: 0, padding: 0 }} className={`${styles.indexContentListItem} drag`}></div>
|
||
<div style={{ visibility: 'hidden', margin: 0, padding: 0 }} className={`${styles.indexContentListItem} drag`}></div>
|
||
</div> :
|
||
<div className={styles.indexContentEmpty}>
|
||
<Empty />
|
||
</div>
|
||
}
|
||
<div className={styles.indexContentPagination}>
|
||
<Pagination size="small" total={list.total} onChange={(e: number) => {
|
||
setList({
|
||
...list,
|
||
pageIndex: e
|
||
})
|
||
}} pageSize={list.pageSize} showSizeChanger={false} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<Modal title={isCreateRoom ? '新建会议室' : '修改会议信息'} open={createRoomModal} footer={null} closable={false} centered width={'400px'}>
|
||
<div>
|
||
<div className={styles.createRoom}>
|
||
{isCreateRoom ? <div>
|
||
<span>房间号:</span>
|
||
<Input
|
||
placeholder="请输入房间号"
|
||
style={{ flexGrow: 1 }}
|
||
className={styles.letterSpacing}
|
||
maxLength={8}
|
||
value={createRoomFrom.roomNum}
|
||
onChange={(e) => {
|
||
const regex = /^[0-9]*$/;
|
||
if (regex.test(e.target.value)) {
|
||
setCreateRoomFrom({
|
||
...createRoomFrom,
|
||
roomNum: e.target.value
|
||
})
|
||
}
|
||
}}
|
||
suffix={
|
||
<span
|
||
style={{ color: '#47D3D0', cursor: 'pointer' }}
|
||
onClick={() => {
|
||
function generateTimestampWithRandom(): string {
|
||
const timestamp = new Date().getTime();
|
||
const lastSixDigits = timestamp.toString().slice(-6);
|
||
const randomTwoDigits = ('0' + Math.floor(Math.random() * 100)).slice(-2);
|
||
return lastSixDigits + randomTwoDigits;
|
||
}
|
||
setCreateRoomFrom({
|
||
...createRoomFrom,
|
||
roomNum: generateTimestampWithRandom(),
|
||
})
|
||
}}
|
||
>获取随机房间号
|
||
</span>
|
||
}
|
||
/>
|
||
</div> : null}
|
||
<div>
|
||
<span>房间名字:</span>
|
||
<Input.TextArea
|
||
placeholder="请输入房间名字"
|
||
style={{ flexGrow: 1 }}
|
||
maxLength={30}
|
||
value={createRoomFrom.roomName}
|
||
onChange={(e) => {
|
||
setCreateRoomFrom({
|
||
...createRoomFrom,
|
||
roomName: e.target.value
|
||
})
|
||
}}
|
||
autoSize />
|
||
</div>
|
||
<div>
|
||
<span>届:</span>
|
||
<Input
|
||
placeholder="请输入届"
|
||
maxLength={4}
|
||
style={{ flexGrow: 1 }}
|
||
value={createRoomFrom.year}
|
||
onChange={(e) => {
|
||
const regex = /^[0-9]*$/;
|
||
if (regex.test(e.target.value)) {
|
||
setCreateRoomFrom({
|
||
...createRoomFrom,
|
||
year: e.target.value
|
||
})
|
||
}
|
||
}}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<span>学科:</span>
|
||
<Select
|
||
placeholder='请选择学科'
|
||
style={{ flexGrow: 1 }}
|
||
options={subjectList}
|
||
value={createRoomFrom.subject} onChange={(e) => {
|
||
setCreateRoomFrom({
|
||
...createRoomFrom,
|
||
subject: e
|
||
})
|
||
}} />
|
||
</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 style={{
|
||
display: 'flex', justifyContent: 'center'
|
||
}}>
|
||
<Button type="primary" style={{ backgroundColor: '#31353A', marginRight: '14px' }} onClick={() => setCreateRoomModal(false)}>取消</Button>
|
||
<Button type="primary" className='m-ant-btn' onClick={() => {
|
||
if (!createRoomFrom.roomName) {
|
||
return message.error('请输入房间名字!')
|
||
}
|
||
if (!createRoomFrom.roomNum) {
|
||
return message.error('请输入房间号!')
|
||
}
|
||
if (createRoomFrom.year === "") {
|
||
return message.error('请输入届!')
|
||
}
|
||
if (isCreateRoom) {
|
||
isGetCheckoutRoomNum(createRoomFrom.roomNum, (bool: boolean) => {
|
||
if (bool) {
|
||
message.error('房间号已存在!')
|
||
} else {
|
||
PostRoom({ ...createRoomFrom, allowAnonymous }).then(res => {
|
||
if (res.code === 200) {
|
||
message.success('创建成功!')
|
||
setCreateRoomModal(false)
|
||
setAllowAnonymous(true)
|
||
getRoomList()
|
||
}
|
||
})
|
||
}
|
||
})
|
||
} else {
|
||
PostRoomInfo({ ...createRoomFrom, allowAnonymous }).then(res => {
|
||
if (res.code === 200) {
|
||
message.success('更新成功!')
|
||
setCreateRoomModal(false)
|
||
setAllowAnonymous(true)
|
||
getRoomList()
|
||
}
|
||
})
|
||
}
|
||
}}>{isCreateRoom ? '创建' : '更新'}</Button>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
<Modal title="选择时间段" destroyOnClose={true} open={timeSelectModal} footer={null} onCancel={() => setTimeSelectModal(false)} centered maskClosable={false} width={'400px'}>
|
||
<div>
|
||
<RangePicker
|
||
showTime={{ format: 'YYYY-MM-DD HH:mm:ss' }}
|
||
format="YYYY-MM-DD HH:mm:ss"
|
||
onChange={(_value, dateString) => {
|
||
setTimeData(dateString)
|
||
}}
|
||
/>
|
||
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '10px' }}>
|
||
<Button type="primary"
|
||
style={{ backgroundColor: '#31353A', marginRight: '10px' }}
|
||
onClick={() => setTimeSelectModal(false)}>取消</Button>
|
||
<Button type="primary" className='m-ant-btn' onClick={async () => {
|
||
const setting = JSON.parse(storage.getItem('setting') as string)
|
||
if (timeData.length === 2) {
|
||
GetRecord(dayjs(timeData[0]).unix(), dayjs(timeData[1]).unix(), currentRoomInfo.roomNum).then(res => {
|
||
if (res.code === 200) {
|
||
const fileName = res.data.split('/').pop().split('?')[0];
|
||
fileUpLoad({
|
||
url: res.data,
|
||
content: `下载参会记录成功!文件已保存至:${setting.shareFilesPath}`,
|
||
fileName
|
||
})
|
||
}
|
||
setTimeSelectModal(false)
|
||
})
|
||
}
|
||
}}>导出</Button>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
<JoinSetting ref={joinSettingRef} />
|
||
<StupWizard ref={stupWizardRef} />
|
||
<FeedBackModel ref={feedBackModelRef} />
|
||
</>
|
||
)
|
||
}
|
||
|
||
export default Index
|