feat
This commit is contained in:
136
src/api/ops/suppression.ts
Normal file
136
src/api/ops/suppression.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { request } from '@/api/request'
|
||||
|
||||
// 抑制规则类型
|
||||
export type SuppressionType = 'dedup' | 'aggregate' | 'dependency' | 'throttle' | 'schedule'
|
||||
|
||||
// 抑制规则接口定义
|
||||
export interface SuppressionRule {
|
||||
id?: number
|
||||
name: string
|
||||
description?: string
|
||||
type: SuppressionType
|
||||
enabled?: boolean
|
||||
priority?: number
|
||||
policy_id?: number
|
||||
// dedup 类型字段
|
||||
dedup_window?: number
|
||||
dedup_keys?: string
|
||||
// aggregate 类型字段
|
||||
aggregate_window?: number
|
||||
group_by?: string
|
||||
aggregate_count?: number
|
||||
// dependency 类型字段
|
||||
source_matchers?: string
|
||||
target_matchers?: string
|
||||
// throttle 类型字段
|
||||
throttle_count?: number
|
||||
throttle_window?: number
|
||||
// schedule 类型字段
|
||||
schedule?: string
|
||||
// 通用匹配条件
|
||||
matchers?: string
|
||||
// 运行态统计
|
||||
hit_count?: number
|
||||
suppressed_count?: number
|
||||
last_hit_at?: string
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
// 抑制规则列表查询参数
|
||||
export interface SuppressionListParams {
|
||||
page?: number
|
||||
page_size?: number
|
||||
keyword?: string
|
||||
sort?: string
|
||||
order?: string
|
||||
type?: SuppressionType
|
||||
policy_id?: number
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
// 创建抑制规则参数
|
||||
export interface SuppressionCreateParams {
|
||||
name: string
|
||||
type: SuppressionType
|
||||
description?: string
|
||||
enabled?: boolean
|
||||
priority?: number
|
||||
policy_id?: number
|
||||
dedup_window?: number
|
||||
dedup_keys?: string
|
||||
aggregate_window?: number
|
||||
group_by?: string
|
||||
aggregate_count?: number
|
||||
source_matchers?: string
|
||||
target_matchers?: string
|
||||
throttle_count?: number
|
||||
throttle_window?: number
|
||||
schedule?: string
|
||||
matchers?: string
|
||||
}
|
||||
|
||||
// 更新抑制规则参数
|
||||
export interface SuppressionUpdateParams {
|
||||
id: number
|
||||
name?: string
|
||||
type?: SuppressionType
|
||||
description?: string
|
||||
enabled?: boolean
|
||||
priority?: number
|
||||
policy_id?: number
|
||||
dedup_window?: number
|
||||
dedup_keys?: string
|
||||
aggregate_window?: number
|
||||
group_by?: string
|
||||
aggregate_count?: number
|
||||
source_matchers?: string
|
||||
target_matchers?: string
|
||||
throttle_count?: number
|
||||
throttle_window?: number
|
||||
schedule?: string
|
||||
matchers?: string
|
||||
}
|
||||
|
||||
// 抑制类型选项
|
||||
export const SUPPRESSION_TYPE_OPTIONS = [
|
||||
{ value: 'dedup', label: '去重' },
|
||||
{ value: 'aggregate', label: '聚合' },
|
||||
{ value: 'dependency', label: '依赖抑制' },
|
||||
{ value: 'throttle', label: '限流' },
|
||||
{ value: 'schedule', label: '定时屏蔽' },
|
||||
]
|
||||
|
||||
// 抑制类型标签颜色
|
||||
export const SUPPRESSION_TYPE_COLORS: Record<SuppressionType, string> = {
|
||||
dedup: 'arcoblue',
|
||||
aggregate: 'green',
|
||||
dependency: 'orangered',
|
||||
throttle: 'orange',
|
||||
schedule: 'purple',
|
||||
}
|
||||
|
||||
/** 获取抑制规则列表 */
|
||||
export const fetchSuppressionList = (params?: SuppressionListParams) => {
|
||||
return request.get('/Alert/v1/suppression/list', { params })
|
||||
}
|
||||
|
||||
/** 获取抑制规则详情 */
|
||||
export const fetchSuppressionDetail = (id: number) => {
|
||||
return request.get(`/Alert/v1/suppression/get/${id}`)
|
||||
}
|
||||
|
||||
/** 创建抑制规则 */
|
||||
export const createSuppression = (data: SuppressionCreateParams) => {
|
||||
return request.post('/Alert/v1/suppression/create', data)
|
||||
}
|
||||
|
||||
/** 更新抑制规则 */
|
||||
export const updateSuppression = (data: SuppressionUpdateParams) => {
|
||||
return request.post('/Alert/v1/suppression/update', data)
|
||||
}
|
||||
|
||||
/** 删除抑制规则 */
|
||||
export const deleteSuppression = (id: number) => {
|
||||
return request.delete(`/Alert/v1/suppression/delete/${id}`)
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
:title="title"
|
||||
:width="800"
|
||||
:ok-loading="submitting"
|
||||
:mask-closable="false"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<!-- 基本信息 -->
|
||||
<a-col :span="12">
|
||||
<a-form-item label="规则名称" field="name" :rules="[{ required: true, message: '请输入规则名称' }]">
|
||||
<a-input v-model="formData.name" placeholder="请输入规则名称" :max-length="100" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="抑制类型" field="type" :rules="[{ required: true, message: '请选择抑制类型' }]">
|
||||
<a-select v-model="formData.type" placeholder="请选择抑制类型" :disabled="isEdit">
|
||||
<a-option v-for="item in SUPPRESSION_TYPE_OPTIONS" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="优先级" field="priority">
|
||||
<a-input-number
|
||||
v-model="formData.priority"
|
||||
placeholder="数值越小越先匹配"
|
||||
:min="1"
|
||||
:max="10000"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="关联策略" field="policy_id">
|
||||
<a-select
|
||||
v-model="formData.policy_id"
|
||||
placeholder="选择策略或留空表示全局"
|
||||
allow-clear
|
||||
allow-search
|
||||
>
|
||||
<a-option :value="0">
|
||||
<a-tag color="gray">全局</a-tag>
|
||||
</a-option>
|
||||
<a-option v-for="policy in policyOptions" :key="policy.id" :value="policy.id">
|
||||
{{ policy.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="启用状态" field="enabled">
|
||||
<a-switch v-model="formData.enabled" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="描述" field="description">
|
||||
<a-textarea
|
||||
v-model="formData.description"
|
||||
placeholder="请输入规则描述"
|
||||
:max-length="500"
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 匹配条件 -->
|
||||
<a-form-item label="匹配条件 (JSON)" field="matchers">
|
||||
<a-textarea
|
||||
v-model="formData.matchers"
|
||||
placeholder="JSON 格式的匹配条件,如 {} 表示全部告警"
|
||||
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- dedup 类型配置 -->
|
||||
<template v-if="formData.type === 'dedup'">
|
||||
<a-divider>去重配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="去重窗口 (秒)" field="dedup_window">
|
||||
<a-input-number
|
||||
v-model="formData.dedup_window"
|
||||
placeholder="请输入去重窗口时间"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="去重键" field="dedup_keys">
|
||||
<a-input
|
||||
v-model="formData.dedup_keys"
|
||||
placeholder="逗号分隔,如 fingerprint,alert_name"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- aggregate 类型配置 -->
|
||||
<template v-if="formData.type === 'aggregate'">
|
||||
<a-divider>聚合配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="聚合窗口 (秒)" field="aggregate_window">
|
||||
<a-input-number
|
||||
v-model="formData.aggregate_window"
|
||||
placeholder="请输入聚合窗口时间"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="分组字段" field="group_by">
|
||||
<a-input
|
||||
v-model="formData.group_by"
|
||||
placeholder="逗号分隔"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="聚合阈值" field="aggregate_count">
|
||||
<a-input-number
|
||||
v-model="formData.aggregate_count"
|
||||
placeholder="触发阈值"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- dependency 类型配置 -->
|
||||
<template v-if="formData.type === 'dependency'">
|
||||
<a-divider>依赖抑制配置</a-divider>
|
||||
<a-form-item label="源匹配器 (JSON)" field="source_matchers">
|
||||
<a-textarea
|
||||
v-model="formData.source_matchers"
|
||||
placeholder="JSON 格式的源匹配条件"
|
||||
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="目标匹配器 (JSON)" field="target_matchers">
|
||||
<a-textarea
|
||||
v-model="formData.target_matchers"
|
||||
placeholder="JSON 格式的目标匹配条件"
|
||||
:auto-size="{ minRows: 3, maxRows: 6 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- throttle 类型配置 -->
|
||||
<template v-if="formData.type === 'throttle'">
|
||||
<a-divider>限流配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="限流次数" field="throttle_count">
|
||||
<a-input-number
|
||||
v-model="formData.throttle_count"
|
||||
placeholder="允许发送次数"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="限流窗口 (秒)" field="throttle_window">
|
||||
<a-input-number
|
||||
v-model="formData.throttle_window"
|
||||
placeholder="限流时间窗口"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- schedule 类型配置 -->
|
||||
<template v-if="formData.type === 'schedule'">
|
||||
<a-divider>定时屏蔽配置</a-divider>
|
||||
<a-form-item label="时间计划 (JSON)" field="schedule">
|
||||
<a-textarea
|
||||
v-model="formData.schedule"
|
||||
placeholder="JSON 格式的时间计划配置"
|
||||
:auto-size="{ minRows: 5, maxRows: 10 }"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 编辑时显示统计信息 -->
|
||||
<template v-if="isEdit">
|
||||
<a-divider>运行统计</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-statistic title="命中次数" :value="formData.hit_count || 0" />
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-statistic title="已抑制数量" :value="formData.suppressed_count || 0" />
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-descriptions :column="1">
|
||||
<a-descriptions-item label="最后命中时间">
|
||||
{{ formData.last_hit_at ? formatDateTime(formData.last_hit_at) : '-' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { SUPPRESSION_TYPE_OPTIONS, type SuppressionRule } from '@/api/ops/suppression'
|
||||
import type { PolicyOption, SuppressionFormData } from '../types'
|
||||
import { DEFAULT_FORM_DATA, FORM_RULES } from '../constants'
|
||||
import { formatDateTime, validateJsonFields, buildUpdateData, buildCreateData } from '../utils'
|
||||
import { createSuppression, updateSuppression } from '@/api/ops/suppression'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
isEdit: boolean
|
||||
data: SuppressionFormData
|
||||
policyOptions: PolicyOption[]
|
||||
submitting: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'update:submitting', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const formRef = ref()
|
||||
const formData = ref<SuppressionFormData>({ ...DEFAULT_FORM_DATA })
|
||||
const rules = FORM_RULES
|
||||
|
||||
// 弹窗标题
|
||||
const title = computed(() =>
|
||||
props.isEdit ? `编辑抑制规则 - ${formData.value.name}` : '新建抑制规则'
|
||||
)
|
||||
|
||||
// 监听外部数据变化
|
||||
watch(
|
||||
() => props.data,
|
||||
(newData) => {
|
||||
if (newData) {
|
||||
formData.value = { ...newData }
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
|
||||
// 关闭弹窗
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false)
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const handleOk = async () => {
|
||||
// 先进行表单验证
|
||||
try {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (valid) return false
|
||||
} catch (validateError) {
|
||||
console.log('表单验证失败:', validateError)
|
||||
return false
|
||||
}
|
||||
|
||||
// 校验 JSON 字段
|
||||
const jsonFields = ['matchers', 'source_matchers', 'target_matchers', 'schedule']
|
||||
const jsonError = validateJsonFields(formData.value, jsonFields)
|
||||
if (jsonError) {
|
||||
Message.error(jsonError)
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
emit('update:submitting', true)
|
||||
|
||||
if (props.isEdit) {
|
||||
// 更新
|
||||
const updateData = buildUpdateData(formData.value)
|
||||
const res = await updateSuppression(updateData)
|
||||
if (res.code === 0) {
|
||||
Message.success('更新成功')
|
||||
handleCancel()
|
||||
emit('success')
|
||||
return true
|
||||
} else {
|
||||
Message.error(res.message || '更新失败')
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// 创建
|
||||
const createData = buildCreateData(formData.value)
|
||||
const res = await createSuppression(createData)
|
||||
if (res.code === 0) {
|
||||
Message.success('创建成功')
|
||||
handleCancel()
|
||||
emit('success')
|
||||
return true
|
||||
} else {
|
||||
Message.error(res.message || '创建失败')
|
||||
return false
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('提交失败:', error)
|
||||
Message.error(error.message || '提交失败')
|
||||
return false
|
||||
} finally {
|
||||
emit('update:submitting', false)
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法供父组件调用
|
||||
defineExpose({
|
||||
resetFields: () => formRef.value?.resetFields(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SuppressionModal',
|
||||
}
|
||||
</script>
|
||||
92
src/views/ops/pages/alert/suppress/config/columns.ts
Normal file
92
src/views/ops/pages/alert/suppress/config/columns.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'
|
||||
|
||||
/**
|
||||
* 表格列配置
|
||||
*/
|
||||
export const getTableColumns = (): TableColumnData[] => [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '规则名称',
|
||||
dataIndex: 'name',
|
||||
width: 180,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: '抑制类型',
|
||||
dataIndex: 'type',
|
||||
width: 100,
|
||||
slotName: 'type',
|
||||
},
|
||||
{
|
||||
title: '启用状态',
|
||||
dataIndex: 'enabled',
|
||||
width: 100,
|
||||
slotName: 'enabled',
|
||||
},
|
||||
{
|
||||
title: '优先级',
|
||||
dataIndex: 'priority',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '作用范围',
|
||||
dataIndex: 'policy_id',
|
||||
width: 120,
|
||||
slotName: 'policy',
|
||||
},
|
||||
{
|
||||
title: '关键配置',
|
||||
dataIndex: 'config',
|
||||
width: 200,
|
||||
slotName: 'config',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: '匹配条件',
|
||||
dataIndex: 'matchers',
|
||||
width: 100,
|
||||
slotName: 'matchers',
|
||||
},
|
||||
{
|
||||
title: '命中次数',
|
||||
dataIndex: 'hit_count',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '已抑制',
|
||||
dataIndex: 'suppressed_count',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '最后命中',
|
||||
dataIndex: 'last_hit_at',
|
||||
width: 160,
|
||||
slotName: 'last_hit_at',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 160,
|
||||
slotName: 'created_at',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operations',
|
||||
width: 220,
|
||||
slotName: 'operations',
|
||||
fixed: 'right',
|
||||
},
|
||||
]
|
||||
54
src/views/ops/pages/alert/suppress/config/formItems.ts
Normal file
54
src/views/ops/pages/alert/suppress/config/formItems.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { computed } from 'vue'
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
import { SUPPRESSION_TYPE_OPTIONS } from '@/api/ops/suppression'
|
||||
import type { PolicyOption } from '../types'
|
||||
|
||||
/**
|
||||
* 获取筛选表单项配置
|
||||
*/
|
||||
export const useFormItems = (policyOptions: PolicyOption[]) => {
|
||||
return computed<FormItem[]>(() => [
|
||||
{
|
||||
field: 'keyword',
|
||||
label: '关键字',
|
||||
type: 'input',
|
||||
span: 6,
|
||||
placeholder: '搜索名称/描述',
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
label: '抑制类型',
|
||||
type: 'select',
|
||||
span: 6,
|
||||
placeholder: '请选择',
|
||||
options: [
|
||||
{ value: '', label: '全部' },
|
||||
...SUPPRESSION_TYPE_OPTIONS,
|
||||
],
|
||||
},
|
||||
{
|
||||
field: 'policy_id',
|
||||
label: '关联策略',
|
||||
type: 'select',
|
||||
span: 6,
|
||||
placeholder: '请选择',
|
||||
options: [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '0', label: '仅全局规则' },
|
||||
...policyOptions.map((p) => ({ value: String(p.id), label: p.name })),
|
||||
],
|
||||
},
|
||||
{
|
||||
field: 'enabled',
|
||||
label: '启用状态',
|
||||
type: 'select',
|
||||
span: 6,
|
||||
placeholder: '请选择',
|
||||
options: [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: 'true', label: '已启用' },
|
||||
{ value: 'false', label: '已禁用' },
|
||||
],
|
||||
},
|
||||
])
|
||||
}
|
||||
59
src/views/ops/pages/alert/suppress/constants.ts
Normal file
59
src/views/ops/pages/alert/suppress/constants.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { SuppressionFormData } from './types'
|
||||
|
||||
/**
|
||||
* 页面标题
|
||||
*/
|
||||
export const PAGE_TITLE = '告警抑制规则(降噪)管理'
|
||||
|
||||
/**
|
||||
* 默认表单数据
|
||||
*/
|
||||
export const DEFAULT_FORM_DATA: SuppressionFormData = {
|
||||
name: '',
|
||||
type: 'dedup',
|
||||
enabled: true,
|
||||
priority: 100,
|
||||
policy_id: 0,
|
||||
description: '',
|
||||
matchers: '{}',
|
||||
dedup_window: 300,
|
||||
dedup_keys: '',
|
||||
aggregate_window: 300,
|
||||
group_by: '',
|
||||
aggregate_count: 5,
|
||||
source_matchers: '{}',
|
||||
target_matchers: '{}',
|
||||
throttle_count: 1,
|
||||
throttle_window: 300,
|
||||
schedule: '{}',
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单校验规则
|
||||
*/
|
||||
export const FORM_RULES = {
|
||||
name: [{ required: true, message: '请输入规则名称' }],
|
||||
type: [{ required: true, message: '请选择抑制类型' }],
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认分页配置
|
||||
*/
|
||||
export const DEFAULT_PAGINATION = {
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
showTotal: true,
|
||||
showJumper: true,
|
||||
showPageSize: true,
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认筛选表单
|
||||
*/
|
||||
export const DEFAULT_SEARCH_FORM = {
|
||||
keyword: '',
|
||||
type: '',
|
||||
policy_id: '',
|
||||
enabled: '',
|
||||
}
|
||||
2
src/views/ops/pages/alert/suppress/hooks/index.ts
Normal file
2
src/views/ops/pages/alert/suppress/hooks/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { usePolicy } from './usePolicy'
|
||||
export { useTable } from './useTable'
|
||||
42
src/views/ops/pages/alert/suppress/hooks/usePolicy.ts
Normal file
42
src/views/ops/pages/alert/suppress/hooks/usePolicy.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ref } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { fetchPolicyList } from '@/api/ops/alertPolicy'
|
||||
import type { PolicyOption } from '../types'
|
||||
|
||||
/**
|
||||
* 策略相关逻辑
|
||||
*/
|
||||
export const usePolicy = () => {
|
||||
const policyOptions = ref<PolicyOption[]>([])
|
||||
|
||||
/**
|
||||
* 获取策略列表
|
||||
*/
|
||||
const fetchPolicyOptions = async () => {
|
||||
try {
|
||||
const res = await fetchPolicyList({ page_size: 1000 })
|
||||
if (res.code === 0 && res.details) {
|
||||
policyOptions.value = (res.details.data || []).map((p: any) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取策略列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取策略名称
|
||||
*/
|
||||
const getPolicyName = (policyId: number) => {
|
||||
const policy = policyOptions.value.find((p) => p.id === policyId)
|
||||
return policy?.name || ''
|
||||
}
|
||||
|
||||
return {
|
||||
policyOptions,
|
||||
fetchPolicyOptions,
|
||||
getPolicyName,
|
||||
}
|
||||
}
|
||||
193
src/views/ops/pages/alert/suppress/hooks/useTable.ts
Normal file
193
src/views/ops/pages/alert/suppress/hooks/useTable.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import {
|
||||
fetchSuppressionList,
|
||||
updateSuppression,
|
||||
deleteSuppression,
|
||||
type SuppressionRule,
|
||||
} from '@/api/ops/suppression'
|
||||
import type { SearchFormModel, PaginationConfig, SwitchLoadingMap } from '../types'
|
||||
import { DEFAULT_PAGINATION, DEFAULT_SEARCH_FORM } from '../constants'
|
||||
|
||||
/**
|
||||
* 表格相关逻辑
|
||||
*/
|
||||
export const useTable = () => {
|
||||
const loading = ref(false)
|
||||
const tableData = ref<SuppressionRule[]>([])
|
||||
const switchLoadingMap = ref<SwitchLoadingMap>({})
|
||||
const formModel = ref<SearchFormModel>({ ...DEFAULT_SEARCH_FORM })
|
||||
const pagination = reactive<PaginationConfig>({ ...DEFAULT_PAGINATION })
|
||||
|
||||
/**
|
||||
* 获取抑制规则列表
|
||||
*/
|
||||
const fetchList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
page: pagination.current,
|
||||
page_size: pagination.pageSize,
|
||||
}
|
||||
|
||||
if (formModel.value.keyword) {
|
||||
params.keyword = formModel.value.keyword
|
||||
}
|
||||
|
||||
if (formModel.value.type) {
|
||||
params.type = formModel.value.type
|
||||
}
|
||||
|
||||
if (formModel.value.policy_id) {
|
||||
params.policy_id = Number(formModel.value.policy_id)
|
||||
}
|
||||
|
||||
if (formModel.value.enabled) {
|
||||
params.enabled = formModel.value.enabled === 'true'
|
||||
}
|
||||
|
||||
const res = await fetchSuppressionList(params)
|
||||
if (res.code === 0 && res.details) {
|
||||
tableData.value = (res.details.data || []).map((item: any) => ({
|
||||
...item,
|
||||
switchLoading: false,
|
||||
}))
|
||||
pagination.total = res.details.total || 0
|
||||
} else {
|
||||
Message.error(res.message || '获取抑制规则列表失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取抑制规则列表失败:', error)
|
||||
Message.error(error.message || '获取抑制规则列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理表单模型更新
|
||||
*/
|
||||
const handleFormModelUpdate = (value: Record<string, any>) => {
|
||||
formModel.value = value as SearchFormModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
const handleReset = () => {
|
||||
formModel.value = { ...DEFAULT_SEARCH_FORM }
|
||||
pagination.current = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新
|
||||
*/
|
||||
const handleRefresh = () => {
|
||||
fetchList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页变化
|
||||
*/
|
||||
const handlePageChange = (current: number) => {
|
||||
pagination.current = current
|
||||
fetchList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 每页条数变化
|
||||
*/
|
||||
const handlePageSizeChange = (pageSize: number) => {
|
||||
pagination.pageSize = pageSize
|
||||
pagination.current = 1
|
||||
fetchList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换启用状态
|
||||
*/
|
||||
const handleToggleEnabled = async (record: SuppressionRule) => {
|
||||
if (!record.id) return
|
||||
switchLoadingMap.value[record.id] = true
|
||||
try {
|
||||
const res = await updateSuppression({
|
||||
id: record.id,
|
||||
enabled: record.enabled,
|
||||
})
|
||||
if (res.code === 0) {
|
||||
Message.success(record.enabled ? '已启用' : '已禁用')
|
||||
} else {
|
||||
// 恢复原状态
|
||||
record.enabled = !record.enabled
|
||||
Message.error(res.message || '操作失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
record.enabled = !record.enabled
|
||||
console.error('切换状态失败:', error)
|
||||
Message.error(error.message || '操作失败')
|
||||
} finally {
|
||||
switchLoadingMap.value[record.id] = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除抑制规则
|
||||
*/
|
||||
const handleDelete = (record: SuppressionRule) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: `确定要删除抑制规则「${record.name}」吗?删除后将影响告警降噪行为,请确认是否被模板引用。`,
|
||||
okText: '删除',
|
||||
cancelText: '取消',
|
||||
okButtonProps: { status: 'danger' },
|
||||
onOk: async () => {
|
||||
try {
|
||||
const res = await deleteSuppression(record.id!)
|
||||
if (res.code === 0 || res.data === '删除成功') {
|
||||
Message.success('删除成功')
|
||||
fetchList()
|
||||
} else {
|
||||
Message.error(res.message || '删除失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('删除失败:', error)
|
||||
Message.error(error.message || '删除失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到策略详情
|
||||
*/
|
||||
const handleGoToPolicy = (policyId: number) => {
|
||||
Message.info(`跳转到策略 #${policyId} 详情页`)
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
tableData,
|
||||
switchLoadingMap,
|
||||
formModel,
|
||||
pagination,
|
||||
fetchList,
|
||||
handleFormModelUpdate,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handleRefresh,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleToggleEnabled,
|
||||
handleDelete,
|
||||
handleGoToPolicy,
|
||||
}
|
||||
}
|
||||
208
src/views/ops/pages/alert/suppress/index.vue
Normal file
208
src/views/ops/pages/alert/suppress/index.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<search-table
|
||||
:form-model="formModel"
|
||||
:form-items="formItems"
|
||||
:data="tableData"
|
||||
:columns="tableColumns"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:title="pageTitle"
|
||||
@update:form-model="handleFormModelUpdate"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
@refresh="handleRefresh"
|
||||
@page-change="handlePageChange"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
>
|
||||
<!-- 工具栏左侧:新建按钮 -->
|
||||
<template #toolbar-left>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleOpenCreateModal">
|
||||
<template #icon><icon-plus /></template>
|
||||
新建抑制规则
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 类型列 -->
|
||||
<template #type="{ record }">
|
||||
<a-tag :color="getTypeColor(record.type)">
|
||||
{{ getTypeLabel(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 启用状态列 -->
|
||||
<template #enabled="{ record }">
|
||||
<a-switch
|
||||
v-model="record.enabled"
|
||||
:loading="switchLoadingMap[record.id]"
|
||||
@change="handleToggleEnabled(record)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 作用范围列 -->
|
||||
<template #policy="{ record }">
|
||||
<span v-if="record.policy_id === 0 || !record.policy_id">
|
||||
<a-tag color="gray">全局</a-tag>
|
||||
</span>
|
||||
<span v-else>
|
||||
<a-link @click="handleGoToPolicy(record.policy_id)">
|
||||
{{ getPolicyName(record.policy_id) || `策略 #${record.policy_id}` }}
|
||||
</a-link>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- 关键配置列 -->
|
||||
<template #config="{ record }">
|
||||
<span class="config-text">{{ getConfigSummary(record) }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 匹配条件列 -->
|
||||
<template #matchers="{ record }">
|
||||
<a-tooltip v-if="record.matchers && record.matchers !== '{}'" :content="record.matchers">
|
||||
<a-tag color="arcoblue">
|
||||
<icon-code />
|
||||
已配置
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
<span v-else class="text-gray">全部告警</span>
|
||||
</template>
|
||||
|
||||
<!-- 最后命中时间列 -->
|
||||
<template #last_hit_at="{ record }">
|
||||
<span v-if="record.last_hit_at">{{ formatDateTime(record.last_hit_at) }}</span>
|
||||
<span v-else class="text-gray">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 创建时间列 -->
|
||||
<template #created_at="{ record }">
|
||||
{{ formatDateTime(record.created_at) }}
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #operations="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text" size="small" @click="handleOpenEditModal(record)">
|
||||
编辑
|
||||
</a-button>
|
||||
<a-button type="text" size="small" status="danger" @click="handleDelete(record)">
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</search-table>
|
||||
|
||||
<!-- 新建/编辑抑制规则弹窗 -->
|
||||
<SuppressionModal
|
||||
v-model:visible="modalVisible"
|
||||
v-model:submitting="submitting"
|
||||
:is-edit="isEdit"
|
||||
:data="formData"
|
||||
:policy-options="policyOptions"
|
||||
@success="fetchList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
import { fetchSuppressionDetail, type SuppressionRule } from '@/api/ops/suppression'
|
||||
import SuppressionModal from './components/SuppressionModal.vue'
|
||||
import { PAGE_TITLE, DEFAULT_FORM_DATA } from './constants'
|
||||
import { getTableColumns } from './config/columns'
|
||||
import { useFormItems } from './config/formItems'
|
||||
import { getTypeColor, getTypeLabel, getConfigSummary, formatDateTime } from './utils'
|
||||
import { usePolicy, useTable } from './hooks'
|
||||
import type { SuppressionFormData } from './types'
|
||||
|
||||
// 页面标题
|
||||
const pageTitle = PAGE_TITLE
|
||||
|
||||
// 策略相关
|
||||
const { policyOptions, fetchPolicyOptions, getPolicyName } = usePolicy()
|
||||
|
||||
// 表格相关
|
||||
const {
|
||||
loading,
|
||||
tableData,
|
||||
switchLoadingMap,
|
||||
formModel,
|
||||
pagination,
|
||||
fetchList,
|
||||
handleFormModelUpdate,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handleRefresh,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
handleToggleEnabled,
|
||||
handleDelete,
|
||||
handleGoToPolicy,
|
||||
} = useTable()
|
||||
|
||||
// 筛选表单项配置
|
||||
const formItems = useFormItems(policyOptions.value)
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = computed(() => getTableColumns())
|
||||
|
||||
// 弹窗相关
|
||||
const modalVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const submitting = ref(false)
|
||||
const formData = ref<SuppressionFormData>({ ...DEFAULT_FORM_DATA })
|
||||
|
||||
// 打开新建弹窗
|
||||
const handleOpenCreateModal = () => {
|
||||
isEdit.value = false
|
||||
formData.value = { ...DEFAULT_FORM_DATA }
|
||||
modalVisible.value = true
|
||||
}
|
||||
|
||||
// 打开编辑弹窗
|
||||
const handleOpenEditModal = async (record: SuppressionRule) => {
|
||||
isEdit.value = true
|
||||
try {
|
||||
const res = await fetchSuppressionDetail(record.id!)
|
||||
if (res.code === 0 && res.details) {
|
||||
formData.value = { ...res.details }
|
||||
modalVisible.value = true
|
||||
} else {
|
||||
Message.error(res.message || '获取抑制规则详情失败')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取抑制规则详情失败:', error)
|
||||
Message.error(error.message || '获取抑制规则详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
fetchPolicyOptions()
|
||||
fetchList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'AlertSuppressPage',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.config-text {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
</style>
|
||||
44
src/views/ops/pages/alert/suppress/types.ts
Normal file
44
src/views/ops/pages/alert/suppress/types.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { SuppressionRule, SuppressionType } from '@/api/ops/suppression'
|
||||
|
||||
/**
|
||||
* 筛选表单模型
|
||||
*/
|
||||
export interface SearchFormModel {
|
||||
keyword: string
|
||||
type: string
|
||||
policy_id: string
|
||||
enabled: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 策略选项
|
||||
*/
|
||||
export interface PolicyOption {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页配置
|
||||
*/
|
||||
export interface PaginationConfig {
|
||||
current: number
|
||||
pageSize: number
|
||||
total: number
|
||||
showTotal: boolean
|
||||
showJumper: boolean
|
||||
showPageSize: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单数据(创建/编辑)
|
||||
*/
|
||||
export type SuppressionFormData = SuppressionRule
|
||||
|
||||
/**
|
||||
* 开关加载状态映射
|
||||
*/
|
||||
export type SwitchLoadingMap = Record<number, boolean>
|
||||
|
||||
// 重新导出 API 类型
|
||||
export type { SuppressionRule, SuppressionType }
|
||||
136
src/views/ops/pages/alert/suppress/utils/index.ts
Normal file
136
src/views/ops/pages/alert/suppress/utils/index.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
SUPPRESSION_TYPE_OPTIONS,
|
||||
SUPPRESSION_TYPE_COLORS,
|
||||
type SuppressionType,
|
||||
type SuppressionRule,
|
||||
type SuppressionUpdateParams,
|
||||
type SuppressionCreateParams,
|
||||
} from '@/api/ops/suppression'
|
||||
|
||||
/**
|
||||
* 获取抑制类型标签
|
||||
*/
|
||||
export const getTypeLabel = (type: SuppressionType) => {
|
||||
const option = SUPPRESSION_TYPE_OPTIONS.find((o) => o.value === type)
|
||||
return option?.label || type
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取抑制类型颜色
|
||||
*/
|
||||
export const getTypeColor = (type: SuppressionType) => {
|
||||
return SUPPRESSION_TYPE_COLORS[type] || 'gray'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关键配置摘要
|
||||
*/
|
||||
export const getConfigSummary = (record: SuppressionRule) => {
|
||||
switch (record.type) {
|
||||
case 'dedup':
|
||||
return `窗口 ${record.dedup_window || 0}s · 键 ${record.dedup_keys || '-'}`
|
||||
case 'aggregate':
|
||||
return `窗口 ${record.aggregate_window || 0}s · 分组 ${record.group_by || '-'} · 阈值 ${record.aggregate_count || 0}`
|
||||
case 'throttle':
|
||||
return `${record.throttle_count || 0} 次 / ${record.throttle_window || 0}s`
|
||||
case 'schedule':
|
||||
return '已配置时间窗'
|
||||
case 'dependency':
|
||||
return '源/目标匹配器已配置'
|
||||
default:
|
||||
return '-'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
export const formatDateTime = (dateStr?: string) => {
|
||||
if (!dateStr) return '-'
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
})
|
||||
} catch {
|
||||
return dateStr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 JSON 字段
|
||||
* @returns 错误信息,如果校验通过则返回 null
|
||||
*/
|
||||
export const validateJsonFields = (formData: SuppressionRule, fields: string[]): string | null => {
|
||||
for (const field of fields) {
|
||||
const value = formData[field as keyof SuppressionRule]
|
||||
if (value && typeof value === 'string' && value !== '{}' && value.trim() !== '') {
|
||||
try {
|
||||
JSON.parse(value)
|
||||
} catch {
|
||||
return `${field} 字段 JSON 格式不正确`
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建更新数据(只传非空字段)
|
||||
*/
|
||||
export const buildUpdateData = (formData: SuppressionRule): SuppressionUpdateParams => {
|
||||
const updateData: SuppressionUpdateParams = { id: formData.id! }
|
||||
|
||||
if (formData.name) updateData.name = formData.name
|
||||
if (formData.type) updateData.type = formData.type
|
||||
if (formData.description) updateData.description = formData.description
|
||||
updateData.enabled = formData.enabled
|
||||
if (formData.priority && formData.priority > 0) updateData.priority = formData.priority
|
||||
if (formData.policy_id && formData.policy_id > 0) updateData.policy_id = formData.policy_id
|
||||
if (formData.dedup_window && formData.dedup_window > 0) updateData.dedup_window = formData.dedup_window
|
||||
if (formData.dedup_keys) updateData.dedup_keys = formData.dedup_keys
|
||||
if (formData.aggregate_window && formData.aggregate_window > 0) updateData.aggregate_window = formData.aggregate_window
|
||||
if (formData.group_by) updateData.group_by = formData.group_by
|
||||
if (formData.aggregate_count && formData.aggregate_count > 0) updateData.aggregate_count = formData.aggregate_count
|
||||
if (formData.source_matchers) updateData.source_matchers = formData.source_matchers
|
||||
if (formData.target_matchers) updateData.target_matchers = formData.target_matchers
|
||||
if (formData.throttle_count && formData.throttle_count > 0) updateData.throttle_count = formData.throttle_count
|
||||
if (formData.throttle_window && formData.throttle_window > 0) updateData.throttle_window = formData.throttle_window
|
||||
if (formData.schedule) updateData.schedule = formData.schedule
|
||||
if (formData.matchers) updateData.matchers = formData.matchers
|
||||
|
||||
return updateData
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建创建数据
|
||||
*/
|
||||
export const buildCreateData = (formData: SuppressionRule): SuppressionCreateParams => {
|
||||
const createData: SuppressionCreateParams = {
|
||||
name: formData.name,
|
||||
type: formData.type,
|
||||
enabled: formData.enabled,
|
||||
}
|
||||
|
||||
if (formData.description) createData.description = formData.description
|
||||
if (formData.priority) createData.priority = formData.priority
|
||||
if (formData.policy_id !== undefined) createData.policy_id = formData.policy_id
|
||||
if (formData.dedup_window) createData.dedup_window = formData.dedup_window
|
||||
if (formData.dedup_keys) createData.dedup_keys = formData.dedup_keys
|
||||
if (formData.aggregate_window) createData.aggregate_window = formData.aggregate_window
|
||||
if (formData.group_by) createData.group_by = formData.group_by
|
||||
if (formData.aggregate_count) createData.aggregate_count = formData.aggregate_count
|
||||
if (formData.source_matchers) createData.source_matchers = formData.source_matchers
|
||||
if (formData.target_matchers) createData.target_matchers = formData.target_matchers
|
||||
if (formData.throttle_count) createData.throttle_count = formData.throttle_count
|
||||
if (formData.throttle_window) createData.throttle_window = formData.throttle_window
|
||||
if (formData.schedule) createData.schedule = formData.schedule
|
||||
if (formData.matchers) createData.matchers = formData.matchers
|
||||
|
||||
return createData
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
<!-- 表单内容 -->
|
||||
<div class="page-content">
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-form ref="formRef" :model="form" layout="vertical">
|
||||
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
||||
<!-- 基本信息 -->
|
||||
<a-card class="info-card" title="基本信息">
|
||||
<a-row :gutter="16">
|
||||
@@ -450,6 +450,12 @@ const form = ref<AssetForm>({
|
||||
remarks: '',
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
asset_name: [{ required: true, message: '请输入资产名称' }],
|
||||
asset_code: [{ required: true, message: '请输入资产编号' }],
|
||||
}
|
||||
|
||||
// 提取列表数据的辅助函数
|
||||
const extractList = (res: any): any[] => {
|
||||
const candidate =
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<!-- 表单内容 -->
|
||||
<div class="page-content">
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-form ref="formRef" :model="form" layout="vertical">
|
||||
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
|
||||
<!-- 基本信息 -->
|
||||
<a-card class="info-card" title="基本信息">
|
||||
<a-row :gutter="16">
|
||||
@@ -351,6 +351,21 @@ const form = ref<Supplier>({
|
||||
status: 'active',
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入供应商名称' }],
|
||||
code: [{ required: true, message: '请输入供应商编码' }],
|
||||
supplier_type: [{ required: true, message: '请选择供应商类型' }],
|
||||
status: [{ required: true, message: '请选择状态' }],
|
||||
contact_person: [{ required: true, message: '请输入联系人' }],
|
||||
contact_phone: [{ required: true, message: '请输入联系电话' }],
|
||||
contact_mobile: [{ required: true, message: '请输入备用联系电话' }],
|
||||
contact_email: [
|
||||
{ required: true, message: '请输入联系邮箱' },
|
||||
{ type: 'email', message: '请输入正确的邮箱格式' },
|
||||
],
|
||||
}
|
||||
|
||||
// 初始化页面
|
||||
const initPage = async () => {
|
||||
const id = route.query.id
|
||||
|
||||
Reference in New Issue
Block a user