fix
This commit is contained in:
@@ -5,8 +5,8 @@ export const fetchPolicyList = (data: {
|
||||
page?: number,
|
||||
page_size?: number,
|
||||
keyword?: string,
|
||||
enabled?: string,
|
||||
priority?: number[],
|
||||
enabled?: boolean,
|
||||
priority?: number,
|
||||
created_at_start?: string,
|
||||
created_at_end?: string,
|
||||
order_by?: string,
|
||||
@@ -107,3 +107,8 @@ export const fetchSeverityList = (data: {
|
||||
page_size?: number;
|
||||
enabled?: string;
|
||||
}) => request.get("/Alert/v1/severity/list", { params: data });
|
||||
|
||||
/** 获取工单模板下拉选项 */
|
||||
export const fetchFeedbackTemplateOptions = (data?: {
|
||||
status?: 'active' | 'inactive'
|
||||
}) => request.get('/Feedback/v1/templates/options', { params: data || {} });
|
||||
|
||||
@@ -8,56 +8,53 @@
|
||||
:footer="false"
|
||||
>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-descriptions :column="2" bordered v-if="levelDetail">
|
||||
<a-descriptions-item label="级别名称" :span="2">
|
||||
{{ levelDetail.name }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="级别代码">
|
||||
{{ levelDetail.code }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="优先级">
|
||||
{{ levelDetail.priority }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="颜色">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<div
|
||||
:style="{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
backgroundColor: levelDetail.color,
|
||||
border: '1px solid var(--color-border-2)',
|
||||
borderRadius: '4px'
|
||||
}"
|
||||
></div>
|
||||
<span>{{ levelDetail.color }}</span>
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="图标">
|
||||
{{ levelDetail.icon || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态" :span="2">
|
||||
<a-tag :color="levelDetail.enabled ? 'green' : 'red'">
|
||||
{{ levelDetail.enabled ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="系统级别" :span="2">
|
||||
<a-tag :color="levelDetail.is_default ? 'blue' : 'gray'">
|
||||
{{ levelDetail.is_default ? '是' : '否' }}
|
||||
</a-tag>
|
||||
<span v-if="levelDetail.is_default" style="color: var(--color-text-3); font-size: 12px; margin-left: 8px;">
|
||||
(系统预置的默认级别)
|
||||
</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="描述" :span="2">
|
||||
{{ levelDetail.description || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">
|
||||
{{ formatDate(levelDetail.created_at) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">
|
||||
{{ formatDate(levelDetail.updated_at) }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<template v-if="levelDetail">
|
||||
<a-space direction="vertical" :size="12" fill>
|
||||
<a-card :bordered="false" class="detail-header-card">
|
||||
<div class="detail-header">
|
||||
<div>
|
||||
<div class="detail-title">{{ levelDetail.name || '-' }}</div>
|
||||
<div class="detail-subtitle">代码:{{ levelDetail.code || '-' }}</div>
|
||||
</div>
|
||||
<a-space>
|
||||
<a-tag :color="levelDetail.enabled ? 'green' : 'red'">
|
||||
{{ levelDetail.enabled ? '启用' : '禁用' }}
|
||||
</a-tag>
|
||||
<a-tag :color="levelDetail.is_default ? 'blue' : 'gray'">
|
||||
{{ levelDetail.is_default ? '系统默认' : '自定义' }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="优先级">
|
||||
{{ levelDetail.priority }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="图标">
|
||||
{{ levelDetail.icon || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="颜色" :span="2">
|
||||
<div class="color-display">
|
||||
<div class="color-block" :style="{ backgroundColor: levelDetail.color }"></div>
|
||||
<span>{{ levelDetail.color || '-' }}</span>
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="描述" :span="2">
|
||||
{{ levelDetail.description || '-' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="创建时间">
|
||||
{{ formatDate(levelDetail.created_at) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">
|
||||
{{ formatDate(levelDetail.updated_at) }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
</template>
|
||||
@@ -143,5 +140,39 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
// 样式可以根据需要添加
|
||||
.detail-header-card {
|
||||
background: var(--color-fill-1);
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.detail-subtitle {
|
||||
margin-top: 4px;
|
||||
color: var(--color-text-3);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.color-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.color-block {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 1px solid var(--color-border-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,90 +8,67 @@
|
||||
@update:visible="handleVisibleChange"
|
||||
:confirm-loading="submitting"
|
||||
>
|
||||
<a-form :model="form" layout="vertical" ref="formRef">
|
||||
<a-form-item
|
||||
label="级别名称"
|
||||
field="name"
|
||||
:rules="[{ required: true, message: '请输入级别名称' }]"
|
||||
>
|
||||
<a-input
|
||||
v-model="form.name"
|
||||
placeholder="请输入级别名称,如:紧急、严重、警告、提示"
|
||||
:max-length="50"
|
||||
/>
|
||||
<a-form :model="form" layout="vertical" ref="formRef" class="level-form">
|
||||
<a-alert type="info" show-icon :style="{ marginBottom: '16px' }">
|
||||
建议统一使用语义化代码(如 critical、warning、info),便于规则与派单配置复用。
|
||||
</a-alert>
|
||||
|
||||
<a-divider orientation="left">基础信息</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="级别名称" field="name" :rules="[{ required: true, message: '请输入级别名称' }]">
|
||||
<a-input v-model="form.name" placeholder="如:紧急、严重、警告、提示" :max-length="50" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="级别代码(英文)"
|
||||
field="code"
|
||||
:rules="[{ required: true, message: '请输入级别代码' }, { validator: validateCode }]"
|
||||
>
|
||||
<a-input
|
||||
v-model="form.code"
|
||||
placeholder="如:critical、warning、info"
|
||||
:max-length="50"
|
||||
:disabled="isEdit && level?.is_default"
|
||||
allow-clear
|
||||
/>
|
||||
<template v-if="isEdit && level?.is_default">
|
||||
<div class="field-tips">默认级别的代码不可修改</div>
|
||||
</template>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider orientation="left">显示配置</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="颜色" field="color" :rules="[{ required: true, message: '请选择颜色' }]">
|
||||
<div class="color-row">
|
||||
<input type="color" v-model="form.color" class="color-picker" />
|
||||
<a-input v-model="form.color" placeholder="如:#FF5722" allow-clear />
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="图标" field="icon">
|
||||
<a-input v-model="form.icon" placeholder="可选,输入图标名称或类名" :max-length="100" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="是否启用" field="enabled">
|
||||
<a-space>
|
||||
<a-switch v-model="form.enabled">
|
||||
<template #checked>启用</template>
|
||||
<template #unchecked>禁用</template>
|
||||
</a-switch>
|
||||
<span class="field-tips">禁用后不会参与告警展示与匹配</span>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="级别代码(英文)"
|
||||
field="code"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入级别代码' },
|
||||
{ validator: validateCode }
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model="form.code"
|
||||
placeholder="请输入级别代码,如:critical、warning、info"
|
||||
:max-length="50"
|
||||
:disabled="isEdit && level?.is_default"
|
||||
/>
|
||||
<template v-if="isEdit && level?.is_default">
|
||||
<div style="color: var(--color-text-3); font-size: 12px; margin-top: 4px;">
|
||||
默认级别的代码不可修改
|
||||
</div>
|
||||
</template>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="颜色"
|
||||
field="color"
|
||||
:rules="[{ required: true, message: '请选择颜色' }]"
|
||||
>
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<input
|
||||
type="color"
|
||||
v-model="form.color"
|
||||
style="width: 60px; height: 32px; cursor: pointer; border: 1px solid var(--color-border-2); border-radius: 4px;"
|
||||
/>
|
||||
<a-input
|
||||
v-model="form.color"
|
||||
placeholder="请输入颜色值,如:#FF5722"
|
||||
style="flex: 1;"
|
||||
/>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="图标"
|
||||
field="icon"
|
||||
>
|
||||
<a-input
|
||||
v-model="form.icon"
|
||||
placeholder="请输入图标名称或图标类名"
|
||||
:max-length="100"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="是否启用"
|
||||
field="enabled"
|
||||
>
|
||||
<a-switch v-model="form.enabled">
|
||||
<template #checked>启用</template>
|
||||
<template #unchecked>禁用</template>
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="描述"
|
||||
field="description"
|
||||
>
|
||||
<a-textarea
|
||||
v-model="form.description"
|
||||
placeholder="请输入级别描述"
|
||||
:auto-size="{ minRows: 4, maxRows: 8 }"
|
||||
:max-length="500"
|
||||
/>
|
||||
<a-form-item label="描述" field="description">
|
||||
<a-textarea v-model="form.description" placeholder="补充该级别的使用场景或说明(可选)" :auto-size="{ minRows: 3, maxRows: 6 }" :max-length="500" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
@@ -245,4 +222,27 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.level-form {
|
||||
.field-tips {
|
||||
color: var(--color-text-3);
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.color-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
width: 60px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--color-border-2);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
title="新建告警策略"
|
||||
width="800px"
|
||||
:mask-closable="false"
|
||||
:body-style="{ overflowX: 'hidden' }"
|
||||
@cancel="handleCancel"
|
||||
@before-ok="handleSubmit"
|
||||
@update:visible="handleVisibleChange"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
layout="vertical"
|
||||
:style="{ marginBottom: '40px' }"
|
||||
>
|
||||
<a-divider orientation="left">基础信息</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item field="name" label="策略名称" required>
|
||||
<a-input v-model="formData.name" placeholder="请输入策略名称" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item field="priority" label="优先级">
|
||||
<a-select v-model="formData.priority" placeholder="请选择优先级" allow-clear>
|
||||
<a-option :value="1">1 (最高)</a-option>
|
||||
<a-option :value="2">2</a-option>
|
||||
<a-option :value="3">3</a-option>
|
||||
<a-option :value="4">4</a-option>
|
||||
<a-option :value="5">5 (最低)</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item field="description" label="策略描述">
|
||||
<a-textarea v-model="formData.description" placeholder="请输入策略描述" :auto-size="{ minRows: 2, maxRows: 4 }" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item field="enabled" label="启用状态">
|
||||
<a-switch v-model="formData.enabled" :checked-value="true" :unchecked-value="false">
|
||||
<template #checked> 启用 </template>
|
||||
<template #unchecked> 禁用 </template>
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<a-divider orientation="left">模板与拓展配置</a-divider>
|
||||
|
||||
<a-form-item field="template_id" label="关联模板">
|
||||
<a-select
|
||||
v-model="formData.template_id"
|
||||
placeholder="请选择告警模板(可选)"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="templateLoading"
|
||||
>
|
||||
<a-option v-for="template in templateOptions" :key="template.id" :value="template.id">
|
||||
{{ template.name }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item field="labels" label="拓展配置">
|
||||
<a-textarea
|
||||
v-model="formData.labels"
|
||||
placeholder='请输入拓展配置,JSON格式,例如:{"env":"prod","team":"ops"}'
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
allow-clear
|
||||
/>
|
||||
<template #extra> 请输入有效的 JSON 格式配置 </template>
|
||||
</a-form-item>
|
||||
|
||||
<a-divider orientation="left">自动建单与派单</a-divider>
|
||||
|
||||
<a-form-item field="auto_create_ticket" label="触发时自动建单">
|
||||
<a-switch v-model="formData.auto_create_ticket" :checked-value="true" :unchecked-value="false">
|
||||
<template #checked> 开启 </template>
|
||||
<template #unchecked> 关闭 </template>
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item field="feedback_template_id" label="工单模板">
|
||||
<a-select
|
||||
v-model="formData.feedback_template_id"
|
||||
placeholder="请选择工单模板(可选)"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="feedbackTemplateLoading"
|
||||
:disabled="!formData.auto_create_ticket"
|
||||
>
|
||||
<a-option :value="0">不使用特定模板</a-option>
|
||||
<a-option v-for="item in feedbackTemplateOptions" :key="item.id" :value="item.id">
|
||||
{{ item.template_name }} ({{ item.template_code }})
|
||||
</a-option>
|
||||
</a-select>
|
||||
<template #extra>
|
||||
{{ formData.auto_create_ticket ? '建议默认选择可用模板,未选择时将按 0 处理' : '请先开启自动建单后再选择工单模板' }}
|
||||
</template>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="派单规则">
|
||||
<a-table :data="dispatchRuleData" :pagination="false" :bordered="true" size="medium" class="dispatch-table">
|
||||
<template #columns>
|
||||
<a-table-column title="告警级别" data-index="severity">
|
||||
<template #cell="{ record }">
|
||||
<a-tag :color="record.color">
|
||||
{{ record.name }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="处理人" data-index="assignee_id">
|
||||
<template #cell="{ record }">
|
||||
<a-select
|
||||
v-model="record.assignee_id"
|
||||
placeholder="请选择处理人"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="userLoading"
|
||||
:style="{ width: '100%' }"
|
||||
>
|
||||
<a-option v-for="user in userOptions" :key="user.id" :value="user.id">
|
||||
{{ user.name || user.username }} ({{ user.username }})
|
||||
</a-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="默认处理人">
|
||||
<a-select v-model="defaultAssigneeId" placeholder="请选择默认处理人" allow-search allow-clear :loading="userLoading">
|
||||
<a-option v-for="user in userOptions" :key="user.id" :value="user.id">
|
||||
{{ user.name || user.username }} ({{ user.username }})
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
<a-button @click="handleCancel">取消</a-button>
|
||||
<a-button type="primary" :loading="submitLoading" @click="handleSubmit"> 保存 </a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { FormInstance } from '@arco-design/web-vue'
|
||||
import { createPolicy, fetchTemplateList, fetchSeverityList, fetchFeedbackTemplateOptions } from '@/api/ops/alertPolicy'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
const emit = defineEmits(['update:visible', 'success'])
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const submitLoading = ref(false)
|
||||
const templateLoading = ref(false)
|
||||
const feedbackTemplateLoading = ref(false)
|
||||
const userLoading = ref(false)
|
||||
|
||||
const formData = reactive({
|
||||
name: '',
|
||||
description: '',
|
||||
enabled: true,
|
||||
priority: 3,
|
||||
labels: '',
|
||||
template_id: undefined as number | undefined,
|
||||
auto_create_ticket: false,
|
||||
feedback_template_id: undefined as number | undefined,
|
||||
dispatch_rule: '',
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
name: [{ required: true, message: '请输入策略名称' }],
|
||||
labels: [
|
||||
{
|
||||
validator: (value: string, callback: any) => {
|
||||
if (!value) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
try {
|
||||
JSON.parse(value)
|
||||
callback()
|
||||
} catch (error) {
|
||||
callback('拓展配置格式错误,请输入有效的 JSON 格式')
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const templateOptions = ref<any[]>([])
|
||||
const feedbackTemplateOptions = ref<any[]>([])
|
||||
const userOptions = ref<any[]>([
|
||||
{ id: 1, name: '张三', username: 'zhangsan' },
|
||||
{ id: 2, name: '李四', username: 'lisi' },
|
||||
{ id: 3, name: '王五', username: 'wangwu' },
|
||||
])
|
||||
const severityList = ref<any[]>([
|
||||
{ code: 'critical', name: '严重', color: 'red' },
|
||||
{ code: 'major', name: '重要', color: 'orange' },
|
||||
{ code: 'warning', name: '警告', color: 'yellow' },
|
||||
{ code: 'info', name: '信息', color: 'blue' },
|
||||
{ code: 'minor', name: '次要', color: 'gray' },
|
||||
])
|
||||
const dispatchRuleData = ref<any[]>([])
|
||||
const defaultAssigneeId = ref<number | undefined>(undefined)
|
||||
|
||||
const loadTemplateOptions = async () => {
|
||||
templateLoading.value = true
|
||||
try {
|
||||
const res = await fetchTemplateList({ page: 1, page_size: 1000 })
|
||||
if (res.code === 0 && res.details?.data) {
|
||||
templateOptions.value = res.details.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取模板列表失败:', error)
|
||||
} finally {
|
||||
templateLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadSeverityList = async () => {
|
||||
try {
|
||||
const res = await fetchSeverityList({ page: 1, page_size: 100 })
|
||||
if (res.code === 0 && res.details?.data) {
|
||||
severityList.value = res.details.data
|
||||
initDispatchRuleData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取告警级别失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const loadFeedbackTemplateOptions = async () => {
|
||||
feedbackTemplateLoading.value = true
|
||||
try {
|
||||
const res = await fetchFeedbackTemplateOptions({ status: 'active' })
|
||||
const rawOptions = Array.isArray(res?.data)
|
||||
? res.data
|
||||
: Array.isArray(res?.details?.data)
|
||||
? res.details.data
|
||||
: Array.isArray(res?.details)
|
||||
? res.details
|
||||
: []
|
||||
|
||||
feedbackTemplateOptions.value = rawOptions.map((item: any) => ({
|
||||
id: Number(item.id),
|
||||
template_name: item.template_name || item.name || `模板-${item.id}`,
|
||||
template_code: item.template_code || item.code || '',
|
||||
status: item.status,
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('获取工单模板下拉选项失败:', error)
|
||||
} finally {
|
||||
feedbackTemplateLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const initDispatchRuleData = () => {
|
||||
dispatchRuleData.value = severityList.value.map((severity) => ({
|
||||
severity_code: severity.code,
|
||||
name: severity.name,
|
||||
color: severity.color,
|
||||
assignee_id: undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
const generateDispatchRule = (): string => {
|
||||
const by_severity: Record<string, number> = {}
|
||||
dispatchRuleData.value.forEach((item) => {
|
||||
if (item.assignee_id !== undefined) {
|
||||
by_severity[item.severity_code] = item.assignee_id
|
||||
}
|
||||
})
|
||||
|
||||
const rule: Record<string, any> = {}
|
||||
if (Object.keys(by_severity).length > 0) {
|
||||
rule.by_severity = by_severity
|
||||
}
|
||||
if (defaultAssigneeId.value !== undefined) {
|
||||
rule.default_assignee_id = defaultAssigneeId.value
|
||||
}
|
||||
|
||||
return Object.keys(rule).length > 0 ? JSON.stringify(rule) : ''
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
formData.name = ''
|
||||
formData.description = ''
|
||||
formData.enabled = true
|
||||
formData.priority = 3
|
||||
formData.labels = ''
|
||||
formData.template_id = undefined
|
||||
formData.auto_create_ticket = false
|
||||
formData.feedback_template_id = undefined
|
||||
formData.dispatch_rule = ''
|
||||
defaultAssigneeId.value = undefined
|
||||
initDispatchRuleData()
|
||||
}
|
||||
|
||||
const handleVisibleChange = (visible: boolean) => {
|
||||
emit('update:visible', visible)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false)
|
||||
resetForm()
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
formData.dispatch_rule = generateDispatchRule()
|
||||
const data = {
|
||||
name: formData.name,
|
||||
description: formData.description || undefined,
|
||||
enabled: !!formData.enabled,
|
||||
priority: formData.priority ?? undefined,
|
||||
labels: formData.labels || undefined,
|
||||
template_id: formData.template_id ?? undefined,
|
||||
auto_create_ticket: !!formData.auto_create_ticket,
|
||||
feedback_template_id: formData.feedback_template_id ?? 0,
|
||||
dispatch_rule: formData.dispatch_rule || undefined,
|
||||
}
|
||||
|
||||
await createPolicy(data)
|
||||
Message.success('策略创建成功')
|
||||
emit('success')
|
||||
handleCancel()
|
||||
} catch (error: any) {
|
||||
console.error('创建策略失败:', error)
|
||||
Message.error(error.message || '创建策略失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => formData.auto_create_ticket,
|
||||
(enabled) => {
|
||||
if (!enabled) {
|
||||
formData.feedback_template_id = undefined
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
loadTemplateOptions()
|
||||
loadSeverityList()
|
||||
loadFeedbackTemplateOptions()
|
||||
initDispatchRuleData()
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'PolicyCreateDialog',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.dispatch-table {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
:title="isEdit ? `编辑告警策略 - ${policyName}` : '新建告警策略'"
|
||||
:title="`编辑告警策略 - ${policyName}`"
|
||||
width="800px"
|
||||
:mask-closable="false"
|
||||
:body-style="{ overflowX: 'hidden' }"
|
||||
@cancel="handleCancel"
|
||||
@before-ok="handleSubmit"
|
||||
@update:visible="handleVisibleChange"
|
||||
@@ -69,8 +70,8 @@
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 模板与标签 -->
|
||||
<a-divider orientation="left">模板与标签</a-divider>
|
||||
<!-- 模板与拓展配置 -->
|
||||
<a-divider orientation="left">模板与拓展配置</a-divider>
|
||||
|
||||
<a-form-item field="template_id" label="关联模板">
|
||||
<a-select
|
||||
@@ -90,15 +91,15 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item field="labels" label="标签">
|
||||
<a-form-item field="labels" label="拓展配置">
|
||||
<a-textarea
|
||||
v-model="formData.labels"
|
||||
placeholder='请输入标签,JSON格式,例如:{"env":"prod","team":"ops"}'
|
||||
placeholder='请输入拓展配置,JSON格式,例如:{"env":"prod","team":"ops"}'
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
allow-clear
|
||||
/>
|
||||
<template #extra>
|
||||
请输入有效的 JSON 格式键值对
|
||||
请输入有效的 JSON 格式配置
|
||||
</template>
|
||||
</a-form-item>
|
||||
|
||||
@@ -120,16 +121,26 @@
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item field="feedback_template_id" label="工单模板 ID">
|
||||
<a-input-number
|
||||
<a-form-item field="feedback_template_id" label="工单模板">
|
||||
<a-select
|
||||
v-model="formData.feedback_template_id"
|
||||
placeholder="请输入工单模板 ID"
|
||||
:min="0"
|
||||
placeholder="请选择工单模板(可选)"
|
||||
allow-search
|
||||
allow-clear
|
||||
:style="{ width: '100%' }"
|
||||
/>
|
||||
:loading="feedbackTemplateLoading"
|
||||
:disabled="!formData.auto_create_ticket"
|
||||
>
|
||||
<a-option :value="0">不使用特定模板</a-option>
|
||||
<a-option
|
||||
v-for="item in feedbackTemplateOptions"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.template_name }} ({{ item.template_code }})
|
||||
</a-option>
|
||||
</a-select>
|
||||
<template #extra>
|
||||
0 表示不使用特定模板
|
||||
{{ formData.auto_create_ticket ? '建议默认选择可用模板,未选择时将按 0 处理' : '请先开启自动建单后再选择工单模板' }}
|
||||
</template>
|
||||
</a-form-item>
|
||||
|
||||
@@ -139,16 +150,17 @@
|
||||
:pagination="false"
|
||||
:bordered="true"
|
||||
size="medium"
|
||||
class="dispatch-table"
|
||||
>
|
||||
<template #columns>
|
||||
<a-table-column title="告警级别" data-index="severity" :width="150">
|
||||
<a-table-column title="告警级别" data-index="severity">
|
||||
<template #cell="{ record }">
|
||||
<a-tag :color="record.color">
|
||||
{{ record.name }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column title="处理人" data-index="assignee_id" :width="350">
|
||||
<a-table-column title="处理人" data-index="assignee_id">
|
||||
<template #cell="{ record }">
|
||||
<a-select
|
||||
v-model="record.assignee_id"
|
||||
@@ -170,28 +182,24 @@
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<div :style="{ marginTop: '16px' }">
|
||||
<a-space>
|
||||
<span :style="{ fontWeight: 'bold' }">默认处理人:</span>
|
||||
<a-select
|
||||
v-model="defaultAssigneeId"
|
||||
placeholder="请选择默认处理人"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="userLoading"
|
||||
:style="{ width: '300px' }"
|
||||
>
|
||||
<a-option
|
||||
v-for="user in userOptions"
|
||||
:key="user.id"
|
||||
:value="user.id"
|
||||
>
|
||||
{{ user.name || user.username }} ({{ user.username }})
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="默认处理人">
|
||||
<a-select
|
||||
v-model="defaultAssigneeId"
|
||||
placeholder="请选择默认处理人"
|
||||
allow-search
|
||||
allow-clear
|
||||
:loading="userLoading"
|
||||
>
|
||||
<a-option
|
||||
v-for="user in userOptions"
|
||||
:key="user.id"
|
||||
:value="user.id"
|
||||
>
|
||||
{{ user.name || user.username }} ({{ user.username }})
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
@@ -205,15 +213,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { FormInstance } from '@arco-design/web-vue'
|
||||
import {
|
||||
fetchPolicyDetail,
|
||||
createPolicy,
|
||||
updatePolicy,
|
||||
fetchTemplateList,
|
||||
fetchSeverityList,
|
||||
fetchFeedbackTemplateOptions,
|
||||
} from '@/api/ops/alertPolicy'
|
||||
|
||||
// Props
|
||||
@@ -231,10 +239,9 @@ const formRef = ref<FormInstance>()
|
||||
// 加载状态
|
||||
const submitLoading = ref(false)
|
||||
const templateLoading = ref(false)
|
||||
const feedbackTemplateLoading = ref(false)
|
||||
const userLoading = ref(false)
|
||||
|
||||
// 是否为编辑模式
|
||||
const isEdit = computed(() => !!props.policyId)
|
||||
const policyName = ref('')
|
||||
|
||||
// 表单数据
|
||||
@@ -246,7 +253,7 @@ const formData = reactive({
|
||||
labels: '',
|
||||
template_id: undefined,
|
||||
auto_create_ticket: false,
|
||||
feedback_template_id: 0,
|
||||
feedback_template_id: undefined as number | undefined,
|
||||
dispatch_rule: '',
|
||||
})
|
||||
|
||||
@@ -264,7 +271,7 @@ const formRules = {
|
||||
JSON.parse(value)
|
||||
callback()
|
||||
} catch (error) {
|
||||
callback('标签格式错误,请输入有效的 JSON 格式')
|
||||
callback('拓展配置格式错误,请输入有效的 JSON 格式')
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -273,6 +280,7 @@ const formRules = {
|
||||
|
||||
// 模板选项
|
||||
const templateOptions = ref<any[]>([])
|
||||
const feedbackTemplateOptions = ref<any[]>([])
|
||||
|
||||
// 用户选项(模拟数据,实际应从用户服务获取)
|
||||
const userOptions = ref<any[]>([
|
||||
@@ -321,6 +329,31 @@ const loadSeverityList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadFeedbackTemplateOptions = async () => {
|
||||
feedbackTemplateLoading.value = true
|
||||
try {
|
||||
const res = await fetchFeedbackTemplateOptions({ status: 'active' })
|
||||
const rawOptions = Array.isArray(res?.data)
|
||||
? res.data
|
||||
: Array.isArray(res?.details?.data)
|
||||
? res.details.data
|
||||
: Array.isArray(res?.details)
|
||||
? res.details
|
||||
: []
|
||||
|
||||
feedbackTemplateOptions.value = rawOptions.map((item: any) => ({
|
||||
id: Number(item.id),
|
||||
template_name: item.template_name || item.name || `模板-${item.id}`,
|
||||
template_code: item.template_code || item.code || '',
|
||||
status: item.status,
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('获取工单模板下拉选项失败:', error)
|
||||
} finally {
|
||||
feedbackTemplateLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化派单规则数据
|
||||
const initDispatchRuleData = () => {
|
||||
dispatchRuleData.value = severityList.value.map((severity) => ({
|
||||
@@ -362,12 +395,15 @@ const generateDispatchRule = (): string => {
|
||||
}
|
||||
})
|
||||
|
||||
const rule: any = { by_severity }
|
||||
const rule: any = {}
|
||||
if (Object.keys(by_severity).length > 0) {
|
||||
rule.by_severity = by_severity
|
||||
}
|
||||
if (defaultAssigneeId.value !== undefined) {
|
||||
rule.default_assignee_id = defaultAssigneeId.value
|
||||
}
|
||||
|
||||
return JSON.stringify(rule)
|
||||
return Object.keys(rule).length > 0 ? JSON.stringify(rule) : ''
|
||||
}
|
||||
|
||||
// 加载策略详情
|
||||
@@ -387,7 +423,7 @@ const loadPolicyDetail = async () => {
|
||||
formData.labels = policy.labels || ''
|
||||
formData.template_id = policy.template_id
|
||||
formData.auto_create_ticket = policy.auto_create_ticket || false
|
||||
formData.feedback_template_id = policy.feedback_template_id || 0
|
||||
formData.feedback_template_id = policy.feedback_template_id ?? undefined
|
||||
formData.dispatch_rule = policy.dispatch_rule || ''
|
||||
|
||||
// 解析派单规则
|
||||
@@ -408,7 +444,7 @@ const resetForm = () => {
|
||||
formData.labels = ''
|
||||
formData.template_id = undefined
|
||||
formData.auto_create_ticket = false
|
||||
formData.feedback_template_id = 0
|
||||
formData.feedback_template_id = undefined
|
||||
formData.dispatch_rule = ''
|
||||
policyName.value = ''
|
||||
defaultAssigneeId.value = undefined
|
||||
@@ -436,19 +472,27 @@ const handleSubmit = async () => {
|
||||
try {
|
||||
// 生成派单规则
|
||||
formData.dispatch_rule = generateDispatchRule()
|
||||
|
||||
const data = { ...formData }
|
||||
|
||||
// 编辑模式
|
||||
if (isEdit.value && props.policyId) {
|
||||
await updatePolicy({ id: props.policyId, ...data })
|
||||
Message.success('策略更新成功')
|
||||
} else {
|
||||
// 新建模式
|
||||
await createPolicy(data)
|
||||
Message.success('策略创建成功')
|
||||
|
||||
const data = {
|
||||
name: formData.name,
|
||||
description: formData.description || undefined,
|
||||
enabled: !!formData.enabled,
|
||||
priority: formData.priority ?? undefined,
|
||||
labels: formData.labels || undefined,
|
||||
template_id: formData.template_id ?? undefined,
|
||||
auto_create_ticket: !!formData.auto_create_ticket,
|
||||
feedback_template_id: formData.feedback_template_id ?? 0,
|
||||
dispatch_rule: formData.dispatch_rule || undefined,
|
||||
}
|
||||
|
||||
if (!props.policyId) {
|
||||
Message.error('缺少策略ID,无法编辑')
|
||||
return
|
||||
}
|
||||
|
||||
await updatePolicy({ id: props.policyId, ...data })
|
||||
Message.success('策略更新成功')
|
||||
|
||||
emit('success')
|
||||
handleCancel()
|
||||
} catch (error: any) {
|
||||
@@ -462,19 +506,30 @@ const handleSubmit = async () => {
|
||||
// 监听 visible 变化
|
||||
watch(() => props.visible, (visible) => {
|
||||
if (visible) {
|
||||
if (isEdit.value) {
|
||||
if (props.policyId) {
|
||||
loadPolicyDetail()
|
||||
} else {
|
||||
resetForm()
|
||||
Message.error('缺少策略ID,无法打开编辑弹窗')
|
||||
emit('update:visible', false)
|
||||
}
|
||||
} else {
|
||||
resetForm()
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => formData.auto_create_ticket,
|
||||
(enabled) => {
|
||||
if (!enabled) {
|
||||
formData.feedback_template_id = undefined
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 初始化
|
||||
loadTemplateOptions()
|
||||
loadSeverityList()
|
||||
loadFeedbackTemplateOptions()
|
||||
initDispatchRuleData()
|
||||
</script>
|
||||
|
||||
@@ -483,3 +538,9 @@ export default {
|
||||
name: 'PolicyFormDialog',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.dispatch-table {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -91,23 +91,29 @@
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 额外操作按钮 -->
|
||||
<template #extra>
|
||||
<!-- 工具栏左侧按钮 -->
|
||||
<template #toolbar-left>
|
||||
<a-button type="primary" @click="handleCreate">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
新建告警策略
|
||||
新建策略
|
||||
</a-button>
|
||||
</template>
|
||||
</search-table>
|
||||
|
||||
<!-- 新建/编辑策略弹窗 -->
|
||||
<!-- 编辑策略弹窗 -->
|
||||
<policy-form-dialog
|
||||
v-model:visible="policyFormVisible"
|
||||
:policy-id="currentPolicyId"
|
||||
@success="handlePolicyFormSuccess"
|
||||
/>
|
||||
|
||||
<!-- 新建策略弹窗 -->
|
||||
<policy-create-dialog
|
||||
v-model:visible="createPolicyVisible"
|
||||
@success="handlePolicyFormSuccess"
|
||||
/>
|
||||
|
||||
<!-- 规则管理弹窗 -->
|
||||
<rule-manage-dialog
|
||||
@@ -121,7 +127,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { Message, Modal } from '@arco-design/web-vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { IconPlus } from '@arco-design/web-vue/es/icon'
|
||||
import type { FormItem } from '@/components/search-form/types'
|
||||
import SearchTable from '@/components/search-table/index.vue'
|
||||
@@ -133,6 +139,7 @@ import {
|
||||
updatePolicy,
|
||||
} from '@/api/ops/alertPolicy'
|
||||
import PolicyFormDialog from './components/PolicyFormDialog.vue'
|
||||
import PolicyCreateDialog from './components/PolicyCreateDialog.vue'
|
||||
import RuleManageDialog from './components/RuleManageDialog.vue'
|
||||
|
||||
// 状态管理
|
||||
@@ -163,6 +170,7 @@ const currentPolicyName = ref('')
|
||||
|
||||
// 弹窗可见性
|
||||
const policyFormVisible = ref(false)
|
||||
const createPolicyVisible = ref(false)
|
||||
const ruleManageVisible = ref(false)
|
||||
|
||||
// 获取策略列表
|
||||
@@ -174,24 +182,26 @@ const fetchPolicyData = async () => {
|
||||
page: pagination.current,
|
||||
page_size: pagination.pageSize,
|
||||
keyword: formModel.value.keyword || undefined,
|
||||
order_by: 'created_at',
|
||||
order: 'desc',
|
||||
}
|
||||
|
||||
// 处理启用状态筛选
|
||||
if (formModel.value.enabled && formModel.value.enabled !== 'all') {
|
||||
params.enabled = formModel.value.enabled
|
||||
params.enabled = formModel.value.enabled === 'true'
|
||||
}
|
||||
|
||||
// 处理优先级筛选
|
||||
if (formModel.value.priority) {
|
||||
params.priority = [formModel.value.priority]
|
||||
if (formModel.value.priority !== undefined && formModel.value.priority !== null) {
|
||||
params.priority = Number(formModel.value.priority)
|
||||
}
|
||||
|
||||
const res = await fetchPolicyList(params)
|
||||
|
||||
tableData.value = res.details?.data || []
|
||||
pagination.total = res.details?.total || 0
|
||||
|
||||
// 优先适配分页层字段:total/page/page_size/data,并兼容历史返回结构
|
||||
const pageData = res?.data?.data ?? res?.details?.data ?? res?.data ?? []
|
||||
const pageTotal = res?.data?.total ?? res?.details?.total ?? res?.total ?? 0
|
||||
|
||||
tableData.value = Array.isArray(pageData) ? pageData : []
|
||||
pagination.total = Number(pageTotal) || 0
|
||||
} catch (error) {
|
||||
console.error('获取告警策略列表失败:', error)
|
||||
Message.error('获取告警策略列表失败')
|
||||
@@ -255,8 +265,7 @@ const handleRefresh = () => {
|
||||
|
||||
// 新建策略
|
||||
const handleCreate = () => {
|
||||
currentPolicyId.value = undefined
|
||||
policyFormVisible.value = true
|
||||
createPolicyVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑策略
|
||||
|
||||
Reference in New Issue
Block a user