• Vue3实现动态导入Excel表格数据


    1.  前言

    在开发工作过程中,我们会遇到各种各样的表格数据导入,大部分我们的解决方案:提供一个模板前端进行下载,然后按照这个模板要求进行数据填充,最后上传导入,这是其中一种解决方案。个人认为还有另外一种解决方案,不一定会前面的方案好,方便,但是可以减少人为操作,减少出错,更为通用,就是进行动态数据导入,前端进行自定义导入数据,配置数据对应关系,最后提交导入结果。

    2.  如何实现

    2.1使用技术

    后台使用.Net6.0,前端使用Vue3

    2.2实现思路

    1. 前端通过Excel表格数据导入,导入后客户可以进行数据编辑(未实现)客户数据确认后进行数据回写主页面
    2. 设置数据对应关系,也就是后台所需数据格式和前台数据格式进行绑定
    3. 数据校验,进行数据提交

    2.3具体实现方案

    2.3.1导入Excel数据

    创建列表页面,页面主要布局如下

    Table需要绑定的列是未知的,所以el-table绑定的el-table-column是需要动态进行加载,动态定义表格以及变量

    复制代码
    <el-table :data="state.tableData.data">
      <el-table-column
          v-for="item in state.colunm"
          :prop="item.key"
          :key="item.key"
          :label="item.lable"
      >
      el-table-column>
    el-table>
    复制代码
    const state = reactive({
    colunm: [{key: "", lable: ""}],
    });
    点击导入报名数据,弹出上传数据页面
    这一块的功能在之前的一篇文章有写过:https://www.cnblogs.com/wuyongfu/p/16651107.html
    子组件的核心代码
    页面布局:
    复制代码
    <template>
      <el-dialog :close-on-click-modal="false" v-model="state.dialogVisible" :title="title" width="70%" >
        <el-upload
            class="upload-demo"
            :auto-upload="false"
            :on-change="uploadChange"  style="text-align: left;"  >
          <el-button type="primary">上传文件el-button>
        el-upload>
          <el-form-item>
            <el-button @click='submit'>确认导入el-button>
          el-form-item>
        <el-table :data="state.tableData.data" max-height="500px">
          <el-table-column v-for="item in state.colunm" :prop="item.key" :key="item.key" :label="item.lable">
          el-table-column>
        el-table>
        <div class='block flex justify-end' v-if='state.tableData.total > 0'>
          <el-pagination v-model:currentPage="state.searchInput.PageIndex" v-model:page-size="state.searchInput.PageSize"
            :page-sizes="[10, 50, 200, 1000]" layout="total, sizes, prev, pager, next, jumper" @size-change="getData"
            @current-change="getData" :total="state.tableData.total" />
        div>
      el-dialog>
    template>
    复制代码
    变量定义:
    复制代码
    const state = reactive({
      tempTableData: [{}],//临时存储全部数据
      searchInput: { PageIndex: 1, PageSize: 10 },
      tableData: { data: [{}], total: 0 },//表格加载当前页面数据
      dialogVisible: false,
      colunm: [{ key: '', lable: '' }]
    });
    复制代码
    父页面调用方法,进行表格列头加载,初始化表格数据:
    const childMethod = (data) => {
      state.colunm = data;
      state.tableData.data = [];
      state.dialogVisible = true;
    }
    绑定表格方法,前端进行分页数据处理:(这里可能会有性能问题,暂时没有仔细探究)
    复制代码
    const getData = () => {
      const tempData: any = [];
      state.tempTableData.forEach((value, index) => {
        if (index >= ((state.searchInput.PageIndex - 1) * state.searchInput.PageSize) && index < ((state.searchInput.PageIndex) * state.searchInput.PageSize)) {
          tempData.push(value);
        }
      });
      state.tableData.data = tempData;
      state.tableData.total = state.tempTableData.length;
    }
    复制代码
    提交数据回写父组件方法
    复制代码
    const submit = () => {
      console.log(state.tempTableData);
      context.emit('childClick', state.tempTableData,state.colunm)
    }
    上传Excel读取数据方法,主要动态绑定列以及导入的数据
    const uploadChange = async (file) => {
      let dataBinary = await readFile(file.raw)
      let workBook = XLSX.read(dataBinary, { type: 'binary', cellDates: true })
      let workSheet = workBook.Sheets[workBook.SheetNames[0]]
      let data: any = XLSX.utils.sheet_to_json(workSheet)
    
      let mycolunm={};
      Object.setPrototypeOf(mycolunm,data[0]);
      state.colunm=[];
      for(let key in mycolunm){
        state.colunm.push( { lable: key, key: key })
      }
      
      let tHeader = state.colunm.map(obj => obj.lable)
      let filterVal = state.colunm.map(obj => obj.key)
      tHeader.map(val => filterVal.map(obj => val[obj]))
      const tempData: any = [];
      data.forEach((value) => {
        const ob = {};
        tHeader.forEach((item, index) => {
          ob[filterVal[index]] = value[item].toString();
        })
        tempData.push(ob);
      })
      state.tempTableData = tempData;
      getData();
    }
    复制代码
    这里导入数据后,会调用主组件方法(dataUploadchildClick)用于回写主组件表格数据,列头等数据
    复制代码
    const dataUploadchildClick = (data, colunm) => {
      state.colunm = colunm;
      state.tempData = data;
      getData();
      dataUpload.value.cancel();
    };
    复制代码

    2.3.2设置数据对应关系

    定义后台需要的列
    复制代码
    SelectData: [
      {key: 'zkzh', type: '', value: '', selectValue: '', title: '准考证号'},
      {key: 'zjlb', type: '', value: '', selectValue: '', title: '证件类别'},
      {key: 'zjhm', type: '', value: '', selectValue: '', title: '证件号码'},
      {key: 'ksxm', type: '', value: '', selectValue: '', title: '考生姓名'},
      {key: 'xb', type: '', value: '', selectValue: '', title: '性别'},
      {key: 'ss', type: '', value: '', selectValue: '', title: '省市'},
      {key: 'kq', type: '', value: '', selectValue: '', title: '考区'},
      {key: 'kdh', type: '', value: '', selectValue: '', title: '考点号'},
      {key: 'kdmc', type: '', value: '', selectValue: '', title: '考点名称'},
      {key: 'kch', type: '', value: '', selectValue: '', title: '考场号'},
      {key: 'kchdz', type: '', value: '', selectValue: '', title: '考场地址'},
      {key: 'zwh', type: '', value: '', selectValue: '', title: '座位号'},
      {key: 'chc', type: '', value: '', selectValue: '', title: '场次'}
    ]
    复制代码
    创建Select组件,组件核心代码
    页面布局部分
    复制代码
    <label style="width: 50px;display: inline-block;">{{ itemKey }}:label>
    <el-select v-model="state.type" :placeholder="name" size="large" clearable>
      <el-option
          v-for="item in state.options"
          :key="item.value"
          :label="item.label"
          :value="item.value"
      />
    el-select>
    <el-input v-model="state.value" style="width: 200px;" :placeholder="name" size="large" v-if="state.type=='0'"/>
    <el-select v-model="state.selectValue" style="width: 200px;" :placeholder="name" size="large" clearable
               v-if="state.type=='1'||state.type=='2'">
      <el-option
          v-for="item in state.ValueOptions"
          :key="item.key"
          :label="item.lable"
          :value="item.key"
      />
    el-select>
    复制代码
    接受参数定义
    复制代码
    props: {
      itemKey: String,
      name: String,
      type: String,
      value: String,
      selectValue: String,
      colunm: Object
    },
    复制代码
    页面变量定义,这里定义的默认option主要有三个
    • 0 固定值,这一列为固定值,不从表格中读取
    • 1 下拉框,从导入数据中选择
    • 2 自动生成,这里主要是特殊业务,可以进行自定义扩展
    复制代码
    const state = reactive({
      value: ref(''),
      type: ref(''),
      selectValue: ref([]),
      ValueOptions:[{}],
      options: [
        {
          value: '0',
          label: '固定值',
        },
        {
          value: '1',
          label: '下拉框',
        },
        {
          value: '2',
          label: '自动生成',
        },
      ],
    });
    复制代码
    监听下拉框选择类型:
    watch(() => state.type, (newVal) => {
          context.emit('update:type', newVal);
        }
    );
    监听下拉框选择固定,文本框输入的值:
    watch(() => state.value, (newVal) => {
      context.emit('update:value', newVal)
    })
    监听下拉框选择下拉框,后面下拉框选择的值:
    watch(() => state.selectValue, (newVal) => {
      context.emit('update:selectValue', newVal)
    })
    监听表格的列,动态加载下拉框绑定的值:
    watch(() => props.colunm, (newVal: any) => {
      state.ValueOptions=newVal;
    })
    最终的效果:
    父页面进行引用
    复制代码
    <el-row :gutter="24" justify="start" style="text-align: left;">
              <div v-for="(item,index) in state.SelectData" :key='index' style="margin-top: 5px;width: 100%;">
                <el-col :span="24"><Select
                    :itemKey="item.key"
                    v-model:type="item.type"
                    v-model:value="item.value"
                    v-model:selectValue="item.selectValue"
                    :name="item.title"
                    :colunm="state.colunm"/>el-col>
              div>
            el-row>
    复制代码

    2.3.3进行数据提交

    提交数据到后台进行处理,这里根据自己的业务进行验证,或则进行其它扩展
    复制代码
    const Save = () => {
      if (state.tempData.length == 0) {
        state.active=1;
        ElMessage.warning('请导入考生数据');
        return;
      }
      let CheckSelectData = true;
      state.SelectData.forEach((value, index) => {
        if (!value.type) {
          CheckSelectData = false
        }
      });
      if (!CheckSelectData) {
        state.active=2;
        ElMessage.warning('请设置完成数据对应关系');
        return;
      }
      if (state.tableTimeData.data.length==0){
        state.active=3;
        ElMessage.warning('请添加场次数据');
        return;
      }
      if (!state.ExamData.jiancheng||!state.ExamData.kaikaonianyue||!state.ExamData.quancheng){
        state.active=4;
        ElMessage.warning('请设置任务相关信息');
        return;
      }
      axios.post('/GenerateCheckTemplate', state)
          .then(function (response) {
            if (response.status == 200) {
              ElMessage.success(response.data);
              dataUpload.value.cancel();
              getData();
            } else {
              ElMessage.error(response.data)
            }
          })
    }
    复制代码
    后台定义接口:/GenerateCheckTemplate
    定义实体对前台导入数据进行接收:
    复制代码
    public class GenerateCheckTemplate
        {
            /// 
            /// 考试任务对象
            /// 
            public ExamData ExamData { get; set; }
    
            /// 
            /// 数据对应关系对象
            /// 
            public List SelectData { get; set; }
    
            /// 
            /// 导入数据
            /// 
            public Liststring, string>> tempData { get; set; }
    
            /// 
            /// 导入数据列集合
            /// 
            public List colunm { get; set; }
    
            /// 
            /// 场次对象
            /// 
            public tableTimeData tableTimeData { get; set; }
    }
    
        /// 
        /// 考试任务对象
        /// 
        public class ExamData
        {
            /// 
            /// 简称
            /// 
            public string jiancheng { get; set; }
            /// 
            /// 考试年月
            /// 
            public string kaikaonianyue { get; set; }
            /// 
            /// 简称
            /// 
            public string quancheng { get; set; }
        }
        /// 
        /// 数据对应关系对象
        /// 
        public class Correspondence
        {
            /// 
            /// key
            /// 
            public string key { get; set; }
    
            /// 
            /// 下拉选择数据
            /// 
            public string selectValue { get; set; }
    
            /// 
            /// 标题
            /// 
            public string title { get; set; }
    
            /// 
            /// 类型 0 固定值 1下拉框选择
            /// 
            public string type { get; set; }
    
            /// 
            /// 固定值数据
            /// 
            public string value { get; set; }
        }
    
        /// 
        /// 表格对象
        /// 
        public class tableTimeData
        {
            /// 
            /// 提交表格数据
            /// 
            public List data { get; set; }
            /// 
            /// 总数
            /// 
            public int total { get; set; }
        }
    
        public class ExamTime
        {
            /// 
            /// 场次编码
            /// 
            public int? ExamTimeCode { get; set; }
            /// 
            /// 场次名称
            /// 
    
            public string ExamTimeName { get; set; }
    
            /// 
            /// 开始时间
            /// 
            public string startTime { get; set; }
            /// 
            /// 结束时间
            /// 
            public string endTime { get; set; }
    
            /// 
            /// 是否编辑状态
            /// 
            public bool Edit { get; set; }
        }
        /// 
        /// 导入数据列对象
        /// 
        public class Colunm
        {
            /// 
            /// 键值
            /// 
            public string key { get; set; }
            /// 
            ////// 
            public string lable { get; set; }
        }
    复制代码
    然后处理前端传过来的数据转换成需要的数据,这里type需要修改成枚举类型,类型可以根据需求进行扩展
    复制代码
     /// 
            /// 处理前端传过来的数据
            /// 
            /// 
            private void ProcessingData(GenerateCheckTemplate companiesInput, DataTable dataTable)
            {
                Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
                foreach (var data in companiesInput.tempData)
                {
                    DataRow dataRow = dataTable.NewRow();
    
                    foreach (Correspondence correspondence in companiesInput.SelectData)
                    {
                        if (correspondence.type == "0")
                        {
                            dataRow[correspondence.key] = correspondence.value;
                        }
                        else if (correspondence.type == "1")
                        {
                            dataRow[correspondence.key] = data[correspondence.selectValue];
                        }
                        else if (correspondence.type == "2")
                        {
                            string value = data[correspondence.selectValue];
                            if (keyValuePairs.ContainsKey(value))
                            {
                                dataRow[correspondence.key] = keyValuePairs[value];
                            }
                            else
                            {
                                keyValuePairs.Add(value, (keyValuePairs.Count + 1).ToString().PadLeft(2, '0'));
                                dataRow[correspondence.key] = keyValuePairs[value];
                            }
                        }
                    }
    
                    dataTable.Rows.Add(dataRow);
                }
            }
    复制代码
    整体的功能到这里基本上实现了,具体的细节可能需要根据不同的项目进行优化,有更多的方案可以一起进行交流
    附上源码地址:https://gitee.com/wyf854861085/file-upload.git
  • 相关阅读:
    【Flutter 面试题】解释 Flutter的热重载(Hot Reload)功能
    Canvas和SVG
    矩阵按列相乘运算的并行化实现方法
    论文阅读(13) 水母游泳过程中的神经机械波共振(2021)
    目标检测重要概念——IOU、感受野、空洞卷积、mAP
    【最详细】最新最全Java虚拟机(JVM)面试题(51道)
    MATLAB算法实战应用案例精讲-【智能优化算法】灰狼算法(GWO) (附MATLAB和Python源码)
    添加IDEA到右键打开里面
    如何用 ChatGPT 的 Advanced Data Analysis 帮你采集数据?
    全景环视前装市场“变天”,这家中国本土供应商首次跻身份额第一
  • 原文地址:https://www.cnblogs.com/wuyongfu/p/16880136.html