【Flask】测试平台开发,应用管理模块实现-第十一篇

概述

通过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++

  1. 自定义规则是一些较为复杂的校验通过回调进行逻辑自定义,参考例子第二个form-item

5)上述被定义required: true 规则的控件在会自动增加 红色*****号,trigger 定义什么时候触发校验

开发页面

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

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

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

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

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

  • 目前要求必须填写代码地址,以便测试人员了解信息,编写测试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

    if not body.get('note'):
        body['note'] = ''
    if not body.get('cCEmail'):
        body['cCEmail'] = ''
    if not body.get('gitCode'):
        body['gitCode'] = ''
    if not body.get('wiki'):
        body['wiki'] = ''
    if not body.get('more'):
        body['more'] = ''
    if not body.get('createUser'):
        body['createUser'] = ''
    if not body.get('updateUser'):
        body['updateUser'] = ''

    # 使用连接池链接数据库
    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, `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["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`,`createUser`,`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["createUser"],body["updateUser"]))
                # 提交执行保存新增数据
                connection.commit()

        return resp_success
  • 组合表单的抽屉控件实现

在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: '',
      createUser: '',
      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>
  • 实现接口请求

在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.createUser = 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.createUser = ''
      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
        }
      })
    }
  • 联调前后端运行

分别运行前后端,解决掉运行中的错误后,做两条测试验证功能是否OK

1)添加操作,默认为空数据,提交不完整信息是否有校验提示阻止提交

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

至此添加服务应用管理产品管理的功能模块我们就开发完成了,下一阶段我们将继续开发新的功能菜单栏--测试管理模块

【注解&参考】

  • 注解1\] ++https://element.eleme.io/#/zh-CN/component/drawer++

相关推荐
Ice__Cai5 小时前
深入掌握 Flask 配置管理:从基础到高级实战
python·flask·gunicorn
小鱼儿亮亮6 小时前
Vue.js 父子组件通信的十种方式
前端·vue.js
疯狂踩坑人6 小时前
useRequest - vue3版本
vue.js
P7Dreamer6 小时前
Vue 表格悬停复制指令:优雅地一键复制单元格内容
前端·vue.js
鹏多多6 小时前
Web图像编辑神器tui.image-editor从基础到进阶的实战指南
前端·javascript·vue.js
老华带你飞16 小时前
社区互助|基于SSM+vue的社区互助平台的设计与实现(源码+数据库+文档)
java·前端·数据库·vue.js·小程序·毕设·社区互助平台
chxii17 小时前
7.2elementplus的表单布局与模式
javascript·vue.js·elementui
dreams_dream17 小时前
vue中的与,或,非
前端·javascript·vue.js
1024小神18 小时前
vue/react项目如何跳转到一个已经写好的html页面
vue.js·react.js·html