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:
@@ -1,6 +1,6 @@
|
||||
<!-- src/views/GamesLibrary.vue -->
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import { useGroupStore } from '@/stores/group'
|
||||
import { getGroupGames, deleteGame, exportGames, getAllPlatforms } from '@/api/games'
|
||||
import type { Game, GamePlatform } from '@/types'
|
||||
@@ -12,6 +12,7 @@ import { Close } from '@element-plus/icons-vue'
|
||||
|
||||
const groupStore = useGroupStore()
|
||||
|
||||
const selectedGroupId = ref('')
|
||||
const games = ref<Game[]>([])
|
||||
const loading = ref(false)
|
||||
const searchQuery = ref('')
|
||||
@@ -22,19 +23,35 @@ const showDetail = ref(false)
|
||||
const showAddGame = ref(false)
|
||||
const showImport = ref(false)
|
||||
|
||||
const currentGroupId = computed(() => groupStore.currentGroupId)
|
||||
const groupOptions = computed(() => groupStore.groups)
|
||||
const hasGroups = computed(() => groupOptions.value.length > 0)
|
||||
|
||||
onMounted(async () => {
|
||||
if (currentGroupId.value) {
|
||||
if (groupStore.groups.length === 0) {
|
||||
await groupStore.loadGroups()
|
||||
}
|
||||
// 优先使用当前群组,否则选第一个
|
||||
if (groupStore.currentGroupId) {
|
||||
selectedGroupId.value = groupStore.currentGroupId
|
||||
} else if (groupOptions.value.length > 0) {
|
||||
selectedGroupId.value = groupOptions.value[0].id
|
||||
}
|
||||
if (selectedGroupId.value) {
|
||||
await loadGames()
|
||||
}
|
||||
})
|
||||
|
||||
watch(selectedGroupId, () => {
|
||||
searchQuery.value = ''
|
||||
selectedPlatform.value = ''
|
||||
loadGames()
|
||||
})
|
||||
|
||||
async function loadGames() {
|
||||
if (!currentGroupId.value) return
|
||||
if (!selectedGroupId.value) return
|
||||
try {
|
||||
loading.value = true
|
||||
const result = await getGroupGames(currentGroupId.value, {
|
||||
const result = await getGroupGames(selectedGroupId.value, {
|
||||
limit: 100,
|
||||
search: searchQuery.value || undefined,
|
||||
platform: selectedPlatform.value || undefined
|
||||
@@ -66,8 +83,8 @@ async function handleDeleteGame(game: Game) {
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
if (!currentGroupId.value) return
|
||||
exportGames(currentGroupId.value).then(data => {
|
||||
if (!selectedGroupId.value) return
|
||||
exportGames(selectedGroupId.value).then(data => {
|
||||
const json = JSON.stringify(data.map(g => ({
|
||||
name: g.name,
|
||||
platform: g.platform,
|
||||
@@ -78,7 +95,7 @@ function handleExport() {
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `games-export-${currentGroupId.value}.json`
|
||||
a.download = `games-export-${selectedGroupId.value}.json`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
ElMessage.success(`已导出 ${data.length} 个游戏`)
|
||||
@@ -98,35 +115,56 @@ async function handleImportComplete() {
|
||||
|
||||
<template>
|
||||
<div class="games-library">
|
||||
<!-- 无群组提示 -->
|
||||
<div v-if="!currentGroupId" class="no-group">
|
||||
<p class="no-group-text">请先选择一个群组</p>
|
||||
<p class="no-group-hint">在左侧选择或创建群组后,即可管理游戏库</p>
|
||||
<!-- 无群组引导 -->
|
||||
<div v-if="!hasGroups" class="no-group">
|
||||
<div class="no-group-icon">🎮</div>
|
||||
<p class="no-group-text">还没有群组</p>
|
||||
<p class="no-group-hint">先创建或加入一个群组,然后就可以管理游戏库了</p>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- 顶部栏:群组选择 + 工具栏 -->
|
||||
<div class="page-header">
|
||||
<h1>游戏库</h1>
|
||||
<div class="toolbar">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="搜索游戏..."
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@input="loadGames"
|
||||
/>
|
||||
<el-select v-model="selectedPlatform" placeholder="平台" clearable style="width: 120px" @change="loadGames">
|
||||
<el-option v-for="p in platforms" :key="p" :label="p" :value="p" />
|
||||
<div class="header-row">
|
||||
<el-select
|
||||
v-model="selectedGroupId"
|
||||
placeholder="选择群组"
|
||||
class="group-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="g in groupOptions"
|
||||
:key="g.id"
|
||||
:label="g.name"
|
||||
:value="g.id"
|
||||
/>
|
||||
</el-select>
|
||||
<button class="tool-btn primary" @click="showAddGame = true">添加游戏</button>
|
||||
<button class="tool-btn" @click="showImport = true">导入</button>
|
||||
<button class="tool-btn" @click="handleExport">导出</button>
|
||||
<div class="toolbar">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="搜索游戏..."
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@input="loadGames"
|
||||
/>
|
||||
<el-select v-model="selectedPlatform" placeholder="平台" clearable style="width: 110px" @change="loadGames">
|
||||
<el-option v-for="p in platforms" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions-row">
|
||||
<span class="game-count" v-if="!loading">{{ games.length }} 个游戏</span>
|
||||
<div class="action-btns">
|
||||
<button class="tool-btn primary" @click="showAddGame = true">添加游戏</button>
|
||||
<button class="tool-btn" @click="showImport = true">导入</button>
|
||||
<button class="tool-btn" @click="handleExport">导出</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
|
||||
<div v-else-if="games.length === 0" class="empty">
|
||||
<div class="empty-icon">📦</div>
|
||||
<p class="empty-text">暂无游戏</p>
|
||||
<p class="empty-hint">点击「添加游戏」或「导入」开始管理游戏库</p>
|
||||
</div>
|
||||
@@ -145,9 +183,9 @@ async function handleImportComplete() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<GameDetailDialog v-model="showDetail" :game="selectedGame" :group-id="currentGroupId" @deleted="loadGames" />
|
||||
<AddGameDialog v-model="showAddGame" :group-id="currentGroupId" @created="handleGameAdded" />
|
||||
<ImportGamesDialog v-model="showImport" :group-id="currentGroupId" @imported="handleImportComplete" />
|
||||
<GameDetailDialog v-model="showDetail" :game="selectedGame" :group-id="selectedGroupId" @deleted="loadGames" />
|
||||
<AddGameDialog v-model="showAddGame" :group-id="selectedGroupId" @created="handleGameAdded" />
|
||||
<ImportGamesDialog v-model="showImport" :group-id="selectedGroupId" @imported="handleImportComplete" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -156,70 +194,262 @@ async function handleImportComplete() {
|
||||
.games-library { width: 100%; }
|
||||
|
||||
.no-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100px 32px;
|
||||
text-align: center;
|
||||
padding: 120px 32px;
|
||||
}
|
||||
.no-group-text { font-size: 18px; font-weight: 600; color: var(--gg-text-secondary); margin: 0 0 8px; }
|
||||
.no-group-hint { font-size: 14px; color: var(--gg-text-muted); margin: 0; }
|
||||
|
||||
.page-header { margin-bottom: 24px; }
|
||||
.page-header h1 {
|
||||
font-size: 28px; font-weight: 700; margin: 0 0 16px;
|
||||
background: var(--gg-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
||||
}
|
||||
|
||||
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
.no-group-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.no-group-text {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--gg-text-secondary);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.no-group-hint {
|
||||
font-size: 14px;
|
||||
color: var(--gg-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── 页面头部 ── */
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.group-select {
|
||||
width: 220px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actions-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.game-count {
|
||||
font-size: 13px;
|
||||
color: var(--gg-text-muted);
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tool-btn {
|
||||
padding: 8px 18px; 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; font-weight: 500;
|
||||
cursor: pointer; transition: all 0.2s; white-space: nowrap;
|
||||
padding: 7px 16px;
|
||||
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;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tool-btn:hover { border-color: var(--gg-primary); color: var(--gg-primary); }
|
||||
|
||||
.tool-btn:hover {
|
||||
border-color: var(--gg-primary);
|
||||
color: var(--gg-primary);
|
||||
}
|
||||
|
||||
.tool-btn.primary {
|
||||
background: var(--gg-gradient-green); border-color: transparent; color: white;
|
||||
background: var(--gg-gradient-green);
|
||||
border-color: transparent;
|
||||
color: white;
|
||||
}
|
||||
.tool-btn.primary:hover { opacity: 0.9; }
|
||||
|
||||
.loading { text-align: center; padding: 80px 32px; color: var(--gg-text-muted); }
|
||||
.tool-btn.primary:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.empty { text-align: center; padding: 80px 32px; }
|
||||
.empty-text { font-size: 16px; font-weight: 500; color: var(--gg-text-secondary); margin: 0 0 8px; }
|
||||
.empty-hint { font-size: 14px; color: var(--gg-text-muted); margin: 0; }
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 80px 32px;
|
||||
color: var(--gg-text-muted);
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80px 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--gg-text-secondary);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 14px;
|
||||
color: var(--gg-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── 游戏网格 ── */
|
||||
.games-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.game-card {
|
||||
position: relative; border-radius: var(--gg-radius-md); overflow: hidden; cursor: pointer;
|
||||
background: var(--gg-bg-card); border: 1px solid var(--gg-border);
|
||||
position: relative;
|
||||
border-radius: var(--gg-radius-md);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background: var(--gg-bg-card);
|
||||
border: 1px solid var(--gg-border);
|
||||
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
|
||||
}
|
||||
.game-card:hover { transform: translateY(-4px); border-color: var(--gg-primary); box-shadow: 0 4px 20px rgba(5, 150, 105, 0.15); }
|
||||
|
||||
.game-cover { width: 100%; aspect-ratio: 3/4; object-fit: cover; }
|
||||
.game-card:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: var(--gg-primary);
|
||||
box-shadow: 0 4px 16px rgba(5, 150, 105, 0.12);
|
||||
}
|
||||
|
||||
.game-info { padding: 14px; }
|
||||
.game-cover {
|
||||
width: 100%;
|
||||
aspect-ratio: 3/4;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.game-info {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.game-name {
|
||||
font-size: 16px; font-weight: 700; margin: 0 0 4px; color: var(--gg-text);
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 4px;
|
||||
color: var(--gg-text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.game-platform {
|
||||
font-size: 12px;
|
||||
color: var(--gg-text-secondary);
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
.game-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
.game-platform { font-size: 13px; color: var(--gg-text-secondary); margin: 0 0 8px; }
|
||||
|
||||
.game-tags { display: flex; flex-wrap: wrap; gap: 4px; }
|
||||
.tag {
|
||||
padding: 3px 10px; background: var(--gg-bg-elevated); border-radius: 6px;
|
||||
font-size: 11px; color: var(--gg-primary); font-weight: 500;
|
||||
padding: 2px 8px;
|
||||
background: rgba(5, 150, 105, 0.1);
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--gg-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute; top: 8px; right: 8px; width: 28px; height: 28px;
|
||||
border: none; border-radius: 50%; background: rgba(0,0,0,0.5); color: white;
|
||||
font-size: 12px; cursor: pointer; opacity: 0; transition: opacity 0.2s;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.game-card:hover .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background: var(--gg-danger);
|
||||
}
|
||||
|
||||
/* ── 移动端 ── */
|
||||
@media (max-width: 768px) {
|
||||
.header-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.group-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toolbar .el-input {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.actions-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tool-btn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.games-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
.game-card:hover .delete-btn { opacity: 1; }
|
||||
.delete-btn:hover { background: var(--gg-danger); }
|
||||
</style>
|
||||
|
||||
@@ -219,7 +219,7 @@ async function refreshMembers() {
|
||||
font-size: 13px;
|
||||
padding: 4px 14px;
|
||||
border-radius: 20px;
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
background: rgba(5, 150, 105, 0.15);
|
||||
color: var(--gg-primary-light);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
+257
-86
@@ -4,17 +4,24 @@ import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGroupStore } from '@/stores/group'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useTeamStore } from '@/stores/team'
|
||||
import { getPopularGames } from '@/api/games'
|
||||
import GameDetailDialog from '@/components/game/GameDetailDialog.vue'
|
||||
import { UserStatusMap } from '@/types'
|
||||
import type { Game } from '@/types'
|
||||
import TeamSessionPanel from '@/components/team/TeamSessionPanel.vue'
|
||||
import IdleMembersList from '@/components/team/IdleMembersList.vue'
|
||||
import { User, Promotion, TrendCharts } from '@element-plus/icons-vue'
|
||||
import { Plus, Search, User, Promotion, TrendCharts } from '@element-plus/icons-vue'
|
||||
import CreateGroupDialog from '@/components/group/CreateGroupDialog.vue'
|
||||
import JoinGroupDialog from '@/components/group/JoinGroupDialog.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const groupStore = useGroupStore()
|
||||
const userStore = useUserStore()
|
||||
const teamStore = useTeamStore()
|
||||
|
||||
const showCreateGroup = ref(false)
|
||||
const showJoinGroup = ref(false)
|
||||
|
||||
const popularGames = ref<Game[]>([])
|
||||
const loading = ref(false)
|
||||
@@ -28,6 +35,9 @@ const statusDotClass = computed(() => {
|
||||
|
||||
const statusText = computed(() => UserStatusMap[userStore.userStatus] || '未知')
|
||||
|
||||
const hasNoGroup = computed(() => groupStore.groups.length === 0)
|
||||
const hasNoSession = computed(() => !teamStore.currentSession)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadPopularGames()
|
||||
})
|
||||
@@ -56,33 +66,54 @@ function openGameDetail(game: Game) {
|
||||
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<!-- 欢迎区 -->
|
||||
<section class="welcome-section">
|
||||
<h1 class="welcome-title">
|
||||
欢迎回来, <span class="gg-text-gradient">{{ userStore.user?.username || '玩家' }}</span>!
|
||||
</h1>
|
||||
<div class="status-line">
|
||||
<span :class="statusDotClass" />
|
||||
<span class="status-text">{{ statusText }}</span>
|
||||
<span v-if="userStore.user?.statusNote" class="status-note">{{ userStore.user.statusNote }}</span>
|
||||
<!-- 欢迎 + 状态条 -->
|
||||
<section class="welcome-bar">
|
||||
<div class="welcome-left">
|
||||
<h1 class="welcome-title">
|
||||
欢迎回来, <span class="gg-text-gradient">{{ userStore.user?.username || '玩家' }}</span>
|
||||
</h1>
|
||||
<div class="status-line">
|
||||
<span :class="statusDotClass" />
|
||||
<span class="status-text">{{ statusText }}</span>
|
||||
<span v-if="userStore.user?.statusNote" class="status-note">{{ userStore.user.statusNote }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="welcome-actions">
|
||||
<button class="cta-btn cta-btn--primary" @click="showCreateGroup = true">
|
||||
<el-icon><Plus /></el-icon> 创建群组
|
||||
</button>
|
||||
<button class="cta-btn cta-btn--secondary" @click="showJoinGroup = true">
|
||||
<el-icon><Search /></el-icon> 加入群组
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 无群组引导 -->
|
||||
<section v-if="hasNoGroup" class="onboarding-section">
|
||||
<div class="onboarding-card">
|
||||
<div class="onboarding-icon"><el-icon :size="48"><User /></el-icon></div>
|
||||
<h2 class="onboarding-title">开始你的组队之旅</h2>
|
||||
<p class="onboarding-desc">创建一个群组邀请好友,或搜索加入已有群组</p>
|
||||
<div class="onboarding-actions">
|
||||
<button class="onboarding-btn onboarding-btn--primary" @click="showCreateGroup = true">
|
||||
<el-icon><Plus /></el-icon> 创建群组
|
||||
</button>
|
||||
<button class="onboarding-btn onboarding-btn--outline" @click="showJoinGroup = true">
|
||||
<el-icon><Search /></el-icon> 加入群组
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 主内容双栏 -->
|
||||
<div class="content-grid">
|
||||
<div v-else class="content-grid">
|
||||
<!-- 左列: 我的群组 -->
|
||||
<section class="section groups-section">
|
||||
<h2 class="section-title">
|
||||
<el-icon class="section-icon"><User /></el-icon> 我的群组
|
||||
</h2>
|
||||
|
||||
<div v-if="groupStore.groups.length === 0" class="empty-state">
|
||||
<div class="empty-icon"><el-icon :size="40"><User /></el-icon></div>
|
||||
<p class="empty-text">暂无群组</p>
|
||||
<p class="empty-hint">创建或加入一个群组,开始组队冒险吧</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="group-grid">
|
||||
<div class="group-grid">
|
||||
<div
|
||||
v-for="group in groupStore.groups"
|
||||
:key="group.id"
|
||||
@@ -107,7 +138,7 @@ function openGameDetail(game: Game) {
|
||||
<TeamSessionPanel />
|
||||
</div>
|
||||
|
||||
<div class="idle-section">
|
||||
<div v-if="!hasNoSession" class="idle-section">
|
||||
<IdleMembersList status="idle" />
|
||||
</div>
|
||||
</section>
|
||||
@@ -124,9 +155,8 @@ function openGameDetail(game: Game) {
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="popularGames.length === 0" class="empty-state">
|
||||
<div class="empty-icon"><el-icon :size="40"><TrendCharts /></el-icon></div>
|
||||
<p class="empty-text">暂无热门游戏</p>
|
||||
<div v-else-if="popularGames.length === 0" class="empty-games">
|
||||
<p>暂无热门游戏,去群组中添加游戏吧</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="games-scroll">
|
||||
@@ -152,6 +182,8 @@ function openGameDetail(game: Game) {
|
||||
</section>
|
||||
|
||||
<GameDetailDialog v-model="showGameDetail" :game="selectedGame" @create-team="() => {}" />
|
||||
<CreateGroupDialog v-model="showCreateGroup" />
|
||||
<JoinGroupDialog v-model="showJoinGroup" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -160,12 +192,15 @@ function openGameDetail(game: Game) {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 28px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* ── 欢迎区 ── */
|
||||
.welcome-section {
|
||||
padding: 28px 32px;
|
||||
/* ── 欢迎条 ── */
|
||||
.welcome-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px 28px;
|
||||
background: var(--gg-bg-card);
|
||||
border: 1px solid var(--gg-border);
|
||||
border-radius: var(--gg-radius-lg);
|
||||
@@ -173,7 +208,7 @@ function openGameDetail(game: Game) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.welcome-section::before {
|
||||
.welcome-bar::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -183,30 +218,149 @@ function openGameDetail(game: Game) {
|
||||
background: var(--gg-gradient);
|
||||
}
|
||||
|
||||
.welcome-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 28px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 12px;
|
||||
margin: 0;
|
||||
color: var(--gg-text);
|
||||
}
|
||||
|
||||
.status-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
color: var(--gg-text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-note {
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
color: var(--gg-text-muted);
|
||||
}
|
||||
|
||||
.welcome-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.cta-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 9px 18px;
|
||||
border-radius: var(--gg-radius-sm);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.cta-btn--primary {
|
||||
background: var(--gg-gradient-green);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cta-btn--primary:hover {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 2px 12px rgba(5, 150, 105, 0.3);
|
||||
}
|
||||
|
||||
.cta-btn--secondary {
|
||||
background: var(--gg-bg-card);
|
||||
border: 1px solid var(--gg-border);
|
||||
color: var(--gg-text-secondary);
|
||||
}
|
||||
|
||||
.cta-btn--secondary:hover {
|
||||
border-color: var(--gg-primary);
|
||||
color: var(--gg-primary);
|
||||
}
|
||||
|
||||
/* ── 新手引导 ── */
|
||||
.onboarding-section {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.onboarding-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 48px 32px;
|
||||
background: var(--gg-bg-card);
|
||||
border: 2px dashed var(--gg-border);
|
||||
border-radius: var(--gg-radius-lg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.onboarding-icon {
|
||||
font-size: 48px;
|
||||
color: var(--gg-primary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.onboarding-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--gg-text);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.onboarding-desc {
|
||||
font-size: 14px;
|
||||
color: var(--gg-text-muted);
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
|
||||
.onboarding-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.onboarding-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 28px;
|
||||
border-radius: var(--gg-radius-sm);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.onboarding-btn--primary {
|
||||
background: var(--gg-gradient-green);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.onboarding-btn--primary:hover {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 4px 20px rgba(5, 150, 105, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.onboarding-btn--outline {
|
||||
background: var(--gg-bg-card);
|
||||
border: 2px solid var(--gg-primary);
|
||||
color: var(--gg-primary);
|
||||
}
|
||||
|
||||
.onboarding-btn--outline:hover {
|
||||
background: rgba(5, 150, 105, 0.06);
|
||||
}
|
||||
|
||||
/* ── 通用 section ── */
|
||||
.section {
|
||||
display: flex;
|
||||
@@ -214,9 +368,9 @@ function openGameDetail(game: Game) {
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 16px;
|
||||
margin: 0 0 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -224,47 +378,47 @@ function openGameDetail(game: Game) {
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* ── 双栏内容 ── */
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 340px;
|
||||
gap: 28px;
|
||||
grid-template-columns: 1fr 320px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* ── 群组区 ── */
|
||||
.group-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 14px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.group-card {
|
||||
background: var(--gg-bg-card);
|
||||
border: 1px solid var(--gg-border);
|
||||
border-radius: var(--gg-radius-md);
|
||||
padding: 18px 20px;
|
||||
padding: 16px 18px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s, box-shadow 0.25s, transform 0.2s;
|
||||
}
|
||||
|
||||
.group-card:hover {
|
||||
border-color: var(--gg-primary);
|
||||
box-shadow: 0 0 24px rgba(99, 102, 241, 0.18);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0 20px rgba(5, 150, 105, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.group-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--gg-text);
|
||||
overflow: hidden;
|
||||
@@ -274,10 +428,10 @@ function openGameDetail(game: Game) {
|
||||
|
||||
.member-badge {
|
||||
font-size: 12px;
|
||||
padding: 3px 10px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 20px;
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
color: var(--gg-primary-light);
|
||||
background: rgba(5, 150, 105, 0.12);
|
||||
color: var(--gg-primary);
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -297,7 +451,7 @@ function openGameDetail(game: Game) {
|
||||
.session-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.session-card,
|
||||
@@ -313,6 +467,16 @@ function openGameDetail(game: Game) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.empty-games {
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
color: var(--gg-text-muted);
|
||||
font-size: 14px;
|
||||
background: var(--gg-bg-card);
|
||||
border: 1px solid var(--gg-border);
|
||||
border-radius: var(--gg-radius-md);
|
||||
}
|
||||
|
||||
.games-scroll {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
@@ -332,22 +496,22 @@ function openGameDetail(game: Game) {
|
||||
|
||||
.game-card {
|
||||
flex-shrink: 0;
|
||||
width: 160px;
|
||||
width: 150px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.25s, box-shadow 0.25s;
|
||||
}
|
||||
|
||||
.game-card:hover {
|
||||
transform: translateY(-4px);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.game-card:hover .game-cover-wrap {
|
||||
box-shadow: 0 0 20px rgba(99, 102, 241, 0.2);
|
||||
box-shadow: 0 0 16px rgba(5, 150, 105, 0.15);
|
||||
}
|
||||
|
||||
.game-cover-wrap {
|
||||
width: 160px;
|
||||
height: 200px;
|
||||
width: 150px;
|
||||
height: 190px;
|
||||
border-radius: var(--gg-radius-md);
|
||||
overflow: hidden;
|
||||
background: var(--gg-bg-elevated);
|
||||
@@ -365,12 +529,12 @@ function openGameDetail(game: Game) {
|
||||
.game-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-top: 10px;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.game-name {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--gg-text);
|
||||
overflow: hidden;
|
||||
@@ -384,40 +548,11 @@ function openGameDetail(game: Game) {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(168, 85, 247, 0.15);
|
||||
color: var(--gg-accent-light);
|
||||
background: rgba(5, 150, 105, 0.1);
|
||||
color: var(--gg-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ── 空状态 ── */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px 20px;
|
||||
background: var(--gg-bg-card);
|
||||
border: 1px dashed var(--gg-border);
|
||||
border-radius: var(--gg-radius-md);
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 40px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 15px;
|
||||
color: var(--gg-text-secondary);
|
||||
margin: 0 0 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 13px;
|
||||
color: var(--gg-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── 加载状态 ── */
|
||||
.loading-state {
|
||||
display: flex;
|
||||
@@ -441,4 +576,40 @@ function openGameDetail(game: Game) {
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ── 移动端适配 ── */
|
||||
@media (max-width: 768px) {
|
||||
.welcome-bar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.welcome-actions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cta-btn {
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.group-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.onboarding-actions {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.onboarding-btn {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+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>
|
||||
|
||||
@@ -106,7 +106,7 @@ function handleAvatarUpload() {
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 3px solid var(--gg-primary);
|
||||
box-shadow: 0 0 16px rgba(99, 102, 241, 0.25);
|
||||
box-shadow: 0 0 16px rgba(5, 150, 105, 0.25);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
|
||||
@@ -81,7 +81,7 @@ const showScheduleModal = ref(false)
|
||||
|
||||
.setting-item:hover {
|
||||
border-color: var(--gg-primary);
|
||||
box-shadow: 0 0 12px rgba(99, 102, 241, 0.1);
|
||||
box-shadow: 0 0 12px rgba(5, 150, 105, 0.1);
|
||||
}
|
||||
|
||||
.setting-info {
|
||||
@@ -115,7 +115,7 @@ const showScheduleModal = ref(false)
|
||||
|
||||
.setting-btn:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 0 12px rgba(99, 102, 241, 0.3);
|
||||
box-shadow: 0 0 12px rgba(5, 150, 105, 0.3);
|
||||
}
|
||||
|
||||
.setting-btn:disabled {
|
||||
|
||||
Reference in New Issue
Block a user