这一期来做旅游景点数据的增删改查
先看下我们做好的效果是这样的:
## 1 后台接口
这里的接口已经考虑到了分页的情况,因为前端的表格是带有分页的,接受的前端传过来的get参数为 title 、page、 limit ,titie是查询的关键词,会根据景点的title去模糊查询。
@main.route('/tours', methods=['GET'])
def get_tours():
try:
title = request.args.get('title', '') # 获取查询参数中的 title
page = int(request.args.get('page', 1)) # 获取当前页码,默认为 1
limit = int(request.args.get('limit', 10)) # 获取每页显示的记录数,默认为 10
# 根据 title 进行模糊搜索
query = Tour.query.filter(Tour.title.like(f'%{title}%'))
# 计算总数和获取当前页数据
total = query.count() # 总记录数
tours = query.offset((page - 1) * limit).limit(limit).all() # 当前页的数据
result = tours_schema.dump(tours) # 使用你的序列化方案处理数据
return make_response(data={'total': total, 'records': result})
except Exception as e:
return make_response(code=1, message=str(e))
2 前端表格
前端表格分为表头、表格数据部分、分页,放在一个el-card中,注意到表格栏中有景点图片的处理,可以在表格中直接展示图片。
<el-card class="box-card">
<div slot="header" class="header">
<span class="header-title">旅游景点管理</span>
<div class="header-controls">
<el-input v-model="searchTitle" placeholder="输入标题进行搜索" class="search-input"></el-input>
<el-button type="primary" @click="fetchData">搜索</el-button>
<el-button type="success" @click="handleAddTour">添加景点</el-button>
</div>
</div>
<el-table :data="tours" style="width: 100%">
<el-table-column label="图片" width="120">
<template slot-scope="scope">
<el-image
:src="scope.row.img"
class="tour-image"
:alt="scope.row.title"
fit="cover"
lazy
/>
</template>
</el-table-column>
<el-table-column prop="title" label="景点名称" min-width="180"></el-table-column>
<el-table-column prop="title_en" label="别名" min-width="180"></el-table-column>
<el-table-column prop="comments" label="评论数" min-width="100"></el-table-column>
<el-table-column prop="score" label="评分" min-width="100"></el-table-column>
<el-table-column prop="nation" label="国家" min-width="120"></el-table-column>
<el-table-column prop="city" label="城市" min-width="120"></el-table-column>
<el-table-column label="操作" min-width="180">
<template slot-scope="scope">
<el-button @click="handleEditTour(scope.row)" type="text" size="small">编辑</el-button>
<el-button @click="handleDeleteTour(scope.row)" type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-size="pageSize"
:total="totalItems"
layout="total, sizes, prev, pager, next, jumper"
/>
</el-card>
数据部分是这样的,原先的静态数据改成[]就行了,通过后台接口来获取数据:
data() {
return {
searchTitle: '',
tours: [],
// tours: [
// { id: 1, name: '东京迪士尼度假区', alias: 'Tokyo Disney Resort', reviewCount: 1500, rating: 4.8, featuredReview: '非常美丽的景点', country: '日本', city: '东京' },
// { id: 2, name: '东京塔', alias: 'Tokyo Tower', reviewCount: 2500, rating: 4.9, featuredReview: '历史悠久,气势恢宏', country: '日本', city: '东京' },
// { id: 3, name: '三鹰之森吉卜力美术馆', alias: 'Ghibli Museum', reviewCount: 1800, rating: 4.7, featuredReview: '象征自由的地标', country: '日本', city: '东京' }
// ],
dialogVisible: false,
form: {},
formLabelWidth: '100px',
totalItems: 0,
currentPage: 1,
pageSize: 10,
};
},
在页面加载的时候默认让它显示第一页,进行搜索的时候,会根据关键词去查询后台接口:
mounted() {
this.currentPage = 1
this.loadData()
},
methods: {
fetchData() {
this.loadData()
},
//加载数据
loadData() {
tours(this.searchTitle, this.currentPage, this.pageSize).then(res => {
// console.log(res.data.data.records);
this.tours = res.data.data.records
this.totalItems = res.data.data.total
})
},
handleCurrentChange(page) {
this.currentPage = page;
this.loadData();
},
handleSizeChange(size) {
this.pageSize = size;
this.loadData();
},
}
还有两个分页相关的方法也可以注意下,因为分页控件可以调整页数、每页显示的数量,点击之后也需要加载数据,而且假如说这时候是有搜索关键词的,关键词也需要保持。
样式部分
.tours-container {
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
}
.dialog-footer {
text-align: right;
}
.header-title {
font-size: 18px;
font-weight: bold;
}
.header-controls {
display: flex;
align-items: center;
}
.search-input {
width: 300px;
margin-right: 10px; /* Adjust spacing between input and buttons */
}
在tour.js中需要添加新的接口:
export function tours(title,page=1,limit=10){
return request({
url: '/tours',
method: 'get',
params: {title, page, limit}
})
}
3 搜索效果测试
4 新增景点
先增加后端方法:
@main.route('/tour', methods=['POST'])
def add_tour():
data = request.json # 获取JSON数据
# 这里可以进行数据验证,例如检查必填字段是否存在
required_fields = ['img', 'title', 'title_en', 'comments', 'score', 'select_comment', 'nation', 'city']
for field in required_fields:
if field not in data:
return make_response(code=1, message= f'错误,缺少字段: {field}')
# 创建新的景点对象
new_tour = Tour(
img=data['img'],
title=data['title'],
title_en=data['title_en'],
comments=data['comments'],
score=data['score'],
select_comment=data['select_comment'],
nation=data['nation'],
city=data['city'] )
前端部分我们先增加一个接口,使用的是POST请求:
export function addTour(data){
return request({
url: '/tour',
method: 'post',
data
})
}
页面部分是先点击添加景点,触发handleAddTour,打开对话框:
handleAddTour() {
this.dialogTitle = '新增景点'
this.dialogVisible = true;
this.form = {
img: '',
title: '',
title_en: '',
comments: 0,
score: 0,
select_comment: '',
nation: '',
city: ''
};
},
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible">
<el-form :model="form">
<el-form-item label="景点图片地址" :label-width="formLabelWidth">
<el-input v-model="form.img"></el-input>
</el-form-item>
<el-form-item label="景点名称" :label-width="formLabelWidth">
<el-input v-model="form.title"></el-input>
</el-form-item>
<el-form-item label="别名" :label-width="formLabelWidth">
<el-input v-model="form.title_en"></el-input>
</el-form-item>
<el-form-item label="评论数" :label-width="formLabelWidth">
<el-input v-model="form.comments" type="number"></el-input>
</el-form-item>
<el-form-item label="评分" :label-width="formLabelWidth">
<el-input v-model="form.score" type="number"></el-input>
</el-form-item>
<el-form-item label="精选评论" :label-width="formLabelWidth">
<el-input v-model="form.select_comment"></el-input>
</el-form-item>
<el-form-item label="国家" :label-width="formLabelWidth">
<el-input v-model="form.nation"></el-input>
</el-form-item>
<el-form-item label="城市" :label-width="formLabelWidth">
<el-input v-model="form.city"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveTour">保存</el-button>
</div>
</el-dialog>
对话框填写完毕后,点击handleSaveTour,有后台交互,这里为了偷懒,这个对话框我们打算既支持新增也支持编辑,所以对话框的标题也是动态的数据:
handleSaveTour() {
if (this.form.id) {
//更新景点信息的逻辑
} else {
addTour(this.form).then(res=>{
console.log(res.data.message)
const messageType = res.data.code === 0 ? 'success' : 'error';
Message({
message: res.data.message,
type: messageType, // 'success' 或者 'error' 根据需要
duration: 2000 // 消息框显示的时长(毫秒)
});
})
}
this.dialogVisible = false;
},
根据返回code,弹出不同的消息框(elmentui的Message)。
我们添加了一个测试景点:
返回消息框:
5 修改景点
先新增前后端的接口:
@main.route('/tour/<int:id>', methods=['PUT'])
def update_tour(id):
data = request.json # 获取JSON数据
tour = Tour.query.get(id) # 根据ID查找景点
if not tour:
return make_response(code=1, message='景点不存在')
# 更新景点的字段
for field in ['img', 'title', 'title_en', 'comments', 'score', 'select_comment', 'nation', 'city']:
if field in data:
setattr(tour, field, data[field])
db.session.commit()
return make_response(code=0, message='修改景点成功')
tour.js
// 修改现有景点
export function updateTour(id, data) {
return request({
url: `/tour/${id}`,
method: 'put',
data
});
}
然后就很容易,修改补全刚才未完成的*handleSaveTour
就行了,基本上和新增的都一样。逻辑就是如果form.id 存在,说明是一个修改操作,如果不存在,说明是一个新增操作。*
handleSaveTour() {
if (this.form.id) {
updateTour(this.form.id, this.form).then(res=>{
console.log(res.data.message)
const messageType = res.data.code === 0 ? 'success' : 'error';
Message({
message: res.data.message,
type: messageType, // 'success' 或者 'error' 根据需要
duration: 2000 // 消息框显示的时长(毫秒)
});
})
} else {
addTour(this.form).then(res=>{
console.log(res.data.message)
const messageType = res.data.code === 0 ? 'success' : 'error';
Message({
message: res.data.message,
type: messageType, // 'success' 或者 'error' 根据需要
duration: 2000 // 消息框显示的时长(毫秒)
});
})
}
this.dialogVisible = false;
},
6 删除景点
先增加后端接口
@main.route('/tour/<int:id>', methods=['DELETE'])
def delete_tour(id):
tour = Tour.query.get(id) # 根据ID查找景点
if not tour:
return make_response(code=1, message='景点不存在')
db.session.delete(tour)
db.session.commit()
return make_response(code=0, message='删除景点成功')
然后修改前端tour.js
// 删除景点
export function deleteTour(id) {
return request({
url: `/tour/${id}`,
method: 'delete'
});
}
最后就是完善一下 *handleDeleteTour*
这个方法就是点击每行上的删除按钮触发的:
handleDeleteTour(tour) {
deleteTour(tour.id).then(res=>{
const messageType = res.data.code === 0 ? 'success' : 'error';
Message({
message: res.data.message,
type: messageType, // 'success' 或者 'error' 根据需要
duration: 2000 // 消息框显示的时长(毫秒)
});
})
},