初始提交:浏览器首页 MyHomePage 全栈项目
# 项目概述 个人浏览器首页导航应用,支持书签分类管理、搜索引擎快捷搜索、 必应每日壁纸轮播、前后端分离部署,适配 1Panel 服务器(Docker 模式)。 # 技术栈 - 前端:Vue 3 + TypeScript + Vite + Pinia + Capacitor(Android 打包) - 后端:.NET 8 + SqlSugar(多数据库) + SQLite/MySQL + Swashbuckle - 部署:1Panel 应用商店自定义应用(Docker Compose 模式) # 项目结构 - backend/ .NET 8 API 后端(8 个 Controller + 15 个 Service) - frontend/ Vue 3 前端(19 个组件 + 9 个 API 模块 + 5 个 Store) - docker/ Docker 部署文件(后端镜像 + Nginx 反代) - docs/ 部署手册(1Panel 实战版) - scripts/ E2E 测试脚本 # 已实现功能 - 书签管理:增删改查 + 树形分类 + 拖拽排序 + 主色自适应 - 搜索引擎:8 个内置引擎 + 自定义引擎 + favicon 自动抓取 - 必应壁纸:每日轮播 + 多分辨率自动选择 + 1.6MP 质量优先 - 全局设置:主题/行为/数据/工具 4 分类 + 跨设备同步 - 文件上传:图标/书签/通用(容器持久化 + 跨域 URL 拼接) - 同步:基于变更日志的设备间数据同步 - 跨域部署:前后端分离 + runtime config.json 无需重新编译 # 进度记录 - 已完成 P0~P52 共 53 个开发节点(详细见 说明文档.md) - 当前版本:v1.0 部署就绪 # 部署文档 - README.md:项目说明 + 快速开始 - 说明文档.md:完整开发进度(中文) - docs/DEPLOY.md:1Panel 部署手册(Docker 模式)
This commit is contained in:
@@ -0,0 +1,597 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" class="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>浏览器首页</title>
|
||||
<style id="theme-vars">
|
||||
/* ===== Browser Homepage - Brand CSS ===== */
|
||||
/* Dark theme with glassmorphism, inspired by gaming browser start pages */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
/* ---- Brand Colors ---- */
|
||||
--color-bg-primary: #0d0d12;
|
||||
--color-bg-secondary: #16161e;
|
||||
--color-bg-tertiary: #1e1e2a;
|
||||
--color-bg-card: rgba(30, 30, 42, 0.65);
|
||||
--color-bg-card-hover: rgba(40, 40, 56, 0.75);
|
||||
--color-bg-sidebar: rgba(13, 13, 18, 0.85);
|
||||
--color-bg-search: rgba(22, 22, 30, 0.8);
|
||||
--color-bg-input: rgba(30, 30, 42, 0.5);
|
||||
--color-bg-overlay: rgba(0, 0, 0, 0.5);
|
||||
|
||||
/* Text */
|
||||
--color-text-primary: #e8e8f0;
|
||||
--color-text-secondary: #9494a8;
|
||||
--color-text-muted: #5e5e72;
|
||||
--color-text-inverse: #0d0d12;
|
||||
|
||||
/* Brand accent */
|
||||
--color-brand: #6c5ce7;
|
||||
--color-brand-light: #a29bfe;
|
||||
--color-brand-hover: #7d6ff0;
|
||||
|
||||
/* Border */
|
||||
--color-border: rgba(255, 255, 255, 0.06);
|
||||
--color-border-hover: rgba(255, 255, 255, 0.12);
|
||||
--color-border-active: rgba(108, 92, 231, 0.5);
|
||||
|
||||
/* State colors */
|
||||
--state-success: #00b894;
|
||||
--state-warning: #fdcb6e;
|
||||
--state-error: #e17055;
|
||||
--state-info: #74b9ff;
|
||||
|
||||
/* ---- Typography ---- */
|
||||
--font-sans: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-heading: 'Inter', 'Noto Sans SC', sans-serif;
|
||||
|
||||
--text-xs: 11px;
|
||||
--text-sm: 13px;
|
||||
--text-base: 14px;
|
||||
--text-lg: 16px;
|
||||
--text-xl: 20px;
|
||||
--text-2xl: 24px;
|
||||
--text-3xl: 32px;
|
||||
|
||||
--leading-tight: 1.25;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.625;
|
||||
|
||||
--tracking-tight: -0.01em;
|
||||
--tracking-normal: 0em;
|
||||
|
||||
/* ---- Spacing ---- */
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
|
||||
/* ---- Radius ---- */
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* ---- Shadows ---- */
|
||||
--shadow-card: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
--shadow-float: 0 8px 32px rgba(0, 0, 0, 0.25);
|
||||
--shadow-dropdown: 0 12px 48px rgba(0, 0, 0, 0.35);
|
||||
|
||||
/* ---- Glassmorphism ---- */
|
||||
--glass-bg: rgba(30, 30, 42, 0.65);
|
||||
--glass-blur: 12px;
|
||||
--glass-border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
|
||||
/* ---- Transitions ---- */
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-normal: 250ms ease;
|
||||
--transition-slow: 400ms ease;
|
||||
|
||||
/* ---- Sidebar ---- */
|
||||
--sidebar-width: 240px;
|
||||
--sidebar-collapsed-width: 64px;
|
||||
|
||||
/* ---- Z-index ---- */
|
||||
--z-sidebar: 100;
|
||||
--z-dropdown: 200;
|
||||
--z-modal: 300;
|
||||
--z-tooltip: 400;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.1/dist/index.global.js"></script>
|
||||
<script src="https://unpkg.com/lucide@1.8.0/dist/umd/lucide.min.js"></script>
|
||||
<style type="text/tailwindcss">
|
||||
@theme inline {
|
||||
--color-border: var(--color-border);
|
||||
}
|
||||
@layer base {
|
||||
body { background: var(--color-bg-primary); color: var(--color-text-primary); }
|
||||
td, th { @apply break-words; word-break: break-all; word-break: auto-phrase; }
|
||||
th { @apply whitespace-nowrap; }
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.no-scrollbar::-webkit-scrollbar { display: none; }
|
||||
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
[data-icon] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
-webkit-mask-size: contain;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
mask-position: center;
|
||||
background-color: currentColor;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen font-sans antialiased">
|
||||
<main class="relative max-w-[375px] mx-auto min-h-screen" style="font-family: var(--font-sans);">
|
||||
|
||||
<!-- ===== TOP BAR (single row: hamburger | search | gear) ===== -->
|
||||
<header class="sticky top-0 z-50 flex items-center gap-2 px-4 h-14"
|
||||
style="background: var(--color-bg-secondary); border-bottom: var(--color-border);">
|
||||
|
||||
<!-- Hamburger menu button (44x44) -->
|
||||
<button id="drawer-toggle" type="button"
|
||||
class="inline-flex items-center justify-center w-11 h-11 shrink-0 rounded-lg transition-colors duration-150"
|
||||
style="color: var(--color-text-primary);"
|
||||
aria-label="打开导航菜单"
|
||||
data-dom-id="hamburger-menu">
|
||||
<i data-lucide="menu" class="w-5 h-5"></i>
|
||||
</button>
|
||||
|
||||
<!-- Search bar (engine selector + input) -->
|
||||
<div class="flex items-center gap-2 flex-1 min-w-0 rounded-xl px-2.5 h-11"
|
||||
style="background: var(--color-bg-search); border: var(--glass-border); backdrop-filter: blur(var(--glass-blur));">
|
||||
|
||||
<!-- Search engine selector -->
|
||||
<button id="engine-selector" type="button"
|
||||
class="inline-flex items-center justify-center shrink-0 h-7 px-2 rounded-md text-xs font-medium whitespace-nowrap"
|
||||
style="background: var(--color-bg-tertiary); color: var(--color-brand); border: 1px solid var(--color-border-active);"
|
||||
aria-label="选择搜索引擎"
|
||||
data-dom-id="engine-selector">
|
||||
<i data-lucide="search" class="w-3 h-3 mr-1"></i>
|
||||
<span>Google</span>
|
||||
<i data-lucide="chevron-down" class="w-3 h-3 ml-1"></i>
|
||||
</button>
|
||||
|
||||
<!-- Search input -->
|
||||
<input type="text" placeholder="搜索或输入网址"
|
||||
class="flex-1 min-w-0 bg-transparent outline-none text-sm"
|
||||
style="color: var(--color-text-primary);"
|
||||
aria-label="搜索输入框"
|
||||
data-dom-id="search-input" />
|
||||
</div>
|
||||
|
||||
<!-- Settings gear button (44x44) — replaces top-bar avatar -->
|
||||
<button id="settings-gear-toggle" type="button"
|
||||
class="inline-flex items-center justify-center w-11 h-11 shrink-0 rounded-lg transition-colors duration-150"
|
||||
style="color: var(--color-text-primary);"
|
||||
aria-label="设置"
|
||||
data-dom-id="settings-gear">
|
||||
<i data-lucide="settings" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- ===== CATEGORY TABS ===== -->
|
||||
<nav class="px-4 pt-3 pb-3" aria-label="分类导航">
|
||||
<div class="flex gap-2 overflow-x-auto no-scrollbar pb-1">
|
||||
<button class="shrink-0 inline-flex items-center justify-center h-8 px-4 rounded-lg text-xs font-semibold whitespace-nowrap"
|
||||
style="background: var(--color-brand); color: var(--color-text-inverse);"
|
||||
data-tab-key="all" data-active="true"
|
||||
data-dom-id="tab-all">
|
||||
全部
|
||||
</button>
|
||||
<button class="shrink-0 inline-flex items-center justify-center h-8 px-4 rounded-lg text-xs font-medium whitespace-nowrap"
|
||||
style="background: var(--color-bg-tertiary); color: var(--color-text-secondary); border: 1px solid var(--color-border);"
|
||||
data-tab-key="tools"
|
||||
data-dom-id="tab-tools">
|
||||
常用工具
|
||||
</button>
|
||||
<button class="shrink-0 inline-flex items-center justify-center h-8 px-4 rounded-lg text-xs font-medium whitespace-nowrap"
|
||||
style="background: var(--color-bg-tertiary); color: var(--color-text-secondary); border: 1px solid var(--color-border);"
|
||||
data-tab-key="shopping"
|
||||
data-dom-id="tab-shopping">
|
||||
购物
|
||||
</button>
|
||||
<button class="shrink-0 inline-flex items-center justify-center h-8 px-4 rounded-lg text-xs font-medium whitespace-nowrap"
|
||||
style="background: var(--color-bg-tertiary); color: var(--color-text-secondary); border: 1px solid var(--color-border);"
|
||||
data-tab-key="video"
|
||||
data-dom-id="tab-video">
|
||||
视频
|
||||
</button>
|
||||
<button class="shrink-0 inline-flex items-center justify-center h-8 px-4 rounded-lg text-xs font-medium whitespace-nowrap"
|
||||
style="background: var(--color-bg-tertiary); color: var(--color-text-secondary); border: 1px solid var(--color-border);"
|
||||
data-tab-key="news"
|
||||
data-dom-id="tab-news">
|
||||
资讯
|
||||
</button>
|
||||
<button class="shrink-0 inline-flex items-center justify-center h-8 px-4 rounded-lg text-xs font-medium whitespace-nowrap"
|
||||
style="background: var(--color-bg-tertiary); color: var(--color-text-secondary); border: 1px solid var(--color-border);"
|
||||
data-tab-key="social"
|
||||
data-dom-id="tab-social">
|
||||
社交
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- ===== LINK CARD LIST ===== -->
|
||||
<section class="px-4 pb-24 space-y-2" aria-label="链接列表">
|
||||
<!-- ChatGPT -->
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-3 rounded-xl transition-colors duration-150"
|
||||
style="background: var(--color-bg-card); border: var(--glass-border); backdrop-filter: blur(var(--glass-blur));"
|
||||
data-dom-id="link-chatgpt">
|
||||
<div class="w-10 h-10 rounded-lg shrink-0 flex items-center justify-center"
|
||||
style="background: rgba(16, 163, 127, 0.15);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: #10a37f;">
|
||||
<path d="M12 2a10 10 0 0 1 0 20 10 10 0 0 1 0-20z"/>
|
||||
<path d="M7 12h10M12 7v10"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-semibold truncate" style="color: var(--color-text-primary);">ChatGPT</p>
|
||||
<p class="text-xs truncate mt-0.5" style="color: var(--color-text-secondary);">AI 对话助手</p>
|
||||
</div>
|
||||
<i data-lucide="external-link" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
</a>
|
||||
|
||||
<!-- GitHub -->
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-3 rounded-xl transition-colors duration-150"
|
||||
style="background: var(--color-bg-card); border: var(--glass-border); backdrop-filter: blur(var(--glass-blur));"
|
||||
data-dom-id="link-github">
|
||||
<div class="w-10 h-10 rounded-lg shrink-0 flex items-center justify-center"
|
||||
style="background: rgba(255, 255, 255, 0.08);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="color: var(--color-text-primary);">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-semibold truncate" style="color: var(--color-text-primary);">GitHub</p>
|
||||
<p class="text-xs truncate mt-0.5" style="color: var(--color-text-secondary);">代码托管平台</p>
|
||||
</div>
|
||||
<i data-lucide="external-link" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
</a>
|
||||
|
||||
<!-- Stack Overflow -->
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-3 rounded-xl transition-colors duration-150"
|
||||
style="background: var(--color-bg-card); border: var(--glass-border); backdrop-filter: blur(var(--glass-blur));"
|
||||
data-dom-id="link-stackoverflow">
|
||||
<div class="w-10 h-10 rounded-lg shrink-0 flex items-center justify-center"
|
||||
style="background: rgba(244, 128, 36, 0.15);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: #f48024;">
|
||||
<path d="M4 17l6-6 4 4 6-8"/>
|
||||
<path d="M14 7h6v6"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-semibold truncate" style="color: var(--color-text-primary);">Stack Overflow</p>
|
||||
<p class="text-xs truncate mt-0.5" style="color: var(--color-text-secondary);">开发者问答社区</p>
|
||||
</div>
|
||||
<i data-lucide="external-link" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
</a>
|
||||
|
||||
<!-- MDN -->
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-3 rounded-xl transition-colors duration-150"
|
||||
style="background: var(--color-bg-card); border: var(--glass-border); backdrop-filter: blur(var(--glass-blur));"
|
||||
data-dom-id="link-mdn">
|
||||
<div class="w-10 h-10 rounded-lg shrink-0 flex items-center justify-center"
|
||||
style="background: rgba(83, 40, 209, 0.15);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: #5328d1;">
|
||||
<path d="M4 4h6v6H4z"/>
|
||||
<path d="M14 4h6v6h-6z"/>
|
||||
<path d="M4 14h6v6H4z"/>
|
||||
<path d="M14 14h6v6h-6z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-semibold truncate" style="color: var(--color-text-primary);">MDN Web Docs</p>
|
||||
<p class="text-xs truncate mt-0.5" style="color: var(--color-text-secondary);">Web 技术文档</p>
|
||||
</div>
|
||||
<i data-lucide="external-link" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
</a>
|
||||
|
||||
<!-- VS Code -->
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-3 rounded-xl transition-colors duration-150"
|
||||
style="background: var(--color-bg-card); border: var(--glass-border); backdrop-filter: blur(var(--glass-blur));"
|
||||
data-dom-id="link-vscode">
|
||||
<div class="w-10 h-10 rounded-lg shrink-0 flex items-center justify-center"
|
||||
style="background: rgba(0, 120, 215, 0.15);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color: #0078d7;">
|
||||
<path d="M17.5 2.5l-10 8 4 2 6-10z"/>
|
||||
<path d="M17.5 21.5l-10-8 4-2 6 10z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-semibold truncate" style="color: var(--color-text-primary);">VS Code</p>
|
||||
<p class="text-xs truncate mt-0.5" style="color: var(--color-text-secondary);">代码编辑器</p>
|
||||
</div>
|
||||
<i data-lucide="external-link" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
</a>
|
||||
|
||||
<!-- Notion -->
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-3 rounded-xl transition-colors duration-150"
|
||||
style="background: var(--color-bg-card); border: var(--glass-border); backdrop-filter: blur(var(--glass-blur));"
|
||||
data-dom-id="link-notion">
|
||||
<div class="w-10 h-10 rounded-lg shrink-0 flex items-center justify-center"
|
||||
style="background: rgba(255, 255, 255, 0.08);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style="color: var(--color-text-primary);">
|
||||
<path d="M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 2.685c-.42-.326-.98-.7-2.055-.607L3.39 3.553c-.466.046-.56.28-.374.466zm.793 3.26v13.917c0 .747.373 1.027 1.214.98l14.523-.84c.842-.046.935-.56.935-1.167V6.354c0-.606-.233-.933-.747-.886l-15.178.886c-.56.047-.747.327-.747.933zm14.337.42c.093.42 0 .84-.42.886l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.747 0-.934-.234-1.495-.934l-4.573-7.186v6.953l1.448.327s0 .84-1.214.84l-3.356.187c-.093-.187 0-.653.327-.746l.84-.233V9.854L7.822 9.9c-.093-.42.14-1.026.793-1.073l3.593-.233 4.76 7.28v-6.44l-1.215-.14c-.093-.514.28-.886.747-.933zM2.61.947l10.873-.654c1.355-.093 1.682-.046 2.52.56L18.79 2.87c.56.374.747.47.747.887v16.203c0 .98-.374 1.587-1.682 1.68l-14.946.84c-.98.047-1.448-.093-1.962-.747L.46 19.08C-.094 18.38 0 17.82 0 17.213V2.407c0-.84.374-1.4 1.215-1.493z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-semibold truncate" style="color: var(--color-text-primary);">Notion</p>
|
||||
<p class="text-xs truncate mt-0.5" style="color: var(--color-text-secondary);">协作笔记工具</p>
|
||||
</div>
|
||||
<i data-lucide="external-link" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<!-- ===== FAB BUTTON ===== -->
|
||||
<button class="fixed bottom-6 right-6 w-12 h-12 rounded-full flex items-center justify-center z-40 shadow-lg transition-transform duration-150 active:scale-95"
|
||||
style="background: var(--color-brand); color: var(--color-text-inverse); box-shadow: var(--shadow-float);"
|
||||
aria-label="添加链接"
|
||||
data-dom-id="fab-add-link">
|
||||
<i data-lucide="plus" class="w-5 h-5"></i>
|
||||
</button>
|
||||
|
||||
<!-- ===== NAVIGATION DRAWER OVERLAY ===== -->
|
||||
<div id="drawer-backdrop" class="fixed inset-0 z-50 hidden"
|
||||
style="background: var(--color-bg-overlay); backdrop-filter: blur(2px);"
|
||||
data-dom-id="drawer-backdrop"></div>
|
||||
|
||||
<!-- ===== NAVIGATION DRAWER (closed by default — avatar/profile moved here) ===== -->
|
||||
<aside id="nav-drawer" class="fixed top-0 left-0 bottom-0 z-50 w-[280px] flex flex-col -translate-x-full transition-transform duration-250"
|
||||
style="background: var(--color-bg-secondary); border-right: var(--glass-border); transform: translateX(-100%);"
|
||||
aria-label="导航菜单"
|
||||
data-dom-id="nav-drawer">
|
||||
<!-- User profile (avatar + name + email) -->
|
||||
<div class="flex items-center gap-3 px-4 h-16 shrink-0"
|
||||
style="border-bottom: var(--color-border);">
|
||||
<div class="w-10 h-10 rounded-full flex items-center justify-center shrink-0"
|
||||
style="background: var(--color-brand); color: var(--color-text-inverse);">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-semibold truncate" style="color: var(--color-text-primary);">用户</p>
|
||||
<p class="text-xs truncate" style="color: var(--color-text-muted);">浏览主页</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Two-level category navigation -->
|
||||
<nav class="flex-1 overflow-y-auto px-2 py-3" aria-label="分类导航">
|
||||
<!-- 常用工具 -->
|
||||
<div class="mb-1">
|
||||
<button class="drawer-cat-toggle flex items-center gap-2 w-full px-3 py-2.5 rounded-lg text-sm font-medium transition-colors duration-150"
|
||||
style="color: var(--color-text-primary);"
|
||||
data-dom-id="drawer-cat-tools">
|
||||
<i data-lucide="wrench" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
<span class="flex-1 text-left">常用工具</span>
|
||||
<i data-lucide="chevron-down" class="w-4 h-4 shrink-0 transition-transform duration-150" style="color: var(--color-text-muted);"></i>
|
||||
</button>
|
||||
<div class="drawer-subitems ml-9 hidden">
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">搜索引擎</a>
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">AI 工具</a>
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">开发工具</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 购物 -->
|
||||
<div class="mb-1">
|
||||
<button class="drawer-cat-toggle flex items-center gap-2 w-full px-3 py-2.5 rounded-lg text-sm font-medium transition-colors duration-150"
|
||||
style="color: var(--color-text-primary);"
|
||||
data-dom-id="drawer-cat-shopping">
|
||||
<i data-lucide="shopping-bag" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
<span class="flex-1 text-left">购物</span>
|
||||
<i data-lucide="chevron-down" class="w-4 h-4 shrink-0 transition-transform duration-150" style="color: var(--color-text-muted);"></i>
|
||||
</button>
|
||||
<div class="drawer-subitems ml-9 hidden">
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">电商</a>
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">数码</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频 -->
|
||||
<div class="mb-1">
|
||||
<button class="drawer-cat-toggle flex items-center gap-2 w-full px-3 py-2.5 rounded-lg text-sm font-medium transition-colors duration-150"
|
||||
style="color: var(--color-text-primary);"
|
||||
data-dom-id="drawer-cat-video">
|
||||
<i data-lucide="play-circle" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
<span class="flex-1 text-left">视频</span>
|
||||
<i data-lucide="chevron-down" class="w-4 h-4 shrink-0 transition-transform duration-150" style="color: var(--color-text-muted);"></i>
|
||||
</button>
|
||||
<div class="drawer-subitems ml-9 hidden">
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">影视</a>
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">直播</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 资讯 -->
|
||||
<div class="mb-1">
|
||||
<button class="drawer-cat-toggle flex items-center gap-2 w-full px-3 py-2.5 rounded-lg text-sm font-medium transition-colors duration-150"
|
||||
style="color: var(--color-text-primary);"
|
||||
data-dom-id="drawer-cat-news">
|
||||
<i data-lucide="newspaper" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
<span class="flex-1 text-left">资讯</span>
|
||||
<i data-lucide="chevron-down" class="w-4 h-4 shrink-0 transition-transform duration-150" style="color: var(--color-text-muted);"></i>
|
||||
</button>
|
||||
<div class="drawer-subitems ml-9 hidden">
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">科技</a>
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">新闻</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 社交 -->
|
||||
<div class="mb-1">
|
||||
<button class="drawer-cat-toggle flex items-center gap-2 w-full px-3 py-2.5 rounded-lg text-sm font-medium transition-colors duration-150"
|
||||
style="color: var(--color-text-primary);"
|
||||
data-dom-id="drawer-cat-social">
|
||||
<i data-lucide="message-circle" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
<span class="flex-1 text-left">社交</span>
|
||||
<i data-lucide="chevron-down" class="w-4 h-4 shrink-0 transition-transform duration-150" style="color: var(--color-text-muted);"></i>
|
||||
</button>
|
||||
<div class="drawer-subitems ml-9 hidden">
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">即时通讯</a>
|
||||
<a href="#" class="block px-3 py-2 rounded-md text-xs transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);">社区论坛</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Settings link -->
|
||||
<div class="shrink-0 px-2 py-3" style="border-top: var(--color-border);">
|
||||
<a href="#" class="flex items-center gap-2 px-3 py-2.5 rounded-lg text-sm transition-colors duration-150"
|
||||
style="color: var(--color-text-secondary);"
|
||||
data-dom-id="drawer-settings">
|
||||
<i data-lucide="settings" class="w-4 h-4 shrink-0" style="color: var(--color-text-muted);"></i>
|
||||
<span>设置</span>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
</main>
|
||||
<style>
|
||||
/* Drawer slide-in animation */
|
||||
#nav-drawer.open {
|
||||
transform: translateX(0) !important;
|
||||
}
|
||||
/* Active tab style for category tabs */
|
||||
[data-tab-key][data-active="true"] {
|
||||
background: var(--color-brand) !important;
|
||||
color: var(--color-text-inverse) !important;
|
||||
border-color: var(--color-brand) !important;
|
||||
}
|
||||
[data-tab-key]:not([data-active="true"]):hover {
|
||||
background: var(--color-bg-card-hover) !important;
|
||||
}
|
||||
/* Link card hover */
|
||||
a[style*="background: var(--color-bg-card)"]:hover {
|
||||
background: var(--color-bg-card-hover) !important;
|
||||
}
|
||||
/* Drawer category toggle hover */
|
||||
.drawer-cat-toggle:hover {
|
||||
background: var(--color-bg-tertiary);
|
||||
}
|
||||
.drawer-cat-toggle[aria-expanded="true"] > svg:last-child {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
/* Drawer sub-items visible */
|
||||
.drawer-subitems:not(.hidden) {
|
||||
display: block;
|
||||
}
|
||||
/* FAB hover */
|
||||
button[style*="background: var(--color-brand)"][data-dom-id="fab-add-link"]:hover {
|
||||
background: var(--color-brand-hover) !important;
|
||||
}
|
||||
/* Top-bar icon hover */
|
||||
#drawer-toggle:hover,
|
||||
#settings-gear-toggle:hover {
|
||||
background: var(--color-bg-tertiary) !important;
|
||||
}
|
||||
/* Reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#nav-drawer,
|
||||
.drawer-cat-toggle,
|
||||
.drawer-cat-toggle > svg:last-child,
|
||||
a[style*="background: var(--color-bg-card)"],
|
||||
button[style*="background: var(--color-brand)"][data-dom-id="fab-add-link"],
|
||||
#drawer-toggle,
|
||||
#settings-gear-toggle {
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
/* Drawer backdrop animation */
|
||||
#drawer-backdrop.show {
|
||||
opacity: 1 !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
#drawer-backdrop {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--transition-normal);
|
||||
}
|
||||
#nav-drawer {
|
||||
transition: transform var(--transition-normal);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
/* Drawer open/close (hamburger toggles the drawer that holds avatar/profile) */
|
||||
const drawer = document.getElementById('nav-drawer');
|
||||
const backdrop = document.getElementById('drawer-backdrop');
|
||||
const toggle = document.getElementById('drawer-toggle');
|
||||
const gearToggle = document.getElementById('settings-gear-toggle');
|
||||
function openDrawer() {
|
||||
drawer.classList.add('open');
|
||||
backdrop.classList.remove('hidden');
|
||||
backdrop.classList.add('show');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeDrawer() {
|
||||
drawer.classList.remove('open');
|
||||
backdrop.classList.remove('show');
|
||||
document.body.style.overflow = '';
|
||||
setTimeout(function() { backdrop.classList.add('hidden'); }, 250);
|
||||
}
|
||||
toggle.addEventListener('click', openDrawer);
|
||||
backdrop.addEventListener('click', closeDrawer);
|
||||
/* Gear button also opens the drawer (settings lives at the bottom of the drawer) */
|
||||
if (gearToggle) {
|
||||
gearToggle.addEventListener('click', openDrawer);
|
||||
}
|
||||
/* Drawer category toggle */
|
||||
document.querySelectorAll('.drawer-cat-toggle').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var sub = this.nextElementSibling;
|
||||
var expanded = this.getAttribute('aria-expanded') === 'true';
|
||||
this.setAttribute('aria-expanded', String(!expanded));
|
||||
if (expanded) {
|
||||
sub.classList.add('hidden');
|
||||
} else {
|
||||
sub.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
/* Category tab switching */
|
||||
document.querySelectorAll('[data-tab-key]').forEach(function(tab) {
|
||||
tab.addEventListener('click', function() {
|
||||
document.querySelectorAll('[data-tab-key]').forEach(function(t) {
|
||||
t.removeAttribute('data-active');
|
||||
t.style.background = 'var(--color-bg-tertiary)';
|
||||
t.style.color = 'var(--color-text-secondary)';
|
||||
t.style.border = '1px solid var(--color-border)';
|
||||
});
|
||||
this.setAttribute('data-active', 'true');
|
||||
this.style.background = 'var(--color-brand)';
|
||||
this.style.color = 'var(--color-text-inverse)';
|
||||
this.style.border = '1px solid var(--color-brand)';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>lucide.createIcons();</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user