背景

在现代Web应用开发中,实现用户友好的输入交互是提升用户体验的关键之一。例如,在表单设计中,通过点击输入框触发弹窗来辅助用户输入,并在关闭弹窗时自动填充输入框并进行必要的校验,可以显著提高表单填写的便捷性和准确性。

功能描述

点击输入框(input)时显示一个弹窗,当用户关闭弹窗时,将弹窗中的输入值赋给对应的输入框,并对输入内容进行必填项校验。

实现思路

  • 绑定事件:为 input 元素绑定 focus 事件。
  • 显示弹窗:点击事件触发后显示弹窗。
  • 关闭弹窗:使用 emit 传递参数并关闭弹窗。
  • 赋值与校验:获取传递的参数,赋值给input元素,最后表单提交时校验必填。

代码示例

安装 element-ui

npm install element-ui
# 或者
yarn add element-ui

main.js 引入 element-ui

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import "element-ui/lib/theme-chalk/icon.css";
import Element from "element-ui";

Vue.use(Element);
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

显示弹窗
使用 Element-UI 的 dialog 组件, 给 input 输入框绑定 focus 事件, 当 input 输入框获得焦点时调用 selectUser 组件里定义的 show 方法, 显示 selectUser 组件

// index.vue
<template>
  <div>
    <el-form :model="ruleForm" ref="ruleForm" label-position="left" label-width="86px">
      <el-row>
        <el-col>
          <el-form-item label="项目成员" prop="oneName">
            <el-input size="medium" v-model="ruleForm.oneName" clearable @focus="showDialog"
              placeholder="请选择"></el-input>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <SDialog ref="SDialog"></SDialog>
  </div>
</template>

<script>
import SDialog from '@/views/popupAndSelect/SDialog.vue';

export default {
  name: 'show',
  data() {
    return {
      ruleForm: {
        oneName: "",
      }
    }

  },
  components: { SDialog },
  methods: {
    showDialog() {
      this.$refs.SDialog.show()
    },
  }
}
</script>

selectUser.vue 文件

// selectUser.vue
<template>
  <div>
    <el-dialog title="请选择" :visible.sync="isShow" width="600px" :before-close="handleClose" center :show-close="false"
      :close-on-click-modal="false" append-to-body :close-on-press-escape="false" class="dialog_class">
      <h2>dialog</h2>
      <span slot="footer" class="dialog-footer">
        <el-button class="btn submit" type="primary" @click="ok">确 定</el-button>
        <el-button class="btn cancel" @click="cancel">取 消</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "SDialog",
  data() {
    return {
      isShow: false,
    }
  },
  methods: {
    show() {
      this.isShow = true;
    },
    ok() {
      this.cancel();
    },
    cancel() {
      this.isShow = false;
    },
    handleClose(done) {
      done();
    },
  }
}
</script>

在这里插入图片描述

传递参数并关闭弹窗

选择好人员后, selectUser 组件使用 emit 自定义事件 selectUser 将选择的人员数据传递给 index.vue, index.vueselect-user 标签定义事件 @selectUser="selectUser" 接受数据

// selectUser.vue
<template>
  <div>
    <el-dialog title="请选择项目人员" :visible.sync="isShow" width="1050px" :before-close="handleClose" center :show-close="false"
      :close-on-click-modal="false" append-to-body :close-on-press-escape="false" class="dialog_class">
      <div class="content">
        <el-checkbox-group v-model="userIds" class="checkbox-con" @change="handleCheckedCitiesChange">
          <el-checkbox v-for="item in userList" :key="Math.random()" border :label="item.userId">{{ `${item.userName} -
            ${item.phone}` }}</el-checkbox>
        </el-checkbox-group>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button class="btn submit" type="primary" @click="ok">确 定</el-button>
        <el-button class="btn cancel" @click="cancel">取 消</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import { getUser } from '@/api/prokect.js';
export default {
  name: "SDialog",
  data() {
    return {
      userList: [], // 人员列表
      userIds: [], // 选中的用户id
      isShow: false,
      selectioneds: [], // 选中的项
    }
  },
  methods: {
    show() {
      this.isShow = true;
      this.query();
    },
    ok() {
      this.$emit("selectUser", this.selectioneds);
      this.cancel();
    },
    cancel() {
      this.isShow = false;
    },
     // 查询人员列表
     query() {
      getUser().then(({ data: { userList } }) => {
        this.userList = userList;
      })
    },
     // 多选
     handleCheckedCitiesChange(value) {
      console.log(value); // value勾选的所有项
      this.selectioneds = []; // 先初始化
      this.userIds = [];
      value && value.forEach(v => {
        // 存储用户id
        this.userIds.push(v);
        this.userList.forEach(w => {
          if (v === w.userId) {
            // 选中的项
            this.selectioneds.push(w);
          }
        })
      });
    },
    handleClose(done) {
      done();
    },
  }
}
</script>

index.vue

// index.vue
<template>
  <div>
    <el-form :model="ruleForm" ref="ruleForm" label-position="left" label-width="86px">
      <el-row>
        <el-col>
          <el-form-item label="项目成员" prop="oneName">
            <el-input size="medium" v-model="ruleForm.oneName" clearable @focus="showDialog"
              placeholder="请选择"></el-input>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <SDialog ref="SDialog" @selectUser="selectUser"></SDialog>
  </div>
</template>

<script>
import SDialog from '@/views/popupAndSelect/SDialog.vue';

export default {
  name: 'show',
  data() {
    return {
      ruleForm: {
        oneName: "",
      }
    }

  },
  components: { SDialog },
  methods: {
    showDialog() {
      this.$refs.SDialog.show()
    },
    selectUser(data){
      console.log('data::: ', data);
    }
  }
}
</script>

在这里插入图片描述

校验表单

Element-UI 表单校验规则:

  • 定义表单模型:通常使用 v-model 指令将表单项与 Vue 实例中的数据绑定。
  • 定义校验规则:通过 el-form:rules 属性定义校验规则,这是一个对象,其键是表单域的名字,值是一个对象或校验规则数组。
  • 触发校验:可以通过调用 el-formvalidate 方法来手动触发校验,也可以通过表单控件的 trigger 属性来指定自动触发校验的时机。
  • 下面是 rule 规则的属性介绍:
    • required: 布尔值,表示是否必须填写该项,默认为 false。如果设为 true,则该项不能为空。
    • message: 字符串,当验证失败时显示的消息。
    • trigger: 字符串或字符串数组,表示在什么情况下触发验证。可以是 'blur'(失焦时触发)、'change'(值改变时触发)或 'submit'(提交时触发)。也可以同时设置多个触发器。
    • type: 字符串,验证类型,可以是 'string', 'number', 'integer', 'email', 'url', 'date', 'datetime', 'time'。默认为 'string'
    • min: 数字,对于字符串来说是最小长度,对于数字来说是最小值。
    • max: 数字,对于字符串来说是最大长度,对于数字来说是最大值。
    • pattern: 正则表达式对象,用于验证字段值是否符合正则表达式的模式。
    • validator: 函数,自定义验证方法,接收 (rule, value, callback) 作为参数,其中 rule 是当前的验证规则对象,value 是当前字段的值,callback 是验证完毕后的回调函数。

注意:

el-form-item 标签里的表单元素的 v-model需注意以下几点:

  • 绑定的属性名要与 rules 里定义的规则名字一致,否则不会触发校验。
  • 一定要是 el-formmodel 绑定的对象里的属性,否则会一直校验不通过。
  • el-formmodel 绑定的对象在 data 里定义, 且 el-form-item 下的 v-model 绑定的属性要在该对象里定义。

校验表单示例

<template>
  <div>
    <el-form :model="ruleForm" ref="ruleForm" :rules="rules" label-position="left" label-width="86px">
      <el-row class="row-from">
        <el-col class="text-left col-item">
          <el-form-item label="项目成员" prop="oneName">
            <el-tooltip class="item" size="medium" effect="dark" :disabled="selectNum == 0" placement="top"
              v-model="isShowTooltip">
              <div slot="content" class="tooltip-con">
                <div v-for="item in selectUserList" :key="item.userId" class="size16 team-text">{{ `${item.userName} -
                  ${item.phone}` }}</div>
                <i class="el-icon-close close" @click="closeTooltip"></i>
              </div>
              <el-badge :value="selectNum" class="item" type="primary">
                <el-input size="medium" v-model="ruleForm.oneName" clearable @focus="showDialog"
                  placeholder="请选择"></el-input>
              </el-badge>
            </el-tooltip>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row class="row-from">
        <el-col class="text-left">
          <el-button class="btn select" size="medium" @click="submit" type="primary">提交</el-button>
        </el-col>
      </el-row>
    </el-form>
    <SDialog ref="SDialog" @selectUser="selectUser"></SDialog>
  </div>
</template>

<script>
import SDialog from '@/views/popupAndSelect/SDialog.vue';

export default {
  name: 'show',
  data() {
    const checkOneName = (rule, value, callback) => {
      console.log('checkTeam::: ', value);
      if (!value) {
        callback(new Error('请选择和作社项目组名称'))
      } else {
        callback()
      }
    };
    return {
      isShowTooltip: false,
      selectNum: 0,
      rules: {
        oneName: [
          { required: true, message: '请选择名字', trigger: 'blur,change' },
          { validator: checkOneName, trigger: 'change' }
        ],
      },
      ruleForm: {
        nameList: [],
        userIds: [],
        oneName: "",
      },
      selectUserList: [],
    }

  },
  components: { SDialog },
  methods: {
    showDialog() {
      this.$refs.SDialog.show()
    },

    selectUser(data) {
      console.log('data::: ', data);
      // 拿到所有用户的 userName
      let nameList = data.map(item => item.userName);
      this.ruleForm.oneName = `${data[0].userName} - ${data[0].phone}`;
      this.selectNum = nameList.length;
      this.ruleForm.nameList = nameList;
      // 拿到所有用户的 userId
      this.ruleForm.userIds = data.map(item => item.userId);
      this.selectUserList = data;
    },
    // 关闭 Tooltip
    closeTooltip() {
      this.isShowTooltip = false;
    },
    // 提交表单
    submit() {
      this.$refs.ruleForm.validate(valid => {
        if (valid) {
          this.$message({
            message: '提交成功',
            type: 'success'
          });
        } else {
          console.log('error submit!!');
          return false;
        }
      });
    },
  }
}
</script>

在这里插入图片描述

完整代码

selectUser.vue

// selectUser.vue
<template>
  <div>
    <el-dialog title="请选择项目人员" :visible.sync="isShow" width="1050px" :before-close="handleClose" center
      :show-close="false" :close-on-click-modal="false" append-to-body :close-on-press-escape="false"
      class="dialog_class">

      <div class="content">
        <el-checkbox-group v-model="userIds" class="checkbox-con" @change="handleCheckedCitiesChange">
          <el-checkbox v-for="item in userList" :key="Math.random()" border :label="item.userId">{{ `${item.userName} -
            ${item.phone}` }}</el-checkbox>
        </el-checkbox-group>
      </div>

      <span slot="footer" class="dialog-footer">
        <el-button class="btn submit" type="primary" @click="ok">确 定</el-button>
        <el-button class="btn cancel" @click="cancel">取 消</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import { getUser } from '@/api/prokect.js';

export default {
  name: "selectUser",
  data() {
    return {
      userList: [], // 人员列表
      userIds: [], // 选中的用户id
      isShow: false,
      selectioneds: [], // 选中的项
    }
  },
  methods: {
    show({ selectioneds = [] }) {
      this.isShow = true;
      this.query();
      if (selectioneds.length > 0) {
        this.userIds = selectioneds.map(item => item.userId);
        this.selectioneds = selectioneds;
      }
    },
    // 查询人员列表
    query() {
      getUser().then(({ data: { userList } }) => {
        this.userList = userList;
      })
    },
    // 多选
    handleCheckedCitiesChange(value) {
      console.log(value); // value勾选的所有项
      this.selectioneds = []; // 先初始化
      this.userIds = [];
      value && value.forEach(v => {
        // 存储用户id
        this.userIds.push(v);
        this.userList.forEach(w => {
          if (v === w.userId) {
            // 选中的项
            this.selectioneds.push(w);
          }
        })
      });
    },
    handleClose(done) {
      done();
    },
    ok() {
      this.$emit("selectUser", this.selectioneds);
      this.cancel();
    },
    cancel() {
      this.selectioneds = [];
      this.userIds = [];
      this.userList = [];
      this.isShow = false;
    },
  }
}
</script>

<style lang="scss" scoped>
::v-deep .text-left .el-form-item .el-form-item__label {
  font-size: 15px;
  color: #333;
  font-weight: 400;
}

.pagination {
  float: right;
  padding-top: 18px;
}

.radio-con .el-radio,
.checkbox-con .el-checkbox {
  margin: 20px 20px 0 10px;
  width: 216px;
}

::v-deep .el-dialog {
  margin-top: 5vh !important;
  padding-bottom: 78px;
  max-height: 90vh !important;
  min-height: 300px;
  overflow: hidden;

}

.content {
  overflow-y: scroll;
  max-height: 72vh;
  min-height: 56px;

  &::-webkit-scrollbar {
    width: 7px;
    height: 7px;
    background-color: #fff;
  }

  &::-webkit-scrollbar-thumb {
    border-radius: 5px;
    background-color: rgba(144, 146, 152, 0.3);
  }
}

::v-deep.el-dialog__footer {
  padding: 20px;
  width: 100%;
  position: absolute;
  background-color: #fff;
  bottom: 0;
  z-index: 200;
}
</style>

index.vue 文件

// index.vue
<template>
  <div>
    <el-form :model="ruleForm" ref="ruleForm" :rules="rules" label-position="left" label-width="86px" class="ruleForm">
      <el-row class="row-from">
        <el-col class="text-left col-item">
          <el-form-item label="项目名称" prop="projectName">
            <el-input size="medium" v-model="ruleForm.projectName" placeholder="请输入项目名称">
            </el-input>
          </el-form-item>
        </el-col>
      </el-row>

      <el-row class="row-from">
        <el-col class="text-left col-item">
          <el-form-item label="项目年份" prop="createYear">
            <el-date-picker size="medium" v-model="ruleForm.createYear" type="year" value-format="yyyy"
              placeholder="请选择年份" id="input">
            </el-date-picker>
          </el-form-item>
        </el-col>
      </el-row>

      <el-row class="row-from">
        <el-col class="text-left col-item">
          <el-form-item label="项目成员" class="typeface" prop="oneName">
            <el-tooltip class="item" size="medium" effect="dark" :disabled="selectNum == 0" placement="top"
              v-model="isShowTooltip">
              <div slot="content" class="tooltip-con">
                <div v-for="item in selectUserList" :key="item.userId" class="size16 team-text">{{ `${item.userName} -
                  ${item.phone}` }}</div>
                <i class="el-icon-close close" @click="closeTooltip"></i>
              </div>
              <el-badge :value="selectNum" class="item" type="primary">
                <el-input size="medium" v-model="ruleForm.oneName" clearable @focus="showDialog" @clear="clearName"
                  placeholder="请选择"></el-input>
              </el-badge>
            </el-tooltip>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row class="row-from">
        <el-col class="text-left">
          <el-button class="btn select" size="medium" @click="submit" type="primary">提交</el-button>
          <el-button class="btn select" size="medium" @click="reset" type="primary">重置</el-button>
        </el-col>
      </el-row>
    </el-form>
    <select-user ref="selectUser" @selectUser="selectUser"></select-user>
  </div>
</template>

<script>
import selectUser from '@/views/popupAndSelect/selectUser.vue';

export default {
  name: 'popupAndSelect',
  data() {
    const checkOneName = (rule, value, callback) => {
      console.log('checkTeam::: ', value);
      if (!value) {
        callback(new Error('请选择和作社项目组名称'))
      } else {
        callback()
      }
    };
    return {
      isShowTooltip: false,
      selectNum: 0,
      rules: {
        oneName: [
          { required: true, message: '请选择名字', trigger: 'blur,change' },
          { validator: checkOneName, trigger: 'change' }
        ],
        projectName: [
          { required: true, message: '请输入项目名称', trigger: 'blur' },
        ],
        createYear: [
          { required: true, message: '请选择年份', trigger: 'change' },
        ],
      },
      ruleForm: {
        projectName: "",
        createYear: "",
        nameList: [],
        userIds: [],
        oneName: "",
      },
      selectUserList: [],
    }
  },
  components: { selectUser },
  methods: {
    showDialog() {
      this.$refs.selectUser.show(this.selectUserList)

    },
    selectUser(data) {
      console.log('data::: ', data);
      // 拿到所有用户的 userName
      let nameList = data.map(item => item.userName);
      this.ruleForm.oneName = `${data[0].userName} - ${data[0].phone}`;
      this.selectNum = nameList.length;
      this.ruleForm.nameList = nameList;
      // 拿到所有用户的 userId
      this.ruleForm.userIds = data.map(item => item.userId);
      this.selectUserList = data;
    },
    clearName() {
      this.ruleForm.oneName = '';
      this.selectNum = 0;
      this.ruleForm.nameList = [];
      this.ruleForm.userIds = [];
      this.selectUserList = [];
    },
    // 关闭 Tooltip
    closeTooltip() {
      this.isShowTooltip = false;
    },
    // 提交表单
    submit(){
      this.$refs.ruleForm.validate(valid => {
        if (valid) {
          this.$message({
            message: '提交成功',
            type: 'success'
          });
        } else {
          console.log('error submit!!');
          return false;
        }
      });
    },
    // 重置表单
    reset(){
      this.$refs.ruleForm.resetFields();
      this.ruleForm.nameList = [];
      this.ruleForm.userIds = [];
      this.selectNum = 0;
      this.selectUserList = [];
    }
  }
}
</script>

<style lang="scss" scoped>
.ruleForm {
  display: flex;
  flex-direction: column;

  .row-from {
    flex-wrap: wrap;

    &::before {
      display: none;
    }

    &::after {
      display: none;
    }

    .col-item {
      width: 520px;
    }
  }

}

.text-left {
  text-align: left;
}

::v-deep .el-input__inner {
  width: 434px;
}

.tooltip-con {
  position: relative;
  padding: 24px 36px;

  .close {
    position: absolute;
    top: 0;
    right: 0;
    cursor: pointer;
    font-size: 18px;
  }
}
</style>

表单字段 oneName 的验证规则:

  • 配置必填项验证,点击提交按钮时触发 blurchange 事件。
  • 配置自定义函数 checkOneName ,在 oneName 值发生改变时触发 change 事件。

还有一点得注意,如果需要回显表单数据,通过后端接口获取数据后,千万不要直接赋值给 ruleForm 对象(例: this.ruleForm = res.data),因为 v-model 绑定的是 oneName, 而 oneName 是前端自己定义的属性,后端接口返回的对象里面没有 oneName 属性,直接赋值会覆盖原有的 ruleForm 对象,导致提交的时候就会一直校验不通过。

正确用法: this.ruleForm= Object.assign(this.ruleForm, res.data);

实现效果
在这里插入图片描述

总结

通过上述步骤,我们实现了一个功能完整的表单校验,其中项目成员字段通过点击输入框显示弹窗,并在关闭弹窗时自动给输入框赋值。同时,通过定义验证规则确保了必填项的正确性和完整性。这种设计不仅提高了用户体验,也保证了数据的有效性和一致性。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐