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>

  

posted @ 2025-06-24 10:54  华腾智算  阅读(24)  评论(0)    收藏  举报
https://damo.alibaba.com/ https://tianchi.aliyun.com/course?spm=5176.21206777.J_3941670930.5.87dc17c9BZNvLL