feat(script): 添加脚本管理页面和相关功能

- 实现了脚本管理页面的基本布局和样式
- 添加了脚本列表加载、添加、编辑和删除功能- 集成了文件夹和文件选择对话框
- 优化了主题模式和颜色的动态切换
- 新增了多个脚本相关类型定义
This commit is contained in:
2025-08-04 14:42:31 +08:00
parent 94d038d563
commit 0b1ed48471
21 changed files with 3770 additions and 70 deletions

View File

@@ -0,0 +1,208 @@
import { ref } from 'vue'
import type {
ScriptType,
AddScriptResponse,
MAAScriptConfig,
GeneralScriptConfig,
GetScriptsResponse,
ScriptDetail,
ScriptIndexItem
} from '@/types/script'
const API_BASE_URL = 'http://localhost:8000/api'
export function useScriptApi() {
const loading = ref(false)
const error = ref<string | null>(null)
// 添加脚本
const addScript = async (type: ScriptType): Promise<AddScriptResponse | null> => {
loading.value = true
error.value = null
try {
const response = await fetch(`${API_BASE_URL}/add/scripts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ type }),
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data: AddScriptResponse = await response.json()
return data
} catch (err) {
error.value = err instanceof Error ? err.message : '添加脚本失败'
return null
} finally {
loading.value = false
}
}
// 获取所有脚本
const getScripts = async (): Promise<ScriptDetail[]> => {
loading.value = true
error.value = null
try {
const response = await fetch(`${API_BASE_URL}/get/scripts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({}), // 传空对象获取全部脚本
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const apiResponse: GetScriptsResponse = await response.json()
// 转换API响应为前端需要的格式
const scripts: ScriptDetail[] = apiResponse.index.map((item: ScriptIndexItem) => {
const config = apiResponse.data[item.uid]
const scriptType: ScriptType = item.type === 'MaaConfig' ? 'MAA' : 'General'
// 从配置中获取脚本名称
let name = ''
if (scriptType === 'MAA') {
name = (config as MAAScriptConfig).Info.Name || '未命名MAA脚本'
} else {
name = (config as GeneralScriptConfig).Info.Name || '未命名General脚本'
}
return {
uid: item.uid,
type: scriptType,
name,
config,
createTime: new Date().toLocaleString() // 暂时使用当前时间后续可从API获取
}
})
return scripts
} catch (err) {
error.value = err instanceof Error ? err.message : '获取脚本列表失败'
return []
} finally {
loading.value = false
}
}
// 获取单个脚本
const getScript = async (scriptId: string): Promise<ScriptDetail | null> => {
loading.value = true
error.value = null
try {
const response = await fetch(`${API_BASE_URL}/get/scripts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ scriptId }), // 传scriptId获取单个脚本
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const apiResponse: GetScriptsResponse = await response.json()
// 检查是否有数据返回
if (apiResponse.index.length === 0) {
throw new Error('脚本不存在')
}
const item = apiResponse.index[0]
const config = apiResponse.data[item.uid]
const scriptType: ScriptType = item.type === 'MaaConfig' ? 'MAA' : 'General'
// 从配置中获取脚本名称
let name = ''
if (scriptType === 'MAA') {
name = (config as MAAScriptConfig).Info.Name || '未命名MAA脚本'
} else {
name = (config as GeneralScriptConfig).Info.Name || '未命名General脚本'
}
return {
uid: item.uid,
type: scriptType,
name,
config,
createTime: new Date().toLocaleString() // 暂时使用当前时间后续可从API获取
}
} catch (err) {
error.value = err instanceof Error ? err.message : '获取脚本详情失败'
return null
} finally {
loading.value = false
}
}
// 更新脚本(暂时模拟)
const updateScript = async (scriptId: string, config: MAAScriptConfig | GeneralScriptConfig) => {
loading.value = true
error.value = null
try {
const response = await fetch(`${API_BASE_URL}/update/scripts/${scriptId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(config),
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.json()
} catch (err) {
error.value = err instanceof Error ? err.message : '更新脚本失败'
return null
} finally {
loading.value = false
}
}
// 删除脚本(暂时模拟)
const deleteScript = async (scriptId: string) => {
loading.value = true
error.value = null
try {
const response = await fetch(`${API_BASE_URL}/delete/scripts/${scriptId}`, {
method: 'DELETE',
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.json()
} catch (err) {
error.value = err instanceof Error ? err.message : '删除脚本失败'
return null
} finally {
loading.value = false
}
}
return {
loading,
error,
addScript,
getScripts,
getScript,
updateScript,
deleteScript,
}
}

View File

@@ -61,6 +61,91 @@ const updateTheme = () => {
} else {
document.documentElement.classList.remove('dark')
}
// 更新CSS变量
updateCSSVariables()
}
// 更新CSS变量
const updateCSSVariables = () => {
const root = document.documentElement
const primaryColor = themeColors[themeColor.value]
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-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-layout', '#000000')
root.style.setProperty('--ant-color-bg-elevated', '#1f1f1f')
root.style.setProperty('--ant-color-border', '#424242')
root.style.setProperty('--ant-color-border-secondary', '#303030')
root.style.setProperty('--ant-color-error', '#ff4d4f')
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-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-layout', '#f5f5f5')
root.style.setProperty('--ant-color-bg-elevated', '#ffffff')
root.style.setProperty('--ant-color-border', '#d9d9d9')
root.style.setProperty('--ant-color-border-secondary', '#f0f0f0')
root.style.setProperty('--ant-color-error', '#ff4d4f')
root.style.setProperty('--ant-color-success', '#52c41a')
root.style.setProperty('--ant-color-warning', '#faad14')
}
}
// 颜色工具函数
const hexToRgb = (hex: string) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null
}
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) => {
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)
)
}
const darkenColor = (hex: string, percent: number) => {
const rgb = hexToRgb(hex)
if (!rgb) return hex
const { r, g, b } = rgb
const amount = Math.round(2.55 * percent)
return rgbToHex(
Math.max(0, r - amount),
Math.max(0, g - amount),
Math.max(0, b - amount)
)
}
// 监听系统主题变化
@@ -71,8 +156,9 @@ mediaQuery.addEventListener('change', () => {
}
})
// 监听主题模式变化
// 监听主题模式和颜色变化
watch(themeMode, updateTheme, { immediate: true })
watch(themeColor, updateTheme)
// Ant Design 主题配置
const antdTheme = computed(() => ({