feat: alot
This commit is contained in:
parent
ca8111dab0
commit
3514c1e395
|
@ -14,7 +14,7 @@
|
||||||
"@midwayjs/logger": "^2.14.0",
|
"@midwayjs/logger": "^2.14.0",
|
||||||
"@midwayjs/validate": "^3.0.0",
|
"@midwayjs/validate": "^3.0.0",
|
||||||
"@midwayjs/static-file": "^3.0.0",
|
"@midwayjs/static-file": "^3.0.0",
|
||||||
"@midwayjs/redis": "^3.0.0",
|
"@midwayjs/redis": "3.10.13",
|
||||||
"@midwayjs/typeorm": "^3.0.0",
|
"@midwayjs/typeorm": "^3.0.0",
|
||||||
"@midwayjs/upload": "3.10.14",
|
"@midwayjs/upload": "3.10.14",
|
||||||
"mongoose": "^6.0.7",
|
"mongoose": "^6.0.7",
|
||||||
|
|
|
@ -105,10 +105,14 @@ export class UserController {
|
||||||
try {
|
try {
|
||||||
const { phoneNumber: phoneNumbers } = params;
|
const { phoneNumber: phoneNumbers } = params;
|
||||||
const code = Math.floor(Math.random() * 9000 + 1000);
|
const code = Math.floor(Math.random() * 9000 + 1000);
|
||||||
await this.redisService.set('' + phoneNumbers, code, 'EX', 60);
|
const res = await this.redisService.set(
|
||||||
console.log('redis here');
|
'' + phoneNumbers,
|
||||||
await this.smsService.send({ code, phoneNumbers });
|
code,
|
||||||
console.log('sms here');
|
'EX',
|
||||||
|
60
|
||||||
|
);
|
||||||
|
console.log('redis here', res);
|
||||||
|
// await this.smsService.send({ code, phoneNumbers });
|
||||||
return { code: BizCode.OK };
|
return { code: BizCode.OK };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>回溯 - Backset.cn</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="bs-app"></div>
|
<div id="bs-app"></div>
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-router-dom": "6.8.0",
|
"react-router-dom": "6.8.0",
|
||||||
"@arco-design/web-react": "2.45.0",
|
|
||||||
"@ricons/fluent": "0.12.0",
|
"@ricons/fluent": "0.12.0",
|
||||||
"@ricons/utils": "0.1.6",
|
"@ricons/utils": "0.1.6",
|
||||||
"dplayer": "1.27.1",
|
"dplayer": "1.27.1",
|
||||||
|
@ -28,6 +27,7 @@
|
||||||
"vite-tsconfig-paths": "4.0.5",
|
"vite-tsconfig-paths": "4.0.5",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
"vite": "^4.1.0",
|
"vite": "^4.1.0",
|
||||||
"@types/dplayer": "1.25.2"
|
"@types/dplayer": "1.25.2",
|
||||||
|
"@types/identicon.js": "2.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
BIN
apps/web/public/ali256.jpg
Normal file
BIN
apps/web/public/ali256.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
apps/web/public/alipay.jpg
Normal file
BIN
apps/web/public/alipay.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
apps/web/public/wx.jpg
Normal file
BIN
apps/web/public/wx.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
apps/web/public/wx256.jpg
Normal file
BIN
apps/web/public/wx256.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
|
@ -1,6 +1,5 @@
|
||||||
import { Spin } from "@arco-design/web-react";
|
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { Route, Routes, useNavigate } from "react-router-dom";
|
import { Route, Routes } from "react-router-dom";
|
||||||
import "./assets/base.less";
|
import "./assets/base.less";
|
||||||
import Nav from "./components/Nav";
|
import Nav from "./components/Nav";
|
||||||
import { commonRouters, lazyRouters } from "./router";
|
import { commonRouters, lazyRouters } from "./router";
|
||||||
|
@ -24,7 +23,7 @@ function App() {
|
||||||
key={router.path}
|
key={router.path}
|
||||||
path={router.path}
|
path={router.path}
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={<Spin />}>
|
<Suspense fallback={"loading"}>
|
||||||
<Guard>{<router.element />}</Guard>
|
<Guard>{<router.element />}</Guard>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
// import { message } from "antd";
|
// import { message } from "antd";
|
||||||
import { Message } from "@arco-design/web-react";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useLoginModalStore } from "../store/modal.store";
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
baseURL: "",
|
baseURL: "",
|
||||||
|
@ -13,7 +11,6 @@ const instance = axios.create(config);
|
||||||
|
|
||||||
instance.interceptors.request.use(
|
instance.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
console.log(config);
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
@ -27,7 +24,7 @@ instance.interceptors.response.use(
|
||||||
// midwayjs校验
|
// midwayjs校验
|
||||||
if ("success" in response.data) {
|
if ("success" in response.data) {
|
||||||
const { success, message } = response.data;
|
const { success, message } = response.data;
|
||||||
if (!success) return Message.error(message);
|
if (!success) return console.error(message);
|
||||||
}
|
}
|
||||||
// 业务校验
|
// 业务校验
|
||||||
const { msg, code } = response.data;
|
const { msg, code } = response.data;
|
||||||
|
@ -39,7 +36,7 @@ instance.interceptors.response.use(
|
||||||
// Message.error(`接口: ${response.config.url}, 遇到错误`);
|
// Message.error(`接口: ${response.config.url}, 遇到错误`);
|
||||||
break;
|
break;
|
||||||
case 40000:
|
case 40000:
|
||||||
Message.error(msg);
|
console.error(msg);
|
||||||
// console.log('登录')
|
// console.log('登录')
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,5 +1,23 @@
|
||||||
@import "normalize.css";
|
@import "normalize.css";
|
||||||
@import "@arco-design/web-react/dist/css/arco.css";
|
// @import "@arco-design/web-react/dist/css/arco.css";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-primary-1: #006eed;
|
||||||
|
--color-primary-2: #54a3ff;
|
||||||
|
--color-fill-1: #f7f8fa;
|
||||||
|
--color-fill-2: #f2f3f5;
|
||||||
|
--color-fill-3: #e5e6eb;
|
||||||
|
--color-fill-4: #c9cdd4;
|
||||||
|
--color-border-1: #f2f3f5;
|
||||||
|
--color-border-2: #e5e6eb;
|
||||||
|
--color-border-3: #c9cdd4;
|
||||||
|
--color-border-4: #86909c;
|
||||||
|
--color-text-1: #1d2129;
|
||||||
|
--color-text-2: #4e5969;
|
||||||
|
--color-text-3: #86909c;
|
||||||
|
--color-text-4: #c9cdd4;
|
||||||
|
--color-text-5: #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -46,17 +64,17 @@ input {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-shadow {
|
.bs {
|
||||||
box-shadow: rgb(0 0 0 / 13%) 0px 2px 4px 0px, rgb(0 0 0 / 11%) 0px 1px 1px 0px;
|
&.shadow {
|
||||||
|
box-shadow: rgb(0 0 0 / 13%) 0px 2px 4px 0px,
|
||||||
|
rgb(0 0 0 / 11%) 0px 1px 1px 0px;
|
||||||
}
|
}
|
||||||
|
&.ellipsis {
|
||||||
.bs-ellipsis {
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
&.scrollbar {
|
||||||
.bs-scrollbar {
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
|
@ -68,3 +86,123 @@ input {
|
||||||
background-color: var(--color-text-4);
|
background-color: var(--color-text-4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.fc {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
&.c {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
&.sb {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
&.fe {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mr6 {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ml6 {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ml12 {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mr12 {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mr24 {
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mt12 {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mt24 {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.br3 {
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.br6 {
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tip {
|
||||||
|
position: relative;
|
||||||
|
&:hover {
|
||||||
|
&::after {
|
||||||
|
animation-name: tooltip-appear;
|
||||||
|
animation-duration: 0.1s;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
position: absolute;
|
||||||
|
content: attr(data-tip);
|
||||||
|
opacity: 0;
|
||||||
|
top: 100%;
|
||||||
|
right: 50%;
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 0.5em 0.75em;
|
||||||
|
z-index: 1000000;
|
||||||
|
background: var(--color-text-1);
|
||||||
|
color: var(--color-text-5);
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 3px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
text-align: center;
|
||||||
|
white-space: pre;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.badge {
|
||||||
|
position: relative;
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -2.5px;
|
||||||
|
left: -2.5px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-image: linear-gradient(#54a3ff, #006eed);
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 2px solid #24292f;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.btn {
|
||||||
|
background-color: rgb(36, 41, 47);
|
||||||
|
color: #fff;
|
||||||
|
border: 0;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
&:active {
|
||||||
|
background: rgba(0, 0, 0, 1);
|
||||||
|
}
|
||||||
|
&.outline {
|
||||||
|
color: rgb(36, 41, 47);
|
||||||
|
border: 1px solid rgb(36, 41, 47);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes tooltip-appear {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
import { Card } from "@arco-design/web-react";
|
|
||||||
import { MouseEventHandler } from "react";
|
|
||||||
import "./index.less";
|
|
||||||
const { Meta } = Card;
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
imgUrl: string;
|
|
||||||
title: string;
|
|
||||||
meta?: {
|
|
||||||
desc?: string;
|
|
||||||
action?: string;
|
|
||||||
};
|
|
||||||
styles?: {};
|
|
||||||
onClick?: MouseEventHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
function BsCard(props: IProps) {
|
|
||||||
const { imgUrl, title, meta, styles, ...rest } = props;
|
|
||||||
return meta ? (
|
|
||||||
<Card
|
|
||||||
{...rest}
|
|
||||||
className="bs-card"
|
|
||||||
hoverable
|
|
||||||
style={{ ...styles }}
|
|
||||||
cover={
|
|
||||||
<div
|
|
||||||
className="cover"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url('${imgUrl}')`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="mask">
|
|
||||||
<p>{title}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Meta
|
|
||||||
description={
|
|
||||||
<div className="bottom-des">
|
|
||||||
<span className="bs-ellipsis">{meta?.desc}</span>
|
|
||||||
<a>{meta?.action}</a>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
) : (
|
|
||||||
<Card {...rest} className="bs-card mini" hoverable style={{ ...styles }}>
|
|
||||||
<div
|
|
||||||
className="cover"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url('${imgUrl}')`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="mask">
|
|
||||||
<p>{title}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BsCard;
|
|
|
@ -11,8 +11,4 @@
|
||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
> svg {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
import { Tooltip } from "@arco-design/web-react";
|
|
||||||
|
|
||||||
export const Footer = () => {
|
export const Footer = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -11,22 +10,6 @@ export const Footer = () => {
|
||||||
ICP证: 苏ICP备19008833号-5
|
ICP证: 苏ICP备19008833号-5
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<Tooltip position="top" trigger="hover" content="x-arctanx">
|
|
||||||
<svg viewBox="0 0 32 32">
|
|
||||||
<path
|
|
||||||
d="M27.086 24.78A6.618 6.618 0 0 0 30 19.465c0-3.88-3.776-7.027-8.434-7.027s-8.434 3.147-8.434 7.027s3.777 7.028 8.434 7.028a9.955 9.955 0 0 0 2.754-.385l.247-.037a.892.892 0 0 1 .448.13l1.847 1.066l.162.053a.281.281 0 0 0 .281-.282l-.045-.205l-.38-1.417l-.03-.18a.56.56 0 0 1 .236-.458zM12.12 4.68C6.53 4.68 2 8.455 2 13.114a7.939 7.939 0 0 0 3.497 6.374a.671.671 0 0 1 .283.55l-.035.215l-.456 1.701l-.055.246a.338.338 0 0 0 .337.338l.196-.063l2.216-1.28a1.058 1.058 0 0 1 .536-.155l.298.044a11.967 11.967 0 0 0 3.304.464l.555-.014a6.515 6.515 0 0 1-.34-2.067c0-4.247 4.133-7.691 9.23-7.691l.55.014c-.762-4.029-4.947-7.11-9.995-7.11zm6.633 13.663a1.125 1.125 0 1 1 1.125-1.125a1.124 1.124 0 0 1-1.125 1.125zm5.624 0a1.125 1.125 0 1 1 1.123-1.125a1.125 1.125 0 0 1-1.123 1.125zm-15.631-6.58a1.35 1.35 0 1 1 1.35-1.348a1.349 1.349 0 0 1-1.35 1.349zm6.747 0a1.35 1.35 0 1 1 1.35-1.348a1.349 1.349 0 0 1-1.35 1.349z"
|
|
||||||
fill="currentColor"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip position="top" trigger="hover" content="1141143000">
|
|
||||||
<svg viewBox="0 0 1024 1024">
|
|
||||||
<path
|
|
||||||
d="M824.8 613.2c-16-51.4-34.4-94.6-62.7-165.3C766.5 262.2 689.3 112 511.5 112C331.7 112 256.2 265.2 261 447.9c-28.4 70.8-46.7 113.7-62.7 165.3c-34 109.5-23 154.8-14.6 155.8c18 2.2 70.1-82.4 70.1-82.4c0 49 25.2 112.9 79.8 159c-26.4 8.1-85.7 29.9-71.6 53.8c11.4 19.3 196.2 12.3 249.5 6.3c53.3 6 238.1 13 249.5-6.3c14.1-23.8-45.3-45.7-71.6-53.8c54.6-46.2 79.8-110.1 79.8-159c0 0 52.1 84.6 70.1 82.4c8.5-1.1 19.5-46.4-14.5-155.8z"
|
|
||||||
fill="currentColor"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</Tooltip>
|
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,8 +51,6 @@ header {
|
||||||
}
|
}
|
||||||
|
|
||||||
.end {
|
.end {
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: 32px;
|
margin-left: 32px;
|
||||||
|
@ -61,10 +59,64 @@ header {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.user {
|
.profile {
|
||||||
font-size: 12px;
|
.avatar-ddl {
|
||||||
> div {
|
position: relative;
|
||||||
line-height: 1;
|
z-index: 19940121;
|
||||||
|
.ddl {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
background: #fff;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||||
|
margin-top: 11px;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
text-align: left;
|
||||||
|
min-width: 120px;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: 13px;
|
||||||
|
div {
|
||||||
|
padding: 6px 12px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
border-bottom: 1px solid var(--color-border-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
padding-top: 8px;
|
||||||
|
div {
|
||||||
|
padding: 6px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
background: var(--color-fill-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.avatar,
|
||||||
|
.alert {
|
||||||
|
cursor: pointer;
|
||||||
|
img {
|
||||||
|
margin-right: 2px;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
svg {
|
||||||
|
color: var(--color-text-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,77 +2,39 @@ import "./index.less";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { menuRouters } from "../../router";
|
import { menuRouters } from "../../router";
|
||||||
import { useUserStore } from "../../store/user.store";
|
import { useUserStore } from "../../store/user.store";
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
Button,
|
|
||||||
Dropdown,
|
|
||||||
Link,
|
|
||||||
Menu,
|
|
||||||
Space,
|
|
||||||
Tooltip,
|
|
||||||
} from "@arco-design/web-react";
|
|
||||||
import {
|
|
||||||
IconDown,
|
|
||||||
IconImport,
|
|
||||||
IconSettings,
|
|
||||||
} from "@arco-design/web-react/icon";
|
|
||||||
import Identicon from "identicon.js";
|
import Identicon from "identicon.js";
|
||||||
|
import { Icon } from "@ricons/utils";
|
||||||
const vip1 = (
|
import CaretDown20Filled from "@ricons/fluent/CaretDown20Filled";
|
||||||
<svg viewBox="0 0 1024 1024" width="20" height="20" fill="currentColor">
|
import Alert20Regular from "@ricons/fluent/Alert20Regular";
|
||||||
<path d="M849.2 6.3H174.8C81.7 6.3 6.3 81.7 6.3 174.8v674.3c0 93.1 75.4 168.6 168.6 168.6h674.3c93.1 0 168.6-75.4 168.6-168.6V175c0-93.1-75.5-168.7-168.6-168.7z m-81 632.8l7.9-44.9 47.3-30.5h42.2l-52 295h-42.2l44.1-250.3-47.3 30.7z m104.7-385.2v44h-42.7l-536.6 552h-159V297.3l-39.8-0.6v-42.8h158.9v401.2l397.4-401.2h221.8z"></path>
|
import DoorArrowRight20Regular from "@ricons/fluent/DoorArrowRight20Regular";
|
||||||
</svg>
|
import Settings20Regular from "@ricons/fluent/Settings20Regular";
|
||||||
);
|
import PremiumPerson20Regular from "@ricons/fluent/PremiumPerson20Regular";
|
||||||
|
import { useState } from "react";
|
||||||
const vip2 = (
|
import { useMount } from "../../hook";
|
||||||
<svg viewBox="0 0 1024 1024" width="20" height="20" fill="currentColor">
|
|
||||||
<path d="M849.2 6.3H174.8C81.7 6.3 6.3 81.7 6.3 174.8v674.3c0 93.1 75.4 168.6 168.6 168.6h674.3c93.1 0 168.6-75.4 168.6-168.6V175c0-93.1-75.5-168.7-168.6-168.7z m23.7 247.6v44h-42.7l-536.6 552h-159V297.3l-39.8-0.6v-42.8h158.9v401.2l397.4-401.2h221.8zM711.4 818.8l140.5-144.1c5.4-5.8 9.5-11.2 12-16.2 1.9-4.7 3.2-9.1 4-13.3 2-11.1 0.2-20.7-5.2-28.9-5.7-8.3-15.1-12.5-28.2-12.7-11.6 0-21.9 3.7-30.9 11.1-9.3 7.4-15.8 17.9-19.6 31.4h-42.1c5.4-24.6 17.2-44.8 35.4-60.9 18.3-15.7 38.8-23.6 61.5-23.8 25.3 0.2 44.5 8.4 57.4 24.3 13.2 15.8 17.9 35.8 13.8 60.1-3.5 19.4-12.3 36.7-26.9 51.7L767.3 816.4H880l-7.4 42.2H704.3l7.1-39.8z"></path>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
const vip3 = (
|
|
||||||
<svg viewBox="0 0 1024 1024" width="20" height="20" fill="currentColor">
|
|
||||||
<path d="M849.2 6.3H174.8C81.7 6.3 6.3 81.7 6.3 174.8v674.3c0 93.1 75.4 168.6 168.6 168.6h674.3c93.1 0 168.6-75.4 168.6-168.6V175c0-93.1-75.5-168.7-168.6-168.7z m23.7 247.6v44h-42.7l-536.6 552h-159V297.3l-39.8-0.6v-42.8h158.9v401.2l397.4-401.2h221.8z m-78.9 434h14.6c14.4 0 26.3-4 35.7-12 9.3-7.7 15.1-17.7 17.2-29.9 2-12.3-0.4-22.6-6.9-30.5-6.5-7.7-16.2-11.6-28.6-12-9 0-17.8 3-26.2 8.6-8.9 5.8-15.7 14.8-20.5 26.9h-42.2c6.1-21.9 18.2-40.3 36.3-55.2 17.9-14.8 37.4-22.3 58.7-22.7 26.8 0.2 46.6 8.9 59.4 25.7 12.2 16.4 16.4 35.9 12.8 58.3-2 11.4-5.9 22.2-11.9 32.7-6.5 10.5-16.7 19.9-30.3 28.2 11 8.1 18.3 17.8 21.7 29.1 3.2 11.4 3.8 23.2 1.6 35.6-5.2 27.7-17.5 49.4-37.2 65.3-19.5 16.3-42.2 24.6-68.3 24.9-20.1 0-36.8-6.4-50-19.4-13.2-13-20-31.7-20.3-56.3h42.2c2.1 9.9 5.9 17.8 11.7 23.8 5.9 6.3 14.7 9.5 26.3 9.5 12.3 0 23.6-4.2 33.6-12.7 10-7.9 16.4-19.3 19.4-34 2.3-14.9 0-26.5-7.2-34.8-7-7.9-17.2-12-30.3-12h-17.8l6.5-37.1z"></path>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
const iconStyle = {
|
|
||||||
marginRight: 8,
|
|
||||||
fontSize: 16,
|
|
||||||
transform: "translateY(1px)",
|
|
||||||
};
|
|
||||||
|
|
||||||
function Nav() {
|
function Nav() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const user = useUserStore((s: any) => s.user);
|
const user = useUserStore((s: any) => s.user);
|
||||||
const exit = useUserStore((s: any) => s.userExit);
|
const exit = useUserStore((s: any) => s.userExit);
|
||||||
|
const [profileDropdownVisible, setProfileDropdownVisible] = useState(false);
|
||||||
|
|
||||||
// user.user_avatar = `data:image/png;base64,${new Identicon(h).toString()}`;
|
useMount(() => {
|
||||||
|
document.addEventListener("click", (e) => setProfileDropdownVisible(false));
|
||||||
|
});
|
||||||
|
|
||||||
const onClickMenuItem = (key: string) => {
|
const onClickProfileItem = (command: string) => {
|
||||||
switch (key) {
|
switch (command) {
|
||||||
case "exit":
|
case "exit":
|
||||||
exit();
|
exit();
|
||||||
break;
|
break;
|
||||||
case "setting":
|
case "sub":
|
||||||
console.log("navigate");
|
console.log(user);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// navigate("/subscribe");
|
||||||
};
|
};
|
||||||
|
|
||||||
const dropList = (
|
|
||||||
<Menu onClickMenuItem={(key) => onClickMenuItem(key)}>
|
|
||||||
<Menu.Item key="exit">
|
|
||||||
<IconImport style={iconStyle} />
|
|
||||||
退出
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item key="setting">
|
|
||||||
<IconSettings style={iconStyle} />
|
|
||||||
设置
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
<nav className="container">
|
<nav className="container">
|
||||||
|
@ -98,40 +60,78 @@ function Nav() {
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="end">
|
<div className="end bs fc fe">
|
||||||
{!user ? (
|
{!user ? (
|
||||||
<span className="btn" onClick={() => navigate("/login")}>
|
<span className="btn" onClick={() => navigate("/login")}>
|
||||||
登录
|
登录
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<Space>
|
<div className="profile bs fc">
|
||||||
<div className="user">
|
|
||||||
<div
|
<div
|
||||||
style={{
|
className="alert bs fc mr12 badge tip"
|
||||||
color: "rgba(255,255,255,.7)",
|
data-tip="你有最新的消息"
|
||||||
fontSize: 13,
|
>
|
||||||
paddingBottom: 4,
|
<Icon size={22} color="var(--color-text-3)">
|
||||||
|
<Alert20Regular />
|
||||||
|
</Icon>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="avatar-ddl bs ml12"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.nativeEvent.stopPropagation();
|
||||||
|
setProfileDropdownVisible(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{user.display_name}
|
<div className="avatar bs fc">
|
||||||
</div>
|
|
||||||
<div style={{ color: "rgba(255,255,255,.4)" }}>
|
|
||||||
{user.user_login}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Dropdown droplist={dropList} trigger="hover" position="br">
|
|
||||||
<Space>
|
|
||||||
<Avatar shape="square" size={32}>
|
|
||||||
<img
|
<img
|
||||||
src={`data:image/png;base64,${new Identicon(
|
src={`data:image/png;base64,${new Identicon(
|
||||||
user.user_avatar
|
user.user_avatar
|
||||||
).toString()}`}
|
).toString()}`}
|
||||||
/>
|
/>
|
||||||
</Avatar>
|
<Icon size={16} color="var(--color-text-3)">
|
||||||
<IconDown />
|
<CaretDown20Filled />
|
||||||
</Space>
|
</Icon>
|
||||||
</Dropdown>
|
</div>
|
||||||
</Space>
|
{profileDropdownVisible && (
|
||||||
|
<div className="ddl">
|
||||||
|
<div className="header">
|
||||||
|
<div className="bs fc sb">
|
||||||
|
<span>Hi!</span>{" "}
|
||||||
|
<span style={{ color: "var(--color-text-2)" }}>
|
||||||
|
{user.display_name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="main">
|
||||||
|
<div
|
||||||
|
className="bs fc sb"
|
||||||
|
onClick={() => onClickProfileItem("sub")}
|
||||||
|
>
|
||||||
|
<span>订阅</span>
|
||||||
|
<Icon color="var(--color-text-3)">
|
||||||
|
<PremiumPerson20Regular />
|
||||||
|
</Icon>
|
||||||
|
</div>
|
||||||
|
<div className="bs fc sb">
|
||||||
|
<span>设置</span>
|
||||||
|
<Icon color="var(--color-text-3)">
|
||||||
|
<Settings20Regular />
|
||||||
|
</Icon>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="bs fc sb"
|
||||||
|
onClick={() => onClickProfileItem("exit")}
|
||||||
|
>
|
||||||
|
<span>退出</span>
|
||||||
|
<Icon color="var(--color-text-3)">
|
||||||
|
<DoorArrowRight20Regular />
|
||||||
|
</Icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
9
apps/web/src/components/Result/index.less
Normal file
9
apps/web/src/components/Result/index.less
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.result {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 100px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
> div {
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
23
apps/web/src/components/Result/index.tsx
Normal file
23
apps/web/src/components/Result/index.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import "./index.less";
|
||||||
|
import FlashOff24Regular from "@ricons/fluent/FlashOff24Regular";
|
||||||
|
import { Icon } from "@ricons/utils";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function Result() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container result">
|
||||||
|
<Icon size={48} color="var(--color-text-3)">
|
||||||
|
<FlashOff24Regular />
|
||||||
|
</Icon>
|
||||||
|
<div className="mt12">访问被禁止</div>
|
||||||
|
<button
|
||||||
|
className="bs btn br3 mt24"
|
||||||
|
onClick={() => navigate("/subscribe")}
|
||||||
|
>
|
||||||
|
订阅课程
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import { Button, Result } from "@arco-design/web-react";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { useUserStore } from "../store/user.store";
|
import { useUserStore } from "../store/user.store";
|
||||||
|
import Result from "../components/Result";
|
||||||
|
|
||||||
interface IGuardProps {
|
interface IGuardProps {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
|
@ -15,30 +15,14 @@ export const Guard = (props: IGuardProps) => {
|
||||||
const user = useUserStore((s: any) => s.user);
|
const user = useUserStore((s: any) => s.user);
|
||||||
const fetchUser = useUserStore((s: any) => s.fetchUser);
|
const fetchUser = useUserStore((s: any) => s.fetchUser);
|
||||||
|
|
||||||
const Result403 = (
|
|
||||||
<div style={{ paddingTop: 100 }}>
|
|
||||||
<Result
|
|
||||||
status="403"
|
|
||||||
subTitle="无权访问,请登录"
|
|
||||||
extra={
|
|
||||||
<Button type="primary" onClick={() => navigate("/login")}>
|
|
||||||
登录
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const sign = Cookies.get("_sign_web");
|
const sign = Cookies.get("_sign_web");
|
||||||
const needAuth = needAuthList.some((p) => location.pathname.indexOf(p) > -1);
|
const needAuth = needAuthList.some((p) => location.pathname.indexOf(p) > -1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(user);
|
|
||||||
console.log("location.pathname changed 拦截", location.pathname);
|
|
||||||
if (!user) fetchUser();
|
if (!user) fetchUser();
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
if (!sign && needAuth) return Result403;
|
if (!sign && needAuth) return <Result />;
|
||||||
return props.children;
|
return props.children;
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const lazyRouters: IRoute[] = [
|
||||||
{
|
{
|
||||||
path: "/subscribe",
|
path: "/subscribe",
|
||||||
element: lazy(() => import("../view/Subscribe")),
|
element: lazy(() => import("../view/Subscribe")),
|
||||||
name: "订阅",
|
name: "订阅课程",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/course/detail/:id",
|
path: "/course/detail/:id",
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import Cookie from "js-cookie";
|
import Cookie from "js-cookie";
|
||||||
import { userState } from "../api";
|
import { userState } from "../api";
|
||||||
import { Message } from "@arco-design/web-react";
|
|
||||||
|
|
||||||
export const useUserStore = create((set) => {
|
export const useUserStore = create((set) => {
|
||||||
return {
|
return {
|
||||||
|
@ -19,7 +18,6 @@ export const useUserStore = create((set) => {
|
||||||
set({ user: null });
|
set({ user: null });
|
||||||
Cookie.remove("_sign_web");
|
Cookie.remove("_sign_web");
|
||||||
Cookie.remove("_sign_web.sig");
|
Cookie.remove("_sign_web.sig");
|
||||||
Message.success("拜拜~");
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
3
apps/web/src/view/Blog/index.tsx
Normal file
3
apps/web/src/view/Blog/index.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function Blog() {
|
||||||
|
return <div className="blog-list">blogs</div>;
|
||||||
|
}
|
|
@ -1,14 +1,5 @@
|
||||||
.bs-card {
|
.course-card {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.arco-card-body {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mini {
|
|
||||||
.arco-card-body {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.cover {
|
.cover {
|
||||||
|
@ -18,7 +9,6 @@
|
||||||
|
|
||||||
.cover {
|
.cover {
|
||||||
transition: background-size 0.25s linear;
|
transition: background-size 0.25s linear;
|
||||||
height: 164px;
|
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 100%;
|
background-size: 100%;
|
||||||
|
@ -43,20 +33,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-des {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-size: 13px;
|
|
||||||
span {
|
|
||||||
flex: 1;
|
|
||||||
color: var(--color-text-3);
|
|
||||||
padding-right: 40px;
|
|
||||||
max-width: 160px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
cursor: pointer;
|
|
||||||
color: rgb(var(--primary-6));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
28
apps/web/src/view/Course/components/Card/index.tsx
Normal file
28
apps/web/src/view/Course/components/Card/index.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { MouseEventHandler } from "react";
|
||||||
|
import "./index.less";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
imgUrl: string;
|
||||||
|
title: string;
|
||||||
|
styles?: {};
|
||||||
|
onClick?: MouseEventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Card(props: IProps) {
|
||||||
|
const { imgUrl, title, styles, ...rest } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="course-card" {...rest} style={{ ...styles }}>
|
||||||
|
<div
|
||||||
|
className="cover"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('${imgUrl}')`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="mask">
|
||||||
|
<p className="bs ellipsis">{title}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import BsCard from "../../../../components/Card";
|
import Card from "../Card";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
|
||||||
export const recommendListDefault = [
|
export const recommendListDefault = [
|
||||||
|
@ -38,7 +38,7 @@ function Recommends() {
|
||||||
return (
|
return (
|
||||||
<div className="recommends">
|
<div className="recommends">
|
||||||
{recommendList.map((item, index) => (
|
{recommendList.map((item, index) => (
|
||||||
<BsCard {...item} key={index} />
|
<Card {...item} key={index} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
grid-column-gap: 10px;
|
grid-column-gap: 10px;
|
||||||
grid-row-gap: 10px;
|
grid-row-gap: 10px;
|
||||||
|
|
||||||
.bs-card {
|
.course-card {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
.cover {
|
.cover {
|
||||||
height: 120px;
|
height: 120px;
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
}
|
}
|
||||||
&.tab {
|
&.tab {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
.bs-card {
|
.course-card {
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
.cover {
|
.cover {
|
||||||
height: 330px;
|
height: 330px;
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
import { Space, Tooltip, Dropdown, Button, Menu } from "@arco-design/web-react";
|
|
||||||
import BsCard from "../../components/Card";
|
|
||||||
import Timeline, { IOnScrollParam } from "./components/Timeline";
|
import Timeline, { IOnScrollParam } from "./components/Timeline";
|
||||||
import Tab20Regular from "@ricons/fluent/Tab20Regular";
|
import Tab20Regular from "@ricons/fluent/Tab20Regular";
|
||||||
import Table20Regular from "@ricons/fluent/Table20Regular";
|
import Table20Regular from "@ricons/fluent/Table20Regular";
|
||||||
import Filter20Regular from "@ricons/fluent/Filter20Regular";
|
import { useRef, useState } from "react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
// import { courseTimeListDefault } from "./mock";
|
|
||||||
import { Icon } from "@ricons/utils";
|
import { Icon } from "@ricons/utils";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useMount } from "../../hook";
|
import { useMount } from "../../hook";
|
||||||
import { getCourseList } from "../../api";
|
import { getCourseList } from "../../api";
|
||||||
import { processTime } from "./util";
|
import { processTime } from "./util";
|
||||||
import { Footer } from "../../components/Footer";
|
import { Footer } from "../../components/Footer";
|
||||||
|
import Card from "./components/Card";
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -22,21 +19,7 @@ export default function Index() {
|
||||||
top: -4,
|
top: -4,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dropList = (
|
|
||||||
<Menu>
|
|
||||||
<Menu.Item key="asec">升序</Menu.Item>
|
|
||||||
<Menu.Item key="desc">降序</Menu.Item>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
const [actions, setActions] = useState([
|
const [actions, setActions] = useState([
|
||||||
{
|
|
||||||
key: "tab",
|
|
||||||
icon: <Tab20Regular />,
|
|
||||||
active: false,
|
|
||||||
tip: "使用大缩略图显示单个项目",
|
|
||||||
gridClass: "tab",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "table",
|
key: "table",
|
||||||
icon: <Table20Regular />,
|
icon: <Table20Regular />,
|
||||||
|
@ -44,6 +27,13 @@ export default function Index() {
|
||||||
tip: "列出更多项目",
|
tip: "列出更多项目",
|
||||||
gridClass: "table",
|
gridClass: "table",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "tab",
|
||||||
|
icon: <Tab20Regular />,
|
||||||
|
active: false,
|
||||||
|
tip: "使用大缩略图显示单个项目",
|
||||||
|
gridClass: "tab",
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useMount(() => {
|
useMount(() => {
|
||||||
|
@ -87,38 +77,24 @@ export default function Index() {
|
||||||
return (
|
return (
|
||||||
<div className="container course">
|
<div className="container course">
|
||||||
<div className="action-bar">
|
<div className="action-bar">
|
||||||
<Space>
|
|
||||||
{actions.map((action) => (
|
{actions.map((action) => (
|
||||||
<Tooltip key={action.key} content={action.tip}>
|
<span
|
||||||
<Button
|
key={action.key}
|
||||||
type="text"
|
style={{ cursor: "pointer" }}
|
||||||
onClick={() => onClickActionItem(action)}
|
onClick={() => onClickActionItem(action)}
|
||||||
icon={
|
className="bs tip ml6"
|
||||||
|
data-tip={action.tip}
|
||||||
|
>
|
||||||
<Icon
|
<Icon
|
||||||
size={20}
|
size={20}
|
||||||
color={
|
color={
|
||||||
action.active
|
action.active ? "var(--color-text-2)" : "var(--color-text-4)"
|
||||||
? "rgb(var(--primary-6))"
|
|
||||||
: "var(--color-text-2)"
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{action.icon}
|
{action.icon}
|
||||||
</Icon>
|
</Icon>
|
||||||
}
|
</span>
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
))}
|
))}
|
||||||
<Dropdown droplist={dropList} position="bl">
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={
|
|
||||||
<Icon size={20} color="var(--color-text-2)">
|
|
||||||
<Filter20Regular />
|
|
||||||
</Icon>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Dropdown>
|
|
||||||
</Space>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="thumbnail-timeline">
|
<div className="thumbnail-timeline">
|
||||||
<div className="thumbnail-container">
|
<div className="thumbnail-container">
|
||||||
|
@ -133,7 +109,7 @@ export default function Index() {
|
||||||
className={`grid ${actions.find((a) => a.active)?.gridClass}`}
|
className={`grid ${actions.find((a) => a.active)?.gridClass}`}
|
||||||
>
|
>
|
||||||
{item.data.map((d: any) => (
|
{item.data.map((d: any) => (
|
||||||
<BsCard
|
<Card
|
||||||
onClick={() => onClickCourseItem(d)}
|
onClick={() => onClickCourseItem(d)}
|
||||||
key={d.course_id}
|
key={d.course_id}
|
||||||
imgUrl={d.course_cover_url}
|
imgUrl={d.course_cover_url}
|
||||||
|
|
|
@ -1,37 +1,53 @@
|
||||||
.course-detail {
|
.course-detail {
|
||||||
padding-top: 60px;
|
|
||||||
|
|
||||||
.table-of-content {
|
.table-of-content {
|
||||||
|
position: fixed;
|
||||||
|
top: 60px;
|
||||||
|
bottom: 0;
|
||||||
|
width: 300px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
border-right: 1px solid var(--color-border-2);
|
||||||
|
|
||||||
> h2 {
|
> h2 {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc {
|
.toc {
|
||||||
|
padding-top: 20px;
|
||||||
.level-1 {
|
.level-1 {
|
||||||
color: var(--color-text-4);
|
color: var(--color-text-4);
|
||||||
padding: 20px 0 5px 0;
|
line-height: 1.5;
|
||||||
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
.level-2 {
|
.level-2 {
|
||||||
display: grid;
|
padding: 5px 0;
|
||||||
padding-left: 10px;
|
display: flex;
|
||||||
line-height: 24px;
|
align-items: center;
|
||||||
grid-template-columns: 9fr 1fr;
|
justify-content: space-between;
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
color: rgb(var(--primary-4));
|
color: var(--color-primary-1);
|
||||||
|
}
|
||||||
|
.xicon {
|
||||||
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
.time {
|
.time {
|
||||||
color: var(--color-text-4);
|
color: var(--color-text-4);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 100px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.content {
|
||||||
|
position: fixed;
|
||||||
|
left: 300px;
|
||||||
|
right: 0;
|
||||||
|
top: 60px;
|
||||||
|
bottom: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
import { ResizeBox, Space, Result, Button } from "@arco-design/web-react";
|
|
||||||
import Guide from "./components/Guide";
|
import Guide from "./components/Guide";
|
||||||
import { useMount } from "../../hook";
|
import { useMount } from "../../hook";
|
||||||
import Player from "./components/DPlayer";
|
import Player from "./components/DPlayer";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { getChapterGuideById } from "../../api";
|
import { getChapterGuideById } from "../../api";
|
||||||
import { ms2Time } from "./util";
|
import { ms2Time } from "./util";
|
||||||
|
import Result from "../../components/Result";
|
||||||
|
import PlayCircle20Regular from "@ricons/fluent/PlayCircle20Regular";
|
||||||
|
import BookLetter20Regular from "@ricons/fluent/BookLetter20Regular";
|
||||||
|
import { Icon } from "@ricons/utils";
|
||||||
|
|
||||||
function CourseDetail() {
|
function CourseDetail() {
|
||||||
const { id: course_id = "" } = useParams();
|
const { id: course_id = "" } = useParams();
|
||||||
|
@ -22,6 +25,11 @@ function CourseDetail() {
|
||||||
title: item.chapter_title,
|
title: item.chapter_title,
|
||||||
level: +item.chapter_level,
|
level: +item.chapter_level,
|
||||||
time: ms2Time(+item.media_time),
|
time: ms2Time(+item.media_time),
|
||||||
|
icon: !!item.media_url ? (
|
||||||
|
<Icon size={20}>
|
||||||
|
<PlayCircle20Regular />
|
||||||
|
</Icon>
|
||||||
|
) : null,
|
||||||
active: false,
|
active: false,
|
||||||
view: (
|
view: (
|
||||||
<Player
|
<Player
|
||||||
|
@ -54,32 +62,17 @@ function CourseDetail() {
|
||||||
setToc((t: any) =>
|
setToc((t: any) =>
|
||||||
t.map((p: any) => ({ ...p, active: i.title === p.title }))
|
t.map((p: any) => ({ ...p, active: i.title === p.title }))
|
||||||
);
|
);
|
||||||
setView(
|
setView(i.view ?? <Result />);
|
||||||
i.view ?? (
|
|
||||||
<Result
|
|
||||||
status="403"
|
|
||||||
subTitle="无权访问"
|
|
||||||
extra={<Button type="text">订阅</Button>}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="course-detail">
|
<div className="course-detail">
|
||||||
<ResizeBox.Split
|
|
||||||
direction="horizontal"
|
|
||||||
style={{ height: "calc(100vh - 60px)" }}
|
|
||||||
max={0.9}
|
|
||||||
min={0.1}
|
|
||||||
size={0.2}
|
|
||||||
panes={[
|
|
||||||
<aside className="table-of-content">
|
<aside className="table-of-content">
|
||||||
<h2>云顶计划:K线</h2>
|
<h2>云顶计划:K线</h2>
|
||||||
<div>
|
<div>
|
||||||
<Space style={{ color: "var(--color-text-3)" }}>
|
<div style={{ color: "var(--color-text-3)" }}>
|
||||||
<span>2023年3月2日</span>
|
<span>2023年3月2日</span>
|
||||||
</Space>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="toc">
|
<div className="toc">
|
||||||
{toc.map((i: any) => {
|
{toc.map((i: any) => {
|
||||||
|
@ -96,17 +89,18 @@ function CourseDetail() {
|
||||||
key={i.title}
|
key={i.title}
|
||||||
onClick={() => onclickItem(i)}
|
onClick={() => onclickItem(i)}
|
||||||
>
|
>
|
||||||
<span className="bs-ellipsis">{i.title}</span>
|
<div className="bs ellipsis fc">
|
||||||
|
{i.icon}
|
||||||
|
<span>{i.title}</span>
|
||||||
|
</div>
|
||||||
<span className="time">{i.time}</span>
|
<span className="time">{i.time}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</aside>,
|
</aside>
|
||||||
view,
|
<div className="content">{view}</div>
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,6 @@ export const ms2Time = (time: number) => {
|
||||||
const mind = time % (60 * 60);
|
const mind = time % (60 * 60);
|
||||||
const minutes = Math.floor(mind / 60);
|
const minutes = Math.floor(mind / 60);
|
||||||
const seconds = Math.ceil(mind % 60);
|
const seconds = Math.ceil(mind % 60);
|
||||||
return minutes + "分" + seconds + "秒";
|
const prefix = (m: number) => (m < 10 ? "0" + m : m);
|
||||||
|
return prefix(minutes) + ":" + prefix(seconds);
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,22 +34,20 @@
|
||||||
color: rgba(0, 0, 0, 0.4);
|
color: rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sms-btn {
|
||||||
|
min-width: 40px;
|
||||||
|
text-align: right;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn {
|
.submit-btn {
|
||||||
margin-top: 1rem;
|
margin-top: 2rem;
|
||||||
background-color: rgb(36, 41, 47);
|
|
||||||
color: #fff;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
|
||||||
letter-spacing: 10px;
|
letter-spacing: 10px;
|
||||||
transition: all 0.25s ease;
|
|
||||||
&:active {
|
|
||||||
background: rgba(0, 0, 0, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sms-group {
|
.sms-group {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { sms, userLogin } from "../../api";
|
import { sms, userLogin } from "../../api";
|
||||||
import { Message, Button, Space } from "@arco-design/web-react";
|
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useUserStore } from "../../store/user.store";
|
import { useUserStore } from "../../store/user.store";
|
||||||
|
@ -20,11 +19,11 @@ export function Login() {
|
||||||
|
|
||||||
const onClickSmsBtn = () => {
|
const onClickSmsBtn = () => {
|
||||||
if (!/^1[3456789]\d{9}$/.test(loginForm.user_login))
|
if (!/^1[3456789]\d{9}$/.test(loginForm.user_login))
|
||||||
return Message.error("手机号格式有误");
|
return alert("手机号格式有误");
|
||||||
sms(loginForm.user_login).then((res: any) => {
|
sms(loginForm.user_login).then((res: any) => {
|
||||||
if (res?.code === 10000)
|
if (res?.code === 10000)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Message.success("验证码已发送");
|
alert("验证码已发送");
|
||||||
timer.current = setInterval(() => setCountdown(countdown--), 1000);
|
timer.current = setInterval(() => setCountdown(countdown--), 1000);
|
||||||
}, 50);
|
}, 50);
|
||||||
});
|
});
|
||||||
|
@ -34,11 +33,11 @@ export function Login() {
|
||||||
userLogin(loginForm).then((res: any) => {
|
userLogin(loginForm).then((res: any) => {
|
||||||
const { code, data, msg } = res;
|
const { code, data, msg } = res;
|
||||||
if (code === 10000) {
|
if (code === 10000) {
|
||||||
Message.success(msg);
|
console.log(msg);
|
||||||
navigate(-1);
|
navigate(-1);
|
||||||
setUser(data);
|
setUser(data);
|
||||||
}
|
}
|
||||||
if (code === 20000) Message.error(msg);
|
if (code === 20000) console.error(msg);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -65,17 +64,6 @@ export function Login() {
|
||||||
<h4>欢迎,Backset!</h4>
|
<h4>欢迎,Backset!</h4>
|
||||||
<div style={{ width: 320 }}>
|
<div style={{ width: 320 }}>
|
||||||
<div className="form">
|
<div className="form">
|
||||||
{/* <input
|
|
||||||
type="text"
|
|
||||||
className="input"
|
|
||||||
placeholder="神秘代码"
|
|
||||||
onChange={(e) =>
|
|
||||||
setLoginForm((p) => ({
|
|
||||||
...p,
|
|
||||||
xcode: e.target.value,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/> */}
|
|
||||||
<input
|
<input
|
||||||
style={{ marginTop: "1rem" }}
|
style={{ marginTop: "1rem" }}
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -118,16 +106,15 @@ export function Login() {
|
||||||
}
|
}
|
||||||
placeholder="验证码"
|
placeholder="验证码"
|
||||||
/>
|
/>
|
||||||
<Button
|
<span
|
||||||
style={{ marginLeft: 12, minWidth: 60 }}
|
className="bs btn ml12 sms-btn br3 outline"
|
||||||
type="text"
|
|
||||||
onClick={onClickSmsBtn}
|
onClick={onClickSmsBtn}
|
||||||
>
|
>
|
||||||
{countdown === DURATION ? "获取" : countdown + "s"}
|
{countdown === DURATION ? "获取" : countdown + "s"}
|
||||||
</Button>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button className="submit-btn" onClick={onClickLogin}>
|
<button className="submit-btn bs btn" onClick={onClickLogin}>
|
||||||
进入
|
进入
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -48,6 +48,9 @@
|
||||||
color: rgb(var(--primary-5));
|
color: rgb(var(--primary-5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
> img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.original {
|
.original {
|
||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
|
@ -57,6 +60,7 @@
|
||||||
letter-spacing: 3px;
|
letter-spacing: 3px;
|
||||||
}
|
}
|
||||||
.price {
|
.price {
|
||||||
|
margin-bottom: 40px;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-family: DINCondensed-Bold, "Century Gothic", CenturyGothic,
|
font-family: DINCondensed-Bold, "Century Gothic", CenturyGothic,
|
||||||
|
@ -75,7 +79,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
margin: 40px 0 0 0;
|
margin: 0;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Button } from "@arco-design/web-react";
|
|
||||||
import Checkmark12Filled from "@ricons/fluent/Checkmark12Filled";
|
import Checkmark12Filled from "@ricons/fluent/Checkmark12Filled";
|
||||||
import { Icon } from "@ricons/utils";
|
import { Icon } from "@ricons/utils";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
@ -7,100 +6,49 @@ function Subscribe() {
|
||||||
return (
|
return (
|
||||||
<div className="subscribe">
|
<div className="subscribe">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<h2>支持一下</h2>
|
<h2>订阅</h2>
|
||||||
<h4>
|
<h4>订阅后,全站免费,无任何其他附加收费</h4>
|
||||||
您可以手工通过“支付宝/微信”打赏
|
<h4>IF 忘记备注、开通慢,加微信: x-arctanx</h4>
|
||||||
Backset,在收到你的打赏之后,我们会尽快 (5 - 30分钟)
|
|
||||||
为您解锁全部学习内容权限
|
|
||||||
</h4>
|
|
||||||
<h4>
|
|
||||||
PS:转账请附上“用户名/手机号”,如果忘记了,请联系微信:x-arctanx
|
|
||||||
</h4>
|
|
||||||
<div className="options overlay">
|
<div className="options overlay">
|
||||||
<section>
|
<section>
|
||||||
<div className="original">¥1,299</div>
|
<h3>微信</h3>
|
||||||
<div className="price">199</div>
|
<p>请备注: 手机号</p>
|
||||||
<h3>季度</h3>
|
<img src="/wx256.jpg" />
|
||||||
<p>
|
|
||||||
三个月内,在线学习 backset.cn
|
|
||||||
上的所有课程,通过高效的内容,快速掌握各种软件应用开发技术。
|
|
||||||
</p>
|
|
||||||
<Button type="outline">订阅</Button>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<Icon size={20}>
|
|
||||||
<Checkmark12Filled />
|
|
||||||
</Icon>
|
|
||||||
<div>
|
|
||||||
全部视频免费<span>看看看看</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Icon size={20}>
|
|
||||||
<Checkmark12Filled />
|
|
||||||
</Icon>
|
|
||||||
<div>
|
|
||||||
全部视频免费<span>看看看看</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<div className="original">¥1,299</div>
|
<h3>支付宝</h3>
|
||||||
<div className="price">299</div>
|
<p>请备注: 手机号</p>
|
||||||
<h3>季度</h3>
|
<img src="/ali256.jpg" />
|
||||||
<p>
|
|
||||||
三个月内,在线学习 backset.cn
|
|
||||||
上的所有课程,通过高效的内容,快速掌握各种软件应用开发技术。
|
|
||||||
</p>
|
|
||||||
<Button type="outline">订阅</Button>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<Icon size={20}>
|
|
||||||
<Checkmark12Filled />
|
|
||||||
</Icon>
|
|
||||||
<div>
|
|
||||||
全部视频免费<span>看看看看</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Icon size={20}>
|
|
||||||
<Checkmark12Filled />
|
|
||||||
</Icon>
|
|
||||||
<div>
|
|
||||||
全部视频免费<span>看看看看</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
</section>
|
||||||
{/* popular */}
|
{/* popular */}
|
||||||
<section className="popular">
|
<section className="popular">
|
||||||
<div className="original">¥1,299</div>
|
<div className="original">¥499</div>
|
||||||
<div className="price">399</div>
|
<div className="price">256</div>
|
||||||
<h3>季度</h3>
|
<h3>年度订阅</h3>
|
||||||
<p>
|
<p>
|
||||||
三个月内,在线学习 backset.cn
|
12个月内,在线学习所有课程(视频、文字),快速掌握各种指标编写技术。随着菜场猪肉价格波动,订阅价格也会产生波动,越早订阅越划算
|
||||||
上的所有课程,通过高效的内容,快速掌握各种软件应用开发技术。
|
|
||||||
</p>
|
</p>
|
||||||
<Button type="primary" size="large" long>
|
{/* <button className="bs btn br3" style={{ width: "100%" }}>
|
||||||
订阅
|
订阅
|
||||||
</Button>
|
</button> */}
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<Icon size={20}>
|
<Icon size={20}>
|
||||||
<Checkmark12Filled />
|
<Checkmark12Filled />
|
||||||
</Icon>
|
</Icon>
|
||||||
<div>
|
<div>全部课程观看免费</div>
|
||||||
全部视频免费<span>看看看看</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Icon size={20}>
|
<Icon size={20}>
|
||||||
<Checkmark12Filled />
|
<Checkmark12Filled />
|
||||||
</Icon>
|
</Icon>
|
||||||
<div>
|
<div>私人小群</div>
|
||||||
全部视频免费<span>看看看看</span>
|
</li>
|
||||||
</div>
|
<li>
|
||||||
|
<Icon size={20}>
|
||||||
|
<Checkmark12Filled />
|
||||||
|
</Icon>
|
||||||
|
<div>物料下载</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
690
pnpm-lock.yaml
690
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user