mrzcode

微信搜索【大奇测试开】,关注这个坚持分享测试开发干货的家伙。

本篇继续提测平台开发,按惯例先给出学习的思维导图,以便快速了解学习知识和平台功能实现的重点。

基本知识点学习

远程搜索

显示的数据通过输入关键词,从服务器搜索并返回,一般主要用于数据很多服务全部获取显示的,常用selected组件中,比如一个增改操作要指定一个公司的联系人场景,在element-vue 选择组件中主要是通过 :remote-method 调一个方法来实现的,而这个方式实现就是一个接口请求,即输入关键立马请求接口,接口根据关键字返回数据回填,说白了就是多次请求。

 

在Element Input组件中也有这个能力,是使用 :fetch-suggestions 场景是用于输入信息给填写建议。这个远程搜索功能在很多可组件上自己利用判断绑定的值的改变,调方法做相应的操作,这块具体可以参考后边功能开发例子。

vue中$router路由

在之前的页面功能如添加/修改的实现上都是通过抽屉或者弹对话框实现的,其实有些场景涉及到需要一个新的页面来实现,那么这个就要用到this.$router.push()来实现,基本一些使用方法如下: 

1.  跳转页面对象 参数传的是菜单路由的定义的页面名称,比如我想点击某个按钮跳转到提测系统的app管理页面,在按钮出发点事件方法中使用

this.$router(\'apps\')
// 或
this.$router({name: \'apps\'}

2. 跳转通过路径 同样为菜单设置的path 相对应

this.$router.push({path: \'/login});

3.带参数跳转

this.$router({name: \'app\', params: { appId: 101 }})
// 上边是以params隐藏传递过去的,下边是在URL显式参数传的
this.$router.push({path: \'/login?url=\' + this.$route.path});

4. 跳转过来的新页获取传过来的值

// 获取通过params的参数
this.$route.params.appId
// 获取URL中的参数
this.$route.query.appId

5. 返回上一页

this.$router.go(-1)

以上这些是一些基本的vue router的用法,接着将在新建提测需求这个功能得到部分应用。

需求功能实现

按照之前的需求文档,由于提测需要提交的信息很多,所以通过点击新按钮会跳转到一个新的vue页面进行操作,先实现python后端的需求添加接口,之前的需求中写过好多类似的了,插入接口的代码直接上了。

提测添加接口

@test_manager.route("/api/test/create",methods=[\'POST\'])
def createReqeust():
    # 获取传递的数据,并转换成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\'] = \'appId 提测应用不能为空\'
        return resp_failed
    elif \'tester\' not in body:
        resp_failed[\'message\'] = \'tester 测试人员不能为空\'
        return resp_failed
    elif \'developer\' not in body:
        resp_failed[\'message\'] = \'developer 提测人不能为空\'
        return resp_failed
    elif \'title\' not in body:
        resp_failed[\'message\'] = \'title提测标题不能为空\'
        return resp_failed

    # 使用连接池链接数据库
    connection = pool.connection()

    # 判断增加或是修改逻辑
    with connection:
        try:
            with connection.cursor() as cursor:
                # 拼接插入语句,并用参数化%s构造防止基本的SQL注入
                # 其中id为自增,插入数据默认数据设置的当前时间
                sqlInsert = "INSERT INTO request (title,appId,developer,tester,CcMail,verison,`type`,scope,gitCode,wiki,`more`,`status`,createUser,updateUser) " \
                            "VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
                cursor.execute(sqlInsert, (
                    body["title"], body["appId"], body["developer"], body["tester"], body["CcMail"], body["version"],
                    body[\'type\'],
                    body["scope"], body["gitCode"], body["wiki"], body["more"], \'1\', body["createUser"],
                    body["updateUser"]))
                # 提交执行保存新增数据
                id = cursor.lastrowid
                connection.commit()            return resp_success
        except Exception as err:
            resp_failed[\'message\'] = \'提测失败了:\' + err
            return resp_failed
 

然后重新启动下服务,来做个接口测试,看插入有没有问题,接下来需要重点看下是这部分代码逻辑,关于如果用户勾选了发邮件,则在需要提交数据进行邮件发送,这就用到之前讲解到的邮件工具那边文章了,如果还没练习的可以先看下 学习Python邮件发送方法&落地有邮件工具类-测试开发【提测平台】分享11 ,实现就在commit()后进行逻辑判断,并且特别注意的是之前还预留个是否发送成功的字段,也就是还需要判断邮件是否发送成功了,再对插入的数据立马进行一次更新操作。

if body[\'isEmail\'] == \'true\':
                # 新建成功发送Email
                if body[\'type\'] == \'1\':
                    version = \'功能测试\'
                elif body[\'type\'] == \'2\':
                    version = \'性能测试\'
                elif body[\'type\'] == \'3\':
                    version = \'安全测试\'

                receivers = body["tester"].split(\',\') + body["developer"].split(\',\')
                if not body["CcMail"] is None:
                    receivers = receivers + body["CcMail"].split(\',\')

                subject = \'【提测】\' + body[\'title\']
                reuslt = sendEmail(receivers, subject, [
                    \'<strong>[提测应用]</strong>\',
                    body[\'appName\'],
                    \'<strong>[提测人]</strong>\',
                    body[\'developer\'],
                    \'<strong>[提测版本]</strong>\',
                    body[\'version\'],
                    \'<strong>[提测类型]</strong>\',
                    version,
                    \'<strong>[测试内容]</strong>\',
                    body[\'scope\'],
                    \'<strong>[相关文档]</strong>\',
                    body[\'wiki\'],
                    \'<strong>[补充信息]</strong>\',
                    body[\'more\']
                ])
                if reuslt:
                    sendOk = 1
                else:
                    sendOk = 2
                with connection.cursor() as cursor:
                    # 更新Emai是否发送成功1-成功 2-失败
                    updateEmail = "UPDATE request SET sendEmail=%s, updateUser=%s,`updateDate`= NOW() WHERE id=%s"
                    cursor.execute(updateEmail, (sendOk, body["updateUser"], id))
                    # 提交修改邮件是否发送成功
                    connection.commit()
            else:
                print(\'不发送邮件!\')

两代码合并后再次运行进行下测试,这里其实是有个性能问题,因为是阻塞需要等待邮件的结果,所以接口的相应会有些慢,大家可以理由异步调用进行优化的,查查资料看怎么实现,体现学习能力的时候到了。 

远程应用搜索接口

还有一个接口,在这里一并先实现了,就是需要为应用的远程搜索单独写个按照条件查询接口,此接口有个和之前条件查询特别的需求,是需要同时支持appid或者描述的任意条件的查询,具体实现请看代码(application.py):

@app_application.route("/api/application/options", methods=[\'GET\'])
def getOptionsForSelected():

    value = request.args.get(\'value\', \'\')
    response = format.resp_format_success

    connection = pool.connection()

    with connection.cursor() as cursor:

        # 先按appid模糊搜索,没有数据再按note搜索
        sqlByAppId = "SELECT * FROM apps WHERE appId LIKE \'%"+value+"%\'"
        cursor.execute(sqlByAppId)
        dataByppId = cursor.fetchall()
        if len(dataByppId) > 0 :
            response[\'data\'] = dataByppId
        else:
            sqlByNote = "SELECT * FROM apps WHERE note LIKE \'%" + value + "%\'"
            cursor.execute(sqlByNote)
            dataByNote = cursor.fetchall()
            response[\'data\'] = dataByNote

    return response

一个笨方法就先默认按照appId查询,没有再按照note条件查询,如果你有更好的实践方法,欢迎提交流,启动服务对接口进行测试(永远不要忘了测试)

到此后端的内容就这些,完整的代码在新的接口文件 testmanager.py 可以在github对照查看。

 

前端提测需求新建实现

自实现步骤步骤为

1)实现上次的添加按钮功能,利用 this.$router.push 实现

2)跳转的时候需要带个动作标记参数,主要为了区分是添加操作还,后续实现的修改操作

3)自行在element ui官方查询 “header页头” 的组件的使用,并实现 <- 按钮的点击跳回上一页面

4)按照产品原型实现form表单各项控件

5)应用下拉框实现远程关键词搜索,并且还要实现在选择对应的应用后,需要应用配置的时候一些默认信息,反填到其他相关的输入框中

6)实现添加和取消按钮的逻辑功能

7)添加成功后跳回列表并让列表列感知刷新最新数据

 

下边是我的实现参考

1. 新建按钮跳转

创建一个空的提测页面,叫commit.vue,并设置好个不显示的在菜单上的路由,配置好path和name以及跳转地址

{
    path: \'commit\',
    name: \'commit\',
    hidden: true,
    component: () => import(\'@/views/test/manger/commit\'),
    meta: { title: \'需求提测\', icon: \'dashboard\' }
},

然后就是编写提测列表页面的新建按钮的点击触发方法逻辑

doCommit() {
   this.$router.push({ name: \'commit\', params: { action: \'ADD\' }})
},

 

2. 实现提测form表单

这里是个全新的页面,我就不再分解了,直接给出上边3-6的实现代码,必要的注解都已经代码了,可以参照对比自己的实现代码,如果部分不是很完成,还是去下载最新的代码去查看

Template模块代码

重点关注selected远程搜索的处理方法和Header组件的如何使用

<template>
  <div class="app-container">
    <el-header>
      <el-page-header @back="goBack" content="提测"/>
    </el-header>
    <el-main>
      <el-form :model="requestForm" :rules="requestRules" ref="ruleForm" label-width="100px" >
        <el-form-item label="提测标题" prop="title">
          <el-input v-model="requestForm.title" placeholder="提测标题" style="width: 350px"></el-input>
        </el-form-item>
        <el-form-item label="服务应用" prop="appId">
          <el-select
             v-model="requestForm.appId"
             filterable
             remote
             reserve-keyword
             placeholder="请输入关键词(远程搜索)"
             :remote-method="remoteMethod"
             :loading="appIdloading"
             @change="appSelected"
             style="width: 300px">
            <el-option
              v-for="item in appIdList"
              :key="item.id"
              :label="item.appId"
              :value="item.id">
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="提测RD" prop="developer">
          <el-input v-model="requestForm.developer" placeholder="提测人研发" style="width: 350px"></el-input>
        </el-form-item>
        <el-form-item label="测试QA" prop="tester">
          <el-input v-model="requestForm.tester" placeholder="测试人" style="width: 350px"></el-input>
        </el-form-item>
        <el-form-item label="关系人" prop="CcMail">
          <el-input v-model="requestForm.CcMail" placeholder="邮件抄送人" style="width: 350px"></el-input>
        </el-form-item>
        <el-form-item label="提测版本" prop="version">
          <el-input v-model="requestForm.version" placeholder="部署版本号/分支/Tag" style="width: 350px"></el-input>
        </el-form-item>
        <el-form-item label="提测类型" prop="type">
          <el-select v-model="requestForm.type" clearable placeholder="请选择..." style="width: 300px">
            <el-option
              v-for="item in opsType"
              :key="item.value"
              :label="item.label"
              :value="item.value">
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="测试范围" prop="scope">
          <el-input v-model="requestForm.scope" type="textarea" :rows="3" placeholder="1.功能点 \n 2.测试点 \n 3.回归点" style="width: 350px"></el-input>
        </el-form-item>
        <el-form-item label="代码地址" prop="gitCode">
          <el-input v-model="requestForm.gitCode" placeholder="git代码地址" style="width: 350px"></el-input>
        </el-form-item>
        <el-form-item label="产品文档" prop="wiki">
          <el-input v-model="requestForm.wiki" placeholder="文档说明地址" style="width: 350px"></el-input>
        </el-form-item>
        <el-form-item label="更多信息" prop="more">
          <el-input v-model="requestForm.more" type="textarea" :rows="3" placeholder="其他补充信息" style="width: 350px"></el-input>
        </el-form-item>
        <el-form-item>
          <el-checkbox v-model="requestForm.isEmail" true-label="true">发送邮件</el-checkbox>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="onSubmit">立即创建</el-button>
          <el-button @click="onCancel">取 消</el-button>
        </el-form-item>
      </el-form>
    </el-main>
  </div>
</template>

 

Javacript模块代码

这部分代码逻辑中重点关注

1)mounted 页面初始化时候获取上级页面的传参值;

2)表单规则名称增加一个字符多少的校验;

3)选择应用后数据的反填特殊处理,即如果用户已经手写改过之后,就不能将其覆盖填写其默认值

4)头返回和取消返回分别使用两种方法

5)提交添加成功后也有个跳转到列表的处理,列表需要同样用判断是否有回传的方式,处理是否需要立即刷新最新列表数据

<script>
import { apiAppsIds } from \'@/api/apps\'
import { reqCreate } from \'@/api/test\'
import store from \'@/store\'

export default {
  name: \'Commit\',
  data() {
    return {
      op_user: store.getters.name,
      testAction: \'\',
      appIdloading: false,
      requestForm: {
        id: undefined,
        title: \'\',
        appId: \'\',
        appName: \'\',
        developer: \'\',
        tester: \'\',
        CcMail: \'\',
        version: \'\',
        type: \'\',
        scope: \'\',
        gitCode: \'\',
        wiki: \'\',
        more: \'\',
        isEmail: \'true\',
        createUser: \'\',
        updateUser: \'\'
      },
      requestRules: {
        title: [
          { required: true, message: \'请输入活动名称\', trigger: \'blur\' },
          { min: 3, message: \'长度在大于3个字符\', trigger: \'blur\' }
        ],
        appId: [
          { required: true, message: \'请选择对应的服务应用\', trigger: \'change\' }
        ],
        developer: [
          { required: true, message: \'请填写提测人RD\', trigger: \'change\' }
        ],
        tester: [
          { required: true, message: \'请填写对应的测试人Tester\', trigger: \'change\' }
        ]

      },
      opsType: [
        { label: \'功能测试\', value: \'1\' },
        { label: \'性能测试\', value: \'2\' },
        { label: \'安全测试\', value: \'3\' }
      ],
      appIdList: []
    }
  },
  mounted() {
    if (this.$route.params.action) {
      this.testAction = this.$route.params.action
    } 
  },
  methods: {
    goBack() {
      this.$router.go(-1)
    },
    remoteMethod(query) {
      if (query !== \'\') {
        this.appIdloading = true
        setTimeout(() => {
          apiAppsIds(query).then(resp => {
            this.appIdList = resp.data
          })
          this.appIdloading = false
        }, 200)
      } else {
        this.appIdList = []
      }
    },
    appSelected() {
      // 判断获取选择应用的其他信息
      for (var it in this.appIdList) {
        if (this.appIdList[it].id === this.requestForm.appId) {
          // 以下判断为在字符为空的情况下添加,即认为没有人工再输入,快捷反填已知道信息
          if (!this.requestForm.developer) {
            this.requestForm.developer = this.appIdList[it].developer
          }
          if (!this.requestForm.tester) {
            this.requestForm.tester = this.appIdList[it].tester
          }
          if (!this.requestForm.CcMail) {
            this.requestForm.CcMail = this.appIdList[it].CcEmail
          }
          if (!this.requestForm.wiki) {
            this.requestForm.wiki = this.appIdList[it].wiki
          }
          if (!this.requestForm.gitCode) {
            this.requestForm.gitCode = this.appIdList[it].gitCode
          }
          // 填写appName信息,用于邮件发送不再额外查询
          this.requestForm.appName = this.appIdList[it].appId
        }
      }
    },
    onSubmit() {
      this.$refs[\'ruleForm\'].validate((valid) => {
        if (valid) {
          if (this.testAction === \'ADD\') {
            this.requestForm.id = undefined
            this.requestForm.type = \'1\'
            this.requestForm.createUser = this.op_user
            this.requestForm.updateUser = this.op_user
            reqCreate(this.requestForm).then(response => {
              // 如果request.js没有拦截即表示成功,给出对应提示和操作
              this.$notify({
                title: \'成功\',
                message: this.testAction === \'ADD\' ? \'提测添加成功\' : \'提测修改成功\',
                type: \'success\'
              })
              // 回到列表页面
              this.$router.push({ name: \'test\', params: { needUp: \'true\' }})
            })
          }
        } else {
          return false
        }
      })
    },
    onCancel() {
      this.$router.push(\'test\')
    }
  }
}
</script>

 

3. 提测列表叶刷新代码

即需要对页面判断是否是有对应的回调参数,是否需要刷新,如果有则调用查询方法刷新最新数据。

mounted() {
    if (this.$route.params.needUp && this.$route.params.needUp.needUp === \'true\') {
      this.searchClick()
    }
  }, 

 

最终完成的效果如演示

1)新页面转和form表单

2)远程搜索应用搜索和反填,注意下边请求,在每输入一次关键词后将触发搜索,并将结果回填到form表单中

3)输入所有信息,落库成功后,返回上一页并刷新数据,这就不做GIF了,自己看下自己的成果个吧。 

 

【代码更新】

  • 地址:https://github.com/mrzcode/TestProjectManagement

  • TAG:TPMShare13

坚持原创,坚持实践,坚持干货,如果你觉得有用,请点击推荐,也欢迎关注我博客园和微信公众号。

分类:

技术点:

相关文章:

  • 2021-07-15
  • 2021-11-07
  • 2021-12-03
  • 2022-02-20
  • 2022-01-24
  • 2021-09-04
  • 2022-02-25
  • 2022-01-20
猜你喜欢
  • 2022-01-01
  • 2021-11-28
  • 2021-10-28
  • 2021-12-19
  • 2022-03-11
  • 2021-04-06
相关资源
相似解决方案