前面示範了套版的改造,今天要說明一個常見需求──匯出 Excel。需求很單純:查詢資料後,將結果匯出為 Excel。我們預計達成以下目標:
<script setup>
區塊匯入套件 exceljs
與 file-saver
。
exceljs
:用於在前端建立/讀取/編輯 Excel(.xlsx)檔案的函式庫。file-saver
:在瀏覽器端將 Blob
(檔案位元資料容器)另存為本機檔案。<script setup lang="ts">
import ExcelJS from 'exceljs';
import { saveAs } from 'file-saver';
</script>
softwareStore.ts
pinia
與 fetch-wrapper.ts
。SoftwareDto
。softwareStore
中定義 state softwareList
。softwareStore
中定義 action fetchSoftwareList
,向後端發出請求,取得資料後將回應寫入 state 的 softwareList
陣列中。import { defineStore } from 'pinia';
import { fetchWrapper , type ApiRspMessage ,type ActionRsp } from '@/utils/helpers/fetch-wrapper';
export interface SoftwareDto {
assetId?: string;
softDefine?: string;
softDefineNM?: string;
buId?: string;
buNM?: string;
softwareItem?: string;
softwareItemNM?: string;
assetName?: string;
assetType?: string;
assetTypeNM?: string;
assetStatus?: string;
assetStatusNM?: string;
serverName?: string;
assetAmount: number;
version?: string;
modInfo?: string;
}
const querySoftwareUrl = `${import.meta.env.VITE_API_URL}/Software`;
export const useSoftewareStore = defineStore({
id: 'softewareStore',
state: () => ({
softwarList : [] as SearchSoftwareDto[],
}),
actions: {
async fetchSoftwareList() {
try {
const respData: SearchSoftwareDto[] = await fetchWrapper.post(querySoftwareUrl) as SearchSoftwareDto[];
this.softwarList = respData;
}catch (error) {
if (error instanceof Response) {
const errorText = await error.text();
console.error('Failed to create software:', errorText);
} else {
console.error('Failed to create software:', error);
}
}
},
},
});
softwareStore.softwareList
取得並同步後端資料。workbook
:新增一個活頁簿(Workbook)。sheet
:新增工作表並可自行命名。worksheet.columns
:設定各欄的標題、對應資料物件的 Key,以及欄寬。<script setup lang="ts">
const handelExportExcel = async () => {
//透過 softwareStore.ts 發送同步資料的請求,將後端回傳的資料存放於 softwareStore.softwarList 陣列中
await softwareStore.fetchSoftwarList();
const workbook = new ExcelJS.Workbook(); //新增 Excel
const worksheet = workbook.addWorksheet('sheetXXXX'); // sheet 名稱
//定義表頭
worksheet.columns = [
{ header: '公司別', key: 'buNM', width: 20 },
{ header: '資產編號', key: 'assetId', width: 20 },
{ header: '定義項目', key: 'softDefineNM', width: 20 },
{ header: '軟體簡稱', key: 'softwareItemNM', width: 20 },
{ header: '使用類別', key: 'assetTypeNM', width: 20 },
{ header: '資產名稱', key: 'assetName', width: 30 },
{ header: 'ServerName', key: 'serverName', width: 20 },
{ header: '廠牌', key: 'brand', width: 20 },
{ header: '數量', key: 'assetAmount', width: 20 },
{ header: '版本', key: 'version', width: 20 },
{ header: '維護部門', key: 'keyinBgNM', width: 20 },
{ header: '異動資訊', key: 'modInfo', width: 20 },
];
//使用foreach 迴圈softwareStore中的讀取approveDetailDatas 的陣列資料
//讀取每一列會綁定對應的 Excel 行
softwareStore.softwarList.forEach((item) => {
worksheet.addRow({
buNM: item.buNM,
assetId: item.assetId,
softDefineNM: item.softDefineNM,
softwareItemNM: item.softwareItemNM,
assetTypeNM: item.assetTypeNM,
assetName: item.assetName,
serverName: item.serverName,
brand: item.brand,
assetAmount: item.assetAmount,
version: item.version,
modInfo: item.modInfo,
});
});
// 將 Excel 文件保存為 Blob
const buffer = await workbook.xlsx.writeBuffer();
//取得目前時間,且傳為字串 YYYYMMDD_HHMMSS
const now = new Date();
const year = now.getFullYear(); // 取得年份
const month = String(now.getMonth() + 1).padStart(2, '0'); // 取得月份,補零到兩位數
const day = String(now.getDate()).padStart(2, '0'); // 取得日期,補零到兩位數
const hours = String(now.getHours()).padStart(2, '0'); // 取得小時,補零到兩位數
const minutes = String(now.getMinutes()).padStart(2, '0'); // 取得分鐘,補零到兩位數
const formattedDateTime = `${year}${month}${day}_${hours}${minutes}`; // 組合成
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
// 使用 file-saver 將檔案下載
saveAs(blob, '軟體資產_' + formattedDateTime + '.xlsx')
};
</script>
namespace Core.Models.Dto
{
public class SoftwareDto
{
public string? AssetId { get; set; }
public string? SoftDefine { get; set; }
public string? SoftDefineNM { get; set; }
public string? BuId { get; set; }
public string? BuNM { get; set; }
public string? SoftwareItem { get; set; }
public string? SoftwareItemNM { get; set; }
public string? AssetName { get; set; }
public string? AssetType { get; set; }
public string? AssetTypeNM { get; set; }
public string? AssetStatus { get; set; }
public string? AssetStatusNM { get; set; }
public string? ServerName { get; set; }
public int AssetAmount { get; set; }
public string? Version { get; set; }
public string? ModInfo { get; set; }
}
}
SoftwareController.cs
:負責所有軟體相關的 API 端點。SoftwareService
,由服務取得軟體清單資料,並彙整為符合前端 SearchSoftwareDto
的結構後回傳。namespace backendAPI.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class SoftwareController : ControllerBase
{
private readonly SoftwareService _softwareService;
public SoftwareController(SoftwareService softwareService)
{
_softwareService = softwareService;
}
/// <summary>取得軟體資產列表</summary>
/// <param name="softwareDto"></param>
/// <returns></returns>>
[HttpPost("GetSoftwares")]
[ProducesResponseType(typeof(List<SearchSoftwareDto>), 200)]
public async Task<IActionResult> GetSoftwareList()
{
try
{
var emailClaim = User.FindFirst(ClaimTypes.Upn)?.Value;
List<SearchSoftwareDto> resultList = await _softwareService.querySoftwareList(softwareDto, emailClaim);
return Ok(resultList);
}
catch (Exception ex)
{
Log.Error("Error ex : " + ex.TargetSite);
return StatusCode(500, "Internal server error");
}
}
}
}
圖25-1:在查詢介面中,點擊「匯出」功能按鈕。
圖25-2:打開 F12 開發者工具,檢查後端回應為 200 成功狀態。
圖25-3:檢視後端回應,確認已成功回傳資料內容。
圖25-4:前端成功取得資料並匯出 Excel。
圖25-5:開啟匯出檔案,驗證檔案內容是否正確。