薪資幣別切換 Mockup PRD(PM_34)
此文件依
prd-gen訪談結果撰寫,供 backend-api-spec-generator 與前後端實作對齊。
1. 文件資訊
- 文件類型:Mockup 需求規格書(前後端共用視圖)
- 適用對象:前端開發、後端開發、產品、QA
- 最後更新:2026/04/14
- 版本:3.0.0
- 對應 Storybook:
Mockups/Recruitment/EditJobSalaryCurrency - 建立日期:2026/04/10
2. 開發進度與設計來源
開發進度
- Storybook Mockup:✅ 已完成(commit
71e7f32) - 前端 PR #:TBD
- 後端 PR #:TBD
設計來源
- Figma 連結:無
- 既有畫面參照:
edit-job(熱火數碼現行前端畫面)- 既有職缺列表/職缺詳情/職缺搜尋頁
- 既有履歷編輯頁(期望待遇)
3. 模擬頁面摘要(Mockup Summary)
3.1 功能概述
- 功能名稱:薪資幣別切換(職缺 + 履歷)
- 主要使用者角色:
- 企業 HR/招募人員(編輯職缺薪資)
- 求職者(編輯履歷期望待遇、使用搜尋篩選)
- 核心目標:
- 解決薪資幣別固定
NTD造成海外市場無法使用的問題 - 支援全球化招募(先以越南市場擴張為主要動機)
- 保證「編輯值、儲存值、顯示值、搜尋條件」幣別一致
- 解決薪資幣別固定
3.2 範圍界定
- PRD 文件策略(同一份文件、三個模組):
- 本需求同時涵蓋
edit-job、job-search、resume-edit三個模組。 - 為避免規則分散與版本不一致,統一收斂於同一份 PRD 管理(single source of truth)。
- 文件內依模組分段描述;實作與驗收可拆分任務,但共用同一套幣別規則與資料定義。
- 本需求同時涵蓋
- 包含範圍:
- 職缺端:
edit-job可編輯薪資幣別,預設值依「公司所在地」 - 職缺端:列表/詳情/搜尋結果顯示幣別一致
- 搜尋端:薪資篩選可選幣別(同幣別篩選)
- 履歷端:期望待遇可編輯薪資幣別(與職缺同一幣別規則)
- 幣別清單:支援 ISO 4217 全量法定貨幣
- 職缺端:
- 排除範圍:
- 不做即時匯率換算
- 不做跨幣別換算比較/排序
- 不做多幣別同時顯示(單筆資料只存單一幣別)
3.3 成功條件
- 企業可成功上架非
NTD幣別職缺 - 職缺與履歷皆可正確儲存與呈現 ISO 幣別代碼對應
- 搜尋可用幣別條件過濾職缺(不做換算)
- 上線後追蹤指標:非 NTD 職缺數量成長
4. 商業規則對齊(Business Rules Alignment)
4.1 本功能使用到的業務規則
| 規則 ID | 規則摘要 | 是否關鍵 | 備註 |
|---|---|---|---|
| BR-008 | 職缺發布條件(必填欄位) | ✅ 是 | standard salary 必填(含金額與幣別) |
| BR-009 | 職缺狀態管理與可見性 | ✅ 是 | 幣別異常時職缺需轉下架 |
| BR-015 | 職缺搜尋顯示條件 | ✅ 是 | 搜尋新增幣別條件(同幣別) |
| BR-NEW-CURRENCY-1 | 幣別預設策略 | ✅ 是 | 預設依公司所在地推導 |
| BR-NEW-CURRENCY-2 | 幣別異常處理 | ✅ 是 | 不支援 code 清空並要求重填 |
4.2 補充說明(新增動作上限/約束)
- 本功能無「新增資源」上限議題;幣別可選範圍為 ISO 4217 全量(不限制 30 種)。
- 儲存層僅存
ISO code(例如TWD、VND),不存匯率資料。
5. 資料實體與欄位(Data Entities & Fields)
5.1 實體列表
| 實體名稱 | 說明 | 來源頁面/區塊 |
|---|---|---|
| CurrencyMaster | ISO 4217 幣別主檔 | 後端設定 / 共用查詢 |
| JobSalaryInfo | 職缺薪資與幣別 | edit-job、職缺顯示、搜尋 |
| ResumeExpectedSalary | 履歷期望待遇與幣別 | 履歷編輯頁 |
5.2 實體欄位定義(TypeScript 介面)
type CurrencyCode = string; // ISO 4217, e.g. "TWD", "VND", "USD"
interface CurrencyMaster {
code: CurrencyCode;
name_en: string;
name_zh?: string;
symbol?: string;
status: "active" | "deprecated";
}
interface JobSalaryInfo {
job_id: string;
salary_mode: "standard" | "custom";
salary_min?: number | null;
salary_max?: number | null;
salary_period?: "hourly" | "monthly" | "yearly" | null;
salary_currency_code?: CurrencyCode | null;
}
interface ResumeExpectedSalary {
resume_id: string;
expected_salary_mode: "standard" | "custom";
expected_salary_min?: number | null;
expected_salary_max?: number | null;
expected_salary_period?: "hourly" | "monthly" | "yearly" | null;
expected_salary_currency_code?: CurrencyCode | null;
}
6. 畫面區塊與資料需求(UI Sections & Data Needs)
6.1 區塊列表
| 區塊 ID | 區塊名稱 | 說明 |
|---|---|---|
| SEC-1 | Edit Job Salary | 職缺薪資欄位可選幣別 |
| SEC-2 | Job List / Job Detail | 顯示幣別與薪資一致 |
| SEC-3 | Job Search Filter | 幣別篩選條件 |
| SEC-4 | Resume Expected Salary | 履歷期望待遇可選幣別 |
6.2 各區塊資料需求
SEC-1 Edit Job Salary
- 初始值:依公司所在地預設幣別
- 儲存值:
salary_currency_code(ISO code) - 驗證:
salary_mode=standard時,薪資金額 + 幣別必填
SEC-2 Job List / Job Detail
- 顯示值:由職缺儲存值回填,不可自行推導
- 異常值:若 code 不存在主檔,不顯示錯誤碼;按異常規則處理(見第 12 節)
SEC-3 Job Search Filter
- 幣別下拉來源:
CurrencyMaster(active) - 僅同幣別篩選,不做換算比較
SEC-4 Resume Expected Salary
- 與職缺同一幣別清單
expected_salary_mode=custom時,薪資金額 + 幣別必填
7. 使用者動作與後端需求(User Actions & Backend Needs)
| 動作 ID | 動作名稱 | 類型 | 需要後端 | 涉及實體 | 說明 |
|---|---|---|---|---|---|
| ACT-1 | 開啟 edit-job | 查詢(Read) | ✅ 是 | JobSalaryInfo, CurrencyMaster | 回傳薪資資料 + 幣別清單 + 預設幣別 |
| ACT-2 | 編輯職缺幣別 | 寫入(Write) | ✅ 是 | JobSalaryInfo | 儲存 ISO code |
| ACT-3 | 搜尋條件選擇希望待遇幣別 | 查詢(Read) | ✅ 是 | CurrencyMaster | Search filter 幣別下拉來源 |
| ACT-4 | 搜尋結果卡片顯示薪資幣別 | 查詢(Read) | ✅ 是 | JobSalaryInfo | Job card 薪資需帶幣別 |
| ACT-5 | Job 頁顯示薪資幣別 | 查詢(Read) | ✅ 是 | JobSalaryInfo | Job detail 薪資需帶幣別 |
| ACT-6 | 編輯履歷期望待遇幣別 | 寫入(Write) | ✅ 是 | ResumeExpectedSalary | custom 模式必填 |
| ACT-7 | 顯示履歷與快照履歷幣別 | 查詢(Read) | ✅ 是 | ResumeExpectedSalary | Resume edit/preview 都需顯示幣別 |
| ACT-8 | 不支援幣別異常處理 | 狀態轉換 | ✅ 是 | JobSalaryInfo | 清空幣別並下架職缺 |
邊界場景(逐一)
- 再次進入編輯頁:沿用既有 unsaved 提示;不新增草稿機制。
- 不支援幣別 code:清為空值,要求重新選擇;若是職缺則轉下架。
- 未登入:可看職缺幣別,不可編輯職缺/履歷幣別。
- 手機/桌機:都必須提供可用幣別選擇與一致驗證。
8. API Hints(提示用,非最終 API 設計)
8.1 資料讀取需求(Read)
-
HINT-GET-1:取得幣別主檔
- 輸入:
{ status?: "active" } - 輸出:
CurrencyMaster[]
- 輸入:
-
HINT-GET-2:取得職缺編輯資料(含幣別)
- 輸入:
{ job_id } - 輸出:
JobSalaryInfo+default_currency_code
- 輸入:
-
HINT-GET-3:取得履歷期望待遇(含幣別)
- 輸入:
{ resume_id } - 輸出:
ResumeExpectedSalary
- 輸入:
-
HINT-GET-4:搜尋職缺條件初始化(希望待遇 filter)
- 輸入:
{ locale?, user_type? } - 輸出:
currency_filter_options: CurrencyMaster[] - 說明:搜尋頁的「希望待遇」幣別下拉需由 API 提供,不寫死前端常數
- 輸入:
-
HINT-GET-5:搜尋職缺列表(卡片)
- 輸入:
{ keyword?, location?, salary_min?, salary_max?, salary_currency_code? } - 輸出:
jobs[].salary_{min,max,period,currency_code} - 說明:當帶入
salary_currency_code時,採同幣別過濾;每張 job card 需顯示該幣別
- 輸入:
-
HINT-GET-6:取得職缺詳情(Job 頁)
- 輸入:
{ job_id } - 輸出:
JobSalaryInfo - 說明:Job 頁薪資顯示必須使用資料內
salary_currency_code,不得前端推導預設幣別
- 輸入:
-
HINT-GET-7:取得履歷快照資料(Preview / Snapshot)
- 輸入:
{ resume_id }或{ snapshot_id } - 輸出:
resume_expected_salary.{ expected_salary_min, expected_salary_max, expected_salary_period, expected_salary_currency_code } - 說明:履歷編輯態與快照態都要顯示幣別,格式規則一致
- 輸入:
8.2 資料寫入需求(Write)
-
HINT-MUTATION-1:更新職缺薪資
- 輸入:
{ job_id, salary_mode, salary_min, salary_max, salary_period, salary_currency_code } - 驗證:
salary_mode=standard時,金額與幣別必填,code 必須存在CurrencyMaster(active|deprecated)策略內
- 輸入:
-
HINT-MUTATION-2:更新履歷期望待遇
- 輸入:
{ resume_id, expected_salary_mode, expected_salary_min, expected_salary_max, expected_salary_period, expected_salary_currency_code } - 驗證:
expected_salary_mode=custom時,金額與幣別必填
- 輸入:
8.3 本次 API 實作範圍(新增確認)
- 除了
edit-job與resume-edit的寫入 API,本次需一併涵蓋以下讀取能力:search job的希望待遇 filter 幣別選擇來源search job結果卡片薪資幣別顯示job詳情頁薪資幣別顯示resume與resume snapshot的期望待遇幣別顯示
9. 導航 / Storybook Map
- Storybook 標題:
Mockups/Recruitment/EditJobSalaryCurrency - Story ID:
mockups-recruitment20-edit-job-salary-currency--default - Stories 路徑:
storybook/src/stories/mockups/recruitment/EditJobSalaryCurrency.stories.ts - Mockup 元件:
storybook/src/mockups/edit-job-salary-currency-demo.vue - Storybook(Publish URL):
https://storybook.wport.me/?path=/docs/mockups-recruitment20-edit-job-salary-currency--docs - 對應 production 路由(示意):
company/jobs/:jobId/editjobs/:jobIdjobs/searchresume/edit
三個模組的具體前端頁面
-
Edit Job Salary
- Route:
company/jobs/:jobId/edit - Source page:
W101-Web/main-web/packages/recruitment/src/views/jobs/edit-job/index.vue - Related components:
W101-Web/main-web/packages/recruitment/src/components/JobManager/JobDescriptionCard.vueW101-Web/main-web/packages/recruitment/src/components/JobManager/RecruitmentConditionsCard.vueW101-Web/main-web/packages/recruitment/src/components/JobManager/ApplicationMethodCard.vue
- Route:
-
Job Search
- Route:
jobs/search - Source page:
W101-Web/main-site/pages/jobs/index.vue - Related components:
W101-Web/main-site/components/SearchFilterCard/index.vueW101-Web/main-site/components/job/JobCard.vue
- Route:
-
Resume Edit (Expected Salary)
- Route:
resume/edit - Source page:
W101-Web/main-web/packages/user/src/components/ResumeSections/JobPreference/index.vue - Related component:
W101-Web/main-web/packages/user/src/components/ResumeSections/JobPreference/preview-mode.vue
- Route:
Mockup 實作摘要
- 類型:existing_increment(既有頁面增量)
- 母版頁:
W101-Web/main-web/packages/recruitment/src/views/jobs/edit-job/index.vue - Delta:在「希望待遇」欄位的
compensation_scheme=standard模式下,原「元」文字位置替換為 ISO 4217 幣別下拉選單(filterable,~130 筆法定貨幣) - 技術手段:Vue
<Teleport>注入到母版元件 DOM 內部,像素級對齊(Playwright 驗證 diff=0px) - 條件顯示:僅
compensation_scheme === 'standard'時可見
10. Flowcharts(User / System / Navigation)
10.1 User Flow(共用子流程:幣別載入與驗證)
flowchart TD
A[使用者進入職缺或履歷編輯頁] --> B[載入幣別主檔與當前資料]
B --> C{是否有既有幣別?}
C -->|有| D[顯示既有幣別]
C -->|無| E[依公司所在地預設幣別]
D --> F[使用者可切換幣別]
E --> F
F --> G{模式檢查}
G -->|standard/job| H[薪資與幣別必填]
G -->|custom/resume| I[薪資與幣別必填]
H --> J[提交儲存]
I --> J
J --> K[成功後顯示於列表/詳情/search/履歷]
10.2 System Flow(前端狀態與資料流)
sequenceDiagram
participant UI as 前端頁面
participant API as Backend API
participant DB as Database
UI->>API: GET /currency-master
API->>DB: query currency master (ISO 4217)
DB-->>API: currency list
API-->>UI: currency list
UI->>API: GET job/resume salary payload
API-->>UI: payload + default_currency_code
UI->>API: PATCH salary payload
API->>API: validate mode + required + currency code
API->>DB: persist salary fields
DB-->>API: ok
API-->>UI: success
10.3 Navigation Flow(入口與狀態約束)
| 入口 | 狀態欄位 | 約束 | 預期行為 |
|---|---|---|---|
| edit-job | salary_mode=standard | salary + currency 必填 | 未填不可上架 |
| resume-edit | expected_salary_mode=custom | expected salary + currency 必填 | 未填不可儲存該段 |
| job-search | selected_currency | 不做換算 | 僅同幣別結果 |
| job-detail/list | currency_code 異常 | code 不支援 | 顯示前觸發資料修正流程 |
flowchart LR
N1[入口: edit-job / resume-edit / search] --> N2[讀取資料]
N2 --> N3{幣別 code 可用?}
N3 -->|是| N4[正常顯示與操作]
N3 -->|否| N5[清空幣別 + 要求重填]
N5 --> N6{是否職缺?}
N6 -->|是| N7[職缺轉下架]
N6 -->|否| N8[履歷停留編輯狀態]
11. Mock Data Schema(Mock 資料結構)
{
"currency_master": [
{ "code": "TWD", "name_en": "New Taiwan Dollar", "status": "active" },
{ "code": "VND", "name_en": "Vietnamese Dong", "status": "active" },
{ "code": "USD", "name_en": "US Dollar", "status": "active" }
],
"job_salary_info": {
"job_id": "job_123",
"salary_mode": "standard",
"salary_min": 45000,
"salary_max": 60000,
"salary_period": "monthly",
"salary_currency_code": "VND"
},
"resume_expected_salary": {
"resume_id": "resume_456",
"expected_salary_mode": "custom",
"expected_salary_min": 30000,
"expected_salary_max": 50000,
"expected_salary_period": "monthly",
"expected_salary_currency_code": "USD"
}
}
12. 實作備註(Implementation Notes)
- 儲存規範:只存 ISO code,不做匯率表與換算欄位。
- 顯示規範:UI 可顯示
NTD,但資料層建議統一TWD(mapping 由前後端共識維護)。 - 預設規則:職缺預設幣別依公司所在地;查無對應時 fallback
TWD。 - 異常規則(已確認):若遇到不支援幣別 code,清空該值並要求使用者重選;職缺自動轉下架避免錯誤揭露。
- 既有 unsaved 流程:沿用現有未儲存離頁提醒,不新增中斷還原機制。
- 資料修正建議:加一支後台修正腳本檢查 invalid code 並標記待補填。
13. 下一步(Next Steps)
Storybook Mockup(SEC-1 edit-job)✅ 已完成- 定稿
CurrencyMaster資料來源(靜態檔或後台可維護表)。 - 先出 schema(你已指定),再進 API 與前端串接。
- 產出 QA 測試矩陣:職缺端、履歷端、搜尋端、異常 code 下架情境。
- 上線後觀測 KPI:非 NTD(非 TWD)職缺數量。
文件結束