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_API_BASE_URL='http://127.0.0.1:3000'
VITE_API_BASE_URL='http://1.95.0.111:3000'
VITE_API_BASE_URL='http://127.0.0.1:3000'
#VITE_API_BASE_URL='http://1.95.0.111:4000'
VITE_TITLE_NAME='五味子云存储'

View File

@@ -7,7 +7,7 @@ VITE_APP_BASE_API='/api'
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='五味子云存储'
@@ -15,4 +15,4 @@ VITE_TITLE_NAME='五味子云存储'
VITE_APP_TOKEN_KEY='token'
# 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/react-in-jsx-scope': 0, // 同上
'import/first': 0, // 消除绝对路径必须要在相对路径前引入,
'no-mixed-spaces-and-tabs': 2, // 禁止空格和 tab 的混合缩进
// 'no-mixed-spaces-and-tabs': 2, // 禁止空格和 tab 的混合缩进
'no-debugger': 2, // 禁止有debugger
'space-infix-ops': 2, // 要求操作符周围有空格
'space-before-blocks': 2, // 要求语句块之前有空格

View File

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

View File

@@ -1,66 +1,69 @@
{
"name": "schisandra-cloud-album-front-react",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --mode development --host",
"build": "tsc && vite build --mode production",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons": "^5.3.7",
"@ant-design/pro-components": "^2.7.1",
"@ant-design/use-emotion-css": "^1.0.4",
"@babel/preset-env": "^7.24.5",
"@types/crypto-js": "^4.2.2",
"@vitejs/plugin-legacy": "^5.4.0",
"antd": "^5.17.0",
"autoprefixer": "^10.4.19",
"axios": "^1.6.8",
"core-js": "^3.37.0",
"crypto-js": "^4.2.0",
"gsap": "^3.12.5",
"jsencrypt": "^3.3.2",
"localforage": "^1.10.0",
"mobx": "^6.12.3",
"mobx-persist-store": "^1.1.5",
"mobx-react": "^9.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-rotate-captcha": "^1.0.26",
"react-router-dom": "^6.23.0",
"regenerator-runtime": "^0.14.1",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.2",
"vite-plugin-svg-icons": "^2.0.1"
},
"devDependencies": {
"@rollup/plugin-babel": "^6.0.4",
"@types/gsap": "^3.0.0",
"@types/node": "^20.12.11",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"less": "^4.2.0",
"postcss-less": "^6.0.0",
"postcss-preset-env": "^9.5.11",
"prettier": "^3.2.5",
"stylelint": "^16.5.0",
"stylelint-config-recess-order": "^5.0.1",
"stylelint-config-standard-less": "^3.0.1",
"stylelint-order": "^6.0.4",
"typescript": "^5.4.5",
"unplugin-imagemin": "^0.5.18",
"vite": "^5.2.11"
}
"name": "schisandra-cloud-album-front-react",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --mode development --host",
"build": "tsc && vite build --mode production",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons": "^5.3.7",
"@ant-design/pro-components": "^2.7.1",
"@ant-design/use-emotion-css": "^1.0.4",
"@babel/preset-env": "^7.24.5",
"@types/crypto-js": "^4.2.2",
"@vitejs/plugin-legacy": "^5.4.0",
"antd": "^5.17.0",
"autoprefixer": "^10.4.19",
"axios": "^1.6.8",
"core-js": "^3.37.0",
"crypto-js": "^4.2.0",
"gsap": "^3.12.5",
"jsencrypt": "^3.3.2",
"localforage": "^1.10.0",
"mobx": "^6.12.3",
"mobx-persist-store": "^1.1.5",
"mobx-react": "^9.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-rotate-captcha": "^1.0.26",
"react-router-dom": "^6.23.0",
"regenerator-runtime": "^0.14.1",
"tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.3",
"tailwindcss-animate": "^1.0.7",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.2",
"vite-plugin-svg-icons": "^2.0.1"
},
"devDependencies": {
"@rollup/plugin-babel": "^6.0.4",
"@types/gsap": "^3.0.0",
"@types/node": "^20.12.11",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"less": "^4.2.0",
"postcss-less": "^6.0.0",
"postcss-preset-env": "^9.5.11",
"prettier": "^3.2.5",
"stylelint": "^16.5.0",
"stylelint-config-recess-order": "^5.0.1",
"stylelint-config-standard-less": "^3.0.1",
"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 = () => {
return web.post('/ReactRotateCaptcha/get')
}
return web.request({
url: "/ReactRotateCaptcha/get",
method: "get",
});
};
/**
* 验证验证码
* @param data
* @constructor
*/
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 src_src = Math.max(Math.min(img.width, img.height, size), 160)
const dst_w = src_src
const dst_h = src_src
const src_src = Math.max(Math.min(img.width, img.height, size), 160);
const dst_w = src_src;
const dst_h = src_src;
const dst_scale = dst_h / dst_w // Target image ratio
const src_scale = img.height / img.width // Original image aspect ratio
const dst_scale = dst_h / dst_w; // Target image ratio
const src_scale = img.height / img.width; // Original image aspect ratio
const info =
src_scale >= dst_scale
? [
Math.round(img.height * (src_src / img.width)),
0,
Math.round((img.height - img.width) / 2),
]
: [
Math.round(img.width * (src_src / img.height)),
Math.round((img.width - img.height) / 2),
0,
]
const info =
src_scale >= dst_scale
? [
Math.round(img.height * (src_src / img.width)),
0,
Math.round((img.height - img.width) / 2),
]
: [
Math.round(img.width * (src_src / img.height)),
Math.round((img.width - img.height) / 2),
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 [src_w, src_h, size, tar_size, x, y] = sizes
const canvas = document.createElement('canvas')
canvas.width = size
canvas.height = size
const [src_w, src_h, size, tar_size, x, y] = sizes;
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
const max = 275 - 50
const min = 0
const max = 275 - 50;
const min = 0;
const ave = Math.round((360 / max) * 100) / 100
const ctx = canvas.getContext('2d')
const ave = Math.round((360 / max) * 100) / 100;
const ctx = canvas.getContext("2d");
const coordinate = size / 2
const moveX = Math.floor(Math.random() * (max - min + 1))
const coordinate = size / 2;
const moveX = Math.floor(Math.random() * (max - min + 1));
ctx?.beginPath()
ctx?.translate(coordinate, coordinate)
ctx?.rotate((moveX * -1 * ave * Math.PI) / 180)
ctx?.translate(-coordinate, -coordinate)
ctx?.drawImage(img, x, y, src_w, src_h, 0, 0, tar_size, size)
ctx!.globalCompositeOperation = 'destination-in'
ctx?.arc(size / 2, size / 2, size / 2, 0, (360 * Math.PI) / 180, false)
ctx?.fill()
ctx?.restore()
ctx?.beginPath();
ctx?.translate(coordinate, coordinate);
ctx?.rotate((moveX * -1 * ave * Math.PI) / 180);
ctx?.translate(-coordinate, -coordinate);
ctx?.drawImage(img, x, y, src_w, src_h, 0, 0, tar_size, size);
ctx!.globalCompositeOperation = "destination-in";
ctx?.arc(size / 2, size / 2, size / 2, 0, (360 * Math.PI) / 180, false);
ctx?.fill();
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) =>
new Promise<[number, string]>((resovle) => {
const img = new Image()
img.onerror = function () {
console.log('image load error')
}
new Promise<[number, string]>((resovle) => {
const img = new Image();
img.onerror = function () {
console.log("image load error");
};
img.onload = function () {
const sizes = calcSize(img, size)
const arc_img = build(img, sizes)
img.onload = function () {
const sizes = calcSize(img, size);
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 { getCaptcha, VerfiyCaptcha } from '@/api/captcha/api.ts'
import type { TicketInfoType, TokenInfoType } from "react-rotate-captcha";
import { getCaptcha, VerfiyCaptcha } from "@/api/captcha/api.ts";
export type ActionType = {
code: 0 | 1
msg: string
}
let image: string = ''
code: 0 | 1;
msg: string;
};
let image: string = "";
export async function get(): Promise<TokenInfoType> {
const res: any = await getCaptcha()
image = res.data.str
return res
const res: any = await getCaptcha();
image = res.data.str;
return res;
}
export function isSupportWebp() {
try {
return (
document
.createElement('canvas')
.toDataURL('image/webp', 0.5)
.indexOf('data:image/webp') === 0
)
} catch (err) {
return false
}
try {
return (
document
.createElement("canvas")
.toDataURL("image/webp", 0.5)
.indexOf("data:image/webp") === 0
);
} catch (err) {
return false;
}
}
export async function load() {
return image
return image;
}
export function sleep(time: number) {
return new Promise((resolve) => {
setTimeout(() => resolve(true), time)
})
return new Promise((resolve) => {
setTimeout(() => resolve(true), time);
});
}
export async function verify(token: string, deg: number): Promise<TicketInfoType> {
const data: any = {
token: token,
deg: deg,
}
const res: any = await VerfiyCaptcha(data)
return res
const data: any = {
token: token,
deg: deg,
};
const res: any = await VerfiyCaptcha(data);
return res;
}

View File

@@ -1,12 +1,30 @@
import web from '@/utils/axios/web.ts'
import web from "@/utils/axios/web.ts";
/**
* 初始化minio
*/
export const initMinio = (data: string) => {
return web.post('/oss/minio/init', data)
}
export const initMinio = (data: any) => {
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) => {
return web.post('/oss/minio/getBaseInfo', data)
}
export const getBaseInfo = (data: any) => {
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 {
visibility: hidden;
}

View File

@@ -1,47 +1,50 @@
import React, { useEffect } from 'react'
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),
})
}
/** @format */
window.addEventListener('pointermove', UPDATE)
}, [])
return (
<>
<article>
<img src='https://assets.codepen.io/605876/osaka-sky.jpeg' alt='' />
<h3>Osaka</h3>
<img src='https://assets.codepen.io/605876/osaka-tower.png' alt='' />
<div className={styles.blur}>
<img src='https://assets.codepen.io/605876/osaka.jpeg' alt='' />
<div></div>
--&gt;
</div>
<div className={styles.content}>
<p>
<svg
xmlns='http://www.w3.org/2000/svg'
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>
<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
import React, { useEffect } from "react";
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);
}, []);
return (
<>
<article>
<img src="https://assets.codepen.io/605876/osaka-sky.jpeg" alt="" />
<h3>Osaka</h3>
<img src="https://assets.codepen.io/605876/osaka-tower.png" alt="" />
<div className={styles.blur}>
<img src="https://assets.codepen.io/605876/osaka.jpeg" alt="" />
<div></div>
--&gt;
</div>
<div className={styles.content}>
<p>
<svg
xmlns="http://www.w3.org/2000/svg"
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>
<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'
import { Suspense } from 'react'
/** @format */
import Loading from "@/components/Loading";
import { Suspense } from "react";
const ComponentLoading = (Component: any, props: any) => (
<Suspense fallback={<Loading />}>
<Component {...props} />
</Suspense>
)
export default ComponentLoading
<Suspense fallback={<Loading />}>
<Component {...props} />
</Suspense>
);
export default ComponentLoading;

View File

@@ -1,17 +1,19 @@
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>
)
}
/** @format */
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'
import './index.less'
/** @format */
import React, { useEffect } from "react";
import "./index.less";
const Loading: React.FC = () => {
useEffect(() => {
document.body.classList.add('loading-body')
return () => {
document.body.classList.remove('loading-body')
}
}, [])
return (
<>
<svg className='gegga'>
<defs>
<filter id='gegga'>
<feGaussianBlur
in='SourceGraphic'
stdDeviation='7'
result='blur'></feGaussianBlur>
<feColorMatrix
in='blur'
mode='matrix'
values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10'
result='inreGegga'></feColorMatrix>
<feComposite
in='SourceGraphic'
in2='inreGegga'
operator='atop'></feComposite>
</filter>
</defs>
</svg>
<svg className='snurra' width='200' height='200' viewBox='0 0 200 200'>
<defs>
<linearGradient id='linjärGradient'>
<stop className='stopp1' offset='0'></stop>
<stop className='stopp2' offset='1'></stop>
</linearGradient>
<linearGradient
y2='160'
x2='160'
y1='40'
x1='40'
gradientUnits='userSpaceOnUse'
id='gradient'
xlinkHref='#linjärGradient'></linearGradient>
</defs>
<path
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>
<circle className='strecken' cx='100' cy='100' r='64'></circle>
</svg>
{/*<svg className='skugga' width='200' height='200' viewBox='0 0 200 200'>*/}
{/* <path*/}
{/* 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>*/}
{/* <circle className='strecken' cx='100' cy='100' r='64'></circle>*/}
{/*</svg>*/}
</>
)
}
export default React.memo(Loading)
useEffect(() => {
document.body.classList.add("loading-body");
return () => {
document.body.classList.remove("loading-body");
};
}, []);
return (
<>
<svg className="gegga">
<defs>
<filter id="gegga">
<feGaussianBlur
in="SourceGraphic"
stdDeviation="7"
result="blur"></feGaussianBlur>
<feColorMatrix
in="blur"
mode="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10"
result="inreGegga"></feColorMatrix>
<feComposite
in="SourceGraphic"
in2="inreGegga"
operator="atop"></feComposite>
</filter>
</defs>
</svg>
<svg className="snurra" width="200" height="200" viewBox="0 0 200 200">
<defs>
<linearGradient id="linjärGradient">
<stop className="stopp1" offset="0"></stop>
<stop className="stopp2" offset="1"></stop>
</linearGradient>
<linearGradient
y2="160"
x2="160"
y1="40"
x1="40"
gradientUnits="userSpaceOnUse"
id="gradient"
xlinkHref="#linjärGradient"></linearGradient>
</defs>
<path
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>
<circle className="strecken" cx="100" cy="100" r="64"></circle>
</svg>
{/*<svg className='skugga' width='200' height='200' viewBox='0 0 200 200'>*/}
{/* <path*/}
{/* 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>*/}
{/* <circle className='strecken' cx='100' cy='100' r='64'></circle>*/}
{/*</svg>*/}
</>
);
};
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'
import LeftArea from '@/layout/default/left-area'
import MainArea from '@/layout/default/main-area'
import RightArea from '@/layout/default/right-area'
import './index.less'
import Footer from '@/components/Footer'
/** @format */
import React, { useEffect } from "react";
import LeftArea from "@/layout/default/left-area";
import MainArea from "@/layout/default/main-area";
import RightArea from "@/layout/default/right-area";
import "./index.less";
import Footer from "@/components/Footer";
const DefaultLayOut: React.FC = () => {
useEffect(() => {
document.body.classList.add('main-body')
return () => {
document.body.classList.remove('main-body')
}
}, [])
return (
<>
<div className='app-container'>
<LeftArea />
<MainArea />
<RightArea />
</div>
<Footer />
</>
)
}
export default DefaultLayOut
useEffect(() => {
document.body.classList.add("main-body");
return () => {
document.body.classList.remove("main-body");
};
}, []);
return (
<>
<div className="app-container">
<LeftArea />
<MainArea />
<RightArea />
</div>
<Footer />
</>
);
};
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'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createBrowserRouter } from 'react-router-dom'
import './assets/styles/index.less'
import routeConfig from './router'
import 'virtual:svg-icons-register'
import { Provider as MobxProvider } from 'mobx-react'
import { RootStore } from '@/store'
const router = createBrowserRouter(routeConfig)
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<MobxProvider {...RootStore}>
<RouterProvider router={router} />
</MobxProvider>,
</React.StrictMode>,
)
/** @format */
import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import "./assets/styles/index.less";
import routeConfig from "./router";
import "virtual:svg-icons-register";
import { Provider as MobxProvider } from "mobx-react";
import { RootStore } from "@/store";
const router = createBrowserRouter(routeConfig);
ReactDOM.createRoot(document.getElementById("root")!).render(
<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 Login from './modules/login/index.ts'
import Register from './modules/register/index.ts'
import home from './modules/home/index.ts'
import Main from './modules/main/index.ts'
import type { RouteObject } from "react-router-dom";
import NoFound from "@/views/404/404";
import Login from "./modules/login/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[] = [
{
path: '/',
Component: (props) => ComponentLoading(home, props),
},
{
path: '*',
Component: NoFound,
},
{
path: '/register',
Component: (props) => ComponentLoading(Register, props),
},
// {
// path: '/home',
// Component: home,
// },
{
path: '/login',
Component: (props) => ComponentLoading(Login, props),
},
{
path: '/main',
Component: (props) => ComponentLoading(Main, props),
},
]
{
path: "/",
Component: (props) => ComponentLoading(home, props),
},
{
path: "*",
Component: NoFound,
},
{
path: "/register",
Component: (props) => ComponentLoading(Register, props),
},
// {
// path: '/home',
// Component: home,
// },
{
path: "/login",
Component: (props) => ComponentLoading(Login, props),
},
{
path: "/main",
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(
() =>
new Promise((resolve: any) => {
setTimeout(() => resolve(import('@/views/Home/')), 1000)
}),
)
export default home
() =>
new Promise((resolve: any) => {
setTimeout(() => resolve(import("@/views/Home/")), 1000);
}),
);
export default home;

View File

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

View File

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

View File

@@ -1,145 +1,101 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
import { message } from 'antd'
/** @format */
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
import { message } from "antd";
class Request {
private instance: AxiosInstance | undefined
private instance: AxiosInstance | undefined;
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config)
// 全局请求拦截
this.instance.interceptors.request.use(
(config) => {
return config
},
(error) => {
return Promise.reject(error)
},
)
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config);
// 全局请求拦截
this.instance.interceptors.request.use(
(config) => {
return config;
},
(error) => {
return Promise.reject(error);
},
);
// 全局响应拦截
this.instance.interceptors.response.use(
(res) => {
// if (res.data.code && res.data.code !== 200) {
// message.error(res.data.message).then()
// return Promise.reject(res.data)
// }
return res.data
},
(error) => {
const { response } = error
if (response) {
this.handleCode(response.status)
}
if (!window.navigator.onLine) {
message.error('网络连接失败')
// return router.push({
// path: '/404',
// })
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)
})
})
}
// 全局响应拦截
this.instance.interceptors.response.use(
(res) => {
// if (res.data.code && res.data.code !== 200) {
// message.error(res.data.message).then()
// return Promise.reject(res.data)
// }
return res.data;
},
(error) => {
const { response } = error;
if (response) {
this.handleCode(response.status);
}
if (!window.navigator.onLine) {
message.error("网络连接失败");
// return router.push({
// path: '/404',
// })
return Promise.reject(error);
}
},
);
}
get(url: string) {
return new Promise((resolve, reject) => {
this.instance
?.get(url)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
handleCode(code: number): void {
switch (code) {
case 400:
message.error("请求错误(400)").then();
break;
case 401:
message.error("未授权,请重新登录(401)").then();
break;
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 = {}) {
return new Promise((resolve, reject) => {
this.instance
?.post(url, data)
.then((res) => {
resolve(res)
})
.catch((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)
})
})
}
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);
});
});
}
}
export default Request
export default Request;

View File

@@ -1,14 +1,16 @@
import Request from './request'
import { handleLocalforage } from '@/utils/localforage'
/** @format */
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({
baseURL: import.meta.env.VITE_APP_BASE_API,
headers: {
// 'Content-Type': 'application/json;charset=utf-8',
// Accept: 'application/json',
Authorization: token,
},
})
baseURL: import.meta.env.VITE_APP_BASE_API,
headers: {
// 'Content-Type': 'application/json;charset=utf-8',
// Accept: 'application/json',
Authorization: token,
},
});
export default web
export default web;

View File

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

View File

@@ -1,169 +1,173 @@
import JSEncrypt from 'jsencrypt'
import CryptoJS from 'crypto-js'
/** @format */
import JSEncrypt from "jsencrypt";
import CryptoJS from "crypto-js";
// 加密
export function rsaEncrypt(Str: string, afterPublicKey: string) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(afterPublicKey) // 设置公钥
return encryptor.encrypt(Str) // 对数据进行加密
const encryptor = new JSEncrypt();
encryptor.setPublicKey(afterPublicKey); // 设置公钥
return encryptor.encrypt(Str); // 对数据进行加密
}
// 解密
export function rsaDecrypt(Str: string, frontPrivateKey: string) {
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(frontPrivateKey) // 设置私钥
return encryptor.decrypt(Str) // 对数据进行解密
const encryptor = new JSEncrypt();
encryptor.setPrivateKey(frontPrivateKey); // 设置私钥
return encryptor.decrypt(Str); // 对数据进行解密
}
export function aesEncrypt(aeskey: string, Str: string) {
// 设置一个默认值,如果第二个参数为空采用默认值,不为空则采用新设置的密钥
const key = CryptoJS.enc.Utf8.parse(aeskey)
const srcs = CryptoJS.enc.Utf8.parse(Str)
const encrypted = CryptoJS.AES.encrypt(srcs, key, {
// 切记 需要和后端算法模式一致
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
})
// 设置一个默认值,如果第二个参数为空采用默认值,不为空则采用新设置的密钥
const key = CryptoJS.enc.Utf8.parse(aeskey);
const srcs = CryptoJS.enc.Utf8.parse(Str);
const encrypted = CryptoJS.AES.encrypt(srcs, key, {
// 切记 需要和后端算法模式一致
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString()
return encrypted.toString();
}
export function aesDecrypt(aeskey: string, Str: string) {
const key = CryptoJS.enc.Utf8.parse(aeskey)
const decrypt = CryptoJS.AES.decrypt(Str, key, {
// 切记 需要和后端算法模式一致
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
})
return CryptoJS.enc.Utf8.stringify(decrypt).toString()
const key = CryptoJS.enc.Utf8.parse(aeskey);
const decrypt = CryptoJS.AES.decrypt(Str, key, {
// 切记 需要和后端算法模式一致
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}
/**
* 获取16位随机码AES
* @returns {string}
*/
export function get16RandomNum() {
const chars = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
]
let nums = ''
//这个地方切记要选择16位因为美国对密钥长度有限制选择32位的话加解密会报错需要根据jdk版本去修改相关jar包有点恼火选择16位就不用处理。
for (let i = 0; i < 16; i++) {
const id = parseInt(String(Math.random() * 61))
nums += chars[id]
}
return nums
const chars = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
];
let nums = "";
//这个地方切记要选择16位因为美国对密钥长度有限制选择32位的话加解密会报错需要根据jdk版本去修改相关jar包有点恼火选择16位就不用处理。
for (let i = 0; i < 16; i++) {
const id = parseInt(String(Math.random() * 61));
nums += chars[id];
}
return nums;
}
//获取rsa密钥对
export function getRsaKeys() {
return new Promise((resolve, reject) => {
window.crypto.subtle
.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
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)
['encrypt', 'decrypt'], //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
)
.then(function (key) {
window.crypto.subtle
.exportKey('pkcs8', key.privateKey)
.then(function (keydata1) {
window.crypto.subtle
.exportKey('spki', key.publicKey)
.then(function (keydata2) {
const privateKey = RSA2text(keydata1, 1)
return new Promise((resolve, reject) => {
window.crypto.subtle
.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
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)
["encrypt", "decrypt"], //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
)
.then(function (key) {
window.crypto.subtle
.exportKey("pkcs8", key.privateKey)
.then(function (keydata1) {
window.crypto.subtle
.exportKey("spki", key.publicKey)
.then(function (keydata2) {
const privateKey = RSA2text(keydata1, 1);
const publicKey = RSA2text(keydata2)
const publicKey = RSA2text(keydata2);
resolve({ privateKey, publicKey })
})
.catch(function (err) {
reject(err)
})
})
.catch(function (err) {
reject(err)
})
})
.catch(function (err) {
reject(err)
})
})
resolve({ privateKey, publicKey });
})
.catch(function (err) {
reject(err);
});
})
.catch(function (err) {
reject(err);
});
})
.catch(function (err) {
reject(err);
});
});
}
function RSA2text(buffer: any, _isPrivate: number = 0) {
let binary = ''
const bytes = new Uint8Array(buffer)
const len = bytes.byteLength
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
const base64 = window.btoa(binary)
const text = base64.replace(/[^\x00-\xff]/g, '$&\x01').replace(/.{64}\x01?/g, '$&\n')
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
const base64 = window.btoa(binary);
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'
import { globalConfig } from './interface'
/** @format */
import { encrypt, decrypt } from "./encry";
import { globalConfig } from "./interface";
const config: globalConfig = {
type: 'localStorage', //存储类型localStorage | sessionStorage
prefix: 'schisandra_', //版本号
expire: 24 * 60, //过期时间,默认为一天,单位为分钟
isEncrypt: true, //支持加密、解密数据处理
}
type: "localStorage", //存储类型localStorage | sessionStorage
prefix: "schisandra_", //版本号
expire: 24 * 60, //过期时间,默认为一天,单位为分钟
isEncrypt: true, //支持加密、解密数据处理
};
const setStorage = (key: string, value: any, expire: number = 24 * 60): boolean => {
//设定值
if (value === '' || value === null || value === undefined) {
//空值重置
value = null
}
if (isNaN(expire) || expire < 0) {
//过期时间值合理性判断
throw new Error('Expire must be a number')
}
const data = {
value, //存储值
time: Date.now(), //存储日期
expire: Date.now() + 1000 * 60 * expire, //过期时间
}
//是否需要加密,判断装载加密数据或原数据
window[config.type].setItem(
autoAddPreFix(key),
config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data),
)
return true
}
//设定值
if (value === "" || value === null || value === undefined) {
//空值重置
value = null;
}
if (isNaN(expire) || expire < 0) {
//过期时间值合理性判断
throw new Error("Expire must be a number");
}
const data = {
value, //存储值
time: Date.now(), //存储日期
expire: Date.now() + 1000 * 60 * expire, //过期时间
};
//是否需要加密,判断装载加密数据或原数据
window[config.type].setItem(
autoAddPreFix(key),
config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data),
);
return true;
};
const getStorageFromKey = (key: string) => {
//获取指定值
if (config.prefix) {
key = autoAddPreFix(key)
}
if (!window[config.type].getItem(key)) {
//不存在判断
return null
}
//获取指定值
if (config.prefix) {
key = autoAddPreFix(key);
}
if (!window[config.type].getItem(key)) {
//不存在判断
return null;
}
const storageVal = config.isEncrypt
? JSON.parse(decrypt(window[config.type].getItem(key) as string))
: JSON.parse(window[config.type].getItem(key) as string)
const now = Date.now()
if (now >= storageVal.expire) {
//过期销毁
removeStorageFromKey(key)
return null
//不过期回值
} else {
return storageVal.value
}
}
const storageVal = config.isEncrypt
? JSON.parse(decrypt(window[config.type].getItem(key) as string))
: JSON.parse(window[config.type].getItem(key) as string);
const now = Date.now();
if (now >= storageVal.expire) {
//过期销毁
removeStorageFromKey(key);
return null;
//不过期回值
} else {
return storageVal.value;
}
};
const getAllStorage = () => {
//获取所有值
const storageList: any = {}
const keys = Object.keys(window[config.type])
keys.forEach((key) => {
const value = getStorageFromKey(autoRemovePreFix(key))
if (value !== null) {
//如果值没有过期,加入到列表中
storageList[autoRemovePreFix(key)] = value
}
})
return storageList
}
//获取所有值
const storageList: any = {};
const keys = Object.keys(window[config.type]);
keys.forEach((key) => {
const value = getStorageFromKey(autoRemovePreFix(key));
if (value !== null) {
//如果值没有过期,加入到列表中
storageList[autoRemovePreFix(key)] = value;
}
});
return storageList;
};
const getStorageLength = () => {
//获取值列表长度
return window[config.type].length
}
//获取值列表长度
return window[config.type].length;
};
const removeStorageFromKey = (key: string) => {
//删除值
if (config.prefix) {
key = autoAddPreFix(key)
}
window[config.type].removeItem(key)
}
//删除值
if (config.prefix) {
key = autoAddPreFix(key);
}
window[config.type].removeItem(key);
};
const clearStorage = () => {
window[config.type].clear()
}
window[config.type].clear();
};
const autoAddPreFix = (key: string) => {
//添加前缀,保持唯一性
const prefix = config.prefix || ''
return `${prefix}_${key}`
}
//添加前缀,保持唯一性
const prefix = config.prefix || "";
return `${prefix}_${key}`;
};
const autoRemovePreFix = (key: string) => {
//删除前缀,进行增删改查
const lineIndex = config.prefix.length + 1
return key.substr(lineIndex)
}
//删除前缀,进行增删改查
const lineIndex = config.prefix.length + 1;
return key.substr(lineIndex);
};
export {
setStorage,
getStorageFromKey,
getAllStorage,
getStorageLength,
removeStorageFromKey,
clearStorage,
}
setStorage,
getStorageFromKey,
getAllStorage,
getStorageLength,
removeStorageFromKey,
clearStorage,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

@@ -1,34 +1,41 @@
/** @format */
/// <reference types="vite/client" />
declare interface ImportMetaEnv {
readonly VITE_APP_BASE_API: string
readonly VITE_APP_TITLE: string
readonly VITE_API_BASE_URL: string
readonly VITE_NODE_ENV: string
readonly VITE_TITLE_NAME: string
readonly VITE_APP_TOKEN_KEY?: string
readonly VITE_UPLOAD_URL?: string
readonly VITE_APP_BASE_API: string;
readonly VITE_APP_TITLE: string;
readonly VITE_API_BASE_URL: string;
readonly VITE_NODE_ENV: string;
readonly VITE_TITLE_NAME: string;
readonly VITE_APP_TOKEN_KEY?: string;
readonly VITE_UPLOAD_URL?: string;
}
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 'gsap/ScrollTrigger'
declare module "*.svg" {
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": {
"esModuleInterop": true,
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "node",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": "./",
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"types/*.d.ts",
"vite.config.ts",
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
"compilerOptions": {
"esModuleInterop": true,
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": "./",
"paths": {
"@/*": [
"./src/*"
],
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"types/*.d.ts",
"vite.config.ts",
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

View File

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