refactor(notify): 重构通知设置,支持自定义Webhook管理与模板配置

This commit is contained in:
2025-09-27 16:54:50 +08:00
parent 28ce06c92d
commit 14a8a308c5
26 changed files with 1207 additions and 389 deletions

View File

@@ -9,19 +9,22 @@ export type { OpenAPIConfig } from './core/OpenAPI';
export type { ComboBoxItem } from './models/ComboBoxItem';
export type { ComboBoxOut } from './models/ComboBoxOut';
export type { CustomWebhook } from './models/CustomWebhook';
export type { DispatchIn } from './models/DispatchIn';
export type { GeneralConfig } from './models/GeneralConfig';
export type { GeneralConfig_Game } from './models/GeneralConfig_Game';
export type { GeneralConfig_Info } from './models/GeneralConfig_Info';
export type { GeneralConfig_Run } from './models/GeneralConfig_Run';
export type { GeneralConfig_Script } from './models/GeneralConfig_Script';
export type { GeneralUserConfig } from './models/GeneralUserConfig';
export type { GeneralUserConfig_Data } from './models/GeneralUserConfig_Data';
export type { GeneralUserConfig_Info } from './models/GeneralUserConfig_Info';
export type { GeneralUserConfig_Input } from './models/GeneralUserConfig_Input';
export type { GeneralUserConfig_Output } from './models/GeneralUserConfig_Output';
export { GetStageIn } from './models/GetStageIn';
export type { GlobalConfig } from './models/GlobalConfig';
export type { GlobalConfig_Function } from './models/GlobalConfig_Function';
export type { GlobalConfig_Input } from './models/GlobalConfig_Input';
export type { GlobalConfig_Notify } from './models/GlobalConfig_Notify';
export type { GlobalConfig_Output } from './models/GlobalConfig_Output';
export type { GlobalConfig_Start } from './models/GlobalConfig_Start';
export type { GlobalConfig_UI } from './models/GlobalConfig_UI';
export type { GlobalConfig_Update } from './models/GlobalConfig_Update';
@@ -40,9 +43,10 @@ export type { MaaConfig_Run } from './models/MaaConfig_Run';
export type { MaaPlanConfig } from './models/MaaPlanConfig';
export type { MaaPlanConfig_Info } from './models/MaaPlanConfig_Info';
export type { MaaPlanConfig_Item } from './models/MaaPlanConfig_Item';
export type { MaaUserConfig } from './models/MaaUserConfig';
export type { MaaUserConfig_Data } from './models/MaaUserConfig_Data';
export type { MaaUserConfig_Info } from './models/MaaUserConfig_Info';
export type { MaaUserConfig_Input } from './models/MaaUserConfig_Input';
export type { MaaUserConfig_Output } from './models/MaaUserConfig_Output';
export type { MaaUserConfig_Task } from './models/MaaUserConfig_Task';
export type { NoticeOut } from './models/NoticeOut';
export type { OutBase } from './models/OutBase';

View File

@@ -0,0 +1,35 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type CustomWebhook = {
/**
* Webhook唯一标识
*/
id: string;
/**
* Webhook名称
*/
name: string;
/**
* Webhook URL
*/
url: string;
/**
* 消息模板
*/
template: string;
/**
* 是否启用
*/
enabled?: boolean;
/**
* 自定义请求头
*/
headers?: (Record<string, string> | null);
/**
* 请求方法
*/
method?: ('POST' | 'GET' | null);
};

View File

@@ -5,7 +5,7 @@
import type { GeneralUserConfig_Data } from './GeneralUserConfig_Data';
import type { GeneralUserConfig_Info } from './GeneralUserConfig_Info';
import type { UserConfig_Notify } from './UserConfig_Notify';
export type GeneralUserConfig = {
export type GeneralUserConfig_Input = {
/**
*
*/

View File

@@ -0,0 +1,22 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GeneralUserConfig_Data } from './GeneralUserConfig_Data';
import type { GeneralUserConfig_Info } from './GeneralUserConfig_Info';
import type { UserConfig_Notify } from './UserConfig_Notify';
export type GeneralUserConfig_Output = {
/**
* 用户信息
*/
Info?: (GeneralUserConfig_Info | null);
/**
* 用户数据
*/
Data?: (GeneralUserConfig_Data | null);
/**
* 单独通知
*/
Notify?: (UserConfig_Notify | null);
};

View File

@@ -8,7 +8,7 @@ import type { GlobalConfig_Start } from './GlobalConfig_Start';
import type { GlobalConfig_UI } from './GlobalConfig_UI';
import type { GlobalConfig_Update } from './GlobalConfig_Update';
import type { GlobalConfig_Voice } from './GlobalConfig_Voice';
export type GlobalConfig = {
export type GlobalConfig_Input = {
/**
*
*/

View File

@@ -2,6 +2,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CustomWebhook } from './CustomWebhook';
export type GlobalConfig_Notify = {
/**
* 任务结果推送时机
@@ -48,12 +49,8 @@ export type GlobalConfig_Notify = {
*/
ServerChanKey?: (string | null);
/**
* 是否使用企微Webhook推送
* 自定义Webhook列表
*/
IfCompanyWebHookBot?: (boolean | null);
/**
* 企微Webhook Bot URL
*/
CompanyWebHookBotUrl?: (string | null);
CustomWebhooks?: (Array<CustomWebhook> | null);
};

View File

@@ -0,0 +1,37 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GlobalConfig_Function } from './GlobalConfig_Function';
import type { GlobalConfig_Notify } from './GlobalConfig_Notify';
import type { GlobalConfig_Start } from './GlobalConfig_Start';
import type { GlobalConfig_UI } from './GlobalConfig_UI';
import type { GlobalConfig_Update } from './GlobalConfig_Update';
import type { GlobalConfig_Voice } from './GlobalConfig_Voice';
export type GlobalConfig_Output = {
/**
* 功能相关配置
*/
Function?: (GlobalConfig_Function | null);
/**
* 语音相关配置
*/
Voice?: (GlobalConfig_Voice | null);
/**
* 启动相关配置
*/
Start?: (GlobalConfig_Start | null);
/**
* 界面相关配置
*/
UI?: (GlobalConfig_UI | null);
/**
* 通知相关配置
*/
Notify?: (GlobalConfig_Notify | null);
/**
* 更新相关配置
*/
Update?: (GlobalConfig_Update | null);
};

View File

@@ -6,7 +6,7 @@ import type { MaaUserConfig_Data } from './MaaUserConfig_Data';
import type { MaaUserConfig_Info } from './MaaUserConfig_Info';
import type { MaaUserConfig_Task } from './MaaUserConfig_Task';
import type { UserConfig_Notify } from './UserConfig_Notify';
export type MaaUserConfig = {
export type MaaUserConfig_Input = {
/**
*
*/

View File

@@ -0,0 +1,27 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { MaaUserConfig_Data } from './MaaUserConfig_Data';
import type { MaaUserConfig_Info } from './MaaUserConfig_Info';
import type { MaaUserConfig_Task } from './MaaUserConfig_Task';
import type { UserConfig_Notify } from './UserConfig_Notify';
export type MaaUserConfig_Output = {
/**
* 基础信息
*/
Info?: (MaaUserConfig_Info | null);
/**
* 用户数据
*/
Data?: (MaaUserConfig_Data | null);
/**
* 任务列表
*/
Task?: (MaaUserConfig_Task | null);
/**
* 单独通知
*/
Notify?: (UserConfig_Notify | null);
};

View File

@@ -2,7 +2,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GlobalConfig } from './GlobalConfig';
import type { GlobalConfig_Output } from './GlobalConfig_Output';
export type SettingGetOut = {
/**
* 状态码
@@ -19,6 +19,6 @@ export type SettingGetOut = {
/**
* 全局设置数据
*/
data: GlobalConfig;
data: GlobalConfig_Output;
};

View File

@@ -2,11 +2,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GlobalConfig } from './GlobalConfig';
import type { GlobalConfig_Input } from './GlobalConfig_Input';
export type SettingUpdateIn = {
/**
* 全局设置需要更新的数据
*/
data: GlobalConfig;
data: GlobalConfig_Input;
};

View File

@@ -2,6 +2,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CustomWebhook } from './CustomWebhook';
export type UserConfig_Notify = {
/**
* 是否启用通知
@@ -32,12 +33,8 @@ export type UserConfig_Notify = {
*/
ServerChanKey?: (string | null);
/**
* 是否使用Webhook推送
* 用户自定义Webhook列表
*/
IfCompanyWebHookBot?: (boolean | null);
/**
* 企微Webhook Bot URL
*/
CompanyWebHookBotUrl?: (string | null);
CustomWebhooks?: (Array<CustomWebhook> | null);
};

View File

@@ -2,8 +2,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GeneralUserConfig } from './GeneralUserConfig';
import type { MaaUserConfig } from './MaaUserConfig';
import type { GeneralUserConfig_Output } from './GeneralUserConfig_Output';
import type { MaaUserConfig_Output } from './MaaUserConfig_Output';
export type UserCreateOut = {
/**
* 状态码
@@ -24,6 +24,6 @@ export type UserCreateOut = {
/**
* 用户配置数据
*/
data: (MaaUserConfig | GeneralUserConfig);
data: (MaaUserConfig_Output | GeneralUserConfig_Output);
};

View File

@@ -2,8 +2,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GeneralUserConfig } from './GeneralUserConfig';
import type { MaaUserConfig } from './MaaUserConfig';
import type { GeneralUserConfig_Output } from './GeneralUserConfig_Output';
import type { MaaUserConfig_Output } from './MaaUserConfig_Output';
import type { UserIndexItem } from './UserIndexItem';
export type UserGetOut = {
/**
@@ -25,6 +25,6 @@ export type UserGetOut = {
/**
* 用户数据字典, key来自于index列表的uid
*/
data: Record<string, (MaaUserConfig | GeneralUserConfig)>;
data: Record<string, (MaaUserConfig_Output | GeneralUserConfig_Output)>;
};

View File

@@ -2,8 +2,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { GeneralUserConfig } from './GeneralUserConfig';
import type { MaaUserConfig } from './MaaUserConfig';
import type { GeneralUserConfig_Input } from './GeneralUserConfig_Input';
import type { MaaUserConfig_Input } from './MaaUserConfig_Input';
export type UserUpdateIn = {
/**
* 所属脚本ID
@@ -16,6 +16,6 @@ export type UserUpdateIn = {
/**
* 用户更新数据
*/
data: (MaaUserConfig | GeneralUserConfig);
data: (MaaUserConfig_Input | GeneralUserConfig_Input);
};

View File

@@ -994,6 +994,86 @@ export class Service {
url: '/api/setting/test_notify',
});
}
/**
* 创建自定义Webhook
* 创建自定义Webhook
* @param requestBody
* @returns OutBase Successful Response
* @throws ApiError
*/
public static createWebhookApiSettingWebhookCreatePost(
requestBody: Record<string, any>,
): CancelablePromise<OutBase> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/setting/webhook/create',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* 更新自定义Webhook
* 更新自定义Webhook
* @param requestBody
* @returns OutBase Successful Response
* @throws ApiError
*/
public static updateWebhookApiSettingWebhookUpdatePost(
requestBody: Record<string, any>,
): CancelablePromise<OutBase> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/setting/webhook/update',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* 删除自定义Webhook
* 删除自定义Webhook
* @param requestBody
* @returns OutBase Successful Response
* @throws ApiError
*/
public static deleteWebhookApiSettingWebhookDeletePost(
requestBody: Record<string, any>,
): CancelablePromise<OutBase> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/setting/webhook/delete',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* 测试自定义Webhook
* 测试自定义Webhook
* @param requestBody
* @returns OutBase Successful Response
* @throws ApiError
*/
public static testWebhookApiSettingWebhookTestPost(
requestBody: Record<string, any>,
): CancelablePromise<OutBase> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/setting/webhook/test',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* 检查更新
* @param requestBody

View File

@@ -0,0 +1,524 @@
<template>
<div class="webhook-manager">
<div class="webhook-header">
<h3>自定义 Webhook 通知</h3>
<a-button type="primary" @click="showAddModal" size="middle">
<template #icon>
<PlusOutlined />
</template>
添加 Webhook
</a-button>
</div>
<!-- Webhook 列表 -->
<div class="webhook-list" v-if="webhooks.length > 0">
<div
v-for="webhook in webhooks"
:key="webhook.id"
class="webhook-item"
:class="{ 'webhook-disabled': !webhook.enabled }"
>
<div class="webhook-info">
<div class="webhook-name">
<span class="name-text">{{ webhook.name }}</span>
<a-tag :color="webhook.enabled ? 'green' : 'red'" size="small">
{{ webhook.enabled ? '启用' : '禁用' }}
</a-tag>
</div>
<div class="webhook-url">{{ webhook.url }}</div>
</div>
<div class="webhook-actions">
<a-button type="text" size="small" @click="testWebhook(webhook)" :loading="testingWebhooks[webhook.id]">
<template #icon>
<PlayCircleOutlined />
</template>
测试
</a-button>
<a-button type="text" size="small" @click="editWebhook(webhook)">
<template #icon>
<EditOutlined />
</template>
编辑
</a-button>
<a-button type="text" size="small" danger @click="deleteWebhook(webhook)">
<template #icon>
<DeleteOutlined />
</template>
删除
</a-button>
</div>
</div>
</div>
<div v-else class="empty-state">
<div class="empty-icon">
<ApiOutlined />
</div>
<div class="empty-text">暂无自定义 Webhook</div>
<div class="empty-description">点击上方按钮添加您的第一个 Webhook</div>
</div>
<!-- 添加/编辑 Webhook 弹窗 -->
<a-modal
v-model:open="modalVisible"
:title="isEditing ? '编辑 Webhook' : '添加 Webhook'"
width="800px"
:ok-text="isEditing ? '更新' : '添加'"
@ok="handleSubmit"
@cancel="handleCancel"
:confirm-loading="submitting"
>
<a-form :model="formData" layout="vertical" ref="formRef">
<!-- 模板选择放在最上面 -->
<a-form-item label="选择模板">
<a-select
v-model:value="selectedTemplate"
placeholder="选择预设模板或自定义"
@change="applyTemplate"
allow-clear
>
<a-select-option v-for="template in WEBHOOK_TEMPLATES" :key="template.name" :value="template.name">
{{ template.name }} - {{ template.description }}
</a-select-option>
</a-select>
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="Webhook 名称" name="name" :rules="[{ required: true, message: '请输入 Webhook 名称' }]">
<a-input v-model:value="formData.name" placeholder="请输入 Webhook 名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="请求方法" name="method">
<a-select v-model:value="formData.method">
<a-select-option value="POST">POST</a-select-option>
<a-select-option value="GET">GET</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="Webhook URL" name="url" :rules="[{ required: true, message: '请输入 Webhook URL' }]">
<a-input v-model:value="formData.url" placeholder="https://your-webhook-url.com/api/notify" />
</a-form-item>
<a-form-item label="消息模板">
<a-textarea
v-model:value="formData.template"
:rows="6"
placeholder="请输入消息模板,支持变量: {title}, {content}, {datetime}, {date}, {time}"
/>
<div class="template-help">
<a-typography-text type="secondary" style="font-size: 12px;">
支持的变量
<a-tag size="small" v-for="variable in TEMPLATE_VARIABLES" :key="variable.name">
{{ variable.name }}
</a-tag>
</a-typography-text>
</div>
</a-form-item>
<a-form-item label="自定义请求头 (可选)">
<div class="headers-input">
<div v-for="(header, index) in formData.headersList" :key="index" class="header-row">
<a-input
v-model:value="header.key"
placeholder="Header 名称"
style="width: 40%; margin-right: 8px;"
/>
<a-input
v-model:value="header.value"
placeholder="Header 值"
style="width: 40%; margin-right: 8px;"
/>
<a-button type="text" danger @click="removeHeader(index)" size="small">
<template #icon>
<DeleteOutlined />
</template>
</a-button>
</div>
<a-button type="dashed" @click="addHeader" size="small" style="width: 100%; margin-top: 8px;">
<template #icon>
<PlusOutlined />
</template>
添加请求头
</a-button>
</div>
</a-form-item>
<a-form-item>
<a-checkbox v-model:checked="formData.enabled">启用此 Webhook</a-checkbox>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { message } from 'ant-design-vue'
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
PlayCircleOutlined,
ApiOutlined
} from '@ant-design/icons-vue'
import type { CustomWebhook } from '@/types/settings'
import { WEBHOOK_TEMPLATES, TEMPLATE_VARIABLES } from '@/utils/webhookTemplates'
import { Service } from '@/api/services/Service'
const props = defineProps<{
webhooks: CustomWebhook[]
}>()
const emit = defineEmits<{
'update:webhooks': [webhooks: CustomWebhook[]]
'change': []
}>()
// 响应式数据
const modalVisible = ref(false)
const isEditing = ref(false)
const submitting = ref(false)
const selectedTemplate = ref<string>()
const testingWebhooks = ref<Record<string, boolean>>({})
const formRef = ref()
// 表单数据
const formData = reactive({
id: '',
name: '',
url: '',
template: '',
method: 'POST' as 'POST' | 'GET',
enabled: true,
headersList: [] as Array<{ key: string; value: string }>
})
// 计算属性
const webhooks = computed({
get: () => props.webhooks || [],
set: (value) => emit('update:webhooks', value)
})
// 监听 webhooks 变化
watch(() => props.webhooks, (newWebhooks) => {
// 可以在这里处理 webhooks 变化的逻辑
}, { deep: true })
// 显示添加弹窗
const showAddModal = () => {
isEditing.value = false
resetForm()
modalVisible.value = true
}
// 编辑 Webhook
const editWebhook = (webhook: CustomWebhook) => {
isEditing.value = true
formData.id = webhook.id
formData.name = webhook.name
formData.url = webhook.url
formData.template = webhook.template
formData.method = webhook.method || 'POST'
formData.enabled = webhook.enabled
// 转换 headers 为列表格式
formData.headersList = webhook.headers
? Object.entries(webhook.headers).map(([key, value]) => ({ key, value }))
: []
modalVisible.value = true
}
// 删除 Webhook
const deleteWebhook = (webhook: CustomWebhook) => {
const newWebhooks = webhooks.value.filter(w => w.id !== webhook.id)
webhooks.value = newWebhooks
emit('change')
message.success('Webhook 删除成功')
}
// 测试 Webhook
const testWebhook = async (webhook: CustomWebhook) => {
testingWebhooks.value[webhook.id] = true
try {
const response = await Service.testWebhookApiSettingWebhookTestPost({
id: webhook.id,
name: webhook.name,
url: webhook.url,
template: webhook.template,
method: webhook.method || 'POST',
enabled: webhook.enabled,
headers: webhook.headers || {}
})
if (response.code === 200) {
message.success(`Webhook "${webhook.name}" 测试成功`)
} else {
message.error(`Webhook 测试失败: ${response.message || '未知错误'}`)
}
} catch (error: any) {
console.error('Webhook测试错误:', error)
message.error(`Webhook 测试失败: ${error.response?.data?.message || error.message || '网络错误'}`)
} finally {
testingWebhooks.value[webhook.id] = false
}
}
// 应用模板
const applyTemplate = (templateName: string) => {
if (!templateName) {
// 清空模板时不做任何操作
return
}
const template = WEBHOOK_TEMPLATES.find(t => t.name === templateName)
if (template) {
// 强制清空所有内容再应用新模板
formData.name = ''
formData.url = template.example || ''
formData.template = template.template
formData.method = template.method
formData.headersList = []
// 设置默认请求头
if (template.headers) {
formData.headersList = Object.entries(template.headers).map(([key, value]) => ({ key, value }))
}
}
}
// 添加请求头
const addHeader = () => {
formData.headersList.push({ key: '', value: '' })
}
// 删除请求头
const removeHeader = (index: number) => {
formData.headersList.splice(index, 1)
}
// 重置表单
const resetForm = () => {
formData.id = ''
formData.name = ''
formData.url = ''
formData.template = ''
formData.method = 'POST'
formData.enabled = true
formData.headersList = []
selectedTemplate.value = undefined
}
// 提交表单
const handleSubmit = async () => {
try {
await formRef.value.validate()
submitting.value = true
// 转换 headersList 为 headers 对象
const headers: Record<string, string> = {}
formData.headersList.forEach(header => {
if (header.key && header.value) {
headers[header.key] = header.value
}
})
const webhookData: CustomWebhook = {
id: formData.id || `webhook_${Date.now()}`,
name: formData.name,
url: formData.url,
template: formData.template,
method: formData.method,
enabled: formData.enabled,
headers: Object.keys(headers).length > 0 ? headers : undefined
}
let newWebhooks: CustomWebhook[]
if (isEditing.value) {
// 更新现有 Webhook
newWebhooks = webhooks.value.map(w =>
w.id === webhookData.id ? webhookData : w
)
message.success('Webhook 更新成功')
} else {
// 添加新 Webhook
newWebhooks = [...webhooks.value, webhookData]
message.success('Webhook 添加成功')
}
webhooks.value = newWebhooks
emit('change')
modalVisible.value = false
} catch (error) {
console.error('表单验证失败:', error)
} finally {
submitting.value = false
}
}
// 取消操作
const handleCancel = () => {
modalVisible.value = false
resetForm()
}
</script>
<style scoped>
.webhook-manager {
margin-top: 16px;
}
.webhook-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.webhook-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.webhook-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.webhook-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border: 1px solid var(--ant-color-border);
border-radius: 8px;
background: var(--ant-color-bg-container);
transition: all 0.2s ease;
}
.webhook-item:hover {
border-color: var(--ant-color-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.webhook-item.webhook-disabled {
opacity: 0.6;
background: var(--ant-color-bg-layout);
}
.webhook-info {
flex: 1;
min-width: 0;
}
.webhook-name {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.name-text {
font-weight: 500;
font-size: 14px;
}
.webhook-url {
font-size: 12px;
color: var(--ant-color-text-secondary);
word-break: break-all;
}
.webhook-actions {
display: flex;
gap: 4px;
flex-shrink: 0;
}
.empty-state {
text-align: center;
padding: 48px 24px;
color: var(--ant-color-text-secondary);
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.empty-text {
font-size: 16px;
margin-bottom: 8px;
}
.empty-description {
font-size: 14px;
}
.template-help {
margin-top: 8px;
}
.headers-input {
border: 1px solid var(--ant-color-border);
border-radius: 6px;
padding: 12px;
background: var(--ant-color-bg-layout);
}
.header-row {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.header-row:last-child {
margin-bottom: 0;
}
/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
.webhook-item {
border-color: var(--ant-color-border-secondary);
}
.webhook-item.webhook-disabled {
background: var(--ant-color-bg-base);
}
.headers-input {
background: var(--ant-color-bg-base);
border-color: var(--ant-color-border-secondary);
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.webhook-item {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.webhook-actions {
width: 100%;
justify-content: flex-end;
}
.webhook-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
}
</style>

View File

@@ -154,8 +154,7 @@ export function useScriptApi() {
ToAddress: maaUserData.Notify?.ToAddress !== undefined ? maaUserData.Notify.ToAddress : '',
IfServerChan: maaUserData.Notify?.IfServerChan !== undefined ? maaUserData.Notify.IfServerChan : false,
ServerChanKey: maaUserData.Notify?.ServerChanKey !== undefined ? maaUserData.Notify.ServerChanKey : '',
IfCompanyWebHookBot: maaUserData.Notify?.IfCompanyWebHookBot !== undefined ? maaUserData.Notify.IfCompanyWebHookBot : false,
CompanyWebHookBotUrl: maaUserData.Notify?.CompanyWebHookBotUrl !== undefined ? maaUserData.Notify.CompanyWebHookBotUrl : '',
CustomWebhooks: maaUserData.Notify?.CustomWebhooks !== undefined ? maaUserData.Notify.CustomWebhooks : [],
},
Data: {
LastAnnihilationDate: maaUserData.Data?.LastAnnihilationDate !== undefined ? maaUserData.Data.LastAnnihilationDate : '',
@@ -188,8 +187,7 @@ export function useScriptApi() {
ToAddress: generalUserData.Notify?.ToAddress !== undefined ? generalUserData.Notify.ToAddress : '',
IfServerChan: generalUserData.Notify?.IfServerChan !== undefined ? generalUserData.Notify.IfServerChan : false,
ServerChanKey: generalUserData.Notify?.ServerChanKey !== undefined ? generalUserData.Notify.ServerChanKey : '',
IfCompanyWebHookBot: generalUserData.Notify?.IfCompanyWebHookBot !== undefined ? generalUserData.Notify.IfCompanyWebHookBot : false,
CompanyWebHookBotUrl: generalUserData.Notify?.CompanyWebHookBotUrl !== undefined ? generalUserData.Notify.CompanyWebHookBotUrl : '',
CustomWebhooks: generalUserData.Notify?.CustomWebhooks !== undefined ? generalUserData.Notify.CustomWebhooks : [],
},
Data: {
LastProxyDate: generalUserData.Data?.LastProxyDate !== undefined ? generalUserData.Data.LastProxyDate : '',

View File

@@ -109,11 +109,18 @@ export interface User {
Status: boolean
}
Notify: {
CompanyWebHookBotUrl: string
Enabled: boolean
IfCompanyWebHookBot: boolean
IfSendMail: boolean
IfSendSixStar: boolean
CustomWebhooks: Array<{
id: string
name: string
url: string
template: string
enabled: boolean
headers?: Record<string, string>
method?: 'POST' | 'GET'
}>
IfSendStatistic: boolean
IfServerChan: boolean
ServerChanChannel: string

View File

@@ -1,3 +1,14 @@
// 自定义Webhook配置
export interface CustomWebhook {
id: string
name: string
url: string
template: string
enabled: boolean
headers?: Record<string, string>
method?: 'POST' | 'GET'
}
// 设置相关类型定义
export interface SettingsData {
UI: {
@@ -26,8 +37,7 @@ export interface SettingsData {
ServerChanKey: string
ServerChanChannel: string
ServerChanTag: string
IfCompanyWebHookBot: boolean
CompanyWebHookBotUrl: string
CustomWebhooks: CustomWebhook[]
}
Update: {
IfAutoUpdate: boolean

View File

@@ -0,0 +1,119 @@
// Webhook 模板配置
export interface WebhookTemplate {
name: string
description: string
template: string
headers?: Record<string, string>
method: 'POST' | 'GET'
example?: string
}
export const WEBHOOK_TEMPLATES: WebhookTemplate[] = [
{
name: 'Bark (iOS推送)',
description: 'Bark是一款iOS推送通知应用',
template: '{"title": "{title}", "body": "{content}", "sound": "default"}',
method: 'POST',
example: 'https://api.day.app/your_key/',
headers: {
'Content-Type': 'application/json'
}
},
{
name: 'Server酱 (微信推送)',
description: 'Server酱微信推送服务',
template: '{"title": "{title}", "desp": "{content}"}',
method: 'POST',
example: 'https://sctapi.ftqq.com/your_key.send',
headers: {
'Content-Type': 'application/json'
}
},
{
name: '企业微信机器人',
description: '企业微信群机器人推送',
template: '{"msgtype": "text", "text": {"content": "{title}\\n{content}"}}',
method: 'POST',
example: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_key',
headers: {
'Content-Type': 'application/json'
}
},
{
name: 'DingTalk (钉钉机器人)',
description: '钉钉群机器人推送',
template: '{"msgtype": "text", "text": {"content": "{title}\\n{content}"}}',
method: 'POST',
example: 'https://oapi.dingtalk.com/robot/send?access_token=your_token',
headers: {
'Content-Type': 'application/json'
}
},
{
name: 'Telegram Bot',
description: 'Telegram机器人推送',
template: '{"chat_id": "your_chat_id", "text": "{title}\\n{content}"}',
method: 'POST',
example: 'https://api.telegram.org/bot{your_bot_token}/sendMessage',
headers: {
'Content-Type': 'application/json'
}
},
{
name: 'Discord Webhook',
description: 'Discord频道Webhook推送',
template: '{"content": "**{title}**\\n{content}"}',
method: 'POST',
example: 'https://discord.com/api/webhooks/your_webhook_url',
headers: {
'Content-Type': 'application/json'
}
},
{
name: 'Slack Webhook',
description: 'Slack频道Webhook推送',
template: '{"text": "{title}\\n{content}"}',
method: 'POST',
example: 'https://hooks.slack.com/services/your/webhook/url',
headers: {
'Content-Type': 'application/json'
}
},
{
name: 'PushPlus (微信推送)',
description: 'PushPlus微信推送服务',
template: '{"token": "your_token", "title": "{title}", "content": "{content}"}',
method: 'POST',
example: 'http://www.pushplus.plus/send',
headers: {
'Content-Type': 'application/json'
}
},
{
name: '自定义JSON',
description: '自定义JSON格式推送',
template: '{"message": "{title}: {content}", "timestamp": "{datetime}"}',
method: 'POST',
example: 'https://your-api.com/webhook',
headers: {
'Content-Type': 'application/json'
}
},
{
name: '自定义GET请求',
description: '通过GET请求发送通知',
template: 'title={title}&content={content}&time={datetime}',
method: 'GET',
example: 'https://your-api.com/notify',
headers: {}
}
]
// 获取模板变量说明
export const TEMPLATE_VARIABLES = [
{ name: '{title}', description: '通知标题' },
{ name: '{content}', description: '通知内容' },
{ name: '{datetime}', description: '完整日期时间 (YYYY-MM-DD HH:MM:SS)' },
{ name: '{date}', description: '日期 (YYYY-MM-DD)' },
{ name: '{time}', description: '时间 (HH:MM:SS)' }
]

View File

@@ -85,85 +85,85 @@
<div class="section-header">
<h3>基本信息</h3>
</div>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item name="userName" required>
<template #label>
<a-tooltip title="用于识别用户的显示名称">
<span class="form-label">
用户名
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-input
v-model:value="formData.userName"
placeholder="请输入用户名"
:disabled="loading"
size="large"
class="modern-input"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="status">
<template #label>
<a-tooltip title="是否启用该用户">
<span class="form-label">
启用状态
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-select v-model:value="formData.Info.Status" size="large">
<a-select-option :value="true"></a-select-option>
<a-select-option :value="false"></a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="remainedDay">
<template #label>
<a-tooltip title="账号剩余的有效天数,「-1」表示无限">
<span class="form-label">
剩余天数
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-input-number
v-model:value="formData.Info.RemainedDay"
:min="-1"
:max="9999"
placeholder="-1"
:disabled="loading"
size="large"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<!-- 占位列 -->
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item name="userName" required>
<template #label>
<a-tooltip title="用于识别用户的显示名称">
<span class="form-label">
用户名
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-input
v-model:value="formData.userName"
placeholder="请输入用户名"
:disabled="loading"
size="large"
class="modern-input"
/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="status">
<template #label>
<a-tooltip title="是否启用该用户">
<span class="form-label">
启用状态
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-select v-model:value="formData.Info.Status" size="large">
<a-select-option :value="true"></a-select-option>
<a-select-option :value="false"></a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item name="remainedDay">
<template #label>
<a-tooltip title="账号剩余的有效天数,「-1」表示无限">
<span class="form-label">
剩余天数
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-input-number
v-model:value="formData.Info.RemainedDay"
:min="-1"
:max="9999"
placeholder="-1"
:disabled="loading"
size="large"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<!-- 占位列 -->
</a-col>
</a-row>
<a-form-item name="notes">
<template #label>
<a-tooltip title="为用户添加备注信息">
<span class="form-label">
备注
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-textarea
v-model:value="formData.Info.Notes"
placeholder="请输入备注信息"
:rows="4"
:disabled="loading"
class="modern-input"
/>
</a-form-item>
<a-form-item name="notes">
<template #label>
<a-tooltip title="为用户添加备注信息">
<span class="form-label">
备注
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-textarea
v-model:value="formData.Info.Notes"
placeholder="请输入备注信息"
:rows="4"
:disabled="loading"
class="modern-input"
/>
</a-form-item>
</div>
<!-- 额外脚本 -->
@@ -171,90 +171,90 @@
<div class="section-header">
<h3>额外脚本</h3>
</div>
<a-form-item name="scriptBeforeTask">
<template #label>
<a-tooltip title="在任务执行前运行自定义脚本">
<span class="form-label">
任务前执行脚本
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-row :gutter="24" align="middle">
<a-col :span="4">
<a-switch
v-model:checked="formData.Info.IfScriptBeforeTask"
:disabled="loading"
size="default"
/>
</a-col>
<a-col :span="20">
<a-input-group compact class="path-input-group">
<a-input
v-model:value="formData.Info.ScriptBeforeTask"
placeholder="请选择脚本文件"
:disabled="loading || !formData.Info.IfScriptBeforeTask"
size="large"
class="path-input"
readonly
<a-form-item name="scriptBeforeTask">
<template #label>
<a-tooltip title="在任务执行前运行自定义脚本">
<span class="form-label">
任务前执行脚本
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-row :gutter="24" align="middle">
<a-col :span="4">
<a-switch
v-model:checked="formData.Info.IfScriptBeforeTask"
:disabled="loading"
size="default"
/>
<a-button
size="large"
@click="selectScriptBeforeTask"
:disabled="loading || !formData.Info.IfScriptBeforeTask"
class="path-button"
>
<template #icon>
<FileOutlined />
</template>
选择文件
</a-button>
</a-input-group>
</a-col>
</a-row>
</a-form-item>
<a-form-item name="scriptAfterTask">
<template #label>
<a-tooltip title="在任务执行后运行自定义脚本">
<span class="form-label">
任务后执行脚本
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-row :gutter="24" align="middle">
<a-col :span="4">
<a-switch
v-model:checked="formData.Info.IfScriptAfterTask"
:disabled="loading"
size="default"
/>
</a-col>
<a-col :span="20">
<a-input-group compact class="path-input-group">
<a-input
v-model:value="formData.Info.ScriptAfterTask"
placeholder="请选择脚本文件"
:disabled="loading || !formData.Info.IfScriptAfterTask"
size="large"
class="path-input"
readonly
</a-col>
<a-col :span="20">
<a-input-group compact class="path-input-group">
<a-input
v-model:value="formData.Info.ScriptBeforeTask"
placeholder="请选择脚本文件"
:disabled="loading || !formData.Info.IfScriptBeforeTask"
size="large"
class="path-input"
readonly
/>
<a-button
size="large"
@click="selectScriptBeforeTask"
:disabled="loading || !formData.Info.IfScriptBeforeTask"
class="path-button"
>
<template #icon>
<FileOutlined />
</template>
选择文件
</a-button>
</a-input-group>
</a-col>
</a-row>
</a-form-item>
<a-form-item name="scriptAfterTask">
<template #label>
<a-tooltip title="在任务执行后运行自定义脚本">
<span class="form-label">
任务后执行脚本
<QuestionCircleOutlined class="help-icon" />
</span>
</a-tooltip>
</template>
<a-row :gutter="24" align="middle">
<a-col :span="4">
<a-switch
v-model:checked="formData.Info.IfScriptAfterTask"
:disabled="loading"
size="default"
/>
<a-button
size="large"
@click="selectScriptAfterTask"
:disabled="loading || !formData.Info.IfScriptAfterTask"
class="path-button"
>
<template #icon>
<FileOutlined />
</template>
选择文件
</a-button>
</a-input-group>
</a-col>
</a-row>
</a-form-item>
</a-col>
<a-col :span="20">
<a-input-group compact class="path-input-group">
<a-input
v-model:value="formData.Info.ScriptAfterTask"
placeholder="请选择脚本文件"
:disabled="loading || !formData.Info.IfScriptAfterTask"
size="large"
class="path-input"
readonly
/>
<a-button
size="large"
@click="selectScriptAfterTask"
:disabled="loading || !formData.Info.IfScriptAfterTask"
class="path-button"
>
<template #icon>
<FileOutlined />
</template>
选择文件
</a-button>
</a-input-group>
</a-col>
</a-row>
</a-form-item>
</div>
<!-- 通知配置 -->
@@ -262,92 +262,77 @@
<div class="section-header">
<h3>通知配置</h3>
</div>
<a-row :gutter="24" align="middle">
<a-col :span="6">
<span style="font-weight: 500">启用通知</span>
</a-col>
<a-col :span="18">
<a-switch v-model:checked="formData.Notify.Enabled" :disabled="loading" />
<span class="switch-description">启用后将发送任务通知</span>
</a-col>
</a-row>
<a-row :gutter="24" align="middle">
<a-col :span="6">
<span style="font-weight: 500">启用通知</span>
</a-col>
<a-col :span="18">
<a-switch v-model:checked="formData.Notify.Enabled" :disabled="loading" />
<span class="switch-description">启用后将发送任务通知</span>
</a-col>
</a-row>
<!-- 发送统计 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<span style="font-weight: 500">通知内容</span>
</a-col>
<a-col :span="18">
<a-checkbox
v-model:checked="formData.Notify.IfSendStatistic"
:disabled="loading || !formData.Notify.Enabled"
>统计信息
</a-checkbox>
</a-col>
</a-row>
<!-- 发送统计 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<span style="font-weight: 500">通知内容</span>
</a-col>
<a-col :span="18">
<a-checkbox
v-model:checked="formData.Notify.IfSendStatistic"
:disabled="loading || !formData.Notify.Enabled"
>统计信息
</a-checkbox>
</a-col>
</a-row>
<!-- 邮件通知 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<a-checkbox
v-model:checked="formData.Notify.IfSendMail"
:disabled="loading || !formData.Notify.Enabled"
>邮件通知
</a-checkbox>
</a-col>
<a-col :span="18">
<a-input
v-model:value="formData.Notify.ToAddress"
placeholder="请输入收件人邮箱地址"
:disabled="loading || !formData.Notify.Enabled || !formData.Notify.IfSendMail"
size="large"
style="width: 100%"
<!-- 邮件通知 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<a-checkbox
v-model:checked="formData.Notify.IfSendMail"
:disabled="loading || !formData.Notify.Enabled"
>邮件通知
</a-checkbox>
</a-col>
<a-col :span="18">
<a-input
v-model:value="formData.Notify.ToAddress"
placeholder="请输入收件人邮箱地址"
:disabled="loading || !formData.Notify.Enabled || !formData.Notify.IfSendMail"
size="large"
style="width: 100%"
/>
</a-col>
</a-row>
<!-- Server酱通知 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<a-checkbox
v-model:checked="formData.Notify.IfServerChan"
:disabled="loading || !formData.Notify.Enabled"
>Server酱
</a-checkbox>
</a-col>
<a-col :span="18">
<a-input
v-model:value="formData.Notify.ServerChanKey"
placeholder="请输入SENDKEY"
:disabled="loading || !formData.Notify.Enabled || !formData.Notify.IfServerChan"
size="large"
style="width: 100%"
/>
</a-col>
</a-row>
<!-- 自定义 Webhook 通知 -->
<div style="margin-top: 16px">
<WebhookManager
v-model:webhooks="formData.Notify.CustomWebhooks"
@change="handleWebhookChange"
/>
</a-col>
</a-row>
<!-- Server酱通知 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<a-checkbox
v-model:checked="formData.Notify.IfServerChan"
:disabled="loading || !formData.Notify.Enabled"
>Server酱
</a-checkbox>
</a-col>
<a-col :span="18">
<a-input
v-model:value="formData.Notify.ServerChanKey"
placeholder="请输入SENDKEY"
:disabled="loading || !formData.Notify.Enabled || !formData.Notify.IfServerChan"
size="large"
style="width: 100%"
/>
</a-col>
</a-row>
<!-- 企业微信群机器人通知 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<a-checkbox
v-model:checked="formData.Notify.IfCompanyWebHookBot"
:disabled="loading || !formData.Notify.Enabled"
>企业微信群机器人
</a-checkbox>
</a-col>
<a-col :span="18">
<a-input
v-model:value="formData.Notify.CompanyWebHookBotUrl"
placeholder="请输入机器人Webhook地址"
:disabled="
loading || !formData.Notify.Enabled || !formData.Notify.IfCompanyWebHookBot
"
size="large"
style="width: 100%"
class="modern-input"
/>
</a-col>
</a-row>
</div>
</div>
</a-form>
</a-card>
@@ -384,6 +369,7 @@ import { useScriptApi } from '@/composables/useScriptApi'
import { useWebSocket } from '@/composables/useWebSocket'
import { Service } from '@/api'
import { TaskCreateIn } from '@/api/models/TaskCreateIn'
import WebhookManager from '@/components/WebhookManager.vue'
const router = useRouter()
const route = useRoute()
@@ -426,11 +412,10 @@ const getDefaultGeneralUserData = () => ({
IfSendMail: false,
IfSendStatistic: false,
IfServerChan: false,
IfCompanyWebHookBot: false,
ServerChanKey: '',
ServerChanChannel: '',
ServerChanTag: '',
CompanyWebHookBotUrl: '',
CustomWebhooks: [],
},
Data: {
LastProxyDate: '2000-01-01',
@@ -639,16 +624,19 @@ const handleGeneralConfig = async () => {
message.success(`已开始配置用户 ${formData.userName} 的通用设置`)
// 设置 30 分钟超时自动断开
generalConfigTimeout = window.setTimeout(() => {
if (generalWebsocketId.value) {
const id = generalWebsocketId.value
unsubscribe(id)
generalWebsocketId.value = null
showGeneralConfigMask.value = false
message.info(`用户 ${formData.userName} 的配置会话已超时断开`)
}
generalConfigTimeout = null
}, 30 * 60 * 1000)
generalConfigTimeout = window.setTimeout(
() => {
if (generalWebsocketId.value) {
const id = generalWebsocketId.value
unsubscribe(id)
generalWebsocketId.value = null
showGeneralConfigMask.value = false
message.info(`用户 ${formData.userName} 的配置会话已超时断开`)
}
generalConfigTimeout = null
},
30 * 60 * 1000
)
} else {
message.error(response?.message || '启动通用配置失败')
}
@@ -695,7 +683,7 @@ const selectScriptBeforeTask = async () => {
{ name: '脚本文件', extensions: ['py', 'js', 'sh'] },
{ name: '所有文件', extensions: ['*'] },
])
if (path && path.length > 0) {
formData.Info.ScriptBeforeTask = path[0]
message.success('任务前脚本路径选择成功')
@@ -713,7 +701,7 @@ const selectScriptAfterTask = async () => {
{ name: '脚本文件', extensions: ['py', 'js', 'sh'] },
{ name: '所有文件', extensions: ['*'] },
])
if (path && path.length > 0) {
formData.Info.ScriptAfterTask = path[0]
message.success('任务后脚本路径选择成功')
@@ -724,6 +712,13 @@ const selectScriptAfterTask = async () => {
}
}
// 处理 Webhook 变化
const handleWebhookChange = () => {
// 这里可以添加额外的处理逻辑,比如验证或保存
console.log('User webhooks changed:', formData.Notify.CustomWebhooks)
// 注意:实际保存会在用户点击保存按钮时进行,这里只是更新本地数据
}
const handleCancel = () => {
if (generalWebsocketId.value) {
unsubscribe(generalWebsocketId.value)
@@ -991,4 +986,4 @@ onMounted(() => {
display: flex;
justify-content: center;
}
</style>
</style>

View File

@@ -41,7 +41,13 @@
<div class="user-edit-content">
<a-card class="config-card">
<a-form ref="formRef" :model="formData" :rules="rules" layout="vertical" class="config-form">
<a-form
ref="formRef"
:model="formData"
:rules="rules"
layout="vertical"
class="config-form"
>
<!-- 基本信息组件 -->
<BasicInfoSection
:form-data="formData"
@@ -91,22 +97,13 @@
/>
<!-- 任务配置组件 -->
<TaskConfigSection
:form-data="formData"
:loading="loading"
/>
<TaskConfigSection :form-data="formData" :loading="loading" />
<!-- 森空岛配置组件 -->
<SkylandConfigSection
:form-data="formData"
:loading="loading"
/>
<SkylandConfigSection :form-data="formData" :loading="loading" />
<!-- 通知配置组件 -->
<NotifyConfigSection
:form-data="formData"
:loading="loading"
/>
<NotifyConfigSection :form-data="formData" :loading="loading" />
</a-form>
</a-card>
</div>
@@ -459,11 +456,10 @@ const getDefaultMAAUserData = () => ({
IfSendSixStar: false,
IfSendStatistic: false,
IfServerChan: false,
IfCompanyWebHookBot: false,
ServerChanKey: '',
ServerChanChannel: '',
ServerChanTag: '',
CompanyWebHookBotUrl: '',
CustomWebhooks: [],
},
Data: {
CustomInfrastPlanIndex: '',
@@ -790,7 +786,10 @@ const handleMAAConfig = async () => {
subscribe(wsId, {
onMessage: (wsMessage: any) => {
if (wsMessage.type === 'error') {
console.error(`用户 ${formData.Info?.Name || formData.userName} MAA配置错误:`, wsMessage.data)
console.error(
`用户 ${formData.Info?.Name || formData.userName} MAA配置错误:`,
wsMessage.data
)
message.error(`MAA配置连接失败: ${wsMessage.data}`)
unsubscribe(wsId)
maaWebsocketId.value = null
@@ -812,16 +811,19 @@ const handleMAAConfig = async () => {
message.success(`已开始配置用户 ${formData.Info?.Name || formData.userName} 的MAA设置`)
// 设置 30 分钟超时自动断开
maaConfigTimeout = window.setTimeout(() => {
if (maaWebsocketId.value) {
const id = maaWebsocketId.value
unsubscribe(id)
maaWebsocketId.value = null
showMAAConfigMask.value = false
message.info(`用户 ${formData.Info?.Name || formData.userName} 的配置会话已超时断开`)
}
maaConfigTimeout = null
}, 30 * 60 * 1000)
maaConfigTimeout = window.setTimeout(
() => {
if (maaWebsocketId.value) {
const id = maaWebsocketId.value
unsubscribe(id)
maaWebsocketId.value = null
showMAAConfigMask.value = false
message.info(`用户 ${formData.Info?.Name || formData.userName} 的配置会话已超时断开`)
}
maaConfigTimeout = null
},
30 * 60 * 1000
)
} else {
message.error(response?.message || '启动MAA配置失败')
}
@@ -1017,7 +1019,6 @@ const updateStageRemain = (value: string) => {
}
}
// 初始化加载
onMounted(() => {
if (!scriptId) {

View File

@@ -71,36 +71,30 @@
</a-col>
</a-row>
<!-- 企业微信群机器人通知 -->
<a-row :gutter="24" style="margin-top: 16px">
<a-col :span="6">
<a-checkbox
v-model:checked="formData.Notify.IfCompanyWebHookBot"
:disabled="loading || !formData.Notify.Enabled"
>企业微信群机器人
</a-checkbox>
</a-col>
<a-col :span="18">
<a-input
v-model:value="formData.Notify.CompanyWebHookBotUrl"
placeholder="请输入机器人Webhook地址"
:disabled="
loading || !formData.Notify.Enabled || !formData.Notify.IfCompanyWebHookBot
"
size="large"
style="width: 100%"
class="modern-input"
/>
</a-col>
</a-row>
<!-- 自定义 Webhook 通知 -->
<div style="margin-top: 16px">
<WebhookManager
v-model:webhooks="formData.Notify.CustomWebhooks"
@change="handleWebhookChange"
/>
</div>
</div>
</template>
<script setup lang="ts">
import WebhookManager from '@/components/WebhookManager.vue'
defineProps<{
formData: any
loading: boolean
}>()
// 处理 Webhook 变化
const handleWebhookChange = () => {
// 这里可以添加额外的处理逻辑,比如验证或保存
console.log('User webhooks changed:', formData.Notify.CustomWebhooks)
// 注意:实际保存会在用户点击保存按钮时进行,这里只是更新本地数据
}
</script>
<style scoped>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { QuestionCircleOutlined } from '@ant-design/icons-vue'
import type { SettingsData } from '@/types/settings'
import WebhookManager from '@/components/WebhookManager.vue'
const props = defineProps<{
settings: SettingsData
@@ -11,6 +12,11 @@ const props = defineProps<{
}>()
const { settings, sendTaskResultTimeOptions, handleSettingChange, testNotify, testingNotify } = props
// 处理 Webhook 变化
const handleWebhookChange = async () => {
await handleSettingChange('Notify', 'CustomWebhooks', settings.Notify.CustomWebhooks)
}
</script>
<template>
@@ -273,54 +279,20 @@ const { settings, sendTaskResultTimeOptions, handleSettingChange, testNotify, te
<div class="form-section">
<div class="section-header">
<h3>企业微信机器人通知</h3>
<h3>自定义 Webhook 通知</h3>
<a
href="https://doc.auto-mas.top/docs/advanced-features.html#%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E7%BE%A4%E6%9C%BA%E5%99%A8%E4%BA%BA%E9%80%9A%E7%9F%A5%E6%8E%A8%E9%80%81%E6%B8%A0%E9%81%93"
href="https://doc.auto-mas.top/docs/advanced-features.html#%E8%87%AA%E5%AE%9A%E4%B9%89webhook%E9%80%9A%E7%9F%A5"
target="_blank"
class="section-doc-link"
title="查看企业微信机器人配置文档"
title="查看自定义Webhook配置文档"
>
文档
</a>
</div>
<a-row :gutter="24">
<a-col :span="12">
<div class="form-item-vertical">
<div class="form-label-wrapper">
<span class="form-label">启用企业微信机器人通知</span>
<a-tooltip title="使用企业微信机器人推送通知">
<QuestionCircleOutlined class="help-icon" />
</a-tooltip>
</div>
<a-select
v-model:value="settings.Notify.IfCompanyWebHookBot"
@change="(checked: any) => handleSettingChange('Notify', 'IfCompanyWebHookBot', checked)"
size="large"
style="width: 100%"
>
<a-select-option :value="true"></a-select-option>
<a-select-option :value="false"></a-select-option>
</a-select>
</div>
</a-col>
<a-col :span="12">
<div class="form-item-vertical">
<div class="form-label-wrapper">
<span class="form-label">Webhook URL</span>
<a-tooltip title="企业微信机器人的Webhook地址">
<QuestionCircleOutlined class="help-icon" />
</a-tooltip>
</div>
<a-input
v-model:value="settings.Notify.CompanyWebHookBotUrl"
@blur="handleSettingChange('Notify', 'CompanyWebHookBotUrl', settings.Notify.CompanyWebHookBotUrl)"
:disabled="!settings.Notify.IfCompanyWebHookBot"
placeholder="请输入Webhook URL"
size="large"
/>
</div>
</a-col>
</a-row>
<WebhookManager
v-model:webhooks="settings.Notify.CustomWebhooks"
@change="handleWebhookChange"
/>
</div>
<!-- 测试按钮已移至通知内容标题右侧 -->

View File

@@ -72,8 +72,7 @@ const settings = reactive<SettingsData>({
ServerChanKey: '',
ServerChanChannel: '',
ServerChanTag: '',
IfCompanyWebHookBot: false,
CompanyWebHookBotUrl: '',
CustomWebhooks: [],
},
Voice: { Enabled: false, Type: 'simple' },
Start: { IfSelfStart: false, IfMinimizeDirectly: false },