vue3复杂二维布局
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue3 复杂二维布局仪表盘</title> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } :root { --primary: #4361ee; --primary-light: #4895ef; --secondary: #3f37c9; --accent: #f72585; --success: #4cc9f0; --warning: #f9c74f; --danger: #f94144; --text-primary: #2b2d42; --text-secondary: #8d99ae; --bg-main: #f8f9fa; --bg-card: #ffffff; --bg-header: #ffffff; --border: #e9ecef; --shadow: 0 4px 6px rgba(0, 0, 0, 0.05); --transition: all 0.3s ease; } .dark-theme { --text-primary: #edf2f4; --text-secondary: #8d99ae; --bg-main: #121212; --bg-card: #1e1e1e; --bg-header: #1e1e1e; --border: #333333; --shadow: 0 4px 6px rgba(0, 0, 0, 0.2); } body { font-family: 'Segoe UI', system-ui, sans-serif; background-color: var(--bg-main); color: var(--text-primary); transition: var(--transition); line-height: 1.6; } #app { min-height: 100vh; display: grid; grid-template-columns: 240px 1fr; grid-template-rows: 60px 1fr 40px; grid-template-areas: "sidebar header" "sidebar main" "sidebar footer"; transition: var(--transition); } /* 头部样式 */ .header { grid-area: header; background: var(--bg-header); box-shadow: var(--shadow); display: flex; align-items: center; justify-content: space-between; padding: 0 20px; z-index: 100; border-bottom: 1px solid var(--border); } .logo { display: flex; align-items: center; gap: 12px; font-weight: 700; font-size: 1.2rem; } .logo i { color: var(--primary); font-size: 1.8rem; } .header-actions { display: flex; align-items: center; gap: 20px; } .theme-toggle { background: none; border: none; cursor: pointer; font-size: 1.2rem; color: var(--text-secondary); } .user-profile { display: flex; align-items: center; gap: 10px; } .avatar { width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, var(--primary), var(--secondary)); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; } /* 侧边栏样式 */ .sidebar { grid-area: sidebar; background: var(--bg-card); border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; transition: var(--transition); } .sidebar-header { padding: 20px; border-bottom: 1px solid var(--border); } .nav-title { font-size: 0.9rem; text-transform: uppercase; color: var(--text-secondary); padding: 15px 20px 5px; } .nav-menu { flex: 1; padding: 10px 0; overflow-y: auto; } .nav-item { display: flex; align-items: center; padding: 12px 20px; color: var(--text-secondary); text-decoration: none; transition: var(--transition); cursor: pointer; } .nav-item:hover, .nav-item.active { background: rgba(67, 97, 238, 0.1); color: var(--primary); } .nav-item i { width: 24px; margin-right: 12px; font-size: 1.1rem; } /* 主内容区域 - 复杂的二维布局 */ .main-content { grid-area: main; padding: 20px; display: grid; gap: 20px; grid-template-columns: repeat(4, 1fr); grid-auto-rows: minmax(100px, auto); grid-template-areas: "stats stats stats stats" "chart chart chart activity" "chart chart chart activity" "projects projects tasks tasks" "projects projects tasks tasks"; } /* 响应式布局调整 */ @media (max-width: 1200px) { .main-content { grid-template-areas: "stats stats stats stats" "chart chart chart chart" "activity activity activity activity" "projects projects projects projects" "tasks tasks tasks tasks"; } } @media (max-width: 768px) { #app { grid-template-columns: 1fr; grid-template-rows: 60px 1fr 40px; grid-template-areas: "header" "main" "footer"; } .sidebar { position: fixed; left: -240px; top: 60px; height: calc(100vh - 100px); z-index: 1000; box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); } .sidebar.active { left: 0; } } /* 卡片通用样式 */ .card { background: var(--bg-card); border-radius: 12px; box-shadow: var(--shadow); overflow: hidden; transition: var(--transition); border: 1px solid var(--border); } .card:hover { transform: translateY(-5px); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); } .card-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; } .card-title { font-size: 1.1rem; font-weight: 600; } .card-body { padding: 20px; } /* 特定卡片区域 */ .stats-container { grid-area: stats; display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; } .stat-card { display: flex; flex-direction: column; padding: 20px; position: relative; overflow: hidden; } .stat-card::before { content: ""; position: absolute; top: 0; left: 0; width: 4px; height: 100%; } .stat-card:nth-child(1)::before { background: var(--primary); } .stat-card:nth-child(2)::before { background: var(--success); } .stat-card:nth-child(3)::before { background: var(--warning); } .stat-card:nth-child(4)::before { background: var(--accent); } .stat-value { font-size: 1.8rem; font-weight: 700; margin: 10px 0; } .stat-label { color: var(--text-secondary); font-size: 0.9rem; } .stat-icon { position: absolute; top: 20px; right: 20px; font-size: 2rem; opacity: 0.2; } .chart-container { grid-area: chart; } .activity-container { grid-area: activity; } .projects-container { grid-area: projects; } .tasks-container { grid-area: tasks; } /* 图表容器 */ .chart-wrapper { height: 300px; position: relative; } /* 活动列表 */ .activity-list { display: flex; flex-direction: column; gap: 15px; } .activity-item { display: flex; gap: 15px; padding: 10px 0; } .activity-item:not(:last-child) { border-bottom: 1px solid var(--border); } .activity-icon { width: 40px; height: 40px; border-radius: 10px; background: rgba(67, 97, 238, 0.1); display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--primary); } .activity-content { flex: 1; } .activity-title { font-weight: 500; margin-bottom: 5px; } .activity-time { font-size: 0.85rem; color: var(--text-secondary); } /* 项目表格 */ .projects-table { width: 100%; border-collapse: collapse; } .projects-table th { text-align: left; padding: 12px 0; color: var(--text-secondary); font-weight: 500; border-bottom: 1px solid var(--border); } .projects-table td { padding: 12px 0; border-bottom: 1px solid var(--border); } .project-name { font-weight: 500; } .project-team { display: flex; } .team-member { width: 28px; height: 28px; border-radius: 50%; background: var(--primary-light); display: flex; align-items: center; justify-content: center; color: white; font-size: 0.7rem; margin-right: -8px; border: 2px solid var(--bg-card); } .status-badge { padding: 4px 10px; border-radius: 20px; font-size: 0.8rem; } .status-active { background: rgba(76, 201, 240, 0.15); color: var(--success); } .status-pending { background: rgba(249, 199, 79, 0.15); color: var(--warning); } .status-completed { background: rgba(67, 97, 238, 0.15); color: var(--primary); } /* 任务列表 */ .task-item { display: flex; align-items: center; padding: 12px 0; border-bottom: 1px solid var(--border); } .task-item:last-child { border-bottom: none; } .task-checkbox { margin-right: 15px; } .task-content { flex: 1; } .task-title { margin-bottom: 5px; } .task-progress { height: 6px; background: var(--border); border-radius: 3px; margin-top: 8px; overflow: hidden; } .progress-bar { height: 100%; border-radius: 3px; } /* 页脚样式 */ .footer { grid-area: footer; display: flex; align-items: center; justify-content: center; padding: 10px; background: var(--bg-header); border-top: 1px solid var(--border); color: var(--text-secondary); font-size: 0.9rem; } /* 移动端菜单按钮 */ .menu-toggle { display: none; background: none; border: none; font-size: 1.4rem; cursor: pointer; color: var(--text-primary); } @media (max-width: 768px) { .menu-toggle { display: block; } } </style> </head> <body> <div id="app"> <!-- 头部区域 --> <header class="header"> <div class="logo"> <button class="menu-toggle" @click="toggleSidebar"> <i class="fas fa-bars"></i> </button> <i class="fas fa-chart-pie"></i> <span>数据仪表盘</span> </div> <div class="header-actions"> <button class="theme-toggle" @click="toggleTheme"> <i :class="darkMode ? 'fas fa-sun' : 'fas fa-moon'"></i> </button> <div class="user-profile"> <div class="avatar">A</div> <span>管理员</span> </div> </div> </header> <!-- 侧边导航 --> <aside class="sidebar" :class="{ active: sidebarActive }"> <div class="sidebar-header"> <h2>导航菜单</h2> </div> <div class="nav-menu"> <div class="nav-title">主要功能</div> <div v-for="item in menuItems" :key="item.id" class="nav-item" :class="{ active: activeMenu === item.id }" @click="activeMenu = item.id" > <i :class="item.icon"></i> <span>{{ item.title }}</span> </div> </div> </aside> <!-- 主内容区域 - 复杂二维布局 --> <main class="main-content"> <!-- 统计卡片区域 --> <div class="stats-container"> <div class="card stat-card"> <div class="stat-label">总访问量</div> <div class="stat-value">24,568</div> <i class="fas fa-eye stat-icon"></i> </div> <div class="card stat-card"> <div class="stat-label">用户注册</div> <div class="stat-value">1,254</div> <i class="fas fa-users stat-icon"></i> </div> <div class="card stat-card"> <div class="stat-label">总收入</div> <div class="stat-value">¥56,780</div> <i class="fas fa-yen-sign stat-icon"></i> </div> <div class="card stat-card"> <div class="stat-label">任务完成率</div> <div class="stat-value">78%</div> <i class="fas fa-tasks stat-icon"></i> </div> </div> <!-- 图表区域 --> <div class="card chart-container"> <div class="card-header"> <h3 class="card-title">月度数据分析</h3> <div class="card-actions"> <select v-model="selectedChart"> <option value="sales">销售额</option> <option value="users">用户增长</option> <option value="conversion">转化率</option> </select> </div> </div> <div class="card-body"> <div class="chart-wrapper"> <canvas ref="chart"></canvas> </div> </div> </div> <!-- 活动区域 --> <div class="card activity-container"> <div class="card-header"> <h3 class="card-title">最近活动</h3> <button class="btn-icon"> <i class="fas fa-ellipsis-h"></i> </button> </div> <div class="card-body"> <div class="activity-list"> <div class="activity-item" v-for="activity in activities" :key="activity.id"> <div class="activity-icon"> <i :class="activity.icon"></i> </div> <div class="activity-content"> <div class="activity-title">{{ activity.title }}</div> <div class="activity-desc">{{ activity.description }}</div> <div class="activity-time">{{ activity.time }}</div> </div> </div> </div> </div> </div> <!-- 项目区域 --> <div class="card projects-container"> <div class="card-header"> <h3 class="card-title">项目进度</h3> <button class="btn-icon"> <i class="fas fa-plus"></i> </button> </div> <div class="card-body"> <table class="projects-table"> <thead> <tr> <th>项目名称</th> <th>团队</th> <th>截止日期</th> <th>状态</th> <th>进度</th> </tr> </thead> <tbody> <tr v-for="project in projects" :key="project.id"> <td> <div class="project-name">{{ project.name }}</div> <div class="project-client">{{ project.client }}</div> </td> <td> <div class="project-team"> <div class="team-member" v-for="(member, index) in project.team" :key="index"> {{ member.charAt(0) }} </div> </div> </td> <td>{{ project.deadline }}</td> <td> <span class="status-badge" :class="'status-' + project.status"> {{ getStatusText(project.status) }} </span> </td> <td> <div class="progress"> <div class="task-progress"> <div class="progress-bar" :style="{ width: project.progress + '%', 'background-color': getStatusColor(project.status) }"></div> </div> <div class="progress-value">{{ project.progress }}%</div> </div> </td> </tr> </tbody> </table> </div> </div> <!-- 任务区域 --> <div class="card tasks-container"> <div class="card-header"> <h3 class="card-title">任务列表</h3> <button class="btn-icon"> <i class="fas fa-sync"></i> </button> </div> <div class="card-body"> <div class="task-list"> <div class="task-item" v-for="task in tasks" :key="task.id"> <div class="task-checkbox"> <input type="checkbox" v-model="task.completed"> </div> <div class="task-content"> <div class="task-title" :class="{ completed: task.completed }">{{ task.title }}</div> <div class="task-desc">{{ task.description }}</div> <div class="task-progress"> <div class="progress-bar" :style="{ width: task.progress + '%', 'background-color': task.completed ? '#4cc9f0' : '#4361ee' }"></div> </div> </div> <div class="task-date">{{ task.date }}</div> </div> </div> </div> </div> </main> <!-- 页脚 --> <footer class="footer"> <p>© 2023 数据仪表盘 | Vue3 二维布局示例</p> </footer> </div> <script> const { createApp, ref, reactive, onMounted, watch, nextTick } = Vue; createApp({ setup() { // 状态管理 const darkMode = ref(false); const sidebarActive = ref(false); const activeMenu = ref('dashboard'); const selectedChart = ref('sales'); // 图表引用 const chart = ref(null); let chartInstance = null; // 菜单项 const menuItems = ref([ { id: 'dashboard', title: '仪表盘', icon: 'fas fa-tachometer-alt' }, { id: 'analytics', title: '数据分析', icon: 'fas fa-chart-line' }, { id: 'projects', title: '项目管理', icon: 'fas fa-tasks' }, { id: 'users', title: '用户管理', icon: 'fas fa-users' }, { id: 'settings', title: '系统设置', icon: 'fas fa-cog' }, { id: 'messages', title: '消息中心', icon: 'fas fa-envelope' }, { id: 'calendar', title: '日程安排', icon: 'fas fa-calendar' }, { id: 'documents', title: '文档中心', icon: 'fas fa-file-alt' } ]); // 活动数据 const activities = ref([ { id: 1, icon: 'fas fa-user-plus', title: '新用户注册', description: '用户 "张三" 注册了账户', time: '10分钟前' }, { id: 2, icon: 'fas fa-shopping-cart', title: '新订单', description: '订单 #ORD-20230925001 已创建', time: '25分钟前' }, { id: 3, icon: 'fas fa-file-invoice', title: '发票生成', description: '发票 #INV-20230924005 已生成', time: '1小时前' }, { id: 4, icon: 'fas fa-comment', title: '新评论', description: '用户 "李四" 在商品页发表了评论', time: '2小时前' }, { id: 5, icon: 'fas fa-server', title: '系统更新', description: '系统已完成版本更新至 v2.5.0', time: '3小时前' } ]); // 项目数据 const projects = ref([ { id: 1, name: '官网改版', client: 'ABC公司', team: ['张三', '李四', '王五'], deadline: '2023-10-15', status: 'active', progress: 65 }, { id: 2, name: '移动端应用', client: 'XYZ科技', team: ['赵六', '钱七'], deadline: '2023-11-30', status: 'pending', progress: 25 }, { id: 3, name: '数据平台', client: '内部项目', team: ['孙八', '周九', '吴十'], deadline: '2023-09-30', status: 'completed', progress: 100 }, { id: 4, name: '营销活动', client: '市场部', team: ['郑十一', '王十二'], deadline: '2023-10-10', status: 'active', progress: 85 } ]); // 任务数据 const tasks = ref([ { id: 1, title: '完成设计稿', description: '首页及产品页面设计', progress: 75, date: '今天', completed: false }, { id: 2, title: '开发用户模块', description: '用户注册登录功能', progress: 50, date: '明天', completed: false }, { id: 3, title: '编写测试用例', description: '核心功能测试用例', progress: 30, date: '09/28', completed: false }, { id: 4, title: '项目会议', description: '与客户讨论需求变更', progress: 100, date: '已完成', completed: true } ]); // 切换主题 const toggleTheme = () => { darkMode.value = !darkMode.value; document.documentElement.classList.toggle('dark-theme', darkMode.value); }; // 切换侧边栏(移动端) const toggleSidebar = () => { sidebarActive.value = !sidebarActive.value; }; // 获取状态文本 const getStatusText = (status) => { const statusMap = { active: '进行中', pending: '待启动', completed: '已完成' }; return statusMap[status] || status; }; // 获取状态颜色 const getStatusColor = (status) => { const colorMap = { active: '#4cc9f0', pending: '#f9c74f', completed: '#4361ee' }; return colorMap[status] || '#4361ee'; }; // 初始化图表 const initChart = () => { if (chartInstance) { chartInstance.destroy(); } const ctx = chart.value.getContext('2d'); // 根据选择的图表类型显示不同数据 let data, options; if (selectedChart.value === 'sales') { data = { labels: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月'], datasets: [{ label: '销售额 (万元)', data: [65, 59, 80, 81, 56, 55, 40, 75, 92], backgroundColor: 'rgba(67, 97, 238, 0.2)', borderColor: 'rgba(67, 97, 238, 1)', borderWidth: 2, tension: 0.4, fill: true }] }; options = { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } } }; } else if (selectedChart.value === 'users') { data = { labels: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月'], datasets: [{ label: '新增用户', data: [120, 190, 300, 500, 200, 300, 450, 600, 750], backgroundColor: 'rgba(76, 201, 240, 0.2)', borderColor: 'rgba(76, 201, 240, 1)', borderWidth: 2, tension: 0.4, fill: true }] }; options = { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } } }; } else { data = { labels: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月'], datasets: [{ label: '转化率 (%)', data: [2.5, 3.1, 2.8, 4.2, 3.5, 5.0, 4.2, 5.5, 6.8], backgroundColor: 'rgba(247, 37, 133, 0.2)', borderColor: 'rgba(247, 37, 133, 1)', borderWidth: 2, tension: 0.4, fill: true }] }; options = { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return value + '%'; } } } } }; } chartInstance = new Chart(ctx, { type: 'line', data: data, options: options }); }; // 监听图表类型变化 watch(selectedChart, () => { initChart(); }); // 组件挂载后初始化图表 onMounted(() => { nextTick(() => { initChart(); }); }); return { darkMode, sidebarActive, activeMenu, selectedChart, chart, menuItems, activities, projects, tasks, toggleTheme, toggleSidebar, getStatusText, getStatusColor }; } }).mount('#app'); </script> </body> </html>