From adaa6e702fb574093ae2a9484e3e63b9c8abe687 Mon Sep 17 00:00:00 2001 From: yyx <20328610@qq.com> Date: Wed, 24 Dec 2025 19:41:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=B1=E8=89=B2=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E7=BB=86=E8=8A=82=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ImagePreviewModal.vue | 116 ++++++++- .../src/components/IntensitySlider.vue | 29 +++ src/frontend/src/components/NavBar.vue | 41 +-- src/frontend/src/components/Toast.vue | 145 +++++++++++ src/frontend/src/utils/request.js | 33 ++- src/frontend/src/utils/toast.js | 68 +++++ src/frontend/src/views/MainFlow.vue | 10 +- .../src/views/Page1/subpages/QuickMode.vue | 43 ++++ .../views/Page1/subpages/UniversalMode.vue | 95 +++++++ .../views/Page2/subpages/SubpageContainer.vue | 57 +++++ .../views/Page3/subpages/SubpageContainer.vue | 68 +++++ src/frontend/src/views/Page4/Page4.vue | 235 +++++++++++++++--- .../views/Page5/subpages/SubpageContainer.vue | 52 ++++ src/frontend/src/views/RegisterView.vue | 176 ++++++++----- 14 files changed, 1037 insertions(+), 131 deletions(-) create mode 100644 src/frontend/src/components/Toast.vue create mode 100644 src/frontend/src/utils/toast.js diff --git a/src/frontend/src/components/ImagePreviewModal.vue b/src/frontend/src/components/ImagePreviewModal.vue index 1855833..d3ae859 100644 --- a/src/frontend/src/components/ImagePreviewModal.vue +++ b/src/frontend/src/components/ImagePreviewModal.vue @@ -36,7 +36,7 @@
-

加载预览中...

+
@@ -545,4 +545,118 @@ const close = () => { emit('close') } .modal-fade-enter-active, .modal-fade-leave-active { transition: opacity 0.3s ease; } .modal-fade-enter-from, .modal-fade-leave-to { opacity: 0; } + +/* + =========================================== + 🌑 深色模式适配 (Dark Mode) + =========================================== +*/ + +/* 弹窗卡片主体 */ +html.dark-mode .preview-card { + background: #1e293b; /* Slate 800 */ + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7); +} + +/* 头部 */ +html.dark-mode .card-header { + background: rgba(30, 41, 59, 0.95); + border-bottom-color: rgba(255, 255, 255, 0.1); +} + +html.dark-mode .header-info h3 { + color: #f1f5f9; +} + +html.dark-mode .task-tag { + background: rgba(255, 255, 255, 0.1); + color: #cbd5e1; +} + +html.dark-mode .close-btn { + color: #94a3b8; +} +html.dark-mode .close-btn:hover { + color: #fff; +} + +/* 内容区域背景 */ +html.dark-mode .card-body { + background: #0f172a; /* Slate 900 */ +} + +html.dark-mode .loading-state, +html.dark-mode .error-state { + color: #94a3b8; +} + +/* === 图片对比模式 === */ +html.dark-mode .img-box { + background: #1e293b; + border-color: rgba(255, 255, 255, 0.1); + box-shadow: 0 4px 12px rgba(0,0,0,0.3); +} + +html.dark-mode .no-img { + color: #64748b; +} + +html.dark-mode .divider { + color: #475569; +} + +/* 导航箭头 (保持浅色或反转) */ +html.dark-mode .nav-arrow { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); +} +html.dark-mode .nav-arrow:hover { + background: var(--color-accent-secondary); + border-color: var(--color-accent-secondary); +} + +/* === 报告/热力图模式 === */ +html.dark-mode .report-stage { + background: #0f172a; +} + +html.dark-mode .report-container { + background: #1e293b; + /* 滚动条深色适配 */ + scrollbar-color: #475569 #1e293b; +} +html.dark-mode .report-container::-webkit-scrollbar-track { + background: #1e293b; +} +html.dark-mode .report-container::-webkit-scrollbar-thumb { + background-color: #475569; +} + +html.dark-mode .report-header-tip { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.1); + color: #e2e8f0; +} + +html.dark-mode .sub-tip { + color: #94a3b8; +} + +/* 底部 */ +html.dark-mode .card-footer { + background: #1e293b; + border-top-color: rgba(255, 255, 255, 0.1); +} + +html.dark-mode .thumb-item { + background: #334155; + color: #94a3b8; +} + +html.dark-mode .thumb-item.active { + background: var(--color-accent-secondary); /* 保持高亮色 */ + color: #fff; +} + \ No newline at end of file diff --git a/src/frontend/src/components/IntensitySlider.vue b/src/frontend/src/components/IntensitySlider.vue index dc1beef..4b08169 100644 --- a/src/frontend/src/components/IntensitySlider.vue +++ b/src/frontend/src/components/IntensitySlider.vue @@ -144,4 +144,33 @@ const backgroundStyle = computed(() => { font-size: 0.8rem; margin-top: 5px; } + +/* + =========================================== + 🌑 深色模式适配 (Dark Mode) + =========================================== +*/ +html.dark-mode .intensity-slider-container { + background: #0f172a; /* Slate 900 */ + border-color: rgba(255, 255, 255, 0.1); +} + +html.dark-mode .intensity-slider-container:hover { + background: #1e293b; /* Slate 800 */ + box-shadow: 0 4px 12px rgba(0,0,0,0.3); +} + +html.dark-mode .label { + color: #f1f5f9; +} + +html.dark-mode .custom-range::-webkit-slider-thumb { + background: #1e293b; + border-color: var(--color-accent-secondary); +} + +html.dark-mode .range-labels { + color: #64748b; +} + \ No newline at end of file diff --git a/src/frontend/src/components/NavBar.vue b/src/frontend/src/components/NavBar.vue index 88d4c71..cfbe362 100644 --- a/src/frontend/src/components/NavBar.vue +++ b/src/frontend/src/components/NavBar.vue @@ -245,10 +245,16 @@ label[for="nav-toggle"] { /* === 降级 - 底部导航栏模式 (宽 < 900px 或 高缩放) === */ +/* === 降级 - 顶部导航栏模式 (宽 < 900px 或 高缩放) === */ @media (max-width: 900px) { #navbar-container { width: 100% !important; height: auto !important; - top: auto; bottom: 0; left: 0; + + /* 改为定位在顶部 */ + top: 0; + bottom: auto; + left: 0; + flex-direction: row; justify-content: center; background: transparent; z-index: 999; } @@ -257,9 +263,14 @@ label[for="nav-toggle"] { width: 100% !important; height: 70px !important; min-height: auto; max-height: none; margin: 0 !important; - border-radius: 20px 20px 0 0; + + /* 圆角改为下面两个角,更像挂在顶部的 header */ + border-radius: 0 0 20px 20px; + flex-direction: row; - box-shadow: 0 -5px 20px rgba(0,0,0,0.1); + + /* 阴影向下投射 */ + box-shadow: 0 5px 20px rgba(0,0,0,0.1); } #nav-header { display: none; } @@ -283,30 +294,32 @@ label[for="nav-toggle"] { .nav-button.active { background: var(--color-bg-primary); color: var(--color-accent-secondary); - transform: translateY(-5px); + transform: translateY(5px); /* 向下微动 */ box-shadow: 0 5px 10px rgba(0,0,0,0.1); } /* - 【核心修复】 - 在窄屏/高缩放模式下,强制按钮横向排列 (row) - 这样只占用一行高度,不会被顶出屏幕 + 外部操作按钮组 (登出/主题/个人中心) + 导航栏在顶部后,这些按钮如果也在顶部会很挤。 + 建议将它们作为 "悬浮按钮 (FAB)" 放在屏幕右下角。 */ .external-actions { - position: absolute; - right: 15px; - bottom: 85px; /* 位于底部导航栏上方 */ + position: fixed; /* 固定定位,脱离导航栏流 */ + right: 20px; + bottom: 30px; /* 放在底部,方便手指点击 */ - flex-direction: row; /* 横向排列 */ - align-items: center; /* 垂直居中对齐 */ + flex-direction: column-reverse; /* 竖向排列,或者 row 横向排列 */ + align-items: center; width: auto !important; margin: 0; padding: 0; - gap: 12px; + gap: 15px; + z-index: 1000; /* 确保在最上层 */ } + /* 稍微缩小按钮尺寸,适配移动端 */ .page-5-btn, .logout-btn, .theme-btn { - width: 45px; height: 45px; font-size: 1.1rem; + width: 48px; height: 48px; font-size: 1.1rem; box-shadow: 0 4px 15px rgba(0,0,0,0.2); } .logout-btn { background: rgba(255, 107, 107, 0.9); color: white; border: none; } diff --git a/src/frontend/src/components/Toast.vue b/src/frontend/src/components/Toast.vue new file mode 100644 index 0000000..11c82d5 --- /dev/null +++ b/src/frontend/src/components/Toast.vue @@ -0,0 +1,145 @@ + + + + + \ No newline at end of file diff --git a/src/frontend/src/utils/request.js b/src/frontend/src/utils/request.js index 5c9778e..cc9ee8c 100644 --- a/src/frontend/src/utils/request.js +++ b/src/frontend/src/utils/request.js @@ -1,6 +1,7 @@ import axios from 'axios' import router from '@/router' import { useUserStore } from '@/stores/userStore' +import toast from '@/utils/toast' // 创建 axios 实例 const service = axios.create({ @@ -17,6 +18,11 @@ service.interceptors.request.use( if (userStore.token) { config.headers['Authorization'] = `Bearer ${userStore.token}` } + //如果是二进制流请求,处理特殊配置 + if (config.returnRawResponse) { + config.responseType = 'arraybuffer' + } + return config }, error => { @@ -57,7 +63,7 @@ service.interceptors.response.use(undefined, (err) => { // === 响应拦截器 === service.interceptors.response.use( response => { - // 如果配置了 returnRawResponse,则返回完整对象(包含 headers) + //支持返回完整响应(用于 Multipart 解析获取 Headers) if (response.config.returnRawResponse) { return response } @@ -68,23 +74,30 @@ service.interceptors.response.use( if (error.response) { const status = error.response.status + // 尝试解析 Blob/ArrayBuffer 类型的错误信息 const data = error.response.data || {} - // 优先使用后端返回的 error 描述 - const serverMsg = data.error || data.message + + // 如果是 JSON 错误信息 + let serverMsg = data.error || data.message + + // 如果返回的是 ArrayBuffer (图片流报错), 需要转回文本 + if (data instanceof ArrayBuffer) { + try { + const text = new TextDecoder().decode(data) + const json = JSON.parse(text) + serverMsg = json.error || json.message + } catch (e) { /* ignore */ } + } switch (status) { case 400: message = serverMsg || '请求参数错误'; break; case 401: - // === [核心修改点] === - // 如果请求的是登录接口,401 代表账号密码错误 if (error.config.url.includes('/auth/login')) { message = '用户名或密码错误'; } else { - // 如果是其他接口,401 代表 Token 过期 message = '登录已过期,请重新登录'; const userStore = useUserStore() userStore.logout() - if (router.currentRoute.value.path !== '/login') { router.push('/login'); } @@ -99,10 +112,10 @@ service.interceptors.response.use( message = '请求超时,请检查网络'; } - // 修改弹窗逻辑: - // 如果不是 Token 过期的 401(即:是密码错误的 401,或者其他错误),都弹窗提示 + // 使用 Toast 替代 Alert (排除 Token 过期的自动跳转,避免重复弹窗) + // 401 非登录接口报错通常伴随跳转,可根据需求决定是否弹窗 if (error.response?.status !== 401 || error.config.url.includes('/auth/login')) { - alert(message) + toast.error(message) } return Promise.reject(new Error(message)) diff --git a/src/frontend/src/utils/toast.js b/src/frontend/src/utils/toast.js new file mode 100644 index 0000000..dec6fe5 --- /dev/null +++ b/src/frontend/src/utils/toast.js @@ -0,0 +1,68 @@ +// src/utils/toast.js +import { createVNode, render } from 'vue' +import ToastComponent from '@/components/Toast.vue' + +const instances = [] +let seed = 1 + +/** + * 核心渲染函数 + * @param {Object} options - { type, message, duration } + */ +const toast = (options) => { + // 创建容器 + const container = document.createElement('div') + const id = `toast_${seed++}` + + // 计算垂直偏移量 (堆叠效果) + let verticalOffset = 20 + instances.forEach(({ vm }) => { + verticalOffset += (vm.el?.offsetHeight || 60) + 16 + }) + + // 销毁回调 + const onClose = () => { + const idx = instances.findIndex(item => item.id === id) + if (idx === -1) return + + // 移除当前实例 + const { container } = instances[idx] + render(null, container) // 卸载 Vue 组件 + // document.body.removeChild(container) // 移除 DOM (render null 有时会残留空 div,手动移除更保险) + // 注意:createVNode 渲染的组件在 leave 动画结束后会由组件内部逻辑处理, + // 但这里我们简单处理:组件 close 后触发 props.onClose + + instances.splice(idx, 1) // 从数组移除 + + // 重新计算剩余 Toast 的位置 (向上移动填补空缺) + // 简化版:这里暂不实现动态上移补位动画,仅防止重叠 + // 若要完美补位需要响应式 top,这里为简单起见不做复杂布局重算 + } + + // 创建虚拟节点 + const vm = createVNode(ToastComponent, { + ...options, + id, + offset: verticalOffset, + onClose: (closedId) => { + // 从 DOM 中彻底移除 + render(null, container) + const idx = instances.findIndex(ins => ins.id === closedId) + if (idx !== -1) instances.splice(idx, 1) + } + }) + + // 渲染并挂载 + render(vm, container) + document.body.appendChild(container.firstElementChild) // 将组件根元素移入 Body + + instances.push({ id, vm, container }) +} + +// 导出便捷方法 +export default { + success: (msg) => toast({ type: 'success', message: msg }), + error: (msg) => toast({ type: 'error', message: msg }), + warning: (msg) => toast({ type: 'warning', message: msg }), + info: (msg) => toast({ type: 'info', message: msg }) +} \ No newline at end of file diff --git a/src/frontend/src/views/MainFlow.vue b/src/frontend/src/views/MainFlow.vue index ee6f735..b9a928c 100644 --- a/src/frontend/src/views/MainFlow.vue +++ b/src/frontend/src/views/MainFlow.vue @@ -332,10 +332,9 @@ onUnmounted(() => { .fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; } -/* === 核心修改:高缩放/窄屏模式 === */ -/* 当屏幕宽度小于 900px(约等于放大 200%-300%)时 */ +/* === 高缩放/窄屏模式 === */ @media (max-width: 900px) { - /* 1. 内容取消左边距(因为导航栏跑下面去了) */ + /* 1. 内容取消左边距 */ .layout-content, .layout-content.nav-expanded { margin-left: 0 !important; } @@ -346,9 +345,10 @@ onUnmounted(() => { z-index: 200; } - /* 3. 增加底部 Padding,防止内容被底部导航遮挡 */ + /* 3. 调整 Padding 以适应顶部导航栏 */ .scroll-section.is-active, .page-standalone, .subpage-wrapper { - padding-bottom: 90px !important; + padding-top: 100px !important; + padding-bottom: 40px !important; } } \ No newline at end of file diff --git a/src/frontend/src/views/Page1/subpages/QuickMode.vue b/src/frontend/src/views/Page1/subpages/QuickMode.vue index 317ff4e..72f0bb7 100644 --- a/src/frontend/src/views/Page1/subpages/QuickMode.vue +++ b/src/frontend/src/views/Page1/subpages/QuickMode.vue @@ -224,4 +224,47 @@ onUnmounted(() => { if (specificPollTimer) clearInterval(specificPollTimer) }) .card-body { overflow: visible; padding: 20px; } .style-selector { grid-template-columns: 1fr; gap: 10px; } } + +/* + =========================================== + 🌑 深色模式适配 (Dark Mode) + =========================================== +*/ +html.dark-mode .content-card { + background: #1e293b; + border: 1px solid rgba(255, 255, 255, 0.05); +} +html.dark-mode .card-header { border-bottom-color: rgba(255, 255, 255, 0.08); } +html.dark-mode .card-header h2 { color: #f1f5f9; } +html.dark-mode .desc-text { + background: rgba(255, 255, 255, 0.05); + color: #cbd5e1; + border-left-color: var(--color-accent-secondary); +} +html.dark-mode .form-group label { color: #e2e8f0; } +html.dark-mode .ui-input { + background: #0f172a; + border-color: rgba(255, 255, 255, 0.1); + color: #f1f5f9; +} +html.dark-mode .style-option { + background: #0f172a; +} +html.dark-mode .style-option:hover { background: #1e293b; } +html.dark-mode .style-option.active { + background: #1e293b; + border-color: var(--color-accent-secondary); +} +html.dark-mode .icon-circle { background: #1e293b; color: #94a3b8; } +html.dark-mode .style-option.active .icon-circle { background: var(--color-accent-secondary); color: #1e293b; } +html.dark-mode .opt-title { color: #f1f5f9; } +html.dark-mode .upload-zone { + background: #0f172a; + border-color: rgba(255,255,255,0.1); +} +html.dark-mode .upload-zone.has-file { + background: rgba(46, 125, 50, 0.1); + border-color: #2e7d32; +} +html.dark-mode .file-name { color: #f1f5f9; } \ No newline at end of file diff --git a/src/frontend/src/views/Page1/subpages/UniversalMode.vue b/src/frontend/src/views/Page1/subpages/UniversalMode.vue index de8e124..3f15117 100644 --- a/src/frontend/src/views/Page1/subpages/UniversalMode.vue +++ b/src/frontend/src/views/Page1/subpages/UniversalMode.vue @@ -394,4 +394,99 @@ const startSpecificPolling = (taskId) => { .row-group { flex-direction: column; gap: 15px; } .style-selector { grid-template-columns: 1fr; } } + +/* + =========================================== + 🌑 深色模式适配 (Dark Mode) + =========================================== +*/ +html.dark-mode .content-card { + background: #1e293b; + border: 1px solid rgba(255, 255, 255, 0.05); +} + +html.dark-mode .card-header { + border-bottom-color: rgba(255, 255, 255, 0.08); +} + +html.dark-mode .card-header h2 { color: #f1f5f9; } + +html.dark-mode .desc-text { + background: rgba(255, 255, 255, 0.05); + color: #cbd5e1; + border-left-color: var(--color-accent-secondary); +} + +html.dark-mode .form-group label { + color: #e2e8f0; +} + +/* 输入框 & 下拉框 */ +html.dark-mode .ui-input, +html.dark-mode .select-trigger { + background: #0f172a; + border-color: rgba(255, 255, 255, 0.1); + color: #f1f5f9; +} + +html.dark-mode .ui-input:focus, +html.dark-mode .select-trigger.is-open { + border-color: var(--color-accent-secondary); + background: #0f172a; +} + +html.dark-mode .select-options { + background: #1e293b; + border-color: rgba(255, 255, 255, 0.1); + box-shadow: 0 10px 30px rgba(0,0,0,0.5); +} + +html.dark-mode .option-item { color: #f1f5f9; } +html.dark-mode .option-item:hover { background: rgba(255,255,255,0.05); } +html.dark-mode .option-item.selected { background: rgba(255, 159, 28, 0.1); color: var(--color-accent-secondary); } + +/* 风格卡片 */ +html.dark-mode .style-option { + background: #0f172a; + border-color: transparent; +} +html.dark-mode .style-option.active { + background: #1e293b; + border-color: var(--color-contrast-dark); /* 或 accent color */ +} +html.dark-mode .icon-circle { + background: #1e293b; + color: #94a3b8; +} +html.dark-mode .style-option.active .icon-circle { + background: var(--color-contrast-dark); + color: #1e293b; +} +html.dark-mode .opt-title { color: #f1f5f9; } +html.dark-mode .opt-desc { color: #94a3b8; } + +/* 强度切换 */ +html.dark-mode .mode-toggle { background: #0f172a; } +html.dark-mode .mode-toggle span { color: #94a3b8; } +html.dark-mode .mode-toggle span.active { background: #334155; color: #f1f5f9; } + +html.dark-mode .strength-selector { background: #0f172a; border-color: rgba(255,255,255,0.1); } +html.dark-mode .str-item { color: #94a3b8; } +html.dark-mode .str-item:hover { background: rgba(255,255,255,0.05); } +html.dark-mode .str-item.active { background: var(--color-accent-secondary); color: #1e293b; } + +/* 上传区域 */ +html.dark-mode .upload-zone { + background: #0f172a; + border-color: rgba(255,255,255,0.1); +} +html.dark-mode .upload-zone:hover { + border-color: var(--color-accent-secondary); + background: rgba(255,255,255,0.02); +} +html.dark-mode .upload-zone.has-file { + background: rgba(46, 125, 50, 0.1); + border-color: #2e7d32; +} +html.dark-mode .file-name { color: #f1f5f9; } \ No newline at end of file diff --git a/src/frontend/src/views/Page2/subpages/SubpageContainer.vue b/src/frontend/src/views/Page2/subpages/SubpageContainer.vue index 370695e..fc6e771 100644 --- a/src/frontend/src/views/Page2/subpages/SubpageContainer.vue +++ b/src/frontend/src/views/Page2/subpages/SubpageContainer.vue @@ -339,4 +339,61 @@ const submitTask = async () => { .style-selector { grid-template-columns: 1fr; gap: 10px; } .style-presets-grid { grid-template-columns: 1fr; } } + +/* + =========================================== + 🌑 深色模式适配 (Dark Mode) + =========================================== +*/ +html.dark-mode .content-card { + background: #1e293b; + border: 1px solid rgba(255, 255, 255, 0.05); +} +html.dark-mode .card-header { border-bottom-color: rgba(255, 255, 255, 0.08); } +html.dark-mode .card-header h2 { color: #f1f5f9; } +html.dark-mode .desc-text { + background: rgba(255, 255, 255, 0.05); + color: #cbd5e1; + border-left-color: var(--color-contrast-dark); /* or accent */ +} +html.dark-mode .form-group label { color: #e2e8f0; } +html.dark-mode .ui-input { + background: #0f172a; + border-color: rgba(255, 255, 255, 0.1); + color: #f1f5f9; +} +html.dark-mode .readonly-field { + background: #0f172a; + border-color: rgba(255,255,255,0.1); + color: #94a3b8; +} +html.dark-mode .style-option { background: #0f172a; } +html.dark-mode .style-option.active { background: #1e293b; border-color: var(--color-accent-secondary); } +html.dark-mode .icon-circle { background: #1e293b; color: #94a3b8; } +html.dark-mode .style-option.active .icon-circle { background: var(--color-accent-secondary); color: #1e293b; } +html.dark-mode .opt-title { color: #f1f5f9; } + +/* 风格预设卡片 */ +html.dark-mode .preset-card { + background: #0f172a; + border-color: transparent; +} +html.dark-mode .preset-card:hover { background: #1e293b; } +html.dark-mode .preset-card.active { + background: #1e293b; + border-color: var(--color-accent-secondary); +} +html.dark-mode .preset-name { color: #f1f5f9; } +html.dark-mode .preset-desc { color: #94a3b8; } + +html.dark-mode .upload-zone { + background: #0f172a; + border-color: rgba(255,255,255,0.1); +} +html.dark-mode .upload-zone.has-file { + background: rgba(46, 125, 50, 0.1); + border-color: #2e7d32; +} +html.dark-mode .file-name { color: #f1f5f9; } + \ No newline at end of file diff --git a/src/frontend/src/views/Page3/subpages/SubpageContainer.vue b/src/frontend/src/views/Page3/subpages/SubpageContainer.vue index 8c25f51..f262114 100644 --- a/src/frontend/src/views/Page3/subpages/SubpageContainer.vue +++ b/src/frontend/src/views/Page3/subpages/SubpageContainer.vue @@ -495,4 +495,72 @@ onMounted(() => taskStore.fetchTasks()) .tab-btn { width: 100%; text-align: center; } .style-selector { grid-template-columns: 1fr; } } + +/* + =========================================== + 🌑 深色模式适配 (Dark Mode) + =========================================== +*/ +html.dark-mode .content-card { + background: #1e293b; + border: 1px solid rgba(255, 255, 255, 0.05); +} +html.dark-mode .card-header { border-bottom-color: rgba(255, 255, 255, 0.08); } +html.dark-mode .card-header h2 { color: #f1f5f9; } +html.dark-mode .form-group label { color: #e2e8f0; } +html.dark-mode .ui-input { + background: #0f172a; + border-color: rgba(255, 255, 255, 0.1); + color: #f1f5f9; +} + +/* Tabs */ +html.dark-mode .mode-tabs { border-bottom-color: rgba(255,255,255,0.1); } +html.dark-mode .tab-btn { color: #94a3b8; } +html.dark-mode .tab-btn.active { background: var(--color-accent-secondary); color: #1e293b; } +html.dark-mode .tab-btn.vip.active { color: #fff; } + +/* 来源选择 */ +html.dark-mode .source-trigger { + background: #0f172a; + border-color: rgba(255,255,255,0.1); + color: #f1f5f9; +} +html.dark-mode .source-list { + background: #1e293b; + border-color: rgba(255,255,255,0.1); + box-shadow: 0 10px 30px rgba(0,0,0,0.5); +} +html.dark-mode .source-item { color: #cbd5e1; } +html.dark-mode .source-item:hover { background: rgba(255,255,255,0.05); } + +/* 图片选择网格 */ +html.dark-mode .image-select-container { + background: #0f172a; + border-color: rgba(255,255,255,0.1); +} +html.dark-mode .img-item { background: #1e293b; } +html.dark-mode .single-preview-area { + background: #0f172a; + border-color: rgba(255,255,255,0.1); +} +html.dark-mode .preview-label { color: #94a3b8; } +html.dark-mode .preview-img-sm { border-color: rgba(255,255,255,0.1); } + +/* 样式选择卡片 */ +html.dark-mode .style-option { background: #0f172a; } +html.dark-mode .style-option:hover { background: #1e293b; } +html.dark-mode .style-option.active { + background: #1e293b; + border-color: var(--color-accent-secondary); +} +html.dark-mode .opt-title { color: #f1f5f9; } +html.dark-mode .opt-desc { color: #94a3b8; } + +html.dark-mode .upload-zone { + background: #0f172a; + border-color: rgba(255,255,255,0.1); +} +html.dark-mode .file-name { color: #f1f5f9; } + \ No newline at end of file diff --git a/src/frontend/src/views/Page4/Page4.vue b/src/frontend/src/views/Page4/Page4.vue index cb3939c..20b77d0 100644 --- a/src/frontend/src/views/Page4/Page4.vue +++ b/src/frontend/src/views/Page4/Page4.vue @@ -8,7 +8,7 @@

Task History Management

- +
- +
- +
ID @@ -96,6 +96,7 @@
+ {{ getTaskName(task) }}
@@ -205,8 +206,8 @@ const userStore = useUserStore() // 筛选状态 const currentStatus = ref('all') -const searchKeyword = ref('') // 新增:搜索关键词 -const selectedTaskType = ref('all') // 新增:类型筛选 +const searchKeyword = ref('') +const selectedTaskType = ref('all') const statusTabs = [ { key: 'all', label: '全部' }, @@ -221,7 +222,6 @@ const showPreview = ref(false) const previewTaskId = ref(null) const previewTaskType = ref('') -// 日志状态 const showLogModal = ref(false) const currentLogTaskId = ref(null) const logContent = ref('') @@ -231,7 +231,6 @@ const normalizeStatus = (status) => { return status } -// 状态汉化 const formatStatusLabel = (s) => { const map = { running: '运行中', processing: '处理中', pending: '排队中', waiting: '排队中', @@ -240,7 +239,6 @@ const formatStatusLabel = (s) => { return map[s] || s } -// 类型汉化 const formatTypeLabel = (t) => { const map = { perturbation: '通用防护', @@ -263,33 +261,26 @@ const formatDate = (iso) => iso ? new Date(iso).toLocaleString('zh-CN', { hour12 const handleStatusChange = (status) => { currentStatus.value = status; currentPage.value = 1 } -// === 核心过滤逻辑修改 === const filteredAndSortedTasks = computed(() => { let result = [...(taskStore.tasks || [])] - // 1. 状态筛选 if (currentStatus.value !== 'all') { result = result.filter(t => normalizeStatus(t.status) === currentStatus.value) } - // 2. 类型筛选 (新增) if (selectedTaskType.value !== 'all') { result = result.filter(t => t.task_type === selectedTaskType.value) } - // 3. 关键词搜索 (新增) if (searchKeyword.value.trim()) { const keyword = searchKeyword.value.toLowerCase().trim() result = result.filter(t => { - // 搜索 ID const idMatch = String(t.task_id).includes(keyword) - // 搜索 任务名称 const nameMatch = getTaskName(t).toLowerCase().includes(keyword) return idMatch || nameMatch }) } - // 4. 排序 if (sortRules.value.length > 0) { result.sort((a, b) => { const rule = sortRules.value[0] @@ -399,6 +390,7 @@ onMounted(() => taskStore.fetchTasks()) box-sizing: border-box; padding: 40px 20px 120px 20px; } +/* Header */ .header-section { flex: 0 0 auto; margin-bottom: 30px; display: flex; justify-content: space-between; align-items: flex-end; @@ -409,6 +401,7 @@ onMounted(() => taskStore.fetchTasks()) } .page-desc { color: #94a3b8; margin-top: 6px; font-weight: 500; } +/* Filter Tabs */ .filter-tabs { display: flex; background: rgba(255,255,255,0.6); padding: 5px; border-radius: 12px; gap: 5px; @@ -483,7 +476,7 @@ onMounted(() => taskStore.fetchTasks()) border: 1px solid #e2e8f0; border-radius: 8px; font-size: 0.9rem; color: #334155; background: #f8fafc; cursor: pointer; - appearance: none; /* remove default arrow */ + appearance: none; } .filter-select:focus { background: #fff; border-color: var(--color-accent-secondary); @@ -497,11 +490,31 @@ onMounted(() => taskStore.fetchTasks()) */ .list-header-row { display: grid; - grid-template-columns: 80px 2fr 120px 180px 140px 160px; + /* + 全列自适应比例分配: + 不再使用固定宽度,而是用 minmax + fr 组合。 + 总权重约 8份,按内容重要性分配: + 1. ID: 0.5fr (最小60px) + 2. 任务名: 3fr (最小200px,占主要空间) + 3. 类型: 1fr (最小100px) + 4. 时间: 1.5fr (最小160px,时间较长) + 5. 状态: 1fr (最小100px) + 6. 操作: 1fr (最小120px) + + 效果:无论屏幕多宽,所有列都会均匀拉伸,不再有死角空白。 + */ + grid-template-columns: + minmax(60px, 0.5fr) + minmax(200px, 3fr) + minmax(100px, 1fr) + minmax(160px, 1.5fr) + minmax(100px, 1fr) + minmax(120px, 1fr); + padding: 20px 30px; align-items: center; background: transparent; - border-bottom: 2px solid #e2e8f0; /* 加深线条 */ + border-bottom: 2px solid #e2e8f0; font-size: 0.9rem; font-weight: 700; @@ -518,33 +531,43 @@ onMounted(() => taskStore.fetchTasks()) /* ------------------------------------------ - Table Rows (Clean Style) + Table Rows (Clean Style + Multiline Support) ------------------------------------------ */ .list-row { display: grid; - grid-template-columns: 80px 2fr 120px 180px 140px 160px; - padding: 18px 30px; - align-items: center; + /* 与 Header 保持完全一致的列定义 */ + grid-template-columns: + minmax(60px, 0.5fr) + minmax(200px, 3fr) + minmax(100px, 1fr) + minmax(160px, 1.5fr) + minmax(100px, 1fr) + minmax(120px, 1fr); + + padding: 20px 30px; + align-items: center; - /* 分隔线设计 */ - border-bottom: 1px solid #e2e8f0; + border-bottom: 1px solid #e2e8f0; background: #fff; transition: background-color 0.2s; } -/* 移除悬浮特效,仅保留背景色微调 */ .list-row:hover { - background-color: #f8fafc; /* 极淡的灰色 */ - /* transform: none; box-shadow: none; removed */ + background-color: #f8fafc; /* 仅背景色微调 */ } .id-text { font-family: monospace; color: #94a3b8; font-weight: 600; } + +/* 任务名称样式:允许换行 */ .task-name { font-weight: 600; color: #334155; font-size: 0.95rem; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + white-space: normal; /* 允许换行 */ + word-break: break-word; /* 防止长单词溢出 */ + line-height: 1.5; /* 增加行高,多行时更好看 */ padding-right: 20px; } + .time-text { color: #64748b; font-size: 0.85rem; font-variant-numeric: tabular-nums; } .type-tag { @@ -589,7 +612,7 @@ onMounted(() => taskStore.fetchTasks()) .pagination-footer { padding: 20px 30px; display: flex; justify-content: flex-end; align-items: center; - border-top: 1px solid #e2e8f0; /* 分隔线 */ + border-top: 1px solid #e2e8f0; background: #fff; } .page-info { margin: 0 15px; color: #64748b; font-size: 0.9rem; font-weight: 500; } @@ -616,7 +639,6 @@ onMounted(() => taskStore.fetchTasks()) .header-section { flex-direction: column; align-items: flex-start; gap: 15px; } .filter-tabs { width: 100%; overflow-x: auto; padding-bottom: 5px; -webkit-overflow-scrolling: touch; } - /* 移动端隐藏工具栏的部分样式,或者调整布局 */ .toolbar-section { flex-direction: column; align-items: stretch; padding: 15px; } .search-box { width: 100%; } @@ -629,13 +651,16 @@ onMounted(() => taskStore.fetchTasks()) align-items: stretch; padding: 20px; margin-bottom: 0; - border-bottom: 10px solid #f1f5f9; /* 移动端用粗线条分隔 */ + border-bottom: 10px solid #f1f5f9; } .mobile-row-header { display: flex; justify-content: space-between; align-items: center; } .id-badge { background: #f1f5f9; padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; color: #64748b; font-weight: 700; } - .mobile-task-name { font-size: 1.1rem; font-weight: 700; color: #1e293b; margin: 5px 0; } + .mobile-task-name { + font-size: 1.1rem; font-weight: 700; color: #1e293b; margin: 5px 0; + white-space: normal; word-break: break-word; line-height: 1.4; + } .mobile-meta-row { display: flex; justify-content: space-between; align-items: center; font-size: 0.85rem; color: #94a3b8; } .col-action { @@ -694,4 +719,150 @@ onMounted(() => taskStore.fetchTasks()) align-items: center; justify-content: center; gap: 15px; color: #888; } .loading-log i { font-size: 2rem; color: var(--color-accent-secondary); } + +html.dark-mode .view-container { + /* 确保容器本身背景融合 */ + color: #e2e8f0; +} + +html.dark-mode .page-header { + color: #f1f5f9; +} + +html.dark-mode .modern-table-card { + background: #1e293b; /* Slate 800 */ + border: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: 0 20px 40px -5px rgba(0, 0, 0, 0.3); +} + +/* --- 搜索栏 --- */ +html.dark-mode .toolbar-section { + border-bottom-color: rgba(255, 255, 255, 0.08); +} + +html.dark-mode .search-input, +html.dark-mode .filter-select { + background: #0f172a; /* Slate 900 */ + border-color: rgba(255, 255, 255, 0.1); + color: #f1f5f9; +} + +html.dark-mode .search-input:focus, +html.dark-mode .filter-select:focus { + border-color: var(--color-accent-secondary); + background: #0f172a; +} + +/* --- 表头 --- */ +html.dark-mode .list-header-row { + border-bottom-color: rgba(255, 255, 255, 0.08); + color: #94a3b8; +} + +/* --- 表格行 --- */ +html.dark-mode .list-row { + background: #1e293b; + border-bottom-color: rgba(255, 255, 255, 0.05); +} + +html.dark-mode .list-row:hover { + background-color: rgba(255, 255, 255, 0.04); /* 微弱的悬浮高亮 */ +} + +/* 文本颜色修正 */ +html.dark-mode .task-name { + color: #f8fafc; /* 亮白色 */ +} + +html.dark-mode .id-text, +html.dark-mode .time-text { + color: #94a3b8; /* 灰色 */ +} + +html.dark-mode .type-tag { + background: rgba(255, 255, 255, 0.05); + border-color: rgba(255, 255, 255, 0.1); + color: #cbd5e1; +} + +/* --- 状态胶囊 (降低亮度,增加透明度) --- */ +html.dark-mode .status-pill.completed { + background: rgba(22, 163, 74, 0.2); + color: #4ade80; +} +html.dark-mode .status-pill.running { + background: rgba(7, 89, 133, 0.3); + color: #38bdf8; +} +html.dark-mode .status-pill.failed { + background: rgba(153, 27, 27, 0.3); + color: #f87171; +} + +/* --- 操作按钮 --- */ +html.dark-mode .action-btn { + color: #94a3b8; +} +html.dark-mode .action-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} + +/* --- 分页器 --- */ +html.dark-mode .pagination-footer { + background: #1e293b; + border-top-color: rgba(255, 255, 255, 0.08); +} + +html.dark-mode .page-btn { + background: #0f172a; + border-color: rgba(255, 255, 255, 0.1); + color: #cbd5e1; +} + +html.dark-mode .page-btn:hover:not(:disabled) { + background: #334155; + color: #fff; +} + +html.dark-mode .page-btn:disabled { + background: #1e293b; + opacity: 0.3; +} + +html.dark-mode .empty-state { + color: #64748b; +} + +/* --- 移动端卡片 --- */ +html.dark-mode .list-row { + /* 移动端复用了 list-row,但如果在窄屏下有特定边框需要处理 */ + box-shadow: none; +} + +html.dark-mode .id-badge { + background: rgba(255, 255, 255, 0.05); + color: #94a3b8; +} + +html.dark-mode .mobile-task-name { + color: #f1f5f9; +} + +@media (max-width: 900px) { + html.dark-mode .list-row { + border: 1px solid rgba(255, 255, 255, 0.05); + background: #1e293b; + border-bottom: 10px solid #0f172a; /* 深色背景间隙 */ + } + + html.dark-mode .col-action { + border-top-color: rgba(255, 255, 255, 0.1); + } + + html.dark-mode .action-btn { + background: rgba(255, 255, 255, 0.05); + } +} + \ No newline at end of file diff --git a/src/frontend/src/views/Page5/subpages/SubpageContainer.vue b/src/frontend/src/views/Page5/subpages/SubpageContainer.vue index 055ed0b..fb7fad3 100644 --- a/src/frontend/src/views/Page5/subpages/SubpageContainer.vue +++ b/src/frontend/src/views/Page5/subpages/SubpageContainer.vue @@ -449,4 +449,56 @@ onMounted(() => { justify-content: flex-end; } } + +/* + =========================================== + 🌑 深色模式适配 (Dark Mode) + =========================================== +*/ +html.dark-mode .content-card, +html.dark-mode .sub-modal-card { + background: #1e293b; + border: 1px solid rgba(255, 255, 255, 0.05); +} +html.dark-mode .overlay-header, +html.dark-mode .card-header, +html.dark-mode .log-header { + border-bottom-color: rgba(255, 255, 255, 0.08); +} +html.dark-mode h3 { color: #f1f5f9; } +html.dark-mode .form-group label { color: #e2e8f0; } +html.dark-mode .ui-input { + background: #0f172a; + border-color: rgba(255, 255, 255, 0.1); + color: #f1f5f9; +} + +/* 管理员表格 */ +html.dark-mode .table-wrapper { border-color: rgba(255,255,255,0.1); } +html.dark-mode .list-head-row { + background: #0f172a; + border-bottom-color: rgba(255,255,255,0.1); + color: #94a3b8; +} +html.dark-mode .list-row { + border-bottom-color: rgba(255,255,255,0.05); + color: #cbd5e1; +} +html.dark-mode .list-row:hover { background: rgba(255,255,255,0.02); } +html.dark-mode .u-name { color: #f1f5f9; } +html.dark-mode .u-email { color: #94a3b8; } +html.dark-mode .empty-row { color: #64748b; } + +/* 角色标签 */ +html.dark-mode .role-tag.user { background: rgba(255,255,255,0.1); color: #cbd5e1; } +html.dark-mode .role-tag.vip { background: rgba(255, 193, 7, 0.2); color: #ffc107; } + +/* 分页 */ +html.dark-mode .page-btn { + background: #0f172a; + border-color: rgba(255,255,255,0.1); + color: #cbd5e1; +} +html.dark-mode .page-btn:hover:not(:disabled) { background: #334155; } + \ No newline at end of file diff --git a/src/frontend/src/views/RegisterView.vue b/src/frontend/src/views/RegisterView.vue index 6389401..8073f23 100644 --- a/src/frontend/src/views/RegisterView.vue +++ b/src/frontend/src/views/RegisterView.vue @@ -20,40 +20,45 @@
+ -
- +
+ +
-
- +
+ +
- +
-
- +
+ +
+