init: 0.1.2
feat: login page && auth required todo: file dir cleanup
This commit is contained in:
32
frontend/src/api/auth.ts
Normal file
32
frontend/src/api/auth.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import {useState} from "react";
|
||||
import {message} from "../component/message/u-message.tsx";
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
login_at: number;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export const useAuth = () => {
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
|
||||
const login = async (username: string, password: string) => {
|
||||
let res = await fetch("/api/uauth/login", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({username: username, password: password}),
|
||||
})
|
||||
|
||||
if(res.status !== 200) {
|
||||
message.error("账号或密码错误")
|
||||
throw new Error(await res.text())
|
||||
}
|
||||
|
||||
let jes = await res.json() as User;
|
||||
|
||||
setUser(jes)
|
||||
}
|
||||
|
||||
return {user, login}
|
||||
}
|
@ -17,7 +17,7 @@ export const useFileUpload = () => {
|
||||
|
||||
try {
|
||||
console.log(`[D] api.Upload: upload file = ${file.name}, size = ${file.size}`, file);
|
||||
const url = `/api/share/${file.name}`;
|
||||
const url = `/api/ushare/${file.name}`;
|
||||
|
||||
// 1. 初始化上传
|
||||
const res1 = await fetch(url, {
|
||||
@ -51,7 +51,7 @@ export const useFileUpload = () => {
|
||||
const end = Math.min(start + CHUNK_SIZE, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
const res = await fetch(`/api/share/${code}`, {
|
||||
const res = await fetch(`/api/ushare/${code}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Range": `bytes=${start}-${end - 1}`,
|
||||
|
@ -41,7 +41,7 @@ export const PanelRight = () => {
|
||||
}
|
||||
|
||||
async function onFetchFile() {
|
||||
const url = `/api/share/${code}`
|
||||
const url = `/ushare/${code}`
|
||||
console.log('[D] onFetchFile: url =', url)
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import {createUseStyles} from "react-jss";
|
||||
import {CloudBackground} from "../component/fluid/cloud.tsx";
|
||||
import React, { useState } from "react";
|
||||
import { createUseStyles } from "react-jss";
|
||||
import { CloudBackground } from "../component/fluid/cloud.tsx";
|
||||
import {useAuth} from "../api/auth.ts";
|
||||
|
||||
const useClass = createUseStyles({
|
||||
container: {
|
||||
@ -75,24 +76,106 @@ const useClass = createUseStyles({
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
inputContainer: {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
marginTop: '20px',
|
||||
},
|
||||
inputField: {
|
||||
width: "calc(100% - 52px)",
|
||||
padding: "12px 35px 12px 15px",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "6px",
|
||||
fontSize: "16px",
|
||||
transition: "border-color 0.3s",
|
||||
"&:focus": {
|
||||
outline: "none",
|
||||
borderColor: "#1a73e8",
|
||||
boxShadow: "0 0 0 2px rgba(26, 115, 232, 0.2)",
|
||||
},
|
||||
"&:hover": {
|
||||
borderColor: "#1a73e8",
|
||||
}
|
||||
},
|
||||
iconButton: {
|
||||
position: 'absolute',
|
||||
right: '10px',
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
color: '#666',
|
||||
"&:hover": {
|
||||
color: '#333',
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const Login: React.FC = () => {
|
||||
const classes = useClass()
|
||||
const {login} = useAuth()
|
||||
const [username, setUsername] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
|
||||
const onLogin = async () => {
|
||||
try {
|
||||
await login(username, password)
|
||||
window.location.href = "/"
|
||||
} catch (_e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return <div className={classes.container}>
|
||||
<CloudBackground/>
|
||||
<div className={classes.login_container}>
|
||||
<div className={classes.form}>
|
||||
<h2>UShare</h2>
|
||||
<div className={classes.input}>
|
||||
<input placeholder={"请输入账号"}/>
|
||||
|
||||
{/* 用户名输入框 */}
|
||||
<div className={classes.inputContainer}>
|
||||
<input
|
||||
className={classes.inputField}
|
||||
placeholder="请输入账号"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
{username && (
|
||||
<button
|
||||
className={classes.iconButton}
|
||||
onClick={() => setUsername("")}
|
||||
style={{ right: '10px', fontSize: '16px' }}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.input}>
|
||||
<input placeholder={"请输入密码"} type={"password"} />
|
||||
|
||||
{/* 密码输入框 */}
|
||||
<div className={classes.inputContainer}>
|
||||
<input
|
||||
className={classes.inputField}
|
||||
placeholder="请输入密码"
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className={classes.iconButton}
|
||||
onMouseDown={() => setShowPassword(true)}
|
||||
onMouseUp={() => setShowPassword(false)}
|
||||
onMouseLeave={() => setShowPassword(false)}
|
||||
style={{ right: '10px', fontSize: '12px' }}
|
||||
>
|
||||
{showPassword ? "👁" : "👁"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={classes.button}>
|
||||
<button>登录</button>
|
||||
<button onClick={onLogin}>登录</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,7 +9,11 @@ export default defineConfig({
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:9119',
|
||||
changeOrigin: true
|
||||
}
|
||||
},
|
||||
'/ushare': {
|
||||
target: 'http://127.0.0.1:9119',
|
||||
changeOrigin: true
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
Reference in New Issue
Block a user