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

6 months ago
<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>
6 months ago
</div>
<div class="stat-card green">
<div class="stat-number">856</div>
<div class="stat-label">可借阅</div>
6 months ago
</div>
<div class="stat-card orange">
<div class="stat-number">391</div>
<div class="stat-label">已借出</div>
6 months ago
</div>
<div class="stat-card purple">
<div class="stat-number">23</div>
<div class="stat-label">维护中</div>
6 months ago
</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">
6 months ago
<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;">支持 JPGPNG 格式建议尺寸 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>
6 months ago
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'
6 months ago
},
{
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'
6 months ago
},
{
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'
6 months ago
}
],
total: 1247,
listQuery: {
page: 1,
limit: 10
6 months ago
},
multipleSelection: [],
bookForm: {
title: '',
author: '',
isbn: '',
publisher: '',
year: null,
category: '',
description: '',
6 months ago
cover: '',
cover_id: ''
6 months ago
},
bookRules: {
title: [
{ required: true, message: '请输入书名', trigger: 'blur' }
],
author: [
{ required: true, message: '请输入作者', trigger: 'blur' }
],
category: [
6 months ago
{ required: true, message: '请输入分类', trigger: 'blur' }
]
6 months ago
}
}
},
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'
6 months ago
}
6 months ago
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()
},
6 months ago
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
}
6 months ago
// 预览
const reader = new FileReader()
reader.onload = (e) => {
this.bookForm.cover = e.target.result
}
reader.readAsDataURL(file)
6 months ago
// 上传
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('封面上传失败')
}
}
},
6 months ago
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)'
})
6 months ago
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()
6 months ago
// 重新获取列表或手动添加
} 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: '',
6 months ago
cover: '',
cover_id: ''
}
},
getCategoryText(category) {
const categoryMap = {
tech: '技术类',
business: '商业类',
management: '管理类',
finance: '金融类'
}
6 months ago
return categoryMap[category] || category || ''
6 months ago
}
}
}
</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;
6 months ago
}
.page-header {
flex-direction: column;
gap: 15px;
text-align: center;
6 months ago
}
.action-buttons {
flex-direction: column;
6 months ago
}
.search-section .el-form {
display: block;
6 months ago
}
.search-section .el-form-item {
margin-bottom: 10px;
6 months ago
}
}
@media (max-width: 480px) {
.book-stats {
grid-template-columns: 1fr;
6 months ago
}
.library-container {
padding: 10px;
6 months ago
}
.stat-number {
font-size: 24px;
6 months ago
}
}
</style>