皮肤上传
This commit is contained in:
parent
6b489789d3
commit
ab5e87ef42
@ -4,6 +4,7 @@ import Login from '@/views/Login'
|
|||||||
import Register from '@/views/Register'
|
import Register from '@/views/Register'
|
||||||
import Profile from '@/views/profile/Profile'
|
import Profile from '@/views/profile/Profile'
|
||||||
import Textures from '@/views/profile/Textures'
|
import Textures from '@/views/profile/Textures'
|
||||||
|
import Security from '@/views/profile/Security'
|
||||||
import Layout from '@/views/Layout'
|
import Layout from '@/views/Layout'
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
@ -19,7 +20,7 @@ function Root() {
|
|||||||
<Route path="/register" element={<Register />} />
|
<Route path="/register" element={<Register />} />
|
||||||
<Route path="/profile" element={<Profile />} />
|
<Route path="/profile" element={<Profile />} />
|
||||||
<Route path="/textures" element={<Textures />} />
|
<Route path="/textures" element={<Textures />} />
|
||||||
|
<Route path="/security" element={<Security />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
|
@ -54,3 +54,20 @@ export async function yggProfile(uuid: string) {
|
|||||||
}
|
}
|
||||||
return data as YggProfile
|
return data as YggProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function upTextures(uuid: string, token: string, textureType: 'skin' | 'cape', model: 'slim' | '', file: File) {
|
||||||
|
const f = new FormData()
|
||||||
|
f.set("file", file)
|
||||||
|
f.set("model", model)
|
||||||
|
|
||||||
|
const r = await fetch(import.meta.env.VITE_APIADDR + "/api/yggdrasil/api/user/profile/" + uuid + "/" + textureType, {
|
||||||
|
method: "PUT",
|
||||||
|
body: f,
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Bearer " + token
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (r.status != 204) {
|
||||||
|
throw new Error("上传失败 " + String(r.status))
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ export interface tokenData {
|
|||||||
accessToken: string
|
accessToken: string
|
||||||
selectedProfile: {
|
selectedProfile: {
|
||||||
name: string
|
name: string
|
||||||
uuid: string
|
id: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ const MyToolbar = memo(function MyToolbar() {
|
|||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
setNowUser({ name: "", uuid: "" })
|
setNowUser({ name: "", uuid: "" })
|
||||||
setToken("")
|
setToken("")
|
||||||
navigate("/")
|
navigate("/login")
|
||||||
})
|
})
|
||||||
|
|
||||||
useTilg()
|
useTilg()
|
||||||
@ -240,7 +240,7 @@ const MyDrawer = function MyDrawer() {
|
|||||||
{
|
{
|
||||||
icon: <SecurityIcon />,
|
icon: <SecurityIcon />,
|
||||||
title: '安全设置',
|
title: '安全设置',
|
||||||
link: '/setting'
|
link: '/security'
|
||||||
}
|
}
|
||||||
] as ListItem[], [])
|
] as ListItem[], [])
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export default function SignIn() {
|
|||||||
if (!v) return
|
if (!v) return
|
||||||
setToken(v.accessToken)
|
setToken(v.accessToken)
|
||||||
setUserInfo({
|
setUserInfo({
|
||||||
uuid: v.selectedProfile.uuid,
|
uuid: v.selectedProfile.id,
|
||||||
name: v.selectedProfile.name,
|
name: v.selectedProfile.name,
|
||||||
})
|
})
|
||||||
navigate("/profile")
|
navigate("/profile")
|
||||||
|
@ -5,10 +5,9 @@ import Button from '@mui/material/Button';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import CardHeader from '@mui/material/CardHeader';
|
import CardHeader from '@mui/material/CardHeader';
|
||||||
import { useHover, useRequest, useUnmount } from 'ahooks';
|
import { useHover, useRequest, useUnmount } from 'ahooks';
|
||||||
import { ApiErr } from '@/apis/error';
|
import { LayoutAlertErr, user } from '@/store/store';
|
||||||
import { LayoutAlertErr, token } from '@/store/store';
|
|
||||||
import { useAtomValue, useSetAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import { userInfo, yggProfile } from '@/apis/apis';
|
import { yggProfile } from '@/apis/apis';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
@ -22,32 +21,21 @@ import useTilg from 'tilg';
|
|||||||
import useTitle from '@/hooks/useTitle';
|
import useTitle from '@/hooks/useTitle';
|
||||||
|
|
||||||
const Profile = function Profile() {
|
const Profile = function Profile() {
|
||||||
const nowToken = useAtomValue(token)
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const setErr = useSetAtom(LayoutAlertErr)
|
const setErr = useSetAtom(LayoutAlertErr)
|
||||||
|
const userinfo = useAtomValue(user)
|
||||||
|
|
||||||
const [textures, setTextures] = useState({ skin: "", cape: "", model: "default" })
|
const [textures, setTextures] = useState({ skin: "", cape: "", model: "default" })
|
||||||
useTitle("个人信息")
|
useTitle("个人信息")
|
||||||
|
|
||||||
const userinfo = useRequest(() => userInfo(nowToken), {
|
|
||||||
refreshDeps: [nowToken],
|
|
||||||
cacheKey: "/api/v1/user" + nowToken,
|
|
||||||
staleTime: 60000,
|
|
||||||
onError: e => {
|
|
||||||
if (e instanceof ApiErr && e.code == 5) {
|
|
||||||
navigate("/login")
|
|
||||||
}
|
|
||||||
console.warn(e)
|
|
||||||
setErr(String(e))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const SkinInfo = useRequest(() => yggProfile(userinfo.data?.uuid ?? ""), {
|
const SkinInfo = useRequest(() => yggProfile(userinfo.uuid ?? ""), {
|
||||||
cacheKey: "/api/yggdrasil/sessionserver/session/minecraft/profile/" + userinfo.data?.uuid,
|
cacheKey: "/api/yggdrasil/sessionserver/session/minecraft/profile/" + userinfo?.uuid,
|
||||||
onError: e => {
|
onError: e => {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
setErr(String(e))
|
setErr(String(e))
|
||||||
},
|
},
|
||||||
refreshDeps: [userinfo.data?.uuid],
|
refreshDeps: [userinfo?.uuid],
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -70,12 +58,10 @@ const Profile = function Profile() {
|
|||||||
<Card sx={{ gridArea: "a" }}>
|
<Card sx={{ gridArea: "a" }}>
|
||||||
<CardHeader title="信息" />
|
<CardHeader title="信息" />
|
||||||
<CardContent sx={{ display: "grid", gridTemplateColumns: "4em auto" }}>
|
<CardContent sx={{ display: "grid", gridTemplateColumns: "4em auto" }}>
|
||||||
<Typography>uid</Typography>
|
|
||||||
<Typography sx={{ wordBreak: 'break-all' }}>{(userinfo.loading && !userinfo.data) ? <Skeleton /> : userinfo.data?.uid}</Typography>
|
|
||||||
<Typography>name</Typography>
|
<Typography>name</Typography>
|
||||||
<Typography>{(SkinInfo.loading || userinfo.loading) && !SkinInfo.data ? <Skeleton /> : SkinInfo.data?.name}</Typography>
|
<Typography>{userinfo.name}</Typography>
|
||||||
<Typography>uuid</Typography>
|
<Typography>uuid</Typography>
|
||||||
<Typography sx={{ wordBreak: 'break-all' }}>{(userinfo.loading && !userinfo.data) ? <Skeleton /> : userinfo.data?.uuid}</Typography>
|
<Typography sx={{ wordBreak: 'break-all' }}>{userinfo.uuid}</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
{/* <CardActions>
|
{/* <CardActions>
|
||||||
<Button size="small">更改</Button>
|
<Button size="small">更改</Button>
|
||||||
|
6
frontend/src/views/profile/Security.tsx
Normal file
6
frontend/src/views/profile/Security.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
export default function Security() {
|
||||||
|
return (<>
|
||||||
|
<p>密码设置</p>
|
||||||
|
</>)
|
||||||
|
}
|
@ -16,45 +16,48 @@ import Box from "@mui/material/Box";
|
|||||||
import ReactSkinview3d from "react-skinview3d";
|
import ReactSkinview3d from "react-skinview3d";
|
||||||
import { useUnmount } from "ahooks";
|
import { useUnmount } from "ahooks";
|
||||||
import { SkinViewer } from "skinview3d";
|
import { SkinViewer } from "skinview3d";
|
||||||
|
import { useAtomValue, useSetAtom } from "jotai";
|
||||||
|
import { LayoutAlertErr, token, user } from "@/store/store";
|
||||||
|
import { upTextures } from "@/apis/apis";
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
|
||||||
const Textures = function Textures() {
|
const Textures = function Textures() {
|
||||||
const [redioValue, setRedioValue] = useState("skin")
|
const [redioValue, setRedioValue] = useState("skin")
|
||||||
useTitle("上传皮肤")
|
useTitle("上传皮肤")
|
||||||
const [file, setFile] = useState<File | null>(null)
|
const [file, setFile] = useState<File | null>(null)
|
||||||
const skin = useRef({
|
const skin = useRef("")
|
||||||
skinUrl: "",
|
|
||||||
capeUrl: "",
|
|
||||||
})
|
|
||||||
const skinview3dView = useRef<SkinViewer | null>(null);
|
const skinview3dView = useRef<SkinViewer | null>(null);
|
||||||
|
const setErr = useSetAtom(LayoutAlertErr)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const userinfo = useAtomValue(user)
|
||||||
|
const nowToken = useAtomValue(token)
|
||||||
|
|
||||||
useUnmount(() => {
|
useUnmount(() => {
|
||||||
skin.current.skinUrl && URL.revokeObjectURL(skin.current.skinUrl)
|
skin.current && URL.revokeObjectURL(skin.current)
|
||||||
skin.current.capeUrl && URL.revokeObjectURL(skin.current.capeUrl)
|
|
||||||
skinview3dView.current?.dispose()
|
skinview3dView.current?.dispose()
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (file) {
|
if (file) {
|
||||||
const nu = URL.createObjectURL(file)
|
const nu = URL.createObjectURL(file)
|
||||||
skin.current.skinUrl && URL.revokeObjectURL(skin.current.skinUrl)
|
skin.current && URL.revokeObjectURL(skin.current)
|
||||||
skin.current.capeUrl && URL.revokeObjectURL(skin.current.capeUrl)
|
|
||||||
skinview3dView.current?.loadSkin(null)
|
skinview3dView.current?.loadSkin(null)
|
||||||
skinview3dView.current?.loadCape(null)
|
skinview3dView.current?.loadCape(null)
|
||||||
switch (redioValue) {
|
switch (redioValue) {
|
||||||
case "skin":
|
case "skin":
|
||||||
skin.current.skinUrl = nu
|
skin.current = nu
|
||||||
skinview3dView.current?.loadSkin(nu, { model: "default" }).then(() =>
|
skinview3dView.current?.loadSkin(nu, { model: "default" }).then(() =>
|
||||||
skinview3dView.current?.loadSkin(nu, { model: "default" })
|
skinview3dView.current?.loadSkin(nu, { model: "default" })
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case "slim":
|
case "slim":
|
||||||
skin.current.skinUrl = nu
|
skin.current = nu
|
||||||
skinview3dView.current?.loadSkin(nu, { model: "slim" }).then(() =>
|
skinview3dView.current?.loadSkin(nu, { model: "slim" }).then(() =>
|
||||||
skinview3dView.current?.loadSkin(nu, { model: "slim" })
|
skinview3dView.current?.loadSkin(nu, { model: "slim" })
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
case "cape":
|
case "cape":
|
||||||
skin.current.capeUrl = nu
|
skin.current = nu
|
||||||
skinview3dView.current?.loadCape(nu).then(() => {
|
skinview3dView.current?.loadCape(nu).then(() => {
|
||||||
skinview3dView.current?.loadCape(nu)
|
skinview3dView.current?.loadCape(nu)
|
||||||
})
|
})
|
||||||
@ -70,6 +73,15 @@ const Textures = function Textures() {
|
|||||||
setFile(newFile)
|
setFile(newFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleToUpload = () => {
|
||||||
|
if (!file || loading) return
|
||||||
|
setLoading(true)
|
||||||
|
const textureType = redioValue == "cape" ? "cape" : "skin"
|
||||||
|
const model = redioValue == "slim" ? "slim" : ""
|
||||||
|
upTextures(userinfo.uuid, nowToken, textureType, model, file).catch(e => [setErr(String(e)), console.warn(e)]).
|
||||||
|
finally(() => setLoading(false))
|
||||||
|
}
|
||||||
|
|
||||||
useTilg()
|
useTilg()
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
@ -98,7 +110,7 @@ const Textures = function Textures() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions>
|
<CardActions>
|
||||||
<Button variant="contained" sx={{ maxWidth: "3em" }}>上传</Button>
|
<Button variant="contained" sx={{ maxWidth: "3em" }} onClick={handleToUpload}>上传</Button>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
<Card sx={{ gridArea: "b" }}>
|
<Card sx={{ gridArea: "b" }}>
|
||||||
@ -114,6 +126,7 @@ const Textures = function Textures() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Box>
|
</Box>
|
||||||
|
{loading && <Loading />}
|
||||||
</>)
|
</>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user