20 changed files with 349 additions and 57 deletions
@ -0,0 +1,2 @@ |
|||
# appwrite的项目ID |
|||
VUE_APPWRITE_PROJECT_ID= |
@ -0,0 +1,16 @@ |
|||
import { Toast } from "@/utils/toast" |
|||
import { Account, Client, ID } from "appwrite" |
|||
|
|||
const client = new Client() |
|||
|
|||
client.setProject(import.meta.env.VUE_APPWRITE_PROJECT_ID) |
|||
|
|||
export async function register(opts: { email: string; password: string }, errText: string) { |
|||
const account = new Account(client) |
|||
try { |
|||
await account.create(ID.unique(), opts.email, opts.password) |
|||
} catch (error: any) { |
|||
Toast.error(error?.message ?? errText) |
|||
return Promise.reject(error) |
|||
} |
|||
} |
@ -1,5 +1,5 @@ |
|||
import { createGlobalStyle } from "styled-components" |
|||
|
|||
export const GlobalStyles = createGlobalStyle` |
|||
|
|||
|
|||
` |
|||
|
@ -0,0 +1,9 @@ |
|||
import { atom } from "jotai" |
|||
|
|||
const userStore = { |
|||
userAtom: atom({ |
|||
token: "", |
|||
}), |
|||
} |
|||
|
|||
export { userStore } |
@ -0,0 +1,52 @@ |
|||
import { OpacityIn } from "@/effect" |
|||
import styled from "styled-components" |
|||
|
|||
const FigureElement = styled.figure` |
|||
margin: 0; |
|||
position: relative; |
|||
height: 350px; |
|||
.img-wrapper { |
|||
height: 100%; |
|||
width: 100%; |
|||
filter: brightness(0.75); |
|||
img { |
|||
height: 100%; |
|||
width: 100%; |
|||
object-fit: cover; |
|||
object-position: top -100px right 0; |
|||
display: block; |
|||
} |
|||
} |
|||
.mask { |
|||
background: url(data:image/webp;base64,UklGRkYAAABXRUJQVlA4WAoAAAAQAAAAAQAAAQAAQUxQSAUAAAAA/wAAAABWUDggGgAAADABAJ0BKgIAAgAAwBIlpAADcAD+/u6qAAAA); |
|||
position: absolute; |
|||
width: 100%; |
|||
height: 100%; |
|||
top: 0; |
|||
} |
|||
figcaption { |
|||
position: absolute; |
|||
left: 50%; |
|||
top: 50%; |
|||
transform: translate(-50%, -50%); |
|||
color: white; |
|||
padding: 15px 20px; |
|||
font-size: 2em; |
|||
text-align: justify; |
|||
} |
|||
` |
|||
|
|||
export function Hero() { |
|||
return ( |
|||
<FigureElement> |
|||
<OpacityIn className="img-wrapper"> |
|||
<img |
|||
src="https://api.r10086.com/%E5%9B%BE%E5%8C%85webp/%E5%8A%A8%E6%BC%AB%E7%BB%BC%E5%90%882/48cf0b92dd80ccdd4898e6b4734105fc.png!q90.webp" |
|||
alt="月宫" |
|||
/> |
|||
</OpacityIn> |
|||
<div className="mask"></div> |
|||
<figcaption>珠光照月,斑驳裂影。桃艳压山,风月无边。</figcaption> |
|||
</FigureElement> |
|||
) |
|||
} |
@ -0,0 +1,22 @@ |
|||
import ReactMarkdown from "react-markdown" |
|||
import styled from "styled-components" |
|||
|
|||
const MarkdownElement = styled(ReactMarkdown)` |
|||
blockquote { |
|||
margin: 0; |
|||
background-color: gainsboro; |
|||
padding: 10px 10px 10px 10px; |
|||
p:last-child { |
|||
margin-bottom: 0; |
|||
font-size: 1.1em; |
|||
} |
|||
} |
|||
` |
|||
|
|||
interface IProps { |
|||
children: string |
|||
} |
|||
|
|||
export function Markdown({ children }: IProps) { |
|||
return <MarkdownElement>{children}</MarkdownElement> |
|||
} |
@ -0,0 +1,11 @@ |
|||
// import { Tag } from "@blueprintjs/core";
|
|||
|
|||
export function Notice() { |
|||
return ( |
|||
<> |
|||
{/* <Tag large round icon="notifications" intent="warning" interactive> |
|||
公告: 吾生也有涯,而知也无涯。以有涯随无涯,殆已! |
|||
</Tag> */} |
|||
</> |
|||
) |
|||
} |
@ -0,0 +1,111 @@ |
|||
// import { Tag } from "@blueprintjs/core";
|
|||
|
|||
import { register } from "@/api" |
|||
import { Toast } from "@/utils/toast" |
|||
import { Button, FormGroup, InputGroup, Intent, Tooltip } from "@blueprintjs/core" |
|||
import { FC, ReactNode, useCallback, useEffect, useState } from "react" |
|||
|
|||
interface IProps { |
|||
onSuccess?: () => void |
|||
children?: { |
|||
Wrapper?: FC<{ children: ReactNode }> |
|||
Default?: FC<{ children: ReactNode }> //({ children }: { children: ReactNode }) => JSX.Element
|
|||
} |
|||
} |
|||
|
|||
export function Register({ onSuccess, children }: IProps) { |
|||
const [isLoading, setIsLoading] = useState(false) |
|||
const [showPassword, setShowPassword] = useState(false) |
|||
const [email, setEmail] = useState("") |
|||
const [password, setPassword] = useState("") |
|||
const [repeatPassword, setRepeatPassword] = useState("") |
|||
|
|||
const clickRegister = useCallback(async () => { |
|||
if (isLoading) { |
|||
Toast.success("正在请求中") |
|||
return |
|||
} |
|||
if (!email) return Toast.error("请输入邮箱") |
|||
if (!password) return Toast.error("请输入密码") |
|||
if (!repeatPassword) return Toast.error("请再一次输入密码") |
|||
if (repeatPassword !== password) return Toast.error("两次输入的密码不一致") |
|||
let toastID = Toast.loading("注册中") |
|||
setIsLoading(true) |
|||
try { |
|||
await register({ email, password }, "注册失败") |
|||
Toast.success("注册成功") |
|||
onSuccess && onSuccess() |
|||
} catch (error) { |
|||
console.error(error) |
|||
} finally { |
|||
Toast.loadingDismiss(toastID) |
|||
setIsLoading(false) |
|||
} |
|||
}, [password, email, repeatPassword, isLoading]) |
|||
|
|||
useEffect(()=>{ |
|||
return ()=>{ |
|||
console.log("取消请求"); |
|||
} |
|||
}, []) |
|||
|
|||
const WrapperComp = useCallback( |
|||
({ children: child }: { children: ReactNode }) => children?.Wrapper?.({ children: child }) ?? <>{child}</>, |
|||
[children?.Wrapper], |
|||
) |
|||
const DefaultComp = useCallback( |
|||
({ children: child }: { children: ReactNode }) => children?.Default?.({ children: child }) ?? <>{child}</>, |
|||
[children?.Default], |
|||
) |
|||
|
|||
return ( |
|||
<> |
|||
<form onSubmitCapture={clickRegister}> |
|||
<WrapperComp> |
|||
<FormGroup helperText="请输入一个可用的邮箱" label="邮箱" labelFor="text-input" labelInfo="(必须)"> |
|||
<InputGroup |
|||
value={email} |
|||
onChange={v => setEmail(v.target.value)} |
|||
id="email-input" |
|||
placeholder="请输入邮箱" |
|||
type="text" |
|||
/> |
|||
</FormGroup> |
|||
<FormGroup helperText="请输入密码" label="密码" labelFor="pwd-input" labelInfo="(必须)"> |
|||
<InputGroup |
|||
id="pwd-input" |
|||
value={password} |
|||
onChange={v => setPassword(v.target.value)} |
|||
placeholder="请输入密码" |
|||
rightElement={ |
|||
<Tooltip content={`${showPassword ? "Hide" : "Show"} Password`}> |
|||
<Button |
|||
icon={showPassword ? "unlock" : "lock"} |
|||
intent={Intent.WARNING} |
|||
minimal={true} |
|||
onClick={() => setShowPassword(!showPassword)} |
|||
/> |
|||
</Tooltip> |
|||
} |
|||
type={showPassword ? "text" : "password"} |
|||
/> |
|||
</FormGroup> |
|||
<FormGroup helperText="请再输入一遍密码" label="重复密码" labelFor="rpwd-input" labelInfo="(必须)"> |
|||
<InputGroup |
|||
value={repeatPassword} |
|||
onChange={v => setRepeatPassword(v.target.value)} |
|||
id="rpwd-input" |
|||
placeholder="请输入密码" |
|||
type="text" |
|||
/> |
|||
</FormGroup> |
|||
</WrapperComp> |
|||
<DefaultComp> |
|||
<Button intent="primary" type="submit"> |
|||
注册 |
|||
</Button> |
|||
</DefaultComp> |
|||
</form> |
|||
</> |
|||
) |
|||
} |
@ -0,0 +1,21 @@ |
|||
import { OverlayToaster, Position, Toaster } from "@blueprintjs/core" |
|||
|
|||
interface Toaster2 { |
|||
error: Function |
|||
success: Function |
|||
loading: Function |
|||
loadingDismiss: Function |
|||
} |
|||
|
|||
export const Toast: Toaster & Toaster2 = OverlayToaster.create({ position: Position.TOP_RIGHT }) as any |
|||
export const TToast: Toaster & Toaster2 = OverlayToaster.create({ position: Position.TOP }) as any |
|||
|
|||
Toast.loadingDismiss = (key: string) => { |
|||
TToast.dismiss(key) |
|||
} |
|||
Toast.error = (message: string) => Toast.show({ message: message, icon: "error", intent: "danger", timeout: 3000 }) |
|||
Toast.success = (message: string) => Toast.show({ message: message, intent: "success", timeout: 3000 }) |
|||
Toast.loading = (message: string) => { |
|||
const key = TToast.show({ message: message, intent: "primary", timeout: 0 }) |
|||
return key |
|||
} |
@ -1,37 +1,19 @@ |
|||
import withPage from "@/base/withPage" |
|||
import { useHistory } from "react-router-dom" |
|||
import styled from "styled-components" |
|||
import ReactMarkdown from "react-markdown" |
|||
import MdText from "./index.md?raw" |
|||
import { Hero } from "@/ui/Hero" |
|||
import { ReactNode } from "react" |
|||
|
|||
const Title = styled.h1` |
|||
font-size: 1.5em; |
|||
text-align: center; |
|||
color: #bf4f74; |
|||
` |
|||
interface IProps { |
|||
children: ReactNode |
|||
} |
|||
|
|||
const Wrapper = styled.section` |
|||
padding: 4em; |
|||
background: papayawhip; |
|||
` |
|||
|
|||
export default withPage(function Project({ children }) { |
|||
const router = useHistory() |
|||
function toChild() { |
|||
router.push("/project/child") |
|||
} |
|||
export default withPage(function Project({}: IProps) { |
|||
return ( |
|||
<div className="container"> |
|||
<Wrapper> |
|||
<Title>HOME</Title> |
|||
</Wrapper> |
|||
<ReactMarkdown>{MdText}</ReactMarkdown> |
|||
<div> |
|||
<div onClick={toChild} style={{ height: "200px" }}> |
|||
vaas |
|||
</div> |
|||
{children} |
|||
<> |
|||
<Hero></Hero> |
|||
|
|||
<div className="container"> |
|||
|
|||
</div> |
|||
</div> |
|||
</> |
|||
) |
|||
}) |
|||
|
Loading…
Reference in new issue