首页人员

This commit is contained in:
yj 2024-07-08 16:29:51 +08:00
parent 375fb2a022
commit 12859e9e0b
22 changed files with 1011 additions and 186 deletions

25
main.js
View File

@ -1,4 +1,4 @@
const { app, systemPreferences, BrowserWindow, screen, Tray, nativeImage, Menu, ipcMain } = require('electron');
const { app, BrowserWindow, screen, Tray, nativeImage, Menu, ipcMain, clipboard } = require('electron');
const path = require('node:path')
app.allowRendererProcessReuse = false;
let mainWindow = null;
@ -141,6 +141,12 @@ app.on('ready', () => {
ipcMain.handle('getIsMaximized', () => {
return mainWindow.isMaximized();
});
// 复制文字
ipcMain.handle('setWriteText', (event, text) => {
clipboard.writeText(text)
});
// 设置桌面应用基础属性
ipcMain.handle('setMainWindowSize', (event, config) => {
// 设置最小窗口尺寸
@ -163,20 +169,3 @@ app.on('ready', () => {
});
});
// 检查并获取设备权限
async function checkAndApplyDeviceAccessPrivilege() {
// 检查并获取摄像头权限
const cameraPrivilege = systemPreferences.getMediaAccessStatus('camera');
if (cameraPrivilege !== 'granted') {
await systemPreferences.askForMediaAccess('camera');
}
// 检查并获取麦克风权限
const micPrivilege = systemPreferences.getMediaAccessStatus('microphone');
if (micPrivilege !== 'granted') {
await systemPreferences.askForMediaAccess('microphone');
}
}
checkAndApplyDeviceAccessPrivilege();

11
package-lock.json generated
View File

@ -15,6 +15,7 @@
"axios": "^1.7.2",
"dayjs": "^1.11.11",
"electron-squirrel-startup": "^1.0.1",
"js-md5": "^0.8.3",
"path": "^0.12.7",
"postcss-px-to-viewport-8-plugin": "^1.2.5",
"react": "^18.3.1",
@ -8783,6 +8784,11 @@
"node": ">= 4"
}
},
"node_modules/js-md5": {
"version": "0.8.3",
"resolved": "https://registry.npmmirror.com/js-md5/-/js-md5-0.8.3.tgz",
"integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ=="
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
@ -20730,6 +20736,11 @@
"is-object": "^1.0.1"
}
},
"js-md5": {
"version": "0.8.3",
"resolved": "https://registry.npmmirror.com/js-md5/-/js-md5-0.8.3.tgz",
"integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",

View File

@ -32,6 +32,7 @@
"axios": "^1.7.2",
"dayjs": "^1.11.11",
"electron-squirrel-startup": "^1.0.1",
"js-md5": "^0.8.3",
"path": "^0.12.7",
"postcss-px-to-viewport-8-plugin": "^1.2.5",
"react": "^18.3.1",

View File

@ -7,7 +7,9 @@ const {
VideoViewSetupMode,
ScreenCaptureSourceType,
RenderModeType,
ChannelProfileType
ChannelProfileType,
MediaRecorderContainerFormat,
MediaRecorderStreamType
} = require("agora-electron-sdk");
const agoraAonfig = require('./src/utils/package/agoraConfig');
const { message } = require('antd');
@ -15,16 +17,54 @@ const rtcEngine = createAgoraRtcEngine();
rtcEngine.initialize({
appId: agoraAonfig.appid,
});
let videoID = '';
let iMediaRecorder = '';
const getDom = () => {
return document.getElementById(videoID);
}
// 离开频道
const leaveChannel = () => {
rtcEngine.leaveChannel({
stopAudioMixing: true,
stopAllEffect: true,
stopMicrophoneRecording: true,
})
}
// 离开频道
const joinChannel = (bool) => {
if (bool) {
rtcEngine.joinChannel(agoraAonfig.token, agoraAonfig.channelId, 123, {
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting, //设置频道场景为直播场景
clientRoleType: ClientRoleType.ClientRoleBroadcaster, //用户角色 1主播 2观众
publishMicrophoneTrack: true, //设置是否发布麦克风采集到的音频
publishCameraTrack: false, //设置是否发布摄像头采集的视频
publishScreenTrack: true, //设置是否发布屏幕采集的视频
autoSubscribeAudio: true, //设置是否自动订阅所有音频流
autoSubscribeVideo: true, //设置是否自动订阅所有视频流
});
} else {
rtcEngine.joinChannel(agoraAonfig.token, agoraAonfig.channelId, 123, {
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting, //设置频道场景为直播场景
clientRoleType: ClientRoleType.ClientRoleBroadcaster, //设置用户角色为主播;如果要将用户角色设置为观众,保持默认值即可
publishMicrophoneTrack: true, //发布麦克风采集的音频
publishCameraTrack: true, //发布摄像头采集的视频
publishScreenTrack: false, //设置是否发布屏幕采集的视频
autoSubscribeAudio: true, //自动订阅所有音频流
autoSubscribeVideo: true, //自动订阅所有视频流
});
}
}
const EventHandles = {
// 停止共享屏幕
const stopScreenCapture = () => {
rtcEngine.stopScreenCapture();
}
rtcEngine.registerEventHandler({
// 监听本地用户加入频道事件
onJoinChannelSuccess: ({ channelId, localUid }, elapsed) => {
console.log({ channelId, localUid });
console.log({ channelId, localUid }, elapsed, '加入房间');
// 本地用户加入频道后,设置本地视频窗口
rtcEngine.setupLocalVideo({
renderMode: RenderModeType.RenderModeFit,
@ -50,6 +90,18 @@ const EventHandles = {
);
},
// 视频发布状态改变回调
onVideoPublishStateChanged: (source, channel, oldState, newState, elapseSinceLastState) => {
if (newState === 1) {
}
},
// 音频发布状态改变回调
onAudioPublishStateChanged: (channel, oldState, newState, elapseSinceLastState) => {
if (newState === 1) {
}
},
// 监听用户离开频道事件
onUserOffline: ({ channelId, localUid }, remoteUid, reason) => {
// 远端用户离开频道后,关闭远端视频窗口
@ -70,8 +122,7 @@ const EventHandles = {
document.getElementById('recordingDeviceTest').style.width = `${percentage}%`
}
}
};
rtcEngine.registerEventHandler(EventHandles);
});
contextBridge.exposeInMainWorld(
'electron',
@ -83,7 +134,7 @@ contextBridge.exposeInMainWorld(
// 共享屏幕采集
setDesktopCapturerVideo: (targetSource) => {
rtcEngine.stopScreenCapture()
stopScreenCapture()
if (
targetSource.type ===
ScreenCaptureSourceType.ScreencapturesourcetypeScreen
@ -109,34 +160,13 @@ contextBridge.exposeInMainWorld(
);
}
videoID = `vidoe-${123}-${agoraAonfig.channelId}`;
rtcEngine.joinChannelEx(agoraAonfig.token, {
channelId: agoraAonfig.channelId,
localUid: 123,
}, {
autoSubscribeAudio: true, //设置是否自动订阅所有音频流
autoSubscribeVideo: true, //设置是否自动订阅所有视频流
publishMicrophoneTrack: false, //设置是否发布麦克风采集到的音频
publishCameraTrack: false, //设置是否发布摄像头采集的视频
clientRoleType: ClientRoleType.ClientRoleBroadcaster, //用户角色 1主播 2观众
publishScreenTrack: true, //设置是否发布屏幕采集的视频
});
joinChannel(true)
},
// 摄像头采集
setCameraCapture: () => {
rtcEngine.startCameraCapture(VideoSourceType.VideoSourceCamera, {})
videoID = `vidoe-${123}-${agoraAonfig.channelId}`;
rtcEngine.joinChannelEx(agoraAonfig.token, {
channelId: agoraAonfig.channelId,
localUid: 123,
}, {
channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting, //设置频道场景为直播场景
clientRoleType: ClientRoleType.ClientRoleBroadcaster, //设置用户角色为主播;如果要将用户角色设置为观众,保持默认值即可
publishMicrophoneTrack: true, //发布麦克风采集的音频
publishCameraTrack: true, //发布摄像头采集的视频
autoSubscribeAudio: true, //自动订阅所有音频流
autoSubscribeVideo: true, //自动订阅所有视频流
publishScreenTrack: false, //设置是否发布屏幕采集的视频
});
joinChannel(false)
},
// 加入频道
setJoinChannel: () => {
@ -153,6 +183,22 @@ contextBridge.exposeInMainWorld(
publishScreenTrack: true, //设置是否发布屏幕采集的视频
});
},
// 离开频道
leaveChannel: () => {
leaveChannel()
},
// 停止共享屏幕
stopScreenCapture: () => {
stopScreenCapture()
},
// 取消或恢复发布本地音频流
muteLocalAudioStream: (mute) => {
rtcEngine.muteLocalAudioStream(mute)
},
// 取消或恢复发布本地视频流
muteLocalVideoStream: (mute) => {
rtcEngine.muteLocalVideoStream(mute)
},
// 获取当前生成的视频id
getVideoId: () => {
return videoID;
@ -223,7 +269,35 @@ contextBridge.exposeInMainWorld(
resolve(true)
});
})
},
// 开始录制音视频
startRecording: () => {
iMediaRecorder = rtcEngine.createMediaRecorder({
channelId: agoraAonfig.channelId,
uid: 123,
})
iMediaRecorder.setMediaRecorderObserver({
// 录制状态发生改变回调。
onRecorderStateChanged: (channelId, uid, state, reason) => {
console.log(channelId, uid, state, reason, '录制状态发生改变回调。');
},
// 录制信息更新回调。
onRecorderInfoUpdated: (channelId, uid, info) => {
console.log(channelId, uid, info, '录制信息更新回调。');
},
})
iMediaRecorder.startRecording({
storagePath: `D:/word/${+new Date()}.mp4`, //录音文件在本地保存的绝对路径,需精确到文件名及格式
containerFormat: MediaRecorderContainerFormat.FormatMp4, //录制文件的格式
streamType: MediaRecorderStreamType.StreamTypeBoth, //录制内容
maxDurationMs: 7200000, //maxDurationMs
})
},
// 停止录制音视频
stopRecording: () => {
iMediaRecorder.stopRecording()
rtcEngine.destroyMediaRecorder(iMediaRecorder)
iMediaRecorder = ""
},
// 设置窗口大小
setMainWindowSize: (config) => {
@ -236,6 +310,10 @@ contextBridge.exposeInMainWorld(
// 获取当前是否全屏
getIsMaximized: () => {
return ipcRenderer.invoke('getIsMaximized')
},
// 复制文字
setWriteText: (text) => {
return ipcRenderer.invoke('setWriteText', text)
}
}
)

View File

@ -0,0 +1,20 @@
import { request } from '@/utils'
export const GetRoom = (data: { pageIndex: number, pageSize: number }) =>
request({
url: `/home/room?pageIndex=${data.pageIndex}&pageSize=${data.pageSize}`,
method: 'get'
})
export const PostRomm = (data: any) =>
request({
url: `/home/room`,
method: 'post',
data,
})
export const GetCheckoutRoomNum = (roomNum: string) =>
request({
url: `/room/checkout?roomNum=${roomNum}`,
method: 'get',
})

View File

@ -0,0 +1,39 @@
import { request } from '@/utils'
export const GetUserList = (data: { pageIndex: number, pageSize: number, searchKeywod: string }) =>
request({
url: `/user/list?pageIndex=${data.pageIndex}&pageSize=${data.pageSize}&searchKeywod=${data.searchKeywod}`,
method: 'get'
})
export const PostUser = (data: any) =>
request({
url: `/user`,
method: 'post',
data,
})
export const PutUser = (data: any) =>
request({
url: `/user`,
method: 'put',
data
})
export const DeleteUser = (data: any) =>
request({
url: `/user`,
method: 'delete',
data
})
export const PutUserPwd = (data: { id: string, pwd: string }) =>
request({
url: `/user/pwd`,
method: 'put',
data
})
export const GetRoleDpList = () =>
request({
url: `/pub/role-dp-list`,
method: 'get',
})

View File

@ -5,21 +5,10 @@ export const GetCheckUser = (userName: string) =>
method: 'get'
})
export const GetOcrDetail = (mid: string) =>
export const PostLogin = (data: any) =>
request({
url: `/api/ocr/${mid}`,
method: 'get'
})
export const PostSave = (data: any) =>
request({
url: `/api/ocr/save`,
url: `/auth/login`,
method: 'post',
data,
})
export const GetLock = (mid: string) =>
request({
url: `/api/ocr/lock?mid=${mid}`,
method: 'get',
})

View File

Before

Width:  |  Height:  |  Size: 977 B

After

Width:  |  Height:  |  Size: 977 B

View File

@ -2,10 +2,13 @@ import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import '@/utils/styles/main.css'
import { HashRouter } from 'react-router-dom';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
ReactDOM.createRoot(document.getElementById('root')!).render(
<HashRouter>
<App />
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
</HashRouter>
)

View File

@ -44,18 +44,20 @@
color: white;
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
}
.indexContentList {
margin: 20px 0;
overflow-y: scroll;
flex-grow: 1;
height: 0px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
align-content: flex-start;
>div {
.indexContentListItem {
border: 1px solid #353741;
margin-bottom: 34px;
background-color: #1E1E1F;
@ -118,5 +120,18 @@
}
}
}
.indexContentEmpty {
flex-grow: 1;
display: flex;
justify-content: center;
margin-top: 18%;
}
.indexContentPagination {
flex-shrink: 0;
display: flex;
justify-content: center;
}
}
}

View File

@ -2,13 +2,57 @@ import styles from '@/page/Home/Index/index.module.scss'
import { useEffect, useState } from "react";
import Operation from '@/components/Operation';
import { useNavigate } from 'react-router-dom';
import { Button } from "antd";
import { Button, Input, Modal, Pagination, Empty, message } from "antd";
import { GetRoom, PostRomm, GetCheckoutRoomNum } from '@/api/Home/Index';
// import { clipboard } from 'electron'
const Index: React.FC = () => {
const navigate = useNavigate();
const [list, setList] = useState([{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}])
const [list, setList] = useState({
data: [],
total: 0,
pageIndex: 1,
pageSize: 12,
})
const [createRoomModal, setCreateRoomModal] = useState(false)
const [createRoomFrom, setCreateRoomFrom] = useState<{ roomName: string, roomNum: string }>({
roomName: "",
roomNum: ""
})
const [joinRoomModal, setJoinRoomModal] = useState(false)
const [joinRoomFrom, setJoinRoomFrom] = useState<string>('')
useEffect(() => {
getRoomList()
}, [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,
})
}
})
}
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)
}
})
}
}, []);
return (
<>
<div className={styles.index}>
@ -18,10 +62,21 @@ const Index: React.FC = () => {
<div className={styles.indexBtns}>
<Button type="primary"
icon={<img src="/src/assets/icon8.png" alt="" />}
className='m-ant-btn drag'>
className='m-ant-btn drag'
onClick={() => {
setCreateRoomFrom({
roomName: "",
roomNum: ""
})
setCreateRoomModal(true)
}}
>
</Button>
<Button type="primary"
<Button type="primary" onClick={() => {
setJoinRoomFrom('')
setJoinRoomModal(true)
}}
icon={<img src="/src/assets/icon7.png" alt="" />}
className={`${styles.indexBtnsJoin} drag`}>
@ -31,24 +86,25 @@ const Index: React.FC = () => {
<div className={styles.indexContentTitle}>
</div>
<div className={`${styles.indexContentList} drag`}>
{list.map((_item, index: number) => {
{list.data.length ? <div className={styles.indexContentList}>
{list.data.map((item: any, index: number) => {
return (
<div className={styles.indexContentListItem} key={index}>
<div className={`${styles.indexContentListItem} drag`} key={index}>
<div>
<div></div>
<div>{item.roomName}</div>
<div>
<img src="/src/assets/icon11.png" alt="" />
<span>2</span>
<span>{item.onlineUserCount}</span>
</div>
</div>
<div>
<div>
<span>252535356565</span>
<div onClick={() => copyRoomNum(item.roomNum)} title='复制房间号'>
<span>{item.roomNum}</span>
<img src="/src/assets/icon10.png" alt="" />
</div>
<div>
<Button type="primary" danger></Button>
{/* <Button type="primary" danger>设置</Button> */}
<Button type="primary"
iconPosition={'end'}
onClick={() => {
@ -63,11 +119,130 @@ const Index: React.FC = () => {
</div>
)
})}
<div style={{ visibility: 'hidden' }}></div>
<div style={{ visibility: 'hidden' }}></div>
<div style={{ visibility: 'hidden' }} className={`${styles.indexContentListItem} drag`}></div>
<div style={{ visibility: 'hidden' }} className={`${styles.indexContentListItem} drag`}></div>
</div> :
<div className={styles.indexContentEmpty}>
<Empty />
</div>
}
<div className={styles.indexContentPagination}>
<Pagination size="small" total={list.total} onChange={(e) => {
setList({
...list,
pageIndex: e
})
}} pageSize={list.pageSize} />
</div>
</div>
</div>
<Modal title="新建会议室" open={createRoomModal} footer={null} closable={false} centered width={'30vw'}>
<div>
<div>
<Input
placeholder="请输入房间名字"
style={{ marginBottom: '14px' }}
value={createRoomFrom.roomName}
onChange={(e) => {
setCreateRoomFrom({
...createRoomFrom,
roomName: e.target.value
})
}}
/>
<Input
placeholder="请输入房间号"
style={{ marginBottom: '14px' }}
showCount
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={() => {
setCreateRoomFrom({
...createRoomFrom,
roomNum: Math.floor(+new Date() / 1000).toString().slice(-8),
})
}}
>
</span>
}
/>
</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('请输入房间号!')
}
isGetCheckoutRoomNum(createRoomFrom.roomNum, (bool: boolean) => {
if (bool) {
message.error('房间号已存在!')
} else {
PostRomm(createRoomFrom).then(res => {
if (res.code === 200) {
message.success('创建成功!')
setCreateRoomModal(false)
getRoomList()
}
})
}
})
}}></Button>
</div>
</div>
</Modal>
<Modal title="加入会议室" open={joinRoomModal} footer={null} closable={false} centered width={'30vw'}>
<div>
<div>
<Input
placeholder="请输入房间号"
style={{ marginBottom: '14px' }}
showCount
maxLength={8}
value={joinRoomFrom}
onChange={(e) => {
const regex = /^[0-9 ]*$/;
if (regex.test(e.target.value)) {
setJoinRoomFrom(e.target.value)
}
}}
/>
</div>
<div style={{
display: 'flex', justifyContent: 'center'
}}>
<Button type="primary" style={{ backgroundColor: '#31353A', marginRight: '14px' }} onClick={() => setJoinRoomModal(false)}></Button>
<Button type="primary" className='m-ant-btn' onClick={() => {
if (!joinRoomFrom) {
return message.error('请输入房间号!')
}
isGetCheckoutRoomNum(joinRoomFrom, (bool: boolean) => {
if (bool) {
} else {
message.error('房间号不存在!')
}
})
}}></Button>
</div>
</div>
</Modal>
</>
)
}

View File

@ -63,7 +63,23 @@
>span {
color: #8B8787;
font-size: 20px;
}
}
}
}
.addUserModal {
>div {
display: flex;
align-items: center;
margin-bottom: 20px;
>span {
flex-shrink: 0;
color: #EEEEEE;
width: 120px;
text-align: right;
}
}
}

View File

@ -1,55 +1,76 @@
import styles from '@/page/Home/User/index.module.scss'
import { useEffect, useState } from "react";
import Operation from '@/components/Operation';
import { Button, Input, Table, Pagination } from "antd";
import { Button, Input, Table, Pagination, Modal, message, Select } from "antd";
import { SearchOutlined } from '@ant-design/icons';
import type { TableColumnsType } from 'antd';
const columns: TableColumnsType = [
{
title: '姓名',
dataIndex: 'name',
},
{
title: '账号',
dataIndex: 'account',
},
{
title: '角色',
dataIndex: 'role',
},
{
title: '账号状态',
dataIndex: 'status',
render: (text) => {
return (
<div style={{ color: '#02B188' }}>{text}</div>
)
},
},
];
const data = [] as any;
for (let i = 0; i < 46; i++) {
data.push({
key: i,
name: `潇潇`,
account: 5256589545,
role: `教师`,
status: `在线`,
});
}
import { GetUserList, PostUser, PutUser, DeleteUser, PutUserPwd, GetRoleDpList } from '@/api/Home/User';
import { md5 } from 'js-md5';
const { Column } = Table
const User: React.FC = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
useEffect(() => {
const [isCreateUser, setIsCreateUser] = useState(false);
const [list, setList] = useState({
data: [],
searchKeywod: '',
total: 0,
pageIndex: 1,
pageSize: 6,
})
const [roleList, setRoleList] = useState([])
const [addUserModal, setAddUserModal] = useState(false)
const [addUserFrom, setAddUserFrom] = useState({
Id: "",
Account: "",
RoleId: null,
Pwd: "",
UserName: ""
})
const [changeUserPawModal, setChangeUserPawModal] = useState(false)
const [changeUserPawFrom, setChangeUserPawFrom] = useState({
Pwd: "",
newPwd: '',
})
const [deleteUserPawModal, setDeleteUserPawModal] = useState(false)
}, []);
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
setSelectedRowKeys(newSelectedRowKeys);
};
const rowSelection = {
selectedRowKeys,
onChange: onSelectChange,
};
useEffect(() => {
getUserList()
}, [list.pageIndex]);
const getUserList = async (): Promise<void> => {
await GetUserList({
pageIndex: list.pageIndex,
pageSize: list.pageSize,
searchKeywod: list.searchKeywod,
}).then(res => {
if (res.code === 200) {
setList({
...list,
total: res.data.total,
data: res.data.items.map((item: any) => {
return {
...item,
key: item.id,
}
}),
})
}
})
}
const getRoleDpList = async (callBack: Function): Promise<void> => {
await GetRoleDpList().then(res => {
if (res.code === 200) {
setRoleList(res.data.map((item: any) => {
return {
...item,
value: item.id,
label: item.roleName
}
}))
callBack(true)
}
})
}
return (
<>
<div className={styles.user}>
@ -59,36 +80,313 @@ const User: React.FC = () => {
<div className={styles.userBtns}>
<div className={`${styles.userBtnsLeft} drag`}>
<Button type="primary"
onClick={() => {
getRoleDpList((bool: boolean) => {
if (bool) {
setIsCreateUser(true)
setAddUserFrom({
Id: "",
Account: "",
RoleId: null,
Pwd: "",
UserName: "",
})
setAddUserModal(true)
}
})
}}
icon={<img src="/src/assets/icon8.png" alt="" />}
className='m-ant-btn'>
</Button>
<Button type="primary"
icon={<img src="/src/assets/icon21.png" alt="" />}
className={styles.userBtnsDel}>
className={styles.userBtnsDel}
onClick={() => {
setDeleteUserPawModal(true)
}}
>
</Button>
</div>
<div className={`${styles.userBtnsRight} drag`}>
<Input placeholder="请输入用户名" prefix={<SearchOutlined style={{ color: 'white' }} />} />
<Button className='m-border-ant-button'></Button>
<Input
placeholder="请输入用户名"
prefix={<SearchOutlined style={{ color: 'white' }} />}
onChange={(e) => {
setList({
...list,
searchKeywod: e.target.value,
})
}}
/>
<Button className='m-border-ant-button' onClick={() => {
if (list.pageIndex === 1) {
getUserList()
} else {
setList({
...list,
pageIndex: 1
})
}
}}></Button>
</div>
</div>
<div className={`${styles.userContent} drag`}>
<Table
rowSelection={rowSelection}
columns={columns}
dataSource={data}
size={'small'}
rowSelection={{
selectedRowKeys,
onChange: (newSelectedRowKeys: React.Key[]) => {
setSelectedRowKeys(newSelectedRowKeys);
}
}}
dataSource={list.data}
pagination={false}
scroll={{ y: '64vh' }}
style={{ width: '77.6vw', flexGrow: 1 }}
/>
scroll={{ y: '70vh' }}
style={{ width: '81.4vw', flexGrow: 1 }}
>
<Column title="姓名" dataIndex="userName" key="userName" />
<Column title="账号" dataIndex="account" key="account" />
<Column title="角色" dataIndex="roleName" key="roleName" />
<Column title="在线状态" render={(item) => (
<>
<div style={{ color: '#02B188' }}>{item.account}</div>
</>
)} />
<Column title="操作" render={(item) => (
<>
<Button
type="primary"
style={{ backgroundColor: '#0085FF30', marginRight: '14px' }}
size={'small'}
onClick={() => {
getRoleDpList((bool: boolean) => {
if (bool) {
setIsCreateUser(false)
setAddUserFrom({
...addUserFrom,
Id: item.id,
Account: item.account,
RoleId: item.roleId,
UserName: item.userName,
})
setAddUserModal(true)
}
})
}}></Button>
<Button
type="primary"
style={{ backgroundColor: '#715AFF40', marginRight: '14px' }}
size={'small'}
onClick={() => {
setChangeUserPawFrom({
Pwd: "",
newPwd: '',
})
setAddUserFrom({
...addUserFrom,
Id: item.id,
Account: item.account,
RoleId: item.roleId,
UserName: item.userName,
})
setChangeUserPawModal(true)
}}></Button>
</>
)} />
</Table>
<div className={styles.userContentPagination}>
<span>653</span>
<Pagination size="small" total={50} />
<span>{list.total}</span>
<Pagination size="small" total={list.total} onChange={(e) => {
setList({
...list,
pageIndex: e
})
}} pageSize={list.pageSize} current={list.pageIndex} hideOnSinglePage={true} />
</div>
</div>
</div>
<Modal title={isCreateUser ? '添加用户' : '编辑用户'} open={addUserModal} footer={null} closable={false} centered width={'40vw'}>
<div>
<div className={styles.addUserModal}>
<div>
<span></span>
<Input
placeholder="请输入账号"
value={addUserFrom.Account}
onChange={(e) => {
setAddUserFrom({
...addUserFrom,
Account: e.target.value,
});
}}
/>
</div>
<div>
<span></span>
<Select
placeholder='请选择角色'
options={roleList}
style={{ flexGrow: 1 }}
value={addUserFrom.RoleId} onChange={(e) => {
setAddUserFrom({
...addUserFrom,
RoleId: e
});
}} />;
</div>
{isCreateUser ? <div>
<span></span>
<Input.Password
placeholder="请输入密码"
style={{ flexGrow: 1 }}
value={addUserFrom.Pwd}
onChange={(e) => {
setAddUserFrom({
...addUserFrom,
Pwd: e.target.value,
});
}}
/>
</div> : null}
<div>
<span></span>
<Input
placeholder="请输入用户名称"
style={{ flexGrow: 1 }}
value={addUserFrom.UserName}
onChange={(e) => {
setAddUserFrom({
...addUserFrom,
UserName: e.target.value,
});
}}
/>
</div>
</div>
<div style={{
display: 'flex', justifyContent: 'center'
}}>
<Button type="primary" style={{ backgroundColor: '#31353A', marginRight: '14px' }} onClick={() => setAddUserModal(false)}></Button>
<Button type="primary" className='m-ant-btn' onClick={async () => {
if (!addUserFrom.Account) {
return message.error('请输入账号!')
}
if (!addUserFrom.RoleId) {
return message.error('请选择角色!')
}
if (!addUserFrom.Pwd && isCreateUser) {
return message.error('请输入密码!')
}
if (!addUserFrom.UserName) {
return message.error('请输入用户名称!')
}
if (isCreateUser) {
await PostUser({
...addUserFrom,
Pwd: md5(addUserFrom.Pwd)
}).then(res => {
if (res.code === 200) {
setAddUserModal(false)
message.success('添加成功!')
}
})
} else {
await PutUser({
Id: addUserFrom.Id,
Account: addUserFrom.Account,
RoleId: addUserFrom.RoleId,
UserName: addUserFrom.UserName
}).then(res => {
if (res.code === 200) {
setAddUserModal(false)
message.success('修改成功!')
}
})
}
await getUserList()
}}>{isCreateUser ? '添加' : '修改'}</Button>
</div>
</div>
</Modal>
<Modal title='更改密码' open={changeUserPawModal} footer={null} closable={false} centered width={'40vw'}>
<div>
<div className={styles.addUserModal}>
<div>
<span></span>
<Input.Password
placeholder="请输入新密码"
style={{ flexGrow: 1 }}
value={changeUserPawFrom.Pwd}
onChange={(e) => {
setChangeUserPawFrom({
...changeUserPawFrom,
Pwd: e.target.value,
});
}}
/>
</div>
<div>
<span></span>
<Input.Password
placeholder="再次输入密码"
style={{ flexGrow: 1 }}
value={changeUserPawFrom.newPwd}
onChange={(e) => {
setChangeUserPawFrom({
...changeUserPawFrom,
newPwd: e.target.value,
});
}}
/>
</div>
</div>
<div style={{
display: 'flex', justifyContent: 'center'
}}>
<Button type="primary" style={{ backgroundColor: '#31353A', marginRight: '14px' }} onClick={() => setChangeUserPawModal(false)}></Button>
<Button type="primary" className='m-ant-btn' onClick={async () => {
if (!changeUserPawFrom.Pwd || !changeUserPawFrom.newPwd) {
return message.error('请输入密码!')
}
if (changeUserPawFrom.Pwd !== changeUserPawFrom.newPwd) {
return message.error('新密码与确认密码不一致!')
}
await PutUserPwd({ id: addUserFrom.Id, pwd: md5(changeUserPawFrom.Pwd) }).then(res => {
if (res.code === 200) {
setChangeUserPawModal(false)
message.success('修改成功')
}
})
await getUserList()
}}></Button>
</div>
</div>
</Modal>
<Modal title='' open={deleteUserPawModal} footer={null} centered width={'24vw'}>
<div>
<div style={{ color: 'white', fontSize: '18px', textAlign: 'center', marginBottom: '20px' }}>
</div>
<div style={{
display: 'flex', justifyContent: 'center'
}}>
<Button type="primary" style={{ backgroundColor: '#31353A', marginRight: '14px' }}
onClick={() => setDeleteUserPawModal(false)}></Button>
<Button type="primary" className='m-ant-btn' onClick={() => {
DeleteUser(selectedRowKeys).then(res => {
if (res.code === 200) {
setDeleteUserPawModal(true)
setSelectedRowKeys([])
message.success('删除成功!')
getUserList()
}
})
}}></Button>
</div>
</div>
</Modal>
</>
)
}

View File

@ -4,6 +4,7 @@ import { Outlet, useNavigate } from 'react-router-dom';
import { Popconfirm } from 'antd';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn'
import { storage } from '@/utils';
dayjs.locale('zh-cn');
type navListType = {
title: string;
@ -31,6 +32,7 @@ const Home: React.FC = () => {
path: '/home/user'
},
]);
const [userInfo, setUserInfo] = useState<any>({})
const [dateInfo, setDateInfo] = useState<{
work: string;
time: string;
@ -41,6 +43,10 @@ const Home: React.FC = () => {
specific: '',
})
useEffect(() => {
const user = JSON.parse(storage.getItem('user') as string);
if (user) {
setUserInfo(user)
}
const updateTime = () => {
setDateInfo({
work: dayjs().format('ddd'),
@ -76,7 +82,7 @@ const Home: React.FC = () => {
<div>
<img src="/src/assets/avatar.png" alt="" />
</div>
<span>u0001</span>
<span>{userInfo?.userName}</span>
</div>
<div>
<img src="/src/assets/icon14.png" alt="" />

View File

@ -2,13 +2,15 @@
import styles from '@/page/Login/index.module.scss'
import { useEffect, useState } from "react";
import { useNavigate } from 'react-router-dom';
import { Input, Button, Checkbox } from "antd"
import { Input, Button, Checkbox, message } from "antd"
import { storage } from '@/utils'
import { GetCheckUser } from '@/api/Login'
import { GetCheckUser, PostLogin } from '@/api/Login'
import { md5 } from 'js-md5';
const Login: React.FC = () => {
const navigate = useNavigate();
const [accountPasswordStatus, setAccountPasswordStatus] = useState<boolean>(false);
const [accountStatus, setAccountStatus] = useState<boolean>(false);
const [operation, setOperation] = useState<{
isRememberPassword: boolean;
isAutoLogin: boolean;
@ -29,6 +31,11 @@ const Login: React.FC = () => {
});
useEffect(() => {
window.electron.setMainWindowSize({
width: 752,
height: 520,
key: 'login'
})
if (storage.getItem('login')) {
const login = JSON.parse(storage.getItem('login') as string);
const data = {
@ -49,6 +56,11 @@ const Login: React.FC = () => {
...data,
})
}
GetCheckUser(login.account).then(res => {
if (res.code === 200) {
setAccountStatus(res.data)
}
})
}
}, []);
@ -88,18 +100,27 @@ const Login: React.FC = () => {
// 登录
const loginClick = (): void => {
storage.setItem('login', JSON.stringify({
isRememberPassword: operation.optionsValue.includes('isRememberPassword'),
isAutoLogin: operation.optionsValue.includes('isAutoLogin'),
PostLogin({
account: operation.account,
password: operation.password,
optionsValue: operation.optionsValue,
}))
window.electron.setMainWindowSize({
width: 1200,
height: 800,
pwd: md5(operation.password)
}).then(res => {
if (res.code === 200) {
message.success('登录成功!')
storage.setItem('login', JSON.stringify({
isRememberPassword: operation.optionsValue.includes('isRememberPassword'),
isAutoLogin: operation.optionsValue.includes('isAutoLogin'),
account: operation.account,
password: operation.password,
optionsValue: operation.optionsValue,
}))
storage.setItem('user', JSON.stringify(res.data))
window.electron.setMainWindowSize({
width: 1200,
height: 800,
})
navigate('/home')
}
})
navigate('/home')
}
return (
@ -126,9 +147,13 @@ const Login: React.FC = () => {
...operation,
account: e.target.value
})
// GetCheckUser(e.target.value).then(res => {
// console.log(res);
// })
if (e.target.value) {
GetCheckUser(e.target.value).then(res => {
if (res.code === 200) {
setAccountStatus(res.data)
}
})
}
}}
className={`${styles.loginInputIcon} drag`}
style={{ marginBottom: '12px' }}
@ -143,7 +168,7 @@ const Login: React.FC = () => {
}
/>
{operation.account && !accountPasswordStatus ? <div style={{ marginTop: '36px' }} className='drag'>
{operation.account && !accountPasswordStatus && accountStatus ? <div style={{ marginTop: '36px' }} className='drag'>
<Button type="primary"
onClick={continueClick}
style={{ width: '100%' }}

View File

@ -20,64 +20,68 @@ const Meeting: React.FC = () => {
const [isStupWizard, setIsStupWizard] = useState(false);
const [sharedScreenList, setSharedScreenList] = useState<any>([]);
const [sharedScreenItem, setSharedScreenItem] = useState<any>('');
const [footerList] = useState([
const [footerList, setFooterList] = useState([
[
{
title: '关闭声音',
icon: '/src/assets/icon22.png',
icon: '/src/assets/icon22',
active: false,
},
{
title: '关闭视频',
icon: '/src/assets/icon23.png',
icon: '/src/assets/icon23',
active: false,
},
],
[
{
title: '共享屏幕',
icon: '/src/assets/icon24.png',
icon: '/src/assets/icon24',
active: false,
},
{
title: '共享文件',
icon: '/src/assets/icon25.png',
icon: '/src/assets/icon25',
active: false,
},
{
title: '邀请人员',
icon: '/src/assets/icon26.png',
icon: '/src/assets/icon26',
active: false,
},
{
title: '录制',
icon: '/src/assets/icon27.png',
icon: '/src/assets/icon27',
active: false,
},
{
title: '设置向导',
icon: '/src/assets/icon28.png',
icon: '/src/assets/icon28',
active: false,
},
{
title: '结束',
icon: '/src/assets/icon29.png',
icon: '/src/assets/icon29',
active: false,
},
],
[
{
title: '成员列表',
icon: '/src/assets/icon30.png',
icon: '/src/assets/icon30',
active: false,
},
{
title: '聊天',
icon: '/src/assets/icon31.png',
active: false
icon: '/src/assets/icon31',
active: false,
},
],
])
const [footerListIndex, setFooterListIndex] = useState<any>({
itemIndex: 0,
rowIndex: 0,
});
const [audioDeviceManager, setAudioDeviceManager] = useState<any>({
currentDevices: [],
currentDevice: {},
@ -93,7 +97,12 @@ const Meeting: React.FC = () => {
}, []);
const changeStatusList = (row: any): void => {
const changeStatusList = (row: any, itemIndex: number, rowIndex: number): void => {
const footerListTemplate = [...footerList]
setFooterListIndex({
itemIndex,
rowIndex,
})
switch (row.title) {
case '成员列表':
setStatusList({
@ -111,15 +120,51 @@ const Meeting: React.FC = () => {
getDesktopCapturerVideo()
setIsSharedScreenModal(true)
break;
case '停止共享':
window.electron.stopScreenCapture()
footerListTemplate[itemIndex][rowIndex].title = '共享屏幕'
break;
case '关闭声音':
window.electron.setJoinChannel()
setVideoID(window.electron.getVideoId())
footerListTemplate[itemIndex][rowIndex].title = '开启声音'
footerListTemplate[itemIndex][rowIndex].active = true
setFooterList(footerListTemplate)
window.electron.muteLocalAudioStream(true)
break;
case '开启声音':
footerListTemplate[itemIndex][rowIndex].title = '关闭声音'
footerListTemplate[itemIndex][rowIndex].active = false
setFooterList(footerListTemplate)
window.electron.muteLocalAudioStream(false)
break;
case '关闭视频':
footerListTemplate[itemIndex][rowIndex].title = '开启视频'
footerListTemplate[itemIndex][rowIndex].active = true
setFooterList(footerListTemplate)
window.electron.muteLocalVideoStream(true)
break;
case '开启视频':
footerListTemplate[itemIndex][rowIndex].title = '关闭视频'
footerListTemplate[itemIndex][rowIndex].active = false
setFooterList(footerListTemplate)
window.electron.muteLocalVideoStream(false)
break;
case '设置向导':
getAudioMediaList()
window.electron.startRecordingDeviceTest(200)
setIsStupWizard(true)
break;
case '录制':
footerListTemplate[itemIndex][rowIndex].title = '录制中'
footerListTemplate[itemIndex][rowIndex].active = true
setFooterList(footerListTemplate)
window.electron.startRecording()
break;
case '录制中':
footerListTemplate[itemIndex][rowIndex].title = '录制'
footerListTemplate[itemIndex][rowIndex].active = false
setFooterList(footerListTemplate)
window.electron.stopRecording()
break;
}
}
@ -129,6 +174,8 @@ const Meeting: React.FC = () => {
} else {
let data = sharedScreenList.find((item: any) => item.sourceId === sharedScreenItem.sourceId)
if (data) {
const footerListTemplate = [...footerList]
footerListTemplate[footerListIndex.itemIndex][footerListIndex.rowIndex].title = '停止共享'
setIsSharedScreenModal(false)
window.electron.setDesktopCapturerVideo(sharedScreenItem)
setVideoID(window.electron.getVideoId())
@ -300,9 +347,15 @@ const Meeting: React.FC = () => {
content={
<div className='meetingContentFooterPopover'>
<div onClick={() => {
window.electron.leaveChannel()
window.electron.stopScreenCapture()
navigate(-1)
}}></div>
<div onClick={() => { navigate(-1) }}></div>
<div onClick={() => {
window.electron.leaveChannel()
window.electron.stopScreenCapture()
navigate(-1)
}}></div>
<div onClick={() => { setOpen(false) }}></div>
</div>
}
@ -312,12 +365,12 @@ const Meeting: React.FC = () => {
onOpenChange={() => setOpen(true)}
>
<div className='drag'>
<img src={row.icon} alt="" />
<img src={row.active ? row.icon + '-active.png' : row.icon + '.png'} alt="" />
<span>{row.title}</span>
</div>
</Popover> :
<div className='drag' onClick={() => changeStatusList(row)} key={rowIndex}>
<img src={row.icon} alt="" />
<div className='drag' onClick={() => changeStatusList(row, itemIndex, rowIndex)} key={rowIndex}>
<img src={row.active ? row.icon + '-active.png' : row.icon + '.png'} alt="" />
<span>{row.title}</span>
</div>
)

9
src/render.d.ts vendored
View File

@ -11,9 +11,16 @@ export interface IElectronAPI {
setViewStatus: (status: 'quit' | 'maximize' | 'minimize' | 'unmaximize') => void;
setJoinChannel: () => Promise<void>;
getVideoId: () => string;
startRecordingDeviceTest: (indicationInterval:number) => void;
startRecordingDeviceTest: (indicationInterval: number) => void;
stopAudioDeviceLoopbackTest: () => void;
startPreview: () => Promise<boolean>;
startRecording: () => void;
stopRecording: () => void;
leaveChannel: () => void;
stopScreenCapture: () => void;
muteLocalAudioStream: (mute: boolean) => void;
muteLocalVideoStream: (mute: boolean) => void;
setWriteText: (text: string) => void;
}
declare global {

View File

@ -3,7 +3,6 @@ import { RequestConfig, RequestInterceptors } from './types'
import { storage } from '@/utils'
import { constant } from '@/config'
import { message } from 'antd';
class Request {
// axios实例
instance: AxiosInstance
@ -17,10 +16,10 @@ class Request {
// 类请求拦截器
this.instance.interceptors.request.use(
(req: any) => {
const token = localStorage.getItem('token')
if (token) {
const user = JSON.parse(storage.getItem('user') as string);
if (user) {
// 如果有token给请求头加上
req.headers.Authorization = `${token}`
req.headers.Authorization = `Bearer ${user.token}`
req.timeout = constant.CONFIG_REQUEST_TIMEOUT_TIME
}
if (req.contentType) {
@ -62,13 +61,13 @@ class Request {
},
(err: any) => {
function toLogin() {
storage.removeItem(constant.CONFIG_TOKEN)
storage.removeItem('user')
location.href = location.origin + '/#/login'
}
// 根据自己业务/接口返回做相应调整
if (err.response) {
const { status, data } = err.response
message.error(data.message || data.title)
const { status } = err.response
message.error(err.message)
switch (status) {
case 401:
toLogin()

View File

@ -1,7 +1,25 @@
.ant-select .ant-select-selector {
background-color: #28282C !important;
border: 1px solid #404145 !important;
box-sizing: border-box;
}
.ant-select .ant-select-selector .ant-select-selection-item {
color: white;
}
.ant-select .ant-select-selector .ant-select-selection-placeholder {
color: #EEEEEE;
}
.ant-select .ant-select-suffix {
color: white !important;
}
.ant-input {
background-color: #28282C !important;
border: 1px solid #404145;
color: white;
color: white !important;
box-sizing: border-box;
}
@ -23,6 +41,10 @@
color: white !important;
}
.ant-input-affix-wrapper .ant-input-show-count-suffix {
color: white;
}
.m-ant-btn.ant-btn {
background-color: #3F51B5;
box-shadow: none;
@ -94,6 +116,18 @@
background-color: #0d0f12 !important;
}
.ant-table .ant-table-body .ant-table-placeholder {
background: transparent !important;
}
.ant-table .ant-table-body .ant-table-placeholder .ant-table-cell {
border: none;
}
.ant-pagination {
-webkit-app-region: no-drag;
}
.ant-pagination .ant-pagination-prev {
margin-right: 10px !important;
}
@ -120,6 +154,10 @@
margin-right: 10px !important;
}
.ant-pagination .ant-pagination-item > a {
color: #878787 !important;
}
.ant-pagination .ant-pagination-item:hover {
background: #5575F2 !important;
border: none;
@ -140,6 +178,10 @@
color: black !important;
}
.ant-popover {
-webkit-app-region: no-drag;
}
.ant-popover:not(.ant-popconfirm) .ant-popover-arrow::before {
background-color: #07090B !important;
}
@ -152,6 +194,10 @@
background-color: rgba(0, 0, 0, 0.25) !important;
}
.ant-modal {
-webkit-app-region: no-drag;
}
.ant-modal .ant-modal-content {
background-color: #07090B;
}
@ -187,3 +233,7 @@
background-color: #3672E9;
box-shadow: 0 0 0 2px #3672E9;
}
.ant-empty .ant-empty-description {
color: #808080;
}

View File

@ -1 +1 @@
.ant-input{background-color:#28282C !important;border:1px solid #404145;color:white;box-sizing:border-box}.ant-input::placeholder{color:#E6E6E6}.ant-input-affix-wrapper{background-color:#28282C !important;border:1px solid #404145;box-sizing:border-box}.ant-input-affix-wrapper .ant-input{color:white !important}.ant-input-affix-wrapper .ant-input-password-icon{color:white !important}.m-ant-btn.ant-btn{background-color:#3F51B5;box-shadow:none}.m-ant-btn.ant-btn:hover{background-color:#606fc7 !important}.m-ant-btn.ant-btn:active{background-color:#32408f !important}.m-border-ant-button.ant-btn{border:1px solid #5575F2 !important;color:#5575F2 !important;background-color:#1E1E1F !important}.ant-checkbox-wrapper{color:#848484}.ant-checkbox-wrapper .ant-checkbox .ant-checkbox-inner{background-color:#28282C !important;border:1px solid #404145}.ant-checkbox-wrapper .ant-checkbox .ant-checkbox-inner::after{background-color:#3F51B5 !important}.ant-checkbox-wrapper .ant-checkbox-checked .ant-checkbox-inner{background-color:#3F51B5 !important;border:1px solid #404145}.ant-table{background-color:#1B1E24 !important;border-radius:0px !important}.ant-table .ant-table-header .ant-table-cell{background-color:#1B1E24;color:#808080;box-shadow:none;border-bottom:1px solid transparent}.ant-table .ant-table-header .ant-table-cell::before{visibility:hidden}.ant-table .ant-table-body .ant-table-row{background-color:#16191e;color:white}.ant-table .ant-table-body .ant-table-row .ant-table-cell{border-bottom:1px solid #292F3A}.ant-table .ant-table-body .ant-table-row-selected .ant-table-cell{background-color:#0d0f12 !important;color:white}.ant-table .ant-table-body .ant-table-cell-row-hover{background-color:#0d0f12 !important}.ant-pagination .ant-pagination-prev{margin-right:10px !important}.ant-pagination .ant-pagination-prev,.ant-pagination .ant-pagination-next{width:30px !important;height:30px !important;border-radius:50%;background:#20242C}.ant-pagination .ant-pagination-prev .anticon,.ant-pagination .ant-pagination-next .anticon{color:#808080}.ant-pagination .ant-pagination-item{width:30px !important;height:30px !important;line-height:30px !important;border-radius:50%;background:#20242C !important;margin-right:10px !important}.ant-pagination .ant-pagination-item:hover{background:#5575F2 !important;border:none;box-shadow:0px 0px 10px 0px #66C8FF}.ant-pagination .ant-pagination-item:hover a{color:black !important}.ant-pagination .ant-pagination-item-active{background:#5575F2 !important;border:none;box-shadow:0px 0px 10px 0px #66C8FF}.ant-pagination .ant-pagination-item-active a{color:black !important}.ant-popover:not(.ant-popconfirm) .ant-popover-arrow::before{background-color:#07090B !important}.ant-popover:not(.ant-popconfirm) .ant-popover-content .ant-popover-inner{background-color:#07090B}.ant-modal-mask{background-color:rgba(0,0,0,0.25) !important}.ant-modal .ant-modal-content{background-color:#07090B}.ant-modal .ant-modal-content .ant-modal-header{background-color:#07090B}.ant-modal .ant-modal-content .ant-modal-header .ant-modal-title{text-align:center;color:#EEEEEE;font-weight:bold}.ant-modal .ant-modal-content .ant-modal-body{max-height:70vh;overflow-y:auto}.ant-slider:hover .ant-slider-rail{background-color:#fff}.ant-slider .ant-slider-rail{background-color:#D9D9D9}.ant-slider .ant-slider-track{background-color:#3672E9}.ant-slider .ant-slider-handle::after{background-color:#3672E9;box-shadow:0 0 0 2px #3672E9}
.ant-select .ant-select-selector{background-color:#28282C !important;border:1px solid #404145 !important;box-sizing:border-box}.ant-select .ant-select-selector .ant-select-selection-item{color:white}.ant-select .ant-select-selector .ant-select-selection-placeholder{color:#EEEEEE}.ant-select .ant-select-suffix{color:white !important}.ant-input{background-color:#28282C !important;border:1px solid #404145;color:white !important;box-sizing:border-box}.ant-input::placeholder{color:#E6E6E6}.ant-input-affix-wrapper{background-color:#28282C !important;border:1px solid #404145;box-sizing:border-box}.ant-input-affix-wrapper .ant-input{color:white !important}.ant-input-affix-wrapper .ant-input-password-icon{color:white !important}.ant-input-affix-wrapper .ant-input-show-count-suffix{color:white}.m-ant-btn.ant-btn{background-color:#3F51B5;box-shadow:none}.m-ant-btn.ant-btn:hover{background-color:#606fc7 !important}.m-ant-btn.ant-btn:active{background-color:#32408f !important}.m-border-ant-button.ant-btn{border:1px solid #5575F2 !important;color:#5575F2 !important;background-color:#1E1E1F !important}.ant-checkbox-wrapper{color:#848484}.ant-checkbox-wrapper .ant-checkbox .ant-checkbox-inner{background-color:#28282C !important;border:1px solid #404145}.ant-checkbox-wrapper .ant-checkbox .ant-checkbox-inner::after{background-color:#3F51B5 !important}.ant-checkbox-wrapper .ant-checkbox-checked .ant-checkbox-inner{background-color:#3F51B5 !important;border:1px solid #404145}.ant-table{background-color:#1B1E24 !important;border-radius:0px !important}.ant-table .ant-table-header .ant-table-cell{background-color:#1B1E24;color:#808080;box-shadow:none;border-bottom:1px solid transparent}.ant-table .ant-table-header .ant-table-cell::before{visibility:hidden}.ant-table .ant-table-body .ant-table-row{background-color:#16191e;color:white}.ant-table .ant-table-body .ant-table-row .ant-table-cell{border-bottom:1px solid #292F3A}.ant-table .ant-table-body .ant-table-row-selected .ant-table-cell{background-color:#0d0f12 !important;color:white}.ant-table .ant-table-body .ant-table-cell-row-hover{background-color:#0d0f12 !important}.ant-table .ant-table-body .ant-table-placeholder{background:transparent !important}.ant-table .ant-table-body .ant-table-placeholder .ant-table-cell{border:none}.ant-pagination{-webkit-app-region:no-drag}.ant-pagination .ant-pagination-prev{margin-right:10px !important}.ant-pagination .ant-pagination-prev,.ant-pagination .ant-pagination-next{width:30px !important;height:30px !important;border-radius:50%;background:#20242C}.ant-pagination .ant-pagination-prev .anticon,.ant-pagination .ant-pagination-next .anticon{color:#808080}.ant-pagination .ant-pagination-item{width:30px !important;height:30px !important;line-height:30px !important;border-radius:50%;background:#20242C !important;margin-right:10px !important}.ant-pagination .ant-pagination-item>a{color:#878787 !important}.ant-pagination .ant-pagination-item:hover{background:#5575F2 !important;border:none;box-shadow:0px 0px 10px 0px #66C8FF}.ant-pagination .ant-pagination-item:hover a{color:black !important}.ant-pagination .ant-pagination-item-active{background:#5575F2 !important;border:none;box-shadow:0px 0px 10px 0px #66C8FF}.ant-pagination .ant-pagination-item-active a{color:black !important}.ant-popover{-webkit-app-region:no-drag}.ant-popover:not(.ant-popconfirm) .ant-popover-arrow::before{background-color:#07090B !important}.ant-popover:not(.ant-popconfirm) .ant-popover-content .ant-popover-inner{background-color:#07090B}.ant-modal-mask{background-color:rgba(0,0,0,0.25) !important}.ant-modal{-webkit-app-region:no-drag}.ant-modal .ant-modal-content{background-color:#07090B}.ant-modal .ant-modal-content .ant-modal-header{background-color:#07090B}.ant-modal .ant-modal-content .ant-modal-header .ant-modal-title{text-align:center;color:#EEEEEE;font-weight:bold}.ant-modal .ant-modal-content .ant-modal-body{max-height:70vh;overflow-y:auto}.ant-slider:hover .ant-slider-rail{background-color:#fff}.ant-slider .ant-slider-rail{background-color:#D9D9D9}.ant-slider .ant-slider-track{background-color:#3672E9}.ant-slider .ant-slider-handle::after{background-color:#3672E9;box-shadow:0 0 0 2px #3672E9}.ant-empty .ant-empty-description{color:#808080}

View File

@ -8,11 +8,31 @@ $table-content-background-color: rgb(22, 25, 30);
$pagination-background-color: #20242C;
$pagination-hover-background-color: #5575F2;
.ant-select {
.ant-select-selector {
background-color: $input-background-color !important;
border: 1px solid $input-border-color !important;
box-sizing: border-box;
.ant-select-selection-item {
color: white;
}
.ant-select-selection-placeholder {
color: #EEEEEE;
}
}
.ant-select-suffix {
color: white !important;
}
}
// input
.ant-input {
background-color: $input-background-color !important;
border: 1px solid $input-border-color;
color: white;
color: white !important;
box-sizing: border-box;
&::placeholder {
@ -32,6 +52,10 @@ $pagination-hover-background-color: #5575F2;
.ant-input-password-icon {
color: white !important;
}
.ant-input-show-count-suffix {
color: white;
}
}
// button
@ -115,11 +139,21 @@ $pagination-hover-background-color: #5575F2;
.ant-table-cell-row-hover {
background-color: darken($table-content-background-color, 4%) !important;
}
.ant-table-placeholder {
background: transparent !important;
.ant-table-cell {
border: none;
}
}
}
}
// pagination
.ant-pagination {
-webkit-app-region: no-drag;
.ant-pagination-prev {
margin-right: 10px !important;
}
@ -144,6 +178,10 @@ $pagination-hover-background-color: #5575F2;
background: $pagination-background-color !important;
margin-right: 10px !important;
>a {
color: #878787 !important;
}
&:hover {
background: $pagination-hover-background-color !important;
border: none;
@ -167,6 +205,10 @@ $pagination-hover-background-color: #5575F2;
}
// popover
.ant-popover {
-webkit-app-region: no-drag;
}
.ant-popover:not(.ant-popconfirm) {
.ant-popover-arrow {
&::before {
@ -187,6 +229,8 @@ $pagination-hover-background-color: #5575F2;
}
.ant-modal {
-webkit-app-region: no-drag;
.ant-modal-content {
background-color: #07090B;
@ -229,4 +273,11 @@ $pagination-hover-background-color: #5575F2;
box-shadow: 0 0 0 2px #3672E9;
}
}
}
// empty
.ant-empty {
.ant-empty-description {
color: #808080;
}
}

View File

@ -50,5 +50,5 @@ export default defineConfig({
base: './', // 这里更改打包相对绝对路径
plugins: [
react(),
],
]
})