后端abp,前端vue導入excel,開始准備用直接用npoi,覺得要寫太多的代碼,就算從以前的復制粘貼也麻煩,所以偷懶直接用別人的輪子
Magicodes.IE。這樣可以節省很多工作,根據實體生成excel模板、支持枚舉、導入時自動驗證數據是否合法(必填、類型等)
Excel模板
要導入首先要有錄入數據的excel模板,以前都是把模板做好,放到服務器上,給一個下載鏈接給用戶下載,這里可以直接用對象動態生成模板。
//ExcelAppService.cs
/// <summary>
/// 生成excel模板
/// </summary>
/// <typeparam name="T">模板內容實體</typeparam>
/// <param name="fileName">下載文件名稱</param>
/// <returns>輸出文件流</returns>
internal async Task<FileContentResult> GetTemplate<T>(string fileName = "模板") where T : class, new()
{
byte[] fileBytes = await importer.GenerateTemplateBytes<T>();
return new FileContentResult(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet)
{
FileDownloadName = $"{fileName}.xlsx"
};
}
importer是在構造函數中注入的IImporter類型,如果你使用注入需要先在module的Initialize()方法中注冊。
//module.Initialize()方法
IocManager.Register<Magicodes.ExporterAndImporter.Core.IImporter, Magicodes.ExporterAndImporter.Excel.ExcelImporter>(DependencyLifeStyle.Transient);
你也可以直接使用
IImporter importer=new ExcelImporter()
生成模板就做完了,剩下的就是在需要下載的地方調用此方法,公開一個api接口就可以了
/// <summary>
/// 下載導入模板
/// </summary>
/// <returns></returns>
public async Task<ActionResult> GetTemplate()
{
return await excelAppService.GetTemplate<XXXXImportExcelDto>();
}
XXXXImportExcelDto是導入的實體類型,具體定義方式可以見https://github.com/xin-lai/Magicodes.IE
如果你用的abp官方提供的vue項目,使用的axios請求后端,也就是ajax請求,這個文件流是不會彈出保存文件框的,需要在axios請求后攔截文件流彈出下載框。找到src\lib\ajax.ts文件,修改ajax.interceptors.response方法,並添加一個downloadUrl方法
ajax.interceptors.response.use((respon)=>{
++ //攔截文件下載請求
++ if (respon.headers && (respon.headers['content-type'] === 'application/octet-stream')) {
++ downloadUrl(respon.request.responseURL)
++ respon.data='';
++ respon.headers['content-type'] = 'text/json'
++ return respon;
++ }
return respon
},(error)=>{
if(!!error.response&&!!error.response.data.error&&!!error.response.data.error.message&&error.response.data.error.details){
vm.$Modal.error({title:error.response.data.error.message,content:error.response.data.error.details})
}else if(!!error.response&&!!error.response.data.error&&!!error.response.data.error.message){
vm.$Modal.error({title:window.abp.localization.localize("LoginFailed"),content:error.response.data.error.message})
}else if(!error.response){
vm.$Modal.error(window.abp.localization.localize('UnknownError'));
}
setTimeout(()=>{
vm.$Message.destroy();
},1000);
return Promise.reject(error);
})
++const downloadUrl = url => {
++ let iframe = document.createElement('iframe')
++ iframe.style.display = 'none'
++ iframe.src = url
++ iframe.onload = function () {
++ document.body.removeChild(iframe)
++ }
++ document.body.appendChild(iframe)
++}
導入excel
導入分為兩步:上傳excel文件和解析數據。由於沒有找到一個一次能處理這兩步的方法(因為需要指定解析后的類型,這是一個強類型參數),我采用的方式是:
- 加一個自定義組件,主要用於上傳,提供一個上傳完成事件,在上傳完成后觸發事件並傳入后台excel文件的名稱,
- 使用的地方綁定事件並把帶着文件名請求后台,
- 后台再調用通用方法的解析數據
定義組件
<template>
<div>
<Upload
:action="uploadURL"
:on-success="onSuccess"
accept=".xls, .xlsx"
:show-upload-list="false"
>
<Button icon="android-add" type="primary">{{btnTitle}}</Button>
</Upload>
</div>
</template>
<script lang="ts">
import { Component, Vue, Inject, Prop, Watch } from "vue-property-decorator";
import Util from "../../lib/util";
import AbpBase from "../../lib/abpbase";
import appconst from "../../lib/appconst";
@Component
export default class ImportExcel extends AbpBase {
uploadURL =
appconst.remoteServiceBaseUrl + "/api/services/app/Excel/UploadExcelFile";
async onSuccess(response, file, fileList) {
//上傳完成觸發事件uploadCompleted
this.$emit("uploadCompleted", response.result);
}
/**按鈕顯示內容 */
@Prop({ type: String, default: "" }) btnTitle: String;
}
</script>
<style lang="less" scoped>
</style>
后端接收文件方法
//ExcelAppService.cs
/// <summary>
/// 接收上傳文件方法
/// </summary>
/// <param name="file">文件內容</param>
/// <returns>文件名稱</returns>
public async Task<string> UploadExcelFile(IFormFile file)
{
//FileDir是存儲臨時文件的目錄,相對路徑
//private const string FileDir = "/File/ExcelTemp";
string url = await WriteFile(file, FileDir);
string fullpath = Path.GetFullPath($"{Environment.CurrentDirectory}" + url);
return Path.GetFileName(url);
}
/// <summary>
/// 寫入文件
/// </summary>
/// <param name="avatar"></param>
/// <param name="reDir"></param>
/// <returns></returns>
public async Task<string> WriteFile(IFormFile avatar, string reDir)
{
string reName = Guid.NewGuid() + Path.GetExtension(avatar.FileName);
string dir = GetDirPath(reDir);
string path = $"{dir}\\{reName}";
Stream stream = avatar.OpenReadStream();
using (FileStream fileStream = new FileStream(path, FileMode.Create))
{
await avatar.CopyToAsync(fileStream);
}
return $"{reDir}/{reName}";
}
public string GetDirPath(string reDir)
{
string dir = $"{Environment.CurrentDirectory}/{reDir}";
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
return Path.GetFullPath(dir);
}
使用組件
<template>
<ImprotExcel @uploadCompleted="importExcel" :btnTitle="'導入excel'" ></ImprotExcel>
</template>
<script>
async importExcel(fileName: string) {
//請求后端api
await this.$store.dispatch({
type: "xxx/importExcel",
data: { fileName: fileName, labId: this.labId }
});
(<any>this.$Message).success({ background: true, content: "導入成功" });
}
</script>
后端解析文件方法
/// <summary>
/// 導入
/// </summary>
/// <param name="input">導入excel參數</param>
/// <returns></returns>
[HttpPost]
public async Task ImportExcel(XXXImprotExcelInput input)
{
var data = await excelAppService.GetData<XXXImportExcelDto>(input.FileName);
if (!data.Any())
{
return;
}
//你的邏輯
}
//XXXImprotExcelInput.cs
/// <summary>
/// 導入excel
/// </summary>
public class XXXImprotExcelInput
{
/// <summary>
/// 上傳的excel文件名稱
/// </summary>
public string FileName { get; set; }
//你的其他參數
}
//ExcelAppService.cs
/// <summary>
/// 解析excel數據
/// </summary>
/// <typeparam name="T">要解析的數據類型</typeparam>
/// <param name="fileName">excel文件名稱,不含路徑</param>
/// <returns></returns>
internal async Task<IEnumerable<T>> GetData<T>(string fileName) where T : class, new()
{
var fullpath = GetFullPath(fileName);
var result = await importer.Import<T>(fullpath);
if (result.HasError)
{
var errFile = Path.GetFileNameWithoutExtension(fileName) + "_" + Path.GetExtension(fileName);
//如果excel文件內容不符合要求(格式錯誤、必填數據未填、數據類型錯誤),則彈出錯誤提示並給出下載鏈接
throw new UserFriendlyException("導入錯誤", GetErrorExcelDownLoadUrl(errFile));
}
return result.Data;
}
/// <summary>
/// 下載excel文件
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
[HttpGet]
public async Task<FileContentResult> DownLoadFile(string fileName)
{
var fullPath = GetFullPath(fileName);
byte[] fileBytes = await File.ReadAllBytesAsync(fullPath);
return new FileContentResult(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet)
{
FileDownloadName = fileName
};
}
/// <summary>
/// 獲取文件全路徑
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
private string GetFullPath(string fileName)
{
fileName = Path.GetFileName(fileName);
var fullpath = Path.GetFullPath(Environment.CurrentDirectory.EnsureEndsWith('/') + FileDir.EnsureEndsWith('/') + fileName);
return fullpath;
}
/// <summary>
/// 獲取excel下載鏈接
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
private string GetErrorExcelDownLoadUrl(string fileName)
{
return $"請按照excel文件內的錯誤提示修改后再次導入,<a href='{GetHost()}/api/services/app/Excel/DownLoadFile?fileName={fileName}' target='_blank'>點擊下載excel</a>"
;
}
/// <summary>
/// 獲取當前域名地址
/// </summary>
/// <returns></returns>
private string GetHost()
{
var req = httpContextAccessor.HttpContext.Request;
return $"{req.Scheme}://{req.Host}";
}
參考資料