vue 实现动态渲染el-table表格

167 阅读4分钟

需求:表格的数据结构并不是固定不变的,表头,表体和tip都是根据配置,去请求接口动态生成,废话不多说,直接上代码。

效果图:

image.png

实现逻辑梳理:

表头数据获取需要单独调一个接口,这个接口的数据包含表头名称和对应的值,表体部分,则需要调取另外一个接口,这个接口数据包含表头名称,tips提示,类型,长度等,但不包含表头对应的值,所以需要将两个数组进行对比,然后进行动态赋值:

表头数据结构:

a20b4040d2f260796b8a4c137ecee11.png

表体数据结构:

image.png

DOM结构:

<!-- 数据表格开始 -->
  <div class="tableData" v-if="attrListArr.length > 0">
    <!-- 表格列表,新增 type="selection" 列 -->
    <el-table
      v-if="showTable"
      class="customer-table-style"
      v-model="multipleSelection"
      :data="tableData"
      v-loading="loading"
      height="6.4rem"
      style="width: 100%"
      border
      @selection-change="handleSelectionChange"
    >
      <el-table-column fixed type="selection" width="55"></el-table-column>
      <el-table-column fixed prop="ts" label="ts" width="230rem" showOverflowTooltip></el-table-column>
      <el-table-column
        v-for="column in columns" :key="column.prop" :prop="column.prop" :label="column.label" :min-width="column.width">
        <template slot="header" slot-scope="scope">
          <el-tooltip v-if="column.description" :content="column.description" placement="top">
            <span>{{ column.prop }}</span>
          </el-tooltip>
          <span v-else>{{ column.prop }}</span>
        </template>
      </el-table-column>
      <el-table-column fixed="right" v-if="tableData.length > 0" label="操作" width="110">
        <template slot-scope="scope">
          <icon-button title="修改" type="xiugai" @click="getOperateDialog(scope.row, 'edit')"></icon-button>
          <icon-button class="last-icon-btn" title="删除" type="shanchu" @click="deleteAll('table-select', scope.row)"></icon-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination v-if="total>0" :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total"
                @pagination="getList"/>
  </div>

js数据结构:

//查询列表
async getList() {
  this.loading = true;
  // 重新渲染表格DOM树,防止表格样式错乱
  this.showTable = false
  this.listQuery.id = this.detailData.id
  let newArr = []
  // 获取表头数据结构
  const {data:res} = await getMetaDataAttrByMetaDataId(this.detailData.metadataId)
  this.attrListArr = res.data
  if (this.attrListArr.length == 0) {
    // 如果表头没有数据,则表格也没存在意义,直接置为空
    this.tableData = []
    return
  }
  // 获取数据集新增过的表体属性列表
  const {data} = await getMetaDataAttrList(this.listQuery)
  if (data.data && data.code == '0') {
    // 构造新数组:数据集列表顺序和元数据属性列表保持一致
    newArr = data.data.records.map(attr => {
      // 单独构造ts属性数据,并进行数据回显
      const newObj = { ts: attr.ts || '' };
      this.attrListArr.forEach(item => {
        // 过滤表格数据,将属性值为undefined,转换为--
        newObj[item.name] = attr[item.name] !== undefined ? attr[item.name] : '--';
      });
      return newObj;
    });

    // 处理表体数据,将boolean值,转换为字符串
    this.tableData = newArr.map(record => {
      return Object.keys(record).reduce((acc, key) => {
        acc[key] = typeof record[key] === 'boolean'  ? record[key].toString() : record[key];
        return acc;
      }, {});
    });
    
    // 重新构造表体数据,进行组织各种所需要数据
    this.columns = [
      ...this.attrListArr.map(column => ({
        prop: column.name,
        label: column.label,
        width: '230rem',
        align: column.align,
        sortable: column.sortable,
        description: column.description, // 确保 description 被正确赋值
        showOverflowTooltip: column.showOverflowTooltip
      }))
    ];
    this.total = data.data.total
  } else {
    this.tableData = []
    this.total = 0
    this.columns = [] // 清空表头
  }
  this.loading = false;
  this.showTable = true
},

完整代码:index.vue

<template>
  <div class="content-list api-store-box">
    <div class="detail-title" style="width: calc(100% - 2rem);">
      <div>
        <span>数据集 - </span>
        <span>{{ detailData.name }}</span>
      </div>
      <div><img @click="goBack" src="@/assets/images/back.png"></div>
    </div>
    <div style="height:.36rem"></div>
    <div class="card">
      <!-- 表单开始 -->
      <div class="form">
        <el-form ref="ruleForm" :inline="true" label-width="0.7rem" size="small" @submit.native.prevent>
          <el-form-item label="属性时间">
            <el-date-picker v-model.trim="dateTime" @change="pickerChange" value-format="yyyy-MM-dd HH:mm:ss" type="datetimerange" size="small" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间">
            </el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button class="btn-onelevel" type="primary" size="small" @click="search" style="margin-right:10px">
              搜索
            </el-button>
            <el-button type="warning" class="btn-clear" size="small" @click="clear" style="margin-right:10px">
              清除
            </el-button>
            <el-button class="btn-onelevel" type="primary" @click="getOperateDialog('', 'create')">新增</el-button>
            <el-button class="btn-onelevel" type="primary" size="small" @click="getExportExcel" style="margin-right:10px">
              从表格文件导入
            </el-button>
            <el-button class="btn-onelevel" type="primary" size="small" @click="getExportService" style="margin-right:10px">
              从数据服务导入
            </el-button>
            <el-button type="warning" class="btn-clear" size="small" @click="getCreateVersion" style="margin-right:10px">
              创建数据集版本
            </el-button>
            <!-- 删除数据按钮替换为下拉菜单 -->
            <el-dropdown @command="handleDeleteCommand">
              <el-button type="primary" class="btn-clear" size="small">
                删除数据<i class="el-icon-arrow-down el-icon--right"></i>
              </el-button>
              <el-dropdown-menu slot="dropdown">
                <el-dropdown-item command="selected">删除选中数据</el-dropdown-item>
                <el-dropdown-item command="all">删除查询结果</el-dropdown-item>
              </el-dropdown-menu>
            </el-dropdown>
          </el-form-item>
        </el-form>
      </div>
      <!-- 表单结束 -->

      <!-- 数据表格开始 -->
      <div class="tableData" v-if="attrListArr.length > 0">
        <!-- 表格列表,新增 type="selection" 列 -->
        <el-table
          v-if="showTable"
          class="customer-table-style"
          v-model="multipleSelection"
          :data="tableData"
          v-loading="loading"
          height="6.4rem"
          style="width: 100%"
          border
          @selection-change="handleSelectionChange"
        >
          <el-table-column fixed type="selection" width="55"></el-table-column>
          <el-table-column fixed prop="ts" label="ts" width="230rem" showOverflowTooltip></el-table-column>
          <el-table-column
            v-for="column in columns" :key="column.prop" :prop="column.prop" :label="column.label" :min-width="column.width">
            <template slot="header" slot-scope="scope">
              <el-tooltip v-if="column.description" :content="column.description" placement="top">
                <span>{{ column.prop }}</span>
              </el-tooltip>
              <span v-else>{{ column.prop }}</span>
            </template>
          </el-table-column>
          <el-table-column fixed="right" v-if="tableData.length > 0" label="操作" width="110">
            <template slot-scope="scope">
              <icon-button title="修改" type="xiugai" @click="getOperateDialog(scope.row, 'edit')"></icon-button>
              <icon-button class="last-icon-btn" title="删除" type="shanchu" @click="deleteAll('table-select', scope.row)"></icon-button>
            </template>
          </el-table-column>
        </el-table>
        <pagination v-if="total>0" :limit.sync="listQuery.pageSize" :page.sync="listQuery.pageNo" :total="total"
                    @pagination="getList"/>
      </div>
      <div class="on-data">暂无数据</div>
    </div>
    <formDialog ref="formRefDialog" @getInit="getList"></formDialog>
    <VersionFormDialog ref="formVersionDialog"></VersionFormDialog>
    <exportExcelDialog ref="exportExcelRefDialog" @getInit="getList"></exportExcelDialog>
    <exportServiceDialog ref="exportRefServiceDialog" @getInit="getList"></exportServiceDialog>
  </div>
</template>

<script>
import formDialog from "./formDialog.vue";
import { dictitemList } from "@/api/myapi";
import VersionFormDialog from "./VersionFormDialog.vue";
import exportExcelDialog from "./exportExcelDialog.vue";
import exportServiceDialog from "./exportServiceDialog.vue";
import Pagination from "@/components/Pagination/index.vue";
import {sortMixin, tableScrollTop} from "@/utils/mixin";
import {deleteSimulatorListPage, simulatorListPage} from "@/api/myapi";
import { deleteDataAttribute, getMetaDataAttrByMetaDataId, getMetaDataAttrList, getDataSetDetail } from "@/api/AIModel";

export default {
  name: "APIStore",
  components: { Pagination, formDialog, exportServiceDialog, exportExcelDialog, VersionFormDialog },
  mixins: [sortMixin, tableScrollTop],
  data() {
    return {
      showTable: false,
      touchTableDesign: false, // 记录是否初始化
      columnsBak: '',
      columnsConfig: {
        auto: ['name'], // 宽度auto
        enabled: ['opts'], // 不可设宽 huashuoqueshi xiangdangnvren guangpgjiu
        isDisabled: ['name', 'opts'], // 不可更改的config: auto & enabled
      },
      loading: false,
      multipleSelection: [],
      selectedTsValues: [], // 新增数组,用于存储选中的 ts 值
      tableData: [],
      total: 0,
      listQuery: {
        pageNo: 1,
        pageSize: 15,
        startTime: '',
        endTime: ''
      },
      // 栏位
      columns: [], // 初始化为空数组,动态生成
      dateTime: [],
      ruleForm: {
        name: '',
      },
      detailData: {},
      attrListArr: []
    }
  },
  created() {},
  activated() {
    this.setBread()
    this.getDetail()
  },
  deactivated() {
    this.touchTableDesign = false
  },
  beforeDestroy() {
    this.$tableDesign.clear()
    this.$tableExport.clear()
  },
  methods: {
    setBread() {
      let arr = [{name: '数据开发', isNode: true}, {name: '数据集', isNode: true}, {name: '详情', isNode: true}]
      this.$store.commit("setBreadList", arr)
    },

    getInitTable(data) {
      this.listQuery.metadataId = data.metadataId
      this.getList()
    },

    getExportData() {
      let searchInfo = {
        pageNo: this.listQuery.pageNo,
        pageSize: this.listQuery.pageSize,
        name: this.ruleForm.name
      }

      let paginationTotalInfo = [this.listQuery.pageNo, this.total]
      let paginationLimitInfo = [this.listQuery.pageNo, this.listQuery.pageSize]
      return {searchInfo, paginationTotalInfo, paginationLimitInfo, callback: simulatorListPage, noSearchParams: true}
    },

    // 时间查询
    //时间选择
    pickerChange() {
      if (this.dateTime) {
        this.listQuery.startTime = this.dateTime[0]
        this.listQuery.endTime = this.dateTime[1]
      } else {
        this.listQuery.startTime = ''
        this.listQuery.endTime = ''
      }
    },

    //查询列表
    async getList() {
      this.loading = true;
      // 重新渲染表格DOM树,防止表格样式错乱
      this.showTable = false
      this.listQuery.id = this.detailData.id
      let newArr = []
      // 获取表头数据结构
      const {data:res} = await getMetaDataAttrByMetaDataId(this.detailData.metadataId)
      this.attrListArr = res.data
      if (this.attrListArr.length == 0) {
        // 如果表头没有数据,则表格也没存在意义,直接置为空
        this.tableData = []
        return
      }
      // 获取数据集新增过的表体属性列表
      const {data} = await getMetaDataAttrList(this.listQuery)
      if (data.data && data.code == '0') {
        // 构造新数组:数据集列表顺序和元数据属性列表保持一致
        newArr = data.data.records.map(attr => {
          // 单独构造ts属性数据,并进行数据回显
          const newObj = { ts: attr.ts || '' };
          this.attrListArr.forEach(item => {
            // 过滤表格数据,将属性值为undefined,转换为--
            newObj[item.name] = attr[item.name] !== undefined ? attr[item.name] : '--';
          });
          return newObj;
        });

        // 处理表体数据,将boolean值,转换为字符串
        this.tableData = newArr.map(record => {
          return Object.keys(record).reduce((acc, key) => {
            acc[key] = typeof record[key] === 'boolean'  ? record[key].toString() : record[key];
            return acc;
          }, {});
        });
        this.columns = [
          ...this.attrListArr.map(column => ({
            prop: column.name,
            label: column.label,
            width: '230rem',
            align: column.align,
            sortable: column.sortable,
            description: column.description, // 确保 description 被正确赋值
            showOverflowTooltip: column.showOverflowTooltip
          }))
        ];
        this.total = data.data.total
      } else {
        this.tableData = []
        this.total = 0
        this.columns = [] // 清空表头
      }
      this.loading = false;
      this.showTable = true
    },

    async getDetail() {
      const { data: res } = await getDataSetDetail(this.$route.query.attributeId)
      if (res.code == '0' && res.data) {
        this.detailData = res.data
        this.getList();
      }
    },

    async getDictionaryAttributeList(enumId) {
      const { data: res } = await dictitemList({ dictId: enumId })
      if (res.code == '0' && res.data) {
        return res.data.map(item => ({
          id: item.itemValue,
          dictName: item.itemText
        }))
      } else {
        return []
      }
    },

    // 表格勾选操作
    handleSelectionChange(rows) {
      this.multipleSelection = rows;
      this.selectedTsValues = rows.map(row => row.ts); // 提取选中的 ts 值并推入数组
    },

    search() {
      this.listQuery.pageNo = 1;
      this.getList();
    },
    clear() {
      this.dateTime = []
      this.listQuery.startTime = ''
      this.listQuery.endTime = ''
      this.listQuery.pageNo = 1
      this.getList();
    },
    //新增
    getOperateDialog(data, type) {
      this.$refs.formRefDialog.openDialog(data, type, this.listQuery.metadataId)
    },

    // 从数据服务导入
    getExportService() {
      this.$refs.exportRefServiceDialog.openDialog(this.listQuery.attributeId)
    },

    // 从表格中导入
    getExportExcel() {
      this.$refs.exportExcelRefDialog.openDialog(this.listQuery.attributeId)
    },

    // 创建数据集版本
    getCreateVersion(){
      let apiParams = {
        startTime: this.listQuery.startTime,
        endTime: this.listQuery.endTime
      }
      this.$refs.formVersionDialog.openDialog(apiParams)
    },

    // 查看数据属性
    handleDetailsClick(row) {
      const scrollTop = this.$refs.tablelist.$refs.table.bodyWrapper.scrollTop
      this.$router.push({
        path: '/index/emindex/queryservice/coolinghome/coolingdetails',
        query: {
          projectId: row.projectId,
          id: row.id,
          gateWayId:row.gateWayId,
          gateWayName:row.gateWayName,
          scrollTop: scrollTop,
          formCache: encodeURIComponent(JSON.stringify(this.ruleForm)),
          listQueryCache: encodeURIComponent(JSON.stringify(this.listQuery))
        }
      })
    },
    // 新增处理删除命令的方法
    handleDeleteCommand(command) {
      if (command === 'selected') {
        if (this.multipleSelection.length === 0) {
          this.$message({
            message: "请先勾选数据",
            type: "warning",
            offset: 120
          });
          return;
        }
        this.deleteAll('select');
      } else if (command === 'all') {
        if (this.listQuery.startTime === '' && this.listQuery.endTime === '') {
          this.$message({
            message: "请先查询数据",
            type: "warning",
            offset: 120
          });
          return;
        }
        this.deleteAll('search');
      }
    },
    // 删除查询结果的方法
    async deleteAll(type, row) {
      let apiParams = []
      let confirmMessage = ''
      if (type === 'select') {
        this.listQuery.pageNo = 1;
        apiParams = this.selectedTsValues
        confirmMessage = '此操作将删除选中的数据, 是否继续?'
      } else if (type === 'search') {
        if (this.listQuery.startTime && this.listQuery.endTime) {
          apiParams = [this.listQuery.startTime, this.listQuery.endTime]
          confirmMessage = `此操作将删除 ${this.listQuery.startTime}${this.listQuery.endTime} 区间的所有数据, 是否继续?`
        } else {
          confirmMessage = '此操作将清空当前数据集所有数据,是否继续?'
        }
      } else {
        apiParams = [row.ts]
        confirmMessage = '确认删除所选内容吗?'
      }
      this.$confirm(confirmMessage, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        const {data} = await deleteDataAttribute(this.$route.query.attributeId, apiParams)
        if (data.code == '0') {
          this.$message({
            message: "删除成功",
            type: "success",
            offset: 120
          })
          if (type === 'search') {
            // 仅清空请求参数,不影响dateTime回显
            this.dateTime = ''
            this.listQuery.startTime = '';
            this.listQuery.endTime = '';
          }
          this.getList()
        } else {
          this.$message({
            message: data.message,
            type: "error",
            offset: 120
          })
        }
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });          
      });
    },
    goBack() {
      if (this.$route.query.pageType == 'otherPage') {
        this.$router.back()
      } else {
        this.$router.push({
          path: '/index/aiindex/dataservicebuild/datacollect',
            query: {
              metadataId: this.$route.query.metadataId,
              pageNo: this.$route.query.pageNo,
              handelTreeFlag: this.$route.query.handelTreeFlag
          }
        })
      }
    },
  }
}
</script>

<style lang="scss" scoped>
.api-store-box{
  height: calc(100% - 1.4rem);
}
/deep/ .iconbutton {
  margin-right: 0.1rem;
}
/deep/.last-icon-btn {
  margin-right: 0!important;
}
.on-data {
  margin-top: 3.3rem;
  color: #909399;
  text-align: center;
}
</style>

anchor.vue

同上anchor.vue

END...