文章目录
TypeScript 简介
TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。TypeScript 由微软开发的自由和开源的编程语言。其设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
简单点说,你可以认为它是一个强类型的 JavaScript 方言,在面对一些大型项目时,拥有类型检查总能协助开发者降低一些 bug 的出现几率。随着本项目的规模不断扩大,我决定为项目引入 TypeScript ,为项目进行重构。
依赖安装
首先我们安装好 typescript
yarn add -D typescript
为了让 webpack 能够识别我们书写的 ts 代码,我们还需要引入 ts-loader:
yarn add -D ts-loader
由于 typescript 与 react 结合得很好,我们不需要再使用 babel 对代码进行转换,typescript 自己会将自己编译好。因此,我们也可以将原来的 babel 依赖从项目中移除了:
yarn remove @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-react babel-loader
不过,由于 react 与 react-dom 本身没有实现类型定义,所以我们为了能用 ts 书写 react 代码,还需要额外引入 react 和 react-dom 的 types 依赖:
yarn add -D @types/react @types/react-dom
现在,所需要的依赖已经安装好了,我们可以来书写代码了。
tsconfig.json
我们在项目的根目录下打开终端,键入命令:
npx typescript init
我们会在项目根目录下看到一个自动生成的 tsconfig.json
文件,我们可以根据自己的实际需要进行配置,我的配置如下:
{
"compilerOptions": {
"target": "es2016",
"jsx": "react",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"baseUrl": "./",
"resolveJsonModule": true,
"paths": {
"Components/*": ["src/Components/*"],
"Utils/*": ["src/Utils/*"],
"Views/*": ["src/Views/*"]
}
},
"include": ["src/**/*"]
}
修改 webpack.config.js
由于我们将项目重构为了 typescript 的项目,因此,我们编写的不再是 .js
、.jsx
文件,而是 .ts
和 .tsx
文件,我们的 webpack.config.js
也需要据此做出改动:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {
CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
devServer: {
static: path.join(__dirname, 'public'),
host: '127.0.0.1',
port: 9000,
},
resolve: {
extensions: ['.js', '.json', '.ts', '.tsx'],
alias: {
Components: path.join(__dirname, 'src/Components'),
Views: path.join(__dirname, 'src/Views'),
Utils: path.join(__dirname, 'src/Utils'),
},
},
entry: {
login: './src/Views/Login/index.tsx',
register: './src/Views/Register/index.tsx',
},
output: {
path: path.resolve(__dirname, './build'),
filename: '[name]/index.[chunkhash:8].js',
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
'css-loader',
'resolve-url-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
{
test: /\.tsx?$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'ts-loader',
},
],
},
{
test: /\.(png|jpg|gif|mp3)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024, //对文件的大小做限制,1kb
},
},
],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: 'login/index.html',
chunks: ['login'],
template: './public/index.html',
}),
new HtmlWebpackPlugin({
filename: 'register/index.html',
chunks: ['register'],
template: './public/index.html',
}),
],
};
修改旧的代码
index.tsx
import React from 'react';
// let rootDiv = document.getElementById('root')
// if (!rootDiv) {
// rootDiv = document.createElement('div')
// rootDiv.setAttribute('id', 'root')
// }
// createRoot(rootDiv).render(<App />)
import {
render } from 'react-dom';
// import { createRoot } from 'react-dom/client';
import App from './App';
render(<App />, document.getElementById('root'));
登录页
import {
LoadingOutlined, LockOutlined, UserOutlined } from '@ant-design/icons';
import {
Checkbox, Form, Input } from 'antd';
import {
globalMessage } from 'Components/GlobalMessage/GlobalMessage';
import {
LogoIcon, MinimizeIcon, RegisterIcon, ShutdownIcon } from 'Components/MyIcon/MyIcon';
import RippleButton from 'Components/RippleButton/RippleButton';
import {
Victor } from 'Components/Victor/Victor';
import React, {
useEffect, useRef, useState } from 'react';
import {
ajax } from 'Utils/Axios/Axios';
import {
decodeJWT } from 'Utils/Global';
import './App.scss';
export default function App() {
const [form] = Form.useForm();
const lastUserId = localStorage.getItem('userId');
const [userId, setUserId] = useState(lastUserId === 'null' ? '' : lastUserId);
const [userPassword, setUserPassword] = useState('');
const [rememberPassword, setRememberPassword] = useState(
localStorage.getItem('rememberPassword') === 'true'
);
useEffect(() => {
if (rememberPassword) {
(window as any).ipc.invoke('GET_LAST_PASSWORD').then((psw: string) => {
form.setFieldsValue({
password: psw });
});
}
}, []);
const [rotating, setRotating] = useState(false);
useEffect(() => {
if (rotating) {
const mainBody = mainBodyRef.current as HTMLDivElement;
mainBody.style.animationName = 'rotateOut';
let timeout = setTimeout(() => {
mainBody.style.animationName = 'rotateIn';
setShowRegister(!showRegister);
clearTimeout(timeout);
}, 250);
return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}
}, [rotating]);
const [showRegister, setShowRegister] = useState(false);
useEffect(() => {
let timeout = setTimeout(() => {
setRotating(false);
clearTimeout(timeout)
}, 250);
return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, [showRegister]);
const mainBodyRef = useRef<HTMLDivElement>(null);
// 设置动态背景
useEffect(() => {
const victor = Victor('header', 'canvas');
const theme = ['#ff1324', '#ff3851'];
if (victor)
victor(theme).set();
}, []);
const [isLogining, setIsLogining] = useState(false);
const login = () => {
form.validateFields(['username', 'password'])
.then(async (values) => {
setIsLogining(true);
const text = values.username;
const password = values.password;
const res = await ajax.post('/login_and_register/login', {
text, password });
if (res.code === 200) {
globalMessage.success('登录成功');
localStorage.setItem('rememberPassword', `${
rememberPassword}`);
localStorage.setItem('autoLogin', `${
autoLogin}`);
(window as any).ipc.send('SAFE_PASSWORD', rememberPassword, password);
localStorage.setItem('userId', text);
(window as any).ipc.send('USER_LOGIN', res.data.token, decodeJWT(res.data.token).email);
} else {
globalMessage.error(res.message);
setIsLogining(false);
}
})
.catch((err) => {
if (err.ajax) {
globalMessage.error('服务器错误,请稍后重试');
} else {
const {
values } = err;
if (values.username === undefined) {
globalMessage.error('请输入用户名或邮箱!');
} else if (values.password === undefined) {
globalMessage.error('请输入密码!');
}
}
setIsLogining(false);
});
};
/**
* INFO: 自动登录
*/
const [autoLogin, setAutoLogin] = useState(localStorage.getItem('autoLogin') === 'true');
useEffect(() => {
let timeout = setTimeout(() => {
if (autoLogin) login();
clearTimeout(timeout);
}, 0);
return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, []);
return (
<>
<div id='dragBar' />
<div id='mainBody' ref={
mainBodyRef}>
<div id='header'>
<div id='titleBar'>
<LogoIcon style={
{
fontSize: '1.5rem' }} />
<span style={
{
fontFamily: 'Microsoft Yahei' }}>山大会议</span>
<button
className='titleBtn'
id='shutdown'
title='退出'
onClick={
() => {
(window as any).ipc.send('QUIT');
}}>
<ShutdownIcon />
</button>
<button
className='titleBtn'
id='minimize'
title='最小化'
onClick={
() => {
(window as any).ipc.send('MINIMIZE_LOGIN_WINDOW');
}}>
<MinimizeIcon />
</button>
<button
className='titleBtn'
id='switch'
title={
showRegister ? '返回登录' : '注册账号'}
onClick={
() => {
setRotating(true);
}}>
<RegisterIcon />
</button>
</div>
<div id='canvas' />
</div>
<div className='main'>
<div
className='form'
id='loginForm'
style={
{
display: showRegister ? 'none' : 'block' }}>
<Form form={
form}>
<Form.Item
name='username'
rules={
[
{
required: true,
message: '请输入用户名或邮箱',
},
]}
initialValue={
userId}>
<Input
placeholder='请输入用户名或邮箱'
spellCheck={
false}
prefix={
<UserOutlined />}
size={
'large'}
style={
{
width: '65%' }}
onChange={
(event) => {
setUserId(event.target.value);
}}
/>
</Form.Item>
<Form.Item
name='password'
rules={
[
{
required: true,
message: '密码不得为空',
},
]}
initialValue={
userPassword}>
<Input.Password
placeholder='请输入密码'
spellCheck={
false}
prefix={
<LockOutlined />}
size={
'large'}
style={
{
width: '65%' }}
onChange={
(event) => {
setUserPassword(event.target.value);
}}
/>
</Form.Item>
<Form.Item>
<Checkbox
style={
{
fontSize: '0.75rem' }}
checked={
rememberPassword}
onChange={
(e) => {
setRememberPassword(e.target.checked);
}}>
记住密码
</Checkbox>
<Checkbox
style={
{
fontSize: '0.75rem' }}
checked={
autoLogin}
onChange={
(e) => {
setAutoLogin(e.target.checked);
}}>
自动登录
</Checkbox>
</Form.Item>
<Form.Item>
<RippleButton
className='submit'
onClick={
login}
disabled={
isLogining}>
<>{
isLogining && <LoadingOutlined />} 登 录</>
</RippleButton>
</Form.Item>
</Form>
</div>
<div
className='form'
id='registerForm'
style={
{
display: showRegister ? 'flex' : 'none' }}>
<RippleButton
className='submit'
onClick={
() => {
const registerUrl =
process.env.NODE_ENV === 'development'
? './register/'
: '../register/index.html';
window.open(registerUrl);
}}>
注 册
</RippleButton>
</div>
</div>
</div>
</>
);
}
Victor.ts
let CAV: any = {
FRONT: 0,
BACK: 1,
DOUBLE: 2,
SVGNS: 'http://www.w3.org/2000/svg',
Array: typeof Float32Array === 'function' ? Float32Array : Array,
Utils: {
isNumber: function (a: any) {
return !isNaN(parseFloat(a)) && isFinite(a);
},
}
};
(function () {
for (
var a = 0, b = ['ms', 'moz', 'webkit', 'o'], c = 0;
c < b.length && !window.requestAnimationFrame;
++c
)
(window.requestAnimationFrame = (window as any)[b[c] + 'RequestAnimationFrame']),
(window.cancelAnimationFrame =
(window as any)[b[c] + 'CancelAnimationFrame'] ||
(window as any)[b[c] + 'CancelRequestAnimationFrame']);
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function (b) {
var c = new Date().getTime(),
f = Math.max(0, 16 - (c - a)),
g = window.setTimeout(function () {
b(c + f);
}, f);
a = c + f;
return g;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function (a) {
clearTimeout(a);
};
})();
const randomInRange = function (a: number, b: number) {
return a + (b - a) * Math.random();
};
const clamp = function (a: number, b: number, c: number) {
a = Math.max(a, b);
return (a = Math.min(a, c));
};
CAV.Vector3 = {
create: function (a: any, b: any, c: any) {
var d = new CAV.Array(3);
this.set(d, a, b, c);
return d;
},
clone: function (a: any) {
var b = this.create();
this.copy(b, a);
return b;
},
set: function (a: any[], b: number, c: number, d: number) {
a[0] = b || 0;
a[1] = c || 0;
a[2] = d || 0;
return this;
},
setX: function (a: any[], b: number) {
a[0] = b || 0;
return this;
},
setY: function (a: any[], b: number) {
a[1] = b || 0;
return this;
},
setZ: function (a: any[], b: number) {
a[2] = b || 0;
return this;
},
copy: function (a: any[], b: any[]) {
a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
return this;
},
add: function (a: any[], b: any[]) {
a[0] += b[0];
a[1] += b[1];
a[2] += b[2];
return this;
},
addVectors: function (a: any[], b: any[], c: any[]) {
a[0] = b[0] + c[0];
a[1] = b[1] + c[1];
a[2] = b[2] + c[2];
return this;
},
addScalar: function (a: any[], b: any) {
a[0] += b;
a[1] += b;
a[2] += b;
return this;
},
subtract: function (a: number[], b: number[]) {
a[0] -= b[0];
a[1] -= b[1];
a[2] -= b[2];
return this;
},
subtractVectors: function (a: number[], b: number[], c: number[]) {
a[0] = b[0] - c[0];
a[1] = b[1] - c[1];
a[2] = b[2] - c[2];
return this;
},
subtractScalar: function (a: number[], b: number) {
a[0] -= b;
a[1] -= b;
a[2] -= b;
return this;
},
multiply: function (a: number[], b: number[]) {
a[0] *= b[0];
a[1] *= b[1];
a[2] *= b[2];
return this;
},
multiplyVectors: function (a: number[], b: number[], c: number[]) {
a[0] = b[0] * c[0];
a[1] = b[1] * c[1];
a[2] = b[2] * c[2];
return this;
},
multiplyScalar: function (a: number[], b: number) {
a[0] *= b;
a[1] *= b;
a[2] *= b;
return this;
},
divide: function (a: number[], b: number[]) {
a[0] /= b[0];
a[1] /= b[1];
a[2] /= b[2];
return this;
},
divideVectors: function (a: number[], b: number[], c: number[]) {
a[0] = b[0] / c[0];
a[1] = b[1] / c[1];
a[2] = b[2] / c[2];
return this;
},
divideScalar: function (a: number[], b: number) {
b !== 0 ? ((a[0] /= b), (a[1] /= b), (a[2] /= b)) : ((a[0] = 0), (a[1] = 0), (a[2] = 0));
return this;
},
cross: function (a: number[], b: number[]) {
var c = a[0],
d = a[1],
e = a[2];
a[0] = d * b[2] - e * b[1];
a[1] = e * b[0] - c * b[2];
a[2] = c * b[1] - d * b[0];
return this;
},
crossVectors: function (a: number[], b: number[], c: number[]) {
a[0] = b[1] * c[2] - b[2] * c[1];
a[1] = b[2] * c[0] - b[0] * c[2];
a[2] = b[0] * c[1] - b[1] * c[0];
return this;
},
min: function (a: any[], b: number) {
a[0] < b && (a[0] = b);
a[1] < b && (a[1] = b);
a[2] < b && (a[2] = b);
return this;
},
max: function (a: any[], b: number) {
a[0] > b && (a[0] = b);
a[1] > b && (a[1] = b);
a[2] > b && (a[2] = b);
return this;
},
clamp: function (a: any, b: any, c: any) {
this.min(a, b);
this.max(a, c);
return this;
},
limit: function (a: any, b: number | null, c: number | null) {
var d = this.length(a);
b !== null && d < b ? this.setLength(a, b) : c !== null && d > c && this.setLength(a, c);
return this;
},
dot: function (a: number[], b: number[]) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
},
normalise: function (a: any) {
return this.divideScalar(a, this.length(a));
},
negate: function (a: any) {
return this.multiplyScalar(a, -1);
},
distanceSquared: function (a: number[], b: number[]) {
var c = a[0] - b[0],
d = a[1] - b[1],
e = a[2] - b[2];
return c * c + d * d + e * e;
},
distance: function (a: any, b: any) {
return Math.sqrt(this.distanceSquared(a, b));
},
lengthSquared: function (a: number[]) {
return a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
},
length: function (a: any) {
return Math.sqrt(this.lengthSquared(a));
},
setLength: function (a: any, b: number) {
var c = this.length(a);
c !== 0 && b !== c && this.multiplyScalar(a, b / c);
return this;
},
};
CAV.Vector4 = {
create: function (a: any, b: any, c: any) {
var d = new CAV.Array(4);
this.set(d, a, b, c);
return d;
},
set: function (a: any[], b: number, c: number, d: number, e: number) {
a[0] = b || 0;
a[1] = c || 0;
a[2] = d || 0;
a[3] = e || 0;
return this;
},
setX: function (a: any[], b: number) {
a[0] = b || 0;
return this;
},
setY: function (a: any[], b: number) {
a[1] = b || 0;
return this;
},
setZ: function (a: any[], b: number) {
a[2] = b || 0;
return this;
},
setW: function (a: any[], b: number) {
a[3] = b || 0;
return this;
},
add: function (a: any[], b: any[]) {
a[0] += b[0];
a[1] += b[1];
a[2] += b[2];
a[3] += b[3];
return this;
},
multiplyVectors: function (a: number[], b: number[], c: number[]) {
a[0] = b[0] * c[0];
a[1] = b[1] * c[1];
a[2] = b[2] * c[2];
a[3] = b[3] * c[3];
return this;
},
multiplyScalar: function (a: number[], b: number) {
a[0] *= b;
a[1] *= b;
a[2] *= b;
a[3] *= b;
return this;
},
min: function (a: any[], b: number) {
a[0] < b && (a[0] = b);
a[1] < b && (a[1] = b);
a[2] < b && (a[2] = b);
a[3] < b && (a[3] = b);
return this;
},
max: function (a: any[], b: number) {
a[0] > b && (a[0] = b);
a[1] > b && (a[1] = b);
a[2] > b && (a[2] = b);
a[3] > b && (a[3] = b);
return this;
},
clamp: function (a: any, b: any, c: any) {
this.min(a, b);
this.max(a, c);
return this;
},
};
CAV.Color = function (a: string, b: any) {
this.rgba = CAV.Vector4.create();
this.hex = a || '#000000';
this.opacity = CAV.Utils.isNumber(b) ? b : 1;
this.set(this.hex, this.opacity);
};
CAV.Color.prototype = {
set: function (a: string, b: any) {
var a = a.replace('#', ''),
c = a.length / 3;
this.rgba[0] = parseInt(a.substring(c * 0, c * 1), 16) / 255;
this.rgba[1] = parseInt(a.substring(c * 1, c * 2), 16) / 255;
this.rgba[2] = parseInt(a.substring(c * 2, c * 3), 16) / 255;
this.rgba[3] = CAV.Utils.isNumber(b) ? b : this.rgba[3];
return this;
},
hexify: function (a: string | number) {
a = Math.ceil(Number(a) * 255).toString(16);
a.length === 1 && (a = '0' + a);
return a;
},
format: function () {
var a = this.hexify(this.rgba[0]),
b = this.hexify(this.rgba[1]),
c = this.hexify(this.rgba[2]);
return (this.hex = '#' + a + b + c);
},
};
CAV.Object = function () {
this.position = CAV.Vector3.create();
};
CAV.Object.prototype = {
setPosition: function (a: any, b: any, c: any) {
CAV.Vector3.set(this.position, a, b, c);
return this;
},
};
CAV.Light = function (a: any, b: any) {
CAV.Object.call(this);
this.ambient = new CAV.Color(a || '#FFFFFF');
this.diffuse = new CAV.Color(b || '#FFFFFF');
this.ray = CAV.Vector3.create();
};
CAV.Light.prototype = Object.create(CAV.Object.prototype);
CAV.Vertex = function (a: any, b: any, c: any) {
this.position = CAV.Vector3.create(a, b, c);
};
CAV.Vertex.prototype = {
setPosition: function (a: any, b: any, c: any) {
CAV.Vector3.set(this.position, a, b, c);
return this;
},
};
CAV.Triangle = function (a: any, b: any, c: any) {
this.a = a || new CAV.Vertex();
this.b = b || new CAV.Vertex();
this.c = c || new CAV.Vertex();
this.vertices = [this.a, this.b, this.c];
this.u = CAV.Vector3.create();
this.v = CAV.Vector3.create();
this.centroid = CAV.Vector3.create();
this.normal = CAV.Vector3.create();
this.color = new CAV.Color();
this.polygon = document.createElementNS(CAV.SVGNS, 'polygon');
this.polygon.setAttributeNS(null, 'stroke-linejoin', 'round');
this.polygon.setAttributeNS(null, 'stroke-miterlimit', '1');
this.polygon.setAttributeNS(null, 'stroke-width', '1');
this.computeCentroid();
this.computeNormal();
};
CAV.Triangle.prototype = {
computeCentroid: function () {
this.centroid[0] = this.a.position[0] + this.b.position[0] + this.c.position[0];
this.centroid[1] = this.a.position[1] + this.b.position[1] + this.c.position[1];
this.centroid[2] = this.a.position[2] + this.b.position[2] + this.c.position[2];
CAV.Vector3.divideScalar(this.centroid, 3);
return this;
},
computeNormal: function () {
CAV.Vector3.subtractVectors(this.u, this.b.position, this.a.position);
CAV.Vector3.subtractVectors(this.v, this.c.position, this.a.position);
CAV.Vector3.crossVectors(this.normal, this.u, this.v);
CAV.Vector3.normalise(this.normal);
return this;
},
};
CAV.Geometry = function () {
this.vertices = [];
this.triangles = [];
this.dirty = false;
};
CAV.Geometry.prototype = {
update: function () {
if (this.dirty) {
var a, b;
for (a = this.triangles.length - 1; a >= 0; a--)
(b = this.triangles[a]), b.computeCentroid(), b.computeNormal();
this.dirty = false;
}
return this;
},
};
CAV.Plane = function (a: number, b: number, _c: number, d: number) {
let t0, t1;
CAV.Geometry.call(this);
this.width = a || 100;
this.height = b || 100;
this.segments = _c || 4;
this.slices = d || 4;
this.segmentWidth = this.width / this.segments;
this.sliceHeight = this.height / this.slices;
var e,
f,
g,
c = [];
e = this.width * -0.5;
f = this.height * 0.5;
for (a = 0; a <= this.segments; a++) {
c.push([]);
for (b = 0; b <= this.slices; b++)
(d = new CAV.Vertex(e + a * this.segmentWidth, f - b * this.sliceHeight)),
(c[a] as Array<any>).push(d),
this.vertices.push(d);
}
for (a = 0; a < this.segments; a++)
for (b = 0; b < this.slices; b++)
(d = c[a + 0][b + 0]),
(e = c[a + 0][b + 1]),
(f = c[a + 1][b + 0]),
(g = c[a + 1][b + 1]),
(t0 = new CAV.Triangle(d, e, f)),
(t1 = new CAV.Triangle(f, e, g)),
this.triangles.push(t0, t1);
};
CAV.Plane.prototype = Object.create(CAV.Geometry.prototype);
CAV.Material = function (a: any, b: any) {
this.ambient = new CAV.Color(a || '#444444');
this.diffuse = new CAV.Color(b || '#FFFFFF');
this.slave = new CAV.Color();
};
CAV.Mesh = function (a: any, b: any) {
CAV.Object.call(this);
this.geometry = a || new CAV.Geometry();
this.material = b || new CAV.Material();
this.side = CAV.FRONT;
this.visible = true;
};
CAV.Mesh.prototype = Object.create(CAV.Object.prototype);
CAV.Mesh.prototype.update = function (a: string | any[], b: any) {
var c, d, e, f, g;
this.geometry.update();
if (b)
for (c = this.geometry.triangles.length - 1; c >= 0; c--) {
d = this.geometry.triangles[c];
CAV.Vector4.set(d.color.rgba);
for (e = a.length - 1; e >= 0; e--)
(f = a[e]),
CAV.Vector3.subtractVectors(f.ray, f.position, d.centroid),
CAV.Vector3.normalise(f.ray),
(g = CAV.Vector3.dot(d.normal, f.ray)),
this.side === CAV.FRONT
? (g = Math.max(g, 0))
: this.side === CAV.BACK
? (g = Math.abs(Math.min(g, 0)))
: this.side === CAV.DOUBLE && (g = Math.max(Math.abs(g), 0)),
CAV.Vector4.multiplyVectors(
this.material.slave.rgba,
this.material.ambient.rgba,
f.ambient.rgba
),
CAV.Vector4.add(d.color.rgba, this.material.slave.rgba),
CAV.Vector4.multiplyVectors(
this.material.slave.rgba,
this.material.diffuse.rgba,
f.diffuse.rgba
),
CAV.Vector4.multiplyScalar(this.material.slave.rgba, g),
CAV.Vector4.add(d.color.rgba, this.material.slave.rgba);
CAV.Vector4.clamp(d.color.rgba, 0, 1);
}
return this;
};
CAV.Scene = function () {
this.meshes = [];
this.lights = [];
};
CAV.Scene.prototype = {
add: function (a: any) {
a instanceof CAV.Mesh && !~this.meshes.indexOf(a)
? this.meshes.push(a)
: a instanceof CAV.Light && !~this.lights.indexOf(a) && this.lights.push(a);
return this;
},
remove: function (a: any) {
a instanceof CAV.Mesh && ~this.meshes.indexOf(a)
? this.meshes.splice(this.meshes.indexOf(a), 1)
: a instanceof CAV.Light &&
~this.lights.indexOf(a) &&
this.lights.splice(this.lights.indexOf(a), 1);
return this;
},
};
CAV.Renderer = function () {
this.halfHeight = this.halfWidth = this.height = this.width = 0;
};
CAV.Renderer.prototype = {
setSize: function (a: any, b: any) {
if (!(this.width === a && this.height === b))
return (
(this.width = a),
(this.height = b),
(this.halfWidth = this.width * 0.5),
(this.halfHeight = this.height * 0.5),
this
);
},
clear: function () {
return this;
},
render: function () {
return this;
},
};
CAV.CanvasRenderer = function () {
CAV.Renderer.call(this);
this.element = document.createElement('canvas');
this.element.style.display = 'block';
this.context = this.element.getContext('2d');
this.setSize(this.element.width, this.element.height);
};
CAV.CanvasRenderer.prototype = Object.create(CAV.Renderer.prototype);
CAV.CanvasRenderer.prototype.setSize = function (a: any, b: any) {
CAV.Renderer.prototype.setSize.call(this, a, b);
this.element.width = a;
this.element.height = b;
this.context.setTransform(1, 0, 0, -1, this.halfWidth, this.halfHeight);
return this;
};
CAV.CanvasRenderer.prototype.clear = function () {
CAV.Renderer.prototype.clear.call(this);
this.context.clearRect(-this.halfWidth, -this.halfHeight, this.width, this.height);
return this;
};
CAV.CanvasRenderer.prototype.render = function (a: {
meshes: string | any[]; lights: any; }) {
CAV.Renderer.prototype.render.call(this, a);
var b, c, d, e, f;
this.clear();
this.context.lineJoin = 'round';
this.context.lineWidth = 1;
for (b = a.meshes.length - 1; b >= 0; b--)
if (((c = a.meshes[b]), c.visible)) {
c.update(a.lights, true);
for (d = c.geometry.triangles.length - 1; d >= 0; d--)
(e = c.geometry.triangles[d]),
(f = e.color.format()),
this.context.beginPath(),
this.context.moveTo(e.a.position[0], e.a.position[1]),
this.context.lineTo(e.b.position[0], e.b.position[1]),
this.context.lineTo(e.c.position[0], e.c.position[1]),
this.context.closePath(),
(this.context.strokeStyle = f),
(this.context.fillStyle = f),
this.context.stroke(),
this.context.fill();
}
return this;
};
export function Victor(container: string, anitOut: string) {
let J, l;
if (!!document.createElement('canvas').getContext) {
var t = {
width: 1.5,
height: 1.5,
depth: 10,
segments: 12,
slices: 6,
xRange: 0.8,
yRange: 0.1,
zRange: 1,
ambient: '#525252',
diffuse: '#FFFFFF',
speed: 0.0002,
};
var G = {
count: 2,
xyScalar: 1,
zOffset: 100,
ambient: '#002c4a',
diffuse: '#005584',
speed: 0.001,
gravity: 1200,
dampening: 0.95,
minLimit: 10,
maxLimit: null,
minDistance: 20,
maxDistance: 400,
autopilot: false,
draw: false,
bounds: CAV.Vector3.create(),
step: CAV.Vector3.create(
randomInRange(0.2, 1),
randomInRange(0.2, 1),
randomInRange(0.2, 1)
),
};
var m = 'canvas';
var E = 'svg';
var x = {
renderer: m,
};
var i: number,
n = Date.now();
var L = CAV.Vector3.create();
var k = CAV.Vector3.create();
var z = document.getElementById(container || 'container');
var w = document.getElementById(anitOut || 'anitOut');
var D: {
element: any; setSize: (arg0: number, arg1: number) => void; clear: () => void; width: number; height: number; halfWidth: any; halfHeight: any; render: (arg0: any) => void; }, I: {
remove: (arg0: any) => void; add: (arg0: any) => void; lights: string | any[]; }, h: any, q: {
vertices: string | any[]; segmentWidth: number; sliceHeight: number; dirty: boolean; }, y;
var g: any;
var r;
function C() {
F();
p();
s();
B();
v();
K((z as HTMLElement).offsetWidth, (z as HTMLElement).offsetHeight);
o();
}
function F() {
g = new CAV.CanvasRenderer();
H(x.renderer);
}
function H(N: string) {
if (D) {
(w as HTMLElement).removeChild(D.element);
}
switch (N) {
case m:
D = g;
break;
}
D.setSize((z as HTMLElement).offsetWidth, (z as HTMLElement).offsetHeight);
(w as HTMLElement).appendChild(D.element);
}
function p() {
I = new CAV.Scene();
}
function s() {
I.remove(h);
D.clear();
q = new CAV.Plane(t.width * D.width, t.height * D.height, t.segments, t.slices);
y = new CAV.Material(t.ambient, t.diffuse);
h = new CAV.Mesh(q, y);
I.add(h);
var N, O;
for (N = q.vertices.length - 1; N >= 0; N--) {
O = q.vertices[N];
O.anchor = CAV.Vector3.clone(O.position);
O.step = CAV.Vector3.create(
randomInRange(0.2, 1),
randomInRange(0.2, 1),
randomInRange(0.2, 1)
);
O.time = randomInRange(0, Math.PI * 2);
}
}
function B() {
var O, N;
for (O = I.lights.length - 1; O >= 0; O--) {
N = I.lights[O];
I.remove(N);
}
D.clear();
for (O = 0; O < G.count; O++) {
N = new CAV.Light(G.ambient, G.diffuse);
N.ambientHex = N.ambient.format();
N.diffuseHex = N.diffuse.format();
I.add(N);
N.mass = randomInRange(0.5, 1);
N.velocity = CAV.Vector3.create();
N.acceleration = CAV.Vector3.create();
N.force = CAV.Vector3.create();
}
}
function K(O: number, N: number) {
D.setSize(O, N);
CAV.Vector3.set(L, D.halfWidth, D.halfHeight);
s();
}
function o() {
i = Date.now() - n;
u();
M();
requestAnimationFrame(o);
}
function u() {
var Q,
P,
O,
R,
T,
V,
U,
S = t.depth / 2;
CAV.Vector3.copy(G.bounds, L);
CAV.Vector3.multiplyScalar(G.bounds, G.xyScalar);
CAV.Vector3.setZ(k, G.zOffset);
for (R = I.lights.length - 1; R >= 0; R--) {
T = I.lights[R];
CAV.Vector3.setZ(T.position, G.zOffset);
var N = clamp(
CAV.Vector3.distanceSquared(T.position, k),
G.minDistance,
G.maxDistance
);
var W = (G.gravity * T.mass) / N;
CAV.Vector3.subtractVectors(T.force, k, T.position);
CAV.Vector3.normalise(T.force);
CAV.Vector3.multiplyScalar(T.force, W);
CAV.Vector3.set(T.acceleration);
CAV.Vector3.add(T.acceleration, T.force);
CAV.Vector3.add(T.velocity, T.acceleration);
CAV.Vector3.multiplyScalar(T.velocity, G.dampening);
CAV.Vector3.limit(T.velocity, G.minLimit, G.maxLimit);
CAV.Vector3.add(T.position, T.velocity);
}
for (V = q.vertices.length - 1; V >= 0; V--) {
U = q.vertices[V];
Q = Math.sin(U.time + U.step[0] * i * t.speed);
P = Math.cos(U.time + U.step[1] * i * t.speed);
O = Math.sin(U.time + U.step[2] * i * t.speed);
CAV.Vector3.set(
U.position,
t.xRange * q.segmentWidth * Q,
t.yRange * q.sliceHeight * P,
t.zRange * S * O - S
);
CAV.Vector3.add(U.position, U.anchor);
}
q.dirty = true;
}
function M() {
D.render(I);
}
J = (O: any) => {
var Q,
N,
S = O;
var P = function (T: any) {
for (Q = 0, l = I.lights.length; Q < l; Q++) {
N = I.lights[Q];
N.ambient.set(T);
N.ambientHex = N.ambient.format();
}
};
var R = function (T: any) {
for (Q = 0, l = I.lights.length; Q < l; Q++) {
N = I.lights[Q];
N.diffuse.set(T);
N.diffuseHex = N.diffuse.format();
}
};
return {
set: function () {
P(S[0]);
R(S[1]);
},
};
};
function v() {
window.addEventListener('resize', j);
}
function A(N: {
x: any; y: number; }) {
CAV.Vector3.set(k, N.x, D.height - N.y);
CAV.Vector3.subtract(k, L);
}
function j(N: any) {
K((z as HTMLElement).offsetWidth, (z as HTMLElement).offsetHeight);
M();
}
C();
}
return J;
}
注册页
import {
KeyOutlined,
LockFilled,
LockOutlined,
MailOutlined,
UserOutlined
} from '@ant-design/icons';
import {
Button, Form, Input, notification, Select } from 'antd';
import React, {
useEffect, useState } from 'react';
import {
ajax } from 'Utils/Axios/Axios';
import './App.scss';
export default function App() {
const [sendCaptchaTick, setSendCaptchaTick] = useState(0);
const [sendCaptchaInterval, setSendCaptchaInterval] = useState<NodeJS.Timeout | null>(null);
useEffect(() => {
return () => {
if (sendCaptchaInterval) {
clearInterval(sendCaptchaInterval);
setSendCaptchaInterval(null);
}
};
}, []);
const [chosenEmail, setChosenEmail] = useState('@mail.sdu.edu.cn');
const [form] = Form.useForm();
const [isRegistering, setIsRegistering] = useState(false);
const {
Option } = Select;
return (
<div className='register' style={
{
backgroundImage: `url(${
require('./bg.jpg').default})` }}>
<div className='container'>
<div className='title'>山大会议 注册账号</div>
<div className='inputs'>
<Form
onFinish={
(values) => {
submitForm(values, chosenEmail, setIsRegistering);
}}
autoComplete='off'
form={
form}>
<Form.Item
rules={
[
{
required: true,
message: '请输入注册用的昵称',
},
{
pattern: /^[^@]+$/,
message: '昵称中不允许出现"@"',
},
]}
name={
'username'}>
<Input placeholder='请输入昵称' prefix={
<UserOutlined />} />
</Form.Item>
<Form.Item
rules={
[
{
required: true, message: '请输入密码' },
{
min: 6,
message: '请输入长度超过6位的密码',
},
]}
name={
'password'}>
<Input.Password placeholder='请输入密码' prefix={
<LockOutlined />} />
</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='请再次输入密码' prefix={
<LockFilled />} />
</Form.Item>
<Form.Item
rules={
[
{
required: true,
message: '请输入邮箱',
},
{
pattern: /^[^@]+$/,
message: '请不要再次输入"@"',
},
]}
name={
'email'}>
<Input
placeholder='请输入邮箱'
addonAfter={
<Select defaultValue={
chosenEmail} onSelect={
setChosenEmail}>
<Option value='@mail.sdu.edu.cn'>@mail.sdu.edu.cn</Option>
<Option value='@sdu.edu.cn'>@sdu.edu.cn</Option>
</Select>
}
prefix={
<MailOutlined />}
/>
</Form.Item>
<Form.Item
rules={
[
{
required: true,
message: '请输入验证码',
},
]}
name={
'captcha'}>
<Input placeholder='请输入邮箱验证码' prefix={
<KeyOutlined />} />
</Form.Item>
<Form.Item>
<div style={
{
display: 'flex', justifyContent: 'space-around' }}>
<Button
disabled={
sendCaptchaTick > 0}
loading={
sendCaptchaTick === -1}
onClick={
() => {
form.validateFields(['username', 'email'])
.then((values) => {
setSendCaptchaTick(-1);
const {
username, email } = values;
ajax.post('/login_and_register/code', {
username,
email: `${
email}${
chosenEmail}`,
}).then((response) => {
if (response.code === 200) {
notification.success({
message: '验证码发送成功',
description:
'验证码已发送,请前往邮箱查询验证码',
});
sendCaptcha(
setSendCaptchaTick,
setSendCaptchaInterval
);
} else {
notification.error({
message: '验证码发送失败',
description: response.message,
});
}
});
})
.catch(() => {
});
}}>
{
sendCaptchaTick > 0
? `${
sendCaptchaTick}秒后可再次发送`
: '发送验证码'}
</Button>
<Button loading={
isRegistering} type='primary' htmlType='submit'>
注册
</Button>
</div>
</Form.Item>
</Form>
</div>
</div>
</div>
);
}
async function submitForm(values: {
username: any; password: any; captcha: any; email: any; }, chosenEmail: string, setIsRegistering: {
(value: React.SetStateAction<boolean>): void; (arg0: boolean): void; }) {
setIsRegistering(true);
const {
username, password, captcha, email } = values;
const res = await ajax.post('/login_and_register/register', {
username,
password,
email: `${
email}${
chosenEmail}`,
code: captcha,
});
if (res.code === 200) {
notification.success({
message: '注册成功', description: '注册成功,请前往登录吧' });
} else {
notification.error({
message: '注册失败', description: res.message });
}
setIsRegistering(false);
}
function sendCaptcha(setSendCaptchaTick: {
(value: React.SetStateAction<number>): void; (arg0: number): void; }, setSendCaptchaInterval: {
(value: React.SetStateAction<NodeJS.Timeout | null>): void; (arg0: NodeJS.Timer | null): void; }) {
let sendCaptchaTick = 60;
setSendCaptchaTick(sendCaptchaTick);
const interval = setInterval(() => {
setSendCaptchaTick(--sendCaptchaTick);
if (sendCaptchaTick === 0) {
clearInterval(interval);
setSendCaptchaInterval(null);
}
}, 1000);
setSendCaptchaInterval(interval);
}
Redux
actions.ts
/**
* action 类型
*/
import {
ChatWebSocketType, DEVICE_TYPE } from "Utils/Constraints";
import {
DeviceInfo } from "Utils/Types";
const UNDEFINED_ACTION = 'UNDEFINED_ACTION'
// 选择当前聊天的对象 ID
export const SET_NOW_CHATTING_ID = 'SET_NOW_CHATTING_ID';
export function setNowChattingId(nowChattingId: number | null) {
return {
type: SET_NOW_CHATTING_ID, nowChattingId };
}
export const SET_NOW_WEBRTC_FRIEND_ID = 'SET_NOW_WEBRTC_FRIEND_ID';
export function setNowWebrtcFriendId(nowWebrtcFriendId: number | null) {
return {
type: SET_NOW_WEBRTC_FRIEND_ID, nowWebrtcFriendId };
}
// 更新可用的音视频设备
export const UPDATE_AVAILABLE_VIDEO_DEVICES = 'UPDATE_AVAILABLE_VIDEO_DEVICES';
export const UPDATE_AVAILABLE_AUDIO_DEVICES = 'UPDATE_AVAILABLE_AUDIO_DEVICES';
export function updateAvailableDevices(deviceType: string, devices: DeviceInfo[]) {
switch (deviceType) {
case DEVICE_TYPE.VIDEO_DEVICE:
return {
type: UPDATE_AVAILABLE_VIDEO_DEVICES, devices };
case DEVICE_TYPE.AUDIO_DEVICE:
return {
type: UPDATE_AVAILABLE_AUDIO_DEVICES, devices };
default:
return {
type: UNDEFINED_ACTION };
}
}
// 更换选中的音视频设备
export const EXCHANGE_VIDEO_DEVICE = 'EXCHANGE_VIDEO_DEVICE';
export const EXCHANGE_AUDIO_DEVICE = 'EXCHANGE_AUDIO_DEVICE';
export function exchangeMediaDevice(deviceType: string, deviceInfo: DeviceInfo) {
switch (deviceType) {
case DEVICE_TYPE.VIDEO_DEVICE:
return {
type: EXCHANGE_VIDEO_DEVICE, deviceInfo };
case DEVICE_TYPE.AUDIO_DEVICE:
return {
type: EXCHANGE_AUDIO_DEVICE, deviceInfo };
default:
return {
type: UNDEFINED_ACTION };
}
}
// 设置用户 Token
export const SET_AUTH_TOKEN = 'SET_AUTH_TOKEN';
export function setAuthToken(token: string) {
return {
type: SET_AUTH_TOKEN, token };
}
// 管理未读消息
export const ADD_UNREAD_MESSAGE = 'ADD_UNREAD_MESSAGE';
export const REMOVE_UNREAD_MESSAGES = 'REMOVE_UNREAD_MESSAGES';
export function setUnreadMessages(operation: string, payload: any) {
return {
type: operation, payload };
}
// 管理消息记录
export const INIT_MESSAGE_HISTORY = 'INIT_MESSAGE_HISTORY';
export const SYNC_CLOUD_MESSAGE_HISTORY = 'SYNC_CLOUD_MESSAGE_HISTORY';
export const GET_MORE_MESSAGE_HISTORY = 'GET_MORE_MESSAGE_HISTORY';
export const ADD_MESSAGE_HISTORY = 'ADD_MESSAGE_HISTORY';
export const REMOVE_MESSAGE_HISTORY = 'REMOVE_MESSAGE_HISTORY';
export function setMessageHistory(operation: string, payload: any) {
return {
type: operation, payload };
}
// 应用通话状态
export const SET_CALL_STATUS = 'SET_CALL_STATUS';
export function setCallStatus(status: ChatWebSocketType) {
return {
type: SET_CALL_STATUS, status };
}
reducers.ts
import {
combineReducers } from '@reduxjs/toolkit';
import {
CALL_STATUS_FREE, ChatWebSocketType } from 'Utils/Constraints';
import {
ChatMessage, DeviceInfo } from 'Utils/Types';
import {
ADD_MESSAGE_HISTORY,
ADD_UNREAD_MESSAGE,
EXCHANGE_AUDIO_DEVICE,
EXCHANGE_VIDEO_DEVICE,
GET_MORE_MESSAGE_HISTORY,
INIT_MESSAGE_HISTORY,
REMOVE_MESSAGE_HISTORY,
REMOVE_UNREAD_MESSAGES,
SET_AUTH_TOKEN,
SET_CALL_STATUS,
SET_NOW_CHATTING_ID,
SET_NOW_WEBRTC_FRIEND_ID,
SYNC_CLOUD_MESSAGE_HISTORY,
UPDATE_AVAILABLE_AUDIO_DEVICES,
UPDATE_AVAILABLE_VIDEO_DEVICES
} from './actions';
function setNowChattingId(state = null, action: {
type: string, nowChattingId: number | null }) {
if (action.type === SET_NOW_CHATTING_ID) {
return action.nowChattingId;
}
return state;
}
function setNowWebrtcFriendId(state = null, action: {
type: string, nowWebrtcFriendId: number | null }) {
if (action.type === SET_NOW_WEBRTC_FRIEND_ID) {
return action.nowWebrtcFriendId;
}
return state;
}
function updateAvailableVideoDevices(state = new Array(), action: {
type: string, devices: DeviceInfo[] }): Array<DeviceInfo> {
switch (action.type) {
case UPDATE_AVAILABLE_VIDEO_DEVICES:
return action.devices;
default:
return state;
}
}
function updateAvailableAudioDevices(state = new Array(), action: {
type: string, devices: DeviceInfo[] }): Array<DeviceInfo> {
switch (action.type) {
case UPDATE_AVAILABLE_AUDIO_DEVICES:
return action.devices;
default:
return state;
}
}
function exchangeVideoDevice(state = null, action: {
type: string, deviceInfo: DeviceInfo }) {
switch (action.type) {
case EXCHANGE_VIDEO_DEVICE:
localStorage.setItem('usingVideoDevice', action.deviceInfo.deviceId);
return action.deviceInfo;
default:
return state;
}
}
function exchangeAudioDevice(state = null, action: {
type: string, deviceInfo: DeviceInfo }) {
switch (action.type) {
case EXCHANGE_AUDIO_DEVICE:
localStorage.setItem('usingAudioDevice', action.deviceInfo.deviceId);
return action.deviceInfo;
default:
return state;
}
}
function setAuthToken(state = null, action: {
type: string, token: string }) {
if (action.type === SET_AUTH_TOKEN) return action.token;
return state;
}
function setUnreadMessages(state = {
}, action: {
type: string, payload: any })
: {
[user: string]: ChatMessage[]
} {
switch (action.type) {
case ADD_UNREAD_MESSAGE:
const {
fromId, toId, myId } = action.payload;
const messageOwnerId = fromId === myId ? toId : fromId;
const newArr = state[`${
messageOwnerId}` as keyof typeof state]
? [...state[`${
messageOwnerId}` as keyof typeof state]]
: new Array();
newArr.push(action.payload);
return Object.assign({
}, state, {
[`${
messageOwnerId}`]: newArr,
});
case REMOVE_UNREAD_MESSAGES:
const {
userId } = action.payload;
const newState = Object.assign({
}, state);
delete newState[`${
userId}` as keyof typeof newState];
return newState;
default:
return state;
}
}
function setMessageHistory(state = {
}, action: {
type: string, payload: any }) {
switch (action.type) {
case INIT_MESSAGE_HISTORY:
return action.payload;
case SYNC_CLOUD_MESSAGE_HISTORY:
return Object.assign({
}, state, action.payload);
case GET_MORE_MESSAGE_HISTORY:
const chatId = action.payload.chatId as keyof typeof state
const newArr1 = state[`${
chatId}`] ? [...state[`${
chatId}`]] : new Array();
newArr1.unshift(action.payload);
return Object.assign({
}, state, {
[`${
chatId}`]: newArr1,
});
case ADD_MESSAGE_HISTORY:
const {
fromId, toId, myId } = action.payload;
const messageOwnerId = (fromId === myId ? toId : fromId) as keyof typeof state;
const newArr2 = state[`${
messageOwnerId}`]
? [...state[`${
messageOwnerId}`]]
: new Array();
newArr2.push(action.payload);
const newMessages = Object.assign({
}, state, {
[`${
messageOwnerId}`]: newArr2,
});
return newMessages;
case REMOVE_MESSAGE_HISTORY:
const newState = Object.assign({
}, state);
const userId = action.payload.userId as keyof typeof newState;
delete newState[`${
userId}`];
return newState;
default:
return state;
}
}
function setCallStatus(state = CALL_STATUS_FREE, action: {
type: string, status: ChatWebSocketType }) {
if (action.type === SET_CALL_STATUS) {
return action.status;
}
return state;
}
const reducers = combineReducers({
nowChattingId: setNowChattingId,
nowWebrtcFriendId: setNowWebrtcFriendId,
availableVideoDevices: updateAvailableVideoDevices,
availableAudioDevices: updateAvailableAudioDevices,
usingVideoDevice: exchangeVideoDevice,
usingAudioDevice: exchangeAudioDevice,
authToken: setAuthToken,
unreadMessages: setUnreadMessages,
messageHistory: setMessageHistory,
callStatus: setCallStatus,
});
export default reducers;
store.ts
import {
configureStore } from '@reduxjs/toolkit';
import reducers from './reducers';
const store = configureStore({
reducer: reducers,
});
export default store;
增加新的代码
为了提高开发效率和代码复用能力,我在 src/Utils
文件夹下定义了三个新的文件:
- Constraints.ts:定义了一些全局常量
- Global.ts:定义了一些零散的、但是经常被各种组件引用的全局函数
- Types.ts:为了开发方便而定义的一些类型
Constraints.ts
/**
* 这个文件用来存放一些常量
*/
// 音视频设备
export const DEVICE_TYPE = {
VIDEO_DEVICE: 'video',
AUDIO_DEVICE: 'audio',
};
/**
* 通话状态
*/
export const CALL_STATUS_FREE = 0;
export const CALL_STATUS_OFFERING = 1;
export const CALL_STATUS_OFFERED = 2;
export const CALL_STATUS_ANSWERING = 3;
export const CALL_STATUS_CALLING = 4;
/**
* 回复好友申请
*/
export const ACCEPT_FRIEND_REQUEST = 2;
export const REJECT_FRIEND_REQUEST = 1;
export const NO_OPERATION_FRIEND_REQUEST = -1;
/**
* 聊天系统 WebSocket type 参数
*/
export enum ChatWebSocketType {
UNDEFINED_0, // 未定义 0 占位
CHAT_SEND_PRIVATE_MESSAGE, // 发送私聊消息
CHAT_READ_MESSAGE, // 签收私聊消息
CHAT_SEND_FRIEND_REQUEST, // 发送好友请求
CHAT_ANSWER_FRIEND_REQUEST, // 响应好友请求
CHAT_PRIVATE_WEBRTC_OFFER, // 发送视频聊天请求 OFFER
CHAT_PRIVATE_WEBRTC_ANSWER, // 响应视频聊天请求 ANSWER
CHAT_PRIVATE_WEBRTC_CANDIDATE, // 视频聊天 ICE 候选者
CHAT_PRIVATE_WEBRTC_DISCONNECT, // 断开视频聊天
}
Global.ts
/**
* 这个文件用来存放一些不好分类的全局函数
*/
import jwtDecode from 'jwt-decode';
import {
DEVICE_TYPE } from './Constraints';
import store from './Store/store';
import {
DeviceInfo } from './Types';
/**
* 用来返回 mainContent 模态屏遮罩层挂载DOM
* @returns Id值为'mainContent'的DOM
*/
function getMainContent() {
const content = document.getElementById('mainContent');
if (content) {
return content
} else {
return document.body
}
}
/**
* 由于直接使用 jwtDecode 解析非法 token 会报错,因此进行封装
* @param {string} token
* @returns 解析后的 token
*/
function decodeJWT(token: string): any {
try {
return jwtDecode(token);
} catch (error: any) {
if (error.message === 'Invalid token specified') return undefined;
console.log(error);
}
}
/**
* 封装后的获取设备流函数
* @param {string} device 设备类型 DEVICE_TYPE
* @returns
*/
function getDeviceStream(device: string) {
switch (device) {
case DEVICE_TYPE.AUDIO_DEVICE:
const audioDevice = store.getState().usingAudioDevice as DeviceInfo;
const audioConstraints = {
deviceId: {
exact: audioDevice.deviceId,
},
noiseSuppression: localStorage.getItem('noiseSuppression') !== 'false',
echoCancellation: localStorage.getItem('echoCancellation') !== 'false',
};
return navigator.mediaDevices.getUserMedia({
audio: audioConstraints });
case DEVICE_TYPE.VIDEO_DEVICE:
const videoDevice = store.getState().usingVideoDevice as DeviceInfo;
const videoConstraints = {
deviceId: {
exact: videoDevice.deviceId,
},
width: 1920,
height: 1080,
frameRate: {
max: 30,
},
};
return navigator.mediaDevices.getUserMedia({
video: videoConstraints,
});
default:
return Promise.resolve(new MediaStream());
}
}
export const A_SECOND_TIME = 1000;
export const A_MINUTE_TIME = 60 * A_SECOND_TIME;
export const AN_HOUR_TIME = 60 * A_MINUTE_TIME;
export const A_DAY_TIME = 24 * AN_HOUR_TIME;
export const isSameDay = (timeStampA: string | number | Date, timeStampB: string | number | Date) => {
const dateA = new Date(timeStampA);
const dateB = new Date(timeStampB);
return dateA.setHours(0, 0, 0, 0) === dateB.setHours(0, 0, 0, 0);
};
export const isSameWeek = (timeStampA: string | number | Date, timeStampB: string | number | Date) => {
let A = new Date(timeStampA).setHours(0, 0, 0, 0);
let B = new Date(timeStampB).setHours(0, 0, 0, 0);
const timeDistance = Math.abs(A - B);
return timeDistance / A_DAY_TIME;
};
export const isSameYear = (timeStampA: string | number | Date, timeStampB: string | number | Date) => {
const dateA = new Date(timeStampA);
const dateB = new Date(timeStampB);
dateA.setHours(0, 0, 0, 0);
dateB.setHours(0, 0, 0, 0);
dateA.setMonth(0, 1);
dateB.setMonth(0, 1);
return dateA.getFullYear() === dateB.getFullYear();
};
export const translateDayNumberToDayChara = (day: any) => {
if (typeof day === 'number') {
day = day % 7;
}
switch (day) {
case 0:
return '星期天';
case 1:
return '星期一';
case 2:
return '星期二';
case 3:
return '星期三';
case 4:
return '星期四';
case 5:
return '星期五';
case 6:
return '星期六';
default:
return String(day);
}
};
export {
decodeJWT, getMainContent, getDeviceStream };
Types.ts
import {
ReactNode } from "react"
export interface ChatMessage {
date: number,
fromId: number,
id: number,
message: string,
toId: number,
myId?: number,
userId: number
}
export interface DeviceInfo {
webLabel?: ReactNode
deviceId: string,
label: string,
}
export interface UserInfo {
email: string,
exp: number,
iat: number,
id: number,
iss: string,
profile: string,
role: [
{
authority: string,
id: number
}
],
sub: string,
username: string
}