基于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">© 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动态渲染页面内容。系统运用响应式设计,能适配不同屏幕尺寸。
(一)功能模块
- 导航栏模块:包含系统标题、主菜单、搜索框与用户图标,支持移动端菜单展开与收缩。
- 仪表盘模块:展示系统关键统计数据,如总藏书量、借出图书、注册用户和逾期未还数量,还有借阅趋势与分类统计图表,以及最近借阅记录。
- 图书管理模块:可搜索、筛选图书,添加、编辑和删除图书信息,支持分页显示。
- 借阅管理模块:能搜索、筛选借阅记录,新增借阅,处理图书归还,支持分页显示。
- 用户管理模块:可搜索、筛选用户,添加、编辑用户信息,封禁和解封用户,支持分页显示。
- 页脚模块:显示系统标题与版权信息、社交链接。
- 模态框模块:用于添加/编辑图书、新增借阅、添加/编辑用户、借阅详情、确认对话框。
(二)代码树形结构
整个系统的界面采用单页面应用(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>的样式与布局:
- bg-white shadow-md sticky top-0 z-50:设置导航栏背景为白色,添加阴影效果,使其固定在页面顶部,并设置较高的层叠顺序。
- transition-all duration-300:添加过渡效果,使导航栏样式变化更平滑。
- :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>
- hidden md:flex:在小屏幕设备上隐藏,在中等及以上屏幕设备上显示。
- :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>
- 在中等及以上屏幕设备上显示,输入框获得焦点时会有动画效果。
- 显示用户图标,悬停时背景颜色变化。
- 移动端菜单按钮:在小屏幕设备上显示,点击时调用 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>
- md:hidden:在中等及以上屏幕设备上隐藏。
- :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>
代码解析:
- 显示与隐藏控制:使用v-if="isBookModalOpen"来控制模态框的显示与隐藏,isBookModalOpen是一个Vue响应式数据。
- 背景遮罩:class="fixed inset-0 bg-black bg-opacity-50" 创建了一个覆盖整个屏幕的半透明黑色背景,增强了模态框的聚焦效果。
- 标题动态显示:通过 {{ editingBook ? '编辑图书' : '添加图书' }} 根据是否处于编辑状态动态显示标题。
- 表单数据绑定:使用 v-model 指令将表单输入项与 form 对象的属性进行绑定,方便数据的收集和处理。
- 关闭模态框:点击背景(@click.self="closeBookModal")或关闭按钮(@click="closeBookModal")可以关闭模态框。
- 表单提交:表单提交事件使用 @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>
代码解析:
- 显示与隐藏控制:通过一个布尔型的Vue响应式数据来控制模态框的显示与隐藏。
- 背景遮罩:创建一个覆盖整个屏幕的半透明背景。
- 表单元素:包含图书选择、借阅人选择、借阅日期、应归还日期等表单输入项。
- 关闭和提交按钮:提供关闭模态框和提交借阅信息的按钮。
新增借阅记录模态框的效果如下:
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>
代码解析:
- 显示与隐藏控制:使用一个布尔型的 Vue 响应式数据来控制模态框的显示与隐藏。
- 背景遮罩:创建一个覆盖整个屏幕的半透明背景。
- 表单元素:包含用户名、学号、角色、状态等表单输入项。
- 关闭和提交按钮:提供关闭模态框和保存用户信息的按钮。
添加用户的模态框效果如下:
编辑用户的模态框效果如下:
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>
代码解析:
- 显示与隐藏控制:通过一个布尔型的 Vue 响应式数据来控制模态框的显示与隐藏。
- 背景遮罩:创建一个覆盖整个屏幕的半透明背景。
- 详情信息展示:显示图书信息、借阅人信息、借阅日期、应归还日期、实际归还日期、状态等详细信息。
- 关闭按钮:提供关闭模态框的按钮。
借阅详情模态框效果如下:
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>
代码解析:
- 显示与隐藏控制:使用 v-if="isConfirmDialogOpen" 来控制对话框的显示与隐藏,isConfirmDialogOpen 是一个Vue响应式数据。
- 背景遮罩:创建一个覆盖整个屏幕的半透明黑色背景。
- 确认信息显示:通过{{ confirmDialogTitle }}和 {{ confirmDialogMessage}} 动态显示标题和确认信息。
- 取消和确认按钮:点击取消按钮(@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逻辑部分,将在下一篇文章中讲解。
相关文章:
基于Vue.js的图书管理系统前端界面设计
一、系统前端界面设计要求与效果 (一)系统功能结构图 设计一个基于Vue.js的图书管理系统前端界面。要充分体现Vue的核心特性和应用场景,同时结合信息管理专业的知识。要求系统分为仪表盘、图书管理、借阅管理和用户管理四个主要模块&#x…...
FPGA故障注入测试软件使用指南
有数字芯片之母别称的FPGA,是国内在半导体行业率先取得重大突破的细分赛道,正迎来技术和市场形成共振的黄金发展期。 国内拥有最多的应用设计工程师与新兴从业人员,但到目前为止,还没有一款位流级别的专用EDA软件,服务用户日常应用开发所需的调试验证工作。 第一大厂商赛…...
Oracle 数据库查询:单表查询
作者:IvanCodes 日期:2025年6月22日 专栏:Oracle教程 在 Oracle 数据库操作中,查询数据是最频繁、最核心的操作之一。单表查询,即仅从一个表中检索信息,是所有复杂查询的基础。本笔记将系统梳理单表查询的关…...
【DDD】——带你领略领域驱动设计的独特魅力
🎼个人主页:【Y小夜】 😎作者简介:一位双非学校的大三学生,编程爱好者, 专注于基础和实战分享,欢迎私信咨询! 🎆入门专栏:🎇【MySQL࿰…...
阿里云CentOS系统搭建全攻略:开启云端技术之旅
前期准备:开启云端征程前的必备事项 在当今数字化时代,云计算已成为企业和开发者构建应用和服务的重要基础设施。阿里云作为全球领先的云计算服务提供商,提供了丰富的云计算产品和服务,其中 CentOS 系统在阿里云上的应用非常广泛…...
Flink图之间流转解析:从逻辑构建到物理执行的深度剖析
在Flink强大的数据处理体系中,Table Connectors实现了与外部结构化数据的高效交互,而Flink作业从代码到实际执行的背后,是各类图结构之间的流转与转换。这些图结构承载着作业的逻辑定义、任务划分与资源调度等关键信息,其流转过程…...
详解Redis数据库和缓存不一致的情况及解决方案
数据库与缓存不一致是分布式系统中常见问题,本质是数据在缓存层和存储层出现版本差异。 一、并发写操作导致不一致(最常见) 场景描述 线程A更新数据库 → 线程B更新数据库 → 线程B更新缓存 → 线程A更新缓存 结果:缓存中存储的…...
【CSS】CSS3媒体查询全攻略
媒体查询教程 媒体查询(Media Queries)是CSS3中引入的强大功能,允许内容根据设备特性(如屏幕尺寸、分辨率、方向等)进行自适应调整。以下是媒体查询的详细教程: 基本语法 media mediatype and (media feature) {/* CSS规则 */ }常用媒体类型 all - 所…...
深入理解Spring的ResponseBodyAdvice接口
什么是ResponseBodyAdvice? ResponseBodyAdvice是Spring框架4.2版本引入的一个非常有用的接口,它允许我们在控制器方法执行后、响应体写入前对响应进行统一处理。这个接口为开发者提供了对返回数据进行统一拦截和修改的能力,是Spring MVC响应处理流程中…...
C++法则5: 在函数调用过程中,具有非引用类型的参数要进行拷贝初始化。
C法则5: 在函数调用过程中,具有非引用类型的参数要进行拷贝初始化。 在 C 中,法则5指的是:当函数参数是非引用类型(即按值传递)时,传递给函数的实参会进行拷贝初始化(copy initializ…...
Python 使用 Requests 模块进行爬虫
目录 一、请求数据二、获取并解析数据四、保存数据1. 保存为 CSV 文件2. 保存为 Excel 文件打开网页图片并将其插入到 Excel 文件中 五、加密参数逆向分析1. 定位加密位置2. 断点调试分析3. 复制相关 js 加密代码,在本地进行调试(难)4. 获取 …...
day039-nginx配置补充
文章目录 0. 老男孩思想-如何提升能力?1. nginx登录认证功能1.1 创建密码文件1.2 修改子配置文件1.3 重启服务 2. nginx处理请求流程3. 配置默认站点4. location 命令5. 案例1-搭建大型直播购物网站5.1 配置本地hosts解析5.2 编写子配置文件5.3 创建相关目录/文件并…...
K8s入门指南:架构解析浓缩版与服务间调用实战演示
目录 前言一、k8s概念理解1、k8s整体架构(1) Master 主节点(2) Node 工作节点(3) Etcd 键值存储数据库 2、Pod被视为最小的部署单元3、k8s的五种控制器类型(1)…...
如何用AI开发完整的小程序<10>—总结
通过之前9节的学习。 如何用Ai制作一款简单小程序的内容就已经都介绍完了。 总结起来就以下几点: 1、搭建开发制作环境 2、创建页面(需要手动) 3、在页面上制作UI效果(让Ai搞,自己懂了后可以自己调) 4…...
Javaweb - 3 CSS
CSS 层叠样式表(Cascading Style Sheets),能够对网页中元素位置的排版进行像素级精确控制,支持几乎所有的字体字号样式,拥有对网页对象和模型样式编辑的能力。 简单来说,HTML 搭建一个毛坯房,C…...
【算法】【优选算法】优先级队列
目录 一、1046.最后一块石头的重量二、703. 数据流中的第 K 大元素三、692. 前 K 个⾼频单词四、295. 数据流的中位数 一、1046.最后一块石头的重量 题目链接:1046.最后一块石头的重量 题目描述: 题目解析: 题意就是让我们拿出提供的数组…...
PaddleOCR + Flask 构建 Web OCR 服务实战
1、前言 随着图像识别技术的发展,OCR(光学字符识别)已经成为很多应用场景中的基础能力。PaddleOCR 是百度开源的一个高性能 OCR 工具库,支持中英文、多语言、轻量级部署等特性。 而 Flask 是一个轻量级的 Python Web 框架,非常适合快速构建 RESTful API 或小型 Web 应用…...
openapi-generator-maven-plugin自动生成HTTP远程调用客户端
Java开发中调用http接口的时候,有很多可选的技术方案,比如:HttpURLConnection、RestTemplate、WebClient、Feign、Retrofit、Okhttp等,今天我们来看一个更优的技术方案OpenAPI Generator(http://openapi-generator.tech/) OpenAP…...
ms-swift 部分命令行参数说明
参考链接 命令行参数 — swift 3.6.0.dev0 文档 Qwen Chat num_train_epochs 训练的epoch数,默认为3 假设你有 1000 条训练样本,并且设置了: num_train_epochs 3 这意味着: 模型会完整地遍历这 1000 条数据 3 次。每一次…...
【学习笔记】深入理解Java虚拟机学习笔记——第10章 前端编译与优化
第10章 前端编译与优化 10.1 概述 1>前端编译器:Javac命令。 【.java文件->.class文件】 2>即时编译器:Hotspot.C1.C2 【.class文件->机器码】 3>提前编译器:JDK的Jaotc等【.java->机器码】 10.2 Javac 编译器 10.2.1 …...
删除node并且重装然后重装vue
参考第一篇文章 node.js卸载与安装超详细教程_node卸载重装-CSDN博客 第二篇文章安装vue Vue安装与配置教程(非常详细)_安装vue-CSDN博客...
Flink源码阅读环境准备全攻略:搭建高效探索的基石
想要深入探索Flink的底层原理,搭建一套完整且适配的源码阅读环境是必经之路。这不仅能让我们更清晰地剖析代码逻辑,还能在调试过程中精准定位关键环节。接下来,结合有道云笔记内容,从开发工具安装、源码获取导入到调试配置&#x…...
【破局痛点,赋能未来】领码 SPARK:铸就企业业务永续进化的智慧引擎—— 深度剖析持续演进之道,引领数字化新范式
摘要 在瞬息万变的数字时代,企业对业务连续性、敏捷创新及高效运营的需求日益迫切。领码 SPARK 融合平台,秉持“持续演进”这一核心理念,以 iPaaS 与 aPaaS 为双擎驱动,深度融合元数据驱动、智能端口调度、自动化灰度切换、AI 智…...
Flink SQL Connector Kafka 核心参数全解析与实战指南
Flink SQL Connector Kafka 是连接Flink SQL与Kafka的核心组件,通过将Kafka主题抽象为表结构,允许用户使用标准SQL语句完成数据读写操作。本文基于Apache Flink官方文档(2.0版本),系统梳理从表定义、参数配置到实战调优…...
Linux 服务器运维:磁盘管理与网络配置
🤵♂️ 个人主页:布说在见 ✍🏻作者简介: 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏…...
PyTorch 入门学习笔记
目录 1 张量 1)张量的初始化和属性 2)张量操作 3)使用 NumPy 进行桥接 2 torch.autograd 1)背景 2)在 PyTorch 中的使用 3)Autograd 的微分机制 4)计算图原理 3 神经网络 1ÿ…...
9大策略深度解析MySQL多表JOIN性能优化
一、多表JOIN的现实挑战 在实际开发中,MySQL多表JOIN场景主要源于两类场景: • 历史遗留系统:老代码中未严格遵循范式设计的SQL语句• 数据库迁移:从Oracle迁移至MySQL时保留的复杂关联查询 这类操作潜藏多重风险: …...
CSS 逐帧动画
CSS 逐帧动画实现指南 逐帧动画(frame-by-frame animation)是一种通过快速连续显示一系列静态图像来创造运动效果的技术。以下是使用CSS实现逐帧动画的几种方法。 1. 使用 steps() 计时函数 这是实现逐帧动画最常用的方法,通过animation-timing-function的steps(…...
UE5 游戏模板 —— ThirdPersonGame
UE5 游戏模板 —— ThirdPersonGame 前言一、初始化旋转控制参数1.参数一2.参数二3.参数三4.参数四 二、输入系统总结 前言 有了前面的铺垫,第三人称模板简直是手到擒来了,我们只需要注意一些初始化的变量是做什么的即可,因为UE的Character …...
java中关于异步转同步的一些解决方案的对比与思考。【spring mvc堵塞式】
文章目录 1、Spring MVC堵塞式编程中的技术方案a) 最简单的方案,使用 DeferredResult 代码如下,代码解读:最终控制台输出如下。用户收到的结果 b) 上点难度,使用redis监听事件,根据事件的不同返回不同的数据…...
【数据结构与算法】数据结构核心概念系统梳理
第一章 绪论:基础概念体系 🚩算法:问题求解步骤的描述。 🚩非递归的算法效率更高。 1.1 逻辑结构 vs 存储结构 维度逻辑结构存储结构(物理结构)定义数据元素之间的逻辑关系数据结构在计算机中的实现方式分类线性/树形/图/集合顺序/链式/索引/散列独立性独立于存储结构…...
《HTTP权威指南》 第7章 缓存
带着问题学习: 缓存如何提高性能如何衡量缓存的有效性缓存置于何处作用最大HTTP如何保持缓存副本的新鲜度缓存如何与其他缓存及服务器通信 web缓存是可以自动保存常见文档副本的HTTP设备。 缓存优点 减少冗余的数据传输,节省网络费用缓解网络瓶颈问题&…...
【Zephyr 系列 28】MCU 闪存文件系统详解:LittleFS + NVS + 块设备设计实战
🧠关键词:Zephyr 文件系统、LittleFS、NVS、Flash 分区、嵌入式存储、断电保护、wear leveling 📌 1. 为什么 MCU 上需要文件系统? 在嵌入式开发中,很多开发者起初直接操作 Flash 保存参数,但随着需求增长…...
ICML 2025 | 时间序列(Time Series)论文总结
ICML 2025将在2025年7月13日至7月19日(周六)在温哥华会议中心举行,本文总结了ICML 2025有关时间序列(Time Series)相关文章,共计63篇。 时间序列Topic:预测,分类,异常检测,生成&…...
理解后端开发中的中间件(以gin框架为例)
中间件(Middleware)是后端开发中的一个核心概念,它在请求(Request)和响应(Response)之间扮演着桥梁角色。以下是关于中间件的详细解释: 基本概念 中间件是在请求到达最终处理程序之前或响应返回客户端之前执行的一系列函数或组件。它可以: 访…...
【分布式技术】Bearer Token以及MAC Token深入理解
Bearer Token以及MAC Token深入理解 **Bearer Token 详解****1. 什么是 Bearer Token?****2. Bearer Token 的构建详情****(1)生成流程****(2)Token 示例(JWT)****(3)Tok…...
WebRTC(七):媒体能力协商
目的 在 WebRTC 中,每个浏览器或终端支持的音视频编解码器、分辨率、码率、帧率等可能不同。媒体能力协商的目的就是: 确保双方能“听得懂”对方发的媒体流;明确谁发送、谁接收、怎么发送;保障连接的互操作性和兼容性。 P2P的基…...
(线性代数最小二乘问题)Normal Equation(正规方程)
Normal Equation(正规方程) 是线性代数中的一个重要概念,主要用于解决最小二乘问题(Least Squares Problem)。它通过直接求解一个线性方程组,找到线性回归模型的最优参数(如权重或系数ÿ…...
【机器学习】数学基础——标量
目录 一、标量的定义 二、标量的核心特征:无方向的纯粹量级 2.1 标量 vs 矢量 直观对比 三、 标量的数学本质:零阶张量 3.1 张量阶数金字塔 3.2 标量的数学特性 四、 现实世界的标量图谱 4.1 常见标量家族 4.2 经典案例解析 五、 标量的运算奥秘…...
基于python代码的通过爬虫方式实现TK下载视频(2025年6月)
Tk的视频页面通常需要登录才能获取完整数据,但通过构造匿名游客的请求,我们可以绕过登录限制,提取视频的元信息(如标题、ID和播放地址)。核心思路如下: 构造匿名Cookie:通过模拟浏览器的请求,获取Tk服务器分配的游客Cookie。解析网页:利用BeautifulSoup解析HTML,定位…...
Go 语言的堆糖图片爬虫
基于 Go 语言的堆糖图片爬取探索之旅 在互联网的浩瀚海洋中,堆糖网以其丰富多样的高清图片、美图壁纸等内容吸引了众多用户。对于图片爱好者来说,能高效获取心仪的图片资源无疑是一件极具吸引力的事情。今天,就带大家走进一段基于 Go 语言的…...
python+uni-app基于微信小程序的儿童安全教育系统
文章目录 具体实现截图本项目支持的技术路线源码获取详细视频演示:文章底部获取博主联系方式!!!!本系统开发思路进度安排及各阶段主要任务java类核心代码部分展示主要参考文献:源码获取/详细视频演示 ##项目…...
DAY 39 图像数据与显存
图像数据的格式:灰度和彩色数据模型的定义显存占用的4种地方 模型参数梯度参数优化器参数数据批量所占显存神经元输出中间状态 batchisize和训练的关系 import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader ,…...
ELK搭建
1、elasticsearch和kibana搭建配置见 https://blog.csdn.net/yh_zeng2/article/details/148812447?spm1001.2014.3001.5501 2、logstash 下载 下载和elasticsearch版本一致的logstash,下载地址: Past Releases of Elastic Stack Software | Elastic …...
【ELK(Elasticsearch+Logstash+Kibana) 从零搭建实战记录:日志采集与可视化】
ELK(ElasticsearchLogstashKibana) 从零搭建实战记录:日志采集与可视化 本文记录了我在搭建ELK(Elasticsearch, Logstash, Kibana)技术栈时的完整实战过程。使用Docker Compose快速搭建了ELK服务端(监控主机),并通过Filebeat实现…...
反无人机系统:技术利刃如何守护低空安全?
反无人机系统:技术利刃如何守护低空安全? ——从军事防御到城市安防的全景解析 一、技术体系:从“电磁软杀伤”到“激光硬摧毁”的立体防御网 反无人机技术本质是一场“降维打击”:用百万级防御系统对抗千元级消费无人机。当前…...
第十章——8天Python从入门到精通【itheima】-102-Python基础综合案例-数据可视化(pyecharts的入门使用+数据处理)
目录 102节——pyecharts的入门使用 1.学习目标 2.pyecharts入门——基础折线图 3.pyecharts的配置对象有哪些? 4.全局配置——set_global_opts 5.小节总结 103节——数据处理 1.学习目标 2.无法继续关于第一阶段的pyecharts的相关学习 因为关于JSON数据获…...
Neo4j 中存储和查询数组数据的完整指南
Neo4j 中存储和查询数组数据的完整指南 图形数据库 Neo4j 不仅擅长处理节点和关系,还提供了强大的数组(Array)存储和操作能力。本文将全面介绍如何在 Neo4j 中高效地使用数组,包括存储、查询、优化以及实际应用场景。 数组在 Neo4j 中的基本使用 数组…...
云原生/容器相关概念记录
文章目录 网络与虚拟化技术云平台与架构容器与编排容器网络方案性能优化与工具硬件与协议 网络与虚拟化技术 P4可编程网关 P4: Programming Protocol-independent Packet Processors一种基于P4语言的可编程网络设备,支持自定义数据包处理逻辑。P4可编程技术详解&am…...
uni-app项目实战笔记21--uniapp缓存的写入和读取
一、缓存的写入 uni.setStorageSync("storageClassList",classifyList.value) 二、缓存的读取,如果缓存不存在,则返回空数组 const storageClassList uni.getStorageSync("storageClassList") || []; 三、对读取到的数据进行转…...