diff --git a/package.json b/package.json index 90008f6..094ec72 100644 --- a/package.json +++ b/package.json @@ -12,22 +12,22 @@ "format": "prettier --write ." }, "dependencies": { - "alova": "^3.2.6", + "alova": "^3.2.7", "chroma-js": "^3.1.2", "clsx": "^2.1.1", - "highlight.js": "^11.10.0", + "highlight.js": "^11.11.1", "install": "^0.13.0", - "katex": "^0.16.15", + "katex": "^0.16.20", "luxon": "^3.5.0", "nanoid": "^5.0.9", "overlayscrollbars": "^2.10.1", "overlayscrollbars-react": "^0.5.6", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-error-boundary": "^4.1.2", - "react-markdown": "^9.0.1", - "react-resize-detector": "^12.0.1", - "react-router": "^7.0.2", + "react-error-boundary": "^5.0.0", + "react-markdown": "^9.0.3", + "react-resize-detector": "^12.0.2", + "react-router": "^7.1.1", "react-transition-group": "^4.4.5", "rehype-autolink-headings": "^7.1.0", "rehype-highlight": "^7.0.1", @@ -37,31 +37,31 @@ "remark-math": "^6.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", - "sass": "^1.83.0", - "zustand": "^5.0.2" + "sass": "^1.83.1", + "zustand": "^5.0.3" }, "devDependencies": { - "@eslint/js": "^9.17.0", - "@iconify-json/solar": "^1.2.1", - "@iconify-json/svg-spinners": "^1.2.1", + "@eslint/js": "^9.18.0", + "@iconify-json/solar": "^1.2.2", + "@iconify-json/svg-spinners": "^1.2.2", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", - "@types/chroma-js": "^2.4.4", + "@types/chroma-js": "^3.1.0", "@types/katex": "^0.16.7", "@types/luxon": "^3.4.2", - "@types/node": "^22.10.2", - "@types/react": "^19.0.1", - "@types/react-dom": "^19.0.2", + "@types/node": "^22.10.5", + "@types/react": "^19.0.6", + "@types/react-dom": "^19.0.3", "@types/react-transition-group": "^4.4.12", "@vitejs/plugin-react-swc": "^3.7.2", - "eslint": "^9.17.0", + "eslint": "^9.18.0", "eslint-plugin-react-hooks": "^5.1.0", - "eslint-plugin-react-refresh": "^0.4.16", - "globals": "^15.13.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", "prettier": "^3.4.2", - "typescript": "^5.7.2", - "typescript-eslint": "^8.18.0", - "unplugin-icons": "^0.21.0", - "vite": "^6.0.3" + "typescript": "^5.7.3", + "typescript-eslint": "^8.19.1", + "unplugin-icons": "^22.0.0", + "vite": "^6.0.7" } } diff --git a/src/components/core/Avatar/Avatar.module.scss b/src/components/core/Avatar/Avatar.module.scss index b378c48..6d42ca8 100644 --- a/src/components/core/Avatar/Avatar.module.scss +++ b/src/components/core/Avatar/Avatar.module.scss @@ -1,19 +1,22 @@ -$size: var(--avatar-size); -$border-color: var(--avatar-border-color); - .root { display: flex; justify-content: center; align-items: center; border-radius: 50%; - border: 2px dashed $border-color; - width: $size; - height: $size; + border: 2px dashed var(--avatar-border-color); + width: var(--avatar-size); + height: var(--avatar-size); } .fallback { overflow: hidden; - color: $border-color; + color: var(--avatar-border-color); + display: flex; + justify-content: center; + align-items: center; + width: 95%; + height: 95%; + font-size: 1.5rem; } .img { diff --git a/src/components/core/Avatar/Avatar.tsx b/src/components/core/Avatar/Avatar.tsx index 8ecf6b0..bf02858 100644 --- a/src/components/core/Avatar/Avatar.tsx +++ b/src/components/core/Avatar/Avatar.tsx @@ -1,5 +1,5 @@ import styles from "./Avatar.module.scss"; -import UserCircleBoldDuotone from "~icons/solar/user-circle-bold-duotone"; +import UserCircleBoldDuotone from "~icons/solar/user-circle-bold"; import useThemeColor from "@/hooks/useThemeColor"; import { ComponentProps, useState } from "react"; import { Box } from "../Box"; @@ -19,7 +19,9 @@ export function Avatar(props: AvatarProps) { alt = "", color = "primary", size = "3rem", - fallback = , + fallback = ( + + ), style, ...rest } = props; diff --git a/src/components/core/Button/Button.module.scss b/src/components/core/Button/Button.module.scss index 1b7c8f4..f143420 100644 --- a/src/components/core/Button/Button.module.scss +++ b/src/components/core/Button/Button.module.scss @@ -29,6 +29,19 @@ border-color: var(--button-bg-color); } + &[data-variant="tonal"] { + background-color: var(--bg-color); + border-color: var(--button-bg-color); + } + + &[data-variant="subtle"] { + background-color: color-mix( + in srgb, + var(--button-bg-color) 30%, + #ffffff 10% + ); + } + &[data-variant="ghost"] { background-color: transparent; box-shadow: none; diff --git a/src/components/core/Button/Button.tsx b/src/components/core/Button/Button.tsx index 7b8ee08..1131592 100644 --- a/src/components/core/Button/Button.tsx +++ b/src/components/core/Button/Button.tsx @@ -10,7 +10,8 @@ export interface ButtonProps extends ComponentPropsWithRef<"button"> { width?: number | string; height?: string; color?: ThemeColor | string; - variant?: "solid" | "outlined" | "ghost"; + bgColor?: string; + variant?: "solid" | "outlined" | "tonal" | "subtle" | "ghost"; justify?: "start" | "center" | "end"; align?: "start" | "center" | "end"; radius?: string | number; diff --git a/src/components/widgets/Navbar/Dropdown/Dropdown.tsx b/src/components/widgets/Navbar/Dropdown/Dropdown.tsx index 0fa958d..f2995f2 100644 --- a/src/components/widgets/Navbar/Dropdown/Dropdown.tsx +++ b/src/components/widgets/Navbar/Dropdown/Dropdown.tsx @@ -1,13 +1,16 @@ import Sun2BoldDuotone from "~icons/solar/sun-2-bold-duotone"; import MoonBoldDuotone from "~icons/solar/moon-bold-duotone"; +import LogoutLineDuotone from "~icons/solar/logout-linear"; import { Avatar, Button, Stack } from "@/components/core"; import { Flex } from "@/components/core/Flex"; import { useThemeStore } from "@/stores/theme"; import { IconButton } from "@/components/core/IconButton"; import { useAuthStore } from "@/stores/auth"; +import { useNavigate } from "react-router"; export function Dropdown() { const authStore = useAuthStore(); + const navigate = useNavigate(); return ( - + {authStore?.user && ( + + )} ); } diff --git a/src/components/widgets/Navbar/Navbar.tsx b/src/components/widgets/Navbar/Navbar.tsx index 744cc46..763ce9f 100644 --- a/src/components/widgets/Navbar/Navbar.tsx +++ b/src/components/widgets/Navbar/Navbar.tsx @@ -2,6 +2,7 @@ import PlanetBoldDuotone from "~icons/solar/planet-bold-duotone"; import Book2BoldDuotone from "~icons/solar/book-2-bold-duotone"; import FlagBoldDuotone from "~icons/solar/flag-bold-duotone"; import UsersGroupTwoRoundedBoldDuotone from "~icons/solar/users-group-two-rounded-bold-duotone"; +import UserBoldDuotone from "~icons/solar/user-circle-bold-duotone"; import SolarSettingsBoldDuotone from "~icons/solar/settings-bold-duotone"; import SolarRoundArrowLeftBoldDuotone from "~icons/solar/round-arrow-left-bold-duotone"; import StarFallMinimalistic2BoldDuotone from "~icons/solar/star-fall-minimalistic-2-bold-duotone"; @@ -23,10 +24,12 @@ import { Dropdown } from "./Dropdown"; import React from "react"; import { get } from "@/api/game"; import { Game } from "@/models/game"; +import { useAuthStore } from "@/stores/auth"; export function Navbar() { const location = useLocation(); const { id } = useParams(); + const authStore = useAuthStore(); const links = { default: [ @@ -222,8 +225,18 @@ export function Navbar() { ref={dropdownMenuButtonRef} > E} + src={ + authStore?.user + ? `/api/users/${authStore?.user?.id}/avatar` + : "" + } + fallback={ + + } color={"transparent"} /> diff --git a/src/pages/challenges/_blocks/Empty/Empty.module.scss b/src/pages/challenges/_blocks/Empty/Empty.module.scss new file mode 100644 index 0000000..bc3bd70 --- /dev/null +++ b/src/pages/challenges/_blocks/Empty/Empty.module.scss @@ -0,0 +1,8 @@ +.root { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 1.5em; + user-select: none; +} diff --git a/src/pages/challenges/_blocks/Empty/Empty.tsx b/src/pages/challenges/_blocks/Empty/Empty.tsx new file mode 100644 index 0000000..b529932 --- /dev/null +++ b/src/pages/challenges/_blocks/Empty/Empty.tsx @@ -0,0 +1,17 @@ +import { Stack } from "@/components/core"; +import FlagBoldDuotone from "~icons/solar/flag-bold-duotone"; +import styles from "./Empty.module.scss"; + +export function Empty() { + return ( + + + 暂无题目 + + ); +} diff --git a/src/pages/challenges/_blocks/Empty/index.tsx b/src/pages/challenges/_blocks/Empty/index.tsx new file mode 100644 index 0000000..62db648 --- /dev/null +++ b/src/pages/challenges/_blocks/Empty/index.tsx @@ -0,0 +1 @@ +export { Empty } from "./Empty"; diff --git a/src/pages/challenges/index.tsx b/src/pages/challenges/index.tsx index 0aab40e..69c5737 100644 --- a/src/pages/challenges/index.tsx +++ b/src/pages/challenges/index.tsx @@ -19,6 +19,7 @@ import { useResizeDetector } from "react-resize-detector"; import { LoadingOverlay } from "@/components/core/LoadingOverlay/LoadingOverlay"; import { Box } from "@/components/core/Box"; import { useSharedStore } from "@/stores/shared"; +import { Empty } from "./_blocks/Empty"; export function Index() { const authStore = useAuthStore(); @@ -75,7 +76,6 @@ export function Index() { }).then((res) => { setChallengeStatus(res.data); }); - setLoading(false); } useEffect(() => { @@ -95,6 +95,7 @@ export function Index() { if (challenges?.length) { fetchChallengeStatus(); } + setLoading(false); }, [challenges, sharedStore.refresh]); return ( @@ -124,6 +125,9 @@ export function Index() { onChange={setIdInput} variant={"solid"} width={"10%"} + style={{ + minWidth: "125px", + }} /> } @@ -168,20 +172,26 @@ export function Index() { }} ref={challengeGroupRef} > - {challenges?.map((challenge) => ( - { - setSelectedChallenge(challenge); - setSelectedChallengeStatus( + {challenges?.length ? ( + challenges?.map((challenge) => ( + - ))} + } + key={challenge?.id} + onClick={() => { + setSelectedChallenge(challenge); + setSelectedChallengeStatus( + challengeStatus?.[challenge.id!] + ); + setModalOpen(true); + }} + /> + )) + ) : ( + + )} diff --git a/src/pages/games/_blocks/All/All.tsx b/src/pages/games/_blocks/All/All.tsx index 38cc349..d8dba39 100644 --- a/src/pages/games/_blocks/All/All.tsx +++ b/src/pages/games/_blocks/All/All.tsx @@ -6,6 +6,7 @@ import { Game } from "@/models/game"; import { get } from "@/api/game"; import { GameCard } from "./GameCard"; import { useNavigate } from "react-router"; +import { Empty } from "../Empty"; export function All() { const navigate = useNavigate(); @@ -65,14 +66,23 @@ export function All() { - - {games?.map((game) => ( - navigate(`/games/${game?.id}`)} - /> - ))} + + {games?.length ? ( + games?.map((game) => ( + navigate(`/games/${game?.id}`)} + /> + )) + ) : ( + + )} + + 暂无比赛 + + ); +} diff --git a/src/pages/games/_blocks/Empty/index.tsx b/src/pages/games/_blocks/Empty/index.tsx new file mode 100644 index 0000000..62db648 --- /dev/null +++ b/src/pages/games/_blocks/Empty/index.tsx @@ -0,0 +1 @@ +export { Empty } from "./Empty"; diff --git a/src/pages/games/_blocks/Recent/Recent.module.scss b/src/pages/games/_blocks/Recent/Recent.module.scss index 37ea833..6d4f5e7 100644 --- a/src/pages/games/_blocks/Recent/Recent.module.scss +++ b/src/pages/games/_blocks/Recent/Recent.module.scss @@ -25,7 +25,7 @@ transform: translate(-50%, -50%); padding: 1.5rem; border-radius: 25px; - background-color: light-dark(#0000000d, #ffffff0d); + background-color: light-dark(#0000000d, #ffffff00); .info-section { position: absolute; @@ -39,9 +39,9 @@ box-sizing: border-box; position: absolute; border-radius: 35px; - top: -2px; - left: -2px; - height: calc(12vh + 4px); + top: -3px; + left: -3px; + height: calc(12vh + 6px); aspect-ratio: 1.25/1; background-color: var(--color-primary); border: 2.75px solid var(--color-primary); diff --git a/src/pages/games/_blocks/Recent/Recent.tsx b/src/pages/games/_blocks/Recent/Recent.tsx index d50f987..7ee5892 100644 --- a/src/pages/games/_blocks/Recent/Recent.tsx +++ b/src/pages/games/_blocks/Recent/Recent.tsx @@ -9,6 +9,7 @@ import CupBold from "~icons/solar/cup-bold"; import Planet2BoldDuotone from "~icons/solar/planet-2-bold-duotone"; import PlanetBoldDuotone from "~icons/solar/planet-bold-duotone"; import { useNavigate } from "react-router"; +import { Empty } from "../Empty"; export function Recent() { const navigate = useNavigate(); @@ -70,194 +71,213 @@ export function Recent() { return ( - - - - - 积分榜 - - - - {scoreboard?.map((item, index) => ( - - - - {getRankIcon(item?.game_team?.rank!)} - - - - {item?.game_team?.team?.name} - - - - - {item?.game_team?.pts} - - - ))} - - - - {!scoreboard?.length && ( - - - 暂无数据 - - )} - - - - - - - - - - - - } - height="100%" - width="100%" - radius={9999} - style={{ - border: "2px solid white", - backgroundColor: "transparent", - }} - /> - - - + + + - {games?.[index]?.title} + 积分榜 - + + {scoreboard?.map((item, index) => ( + + + + {getRankIcon( + item?.game_team?.rank! + )} + + + + { + item?.game_team?.team + ?.name + } + + + + + {item?.game_team?.pts} + + + ))} + + + + {!scoreboard?.length && ( + - {games?.[index]?.sketch} - - - - - - + + 暂无数据 + + )} + - - - {games?.map((_, i) => ( - + + + + {games?.map((_, i) => ( + - {/* */} +