diff --git a/frontend/src/views/systemMonitor/diskMonitor/index.vue b/frontend/src/views/systemMonitor/diskMonitor/index.vue index 8bccb31..15ed61b 100644 --- a/frontend/src/views/systemMonitor/diskMonitor/index.vue +++ b/frontend/src/views/systemMonitor/diskMonitor/index.vue @@ -45,7 +45,14 @@ import DiskMonitorPolicyForm from './components/DiskMonitorPolicyForm.vue' import DiskMonitorSummary from './components/DiskMonitorSummary.vue' import DiskMonitorTargetDialog from './components/DiskMonitorTargetDialog.vue' import DiskMonitorTargetTable from './components/DiskMonitorTargetTable.vue' -import { createDefaultPolicy, createEmptyTarget, validatePolicy, validateTarget } from './utils/form' +import { + createDefaultPolicy, + createEmptyTarget, + normalizeTargetItem, + validatePolicy, + validateTarget, + validateTargetNotifications +} from './utils/form' defineOptions({ name: 'DiskMonitorPage' @@ -71,11 +78,14 @@ const handleBack = async () => { await router.push('/systemMonitor') } -const cloneTarget = (target: DiskMonitor.TargetItem): DiskMonitor.TargetItem => ({ - ...target, - notifyPathList: target.notifyPathList.map(item => ({ ...item })), - notifyHttpList: target.notifyHttpList.map(item => ({ ...item })) -}) +const cloneTarget = (target: DiskMonitor.TargetItem): DiskMonitor.TargetItem => { + const normalized = normalizeTargetItem(target) + return { + ...normalized, + notifyPathList: normalized.notifyPathList.map(item => ({ ...item })), + notifyHttpList: normalized.notifyHttpList.map(item => ({ ...item })) + } +} const openAddTarget = () => { editingTargetIndex.value = -1 @@ -86,23 +96,30 @@ const openAddTarget = () => { const openEditTarget = (row: DiskMonitor.TargetItem, index: number) => { editingTargetIndex.value = index // 编辑时克隆当前行,避免未确认前直接污染列表数据 - editingTarget.value = cloneTarget(row) + editingTarget.value = cloneTarget(normalizeTargetItem(row)) targetDialogVisible.value = true } const confirmTarget = () => { - // 提交前统一规范盘符并做去重、阈值关系校验 + // 提交前统一规范盘符并做去重、阈值与通知配置校验 const normalizedDriveLetter = editingTarget.value.driveLetter.trim().toUpperCase() const payload: DiskMonitor.TargetItem = { - ...editingTarget.value, + ...normalizeTargetItem(editingTarget.value), driveLetter: normalizedDriveLetter } const exists = targetList.value .filter((_, index) => index !== editingTargetIndex.value) .map(item => item.driveLetter.trim().toUpperCase()) - const errorMessage = validateTarget(payload, exists) - if (errorMessage) { - ElMessage.warning(errorMessage) + + const targetErrorMessage = validateTarget(payload, exists) + if (targetErrorMessage) { + ElMessage.warning(targetErrorMessage) + return + } + + const notifyErrorMessage = validateTargetNotifications(payload) + if (notifyErrorMessage) { + ElMessage.warning(notifyErrorMessage) return } @@ -125,7 +142,8 @@ const loadPolicyDetail = async () => { if (!detail) return policyForm.value = detail.policy || createDefaultPolicy() - targetList.value = detail.targets || [] + // 后端列表字段允许为空,这里统一归一化为数组,避免编辑器和克隆流程出现空引用 + targetList.value = (detail.targets || []).map(item => normalizeTargetItem(item)) } const loadLatestJob = async () => { @@ -144,7 +162,7 @@ const loadLatestJob = async () => { const loadPageData = async () => { loading.init = true try { - // 页面初始化时并行拉取策略和最近任务,保证摘要卡片和表单状态一致。 + // 页面初始化时并行拉取策略和最近任务,保证摘要卡片和表单状态一致 await Promise.all([loadPolicyDetail(), loadLatestJob()]) } finally { loading.init = false @@ -167,7 +185,7 @@ const handleSave = async () => { targets: targetList.value }) ElMessage.success('配置保存成功') - // 保存完成后重新拉取数据,避免本地状态与服务端策略偏差。 + // 保存完成后重新拉取数据,避免本地状态与服务端策略偏差 await loadPageData() } finally { loading.save = false @@ -183,7 +201,7 @@ const handleRun = async () => { jobSource: 'MANUAL' }) ElMessage.success('监控任务已启动') - // 手动触发任务后刷新摘要,展示最新任务状态。 + // 手动触发任务后刷新摘要,展示最新任务状态 await loadPageData() } finally { loading.run = false diff --git a/frontend/src/views/systemMonitor/diskMonitor/utils/form.ts b/frontend/src/views/systemMonitor/diskMonitor/utils/form.ts index 44fb03f..698eb49 100644 --- a/frontend/src/views/systemMonitor/diskMonitor/utils/form.ts +++ b/frontend/src/views/systemMonitor/diskMonitor/utils/form.ts @@ -52,3 +52,26 @@ export const validateTarget = (target: DiskMonitor.TargetItem, exists: string[]) if (target.alarmUsagePercent < target.warningUsagePercent) return '告警使用率不能小于预警使用率' return '' } + +export const validateTargetNotifications = (target: DiskMonitor.TargetItem) => { + if (target.notifyPathEnabled) { + const hasInvalidPath = (target.notifyPathList || []).some(item => !item.path?.trim()) + if (hasInvalidPath) return '路径通知目标路径不能为空' + } + + if (target.notifyHttpEnabled) { + const hasInvalidUrl = (target.notifyHttpList || []).some(item => { + const url = item.url?.trim() + return !url || !/^https?:\/\/\S+$/i.test(url) + }) + if (hasInvalidUrl) return 'HTTP 通知目标 URL 需要为有效的 HTTP/HTTPS 地址' + } + + return '' +} + +export const normalizeTargetItem = (target: DiskMonitor.TargetItem): DiskMonitor.TargetItem => ({ + ...target, + notifyPathList: Array.isArray(target.notifyPathList) ? target.notifyPathList : [], + notifyHttpList: Array.isArray(target.notifyHttpList) ? target.notifyHttpList : [] +})