测试开发【提测平台】分享10-Element UI抽屉和表单校验&增改接口合并实现应用管理

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

开篇说个小讨论,一个群里聊天聊到关于更新篇章的长度,是小篇幅多次,还是每次按照一个小完整的功能,我个人的是按照后种来的,主要的思考就是希望集中的时间段去实践,这样效率高且有每次会有一个小小的成就感,另一种当然有它的好处,更能持续输出,看的内容少,且很有助于阅读量。因此做个个小小的互动,欢迎留言,看看大家的想法~

本篇内容思维导

测试开发【提测平台】分享10-Element UI抽屉和表单校验&增改接口合并实现应用管理

 服务应用管理,主要是前端的知识点,学习一种新的前端控件作为新增修改基层页,再重点掌握下表单数据提交的时候校验,这里不限于非空、格式等。对于后端的没有新增的知识点,可以参照项目产品分类管理的代码分别写新增和修改两个接口,也可按照这次我将两个接口合并成一个接口,其实就是之前有一章节留过的一个思考题“如何实现两个接口的合并”不知道还有没印象,总之本篇内容不是很多,加油~

 

知识点学习

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 定义什么时候触发校验

 

项目实战应用

按惯例,看完新知识点后,继续参照之前的产品原型和需求实现本期项目开发内容,即应用管理中的增加和修改功能原型和需求    

测试开发【提测平台】分享10-Element UI抽屉和表单校验&增改接口合并实现应用管理

 此页面 添加/修改 功能需求说明:

  • 点击添加/编辑弹抽屉,红色 * 为必填选项

  • 分类来源归属分类,外键关联

  • 应用名称有重名校验,创建后不可以修改

  • 默认必须有测试、研发和产品负责人,多人邮件用;分隔

  • 目前要求必须填写代码地址,以便测试人员了解信息,编写测试code

  • 以上数据字符长度暂无限制

 

功能实现(步骤)伪代码

  1. Python Flask 编写一个接口同时实现添加和修改数据功能

  2. 创建抽屉控件,内嵌form,实现原型中的各控件绑定

  3. 控件红色*标记的规则配置,触发方式trigger: 'blur'即点击提交统一校验,

  4. 页面修改和添加使用同一个Drawer 标题根据上一步操作动态显示 “应用添加” / “应用编辑”

  5. 应用编辑的自增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)添加操作,默认为空数据,提交不完整信息是否有校验提示阻止提交

测试开发【提测平台】分享10-Element UI抽屉和表单校验&增改接口合并实现应用管理

2)编辑操作,数据是否正常反填,修改后提交是否正常更新落库

测试开发【提测平台】分享10-Element UI抽屉和表单校验&增改接口合并实现应用管理

 

以上为本篇全部内容,目前应用管理的方面开发全部开发完了,后边将进入提测的主流程阶段。

 

设计开发中会遇到各种各样的问题,这些文章有我在写的时候都需要半天,有时候需要几天,因为总会有困难点和调试的问题,我相信大家在实践中更是如此,就即使你是完全复制粘贴的代码,但有问题我觉得是好事,这能让我们可以知其然,知其所以然,以及逐渐了解到解决问题方式。大家有什么问题可以留言交流或和关注公众号发私信,看到我会尽可能帮忙解答。

 

问题集锦

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

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

上一篇:Cookie、Session


下一篇:javaweb-servlet