feat: update

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,9 @@
"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"

1190
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,10 +1,10 @@
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
@@ -17,52 +17,52 @@ const calcSize = (img: HTMLImageElement, size: number) => {
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()
const img = new Image();
img.onerror = function () {
console.log('image load error')
}
console.log("image load error");
};
img.onload = function () {
const sizes = calcSize(img, size)
const arc_img = build(img, sizes)
const sizes = calcSize(img, size);
const arc_img = build(img, sizes);
resovle(arc_img)
}
resovle(arc_img);
};
img.src = url
})
img.src = url;
});

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 0 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 0 B

View File

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

View File

@@ -1,40 +1,43 @@
import React, { useEffect } from 'react'
import styles from './index.module.less'
import { gsap } from 'gsap'
/** @format */
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),
})
}
"--x": gsap.utils.mapRange(0, window.innerWidth, -1, 1, x),
"--y": gsap.utils.mapRange(0, window.innerHeight, -1, 1, y),
});
};
window.addEventListener('pointermove', UPDATE)
}, [])
window.addEventListener("pointermove", UPDATE);
}, []);
return (
<>
<article>
<img src='https://assets.codepen.io/605876/osaka-sky.jpeg' alt='' />
<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='' />
<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='' />
<img src="https://assets.codepen.io/605876/osaka.jpeg" alt="" />
<div></div>
--&gt;
</div>
<div className={styles.content}>
<p>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 24 24'
fill='currentColor'
className='w-6 h-6'>
<path d='M15.75 8.25a.75.75 0 0 1 .75.75c0 1.12-.492 2.126-1.27 2.812a.75.75 0 1 1-.992-1.124A2.243 2.243 0 0 0 15 9a.75.75 0 0 1 .75-.75Z'></path>
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>
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>
@@ -42,6 +45,6 @@ const BlurCard: React.FC = () => {
</div>
</article>
</>
)
}
export default BlurCard
);
};
export default BlurCard;

View File

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

View File

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

View File

@@ -1,92 +1,95 @@
import React, { useEffect } from 'react'
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import './index.less'
import SvgIcon from '@/components/SvgIcon/SvgIcon.tsx'
/** @format */
import React, { useEffect } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import "./index.less";
import SvgIcon from "@/components/SvgIcon/SvgIcon.tsx";
const HomeIndex: React.FC = () => {
const animationFunction = () => {
if (!CSS.supports('animation-timeline: scroll()')) {
if (!CSS.supports("animation-timeline: scroll()")) {
// const SPAN = 'max(45vw, 260px)';
const CONFIG = [
{
x: () => {
return Math.max(260, window.innerWidth * 0.45) * -1
return Math.max(260, window.innerWidth * 0.45) * -1;
},
y: -10,
r: -8,
h: 160,
w: (el: any) => {
return Math.max(320, el.parentNode.offsetWidth * 0.55)
return Math.max(320, el.parentNode.offsetWidth * 0.55);
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45)
return Math.max(260, window.innerWidth * 0.45);
},
y: -50,
r: 15,
h: 360,
w: (el: any) => {
return Math.max(220, el.parentNode.offsetWidth * 0.3)
return Math.max(220, el.parentNode.offsetWidth * 0.3);
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45) * -1
return Math.max(260, window.innerWidth * 0.45) * -1;
},
y: -30,
r: 6,
h: 300,
w: (el: any) => {
return Math.max(330, el.parentNode.offsetWidth * 0.55)
return Math.max(330, el.parentNode.offsetWidth * 0.55);
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45)
return Math.max(260, window.innerWidth * 0.45);
},
y: -30,
r: -5,
h: 400,
w: (el: any) => {
return Math.max(305, el.parentNode.offsetWidth * 0.45)
return Math.max(305, el.parentNode.offsetWidth * 0.45);
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45) * -1
return Math.max(260, window.innerWidth * 0.45) * -1;
},
y: -45,
r: -20,
h: 525,
w: (el: any) => {
return Math.max(160, el.parentNode.offsetWidth * 0.3)
return Math.max(160, el.parentNode.offsetWidth * 0.3);
},
},
{
x: () => {
return Math.max(260, window.innerWidth * 0.45)
return Math.max(260, window.innerWidth * 0.45);
},
y: 10,
r: 10,
h: 160,
w: (el: any) => {
return Math.max(320, el.parentNode.offsetWidth * 0.55)
return Math.max(320, el.parentNode.offsetWidth * 0.55);
},
},
]
];
gsap.registerPlugin(ScrollTrigger)
console.info('gsap: ScrollTrigger registered')
gsap.registerPlugin(ScrollTrigger);
console.info("gsap: ScrollTrigger registered");
gsap.set('.hero', { position: 'absolute' })
gsap.set(".hero", { position: "absolute" });
const cards = document.querySelectorAll('.card')
const cards = document.querySelectorAll(".card");
for (const [index, card] of [...cards].entries()) {
if (CONFIG[index]) {
@@ -97,420 +100,420 @@ const HomeIndex: React.FC = () => {
rotate: CONFIG[index].r,
width: CONFIG[index].w,
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 50%',
trigger: ".scroller",
start: "top bottom",
end: "top 50%",
scrub: true,
},
})
});
}
}
gsap.from(
[
'.card__content',
'.card--two .card__column:last-of-type',
'.card--three .card__column:last-of-type',
'.card--five .card__column:last-of-type',
".card__content",
".card--two .card__column:last-of-type",
".card--three .card__column:last-of-type",
".card--five .card__column:last-of-type",
],
{
y: '-100cqh',
y: "-100cqh",
scrollTrigger: {
trigger: '.scroller',
start: 'top 80%',
end: 'top top',
trigger: ".scroller",
start: "top 80%",
end: "top top",
scrub: true,
},
},
)
);
gsap.from(['.card__avatar img', '.password svg'], {
gsap.from([".card__avatar img", ".password svg"], {
opacity: 0,
scrollTrigger: {
trigger: '.scroller',
start: 'top 50%',
end: 'top top',
trigger: ".scroller",
start: "top 50%",
end: "top top",
scrub: true,
},
})
});
gsap.from(['.card--five .card__dummy', '.card--six .card__dummy'], {
gsap.from([".card--five .card__dummy", ".card--six .card__dummy"], {
width: (el: any) => el.parentNode.offsetWidth * 0.26,
scrollTrigger: {
trigger: '.scroller',
start: 'top 80%',
end: 'top top',
trigger: ".scroller",
start: "top 80%",
end: "top top",
scrub: true,
},
})
});
gsap.from(['.card--one .card__avatar', '.card--four .card__avatar'], {
gsap.from([".card--one .card__avatar", ".card--four .card__avatar"], {
scale: 2,
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top top',
trigger: ".scroller",
start: "top bottom",
end: "top top",
scrub: true,
},
})
});
gsap.from('.card--two .card__avatar', {
gsap.from(".card--two .card__avatar", {
width: (el: any) => Math.max(330, el.parentNode.offsetWidth * 0.55) - 32,
borderRadius: '12px',
height: 'calc(300cqh - 2rem)',
borderRadius: "12px",
height: "calc(300cqh - 2rem)",
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 20%',
trigger: ".scroller",
start: "top bottom",
end: "top 20%",
scrub: true,
},
})
});
gsap.from('.card--six .card__column:last-of-type .card__company', {
gsap.from(".card--six .card__column:last-of-type .card__company", {
width: 120,
x: '-1rem',
x: "-1rem",
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 20%',
trigger: ".scroller",
start: "top bottom",
end: "top 20%",
scrub: true,
},
})
});
gsap.from('.cta', {
gsap.from(".cta", {
scale: 1,
scrollTrigger: {
trigger: '.scroller',
start: 'top bottom',
end: 'top 20%',
trigger: ".scroller",
start: "top bottom",
end: "top 20%",
scrub: true,
},
})
}
});
}
};
useEffect(() => {
document.body.classList.add('body')
window.addEventListener('resize', animationFunction)
document.body.classList.add("body");
window.addEventListener("resize", animationFunction);
return () => {
document.body.classList.remove('body')
window.removeEventListener('resize', animationFunction)
}
}, [])
document.body.classList.remove("body");
window.removeEventListener("resize", animationFunction);
};
}, []);
return (
<>
<nav className={'nav'}>
<div className='navbar'>
<nav className={"nav"}>
<div className="navbar">
<a
className='bear-link'
href='/login'
target='_blank'
rel='noreferrer noopener'>
<SvgIcon name={'schisandra'} size={50} />
className="bear-link"
href="/login"
target="_blank"
rel="noreferrer noopener">
<SvgIcon name={"schisandra"} size={50} />
</a>
</div>
</nav>
<header className={'header'}>
<div className='hero'>
<div className='content'>
<h1 className={'h1'}>
<header className={"header"}>
<div className="hero">
<div className="content">
<h1 className={"h1"}>
<br />
<span>schisandra</span>
</h1>
<p>Start your journey and join thousands of others.</p>
<a href='/login' target='_blank' rel='noreferrer noopener'>
<a href="/login" target="_blank" rel="noreferrer noopener">
Start now
</a>
</div>
</div>
<div className='sticker'>
<div className='content'>
<div className='panel'>
<div className='panel__row'>
<div className='card card--one'>
<div className='card__column'>
<div className='card__avatar'>
<img src='@/assets/images/test/526.jpg' alt='' />
<div className="sticker">
<div className="content">
<div className="panel">
<div className="panel__row">
<div className="card card--one">
<div className="card__column">
<div className="card__avatar">
<img src="@/assets/images/test/526.jpg" alt="" />
</div>
</div>
<div className='card__content'>
<div className='card__details'>
<div className='text'></div>
<div className='image headspace'>
<div className="card__content">
<div className="card__details">
<div className="text"></div>
<div className="image headspace">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Headspace</title>
<path d='M23.9711 11.8612c.279 3.8878-1.5272 6.0933-2.6155 7.6357-1.694 1.7856-3.8397 4.2203-9.291 4.3565-4.6237.1827-6.8957-1.8508-8.8034-3.617-2.487-2.7336-3.1366-4.3512-3.261-8.3752-.0118-2.467.9397-4.9292 2.6025-7.0954C4.934 1.4736 8.6408.3699 12.0646.1426c3.5923-.1392 6.4493 1.6723 8.3993 3.624 2.4963 2.632 3.2629 4.8923 3.5054 8.0946Z'></path>
<path d="M23.9711 11.8612c.279 3.8878-1.5272 6.0933-2.6155 7.6357-1.694 1.7856-3.8397 4.2203-9.291 4.3565-4.6237.1827-6.8957-1.8508-8.8034-3.617-2.487-2.7336-3.1366-4.3512-3.261-8.3752-.0118-2.467.9397-4.9292 2.6025-7.0954C4.934 1.4736 8.6408.3699 12.0646.1426c3.5923-.1392 6.4493 1.6723 8.3993 3.624 2.4963 2.632 3.2629 4.8923 3.5054 8.0946Z"></path>
</svg>
</div>
<div className='text'></div>
<div className='card__dummy'>
<div className='text-wrap'>
<div className='text'></div>
<div className='text'></div>
<div className="text"></div>
<div className="card__dummy">
<div className="text-wrap">
<div className="text"></div>
<div className="text"></div>
</div>
</div>
</div>
</div>
<div className='card__column'>
<div className='card__company alexa'>
<div className="card__column">
<div className="card__company alexa">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Amazon Alexa</title>
<path d='M12 0C5.37 0 0 5.37 0 12C0 18.09 4.53 23.11 10.4 23.9V21.5A1.59 1.59 0 0 0 9.32 19.97A8.41 8.41 0 0 1 3.6 11.8A8.37 8.37 0 0 1 12.09 3.6A8.4 8.4 0 0 1 20.4 12.31L20.39 12.38A8.68 8.68 0 0 1 20.36 12.76C20.36 12.83 20.35 12.9 20.34 12.96C20.34 13.04 20.33 13.12 20.32 13.19L20.3 13.29C19.27 20.07 10.45 23.87 10.4 23.9C10.92 23.97 11.46 24 12 24C18.63 24 24 18.63 24 12S18.63 0 12 0Z'></path>
<path d="M12 0C5.37 0 0 5.37 0 12C0 18.09 4.53 23.11 10.4 23.9V21.5A1.59 1.59 0 0 0 9.32 19.97A8.41 8.41 0 0 1 3.6 11.8A8.37 8.37 0 0 1 12.09 3.6A8.4 8.4 0 0 1 20.4 12.31L20.39 12.38A8.68 8.68 0 0 1 20.36 12.76C20.36 12.83 20.35 12.9 20.34 12.96C20.34 13.04 20.33 13.12 20.32 13.19L20.3 13.29C19.27 20.07 10.45 23.87 10.4 23.9C10.92 23.97 11.46 24 12 24C18.63 24 24 18.63 24 12S18.63 0 12 0Z"></path>
</svg>
</div>
</div>
</div>
</div>
<div className='panel__row'>
<div className='card card--three'>
<div className='card__column'>
<div className='card__avatar'>
<div className="panel__row">
<div className="card card--three">
<div className="card__column">
<div className="card__avatar">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Google Chrome</title>
<path
fill='#4285F4'
d='M12 0C8.21 0 4.831 1.757 2.632 4.501l3.953 6.848A5.454 5.454 0 0 1 12 6.545h10.691A12 12 0 0 0 12 0zM1.931 5.47A11.943 11.943 0 0 0 0 12c0 6.012 4.42 10.991 10.189 11.864l3.953-6.847a5.45 5.45 0 0 1-6.865-2.29zm13.342 2.166a5.446 5.446 0 0 1 1.45 7.09l.002.001h-.002l-5.344 9.257c.206.01.413.016.621.016 6.627 0 12-5.373 12-12 0-1.54-.29-3.011-.818-4.364zM12 16.364a4.364 4.364 0 1 1 0-8.728 4.364 4.364 0 0 1 0 8.728Z'></path>
fill="#4285F4"
d="M12 0C8.21 0 4.831 1.757 2.632 4.501l3.953 6.848A5.454 5.454 0 0 1 12 6.545h10.691A12 12 0 0 0 12 0zM1.931 5.47A11.943 11.943 0 0 0 0 12c0 6.012 4.42 10.991 10.189 11.864l3.953-6.847a5.45 5.45 0 0 1-6.865-2.29zm13.342 2.166a5.446 5.446 0 0 1 1.45 7.09l.002.001h-.002l-5.344 9.257c.206.01.413.016.621.016 6.627 0 12-5.373 12-12 0-1.54-.29-3.011-.818-4.364zM12 16.364a4.364 4.364 0 1 1 0-8.728 4.364 4.364 0 0 1 0 8.728Z"></path>
</svg>
<img src='@/assets/images/test/430.jpg' alt='' />
<img src="@/assets/images/test/430.jpg" alt="" />
</div>
<div className='card__dummy'>
<div className='text-wrap'>
<div className='text'></div>
<div className='text'></div>
<div className="card__dummy">
<div className="text-wrap">
<div className="text"></div>
<div className="text"></div>
</div>
<div className='cta'></div>
<div className="cta"></div>
</div>
</div>
<div className='card__content'>
<div className='card__details'>
<div className='text'></div>
<div className='image youtube'>
<div className="card__content">
<div className="card__details">
<div className="text"></div>
<div className="image youtube">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>YouTube</title>
<path d='M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z'></path>
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"></path>
</svg>
</div>
<div className='text'></div>
<div className="text"></div>
</div>
</div>
<div className='card__column'>
<div className='card__company slack'>
<div className="card__column">
<div className="card__company slack">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Slack</title>
<path d='M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z'></path>
<path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"></path>
</svg>
</div>
</div>
</div>
</div>
<div className='panel__row'>
<div className='card card--two'>
<div className='card__column'>
<div className='card__avatar'>
<img src='@/assets/images/test/535.jpg' alt='' />
<div className="panel__row">
<div className="card card--two">
<div className="card__column">
<div className="card__avatar">
<img src="@/assets/images/test/535.jpg" alt="" />
</div>
</div>
<div className='card__content'>
<div className='card__details'>
<div className='text'></div>
<div className='image messenger'>
<div className="card__content">
<div className="card__details">
<div className="text"></div>
<div className="image messenger">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Messenger</title>
<path d='M.001 11.639C.001 4.949 5.241 0 12.001 0S24 4.95 24 11.639c0 6.689-5.24 11.638-12 11.638-1.21 0-2.38-.16-3.47-.46a.96.96 0 00-.64.05l-2.39 1.05a.96.96 0 01-1.35-.85l-.07-2.14a.97.97 0 00-.32-.68A11.39 11.389 0 01.002 11.64zm8.32-2.19l-3.52 5.6c-.35.53.32 1.139.82.75l3.79-2.87c.26-.2.6-.2.87 0l2.8 2.1c.84.63 2.04.4 2.6-.48l3.52-5.6c.35-.53-.32-1.13-.82-.75l-3.79 2.87c-.25.2-.6.2-.86 0l-2.8-2.1a1.8 1.8 0 00-2.61.48z'></path>
<path d="M.001 11.639C.001 4.949 5.241 0 12.001 0S24 4.95 24 11.639c0 6.689-5.24 11.638-12 11.638-1.21 0-2.38-.16-3.47-.46a.96.96 0 00-.64.05l-2.39 1.05a.96.96 0 01-1.35-.85l-.07-2.14a.97.97 0 00-.32-.68A11.39 11.389 0 01.002 11.64zm8.32-2.19l-3.52 5.6c-.35.53.32 1.139.82.75l3.79-2.87c.26-.2.6-.2.87 0l2.8 2.1c.84.63 2.04.4 2.6-.48l3.52-5.6c.35-.53-.32-1.13-.82-.75l-3.79 2.87c-.25.2-.6.2-.86 0l-2.8-2.1a1.8 1.8 0 00-2.61.48z"></path>
</svg>
</div>
<div className='text'></div>
<div className="text"></div>
</div>
</div>
<div className='card__column'>
<div className='card__company notion'>
<div className="card__column">
<div className="card__company notion">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Notion</title>
<path d='M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632z'></path>
<path d="M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.981-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.841-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.748-.887l-15.177.887c-.56.047-.747.327-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.748 0-.935-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.139c-.093-.514.28-.887.747-.933zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.934.653.934 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.447-1.632z"></path>
</svg>
</div>
</div>
</div>
</div>
<div className='panel__row'>
<div className='card card--six'>
<div className='card__column'>
<div className='card__avatar'>
<div className="panel__row">
<div className="card card--six">
<div className="card__column">
<div className="card__avatar">
<svg
role='img'
role="img"
style={{ background: `white` }}
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Twilio</title>
<path
fill='#F22F46'
d='M12 0C5.381-.008.008 5.352 0 11.971V12c0 6.64 5.359 12 12 12 6.64 0 12-5.36 12-12 0-6.641-5.36-12-12-12zm0 20.801c-4.846.015-8.786-3.904-8.801-8.75V12c-.014-4.846 3.904-8.786 8.75-8.801H12c4.847-.014 8.786 3.904 8.801 8.75V12c.015 4.847-3.904 8.786-8.75 8.801H12zm5.44-11.76c0 1.359-1.12 2.479-2.481 2.479-1.366-.007-2.472-1.113-2.479-2.479 0-1.361 1.12-2.481 2.479-2.481 1.361 0 2.481 1.12 2.481 2.481zm0 5.919c0 1.36-1.12 2.48-2.481 2.48-1.367-.008-2.473-1.114-2.479-2.48 0-1.359 1.12-2.479 2.479-2.479 1.361-.001 2.481 1.12 2.481 2.479zm-5.919 0c0 1.36-1.12 2.48-2.479 2.48-1.368-.007-2.475-1.113-2.481-2.48 0-1.359 1.12-2.479 2.481-2.479 1.358-.001 2.479 1.12 2.479 2.479zm0-5.919c0 1.359-1.12 2.479-2.479 2.479-1.367-.007-2.475-1.112-2.481-2.479 0-1.361 1.12-2.481 2.481-2.481 1.358 0 2.479 1.12 2.479 2.481z'></path>
fill="#F22F46"
d="M12 0C5.381-.008.008 5.352 0 11.971V12c0 6.64 5.359 12 12 12 6.64 0 12-5.36 12-12 0-6.641-5.36-12-12-12zm0 20.801c-4.846.015-8.786-3.904-8.801-8.75V12c-.014-4.846 3.904-8.786 8.75-8.801H12c4.847-.014 8.786 3.904 8.801 8.75V12c.015 4.847-3.904 8.786-8.75 8.801H12zm5.44-11.76c0 1.359-1.12 2.479-2.481 2.479-1.366-.007-2.472-1.113-2.479-2.479 0-1.361 1.12-2.481 2.479-2.481 1.361 0 2.481 1.12 2.481 2.481zm0 5.919c0 1.36-1.12 2.48-2.481 2.48-1.367-.008-2.473-1.114-2.479-2.48 0-1.359 1.12-2.479 2.479-2.479 1.361-.001 2.481 1.12 2.481 2.479zm-5.919 0c0 1.36-1.12 2.48-2.479 2.48-1.368-.007-2.475-1.113-2.481-2.48 0-1.359 1.12-2.479 2.481-2.479 1.358-.001 2.479 1.12 2.479 2.479zm0-5.919c0 1.359-1.12 2.479-2.479 2.479-1.367-.007-2.475-1.112-2.481-2.479 0-1.361 1.12-2.481 2.481-2.481 1.358 0 2.479 1.12 2.479 2.481z"></path>
</svg>
<img src='@/assets/images/test/332.jpg' alt='' />
<img src="@/assets/images/test/332.jpg" alt="" />
</div>
<div className='card__dummy'>
<div className='text-wrap'>
<div className='text'></div>
<div className='text'></div>
<div className="card__dummy">
<div className="text-wrap">
<div className="text"></div>
<div className="text"></div>
</div>
<div className='grid'>
<div className='grid__panel'></div>
<div className='grid__panel'></div>
<div className='grid__panel'></div>
<div className='grid__panel'></div>
<div className="grid">
<div className="grid__panel"></div>
<div className="grid__panel"></div>
<div className="grid__panel"></div>
<div className="grid__panel"></div>
</div>
</div>
</div>
<div className='card__content'>
<div className='card__details'>
<div className='text'></div>
<div className='image paypal'>
<div className="card__content">
<div className="card__details">
<div className="text"></div>
<div className="image paypal">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>PayPal</title>
<path d='M7.016 19.198h-4.2a.562.562 0 0 1-.555-.65L5.093.584A.692.692 0 0 1 5.776 0h7.222c3.417 0 5.904 2.488 5.846 5.5-.006.25-.027.5-.066.747A6.794 6.794 0 0 1 12.071 12H8.743a.69.69 0 0 0-.682.583l-.325 2.056-.013.083-.692 4.39-.015.087zM19.79 6.142c-.01.087-.01.175-.023.261a7.76 7.76 0 0 1-7.695 6.598H9.007l-.283 1.795-.013.083-.692 4.39-.134.843-.014.088H6.86l-.497 3.15a.562.562 0 0 0 .555.65h3.612c.34 0 .63-.249.683-.585l.952-6.031a.692.692 0 0 1 .683-.584h2.126a6.793 6.793 0 0 0 6.707-5.752c.306-1.95-.466-3.744-1.89-4.906z'></path>
<path d="M7.016 19.198h-4.2a.562.562 0 0 1-.555-.65L5.093.584A.692.692 0 0 1 5.776 0h7.222c3.417 0 5.904 2.488 5.846 5.5-.006.25-.027.5-.066.747A6.794 6.794 0 0 1 12.071 12H8.743a.69.69 0 0 0-.682.583l-.325 2.056-.013.083-.692 4.39-.015.087zM19.79 6.142c-.01.087-.01.175-.023.261a7.76 7.76 0 0 1-7.695 6.598H9.007l-.283 1.795-.013.083-.692 4.39-.134.843-.014.088H6.86l-.497 3.15a.562.562 0 0 0 .555.65h3.612c.34 0 .63-.249.683-.585l.952-6.031a.692.692 0 0 1 .683-.584h2.126a6.793 6.793 0 0 0 6.707-5.752c.306-1.95-.466-3.744-1.89-4.906z"></path>
</svg>
</div>
<div className='text'></div>
<div className="text"></div>
</div>
</div>
<div className='card__column'>
<div className='card__company password'>
<div className="card__column">
<div className="card__company password">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>1Password</title>
<path d='M12 .007C5.373.007 0 5.376 0 11.999c0 6.624 5.373 11.994 12 11.994S24 18.623 24 12C24 5.376 18.627.007 12 .007Zm-.895 4.857h1.788c.484 0 .729.002.914.096a.86.86 0 0 1 .377.377c.094.185.095.428.095.912v6.016c0 .12 0 .182-.015.238a.427.427 0 0 1-.067.137.923.923 0 0 1-.174.162l-.695.564c-.113.092-.17.138-.191.194a.216.216 0 0 0 0 .15c.02.055.078.101.191.193l.695.565c.094.076.14.115.174.162.03.042.053.087.067.137a.936.936 0 0 1 .015.238v2.746c0 .484-.001.727-.095.912a.86.86 0 0 1-.377.377c-.185.094-.43.096-.914.096h-1.788c-.484 0-.726-.002-.912-.096a.86.86 0 0 1-.377-.377c-.094-.185-.095-.428-.095-.912v-6.016c0-.12 0-.182.015-.238a.437.437 0 0 1 .067-.139c.034-.047.08-.083.174-.16l.695-.564c.113-.092.17-.138.191-.194a.216.216 0 0 0 0-.15c-.02-.055-.078-.101-.191-.193l-.695-.565a.92.92 0 0 1-.174-.162.437.437 0 0 1-.067-.139.92.92 0 0 1-.015-.236V6.25c0-.484.001-.727.095-.912a.86.86 0 0 1 .377-.377c.186-.094.428-.096.912-.096z'></path>
<path d="M12 .007C5.373.007 0 5.376 0 11.999c0 6.624 5.373 11.994 12 11.994S24 18.623 24 12C24 5.376 18.627.007 12 .007Zm-.895 4.857h1.788c.484 0 .729.002.914.096a.86.86 0 0 1 .377.377c.094.185.095.428.095.912v6.016c0 .12 0 .182-.015.238a.427.427 0 0 1-.067.137.923.923 0 0 1-.174.162l-.695.564c-.113.092-.17.138-.191.194a.216.216 0 0 0 0 .15c.02.055.078.101.191.193l.695.565c.094.076.14.115.174.162.03.042.053.087.067.137a.936.936 0 0 1 .015.238v2.746c0 .484-.001.727-.095.912a.86.86 0 0 1-.377.377c-.185.094-.43.096-.914.096h-1.788c-.484 0-.726-.002-.912-.096a.86.86 0 0 1-.377-.377c-.094-.185-.095-.428-.095-.912v-6.016c0-.12 0-.182.015-.238a.437.437 0 0 1 .067-.139c.034-.047.08-.083.174-.16l.695-.564c.113-.092.17-.138.191-.194a.216.216 0 0 0 0-.15c-.02-.055-.078-.101-.191-.193l-.695-.565a.92.92 0 0 1-.174-.162.437.437 0 0 1-.067-.139.92.92 0 0 1-.015-.236V6.25c0-.484.001-.727.095-.912a.86.86 0 0 1 .377-.377c.186-.094.428-.096.912-.096z"></path>
</svg>
</div>
</div>
</div>
</div>
<div className='panel__row'>
<div className='card card--five'>
<div className='card__column'>
<div className='card__avatar'>
<div className="panel__row">
<div className="card card--five">
<div className="card__column">
<div className="card__avatar">
<svg
style={{ background: `white` }}
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Spotify</title>
<path
fill='#1DB954'
d='M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z'></path>
fill="#1DB954"
d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"></path>
</svg>
<img src='@/assets/images/test/1011.jpg' alt='' />
<img src="@/assets/images/test/1011.jpg" alt="" />
</div>
<div className='card__dummy'>
<div className='text-wrap'>
<div className='text'></div>
<div className='text'></div>
<div className="card__dummy">
<div className="text-wrap">
<div className="text"></div>
<div className="text"></div>
</div>
<div className='grid'>
<div className='grid__panel'></div>
<div className='grid__panel'></div>
<div className='grid__panel'></div>
<div className='grid__panel'></div>
<div className="grid">
<div className="grid__panel"></div>
<div className="grid__panel"></div>
<div className="grid__panel"></div>
<div className="grid__panel"></div>
</div>
<div className='cta'></div>
<div className="cta"></div>
</div>
</div>
<div className='card__content'>
<div className='card__details'>
<div className='text'></div>
<div className='image calendly'>
<div className="card__content">
<div className="card__details">
<div className="text"></div>
<div className="image calendly">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Calendly</title>
<path d='M19.655 14.262c.281 0 .557.023.828.064 0 .005-.005.01-.005.014-.105.267-.234.534-.381.786l-1.219 2.106c-1.112 1.936-3.177 3.127-5.411 3.127h-2.432c-2.23 0-4.294-1.191-5.412-3.127l-1.218-2.106a6.251 6.251 0 0 1 0-6.252l1.218-2.106C6.736 4.832 8.8 3.641 11.035 3.641h2.432c2.23 0 4.294 1.191 5.411 3.127l1.219 2.106c.147.252.271.519.381.786 0 .004.005.009.005.014-.267.041-.543.064-.828.064-1.816 0-2.501-.607-3.291-1.306-.764-.676-1.711-1.517-3.44-1.517h-1.029c-1.251 0-2.387.455-3.2 1.278-.796.805-1.233 1.904-1.233 3.099v1.411c0 1.196.437 2.295 1.233 3.099.813.823 1.949 1.278 3.2 1.278h1.034c1.729 0 2.676-.841 3.439-1.517.791-.703 1.471-1.306 3.287-1.301Zm.005-3.237c.399 0 .794-.036 1.179-.11-.002-.004-.002-.01-.002-.014-.073-.414-.193-.823-.349-1.218.731-.12 1.407-.396 1.986-.819 0-.004-.005-.013-.005-.018-.331-1.085-.832-2.101-1.489-3.03-.649-.915-1.435-1.719-2.331-2.395-1.867-1.398-4.088-2.138-6.428-2.138-1.448 0-2.855.28-4.175.841-1.273.543-2.423 1.315-3.407 2.299S2.878 6.552 2.341 7.83c-.557 1.324-.842 2.726-.842 4.175 0 1.448.281 2.855.842 4.174.542 1.274 1.314 2.423 2.298 3.407s2.129 1.761 3.407 2.299c1.324.556 2.727.841 4.175.841 2.34 0 4.561-.74 6.428-2.137a10.815 10.815 0 0 0 2.331-2.396c.652-.929 1.158-1.949 1.489-3.03 0-.004.005-.014.005-.018-.579-.423-1.255-.699-1.986-.819.161-.395.276-.804.349-1.218.005-.009.005-.014.005-.023.869.166 1.692.506 2.404 1.035.685.505.552 1.075.446 1.416C22.184 20.437 17.619 24 12.221 24c-6.625 0-12-5.375-12-12s5.37-12 12-12c5.398 0 9.963 3.563 11.471 8.464.106.341.239.915-.446 1.421-.717.529-1.535.873-2.404 1.034.128.716.128 1.45 0 2.166-.387-.074-.782-.11-1.182-.11-4.184 0-3.968 2.823-6.736 2.823h-1.029c-1.899 0-3.15-1.357-3.15-3.095v-1.411c0-1.738 1.251-3.094 3.15-3.094h1.034c2.768 0 2.552 2.823 6.731 2.827Z'></path>
<path d="M19.655 14.262c.281 0 .557.023.828.064 0 .005-.005.01-.005.014-.105.267-.234.534-.381.786l-1.219 2.106c-1.112 1.936-3.177 3.127-5.411 3.127h-2.432c-2.23 0-4.294-1.191-5.412-3.127l-1.218-2.106a6.251 6.251 0 0 1 0-6.252l1.218-2.106C6.736 4.832 8.8 3.641 11.035 3.641h2.432c2.23 0 4.294 1.191 5.411 3.127l1.219 2.106c.147.252.271.519.381.786 0 .004.005.009.005.014-.267.041-.543.064-.828.064-1.816 0-2.501-.607-3.291-1.306-.764-.676-1.711-1.517-3.44-1.517h-1.029c-1.251 0-2.387.455-3.2 1.278-.796.805-1.233 1.904-1.233 3.099v1.411c0 1.196.437 2.295 1.233 3.099.813.823 1.949 1.278 3.2 1.278h1.034c1.729 0 2.676-.841 3.439-1.517.791-.703 1.471-1.306 3.287-1.301Zm.005-3.237c.399 0 .794-.036 1.179-.11-.002-.004-.002-.01-.002-.014-.073-.414-.193-.823-.349-1.218.731-.12 1.407-.396 1.986-.819 0-.004-.005-.013-.005-.018-.331-1.085-.832-2.101-1.489-3.03-.649-.915-1.435-1.719-2.331-2.395-1.867-1.398-4.088-2.138-6.428-2.138-1.448 0-2.855.28-4.175.841-1.273.543-2.423 1.315-3.407 2.299S2.878 6.552 2.341 7.83c-.557 1.324-.842 2.726-.842 4.175 0 1.448.281 2.855.842 4.174.542 1.274 1.314 2.423 2.298 3.407s2.129 1.761 3.407 2.299c1.324.556 2.727.841 4.175.841 2.34 0 4.561-.74 6.428-2.137a10.815 10.815 0 0 0 2.331-2.396c.652-.929 1.158-1.949 1.489-3.03 0-.004.005-.014.005-.018-.579-.423-1.255-.699-1.986-.819.161-.395.276-.804.349-1.218.005-.009.005-.014.005-.023.869.166 1.692.506 2.404 1.035.685.505.552 1.075.446 1.416C22.184 20.437 17.619 24 12.221 24c-6.625 0-12-5.375-12-12s5.37-12 12-12c5.398 0 9.963 3.563 11.471 8.464.106.341.239.915-.446 1.421-.717.529-1.535.873-2.404 1.034.128.716.128 1.45 0 2.166-.387-.074-.782-.11-1.182-.11-4.184 0-3.968 2.823-6.736 2.823h-1.029c-1.899 0-3.15-1.357-3.15-3.095v-1.411c0-1.738 1.251-3.094 3.15-3.094h1.034c2.768 0 2.552 2.823 6.731 2.827Z"></path>
</svg>
</div>
<div className='text'></div>
<div className="text"></div>
</div>
</div>
<div className='card__column'>
<div className='card__company bluesky'>
<div className="card__column">
<div className="card__company bluesky">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Bluesky</title>
<path d='M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z'></path>
<path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z"></path>
</svg>
</div>
</div>
</div>
</div>
<div className='panel__row'>
<div className='card card--four'>
<div className='card__column'>
<div className='card__avatar'>
<img src='@/assets/images/test/47.jpg' alt='' />
<div className="panel__row">
<div className="card card--four">
<div className="card__column">
<div className="card__avatar">
<img src="@/assets/images/test/47.jpg" alt="" />
</div>
</div>
<div className='card__content'>
<div className='card__details'>
<div className='text'></div>
<div className='image instagram'>
<div className="card__content">
<div className="card__details">
<div className="text"></div>
<div className="image instagram">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>Instagram</title>
<path d='M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077'></path>
<path d="M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"></path>
</svg>
</div>
<div className='text'></div>
<div className='card__dummy'>
<div className='text-wrap'>
<div className='text'></div>
<div className='text'></div>
<div className="text"></div>
<div className="card__dummy">
<div className="text-wrap">
<div className="text"></div>
<div className="text"></div>
</div>
</div>
</div>
</div>
<div className='card__column'>
<div className='card__company x'>
<div className="card__column">
<div className="card__company x">
<svg
role='img'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'>
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<title>X</title>
<path d='M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z'></path>
<path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"></path>
</svg>
</div>
</div>
@@ -519,9 +522,9 @@ const HomeIndex: React.FC = () => {
</div>
</div>
</div>
<div className='scroller'>
<div className='content'>
<div className='panel'>
<div className="scroller">
<div className="content">
<div className="panel">
<div></div>
<div></div>
<div></div>
@@ -531,11 +534,11 @@ const HomeIndex: React.FC = () => {
</div>
</div>
</div>
<div className='ring ring--under'>
<img src='https://assets.codepen.io/605876/portal-ring.png' alt='' />
<div className="ring ring--under">
<img src="https://assets.codepen.io/605876/portal-ring.png" alt="" />
</div>
<div className='ring ring--over'>
<img src='https://assets.codepen.io/605876/portal-ring.png' alt='' />
<div className="ring ring--over">
<img src="https://assets.codepen.io/605876/portal-ring.png" alt="" />
</div>
</header>
<main>
@@ -543,8 +546,8 @@ const HomeIndex: React.FC = () => {
{/* <h2 className={'h2'}>Pretty rad.</h2>*/}
{/*</section>*/}
</main>
<footer className={'footer'}>ʕʔ schisandra © 2024</footer>
<footer className={"footer"}>ʕʔ schisandra © 2024</footer>
</>
)
}
export default HomeIndex
);
};
export default HomeIndex;

View File

@@ -1,52 +1,55 @@
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')
document.body.classList.add("loading-body");
return () => {
document.body.classList.remove('loading-body')
}
}, [])
document.body.classList.remove("loading-body");
};
}, []);
return (
<>
<svg className='gegga'>
<svg className="gegga">
<defs>
<filter id='gegga'>
<filter id="gegga">
<feGaussianBlur
in='SourceGraphic'
stdDeviation='7'
result='blur'></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>
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>
in="SourceGraphic"
in2="inreGegga"
operator="atop"></feComposite>
</filter>
</defs>
</svg>
<svg className='snurra' width='200' height='200' viewBox='0 0 200 200'>
<svg className="snurra" width="200" height="200" viewBox="0 0 200 200">
<defs>
<linearGradient id='linjärGradient'>
<stop className='stopp1' offset='0'></stop>
<stop className='stopp2' offset='1'></stop>
<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>
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>
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*/}
@@ -55,6 +58,6 @@ const Loading: React.FC = () => {
{/* <circle className='strecken' cx='100' cy='100' r='64'></circle>*/}
{/*</svg>*/}
</>
)
}
export default React.memo(Loading)
);
};
export default React.memo(Loading);

View File

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

View File

@@ -1,25 +1,28 @@
import React, { useEffect } from 'react'
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')
document.body.classList.add("main-body");
return () => {
document.body.classList.remove('main-body')
}
}, [])
document.body.classList.remove("main-body");
};
}, []);
return (
<>
<div className='app-container'>
<div className="app-container">
<LeftArea />
<MainArea />
<RightArea />
</div>
<Footer />
</>
)
}
export default DefaultLayOut
);
};
export default DefaultLayOut;

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

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

View File

@@ -1,16 +1,20 @@
import React from 'react'
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(
/** @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>,
</MobxProvider>
,
</React.StrictMode>,
)
);

View File

@@ -1,23 +1,26 @@
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: '/',
path: "/",
Component: (props) => ComponentLoading(home, props),
},
{
path: '*',
path: "*",
Component: NoFound,
},
{
path: '/register',
path: "/register",
Component: (props) => ComponentLoading(Register, props),
},
// {
@@ -25,13 +28,13 @@ const routes: RouteObject[] = [
// Component: home,
// },
{
path: '/login',
path: "/login",
Component: (props) => ComponentLoading(Login, props),
},
{
path: '/main',
path: "/main",
Component: (props) => ComponentLoading(Main, props),
},
]
];
export default routes
export default routes;

View File

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

View File

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

View File

@@ -1,8 +1,12 @@
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 = ''
token: any = "";
constructor() {
makeAutoObservable(
this,
@@ -10,13 +14,13 @@ export class useUserStore {
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数据会在数据返回后再自动计算
name: "token", // 保存的name用于在storage中的名称标识只要不和storage中其他名称重复就可以
properties: ["token"], // 要保存的字段这些字段会被保存在name对应的storage中注意不写在这里面的字段将不会被保存刷新页面也将丢失get字段例外。get数据会在数据返回后再自动计算
storage: handleLocalforage, // 保存的位置看自己的业务情况选择可以是localStoragesessionstorage
// 。。还有一些其他配置参数例如数据过期时间等等可以康康文档像storage这种字段可以配置在全局配置里详见文档
}).then(
@@ -24,15 +28,18 @@ export class useUserStore {
// persist 完成的回调在这里可以执行一些拿到数据后需要执行的操作如果在页面上要判断是否完成persist使用 isHydrated
// console.log(persistStore)
}),
)
);
}
get getToken() {
return this.token ? this.token : null
return this.token ? this.token : null;
}
get isHydrated() {
return isHydrated(this)
return isHydrated(this);
}
setToken(token: string) {
this.token = token
this.token = token;
}
}

View File

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

View File

@@ -1,7 +1,9 @@
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: {
@@ -9,6 +11,6 @@ const web: Request = new Request({
// Accept: 'application/json',
Authorization: token,
},
})
});
export default web
export default web;

View File

@@ -1,19 +1,25 @@
/** 配置 */
/**
* 配置
*
* @format
*/
interface Options {
/** key前缀 */
prefix?: string
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 ?? ''
const { prefix } = options;
this.prefix = prefix ?? "";
}
/**
@@ -25,13 +31,13 @@ class CookieStorage {
* @Author: mulingyuer
*/
public setItem(key: string, value: string | number, expires?: number): void {
const timestamp = Date.now()
if (typeof expires === 'number') {
expires = timestamp + expires
const timestamp = Date.now();
if (typeof expires === "number") {
expires = timestamp + expires;
} else {
expires = timestamp + 2 * 365 * 24 * 60 * 60 * 1000
expires = timestamp + 2 * 365 * 24 * 60 * 60 * 1000;
}
document.cookie = `${this.prefix}${key}=${value};expires=${new Date(expires).toUTCString()}`
document.cookie = `${this.prefix}${key}=${value};expires=${new Date(expires).toUTCString()}`;
}
/**
@@ -41,18 +47,18 @@ class CookieStorage {
* @Author: mulingyuer
*/
public getItem(key: string): string | undefined {
const cookies = document.cookie.split('; ')
let val: string | undefined = undefined
const cookies = document.cookie.split("; ");
let val: string | undefined = undefined;
cookies.find((item) => {
const [k, v] = item.split('=')
const [k, v] = item.split("=");
if (k === `${this.prefix}${key}`) {
val = v
return true
val = v;
return true;
}
return false
})
return false;
});
return val
return val;
}
/**
@@ -62,7 +68,7 @@ class CookieStorage {
* @Author: mulingyuer
*/
public removeItem(key: string): void {
this.setItem(key, '', -1)
this.setItem(key, "", -1);
}
/**
@@ -71,15 +77,15 @@ class CookieStorage {
* @Author: mulingyuer
*/
public clear(): void {
const cookies = document.cookie.split('; ')
const cookies = document.cookie.split("; ");
cookies.forEach((item) => {
const [k] = item.split('=')
this.removeItem(k)
})
const [k] = item.split("=");
this.removeItem(k);
});
}
}
const cookieStorage = new CookieStorage()
const cookieStorage = new CookieStorage();
export default cookieStorage
export { CookieStorage }
export default cookieStorage;
export { CookieStorage };

View File

@@ -1,169 +1,173 @@
import JSEncrypt from 'jsencrypt'
import CryptoJS from 'crypto-js'
/** @format */
import JSEncrypt from "jsencrypt";
import CryptoJS from "crypto-js";
// 加密
export function rsaEncrypt(Str: string, afterPublicKey: string) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(afterPublicKey) // 设置公钥
return encryptor.encrypt(Str) // 对数据进行加密
const encryptor = new JSEncrypt();
encryptor.setPublicKey(afterPublicKey); // 设置公钥
return encryptor.encrypt(Str); // 对数据进行加密
}
// 解密
export function rsaDecrypt(Str: string, frontPrivateKey: string) {
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(frontPrivateKey) // 设置私钥
return encryptor.decrypt(Str) // 对数据进行解密
const encryptor = new JSEncrypt();
encryptor.setPrivateKey(frontPrivateKey); // 设置私钥
return encryptor.decrypt(Str); // 对数据进行解密
}
export function aesEncrypt(aeskey: string, Str: string) {
// 设置一个默认值,如果第二个参数为空采用默认值,不为空则采用新设置的密钥
const key = CryptoJS.enc.Utf8.parse(aeskey)
const srcs = CryptoJS.enc.Utf8.parse(Str)
const 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 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()
});
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 = ''
"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]
const id = parseInt(String(Math.random() * 61));
nums += chars[id];
}
return nums
return nums;
}
//获取rsa密钥对
export function getRsaKeys() {
return new Promise((resolve, reject) => {
window.crypto.subtle
.generateKey(
{
name: 'RSA-OAEP',
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"
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"]
["encrypt", "decrypt"], //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
)
.then(function (key) {
window.crypto.subtle
.exportKey('pkcs8', key.privateKey)
.exportKey("pkcs8", key.privateKey)
.then(function (keydata1) {
window.crypto.subtle
.exportKey('spki', key.publicKey)
.exportKey("spki", key.publicKey)
.then(function (keydata2) {
const privateKey = RSA2text(keydata1, 1)
const privateKey = RSA2text(keydata1, 1);
const publicKey = RSA2text(keydata2)
const publicKey = RSA2text(keydata2);
resolve({ privateKey, publicKey })
resolve({ privateKey, publicKey });
})
.catch(function (err) {
reject(err)
})
reject(err);
});
})
.catch(function (err) {
reject(err)
})
reject(err);
});
})
.catch(function (err) {
reject(err)
})
})
reject(err);
});
});
}
function RSA2text(buffer: any, _isPrivate: number = 0) {
let binary = ''
const bytes = new Uint8Array(buffer)
const len = bytes.byteLength
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
binary += String.fromCharCode(bytes[i]);
}
const base64 = window.btoa(binary)
const text = base64.replace(/[^\x00-\xff]/g, '$&\x01').replace(/.{64}\x01?/g, '$&\n')
const base64 = window.btoa(binary);
const text = base64.replace(/[^\x00-\xff]/g, "$&\x01").replace(/.{64}\x01?/g, "$&\n");
return text
return text;
}

View File

@@ -1,96 +1,98 @@
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_', //版本号
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) {
if (value === "" || value === null || value === undefined) {
//空值重置
value = null
value = null;
}
if (isNaN(expire) || expire < 0) {
//过期时间值合理性判断
throw new Error('Expire must be a number')
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
}
);
return true;
};
const getStorageFromKey = (key: string) => {
//获取指定值
if (config.prefix) {
key = autoAddPreFix(key)
key = autoAddPreFix(key);
}
if (!window[config.type].getItem(key)) {
//不存在判断
return null
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()
: JSON.parse(window[config.type].getItem(key) as string);
const now = Date.now();
if (now >= storageVal.expire) {
//过期销毁
removeStorageFromKey(key)
return null
removeStorageFromKey(key);
return null;
//不过期回值
} else {
return storageVal.value
}
return storageVal.value;
}
};
const getAllStorage = () => {
//获取所有值
const storageList: any = {}
const keys = Object.keys(window[config.type])
const storageList: any = {};
const keys = Object.keys(window[config.type]);
keys.forEach((key) => {
const value = getStorageFromKey(autoRemovePreFix(key))
const value = getStorageFromKey(autoRemovePreFix(key));
if (value !== null) {
//如果值没有过期,加入到列表中
storageList[autoRemovePreFix(key)] = value
}
})
return storageList
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)
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,
@@ -99,4 +101,4 @@ export {
getStorageLength,
removeStorageFromKey,
clearStorage,
}
};

View File

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

View File

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

View File

@@ -1,22 +1,24 @@
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 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)
}
});
return encrypted.ciphertext.toString(output);
};
/**
* 解密
@@ -24,51 +26,51 @@ export const encrypt = (data: string, output?: any) => {
*/
export const decrypt = (data: string | null) => {
if (data === null) {
return
return;
}
const encryptedHex = CryptoJS.enc.Hex.parse(data)
const encryptedHexStr = CryptoJS.enc.Base64.stringify(encryptedHex)
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()
}
});
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))
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
const value: any = decrypt(await localforage.getItem(key)) as any;
// 如果值是 undefined返回 null
if (value === undefined) {
return null
return null;
}
// 如果值是 T 类型,直接返回
if (typeof value === 'object' && value !== null) {
return value as T
if (typeof value === "object" && value !== null) {
return value as T;
}
// 如果值是 string 类型,直接返回
return value as string
return value as string;
} catch (error) {
console.error('Error retrieving data from localforage:', error)
return null
console.error("Error retrieving data from localforage:", error);
return null;
}
},
removeItem: async (key: string): Promise<void> => {
await localforage.removeItem(key)
await localforage.removeItem(key);
},
clear: async () => {
return await localforage.clear()
return await localforage.clear();
},
createInstance: async (name: string) => {
return localforage.createInstance({
name,
})
});
},
}
};

View File

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

View File

@@ -1,161 +1,164 @@
import { useNavigate } from 'react-router-dom'
import './index.less'
/** @format */
import { useNavigate } from "react-router-dom";
// import "./index.less";
import { useEffect } from "react";
export default () => {
const navigate = useNavigate()
const navigate = useNavigate();
const goBack = () => {
navigate(-1)
}
navigate(-1);
};
useEffect(() => {
document.body.classList.add('not-fount-body')
document.body.classList.add("not-fount-body");
return () => {
document.body.classList.remove('not-fount-body')
}
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>
<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 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 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 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 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 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 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 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 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 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 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 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 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 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 className="number">4</div>
</div>
<div className='subtitle'>Oops. Looks like you took a wrong turn.</div>
<div className="subtitle">Oops. Looks like you took a wrong turn.</div>
<button
style={{
marginTop: '1.5em',
marginTop: "1.5em",
}}
onClick={goBack}>
Go back
@@ -164,5 +167,5 @@ export default () => {
</div>
</body>
</>
)
}
);
};

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
/** @format */
import {
GithubOutlined,
GitlabOutlined,
@@ -6,31 +8,31 @@ import {
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'
} 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 [form] = Form.useForm();
const captcha = useRef<CaptchaInstance>(null);
const items = [
{
label: (
@@ -39,7 +41,7 @@ export default observer(() => {
</span>
),
key: 'account',
key: "account",
},
{
label: (
@@ -48,22 +50,22 @@ export default observer(() => {
</span>
),
key: 'phone',
key: "phone",
},
]
const [loginType, setLoginType] = useState<LoginType>('account')
];
const [loginType, setLoginType] = useState<LoginType>("account");
const onSubmit = async (formData: object) => {
console.log(formData)
}
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'>
<Space align="center" className={styles.mp_code}>
<Space direction="vertical" align="center">
<span className={styles.mp_code_title}></span>
<Image
preview={false}
@@ -100,12 +102,12 @@ export default observer(() => {
initialValues={{
autoLogin: true,
}}>
<Space direction='vertical' align='center'>
<Space direction="vertical" align="center">
<Space className={styles.logo}>
<img
alt='logo'
alt="logo"
src={logo}
style={{ width: '44px', height: '44px' }}
style={{ width: "44px", height: "44px" }}
/>
<span></span>
</Space>
@@ -120,39 +122,39 @@ export default observer(() => {
setLoginType(activeKey as LoginType)
}></Tabs>
{loginType === 'account' && (
{loginType === "account" && (
<>
<ProFormText
name='username'
name="username"
fieldProps={{
size: 'large',
prefix: <UserOutlined className={'prefixIcon'} />,
size: "large",
prefix: <UserOutlined className={"prefixIcon"} />,
}}
placeholder={'请输入账号/邮箱/电话号码'}
placeholder={"请输入账号/邮箱/电话号码"}
rules={[
{
required: true,
message: '请输入用户名!',
message: "请输入用户名!",
},
]}
/>
<ProFormText.Password
name='password'
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
}}
placeholder={'请输入密码'}
placeholder={"请输入密码"}
rules={[
{
required: true,
message: '请输入密码!',
message: "请输入密码!",
},
{
pattern:
/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{6,18}$/,
message:
'密码长度需在6~18位字符且必须包含字母和数字',
"密码长度需在6~18位字符且必须包含字母和数字",
},
]}
/>
@@ -178,24 +180,24 @@ export default observer(() => {
{/*/>*/}
</>
)}
{loginType === 'phone' && (
{loginType === "phone" && (
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileOutlined className={'prefixIcon'} />,
autoComplete: 'off',
size: "large",
prefix: <MobileOutlined className={"prefixIcon"} />,
autoComplete: "off",
}}
name='mobile'
placeholder={'请输入手机号'}
name="mobile"
placeholder={"请输入手机号"}
rules={[
{
required: true,
message: '请输入手机号!',
message: "请输入手机号!",
},
{
pattern: /^1\d{10}$/,
message: '手机号格式错误!',
message: "手机号格式错误!",
},
]}
/>
@@ -221,149 +223,149 @@ export default observer(() => {
{/*/>*/}
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
}}
captchaProps={{
size: 'large',
size: "large",
}}
placeholder={'请输入验证码'}
placeholder={"请输入验证码"}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${'获取验证码'}`
return `${count} ${"获取验证码"}`;
}
return '获取验证码'
return "获取验证码";
}}
name='captcha'
name="captcha"
rules={[
{
required: true,
message: '请输入验证码!',
message: "请输入验证码!",
},
]}
onGetCaptcha={async () => {
captcha.current!.open()
message.success('获取验证码成功验证码为1234')
captcha.current!.open();
message.success("获取验证码成功验证码为1234");
}}
/>
</>
)}
<div style={{ marginBlockEnd: 14 }}>
<ProFormCheckbox noStyle name='autoLogin'>
<ProFormCheckbox noStyle name="autoLogin">
</ProFormCheckbox>
<a style={{ float: 'right' }}> </a>
<a style={{ float: "right" }}> </a>
</div>
<Button
type='primary'
type="primary"
block
size='large'
size="large"
onClick={async () => {
let validateFields
if (loginType === 'account') {
validateFields = ['username', 'password', 'code']
let validateFields;
if (loginType === "account") {
validateFields = ["username", "password", "code"];
} else {
validateFields = ['mobile', 'captcha', 'code']
validateFields = ["mobile", "captcha", "code"];
}
await form
.validateFields(validateFields)
.then(async (values) => {
if (loginType === 'account') {
captcha.current!.open()
if (loginType === "account") {
captcha.current!.open();
}
await onSubmit(values as API.PhoneRegisterRequest)
await onSubmit(values as API.PhoneRegisterRequest);
})
.catch((errorInfo) => {
console.error(errorInfo)
})
console.error(errorInfo);
});
}}>
</Button>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
}}>
<Divider plain>
<span
style={{
color: '#CCC',
fontWeight: 'normal',
color: "#CCC",
fontWeight: "normal",
fontSize: 14,
}}>
</span>
</Divider>
<Space align='center' size={24}>
<Space align="center" size={24}>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
border: "1px solid #D4D8DD",
borderRadius: "50%",
}}>
<QqOutlined
style={{ ...iconStyles, color: '#1677FF' }}
style={{ ...iconStyles, color: "#1677FF" }}
/>
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
border: "1px solid #D4D8DD",
borderRadius: "50%",
}}>
<WechatOutlined
style={{ ...iconStyles, color: '#08a327' }}
style={{ ...iconStyles, color: "#08a327" }}
/>
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
border: "1px solid #D4D8DD",
borderRadius: "50%",
}}>
<GithubOutlined
style={{ ...iconStyles, color: '#333333' }}
style={{ ...iconStyles, color: "#333333" }}
/>
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
border: "1px solid #D4D8DD",
borderRadius: "50%",
}}>
<GitlabOutlined
style={{ ...iconStyles, color: '#FF6A10' }}
style={{ ...iconStyles, color: "#FF6A10" }}
/>
</div>
</Space>
</div>
</Form>
<a href='/register' className={styles.go_to_register}>
<a href="/register" className={styles.go_to_register}>
<span></span>
</a>
</Space>
@@ -372,5 +374,5 @@ export default observer(() => {
<FooterComponent></FooterComponent>
</div>
</RotateCaptcha>
)
})
);
});

View File

@@ -1,21 +1,23 @@
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 [form] = Form.useForm();
const items = [
{
key: 'phone',
key: "phone",
label: (
<span>
<MobileOutlined />
@@ -23,19 +25,19 @@ export default observer(() => {
</span>
),
},
]
const [loginType, setLoginType] = useState<LoginType>('phone')
];
const [loginType, setLoginType] = useState<LoginType>("phone");
const onSubmit = async (formData: object) => {
console.log(formData)
}
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'>
<Space align="center" className={styles.mp_code}>
<Space direction="vertical" align="center">
<span className={styles.mp_code_title}></span>
<Image
preview={false}
@@ -70,12 +72,12 @@ export default observer(() => {
initialValues={{
autoLogin: true,
}}>
<Space direction='vertical' align='center'>
<Space direction="vertical" align="center">
<Space className={styles.logo}>
<img
alt='logo'
alt="logo"
src={logo}
style={{ width: '44px', height: '44px' }}
style={{ width: "44px", height: "44px" }}
/>
<span></span>
</Space>
@@ -93,123 +95,123 @@ export default observer(() => {
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileOutlined className={'prefixIcon'} />,
autoComplete: 'off',
size: "large",
prefix: <MobileOutlined className={"prefixIcon"} />,
autoComplete: "off",
}}
name='phone'
placeholder='请输入手机号!'
name="phone"
placeholder="请输入手机号!"
rules={[
{
required: true,
message: '请输入手机号!',
message: "请输入手机号!",
},
{
pattern: /^1\d{10}$/,
message: '手机号格式错误!',
message: "手机号格式错误!",
},
]}
/>
<ProFormText.Password
name='password'
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
}}
placeholder='请输入密码'
placeholder="请输入密码"
rules={[
{
required: true,
message: '请输入密码!',
message: "请输入密码!",
},
{
pattern:
/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{6,18}$/,
message:
'密码长度需在6~18位字符且必须包含字母和数字',
"密码长度需在6~18位字符且必须包含字母和数字",
},
]}
/>
<ProFormText.Password
name='confirmPassword'
dependencies={['password']}
name="confirmPassword"
dependencies={["password"]}
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
}}
placeholder='请再次确认密码'
placeholder="请再次确认密码"
rules={[
{
required: true,
message: '请再次确认密码!',
message: "请再次确认密码!",
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve()
if (!value || getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(
new Error('两次输入的密码不一致!'),
)
new Error("两次输入的密码不一致!"),
);
},
}),
]}
/>
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
}}
captchaProps={{
size: 'large',
size: "large",
}}
placeholder={'请输入验证码'}
placeholder={"请输入验证码"}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${'获取验证码'}`
return `${count} ${"获取验证码"}`;
}
return '获取验证码'
return "获取验证码";
}}
name='captcha'
name="captcha"
rules={[
{
required: true,
message: '请输入验证码!',
message: "请输入验证码!",
},
]}
onGetCaptcha={async () => {
message.success('获取验证码成功验证码为1234')
message.success("获取验证码成功验证码为1234");
}}
/>
</>
<Button
type='primary'
type="primary"
block
size='large'
size="large"
onClick={async () => {
const validateFields = [
'phone',
'username',
'password',
'captcha',
'code',
'confirmPassword',
]
"phone",
"username",
"password",
"captcha",
"code",
"confirmPassword",
];
await form
.validateFields(validateFields)
.then(async (values) => {
await onSubmit(values as API.PhoneRegisterRequest)
await onSubmit(values as API.PhoneRegisterRequest);
})
.catch((errorInfo) => {
console.error(errorInfo)
})
console.error(errorInfo);
});
}}>
</Button>
</Form>
<a href='/login' className={styles.go_to_register}>
<a href="/login" className={styles.go_to_register}>
<span></span>
</a>
</Space>
@@ -217,5 +219,5 @@ export default observer(() => {
</div>
<FooterComponent />
</div>
)
})
);
});

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

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

View File

@@ -11,7 +11,7 @@
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "node",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
@@ -26,7 +26,7 @@
"paths": {
"@/*": [
"./src/*"
]
],
}
},
"include": [
@@ -35,7 +35,6 @@
"src/**/*.tsx",
"types/*.d.ts",
"vite.config.ts",
],
"references": [
{

View File

@@ -1,68 +1,70 @@
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())
const env = loadEnv(mode, process.cwd());
return {
base: '/',
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',
"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'],
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',
"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'],
modernPolyfills: ["es.promise.all-settled", "es.object.entries"],
}),
// 修改 icons 相关配置
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(__dirname, './src/assets/icons')],
iconDirs: [path.resolve(__dirname, "./src/assets/icons")],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]',
symbolId: "icon-[dir]-[name]",
}),
imagemin({
// Default mode sharp. support squoosh and sharp
mode: 'sharp',
mode: "sharp",
beforeBundle: true,
// Default configuration options for compressing different pictures
compress: {
@@ -80,17 +82,17 @@ export default defineConfig(({ mode }) => {
},
},
conversion: [
{ from: 'jpeg', to: 'webp' },
{ from: 'png', to: 'webp' },
{ from: 'JPG', to: 'jpeg' },
{ 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',
algorithm: "gzip", // 压缩算法,可选['gzip'' brotliccompress ''deflate ''deflateRaw']
ext: ".gz",
deleteOriginFile: true, // 源文件压缩后是否删除
}),
createHtmlPlugin({
@@ -99,12 +101,12 @@ export default defineConfig(({ mode }) => {
* 在这里写entry后你将不需要在`index.html`内添加 script 标签,原有标签需要删除
* @default src/main.ts
*/
entry: 'src/main.tsx',
entry: "src/main.tsx",
/**
* 如果你想将 `index.html`存放在指定文件夹,可以修改它,否则不需要配置
* @default index.html
*/
template: 'index.html',
template: "index.html",
/**
* 需要注入 index.html ejs 模版的数据
*/
@@ -117,15 +119,15 @@ export default defineConfig(({ mode }) => {
],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
"@": resolve(__dirname, "src"),
},
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'], // 默认值,这些文件引入时不需要写后缀
extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json"], // 默认值,这些文件引入时不需要写后缀
},
css: {
modules: {
// 一般我们可以通过 generateScopedName 属性来对生成的类名进行自定义
// 其中name 表示当前文件名local 表示类名
generateScopedName: '[name]__[local]___[hash:base64:5]',
generateScopedName: "[name]__[local]___[hash:base64:5]",
},
postcss: {
plugins: [
@@ -133,11 +135,11 @@ export default defineConfig(({ mode }) => {
autoprefixer({
// 自动添加前缀
overrideBrowserslist: [
'Android 4.1',
'iOS 7.1',
'Chrome > 31',
'ff > 31',
'ie >= 8',
"Android 4.1",
"iOS 7.1",
"Chrome > 31",
"ff > 31",
"ie >= 8",
],
}),
],
@@ -152,12 +154,12 @@ export default defineConfig(({ mode }) => {
// drop: ['console', 'debugger'],
},
build: {
outDir: 'dist', // 指定输出路径
assetsDir: 'assets', // 指定生成静态文件目录
assetsInlineLimit: '4096', // 小于此阈值的导入或引用资源将内联为 base64 编码
outDir: "dist", // 指定输出路径
assetsDir: "assets", // 指定生成静态文件目录
assetsInlineLimit: "4096", // 小于此阈值的导入或引用资源将内联为 base64 编码
cssCodeSplit: true, // 启用 CSS 代码拆分
sourcemap: false, // 构建后是否生成 source map 文件
minify: 'esbuild', // 指定使用哪种混淆器
minify: "esbuild", // 指定使用哪种混淆器
write: true, // 启用将构建后的文件写入磁盘
emptyOutDir: true, // 构建时清空该目录
brotliSize: true, // 启用 brotli 压缩大小报告
@@ -171,9 +173,9 @@ export default defineConfig(({ mode }) => {
target: env.VITE_API_BASE_URL,
//是否允许跨域
changeOrigin: true,
rewrite: (path) => path.replace(RegExp(`^${env.VITE_APP_BASE_API}`), ''),
rewrite: (path) => path.replace(RegExp(`^${env.VITE_APP_BASE_API}`), ""),
},
},
},
}
})
};
});