Vue3實現動態匯入Excel表格資料

2022-11-11 15:03:05

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
    {
        /// <summary>
        /// 考試任務物件
        /// </summary>
        public ExamData ExamData { get; set; }

        /// <summary>
        /// 資料對應關係物件
        /// </summary>
        public List<Correspondence> SelectData { get; set; }

        /// <summary>
        /// 匯入資料
        /// </summary>
        public List<Dictionary<string, string>> tempData { get; set; }

        /// <summary>
        /// 匯入資料列集合
        /// </summary>
        public List<Colunm> colunm { get; set; }

        /// <summary>
        /// 場次物件
        /// </summary>
        public tableTimeData tableTimeData { get; set; }
}

    /// <summary>
    /// 考試任務物件
    /// </summary>
    public class ExamData
    {
        /// <summary>
        /// 簡稱
        /// </summary>
        public string jiancheng { get; set; }
        /// <summary>
        /// 考試年月
        /// </summary>
        public string kaikaonianyue { get; set; }
        /// <summary>
        /// 簡稱
        /// </summary>
        public string quancheng { get; set; }
    }
    /// <summary>
    /// 資料對應關係物件
    /// </summary>
    public class Correspondence
    {
        /// <summary>
        /// key
        /// </summary>
        public string key { get; set; }

        /// <summary>
        /// 下拉選擇資料
        /// </summary>
        public string selectValue { get; set; }

        /// <summary>
        /// 標題
        /// </summary>
        public string title { get; set; }

        /// <summary>
        /// 型別 0 固定值 1下拉框選擇
        /// </summary>
        public string type { get; set; }

        /// <summary>
        /// 固定值資料
        /// </summary>
        public string value { get; set; }
    }

    /// <summary>
    /// 表格物件
    /// </summary>
    public class tableTimeData
    {
        /// <summary>
        /// 提交表格資料
        /// </summary>
        public List<ExamTime> data { get; set; }
        /// <summary>
        /// 總數
        /// </summary>
        public int total { get; set; }
    }

    public class ExamTime
    {
        /// <summary>
        /// 場次編碼
        /// </summary>
        public int? ExamTimeCode { get; set; }
        /// <summary>
        /// 場次名稱
        /// </summary>

        public string ExamTimeName { get; set; }

        /// <summary>
        /// 開始時間
        /// </summary>
        public string startTime { get; set; }
        /// <summary>
        /// 結束時間
        /// </summary>
        public string endTime { get; set; }

        /// <summary>
        /// 是否編輯狀態
        /// </summary>
        public bool Edit { get; set; }
    }
    /// <summary>
    /// 匯入資料列物件
    /// </summary>
    public class Colunm
    {
        /// <summary>
        /// 鍵值
        /// </summary>
        public string key { get; set; }
        /// <summary>
        ////// </summary>
        public string lable { get; set; }
    }
然後處理前端傳過來的資料轉換成需要的資料,這裡type需要修改成列舉型別,型別可以根據需求進行擴充套件
 /// <summary>
        /// 處理前端傳過來的資料
        /// </summary>
        /// <param name="companiesInput"></param>
        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