当前位置:网站首页>【山大会议】使用TypeScript为项目进行重构
【山大会议】使用TypeScript为项目进行重构
2022-06-22 14:41:00 【小栗帽今天吃什么】
文章目录
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
}
边栏推荐
猜你喜欢

MongoDB在腾讯零售优码中的应用

Discover the number of media new users can insert

Fast and accurate point cloud registration based on minimizing 3D NDT distance

HMS Core新闻行业解决方案:让技术加上人文的温度

建议自查!MySQL驱动Bug引发的事务不回滚问题,也许你正面临该风险!

Hongshi electric appliance rushes to the Growth Enterprise Market: the annual revenue is 600million yuan. Liujinxian's equity was frozen by Guangde small loan

Ultimate efficiency is the foundation for the cloud native database tdsql-c to settle down

Recommend several AI Intelligent Platforms

Scala语言学习-05-递归和尾递归效率对比

Jenkins 通过检查代码提交自动触发编译
随机推荐
ArcGIS JS之 4.23之IIS本地部署与问题解决
得物App数据模拟平台的探索和实践
mysql - sql执行过程
New design of databend SQL planner
[Huawei cloud to Jian Zhiyuan] the winning list of essay solicitation is released!
Bridging the gap between open source databases and database services
[Shangshui Shuo series] day three - VIDEO
Scala语言学习-05-递归和尾递归效率对比
Development status of full color LED display
Huawei cloud hcdez special session and Distributed Technology Summit: Huawei cloud distributed cloud native technology and Practice
二分查找(整数二分)
关于 GIN 的路由树
pymssql模块使用指南
C language learning -17- function is passed in as a parameter
OOP 多重收纳(类模板)
问一下想获取到sqlserver的start_lsn有好的办法吗?
[single chip microcomputer] [make buzzer sound] know the buzzer and let it make the sound you want
建议自查!MySQL驱动Bug引发的事务不回滚问题,也许你正面临该风险!
DDD understanding of Domain Driven Design
School enterprise alliance is on the way! Huawei cloud gaussdb has come to universities again