You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

738 lines
20 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="library-container">
<!-- 页面标题 -->
<div class="page-header">
<h2 class="page-title">
<i class="el-icon-s-comment"></i>
图书管理
</h2>
<el-button type="primary" icon="el-icon-plus" @click="showUploadModal = true">
添加图书
</el-button>
</div>
<!-- 图书统计 -->
<div class="book-stats">
<div class="stat-card blue">
<div class="stat-number">1,247</div>
<div class="stat-label">总图书数量</div>
</div>
<div class="stat-card green">
<div class="stat-number">856</div>
<div class="stat-label">可借阅</div>
</div>
<div class="stat-card orange">
<div class="stat-number">391</div>
<div class="stat-label">已借出</div>
</div>
<div class="stat-card purple">
<div class="stat-number">23</div>
<div class="stat-label">维护中</div>
</div>
</div>
<!-- 搜索区域 -->
<div class="search-section">
<el-form :model="filters" inline>
<el-form-item>
<el-input v-model="filters.keyword" placeholder="搜索书名、作者、ISBN"></el-input>
</el-form-item>
<el-form-item>
<el-select v-model="filters.category" placeholder="全部分类" clearable>
<el-option label="技术类" value="tech"></el-option>
<el-option label="商业类" value="business"></el-option>
<el-option label="管理类" value="management"></el-option>
<el-option label="金融类" value="finance"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="filters.status" placeholder="全部状态" clearable>
<el-option label="可借阅" value="available"></el-option>
<el-option label="已借出" value="borrowed"></el-option>
<el-option label="维护中" value="maintenance"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
</el-form-item>
<el-form-item>
<el-button type="success" icon="el-icon-download" @click="handleExport">导出数据</el-button>
</el-form-item>
<el-form-item>
<el-button type="info" icon="el-icon-upload2" @click="handleImport">批量导入</el-button>
</el-form-item>
</el-form>
</div>
<!-- 图书列表表格 -->
<div class="table-container">
<el-table
:data="list"
@selection-change="handleSelectionChange"
style="width: 100%"
>
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="封面" width="80">
<template slot-scope="scope">
<img :src="scope.row.cover" alt="图书封面" class="book-cover">
</template>
</el-table-column>
<el-table-column label="图书信息" min-width="250">
<template slot-scope="scope">
<div class="book-title">{{ scope.row.title }}</div>
<div class="book-author">作者:{{ scope.row.author }} · 出版社:{{ scope.row.publisher }} · {{ scope.row.year }}年</div>
</template>
</el-table-column>
<el-table-column label="分类" width="100">
<template slot-scope="scope">
<el-tag :type="getCategoryTagType(scope.row.category)" size="small">{{ scope.row.categoryText }}</el-tag>
</template>
</el-table-column>
<el-table-column label="ISBN" width="150">
<template slot-scope="scope">
{{ scope.row.isbn }}
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template slot-scope="scope">
<el-tag :type="getStatusTagType(scope.row.status)" size="small">{{ scope.row.statusText }}</el-tag>
</template>
</el-table-column>
<el-table-column label="添加时间" width="150">
<template slot-scope="scope">
<div class="time-display">{{ scope.row.addDate }}</div>
<div class="time-display-secondary">{{ scope.row.addTime }}</div>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template slot-scope="scope">
<div class="action-buttons">
<el-button type="primary" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="info" size="mini" icon="el-icon-view" @click="handleView(scope.row)">详情</el-button>
<el-button v-if="scope.row.status === 'borrowed'" type="warning" size="mini" icon="el-icon-refresh-left" @click="handleReturn(scope.row)">归还</el-button>
<el-button type="danger" size="mini" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页区域 -->
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="listQuery.page"
:page-sizes="[10, 20, 50]"
:page-size="listQuery.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</div>
<!-- 添加图书弹窗 -->
<el-dialog
title="添加图书"
:visible.sync="showUploadModal"
width="600px"
:before-close="handleCloseModal"
>
<el-form :model="bookForm" :rules="bookRules" ref="bookForm" label-width="100px">
<!-- 基本信息 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-info"></i>
基本信息
</div>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="书名" prop="title">
<el-input v-model="bookForm.title" placeholder="请输入书名"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="作者" prop="author">
<el-input v-model="bookForm.author" placeholder="请输入作者"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="ISBN">
<el-input v-model="bookForm.isbn" placeholder="978-7-xxx-xxxxx-x"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出版社">
<el-input v-model="bookForm.publisher" placeholder="请输入出版社"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="出版年份">
<el-input-number v-model="bookForm.year" :min="1900" :max="2030" placeholder="年份"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分类" prop="category">
<el-input v-model="bookForm.category" placeholder="请输入分类"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="图书简介">
<el-input
type="textarea"
v-model="bookForm.description"
:rows="3"
:maxlength="500"
placeholder="请输入图书简介"
show-word-limit
></el-input>
</el-form-item>
</div>
<!-- 封面上传 -->
<div class="form-section">
<div class="section-title">
<i class="el-icon-picture"></i>
图书封面
</div>
<div class="upload-area" @click="triggerCoverUpload">
<div v-if="!bookForm.cover" class="upload-placeholder">
<i class="el-icon-upload" style="font-size: 32px; color: #9ca3af; margin-bottom: 10px;"></i>
<div style="color: #374151; font-weight: 500;">点击上传图书封面</div>
<div style="color: #9ca3af; font-size: 12px; margin-top: 5px;">支持 JPG、PNG 格式,建议尺寸 300×400</div>
</div>
<img v-else :src="bookForm.cover" alt="封面预览" class="upload-preview">
</div>
<input
ref="coverInput"
type="file"
accept="image/*"
style="display: none;"
@change="handleCoverUpload"
>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="showUploadModal = false">取消</el-button>
<el-button type="primary" @click="saveBook"></el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { save } from '@/api/library'
import { uploads } from '@/api/uploads'
export default {
name: 'Library',
data() {
return {
showUploadModal: false,
filters: {
keyword: '',
category: '',
status: ''
},
list: [
{
id: 1,
title: '深度学习实战指南',
author: '张三',
publisher: '机械工业出版社',
year: '2023',
isbn: '978-7-111-12345-6',
category: 'tech',
categoryText: '技术类',
status: 'available',
statusText: '可借阅',
cover: 'https://via.placeholder.com/50x70/4285f4/ffffff?text=书',
addDate: '2024-01-15',
addTime: '10:30'
},
{
id: 2,
title: '创业公司股权设计',
author: '李四',
publisher: '中信出版社',
year: '2023',
isbn: '978-7-508-67890-1',
category: 'business',
categoryText: '商业类',
status: 'borrowed',
statusText: '已借出',
cover: 'https://via.placeholder.com/50x70/ff9800/ffffff?text=书',
addDate: '2024-01-14',
addTime: '15:20'
},
{
id: 3,
title: '敏捷项目管理实践',
author: '王五',
publisher: '电子工业出版社',
year: '2022',
isbn: '978-7-121-34567-8',
category: 'management',
categoryText: '管理类',
status: 'available',
statusText: '可借阅',
cover: 'https://via.placeholder.com/50x70/4caf50/ffffff?text=书',
addDate: '2024-01-13',
addTime: '09:15'
}
],
total: 1247,
listQuery: {
page: 1,
limit: 10
},
multipleSelection: [],
bookForm: {
title: '',
author: '',
isbn: '',
publisher: '',
year: null,
category: '',
description: '',
cover: '',
cover_id: ''
},
bookRules: {
title: [
{ required: true, message: '请输入书名', trigger: 'blur' }
],
author: [
{ required: true, message: '请输入作者', trigger: 'blur' }
],
category: [
{ required: true, message: '请输入分类', trigger: 'blur' }
]
}
}
},
methods: {
handleSearch() {
console.log('搜索条件:', this.filters)
this.$message.success('搜索已触发')
},
handleExport() {
console.log('导出数据')
this.$message.info('数据导出中...')
},
handleImport() {
console.log('批量导入')
this.$message.info('批量导入功能')
},
handleSelectionChange(val) {
this.multipleSelection = val
},
getCategoryTagType(category) {
const categoryMap = {
tech: 'primary',
business: 'success',
management: 'warning',
finance: 'danger'
}
return categoryMap[category] || 'info'
},
getStatusTagType(status) {
const statusMap = {
available: 'success',
borrowed: 'warning',
maintenance: 'danger'
}
return statusMap[status]
},
handleEdit(row) {
console.log('编辑:', row.id)
this.$message.info('跳转到编辑页面')
},
handleView(row) {
console.log('查看详情:', row.id)
this.$message.info('跳转到详情页面')
},
handleReturn(row) {
this.$confirm('确认这本图书已归还吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
console.log('归还图书:', row.id)
row.status = 'available'
row.statusText = '可借阅'
this.$message.success('图书归还成功!')
}).catch(() => {
this.$message.info('已取消归还')
})
},
handleDelete(row) {
this.$confirm('确定要删除这本图书吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
console.log('删除图书:', row.id)
// 这里应该调用删除API
const index = this.list.findIndex(item => item.id === row.id)
if (index > -1) {
this.list.splice(index, 1)
}
this.$message.success('图书删除成功!')
}).catch(() => {
this.$message.info('已取消删除')
})
},
handleSizeChange(val) {
this.listQuery.limit = val
console.log('每页显示条数:', val)
},
handleCurrentChange(val) {
this.listQuery.page = val
console.log('当前页:', val)
},
handleCloseModal() {
this.resetForm()
this.showUploadModal = false
},
triggerCoverUpload() {
this.$refs.coverInput.click()
},
async handleCoverUpload(event) {
const file = event.target.files[0]
if (file) {
if (!file.type.match(/^image\/(jpeg|jpg|png)$/)) {
this.$message.error('请选择 JPG 或 PNG 格式的图片')
return
}
if (file.size > 2 * 1024 * 1024) {
this.$message.error('图片大小不能超过 2MB')
return
}
// 预览
const reader = new FileReader()
reader.onload = (e) => {
this.bookForm.cover = e.target.result
}
reader.readAsDataURL(file)
// 上传
const formData = new FormData()
formData.append('file', file)
try {
const res = await uploads(formData)
this.bookForm.cover_id = res.id
this.$message.success('封面上传成功')
} catch (e) {
this.$message.error('封面上传失败')
}
}
},
async saveBook() {
this.$refs.bookForm.validate(async (valid) => {
if (valid) {
const loading = this.$loading({
lock: true,
text: '正在保存...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
const params = {
title: this.bookForm.title,
author: this.bookForm.author,
isbn: this.bookForm.isbn,
publisher: this.bookForm.publisher,
publish_year: this.bookForm.year,
category: this.bookForm.category,
description: this.bookForm.description,
cover_id: this.bookForm.cover_id
}
console.log('params', params)
await save(params)
loading.close()
this.$message.success('图书添加成功!')
this.showUploadModal = false
this.resetForm()
// 重新获取列表或手动添加
} catch (e) {
loading.close()
this.$message.error('添加失败,请重试' + e.message)
}
} else {
this.$message.error('请填写必填字段')
return false
}
})
},
resetForm() {
this.$refs.bookForm.resetFields()
this.bookForm = {
title: '',
author: '',
isbn: '',
publisher: '',
year: null,
category: '',
description: '',
cover: '',
cover_id: ''
}
},
getCategoryText(category) {
const categoryMap = {
tech: '技术类',
business: '商业类',
management: '管理类',
finance: '金融类'
}
return categoryMap[category] || category || ''
}
}
}
</script>
<style scoped>
.library-container {
padding: 20px;
background-color: #f8f9fa;
min-height: 100vh;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 2px solid #e9ecef;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.page-title {
font-size: 24px;
font-weight: bold;
color: #2c3e50;
display: flex;
align-items: center;
margin: 0;
}
.page-title i {
margin-right: 10px;
color: #3498db;
font-size: 28px;
}
.book-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
border-left: 4px solid;
text-align: center;
}
.stat-card.blue { border-left-color: #3498db; }
.stat-card.green { border-left-color: #2ecc71; }
.stat-card.orange { border-left-color: #f39c12; }
.stat-card.purple { border-left-color: #9b59b6; }
.stat-number {
font-size: 28px;
font-weight: bold;
color: #2c3e50;
}
.stat-label {
font-size: 14px;
color: #7f8c8d;
margin-top: 5px;
}
.search-section {
background: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.table-container {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.book-cover {
width: 50px;
height: 70px;
object-fit: cover;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.book-title {
font-weight: 600;
color: #2c3e50;
margin-bottom: 4px;
font-size: 14px;
}
.book-author {
font-size: 12px;
color: #7f8c8d;
}
.time-display {
font-size: 12px;
color: #333;
}
.time-display-secondary {
font-size: 11px;
color: #666;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.action-buttons .el-button {
margin: 0;
}
.pagination-container {
display: flex;
justify-content: center;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.form-section {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
margin-bottom: 15px;
display: flex;
align-items: center;
}
.section-title i {
margin-right: 8px;
color: #3498db;
font-size: 18px;
}
.upload-area {
border: 2px dashed #d1d5db;
border-radius: 8px;
padding: 30px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
background: white;
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.upload-area:hover {
border-color: #3498db;
background: rgba(52, 152, 219, 0.05);
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
}
.upload-preview {
max-width: 120px;
max-height: 160px;
border-radius: 4px;
object-fit: cover;
}
.dialog-footer {
text-align: right;
}
/* Element UI 表格样式覆盖 */
::v-deep .el-table th {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: 600;
}
::v-deep .el-table tbody tr:hover > td {
background-color: #f8f9fa;
}
::v-deep .el-pagination {
text-align: center;
}
/* 响应式设计 */
@media (max-width: 768px) {
.book-stats {
grid-template-columns: 1fr 1fr;
}
.page-header {
flex-direction: column;
gap: 15px;
text-align: center;
}
.action-buttons {
flex-direction: column;
}
.search-section .el-form {
display: block;
}
.search-section .el-form-item {
margin-bottom: 10px;
}
}
@media (max-width: 480px) {
.book-stats {
grid-template-columns: 1fr;
}
.library-container {
padding: 10px;
}
.stat-number {
font-size: 24px;
}
}
</style>