+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
提示:Prompt 會自動替換以下變數:
- •
{existingData} → 當前表單已填寫的資料(JSON 格式)
- •
{positionName} → 崗位名稱(用於招聘要求頁籤)
+
+
💡 可用變數
+
+ • {existingData} 當前表單已填寫的資料(JSON 格式)
+ • {positionName} 崗位名稱(用於招聘要求頁籤)
+
-
diff --git a/rename_field_ids.py b/rename_field_ids.py
new file mode 100644
index 0000000..14255b3
--- /dev/null
+++ b/rename_field_ids.py
@@ -0,0 +1,255 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+欄位 ID 自動重命名腳本
+根據 ID重命名對照表.md 批量替換 HTML 和 JavaScript 中的欄位 ID
+"""
+
+import re
+import os
+from pathlib import Path
+
+# ID 重命名對照表
+ID_MAPPINGS = {
+ # 模組 1: 崗位基礎資料 - 基礎資料頁籤 (15個)
+ 'businessUnit': 'pos_businessUnit',
+ 'division': 'pos_division',
+ 'department': 'pos_department',
+ 'section': 'pos_section',
+ 'positionCode': 'pos_code',
+ 'effectiveDate': 'pos_effectiveDate',
+ 'positionName': 'pos_name',
+ 'positionLevel': 'pos_level',
+ 'positionCategory': 'pos_category',
+ 'positionCategoryName': 'pos_categoryName',
+ 'positionNature': 'pos_type',
+ 'positionNatureName': 'pos_typeName',
+ 'headcount': 'pos_headcount',
+ 'positionDesc': 'pos_desc',
+ 'positionRemark': 'pos_remark',
+
+ # 模組 2: 崗位基礎資料 - 招聘要求頁籤 (18個)
+ 'minEducation': 'rec_eduLevel',
+ 'requiredGender': 'rec_gender',
+ 'salaryRange': 'rec_salaryRange',
+ 'workExperience': 'rec_expYears',
+ 'minAge': 'rec_minAge',
+ 'maxAge': 'rec_maxAge',
+ 'jobType': 'rec_jobType',
+ 'recruitPosition': 'rec_position',
+ 'jobTitle': 'rec_jobTitle',
+ 'superiorPosition': 'rec_superiorCode',
+ 'jobDesc': 'rec_jobDesc',
+ 'positionReq': 'rec_positionReq',
+ 'titleReq': 'rec_certReq',
+ 'majorReq': 'rec_majorReq',
+ 'skillReq': 'rec_skillReq',
+ 'langReq': 'rec_langReq',
+ 'otherReq': 'rec_otherReq',
+ 'recruitRemark': 'rec_remark',
+
+ # 模組 3: 職務基礎資料 (12個)
+ 'jobCategoryCode': 'job_category',
+ 'jobCategoryName': 'job_categoryName',
+ 'jobCode': 'job_code',
+ 'jobName': 'job_name',
+ 'jobNameEn': 'job_nameEn',
+ 'jobEffectiveDate': 'job_effectiveDate',
+ 'jobLevel': 'job_level',
+ 'jobHeadcount': 'job_headcount',
+ 'jobSortOrder': 'job_sortOrder',
+ 'hasAttendanceBonus': 'job_hasAttBonus',
+ 'hasHousingAllowance': 'job_hasHouseAllow',
+ 'jobRemark': 'job_remark',
+
+ # 模組 4: 部門職責 (19個 - 包含合併重複欄位)
+ 'deptFunctionCode': 'df_code',
+ 'deptFunctionName': 'df_name',
+ 'deptFunctionBU': 'df_businessUnit',
+ 'deptFunc_businessUnit': 'df_businessUnit', # 合併
+ 'deptFunc_division': 'df_division',
+ 'deptFunc_department': 'df_department',
+ 'deptFunc_section': 'df_section',
+ 'deptFunc_positionTitle': 'df_posTitle',
+ 'deptFunc_positionLevel': 'df_posLevel',
+ 'deptManager': 'df_managerTitle',
+ 'deptFunctionEffectiveDate': 'df_effectiveDate',
+ 'deptHeadcount': 'df_headcountLimit',
+ 'deptStatus': 'df_status',
+ 'deptMission': 'df_mission',
+ 'deptVision': 'df_vision',
+ 'deptCoreFunctions': 'df_coreFunc',
+ 'deptKPIs': 'df_kpis',
+ 'deptCollaboration': 'df_collab',
+ 'deptFunctionRemark': 'df_remark',
+
+ # 模組 5: 崗位描述 (8個需要變更的)
+ 'jd_positionCode': 'jd_posCode',
+ 'jd_positionName': 'jd_posName',
+ 'jd_positionLevel': 'jd_posLevel',
+ 'jd_positionEffectiveDate': 'jd_posEffDate',
+ 'jd_directSupervisor': 'jd_supervisor',
+ 'jd_positionGradeJob': 'jd_gradeJob',
+ 'jd_workLocation': 'jd_location',
+ 'jd_empAttribute': 'jd_empAttr',
+ 'jd_deptFunctionCode': 'jd_dfCode',
+ 'jd_positionPurpose': 'jd_purpose',
+ 'jd_mainResponsibilities': 'jd_mainResp',
+ 'jd_education': 'jd_eduLevel',
+ 'jd_basicSkills': 'jd_basicSkills',
+ 'jd_professionalKnowledge': 'jd_proKnowledge',
+ 'jd_workExperienceReq': 'jd_expReq',
+ 'jd_otherRequirements': 'jd_otherReq',
+}
+
+# 需要特殊處理的函數名映射(onchange事件等)
+FUNCTION_MAPPINGS = {
+ 'updateCategoryName': 'updateCategoryName', # 保持不變,但內部需要更新
+ 'updateNatureName': 'updateTypeName', # positionNature -> pos_type
+ 'updateJobCategoryName': 'updateJobCategoryName', # 保持不變
+}
+
+
+def replace_in_file(file_path, dry_run=False):
+ """
+ 在文件中替換所有匹配的 ID
+
+ Args:
+ file_path: 文件路徑
+ dry_run: 如果為 True,只輸出變更不實際修改
+
+ Returns:
+ (總替換次數, 變更詳情列表)
+ """
+ with open(file_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ original_content = content
+ changes = []
+ total_replacements = 0
+
+ # 排序:按舊ID長度降序,避免短ID誤替換長ID
+ sorted_mappings = sorted(ID_MAPPINGS.items(), key=lambda x: len(x[0]), reverse=True)
+
+ for old_id, new_id in sorted_mappings:
+ if old_id == new_id:
+ continue
+
+ # 匹配模式:
+ # 1. HTML id="oldId"
+ # 2. JavaScript getElementById('oldId') 或 getElementById("oldId")
+ # 3. HTML for="oldId"
+ # 4. HTML name="oldId"
+ # 5. 對象屬性 {oldId: ...} 或 data.oldId
+
+ patterns = [
+ # HTML id 屬性
+ (rf'\bid=["\']({re.escape(old_id)})["\']', rf'id="\1"', lambda m: f'id="{new_id}"'),
+ # HTML for 屬性
+ (rf'\bfor=["\']({re.escape(old_id)})["\']', rf'for="\1"', lambda m: f'for="{new_id}"'),
+ # HTML name 屬性
+ (rf'\bname=["\']({re.escape(old_id)})["\']', rf'name="\1"', lambda m: f'name="{new_id}"'),
+ # getElementById
+ (rf'getElementById\(["\']({re.escape(old_id)})["\']', rf'getElementById("\1")', lambda m: f'getElementById("{new_id}")'),
+ # 對象屬性訪問 .oldId (謹慎使用,確保前面是合理的對象)
+ (rf'\.({re.escape(old_id)})\b', rf'.\1', lambda m: f'.{new_id}'),
+ # 對象字面量屬性 oldId: 或 "oldId":
+ (rf'\b({re.escape(old_id)}):', rf'\1:', lambda m: f'{new_id}:'),
+ ]
+
+ for pattern, _, replacement_func in patterns:
+ matches = list(re.finditer(pattern, content))
+ if matches:
+ # 從後往前替換,避免索引偏移
+ for match in reversed(matches):
+ start, end = match.span()
+ old_text = content[start:end]
+ new_text = replacement_func(match)
+
+ if old_text != new_text:
+ content = content[:start] + new_text + content[end:]
+ changes.append({
+ 'old': old_text,
+ 'new': new_text,
+ 'line': content[:start].count('\n') + 1
+ })
+ total_replacements += 1
+
+ # 如果有變更且非 dry run,寫回文件
+ if content != original_content and not dry_run:
+ with open(file_path, 'w', encoding='utf-8', newline='') as f:
+ f.write(content)
+
+ return total_replacements, changes
+
+
+def main():
+ """主函數"""
+ base_dir = Path(__file__).parent
+
+ # 需要處理的文件列表
+ files_to_process = [
+ base_dir / 'index.html',
+ base_dir / 'js' / 'ui.js',
+ base_dir / 'js' / 'ai-bags.js',
+ base_dir / 'js' / 'main.js',
+ ]
+
+ print("=" * 80)
+ print("欄位 ID 重命名工具")
+ print("=" * 80)
+ print(f"\n📋 總計需要重命名:{len(ID_MAPPINGS)} 個 ID")
+ print(f"📂 需要處理:{len(files_to_process)} 個文件\n")
+
+ # 先 dry run 顯示變更
+ print("🔍 掃描變更(Dry Run)...")
+ print("-" * 80)
+
+ total_changes = 0
+ for file_path in files_to_process:
+ if not file_path.exists():
+ print(f"⚠️ 文件不存在:{file_path.name}")
+ continue
+
+ count, changes = replace_in_file(file_path, dry_run=True)
+ total_changes += count
+
+ if count > 0:
+ print(f"\n📄 {file_path.name}: {count} 處變更")
+ # 顯示前 5 個變更示例
+ for i, change in enumerate(changes[:5]):
+ print(f" L{change['line']}: {change['old']} → {change['new']}")
+ if len(changes) > 5:
+ print(f" ... 還有 {len(changes) - 5} 處變更")
+
+ print("\n" + "=" * 80)
+ print(f"📊 總計:{total_changes} 處需要替換")
+ print("=" * 80)
+
+ # 詢問是否執行
+ response = input("\n是否執行替換?(y/n): ").strip().lower()
+
+ if response == 'y':
+ print("\n🚀 開始執行替換...")
+ print("-" * 80)
+
+ for file_path in files_to_process:
+ if not file_path.exists():
+ continue
+
+ count, _ = replace_in_file(file_path, dry_run=False)
+ if count > 0:
+ print(f"✅ {file_path.name}: 已替換 {count} 處")
+
+ print("\n✨ 替換完成!")
+ print("\n⚠️ 請執行以下步驟:")
+ print(" 1. 測試所有表單功能")
+ print(" 2. 檢查瀏覽器控制台是否有錯誤")
+ print(" 3. 使用 git diff 檢查變更")
+ print(" 4. 提交變更:git add -A && git commit -m 'refactor: 標準化欄位 ID 命名'")
+ else:
+ print("\n❌ 已取消執行")
+
+
+if __name__ == '__main__':
+ main()
diff --git a/styles/components.css b/styles/components.css
index cd4eddb..bf56863 100644
--- a/styles/components.css
+++ b/styles/components.css
@@ -269,58 +269,58 @@ select {
.ai-bags-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
- gap: 16px;
- margin-bottom: 24px;
+ gap: 8px;
+ margin-bottom: 16px;
}
.ai-bag {
position: relative;
- padding: 20px;
+ padding: 8px 12px;
background: linear-gradient(135deg, #9b59b6 0%, #8e44ad 100%);
- border-radius: var(--radius);
+ border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
- box-shadow: 0 4px 15px rgba(155, 89, 182, 0.3);
+ box-shadow: 0 2px 8px rgba(155, 89, 182, 0.3);
text-align: center;
}
.ai-bag:hover {
- transform: translateY(-4px);
- box-shadow: 0 6px 20px rgba(155, 89, 182, 0.5);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(155, 89, 182, 0.5);
}
.ai-bag:active {
- transform: translateY(-2px);
+ transform: translateY(-1px);
}
.ai-bag .bag-icon {
- font-size: 2rem;
- margin-bottom: 8px;
+ font-size: 1rem;
+ margin-bottom: 2px;
}
.ai-bag .bag-title {
color: white;
font-weight: 600;
- font-size: 0.95rem;
- margin-bottom: 4px;
+ font-size: 0.75rem;
+ margin-bottom: 2px;
}
.ai-bag .bag-subtitle {
color: rgba(255, 255, 255, 0.8);
- font-size: 0.75rem;
- margin-top: 4px;
+ font-size: 0.65rem;
+ margin-top: 2px;
}
.ai-bag .bag-edit-btn {
position: absolute;
- top: 8px;
- right: 8px;
+ top: 4px;
+ right: 4px;
background: rgba(255, 255, 255, 0.2);
border: none;
- border-radius: 4px;
- padding: 4px 8px;
+ border-radius: 3px;
+ padding: 2px 4px;
cursor: pointer;
- font-size: 0.9rem;
+ font-size: 0.7rem;
transition: all 0.2s ease;
line-height: 1;
}
@@ -336,13 +336,13 @@ select {
}
.ai-bag .spinner {
- width: 20px;
- height: 20px;
+ width: 14px;
+ height: 14px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
- margin: 8px auto;
+ margin: 4px auto;
}
/* ==================== Old AI Button (Deprecated) ==================== */