- 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>
1363 lines
49 KiB
HTML
1363 lines
49 KiB
HTML
<!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="用一句話高度概括這個崗位存在的價值及其對組織的核心貢獻... 例如:我的使命是透過優質產品解決客戶痛點,創造商業價值"></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>
|