update user navbars

pull/52/head
codemystery 4 years ago
parent 25acb1cf9b
commit 4feea1b987

@ -11,7 +11,8 @@ export let config = {
},
user: {
nicknameMaxLen: 64,
usernameMaxLen: 39
usernameMaxLen: 39,
navbarMaxLen: 20
}
}

@ -9,7 +9,7 @@ import { useRouter } from "next/router"
import React, { useEffect, useState } from "react"
import { FaFacebook, FaFile, FaGithub, FaHeart, FaPlus, FaRegStar, FaStackOverflow, FaStar, FaTwitter, FaWeibo, FaZhihu } from "react-icons/fa"
import { ReserveUrls } from "src/data/reserve-urls"
import { User } from "src/types/user"
import { Navbar, NavbarType, User } from "src/types/user"
import { requestApi } from "utils/axios/request"
import moment from 'moment'
import { Story } from "src/types/story"
@ -35,6 +35,7 @@ const UserPage = () => {
const [tags, setTags]: [Tag[], any] = useState([])
const [tagFilter, setTagFilter]: [Tag, any] = useState(null)
const [followers, setFollowers]: [User[], any] = useState([])
const [navbars,setNavbars]:[Navbar[],any] = useState([])
const borderColor = useColorModeValue('white', 'transparent')
const stackBorderColor = useColorModeValue(userCustomTheme.borderColor.light, userCustomTheme.borderColor.dark)
useEffect(() => {
@ -48,12 +49,17 @@ const UserPage = () => {
setUser(res.data)
getTags(res.data.id)
getNavbars(res.data.id)
const res1 = await requestApi.get(`/user/posts/${res.data.id}`)
setPosts(res1.data)
setRawPosts(res1.data)
}
const getNavbars = async userID => {
const res = await requestApi.get(`/user/navbars/${userID}`)
setNavbars(res.data)
}
const getTags = async (userID) => {
const res = await requestApi.get(`/tag/user/${userID}`)
setTags(res.data)
@ -129,11 +135,15 @@ const UserPage = () => {
</Box>
</Link>
<Link href={`/${username}?nav=react`}>
{
navbars.map(nv =>
<Link href={nv.type === NavbarType.Link ? nv.value : `${ReserveUrls.Series}/${nv.value}`}>
<Box cursor="pointer" fontWeight={isSubNavActive('react') ? "bold" : "550"} layerStyle={isSubNavActive('react') ? null : "textSecondary"}>
REACT
{nv.label}
</Box>
</Link>
</Link>)
}
</HStack>

@ -1,110 +1,188 @@
import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, } from "@chakra-ui/react"
import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, Table, Thead, Tr, Th, Tbody, Td, IconButton, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, NumberInput, NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper } from "@chakra-ui/react"
import Card from "components/card"
import Nav from "layouts/nav/nav"
import PageContainer from "layouts/page-container"
import Sidebar from "layouts/sidebar/sidebar"
import React, { useEffect, useState } from "react"
import { adminLinks, settingLinks } from "src/data/links"
import { settingLinks } from "src/data/links"
import { requestApi } from "utils/axios/request"
import { useRouter } from "next/router"
import { Field, Form, Formik } from "formik"
import { config } from "configs/config"
import Tags from "components/tags/tags"
var validator = require('validator');
import { getSvgIcon } from "components/svg-icon"
import { Navbar, NavbarType } from "src/types/user"
import { cloneDeep } from "lodash"
import { IDType } from "src/types/id"
import { Story } from "src/types/story"
const UserNavbarPage = () => {
const [user, setUser] = useState(null)
const [skills, setSkills] = useState([])
const [isLargerThan1280] = useMediaQuery("(min-width: 768px)")
const [navbars, setNavbars]:[Navbar[],any] = useState([])
const [series, setSeries]: [Story[], any] = useState([])
const [currentNavbar, setCurrentNavbar]: [Navbar, any] = useState(null)
const { isOpen, onOpen, onClose } = useDisclosure()
const toast = useToast()
useEffect(() => {
requestApi.get("/user/self").then(res => setUser(res.data))
getNavbars()
getSeries()
}, [])
const router = useRouter()
const toast = useToast()
const submitUser = async (values, _) => {
await requestApi.post(`/user/update`, values)
setUser(values)
const getNavbars = async () => {
const res = await requestApi.get("/user/navbars/0")
setNavbars(res.data)
}
const getSeries = async () => {
const res = await requestApi.get(`/story/posts/editor?type=${IDType.Series}`)
setSeries(res.data)
}
const submitNavbar = async () => {
if (!currentNavbar.label || !currentNavbar.value) {
toast({
description: "更新成功",
status: "success",
description: "值不能为空",
status: "error",
duration: 2000,
isClosable: true,
})
return
}
function validateNickname(value) {
let error
if (!value?.trim()) {
error = "昵称不能为空"
if (currentNavbar.label.length > config.user.navbarMaxLen) {
toast({
description: `Label长度不能超过${config.user.navbarMaxLen}`,
status: "error",
duration: 2000,
isClosable: true,
})
return
}
if (value?.length > config.user.nicknameMaxLen) {
error = `长度不能超过${config.user.nicknameMaxLen}`
}
return error
await requestApi.post(`/user/navbar`, currentNavbar)
setCurrentNavbar(null)
onClose()
getNavbars()
}
function validateEmail(value) {
let email = value?.trim()
let error
if (email?.length > config.user.usernameMaxLen) {
error = `长度不能超过${config.user.usernameMaxLen}`
return error
const onAddNavbar = () => {
setCurrentNavbar({ weight: 0, type: NavbarType.Link, label: "", value: "https://" })
onOpen()
}
if (email) {
if (!validator.isEmail(email)) {
error = "Email格式不合法"
return error
}
const onEditNavbar = nav => {
setCurrentNavbar(nav)
onOpen()
}
return error
}
function validateUrl(value, canBeEmpty = true) {
let url = value?.trim()
let error
if (!canBeEmpty) {
if (!url) {
error = "url不能为空"
return error
}
const onNavbarChange = () => {
const nv = cloneDeep(currentNavbar)
setCurrentNavbar(nv)
}
if (url) {
if (!validator.isURL(value)) {
error = "URL格式不合法"
return error
const onNvTypeChange = v => {
const tp = parseInt(v);
currentNavbar.type = tp
if (tp === NavbarType.Link) {
currentNavbar.value = ""
} else {
currentNavbar.value = series[0].id
}
onNavbarChange()
}
const getSeriesTitle = id => {
for (const s of series) {
if (s.id === id) {
return s.title
}
return error
}
function validateLen(value) {
let error
if (value?.length > config.commonMaxlen) {
error = `长度不能超过${config.commonMaxlen}`
return ""
}
return error
const onDeleteNavbar = async id => {
requestApi.delete(`/user/navbar/${id}`)
getNavbars()
}
const Layout = isLargerThan1280 ? HStack : VStack
return (
<>
<PageContainer>
<Box display="flex">
<Sidebar routes={settingLinks} width={["120px", "120px", "250px", "250px"]} height="fit-content" title="博客设置" />
{user && <Card ml="4" width="100%">
<Heading></Heading>
</Card>}
<Card ml="4" width="100%">
<Flex justifyContent="space-between" alignItems="center">
<Heading size="sm"></Heading>
<Button colorScheme="teal" size="sm" onClick={onAddNavbar} _focus={null}></Button>
</Flex>
<Table variant="simple" mt="4">
<Thead>
<Tr>
<Th>Label</Th>
<Th>Type</Th>
<Th>Value</Th>
<Th>Weight</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{
navbars.map((nv,i) => <Tr key={i}>
<Td>{nv.label}</Td>
<Td>{nv.type === NavbarType.Link ? "link" : "series"}</Td>
<Td>{nv.type === NavbarType.Link ? nv.value : getSeriesTitle(nv.value)}</Td>
<Td>{nv.weight}</Td>
<Td>
<IconButton aria-label="edit navbar" variant="ghost" icon={getSvgIcon('edit', ".95rem")} onClick={() => onEditNavbar(nv)}/>
<IconButton aria-label="delete navbar" variant="ghost" icon={getSvgIcon('close', "1rem")} onClick={() => onDeleteNavbar(nv.id)} />
</Td>
</Tr>)
}
</Tbody>
</Table>
</Card>
</Box>
</PageContainer>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
{currentNavbar && <ModalContent>
<ModalHeader>{currentNavbar.label ? "编辑菜单项" : "新建菜单项"}</ModalHeader>
<ModalBody mb="2">
<VStack spacing="4" alignItems="left">
<HStack spacing="4">
<Heading size="xs">Label</Heading>
<Input value={currentNavbar.label} _focus={null} variant="flushed" onChange={e => { currentNavbar.label = e.currentTarget.value; onNavbarChange() }}></Input>
</HStack>
<HStack spacing="4">
<Heading size="xs">Type</Heading>
<Select value={currentNavbar.type} onChange={e => onNvTypeChange(e.currentTarget.value)} variant="flushed" _focus={null}>
<option value={NavbarType.Link}>Link</option>
<option value={NavbarType.Series}>Series</option>
</Select>
</HStack>
<HStack spacing="4">
<Heading size="xs">Value</Heading>
{currentNavbar.type === NavbarType.Link ? <Input value={currentNavbar.value} _focus={null} variant="flushed" onChange={e => { currentNavbar.value = e.currentTarget.value; onNavbarChange() }} /> :
<Select value={currentNavbar.value} variant="flushed" _focus={null} onChange={e => { currentNavbar.value = e.currentTarget.value; onNavbarChange() }}>
{series.map(s => <option key={s.id} value={s.id}>{s.title}</option>)}
</Select>}
</HStack>
<HStack spacing="4">
<Heading size="xs">Weight</Heading>
<NumberInput min={0} max={10} value={currentNavbar.weight} variant="flushed" onChange={e => { currentNavbar.weight = parseInt(e); onNavbarChange() }}>
<NumberInputField _focus={null} />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</HStack>
</VStack>
<Button colorScheme="teal" variant="outline" mt="6" onClick={submitNavbar}></Button>
</ModalBody>
</ModalContent>}
</Modal>
</>
)
}

@ -98,7 +98,7 @@ func SubmitNavbar(c *gin.Context) {
}
nav.UserID = u.ID
err1 := user.AddNavbar(nav)
err1 := user.SubmitNavbar(nav)
if err != nil {
c.JSON(err1.Status, common.RespError(err1.Message))
return
@ -106,3 +106,33 @@ func SubmitNavbar(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
func GetNavbars(c *gin.Context) {
userID := c.Param("userID")
if userID == "0" {
u := user.CurrentUser(c)
userID = u.ID
}
navbars, err := user.GetNavbars(userID)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(navbars))
}
func DeleteNavbar(c *gin.Context) {
id := c.Param("id")
u := user.CurrentUser(c)
err := user.DeleteNavbar(u.ID, id)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}

@ -25,6 +25,7 @@ type PostsConfig struct {
type UserConfig struct {
NicknameMaxLen int `json:"nicknameMaxLen"`
UsernameMaxLen int `json:"usernameMaxLen"`
NavabarMaxLen int `json:"navbarMaxLen"`
}
// 在后台页面配置存储到mysql中
@ -41,6 +42,7 @@ func GetConfig(c *gin.Context) {
User: &UserConfig{
UsernameMaxLen: 39,
NicknameMaxLen: 64,
NavabarMaxLen: 20,
},
}

@ -85,6 +85,8 @@ func (s *Server) Start() error {
r.POST("/user/login", user.Login)
r.POST("/user/logout", user.Logout)
r.POST("/user/navbar", IsLogin(), api.SubmitNavbar)
r.GET("/user/navbars/:userID", api.GetNavbars)
r.DELETE("/user/navbar/:id", IsLogin(), api.DeleteNavbar)
// interaction apis
r.POST("/interaction/like/:id", IsLogin(), api.Like)
r.POST("/interaction/follow/:id", IsLogin(), api.Follow)

@ -188,6 +188,7 @@ var sqlTables = map[string]string{
`,
"user_navbar": `CREATE TABLE IF NOT EXISTS user_navbar (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id VARCHAR(255),
label VARCHAR(20),
type TINYINT,

@ -9,11 +9,17 @@ import (
"github.com/imdotdev/im.dev/server/pkg/models"
)
func AddNavbar(nav *models.Navbar) *e.Error {
_, err := db.Conn.Exec("INSERT INTO user_navbar (user_id,label,type,value,weight) VALUES (?,?,?,?,?)",
func SubmitNavbar(nav *models.Navbar) *e.Error {
var err error
if nav.ID == 0 {
_, err = db.Conn.Exec("INSERT INTO user_navbar (user_id,label,type,value,weight) VALUES (?,?,?,?,?)",
nav.UserID, nav.Label, nav.Type, nav.Value, nav.Weight)
} else {
_, err = db.Conn.Exec("UPDATE user_navbar SET label=?,type=?,value=?,weight=? WHERE id=? and user_id=?", nav.Label, nav.Type, nav.Value, nav.Weight, nav.ID, nav.UserID)
}
if err != nil {
logger.Warn("add user navbar error", "error", err)
logger.Warn("submit user navbar error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
@ -21,7 +27,7 @@ func AddNavbar(nav *models.Navbar) *e.Error {
}
func GetNavbars(userID string) (models.Navbars, *e.Error) {
rows, err := db.Conn.Query("SELECT label,type,value,weight FROM navbar WHERE user_id=?", userID)
rows, err := db.Conn.Query("SELECT id,label,type,value,weight FROM user_navbar WHERE user_id=?", userID)
if err != nil {
logger.Warn("get user navbar error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
@ -30,7 +36,7 @@ func GetNavbars(userID string) (models.Navbars, *e.Error) {
navs := make(models.Navbars, 0)
for rows.Next() {
nav := &models.Navbar{}
rows.Scan(&nav.Label, &nav.Type, &nav.Value, &nav.Weight)
rows.Scan(&nav.ID, &nav.Label, &nav.Type, &nav.Value, &nav.Weight)
navs = append(navs, nav)
}
@ -38,3 +44,13 @@ func GetNavbars(userID string) (models.Navbars, *e.Error) {
return navs, nil
}
func DeleteNavbar(userID string, id string) *e.Error {
_, err := db.Conn.Exec("DELETE FROM user_navbar WHERE id=? and user_id=?", id, userID)
if err != nil {
logger.Warn("submit user navbar error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
return nil
}

@ -6,6 +6,7 @@ const (
)
type Navbar struct {
ID int `json:"id"`
UserID string `json:"userID"`
Label string `json:"label"`
Type int `json:"type"`

@ -52,6 +52,9 @@ export function getSvgIcon(name, height = "1.4rem") {
case "navbar":
svg = <svg height={height} fill="currentColor" viewBox="0 0 448 512"><path d="M424 96h-80c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96h-64v-64h64v64zM264 96h-80c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96h-64v-64h64v64zM104 96H24c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96H32v-64h64v64zm328 96h-80c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96h-64v-64h64v64zm-152-96h-80c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96h-64v-64h64v64zm-152-96H24c-13.22 0-24 10.77-24 24v80c0 13.23 10.78 24 24 24h80c13.22 0 24-10.77 24-24v-80c0-13.23-10.78-24-24-24zm-8 96H32v-64h64v64z"></path></svg>
break
case "close":
svg = <svg height={height} fill="currentColor" viewBox="0 0 320 512"><path d="M193.94 256L296.5 153.44l21.15-21.15c3.12-3.12 3.12-8.19 0-11.31l-22.63-22.63c-3.12-3.12-8.19-3.12-11.31 0L160 222.06 36.29 98.34c-3.12-3.12-8.19-3.12-11.31 0L2.34 120.97c-3.12 3.12-3.12 8.19 0 11.31L126.06 256 2.34 379.71c-3.12 3.12-3.12 8.19 0 11.31l22.63 22.63c3.12 3.12 8.19 3.12 11.31 0L160 289.94 262.56 392.5l21.15 21.15c3.12 3.12 8.19 3.12 11.31 0l22.63-22.63c3.12-3.12 3.12-8.19 0-11.31L193.94 256z"></path></svg>
break
default:
break;
}

@ -46,3 +46,16 @@ export interface UserSimple {
nickname: string
avatar: string
}
export enum NavbarType {
Link = 1,
Series = 2
}
export interface Navbar {
id?: number,
label: string
type: number
value: string
weight: number
}
Loading…
Cancel
Save