ソースを参照

fix:应急指挥-模拟演练对接接口

null 3 日 前
コミット
3af817b9d7

+ 40 - 0
src/api/pipeNetwork/basic.js

@@ -151,6 +151,46 @@ export function getPressureAlarmHistory(pageNum, pageSize, startDate, endDate, d
 export function getPressureAlarmStats() { return request({ url: '/api/pressure-alarm/stats', method: 'get' }) }
 export function resolvePressureAlarm(id) { return request({ url: '/api/pressure-alarm/resolve/' + id, method: 'put' }) }
 export function dispatchPressureAlarm(id) { return request({ url: '/api/pressure-alarm/dispatch/' + id, method: 'post' }) }
+// 培训课程
+export function getCourseList(params) { return request({ url: '/api/drill/course/list', method: 'get', params }) }
+export function saveCourse(data) { return request({ url: '/api/drill/course', method: 'post', data }) }
+export function updateCourse(data) { return request({ url: '/api/drill/course', method: 'put', data }) }
+export function deleteCourse(id) { return request({ url: '/api/drill/course/' + id, method: 'delete' }) }
+// 演练计划
+export function getDrillPlanList(params) { return request({ url: '/api/drill/plan/list', method: 'get', params }) }
+export function saveDrillPlan(data) { return request({ url: '/api/drill/plan', method: 'post', data }) }
+export function updateDrillPlan(data) { return request({ url: '/api/drill/plan', method: 'put', data }) }
+export function deleteDrillPlan(id) { return request({ url: '/api/drill/plan/' + id, method: 'delete' }) }
+// 实战演练
+// 实战演练模板
+export function getPracticalTemplates() { return request({ url: '/api/drill/practical/template', method: 'get' }) }
+export function savePracticalTemplate(data) { return request({ url: '/api/drill/practical/template', method: 'post', data }) }
+export function updatePracticalTemplate(data) { return request({ url: '/api/drill/practical/template', method: 'put', data }) }
+export function deletePracticalTemplate(id) { return request({ url: '/api/drill/practical/template/' + id, method: 'delete' }) }
+// 实战演练计划
+export function getPracticalPlans() { return request({ url: '/api/drill/practical/plan', method: 'get' }) }
+export function savePracticalPlan(data) { return request({ url: '/api/drill/practical/plan', method: 'post', data }) }
+export function updatePracticalPlan(data) { return request({ url: '/api/drill/practical/plan', method: 'put', data }) }
+export function deletePracticalPlan(id) { return request({ url: '/api/drill/practical/plan/' + id, method: 'delete' }) }
+// 模拟演练
+export function getSimProjects() { return request({ url: '/api/drill/sim/project', method: 'get' }) }
+export function saveSimProject(data) { return request({ url: '/api/drill/sim/project', method: 'post', data }) }
+export function deleteSimProject(id) { return request({ url: '/api/drill/sim/project/' + id, method: 'delete' }) }
+export function getSimScene(pid) { return request({ url: '/api/drill/sim/scene/' + pid, method: 'get' }) }
+export function getSimEvent(pid) { return request({ url: '/api/drill/sim/event/' + pid, method: 'get' }) }
+export function getSimRole(pid) { return request({ url: '/api/drill/sim/role/' + pid, method: 'get' }) }
+export function getSimTask(pid) { return request({ url: '/api/drill/sim/task/' + pid, method: 'get' }) }
+export function getSimLog(pid) { return request({ url: '/api/drill/sim/log/' + pid, method: 'get' }) }
+export function getSimResult(pid) { return request({ url: '/api/drill/sim/result/' + pid, method: 'get' }) }
+export function addSimLog(data) { return request({ url: '/api/drill/sim/log', method: 'post', data }) }
+export function processSimDrill(projectId, action, category) { return request({ url: '/api/drill/sim/process', method: 'post', params: { projectId, action, category } }) }
+export function saveSimScene(data) { return request({ url: '/api/drill/sim/scene', method: 'post', data }) }
+export function deleteSimScene(id) { return request({ url: '/api/drill/sim/scene/' + id, method: 'delete' }) }
+export function saveSimEvent(data) { return request({ url: '/api/drill/sim/event', method: 'post', data }) }
+export function deleteSimEvent(id) { return request({ url: '/api/drill/sim/event/' + id, method: 'delete' }) }
+export function saveSimRole(data) { return request({ url: '/api/drill/sim/role', method: 'post', data }) }
+export function saveSimTask(data) { return request({ url: '/api/drill/sim/task', method: 'post', data }) }
+export function getDrillStats() { return request({ url: '/api/drill/stats', method: 'get' }) }
 export function getRepairRecordById(id) {
   return request({ url: '/api/repair-record/getById/' + id, method: 'get' })
 }

+ 157 - 116
src/views/subSystem/dispatch/disExerciseMan/DIsSimulationDrill.vue

@@ -1,5 +1,6 @@
 <!--模拟演练管理-->
 <template>
+  <div class="sim-wrap">
   <div class="app-container">
     <div class="sim-layout">
       <!-- ===== 左列 ===== -->
@@ -11,7 +12,7 @@
             <el-button type="primary" size="small" @click="handleAddProject">新增</el-button>
           </div>
           <div class="panel-body">
-            <div class="project-item" v-for="item in projects" :key="item.id">
+            <div class="project-item" v-for="item in projects" :key="item.id" :class="{ selected: activeProjectId === item.id }" @click="selectProject(item)">
               <div class="project-info">
                 <div class="project-name">{{ item.name }}</div>
                 <div class="project-date">{{ item.date }}</div>
@@ -149,147 +150,185 @@
           </div>
           <div class="panel-body">
             <div class="metric-grid">
-              <div class="metric-item">
-                <div class="metric-label">任务完成率</div>
-                <div class="metric-value">33%</div>
-              </div>
-              <div class="metric-item">
-                <div class="metric-label">平均响应时间</div>
-                <div class="metric-value">30s</div>
-              </div>
-              <div class="metric-item">
-                <div class="metric-label">应急评估等级</div>
-                <div class="metric-value highlight">良好</div>
-              </div>
-              <div class="metric-item">
-                <div class="metric-label">建议</div>
-                <div class="metric-value small">加强指挥调度协同</div>
-              </div>
-            </div>
-            <div class="stat-cards">
-              <div class="stat-card">
-                <div class="stat-title">应急统计</div>
-                <div class="stat-content">事件响应时间 28秒</div>
-              </div>
-              <div class="stat-card">
-                <div class="stat-title">应急评估</div>
-                <div class="stat-content">任务完成率 67%</div>
-              </div>
+              <div class="metric-item"><div class="metric-label">任务完成率</div><div class="metric-value">{{ simResultData.task_completion_rate || '--' }}</div></div>
+              <div class="metric-item"><div class="metric-label">平均响应时间</div><div class="metric-value">{{ simResultData.avg_response_time || '--' }}</div></div>
+              <div class="metric-item"><div class="metric-label">应急评估等级</div><div class="metric-value highlight">{{ simResultData.emergency_level || '--' }}</div></div>
+              <div class="metric-item"><div class="metric-label">建议</div><div class="metric-value small">{{ simResultData.suggestion || '暂无' }}</div></div>
             </div>
           </div>
         </div>
       </div>
     </div>
   </div>
+
+  <!-- 项目表单弹窗 -->
+  <el-dialog v-model="projDialogVisible" title="演练项目" width="450px" destroy-on-close>
+    <el-form :model="projForm" label-width="80px">
+      <el-form-item label="项目名称"><el-input v-model="projForm.name" /></el-form-item>
+      <el-form-item label="所属系统">
+        <el-select v-model="projForm.category" style="width:100%">
+          <el-option label="燃气" value="燃气" /><el-option label="排水" value="排水" /><el-option label="供水" value="供水" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer><el-button @click="projDialogVisible=false">取消</el-button><el-button type="primary" @click="handleProjSubmit">确定</el-button></template>
+  </el-dialog>
+
+  <!-- 场景编辑弹窗 -->
+  <el-dialog v-model="sceneDialogVisible" title="场景编辑" width="450px" destroy-on-close>
+    <el-input v-model="sceneNewTag" placeholder="输入新场景元素" style="width:70%"><template #append><el-button @click="addSceneTag()">添加</el-button></template></el-input>
+    <div style="margin-top:12px"><el-tag v-for="t in currentSceneTags" :key="t" closable @close="removeSceneTag(t)" style="margin:4px">{{t}}</el-tag></div>
+    <template #footer><el-button @click="sceneDialogVisible=false">关闭</el-button></template>
+  </el-dialog>
+
+  <!-- 事件弹窗 -->
+  <el-dialog v-model="eventDialogVisible" title="新增事件" width="450px" destroy-on-close>
+    <el-form :model="eventForm" label-width="80px">
+      <el-form-item label="事件名称"><el-input v-model="eventForm.name" /></el-form-item>
+      <el-form-item label="事件描述"><el-input v-model="eventForm.description" type="textarea" :rows="3" /></el-form-item>
+    </el-form>
+    <template #footer><el-button @click="eventDialogVisible=false">取消</el-button><el-button type="primary" @click="handleEventSubmit">确定</el-button></template>
+  </el-dialog>
+
+  <!-- 角色弹窗 -->
+  <el-dialog v-model="roleDialogVisible" title="新增角色" width="450px" destroy-on-close>
+    <el-form :model="roleForm" label-width="80px">
+      <el-form-item label="角色名称"><el-input v-model="roleForm.name" /></el-form-item>
+      <el-form-item label="角色类型"><el-select v-model="roleForm.roleType" style="width:100%"><el-option label="指挥类" value="指挥类"/><el-option label="救援类" value="救援类"/><el-option label="观摩类" value="观摩类"/></el-select></el-form-item>
+      <el-form-item label="技能"><el-input v-model="roleForm.skills" /></el-form-item>
+      <el-form-item label="装备"><el-input v-model="roleForm.equipment" /></el-form-item>
+    </el-form>
+    <template #footer><el-button @click="roleDialogVisible=false">取消</el-button><el-button type="primary" @click="handleRoleSubmit">确定</el-button></template>
+  </el-dialog>
+
+  <!-- 任务弹窗 -->
+  <el-dialog v-model="taskDialogVisible" title="新增任务" width="450px" destroy-on-close>
+    <el-form :model="taskForm" label-width="80px">
+      <el-form-item label="任务名称"><el-input v-model="taskForm.name" /></el-form-item>
+      <el-form-item label="任务描述"><el-input v-model="taskForm.description" type="textarea" :rows="3" /></el-form-item>
+      <el-form-item label="绑定角色"><el-select v-model="taskForm.bindRoleId" style="width:100%"><el-option v-for="r in roles" :key="r.id" :label="r.name" :value="r.id"/></el-select></el-form-item>
+    </el-form>
+    <template #footer><el-button @click="taskDialogVisible=false">取消</el-button><el-button type="primary" @click="handleTaskSubmit">确定</el-button></template>
+  </el-dialog>
+  </div>
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { ref, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import {
   Folder, Edit, Delete, PictureFilled, Warning, User, List,
   VideoPlay, Promotion, Document, DataAnalysis
 } from '@element-plus/icons-vue'
+import { getSimProjects, saveSimProject, deleteSimProject, addSimLog, processSimDrill, getSimScene, getSimEvent, getSimRole, getSimTask, getSimLog, getSimResult, saveSimScene, deleteSimScene, saveSimEvent, deleteSimEvent, saveSimRole, saveSimTask } from '@/api/pipeNetwork/basic'
 
-// ---------- 演练项目 ----------
-let projectId = 3
-const projects = ref([
-  { id: 1, name: '化工园区毒气泄漏演练', date: '2025-01-01' },
-  { id: 2, name: '海上溢油应急联合演练', date: '2025-01-01' },
-  { id: 3, name: '化工园区综合应急演练', date: '2025-03-15' }
-])
-
+const projects = ref([])
 const projectSearch = ref('')
-const handleSearchProject = () => { ElMessage.success('查询项目(Mock)') }
-const handleAddProject = () => { ElMessage.success('新增演练项目(Mock)') }
-const handleEditProject = (item) => { ElMessage.success(`编辑项目: ${item.name}`) }
-const handleDeleteProject = (item) => {
-  ElMessageBox.confirm('确认删除该项目?', '提示', { type: 'warning' }).then(() => {
-    projects.value = projects.value.filter(p => p.id !== item.id)
-    ElMessage.success('删除成功')
-  }).catch(() => {})
+const handleSearchProject = () => { loadData() }
+// 项目弹窗
+const projDialogVisible = ref(false)
+const projForm = ref({ id: null, name: '', category: '燃气' })
+const handleAddProject = () => { projForm.value = { id: null, name: '', category: '燃气' }; projDialogVisible.value = true }
+const handleEditProject = async (item) => { projForm.value = { ...item }; projDialogVisible.value = true }
+const handleDeleteProject = (item) => { ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' }).then(async () => { await deleteSimProject(item.id); ElMessage.success('删除成功'); loadData() }).catch(() => {}) }
+const handleProjSubmit = async () => {
+  const d = projForm.value
+  try {
+    if (d.id) { const p = projects.value.find(x => x.id === d.id); if (p) { p.name = d.name; p.category = d.category } }
+    else { await saveSimProject({ name: d.name, category: d.category, createDate: new Date().toISOString().substring(0,10) }); }
+    ElMessage.success('保存成功'); projDialogVisible.value = false; loadData()
+  } catch (e) { ElMessage.error('保存失败') }
 }
 
-// ---------- 场景设置 ----------
-const currentSceneTags = ref(['控制室', '泄漏点', '氨气泄露', '火灾蔓延'])
-const sceneLibrary = ref(['控制室', '泄漏点', '储罐区', '氨气泄露', '火灾蔓延', '风向标'])
-
-const addSceneTag = (tag) => {
-  if (!currentSceneTags.value.includes(tag)) {
-    currentSceneTags.value.push(tag)
-  }
+// 选中项目时加载子数据
+const activeProjectId = ref(null)
+const activeCategory = ref('')
+
+// 场景
+const currentSceneTags = ref([])
+const sceneLibrary = ref([])
+const sceneDialogVisible = ref(false)
+const sceneNewTag = ref('')
+let sceneDataCache = []
+async function loadScene(id) { try { const r = await getSimScene(id); sceneDataCache = r.data||[]; sceneLibrary.value = sceneDataCache.map(s => s.tagName); currentSceneTags.value = sceneDataCache.filter(s => s.isActive===1).map(s => s.tagName) } catch (e) {} }
+const addSceneTag = async (tag) => {
+  const t = String(tag || sceneNewTag.value || '').trim(); if (!t) return
+  if (currentSceneTags.value.includes(t)) { ElMessage.warning('已存在'); return }
+  try {
+    await saveSimScene({ projectId: activeProjectId.value, tagName: t, isActive: 1 })
+    sceneNewTag.value = ''; loadScene(activeProjectId.value)
+  } catch (e) { ElMessage.error('添加失败') }
 }
-const removeSceneTag = (tag) => {
-  currentSceneTags.value = currentSceneTags.value.filter(t => t !== tag)
+const removeSceneTag = async (tag) => {
+  const existing = sceneDataCache.find(s => s.tagName === tag && s.isActive === 1)
+  if (existing) { try { await deleteSimScene(existing.id); loadScene(activeProjectId.value) } catch (e) { ElMessage.error('删除失败') } }
+  else { currentSceneTags.value = currentSceneTags.value.filter(t => t !== tag) }
 }
-const handleEditScene = () => { ElMessage.success('编辑场景(Mock)') }
-
-// ---------- 事件设置 ----------
-const events = ref([
-  { id: 1, name: '氯气泄漏', desc: '突发事件: 储罐区发生泄漏,需紧急处置' },
-  { id: 2, name: '火灾蔓延', desc: '次生灾害: 爆炸引起火灾' }
-])
-
-const handleAddEvent = () => { ElMessage.success('新增事件(Mock)') }
-const addToScene = (evt) => { ElMessage.success(`已将"${evt.name}"添加到场景`) }
-
-// ---------- 角色设置 ----------
-const roles = ref([
-  { id: 1, name: '现场指挥员', type: '指挥类', skills: '消防指挥、泄漏处置', equipment: '防护服、指挥终端' },
-  { id: 2, name: '消防队员', type: '救援类', skills: '灭火、堵漏', equipment: '防护服、灭火器' }
-])
-
-const handleAddRole = () => { ElMessage.success('新增角色(Mock)') }
-
-// ---------- 任务设置 ----------
-const tasks = ref([
-  { id: 1, name: '启动应急响应', desc: '指挥员需下达疏散命令', bindRole: '现场指挥员' },
-  { id: 2, name: '演练整体评估', desc: '完成评价报告', bindRole: '现场指挥员' }
-])
-
-const handleAddTask = () => { ElMessage.success('新增任务(Mock)') }
-
-// ---------- 过程管理 ----------
-const eventLogs = ref([
-  { time: '16:11:05', text: '演练启动' },
-  { time: '16:11:06', text: '演练暂停' },
-  { time: '16:11:07', text: '触发事件: 氯气泄漏 - 影响范围扩大,角色已分配' },
-  { time: '16:11:10', text: '触发事件: 氯气泄漏 - 影响范围进一步扩大' }
-])
-
-const startDrill = () => {
-  const now = new Date()
-  const time = now.toTimeString().slice(0, 8)
-  eventLogs.value.push({ time, text: '演练启动' })
-  ElMessage.success('演练已启动')
+const handleEditScene = () => { sceneNewTag.value = ''; sceneDialogVisible.value = true }
+
+// 事件
+const events = ref([])
+const eventDialogVisible = ref(false)
+const eventForm = ref({ name: '', description: '' })
+async function loadEvent(id) { try { const r = await getSimEvent(id); events.value = (r.data||[]).map(e => ({ id: e.id, name: e.name, desc: e.description })) } catch (e) {} }
+const handleAddEvent = () => { eventForm.value = { name: '', description: '' }; eventDialogVisible.value = true }
+const handleEventSubmit = async () => {
+  try { await saveSimEvent({ projectId: activeProjectId.value, name: eventForm.value.name, description: eventForm.value.description }); ElMessage.success('新增成功'); eventDialogVisible.value = false; loadEvent(activeProjectId.value) } catch (e) { ElMessage.error('新增失败') }
 }
-const pauseDrill = () => {
-  const now = new Date()
-  const time = now.toTimeString().slice(0, 8)
-  eventLogs.value.push({ time, text: '演练暂停' })
-  ElMessage.warning('演练已暂停')
+const addToScene = async (evt) => {
+  const existing = sceneDataCache.find(s => s.tagName === evt.name)
+  if (existing) {
+    try { await saveSimScene({ id: existing.id, projectId: activeProjectId.value, tagName: evt.name, isActive: 1 }); loadScene(activeProjectId.value); ElMessage.success(`已将"${evt.name}"添加到场景`) } catch (e) {}
+  } else {
+    try { await saveSimScene({ projectId: activeProjectId.value, tagName: evt.name, isActive: 1 }); loadScene(activeProjectId.value); ElMessage.success(`已将"${evt.name}"添加到场景`) } catch (e) {}
+  }
 }
-const exitDrill = () => {
-  ElMessageBox.confirm('确认退出演练?', '提示', { type: 'warning' }).then(() => {
-    const now = new Date()
-    const time = now.toTimeString().slice(0, 8)
-    eventLogs.value.push({ time, text: '演练退出' })
-    ElMessage.info('演练已退出')
-  }).catch(() => {})
+
+// 角色
+const roles = ref([])
+const roleDialogVisible = ref(false)
+const roleForm = ref({ name: '', roleType: '指挥类', skills: '', equipment: '' })
+async function loadRole(id) { try { const r = await getSimRole(id); roles.value = (r.data||[]).map(rr => ({ id: rr.id, name: rr.name, type: rr.roleType, skills: rr.skills, equipment: rr.equipment })) } catch (e) {} }
+const handleAddRole = () => { roleForm.value = { name: '', roleType: '指挥类', skills: '', equipment: '' }; roleDialogVisible.value = true }
+const handleRoleSubmit = async () => {
+  try { await saveSimRole({ projectId: activeProjectId.value, ...roleForm.value }); ElMessage.success('新增成功'); roleDialogVisible.value = false; loadRole(activeProjectId.value) } catch (e) { ElMessage.error('新增失败') }
 }
 
-const triggerEvent = () => {
-  const now = new Date()
-  const time = now.toTimeString().slice(0, 8)
-  eventLogs.value.push({ time, text: '触发事件(发展): 事态发展变化' })
-  ElMessage.success('事件已触发')
+// 任务
+const tasks = ref([])
+const taskDialogVisible = ref(false)
+const taskForm = ref({ name: '', description: '', bindRoleId: null })
+async function loadTask(id) { try { const r = await getSimTask(id); tasks.value = (r.data||[]).map(t => ({ id: t.id, name: t.name, desc: t.description, bindRole: t.bindRoleId })) } catch (e) {} }
+const handleAddTask = () => { taskForm.value = { name: '', description: '', bindRoleId: roles.value[0]?.id || null }; taskDialogVisible.value = true }
+const handleTaskSubmit = async () => {
+  try { await saveSimTask({ projectId: activeProjectId.value, ...taskForm.value }); ElMessage.success('新增成功'); taskDialogVisible.value = false; loadTask(activeProjectId.value) } catch (e) { ElMessage.error('新增失败') }
 }
-const endEvent = () => {
-  const now = new Date()
-  const time = now.toTimeString().slice(0, 8)
-  eventLogs.value.push({ time, text: '事件结束' })
-  ElMessage.info('事件已结束')
+
+// 结果数据
+const simResultData = ref({})
+async function loadResult(id) { try { const r = await getSimResult(id); simResultData.value = r.data || {} } catch (e) {} }
+async function loadLogs(id) { try { const r = await getSimLog(id); const rows = r.data || []; if (rows.length) eventLogs.value = rows.map(l => ({ time: (l.logTime||'').substring(11,19) || '--', text: l.content || l.action })) } catch (e) {} }
+
+// 选中项目
+function selectProject(item) {
+  activeProjectId.value = item.id
+  activeCategory.value = item.category || item.category_name || ''
+  loadScene(item.id); loadEvent(item.id); loadRole(item.id); loadTask(item.id); loadResult(item.id); loadLogs(item.id)
 }
+
+// ---------- 过程管理 ----------
+const eventLogs = ref([])
+
+async function loadData() { try { const res = await getSimProjects(); const rows = res.data || []; if (rows.length) { projects.value = rows.map(d => ({ id: d.id, name: d.name, category: d.category, date: d.create_date || d.createDate })); selectProject(projects.value[0]) } } catch (e) {} }
+onMounted(() => { loadData() })
+
+const doProcess = (action) => { return processSimDrill(activeProjectId.value, action, activeCategory.value) }
+const reloadLogs = () => { if (activeProjectId.value) loadLogs(activeProjectId.value) }
+const startDrill = () => { doProcess('启动').then(() => { reloadLogs(); ElMessage.success('演练已启动') }).catch(()=>{}) }
+const pauseDrill = () => { doProcess('暂停').then(() => { reloadLogs(); ElMessage.warning('演练已暂停') }).catch(()=>{}) }
+const exitDrill = () => { ElMessageBox.confirm('确认退出?','提示',{type:'warning'}).then(()=>{ doProcess('退出').then(() => reloadLogs()).catch(()=>{}) }).catch(()=>{}) }
+const triggerEvent = () => { doProcess('触发事件').then(() => { reloadLogs(); ElMessage.success('事件已触发') }).catch(()=>{}) }
+const endEvent = () => { doProcess('结束事件').then(() => { reloadLogs(); ElMessage.info('事件已结束') }).catch(()=>{}) }
 </script>
 
 <style scoped lang="scss">
@@ -360,7 +399,9 @@ const endEvent = () => {
   align-items: center;
   padding: 8px 0;
   border-bottom: 1px dashed #ebeef5;
+  cursor: pointer;
 
+  &.selected { background: #ecf5ff; border-left: 3px solid #409eff; padding-left: 8px; }
   &:last-child { border-bottom: none; }
 
   .project-info {

+ 23 - 5
src/views/subSystem/dispatch/disExerciseMan/DisCurriculum.vue

@@ -82,7 +82,7 @@
       <el-table-column label="操作" width="120" align="center">
         <template #default="{ row }">
           <el-button link type="primary" @click="handleEdit(row)"><el-icon><Edit /></el-icon></el-button>
-          <el-button link type="danger" @click="handleDelete(row)"><el-icon><Delete /></el-icon></el-button>
+          <el-button link type="danger" @click="handleDeleteApi(row)"><el-icon><Delete /></el-icon></el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -93,6 +93,13 @@
         <el-form-item label="课程名称" prop="name">
           <el-input v-model="form.name" placeholder="请输入课程名称" />
         </el-form-item>
+        <el-form-item label="所属系统" prop="category">
+          <el-select v-model="form.category" placeholder="请选择" style="width: 100%">
+            <el-option label="燃气" value="燃气" />
+            <el-option label="排水" value="排水" />
+            <el-option label="供水" value="供水" />
+          </el-select>
+        </el-form-item>
         <el-form-item label="培训方式" prop="method">
           <el-select v-model="form.method" placeholder="请选择培训方式" style="width: 100%">
             <el-option label="知识培训" value="知识培训" />
@@ -124,16 +131,17 @@
       </el-form>
       <template #footer>
         <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="handleSubmit">课程提交</el-button>
+        <el-button type="primary" @click="handleSubmitApi">课程提交</el-button>
       </template>
     </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
+import { ref, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Reading, VideoPlay, Edit, Delete } from '@element-plus/icons-vue'
+import { getCourseList, saveCourse, updateCourse, deleteCourse } from '@/api/pipeNetwork/basic'
 
 // ---------- 模版示例数据 ----------
 const knowledgeExamples = [
@@ -214,8 +222,17 @@ const filteredList = computed(() => {
   )
 })
 
-const handleSearch = () => {}
-const handleReset = () => { queryParams.value.keyword = '' }
+const handleSearch = () => { loadData() }
+const handleReset = () => { queryParams.value.keyword = ''; loadData() }
+async function loadData() { try { const res = await getCourseList({ pageSize: 50 }); const rows = res.data?.records || []; if (rows.length) tableData.value = rows } catch (e) {} }
+onMounted(() => { loadData() })
+const handleSubmitApi = async () => {
+  const v = await formRef.value?.validate().catch(() => false); if (!v) return
+  const data = { name: form.value.name, category: form.value.category, method: form.value.method, content: form.value.content, completion: form.value.completion, remark: form.value.remark, id: form.value.id }
+  if (form.value.dateRange?.length === 2) { data.startDate = form.value.dateRange[0]; data.endDate = form.value.dateRange[1] }
+  try { data.id ? await updateCourse(data) : await saveCourse(data); dialogVisible.value = false; ElMessage.success('保存成功'); loadData() } catch (e) { ElMessage.error('保存失败') }
+}
+const handleDeleteApi = (row) => { ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' }).then(async () => { await deleteCourse(row.id); ElMessage.success('删除成功'); loadData() }).catch(() => {}) }
 
 // ---------- 弹窗 ----------
 const dialogVisible = ref(false)
@@ -225,6 +242,7 @@ const formRef = ref(null)
 const defaultForm = () => ({
   id: null,
   name: '',
+  category: '燃气',
   method: '知识培训',
   content: '',
   dateRange: null,

+ 28 - 18
src/views/subSystem/dispatch/disExerciseMan/DisPlanning.vue

@@ -33,7 +33,7 @@
       <el-table-column label="操作" width="140" align="center">
         <template #default="{ row }">
           <el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
-          <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
+          <el-button link type="danger" @click="handleDeleteApi(row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -44,8 +44,18 @@
         <el-form-item label="演练名称" prop="name">
           <el-input v-model="form.name" placeholder="请输入演练名称" />
         </el-form-item>
-        <el-form-item label="演练分类" prop="category">
-          <el-select v-model="form.category" placeholder="请选择演练分类" style="width: 100%">
+        <el-form-item label="所属系统" prop="category">
+          <el-select v-model="form.category" placeholder="请选择" style="width: 100%">
+            <el-option label="燃气" value="燃气" /><el-option label="排水" value="排水" /><el-option label="供水" value="供水" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="演练类型" prop="drillType">
+          <el-select v-model="form.drillType" style="width: 100%">
+            <el-option label="模拟演练" value="模拟演练" /><el-option label="实战演练" value="实战演练" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="演练分类" prop="drillSubtype">
+          <el-select v-model="form.drillSubtype" placeholder="请选择演练分类" style="width: 100%">
             <el-option v-for="item in categoryOptions" :key="item" :label="item" :value="item" />
           </el-select>
         </el-form-item>
@@ -77,16 +87,17 @@
       </el-form>
       <template #footer>
         <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="handleSubmit">保存提交</el-button>
+        <el-button type="primary" @click="handleSubmitApi">保存提交</el-button>
       </template>
     </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { ref, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Document, Plus } from '@element-plus/icons-vue'
+import { getDrillPlanList, saveDrillPlan, updateDrillPlan, deleteDrillPlan } from '@/api/pipeNetwork/basic'
 
 // ---------- 常量 ----------
 const categoryOptions = [
@@ -168,14 +179,8 @@ const dialogTitle = ref('新增演练计划')
 const formRef = ref(null)
 
 const defaultForm = () => ({
-  id: null,
-  name: '',
-  category: '',
-  base: '',
-  dateRange: null,
-  method: '',
-  status: '计划中',
-  remark: ''
+  id: null, name: '', category: '燃气', drillType: '模拟演练', drillSubtype: '',
+  base: '', dateRange: null, method: '', status: '计划中', remark: ''
 })
 
 const form = ref(defaultForm())
@@ -226,12 +231,17 @@ const handleSubmit = () => {
 }
 
 // ---------- 删除 ----------
-const handleDelete = (row) => {
-  ElMessageBox.confirm('确认删除该条演练计划?', '提示', { type: 'warning' }).then(() => {
-    tableData.value = tableData.value.filter(i => i.id !== row.id)
-    ElMessage.success('删除成功')
-  }).catch(() => {})
+async function loadData() { try { const res = await getDrillPlanList({ pageSize: 50 }); const rows = res.data?.records || []; if (rows.length) tableData.value = rows } catch (e) {} }
+onMounted(() => { loadData() })
+const handleSubmitApi = () => {
+  formRef.value?.validate(async (valid) => {
+    if (!valid) return
+    const data = { name: form.value.name, category: form.value.category, drillType: form.value.drillType, drillSubtype: form.value.drillSubtype, base: form.value.base, method: form.value.method, status: form.value.status, remark: form.value.remark, id: form.value.id }
+    if (form.value.dateRange?.length === 2) { data.startDate = form.value.dateRange[0]; data.endDate = form.value.dateRange[1] }
+    try { data.id ? await updateDrillPlan(data) : await saveDrillPlan(data); dialogVisible.value = false; ElMessage.success('保存成功'); loadData() } catch (e) { ElMessage.error('保存失败') }
+  })
 }
+const handleDeleteApi = (row) => { ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' }).then(async () => { await deleteDrillPlan(row.id); ElMessage.success('删除成功'); loadData() }).catch(() => {}) }
 </script>
 
 <style scoped lang="scss">

+ 71 - 50
src/views/subSystem/dispatch/disExerciseMan/DisPracticalDrill.vue

@@ -1,5 +1,6 @@
 <!--实战演练管理-->
 <template>
+  <div>
   <div class="app-container">
     <div class="drill-layout">
       <!-- ===== 左列 ===== -->
@@ -19,6 +20,7 @@
               <div class="template-actions">
                 <el-button link type="primary" @click="handleEditTemplate(tpl)"><el-icon><Edit /></el-icon></el-button>
                 <el-button link type="success" @click="handleUseTemplate(tpl)">使用</el-button>
+                <el-button link type="danger" @click="handleDeleteTemplate(tpl)"><el-icon><Delete /></el-icon></el-button>
               </div>
             </div>
           </div>
@@ -162,71 +164,90 @@
       </div>
     </div>
   </div>
+
+    <!-- 模板弹窗 -->
+  <el-dialog v-model="tplDialogVisible" title="演练模板" width="500px" destroy-on-close>
+    <el-form :model="tplForm" label-width="80px">
+      <el-form-item label="模板名称"><el-input v-model="tplForm.name" /></el-form-item>
+      <el-form-item label="所属系统">
+        <el-select v-model="tplForm.category" style="width:100%"><el-option label="燃气" value="燃气" /><el-option label="排水" value="排水" /><el-option label="供水" value="供水" /></el-select>
+      </el-form-item>
+      <el-form-item label="模板内容"><el-input v-model="tplForm.content" type="textarea" :rows="4" /></el-form-item>
+    </el-form>
+    <template #footer><el-button @click="tplDialogVisible=false">取消</el-button><el-button type="primary" @click="handleTplSubmit">确定</el-button></template>
+  </el-dialog>
+  <!-- 计划表单弹窗 -->
+    <el-dialog v-model="planDialogVisible" :title="planFormTitle" width="550px" destroy-on-close>
+      <el-form :model="planForm" ref="planFormRef" label-width="90px">
+        <el-form-item label="演练名称"><el-input v-model="planForm.name" /></el-form-item>
+        <el-form-item label="所属系统">
+          <el-select v-model="planForm.category" style="width:100%">
+            <el-option label="燃气" value="燃气" /><el-option label="排水" value="排水" /><el-option label="供水" value="供水" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="演练目标"><el-input v-model="planForm.target" type="textarea" :rows="2" /></el-form-item>
+        <el-form-item label="演练项目"><el-input v-model="planForm.project" type="textarea" :rows="2" /></el-form-item>
+        <el-form-item label="事件描述"><el-input v-model="planForm.eventDesc" /></el-form-item>
+        <el-form-item label="参与人员"><el-input v-model="planForm.personnel" /></el-form-item>
+        <el-form-item label="状态"><el-select v-model="planForm.status" style="width:100%"><el-option label="计划中" value="计划中" /><el-option label="进行中" value="进行中" /><el-option label="已完成" value="已完成" /></el-select></el-form-item>
+      </el-form>
+      <template #footer><el-button @click="planDialogVisible=false">取消</el-button><el-button type="primary" @click="handlePlanSubmit">确定</el-button></template>
+    </el-dialog>
+  </div>
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { ref, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import {
   Document, Edit, Delete, Setting, DataAnalysis, Monitor,
   Phone, Guide, Operation, ChatDotRound, TrendCharts
 } from '@element-plus/icons-vue'
-
-// ---------- 模板 ----------
-const templates = ref([
-  { id: 1, name: '化工泄漏实战演练模板', date: '2025-01-01' },
-  { id: 2, name: '地震救援实战演练模板', date: '2025-01-01' }
-])
-
-const handleAddTemplate = () => { ElMessage.success('新增演练模板(Mock)') }
-const handleEditTemplate = (tpl) => { ElMessage.success(`编辑模板: ${tpl.name}`) }
+import { getPracticalPlans, savePracticalPlan, updatePracticalPlan, deletePracticalPlan, getPracticalTemplates, savePracticalTemplate, updatePracticalTemplate, deletePracticalTemplate } from '@/api/pipeNetwork/basic'
+
+const templates = ref([])
+// 模板弹窗
+const tplDialogVisible = ref(false)
+const tplForm = ref({ id: null, name: '', category: '燃气', content: '' })
+const handleAddTemplate = () => { tplForm.value = { id: null, name: '', category: '燃气', content: '' }; tplDialogVisible.value = true }
+const handleEditTemplate = (tpl) => { tplForm.value = { ...tpl }; tplDialogVisible.value = true }
 const handleUseTemplate = (tpl) => { ElMessage.success(`已应用模板: ${tpl.name}`) }
+const handleTplSubmit = async () => {
+  const d = tplForm.value
+  try {
+    d.id ? await updatePracticalTemplate(d) : await savePracticalTemplate(d)
+    ElMessage.success('保存成功'); tplDialogVisible.value = false; loadTemplates()
+  } catch (e) { ElMessage.error('保存失败') }
+}
+const handleDeleteTemplate = (tpl) => { ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' }).then(async () => { await deletePracticalTemplate(tpl.id); ElMessage.success('删除成功'); loadTemplates() }).catch(() => {}) }
+async function loadTemplates() { try { const res = await getPracticalTemplates(); const rows = res.data || []; templates.value = rows.map(d => ({ id: d.id, name: d.name, content: d.content, date: d.createDate })) } catch (e) {} }
 
-// ---------- 计划 ----------
 const planSearch = ref('')
-const plans = ref([
-  {
-    id: 1,
-    name: '2025夏季危化品泄漏实战演练',
-    target: '提升泄漏封堵效率',
-    project: '储罐区泄漏及人员疏散',
-    event: '泄漏扩大',
-    personnel: '指挥员张立、消防队12人',
-    status: '进行中'
-  },
-  {
-    id: 2,
-    name: '洪涝灾害救援演练',
-    target: '提升水上救援能力',
-    project: '城市内涝人员转移',
-    event: '暴雨持续水位上涨',
-    personnel: '指挥员王伟、救援队8人',
-    status: '已完成'
-  },
-  {
-    id: 3,
-    name: '化工泄漏实战演练 衍生计划',
-    target: '检验应急预案有效性',
-    project: '危化品储罐区综合演练',
-    event: '泄漏+火灾叠加',
-    personnel: '指挥员李明、消防队15人',
-    status: '进行中'
-  }
-])
-
-const handleAddPlan = () => { ElMessage.success('新增演练计划(Mock)') }
-const handleEditPlan = (plan) => { ElMessage.success(`编辑计划: ${plan.name}`) }
-const handleDeletePlan = (plan) => {
-  ElMessageBox.confirm('确认删除该计划?', '提示', { type: 'warning' }).then(() => {
-    plans.value = plans.value.filter(p => p.id !== plan.id)
-    ElMessage.success('删除成功')
-  }).catch(() => {})
+const plans = ref([])
+
+// 计划弹窗
+const planDialogVisible = ref(false)
+const planFormTitle = ref('新增计划')
+const planForm = ref({ id: null, name: '', category: '燃气', target: '', project: '', eventDesc: '', personnel: '', status: '计划中' })
+const planFormRef = ref(null)
+const handleAddPlan = () => { planForm.value = { id: null, name: '', category: '燃气', target: '', project: '', eventDesc: '', personnel: '', status: '计划中' }; planFormTitle.value = '新增计划'; planDialogVisible.value = true }
+const handleEditPlan = (plan) => { planForm.value = { id: plan.id, name: plan.name, category: plan.category || '燃气', target: plan.target || '', project: plan.project || '', eventDesc: plan.eventDesc || plan.event || '', personnel: plan.personnel || '', status: plan.status || '计划中' }; planFormTitle.value = '编辑计划'; planDialogVisible.value = true }
+const handleDeletePlan = (plan) => { ElMessageBox.confirm('确认删除?', '提示', { type: 'warning' }).then(async () => { await deletePracticalPlan(plan.id); ElMessage.success('删除成功'); loadData() }).catch(() => {}) }
+const handlePlanSubmit = async () => {
+  const d = planForm.value
+  try {
+    d.id ? await updatePracticalPlan(d) : await savePracticalPlan(d)
+    ElMessage.success('保存成功'); planDialogVisible.value = false; loadData()
+  } catch (e) { ElMessage.error('保存失败') }
 }
-const handleSearchPlan = () => { ElMessage.success('查询计划(Mock)') }
+const handleSearchPlan = () => { loadData() }
 
 // ---------- 发送至子系统 ----------
+async function loadData() { try { const res = await getPracticalPlans(); const rows = res.data || []; if (rows.length) plans.value = rows.map(d => ({ id: d.id, name: d.name, category: d.category, target: d.target, project: d.project, eventDesc: d.eventDesc, personnel: d.personnel, status: d.status })) } catch (e) {} }
+onMounted(() => { loadData(); loadTemplates() })
+
 const sendToSubsystem = () => {
-  ElMessage.success('已发送至统计分析子系统 & 应急评估子系统')
+  ElMessage.success('已发送至统计分析子系统 & 应急评估子系统(伪)')
 }
 </script>