diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a5d7f2e --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +SHELL := /bin/bash + +APP_NAME := sftp-manager +PORT := 48081 +BASE_PATH := /sftp-manager +APP_URL := http://localhost:$(PORT)$(BASE_PATH) + +MVN := mvn +DOCKER_COMPOSE := docker compose + +.DEFAULT_GOAL := help + +.PHONY: help check-mvn check-docker install build test start stop restart status logs deploy bootstrap clean jar-start jar-stop jar-restart + +help: + @echo "Available targets:" + @echo " make bootstrap - One command install + deploy + start (Docker)" + @echo " make deploy - Build app and start with Docker Compose" + @echo " make install - Maven install (skip tests)" + @echo " make build - Maven package (skip tests)" + @echo " make test - Run all tests" + @echo " make start - Start service by Docker Compose" + @echo " make stop - Stop service by Docker Compose" + @echo " make restart - Restart Docker service" + @echo " make status - Show Docker service status" + @echo " make logs - Tail service logs" + @echo " make jar-start - Start packaged jar using deploy.sh" + @echo " make jar-stop - Stop packaged jar using deploy.sh" + @echo " make jar-restart - Restart packaged jar using deploy.sh" + +check-mvn: + @command -v $(MVN) >/dev/null 2>&1 || { echo "Error: mvn not found"; exit 1; } + +check-docker: + @command -v docker >/dev/null 2>&1 || { echo "Error: docker not found"; exit 1; } + @$(DOCKER_COMPOSE) version >/dev/null 2>&1 || { echo "Error: docker compose not available"; exit 1; } + +install: check-mvn + @echo ">>> Installing project artifacts (skip tests)..." + @$(MVN) clean install -DskipTests + +build: check-mvn + @echo ">>> Packaging application (skip tests)..." + @$(MVN) clean package -DskipTests + +test: check-mvn + @echo ">>> Running tests..." + @$(MVN) clean test + +start: check-docker + @echo ">>> Starting $(APP_NAME) with Docker Compose..." + @$(DOCKER_COMPOSE) up -d --build + @echo ">>> Started. Visit: $(APP_URL)" + +stop: check-docker + @echo ">>> Stopping $(APP_NAME)..." + @$(DOCKER_COMPOSE) down + +restart: stop start + +status: check-docker + @$(DOCKER_COMPOSE) ps + +logs: check-docker + @$(DOCKER_COMPOSE) logs -f --tail=200 $(APP_NAME) + +deploy: build start + +bootstrap: install start + +clean: check-mvn + @$(MVN) clean + +jar-start: build + @chmod +x ./deploy.sh + @./deploy.sh start + +jar-stop: + @chmod +x ./deploy.sh + @./deploy.sh stop + +jar-restart: build + @chmod +x ./deploy.sh + @./deploy.sh restart diff --git a/docker-compose.yml b/docker-compose.yml index 57902ee..b4c3850 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,8 +8,8 @@ services: - "48081:48081" volumes: # 默认映射到用户家目录(Linux/Mac 为 $HOME,未设置时为当前目录) - - ${HOME:-.}/sftp-manager/data:/app/data - - ${HOME:-.}/sftp-manager/logs:/app/logs + - ./docker/data:/app/data + - ./docker/logs:/app/logs environment: - SPRING_PROFILES_ACTIVE=prod restart: unless-stopped diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css index fd232d1..e155bcd 100644 --- a/src/main/resources/static/css/style.css +++ b/src/main/resources/static/css/style.css @@ -1,167 +1,269 @@ -/* SFTP Manager 自定义样式 */ -/* 全局样式 */ +:root { + --bg: #f7f8fc; + --surface: #ffffff; + --surface-alt: #f2f4fa; + --ink: #1d2433; + --muted: #5c6475; + --line: #d8deea; + --primary: #1f3b7a; + --primary-soft: #e8eefc; + --accent: #d88a26; + --danger: #b42318; + --ok: #0f9f6e; + --radius: 14px; + --radius-sm: 10px; + --shadow: 0 18px 48px rgba(22, 32, 58, 0.12); + --trans: 180ms ease; +} + * { - margin: 0; - padding: 0; box-sizing: border-box; } -:root { - /* 设计令牌(简化版) */ - --color-primary: #0d6efd; - --color-success: #198754; - --color-warning: #ffc107; - --color-danger: #dc3545; - --color-dark: #0f172a; - --color-gray-700: #334155; - --color-gray-600: #6b7280; - --color-gray-200: #e5e7eb; - --color-gray-100: #f3f4f6; - - --radius-sm: 4px; - --radius-md: 12px; - --shadow-card: 0 22px 55px rgba(15, 23, 42, 0.18); +html, +body { + width: 100%; + height: 100%; } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - font-size: 14px; - line-height: 1.5; - color: var(--color-dark); - background: - radial-gradient(circle at top left, #e0f2fe 0, transparent 55%), - radial-gradient(circle at bottom right, #e9d5ff 0, transparent 55%), - #f3f4f6; + margin: 0; + font-family: Manrope, "Segoe UI", "PingFang SC", "Hiragino Sans GB", sans-serif; + background: radial-gradient(circle at 5% 10%, #edf2ff 0, transparent 38%), + radial-gradient(circle at 95% 90%, #fff3e5 0, transparent 36%), + var(--bg); + color: var(--ink); overflow: hidden; } -/* 应用容器 */ -.app-container { +.app-shell { + height: 100vh; display: flex; flex-direction: column; - height: calc(100vh - 32px); - max-width: 1220px; - margin: 16px auto; - background-color: #ffffff; - border-radius: var(--radius-md); - box-shadow: var(--shadow-card); - overflow: hidden; + padding: 14px; + gap: 10px; } -/* 导航栏 */ -.navbar { - flex-shrink: 0; - padding: 8px 16px; - min-height: 48px; +.topbar { + min-height: 62px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 14px; + border-radius: var(--radius); + background: linear-gradient(132deg, #233b75 0%, #405f9f 58%, #355086 100%); + color: #fff; + box-shadow: var(--shadow); } -.navbar-brand { - font-size: 18px; +.brand-title { + margin: 0; + font-size: 21px; + line-height: 1.2; + font-weight: 700; + letter-spacing: 0.01em; +} + +.brand-subtitle { + margin: 2px 0 0; + font-size: 13px; + color: rgba(255, 255, 255, 0.85); +} + +.topbar-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.topbar .btn { + min-height: 38px; + border-radius: 999px; + padding: 0 14px; font-weight: 600; } -/* 工具栏 */ -.toolbar { - flex-shrink: 0; - padding: 8px 16px; +.workspace { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + gap: 10px; +} + +.summary-strip { + display: grid; + grid-template-columns: 1fr 1fr 2fr; + gap: 10px; +} + +.summary-item { + min-height: 58px; + border: 1px solid var(--line); + border-radius: var(--radius-sm); + background: var(--surface); + padding: 10px 12px; + display: flex; + flex-direction: column; + justify-content: center; + gap: 2px; +} + +.summary-k { + font-size: 11px; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 700; +} + +.summary-v { + font-size: 13px; + color: var(--ink); + line-height: 1.35; + font-weight: 600; +} + +.ops-toolbar { + border: 1px solid var(--line); + border-radius: var(--radius-sm); + background: var(--surface); + padding: 10px; display: flex; align-items: center; gap: 8px; - min-height: 44px; - background-color: #f9fafb; - border-bottom: 1px solid #e5e7eb; + flex-wrap: wrap; +} + +.ops-group { + display: flex; + gap: 6px; + flex-wrap: wrap; +} + +.ops-group-right { + margin-left: auto; +} + +.ops-toolbar .btn { + min-height: 36px; + border-radius: 999px; + padding-inline: 14px; + font-weight: 600; +} + +.ops-toolbar .btn-primary { + background-color: var(--accent); + border-color: var(--accent); + color: #1e1e1e; +} + +.ops-toolbar .btn-primary:hover { + background-color: #c97816; + border-color: #c97816; } -/* 传输进度(上传/下载/跨面板传输) */ .transfer-progress { + margin-left: auto; display: inline-flex; align-items: center; - vertical-align: middle; - gap: 8px; + gap: 10px; } .transfer-progress-label { font-size: 12px; - color: #333; - max-width: 200px; + color: var(--muted); + max-width: 230px; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; } -.transfer-progress .progress { - border-radius: 4px; - overflow: hidden; -} - -.transfer-progress .progress-bar { - transition: width 0.3s ease; - font-size: 11px; - line-height: 20px; - text-align: center; - color: white; - background-color: #0d6efd; -} - -/* 双面板容器 */ -.panels-container { - display: flex; +.panes-grid { flex: 1; - overflow: hidden; + min-height: 0; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; } -/* 面板 */ -.panel { - flex: 1; +.pane { + min-width: 0; + min-height: 0; display: flex; flex-direction: column; - border-right: 1px solid #dee2e6; + border: 1px solid var(--line); + border-radius: var(--radius); + background: var(--surface); overflow: hidden; - background-color: #ffffff; - min-width: 0; } -.panel:last-child { - border-right: none; -} - -/* 当前活动面板高亮 */ -.panel.active-panel { - box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.4); -} - -/* 拖拽上传效果 */ -.panel.drag-over { - background-color: #e7f3ff; - border: 2px dashed #0d6efd; -} - -/* 面板头部 */ -.panel-header { - flex-shrink: 0; - padding: 8px; - background-color: #e9ecef; - border-bottom: 1px solid #dee2e6; - display: flex; +.pane-head { + padding: 10px; + border-bottom: 1px solid var(--line); + background: var(--surface-alt); + display: grid; gap: 8px; } -.panel-mode { - flex: 1; - min-width: 100px; -} - -.connection-select { - flex: 2; - min-width: 120px; -} - -/* 连接状态指示器 */ -.connection-status { +.pane-head-title { display: flex; align-items: center; + gap: 8px; +} + +.pane-head-text { + font-size: 12px; + color: var(--muted); + font-weight: 600; +} + +.pane-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 26px; + padding: 0 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 700; +} + +.pane-badge-master { + background: #e9f0ff; + color: #244492; +} + +.pane-badge-slave { + background: #fff2e2; + color: #9a5c07; +} + +.pane-head-controls { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 8px; +} + +.panel-mode, +.connection-select, +.path-input { + border-radius: 10px; + border: 1px solid #cfd7e6; + min-height: 38px; + font-size: 13px; +} + +.connection-status { + display: inline-flex; + align-items: center; + gap: 6px; padding: 0 8px; - flex-shrink: 0; + min-height: 34px; + border: 1px solid #d5dceb; + border-radius: 999px; + background: #fff; } .status-dot { @@ -169,311 +271,369 @@ body { height: 10px; border-radius: 50%; display: inline-block; - margin-right: 5px; } .status-dot[data-status="connected"] { - background-color: #198754; + background: var(--ok); } .status-dot[data-status="disconnected"] { - background-color: #dc3545; + background: var(--danger); } .status-dot[data-status="connecting"] { - background-color: #ffc107; + background: #f59e0b; animation: pulse 1s infinite; } @keyframes pulse { - 0%, 100% { + 0%, + 100% { opacity: 1; } 50% { - opacity: 0.5; + opacity: 0.35; } } -.connection-status .status-text { +.status-text { font-size: 12px; - color: #666; + font-weight: 600; + color: var(--muted); } -.connection-status.connected .status-text { - color: #198754; +.slave-targets { + border-bottom: 1px solid var(--line); + padding: 8px 10px; + background: #fcfdff; } -.connection-status.disconnected .status-text { - color: #dc3545; -} - -.connection-status.connecting .status-text { - color: #ffc107; -} - -/* 路径栏 */ -.path-bar { - flex-shrink: 0; - padding: 8px; - display: flex; - gap: 8px; - background-color: #fff; - border-bottom: 1px solid #dee2e6; -} - -.path-input { - flex: 1; +.slave-target-search { + min-height: 34px; font-size: 12px; + border-radius: 9px; + border: 1px solid #d3dbeb; + margin-bottom: 8px; } -/* 文件列表 */ -.file-list { - flex: 1; - overflow-y: auto; - overflow-x: hidden; - background-color: #fff; -} - -/* 文件列表拖拽悬停效果 */ -.file-list.drag-over { - background-color: #e7f3ff; - outline: 2px dashed #0d6efd; - outline-offset: -2px; -} - -/* 文件项拖拽中 */ -.file-item.dragging { - opacity: 0.5; -} - -/* 文件项 */ -.file-item { - padding: 8px 12px; - min-height: 44px; - cursor: pointer; +.slave-targets-head { display: flex; align-items: center; - border-bottom: 1px solid #f0f0f0; - transition: background-color 0.15s; - user-select: none; + justify-content: space-between; + margin-bottom: 8px; + font-size: 12px; + color: var(--muted); + font-weight: 700; +} + +.slave-targets-actions { + display: flex; + gap: 4px; +} + +.btn-xs { + min-height: 28px; + padding: 0 10px; + font-size: 11px; + border-radius: 999px; +} + +.slave-target-list { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 6px; + max-height: 110px; + overflow: auto; +} + +.slave-target-item { + border: 1px solid #d2d9e8; + border-radius: 10px; + background: #fff; + padding: 6px 8px; + display: grid; + grid-template-columns: 18px 1fr; + gap: 6px; + align-items: center; + min-height: 44px; + cursor: pointer; + transition: border-color var(--trans), box-shadow var(--trans); +} + +.slave-target-item.is-hidden { + display: none; +} + +.slave-target-item:hover { + border-color: #8fa4d3; + box-shadow: 0 4px 14px rgba(43, 70, 126, 0.13); +} + +.slave-target-item:has(.slave-target-check:checked) { + border-color: #5376bf; + box-shadow: 0 0 0 1px rgba(83, 118, 191, 0.25); + background: #f4f7ff; +} + +.slave-target-main { + font-size: 12px; + font-weight: 700; + color: var(--ink); + line-height: 1.2; +} + +.slave-target-sub { + grid-column: 2; + font-size: 11px; + color: var(--muted); + line-height: 1.15; + font-family: "JetBrains Mono", monospace; +} + +.slave-target-status { + grid-column: 2; + font-size: 10px; + color: #687386; + line-height: 1.2; + margin-top: 2px; +} + +.slave-target-status-success { + color: #0f9f6e; +} + +.slave-target-status-failed { + color: #c0392b; +} + +.slave-target-status-running { + color: #1f3b7a; +} + +.path-bar { + display: grid; + grid-template-columns: auto 1fr; + gap: 8px; + padding: 8px 10px; + border-bottom: 1px solid var(--line); +} + +.path-bar .btn { + min-height: 38px; + min-width: 42px; + border-radius: 10px; +} + +.file-list { + flex: 1; + overflow: auto; + background: #fff; +} + +.file-item { + display: grid; + grid-template-columns: 56px minmax(140px, 1fr) 90px 145px; + gap: 8px; + align-items: center; + min-height: 44px; + padding: 7px 10px; + border-bottom: 1px solid #eef2f8; + cursor: pointer; + transition: background-color var(--trans); } .file-item:hover { - background-color: #f8f9fa; + background: #f4f7ff; } .file-item.selected { - background-color: #0d6efd; - color: white; -} - -.file-item.selected .file-name { - color: #ffffff; + background: #1f3b7a; + color: #fff; } .file-icon { - margin-right: 10px; - width: 40px; - text-align: center; - font-size: 16px; - flex-shrink: 0; + display: inline-flex; } -/* 文件类型徽标(替代 emoji 图标) */ .file-type { display: inline-flex; align-items: center; justify-content: center; - min-width: 34px; - padding: 2px 8px; + min-width: 40px; + height: 22px; border-radius: 999px; - background-color: rgba(15, 23, 42, 0.06); - color: #0f172a; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; + background: #edf1f9; + color: #273042; + font-size: 10px; + font-weight: 700; letter-spacing: 0.04em; } .file-type-dir { - background-color: rgba(37, 99, 235, 0.15); - color: #1d4ed8; + background: #e8eefc; + color: #1f3b7a; } .file-item.selected .file-type, .file-item.selected .file-type-dir { - background-color: rgba(15, 23, 42, 0.32); - color: #ffffff; + background: rgba(255, 255, 255, 0.2); + color: #fff; } -.file-name { - flex: 1; +.file-name, +.file-size, +.file-date { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.file-size { - margin-left: 10px; - min-width: 80px; - text-align: right; - font-size: 12px; - color: #666; - flex-shrink: 0; -} - +.file-size, .file-date { - margin-left: 10px; - min-width: 140px; - text-align: right; + color: #5e6778; font-size: 12px; - color: #666; - flex-shrink: 0; } -/* 选中状态下的文件大小和日期 */ .file-item.selected .file-size, -.file-item.selected .file-date { - color: rgba(255, 255, 255, 0.9); +.file-item.selected .file-date, +.file-item.selected .file-name { + color: #fff; +} + +.panel.active-panel { + box-shadow: inset 0 0 0 2px rgba(216, 138, 38, 0.55); +} + +.panel.drag-over, +.file-list.drag-over { + background: #eef3ff; + outline: 2px dashed #4b69a7; + outline-offset: -2px; } -/* 状态栏 */ .status-bar { - flex-shrink: 0; - padding: 4px 16px; + min-height: 38px; + border: 1px solid var(--line); + border-radius: var(--radius-sm); + background: var(--surface); + display: flex; + align-items: center; + padding: 0 12px; font-size: 12px; - color: #666; - background-color: #f9fafb; - border-top: 1px solid #e5e7eb; + color: var(--muted); } -/* 上下文菜单 */ -.context-menu { - position: absolute; - background-color: #fff; - border: 1px solid #ccc; - box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); - z-index: 1000; - min-width: 120px; -} - -.menu-item { - padding: 8px 12px; - cursor: pointer; - user-select: none; -} - -.menu-item:hover { - background-color: #0d6efd; - color: white; -} - -/* 连接列表 */ .connection-item { - padding: 8px 12px; - border-bottom: 1px solid #f0f0f0; + padding: 10px 12px; + border-bottom: 1px solid #eef2f8; display: flex; justify-content: space-between; align-items: center; -} - -.connection-item:hover { - background-color: #f8f9fa; + gap: 8px; } .connection-name { - font-weight: 600; + font-weight: 700; } .connection-info { font-size: 12px; - color: #666; + color: var(--muted); } .connection-actions { display: flex; - gap: 4px; -} - -.connection-actions button { - padding: 2px 8px; - font-size: 12px; -} - -/* 新建文件夹项(内联编辑) */ -.new-folder { - background-color: #e7f3ff; -} - -.new-folder-input { - width: 100%; - padding: 2px 4px; - border: 1px solid #0d6efd; - outline: none; - font-size: 14px; -} - -/* 页面头部信息区域 */ -.app-header { - padding: 12px 20px 4px; - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; - background: linear-gradient(120deg, rgba(37, 99, 235, 0.07), rgba(56, 189, 248, 0.04)); - border-bottom: 1px solid rgba(148, 163, 184, 0.35); -} - -.app-header-main { - max-width: 70%; -} - -.app-title { - font-size: 18px; - font-weight: 600; - color: #0f172a; - margin-bottom: 4px; -} - -.app-subtitle { - font-size: 13px; - color: #6b7280; - margin-bottom: 0; -} - -.app-header-tags { - display: flex; - flex-wrap: wrap; gap: 6px; - justify-content: flex-end; } -.header-tag { - padding: 3px 10px; - border-radius: 999px; - font-size: 11px; - color: #1e40af; - background-color: rgba(59, 130, 246, 0.08); - border: 1px solid rgba(59, 130, 246, 0.18); - white-space: nowrap; +.context-menu { + position: fixed; + z-index: 50; } -/* 全局聚焦可见样式(键盘导航) */ *:focus-visible { - outline: 2px solid var(--color-primary); + outline: 2px solid #335baf; outline-offset: 2px; } -/* 响应式设计 */ -@media (max-width: 768px) { - .panels-container { - flex-direction: column; +@media (max-width: 1180px) { + .summary-strip { + grid-template-columns: 1fr 1fr; } - .panel { - height: 50%; - border-right: none; - border-bottom: 1px solid #dee2e6; + .summary-item-wide { + grid-column: 1 / -1; + } +} + +@media (max-width: 980px) { + body { + overflow: auto; + } + + .app-shell { + height: auto; + min-height: 100vh; + } + + .panes-grid { + grid-template-columns: 1fr; + grid-template-rows: minmax(320px, 1fr) minmax(320px, 1fr); + } + + .file-item { + grid-template-columns: 52px 1fr 88px; + } + + .file-date { + display: none; + } +} + +@media (max-width: 680px) { + .topbar { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .topbar-actions { + width: 100%; + justify-content: flex-start; + flex-wrap: wrap; + } + + .summary-strip { + grid-template-columns: 1fr; + } + + .ops-group-right, + .transfer-progress { + margin-left: 0; + width: 100%; + } + + .ops-group { + width: 100%; + } + + .ops-group .btn, + .ops-group-right .btn { + flex: 1; + min-width: 128px; + } + + .pane-head-controls { + grid-template-columns: 1fr; + } + + .connection-status { + width: fit-content; + } + + .file-item { + grid-template-columns: 44px 1fr; + gap: 6px; } .file-size, @@ -481,214 +641,11 @@ body { display: none; } - .toolbar { - flex-wrap: wrap; - } - - .navbar-brand { - font-size: 16px; + .slave-target-list { + grid-template-columns: 1fr; } } -@media (max-width: 576px) { - .toolbar button { - padding: 2px 8px; - font-size: 12px; - } - - .file-item { - padding: 6px 8px; - } -} - -/* ============================ - ui-ux-pro-max 主题覆盖样式 - ============================ */ - -/* 设计令牌(Minimal + Gold) */ -:root { - --color-primary: #171717; - --color-secondary: #404040; - --color-cta: #D4AF37; - - --color-text: #171717; - --color-bg: #ffffff; - - --radius-sm: 4px; - --radius-md: 14px; - --shadow-card: 0 24px 60px rgba(15, 23, 42, 0.18); - - --transition-base: 150ms ease; -} - -body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - color: var(--color-text, #171717); - background-color: var(--color-bg, #ffffff); - background-image: - radial-gradient(circle at top left, rgba(23, 23, 23, 0.04) 0, transparent 55%), - radial-gradient(circle at bottom right, rgba(64, 64, 64, 0.04) 0, transparent 55%); - -webkit-font-smoothing: antialiased; -} - -.app-container { - border-radius: var(--radius-md); - box-shadow: var(--shadow-card); - border: 1px solid rgba(15, 23, 42, 0.06); -} - -.navbar { - background-color: #171717 !important; - border-bottom: 1px solid #27272a; -} - -.navbar-brand { - letter-spacing: 0.02em; -} - -.navbar .btn-primary.btn-sm { - background-color: var(--color-cta, #D4AF37); - border-color: var(--color-cta, #D4AF37); - color: #171717; - font-weight: 500; - padding-inline: 14px; - border-radius: 999px; - transition: background-color var(--transition-base), border-color var(--transition-base), transform var(--transition-base), box-shadow var(--transition-base); -} - -.navbar .btn-primary.btn-sm:hover { - background-color: #b48b1f; - border-color: #b48b1f; - transform: translateY(-1px); - box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25); -} - -.navbar .btn-primary.btn-sm:active { - transform: translateY(0); - box-shadow: none; -} - -.toolbar { - background-color: #f9fafb; - border-bottom: 1px solid #e5e7eb; -} - -.toolbar .btn-group { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.toolbar .btn { - border-radius: 999px; - padding: 4px 14px; - font-size: 13px; - font-weight: 500; - border-width: 1px; - border-style: solid; - border-color: rgba(148, 163, 184, 0.55); - background-color: #ffffff; - color: var(--color-secondary, #404040); - transition: background-color var(--transition-base), color var(--transition-base), border-color var(--transition-base), box-shadow var(--transition-base), transform var(--transition-base); -} - -.toolbar .btn-outline-primary { - border-color: rgba(148, 163, 184, 0.7); -} - -.toolbar .btn-outline-primary:hover { - background-color: var(--color-primary); - color: #ffffff; - border-color: var(--color-primary); - box-shadow: 0 8px 20px rgba(15, 23, 42, 0.18); -} - -.toolbar .btn-outline-secondary { - border-color: rgba(148, 163, 184, 0.55); - color: var(--color-secondary); - background-color: #f9fafb; -} - -.toolbar .btn-outline-secondary:hover { - background-color: #e5e7eb; - border-color: #9ca3af; -} - -.toolbar .btn-outline-danger { - border-color: rgba(220, 38, 38, 0.35); - color: #b91c1c; - background-color: #fef2f2; -} - -.toolbar .btn-outline-danger:hover { - background-color: #dc2626; - border-color: #b91c1c; - color: #ffffff; -} - -.toolbar .btn:active { - transform: scale(0.97); - box-shadow: none; -} - -.panel { - border-right: 1px solid #e5e7eb; -} - -.panel.active-panel { - box-shadow: inset 0 0 0 1px rgba(212, 175, 55, 0.65); -} - -.panel-header { - background-color: #f3f4f6; - border-bottom: 1px solid #e5e7eb; -} - -.path-bar { - background-color: #ffffff; - border-bottom: 1px solid #e5e7eb; -} - -.file-item { - transition: background-color var(--transition-base), color var(--transition-base); -} - -.app-header { - background: linear-gradient(120deg, rgba(23, 23, 23, 0.03), rgba(64, 64, 64, 0.02)); -} - -.app-title { - font-size: 20px; -} - -.header-tag { - color: #404040; - background-color: #f4f4f5; - border: 1px solid #e4e4e7; -} - -/* 主按钮:统一金色 CTA 风格 */ -.btn-primary { - background-color: var(--color-cta, #D4AF37); - border-color: var(--color-cta, #D4AF37); - color: #171717; - font-weight: 500; - transition: background-color var(--transition-base), border-color var(--transition-base), box-shadow var(--transition-base), transform var(--transition-base); -} - -.btn-primary:hover { - background-color: #b48b1f; - border-color: #b48b1f; - box-shadow: 0 10px 24px rgba(0, 0, 0, 0.25); - transform: translateY(-1px); -} - -.btn-primary:active { - transform: translateY(0); - box-shadow: none; -} - -/* 尊重用户的「减少动态效果」系统设置 */ @media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index facef69..ef0b4f0 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -4,116 +4,153 @@