实战踩坑

ui

1. element 表单验证, `ref`+`prop`+`rules`

返回目录

1. element ui

  1. element 表单验证, ref+prop+rules
  2. 覆盖样式 >>> .class , /deep/.class
  3. 页面最小宽度 overflow: auto; , min-width: 1000px;

2. element 大图预览循环

:preview-src-list="imgListObj.list.map(item=>{return item.img}).filter((item,val)=>{return val>=index}).concat(imgListObj.list.map(item=>{return item.img}).filter((item,val)=>{return val<index}))"

3. elememt upload

<el-upload
  ref="uploadcover"
  class="avatar-uploader"
  action
  :show-file-list="false"
  :before-upload="beforeUpload"
  :http-request="uploadFile"
>
  <el-image
    v-if="infoValue.cover"
    :src="infoValue.cover"
    class="avatar"
    fit="scale-down"
    @click="uploadWhat('cover')"
  />
  <i
    v-else
    class="el-icon-plus avatar-uploader-icon"
    @click="uploadWhat('cover')"
  />
  <div slot="tip" class="el-upload_tip">
    只能上传一张jpg,png图片,且不超过1Mb
  </div>
</el-upload>
$upload: 100px;
/deep/.avatar-uploader .el-upload {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.avatar-uploader .el-upload:hover {
  border-color: #409eff;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: $upload;
  height: $upload;
  line-height: $upload;
  text-align: center;
}
.avatar {
  width: $upload;
  height: $upload;
  display: block;
}
  beforeUpload(file) {
      console.log('beforeUpload', file)
      const isImage = /^image\/(jpg|png)$/.test(file.type)
      const isLt1M = file.size / 1024 / 1024 < 1
      if (!isImage) {
        this.$message.error('上传图片只能是 jpg/png 格式!')
      } else if (!isLt1M) {
        this.$message.error('上传图片大小不能超过 1MB!')
      }
      return isImage && isLt1M
    },
    uploadFile(param) {
      const fileObj = param.file
      var form = new FormData()
      form.append('file', fileObj)
      form.append('type', 1)
      upload(form).then(res => {
        console.log('上传文件', res)
        if (!res.errcode) {
          console.log('上传文件', res.data)
          if (this.uploadWhatVal === 'cover') {
            this.infoValue.cover = res.data
          }
          if (this.uploadWhatVal === 'avatar') {
            this.infoValue.avatar = res.data
          }
        }
      })
    }

4. vant 复选框全选

vant 用 dom 操作全选会造成卡顿
解决办法:点击全选时把渲染列表赋值到选中列表中即可

5. elemennt el-input 组件重复获取焦点

需求:input 框组件每次显示隐藏都自动获取焦点
问题:设置 autofocus 属性只会在第一次生效,后续再次打开 input 组件不会自动聚焦

解决方法

// 每次打开input框执行以下方法
// 可以使用watch侦听组件的显示/隐藏,显示时执行以下 foucs()
this.$nextTick(() => {
  this.$refs.elinput.focus();
});

6. elememt 表格+无限滚动解决办法

vue-infinite-loading

7. element 级联选择器

//国,省,市
 <el-cascader v-model="region" :props="props" />

  props: {
        lazy: true,
        lazyLoad(node, resolve) {
          const { level, value } = node
          const params = {
            address: value
          }
          getAddress(params).then(res => {
            const { errcode, data } = res
            if (!errcode) {
              const listObject = data
              const listArray = []
              Object.keys(listObject).forEach(key => {
                listArray.push({
                  label: listObject[key],
                  value: key,
                  leaf: level >= 2
                })
              })
              resolve(listArray)
            }
          })
        }
      }
<el-cascader
  v-model="searchForm.user"
  style="width:250px"
  placeholder="选择个人号"
  :props="searchForm.props"
  :show-all-levels="false"
/>

props: {
  lazy: true,
  lazyLoad(node, resolve) {
    const { level, value } = node
    const data = {
      groupid: value
    }
    // console.log('有没有value', value)
    const listArray = []
    if (!value) {
      getGroup().then(res => {
        const { errcode, data } = res
        if (!errcode) {
          const listObject = data.list
          const allitem = {
            label: '全部个人号',
            value: undefined,
            leaf: level >= 0
          }
          listArray.push(allitem)
          listObject.forEach(item => {
            listArray.push({
              label: `${item.group_name}(${item.cnt})`,
              value: item.id,
              leaf: level >= 2
            })
          })
          resolve(listArray)
        }
      })
    } else {
      getPersonalList(data).then(res => {
        const { errcode, data } = res
        if (!errcode) {
          const listObject = data.list
          listObject.forEach(item => {
            listArray.push({
              label: item.nickname,
              value: item.mainwxid,
              leaf: level >= 1
            })
          })
          resolve(listArray)
        }
      })
    }

    // .setTimeout(() => {
    //   const nodes = Array.from({ length: level + 1 })
    //     .map(item => ({
    //       value: ++id,
    //       label: `选项${id}`,
    //       leaf: level >= 2
    //     }))
    //   // 通过调用resolve将子节点数据返回,通知组件数据加载完成
    //   resolve(nodes)
    // }, 1000)
  }
},

8. element 时间日期选择器配置项

<template>
  <div>
    <!-- 调用此组件时注意 size 为 默认值为small ,可以传参改变size -->
    <el-date-picker
      v-model="dateTime"
      type="daterange"
      :picker-options="pickerOptions"
      range-separator="至"
      start-placeholder="开始日期"
      end-placeholder="结束日期"
      :size="size"
      @change="pickdatetime"
    />
  </div>
</template>

<script>
  export default {
    name: "DataPicker",
    props: {
      size: {
        type: String,
        default: "small",
      },
      defaultVal: {
        type: Array,
        default() {
          return [];
        },
      },
    },
    data() {
      return {
        dateTime: "",
        pickerOptions: {
          disabledDate(time) {
            return time.getTime() > Date.now();
          },
          shortcuts: [
            {
              text: "最近一周",
              onClick(picker) {
                const end = new Date();
                const start = new Date();
                start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
                picker.$emit("pick", [start, end]);
              },
            },
            {
              text: "最近一个月",
              onClick(picker) {
                const end = new Date();
                const start = new Date();
                start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
                picker.$emit("pick", [start, end]);
              },
            },
            {
              text: "最近三个月",
              onClick(picker) {
                const end = new Date();
                const start = new Date();
                start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
                picker.$emit("pick", [start, end]);
              },
            },
          ],
        },
      };
    },
    watch: {
      defaultVal(newval, oldval) {
        console.log(newval);
        if (!newval.length) {
          this.dateTime = "";
        }
      },
    },
    methods: {
      pickdatetime() {
        //   console.log("选中的时间", this.dateTime);
        this.$emit("pickdatetime", this.dateTime);
      },
    },
  };
</script>

9. element 二次封装 el-Popover 组件

<template>
  <div>
    <el-popover
      ref="popover"
      v-model="visible"
      placement="top"
      trigger="manual"
      popper-class="right--click--popover"
    >
      <div ref="popover" class="right--click--popover__inner">
        <el-button type="text" @click="handlerRecall">撤回</el-button>
      </div>
      <slot slot="reference" name="inner" />
    </el-popover>
  </div>
</template>
<script>
import { sendMsg } from "@/api/chat.js";

export default {
  name: "Name",
  components: {},
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    activeChat: {
      type: Object,
      default() {
        return {};
      },
    },
    msgId: {
      type: String,
      default: "",
    },
  },
  data() {
    return {};
  },
  watch: {
    visible(newval, oldval) {
      if (newval) {
        // 显示添加监听
        document.addEventListener("click", this.handlePopVisible);
      } else {
        // 隐藏移除监听
        document.removeEventListener("click", this.handlePopVisible);
      }
    },
  },
  methods: {
    handlePopVisible(e) {
      this.$nextTick(() => {
        const pop = document.querySelector(".right--click--popover");
        // console.log('pop', e.target)
        if (!pop.contains(e.target)) {
          this.$emit("handlerRecall", false);
        }
      });
    },
    handlerRecall() {
      this.$emit("handlerRecall", false);
    },
  },
};
</script>
<style lang="scss">
@import "../../../../styles/theme/index.css";
.right--click--popover {
  min-width: 0px;
  padding: 0px 10px;
}
</style>

部分调用代码

<template>
  <div>
   <PopMenu :visible="message.popMenuVisible" :active-chat="activeChat" :msg-id="message.msg_id" @handlerRecall="handlerRecall">
   <div slot="inner"  @contextmenu.prevent="handleRightClick(message)">右击显示</div>
   </PopMenu>
  </div>
</template>

<script>
import PopMenu from '../../components/PopMenu/index'
export default {
  name: 'Name',
  components: {
    PopMenu
  },
  methods: {
    handleRightClick(val) {
      console.log('右击', val)
      console.log('seatName', this.fanId)
      this.list = this.list.map(item => {
        item.isFromAlt = item.sender_key === this.altKey
        if (item.msg_id === val.msg_id && item.isFromAlt) {
          item.popMenuVisible = true
        } else {
          item.popMenuVisible = false
        }
        return item
      })
      this.popMenuVisibleGlobal = true
    },
    handleScroll(e) {
      console.log(e)
      this.handlerRecall()
    },
    handlerRecall() {
      this.list = this.list.map(item => {
        item.popMenuVisible = false
        return item
      })
      this.popMenuVisibleGlobal = false
    }
  }
<script>

10. element 隐藏 checkbox 的文本

<!-- 1. 加个空格 -->
<el-checkbox :label="item.id">&nbsp;</el-checkbox>
// 2. 隐藏label的区域
.el-checkbox {
  /deep/.el-checkbox__label {
    display: none;
  }
}

11. element checkbox-group 高级使用

    // 根据id获取整个对象
    checkListObj() {
      this.getListObj.list.forEach(item => {
        if (this.checkedList.includes(item.id)) {
          if (!this.arr.map(item => item.id).includes(item.id)) {
            this.arr.push(item)
          }
        }
        //  else {
        //   this.arr = this.arr.filter(item => {
        //     if (this.checkedList.includes(item.id)) {
        //       return item
        //     }
        //   })
        // }
      })
      if (this.arr.length !== this.checkedList.length) {
        this.arr = this.arr.filter(item => {
          if (this.checkedList.includes(item.id)) {
            return item
          }
        })
      }
    },
    // 全选
    handleCheckAllChange(val) {
      // this.checkedList = val ? this.getListObj.list.map(item => {
      //   return item.id
      // }) : []
      // this.checkedList = val ? this.getListObj.list : []
      // this.isIndeterminate = false

      // 当前页全选
      const arr = this.getListObj.list.map(item => item.id)
      if (val) {
        arr.forEach(item => {
          if (!this.checkedList.includes(item)) {
            this.checkedList.push(item)
          }
        })
      } else {
        this.checkedList = this.checkedList.filter(item => {
          return !arr.includes(item)
        })
      }
      this.isIndeterminate = false
    },
    // 选择单个
    handleCheckedItemChange(value) {
      // let checkedCount =value.length
      // this.checkAll = checkedCount === this.getListObj.list.length
      // this.isIndeterminate =
      //   checkedCount > 0 && checkedCount < this.getListObj.list.length
      // console.log(checkedCount)
      let checkedCount = 0
      const arr = this.getListObj.list.map(item => item.id)
      value.forEach(item => {
        if (arr.includes(item)) {
          checkedCount++
        }
      })
      this.checkAll = checkedCount === arr.length
      this.isIndeterminate = checkedCount > 0 && checkedCount < arr.length
    },

12. element 阻止组件的 @change 事件冒泡

主角: @click.native.stop

<!-- checkbox的 @change 事件会触发折叠面板的 @change 事件 -->

<el-collapse
  v-model="tabsAct"
  class="collapse"
  accordion
  @change="collapseChange"
>
  <el-collapse-item v-for="(i) in catalog" :key="i.status" :name="i.status">
    <template slot="title">
      <!-- <i :class="item.icon" /> -->
      <el-checkbox
        v-model="checkAll"
        @change="handleCheckAllChange"
        @click.native.stop
        >{{ i.title }}</el-checkbox
      >
    </template>
  </el-collapse-item>
</el-collapse>

13. elememt 表格复选功能数据处理

// 需要的数据类型
obj: {
// 类型: [分组1, 分组2]
1: [1,2,3],
2: [3,4,5],
3: [],
4: [1],
}
/*
// data中的数据:
      catalog: [
        { title: '文字', icon: 'el-icon-picture-outline', status: '5' },
        { title: '图片', icon: 'el-icon-picture-outline', status: '1' },
        { title: '语音', icon: 'el-icon-microphone', status: '2' },
        { title: '分享页', icon: 'el-icon-link', status: '3' },
        { title: '小视频', icon: 'el-icon-video-camera', status: '4' },
        { title: '二维码', icon: 'el-icon-video-camera', status: '6' }
      ],
      checkedListObj: { '1': [], '2': [], '3': [], '4': [], '5': [], '6': [] }
      */
    handleSelectionChange(val) {
      this.catalog.forEach((item) => {
        if (item.status === this.tabsAct) {
          this.checkedListObj[this.tabsAct] = val
        }
      })
    },
    // 选择5个后禁用表格中的复选框
     canSelect(row, index) {
      if (this.checkedListObj[this.tabsAct].length >= 5) {
        return this.checkedListObj[this.tabsAct].includes(row)
      } else {
        return true
      }
    }

14. element 表格拖动排序

import Sortable from "sortablejs";
const el = this.$refs.dragtable.$el.querySelector("tbody");
Sortable.create(el, {
  handle: "#allowDrag",
  onUpdate: (evt) => {
    // 监听拖动结束事件
    // // console.log(this) // this是当前vue上下文
    // console.log(evt.oldIndex) // 当前行的被拖拽前的顺序
    // console.log(evt.newIndex) // 当前行的被拖拽后的顺序

    // 这里就可以写我们需要传给后台的逻辑代码
    if (evt.oldIndex > evt.newIndex) {
      this.propsList.splice(evt.newIndex, 0, this.propsList[evt.oldIndex]);
      this.propsList.splice(evt.oldIndex + 1, 1);
    } else {
      // 如果当前元素在拖动目标位置的上方(即把该元素向下拖),
      // 同样,给数组newIndex+1的地方新增一个和当前元素值一样的元素,
      // 这时,将之前的那个拖动的元素删除掉,索引不变,还是oldIndex
      this.propsList.splice(evt.newIndex + 1, 0, this.propsList[evt.oldIndex]);
      this.propsList.splice(evt.oldIndex, 1);
    }
    this.$emit("drag", this.propsList);
  },
});

15. element 的 button 被禁用时 Tooltip 无效

在 button 外面多套一层 span 就行了

<el-tooltip
  class="item"
  effect="dark"
  :content="button被禁用也能正常使用"
  placement="top-start"
>
  <span>
    <el-button
      plain
      type="success"
      :disabled="true"
      @click="onBtnClick(item.id)"
      >{{ item.name }}</el-button
    >
  </span>
</el-tooltip>

16. element 表单提交验证失败页面滚动到第一个失败位置

this.$nextTick(() => {
  const isError = document.getElementsByClassName("is-error");
  isError[0].scrollIntoView({
    behavior: "smooth",
    block: "center",
  });
});

// 完整 demo

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- 引入开发vue -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <!-- 引入样式 -->
    <link
      rel="stylesheet"
      href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
    />
    <!-- 引入组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  </head>
  <style>
    .container {
      width: 500px;
    }
  </style>
  <body>
    <div id="app">
      <template>
        <div ref="container" class="container">
          <el-form
            :model="ruleForm"
            :rules="rules"
            ref="ruleForm"
            label-width="100px"
            class="demo-ruleForm"
          >
            <el-form-item label="活动名称" prop="name">
              <el-input v-model="ruleForm.name"></el-input>
            </el-form-item>
            <el-form-item label="活动形式" prop="desc">
              <el-input type="textarea" v-model="ruleForm.desc"></el-input>
            </el-form-item>
            <el-form-item label="活动区域" prop="region">
              <el-select v-model="ruleForm.region" placeholder="请选择活动区域">
                <el-option label="区域一" value="shanghai"></el-option>
                <el-option label="区域二" value="beijing"></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="活动时间" required>
              <el-col :span="11">
                <el-form-item prop="date1">
                  <el-date-picker
                    type="date"
                    placeholder="选择日期"
                    v-model="ruleForm.date1"
                    style="width: 100%"
                  ></el-date-picker>
                </el-form-item>
              </el-col>
              <el-col class="line" :span="2">-</el-col>
              <el-col :span="11">
                <el-form-item prop="date2">
                  <el-time-picker
                    placeholder="选择时间"
                    v-model="ruleForm.date2"
                    style="width: 100%"
                  ></el-time-picker>
                </el-form-item>
              </el-col>
            </el-form-item>
            <el-form-item label="即时配送" prop="delivery">
              <el-switch v-model="ruleForm.delivery"></el-switch>
            </el-form-item>
            <el-form-item label="活动性质" prop="type">
              <el-checkbox-group v-model="ruleForm.type">
                <el-checkbox
                  label="美食/餐厅线上活动"
                  name="type"
                ></el-checkbox>
                <el-checkbox label="地推活动" name="type"></el-checkbox>
                <el-checkbox label="线下主题活动" name="type"></el-checkbox>
                <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
              </el-checkbox-group>
            </el-form-item>
            <div class="" style="height: 1000px;"></div>
            <el-form-item label="活动性质" prop="type">
              <el-checkbox-group v-model="ruleForm.type">
                <el-checkbox
                  label="美食/餐厅线上活动"
                  name="type"
                ></el-checkbox>
                <el-checkbox label="地推活动" name="type"></el-checkbox>
                <el-checkbox label="线下主题活动" name="type"></el-checkbox>
                <el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
              </el-checkbox-group>
            </el-form-item>

            <el-form-item label="特殊资源" prop="resource">
              <el-radio-group v-model="ruleForm.resource">
                <el-radio label="线上品牌商赞助"></el-radio>
                <el-radio label="线下场地免费"></el-radio>
              </el-radio-group>
            </el-form-item>
            <el-form-item label="活动形式" prop="desc">
              <el-input type="textarea" v-model="ruleForm.desc"></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>
        </div>
      </template>
    </div>
  </body>

  <script>
    var app = new Vue({
      el: "#app",
      data: {
        message: "Hello Vue!",
        ruleForm: {
          name: "",
          region: "",
          date1: "",
          date2: "",
          delivery: false,
          type: [],
          resource: "",
          desc: "",
        },
        rules: {
          name: [
            { required: true, message: "请输入活动名称", trigger: "blur" },
            {
              min: 3,
              max: 5,
              message: "长度在 3 到 5 个字符",
              trigger: "blur",
            },
          ],
          region: [
            { required: true, message: "请选择活动区域", trigger: "change" },
          ],
          date1: [
            {
              type: "date",
              required: true,
              message: "请选择日期",
              trigger: "change",
            },
          ],
          date2: [
            {
              type: "date",
              required: true,
              message: "请选择时间",
              trigger: "change",
            },
          ],
          type: [
            {
              type: "array",
              required: true,
              message: "请至少选择一个活动性质",
              trigger: "change",
            },
          ],
          resource: [
            { required: true, message: "请选择活动资源", trigger: "change" },
          ],
          desc: [
            { required: true, message: "请填写活动形式", trigger: "blur" },
          ],
        },
      },
      methods: {
        submitForm(formName) {
          this.$refs[formName].validate((valid) => {
            if (valid) {
              alert("submit!");
            } else {
              // 找到验证不通过的第一个元素平滑滚动
              this.$nextTick(() => {
                const isError = document.getElementsByClassName("is-error");
                isError[0].scrollIntoView({
                  behavior: "smooth",
                  block: "center",
                });
              });
              console.log("error submit!!");
              return false;
            }
          });
        },
        resetForm(formName) {
          this.$refs[formName].resetFields();
        },
      },
    });
  </script>
</html>

17. element 的table表格控件表头与内容列不对齐问题

APP.vue

/* 解决element-ui的table表格控件表头与内容列不对齐问题 */
body .el-table th.gutter {
  display: table-cell !important;
}
// element表格出现滚动条时导致表头和数据不对齐
.el-table {
  th.gutter,
  colgroup.gutter {
    display: block !important;
    width: 10px !important;
  }
}