Files
hr-performance-system/績效管理系統_UI預覽_v1.2_AI輔助.html
donald c24634f4b7 Initial commit: HR Performance System
- Database schema with 31 tables for 4-card system
- LLM API integration (Gemini, DeepSeek, OpenAI)
- Error handling system with modal component
- Connection test UI for LLM services
- Environment configuration files
- Complete database documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 23:34:13 +08:00

1363 lines
49 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>績效管理系統 - 四卡循環 v1.2 (含AI輔助)</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #1e40af;
--primary-light: #3b82f6;
--primary-50: #eff6ff;
--success: #059669;
--success-light: #10b981;
--success-50: #ecfdf5;
--warning: #f59e0b;
--warning-50: #fffbeb;
--danger: #dc2626;
--danger-50: #fef2f2;
--purple: #7c3aed;
--purple-50: #f5f3ff;
--ai-gradient: linear-gradient(135deg, #8b5cf6, #ec4899);
--slate-50: #f8fafc;
--slate-100: #f1f5f9;
--slate-200: #e2e8f0;
--slate-300: #cbd5e1;
--slate-400: #94a3b8;
--slate-500: #64748b;
--slate-600: #475569;
--slate-700: #334155;
--slate-800: #1e293b;
--slate-900: #0f172a;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Noto Sans TC', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--slate-50);
color: var(--slate-800);
line-height: 1.6;
}
/* Header */
.header {
background: linear-gradient(135deg, var(--slate-900), var(--slate-800));
color: white;
padding: 0.875rem 1.5rem;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
max-width: 1440px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo-section {
display: flex;
align-items: center;
gap: 1rem;
}
.logo-icon {
width: 42px;
height: 42px;
background: linear-gradient(135deg, #60a5fa, #3b82f6);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
.logo-text h1 {
font-size: 1.125rem;
font-weight: 700;
}
.logo-text p {
font-size: 0.65rem;
color: var(--slate-400);
}
.header-actions {
display: flex;
align-items: center;
gap: 0.75rem;
}
.lang-switch {
display: flex;
background: var(--slate-700);
border-radius: 8px;
padding: 2px;
font-size: 0.75rem;
}
.lang-btn {
padding: 0.375rem 0.75rem;
border: none;
background: transparent;
color: var(--slate-400);
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
font-weight: 500;
}
.lang-btn.active {
background: var(--primary);
color: white;
}
.user-profile {
display: flex;
align-items: center;
gap: 0.625rem;
padding-left: 0.75rem;
border-left: 1px solid var(--slate-700);
}
.user-avatar {
width: 34px;
height: 34px;
background: linear-gradient(135deg, #fbbf24, #f97316);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.8rem;
}
.user-info .name {
font-weight: 600;
font-size: 0.8rem;
}
.user-info .role {
font-size: 0.65rem;
color: var(--slate-400);
}
/* Main Container */
.main-container {
max-width: 1200px;
margin: 0 auto;
padding: 1.5rem;
}
/* Card Tabs */
.card-tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
background: white;
padding: 0.5rem;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.card-tab {
flex: 1;
padding: 0.875rem 1rem;
border: none;
background: transparent;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--slate-500);
}
.card-tab:hover {
background: var(--slate-50);
}
.card-tab.active {
background: var(--primary);
color: white;
box-shadow: 0 4px 12px rgba(30, 64, 175, 0.3);
}
.card-tab .icon {
font-size: 1.25rem;
}
.card-tab .status {
font-size: 0.65rem;
padding: 0.125rem 0.5rem;
border-radius: 10px;
background: rgba(255,255,255,0.2);
}
.card-tab:not(.active) .status {
background: var(--slate-100);
}
/* Main Panel */
.main-panel {
background: white;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
border: 1px solid var(--slate-200);
}
.panel-header {
padding: 1.25rem 1.5rem;
background: linear-gradient(135deg, var(--slate-50), white);
border-bottom: 1px solid var(--slate-200);
display: flex;
justify-content: space-between;
align-items: center;
}
.panel-title-section {
display: flex;
align-items: center;
gap: 0.875rem;
}
.panel-icon {
width: 46px;
height: 46px;
background: var(--primary-50);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.375rem;
}
.panel-title h2 {
font-size: 1.2rem;
font-weight: 700;
color: var(--slate-800);
}
.panel-title p {
font-size: 0.75rem;
color: var(--slate-500);
}
/* Help Me Button */
.help-me-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.25rem;
background: var(--ai-gradient);
color: white;
border: none;
border-radius: 12px;
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 15px rgba(139, 92, 246, 0.4);
position: relative;
overflow: hidden;
}
.help-me-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
transition: left 0.5s;
}
.help-me-btn:hover::before {
left: 100%;
}
.help-me-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(139, 92, 246, 0.5);
}
.help-me-btn.loading {
pointer-events: none;
opacity: 0.8;
}
.help-me-btn .sparkle {
font-size: 1.1rem;
}
.help-me-btn .spinner {
display: none;
width: 18px;
height: 18px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.help-me-btn.loading .spinner {
display: block;
}
.help-me-btn.loading .sparkle {
display: none;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Content Area */
.content-area {
padding: 1.5rem;
}
/* Form Section */
.form-section {
margin-bottom: 2rem;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 2px solid var(--slate-100);
}
.section-title {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1rem;
font-weight: 600;
color: var(--slate-800);
}
/* Form Groups */
.form-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-bottom: 1rem;
}
.form-row.single {
grid-template-columns: 1fr;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.form-label {
font-size: 0.8rem;
font-weight: 500;
color: var(--slate-600);
display: flex;
align-items: center;
gap: 0.25rem;
}
.form-label .required {
color: var(--danger);
}
.form-input, .form-textarea, .form-select {
padding: 0.75rem 1rem;
border: 1.5px solid var(--slate-200);
border-radius: 10px;
font-size: 0.875rem;
transition: all 0.2s;
font-family: inherit;
}
.form-input:focus, .form-textarea:focus, .form-select:focus {
outline: none;
border-color: var(--primary-light);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.form-textarea {
min-height: 100px;
resize: vertical;
}
.form-input.ai-filled, .form-textarea.ai-filled {
border-color: #a855f7;
background: linear-gradient(135deg, rgba(168, 85, 247, 0.05), rgba(236, 72, 153, 0.05));
animation: aiGlow 2s ease-out;
}
@keyframes aiGlow {
0% {
box-shadow: 0 0 0 0 rgba(168, 85, 247, 0.4);
}
50% {
box-shadow: 0 0 20px 5px rgba(168, 85, 247, 0.2);
}
100% {
box-shadow: 0 0 0 0 rgba(168, 85, 247, 0);
}
}
/* KRA Section */
.kra-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.kra-item {
background: var(--slate-50);
border: 1px solid var(--slate-200);
border-radius: 12px;
padding: 1.25rem;
}
.kra-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.kra-badge {
font-size: 0.7rem;
font-weight: 600;
background: var(--primary-50);
color: var(--primary);
padding: 0.25rem 0.625rem;
border-radius: 6px;
}
.kra-weight {
display: flex;
align-items: center;
gap: 0.5rem;
}
.kra-weight input {
width: 60px;
padding: 0.375rem 0.5rem;
border: 1.5px solid var(--slate-200);
border-radius: 6px;
text-align: center;
font-size: 0.875rem;
}
/* Responsibility List */
.responsibility-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.responsibility-item {
display: flex;
align-items: center;
gap: 0.5rem;
}
.responsibility-item .num {
width: 24px;
height: 24px;
background: var(--primary-50);
color: var(--primary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: 600;
flex-shrink: 0;
}
.responsibility-item input {
flex: 1;
padding: 0.625rem 0.875rem;
border: 1.5px solid var(--slate-200);
border-radius: 8px;
font-size: 0.875rem;
}
.responsibility-item input:focus {
outline: none;
border-color: var(--primary-light);
}
.responsibility-item input.ai-filled {
border-color: #a855f7;
background: linear-gradient(135deg, rgba(168, 85, 247, 0.05), rgba(236, 72, 153, 0.05));
animation: aiGlow 2s ease-out;
}
.add-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
padding: 0.625rem;
border: 2px dashed var(--slate-300);
border-radius: 8px;
background: transparent;
color: var(--slate-500);
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
margin-top: 0.5rem;
}
.add-btn:hover {
border-color: var(--primary-light);
color: var(--primary);
background: var(--primary-50);
}
/* Footer */
.panel-footer {
padding: 1rem 1.5rem;
background: var(--slate-50);
border-top: 1px solid var(--slate-200);
display: flex;
justify-content: space-between;
align-items: center;
}
.btn {
padding: 0.75rem 1.5rem;
border-radius: 10px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: none;
display: inline-flex;
align-items: center;
gap: 0.375rem;
}
.btn-ghost {
background: transparent;
color: var(--slate-600);
}
.btn-ghost:hover {
background: var(--slate-200);
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
box-shadow: 0 4px 12px rgba(30, 64, 175, 0.3);
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(30, 64, 175, 0.35);
}
.btn-group {
display: flex;
gap: 0.75rem;
}
/* AI Toast Notification */
.ai-toast {
position: fixed;
bottom: 2rem;
right: 2rem;
background: white;
border-radius: 16px;
box-shadow: 0 10px 40px rgba(0,0,0,0.15);
padding: 1.25rem 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
z-index: 1000;
transform: translateY(150%);
opacity: 0;
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
border-left: 4px solid;
border-image: var(--ai-gradient) 1;
max-width: 400px;
}
.ai-toast.show {
transform: translateY(0);
opacity: 1;
}
.ai-toast .icon {
width: 48px;
height: 48px;
background: var(--ai-gradient);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
flex-shrink: 0;
}
.ai-toast .content h4 {
font-size: 0.9rem;
font-weight: 600;
color: var(--slate-800);
margin-bottom: 0.25rem;
}
.ai-toast .content p {
font-size: 0.8rem;
color: var(--slate-500);
}
.ai-toast .close-btn {
position: absolute;
top: 0.5rem;
right: 0.5rem;
background: none;
border: none;
color: var(--slate-400);
cursor: pointer;
font-size: 1.25rem;
line-height: 1;
}
/* AI Processing Overlay */
.ai-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.6);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.ai-overlay.show {
opacity: 1;
pointer-events: all;
}
.ai-processing-card {
background: white;
border-radius: 20px;
padding: 2.5rem 3rem;
text-align: center;
box-shadow: 0 20px 60px rgba(0,0,0,0.2);
}
.ai-processing-card .icon {
width: 80px;
height: 80px;
background: var(--ai-gradient);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
margin: 0 auto 1.5rem;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.ai-processing-card h3 {
font-size: 1.25rem;
font-weight: 700;
color: var(--slate-800);
margin-bottom: 0.5rem;
}
.ai-processing-card p {
font-size: 0.9rem;
color: var(--slate-500);
margin-bottom: 1.5rem;
}
.progress-bar {
width: 200px;
height: 6px;
background: var(--slate-200);
border-radius: 3px;
margin: 0 auto;
overflow: hidden;
}
.progress-bar .fill {
height: 100%;
background: var(--ai-gradient);
border-radius: 3px;
animation: progress 2s ease-in-out infinite;
}
@keyframes progress {
0% { width: 0%; margin-left: 0; }
50% { width: 60%; margin-left: 20%; }
100% { width: 0%; margin-left: 100%; }
}
/* Responsive */
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
}
.card-tabs {
flex-wrap: wrap;
}
.card-tab {
flex: 1 1 45%;
}
.panel-header {
flex-direction: column;
gap: 1rem;
}
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="header-content">
<div class="logo-section">
<div class="logo-icon">🏆</div>
<div class="logo-text">
<h1>績效管理系統</h1>
<p>Performance Management System</p>
</div>
</div>
<div class="header-actions">
<div class="lang-switch">
<button class="lang-btn active">繁中</button>
<button class="lang-btn">EN</button>
</div>
<div class="user-profile">
<div class="user-avatar"></div>
<div class="user-info">
<div class="name">王小明</div>
<div class="role">產品部 PM</div>
</div>
</div>
</div>
</div>
</header>
<!-- Main Container -->
<div class="main-container">
<!-- Card Tabs -->
<div class="card-tabs">
<button class="card-tab active" onclick="switchCard('role')">
<span class="icon">👤</span>
<span>角色卡</span>
<span class="status">編輯中</span>
</button>
<button class="card-tab" onclick="switchCard('competency')">
<span class="icon">🎯</span>
<span>能力卡</span>
<span class="status">待填寫</span>
</button>
<button class="card-tab" onclick="switchCard('performance')">
<span class="icon">📊</span>
<span>績效卡</span>
<span class="status">待開始</span>
</button>
<button class="card-tab" onclick="switchCard('growth')">
<span class="icon">🌱</span>
<span>成長卡</span>
<span class="status">待開始</span>
</button>
</div>
<!-- Main Panel -->
<main class="main-panel">
<div class="panel-header">
<div class="panel-title-section">
<div class="panel-icon">👤</div>
<div class="panel-title">
<h2>角色卡 - 產品經理</h2>
<p>Role Card - Product Manager</p>
</div>
</div>
<!-- ✨ Help Me Button -->
<button class="help-me-btn" onclick="helpMeWithAI()">
<span class="sparkle"></span>
<div class="spinner"></div>
<span>Help Me</span>
</button>
</div>
<!-- Content -->
<div class="content-area">
<!-- Section 1: Basic Info -->
<div class="form-section">
<div class="section-header">
<h3 class="section-title">
📋 一、核心標識信息 (Basic Identification)
</h3>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">部門名稱 <span class="required">*</span></label>
<select class="form-select" id="department">
<option value="">請選擇部門</option>
<option value="product" selected>產品部</option>
<option value="rd">研發部</option>
<option value="marketing">行銷部</option>
</select>
</div>
<div class="form-group">
<label class="form-label">角色名稱 <span class="required">*</span></label>
<input type="text" class="form-input" id="roleName" value="產品經理" placeholder="請輸入角色名稱">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">職級 <span class="required">*</span></label>
<select class="form-select" id="level">
<option value="">請選擇職級</option>
<option value="P1">P1 初級</option>
<option value="P2">P2 中級</option>
<option value="P3" selected>P3 資深</option>
<option value="P4">P4 專家</option>
</select>
</div>
<div class="form-group">
<label class="form-label">姓名</label>
<input type="text" class="form-input" id="name" value="王小明" placeholder="請輸入姓名">
</div>
</div>
</div>
<!-- Section 2: Mission -->
<div class="form-section">
<div class="section-header">
<h3 class="section-title">
🎯 二、角色使命 (Role Mission)
</h3>
</div>
<div class="form-row single">
<div class="form-group">
<label class="form-label">使命描述 <span class="required">*</span></label>
<textarea class="form-textarea" id="mission" placeholder="用一句話高度概括這個崗位存在的價值及其對組織的核心貢獻...&#10;例如:我的使命是透過優質產品解決客戶痛點,創造商業價值"></textarea>
</div>
</div>
</div>
<!-- Section 3: Responsibilities -->
<div class="form-section">
<div class="section-header">
<h3 class="section-title">
📝 三、核心職責 (Key Responsibilities)
</h3>
<span style="font-size: 0.75rem; color: var(--slate-500);">建議 4-8 項,動詞開頭</span>
</div>
<div class="responsibility-list" id="responsibilityList">
<div class="responsibility-item">
<span class="num">1</span>
<input type="text" placeholder="例如:規劃產品路線圖與版本迭代策略" id="resp1">
</div>
<div class="responsibility-item">
<span class="num">2</span>
<input type="text" placeholder="例如:收集並分析用戶需求與市場趨勢" id="resp2">
</div>
<div class="responsibility-item">
<span class="num">3</span>
<input type="text" placeholder="例如:協調跨部門資源推動產品開發" id="resp3">
</div>
<div class="responsibility-item">
<span class="num">4</span>
<input type="text" placeholder="請輸入職責..." id="resp4">
</div>
<div class="responsibility-item">
<span class="num">5</span>
<input type="text" placeholder="請輸入職責..." id="resp5">
</div>
</div>
<button class="add-btn" onclick="addResponsibility()">+ 新增職責</button>
</div>
<!-- Section 4: Organizational Linkages -->
<div class="form-section">
<div class="section-header">
<h3 class="section-title">
🔗 四、組織關聯 (Organizational Linkages)
</h3>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">匯報對象 <span class="required">*</span></label>
<input type="text" class="form-input" id="reportTo" placeholder="請輸入直屬主管">
</div>
<div class="form-group">
<label class="form-label">督導對象</label>
<input type="text" class="form-input" id="supervise" placeholder="請輸入您管理的人員">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">關鍵協作夥伴(內部)</label>
<input type="text" class="form-input" id="internalPartners" placeholder="例如:研發部、設計部、行銷部">
</div>
<div class="form-group">
<label class="form-label">主要對外接口</label>
<input type="text" class="form-input" id="externalPartners" placeholder="例如:客戶、供應商、合作夥伴">
</div>
</div>
</div>
<!-- Section 5: KRA/KPI -->
<div class="form-section">
<div class="section-header">
<h3 class="section-title">
📊 五、關鍵成果領域與績效指標 (KRA/KPI)
</h3>
<span style="font-size: 0.75rem; color: var(--slate-500);">權重總和須為 100%</span>
</div>
<div class="kra-list" id="kraList">
<div class="kra-item">
<div class="kra-header">
<span class="kra-badge">KRA 1</span>
<div class="kra-weight">
<span style="font-size: 0.8rem; color: var(--slate-600);">權重:</span>
<input type="number" value="40" min="0" max="100" id="kra1Weight">
<span style="font-size: 0.8rem; color: var(--slate-600);">%</span>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">KRA 名稱 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra1Name" placeholder="例如:產品上市時程">
</div>
<div class="form-group">
<label class="form-label">KPI 指標 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra1Kpi" placeholder="例如:準時上市率">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">衡量方式 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra1Measure" placeholder="例如:實際上市日 vs 計畫日">
</div>
<div class="form-group">
<label class="form-label">目標值 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra1Target" placeholder="例如≥90%">
</div>
</div>
</div>
<div class="kra-item">
<div class="kra-header">
<span class="kra-badge">KRA 2</span>
<div class="kra-weight">
<span style="font-size: 0.8rem; color: var(--slate-600);">權重:</span>
<input type="number" value="30" min="0" max="100" id="kra2Weight">
<span style="font-size: 0.8rem; color: var(--slate-600);">%</span>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">KRA 名稱 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra2Name" placeholder="例如:客戶滿意度">
</div>
<div class="form-group">
<label class="form-label">KPI 指標 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra2Kpi" placeholder="例如NPS 分數">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">衡量方式 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra2Measure" placeholder="例如:季度 NPS 調查">
</div>
<div class="form-group">
<label class="form-label">目標值 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra2Target" placeholder="例如≥45">
</div>
</div>
</div>
<div class="kra-item">
<div class="kra-header">
<span class="kra-badge">KRA 3</span>
<div class="kra-weight">
<span style="font-size: 0.8rem; color: var(--slate-600);">權重:</span>
<input type="number" value="30" min="0" max="100" id="kra3Weight">
<span style="font-size: 0.8rem; color: var(--slate-600);">%</span>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">KRA 名稱 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra3Name" placeholder="請輸入 KRA 名稱">
</div>
<div class="form-group">
<label class="form-label">KPI 指標 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra3Kpi" placeholder="請輸入 KPI 指標">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">衡量方式 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra3Measure" placeholder="請輸入衡量方式">
</div>
<div class="form-group">
<label class="form-label">目標值 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra3Target" placeholder="請輸入目標值">
</div>
</div>
</div>
</div>
<button class="add-btn" onclick="addKRA()">+ 新增 KRA</button>
</div>
</div>
<!-- Footer -->
<div class="panel-footer">
<button class="btn btn-ghost">💾 暫存草稿</button>
<div class="btn-group">
<button class="btn btn-primary" onclick="submitForApproval()">送出審批 →</button>
</div>
</div>
</main>
</div>
<!-- AI Toast Notification -->
<div class="ai-toast" id="aiToast">
<button class="close-btn" onclick="hideToast()">×</button>
<div class="icon"></div>
<div class="content">
<h4>AI 已為您補充 <span id="filledCount">5</span> 個欄位</h4>
<p>紫色標記的欄位為 AI 建議內容,請確認後調整</p>
</div>
</div>
<!-- AI Processing Overlay -->
<div class="ai-overlay" id="aiOverlay">
<div class="ai-processing-card">
<div class="icon">🤖</div>
<h3>AI 正在分析您的資料...</h3>
<p>根據已填寫內容智慧補充空白欄位</p>
<div class="progress-bar">
<div class="fill"></div>
</div>
</div>
</div>
<script>
// AI 補充資料庫 - 根據角色和部門智慧生成內容
const aiSuggestions = {
mission: "我的使命是透過深入理解市場需求與用戶痛點,規劃並推動創新產品開發,為客戶創造價值並達成公司商業目標。",
responsibilities: [
"規劃產品路線圖與版本迭代策略,確保產品發展方向符合市場需求",
"收集並分析用戶需求與市場趨勢,轉化為可執行的產品規格",
"協調研發、設計、行銷等跨部門資源,推動產品如期上線",
"監控產品上線後的用戶反饋與數據指標,持續優化產品體驗",
"管理產品生命週期,制定產品定價與上下架策略"
],
reportTo: "產品總監",
supervise: "產品助理 x 2",
internalPartners: "研發部、UI/UX 設計部、行銷部、客服部",
externalPartners: "合作廠商、重要客戶、產業顧問",
kra1: {
name: "產品準時上市",
kpi: "專案準時交付率",
measure: "實際上線日期 vs 計畫上線日期,計算準時交付比例",
target: "≥ 90%"
},
kra2: {
name: "客戶滿意度提升",
kpi: "NPS (淨推薦值)",
measure: "每季進行用戶 NPS 調查,計算季度平均分數",
target: "≥ 45 分"
},
kra3: {
name: "產品營收達成",
kpi: "產品線營收達成率",
measure: "實際營收 / 目標營收 × 100%",
target: "≥ 100%"
}
};
// Help Me 主函數
function helpMeWithAI() {
const btn = document.querySelector('.help-me-btn');
const overlay = document.getElementById('aiOverlay');
// 顯示處理中狀態
btn.classList.add('loading');
overlay.classList.add('show');
// 模擬 AI 處理時間
setTimeout(() => {
const filledCount = fillEmptyFields();
// 隱藏處理中狀態
overlay.classList.remove('show');
btn.classList.remove('loading');
// 顯示結果通知
if (filledCount > 0) {
showToast(filledCount);
} else {
alert('所有欄位皆已填寫完成!');
}
}, 2000);
}
// 檢查並填充空白欄位
function fillEmptyFields() {
let filledCount = 0;
// 1. 檢查角色使命
const missionField = document.getElementById('mission');
if (!missionField.value.trim()) {
missionField.value = aiSuggestions.mission;
missionField.classList.add('ai-filled');
filledCount++;
}
// 2. 檢查核心職責
for (let i = 1; i <= 5; i++) {
const respField = document.getElementById('resp' + i);
if (respField && !respField.value.trim() && aiSuggestions.responsibilities[i-1]) {
respField.value = aiSuggestions.responsibilities[i-1];
respField.classList.add('ai-filled');
filledCount++;
}
}
// 3. 檢查組織關聯
const reportToField = document.getElementById('reportTo');
if (!reportToField.value.trim()) {
reportToField.value = aiSuggestions.reportTo;
reportToField.classList.add('ai-filled');
filledCount++;
}
const superviseField = document.getElementById('supervise');
if (!superviseField.value.trim()) {
superviseField.value = aiSuggestions.supervise;
superviseField.classList.add('ai-filled');
filledCount++;
}
const internalField = document.getElementById('internalPartners');
if (!internalField.value.trim()) {
internalField.value = aiSuggestions.internalPartners;
internalField.classList.add('ai-filled');
filledCount++;
}
const externalField = document.getElementById('externalPartners');
if (!externalField.value.trim()) {
externalField.value = aiSuggestions.externalPartners;
externalField.classList.add('ai-filled');
filledCount++;
}
// 4. 檢查 KRA 1
const kra1Fields = ['kra1Name', 'kra1Kpi', 'kra1Measure', 'kra1Target'];
const kra1Values = [aiSuggestions.kra1.name, aiSuggestions.kra1.kpi, aiSuggestions.kra1.measure, aiSuggestions.kra1.target];
kra1Fields.forEach((fieldId, index) => {
const field = document.getElementById(fieldId);
if (field && !field.value.trim()) {
field.value = kra1Values[index];
field.classList.add('ai-filled');
filledCount++;
}
});
// 5. 檢查 KRA 2
const kra2Fields = ['kra2Name', 'kra2Kpi', 'kra2Measure', 'kra2Target'];
const kra2Values = [aiSuggestions.kra2.name, aiSuggestions.kra2.kpi, aiSuggestions.kra2.measure, aiSuggestions.kra2.target];
kra2Fields.forEach((fieldId, index) => {
const field = document.getElementById(fieldId);
if (field && !field.value.trim()) {
field.value = kra2Values[index];
field.classList.add('ai-filled');
filledCount++;
}
});
// 6. 檢查 KRA 3
const kra3Fields = ['kra3Name', 'kra3Kpi', 'kra3Measure', 'kra3Target'];
const kra3Values = [aiSuggestions.kra3.name, aiSuggestions.kra3.kpi, aiSuggestions.kra3.measure, aiSuggestions.kra3.target];
kra3Fields.forEach((fieldId, index) => {
const field = document.getElementById(fieldId);
if (field && !field.value.trim()) {
field.value = kra3Values[index];
field.classList.add('ai-filled');
filledCount++;
}
});
return filledCount;
}
// 顯示 Toast 通知
function showToast(count) {
const toast = document.getElementById('aiToast');
document.getElementById('filledCount').textContent = count;
toast.classList.add('show');
// 5秒後自動隱藏
setTimeout(() => {
hideToast();
}, 5000);
}
// 隱藏 Toast 通知
function hideToast() {
const toast = document.getElementById('aiToast');
toast.classList.remove('show');
}
// 切換卡片
function switchCard(cardType) {
document.querySelectorAll('.card-tab').forEach(tab => {
tab.classList.remove('active');
});
event.target.closest('.card-tab').classList.add('active');
const cardNames = {
'role': '角色卡',
'competency': '能力卡',
'performance': '績效卡',
'growth': '成長卡'
};
const panelTitle = document.querySelector('.panel-title h2');
const panelIcon = document.querySelector('.panel-icon');
const icons = {
'role': '👤',
'competency': '🎯',
'performance': '📊',
'growth': '🌱'
};
panelTitle.textContent = cardNames[cardType] + ' - 產品經理';
panelIcon.textContent = icons[cardType];
}
// 新增職責
let respCount = 5;
function addResponsibility() {
respCount++;
const list = document.getElementById('responsibilityList');
const newItem = document.createElement('div');
newItem.className = 'responsibility-item';
newItem.innerHTML = `
<span class="num">${respCount}</span>
<input type="text" placeholder="請輸入職責..." id="resp${respCount}">
`;
list.appendChild(newItem);
}
// 新增 KRA
let kraCount = 3;
function addKRA() {
kraCount++;
const list = document.getElementById('kraList');
const newKRA = document.createElement('div');
newKRA.className = 'kra-item';
newKRA.innerHTML = `
<div class="kra-header">
<span class="kra-badge">KRA ${kraCount}</span>
<div class="kra-weight">
<span style="font-size: 0.8rem; color: var(--slate-600);">權重:</span>
<input type="number" value="0" min="0" max="100" id="kra${kraCount}Weight">
<span style="font-size: 0.8rem; color: var(--slate-600);">%</span>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">KRA 名稱 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra${kraCount}Name" placeholder="請輸入 KRA 名稱">
</div>
<div class="form-group">
<label class="form-label">KPI 指標 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra${kraCount}Kpi" placeholder="請輸入 KPI 指標">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">衡量方式 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra${kraCount}Measure" placeholder="請輸入衡量方式">
</div>
<div class="form-group">
<label class="form-label">目標值 <span class="required">*</span></label>
<input type="text" class="form-input" id="kra${kraCount}Target" placeholder="請輸入目標值">
</div>
</div>
`;
list.appendChild(newKRA);
}
// 送出審批
function submitForApproval() {
const mission = document.getElementById('mission').value;
const reportTo = document.getElementById('reportTo').value;
if (!mission || !reportTo) {
if (confirm('尚有必填欄位未完成,是否使用 AI 輔助填寫?')) {
helpMeWithAI();
}
return;
}
alert('已送出審批!您的直屬主管將收到通知。');
}
// 移除 AI 填充樣式當使用者修改時
document.addEventListener('input', function(e) {
if (e.target.classList.contains('ai-filled')) {
e.target.classList.remove('ai-filled');
}
});
</script>
</body>
</html>