微信搜索【大奇测试开】,关注这个坚持分享测试开发干货的家伙。
本章内容思维导图如下,由于需要各种状态下的菜单操作,所以需要先实现提测信息的列表基础页面,然后再推进其他需求开发
基本知识点学习
DatePicker 日期组件
Element ui 组件中有两个时间相关的控件,一个是 TimePicker 组件,以十分秒维度,另一个是 DatePicker 组件,以年月日时分秒维度,两者用法基本相同,本篇开发用到的是DatePicker,它的基本使用方法为
<el-date-picker v-model="绑定变量值" align="right" type="date" placeholder="选择日期"> </el-date-picker>
其中v-model一如即往的是指绑定的变量参数,align是对其的方向,placeholder标题没有值的时候提示信息,最重要的type是指日期的样式和类型,本例子选中时间后将展示如:2021-10-10 的格式时间,其他类型还报告年、月、星期、时间范围月范围等,实战开发还会用到带快捷选项 picker-options 属性,即可以日期选项左侧可以设定和快捷选项一个范围,比如近一个月,近三个月等,样式如,更多内容参考官方例子 [注解-1]
Select 选择器
这个组件在之前的文章是讲过的,当时直接举例没有讲清楚具体使用场景,这里举例说明下:
场景1: 如果是少量比较固定的,可以直接前端写<el-option key="",value="",label=""/>
场景2: 如果是动态固定数据,则用v-for的语法绑定一个list选项(用到之前请求接口赋予)
场景3: 如果是动态大量数据,可以使用远程搜索模式,即数据关键词后时时请求接口后台查询后显示,如公司用户查询选择。
选项中key关键词、value表示选择后的值,lebel为实际展示的值
SQL联合表查询
依稀记得在上学的时候设计数据表的时候,尤其是关系型数据库表,有很多的关联表,也很规范的使用外键等,不过目前从工作后,尤其是近几年很少看到这么严谨了,基本上如果查询少就在新表中多个相同字段,否则也是简单关联查询,语法格式为 select A.*,B.字段 from A, B where A.id=B.aid
在项目编写后端多条件查询语句的时候,最好是设计相关表后,插入几条关联数据,在数据库工具里先写好关联查询语句,测试通过后在去拼写后端请求代码,千万不要自信满满,笔记就是直接上代码,可以由于空格,语法等就要浪费好多时间去一点点debug,就本文带有时间范围的联合表查询,可以先完全先写sql语句,进行测试,然后再粘贴到代码里实现,比如是我先实现的语句验证OK。
需求功能实现
提测列表的搜索和展示需求其实和之前分享的应用列表管理非常类似,这里就不再贴需求原型,直接给出实现部分和注意点,最后看下整体实现。
服务查询接口
1. 查询接口的数据字典
直接给出如下,方便大家直接调试
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for request -- ---------------------------- DROP TABLE IF EXISTS `request`; CREATE TABLE `request` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '自增ID', `title` varchar(200) DEFAULT NULL COMMENT '提测标题', `appId` varchar(50) DEFAULT NULL COMMENT '应用服务', `developer` varchar(255) DEFAULT NULL COMMENT '提测RD', `tester` varchar(255) DEFAULT NULL COMMENT '测试QA', `CcMail` varchar(500) DEFAULT NULL COMMENT '关系人', `verison` varchar(100) DEFAULT NULL COMMENT '提测版本', `type` tinyint(1) DEFAULT NULL COMMENT '提测类型 1.功能 2.性能 3.安全', `scope` text COMMENT '测试说明', `gitCode` varchar(200) DEFAULT NULL COMMENT '项目代码', `wiki` varchar(200) DEFAULT NULL COMMENT '产品文档', `more` text COMMENT '是否发送邮件,0未操作,1成功,2失败', `status` tinyint(1) DEFAULT NULL COMMENT '测试状态 1-已提测 2-测试中 3-通过 4-失败 9-废弃', `sendEmail` tinyint(1) DEFAULT NULL COMMENT '是否发送消息,0未操作,1成功,2失败', `isDel` tinyint(1) DEFAULT '0' COMMENT '状态0正常1删除', `createUser` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人', `createDate` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updateUser` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '修改人', `updateDate` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; SET FOREIGN_KEY_CHECKS = 1;
2. 后端提测数据查询接口
新建文件testmanager.py,沿用数据链接池不变,这里主要注意代码里的拼接,按照知识点里说最好现在把测试语句在数据库查询命令行中测试通过,注意需要支持分页。
# -*- coding:utf-8 -*- # testmanager.py from flask import Blueprint from dbutils.pooled_db import PooledDB from configs import config, format from flask import request import pymysql.cursors import json # 使用数据库连接池的方式链接数据库,提高资源利用率 pool = PooledDB(pymysql, mincached=2, maxcached=5,host=config.MYSQL_HOST, port=config.MYSQL_PORT, user=config.MYSQL_USER, passwd= config.MYSQL_PASSWORD, database=config.MYSQL_DATABASE, cursorclass=pymysql.cursors.DictCursor) test_manager = Blueprint("test_manager", __name__) @test_manager.route("/api/test/search",methods=['POST']) def searchBykey(): body = request.get_data() body = json.loads(body) # 基础语句定义 sql = "" # 获取pageSize和 pageSize = 10 if 'pageSize' not in body or body['pageSize'] is None else body['pageSize'] currentPage = 1 if 'currentPage' not in body or body['currentPage'] is None else body['currentPage'] # 拼接查询条件 if 'productId' in body and body['productId'] != '': sql = sql + " AND A.productId LIKE '%{}%'".format(body['productId']) if 'appId' in body and body['appId'] != '': sql = sql + " AND A.appId LIKE '%{}%'".format(body['appId']) if 'tester' in body and body['tester'] != '': sql = sql + " AND R.tester LIKE '%{}%'".format(body['tester']) if 'developer' in body and body['developer'] != '': sql = sql + " AND R.developer LIKE '%{}%'".format(body['developer']) if 'status' in body and body['status'] != '': sql = sql + " AND R.status = '{}'".format(body['status']) if 'pickTime' in body and body['pickTime'] != '': sql = sql + " AND R.updateDate >= '{}' and R.updateDate <= '{}' ".format(body['pickTime'][0],body['pickTime'][1]) # 排序和页数拼接 sql = sql + ' ORDER BY R.updateDate DESC LIMIT {},{}'.format((currentPage - 1) * pageSize, pageSize) print(sql) # 使用连接池链接数据库 connection = pool.connection() with connection: # 先查询总数 with connection.cursor() as cursor: count_select = 'SELECT COUNT(*) as `count` FROM request as R , apps as A where R.appId = A.id AND R.isDel=0' + sql print(count_select) cursor.execute(count_select) total = cursor.fetchall() # 执行查询 with connection.cursor() as cursor: # 按照条件进行查询 cursor.execute('SELECT A.appId,R.* FROM request as R , apps as A where R.appId = A.id AND R.isDel=0' + sql) data = cursor.fetchall() # 按分页模版返回查询数据 response = format.resp_format_success response['data'] = data response['total'] = total[0]['count'] return response
前端多条件搜索
此区域位于提测vue页面的顶部,有两个下拉选择框,分别是“归属分类” 数据从服务接口获,“测试状态” 所有选项只有几个前端写了就行,点击查询按钮进行接口请求和查询,其中一个“新建提测”按钮为预留,下一篇新页面路由跳转的逻辑。
1. HTML部分代码参考
<div class="filter-container"> <el-form :inline="true" :model="search"> <el-form-item label="归属分类"> <el-select v-model="search.productId"> <el-option value="" label="所有" /> <el-option v-for="item in optsProduct" :key="item.id" :label="item.title" :value="item.id" /> </el-select> </el-form-item> <el-form-item label="应用ID"> <el-input v-model="search.appId" placeholder="服务ID关键词" style="width: 200px;" clearable /> </el-form-item> <el-form-item label="测试人"> <el-input v-model="search.tester" placeholder="默认测试" style="width: 210px;" clearable /> </el-form-item> <el-form-item label="提测人"> <el-input v-model="search.developer" placeholder="默认测试" style="width: 210px;" clearable /> </el-form-item> <el-form-item label="时间范围"> <el-date-picker v-model="search.pickTime" type="datetimerange" :picker-options="pickerOptions" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" align="right"> </el-date-picker> </el-form-item> <el-form-item label="测试状态"> <el-select v-model="search.status" placeholder="请选择"> <el-option value="" label="所有" /> <el-option key=1 label="已提测" value=1></el-option> <el-option key=2 label="测试中" value=2></el-option> <el-option key=3 label="通过" value=3></el-option> <el-option key=4 label="失败" value=4></el-option> <el-option key=9 label="废弃" value=9></el-option> </el-select> </el-form-item> <el-form-item> <el-button type="primary" plain @click="searchClick()">查询</el-button> </el-form-item> </el-form> <el-button type="primary" icon="el-icon-plus" style="float:right" @click="doCommit()">新建提测</el-button> </div>
2. JS部分所涉及代码参考
所属分类的接口请求不需要新写,沿用应用管理那部分实现
1)data() 变量定义
主要是绑定的搜索条件、日期快捷,顺便定义table存储变量
import { apiAppsProduct } from '@/api/apps' // 新定义的提测api js文件 import { apiTestSearch } from '@/api/test.js' data() { return { // 条件查询变量定义 search: { productId: '', appId: '', developer: '', tester: '', status: '', pickTime: '' }, // 范围日期组件的快捷选项配置 pickerOptions: { shortcuts: [{ text: '最近一周', onClick(picker) { const end = new Date() const start = new Date() // 当前时间 start.setTime(start.getTime() - 3600 * 1000 * 24 * 7) //单位毫秒加减计算 picker.$emit('pick', [start, end]) } }, { text: '最近一个月', onClick(picker) { const end = new Date() const start = new Date() start.setTime(start.getTime() - 3600 * 1000 * 24 * 30) picker.$emit('pick', [start, end]) } }] }, // 所属分类选项-沿用应用管理 optsProduct: [], // 数据表格展示和分页变量数据定义 testData: [], pageValues: { pageSize: 10, currentPage: 1, total: 0 } } }
2. methods方法相关实现
默认归类数据的请求与赋值,以及点击搜索按钮后的查询接口请求和表格数据回填,页码数据回填。
// 所属归类选项数据查询 productList() { apiAppsProduct().then(resp => { this.optsProduct = resp.data }) }, // 按照条件查询,如果某个控件为空,则不会此字段请求 searchClick() { const body = { pageSize: this.pageValues.pageSize, currentPage: this.pageValues.currentPage, productId: this.search.productId, appId: this.search.appId, tester: this.search.tester, developer: this.search.developer, pickTime: this.search.pickTime, status: this.search.status } apiTestSearch(body).then(response => { // 将返回的结果赋值给表格自动匹配 this.testData = response.data this.pageValues.total = response.total }) },
数据展示和操作逻辑
上边已经完成了所有数据绑定部分,接着就是进行展示
1. 分别定义 table 和 pagination 元素
这里特别要关注实现就是操作的功能的按钮要根据每条数据的状态数值判断是否给予显示。之前需求文档给出来的逻辑如下(测试中状态有更正)
测试状态 |
状态码 |
操作菜单 |
已提测 |
1(新建默认) |
开始测试 / 编辑提测 / 提测详细 |
测试中 |
2 |
添加结果 / 编辑提测 / 提测详细 |
通过 |
3 |
查看报告 / 编辑结果 / 提测详细 |
失败 |
4 |
查看报告 / 编辑结果 / 提测详细 |
废弃 |
9 |
删除提测 / 编辑结果 / 提测详细 |
html代码参考
<div> <el-table :data="testData"> <!--:data prop绑定{}中的key,label为自定义显示的列表头--> <el-table-column prop="appId" label="应用ID" /> <el-table-column prop="title" label="提测标题" show-overflow-tooltip /> <el-table-column :formatter="formatStatus" prop="status" label="测试状态"/> <el-table-column :formatter="formatType" prop="type" label="类型" /> <el-table-column prop="developer" label="提测人" /> <el-table-column prop="tester" label="测试人" /> <el-table-column prop="updateUser" label="更新人" /> <el-table-column :formatter="formatDate" prop="updateDate" label="更新时间" /> <el-table-column label="操作" width="300"> <template slot-scope="scope"> <!--<label>菜单逻辑判断一列</label>--> <el-link type="primary" v-if="scope.row.status===1" @click="startTest(scope.row)">开始测试</el-link> <el-link type="primary" v-if="scope.row.status===2" >添加结果</el-link> <el-link type="primary" v-if="scope.row.status===3 || scope.row.status == 4" >查看报告</el-link> <el-link type="primary" v-if="scope.row.status===9" >删除结果</el-link> <!--<label>菜单逻辑判断二列</label>--> <el-divider direction="vertical"></el-divider> <el-link type="primary" v-if="[1,2].includes(scope.row.status)">编辑提测</el-link> <el-link type="primary" v-if="[3,4,9].includes(scope.row.status)">编辑结果</el-link> <el-divider direction="vertical"></el-divider> <el-link type="primary" >提测详情</el-link> </template> </el-table-column> </el-table> </div> <div> <br> <el-pagination background :current-page.sync="pageValues.currentPage" :page-size="pageValues.pageSize" layout="total, sizes, prev, pager, next" :page-sizes="[5, 10, 20, 30, 50]" :total="pageValues.total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div>
2. 对应元素JS代码
分别要对时间格式化(之前有案例),状态和类型列也要格式化,使用javascript 中的swith case 语法语句进行判断,实现将本来的不可辨别的数字翻译成对应的文字展示。
formatDate(row, column) { const date = row[column.property] if (date === undefined) { return '' } // 使用moment格式化时间,由于我的数据库是默认时区,偏移量设置0,各自根据情况进行配置 return moment(date).utcOffset(0).format('YYYY-MM-DD HH:mm') }, formatStatus(row, column) { const status = row[column.property] switch (status) { case 1: return '已提测' case 2: return '测试中' case 3: return '通过' case 4: return '失败' case 9: return '已废弃' default: return '未知状态' } }, formatType(row, column) { const type = row[column.property] switch (type) { case 1: return '功能测试' case 2: return '性能测试' case 3: return '安全测试' default: return '未知状态' } },
另外一部分js代码是对分页的实现,请自行实现或参考git上源码。
至此前后的逻辑代码部分已经全部给出来了,没有给出部分是api请求request 和本页的菜单配置,相信如果你是一步步跟着之前文章实战下来的,这些已经太轻车熟路了,启动前后端最终实现页面如下,最后再想想需要写哪些CASE对本功能进行一个全面的测试。
学习交流群已经开放,可以关注公众号【大奇测试开发】发送 “测试开发” 获取最新二维码入群。
【代码更新】
-
地址:https://github.com/mrzcode/TestProjectManagement
-
TAG:TPMShare12
【注解&参考】
-
[注解-1] : https://element.eleme.io/#/zh-CN/component/date-picker
坚持原创,坚持实践,坚持干货,如果你觉得有用,请点击推荐,也欢迎关注我博客园和微信公众号。