2026-03-14 20:55:15 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<Layout>
|
|
|
|
|
|
<template #sidebar>
|
|
|
|
|
|
<div class="ks-sidebar-header">
|
|
|
|
|
|
<a-flex class="ks-sidebar-title">
|
|
|
|
|
|
<span class="icon"></span>
|
|
|
|
|
|
<span class="text">火力规则管理</span>
|
|
|
|
|
|
</a-flex>
|
|
|
|
|
|
<a-button class="ks-sidebar-add" size="small" @click="handleCreate">
|
|
|
|
|
|
<PlusOutlined />
|
|
|
|
|
|
新增
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-list item-layout="horizontal" :data-source="datasource" class="ks-sidebar-list">
|
|
|
|
|
|
<template #renderItem="{ item }">
|
|
|
|
|
|
<a-list-item @click="()=> handleSelect(item)" :class="selectedFireRule?.id === item.id ? 'selected' : null">
|
|
|
|
|
|
<a-list-item-meta :description="substring(item.description,20)">
|
|
|
|
|
|
<template #title>
|
|
|
|
|
|
<span class="ks-algorithm-name">{{ substring(item.name, 20) }}</span>
|
|
|
|
|
|
<span class="ks-sidebar-list-type"><a-badge size="small" :count="getSceneTypeName(item)"></a-badge></span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-list-item-meta>
|
|
|
|
|
|
</a-list-item>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-list>
|
|
|
|
|
|
|
|
|
|
|
|
<a-pagination
|
|
|
|
|
|
v-model:current="query.pageNum"
|
|
|
|
|
|
:page-size="query.pageSize"
|
|
|
|
|
|
:total="datasourceTotal"
|
|
|
|
|
|
simple size="small" @change="handleChange" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="w-full h-full">
|
|
|
|
|
|
|
|
|
|
|
|
<a-card class="ks-page-card ks-algorithm-card">
|
|
|
|
|
|
|
|
|
|
|
|
<template #title>
|
|
|
|
|
|
<a-space>
|
|
|
|
|
|
<span class="point"></span>
|
|
|
|
|
|
<span class="text">规则配置</span>
|
|
|
|
|
|
</a-space>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="ks-scrollable" style="height: 80.5vh;overflow-y: auto;padding-right: 10px">
|
|
|
|
|
|
|
|
|
|
|
|
<a-row :gutter="15">
|
|
|
|
|
|
<a-col :span="16">
|
|
|
|
|
|
<a-form
|
|
|
|
|
|
ref="formRef"
|
|
|
|
|
|
:label-col="{span: 6}"
|
|
|
|
|
|
:model="selectedFireRule"
|
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
|
layout="horizontal"
|
|
|
|
|
|
name="basic"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-form-item
|
|
|
|
|
|
label="规则名称"
|
|
|
|
|
|
:rules="[{ required: true, message: '请输入规则名称!', trigger: ['input', 'change'] }]"
|
|
|
|
|
|
:name="['name']"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-input v-model:value="selectedFireRule.name" placeholder="请输入规则名称" />
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<a-form-item
|
|
|
|
|
|
label="场景类型"
|
|
|
|
|
|
:name="['sceneType']"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-select v-model:value="selectedFireRule.sceneType" placeholder="请选择场景类型">
|
|
|
|
|
|
<a-select-option :value="null">通用</a-select-option>
|
|
|
|
|
|
<a-select-option :value="0">防御</a-select-option>
|
|
|
|
|
|
<a-select-option :value="1">空降</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<a-form-item
|
|
|
|
|
|
label="触发条件"
|
2026-03-16 22:43:12 +08:00
|
|
|
|
:rules="[{ required: true, message: '请输入触发条件!', trigger: [ 'change'] }]"
|
|
|
|
|
|
:name="['conditionsArray']"
|
2026-03-14 20:55:15 +08:00
|
|
|
|
>
|
2026-03-16 22:43:12 +08:00
|
|
|
|
<a-form-item-rest>
|
|
|
|
|
|
<div class="ks-sidebar-list-param-list">
|
|
|
|
|
|
<div class="ks-sidebar-list-param-item" v-for="(item,index) in selectedFireRule.conditionsArray">
|
|
|
|
|
|
<a-row :gutter="15">
|
|
|
|
|
|
<a-col :span="21">
|
2026-03-17 00:36:16 +08:00
|
|
|
|
<PlatformSelect @change="(payload: PlatformComponentPayload)=> handleUpdateCondition(payload, index)"
|
|
|
|
|
|
:payload="item"/>
|
2026-03-16 22:43:12 +08:00
|
|
|
|
</a-col>
|
|
|
|
|
|
<a-col :span="3">
|
|
|
|
|
|
<a-space class="ks-sidebar-list-param-actions">
|
|
|
|
|
|
<MinusCircleOutlined @click="()=> handleMinusCondition(index)" />
|
|
|
|
|
|
<PlusCircleOutlined @click="()=> handleAddCondition(index)" v-if="index === 0" />
|
|
|
|
|
|
</a-space>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
</a-row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-form-item-rest>
|
2026-03-14 20:55:15 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<a-form-item
|
|
|
|
|
|
label="响应动作"
|
2026-03-16 22:43:12 +08:00
|
|
|
|
:rules="[{ required: true, message: '请输入响应动作!', trigger: ['change'] }]"
|
|
|
|
|
|
:name="['actionsArray']"
|
2026-03-14 20:55:15 +08:00
|
|
|
|
>
|
2026-03-16 22:43:12 +08:00
|
|
|
|
<a-form-item-rest>
|
|
|
|
|
|
<div class="ks-sidebar-list-param-list">
|
|
|
|
|
|
<div class="ks-sidebar-list-param-item" v-for="(item,index) in selectedFireRule.actionsArray">
|
|
|
|
|
|
<a-row :gutter="15">
|
|
|
|
|
|
<a-col :span="21">
|
2026-03-17 00:36:16 +08:00
|
|
|
|
<PlatformSelect @change="(payload: PlatformComponentPayload)=> handleUpdateAction(payload, index)"
|
|
|
|
|
|
:payload="item"/>
|
2026-03-16 22:43:12 +08:00
|
|
|
|
</a-col>
|
|
|
|
|
|
<a-col :span="3">
|
|
|
|
|
|
<a-space class="ks-sidebar-list-param-actions">
|
|
|
|
|
|
<MinusCircleOutlined @click="()=> handleMinusAction(index)" />
|
|
|
|
|
|
<PlusCircleOutlined @click="()=> handleAddAction(index)" v-if="index === 0" />
|
|
|
|
|
|
</a-space>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
</a-row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-form-item-rest>
|
2026-03-14 20:55:15 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<a-form-item
|
|
|
|
|
|
label="优先级(数值越小优先级越高)"
|
|
|
|
|
|
:rules="[{ required: true, message: '请输入优先级!', trigger: ['input', 'change'] }]"
|
|
|
|
|
|
:name="['priority']"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-input-number style="width:100%;" v-model:value="selectedFireRule.priority" placeholder="请输入优先级" />
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<a-form-item
|
|
|
|
|
|
label="是否启用"
|
|
|
|
|
|
:name="['priority']"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-switch v-model:checked="selectedFireRule.enabled" />
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<a-form-item
|
|
|
|
|
|
:wrapper-col="{offset: 6}"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-space>
|
|
|
|
|
|
<a-button @click="handleSave" type="primary">保存规则</a-button>
|
|
|
|
|
|
|
|
|
|
|
|
<a-popconfirm
|
|
|
|
|
|
v-if="selectedFireRule && selectedFireRule.id > 0"
|
|
|
|
|
|
title="确定删除?"
|
|
|
|
|
|
@confirm="handleDelete"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-button danger>删除规则</a-button>
|
|
|
|
|
|
</a-popconfirm>
|
|
|
|
|
|
|
|
|
|
|
|
</a-space>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
</a-form>
|
|
|
|
|
|
</a-col>
|
|
|
|
|
|
</a-row>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
</a-card>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Layout>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-03-17 00:36:16 +08:00
|
|
|
|
import { onMounted, ref, nextTick } from 'vue';
|
2026-03-14 20:55:15 +08:00
|
|
|
|
import { type FormInstance, message } from 'ant-design-vue';
|
2026-03-16 22:43:12 +08:00
|
|
|
|
import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
2026-03-14 20:55:15 +08:00
|
|
|
|
import Layout from '../layout.vue';
|
|
|
|
|
|
import { createFireRule, deleteFireRule, findFireRuleByQuery, updateFireRule } from './api';
|
|
|
|
|
|
import type { FireRule, FireRuleRequest } from './types';
|
|
|
|
|
|
import { substring } from '@/utils/strings';
|
2026-03-16 22:43:12 +08:00
|
|
|
|
import PlatformSelect from './PlatformSelect.vue';
|
|
|
|
|
|
import type { PlatformComponentPayload } from '../types';
|
2026-03-14 20:55:15 +08:00
|
|
|
|
|
|
|
|
|
|
const query = ref<Partial<FireRuleRequest>>({
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const defaultFireRule: FireRule = {
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
// 规则名称
|
|
|
|
|
|
name: null,
|
|
|
|
|
|
// 场景类型:0-防御,1-空降,null表示通用
|
|
|
|
|
|
sceneType: null,
|
|
|
|
|
|
// 触发条件(JSON格式)
|
|
|
|
|
|
conditions: null,
|
2026-03-16 22:43:12 +08:00
|
|
|
|
conditionsArray: [
|
|
|
|
|
|
{
|
|
|
|
|
|
platform: null,
|
|
|
|
|
|
component: null,
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2026-03-14 20:55:15 +08:00
|
|
|
|
// 响应动作(JSON格式)
|
|
|
|
|
|
actions: null,
|
2026-03-16 22:43:12 +08:00
|
|
|
|
actionsArray: [
|
|
|
|
|
|
{
|
|
|
|
|
|
platform: null,
|
|
|
|
|
|
component: null,
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
2026-03-14 20:55:15 +08:00
|
|
|
|
// 优先级(数值越小优先级越高)
|
|
|
|
|
|
priority: 0,
|
|
|
|
|
|
// 是否启用(0禁用,1启用)
|
|
|
|
|
|
enabled: true,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getSceneTypeName = (item: FireRule): string => {
|
|
|
|
|
|
if (0 === item.sceneType) {
|
|
|
|
|
|
return '防御';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (1 === item.sceneType) {
|
|
|
|
|
|
return '空降';
|
|
|
|
|
|
}
|
|
|
|
|
|
return '通用';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const resolveItem = (item: FireRule) => {
|
2026-03-17 00:36:16 +08:00
|
|
|
|
let newItem: FireRule = JSON.parse(JSON.stringify(item)) as FireRule;
|
|
|
|
|
|
if (typeof item.conditions === 'string') {
|
|
|
|
|
|
try{
|
|
|
|
|
|
newItem.conditionsArray = JSON.parse(item.conditions as string);
|
|
|
|
|
|
} catch(e: any){
|
|
|
|
|
|
newItem.conditionsArray = []
|
|
|
|
|
|
}
|
2026-03-16 22:43:12 +08:00
|
|
|
|
}
|
2026-03-17 00:36:16 +08:00
|
|
|
|
|
2026-03-16 22:43:12 +08:00
|
|
|
|
if(!newItem.conditionsArray) {
|
|
|
|
|
|
newItem.conditionsArray = []
|
|
|
|
|
|
}
|
|
|
|
|
|
if(newItem.conditionsArray.length===0){
|
|
|
|
|
|
newItem.conditionsArray.push({
|
|
|
|
|
|
platform: null,
|
|
|
|
|
|
component: null,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 00:36:16 +08:00
|
|
|
|
if (typeof item.actions === 'string') {
|
|
|
|
|
|
try{
|
|
|
|
|
|
newItem.actionsArray = JSON.parse(item.actions as string);
|
|
|
|
|
|
} catch(e: any){
|
|
|
|
|
|
newItem.actionsArray = []
|
|
|
|
|
|
}
|
2026-03-16 22:43:12 +08:00
|
|
|
|
}
|
2026-03-17 00:36:16 +08:00
|
|
|
|
|
2026-03-16 22:43:12 +08:00
|
|
|
|
if(!newItem.actionsArray){
|
|
|
|
|
|
newItem.actionsArray = []
|
|
|
|
|
|
}
|
|
|
|
|
|
if(newItem.actionsArray.length===0){
|
|
|
|
|
|
newItem.actionsArray.push({
|
|
|
|
|
|
platform: null,
|
|
|
|
|
|
component: null,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-03-17 00:36:16 +08:00
|
|
|
|
|
2026-03-14 20:55:15 +08:00
|
|
|
|
return newItem;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const datasource = ref<FireRule[]>([]);
|
|
|
|
|
|
const datasourceTotal = ref<number>(0);
|
2026-03-16 22:43:12 +08:00
|
|
|
|
const selectedFireRule = ref<FireRule>({...defaultFireRule});
|
2026-03-14 20:55:15 +08:00
|
|
|
|
const formRef = ref<FormInstance | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
const load = () => {
|
|
|
|
|
|
datasource.value = [];
|
|
|
|
|
|
datasourceTotal.value = 0;
|
|
|
|
|
|
formRef.value?.resetFields();
|
2026-03-16 22:43:12 +08:00
|
|
|
|
selectedFireRule.value = {...defaultFireRule};
|
2026-03-14 20:55:15 +08:00
|
|
|
|
|
|
|
|
|
|
findFireRuleByQuery(query.value).then(r => {
|
|
|
|
|
|
datasource.value = r.rows ?? [];
|
|
|
|
|
|
datasourceTotal.value = r.total ?? 0;
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleCreate = () => {
|
2026-03-16 22:43:12 +08:00
|
|
|
|
selectedFireRule.value = {...defaultFireRule};
|
2026-03-14 20:55:15 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSelect = (item: FireRule) => {
|
2026-03-17 00:36:16 +08:00
|
|
|
|
// 1. 先重置表单
|
2026-03-14 20:55:15 +08:00
|
|
|
|
formRef.value?.resetFields();
|
2026-03-17 00:36:16 +08:00
|
|
|
|
// 2. 再赋值(使用nextTick确保DOM更新完成)
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
selectedFireRule.value = resolveItem(item);
|
|
|
|
|
|
});
|
2026-03-14 20:55:15 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleDelete = () => {
|
|
|
|
|
|
if (selectedFireRule.value && selectedFireRule.value.id > 0) {
|
|
|
|
|
|
deleteFireRule(selectedFireRule.value.id).then(r => {
|
|
|
|
|
|
if (r.code === 200) {
|
|
|
|
|
|
load();
|
|
|
|
|
|
message.info(r.msg ?? '删除成功');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
message.error(r.msg ?? '删除失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSave = () => {
|
|
|
|
|
|
if (formRef.value) {
|
|
|
|
|
|
formRef.value.validate().then(() => {
|
|
|
|
|
|
let res = null;
|
|
|
|
|
|
let savedValue: FireRule = JSON.parse(JSON.stringify(selectedFireRule.value)) as any as FireRule;
|
2026-03-16 22:43:12 +08:00
|
|
|
|
savedValue.conditions = JSON.stringify(savedValue.conditionsArray ?? null) as string;
|
|
|
|
|
|
savedValue.actions = JSON.stringify(savedValue.actionsArray ?? null) as string;
|
2026-03-14 20:55:15 +08:00
|
|
|
|
if (savedValue.id > 0) {
|
|
|
|
|
|
res = updateFireRule(savedValue);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
res = createFireRule(savedValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (res) {
|
|
|
|
|
|
res.then(r => {
|
|
|
|
|
|
if (r.code === 200) {
|
|
|
|
|
|
load();
|
|
|
|
|
|
message.info(r.msg ?? '操作成功');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
message.error(r.msg ?? '操作失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-16 22:43:12 +08:00
|
|
|
|
const handleMinusCondition = (index: number) => {
|
|
|
|
|
|
if(selectedFireRule.value){
|
|
|
|
|
|
const paramList = selectedFireRule.value.conditionsArray;
|
|
|
|
|
|
if (index === 0 && selectedFireRule.value.conditionsArray.length === 1) {
|
|
|
|
|
|
selectedFireRule.value.conditionsArray = [{
|
|
|
|
|
|
platform: null,
|
|
|
|
|
|
component: null,
|
|
|
|
|
|
}];
|
|
|
|
|
|
} else if (index >= 0 && index < paramList.length && paramList.length > 1) {
|
|
|
|
|
|
paramList.splice(index, 1);
|
|
|
|
|
|
selectedFireRule.value.conditionsArray = [...paramList];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleAddCondition = (index: number)=> {
|
|
|
|
|
|
if(selectedFireRule.value){
|
|
|
|
|
|
selectedFireRule.value.conditionsArray.push({
|
|
|
|
|
|
platform: null,
|
|
|
|
|
|
component: null,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleMinusAction = (index: number) => {
|
|
|
|
|
|
if(selectedFireRule.value){
|
|
|
|
|
|
const paramList = selectedFireRule.value.actionsArray;
|
|
|
|
|
|
if (index === 0 && selectedFireRule.value.actionsArray.length === 1) {
|
|
|
|
|
|
selectedFireRule.value.actionsArray = [{
|
|
|
|
|
|
platform: null,
|
|
|
|
|
|
component: null,
|
|
|
|
|
|
}];
|
|
|
|
|
|
} else if (index >= 0 && index < paramList.length && paramList.length > 1) {
|
|
|
|
|
|
paramList.splice(index, 1);
|
|
|
|
|
|
selectedFireRule.value.actionsArray = [...paramList];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleAddAction = (_index: number)=> {
|
|
|
|
|
|
if(selectedFireRule.value){
|
|
|
|
|
|
selectedFireRule.value.actionsArray.push({
|
|
|
|
|
|
platform: null,
|
|
|
|
|
|
component: null,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleUpdateCondition = (payload: PlatformComponentPayload, index: number)=> {
|
2026-03-17 00:36:16 +08:00
|
|
|
|
console.error('handleUpdateCondition', payload)
|
2026-03-16 22:43:12 +08:00
|
|
|
|
if(selectedFireRule.value && selectedFireRule.value.conditionsArray[index]){
|
|
|
|
|
|
selectedFireRule.value.conditionsArray[index] = payload;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleUpdateAction = (payload: PlatformComponentPayload, index: number)=> {
|
|
|
|
|
|
if(selectedFireRule.value && selectedFireRule.value.actionsArray[index]){
|
|
|
|
|
|
selectedFireRule.value.actionsArray[index] = payload;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-14 20:55:15 +08:00
|
|
|
|
|
|
|
|
|
|
const handleChange = (page: number, pageSize: number) => {
|
|
|
|
|
|
query.value.pageNum = page;
|
|
|
|
|
|
query.value.pageSize = pageSize;
|
|
|
|
|
|
load();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => load());
|
|
|
|
|
|
|
|
|
|
|
|
</script>
|