渲染进程代码
在 src/Views
文件夹下,我们新建一个 Register
文件夹,其中是我们的注册页面。
index.jsx
首先,我们来书写注册页的入口文件
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
App.jsx
现在,我们将 App
组件进行补全
import {
Button, Form, Input } from 'antd';
import React, {
useEffect, useState } from 'react';
import './App.scss';
import bg from './bg.jpg';
export default function App() {
const [sendCaptchaTick, setSendCaptchaTick] = useState(0);
const [sendCaptchaInterval, setSendCaptchaInterval] = useState(null);
useEffect(() => {
return () => {
if (sendCaptchaInterval) {
clearInterval(sendCaptchaInterval);
setSendCaptchaInterval(null);
}
};
}, []);
const [form] = Form.useForm();
return (
<div className='register' style={
{
backgroundImage: `url(${
bg})` }}>
<div className='container'>
<div className='title'>山大会议 注册账号</div>
<div className='inputs'>
<Form onFinish={
submitForm} autoComplete='off' form={
form}>
<Form.Item
rules={
[
{
required: true,
message: '请输入注册用的昵称',
},
{
pattern: /^[^@]+$/,
message: '昵称中不允许出现"@"',
},
]}
name={
'username'}>
<Input placeholder='请输入昵称' />
</Form.Item>
<Form.Item
rules={
[
{
required: true, message: '请输入密码' },
{
min: 6,
message: '请输入长度超过6位的密码',
},
]}
name={
'password'}>
<Input.Password placeholder='请输入密码' />
</Form.Item>
<Form.Item
validateTrigger='onBlur'
rules={
[
{
required: true,
message: '请再次输入密码',
},
({
getFieldValue }) => ({
validator(rule, value) {
if (getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject('两次输入的密码不一致');
},
}),
]}
name={
'passwordCheck'}>
<Input.Password placeholder='请再次输入密码' />
</Form.Item>
<Form.Item
rules={
[
{
required: true,
message: '请输入山大邮箱',
},
{
pattern: /^[^@]+$/,
message: '请不要再次输入"@"',
},
]}
name={
'email'}>
<Input placeholder='请输入山大邮箱' addonAfter='@mail.sdu.edu.cn' />
</Form.Item>
<Form.Item
rules={
[
{
required: true,
message: '请输入验证码',
},
]}
name={
'captcha'}>
<Input placeholder='请输入邮箱验证码' />
</Form.Item>
<Form.Item>
<div style={
{
display: 'flex', justifyContent: 'space-around' }}>
<Button
disabled={
sendCaptchaTick}
onClick={
() => {
form.validateFields(['username', 'email'])
.then(() => {
sendCaptcha(
setSendCaptchaTick,
setSendCaptchaInterval
);
})
.catch(() => {
});
}}>
{
sendCaptchaTick
? `${
sendCaptchaTick}秒后可再次发送`
: '发送验证码'}
</Button>
<Button type='primary' htmlType='submit'>
注册
</Button>
</div>
</Form.Item>
</Form>
</div>
</div>
</div>
);
}
function submitForm(values) {
console.log(values);
}
function sendCaptcha(setSendCaptchaTick, setSendCaptchaInterval) {
let sendCaptchaTick = 60;
setSendCaptchaTick(sendCaptchaTick);
const interval = setInterval(() => {
setSendCaptchaTick(--sendCaptchaTick);
if (sendCaptchaTick === 0) {
clearInterval(interval);
setSendCaptchaInterval(null);
}
}, 1000);
setSendCaptchaInterval(interval);
}
同时为该组件编写样式文件:
// App.scss
@import '~antd/dist/antd.css';
* {
margin: 0%;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
transition-delay: 111111s;
transition: color 111111s ease-out, background-color 111111s ease-out;
}
.register {
width: 100vw;
height: 100vh;
background-repeat: no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: center;
align-items: center;
.container {
width: 50%;
min-height: 60%;
background-color: rgba($color: #000, $alpha: 0.5);
transition: 300ms;
border-radius: 1rem;
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.title {
font-size: 2rem;
user-select: none;
}
&:hover,
&:focus {
backdrop-filter: blur(10px);
}
.inputs {
width: 70%;
.ant-input-affix-wrapper.ant-input-password,
.ant-input,
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
background-color: rgba($color: #000000, $alpha: 0);
color: white;
border-top: none;
border-left: none;
border-right: none;
box-shadow: none;
font-size: 1.25rem;
&::selection {
background-color: rgba($color: #000, $alpha: 0.3);
}
.ant-input-password-icon {
color: white;
}
}
.ant-form-item-has-error :not(.ant-input-disabled):not(.ant-input-borderless) {
&.ant-input {
&,
&:focus {
background-color: rgba($color: #000000, $alpha: 0);
box-shadow: none;
}
}
}
.ant-input-group-addon {
color: white;
background-color: rgba($color: #000000, $alpha: 0);
// border-top: none;
// border-right: none;
border: none;
user-select: none;
font-size: 1.25rem;
}
}
}
}
修改登录页代码
修改后的登录页代码如下:
- App.jsx:
import React from 'react';
import './App.scss';
import {
DEVICE_TYPE, exchangeMediaDevice, updateAvailableDevices } from 'Utils/Store/actions';
import store from 'Utils/Store/store';
import Index from 'Components/Index/Index';
// INFO: 由于需要在所有组件挂载之前全局引入 electron ,故只能使用带有构造函数的类声明 App 组件
export default class App extends React.Component {
constructor(props) {
super(props);
window.ipcRenderer = window.require('electron').ipcRenderer; // 全局引入 electron 模块
}
componentDidMount() {
this.overwriteGetDisplayMedia();
this.getUserMediaDevices();
}
render() {
return (
<div className='App'>
<Index />
</div>
);
}
/**
* 重写 window.mediaDevices.getDisplayMedia() 方法
*/
overwriteGetDisplayMedia() {
window.navigator.mediaDevices.getDisplayMedia = () => {
return new Promise(async (resolve, reject) => {
try {
const source = await window.ipcRenderer.invoke('DESKTOP_CAPTURE');
const stream = await window.navigator.mediaDevices.getUserMedia({
audio: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: source.id,
},
},
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: source.id,
},
},
});
resolve(stream);
} catch (err) {
reject(err);
}
});
};
}
/**
* 获取用户多媒体设备
*/
getUserMediaDevices() {
navigator.mediaDevices.enumerateDevices().then((devices) => {
const generateDeviceJson = (device) => {
const formerIndex = device.label.indexOf(' (');
const latterIndex = device.label.lastIndexOf(' (');
const {
label, webLabel } = ((label, deviceId) => {
switch (deviceId) {
case 'default':
return {
label: label.replace('Default - ', ''),
webLabel: label.replace('Default - ', '默认 - '),
};
case 'communications':
return {
label: label.replace('Communications - ', ''),
webLabel: label.replace('Communications - ', '通讯设备 - '),
};
default:
return {
label: label, webLabel: label };
}
})(
formerIndex === latterIndex
? device.label
: device.label.substring(0, latterIndex),
device.deviceId
);
return {
label, webLabel, deviceId: device.deviceId };
};
let videoDevices = [],
audioDevices = [];
for (const index in devices) {
const device = devices[index];
if (device.kind === 'videoinput') {
videoDevices.push(generateDeviceJson(device));
} else if (device.kind === 'audioinput') {
audioDevices.push(generateDeviceJson(device));
}
}
store.dispatch(updateAvailableDevices(DEVICE_TYPE.VIDEO_DEVICE, videoDevices));
store.dispatch(updateAvailableDevices(DEVICE_TYPE.AUDIO_DEVICE, audioDevices));
const lastVideoDevice = localStorage.getItem('usingVideoDevice');
const lastAudioDevice = localStorage.getItem('usingAudioDevice');
(() => {
store.dispatch(exchangeMediaDevice(DEVICE_TYPE.VIDEO_DEVICE, videoDevices[0]));
for (const device of videoDevices) {
if (device.deviceId === lastVideoDevice) {
store.dispatch(
exchangeMediaDevice(DEVICE_TYPE.VIDEO_DEVICE, {
key: device.deviceId,
value: device.label,
children: device.webLabel,
})
);
return;
}
}
})();
(() => {
store.dispatch(exchangeMediaDevice(DEVICE_TYPE.AUDIO_DEVICE, audioDevices[0]));
for (const device of audioDevices) {
if (device.deviceId === lastAudioDevice) {
store.dispatch(
exchangeMediaDevice(DEVICE_TYPE.AUDIO_DEVICE, {
key: device.deviceId,
value: device.label,
children: device.webLabel,
})
);
return;
}
}
})();
});
}
}
- App.scss:
@import '~antd/dist/antd.css';
* {
margin: 0;
padding: 0;
}
.App {
position: absolute;
left: 1.25px;
top: 1.25px;
width: calc(100vw - 2.5px);
height: calc(100vh - 2.5px);
overflow: hidden;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.8);
}
::-webkit-scrollbar {
width: 0.25rem; // 纵向滚动条宽度
height: 0.25rem; // 横向滚动条宽度
}
::-webkit-scrollbar-thumb {
background-color: #a8a8a8;
border-radius: 0.25rem;
}
::-webkit-scrollbar-track {
background-color: #eaeaea;
}