微信搜索【大奇测试开】,关注这个坚持分享测试开发干货的家伙。
开篇说个小讨论,一个群里聊天聊到关于更新篇章的长度,是小篇幅多次,还是每次按照一个小完整的功能,我个人的是按照后种来的,主要的思考就是希望集中的时间段去实践,这样效率高且有每次会有一个小小的成就感,另一种当然有它的好处,更能持续输出,看的内容少,且很有助于阅读量。因此做个个小小的互动,欢迎留言,看看大家的想法~
本篇内容思维导
服务应用管理,主要是前端的知识点,学习一种新的前端控件作为新增修改基层页,再重点掌握下表单数据提交的时候校验,这里不限于非空、格式等。对于后端的没有新增的知识点,可以参照项目产品分类管理的代码分别写新增和修改两个接口,也可按照这次我将两个接口合并成一个接口,其实就是之前有一章节留过的一个思考题“如何实现两个接口的合并”不知道还有没印象,总之本篇内容不是很多,加油~
知识点学习
Drawer 抽屉
之前产品修改和添加是使用Dialog组件实现的,但这个组件有时候并不满足我们的需求, 比如表单很长, 亦或是你需要临时展示一些文档, Drawer 是可以从侧面弹出的一个层,可以容纳更多的控件,优化交互体验。基本用法
<el-drawer title="我是从右到左侧展示的抽屉" :visible.sync="drawer" direction="rtl"> 这里可组合放其他组件Body部分 </el-drawer> <!..script部分省略..>
显示和隐藏通过 visible 属性,类型是 boolean,当为 true 时显示 Drawer。Drawer 分为两个部分:title 和 body,title 可省略, direction为设置打开方向, Drawer 默认是从右往左打开,其他方向包括ltr(从左到右)、ttb(从上到下)、btt(从下往上),更多属性事件参考官方[注解1]
Form 表单验证
在之前的产品添加和修改功能都是直接提交的,一些验证是在后端做的处理,正常情况下,前端提交数据的时候就要进行一些如非空校验、是否为字符串、是否符合正则规则等,这里Form 组件是直接提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,支持默认属性绑定和自定义校验。更多参考[注解2],示例代码:
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="绑定规则校验对应prop属性" prop="name"> <el-input v-model="ruleForm.name"></el-input> </el-form-item> <el-form-item label="密码自定义校验" prop="pass"> <el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input> </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> <script> export default { data() { var validatePass = (rule, value, callback) => { if (value === '') { callback(new Error('请输入密码')); } else { if (this.ruleForm.checkPass !== '') { this.$refs.ruleForm.validateField('checkPass'); } callback(); } }, return { ruleForm: { name: '', checkPass:'' }, rules: { name: [ { required: true, message: '请输入活动名称', trigger: 'blur' }, { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } ], pass: [ { validator: validatePass, trigger: 'blur' } ] } }; }, methods: { submitForm(formName) { // 这里是提交前触发校验 this.$refs[formName].validate((valid) => { if (valid) { alert('submit!'); } else { console.log('error submit!!'); return false; } }); }, resetForm(formName) { // 清除所有校验提示 this.$refs[formName].resetFields(); } } } </script>
1)表单通过 :rules="rules" ref="ruleForm"进行数据和属性绑定
2)行项目中 prop=""去绑定规则对应的key,这里最好数据和验证的key保持一致
3)基本属性配置包括是否必须,字符的长短等,详细参考规则async-validator
4) 自定义规则是一些较为复杂的校验通过回调进行逻辑自定义,参考例子第二个form-item
5)上述被定义required: true规则的控件在会自动增加 红色*号,trigger 定义什么时候触发校验
项目实战应用
按惯例,看完新知识点后,继续参照之前的产品原型和需求实现本期项目开发内容,即应用管理中的增加和修改功能原型和需求
此页面 添加/修改 功能需求说明:
-
点击添加/编辑弹抽屉,红色 * 为必填选项
-
分类来源归属分类,外键关联
-
应用名称有重名校验,创建后不可以修改
-
默认必须有测试、研发和产品负责人,多人邮件用;分隔
-
目前要求必须填写代码地址,以便测试人员了解信息,编写测试code
-
以上数据字符长度暂无限制
功能实现(步骤)伪代码
-
Python Flask 编写一个接口同时实现添加和修改数据功能
-
创建抽屉控件,内嵌form,实现原型中的各控件绑定
-
控件红色*标记的规则配置,触发方式trigger: 'blur'即点击提交统一校验,
-
页面修改和添加使用同一个Drawer 标题根据上一步操作动态显示 “应用添加” / “应用编辑”
-
应用编辑的自增ID不需要显示,应用ID不可编辑
实践参考(本章)实现
1. 应用增改接口实现
这个合并接口的实现核心的逻辑点就是根据前端是否传了数据库id主键,如果有便认为是历史数据,走修改操作,否则走添加逻辑,完整代码和说明见代码:
@app_application.route("/api/application/update",methods=['POST']) def product_update(): # 获取传递的数据,并转换成JSON body = request.get_data() body = json.loads(body) # 定义默认返回体 resp_success = format.resp_format_success resp_failed = format.resp_format_failed # 判断必填参数 if 'appId' not in body: resp_failed.message = '应用不能为空' return resp_failed elif 'tester' not in body: resp_failed.message = '测试负责人不能为空' return resp_failed elif 'developer' not in body: resp_failed.message = '测试负责人不能为空' return resp_failed elif 'producer' not in body: resp_failed.message = '产品负责人不能为空' return # 使用连接池链接数据库 connection = pool.connection() # 判断增加或是修改逻辑 with connection: # 如果传的值有ID,那么进行修改操作,否则为新增数据 if 'id' in body and body['id'] != '': with connection.cursor() as cursor: # 拼接修改语句,由于应用名不可修改,不需要做重复校验appId sql = "UPDATE `apps` SET `productId`=%s, `note`=%s,`tester`=%s,`developer`=%s,`producer`=%s,`cCEmail`=%s, " \ "`gitCode`=%s, `wiki`=%s, `more`=%s, `creteUser`=%s, `updateUser`=%s, `updateDate`= NOW() WHERE id=%s" cursor.execute(sql, (body["productId"], body["note"], body["tester"], body["developer"], body['producer'], body["cCEmail"], body["gitCode"], body["wiki"], body["more"], body["creteUser"], body["updateUser"], body["id"])) # 提交执行保存更新数据 connection.commit() else: # 新增需要判断appId是否重复 with connection.cursor() as cursor: select = "SELECT * FROM `apps` WHERE `appId`=%s AND `status`=0" cursor.execute(select, (body["appId"],)) result = cursor.fetchall() # 有数据说明存在相同值,封装提示直接返回 if len(result) > 0: resp_failed["code"] = 20001 resp_failed["message"] = "唯一编码keyCode已存在" return resp_failed with connection.cursor() as cursor: # 拼接插入语句,并用参数化%s构造防止基本的SQL注入 # 其中id为自增,插入数据默认数据设置的当前时间 sql = "INSERT INTO `apps` (`appId`,`productId`,`note`,`tester`,`developer`,`producer`,`cCEmail`,`gitCode`" \ ",`wiki`,`more`,`creteUser`,`updateUser`) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)" cursor.execute(sql, (body["appId"],body["productId"], body["note"], body["tester"], body["developer"], body['producer'],body["cCEmail"], body["gitCode"],body["wiki"],body["more"],body["creteUser"],body["updateUser"])) # 提交执行保存新增数据 connection.commit() return resp_success
2. 组合表单的抽屉控件实现
在app.vue 代码文件<script></script> 添加绑定数据和基本规则
1)不要忘记登录人的变量导入
2)规则和请求变量的key一定确保一致
3)除了选择框为改变时候触发校验,其他都使用提交时候再做统一校验
data() { return { // 获得登录的名字 op_user: store.getters.name, // 定义动作 appAction: 'ADD', // 控制抽屉显示隐藏 drawerVisible: false, // 添加/修改绑定的数据 appInfo: { id: '', appId: '', productId: '', note: '', tester: '', developer: '', producer: '', cCEmail: '', gitCode: '', wiki: '', more: '', creteUser: '', updateUser: '' }, // 规则设定 rules: { appId: [ { required: true, message: '请输应用名称', trigger: 'blur' } ], productId: [ { required: true, message: '请选择所属范围', trigger: 'change' } ], tester: [ { required: true, message: '请输入测试负责人', trigger: 'blur' } ], developer: [ { required: true, message: '请输入开发负责人', trigger: 'blur' } ], producer: [ { required: true, message: '请输入产品负责人', trigger: 'blur' } ] } } }
在app.vue 代码文件 <div class="app-container"> </div>内编写组合控件
1)标题和appId是否可编辑根据 appAction 判断根据
2)归属分类沿用搜索里的下拉实现,也可以使用基本方式
3)实现规则一定注意el-form-item 中 prop 的定义和一致性
<el-drawer :title="appAction==='ADD'? '添加应用': '修改应用'" :visible.sync="drawerVisible" size="45%" direction="rtl"> <div> <el-form :model="appInfo" :rules="rules" ref="appInfo" label-width="120px"> <el-form-item label="应用ID" prop="appId" > <el-input v-model="appInfo.appId" :disabled="appAction==='ADD'? false : true" style="width: 300px"/> </el-form-item> <el-form-item label="归属分类" prop="productId"> <el-select v-model="appInfo.productId" style="width: 300px"> <el-option v-for="item in options" :key="item.id" :label="item.title" :value="item.id"> <span style="float: left">{{ item.keyCode }}</span> <span style="float: right; color: #8492a6; font-size: 13px">{{ item.title }}</span> </el-option> </el-select> </el-form-item> <el-form-item label="应用描述"> <el-input v-model="appInfo.note" style="width: 300px"/> </el-form-item> <el-form-item label="测试负责" prop="tester"> <el-input v-model="appInfo.tester" style="width: 300px"/> </el-form-item> <el-form-item label="研发负责" prop="developer"> <el-input v-model="appInfo.developer" style="width: 300px"/> </el-form-item> <el-form-item label="产品负责" prop="producer"> <el-input v-model="appInfo.producer" style="width: 300px"/> </el-form-item> <el-form-item label="默认抄送"> <el-input v-model="appInfo.cCEmail" style="width: 300px"/> </el-form-item> <el-form-item label="代码地址"> <el-input v-model="appInfo.gitCode" style="width: 300px"/> </el-form-item> <el-form-item label="相关wiki"> <el-input v-model="appInfo.wiki" style="width: 300px"/> </el-form-item> <el-form-item label="更多信息"> <el-input v-model="appInfo.more" style="width: 300px"/> </el-form-item> <el-form-item> <span class="dialog-footer"> <el-button @click="drawerVisible=false">取 消</el-button> <el-button type="primary" @click="commitApp('appInfo')">提 交</el-button> </span> </el-form-item> </el-form> </div> </el-drawer>
3. 实现接口请求
在app.js 定义/api/application/update接口模版请求
// 调用应用增加/修改统一接口 export function apiAppsCommit(requestBody) { return request({ url: '/api/application/update', method: 'post', data: requestBody }) }
在app.vue 代码文件<script></script> 实现添加和修改请求方法
1)addApp上节的占位的方法体,这里要实现信息清空和动作定义
2)updateApp 同样,实现选择的数据反填和遗留信息清空基本操作
3)请求后端接口要在所以规则校验通过后才进行真正的提交
addApp() { // 定义动作,以抽屉做判断 this.appAction = 'ADD' // 添加数据初始化 this.appInfo.id = '' this.appInfo.appId = '' this.appInfo.productId = '' this.appInfo.note = '' this.appInfo.tester = '' this.appInfo.developer = '' this.appInfo.producer = '' this.appInfo.cCEmail = '' this.appInfo.gitCode = '' this.appInfo.wiki = '' this.appInfo.more = '' this.appInfo.creteUser = this.op_user this.appInfo.updateUser = this.op_user // 初始化完成后显示抽屉 this.drawerVisible = true // 如果有遗留验证清空 this.$nextTick(() => { this.$refs['appInfo'].resetFields() }) }, updateApp(row) { // 定义动作,以抽屉做判断 this.appAction = 'UPDATE' // 初始化完成后显示抽屉 this.drawerVisible = true // 如果有遗留验证清空 this.$nextTick(() => { this.$refs['appInfo'].resetFields() }) // 选择数据反填抽屉表单中 this.appInfo.id = row.id this.appInfo.appId = row.appId this.appInfo.productId = row.productId this.appInfo.note = row.note this.appInfo.tester = row.tester this.appInfo.developer = row.developer this.appInfo.producer = row.producer this.appInfo.cCEmail = row.cCEmail this.appInfo.gitCode = row.gitCode this.appInfo.wiki = row.wiki this.appInfo.more = row.more this.appInfo.creteUser = '' this.appInfo.updateUser = row.updateUser }, commitApp() { // 上边form定义ref,验证通过if valid的方式判断 this.$refs['appInfo'].validate((valid) => { if (valid) { this.appInfo.updateUser = this.op_user apiAppsCommit(this.appInfo).then(response => { // 如果request.js没有拦截即表示成功,给出对应提示和操作 this.$notify({ title: '成功', message: this.appAction === 'ADD' ? '应用添加成功' : '应用修改成功', type: 'success' }) // 关闭对话框 this.drawerVisible = false // 重新查询刷新数据显示 this.getProductList() }) } else { return false } }) }
4. 联调前后端运行
分别运行前后端,解决掉运行中的错误后,做两条测试验证功能是否OK
1)添加操作,默认为空数据,提交不完整信息是否有校验提示阻止提交
2)编辑操作,数据是否正常反填,修改后提交是否正常更新落库
以上为本篇全部内容,目前应用管理的方面开发全部开发完了,后边将进入提测的主流程阶段。
设计开发中会遇到各种各样的问题,这些文章有我在写的时候都需要半天,有时候需要几天,因为总会有困难点和调试的问题,我相信大家在实践中更是如此,就即使你是完全复制粘贴的代码,但有问题我觉得是好事,这能让我们可以知其然,知其所以然,以及逐渐了解到解决问题方式。大家有什么问题可以留言交流或和关注公众号发私信,看到我会尽可能帮忙解答。
问题集锦
1. Form表单中的验证无效
本篇在开发整理中遇到了,form表单验证怎么也不生效的问题,搞了好久,最终是由于绑定的数据的方式弄混了,将 :mode 习惯的用了v-mode,另外也涉及了:ref定义一致性问题,如果你也遇到规则不生效请检查这些方面。
2.规则验证重置resetFields报错
在添加和修改的方法中,为了清除掉之前可能遗留的验证提示,使用了resetFields,但却忽略了它是需要依赖控件加载完成后才能调用,所以需要调在抽屉显示之后才调用,另外还需要使用到 this.$nextTick 回调延迟到下次DOM更新循环之后执行。
【代码更新】
- 地址:https://github.com/mrzcode/TestProjectManagement
- TAG:TPMShare10
【注解&参考】
-
[注解1] https://element.eleme.io/#/zh-CN/component/drawer
-
[注解2] https://element.eleme.io/#/zh-CN/component/form#biao-dan-yan-zheng
坚持原创,坚持实践,坚持干货,如果你觉得有用,请点击推荐,也欢迎关注我博客园和微信公众号。