| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600 |
- <!--实战演练管理 - 基于业务场景的优化版-->
- <template>
- <div class="practical-drill-container">
- <!-- 顶部操作栏 -->
- <div class="drill-top-bar">
- <div class="top-left">
- <h2 class="page-title"><el-icon><TrendCharts /></el-icon>实战演练管理</h2>
- <el-tag type="warning" size="large">5.2.3.3.4 实战演练管理</el-tag>
- </div>
- <div class="top-right">
- <el-button type="primary" size="large" @click="handleCreatePlan">
- <el-icon><Plus /></el-icon>创建实战演练
- </el-button>
- <el-button size="large" @click="showAnalysisReport">
- <el-icon><Document /></el-icon>分析报表
- </el-button>
- </div>
- </div>
- <div class="drill-main-layout">
- <!-- ===== 左列 ===== -->
- <div class="left-column">
- <!-- 演练计划模板 -->
- <div class="panel template-panel">
- <div class="panel-header">
- <span><el-icon><Document /></el-icon> 演练计划模板</span>
- <el-button type="primary" size="small" @click="openTemplateDialog">
- <el-icon><Plus /></el-icon>新增模板
- </el-button>
- </div>
- <div class="panel-body">
- <div class="template-list">
- <el-empty v-if="templates.length === 0" description="暂无演练模板" :image-size="80" />
- <div v-for="tpl in templates" :key="tpl.id" class="template-card">
- <div class="template-card-header">
- <div class="template-name">{{ tpl.name }}</div>
- <el-tag :type="getCategoryTagType(tpl.category)" size="small">{{ getCategoryLabel(tpl.category) }}</el-tag>
- </div>
- <div class="template-meta">
- <span class="meta-item"><el-icon><Calendar /></el-icon>{{ tpl.createDate || '未知日期' }}</span>
- <span class="meta-item"><el-icon><User /></el-icon>{{ tpl.userCount || 0 }}人</span>
- </div>
- <div class="template-actions">
- <el-button link type="primary" size="small" @click.stop="openEditTemplate(tpl)">
- <el-icon><Edit /></el-icon>编辑
- </el-button>
- <el-button link type="success" size="small" @click.stop="applyTemplate(tpl)">
- <el-icon><SwitchButton /></el-icon>应用
- </el-button>
- <el-button link type="danger" size="small" @click.stop="confirmDeleteTemplate(tpl)">
- <el-icon><Delete /></el-icon>删除
- </el-button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 演练计划设置 -->
- <div class="panel plan-panel">
- <div class="panel-header">
- <span><el-icon><Setting /></el-icon> 演练计划设置</span>
- <div class="header-actions">
- <el-select v-model="planFilterStatus" placeholder="筛选状态" size="small" style="width: 90px; margin-right: 5px" clearable>
- <el-option label="全部" value="" />
- <el-option label="计划中" value="计划中" />
- <el-option label="进行中" value="进行中" />
- <el-option label="已完成" value="已完成" />
- </el-select>
- <el-button type="primary" size="small" @click="openPlanDialog">
- <el-icon><Plus /></el-icon>新建计划
- </el-button>
- </div>
- </div>
- <div class="panel-body">
- <div class="search-filter">
- <el-input v-model="planSearchText" placeholder="搜索演练名称/目标/项目" size="small" style="width: 200px" clearable>
- <template #prefix><el-icon><Search /></el-icon></template>
- </el-input>
- <el-button type="primary" size="small" @click="filterPlans">
- <el-icon><Search /></el-icon>查询
- </el-button>
- </div>
- <div class="plan-list">
- <el-empty v-if="filteredPlans.length === 0" description="暂无演练计划" :image-size="80" />
- <div
- v-for="plan in filteredPlans"
- :key="plan.id"
- :class="['plan-card', { active: selectedPlanId === plan.id }]"
- @click="selectPlan(plan)"
- >
- <div class="plan-status-badge" :class="plan.status">{{ plan.status }}</div>
- <div class="plan-card-content">
- <h4 class="plan-title">{{ plan.name }}</h4>
- <div class="plan-category">
- <el-tag :type="getCategoryTagType(plan.category)" size="small">{{ getCategoryLabel(plan.category) }}</el-tag>
- <span class="plan-date">{{ plan.endTime || plan.updateTime || '—' }}</span>
- </div>
- <div class="plan-info-list">
- <div class="info-item" v-if="plan.target">
- <el-icon><Flag /></el-icon>
- <span>目标:</span>
- <span class="info-text">{{ plan.target }}</span>
- </div>
- <div class="info-item" v-if="plan.project">
- <el-icon><List /></el-icon>
- <span>项目:</span>
- <span class="info-text">{{ plan.project }}</span>
- </div>
- <div class="info-item" v-if="plan.eventDesc">
- <el-icon><Bell /></el-icon>
- <span>事件:</span>
- <span class="info-text">{{ plan.eventDesc }}</span>
- </div>
- <div class="info-item" v-if="plan.personnel">
- <el-icon><User /></el-icon>
- <span>人员:</span>
- <span class="info-text">{{ plan.personnel }}</span>
- </div>
- <div class="info-item" v-if="plan.participantCount">
- <el-icon><UserFilled /></el-icon>
- <span>参与人数:</span>
- <span class="info-value">{{ plan.participantCount }}人</span>
- </div>
- <div class="info-item" v-if="plan.progress !== null && plan.progress !== undefined">
- <el-icon><CircleCheckFilled /></el-icon>
- <span>进度:</span>
- <el-progress :percentage="plan.progress" :status="plan.progress === 100 ? 'success' : ''" :stroke-width="6" />
- </div>
- </div>
- <div class="plan-actions">
- <el-button link type="primary" size="small" @click.stop="editPlan(plan)">
- <el-icon><Edit /></el-icon>编辑
- </el-button>
- <el-button link type="warning" size="small" @click.stop="startDrill(plan)">
- <el-icon><VideoPlay /></el-icon>开始演练
- </el-button>
- <el-button link type="success" size="small" @click.stop="viewDrillRecord(plan)">
- <el-icon><Document /></el-icon>查看记录
- </el-button>
- <el-button link type="danger" size="small" @click.stop="confirmDeletePlan(plan)">
- <el-icon><Delete /></el-icon>删除
- </el-button>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- ===== 右列 ===== -->
- <div class="right-column">
- <!-- 当前演练状态 -->
- <div class="panel current-drill-panel" v-if="currentDrillInfo">
- <div class="panel-header">
- <span><el-icon><Monitor /></el-icon> 当前演练状态</span>
- <el-button v-if="isDrilling" type="danger" size="small" @click="stopDrill">
- <el-icon><VideoPause /></el-icon>结束演练
- </el-button>
- </div>
- <div class="panel-body">
- <div class="drill-status-summary">
- <div class="status-badge large" :class="currentDrillStatus">
- <el-icon><CircleCheckFilled /></el-icon>
- {{ currentDrillStatus }}
- </div>
- <div class="drill-timeline">
- <el-timeline>
- <el-timeline-item
- v-for="(action, index) in drillActions"
- :key="index"
- :timestamp="action.time"
- placement="top"
- :type="action.type"
- >
- {{ action.message }}
- </el-timeline-item>
- </el-timeline>
- </div>
- </div>
- </div>
- </div>
-
- <!-- 演练结果记录 -->
- <div class="panel record-panel">
- <div class="panel-header">
- <span><el-icon><DataAnalysis /></el-icon> 演练结果记录</span>
- <el-button type="primary" size="small" @click="saveAllRecord">
- <el-icon><CircleCheckFilled /></el-icon>保存记录
- </el-button>
- </div>
- <div class="panel-body">
- <el-descriptions :column="2" border v-if="currentDrillRecord">
- <el-descriptions-item label="演练项目">{{ currentDrillRecord.drillProjectName }}</el-descriptions-item>
- <el-descriptions-item label="记录时间">{{ currentDrillRecord.recordTime }}</el-descriptions-item>
- <el-descriptions-item label="演练类型">{{ currentDrillRecord.drillType }}</el-descriptions-item>
- <el-descriptions-item label="参演部门">{{ currentDrillRecord.participatingDept }}</el-descriptions-item>
- <el-descriptions-item label="演练负责人" :span="2">{{ currentDrillRecord.responsiblePerson }}</el-descriptions-item>
- </el-descriptions>
-
- <!-- 监测监控信息 -->
- <div class="record-section">
- <div class="section-title-row">
- <h4 class="section-title"><el-icon><Monitor /></el-icon> 监测监控信息</h4>
- <el-button type="primary" size="small" @click="openAddMonitoring">
- <el-icon><Plus /></el-icon>添加监测数据
- </el-button>
- </div>
- <el-table :data="monitoringInfo" stripe size="small" style="width: 100%" max-height="200">
- <el-table-column prop="sensorType" label="传感器类型" min-width="120" />
- <el-table-column prop="location" label="监测位置" min-width="140" show-overflow-tooltip />
- <el-table-column prop="value" label="监测数值" width="100" />
- <el-table-column prop="unit" label="单位" width="70" />
- <el-table-column prop="alarmLevel" label="报警等级" width="90">
- <template #default="{ row }">
- <el-tag :type="getAlarmLevelType(row.alarmLevel)" size="small">{{ row.alarmLevel }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="time" label="监测时间" width="150" />
- <el-table-column label="操作" width="120" fixed="right">
- <template #default="{ row }">
- <el-button link type="primary" size="small" @click="openEditMonitoring(row)">编辑</el-button>
- <el-button link type="danger" size="small" @click="confirmDeleteMonitoring(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <el-empty v-if="monitoringInfo.length === 0" description="暂无监测数据" :image-size="60" />
- </div>
-
- <!-- 接警信息 -->
- <div class="record-section" v-if="currentDrillRecord">
- <h4 class="section-title"><el-icon><Phone /></el-icon> 接警信息</h4>
- <el-descriptions :column="2" border>
- <el-descriptions-item label="接警时间">{{ currentDrillRecord.reportTime || '—' }}</el-descriptions-item>
- <el-descriptions-item label="报警方式">{{ currentDrillRecord.reportMethod || '电话' }}</el-descriptions-item>
- <el-descriptions-item label="报警人" :span="2">{{ currentDrillRecord.reporter || '—' }}</el-descriptions-item>
- <el-descriptions-item label="联系电话" :span="2">{{ currentDrillRecord.contactPhone || '—' }}</el-descriptions-item>
- <el-descriptions-item label="事件地点" :span="2">{{ currentDrillRecord.eventLocation || '—' }}</el-descriptions-item>
- <el-descriptions-item label="事件描述" :span="2">
- <el-input v-model="currentDrillRecord.eventDescription" type="textarea" :rows="2" placeholder="请输入事件描述" size="small" />
- </el-descriptions-item>
- </el-descriptions>
- </div>
-
- <!-- 指挥调度信息 -->
- <div class="record-section">
- <h4 class="section-title"><el-icon><Guide /></el-icon> 指挥调度信息</h4>
- <el-form :model="dispatchForm" label-width="80px" size="small">
- <el-form-item label="调度指令">
- <el-input v-model="dispatchForm.instruction" type="textarea" :rows="2" placeholder="请输入调度指令" />
- </el-form-item>
- <el-form-item label="调派力量">
- <el-select v-model="dispatchForm.forces" multiple placeholder="请选择调派力量" style="width: 100%">
- <el-option label="消防中队" value="消防中队" />
- <el-option label="危化品处置小组" value="危化品处置小组" />
- <el-option label="医疗救援队" value="医疗救援队" />
- <el-option label="应急抢险队" value="应急抢险队" />
- <el-option label="公安警力" value="公安警力" />
- </el-select>
- </el-form-item>
- <el-form-item label="现场指挥部">
- <el-switch v-model="dispatchForm.commandCenter" />
- </el-form-item>
- <el-form-item label="人员伤亡">
- <el-input v-model="dispatchForm.casualties" placeholder="暂无人员伤亡报告" />
- </el-form-item>
- </el-form>
- </div>
-
- <!-- 决策信息 -->
- <div class="record-section">
- <h4 class="section-title"><el-icon><Operation /></el-icon> 决策信息</h4>
- <el-form :model="decisionForm" label-width="80px" size="small">
- <el-form-item label="响应级别">
- <el-select v-model="decisionForm.responseLevel" style="width: 100%">
- <el-option label="一级响应" value="一级响应" />
- <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="decisionForm.command" type="textarea" :rows="2" placeholder="请输入总指挥命令" />
- </el-form-item>
- <el-form-item label="疏散范围">
- <el-input v-model="decisionForm.evacuationRange" placeholder="请输入疏散范围" />
- </el-form-item>
- <el-form-item label="增援请求">
- <el-switch v-model="decisionForm.reinforcement" />
- </el-form-item>
- <el-form-item label="备注说明">
- <el-input v-model="decisionForm.remark" type="textarea" :rows="2" placeholder="请输入备注说明" />
- </el-form-item>
- </el-form>
- </div>
-
- <!-- 对外发布信息 -->
- <div class="record-section">
- <h4 class="section-title"><el-icon><ChatDotRound /></el-icon> 对外发布信息</h4>
- <el-form label-width="80px" size="small">
- <el-form-item label="发布渠道">
- <el-checkbox-group v-model="releaseChannels">
- <el-checkbox label="官方微博">官方微博</el-checkbox>
- <el-checkbox label="微信公众号">微信公众号</el-checkbox>
- <el-checkbox label="门户网站">门户网站</el-checkbox>
- <el-checkbox label="媒体发布">媒体发布</el-checkbox>
- <el-checkbox label="短信通知">短信通知</el-checkbox>
- </el-checkbox-group>
- </el-form-item>
- <el-form-item label="发布内容">
- <el-input v-model="releaseContent" type="textarea" :rows="4" placeholder="请输入对外发布信息内容" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" size="small" @click="saveReleaseInfo">保存并发布</el-button>
- <el-button size="small" @click="previewRelease">预览</el-button>
- </el-form-item>
- </el-form>
- </div>
- </div>
- </div>
-
- <!-- 统计分析子系统结果 -->
- <div class="panel analysis-panel">
- <div class="panel-header">
- <span><el-icon><TrendCharts /></el-icon> 统计分析子系统结果</span>
- <div class="header-actions">
- <el-button type="warning" size="small" @click="sendToAnalysisSubsystems" :disabled="!canSendToSubsystems">
- <el-icon><Share /></el-icon>发送至子系统和评估系统
- </el-button>
- <el-button type="success" size="small" @click="exportAnalysisReport">
- <el-icon><Download /></el-icon>导出分析报告
- </el-button>
- </div>
- </div>
- <div class="panel-body">
- <div class="analysis-summary" v-if="analysisResult">
- <div class="analysis-time">
- <el-icon><Clock /></el-icon>
- 分析时间:{{ analysisResult.analyzeTime || '--:--:--' }}
- </div>
- <div class="analysis-basic">
- <h4 class="basic-title">演练能力评估</h4>
- <div class="capability-grid">
- <div class="capability-item">
- <div class="cap-label">技能掌握</div>
- <div class="cap-score">{{ analysisResult.skillScore }}/100</div>
- <el-progress :percentage="analysisResult.skillScore" :stroke-width="8" />
- </div>
- <div class="capability-item">
- <div class="cap-label">装备使用</div>
- <div class="cap-score">{{ analysisResult.equipmentScore }}/100</div>
- <el-progress :percentage="analysisResult.equipmentScore" :stroke-width="8" />
- </div>
- <div class="capability-item">
- <div class="cap-label">响应时效</div>
- <div class="cap-score">{{ analysisResult.responseScore }}/100</div>
- <el-progress :percentage="analysisResult.responseScore" :stroke-width="8" />
- </div>
- <div class="capability-item">
- <div class="cap-label">协同配合</div>
- <div class="cap-score">{{ analysisResult.cooperationScore }}/100</div>
- <el-progress :percentage="analysisResult.cooperationScore" :stroke-width="8" />
- </div>
- </div>
- </div>
- <div class="analysis-task">
- <h4 class="task-title">任务执行情况</h4>
- <div class="task-stats">
- <div class="task-stat-item">
- <div class="stat-label">任务总数</div>
- <div class="stat-value highlight-blue">{{ analysisResult.totalTasks }}</div>
- </div>
- <div class="task-stat-item">
- <div class="stat-label">完成任务</div>
- <div class="stat-value highlight-green">{{ analysisResult.completedTasks }}</div>
- </div>
- <div class="task-stat-item">
- <div class="stat-label">完成率</div>
- <div class="stat-value highlight-orange">{{ analysisResult.completionRate }}%</div>
- </div>
- <div class="task-stat-item">
- <div class="stat-label">超时任务</div>
- <div class="stat-value highlight-red">{{ analysisResult.overtimeTasks }}</div>
- </div>
- </div>
- </div>
- <div class="analysis-comprehensive">
- <h4 class="comprehensive-title">综合评估结果</h4>
- <div class="eval-grid">
- <div class="eval-item">
- <div class="eval-label">综合等级</div>
- <div class="eval-value" :class="{ success: analysisResult.overallLevel === '优秀', warning: analysisResult.overallLevel === '良好', info: analysisResult.overallLevel === '合格', danger: analysisResult.overallLevel === '待改进' }">
- {{ analysisResult.overallLevel }}
- </div>
- </div>
- </div>
- <div class="eval-description">
- <strong>评估结论:</strong>
- <p>{{ analysisResult.evalConclusion || '无评估结论' }}</p>
- </div>
- <div class="suggestion-section">
- <strong>改进建议:</strong>
- <ul>
- <li v-for="(sug, idx) in analysisResult.improvementSuggestions" :key="idx">{{ sug }}</li>
- </ul>
- </div>
- </div>
- </div>
- <el-empty v-else description="暂无统计分析结果,请点击“分析报表”加载" :image-size="100" />
- </div>
- </div>
- </div>
- </div>
- <!-- 弹窗:模板管理 -->
- <el-dialog
- v-model="templateDialogVisible"
- :title="templateDialogTitle"
- width="600px"
- destroy-on-close
- >
- <el-form :model="templateForm" label-width="90px">
- <el-form-item label="模板名称">
- <el-input v-model="templateForm.name" placeholder="请输入演练模板名称" />
- </el-form-item>
- <el-form-item label="所属系统">
- <el-select v-model="templateForm.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="templateForm.target"
- type="textarea"
- :rows="2"
- placeholder="请输入演练目标"
- />
- </el-form-item>
- <el-form-item label="演练项目">
- <el-input
- v-model="templateForm.project"
- type="textarea"
- :rows="3"
- placeholder="请输入演练项目详情"
- />
- </el-form-item>
- <el-form-item label="涉及事件">
- <el-input
- v-model="templateForm.eventDesc"
- type="textarea"
- :rows="2"
- placeholder="请输入可能涉及的事件类型"
- />
- </el-form-item>
- <el-form-item label="参与人员">
- <el-input
- v-model="templateForm.personnel"
- type="textarea"
- :rows="2"
- placeholder="例如:指挥中心、消防队、医疗组等"
- />
- </el-form-item>
- <el-form-item label="完成时限">
- <el-date-picker
- v-model="templateForm.dueDate"
- type="date"
- placeholder="选择截止日期"
- format="YYYY-MM-DD"
- date-format="YYYY-MM-DD"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="预案内容">
- <el-input
- v-model="templateForm.content"
- type="textarea"
- :rows="4"
- placeholder="请输入详细的预案内容和操作流程"
- />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="closeTemplateDialog">取消</el-button>
- <el-button type="primary" @click="submitTemplateForm">确定</el-button>
- </template>
- </el-dialog>
- <!-- 弹窗:计划管理 -->
- <el-dialog
- v-model="planDialogVisible"
- :title="planDialogTitle"
- width="600px"
- destroy-on-close
- >
- <el-form :model="planForm" ref="planFormRef" label-width="90px">
- <el-form-item label="演练名称" required>
- <el-input v-model="planForm.name" placeholder="请输入演练名称" />
- </el-form-item>
- <el-form-item label="所属系统" required>
- <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="演练目标" required>
- <el-input
- v-model="planForm.target"
- type="textarea"
- :rows="2"
- placeholder="请输入演练目标"
- />
- </el-form-item>
- <el-form-item label="演练项目" required>
- <el-input
- v-model="planForm.project"
- type="textarea"
- :rows="3"
- placeholder="请输入演练项目详情"
- />
- </el-form-item>
- <el-form-item label="事件描述">
- <el-input
- v-model="planForm.eventDesc"
- type="textarea"
- :rows="2"
- placeholder="请输入模拟事件的描述"
- />
- </el-form-item>
- <el-form-item label="参与人员" required>
- <el-input
- v-model="planForm.personnel"
- type="textarea"
- :rows="2"
- placeholder="请输入参与演练的人员或部门"
- />
- </el-form-item>
- <el-form-item label="完成时间">
- <el-date-picker
- v-model="planForm.endTime"
- type="date"
- placeholder="选择完成时间"
- format="YYYY-MM-DD"
- date-format="YYYY-MM-DD"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="参与人数">
- <el-input-number
- v-model="planForm.participantCount"
- :min="1"
- :max="1000"
- style="width: 100%"
- />
- </el-form-item>
- <el-form-item label="演练进度">
- <el-slider v-model="planForm.progress" :min="0" :max="100" :step="10" />
- </el-form-item>
- <el-form-item label="状态" required>
- <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="submitPlanForm">确定</el-button>
- </template>
- </el-dialog>
- <!-- 弹窗:监测数据 -->
- <el-dialog v-model="monitoringDialogVisible" :title="monitoringDialogTitle" width="500px" destroy-on-close>
- <el-form :model="monitoringForm" label-width="90px">
- <el-form-item label="传感器类型" required>
- <el-input v-model="monitoringForm.sensorType" placeholder="如:燃气浓度传感器" />
- </el-form-item>
- <el-form-item label="监测位置" required>
- <el-input v-model="monitoringForm.location" placeholder="如:XX路XX号管点" />
- </el-form-item>
- <el-form-item label="监测数值">
- <el-input v-model="monitoringForm.value" placeholder="如:50" />
- </el-form-item>
- <el-form-item label="单位">
- <el-input v-model="monitoringForm.unit" placeholder="如:%LEL" />
- </el-form-item>
- <el-form-item label="报警等级">
- <el-select v-model="monitoringForm.alarmLevel" style="width: 100%">
- <el-option label="一般" value="一般" />
- <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="monitoringForm.time" placeholder="如:2024-01-01 12:00:00" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="monitoringDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="submitMonitoringForm">确定</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
- import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
- import {
- Document, Edit, Delete, Setting, DataAnalysis, Monitor,
- Phone, Guide, Operation, ChatDotRound, TrendCharts, Plus, Calendar,
- User, SwitchButton, Search, Flag, Bell, List, Share, Download,
- Clock, VideoPlay, VideoPause, UserFilled, CircleCheckFilled
- } from '@element-plus/icons-vue'
- import {
- getPracticalTemplates, savePracticalTemplate, updatePracticalTemplate, deletePracticalTemplate,
- getPracticalPlans, savePracticalPlan, updatePracticalPlan, deletePracticalPlan,
- sendDrillAnalysis,
- getDrillRecord, saveDrillRecord, updateDrillRecord,
- getDrillMonitoring, saveDrillMonitoring, deleteDrillMonitoring,
- getDrillAnalysis, exportDrillReport, updatePlanStatus
- } from '@/api/pipeNetwork/basic'
- // ========== 卸载保护 ==========
- let isUnmounted = false
- // ========== 演练模板相关 ==========
- const templates = ref([])
- const templateDialogVisible = ref(false)
- const templateDialogTitle = ref('新增演练模板')
- const templateForm = ref({
- id: null, name: '', category: '燃气', target: '', project: '', eventDesc: '',
- personnel: '', dueDate: null, content: ''
- })
- const openTemplateDialog = () => {
- templateForm.value = {
- id: null, name: '', category: '燃气', target: '', project: '',
- eventDesc: '', personnel: '', dueDate: null, content: ''
- }
- templateDialogTitle.value = '新增演练模板'
- templateDialogVisible.value = true
- }
- const closeTemplateDialog = () => { templateDialogVisible.value = false }
- const openEditTemplate = (tpl) => {
- templateForm.value = { ...tpl, dueDate: tpl.dueDate || null }
- templateDialogTitle.value = '编辑演练模板'
- templateDialogVisible.value = true
- }
- const submitTemplateForm = async () => {
- if (!templateForm.value.name) {
- ElMessage.warning('请输入模板名称')
- return
- }
- try {
- const d = templateForm.value
- if (d.id) await updatePracticalTemplate(d)
- else await savePracticalTemplate(d)
- if (isUnmounted) return
- ElMessage.success('保存成功')
- templateDialogVisible.value = false
- loadTemplates()
- } catch (e) {
- ElMessage.error('保存失败')
- }
- }
- // 应用模板 → 打开计划弹窗并预填模板数据
- const applyTemplate = (tpl) => {
- planForm.value = {
- id: null, name: tpl.name, category: tpl.category || '燃气',
- target: tpl.target || '', project: tpl.project || '',
- eventDesc: tpl.eventDesc || '', personnel: tpl.personnel || '',
- endTime: tpl.dueDate || null, participantCount: 1, progress: 0, status: '计划中'
- }
- planDialogTitle.value = '基于模板创建演练计划'
- planDialogVisible.value = true
- ElMessage.info(`已应用模板「${tpl.name}」,请完善计划信息`)
- }
- const confirmDeleteTemplate = (tpl) => {
- ElMessageBox.confirm('确认删除此演练模板?', '提示', { type: 'warning' })
- .then(async () => {
- await deletePracticalTemplate(tpl.id)
- ElMessage.success('删除成功')
- loadTemplates()
- })
- .catch(() => {})
- }
- async function loadTemplates() {
- try {
- const res = await getPracticalTemplates()
- if (isUnmounted) return
- const rows = res.data || []
- templates.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, dueDate: d.dueDate,
- content: d.content, createDate: d.createDate || d.updateTime,
- userCount: d.userCount || 0
- }))
- } catch (e) {
- console.error('加载模板失败', e)
- }
- }
- const getCategoryTagType = (category) => {
- const types = { 燃气: 'danger', 排水: 'warning', 供水: 'primary' }
- return types[category] || 'info'
- }
- const getCategoryLabel = (category) => {
- const labels = { 燃气: '燃气管网', 排水: '排水管网', 供水: '供水管网' }
- return labels[category] || category
- }
- // ========== 演练计划相关 ==========
- const planSearchText = ref('')
- const planFilterStatus = ref('')
- const plans = ref([])
- const filteredPlans = computed(() => {
- let result = plans.value
- if (planFilterStatus.value) {
- result = result.filter(p => p.status === planFilterStatus.value)
- }
- if (planSearchText.value) {
- const keyword = planSearchText.value.toLowerCase()
- result = result.filter(p =>
- p.name?.toLowerCase().includes(keyword) ||
- p.target?.toLowerCase().includes(keyword) ||
- p.project?.toLowerCase().includes(keyword)
- )
- }
- return result
- })
- const selectedPlanId = ref(null)
- const currentPlan = ref(null)
- // 选中计划 → 加载关联的演练记录
- const selectPlan = async (plan) => {
- selectedPlanId.value = plan.id
- currentPlan.value = plan
- await loadDrillRecord(plan.id)
- }
- const planDialogVisible = ref(false)
- const planDialogTitle = ref('新建演练计划')
- const planForm = ref({
- id: null, name: '', category: '燃气', target: '', project: '',
- eventDesc: '', personnel: '', endTime: null, participantCount: 1, progress: 0, status: '计划中'
- })
- const planFormRef = ref(null)
- const openPlanDialog = () => {
- planForm.value = {
- id: null, name: '', category: '燃气', target: '', project: '',
- eventDesc: '', personnel: '', endTime: null, participantCount: 1, progress: 0, status: '计划中'
- }
- planDialogTitle.value = '新建演练计划'
- planDialogVisible.value = true
- }
- const editPlan = (plan) => {
- planForm.value = { ...plan, endTime: plan.endTime || null }
- planDialogTitle.value = '编辑演练计划'
- planDialogVisible.value = true
- }
- const submitPlanForm = async () => {
- if (!planForm.value.name) {
- ElMessage.warning('请输入演练名称')
- return
- }
- try {
- const d = planForm.value
- if (d.id) await updatePracticalPlan(d)
- else await savePracticalPlan(d)
- if (isUnmounted) return
- ElMessage.success('保存成功')
- planDialogVisible.value = false
- loadData()
- } catch (e) {
- ElMessage.error('保存失败')
- }
- }
- const confirmDeletePlan = (plan) => {
- ElMessageBox.confirm('确认删除此演练计划?', '提示', { type: 'warning' })
- .then(async () => {
- await deletePracticalPlan(plan.id)
- ElMessage.success('删除成功')
- if (selectedPlanId.value === plan.id) {
- selectedPlanId.value = null
- currentPlan.value = null
- currentDrillRecord.value = null
- currentDrillInfo.value = null
- }
- loadData()
- })
- .catch(() => {})
- }
- const handleCreatePlan = () => { openPlanDialog() }
- const filterPlans = () => { /* 过滤由 computed 自动处理 */ }
- async function loadData() {
- try {
- const res = await getPracticalPlans()
- if (isUnmounted) return
- const rows = res.data || []
- plans.value = rows.map(d => ({
- id: d.id, name: d.name, category: d.category || '燃气',
- target: d.target, project: d.project, eventDesc: d.eventDesc || d.event,
- personnel: d.personnel, status: d.status || '计划中',
- endTime: d.endTime || d.updateTime, participantCount: d.participantCount || 0,
- progress: d.progress !== undefined ? d.progress : 0
- }))
- } catch (e) {
- console.error('加载计划失败', e)
- }
- }
- // ========== 演练记录相关 ==========
- const currentDrillInfo = ref(null)
- const currentDrillRecord = ref(null)
- const isDrilling = ref(false)
- const currentDrillStatus = ref('未开始')
- const drillActions = ref([])
- const monitoringInfo = ref([])
- const dispatchForm = ref({ instruction: '', forces: [], commandCenter: false, casualties: '' })
- const decisionForm = ref({ responseLevel: '三级响应', command: '', evacuationRange: '', reinforcement: false, remark: '' })
- const releaseChannels = ref([])
- const releaseContent = ref('')
- // 加载演练记录
- async function loadDrillRecord(planId) {
- try {
- const res = await getDrillRecord(planId)
- if (isUnmounted) return
- const data = res.data
- if (data) {
- currentDrillRecord.value = data
- currentDrillInfo.value = { planId, status: data.status || '已记录' }
- currentDrillStatus.value = data.status || '已记录'
- if (data.dispatchInfo) dispatchForm.value = { ...dispatchForm.value, ...data.dispatchInfo }
- if (data.decisionInfo) decisionForm.value = { ...decisionForm.value, ...data.decisionInfo }
- if (data.releaseChannels) releaseChannels.value = data.releaseChannels
- if (data.releaseContent) releaseContent.value = data.releaseContent
- if (data.id) await loadMonitoring(data.id)
- if (data.actions) drillActions.value = data.actions
- } else {
- currentDrillRecord.value = null
- currentDrillInfo.value = null
- monitoringInfo.value = []
- drillActions.value = []
- }
- } catch (e) {
- currentDrillRecord.value = null
- currentDrillInfo.value = null
- monitoringInfo.value = []
- drillActions.value = []
- }
- }
- // 加载监测信息
- async function loadMonitoring(recordId) {
- if (!recordId) {
- monitoringInfo.value = []
- return
- }
- try {
- const res = await getDrillMonitoring(recordId)
- if (isUnmounted) return
- monitoringInfo.value = res.data || []
- } catch (e) {
- monitoringInfo.value = []
- }
- }
- // 保存全部演练记录
- const saveAllRecord = async () => {
- if (!currentDrillRecord.value) {
- ElMessage.warning('请先开始演练或选择有记录的计划')
- return
- }
- try {
- const recordData = {
- ...currentDrillRecord.value,
- planId: selectedPlanId.value,
- dispatchInfo: { ...dispatchForm.value },
- decisionInfo: { ...decisionForm.value },
- releaseChannels: releaseChannels.value,
- releaseContent: releaseContent.value,
- actions: drillActions.value
- }
- if (currentDrillRecord.value.id) {
- await updateDrillRecord(recordData)
- } else {
- const res = await saveDrillRecord(recordData)
- if (res.data) {
- const saved = typeof res.data === 'object' ? res.data : { id: res.data }
- currentDrillRecord.value.id = saved.id
- }
- }
- if (isUnmounted) return
- ElMessage.success('演练记录已保存')
- } catch (e) {
- ElMessage.error('保存失败')
- }
- }
- // ========== 监测信息弹窗 ==========
- const monitoringDialogVisible = ref(false)
- const monitoringDialogTitle = ref('添加监测数据')
- const monitoringForm = ref({
- id: null, sensorType: '', location: '', value: '', unit: '', alarmLevel: '一般', time: ''
- })
- const openAddMonitoring = () => {
- if (!currentDrillRecord.value) {
- ElMessage.warning('请先开始演练或加载演练记录')
- return
- }
- monitoringForm.value = {
- id: null, sensorType: '', location: '', value: '', unit: '', alarmLevel: '一般',
- time: new Date().toLocaleString()
- }
- monitoringDialogTitle.value = '添加监测数据'
- monitoringDialogVisible.value = true
- }
- const openEditMonitoring = (row) => {
- monitoringForm.value = { ...row }
- monitoringDialogTitle.value = '编辑监测数据'
- monitoringDialogVisible.value = true
- }
- const submitMonitoringForm = async () => {
- if (!monitoringForm.value.sensorType || !monitoringForm.value.location) {
- ElMessage.warning('请填写传感器类型和监测位置')
- return
- }
- try {
- const data = { ...monitoringForm.value, recordId: currentDrillRecord.value.id }
- await saveDrillMonitoring(data)
- if (isUnmounted) return
- ElMessage.success('保存成功')
- monitoringDialogVisible.value = false
- if (currentDrillRecord.value.id) await loadMonitoring(currentDrillRecord.value.id)
- } catch (e) {
- ElMessage.error('保存失败')
- }
- }
- const confirmDeleteMonitoring = (row) => {
- ElMessageBox.confirm('确认删除此监测数据?', '提示', { type: 'warning' })
- .then(async () => {
- await deleteDrillMonitoring(row.id)
- ElMessage.success('删除成功')
- if (currentDrillRecord.value?.id) await loadMonitoring(currentDrillRecord.value.id)
- })
- .catch(() => {})
- }
- const getAlarmLevelType = (level) => {
- const types = { 一般: 'info', 较大: 'warning', 重大: 'danger', 特别重大: 'danger' }
- return types[level] || 'info'
- }
- // ========== 对外发布 ==========
- const saveReleaseInfo = () => {
- if (!releaseChannels.value.length && !releaseContent.value) {
- ElMessage.warning('请填写发布渠道和内容')
- return
- }
- saveAllRecord()
- }
- const previewRelease = () => {
- if (!releaseContent.value) {
- ElMessage.warning('请先输入发布内容')
- return
- }
- ElMessageBox.alert(releaseContent.value, '发布内容预览', { confirmButtonText: '确定' })
- }
- // ========== 统计分析子系统 ==========
- const analysisResult = ref(null)
- const canSendToSubsystems = computed(() => !!currentDrillRecord.value)
- const showAnalysisReport = async () => {
- if (!selectedPlanId.value) {
- ElMessage.warning('请先选择演练计划')
- return
- }
- try {
- const res = await getDrillAnalysis(selectedPlanId.value)
- if (isUnmounted) return
- if (res.data) {
- analysisResult.value = res.data
- ElMessage.success('分析报表已加载')
- } else {
- ElMessage.info('暂无分析数据,请先完成演练并保存记录')
- }
- } catch (e) {
- ElMessage.error('加载分析数据失败')
- }
- }
- const exportAnalysisReport = async () => {
- if (!selectedPlanId.value) {
- ElMessage.warning('请先选择演练计划')
- return
- }
- try {
- const data = await exportDrillReport(selectedPlanId.value)
- const blob = new Blob(['\ufeff' + data], { type: 'text/plain;charset=utf-8' })
- const url = window.URL.createObjectURL(blob)
- const a = document.createElement('a')
- a.href = url
- a.download = `演练分析报告_${currentPlan.value?.name || ''}.txt`
- a.click()
- window.URL.revokeObjectURL(url)
- ElMessage.success('报告已导出')
- } catch (e) {
- ElMessage.error('导出失败')
- }
- }
- const sendToAnalysisSubsystems = async () => {
- if (!currentDrillRecord.value) {
- ElMessage.warning('请先录入演练结果信息')
- return
- }
- try {
- const sendData = {
- ...currentDrillRecord.value,
- planId: selectedPlanId.value,
- planName: currentPlan.value?.name,
- dispatchInfo: { ...dispatchForm.value },
- decisionInfo: { ...decisionForm.value },
- releaseChannels: releaseChannels.value,
- releaseContent: releaseContent.value,
- monitoringInfo: monitoringInfo.value,
- actions: drillActions.value
- }
- await sendDrillAnalysis(sendData)
- if (isUnmounted) return
- ElNotification({
- title: '发送成功',
- message: '演练分析数据已成功发送至统计分析和应急评估子系统',
- type: 'success',
- duration: 3000
- })
- } catch (e) {
- ElMessage.error('发送失败')
- }
- }
- // ========== 演练流程控制 ==========
- const startDrill = async (plan) => {
- try {
- await updatePlanStatus(plan.id, '进行中')
- if (isUnmounted) return
- plan.status = '进行中'
- plan.progress = Math.max(plan.progress, 10)
- selectedPlanId.value = plan.id
- currentPlan.value = plan
- isDrilling.value = true
- currentDrillStatus.value = '进行中'
- currentDrillInfo.value = { planId: plan.id, status: '进行中' }
- drillActions.value = [
- { time: new Date().toLocaleString(), type: 'primary', message: `开始演练:${plan.name}` }
- ]
- currentDrillRecord.value = {
- drillProjectName: plan.name,
- recordTime: new Date().toLocaleString(),
- drillType: '实战演练',
- participatingDept: plan.personnel || '—',
- responsiblePerson: '—',
- planId: plan.id,
- status: '进行中',
- reportTime: new Date().toLocaleString(),
- reportMethod: '电话',
- reporter: '',
- contactPhone: '',
- eventLocation: '',
- eventDescription: plan.eventDesc || ''
- }
- dispatchForm.value = { instruction: '', forces: [], commandCenter: false, casualties: '' }
- decisionForm.value = { responseLevel: '三级响应', command: '', evacuationRange: '', reinforcement: false, remark: '' }
- releaseChannels.value = []
- releaseContent.value = ''
- monitoringInfo.value = []
- ElMessage.success('演练已开始,请录入演练过程信息')
- } catch (e) {
- ElMessage.error('启动演练失败')
- }
- }
- const stopDrill = async () => {
- if (!currentDrillRecord.value) return
- try {
- await saveAllRecord()
- if (selectedPlanId.value) {
- await updatePlanStatus(selectedPlanId.value, '已完成')
- }
- if (isUnmounted) return
- isDrilling.value = false
- currentDrillStatus.value = '已完成'
- currentDrillRecord.value.status = '已完成'
- drillActions.value.push({ time: new Date().toLocaleString(), type: 'success', message: '演练结束,记录已保存' })
- const plan = plans.value.find(p => p.id === selectedPlanId.value)
- if (plan) {
- plan.status = '已完成'
- plan.progress = 100
- }
- ElMessage.success('演练已结束,记录已保存')
- } catch (e) {
- ElMessage.error('结束演练失败')
- }
- }
- const viewDrillRecord = async (plan) => {
- selectedPlanId.value = plan.id
- currentPlan.value = plan
- await loadDrillRecord(plan.id)
- if (currentDrillRecord.value) {
- currentDrillInfo.value = { planId: plan.id, status: currentDrillRecord.value.status || '已记录' }
- currentDrillStatus.value = currentDrillRecord.value.status || '已记录'
- if (!drillActions.value.length) {
- drillActions.value = [
- { time: currentDrillRecord.value.recordTime, type: 'info', message: `查看记录:${plan.name}` }
- ]
- }
- ElMessage.success('已加载演练记录')
- } else {
- ElMessage.info('该计划暂无演练记录,请先开始演练')
- }
- }
- // ========== 生命周期 ==========
- onMounted(async () => {
- try {
- await Promise.all([loadData(), loadTemplates()])
- } catch (e) {
- console.error(e)
- }
- })
- onBeforeUnmount(() => {
- isUnmounted = true
- })
- </script>
- <style scoped lang="scss">
- /* ========== 实战演练管理容器 ==========
- .practical-drill-container {
- padding: 20px;
- min-height: calc(100vh - 160px);
- }
- /* 顶部操作栏 */
- .drill-top-bar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- padding: 20px;
- background: white;
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- .top-left {
- display: flex;
- align-items: center;
- gap: 16px;
- .page-title {
- font-size: 24px;
- font-weight: bold;
- color: #303133;
- margin: 0;
- }
- }
- }
- /* 主布局 */
- .drill-main-layout {
- display: grid;
- grid-template-columns: 350px 1fr;
- gap: 20px;
- min-height: calc(100vh - 220px);
- }
- .left-column {
- display: flex;
- flex-direction: column;
- gap: 20px;
- }
- .right-column {
- display: flex;
- flex-direction: column;
- gap: 20px;
- }
- /* 通用面板样式 */
- .panel {
- background: white;
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- overflow: hidden;
- .panel-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 14px 16px;
- background: linear-gradient(135deg, #f5f7fa 0%, #e9ecef 100%);
- border-bottom: 2px solid #e4e7ed;
- font-weight: bold;
- font-size: 15px;
- color: #303133;
- gap: 8px;
- span {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .el-icon {
- color: #409eff;
- font-size: 18px;
- }
- }
- .panel-body {
- padding: 16px;
- max-height: 600px;
- overflow-y: auto;
- }
- }
- /* 模板卡片 */
- .template-panel, .plan-panel {
- .panel-body {
- max-height: none;
- }
- }
- .template-list, .plan-list {
- .template-card,
- .plan-card {
- border: 1px solid #ebeef5;
- border-radius: 6px;
- padding: 12px;
- margin-bottom: 10px;
- background: #fafafa;
- transition: all 0.3s;
- cursor: pointer;
- position: relative;
- &:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- border-color: #409eff;
- }
- &.active {
- border-left: 4px solid #409eff;
- background: #ecf5ff;
- }
- .template-card-header,
- .plan-card-content {
- margin-bottom: 10px;
- h4 {
- margin: 0 0 8px 0;
- font-size: 16px;
- font-weight: bold;
- color: #303133;
- }
- }
- .template-name,
- .plan-title {
- font-size: 15px;
- font-weight: bold;
- color: #303133;
- margin-bottom: 6px;
- }
- .template-meta,
- .plan-category {
- display: flex;
- gap: 12px;
- margin-bottom: 8px;
- font-size: 12px;
- color: #909399;
- .meta-item,
- .plan-date {
- display: flex;
- align-items: center;
- gap: 4px;
- }
- }
- .plan-info-list {
- .info-item {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- margin-bottom: 4px;
- .info-text {
- flex: 1;
- color: #606266;
- }
- .info-value {
- font-weight: bold;
- color: #409eff;
- }
- }
- }
- .template-actions,
- .plan-actions {
- display: flex;
- gap: 8px;
- margin-top: 10px;
- padding-top: 10px;
- border-top: 1px dashed #ebeef5;
- .el-button {
- font-size: 12px;
- }
- }
- }
- }
- .plan-status-badge {
- position: absolute;
- top: 10px;
- right: 10px;
- padding: 4px 10px;
- border-radius: 12px;
- font-size: 12px;
- font-weight: bold;
-
- &.计划中 { background: #f0f0f0; color: #909399; }
- &.进行中 { background: #fff4e6; color: #e6a23c; }
- &.已完成 { background: #f0f9ff; color: #409eff; }
- }
- .search-filter {
- display: flex;
- gap: 8px;
- margin-bottom: 12px;
- }
- /* 当前演练状态 */
- .current-drill-panel {
- .drill-status-summary {
- .status-badge.large {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- padding: 12px 24px;
- border-radius: 20px;
- font-size: 16px;
- font-weight: bold;
- background: #f0f9ff;
- color: #409eff;
- margin-bottom: 16px;
- }
- .drill-timeline {
- margin-top: 16px;
- }
- }
- }
- /* 记录面板 */
- .record-panel {
- .record-section {
- margin-top: 20px;
- padding-top: 16px;
- border-top: 1px dashed #ebeef5;
- &:first-child {
- margin-top: 0;
- padding-top: 0;
- border-top: none;
- }
- .section-title-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
- .section-title {
- margin-bottom: 0;
- }
- }
- .section-title {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 14px;
- font-weight: bold;
- color: #303133;
- margin-bottom: 12px;
- .el-icon {
- color: #409eff;
- }
- }
- }
- }
- /* 分析面板 */
- .analysis-panel {
- .analysis-summary {
- .analysis-time {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 13px;
- color: #909399;
- margin-bottom: 16px;
- }
- .analysis-basic, .analysis-task, .analysis-comprehensive {
- margin-bottom: 20px;
- padding: 16px;
- border: 1px solid #ebeef5;
- border-radius: 8px;
- background: #fafafa;
- &:last-child { margin-bottom: 0; }
- }
- .basic-title,
- .task-title,
- .comprehensive-title {
- font-size: 15px;
- font-weight: bold;
- color: #303133;
- margin-bottom: 12px;
- }
- /* 能力评估网格 */
- .capability-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 16px;
- .capability-item {
- display: flex;
- flex-direction: column;
- gap: 6px;
- .cap-label {
- font-size: 12px;
- color: #606266;
- }
- .cap-score {
- font-size: 18px;
- font-weight: bold;
- color: #409eff;
- }
- }
- }
- /* 任务统计 */
- .task-stats {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 12px;
- .task-stat-item {
- display: flex;
- flex-direction: column;
- gap: 4px;
- text-align: center;
- padding: 12px;
- background: white;
- border-radius: 6px;
- .stat-label {
- font-size: 12px;
- color: #909399;
- }
- .stat-value {
- font-size: 20px;
- font-weight: bold;
- &.highlight-blue { color: #409eff; }
- &.highlight-green { color: #67c23a; }
- &.highlight-orange { color: #e6a23c; }
- &.highlight-red { color: #f56c6c; }
- }
- }
- }
- /* 综合评估 */
- .eval-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 12px;
- margin-bottom: 16px;
- .eval-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 16px;
- background: white;
- border-radius: 6px;
- .eval-label {
- font-size: 12px;
- color: #909399;
- margin-bottom: 8px;
- }
- .eval-value {
- font-size: 18px;
- font-weight: bold;
- &.success { color: #67c23a; }
- &.warning { color: #e6a23c; }
- &.info { color: #409eff; }
- &.danger { color: #f56c6c; }
- }
- &.level-high { border-left: 4px solid #67c23a; }
- &.level-middle { border-left: 4px solid #e6a23c; }
- &.level-medium { border-left: 4px solid #409eff; }
- &.level-low { border-left: 4px solid #f56c6c; }
- }
- }
- .eval-description,
- .suggestion-section {
- margin-top: 12px;
- font-size: 13px;
- line-height: 1.6;
- color: #606266;
- strong {
- color: #303133;
- display: block;
- margin-bottom: 4px;
- }
- p, ul {
- margin: 4px 0;
- padding-left: 20px;
- }
- li {
- margin-bottom: 4px;
- }
- }
- }
- }
- /* 响应式布局 */
- @media (max-width: 1200px) {
- .drill-main-layout {
- grid-template-columns: 1fr;
- }
- .left-column,
- .right-column {
- min-height: auto;
- }
- }
- </style>
|