기존 코드

엑셀 양식 다운로드 버튼을 누르면 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);
    };
    
    1. getExcelTemplate로 blob 데이터를 받아옴
    2. createObjectURL로 blob 파일의 객체 URL 생성후
    3. 가상의 a 태그안에 박고, click()으로 바로 실행해서 다운로드 처리
    4. 마지막으로 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;
    
    




참고

Blob에 대해서 이해하고 엑셀 파일 갖고 오기

아 엑셀다운로드 개발,,, 쉽고 빠르게 하고 싶다 우아한형제들 기술블로그

React Material - 파일 브라우저에서 파일 다운로드하기 (React File Download with Node JS)

[React] 로컬에 있는 json으로 url을 설정하는 방법

댓글남기기