๐ฏ ์จ๋ผ์ธ ์ด๋ ฅ์ ๋น๋ - ํ ํ๋ฆฟ ์ ํ & PDF ๋ค์ด๋ก๋
resume-builder-pdf-export
๋ฌธ์ ์ค๋ช
ํ๋ก์ ํธ ๊ฐ์
์ทจ์ ์ค๋น์๊ณผ ์ด์ง์๋ฅผ ์ํ ์จ๋ผ์ธ ์ด๋ ฅ์ ์์ฑ ํ๋ซํผ์ ๋ง๋ญ๋๋ค. ์ฌ์ฉ์๊ฐ ๊ฐ์ธ์ ๋ณด, ํ๋ ฅ, ๊ฒฝ๋ ฅ, ๊ธฐ์ ์คํ์ ์ ๋ ฅํ๋ฉด ์ค์๊ฐ์ผ๋ก ์ด๋ ฅ์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ ์์ฑ๋๊ณ , ์์ฑ๋ ์ด๋ ฅ์๋ฅผ PDF๋ก ๋ค์ด๋ก๋ํ ์ ์์ต๋๋ค.
ํต์ฌ ๊ธฐ๋ฅ
- โ ๋จ๊ณ๋ณ ์ด๋ ฅ์ ์์ฑ ํผ: ๊ฐ์ธ์ ๋ณด โ ํ๋ ฅ โ ๊ฒฝ๋ ฅ โ ๊ธฐ์ ์คํ ์์๋ก ์ ๋ ฅ
- โ ์ค์๊ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ: ์ ๋ ฅํ ๋๋ง๋ค ์ค๋ฅธ์ชฝ์ ์ด๋ ฅ์ ๋์์ธ ์ค์๊ฐ ๋ ๋๋ง
- โ ํ ํ๋ฆฟ ์ ํ: 2๊ฐ์ง ์ด๋ ฅ์ ๋์์ธ ํ ํ๋ฆฟ ์ค ์ ํ (์ฌํํ, ๋ชจ๋ํ)
- โ ๋ฐ์ดํฐ ์ ์ฅ: Laravel API๋ก ์์ฑ ์ค์ธ ์ด๋ ฅ์ ์์ ์ ์ฅ
- โ PDF ๋ด๋ณด๋ด๊ธฐ: ์์ฑ๋ ์ด๋ ฅ์๋ฅผ PDF ํ์ผ๋ก ๋ค์ด๋ก๋
์ ํ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ๐จ ์ถ๊ฐ ํ ํ๋ฆฟ ๋์์ธ (์ปฌ๋ฌํํ, ๋ฏธ๋๋ฉํ ๋ฑ)
- ๐ง ์ด๋ ฅ์ ์ด๋ฉ์ผ ์ ์ก ๊ธฐ๋ฅ
- ๐ ๊ณต์ ๊ฐ๋ฅํ ์ด๋ ฅ์ ๋งํฌ ์์ฑ (๊ณต๊ฐ/๋น๊ณต๊ฐ ์ค์ )
- ๐ ์ด๋ ฅ์ ์กฐํ์ ํต๊ณ
๊ธฐ์ ์คํ
- ๋ฐฑ์๋: Laravel 13.x (RESTful API, SQLite DB)
- ํ๋ก ํธ์๋: React 19 (Vite, Tailwind CSS)
- PDF ์์ฑ: jsPDF ๋๋ html2pdf.js
์ด ํ๋ก์ ํธ๋ฅผ ํตํด Laravel API ์ค๊ณ, React ํผ ํธ๋ค๋ง, ํด๋ผ์ด์ธํธ ์ฌ์ด๋ PDF ์์ฑ์ ํ์ตํ ์ ์์ต๋๋ค.
์ ์ฝ ์กฐ๊ฑด
- โข Laravel 13.x + React 19 ํ์คํ ๊ตฌ์กฐ (๋ฐฑ์๋/ํ๋ก ํธ์๋ ๋ถ๋ฆฌ)
- โข Laravel์ RESTful API ์๋ฒ๋ก๋ง ๋์ (Blade ํ ํ๋ฆฟ ์ฌ์ฉ ๊ธ์ง)
- โข React๋ Vite๋ก ๋ณ๋ ํ๋ก์ ํธ ๊ตฌ์ฑ
- โข ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ SQLite ์ฌ์ฉ (๊ฐ๋จํ ์ค์ )
- โข Tailwind CSS๋ก ๋ฐ์ํ ๋์์ธ ๊ตฌํ
- โข PDF ์์ฑ์ ํด๋ผ์ด์ธํธ ์ฌ์ด๋์์ ์ฒ๋ฆฌ (jsPDF ๋๋ html2pdf.js)
- โข ์ด๋ ฅ์ ์์ฑ ํผ์ ๋ค๋จ๊ณ(multi-step) ํ์์ผ๋ก ๊ตฌํ
- โข ์ค์๊ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ React state๋ก ๊ด๋ฆฌ
- โข CORS ์ค์ ํ์ (Laravel API โ React ํต์ )
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
mkdir resume-builder-pdf-export && cd resume-builder-pdf-export
์ด ํด๋์์ ํ๋ก์ ํธ๋ฅผ ์์ํฉ๋๋ค.
## 1๋จ๊ณ: Laravel 13.x API ๋ฐฑ์๋ ํ๋ก์ ํธ ์์ฑ
Laravel 13.x ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ณ ๊ธฐ๋ณธ ์ค์ ์ ์๋ฃํ์ธ์.
### ์๊ตฌ์ฌํญ
1. Laravel 13.x ์ค์น (Composer ์ฌ์ฉ)
2. SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์
- `.env` ํ์ผ์์ `DB_CONNECTION=sqlite` ์ค์
- `database/database.sqlite` ํ์ผ ์์ฑ
3. CORS ์ค์ (ํ๋ก ํธ์๋ React์ ํต์ ์ ์ํด)
- `config/cors.php`์์ `allowed_origins`์ `http://localhost:5173` ์ถ๊ฐ
4. ์ด๋ ฅ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ `resumes` ํ
์ด๋ธ ๋ง์ด๊ทธ๋ ์ด์
์์ฑ
- ์ปฌ๋ผ: `id`, `full_name`, `email`, `phone`, `education` (JSON), `experience` (JSON), `skills` (JSON), `template` (string), `created_at`, `updated_at`
5. Resume ๋ชจ๋ธ ์์ฑ (`app/Models/Resume.php`)
- `$fillable` ์์ฑ์ ๋ชจ๋ ์ปฌ๋ผ ์ถ๊ฐ
- `$casts` ์์ฑ์ `education`, `experience`, `skills`๋ฅผ `array`๋ก ์บ์คํ
6. API ๋ผ์ฐํธ ์ค์ (`routes/api.php`)
- `POST /api/resumes` - ์ด๋ ฅ์ ์์ฑ
- `GET /api/resumes/{id}` - ์ด๋ ฅ์ ์กฐํ
- `PUT /api/resumes/{id}` - ์ด๋ ฅ์ ์์
7. ResumeController ์์ฑ (`app/Http/Controllers/ResumeController.php`)
- `store()` ๋ฉ์๋: ์ ์ด๋ ฅ์ ์์ฑ ๋ฐ JSON ์๋ต
- `show()` ๋ฉ์๋: ์ด๋ ฅ์ ์กฐํ ๋ฐ JSON ์๋ต
- `update()` ๋ฉ์๋: ์ด๋ ฅ์ ์์ ๋ฐ JSON ์๋ต
### ์ฒดํฌํฌ์ธํธ
- `php artisan serve`๋ก Laravel ์๋ฒ๊ฐ `http://localhost:8000`์์ ์คํ๋๋์ง ํ์ธ
- Postman ๋๋ curl๋ก `POST /api/resumes` ํ
์คํธ
- ์์ ์์ฒญ body:
```json
{
"full_name": "ํ๊ธธ๋",
"email": "hong@example.com",
"phone": "010-1234-5678",
"education": [{"school": "์์ธ๋ํ๊ต", "major": "์ปดํจํฐ๊ณตํ", "period": "2018-2022"}],
"experience": [{"company": "๋ค์ด๋ฒ", "position": "ํ๋ก ํธ์๋ ๊ฐ๋ฐ์", "period": "2022-ํ์ฌ"}],
"skills": ["JavaScript", "React", "Node.js"],
"template": "simple"
}
```
- ์๋ต์ผ๋ก ์์ฑ๋ ์ด๋ ฅ์ ๋ฐ์ดํฐ์ `id`๊ฐ ๋ฐํ๋๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel 13.x API ์๋ฒ๊ฐ ์์ฑ๋๊ณ , SQLite DB์ ์ด๋ ฅ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ/์กฐํ/์์ ํ ์ ์๋ RESTful API ์๋ํฌ์ธํธ๊ฐ ์๋ํฉ๋๋ค. CORS ์ค์ ์ผ๋ก ํ๋ก ํธ์๋์ ํต์ ๊ฐ๋ฅํ ์ํ์ ๋๋ค.
## 2๋จ๊ณ: React 19 ํ๋ก ํธ์๋ ํ๋ก์ ํธ ์์ฑ ๋ฐ ๊ธฐ๋ณธ UI ๊ตฌ์ฑ
React 19 ํ๋ก์ ํธ๋ฅผ Vite๋ก ์์ฑํ๊ณ , ์ด๋ ฅ์ ์์ฑ ํผ์ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ๋ง๋์ธ์.
### ์๊ตฌ์ฌํญ
1. React 19 + Vite ํ๋ก์ ํธ ์์ฑ
- `frontend` ํด๋์ ์์ฑ (`npm create vite@latest frontend -- --template react`)
- `cd frontend && npm install`
2. Tailwind CSS ์ค์น ๋ฐ ์ค์
- `npm install -D tailwindcss postcss autoprefixer`
- `npx tailwindcss init -p`
- `tailwind.config.js`์ `content: ['./index.html', './src/**/*.{js,jsx}']` ์ถ๊ฐ
- `src/index.css`์ Tailwind ๋๋ ํฐ๋ธ ์ถ๊ฐ
3. ํ๋ก์ ํธ ๊ตฌ์กฐ ์ค์
- `src/components/` ํด๋ ์์ฑ
- `src/pages/` ํด๋ ์์ฑ
- `src/services/api.js` ํ์ผ ์์ฑ (Laravel API ํต์ ํจ์)
4. `src/services/api.js` ๊ตฌํ
- `axios` ์ค์น (`npm install axios`)
- `baseURL: 'http://localhost:8000/api'`๋ก ์ค์
- `createResume(data)`, `getResume(id)`, `updateResume(id, data)` ํจ์ ์์ฑ
5. `src/pages/ResumeBuilder.jsx` ์์ฑ
- ๋ค๋จ๊ณ ํผ UI ๊ตฌ์กฐ (4๋จ๊ณ: ๊ฐ์ธ์ ๋ณด โ ํ๋ ฅ โ ๊ฒฝ๋ ฅ โ ๊ธฐ์ )
- ํ์ฌ ๋จ๊ณ๋ฅผ ํ์ํ๋ ์งํ ๋ฐ(progress bar)
- "๋ค์", "์ด์ ", "์ ์ฅ" ๋ฒํผ
6. `src/App.jsx`์์ `ResumeBuilder` ์ปดํฌ๋ํธ ๋ ๋๋ง
7. ๊ฐ๋ฐ ์๋ฒ ์คํ ํ์ธ
- `npm run dev`๋ก `http://localhost:5173` ์ ์
- ๊ธฐ๋ณธ UI๊ฐ ํ์๋๋์ง ํ์ธ
### ๋์์ธ ๊ฐ์ด๋
- Tailwind CSS๋ก ๊น๋ํ ์นด๋ ๋ ์ด์์
- ์งํ ๋ฐ๋ ์๋จ์ ๊ณ ์ , ํ์ฌ ๋จ๊ณ ๊ฐ์กฐ (ํ๋์)
- ํผ ์
๋ ฅ ํ๋๋ ๋๋ํ padding, border-radius ์ ์ฉ
- ๋ฒํผ์ primary(ํ๋์), secondary(ํ์) ์คํ์ผ ๊ตฌ๋ถ
### ์ฒดํฌํฌ์ธํธ
- React ๊ฐ๋ฐ ์๋ฒ๊ฐ ์ ์ ์คํ๋๊ณ ๋น ํผ UI๊ฐ ๋ณด์ด๋์ง ํ์ธ
- Tailwind CSS ์คํ์ผ์ด ์ ์ฉ๋๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: React 19 ํ๋ก ํธ์๋ ํ๋ก์ ํธ๊ฐ ์์ฑ๋๊ณ , Tailwind CSS๊ฐ ์ ์ฉ๋ ์ด๋ ฅ์ ์์ฑ ํผ์ ๊ธฐ๋ณธ UI ๊ตฌ์กฐ๊ฐ ์์ฑ๋ฉ๋๋ค. ์์ง ๊ธฐ๋ฅ์ ์์ง๋ง 4๋จ๊ณ ํผ ๋ ์ด์์๊ณผ ์งํ ๋ฐ๊ฐ ํ์๋ฉ๋๋ค.
## 3๋จ๊ณ: ๋ค๋จ๊ณ ํผ ๊ตฌํ ๋ฐ ์ค์๊ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ ฅ์ ์์ฑ ํผ์ ๊ฐ ๋จ๊ณ๋ฅผ ๊ตฌํํ๊ณ , ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ ๋ฐ์ํ์ธ์. ### ์๊ตฌ์ฌํญ 1. `src/components/PersonalInfoForm.jsx` ์์ฑ - ์ ๋ ฅ ํ๋: ์ด๋ฆ(full_name), ์ด๋ฉ์ผ(email), ์ ํ๋ฒํธ(phone) - React state๋ก ์ ๋ ฅ๊ฐ ๊ด๋ฆฌ (`useState`) - ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ก ๋ฐ์ดํฐ ์ ๋ฌ (props callback) 2. `src/components/EducationForm.jsx` ์์ฑ - ์ ๋ ฅ ํ๋: ํ๊ต๋ช , ์ ๊ณต, ๊ธฐ๊ฐ - "+ ํ๋ ฅ ์ถ๊ฐ" ๋ฒํผ์ผ๋ก ์ฌ๋ฌ ํ๋ ฅ ์ ๋ ฅ ๊ฐ๋ฅ (๋ฐฐ์ด state) - ๊ฐ ํญ๋ชฉ ์ญ์ ๋ฒํผ 3. `src/components/ExperienceForm.jsx` ์์ฑ - ์ ๋ ฅ ํ๋: ํ์ฌ๋ช , ์ง์ฑ , ๊ธฐ๊ฐ - "+ ๊ฒฝ๋ ฅ ์ถ๊ฐ" ๋ฒํผ์ผ๋ก ์ฌ๋ฌ ๊ฒฝ๋ ฅ ์ ๋ ฅ ๊ฐ๋ฅ - ๊ฐ ํญ๋ชฉ ์ญ์ ๋ฒํผ 4. `src/components/SkillsForm.jsx` ์์ฑ - ์ ๋ ฅ ํ๋: ๊ธฐ์ ๋ช (ํ๊ทธ ํ์์ผ๋ก ์ ๋ ฅ) - "+ ๊ธฐ์ ์ถ๊ฐ" ๋ฒํผ - ๊ฐ ๊ธฐ์ ์ญ์ ๋ฒํผ (X ์์ด์ฝ) 5. `src/components/ResumePreview.jsx` ์์ฑ - ์ ๋ ฅ๋ ๋ฐ์ดํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ด๋ ฅ์ ํ์์ผ๋ก ๋ ๋๋ง - 2๊ฐ์ง ํ ํ๋ฆฟ ๋์์ธ (simple, modern) - ํ ํ๋ฆฟ ์ ํ ๋ฒํผ 6. `src/pages/ResumeBuilder.jsx` ์ ๋ฐ์ดํธ - ์ ์ฒด ํผ ๋ฐ์ดํฐ๋ฅผ ํ๋์ state๋ก ๊ด๋ฆฌ - ํ์ฌ ๋จ๊ณ์ ๋ฐ๋ผ ์ ์ ํ ํผ ์ปดํฌ๋ํธ ๋ ๋๋ง - "๋ค์" ๋ฒํผ ํด๋ฆญ ์ ๋ค์ ๋จ๊ณ๋ก ์ด๋ - "์ด์ " ๋ฒํผ ํด๋ฆญ ์ ์ด์ ๋จ๊ณ๋ก ์ด๋ - ๋ง์ง๋ง ๋จ๊ณ์์ "์ ์ฅ" ๋ฒํผ์ผ๋ก Laravel API ํธ์ถ 7. ๋ ์ด์์ ๊ตฌ์ฑ - ์ผ์ชฝ 50%: ํผ ์ ๋ ฅ ์์ญ - ์ค๋ฅธ์ชฝ 50%: ์ค์๊ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (๊ณ ์ ) ### ๋์์ธ ๊ฐ์ด๋ - **Simple ํ ํ๋ฆฟ**: ํฐ์ ๋ฐฐ๊ฒฝ, ๊ฒ์์ ํ ์คํธ, ์น์ ๊ตฌ๋ถ์ - **Modern ํ ํ๋ฆฟ**: ์ข์ธก ์ฌ์ด๋๋ฐ(ํ๋์ ๋ฐฐ๊ฒฝ)์ ๊ฐ์ธ์ ๋ณด, ์ฐ์ธก์ ํ๋ ฅ/๊ฒฝ๋ ฅ/๊ธฐ์ - ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ A4 ์ฉ์ง ๋น์จ(210mm x 297mm, ๋น์จ 1:1.414) ์ ์ง - ๋ฐ์ํ: ๋ชจ๋ฐ์ผ์์๋ ํผ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์ธ๋ก๋ก ๋ฐฐ์น ### ์ฒดํฌํฌ์ธํธ - ๊ฐ ๋จ๊ณ์์ ์ ๋ ฅํ ๋ด์ฉ์ด ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ ์ฆ์ ๋ฐ์๋๋์ง ํ์ธ - "๋ค์"/"์ด์ " ๋ฒํผ์ผ๋ก ๋จ๊ณ ์ด๋์ด ์ ์ ์๋ํ๋์ง ํ์ธ - ํ๋ ฅ/๊ฒฝ๋ ฅ/๊ธฐ์ ์ถ๊ฐ/์ญ์ ๊ฐ ์ ์ ์๋ํ๋์ง ํ์ธ - ํ ํ๋ฆฟ ์ ํ ์ ๋์์ธ์ด ๋ณ๊ฒฝ๋๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ค๋จ๊ณ ํผ์ด ์์ฑ๋๊ณ , ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋ฐ์ดํฐ๊ฐ ์ค์๊ฐ์ผ๋ก ์ค๋ฅธ์ชฝ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ ๋ ๋๋ง๋ฉ๋๋ค. 2๊ฐ์ง ํ ํ๋ฆฟ ๋์์ธ ๊ฐ ์ ํ์ด ๊ฐ๋ฅํ๋ฉฐ, ํ๋ ฅ/๊ฒฝ๋ ฅ/๊ธฐ์ ์ ๋์ ์ผ๋ก ์ถ๊ฐ/์ญ์ ํ ์ ์์ต๋๋ค.
## 4๋จ๊ณ: Laravel API ์ฐ๋ ๋ฐ PDF ๋ค์ด๋ก๋ ๊ธฐ๋ฅ ๊ตฌํ
์์ฑํ ์ด๋ ฅ์๋ฅผ Laravel API์ ์ ์ฅํ๊ณ , PDF๋ก ๋ค์ด๋ก๋ํ๋ ๊ธฐ๋ฅ์ ์์ฑํ์ธ์.
### ์๊ตฌ์ฌํญ
1. "์ ์ฅ" ๋ฒํผ ํด๋ฆญ ์ Laravel API ํธ์ถ
- `src/services/api.js`์ `createResume()` ํจ์ ์ฌ์ฉ
- ์ฑ๊ณต ์ ์ด๋ ฅ์ ID๋ฅผ ๋ฐ์์ state์ ์ ์ฅ
- ์ฑ๊ณต ๋ฉ์์ง ํ์ (ํ ์คํธ ์๋ฆผ ๋๋ alert)
2. ์ ์ฅ๋ ์ด๋ ฅ์ ๋ถ๋ฌ์ค๊ธฐ ๊ธฐ๋ฅ
- URL ํ๋ผ๋ฏธํฐ๋ก ์ด๋ ฅ์ ID ๋ฐ๊ธฐ (`?id=123`)
- `useEffect`๋ก ํ์ด์ง ๋ก๋ ์ API ํธ์ถํ์ฌ ๋ฐ์ดํฐ ๋ถ๋ฌ์ค๊ธฐ
- ๋ถ๋ฌ์จ ๋ฐ์ดํฐ๋ฅผ ํผ์ ์ฑ์ฐ๊ธฐ
3. PDF ๋ค์ด๋ก๋ ๊ธฐ๋ฅ ๊ตฌํ
- `jspdf`์ `html2canvas` ์ค์น (`npm install jspdf html2canvas`)
- "PDF ๋ค์ด๋ก๋" ๋ฒํผ ์ถ๊ฐ (๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ๋จ)
- ๋ฒํผ ํด๋ฆญ ์ `ResumePreview` ์ปดํฌ๋ํธ๋ฅผ ์บก์ฒํ์ฌ PDF ์์ฑ
- ํ์ผ๋ช
: `์ด๋ ฅ์_์ด๋ฆ_๋ ์ง.pdf` (์: `์ด๋ ฅ์_ํ๊ธธ๋_20250101.pdf`)
4. ๋ก๋ฉ ์ํ ๋ฐ ์๋ฌ ์ฒ๋ฆฌ
- API ํธ์ถ ์ค ๋ก๋ฉ ์คํผ๋ ํ์
- API ์๋ฌ ๋ฐ์ ์ ์๋ฌ ๋ฉ์์ง ํ์
- PDF ์์ฑ ์ค ๋ก๋ฉ ํ์
5. ์ต์ข
UX ๊ฐ์
- ํผ ์
๋ ฅ ํ๋ ์ ํจ์ฑ ๊ฒ์ฌ (์ด๋ฉ์ผ ํ์, ํ์ ์
๋ ฅ ๋ฑ)
- "๋ค์" ๋ฒํผ ๋นํ์ฑํ (ํ์ ํ๋ ๋ฏธ์
๋ ฅ ์)
- ์ ์ฅ ์ฑ๊ณต ํ "์ ์ด๋ ฅ์ ์์ฑ" ๋ฒํผ ์ถ๊ฐ
### PDF ์์ฑ ๊ฐ์ด๋
```javascript
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
const handleDownloadPDF = async () => {
const element = document.getElementById('resume-preview');
const canvas = await html2canvas(element, { scale: 2 });
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF('p', 'mm', 'a4');
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
pdf.save(`์ด๋ ฅ์_${formData.full_name}_${new Date().toISOString().slice(0, 10)}.pdf`);
};
```
### ์ฒดํฌํฌ์ธํธ
- "์ ์ฅ" ๋ฒํผ ํด๋ฆญ ์ Laravel API์ ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋๋์ง ํ์ธ (SQLite DB ํ์ธ)
- ์ ์ฅ๋ ์ด๋ ฅ์ ID๋ก ํ์ด์ง ์๋ก๊ณ ์นจ ์ ๋ฐ์ดํฐ๊ฐ ๋ถ๋ฌ์์ง๋์ง ํ์ธ
- PDF ๋ค์ด๋ก๋ ๋ฒํผ ํด๋ฆญ ์ ์ด๋ ฅ์๊ฐ PDF ํ์ผ๋ก ๋ค์ด๋ก๋๋๋์ง ํ์ธ
- PDF ํ์ผ์ ์ด์ด์ ๋ด์ฉ์ด ์ ์์ ์ผ๋ก ๋ณด์ด๋์ง ํ์ธ
- ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์๋ํ๋์ง ํ์ธ (์ด๋ฉ์ผ ํ์ ์ค๋ฅ ์ ์๋ฌ ๋ฉ์์ง)
- ๋ก๋ฉ ์ํ๊ฐ ์ ์ ํ ํ์๋๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: ์ด๋ ฅ์ ์์ฑ โ ์ ์ฅ โ ๋ถ๋ฌ์ค๊ธฐ โ PDF ๋ค์ด๋ก๋ ์ ์ฒด ํ๋ก์ฐ๊ฐ ์์ฑ๋ฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์์ฑํ ์ด๋ ฅ์๊ฐ Laravel API์ ์ ์ฅ๋๊ณ , ์ธ์ ๋ ๋ถ๋ฌ์์ ์์ ํ ์ ์์ผ๋ฉฐ, PDF ํ์ผ๋ก ๋ค์ด๋ก๋ํ ์ ์์ต๋๋ค. ์ค์ฉ์ ์ธ ์ด๋ ฅ์ ๋น๋ ์๋น์ค๊ฐ ์์ฑ๋ฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!