Files
cma-management/frontend/src/views/AlertList.vue
T
Hermes CI Fix 3dddd36866 init: 管理会计OS初始代码
包含前后端完整代码:
- 前端:Vue3+Vite+ElementPlus
- 后端:FastAPI+SQLAlchemy
- 模块:驾驶舱/KPI/战略地图/预警/预算/成本/预测/改善行动
- 当前版本:v1.0.0
2026-05-28 17:32:22 +08:00

293 lines
11 KiB
Vue

<template>
<div>
<h3>预警中心</h3>
<div style="margin:12px 0;display:flex;gap:12px;">
<el-radio-group v-model="activeTab" size="small">
<el-radio-button value="alerts">待处理预警</el-radio-button>
<el-radio-button value="plans">改善行动</el-radio-button>
</el-radio-group>
</div>
<!-- ===== 预警列表 ===== -->
<template v-if="activeTab === 'alerts'">
<el-table :data="alerts" v-loading="loading" style="width:100%" border stripe>
<el-table-column prop="alert_message" label="预警信息" min-width="300" />
<el-table-column prop="alert_level" label="级别" width="70">
<template #default="{ row }">
<el-tag :type="row.alert_level==='red'?'danger':'warning'" size="small">
{{ row.alert_level==='red'?'🔴紧急':'🟡警告' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="90">
<template #default="{ row }">{{ {pending:'待处理',processing:'处理中',resolved:'已处理'}[row.status] || row.status }}</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button v-if="row.status==='pending'" size="small" type="primary" @click="openResolveDialog(row)">处理</el-button>
<el-button v-if="row.status==='pending'" size="small" @click="openPlanDialog(row)">创建改善计划</el-button>
</template>
</el-table-column>
</el-table>
</template>
<!-- ===== 改善行动计划 ===== -->
<template v-if="activeTab === 'plans'">
<div style="margin-bottom:12px;">
<el-button type="primary" size="small" @click="showPlanForm = true; planForm = { title: '', kpi_id: null, assignee: '', priority: 'medium', description: '' }">新建改善计划</el-button>
</div>
<el-table :data="plans" v-loading="loadingPlans" style="width:100%" border stripe>
<el-table-column prop="title" label="计划名称" min-width="200" />
<el-table-column prop="kpi_name" label="关联KPI" width="140" />
<el-table-column prop="assignee" label="负责人" width="100" />
<el-table-column prop="priority" label="优先级" width="80">
<template #default="{ row }">
<el-tag :type="row.priority==='high'?'danger':row.priority==='medium'?'warning':'info'" size="small">
{{ {high:'高',medium:'中',low:'低'}[row.priority] || row.priority }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status==='completed'?'success':row.status==='in_progress'?'warning':'info'" size="small">
{{ {pending:'待开始',in_progress:'进行中',completed:'已完成',cancelled:'已取消'}[row.status] || row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="progress" label="进度" width="120">
<template #default="{ row }">
<el-progress :percentage="row.progress || 0" :status="row.progress >= 100 ? 'success' : ''" :stroke-width="12" />
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="editPlan(row)">编辑</el-button>
<el-button size="small" type="danger" @click="deletePlan(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<!-- 处理预警弹窗 -->
<el-dialog v-model="showDialog" title="处理预警" width="500px">
<el-form :model="resolveForm" label-width="100px">
<el-form-item label="处理人">
<el-select v-model="resolveForm.assignee" filterable style="width:100%;">
<el-option v-for="u in users" :key="u.id" :label="u.name" :value="u.name" />
</el-select>
</el-form-item>
<el-form-item label="处理结果">
<el-input v-model="resolveForm.resolution" type="textarea" :rows="3" placeholder="描述处理结果" />
</el-form-item>
<el-form-item label="创建改善计划">
<el-checkbox v-model="resolveForm.createPlan">同时创建改善行动计划</el-checkbox>
</el-form-item>
<template v-if="resolveForm.createPlan">
<el-form-item label="计划名称">
<el-input v-model="resolveForm.planTitle" placeholder="如:制定客户拓展方案" />
</el-form-item>
<el-form-item label="截止日期">
<el-date-picker v-model="resolveForm.planDueDate" type="date" placeholder="选择截止日期" style="width:100%;" />
</el-form-item>
</template>
</el-form>
<template #footer>
<el-button @click="showDialog=false">取消</el-button>
<el-button type="primary" @click="doResolve" :loading="resolving">确认处理</el-button>
</template>
</el-dialog>
<!-- 新建/编辑改善计划弹窗 -->
<el-dialog v-model="showPlanForm" :title="editingPlan ? '编辑改善计划' : '新建改善计划'" width="550px">
<el-form :model="planForm" label-width="100px">
<el-form-item label="计划名称"><el-input v-model="planForm.title" /></el-form-item>
<el-form-item label="关联KPI">
<el-select v-model="planForm.kpi_id" filterable style="width:100%;">
<el-option v-for="k in kpiOptions" :key="k.id" :label="k.kpi_name" :value="k.id" />
</el-select>
</el-form-item>
<el-form-item label="负责人">
<el-select v-model="planForm.assignee" filterable style="width:100%;">
<el-option v-for="u in users" :key="u.id" :label="u.name" :value="u.name" />
</el-select>
</el-form-item>
<el-form-item label="优先级">
<el-select v-model="planForm.priority">
<el-option label="高" value="high" />
<el-option label="中" value="medium" />
<el-option label="低" value="low" />
</el-select>
</el-form-item>
<el-form-item label="描述">
<el-input v-model="planForm.description" type="textarea" :rows="3" />
</el-form-item>
<el-form-item label="截止日期">
<el-date-picker v-model="planForm.due_date" type="date" placeholder="选择截止日期" style="width:100%;" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showPlanForm=false">取消</el-button>
<el-button type="primary" @click="savePlan" :loading="savingPlan">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { alertApi, userApi, actionPlanApi, kpiApi } from '../api/index'
const route = useRoute()
const activeTab = ref((route.meta?.tab as string) || 'alerts')
// 预警
const alerts = ref<any[]>([])
const loading = ref(false)
const showDialog = ref(false)
const currentAlert = ref<any>(null)
const resolving = ref(false)
const resolveForm = ref({
assignee: '', resolution: '',
createPlan: false, planTitle: '', planDueDate: null,
})
const users = ref<any[]>([])
// 改善计划
const plans = ref<any[]>([])
const loadingPlans = ref(false)
const showPlanForm = ref(false)
const editingPlan = ref<any>(null)
const savingPlan = ref(false)
const kpiOptions = ref<any[]>([])
const planForm = ref<any>({
title: '', kpi_id: null, assignee: '', priority: 'medium',
description: '', due_date: null,
})
function openResolveDialog(row: any) {
currentAlert.value = row
resolveForm.value = {
assignee: '', resolution: '',
createPlan: false, planTitle: `${row.alert_message?.slice(0, 20) || '改善'}方案`,
planDueDate: null,
}
showDialog.value = true
}
function openPlanDialog(row: any) {
// 从预警创建改善计划
const kpiId = row.kpi_id || null
showPlanForm.value = true
editingPlan.value = null
planForm.value = {
title: `${row.alert_message?.slice(0, 30) || '改善'}方案`,
kpi_id: kpiId,
assignee: '',
priority: 'high',
description: row.alert_message || '',
due_date: null,
}
}
async function doResolve() {
if (!resolveForm.value.assignee) { ElMessage.warning('请选择处理人'); return }
resolving.value = true
try {
await alertApi.resolve(currentAlert.value.id, {
assignee: resolveForm.value.assignee,
resolution: resolveForm.value.resolution,
})
// 如果勾选了创建改善计划
if (resolveForm.value.createPlan && resolveForm.value.planTitle) {
await actionPlanApi.create({
title: resolveForm.value.planTitle,
kpi_id: currentAlert.value.kpi_id || 1,
alert_id: currentAlert.value.id,
description: resolveForm.value.resolution || '',
assignee: resolveForm.value.assignee,
priority: 'high',
due_date: resolveForm.value.planDueDate || null,
})
}
ElMessage.success('已处理')
showDialog.value = false
load()
} catch (e) { ElMessage.error('操作失败') }
resolving.value = false
}
async function savePlan() {
if (!planForm.value.title || !planForm.value.kpi_id) {
ElMessage.warning('请填写计划名称并选择关联KPI')
return
}
savingPlan.value = true
try {
const data = { ...planForm.value }
if (data.due_date) data.due_date = new Date(data.due_date).toISOString()
if (editingPlan.value) {
await actionPlanApi.update(editingPlan.value.id, data)
ElMessage.success('已更新')
} else {
await actionPlanApi.create(data)
ElMessage.success('已创建')
}
showPlanForm.value = false
loadPlans()
} catch (e) { ElMessage.error('保存失败') }
savingPlan.value = false
}
function editPlan(row: any) {
editingPlan.value = row
planForm.value = {
title: row.title,
kpi_id: row.kpi_id,
assignee: row.assignee,
priority: row.priority,
description: row.description,
due_date: row.due_date || null,
}
showPlanForm.value = true
}
async function deletePlan(id: number) {
try {
await ElMessageBox.confirm('确定删除此改善计划?')
await actionPlanApi.delete(id)
ElMessage.success('已删除')
loadPlans()
} catch (e) {
if (e !== 'cancel') ElMessage.error('删除失败')
}
}
async function load() {
loading.value = true
try { const r: any = await alertApi.list(); alerts.value = r.data || [] } catch (e) {}
try { const u: any = await userApi.list(); users.value = u.data || [] } catch (e) {}
loading.value = false
}
async function loadPlans() {
loadingPlans.value = true
try { const r: any = await actionPlanApi.list(); plans.value = r.data || [] } catch (e) {}
loadingPlans.value = false
}
async function loadKpis() {
try { const r: any = await kpiApi.list(); kpiOptions.value = r.data || [] } catch (e) {}
}
onMounted(() => {
load()
loadPlans()
loadKpis()
})
</script>