測試開發【提測平臺】分享10-Element UI抽屜和表單校驗&增改介面合併實現應用管理

MrZ大奇發表於2021-09-21

微信搜尋【大奇測試開】,關注這個堅持分享測試開發乾貨的傢伙。

開篇說個小討論,一個群裡聊天聊到關於更新篇章的長度,是小篇幅多次,還是每次按照一個小完整的功能,我個人的是按照後種來的,主要的思考就是希望集中的時間段去實踐,這樣效率高且有每次會有一個小小的成就感,另一種當然有它的好處,更能持續輸出,看的內容少,且很有助於閱讀量。因此做個個小小的互動,歡迎留言,看看大家的想法~

本篇內容思維導

 服務應用管理,主要是前端的知識點,學習一種新的前端控制元件作為新增修改基層頁,再重點掌握下表單資料提交的時候校驗,這裡不限於非空、格式等。對於後端的沒有新增的知識點,可以參照專案產品分類管理的程式碼分別寫新增和修改兩個介面,也可按照這次我將兩個介面合併成一個介面,其實就是之前有一章節留過的一個思考題“如何實現兩個介面的合併”不知道還有沒印象,總之本篇內容不是很多,加油~

 

知識點學習

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

  • 以上資料字元長度暫無限制

 

功能實現(步驟)虛擬碼

  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)新增操作,預設為空資料,提交不完整資訊是否有校驗提示阻止提交

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

堅持原創,堅持實踐,堅持乾貨,如果你覺得有用,請點選推薦,也歡迎關注我部落格園和微信公眾號。

相關文章