feat: index

This commit is contained in:
mozzie 2023-03-13 16:52:27 +08:00
parent 0a7b4046a7
commit 24b8b4c91d
18 changed files with 225 additions and 104 deletions

View File

@ -25,7 +25,8 @@
"dotenv": "16.0.3", "dotenv": "16.0.3",
"jsonwebtoken": "9.0.0", "jsonwebtoken": "9.0.0",
"tencentcloud-sdk-nodejs": "4.0.552", "tencentcloud-sdk-nodejs": "4.0.552",
"vod-node-sdk": "1.1.0" "vod-node-sdk": "1.1.0",
"chinese-random-name": "2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@midwayjs/cli": "^2.0.0", "@midwayjs/cli": "^2.0.0",

View File

@ -1,17 +0,0 @@
import { Inject, Controller, Post, Body } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import { Validate } from '@midwayjs/validate';
import { UserDTO } from '../dto/user.dto';
@Controller('/')
export class APIController {
@Inject()
ctx: Context;
@Post('/get_user')
@Validate({
errorStatus: 422,
})
async getUser(@Body() user: UserDTO) {
return { success: true, message: 'OK', data: user };
}
}

View File

@ -1,33 +0,0 @@
import { Controller, Get, Inject } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Context } from '@midwayjs/koa';
import { Photo } from '../entity/photo.entity';
import { Repository } from 'typeorm';
@Controller('/')
export class HomeController {
@Inject()
ctx: Context;
@InjectEntityModel(Photo)
photoModel: Repository<Photo>;
@Get('/testMysql')
async testMysql() {
// create a entity object
let photo = new Photo();
photo.name = 'Me and Bears';
photo.description = 'I am near polar bears';
photo.filename = 'photo-with-bears.jpg';
photo.views = 1;
photo.isPublished = true;
// save entity
const photoResult = await this.photoModel.save(photo);
// save success
console.log('photo id = ', photoResult.id);
this.ctx.body = photoResult.id;
}
}

View File

@ -0,0 +1,39 @@
import { Body, Context, Controller, Inject, Post } from '@midwayjs/core';
import { BizCode } from '../biz/code';
import { UserWebAuthDTO } from '../dto/user.dto';
import { UserService } from '../service/user.service';
import { md5 } from '../util/encrypt';
@Controller('/user')
export class UserController {
@Inject()
ctx: Context;
@Inject()
userService: UserService;
@Post('/web/auth')
async webAuth(@Body() params: UserWebAuthDTO) {
try {
const userExist = await this.userService.select(params);
if (userExist?.id) {
const { user_pass, ...rest } = userExist;
const passValid = userExist.user_pass === md5(params.user_pass);
return passValid
? { code: BizCode.OK, msg: '欢迎回来', data: { ...rest } }
: { code: BizCode.ERROR, msg: '密码错误' };
} else {
const createUser = await this.userService.save(params);
const { user_pass, ...rest } = createUser;
return {
code: BizCode.OK,
data: { ...rest },
msg: '欢迎来到 backset.cn',
};
}
} catch (error) {
this.ctx.logger.error(error);
return { code: BizCode.ERROR, msg: '[error] web/auth error' };
}
}
}

View File

@ -1,16 +1,10 @@
// src/dto/user.ts // src/dto/user.ts
import { Rule, RuleType } from '@midwayjs/validate'; import { Rule, RuleType } from '@midwayjs/validate';
export class UserDTO { export class UserWebAuthDTO {
@Rule(RuleType.number().required()) @Rule(RuleType.string().required())
id: number; user_login: string;
@Rule(RuleType.string().required()) @Rule(RuleType.string().required())
firstName: string; user_pass: string;
@Rule(RuleType.string().max(10))
lastName: string;
@Rule(RuleType.number().max(60))
age: number;
} }

View File

@ -1,3 +1,29 @@
export class User{ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
const randomName = require('chinese-random-name');
}
@Entity('user')
export class User {
@PrimaryGeneratedColumn('increment')
id?: number;
@Column({ unique: true })
user_login?: string;
@Column()
user_pass?: string;
@Column({ unique: true, default: '' })
user_phone?: string;
@Column({ default: '', unique: true })
user_email?: string;
@Column({ default: Date.now() })
user_create_time?: string;
@Column({ default: true })
user_status?: boolean;
@Column({ default: randomName.generate() })
display_name?: string;
}

View File

@ -1,14 +1,26 @@
import { Provide } from '@midwayjs/core'; import { Provide } from '@midwayjs/core';
import { IUserOptions } from '../interface'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { UserWebAuthDTO } from '../dto/user.dto';
import { User } from '../entity/user.entity';
import { md5 } from '../util/encrypt';
@Provide() @Provide()
export class UserService { export class UserService {
async getUser(options: IUserOptions) { @InjectEntityModel(User)
return { userModel: Repository<User>;
uid: options.uid,
username: 'mockedName', async select(p: UserWebAuthDTO): Promise<User> {
phone: '12345678901', const { user_login } = p;
email: 'xxx.xxx@xxx.com', const user = await this.userModel.findOne({
}; where: [{ user_phone: user_login }, { user_login }],
});
return user;
}
async save(user: User) {
user.user_pass = md5(user.user_pass);
const result = await this.userModel.save(user);
return result;
} }
} }

View File

@ -17,7 +17,8 @@
"@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",
"highlight.js": "11.7.0" "highlight.js": "11.7.0",
"js-cookie": "3.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.27", "@types/react": "^18.0.27",
@ -27,6 +28,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/js-cookie": "3.0.3"
} }
} }

4
apps/web/src/api/dto.ts Normal file
View File

@ -0,0 +1,4 @@
export interface ILoginRequest {
user_login: string;
user_pass: string;
}

View File

@ -1,6 +1,10 @@
import { ILoginRequest } from "./dto";
import R from "./request"; import R from "./request";
export const getCourseList = () => R.post("/api/course/select/all"); export const getCourseList = () => R.post("/api/course/select/all");
export const getChapterGuideById = (course_id: string) => export const getChapterGuideById = (course_id: string) =>
R.post("/api/course/chapter/select", { course_id }); R.post("/api/course/chapter/select", { course_id });
export const userLogin = (p: ILoginRequest) =>
R.post("/api/user/web/auth", { ...p });

View File

@ -0,0 +1,4 @@
.main-footer {
padding-bottom: 20px;
text-align: center;
}

View File

@ -0,0 +1,12 @@
import "./index.less";
export const Footer = () => {
return (
<footer className="main-footer">
<span>
© 2023 Developed by Backset.cn ·{" "}
<a href="http://beian.miit.gov.cn">ICP备19008833号-5</a>
</span>
</footer>
);
};

View File

@ -196,7 +196,7 @@ header {
} }
} }
.login-pass { .login-qrcode {
position: absolute; position: absolute;
top: 20%; top: 20%;
left: 0; left: 0;
@ -245,8 +245,6 @@ header {
.form-holder { .form-holder {
border-radius: 8px; border-radius: 8px;
background-color: #fff;
border: 1px solid #eee;
overflow: hidden; overflow: hidden;
margin-top: 50px; margin-top: 50px;
opacity: 1; opacity: 1;

View File

@ -14,16 +14,21 @@ import {
const InputSearch = Input.Search; const InputSearch = Input.Search;
const RadioGroup = Radio.Group; const RadioGroup = Radio.Group;
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { userLogin } from "../../api";
type TLoginMethod = "usePass" | "useSms"; const defaultForm = {
user_login: "",
user_pass: "",
};
type TLoginMethod = "useQRCode" | "useSms";
const DURATION = 3; // 验证码倒计时 const DURATION = 3; // 验证码倒计时
function Nav() { function Nav() {
const navigate = useNavigate(); const navigate = useNavigate();
const [loginForm] = Form.useForm(); const [smsLoginForm, setSmsLoginForm] = useState(defaultForm);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const [loginMethod, setLoginMethod] = useState<TLoginMethod>("useSms"); const [loginMethod, setLoginMethod] = useState<TLoginMethod>("useSms");
let [countdown, setCountdown] = useState(DURATION); let [countdown, setCountdown] = useState(DURATION);
const timer = useRef<any>(); const timer = useRef<any>();
@ -35,6 +40,17 @@ function Nav() {
}, 500); }, 500);
}; };
const onClickSmsLogin = () => {
userLogin(smsLoginForm).then((res: any) => {
const { code, data, msg } = res;
if (code === 10000) {
Message.success(msg);
console.log(data);
}
if (code === 20000) Message.error(msg);
});
};
useEffect(() => { useEffect(() => {
if (countdown === 0) { if (countdown === 0) {
clearInterval(timer.current); clearInterval(timer.current);
@ -75,7 +91,6 @@ function Nav() {
visible={visible} visible={visible}
closeIcon={false} closeIcon={false}
footer={null} footer={null}
confirmLoading={confirmLoading}
onCancel={() => setVisible(false)} onCancel={() => setVisible(false)}
> >
<div className="form-structor"> <div className="form-structor">
@ -94,12 +109,24 @@ function Nav() {
<input <input
type="text" type="text"
className="input" className="input"
placeholder="用户名/手机" placeholder="手机"
onChange={(e) =>
setSmsLoginForm((p) => ({
...p,
user_login: e.target.value,
}))
}
/> />
<div className="sms-group"> <div className="sms-group">
<input <input
className="input" className="input"
type="password" type="text"
onChange={(e) =>
setSmsLoginForm((p) => ({
...p,
user_pass: e.target.value,
}))
}
placeholder="验证码" placeholder="验证码"
></input> ></input>
<Button type="text" onClick={onClickSmsBtn}> <Button type="text" onClick={onClickSmsBtn}>
@ -107,34 +134,25 @@ function Nav() {
</Button> </Button>
</div> </div>
</div> </div>
<button className="submit-btn"></button> <button className="submit-btn" onClick={onClickSmsLogin}>
</button>
</div> </div>
{/* 密码 */} {/* 码 */}
<div <div
className={`login-pass ${ className={`login-qrcode ${
loginMethod === "usePass" ? "" : "slide-up" loginMethod === "useQRCode" ? "" : "slide-up"
}`} }`}
> >
<div className="center"> <div className="center">
<h2 <h2
className="form-title" className="form-title"
onClick={() => setLoginMethod("usePass")} // onClick={() => setLoginMethod("useQRCode")}
onClick={() => Message.info("开发中... 😀 ")}
> >
</h2> </h2>
<div className="form-holder"> <div className="form-holder"></div>
<input
type="text"
className="input"
placeholder="用户名/手机"
/>
<input
type="password"
className="input"
placeholder="密码"
/>
</div>
<button className="submit-btn"></button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,16 +1,31 @@
import React, { useEffect } from "react"; import { Button, Result } from "@arco-design/web-react";
import { useEffect } from "react";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import Cookies from "js-cookie";
interface IGuardProps { interface IGuardProps {
children: JSX.Element; children: JSX.Element;
} }
const needAuthList = ["course/detail"];
export const Guard = (props: IGuardProps) => { export const Guard = (props: IGuardProps) => {
const location = useLocation(); const location = useLocation();
const sessionExist = !!Cookies.get("_session");
const needAuth = needAuthList.some((p) => location.pathname.indexOf(p) > -1);
useEffect(() => { useEffect(() => {
console.log("location.pathname changed 拦截", location.pathname); console.log("location.pathname changed 拦截", location.pathname);
if (location.pathname.indexOf("course/detail/") > -1) {
console.log("进入课程详情页");
}
}, [location.pathname]); }, [location.pathname]);
if (needAuth && !sessionExist)
return (
<div style={{ paddingTop: 100 }}>
<Result status="403" subTitle="无权访问,请登录" />
</div>
);
return props.children; return props.children;
}; };

View File

@ -12,6 +12,7 @@ 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";
export default function Index() { export default function Index() {
const navigate = useNavigate(); const navigate = useNavigate();
@ -151,6 +152,7 @@ export default function Index() {
model={{ top: timeline.top }} model={{ top: timeline.top }}
/> />
</div> </div>
<Footer />
</div> </div>
); );
} }

View File

@ -26,7 +26,14 @@ function Guide(props: IProps) {
}, [html]); }, [html]);
return ( return (
<div style={{ width: 1120, margin: "0 auto", overflow: "hidden" }}> <div
style={{
maxWidth: 1120,
margin: "0 auto",
overflow: "hidden",
padding: "0 24px",
}}
>
<article <article
dangerouslySetInnerHTML={{ __html: html }} dangerouslySetInnerHTML={{ __html: html }}
className="markdown-body" className="markdown-body"

View File

@ -147,6 +147,7 @@ importers:
'@types/node': '14' '@types/node': '14'
'@typescript-eslint/eslint-plugin': ^5.0.0 '@typescript-eslint/eslint-plugin': ^5.0.0
'@typescript-eslint/parser': ^5.0.0 '@typescript-eslint/parser': ^5.0.0
chinese-random-name: 2.0.0
dotenv: 16.0.3 dotenv: 16.0.3
jsonwebtoken: 9.0.0 jsonwebtoken: 9.0.0
mongoose: ^6.0.7 mongoose: ^6.0.7
@ -172,6 +173,7 @@ importers:
'@midwayjs/upload': registry.npmmirror.com/@midwayjs/upload/3.10.14 '@midwayjs/upload': registry.npmmirror.com/@midwayjs/upload/3.10.14
'@midwayjs/validate': registry.npmmirror.com/@midwayjs/validate/3.10.10 '@midwayjs/validate': registry.npmmirror.com/@midwayjs/validate/3.10.10
'@typegoose/typegoose': registry.npmmirror.com/@typegoose/typegoose/10.1.1_mongoose@6.9.1 '@typegoose/typegoose': registry.npmmirror.com/@typegoose/typegoose/10.1.1_mongoose@6.9.1
chinese-random-name: 2.0.0
dotenv: registry.npmmirror.com/dotenv/16.0.3 dotenv: registry.npmmirror.com/dotenv/16.0.3
jsonwebtoken: registry.npmmirror.com/jsonwebtoken/9.0.0 jsonwebtoken: registry.npmmirror.com/jsonwebtoken/9.0.0
mongoose: registry.npmmirror.com/mongoose/6.9.1 mongoose: registry.npmmirror.com/mongoose/6.9.1
@ -197,12 +199,14 @@ importers:
'@ricons/fluent': 0.12.0 '@ricons/fluent': 0.12.0
'@ricons/utils': 0.1.6 '@ricons/utils': 0.1.6
'@types/dplayer': 1.25.2 '@types/dplayer': 1.25.2
'@types/js-cookie': 3.0.3
'@types/react': ^18.0.27 '@types/react': ^18.0.27
'@types/react-dom': ^18.0.10 '@types/react-dom': ^18.0.10
'@types/react-router-dom': 5.3.3 '@types/react-router-dom': 5.3.3
'@vitejs/plugin-react': ^3.1.0 '@vitejs/plugin-react': ^3.1.0
dplayer: 1.27.1 dplayer: 1.27.1
highlight.js: 11.7.0 highlight.js: 11.7.0
js-cookie: 3.0.1
less: ^4.1.3 less: ^4.1.3
react: ^18.2.0 react: ^18.2.0
react-dom: ^18.2.0 react-dom: ^18.2.0
@ -216,12 +220,14 @@ importers:
'@ricons/utils': 0.1.6_biqbaboplfbrettd7655fr4n2y '@ricons/utils': 0.1.6_biqbaboplfbrettd7655fr4n2y
dplayer: 1.27.1 dplayer: 1.27.1
highlight.js: 11.7.0 highlight.js: 11.7.0
js-cookie: 3.0.1
less: 4.1.3 less: 4.1.3
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
react-router-dom: 6.8.0_biqbaboplfbrettd7655fr4n2y react-router-dom: 6.8.0_biqbaboplfbrettd7655fr4n2y
devDependencies: devDependencies:
'@types/dplayer': 1.25.2 '@types/dplayer': 1.25.2
'@types/js-cookie': 3.0.3
'@types/react': 18.0.27 '@types/react': 18.0.27
'@types/react-dom': 18.0.10 '@types/react-dom': 18.0.10
'@types/react-router-dom': 5.3.3 '@types/react-router-dom': 5.3.3
@ -1475,6 +1481,11 @@ packages:
to-fast-properties: 2.0.0 to-fast-properties: 2.0.0
dev: true dev: true
/@crand/mt19937/3.1.1:
resolution: {integrity: sha512-WqBOxMN7ckc+nFJT6Bt0B5NjOm/IWoNlpy05enZQF/bmlv41gUdn4kwUmbIgqS8lwEn9kmgM2us2tfB9J7rIAg==}
requiresBuild: true
dev: false
/@emotion/hash/0.8.0: /@emotion/hash/0.8.0:
resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
dev: false dev: false
@ -1749,6 +1760,10 @@ packages:
'@types/sizzle': 2.3.3 '@types/sizzle': 2.3.3
dev: true dev: true
/@types/js-cookie/3.0.3:
resolution: {integrity: sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==}
dev: true
/@types/node/14.14.45: /@types/node/14.14.45:
resolution: {integrity: sha512-DssMqTV9UnnoxDWu959sDLZzfvqCF0qDNRjaWeYSui9xkFe61kKo4l1TWNTQONpuXEm+gLMRvdlzvNHBamzmEw==} resolution: {integrity: sha512-DssMqTV9UnnoxDWu959sDLZzfvqCF0qDNRjaWeYSui9xkFe61kKo4l1TWNTQONpuXEm+gLMRvdlzvNHBamzmEw==}
dev: false dev: false
@ -1892,6 +1907,14 @@ packages:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
dev: true dev: true
/chinese-random-name/2.0.0:
resolution: {integrity: sha512-BsNqsxDQMpBn1lusoIipj1A2mo6rz09MqRusfVaPK7AWF0Y6F+HqauuQFBPDN3VxLg6GjabYcZZYDu34y7M2vA==}
requiresBuild: true
dependencies:
'@crand/mt19937': 3.1.1
flatten: 1.0.3
dev: false
/color-convert/1.9.3: /color-convert/1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies: dependencies:
@ -2050,6 +2073,11 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/flatten/1.0.3:
resolution: {integrity: sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==}
deprecated: flatten is deprecated in favor of utility frameworks such as lodash.
dev: false
/focus-lock/0.11.6: /focus-lock/0.11.6:
resolution: {integrity: sha512-KSuV3ur4gf2KqMNoZx3nXNVhqCkn42GuTYCX4tXPEwf0MjpFQmNMiN6m7dXaUXgIoivL6/65agoUMg4RLS0Vbg==} resolution: {integrity: sha512-KSuV3ur4gf2KqMNoZx3nXNVhqCkn42GuTYCX4tXPEwf0MjpFQmNMiN6m7dXaUXgIoivL6/65agoUMg4RLS0Vbg==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -2195,6 +2223,11 @@ packages:
resolution: {integrity: sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==} resolution: {integrity: sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==}
dev: true dev: true
/js-cookie/3.0.1:
resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==}
engines: {node: '>=12'}
dev: false
/js-tokens/4.0.0: /js-tokens/4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}