初始化
|
|
@ -0,0 +1,7 @@
|
||||||
|
#基础API 绝对的
|
||||||
|
VITE_BASE_URL_API = 'http://192.168.2.9:6500'
|
||||||
|
VITE_BASE_URL_DRAW_API = 'http://192.168.2.9:6555'
|
||||||
|
#当前IP 相对的
|
||||||
|
VITE_BASE_CURRENT_API = '.'
|
||||||
|
#开发环境
|
||||||
|
VITE_ENV = 'development'
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
#基础API 绝对的
|
||||||
|
VITE_BASE_URL_API = 'http://192.168.2.9:6500'
|
||||||
|
VITE_BASE_URL_DRAW_API = 'http://192.168.2.9:6555'
|
||||||
|
#当前IP 相对的
|
||||||
|
VITE_BASE_CURRENT_API = '.'
|
||||||
|
#生产环境
|
||||||
|
VITE_ENV = 'production'
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
#基础API 绝对的
|
||||||
|
VITE_BASE_URL_API = 'http://192.168.2.9:6500'
|
||||||
|
VITE_BASE_URL_DRAW_API = 'http://192.168.2.9:6555'
|
||||||
|
#当前IP 相对的
|
||||||
|
VITE_BASE_CURRENT_API = '.'
|
||||||
|
#测试环境
|
||||||
|
VITE_ENV = 'test'
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
out/
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# React + TypeScript + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
|
|
||||||
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||||
|
|
||||||
|
- Configure the top-level `parserOptions` property like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
// other rules...
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
|
||||||
|
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
|
||||||
|
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
|
||||||
|
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
packagerConfig: {
|
||||||
|
"name": "MyElectronApp", // 应用程序的名称
|
||||||
|
"files": [],
|
||||||
|
"productName": "My Electron App", // 产品名称(用于生成安装包的名称)
|
||||||
|
// "icon": "path/to/icon.png", // 应用程序的图标路径
|
||||||
|
"out": "build/", // 输出目录的路径
|
||||||
|
"overwrite": true, // 是否覆盖已存在的打包文件
|
||||||
|
"asar": true, // 是否使用asar打包格式
|
||||||
|
"version": "0.0.1", // 应用程序版本号
|
||||||
|
// "copyright": "Copyright © 2023", // 版权信息
|
||||||
|
// "ignore": [ // 不需要打包的文件和文件夹的路径列表
|
||||||
|
// ".git",
|
||||||
|
// ".vscode",
|
||||||
|
// "node_modules/.cache",
|
||||||
|
// "src"
|
||||||
|
// ],
|
||||||
|
},
|
||||||
|
rebuildConfig: {},
|
||||||
|
makers: [
|
||||||
|
{
|
||||||
|
name: '@electron-forge/maker-squirrel',
|
||||||
|
config: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '@electron-forge/maker-zip',
|
||||||
|
platforms: ['darwin'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '@electron-forge/maker-deb',
|
||||||
|
config: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '@electron-forge/maker-rpm',
|
||||||
|
config: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: '@electron-forge/plugin-auto-unpack-natives',
|
||||||
|
config: {},
|
||||||
|
},
|
||||||
|
// Fuses are used to enable/disable various Electron functionality
|
||||||
|
// at package time, before code signing the application
|
||||||
|
new FusesPlugin({
|
||||||
|
version: FuseVersion.V1,
|
||||||
|
[FuseV1Options.RunAsNode]: false,
|
||||||
|
[FuseV1Options.EnableCookieEncryption]: true,
|
||||||
|
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
|
||||||
|
[FuseV1Options.EnableNodeCliInspectArguments]: false,
|
||||||
|
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
|
||||||
|
[FuseV1Options.OnlyLoadAppFromAsar]: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
|
||||||
|
<!-- <meta http-equiv="Content-Security-Policy"
|
||||||
|
content="script-src 'self' https://www.google-analytics.com; style-src 'self' https://animate.style"> -->
|
||||||
|
<title>智汇享</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
const { app, systemPreferences, BrowserWindow, screen, Tray, nativeImage, Menu, ipcMain } = require('electron');
|
||||||
|
const path = require('node:path')
|
||||||
|
app.allowRendererProcessReuse = false;
|
||||||
|
let mainWindow = null;
|
||||||
|
let isMaximized = false;
|
||||||
|
|
||||||
|
class AppWindow extends BrowserWindow {
|
||||||
|
constructor(config) {
|
||||||
|
const basicConfig = {
|
||||||
|
webPreferences: {
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: true,
|
||||||
|
enableRemoteModule: true,
|
||||||
|
nodeIntegrationInWorker: true,
|
||||||
|
allowMediaDevices: true,
|
||||||
|
preload: path.join(__dirname, 'preload.js')
|
||||||
|
},
|
||||||
|
show: false,
|
||||||
|
frame: false,
|
||||||
|
icon: '',
|
||||||
|
icon: '',
|
||||||
|
backgroundColor: '#00000000',
|
||||||
|
transparent: true,
|
||||||
|
};
|
||||||
|
const finalConfig = { ...basicConfig, ...config };
|
||||||
|
super(finalConfig);
|
||||||
|
const env = process.argv.find((arg) => arg.startsWith('--env='))?.split('=')[1];
|
||||||
|
if (env) {
|
||||||
|
// 开发
|
||||||
|
this.loadURL('http://localhost:3000');
|
||||||
|
} else {
|
||||||
|
// 打包
|
||||||
|
this.loadFile(path.resolve(__dirname, './dist/index.html'));
|
||||||
|
}
|
||||||
|
this.once('ready-to-show', () => {
|
||||||
|
this.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWindow() {
|
||||||
|
// 如果主窗口已经存在但被最小化了,则恢复显示
|
||||||
|
if (mainWindow && mainWindow.isMinimized()) {
|
||||||
|
mainWindow.show();
|
||||||
|
}
|
||||||
|
// 如果主窗口已存在但不是焦点窗口,则将其置为焦点
|
||||||
|
if (mainWindow && !mainWindow.isFocused()) {
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
|
// 如果主窗口还没有被创建,则创建它
|
||||||
|
if (!mainWindow) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function createTray() {
|
||||||
|
const iconPath = `${__dirname}/src/assets/icon.png`;
|
||||||
|
const trayIcon = nativeImage.createFromPath(iconPath);
|
||||||
|
const tray = new Tray(trayIcon);
|
||||||
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: '打开', click: () => {
|
||||||
|
showWindow()
|
||||||
|
},
|
||||||
|
icon: iconPath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '退出', click: () => {
|
||||||
|
app.quit();
|
||||||
|
mainWindow = null;
|
||||||
|
},
|
||||||
|
icon: iconPath,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '退出到系统托盘', click: () => {
|
||||||
|
mainWindow.hide();
|
||||||
|
},
|
||||||
|
icon: iconPath,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
tray.setToolTip('智汇享');
|
||||||
|
tray.setContextMenu(contextMenu);
|
||||||
|
tray.on('click', () => {
|
||||||
|
if (mainWindow.isVisible()) {
|
||||||
|
mainWindow.hide()
|
||||||
|
} else {
|
||||||
|
mainWindow.show()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
mainWindow = new AppWindow();
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.on('ready', () => {
|
||||||
|
createWindow()
|
||||||
|
createTray()
|
||||||
|
|
||||||
|
// 监听f12打开控制台
|
||||||
|
mainWindow.webContents.on('before-input-event', (event, input) => {
|
||||||
|
if (input.key === 'F12') {
|
||||||
|
mainWindow.webContents.openDevTools()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听移动
|
||||||
|
mainWindow.on('move', () => {
|
||||||
|
// 如果是全屏自动恢复到上次窗口大小
|
||||||
|
if (isMaximized) {
|
||||||
|
mainWindow.setResizable(true)
|
||||||
|
mainWindow.unmaximize()
|
||||||
|
isMaximized = false;
|
||||||
|
}
|
||||||
|
if (mainWindow.isMaximized()) {
|
||||||
|
isMaximized = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 放大缩小退出窗口
|
||||||
|
ipcMain.handle('setViewStatus', (event, status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'quit':
|
||||||
|
app.quit();
|
||||||
|
mainWindow = null;
|
||||||
|
break;
|
||||||
|
case 'maximize':
|
||||||
|
mainWindow.maximize()
|
||||||
|
mainWindow.setResizable(false)
|
||||||
|
break;
|
||||||
|
case 'unmaximize':
|
||||||
|
mainWindow.setResizable(true)
|
||||||
|
mainWindow.unmaximize()
|
||||||
|
break;
|
||||||
|
case 'minimize':
|
||||||
|
mainWindow.minimize()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 导出是否全屏
|
||||||
|
ipcMain.handle('getIsMaximized', () => {
|
||||||
|
return mainWindow.isMaximized();
|
||||||
|
});
|
||||||
|
// 设置桌面应用基础属性
|
||||||
|
ipcMain.handle('setMainWindowSize', (event, config) => {
|
||||||
|
// 设置最小窗口尺寸
|
||||||
|
mainWindow.setMinimumSize(config.width, config.height);
|
||||||
|
// 设置最大尺寸
|
||||||
|
const primaryDisplay = screen.getPrimaryDisplay()
|
||||||
|
const { width, height } = primaryDisplay.workAreaSize
|
||||||
|
if (config.key === 'login') {
|
||||||
|
mainWindow.setMaximumSize(config.width, config.height);
|
||||||
|
} else {
|
||||||
|
mainWindow.setMaximumSize(width, height);
|
||||||
|
}
|
||||||
|
// 设置窗口尺寸
|
||||||
|
mainWindow.setSize(config.width, config.height)
|
||||||
|
// 设置窗口位置使其居中于当前屏幕
|
||||||
|
const display = screen.getDisplayMatching({ ...mainWindow.getBounds() });
|
||||||
|
const x = Math.round((display.workArea.width - mainWindow.getSize()[0]) / 2);
|
||||||
|
const y = Math.round((display.workArea.height - mainWindow.getSize()[1]) / 2);
|
||||||
|
mainWindow.setPosition(x, y);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查并获取设备权限
|
||||||
|
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();
|
||||||
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"name": "multi.person.meeting",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": "main.js",
|
||||||
|
"authors": "yj",
|
||||||
|
"description": "test",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "concurrently \"electron . --env=development\" \"cross-env BROWSER=none vite\"",
|
||||||
|
"test": "concurrently \"electron . --env=test\" \"cross-env BROWSER=none vite\"",
|
||||||
|
"prod": "concurrently \"electron . --env=production\" \"cross-env BROWSER=none vite\"",
|
||||||
|
"build": "vite build --mode development",
|
||||||
|
"build:test": "vite build --mode test",
|
||||||
|
"build:prod": "vite build --mode production",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"start": "electron-forge start",
|
||||||
|
"package": "electron-forge package",
|
||||||
|
"make": "vite build --mode development & electron-forge make",
|
||||||
|
"make:test": "vite build --mode test & electron-forge make",
|
||||||
|
"make:prod": "vite build --mode production & electron-forge make"
|
||||||
|
},
|
||||||
|
"agora_electron": {
|
||||||
|
"prebuilt": true
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.3.7",
|
||||||
|
"@types/node": "^20.14.9",
|
||||||
|
"agora-electron-sdk": "^4.3.2",
|
||||||
|
"antd": "^5.18.2",
|
||||||
|
"axios": "^1.7.2",
|
||||||
|
"dayjs": "^1.11.11",
|
||||||
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
|
"path": "^0.12.7",
|
||||||
|
"postcss-px-to-viewport-8-plugin": "^1.2.5",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.23.1",
|
||||||
|
"sass": "^1.77.5",
|
||||||
|
"swiper": "^11.1.4",
|
||||||
|
"tldraw": "^2.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@electron-forge/cli": "^7.4.0",
|
||||||
|
"@electron-forge/maker-deb": "^7.4.0",
|
||||||
|
"@electron-forge/maker-rpm": "^7.4.0",
|
||||||
|
"@electron-forge/maker-squirrel": "^7.4.0",
|
||||||
|
"@electron-forge/maker-zip": "^7.4.0",
|
||||||
|
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
|
||||||
|
"@electron-forge/plugin-fuses": "^7.4.0",
|
||||||
|
"@electron/fuses": "^1.8.0",
|
||||||
|
"@types/react": "^17.0.33",
|
||||||
|
"@types/react-dom": "^17.0.25",
|
||||||
|
"@vitejs/plugin-react": "^1.0.7",
|
||||||
|
"concurrently": "^7.6.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"electron": "^17.4.11",
|
||||||
|
"typescript": "^4.5.4",
|
||||||
|
"vite": "^2.8.0"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"forge": "./forge.config.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
// 在 preload 脚本中。
|
||||||
|
const { ipcRenderer, contextBridge } = require('electron')
|
||||||
|
const {
|
||||||
|
createAgoraRtcEngine,
|
||||||
|
ClientRoleType,
|
||||||
|
VideoSourceType,
|
||||||
|
VideoViewSetupMode,
|
||||||
|
ScreenCaptureSourceType,
|
||||||
|
RenderModeType,
|
||||||
|
} = require("agora-electron-sdk");
|
||||||
|
const agoraAonfig = require('./src/utils/package/agoraConfig')
|
||||||
|
const rtcEngine = createAgoraRtcEngine();
|
||||||
|
rtcEngine.initialize({
|
||||||
|
appId: agoraAonfig.appid,
|
||||||
|
});
|
||||||
|
|
||||||
|
let videoID = '';
|
||||||
|
const getDom = () => {
|
||||||
|
return document.getElementById(videoID);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EventHandles = {
|
||||||
|
// 监听本地用户加入频道事件
|
||||||
|
onJoinChannelSuccess: ({ channelId, localUid }, elapsed) => {
|
||||||
|
const dom = document.getElementById('video1')
|
||||||
|
// 本地用户加入频道后,设置本地视频窗口
|
||||||
|
rtcEngine.setupLocalVideo({
|
||||||
|
renderMode: RenderModeType.RenderModeFit,
|
||||||
|
sourceType: VideoSourceType.VideoSourceScreen,
|
||||||
|
uid: localUid,
|
||||||
|
view: dom,
|
||||||
|
// view: getDom(),
|
||||||
|
setupMode: VideoViewSetupMode.VideoViewSetupAdd,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 监听远端用户加入频道事件
|
||||||
|
onUserJoined: ({ channelId, localUid }, remoteUid, elapsed) => {
|
||||||
|
console.log('远端用户 ' + remoteUid + ' 已加入');
|
||||||
|
const dom = document.getElementById('video2')
|
||||||
|
// 远端用户加入频道后,设置远端视频窗口
|
||||||
|
rtcEngine.setupRemoteVideo(
|
||||||
|
{
|
||||||
|
renderMode: RenderModeType.RenderModeFit,
|
||||||
|
sourceType: VideoSourceType.VideoSourceRemote,
|
||||||
|
uid: remoteUid,
|
||||||
|
view: dom,
|
||||||
|
setupMode: VideoViewSetupMode.VideoViewSetupAdd,
|
||||||
|
},
|
||||||
|
{ channelId },
|
||||||
|
);
|
||||||
|
|
||||||
|
},
|
||||||
|
// 监听用户离开频道事件
|
||||||
|
onUserOffline: (info, remoteUid, reason) => {
|
||||||
|
console.log('远端用户 ' + remoteUid + ' 已离开频道');
|
||||||
|
// 远端用户离开频道后,关闭远端视频窗口
|
||||||
|
const dom = document.getElementById('video2')
|
||||||
|
rtcEngine.setupRemoteVideo(
|
||||||
|
{
|
||||||
|
renderMode: RenderModeType.RenderModeFit,
|
||||||
|
sourceType: VideoSourceType.VideoSourceRemote,
|
||||||
|
uid: remoteUid,
|
||||||
|
view: dom,
|
||||||
|
setupMode: VideoViewSetupMode.VideoViewSetupRemove,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
rtcEngine.registerEventHandler(EventHandles);
|
||||||
|
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld(
|
||||||
|
'electron',
|
||||||
|
{
|
||||||
|
// 桌面捕获音频和视频的媒体源的信息
|
||||||
|
getDesktopCapturerVideo: async () => {
|
||||||
|
return rtcEngine.getScreenCaptureSources({ width: 300, height: 300 }, { width: 300, height: 300 }, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 设置视频播放
|
||||||
|
setDesktopCapturerVideo: (targetSource) => {
|
||||||
|
rtcEngine.stopScreenCapture()
|
||||||
|
if (
|
||||||
|
targetSource.type ===
|
||||||
|
ScreenCaptureSourceType.ScreencapturesourcetypeScreen
|
||||||
|
) {
|
||||||
|
rtcEngine.startScreenCaptureByDisplayId(
|
||||||
|
targetSource.sourceId,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
windowFocus: true,
|
||||||
|
enableHighLight: true,
|
||||||
|
highLightColor: 0xFF99CC00,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
rtcEngine.startScreenCaptureByWindowId(
|
||||||
|
targetSource.sourceId,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
windowFocus: true,
|
||||||
|
enableHighLight: true,
|
||||||
|
highLightColor: 0xFF99CC00,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
videoID = `vidoe-${123}-${agoraAonfig.channelId}`;
|
||||||
|
rtcEngine.joinChannel(agoraAonfig.token, agoraAonfig.channelId, 123, {
|
||||||
|
autoSubscribeAudio: true, //设置是否自动订阅所有音频流
|
||||||
|
autoSubscribeVideo: true, //设置是否自动订阅所有视频流
|
||||||
|
publishMicrophoneTrack: false, //设置是否发布麦克风采集到的音频
|
||||||
|
publishCameraTrack: false, //设置是否发布摄像头采集的视频
|
||||||
|
clientRoleType: ClientRoleType.ClientRoleBroadcaster, //用户角色 1主播 2观众
|
||||||
|
publishScreenTrack: true, //设置是否发布屏幕采集的视频
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 加入频道
|
||||||
|
setJoinChannel: () => {
|
||||||
|
videoID = `vidoe-${234}-${agoraAonfig.channelId}`;
|
||||||
|
rtcEngine.joinChannelEx(agoraAonfig.token, {
|
||||||
|
channelId: agoraAonfig.channelId,
|
||||||
|
localUid: 234,
|
||||||
|
}, {
|
||||||
|
autoSubscribeAudio: true, //设置是否自动订阅所有音频流
|
||||||
|
autoSubscribeVideo: true, //设置是否自动订阅所有视频流
|
||||||
|
publishMicrophoneTrack: false, //设置是否发布麦克风采集到的音频
|
||||||
|
publishCameraTrack: false, //设置是否发布摄像头采集的视频
|
||||||
|
clientRoleType: ClientRoleType.ClientRoleAudience, //用户角色 1主播 2观众
|
||||||
|
publishScreenTrack: true, //设置是否发布屏幕采集的视频
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 获取当前生成的视频id
|
||||||
|
getVideoId: () => {
|
||||||
|
return videoID;
|
||||||
|
},
|
||||||
|
// 获取摄像头以及音频内容
|
||||||
|
getCameraAndMicrophoneMedia: async () => {
|
||||||
|
try {
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: true,
|
||||||
|
audio: true,
|
||||||
|
});
|
||||||
|
stream.getTracks().forEach(track => track.stop());
|
||||||
|
return stream
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 设置窗口大小
|
||||||
|
setMainWindowSize: (config) => {
|
||||||
|
ipcRenderer.invoke('setMainWindowSize', { ...config })
|
||||||
|
},
|
||||||
|
// 设置窗口状态
|
||||||
|
setViewStatus: (status) => {
|
||||||
|
ipcRenderer.invoke('setViewStatus', status)
|
||||||
|
},
|
||||||
|
// 获取当前是否全屏
|
||||||
|
getIsMaximized: () => {
|
||||||
|
return ipcRenderer.invoke('getIsMaximized')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import '@/utils/styles/App.scss'
|
||||||
|
import { Route, Routes, useNavigate, Navigate } from 'react-router-dom';
|
||||||
|
import Home from '@/page/Home/index'
|
||||||
|
import Index from '@/page/Home/Index/index'
|
||||||
|
import User from '@/page/Home/User/index'
|
||||||
|
import Login from '@/page/Login/index'
|
||||||
|
import Meeting from '@/page/Meeting/index'
|
||||||
|
import NotFound from '@/page/NotFound/index'
|
||||||
|
import { storage } from '@/utils'
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [_windowSize, setWindowSize] = useState({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (storage.getItem('TOKEN')) {
|
||||||
|
window.electron.setMainWindowSize({
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
})
|
||||||
|
navigate('/home')
|
||||||
|
} else {
|
||||||
|
window.electron.setMainWindowSize({
|
||||||
|
width: 752,
|
||||||
|
height: 520,
|
||||||
|
key: 'login'
|
||||||
|
})
|
||||||
|
navigate('/login')
|
||||||
|
}
|
||||||
|
const handleResize = () => {
|
||||||
|
setWindowSize({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
});
|
||||||
|
window.electron.getIsMaximized().then((res: boolean) => {
|
||||||
|
const dom = document.getElementById('root') as any;
|
||||||
|
dom.style.borderRadius = res ? '0px' : '10px'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Routes>
|
||||||
|
<Route path='/' element={<Home />} />
|
||||||
|
<Route path='/home' element={<Home />}>
|
||||||
|
<Route path='/home' element={<Navigate to='/home/index' />} />
|
||||||
|
<Route path='/home/index' element={<Index />} />
|
||||||
|
<Route path='/home/user' element={<User />} />
|
||||||
|
</Route>
|
||||||
|
<Route path='/login' element={<Login />} />
|
||||||
|
<Route path='/meeting' element={<Meeting />} />
|
||||||
|
<Route path='*' element={<NotFound />} />
|
||||||
|
</Routes>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { request } from '@/utils'
|
||||||
|
export const GetViewSize = (data: any) =>
|
||||||
|
request({
|
||||||
|
url: `/draw/position?X=${data.X}&Y=${data.Y}&Width=${data.Width}&Height=${data.Height}&ImageUrl=${data.ImageUrl}&ImageWidth=${data.ImageWidth}&ImageHeight=${data.ImageHeight}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const GetOcrDetail = (mid: string) =>
|
||||||
|
request({
|
||||||
|
url: `/api/ocr/${mid}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PostSave = (data: any) =>
|
||||||
|
request({
|
||||||
|
url: `/api/ocr/save`,
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const GetLock = (mid: string) =>
|
||||||
|
request({
|
||||||
|
url: `/api/ocr/lock?mid=${mid}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 827 B |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 361 B |
|
After Width: | Height: | Size: 386 B |
|
After Width: | Height: | Size: 337 B |
|
After Width: | Height: | Size: 493 B |
|
After Width: | Height: | Size: 267 B |
|
After Width: | Height: | Size: 378 B |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 344 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 404 B |
|
After Width: | Height: | Size: 130 B |
|
After Width: | Height: | Size: 184 B |
|
After Width: | Height: | Size: 213 B |
|
After Width: | Height: | Size: 337 B |
|
After Width: | Height: | Size: 156 B |
|
After Width: | Height: | Size: 414 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 622 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 477 B |
|
After Width: | Height: | Size: 530 B |
|
After Width: | Height: | Size: 495 B |
|
After Width: | Height: | Size: 675 B |
|
After Width: | Height: | Size: 977 B |
|
After Width: | Height: | Size: 760 B |
|
After Width: | Height: | Size: 572 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 312 B |
|
After Width: | Height: | Size: 769 B |
|
After Width: | Height: | Size: 641 B |
|
After Width: | Height: | Size: 299 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 309 B |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 213 B |
|
After Width: | Height: | Size: 186 B |
|
After Width: | Height: | Size: 237 B |
|
After Width: | Height: | Size: 113 KiB |
|
|
@ -0,0 +1,27 @@
|
||||||
|
.operation {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(1),
|
||||||
|
>div:nth-child(2) {
|
||||||
|
&:hover {
|
||||||
|
background-color: #424242;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(3) {
|
||||||
|
&:hover {
|
||||||
|
background-color: #880F0F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
import styles from '@/components/Operation/index.module.scss'
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
type OperationKeyType = 'minimize' | 'quit' | 'maximize' | 'unmaximize';
|
||||||
|
type OperationType = {
|
||||||
|
icon: string;
|
||||||
|
key: OperationKeyType;
|
||||||
|
title: string;
|
||||||
|
onClick: Function;
|
||||||
|
show: boolean;
|
||||||
|
}
|
||||||
|
const Operation: React.FC = () => {
|
||||||
|
const [_windowSize, setWindowSize] = useState({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
});
|
||||||
|
const [operationList, setOperationList] = useState<OperationType[]>([{
|
||||||
|
icon: '/src/assets/icon17.png',
|
||||||
|
key: 'minimize',
|
||||||
|
title: '最小化',
|
||||||
|
onClick: (key: OperationKeyType) => {
|
||||||
|
window.electron.setViewStatus(key)
|
||||||
|
},
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '/src/assets/icon20.png',
|
||||||
|
key: 'maximize',
|
||||||
|
title: '最大化',
|
||||||
|
onClick: (key: OperationKeyType) => {
|
||||||
|
window.electron.setViewStatus(key)
|
||||||
|
},
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '/src/assets/icon19.png',
|
||||||
|
key: 'unmaximize',
|
||||||
|
title: '还原大小',
|
||||||
|
onClick: (key: OperationKeyType) => {
|
||||||
|
window.electron.setViewStatus(key)
|
||||||
|
},
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: '/src/assets/icon18.png',
|
||||||
|
key: 'quit',
|
||||||
|
title: '关闭',
|
||||||
|
onClick: (key: OperationKeyType) => {
|
||||||
|
window.electron.setViewStatus(key)
|
||||||
|
},
|
||||||
|
show: true,
|
||||||
|
},])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getIsMaximized()
|
||||||
|
const handleResize = () => {
|
||||||
|
setWindowSize({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
});
|
||||||
|
getIsMaximized()
|
||||||
|
};
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getIsMaximized = (): void => {
|
||||||
|
window.electron.getIsMaximized().then((res: boolean) => {
|
||||||
|
changeOperationList(res ? 'maximize' : 'unmaximize')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeOperationList = (str: OperationKeyType): void => {
|
||||||
|
const newOperationList = [...operationList]
|
||||||
|
const unmaximize = newOperationList.find((item: OperationType) => item.key === 'unmaximize') as OperationType;
|
||||||
|
const maximize = newOperationList.find((item: OperationType) => item.key === 'maximize') as OperationType;
|
||||||
|
unmaximize.show = str === 'maximize' ? true : false;
|
||||||
|
maximize.show = str === 'maximize' ? false : true;
|
||||||
|
setOperationList(newOperationList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={`${styles.operation} drag`}>
|
||||||
|
{
|
||||||
|
operationList.map((item: OperationType, index: number) => {
|
||||||
|
return (item.show ?
|
||||||
|
<div title={item.title} key={index} onClick={() => item.onClick(item.key)}>
|
||||||
|
<img src={item.icon} alt="" />
|
||||||
|
</div> : null
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Operation
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import '@/components/TldrawView/index.scss'
|
||||||
|
import {
|
||||||
|
Tldraw,
|
||||||
|
createShapeId,
|
||||||
|
Editor,
|
||||||
|
} from 'tldraw'
|
||||||
|
import 'tldraw/tldraw.css'
|
||||||
|
const TldrawView: React.FC = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tldraw
|
||||||
|
components={{
|
||||||
|
// ContextMenu: null,
|
||||||
|
// ActionsMenu: null,
|
||||||
|
// HelpMenu: null,
|
||||||
|
// ZoomMenu: null,
|
||||||
|
// MainMenu: null,
|
||||||
|
// Minimap: null,
|
||||||
|
// StylePanel: null,
|
||||||
|
PageMenu: null,
|
||||||
|
// NavigationPanel: null,
|
||||||
|
// Toolbar: null,
|
||||||
|
// KeyboardShortcutsDialog: null,
|
||||||
|
// QuickActions: null,
|
||||||
|
// HelperButtons: null,
|
||||||
|
DebugPanel: null,
|
||||||
|
DebugMenu: null,
|
||||||
|
// SharePanel: null,
|
||||||
|
// MenuPanel: null,
|
||||||
|
// TopPanel: null,
|
||||||
|
}}
|
||||||
|
onMount={(editor: Editor) => {
|
||||||
|
editor.sideEffects.registerAfterChangeHandler('shape', (_prevShape, _nextShape) => {
|
||||||
|
})
|
||||||
|
// editor.getRenderingShapes()
|
||||||
|
// editor.getPages()
|
||||||
|
// editor.createShapes().zoomToFit({ animation: { duration: 0 } })
|
||||||
|
}}>
|
||||||
|
</Tldraw>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default TldrawView
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// 常量配置
|
||||||
|
enum constant {
|
||||||
|
CONFIG_TITLE = 'vue3-vite-ts-pinia',
|
||||||
|
CONFIG_REQUEST_TIMEOUT_TIME = 10000, // 请求超时时间
|
||||||
|
CONFIG_TOKEN = 'TOKEN', // token
|
||||||
|
CONFIG_USERINFO = 'USERINFO', // 用户信息
|
||||||
|
CONFIG_STATUS_CODE_SUCCESS = 100, // 自定义代码 100成功、101失败
|
||||||
|
CONFIG_STATUS_CODE_ERROR = 101,
|
||||||
|
CONFIG_USERNAME_KEY = 'USERNAME', // 用户名
|
||||||
|
CONFIG_PASSWORD_KEY = 'PASSWORD', // 密码
|
||||||
|
CONFIG_IS_REMEMBER_KEY = 'REMEMBER', // 是否记住密码
|
||||||
|
CONFIG_CODE_SUCCESS = 200, // 成功码
|
||||||
|
}
|
||||||
|
// 常规配置
|
||||||
|
const config = {}
|
||||||
|
|
||||||
|
export { config, constant }
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import App from './App.tsx'
|
||||||
|
import '@/utils/styles/main.css'
|
||||||
|
import { HashRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<HashRouter>
|
||||||
|
<App />
|
||||||
|
</HashRouter>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
.index {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.indexOperation {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indexBtns {
|
||||||
|
padding: 10px 0 30px;
|
||||||
|
margin: 0 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid #2C2C2C;
|
||||||
|
|
||||||
|
.indexBtnsJoin {
|
||||||
|
background-color: #FFCFEB;
|
||||||
|
color: red;
|
||||||
|
margin-left: 22px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten(#FFCFEB, 5%) !important;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: darken(#FFCFEB, 5%) !important;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.indexContent {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.indexContentTitle {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indexContentList {
|
||||||
|
overflow-y: scroll;
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
border: 1px solid #353741;
|
||||||
|
margin-bottom: 34px;
|
||||||
|
background-color: #1E1E1F;
|
||||||
|
width: calc(98% / 3);
|
||||||
|
padding: 20px 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
font-size: 18px;
|
||||||
|
color: white;
|
||||||
|
flex-grow: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: #767676;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
margin-top: 22px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: #767676;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
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";
|
||||||
|
const Index: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [list, setList] = useState([{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}])
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.index}>
|
||||||
|
<div className={styles.indexOperation}>
|
||||||
|
<Operation></Operation>
|
||||||
|
</div>
|
||||||
|
<div className={styles.indexBtns}>
|
||||||
|
<Button type="primary"
|
||||||
|
icon={<img src="/src/assets/icon8.png" alt="" />}
|
||||||
|
className='m-ant-btn drag'>
|
||||||
|
新建会议室
|
||||||
|
</Button>
|
||||||
|
<Button type="primary"
|
||||||
|
icon={<img src="/src/assets/icon7.png" alt="" />}
|
||||||
|
className={`${styles.indexBtnsJoin} drag`}>
|
||||||
|
加入会议
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.indexContent}>
|
||||||
|
<div className={styles.indexContentTitle}>
|
||||||
|
会议室列表
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.indexContentList} drag`}>
|
||||||
|
{list.map((_item, index: number) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.indexContentListItem} key={index}>
|
||||||
|
<div>
|
||||||
|
<div>奉节中学期末考分析总结会议</div>
|
||||||
|
<div>
|
||||||
|
<img src="/src/assets/icon11.png" alt="" />
|
||||||
|
<span>2人</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<span>252535356565</span>
|
||||||
|
<img src="/src/assets/icon10.png" alt="" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button type="primary" danger>设置</Button>
|
||||||
|
<Button type="primary"
|
||||||
|
iconPosition={'end'}
|
||||||
|
onClick={() => {
|
||||||
|
navigate('/meeting')
|
||||||
|
}}
|
||||||
|
icon={<img src="/src/assets/icon9.png" alt="" />}
|
||||||
|
className='m-ant-btn'>
|
||||||
|
进入
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<div style={{ visibility: 'hidden' }}></div>
|
||||||
|
<div style={{ visibility: 'hidden' }}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Index
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
.user {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.userOperation {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userBtns {
|
||||||
|
padding: 10px 0 30px;
|
||||||
|
margin: 0 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.userBtnsLeft {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.userBtnsDel {
|
||||||
|
background-color: #FFCFEB;
|
||||||
|
color: red;
|
||||||
|
margin-left: 22px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten(#FFCFEB, 5%) !important;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: darken(#FFCFEB, 5%) !important;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.userBtnsRight {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>button {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.userContent {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 30px 20px 30px 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.userContentPagination {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: #8B8787;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
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 { 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: `在线`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const User: React.FC = () => {
|
||||||
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
|
||||||
|
setSelectedRowKeys(newSelectedRowKeys);
|
||||||
|
};
|
||||||
|
const rowSelection = {
|
||||||
|
selectedRowKeys,
|
||||||
|
onChange: onSelectChange,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.user}>
|
||||||
|
<div className={styles.userOperation}>
|
||||||
|
<Operation></Operation>
|
||||||
|
</div>
|
||||||
|
<div className={styles.userBtns}>
|
||||||
|
<div className={`${styles.userBtnsLeft} drag`}>
|
||||||
|
<Button type="primary"
|
||||||
|
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}>
|
||||||
|
删除用户
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.userBtnsRight} drag`}>
|
||||||
|
<Input placeholder="请输入用户名" prefix={<SearchOutlined style={{ color: 'white' }} />} />
|
||||||
|
<Button className='m-border-ant-button'>查询</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.userContent} drag`}>
|
||||||
|
<Table
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data}
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ y: '64vh' }}
|
||||||
|
style={{ width: '77.6vw', flexGrow: 1 }}
|
||||||
|
/>
|
||||||
|
<div className={styles.userContentPagination}>
|
||||||
|
<span>共653项数据</span>
|
||||||
|
<Pagination size="small" total={50} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default User
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
.home {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.homeLeft {
|
||||||
|
width: 300px;
|
||||||
|
background-color: rgb(31, 33, 37);
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 20px 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
@for $i from 1 through 4 {
|
||||||
|
>div:nth-child(#{$i}) {
|
||||||
|
@if $i ==1 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-right: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: white;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if $i ==2 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 20px 0 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 82px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
>div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>span:nth-child(1) {
|
||||||
|
font-size: 36px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
>span:nth-child(2) {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #979797;
|
||||||
|
width: 16px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #979797;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if $i ==3 {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
@for $i from 1 through 20 {
|
||||||
|
>div:nth-child(#{$i}) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 48px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
>img {
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>span {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #98989A;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(47, 48, 50);
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: #5575F2 !important;
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if $i ==4 {
|
||||||
|
border-top: #565656 solid 1px;
|
||||||
|
padding-top: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
@for $i from 1 through 2 {
|
||||||
|
>div:nth-child(#{$i}) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 48px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0px 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
@if $i ==1 {
|
||||||
|
background: url('/src/assets/icon16.png') no-repeat center/cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if $i ==2 {
|
||||||
|
background: url('/src/assets/icon15.png') no-repeat center/cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
>span {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #98989A;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
>div {
|
||||||
|
@if $i ==1 {
|
||||||
|
background: url('/src/assets/icon16-active.png') no-repeat center/cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if $i ==2 {
|
||||||
|
background: url('/src/assets/icon15-active.png') no-repeat center/cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.homeRight {
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: rgb(22, 25, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
import styles from '@/page/Home/index.module.scss'
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Outlet, useNavigate } from 'react-router-dom';
|
||||||
|
import { Popconfirm } from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import 'dayjs/locale/zh-cn'
|
||||||
|
dayjs.locale('zh-cn');
|
||||||
|
type navListType = {
|
||||||
|
title: string;
|
||||||
|
icon: string;
|
||||||
|
path: string;
|
||||||
|
isHover: boolean;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Home: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [navList, setNavList] = useState<navListType[]>([
|
||||||
|
{
|
||||||
|
title: '首页',
|
||||||
|
icon: '/src/assets/icon12',
|
||||||
|
isHover: false,
|
||||||
|
isActive: true,
|
||||||
|
path: '/home/index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '人员',
|
||||||
|
icon: '/src/assets/icon13',
|
||||||
|
isHover: false,
|
||||||
|
isActive: false,
|
||||||
|
path: '/home/user'
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const [dateInfo, setDateInfo] = useState<{
|
||||||
|
work: string;
|
||||||
|
time: string;
|
||||||
|
specific: string;
|
||||||
|
}>({
|
||||||
|
work: '',
|
||||||
|
time: '',
|
||||||
|
specific: '',
|
||||||
|
})
|
||||||
|
useEffect(() => {
|
||||||
|
const updateTime = () => {
|
||||||
|
setDateInfo({
|
||||||
|
work: dayjs().format('ddd'),
|
||||||
|
time: dayjs().format('HH:mm'),
|
||||||
|
specific: dayjs().format('YYYY/MM/DD')
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const timer = setInterval(updateTime, 1000);
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const changtNavList = (index: number, bool?: boolean): void => {
|
||||||
|
const newNavList = [...navList];
|
||||||
|
if (typeof bool === 'boolean') {
|
||||||
|
newNavList[index].isHover = bool;
|
||||||
|
} else {
|
||||||
|
newNavList.forEach((item) => {
|
||||||
|
item.isActive = false;
|
||||||
|
})
|
||||||
|
newNavList[index].isActive = true;
|
||||||
|
navigate(newNavList[index].path)
|
||||||
|
}
|
||||||
|
setNavList(newNavList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.home}>
|
||||||
|
<div className={styles.homeLeft}>
|
||||||
|
<div className='drag'>
|
||||||
|
<div>
|
||||||
|
<img src="/src/assets/avatar.png" alt="" />
|
||||||
|
</div>
|
||||||
|
<span>欢迎您,u0001</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img src="/src/assets/icon14.png" alt="" />
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<span>{dateInfo.time}</span>
|
||||||
|
<span>{dateInfo.work}</span>
|
||||||
|
</div>
|
||||||
|
<div>{dateInfo.specific}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{navList.map((item: navListType, index: number) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`${item.isActive ? styles.active : ''} drag`}
|
||||||
|
title={item.title}
|
||||||
|
onMouseLeave={() => changtNavList(index, false)}
|
||||||
|
onMouseEnter={() => changtNavList(index, true)}
|
||||||
|
onClick={() => changtNavList(index)}>
|
||||||
|
<div>
|
||||||
|
{item.isHover || item.isActive ?
|
||||||
|
<img src={item.icon + '-active.png'} alt="" /> :
|
||||||
|
<img src={item.icon + '.png'} alt="" />}
|
||||||
|
</div>
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className='drag' title='设置'>
|
||||||
|
<div></div>
|
||||||
|
<span>设置</span>
|
||||||
|
</div>
|
||||||
|
<Popconfirm
|
||||||
|
title="提示"
|
||||||
|
description="确认退出吗?"
|
||||||
|
onConfirm={() => {
|
||||||
|
window.electron.setMainWindowSize({
|
||||||
|
width: 752,
|
||||||
|
height: 520,
|
||||||
|
key: 'login'
|
||||||
|
})
|
||||||
|
navigate('/login')
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
|
||||||
|
}}
|
||||||
|
okText="确认"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<div className='drag' title='退出'>
|
||||||
|
<div></div>
|
||||||
|
<span>退出</span>
|
||||||
|
</div>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.homeRight}>
|
||||||
|
<Outlet></Outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Home
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
.login {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #16191E;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.loginBg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 370px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginContent {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 20px 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
@for $i from 1 through 3 {
|
||||||
|
>div:nth-child(#{$i}) {
|
||||||
|
@if $i ==1 {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.quit {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin-top: 40px;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 126px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if $i ==2 {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.operation {
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.ant-checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if $i ==3 {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 1px;
|
||||||
|
background-color: #3F3F3F;
|
||||||
|
}
|
||||||
|
|
||||||
|
>span {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0 4px;
|
||||||
|
color: #7A7A7A;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 34px;
|
||||||
|
|
||||||
|
@for $i from 1 through 2 {
|
||||||
|
>div:nth-child(#{$i}) {
|
||||||
|
@if $i ==1 {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@else if $i ==2 {
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #FFCFEB;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 56px;
|
||||||
|
margin-left: 4px;
|
||||||
|
transition: 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten(#FFCFEB, 5%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: darken(#FFCFEB, 5%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录页固定大小不允许放大缩小,固需要单独写样式~!
|
||||||
|
.loginInput {
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginInputIcon {
|
||||||
|
:global {
|
||||||
|
.ant-input {
|
||||||
|
height: 34px;
|
||||||
|
line-height: 34px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginButton {
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
|
||||||
|
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 { storage } from '@/utils'
|
||||||
|
const Login: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [accountPasswordStatus, setAccountPasswordStatus] = useState<boolean>(false);
|
||||||
|
const [operation, setOperation] = useState<{
|
||||||
|
isRememberPassword: boolean;
|
||||||
|
isAutoLogin: boolean;
|
||||||
|
account: string;
|
||||||
|
password: string;
|
||||||
|
options: { label: string; value: string }[];
|
||||||
|
optionsValue: string[];
|
||||||
|
}>({
|
||||||
|
isRememberPassword: false,
|
||||||
|
isAutoLogin: false,
|
||||||
|
account: '',
|
||||||
|
password: '',
|
||||||
|
options: [
|
||||||
|
{ label: '记住密码', value: 'isRememberPassword' },
|
||||||
|
{ label: '自动登录', value: 'isAutoLogin' },
|
||||||
|
],
|
||||||
|
optionsValue: []
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (storage.getItem('login')) {
|
||||||
|
const login = JSON.parse(storage.getItem('login') as string);
|
||||||
|
const data = {
|
||||||
|
isRememberPassword: login.isRememberPassword,
|
||||||
|
isAutoLogin: login.isAutoLogin,
|
||||||
|
optionsValue: login.optionsValue,
|
||||||
|
}
|
||||||
|
if (login.isRememberPassword) {
|
||||||
|
setOperation({
|
||||||
|
...operation,
|
||||||
|
...data,
|
||||||
|
account: login.account,
|
||||||
|
password: login.password,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setOperation({
|
||||||
|
...operation,
|
||||||
|
...data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 退出
|
||||||
|
const quitClick = (): void => {
|
||||||
|
window.electron.setViewStatus('quit')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetClick = (): void => {
|
||||||
|
setOperation({
|
||||||
|
...operation,
|
||||||
|
account: '',
|
||||||
|
password: '',
|
||||||
|
})
|
||||||
|
setAccountPasswordStatus(false)
|
||||||
|
}
|
||||||
|
// 继续
|
||||||
|
const continueClick = (): void => {
|
||||||
|
setAccountPasswordStatus(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置勾选
|
||||||
|
const changeOptionsValue = (checkedValues: string[]): void => {
|
||||||
|
setOperation({
|
||||||
|
...operation,
|
||||||
|
optionsValue: checkedValues,
|
||||||
|
})
|
||||||
|
storage.setItem('login', JSON.stringify({
|
||||||
|
isRememberPassword: checkedValues.includes('isRememberPassword'),
|
||||||
|
isAutoLogin: checkedValues.includes('isAutoLogin'),
|
||||||
|
account: operation.account,
|
||||||
|
password: operation.password,
|
||||||
|
optionsValue: checkedValues,
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
const loginClick = (): void => {
|
||||||
|
storage.setItem('login', JSON.stringify({
|
||||||
|
isRememberPassword: operation.optionsValue.includes('isRememberPassword'),
|
||||||
|
isAutoLogin: operation.optionsValue.includes('isAutoLogin'),
|
||||||
|
account: operation.account,
|
||||||
|
password: operation.password,
|
||||||
|
optionsValue: operation.optionsValue,
|
||||||
|
}))
|
||||||
|
window.electron.setMainWindowSize({
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
})
|
||||||
|
navigate('/home')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.login}>
|
||||||
|
<div className={styles.loginBg}>
|
||||||
|
<img src="/src/assets/icon1.png" alt="" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.loginContent}>
|
||||||
|
<div>
|
||||||
|
<div className={styles.quit}>
|
||||||
|
<img src="/src/assets/icon2.png" alt="" title='退出' className='drag' onClick={quitClick} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.logo}>
|
||||||
|
<img src="/src/assets/icon4.png" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
value={operation.account}
|
||||||
|
onChange={e => {
|
||||||
|
setOperation({
|
||||||
|
...operation,
|
||||||
|
account: e.target.value
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className={`${styles.loginInputIcon} drag`}
|
||||||
|
style={{ marginBottom: '12px' }}
|
||||||
|
placeholder="请输入账号"
|
||||||
|
prefix={<img src="/src/assets/icon5.png" alt="" />}
|
||||||
|
suffix={
|
||||||
|
<span
|
||||||
|
style={{ color: '#47D3D0', cursor: 'pointer' }}
|
||||||
|
onClick={resetClick}
|
||||||
|
>重置
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{operation.account && !accountPasswordStatus ? <div style={{ marginTop: '36px' }} className='drag'>
|
||||||
|
<Button type="primary"
|
||||||
|
onClick={continueClick}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
className={`${styles.loginButton} m-ant-btn`}
|
||||||
|
>继续</Button>
|
||||||
|
</div> : null}
|
||||||
|
{accountPasswordStatus ? <div>
|
||||||
|
<Input.Password
|
||||||
|
value={operation.password}
|
||||||
|
onChange={e => {
|
||||||
|
setOperation({
|
||||||
|
...operation,
|
||||||
|
password: e.target.value
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className={`${styles.loginInputIcon} drag`}
|
||||||
|
style={{ marginBottom: '36px' }}
|
||||||
|
placeholder="请输入密码"
|
||||||
|
prefix={<img src="/src/assets/icon6.png" alt="" />}
|
||||||
|
/>
|
||||||
|
<Button type="primary" className={`${styles.loginButton} drag m-ant-btn`} onClick={loginClick} style={{ width: '100%' }}>登录</Button>
|
||||||
|
<div className={`${styles.operation} drag`}>
|
||||||
|
<Checkbox.Group
|
||||||
|
options={operation.options}
|
||||||
|
value={operation.optionsValue}
|
||||||
|
onChange={changeOptionsValue}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div> : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<div></div>
|
||||||
|
<span>or</span>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.code} drag`}>
|
||||||
|
<div>
|
||||||
|
<Input placeholder="输入会议号" className={`${styles.loginInput}`} />
|
||||||
|
</div>
|
||||||
|
<div><img src="/src/assets/icon3.png" alt="" /></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login
|
||||||
|
|
@ -0,0 +1,579 @@
|
||||||
|
.meeting {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(16, 19, 23);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.meetingHeader {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #2C2C2C;
|
||||||
|
padding: 10px 0 10px 52px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
margin-right: 20px;
|
||||||
|
display: flex;
|
||||||
|
transform: rotate(180deg) scaleX(-1);
|
||||||
|
|
||||||
|
>span {
|
||||||
|
background-color: #02B188;
|
||||||
|
width: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 through 4 {
|
||||||
|
>span:nth-child(#{$i}) {
|
||||||
|
height: calc(6px * #{$i} + 2px)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #EEEEEE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
color: #EEEEEE;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(3) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.meetingGrayButton {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #31353A;
|
||||||
|
color: #EEEEEE;
|
||||||
|
width: 154px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 20px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten(#31353A, 4%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: darken(#31353A, 4%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
margin: -10px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingContent {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.meetingContentBody {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.meetingContentBodyLeft {
|
||||||
|
height: 100%;
|
||||||
|
width: 0px;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-bottom: 18px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.meetingContentSwiper {
|
||||||
|
margin: 20px 0 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.swiper-slide-active {
|
||||||
|
border: 1px solid #EBEBEB;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingContentSwiperCard {
|
||||||
|
height: 196px;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
.meetingContentSwiperCardVdeio {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background:black url('/src/assets/error.png') no-repeat center/30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingContentVideo {
|
||||||
|
flex-grow: 1;
|
||||||
|
position: relative;
|
||||||
|
width: 1378px;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: 0px;
|
||||||
|
display: flex;
|
||||||
|
.meetingContentVideoDom {
|
||||||
|
border: 1px red solid;
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
background:black url('/src/assets/error.png') no-repeat center/30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingContentUser {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.meetingContentUserRole {
|
||||||
|
background: #FDC229;
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 44px;
|
||||||
|
height: 34px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 6px;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingContentUserName {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #0000009E;
|
||||||
|
border-radius: 6px;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0 4px;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: #EEEEEE;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingContentBodyRight {
|
||||||
|
width: 374px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.meetingUserList {
|
||||||
|
height: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #16191E;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.meetingUserListTitle {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: #EEEEEE;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
>img {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingUserListContent {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 0px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 20px 0;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #F3F3F5;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
>div {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 17px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingUserListFooter {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #31353A;
|
||||||
|
color: #EEEEEE;
|
||||||
|
width: 154px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten(#31353A, 4%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: darken(#31353A, 4%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingUserChat {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #16191E;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.meetingUserChatTitle {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid #23272E;
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: #EEEEEE;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
>img {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingUserChatContent {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 0px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid #23272E;
|
||||||
|
|
||||||
|
.meetingUserChatContentLeft {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #F3F3F5;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
>div {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
background-color: #5575F2;
|
||||||
|
color: #F3F3F5;
|
||||||
|
max-width: 266px;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 0 25px 25px 25px;
|
||||||
|
margin: 10px 0 10px 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingUserChatContentRight {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
|
||||||
|
>span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #F3F3F5;
|
||||||
|
}
|
||||||
|
|
||||||
|
>div {
|
||||||
|
margin-left: 4px;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
background-color: #464E6B;
|
||||||
|
color: #F3F3F5;
|
||||||
|
max-width: 266px;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 25px 0 25px 25px;
|
||||||
|
margin: 10px 40px 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingUserChatInput {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 220px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.meetingContentFooter {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: #07090B;
|
||||||
|
padding: 10px 50px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
>img {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: #EEEEEE;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.meetingContentFooterPopover {
|
||||||
|
>div {
|
||||||
|
width: 220px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #EEEEEE;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
background-color: #FF5219;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten(#FF5219, 4%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: darken(#FF5219, 4%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
background-color: #31353A;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten(#31353A, 4%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: darken(#31353A, 4%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(3) {
|
||||||
|
background-color: #101418;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten(#101418, 4%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: darken(#101418, 4%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sharedScreenModal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
>div:nth-child(1) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
align-content: flex-start;
|
||||||
|
padding: 50px 0 0;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: calc(100% / 4);
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
>span {
|
||||||
|
color: #EEEEEE;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 4px;
|
||||||
|
width: 144px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
>div {
|
||||||
|
width: 144px;
|
||||||
|
height: 94px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
>img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>img {
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
left: 0;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
>div {
|
||||||
|
border: 1px solid #EBEBEB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
>div {
|
||||||
|
border: 1px solid #EBEBEB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
>div:nth-child(2) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,355 @@
|
||||||
|
import styles from '@/page/Meeting/index.module.scss'
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Operation from '@/components/Operation';
|
||||||
|
import { Navigation, Pagination } from 'swiper/modules';
|
||||||
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
|
import 'swiper/css';
|
||||||
|
import 'swiper/css/navigation';
|
||||||
|
import 'swiper/css/pagination';
|
||||||
|
import { Button, Input, Popover, Modal, Checkbox, message } from "antd";
|
||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { thumbImageBufferToBase64 } from '@/utils/package/base64'
|
||||||
|
const Meeting: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [statusList, setStatusList] = useState({
|
||||||
|
userList: false,
|
||||||
|
userChatList: false,
|
||||||
|
})
|
||||||
|
const [isSharedScreenModal, setIsSharedScreenModal] = useState(false);
|
||||||
|
const [sharedScreenList, setSharedScreenList] = useState<any>([]);
|
||||||
|
const [sharedScreenItem, setSharedScreenItem] = useState<any>('');
|
||||||
|
const [footerList] = useState([
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: '关闭声音',
|
||||||
|
icon: '/src/assets/icon22.png',
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '关闭视频',
|
||||||
|
icon: '/src/assets/icon23.png',
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: '共享屏幕',
|
||||||
|
icon: '/src/assets/icon24.png',
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '共享文件',
|
||||||
|
icon: '/src/assets/icon25.png',
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '邀请人员',
|
||||||
|
icon: '/src/assets/icon26.png',
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '录制',
|
||||||
|
icon: '/src/assets/icon27.png',
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '设置向导',
|
||||||
|
icon: '/src/assets/icon28.png',
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '结束',
|
||||||
|
icon: '/src/assets/icon29.png',
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
title: '成员列表',
|
||||||
|
icon: '/src/assets/icon30.png',
|
||||||
|
active: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '聊天',
|
||||||
|
icon: '/src/assets/icon31.png',
|
||||||
|
active: false
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
const [list] = useState<number[]>([1, 2, 3, 4, 5, 6, 7])
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [videoID, setVideoID] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const changeStatusList = (row: any): void => {
|
||||||
|
switch (row.title) {
|
||||||
|
case '成员列表':
|
||||||
|
setStatusList({
|
||||||
|
userList: true,
|
||||||
|
userChatList: false,
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case '聊天':
|
||||||
|
setStatusList({
|
||||||
|
userList: false,
|
||||||
|
userChatList: true,
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case '共享屏幕':
|
||||||
|
getDesktopCapturerVideo()
|
||||||
|
setIsSharedScreenModal(true)
|
||||||
|
break;
|
||||||
|
case '关闭声音':
|
||||||
|
window.electron.setJoinChannel()
|
||||||
|
setVideoID(window.electron.getVideoId())
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeVdeio = async (bool: boolean): Promise<void> => {
|
||||||
|
if (bool) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let data = sharedScreenList.find((item: any) => item.sourceId === sharedScreenItem.sourceId)
|
||||||
|
if (data) {
|
||||||
|
setIsSharedScreenModal(false)
|
||||||
|
window.electron.setDesktopCapturerVideo(sharedScreenItem)
|
||||||
|
setVideoID(window.electron.getVideoId())
|
||||||
|
} else {
|
||||||
|
message.error('请选择应用!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getDesktopCapturerVideo = (): void => {
|
||||||
|
window.electron.getDesktopCapturerVideo().then((res: any) => {
|
||||||
|
if (sharedScreenList.length !== res.length) {
|
||||||
|
res.forEach((item: any) => {
|
||||||
|
if (item.thumbImage.buffer) {
|
||||||
|
item.thumbnailUrl = thumbImageBufferToBase64(item.thumbImage)
|
||||||
|
}
|
||||||
|
if (item.iconImage.buffer) {
|
||||||
|
item.iconDataUrl = thumbImageBufferToBase64(item.iconImage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setSharedScreenList(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.meeting}>
|
||||||
|
<div className={styles.meetingHeader}>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<div>00:13:45</div>
|
||||||
|
</div>
|
||||||
|
<div>会议号:2323235</div>
|
||||||
|
<div className='drag'>
|
||||||
|
<div className={styles.meetingGrayButton}>演讲者模式</div>
|
||||||
|
<Operation></Operation>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.meetingContent}>
|
||||||
|
<div className={styles.meetingContentBody}>
|
||||||
|
<div className={styles.meetingContentBodyLeft}>
|
||||||
|
<div className={`${styles.meetingContentSwiper} drag`}>
|
||||||
|
<Swiper
|
||||||
|
loop={false}
|
||||||
|
centeredSlides={true}
|
||||||
|
modules={[Navigation, Pagination]}
|
||||||
|
spaceBetween={20}
|
||||||
|
slidesPerView={6}
|
||||||
|
navigation
|
||||||
|
pagination={false}
|
||||||
|
scrollbar={{ draggable: true }}
|
||||||
|
onSwiper={(swiper: any) => {
|
||||||
|
swiper.on('click', (e: any) => {
|
||||||
|
swiper.slideTo(e.clickedIndex)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onSlideChange={() => { }}
|
||||||
|
>
|
||||||
|
{list.map((item) =>
|
||||||
|
<SwiperSlide key={item}>
|
||||||
|
<div className={styles.meetingContentSwiperCard}>
|
||||||
|
<video src="" className={styles.meetingContentSwiperCardVdeio} ></video>
|
||||||
|
<div style={{ color: 'white', position: 'absolute', left: 0, top: 0 }}>{item}</div>
|
||||||
|
{meetingContentUser()}
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
)}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.meetingContentVideo} drag`}>
|
||||||
|
<div className={styles.meetingContentVideoDom} id='video1'></div>
|
||||||
|
<div className={styles.meetingContentVideoDom} id='video2'></div>
|
||||||
|
{/* <div className={styles.meetingContentVideoDom} id={videoID}></div> */}
|
||||||
|
{/* {meetingContentUser()} */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
(statusList.userList || statusList.userChatList) ? (
|
||||||
|
<div className={styles.meetingContentBodyRight}>
|
||||||
|
{statusList.userList ?
|
||||||
|
<div className={styles.meetingUserList}>
|
||||||
|
<div className={styles.meetingUserListTitle}>
|
||||||
|
<span>成员列表</span>
|
||||||
|
<img src="/src/assets/icon18.png" alt="" className='drag' onClick={() => {
|
||||||
|
setStatusList({
|
||||||
|
userList: false,
|
||||||
|
userChatList: false,
|
||||||
|
})
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
<Input placeholder="请输入用户名" className='drag' prefix={<SearchOutlined style={{ color: 'white' }} />} />
|
||||||
|
<div className={styles.meetingUserListContent}>
|
||||||
|
{list.map((item: number) =>
|
||||||
|
<div key={item} className='drag'>
|
||||||
|
<div>
|
||||||
|
<div><img src="/src/assets/avatar.png" alt="" /></div>
|
||||||
|
<span>潇潇<span style={{ color: '#02B188', marginLeft: '4px' }}>主持人</span></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img src="/src/assets/icon22.png" alt="" />
|
||||||
|
<img src="/src/assets/icon23.png" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.meetingUserListFooter} drag`}>
|
||||||
|
<div>邀请</div>
|
||||||
|
<div>全员静音</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div className={styles.meetingUserChat}>
|
||||||
|
<div className={styles.meetingUserChatTitle}>
|
||||||
|
<span>聊天</span>
|
||||||
|
<img src="/src/assets/icon18.png" alt="" className='drag' onClick={() => {
|
||||||
|
setStatusList({
|
||||||
|
userList: false,
|
||||||
|
userChatList: false,
|
||||||
|
})
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.meetingUserChatContent}>
|
||||||
|
{list.map((item: number) =>
|
||||||
|
<div key={item} className={`${styles.meetingUserChatContentLeft} drag`}>
|
||||||
|
<div>
|
||||||
|
<div><img src="/src/assets/avatar.png" alt="" /></div>
|
||||||
|
<span>潇潇</span>
|
||||||
|
</div>
|
||||||
|
<div>哈哈哈哈</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.meetingUserChatInput} drag`}>
|
||||||
|
<Input.TextArea placeholder="请输入消息" style={{ flexGrow: 1 }}></Input.TextArea>
|
||||||
|
<Button type="primary" className='m-ant-btn' style={{ flexShrink: 0, marginTop: '4px' }}>发送</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className={styles.meetingContentFooter}>
|
||||||
|
{footerList.map((item, itemIndex) => {
|
||||||
|
return (
|
||||||
|
<div key={itemIndex}>
|
||||||
|
{item.map((row, rowIndex) => {
|
||||||
|
return (
|
||||||
|
row.title === '结束' ?
|
||||||
|
<Popover key={rowIndex}
|
||||||
|
content={
|
||||||
|
<div className='meetingContentFooterPopover'>
|
||||||
|
<div onClick={() => {
|
||||||
|
navigate(-1)
|
||||||
|
}}>全员结束会议</div>
|
||||||
|
<div onClick={() => { navigate(-1) }}>仅自己离开</div>
|
||||||
|
<div onClick={() => { setOpen(false) }}>取消</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
title=""
|
||||||
|
trigger="click"
|
||||||
|
open={open}
|
||||||
|
onOpenChange={() => setOpen(true)}
|
||||||
|
>
|
||||||
|
<div className='drag'>
|
||||||
|
<img src={row.icon} alt="" />
|
||||||
|
<span>{row.title}</span>
|
||||||
|
</div>
|
||||||
|
</Popover> :
|
||||||
|
<div className='drag' onClick={() => changeStatusList(row)} key={rowIndex}>
|
||||||
|
<img src={row.icon} alt="" />
|
||||||
|
<span>{row.title}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Modal title="共享屏幕" open={isSharedScreenModal} footer={null} closable={false} centered width={'40vw'}>
|
||||||
|
<div className={styles.sharedScreenModal}>
|
||||||
|
<div>
|
||||||
|
{sharedScreenList.map((item: any, index: number) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={sharedScreenItem.sourceId === item.sourceId ? styles.active : ''}
|
||||||
|
key={index}
|
||||||
|
onClick={() => {
|
||||||
|
setSharedScreenItem(item)
|
||||||
|
}}>
|
||||||
|
{item.iconDataUrl ? <img src={item.iconDataUrl} alt="" /> : ''}
|
||||||
|
<div><img src={item.thumbnailUrl} alt="" /></div>
|
||||||
|
<span>{item.sourceTitle}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Checkbox onChange={() => {
|
||||||
|
|
||||||
|
}}>共享电脑音频</Checkbox>
|
||||||
|
<div>
|
||||||
|
<Button type="primary" onClick={() => { setIsSharedScreenModal(false) }} style={{ backgroundColor: '#31353A', marginRight: '14px' }}>取消</Button>
|
||||||
|
<Button type="primary" className='m-ant-btn' onClick={() => changeVdeio(false)}>共享</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const meetingContentUser = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.meetingContentUser}>
|
||||||
|
<div className={styles.meetingContentUserRole}>
|
||||||
|
<img src="/src/assets/icon32.png" alt="" />
|
||||||
|
</div>
|
||||||
|
<div className={styles.meetingContentUserName}>
|
||||||
|
<img src="/src/assets/icon22.png" alt="" />
|
||||||
|
<span>张大龙</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Meeting
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const NotFound: React.FC = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="notfound"></div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotFound
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// electron-env.d.ts
|
||||||
|
export interface IElectronAPI {
|
||||||
|
getDesktopCapturerVideo: () => Promise<void>;
|
||||||
|
setDesktopCapturerVideo: (data: any) => Promise<void>;
|
||||||
|
getCameraAndMicrophoneMedia: () => Promise<void>;
|
||||||
|
setMainWindowSize: (config: any) => void;
|
||||||
|
getIsMaximized: () => Promise<boolean>;
|
||||||
|
setViewStatus: (status: 'quit' | 'maximize' | 'minimize' | 'unmaximize') => void;
|
||||||
|
setJoinChannel: () => Promise<void>;
|
||||||
|
getVideoId: () => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
electron: IElectronAPI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
declare module 'react-dom/client';
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import storage from './package/storage'
|
||||||
|
import request from './request'
|
||||||
|
|
||||||
|
export { storage, request }
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
appid: "dcfc466a6ecb4a1f972630065dfb1e75",
|
||||||
|
token: "007eJxTYNDz89yosT64YN5i3snT6nzY/aVOvs1Tmu3zc9r6qUkXHyxSYEhJTks2MTNLNEtNTjJJNEyzNDcyMzYwMDNNSUsyTDU3FQxuTmsIZGSQn/iShZEBAkF8bgZDc3MjYwMLU2MTMwYGAAHrH7M=",
|
||||||
|
channelId: '17723085346',
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { ThumbImageBuffer } from 'agora-electron-sdk';
|
||||||
|
|
||||||
|
export default (input: any) => {
|
||||||
|
const keyStr =
|
||||||
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||||
|
let output = '';
|
||||||
|
let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < input.length) {
|
||||||
|
chr1 = input[i++];
|
||||||
|
chr2 = i < input.length ? input[i++] : Number.NaN; // Not sure if the index
|
||||||
|
chr3 = i < input.length ? input[i++] : Number.NaN; // checks are needed here
|
||||||
|
|
||||||
|
enc1 = chr1 >> 2;
|
||||||
|
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||||
|
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||||
|
enc4 = chr3 & 63;
|
||||||
|
|
||||||
|
if (isNaN(chr2)) {
|
||||||
|
enc3 = enc4 = 64;
|
||||||
|
} else if (isNaN(chr3)) {
|
||||||
|
enc4 = 64;
|
||||||
|
}
|
||||||
|
output +=
|
||||||
|
keyStr.charAt(enc1) +
|
||||||
|
keyStr.charAt(enc2) +
|
||||||
|
keyStr.charAt(enc3) +
|
||||||
|
keyStr.charAt(enc4);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const thumbImageBufferToBase64 = (target?: ThumbImageBuffer) => {
|
||||||
|
if (!target) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return '';
|
||||||
|
|
||||||
|
const width = (canvas.width = target.width!);
|
||||||
|
const height = (canvas.height = target.height!);
|
||||||
|
|
||||||
|
const rowBytes = width * 4;
|
||||||
|
for (let row = 0; row < height; row++) {
|
||||||
|
const srow = row;
|
||||||
|
const imageData = ctx.createImageData(width, 1);
|
||||||
|
const start = srow * width * 4;
|
||||||
|
for (let i = 0; i < rowBytes; i += 4) {
|
||||||
|
imageData.data[i] = target.buffer![start + i + 2]!;
|
||||||
|
imageData.data[i + 1] = target.buffer![start + i + 1]!;
|
||||||
|
imageData.data[i + 2] = target.buffer![start + i]!;
|
||||||
|
imageData.data[i + 3] = target.buffer![start + i + 3]!;
|
||||||
|
}
|
||||||
|
// if (process.platform === 'win32') {
|
||||||
|
// for (let i = 0; i < rowBytes; i += 4) {
|
||||||
|
// imageData.data[i] = target.buffer![start + i + 2]!;
|
||||||
|
// imageData.data[i + 1] = target.buffer![start + i + 1]!;
|
||||||
|
// imageData.data[i + 2] = target.buffer![start + i]!;
|
||||||
|
// imageData.data[i + 3] = target.buffer![start + i + 3]!;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// for (let i = 0; i < rowBytes; ++i) {
|
||||||
|
// imageData.data[i] = target.buffer![start + i]!;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
ctx.putImageData(imageData, 0, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas.toDataURL('image/png');
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
// import AgoraRTC from "agora-rtc-sdk-ng"
|
||||||
|
|
||||||
|
// let rtcClient = null as any;
|
||||||
|
|
||||||
|
// const options = {
|
||||||
|
// appId: "dcfc466a6ecb4a1f972630065dfb1e75",
|
||||||
|
// channel: '17723085346',
|
||||||
|
// token: "007eJxTYNDlkfJ9fD3lzh2e0KI5Oyy6vdU7phyIXOpV8F36/xPOeS8VGFKS05JNzMwSzVKTk0wSDdMszY3MjA0MzExT0pIMU81Np/g0pTUEMjJExEqyMjJAIIjPzWBobm5kbGBhamxixsAAAC6mH9k=",
|
||||||
|
// uid: 'eaecab',
|
||||||
|
// };
|
||||||
|
|
||||||
|
// rtcClient = AgoraRTC.createClient({ mode: "live", codec: "vp8" });
|
||||||
|
|
||||||
|
// // 以观众身份加入房间
|
||||||
|
// export async function AudienceJoinChannel(channel: any, uid: string | number) {
|
||||||
|
// await SetRule(1)
|
||||||
|
// await rtcClient.join(options.appId, channel, options.token, uid)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 加入房间
|
||||||
|
// export async function JoinChannel(channel: any, uid: string | number) {
|
||||||
|
// await rtcClient.join(options.appId, channel, options.token, uid)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 设置用户角色 1=观众 2=主播
|
||||||
|
// export async function SetRule(type: number) {
|
||||||
|
// await rtcClient.setClientRole(type == 1 ? "audience" : "host");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 接流
|
||||||
|
// export async function ReceiveFLow(flowHandld: any) {
|
||||||
|
// rtcClient.on("user-published", async (user: any, mediaType: any) => {
|
||||||
|
// await rtcClient.subscribe(user, mediaType);
|
||||||
|
// console.log(user,'嘻嘻');
|
||||||
|
|
||||||
|
// flowHandld(user)
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 远端取消推流
|
||||||
|
// export function ChannelFlow(channelHandle: any) {
|
||||||
|
// rtcClient.on("user-unpublished", (user: any) => {
|
||||||
|
// channelHandle(user)
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 离开频道
|
||||||
|
// export function LeaveChannel() {
|
||||||
|
// rtcClient.leave()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 发布本地音频轨道
|
||||||
|
// export async function PublishAudio(deviceId: string) {
|
||||||
|
// var audioTrack = await AgoraRTC.createMicrophoneAudioTrack({
|
||||||
|
// AEC: true, // 是否开启回声消除
|
||||||
|
// AGC: true, // 是否开启自动增益
|
||||||
|
// ANS: true, // 是否开启噪声抑制
|
||||||
|
// microphoneId: deviceId // 麦克风的设备 ID
|
||||||
|
// })
|
||||||
|
// await rtcClient.publish(audioTrack)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 发布本地视频轨道 硬件设备
|
||||||
|
// export async function PublishVideo(deviceId: string) {
|
||||||
|
// var videoTrack = await AgoraRTC.createCameraVideoTrack({
|
||||||
|
// cameraId: deviceId,
|
||||||
|
// encoderConfig: '1080p_3'
|
||||||
|
// })
|
||||||
|
// await rtcClient.publish(videoTrack)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 发布本地视频轨道 屏幕共享
|
||||||
|
// export async function PublishVideoScreen(sharedScreenItem: any) {
|
||||||
|
// var screenTrack = await AgoraRTC.createScreenVideoTrack({
|
||||||
|
// encoderConfig: "1080p_1",
|
||||||
|
// electronScreenSourceId: sharedScreenItem.id,
|
||||||
|
// })
|
||||||
|
// await rtcClient.publish(screenTrack)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 获取麦克风设备列表
|
||||||
|
// export async function GetMicrophones() {
|
||||||
|
// return await AgoraRTC.getMicrophones()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 获取摄像头设备列表
|
||||||
|
// export async function GetCameras() {
|
||||||
|
// return await AgoraRTC.getCameras()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 取消发布所有流
|
||||||
|
// export async function UnpublishAll() {
|
||||||
|
// await rtcClient.unpublish()
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
class LocalStorage {
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
private static instance: LocalStorage | null = null
|
||||||
|
|
||||||
|
static getInstance() {
|
||||||
|
if (LocalStorage.instance === null) {
|
||||||
|
LocalStorage.instance = new LocalStorage()
|
||||||
|
}
|
||||||
|
return LocalStorage.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
setItem(key: string, value: any) {
|
||||||
|
localStorage.setItem(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
getItem(key: string) {
|
||||||
|
return localStorage.getItem(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeItem(key: string) {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAll() {
|
||||||
|
localStorage.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LocalStorage.getInstance()
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||||
|
import Request from './request'
|
||||||
|
import { constant } from '@/config'
|
||||||
|
|
||||||
|
// 实例化
|
||||||
|
const req = new Request({
|
||||||
|
baseURL: import.meta.env.VITE_BASE_URL_API,
|
||||||
|
timeout: constant.CONFIG_REQUEST_TIMEOUT_TIME as number,
|
||||||
|
interceptors: {
|
||||||
|
// 请求拦截器
|
||||||
|
requestInterceptors: (config: AxiosRequestConfig) => config,
|
||||||
|
// 响应拦截器 <T = AxiosResponse>(result: T)
|
||||||
|
responseInterceptors: <T = AxiosResponse>(result: T) => result,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const request = (config: any) => {
|
||||||
|
const { method = 'GET' } = config
|
||||||
|
|
||||||
|
if (method === 'get' || method === 'GET') {
|
||||||
|
config.params = config.data
|
||||||
|
}
|
||||||
|
return req.request<any>(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default request
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
import axios, { AxiosInstance, AxiosResponse } from 'axios'
|
||||||
|
import { RequestConfig, RequestInterceptors } from './types'
|
||||||
|
import { storage } from '@/utils'
|
||||||
|
import { constant } from '@/config'
|
||||||
|
import { message } from 'antd';
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
// axios实例
|
||||||
|
instance: AxiosInstance
|
||||||
|
|
||||||
|
// 拦截器对象
|
||||||
|
interceptorsObj?: RequestInterceptors
|
||||||
|
|
||||||
|
constructor(config: RequestConfig) {
|
||||||
|
// 创建实例
|
||||||
|
this.instance = axios.create(config)
|
||||||
|
// 类请求拦截器
|
||||||
|
this.instance.interceptors.request.use(
|
||||||
|
(req: any) => {
|
||||||
|
const token = localStorage.getItem('token')
|
||||||
|
if (token) {
|
||||||
|
// 如果有token给请求头加上
|
||||||
|
req.headers.Authorization = `${token}`
|
||||||
|
req.timeout = constant.CONFIG_REQUEST_TIMEOUT_TIME
|
||||||
|
}
|
||||||
|
if (req.contentType) {
|
||||||
|
req.headers["Content-Type"] = req.contentType;
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
},
|
||||||
|
(_err: any) => {
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 类响应拦截器
|
||||||
|
this.instance.interceptors.response.use(
|
||||||
|
(res: AxiosResponse) => {
|
||||||
|
const { data: resData, status, config } = res
|
||||||
|
if (status == constant.CONFIG_CODE_SUCCESS && resData && Object.prototype.toString.call(resData) == '[object Object]') {
|
||||||
|
resData.success = false
|
||||||
|
resData.code == constant.CONFIG_CODE_SUCCESS && (resData.success = true)
|
||||||
|
}
|
||||||
|
resData.headers = res.headers['content-disposition'];
|
||||||
|
if (resData.code !== 200) {
|
||||||
|
if (config.responseType === 'blob') {
|
||||||
|
const reader = new FileReader() as any;
|
||||||
|
reader.readAsText(res.data, "utf-8");
|
||||||
|
reader.onload = function () {
|
||||||
|
if (reader.result) {
|
||||||
|
try {
|
||||||
|
message.error(JSON.parse(reader.result).message)
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
message.error(resData.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resData
|
||||||
|
},
|
||||||
|
(err: any) => {
|
||||||
|
function toLogin() {
|
||||||
|
storage.removeItem(constant.CONFIG_TOKEN)
|
||||||
|
|
||||||
|
}
|
||||||
|
// 根据自己业务/接口返回做相应调整
|
||||||
|
if (err.response) {
|
||||||
|
const { status, data } = err.response
|
||||||
|
message.error(data.message || data.title)
|
||||||
|
switch (status) {
|
||||||
|
case 401:
|
||||||
|
toLogin()
|
||||||
|
break
|
||||||
|
case 403:
|
||||||
|
toLogin()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error(err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
request<T>(config: RequestConfig): Promise<T> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (config.interceptors?.requestInterceptors) {
|
||||||
|
config = config.interceptors.requestInterceptors(config)
|
||||||
|
}
|
||||||
|
this.instance
|
||||||
|
.request<any, T>(config)
|
||||||
|
.then((res: T) => {
|
||||||
|
// 如果我们为单个响应设置拦截器,这里使用单个响应的拦截器
|
||||||
|
if (config.interceptors?.responseInterceptors) {
|
||||||
|
res = config.interceptors.responseInterceptors<T>(res)
|
||||||
|
}
|
||||||
|
resolve(res || { code: 0 } as any)
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Request
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||||
|
|
||||||
|
// 实例拦截器
|
||||||
|
export interface RequestInterceptors {
|
||||||
|
// 请求拦截
|
||||||
|
requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig
|
||||||
|
requestInterceptorsCatch?: (err: any) => any
|
||||||
|
// 响应拦截
|
||||||
|
responseInterceptors?: <T = AxiosResponse>(config: T) => T
|
||||||
|
responseInterceptorsCatch?: (err: any) => any
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义传入的参数
|
||||||
|
export interface RequestConfig extends AxiosRequestConfig {
|
||||||
|
interceptors?: RequestInterceptors
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface useRequestConfig<T> extends RequestConfig {
|
||||||
|
data?: T
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +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}
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
$btn-background-color: #3F51B5;
|
||||||
|
$btn-border-background-color: #1E1E1F;
|
||||||
|
$btn-border-text-color: #5575F2;
|
||||||
|
$input-background-color: #28282C;
|
||||||
|
$input-border-color: #404145;
|
||||||
|
$table-header-background-color: #1B1E24;
|
||||||
|
$table-content-background-color: rgb(22, 25, 30);
|
||||||
|
$pagination-background-color: #20242C;
|
||||||
|
$pagination-hover-background-color: #5575F2;
|
||||||
|
|
||||||
|
// input
|
||||||
|
.ant-input {
|
||||||
|
background-color: $input-background-color !important;
|
||||||
|
border: 1px solid $input-border-color;
|
||||||
|
color: white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: #E6E6E6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-affix-wrapper {
|
||||||
|
background-color: $input-background-color !important;
|
||||||
|
border: 1px solid $input-border-color;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.ant-input {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-password-icon {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// button
|
||||||
|
.m-ant-btn.ant-btn {
|
||||||
|
background-color: $btn-background-color;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten($btn-background-color, 10%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: darken($btn-background-color, 10%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-border-ant-button.ant-btn {
|
||||||
|
border: 1px solid $btn-border-text-color !important;
|
||||||
|
color: $btn-border-text-color !important;
|
||||||
|
background-color: $btn-border-background-color !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkbox
|
||||||
|
.ant-checkbox-wrapper {
|
||||||
|
color: #848484;
|
||||||
|
|
||||||
|
.ant-checkbox {
|
||||||
|
.ant-checkbox-inner {
|
||||||
|
background-color: $input-background-color !important;
|
||||||
|
border: 1px solid $input-border-color;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background-color: $btn-background-color !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-checkbox-checked {
|
||||||
|
.ant-checkbox-inner {
|
||||||
|
background-color: $btn-background-color !important;
|
||||||
|
border: 1px solid $input-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// table
|
||||||
|
.ant-table {
|
||||||
|
background-color: $table-header-background-color !important;
|
||||||
|
border-radius: 0px !important;
|
||||||
|
|
||||||
|
.ant-table-header {
|
||||||
|
.ant-table-cell {
|
||||||
|
background-color: $table-header-background-color;
|
||||||
|
color: #808080;
|
||||||
|
box-shadow: none;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-body {
|
||||||
|
.ant-table-row {
|
||||||
|
background-color: $table-content-background-color;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
.ant-table-cell {
|
||||||
|
border-bottom: 1px solid #292F3A;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-row-selected {
|
||||||
|
.ant-table-cell {
|
||||||
|
background-color: darken($table-content-background-color, 4%) !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-cell-row-hover {
|
||||||
|
background-color: darken($table-content-background-color, 4%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pagination
|
||||||
|
.ant-pagination {
|
||||||
|
.ant-pagination-prev {
|
||||||
|
margin-right: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pagination-prev,
|
||||||
|
.ant-pagination-next {
|
||||||
|
width: 30px !important;
|
||||||
|
height: 30px !important;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $pagination-background-color;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
color: #808080;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pagination-item {
|
||||||
|
width: 30px !important;
|
||||||
|
height: 30px !important;
|
||||||
|
line-height: 30px !important;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $pagination-background-color !important;
|
||||||
|
margin-right: 10px !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $pagination-hover-background-color !important;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0px 0px 10px 0px #66C8FF;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pagination-item-active {
|
||||||
|
background: $pagination-hover-background-color !important;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0px 0px 10px 0px #66C8FF;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// popover
|
||||||
|
.ant-popover:not(.ant-popconfirm) {
|
||||||
|
.ant-popover-arrow {
|
||||||
|
&::before {
|
||||||
|
background-color: #07090B !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-popover-content {
|
||||||
|
.ant-popover-inner {
|
||||||
|
background-color: #07090B;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// modal
|
||||||
|
.ant-modal-mask {
|
||||||
|
background-color: rgba(0, 0, 0, 0.25) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal {
|
||||||
|
.ant-modal-content {
|
||||||
|
background-color: #07090B;
|
||||||
|
|
||||||
|
.ant-modal-header {
|
||||||
|
background-color: #07090B;
|
||||||
|
|
||||||
|
.ant-modal-title {
|
||||||
|
text-align: center;
|
||||||
|
color: #EEEEEE;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 10px;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.drag {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修改垂直滚动条 */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修改滚动条轨道背景色 */
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修改滚动条滑块颜色 */
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgb(52, 52, 52);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修改滚动条滑块悬停时的颜色 */
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgb(109, 109, 109);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"./auto-imports.d.ts"
|
||||||
|
],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import pxtovw from 'postcss-px-to-viewport-8-plugin'
|
||||||
|
import { resolve } from 'path'
|
||||||
|
const loder_pxtovw = pxtovw({
|
||||||
|
viewportWidth: 1900,
|
||||||
|
viewportUnit: 'vw',
|
||||||
|
selectorBlackList: ['.login','.ant-pagination']
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
css: {
|
||||||
|
postcss: {
|
||||||
|
plugins: [loder_pxtovw],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
proxy: {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
find: '@',
|
||||||
|
replacement: resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
minify: 'terser',
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||||
|
entryFileNames: 'assets/js/[name]-[hash].js',
|
||||||
|
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
|
||||||
|
compact: true,
|
||||||
|
manualChunks: {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
base: './', // 这里更改打包相对绝对路径
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
],
|
||||||
|
})
|
||||||