feat(script): 添加脚本管理页面和相关功能
- 实现了脚本管理页面的基本布局和样式 - 添加了脚本列表加载、添加、编辑和删除功能- 集成了文件夹和文件选择对话框 - 优化了主题模式和颜色的动态切换 - 新增了多个脚本相关类型定义
This commit is contained in:
208
frontend/src/composables/useScriptApi.ts
Normal file
208
frontend/src/composables/useScriptApi.ts
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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(() => ({
|
||||
|
||||
Reference in New Issue
Block a user