当前位置: 首页 > news >正文

基于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/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/vue@3.2.47/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: "zhangsan@example.com",avatar: "https://picsum.photos/seed/user1/200/200",notes: "计算机系大三学生,借阅记录良好",isBlocked: false},{id: 2,name: "李四",studentId: "2022002",role: "student",contact: "13900139002",email: "lisi@example.com",avatar: "https://picsum.photos/seed/user2/200/200",notes: "数学系大二学生,有逾期记录",isBlocked: false},{id: 3,name: "王五",studentId: "T2021001",role: "teacher",contact: "13700137003",email: "wangwu@example.com",avatar: "https://picsum.photos/seed/user3/200/200",notes: "计算机系教授,经常借阅专业书籍",isBlocked: false},{id: 4,name: "赵六",studentId: "2022003",role: "student",contact: "13600136004",email: "zhaoliu@example.com",avatar: "https://picsum.photos/seed/user4/200/200",notes: "历史系大一学生",isBlocked: false},{id: 5,name: "管理员",studentId: "A0001",role: "admin",contact: "13500135005",email: "admin@example.com",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/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/vue@3.2.47/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逻辑部分,将在下一篇文章中讲解。

http://www.lryc.cn/news/573776.html

相关文章:

  • 包教包会,ES6类class的基本入门
  • TS类型啊啊啊2
  • 计算机系统结构课堂测验
  • Claude:Anthropic打造的安全优先AI助手
  • 2025中科院2区SCI-状态优化算法Status-based Optimization-附Matlab免费代码
  • 基于split-Bregman算法的L1正则化matlab仿真,对比GRSR算法
  • 《情感反诈模拟器》2025学习版
  • 【Redis】解码Redis中hash类型:理解基础命令,以及内部编码方式和使用场景
  • Docker Desktop 4.42集成的MCP工具包
  • 安卓对外发布工程源码:怎么做到仅UI层公布
  • linux-vim编辑器
  • Perplexity AI:对话式搜索引擎的革新者与未来认知操作系统
  • 课程专注度分析系统文档
  • DNS:互联网世界的隐形电话簿——深入解析域名解析系统
  • rust单体web项目模板搭建
  • json格式化、验证、压缩和转换json数据
  • C++ 第二阶段:类与对象 - 第三节:成员函数与访问权限
  • 理解贝叶斯分析中的“模型比较”部分
  • 【ISP】WDR and HDR
  • Netty ChannelPipeline和ChannelHandler详解
  • 61-Oracle SQL Monitor-实操
  • 多源异构数据接入与实时分析:衡石科技的技术突破
  • RabbitMQ从入门到实践:消息队列核心原理与典型应用场景
  • Java基础 6.22
  • 开源 python 应用 开发(一)python、pip、pyAutogui、python opencv安装
  • 通达信【千军趋势决策系统】幅图指标
  • idea2023+zulu-jdk+maven3.9.10
  • 创建 Vue 3.0 项目的两种方法对比:npm init vue@latest vs npm init vite@latest
  • 新冠疫情分布动态展示图
  • 多设备Obsidian笔记同步:WebDAV与内网穿透技术高效实现教程