测试开发【提测平台】分享13-远程搜索和路由$route使用实现新建提测需求

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

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

基本知识点学习

远程搜索

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

 

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

vue中$router路由

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

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

1
2
3
this.$router('apps')
// 或
this.$router({name: 'apps'}

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

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

3.带参数跳转

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

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

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

5. 返回上一页

1
this.$router.go(-1)

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

需求功能实现

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

提测添加接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@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()后进行逻辑判断,并且特别注意的是之前还预留个是否发送成功的字段,也就是还需要判断邮件是否发送成功了,再对插入的数据立马进行一次更新操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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以及跳转地址

1
2
3
4
5
6
7
{
    path: 'commit',
    name: 'commit',
    hidden: true,
    component: () => import('@/views/test/manger/commit'),
    meta: { title: '需求提测', icon: 'dashboard' }
},

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

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

 

2. 实现提测form表单

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

Template模块代码

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<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)提交添加成功后也有个跳转到列表的处理,列表需要同样用判断是否有回传的方式,处理是否需要立即刷新最新列表数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<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. 提测列表叶刷新代码

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

1
2
3
4
5
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

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

posted @   MegaQi  阅读(271)  评论(0编辑  收藏  举报
编辑推荐:
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
点击右上角即可分享
微信分享提示