@@ -1,188 +1,144 @@
< template >
< div class = "alert-tackle- container" >
< a-card :bordered = "false" class = "general-card" >
< search-form
v-model = "searchPara ms"
:form-items = "searchFormConfig "
:show-butto ns = "false "
>
< template # extra >
< a-range-picker
v-model = "timeRange "
: time -picker -props = " { defaultValue : ' 00 : 00 : 00 ' } "
forma t ="YYYY-MM-DD HH:mm:ss "
show -time
style = "width: 380px; margin-right: 12px "
/ >
< a-button type = "primary" @click ="handleSearch" >
< template # icon >
< icon-search / >
< / template >
查询
< / a-button >
< a-button @click ="handleReset" >
< template # icon >
< icon-refresh / >
< / template >
重置
< / a-button >
< / template >
< / search-form >
< a-divider style = "margin: 0" / >
< a-row class = "toolbar" >
< a-col :span = "12" >
< a-space >
< a-button
v-if = "selectedRowKeys.length > 0"
type = "primary"
status = "danger"
@click ="handleBatchAck"
>
批量确认 ( { { selectedRowKeys . length } } )
< / a-button >
< / a-space >
< div class = "container" >
< search-table
:form-model = "formModel"
:form-items = "formIte ms"
:data = "tableData "
:colum ns= "columns "
:loading = "loading"
:pagination = "pagination"
title = "告警受理处理"
search -button -text = " 查询 "
reset -button -text = " 重置 "
refresh -tooltip -tex t =" 刷新数据 "
density -tooltip -text = " 表格密度 "
column -setting -tooltip -text = " 列设置 "
@ update :form-model = "handleFormModelUpdate"
@search ="handleSearch"
@reset ="handleReset"
@ page -change = " handlePageChange "
@ page -size -change = " handlePageSizeChange "
@ row -click = " handleRowClick "
@refresh ="handleRefresh"
>
< template # form -items >
< a-col :span = "16" >
< a-form-item
label = "时间范围"
: label -col -props = " { span : 6 } "
: wrapper -col -props = " { span : 18 } "
>
< a-range-picker
v-model = "timeRange"
: time -picker -props = " { defaultValue : ' 00 : 00 : 00 ' } "
format = "YYYY-MM-DD HH:mm:ss"
show -time
style = "width: 100%; max-width: 420px"
/ >
< / a-form-item >
< / a-col >
< / a-row >
< / template >
< a-table
:data = "tableData"
:columns = "columns"
:loading = "loading"
:pagination = "pagination"
:row-selection = "rowSelection"
: scroll = "{ x: 2000 }"
@ page -change = " onPageChange "
@ page -size -change = " onPageSizeChange "
@ selection -change = " handleSelectionChange "
@ row -click = " handleRowClick "
>
< template # index = "{ rowIndex }" >
{ { rowIndex + 1 } }
< / template >
< template # index = "{ rowIndex }" >
{ { rowIndex + 1 + ( pagination . current - 1 ) * pagination . pageSize } }
< / template >
< template # status = "{ record }" >
< a-tag :color = "getStatusColor(record.status)" >
{ { getStatusText ( record . status ) } }
< / a-tag >
< / template >
< template # status = "{ record }" >
< a-tag :color = "getStatusColor(record.status)" >
{ { getStatusText ( record . status ) } }
< / a-tag >
< / template >
< template # severity = "{ record }" >
< a-tag v-if = "record.severity" :color="record.severity.color" >
{{ record.severity.name | | record.severity.code }}
< / a -tag >
< / template >
< template # severity = "{ record }" >
< a-tag v-if = "record.severity" :color="record.severity.color" >
{{ record.severity.name | | record.severity.code }}
< / a -tag >
< / template >
< template # starts_at = "{ record }" >
{ { formatDateTime ( record . starts _at ) } }
< / template >
< template # starts_at = "{ record }" >
{ { formatDateTime ( record . starts _at ) } }
< / template >
< template # duration = "{ record }" >
{ { formatDuration ( record . duration ) } }
< / template >
< template # duration = "{ record }" >
{ { formatDuration ( record . duration ) } }
< / template >
< template # process_status = "{ record }" >
< a-tag v-if = "record.process_status" color="arcoblue" >
{{ record.process_status }}
< / a -tag >
< span v-else > - < / span >
< / template >
< template # process_status = "{ record }" >
< a-tag v-if = "record.process_status" color="arcoblue" >
{{ record.process_status }}
< / a -tag >
< span v-else > - < / span >
< / template >
< template # processed_at = "{ record }" >
{ { record . processed _at ? formatDateTime ( record . processed _at ) : '-' } }
< / template >
< template # processed_at = "{ record }" >
{ { record . processed _at ? formatDateTime ( record . processed _at ) : '-' } }
< / template >
< template # notify_status = "{ record }" >
< a-tag v-if = "record.notify_status" :color="getNotifyStatusColor(record.notify_status)" >
{{ getNotifyStatusText ( record.notify_status ) }}
< / a -tag >
< span v-else > - < / span >
< / template >
< template # notify_status = "{ record }" >
< a-tag v-if = "record.notify_status" :color="getNotifyStatusColor(record.notify_status)" >
{{ getNotifyStatusText ( record.notify_status ) }}
< / a -tag >
< span v-else > - < / span >
< / template >
< template # labels = "{ record }" >
< a-space v-if = "parsed Labels(record.labels)" wrap >
< a-tag
v-for = "(value, key) in parsedLabels(record.labels)"
:key = "key"
size = "small"
>
{ { key } } : { { value } }
< / a-tag >
< / a-space >
< span v-else > - < / span >
< / template >
< template # labels = "{ record }" >
< a-tooltip v-if = "format LabelsLine (record.labels)" :content="formatLabelsLine(record.labels)" >
< span class = "cell-ellipsis" > { { formatLabelsLine ( record . labels ) } } < / span >
< / a-tooltip >
< span v-else > - < / span >
< / template >
< template # actions = "{ record }" >
< a-space >
< a-button type = "text" size = "small" @click ="handleAck(record)" >
< template # icon >
< icon-check / >
< / template >
确认
< template # actions = "{ record }" >
< a-space size = "small" :wrap = "false" >
< a-button type = "text" size = "small" @click.stop ="handleAck(record)" >
确认
< / a -button >
< a-button type = "text" size = "small" @click.stop ="handleResolve(record)" >
解决
< / a -button >
< a-button type = "text" size = "small" @click.stop ="handleSilence(record)" >
屏蔽
< / a -button >
< a-button type = "text" size = "small" @click.stop ="handleComment(record)" >
评论
< / a -button >
< a-dropdown @select ="(v) => handleMoreSelect(v, record)" >
< a -button type = "text" size = "small" @click.stop >
更多
< / a -button >
< a-button type = "text" size = "small" @click ="handleResolve(record)" >
< template # icon >
< icon-check-circle / >
< / template >
解决
< / a-button >
< a-button type = "text" size = "small" @click ="handleSilence(record)" >
< template # icon >
< icon-eye-invisible / >
< / template >
屏蔽
< / a-button >
< a-button type = "text" size = "small" @click ="handleComment(record)" >
< template # icon >
< icon-message / >
< / template >
评论
< / a-button >
< a-dropdown @select ="handleMoreAction($event, record)" >
< a -button type = "text" size = "small" >
更多
< icon-down / >
< / a-button >
< template # content >
< a-doption value = "detail" > 详情 < / a-doption >
< a-doption value = "process" > 处理记录 < / a-doption >
< / template >
< / a-dropdown >
< / a-space >
< / template >
< / a-table >
< / a-card >
< template # content >
< a-doption value = "detail" > 详情 < / a-doption >
< a-doption value = "process" > 处理记录 < / a-doption >
< / template >
< / a-dropdown >
< / a-space >
< / template >
< / search-table >
<!-- 确认对话框 -- >
< ack-dialog
v -model :visible = "ackDialogVisible"
:alert-record-id = "currentRecord.id"
@success ="handleSuccess"
/ >
<!-- 解决对话框 -- >
< resolve-dialog
v -model :visible = "resolveDialogVisible"
:alert-record-id = "currentRecord.id"
@success ="handleSuccess"
/ >
<!-- 屏蔽对话框 -- >
< silence-dialog
v -model :visible = "silenceDialogVisible"
:alert-record-id = "currentRecord.id"
@success ="handleSuccess"
/ >
<!-- 评论对话框 -- >
< comment-dialog
v -model :visible = "commentDialogVisible"
:alert-record-id = "currentRecord.id"
@success ="handleSuccess"
/ >
<!-- 详情对话框 -- >
< detail-dialog
v -model :visible = "detailDialogVisible"
:alert-record-id = "currentRecord.id"
@@ -193,21 +149,13 @@
< script lang = "ts" setup >
import { ref , reactive , computed , onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import {
IconSearch ,
IconRefresh ,
IconCheck ,
IconCheckCircle ,
IconEyeInvisible ,
IconMessage ,
IconDown ,
} from '@arco-design/web-vue/es/icon'
import { useRouter } from 'vue-router'
import SearchForm from '@/components/search-form/index.vu e'
import type { FormItem } from '@/components/search-form/types'
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interfac e'
import SearchTable from '@/components/search-table/index.vue'
import { searchFormConfig } from './config/search-form'
import { columns } from './config/columns'
import { fetchAlertRecords , createAlertProcess } from '@/api/ops/alertRecord'
import { fetchAlertRecords } from '@/api/ops/alertRecord'
import { fetchAlertLevelList } from '@/api/ops/alertLevel'
import AckDialog from './components/AckDialog.vue'
@@ -218,21 +166,17 @@ import DetailDialog from './components/DetailDialog.vue'
const router = useRouter ( )
// 状态管理
const loading = ref ( false )
const tableData = ref < any [ ] > ( [ ] )
const timeRange = ref < any [ ] > ( [ ] )
const selectedRowKeys = ref < number [ ] > ( [ ] )
const currentRecord = ref < any > ( { } )
// 对话框状态
const ackDialogVisible = ref ( false )
const resolveDialogVisible = ref ( false )
const silenceDialogVisible = ref ( false )
const commentDialogVisible = ref ( false )
const detailDialogVisible = ref ( false )
// 分页
const pagination = reactive ( {
current : 1 ,
pageSize : 20 ,
@@ -242,16 +186,23 @@ const pagination = reactive({
pageSizeOptions : [ '10' , '20' , '50' , '100' ] ,
} )
// 行选择
const rowSelection = computed ( ( ) => ( {
type : 'checkbox' ,
showCheckedAll : true ,
} ) )
const severityOptions = ref < SelectOptionData [ ] > ( [ ] )
// 搜索参数
const searchParams = ref < any > ( { } )
const formModel = ref < Record < string , any > > ( {
keyword : '' ,
status : '' ,
severity _id : '' ,
} )
const formItems = computed < FormItem [ ] > ( ( ) =>
searchFormConfig . map ( ( item ) => {
if ( item . field === 'severity_id' ) {
return { ... item , options : severityOptions . value }
}
return item
} ) ,
)
// 加载告警级别列表
onMounted ( async ( ) => {
await loadSeverityOptions ( )
handleSearch ( )
@@ -259,51 +210,58 @@ onMounted(async () => {
const loadSeverityOptions = async ( ) => {
try {
const result = await fetchAlertLevelList ( { page : 1 , page _size : 100 } )
const severityConfig = searchFormConfig . find ( ( item ) => item . field === 'severity_id' )
if ( severityConfig && resul t. details ) {
severityConfig . options = result . data . map ( ( item : any ) => ( {
label : item . name || item . code ,
value : item . id ,
} ) )
}
const res = await fetchAlertLevelList ( { page : 1 , page _size : 100 } )
const list = res . details ? . data ? ? ( res as any ) . data ? ? [ ]
severityOptions . value = lis t. map ( ( item : any ) => ( {
label : item . name || item . code ,
value : item . id ,
} ) )
} catch ( error ) {
console . error ( '加载告警级别失败:' , error )
}
}
// 搜索
const handleFormModelUpdate = ( value : Record < string , any > ) => {
formModel . value = value
}
const handleSearch = ( ) => {
pagination . current = 1
loadData ( )
}
const handleReset = ( ) => {
formModel . value = {
keyword : '' ,
status : '' ,
severity _id : '' ,
}
timeRange . value = [ ]
searchParams . value = { }
pagination . current = 1
loadData ( )
}
// 加载数据
const loadData = async ( ) => {
loading . value = true
try {
const params : any = {
page : pagination . current ,
page _size : pagination . pageSize ,
... searchParams . value ,
keyword : formModel . value . keyword || undefined ,
status : formModel . value . status || undefined ,
}
if ( formModel . value . severity _id !== '' && formModel . value . severity _id != null ) {
params . severity _id = formModel . value . severity _id
}
// 处理时间范围
if ( timeRange . value && timeRange . value . length === 2 ) {
params . start _time = new Date ( timeRange . value [ 0 ] ) . toISOString ( )
params . end _time = new Date ( timeRange . value [ 1 ] ) . toISOString ( )
}
const result = await fetchAlertRecords ( params )
tableData . value = result . details . data || [ ]
pagination . total = result . details . total || 0
tableData . value = result . details ? . data || [ ]
pagination . total = result . details ? . total || 0
} catch ( error ) {
console . error ( '加载数据失败:' , error )
Message . error ( '加载数据失败' )
@@ -312,30 +270,22 @@ const loadData = async () => {
}
}
// 分页
const onPageChange = ( page : number ) => {
const handlePageChange = ( page : number ) => {
pagination . current = page
loadData ( )
}
const on PageSizeChange = ( pageSize : number ) => {
const handle PageSizeChange = ( pageSize : number ) => {
pagination . pageSize = pageSize
pagination . current = 1
loadData ( )
}
// 行选择
const handleSelectionChange = ( rowKeys : number [ ] ) => {
selectedRowKeys . value = rowKeys
}
// 行点击
const handleRowClick = ( record : any ) => {
const handleRowClick = ( record : any , _ev : Event ) => {
currentRecord . value = record
detailDialogVisible . value = true
}
// 处理操作
const handleAck = ( record : any ) => {
currentRecord . value = record
ackDialogVisible . value = true
@@ -356,28 +306,6 @@ const handleComment = (record: any) => {
commentDialogVisible . value = true
}
// 批量确认
const handleBatchAck = async ( ) => {
try {
const promises = selectedRowKeys . value . map ( ( id ) =>
createAlertProcess ( {
alert _record _id : id ,
action : 'ack' ,
operator : getCurrentUser ( ) ,
comment : '批量确认' ,
} )
)
await Promise . all ( promises )
Message . success ( ` 成功确认 ${ selectedRowKeys . value . length } 条告警 ` )
selectedRowKeys . value = [ ]
loadData ( )
} catch ( error ) {
console . error ( '批量确认失败:' , error )
Message . error ( '批量确认失败' )
}
}
// 更多操作
const handleMoreAction = ( action : string , record : any ) => {
currentRecord . value = record
switch ( action ) {
@@ -393,12 +321,22 @@ const handleMoreAction = (action: string, record: any) => {
}
}
// 操作成功回调
const handleMoreSelect = (
value : string | number | Record < string , unknown > | undefined ,
record : any ,
) => {
handleMoreAction ( String ( value ) , record )
}
const handleSuccess = ( ) => {
loadData ( )
}
// 格式化函数
const handleRefresh = ( ) => {
loadData ( )
Message . success ( '数据已刷新' )
}
const formatDateTime = ( datetime : string ) => {
if ( ! datetime ) return '-'
return new Date ( datetime ) . toLocaleString ( 'zh-CN' )
@@ -424,7 +362,15 @@ const parsedLabels = (labels: string) => {
}
}
// 状态相关
/** 单行展示,避免 a-space wrap + 多 tag 撑高整行 td( 与其它告警列表页行高一致) */
const formatLabelsLine = ( labels : string ) => {
const obj = parsedLabels ( labels )
if ( ! obj || typeof obj !== 'object' ) return ''
return Object . entries ( obj as Record < string , unknown > )
. map ( ( [ k , v ] ) => ` ${ k } : ${ v == null ? '' : String ( v ) } ` )
. join ( ', ' )
}
const getStatusColor = ( status : string ) => {
const colorMap : Record < string , string > = {
firing : 'red' ,
@@ -466,23 +412,35 @@ const getNotifyStatusText = (status: string) => {
}
return textMap [ status ] || status
}
< / script >
function getCurrentUser ( ) {
// TODO: 从全局状态获取当前用户
return 'admin'
< script lang = "ts" >
export default {
name : 'AlertTackle' ,
}
< / script >
< style scoped lang = "less" >
. alert - tackle - container {
padding : 20 px ;
. container {
margin - top : 20 px ;
. general - card {
padding : 20 px ;
/* 与未设置 table scroll 的告警列表页一致行高;宽表在表格外层横向滚动,避免 Arco scroll 模式改变单元格高度 */
: deep ( . search - table - container . data - table ) {
overflow - x : auto ;
}
. toolbar {
padding : 16 px 0 ;
/* 标签列单行省略,与其它页纯文本单元格行高一致 */
. cell - ellipsis {
display : block ;
overflow : hidden ;
text - overflow : ellipsis ;
white - space : nowrap ;
max - width : 200 px ;
}
/* 与其它告警 SearchTable 页一致:正文单元格垂直居中 */
: deep ( . arco - table - size - medium . arco - table - td ) {
vertical - align : middle ;
}
}
< / style >