diff --git a/frontend/src/apis/model.ts b/frontend/src/apis/model.ts index da71398..835fd72 100644 --- a/frontend/src/apis/model.ts +++ b/frontend/src/apis/model.ts @@ -51,8 +51,9 @@ export interface ApiConfig { export interface UserInfo { uid: number - uuid: number + uuid: string is_admin: boolean + is_disable: boolean email: string reg_ip: string name: string @@ -63,4 +64,6 @@ export interface EditUser { name: string password: string is_admin: boolean + is_disable: boolean + del_textures: boolean } \ No newline at end of file diff --git a/frontend/src/components/Loading.tsx b/frontend/src/components/Loading.tsx index 95637f2..9b09deb 100644 --- a/frontend/src/components/Loading.tsx +++ b/frontend/src/components/Loading.tsx @@ -5,7 +5,7 @@ export default function Loading() { return ( <> theme.zIndex.drawer + 1 }} + sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.modal + 1 }} open={true} > diff --git a/frontend/src/components/SkinViewUUID.tsx b/frontend/src/components/SkinViewUUID.tsx new file mode 100644 index 0000000..d68b300 --- /dev/null +++ b/frontend/src/components/SkinViewUUID.tsx @@ -0,0 +1,89 @@ +import { yggProfile } from "@/apis/apis"; +import { decodeSkin } from "@/utils/skin"; +import Skeleton from "@mui/material/Skeleton"; +import { useHover, useMemoizedFn, useRequest, useUnmount } from "ahooks"; +import { memo, useEffect, useRef, useState } from "react"; +import ReactSkinview3d, { ReactSkinview3dOptions } from "@/components/Skinview3d"; +import { SkinViewer, WalkingAnimation } from "skinview3d"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; + +interface prop { + uuid: string + width: number + height: number +} + +const SkinViewUUID = memo(function SkinViewUUID({ uuid, width, height }: prop) { + const [textures, setTextures] = useState({ skin: "", cape: "", model: "default" }) + const [err, setErr] = useState("") + + const SkinInfo = useRequest(() => yggProfile(uuid), { + cacheKey: "/api/yggdrasil/sessionserver/session/minecraft/profile/" + uuid, + onError: e => { + console.warn(e) + setErr(String(e)) + }, + refreshDeps: [uuid], + }) + + useEffect(() => { + if (!SkinInfo.data) return + const [skin, cape, model] = decodeSkin(SkinInfo.data) + setTextures({ cape: cape, skin: skin, model: model }) + }, [SkinInfo.data]) + + if (err != "") { + return {err} + } + return (<> + { + (SkinInfo.loading && !SkinInfo.data) ? + : (textures.skin != "" || textures.cape != "") ? ( + ) : + 还没有设置皮肤 + + } + ) +}) + + +const MySkin = function MySkin(p: ReactSkinview3dOptions) { + const refSkinview3d = useRef(null); + const skinisHovering = useHover(refSkinview3d); + const skinview3dView = useRef(null); + + useEffect(() => { + if (skinview3dView.current) { + skinview3dView.current.autoRotate = !skinisHovering + } + if (skinview3dView.current?.animation) { + skinview3dView.current.animation.paused = skinisHovering + } + }, [skinisHovering]) + + useUnmount(() => { + skinview3dView.current?.dispose() + }) + + const handelOnReady = useMemoizedFn(v => { + v.viewer.animation = new WalkingAnimation() + v.viewer.autoRotate = true + skinview3dView.current = v.viewer + }) + + return
+ +
+} + +export default SkinViewUUID \ No newline at end of file diff --git a/frontend/src/views/Layout.tsx b/frontend/src/views/Layout.tsx index 8f429ae..114c4cd 100644 --- a/frontend/src/views/Layout.tsx +++ b/frontend/src/views/Layout.tsx @@ -34,7 +34,7 @@ import PersonIcon from '@mui/icons-material/Person'; import SecurityIcon from '@mui/icons-material/Security'; import SettingsIcon from '@mui/icons-material/Settings'; import { Link } from "react-router-dom"; - +import GroupIcon from '@mui/icons-material/Group'; const drawerWidth = 240; const DrawerOpen = atom(false) @@ -242,8 +242,9 @@ const MyDrawer = function MyDrawer() { const adminDrawerList = React.useMemo(() => [ { - icon: , - title: 'test' + icon: , + title: '用户管理', + link: '/admin/user' } ] as ListItem[], []) diff --git a/frontend/src/views/admin/UserAdmin.tsx b/frontend/src/views/admin/UserAdmin.tsx index de7ad47..0545fb2 100644 --- a/frontend/src/views/admin/UserAdmin.tsx +++ b/frontend/src/views/admin/UserAdmin.tsx @@ -6,8 +6,8 @@ import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Paper from '@mui/material/Paper'; import useTitle from '@/hooks/useTitle'; -import { useRequest } from 'ahooks'; -import { ListUser } from '@/apis/apis'; +import { useRequest } from 'ahooks'; +import { ListUser, editUser } from '@/apis/apis'; import { useEffect, useState } from 'react'; import { useAtomValue } from 'jotai'; import { token } from '@/store/store'; @@ -22,8 +22,13 @@ import Dialog from '@mui/material/Dialog'; import DialogTitle from '@mui/material/DialogTitle'; import DialogContent from '@mui/material/DialogContent'; import DialogActions from '@mui/material/DialogActions'; -import { UserInfo } from '@/apis/model'; +import { EditUser, UserInfo } from '@/apis/model'; import { produce } from 'immer' +import Checkbox from '@mui/material/Checkbox'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import SkinViewUUID from '@/components/SkinViewUUID'; +import Loading from '@/components/Loading'; export default function UserAdmin() { useTitle("用户管理") @@ -102,7 +107,7 @@ export default function UserAdmin() { setErr("")} severity="error">{err} - + run(page, nowtoken, email, name)} /> ); } @@ -110,45 +115,109 @@ interface MyDialogProp { open: boolean setOpen: (b: boolean) => void row: UserInfo | null + onUpdate: () => void } -function MyDialog({ open, row, setOpen }: MyDialogProp) { +function MyDialog({ open, row, setOpen, onUpdate }: MyDialogProp) { const handleClose = () => { setOpen(false) } - const [nrow, setNrow] = useState(row) + const [erow, setErow] = useState({ + email: "", + name: "", + password: "", + is_admin: false, + is_disable: false, + del_textures: false, + }) + const [load, setLoad] = useState(false) + const nowToken = useAtomValue(token) + const [err, setErr] = useState("") useEffect(() => { - setNrow(row) + setErow({ + email: row?.email ?? "", + name: row?.name ?? "", + password: "", + is_admin: row?.is_admin ?? false, + is_disable: row?.is_disable ?? false, + del_textures: false, + }) }, [row]) const handleOpen = () => { - + if (load) return + setLoad(true) + editUser(erow, nowToken, String(row?.uid)).then(() => [setOpen(false), onUpdate()]).finally(() => setLoad(false)). + catch(e => setErr(String(e))) } - return ( + return (<> 修改用户信息 - - setNrow(produce(v => { - if (!v) return - v.email = e.target.value - return - }))} - /> + + + setErow(produce(v => { + v.email = e.target.value + return + }))} + /> + setErow(produce(v => { + v.name = e.target.value + return + }))} + /> + setErow(produce(v => { + v.password = e.target.value + return + }))} + /> + + setErow(produce(v => { + v.is_admin = e.target.checked + }))} />} label="管理权限" /> + setErow(produce(v => { + v.is_disable = e.target.checked + }))} />} label="禁用" /> + setErow(produce(v => { + v.del_textures = e.target.checked + }))} />} label="清空材质" /> + + + - ) + {load && } + + setErr("")} severity="error">{err} + + ) } \ No newline at end of file diff --git a/frontend/src/views/profile/Profile.tsx b/frontend/src/views/profile/Profile.tsx index 0cdaf32..0f66eef 100644 --- a/frontend/src/views/profile/Profile.tsx +++ b/frontend/src/views/profile/Profile.tsx @@ -4,48 +4,20 @@ import CardContent from '@mui/material/CardContent'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import CardHeader from '@mui/material/CardHeader'; -import { useHover, useMemoizedFn, useRequest, useUnmount } from 'ahooks'; -import { LayoutAlertErr, user } from '@/store/store'; -import { useAtomValue, useSetAtom } from 'jotai'; -import { yggProfile } from '@/apis/apis'; +import { user } from '@/store/store'; +import { useAtomValue } from 'jotai'; import { useNavigate } from 'react-router-dom'; import Box from '@mui/material/Box'; -import { useEffect, useRef, useState } from 'react'; -import { decodeSkin } from '@/utils/skin'; -import ReactSkinview3d from '@/components/Skinview3d' -import type { ReactSkinview3dOptions } from '@/components/Skinview3d' -import { WalkingAnimation } from "skinview3d" -import type { SkinViewer } from "skinview3d" -import Skeleton from '@mui/material/Skeleton'; import useTitle from '@/hooks/useTitle'; +import SkinViewUUID from '@/components/SkinViewUUID'; const Profile = function Profile() { const navigate = useNavigate(); - const setErr = useSetAtom(LayoutAlertErr) const userinfo = useAtomValue(user) - const [textures, setTextures] = useState({ skin: "", cape: "", model: "default" }) useTitle("个人信息") - const SkinInfo = useRequest(() => yggProfile(userinfo.uuid ?? ""), { - cacheKey: "/api/yggdrasil/sessionserver/session/minecraft/profile/" + userinfo?.uuid, - onError: e => { - console.warn(e) - setErr(String(e)) - }, - refreshDeps: [userinfo?.uuid], - }) - - useEffect(() => { - if (!SkinInfo.data) return - const [skin, cape, model] = decodeSkin(SkinInfo.data) - setTextures({ cape: cape, skin: skin, model: model }) - }, [SkinInfo.data]) - - - - return ( <> - { - (SkinInfo.loading && !SkinInfo.data) ? - : (textures.skin != "" || textures.cape != "") ? ( - ) : - 你还没有设置皮肤 - - - } + @@ -101,39 +60,6 @@ const Profile = function Profile() { ) } - -const MySkin = function MySkin(p: ReactSkinview3dOptions) { - const refSkinview3d = useRef(null); - const skinisHovering = useHover(refSkinview3d); - const skinview3dView = useRef(null); - - useEffect(() => { - if (skinview3dView.current) { - skinview3dView.current.autoRotate = !skinisHovering - } - if (skinview3dView.current?.animation) { - skinview3dView.current.animation.paused = skinisHovering - } - }, [skinisHovering]) - - useUnmount(() => { - skinview3dView.current?.dispose() - }) - - const handelOnReady = useMemoizedFn(v => { - v.viewer.animation = new WalkingAnimation() - v.viewer.autoRotate = true - skinview3dView.current = v.viewer - }) - - return
- -
-} - function getYggRoot() { const u = new URL((import.meta.env.VITE_APIADDR ?? location.origin) + "/api/yggdrasil") return u.toString() diff --git a/model/model.go b/model/model.go index ced49c0..fce22e0 100644 --- a/model/model.go +++ b/model/model.go @@ -48,9 +48,10 @@ type ChangePasswd struct { type UserList struct { UserInfo - Email string `json:"email"` - RegIp string `json:"reg_ip"` - Name string `json:"name"` + Email string `json:"email"` + RegIp string `json:"reg_ip"` + Name string `json:"name"` + IsDisable bool `json:"is_disable"` } type ChangeName struct { diff --git a/service/admin.go b/service/admin.go index e8bfc76..ca1234c 100644 --- a/service/admin.go +++ b/service/admin.go @@ -63,9 +63,10 @@ func (w *WebService) ListUser(ctx context.Context, page int, email, name string) UUID: v.Edges.Profile.UUID, IsAdmin: utilsService.IsAdmin(v.State), }, - Email: v.Email, - RegIp: v.RegIP, - Name: v.Edges.Profile.Name, + Email: v.Email, + RegIp: v.RegIP, + Name: v.Edges.Profile.Name, + IsDisable: utilsService.IsDisable(v.State), }) } @@ -77,6 +78,7 @@ func (w *WebService) ListUser(ctx context.Context, page int, email, name string) } func (w *WebService) EditUser(ctx context.Context, u model.EditUser, uid int) error { + uuid := "" err := utils.WithTx(ctx, w.client, func(tx *ent.Tx) error { up := tx.User.UpdateOneID(uid).SetEmail(u.Email) if u.Password != "" { @@ -93,13 +95,14 @@ func (w *WebService) EditUser(ctx context.Context, u model.EditUser, uid int) er } if u.DelTextures { - up, err := tx.UserProfile.Query().Where(userprofile.ID(uid)).First(ctx) + userProfile, err := tx.UserProfile.Query().Where(userprofile.ID(uid)).First(ctx) if err != nil { return err } + uuid = userProfile.UUID tl := []string{"skin", "cape"} for _, v := range tl { - err := utilsService.DelTexture(ctx, up.ID, v, w.client, w.config) + err := utilsService.DelTexture(ctx, userProfile.ID, v, w.client, w.config) if err != nil { return err } @@ -122,9 +125,14 @@ func (w *WebService) EditUser(ctx context.Context, u model.EditUser, uid int) er return nil }) - if err != nil { return fmt.Errorf("EditUser: %w", err) } + if uuid != "" { + err = w.cache.Del([]byte("Profile" + uuid)) + if err != nil { + return fmt.Errorf("EditUser: %w", err) + } + } return nil }