diff --git a/static/style.css b/static/style.css index 16fa560..cf94adb 100644 --- a/static/style.css +++ b/static/style.css @@ -1,30 +1,56 @@ /** - * STP模具几何分析中心 - 统一设计系统 - * 优化版本:现代化UI设计,响应式布局 + * STP模具几何分析中心 - 暖白色主题设计系统 + * 版本: 4.0.0 - 现代化暖色系UI */ -/* 设计系统变量 */ +/* ================================ + 设计系统变量 - 暖白色主题 + ================================ */ :root { - /* 颜色系统 */ - --primary-color: #2563eb; - --primary-hover: #1d4ed8; - --secondary-color: #64748b; - --success-color: #10b981; - --warning-color: #f59e0b; - --error-color: #ef4444; - --info-color: #3b82f6; + /* 主色调 - 温暖的琥珀/橙色系 */ + --primary-color: #e67e22; + --primary-hover: #d35400; + --primary-light: #fdebd0; + --primary-dark: #ba4a00; - /* 中性色 */ - --gray-50: #f8fafc; - --gray-100: #f1f5f9; - --gray-200: #e2e8f0; - --gray-300: #cbd5e1; - --gray-400: #94a3b8; - --gray-500: #64748b; - --gray-600: #475569; - --gray-700: #334155; - --gray-800: #1e293b; - --gray-900: #0f172a; + /* 辅助色 */ + --secondary-color: #8e6d4a; + --accent-color: #c0392b; + + /* 状态色 */ + --success-color: #27ae60; + --success-light: #d5f4e6; + --warning-color: #f39c12; + --warning-light: #fef9e7; + --error-color: #e74c3c; + --error-light: #fdedec; + --info-color: #3498db; + --info-light: #ebf5fb; + + /* 暖白色系 */ + --warm-white: #faf8f5; + --cream: #fdf9f3; + --ivory: #fffff0; + --linen: #faf0e6; + --seashell: #fff5ee; + + /* 暖灰色系 */ + --warm-gray-50: #faf9f7; + --warm-gray-100: #f5f3f0; + --warm-gray-200: #e8e4de; + --warm-gray-300: #d4cfc6; + --warm-gray-400: #a69f94; + --warm-gray-500: #7a7267; + --warm-gray-600: #5c564d; + --warm-gray-700: #3d3832; + --warm-gray-800: #2c2824; + --warm-gray-900: #1a1816; + + /* 文字颜色 */ + --text-primary: #3d3832; + --text-secondary: #5c564d; + --text-muted: #7a7267; + --text-light: #a69f94; /* 间距系统 */ --space-xs: 0.25rem; @@ -33,9 +59,10 @@ --space-lg: 1.5rem; --space-xl: 2rem; --space-2xl: 3rem; + --space-3xl: 4rem; /* 字体系统 */ - --font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; + --font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif; --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; @@ -43,22 +70,34 @@ --font-size-xl: 1.25rem; --font-size-2xl: 1.5rem; --font-size-3xl: 1.875rem; + --font-size-4xl: 2.25rem; - /* 阴影系统 */ - --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); - --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1); + /* 阴影系统 - 温暖阴影 */ + --shadow-xs: 0 1px 2px rgba(58, 45, 35, 0.05); + --shadow-sm: 0 2px 4px rgba(58, 45, 35, 0.08); + --shadow-md: 0 4px 12px rgba(58, 45, 35, 0.1); + --shadow-lg: 0 8px 24px rgba(58, 45, 35, 0.12); + --shadow-xl: 0 16px 48px rgba(58, 45, 35, 0.15); + --shadow-warm: 0 4px 20px rgba(230, 126, 34, 0.15); /* 圆角系统 */ --radius-sm: 0.375rem; - --radius-md: 0.5rem; - --radius-lg: 0.75rem; - --radius-xl: 1rem; + --radius-md: 0.625rem; + --radius-lg: 0.875rem; + --radius-xl: 1.25rem; + --radius-2xl: 1.5rem; --radius-full: 9999px; + + /* 过渡动画 */ + --transition-fast: 0.15s ease; + --transition-base: 0.25s ease; + --transition-slow: 0.4s ease; + --transition-bounce: 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55); } -/* 基础重置 */ +/* ================================ + 基础重置与全局样式 + ================================ */ * { margin: 0; padding: 0; @@ -67,83 +106,138 @@ html { scroll-behavior: smooth; + font-size: 16px; } body { font-family: var(--font-family); - background: linear-gradient(135deg, #f0f4f8 0%, #e2e8f0 100%); + background: linear-gradient(135deg, var(--warm-white) 0%, var(--cream) 50%, var(--linen) 100%); + background-attachment: fixed; min-height: 100vh; - padding: var(--space-md); - color: var(--gray-600); - line-height: 1.6; + padding: var(--space-lg); + color: var(--text-primary); + line-height: 1.7; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } -/* 应用外壳 */ +/* 背景装饰 */ +body::before { + content: ''; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: + radial-gradient(circle at 20% 80%, rgba(230, 126, 34, 0.05) 0%, transparent 50%), + radial-gradient(circle at 80% 20%, rgba(243, 156, 18, 0.05) 0%, transparent 50%), + radial-gradient(circle at 40% 40%, rgba(39, 174, 96, 0.03) 0%, transparent 40%); + pointer-events: none; + z-index: -1; +} + +/* ================================ + 应用外壳 + ================================ */ .app-shell { max-width: 1400px; margin: 0 auto; - min-height: calc(100vh - 2 * var(--space-md)); + min-height: calc(100vh - 2 * var(--space-lg)); display: flex; flex-direction: column; + animation: fadeInUp 0.6s ease-out; } -/* 通知系统 */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* ================================ + 通知系统 + ================================ */ .notification-container { position: fixed; - top: var(--space-md); - right: var(--space-md); + top: var(--space-lg); + right: var(--space-lg); z-index: 1000; - max-width: 400px; + max-width: 420px; + display: flex; + flex-direction: column; + gap: var(--space-sm); } .notification { background: white; - border-radius: var(--radius-md); - padding: var(--space-md); - margin-bottom: var(--space-sm); + border-radius: var(--radius-lg); + padding: var(--space-md) var(--space-lg); box-shadow: var(--shadow-lg); border-left: 4px solid var(--info-color); - animation: slideIn 0.3s ease-out; + animation: slideInRight 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55); display: flex; align-items: center; justify-content: space-between; + backdrop-filter: blur(10px); + transition: transform var(--transition-base), box-shadow var(--transition-base); +} + +.notification:hover { + transform: translateX(-4px); + box-shadow: var(--shadow-xl); } .notification-success { border-left-color: var(--success-color); + background: linear-gradient(135deg, white 0%, var(--success-light) 100%); } .notification-error { border-left-color: var(--error-color); + background: linear-gradient(135deg, white 0%, var(--error-light) 100%); } .notification-warning { border-left-color: var(--warning-color); + background: linear-gradient(135deg, white 0%, var(--warning-light) 100%); } .notification-message { flex: 1; - color: var(--gray-700); + color: var(--text-primary); + font-weight: 500; } .notification-close { - background: none; + background: var(--warm-gray-100); border: none; font-size: var(--font-size-lg); - color: var(--gray-400); + color: var(--text-muted); cursor: pointer; padding: var(--space-xs); - margin-left: var(--space-sm); - border-radius: var(--radius-sm); - transition: all 0.2s; + margin-left: var(--space-md); + border-radius: var(--radius-full); + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition-fast); } .notification-close:hover { - background: var(--gray-100); - color: var(--gray-600); + background: var(--error-light); + color: var(--error-color); + transform: rotate(90deg); } -@keyframes slideIn { +@keyframes slideInRight { from { transform: translateX(100%); opacity: 0; @@ -154,88 +248,146 @@ body { } } -/* 应用头部 */ +/* ================================ + 应用头部 + ================================ */ .app-header { - background: white; - border-radius: var(--radius-xl) var(--radius-xl) 0 0; - padding: var(--space-2xl); + background: linear-gradient(135deg, white 0%, var(--cream) 100%); + border-radius: var(--radius-2xl) var(--radius-2xl) 0 0; + padding: var(--space-2xl) var(--space-3xl); box-shadow: var(--shadow-md); margin-bottom: var(--space-lg); + border: 1px solid var(--warm-gray-200); + border-bottom: none; + position: relative; + overflow: hidden; +} + +.app-header::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, var(--primary-color), var(--warning-color), var(--success-color)); } .app-title { display: flex; align-items: center; - gap: var(--space-lg); - margin-bottom: var(--space-lg); + gap: var(--space-xl); + margin-bottom: var(--space-xl); } .logo { - font-size: var(--font-size-3xl); - background: linear-gradient(135deg, var(--primary-color), var(--info-color)); + font-size: var(--font-size-4xl); + background: linear-gradient(135deg, var(--primary-color), var(--warning-color)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; + filter: drop-shadow(0 2px 4px rgba(230, 126, 34, 0.3)); + animation: float 3s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-5px); } } .app-title h1 { font-size: var(--font-size-3xl); - color: var(--gray-700); + color: var(--text-primary); margin-bottom: var(--space-xs); + font-weight: 700; + letter-spacing: -0.02em; } .app-subtitle { - color: var(--gray-600); + color: var(--text-secondary); font-size: var(--font-size-base); + font-weight: 400; } .app-nav { display: flex; - gap: var(--space-md); - border-top: 1px solid var(--gray-200); + gap: var(--space-sm); + border-top: 1px solid var(--warm-gray-200); padding-top: var(--space-lg); } .nav-link { - padding: var(--space-sm) var(--space-md); + padding: var(--space-sm) var(--space-lg); text-decoration: none; - color: var(--gray-600); - border-radius: var(--radius-md); - transition: all 0.2s; + color: var(--text-secondary); + border-radius: var(--radius-full); + transition: all var(--transition-base); font-weight: 500; + display: flex; + align-items: center; + gap: var(--space-sm); + position: relative; + overflow: hidden; +} + +.nav-link::before { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 2px; + background: var(--primary-color); + transition: all var(--transition-base); + transform: translateX(-50%); } .nav-link:hover { color: var(--primary-color); - background: var(--gray-50); + background: var(--primary-light); +} + +.nav-link:hover::before { + width: 60%; } .nav-link.active { - color: var(--primary-color); - background: var(--gray-50); - box-shadow: var(--shadow-sm); + color: white; + background: linear-gradient(135deg, var(--primary-color), var(--primary-hover)); + box-shadow: var(--shadow-warm); } -/* 主要内容区域 */ +.nav-link.active::before { + display: none; +} + +/* ================================ + 主要内容区域 + ================================ */ .app-main { flex: 1; background: white; - border-radius: 0 0 var(--radius-xl) var(--radius-xl); - padding: var(--space-2xl); + border-radius: 0 0 var(--radius-2xl) var(--radius-2xl); + padding: var(--space-2xl) var(--space-3xl); box-shadow: var(--shadow-md); + border: 1px solid var(--warm-gray-200); + border-top: none; } -/* 应用底部 */ +/* ================================ + 应用底部 + ================================ */ .app-footer { - background: var(--gray-200); - color: var(--gray-700); - padding: var(--space-lg); - border-radius: var(--radius-md); + background: linear-gradient(135deg, var(--warm-gray-100) 0%, var(--warm-gray-200) 100%); + color: var(--text-secondary); + padding: var(--space-lg) var(--space-xl); + border-radius: var(--radius-xl); margin-top: var(--space-lg); display: flex; justify-content: space-between; align-items: center; font-size: var(--font-size-sm); + border: 1px solid var(--warm-gray-200); } .footer-left, .footer-right { @@ -246,908 +398,1206 @@ body { .status-success { color: var(--success-color); + font-weight: 600; } .status-error { color: var(--error-color); -} - -.results-section { - padding: 0 40px 40px; -} - -.result-card { - background: #f8f9fa; - border-radius: 10px; - padding: 25px; - margin: 15px 0; - border-left: 5px solid #3498db; - box-shadow: 0 4px 6px rgba(0,0,0,0.1); -} - -.result-card h3 { - color: #2c3e50; - margin-bottom: 20px; - display: flex; - align-items: center; - gap: 10px; - font-size: 1.3em; -} - -.task-info { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 15px; -} - -.geometry-data { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 15px; -} - -.bounding-box-data { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 15px; -} - -.topology-data { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 15px; -} - -.features-data { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 15px; -} - -.recommendations-data { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); - gap: 15px; -} - -.metrics-data { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 15px; -} - -.analysis-info { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 15px; -} - -.data-item { - background: white; - padding: 20px; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - transition: transform 0.2s ease; -} - -.data-item:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); -} - -.data-label { - font-weight: bold; - color: #2c3e50; - margin-bottom: 8px; - font-size: 0.95em; - display: flex; - align-items: center; - gap: 8px; -} - -.data-value { - color: #34495e; - font-family: 'Courier New', monospace; - font-size: 1.1em; - font-weight: 600; -} - -.data-unit { - color: #7f8c8d; - font-size: 0.9em; - margin-left: 4px; -} - -.coordinate-item { - background: linear-gradient(135deg, #e8f4fc, #d1edff); - padding: 15px; - border-radius: 6px; -} - -.coordinate-label { - font-weight: bold; - color: #2980b9; - margin-bottom: 5px; -} - -.coordinate-value { - font-family: 'Courier New', monospace; - color: #2c3e50; -} - -.status-badge { - padding: 6px 16px; - border-radius: 20px; - font-size: 0.9em; - font-weight: bold; - display: inline-block; -} - -.status-processing { - background: #fff3cd; - color: #856404; - border: 1px solid #ffeaa7; -} - -.status-completed { - background: #d1edff; - color: #0c5460; - border: 1px solid #bee5eb; -} - -.status-failed { - background: #f8d7da; - color: #721c24; - border: 1px solid #f5c6cb; -} - -.loading { - display: none; - text-align: center; - padding: 30px; -} - -.spinner { - border: 4px solid #f3f3f3; - border-top: 4px solid #3498db; - border-radius: 50%; - width: 50px; - height: 50px; - animation: spin 1s linear infinite; - margin: 0 auto 20px; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.error-message { - background: #f8d7da; - color: #721c24; - padding: 20px; - border-radius: 8px; - margin: 20px 0; - display: none; - border-left: 5px solid #e74c3c; -} - -.system-info { - background: #2c3e50; - color: white; - padding: 20px; - text-align: center; - margin-top: 20px; - border-radius: 0 0 15px 15px; -} - -/* 特征和建议的特殊样式 */ -.feature-item { - background: linear-gradient(135deg, #e8f4fc, #d1edff); - padding: 20px; - border-radius: 10px; - border-left: 4px solid #3498db; -} - -.recommendation-item { - background: linear-gradient(135deg, #fff3cd, #ffeaa7); - padding: 20px; - border-radius: 10px; - border-left: 4px solid #f39c12; -} - -.recommendation-high { - border-left-color: #e74c3c; - background: linear-gradient(135deg, #f8d7da, #f5c6cb); -} - -.recommendation-medium { - border-left-color: #f39c12; - background: linear-gradient(135deg, #fff3cd, #ffeaa7); -} - -.recommendation-low { - border-left-color: #27ae60; - background: linear-gradient(135deg, #d1edff, #bee5eb); -} - -.feature-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 10px; -} - -.feature-type { - font-weight: bold; - color: #2c3e50; - font-size: 1.1em; -} - -.confidence-badge { - background: #3498db; - color: white; - padding: 4px 12px; - border-radius: 12px; - font-size: 0.9em; -} - -.recommendation-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 10px; -} - -.priority-badge { - padding: 4px 12px; - border-radius: 12px; - font-size: 0.9em; - font-weight: bold; -} - -.priority-high { - background: #e74c3c; - color: white; -} - -.priority-medium { - background: #f39c12; - color: white; -} - -.priority-low { - background: #27ae60; - color: white; -} - -.recommendation-list { - list-style: none; - padding: 0; - margin: 10px 0; -} - -.recommendation-list li { - padding: 8px 0; - border-bottom: 1px solid #eee; - display: flex; - align-items: flex-start; - gap: 8px; -} - -.recommendation-list li:before { - content: "💡"; - font-size: 1.1em; -} - -.recommendation-list li:last-child { - border-bottom: none; -} - -.metric-item { - background: white; - padding: 20px; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - text-align: center; -} - -.metric-value { - font-size: 1.8em; - font-weight: bold; - color: #2c3e50; - margin: 10px 0; -} - -.metric-label { - color: #7f8c8d; - font-size: 0.9em; -} - -.metric-good { - color: #27ae60; -} - -.metric-warning { - color: #f39c12; -} - -.metric-poor { - color: #e74c3c; -} - -/* 响应式设计 */ -@media (max-width: 768px) { - .container { - margin: 10px; - border-radius: 10px; - } - - .header { - padding: 20px; - } - - .header h1 { - font-size: 2em; - } - - .upload-section { - padding: 20px; - } - - .upload-area { - padding: 30px 20px; - } - - .results-section { - padding: 0 20px 20px; - } - - .geometry-data, - .bounding-box-data, - .topology-data, - .features-data, - .recommendations-data, - .metrics-data, - .analysis-info { - grid-template-columns: 1fr; - } -} - -/* ===== 新版 Vue SPA 布局样式 ===== */ - -.app-shell { - max-width: 1280px; - margin: 0 auto; - min-height: calc(100vh - 32px); - background: #0b1120; - border-radius: 18px; - box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5); - display: flex; - flex-direction: column; - overflow: hidden; -} - -.app-header { - padding: 18px 24px; - background: radial-gradient(circle at top left, #1d4ed8, #020617 60%); - border-bottom: 1px solid rgba(148, 163, 184, 0.4); - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; -} - -.app-title { - display: flex; - align-items: center; - gap: 14px; - color: #e5e7eb; -} - -.app-title h1 { - font-size: 1.5rem; - margin-bottom: 2px; -} - -.app-title p { - font-size: 0.9rem; - color: #9ca3af; -} - -.logo { - width: 42px; - height: 42px; - border-radius: 999px; - display: inline-flex; - align-items: center; - justify-content: center; - background: radial-gradient(circle at 30% 20%, #f59e0b, #ef4444 60%); - font-size: 1.4rem; -} - -.app-nav { - display: flex; - gap: 10px; - align-items: center; -} - -.nav-link { - padding: 8px 16px; - border-radius: 999px; - text-decoration: none; - color: #e5e7eb; - font-size: 0.9rem; - border: 1px solid transparent; - transition: all 0.2s ease; -} - -.nav-link:hover { - border-color: rgba(148, 163, 184, 0.7); - background: rgba(15, 23, 42, 0.7); -} - -.nav-link.active { - background: linear-gradient(135deg, #3b82f6, #6366f1); - color: white; - border-color: transparent; -} - -.app-main { - flex: 1; - padding: 20px 24px 18px; - background: - radial-gradient(circle at top left, rgba(59, 130, 246, 0.25), transparent 55%), - radial-gradient(circle at bottom right, rgba(236, 72, 153, 0.18), transparent 55%), - #020617; - color: #e5e7eb; - overflow-y: auto; -} - -.app-footer { - padding: 10px 18px; - font-size: 0.8rem; - color: #9ca3af; - background: #020617; - border-top: 1px solid rgba(148, 163, 184, 0.3); - display: flex; - justify-content: space-between; - align-items: center; -} - -.footer-left span + span { - margin-left: 6px; -} - -.dashboard, -.history-view, -.result-view { - display: flex; - flex-direction: column; - gap: 18px; - max-width: 1040px; - margin: 0 auto; -} - -.dashboard h2, -.history-view h2, -.result-view h2 { - font-size: 1.3rem; - margin-bottom: 4px; -} - -.card-subtitle { - color: #9ca3af; - font-size: 0.9rem; -} - -.grid-2 { - display: grid; - grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); - gap: 20px; -} - -.grid-3 { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 18px; - margin-top: 12px; -} - -.card { - background: - radial-gradient(circle at top left, rgba(30, 64, 175, 0.22), transparent 60%), - radial-gradient(circle at bottom right, rgba(8, 47, 73, 0.36), transparent 55%), - #020617; - border-radius: 16px; - padding: 16px 18px; - border: 1px solid rgba(148, 163, 184, 0.32); - box-shadow: 0 18px 40px rgba(15, 23, 42, 0.85); -} - -.card h2, -.card h3 { - font-size: 1.05rem; - margin-bottom: 8px; - color: #e5e7eb; -} - -.upload-panel { - margin-top: 10px; - display: flex; - flex-direction: column; - gap: 10px; -} - -.upload-dropzone { - border-radius: 14px; - border: 1px dashed rgba(148, 163, 184, 0.6); - padding: 26px 18px; - text-align: center; - background: linear-gradient(135deg, rgba(15, 23, 42, 0.9), rgba(30, 64, 175, 0.4)); - cursor: pointer; - transition: all 0.2s ease; - color: #e5e7eb; -} - -.upload-dropzone:hover, -.upload-dropzone.drag-over { - border-color: #60a5fa; - background: linear-gradient(135deg, rgba(15, 23, 42, 1), rgba(37, 99, 235, 0.6)); + font-weight: 600; +} + +/* ================================ + 上传区域 + ================================ */ +.upload-section { + margin-bottom: var(--space-2xl); +} + +.upload-area { + border: 2px dashed var(--warm-gray-300); + border-radius: var(--radius-xl); + padding: var(--space-3xl); + text-align: center; + transition: all var(--transition-base); + background: linear-gradient(135deg, var(--warm-gray-50) 0%, white 100%); + cursor: pointer; + position: relative; + overflow: hidden; +} + +.upload-area::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, var(--primary-light) 0%, transparent 100%); + opacity: 0; + transition: opacity var(--transition-base); +} + +.upload-area:hover, +.upload-area.drag-over { + border-color: var(--primary-color); + background: var(--cream); + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.upload-area:hover::before, +.upload-area.drag-over::before { + opacity: 1; } .upload-icon { - font-size: 2.3rem; - margin-bottom: 6px; + font-size: 4rem; + margin-bottom: var(--space-lg); + display: block; + animation: bounce 2s ease-in-out infinite; } -.clear-file-btn { - margin-top: 8px; - padding: 4px 12px; - font-size: 0.8rem; - background: rgba(239, 68, 68, 0.2); - color: #fca5a5; - border: 1px solid rgba(239, 68, 68, 0.4); - border-radius: 6px; - cursor: pointer; - transition: all 0.2s ease; +@keyframes bounce { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } } -.clear-file-btn:hover { - background: rgba(239, 68, 68, 0.3); - border-color: rgba(239, 68, 68, 0.6); +.upload-area h3 { + color: var(--text-primary); + font-size: var(--font-size-xl); + margin-bottom: var(--space-sm); } -.upload-btn { - background: linear-gradient(135deg, #3b82f6, #6366f1); - color: white; - border: none; - padding: 10px 18px; - font-size: 0.95rem; - border-radius: 999px; - cursor: pointer; - transition: all 0.2s ease; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; +.upload-area p { + color: var(--text-muted); + margin-bottom: var(--space-md); } -.upload-btn:hover:not(:disabled) { - transform: translateY(-1px); - box-shadow: 0 10px 25px rgba(37, 99, 235, 0.4); +.file-input { + display: none; } -.upload-btn:disabled { - background: #4b5563; - cursor: not-allowed; - box-shadow: none; +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); + padding: var(--space-sm) var(--space-lg); + border-radius: var(--radius-full); + font-weight: 600; + font-size: var(--font-size-sm); + cursor: pointer; + transition: all var(--transition-base); + border: none; + text-decoration: none; } -.error-text { - color: #fecaca; - font-size: 0.85rem; +.btn-primary { + background: linear-gradient(135deg, var(--primary-color), var(--primary-hover)); + color: white; + box-shadow: var(--shadow-sm), 0 4px 12px rgba(230, 126, 34, 0.3); } -.current-task { - margin-top: 10px; - border-radius: 14px; - border: 1px solid rgba(148, 163, 184, 0.4); - background: radial-gradient(circle at top left, #020617, #020617 70%); +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md), 0 6px 20px rgba(230, 126, 34, 0.4); } -.info-row { - display: flex; - justify-content: space-between; - font-size: 0.9rem; - color: #e5e7eb; +.btn-primary:active { + transform: translateY(0); } -.info-row + .info-row { - margin-top: 4px; +.btn-secondary { + background: white; + color: var(--text-primary); + border: 2px solid var(--warm-gray-300); } -.small-hint { - margin-top: 6px; - font-size: 0.8rem; - color: #9ca3af; +.btn-secondary:hover { + border-color: var(--primary-color); + color: var(--primary-color); + background: var(--primary-light); } -.summary-card { - display: flex; - flex-direction: column; - gap: 10px; +.btn-success { + background: linear-gradient(135deg, var(--success-color), #219a52); + color: white; + box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3); } -.summary-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 12px; - margin-top: 4px; +.btn-success:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4); } -.summary-item { - background: rgba(15, 23, 42, 0.92); - border-radius: 12px; - padding: 8px 10px; - border: 1px solid rgba(148, 163, 184, 0.4); +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none !important; } -.summary-label { - font-size: 0.85rem; - color: #9ca3af; - margin-bottom: 2px; +/* ================================ + 文件信息卡片 + ================================ */ +.file-info { + background: linear-gradient(135deg, var(--primary-light) 0%, white 100%); + border-radius: var(--radius-lg); + padding: var(--space-lg); + margin-top: var(--space-lg); + border: 1px solid var(--primary-color); + animation: scaleIn 0.3s ease-out; } -.summary-value { - font-size: 1.2rem; - font-weight: 600; - color: #e5e7eb; +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } } -.summary-value.text-left { - font-size: 0.9rem; +.file-info h4 { + color: var(--primary-color); + margin-bottom: var(--space-sm); + display: flex; + align-items: center; + gap: var(--space-sm); } -.summary-value .filename { - font-weight: 600; +.file-info p { + color: var(--text-secondary); + font-size: var(--font-size-sm); } -.summary-value .meta { - color: #9ca3af; - margin-top: 2px; +/* ================================ + 进度条 + ================================ */ +.progress-container { + margin: var(--space-xl) 0; } -.summary-empty { - grid-column: 1 / -1; - font-size: 0.9rem; - color: #9ca3af; +.progress-bar { + height: 8px; + background: var(--warm-gray-200); + border-radius: var(--radius-full); + overflow: hidden; + position: relative; } -.link-btn { - margin-top: 4px; - font-size: 0.9rem; - color: #93c5fd; - text-decoration: none; +.progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--primary-color), var(--warning-color), var(--success-color)); + background-size: 200% 100%; + border-radius: var(--radius-full); + transition: width var(--transition-slow); + animation: shimmer 2s linear infinite; } -.link-btn:hover { - text-decoration: underline; +@keyframes shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } } -.center-block { - text-align: center; - padding: 24px 8px; - color: #9ca3af; +.progress-text { + text-align: center; + margin-top: var(--space-sm); + color: var(--text-secondary); + font-size: var(--font-size-sm); } -.spinner { - border: 3px solid rgba(148, 163, 184, 0.3); - border-top: 3px solid #60a5fa; - border-radius: 50%; - width: 32px; - height: 32px; - animation: spin 0.9s linear infinite; - margin: 0 auto 10px; +/* ================================ + 结果展示区域 + ================================ */ +.results-section { + padding: var(--space-lg) 0; } -.spinner.small { - width: 22px; - height: 22px; - margin-bottom: 6px; +.result-card { + background: white; + border-radius: var(--radius-xl); + padding: var(--space-xl); + margin: var(--space-lg) 0; + border: 1px solid var(--warm-gray-200); + box-shadow: var(--shadow-sm); + transition: all var(--transition-base); } -.status-pill { - padding: 2px 10px; - border-radius: 999px; - font-size: 0.75rem; - font-weight: 600; - text-transform: none; +.result-card:hover { + box-shadow: var(--shadow-lg); + transform: translateY(-2px); } -.status-pending { - background: rgba(148, 163, 184, 0.15); - color: #94a3b8; +.result-card h3 { + color: var(--text-primary); + margin-bottom: var(--space-lg); + display: flex; + align-items: center; + gap: var(--space-sm); + font-size: var(--font-size-xl); + padding-bottom: var(--space-md); + border-bottom: 2px solid var(--warm-gray-100); } -.status-processing { - background: rgba(250, 204, 21, 0.15); - color: #facc15; -} - -.status-completed { - background: rgba(52, 211, 153, 0.18); - color: #6ee7b7; -} - -.status-failed { - background: rgba(248, 113, 113, 0.18); - color: #fb7185; -} - -.file-list { - display: flex; - flex-direction: column; - gap: 10px; - margin-top: 8px; -} - -.file-card { - border-radius: 14px; - border: 1px solid rgba(148, 163, 184, 0.4); - background: rgba(15, 23, 42, 0.9); - padding: 10px 12px; -} - -.file-card.expanded { - border-color: #60a5fa; -} - -.file-header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; - cursor: pointer; -} - -.file-name { - font-size: 0.95rem; - color: #e5e7eb; -} - -.file-meta { - font-size: 0.8rem; - color: #9ca3af; -} - -.expand-btn { - border: none; - background: transparent; - color: #9ca3af; - font-size: 0.8rem; - cursor: pointer; -} - -.record-list { - margin-top: 8px; - border-top: 1px dashed rgba(55, 65, 81, 0.8); - padding-top: 6px; -} - -.record-item { - border-radius: 12px; - border: 1px solid rgba(55, 65, 81, 0.9); - background: rgba(15, 23, 42, 0.95); - padding: 8px 10px; - margin-top: 6px; - cursor: pointer; - transition: background 0.2s ease, transform 0.1s ease; -} - -.record-item:hover { - background: rgba(17, 24, 39, 1); - transform: translateY(-1px); -} - -.record-header { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.82rem; - color: #e5e7eb; -} - -.record-meta { - margin-top: 4px; - font-size: 0.78rem; - color: #9ca3af; - display: flex; - gap: 10px; - flex-wrap: wrap; -} - -.result-layout { - display: flex; - flex-direction: column; - gap: 14px; -} - -.task-card .info-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 10px; - font-size: 0.88rem; -} - -.info-item { - display: flex; - flex-direction: column; - gap: 2px; -} - -.info-label { - font-size: 0.8rem; - color: #9ca3af; -} - -.info-value { - font-size: 0.92rem; - color: #e5e7eb; -} - -.data-grid { - display: flex; - flex-direction: column; - gap: 6px; +/* 数据网格 */ +.task-info, +.geometry-data, +.bounding-box-data, +.topology-data, +.features-data, +.recommendations-data, +.metrics-data, +.analysis-info { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: var(--space-md); } +/* 数据项 */ .data-item { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.88rem; - color: #e5e7eb; + background: linear-gradient(135deg, var(--warm-gray-50) 0%, white 100%); + padding: var(--space-lg); + border-radius: var(--radius-lg); + border: 1px solid var(--warm-gray-200); + transition: all var(--transition-base); +} + +.data-item:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-md); + border-color: var(--primary-light); } .data-label { - color: #9ca3af; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: var(--space-sm); + font-size: var(--font-size-sm); + display: flex; + align-items: center; + gap: var(--space-sm); + text-transform: uppercase; + letter-spacing: 0.05em; } .data-value { - font-weight: 500; + color: var(--text-primary); + font-family: 'SF Mono', 'Consolas', monospace; + font-size: var(--font-size-lg); + font-weight: 700; } -.placeholder { - font-size: 0.85rem; - color: #6b7280; +.data-unit { + color: var(--text-muted); + font-size: var(--font-size-sm); + margin-left: var(--space-xs); + font-weight: 400; } -.error-box { - margin-top: 8px; - padding: 8px 10px; - border-radius: 10px; - border: 1px solid rgba(248, 113, 113, 0.5); - background: rgba(127, 29, 29, 0.4); - font-size: 0.85rem; - color: #fecaca; +/* 坐标项 */ +.coordinate-item { + background: linear-gradient(135deg, var(--info-light) 0%, white 100%); + padding: var(--space-md); + border-radius: var(--radius-md); + border: 1px solid var(--info-color); } -@media (max-width: 900px) { - .app-shell { - border-radius: 14px; - } - .app-header { - flex-direction: column; - align-items: flex-start; - } - .grid-2 { - grid-template-columns: minmax(0, 1fr); - } - .grid-3 { - grid-template-columns: minmax(0, 1fr); - } - .task-card .info-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} \ No newline at end of file +.coordinate-label { + font-weight: 600; + color: var(--info-color); + margin-bottom: var(--space-xs); + font-size: var(--font-size-sm); +} + +.coordinate-value { + font-family: 'SF Mono', 'Consolas', monospace; + color: var(--text-primary); + font-weight: 600; +} + +/* ================================ + 状态徽章 + ================================ */ +.status-badge { + padding: var(--space-xs) var(--space-md); + border-radius: var(--radius-full); + font-size: var(--font-size-sm); + font-weight: 600; + display: inline-flex; + align-items: center; + gap: var(--space-xs); +} + +.status-pending { + background: var(--warning-light); + color: #8b6914; + border: 1px solid var(--warning-color); +} + +.status-processing { + background: var(--info-light); + color: #1a5276; + border: 1px solid var(--info-color); + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } +} + +.status-completed { + background: var(--success-light); + color: #1e8449; + border: 1px solid var(--success-color); +} + +.status-failed { + background: var(--error-light); + color: #922b21; + border: 1px solid var(--error-color); +} + +/* ================================ + 特征和建议卡片 + ================================ */ +.feature-item { + background: linear-gradient(135deg, var(--info-light) 0%, white 100%); + padding: var(--space-lg); + border-radius: var(--radius-lg); + border-left: 4px solid var(--info-color); + transition: all var(--transition-base); +} + +.feature-item:hover { + transform: translateX(4px); + box-shadow: var(--shadow-md); +} + +.recommendation-item { + background: linear-gradient(135deg, var(--warning-light) 0%, white 100%); + padding: var(--space-lg); + border-radius: var(--radius-lg); + border-left: 4px solid var(--warning-color); + transition: all var(--transition-base); +} + +.recommendation-item:hover { + transform: translateX(4px); + box-shadow: var(--shadow-md); +} + +.recommendation-high { + border-left-color: var(--error-color); + background: linear-gradient(135deg, var(--error-light) 0%, white 100%); +} + +.recommendation-medium { + border-left-color: var(--warning-color); + background: linear-gradient(135deg, var(--warning-light) 0%, white 100%); +} + +.recommendation-low { + border-left-color: var(--success-color); + background: linear-gradient(135deg, var(--success-light) 0%, white 100%); +} + +.feature-header, +.recommendation-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-md); +} + +.feature-type { + font-weight: 700; + color: var(--text-primary); + font-size: var(--font-size-lg); +} + +.confidence-badge { + background: linear-gradient(135deg, var(--info-color), #2980b9); + color: white; + padding: var(--space-xs) var(--space-md); + border-radius: var(--radius-full); + font-size: var(--font-size-sm); + font-weight: 600; +} + +.priority-badge { + padding: var(--space-xs) var(--space-md); + border-radius: var(--radius-full); + font-size: var(--font-size-sm); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.priority-high { + background: linear-gradient(135deg, var(--error-color), #c0392b); + color: white; +} + +.priority-medium { + background: linear-gradient(135deg, var(--warning-color), #d68910); + color: white; +} + +.priority-low { + background: linear-gradient(135deg, var(--success-color), #219a52); + color: white; +} + +.recommendation-list { + list-style: none; + padding: 0; + margin: var(--space-md) 0 0; +} + +.recommendation-list li { + padding: var(--space-sm) 0; + border-bottom: 1px solid var(--warm-gray-200); + display: flex; + align-items: flex-start; + gap: var(--space-sm); + color: var(--text-secondary); +} + +.recommendation-list li:last-child { + border-bottom: none; +} + +.recommendation-list li::before { + content: "💡"; + font-size: 1.1em; + flex-shrink: 0; +} + +/* ================================ + 指标卡片 + ================================ */ +.metric-item { + background: white; + padding: var(--space-xl); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-sm); + text-align: center; + border: 1px solid var(--warm-gray-200); + transition: all var(--transition-base); +} + +.metric-item:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-lg); +} + +.metric-value { + font-size: var(--font-size-3xl); + font-weight: 800; + color: var(--text-primary); + margin: var(--space-md) 0; + line-height: 1; +} + +.metric-label { + color: var(--text-muted); + font-size: var(--font-size-sm); + text-transform: uppercase; + letter-spacing: 0.1em; +} + +.metric-good { + color: var(--success-color); +} + +.metric-warning { + color: var(--warning-color); +} + +.metric-poor { + color: var(--error-color); +} + +/* ================================ + 加载动画 + ================================ */ +.loading { + text-align: center; + padding: var(--space-2xl); +} + +.spinner { + width: 50px; + height: 50px; + border: 4px solid var(--warm-gray-200); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto var(--space-lg); +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.loading-text { + color: var(--text-secondary); + font-size: var(--font-size-lg); +} + +/* ================================ + 错误消息 + ================================ */ +.error-message { + background: linear-gradient(135deg, var(--error-light) 0%, white 100%); + color: var(--error-color); + padding: var(--space-lg); + border-radius: var(--radius-lg); + margin: var(--space-lg) 0; + border-left: 4px solid var(--error-color); + display: flex; + align-items: center; + gap: var(--space-md); +} + +.error-message::before { + content: "⚠️"; + font-size: 1.5em; +} + +/* ================================ + 历史记录表格 + ================================ */ +.history-table { + width: 100%; + border-collapse: collapse; + margin-top: var(--space-lg); + background: white; + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow-sm); +} + +.history-table th, +.history-table td { + padding: var(--space-md) var(--space-lg); + text-align: left; + border-bottom: 1px solid var(--warm-gray-200); +} + +.history-table th { + background: linear-gradient(135deg, var(--warm-gray-100) 0%, var(--warm-gray-50) 100%); + color: var(--text-secondary); + font-weight: 600; + font-size: var(--font-size-sm); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.history-table tr:hover td { + background: var(--warm-gray-50); +} + +.history-table tr:last-child td { + border-bottom: none; +} + +/* ================================ + 空状态 + ================================ */ +.empty-state { + text-align: center; + padding: var(--space-3xl); + color: var(--text-muted); +} + +.empty-state-icon { + font-size: 4rem; + margin-bottom: var(--space-lg); + opacity: 0.5; +} + +.empty-state h3 { + color: var(--text-secondary); + margin-bottom: var(--space-sm); +} + +/* ================================ + 统计卡片 + ================================ */ +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-lg); + margin-bottom: var(--space-2xl); +} + +.stat-card { + background: white; + border-radius: var(--radius-xl); + padding: var(--space-xl); + text-align: center; + border: 1px solid var(--warm-gray-200); + transition: all var(--transition-base); + position: relative; + overflow: hidden; +} + +.stat-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, var(--primary-color), var(--warning-color)); +} + +.stat-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-lg); +} + +.stat-icon { + font-size: 2.5rem; + margin-bottom: var(--space-md); +} + +.stat-value { + font-size: var(--font-size-3xl); + font-weight: 800; + color: var(--text-primary); + line-height: 1; +} + +.stat-label { + color: var(--text-muted); + font-size: var(--font-size-sm); + margin-top: var(--space-sm); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* ================================ + 响应式设计 + ================================ */ +@media (max-width: 1024px) { + .app-header { + padding: var(--space-xl); + } + + .app-main { + padding: var(--space-xl); + } + + .app-title { + flex-direction: column; + align-items: flex-start; + gap: var(--space-md); + } +} + +@media (max-width: 768px) { + body { + padding: var(--space-md); + } + + .app-header { + padding: var(--space-lg); + border-radius: var(--radius-xl) var(--radius-xl) 0 0; + } + + .app-main { + padding: var(--space-lg); + border-radius: 0 0 var(--radius-xl) var(--radius-xl); + } + + .app-title h1 { + font-size: var(--font-size-2xl); + } + + .app-nav { + flex-wrap: wrap; + } + + .nav-link { + padding: var(--space-sm) var(--space-md); + font-size: var(--font-size-sm); + } + + .upload-area { + padding: var(--space-xl); + } + + .upload-icon { + font-size: 3rem; + } + + .task-info, + .geometry-data, + .bounding-box-data, + .topology-data, + .features-data, + .recommendations-data, + .metrics-data, + .analysis-info { + grid-template-columns: 1fr; + } + + .app-footer { + flex-direction: column; + gap: var(--space-md); + text-align: center; + } + + .footer-left, + .footer-right { + flex-wrap: wrap; + justify-content: center; + } + + .stats-grid { + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width: 480px) { + .stats-grid { + grid-template-columns: 1fr; + } + + .notification-container { + left: var(--space-md); + right: var(--space-md); + max-width: none; + } + + .history-table { + font-size: var(--font-size-sm); + } + + .history-table th, + .history-table td { + padding: var(--space-sm); + } +} + +/* ================================ + 打印样式 + ================================ */ +@media print { + body { + background: white; + padding: 0; + } + + .app-header, + .app-footer, + .notification-container, + .upload-section, + .nav-link { + display: none; + } + + .app-main { + box-shadow: none; + border: none; + padding: 0; + } + + .result-card { + break-inside: avoid; + box-shadow: none; + border: 1px solid #ddd; + } +} + +/* ================================ + 深色模式支持(可选) + ================================ */ +@media (prefers-color-scheme: dark) { + :root { + --warm-white: #1a1816; + --cream: #2c2824; + --text-primary: #f5f3f0; + --text-secondary: #d4cfc6; + --text-muted: #a69f94; + } +} + +/* ================================ + Vue 过渡动画 + ================================ */ +.fade-slide-enter-active, +.fade-slide-leave-active { + transition: all 0.3s ease; +} + +.fade-slide-enter-from { + opacity: 0; + transform: translateY(10px); +} + +.fade-slide-leave-to { + opacity: 0; + transform: translateY(-10px); +} + +.notification-enter-active, +.notification-leave-active { + transition: all 0.3s ease; +} + +.notification-enter-from { + opacity: 0; + transform: translateX(100%); +} + +.notification-leave-to { + opacity: 0; + transform: translateX(100%); +} + +.notification-hidden { + opacity: 0; + transform: translateX(20px); +} + +.list-enter-active, +.list-leave-active { + transition: all 0.4s ease; +} + +.list-enter-from { + opacity: 0; + transform: translateY(20px); +} + +.list-leave-to { + opacity: 0; + transform: translateY(-20px); +} + +.list-move { + transition: transform 0.4s ease; +} + +.expand-enter-active, +.expand-leave-active { + transition: all 0.3s ease; + overflow: hidden; +} + +.expand-enter-from, +.expand-leave-to { + opacity: 0; + max-height: 0; +} + +.expand-enter-to, +.expand-leave-from { + max-height: 1000px; +} + +/* ================================ + 卡片和布局组件 + ================================ */ +.card { + background: white; + border-radius: var(--radius-xl); + padding: var(--space-xl); + border: 1px solid var(--warm-gray-200); + box-shadow: var(--shadow-sm); + transition: all var(--transition-base); +} + +.card:hover { + box-shadow: var(--shadow-md); +} + +.card h2 { + display: flex; + align-items: center; + gap: var(--space-sm); + color: var(--text-primary); + font-size: var(--font-size-xl); + margin-bottom: var(--space-md); +} + +.card-icon { + font-size: 1.5em; +} + +.card-subtitle { + color: var(--text-muted); + font-size: var(--font-size-sm); + margin-bottom: var(--space-lg); +} + +.grid-2 { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-lg); +} + +.grid-3 { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--space-lg); +} + +@media (max-width: 1024px) { + .grid-2, + .grid-3 { + grid-template-columns: 1fr; + } +} + +/* ================================ + 上传卡片 + ================================ */ +.upload-card { + min-height: 400px; +} + +.upload-btn { + width: 100%; + margin-top: var(--space-lg); + padding: var(--space-md) var(--space-xl); + font-size: var(--font-size-lg); +} + +.clear-btn { + margin-top: var(--space-sm); +} + +.current-task-card { + background: linear-gradient(135deg, var(--warm-gray-50) 0%, white 100%); + border-radius: var(--radius-lg); + padding: var(--space-lg); + margin-top: var(--space-xl); + border: 1px solid var(--warm-gray-200); +} + +.current-task-card h4 { + color: var(--text-primary); + margin-bottom: var(--space-md); +} + +/* ================================ + 文件预览 + ================================ */ +.latest-file { + margin-bottom: var(--space-lg); +} + +.latest-file h4 { + color: var(--text-secondary); + font-size: var(--font-size-sm); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: var(--space-md); +} + +.file-preview { + display: flex; + align-items: center; + gap: var(--space-md); + padding: var(--space-md); + background: var(--warm-gray-50); + border-radius: var(--radius-lg); +} + +.file-preview .file-icon { + font-size: 2rem; +} + +.file-preview .file-name { + font-weight: 600; + color: var(--text-primary); + margin-bottom: var(--space-xs); +} + +.file-preview .file-meta { + display: flex; + gap: var(--space-md); + color: var(--text-muted); + font-size: var(--font-size-sm); +} + +.link-btn { + display: inline-flex; + margin-top: var(--space-lg); +} + +/* ================================ + 历史记录视图 + ================================ */ +.history-view h2 { + display: flex; + align-items: center; + gap: var(--space-sm); + color: var(--text-primary); + margin-bottom: var(--space-sm); +} + +.file-list { + display: flex; + flex-direction: column; + gap: var(--space-md); + margin-top: var(--space-xl); +} + +.file-card { + background: white; + border-radius: var(--radius-lg); + border: 1px solid var(--warm-gray-200); + overflow: hidden; + transition: all var(--transition-base); +} + +.file-card:hover { + box-shadow: var(--shadow-md); +} + +.file-card.expanded { + box-shadow: var(--shadow-lg); +} + +.file-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--space-lg); + cursor: pointer; + transition: background var(--transition-fast); +} + +.file-header:hover { + background: var(--warm-gray-50); +} + +.file-info .file-name { + font-weight: 600; + color: var(--text-primary); + margin-bottom: var(--space-xs); +} + +.file-info .file-meta { + color: var(--text-muted); + font-size: var(--font-size-sm); +} + +.expand-btn { + flex-shrink: 0; +} + +.record-list { + padding: 0 var(--space-lg) var(--space-lg); +} + +.record-item { + padding: var(--space-md); + background: var(--warm-gray-50); + border-radius: var(--radius-md); + margin-bottom: var(--space-sm); + cursor: pointer; + transition: all var(--transition-fast); +} + +.record-item:hover { + background: var(--primary-light); + transform: translateX(4px); +} + +.record-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-sm); +} + +.record-date { + color: var(--text-secondary); + font-size: var(--font-size-sm); +} + +.record-meta { + display: flex; + flex-wrap: wrap; + gap: var(--space-md); + color: var(--text-muted); + font-size: var(--font-size-sm); +} + +/* ================================ + 结果视图 + ================================ */ +.result-view h2 { + display: flex; + align-items: center; + gap: var(--space-sm); + color: var(--text-primary); + margin-bottom: var(--space-xl); +} + +.result-layout { + display: flex; + flex-direction: column; + gap: var(--space-xl); +} + +/* ================================ + 通知图标 + ================================ */ +.notification-icon { + width: 24px; + height: 24px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: bold; + flex-shrink: 0; +} + +.notification-success .notification-icon { + background: var(--success-color); + color: white; +} + +.notification-error .notification-icon { + background: var(--error-color); + color: white; +} + +.notification-warning .notification-icon { + background: var(--warning-color); + color: white; +} + +.notification-info .notification-icon { + background: var(--info-color); + color: white; +} + +/* ================================ + 页脚增强 + ================================ */ +.footer-item { + display: inline-flex; + align-items: center; + gap: var(--space-xs); +} + +.footer-label { + color: var(--text-muted); +} + +.footer-divider { + color: var(--warm-gray-400); +} diff --git a/static/vue-app.js b/static/vue-app.js index 2e7d671..a313941 100644 --- a/static/vue-app.js +++ b/static/vue-app.js @@ -1,16 +1,17 @@ /** * STP模具几何分析中心 - Vue3单页应用 - * 优化版本:统一前端实现,增强用户体验 + * 版本: 4.0.0 - 暖白色主题 + 增强动画效果 */ -const { createApp, ref, computed, onMounted, reactive } = Vue; +const { createApp, ref, computed, onMounted, reactive, watch, nextTick } = Vue; const { createRouter, createWebHistory, useRoute, useRouter } = VueRouter; // 全局状态管理 const appState = reactive({ health: null, loading: false, - notifications: [] + notifications: [], + theme: 'warm' }); // 通用工具函数 @@ -48,7 +49,6 @@ function statusText(status) { return map[status] || status || "未知"; } -// 状态样式映射 function getStatusClass(status) { const classMap = { pending: "status-pending", @@ -59,26 +59,33 @@ function getStatusClass(status) { return classMap[status] || "status-pending"; } -// 添加通知 +let notificationId = 0; + function addNotification(message, type = 'info') { + const id = ++notificationId; const notification = { - id: Date.now(), + id, message, type, - timestamp: new Date() + timestamp: new Date(), + visible: true }; appState.notifications.push(notification); - // 自动移除通知 setTimeout(() => { - const index = appState.notifications.findIndex(n => n.id === notification.id); + const index = appState.notifications.findIndex(n => n.id === id); if (index > -1) { - appState.notifications.splice(index, 1); + appState.notifications[index].visible = false; + setTimeout(() => { + const idx = appState.notifications.findIndex(n => n.id === id); + if (idx > -1) { + appState.notifications.splice(idx, 1); + } + }, 300); } }, 5000); } -// 错误处理 function handleApiError(error, context = '') { console.error(`API错误 [${context}]:`, error); const message = error.message || '请求失败,请稍后重试'; @@ -110,13 +117,18 @@ const App = { const dismissNotification = (id) => { const index = appState.notifications.findIndex(n => n.id === id); if (index > -1) { - appState.notifications.splice(index, 1); + appState.notifications[index].visible = false; + setTimeout(() => { + const idx = appState.notifications.findIndex(n => n.id === id); + if (idx > -1) { + appState.notifications.splice(idx, 1); + } + }, 300); } }; onMounted(() => { loadHealth(); - // 定时检查健康状态(每30秒) setInterval(loadHealth, 30000); }); @@ -134,14 +146,19 @@ const App = {
-
- {{ notification.message }} - -
+ +
+ + {{ notification.type === 'success' ? '✓' : notification.type === 'error' ? '✕' : notification.type === 'warning' ? '⚠' : 'ℹ' }} + + {{ notification.message }} + +
+
@@ -154,29 +171,42 @@ const App = {
- + + + + +
@@ -195,7 +225,8 @@ const DashboardView = { currentTask: null, polling: false, historySummary: null, - dragOver: false + dragOver: false, + progress: 0 }); const totalFiles = computed(() => state.historySummary?.total_files || 0); @@ -256,6 +287,7 @@ const DashboardView = { if (!state.selectedFile) return; state.uploading = true; state.error = ""; + state.progress = 0; const formData = new FormData(); formData.append("file", state.selectedFile); @@ -287,16 +319,18 @@ const DashboardView = { const startPolling = async (taskId) => { state.polling = true; + state.progress = 10; let pollCount = 0; - const maxPolls = 300; // 最多轮询5分钟(300秒) + const maxPolls = 300; const poll = async () => { try { pollCount++; + state.progress = Math.min(90, 10 + pollCount * 0.5); + const res = await fetch(`/status/${taskId}`, { method: "POST" }); if (res.status === 404) { - // 任务不存在,停止轮询 state.polling = false; state.error = "任务不存在或已被删除"; addNotification("任务不存在或已被删除", 'error'); @@ -312,18 +346,20 @@ const DashboardView = { if (task.status === "completed") { state.polling = false; + state.progress = 100; addNotification(`分析完成,正在跳转到结果页面...`, 'success'); setTimeout(() => { router.push(`/result/${task.task_id}`); }, 1000); } else if (task.status === "failed") { state.polling = false; + state.progress = 0; const errorMsg = task.error || "未知错误"; state.error = `分析失败: ${errorMsg}`; addNotification(`分析失败: ${errorMsg}`, 'error'); } else if (pollCount >= maxPolls) { - // 超时停止轮询 state.polling = false; + state.progress = 0; state.error = "任务处理超时,请稍后查看结果"; addNotification("任务处理超时,请稍后查看结果", 'warning'); } else { @@ -331,6 +367,7 @@ const DashboardView = { } } catch (e) { state.polling = false; + state.progress = 0; const errorMsg = handleApiError(e, '任务轮询'); state.error = errorMsg; } @@ -378,14 +415,42 @@ const DashboardView = { }, template: `
-
+ +
+
+
📁
+
{{ totalFiles }}
+
已分析文件
+
+
+
📊
+
{{ totalRecords }}
+
处理记录
+
+
+
⚡
+
{{ state.polling ? '分析中' : '就绪' }}
+
系统状态
+
+
+
🎯
+
{{ latestFile ? '1' : '0' }}
+
最近文件
+
+
+ +
+
-

📁 上传 STP / STEP 文件

+

+ 📤 + 上传 STP / STEP 文件 +

上传后系统会自动解析几何、生成模具型腔并计算关键工艺参数。

-
+
+
`, }; @@ -497,7 +577,6 @@ const HistoryView = { const toggleExpand = async (filename) => { expanded.value[filename] = !expanded.value[filename]; - // 首次展开时加载该文件的所有记录 if (expanded.value[filename]) { const file = files.value.find((f) => f.filename === filename); if (!file.records) { @@ -535,22 +614,26 @@ const HistoryView = { }, template: `
-

📋 历史记录

+

+ 📋 + 历史记录 +

按文件名聚合展示所有上传与分析任务。

-
+
-

正在加载历史记录...

+

正在加载历史记录...

-

{{ error }}

+

{{ error }}

-
-
📁
-

暂无历史记录,先在仪表盘上传一个文件吧。

+
+
📁
+

暂无历史记录

+

先在仪表盘上传一个文件吧。

-
+
-
+
📄 {{ file.filename }}
总记录:{{ file.record_count }} 条 | 最近上传:{{ formatDateTime(file.last_upload) }}
-
-
-
-
-

正在加载记录...

-
-
- 暂无详细记录 -
-
-
- 📅 {{ formatDateTime(record.upload_time) }} - - {{ statusText(record.status) }} - + +
+
+
+

正在加载记录...

-
- 任务 ID:{{ record.task_id }} - 文件大小:{{ formatFileSize(record.file_size) }} - 完成时间:{{ formatDateTime(record.completed_at) }} +
+

暂无详细记录

+ +
+
+ 📅 {{ formatDateTime(record.upload_time) }} + + {{ statusText(record.status) }} + +
+
+ 任务 ID:{{ record.task_id }} + 文件大小:{{ formatFileSize(record.file_size) }} + 完成时间:{{ formatDateTime(record.completed_at) }} +
+
+
-
+
-
+
`, }; @@ -651,67 +737,72 @@ const ResultView = { }, template: `
-

📊 分析结果详情

+

+ 📊 + 分析结果详情 +

-
+
-

正在加载任务 {{ taskId }} 的分析结果...

+

正在加载任务 {{ taskId }} 的分析结果...

-

{{ error }}

+

{{ error }}

-
+ +

📝 任务信息

-
-
- 任务 ID - {{ task.task_id || 'N/A' }} +
+
+ 任务 ID + {{ task.task_id || 'N/A' }}
-
- 文件名 - {{ task.filename || 'N/A' }} +
+ 文件名 + {{ task.filename || 'N/A' }}
-
- 文件大小 - {{ task.file_size ? formatFileSize(task.file_size) : 'N/A' }} +
+ 文件大小 + {{ task.file_size ? formatFileSize(task.file_size) : 'N/A' }}
-
- 状态 - - - {{ statusText(task.status) }} - +
+ 状态 + + {{ statusText(task.status) }}
-
- 上传时间 - {{ task.upload_time ? formatDateTime(task.upload_time) : 'N/A' }} +
+ 上传时间 + {{ task.upload_time ? formatDateTime(task.upload_time) : 'N/A' }}
-
- 完成时间 - {{ task.completed_at ? formatDateTime(task.completed_at) : 'N/A' }} +
+ 完成时间 + {{ task.completed_at ? formatDateTime(task.completed_at) : 'N/A' }}
-
+
错误信息:{{ task.error }}
+
-
+

📐 几何属性

-
+
体积 - {{ geometry.volume ? formatNumber(geometry.volume) + ' mm³' : 'N/A' }} + {{ geometry.volume ? formatNumber(geometry.volume) : 'N/A' }} + mm³
表面积 - {{ geometry.surface_area ? formatNumber(geometry.surface_area) + ' mm²' : 'N/A' }} + {{ geometry.surface_area ? formatNumber(geometry.surface_area) : 'N/A' }} + mm²
@@ -725,13 +816,15 @@ const ResultView = {
-
无几何数据
+
+

无几何数据

+
-
+

📦 边界框

-
-
+
+
尺寸 (mm) {{ geometry.bounding_box.dimensions[0].toFixed(2) }} × @@ -739,29 +832,31 @@ const ResultView = { {{ geometry.bounding_box.dimensions[2].toFixed(2) }}
-
+
最小坐标 - + X: {{ geometry.bounding_box.min[0].toFixed(2) }}, Y: {{ geometry.bounding_box.min[1].toFixed(2) }}, Z: {{ geometry.bounding_box.min[2].toFixed(2) }}
-
+
最大坐标 - + X: {{ geometry.bounding_box.max[0].toFixed(2) }}, Y: {{ geometry.bounding_box.max[1].toFixed(2) }}, Z: {{ geometry.bounding_box.max[2].toFixed(2) }}
-
无边界框数据
+
+

无边界框数据

+
-
+

🔺 拓扑结构

-
+
面数 {{ geometry.topology.faces || 0 }} @@ -775,35 +870,30 @@ const ResultView = { {{ geometry.topology.vertices || 0 }}
-
无拓扑数据
+
+

无拓扑数据

+
+
-
+

🔧 模具 / 工艺关键信息

-
+
收缩率 - - {{ keyInfo.metadata?.shrinkage_rate ?? 'N/A' }} - + {{ keyInfo.metadata?.shrinkage_rate ?? 'N/A' }}
拔模角 - {{ - keyInfo.metadata?.draft_angle !== undefined - ? keyInfo.metadata.draft_angle + '°' - : 'N/A' - }} + {{ keyInfo.metadata?.draft_angle !== undefined ? keyInfo.metadata.draft_angle + '°' : 'N/A' }}
预估夹紧力 - - {{ keyInfo.manufacturing_info?.estimated_clamping_force || 'N/A' }} - + {{ keyInfo.manufacturing_info?.estimated_clamping_force || 'N/A' }}
模具尺寸 (mm) @@ -822,34 +912,28 @@ const ResultView = {
产品重量 - {{ - keyInfo.mold_cavities?.cavity_key_info?.geometric_characteristics - ?.product_weight || 'N/A' - }} + {{ keyInfo.mold_cavities?.cavity_key_info?.geometric_characteristics?.product_weight || 'N/A' }}
壁厚范围 - {{ - keyInfo.mold_cavities?.cavity_key_info?.geometric_characteristics - ?.wall_thickness_range || 'N/A' - }} + {{ keyInfo.mold_cavities?.cavity_key_info?.geometric_characteristics?.wall_thickness_range || 'N/A' }}
预估成型周期 - - {{ keyInfo.manufacturing_info?.estimated_cycle_time || 'N/A' }} - + {{ keyInfo.manufacturing_info?.estimated_cycle_time || 'N/A' }}
-
无型腔 / 工艺数据
+
+

无型腔 / 工艺数据

+
-
+

🧱 网格摘要

-
+
网格质量等级 {{ meshSummary.quality }} @@ -867,8 +951,8 @@ const ResultView = { {{ meshSummary.point_count ?? 'N/A' }}
-
- 当前任务未生成网格摘要,或网格生成过程中出现问题。 +
+

当前任务未生成网格摘要

@@ -891,4 +975,3 @@ const router = createRouter({ // 挂载应用 createApp(App).use(router).mount("#app"); -