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 @@
+
+
+
+
+
+
+
+
{{ title }}
+
{{ message }}
+
+
+
+
+
+
+
+
\ 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
-
+