엑셀 양식 다운로드 기능 개선
기존 코드
엑셀 양식 다운로드 버튼을 누르면 handleDownloadExcelTemplate
함수를 호출하고, sheet.js로 만든 엑셀 파일을 내보내도록 처리했습니다.
const handleDownloadExcelTemplate = () => {
const ws = XLSX.utils.aoa_to_sheet([
[`응시자 목록`],
[],
["번호", "응시번호", "이름", "이메일", "전화번호", "점수", "상태"],
]);
//만약 더미데이터가 있다면 예시로 열 제목에 맞춰 값을 표시
participantList.map((participant: Participant, index) => {
XLSX.utils.sheet_add_aoa(
ws,
[
[
index + 1,
participant.participantId,
participant.name,
participant.email,
participant.phoneNumber,
resultScore(participant.result),
participant.status,
],
],
{
origin: -1,
}
);
ws["!cols"] = [{ wpx: 200 }, { wpx: 200 }];
return false;
});
const wb: any = { Sheets: { data: ws }, SheetNames: ["data"] };
const excelButter = XLSX.write(wb, { bookType: "xlsx", type: "array" });
const excelFile = new Blob([excelButter], { type: excelFileType });
FileSaver.saveAs(excelFile, excelFileName + excelFileExtension);
};
문제
sheet.js로 직접 배열로 엑셀 칸마다 내용을 작성하기에, 내용이 많아지면 불편했고, sheet.js 무료버전은 엑셀 시트 스타일을 사용 수 없어서 칸의 내용이 짤리는 등 보기 좋지 않았습니다.
해결
그래서 미리 작성한 엑셀양식을 로컬 파일로 두고 배포한 뒤 fetch해서 createObjectUrl로 다운로드할 수 있도록 처리했습니다. 이렇게 하면 엑셀 스타일 및 내용을 직접 파일로 갖고 있기에 위처럼 sheet.js로 어렵게 작성할 필요가 없습니다.
자세한 코드
- 엑셀 템플릿 위치
public> excelTemplates > 엑셀양식명.xlsx
-
배포된 프론트단에서 엑셀 파일을 blob 형태로 get하는 코드
import axios from "axios"; export const getExcelTemplate = async (url: string) => { const { data } = await axios.get(url, { responseType: "blob" }); return data; };
-
UploadExcelModal.tsx
양식 다운로드 버튼을 누르면 아래 메서드가 실행됨const handelDownloadTemplate = async () => { const data = await getExcelTemplate("/excelTemplates/엑셀양식명.xlsx"); const blobUrl = window.URL.createObjectURL(data); const link = document.createElement("a"); link.href = blobUrl; link.download = "응시자등록_기본양식"; link.click(); URL.revokeObjectURL(blobUrl); };
getExcelTemplate
로 blob 데이터를 받아옴createObjectURL
로 blob 파일의 객체 URL 생성후- 가상의 a 태그안에 박고, click()으로 바로 실행해서 다운로드 처리
- 마지막으로 gc를 위해 blob URL 제거
-
UploadExcelModal.tsx
전체 코드import Modal from "."; import { AiOutlineClose } from "react-icons/ai"; import { ChangeEvent, Dispatch, FormEvent, SetStateAction, useState, } from "react"; import uploadIcon from "@images/upload_file_icon.svg"; import excelIcon from "@images/excel_blue.svg"; import buttonLoading from "@images/buttonLoading.svg"; import { useSelector } from "react-redux"; import { RootState } from "@store"; import { getExcelTemplate } from "@services/templates"; interface Props { templateLink: string; title: string; showUploadModal: boolean; setShowUploadModal: Dispatch<SetStateAction<boolean>>; onSubmit: (file: File) => void; } const UploadExcelModal = ({ templateLink, title, showUploadModal, setShowUploadModal, onSubmit, }: Props) => { const { isLoading } = useSelector( (state: RootState) => state.states.question ); const [file, setFile] = useState<File>(); const [error, setError] = useState(""); const handelDownloadTemplate = async () => { const data = await getExcelTemplate( "/excelTemplates/엑셀양식명.xlsx" ); const blobUrl = window.URL.createObjectURL(data); const link = document.createElement("a"); link.href = blobUrl; link.download = "응시자등록_기본양식"; link.click(); URL.revokeObjectURL(blobUrl); }; const handleChangeFile = (e: ChangeEvent<HTMLInputElement>) => { const { files } = e.target; if (files) { setError(""); setFile(files[0]); } }; const handleSubmitFile = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const fileReader = new FileReader(); if (file) { fileReader.readAsText(file); onSubmit(file); } else { setError("파일을 선택해주세요."); } }; return ( <Modal visible={showUploadModal} onClose={() => setShowUploadModal(false)}> <form onSubmit={handleSubmitFile} className="flex flex-col relative"> <AiOutlineClose onClick={() => setShowUploadModal(false)} className="cursor-pointer absolute top-0 right-0" /> <h1 className="font-bold text-[18px]">응시자 대량 등록하기</h1> <div className="flex items-center gap-2 text-[10px]"> <button type="button" className="w-[144px] h-[24px] flex items-center justify-center text-modu-blue bg-main-white border border-modu-blue hover:opacity-70 flex items-center gap-1 rounded" onClick={handelDownloadTemplate} > <img src={excelIcon} alt="excelIcon" /> <span>양식 다운로드</span> </button> </div> <hr className="mt-[17px] mb-[24px]" /> <div className="flex items-start gap-2"> <div className=""> <label htmlFor="file" className="w-[375px] h-[40px] text-[14px] text-main-gray border border-main-line flex items-center gap-2 px-4 rounded" > <img src={uploadIcon} alt="uploadIcon" /> {file ? file.name : "파일을 선택해주세요."} </label> <input type="file" id="file" onChange={handleChangeFile} accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" placeholder="파일선택" /> </div> <button type="submit" className="w-[93px] h-[40px] bg-modu-blue text-white flex justify-center items-center rounded hover:bg-blue-button-hover" > {isLoading ? ( <img src={buttonLoading} alt="loading" className="w-24" /> ) : ( <span>업로드</span> )} </button> </div> <p className="relative top-[-20px] ml-4 text-[12px] text-error"> {error} </p> <div className="flex justify-center items-center text-white font-bold gap-2 px-5 mt-[30px]"> <button type="reset" className="w-[160px] h-[40px] bg-cancel flex justify-center items-center rounded hover:bg-cancel-button-hover" onClick={() => setShowUploadModal(false)} > 닫기 </button> </div> </form> </Modal> ); }; export default UploadExcelModal;
참고
아 엑셀다운로드 개발,,, 쉽고 빠르게 하고 싶다 우아한형제들 기술블로그
React Material - 파일 브라우저에서 파일 다운로드하기 (React File Download with Node JS)
댓글남기기