Browse Source

Merge branch 'main' of ssh://git.xieyaxin.top:8892/somebuild-org/my-react-app

main
谢亚昕 5 months ago
parent
commit
17358d5147
  1. 2
      .env
  2. 2
      package.json
  3. 28
      pnpm-lock.yaml
  4. 16
      src/api/index.ts
  5. 2
      src/base/withPage.tsx
  6. 2
      src/effect/index.tsx
  7. 2
      src/layout/GlobalStyles.ts
  8. 68
      src/layout/base.tsx
  9. 2
      src/router/AppRouter.tsx
  10. 6
      src/router/Loading.tsx
  11. 4
      src/router/index.ts
  12. 9
      src/store/account.ts
  13. 52
      src/ui/Hero/index.tsx
  14. 22
      src/ui/Markdown/index.tsx
  15. 11
      src/ui/Notice/index.tsx
  16. 111
      src/ui/Register/Register.tsx
  17. 21
      src/utils/toast.ts
  18. 3
      src/views/Child.tsx
  19. 42
      src/views/Home/index.tsx
  20. 1
      vite.config.ts

2
.env

@ -0,0 +1,2 @@
# appwrite的项目ID
VUE_APPWRITE_PROJECT_ID=

2
package.json

@ -19,7 +19,9 @@
"@types/lodash": "^4.17.13",
"@types/react-router-dom": "^5.3.3",
"axios": "^1.7.7",
"appwrite": "^16.0.2",
"framer-motion": "^11.11.17",
"jotai": "^2.10.3",
"lodash": "^4.17.21",
"normalize.css": "^8.0.1",
"react": "^18.3.1",

28
pnpm-lock.yaml

@ -29,9 +29,15 @@ importers:
axios:
specifier: ^1.7.7
version: 1.7.7
appwrite:
specifier: ^16.0.2
version: 16.0.2
framer-motion:
specifier: ^11.11.17
version: 11.11.17(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
jotai:
specifier: ^2.10.3
version: 2.10.3(@types/react@18.3.12)(react@18.3.1)
lodash:
specifier: ^4.17.21
version: 4.17.21
@ -661,6 +667,9 @@ packages:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
appwrite@16.0.2:
resolution: {integrity: sha512-sCMVOe9wdB8OneIz6LtoYhp797GEXoudAdygNZgezPiybGOlMPcQ8kP1c/FW1eES0ledaqgaZ0PHb+LwZia8pw==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@ -1015,6 +1024,18 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
jotai@2.10.3:
resolution: {integrity: sha512-Nnf4IwrLhNfuz2JOQLI0V/AgwcpxvVy8Ec8PidIIDeRi4KCFpwTFIpHAAcU+yCgnw/oASYElq9UY0YdUUegsSA==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=17.0.0'
react: '>=17.0.0'
peerDependenciesMeta:
'@types/react':
optional: true
react:
optional: true
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@ -2012,6 +2033,8 @@ snapshots:
dependencies:
color-convert: 2.0.1
appwrite@16.0.2: {}
argparse@2.0.1: {}
asynckit@0.4.0: {}
@ -2411,6 +2434,11 @@ snapshots:
isexe@2.0.0: {}
jotai@2.10.3(@types/react@18.3.12)(react@18.3.1):
optionalDependencies:
'@types/react': 18.3.12
react: 18.3.1
js-tokens@4.0.0: {}
js-yaml@4.1.0:

16
src/api/index.ts

@ -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)
}
}

2
src/base/withPage.tsx

@ -1,5 +1,5 @@
import { FC, ReactNode } from "react"
import { LeftIn, OpacityIn } from "@/effect"
import { OpacityIn } from "@/effect"
import styled from "styled-components"
interface PageProps {

2
src/effect/index.tsx

@ -1,5 +1,5 @@
import { motion } from "framer-motion"
import styled from "styled-components"
export function OpacityIn({ children, className }: any) {
return (

2
src/layout/GlobalStyles.ts

@ -1,5 +1,5 @@
import { createGlobalStyle } from "styled-components"
export const GlobalStyles = createGlobalStyle`
`

68
src/layout/base.tsx

@ -3,6 +3,9 @@ import { ReactNode, useState } from "react"
import { NavLink, useHistory } from "react-router-dom"
import styled from "styled-components"
import { GlobalStyles } from "./GlobalStyles"
import { Notice } from "@/ui/Notice"
import { Markdown } from "@/ui/Markdown"
import { Register } from "@/ui/Register/Register"
interface PageProps {
children: ReactNode
@ -24,18 +27,29 @@ const ContentWrapper = styled.div`
function BaseLayout(props: PageProps) {
const [isOpen, setIsOpen] = useState(false)
const [isOpenAbout, setIsOpenAbout] = useState(false)
const [isOpenRegister, setIsOpenRegister] = useState(false)
const router = useHistory()
const about = `
> 退
[Github](https://github.com/npmrun)
`
return (
<PageWrapper>
<GlobalStyles />
<Navbar>
<Navbar.Group align={Alignment.LEFT}>
<Navbar.Heading></Navbar.Heading>
<Navbar.Heading></Navbar.Heading>
<Navbar.Divider />
<NavLink to={"/home"} strict exact activeClassName="actived">
<Button className="bp5-minimal" icon="home" text="Home" />
<Button className="bp5-minimal" icon="home" text="首页" />
</NavLink>
<Popover
isOpen={isOpen}
@ -43,35 +57,59 @@ function BaseLayout(props: PageProps) {
onInteraction={isOpen => setIsOpen(isOpen)}
content={
<Menu>
<MenuItem icon="graph" text="/project" onClick={() => router.push("/project")} />
<MenuItem icon="map" text="/project/child" onClick={() => router.push("/project/child")} />
<MenuItem icon="th" text="Table" shouldDismissPopover={false} />
<MenuItem icon="zoom-to-fit" text="Browser" disabled={true} />
<MenuItem icon="flow-review-branch" text="天涯笔记" disabled onClick={() => router.push("/project")} />
<MenuItem icon="third-party" text="全聚德" disabled onClick={() => router.push("/project/child")} />
<MenuDivider />
<MenuItem icon="cog" text="Settings...">
<MenuItem icon="add" text="Add new application" disabled={true} />
<MenuItem icon="remove" text="Remove application" />
<MenuItem icon="document" text="文档">
<MenuItem icon="build" text="CSS选择器示例" />
</MenuItem>
<MenuDivider />
<MenuItem icon="cog" text="其他">
<MenuItem icon="build" text="CSS选择器示例" />
</MenuItem>
</Menu>
}
fill={true}
placement="bottom"
>
<Button className="bp5-minimal" icon="document" text="Files" rightIcon={isOpen ? "caret-down" : "caret-right"} />
<Button className="bp5-minimal" icon="code" text="开源应用" rightIcon={isOpen ? "caret-down" : "caret-right"} />
</Popover>
</Navbar.Group>
<Navbar.Group align={Alignment.LEFT} style={{ marginLeft: "10px", maxWidth: "400px" }}>
<Notice />
</Navbar.Group>
<Navbar.Group align={Alignment.RIGHT}>
<Button className="bp5-minimal" icon="settings" />
<Button icon="person" className="bp5-minimal" text="注册" onClick={() => setIsOpenRegister(true)} />
<Navbar.Divider />
{/* <NavLink to={"/about"}> */}
<Button onClick={() => setIsOpenAbout(true)} className="bp5-minimal" icon="person" text="关于" />
<Button onClick={() => setIsOpenAbout(true)} className="bp5-minimal" text="关于" />
{/* </NavLink> */}
</Navbar.Group>
</Navbar>
<ContentWrapper>{props.children}</ContentWrapper>
<Dialog isOpen={isOpenAbout} title="通知" icon="info-sign" onClose={() => setIsOpenAbout(false)}>
<DialogBody>{/* body contents here */}</DialogBody>
<DialogFooter actions={<Button intent="primary" text="关闭" onClick={() => setIsOpenAbout(false)} />} />
<Dialog isOpen={isOpenAbout} title="关于我" icon="info-sign" onClose={() => setIsOpenAbout(false)}>
<DialogBody>
<Markdown>{about}</Markdown>
<Notice></Notice>
</DialogBody>
<DialogFooter actions={<Button intent="primary" text="合起" onClick={() => setIsOpenAbout(false)} />} />
</Dialog>
<Dialog isOpen={isOpenRegister} title="注册" icon="info-sign" onClose={() => setIsOpenRegister(false)}>
<Register>
{{
Wrapper: ({ children }: { children: ReactNode }) => <DialogBody>{children}</DialogBody>,
Default: ({ children }: { children: ReactNode }) => (
<DialogFooter
actions={
<>
{children}
<Button text="合起" onClick={() => setIsOpenRegister(false)} />
</>
}
/>
),
}}
</Register>
</Dialog>
</PageWrapper>
)

2
src/router/AppRouter.tsx

@ -1,5 +1,5 @@
import { HashRouter as Router, Switch, Redirect, Route } from "react-router-dom"
import React, { Fragment, ReactNode } from "react"
import React, { Fragment } from "react"
import AuthRoute from "./AuthRoute"
import routes from "./route"
import Loading from "./Loading"

6
src/router/Loading.tsx

@ -1,8 +1,6 @@
import { createRef, ReactNode, useEffect, useState } from "react"
import { ReactNode, useEffect, useState } from "react"
import styled from "styled-components"
interface IProps {}
const LoadingComp = styled.div`
position: absolute;
top: 0;
@ -37,7 +35,7 @@ const LoadingComp = styled.div`
}
`
export default function Loading(props: IProps): ReactNode {
export default function Loading(): ReactNode {
const [show, setShow] = useState(false)
useEffect(() => {
const timeId = setTimeout(() => {

4
src/router/index.ts

@ -30,7 +30,7 @@ export default function useRoute() {
const { pathname } = useLocation()
let oroute = JSON.parse(JSON.stringify(routes))
let index = foundRoute(routes, pathname)
let isLayout = true
// let isLayout = true
let curA = null
let curB = null
if (index && index.length) {
@ -45,7 +45,7 @@ export default function useRoute() {
cur.children = []
}
if (cur.layout != undefined) {
isLayout = !!cur.layout
// isLayout = !!cur.layout
}
curA = cur
curB = res

9
src/store/account.ts

@ -0,0 +1,9 @@
import { atom } from "jotai"
const userStore = {
userAtom: atom({
token: "",
}),
}
export { userStore }

52
src/ui/Hero/index.tsx

@ -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>
)
}

22
src/ui/Markdown/index.tsx

@ -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>
}

11
src/ui/Notice/index.tsx

@ -0,0 +1,11 @@
// import { Tag } from "@blueprintjs/core";
export function Notice() {
return (
<>
{/* <Tag large round icon="notifications" intent="warning" interactive>
公告: 吾生也有涯
</Tag> */}
</>
)
}

111
src/ui/Register/Register.tsx

@ -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>
</>
)
}

21
src/utils/toast.ts

@ -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
}

3
src/views/Child.tsx

@ -1,5 +1,4 @@
import { Button, Dialog, DialogBody, DialogFooter } from "@blueprintjs/core";
import { useCallback, useState } from "react";
import { Button } from "@blueprintjs/core";
function Child() {
return <>

42
src/views/Home/index.tsx

@ -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>
</>
)
})

1
vite.config.ts

@ -4,6 +4,7 @@ import react from "@vitejs/plugin-react-swc"
// https://vite.dev/config/
export default defineConfig({
base: "./",
resolve: {
alias: {
"@": join(__dirname, "src"),

Loading…
Cancel
Save