mirror of https://github.com/sunface/rust-course
parent
e5511c3790
commit
0dbe149de9
@ -1,27 +1,112 @@
|
||||
import { chakra } from "@chakra-ui/react"
|
||||
import { AddIcon } from "@chakra-ui/icons"
|
||||
import {
|
||||
Box, Button, chakra, Flex, HStack, VStack, Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
IconButton,
|
||||
Heading,
|
||||
Divider
|
||||
} from "@chakra-ui/react"
|
||||
import Card from "components/card"
|
||||
import PostCard from "components/posts/post-card"
|
||||
import Posts from "components/posts/posts"
|
||||
import SimplePostCard from "components/posts/simple-post-card"
|
||||
import SEO from "components/seo"
|
||||
import siteConfig from "configs/site-config"
|
||||
import Nav from "layouts/nav/nav"
|
||||
import PageContainer from "layouts/page-container"
|
||||
import React from "react"
|
||||
|
||||
const HomePage = () => (
|
||||
<>
|
||||
<SEO
|
||||
title={siteConfig.seo.title}
|
||||
description={siteConfig.seo.description}
|
||||
/>
|
||||
<PageContainer>
|
||||
<Card width="200px">
|
||||
<chakra.h1>NOT FOUND</chakra.h1>
|
||||
<p>You just hit a route that doesn't exist... the sadness.</p>
|
||||
</Card>
|
||||
|
||||
|
||||
</PageContainer>
|
||||
</>
|
||||
)
|
||||
import PageContainer1 from "layouts/page-container1"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { PostFilter } from "src/types/posts"
|
||||
import { requestApi } from "utils/axios/request"
|
||||
|
||||
const HomePage = () => {
|
||||
const [posts,setPosts] = useState([])
|
||||
const [filter, setFilter] = useState(PostFilter.Best)
|
||||
const initData = async () => {
|
||||
const res = await requestApi.get(`/home/posts/${filter}`)
|
||||
setPosts(res.data)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
initData()
|
||||
},[filter])
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO
|
||||
title={siteConfig.seo.title}
|
||||
description={siteConfig.seo.description}
|
||||
/>
|
||||
<PageContainer1>
|
||||
<HStack alignItems="top" p="4">
|
||||
<VStack alignItems="left" width={["100%", "100%", "70%", "70%"]}>
|
||||
<Card p="2">
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<HStack>
|
||||
<Button _focus={null} onClick={() => setFilter(PostFilter.Best)} size="sm" colorScheme={filter === PostFilter.Best ? 'teal' : null} leftIcon={<svg fill="currentColor" height="1.4rem" viewBox="0 0 448 512"><path d="M448 281.6c0-53.27-51.98-163.13-124.44-230.4-20.8 19.3-39.58 39.59-56.22 59.97C240.08 73.62 206.28 35.53 168 0 69.74 91.17 0 209.96 0 281.6 0 408.85 100.29 512 224 512c.53 0 1.04-.08 1.58-.08.32 0 .6.08.92.08 1.88 0 3.71-.35 5.58-.42C352.02 507.17 448 406.04 448 281.6zm-416 0c0-50.22 47.51-147.44 136.05-237.09 27.38 27.45 52.44 56.6 73.39 85.47l24.41 33.62 26.27-32.19a573.83 573.83 0 0130.99-34.95C379.72 159.83 416 245.74 416 281.6c0 54.69-21.53 104.28-56.28 140.21 12.51-35.29 10.88-75.92-8.03-112.02a357.34 357.34 0 00-10.83-19.19l-22.63-37.4-28.82 32.87-25.86 29.5c-24.93-31.78-59.31-75.5-63.7-80.54l-24.65-28.39-24.08 28.87C108.16 287 80 324.21 80 370.41c0 19.02 3.62 36.66 9.77 52.79C54.17 387.17 32 337.03 32 281.6zm193.54 198.32C162.86 479.49 112 437.87 112 370.41c0-33.78 21.27-63.55 63.69-114.41 6.06 6.98 86.48 109.68 86.48 109.68l51.3-58.52a334.43 334.43 0 019.87 17.48c23.92 45.66 13.83 104.1-29.26 134.24-17.62 12.33-39.14 19.71-62.37 20.73-2.06.07-4.09.29-6.17.31z"></path></svg>} variant="ghost" >Best</Button>
|
||||
<Button _focus={null} onClick={() => setFilter(PostFilter.Featured)} size="sm" colorScheme={filter === PostFilter.Featured ? 'teal' : null} leftIcon={<svg fill="currentColor" height="1.4rem" viewBox="0 0 512 512"><path d="M493.7 232.4l-140.2-35 66.9-83.3c5.2-6.5 4.7-15.5-1.1-21.3-5.9-5.8-14.8-6.3-21.3-1.1l-83.4 66.7-35-140c-6.1-24.4-41-24.4-47.2 0l-35 140.2-83.3-67c-6.5-5.2-15.5-4.8-21.3 1.1-5.8 5.8-6.3 14.8-1.1 21.4l66.7 83.4-140 35C7.5 235.2 0 244.7 0 256c0 10.2 6.5 20.7 18.4 23.6l140.2 35-66.9 83.3c-5.2 6.5-4.7 15.5 1.1 21.3 5.6 5.5 14.5 6.5 21.3 1.1l83.4-66.7 35 140c3 11.9 13.3 18.4 23.6 18.4 4.5 0 19.4-2.1 23.6-18.4l35-140.2 83.3 67c6.9 5.5 15.8 4.4 21.3-1.1 5.8-5.8 6.3-14.8 1.1-21.3l-66.7-83.4 139.9-35c11.7-2.9 18.5-13.1 18.5-23.6-.1-10.3-6.6-20.6-18.4-23.6zM296 296l-40 160-40-160-160-40 160-40 40-160 40 160 160 40-160 40z"></path></svg>} variant="ghost">Fetured</Button>
|
||||
<Button _focus={null} onClick={() => setFilter(PostFilter.Recent)} size="sm" colorScheme={filter === PostFilter.Recent ? 'teal' : null} leftIcon={<svg fill="currentColor" height="1.4rem" viewBox="0 0 512 512"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm216 248c0 118.7-96.1 216-216 216-118.7 0-216-96.1-216-216 0-118.7 96.1-216 216-216 118.7 0 216 96.1 216 216zm-148.9 88.3l-81.2-59c-3.1-2.3-4.9-5.9-4.9-9.7V116c0-6.6 5.4-12 12-12h14c6.6 0 12 5.4 12 12v146.3l70.5 51.3c5.4 3.9 6.5 11.4 2.6 16.8l-8.2 11.3c-3.9 5.3-11.4 6.5-16.8 2.6z"></path></svg>} variant="ghost">Fetured</Button>
|
||||
</HStack>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label="Options"
|
||||
icon={<svg fill="none" stroke="currentColor" opacity="0.75" height="1.3rem" viewBox="0 0 55 55"><path d="M2 2h51v21H2V2zm0 30h51v21H2V32z" stroke="stroke-current" strokeWidth="4"></path></svg>}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
_focus={null}
|
||||
/>
|
||||
<MenuList>
|
||||
<MenuItem icon={<svg fill="none" stroke="currentColor" opacity="0.75" height="1.3rem" viewBox="0 0 55 55"><path d="M2 2h51v21H2V2zm0 30h51v21H2V32z" stroke="stroke-current" strokeWidth="4"></path></svg>}>
|
||||
Modern
|
||||
</MenuItem>
|
||||
<MenuItem icon={<svg stroke="currentColor" height="1.2rem" viewBox="0 0 55 55" fill="none"><path d="M2 2h51v11H2V2zm0 40h51v11H2V42zm0-20h51v11H2V22z" stroke="stoke-current" strokeWidth="4"></path></svg>}>
|
||||
Compact
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Flex>
|
||||
</Card>
|
||||
<Card width="100%" height="fit-content" p="0" px="3">
|
||||
<Posts posts={posts} />
|
||||
</Card>
|
||||
</VStack>
|
||||
<HomeSidebar />
|
||||
</HStack>
|
||||
|
||||
</PageContainer1>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default HomePage
|
||||
|
||||
|
||||
export const HomeSidebar = () => {
|
||||
const [posts,setPosts] = useState([])
|
||||
const [filter, setFilter] = useState(PostFilter.Best)
|
||||
const initData = async () => {
|
||||
const res = await requestApi.get(`/home/posts/${filter}`)
|
||||
setPosts(res.data)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
initData()
|
||||
},[filter])
|
||||
|
||||
return (
|
||||
<VStack alignItems="left" width="30%" display={{ base: "none", md: "flex" }}>
|
||||
<Card p="0">
|
||||
<HStack px="4" py="3">
|
||||
<Heading size="sm">Top ariticles</Heading>
|
||||
<Button variant="ghost" size="sm">1d</Button>
|
||||
<Button variant="ghost" size="sm">1w</Button>
|
||||
<Button variant="ghost" size="sm">1m</Button>
|
||||
</HStack>
|
||||
<Divider />
|
||||
<VStack px="4" pt="1" alignItems="left">
|
||||
<Posts posts={posts} card={SimplePostCard} size="sm" showFooter={false}></Posts>
|
||||
</VStack>
|
||||
</Card>
|
||||
</VStack>
|
||||
)
|
||||
}
|
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 7.2 KiB |
@ -0,0 +1,56 @@
|
||||
import React from "react"
|
||||
import { Box, chakra, Flex, Heading, HStack, Image, Text, useMediaQuery, VStack } from "@chakra-ui/react"
|
||||
import { Post } from "src/types/posts"
|
||||
import PostAuthor from "./post-author"
|
||||
import Link from "next/link"
|
||||
import UnicornLike from "./heart-like"
|
||||
import { FaHeart, FaRegBookmark, FaRegComment, FaRegHeart } from "react-icons/fa"
|
||||
import SvgButton from "components/svg-button"
|
||||
|
||||
interface Props {
|
||||
post: Post
|
||||
}
|
||||
|
||||
|
||||
export const PostCard = (props: Props) => {
|
||||
const { post } = props
|
||||
const [isLargeScreen] = useMediaQuery("(min-width: 768px)")
|
||||
const Layout = isLargeScreen ? HStack : VStack
|
||||
return (
|
||||
<VStack alignItems="left" spacing="4" p="1">
|
||||
<PostAuthor post={post} showFooter={false} size="md" />
|
||||
<Link href={`/${post.creator.username}/${post.id}`}>
|
||||
<Layout alignItems={isLargeScreen ? "top" : "left"} cursor="pointer" pl="2" pt="1">
|
||||
<VStack alignItems="left" spacing="3" width={isLargeScreen ? "calc(100% - 18rem)" : '100%'}>
|
||||
<Heading size="md">{post.title}</Heading>
|
||||
<Text layerStyle="textSecondary">{post.brief}</Text>
|
||||
</VStack>
|
||||
{post.cover && <Image src={post.cover} width="18rem" height="120px" pt={isLargeScreen ? 0 : 2} borderRadius="4px" />}
|
||||
</Layout>
|
||||
</Link>
|
||||
|
||||
<HStack pl="2" spacing="5">
|
||||
<HStack opacity="0.9">
|
||||
{post.liked ?
|
||||
<Box color="red.400"><FaHeart fontSize="1.1rem" /></Box>
|
||||
:
|
||||
<FaRegHeart fontSize="1.1rem" />}
|
||||
<Text ml="2">{post.likes}</Text>
|
||||
</HStack>
|
||||
|
||||
<Link href={`/${post.creator.username}/${post.id}#comments`}>
|
||||
<HStack opacity="0.9" cursor="pointer">
|
||||
<FaRegComment fontSize="1.1rem" />
|
||||
<Text ml="2">{post.comments}</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
|
||||
|
||||
|
||||
<SvgButton icon="bookmark" height="1rem" onClick={null} style={{marginLeft: '4px'}}/>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default PostCard
|
@ -0,0 +1,32 @@
|
||||
import React from "react"
|
||||
import { Box, Center, Text, useColorModeValue, VStack } from "@chakra-ui/react"
|
||||
import { Post } from "src/types/posts"
|
||||
import PostCard from "./post-card"
|
||||
import userCustomTheme from "theme/user-custom"
|
||||
|
||||
interface Props {
|
||||
posts: Post[]
|
||||
card?: any
|
||||
size?: 'sm' | 'md'
|
||||
showFooter?: boolean
|
||||
}
|
||||
|
||||
|
||||
export const Posts = (props: Props) => {
|
||||
const { posts,card=PostCard,showFooter=true} = props
|
||||
const postBorderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
|
||||
const Card = card
|
||||
return (
|
||||
<>
|
||||
<VStack alignItems="left">
|
||||
{posts.map(post =>
|
||||
<Box py="4" borderBottom={`1px solid ${postBorderColor}`} key={post.id}>
|
||||
<Card post={post} size={props.size}/>
|
||||
</Box>)}
|
||||
</VStack>
|
||||
{showFooter && <Center><Text layerStyle="textSecondary" fontSize="sm" py="4">没有更多文章了</Text></Center>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Posts
|
@ -0,0 +1,49 @@
|
||||
import React from "react"
|
||||
import { Box, chakra, Flex, Heading, HStack, Image, Text, useMediaQuery, VStack } from "@chakra-ui/react"
|
||||
import { Post } from "src/types/posts"
|
||||
import PostAuthor from "./post-author"
|
||||
import Link from "next/link"
|
||||
import UnicornLike from "./heart-like"
|
||||
import { FaHeart, FaRegBookmark, FaRegComment, FaRegHeart } from "react-icons/fa"
|
||||
import SvgButton from "components/svg-button"
|
||||
|
||||
interface Props {
|
||||
post: Post
|
||||
size?: 'md' | 'sm'
|
||||
}
|
||||
|
||||
|
||||
export const SimplePostCard = (props: Props) => {
|
||||
const { post,size='md' } = props
|
||||
const [isLargeScreen] = useMediaQuery("(min-width: 768px)")
|
||||
const Layout = isLargeScreen ? HStack : VStack
|
||||
return (
|
||||
<VStack alignItems="left" spacing="0">
|
||||
<Link href={`/${post.creator.username}/${post.id}`}><Heading pb="2" size="sm" cursor="pointer">{post.title}</Heading></Link>
|
||||
<HStack pl="1" spacing="5" fontSize={size==='md'? '1rem' : ".9rem"}>
|
||||
<Link href={`/${post.creator.username}`}><Text cursor="pointer">{post.creator.nickname}</Text></Link>
|
||||
|
||||
<HStack opacity="0.9">
|
||||
{post.liked ?
|
||||
<Box color="red.400"><FaHeart fontSize="1.1rem" /></Box>
|
||||
:
|
||||
<FaRegHeart fontSize="1.1rem" />}
|
||||
<Text ml="2">{post.likes}</Text>
|
||||
</HStack>
|
||||
|
||||
<Link href={`/${post.creator.username}/${post.id}#comments`}>
|
||||
<HStack opacity="0.9" cursor="pointer">
|
||||
<FaRegComment fontSize="1.1rem" />
|
||||
<Text ml="2">{post.comments}</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
|
||||
|
||||
|
||||
<SvgButton icon="bookmark" height="1rem" onClick={null} style={{marginLeft: '4px'}}/>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
export default SimplePostCard
|
@ -0,0 +1,39 @@
|
||||
import React from "react"
|
||||
import {chakra, PropsOf, IconButton } from "@chakra-ui/react"
|
||||
|
||||
type Props = PropsOf<typeof chakra.div> & {
|
||||
icon: 'bookmark' | 'edit' | 'share'
|
||||
onClick: any
|
||||
height?: string
|
||||
}
|
||||
|
||||
|
||||
export const SvgButton= (props:Props) =>{
|
||||
const {icon,height="1.7rem",...rest} = props
|
||||
|
||||
let iconSvg
|
||||
switch (icon) {
|
||||
case "bookmark":
|
||||
iconSvg = <svg height={height} fill="currentColor" viewBox="0 0 384 512"><path d="M336 0H48C21.49 0 0 21.49 0 48v464l192-112 192 112V48c0-26.51-21.49-48-48-48zm16 456.287l-160-93.333-160 93.333V48c0-8.822 7.178-16 16-16h288c8.822 0 16 7.178 16 16v408.287z"></path></svg>
|
||||
break;
|
||||
case "share":
|
||||
iconSvg = <svg height="1.7rem" fill="currentColor" viewBox="0 0 448 512"><path d="M352 320c-28.6 0-54.2 12.5-71.8 32.3l-95.5-59.7c9.6-23.4 9.7-49.8 0-73.2l95.5-59.7c17.6 19.8 43.2 32.3 71.8 32.3 53 0 96-43 96-96S405 0 352 0s-96 43-96 96c0 13 2.6 25.3 7.2 36.6l-95.5 59.7C150.2 172.5 124.6 160 96 160c-53 0-96 43-96 96s43 96 96 96c28.6 0 54.2-12.5 71.8-32.3l95.5 59.7c-4.7 11.3-7.2 23.6-7.2 36.6 0 53 43 96 96 96s96-43 96-96c-.1-53-43.1-96-96.1-96zm0-288c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zM96 320c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64zm256 160c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z"></path></svg>
|
||||
break
|
||||
case "edit":
|
||||
iconSvg = <svg height="1.5rem" fill="currentColor" viewBox="0 0 512 512"><path d="M493.255 56.236l-37.49-37.49c-24.993-24.993-65.515-24.994-90.51 0L12.838 371.162.151 485.346c-1.698 15.286 11.22 28.203 26.504 26.504l114.184-12.687 352.417-352.417c24.992-24.994 24.992-65.517-.001-90.51zm-95.196 140.45L174 420.745V386h-48v-48H91.255l224.059-224.059 82.745 82.745zM126.147 468.598l-58.995 6.555-30.305-30.305 6.555-58.995L63.255 366H98v48h48v34.745l-19.853 19.853zm344.48-344.48l-49.941 49.941-82.745-82.745 49.941-49.941c12.505-12.505 32.748-12.507 45.255 0l37.49 37.49c12.506 12.506 12.507 32.747 0 45.255z"></path></svg>
|
||||
break
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<IconButton
|
||||
aria-label="a icon button"
|
||||
variant="ghost"
|
||||
_focus={null}
|
||||
icon={iconSvg}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default SvgButton
|
@ -0,0 +1,74 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { Box, Tag as ChakraTag, TagCloseButton, TagLabel } from "@chakra-ui/react"
|
||||
import TagInput from "./tag-input"
|
||||
import { requestApi } from "utils/axios/request"
|
||||
import { Tag } from "src/types/tag"
|
||||
import { cloneDeep, remove } from "lodash"
|
||||
|
||||
interface Props {
|
||||
tags: number[]
|
||||
onChange: any
|
||||
size?: 'lg' | 'md'
|
||||
}
|
||||
|
||||
export const Tags = (props: Props) => {
|
||||
// 所有的tags选项
|
||||
const [options, setOptions]: [Tag[], any] = useState([])
|
||||
// 当前已选择的tags
|
||||
const [tags, setTags]: [Tag[], any] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
requestApi.get('/tags').then(res => {
|
||||
setOptions(res.data)
|
||||
const t = []
|
||||
props.tags?.forEach(id => {
|
||||
res.data.forEach(tag => {
|
||||
if (tag.id === id) {
|
||||
t.push(tag)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
setTags(t)
|
||||
})
|
||||
}, [])
|
||||
|
||||
|
||||
const addTag = t => {
|
||||
setTags(t)
|
||||
|
||||
const ids = []
|
||||
t.forEach(tag => ids.push(tag.id))
|
||||
props.onChange(ids)
|
||||
}
|
||||
|
||||
const removeTag = t => {
|
||||
const newTags = cloneDeep(tags)
|
||||
remove(newTags, tag => tag.id === t.id)
|
||||
setTags(newTags)
|
||||
|
||||
const ids = []
|
||||
newTags.forEach(tag => ids.push(tag.id))
|
||||
|
||||
props.onChange(ids)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TagInput options={options} selected={tags} onChange={addTag} size={props.size}/>
|
||||
|
||||
{tags.length > 0 && <Box mt={props.size === 'lg' ? 4 : 2}>
|
||||
{
|
||||
tags.map(tag =>
|
||||
<ChakraTag key={tag.id} mr="2" colorScheme="teal" variant="solid" px="2" py={props.size === 'lg' ? 2 : 1}>
|
||||
<TagLabel>{tag.title}</TagLabel>
|
||||
<TagCloseButton onClick={_ => removeTag(tag)} />
|
||||
</ChakraTag>)
|
||||
}
|
||||
</Box>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tags
|
Loading…
Reference in new issue