feat: update

This commit is contained in:
landaiqing
2024-05-20 14:34:52 +08:00
parent 3d60f1478a
commit 7e87781c85
53 changed files with 3726 additions and 2537 deletions

View File

@@ -8,8 +8,8 @@ VITE_APP_BASE_API='/api'
VITE_APP_TITLE=开发环境 VITE_APP_TITLE=开发环境
# 网络请求公用地址 # 网络请求公用地址
#VITE_API_BASE_URL='http://127.0.0.1:3000' VITE_API_BASE_URL='http://127.0.0.1:3000'
VITE_API_BASE_URL='http://1.95.0.111:3000' #VITE_API_BASE_URL='http://1.95.0.111:4000'
VITE_TITLE_NAME='五味子云存储' VITE_TITLE_NAME='五味子云存储'

View File

@@ -7,7 +7,7 @@ VITE_APP_BASE_API='/api'
VITE_APP_TITLE=生产环境 VITE_APP_TITLE=生产环境
# 网络请求公用地址 # 网络请求公用地址
VITE_API_BASE_URL='http://1.95.0.111:4000' VITE_API_BASE_URL='http://1.95.0.111:3000'
VITE_TITLE_NAME='五味子云存储' VITE_TITLE_NAME='五味子云存储'
@@ -15,4 +15,4 @@ VITE_TITLE_NAME='五味子云存储'
VITE_APP_TOKEN_KEY='token' VITE_APP_TOKEN_KEY='token'
# the upload url # the upload url
VITE_UPLOAD_URL='http://1.95.0.111:4000' VITE_UPLOAD_URL='http://1.95.0.111:3000'

View File

@@ -37,7 +37,7 @@ module.exports = {
'react/jsx-use-react': 0, // React V17开始JSX已经不再需要引入React 'react/jsx-use-react': 0, // React V17开始JSX已经不再需要引入React
'react/react-in-jsx-scope': 0, // 同上 'react/react-in-jsx-scope': 0, // 同上
'import/first': 0, // 消除绝对路径必须要在相对路径前引入, 'import/first': 0, // 消除绝对路径必须要在相对路径前引入,
'no-mixed-spaces-and-tabs': 2, // 禁止空格和 tab 的混合缩进 // 'no-mixed-spaces-and-tabs': 2, // 禁止空格和 tab 的混合缩进
'no-debugger': 2, // 禁止有debugger 'no-debugger': 2, // 禁止有debugger
'space-infix-ops': 2, // 要求操作符周围有空格 'space-infix-ops': 2, // 要求操作符周围有空格
'space-before-blocks': 2, // 要求语句块之前有空格 'space-before-blocks': 2, // 要求语句块之前有空格

View File

@@ -1,16 +1,16 @@
module.exports = { module.exports = {
printWidth: 100, //单行长度 printWidth: 100, //单行长度
tabWidth: 4, //缩进长度 // tabWidth: 4, //缩进长度
useTabs: false, //使用空格代替tab缩进 // useTabs: true, //使用空格代替tab缩进
semi: false, //句末使用分号 semi: true, //句末使用分号
singleQuote: true, //使用单引号 // singleQuote: true, //使用单引号
quoteProps: 'as-needed', //仅在必需时为对象的key添加引号 quoteProps: 'as-needed', //仅在必需时为对象的key添加引号
jsxSingleQuote: true, // jsx中使用单引号 jsxSingleQuote: false, // jsx中使用单引号
bracketSpacing: true, //在对象前后添加空格-eg: { foo: bar } bracketSpacing: true, //在对象前后添加空格-eg: { foo: bar }
jsxBracketSameLine: true, //多属性html标签的>’折行放置 jsxBracketSameLine: true, //多属性html标签的>’折行放置
arrowParens: 'always', //单参数箭头函数参数周围使用圆括号-eg: (x) => x arrowParens: 'always', //单参数箭头函数参数周围使用圆括号-eg: (x) => x
requirePragma: false, //无需顶部注释即可格式化 requirePragma: false, //无需顶部注释即可格式化
insertPragma: false, //在已被preitter格式化的文件顶部加上标注 insertPragma: true, //在已被preitter格式化的文件顶部加上标注
endOfLine: 'auto', //结束行形式 endOfLine: 'auto', //结束行形式
embeddedLanguageFormatting: 'auto', //对引用代码进行格式化 embeddedLanguageFormatting: 'auto', //对引用代码进行格式化
} }

View File

@@ -1,66 +1,69 @@
{ {
"name": "schisandra-cloud-album-front-react", "name": "schisandra-cloud-album-front-react",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --mode development --host", "dev": "vite --mode development --host",
"build": "tsc && vite build --mode production", "build": "tsc && vite build --mode production",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.3.7", "@ant-design/icons": "^5.3.7",
"@ant-design/pro-components": "^2.7.1", "@ant-design/pro-components": "^2.7.1",
"@ant-design/use-emotion-css": "^1.0.4", "@ant-design/use-emotion-css": "^1.0.4",
"@babel/preset-env": "^7.24.5", "@babel/preset-env": "^7.24.5",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@vitejs/plugin-legacy": "^5.4.0", "@vitejs/plugin-legacy": "^5.4.0",
"antd": "^5.17.0", "antd": "^5.17.0",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"axios": "^1.6.8", "axios": "^1.6.8",
"core-js": "^3.37.0", "core-js": "^3.37.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"gsap": "^3.12.5", "gsap": "^3.12.5",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"mobx": "^6.12.3", "mobx": "^6.12.3",
"mobx-persist-store": "^1.1.5", "mobx-persist-store": "^1.1.5",
"mobx-react": "^9.1.1", "mobx-react": "^9.1.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-rotate-captcha": "^1.0.26", "react-rotate-captcha": "^1.0.26",
"react-router-dom": "^6.23.0", "react-router-dom": "^6.23.0",
"regenerator-runtime": "^0.14.1", "regenerator-runtime": "^0.14.1",
"vite-plugin-compression": "^0.5.1", "tailwind-merge": "^2.3.0",
"vite-plugin-html": "^3.2.2", "tailwindcss": "^3.4.3",
"vite-plugin-svg-icons": "^2.0.1" "tailwindcss-animate": "^1.0.7",
}, "vite-plugin-compression": "^0.5.1",
"devDependencies": { "vite-plugin-html": "^3.2.2",
"@rollup/plugin-babel": "^6.0.4", "vite-plugin-svg-icons": "^2.0.1"
"@types/gsap": "^3.0.0", },
"@types/node": "^20.12.11", "devDependencies": {
"@types/react": "^18.3.1", "@rollup/plugin-babel": "^6.0.4",
"@types/react-dom": "^18.3.0", "@types/gsap": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^7.8.0", "@types/node": "^20.12.11",
"@typescript-eslint/parser": "^7.8.0", "@types/react": "^18.3.1",
"@vitejs/plugin-react": "^4.2.1", "@types/react-dom": "^18.3.0",
"eslint": "^8.57.0", "@typescript-eslint/eslint-plugin": "^7.8.0",
"eslint-config-prettier": "^9.1.0", "@typescript-eslint/parser": "^7.8.0",
"eslint-plugin-prettier": "^5.1.3", "@vitejs/plugin-react": "^4.2.1",
"eslint-plugin-react": "^7.34.1", "eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-react-refresh": "^0.4.6", "eslint-plugin-prettier": "^5.1.3",
"less": "^4.2.0", "eslint-plugin-react": "^7.34.1",
"postcss-less": "^6.0.0", "eslint-plugin-react-hooks": "^4.6.0",
"postcss-preset-env": "^9.5.11", "eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.2.5", "less": "^4.2.0",
"stylelint": "^16.5.0", "postcss-less": "^6.0.0",
"stylelint-config-recess-order": "^5.0.1", "postcss-preset-env": "^9.5.11",
"stylelint-config-standard-less": "^3.0.1", "prettier": "^3.2.5",
"stylelint-order": "^6.0.4", "stylelint": "^16.5.0",
"typescript": "^5.4.5", "stylelint-config-recess-order": "^5.0.1",
"unplugin-imagemin": "^0.5.18", "stylelint-config-standard-less": "^3.0.1",
"vite": "^5.2.11" "stylelint-order": "^6.0.4",
} "typescript": "^5.4.5",
"unplugin-imagemin": "^0.5.18",
"vite": "^5.2.11"
}
} }

1190
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,28 @@
import web from '@/utils/axios/web.ts' /** @format */
import web from "@/utils/axios/web.ts";
/** /**
* 获取验证码 * 获取验证码
*/ */
export const getCaptcha = () => { export const getCaptcha = () => {
return web.post('/ReactRotateCaptcha/get') return web.request({
} url: "/ReactRotateCaptcha/get",
method: "get",
});
};
/** /**
* 验证验证码 * 验证验证码
* @param data * @param data
* @constructor * @constructor
*/ */
export const VerfiyCaptcha = (data: any) => { export const VerfiyCaptcha = (data: any) => {
return web.post('/ReactRotateCaptcha/verfiy', data) return web.request({
} url: "/ReactRotateCaptcha/verfiy",
method: "post",
headers: {
"Content-Type": "application/json;charset=UTF-8",
},
data: data,
});
};

View File

@@ -1,68 +1,68 @@
const calcSize = (img: HTMLImageElement, size: number) => { const calcSize = (img: HTMLImageElement, size: number) => {
const src_src = Math.max(Math.min(img.width, img.height, size), 160) const src_src = Math.max(Math.min(img.width, img.height, size), 160);
const dst_w = src_src const dst_w = src_src;
const dst_h = src_src const dst_h = src_src;
const dst_scale = dst_h / dst_w // Target image ratio const dst_scale = dst_h / dst_w; // Target image ratio
const src_scale = img.height / img.width // Original image aspect ratio const src_scale = img.height / img.width; // Original image aspect ratio
const info = const info =
src_scale >= dst_scale src_scale >= dst_scale
? [ ? [
Math.round(img.height * (src_src / img.width)), Math.round(img.height * (src_src / img.width)),
0, 0,
Math.round((img.height - img.width) / 2), Math.round((img.height - img.width) / 2),
] ]
: [ : [
Math.round(img.width * (src_src / img.height)), Math.round(img.width * (src_src / img.height)),
Math.round((img.width - img.height) / 2), Math.round((img.width - img.height) / 2),
0, 0,
] ];
return [img.width, img.height, src_src, ...info] return [img.width, img.height, src_src, ...info];
} };
const build = (img: HTMLImageElement, sizes: number[]): [number, string] => { const build = (img: HTMLImageElement, sizes: number[]): [number, string] => {
const [src_w, src_h, size, tar_size, x, y] = sizes const [src_w, src_h, size, tar_size, x, y] = sizes;
const canvas = document.createElement('canvas') const canvas = document.createElement("canvas");
canvas.width = size canvas.width = size;
canvas.height = size canvas.height = size;
const max = 275 - 50 const max = 275 - 50;
const min = 0 const min = 0;
const ave = Math.round((360 / max) * 100) / 100 const ave = Math.round((360 / max) * 100) / 100;
const ctx = canvas.getContext('2d') const ctx = canvas.getContext("2d");
const coordinate = size / 2 const coordinate = size / 2;
const moveX = Math.floor(Math.random() * (max - min + 1)) const moveX = Math.floor(Math.random() * (max - min + 1));
ctx?.beginPath() ctx?.beginPath();
ctx?.translate(coordinate, coordinate) ctx?.translate(coordinate, coordinate);
ctx?.rotate((moveX * -1 * ave * Math.PI) / 180) ctx?.rotate((moveX * -1 * ave * Math.PI) / 180);
ctx?.translate(-coordinate, -coordinate) ctx?.translate(-coordinate, -coordinate);
ctx?.drawImage(img, x, y, src_w, src_h, 0, 0, tar_size, size) ctx?.drawImage(img, x, y, src_w, src_h, 0, 0, tar_size, size);
ctx!.globalCompositeOperation = 'destination-in' ctx!.globalCompositeOperation = "destination-in";
ctx?.arc(size / 2, size / 2, size / 2, 0, (360 * Math.PI) / 180, false) ctx?.arc(size / 2, size / 2, size / 2, 0, (360 * Math.PI) / 180, false);
ctx?.fill() ctx?.fill();
ctx?.restore() ctx?.restore();
return [(360 / (max - min)) * moveX, canvas.toDataURL('image/png')] return [(360 / (max - min)) * moveX, canvas.toDataURL("image/png")];
} };
export const handle = (url: string, size: number = 350) => export const handle = (url: string, size: number = 350) =>
new Promise<[number, string]>((resovle) => { new Promise<[number, string]>((resovle) => {
const img = new Image() const img = new Image();
img.onerror = function () { img.onerror = function () {
console.log('image load error') console.log("image load error");
} };
img.onload = function () { img.onload = function () {
const sizes = calcSize(img, size) const sizes = calcSize(img, size);
const arc_img = build(img, sizes) const arc_img = build(img, sizes);
resovle(arc_img) resovle(arc_img);
} };
img.src = url img.src = url;
}) });

View File

@@ -1,44 +1,46 @@
import type { TicketInfoType, TokenInfoType } from 'react-rotate-captcha' import type { TicketInfoType, TokenInfoType } from "react-rotate-captcha";
import { getCaptcha, VerfiyCaptcha } from '@/api/captcha/api.ts' import { getCaptcha, VerfiyCaptcha } from "@/api/captcha/api.ts";
export type ActionType = { export type ActionType = {
code: 0 | 1 code: 0 | 1;
msg: string msg: string;
} };
let image: string = '' let image: string = "";
export async function get(): Promise<TokenInfoType> { export async function get(): Promise<TokenInfoType> {
const res: any = await getCaptcha() const res: any = await getCaptcha();
image = res.data.str image = res.data.str;
return res return res;
} }
export function isSupportWebp() { export function isSupportWebp() {
try { try {
return ( return (
document document
.createElement('canvas') .createElement("canvas")
.toDataURL('image/webp', 0.5) .toDataURL("image/webp", 0.5)
.indexOf('data:image/webp') === 0 .indexOf("data:image/webp") === 0
) );
} catch (err) { } catch (err) {
return false return false;
} }
} }
export async function load() { export async function load() {
return image return image;
} }
export function sleep(time: number) { export function sleep(time: number) {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => resolve(true), time) setTimeout(() => resolve(true), time);
}) });
} }
export async function verify(token: string, deg: number): Promise<TicketInfoType> { export async function verify(token: string, deg: number): Promise<TicketInfoType> {
const data: any = { const data: any = {
token: token, token: token,
deg: deg, deg: deg,
} };
const res: any = await VerfiyCaptcha(data) const res: any = await VerfiyCaptcha(data);
return res return res;
} }

View File

@@ -1,12 +1,30 @@
import web from '@/utils/axios/web.ts' import web from "@/utils/axios/web.ts";
/** /**
* 初始化minio * 初始化minio
*/ */
export const initMinio = (data: string) => { export const initMinio = (data: any) => {
return web.post('/oss/minio/init', data) return web.request({
} url: "/oss/minio/init",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
},
method: "post",
data: {
userId: data,
},
});
};
export const getBaseInfo = (data: string) => { export const getBaseInfo = (data: any) => {
return web.post('/oss/minio/getBaseInfo', data) return web.request({
} url: "/oss/minio/getBaseInfo",
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
},
data: {
fileName: data,
},
});
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
src/assets/images/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
src/assets/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/assets/images/pilot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 0 B

View File

@@ -28,4 +28,3 @@
&::-webkit-resizer { &::-webkit-resizer {
visibility: hidden; visibility: hidden;
} }

View File

@@ -1,47 +1,50 @@
import React, { useEffect } from 'react' /** @format */
import styles from './index.module.less'
import { gsap } from 'gsap'
const BlurCard: React.FC = () => {
useEffect(() => {
const UPDATE = ({ x, y }: { x: any; y: any }) => {
gsap.set(document.documentElement, {
'--x': gsap.utils.mapRange(0, window.innerWidth, -1, 1, x),
'--y': gsap.utils.mapRange(0, window.innerHeight, -1, 1, y),
})
}
window.addEventListener('pointermove', UPDATE) import React, { useEffect } from "react";
}, []) import styles from "./index.module.less";
return ( import { gsap } from "gsap";
<>
<article> const BlurCard: React.FC = () => {
<img src='https://assets.codepen.io/605876/osaka-sky.jpeg' alt='' /> useEffect(() => {
<h3>Osaka</h3> const UPDATE = ({ x, y }: { x: any; y: any }) => {
<img src='https://assets.codepen.io/605876/osaka-tower.png' alt='' /> gsap.set(document.documentElement, {
<div className={styles.blur}> "--x": gsap.utils.mapRange(0, window.innerWidth, -1, 1, x),
<img src='https://assets.codepen.io/605876/osaka.jpeg' alt='' /> "--y": gsap.utils.mapRange(0, window.innerHeight, -1, 1, y),
<div></div> });
--&gt; };
</div>
<div className={styles.content}> window.addEventListener("pointermove", UPDATE);
<p> }, []);
<svg return (
xmlns='http://www.w3.org/2000/svg' <>
viewBox='0 0 24 24' <article>
fill='currentColor' <img src="https://assets.codepen.io/605876/osaka-sky.jpeg" alt="" />
className='w-6 h-6'> <h3>Osaka</h3>
<path d='M15.75 8.25a.75.75 0 0 1 .75.75c0 1.12-.492 2.126-1.27 2.812a.75.75 0 1 1-.992-1.124A2.243 2.243 0 0 0 15 9a.75.75 0 0 1 .75-.75Z'></path> <img src="https://assets.codepen.io/605876/osaka-tower.png" alt="" />
<path <div className={styles.blur}>
fillRule='evenodd' <img src="https://assets.codepen.io/605876/osaka.jpeg" alt="" />
d='M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25ZM4.575 15.6a8.25 8.25 0 0 0 9.348 4.425 1.966 1.966 0 0 0-1.84-1.275.983.983 0 0 1-.97-.822l-.073-.437c-.094-.565.25-1.11.8-1.267l.99-.282c.427-.123.783-.418.982-.816l.036-.073a1.453 1.453 0 0 1 2.328-.377L16.5 15h.628a2.25 2.25 0 0 1 1.983 1.186 8.25 8.25 0 0 0-6.345-12.4c.044.262.18.503.389.676l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 0 1-1.161.886l-.143.048a1.107 1.107 0 0 0-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 0 1-1.652.928l-.679-.906a1.125 1.125 0 0 0-1.906.172L4.575 15.6Z' <div></div>
clipRule='evenodd'></path> --&gt;
</svg> </div>
<span>GuGong GuGong</span> <div className={styles.content}>
</p> <p>
<p>GuGong, China</p> <svg
</div> xmlns="http://www.w3.org/2000/svg"
</article> viewBox="0 0 24 24"
</> fill="currentColor"
) className="w-6 h-6">
} <path d="M15.75 8.25a.75.75 0 0 1 .75.75c0 1.12-.492 2.126-1.27 2.812a.75.75 0 1 1-.992-1.124A2.243 2.243 0 0 0 15 9a.75.75 0 0 1 .75-.75Z"></path>
export default BlurCard <path
fillRule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25ZM4.575 15.6a8.25 8.25 0 0 0 9.348 4.425 1.966 1.966 0 0 0-1.84-1.275.983.983 0 0 1-.97-.822l-.073-.437c-.094-.565.25-1.11.8-1.267l.99-.282c.427-.123.783-.418.982-.816l.036-.073a1.453 1.453 0 0 1 2.328-.377L16.5 15h.628a2.25 2.25 0 0 1 1.983 1.186 8.25 8.25 0 0 0-6.345-12.4c.044.262.18.503.389.676l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 0 1-1.161.886l-.143.048a1.107 1.107 0 0 0-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 0 1-1.652.928l-.679-.906a1.125 1.125 0 0 0-1.906.172L4.575 15.6Z"
clipRule="evenodd"></path>
</svg>
<span>GuGong GuGong</span>
</p>
<p>GuGong, China</p>
</div>
</article>
</>
);
};
export default BlurCard;

View File

@@ -1,8 +1,11 @@
import Loading from '@/components/Loading' /** @format */
import { Suspense } from 'react'
import Loading from "@/components/Loading";
import { Suspense } from "react";
const ComponentLoading = (Component: any, props: any) => ( const ComponentLoading = (Component: any, props: any) => (
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
<Component {...props} /> <Component {...props} />
</Suspense> </Suspense>
) );
export default ComponentLoading export default ComponentLoading;

View File

@@ -1,17 +1,19 @@
import React from 'react' /** @format */
const FooterComponent: React.FC = () => {
return (
<div
style={{
textAlign: 'center',
color: 'black',
bottom: 0,
position: 'absolute',
width: '100%',
}}>
schisandra ©{new Date().getFullYear()} Created by schisandra
</div>
)
}
export default FooterComponent import React from "react";
const FooterComponent: React.FC = () => {
return (
<div
style={{
textAlign: "center",
color: "black",
bottom: 0,
position: "absolute",
width: "100%",
}}>
schisandra ©{new Date().getFullYear()} Created by schisandra
</div>
);
};
export default FooterComponent;

File diff suppressed because it is too large Load Diff

View File

@@ -1,60 +1,63 @@
import React, { useEffect } from 'react' /** @format */
import './index.less'
import React, { useEffect } from "react";
import "./index.less";
const Loading: React.FC = () => { const Loading: React.FC = () => {
useEffect(() => { useEffect(() => {
document.body.classList.add('loading-body') document.body.classList.add("loading-body");
return () => { return () => {
document.body.classList.remove('loading-body') document.body.classList.remove("loading-body");
} };
}, []) }, []);
return ( return (
<> <>
<svg className='gegga'> <svg className="gegga">
<defs> <defs>
<filter id='gegga'> <filter id="gegga">
<feGaussianBlur <feGaussianBlur
in='SourceGraphic' in="SourceGraphic"
stdDeviation='7' stdDeviation="7"
result='blur'></feGaussianBlur> result="blur"></feGaussianBlur>
<feColorMatrix <feColorMatrix
in='blur' in="blur"
mode='matrix' mode="matrix"
values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10' values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10"
result='inreGegga'></feColorMatrix> result="inreGegga"></feColorMatrix>
<feComposite <feComposite
in='SourceGraphic' in="SourceGraphic"
in2='inreGegga' in2="inreGegga"
operator='atop'></feComposite> operator="atop"></feComposite>
</filter> </filter>
</defs> </defs>
</svg> </svg>
<svg className='snurra' width='200' height='200' viewBox='0 0 200 200'> <svg className="snurra" width="200" height="200" viewBox="0 0 200 200">
<defs> <defs>
<linearGradient id='linjärGradient'> <linearGradient id="linjärGradient">
<stop className='stopp1' offset='0'></stop> <stop className="stopp1" offset="0"></stop>
<stop className='stopp2' offset='1'></stop> <stop className="stopp2" offset="1"></stop>
</linearGradient> </linearGradient>
<linearGradient <linearGradient
y2='160' y2="160"
x2='160' x2="160"
y1='40' y1="40"
x1='40' x1="40"
gradientUnits='userSpaceOnUse' gradientUnits="userSpaceOnUse"
id='gradient' id="gradient"
xlinkHref='#linjärGradient'></linearGradient> xlinkHref="#linjärGradient"></linearGradient>
</defs> </defs>
<path <path
className='halvan' className="halvan"
d='m 164,100 c 0,-35.346224 -28.65378,-64 -64,-64 -35.346224,0 -64,28.653776 -64,64 0,35.34622 28.653776,64 64,64 35.34622,0 64,-26.21502 64,-64 0,-37.784981 -26.92058,-64 -64,-64 -37.079421,0 -65.267479,26.922736 -64,64 1.267479,37.07726 26.703171,65.05317 64,64 37.29683,-1.05317 64,-64 64,-64'></path> d="m 164,100 c 0,-35.346224 -28.65378,-64 -64,-64 -35.346224,0 -64,28.653776 -64,64 0,35.34622 28.653776,64 64,64 35.34622,0 64,-26.21502 64,-64 0,-37.784981 -26.92058,-64 -64,-64 -37.079421,0 -65.267479,26.922736 -64,64 1.267479,37.07726 26.703171,65.05317 64,64 37.29683,-1.05317 64,-64 64,-64"></path>
<circle className='strecken' cx='100' cy='100' r='64'></circle> <circle className="strecken" cx="100" cy="100" r="64"></circle>
</svg> </svg>
{/*<svg className='skugga' width='200' height='200' viewBox='0 0 200 200'>*/} {/*<svg className='skugga' width='200' height='200' viewBox='0 0 200 200'>*/}
{/* <path*/} {/* <path*/}
{/* className='halvan'*/} {/* className='halvan'*/}
{/* d='m 164,100 c 0,-35.346224 -28.65378,-64 -64,-64 -35.346224,0 -64,28.653776 -64,64 0,35.34622 28.653776,64 64,64 35.34622,0 64,-26.21502 64,-64 0,-37.784981 -26.92058,-64 -64,-64 -37.079421,0 -65.267479,26.922736 -64,64 1.267479,37.07726 26.703171,65.05317 64,64 37.29683,-1.05317 64,-64 64,-64'></path>*/} {/* d='m 164,100 c 0,-35.346224 -28.65378,-64 -64,-64 -35.346224,0 -64,28.653776 -64,64 0,35.34622 28.653776,64 64,64 35.34622,0 64,-26.21502 64,-64 0,-37.784981 -26.92058,-64 -64,-64 -37.079421,0 -65.267479,26.922736 -64,64 1.267479,37.07726 26.703171,65.05317 64,64 37.29683,-1.05317 64,-64 64,-64'></path>*/}
{/* <circle className='strecken' cx='100' cy='100' r='64'></circle>*/} {/* <circle className='strecken' cx='100' cy='100' r='64'></circle>*/}
{/*</svg>*/} {/*</svg>*/}
</> </>
) );
} };
export default React.memo(Loading) export default React.memo(Loading);

View File

@@ -1 +0,0 @@
export const DEFAULT_NAME = 'admin'

View File

@@ -1,25 +1,28 @@
import React, { useEffect } from 'react' /** @format */
import LeftArea from '@/layout/default/left-area'
import MainArea from '@/layout/default/main-area' import React, { useEffect } from "react";
import RightArea from '@/layout/default/right-area' import LeftArea from "@/layout/default/left-area";
import './index.less' import MainArea from "@/layout/default/main-area";
import Footer from '@/components/Footer' import RightArea from "@/layout/default/right-area";
import "./index.less";
import Footer from "@/components/Footer";
const DefaultLayOut: React.FC = () => { const DefaultLayOut: React.FC = () => {
useEffect(() => { useEffect(() => {
document.body.classList.add('main-body') document.body.classList.add("main-body");
return () => { return () => {
document.body.classList.remove('main-body') document.body.classList.remove("main-body");
} };
}, []) }, []);
return ( return (
<> <>
<div className='app-container'> <div className="app-container">
<LeftArea /> <LeftArea />
<MainArea /> <MainArea />
<RightArea /> <RightArea />
</div> </div>
<Footer /> <Footer />
</> </>
) );
} };
export default DefaultLayOut export default DefaultLayOut;

8
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,8 @@
/** @format */
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -1,16 +1,20 @@
import React from 'react' /** @format */
import ReactDOM from 'react-dom/client'
import { RouterProvider, createBrowserRouter } from 'react-router-dom' import React from "react";
import './assets/styles/index.less' import ReactDOM from "react-dom/client";
import routeConfig from './router' import { RouterProvider, createBrowserRouter } from "react-router-dom";
import 'virtual:svg-icons-register' import "./assets/styles/index.less";
import { Provider as MobxProvider } from 'mobx-react' import routeConfig from "./router";
import { RootStore } from '@/store' import "virtual:svg-icons-register";
const router = createBrowserRouter(routeConfig) import { Provider as MobxProvider } from "mobx-react";
ReactDOM.createRoot(document.getElementById('root')!).render( import { RootStore } from "@/store";
<React.StrictMode>
<MobxProvider {...RootStore}> const router = createBrowserRouter(routeConfig);
<RouterProvider router={router} /> ReactDOM.createRoot(document.getElementById("root")!).render(
</MobxProvider>, <React.StrictMode>
</React.StrictMode>, <MobxProvider {...RootStore}>
) <RouterProvider router={router} />
</MobxProvider>
,
</React.StrictMode>,
);

View File

@@ -1,37 +1,40 @@
import type { RouteObject } from 'react-router-dom' /** @format */
import NoFound from '@/views/404/404' import type { RouteObject } from "react-router-dom";
import Login from './modules/login/index.ts'
import Register from './modules/register/index.ts' import NoFound from "@/views/404/404";
import home from './modules/home/index.ts' import Login from "./modules/login/index.ts";
import Main from './modules/main/index.ts' import Register from "./modules/register/index.ts";
import home from "./modules/home/index.ts";
import Main from "./modules/main/index.ts";
import ComponentLoading from "@/components/ComponentLoading";
import ComponentLoading from '@/components/ComponentLoading'
const routes: RouteObject[] = [ const routes: RouteObject[] = [
{ {
path: '/', path: "/",
Component: (props) => ComponentLoading(home, props), Component: (props) => ComponentLoading(home, props),
}, },
{ {
path: '*', path: "*",
Component: NoFound, Component: NoFound,
}, },
{ {
path: '/register', path: "/register",
Component: (props) => ComponentLoading(Register, props), Component: (props) => ComponentLoading(Register, props),
}, },
// { // {
// path: '/home', // path: '/home',
// Component: home, // Component: home,
// }, // },
{ {
path: '/login', path: "/login",
Component: (props) => ComponentLoading(Login, props), Component: (props) => ComponentLoading(Login, props),
}, },
{ {
path: '/main', path: "/main",
Component: (props) => ComponentLoading(Main, props), Component: (props) => ComponentLoading(Main, props),
}, },
] ];
export default routes export default routes;

View File

@@ -1,9 +1,11 @@
import { lazy } from 'react' /** @format */
import { lazy } from "react";
const home = lazy( const home = lazy(
() => () =>
new Promise((resolve: any) => { new Promise((resolve: any) => {
setTimeout(() => resolve(import('@/views/Home/')), 1000) setTimeout(() => resolve(import("@/views/Home/")), 1000);
}), }),
) );
export default home export default home;

View File

@@ -1,6 +1,8 @@
import { useUserStore } from './modules/user.ts' /** @format */
import { useUserStore } from "./modules/user.ts";
/** 将每个Store实例化 */ /** 将每个Store实例化 */
export const RootStore = { export const RootStore = {
user: new useUserStore(), user: new useUserStore(),
} };

View File

@@ -1,38 +1,45 @@
import { action, makeAutoObservable } from 'mobx' /** @format */
import { makePersistable, isHydrated } from 'mobx-persist-store'
import { handleLocalforage } from '@/utils/localforage' import { action, makeAutoObservable } from "mobx";
import { makePersistable, isHydrated } from "mobx-persist-store";
import { handleLocalforage } from "@/utils/localforage";
export class useUserStore { export class useUserStore {
token: any = '' token: any = "";
constructor() {
makeAutoObservable( constructor() {
this, makeAutoObservable(
{ this,
setToken: action, {
}, setToken: action,
{ autoBind: true }, },
) { autoBind: true },
// eslint-disable-next-line @typescript-eslint/ban-ts-comment );
// @ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-comment
makePersistable(this, { // @ts-ignore
// 在构造函数内使用 makePersistable makePersistable(this, {
name: 'token', // 保存的name用于在storage中的名称标识只要不和storage中其他名称重复就可以 // 在构造函数内使用 makePersistable
properties: ['token'], // 保存的字段这些字段会被保存在name对应的storage中注意不写在这里面的字段将不会被保存刷新页面也将丢失get字段例外。get数据会在数据返回后再自动计算 name: "token", // 保存的name用于在storage中的名称标识只要不和storage中其他名称重复就可以
storage: handleLocalforage, // 保存的位置看自己的业务情况选择可以是localStoragesessionstorage properties: ["token"], // 保存的字段这些字段会被保存在name对应的storage中注意不写在这里面的字段将不会被保存刷新页面也将丢失get字段例外。get数据会在数据返回后再自动计算
// 。。还有一些其他配置参数例如数据过期时间等等可以康康文档像storage这种字段可以配置在全局配置里详见文档 storage: handleLocalforage, // 保存的位置看自己的业务情况选择可以是localStoragesessionstorage
}).then( // 。。还有一些其他配置参数例如数据过期时间等等可以康康文档像storage这种字段可以配置在全局配置里详见文档
action(() => { }).then(
// persist 完成的回调在这里可以执行一些拿到数据后需要执行的操作如果在页面上要判断是否完成persist使用 isHydrated action(() => {
// console.log(persistStore) // persist 完成的回调在这里可以执行一些拿到数据后需要执行的操作如果在页面上要判断是否完成persist使用 isHydrated
}), // console.log(persistStore)
) }),
} );
get getToken() { }
return this.token ? this.token : null
} get getToken() {
get isHydrated() { return this.token ? this.token : null;
return isHydrated(this) }
}
setToken(token: string) { get isHydrated() {
this.token = token return isHydrated(this);
} }
setToken(token: string) {
this.token = token;
}
} }

View File

@@ -1,145 +1,101 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios' /** @format */
import { message } from 'antd'
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
import { message } from "antd";
class Request { class Request {
private instance: AxiosInstance | undefined private instance: AxiosInstance | undefined;
constructor(config: AxiosRequestConfig) { constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config) this.instance = axios.create(config);
// 全局请求拦截 // 全局请求拦截
this.instance.interceptors.request.use( this.instance.interceptors.request.use(
(config) => { (config) => {
return config return config;
}, },
(error) => { (error) => {
return Promise.reject(error) return Promise.reject(error);
}, },
) );
// 全局响应拦截 // 全局响应拦截
this.instance.interceptors.response.use( this.instance.interceptors.response.use(
(res) => { (res) => {
// if (res.data.code && res.data.code !== 200) { // if (res.data.code && res.data.code !== 200) {
// message.error(res.data.message).then() // message.error(res.data.message).then()
// return Promise.reject(res.data) // return Promise.reject(res.data)
// } // }
return res.data return res.data;
}, },
(error) => { (error) => {
const { response } = error const { response } = error;
if (response) { if (response) {
this.handleCode(response.status) this.handleCode(response.status);
} }
if (!window.navigator.onLine) { if (!window.navigator.onLine) {
message.error('网络连接失败') message.error("网络连接失败");
// return router.push({ // return router.push({
// path: '/404', // path: '/404',
// }) // })
return Promise.reject(error) return Promise.reject(error);
} }
}, },
) );
} }
handleCode(code: number): void {
switch (code) {
case 400:
message.error('请求错误(400)')
break
case 401:
message.error('未授权,请重新登录(401)')
break
case 403:
message.error('拒绝访问(403)')
break
case 404:
message.error('请求出错(404)')
break
case 408:
message.error('请求超时(408)')
break
case 500:
message.error('服务器错误(500)')
break
case 501:
message.error('服务未实现(501)')
break
case 502:
message.error('网络错误(502)')
break
case 503:
message.error('服务不可用(503)')
break
case 504:
message.error('网络超时(504)')
break
case 505:
message.error('HTTP版本不受支持(505)')
break
default:
message.error(`连接出错(${code})!`)
break
}
}
request<T>(config: AxiosRequestConfig<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
this.instance
?.request<any, T>(config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
get(url: string) { handleCode(code: number): void {
return new Promise((resolve, reject) => { switch (code) {
this.instance case 400:
?.get(url) message.error("请求错误(400)").then();
.then((res) => { break;
resolve(res) case 401:
}) message.error("未授权,请重新登录(401)").then();
.catch((err) => { break;
reject(err) case 403:
}) message.error("拒绝访问(403)").then();
}) break;
} case 404:
message.error("请求出错(404)").then();
break;
case 408:
message.error("请求超时(408)").then();
break;
case 500:
message.error("服务器错误(500)").then();
break;
case 501:
message.error("服务未实现(501)").then();
break;
case 502:
message.error("网络错误(502)").then();
break;
case 503:
message.error("服务不可用(503)").then();
break;
case 504:
message.error("网络超时(504)").then();
break;
case 505:
message.error("HTTP版本不受支持(505)").then();
break;
default:
message.error(`连接出错(${code})!`).then();
break;
}
}
post(url: string, data = {}) { request<T>(config: AxiosRequestConfig<T>): Promise<T> {
return new Promise((resolve, reject) => { return new Promise<T>((resolve, reject) => {
this.instance this.instance
?.post(url, data) ?.request<any, T>(config)
.then((res) => { .then((res) => {
resolve(res) resolve(res);
}) })
.catch((err) => { .catch((err) => {
reject(err) reject(err);
}) });
}) });
} }
put(url: string, data = {}) {
return new Promise((resolve, reject) => {
this.instance
?.put(url, data)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
delete(url: string) {
return new Promise((resolve, reject) => {
this.instance
?.delete(url)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
} }
export default Request
export default Request;

View File

@@ -1,14 +1,16 @@
import Request from './request' /** @format */
import { handleLocalforage } from '@/utils/localforage'
const token = String(handleLocalforage.getItem('token')) import Request from "./request";
import { handleLocalforage } from "@/utils/localforage";
const token = String(handleLocalforage.getItem("token"));
const web: Request = new Request({ const web: Request = new Request({
baseURL: import.meta.env.VITE_APP_BASE_API, baseURL: import.meta.env.VITE_APP_BASE_API,
headers: { headers: {
// 'Content-Type': 'application/json;charset=utf-8', // 'Content-Type': 'application/json;charset=utf-8',
// Accept: 'application/json', // Accept: 'application/json',
Authorization: token, Authorization: token,
}, },
}) });
export default web export default web;

View File

@@ -1,85 +1,91 @@
/** 配置 */ /**
* 配置
*
* @format
*/
interface Options { interface Options {
/** key前缀 */ /** key前缀 */
prefix?: string prefix?: string;
} }
/** 默认配置 */ /** 默认配置 */
const defaultOptions: Options = { const defaultOptions: Options = {
prefix: '', prefix: "",
} };
class CookieStorage { class CookieStorage {
private prefix: string private prefix: string;
constructor(options: Options = defaultOptions) { constructor(options: Options = defaultOptions) {
const { prefix } = options const { prefix } = options;
this.prefix = prefix ?? '' this.prefix = prefix ?? "";
} }
/** /**
* @description: 设置cookie * @description: 设置cookie
* @param {string} key 键 * @param {string} key 键
* @param {any} value 值 * @param {any} value 值
* @param {number} expires 过期时间s毫秒不传默认2年有效 * @param {number} expires 过期时间s毫秒不传默认2年有效
* @Date: 2023-05-17 18:10:34 * @Date: 2023-05-17 18:10:34
* @Author: mulingyuer * @Author: mulingyuer
*/ */
public setItem(key: string, value: string | number, expires?: number): void { public setItem(key: string, value: string | number, expires?: number): void {
const timestamp = Date.now() const timestamp = Date.now();
if (typeof expires === 'number') { if (typeof expires === "number") {
expires = timestamp + expires expires = timestamp + expires;
} else { } else {
expires = timestamp + 2 * 365 * 24 * 60 * 60 * 1000 expires = timestamp + 2 * 365 * 24 * 60 * 60 * 1000;
} }
document.cookie = `${this.prefix}${key}=${value};expires=${new Date(expires).toUTCString()}` document.cookie = `${this.prefix}${key}=${value};expires=${new Date(expires).toUTCString()}`;
} }
/** /**
* @description: 获取cookie * @description: 获取cookie
* @param {string} key 键 * @param {string} key 键
* @Date: 2023-05-17 18:31:50 * @Date: 2023-05-17 18:31:50
* @Author: mulingyuer * @Author: mulingyuer
*/ */
public getItem(key: string): string | undefined { public getItem(key: string): string | undefined {
const cookies = document.cookie.split('; ') const cookies = document.cookie.split("; ");
let val: string | undefined = undefined let val: string | undefined = undefined;
cookies.find((item) => { cookies.find((item) => {
const [k, v] = item.split('=') const [k, v] = item.split("=");
if (k === `${this.prefix}${key}`) { if (k === `${this.prefix}${key}`) {
val = v val = v;
return true return true;
} }
return false return false;
}) });
return val return val;
} }
/** /**
* @description: 删除指定key的cookie * @description: 删除指定key的cookie
* @param {string} key 键 * @param {string} key 键
* @Date: 2023-05-17 18:32:56 * @Date: 2023-05-17 18:32:56
* @Author: mulingyuer * @Author: mulingyuer
*/ */
public removeItem(key: string): void { public removeItem(key: string): void {
this.setItem(key, '', -1) this.setItem(key, "", -1);
} }
/** /**
* @description: 清空所有cookie * @description: 清空所有cookie
* @Date: 2023-05-17 23:13:04 * @Date: 2023-05-17 23:13:04
* @Author: mulingyuer * @Author: mulingyuer
*/ */
public clear(): void { public clear(): void {
const cookies = document.cookie.split('; ') const cookies = document.cookie.split("; ");
cookies.forEach((item) => { cookies.forEach((item) => {
const [k] = item.split('=') const [k] = item.split("=");
this.removeItem(k) this.removeItem(k);
}) });
} }
} }
const cookieStorage = new CookieStorage() const cookieStorage = new CookieStorage();
export default cookieStorage export default cookieStorage;
export { CookieStorage } export { CookieStorage };

View File

@@ -1,169 +1,173 @@
import JSEncrypt from 'jsencrypt' /** @format */
import CryptoJS from 'crypto-js'
import JSEncrypt from "jsencrypt";
import CryptoJS from "crypto-js";
// 加密 // 加密
export function rsaEncrypt(Str: string, afterPublicKey: string) { export function rsaEncrypt(Str: string, afterPublicKey: string) {
const encryptor = new JSEncrypt() const encryptor = new JSEncrypt();
encryptor.setPublicKey(afterPublicKey) // 设置公钥 encryptor.setPublicKey(afterPublicKey); // 设置公钥
return encryptor.encrypt(Str) // 对数据进行加密 return encryptor.encrypt(Str); // 对数据进行加密
} }
// 解密 // 解密
export function rsaDecrypt(Str: string, frontPrivateKey: string) { export function rsaDecrypt(Str: string, frontPrivateKey: string) {
const encryptor = new JSEncrypt() const encryptor = new JSEncrypt();
encryptor.setPrivateKey(frontPrivateKey) // 设置私钥 encryptor.setPrivateKey(frontPrivateKey); // 设置私钥
return encryptor.decrypt(Str) // 对数据进行解密 return encryptor.decrypt(Str); // 对数据进行解密
} }
export function aesEncrypt(aeskey: string, Str: string) { export function aesEncrypt(aeskey: string, Str: string) {
// 设置一个默认值,如果第二个参数为空采用默认值,不为空则采用新设置的密钥 // 设置一个默认值,如果第二个参数为空采用默认值,不为空则采用新设置的密钥
const key = CryptoJS.enc.Utf8.parse(aeskey) const key = CryptoJS.enc.Utf8.parse(aeskey);
const srcs = CryptoJS.enc.Utf8.parse(Str) const srcs = CryptoJS.enc.Utf8.parse(Str);
const encrypted = CryptoJS.AES.encrypt(srcs, key, { const encrypted = CryptoJS.AES.encrypt(srcs, key, {
// 切记 需要和后端算法模式一致 // 切记 需要和后端算法模式一致
mode: CryptoJS.mode.ECB, mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7, padding: CryptoJS.pad.Pkcs7,
}) });
return encrypted.toString() return encrypted.toString();
} }
export function aesDecrypt(aeskey: string, Str: string) { export function aesDecrypt(aeskey: string, Str: string) {
const key = CryptoJS.enc.Utf8.parse(aeskey) const key = CryptoJS.enc.Utf8.parse(aeskey);
const decrypt = CryptoJS.AES.decrypt(Str, key, { const decrypt = CryptoJS.AES.decrypt(Str, key, {
// 切记 需要和后端算法模式一致 // 切记 需要和后端算法模式一致
mode: CryptoJS.mode.ECB, mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7, padding: CryptoJS.pad.Pkcs7,
}) });
return CryptoJS.enc.Utf8.stringify(decrypt).toString() return CryptoJS.enc.Utf8.stringify(decrypt).toString();
} }
/** /**
* 获取16位随机码AES * 获取16位随机码AES
* @returns {string} * @returns {string}
*/ */
export function get16RandomNum() { export function get16RandomNum() {
const chars = [ const chars = [
'0', "0",
'1', "1",
'2', "2",
'3', "3",
'4', "4",
'5', "5",
'6', "6",
'7', "7",
'8', "8",
'9', "9",
'A', "A",
'B', "B",
'C', "C",
'D', "D",
'E', "E",
'F', "F",
'G', "G",
'H', "H",
'I', "I",
'J', "J",
'K', "K",
'L', "L",
'M', "M",
'N', "N",
'O', "O",
'P', "P",
'Q', "Q",
'R', "R",
'S', "S",
'T', "T",
'U', "U",
'V', "V",
'W', "W",
'X', "X",
'Y', "Y",
'Z', "Z",
'a', "a",
'b', "b",
'c', "c",
'd', "d",
'e', "e",
'f', "f",
'g', "g",
'h', "h",
'i', "i",
'j', "j",
'k', "k",
'l', "l",
'm', "m",
'n', "n",
'o', "o",
'p', "p",
'q', "q",
'r', "r",
's', "s",
't', "t",
'u', "u",
'v', "v",
'w', "w",
'x', "x",
'y', "y",
'z', "z",
] ];
let nums = '' let nums = "";
//这个地方切记要选择16位因为美国对密钥长度有限制选择32位的话加解密会报错需要根据jdk版本去修改相关jar包有点恼火选择16位就不用处理。 //这个地方切记要选择16位因为美国对密钥长度有限制选择32位的话加解密会报错需要根据jdk版本去修改相关jar包有点恼火选择16位就不用处理。
for (let i = 0; i < 16; i++) { for (let i = 0; i < 16; i++) {
const id = parseInt(String(Math.random() * 61)) const id = parseInt(String(Math.random() * 61));
nums += chars[id] nums += chars[id];
} }
return nums return nums;
} }
//获取rsa密钥对 //获取rsa密钥对
export function getRsaKeys() { export function getRsaKeys() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
window.crypto.subtle window.crypto.subtle
.generateKey( .generateKey(
{ {
name: 'RSA-OAEP', name: "RSA-OAEP",
modulusLength: 2048, //can be 1024, 2048, or 4096 modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: { name: 'SHA-512' }, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512" hash: { name: "SHA-512" }, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
}, },
true, //whether the key is extractable (i.e. can be used in exportKey) true, //whether the key is extractable (i.e. can be used in exportKey)
['encrypt', 'decrypt'], //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"] ["encrypt", "decrypt"], //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
) )
.then(function (key) { .then(function (key) {
window.crypto.subtle window.crypto.subtle
.exportKey('pkcs8', key.privateKey) .exportKey("pkcs8", key.privateKey)
.then(function (keydata1) { .then(function (keydata1) {
window.crypto.subtle window.crypto.subtle
.exportKey('spki', key.publicKey) .exportKey("spki", key.publicKey)
.then(function (keydata2) { .then(function (keydata2) {
const privateKey = RSA2text(keydata1, 1) const privateKey = RSA2text(keydata1, 1);
const publicKey = RSA2text(keydata2) const publicKey = RSA2text(keydata2);
resolve({ privateKey, publicKey }) resolve({ privateKey, publicKey });
}) })
.catch(function (err) { .catch(function (err) {
reject(err) reject(err);
}) });
}) })
.catch(function (err) { .catch(function (err) {
reject(err) reject(err);
}) });
}) })
.catch(function (err) { .catch(function (err) {
reject(err) reject(err);
}) });
}) });
} }
function RSA2text(buffer: any, _isPrivate: number = 0) { function RSA2text(buffer: any, _isPrivate: number = 0) {
let binary = '' let binary = "";
const bytes = new Uint8Array(buffer) const bytes = new Uint8Array(buffer);
const len = bytes.byteLength const len = bytes.byteLength;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]) binary += String.fromCharCode(bytes[i]);
} }
const base64 = window.btoa(binary) const base64 = window.btoa(binary);
const text = base64.replace(/[^\x00-\xff]/g, '$&\x01').replace(/.{64}\x01?/g, '$&\n') const text = base64.replace(/[^\x00-\xff]/g, "$&\x01").replace(/.{64}\x01?/g, "$&\n");
return text return text;
} }

View File

@@ -1,102 +1,104 @@
import { encrypt, decrypt } from './encry' /** @format */
import { globalConfig } from './interface'
import { encrypt, decrypt } from "./encry";
import { globalConfig } from "./interface";
const config: globalConfig = { const config: globalConfig = {
type: 'localStorage', //存储类型localStorage | sessionStorage type: "localStorage", //存储类型localStorage | sessionStorage
prefix: 'schisandra_', //版本号 prefix: "schisandra_", //版本号
expire: 24 * 60, //过期时间,默认为一天,单位为分钟 expire: 24 * 60, //过期时间,默认为一天,单位为分钟
isEncrypt: true, //支持加密、解密数据处理 isEncrypt: true, //支持加密、解密数据处理
} };
const setStorage = (key: string, value: any, expire: number = 24 * 60): boolean => { const setStorage = (key: string, value: any, expire: number = 24 * 60): boolean => {
//设定值 //设定值
if (value === '' || value === null || value === undefined) { if (value === "" || value === null || value === undefined) {
//空值重置 //空值重置
value = null value = null;
} }
if (isNaN(expire) || expire < 0) { if (isNaN(expire) || expire < 0) {
//过期时间值合理性判断 //过期时间值合理性判断
throw new Error('Expire must be a number') throw new Error("Expire must be a number");
} }
const data = { const data = {
value, //存储值 value, //存储值
time: Date.now(), //存储日期 time: Date.now(), //存储日期
expire: Date.now() + 1000 * 60 * expire, //过期时间 expire: Date.now() + 1000 * 60 * expire, //过期时间
} };
//是否需要加密,判断装载加密数据或原数据 //是否需要加密,判断装载加密数据或原数据
window[config.type].setItem( window[config.type].setItem(
autoAddPreFix(key), autoAddPreFix(key),
config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data), config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data),
) );
return true return true;
} };
const getStorageFromKey = (key: string) => { const getStorageFromKey = (key: string) => {
//获取指定值 //获取指定值
if (config.prefix) { if (config.prefix) {
key = autoAddPreFix(key) key = autoAddPreFix(key);
} }
if (!window[config.type].getItem(key)) { if (!window[config.type].getItem(key)) {
//不存在判断 //不存在判断
return null return null;
} }
const storageVal = config.isEncrypt const storageVal = config.isEncrypt
? JSON.parse(decrypt(window[config.type].getItem(key) as string)) ? JSON.parse(decrypt(window[config.type].getItem(key) as string))
: JSON.parse(window[config.type].getItem(key) as string) : JSON.parse(window[config.type].getItem(key) as string);
const now = Date.now() const now = Date.now();
if (now >= storageVal.expire) { if (now >= storageVal.expire) {
//过期销毁 //过期销毁
removeStorageFromKey(key) removeStorageFromKey(key);
return null return null;
//不过期回值 //不过期回值
} else { } else {
return storageVal.value return storageVal.value;
} }
} };
const getAllStorage = () => { const getAllStorage = () => {
//获取所有值 //获取所有值
const storageList: any = {} const storageList: any = {};
const keys = Object.keys(window[config.type]) const keys = Object.keys(window[config.type]);
keys.forEach((key) => { keys.forEach((key) => {
const value = getStorageFromKey(autoRemovePreFix(key)) const value = getStorageFromKey(autoRemovePreFix(key));
if (value !== null) { if (value !== null) {
//如果值没有过期,加入到列表中 //如果值没有过期,加入到列表中
storageList[autoRemovePreFix(key)] = value storageList[autoRemovePreFix(key)] = value;
} }
}) });
return storageList return storageList;
} };
const getStorageLength = () => { const getStorageLength = () => {
//获取值列表长度 //获取值列表长度
return window[config.type].length return window[config.type].length;
} };
const removeStorageFromKey = (key: string) => { const removeStorageFromKey = (key: string) => {
//删除值 //删除值
if (config.prefix) { if (config.prefix) {
key = autoAddPreFix(key) key = autoAddPreFix(key);
} }
window[config.type].removeItem(key) window[config.type].removeItem(key);
} };
const clearStorage = () => { const clearStorage = () => {
window[config.type].clear() window[config.type].clear();
} };
const autoAddPreFix = (key: string) => { const autoAddPreFix = (key: string) => {
//添加前缀,保持唯一性 //添加前缀,保持唯一性
const prefix = config.prefix || '' const prefix = config.prefix || "";
return `${prefix}_${key}` return `${prefix}_${key}`;
} };
const autoRemovePreFix = (key: string) => { const autoRemovePreFix = (key: string) => {
//删除前缀,进行增删改查 //删除前缀,进行增删改查
const lineIndex = config.prefix.length + 1 const lineIndex = config.prefix.length + 1;
return key.substr(lineIndex) return key.substr(lineIndex);
} };
export { export {
setStorage, setStorage,
getStorageFromKey, getStorageFromKey,
getAllStorage, getAllStorage,
getStorageLength, getStorageLength,
removeStorageFromKey, removeStorageFromKey,
clearStorage, clearStorage,
} };

View File

@@ -1,37 +1,39 @@
import CryptoJS from 'crypto-js' /** @format */
const SECRET_KEY = CryptoJS.enc.Utf8.parse('3333e6e143439161') //十六位十六进制数作为密钥 import CryptoJS from "crypto-js";
const SECRET_IV = CryptoJS.enc.Utf8.parse('e3bbe7e3ba84431a') //十六位十六进制数作为密钥偏移量
const SECRET_KEY = CryptoJS.enc.Utf8.parse("3333e6e143439161"); //十六位十六进制数作为密钥
const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a"); //十六位十六进制数作为密钥偏移量
const encrypt = (data: object | string): string => { const encrypt = (data: object | string): string => {
//加密 //加密
if (typeof data === 'object') { if (typeof data === "object") {
try { try {
data = JSON.stringify(data) data = JSON.stringify(data);
} catch (e) { } catch (e) {
throw new Error('encrypt error' + e) throw new Error("encrypt error" + e);
} }
} }
const dataHex = CryptoJS.enc.Utf8.parse(data) const dataHex = CryptoJS.enc.Utf8.parse(data);
const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, { const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
iv: SECRET_IV, iv: SECRET_IV,
mode: CryptoJS.mode.CBC, mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7, padding: CryptoJS.pad.Pkcs7,
}) });
return encrypted.ciphertext.toString() return encrypted.ciphertext.toString();
} };
const decrypt = (data: string) => { const decrypt = (data: string) => {
//解密 //解密
const encryptedHexStr = CryptoJS.enc.Hex.parse(data) const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
const str = CryptoJS.enc.Base64.stringify(encryptedHexStr) const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, { const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
iv: SECRET_IV, iv: SECRET_IV,
mode: CryptoJS.mode.CBC, mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7, padding: CryptoJS.pad.Pkcs7,
}) });
const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8) const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString() return decryptedStr.toString();
} };
export { encrypt, decrypt } export { encrypt, decrypt };

View File

@@ -1,8 +1,10 @@
/** @format */
interface globalConfig { interface globalConfig {
type: 'localStorage' | 'sessionStorage' type: "localStorage" | "sessionStorage";
prefix: string prefix: string;
expire: number expire: number;
isEncrypt: boolean isEncrypt: boolean;
} }
export type { globalConfig } export type { globalConfig };

View File

@@ -1,74 +1,76 @@
import localforage from 'localforage' /** @format */
import CryptoJS from 'crypto-js'
const SECRET_KEY = CryptoJS.enc.Utf8.parse('3333e6e143439161') //十六位十六进制数作为密钥 import localforage from "localforage";
const SECRET_IV = CryptoJS.enc.Utf8.parse('e3bbe7e3ba84431a') //十六位十六进制数作为密钥偏移量 import CryptoJS from "crypto-js";
const SECRET_KEY = CryptoJS.enc.Utf8.parse("3333e6e143439161"); //十六位十六进制数作为密钥
const SECRET_IV = CryptoJS.enc.Utf8.parse("e3bbe7e3ba84431a"); //十六位十六进制数作为密钥偏移量
/** /**
* 加密 * 加密
* @param data * @param data
* @param output * @param output
*/ */
export const encrypt = (data: string, output?: any) => { export const encrypt = (data: string, output?: any) => {
const dataHex = CryptoJS.enc.Utf8.parse(data) const dataHex = CryptoJS.enc.Utf8.parse(data);
const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, { const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
iv: SECRET_IV, iv: SECRET_IV,
mode: CryptoJS.mode.CBC, mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7, padding: CryptoJS.pad.Pkcs7,
}) });
return encrypted.ciphertext.toString(output) return encrypted.ciphertext.toString(output);
} };
/** /**
* 解密 * 解密
* @param data * @param data
*/ */
export const decrypt = (data: string | null) => { export const decrypt = (data: string | null) => {
if (data === null) { if (data === null) {
return return;
} }
const encryptedHex = CryptoJS.enc.Hex.parse(data) const encryptedHex = CryptoJS.enc.Hex.parse(data);
const encryptedHexStr = CryptoJS.enc.Base64.stringify(encryptedHex) const encryptedHexStr = CryptoJS.enc.Base64.stringify(encryptedHex);
const decrypted = CryptoJS.AES.decrypt(encryptedHexStr, SECRET_KEY, { const decrypted = CryptoJS.AES.decrypt(encryptedHexStr, SECRET_KEY, {
iv: SECRET_IV, iv: SECRET_IV,
mode: CryptoJS.mode.CBC, mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7, padding: CryptoJS.pad.Pkcs7,
}) });
const decryptedStr = decrypted.toString(CryptoJS.enc.Utf8) const decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString() return decryptedStr.toString();
} };
export const handleLocalforage = { export const handleLocalforage = {
config: async (options?: LocalForageOptions) => localforage.config(options || {}), config: async (options?: LocalForageOptions) => localforage.config(options || {}),
setItem: async (key: string, value: string): Promise<void> => { setItem: async (key: string, value: string): Promise<void> => {
await localforage.setItem(key, encrypt(value)) await localforage.setItem(key, encrypt(value));
}, },
getItem: async function getItem<T>(key: string): Promise<T | string | null> { getItem: async function getItem<T>(key: string): Promise<T | string | null> {
try { try {
const value: any = decrypt(await localforage.getItem(key)) as any const value: any = decrypt(await localforage.getItem(key)) as any;
// 如果值是 undefined返回 null // 如果值是 undefined返回 null
if (value === undefined) { if (value === undefined) {
return null return null;
} }
// 如果值是 T 类型,直接返回 // 如果值是 T 类型,直接返回
if (typeof value === 'object' && value !== null) { if (typeof value === "object" && value !== null) {
return value as T return value as T;
} }
// 如果值是 string 类型,直接返回 // 如果值是 string 类型,直接返回
return value as string return value as string;
} catch (error) { } catch (error) {
console.error('Error retrieving data from localforage:', error) console.error("Error retrieving data from localforage:", error);
return null return null;
} }
}, },
removeItem: async (key: string): Promise<void> => { removeItem: async (key: string): Promise<void> => {
await localforage.removeItem(key) await localforage.removeItem(key);
}, },
clear: async () => { clear: async () => {
return await localforage.clear() return await localforage.clear();
}, },
createInstance: async (name: string) => { createInstance: async (name: string) => {
return localforage.createInstance({ return localforage.createInstance({
name, name,
}) });
}, },
} };

View File

@@ -1,11 +1,13 @@
import { MobXProviderContext } from 'mobx-react' /** @format */
import { useContext } from 'react'
import { RootStore } from '@/store' import { MobXProviderContext } from "mobx-react";
import { useContext } from "react";
import { RootStore } from "@/store";
// 根据RootStore来实现参数的自动获取和返回值的自动推导 // 根据RootStore来实现参数的自动获取和返回值的自动推导
function useStore<T extends typeof RootStore, V extends keyof T>(name: V): T[V] { function useStore<T extends typeof RootStore, V extends keyof T>(name: V): T[V] {
const store = useContext(MobXProviderContext) as T const store = useContext(MobXProviderContext) as T;
return store[name] return store[name];
} }
export default useStore export default useStore;

View File

@@ -1,168 +1,171 @@
import { useNavigate } from 'react-router-dom' /** @format */
import './index.less'
import {useEffect} from "react"; import { useNavigate } from "react-router-dom";
// import "./index.less";
import { useEffect } from "react";
export default () => { export default () => {
const navigate = useNavigate() const navigate = useNavigate();
const goBack = () => { const goBack = () => {
navigate(-1) navigate(-1);
} };
useEffect(() => { useEffect(() => {
document.body.classList.add('not-fount-body') document.body.classList.add("not-fount-body");
return ()=>{ return () => {
document.body.classList.remove('not-fount-body') document.body.classList.remove("not-fount-body");
} };
}, []); }, []);
return ( return (
<> <>
<body translate='no'> <body translate="no">
<div className='container container-star'> <div className="container container-star">
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-1'></div> <div className="star-1"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
<div className='star-2'></div> <div className="star-2"></div>
</div> </div>
<div className='container container-bird'> <div className="container container-bird">
<div className='bird bird-anim'> <div className="bird bird-anim">
<div className='bird-container'> <div className="bird-container">
<div className='wing wing-left'> <div className="wing wing-left">
<div className='wing-left-top'></div> <div className="wing-left-top"></div>
</div> </div>
<div className='wing wing-right'> <div className="wing wing-right">
<div className='wing-right-top'></div> <div className="wing-right-top"></div>
</div> </div>
</div> </div>
</div> </div>
<div className='bird bird-anim'> <div className="bird bird-anim">
<div className='bird-container'> <div className="bird-container">
<div className='wing wing-left'> <div className="wing wing-left">
<div className='wing-left-top'></div> <div className="wing-left-top"></div>
</div> </div>
<div className='wing wing-right'> <div className="wing wing-right">
<div className='wing-right-top'></div> <div className="wing-right-top"></div>
</div> </div>
</div> </div>
</div> </div>
<div className='bird bird-anim'> <div className="bird bird-anim">
<div className='bird-container'> <div className="bird-container">
<div className='wing wing-left'> <div className="wing wing-left">
<div className='wing-left-top'></div> <div className="wing-left-top"></div>
</div> </div>
<div className='wing wing-right'> <div className="wing wing-right">
<div className='wing-right-top'></div> <div className="wing-right-top"></div>
</div> </div>
</div> </div>
</div> </div>
<div className='bird bird-anim'> <div className="bird bird-anim">
<div className='bird-container'> <div className="bird-container">
<div className='wing wing-left'> <div className="wing wing-left">
<div className='wing-left-top'></div> <div className="wing-left-top"></div>
</div> </div>
<div className='wing wing-right'> <div className="wing wing-right">
<div className='wing-right-top'></div> <div className="wing-right-top"></div>
</div> </div>
</div> </div>
</div> </div>
<div className='bird bird-anim'> <div className="bird bird-anim">
<div className='bird-container'> <div className="bird-container">
<div className='wing wing-left'> <div className="wing wing-left">
<div className='wing-left-top'></div> <div className="wing-left-top"></div>
</div> </div>
<div className='wing wing-right'> <div className="wing wing-right">
<div className='wing-right-top'></div> <div className="wing-right-top"></div>
</div> </div>
</div> </div>
</div> </div>
<div className='bird bird-anim'> <div className="bird bird-anim">
<div className='bird-container'> <div className="bird-container">
<div className='wing wing-left'> <div className="wing wing-left">
<div className='wing-left-top'></div> <div className="wing-left-top"></div>
</div> </div>
<div className='wing wing-right'> <div className="wing wing-right">
<div className='wing-right-top'></div> <div className="wing-right-top"></div>
</div> </div>
</div> </div>
</div> </div>
<div className='container-title'> <div className="container-title">
<div className='title'> <div className="title">
<div className='number'>4</div> <div className="number">4</div>
<div className='moon'> <div className="moon">
<div className='face'> <div className="face">
<div className='mouth'></div> <div className="mouth"></div>
<div className='eyes'> <div className="eyes">
<div className='eye-left'></div> <div className="eye-left"></div>
<div className='eye-right'></div> <div className="eye-right"></div>
</div> </div>
</div> </div>
</div> </div>
<div className='number'>4</div> <div className="number">4</div>
</div> </div>
<div className='subtitle'>Oops. Looks like you took a wrong turn.</div> <div className="subtitle">Oops. Looks like you took a wrong turn.</div>
<button <button
style={{ style={{
marginTop: '1.5em', marginTop: "1.5em",
}} }}
onClick={goBack}> onClick={goBack}>
Go back Go back
</button> </button>
</div> </div>
</div> </div>
</body> </body>
</> </>
) );
} };

View File

@@ -1,30 +1,13 @@
import HomeIndex from '@/components/HomeIndex' /** @format */
import { useEffect } from 'react' import { useEffect } from "react";
import { getBaseInfo, initMinio } from '@/api/oss/minio'
import { Button } from 'antd' import HomeIndex from "@/components/HomeIndex";
export default () => { export default () => {
const minioInit = () => { useEffect(() => {}, []);
initMinio('1').then(() => { return (
getBaseInfo('wallhaven-1pd98v.jpg').then((res) => { <div>
console.log(res) <HomeIndex />
}) </div>
}) );
} };
const init = () => {
initMinio('2').then(() => {
getBaseInfo('background.png').then((res) => {
console.log(res)
})
})
}
useEffect(() => {}, [])
return (
<div>
<HomeIndex />
<Button onClick={minioInit}></Button>
<Button onClick={init}></Button>
</div>
)
}

View File

@@ -1,8 +1,11 @@
import DefaultLayOut from '@/layout/default' /** @format */
import DefaultLayOut from "@/layout/default";
export default () => { export default () => {
return ( return (
<> <>
<DefaultLayOut /> <DefaultLayOut />
</> </>
) );
} };

View File

@@ -1,376 +1,378 @@
/** @format */
import { import {
GithubOutlined, GithubOutlined,
GitlabOutlined, GitlabOutlined,
LockOutlined, LockOutlined,
MobileOutlined, MobileOutlined,
QqOutlined, QqOutlined,
UserOutlined, UserOutlined,
WechatOutlined, WechatOutlined,
} from '@ant-design/icons' } from "@ant-design/icons";
import { ProFormCaptcha, ProFormCheckbox, ProFormText } from '@ant-design/pro-components' import { ProFormCaptcha, ProFormCheckbox, ProFormText } from "@ant-design/pro-components";
import { Divider, Space, Tabs, message, Image, Alert, Form, Button } from 'antd' import { Divider, Space, Tabs, message, Image, Alert, Form, Button } from "antd";
import { CSSProperties, useRef } from 'react' import { CSSProperties, useRef } from "react";
import { useState } from 'react' import { useState } from "react";
import logo from '@/assets/icons/schisandra.svg' import logo from "@/assets/icons/schisandra.svg";
import qrCode from '@/assets/images/login_qrcode-landaiqing.jpg' import qrCode from "@/assets/images/login_qrcode-landaiqing.jpg";
import styles from './index.module.less' import styles from "./index.module.less";
import { observer } from 'mobx-react' import { observer } from "mobx-react";
import FooterComponent from '@/components/Footer' import FooterComponent from "@/components/Footer";
import RotateCaptcha, { CaptchaInstance } from 'react-rotate-captcha' import RotateCaptcha, { CaptchaInstance } from "react-rotate-captcha";
import { get, load, verify } from '@/api/captcha/index.ts' import { get, load, verify } from "@/api/captcha/index.ts";
// import useStore from '@/utils/store/useStore.tsx' // import useStore from '@/utils/store/useStore.tsx'
type LoginType = 'account' | 'phone' type LoginType = "account" | "phone";
const iconStyles: CSSProperties = { const iconStyles: CSSProperties = {
color: 'rgba(0, 0, 0, 0.2)', color: "rgba(0, 0, 0, 0.2)",
fontSize: '18px', fontSize: "18px",
verticalAlign: 'middle', verticalAlign: "middle",
cursor: 'pointer', cursor: "pointer",
} };
export default observer(() => { export default observer(() => {
const [form] = Form.useForm() const [form] = Form.useForm();
const captcha = useRef<CaptchaInstance>(null) const captcha = useRef<CaptchaInstance>(null);
const items = [ const items = [
{ {
label: ( label: (
<span> <span>
<UserOutlined /> <UserOutlined />
</span> </span>
), ),
key: 'account', key: "account",
}, },
{ {
label: ( label: (
<span> <span>
<MobileOutlined /> <MobileOutlined />
</span> </span>
), ),
key: 'phone', key: "phone",
}, },
] ];
const [loginType, setLoginType] = useState<LoginType>('account') const [loginType, setLoginType] = useState<LoginType>("account");
const onSubmit = async (formData: object) => { const onSubmit = async (formData: object) => {
console.log(formData) console.log(formData);
} };
return ( return (
<RotateCaptcha get={get} load={load} verify={verify} limit={2} ref={captcha}> <RotateCaptcha get={get} load={load} verify={verify} limit={2} ref={captcha}>
<div className={styles.container}> <div className={styles.container}>
<div className={styles.content}> <div className={styles.content}>
<Space className={styles.content_content}> <Space className={styles.content_content}>
<Space className={styles.login_content}> <Space className={styles.login_content}>
<Space align='center' className={styles.mp_code}> <Space align="center" className={styles.mp_code}>
<Space direction='vertical' align='center'> <Space direction="vertical" align="center">
<span className={styles.mp_code_title}></span> <span className={styles.mp_code_title}></span>
<Image <Image
preview={false} preview={false}
height={210} height={210}
width={200} width={200}
className={styles.mp_code_img} className={styles.mp_code_img}
// src={generateMpRegCodeData.data?.qrCodeUrl} // src={generateMpRegCodeData.data?.qrCodeUrl}
src={qrCode} src={qrCode}
/> />
<Alert <Alert
// message={(<span>微信扫码<span>关注公众号</span></span>)} // message={(<span>微信扫码<span>关注公众号</span></span>)}
description={ description={
<div> <div>
<span> <span>
<span className={styles.mp_tips}> <span className={styles.mp_tips}>
</span> </span>
</span> </span>
<br /> <br />
</div> </div>
} }
// type="success" // type="success"
showIcon={true} showIcon={true}
className={styles.alert} className={styles.alert}
icon={<WechatOutlined />} icon={<WechatOutlined />}
/> />
</Space> </Space>
</Space> </Space>
<Form <Form
form={form} form={form}
className={styles.login_form} className={styles.login_form}
initialValues={{ initialValues={{
autoLogin: true, autoLogin: true,
}}> }}>
<Space direction='vertical' align='center'> <Space direction="vertical" align="center">
<Space className={styles.logo}> <Space className={styles.logo}>
<img <img
alt='logo' alt="logo"
src={logo} src={logo}
style={{ width: '44px', height: '44px' }} style={{ width: "44px", height: "44px" }}
/> />
<span></span> <span></span>
</Space> </Space>
<div className={styles.subTitle}></div> <div className={styles.subTitle}></div>
</Space> </Space>
<Tabs <Tabs
centered={true} centered={true}
items={items} items={items}
activeKey={loginType} activeKey={loginType}
onChange={(activeKey) => onChange={(activeKey) =>
setLoginType(activeKey as LoginType) setLoginType(activeKey as LoginType)
}></Tabs> }></Tabs>
{loginType === 'account' && ( {loginType === "account" && (
<> <>
<ProFormText <ProFormText
name='username' name="username"
fieldProps={{ fieldProps={{
size: 'large', size: "large",
prefix: <UserOutlined className={'prefixIcon'} />, prefix: <UserOutlined className={"prefixIcon"} />,
}} }}
placeholder={'请输入账号/邮箱/电话号码'} placeholder={"请输入账号/邮箱/电话号码"}
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入用户名!', message: "请输入用户名!",
}, },
]} ]}
/> />
<ProFormText.Password <ProFormText.Password
name='password' name="password"
fieldProps={{ fieldProps={{
size: 'large', size: "large",
prefix: <LockOutlined className={'prefixIcon'} />, prefix: <LockOutlined className={"prefixIcon"} />,
}} }}
placeholder={'请输入密码'} placeholder={"请输入密码"}
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入密码!', message: "请输入密码!",
}, },
{ {
pattern: pattern:
/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{6,18}$/, /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{6,18}$/,
message: message:
'密码长度需在6~18位字符且必须包含字母和数字', "密码长度需在6~18位字符且必须包含字母和数字",
}, },
]} ]}
/> />
{/*<ProFormText*/} {/*<ProFormText*/}
{/* addonAfter={CodeImg}*/} {/* addonAfter={CodeImg}*/}
{/* name='code'*/} {/* name='code'*/}
{/* fieldProps={{*/} {/* fieldProps={{*/}
{/* size: 'large',*/} {/* size: 'large',*/}
{/* prefix: <BarcodeOutlined className={'prefixIcon'} />,*/} {/* prefix: <BarcodeOutlined className={'prefixIcon'} />,*/}
{/* autoComplete: 'off',*/} {/* autoComplete: 'off',*/}
{/* }}*/} {/* }}*/}
{/* placeholder='请输入图形验证码'*/} {/* placeholder='请输入图形验证码'*/}
{/* rules={[*/} {/* rules={[*/}
{/* {*/} {/* {*/}
{/* required: true,*/} {/* required: true,*/}
{/* message: '请输入图形验证码!',*/} {/* message: '请输入图形验证码!',*/}
{/* },*/} {/* },*/}
{/* {*/} {/* {*/}
{/* pattern: /^[a-zA-Z0-9]{5}$/,*/} {/* pattern: /^[a-zA-Z0-9]{5}$/,*/}
{/* message: '图形验证码格式不正确',*/} {/* message: '图形验证码格式不正确',*/}
{/* },*/} {/* },*/}
{/* ]}*/} {/* ]}*/}
{/*/>*/} {/*/>*/}
</> </>
)} )}
{loginType === 'phone' && ( {loginType === "phone" && (
<> <>
<ProFormText <ProFormText
fieldProps={{ fieldProps={{
size: 'large', size: "large",
prefix: <MobileOutlined className={'prefixIcon'} />, prefix: <MobileOutlined className={"prefixIcon"} />,
autoComplete: 'off', autoComplete: "off",
}} }}
name='mobile' name="mobile"
placeholder={'请输入手机号'} placeholder={"请输入手机号"}
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入手机号!', message: "请输入手机号!",
}, },
{ {
pattern: /^1\d{10}$/, pattern: /^1\d{10}$/,
message: '手机号格式错误!', message: "手机号格式错误!",
}, },
]} ]}
/> />
{/*<ProFormText*/} {/*<ProFormText*/}
{/* addonAfter={CodeImg}*/} {/* addonAfter={CodeImg}*/}
{/* name='code'*/} {/* name='code'*/}
{/* fieldProps={{*/} {/* fieldProps={{*/}
{/* size: 'large',*/} {/* size: 'large',*/}
{/* prefix: <BarcodeOutlined className={'prefixIcon'} />,*/} {/* prefix: <BarcodeOutlined className={'prefixIcon'} />,*/}
{/* autoComplete: 'off',*/} {/* autoComplete: 'off',*/}
{/* }}*/} {/* }}*/}
{/* placeholder='请输入图形验证码'*/} {/* placeholder='请输入图形验证码'*/}
{/* rules={[*/} {/* rules={[*/}
{/* {*/} {/* {*/}
{/* required: true,*/} {/* required: true,*/}
{/* message: '请输入图形验证码!',*/} {/* message: '请输入图形验证码!',*/}
{/* },*/} {/* },*/}
{/* {*/} {/* {*/}
{/* pattern: /^[a-zA-Z0-9]{5}$/,*/} {/* pattern: /^[a-zA-Z0-9]{5}$/,*/}
{/* message: '图形验证码格式不正确',*/} {/* message: '图形验证码格式不正确',*/}
{/* },*/} {/* },*/}
{/* ]}*/} {/* ]}*/}
{/*/>*/} {/*/>*/}
<ProFormCaptcha <ProFormCaptcha
fieldProps={{ fieldProps={{
size: 'large', size: "large",
prefix: <LockOutlined className={'prefixIcon'} />, prefix: <LockOutlined className={"prefixIcon"} />,
}} }}
captchaProps={{ captchaProps={{
size: 'large', size: "large",
}} }}
placeholder={'请输入验证码'} placeholder={"请输入验证码"}
captchaTextRender={(timing, count) => { captchaTextRender={(timing, count) => {
if (timing) { if (timing) {
return `${count} ${'获取验证码'}` return `${count} ${"获取验证码"}`;
} }
return '获取验证码' return "获取验证码";
}} }}
name='captcha' name="captcha"
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入验证码!', message: "请输入验证码!",
}, },
]} ]}
onGetCaptcha={async () => { onGetCaptcha={async () => {
captcha.current!.open() captcha.current!.open();
message.success('获取验证码成功验证码为1234') message.success("获取验证码成功验证码为1234");
}} }}
/> />
</> </>
)} )}
<div style={{ marginBlockEnd: 14 }}> <div style={{ marginBlockEnd: 14 }}>
<ProFormCheckbox noStyle name='autoLogin'> <ProFormCheckbox noStyle name="autoLogin">
</ProFormCheckbox> </ProFormCheckbox>
<a style={{ float: 'right' }}> </a> <a style={{ float: "right" }}> </a>
</div> </div>
<Button <Button
type='primary' type="primary"
block block
size='large' size="large"
onClick={async () => { onClick={async () => {
let validateFields let validateFields;
if (loginType === 'account') { if (loginType === "account") {
validateFields = ['username', 'password', 'code'] validateFields = ["username", "password", "code"];
} else { } else {
validateFields = ['mobile', 'captcha', 'code'] validateFields = ["mobile", "captcha", "code"];
} }
await form await form
.validateFields(validateFields) .validateFields(validateFields)
.then(async (values) => { .then(async (values) => {
if (loginType === 'account') { if (loginType === "account") {
captcha.current!.open() captcha.current!.open();
} }
await onSubmit(values as API.PhoneRegisterRequest) await onSubmit(values as API.PhoneRegisterRequest);
}) })
.catch((errorInfo) => { .catch((errorInfo) => {
console.error(errorInfo) console.error(errorInfo);
}) });
}}> }}>
</Button> </Button>
<div <div
style={{ style={{
display: 'flex', display: "flex",
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
flexDirection: 'column', flexDirection: "column",
}}> }}>
<Divider plain> <Divider plain>
<span <span
style={{ style={{
color: '#CCC', color: "#CCC",
fontWeight: 'normal', fontWeight: "normal",
fontSize: 14, fontSize: 14,
}}> }}>
</span> </span>
</Divider> </Divider>
<Space align='center' size={24}> <Space align="center" size={24}>
<div <div
style={{ style={{
display: 'flex', display: "flex",
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
flexDirection: 'column', flexDirection: "column",
height: 40, height: 40,
width: 40, width: 40,
border: '1px solid #D4D8DD', border: "1px solid #D4D8DD",
borderRadius: '50%', borderRadius: "50%",
}}> }}>
<QqOutlined <QqOutlined
style={{ ...iconStyles, color: '#1677FF' }} style={{ ...iconStyles, color: "#1677FF" }}
/> />
</div> </div>
<div <div
style={{ style={{
display: 'flex', display: "flex",
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
flexDirection: 'column', flexDirection: "column",
height: 40, height: 40,
width: 40, width: 40,
border: '1px solid #D4D8DD', border: "1px solid #D4D8DD",
borderRadius: '50%', borderRadius: "50%",
}}> }}>
<WechatOutlined <WechatOutlined
style={{ ...iconStyles, color: '#08a327' }} style={{ ...iconStyles, color: "#08a327" }}
/> />
</div> </div>
<div <div
style={{ style={{
display: 'flex', display: "flex",
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
flexDirection: 'column', flexDirection: "column",
height: 40, height: 40,
width: 40, width: 40,
border: '1px solid #D4D8DD', border: "1px solid #D4D8DD",
borderRadius: '50%', borderRadius: "50%",
}}> }}>
<GithubOutlined <GithubOutlined
style={{ ...iconStyles, color: '#333333' }} style={{ ...iconStyles, color: "#333333" }}
/> />
</div> </div>
<div <div
style={{ style={{
display: 'flex', display: "flex",
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
flexDirection: 'column', flexDirection: "column",
height: 40, height: 40,
width: 40, width: 40,
border: '1px solid #D4D8DD', border: "1px solid #D4D8DD",
borderRadius: '50%', borderRadius: "50%",
}}> }}>
<GitlabOutlined <GitlabOutlined
style={{ ...iconStyles, color: '#FF6A10' }} style={{ ...iconStyles, color: "#FF6A10" }}
/> />
</div> </div>
</Space> </Space>
</div> </div>
</Form> </Form>
<a href='/register' className={styles.go_to_register}> <a href="/register" className={styles.go_to_register}>
<span></span> <span></span>
</a> </a>
</Space> </Space>
</Space> </Space>
</div> </div>
<FooterComponent></FooterComponent> <FooterComponent></FooterComponent>
</div> </div>
</RotateCaptcha> </RotateCaptcha>
) );
}) });

View File

@@ -1,221 +1,223 @@
import { LockOutlined, MobileOutlined, WechatOutlined } from '@ant-design/icons' /** @format */
import { ProFormCaptcha, ProFormText } from '@ant-design/pro-components'
import { Space, Tabs, message, Image, Alert, Form, Button } from 'antd' import { LockOutlined, MobileOutlined, WechatOutlined } from "@ant-design/icons";
import { useState } from 'react' import { ProFormCaptcha, ProFormText } from "@ant-design/pro-components";
import logo from '@/assets/icons/schisandra.svg' import { Space, Tabs, message, Image, Alert, Form, Button } from "antd";
import { useState } from "react";
import logo from "@/assets/icons/schisandra.svg";
// import background from '@/assets/images/background.png' // import background from '@/assets/images/background.png'
import qrCode from '@/assets/images/login_qrcode-landaiqing.jpg' import qrCode from "@/assets/images/login_qrcode-landaiqing.jpg";
import styles from './index.module.less' import styles from "./index.module.less";
import { observer } from 'mobx-react' import { observer } from "mobx-react";
import FooterComponent from '@/components/Footer' import FooterComponent from "@/components/Footer";
// import useStore from '@/utils/store/useStore.tsx' // import useStore from '@/utils/store/useStore.tsx'
type LoginType = 'phone' type LoginType = "phone";
export default observer(() => { export default observer(() => {
const [form] = Form.useForm() const [form] = Form.useForm();
const items = [ const items = [
{ {
key: 'phone', key: "phone",
label: ( label: (
<span> <span>
<MobileOutlined /> <MobileOutlined />
</span> </span>
), ),
}, },
] ];
const [loginType, setLoginType] = useState<LoginType>('phone') const [loginType, setLoginType] = useState<LoginType>("phone");
const onSubmit = async (formData: object) => { const onSubmit = async (formData: object) => {
console.log(formData) console.log(formData);
} };
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.content}> <div className={styles.content}>
<Space> <Space>
<Space className={styles.login_content}> <Space className={styles.login_content}>
<Space align='center' className={styles.mp_code}> <Space align="center" className={styles.mp_code}>
<Space direction='vertical' align='center'> <Space direction="vertical" align="center">
<span className={styles.mp_code_title}></span> <span className={styles.mp_code_title}></span>
<Image <Image
preview={false} preview={false}
height={210} height={210}
width={200} width={200}
className={styles.mp_code_img} className={styles.mp_code_img}
// src={generateMpRegCodeData.data?.qrCodeUrl} // src={generateMpRegCodeData.data?.qrCodeUrl}
src={qrCode} src={qrCode}
/> />
<Alert <Alert
// message={(<span>微信扫码<span>关注公众号</span></span>)} // message={(<span>微信扫码<span>关注公众号</span></span>)}
description={ description={
<div> <div>
<span> <span>
<span className={styles.mp_tips}></span> <span className={styles.mp_tips}></span>
</span> </span>
<br /> <br />
</div> </div>
} }
// type="success" // type="success"
showIcon={true} showIcon={true}
className={styles.alert} className={styles.alert}
icon={<WechatOutlined />} icon={<WechatOutlined />}
/> />
</Space> </Space>
</Space> </Space>
<Form <Form
form={form} form={form}
className={styles.login_form} className={styles.login_form}
initialValues={{ initialValues={{
autoLogin: true, autoLogin: true,
}}> }}>
<Space direction='vertical' align='center'> <Space direction="vertical" align="center">
<Space className={styles.logo}> <Space className={styles.logo}>
<img <img
alt='logo' alt="logo"
src={logo} src={logo}
style={{ width: '44px', height: '44px' }} style={{ width: "44px", height: "44px" }}
/> />
<span></span> <span></span>
</Space> </Space>
<div className={styles.subTitle}></div> <div className={styles.subTitle}></div>
</Space> </Space>
<Tabs <Tabs
centered={true} centered={true}
items={items} items={items}
activeKey={loginType} activeKey={loginType}
onChange={(activeKey) => onChange={(activeKey) =>
setLoginType(activeKey as LoginType) setLoginType(activeKey as LoginType)
}></Tabs> }></Tabs>
<> <>
<ProFormText <ProFormText
fieldProps={{ fieldProps={{
size: 'large', size: "large",
prefix: <MobileOutlined className={'prefixIcon'} />, prefix: <MobileOutlined className={"prefixIcon"} />,
autoComplete: 'off', autoComplete: "off",
}} }}
name='phone' name="phone"
placeholder='请输入手机号!' placeholder="请输入手机号!"
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入手机号!', message: "请输入手机号!",
}, },
{ {
pattern: /^1\d{10}$/, pattern: /^1\d{10}$/,
message: '手机号格式错误!', message: "手机号格式错误!",
}, },
]} ]}
/> />
<ProFormText.Password <ProFormText.Password
name='password' name="password"
fieldProps={{ fieldProps={{
size: 'large', size: "large",
prefix: <LockOutlined className={'prefixIcon'} />, prefix: <LockOutlined className={"prefixIcon"} />,
}} }}
placeholder='请输入密码' placeholder="请输入密码"
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入密码!', message: "请输入密码!",
}, },
{ {
pattern: pattern:
/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{6,18}$/, /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{6,18}$/,
message: message:
'密码长度需在6~18位字符且必须包含字母和数字', "密码长度需在6~18位字符且必须包含字母和数字",
}, },
]} ]}
/> />
<ProFormText.Password <ProFormText.Password
name='confirmPassword' name="confirmPassword"
dependencies={['password']} dependencies={["password"]}
fieldProps={{ fieldProps={{
size: 'large', size: "large",
prefix: <LockOutlined className={'prefixIcon'} />, prefix: <LockOutlined className={"prefixIcon"} />,
}} }}
placeholder='请再次确认密码' placeholder="请再次确认密码"
rules={[ rules={[
{ {
required: true, required: true,
message: '请再次确认密码!', message: "请再次确认密码!",
}, },
({ getFieldValue }) => ({ ({ getFieldValue }) => ({
validator(_, value) { validator(_, value) {
if (!value || getFieldValue('password') === value) { if (!value || getFieldValue("password") === value) {
return Promise.resolve() return Promise.resolve();
} }
return Promise.reject( return Promise.reject(
new Error('两次输入的密码不一致!'), new Error("两次输入的密码不一致!"),
) );
}, },
}), }),
]} ]}
/> />
<ProFormCaptcha <ProFormCaptcha
fieldProps={{ fieldProps={{
size: 'large', size: "large",
prefix: <LockOutlined className={'prefixIcon'} />, prefix: <LockOutlined className={"prefixIcon"} />,
}} }}
captchaProps={{ captchaProps={{
size: 'large', size: "large",
}} }}
placeholder={'请输入验证码'} placeholder={"请输入验证码"}
captchaTextRender={(timing, count) => { captchaTextRender={(timing, count) => {
if (timing) { if (timing) {
return `${count} ${'获取验证码'}` return `${count} ${"获取验证码"}`;
} }
return '获取验证码' return "获取验证码";
}} }}
name='captcha' name="captcha"
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入验证码!', message: "请输入验证码!",
}, },
]} ]}
onGetCaptcha={async () => { onGetCaptcha={async () => {
message.success('获取验证码成功验证码为1234') message.success("获取验证码成功验证码为1234");
}} }}
/> />
</> </>
<Button <Button
type='primary' type="primary"
block block
size='large' size="large"
onClick={async () => { onClick={async () => {
const validateFields = [ const validateFields = [
'phone', "phone",
'username', "username",
'password', "password",
'captcha', "captcha",
'code', "code",
'confirmPassword', "confirmPassword",
] ];
await form await form
.validateFields(validateFields) .validateFields(validateFields)
.then(async (values) => { .then(async (values) => {
await onSubmit(values as API.PhoneRegisterRequest) await onSubmit(values as API.PhoneRegisterRequest);
}) })
.catch((errorInfo) => { .catch((errorInfo) => {
console.error(errorInfo) console.error(errorInfo);
}) });
}}> }}>
</Button> </Button>
</Form> </Form>
<a href='/login' className={styles.go_to_register}> <a href="/login" className={styles.go_to_register}>
<span></span> <span></span>
</a> </a>
</Space> </Space>
</Space> </Space>
</div> </div>
<FooterComponent /> <FooterComponent />
</div> </div>
) );
}) });

59
src/vite-env.d.ts vendored
View File

@@ -1,34 +1,41 @@
/** @format */
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare interface ImportMetaEnv { declare interface ImportMetaEnv {
readonly VITE_APP_BASE_API: string readonly VITE_APP_BASE_API: string;
readonly VITE_APP_TITLE: string readonly VITE_APP_TITLE: string;
readonly VITE_API_BASE_URL: string readonly VITE_API_BASE_URL: string;
readonly VITE_NODE_ENV: string readonly VITE_NODE_ENV: string;
readonly VITE_TITLE_NAME: string readonly VITE_TITLE_NAME: string;
readonly VITE_APP_TOKEN_KEY?: string readonly VITE_APP_TOKEN_KEY?: string;
readonly VITE_UPLOAD_URL?: string readonly VITE_UPLOAD_URL?: string;
} }
interface ImportMeta { interface ImportMeta {
readonly env: ImportMetaEnv readonly env: ImportMetaEnv;
} }
declare module '*.svg' {
const content: any
export default content
}
declare module '.*' {
const value: any
export default value
}
declare module '*.tsx'
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
declare module 'gsap' declare module "*.svg" {
declare module 'gsap/ScrollTrigger' const content: any;
export default content;
}
declare module "*.js" {
const content: any;
export default content;
}
declare module ".*" {
const value: any;
export default value;
}
declare module "*.tsx";
declare module "*.svg";
declare module "*.png";
declare module "*.jpg";
declare module "*.jpeg";
declare module "*.gif";
declare module "*.bmp";
declare module "*.tiff";
declare module "gsap";
declare module "gsap/ScrollTrigger";

View File

@@ -1,45 +1,44 @@
{ {
"compilerOptions": { "compilerOptions": {
"esModuleInterop": true, "esModuleInterop": true,
"target": "ES2020", "target": "ES2020",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": [ "lib": [
"ES2020", "ES2020",
"DOM", "DOM",
"DOM.Iterable" "DOM.Iterable"
], ],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "node", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
"@/*": [ "@/*": [
"./src/*" "./src/*"
] ],
} }
}, },
"include": [ "include": [
"src/**/*.ts", "src/**/*.ts",
"src/**/*.d.ts", "src/**/*.d.ts",
"src/**/*.tsx", "src/**/*.tsx",
"types/*.d.ts", "types/*.d.ts",
"vite.config.ts", "vite.config.ts",
],
], "references": [
"references": [ {
{ "path": "./tsconfig.node.json"
"path": "./tsconfig.node.json" }
} ]
]
} }

View File

@@ -1,179 +1,181 @@
import { defineConfig, loadEnv } from 'vite' /** @format */
import react from '@vitejs/plugin-react'
import { resolve } from 'path' import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import { resolve } from "path";
// icons plugin // icons plugin
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import * as path from 'path' import * as path from "path";
import imagemin from 'unplugin-imagemin/vite' import imagemin from "unplugin-imagemin/vite";
import viteCompression from 'vite-plugin-compression' import viteCompression from "vite-plugin-compression";
import { createHtmlPlugin } from 'vite-plugin-html' import { createHtmlPlugin } from "vite-plugin-html";
import legacy from '@vitejs/plugin-legacy' import legacy from "@vitejs/plugin-legacy";
import postcssPresetEnv from 'postcss-preset-env' import postcssPresetEnv from "postcss-preset-env";
import autoprefixer from 'autoprefixer' import autoprefixer from "autoprefixer";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error // @ts-expect-error
//配置参数 //配置参数
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd()) const env = loadEnv(mode, process.cwd());
return { return {
base: '/', base: "/",
plugins: [ plugins: [
react(), react(),
legacy({ legacy({
targets: [ targets: [
'ie >= 11', "ie >= 11",
'chrome 52', "chrome 52",
'Chrome > 70', "Chrome > 70",
'Safari 12.1', "Safari 12.1",
'last 2 versions and since 2018 and > 0.5%', "last 2 versions and since 2018 and > 0.5%",
'iOS >= 9', "iOS >= 9",
'Android >= 4.4', "Android >= 4.4",
'last 2 versions', "last 2 versions",
], ],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'], additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
renderLegacyChunks: true, renderLegacyChunks: true,
polyfills: [ polyfills: [
'es.promise.all-settled', "es.promise.all-settled",
'es.symbol', "es.symbol",
'es.array.filter', "es.array.filter",
'es.promise', "es.promise",
'es.promise.finally', "es.promise.finally",
'es/map', "es/map",
'es/set', "es/set",
'es.array.for-each', "es.array.for-each",
'es.object.define-properties', "es.object.define-properties",
'es.object.define-property', "es.object.define-property",
'es.object.get-own-property-descriptor', "es.object.get-own-property-descriptor",
'es.object.get-own-property-descriptors', "es.object.get-own-property-descriptors",
'es.object.keys', "es.object.keys",
'es.object.to-string', "es.object.to-string",
'web.dom-collections.for-each', "web.dom-collections.for-each",
'esnext.global-this', "esnext.global-this",
'esnext.string.match-all', "esnext.string.match-all",
], ],
modernPolyfills: ['es.promise.all-settled', 'es.object.entries'], modernPolyfills: ["es.promise.all-settled", "es.object.entries"],
}), }),
// 修改 icons 相关配置 // 修改 icons 相关配置
createSvgIconsPlugin({ createSvgIconsPlugin({
// 指定需要缓存的图标文件夹 // 指定需要缓存的图标文件夹
iconDirs: [path.resolve(__dirname, './src/assets/icons')], iconDirs: [path.resolve(__dirname, "./src/assets/icons")],
// 指定symbolId格式 // 指定symbolId格式
symbolId: 'icon-[dir]-[name]', symbolId: "icon-[dir]-[name]",
}), }),
imagemin({ imagemin({
// Default mode sharp. support squoosh and sharp // Default mode sharp. support squoosh and sharp
mode: 'sharp', mode: "sharp",
beforeBundle: true, beforeBundle: true,
// Default configuration options for compressing different pictures // Default configuration options for compressing different pictures
compress: { compress: {
jpg: { jpg: {
quality: 10, quality: 10,
}, },
jpeg: { jpeg: {
quality: 10, quality: 10,
}, },
png: { png: {
quality: 10, quality: 10,
}, },
webp: { webp: {
quality: 10, quality: 10,
}, },
}, },
conversion: [ conversion: [
{ from: 'jpeg', to: 'webp' }, { from: "jpeg", to: "webp" },
{ from: 'png', to: 'webp' }, { from: "png", to: "webp" },
{ from: 'JPG', to: 'jpeg' }, { from: "JPG", to: "jpeg" },
], ],
}), }),
viteCompression({ viteCompression({
verbose: true, // 是否在控制台中输出压缩结果 verbose: true, // 是否在控制台中输出压缩结果
disable: false, disable: false,
threshold: 10240, // 如果体积大于阈值将被压缩单位为b体积过小时请不要压缩以免适得其反 threshold: 10240, // 如果体积大于阈值将被压缩单位为b体积过小时请不要压缩以免适得其反
algorithm: 'gzip', // 压缩算法,可选['gzip'' brotliccompress ''deflate ''deflateRaw'] algorithm: "gzip", // 压缩算法,可选['gzip'' brotliccompress ''deflate ''deflateRaw']
ext: '.gz', ext: ".gz",
deleteOriginFile: true, // 源文件压缩后是否删除 deleteOriginFile: true, // 源文件压缩后是否删除
}), }),
createHtmlPlugin({ createHtmlPlugin({
minify: true, minify: true,
/** /**
* 在这里写entry后你将不需要在`index.html`内添加 script 标签,原有标签需要删除 * 在这里写entry后你将不需要在`index.html`内添加 script 标签,原有标签需要删除
* @default src/main.ts * @default src/main.ts
*/ */
entry: 'src/main.tsx', entry: "src/main.tsx",
/** /**
* 如果你想将 `index.html`存放在指定文件夹,可以修改它,否则不需要配置 * 如果你想将 `index.html`存放在指定文件夹,可以修改它,否则不需要配置
* @default index.html * @default index.html
*/ */
template: 'index.html', template: "index.html",
/** /**
* 需要注入 index.html ejs 模版的数据 * 需要注入 index.html ejs 模版的数据
*/ */
inject: { inject: {
data: { data: {
title: env.VITE_TITLE_NAME, title: env.VITE_TITLE_NAME,
}, },
}, },
}), }),
], ],
resolve: { resolve: {
alias: { alias: {
'@': resolve(__dirname, 'src'), "@": resolve(__dirname, "src"),
}, },
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'], // 默认值,这些文件引入时不需要写后缀 extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json"], // 默认值,这些文件引入时不需要写后缀
}, },
css: { css: {
modules: { modules: {
// 一般我们可以通过 generateScopedName 属性来对生成的类名进行自定义 // 一般我们可以通过 generateScopedName 属性来对生成的类名进行自定义
// 其中name 表示当前文件名local 表示类名 // 其中name 表示当前文件名local 表示类名
generateScopedName: '[name]__[local]___[hash:base64:5]', generateScopedName: "[name]__[local]___[hash:base64:5]",
}, },
postcss: { postcss: {
plugins: [ plugins: [
postcssPresetEnv(), postcssPresetEnv(),
autoprefixer({ autoprefixer({
// 自动添加前缀 // 自动添加前缀
overrideBrowserslist: [ overrideBrowserslist: [
'Android 4.1', "Android 4.1",
'iOS 7.1', "iOS 7.1",
'Chrome > 31', "Chrome > 31",
'ff > 31', "ff > 31",
'ie >= 8', "ie >= 8",
], ],
}), }),
], ],
}, },
preprocessorOptions: { preprocessorOptions: {
less: { less: {
javascriptEnabled: true, javascriptEnabled: true,
}, },
}, },
}, },
esbuild: { esbuild: {
// drop: ['console', 'debugger'], // drop: ['console', 'debugger'],
}, },
build: { build: {
outDir: 'dist', // 指定输出路径 outDir: "dist", // 指定输出路径
assetsDir: 'assets', // 指定生成静态文件目录 assetsDir: "assets", // 指定生成静态文件目录
assetsInlineLimit: '4096', // 小于此阈值的导入或引用资源将内联为 base64 编码 assetsInlineLimit: "4096", // 小于此阈值的导入或引用资源将内联为 base64 编码
cssCodeSplit: true, // 启用 CSS 代码拆分 cssCodeSplit: true, // 启用 CSS 代码拆分
sourcemap: false, // 构建后是否生成 source map 文件 sourcemap: false, // 构建后是否生成 source map 文件
minify: 'esbuild', // 指定使用哪种混淆器 minify: "esbuild", // 指定使用哪种混淆器
write: true, // 启用将构建后的文件写入磁盘 write: true, // 启用将构建后的文件写入磁盘
emptyOutDir: true, // 构建时清空该目录 emptyOutDir: true, // 构建时清空该目录
brotliSize: true, // 启用 brotli 压缩大小报告 brotliSize: true, // 启用 brotli 压缩大小报告
chunkSizeWarningLimit: 2000, // chunk 大小警告的限制 chunkSizeWarningLimit: 2000, // chunk 大小警告的限制
watch: null, // 设置为 {} 则会启用 rollup 的监听器 watch: null, // 设置为 {} 则会启用 rollup 的监听器
}, },
server: { server: {
proxy: { proxy: {
[env.VITE_APP_BASE_API]: { [env.VITE_APP_BASE_API]: {
//后端接口的baseurl //后端接口的baseurl
target: env.VITE_API_BASE_URL, target: env.VITE_API_BASE_URL,
//是否允许跨域 //是否允许跨域
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(RegExp(`^${env.VITE_APP_BASE_API}`), ''), rewrite: (path) => path.replace(RegExp(`^${env.VITE_APP_BASE_API}`), ""),
}, },
}, },
}, },
} };
}) });