从0开始搭建vue + flask 旅游景点数据分析系统(九):旅游景点管理之增删改查

news2024/9/22 9:39:59

这一期来做旅游景点数据的增删改查

先看下我们做好的效果是这样的:
在这里插入图片描述## 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 // 消息框显示的时长(毫秒)
        });
      })
    },

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2042171.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#学习笔记15:上位机助手_usercontrol窗体内嵌的应用

今日完善一下之前的上位机助手&#xff0c;做一个组合窗体内嵌的多功能助手软件应用, 与之前的上位机软件相比: 更注重控件能够随着窗体缩放而缩放变换&#xff0c;串口助手部分能自动后台检测串口设备&#xff0c;解决市面上大部分串口助手的打开初始化会卡顿的问题 ( 多线程后…

Android全面解析之context机制(三): 从源码角度分析context创建流程(下)

前言 前面已经讲了什么是context以及从源码角度分析context创建流程&#xff08;上&#xff09;。限于篇幅把四大组件中的广播和内容提供器的context获取流程放在了这篇文章。广播和内容提供器并不是context家族里的一员&#xff0c;所以他们本身并不是context&#xff0c;因而…

Spring日志

1.日志的作用 定位和发现问题(主要)系统监控数据采集日志审计...... 2.日志的使用 2.1 ⽇志格式的说明 2.2 打印日志 Spring集成了日志框架,直接使用即可 步骤: 1.定义日志对象 2.使⽤⽇志对象打印⽇志 RestController public class LoggerController {private static Logger…

Ecovadis认证评估什么 Ecovadis认证有哪些注意事

Ecovadis认证是一个全球性的企业可持续性评估平台&#xff0c;它通过评估企业在环境、劳工与人权、公平商业实践、可持续采购等四个领域的表现&#xff0c;帮助企业识别潜在风险&#xff0c;提升ESG(环境、社会和公司治理)绩效&#xff0c;实现可持续发展 Ecovadis认证注意事项…

Linux-文件系统与日志分析

系列文章目录 提示&#xff1a;仅用于个人学习&#xff0c;进行查漏补缺使用。 1.Linux介绍、目录结构、文件基本属性、Shell 2.Linux常用命令 3.Linux文件管理 4.Linux 命令安装(rpm、install) 5.Linux账号管理 6.Linux文件/目录权限管理 7.Linux磁盘管理/文件系统 8.Linu…

MapReduce 简单介绍

MapReduce 一、MapReduce概述二、MapReduce 基本设计思想分而治之2.2 抽象成模型2.3 上升到框架 三、MapReduce 优缺点3.1 MapReduce 的优点3.1 MapReduce 的缺点 四、MapReduce 编程模型4.1 MapReduce 分布式计算原理4.2 MapReduce 编程模型4.3 剖析 MapReduce 编程模型4.3.1 …

好书推荐!《Building LLM Apps》构建大语言模型LLM应用!一次性讲清楚!

《Building LLM Apps》这本书是一份全面而实用的指南&#xff0c;它不仅介绍了大型语言模型&#xff08;LLM&#xff09;的基础知识和前沿技术&#xff0c;还深入探讨了如何将这些模型应用到实际的AI应用中。 书中从对LLM的深入介绍入手&#xff0c;接着探讨了包括GPT 3.5、GP…

RxJava在Android中的应用

RxJava是一个基于事件流、异步和响应式编程的库&#xff0c;它在Android开发中广泛用于简化异步操作和事件处理。通过RxJava&#xff0c;我们可以以声明式方式管理异步任务&#xff0c;并有效整合多个数据源。 1. RxJava核心组件介绍 1.1 Observable与Observer Observable&a…

大模型面试系列-大模型算法工程师的面试题目与解答技巧详细说明

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下大模型面试系列-大模型算法工程师的面试题目与解答技巧详细说明。 文章目录 大模型算法工程师面试题1. Llama 2 中使用的注意力机制是什么&#xff1f;描述一下查询分组注意力。2. LangChain 的结构详细描述一下。…

2024年8月15日嵌入式学习

今日主要学习线程和线程的互斥锁 pthread_cancel函数 它用于取消一个线程&#xff0c;当一个线程收到取消的申请时&#xff0c;他不会立即停止&#xff0c;而是在下一个取消点处结束运行&#xff0c;取消点是程序中一个特定的位置。如果线程在执行一个不可中断的系统调用&…

网络安全风险扫描原理及工具使用

课程目标 1.熟悉常见网络安全风险扫描工具 2.了解网络安全风险扫描原理 3.掌握扫描工具使用方法 为什么要做网络安全风险扫描&#xff1f; 什么是网络安全风险扫描&#xff1f; 通过一定的技术手段发现系统和软件存在的安全漏洞、弱口令 网络安全风险扫描的目的&#xff1…

【AI 绘画】web_ui 搭建(基于gradio)

AI 绘画- web_ui 搭建(基于gradio) 1. 内容介绍 Gradio的优势在于易用性,代码结构相比Streamlit简单,只需简单定义输入和输出接口即可快速构建简单的交互页面,更轻松部署模型。适合场景相对简单,想要快速部署应用的开发者。便于分享:gradio可以在启动应用时设置share=…

QT文件操作实战

QT文件操作实战 页面布局如下 读取文件:文件→界面文本框 采用“浏览”按钮的槽函数,编写的代码如下 void Widget::on_pushButton_clicked() {//读取txt文件,获取要打开的文件名,并将文件名(包含)填入lineEdit中// QString fileName = QFileDialog::getOpenFileName(th…

云HIS平台源码,云医院管理信息系统源码,云HIS医疗卫生管理系统源码

云医院管理信息系统源码&#xff0c;云HIS医疗卫生管理系统源码&#xff0c;医疗云HIS系统源码&#xff0c;自主版权二级医院应用案例 云HIS平台采用SaaS服务模式&#xff0c;软件使用者无需购置额外硬件设备、软件许可证及安装和维护软件系统&#xff0c;通过互联网浏览器在任…

YS9082HP量产工具,支持N38B开卡(ID:89D3AC32C204),解决YS9082HP N38B开卡到87%报错,状态8817,Fail:写表失败

收的固态硬盘&#xff0c;主控是YS9082HP&#xff0c;颗粒是Intel的N38B&#xff1a; 从网上找了个YS9082HP_MPToolV8.00.00.01.025_HPS2704M_release_N38B版本试试&#xff0c;倒是能识别颗粒&#xff0c;到87%就报错&#xff0c;Fail:写表失败&#xff0c;错误状态是8817&…

山东易注册网络科技有限公司:合伙人模式的机遇与创新

在互联网高速发展的今天&#xff0c;合伙人模式成为网络运营的新趋势。山东易注册网络科技有限公司以其创新的合伙人模式&#xff0c;为用户带来了前所未有的机遇。 加入山东易注册的合伙人&#xff0c;可以享受到独立搭建系统和独立服务器的权益。用户可以打造自己的独立域名和…

怎样用python函数画图像

打开Python的shell界面&#xff0c;如图所示。&#xff08;注意我们需要已经安装了matplotlib库包&#xff09;。 输入以下代码&#xff0c;导入我们用到的函数库。 >>> import numpy as np >>> import matplotlib.pyplot as plt 产生我们要画的的函数的数据…

数据集的简单制作和使用

数据集的简单制作和使用 参考资料&#xff1a;Labelme分割标注软件使用 使用labelme软件对数据集进行分割 每张图片获得一个json文件 我们看看其中一个文件&#xff0c;内容包含每个点在图片中的位置 我们可以自己写一个脚本&#xff08;或使用别人的&#xff09;将上述json…

突破传统看车局限,3DCAT实时云渲染为东风日产奇骏赋能

在当今数字化飞速发展的时代&#xff0c;汽车行业的营销也面临着诸多变革与挑战。线下展示由于受到场地空间的限制&#xff0c;往往无法全面展示所有车型&#xff0c;且建设成本高昂。而一些销售门店可能因位置偏僻等因素&#xff0c;导致客户上门看车、试驾的邀约变得困难重重…

哈工大李治军老师OS课程笔记(4)——内存管理

一 内存使用与分段&#xff08;实验六&#xff09; 内存是如何用起来的&#xff1f; 内存使用&#xff1a;将程序放在内存中&#xff0c;PC指向开始地址 重定位&#xff1a;修改程序中的地址&#xff08;是相对地址&#xff09; 什么时候完成重定位&#xff1f; 编译时加基址…