Vue3 + Tailwind CSS 后台管理系统教程

Vue3 搭配 Tailwind CSS 是构建现代后台管理系统的绝佳组合。Vue3 提供了高效的响应式框架,而 Tailwind CSS 则让样式编写变得快速且灵活。下面我将分步骤教你如何创建一个功能完整的后台管理系统。

第 1 步:创建项目

首先,我们需要使用 Vite 创建一个 Vue3 项目,并安装 Tailwind CSS、路由、 Font Awesome:

html 复制代码
npm init vite@latest admin-system -- --template vue
cd admin-system
npm install
npm install -D tailwindcss@3.4.1 postcss autoprefixer
npx tailwindcss init -p
npm install vue-router@4
npm install font-awesome

第 2 步:配置 Tailwind CSS

创建 tailwind.config.js 文件并配置:

javascript 复制代码
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{vue,js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {
      colors: {
        primary: '#165DFF',
        secondary: '#36CFC9',
        success: '#52C41A',
        warning: '#FAAD14',
        danger: '#F5222D',
        info: '#86909C',
        light: '#F2F3F5',
        dark: '#1D2129',
      },
      fontFamily: {
        inter: ['Inter', 'sans-serif'],
      },
    },
  },
  plugins: [],
}

src/index.css 中引入 Tailwind CSS:

javascript 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
  .content-auto {
    content-visibility: auto;
  }
  .sidebar-item-active {
    @apply bg-primary/10 text-primary border-l-4 border-primary;
  }
}

第 4 步:创建布局组件

html 复制代码
<template>
  <div class="min-h-screen flex flex-col bg-gray-50">
    <!-- 顶部导航栏 -->
    <header class="bg-white shadow-sm">
      <div class="flex items-center justify-between px-4 py-3">
        <div class="flex items-center">
          <button @click="toggleSidebar" class="md:hidden text-gray-500 focus:outline-none">
            <i class="fa fa-bars text-xl"></i>
          </button>
          <div class="ml-4 text-xl font-bold text-primary">管理系统</div>
        </div>
        
        <div class="flex items-center">
          <div class="relative mr-4">
            <input type="text" placeholder="搜索..." class="pl-8 pr-4 py-2 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary/50">
            <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
          </div>
          
          <div class="relative ml-4">
            <button class="relative text-gray-500 focus:outline-none">
              <i class="fa fa-bell text-xl"></i>
              <span class="absolute top-0 right-0 h-4 w-4 bg-red-500 rounded-full flex items-center justify-center text-white text-xs">3</span>
            </button>
          </div>
          
          <div class="relative ml-6">
            <button @click="toggleDropdown" class="flex items-center focus:outline-none">
              <img src="https://picsum.photos/id/1005/40/40" alt="用户头像" class="w-8 h-8 rounded-full object-cover">
              <span class="ml-2 text-sm font-medium">管理员</span>
              <i class="fa fa-angle-down ml-1 text-gray-500"></i>
            </button>
            
            <div v-show="dropdownVisible" class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-10">
              <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
                <i class="fa fa-user mr-2"></i>个人信息
              </a>
              <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
                <i class="fa fa-cog mr-2"></i>设置
              </a>
              <div class="border-t border-gray-100 my-1"></div>
              <a href="#" @click="logout" class="block px-4 py-2 text-sm text-red-600 hover:bg-gray-100">
                <i class="fa fa-sign-out mr-2"></i>退出登录
              </a>
            </div>
          </div>
        </div>
      </div>
    </header>
    
    <div class="flex flex-1 overflow-hidden">
      <!-- 侧边栏导航 -->
      <aside :class="sidebarVisible ? 'translate-x-0' : '-translate-x-full'" class="fixed md:relative z-20 w-64 bg-white shadow-lg h-full transition-transform duration-300 ease-in-out">
        <nav class="py-4">
          <div class="px-4 mb-6">
            <div class="flex items-center">
              <img src="https://picsum.photos/id/1005/40/40" alt="用户头像" class="w-10 h-10 rounded-full object-cover">
              <div class="ml-3">
                <div class="text-sm font-medium text-gray-900">管理员</div>
                <div class="text-xs text-gray-500">系统管理员</div>
              </div>
            </div>
          </div>
          
          <div class="px-2 space-y-1">
            <a href="/dashboard" class="flex items-center px-4 py-3 text-gray-600 rounded-lg sidebar-item-active">
              <i class="fa fa-tachometer mr-3"></i>
              <span>仪表盘</span>
            </a>
            
            <a href="/users" class="flex items-center px-4 py-3 text-gray-600 rounded-lg hover:bg-gray-100 transition-colors duration-200">
              <i class="fa fa-users mr-3"></i>
              <span>用户管理</span>
            </a>
            
            <a href="/products" class="flex items-center px-4 py-3 text-gray-600 rounded-lg hover:bg-gray-100 transition-colors duration-200">
              <i class="fa fa-shopping-bag mr-3"></i>
              <span>产品管理</span>
            </a>
            
            <a href="#" class="flex items-center px-4 py-3 text-gray-600 rounded-lg hover:bg-gray-100 transition-colors duration-200">
              <i class="fa fa-bar-chart mr-3"></i>
              <span>数据分析</span>
            </a>
            
            <a href="#" class="flex items-center px-4 py-3 text-gray-600 rounded-lg hover:bg-gray-100 transition-colors duration-200">
              <i class="fa fa-cog mr-3"></i>
              <span>系统设置</span>
            </a>
          </div>
          
          <div class="px-4 py-4 mt-6 border-t border-gray-100">
            <div class="text-xs font-medium text-gray-500 uppercase tracking-wider">帮助</div>
            <a href="#" class="flex items-center px-4 py-2 mt-2 text-gray-600 rounded-lg hover:bg-gray-100 transition-colors duration-200">
              <i class="fa fa-question-circle mr-3"></i>
              <span>帮助中心</span>
            </a>
            <a href="#" class="flex items-center px-4 py-2 text-gray-600 rounded-lg hover:bg-gray-100 transition-colors duration-200">
              <i class="fa fa-life-ring mr-3"></i>
              <span>联系支持</span>
            </a>
          </div>
        </nav>
      </aside>
      
      <!-- 主内容区 -->
      <main class="flex-1 overflow-y-auto p-6 bg-gray-50">
        <div class="mb-6">
          <h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-gray-900">仪表盘</h1>
          <p class="mt-1 text-gray-500">欢迎使用管理系统</p>
        </div>
        
        <div class="mb-6 flex flex-col md:flex-row md:space-x-4 space-y-4 md:space-y-0">
          <div class="bg-white rounded-xl shadow-sm p-6 flex-1">
            <div class="flex items-center justify-between">
              <div>
                <p class="text-sm font-medium text-gray-500">用户总数</p>
                <h3 class="text-3xl font-bold text-gray-900 mt-1">1,284</h3>
                <p class="text-xs text-green-500 mt-2">
                  <i class="fa fa-arrow-up"></i> 12% 较上月
                </p>
              </div>
              <div class="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center">
                <i class="fa fa-users text-primary text-xl"></i>
              </div>
            </div>
          </div>
          
          <div class="bg-white rounded-xl shadow-sm p-6 flex-1">
            <div class="flex items-center justify-between">
              <div>
                <p class="text-sm font-medium text-gray-500">产品总数</p>
                <h3 class="text-3xl font-bold text-gray-900 mt-1">528</h3>
                <p class="text-xs text-green-500 mt-2">
                  <i class="fa fa-arrow-up"></i> 8% 较上月
                </p>
              </div>
              <div class="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center">
                <i class="fa fa-shopping-bag text-green-600 text-xl"></i>
              </div>
            </div>
          </div>
          
          <div class="bg-white rounded-xl shadow-sm p-6 flex-1">
            <div class="flex items-center justify-between">
              <div>
                <p class="text-sm font-medium text-gray-500">订单总数</p>
                <h3 class="text-3xl font-bold text-gray-900 mt-1">2,451</h3>
                <p class="text-xs text-green-500 mt-2">
                  <i class="fa fa-arrow-up"></i> 16% 较上月
                </p>
              </div>
              <div class="w-12 h-12 rounded-full bg-purple-100 flex items-center justify-center">
                <i class="fa fa-file-text text-purple-600 text-xl"></i>
              </div>
            </div>
          </div>
          
          <div class="bg-white rounded-xl shadow-sm p-6 flex-1">
            <div class="flex items-center justify-between">
              <div>
                <p class="text-sm font-medium text-gray-500">销售额</p>
                <h3 class="text-3xl font-bold text-gray-900 mt-1">¥156,284</h3>
                <p class="text-xs text-green-500 mt-2">
                  <i class="fa fa-arrow-up"></i> 23% 较上月
                </p>
              </div>
              <div class="w-12 h-12 rounded-full bg-yellow-100 flex items-center justify-center">
                <i class="fa fa-line-chart text-yellow-600 text-xl"></i>
              </div>
            </div>
          </div>
        </div>
        
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
          <div class="lg:col-span-2 bg-white rounded-xl shadow-sm p-6">
            <div class="flex items-center justify-between mb-6">
              <h2 class="text-lg font-semibold text-gray-900">销售趋势</h2>
              <div class="flex space-x-2">
                <button class="px-3 py-1 text-xs rounded-full bg-gray-100 text-gray-600">日</button>
                <button class="px-3 py-1 text-xs rounded-full bg-primary text-white">周</button>
                <button class="px-3 py-1 text-xs rounded-full bg-gray-100 text-gray-600">月</button>
              </div>
            </div>
            <div class="h-80">
              <!-- 这里可以放置图表组件 -->
              <div class="w-full h-full flex items-center justify-center">
                <p class="text-gray-400">销售趋势图表将显示在这里</p>
              </div>
            </div>
          </div>
          
          <div class="bg-white rounded-xl shadow-sm p-6">
            <div class="flex items-center justify-between mb-6">
              <h2 class="text-lg font-semibold text-gray-900">销售分布</h2>
              <button class="text-primary text-sm">查看全部</button>
            </div>
            <div class="h-80">
              <!-- 这里可以放置图表组件 -->
              <div class="w-full h-full flex items-center justify-center">
                <p class="text-gray-400">销售分布图表将显示在这里</p>
              </div>
            </div>
          </div>
        </div>
        
        <div class="mt-6 bg-white rounded-xl shadow-sm p-6">
          <div class="flex items-center justify-between mb-6">
            <h2 class="text-lg font-semibold text-gray-900">最近订单</h2>
            <button class="text-primary text-sm">查看全部</button>
          </div>
          
          <div class="overflow-x-auto">
            <table class="min-w-full divide-y divide-gray-200">
              <thead>
                <tr>
                  <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">订单ID</th>
                  <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">客户</th>
                  <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">产品</th>
                  <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">金额</th>
                  <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
                  <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
                </tr>
              </thead>
              <tbody class="bg-white divide-y divide-gray-200">
                <tr>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">#ORD-12345</td>
                  <td class="px-6 py-4 whitespace-nowrap">
                    <div class="flex items-center">
                      <div class="flex-shrink-0 h-10 w-10">
                        <img class="h-10 w-10 rounded-full" src="https://picsum.photos/id/1001/40/40" alt="用户头像">
                      </div>
                      <div class="ml-4">
                        <div class="text-sm font-medium text-gray-900">张三</div>
                        <div class="text-sm text-gray-500">zhangsan@example.com</div>
                      </div>
                    </div>
                  </td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">电子产品</td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">¥1,299.00</td>
                  <td class="px-6 py-4 whitespace-nowrap">
                    <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">已完成</span>
                  </td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-primary">
                    <button>查看详情</button>
                  </td>
                </tr>
                <tr>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">#ORD-12346</td>
                  <td class="px-6 py-4 whitespace-nowrap">
                    <div class="flex items-center">
                      <div class="flex-shrink-0 h-10 w-10">
                        <img class="h-10 w-10 rounded-full" src="https://picsum.photos/id/1002/40/40" alt="用户头像">
                      </div>
                      <div class="ml-4">
                        <div class="text-sm font-medium text-gray-900">李四</div>
                        <div class="text-sm text-gray-500">lisi@example.com</div>
                      </div>
                    </div>
                  </td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">家居用品</td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">¥499.00</td>
                  <td class="px-6 py-4 whitespace-nowrap">
                    <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">处理中</span>
                  </td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-primary">
                    <button>查看详情</button>
                  </td>
                </tr>
                <tr>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">#ORD-12347</td>
                  <td class="px-6 py-4 whitespace-nowrap">
                    <div class="flex items-center">
                      <div class="flex-shrink-0 h-10 w-10">
                        <img class="h-10 w-10 rounded-full" src="https://picsum.photos/id/1003/40/40" alt="用户头像">
                      </div>
                      <div class="ml-4">
                        <div class="text-sm font-medium text-gray-900">王五</div>
                        <div class="text-sm text-gray-500">wangwu@example.com</div>
                      </div>
                    </div>
                  </td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">图书音像</td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">¥299.00</td>
                  <td class="px-6 py-4 whitespace-nowrap">
                    <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">已发货</span>
                  </td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-primary">
                    <button>查看详情</button>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </main>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()

const sidebarVisible = ref(true)
const dropdownVisible = ref(false)

const toggleSidebar = () => {
  sidebarVisible.value = !sidebarVisible.value
}

const toggleDropdown = () => {
  dropdownVisible.value = !dropdownVisible.value
}

const logout = () => {
  localStorage.removeItem('token')
  router.push('/login')
}

onMounted(() => {
  // 检查登录状态
  if (!localStorage.getItem('token')) {
    router.push('/login')
  }
})
</script>

<style scoped>
/* 移动端适配 */
@media (max-width: 768px) {
  .sidebar {
    position: fixed;
    z-index: 100;
    transform: translateX(-100%);
    transition: transform 0.3s ease-in-out;
  }
  
  .sidebar-visible {
    transform: translateX(0);
  }
  
  .overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 99;
    display: none;
  }
  
  .sidebar-visible + .overlay {
    display: block;
  }
}
</style>

第 5 步:创建登录页面

创建登录页面 src/views/Login.vue

html 复制代码
<template>
  <div class="min-h-screen bg-gray-50 flex items-center justify-center p-4">
    <div class="max-w-md w-full bg-white rounded-xl shadow-lg overflow-hidden">
      <div class="p-6 sm:p-8">
        <div class="text-center mb-8">
          <h1 class="text-2xl font-bold text-gray-900">管理系统登录</h1>
          <p class="mt-1 text-sm text-gray-500">请输入账号密码登录</p>
        </div>
        
        <form @submit.prevent="handleLogin">
          <div class="space-y-4">
            <div>
              <label for="email" class="block text-sm font-medium text-gray-700">邮箱</label>
              <div class="mt-1 relative">
                <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                  <i class="fa fa-envelope text-gray-400"></i>
                </div>
                <input type="email" id="email" v-model="form.email" class="pl-10 block w-full rounded-md border-gray-300 shadow-sm focus:ring-primary focus:border-primary" placeholder="your@email.com">
              </div>
            </div>
            
            <div>
              <label for="password" class="block text-sm font-medium text-gray-700">密码</label>
              <div class="mt-1 relative">
                <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                  <i class="fa fa-lock text-gray-400"></i>
                </div>
                <input type="password" id="password" v-model="form.password" class="pl-10 block w-full rounded-md border-gray-300 shadow-sm focus:ring-primary focus:border-primary" placeholder="••••••••">
              </div>
            </div>
            
            <div class="flex items-center justify-between">
              <div class="flex items-center">
                <input id="remember-me" name="remember-me" type="checkbox" class="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded">
                <label for="remember-me" class="ml-2 block text-sm text-gray-900">记住我</label>
              </div>
              
              <div class="text-sm">
                <a href="#" class="font-medium text-primary hover:text-primary/80">忘记密码?</a>
              </div>
            </div>
            
            <div>
              <button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
                登录
              </button>
            </div>
          </div>
        </form>
        
        <div class="mt-6 relative">
          <div class="absolute inset-0 flex items-center">
            <div class="w-full border-t border-gray-300"></div>
          </div>
          <div class="relative flex justify-center text-sm">
            <span class="px-2 bg-white text-gray-500">其他登录方式</span>
          </div>
        </div>
        
        <div class="mt-6 grid grid-cols-3 gap-3">
          <button type="button" class="inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
            <i class="fa fa-weixin text-green-600"></i>
          </button>
          
          <button type="button" class="inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
            <i class="fa fa-qq text-blue-500"></i>
          </button>
          
          <button type="button" class="inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
            <i class="fa fa-github text-gray-800"></i>
          </button>
        </div>
      </div>
      
      <div class="bg-gray-50 px-6 py-4 text-center">
        <p class="text-sm text-gray-600">
          还没有账号? <a href="#" class="font-medium text-primary hover:text-primary/80">注册</a>
        </p>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()

const form = ref({
  email: '',
  password: ''
})

const handleLogin = () => {
  // 模拟登录验证
  if (form.value.email && form.value.password) {
    localStorage.setItem('token', 'fake_token')
    router.push('/dashboard')
  } else {
    alert('请输入邮箱和密码')
  }
}
</script>

第 6 步:创建用户管理页面

创建用户管理页面 src/views/Users.vue

html 复制代码
<template>
  <div class="bg-white rounded-xl shadow-sm p-6">
    <div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
      <div>
        <h2 class="text-xl font-bold text-gray-900">用户管理</h2>
        <p class="mt-1 text-sm text-gray-500">管理系统用户信息</p>
      </div>
      
      <div class="mt-4 md:mt-0 flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-3">
        <div class="relative">
          <input type="text" v-model="searchQuery" placeholder="搜索用户..." class="pl-10 pr-4 py-2 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary/50 w-full sm:w-64">
          <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
        </div>
        
        <button @click="handleCreateUser" class="inline-flex items-center px-4 py-2 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
          <i class="fa fa-plus mr-2"></i>
          创建用户
        </button>
      </div>
    </div>
    
    <div class="overflow-x-auto">
      <table class="min-w-full divide-y divide-gray-200">
        <thead class="bg-gray-50">
          <tr>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">用户信息</th>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">角色</th>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">创建时间</th>
            <th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
          </tr>
        </thead>
        <tbody class="bg-white divide-y divide-gray-200">
          <tr v-for="user in users" :key="user.id">
            <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ user.id }}</td>
            <td class="px-6 py-4 whitespace-nowrap">
              <div class="flex items-center">
                <div class="flex-shrink-0 h-10 w-10">
                  <img class="h-10 w-10 rounded-full" :src="user.avatar" alt="用户头像">
                </div>
                <div class="ml-4">
                  <div class="text-sm font-medium text-gray-900">{{ user.name }}</div>
                  <div class="text-sm text-gray-500">{{ user.email }}</div>
                </div>
              </div>
            </td>
            <td class="px-6 py-4 whitespace-nowrap">
              <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
                {{ user.role }}
              </span>
            </td>
            <td class="px-6 py-4 whitespace-nowrap">
              <span :class="user.status === 'active' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full">
                {{ user.status === 'active' ? '活跃' : '禁用' }}
              </span>
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
              {{ user.createdAt }}
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
              <button class="text-indigo-600 hover:text-indigo-900 mr-3" @click="handleEditUser(user)">编辑</button>
              <button class="text-red-600 hover:text-red-900" @click="handleDeleteUser(user)">删除</button>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    
    <div class="mt-6 flex items-center justify-between">
      <div class="flex-1 flex justify-between sm:hidden">
        <button class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
          上一页
        </button>
        <button class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
          下一页
        </button>
      </div>
      <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
        <div>
          <p class="text-sm text-gray-700">
            显示第 <span class="font-medium">{{ (currentPage - 1) * pageSize + 1 }}</span> 至 <span class="font-medium">{{ Math.min(currentPage * pageSize, totalUsers) }}</span> 条,共 <span class="font-medium">{{ totalUsers }}</span> 条记录
          </p>
        </div>
        <div>
          <nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
            <button :disabled="currentPage === 1" @click="currentPage--" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
              <span class="sr-only">上一页</span>
              <i class="fa fa-chevron-left h-5 w-5"></i>
            </button>
            
            <button v-for="page in totalPages" :key="page" :class="page === currentPage ? 'z-10 bg-primary text-white' : 'bg-white text-gray-700'" :aria-current="page === currentPage ? 'page' : undefined" @click="currentPage = page" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium">
              {{ page }}
            </button>
            
            <button :disabled="currentPage === totalPages" @click="currentPage++" class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
              <span class="sr-only">下一页</span>
              <i class="fa fa-chevron-right h-5 w-5"></i>
            </button>
          </nav>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'

const searchQuery = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const totalUsers = ref(124)

const totalPages = computed(() => {
  return Math.ceil(totalUsers.value / pageSize.value)
})

const users = ref([
  {
    id: 1,
    name: '张三',
    email: 'zhangsan@example.com',
    role: '管理员',
    status: 'active',
    avatar: 'https://picsum.photos/id/1001/40/40',
    createdAt: '2023-01-15'
  },
  {
    id: 2,
    name: '李四',
    email: 'lisi@example.com',
    role: '编辑',
    status: 'active',
    avatar: 'https://picsum.photos/id/1002/40/40',
    createdAt: '2023-02-20'
  },
  {
    id: 3,
    name: '王五',
    email: 'wangwu@example.com',
    role: '普通用户',
    status: 'disabled',
    avatar: 'https://picsum.photos/id/1003/40/40',
    createdAt: '2023-03-10'
  },
  {
    id: 4,
    name: '赵六',
    email: 'zhaoliu@example.com',
    role: '普通用户',
    status: 'active',
    avatar: 'https://picsum.photos/id/1004/40/40',
    createdAt: '2023-04-05'
  },
  {
    id: 5,
    name: '钱七',
    email: 'qianqi@example.com',
    role: '编辑',
    status: 'active',
    avatar: 'https://picsum.photos/id/1005/40/40',
    createdAt: '2023-05-12'
  }
])

const handleCreateUser = () => {
  console.log('创建用户')
  // 打开创建用户模态框
}

const handleEditUser = (user) => {
  console.log('编辑用户', user)
  // 打开编辑用户模态框
}

const handleDeleteUser = (user) => {
  if (confirm(`确定要删除用户 ${user.name} 吗?`)) {
    console.log('删除用户', user)
    // 调用API删除用户
  }
}

onMounted(() => {
  // 加载用户数据
})
</script>

第 7 步:创建产品管理页面

创建产品管理页面 src/views/Products.vue

html 复制代码
<template>
  <div class="bg-white rounded-xl shadow-sm p-6">
    <div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
      <div>
        <h2 class="text-xl font-bold text-gray-900">产品管理</h2>
        <p class="mt-1 text-sm text-gray-500">管理系统产品信息</p>
      </div>
      
      <div class="mt-4 md:mt-0 flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-3">
        <div class="relative">
          <input type="text" v-model="searchQuery" placeholder="搜索产品..." class="pl-10 pr-4 py-2 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary/50 w-full sm:w-64">
          <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
        </div>
        
        <div class="relative">
          <select v-model="categoryFilter" class="pl-4 pr-10 py-2 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary/50 appearance-none bg-white">
            <option value="">全部分类</option>
            <option value="electronics">电子产品</option>
            <option value="clothing">服装</option>
            <option value="home">家居用品</option>
            <option value="books">图书音像</option>
          </select>
          <i class="fa fa-chevron-down absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 pointer-events-none"></i>
        </div>
        
        <button @click="handleCreateProduct" class="inline-flex items-center px-4 py-2 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
          <i class="fa fa-plus mr-2"></i>
          创建产品
        </button>
      </div>
    </div>
    
    <div class="overflow-x-auto">
      <table class="min-w-full divide-y divide-gray-200">
        <thead class="bg-gray-50">
          <tr>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">产品信息</th>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">分类</th>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">价格</th>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">库存</th>
            <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
            <th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
          </tr>
        </thead>
        <tbody class="bg-white divide-y divide-gray-200">
          <tr v-for="product in products" :key="product.id">
            <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ product.id }}</td>
            <td class="px-6 py-4 whitespace-nowrap">
              <div class="flex items-center">
                <div class="flex-shrink-0 h-12 w-12">
                  <img class="h-12 w-12 rounded-md object-cover" :src="product.image" alt="产品图片">
                </div>
                <div class="ml-4">
                  <div class="text-sm font-medium text-gray-900">{{ product.name }}</div>
                  <div class="text-sm text-gray-500">{{ product.description }}</div>
                </div>
              </div>
            </td>
            <td class="px-6 py-4 whitespace-nowrap">
              <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-purple-100 text-purple-800">
                {{ product.category }}
              </span>
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">¥{{ product.price.toFixed(2) }}</td>
            <td class="px-6 py-4 whitespace-nowrap">
              <span :class="product.stock < 10 ? 'text-red-600' : 'text-gray-900'" class="text-sm font-medium">
                {{ product.stock }}
              </span>
            </td>
            <td class="px-6 py-4 whitespace-nowrap">
              <span :class="product.status === 'active' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full">
                {{ product.status === 'active' ? '上架' : '下架' }}
              </span>
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
              <button class="text-indigo-600 hover:text-indigo-900 mr-3" @click="handleEditProduct(product)">编辑</button>
              <button class="text-red-600 hover:text-red-900" @click="handleDeleteProduct(product)">删除</button>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    
    <div class="mt-6 flex items-center justify-between">
      <div class="flex-1 flex justify-between sm:hidden">
        <button class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
          上一页
        </button>
        <button class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
          下一页
        </button>
      </div>
      <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
        <div>
          <p class="text-sm text-gray-700">
            显示第 <span class="font-medium">{{ (currentPage - 1) * pageSize + 1 }}</span> 至 <span class="font-medium">{{ Math.min(currentPage * pageSize, totalProducts) }}</span> 条,共 <span class="font-medium">{{ totalProducts }}</span> 条记录
          </p>
        </div>
        <div>
          <nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
            <button :disabled="currentPage === 1" @click="currentPage--" class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
              <span class="sr-only">上一页</span>
              <i class="fa fa-chevron-left h-5 w-5"></i>
            </button>
            
            <button v-for="page in totalPages" :key="page" :class="page === currentPage ? 'z-10 bg-primary text-white' : 'bg-white text-gray-700'" :aria-current="page === currentPage ? 'page' : undefined" @click="currentPage = page" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium">
              {{ page }}
            </button>
            
            <button :disabled="currentPage === totalPages" @click="currentPage++" class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
              <span class="sr-only">下一页</span>
              <i class="fa fa-chevron-right h-5 w-5"></i>
            </button>
          </nav>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'

const searchQuery = ref('')
const categoryFilter = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const totalProducts = ref(86)

const totalPages = computed(() => {
  return Math.ceil(totalProducts.value / pageSize.value)
})

const products = ref([
  {
    id: 1,
    name: '智能手表',
    description: '多功能智能手表,支持心率监测、睡眠分析等功能',
    category: '电子产品',
    price: 1299.99,
    stock: 56,
    status: 'active',
    image: 'https://picsum.photos/id/1/80/80'
  },
  {
    id: 2,
    name: '无线耳机',
    description: '主动降噪无线耳机,提供沉浸式音乐体验',
    category: '电子产品',
    price: 899.99,
    stock: 32,
    status: 'active',
    image: 'https://picsum.photos/id/2/80/80'
  },
  {
    id: 3,
    name: '纯棉T恤',
    description: '舒适纯棉T恤,多种颜色可选',
    category: '服装',
    price: 99.99,
    stock: 87,
    status: 'active',
    image: 'https://picsum.photos/id/3/80/80'
  },
  {
    id: 4,
    name: '家用咖啡机',
    description: '全自动家用咖啡机,一键制作美味咖啡',
    category: '家居用品',
    price: 1999.99,
    stock: 12,
    status: 'active',
    image: 'https://picsum.photos/id/4/80/80'
  },
  {
    id: 5,
    name: '前端开发实战',
    description: '全面讲解前端开发技术,从入门到精通',
    category: '图书音像',
    price: 89.99,
    stock: 5,
    status: 'active',
    image: 'https://picsum.photos/id/5/80/80'
  }
])

const handleCreateProduct = () => {
  console.log('创建产品')
  // 打开创建产品模态框
}

const handleEditProduct = (product) => {
  console.log('编辑产品', product)
  // 打开编辑产品模态框
}

const handleDeleteProduct = (product) => {
  if (confirm(`确定要删除产品 ${product.name} 吗?`)) {
    console.log('删除产品', product)
    // 调用API删除产品
  }
}

onMounted(() => {
  // 加载产品数据
})
</script>

第 8 步:配置主应用

修改 src/main.js 来配置主应用:

html 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './index.css'
import 'font-awesome/css/font-awesome.min.css'

const app = createApp(App)

app.use(router)

app.mount('#app')

第 9 步:创建主应用组件

修改 src/App.vue

html 复制代码
<template>
  <router-view />
</template>

<script setup>
// 主应用逻辑
</script>

<style>
/* 全局样式 */
body {
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
</style>
相关推荐
OEC小胖胖4 小时前
去中心化身份:2025年Web3身份验证系统开发实践
前端·web3·去中心化·区块链
vvilkim5 小时前
Electron 进程间通信(IPC)深度优化指南
前端·javascript·electron
ai小鬼头7 小时前
百度秒搭发布:无代码编程如何让普通人轻松打造AI应用?
前端·后端·github
漂流瓶jz7 小时前
清除浮动/避开margin折叠:前端CSS中BFC的特点与限制
前端·css·面试
前端 贾公子7 小时前
在移动端使用 Tailwind CSS (uniapp)
前端·uni-app
散步去海边7 小时前
Cursor 进阶使用教程
前端·ai编程·cursor
清幽竹客7 小时前
vue-30(理解 Nuxt.js 目录结构)
前端·javascript·vue.js
weiweiweb8887 小时前
cesium加载Draco几何压缩数据
前端·javascript·vue.js
幼儿园技术家7 小时前
微信小店与微信小程序简单集成指南
前端
我不吃饼干9 天前
鸽了六年的某大厂面试题:你会手写一个模板引擎吗?
前端·javascript·面试