feat(script): 添加脚本管理页面和相关功能
- 实现了脚本管理页面的基本布局和样式 - 添加了脚本列表加载、添加、编辑和删除功能- 集成了文件夹和文件选择对话框 - 优化了主题模式和颜色的动态切换 - 新增了多个脚本相关类型定义
This commit is contained in:
@@ -38,8 +38,8 @@ const path = __importStar(require("path"));
|
||||
let mainWindow = null;
|
||||
function createWindow() {
|
||||
mainWindow = new electron_1.BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
width: 1600,
|
||||
height: 900,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
icon: path.join(__dirname, '../public/AUTO_MAA.ico'), // 设置应用图标
|
||||
@@ -73,6 +73,35 @@ electron_1.ipcMain.handle('open-dev-tools', () => {
|
||||
mainWindow.webContents.openDevTools({ mode: 'undocked' });
|
||||
}
|
||||
});
|
||||
// 处理文件夹选择请求
|
||||
electron_1.ipcMain.handle('select-folder', async () => {
|
||||
if (!mainWindow)
|
||||
return null;
|
||||
const result = await electron_1.dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openDirectory'],
|
||||
title: '选择文件夹'
|
||||
});
|
||||
if (result.canceled) {
|
||||
return null;
|
||||
}
|
||||
return result.filePaths[0];
|
||||
});
|
||||
// 处理文件选择请求
|
||||
electron_1.ipcMain.handle('select-file', async (event, filters = []) => {
|
||||
if (!mainWindow)
|
||||
return null;
|
||||
const result = await electron_1.dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
title: '选择文件',
|
||||
filters: filters.length > 0 ? filters : [
|
||||
{ name: '所有文件', extensions: ['*'] }
|
||||
]
|
||||
});
|
||||
if (result.canceled) {
|
||||
return null;
|
||||
}
|
||||
return result.filePaths[0];
|
||||
});
|
||||
electron_1.app.whenReady().then(createWindow);
|
||||
electron_1.app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin')
|
||||
|
||||
@@ -6,5 +6,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
// 暴露安全的 API 给渲染进程
|
||||
electron_1.contextBridge.exposeInMainWorld('electronAPI', {
|
||||
openDevTools: () => electron_1.ipcRenderer.invoke('open-dev-tools')
|
||||
openDevTools: () => electron_1.ipcRenderer.invoke('open-dev-tools'),
|
||||
selectFolder: () => electron_1.ipcRenderer.invoke('select-folder'),
|
||||
selectFile: (filters) => electron_1.ipcRenderer.invoke('select-file', filters)
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { app, BrowserWindow, ipcMain } from 'electron'
|
||||
import { app, BrowserWindow, ipcMain, dialog } from 'electron'
|
||||
import * as path from 'path'
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
width: 1600,
|
||||
height: 900,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
icon: path.join(__dirname, '../public/AUTO_MAA.ico'), // 设置应用图标
|
||||
@@ -44,6 +44,41 @@ ipcMain.handle('open-dev-tools', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// 处理文件夹选择请求
|
||||
ipcMain.handle('select-folder', async () => {
|
||||
if (!mainWindow) return null
|
||||
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openDirectory'],
|
||||
title: '选择文件夹'
|
||||
})
|
||||
|
||||
if (result.canceled) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result.filePaths[0]
|
||||
})
|
||||
|
||||
// 处理文件选择请求
|
||||
ipcMain.handle('select-file', async (event, filters = []) => {
|
||||
if (!mainWindow) return null
|
||||
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openFile'],
|
||||
title: '选择文件',
|
||||
filters: filters.length > 0 ? filters : [
|
||||
{ name: '所有文件', extensions: ['*'] }
|
||||
]
|
||||
})
|
||||
|
||||
if (result.canceled) {
|
||||
return null
|
||||
}
|
||||
|
||||
return result.filePaths[0]
|
||||
})
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
|
||||
@@ -6,5 +6,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// 暴露安全的 API 给渲染进程
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
openDevTools: () => ipcRenderer.invoke('open-dev-tools')
|
||||
openDevTools: () => ipcRenderer.invoke('open-dev-tools'),
|
||||
selectFolder: () => ipcRenderer.invoke('select-folder'),
|
||||
selectFile: (filters?: any[]) => ipcRenderer.invoke('select-file', filters)
|
||||
})
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
BIN
frontend/src/assets/AUTO_MAA.png
Normal file
BIN
frontend/src/assets/AUTO_MAA.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
frontend/src/assets/MAA.png
Normal file
BIN
frontend/src/assets/MAA.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 266 KiB |
@@ -12,7 +12,7 @@
|
||||
<div class="sider-content">
|
||||
<!-- Logo区域 - 点击切换展开/折叠 -->
|
||||
<div class="logo" @click="collapsed = !collapsed">
|
||||
<img src="/AUTO_MAA.ico" alt="AUTO MAA" class="logo-image" />
|
||||
<img src="/src/assets/AUTO_MAA.ico" alt="AUTO MAA" class="logo-image" />
|
||||
<div v-if="!collapsed" class="logo-text">AUTO_MAA</div>
|
||||
</div>
|
||||
|
||||
|
||||
340
frontend/src/components/ScriptEditor.vue
Normal file
340
frontend/src/components/ScriptEditor.vue
Normal file
@@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:title="isEdit ? '编辑脚本' : '新建脚本'"
|
||||
width="800px"
|
||||
:confirm-loading="loading"
|
||||
@ok="handleSave"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="脚本名称" name="name">
|
||||
<a-input v-model:value="formData.name" placeholder="请输入脚本名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="脚本类型" name="type">
|
||||
<a-select v-model:value="formData.type" :disabled="isEdit">
|
||||
<a-select-option value="MAA">MAA</a-select-option>
|
||||
<a-select-option value="General">General</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- MAA脚本配置 -->
|
||||
<template v-if="formData.type === 'MAA'">
|
||||
<a-divider>基础配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="脚本路径" name="path">
|
||||
<a-input v-model:value="maaConfig.Info.Path" placeholder="请输入MAA脚本路径" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider>运行配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="ADB搜索范围">
|
||||
<a-input-number
|
||||
v-model:value="maaConfig.Run.ADBSearchRange"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="剿灭时间限制(分钟)">
|
||||
<a-input-number
|
||||
v-model:value="maaConfig.Run.AnnihilationTimeLimit"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="代理次数限制">
|
||||
<a-input-number
|
||||
v-model:value="maaConfig.Run.ProxyTimesLimit"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="日常时间限制(分钟)">
|
||||
<a-input-number
|
||||
v-model:value="maaConfig.Run.RoutineTimeLimit"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="运行次数限制">
|
||||
<a-input-number
|
||||
v-model:value="maaConfig.Run.RunTimesLimit"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="任务转换方式">
|
||||
<a-select v-model:value="maaConfig.Run.TaskTransitionMethod">
|
||||
<a-select-option value="NoAction">无操作</a-select-option>
|
||||
<a-select-option value="ExitEmulator">退出模拟器</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="剿灭周限制">
|
||||
<a-switch v-model:checked="maaConfig.Run.AnnihilationWeeklyLimit" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- General脚本配置 -->
|
||||
<template v-if="formData.type === 'General'">
|
||||
<a-divider>基础配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="根路径" name="rootPath">
|
||||
<a-input v-model:value="generalConfig.Info.RootPath" placeholder="请输入根路径" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider>游戏配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="游戏路径">
|
||||
<a-input v-model:value="generalConfig.Game.Path" placeholder="请输入游戏路径" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="启动参数">
|
||||
<a-input v-model:value="generalConfig.Game.Arguments" placeholder="请输入启动参数" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="游戏样式">
|
||||
<a-select v-model:value="generalConfig.Game.Style">
|
||||
<a-select-option value="Emulator">模拟器</a-select-option>
|
||||
<a-select-option value="Game">游戏</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="等待时间(秒)">
|
||||
<a-input-number
|
||||
v-model:value="generalConfig.Game.WaitTime"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="启用游戏">
|
||||
<a-switch v-model:checked="generalConfig.Game.Enabled" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider>脚本配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="脚本路径">
|
||||
<a-input v-model:value="generalConfig.Script.ScriptPath" placeholder="请输入脚本路径" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="配置路径">
|
||||
<a-input v-model:value="generalConfig.Script.ConfigPath" placeholder="请输入配置路径" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, computed } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import type { FormInstance } from 'ant-design-vue'
|
||||
import type { ScriptType, MAAScriptConfig, GeneralScriptConfig } from '@/types/script'
|
||||
import { useScriptApi } from '@/composables/useScriptApi'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
scriptData?: {
|
||||
id?: string
|
||||
type: ScriptType
|
||||
config: MAAScriptConfig | GeneralScriptConfig
|
||||
}
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:open', value: boolean): void
|
||||
(e: 'success', data: any): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
open: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const { addScript, updateScript, loading } = useScriptApi()
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const visible = computed({
|
||||
get: () => props.open,
|
||||
set: (value) => emit('update:open', value)
|
||||
})
|
||||
|
||||
const isEdit = computed(() => !!props.scriptData?.id)
|
||||
|
||||
const formData = reactive({
|
||||
name: '',
|
||||
type: 'MAA' as ScriptType
|
||||
})
|
||||
|
||||
// MAA配置
|
||||
const maaConfig = reactive<MAAScriptConfig>({
|
||||
Info: {
|
||||
Name: '',
|
||||
Path: '.'
|
||||
},
|
||||
Run: {
|
||||
ADBSearchRange: 0,
|
||||
AnnihilationTimeLimit: 40,
|
||||
AnnihilationWeeklyLimit: true,
|
||||
ProxyTimesLimit: 0,
|
||||
RoutineTimeLimit: 10,
|
||||
RunTimesLimit: 3,
|
||||
TaskTransitionMethod: 'ExitEmulator'
|
||||
},
|
||||
SubConfigsInfo: {
|
||||
UserData: {
|
||||
instances: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// General配置
|
||||
const generalConfig = reactive<GeneralScriptConfig>({
|
||||
Game: {
|
||||
Arguments: '',
|
||||
Enabled: false,
|
||||
IfForceClose: false,
|
||||
Path: '.',
|
||||
Style: 'Emulator',
|
||||
WaitTime: 0
|
||||
},
|
||||
Info: {
|
||||
Name: '',
|
||||
RootPath: '.'
|
||||
},
|
||||
Run: {
|
||||
ProxyTimesLimit: 0,
|
||||
RunTimeLimit: 10,
|
||||
RunTimesLimit: 3
|
||||
},
|
||||
Script: {
|
||||
Arguments: '',
|
||||
ConfigPath: '.',
|
||||
ConfigPathMode: '所有文件 (*)',
|
||||
ErrorLog: '',
|
||||
IfTrackProcess: false,
|
||||
LogPath: '.',
|
||||
LogPathFormat: '%Y-%m-%d',
|
||||
LogTimeEnd: 1,
|
||||
LogTimeStart: 1,
|
||||
LogTimeFormat: '%Y-%m-%d %H:%M:%S',
|
||||
ScriptPath: '.',
|
||||
SuccessLog: '',
|
||||
UpdateConfigMode: 'Never'
|
||||
},
|
||||
SubConfigsInfo: {
|
||||
UserData: {
|
||||
instances: []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入脚本名称', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '请选择脚本类型', trigger: 'change' }]
|
||||
}
|
||||
|
||||
// 监听props变化,初始化表单数据
|
||||
watch(() => props.scriptData, (data) => {
|
||||
if (data) {
|
||||
formData.name = data.type === 'MAA' ? (data.config as MAAScriptConfig).Info.Name : (data.config as GeneralScriptConfig).Info.Name
|
||||
formData.type = data.type
|
||||
|
||||
if (data.type === 'MAA') {
|
||||
Object.assign(maaConfig, data.config)
|
||||
} else {
|
||||
Object.assign(generalConfig, data.config)
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
|
||||
if (isEdit.value) {
|
||||
// 编辑模式
|
||||
const config = formData.type === 'MAA' ? maaConfig : generalConfig
|
||||
if (formData.type === 'MAA') {
|
||||
maaConfig.Info.Name = formData.name
|
||||
} else {
|
||||
generalConfig.Info.Name = formData.name
|
||||
}
|
||||
|
||||
const result = await updateScript(props.scriptData!.id!, config)
|
||||
if (result) {
|
||||
message.success('脚本更新成功')
|
||||
emit('success', { id: props.scriptData!.id, type: formData.type, config })
|
||||
visible.value = false
|
||||
}
|
||||
} else {
|
||||
// 新建模式
|
||||
const result = await addScript(formData.type)
|
||||
if (result) {
|
||||
message.success(result.message)
|
||||
emit('success', { id: result.scriptId, type: formData.type, config: result.data })
|
||||
visible.value = false
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
</script>
|
||||
567
frontend/src/components/ScriptTable.vue
Normal file
567
frontend/src/components/ScriptTable.vue
Normal file
@@ -0,0 +1,567 @@
|
||||
<template>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="scripts"
|
||||
:pagination="false"
|
||||
:expandable="expandableConfig"
|
||||
row-key="id"
|
||||
class="modern-table"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<div class="script-name-cell">
|
||||
<div class="script-type-logo">
|
||||
<img
|
||||
v-if="record.type === 'MAA'"
|
||||
src="@/assets/MAA.png"
|
||||
alt="MAA"
|
||||
class="script-logo"
|
||||
/>
|
||||
<img v-else src="@/assets/AUTO_MAA.png" alt="AUTO MAA" class="script-logo" />
|
||||
</div>
|
||||
<div class="script-info">
|
||||
<div class="script-title">{{ record.name }}</div>
|
||||
<a-tag :color="record.type === 'MAA' ? 'blue' : 'green'" class="script-type-tag">
|
||||
{{ record.type }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'userCount'">
|
||||
<a-badge
|
||||
:count="record.users && record.users.length ? record.users.length : 0"
|
||||
:number-style="{
|
||||
backgroundColor: record.users.length > 0 ? '#52c41a' : '#d9d9d9',
|
||||
color: record.users.length > 0 ? '#fff' : '#666',
|
||||
}"
|
||||
class="user-count-badge"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space size="small">
|
||||
<a-tooltip title="编辑脚本配置">
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleEdit(record)"
|
||||
class="action-button edit-button"
|
||||
>
|
||||
<template #icon>
|
||||
<EditOutlined />
|
||||
</template>
|
||||
编辑
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="为此脚本添加用户">
|
||||
<a-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click="handleAddUser(record)"
|
||||
class="action-button add-user-button"
|
||||
>
|
||||
<template #icon>
|
||||
<UserAddOutlined />
|
||||
</template>
|
||||
添加用户
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
title="确定要删除这个脚本吗?"
|
||||
description="删除后将无法恢复,请谨慎操作"
|
||||
@confirm="handleDelete(record)"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
>
|
||||
<a-tooltip title="删除脚本">
|
||||
<a-button danger size="small" class="action-button delete-button">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #expandedRowRender="{ record }">
|
||||
<div class="expanded-content">
|
||||
<a-table
|
||||
:columns="userColumns"
|
||||
:data-source="record.users"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
row-key="id"
|
||||
class="user-table"
|
||||
>
|
||||
<template #bodyCell="{ column, record: user }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<div class="status-cell">
|
||||
<PlayCircleOutlined v-if="user.Info.Status" class="status-icon active" />
|
||||
<PauseCircleOutlined v-else class="status-icon inactive" />
|
||||
<a-tag :color="user.Info.Status ? 'success' : 'error'">
|
||||
{{ user.Info.Status ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'lastRun'">
|
||||
<div class="last-run-cell">
|
||||
<div class="run-item">
|
||||
<span class="run-label">剿灭:</span>
|
||||
<span class="run-date">{{ user.Data.LastAnnihilationDate }}</span>
|
||||
</div>
|
||||
<div class="run-item">
|
||||
<span class="run-label">代理:</span>
|
||||
<span class="run-date">{{ user.Data.LastProxyDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'tasks'">
|
||||
<a-space wrap class="task-tags">
|
||||
<a-tag v-if="user.Task.IfBase" color="blue" class="task-tag">基建</a-tag>
|
||||
<a-tag v-if="user.Task.IfCombat" color="green" class="task-tag">作战</a-tag>
|
||||
<a-tag v-if="user.Task.IfMall" color="orange" class="task-tag">商店</a-tag>
|
||||
<a-tag v-if="user.Task.IfMission" color="purple" class="task-tag">任务</a-tag>
|
||||
<a-tag v-if="user.Task.IfRecruiting" color="cyan" class="task-tag">招募</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'userAction'">
|
||||
<a-space size="small">
|
||||
<a-tooltip title="编辑用户配置">
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleEditUser(user)"
|
||||
class="user-action-button"
|
||||
>
|
||||
<template #icon>
|
||||
<EditOutlined />
|
||||
</template>
|
||||
编辑
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
title="确定要删除这个用户吗?"
|
||||
description="删除后将无法恢复"
|
||||
@confirm="handleDeleteUser(user)"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
>
|
||||
<a-tooltip title="删除用户">
|
||||
<a-button type="link" danger size="small" class="user-action-button">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { TableColumnsType } from 'ant-design-vue'
|
||||
import type { Script, User } from '@/types/script'
|
||||
import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
PauseCircleOutlined,
|
||||
PlayCircleOutlined,
|
||||
UserAddOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
interface Props {
|
||||
scripts: Script[]
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'edit', script: Script): void
|
||||
(e: 'delete', script: Script): void
|
||||
(e: 'addUser', script: Script): void
|
||||
(e: 'editUser', user: User): void
|
||||
(e: 'deleteUser', user: User): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const columns: TableColumnsType = [
|
||||
{
|
||||
title: '脚本名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 300,
|
||||
},
|
||||
// {
|
||||
// title: '用户数量',
|
||||
// dataIndex: 'userCount',
|
||||
// key: 'userCount',
|
||||
// width: 120,
|
||||
// align: 'center',
|
||||
// },
|
||||
// {
|
||||
// title: '创建时间',
|
||||
// dataIndex: 'createTime',
|
||||
// key: 'createTime',
|
||||
// width: 180,
|
||||
// },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 250,
|
||||
align: 'center',
|
||||
},
|
||||
]
|
||||
|
||||
const userColumns: TableColumnsType = [
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: ['Info', 'Name'],
|
||||
key: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: ['Info', 'Id'],
|
||||
key: 'id',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '服务器',
|
||||
dataIndex: ['Info', 'Server'],
|
||||
key: 'server',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '最后运行',
|
||||
key: 'lastRun',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '启用任务',
|
||||
key: 'tasks',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: ['Info', 'Notes'],
|
||||
key: 'notes',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'userAction',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
},
|
||||
]
|
||||
|
||||
const expandableConfig = computed(() => ({
|
||||
expandedRowRender: true,
|
||||
rowExpandable: (record: Script) => record.users.length > 0,
|
||||
}))
|
||||
|
||||
const handleEdit = (script: Script) => {
|
||||
emit('edit', script)
|
||||
}
|
||||
|
||||
const handleDelete = (script: Script) => {
|
||||
emit('delete', script)
|
||||
}
|
||||
|
||||
const handleAddUser = (script: Script) => {
|
||||
emit('addUser', script)
|
||||
}
|
||||
|
||||
const handleEditUser = (user: User) => {
|
||||
emit('editUser', user)
|
||||
}
|
||||
|
||||
const handleDeleteUser = (user: User) => {
|
||||
emit('deleteUser', user)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 现代化表格样式 */
|
||||
.modern-table :deep(.ant-table) {
|
||||
background: var(--ant-color-bg-container);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.modern-table :deep(.ant-table-thead > tr > th) {
|
||||
background: var(--ant-color-bg-container);
|
||||
border-bottom: 2px solid var(--ant-color-border-secondary);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--ant-color-text);
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.modern-table :deep(.ant-table-tbody > tr > td) {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--ant-color-border-secondary);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.modern-table :deep(.ant-table-tbody > tr:hover > td) {
|
||||
background: var(--ant-color-bg-layout);
|
||||
}
|
||||
|
||||
.modern-table :deep(.ant-table-expanded-row > td) {
|
||||
padding: 0;
|
||||
background: var(--ant-color-bg-layout);
|
||||
}
|
||||
|
||||
/* 脚本名称单元格 */
|
||||
.script-name-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.script-type-logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border: 1px solid var(--ant-color-border-secondary);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.script-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
object-fit: contain;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.script-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.script-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.script-type-tag {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
/* 用户数量徽章 */
|
||||
.user-count-badge :deep(.ant-badge-count) {
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-button {
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
background: var(--ant-color-primary);
|
||||
border-color: var(--ant-color-primary);
|
||||
}
|
||||
|
||||
.edit-button:hover {
|
||||
background: var(--ant-color-primary-hover);
|
||||
border-color: var(--ant-color-primary-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
.add-user-button {
|
||||
border: 1px solid var(--ant-color-border);
|
||||
background: var(--ant-color-bg-container);
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.add-user-button:hover {
|
||||
border-color: var(--ant-color-primary);
|
||||
color: var(--ant-color-primary);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
/* 展开内容 */
|
||||
.expanded-content {
|
||||
padding: 24px;
|
||||
background: var(--ant-color-bg-layout);
|
||||
border-radius: 0 0 12px 12px;
|
||||
}
|
||||
|
||||
.user-table :deep(.ant-table) {
|
||||
background: var(--ant-color-bg-container);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--ant-color-border-secondary);
|
||||
}
|
||||
|
||||
.user-table :deep(.ant-table-thead > tr > th) {
|
||||
background: var(--ant-color-bg-layout);
|
||||
font-size: 13px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.user-table :deep(.ant-table-tbody > tr > td) {
|
||||
padding: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 状态单元格 */
|
||||
.status-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.status-icon.active {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-icon.inactive {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
/* 最后运行单元格 */
|
||||
.last-run-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.run-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.run-label {
|
||||
color: var(--ant-color-text-secondary);
|
||||
font-weight: 500;
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.run-date {
|
||||
color: var(--ant-color-text);
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}
|
||||
|
||||
/* 任务标签 */
|
||||
.task-tags {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.task-tag {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/* 用户操作按钮 */
|
||||
.user-action-button {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
height: auto;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.user-action-button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 深色模式适配 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.modern-table :deep(.ant-table) {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.edit-button:hover {
|
||||
box-shadow: 0 4px 8px rgba(24, 144, 255, 0.4);
|
||||
}
|
||||
|
||||
.add-user-button:hover {
|
||||
box-shadow: 0 4px 8px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
box-shadow: 0 4px 8px rgba(255, 77, 79, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.modern-table :deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.script-name-cell {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.script-type-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.script-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.expanded-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
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(() => ({
|
||||
|
||||
@@ -18,6 +18,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('../views/Scripts.vue'),
|
||||
meta: { title: '脚本管理' },
|
||||
},
|
||||
{
|
||||
path: '/scripts/:id/edit',
|
||||
name: 'ScriptEdit',
|
||||
component: () => import('../views/ScriptEdit.vue'),
|
||||
meta: { title: '编辑脚本' },
|
||||
},
|
||||
{
|
||||
path: '/plans',
|
||||
name: 'Plans',
|
||||
@@ -48,6 +54,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('../views/Settings.vue'),
|
||||
meta: { title: '设置' },
|
||||
},
|
||||
{
|
||||
path: '/test',
|
||||
name: 'Test',
|
||||
component: () => import('../views/TestScript.vue'),
|
||||
meta: { title: '测试' },
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
@@ -4,76 +4,91 @@
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* 默认浅色模式 CSS 变量 */
|
||||
--ant-color-primary: #1677ff;
|
||||
--ant-color-primary-hover: #4096ff;
|
||||
--ant-color-primary-bg: #1677ff1a;
|
||||
--ant-color-text: rgba(0, 0, 0, 0.88);
|
||||
--ant-color-text-secondary: rgba(0, 0, 0, 0.65);
|
||||
--ant-color-text-tertiary: rgba(0, 0, 0, 0.45);
|
||||
--ant-color-bg-container: #ffffff;
|
||||
--ant-color-bg-layout: #f5f5f5;
|
||||
--ant-color-bg-elevated: #ffffff;
|
||||
--ant-color-border: #d9d9d9;
|
||||
--ant-color-border-secondary: #f0f0f0;
|
||||
--ant-color-error: #ff4d4f;
|
||||
--ant-color-success: #52c41a;
|
||||
--ant-color-warning: #faad14;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
/* 深色模式默认变量 */
|
||||
:root.dark {
|
||||
--ant-color-primary: #1677ff;
|
||||
--ant-color-primary-hover: #4096ff;
|
||||
--ant-color-primary-bg: #1677ff1a;
|
||||
--ant-color-text: rgba(255, 255, 255, 0.88);
|
||||
--ant-color-text-secondary: rgba(255, 255, 255, 0.65);
|
||||
--ant-color-text-tertiary: rgba(255, 255, 255, 0.45);
|
||||
--ant-color-bg-container: #141414;
|
||||
--ant-color-bg-layout: #000000;
|
||||
--ant-color-bg-elevated: #1f1f1f;
|
||||
--ant-color-border: #424242;
|
||||
--ant-color-border-secondary: #303030;
|
||||
--ant-color-error: #ff4d4f;
|
||||
--ant-color-success: #52c41a;
|
||||
--ant-color-warning: #faad14;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
background-color: var(--ant-color-bg-layout);
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
/* 链接样式 */
|
||||
a {
|
||||
color: var(--ant-color-primary);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--ant-color-primary-hover);
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--ant-color-bg-layout);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--ant-color-border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--ant-color-border-secondary);
|
||||
}
|
||||
|
||||
14
frontend/src/types/electron.ts
Normal file
14
frontend/src/types/electron.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// Electron API 类型定义
|
||||
export interface ElectronAPI {
|
||||
openDevTools: () => Promise<void>
|
||||
selectFolder: () => Promise<string | null>
|
||||
selectFile: (filters?: { name: string; extensions: string[] }[]) => Promise<string | null>
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: ElectronAPI
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
171
frontend/src/types/script.ts
Normal file
171
frontend/src/types/script.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
// 脚本类型定义
|
||||
export type ScriptType = 'MAA' | 'General'
|
||||
|
||||
// MAA脚本配置
|
||||
export interface MAAScriptConfig {
|
||||
Info: {
|
||||
Name: string
|
||||
Path: string
|
||||
}
|
||||
Run: {
|
||||
ADBSearchRange: number
|
||||
AnnihilationTimeLimit: number
|
||||
AnnihilationWeeklyLimit: boolean
|
||||
ProxyTimesLimit: number
|
||||
RoutineTimeLimit: number
|
||||
RunTimesLimit: number
|
||||
TaskTransitionMethod: string
|
||||
}
|
||||
SubConfigsInfo: {
|
||||
UserData: {
|
||||
instances: any[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// General脚本配置
|
||||
export interface GeneralScriptConfig {
|
||||
Game: {
|
||||
Arguments: string
|
||||
Enabled: boolean
|
||||
IfForceClose: boolean
|
||||
Path: string
|
||||
Style: string
|
||||
WaitTime: number
|
||||
}
|
||||
Info: {
|
||||
Name: string
|
||||
RootPath: string
|
||||
}
|
||||
Run: {
|
||||
ProxyTimesLimit: number
|
||||
RunTimeLimit: number
|
||||
RunTimesLimit: number
|
||||
}
|
||||
Script: {
|
||||
Arguments: string
|
||||
ConfigPath: string
|
||||
ConfigPathMode: string
|
||||
ErrorLog: string
|
||||
IfTrackProcess: boolean
|
||||
LogPath: string
|
||||
LogPathFormat: string
|
||||
LogTimeEnd: number
|
||||
LogTimeStart: number
|
||||
LogTimeFormat: string
|
||||
ScriptPath: string
|
||||
SuccessLog: string
|
||||
UpdateConfigMode: string
|
||||
}
|
||||
SubConfigsInfo: {
|
||||
UserData: {
|
||||
instances: any[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 脚本基础信息
|
||||
export interface Script {
|
||||
id: string
|
||||
type: ScriptType
|
||||
name: string
|
||||
config: MAAScriptConfig | GeneralScriptConfig
|
||||
users: User[]
|
||||
}
|
||||
|
||||
// 用户配置
|
||||
export interface User {
|
||||
id: string
|
||||
name: string
|
||||
Data: {
|
||||
CustomInfrastPlanIndex: string
|
||||
IfPassCheck: boolean
|
||||
LastAnnihilationDate: string
|
||||
LastProxyDate: string
|
||||
LastSklandDate: string
|
||||
ProxyTimes: number
|
||||
}
|
||||
Info: {
|
||||
Annihilation: string
|
||||
Id: string
|
||||
IfSkland: boolean
|
||||
InfrastMode: string
|
||||
MedicineNumb: number
|
||||
Mode: string
|
||||
Name: string
|
||||
Notes: string
|
||||
Password: string
|
||||
RemainedDay: number
|
||||
Routine: boolean
|
||||
SeriesNumb: string
|
||||
Server: string
|
||||
SklandToken: string
|
||||
Stage: string
|
||||
StageMode: string
|
||||
Stage_1: string
|
||||
Stage_2: string
|
||||
Stage_3: string
|
||||
Stage_Remain: string
|
||||
Status: boolean
|
||||
}
|
||||
Notify: {
|
||||
CompanyWebHookBotUrl: string
|
||||
Enabled: boolean
|
||||
IfCompanyWebHookBot: boolean
|
||||
IfSendMail: boolean
|
||||
IfSendSixStar: boolean
|
||||
IfSendStatistic: boolean
|
||||
IfServerChan: boolean
|
||||
ServerChanChannel: string
|
||||
ServerChanKey: string
|
||||
ServerChanTag: string
|
||||
ToAddress: string
|
||||
}
|
||||
Task: {
|
||||
IfAutoRoguelike: boolean
|
||||
IfBase: boolean
|
||||
IfCombat: boolean
|
||||
IfMall: boolean
|
||||
IfMission: boolean
|
||||
IfReclamation: boolean
|
||||
IfRecruiting: boolean
|
||||
IfWakeUp: boolean
|
||||
}
|
||||
QFluentWidgets: {
|
||||
ThemeColor: string
|
||||
ThemeMode: string
|
||||
}
|
||||
}
|
||||
|
||||
// API响应类型
|
||||
export interface AddScriptResponse {
|
||||
code: number
|
||||
status: string
|
||||
message: string
|
||||
scriptId: string
|
||||
data: MAAScriptConfig | GeneralScriptConfig
|
||||
}
|
||||
|
||||
// 脚本索引项
|
||||
export interface ScriptIndexItem {
|
||||
uid: string
|
||||
type: 'MaaConfig' | 'GeneralConfig'
|
||||
}
|
||||
|
||||
// 获取脚本API响应
|
||||
export interface GetScriptsResponse {
|
||||
code: number
|
||||
status: string
|
||||
message: string
|
||||
index: ScriptIndexItem[]
|
||||
data: Record<string, MAAScriptConfig | GeneralScriptConfig>
|
||||
}
|
||||
|
||||
// 脚本详情(用于前端展示)
|
||||
export interface ScriptDetail {
|
||||
uid: string
|
||||
type: ScriptType
|
||||
name: string
|
||||
config: MAAScriptConfig | GeneralScriptConfig
|
||||
createTime?: string
|
||||
}
|
||||
1549
frontend/src/views/ScriptEdit.vue
Normal file
1549
frontend/src/views/ScriptEdit.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,591 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<h1>脚本管理</h1>
|
||||
<p>这里是脚本管理内容的占位符</p>
|
||||
<div class="scripts-container">
|
||||
<div class="scripts-header">
|
||||
<div class="header-title">
|
||||
<FileTextOutlined class="title-icon" />
|
||||
<h1>脚本管理</h1>
|
||||
</div>
|
||||
<a-space size="middle">
|
||||
<a-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="handleAddScript"
|
||||
class="add-button"
|
||||
>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
添加脚本
|
||||
</a-button>
|
||||
<a-button
|
||||
size="large"
|
||||
@click="handleRefresh"
|
||||
class="refresh-button"
|
||||
>
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
刷新
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="scripts-content">
|
||||
<ScriptTable
|
||||
:scripts="scripts"
|
||||
@edit="handleEditScript"
|
||||
@delete="handleDeleteScript"
|
||||
@add-user="handleAddUser"
|
||||
@edit-user="handleEditUser"
|
||||
@delete-user="handleDeleteUser"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 脚本类型选择弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="typeSelectVisible"
|
||||
title="选择脚本类型"
|
||||
:confirm-loading="addLoading"
|
||||
@ok="handleConfirmAddScript"
|
||||
@cancel="typeSelectVisible = false"
|
||||
class="type-select-modal"
|
||||
width="500px"
|
||||
>
|
||||
<div class="type-selection">
|
||||
<a-radio-group
|
||||
v-model:value="selectedType"
|
||||
class="type-radio-group"
|
||||
>
|
||||
<a-radio-button value="MAA" class="type-option">
|
||||
<div class="type-content">
|
||||
<div class="type-logo-container">
|
||||
<img src="@/assets/MAA.png" alt="MAA" class="type-logo" />
|
||||
</div>
|
||||
<div class="type-info">
|
||||
<div class="type-title">MAA脚本</div>
|
||||
<div class="type-description">明日方舟自动化脚本,支持日常任务、作战等功能</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-button>
|
||||
<a-radio-button value="General" class="type-option">
|
||||
<div class="type-content">
|
||||
<div class="type-logo-container">
|
||||
<img src="@/assets/AUTO_MAA.png" alt="AUTO MAA" class="type-logo" />
|
||||
</div>
|
||||
<div class="type-info">
|
||||
<div class="type-title">General脚本</div>
|
||||
<div class="type-description">通用自动化脚本,支持自定义游戏和脚本配置</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { PlusOutlined, ReloadOutlined, FileTextOutlined } from '@ant-design/icons-vue'
|
||||
import ScriptTable from '@/components/ScriptTable.vue'
|
||||
import type { Script, User, ScriptType, MAAScriptConfig, GeneralScriptConfig, ScriptDetail } from '@/types/script'
|
||||
import { useScriptApi } from '@/composables/useScriptApi'
|
||||
|
||||
const router = useRouter()
|
||||
const { addScript, deleteScript, getScripts, loading } = useScriptApi()
|
||||
|
||||
const scripts = ref<Script[]>([])
|
||||
const typeSelectVisible = ref(false)
|
||||
const selectedType = ref<ScriptType>('MAA')
|
||||
const addLoading = ref(false)
|
||||
|
||||
// 模拟数据
|
||||
const mockScripts: Script[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'MAA',
|
||||
name: 'MAA自动化脚本',
|
||||
config: {
|
||||
Info: {
|
||||
Name: 'MAA自动化脚本',
|
||||
Path: 'D:/MAA_For_AutoMAA'
|
||||
},
|
||||
Run: {
|
||||
ADBSearchRange: 3,
|
||||
AnnihilationTimeLimit: 40,
|
||||
AnnihilationWeeklyLimit: true,
|
||||
ProxyTimesLimit: 0,
|
||||
RoutineTimeLimit: 10,
|
||||
RunTimesLimit: 5,
|
||||
TaskTransitionMethod: 'NoAction'
|
||||
},
|
||||
SubConfigsInfo: {
|
||||
UserData: {
|
||||
instances: []
|
||||
}
|
||||
}
|
||||
} as MAAScriptConfig,
|
||||
users: [
|
||||
{
|
||||
id: 'user1',
|
||||
name: 'aoxuan',
|
||||
Data: {
|
||||
CustomInfrastPlanIndex: '0',
|
||||
IfPassCheck: true,
|
||||
LastAnnihilationDate: '2025-07-28',
|
||||
LastProxyDate: '2025-08-03',
|
||||
LastSklandDate: '2000-01-01',
|
||||
ProxyTimes: 2
|
||||
},
|
||||
Info: {
|
||||
Annihilation: 'Annihilation',
|
||||
Id: '8668',
|
||||
IfSkland: false,
|
||||
InfrastMode: 'Normal',
|
||||
MedicineNumb: 0,
|
||||
Mode: '简洁',
|
||||
Name: 'aoxuan',
|
||||
Notes: '无',
|
||||
Password: 'BVd/Y56Mts0gLywaz5kqT5lU',
|
||||
RemainedDay: -1,
|
||||
Routine: false,
|
||||
SeriesNumb: '0',
|
||||
Server: 'Official',
|
||||
SklandToken: '',
|
||||
Stage: 'AT-8',
|
||||
StageMode: '固定',
|
||||
Stage_1: '-',
|
||||
Stage_2: '-',
|
||||
Stage_3: '-',
|
||||
Stage_Remain: '-',
|
||||
Status: true
|
||||
},
|
||||
Notify: {
|
||||
CompanyWebHookBotUrl: '',
|
||||
Enabled: false,
|
||||
IfCompanyWebHookBot: false,
|
||||
IfSendMail: false,
|
||||
IfSendSixStar: false,
|
||||
IfSendStatistic: false,
|
||||
IfServerChan: false,
|
||||
ServerChanChannel: '',
|
||||
ServerChanKey: '',
|
||||
ServerChanTag: '',
|
||||
ToAddress: ''
|
||||
},
|
||||
Task: {
|
||||
IfAutoRoguelike: false,
|
||||
IfBase: true,
|
||||
IfCombat: true,
|
||||
IfMall: true,
|
||||
IfMission: true,
|
||||
IfReclamation: false,
|
||||
IfRecruiting: true,
|
||||
IfWakeUp: true
|
||||
},
|
||||
QFluentWidgets: {
|
||||
ThemeColor: '#ff009faa',
|
||||
ThemeMode: 'Auto'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'General',
|
||||
name: '通用自动化脚本',
|
||||
config: {
|
||||
Game: {
|
||||
Arguments: '',
|
||||
Enabled: false,
|
||||
IfForceClose: false,
|
||||
Path: '.',
|
||||
Style: 'Emulator',
|
||||
WaitTime: 0
|
||||
},
|
||||
Info: {
|
||||
Name: '通用自动化脚本',
|
||||
RootPath: '.'
|
||||
},
|
||||
Run: {
|
||||
ProxyTimesLimit: 0,
|
||||
RunTimeLimit: 10,
|
||||
RunTimesLimit: 3
|
||||
},
|
||||
Script: {
|
||||
Arguments: '',
|
||||
ConfigPath: '.',
|
||||
ConfigPathMode: '所有文件 (*)',
|
||||
ErrorLog: '',
|
||||
IfTrackProcess: false,
|
||||
LogPath: '.',
|
||||
LogPathFormat: '%Y-%m-%d',
|
||||
LogTimeEnd: 1,
|
||||
LogTimeStart: 1,
|
||||
LogTimeFormat: '%Y-%m-%d %H:%M:%S',
|
||||
ScriptPath: '.',
|
||||
SuccessLog: '',
|
||||
UpdateConfigMode: 'Never'
|
||||
},
|
||||
SubConfigsInfo: {
|
||||
UserData: {
|
||||
instances: []
|
||||
}
|
||||
}
|
||||
} as GeneralScriptConfig,
|
||||
users: []
|
||||
}
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
loadScripts()
|
||||
})
|
||||
|
||||
const loadScripts = async () => {
|
||||
try {
|
||||
const scriptDetails = await getScripts()
|
||||
|
||||
// 将 ScriptDetail 转换为 Script 格式(为了兼容现有的表格组件)
|
||||
scripts.value = scriptDetails.map(detail => ({
|
||||
id: detail.uid,
|
||||
type: detail.type,
|
||||
name: detail.name,
|
||||
config: detail.config,
|
||||
users: [], // 暂时为空,后续可以从其他API获取用户数据
|
||||
createTime: detail.createTime || new Date().toLocaleString()
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('加载脚本列表失败:', error)
|
||||
message.error('加载脚本列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddScript = () => {
|
||||
selectedType.value = 'MAA'
|
||||
typeSelectVisible.value = true
|
||||
}
|
||||
|
||||
const handleConfirmAddScript = async () => {
|
||||
addLoading.value = true
|
||||
try {
|
||||
const result = await addScript(selectedType.value)
|
||||
if (result) {
|
||||
message.success(result.message)
|
||||
typeSelectVisible.value = false
|
||||
// 跳转到编辑页面,传递API返回的数据
|
||||
router.push({
|
||||
path: `/scripts/${result.scriptId}/edit`,
|
||||
state: {
|
||||
scriptData: {
|
||||
id: result.scriptId,
|
||||
type: selectedType.value,
|
||||
config: result.data
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加脚本失败:', error)
|
||||
} finally {
|
||||
addLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleEditScript = (script: Script) => {
|
||||
// 跳转到独立的编辑页面
|
||||
router.push(`/scripts/${script.id}/edit`)
|
||||
}
|
||||
|
||||
const handleDeleteScript = async (script: Script) => {
|
||||
const result = await deleteScript(script.id)
|
||||
if (result) {
|
||||
message.success('脚本删除成功')
|
||||
loadScripts()
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddUser = (script: Script) => {
|
||||
// TODO: 实现添加用户功能
|
||||
message.info('添加用户功能待实现')
|
||||
}
|
||||
|
||||
const handleEditUser = (user: User) => {
|
||||
// TODO: 实现编辑用户功能
|
||||
message.info('编辑用户功能待实现')
|
||||
}
|
||||
|
||||
const handleDeleteUser = (user: User) => {
|
||||
// TODO: 实现删除用户功能
|
||||
message.info('删除用户功能待实现')
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
loadScripts()
|
||||
message.success('刷新成功')
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
.scripts-container {
|
||||
padding: 32px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--ant-color-bg-layout);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.scripts-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 32px;
|
||||
color: var(--ant-color-primary);
|
||||
}
|
||||
|
||||
.header-title h1 {
|
||||
margin: 0;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: var(--ant-color-text);
|
||||
background: linear-gradient(135deg, var(--ant-color-primary), var(--ant-color-primary-hover));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
height: 48px;
|
||||
padding: 0 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(24, 144, 255, 0.4);
|
||||
}
|
||||
|
||||
.refresh-button {
|
||||
height: 48px;
|
||||
padding: 0 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--ant-color-border);
|
||||
background: var(--ant-color-bg-container);
|
||||
color: var(--ant-color-text);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.refresh-button:hover {
|
||||
border-color: var(--ant-color-primary);
|
||||
color: var(--ant-color-primary);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.scripts-content {
|
||||
flex: 1;
|
||||
background: var(--ant-color-bg-container);
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
box-shadow:
|
||||
0 4px 20px rgba(0, 0, 0, 0.08),
|
||||
0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--ant-color-border-secondary);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.scripts-content:hover {
|
||||
box-shadow:
|
||||
0 8px 30px rgba(0, 0, 0, 0.12),
|
||||
0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 脚本类型选择弹窗样式 */
|
||||
.type-select-modal :deep(.ant-modal-content) {
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background: var(--ant-color-bg-container);
|
||||
}
|
||||
|
||||
.type-select-modal :deep(.ant-modal-header) {
|
||||
background: var(--ant-color-bg-container);
|
||||
border-bottom: 1px solid var(--ant-color-border-secondary);
|
||||
padding: 24px 32px 20px;
|
||||
}
|
||||
|
||||
.type-select-modal :deep(.ant-modal-title) {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.type-select-modal :deep(.ant-modal-body) {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.type-selection {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.type-radio-group {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.type-radio-group :deep(.ant-radio-button-wrapper) {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
border: 2px solid var(--ant-color-border);
|
||||
border-radius: 12px;
|
||||
background: var(--ant-color-bg-container);
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.type-radio-group :deep(.ant-radio-button-wrapper:hover) {
|
||||
border-color: var(--ant-color-primary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
.type-radio-group :deep(.ant-radio-button-wrapper-checked) {
|
||||
border-color: var(--ant-color-primary);
|
||||
background: var(--ant-color-primary-bg);
|
||||
box-shadow: 0 4px 16px rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
.type-radio-group :deep(.ant-radio-button-wrapper::before) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.type-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.type-logo-container {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border: 1px solid var(--ant-color-border-secondary);
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.type-logo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
object-fit: contain;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.type-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.type-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--ant-color-text);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.type-description {
|
||||
font-size: 14px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 深色模式适配 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.scripts-content {
|
||||
box-shadow:
|
||||
0 4px 20px rgba(0, 0, 0, 0.3),
|
||||
0 1px 3px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.scripts-content:hover {
|
||||
box-shadow:
|
||||
0 8px 30px rgba(0, 0, 0, 0.4),
|
||||
0 2px 6px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.add-button {
|
||||
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.4);
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
box-shadow: 0 6px 16px rgba(24, 144, 255, 0.5);
|
||||
}
|
||||
|
||||
.refresh-button:hover {
|
||||
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.scripts-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.scripts-header {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-title h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.scripts-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.type-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.type-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
90
frontend/src/views/TestScript.vue
Normal file
90
frontend/src/views/TestScript.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div style="padding: 20px;">
|
||||
<h2>脚本API测试</h2>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="testAddMAA" :loading="loading">
|
||||
测试添加MAA脚本
|
||||
</a-button>
|
||||
<a-button type="primary" @click="testAddGeneral" :loading="loading">
|
||||
测试添加General脚本
|
||||
</a-button>
|
||||
<a-button @click="goToScripts">
|
||||
前往脚本管理页面
|
||||
</a-button>
|
||||
</a-space>
|
||||
|
||||
<div v-if="result" style="margin-top: 20px;">
|
||||
<h3>API响应结果:</h3>
|
||||
<pre>{{ JSON.stringify(result, null, 2) }}</pre>
|
||||
|
||||
<a-space style="margin-top: 10px;">
|
||||
<a-button type="primary" @click="goToEdit">
|
||||
前往编辑页面
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div v-if="error" style="margin-top: 20px; color: red;">
|
||||
<h3>错误信息:</h3>
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px;">
|
||||
<h3>测试说明:</h3>
|
||||
<ul>
|
||||
<li>点击"测试添加MAA脚本"或"测试添加General脚本"来测试API调用</li>
|
||||
<li>成功后会显示API返回的数据,包含scriptId和配置信息</li>
|
||||
<li>点击"前往编辑页面"可以跳转到编辑页面查看配置</li>
|
||||
<li>或者直接前往脚本管理页面测试完整流程</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useScriptApi } from '@/composables/useScriptApi'
|
||||
|
||||
const router = useRouter()
|
||||
const { addScript, loading, error } = useScriptApi()
|
||||
const result = ref<any>(null)
|
||||
const lastScriptType = ref<'MAA' | 'General'>('MAA')
|
||||
|
||||
const testAddMAA = async () => {
|
||||
result.value = null
|
||||
lastScriptType.value = 'MAA'
|
||||
const response = await addScript('MAA')
|
||||
if (response) {
|
||||
result.value = response
|
||||
}
|
||||
}
|
||||
|
||||
const testAddGeneral = async () => {
|
||||
result.value = null
|
||||
lastScriptType.value = 'General'
|
||||
const response = await addScript('General')
|
||||
if (response) {
|
||||
result.value = response
|
||||
}
|
||||
}
|
||||
|
||||
const goToEdit = () => {
|
||||
if (result.value) {
|
||||
router.push({
|
||||
path: `/scripts/${result.value.scriptId}/edit`,
|
||||
state: {
|
||||
scriptData: {
|
||||
id: result.value.scriptId,
|
||||
type: lastScriptType.value,
|
||||
config: result.value.data
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const goToScripts = () => {
|
||||
router.push('/scripts')
|
||||
}
|
||||
</script>
|
||||
4
frontend/src/vite-env.d.ts
vendored
4
frontend/src/vite-env.d.ts
vendored
@@ -3,7 +3,9 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI?: {
|
||||
openDevTools: () => void
|
||||
openDevTools: () => Promise<void>
|
||||
selectFolder: () => Promise<string | null>
|
||||
selectFile: (filters?: Array<{ name: string; extensions: string[] }>) => Promise<string | null>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user