feat: update
@@ -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='五味子云存储'
|
||||
|
||||
|
@@ -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'
|
||||
|
@@ -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, // 要求语句块之前有空格
|
||||
|
@@ -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', //对引用代码进行格式化
|
||||
}
|
||||
|
131
package.json
@@ -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
@@ -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,
|
||||
});
|
||||
};
|
||||
|
@@ -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;
|
||||
});
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
BIN
src/assets/images/cube-leg.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/images/growth.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/images/icon.ico
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
src/assets/images/icon.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/assets/images/looking-ahead.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/images/pilot.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
src/assets/images/reflecting.png
Normal file
After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 0 B |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 0 B |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 0 B |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 0 B |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 0 B |
@@ -28,4 +28,3 @@
|
||||
&::-webkit-resizer {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
@@ -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>
|
||||
-->
|
||||
</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>
|
||||
-->
|
||||
</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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -1 +0,0 @@
|
||||
export const DEFAULT_NAME = 'admin'
|
||||
|
@@ -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
@@ -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));
|
||||
}
|
36
src/main.tsx
@@ -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>,
|
||||
);
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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(),
|
||||
};
|
||||
|
@@ -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, // 保存的位置:看自己的业务情况选择,可以是localStorage,sessionstorage
|
||||
// 。。还有一些其他配置参数,例如数据过期时间等等,可以康康文档,像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, // 保存的位置:看自己的业务情况选择,可以是localStorage,sessionstorage
|
||||
// 。。还有一些其他配置参数,例如数据过期时间等等,可以康康文档,像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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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 };
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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,
|
||||
};
|
||||
|
@@ -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 };
|
||||
|
@@ -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 };
|
||||
|
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import DefaultLayOut from '@/layout/default'
|
||||
/** @format */
|
||||
|
||||
import DefaultLayOut from "@/layout/default";
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<>
|
||||
<DefaultLayOut />
|
||||
</>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<DefaultLayOut />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -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>
|
||||
);
|
||||
});
|
||||
|
@@ -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
@@ -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";
|
||||
|
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
350
vite.config.ts
@@ -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}`), ""),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|