flask+vue:创建一个数据列表并实现简单的查询功能(一)
之前写过一个数据构造工具,当时用的Django+vue,后来又用flask重写了一下后端逻辑
本次打算在现有基础上加点东西:新增一个数据列表,数据列表中展示曾经创建好的数据
拆解下这次要做的功能:
1、每创建一次数据,都把数据写到数据库中;
2、从数据库中查询创建好的数据返给前端;
3、前端构建一个数据列表,把后端数据渲染到列表中;
4、数据列表添加查询功能;
5、数据列表添加分页功能
1、添加查询功能
在页面添加列表查询功能,我需要构造2个查询条件:
【数据类型】,把它做成下拉框形式,筛选对应类型的数据
【创建日期】,通过日期筛选创建日期在所选时间范围内的数据
点【查询】会把对应参数传到请求中,筛选符合条件的结果;
点【重置】会清空查询框输入的条件;
这里要用到element-ui中Select 选择器
、Form 表单
、DatePicker 日期选择器
这部分样式代码如下
<el-row> <el-col :span="24"> <div class="grid-content bg-purple-dark"> <el-form :inline="true" :model="form" size="small" ref="ruleForm" class="demo-form-inline" style="margin-left: 10px"> <el-form-item label="数据类型" prop="class"> <el-select v-model="form.class" placeholder="请选择数据类型" clearable> <el-option label="电话" value="1"></el-option> <el-option label="身份证ID" value="2"></el-option> <el-option label="姓名" value="3"></el-option> </el-select> </el-form-item> <el-form-item label="创建日期" prop="create_date"> <el-date-picker v-model="form.create_date" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd HH:mm:ss" :default-time="['00:00:00', '23:59:59']"> </el-date-picker> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')">查询</el-button> <el-button @click="resetForm('ruleForm')">重置</el-button> </el-form-item> </el-form> </div> </el-col> </el-row>
对应的js代码
<script> export default { data() { return { form: { class: '', create_date: '', } } }, methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { let payload = { // 定义请求参数 class_type: this.form.class, create_date: this.form.create_date } console.log("打印查询条件输入的参数payload") console.log(payload) console.log("打印日期框选择框填写的日期") console.log(this.form.create_date) } else { console.log('error submit!!'); return false; } }); }, resetForm(formName) { this.$refs[formName].resetFields(); }, } } </script>
实现效果
代码说明:
1、点击【重置】,能够清空输入框输入的内容
在js代码中创建了2个方法submitForm()
和resetForm()
,分别绑定到【查询】【重置】按钮
如果想实现点击【重置】清空内容,需要给表单添加添加ref属性
和prop属性
ref的值 是调用 submitForm() 和 resetForm() 时传入的值,比如ref="form_data"
,则调用resetForm()时需要传入ref,即resetForm('form_data')
;
prop的值 是对应表单组件model的值,比如<el-select>中v-model="form.class"
,所以它对应的prop="class"
2、日期控件 DatePicker 的使用配置
日期这块期望实现这样一种效果:选择开始日期-结束日期后,例如2022-01-13~2011-01-15后,接口传参为 2022-01-13 00:00:00~2022-01-15 23:59:59
在element-ui官方文档中,可以找到相关配置参数
使用value-format
指定绑定值的格式,
例如value-format="yyyy-MM-dd HH:mm:ss"
使用default-time
指定起始日期的时刻与结束日期的时刻,
例如:default-time="['00:00:00', '23:59:59']"
3、定义请求参数,查看一下前端传的参数的具体值是什么样的
submitForm()方法中先定义了查询接口触发时所需的参数:一个是数据类型,一个是创建日期
class_type表示数据类型,create_date表示创建日期
它们分别获取前端传来的参数,打印一下结果
可以看到create_date是一个包含开始日期和结束日期数组,
接下来再看一下参数为空的清空
(1)数据类型、创建日期默认为空时,传的参数如下
create_date的值为''
(2)数据类型、创建日期先填写值再重置,传的参数如下
create_date的值为['']
可以看到创建日期默认为空时,传的值为''
,
先赋值再重置,传的值为为['']
所以后端处理create_date为空的情况时需要考虑这种情况
2、添加列表
使用Table 表格
组件添加一个列表展示数据
样式代码
<el-table :data="tableData" border height="350" style="width: 100%; margin-top: 30px;margin-left: 10px"> <!-- height 表格默认高度,添加height属性,即可实现固定表头的表格 --> <el-table-column type="index" width="50"> </el-table-column> <el-table-column prop="date" label="日期" width="180" align="center"> <!--使用align控制对齐方式--> </el-table-column> <el-table-column prop="type" label="类型" width="180" align="center"> </el-table-column> <el-table-column prop="value" label="生成的测试数据" align="center"> </el-table-column> </el-table>
上述代码中,在<el-table-column>中,使用 align="center"
控制每列标题的对齐方式,
:data="tableData"
,表示往列表中插入的数据
对应js代码
<script> export default { data() { return { tableData: [{ date: '2022-01-10', type: '电话号码', value: '13140845519' }, { date: '2022-01-10', name: '电话号码', value: '18136773435' }, { date: '2022-01-10', type: '电话号码', value: '14592741294' }] } } } </script>
上述代码中tableData
表示往列表中插入的数据,目前是一些假数据,等下从后端获取到数据后,需要把数据包装成这种格式赋给tableData
3、添加分页功能
使用 Pagination 分页
组件给列表进行分页
样式代码
<div class="block" style="margin-top: 10px; text-align: right;"> <!--使用text-align控制div右对齐--> <el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page.sync="currentPage" :page-sizes="[5, 10, 20, 30, 50]" :page-size="5" layout="total, sizes, prev, pager, next, jumper" :total="parseInt(count)"> </el-pagination> </div>
对应js代码
<script> export default { data() { return { form: { class: '', create_date: '', }, currentPage: 1, pageSize:5, count: null, tableData: null }; }, methods: { handleSizeChange(val) { console.log(`每页 ${val} 条`); this.pageSize = val console.log("打印当前的pageSize") console.log(this.pageSize) }, handleCurrentChange(val) { console.log(`当前页: ${val}`); }, submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { let payload = { // 定义请求参数 class_type: this.form.class, create_date: this.form.create_date, page_num: this.currentPage, page_size: this.pageSize } console.log("打印查询条件输入的参数payload") console.log(payload) console.log("打印日期框选择框填写的日期") console.log(this.form.create_date) } else { console.log('error submit!!'); return false; } }); } } } </script>
在分页组件中,需要用到几个参数:总数据条数total(共xx条)、每页条数page-size、当前页码current-page
其中总数据条数是根据当前查询条件查询后后端返回的数据总量;
当前页码、每页条数这2个参数需要跟着请求发送,后端根据参数来返回对应查询结果
上述js代码中,在data()
下新增了4个参数:
其中count
用来接收后端返回的数据总量,它的值必须为整数
tableData
用来接收接口返回并处理后的列表数据
其中currentPage
和pageSize
,分别表示当前页码和每页条数,等会儿给请求传参时,我们会用到它俩,所以我们用这2个参数接收前端的current-page和page-size
我期望达到的效果是当选择每页条数或者切换页码时,这个2个参数能够传给后端实时的数值
这里有2种实现方式,一种是利用.sync 修饰符,一种是利用size-change或者current-change事件
(1)利用.sync 修饰符
html中,给当前页码参数current-page通过.sync 修饰符绑定currentPage
这样当前页码变化时,currentPage
也会获取到最新的值
(2)利用size-change事件
上述js代码中,在method()中添加了一个方法handleSizeChange()
方法,它的回调参数就是每页条数
然后在前端组件中用@size-change
绑定这个事件,那么当每页条数发生变化时,就会触发这个事件,回调参数即是当前的每页条数
在handleSizeChange()
中,我把回调参数val的值赋给pageSize
参数,
这样pageSize
就能得到最新的每页条数了
可以直接把val赋给pageSize:this.pageSize = val
同时,在组件中仍然要给page-size
赋一个初始值,这样每次刷新页面,当前每页条数就显示这个定义的初始值
最后观察submitForm()
方法,我在payload对象
中添加了2个参数page_num
、page_size
,这俩参数其实是我传给后端请求接口中的2个参数
它们分别接收data()中的currentPage
和pageSize
的值
在控制台打印下结果,可以看到每次切换当前条数和页码,都能获取到最新的值
4、后端处理
前端代码先写到这里,接下来先在后端把接口定义出来
我们需要定义一个接口来供前端调用,根据前端传参,来返回列表所需的数据
前端会传4个参数:class_type
、create_date
、page_num
、page_size
因为数据创建好后存到了数据库中,所以我们需要从数据库中查出数据返给前端
编写sql时需要考虑到如下几点:
- 当某个查询条件为空时,sql语句中则不加这个条件;
- 当处理日期时,需要考虑前端日期组件传来空值的情况(在上面提了一下,前端创建日期如果默认为空时,传的值为
''
;如果先选择日期再重置,传的值为为['']
);
- 日期存在数据库为datetime对象,期望显示在前端时经过格式化,按照"年-月-日"显示;
- 因为涉及到分页,根据前端请求参数,控制查询第一页数据、第二页数据等以及每页数据条数;
创建一个蓝图,data_list.py
# coding: utf-8 """ author: hmk detail: create_time: """ from flask import Blueprint from flask_restful import Api, Resource from flask import request from utils.connect_db import MysqlConnect import time select_data_bp = Blueprint('select_data', __name__) # 创建一个蓝本 api = Api(select_data_bp) # 使用这个蓝本创建一个Api对象 class SelectData(Resource): def __init__(self): self.db = MysqlConnect() def get(self): """列表查询接口""" class_type = request.args.get("class_type") # 获取前端参数"type" create_date = request.args.getlist("create_date[]") # 获取前端传来的list格式数据(前端叫做array,数组) page_num = int(request.args.get("page_num")) # 当前页码 page_size = int(request.args.get("page_size")) # 每页显示数据条数 print("********************") # print(class_type) print(create_date) # print(page_num) # print(page_size) # print(type(page_num)) class_type_data = { # 定义一个字典,映射数据类型与类型编号的关系 "1": "电话号码", "2": "身份证id", "3": "姓名" } sql1 = None sql2 = None if class_type == "": if not create_date or create_date == ['']: sql1 = "select type_name, value, date_format(create_time, '%Y-%m-%d') from data_list LIMIT {},{}"\ .format((page_num-1)*page_size, page_size) sql2 = "select count(*) from data_list;" else: startDate = create_date[0] # request.args.get("startDate") endDate = create_date[1] # request.args.get("endDate") sql1 = "select type_name, value, date_format(create_time, '%Y-%m-%d') from data_list where " \ "create_time between '{}' AND '{}' LIMIT {},{};"\ .format(startDate, endDate, (page_num-1)*page_size, page_size) sql2 = "select count(*) from data_list where create_time between '{}' AND '{}';"\ .format(startDate, endDate) elif class_type != "": if not create_date or create_date == ['']: sql1 = "select type_name, value, date_format(create_time, '%Y-%m-%d') from data_list " \ "where type_name='{}' LIMIT {},{};"\ .format(class_type_data[class_type], (page_num-1)*page_size, page_size) sql2 = "select count(*) from data_list where type_name='{}';".format(class_type_data[class_type]) else: startDate = create_date[0] # request.args.get("startDate") endDate = create_date[1] # request.args.get("endDate") sql1 = "select type_name, value, date_format(create_time, '%Y-%m-%d') from data_list " \ "where type_name='{}'" \ "and create_time between '{}' AND '{}' LIMIT {},{};"\ .format(class_type_data[class_type], startDate, endDate, (page_num-1)*page_size, page_size) sql2 = "select count(*) from data_list " \ "where type_name='{}'" \ "and create_time between '{}' AND '{}';".format(class_type_data[class_type], startDate, endDate) print("################### 打印sql1 #########################") print(sql1) history_data = self.db.select_all(sql1) print("################### 打印查询到的所有数据 #########################") print(history_data) print("################### 打印sql2 #########################") print(sql2) count = self.db.select_one(sql2)[0] print("################### 打印查询到的数据总条数 #########################") print(count) self.db.close() data = { "code": 200, "records": history_data, "count": count } time.sleep(0.5) return data api.add_resource(SelectData, '/api/select_data')
代码说明:
(1)sql1
是用来查询数据的,查出来后,返回给前端,渲染到列表中;sql2
是用来查询数据总量的,显示当前查询条件下共有多少条数据;
(2)这里定义该接口为get请求,所以用request.args.get来获取前端传来的参数;
注意:在提取日期参数时,是这样提取的
create_date = request.args.getlist("create_date[]")
因为前端传来的create_date
为数组,并且这个是get请求,所以flask在提取这种参数时需要使用getlist
(3)处理分页时,在sql中使用LIMIT
来实现返回对应数据,如下
假如每页显示10条,那么
第1页的数据为1~10,
第2页的数据为11~20,
第3页的数据为21~30,依此类推
对应到sql中limit方法下,
第1页数据为limit 0, 10; 从第1行开始,检索10条记录
第2页数据为limit 10, 10; 从第11行开始,检索10条记录,也就是11~20
第3页数据为limit 20, 10; 从第21行开始,检索10条记录,也就是21~30
了解这个对应关系后,我们从前端获取到 当前页码 page_num
和 每页显示数据条数page_size
后,就可以写出如下sql
这里查出来的数据为元组,如果直接返回到前端会解析为列表
前端请求后,接口返回如下
5、前端发送请求,处理接口返回数据
在submitForm()
方法中添加axios发送请求
submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { let url1 = "http://127.0.0.1:5000/" let payload = { // 定义请求参数 class_type: this.form.class, create_date: this.form.create_date, page_num: this.currentPage, page_size: this.pageSize } console.log("打印查询条件输入的参数payload") console.log(payload) console.log("打印日期框选择框填写的日期") console.log(this.form.create_date) axios({ timeout: 10000, method: "get", params: payload, //发送get请求,使用params关键字接收请求参数 url: url1+"api/select_data" }).then(res => { console.log(res.data.records) //打印返回的原始数据 let data_list = res.data.records.map(function(array) { let rObj = {}; rObj["date"] = array[2] rObj["type"] = array[0] rObj["value"] = array[1] return rObj;}) console.log(data_list) this.tableData = data_list // let data_count = res.data.count this.count = res.data.count // console.log(data_list) // console.log(data_count) if(res.data.code === 200){ //判断响应中的code是否为200 // console.log(res.data) this.$message({ // 接口调用成功后,添加页面toast提示 message: '接口调用成功', type: 'success' }); } else{ console.log(res.data) } }).catch((reason)=>{ console.log(reason) this.$message({ message: '接口调用失败,请检查系统是否正常', type: 'warning' }); }) // console.log(typeof this.currentPage) } else { console.log('error submit!!'); return false; } }); },
代码说明:
1、res.data.records
是发送请求后返回的原始数据,即接口中定义的records
但是它的格式如下,不能直接给前端列表用
前端列表需要如下格式的数据
所以我们需要把里面一个个小的数组转换为对象
可以通过map来实现,代码如下
在map中定义了一个函数,它的作用就是构造一个对象,分别用date、type、value作为键,然后分别赋上接口返回数组中每个小数组对应的值,这样处理后,接口返回的数组就变为了如下形式
2、this.count = res.data.count
它的作用是把接口返回的数据总数赋给count
之前在分页组件中我们把count的值赋给了total,如下
到这里为止,基本目的就达到了,从后端取出数据渲染到前端,同时可以分页、显示数据总量、并且可以查询