From 93a61232cfb373a9a224f1b0bfee22b6f1af720a Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 6 Sep 2025 01:06:12 +0800 Subject: [PATCH 01/17] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E8=AE=B0=E5=BD=95=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/electron/main.ts | 20 +++++ frontend/electron/preload.ts | 4 + frontend/src/types/electron.d.ts | 4 + frontend/src/views/History.vue | 145 ++++++++++++++++++++++++------- 4 files changed, 144 insertions(+), 29 deletions(-) diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts index c634dd3..4af05d3 100644 --- a/frontend/electron/main.ts +++ b/frontend/electron/main.ts @@ -490,6 +490,26 @@ ipcMain.handle('open-url', async (_event, url: string) => { } }) +// 打开文件 +ipcMain.handle('open-file', async (_event, filePath: string) => { + try { + await shell.openPath(filePath) + } catch (error) { + console.error('打开文件失败:', error) + throw error + } +}) + +// 显示文件所在目录并选中文件 +ipcMain.handle('show-item-in-folder', async (_event, filePath: string) => { + try { + shell.showItemInFolder(filePath) + } catch (error) { + console.error('显示文件所在目录失败:', error) + throw error + } +}) + // 环境检查 ipcMain.handle('check-environment', async () => { const appRoot = getAppRoot() diff --git a/frontend/electron/preload.ts b/frontend/electron/preload.ts index 2f19132..4e64079 100644 --- a/frontend/electron/preload.ts +++ b/frontend/electron/preload.ts @@ -50,6 +50,10 @@ contextBridge.exposeInMainWorld('electronAPI', { saveLogsToFile: (logs: string) => ipcRenderer.invoke('save-logs-to-file', logs), loadLogsFromFile: () => ipcRenderer.invoke('load-logs-from-file'), + // 文件系统操作 + openFile: (filePath: string) => ipcRenderer.invoke('open-file', filePath), + showItemInFolder: (filePath: string) => ipcRenderer.invoke('show-item-in-folder', filePath), + // 监听下载进度 onDownloadProgress: (callback: (progress: any) => void) => { ipcRenderer.on('download-progress', (_, progress) => callback(progress)) diff --git a/frontend/src/types/electron.d.ts b/frontend/src/types/electron.d.ts index 33b22c3..832e76a 100644 --- a/frontend/src/types/electron.d.ts +++ b/frontend/src/types/electron.d.ts @@ -40,6 +40,10 @@ export interface ElectronAPI { saveLogsToFile: (logs: string) => Promise loadLogsFromFile: () => Promise + // 文件系统操作 + openFile: (filePath: string) => Promise + showItemInFolder: (filePath: string) => Promise + // 监听下载进度 onDownloadProgress: (callback: (progress: any) => void) => void removeDownloadProgressListener: () => void diff --git a/frontend/src/views/History.vue b/frontend/src/views/History.vue index 7882573..17965de 100644 --- a/frontend/src/views/History.vue +++ b/frontend/src/views/History.vue @@ -90,32 +90,6 @@
- - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -187,7 +161,21 @@
{{ record.date }} + + + {{ record.status }} + + @@ -287,6 +275,30 @@ @@ -316,13 +328,14 @@ import { HistoryOutlined, UserOutlined, GiftOutlined, - ExclamationCircleOutlined, FileSearchOutlined, FileTextOutlined, RightOutlined, + FolderOpenOutlined, + FileOutlined, } from '@ant-design/icons-vue' import { Service } from '@/api/services/Service' -import type { HistorySearchIn, HistoryData, HistoryDataGetIn } from '@/api/models' +import type { HistorySearchIn, HistoryData } from '@/api' import dayjs from 'dayjs' // 响应式数据 @@ -565,6 +578,70 @@ const loadUserLog = async (jsonFile: string) => { } } +// 打开日志文件 +const handleOpenLogFile = async () => { + if (!currentJsonFile.value) { + message.warning('请先选择一条记录') + return + } + + try { + // 将 .json 扩展名替换为 .log + const logFilePath = currentJsonFile.value.replace(/\.json$/, '.log') + + console.log('尝试打开日志文件:', logFilePath) + console.log('electronAPI 可用性:', !!window.electronAPI) + console.log('openFile 方法可用性:', !!(window.electronAPI && (window.electronAPI as any).openFile)) + + // 调用系统API打开文件 + if (window.electronAPI && (window.electronAPI as any).openFile) { + await (window.electronAPI as any).openFile(logFilePath) + message.success('日志文件已打开') + } else { + const errorMsg = !window.electronAPI + ? '当前环境不支持打开文件功能(electronAPI 不可用)' + : '当前环境不支持打开文件功能(openFile 方法不可用)' + console.error(errorMsg) + message.error(errorMsg) + } + } catch (error) { + console.error('打开日志文件失败:', error) + message.error(`打开日志文件失败: ${error}`) + } +} + +// 打开日志文件所在目录 +const handleOpenLogDirectory = async () => { + if (!currentJsonFile.value) { + message.warning('请先选择一条记录') + return + } + + try { + // 将 .json 扩展名替换为 .log + const logFilePath = currentJsonFile.value.replace(/\.json$/, '.log') + + console.log('尝试打开日志文件目录:', logFilePath) + console.log('electronAPI 可用性:', !!window.electronAPI) + console.log('showItemInFolder 方法可用性:', !!(window.electronAPI && (window.electronAPI as any).showItemInFolder)) + + // 调用系统API打开目录并选中文件 + if (window.electronAPI && (window.electronAPI as any).showItemInFolder) { + await (window.electronAPI as any).showItemInFolder(logFilePath) + message.success('日志文件目录已打开') + } else { + const errorMsg = !window.electronAPI + ? '当前环境不支持打开目录功能(electronAPI 不可用)' + : '当前环境不支持打开目录功能(showItemInFolder 方法不可用)' + console.error(errorMsg) + message.error(errorMsg) + } + } catch (error) { + console.error('打开日志文件目录失败:', error) + message.error(`打开日志文件目录失败: ${error}`) + } +} + // 获取日期状态颜色 const getDateStatusColor = (users: Record) => { const hasError = Object.values(users).some( @@ -612,7 +689,7 @@ const getDateStatusColor = (users: Record) => { /* 左侧日期栏 */ .date-sidebar { - width: 320px; + width: 200px; flex-shrink: 0; display: flex; flex-direction: column; @@ -947,6 +1024,16 @@ const getDateStatusColor = (users: Record) => { } } +/* 带tooltip的错误tag样式 */ +.error-tag-with-tooltip { + cursor: help; + position: relative; +} + +.error-tag-with-tooltip:hover { + opacity: 0.8; +} + /* 统计数据标题样式 */ .stat-subtitle { font-size: 12px; From fc8afe6624fe0bd626579173af6c88ceb54a116d Mon Sep 17 00:00:00 2001 From: AoXuan Date: Sat, 6 Sep 2025 01:17:39 +0800 Subject: [PATCH 02/17] =?UTF-8?q?refactor:=20=E5=BD=BB=E5=BA=95=E5=88=A0?= =?UTF-8?q?=E9=99=A4UserEdit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/router/index.ts | 12 - frontend/src/views/Scripts.vue | 19 +- frontend/src/views/UserEdit.vue | 1798 ------------------------------- 3 files changed, 15 insertions(+), 1814 deletions(-) delete mode 100644 frontend/src/views/UserEdit.vue diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index c6f489b..a316355 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -34,18 +34,6 @@ const routes: RouteRecordRaw[] = [ component: () => import('../views/ScriptEdit.vue'), meta: { title: '编辑脚本' }, }, - { - path: '/scripts/:scriptId/users/add', - name: 'UserAdd', - component: () => import('../views/UserEdit.vue'), - meta: { title: '添加用户' }, - }, - { - path: '/scripts/:scriptId/users/:userId/edit', - name: 'UserEdit', - component: () => import('../views/UserEdit.vue'), - meta: { title: '编辑用户' }, - }, { path: '/scripts/:scriptId/users/add/maa', name: 'MAAUserAdd', diff --git a/frontend/src/views/Scripts.vue b/frontend/src/views/Scripts.vue index 9c76545..b51b10b 100644 --- a/frontend/src/views/Scripts.vue +++ b/frontend/src/views/Scripts.vue @@ -454,20 +454,31 @@ const handleDeleteScript = async (script: Script) => { } const handleAddUser = (script: Script) => { - // 跳转到添加用户页面 - router.push(`/scripts/${script.id}/users/add`) + // 根据条件判断跳转到 MAA 还是通用用户添加页面 + if (script.type === 'MAA') { + router.push(`/scripts/${script.id}/users/add/maa`) // 跳转到 MAA 用户添加页面 + } else { + router.push(`/scripts/${script.id}/users/add/general`) // 跳转到通用用户添加页面 + } } const handleEditUser = (user: User) => { // 从用户数据中找到对应的脚本 const script = scripts.value.find(s => s.users.some(u => u.id === user.id)) if (script) { - // 跳转到编辑用户页面 - router.push(`/scripts/${script.id}/users/${user.id}/edit`) + // 判断是 MAA 用户还是通用用户 + if (user.Info.Server) { + // 跳转到 MAA 用户编辑页面 + router.push(`/scripts/${script.id}/users/${user.id}/edit/maa`) + } else { + // 跳转到通用用户编辑页面 + router.push(`/scripts/${script.id}/users/${user.id}/edit/general`) + } } else { message.error('找不到对应的脚本') } } + const handleDeleteUser = async (user: User) => { // 从用户数据中找到对应的脚本 const script = scripts.value.find(s => s.users.some(u => u.id === user.id)) diff --git a/frontend/src/views/UserEdit.vue b/frontend/src/views/UserEdit.vue deleted file mode 100644 index 70c03fd..0000000 --- a/frontend/src/views/UserEdit.vue +++ /dev/null @@ -1,1798 +0,0 @@ - - - - - From bd94a26a150a84c8d5f0436bf4bd8ce4b99ebe55 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 6 Sep 2025 01:36:56 +0800 Subject: [PATCH 03/17] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E8=AE=B0=E5=BD=95=E5=A4=9A=E4=BD=99=E7=9A=84=E5=9B=BE?= =?UTF-8?q?=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/views/History.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/views/History.vue b/frontend/src/views/History.vue index 17965de..ddbd71a 100644 --- a/frontend/src/views/History.vue +++ b/frontend/src/views/History.vue @@ -32,7 +32,7 @@ 按日合并 按周合并 - 按月合并 + 按月合并 @@ -299,7 +299,6 @@ - From f15ed8685293e1240b8c593e322028f5835dce03 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 6 Sep 2025 02:14:45 +0800 Subject: [PATCH 04/17] =?UTF-8?q?feat:=20=E5=90=8C=E6=AD=A5=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=B8=8E=E8=84=9A=E6=9C=AC=E9=85=8D=E7=BD=AE=E7=9A=84?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/views/GeneralUserEdit.vue | 116 ++++++++++++++-- frontend/src/views/MAAUserEdit.vue | 176 +++++++++++++++++++++---- 2 files changed, 262 insertions(+), 30 deletions(-) diff --git a/frontend/src/views/GeneralUserEdit.vue b/frontend/src/views/GeneralUserEdit.vue index 7afd659..bd708bc 100644 --- a/frontend/src/views/GeneralUserEdit.vue +++ b/frontend/src/views/GeneralUserEdit.vue @@ -51,8 +51,13 @@
- - + + + +
+
+

基本信息

+
@@ -69,6 +74,7 @@ placeholder="请输入用户名" :disabled="loading" size="large" + class="modern-input" /> @@ -128,11 +134,16 @@ placeholder="请输入备注信息" :rows="4" :disabled="loading" + class="modern-input" /> - +
- + +
+
+

额外脚本

+
- - + diff --git a/frontend/src/components/TitleBar.vue b/frontend/src/components/TitleBar.vue index f898069..05e0af8 100644 --- a/frontend/src/components/TitleBar.vue +++ b/frontend/src/components/TitleBar.vue @@ -3,6 +3,8 @@
+ + AUTO-MAS
@@ -94,6 +96,7 @@ onMounted(async () => { user-select: none; position: relative; z-index: 1000; + overflow: hidden; /* 新增:裁剪超出顶栏的发光 */ } .title-bar-dark { @@ -112,17 +115,42 @@ onMounted(async () => { display: flex; align-items: center; gap: 8px; + position: relative; /* 使阴影绝对定位基准 */ +} + +/* 新增:主题色虚化圆形阴影 */ +.logo-glow { + position: absolute; + left: 55px; /* 调整:更贴近图标 */ + top: 50%; + transform: translate(-50%, -50%); + width: 200px; /* 缩小尺寸以适配 32px 高度 */ + height: 100px; + pointer-events: none; + border-radius: 50%; + background: radial-gradient(circle at 50% 50%, var(--ant-color-primary) 0%, rgba(0,0,0,0) 70%); + filter: blur(24px); /* 降低模糊避免越界过多 */ + opacity: 0.4; + z-index: 0; +} +.title-bar-dark .logo-glow { + opacity: 0.7; + filter: blur(24px); } .title-logo { width: 20px; height: 20px; + position: relative; + z-index: 1; /* 确保在阴影上方 */ } .title-text { font-size: 13px; font-weight: 600; color: #333; + position: relative; + z-index: 1; } .title-bar-dark .title-text { diff --git a/frontend/src/composables/useTheme.ts b/frontend/src/composables/useTheme.ts index 2b73bc2..2f5d796 100644 --- a/frontend/src/composables/useTheme.ts +++ b/frontend/src/composables/useTheme.ts @@ -71,15 +71,32 @@ const updateCSSVariables = () => { const root = document.documentElement const primaryColor = themeColors[themeColor.value] + // 基础背景(用于估算混合) + const baseLightBg = '#ffffff' + const baseDarkBg = '#141414' + const baseMenuBg = isDark.value ? baseDarkBg : baseLightBg + + // 改进:侧边栏背景使用 HSL 调整而不是简单线性混合,提高在不同主色下的可读性 + const siderBg = deriveSiderBg(primaryColor, baseMenuBg, isDark.value) + const siderBorder = deriveSiderBorder(siderBg, primaryColor, isDark.value) + + // 基础文字候选色 + const candidateTextLight = 'rgba(255,255,255,0.88)' + const candidateTextDark = 'rgba(0,0,0,0.88)' + + // 选菜单文字颜色:对 siderBg 计算对比度,优先满足 >=4.5 + const menuTextColor = pickAccessibleColor(candidateTextDark, candidateTextLight, siderBg) + const iconColor = menuTextColor + + // ===== AntD token 变量(保留) ===== if (isDark.value) { - // 深色模式变量 root.style.setProperty('--ant-color-primary', primaryColor) - root.style.setProperty('--ant-color-primary-hover', lightenColor(primaryColor, 10)) - root.style.setProperty('--ant-color-primary-bg', `${primaryColor}1a`) + root.style.setProperty('--ant-color-primary-hover', hslLighten(primaryColor, 6)) + root.style.setProperty('--ant-color-primary-bg', addAlpha(primaryColor, 0.10)) root.style.setProperty('--ant-color-text', 'rgba(255, 255, 255, 0.88)') root.style.setProperty('--ant-color-text-secondary', 'rgba(255, 255, 255, 0.65)') root.style.setProperty('--ant-color-text-tertiary', 'rgba(255, 255, 255, 0.45)') - root.style.setProperty('--ant-color-bg-container', '#141414') + root.style.setProperty('--ant-color-bg-container', baseDarkBg) root.style.setProperty('--ant-color-bg-layout', '#000000') root.style.setProperty('--ant-color-bg-elevated', '#1f1f1f') root.style.setProperty('--ant-color-border', '#424242') @@ -88,14 +105,13 @@ const updateCSSVariables = () => { root.style.setProperty('--ant-color-success', '#52c41a') root.style.setProperty('--ant-color-warning', '#faad14') } else { - // 浅色模式变量 root.style.setProperty('--ant-color-primary', primaryColor) - root.style.setProperty('--ant-color-primary-hover', darkenColor(primaryColor, 10)) - root.style.setProperty('--ant-color-primary-bg', `${primaryColor}1a`) + root.style.setProperty('--ant-color-primary-hover', hslDarken(primaryColor, 6)) + root.style.setProperty('--ant-color-primary-bg', addAlpha(primaryColor, 0.10)) root.style.setProperty('--ant-color-text', 'rgba(0, 0, 0, 0.88)') root.style.setProperty('--ant-color-text-secondary', 'rgba(0, 0, 0, 0.65)') root.style.setProperty('--ant-color-text-tertiary', 'rgba(0, 0, 0, 0.45)') - root.style.setProperty('--ant-color-bg-container', '#ffffff') + root.style.setProperty('--ant-color-bg-container', baseLightBg) root.style.setProperty('--ant-color-bg-layout', '#f5f5f5') root.style.setProperty('--ant-color-bg-elevated', '#ffffff') root.style.setProperty('--ant-color-border', '#d9d9d9') @@ -104,9 +120,70 @@ const updateCSSVariables = () => { root.style.setProperty('--ant-color-success', '#52c41a') root.style.setProperty('--ant-color-warning', '#faad14') } + + // ===== 自定义菜单配色 ===== + // 动态 Alpha:根据主色亮度调整透明度以保持区分度 + const lumPrim = getLuminance(primaryColor) + const hoverAlphaBase = isDark.value ? 0.22 : 0.14 + const selectedAlphaBase = isDark.value ? 0.38 : 0.26 + const hoverAlpha = clamp01(hoverAlphaBase + (isDark.value ? (lumPrim > 0.65 ? -0.04 : 0) : (lumPrim < 0.30 ? 0.04 : 0))) + const selectedAlpha = clamp01(selectedAlphaBase + (isDark.value ? (lumPrim > 0.65 ? -0.05 : 0) : (lumPrim < 0.30 ? 0.05 : 0))) + + // 估算最终选中背景(混合算实际颜色用于对比度计算) + const estimatedSelectedBg = blendColors(baseMenuBg, primaryColor, selectedAlpha) + const selectedTextColor = pickAccessibleColor('rgba(0,0,0,0.90)', 'rgba(255,255,255,0.92)', estimatedSelectedBg) + const hoverTextColor = menuTextColor + + root.style.setProperty('--app-sider-bg', siderBg) + root.style.setProperty('--app-sider-border-color', siderBorder) + root.style.setProperty('--app-menu-text-color', menuTextColor) + root.style.setProperty('--app-menu-icon-color', iconColor) + root.style.setProperty('--app-menu-item-hover-text-color', hoverTextColor) + root.style.setProperty('--app-menu-item-selected-text-color', selectedTextColor) + + // 背景同时提供 rgba 与 hex alpha(兼容处理) + const hoverRgba = hexToRgba(primaryColor, hoverAlpha) + const selectedRgba = hexToRgba(primaryColor, selectedAlpha) + root.style.setProperty('--app-menu-item-hover-bg', hoverRgba) + root.style.setProperty('--app-menu-item-hover-bg-hex', addAlpha(primaryColor, hoverAlpha)) + root.style.setProperty('--app-menu-item-selected-bg', selectedRgba) + root.style.setProperty('--app-menu-item-selected-bg-hex', addAlpha(primaryColor, selectedAlpha)) } -// 颜色工具函数 +// ===== 新增缺失基础函数(从旧版本恢复) ===== +const addAlpha = (hex: string, alpha: number) => { + const a = alpha > 1 ? alpha / 100 : alpha + const clamped = Math.min(1, Math.max(0, a)) + const alphaHex = Math.round(clamped * 255).toString(16).padStart(2, '0') + return `${hex}${alphaHex}` +} + +const blendColors = (color1: string, color2: string, ratio: number) => { + const r1 = hexToRgb(color1) + const r2 = hexToRgb(color2) + if (!r1 || !r2) return color1 + const r = Math.round(r1.r * (1 - ratio) + r2.r * ratio) + const g = Math.round(r1.g * (1 - ratio) + r2.g * ratio) + const b = Math.round(r1.b * (1 - ratio) + r2.b * ratio) + return rgbToHex(r, g, b) +} + +const getLuminance = (hex: string) => { + const rgb = hexToRgb(hex) + if (!rgb) return 0 + const transform = (v: number) => { + const srgb = v / 255 + return srgb <= 0.03928 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4) + } + const r = transform(rgb.r) + const g = transform(rgb.g) + const b = transform(rgb.b) + return 0.2126 * r + 0.7152 * g + 0.0722 * b +} + +// ===== 新增/改进的颜色工具 ===== +const clamp01 = (v: number) => Math.min(1, Math.max(0, v)) + const hexToRgb = (hex: string) => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) return result @@ -122,24 +199,105 @@ const rgbToHex = (r: number, g: number, b: number) => { return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) } -const lightenColor = (hex: string, percent: number) => { +// 新增:hex -> rgba 字符串 +const hexToRgba = (hex: string, alpha: number) => { const rgb = hexToRgb(hex) - if (!rgb) return hex - - const { r, g, b } = rgb - const amount = Math.round(2.55 * percent) - - return rgbToHex(Math.min(255, r + amount), Math.min(255, g + amount), Math.min(255, b + amount)) + if (!rgb) return 'rgba(0,0,0,0)' + const a = alpha > 1 ? alpha / 100 : alpha + return `rgba(${rgb.r},${rgb.g},${rgb.b},${clamp01(a)})` } -const darkenColor = (hex: string, percent: number) => { +// HSL 转换(感知更平滑) +const rgbToHsl = (r: number, g: number, b: number) => { + r /= 255; g /= 255; b /= 255 + const max = Math.max(r, g, b), min = Math.min(r, g, b) + let h = 0, s = 0 + const l = (max + min) / 2 + const d = max - min + if (d !== 0) { + s = l > 0.5 ? d / (2 - max - min) : d / (max + min) + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break + case g: h = (b - r) / d + 2; break + case b: h = (r - g) / d + 4; break + } + h /= 6 + } + return { h: h * 360, s, l } +} + +const hslToRgb = (h: number, s: number, l: number) => { + h /= 360 + if (s === 0) { + const val = Math.round(l * 255) + return { r: val, g: val, b: val } + } + const hue2rgb = (p: number, q: number, t: number) => { + if (t < 0) t += 1 + if (t > 1) t -= 1 + if (t < 1/6) return p + (q - p) * 6 * t + if (t < 1/2) return q + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6 + return p + } + const q = l < 0.5 ? l * (1 + s) : l + s - l * s + const p = 2 * l - q + const r = hue2rgb(p, q, h + 1/3) + const g = hue2rgb(p, q, h) + const b = hue2rgb(p, q, h - 1/3) + return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) } +} + +const hslAdjust = (hex: string, dl: number) => { const rgb = hexToRgb(hex) if (!rgb) return hex + const { h, s, l } = rgbToHsl(rgb.r, rgb.g, rgb.b) + const nl = clamp01(l + dl) + const nrgb = hslToRgb(h, s, nl) + return rgbToHex(nrgb.r, nrgb.g, nrgb.b) +} - const { r, g, b } = rgb - const amount = Math.round(2.55 * percent) +const hslLighten = (hex: string, percent: number) => hslAdjust(hex, percent/100) +const hslDarken = (hex: string, percent: number) => hslAdjust(hex, -percent/100) - return rgbToHex(Math.max(0, r - amount), Math.max(0, g - amount), Math.max(0, b - amount)) +// 对比度 (WCAG) +const contrastRatio = (hex1: string, hex2: string) => { + const L1 = getLuminance(hex1) + const L2 = getLuminance(hex2) + const light = Math.max(L1, L2) + const dark = Math.min(L1, L2) + return (light + 0.05) / (dark + 0.05) +} + +const rgbaExtractHex = (color: string) => { + // 只支持 hex(#rrggbb) 直接返回;若 rgba 则忽略 alpha 并合成背景为黑假设 + if (color.startsWith('#') && (color.length === 7)) return color + // 简化:返回黑或白占位 + return '#000000' +} + +const pickAccessibleColor = (c1: string, c2: string, bg: string, minRatio = 4.5) => { + const hexBg = rgbaExtractHex(bg) + const hex1 = rgbaExtractHex(c1 === 'rgba(255,255,255,0.88)' ? '#ffffff' : (c1.includes('255,255,255') ? '#ffffff' : '#000000')) + const hex2 = rgbaExtractHex(c2 === 'rgba(255,255,255,0.88)' ? '#ffffff' : (c2.includes('255,255,255') ? '#ffffff' : '#000000')) + const r1 = contrastRatio(hex1, hexBg) + const r2 = contrastRatio(hex2, hexBg) + // 优先满足 >= minRatio;都满足取更高;否则取更高 + if (r1 >= minRatio && r2 >= minRatio) return r1 >= r2 ? c1 : c2 + if (r1 >= minRatio) return c1 + if (r2 >= minRatio) return c2 + return r1 >= r2 ? c1 : c2 +} + +// 改进侧栏背景:如果深色模式,降低亮度并略增饱和;浅色模式提高亮度轻度染色 +const deriveSiderBg = (primary: string, base: string, dark: boolean) => { + const mixRatio = dark ? 0.22 : 0.18 + const mixed = blendColors(base, primary, mixRatio) + return dark ? hslDarken(mixed, 8) : hslLighten(mixed, 6) +} + +const deriveSiderBorder = (siderBg: string, primary: string, dark: boolean) => { + return dark ? blendColors(siderBg, primary, 0.30) : blendColors('#d9d9d9', primary, 0.25) } // 监听系统主题变化 From a43b12bc17093c6a9caa8006fcc632cf7aeb3108 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Mon, 8 Sep 2025 14:57:40 +0800 Subject: [PATCH 11/17] =?UTF-8?q?fix:=20=E4=BB=BB=E5=8A=A1=E7=9A=84?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E5=88=97=E8=A1=A8=E8=A1=A5=E5=85=85=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/task_manager.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/core/task_manager.py b/app/core/task_manager.py index fec90e0..e69a560 100644 --- a/app/core/task_manager.py +++ b/app/core/task_manager.py @@ -212,6 +212,14 @@ class _TaskManager: partial(self.task_dict.pop, script_id) ) await self.task_dict[script_id] + task["status"] = "完成" + await Config.send_json( + WebSocketMessage( + id=str(task_id), + type="Update", + data={"task_list": task_list}, + ).model_dump() + ) async def stop_task(self, task_id: str) -> None: """ From 706bb8584da7185f2ba2ec6d57bd4dddccdff602 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Mon, 8 Sep 2025 15:42:45 +0800 Subject: [PATCH 12/17] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=E6=A0=A1=E9=AA=8C=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/ConfigBase.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/models/ConfigBase.py b/app/models/ConfigBase.py index bf10aa5..cceaf8d 100644 --- a/app/models/ConfigBase.py +++ b/app/models/ConfigBase.py @@ -25,7 +25,7 @@ import uuid import win32com.client from copy import deepcopy from pathlib import Path -from typing import List, Any, Dict, Union +from typing import List, Any, Dict, Union, Optional from app.utils import dpapi_encrypt, dpapi_decrypt @@ -112,7 +112,7 @@ class UUIDValidator(ConfigValidator): class EncryptValidator(ConfigValidator): - """加数据验证器""" + """加密数据验证器""" def validate(self, value: Any) -> bool: if not isinstance(value, str): @@ -185,7 +185,7 @@ class ConfigItem: group: str, name: str, default: Any, - validator: None | ConfigValidator = None, + validator: Optional[ConfigValidator] = None, ): """ Parameters @@ -209,7 +209,10 @@ class ConfigItem: self.validator = validator or ConfigValidator() self.is_locked = False - self.setValue(default) + if not self.validator.validate(self.value): + raise ValueError( + f"配置项 '{self.group}.{self.name}' 的默认值 '{self.value}' 不合法" + ) def setValue(self, value: Any): """ From 41bb159542032b90c39709e3acabb99c9c6c1dc5 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Mon, 8 Sep 2025 19:30:28 +0800 Subject: [PATCH 13/17] =?UTF-8?q?fix:=20=E5=90=8E=E7=AB=AF=E5=AF=B9?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=90=8D=E8=BF=9B=E8=A1=8C=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=B9=E5=90=88=E6=B3=95=E6=80=A7=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 4 ++-- app/models/ConfigBase.py | 41 ++++++++++++++++++++++++++++++++++++++++ app/utils/constants.py | 29 ++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index f71d213..af3bf99 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -194,7 +194,7 @@ class MaaUserConfig(ConfigBase): def __init__(self) -> None: super().__init__() - self.Info_Name = ConfigItem("Info", "Name", "新用户") + self.Info_Name = ConfigItem("Info", "Name", "新用户", UserNameValidator()) self.Info_Id = ConfigItem("Info", "Id", "") self.Info_Mode = ConfigItem( "Info", "Mode", "简洁", OptionsValidator(["简洁", "详细"]) @@ -455,7 +455,7 @@ class GeneralUserConfig(ConfigBase): def __init__(self) -> None: super().__init__() - self.Info_Name = ConfigItem("Info", "Name", "新用户") + self.Info_Name = ConfigItem("Info", "Name", "新用户", UserNameValidator()) self.Info_Status = ConfigItem("Info", "Status", True, BoolValidator()) self.Info_RemainedDay = ConfigItem( "Info", "RemainedDay", -1, RangeValidator(-1, 9999) diff --git a/app/models/ConfigBase.py b/app/models/ConfigBase.py index cceaf8d..2bd977b 100644 --- a/app/models/ConfigBase.py +++ b/app/models/ConfigBase.py @@ -29,6 +29,7 @@ from typing import List, Any, Dict, Union, Optional from app.utils import dpapi_encrypt, dpapi_decrypt +from app.utils.constants import RESERVED_NAMES, ILLEGAL_CHARS class ConfigValidator: @@ -177,6 +178,46 @@ class FolderValidator(ConfigValidator): return Path(value).resolve().as_posix() +class UserNameValidator(ConfigValidator): + """用户名验证器""" + + def validate(self, value: Any) -> bool: + if not isinstance(value, str): + return False + + if not value or not value.strip(): + return False + + if value != value.strip() or value != value.strip("."): + return False + + if any(char in ILLEGAL_CHARS for char in value): + return False + + if value.upper() in RESERVED_NAMES: + return False + if len(value) > 255: + return False + + return True + + def correct(self, value: Any) -> str: + if not isinstance(value, str): + value = "默认用户名" + + value = value.strip().strip(".") + + value = "".join(char for char in value if char not in ILLEGAL_CHARS) + + if value.upper() in RESERVED_NAMES or not value: + value = "默认用户名" + + if len(value) > 255: + value = value[:255] + + return value + + class ConfigItem: """配置项""" diff --git a/app/utils/constants.py b/app/utils/constants.py index 25a323e..a54186e 100644 --- a/app/utils/constants.py +++ b/app/utils/constants.py @@ -226,3 +226,32 @@ MATERIALS_MAP = { "PR-D": "近卫/特种芯片", } """掉落物索引表""" + +RESERVED_NAMES = { + "CON", + "PRN", + "AUX", + "NUL", + "COM1", + "COM2", + "COM3", + "COM4", + "COM5", + "COM6", + "COM7", + "COM8", + "COM9", + "LPT1", + "LPT2", + "LPT3", + "LPT4", + "LPT5", + "LPT6", + "LPT7", + "LPT8", + "LPT9", +} +"""Windows保留名称列表""" + +ILLEGAL_CHARS = set('<>:"/\\|?*') +"""文件名非法字符集合""" From a0c5334f577549243f470697ad1a3e90a485a2aa Mon Sep 17 00:00:00 2001 From: AoXuan Date: Mon, 8 Sep 2025 22:42:56 +0800 Subject: [PATCH 14/17] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E8=84=9A=E6=9C=AC=EF=BC=8C=E6=B7=BB=E5=8A=A0electron-?= =?UTF-8?q?updater=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 44 ++++++++++++++++++++++++++----------------- frontend/yarn.lock | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index e380476..86d393d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,8 @@ "electron-dev": "wait-on http://localhost:5173 && cross-env VITE_DEV_SERVER_URL=http://localhost:5173 electron .", "build:main": "tsc -p tsconfig.electron.json", "build": "vite build && yarn build:main && electron-builder", - "web": "vite" + "web": "vite", + "release": "vite build && yarn build:main && electron-builder --win --publish always" }, "build": { "asar": true, @@ -20,29 +21,37 @@ "appId": "xyz.automaa.frontend", "productName": "AUTO_MAA", "files": [ - "dist", - "dist-electron", - "public", - "!src/assets/*" + "dist/**", + "dist-electron/**", + "public/**", + "!src/**", + "!**/*.map" + ], + "publish": [ + { + "provider": "github", + "owner": "DLmaster_361", + "repo": "AUTO_MAA" + } ], "extraResources": [ - { - "from": "src/assets", - "to": "assets", - "filter": [] - } + { "from": "src/assets", "to": "assets", "filter": ["**/*"] } ], "win": { "requestedExecutionLevel": "requireAdministrator", - "target": "dir", + "target": [ + { "target": "nsis", "arch": ["x64"] } + ], "icon": "public/AUTO-MAS.ico", - "artifactName": "AUTO_MAA.exe" + "artifactName": "AUTO_MAA-Setup-${version}-${arch}.${ext}" }, - "mac": { - "icon": "public/AUTO-MAS.ico" - }, - "linux": { - "icon": "public/AUTO-MAS.ico" + "nsis": { + "oneClick": false, + "perMachine": true, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "shortcutName": "AUTO_MAA", + "differentialPackage": true } }, "dependencies": { @@ -54,6 +63,7 @@ "axios": "^1.11.0", "dayjs": "^1.11.13", "electron-log": "^5.4.3", + "electron-updater": "6.6.2", "form-data": "^4.0.4", "markdown-it": "^14.1.0", "vue": "^3.5.17", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index e8610a4..1d4ea95 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2478,6 +2478,22 @@ __metadata: languageName: node linkType: hard +"electron-updater@npm:6.6.2": + version: 6.6.2 + resolution: "electron-updater@npm:6.6.2" + dependencies: + builder-util-runtime: "npm:9.3.1" + fs-extra: "npm:^10.1.0" + js-yaml: "npm:^4.1.0" + lazy-val: "npm:^1.0.5" + lodash.escaperegexp: "npm:^4.1.2" + lodash.isequal: "npm:^4.5.0" + semver: "npm:^7.6.3" + tiny-typed-emitter: "npm:^2.1.0" + checksum: 10c0/2b9ae5583b95f6772c4a2515ddba7ba52b65460ab81f09ae4f0b97c7e3d7b7e3d9426775eb9a53d3193bd4c3d5466bf30827c1a6ee75e4aca739c647f6ac46ff + languageName: node + linkType: hard + "electron@npm:^37.2.5": version: 37.4.0 resolution: "electron@npm:37.4.0" @@ -3075,6 +3091,7 @@ __metadata: electron: "npm:^37.2.5" electron-builder: "npm:^26.0.12" electron-log: "npm:^5.4.3" + electron-updater: "npm:6.6.2" eslint: "npm:^9.32.0" eslint-config-prettier: "npm:^10.1.8" eslint-plugin-prettier: "npm:^5.5.3" @@ -3872,6 +3889,20 @@ __metadata: languageName: node linkType: hard +"lodash.escaperegexp@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.escaperegexp@npm:4.1.2" + checksum: 10c0/484ad4067fa9119bb0f7c19a36ab143d0173a081314993fe977bd00cf2a3c6a487ce417a10f6bac598d968364f992153315f0dbe25c9e38e3eb7581dd333e087 + languageName: node + linkType: hard + +"lodash.isequal@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f + languageName: node + linkType: hard + "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -5374,6 +5405,13 @@ __metadata: languageName: node linkType: hard +"tiny-typed-emitter@npm:^2.1.0": + version: 2.1.0 + resolution: "tiny-typed-emitter@npm:2.1.0" + checksum: 10c0/522bed4c579ee7ee16548540cb693a3d098b137496110f5a74bff970b54187e6b7343a359b703e33f77c5b4b90ec6cebc0d0ec3dbdf1bd418723c5c3ce36d8a2 + languageName: node + linkType: hard + "tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.14": version: 0.2.14 resolution: "tinyglobby@npm:0.2.14" From 364e7a273fffac290bdc0e8392554e6e68226cd0 Mon Sep 17 00:00:00 2001 From: AoXuan Date: Mon, 8 Sep 2025 22:55:13 +0800 Subject: [PATCH 15/17] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4electron-update?= =?UTF-8?q?r=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 86d393d..f3c0b5e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -63,7 +63,6 @@ "axios": "^1.11.0", "dayjs": "^1.11.13", "electron-log": "^5.4.3", - "electron-updater": "6.6.2", "form-data": "^4.0.4", "markdown-it": "^14.1.0", "vue": "^3.5.17", From b38c81b08c0c588769b1a6e591137584cfe03531 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Mon, 8 Sep 2025 23:06:47 +0800 Subject: [PATCH 16/17] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E7=94=A8=E6=88=B7=E8=87=AA=E5=AE=9A=E4=B9=89=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E8=B7=AF=E5=BE=84=E9=85=8D=E7=BD=AE=E9=A1=B9=E5=88=9D?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index af3bf99..ae3d21e 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -464,13 +464,13 @@ class GeneralUserConfig(ConfigBase): "Info", "IfScriptBeforeTask", False, BoolValidator() ) self.Info_ScriptBeforeTask = ConfigItem( - "Info", "ScriptBeforeTask", "", FileValidator() + "Info", "ScriptBeforeTask", str(Path.cwd()), FileValidator() ) self.Info_IfScriptAfterTask = ConfigItem( "Info", "IfScriptAfterTask", False, BoolValidator() ) self.Info_ScriptAfterTask = ConfigItem( - "Info", "ScriptAfterTask", "", FileValidator() + "Info", "ScriptAfterTask", str(Path.cwd()), FileValidator() ) self.Info_Notes = ConfigItem("Info", "Notes", "无") From 8de3405aa5142051c20ba3761b52904103341546 Mon Sep 17 00:00:00 2001 From: Zrief Date: Mon, 8 Sep 2025 23:59:47 +0800 Subject: [PATCH 17/17] Update tag color and remove background style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改碳关卡的颜色,适配浅色主题 删除指定的css配色,适配深色主题 --- frontend/src/views/Plans.vue | 2 +- frontend/src/views/Scheduler.vue | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/views/Plans.vue b/frontend/src/views/Plans.vue index 7b8b728..1d48585 100644 --- a/frontend/src/views/Plans.vue +++ b/frontend/src/views/Plans.vue @@ -853,7 +853,7 @@ const getSimpleTaskTagColor = (taskName: string) => { '红票-5': 'volcano', '技能-5': 'cyan', '经验-6/5': 'gold', - '碳-5': 'none', + '碳-5': 'default', '奶/盾芯片': 'green', '奶/盾芯片组': 'green', '术/狙芯片': 'purple', diff --git a/frontend/src/views/Scheduler.vue b/frontend/src/views/Scheduler.vue index 6ab6582..fcaf01f 100644 --- a/frontend/src/views/Scheduler.vue +++ b/frontend/src/views/Scheduler.vue @@ -496,7 +496,6 @@ onUnmounted(() => { schedulerTabs.value.forEach(tab => tab.runningTasks.forEach( justify-content: space-between; align-items: center; padding: 8px 16px; - background: #f0f2f5; border-bottom: 1px solid #d9d9d9; }