pull/52/head
sunface 4 years ago
parent 029473b4bc
commit fc935a6ac7

@ -1,4 +1,4 @@
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, Modal, ModalOverlay, ModalContent, ModalBody, useDisclosure, Alert, AlertIcon, } from "@chakra-ui/react"
import Card from "components/card" import Card from "components/card"
import Nav from "layouts/nav/nav" import Nav from "layouts/nav/nav"
import PageContainer from "layouts/page-container" import PageContainer from "layouts/page-container"
@ -13,12 +13,15 @@ import Tags from "components/tags/tags"
var validator = require('validator'); var validator = require('validator');
const UserProfilePage = () => { const UserProfilePage = () => {
const [user, setUser] = useState(null) const { isOpen, onOpen, onClose } = useDisclosure()
const [org, setOrg] = useState(null)
const [newOnwer, setNewOnwer] = useState('')
const [isLargerThan1280] = useMediaQuery("(min-width: 768px)") const [isLargerThan1280] = useMediaQuery("(min-width: 768px)")
const router = useRouter() const router = useRouter()
useEffect(() => { useEffect(() => {
if (router.query.org_id) { if (router.query.org_id) {
requestApi.get(`/user/info/${router.query.org_id}`).then(res => setUser(res.data)) requestApi.get(`/user/info/${router.query.org_id}`).then(res => setOrg(res.data))
} }
}, [router.query.org_id]) }, [router.query.org_id])
@ -27,7 +30,7 @@ const UserProfilePage = () => {
const submitUser = async (values, _) => { const submitUser = async (values, _) => {
await requestApi.post(`/org/update`, values) await requestApi.post(`/org/update`, values)
setUser(values) setOrg(values)
toast({ toast({
description: "更新成功", description: "更新成功",
status: "success", status: "success",
@ -97,15 +100,26 @@ const UserProfilePage = () => {
return error return error
} }
const transferOrg = async () => {
await requestApi.post(`/org/transfer`,{orgID: router.query.org_id, ownerID: newOnwer})
toast({
description: "转移成功",
status: "success",
duration: 2000,
isClosable: true,
})
router.push('/settings/orgs')
}
const Layout = isLargerThan1280 ? HStack : VStack const Layout = isLargerThan1280 ? HStack : VStack
return ( return (
<> <>
<PageContainer> <PageContainer>
<Box display="flex"> <Box display="flex">
<Sidebar routes={orgSettingLinks(router.query.org_id)} width={["120px", "120px", "250px", "250px"]} height="fit-content" title={`管理${user?.nickname}`} /> <Sidebar routes={orgSettingLinks(router.query.org_id)} width={["120px", "120px", "250px", "250px"]} height="fit-content" title={`管理${org?.nickname}`} />
{user && <VStack alignItems="left" ml="4" width="100%"> {org && <VStack alignItems="left" ml="4" width="100%">
<Formik <Formik
initialValues={user} initialValues={org}
onSubmit={submitUser} onSubmit={submitUser}
> >
{(props) => ( {(props) => (
@ -115,10 +129,15 @@ const UserProfilePage = () => {
<Box width="100%"> <Box width="100%">
<VStack alignItems="left" spacing="6"> <VStack alignItems="left" spacing="6">
<Heading size="sm"></Heading> <Heading size="sm"></Heading>
<Box>
<FormLabel>Username</FormLabel>
<Input placeholder="enter your nick name" size="lg" value={org.username} disabled />
</Box>
<Field name="nickname" validate={validateNickname}> <Field name="nickname" validate={validateNickname}>
{({ field, form }) => ( {({ field, form }) => (
<FormControl isInvalid={form.errors.nickname && form.touched.nickname} > <FormControl isInvalid={form.errors.nickname && form.touched.nickname} >
<FormLabel></FormLabel> <FormLabel></FormLabel>
<Input {...field} placeholder="enter your nick name" size="lg" /> <Input {...field} placeholder="enter your nick name" size="lg" />
<FormErrorMessage>{form.errors.nickname}</FormErrorMessage> <FormErrorMessage>{form.errors.nickname}</FormErrorMessage>
</FormControl> </FormControl>
@ -141,7 +160,7 @@ const UserProfilePage = () => {
<FormLabel></FormLabel> <FormLabel></FormLabel>
<Input {...field} placeholder="输入图片链接可以用github或postimg.cc当图片存储服务" size="lg" /> <Input {...field} placeholder="输入图片链接可以用github或postimg.cc当图片存储服务" size="lg" />
<FormErrorMessage>{form.errors.avatar}</FormErrorMessage> <FormErrorMessage>{form.errors.avatar}</FormErrorMessage>
{user.avatar && <Image width="120px" mt="4" src={user.avatar} />} {org.avatar && <Image width="120px" mt="4" src={org.avatar} />}
</FormControl> </FormControl>
)} )}
@ -152,7 +171,7 @@ const UserProfilePage = () => {
<FormLabel></FormLabel> <FormLabel></FormLabel>
<Input {...field} placeholder="输入图片链接" size="lg" /> <Input {...field} placeholder="输入图片链接" size="lg" />
<FormErrorMessage>{form.errors.cover}</FormErrorMessage> <FormErrorMessage>{form.errors.cover}</FormErrorMessage>
{user.cover && <Image width="100%" mt="4" src={user.cover} />} {org.cover && <Image width="100%" mt="4" src={org.cover} />}
</FormControl> </FormControl>
)} )}
@ -192,7 +211,7 @@ const UserProfilePage = () => {
{({ field, form }) => ( {({ field, form }) => (
<FormControl > <FormControl >
<FormLabel></FormLabel> <FormLabel></FormLabel>
<Tags tags={user.skills} onChange={(v) => form.values.skills = v} size="lg"/> <Tags tags={org.skills} onChange={(v) => form.values.skills = v} size="lg" />
</FormControl> </FormControl>
)} )}
</Field> </Field>
@ -277,10 +296,18 @@ const UserProfilePage = () => {
variant="outline" variant="outline"
type="submit" type="submit"
_focus={null} _focus={null}
> >
</Button> </Button>
<Button
colorScheme="red"
_focus={null}
ml="4"
onClick={onOpen}
>
</Button>
</Box> </Box>
</Form> </Form>
)} )}
@ -288,6 +315,23 @@ const UserProfilePage = () => {
</VStack>} </VStack>}
</Box> </Box>
</PageContainer> </PageContainer>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
{<ModalContent p="3">
<ModalBody mb="2">
<Text></Text>
<Text mt="2" fontSize=".9rem" layerStyle="textSecondary"></Text>
<Input _focus={null} mt="4" placeholder="输入转移用户的ID请找该用户询问" value={newOnwer} onChange={e => setNewOnwer(e.currentTarget.value)} />
<Alert status="error" mt="4">
<AlertIcon />
</Alert>
<Button mt="4" colorScheme="teal" onClick={transferOrg}></Button>
</ModalBody>
</ModalContent>}
</Modal>
</> </>
) )
} }

@ -64,6 +64,16 @@ const UserProfilePage = () => {
<Box width="100%"> <Box width="100%">
<VStack alignItems="left" spacing="6"> <VStack alignItems="left" spacing="6">
<Heading size="sm"></Heading> <Heading size="sm"></Heading>
<Box>
<FormLabel>ID</FormLabel>
<Input size="lg" value={user.id} disabled />
</Box>
<Box>
<FormLabel>Username</FormLabel>
<Input size="lg" value={user.username} disabled />
</Box>
<Field name="nickname" validate={validateNickname}> <Field name="nickname" validate={validateNickname}>
{({ field, form }) => ( {({ field, form }) => (
<FormControl isInvalid={form.errors.nickname && form.touched.nickname} > <FormControl isInvalid={form.errors.nickname && form.touched.nickname} >

@ -110,7 +110,7 @@ func GenOrgSecret(c *gin.Context) {
func GetOrgSecret(c *gin.Context) { func GetOrgSecret(c *gin.Context) {
orgID := c.Param("id") orgID := c.Param("id")
currentUser := user.CurrentUser(c) currentUser := user.CurrentUser(c)
if !org.IsOrgAdmin(currentUser.ID, orgID) { if !org.UserInOrg(currentUser.ID, orgID) {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission)) c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
return return
} }
@ -209,3 +209,40 @@ func UpdateOrgMember(c *gin.Context) {
c.JSON(http.StatusOK, common.RespSuccess(nil)) c.JSON(http.StatusOK, common.RespSuccess(nil))
} }
type TransferReq struct {
OrgID string `json:"orgID"`
OwnerID string `json:"ownerID"`
}
func TransferOrg(c *gin.Context) {
req := &TransferReq{}
c.Bind(&req)
// 检查当前用户是否是组织的超级管理员
currentUser := user.CurrentUser(c)
currentRole, err := org.GetMemberRole(req.OrgID, currentUser.ID)
if err != nil {
c.JSON(http.StatusBadRequest, common.RespError(e.BadRequest))
return
}
if currentRole != models.ROLE_SUPER_ADMIN {
c.JSON(http.StatusForbidden, common.RespError("只有超级管理员才能转移组织"))
return
}
// 检查目标用户是否是组织成员
if !org.UserInOrg(req.OwnerID, req.OrgID) {
c.JSON(http.StatusForbidden, common.RespError("目标用户不是组织成员"))
return
}
err0 := org.Transfer(req.OrgID, currentUser.ID, req.OwnerID)
if err0 != nil {
c.JSON(err0.Status, common.RespError(err0.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}

@ -171,3 +171,28 @@ func GetMemberRole(orgID string, memberID string) (models.RoleType, error) {
return role, nil return role, nil
} }
func Transfer(orgID, currentOwner, newOwner string) *e.Error {
tx, err := db.Conn.Begin()
if err != nil {
logger.Warn("start sql transaction error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
_, err = tx.Exec("UPDATE org_member SET role=? WHERE org_id=? and user_id=?", models.ROLE_SUPER_ADMIN, orgID, newOwner)
if err != nil {
logger.Warn("transfer org error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
_, err = tx.Exec("UPDATE org_member SET role=? WHERE org_id=? and user_id=?", models.ROLE_NORMAL, orgID, currentOwner)
if err != nil {
logger.Warn("transfer org error", "error", err)
tx.Rollback()
return e.New(http.StatusInternalServerError, e.Internal)
}
tx.Commit()
return nil
}

@ -112,6 +112,7 @@ func (s *Server) Start() error {
r.GET("/org/secret/:id", IsLogin(), api.GetOrgSecret) r.GET("/org/secret/:id", IsLogin(), api.GetOrgSecret)
r.POST("/org/join", IsLogin(), api.JoinOrg) r.POST("/org/join", IsLogin(), api.JoinOrg)
r.POST("/org/member/role", IsLogin(), api.UpdateOrgMember) r.POST("/org/member/role", IsLogin(), api.UpdateOrgMember)
r.POST("/org/transfer", api.TransferOrg)
// admin apis // admin apis
r.POST("/admin/user", IsLogin(), api.AdminSubmitUser) r.POST("/admin/user", IsLogin(), api.AdminSubmitUser)
r.GET("/admin/user/all", IsLogin(), api.AdminGetUsers) r.GET("/admin/user/all", IsLogin(), api.AdminGetUsers)

@ -102,7 +102,7 @@ export const settingLinks: Route[] = [{
export function orgSettingLinks(orgID) { export function orgSettingLinks(orgID) {
return [{ return [{
title: '组织信息', title: '组织设置',
path: `${ReserveUrls.Settings}/org/profile/${orgID}`, path: `${ReserveUrls.Settings}/org/profile/${orgID}`,
icon: <FaUserCircle />, icon: <FaUserCircle />,
disabled: false disabled: false

Loading…
Cancel
Save