基于Vue.js的图书管理系统前端界面设计

一、 系统 前端界面设计要求与效果

(一)系统功能结构图

设计一个基于Vue.js的图书管理系统前端界面。要充分体现Vue的核心特性和应用场景,同时结合信息管理专业的知识。要求系统分为仪表盘、图书管理、借阅管理和用户管理四个主要模块,每个模块有独立的功能和界面。以下是系统功能结构图:

因为是基于Vue.js的图书管理系统前端界面设计,所以只涉及到表示层,以及为了演示设计的部分数据模型和数据层的书籍信息、用户信息、借阅记录。

(二)界面的完整代码

界面的完整代码如下:

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>图书管理系统</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js"></script>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
  
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            primary: '#3B82F6',
            secondary: '#6366F1',
            accent: '#F59E0B',
            neutral: '#6B7280',
            success: '#10B981',
            warning: '#F59E0B',
            danger: '#EF4444',
          },
          fontFamily: {
            inter: ['Inter', 'sans-serif'],
          },
        },
      }
    }
  </script>
  
  <style type="text/tailwindcss">
    @layer utilities {
      .content-auto {
        content-visibility: auto;
      }
      .card-shadow {
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
      }
      .transition-custom {
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      .scale-hover {
        transition: transform 0.2s ease-in-out;
      }
      .scale-hover:hover {
        transform: scale(1.02);
      }
    }
  </style>
</head>
<body class="font-inter bg-gray-50 text-gray-800 min-h-screen flex flex-col">
  <div id="app">
    <!-- 导航栏 -->
    <nav class="bg-white shadow-md sticky top-0 z-50 transition-all duration-300" :class="{'bg-primary/95 text-white': isScrolled}">
      <div class="container mx-auto px-4 py-3 flex justify-between items-center">
        <div class="flex items-center space-x-2">
          <i class="fa fa-book text-2xl text-primary"></i>
          <span class="text-xl font-bold">图书管理系统</span>
        </div>
        
        <div class="hidden md:flex items-center space-x-6">
          <a href="#" class="font-medium hover:text-primary transition-colors" :class="{'text-primary': currentView === 'dashboard'}">
            <i class="fa fa-tachometer mr-1"></i>仪表盘
          </a>
          <a href="#" class="font-medium hover:text-primary transition-colors" :class="{'text-primary': currentView === 'books'}">
            <i class="fa fa-book mr-1"></i>图书管理
          </a>
          <a href="#" class="font-medium hover:text-primary transition-colors" :class="{'text-primary': currentView === 'borrows'}">
            <i class="fa fa-exchange mr-1"></i>借阅管理
          </a>
          <a href="#" class="font-medium hover:text-primary transition-colors" :class="{'text-primary': currentView === 'users'}">
            <i class="fa fa-users mr-1"></i>用户管理
          </a>
        </div>
        
        <div class="flex items-center space-x-4">
          <div class="relative hidden md:block">
            <input type="text" placeholder="搜索图书..." class="pl-9 pr-4 py-2 rounded-full bg-gray-100 focus:outline-none focus:ring-2 focus:ring-primary/50 w-48 transition-all duration-300 focus: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">
            <button class="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center hover:bg-gray-300 transition-colors">
              <i class="fa fa-user"></i>
            </button>
          </div>
          
          <button class="md:hidden" @click="toggleMobileMenu">
            <i class="fa fa-bars text-xl"></i>
          </button>
        </div>
      </div>
      
      <!-- 移动端菜单 -->
      <div class="md:hidden bg-white border-t border-gray-100 shadow-lg absolute w-full left-0 transition-all duration-300 transform" :class="{'translate-y-0': isMobileMenuOpen, '-translate-y-full': !isMobileMenuOpen}">
        <div class="container mx-auto px-4 py-2">
          <div class="flex flex-col space-y-3 py-2">
            <a href="#" class="py-2 px-3 hover:bg-gray-100 rounded-lg transition-colors" :class="{'bg-primary/10 text-primary': currentView === 'dashboard'}">
              <i class="fa fa-tachometer mr-2"></i>仪表盘
            </a>
            <a href="#" class="py-2 px-3 hover:bg-gray-100 rounded-lg transition-colors" :class="{'bg-primary/10 text-primary': currentView === 'books'}">
              <i class="fa fa-book mr-2"></i>图书管理
            </a>
            <a href="#" class="py-2 px-3 hover:bg-gray-100 rounded-lg transition-colors" :class="{'bg-primary/10 text-primary': currentView === 'borrows'}">
              <i class="fa fa-exchange mr-2"></i>借阅管理
            </a>
            <a href="#" class="py-2 px-3 hover:bg-gray-100 rounded-lg transition-colors" :class="{'bg-primary/10 text-primary': currentView === 'users'}">
              <i class="fa fa-users mr-2"></i>用户管理
            </a>
            <div class="relative">
              <input type="text" placeholder="搜索图书..." class="w-full pl-9 pr-4 py-2 rounded-lg bg-gray-100 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>
        </div>
      </div>
    </nav>
    
    <!-- 主内容区 -->
    <main class="flex-grow container mx-auto px-4 py-6">
      <!-- 仪表盘视图 -->
      <div v-if="currentView === 'dashboard'">
        <div class="mb-8">
          <h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold mb-2">仪表盘</h1>
          <p class="text-gray-600">欢迎使用图书管理系统,以下是系统概览</p>
        </div>
        
        <!-- 统计卡片 -->
        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
          <div class="bg-white rounded-xl p-6 card-shadow scale-hover">
            <div class="flex justify-between items-start">
              <div>
                <p class="text-gray-500 text-sm">总藏书量</p>
                <h3 class="text-3xl font-bold mt-1">{{ books.length }}</h3>
                <p class="text-success text-sm mt-2">
                  <i class="fa fa-arrow-up"></i> 5.2% <span class="text-gray-500">较上月</span>
                </p>
              </div>
              <div class="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
                <i class="fa fa-book text-primary text-xl"></i>
              </div>
            </div>
          </div>
          
          <div class="bg-white rounded-xl p-6 card-shadow scale-hover">
            <div class="flex justify-between items-start">
              <div>
                <p class="text-gray-500 text-sm">借出图书</p>
                <h3 class="text-3xl font-bold mt-1">{{ borrowedBooksCount }}</h3>
                <p class="text-danger text-sm mt-2">
                  <i class="fa fa-arrow-down"></i> 2.8% <span class="text-gray-500">较上月</span>
                </p>
              </div>
              <div class="w-12 h-12 rounded-full bg-accent/10 flex items-center justify-center">
                <i class="fa fa-exchange text-accent text-xl"></i>
              </div>
            </div>
          </div>
          
          <div class="bg-white rounded-xl p-6 card-shadow scale-hover">
            <div class="flex justify-between items-start">
              <div>
                <p class="text-gray-500 text-sm">注册用户</p>
                <h3 class="text-3xl font-bold mt-1">{{ users.length }}</h3>
                <p class="text-success text-sm mt-2">
                  <i class="fa fa-arrow-up"></i> 12.3% <span class="text-gray-500">较上月</span>
                </p>
              </div>
              <div class="w-12 h-12 rounded-full bg-secondary/10 flex items-center justify-center">
                <i class="fa fa-users text-secondary text-xl"></i>
              </div>
            </div>
          </div>
          
          <div class="bg-white rounded-xl p-6 card-shadow scale-hover">
            <div class="flex justify-between items-start">
              <div>
                <p class="text-gray-500 text-sm">逾期未还</p>
                <h3 class="text-3xl font-bold mt-1">{{ overdueBooksCount }}</h3>
                <p class="text-warning text-sm mt-2">
                  <i class="fa fa-arrow-up"></i> 3.1% <span class="text-gray-500">较上月</span>
                </p>
              </div>
              <div class="w-12 h-12 rounded-full bg-danger/10 flex items-center justify-center">
                <i class="fa fa-calendar-times-o text-danger text-xl"></i>
              </div>
            </div>
          </div>
        </div>
        
        <!-- 图表区域 -->
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
          <div class="bg-white rounded-xl p-6 card-shadow lg:col-span-2">
            <div class="flex justify-between items-center mb-6">
              <h3 class="font-bold text-lg">借阅趋势</h3>
              <div class="flex space-x-2">
                <button class="px-3 py-1 text-sm rounded-md bg-primary/10 text-primary">周</button>
                <button class="px-3 py-1 text-sm rounded-md hover:bg-gray-100">月</button>
                <button class="px-3 py-1 text-sm rounded-md hover:bg-gray-100">年</button>
              </div>
            </div>
            <div class="h-80">
              <canvas id="borrowChart"></canvas>
            </div>
          </div>
          
          <div class="bg-white rounded-xl p-6 card-shadow">
            <div class="flex justify-between items-center mb-6">
              <h3 class="font-bold text-lg">分类统计</h3>
              <button class="text-primary hover:text-primary/80">
                <i class="fa fa-refresh"></i>
              </button>
            </div>
            <div class="h-80">
              <canvas id="categoryChart"></canvas>
            </div>
          </div>
        </div>
        
        <!-- 最近借阅 -->
        <div class="bg-white rounded-xl p-6 card-shadow">
          <div class="flex justify-between items-center mb-6">
            <h3 class="font-bold text-lg">最近借阅记录</h3>
            <button class="text-primary hover:text-primary/80">查看全部</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">图书</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 v-for="borrow in recentBorrows" :key="borrow.id" class="hover:bg-gray-50 transition-colors">
                  <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="borrow.book.cover" alt="">
                      </div>
                      <div class="ml-4">
                        <div class="text-sm font-medium text-gray-900">{{ borrow.book.title }}</div>
                        <div class="text-sm text-gray-500">{{ borrow.book.author }}</div>
                      </div>
                    </div>
                  </td>
				  <td class="px-6 py-4 whitespace-nowrap">
				    <div class="text-sm font-medium text-gray-900">{{ borrow.user?.name || '未知用户' }}</div>
				    <div class="text-sm text-gray-500">{{ borrow.user?.studentId || '未知ID' }}</div>
				  </td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                    {{ borrow.borrowDate }}
                  </td>
                  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                    {{ borrow.dueDate }}
                  </td>
                  <td class="px-6 py-4 whitespace-nowrap">
                    <span v-if="borrow.isReturned" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
                      已归还
                    </span>
                    <span v-else-if="isOverdue(borrow.dueDate)" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
                      已逾期
                    </span>
                    <span v-else class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
                      借阅中
                    </span>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </div>
      
      <!-- 图书管理视图 -->
      <div v-if="currentView === 'books'">
        <div class="mb-8">
          <h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold mb-2">图书管理</h1>
          <p class="text-gray-600">管理系统中的所有图书信息</p>
        </div>
        
        <!-- 搜索和筛选 -->
        <div class="bg-white rounded-xl p-6 card-shadow mb-6">
          <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
            <div class="relative">
              <input type="text" v-model="bookSearchQuery" placeholder="搜索图书标题/作者" class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
              <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
            </div>
            <div>
              <select v-model="bookCategoryFilter" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
                <option value="">所有分类</option>
                <option value="计算机">计算机</option>
                <option value="文学">文学</option>
                <option value="历史">历史</option>
                <option value="科学">科学</option>
                <option value="艺术">艺术</option>
                <option value="经济">经济</option>
              </select>
            </div>
            <div>
              <select v-model="bookStatusFilter" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
                <option value="">所有状态</option>
                <option value="available">可借阅</option>
                <option value="borrowed">已借出</option>
              </select>
            </div>
            <div class="flex justify-end">
              <button @click="openBookModal(null)" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg flex items-center transition-colors">
                <i class="fa fa-plus mr-2"></i> 添加图书
              </button>
            </div>
          </div>
        </div>
        
        <!-- 图书列表 -->
        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
          <div v-for="book in filteredBooks" :key="book.id" class="bg-white rounded-xl overflow-hidden card-shadow scale-hover">
            <div class="flex">
              <div class="w-1/3 bg-gray-200">
                <img :src="book.cover" alt="Book cover" class="w-full h-full object-cover">
              </div>
              <div class="w-2/3 p-4">
                <h3 class="font-bold text-lg mb-1 line-clamp-1">{{ book.title }}</h3>
                <p class="text-gray-600 text-sm mb-1 line-clamp-1">{{ book.author }}</p>
                <p class="text-gray-500 text-xs mb-3">
                  <span class="bg-gray-100 px-2 py-0.5 rounded text-xs">{{ book.category }}</span>
                </p>
                <div class="flex justify-between items-center mt-auto">
                  <span v-if="book.isBorrowed" class="text-xs px-2 py-1 bg-red-100 text-red-800 rounded-full">
                    已借出
                  </span>
                  <span v-else class="text-xs px-2 py-1 bg-green-100 text-green-800 rounded-full">
                    可借阅
                  </span>
                  <div class="flex space-x-1">
                    <button @click="openBookModal(book)" class="p-1.5 rounded-full hover:bg-gray-100 text-gray-600 transition-colors">
                      <i class="fa fa-edit"></i>
                    </button>
                    <button @click="deleteBook(book.id)" class="p-1.5 rounded-full hover:bg-red-100 text-red-600 transition-colors">
                      <i class="fa fa-trash"></i>
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
        
        <!-- 分页 -->
        <div class="mt-8 flex justify-between items-center">
          <div class="text-sm text-gray-500">
            显示 {{ (currentPage - 1) * booksPerPage + 1 }} 到 {{ Math.min(currentPage * booksPerPage, filteredBooks.length) }} 共 {{ filteredBooks.length }} 条记录
          </div>
          <div class="flex space-x-1">
            <button @click="prevPage" :disabled="currentPage === 1" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
              <i class="fa fa-chevron-left"></i>
            </button>
            <button v-for="page in totalPages" :key="page" @click="currentPage = page" :class="{'bg-primary text-white border-primary': page === currentPage, 'border-gray-300 text-gray-600 hover:bg-gray-50': page !== currentPage}" class="px-3 py-1 rounded border transition-colors">
              {{ page }}
            </button>
            <button @click="nextPage" :disabled="currentPage === totalPages" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
              <i class="fa fa-chevron-right"></i>
            </button>
          </div>
        </div>
      </div>
      
      <!-- 借阅管理视图 -->
      <div v-if="currentView === 'borrows'">
        <div class="mb-8">
          <h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold mb-2">借阅管理</h1>
          <p class="text-gray-600">管理图书的借阅和归还</p>
        </div>
        
        <!-- 搜索和筛选 -->
        <div class="bg-white rounded-xl p-6 card-shadow mb-6">
          <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
            <div class="relative">
              <input type="text" v-model="borrowSearchQuery" placeholder="搜索图书/借阅人" class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
              <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
            </div>
            <div>
              <select v-model="borrowStatusFilter" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
                <option value="">所有状态</option>
                <option value="borrowed">借阅中</option>
                <option value="returned">已归还</option>
                <option value="overdue">已逾期</option>
              </select>
            </div>
            <div>
              <div class="flex items-center space-x-2">
                <button class="w-full px-4 py-2 rounded-lg border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
                  <i class="fa fa-calendar mr-2"></i> 时间范围
                </button>
              </div>
            </div>
            <div class="flex justify-end">
              <button @click="openBorrowModal" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg flex items-center transition-colors">
                <i class="fa fa-plus mr-2"></i> 新增借阅
              </button>
            </div>
          </div>
        </div>
        
        <!-- 借阅记录表格 -->
        <div class="bg-white rounded-xl p-6 card-shadow 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">图书信息</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>
                <th 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="borrow in filteredBorrows" :key="borrow.id" class="hover:bg-gray-50 transition-colors">
                <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="borrow.book?.cover || 'https://picsum.photos/seed/default/100/100'" alt="Book cover">
                    </div>
                    <div class="ml-4">
                      <div class="text-sm font-medium text-gray-900">{{ borrow.book?.title || '未知图书' }}</div>
                      <div class="text-sm text-gray-500">{{ borrow.book?.author || '未知作者' }}</div>
                    </div>
                  </div>
                </td>
                <td class="px-6 py-4 whitespace-nowrap">
                  <div class="text-sm font-medium text-gray-900">{{ borrow.user.name }}</div>
                  <div class="text-sm text-gray-500">{{ borrow.user.studentId }}</div>
                </td>
                <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                  {{ borrow.borrowDate }}
                </td>
                <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                  {{ borrow.dueDate }}
                </td>
                <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
                  {{ borrow.returnDate || '-' }}
                </td>
                <td class="px-6 py-4 whitespace-nowrap">
                  <span v-if="borrow.isReturned" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
                    已归还
                  </span>
                  <span v-else-if="isOverdue(borrow.dueDate)" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
                    已逾期
                  </span>
                  <span v-else 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-right text-sm font-medium">
                  <div class="flex justify-end space-x-1">
                    <button v-if="!borrow.isReturned" @click="returnBook(borrow)" class="p-1.5 rounded-full hover:bg-green-100 text-green-600 transition-colors">
                      <i class="fa fa-check"></i> 归还
                    </button>
                    <button @click="viewBorrowDetails(borrow)" class="p-1.5 rounded-full hover:bg-gray-100 text-gray-600 transition-colors">
                      <i class="fa fa-eye"></i>
                    </button>
                  </div>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
        
        <!-- 分页 -->
        <div class="mt-8 flex justify-between items-center">
          <div class="text-sm text-gray-500">
            显示 {{ (currentBorrowPage - 1) * borrowsPerPage + 1 }} 到 {{ Math.min(currentBorrowPage * borrowsPerPage, filteredBorrows.length) }} 共 {{ filteredBorrows.length }} 条记录
          </div>
          <div class="flex space-x-1">
            <button @click="prevBorrowPage" :disabled="currentBorrowPage === 1" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
              <i class="fa fa-chevron-left"></i>
            </button>
            <button v-for="page in totalBorrowPages" :key="page" @click="currentBorrowPage = page" :class="{'bg-primary text-white border-primary': page === currentBorrowPage, 'border-gray-300 text-gray-600 hover:bg-gray-50': page !== currentBorrowPage}" class="px-3 py-1 rounded border transition-colors">
              {{ page }}
            </button>
            <button @click="nextBorrowPage" :disabled="currentBorrowPage === totalBorrowPages" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
              <i class="fa fa-chevron-right"></i>
            </button>
          </div>
        </div>
      </div>
      
      <!-- 用户管理视图 -->
      <div v-if="currentView === 'users'">
        <div class="mb-8">
          <h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold mb-2">用户管理</h1>
          <p class="text-gray-600">管理系统中的所有用户</p>
        </div>
        
        <!-- 搜索和筛选 -->
        <div class="bg-white rounded-xl p-6 card-shadow mb-6">
          <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
            <div class="relative">
              <input type="text" v-model="userSearchQuery" placeholder="搜索用户名/学号" class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
              <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
            </div>
            <div>
              <select v-model="userRoleFilter" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
                <option value="">所有角色</option>
                <option value="student">学生</option>
                <option value="teacher">教师</option>
                <option value="admin">管理员</option>
              </select>
            </div>
            <div>
              <select v-model="userStatusFilter" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
                <option value="">所有状态</option>
                <option value="active">活跃</option>
                <option value="blocked">已封禁</option>
              </select>
            </div>
            <div class="flex justify-end">
              <button @click="openUserModal(null)" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg flex items-center transition-colors">
                <i class="fa fa-plus mr-2"></i> 添加用户
              </button>
            </div>
          </div>
        </div>
        
        <!-- 用户列表 -->
        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
          <div v-for="user in filteredUsers" :key="user.id" class="bg-white rounded-xl overflow-hidden card-shadow scale-hover">
            <div class="p-4">
              <div class="flex items-center mb-4">
                <div class="w-16 h-16 rounded-full bg-gray-200 overflow-hidden">
                  <img :src="user.avatar || 'https://picsum.photos/seed/defaultuser/200/200'" alt="User avatar" class="w-full h-full object-cover">
                </div>
                <div class="ml-4">
                  <h3 class="font-bold text-lg">{{ user.name || '未知用户' }}</h3>
                  <p class="text-gray-600 text-sm">{{ user.studentId || '未知ID' }}</p>
                  <div class="flex items-center mt-1">
                    <span class="text-xs px-2 py-0.5 rounded-full bg-blue-100 text-blue-800">
                      {{ user.role === 'student' ? '学生' : user.role === 'teacher' ? '教师' : '管理员' }}
                    </span>
                    <span v-if="user.isBlocked" class="ml-2 text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-800">
                      已封禁
                    </span>
                  </div>
                </div>
              </div>
              
              <div class="grid grid-cols-3 gap-2 text-center mb-4">
                <div class="bg-gray-50 p-2 rounded-lg">
                  <p class="text-sm text-gray-500">借阅中</p>
                  <p class="font-bold">{{ getBorrowingCount(user.id) }}</p>
                </div>
                <div class="bg-gray-50 p-2 rounded-lg">
                  <p class="text-sm text-gray-500">已归还</p>
                  <p class="font-bold">{{ getReturnedCount(user.id) }}</p>
                </div>
                <div class="bg-gray-50 p-2 rounded-lg">
                  <p class="text-sm text-gray-500">逾期</p>
                  <p class="font-bold text-red-500">{{ getOverdueCount(user.id) }}</p>
                </div>
              </div>
              
              <div class="flex justify-end space-x-2">
                <button @click="openUserModal(user)" class="px-3 py-1.5 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors">
                  <i class="fa fa-edit mr-1"></i> 编辑
                </button>
                <button @click="toggleUserBlock(user)" class="px-3 py-1.5 rounded-lg" :class="user.isBlocked ? 'bg-green-500 text-white hover:bg-green-600' : 'bg-red-500 text-white hover:bg-red-600'">
                  <i class="fa" :class="user.isBlocked ? 'fa-unlock-alt mr-1' : 'fa-lock mr-1'"></i>
                  {{ user.isBlocked ? '解封' : '封禁' }}
                </button>
              </div>
            </div>
          </div>
        </div>
        
        <!-- 分页 -->
        <div class="mt-8 flex justify-between items-center">
          <div class="text-sm text-gray-500">
            显示 {{ (currentUserPage - 1) * usersPerPage + 1 }} 到 {{ Math.min(currentUserPage * usersPerPage, filteredUsers.length) }} 共 {{ filteredUsers.length }} 条记录
          </div>
          <div class="flex space-x-1">
            <button @click="prevUserPage" :disabled="currentUserPage === 1" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
              <i class="fa fa-chevron-left"></i>
            </button>
            <button v-for="page in totalUserPages" :key="page" @click="currentUserPage = page" :class="{'bg-primary text-white border-primary': page === currentUserPage, 'border-gray-300 text-gray-600 hover:bg-gray-50': page !== currentUserPage}" class="px-3 py-1 rounded border transition-colors">
              {{ page }}
            </button>
            <button @click="nextUserPage" :disabled="currentUserPage === totalUserPages" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
              <i class="fa fa-chevron-right"></i>
            </button>
          </div>
        </div>
      </div>
    </main>
    
    <!-- 页脚 -->
    <footer class="bg-white border-t border-gray-200 py-6">
      <div class="container mx-auto px-4">
        <div class="flex flex-col md:flex-row justify-between items-center">
          <div class="mb-4 md:mb-0">
            <div class="flex items-center">
              <i class="fa fa-book text-primary text-xl mr-2"></i>
              <span class="font-bold text-lg">图书管理系统</span>
            </div>
            <p class="text-gray-500 text-sm mt-1">信息管理与信息系统专业课程设计</p>
          </div>
          <div class="flex space-x-4">
            <a href="#" class="text-gray-500 hover:text-primary transition-colors">
              <i class="fa fa-github text-xl"></i>
            </a>
            <a href="#" class="text-gray-500 hover:text-primary transition-colors">
              <i class="fa fa-envelope text-xl"></i>
            </a>
            <a href="#" class="text-gray-500 hover:text-primary transition-colors">
              <i class="fa fa-linkedin text-xl"></i>
            </a>
          </div>
        </div>
        <div class="mt-6 pt-6 border-t border-gray-100 text-center text-gray-500 text-sm">
          &copy; 2025 图书管理系统 | 设计与开发
        </div>
      </div>
    </footer>
    
    <!-- 添加/编辑图书模态框 -->
    <div v-if="isBookModalOpen" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" @click.self="closeBookModal">
      <div class="bg-white rounded-xl w-full max-w-lg max-h-[90vh] overflow-y-auto">
        <div class="p-6 border-b border-gray-100">
          <div class="flex justify-between items-center">
            <h3 class="text-lg font-bold">{{ editingBook ? '编辑图书' : '添加图书' }}</h3>
            <button @click="closeBookModal" class="text-gray-500 hover:text-gray-700">
              <i class="fa fa-times"></i>
            </button>
          </div>
        </div>
        
        <div class="p-6">
          <form @submit.prevent="saveBook">
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">图书封面 URL</label>
              <input type="text" v-model="form.bookCover" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">图书标题</label>
              <input type="text" v-model="form.bookTitle" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">作者</label>
              <input type="text" v-model="form.bookAuthor" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">出版社</label>
              <input type="text" v-model="form.bookPublisher" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">出版年份</label>
              <input type="number" v-model="form.bookYear" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">分类</label>
              <select v-model="form.bookCategory" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
                <option value="计算机">计算机</option>
                <option value="文学">文学</option>
                <option value="历史">历史</option>
                <option value="科学">科学</option>
                <option value="艺术">艺术</option>
                <option value="经济">经济</option>
              </select>
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">ISBN</label>
              <input type="text" v-model="form.bookISBN" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">简介</label>
              <textarea v-model="form.bookDescription" rows="4" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"></textarea>
            </div>
            
            <div class="flex justify-end space-x-3 mt-6">
              <button type="button" @click="closeBookModal" class="px-4 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors">
                取消
              </button>
              <button type="submit" class="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors">
                保存
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
    
    <!-- 新增借阅模态框 -->
    <div v-if="isBorrowModalOpen" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" @click.self="closeBorrowModal">
      <div class="bg-white rounded-xl w-full max-w-lg max-h-[90vh] overflow-y-auto">
        <div class="p-6 border-b border-gray-100">
          <div class="flex justify-between items-center">
            <h3 class="text-lg font-bold">新增借阅记录</h3>
            <button @click="closeBorrowModal" class="text-gray-500 hover:text-gray-700">
              <i class="fa fa-times"></i>
            </button>
          </div>
        </div>
        
        <div class="p-6">
          <form @submit.prevent="saveBorrow">
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">选择图书</label>
              <select v-model="form.borrowBookId" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
                <option value="">请选择图书</option>
                <option v-for="book in availableBooks" :key="book.id" :value="book.id">
                  {{ book.title }} - {{ book.author }}
                </option>
              </select>
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">选择用户</label>
              <select v-model="form.borrowUserId" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
                <option value="">请选择用户</option>
                <option v-for="user in users" :key="user.id" :value="user.id">
                  {{ user.name }} - {{ user.studentId }}
                </option>
              </select>
            </div>
            
            <div class="grid grid-cols-2 gap-4 mb-4">
              <div>
                <label class="block text-sm font-medium text-gray-700 mb-1">借阅日期</label>
                <input type="date" v-model="form.borrowDate" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
              </div>
              <div>
                <label class="block text-sm font-medium text-gray-700 mb-1">应归还日期</label>
                <input type="date" v-model="form.dueDate" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
              </div>
            </div>
            
            <div class="flex justify-end space-x-3 mt-6">
              <button type="button" @click="closeBorrowModal" class="px-4 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors">
                取消
              </button>
              <button type="submit" class="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors">
                保存
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
    
    <!-- 添加/编辑用户模态框 -->
    <div v-if="isUserModalOpen" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" @click.self="closeUserModal">
      <div class="bg-white rounded-xl w-full max-w-lg max-h-[90vh] overflow-y-auto">
        <div class="p-6 border-b border-gray-100">
          <div class="flex justify-between items-center">
            <h3 class="text-lg font-bold">{{ editingUser ? '编辑用户' : '添加用户' }}</h3>
            <button @click="closeUserModal" class="text-gray-500 hover:text-gray-700">
              <i class="fa fa-times"></i>
            </button>
          </div>
        </div>
        
        <div class="p-6">
          <form @submit.prevent="saveUser">
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">用户头像 URL</label>
              <input type="text" v-model="form.userAvatar" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">姓名</label>
              <input type="text" v-model="form.userName" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">学号/工号</label>
              <input type="text" v-model="form.userStudentId" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">角色</label>
              <select v-model="form.userRole" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
                <option value="student">学生</option>
                <option value="teacher">教师</option>
                <option value="admin">管理员</option>
              </select>
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">联系方式</label>
              <input type="text" v-model="form.userContact" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">邮箱</label>
              <input type="email" v-model="form.userEmail" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">
            </div>
            
            <div class="mb-4">
              <label class="block text-sm font-medium text-gray-700 mb-1">备注</label>
              <textarea v-model="form.userNotes" rows="3" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"></textarea>
            </div>
            
            <div class="flex justify-end space-x-3 mt-6">
              <button type="button" @click="closeUserModal" class="px-4 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors">
                取消
              </button>
              <button type="submit" class="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors">
                保存
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
    
    <!-- 借阅详情模态框 -->
    <div v-if="isBorrowDetailsModalOpen" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" @click.self="closeBorrowDetailsModal">
      <div class="bg-white rounded-xl w-full max-w-lg max-h-[90vh] overflow-y-auto">
        <div class="p-6 border-b border-gray-100">
          <div class="flex justify-between items-center">
            <h3 class="text-lg font-bold">借阅详情</h3>
            <button @click="closeBorrowDetailsModal" class="text-gray-500 hover:text-gray-700">
              <i class="fa fa-times"></i>
            </button>
          </div>
        </div>
        
        <div class="p-6">
          <div class="flex items-center mb-6">
            <div class="w-20 h-20 rounded-lg bg-gray-200 overflow-hidden">
              <img :src="selectedBorrow.book.cover" alt="Book cover" class="w-full h-full object-cover">
            </div>
            <div class="ml-4">
              <h3 class="font-bold text-lg">{{ selectedBorrow.book.title }}</h3>
              <p class="text-gray-600 text-sm">{{ selectedBorrow.book.author }}</p>
              <p class="text-gray-500 text-xs mt-1">
                <span class="bg-gray-100 px-2 py-0.5 rounded text-xs">{{ selectedBorrow.book.category }}</span>
              </p>
            </div>
          </div>
          
          <div class="space-y-4">
            <div class="flex justify-between">
              <span class="text-gray-600">借阅人</span>
              <span class="font-medium">{{ selectedBorrow.user.name }} ({{ selectedBorrow.user.studentId }})</span>
            </div>
            <div class="flex justify-between">
              <span class="text-gray-600">借阅日期</span>
              <span class="font-medium">{{ selectedBorrow.borrowDate }}</span>
            </div>
            <div class="flex justify-between">
              <span class="text-gray-600">应归还日期</span>
              <span class="font-medium">{{ selectedBorrow.dueDate }}</span>
            </div>
            <div class="flex justify-between" v-if="selectedBorrow.returnDate">
              <span class="text-gray-600">实际归还日期</span>
              <span class="font-medium">{{ selectedBorrow.returnDate }}</span>
            </div>
            <div class="flex justify-between">
              <span class="text-gray-600">借阅状态</span>
              <span class="font-medium" :class="getStatusColor(selectedBorrow)">
                {{ getStatusText(selectedBorrow) }}
              </span>
            </div>
            <div class="flex justify-between" v-if="isOverdue(selectedBorrow.dueDate) && !selectedBorrow.isReturned">
              <span class="text-gray-600">逾期天数</span>
              <span class="font-medium text-red-500">{{ getOverdueDays(selectedBorrow.dueDate) }} 天</span>
            </div>
          </div>
          
          <div class="mt-6 pt-6 border-t border-gray-100">
            <h4 class="font-medium mb-3">借阅历史</h4>
            <div class="space-y-3">
              <div class="bg-gray-50 p-3 rounded-lg" v-for="history in getBorrowHistory(selectedBorrow.book.id)" :key="history.id">
                <div class="flex justify-between text-sm">
                  <span class="font-medium">{{ history.user.name }}</span>
                  <span class="text-gray-500">{{ history.borrowDate }} - {{ history.returnDate || '未归还' }}</span>
                </div>
                <div class="flex justify-between text-xs mt-1">
                  <span>{{ history.isReturned ? '已归还' : '借阅中' }}</span>
                  <span v-if="history.isReturned && history.returnDate > history.dueDate" class="text-red-500">
                    逾期 {{ getOverdueDays(history.dueDate, history.returnDate) }} 天
                  </span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    
    <!-- 确认对话框 -->
    <div v-if="isConfirmDialogOpen" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" @click.self="closeConfirmDialog">
      <div class="bg-white rounded-xl w-full max-w-md p-6">
        <h3 class="text-lg font-bold mb-3">{{ confirmDialogTitle }}</h3>
        <p class="text-gray-600 mb-6">{{ confirmDialogMessage }}</p>
        <div class="flex justify-end space-x-3">
          <button @click="closeConfirmDialog" class="px-4 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors">
            取消
          </button>
          <button @click="confirmAction" class="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors">
            确认
          </button>
        </div>
      </div>
    </div>
  </div>
  
  <script>
    const { createApp, ref, computed, onMounted } = Vue;
    
    createApp({
      setup() {
        // 导航相关
        const currentView = ref('borrows');
        const isScrolled = ref(false);
        const isMobileMenuOpen = ref(false);
        
        // 数据模型
        const books = ref([
          {
            id: 1,
            title: "Python数据分析实战",
            author: "李小明",
            publisher: "电子工业出版社",
            year: 2023,
            category: "计算机",
            isbn: "9787121456789",
            description: "本书全面介绍了Python在数据分析领域的应用,涵盖NumPy、Pandas、Matplotlib等库的使用。",
            cover: "https://picsum.photos/seed/python/200/300",
            isBorrowed: false
          },
          {
            id: 2,
            title: "Web前端开发技术",
            author: "王建国",
            publisher: "人民邮电出版社",
            year: 2022,
            category: "计算机",
            isbn: "9787115589012",
            description: "本书详细讲解了HTML、CSS、JavaScript等前端技术,以及Vue.js框架的使用。",
            cover: "https://picsum.photos/seed/web/200/300",
            isBorrowed: true
          },
          {
            id: 3,
            title: "数据结构与算法",
            author: "张教授",
            publisher: "清华大学出版社",
            year: 2021,
            category: "计算机",
            isbn: "9787302587463",
            description: "本书系统介绍了常用的数据结构和算法,适合计算机专业学生和从业人员阅读。",
            cover: "https://picsum.photos/seed/algorithm/200/300",
            isBorrowed: false
          },
          {
            id: 4,
            title: "平凡的世界",
            author: "路遥",
            publisher: "人民文学出版社",
            year: 2005,
            category: "文学",
            isbn: "9787020049297",
            description: "这部长篇小说以中国20世纪70年代中期到80年代中期十年间为背景,通过复杂的矛盾纠葛,以孙少安和孙少平两兄弟为中心,展示了普通人在大时代历史进程中所走过的艰难曲折的道路。",
            cover: "https://picsum.photos/seed/literature/200/300",
            isBorrowed: false
          },
          {
            id: 5,
            title: "明朝那些事儿",
            author: "当年明月",
            publisher: "中国友谊出版公司",
            year: 2009,
            category: "历史",
            isbn: "9787505725462",
            description: "《明朝那些事儿》主要讲述的是从1344年到1644年这三百年间关于明朝的一些故事。以史料为基础,以年代和具体人物为主线,并加入了小说的笔法,语言幽默风趣。",
            cover: "https://picsum.photos/seed/history/200/300",
            isBorrowed: true
          },
          {
            id: 6,
            title: "时间简史",
            author: "史蒂芬·霍金",
            publisher: "湖南科学技术出版社",
            year: 2018,
            category: "科学",
            isbn: "9787535794567",
            description: "《时间简史》自1988年首版以来,被翻译成40种文字,累计销售量突破2500万册,是畅销全世界的科学著作。",
            cover: "https://picsum.photos/seed/science/200/300",
            isBorrowed: false
          },
          {
            id: 7,
            title: "艺术的故事",
            author: "贡布里希",
            publisher: "广西美术出版社",
            year: 2016,
            category: "艺术",
            isbn: "9787549413866",
            description: "《艺术的故事》概括地叙述了从最早的洞窟绘画到当今的实验艺术的发展历程,以阐明艺术史是'各种传统不断迂回、不断改变的历史,每一件作品在这历史中都既回顾过去又导向未来'。",
            cover: "https://picsum.photos/seed/art/200/300",
            isBorrowed: false
          },
          {
            id: 8,
            title: "经济学原理",
            author: "N·格里高利·曼昆",
            publisher: "北京大学出版社",
            year: 2015,
            category: "经济",
            isbn: "9787301255891",
            description: "《经济学原理》分为微观经济学和宏观经济学两部分,是世界上最流行的经济学教材。",
            cover: "https://picsum.photos/seed/economics/200/300",
            isBorrowed: true
          }
        ]);
        
        const users = ref([
          {
            id: 1,
            name: "张三",
            studentId: "2022001",
            role: "student",
            contact: "13800138001",
            email: "[email protected]",
            avatar: "https://picsum.photos/seed/user1/200/200",
            notes: "计算机系大三学生,借阅记录良好",
            isBlocked: false
          },
          {
            id: 2,
            name: "李四",
            studentId: "2022002",
            role: "student",
            contact: "13900139002",
            email: "[email protected]",
            avatar: "https://picsum.photos/seed/user2/200/200",
            notes: "数学系大二学生,有逾期记录",
            isBlocked: false
          },
          {
            id: 3,
            name: "王五",
            studentId: "T2021001",
            role: "teacher",
            contact: "13700137003",
            email: "[email protected]",
            avatar: "https://picsum.photos/seed/user3/200/200",
            notes: "计算机系教授,经常借阅专业书籍",
            isBlocked: false
          },
          {
            id: 4,
            name: "赵六",
            studentId: "2022003",
            role: "student",
            contact: "13600136004",
            email: "[email protected]",
            avatar: "https://picsum.photos/seed/user4/200/200",
            notes: "历史系大一学生",
            isBlocked: false
          },
          {
            id: 5,
            name: "管理员",
            studentId: "A0001",
            role: "admin",
            contact: "13500135005",
            email: "[email protected]",
            avatar: "https://picsum.photos/seed/admin/200/200",
            notes: "系统管理员",
            isBlocked: false
          }
        ]);
        
        const borrows = ref([
          {
            id: 1,
            bookId: 2,
            userId: 1,
            borrowDate: "2025-06-01",
            dueDate: "2025-06-15",
            returnDate: null,
            isReturned: false
          },
          {
            id: 2,
            bookId: 5,
            userId: 4,
            borrowDate: "2025-05-20",
            dueDate: "2025-06-03",
            returnDate: null,
            isReturned: false
          },
          {
            id: 3,
            bookId: 8,
            userId: 3,
            borrowDate: "2025-05-10",
            dueDate: "2025-05-24",
            returnDate: "2025-05-25",
            isReturned: true
          },
          {
            id: 4,
            bookId: 1,
            userId: 2,
            borrowDate: "2025-05-15",
            dueDate: "2025-05-29",
            returnDate: "2025-05-30",
            isReturned: true
          },
          {
            id: 5,
            bookId: 2,
            userId: 3,
            borrowDate: "2025-04-10",
            dueDate: "2025-04-24",
            returnDate: "2025-04-20",
            isReturned: true
          }
        ]);
        
        // 搜索和筛选
        const bookSearchQuery = ref('');
        const bookCategoryFilter = ref('');
        const bookStatusFilter = ref('');
        
        const borrowSearchQuery = ref('');
        const borrowStatusFilter = ref('');
        
        const userSearchQuery = ref('');
        const userRoleFilter = ref('');
        const userStatusFilter = ref('');
        
        // 分页
        const currentPage = ref(1);
        const booksPerPage = ref(9);
        
        const currentBorrowPage = ref(1);
        const borrowsPerPage = ref(10);
        
        const currentUserPage = ref(1);
        const usersPerPage = ref(6);
        
        // 模态框
        const isBookModalOpen = ref(false);
        const isBorrowModalOpen = ref(false);
        const isUserModalOpen = ref(false);
        const isBorrowDetailsModalOpen = ref(false);
        const isConfirmDialogOpen = ref(false);
        
        const editingBook = ref(null);
        const editingUser = ref(null);
        const selectedBorrow = ref(null);
        
        const confirmDialogTitle = ref('');
        const confirmDialogMessage = ref('');
        let confirmCallback = null;
        
        // 表单数据
        const form = ref({
          bookId: null,
          bookCover: '',
          bookTitle: '',
          bookAuthor: '',
          bookPublisher: '',
          bookYear: '',
          bookCategory: '计算机',
          bookISBN: '',
          bookDescription: '',
          
          borrowBookId: '',
          borrowUserId: '',
          borrowDate: '',
          dueDate: '',
          
          userId: null,
          userAvatar: '',
          userName: '',
          userStudentId: '',
          userRole: 'student',
          userContact: '',
          userEmail: '',
          userNotes: ''
        });
        
        // 计算属性
        const filteredBooks = computed(() => {
          return books.value.filter(book => {
            const titleMatch = book.title.toLowerCase().includes(bookSearchQuery.value.toLowerCase());
            const authorMatch = book.author.toLowerCase().includes(bookSearchQuery.value.toLowerCase());
            const categoryMatch = bookCategoryFilter.value ? book.category === bookCategoryFilter.value : true;
            const statusMatch = bookStatusFilter.value === 'available' ? !book.isBorrowed : 
                               bookStatusFilter.value === 'borrowed' ? book.isBorrowed : true;
            
            return (titleMatch || authorMatch) && categoryMatch && statusMatch;
          });
        });
        
        const totalPages = computed(() => {
          return Math.ceil(filteredBooks.value.length / booksPerPage.value);
        });
        
        const paginatedBooks = computed(() => {
          const start = (currentPage.value - 1) * booksPerPage.value;
          const end = start + booksPerPage.value;
          return filteredBooks.value.slice(start, end);
        });
        
		const filteredBorrows = computed(() => {
		  return borrows.value
		    .map(borrow => {
		      const book = books.value.find(b => b.id === borrow.bookId);
		      const user = users.value.find(u => u.id === borrow.userId);
		      
		      // 添加调试信息
		      if (!book) {
		        console.warn('找不到对应的图书:', borrow);
		      }
		      if (!user) {
		        console.warn('找不到对应的用户:', borrow);
		      }
		      
		      return { ...borrow, book, user };
		    })
		    .filter(borrow => {
		      // 过滤掉没有关联图书或用户的记录
		      if (!borrow.book || !borrow.user) {
		        console.warn('过滤无效借阅记录:', borrow);
		        return false;
		      }
		      
		      // 应用搜索和筛选条件
		      const bookMatch = borrow.book.title.toLowerCase().includes(borrowSearchQuery.value.toLowerCase());
		      const userMatch = borrow.user.name.toLowerCase().includes(borrowSearchQuery.value.toLowerCase());
		      
		      let statusMatch = true;
		      if (borrowStatusFilter.value === 'borrowed') {
		        statusMatch = !borrow.isReturned && !isOverdue(borrow.dueDate);
		      } else if (borrowStatusFilter.value === 'returned') {
		        statusMatch = borrow.isReturned;
		      } else if (borrowStatusFilter.value === 'overdue') {
		        statusMatch = !borrow.isReturned && isOverdue(borrow.dueDate);
		      }
		      
		      return (bookMatch || userMatch) && statusMatch;
		    });
		});
        
        const totalBorrowPages = computed(() => {
          return Math.ceil(filteredBorrows.value.length / borrowsPerPage.value);
        });
        
        const paginatedBorrows = computed(() => {
          const start = (currentBorrowPage.value - 1) * borrowsPerPage.value;
          const end = start + borrowsPerPage.value;
          return filteredBorrows.value.slice(start, end);
        });
        
        const filteredUsers = computed(() => {
          return users.value.filter(user => {
            const nameMatch = user.name.toLowerCase().includes(userSearchQuery.value.toLowerCase());
            const idMatch = user.studentId.toLowerCase().includes(userSearchQuery.value.toLowerCase());
            const roleMatch = userRoleFilter.value ? user.role === userRoleFilter.value : true;
            const statusMatch = userStatusFilter.value === 'active' ? !user.isBlocked : 
                               userStatusFilter.value === 'blocked' ? user.isBlocked : true;
            
            return (nameMatch || idMatch) && roleMatch && statusMatch;
          });
        });
        
        const totalUserPages = computed(() => {
          return Math.ceil(filteredUsers.value.length / usersPerPage.value);
        });
        
        const paginatedUsers = computed(() => {
          const start = (currentUserPage.value - 1) * usersPerPage.value;
          const end = start + usersPerPage.value;
          return filteredUsers.value.slice(start, end);
        });
        
        const borrowedBooksCount = computed(() => {
          return books.value.filter(book => book.isBorrowed).length;
        });
        
        const overdueBooksCount = computed(() => {
          return borrows.value.filter(borrow => !borrow.isReturned && isOverdue(borrow.dueDate)).length;
        });
        
        const recentBorrows = computed(() => {
          return [...borrows.value]
            .sort((a, b) => new Date(b.borrowDate) - new Date(a.borrowDate))
            .slice(0, 5)
            .map(borrow => {
              return {
                ...borrow,
                book: books.value.find(b => b.id === borrow.bookId),
                user: users.value.find(u => u.id === borrow.userId)
              };
            });
        });
        
        const availableBooks = computed(() => {
          return books.value.filter(book => !book.isBorrowed);
        });
        
        // 方法
        const toggleMobileMenu = () => {
          isMobileMenuOpen.value = !isMobileMenuOpen.value;
        };
        
        const changeView = (view) => {
          currentView.value = view;
          isMobileMenuOpen.value = false;
        };
        
        const handleScroll = () => {
          if (window.scrollY > 10) {
            isScrolled.value = true;
          } else {
            isScrolled.value = false;
          }
        };
        
        const isOverdue = (dueDate, returnDate = new Date().toISOString().split('T')[0]) => {
          return dueDate && returnDate > dueDate;
        };
        
        const getOverdueDays = (dueDate, returnDate = new Date().toISOString().split('T')[0]) => {
          if (!isOverdue(dueDate, returnDate)) return 0;
          
          const due = new Date(dueDate);
          const ret = new Date(returnDate);
          const diffTime = Math.abs(ret - due);
          const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
          return diffDays;
        };
        
        const openBookModal = (book) => {
          editingBook.value = book;
          
          if (book) {
            form.value = {
              bookId: book.id,
              bookCover: book.cover,
              bookTitle: book.title,
              bookAuthor: book.author,
              bookPublisher: book.publisher,
              bookYear: book.year,
              bookCategory: book.category,
              bookISBN: book.isbn,
              bookDescription: book.description
            };
          } else {
            form.value = {
              bookId: null,
              bookCover: 'https://picsum.photos/seed/default/200/300',
              bookTitle: '',
              bookAuthor: '',
              bookPublisher: '',
              bookYear: '',
              bookCategory: '计算机',
              bookISBN: '',
              bookDescription: ''
            };
          }
          
          isBookModalOpen.value = true;
        };
        
        const closeBookModal = () => {
          isBookModalOpen.value = false;
        };
        
        const saveBook = () => {
          if (!form.value.bookTitle || !form.value.bookAuthor) {
            alert('请填写图书标题和作者');
            return;
          }
          
          if (editingBook.value) {
            // 更新现有图书
            const index = books.value.findIndex(b => b.id === form.value.bookId);
            if (index !== -1) {
              books.value[index] = {
                ...books.value[index],
                cover: form.value.bookCover,
                title: form.value.bookTitle,
                author: form.value.bookAuthor,
                publisher: form.value.bookPublisher,
                year: form.value.bookYear,
                category: form.value.bookCategory,
                isbn: form.value.bookISBN,
                description: form.value.bookDescription
              };
            }
          } else {
            // 添加新图书
            const newBook = {
              id: books.value.length > 0 ? Math.max(...books.value.map(b => b.id)) + 1 : 1,
              cover: form.value.bookCover,
              title: form.value.bookTitle,
              author: form.value.bookAuthor,
              publisher: form.value.bookPublisher,
              year: form.value.bookYear,
              category: form.value.bookCategory,
              isbn: form.value.bookISBN,
              description: form.value.bookDescription,
              isBorrowed: false
            };
            
            books.value.push(newBook);
          }
          
          isBookModalOpen.value = false;
          showToast(editingBook.value ? '图书更新成功' : '图书添加成功');
        };
        
        const deleteBook = (id) => {
          confirmDialogTitle.value = '确认删除';
          confirmDialogMessage.value = '确定要删除这本书吗?删除后将无法恢复。';
          confirmCallback = () => {
            const borrowExists = borrows.value.some(borrow => borrow.bookId === id);
            if (borrowExists) {
              alert('无法删除,这本书正在被借阅');
              return;
            }
            
            books.value = books.value.filter(book => book.id !== id);
            showToast('图书已删除');
          };
          isConfirmDialogOpen.value = true;
        };
        
        const openBorrowModal = () => {
          const today = new Date().toISOString().split('T')[0];
          const dueDate = new Date();
          dueDate.setDate(dueDate.getDate() + 14);
          
          form.value = {
            borrowBookId: '',
            borrowUserId: '',
            borrowDate: today,
            dueDate: dueDate.toISOString().split('T')[0]
          };
          
          isBorrowModalOpen.value = true;
        };
        
        const closeBorrowModal = () => {
          isBorrowModalOpen.value = false;
        };
        
        const saveBorrow = () => {
          if (!form.value.borrowBookId || !form.value.borrowUserId) {
            alert('请选择图书和用户');
            return;
          }
          
          if (form.value.borrowDate > form.value.dueDate) {
            alert('应归还日期不能早于借阅日期');
            return;
          }
          
          const book = books.value.find(b => b.id === parseInt(form.value.borrowBookId));
          if (book.isBorrowed) {
            alert('这本书已经被借出');
            return;
          }
          
          const newBorrow = {
            id: borrows.value.length > 0 ? Math.max(...borrows.value.map(b => b.id)) + 1 : 1,
            bookId: parseInt(form.value.borrowBookId),
            userId: parseInt(form.value.borrowUserId),
            borrowDate: form.value.borrowDate,
            dueDate: form.value.dueDate,
            returnDate: null,
            isReturned: false
          };
          
          borrows.value.push(newBorrow);
          
          // 更新图书状态
          const bookIndex = books.value.findIndex(b => b.id === newBorrow.bookId);
          if (bookIndex !== -1) {
            books.value[bookIndex].isBorrowed = true;
          }
          
          isBorrowModalOpen.value = false;
          isBorrowModalOpen.value = false;
          showToast('借阅记录已添加');
        };
        
        const returnBook = (borrow) => {
          confirmDialogTitle.value = '确认归还';
          confirmDialogMessage.value = `确定这本书已经归还了吗?`;
          confirmCallback = () => {
            const index = borrows.value.findIndex(b => b.id === borrow.id);
            if (index !== -1) {
              borrows.value[index].isReturned = true;
              borrows.value[index].returnDate = new Date().toISOString().split('T')[0];
              
              // 更新图书状态
              const bookIndex = books.value.findIndex(b => b.id === borrow.bookId);
              if (bookIndex !== -1) {
                books.value[bookIndex].isBorrowed = false;
              }
              
              showToast('图书已归还');
            }
          };
          isConfirmDialogOpen.value = true;
        };
        
        const openUserModal = (user) => {
          editingUser.value = user;
          
          if (user) {
            form.value = {
              userId: user.id,
              userAvatar: user.avatar,
              userName: user.name,
              userStudentId: user.studentId,
              userRole: user.role,
              userContact: user.contact,
              userEmail: user.email,
              userNotes: user.notes
            };
          } else {
            form.value = {
              userId: null,
              userAvatar: 'https://picsum.photos/seed/defaultuser/200/200',
              userName: '',
              userStudentId: '',
              userRole: 'student',
              userContact: '',
              userEmail: '',
              userNotes: ''
            };
          }
          
          isUserModalOpen.value = true;
        };
        
        const closeUserModal = () => {
          isUserModalOpen.value = false;
        };
        
        const saveUser = () => {
          if (!form.value.userName || !form.value.userStudentId) {
            alert('请填写用户名和学号/工号');
            return;
          }
          
          if (editingUser.value) {
            // 更新现有用户
            const index = users.value.findIndex(u => u.id === form.value.userId);
            if (index !== -1) {
              users.value[index] = {
                ...users.value[index],
                avatar: form.value.userAvatar,
                name: form.value.userName,
                studentId: form.value.userStudentId,
                role: form.value.userRole,
                contact: form.value.userContact,
                email: form.value.userEmail,
                notes: form.value.userNotes
              };
            }
          } else {
            // 添加新用户
            const newUser = {
              id: users.value.length > 0 ? Math.max(...users.value.map(u => u.id)) + 1 : 1,
              avatar: form.value.userAvatar,
              name: form.value.userName,
              studentId: form.value.userStudentId,
              role: form.value.userRole,
              contact: form.value.userContact,
              email: form.value.userEmail,
              notes: form.value.userNotes,
              isBlocked: false
            };
            
            users.value.push(newUser);
          }
          
          isUserModalOpen.value = false;
          showToast(editingUser.value ? '用户更新成功' : '用户添加成功');
        };
        
        const toggleUserBlock = (user) => {
          confirmDialogTitle.value = user.isBlocked ? '确认解封' : '确认封禁';
          confirmDialogMessage.value = user.isBlocked 
            ? `确定要解封用户 ${user.name} 吗?` 
            : `确定要封禁用户 ${user.name} 吗?封禁后用户将无法借阅图书。`;
          
          confirmCallback = () => {
            const index = users.value.findIndex(u => u.id === user.id);
            if (index !== -1) {
              users.value[index].isBlocked = !users.value[index].isBlocked;
              showToast(users.value[index].isBlocked ? '用户已封禁' : '用户已解封');
            }
          };
          
          isConfirmDialogOpen.value = true;
        };
        
        const viewBorrowDetails = (borrow) => {
          selectedBorrow.value = {
            ...borrow,
            book: books.value.find(b => b.id === borrow.bookId),
            user: users.value.find(u => u.id === borrow.userId)
          };
          
          isBorrowDetailsModalOpen.value = true;
        };
        
        const closeBorrowDetailsModal = () => {
          isBorrowDetailsModalOpen.value = false;
        };
        
        const closeConfirmDialog = () => {
          isConfirmDialogOpen.value = false;
          confirmCallback = null;
        };
        
        const confirmAction = () => {
          if (typeof confirmCallback === 'function') {
            confirmCallback();
          }
          closeConfirmDialog();
        };
        
        const getBorrowingCount = (userId) => {
          return borrows.value.filter(borrow => 
            borrow.userId === userId && !borrow.isReturned
          ).length;
        };
        
        const getReturnedCount = (userId) => {
          return borrows.value.filter(borrow => 
            borrow.userId === userId && borrow.isReturned
          ).length;
        };
        
        const getOverdueCount = (userId) => {
          return borrows.value.filter(borrow => 
            borrow.userId === userId && !borrow.isReturned && isOverdue(borrow.dueDate)
          ).length;
        };
        
        const getBorrowHistory = (bookId) => {
          return borrows.value
            .filter(borrow => borrow.bookId === bookId)
            .map(borrow => ({
              ...borrow,
              user: users.value.find(u => u.id === borrow.userId)
            }));
        };
        
        const getStatusText = (borrow) => {
          if (borrow.isReturned) return '已归还';
          if (isOverdue(borrow.dueDate)) return '已逾期';
          return '借阅中';
        };
        
        const getStatusColor = (borrow) => {
          if (borrow.isReturned) return 'text-green-600';
          if (isOverdue(borrow.dueDate)) return 'text-red-600';
          return 'text-blue-600';
        };
        
        const prevPage = () => {
          if (currentPage.value > 1) {
            currentPage.value--;
          }
        };
        
        const nextPage = () => {
          if (currentPage.value < totalPages.value) {
            currentPage.value++;
          }
        };
        
        const prevBorrowPage = () => {
          if (currentBorrowPage.value > 1) {
            currentBorrowPage.value--;
          }
        };
        
        const nextBorrowPage = () => {
          if (currentBorrowPage.value < totalBorrowPages.value) {
            currentBorrowPage.value++;
          }
        };
        
        const prevUserPage = () => {
          if (currentUserPage.value > 1) {
            currentUserPage.value--;
          }
        };
        
        const nextUserPage = () => {
          if (currentUserPage.value < totalUserPages.value) {
            currentUserPage.value++;
          }
        };
        
        const showToast = (message) => {
          const toast = document.createElement('div');
          toast.className = 'fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded-lg shadow-lg z-50 transform transition-all duration-300 translate-y-20 opacity-0';
          toast.textContent = message;
          document.body.appendChild(toast);
          
          setTimeout(() => {
            toast.classList.remove('translate-y-20', 'opacity-0');
          }, 100);
          
          setTimeout(() => {
            toast.classList.add('translate-y-20', 'opacity-0');
            setTimeout(() => {
              document.body.removeChild(toast);
            }, 300);
          }, 3000);
        };
        
        // 初始化图表
        const initCharts = () => {
          // 借阅趋势图表
          const borrowCtx = document.getElementById('borrowChart');
          if (borrowCtx) {
            new Chart(borrowCtx, {
              type: 'line',
              data: {
                labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
                datasets: [{
                  label: '借阅数量',
                  data: [12, 19, 15, 17, 20, 14, 16],
                  borderColor: '#3B82F6',
                  backgroundColor: 'rgba(59, 130, 246, 0.1)',
                  tension: 0.4,
                  fill: true
                }, {
                  label: '归还数量',
                  data: [8, 15, 10, 14, 18, 12, 13],
                  borderColor: '#10B981',
                  backgroundColor: 'rgba(16, 185, 129, 0.1)',
                  tension: 0.4,
                  fill: true
                }]
              },
              options: {
                responsive: true,
                maintainAspectRatio: false,
                plugins: {
                  legend: {
                    position: 'top',
                  }
                },
                scales: {
                  y: {
                    beginAtZero: true
                  }
                }
              }
            });
          }
          
          // 分类统计图表
          const categoryCtx = document.getElementById('categoryChart');
          if (categoryCtx) {
            new Chart(categoryCtx, {
              type: 'doughnut',
              data: {
                labels: ['计算机', '文学', '历史', '科学', '艺术', '经济'],
                datasets: [{
                  data: [25, 20, 15, 12, 10, 18],
                  backgroundColor: [
                    '#3B82F6',
                    '#6366F1',
                    '#8B5CF6',
                    '#EC4899',
                    '#F59E0B',
                    '#10B981'
                  ],
                  borderWidth: 0
                }]
              },
              options: {
                responsive: true,
                maintainAspectRatio: false,
                plugins: {
                  legend: {
                    position: 'bottom',
                  }
                },
                cutout: '70%'
              }
            });
          }
        };
        
        // 生命周期钩子
        onMounted(() => {
          window.addEventListener('scroll', handleScroll);
          initCharts();
        });
        
        return {
          // 数据
          currentView,
          isScrolled,
          isMobileMenuOpen,
          books,
          users,
          borrows,
          bookSearchQuery,
          bookCategoryFilter,
          bookStatusFilter,
          borrowSearchQuery,
          borrowStatusFilter,
          userSearchQuery,
          userRoleFilter,
          userStatusFilter,
          currentPage,
          booksPerPage,
          currentBorrowPage,
          borrowsPerPage,
          currentUserPage,
          usersPerPage,
          isBookModalOpen,
          isBorrowModalOpen,
          isUserModalOpen,
          isBorrowDetailsModalOpen,
          isConfirmDialogOpen,
          editingBook,
          editingUser,
          selectedBorrow,
          confirmDialogTitle,
          confirmDialogMessage,
          form,
          
          // 计算属性
          filteredBooks,
          totalPages,
          paginatedBooks,
          filteredBorrows,
          totalBorrowPages,
          paginatedBorrows,
          filteredUsers,
          totalUserPages,
          paginatedUsers,
          borrowedBooksCount,
          overdueBooksCount,
          recentBorrows,
          availableBooks,
          
          // 方法
          toggleMobileMenu,
          changeView,
          handleScroll,
          isOverdue,
          getOverdueDays,
          openBookModal,
          closeBookModal,
          saveBook,
          deleteBook,
          openBorrowModal,
          closeBorrowModal,
          saveBorrow,
          returnBook,
          openUserModal,
          closeUserModal,
          saveUser,
          toggleUserBlock,
          viewBorrowDetails,
          closeBorrowDetailsModal,
          closeConfirmDialog,
          confirmAction,
          getBorrowingCount,
          getReturnedCount,
          getOverdueCount,
          getBorrowHistory,
          getStatusText,
          getStatusColor,
          prevPage,
          nextPage,
          prevBorrowPage,
          nextBorrowPage,
          prevUserPage,
          nextUserPage,
          showToast
        };
      }
    }).mount('#app');
  </script>
</body>
</html>

(三)系统界面效果

1.仪表盘

系统默认界面(仪表盘视图)的效果如下:

2.图书管理

3.借阅管理

4.用户管理

以下是对图书管理系统界面设计代码的详细解析:

、整体架构

此图书管理系统前端基于Vue.js构建,采用单页面应用(SPA)的架构。HTML文件为入口,引入各类外部库与资源,借助Vue.js动态渲染页面内容。系统运用响应式设计,能适配不同屏幕尺寸。

(一)功能模块

  1. 导航栏模块:包含系统标题、主菜单、搜索框与用户图标,支持移动端菜单展开与收缩。
  2. 仪表盘模块:展示系统关键统计数据,如总藏书量、借出图书、注册用户和逾期未还数量,还有借阅趋势与分类统计图表,以及最近借阅记录。
  3. 图书管理模块:可搜索、筛选图书,添加、编辑和删除图书信息,支持分页显示。
  4. 借阅管理模块:能搜索、筛选借阅记录,新增借阅,处理图书归还,支持分页显示。
  5. 用户管理模块:可搜索、筛选用户,添加、编辑用户信息,封禁和解封用户,支持分页显示。
  6. 页脚模块:显示系统标题与版权信息、社交链接。
  7. 模态框模块:用于添加/编辑图书、新增借阅、添加/编辑用户、借阅详情、确认对话框。

(二)代码树形结构

整个系统的界面采用单页面应用(SPA)的架构,即只有一个index.html。以下是index.html页面的树形结构图:

index.html

├── <head>

│ ├── 元数据与页面标题

│ ├── 引入外部库(Tailwind CSS、Font Awesome、Chart.js、Vue.js)

│ ├── Tailwind CSS 配置

│ └── 自定义样式

├── <body>

│ ├── <div id="app">

│ ├── <nav> 导航栏

│ │ ├── 系统标题

│ │ ├── 主菜单(桌面端)

│ │ ├── 搜索框

│ │ ├── 用户图标

│ │ └── 移动端菜单

│ ├── <main> 主内容区

│ │ ├── <div v-if="currentView === 'dashboard'"> 仪表盘视图

│ │ │ ├── 统计卡片

│ │ │ ├── 图表区域

│ │ │ └── 最近借阅记录

│ │ ├── <div v-if="currentView === 'books'"> 图书管理视图

│ │ │ ├── 搜索和筛选

│ │ │ ├── 图书列表

│ │ │ └── 分页

│ │ ├── <div v-if="currentView === 'borrows'"> 借阅管理视图

│ │ │ ├── 搜索和筛选

│ │ │ ├── 借阅记录表格

│ │ │ └── 分页

│ │ └── <div v-if="currentView === 'users'"> 用户管理视图

│ │ ├── 搜索和筛选

│ │ ├── 用户列表

│ │ └── 分页

│ ├── <footer> 页脚

│ │ ├── 系统标题与版权信息

│ │ └── 社交链接

│ └── 模态框部分

│ ├── <div v-if="isBookModalOpen"> 添加/编辑图书模态框

│ │ ├── 模态框标题栏

│ │ │ ├── 标题文本

│ │ │ └── 关闭按钮

│ │ └── 图书表单

│ │ ├── 图书封面URL输入

│ │ ├── 图书标题输入

│ │ ├── 作者输入

│ │ ├── 出版社输入

│ │ ├── 出版年份输入

│ │ ├── 分类选择

│ │ ├── ISBN输入

│ │ ├── 简介文本框

│ │ └── 操作按钮

│ │ ├── 取消按钮

│ │ └── 保存按钮

│ ├── <div v-if="isBorrowModalOpen"> 新增借阅模态框

│ │ ├── 模态框标题栏

│ │ │ ├── 标题文本

│ │ │ └── 关闭按钮

│ │ └── 借阅表单

│ │ ├── 选择图书下拉框

│ │ ├── 选择用户下拉框

│ │ ├── 借阅日期选择

│ │ ├── 应归还日期选择

│ │ └── 操作按钮

│ │ ├── 取消按钮

│ │ └── 保存按钮

│ ├── <div v-if="isUserModalOpen"> 添加/编辑用户模态框

│ │ ├── 模态框标题栏

│ │ │ ├── 标题文本

│ │ │ └── 关闭按钮

│ │ └── 用户表单

│ │ ├── 用户头像URL输入

│ │ ├── 姓名输入

│ │ ├── 学号/工号输入

│ │ ├── 角色选择

│ │ ├── 联系方式输入

│ │ ├── 邮箱输入

│ │ ├── 备注文本框

│ │ └── 操作按钮

│ │ ├── 取消按钮

│ │ └── 保存按钮

│ ├── <div v-if="isBorrowDetailsModalOpen"> 借阅详情模态框

│ │ ├── 模态框标题栏

│ │ │ ├── 标题文本

│ │ │ └── 关闭按钮

│ │ ├── 图书信息

│ │ │ ├── 图书封面

│ │ │ ├── 图书标题

│ │ │ ├── 作者

│ │ │ └── 分类标签

│ │ ├── 借阅详情

│ │ │ ├── 借阅人信息

│ │ │ ├── 借阅日期

│ │ │ ├── 应归还日期

│ │ │ ├── 实际归还日期

│ │ │ ├── 借阅状态

│ │ │ └── 逾期天数(如适用)

│ │ └── 借阅历史

│ │ └── 历史记录列表

│ │ ├── 借阅人

│ │ ├── 借阅时间范围

│ │ └── 状态

│ └── <div v-if="isConfirmDialogOpen"> 确认对话框

│ ├── 标题

│ ├── 确认信息

│ └── 操作按钮

│ ├── 取消按钮

│ └── 确认按钮

、代码详细解析

HTML页面,基本是包含两个部分,head和body。

(一)<head> 部分

Head部分的代码如下:

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>图书管理系统</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css" rel="stylesheet">
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js"></script>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
  
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            primary: '#3B82F6',
            secondary: '#6366F1',
            accent: '#F59E0B',
            neutral: '#6B7280',
            success: '#10B981',
            warning: '#F59E0B',
            danger: '#EF4444',
          },
          fontFamily: {
            inter: ['Inter', 'sans-serif'],
          },
        },
      }
    }
  </script>
  
  <style type="text/tailwindcss">
    @layer utilities {
      .content-auto {
        content-visibility: auto;
      }
      .card-shadow {
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
      }
      .transition-custom {
        transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      }
      .scale-hover {
        transition: transform 0.2s ease-in-out;
      }
      .scale-hover:hover {
        transform: scale(1.02);
      }
    }
  </style>
</head>

以下是详细的代码说明:

1. 元数据与页面标题:

(1)meta charset="UTF-8":设定字符编码为UTF - 8。

(2)meta name="viewport" content="width=device-width, initial-scale=1.0":确保页面在移动设备上正确显示。

(3)<title>图书管理系统</title>:设置页面标题。

2. 引入外部库:

(1)Tailwind CSS:用于快速构建响应式UI。

(2)Font Awesome:提供图标库。

(3)Chart.js:用于绘制图表。

(4)Vue.js:构建交互式界面的JavaScript框架。

(5)Google Fonts:引入Inter字体。

3. Tailwind CSS配置:

扩展颜色和字体家族,方便在HTML中使用自定义类名。

4. 自定义样式:

定义自定义实用类,如卡片阴影、过渡效果和悬停缩放效果。

(二)<body> 部分

<body> 部分包含了一个id为app的<div> ,这是Vue.js应用的挂载点,整个图书管理系统的前端界面都将在这个容器内渲染。以下是对body部分各模块的详细解析,其中第一部分是导航栏。如上代码树形结构可以看出来,

<body>

├── <div id="app">

├── <nav> 导航栏

│ ├── 系统标题

│ ├── 主菜单(桌面端)

│ ├── 搜索框

│ ├── 用户图标

│ └── 移动端菜单

├── <main> 主内容区

1. 导航栏 (<nav>)

在body中的<div id="app">下第一个模块为导航栏<nav>,<nav>里包含了两大部分,桌面端菜单(电脑的浏览器)和移动端菜单。

复制代码
<nav class="bg-white shadow-md sticky top-0 z-50 transition-all duration-300" :class="{'bg-primary/95 text-white': isScrolled}">

  <!-- 导航栏内容 -->

</nav>

导航栏固定在页面顶部,滚动时背景颜色会改变。

滚动后的效果:

1 <nav> 样式与布局:

  1. bg-white shadow-md sticky top-0 z-50:设置导航栏背景为白色,添加阴影效果,使其固定在页面顶部,并设置较高的层叠顺序。
  2. transition-all duration-300:添加过渡效果,使导航栏样式变化更平滑。
  3. :class="{'bg-primary/95 text-white': isScrolled}":这是 Vue.js 的动态类绑定。当 isScrolled 为 true 时,导航栏背景变为半透明的主色调,文字变为白色。

2 导航栏内容:

导航栏由系统标题、主菜单(桌面端)、搜索框、用户图标和移动端菜单组成。代码树形结构如下:

<nav> 导航栏

├── 系统标题

├── 主菜单(桌面端)

├── 搜索框

├── 用户图标

└── 移动端菜单

导航栏内容都放在以下div中。而这个div是放在<nav>中。

复制代码
<div class="container mx-auto px-4 py-3 flex justify-between items-center">

  <!-- 系统标题、主菜单和搜索框、用户图标 -->

</div>

1系统标题:

复制代码
<div class="flex items-center space-x-2">

  <i class="fa fa-book text-2xl text-primary"></i>

  <span class="text-xl font-bold">图书管理系统</span>

</div>

使用Font Awesome图标和文字展示系统标题。

2主菜单(桌面端):

放在"系统标题"的div之后。

复制代码
<div class="hidden md:flex items-center space-x-6">

  <a href="#" class="font-medium hover:text-primary transition-colors" :class="{'text-primary': currentView === 'dashboard'}">

<i class="fa fa-tachometer mr-1"></i>仪表盘

  </a>

  <a href="#" class="font-medium hover:text-primary transition-colors" :class="{'text-primary': currentView === 'books'}">

<i class="fa fa-book mr-1"></i>图书管理

  </a>

  <a href="#" class="font-medium hover:text-primary transition-colors" :class="{'text-primary': currentView === 'borrows'}">

<i class="fa fa-exchange mr-1"></i>借阅管理

  </a>

  <a href="#" class="font-medium hover:text-primary transition-colors" :class="{'text-primary': currentView === 'users'}">

<i class="fa fa-users mr-1"></i>用户管理

  </a>

</div>
  1. hidden md:flex:在小屏幕设备上隐藏,在中等及以上屏幕设备上显示。
  2. :class="{'text-primary': currentView === 'dashboard'}":根据当前视图 currentView 的值,动态设置菜单项的文字颜色。

3搜索框和用户图标:

放在"主菜单"之后。

复制代码
<div class="flex items-center space-x-4">

  <div class="relative hidden md:block">

<input type="text" placeholder="搜索图书..." class="pl-9 pr-4 py-2 rounded-full bg-gray-100 focus:outline-none focus:ring-2 focus:ring-primary/50 w-48 transition-all duration-300 focus: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">

<button class="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center hover:bg-gray-300 transition-colors">

  <i class="fa fa-user"></i>

</button>

  </div>

  <!-- 移动端菜单按钮 -->

  <button class="md:hidden" @click="toggleMobileMenu">

<i class="fa fa-bars text-xl"></i>

  </button>

</div>
  1. 在中等及以上屏幕设备上显示,输入框获得焦点时会有动画效果。
  2. 显示用户图标,悬停时背景颜色变化。
  3. 移动端菜单按钮:在小屏幕设备上显示,点击时调用 toggleMobileMenu 方法切换移动端菜单的显示状态。

3 移动端菜单:

放在以下div中。而这个div是放在<nav>中,与桌面端导航栏对齐。

复制代码
<div class="md:hidden bg-white border-t border-gray-100 shadow-lg absolute w-full left-0 transition-all duration-300 transform" :class="{'translate-y-0': isMobileMenuOpen, '-translate-y-full': !isMobileMenuOpen}">

  <!-- 移动端菜单项 -->

</div>
  1. md:hidden:在中等及以上屏幕设备上隐藏。
  2. :class="{'translate-y-0': isMobileMenuOpen, '-translate-y-full': !isMobileMenuOpen}":根据 isMobileMenuOpen 的值,控制移动端菜单的显示与隐藏,使用过渡效果实现滑动动画。

移动端菜单项内容如下:

复制代码
<div class="container mx-auto px-4 py-2">

  <div class="flex flex-col space-y-3 py-2">

<a href="#" class="py-2 px-3 hover:bg-gray-100 rounded-lg transition-colors" :class="{'bg-primary/10 text-primary': currentView === 'dashboard'}">

  <i class="fa fa-tachometer mr-2"></i>仪表盘

</a>

<a href="#" class="py-2 px-3 hover:bg-gray-100 rounded-lg transition-colors" :class="{'bg-primary/10 text-primary': currentView === 'books'}">

  <i class="fa fa-book mr-2"></i>图书管理

</a>

<a href="#" class="py-2 px-3 hover:bg-gray-100 rounded-lg transition-colors" :class="{'bg-primary/10 text-primary': currentView === 'borrows'}">

  <i class="fa fa-exchange mr-2"></i>借阅管理

</a>

<a href="#" class="py-2 px-3 hover:bg-gray-100 rounded-lg transition-colors" :class="{'bg-primary/10 text-primary': currentView === 'users'}">

  <i class="fa fa-users mr-2"></i>用户管理

</a>

<div class="relative">

  <input type="text" placeholder="搜索图书..." class="w-full pl-9 pr-4 py-2 rounded-lg bg-gray-100 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>

</div>

<body>部分之 主内容区 (<main>)

接着<nav>继续实现body中的其他内容。放在<main>标签中。<main>与<nav>对齐。如上代码树形结构可以看出来。

<body>

├── <div id="app">

├── <nav> 导航栏

├── <main> 主内容区

复制代码
<main class="flex-grow container mx-auto px-4 py-6">

  <!-- 仪表盘视图、图书管理视图、借阅管理视图、用户管理视图 -->

</main>

以下为主内容区 (<main>),包括了仪表盘视图图书管理视图借阅管理视图用户管理视图4个部分。代码树形结构如下:

<main> 主内容区

├── <div v-if="currentView === 'dashboard'"> 仪表盘视图

│ ├── 统计卡片

│ ├── 图表区域

│ └── 最近借阅记录

├── <div v-if="currentView === 'books'"> 图书管理视图

│ ├── 搜索和筛选

│ ├── 图书列表

│ └── 分页

├── <div v-if="currentView === 'borrows'"> 借阅管理视图

│ ├── 搜索和筛选

│ ├── 借阅记录表格

│ └── 分页

└── <div v-if="currentView === 'users'"> 用户管理视图

├── 搜索和筛选

├── 用户列表

└── 分页

1. 仪表盘视图 (<div v-if="currentView === 'dashboard'">)

仪表盘视图是<main>中的第一部分。

复制代码
<div v-if="currentView === 'dashboard'">

  <div class="mb-8">

      <h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold mb-2">仪表盘</h1>

      <p class="text-gray-600">欢迎使用图书管理系统,以下是系统概览</p>

  </div>

<!-- 仪表盘内容 -->

</div>

v-if 是Vue.js的条件渲染指令,当 currentView 为 'dashboard' 时显示该部分内容。

仪表盘视图又分为上中下三部分,分别是统计卡片、图表区域和最近借阅记录。仪表盘的界面效果如下:

(1) 统计卡片:

统计卡片是仪表盘视图中的第一部分。

复制代码
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">

  <div class="bg-white rounded-xl p-6 card-shadow scale-hover">

<div class="flex justify-between items-start">

  <div>

<p class="text-gray-500 text-sm">总藏书量</p>

<h3 class="text-3xl font-bold mt-1">{{ books.length }}</h3>

<p class="text-success text-sm mt-2">

  <i class="fa fa-arrow-up"></i> 5.2% <span class="text-gray-500">较上月</span>

</p>

  </div>

  <div class="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">

<i class="fa fa-book text-primary text-xl"></i>

  </div>

</div>

  </div>

  

  <div class="bg-white rounded-xl p-6 card-shadow scale-hover">

<div class="flex justify-between items-start">

  <div>

<p class="text-gray-500 text-sm">借出图书</p>

<h3 class="text-3xl font-bold mt-1">{{ borrowedBooksCount }}</h3>

<p class="text-danger text-sm mt-2">

  <i class="fa fa-arrow-down"></i> 2.8% <span class="text-gray-500">较上月</span>

</p>

  </div>

  <div class="w-12 h-12 rounded-full bg-accent/10 flex items-center justify-center">

<i class="fa fa-exchange text-accent text-xl"></i>

  </div>

</div>

  </div>

  

  <div class="bg-white rounded-xl p-6 card-shadow scale-hover">

<div class="flex justify-between items-start">

  <div>

<p class="text-gray-500 text-sm">注册用户</p>

<h3 class="text-3xl font-bold mt-1">{{ users.length }}</h3>

<p class="text-success text-sm mt-2">

  <i class="fa fa-arrow-up"></i> 12.3% <span class="text-gray-500">较上月</span>

</p>

  </div>

  <div class="w-12 h-12 rounded-full bg-secondary/10 flex items-center justify-center">

<i class="fa fa-users text-secondary text-xl"></i>

  </div>

</div>

  </div>

  

  <div class="bg-white rounded-xl p-6 card-shadow scale-hover">

<div class="flex justify-between items-start">

  <div>

<p class="text-gray-500 text-sm">逾期未还</p>

<h3 class="text-3xl font-bold mt-1">{{ overdueBooksCount }}</h3>

<p class="text-warning text-sm mt-2">

  <i class="fa fa-arrow-up"></i> 3.1% <span class="text-gray-500">较上月</span>

</p>

  </div>

  <div class="w-12 h-12 rounded-full bg-danger/10 flex items-center justify-center">

<i class="fa fa-calendar-times-o text-danger text-xl"></i>

  </div>

</div>

  </div>

</div>

使用网格布局显示统计卡片,每个卡片显示不同的统计信息,如总藏书量借出图书注册用户逾期未还数量 。{{ books.length }} 是Vue.js的插值表达式,用于显示 books 数组的长度。

(2) 图表区域:

图表区域是仪表盘视图中的第二部分。

复制代码
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">

  <div class="bg-white rounded-xl p-6 card-shadow lg:col-span-2">

    <div class="flex justify-between items-center mb-6">

      <h3 class="font-bold text-lg">借阅趋势</h3>

      <div class="flex space-x-2">

        <button class="px-3 py-1 text-sm rounded-md bg-primary/10 text-primary">周</button>

        <button class="px-3 py-1 text-sm rounded-md hover:bg-gray-100">月</button>

        <button class="px-3 py-1 text-sm rounded-md hover:bg-gray-100">年</button>

      </div>

    </div>

    <div class="h-80">

      <canvas id="borrowChart"></canvas>

    </div>

  </div>

  <div class="bg-white rounded-xl p-6 card-shadow">

    <div class="flex justify-between items-center mb-6">

      <h3 class="font-bold text-lg">分类统计</h3>

      <button class="text-primary hover:text-primary/80">

        <i class="fa fa-refresh"></i>

      </button>

    </div>

    <div class="h-80">

      <canvas id="categoryChart"></canvas>

    </div>

  </div>

</div>

使用网格布局显示两个图表区域,分别是借阅趋势分类统计 ,使用 canvas 元素绘制图表。

(3) 最近借阅记录:

最近借阅记录是仪表盘视图中的第三部分。

复制代码
<div class="bg-white rounded-xl p-6 card-shadow">

  <div class="flex justify-between items-center mb-6">

<h3 class="font-bold text-lg">最近借阅记录</h3>

<button class="text-primary hover:text-primary/80">查看全部</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">图书</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 v-for="borrow in recentBorrows" :key="borrow.id" class="hover:bg-gray-50 transition-colors">

  <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="borrow.book.cover" alt="">

  </div>

  <div class="ml-4">

<div class="text-sm font-medium text-gray-900">{{ borrow.book.title }}</div>

<div class="text-sm text-gray-500">{{ borrow.book.author }}</div>

  </div>

</div>

  </td>

  <td class="px-6 py-4 whitespace-nowrap">

<div class="text-sm font-medium text-gray-900">{{ borrow.user?.name || '未知用户' }}</div>

<div class="text-sm text-gray-500">{{ borrow.user?.studentId || '未知ID' }}</div>

  </td>

  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">

{{ borrow.borrowDate }}

  </td>

  <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">

{{ borrow.dueDate }}

  </td>

  <td class="px-6 py-4 whitespace-nowrap">

<span v-if="borrow.isReturned" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">

  已归还

</span>

<span v-else-if="isOverdue(borrow.dueDate)" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">

  已逾期

</span>

<span v-else class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">

  借阅中

</span>

  </td>

</tr>

  </tbody>

</table>

  </div>

</div>

使用表格显示最近借阅记录,v-for是Vue.js的列表渲染指令,用于遍历 recentBorrows 数组并渲染表格行。

2. 图书管理视图 (<div v-if="currentView === 'books'">)

图书管理视图是<main>中的第二部分。

复制代码
<div v-if="currentView === 'books'">

  <div class="mb-8">

      <h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold mb-2">图书管理</h1>

      <p class="text-gray-600">管理系统中的所有图书信息</p>

  </div>

<!-- 图书管理内容 -->

</div>

当 currentView 为 'books' 时显示该部分内容。

可以在index.html中搜索const currentView = ref('dashboard');将其中的'dashboard'改为'books' ,再刷新页面,即可看到内容。图书管理视图分为搜索和筛选、图书列表、分页三个部分。界面效果如下:

(1) 搜索和筛选:

搜索和筛选是图书管理视图中的第一部分。

复制代码
<div class="bg-white rounded-xl p-6 card-shadow mb-6">

  <div class="grid grid-cols-1 md:grid-cols-4 gap-4">

<div class="relative">

  <input type="text" v-model="bookSearchQuery" placeholder="搜索图书标题/作者" class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

  <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>

</div>

<div>

  <select v-model="bookCategoryFilter" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

<option value="">所有分类</option>

<option value="计算机">计算机</option>

<option value="文学">文学</option>

<option value="历史">历史</option>

<option value="科学">科学</option>

<option value="艺术">艺术</option>

<option value="经济">经济</option>

  </select>

</div>

<div>

  <select v-model="bookStatusFilter" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

<option value="">所有状态</option>

<option value="available">可借阅</option>

<option value="borrowed">已借出</option>

  </select>

</div>

<div class="flex justify-end">

  <button @click="openBookModal(null)" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg flex items-center transition-colors">

<i class="fa fa-plus mr-2"></i> 添加图书

  </button>

</div>

  </div>

</div>

提供搜索框和下拉选择框用于筛选图书,v-model是Vue.js的双向数据绑定指令,将输入框和下拉选择框的值与Vue实例中的数据绑定。点击"添加图书"按钮调用openBookModal方法,弹出添加图书模态窗。

(2) 图书列表:

图书列表是图书管理视图中的第二部分。

复制代码
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">

  <div v-for="book in filteredBooks" :key="book.id" class="bg-white rounded-xl overflow-hidden card-shadow scale-hover">

<div class="flex">

  <div class="w-1/3 bg-gray-200">

<img :src="book.cover" alt="Book cover" class="w-full h-full object-cover">

  </div>

  <div class="w-2/3 p-4">

<h3 class="font-bold text-lg mb-1 line-clamp-1">{{ book.title }}</h3>

<p class="text-gray-600 text-sm mb-1 line-clamp-1">{{ book.author }}</p>

<p class="text-gray-500 text-xs mb-3">

  <span class="bg-gray-100 px-2 py-0.5 rounded text-xs">{{ book.category }}</span>

</p>

<div class="flex justify-between items-center mt-auto">

  <span v-if="book.isBorrowed" class="text-xs px-2 py-1 bg-red-100 text-red-800 rounded-full">

已借出

  </span>

  <span v-else class="text-xs px-2 py-1 bg-green-100 text-green-800 rounded-full">

可借阅

  </span>

  <div class="flex space-x-1">

<button @click="openBookModal(book)" class="p-1.5 rounded-full hover:bg-gray-100 text-gray-600 transition-colors">

  <i class="fa fa-edit"></i>

</button>

<button @click="deleteBook(book.id)" class="p-1.5 rounded-full hover:bg-red-100 text-red-600 transition-colors">

  <i class="fa fa-trash"></i>

</button>

  </div>

</div>

  </div>

</div>

  </div>

</div>

使用网格布局显示图书列表,v-for 遍历 filteredBooks 数组并渲染图书卡片。点击"编辑"按钮调用 openBookModal 方法,弹出编辑图书模态窗,点击"删除"按钮调用 deleteBook 方法,弹出确认删除图书模态窗。

(3) 分页:

分页是图书管理视图中的第三部分。

复制代码
<div class="mt-8 flex justify-between items-center">

  <div class="text-sm text-gray-500">

    显示 {{ (currentPage - 1) * booksPerPage + 1 }} 到 {{ Math.min(currentPage * booksPerPage, filteredBooks.length) }} 共 {{ filteredBooks.length }} 条记录

  </div>

  <div class="flex space-x-1">

    <button @click="prevPage" :disabled="currentPage === 1" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">

      <i class="fa fa-chevron-left"></i>

    </button>

    <button v-for="page in totalPages" :key="page" @click="currentPage = page" :class="{'bg-primary text-white border-primary': page === currentPage, 'border-gray-300 text-gray-600 hover:bg-gray-50': page !== currentPage}" class="px-3 py-1 rounded border transition-colors">

      {{ page }}

    </button>

    <button @click="nextPage" :disabled="currentPage === totalPages" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">

      <i class="fa fa-chevron-right"></i>

    </button>

  </div>

</div>

提供分页功能,显示当前页码和总记录数。点击"上一页"和"下一页"按钮分别调用 prevPage 和 nextPage 方法,点击页码按钮更新 currentPage 的值。

3. 借阅管理视图 (<div v-if="currentView === 'borrows'">)

借阅管理视图是<main>中的第三部分。

复制代码
<div v-if="currentView === 'borrows'">

  <div class="mb-8">

      <h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold mb-2">借阅管理</h1>

      <p class="text-gray-600">管理图书的借阅和归还</p>

  </div>

<!-- 借阅管理内容 -->

</div>

当 currentView 为 'borrows' 时显示该部分内容。

可以在index.html中搜索const currentView = ref('dashboard');将其中的'dashboard'改为'borrows' ,再刷新页面,即可看到内容。借阅管理视图分为搜索和筛选、借阅记录列表、分页三个部分。界面效果如下:

(1) 搜索和筛选:

搜索和筛选是借阅管理视图中的第一部分。

复制代码
<div class="bg-white rounded-xl p-6 card-shadow mb-6">

  <div class="grid grid-cols-1 md:grid-cols-4 gap-4">

<div class="relative">

  <input type="text" v-model="borrowSearchQuery" placeholder="搜索图书/借阅人" class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

  <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>

</div>

<div>

  <select v-model="borrowStatusFilter" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

<option value="">所有状态</option>

<option value="borrowed">借阅中</option>

<option value="returned">已归还</option>

<option value="overdue">已逾期</option>

  </select>

</div>

<div>

  <div class="flex items-center space-x-2">

<button class="w-full px-4 py-2 rounded-lg border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

  <i class="fa fa-calendar mr-2"></i> 时间范围

</button>

  </div>

</div>

<div class="flex justify-end">

  <button @click="openBorrowModal" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg flex items-center transition-colors">

<i class="fa fa-plus mr-2"></i> 新增借阅

  </button>

</div>

  </div>

</div>

提供搜索框和下拉选择框用于筛选借阅记录,v-model 实现双向数据绑定。点击"新增借阅"按钮调用openBorrowModal方法,弹出新增借阅记录模态窗。

(2)借阅记录 列表:

借阅记录列表是借阅管理视图中的第二部分。

复制代码
<div class="bg-white rounded-xl p-6 card-shadow 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">图书信息</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>

<th 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="borrow in filteredBorrows" :key="borrow.id" class="hover:bg-gray-50 transition-colors">

<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="borrow.book?.cover || 'https://picsum.photos/seed/default/100/100'" alt="Book cover">

</div>

<div class="ml-4">

  <div class="text-sm font-medium text-gray-900">{{ borrow.book?.title || '未知图书' }}</div>

  <div class="text-sm text-gray-500">{{ borrow.book?.author || '未知作者' }}</div>

</div>

  </div>

</td>

<td class="px-6 py-4 whitespace-nowrap">

  <div class="text-sm font-medium text-gray-900">{{ borrow.user.name }}</div>

  <div class="text-sm text-gray-500">{{ borrow.user.studentId }}</div>

</td>

<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">

  {{ borrow.borrowDate }}

</td>

<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">

  {{ borrow.dueDate }}

</td>

<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">

  {{ borrow.returnDate || '-' }}

</td>

<td class="px-6 py-4 whitespace-nowrap">

  <span v-if="borrow.isReturned" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">

已归还

  </span>

  <span v-else-if="isOverdue(borrow.dueDate)" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">

已逾期

  </span>

  <span v-else 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-right text-sm font-medium">

  <div class="flex justify-end space-x-1">

<button v-if="!borrow.isReturned" @click="returnBook(borrow)" class="p-1.5 rounded-full hover:bg-green-100 text-green-600 transition-colors">

  <i class="fa fa-check"></i> 归还

</button>

<button @click="viewBorrowDetails(borrow)" class="p-1.5 rounded-full hover:bg-gray-100 text-gray-600 transition-colors">

  <i class="fa fa-eye"></i>

</button>

  </div>

</td>

  </tr>

</tbody>

  </table>

</div>

使用网格布局显示借阅列表,v-for 遍历 filteredBorrows数组并渲染借阅表格卡片。点击"归还"按钮调用 returnBook方法,弹出确认归还图书模态窗,点击"查看详情"按钮调用 viewBorrowDetails方法,弹出该书的借阅详情和借阅历史模态窗。

在借阅管理视图中,filteredBorrows计算属性可能没有正确地将图书和用户对象关联到借阅记录中,导致可能部分数据对不上,因此对borrow.book进行了空值检查。

(3) 分页:

分页是借阅管理视图中的第三部分。

复制代码
<div class="mt-8 flex justify-between items-center">

  <div class="text-sm text-gray-500">

显示 {{ (currentBorrowPage - 1) * borrowsPerPage + 1 }} 到 {{ Math.min(currentBorrowPage * borrowsPerPage, filteredBorrows.length) }} 共 {{ filteredBorrows.length }} 条记录

  </div>

  <div class="flex space-x-1">

<button @click="prevBorrowPage" :disabled="currentBorrowPage === 1" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">

  <i class="fa fa-chevron-left"></i>

</button>

<button v-for="page in totalBorrowPages" :key="page" @click="currentBorrowPage = page" :class="{'bg-primary text-white border-primary': page === currentBorrowPage, 'border-gray-300 text-gray-600 hover:bg-gray-50': page !== currentBorrowPage}" class="px-3 py-1 rounded border transition-colors">

  {{ page }}

</button>

<button @click="nextBorrowPage" :disabled="currentBorrowPage === totalBorrowPages" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">

  <i class="fa fa-chevron-right"></i>

</button>

  </div>

</div>

提供分页功能,显示当前页码和总记录数。点击"上一页"和"下一页"按钮分别调用 prevBorrowPage和 nextBorrowPage方法,点击页码按钮更新 currentBorrowPage的值。

4 . 用户管理视图 (<div v-if="currentView === 'users'">)

用户管理视图是<main>中的第四部分。

复制代码
<div v-if="currentView === 'users'">

  <div class="mb-8">

      <h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold mb-2">用户管理</h1>

      <p class="text-gray-600">管理系统中的所有用户</p>

  </div>

<!-- 用户管理内容 -->

</div>

当 currentView 为 'users' 时显示该部分内容。

可以在index.html中搜索const currentView = ref('dashboard');将其中的'dashboard'改为'users' ,再刷新页面,即可看到内容。借阅管理视图分为搜索和筛选、借阅记录列表、分页三个部分。界面效果如下:

(1) 搜索和筛选:

搜索和筛选是用户管理视图中的第一部分。

复制代码
<div class="bg-white rounded-xl p-6 card-shadow mb-6">

  <div class="grid grid-cols-1 md:grid-cols-4 gap-4">

<div class="relative">

  <input type="text" v-model="userSearchQuery" placeholder="搜索用户名/学号" class="w-full pl-10 pr-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

  <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>

</div>

<div>

  <select v-model="userRoleFilter" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

<option value="">所有角色</option>

<option value="student">学生</option>

<option value="teacher">教师</option>

<option value="admin">管理员</option>

  </select>

</div>

<div>

  <select v-model="userStatusFilter" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

<option value="">所有状态</option>

<option value="active">活跃</option>

<option value="blocked">已封禁</option>

  </select>

</div>

<div class="flex justify-end">

  <button @click="openUserModal(null)" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg flex items-center transition-colors">

<i class="fa fa-plus mr-2"></i> 添加用户

  </button>

</div>

  </div>

</div>

提供搜索框和下拉选择框用于筛选用户信息,v-model 实现双向数据绑定。

(2)用户 列表:

用户列表是用户管理视图中的第二部分。

复制代码
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">

  <div v-for="user in filteredUsers" :key="user.id" class="bg-white rounded-xl overflow-hidden card-shadow scale-hover">

<div class="p-4">

  <div class="flex items-center mb-4">

<div class="w-16 h-16 rounded-full bg-gray-200 overflow-hidden">

  <img :src="user.avatar || 'https://picsum.photos/seed/defaultuser/200/200'" alt="User avatar" class="w-full h-full object-cover">

</div>

<div class="ml-4">

  <h3 class="font-bold text-lg">{{ user.name || '未知用户' }}</h3>

  <p class="text-gray-600 text-sm">{{ user.studentId || '未知ID' }}</p>

  <div class="flex items-center mt-1">

<span class="text-xs px-2 py-0.5 rounded-full bg-blue-100 text-blue-800">

  {{ user.role === 'student' ? '学生' : user.role === 'teacher' ? '教师' : '管理员' }}

</span>

<span v-if="user.isBlocked" class="ml-2 text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-800">

  已封禁

</span>

  </div>

</div>

  </div>

  

  <div class="grid grid-cols-3 gap-2 text-center mb-4">

<div class="bg-gray-50 p-2 rounded-lg">

  <p class="text-sm text-gray-500">借阅中</p>

  <p class="font-bold">{{ getBorrowingCount(user.id) }}</p>

</div>

<div class="bg-gray-50 p-2 rounded-lg">

  <p class="text-sm text-gray-500">已归还</p>

  <p class="font-bold">{{ getReturnedCount(user.id) }}</p>

</div>

<div class="bg-gray-50 p-2 rounded-lg">

  <p class="text-sm text-gray-500">逾期</p>

  <p class="font-bold text-red-500">{{ getOverdueCount(user.id) }}</p>

</div>

  </div>

  

  <div class="flex justify-end space-x-2">

<button @click="openUserModal(user)" class="px-3 py-1.5 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors">

  <i class="fa fa-edit mr-1"></i> 编辑

</button>

<button @click="toggleUserBlock(user)" class="px-3 py-1.5 rounded-lg" :class="user.isBlocked ? 'bg-green-500 text-white hover:bg-green-600' : 'bg-red-500 text-white hover:bg-red-600'">

  <i class="fa" :class="user.isBlocked ? 'fa-unlock-alt mr-1' : 'fa-lock mr-1'"></i>

  {{ user.isBlocked ? '解封' : '封禁' }}

</button>

  </div>

</div>

  </div>

</div>

使用网格布局显示用户列表,v-for 遍历 filteredUsers数组并渲染用户卡片。点击"编辑"按钮调用 openUserModal方法,弹出编辑用户模态窗,点击"封禁"按钮调用 toggleUserBlock方法,弹出确认是否封禁用户的模态窗。

(3) 分页:

分页是用户管理视图中的第三部分。

复制代码
<div class="mt-8 flex justify-between items-center">

  <div class="text-sm text-gray-500">

显示 {{ (currentUserPage - 1) * usersPerPage + 1 }} 到 {{ Math.min(currentUserPage * usersPerPage, filteredUsers.length) }} 共 {{ filteredUsers.length }} 条记录

  </div>

  <div class="flex space-x-1">

<button @click="prevUserPage" :disabled="currentUserPage === 1" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">

  <i class="fa fa-chevron-left"></i>

</button>

<button v-for="page in totalUserPages" :key="page" @click="currentUserPage = page" :class="{'bg-primary text-white border-primary': page === currentUserPage, 'border-gray-300 text-gray-600 hover:bg-gray-50': page !== currentUserPage}" class="px-3 py-1 rounded border transition-colors">

  {{ page }}

</button>

<button @click="nextUserPage" :disabled="currentUserPage === totalUserPages" class="px-3 py-1 rounded border border-gray-300 bg-white text-gray-600 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">

  <i class="fa fa-chevron-right"></i>

</button>

  </div>

</div>

提供分页功能,显示当前页码和总记录数。点击"上一页"和"下一页"按钮分别调用 prevUserPage和 nextUserPage方法,点击页码按钮更新 currentUserPage的值。

<body>部分之 页脚 (<footer>)

通常页脚会包含系统标题、版权信息和社交链接等内容,用于提供额外的信息和导航。

复制代码
<footer class="bg-white border-t border-gray-200 py-6">

  <div class="container mx-auto px-4">

<div class="flex flex-col md:flex-row justify-between items-center">

  <div class="mb-4 md:mb-0">

<div class="flex items-center">

  <i class="fa fa-book text-primary text-xl mr-2"></i>

  <span class="font-bold text-lg">图书管理系统</span>

</div>

<p class="text-gray-500 text-sm mt-1">信息管理与信息系统专业课程设计</p>

  </div>

  <div class="flex space-x-4">

<a href="#" class="text-gray-500 hover:text-primary transition-colors">

  <i class="fa fa-github text-xl"></i>

</a>

<a href="#" class="text-gray-500 hover:text-primary transition-colors">

  <i class="fa fa-envelope text-xl"></i>

</a>

<a href="#" class="text-gray-500 hover:text-primary transition-colors">

  <i class="fa fa-linkedin text-xl"></i>

</a>

  </div>

</div>

<div class="mt-6 pt-6 border-t border-gray-100 text-center text-gray-500 text-sm">

  © 2025 图书管理系统 | 设计与开发

</div>

  </div>

</footer>

页脚包含系统标题、版权信息和社交链接。界面效果如下:

<body>部分之 模态框

在这个图书管理系统中,模态框起到了重要的交互作用,它可以在不切换页面的情况下让用户完成特定的操作,包括了添加/编辑图书模态框新增借阅模态框添加/编辑用户模态框借阅详情模态框确认对话框这些模态窗。以下将对系统中的模态框进行详细解析。

接着<nav>继续实现body中的其他内容。放在<main>标签中。<main>与<nav>对齐。如上代码树形结构可以看出来。

<body>

├── <div id="app">

├── <nav> 导航栏

├── <main> 主内容区

├── <footer> 页脚

└── 模态框部分

├── <div v-if="isBookModalOpen"> 添加/编辑图书模态框

├── <div v-if="isBorrowModalOpen"> 新增借阅模态框

├── <div v-if="isUserModalOpen"> 添加/编辑用户模态框

├── <div v-if="isBorrowDetailsModalOpen"> 借阅详情模态框

└── <div v-if="isConfirmDialogOpen"> 确认对话框

1. 添加/编辑图书模态框

在代码中,模态框的代码是与<footer>标签对齐的。添加/编辑图书模态框的相关代码如下:

复制代码
<!-- 添加/编辑图书模态框 -->

<div v-if="isBookModalOpen" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" @click.self="closeBookModal">

  <div class="bg-white rounded-xl w-full max-w-lg max-h-[90vh] overflow-y-auto">

<div class="p-6 border-b border-gray-100">

  <div class="flex justify-between items-center">

<h3 class="text-lg font-bold">{{ editingBook ? '编辑图书' : '添加图书' }}</h3>

<button @click="closeBookModal" class="text-gray-500 hover:text-gray-700">

  <i class="fa fa-times"></i>

</button>

  </div>

</div>



<div class="p-6">

  <form @submit.prevent="saveBook">

<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">图书封面 URL</label>

  <input type="text" v-model="form.bookCover" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">图书标题</label>

  <input type="text" v-model="form.bookTitle" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">作者</label>

  <input type="text" v-model="form.bookAuthor" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">出版社</label>

  <input type="text" v-model="form.bookPublisher" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">出版年份</label>

  <input type="number" v-model="form.bookYear" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">分类</label>

  <select v-model="form.bookCategory" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

<option value="计算机">计算机</option>

<option value="文学">文学</option>

<option value="历史">历史</option>

<option value="科学">科学</option>

<option value="艺术">艺术</option>

<option value="经济">经济</option>

  </select>

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">ISBN</label>

  <input type="text" v-model="form.bookISBN" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">简介</label>

  <textarea v-model="form.bookDescription" rows="4" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"></textarea>

</div>



<div class="flex justify-end space-x-3 mt-6">

  <button type="button" @click="closeBookModal" class="px-4 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors">

取消

  </button>

  <button type="submit" class="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors">

保存

  </button>

</div>

  </form>

</div>

  </div>

</div>

代码解析:

  1. 显示与隐藏控制:使用v-if="isBookModalOpen"来控制模态框的显示与隐藏,isBookModalOpen是一个Vue响应式数据。
  2. 背景遮罩:class="fixed inset-0 bg-black bg-opacity-50" 创建了一个覆盖整个屏幕的半透明黑色背景,增强了模态框的聚焦效果。
  3. 标题动态显示:通过 {{ editingBook ? '编辑图书' : '添加图书' }} 根据是否处于编辑状态动态显示标题。
  4. 表单数据绑定:使用 v-model 指令将表单输入项与 form 对象的属性进行绑定,方便数据的收集和处理。
  5. 关闭模态框:点击背景(@click.self="closeBookModal")或关闭按钮(@click="closeBookModal")可以关闭模态框。
  6. 表单提交:表单提交事件使用 @submit.prevent="saveBook" 阻止默认提交行为,并调用 saveBook 方法保存图书信息。

添加图书模态框的效果如下:

编辑图书模态框的效果如下:

2. 新增借阅模态框

新增借阅模态框的代码如下:

复制代码
<div v-if="isBorrowModalOpen" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" @click.self="closeBorrowModal">

  <div class="bg-white rounded-xl w-full max-w-lg max-h-[90vh] overflow-y-auto">

<div class="p-6 border-b border-gray-100">

  <div class="flex justify-between items-center">

<h3 class="text-lg font-bold">新增借阅记录</h3>

<button @click="closeBorrowModal" class="text-gray-500 hover:text-gray-700">

  <i class="fa fa-times"></i>

</button>

  </div>

</div>



<div class="p-6">

  <form @submit.prevent="saveBorrow">

<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">选择图书</label>

  <select v-model="form.borrowBookId" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

<option value="">请选择图书</option>

<option v-for="book in availableBooks" :key="book.id" :value="book.id">

  {{ book.title }} - {{ book.author }}

</option>

  </select>

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">选择用户</label>

  <select v-model="form.borrowUserId" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

<option value="">请选择用户</option>

<option v-for="user in users" :key="user.id" :value="user.id">

  {{ user.name }} - {{ user.studentId }}

</option>

  </select>

</div>



<div class="grid grid-cols-2 gap-4 mb-4">

  <div>

<label class="block text-sm font-medium text-gray-700 mb-1">借阅日期</label>

<input type="date" v-model="form.borrowDate" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

  </div>

  <div>

<label class="block text-sm font-medium text-gray-700 mb-1">应归还日期</label>

<input type="date" v-model="form.dueDate" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

  </div>

</div>



<div class="flex justify-end space-x-3 mt-6">

  <button type="button" @click="closeBorrowModal" class="px-4 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors">

取消

  </button>

  <button type="submit" class="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors">

保存

  </button>

</div>

  </form>

</div>

  </div>

</div>

代码解析:

  1. 显示与隐藏控制:通过一个布尔型的Vue响应式数据来控制模态框的显示与隐藏。
  2. 背景遮罩:创建一个覆盖整个屏幕的半透明背景。
  3. 表单元素:包含图书选择、借阅人选择、借阅日期、应归还日期等表单输入项。
  4. 关闭和提交按钮:提供关闭模态框和提交借阅信息的按钮。

新增借阅记录模态框的效果如下:

3. 添加/编辑用户模态框

添加/编辑用户模态框的代码如下:

复制代码
<div v-if="isUserModalOpen" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" @click.self="closeUserModal">

  <div class="bg-white rounded-xl w-full max-w-lg max-h-[90vh] overflow-y-auto">

<div class="p-6 border-b border-gray-100">

  <div class="flex justify-between items-center">

<h3 class="text-lg font-bold">{{ editingUser ? '编辑用户' : '添加用户' }}</h3>

<button @click="closeUserModal" class="text-gray-500 hover:text-gray-700">

  <i class="fa fa-times"></i>

</button>

  </div>

</div>



<div class="p-6">

  <form @submit.prevent="saveUser">

<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">用户头像 URL</label>

  <input type="text" v-model="form.userAvatar" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">姓名</label>

  <input type="text" v-model="form.userName" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">学号/工号</label>

  <input type="text" v-model="form.userStudentId" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">角色</label>

  <select v-model="form.userRole" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

<option value="student">学生</option>

<option value="teacher">教师</option>

<option value="admin">管理员</option>

  </select>

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">联系方式</label>

  <input type="text" v-model="form.userContact" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">邮箱</label>

  <input type="email" v-model="form.userEmail" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary">

</div>



<div class="mb-4">

  <label class="block text-sm font-medium text-gray-700 mb-1">备注</label>

  <textarea v-model="form.userNotes" rows="3" class="w-full px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"></textarea>

</div>



<div class="flex justify-end space-x-3 mt-6">

  <button type="button" @click="closeUserModal" class="px-4 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors">

取消

  </button>

  <button type="submit" class="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors">

保存

  </button>

</div>

  </form>

</div>

  </div>

</div>

代码解析:

  1. 显示与隐藏控制:使用一个布尔型的 Vue 响应式数据来控制模态框的显示与隐藏。
  2. 背景遮罩:创建一个覆盖整个屏幕的半透明背景。
  3. 表单元素:包含用户名、学号、角色、状态等表单输入项。
  4. 关闭和提交按钮:提供关闭模态框和保存用户信息的按钮。

添加用户的模态框效果如下:

编辑用户的模态框效果如下:

4. 借阅详情模态框

借阅详情模态框用于显示借阅记录的详细信息,代码如下:

复制代码
<div v-if="isBorrowDetailsModalOpen" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" @click.self="closeBorrowDetailsModal">

  <div class="bg-white rounded-xl w-full max-w-lg max-h-[90vh] overflow-y-auto">

<div class="p-6 border-b border-gray-100">

  <div class="flex justify-between items-center">

<h3 class="text-lg font-bold">借阅详情</h3>

<button @click="closeBorrowDetailsModal" class="text-gray-500 hover:text-gray-700">

  <i class="fa fa-times"></i>

</button>

  </div>

</div>



<div class="p-6">

  <div class="flex items-center mb-6">

<div class="w-20 h-20 rounded-lg bg-gray-200 overflow-hidden">

  <img :src="selectedBorrow.book.cover" alt="Book cover" class="w-full h-full object-cover">

</div>

<div class="ml-4">

  <h3 class="font-bold text-lg">{{ selectedBorrow.book.title }}</h3>

  <p class="text-gray-600 text-sm">{{ selectedBorrow.book.author }}</p>

  <p class="text-gray-500 text-xs mt-1">

<span class="bg-gray-100 px-2 py-0.5 rounded text-xs">{{ selectedBorrow.book.category }}</span>

  </p>

</div>

  </div>

  

  <div class="space-y-4">

<div class="flex justify-between">

  <span class="text-gray-600">借阅人</span>

  <span class="font-medium">{{ selectedBorrow.user.name }} ({{ selectedBorrow.user.studentId }})</span>

</div>

<div class="flex justify-between">

  <span class="text-gray-600">借阅日期</span>

  <span class="font-medium">{{ selectedBorrow.borrowDate }}</span>

</div>

<div class="flex justify-between">

  <span class="text-gray-600">应归还日期</span>

  <span class="font-medium">{{ selectedBorrow.dueDate }}</span>

</div>

<div class="flex justify-between" v-if="selectedBorrow.returnDate">

  <span class="text-gray-600">实际归还日期</span>

  <span class="font-medium">{{ selectedBorrow.returnDate }}</span>

</div>

<div class="flex justify-between">

  <span class="text-gray-600">借阅状态</span>

  <span class="font-medium" :class="getStatusColor(selectedBorrow)">

{{ getStatusText(selectedBorrow) }}

  </span>

</div>

<div class="flex justify-between" v-if="isOverdue(selectedBorrow.dueDate) && !selectedBorrow.isReturned">

  <span class="text-gray-600">逾期天数</span>

  <span class="font-medium text-red-500">{{ getOverdueDays(selectedBorrow.dueDate) }} 天</span>

</div>

  </div>

  

  <div class="mt-6 pt-6 border-t border-gray-100">

<h4 class="font-medium mb-3">借阅历史</h4>

<div class="space-y-3">

  <div class="bg-gray-50 p-3 rounded-lg" v-for="history in getBorrowHistory(selectedBorrow.book.id)" :key="history.id">

<div class="flex justify-between text-sm">

  <span class="font-medium">{{ history.user.name }}</span>

  <span class="text-gray-500">{{ history.borrowDate }} - {{ history.returnDate || '未归还' }}</span>

</div>

<div class="flex justify-between text-xs mt-1">

  <span>{{ history.isReturned ? '已归还' : '借阅中' }}</span>

  <span v-if="history.isReturned && history.returnDate > history.dueDate" class="text-red-500">

逾期 {{ getOverdueDays(history.dueDate, history.returnDate) }} 天

  </span>

</div>

  </div>

</div>

  </div>

</div>

  </div>

</div>

代码解析:

  1. 显示与隐藏控制:通过一个布尔型的 Vue 响应式数据来控制模态框的显示与隐藏。
  2. 背景遮罩:创建一个覆盖整个屏幕的半透明背景。
  3. 详情信息展示:显示图书信息、借阅人信息、借阅日期、应归还日期、实际归还日期、状态等详细信息。
  4. 关闭按钮:提供关闭模态框的按钮。

借阅详情模态框效果如下:

5. 确认对话框

确认对话框通常用于确认一些重要的操作,如删除图书 、归还确认、封禁用户等。其实现方式如下:

复制代码
<!-- 确认对话框示例 -->

<div v-if="isConfirmDialogOpen" class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" @click.self="closeConfirmDialog">

  <div class="bg-white rounded-xl w-full max-w-md p-6">

<h3 class="text-lg font-bold mb-3">{{ confirmDialogTitle }}</h3>

<p class="text-gray-600 mb-6">{{ confirmDialogMessage }}</p>

<div class="flex justify-end space-x-3">

  <button @click="closeConfirmDialog" class="px-4 py-2 rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 transition-colors">

取消

  </button>

  <button @click="confirmAction" class="px-4 py-2 rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors">

确认

  </button>

</div>

  </div>

</div>

代码解析:

  1. 显示与隐藏控制:使用 v-if="isConfirmDialogOpen" 来控制对话框的显示与隐藏,isConfirmDialogOpen 是一个Vue响应式数据。
  2. 背景遮罩:创建一个覆盖整个屏幕的半透明黑色背景。
  3. 确认信息显示:通过{{ confirmDialogTitle }}和 {{ confirmDialogMessage}} 动态显示标题和确认信息。
  4. 取消和确认按钮:点击取消按钮(@click="closeConfirmDialog")关闭对话框,点击确认按钮(@click="confirmAction")执行相应的操作。

封禁用户的模态框效果如下:

综上所述,这些模态框通过Vue的响应式数据和事件处理机制,结合CSS样式,实现了良好的用户交互体验。

、Vue.js原理说明

(一) 响应式原理

Vue.js利用Object.defineProperty ()或Proxy实现数据的响应式。当数据发生变化时,Vue 会自动更新与之绑定的DOM元素。例如,{{ books.length }} 会随着 books 数组的变化而更新显示。

(二) 指令系统

(1)v-if:条件渲染指令,根据表达式的值决定是否渲染元素。

(2)v-for:列表渲染指令,用于遍历数组或对象并渲染元素。

(3)v-model:双向数据绑定指令,将表单元素的值与Vue实例中的数据绑定。

(4):class:动态类绑定指令,根据表达式的值动态添加或移除类名。

(5)@click:事件绑定指令,绑定点击事件并调用Vue实例中的方法。

(三) 事件处理

通过 @ 符号绑定DOM事件,如 @click、@submit 等,可调用Vue实例中的方法。例如,点击"添加图书"按钮调用 openBookModal 方法。

(四) 组件化开发

虽然当前系统的代码未明确使用组件,但Vue.js支持将页面拆分为多个组件,提高代码的可维护性和复用性。

有关Vue 3构建的图书管理系统的JavaScript逻辑部分,将在下一篇文章中讲解。

相关推荐
我的心巴2 分钟前
Vue2 ElementUI Tree 拖动目标节点能否被放置及获取放置位置
前端·vue.js·elementui
ze_juejin14 分钟前
Subject、BehaviorSubject、ReplaySubject、AsyncSubject、VoidSubject比较
前端·angular.js
每天吃饭的羊19 分钟前
面试-TypeScript 场景类面试题
前端
CRMEB定制开发31 分钟前
CRMEB 注释规范:多端适配下的代码可读性提升之道
前端
中雨202532 分钟前
HarmonyOS Next快速入门:TextInput组件
前端
白晓明35 分钟前
HarmonyOS NEXT端云一体化云侧云函数介绍和开发
前端·harmonyos
白晓明1 小时前
HarmonyOS NEXT端侧工程调用云函数能力实现业务功能
前端·harmonyos
锋利的绵羊1 小时前
【小程序】迁移非主包组件以减少主包体积
前端·微信小程序·uni-app
jqq6661 小时前
解析ElementPlus打包源码
前端·javascript·vue.js