element plus 使用细节

1,638 阅读8分钟

菜鸟一直在纠结这个写不写,因为不难,但是菜鸟老是容易忘记,虽然想想或者搜搜就可以马上写出来,但是感觉每次那样就太麻烦了,不如一股做气写了算了,后面遇见别的就再来补充!

更多关于vue3的知识可以看我的B站笔记:Vue3 + vite + Ts + pinia + 实战 + 源码 + electron

table 表格自定义内容

<el-table-column label="操作" width="230">
  <template #default="scope">
    <el-button type="primary" size="small">详情</el-button>
    <el-popconfirm title="是否删除" @confirm="deleteEvent(scope.row)">
      <template #reference>
        <el-button type="danger" size="small">删除</el-button>
      </template>
    </el-popconfirm>
    <el-button type="primary" size="small" :disabled="scope.row.status == 0">分享</el-button>
  </template>
</el-table-column>

注意

使用了el-popconfirm,权限验证的时候,需要给el-popconfirm和里面的el-button都加权限验证,不然会有问题!

select 显示的是value

之所以显示为 value 就是因为,你 v-model 所给的值,和 el-option 的 value 不一致,最常见的就是 0 和 ‘0’ 了,一定要加以注意!

分页和搜索

这里有个问题,就是element plus官网建议不要使用这些事件了:

在这里插入图片描述

而是推荐使用v-model,菜鸟试了一下确实简单,但是有个问题,就是你监听怎么个监听法?

如果你只把 当前页数每页条数 放入 watchEffect(),搜索单独写一个函数,搜索后切换分页,那你将喜提一个bug;但是如果把全部的搜索条件都放 watchEffect() 里面去了,那你就会发现输入一个字选一个东西就请求一次,是真的不行,所以退而求其次,只能使用 watch,大致代码如下:

// 实时响应分页
watch(
  [currentPage, pageSize],
  (newval, oldval) => {
    console.log(newval, oldval);
    getFormListFun(false);
  },
  {
    immediate: true,
  }
);

// 请求列表
function getFormListFun(bool = false) {
  let pramas = null;
  // bool 用于搜索,让页数从1开始
  if (bool) {
    currentPage.value = 1;
  }
  
  // 判断请求内容
  if (searchVal.value !== "" || formtype.value !== "" || formstatus.value !== "") {
    pramas = {
      pageNo: currentPage.value,
      pageSize: pageSize.value,
      condition: searchVal.value,
      formType: formtype.value,
      formstatus: formstatus.value,
    };
  } else {
    pramas = {
      pageNo: currentPage.value,
      pageSize: pageSize.value,
    };
  }

  // table数据置空
  tableData.value = [];
  getFormList({ ...pramas })
    .then((res) => {
      if (res.code == 200) {
        total = res.data.total;
        tableData.value = tableData.value.concat(res.data.records);
      } else {
        ElMessage({
          message: res.message,
          type: "error",
        });
      }
    })
    .catch((err) => {
      console.log(err);
    });
}

2024/7/19补充

最近菜鸟做项目,感觉上面这个搜索还要去 判断请求内容,不是很方便,所以直接提成组件形式,不提不知道,一提才发现这个分页就变得简单多了,这里奉上代码!

search组件

<script setup>
import { Search, Plus, Delete } from '@element-plus/icons-vue'

const emit = defineEmits(['add', 'search', 'batchdelete'])

// 时间
let datetime = ref('')
// 搜索
let searchVal = ref('')
const search = () => {
  console.log(datetime.value)
  emit('search', {
    value: searchVal.value,
    startTime: datetime.value ? datetime.value[0] : '',
    endTime: datetime.value ? datetime.value[1] : ''
  })
}

// 新增
const add = () => {
  emit('add', true)
}

// 批量删除
const batchdelete = () => {
  emit('batchdelete')
}
</script>

<template>
  <div class="searchBox">
    <el-input v-model="searchVal" placeholder="样本编号/患者姓名/联系电话" clearable></el-input>
    <div class="block">
      <el-date-picker
        v-model="datetime"
        type="datetimerange"
        start-placeholder="开始时间"
        end-placeholder="结束时间"
      />
    </div>
    <el-button type="primary" :icon="Search" @click="search">搜索</el-button>
    <slot name="add">
      <el-button type="primary" :icon="Plus" @click="add">新增</el-button>
    </slot>
    <slot name="batchdelete">
      <el-popconfirm title="是否删除" @confirm="batchdelete">
        <template #reference>
          <el-button type="primary" :icon="Delete">批量删除</el-button>
        </template>
      </el-popconfirm>
    </slot>
  </div>
  <div class="line"></div>
</template>

<style lang="scss" scoped>
@import url('@/assets/css/search.scss');
</style>

父组件

// 分页
let currentPage = ref(1)
let pageSize = ref(10)
let total = ref(0)
let tableData = ref([])

// 搜索
let searchdata = {
  status: statusArr[0]
}
const searchList = (data, bool = true) => {
  getListApi({
    currentPage: bool ? 1 : currentPage.value,
    size: pageSize.value,
    ...searchdata,
    ...data
  })
    .then((res) => {
      console.log(res)
      if (res.code == 200) {
        tableData.value = res.data.records
        total.value = res.data.total
        searchdata = { ...searchdata, ...data }
        currentPage.value = bool ? 1 : currentPage.value
      } else {
        ElMessage({
          message: res.message,
          type: 'error'
        })
      }
    })
    .catch((err) => {
      console.log(err)
    })
}

// 监听事件
watch(
  [currentPage, pageSize],
  () => {
    searchList(searchdata, false)
  },
  {
    immediate: true
  }
)

upload 使用

一个文件

el-upload 的 html 部分:

<el-upload
  class="upload-demo"
  action="#"
  ref="upload"
  :auto-upload="false"
  :limit="1"
  :on-change="checkFile"
  :on-remove="handleRemove"
  :on-exceed="handleExceed"
>
  <el-button type="primary">上传文件</el-button>
  <template #tip>
    <div class="el-upload__tip">必须是xls/xlsx类型文件</div>
  </template>
</el-upload>

upload 逻辑:

// 上传文件
let upload = ref();
let fd = new FormData();
function checkFile(file) {
  if (
    file.name.indexOf(".xls") > 0 ||
    file.name.indexOf(".xlsx") > 0 ||
    file.name.indexOf(".xlsm") > 0
  ) {
    let fileSize = parseInt(file.size / 1024 / 1024);
    if (fileSize > 5) {
      ElMessage({
        message: "文件大小不能超过5MB,请重新上传。",
        type: "error",
      });
      upload.value.clearFiles();
      return;
    }
    fd.append("file", file.raw); //传文件
  } else {
    upload.value.clearFiles();
    ElMessage({
      message: "请上传xlsx文件!",
      type: "error",
    });
  }
}
const handleRemove = (file, uploadFiles) => {
  console.log(file, uploadFiles);
  upload.value.clearFiles();
};
const handleExceed = (files, uploadFiles) => {
  console.log(files, uploadFiles);
  ElMessage.warning(`只能上传一个文件!`);
};

// 提交事件 -》 这部分按逻辑自行更改
const onSubmit = () => {
  if (useAuth("BLAST")) {
    fd.append("bt", form.value.bt);
    fd.append("db", form.value.db);
    fd.append("textarea", form.value.textarea);
    fd.append("num", form.value.num);
    fd.append("evalue", form.value.evalue);
    blastApi(fd).then((res) => {
      if (res.code == 200) {
        console.log(res.data);
        // 见下面el-dialog使用,就是关闭弹窗的方法
        handleClose()
      } else {
        ElMessage.warning(`Blast失败!`);
        fd = new FormData();
        upload.value.clearFiles();
      }
    });
  } else {
    ElMessageBox.alert("您没有此权限", "Notice", {
      confirmButtonText: "Confirm",
      callback: () => {
        return false;
      },
    });
  }
};

多文件 (适用于图片,带预览)

el-upload 的 html 部分:

<el-upload
  class="upload-demo"
  action="#"
  ref="upload"
  list-type="picture-card"
  :auto-upload="false"
  v-model:file-list="fileList"
  :limit="5"
  multiple
  :on-change="checkFile"
  :on-preview="handlePictureCardPreview"
  :on-remove="handleRemove"
  :on-exceed="handleExceed"
>
  <el-icon><i-ep-plus /></el-icon>
</el-upload>
<el-dialog v-model="dialogVisible2">
  <el-image style="width: 100%" :src="dialogImageUrl" alt="预览图片" fit="contain" />
</el-dialog>

upload 逻辑:

// 上传图片
const dialogImageUrl = ref('')
const dialogVisible2 = ref(false)
let upload = ref()
let fileList = ref([])
let fd = new FormData()
function checkFile(file) {
  if (
    file.name.indexOf('.png') > 0 ||
    file.name.indexOf('.jpg') > 0 ||
    file.name.indexOf('.jpeg') > 0
  ) {
    console.log(file)
  } else {
    upload.value.handleRemove(file)
    ElMessage({
      message: '请上传png、jpg、jpeg文件!',
      type: 'error'
    })
  }
}
// 超过限制触发
const handleExceed = () => {
  ElMessage({
    message: '最多上传5张照片!',
    type: 'error'
  })
}
const handleRemove = (uploadFile, uploadFiles) => {
  // uploadFile --> 删除的文件  uploadFiles --> 剩余的文件
  console.log(uploadFile, uploadFiles)
  fileList.value = uploadFiles
}
const handlePictureCardPreview = (file) => {
  dialogImageUrl.value = file.url
  dialogVisible2.value = true
}

// 提交
let sampleForm = ref()
const onSubmit = async (formEl) => {
  if (!formEl) return
  await formEl.validate((valid) => {
    if (valid) {
      if (fileList.value.length === 0) {
        ElMessage({
          message: '未上传寄样样本拍照',
          type: 'error'
        })
        return
      }

      // 循环遍历form,将其属性值赋给fd
      for (let key in form) {
        console.log(key, form[key])
        if ((key === 'samplingDate' || key === 'subTime' || key === 'subDate') && form[key]) {
          fd.append(key, form[key].toISOString())
        } else {
          fd.append(key, form[key])
        }
      }
      // 上传图片
      for (let i = 0; i < fileList.value.length; i++) {
        fd.append('files', fileList.value[i].raw)
      }

      addSampleApi(fd)
        .then((res) => {
          console.log(res)
          if (res.code == 200) {
            ElMessage({
              message: '添加成功,等待审核!',
              type: 'success'
            })
            handleClose()
          } else {
            ElMessage({
              message: res.message,
              type: 'error'
            })
            fd = new FormData()
            fileList.value = []
          }
        })
        .catch((err) => {
          console.log(err)
        })
    } else {
      ElMessage({
        message: '请检查信息是否填写正确!',
        type: 'error'
      })
    }
  })
}

el-dialog 使用

菜鸟发现官网里面都是直接在一个文件里面使用,但是一般 el-dialog 都是在组件里面封装的,所以菜鸟就自己试了一下,结果问题就来了,这里记录一下!

vue2 使用 elementui dialog 的逻辑可以看看我的这篇博客:elementui 的 dialog 常用逻辑总结

vue3 的坑主要在于:vue3单向数据流,但是这个 el-dialog 又要v-model绑定,所以就会报错,但是好在发现官网还有一种绑定方式,那就是 modelvalue !!!

这里菜鸟就写个简单的例子:

父组件:

<script setup>
import Detail from "./components/detail.vue";

// 详情弹窗
let editshow = ref(false);
let detailencodedCode = ref("");
// 打开
const showDetail = (data) => {
  detailencodedCode.value = data.encodedCode;
  editshow.value = true;
};
// 关闭
function hideEdit() {
  editshow.value = false;
}
</script>

<el-table-column label="操作" width="230">
  <template #default="scope">
    <el-button type="primary" size="small" @click="showDetail(scope.row)">详情</el-button>
  </template>
</el-table-column>

<!-- 详情弹窗 -->
<Detail v-if="editshow" :pwd="detailencodedCode" :dialogVisible="editshow" @closeEvent="hideEdit"></Detail>

dialog 组件:

<script setup>
import { ref } from "vue";
import { getShareDetail } from "../../../network/api.js";

const props = defineProps({
  dialogVisible: {
    type: Boolean,
    default: false,
  },
  pwd: {
    type: String,
    default: "",
  },
});

const emit = defineEmits(["closeEvent"]);

// 关闭弹窗
function handleClose() {
  emit("closeEvent", false);
}
const dialogBox = ref()
function closeDialog() {
  dialogBox.value.resetFields();
}
</script>

<template>
  <div>
    <el-dialog
      title="详情"
      ref="dialogBox"
      :modelValue="dialogVisible"
      :before-close="handleClose"
      @close="closeDialog"
      :close-on-click-modal="false"
      :destroy-on-close="true"
    >
      <p>暂无数据!</p>
      <template #footer>
        <div>
          <el-button @click="handleClose">关闭</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

一定要用一个参数接收 defineProps

虽然在 template 里面可以直接使用 defineProps 里面的变量,比如:dialogVisible, 但是 js 里面是不能直接访问的,会提示没有定义,只能通过:props.pwd 访问,当然接收的变量名自己定义就行,不一定要是props !!!

当然,如果你 js 中没有用到 defineProps 里面的变量,也是可以不用设置的!

不要再 el-dialog 上加class

如果你给 el-dialog 上加上了class,那么你将收获一个警告:

Extraneous non-props attributes (class) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.

el-drawer 也是一样

父元素

<script setup>
import Detail from "./components/detail.vue";

// 详情
let detailshow = ref(false)
// 打开
const showDetail = (data) => {
  detailshow.value = true
}
// 关闭
function hideDetail() {
  detailshow.value = false
}
</script>

<el-table-column label="操作" width="230">
  <template #default="scope">
    <el-button type="primary" size="small" @click="showDetail(scope.row)">详情</el-button>
  </template>
</el-table-column>

<!-- 详情抽屉 -->
<Detail v-if="detailshow" :drawer-show="detailshow" @closeEvent="hideDetail"></Detail>

子元素

<script setup>
const props = defineProps({
  drawerShow: {
    type: Boolean,
    default: false
  }
})

const emit = defineEmits(['closeEvent'])

// 关闭弹窗
function handleClose() {
  emit('closeEvent', false)
}
const dialogBox = ref()
function closeDialog() {
  dialogBox.value.resetFields()
}
</script>

<template>
  <div>
    <el-drawer
      :modelValue="drawerShow"
      direction="rtl"
      size="80%"
      :before-close="handleClose"
      @close="closeDialog"
      :close-on-click-modal="false"
      :destroy-on-close="true"
    >
      <template #header>
        <p style="margin: 0">详情</p>
      </template>
      <template #default>
        <p>详情</p>
      </template>
      <template #footer>
        <div>
          <el-button @click="handleClose">关闭</el-button>
        </div>
      </template>
    </el-drawer>
  </div>
</template>

ElLoading(继续引入问题)

在使用 按需引入后,一定不能再在代码里又引入 ElLoading 或者 ElMessage,eg:

import { ElLoading } from 'element-plus'

否则不生效!!!

如果使用了 eslint 会报错,和 ElMessage 一样,加上忽略下一行就行

// eslint-disable-next-line
const loading = ElLoading.service({
  lock: true,
  text: "Loading",
  background: "rgba(0, 0, 0, 0.7)",
});

更优雅的解决办法见我的另一篇掘金文章: element plus使用问题

已经修复了?

后续的问题,菜鸟没遇见了,估计是被修复了还是怎么样,所以可以止步了!当然如果你真的遇见了,也可以继续看,并留言是什么情况才会遇见!

element plus 和 px2rem 不兼容

如果你在 element plus 项目中引入了 px2rem,那么你可能喜提巨大的图标等!

解决巨大的图标

解决办法就是在既有 px2rem 又有用到 ElMessage 的界面上加上如下代码:

.el-message-icon--error {
  font-size: 5px;
}
.el-message-icon--success {
  font-size: 5px;
}
.el-message-icon--info {
  font-size: 5px;
}

记住不要在 style 上加 scoped !!!

element plus错位的图标

这里是 element plus 自己的bug!

el-message

el-message 的关闭按钮可能会偏移,解决办法就是在 public 底下的 index.html 中加入:

/* 解决 element plus 样式问题 */
.el-icon.el-message__closeBtn {
  position: absolute !important;
}

主要产生原因就是 .el-icon 的样式和 .el-message-closeBtn 样式冲突了,如图:

在这里插入图片描述

el-input / el-select

el-input / el-select 里面的图标可能会偏移,解决办法就是在使用的地方加上 css

.searchBox {
  :deep(.el-input__prefix-inner) {
    align-items: center;
  }
  :deep(.el-input__suffix-inner) {
    align-items: center;
  }
}

这里菜鸟发现 el-date-picker 等 也有类似问题,而且并不是覆盖的原因,也不知道为什么 github 上没人提出来!!!

现在好像没有这个问题了,最近菜鸟的项目没有出现,也不知道是不是px2rem搞的,反正记录一下!!!