feat: UI redesign v0.0.2 — color unification, navigation improvements, mobile support
- Unify color palette from mixed green/blue/purple to consistent green theme - Sidebar: add text labels to create/join group buttons for discoverability - Header: add quick action buttons (create group, join group, notifications) - Mobile: add hamburger menu with slide-out sidebar and overlay - Home: add prominent CTA buttons, onboarding card for empty state - Join group dialog: add search-by-name mode alongside existing ID lookup - Games library: inline group selector dropdown instead of external selection Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+220
-39
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useGroupStore } from '@/stores/group'
|
||||
@@ -10,7 +10,7 @@ import WorkScheduleModal from '@/components/team/WorkScheduleModal.vue'
|
||||
import NotificationPanel from '@/components/common/NotificationPanel.vue'
|
||||
import CreateGroupDialog from '@/components/group/CreateGroupDialog.vue'
|
||||
import JoinGroupDialog from '@/components/group/JoinGroupDialog.vue'
|
||||
import { Monitor, HomeFilled, Grid, Link, AlarmClock, SwitchButton, Bell, Plus } from '@element-plus/icons-vue'
|
||||
import { Monitor, HomeFilled, Grid, Plus, Search, Bell, AlarmClock, SwitchButton } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -22,6 +22,7 @@ const notificationStore = useNotificationStore()
|
||||
const showScheduleModal = ref(false)
|
||||
const showCreateGroup = ref(false)
|
||||
const showJoinGroup = ref(false)
|
||||
const sidebarOpen = ref(false)
|
||||
|
||||
let refreshTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
@@ -32,7 +33,6 @@ onMounted(async () => {
|
||||
await notificationStore.loadPendingInvitations()
|
||||
await notificationStore.startListening()
|
||||
|
||||
// 每30秒轮询刷新群组列表和临时小组状态
|
||||
refreshTimer = setInterval(async () => {
|
||||
await groupStore.loadGroups()
|
||||
await teamStore.loadActiveSession()
|
||||
@@ -55,17 +55,30 @@ function handleLogout() {
|
||||
function selectGroup(groupId: string) {
|
||||
groupStore.setCurrentGroup(groupId)
|
||||
router.push({ name: 'GroupView', params: { id: groupId } })
|
||||
sidebarOpen.value = false
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
router.push({ name: 'Home' })
|
||||
sidebarOpen.value = false
|
||||
}
|
||||
|
||||
const pageTitle = computed(() => {
|
||||
if (route.name === 'GroupView') return groupStore.currentGroup?.name
|
||||
if (route.name === 'GamesLibrary') return '游戏库'
|
||||
if (route.name === 'Profile') return '个人中心'
|
||||
if (route.name === 'Settings') return '设置'
|
||||
return '首页'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-layout">
|
||||
<!-- 移动端遮罩 -->
|
||||
<div v-if="sidebarOpen" class="sidebar-overlay" @click="sidebarOpen = false" />
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<aside class="sidebar" :class="{ 'sidebar--open': sidebarOpen }">
|
||||
<div class="sidebar-top">
|
||||
<div class="logo" @click="goHome">
|
||||
<el-icon class="logo-icon"><Monitor /></el-icon>
|
||||
@@ -86,16 +99,26 @@ function goHome() {
|
||||
|
||||
<div class="sidebar-divider" />
|
||||
|
||||
<!-- 群组操作区 -->
|
||||
<div class="sidebar-group-actions">
|
||||
<button class="group-action-btn group-action-btn--create" @click="showCreateGroup = true">
|
||||
<el-icon><Plus /></el-icon>
|
||||
<span>创建群组</span>
|
||||
</button>
|
||||
<button class="group-action-btn group-action-btn--join" @click="showJoinGroup = true">
|
||||
<el-icon><Search /></el-icon>
|
||||
<span>加入群组</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 群组列表 -->
|
||||
<div class="sidebar-groups">
|
||||
<div class="section-header">
|
||||
<span class="section-title">我的群组</span>
|
||||
<div class="section-actions">
|
||||
<button class="section-btn" @click="showCreateGroup = true" title="创建群组"><el-icon><Plus /></el-icon></button>
|
||||
<button class="section-btn" @click="showJoinGroup = true" title="加入群组"><el-icon><Link /></el-icon></button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="groupStore.groups.length === 0" class="empty-groups">
|
||||
暂无群组
|
||||
<p>还没有群组</p>
|
||||
<p class="empty-hint">点击上方按钮创建或加入</p>
|
||||
</div>
|
||||
<div
|
||||
v-for="group in groupStore.groups"
|
||||
@@ -134,10 +157,24 @@ function goHome() {
|
||||
<!-- 右侧 -->
|
||||
<div class="main-wrapper">
|
||||
<header class="top-header">
|
||||
<h2 class="page-title">
|
||||
{{ $route.name === 'GroupView' ? groupStore.currentGroup?.name : $route.name === 'GamesLibrary' ? '游戏库' : $route.name === 'Profile' ? '个人中心' : $route.name === 'Settings' ? '设置' : '首页' }}
|
||||
</h2>
|
||||
<!-- 移动端汉堡按钮 -->
|
||||
<button class="hamburger-btn" @click="sidebarOpen = !sidebarOpen">
|
||||
<span class="hamburger-line" />
|
||||
<span class="hamburger-line" />
|
||||
<span class="hamburger-line" />
|
||||
</button>
|
||||
|
||||
<h2 class="page-title">{{ pageTitle }}</h2>
|
||||
|
||||
<div class="header-actions">
|
||||
<button class="header-action-btn" @click="showCreateGroup = true" title="创建群组">
|
||||
<el-icon><Plus /></el-icon>
|
||||
<span class="header-action-label">创建群组</span>
|
||||
</button>
|
||||
<button class="header-action-btn" @click="showJoinGroup = true" title="加入群组">
|
||||
<el-icon><Search /></el-icon>
|
||||
<span class="header-action-label">加入群组</span>
|
||||
</button>
|
||||
<button class="icon-btn" @click="notificationStore.togglePanel()">
|
||||
<span v-if="notificationStore.unreadCount > 0" class="badge">
|
||||
{{ notificationStore.unreadCount }}
|
||||
@@ -247,6 +284,50 @@ function goHome() {
|
||||
margin: 12px 20px;
|
||||
}
|
||||
|
||||
/* ── 群组操作按钮 ── */
|
||||
.sidebar-group-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 0 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.group-action-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 8px 10px;
|
||||
border-radius: var(--gg-radius-sm);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.group-action-btn--create {
|
||||
background: var(--gg-gradient-green);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.group-action-btn--create:hover {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 2px 12px rgba(5, 150, 105, 0.3);
|
||||
}
|
||||
|
||||
.group-action-btn--join {
|
||||
background: var(--gg-bg-card);
|
||||
border: 1px solid var(--gg-border);
|
||||
color: var(--gg-text-secondary);
|
||||
}
|
||||
|
||||
.group-action-btn--join:hover {
|
||||
border-color: var(--gg-primary);
|
||||
color: var(--gg-primary);
|
||||
}
|
||||
|
||||
/* ── 群组列表 ── */
|
||||
.sidebar-groups {
|
||||
flex: 1;
|
||||
@@ -258,44 +339,32 @@ function goHome() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 4px 14px;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 14px 4px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--gg-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.section-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.section-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--gg-border);
|
||||
border-radius: 6px;
|
||||
color: var(--gg-text-secondary);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
padding: 2px 8px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.section-btn:hover {
|
||||
border-color: var(--gg-primary);
|
||||
color: var(--gg-primary);
|
||||
}
|
||||
|
||||
.empty-groups {
|
||||
text-align: center;
|
||||
color: var(--gg-text-muted);
|
||||
padding: 16px 14px;
|
||||
}
|
||||
|
||||
.empty-groups p {
|
||||
font-size: 13px;
|
||||
padding: 16px;
|
||||
color: var(--gg-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 12px !important;
|
||||
margin-top: 4px !important;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.group-item {
|
||||
@@ -419,6 +488,7 @@ function goHome() {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -426,12 +496,43 @@ function goHome() {
|
||||
font-weight: 600;
|
||||
color: var(--gg-text);
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 14px;
|
||||
border: 1px solid var(--gg-border);
|
||||
border-radius: var(--gg-radius-sm);
|
||||
background: var(--gg-bg-card);
|
||||
color: var(--gg-text-secondary);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.header-action-btn:first-child {
|
||||
background: var(--gg-gradient-green);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.header-action-btn:first-child:hover {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 2px 8px rgba(5, 150, 105, 0.3);
|
||||
}
|
||||
|
||||
.header-action-btn:not(:first-child):hover {
|
||||
border-color: var(--gg-primary);
|
||||
color: var(--gg-primary);
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
@@ -469,4 +570,84 @@ function goHome() {
|
||||
flex: 1;
|
||||
padding: 24px 28px;
|
||||
}
|
||||
|
||||
/* ── 汉堡按钮 ── */
|
||||
.hamburger-btn {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 8px 6px;
|
||||
background: none;
|
||||
border: 1px solid var(--gg-border);
|
||||
border-radius: var(--gg-radius-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hamburger-line {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--gg-text);
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* ── 移动端遮罩 ── */
|
||||
.sidebar-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 90;
|
||||
}
|
||||
|
||||
/* ── 移动端适配 ── */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sidebar--open {
|
||||
transform: translateX(0);
|
||||
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.sidebar-overlay {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.hamburger-btn {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header-action-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-action-btn {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
|
||||
.top-header {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-width: 1024px) {
|
||||
.header-action-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user