Skip to content

Commit 8cdaad7

Browse files
committed
v2.5.2a
1 parent e12fd89 commit 8cdaad7

48 files changed

Lines changed: 2653 additions & 1354 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Plan: Add Dropdown Menu to MobileNav More Button
2+
3+
## Overview
4+
Replace the static "More" button in `MobileNav.vue` with a shadcn-vue dropdown menu that opens upward, containing profile, login, logout, messages, and analytics items.
5+
6+
## Current State
7+
- `MobileNav.vue` has a static "More" button that does nothing
8+
- shadcn-vue `dropdown-menu` components are already installed at `@/components/ui/dropdown-menu/`
9+
- Required icons exist: `IconUser`, `LoginIcon`, `LogoutIcon`, `MessageIcon`, `IconAnalytics`
10+
- Routes exist: `/login`, `/messages`, `/analytics`, `/settings` (profile)
11+
- Auth store has `logout()` function at `@/stores/auth`
12+
13+
## Changes
14+
15+
### File: `frontend/src/components/basic/MobileNav.vue`
16+
17+
1. **Update imports** - Add shadcn-vue dropdown components and icons:
18+
- `DropdownMenu`, `DropdownMenuContent`, `DropdownMenuItem`, `DropdownMenuSeparator`, `DropdownMenuTrigger`
19+
- `IconUser`, `LoginIcon`, `LogoutIcon`, `MessageIcon`, `IconAnalytics` from `../icons`
20+
- `useRouter` from `vue-router` (for programmatic navigation)
21+
- `useAuthStore` from `@/stores/auth` (for logout)
22+
23+
2. **Replace "More" button section** (lines 70-80) with:
24+
- `DropdownMenu` wrapper
25+
- `DropdownMenuTrigger` wrapping the existing icon button
26+
- `DropdownMenuContent` with `side="top"` to open upward, `:side-offset="8"` for spacing
27+
- Menu items:
28+
- **Profile** (`IconUser`) → navigate to `/settings`
29+
- **Login** (`LoginIcon`) → navigate to `/login` (only shown when not authenticated)
30+
- **Messages** (`MessageIcon`) → navigate to `/messages`
31+
- **Analytics** (`IconAnalytics`) → navigate to `/analytics`
32+
- **Separator**
33+
- **Logout** (`LogoutIcon`) → call `authStore.logout()` (the store already navigates to `/` internally), only shown when authenticated, with `variant="destructive"`
34+
35+
3. **Style dropdown content** to match nav's glassmorphism aesthetic:
36+
- Use consistent `dark:` variants
37+
- Keep `font-['Inter']` for labels
38+
- Match the backdrop-blur and transparency style
39+
40+
## Verification
41+
- Run `pnpm run type-check` to ensure no TypeScript errors
42+
- Run `pnpm run lint` to check for lint issues

frontend/components.d.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ declare module 'vue' {
2525
AlertDialogTrigger: typeof import('./src/components/ui/alert-dialog/AlertDialogTrigger.vue')['default']
2626
AProgress: typeof import('ant-design-vue/es')['Progress']
2727
AreaChart: typeof import('./src/components/analytics/AreaChart.vue')['default']
28+
ArrowLeft: typeof import('./src/components/icons/ArrowLeft.vue')['default']
2829
ArticleComments: typeof import('./src/components/blog/ArticleComments.vue')['default']
2930
ArticleDetailLayout: typeof import('./src/components/article/ArticleDetailLayout.vue')['default']
3031
ArticleSummaryCard: typeof import('./src/components/blog/ArticleSummaryCard.vue')['default']
@@ -78,17 +79,35 @@ declare module 'vue' {
7879
CommentItem: typeof import('./src/components/blog/CommentItem.vue')['default']
7980
CommentsTab: typeof import('./src/components/message/CommentsTab.vue')['default']
8081
DelIcon: typeof import('./src/components/icons/DelIcon.vue')['default']
82+
DropdownMenu: typeof import('./src/components/ui/dropdown-menu/DropdownMenu.vue')['default']
83+
DropdownMenuCheckboxItem: typeof import('./src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue')['default']
84+
DropdownMenuContent: typeof import('./src/components/ui/dropdown-menu/DropdownMenuContent.vue')['default']
85+
DropdownMenuGroup: typeof import('./src/components/ui/dropdown-menu/DropdownMenuGroup.vue')['default']
86+
DropdownMenuItem: typeof import('./src/components/ui/dropdown-menu/DropdownMenuItem.vue')['default']
87+
DropdownMenuLabel: typeof import('./src/components/ui/dropdown-menu/DropdownMenuLabel.vue')['default']
88+
DropdownMenuRadioGroup: typeof import('./src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue')['default']
89+
DropdownMenuRadioItem: typeof import('./src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue')['default']
90+
DropdownMenuSeparator: typeof import('./src/components/ui/dropdown-menu/DropdownMenuSeparator.vue')['default']
91+
DropdownMenuShortcut: typeof import('./src/components/ui/dropdown-menu/DropdownMenuShortcut.vue')['default']
92+
DropdownMenuSub: typeof import('./src/components/ui/dropdown-menu/DropdownMenuSub.vue')['default']
93+
DropdownMenuSubContent: typeof import('./src/components/ui/dropdown-menu/DropdownMenuSubContent.vue')['default']
94+
DropdownMenuSubTrigger: typeof import('./src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue')['default']
95+
DropdownMenuTrigger: typeof import('./src/components/ui/dropdown-menu/DropdownMenuTrigger.vue')['default']
8196
EditIcon: typeof import('./src/components/icons/EditIcon.vue')['default']
8297
HomeIcon: typeof import('./src/components/icons/HomeIcon.vue')['default']
8398
HomeSideBar: typeof import('./src/components/layout/HomeSideBar.vue')['default']
8499
IconAnalytics: typeof import('./src/components/icons/IconAnalytics.vue')['default']
85100
IconClose: typeof import('./src/components/icons/IconClose.vue')['default']
101+
IconCloud: typeof import('./src/components/icons/IconCloud.vue')['default']
86102
IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
87103
IconDel: typeof import('./src/components/icons/IconDel.vue')['default']
88104
IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
89105
IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
90106
IconError: typeof import('./src/components/icons/IconError.vue')['default']
107+
IconGlobal: typeof import('./src/components/icons/IconGlobal.vue')['default']
91108
IconInfo: typeof import('./src/components/icons/IconInfo.vue')['default']
109+
IconKey: typeof import('./src/components/icons/IconKey.vue')['default']
110+
IconLock: typeof import('./src/components/icons/IconLock.vue')['default']
92111
IconMemo: typeof import('./src/components/icons/IconMemo.vue')['default']
93112
IconMore: typeof import('./src/components/icons/IconMore.vue')['default']
94113
IconPopular: typeof import('./src/components/icons/IconPopular.vue')['default']

frontend/src/assets/base.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,43 @@ body {
3737
}
3838
}
3939

40+
@theme {
41+
--animate-scale-easeOutElastic: scale-easeOutElastic 0.5s;
42+
@keyframes scale-easeOutElastic {
43+
0% {
44+
transform: scale(1);
45+
}
46+
47+
16% {
48+
transform: scale(-0.32);
49+
}
50+
51+
28% {
52+
transform: scale(0.13);
53+
}
54+
55+
44% {
56+
transform: scale(-0.05);
57+
}
58+
59+
59% {
60+
transform: scale(0.02);
61+
}
62+
63+
73% {
64+
transform: scale(-0.01);
65+
}
66+
67+
88% {
68+
transform: scale(0);
69+
}
70+
71+
100% {
72+
transform: scale(0);
73+
}
74+
}
75+
}
76+
4077
@theme {
4178
--animate-breathe: breathe 1s infinite;
4279
@keyframes breathe {

frontend/src/assets/squircle.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.squircle {
22
/* 回退方案:标准圆角 (对应 tailwind 的 rounded-3xl 或 4xl) */
3-
border-radius: 40px;
3+
border-radius: 2rem;
44

55
/* 超椭圆特性 */
66
@supports (corner-shape: squircle) {

frontend/src/components/basic/BasicDetail.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div>
33
<!-- Title Section with Parallax -->
44
<div
5-
class="relative -z-5 mx-0 mt-60 flex flex-col items-center justify-center bg-transparent"
5+
class="relative -z-5 mx-0 mt-60 flex flex-col items-center justify-center bg-transparent max-sm:mt-30"
66
:style="titleStyle"
77
>
88
<div>

frontend/src/components/basic/BasicNotifier.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@
2424
<div
2525
class="flex h-20 w-20 items-center justify-center rounded-full bg-amber-100 text-amber-600 dark:bg-amber-900/30 dark:text-amber-400"
2626
>
27-
<svg class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
27+
<svg
28+
class="h-10 w-10"
29+
fill="none"
30+
viewBox="0 0 24 24"
31+
stroke="currentColor"
32+
>
2833
<path
2934
stroke-linecap="round"
3035
stroke-linejoin="round"
@@ -44,7 +49,8 @@
4449

4550
<!-- 说明文字 -->
4651
<p class="mb-8 text-center text-gray-600 dark:text-gray-400">
47-
为了获得最佳体验,请使用桌面设备访问本网站。 移动端功能正在开发中,敬请期待!
52+
为了获得最佳体验,请使用桌面设备访问本网站。
53+
移动端功能正在开发中,敬请期待!
4854
</p>
4955

5056
<!-- 确认按钮 -->

frontend/src/components/basic/MobileHeader.vue

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,28 @@
55
:animate="{ y: 0, opacity: 1 }"
66
:exit="{ y: -10, opacity: 0 }"
77
:transition="{ type: 'spring', damping: 20, stiffness: 300 }"
8-
class="fixed top-0 left-0 z-50 flex w-full items-center justify-between rounded-b-[2rem] bg-white/60 px-6 py-4 shadow-[0_20px_40px_rgba(21,28,39,0.06)] backdrop-blur-xl dark:bg-slate-900/60 dark:shadow-[0_20px_40px_rgba(0,0,0,0.2)]"
8+
class="fixed top-0 left-0 z-50 flex w-full items-center justify-between rounded-b-[2rem] border-b border-b-white bg-white/60 px-6 py-4 shadow-[0_20px_40px_rgba(21,28,39,0.06)] backdrop-blur-xl dark:border-b-gray-700 dark:bg-slate-900/60 dark:shadow-[0_20px_40px_rgba(0,0,0,0.2)]"
99
>
1010
<div class="flex items-center gap-3">
1111
<div class="h-10 w-10 overflow-hidden rounded-full border-2 border-blue-600/20">
12-
<img src="/images/about.webp" alt="Avatar" class="h-full w-full object-cover" />
12+
<img
13+
:src="user?.photo || '/images/about.webp'"
14+
alt="Avatar"
15+
class="h-full w-full object-cover"
16+
/>
1317
</div>
1418
<h1
15-
class="font-['Plus_Jakarta_Sans'] text-lg font-bold tracking-tight text-blue-700 dark:text-blue-400"
19+
v-if="$route.path === '/'"
20+
class="font-serif font-bold tracking-tight text-blue-700 italic dark:text-blue-400"
1621
>
17-
Dashboard
22+
{{ user?.username ? `Welcome, ${user.username}!` : "Welcome!" }}
1823
</h1>
24+
<ArrowLeft
25+
v-else
26+
@click.stop="$router.back()"
27+
class="size-6 cursor-pointer text-blue-500 dark:text-blue-400"
28+
title="返回上一页"
29+
/>
1930
</div>
2031
<div class="flex items-center gap-2">
2132
<ThemeToggle class="scale-90" />
@@ -44,6 +55,12 @@
4455

4556
<script setup lang="ts">
4657
import { motion } from "motion-v";
58+
import { storeToRefs } from "pinia";
59+
import { useAuthStore } from "@/stores/auth";
60+
import ArrowLeft from "../icons/ArrowLeft.vue";
61+
62+
const authStore = useAuthStore();
63+
const { user } = storeToRefs(authStore);
4764
</script>
4865

4966
<style scoped></style>

0 commit comments

Comments
 (0)