This commit is contained in:
zxr
2026-03-23 17:00:55 +08:00
parent dba76fa338
commit dc48c1635a
6 changed files with 693 additions and 210 deletions

View File

@@ -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 || {} });

View File

@@ -8,49 +8,44 @@
: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>
<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="颜色">
<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 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>
@@ -58,6 +53,8 @@
{{ 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>

View File

@@ -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-item>
<a-form :model="form" layout="vertical" ref="formRef" class="level-form">
<a-alert type="info" show-icon :style="{ marginBottom: '16px' }">
建议统一使用语义化代码 criticalwarninginfo便于规则与派单配置复用
</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 }
]"
:rules="[{ required: true, message: '请输入级别代码' }, { validator: validateCode }]"
>
<a-input
v-model="form.code"
placeholder="请输入级别代码,critical、warning、info"
placeholder="如critical、warning、info"
:max-length="50"
:disabled="isEdit && level?.is_default"
allow-clear
/>
<template v-if="isEdit && level?.is_default">
<div style="color: var(--color-text-3); font-size: 12px; margin-top: 4px;">
默认级别的代码不可修改
</div>
<div class="field-tips">默认级别的代码不可修改</div>
</template>
</a-form-item>
</a-col>
</a-row>
<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;"
/>
<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-form-item
label="图标"
field="icon"
>
<a-input
v-model="form.icon"
placeholder="请输入图标名称或图标类名"
:max-length="100"
/>
</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-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="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>

View File

@@ -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>

View File

@@ -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,17 +182,15 @@
</a-table-column>
</template>
</a-table>
</a-form-item>
<div :style="{ marginTop: '16px' }">
<a-space>
<span :style="{ fontWeight: 'bold' }">默认处理人</span>
<a-form-item label="默认处理人">
<a-select
v-model="defaultAssigneeId"
placeholder="请选择默认处理人"
allow-search
allow-clear
:loading="userLoading"
:style="{ width: '300px' }"
>
<a-option
v-for="user in userOptions"
@@ -190,8 +200,6 @@
{{ user.name || user.username }} ({{ user.username }})
</a-option>
</a-select>
</a-space>
</div>
</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
@@ -437,17 +473,25 @@ const handleSubmit = async () => {
// 生成派单规则
formData.dispatch_rule = generateDispatchRule()
const data = { ...formData }
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
}
// 编辑模式
if (isEdit.value && props.policyId) {
await updatePolicy({ id: props.policyId, ...data })
Message.success('策略更新成功')
} else {
// 新建模式
await createPolicy(data)
Message.success('策略创建成功')
}
emit('success')
handleCancel()
@@ -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>

View File

@@ -91,24 +91,30 @@
</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
v-model:visible="ruleManageVisible"
@@ -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
}
// 编辑策略