ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

vue3+element-plus如何做一个动态增删同时具有校验功能的table表格

2022-06-20 09:01:14  阅读:156  来源: 互联网

标签:return value element plus key vue3 const ref tableData


项目中有个比较麻烦的需求:

一个表格:

  • 能手动向表格第一行增加一行

  • 每一行的每一列在点击某一格时要能输入

  • 每个表单控件需要有校验功能

  • 支持多选

  • 可以删除所选行

  • 保存后需要前端做一个仅前端部分的查询

原型如下:

image.png

上面这些要求看起来就很麻烦。但是更麻烦的是,这个原型还是修改过的,原来的原型已经实现功能了,刚写完,第二天领导又把原型改掉了。

1.先说大致实现思路:

(1) 如何实现每个每个input输入框都带有校验功能?

答:每个输入框给一个<el-form>,就是每个表单都只有一个表单,每个表单的只有一个<el-form-item>,有着对应的校验规则。在点击保存时获取到当前所有的refs,通过调用form表单的validate()方法,实现每个表单都有校验功能。

(2) 如何向当前表格第一行加一个可编辑的行?

答:先定义一个字段模板,每次新增,用unshift方法添加到当前表格数据列表的第一行。

(3)如何实现点击某一格可以编辑?

答:每一个<td>格子里都有两个元素:一个是表单控件,一个是用来展示的文本。同时给每个表单绑定的字段在每一行的数据中加一个对应的'${key}_isEditing'字段,类型为Boolean,点击时修改这个字段为true或false,同时使用v-if控制当前td的表单控件的显示与隐藏。

主要比较难的就是上面三个功能的实现,其他的增删改都是围绕着<el-table>的当前选中行进行操作的。

具体组件源码如下:

<template>
  <div class="attr-table">
    <el-row :gutter="20">
      <el-col :span="10">
        <el-date-picker style="width: 100%" @change="timeChange" v-model="timeRange" type="datetimerange" range-separator="-" start-placeholder="开始时间" end-placeholder="结束时间" />
      </el-col>
      <el-col :span="6">
        <el-input @clear="searchClearFn" clearable v-model="dataForm.attr" placeholder="属性编码" />
      </el-col>
      <el-col :span="4">
        <el-button type="primary" @click="getData">查询</el-button>
      </el-col>
    </el-row>
    <br />
    <el-row :gutter="20">
      <el-col :span="24" style="text-align: right">
        <el-button @click="addAttr()" type="primary">新增</el-button>
        <el-button @click="save()" type="warning">保存</el-button>
        <el-button @click="deleteRow" type="danger">删除</el-button>
      </el-col>
    </el-row>
    <br />
    <div class="main-table">
      <el-table ref="attrTableRef" @selection-change="handleSelectionChange" v-loading="loading" :data="tableData" style="width: 100%">
        <el-table-column type="selection" width="55" />
        <el-table-column prop="ts" label="时间">
          <template v-slot="scope">
            <el-form :ref="`formRef${scope.$index}ts`" v-if="scope.row.ts_editing" :model="scope.row">
              <el-form-item prop="ts" :rules="{ required: true, message: '请输入时间', trigger: 'blur' }">
                <el-date-picker format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" v-model="scope.row.ts" type="datetime" placeholder="时间"> </el-date-picker>
              </el-form-item>
            </el-form>
            <span v-else @click="itemClick(scope)" class="fill-p">{{ scope.row.ts }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="key" label="属性编码">
          <template v-slot="scope">
            <el-form :ref="`formRef${scope.$index}key`" v-if="scope.row.key_editing" :model="scope.row">
              <el-form-item prop="key" :rules="{ required: true, message: '请输入属性编码', trigger: 'blur' }">
                <el-input type="text" size="small" v-model="scope.row.key" placeholder="属性编码" />
              </el-form-item>
            </el-form>
            <span @click="itemClick(scope)" class="fill-p" v-else>{{ scope.row.key }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="value" label="属性值">
          <template v-slot="scope">
            <el-form :ref="`formRef${scope.$index}value`" v-if="scope.row.value_editing" :model="scope.row">
              <el-form-item prop="value" :rules="{ required: true, message: '请输入属性值', trigger: 'blur' }">
                <el-input type="text" size="small" v-model="scope.row.value" placeholder="属性值" />
              </el-form-item>
            </el-form>
            <span class="fill-p" @click="itemClick(scope)" v-else>{{ scope.row.value }}</span>
          </template>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>

<script setup>
import { ref, getCurrentInstance, defineProps, onMounted, defineEmits, defineExpose, watch } from "vue";
import { ElMessage } from "element-plus";
import { toRefs } from "@vueuse/core";
import baseService from "@/service/baseService";
import { formatDate } from "../../tool.js";

const props = defineProps(["loading", "hasSearch", "defaultData", "thingCode"]);
const emits = defineEmits(["save"]);
const { loading, hasSearch, defaultData, thingCode } = toRefs(props);
const tableData = ref([]);
const initData = ref([]);
const selectedRow = ref([]);
const attrTableRef = ref();
// 查询之前的tableData
const copyTableData = ref([]);
// 用于判断修改之后是否保存
const changeHasSave = ref(true);
const dataForm = ref({
  beginTime: "",
  endTime: "",
  attr: ""
});
const timeRange = ref([]);

watch(defaultData, (v) => {
  tableData.value = v;
});

onMounted(() => {
  const data = [
    // {
    //   code: "1",
    //   startTime: "2022-02-22 06:23:56",
    //   latestTime: "",
    //   frequency: "2"
    // }
  ];
  initData.value = data;

  data.forEach((d) => {
    for (const k in d) {
      d[`${k}_editing`] = false;
    }
  });

  tableData.value = data;
});

// 获取属性编码
function getAttr() {
  if (!changeHasSave.value) {
    ElMessage({
      type: "warning",
      message: "请先保存数据"
    });
    return;
  }
  baseService.get("/cache/cachethingattr/getCacheThingAttrList", { thingCode: thingCode.value, thingAttr: dataForm.value.attr }).then((res) => {
    const data = res?.data || data;
    tableData.value = data.map((e) => {
      return {
        id: e.id,
        ts: e.thingAttr,
        key: e.firstTs,
        value: e.lastTs
      };
    });
  });
}

const context = getCurrentInstance();

const tableKey = ref(0);

const firstLineForm = {
  isUpdate: true,
  isNewLine: true,
  ts: "",
  ts_editing: true,
  key: "",
  key_editing: true,
  value: "",
  value_editing: true
};

/* 用来修改当前表格的某一行的某个数据 */
function upDateTable(index, key, value, newLine) {
  const newTableData = JSON.parse(JSON.stringify(tableData.value));

  if (newLine) {
    newTableData.unshift(newLine);
  } else {
    newTableData[index][key] = value;
  }
  tableData.value = newTableData.map((e, index) => {
    // 给每行加上一个index
    return {
      ...e,
      index
    };
  });
}

function getData() {
  // 查询之前先校验
  if (!tableValidate() || !thingCode.value) return;

  if (!changeHasSave.value) {
    // 没有点击保存,或者没有新增数据
    ElMessage({
      type: "warning",
      message: "当前有没有保存的数据,请先保存"
    });
    return;
  }
  baseService.get("/thing/thing/getSequentialInformationByCondition", { ...dataForm.value, thingCode: thingCode.value }).then((res) => {
    const data = res?.data || [];
    console.log("查询的数据:", data);
    tableData.value = data.map((e) => {
      return {
        ts: formatDate(+e.ts),
        key: e.key || "",
        value: e.value || ""
      };
    });
  });
}

function searchClearFn() {
  if (thingCode.value) {
    getAttr();
    return;
  }
  tableData.value = copyTableData.value;
}

function timeChange(v) {
  dataForm.value.beginTime = formatDate(v[0]);
  dataForm.value.endTime = formatDate(v[1]);
}

function addAttr() {
  upDateTable("", "", "", { ...firstLineForm });
  changeHasSave.value = false;
}

function save(row) {
  /* 物管理的这个保存和缓存设置页面的保存是两个后端写的
   * 这里的基础信息的保存和时许属性的保存是两个分开的接口,所以这里直接用组件保存就行了
   * 缓存设置页面的基础信息的保存和属性的保存是公用一个接口
   * */
  if (!tableValidate()) return;

  if (changeHasSave.value) {
    ElMessage({
      type: "warning",
      message: "没有需要保存的数据"
    });
    return;
  }

  // 都通过了验证,取消编辑状态
  tableData.value.forEach((e) => {
    e.ts_editing = false;
    e.key_editing = false;
    e.value_editing = false;
  });

  // 保存备份用于查询
  // copyTableData.value = JSON.parse(JSON.stringify(tableData.value));

  // 提交
  const tsKvReqParamList = tableData.value.map((e) => {
    return {
      ts: new Date(e.ts).getTime(),
      values: {
        [e.key]: e.value
      }
    };
  });
  baseService.post("/thing/thing/saveSequentialInformationToTb", { thingCode: thingCode.value, tsKvReqParamList }).then((res) => {
    if (res.code === 0) {
      ElMessage({
        type: "success",
        message: "保存成功"
      });
      getData();
    }
  });

  // 已经点击保存
  changeHasSave.value = true;
}

function itemClick(row) {
  const { $index, column } = row;
  /* 修改时已有数据的时间和属性编码不能修改 */
  if (!column.isNewLine && (column.property === "ts" || column.property === "key")) return;
  tableData.value[$index][`${column.property}_editing`] = true;
  if (changeHasSave.value) changeHasSave.value = false;
}

function deleteRow() {
  if (selectedRow.value.length <= 0) {
    ElMessage({
      type: "warning",
      message: "请选择需要删除的属性"
    });
    return;
  }

  // const hasEditing = selectedRow.value.some((e) => {
  //   return e.key_editing || e.ts_editing || e.value_editing;
  // });
  // console.log("当前选择有正在编辑", hasEditing);
  // if(hasEditing){
  //   ElMessage({
  //     type: "warning",
  //     message: "请先保存正在修改的数据"
  //   });
  //   return;
  // }

  // 先删除新增的行。数据库有的暂时不动
  const newLineList = selectedRow.value.filter((e) => {
    return e.isNewLine;
  });
  console.log("是新增:", newLineList);

  const indexs = newLineList.map((e) => e.index).sort((a, b) => b - a); // 把索引从大到小排列
  const newTableData = JSON.parse(JSON.stringify(tableData.value));
  // 从tableData中删除选择的数据
  indexs.forEach((e) => {
    newTableData.splice(e, 1);
  });
  tableData.value = newTableData;
}

function handleSelectionChange(sel) {
  selectedRow.value = sel;
}

// 表格的校验方法
function tableValidate() {
  const { refs } = context;
  let allPassValidate = true;
  // 这里是调用所有行的validate()方法
  for (let ref in refs) {
    if (ref === "attrTableRef") continue;

    refs[ref]?.validate((v) => {
      if (!v) {
        allPassValidate = false;
      }
    });
  }

  return allPassValidate;
}

// 当前组件抛出给父组件的用于清空的方法
function clear() {
  tableData.value = [];
}

// 用于判断是否保存或是否新增了属性
function hasSaved() {
  // 判断之前先校验
  if (!tableValidate()) return false;
  return copyTableData.value.length > 0;
}

defineExpose({ clear, hasSaved });
</script>

<style lang="less">
.fill-p {
  display: inline-block;
  width: 100%;
  height: 24px;
}
</style>

标签:return,value,element,plus,key,vue3,const,ref,tableData
来源: https://www.cnblogs.com/codexlx/p/16392092.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有