Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!--
提交前请阅读:
- 标题应简洁清晰,能一眼看出变更内容
- 尽量用中文完成模板
-->

### 概要
简要说明本次变更做了什么、为什么需要它。

### 变更类型
- [ ] 新功能 Feature
- [ ] 修复 Bug
- [ ] 文档变更
- [ ] 性能优化
- [ ] 重构(无功能改动)
- [ ] 样式/UI 调整
- [ ] 构建/CI 配置
- [ ] 依赖升级
- [ ] 其他:

### 关联 Issue / 需求
- Closes #
- Relates #

### 详细变更说明
- 要点 1:
- 要点 2:

### 破坏性变更(Breaking Changes)
- [ ] 包含破坏性变更
如果勾选,请补充:
1) 具体破坏点:
2) 迁移/兼容指引:
3) 是否需要发布大版本:

### 影响范围与风险
- 影响模块/页面:
- 潜在风险点:
- 回滚预案:

### 测试方案
手动/自动测试步骤(尽量可复现):
1)
2)
3)

测试覆盖:
- [ ] 已添加/更新单元测试
- [ ] 已添加/更新 E2E 测试
- [ ] 需要补充测试(请说明原因):

### 截图/录屏(如涉及 UI)
Before:

After:

### 性能与安全(如适用)
- 性能影响(时延、内存、包体积等):
- 安全考虑(XSS、CSRF、权限、敏感信息等):

### 文档与发布
- [ ] 用户文档/开发文档已更新
- [ ] 需要更新文档(请说明):
- 变更日志(Changelog)条目:
- Feature Flag:

### 提交清单(提交前请自检)
- [ ] 自测通过,关键路径功能可用
- [ ] `pnpm lint` 与类型检查通过
- [ ] `pnpm test` 通过(如有)
- [ ] 已在测试/预发环境验证(如适用)
- [ ] 涉及方已知会并评审(产品/设计/QA/后端 等)
- [ ] 不包含敏感信息(密钥、凭证、个人数据)

### 其他说明
补充任何有助于评审的信息、替代方案对比、设计权衡等。


4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ pnpm dev
```bash
# 此时在main分支
git pull #拉取main分支新代码
git checkout -b <branch-name> #创建并切换到新分支
git checkout -b #[your branch]#创建并切换到新分支

#老分支开发
git rebase <branch-name>
git push origin <branch-name> --force-with-lease
git push origin #[your branch]--force-with-lease #变基后强制推送

#写代码
git add .
Expand Down
154 changes: 73 additions & 81 deletions src/components/NavFooter.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { BadgeCheck, Bell, ChevronsUpDown, CreditCard, LogOut, Sparkles } from 'lucide-vue-next';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -10,41 +11,52 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from '@/components/ui/sidebar';
import defaultAvatar from '@/assets/images/shadcn-avatar.jpg';

withDefaults(
defineProps<{
user?: {
name: string;
email: string;
avatar: string;
};
}>(),
{
user: () => ({
name: 'jyu-yrc',
email: '91yrc.@jyu.com',
avatar: defaultAvatar,
}),
}
);
const { isMobile } = useSidebar();
interface Props {
collapsed?: boolean;
user?: {
name: string;
email: string;
avatar: string;
};
}

withDefaults(defineProps<Props>(), {
collapsed: false,
user: () => ({
name: 'jyu-yrc',
email: '91yrc.@jyu.com',
avatar: defaultAvatar,
}),
});
</script>
<template>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div class="p-3">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button
variant="ghost"
:class="[
'w-full justify-start gap-3 h-auto p-3',
'hover:bg-accent hover:text-accent-foreground',
'data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
]"
>
<Avatar class="h-8 w-8 rounded-lg shrink-0">
<AvatarImage :src="user.avatar" :alt="user.name" />
<AvatarFallback class="rounded-lg"> CN </AvatarFallback>
</Avatar>
<div v-if="!collapsed" class="grid flex-1 text-left text-sm leading-tight min-w-0">
<span class="truncate font-semibold">{{ user.name }}</span>
<span class="truncate text-xs text-muted-foreground">{{ user.email }}</span>
</div>
<ChevronsUpDown v-if="!collapsed" class="ml-auto size-4 shrink-0" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent class="min-w-56 rounded-lg" side="right" align="end" :side-offset="4">
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage :src="user.avatar" :alt="user.name" />
<AvatarFallback class="rounded-lg"> CN </AvatarFallback>
Expand All @@ -53,56 +65,36 @@ const { isMobile } = useSidebar();
<span class="truncate font-semibold">{{ user.name }}</span>
<span class="truncate text-xs">{{ user.email }}</span>
</div>
<ChevronsUpDown class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--reka-dropdown-menu-trigger-width] min-w-56 rounded-lg"
:side="isMobile ? 'bottom' : 'right'"
align="end"
:side-offset="4"
>
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage :src="user.avatar" :alt="user.name" />
<AvatarFallback class="rounded-lg"> CN </AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ user.name }}</span>
<span class="truncate text-xs">{{ user.email }}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Sparkles />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<BadgeCheck />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Bell />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Sparkles />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<BadgeCheck />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<LogOut />
Log out
<Bell />
Notifications
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</template>
64 changes: 36 additions & 28 deletions src/layouts/default.vue
Original file line number Diff line number Diff line change
@@ -1,45 +1,53 @@
<template>
<div class="min-h-screen bg-background w-full">
<SidebarProvider v-model:open="collapsed" class="w-full">
<div class="flex min-h-screen w-full">
<LayoutSider
title="登录"
:menu-items="siderMenuItems"
:active-index="activeSiderIndex"
:collapsed="collapsed"
@select-item="handleSiderSelect"
<TooltipProvider>
<div class="min-h-screen bg-background w-full flex relative">
<!-- 侧边栏 -->
<LayoutSider
:menu-items="siderMenuItems"
:active-index="activeSiderIndex"
:collapsed="sidebarCollapsed"
@select-item="handleSiderSelect"
@toggle-collapse="handleToggleCollapse"
/>

<!-- 主内容区域 -->
<div class="flex-1 min-w-0 flex flex-col h-screen">
<!-- Header -->
<LayoutHeader
:sidebar-collapsed="sidebarCollapsed"
@toggle-sidebar="handleToggleCollapse"
/>
<SidebarInset class="flex-1 w-full min-w-0">
<div class="flex flex-col min-h-screen w-full">
<!-- Header 在 SidebarInset 内部 -->
<LayoutHeader />
<!-- 主要内容区域 -->
<main class="flex-1 w-full overflow-x-auto">
<slot />
</main>
</div>
</SidebarInset>

<!-- 主要内容区域 -->
<main class="flex-1 overflow-auto bg-background">
<slot />
</main>
</div>
</SidebarProvider>
</div>
</div>
</TooltipProvider>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { ref, watch } from 'vue';
import { Home, User, Search, HelpCircle, Database } from 'lucide-vue-next';
import { SidebarProvider, SidebarInset } from '@/components/ui/sidebar';
import { TooltipProvider } from '@/components/ui/tooltip';
import LayoutHeader from './header.vue';
import LayoutSider from './sider.vue';

const activeSiderIndex = ref<number>(0);
const collapsed = ref<boolean>(false);
const sidebarCollapsed = ref<boolean>(false);

// 切换侧边栏收起状态
const handleToggleCollapse = () => {
sidebarCollapsed.value = !sidebarCollapsed.value;
};

// 移除 immediate: true 来避免 SSR 错误
// 监听侧边栏状态变化 - 仅在客户端
if (import.meta.client) {
watch(
() => collapsed.value,
() => {
console.log('collapsed', collapsed.value);
() => sidebarCollapsed.value,
(newValue) => {
console.log('侧边栏状态:', newValue ? '收起' : '展开');
}
);
}
Expand Down
19 changes: 16 additions & 3 deletions src/layouts/header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
<div class="flex items-center justify-between px-5 h-[60px] bg-background border-b w-full">
<div class="flex items-center space-x-3">
<!-- 侧边栏控制按钮 -->
<SidebarTrigger />
<Button variant="ghost" size="sm" class="hover:bg-accent" @click="$emit('toggle-sidebar')">
<Menu class="h-5 w-5" />
</Button>
<h1 class="text-xl font-bold">
<span class="text-[#00B42A]">Y</span><span class="text-foreground">RC6</span>
</h1>
Expand Down Expand Up @@ -70,8 +72,19 @@ import {
DropdownMenuItem,
} from '@/components/ui/dropdown-menu';
import ThemeToggle from '@/components/ThemeToggle.vue';
import { SidebarTrigger } from '@/components/ui/sidebar';
import { Plus, FolderOpen } from 'lucide-vue-next';
import { Plus, FolderOpen, Menu } from 'lucide-vue-next';

// 定义 Props
interface Props {
sidebarCollapsed?: boolean;
}

withDefaults(defineProps<Props>(), {
sidebarCollapsed: false,
});

// 定义 emits
defineEmits(['toggle-sidebar']);

const authStore = useAuthStore();
const modalStore = useModalStore();
Expand Down
Loading