包含前后端完整代码: - 前端:Vue3+Vite+ElementPlus - 后端:FastAPI+SQLAlchemy - 模块:驾驶舱/KPI/战略地图/预警/预算/成本/预测/改善行动 - 当前版本:v1.0.0
293 lines
11 KiB
Vue
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>
|