QSDemo 是一個前後端完全分離的企業級問卷管理平台。前端採用 Angular 19 (Standalone Components) 架構,後端整合 Spring Boot 與 MySQL 實現資料持久化。系統支援多權限管理、完整填答流程、歷史紀錄查閱與管理員後台操作。
⚠️ 注意:本 Repository 為前端專案。後端 Spring Boot 專案請見: 👉 quiz_1141121 (後端 Repository)
⚠️ GitHub Pages 為靜態前端展示,部分需要後端 API 的功能(登入、送出填答等)須在本機啟動後端才能正常運作。UI 操作流程與頁面切換可完整體驗。
src/
├── index.html # 應用程式入口 HTML
├── main.ts # Angular 啟動點
├── styles.scss # 全域樣式
│
└── app/
├── app.component.* # 根元件
├── app.config.ts # Angular 應用程式設定(HttpClient、Router 等)
├── app.routes.ts # 全域路由設定
├── survey.service.ts # ★ 核心服務:所有 API 呼叫集中於此
├── http-error.interceptor.ts # HTTP 錯誤攔截器(統一處理 API 異常)
│
└── page/
├── survey-list/ # 📋 問卷列表首頁(搜尋、篩選、登入入口)
├── survey-admin/ # ⚙️ 管理員後台(新增/編輯問卷、題目管理)
├── survey-question/ # ✏️ 填答頁面(題目渲染、作答邏輯)
├── survey-preview/ # 👁️ 填答預覽頁(送出前確認、歷史查閱)
├── survey-result/ # 📈 統計結果頁(各題選項分佈圖表)
├── survey-member/ # 👤 會員中心(個人資料、填答歷史)
└── survey-register/ # 📝 註冊頁面
[首頁:問卷列表]
│
├─ 未登入 ──→ 點「登入/註冊」──→ [登入彈窗] ──→ 登入成功 ──→ 回首頁
│ └─ 新用戶 ──→ [註冊頁]
│
├─ 已登入 ──→ 右上角顯示「您好, OOO」+ 「會員中心」按鈕
│
├─ 搜尋問卷(關鍵字 / 類型 / 狀態 / 日期區間)
│
└─ 點「開始填寫」
│
↓
[填答頁面:survey-question]
- 渲染題目(單選 / 複選 / 開放題)
- 承上題邏輯:父題未答時子題灰化
- 必填題驗證
- 填寫基本資料(依問卷設定顯示姓名/電話/信箱欄位)
│
↓ 點「預覽並送出」
[預覽頁面:survey-preview]
- 顯示所有題目與填入答案
- 點「返回修改」→ 回填答頁(答案保留)
- 點「確認送出」→ 呼叫後端 API → 跳回問卷列表
[首頁] ──→ 點「會員中心」
│
↓
[會員中心:survey-member]
│
├─ 個人資料區
│ ├─ 點「編輯」→ 修改姓名/電話
│ ├─ 點「儲存」→ 呼叫 update_profile API
│ ├─ 修改密碼(需輸入舊密碼驗證)
│ └─ 點頭像 → 選取圖片 → 上傳至後端
│
└─ 填答歷史紀錄區
├─ 支援關鍵字搜尋 + 狀態篩選(已送出/已過期)
├─ 分頁顯示(每頁 4 筆)
├─ 點「檢視」→ 呼叫 feedback API
│ └─→ [預覽頁面(唯讀模式)]:高亮顯示當時答案
└─ 點「刪除」→ 彈窗確認 → 移除該筆紀錄
[首頁] ──→ 點「登入/註冊」──→ 以管理員帳號登入
│
↓ 登入後右上角顯示「問卷管理中心」
│
┌───────────────────┴──────────────────────┐
│ │
[首頁(管理員視角)] [問卷管理中心:survey-admin]
│ │
├─ 批次管理模式 ├─ 新增問卷
│ ├─ 勾選多份問卷 │ ├─ 設定標題、類型、日期
│ └─ 批次刪除 │ ├─ 設定收集欄位(姓名/電話/信箱)
│ │ └─ 新增題目(單選/複選/開放題)
├─ 每份問卷可執行: │
│ ├─「發佈」/ 「取消發佈」 └─ 編輯現有問卷
│ ├─「編輯」→ 進入管理中心 └─ 修改題目、選項、設定
│ ├─「刪除」→ 彈窗確認
│ └─「檢視結果」→ [統計結果頁]
│
└─ [統計結果頁:survey-result]
└─ 顯示各題選項的填答人數與比例
| 路徑 | 元件 | 說明 |
|---|---|---|
/surveys |
SurveyListComponent | 問卷列表首頁 |
/surveys/:id/question |
SurveyQuestionComponent | 填答頁 |
/surveys/:id/preview |
SurveyPreviewComponent | 預覽/歷史查閱頁 |
/surveys/:id/result |
SurveyResultComponent | 統計結果頁 |
/member |
SurveyMemberComponent | 會員中心 |
/admin |
SurveyAdminComponent | 管理員後台 |
/register |
SurveyRegisterComponent | 註冊頁 |
| 方法 | HTTP | 後端路徑 | 功能 |
|---|---|---|---|
getSurveys() |
GET | /quiz/getAll |
取得所有問卷清單 |
getSurveyById(id) |
GET | /quiz/get_questions_List |
取得問卷題目 |
login(email, pwd) |
POST | /quiz/login |
使用者登入 |
register(user) |
POST | /quiz/register |
使用者註冊 |
submitFillin(payload) |
POST | /quiz/fillin |
提交填答 |
getFeedback(quizId, email) |
POST | /quiz/feedback |
取得填答歷史內容 |
getUserHistory(email) |
GET | /quiz/get_history |
取得填答歷史清單 |
updateUserProfile(user) |
POST | /quiz/update_profile |
更新個人資料 |
changePassword(...) |
POST | /quiz/change_password |
修改密碼 |
uploadAvatar(file, email) |
POST | /quiz/upload_avatar |
上傳頭像 |
publishSurvey(id) |
POST | /quiz/publish |
發佈問卷 |
unpublishSurvey(id) |
POST | /quiz/unpublish |
取消發佈 |
createSurvey(payload) |
POST | /quiz/create |
新增問卷 |
updateSurvey(payload) |
POST | /quiz/update |
更新問卷 |
deleteSingleSurvey(id) |
GET | /quiz/delete_single |
單筆刪除 |
deleteBatchSurveys(ids) |
POST | /quiz/delete |
批次刪除 |
填答頁 → 預覽頁 → 後端這條路徑採用 Angular Router state 傳遞資料,不依賴 localStorage,確保頁面重整後資料不殘留:
survey-question ──(router state)──▶ survey-preview ──(HTTP POST)──▶ 後端 /fillin
▲ │
└──────────(router state)──────────────┘
(點「返回修改」時答案保留)
graph TD
A["Angular 19 前端<br/>Standalone Components"] -->|"HTTP / RESTful API"| B["Spring Boot 後端<br/>Controller → Service → DAO"]
B -->|"JPA / Hibernate"| C[("MySQL 資料庫")]
C --> D["quiz 問卷主表<br/>PK: id"]
C --> E["questions 題目表<br/>複合主鍵: quiz_id + question_id"]
C --> F["fillin 填答記錄表<br/>複合主鍵: quiz_id + question_id + email"]
C --> G["user 使用者表<br/>PK: email"]
D -.外鍵.-> E
D -.外鍵.-> F
資料庫設計:複合主鍵(Composite Primary Key)
在設計 questions 與 fillin 這兩張表時,我沒有直接用單一自增 ID 當主鍵,而是設計成複合主鍵:
questions表:PRIMARY KEY (quiz_id, question_id),搭配獨立的QuestionId.java主鍵類別fillin表:PRIMARY KEY (quiz_id, question_id, email),搭配FillinId.java
原因是一個題目必然歸屬於某一份問卷,一筆填答紀錄則必然對應「某問卷的某一題被某位使用者回答」這個唯一組合。用複合主鍵直接在資料庫層就能防止「同一人對同一問卷同一題重複填答」這類異常資料,不需要額外在應用層寫重複檢查的邏輯。
CRUD 資料型態對應的真實踩坑
開發過程中遇到查詢功能一直失敗的問題:使用者查詢自己的填答歷史(/quiz/feedback)時,後端永遠回傳空結果。追查後發現是 FeedbackReq.java 的 getter 方法寫死回傳固定值,導致實際查詢時 quizId 永遠是 0、email 永遠是 null,跟前端實際送出的資料完全對不上——程式碼能編譯、也不會報錯,但邏輯是錯的。這讓我後續在每個 Request/Response 物件寫完後,會逐一驗證 getter/setter 是否真的對應到欄位,而不是只確認能不能編譯過。
| 工具 | 建議版本 |
|---|---|
| Node.js | 20.x 以上 |
| Angular CLI | 19.x |
| MySQL | 8.0 |
| Java | 17 以上(後端用) |
-- 建立資料庫
CREATE DATABASE quiz_1141121;
-- ⚠️ 頭像欄位需為 LONGTEXT,否則 Base64 字串會被截斷
ALTER TABLE user MODIFY COLUMN avatar LONGTEXT;詳細的資料表 Schema 與後端啟動說明請參考: 👉 後端 Repository — quiz_1141121
# Clone 後端專案
git clone https://github.com/AtayalLin/quiz_1141121.git
# 以 IntelliJ IDEA 或 Maven 啟動 Spring Boot
# 預設監聽 http://localhost:8080# Clone 前端專案
git clone https://github.com/AtayalLin/Angular-qsdemo.git
cd Angular-qsdemo
# 安裝依賴
npm install
# 啟動開發伺服器
ng serve瀏覽器開啟 http://localhost:4200 即可。
若後端網址有變動,請修改
src/app/survey.service.ts中的:private readonly API_BASE = 'http://localhost:8080/quiz';
- [Docs] 重構 README,補充完整操作流程、路由說明與 API 對照表
- [Code] 全專案核心元件加入詳細中文技術註解(原理、功能、資料流)
- [Fix] 修正
getDisplayStatus()接受null型別導致的 TS 編譯錯誤 - [Fix] 補全
Surveyinterface 缺少的start_date、end_date、publishStatus欄位 - [Feature] 新增
unpublishSurvey()方法,實作取消發佈功能
- [Feature] 完成前後端 API 串接(登入、填答、歷史紀錄、頭像上傳)
- [Fix] 修正
FeedbackReqgetter 寫死回傳值導致 404 的問題 - [Fix] 修正預覽頁
quizId傳遞遺失的問題 - [UI] 採用 Glassmorphism 毛玻璃特效優化登入彈窗與 Toast 提示
- 問卷列表、填答、管理員後台基本功能完成
- Frontend: Angular 19 (Standalone Components, HttpClient, Router State)
- Backend: Spring Boot 3.x RESTful API(獨立 Repository)
- Database: MySQL 8.x
- Styling: SCSS (Mobile First RWD)
- Icons: FontAwesome 6
© 2026 QSDemo Engineering Team. Developer: AtayalLin. All Rights Reserved.