learshaw 2 ヶ月 前
コミット
594cb97fa6

+ 87 - 0
ems-ui-cloud/src/api/inspection/plan.js

@@ -0,0 +1,87 @@
+import request from '@/utils/request'
+
+// 查询巡检计划列表
+export function listPlan(query) {
+  return request({
+    url: '/ems/inspection/plan/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询巡检计划详细
+export function getPlan(id) {
+  return request({
+    url: '/ems/inspection/plan/' + id,
+    method: 'get'
+  })
+}
+
+// 根据计划代码查询
+export function getPlanByCode(planCode) {
+  return request({
+    url: '/ems/inspection/plan/code/' + planCode,
+    method: 'get'
+  })
+}
+
+// 新增巡检计划
+export function addPlan(data) {
+  return request({
+    url: '/ems/inspection/plan',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改巡检计划
+export function updatePlan(data) {
+  return request({
+    url: '/ems/inspection/plan',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除巡检计划
+export function delPlan(id) {
+  return request({
+    url: '/ems/inspection/plan/' + id,
+    method: 'delete'
+  })
+}
+
+// 执行自动巡检
+export function executePlan(planCode) {
+  return request({
+    url: '/ems/inspection/plan/execute/' + planCode,
+    method: 'post'
+  })
+}
+
+// 提交手动巡检报告
+export function submitManualReport(data) {
+  return request({
+    url: '/ems/inspection/plan/submitManual',
+    method: 'post',
+    data: data
+  })
+}
+
+// 根据计划代码获取最新报告
+export function getLatestReportByPlanCode(planCode) {
+  return request({
+    url: '/ems/inspection/report/latest/' + planCode,
+    method: 'get'
+  })
+}
+
+// 导出巡检计划
+export function exportPlan(query) {
+  return request({
+    url: '/ems/inspection/plan/export',
+    method: 'post',
+    params: query,
+    responseType: 'blob'
+  })
+}

+ 71 - 0
ems-ui-cloud/src/api/inspection/report.js

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 查询巡检报告列表
+export function listReport(query) {
+  return request({
+    url: '/ems/inspection/report/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询巡检报告详细
+export function getReport(id) {
+  return request({
+    url: '/ems/inspection/report/' + id,
+    method: 'get'
+  })
+}
+
+// 根据报告代码获取详情
+export function getReportByCode(reportCode) {
+  return request({
+    url: '/ems/inspection/report/code/' + reportCode,
+    method: 'get'
+  })
+}
+
+// 修改巡检报告(通用)
+export function updateReport(data) {
+  return request({
+    url: '/ems/inspection/report',
+    method: 'put',
+    data: data
+  })
+}
+
+// 更新手动巡检报告(专用接口,支持更新resultSummary)
+export function updateManualReport(data) {
+  return request({
+    url: '/ems/inspection/report/updateManual',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除巡检报告
+export function delReport(ids) {
+  return request({
+    url: '/ems/inspection/report/' + ids,
+    method: 'delete'
+  })
+}
+
+// 导出巡检报告
+export function exportReport(query) {
+  return request({
+    url: '/ems/inspection/report/export',
+    method: 'post',
+    params: query,
+    responseType: 'blob'
+  })
+}
+
+// 提交手动巡检结果
+export function submitManual(data) {
+  return request({
+    url: '/ems/inspection/report/submit',
+    method: 'post',
+    data: data
+  })
+}

+ 69 - 0
ems-ui-cloud/src/api/inspection/scheduler.js

@@ -0,0 +1,69 @@
+import request from '@/utils/request'
+
+/**
+ * 巡检调度器管理API
+ */
+
+// 获取调度器状态
+export function getSchedulerStatus() {
+  return request({
+    url: '/ems/inspection/scheduler/status',
+    method: 'get'
+  })
+}
+
+// 重新加载所有巡检计划
+export function reloadScheduler() {
+  return request({
+    url: '/ems/inspection/scheduler/reload',
+    method: 'post'
+  })
+}
+
+// 刷新指定巡检计划
+export function refreshPlan(planCode) {
+  return request({
+    url: '/ems/inspection/scheduler/refresh/' + planCode,
+    method: 'post'
+  })
+}
+
+// 注册指定巡检计划到调度器
+export function registerPlan(planCode) {
+  return request({
+    url: '/ems/inspection/scheduler/register/' + planCode,
+    method: 'post'
+  })
+}
+
+// 注销指定巡检计划的定时任务
+export function unregisterPlan(planCode) {
+  return request({
+    url: '/ems/inspection/scheduler/unregister/' + planCode,
+    method: 'post'
+  })
+}
+
+// 手动触发一次巡检执行
+export function triggerInspection(planCode) {
+  return request({
+    url: '/ems/inspection/scheduler/trigger/' + planCode,
+    method: 'post'
+  })
+}
+
+// 检查计划是否已注册
+export function isRegistered(planCode) {
+  return request({
+    url: '/ems/inspection/scheduler/registered/' + planCode,
+    method: 'get'
+  })
+}
+
+// 检查计划是否正在执行
+export function isExecuting(planCode) {
+  return request({
+    url: '/ems/inspection/scheduler/executing/' + planCode,
+    method: 'get'
+  })
+}

+ 0 - 44
ems-ui-cloud/src/api/task/inspectionPlan.js

@@ -1,44 +0,0 @@
-import request from '@/utils/request'
-
-// 查询巡检计划列表
-export function listInspectionPlan(query) {
-  return request({
-    url: '/ems/inspectionPlan/list',
-    method: 'get',
-    params: query
-  })
-}
-
-// 查询巡检计划详细
-export function getInspectionPlan(id) {
-  return request({
-    url: '/ems/inspectionPlan/' + id,
-    method: 'get'
-  })
-}
-
-// 新增巡检计划
-export function addInspectionPlan(data) {
-  return request({
-    url: '/ems/inspectionPlan',
-    method: 'post',
-    data: data
-  })
-}
-
-// 修改巡检计划
-export function updateInspectionPlan(data) {
-  return request({
-    url: '/ems/inspectionPlan',
-    method: 'put',
-    data: data
-  })
-}
-
-// 删除巡检计划
-export function delInspectionPlan(id) {
-  return request({
-    url: '/ems/inspectionPlan/' + id,
-    method: 'delete'
-  })
-}

+ 0 - 44
ems-ui-cloud/src/api/task/inspectionReport.js

@@ -1,44 +0,0 @@
-import request from '@/utils/request'
-
-// 查询巡检报告列表
-export function listInspectionReport(query) {
-  return request({
-    url: '/ems/inspectionReport/list',
-    method: 'get',
-    params: query
-  })
-}
-
-// 查询巡检报告详细
-export function getInspectionReport(id) {
-  return request({
-    url: '/ems/inspectionReport/' + id,
-    method: 'get'
-  })
-}
-
-// 新增巡检报告
-export function addInspectionReport(data) {
-  return request({
-    url: '/ems/inspectionReport',
-    method: 'post',
-    data: data
-  })
-}
-
-// 修改巡检报告
-export function updateInspectionReport(data) {
-  return request({
-    url: '/ems/inspectionReport',
-    method: 'put',
-    data: data
-  })
-}
-
-// 删除巡检报告
-export function delInspectionReport(id) {
-  return request({
-    url: '/ems/inspectionReport/' + id,
-    method: 'delete'
-  })
-}

+ 100 - 0
ems-ui-cloud/src/enums/InspectionEnums.js

@@ -0,0 +1,100 @@
+/**
+ * 巡检模块枚举定义
+ */
+
+// 计划类型
+export const PLAN_TYPE_OPTIONS = [
+  { value: 1, label: '手动巡检', color: '#409EFF' },
+  { value: 2, label: '自动巡检', color: '#67C23A' }
+]
+
+// 计划状态(通用)
+export const PLAN_STATUS_OPTIONS = [
+  { value: 0, label: '待执行' },
+  { value: 1, label: '执行中' },
+  { value: 2, label: '已完成' },
+  { value: 3, label: '已取消' }
+]
+
+// 目标类型
+export const TARGET_TYPE_OPTIONS = [
+  { value: 0, label: '区域', icon: 'el-icon-location-outline' },
+  { value: 1, label: '设施', icon: 'el-icon-office-building' },
+  { value: 2, label: '设备', icon: 'el-icon-cpu' }
+]
+
+// 结果状态
+export const RESULT_STATUS_OPTIONS = [
+  { value: 0, label: '正常', type: 'success', icon: 'el-icon-success' },
+  { value: 1, label: '异常', type: 'danger', icon: 'el-icon-error' },
+  { value: 2, label: '部分异常', type: 'warning', icon: 'el-icon-warning' }
+]
+
+// 检查类型
+export const CHECK_TYPE_OPTIONS = [
+  { value: 1, label: '范围检查' },
+  { value: 2, label: '等值检查' },
+  { value: 3, label: '非空检查' },
+  { value: 4, label: '在线状态' }
+]
+
+/**
+ * 获取计划类型标签
+ */
+export function getPlanTypeLabel(type) {
+  const option = PLAN_TYPE_OPTIONS.find(item => item.value === type)
+  return option ? option.label : '未知'
+}
+
+/**
+ * 获取计划类型颜色
+ */
+export function getPlanTypeColor(type) {
+  const option = PLAN_TYPE_OPTIONS.find(item => item.value === type)
+  return option ? option.color : '#909399'
+}
+
+/**
+ * 获取计划状态信息
+ */
+export function getPlanStatusInfo(status) {
+  const map = {
+    0: { type: 'warning', label: '待执行' },
+    1: { type: 'primary', label: '执行中' },
+    2: { type: 'success', label: '已完成' },
+    3: { type: 'info', label: '已取消' }
+  }
+  return map[status] || { type: 'info', label: '未知' }
+}
+
+/**
+ * 获取目标类型标签
+ */
+export function getTargetTypeLabel(type) {
+  const option = TARGET_TYPE_OPTIONS.find(item => item.value === type)
+  return option ? option.label : '未知'
+}
+
+/**
+ * 获取目标类型图标
+ */
+export function getTargetTypeIcon(type) {
+  const option = TARGET_TYPE_OPTIONS.find(item => item.value === type)
+  return option ? option.icon : 'el-icon-question'
+}
+
+/**
+ * 获取结果状态信息
+ */
+export function getResultStatusInfo(status) {
+  const option = RESULT_STATUS_OPTIONS.find(item => item.value === status)
+  return option || { type: 'info', label: '未知', icon: 'el-icon-question' }
+}
+
+/**
+ * 获取检查类型标签
+ */
+export function getCheckTypeLabel(type) {
+  const option = CHECK_TYPE_OPTIONS.find(item => item.value === type)
+  return option ? option.label : '未知'
+}

+ 216 - 0
ems-ui-cloud/src/views/inspection/plan/components/PlanDetail.vue

@@ -0,0 +1,216 @@
+<template>
+  <el-dialog title="巡检计划详情" :visible.sync="visible" width="850px" append-to-body>
+    <div v-loading="loading" class="plan-detail">
+      <!-- 基本信息 -->
+      <el-descriptions :column="2" border size="medium">
+        <el-descriptions-item label="计划代码">{{ plan.planCode }}</el-descriptions-item>
+        <el-descriptions-item label="计划名称">{{ plan.planName }}</el-descriptions-item>
+        <el-descriptions-item label="归属区域">{{ plan.areaName }}</el-descriptions-item>
+        <el-descriptions-item label="计划类型">
+          <el-tag :style="{ backgroundColor: getPlanTypeColor(plan.planType), borderColor: getPlanTypeColor(plan.planType) }"
+                  effect="dark" size="small">
+            {{ getPlanTypeLabel(plan.planType) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="目标类型">{{ getTargetTypeLabel(plan.targetType) }}</el-descriptions-item>
+        <el-descriptions-item label="计划状态">
+          <el-tag :type="getPlanStatusInfo(plan.planStatus).type" size="small">
+            {{ getPlanStatusInfo(plan.planStatus).label }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="执行人" v-if="plan.planType === 1">{{ plan.executor || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="计划时间">{{ plan.planTime || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ plan.createTime }}</el-descriptions-item>
+        <el-descriptions-item label="计划描述" :span="2">{{ plan.description || '-' }}</el-descriptions-item>
+      </el-descriptions>
+
+      <!-- 调度配置(自动巡检) -->
+      <template v-if="plan.planType === 2">
+        <div class="section-title">
+          <i class="el-icon-time"></i> 定时调度配置
+        </div>
+        <el-descriptions :column="3" border size="medium">
+          <el-descriptions-item label="Cron表达式">
+            <code class="cron-code">{{ plan.cronExpression || '-' }}</code>
+          </el-descriptions-item>
+          <el-descriptions-item label="调度状态">
+            <el-tag v-if="plan.scheduleEnabled === 1" type="success" size="small" effect="light">
+              <i class="el-icon-video-play"></i> 已启用
+            </el-tag>
+            <el-tag v-else type="info" size="small" effect="light">
+              <i class="el-icon-video-pause"></i> 已禁用
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="累计执行">
+            <span class="stat-number">{{ plan.execCount || 0 }}</span> 次
+          </el-descriptions-item>
+          <el-descriptions-item label="上次执行时间">
+            {{ plan.lastExecTime || '暂未执行' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="下次执行时间" :span="2">
+            <span v-if="plan.scheduleEnabled === 1 && plan.nextExecTime">
+              {{ plan.nextExecTime }}
+            </span>
+            <span v-else class="text-muted">-</span>
+          </el-descriptions-item>
+        </el-descriptions>
+      </template>
+
+      <!-- 巡检目标 -->
+      <div class="section-title">
+        <i class="el-icon-aim"></i> 巡检目标
+      </div>
+      <div class="target-tags">
+        <el-tag v-for="(name, index) in targetNameList" :key="index" effect="plain" size="medium" style="margin: 3px">
+          {{ name }}
+        </el-tag>
+        <span v-if="targetNameList.length === 0" class="no-data">暂无</span>
+      </div>
+
+      <!-- 巡检规则(自动巡检) -->
+      <template v-if="plan.planType === 2 && plan.rules && plan.rules.length > 0">
+        <div class="section-title">
+          <i class="el-icon-set-up"></i> 巡检规则
+        </div>
+        <el-table :data="plan.rules" border size="small">
+          <el-table-column label="规则名称" prop="ruleName" width="140" />
+          <el-table-column label="设备模型" prop="deviceModelName" width="150">
+            <template slot-scope="scope">
+              {{ scope.row.deviceModelName || '全部设备' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="检查属性" prop="attrName" width="120" />
+          <el-table-column label="检查类型" width="100">
+            <template slot-scope="scope">
+              {{ getCheckTypeLabel(scope.row.checkType) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="阈值配置">
+            <template slot-scope="scope">
+              <span v-if="scope.row.checkType === 1">{{ scope.row.minValue }} ~ {{ scope.row.maxValue }}</span>
+              <span v-else-if="scope.row.checkType === 2">= {{ scope.row.expectValue }}</span>
+              <span v-else-if="scope.row.checkType === 4">在线值: {{ scope.row.expectValue }}</span>
+              <span v-else>非空检查</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="状态" width="60" align="center">
+            <template slot-scope="scope">
+              <el-tag :type="scope.row.enabled === 1 ? 'success' : 'info'" size="mini">
+                {{ scope.row.enabled === 1 ? '启用' : '禁用' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+        </el-table>
+      </template>
+    </div>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="visible = false">关 闭</el-button>
+      <el-button type="primary" @click="handleEdit">编 辑</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { getPlan } from '@/api/inspection/plan'
+import {
+  getPlanTypeLabel,
+  getPlanTypeColor,
+  getPlanStatusInfo,
+  getTargetTypeLabel,
+  getCheckTypeLabel
+} from '@/enums/InspectionEnums'
+
+export default {
+  name: 'PlanDetail',
+  data() {
+    return {
+      visible: false,
+      loading: false,
+      plan: {}
+    }
+  },
+  computed: {
+    targetNameList() {
+      if (!this.plan.targetNames) return []
+      return this.plan.targetNames.split(',').filter(n => n)
+    }
+  },
+  methods: {
+    getPlanTypeLabel,
+    getPlanTypeColor,
+    getPlanStatusInfo,
+    getTargetTypeLabel,
+    getCheckTypeLabel,
+
+    async open(id) {
+      this.visible = true
+      this.loading = true
+      try {
+        const res = await getPlan(id)
+        this.plan = res.data || {}
+      } catch (e) {
+        this.$modal.msgError('获取详情失败')
+      } finally {
+        this.loading = false
+      }
+    },
+
+    handleEdit() {
+      this.visible = false
+      this.$parent.$refs.planForm.open(this.plan.id)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.plan-detail {
+  max-height: 60vh;
+  overflow-y: auto;
+}
+
+.section-title {
+  font-weight: bold;
+  font-size: 14px;
+  color: #303133;
+  margin: 20px 0 10px 0;
+  padding-left: 10px;
+  border-left: 3px solid #409EFF;
+}
+
+.section-title i {
+  margin-right: 5px;
+  color: #409EFF;
+}
+
+.target-tags {
+  padding: 10px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  min-height: 40px;
+}
+
+.no-data {
+  color: #909399;
+  font-size: 13px;
+}
+
+.cron-code {
+  background: #f5f7fa;
+  padding: 2px 8px;
+  border-radius: 3px;
+  color: #409EFF;
+  font-family: monospace;
+}
+
+.stat-number {
+  font-size: 16px;
+  font-weight: bold;
+  color: #409EFF;
+}
+
+.text-muted {
+  color: #909399;
+}
+</style>

+ 413 - 0
ems-ui-cloud/src/views/inspection/plan/components/PlanForm.vue

@@ -0,0 +1,413 @@
+<template>
+  <el-dialog :title="title" :visible.sync="visible" width="950px" append-to-body
+             :close-on-click-modal="false" @close="handleClose"
+  >
+    <el-form ref="form" :model="form" :rules="rules" label-width="100px" v-loading="formLoading">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="计划名称" prop="planName">
+            <el-input v-model="form.planName" placeholder="请输入计划名称" maxlength="64" show-word-limit/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="归属区域" prop="areaCode">
+            <el-select v-model="form.areaCode" placeholder="请选择归属区域" style="width: 100%" filterable>
+              <el-option v-for="item in flatAreaOptions" :key="item.id" :label="item.label" :value="item.id"/>
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="计划类型">
+            <el-tag :type="form.planType === 2 ? '' : 'warning'">
+              {{ form.planType === 2 ? '自动巡检' : '手动巡检' }}
+            </el-tag>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="目标类型" prop="targetType">
+            <el-radio-group v-model="form.targetType" @change="handleTargetTypeChange">
+              <el-radio-button v-for="item in TARGET_TYPE_OPTIONS" :key="item.value" :label="item.value">
+                {{ item.label }}
+              </el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="20">
+        <el-col :span="12" v-if="form.planType === 1">
+          <el-form-item label="默认执行人" prop="executor">
+            <el-input v-model="form.executor" placeholder="请输入执行人姓名" maxlength="32"/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="计划时间" prop="planTime">
+            <el-date-picker v-model="form.planTime" type="datetime"
+                            format="yyyy-MM-dd HH:mm" value-format="yyyy-MM-dd HH:mm:00"
+                            placeholder="选择计划执行时间" style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+
+      <!-- 自动巡检:调度配置 -->
+      <template v-if="form.planType === 2">
+        <el-divider content-position="left"><span class="divider-title">定时调度配置</span></el-divider>
+
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="Cron表达式" prop="cronExpression">
+              <el-input v-model="form.cronExpression" placeholder="如:0 0 8 * * ?">
+                <template slot="append">
+                  <el-button icon="el-icon-question" @click="showCronHelp">帮助</el-button>
+                </template>
+              </el-input>
+              <div class="cron-preview" v-if="form.cronExpression">
+                <i class="el-icon-time"></i>
+                {{ cronDescription }}
+              </div>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="启用调度">
+              <el-switch v-model="form.scheduleEnabled" :active-value="1" :inactive-value="0"
+                         active-text="启用" inactive-text="禁用" active-color="#13ce66">
+              </el-switch>
+              <span class="schedule-tip" v-if="form.scheduleEnabled === 1 && !form.cronExpression">
+                <i class="el-icon-warning" style="color: #E6A23C"></i>
+                请配置Cron表达式
+              </span>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <!-- Cron快捷选择 -->
+        <el-row :gutter="20">
+          <el-col :span="24">
+            <el-form-item label="快捷选择">
+              <el-button-group>
+                <el-button size="small" @click="setCron('0 0 8 * * ?')">每天8:00</el-button>
+                <el-button size="small" @click="setCron('0 0 8,14 * * ?')">每天8:00/14:00</el-button>
+                <el-button size="small" @click="setCron('0 0 9 * * 2')">每周一9:00</el-button>
+                <el-button size="small" @click="setCron('0 0 9 1 * ?')">每月1号9:00</el-button>
+                <el-button size="small" @click="setCron('0 */30 * * * ?')">每30分钟</el-button>
+                <el-button size="small" @click="setCron('0 0 */2 * * ?')">每2小时</el-button>
+              </el-button-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </template>
+
+      <el-form-item label="巡检目标" prop="targetCodeList">
+        <target-selector ref="targetSelector" v-model="form.targetCodeList"
+                         :target-type="form.targetType" :area-code="form.areaCode" @change="handleTargetChange"
+        />
+      </el-form-item>
+
+      <el-form-item label="计划描述" prop="description">
+        <el-input v-model="form.description" type="textarea" :rows="2" maxlength="256" show-word-limit/>
+      </el-form-item>
+
+      <template v-if="form.planType === 2">
+        <el-divider content-position="left"><span class="divider-title">巡检规则配置</span></el-divider>
+        <rule-config ref="ruleConfig" v-model="form.rules"/>
+      </template>
+    </el-form>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="handleClose">取 消</el-button>
+      <el-button type="primary" @click="submitForm" :loading="submitting">确 定</el-button>
+    </div>
+
+    <!-- Cron帮助对话框 -->
+    <el-dialog title="Cron表达式帮助" :visible.sync="cronHelpVisible" width="600px" append-to-body>
+      <div class="cron-help">
+        <h4>Cron表达式格式</h4>
+        <p>格式: <code>秒 分 时 日 月 周 [年]</code></p>
+        <el-table :data="cronFields" border size="small">
+          <el-table-column label="字段" prop="field" width="80" />
+          <el-table-column label="允许值" prop="allowed" width="200" />
+          <el-table-column label="特殊字符" prop="special" />
+        </el-table>
+
+        <h4 style="margin-top: 20px">常用示例</h4>
+        <el-table :data="cronExamples" border size="small">
+          <el-table-column label="表达式" prop="cron" width="200" />
+          <el-table-column label="说明" prop="desc" />
+        </el-table>
+      </div>
+      <div slot="footer">
+        <el-button type="primary" @click="cronHelpVisible = false">知道了</el-button>
+      </div>
+    </el-dialog>
+  </el-dialog>
+</template>
+
+<script>
+import { getPlan, addPlan, updatePlan } from '@/api/inspection/plan'
+import { registerPlan, unregisterPlan } from '@/api/inspection/scheduler'
+import { PLAN_TYPE_OPTIONS, TARGET_TYPE_OPTIONS } from '@/enums/InspectionEnums'
+import TargetSelector from './TargetSelector.vue'
+import RuleConfig from './RuleConfig.vue'
+
+export default {
+  name: 'PlanForm',
+  components: { TargetSelector, RuleConfig },
+  props: {
+    areaOptions: { type: Array, default: () => [] }
+  },
+  data() {
+    const validateTargets = (rule, value, callback) => {
+      if (!value || value.length === 0) {
+        callback(new Error('请选择至少一个巡检目标'))
+      } else {
+        callback()
+      }
+    }
+    const validateCron = (rule, value, callback) => {
+      if (this.form.scheduleEnabled === 1 && !value) {
+        callback(new Error('启用调度时必须配置Cron表达式'))
+      } else {
+        callback()
+      }
+    }
+    return {
+      visible: false,
+      formLoading: false,
+      submitting: false,
+      title: '',
+      form: this.initForm(),
+      rules: {
+        planName: [{ required: true, message: '请输入计划名称', trigger: 'blur' }],
+        areaCode: [{ required: true, message: '请选择归属区域', trigger: 'change' }],
+        targetType: [{ required: true, message: '请选择目标类型', trigger: 'change' }],
+        targetCodeList: [{ required: true, validator: validateTargets, trigger: 'change' }],
+        executor: [{ required: true, message: '请输入执行人', trigger: 'blur' }],
+        cronExpression: [{ validator: validateCron, trigger: 'blur' }]
+      },
+      PLAN_TYPE_OPTIONS,
+      TARGET_TYPE_OPTIONS,
+      cronHelpVisible: false,
+      cronFields: [
+        { field: '秒', allowed: '0-59', special: ', - * /' },
+        { field: '分', allowed: '0-59', special: ', - * /' },
+        { field: '时', allowed: '0-23', special: ', - * /' },
+        { field: '日', allowed: '1-31', special: ', - * ? / L W' },
+        { field: '月', allowed: '1-12 或 JAN-DEC', special: ', - * /' },
+        { field: '周', allowed: '1-7 或 SUN-SAT', special: ', - * ? / L #' }
+      ],
+      cronExamples: [
+        { cron: '0 0 8 * * ?', desc: '每天早上8点执行' },
+        { cron: '0 0 8,14 * * ?', desc: '每天8点和14点执行' },
+        { cron: '0 0 9 * * 2', desc: '每周一早上9点执行' },
+        { cron: '0 0 9 1 * ?', desc: '每月1号早上9点执行' },
+        { cron: '0 */30 * * * ?', desc: '每30分钟执行一次' },
+        { cron: '0 0 */2 * * ?', desc: '每2小时执行一次' }
+      ]
+    }
+  },
+  computed: {
+    flatAreaOptions() {
+      const result = []
+      const flatten = (list, prefix = '') => {
+        list.forEach(item => {
+          result.push({ id: item.id, label: prefix + item.label })
+          if (item.children && item.children.length > 0) flatten(item.children, prefix + item.label + ' / ')
+        })
+      }
+      flatten(this.areaOptions)
+      return result
+    },
+    cronDescription() {
+      const cron = this.form.cronExpression
+      if (!cron) return ''
+      const parts = cron.split(' ')
+      if (parts.length < 6) return '表达式格式不正确'
+
+      const [second, minute, hour, day, month, week] = parts
+
+      // 每天执行
+      if (day === '*' && month === '*' && week === '?') {
+        if (hour.includes(',')) {
+          return `每天 ${hour.split(',').map(h => h + ':' + minute.padStart(2, '0')).join('、')} 执行`
+        }
+        if (hour.includes('/')) {
+          return `每${hour.split('/')[1]}小时执行一次`
+        }
+        return `每天 ${hour}:${minute.padStart(2, '0')} 执行`
+      }
+      // 每周执行
+      if (day === '?' && week !== '*') {
+        const weekMap = { '1': '周日', '2': '周一', '3': '周二', '4': '周三', '5': '周四', '6': '周五', '7': '周六' }
+        return `每${weekMap[week] || '周' + week} ${hour}:${minute.padStart(2, '0')} 执行`
+      }
+      // 每月执行
+      if (day !== '*' && day !== '?') {
+        return `每月${day}日 ${hour}:${minute.padStart(2, '0')} 执行`
+      }
+      // 分钟级
+      if (minute.includes('/')) {
+        return `每${minute.split('/')[1]}分钟执行一次`
+      }
+
+      return '自定义调度规则'
+    }
+  },
+  methods: {
+    initForm() {
+      return {
+        id: null,
+        planCode: null,
+        planName: '',
+        areaCode: null,
+        planType: 2,
+        targetType: 2,
+        targetCodeList: [],
+        targetNames: '',
+        planTime: null,
+        cronExpression: '',
+        scheduleEnabled: 0, // 新增字段:是否启用调度
+        executor: '',
+        description: '',
+        rules: []
+      }
+    },
+
+    async open(id, planType) {
+      this.form = this.initForm()
+      if (planType) {
+        this.form.planType = planType
+      }
+
+      this.title = id ? '编辑巡检计划' : '新增巡检计划'
+      this.visible = true
+
+      if (id) {
+        this.formLoading = true
+        try {
+          const res = await getPlan(id)
+          const data = res.data || {}
+          this.form = {
+            ...data,
+            targetCodeList: data.targetCodeList || [],
+            scheduleEnabled: data.scheduleEnabled || 0
+          }
+        } catch (e) {
+          this.$modal.msgError('获取计划详情失败')
+          this.visible = false
+        } finally {
+          this.formLoading = false
+        }
+      }
+    },
+
+    handleClose() {
+      this.visible = false
+      this.$refs.form.resetFields()
+      this.form = this.initForm()
+    },
+
+    handleTargetTypeChange() {
+      this.form.targetCodeList = []
+      this.form.targetNames = ''
+    },
+
+    handleTargetChange(names) {
+      this.form.targetNames = names
+    },
+
+    setCron(cron) {
+      this.form.cronExpression = cron
+    },
+
+    showCronHelp() {
+      this.cronHelpVisible = true
+    },
+
+    submitForm() {
+      this.$refs.form.validate(async (valid) => {
+        if (!valid) return
+
+        // 自动巡检校验
+        if (this.form.planType === 2) {
+          if (!this.form.rules || this.form.rules.length === 0) {
+            this.$modal.msgWarning('自动巡检请至少配置一条巡检规则')
+            return
+          }
+          if (this.form.scheduleEnabled === 1 && !this.form.cronExpression) {
+            this.$modal.msgWarning('启用调度时请配置Cron表达式')
+            return
+          }
+        }
+
+        const data = {
+          ...this.form,
+          targetCodes: JSON.stringify(this.form.targetCodeList)
+        }
+
+        this.submitting = true
+        try {
+          const isEdit = !!this.form.id
+          if (isEdit) {
+            await updatePlan(data)
+          } else {
+            await addPlan(data)
+          }
+
+          // 如果是自动巡检且启用了调度,同步注册到调度器
+          if (this.form.planType === 2 && this.form.scheduleEnabled === 1 && this.form.cronExpression) {
+            try {
+              await registerPlan(this.form.planCode || data.planCode)
+            } catch (e) {
+              console.warn('注册调度器失败,但计划已保存', e)
+            }
+          }
+
+          this.$modal.msgSuccess(isEdit ? '修改成功' : '新增成功')
+          this.handleClose()
+          this.$emit('success')
+        } catch (err) {
+          this.$modal.msgError(err.message || '操作失败')
+        } finally {
+          this.submitting = false
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.divider-title {
+  font-weight: bold;
+  color: #409EFF;
+  font-size: 14px;
+}
+
+.cron-preview {
+  margin-top: 5px;
+  font-size: 12px;
+  color: #67C23A;
+}
+
+.schedule-tip {
+  margin-left: 10px;
+  font-size: 12px;
+  color: #E6A23C;
+}
+
+.cron-help h4 {
+  margin-bottom: 10px;
+  color: #303133;
+}
+
+.cron-help code {
+  background: #f5f7fa;
+  padding: 2px 6px;
+  border-radius: 3px;
+  color: #409EFF;
+}
+</style>

+ 302 - 0
ems-ui-cloud/src/views/inspection/plan/components/RuleConfig.vue

@@ -0,0 +1,302 @@
+<template>
+  <div class="rule-config">
+    <!-- 操作按钮 -->
+    <div class="rule-toolbar">
+      <el-button type="primary" size="small" icon="el-icon-plus" @click="addRule">
+        添加规则
+      </el-button>
+      <el-button type="success" size="small" icon="el-icon-document-add" @click="addDefaultRules">
+        添加默认规则
+      </el-button>
+      <span class="rule-tip">
+        <i class="el-icon-info"></i>
+        规则将按顺序检查,不指定设备模型则应用于所有设备
+      </span>
+    </div>
+
+    <!-- 规则表格 -->
+    <el-table :data="rules" border size="small" style="margin-top: 10px" max-height="400">
+      <el-table-column label="序号" type="index" width="50" align="center" />
+      <el-table-column label="规则名称" width="140">
+        <template slot-scope="scope">
+          <el-input v-model="scope.row.ruleName" size="small" placeholder="输入规则名称" />
+        </template>
+      </el-table-column>
+      <el-table-column label="设备模型" width="180">
+        <template slot-scope="scope">
+          <el-select v-model="scope.row.deviceModel" size="small" placeholder="全部设备"
+                     clearable filterable @change="handleModelChange(scope.row)" style="width: 100%">
+            <el-option v-for="item in modelOptions" :key="item.modelCode"
+                       :label="item.modelName" :value="item.modelCode" />
+          </el-select>
+        </template>
+      </el-table-column>
+      <el-table-column label="检查属性" width="150">
+        <template slot-scope="scope">
+          <el-select v-model="scope.row.attrKey" size="small" placeholder="选择属性"
+                     @change="handleAttrChange(scope.row)" style="width: 100%">
+            <el-option label="设备状态" value="deviceStatus" />
+            <el-option-group v-if="getAttrOptions(scope.row.deviceModel).length > 0" label="模型属性">
+              <el-option v-for="item in getAttrOptions(scope.row.deviceModel)" :key="item.attrKey"
+                         :label="item.attrName" :value="item.attrKey" />
+            </el-option-group>
+          </el-select>
+        </template>
+      </el-table-column>
+      <el-table-column label="检查类型" width="120">
+        <template slot-scope="scope">
+          <el-select v-model="scope.row.checkType" size="small" style="width: 100%"
+                     @change="handleCheckTypeChange(scope.row)">
+            <el-option v-for="item in CHECK_TYPE_OPTIONS" :key="item.value"
+                       :label="item.label" :value="item.value" />
+          </el-select>
+        </template>
+      </el-table-column>
+      <el-table-column label="阈值配置" min-width="200">
+        <template slot-scope="scope">
+          <!-- 范围检查 -->
+          <div v-if="scope.row.checkType === 1" class="threshold-input">
+            <el-input v-model="scope.row.minValue" size="small" placeholder="最小值" style="width: 70px" />
+            <span class="threshold-separator">~</span>
+            <el-input v-model="scope.row.maxValue" size="small" placeholder="最大值" style="width: 70px" />
+            <span v-if="scope.row.attrUnit" class="threshold-unit">{{ scope.row.attrUnit }}</span>
+          </div>
+          <!-- 等值检查 -->
+          <el-input v-else-if="scope.row.checkType === 2" v-model="scope.row.expectValue"
+                    size="small" placeholder="期望值" style="width: 120px" />
+          <!-- 在线状态 -->
+          <el-select v-else-if="scope.row.checkType === 4" v-model="scope.row.expectValue"
+                     size="small" placeholder="在线值" style="width: 120px">
+            <el-option label="在线(1)" value="1" />
+            <el-option label="离线(0)" value="0" />
+          </el-select>
+          <!-- 非空检查 -->
+          <span v-else class="threshold-text">检查属性值是否存在</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="启用" width="60" align="center">
+        <template slot-scope="scope">
+          <el-switch v-model="scope.row.enabled" :active-value="1" :inactive-value="0" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="80" align="center">
+        <template slot-scope="scope">
+          <el-button type="text" size="small" icon="el-icon-top"
+                     :disabled="scope.$index === 0" @click="moveUp(scope.$index)" />
+          <el-button type="text" size="small" icon="el-icon-bottom"
+                     :disabled="scope.$index === rules.length - 1" @click="moveDown(scope.$index)" />
+          <el-button type="text" size="small" icon="el-icon-delete"
+                     style="color: #F56C6C" @click="removeRule(scope.$index)" />
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 空状态 -->
+    <el-empty v-if="rules.length === 0" description="暂无规则,请添加巡检规则" :image-size="80" />
+  </div>
+</template>
+
+<script>
+import { listAllModel } from '@/api/basecfg/objModel'
+import { listAttr } from '@/api/basecfg/objAttribute'
+import { CHECK_TYPE_OPTIONS } from '@/enums/InspectionEnums'
+
+export default {
+  name: 'RuleConfig',
+  props: {
+    value: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      rules: [],
+      modelOptions: [],
+      attrCache: {}, // 属性缓存 { modelCode: [attrs] }
+      CHECK_TYPE_OPTIONS
+    }
+  },
+  watch: {
+    value: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        this.rules = val || []
+      }
+    },
+    rules: {
+      deep: true,
+      handler(val) {
+        this.$emit('input', val)
+      }
+    }
+  },
+  created() {
+    this.loadModels()
+  },
+  methods: {
+    /** 加载设备模型 */
+    async loadModels() {
+      try {
+        const res = await listAllModel(2) // 2=设备模型
+        this.modelOptions = res.data || []
+      } catch (e) {
+        console.error('加载设备模型失败', e)
+      }
+    },
+
+    /** 加载模型属性 */
+    async loadAttrsByModel(modelCode) {
+      if (!modelCode || this.attrCache[modelCode]) return
+      try {
+        const res = await listAttr({ modelCode })
+        this.attrCache[modelCode] = res.rows || []
+        this.$forceUpdate()
+      } catch (e) {
+        console.error('加载模型属性失败', e)
+      }
+    },
+
+    /** 获取属性选项 */
+    getAttrOptions(modelCode) {
+      if (!modelCode) return []
+      return this.attrCache[modelCode] || []
+    },
+
+    /** 添加规则 */
+    addRule() {
+      this.rules.push({
+        ruleCode: '',
+        ruleName: '',
+        deviceModel: null,
+        attrKey: 'deviceStatus',
+        attrName: '设备状态',
+        attrUnit: '',
+        checkType: 4,
+        minValue: null,
+        maxValue: null,
+        expectValue: '1',
+        enabled: 1
+      })
+    },
+
+    /** 添加默认规则 */
+    addDefaultRules() {
+      const defaultRules = [
+        {
+          ruleName: '设备在线状态',
+          deviceModel: null,
+          attrKey: 'deviceStatus',
+          attrName: '设备状态',
+          checkType: 4,
+          expectValue: '1',
+          enabled: 1
+        }
+      ]
+      this.rules.push(...defaultRules)
+    },
+
+    /** 移除规则 */
+    removeRule(index) {
+      this.rules.splice(index, 1)
+    },
+
+    /** 上移 */
+    moveUp(index) {
+      if (index > 0) {
+        const temp = this.rules[index]
+        this.$set(this.rules, index, this.rules[index - 1])
+        this.$set(this.rules, index - 1, temp)
+      }
+    },
+
+    /** 下移 */
+    moveDown(index) {
+      if (index < this.rules.length - 1) {
+        const temp = this.rules[index]
+        this.$set(this.rules, index, this.rules[index + 1])
+        this.$set(this.rules, index + 1, temp)
+      }
+    },
+
+    /** 设备模型变更 */
+    handleModelChange(row) {
+      if (row.deviceModel) {
+        this.loadAttrsByModel(row.deviceModel)
+      }
+      // 重置属性
+      row.attrKey = 'deviceStatus'
+      row.attrName = '设备状态'
+      row.attrUnit = ''
+    },
+
+    /** 属性变更 */
+    handleAttrChange(row) {
+      if (row.attrKey === 'deviceStatus') {
+        row.attrName = '设备状态'
+        row.attrUnit = ''
+        row.checkType = 4
+        row.expectValue = '1'
+      } else {
+        const attrs = this.getAttrOptions(row.deviceModel)
+        const attr = attrs.find(a => a.attrKey === row.attrKey)
+        if (attr) {
+          row.attrName = attr.attrName
+          row.attrUnit = attr.attrUnit || ''
+        }
+        // 默认设为范围检查
+        if (row.checkType === 4) {
+          row.checkType = 1
+        }
+      }
+    },
+
+    /** 检查类型变更 */
+    handleCheckTypeChange(row) {
+      row.minValue = null
+      row.maxValue = null
+      row.expectValue = row.checkType === 4 ? '1' : null
+    }
+  }
+}
+</script>
+
+<style scoped>
+.rule-config {
+  width: 100%;
+}
+
+.rule-toolbar {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.rule-tip {
+  margin-left: auto;
+  font-size: 12px;
+  color: #909399;
+}
+
+.threshold-input {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+}
+
+.threshold-separator {
+  color: #909399;
+}
+
+.threshold-unit {
+  color: #909399;
+  font-size: 12px;
+  margin-left: 5px;
+}
+
+.threshold-text {
+  color: #909399;
+  font-size: 12px;
+}
+</style>

+ 174 - 0
ems-ui-cloud/src/views/inspection/plan/components/SchedulerStatus.vue

@@ -0,0 +1,174 @@
+<template>
+  <el-dialog title="调度器状态" :visible.sync="visible" width="700px" append-to-body>
+    <div v-loading="loading" class="scheduler-status">
+      <!-- 调度器概览 -->
+      <el-descriptions title="调度器概览" :column="3" border size="medium">
+        <el-descriptions-item label="运行状态">
+          <el-tag :type="status.running ? 'success' : 'danger'" effect="dark">
+            <i :class="status.running ? 'el-icon-check' : 'el-icon-close'"></i>
+            {{ status.running ? '运行中' : '已停止' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="已注册计划">
+          <span class="stat-number">{{ status.registeredCount || 0 }}</span> 个
+        </el-descriptions-item>
+        <el-descriptions-item label="正在执行">
+          <span class="stat-number" :class="{ 'executing': status.executingCount > 0 }">
+            {{ status.executingCount || 0 }}
+          </span> 个
+        </el-descriptions-item>
+        <el-descriptions-item label="启动时间" :span="2">
+          {{ status.startTime || '-' }}
+        </el-descriptions-item>
+        <el-descriptions-item label="线程池状态">
+          {{ status.threadPoolStatus || '-' }}
+        </el-descriptions-item>
+      </el-descriptions>
+
+      <!-- 已注册计划列表 -->
+      <div class="section-title">
+        <span>已注册计划</span>
+        <el-button type="text" icon="el-icon-refresh" @click="loadStatus">刷新</el-button>
+      </div>
+
+      <el-table :data="status.registeredPlans || []" border size="small" max-height="300">
+        <el-table-column label="计划代码" prop="planCode" width="160" show-overflow-tooltip />
+        <el-table-column label="计划名称" prop="planName" min-width="140" show-overflow-tooltip />
+        <el-table-column label="Cron表达式" prop="cronExpression" width="130" />
+        <el-table-column label="下次执行" prop="nextExecTime" width="160">
+          <template slot-scope="scope">
+            <span v-if="scope.row.nextExecTime">{{ scope.row.nextExecTime }}</span>
+            <span v-else class="text-muted">-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" width="80" align="center">
+          <template slot-scope="scope">
+            <el-tag v-if="scope.row.executing" type="warning" size="mini" effect="light">
+              <i class="el-icon-loading"></i> 执行中
+            </el-tag>
+            <el-tag v-else type="success" size="mini" effect="light">就绪</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="100" align="center">
+          <template slot-scope="scope">
+            <el-button size="mini" type="text" @click="handleUnregister(scope.row)">注销</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-empty v-if="!status.registeredPlans || status.registeredPlans.length === 0"
+                description="暂无已注册的计划" :image-size="60" />
+    </div>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button icon="el-icon-refresh-right" @click="handleReload" :loading="reloading">
+        重新加载全部
+      </el-button>
+      <el-button type="primary" @click="visible = false">关 闭</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { getSchedulerStatus, reloadScheduler, unregisterPlan } from '@/api/inspection/scheduler'
+
+export default {
+  name: 'SchedulerStatus',
+  data() {
+    return {
+      visible: false,
+      loading: false,
+      reloading: false,
+      status: {
+        running: false,
+        registeredCount: 0,
+        executingCount: 0,
+        startTime: null,
+        threadPoolStatus: null,
+        registeredPlans: []
+      }
+    }
+  },
+  methods: {
+    open() {
+      this.visible = true
+      this.loadStatus()
+    },
+
+    async loadStatus() {
+      this.loading = true
+      try {
+        const res = await getSchedulerStatus()
+        this.status = res.data || {}
+      } catch (e) {
+        this.$modal.msgError('获取调度器状态失败')
+        console.error(e)
+      } finally {
+        this.loading = false
+      }
+    },
+
+    async handleReload() {
+      try {
+        this.reloading = true
+        const res = await reloadScheduler()
+        this.$modal.msgSuccess(res.msg || '重新加载完成')
+        await this.loadStatus()
+        this.$emit('refresh')
+      } catch (e) {
+        this.$modal.msgError(e.message || '重新加载失败')
+      } finally {
+        this.reloading = false
+      }
+    },
+
+    async handleUnregister(row) {
+      try {
+        await this.$modal.confirm(`确认注销计划【${row.planName}】的定时任务吗?`)
+        await unregisterPlan(row.planCode)
+        this.$modal.msgSuccess('注销成功')
+        await this.loadStatus()
+        this.$emit('refresh')
+      } catch (e) {
+        if (e !== 'cancel') {
+          this.$modal.msgError(e.message || '注销失败')
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.scheduler-status {
+  max-height: 60vh;
+  overflow-y: auto;
+}
+
+.section-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  font-weight: bold;
+  font-size: 14px;
+  color: #303133;
+  margin: 20px 0 10px 0;
+  padding-left: 10px;
+  border-left: 3px solid #409EFF;
+}
+
+.stat-number {
+  font-size: 18px;
+  font-weight: bold;
+  color: #409EFF;
+}
+
+.stat-number.executing {
+  color: #E6A23C;
+}
+
+.text-muted {
+  color: #909399;
+  font-size: 12px;
+}
+</style>

+ 266 - 0
ems-ui-cloud/src/views/inspection/plan/components/TargetSelector.vue

@@ -0,0 +1,266 @@
+<template>
+  <div class="target-selector">
+    <!-- 搜索选择 -->
+    <el-select
+      v-model="selectedTargets"
+      multiple
+      filterable
+      :filter-method="localFilter"
+      :placeholder="placeholder"
+      :loading="loading"
+      @change="handleChange"
+      style="width: 100%"
+      popper-class="target-select-dropdown"
+    >
+      <el-option v-for="item in filteredOptions" :key="item.value" :label="item.label" :value="item.value">
+        <span style="float: left">{{ item.label }}</span>
+        <span style="float: right; color: #8492a6; font-size: 12px">{{ item.extra }}</span>
+      </el-option>
+    </el-select>
+
+    <!-- 已选择标签展示 -->
+    <div class="selected-tags" v-if="selectedTargets.length > 0">
+      <div class="tags-header">
+        <span class="tags-title">已选择 {{ selectedTargets.length }} 项</span>
+        <el-button type="text" size="mini" @click="clearAll">清空</el-button>
+      </div>
+      <div class="tags-content">
+        <el-tag
+          v-for="code in selectedTargets"
+          :key="code"
+          closable
+          size="small"
+          effect="plain"
+          @close="removeTarget(code)"
+          style="margin: 3px"
+        >
+          {{ getTargetLabel(code) }}
+        </el-tag>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { areaTreeSelect } from '@/api/basecfg/area'
+import { listAllFacs } from '@/api/basecfg/emsfacs'
+import { listDevice } from '@/api/device/device'
+
+export default {
+  name: 'TargetSelector',
+  props: {
+    value: {
+      type: Array,
+      default: () => []
+    },
+    targetType: {
+      type: Number,
+      default: 2
+    },
+    areaCode: {
+      type: String,
+      default: null
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      options: [],         // 全部选项
+      filteredOptions: [], // 过滤后的选项
+      selectedTargets: [],
+      labelMap: {},        // 存储code到label的映射
+      filterKeyword: ''    // 本地过滤关键词
+    }
+  },
+  computed: {
+    placeholder() {
+      const types = { 0: '区域', 1: '设施', 2: '设备' }
+      return `请搜索并选择${types[this.targetType] || '目标'}(可多选)`
+    }
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(val) {
+        this.selectedTargets = val || []
+      }
+    },
+    targetType: {
+      handler() {
+        this.options = []
+        this.filteredOptions = []
+        this.loadOptions()
+      }
+    },
+    areaCode: {
+      handler() {
+        this.loadOptions()
+      }
+    }
+  },
+  created() {
+    this.loadOptions()
+  },
+  methods: {
+    /** 加载选项 */
+    async loadOptions() {
+      this.loading = true
+      try {
+        let result = []
+        switch (this.targetType) {
+          case 0:
+            result = await this.loadAreas()
+            break
+          case 1:
+            result = await this.loadFacs()
+            break
+          case 2:
+            result = await this.loadDevices()
+            break
+        }
+        this.options = result
+        this.filteredOptions = result
+        // 更新标签映射
+        result.forEach(item => {
+          this.labelMap[item.value] = item.label
+        })
+      } catch (e) {
+        console.error('加载选项失败', e)
+      } finally {
+        this.loading = false
+      }
+    },
+
+    /** 本地过滤 */
+    localFilter(keyword) {
+      this.filterKeyword = keyword
+      if (!keyword) {
+        this.filteredOptions = this.options
+      } else {
+        const lowerKeyword = keyword.toLowerCase()
+        this.filteredOptions = this.options.filter(item =>
+          item.label.toLowerCase().includes(lowerKeyword) ||
+          (item.extra && item.extra.toLowerCase().includes(lowerKeyword))
+        )
+      }
+    },
+
+    /** 加载区域 - 使用树形接口并展平 */
+    async loadAreas() {
+      const res = await areaTreeSelect('0', 3) // 获取3级区域树
+      const data = res.data || []
+      return this.flattenAreaTree(data)
+    },
+
+    /** 展平区域树 */
+    flattenAreaTree(tree, prefix = '') {
+      const result = []
+      tree.forEach(node => {
+        const label = prefix ? `${prefix} / ${node.label}` : node.label
+        result.push({
+          value: node.id,
+          label: node.label,
+          fullLabel: label,
+          extra: prefix || '顶级区域'
+        })
+        if (node.children && node.children.length > 0) {
+          const children = this.flattenAreaTree(node.children, node.label)
+          result.push(...children)
+        }
+      })
+      return result
+    },
+
+    /** 加载设施 */
+    async loadFacs() {
+      const params = {}
+      if (this.areaCode) {
+        params.refArea = this.areaCode
+      }
+      const res = await listAllFacs(params)
+      return (res.data || []).map(item => ({
+        value: item.facsCode,
+        label: item.facsName,
+        extra: item.refAreaName || ''
+      }))
+    },
+
+    /** 加载设备 */
+    async loadDevices() {
+      const params = {
+        pageNum: 1,
+        pageSize: 200 // 加载更多设备
+      }
+      if (this.areaCode) {
+        params.areaCode = this.areaCode
+      }
+      const res = await listDevice(params)
+      return (res.rows || []).map(item => ({
+        value: item.deviceCode,
+        label: item.deviceName,
+        extra: item.locationRefName || item.location || ''
+      }))
+    },
+
+    /** 获取目标标签 */
+    getTargetLabel(code) {
+      return this.labelMap[code] || code
+    },
+
+    /** 移除单个目标 */
+    removeTarget(code) {
+      const index = this.selectedTargets.indexOf(code)
+      if (index > -1) {
+        this.selectedTargets.splice(index, 1)
+        this.handleChange(this.selectedTargets)
+      }
+    },
+
+    /** 清空所有 */
+    clearAll() {
+      this.selectedTargets = []
+      this.handleChange([])
+    },
+
+    /** 选择变更 */
+    handleChange(val) {
+      this.$emit('input', val)
+      const names = val.map(code => this.getTargetLabel(code)).join(',')
+      this.$emit('change', names)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.target-selector {
+  width: 100%;
+}
+
+.selected-tags {
+  margin-top: 10px;
+  padding: 10px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+}
+
+.tags-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+  padding-bottom: 8px;
+  border-bottom: 1px dashed #dcdfe6;
+}
+
+.tags-title {
+  font-size: 13px;
+  color: #606266;
+}
+
+.tags-content {
+  max-height: 120px;
+  overflow-y: auto;
+}
+</style>

+ 662 - 0
ems-ui-cloud/src/views/inspection/plan/index.vue

@@ -0,0 +1,662 @@
+<template>
+  <div class="app-container">
+    <!-- Tab标签区分手动/自动巡检 -->
+    <el-tabs v-model="activeTab" type="card" class="inspection-tabs" @tab-click="handleTabChange">
+      <el-tab-pane name="manual">
+        <span slot="label">
+          <i class="el-icon-user"></i> 手动巡检
+        </span>
+      </el-tab-pane>
+      <el-tab-pane name="auto">
+        <span slot="label">
+          <i class="el-icon-setting"></i> 自动巡检
+        </span>
+      </el-tab-pane>
+    </el-tabs>
+
+    <!-- 搜索区域 -->
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
+      <el-form-item label="归属区域" prop="areaCode">
+        <el-select v-model="queryParams.areaCode" placeholder="请选择区域" clearable style="width: 200px">
+          <el-option v-for="item in areaOptions" :key="item.id" :label="item.label" :value="item.id" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="计划名称" prop="planName">
+        <el-input v-model="queryParams.planName" placeholder="请输入计划名称" clearable style="width: 180px"
+                  @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item label="计划状态" prop="planStatus">
+        <el-select v-model="queryParams.planStatus" placeholder="请选择状态" clearable style="width: 120px">
+          <el-option v-for="item in planStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="执行人" prop="executor" v-if="activeTab === 'manual'">
+        <el-input v-model="queryParams.executor" placeholder="请输入执行人" clearable style="width: 120px"
+                  @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作按钮 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+                   v-hasPermi="['ems:inspection:plan:add']">
+          {{ activeTab === 'manual' ? '新增手动巡检' : '新增自动巡检' }}
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"
+                   v-hasPermi="['ems:inspection:plan:edit']">修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
+                   v-hasPermi="['ems:inspection:plan:remove']">删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
+                   v-hasPermi="['ems:inspection:plan:export']">导出</el-button>
+      </el-col>
+      <!-- 自动巡检:调度器管理按钮 -->
+      <el-col :span="1.5" v-if="activeTab === 'auto'">
+        <el-button type="info" plain icon="el-icon-s-operation" size="mini" @click="showSchedulerStatus"
+                   v-hasPermi="['ems:inspection:plan:query']">调度器状态</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 手动巡检表格 -->
+    <el-table v-if="activeTab === 'manual'" key="manual-table" v-loading="loading" :data="planList"
+              @selection-change="handleSelectionChange" border>
+      <el-table-column type="selection" width="50" align="center" />
+      <el-table-column label="计划代码" align="left" prop="planCode" width="180" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <el-link type="primary" @click="handleView(scope.row)">{{ scope.row.planCode }}</el-link>
+        </template>
+      </el-table-column>
+      <el-table-column label="计划名称" align="left" prop="planName" min-width="160" show-overflow-tooltip />
+      <el-table-column label="归属区域" align="center" prop="areaName" width="130" />
+      <el-table-column label="目标类型" align="center" width="80">
+        <template slot-scope="scope">
+          <span>
+            <i :class="getTargetTypeIcon(scope.row.targetType)" style="margin-right: 4px"></i>
+            {{ getTargetTypeLabel(scope.row.targetType) }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="巡检目标" align="left" prop="targetNames" min-width="200" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <el-tooltip :content="scope.row.targetNames" placement="top" :disabled="!scope.row.targetNames">
+            <span>{{ scope.row.targetNames || '-' }}</span>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      <el-table-column label="执行人" align="center" prop="executor" width="90" />
+      <el-table-column label="计划状态" align="center" width="90">
+        <template slot-scope="scope">
+          <el-tag :type="getManualPlanStatusInfo(scope.row.planStatus).type" size="small" effect="light">
+            {{ getManualPlanStatusInfo(scope.row.planStatus).label }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="计划时间" align="center" prop="planTime" width="150">
+        <template slot-scope="scope">
+          <span>{{ scope.row.planTime || '-' }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="150" />
+      <el-table-column label="操作" align="center" fixed="right" width="250" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <!-- 手动巡检:提交报告(待执行状态显示) -->
+          <el-button size="mini" type="text" icon="el-icon-edit-outline"
+                     @click="handleSubmitReport(scope.row)"
+                     v-if="scope.row.planStatus === 0"
+                     v-hasPermi="['ems:inspection:plan:execute']">
+            提交报告
+          </el-button>
+          <!-- 已完成状态:查看报告 -->
+          <el-button size="mini" type="text" icon="el-icon-document"
+                     @click="handleViewReport(scope.row)"
+                     v-if="scope.row.planStatus === 2">
+            查看报告
+          </el-button>
+          <!-- 已完成状态:编辑报告 -->
+          <el-button size="mini" type="text" icon="el-icon-edit-outline"
+                     @click="handleEditReport(scope.row)"
+                     v-if="scope.row.planStatus === 2"
+                     v-hasPermi="['ems:inspection:report:edit']">
+            编辑报告
+          </el-button>
+          <el-button size="mini" type="text" icon="el-icon-edit"
+                     @click="handleUpdate(scope.row)"
+                     v-hasPermi="['ems:inspection:plan:edit']">
+            编辑
+          </el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" class="delete-btn"
+                     @click="handleDelete(scope.row)"
+                     v-hasPermi="['ems:inspection:plan:remove']">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 自动巡检表格 - 新增调度相关列 -->
+    <el-table v-if="activeTab === 'auto'" key="auto-table" v-loading="loading" :data="planList"
+              @selection-change="handleSelectionChange" border>
+      <el-table-column type="selection" width="50" align="center" />
+      <el-table-column label="计划代码" align="left" prop="planCode" width="160" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <el-link type="primary" @click="handleView(scope.row)">{{ scope.row.planCode }}</el-link>
+        </template>
+      </el-table-column>
+      <el-table-column label="计划名称" align="left" prop="planName" min-width="140" show-overflow-tooltip />
+      <el-table-column label="归属区域" align="center" prop="areaName" width="100" />
+      <el-table-column label="目标类型" align="center" width="70">
+        <template slot-scope="scope">
+          <span>
+            <i :class="getTargetTypeIcon(scope.row.targetType)" style="margin-right: 4px"></i>
+            {{ getTargetTypeLabel(scope.row.targetType) }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="Cron表达式" align="center" prop="cronExpression" width="130">
+        <template slot-scope="scope">
+          <el-tooltip :content="getCronDescription(scope.row.cronExpression)" placement="top">
+            <span>{{ scope.row.cronExpression || '-' }}</span>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+      <!-- 调度状态列 -->
+      <el-table-column label="调度状态" align="center" width="90">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.scheduleEnabled === 1" type="success" size="small" effect="light">
+            <i class="el-icon-video-play"></i> 已启用
+          </el-tag>
+          <el-tag v-else type="info" size="small" effect="light">
+            <i class="el-icon-video-pause"></i> 已禁用
+          </el-tag>
+        </template>
+      </el-table-column>
+      <!-- 执行次数 -->
+      <el-table-column label="执行次数" align="center" prop="execCount" width="80">
+        <template slot-scope="scope">
+          <el-badge :value="scope.row.execCount || 0" :max="999" type="primary" />
+        </template>
+      </el-table-column>
+      <!-- 上次执行时间 -->
+      <el-table-column label="上次执行" align="center" width="150">
+        <template slot-scope="scope">
+          <span v-if="scope.row.lastExecTime">{{ scope.row.lastExecTime }}</span>
+          <span v-else class="text-muted">暂未执行</span>
+        </template>
+      </el-table-column>
+      <!-- 下次执行时间 -->
+      <el-table-column label="下次执行" align="center" width="150">
+        <template slot-scope="scope">
+          <span v-if="scope.row.scheduleEnabled === 1 && scope.row.nextExecTime">
+            {{ scope.row.nextExecTime }}
+          </span>
+          <span v-else class="text-muted">-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="计划状态" align="center" width="80">
+        <template slot-scope="scope">
+          <el-tag :type="getPlanStatusInfo(scope.row.planStatus).type" size="small" effect="light">
+            {{ getPlanStatusInfo(scope.row.planStatus).label }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" fixed="right" width="280" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <!-- 调度开关 -->
+          <el-button size="mini" type="text"
+                     :icon="scope.row.scheduleEnabled === 1 ? 'el-icon-video-pause' : 'el-icon-video-play'"
+                     @click="handleToggleSchedule(scope.row)"
+                     v-hasPermi="['ems:inspection:plan:edit']">
+            {{ scope.row.scheduleEnabled === 1 ? '禁用调度' : '启用调度' }}
+          </el-button>
+          <!-- 立即执行 -->
+          <el-button size="mini" type="text" icon="el-icon-caret-right"
+                     @click="handleTrigger(scope.row)"
+                     :disabled="scope.row.planStatus === 1"
+                     v-hasPermi="['ems:inspection:plan:execute']">
+            立即执行
+          </el-button>
+          <el-dropdown @command="(cmd) => handleCommand(cmd, scope.row)" trigger="click">
+            <el-button size="mini" type="text">
+              更多<i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item command="edit" icon="el-icon-edit">编辑计划</el-dropdown-item>
+              <el-dropdown-item command="view" icon="el-icon-view">查看详情</el-dropdown-item>
+              <el-dropdown-item command="reports" icon="el-icon-document">查看报告</el-dropdown-item>
+              <el-dropdown-item command="refresh" icon="el-icon-refresh" divided>刷新调度</el-dropdown-item>
+              <el-dropdown-item command="delete" icon="el-icon-delete" style="color: #F56C6C">删除</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
+                :limit.sync="queryParams.pageSize" @pagination="getList" />
+
+    <!-- 新增/编辑对话框 -->
+    <plan-form ref="planForm" :area-options="areaOptions" :default-plan-type="currentPlanType" @success="getList" />
+
+    <!-- 查看详情对话框 -->
+    <plan-detail ref="planDetail" />
+
+    <!-- 执行结果对话框(自动巡检) -->
+    <report-detail ref="reportDetail" />
+
+    <!-- 手动提交/编辑报告对话框 -->
+    <manual-report-form ref="manualReportForm" @success="getList" />
+
+    <!-- 调度器状态对话框 -->
+    <scheduler-status ref="schedulerStatus" @refresh="getList" />
+
+    <!-- 报告列表对话框 -->
+    <report-list-dialog ref="reportListDialog" />
+  </div>
+</template>
+
+<script>
+import { listPlan, delPlan, executePlan } from '@/api/inspection/plan'
+import { listReport } from '@/api/inspection/report'
+import {
+  getSchedulerStatus,
+  registerPlan,
+  unregisterPlan,
+  triggerInspection,
+  refreshPlan
+} from '@/api/inspection/scheduler'
+import { areaTreeSelect } from '@/api/basecfg/area'
+import {
+  PLAN_STATUS_OPTIONS,
+  getPlanStatusInfo,
+  getTargetTypeLabel,
+  getTargetTypeIcon
+} from '@/enums/InspectionEnums'
+import PlanForm from './components/PlanForm.vue'
+import PlanDetail from './components/PlanDetail.vue'
+import ReportDetail from '../report/components/ReportDetail.vue'
+import ManualReportForm from '../report/components/Manualreportform.vue'
+import SchedulerStatus from './components/SchedulerStatus.vue'
+import ReportListDialog from '../report/components/ReportListDialog.vue'
+
+export default {
+  name: 'InspectionPlan',
+  components: { PlanForm, PlanDetail, ReportDetail, ManualReportForm, SchedulerStatus, ReportListDialog },
+  data() {
+    return {
+      // 当前Tab
+      activeTab: 'manual',
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 总条数
+      total: 0,
+      // 表格数据
+      planList: [],
+      // 区域选项
+      areaOptions: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        areaCode: null,
+        planName: null,
+        planType: 1, // 默认手动巡检
+        planStatus: null,
+        executor: null
+      },
+      // 手动巡检状态选项
+      manualPlanStatusOptions: [
+        { value: 0, label: '待巡检' },
+        { value: 2, label: '已完成' },
+        { value: 3, label: '已取消' }
+      ],
+      // 自动巡检状态选项
+      autoPlanStatusOptions: PLAN_STATUS_OPTIONS
+    }
+  },
+  computed: {
+    // 当前计划类型
+    currentPlanType() {
+      return this.activeTab === 'manual' ? 1 : 2
+    },
+    // 根据Tab动态切换状态选项
+    planStatusOptions() {
+      return this.activeTab === 'manual' ? this.manualPlanStatusOptions : this.autoPlanStatusOptions
+    }
+  },
+  created() {
+    this.getAreaTree()
+    this.getList()
+  },
+  methods: {
+    // 引入枚举工具函数
+    getPlanStatusInfo,
+    getTargetTypeLabel,
+    getTargetTypeIcon,
+
+    // 手动巡检状态信息
+    getManualPlanStatusInfo(status) {
+      const map = {
+        0: { type: 'warning', label: '待巡检' },
+        1: { type: 'primary', label: '巡检中' },
+        2: { type: 'success', label: '已完成' },
+        3: { type: 'info', label: '已取消' }
+      }
+      return map[status] || { type: 'info', label: '未知' }
+    },
+
+    /** 获取Cron表达式描述 */
+    getCronDescription(cron) {
+      if (!cron) return '未配置'
+      // 简单解析常见cron表达式
+      const parts = cron.split(' ')
+      if (parts.length < 6) return cron
+
+      const [second, minute, hour, day, month, week] = parts
+
+      // 每天执行
+      if (day === '*' && month === '*' && week === '?') {
+        return `每天 ${hour}:${minute.padStart(2, '0')} 执行`
+      }
+      // 每周执行
+      if (day === '?' && week !== '*') {
+        const weekMap = { '1': '周日', '2': '周一', '3': '周二', '4': '周三', '5': '周四', '6': '周五', '7': '周六' }
+        return `每${weekMap[week] || week} ${hour}:${minute.padStart(2, '0')} 执行`
+      }
+      // 每月执行
+      if (day !== '*' && day !== '?') {
+        return `每月${day}日 ${hour}:${minute.padStart(2, '0')} 执行`
+      }
+
+      return cron
+    },
+
+    /** 查询区域树 */
+    async getAreaTree() {
+      try {
+        const res = await areaTreeSelect('0', 2)
+        this.areaOptions = res.data || []
+      } catch (e) {
+        console.error('获取区域树失败', e)
+      }
+    },
+
+    /** Tab切换 */
+    handleTabChange() {
+      this.queryParams.planType = this.currentPlanType
+      this.queryParams.pageNum = 1
+      this.queryParams.planStatus = null
+      this.queryParams.executor = null
+      this.getList()
+    },
+
+    /** 查询列表 */
+    getList() {
+      this.loading = true
+      this.queryParams.planType = this.currentPlanType
+      listPlan(this.queryParams).then(res => {
+        this.planList = res.rows || []
+        this.total = res.total || 0
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.queryParams.planType = this.currentPlanType
+      this.handleQuery()
+    },
+
+    /** 多选框选中数据 */
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.$refs.planForm.open(null, this.currentPlanType)
+    },
+
+    /** 查看详情 */
+    handleView(row) {
+      this.$refs.planDetail.open(row.id)
+    },
+
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      const id = row.id || this.ids[0]
+      this.$refs.planForm.open(id)
+    },
+
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id ? [row.id] : this.ids
+      const names = row.planName || this.planList.filter(p => ids.includes(p.id)).map(p => p.planName).join('、')
+
+      this.$modal.confirm(`确认删除巡检计划【${names}】吗?`).then(() => {
+        return delPlan(ids)
+      }).then(() => {
+        this.getList()
+        this.$modal.msgSuccess('删除成功')
+      }).catch(() => {})
+    },
+
+    /** 切换调度状态 */
+    handleToggleSchedule(row) {
+      const newStatus = row.scheduleEnabled === 1 ? 0 : 1
+      const action = newStatus === 1 ? '启用' : '禁用'
+
+      // 启用调度前检查cron表达式
+      if (newStatus === 1 && !row.cronExpression) {
+        this.$modal.msgWarning('请先配置Cron表达式')
+        return
+      }
+
+      this.$modal.confirm(`确认${action}计划【${row.planName}】的定时调度吗?`).then(() => {
+        this.loading = true
+
+        // 根据状态调用注册或注销接口(调度器会自动更新数据库状态)
+        if (newStatus === 1) {
+          return registerPlan(row.planCode)
+        } else {
+          return unregisterPlan(row.planCode)
+        }
+      }).then(() => {
+        this.$modal.msgSuccess(`调度${action}成功`)
+        this.getList()
+      }).catch((err) => {
+        if (err !== 'cancel') {
+          this.$modal.msgError(err.message || `${action}失败`)
+        }
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+
+    /** 手动触发执行 */
+    handleTrigger(row) {
+      this.$modal.confirm(`确认立即执行巡检计划【${row.planName}】吗?`).then(() => {
+        this.loading = true
+        return triggerInspection(row.planCode)
+      }).then(res => {
+        this.$modal.msgSuccess('巡检执行完成')
+        this.getList()
+        // 显示巡检结果
+        if (res.data) {
+          this.$nextTick(() => {
+            this.$refs.reportDetail.open(res.data)
+          })
+        }
+      }).catch((err) => {
+        if (err !== 'cancel') {
+          this.$modal.msgError(err.message || '执行失败')
+        }
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+
+    /** 下拉菜单命令处理 */
+    handleCommand(command, row) {
+      switch (command) {
+        case 'edit':
+          this.handleUpdate(row)
+          break
+        case 'view':
+          this.handleView(row)
+          break
+        case 'reports':
+          this.handleViewReports(row)
+          break
+        case 'refresh':
+          this.handleRefreshSchedule(row)
+          break
+        case 'delete':
+          this.handleDelete(row)
+          break
+      }
+    },
+
+    /** 查看报告列表 */
+    handleViewReports(row) {
+      this.$refs.reportListDialog.open(row.planCode, row.planName)
+    },
+
+    /** 刷新调度配置 */
+    handleRefreshSchedule(row) {
+      this.$modal.confirm(`确认刷新计划【${row.planName}】的调度配置吗?`).then(() => {
+        return refreshPlan(row.planCode)
+      }).then(res => {
+        this.$modal.msgSuccess(res.msg || '刷新成功')
+        this.getList()
+      }).catch((err) => {
+        if (err !== 'cancel') {
+          this.$modal.msgError(err.message || '刷新失败')
+        }
+      })
+    },
+
+    /** 显示调度器状态 */
+    showSchedulerStatus() {
+      this.$refs.schedulerStatus.open()
+    },
+
+    /** 自动巡检:立即执行(旧方法保留兼容) */
+    handleExecute(row) {
+      this.handleTrigger(row)
+    },
+
+    /** 手动巡检:提交报告 */
+    handleSubmitReport(row) {
+      this.$refs.manualReportForm.open(row)
+    },
+
+    /** 手动巡检:查看报告 */
+    async handleViewReport(row) {
+      try {
+        this.loading = true
+        // 根据计划代码查询该计划的报告
+        const res = await listReport({ planCode: row.planCode, pageNum: 1, pageSize: 1 })
+        if (res.rows && res.rows.length > 0) {
+          // 找到报告,打开详情弹窗
+          this.$refs.reportDetail.open(res.rows[0].id)
+        } else {
+          this.$modal.msgWarning('该计划暂无巡检报告')
+        }
+      } catch (e) {
+        this.$modal.msgError('获取报告失败')
+        console.error(e)
+      } finally {
+        this.loading = false
+      }
+    },
+
+    /** 手动巡检:编辑报告 */
+    async handleEditReport(row) {
+      try {
+        this.loading = true
+        // 根据计划代码查询该计划的报告
+        const res = await listReport({ planCode: row.planCode, pageNum: 1, pageSize: 1 })
+        if (res.rows && res.rows.length > 0) {
+          // 找到报告,打开编辑弹窗
+          this.$refs.manualReportForm.openEdit(row, res.rows[0])
+        } else {
+          this.$modal.msgWarning('该计划暂无巡检报告')
+        }
+      } catch (e) {
+        this.$modal.msgError('获取报告失败')
+        console.error(e)
+      } finally {
+        this.loading = false
+      }
+    },
+
+    /** 导出按钮操作 */
+    handleExport() {
+      this.download('/ems/inspection/plan/export', {
+        ...this.queryParams
+      }, `inspection_plan_${this.activeTab}_${new Date().getTime()}.xlsx`)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.inspection-tabs {
+  margin-bottom: 15px;
+}
+
+.inspection-tabs ::v-deep .el-tabs__item {
+  font-size: 14px;
+  padding: 0 20px;
+}
+
+.inspection-tabs ::v-deep .el-tabs__item i {
+  margin-right: 5px;
+}
+
+.inspection-tabs ::v-deep .el-tabs__item.is-active {
+  font-weight: bold;
+}
+
+.delete-btn {
+  color: #F56C6C !important;
+}
+.delete-btn:hover {
+  color: #f78989 !important;
+}
+
+.text-muted {
+  color: #909399;
+  font-size: 12px;
+}
+</style>

+ 423 - 0
ems-ui-cloud/src/views/inspection/report/components/Manualreportform.vue

@@ -0,0 +1,423 @@
+<template>
+  <el-dialog :title="dialogTitle" :visible.sync="visible" width="900px" append-to-body
+             :close-on-click-modal="false" @close="handleClose">
+    <div v-loading="loading" class="manual-report-form">
+      <!-- 计划信息展示 -->
+      <el-card class="plan-info-card" shadow="never">
+        <div slot="header" class="card-header">
+          <span><i class="el-icon-document"></i> 巡检任务信息</span>
+          <el-tag v-if="isEdit" type="warning" size="small" style="margin-left: 10px;">编辑模式</el-tag>
+        </div>
+        <el-descriptions :column="3" border size="small">
+          <el-descriptions-item label="计划名称">{{ plan.planName }}</el-descriptions-item>
+          <el-descriptions-item label="归属区域">{{ plan.areaName }}</el-descriptions-item>
+          <el-descriptions-item label="执行人">{{ plan.executor }}</el-descriptions-item>
+          <el-descriptions-item label="目标类型">{{ getTargetTypeLabel(plan.targetType) }}</el-descriptions-item>
+          <el-descriptions-item label="巡检目标" :span="2">
+            <el-tooltip :content="plan.targetNames" placement="top">
+              <span class="target-text">{{ plan.targetNames }}</span>
+            </el-tooltip>
+          </el-descriptions-item>
+          <el-descriptions-item label="计划时间">{{ plan.planTime || '-' }}</el-descriptions-item>
+          <el-descriptions-item label="计划描述" :span="2">{{ plan.description || '-' }}</el-descriptions-item>
+        </el-descriptions>
+      </el-card>
+
+      <!-- 巡检报告表单 -->
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px" style="margin-top: 20px;">
+        <el-form-item label="巡检结果" prop="resultStatus">
+          <el-radio-group v-model="form.resultStatus">
+            <el-radio-button :label="0">
+              <i class="el-icon-success" style="color: #67C23A"></i> 正常
+            </el-radio-button>
+            <el-radio-button :label="1">
+              <i class="el-icon-error" style="color: #F56C6C"></i> 异常
+            </el-radio-button>
+            <el-radio-button :label="2">
+              <i class="el-icon-warning" style="color: #E6A23C"></i> 部分异常
+            </el-radio-button>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item label="巡检报告" prop="manualRemark" class="report-editor-item">
+          <div class="editor-tip">
+            <i class="el-icon-info"></i>
+            请详细记录巡检过程中发现的问题、设备状态等信息。
+          </div>
+          <!-- 富文本编辑器工具栏 -->
+          <div class="editor-toolbar">
+            <el-button-group>
+              <el-button size="mini" @click="execCommand('bold')" title="加粗">
+                <b>B</b>
+              </el-button>
+              <el-button size="mini" @click="execCommand('italic')" title="斜体">
+                <i>I</i>
+              </el-button>
+              <el-button size="mini" @click="execCommand('underline')" title="下划线">
+                <u>U</u>
+              </el-button>
+            </el-button-group>
+            <el-divider direction="vertical"></el-divider>
+            <el-button-group>
+              <el-button size="mini" @click="execCommand('insertUnorderedList')" title="无序列表">
+                <i class="el-icon-s-operation"></i>
+              </el-button>
+              <el-button size="mini" @click="execCommand('insertOrderedList')" title="有序列表">
+                <i class="el-icon-s-order"></i>
+              </el-button>
+            </el-button-group>
+            <el-divider direction="vertical"></el-divider>
+
+            <input
+              ref="imageInput"
+              type="file"
+              accept="image/*"
+              style="display: none;"
+              @change="handleImageSelect"
+            />
+          </div>
+          <!-- 编辑区域 -->
+          <div
+            ref="editor"
+            class="rich-editor"
+            contenteditable="true"
+            @input="handleEditorInput"
+            @paste="handlePaste"
+            placeholder="请输入巡检报告内容..."
+          ></div>
+        </el-form-item>
+
+        <el-form-item label="备注说明" prop="remark">
+          <el-input v-model="form.remark" type="textarea" :rows="2"
+                    placeholder="其他需要说明的事项(选填)" maxlength="500" show-word-limit />
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="handleClose">取 消</el-button>
+      <el-button type="warning" @click="handleSaveDraft" :loading="submitting" v-if="!isEdit">
+        <i class="el-icon-folder"></i> 保存草稿
+      </el-button>
+      <el-button type="primary" @click="handleSubmit" :loading="submitting">
+        <i class="el-icon-check"></i> {{ isEdit ? '保存修改' : '提交报告' }}
+      </el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { submitManualReport } from '@/api/inspection/plan'
+import { updateManualReport } from '@/api/inspection/report'
+import { getTargetTypeLabel } from '@/enums/InspectionEnums'
+
+export default {
+  name: 'ManualReportForm',
+  data() {
+    return {
+      visible: false,
+      loading: false,
+      submitting: false,
+      isEdit: false,
+      reportId: null,
+      reportCode: null,
+      plan: {},
+      form: {
+        planCode: '',
+        resultStatus: 0,
+        manualRemark: '',
+        remark: ''
+      },
+      rules: {
+        resultStatus: [
+          { required: true, message: '请选择巡检结果', trigger: 'change' }
+        ],
+        manualRemark: [
+          { required: true, message: '请填写巡检报告内容', trigger: 'blur' }
+        ]
+      },
+      imageCount: 0,  // 当前图片数量
+      maxImageCount: 5,  // 最大图片数量
+      maxImageSize: 2 * 1024 * 1024  // 最大图片大小 2MB
+    }
+  },
+  computed: {
+    dialogTitle() {
+      if (this.isEdit) {
+        return `编辑巡检报告 - ${this.plan.planName || ''}`
+      }
+      return `提交巡检报告 - ${this.plan.planName || ''}`
+    }
+  },
+  methods: {
+    getTargetTypeLabel,
+
+    /** 打开新增对话框 */
+    open(plan) {
+      this.isEdit = false
+      this.reportId = null
+      this.reportCode = null
+      this.plan = plan || {}
+      this.form = {
+        planCode: plan.planCode,
+        resultStatus: 0,
+        manualRemark: '',
+        remark: ''
+      }
+      this.imageCount = 0
+      this.visible = true
+
+      this.$nextTick(() => {
+        if (this.$refs.editor) {
+          this.$refs.editor.innerHTML = ''
+        }
+      })
+    },
+
+    /** 打开编辑对话框 */
+    openEdit(plan, report) {
+      this.isEdit = true
+      this.reportId = report.id
+      this.reportCode = report.reportCode
+      this.plan = plan || {}
+
+      // 解析已有报告数据
+      let remark = ''
+      if (report.resultSummary) {
+        try {
+          const summary = JSON.parse(report.resultSummary)
+          remark = summary.remark || ''
+        } catch (e) {
+          console.warn('解析报告摘要失败', e)
+        }
+      }
+
+      this.form = {
+        planCode: plan.planCode,
+        resultStatus: report.resultStatus || 0,
+        manualRemark: report.manualRemark || '',
+        remark: remark
+      }
+
+      this.visible = true
+
+      this.$nextTick(() => {
+        if (this.$refs.editor) {
+          this.$refs.editor.innerHTML = report.manualRemark || ''
+          // 统计已有图片数量
+          this.countImages()
+        }
+      })
+    },
+
+    /** 关闭对话框 */
+    handleClose() {
+      this.visible = false
+      this.isEdit = false
+      this.reportId = null
+      this.reportCode = null
+      this.imageCount = 0
+      this.$refs.form.resetFields()
+    },
+
+    /** 执行编辑器命令 */
+    execCommand(command, value = null) {
+      document.execCommand(command, false, value)
+      this.$refs.editor.focus()
+    },
+
+    /** 编辑器内容变化 */
+    handleEditorInput() {
+      this.form.manualRemark = this.$refs.editor.innerHTML
+      this.countImages()
+    },
+
+    /** 统计编辑器中的图片数量 */
+    countImages() {
+      if (this.$refs.editor) {
+        const imgs = this.$refs.editor.querySelectorAll('img')
+        this.imageCount = imgs.length
+      }
+    },
+
+    /** 触发图片选择 */
+    triggerImageSelect() {
+      if (this.imageCount >= this.maxImageCount) {
+        this.$message.warning(`最多只能插入${this.maxImageCount}张图片`)
+        return
+      }
+      this.$refs.imageInput.click()
+    },
+
+    /** 处理图片选择 */
+    handleImageSelect(e) {
+      const file = e.target.files[0]
+      if (!file) return
+
+      // 重置 input,允许选择相同文件
+      e.target.value = ''
+
+      this.processImage(file)
+    },
+
+    /** 处理粘贴事件(支持粘贴图片) */
+    handlePaste(e) {
+      const items = e.clipboardData?.items
+      if (!items) return
+
+      for (let i = 0; i < items.length; i++) {
+        if (items[i].type.indexOf('image') !== -1) {
+          e.preventDefault()
+          const file = items[i].getAsFile()
+          this.processImage(file)
+          return
+        }
+      }
+    },
+
+
+
+    /** 保存草稿 */
+    handleSaveDraft() {
+      this.form.manualRemark = this.$refs.editor.innerHTML
+      const draftKey = `inspection_draft_${this.form.planCode}`
+      localStorage.setItem(draftKey, JSON.stringify({
+        ...this.form,
+        savedAt: new Date().toISOString()
+      }))
+      this.$message.success('草稿已保存')
+    },
+
+    /** 提交报告 */
+    handleSubmit() {
+      // 更新表单内容
+      this.form.manualRemark = this.$refs.editor.innerHTML
+
+      // 校验内容
+      if (!this.form.manualRemark || this.form.manualRemark === '<br>' ||
+        this.form.manualRemark.replace(/<[^>]+>/g, '').trim() === '') {
+        this.$message.warning('请填写巡检报告内容')
+        return
+      }
+
+      this.$refs.form.validate(valid => {
+        if (!valid) return
+
+        const confirmMsg = this.isEdit
+          ? '确认保存修改吗?'
+          : '确认提交巡检报告吗?提交后将完成该巡检任务。'
+
+        this.$modal.confirm(confirmMsg).then(() => {
+          this.submitting = true
+
+          const submitData = {
+            planCode: this.form.planCode,
+            resultStatus: this.form.resultStatus,
+            manualRemark: this.form.manualRemark,
+            remark: this.form.remark
+          }
+
+          // 编辑模式
+          if (this.isEdit) {
+            submitData.id = this.reportId
+            submitData.reportCode = this.reportCode
+            return updateManualReport(submitData)
+          } else {
+            return submitManualReport(submitData)
+          }
+        }).then(res => {
+          this.$message.success(this.isEdit ? '报告修改成功' : '巡检报告提交成功')
+          localStorage.removeItem(`inspection_draft_${this.form.planCode}`)
+          this.handleClose()
+          this.$emit('success', res.data)
+        }).catch(err => {
+          if (err !== 'cancel' && err.message) {
+            this.$message.error(err.message || '操作失败')
+          }
+        }).finally(() => {
+          this.submitting = false
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.manual-report-form {
+  max-height: 70vh;
+  overflow-y: auto;
+}
+
+.plan-info-card {
+  background: #f5f7fa;
+}
+
+.card-header {
+  font-weight: bold;
+  color: #303133;
+  display: flex;
+  align-items: center;
+}
+
+.target-text {
+  display: inline-block;
+  max-width: 300px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.report-editor-item {
+  margin-top: 10px;
+}
+
+.editor-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-bottom: 10px;
+  padding: 8px 12px;
+  background: #ecf5ff;
+  border-radius: 4px;
+}
+
+.editor-toolbar {
+  display: flex;
+  align-items: center;
+  padding: 8px 10px;
+  background: #f5f7fa;
+  border: 1px solid #dcdfe6;
+  border-bottom: none;
+  border-radius: 4px 4px 0 0;
+  gap: 5px;
+  flex-wrap: wrap;
+}
+
+.rich-editor {
+  min-height: 250px;
+  max-height: 400px;
+  overflow-y: auto;
+  padding: 15px;
+  border: 1px solid #dcdfe6;
+  border-radius: 0 0 4px 4px;
+  outline: none;
+  line-height: 1.8;
+  font-size: 14px;
+}
+
+.rich-editor:focus {
+  border-color: #409EFF;
+}
+
+.rich-editor:empty:before {
+  content: attr(placeholder);
+  color: #c0c4cc;
+}
+
+.rich-editor img {
+  max-width: 100%;
+  border-radius: 4px;
+  display: block;
+  margin: 10px auto;
+}
+</style>

+ 695 - 0
ems-ui-cloud/src/views/inspection/report/components/ReportDetail.vue

@@ -0,0 +1,695 @@
+<template>
+  <el-dialog title="巡检报告详情" :visible.sync="visible" width="1100px" append-to-body top="5vh">
+    <div v-loading="loading" class="report-detail">
+      <!-- 报告概览卡片 -->
+      <el-card class="summary-card" shadow="never">
+        <div slot="header" class="summary-header">
+          <div class="header-left">
+            <span class="report-title">{{ report.planName }}</span>
+            <el-tag :type="getResultStatusInfo(report.resultStatus).type" size="medium" effect="dark">
+              <i :class="getResultStatusInfo(report.resultStatus).icon" style="margin-right: 4px"></i>
+              {{ getResultStatusInfo(report.resultStatus).label }}
+            </el-tag>
+            <el-tag :type="report.planType === 1 ? 'primary' : 'success'" size="small" effect="plain" style="margin-left: 10px">
+              {{ report.planType === 1 ? '手动巡检' : '自动巡检' }}
+            </el-tag>
+          </div>
+          <div class="header-right">
+            <span class="report-code">{{ report.reportCode }}</span>
+          </div>
+        </div>
+
+        <!-- 基本信息 -->
+        <el-row :gutter="20" class="info-row">
+          <el-col :span="6">
+            <div class="info-item">
+              <i class="el-icon-location-outline info-icon"></i>
+              <div class="info-content">
+                <span class="info-label">归属区域</span>
+                <span class="info-value">{{ report.areaName || '-' }}</span>
+              </div>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <i class="el-icon-user info-icon"></i>
+              <div class="info-content">
+                <span class="info-label">执行人</span>
+                <span class="info-value">{{ report.executor || '-' }}</span>
+              </div>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <i class="el-icon-time info-icon"></i>
+              <div class="info-content">
+                <span class="info-label">执行时间</span>
+                <span class="info-value">{{ report.executeTime || '-' }}</span>
+              </div>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="info-item">
+              <i class="el-icon-finished info-icon"></i>
+              <div class="info-content">
+                <span class="info-label">完成时间</span>
+                <span class="info-value">{{ report.finishTime || '-' }}</span>
+              </div>
+            </div>
+          </el-col>
+        </el-row>
+
+        <!-- 自动巡检:统计数据 -->
+        <div v-if="report.planType === 2" class="stats-container">
+          <div class="stat-box total">
+            <div class="stat-number">{{ report.totalCount || 0 }}</div>
+            <div class="stat-text">检查设备数</div>
+          </div>
+          <div class="stat-box normal">
+            <div class="stat-number">{{ report.normalCount || 0 }}</div>
+            <div class="stat-text">正常</div>
+          </div>
+          <div class="stat-box abnormal">
+            <div class="stat-number">{{ report.abnormalCount || 0 }}</div>
+            <div class="stat-text">异常</div>
+          </div>
+          <div class="stat-box rate">
+            <div class="stat-number">{{ passRate }}<span class="stat-unit">%</span></div>
+            <div class="stat-text">通过率</div>
+            <el-progress :percentage="parseFloat(passRate)" :show-text="false"
+                         :stroke-width="6" :color="passRateColor" style="margin-top: 8px" />
+          </div>
+        </div>
+
+        <!-- 手动巡检:巡检目标展示 -->
+        <div v-if="report.planType === 1" class="manual-target-info">
+          <div class="target-label">巡检目标:</div>
+          <div class="target-names">{{ resultSummary.targetNames || '-' }}</div>
+        </div>
+      </el-card>
+
+      <!-- 手动巡检:报告内容 -->
+      <el-card v-if="report.planType === 1" class="manual-report-card" shadow="never">
+        <div slot="header" class="manual-header">
+          <span class="detail-title">
+            <i class="el-icon-document"></i> 巡检报告内容
+          </span>
+          <el-tag :type="getResultStatusInfo(report.resultStatus).type" size="small">
+            巡检结果:{{ getResultStatusInfo(report.resultStatus).label }}
+          </el-tag>
+        </div>
+
+        <!-- 富文本内容展示 -->
+        <div class="manual-content" v-if="report.manualRemark">
+          <div class="rich-content" v-html="report.manualRemark"></div>
+        </div>
+        <el-empty v-else description="暂无报告内容" :image-size="80" />
+
+        <!-- 附件图片展示 -->
+        <div v-if="reportImages.length > 0" class="report-images">
+          <div class="images-title">
+            <i class="el-icon-picture-outline"></i> 现场照片({{ reportImages.length }}张)
+          </div>
+          <div class="images-container">
+            <el-image
+              v-for="(img, index) in reportImages"
+              :key="index"
+              :src="img"
+              :preview-src-list="reportImages"
+              fit="cover"
+              class="report-image"
+            >
+              <div slot="error" class="image-error">
+                <i class="el-icon-picture-outline"></i>
+              </div>
+            </el-image>
+          </div>
+        </div>
+
+        <!-- 附加备注 -->
+        <div v-if="resultSummary.remark" class="extra-remark">
+          <div class="remark-title">附加说明:</div>
+          <div class="remark-content">{{ resultSummary.remark }}</div>
+        </div>
+      </el-card>
+
+      <!-- 自动巡检:设备巡检明细 -->
+      <el-card v-if="report.planType === 2" class="detail-card" shadow="never">
+        <div slot="header" class="detail-header">
+          <span class="detail-title">
+            <i class="el-icon-document-checked"></i> 设备巡检明细
+          </span>
+          <div class="detail-filter">
+            <el-radio-group v-model="detailFilter" size="small">
+              <el-radio-button label="all">全部 ({{ report.totalCount || 0 }})</el-radio-button>
+              <el-radio-button label="normal">正常 ({{ report.normalCount || 0 }})</el-radio-button>
+              <el-radio-button label="abnormal">异常 ({{ report.abnormalCount || 0 }})</el-radio-button>
+            </el-radio-group>
+          </div>
+        </div>
+
+        <el-collapse v-model="activeDevices" accordion v-if="filteredDetails.length > 0">
+          <el-collapse-item v-for="detail in filteredDetails" :key="detail.id" :name="detail.deviceCode">
+            <!-- 设备标题 -->
+            <template slot="title">
+              <div class="device-title">
+                <div class="device-status">
+                  <i :class="detail.resultStatus === 0 ? 'el-icon-success' : 'el-icon-error'"
+                     :style="{ color: detail.resultStatus === 0 ? '#67C23A' : '#F56C6C' }"></i>
+                </div>
+                <div class="device-info">
+                  <span class="device-name">{{ detail.deviceName }}</span>
+                  <span class="device-meta">
+                    {{ detail.deviceModelName }} | {{ detail.areaPath || detail.location }}
+                  </span>
+                </div>
+                <div class="device-badge">
+                  <el-tag :type="detail.resultStatus === 0 ? 'success' : 'danger'" size="mini" effect="plain">
+                    {{ detail.resultStatus === 0 ? '正常' : '异常' }}
+                  </el-tag>
+                </div>
+              </div>
+            </template>
+
+            <!-- 检查项列表 -->
+            <div class="check-list">
+              <div v-for="(item, index) in detail.checkItemList" :key="index" class="check-row">
+                <div class="check-status">
+                  <i :class="item.status === 0 ? 'el-icon-circle-check' : 'el-icon-circle-close'"
+                     :style="{ color: item.status === 0 ? '#67C23A' : '#F56C6C', fontSize: '18px' }"></i>
+                </div>
+                <div class="check-content">
+                  <div class="check-name">{{ item.attrName || item.ruleName }}</div>
+                  <div class="check-detail">
+                    <span class="detail-item">
+                      <span class="detail-label">期望:</span>
+                      <span class="detail-value">{{ item.expectRange }}</span>
+                    </span>
+                    <span class="detail-item">
+                      <span class="detail-label">实际:</span>
+                      <span class="detail-value" :class="{ 'value-error': item.status === 1 }">
+                        {{ item.actualValue || '-' }}{{ item.attrUnit || '' }}
+                      </span>
+                    </span>
+                  </div>
+                </div>
+                <div class="check-result">
+                  <span :class="['result-text', item.status === 0 ? 'success' : 'error']">
+                    {{ item.message }}
+                  </span>
+                </div>
+              </div>
+              <el-empty v-if="!detail.checkItemList || detail.checkItemList.length === 0"
+                        description="暂无检查项" :image-size="60" />
+            </div>
+          </el-collapse-item>
+        </el-collapse>
+
+        <el-empty v-else description="暂无巡检数据" :image-size="100" />
+      </el-card>
+    </div>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="visible = false">关 闭</el-button>
+      <el-button type="primary" icon="el-icon-printer" @click="handlePrint">打印报告</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { getReport } from '@/api/inspection/report'
+import { getResultStatusInfo } from '@/enums/InspectionEnums'
+
+export default {
+  name: 'ReportDetail',
+  data() {
+    return {
+      visible: false,
+      loading: false,
+      report: {},
+      activeDevices: [],
+      detailFilter: 'all'
+    }
+  },
+  computed: {
+    /** 通过率 */
+    passRate() {
+      if (!this.report.totalCount) return '0.0'
+      return ((this.report.normalCount / this.report.totalCount) * 100).toFixed(1)
+    },
+    /** 通过率颜色 */
+    passRateColor() {
+      const rate = parseFloat(this.passRate)
+      if (rate >= 90) return '#67C23A'
+      if (rate >= 70) return '#E6A23C'
+      return '#F56C6C'
+    },
+    /** 过滤后的明细 */
+    filteredDetails() {
+      if (!this.report.details) return []
+      if (this.detailFilter === 'all') return this.report.details
+      if (this.detailFilter === 'normal') return this.report.details.filter(d => d.resultStatus === 0)
+      if (this.detailFilter === 'abnormal') return this.report.details.filter(d => d.resultStatus === 1)
+      return this.report.details
+    },
+    /** 解析结果摘要 */
+    resultSummary() {
+      if (!this.report.resultSummary) return {}
+      try {
+        return JSON.parse(this.report.resultSummary)
+      } catch (e) {
+        return {}
+      }
+    },
+    /** 报告图片列表 */
+    reportImages() {
+      return this.resultSummary.images || []
+    }
+  },
+  methods: {
+    getResultStatusInfo,
+
+    /** 打开对话框 */
+    async open(idOrReport) {
+      this.visible = true
+      this.activeDevices = []
+      this.detailFilter = 'all'
+
+      if (typeof idOrReport === 'object') {
+        // 直接传入报告对象(执行后立即显示)
+        this.report = idOrReport
+        this.expandAbnormalDevice()
+      } else {
+        // 通过ID获取
+        this.loading = true
+        try {
+          const res = await getReport(idOrReport)
+          this.report = res.data || {}
+          this.expandAbnormalDevice()
+        } catch (e) {
+          this.$modal.msgError('获取报告详情失败')
+        } finally {
+          this.loading = false
+        }
+      }
+    },
+
+    /** 默认展开第一个异常设备 */
+    expandAbnormalDevice() {
+      if (this.report.planType === 2 && this.report.details && this.report.details.length > 0) {
+        const abnormal = this.report.details.find(d => d.resultStatus === 1)
+        if (abnormal) {
+          this.activeDevices = [abnormal.deviceCode]
+          this.detailFilter = 'abnormal'
+        } else {
+          this.activeDevices = [this.report.details[0].deviceCode]
+        }
+      }
+    },
+
+    /** 打印报告 */
+    handlePrint() {
+      window.print()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.report-detail {
+  max-height: 75vh;
+  overflow-y: auto;
+  padding-right: 5px;
+}
+
+/* 概览卡片 */
+.summary-card {
+  margin-bottom: 15px;
+}
+
+.summary-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.report-title {
+  font-size: 18px;
+  font-weight: bold;
+  color: #303133;
+}
+
+.report-code {
+  font-size: 13px;
+  color: #909399;
+}
+
+/* 信息行 */
+.info-row {
+  margin-bottom: 20px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  padding: 12px;
+  background: #f5f7fa;
+  border-radius: 6px;
+}
+
+.info-icon {
+  font-size: 24px;
+  color: #409EFF;
+  margin-right: 12px;
+}
+
+.info-content {
+  display: flex;
+  flex-direction: column;
+}
+
+.info-label {
+  font-size: 12px;
+  color: #909399;
+  margin-bottom: 4px;
+}
+
+.info-value {
+  font-size: 14px;
+  color: #303133;
+  font-weight: 500;
+}
+
+/* 统计容器(自动巡检) */
+.stats-container {
+  display: flex;
+  justify-content: space-around;
+  padding: 20px;
+  background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%);
+  border-radius: 8px;
+}
+
+.stat-box {
+  text-align: center;
+  padding: 15px 30px;
+  min-width: 120px;
+}
+
+.stat-number {
+  font-size: 36px;
+  font-weight: bold;
+  line-height: 1.2;
+}
+
+.stat-unit {
+  font-size: 16px;
+  margin-left: 2px;
+}
+
+.stat-text {
+  font-size: 13px;
+  color: #606266;
+  margin-top: 8px;
+}
+
+.stat-box.total .stat-number { color: #409EFF; }
+.stat-box.normal .stat-number { color: #67C23A; }
+.stat-box.abnormal .stat-number { color: #F56C6C; }
+.stat-box.rate .stat-number { color: #303133; }
+
+/* 手动巡检目标信息 */
+.manual-target-info {
+  display: flex;
+  align-items: flex-start;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 6px;
+}
+
+.target-label {
+  font-size: 14px;
+  color: #606266;
+  font-weight: 500;
+  margin-right: 10px;
+  white-space: nowrap;
+}
+
+.target-names {
+  font-size: 14px;
+  color: #303133;
+  line-height: 1.6;
+}
+
+/* 手动巡检报告卡片 */
+.manual-report-card {
+  margin-bottom: 15px;
+}
+
+.manual-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.manual-content {
+  padding: 20px;
+  background: #fafafa;
+  border-radius: 6px;
+  min-height: 200px;
+}
+
+.rich-content {
+  line-height: 1.8;
+  font-size: 14px;
+  color: #303133;
+}
+
+.rich-content img {
+  max-width: 100%;
+  border-radius: 6px;
+  margin: 10px 0;
+}
+
+/* 报告图片 */
+.report-images {
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px dashed #dcdfe6;
+}
+
+.images-title {
+  font-size: 14px;
+  font-weight: 500;
+  color: #606266;
+  margin-bottom: 15px;
+}
+
+.images-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+
+.report-image {
+  width: 120px;
+  height: 120px;
+  border-radius: 6px;
+  cursor: pointer;
+  border: 1px solid #ebeef5;
+}
+
+.image-error {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  background: #f5f7fa;
+  color: #c0c4cc;
+  font-size: 24px;
+}
+
+/* 附加备注 */
+.extra-remark {
+  margin-top: 20px;
+  padding: 15px;
+  background: #fff8e6;
+  border-radius: 6px;
+  border-left: 4px solid #E6A23C;
+}
+
+.remark-title {
+  font-size: 13px;
+  font-weight: 500;
+  color: #E6A23C;
+  margin-bottom: 8px;
+}
+
+.remark-content {
+  font-size: 14px;
+  color: #606266;
+  line-height: 1.6;
+}
+
+/* 明细卡片(自动巡检) */
+.detail-card {
+  margin-bottom: 15px;
+}
+
+.detail-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.detail-title {
+  font-weight: bold;
+  color: #303133;
+}
+
+/* 设备标题 */
+.device-title {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  padding-right: 20px;
+}
+
+.device-status {
+  margin-right: 12px;
+  font-size: 20px;
+}
+
+.device-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.device-name {
+  font-size: 14px;
+  font-weight: 500;
+  color: #303133;
+}
+
+.device-meta {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 2px;
+}
+
+.device-badge {
+  margin-left: auto;
+}
+
+/* 检查列表 */
+.check-list {
+  padding: 10px 20px;
+  background: #fafafa;
+}
+
+.check-row {
+  display: flex;
+  align-items: flex-start;
+  padding: 12px 0;
+  border-bottom: 1px dashed #EBEEF5;
+}
+
+.check-row:last-child {
+  border-bottom: none;
+}
+
+.check-status {
+  width: 30px;
+  flex-shrink: 0;
+  padding-top: 2px;
+}
+
+.check-content {
+  flex: 1;
+  min-width: 0;
+}
+
+.check-name {
+  font-size: 14px;
+  font-weight: 500;
+  color: #303133;
+  margin-bottom: 6px;
+}
+
+.check-detail {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+}
+
+.detail-item {
+  font-size: 13px;
+}
+
+.detail-label {
+  color: #909399;
+  margin-right: 4px;
+}
+
+.detail-value {
+  color: #606266;
+}
+
+.detail-value.value-error {
+  color: #F56C6C;
+  font-weight: 500;
+}
+
+.check-result {
+  width: 80px;
+  text-align: right;
+  flex-shrink: 0;
+}
+
+.result-text {
+  font-size: 13px;
+  font-weight: 500;
+}
+
+.result-text.success { color: #67C23A; }
+.result-text.error { color: #F56C6C; }
+
+/* 打印样式 */
+@media print {
+  .dialog-footer {
+    display: none;
+  }
+  .report-detail {
+    max-height: none;
+    overflow: visible;
+  }
+}
+
+::v-deep .el-collapse-item__header {
+  height: auto !important;
+  line-height: normal !important;
+  padding: 10px 0 !important;
+  align-items: flex-start;
+}
+
+::v-deep .el-collapse-item__header .device-meta {
+  white-space: normal;
+  line-height: 1.4;
+  word-break: break-word;
+}
+
+::v-deep .el-collapse-item__header .device-title {
+  align-items: flex-start;
+}
+
+::v-deep .el-collapse-item__header .el-collapse-item__arrow {
+  margin-top: 2px;
+}
+</style>

+ 191 - 0
ems-ui-cloud/src/views/inspection/report/components/ReportListDialog.vue

@@ -0,0 +1,191 @@
+<template>
+  <el-dialog :title="dialogTitle" :visible.sync="visible" width="900px" append-to-body>
+    <div v-loading="loading">
+      <!-- 筛选条件 -->
+      <el-form :inline="true" size="small" style="margin-bottom: 10px">
+        <el-form-item label="结果状态">
+          <el-select v-model="queryParams.resultStatus" placeholder="全部" clearable style="width: 120px"
+                     @change="handleQuery">
+            <el-option label="正常" :value="0" />
+            <el-option label="异常" :value="1" />
+            <el-option label="部分异常" :value="2" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="时间范围">
+          <el-date-picker v-model="dateRange" type="daterange" range-separator="至"
+                          start-placeholder="开始日期" end-placeholder="结束日期"
+                          value-format="yyyy-MM-dd" style="width: 240px"
+                          @change="handleQuery" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
+        </el-form-item>
+      </el-form>
+
+      <!-- 报告列表 -->
+      <el-table :data="reportList" border size="small" max-height="400">
+        <el-table-column label="报告代码" prop="reportCode" width="170" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <el-link type="primary" @click="viewDetail(scope.row)">{{ scope.row.reportCode }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column label="执行时间" prop="executeTime" width="160" />
+        <el-table-column label="完成时间" prop="finishTime" width="160" />
+        <el-table-column label="执行人" prop="executor" width="90" align="center" />
+        <el-table-column label="结果状态" width="100" align="center">
+          <template slot-scope="scope">
+            <el-tag :type="getResultStatusType(scope.row.resultStatus)" size="small" effect="light">
+              {{ getResultStatusLabel(scope.row.resultStatus) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="检查统计" width="150">
+          <template slot-scope="scope">
+            <span class="stat-item">
+              <span class="stat-label">总数:</span>
+              <span class="stat-value">{{ scope.row.totalCount || 0 }}</span>
+            </span>
+            <span class="stat-item success">
+              <span class="stat-label">正常:</span>
+              <span class="stat-value">{{ scope.row.normalCount || 0 }}</span>
+            </span>
+            <span class="stat-item danger">
+              <span class="stat-label">异常:</span>
+              <span class="stat-value">{{ scope.row.abnormalCount || 0 }}</span>
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="80" align="center">
+          <template slot-scope="scope">
+            <el-button size="mini" type="text" icon="el-icon-view" @click="viewDetail(scope.row)">详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-empty v-if="reportList.length === 0" description="暂无巡检报告" :image-size="80" />
+
+      <!-- 分页 -->
+      <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
+                  :limit.sync="queryParams.pageSize" @pagination="loadReports" layout="total, prev, pager, next" />
+    </div>
+
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="visible = false">关 闭</el-button>
+    </div>
+
+    <!-- 报告详情弹窗 -->
+    <report-detail ref="reportDetail" />
+  </el-dialog>
+</template>
+
+<script>
+import { listReport } from '@/api/inspection/report'
+import ReportDetail from './ReportDetail.vue'
+
+export default {
+  name: 'ReportListDialog',
+  components: { ReportDetail },
+  data() {
+    return {
+      visible: false,
+      loading: false,
+      planCode: null,
+      planName: '',
+      reportList: [],
+      total: 0,
+      dateRange: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        planCode: null,
+        resultStatus: null
+      }
+    }
+  },
+  computed: {
+    dialogTitle() {
+      return this.planName ? `巡检报告 - ${this.planName}` : '巡检报告'
+    }
+  },
+  methods: {
+    open(planCode, planName) {
+      this.planCode = planCode
+      this.planName = planName || ''
+      this.queryParams.planCode = planCode
+      this.queryParams.pageNum = 1
+      this.queryParams.resultStatus = null
+      this.dateRange = []
+      this.visible = true
+      this.loadReports()
+    },
+
+    async loadReports() {
+      this.loading = true
+      try {
+        // 处理日期范围
+        if (this.dateRange && this.dateRange.length === 2) {
+          this.queryParams.params = {
+            beginTime: this.dateRange[0] + ' 00:00:00',
+            endTime: this.dateRange[1] + ' 23:59:59'
+          }
+        } else {
+          this.queryParams.params = {}
+        }
+
+        const res = await listReport(this.queryParams)
+        this.reportList = res.rows || []
+        this.total = res.total || 0
+      } catch (e) {
+        this.$modal.msgError('加载报告列表失败')
+        console.error(e)
+      } finally {
+        this.loading = false
+      }
+    },
+
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.loadReports()
+    },
+
+    viewDetail(row) {
+      this.$refs.reportDetail.open(row.id)
+    },
+
+    getResultStatusType(status) {
+      const map = { 0: 'success', 1: 'danger', 2: 'warning' }
+      return map[status] || 'info'
+    },
+
+    getResultStatusLabel(status) {
+      const map = { 0: '正常', 1: '异常', 2: '部分异常' }
+      return map[status] || '未知'
+    }
+  }
+}
+</script>
+
+<style scoped>
+.stat-item {
+  display: inline-block;
+  margin-right: 8px;
+  font-size: 12px;
+}
+
+.stat-label {
+  color: #909399;
+}
+
+.stat-value {
+  font-weight: bold;
+  margin-left: 2px;
+}
+
+.stat-item.success .stat-value {
+  color: #67C23A;
+}
+
+.stat-item.danger .stat-value {
+  color: #F56C6C;
+}
+</style>

+ 285 - 0
ems-ui-cloud/src/views/inspection/report/index.vue

@@ -0,0 +1,285 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索区域 -->
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
+      <el-form-item label="归属区域" prop="areaCode">
+        <el-select v-model="queryParams.areaCode" placeholder="请选择区域" clearable style="width: 180px">
+          <el-option v-for="item in areaOptions" :key="item.id" :label="item.label" :value="item.id" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="计划名称" prop="planName">
+        <el-input v-model="queryParams.planName" placeholder="请输入计划名称" clearable style="width: 160px"
+                  @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item label="计划类型" prop="planType">
+        <el-select v-model="queryParams.planType" placeholder="请选择" clearable style="width: 110px">
+          <el-option v-for="item in PLAN_TYPE_OPTIONS" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="结果状态" prop="resultStatus">
+        <el-select v-model="queryParams.resultStatus" placeholder="请选择" clearable style="width: 110px">
+          <el-option v-for="item in RESULT_STATUS_OPTIONS" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="执行时间">
+        <el-date-picker v-model="dateRange" type="daterange" range-separator="-"
+                        start-placeholder="开始" end-placeholder="结束" value-format="yyyy-MM-dd"
+                        style="width: 220px" :picker-options="pickerOptions" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作按钮 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
+                   v-hasPermi="['ems:inspection:report:export']">导出</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
+                   v-hasPermi="['ems:inspection:report:remove']">删除</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 数据表格 -->
+    <el-table v-loading="loading" :data="reportList" @selection-change="handleSelectionChange" border>
+      <el-table-column type="selection" width="50" align="center" />
+      <el-table-column label="报告代码" align="left" prop="reportCode" width="180" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <el-link type="primary" @click="handleView(scope.row)">{{ scope.row.reportCode }}</el-link>
+        </template>
+      </el-table-column>
+      <el-table-column label="计划名称" align="left" prop="planName" min-width="150" show-overflow-tooltip />
+      <el-table-column label="归属区域" align="center" prop="areaName" width="120" />
+      <el-table-column label="计划类型" align="center" width="100">
+        <template slot-scope="scope">
+          <el-tag :style="{ backgroundColor: getPlanTypeColor(scope.row.planType), borderColor: getPlanTypeColor(scope.row.planType) }"
+                  effect="dark" size="small">
+            {{ getPlanTypeLabel(scope.row.planType) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="执行人" align="center" prop="executor" width="90" />
+      <el-table-column label="检查统计" align="center" width="130">
+        <template slot-scope="scope">
+          <div class="stat-cell">
+            <span class="stat-normal">{{ scope.row.normalCount }}</span>
+            <span class="stat-separator">/</span>
+            <span class="stat-abnormal">{{ scope.row.abnormalCount }}</span>
+            <span class="stat-separator">/</span>
+            <span class="stat-total">{{ scope.row.totalCount }}</span>
+          </div>
+          <div class="stat-label">正常/异常/总数</div>
+        </template>
+      </el-table-column>
+      <el-table-column label="结果状态" align="center" width="100">
+        <template slot-scope="scope">
+          <el-tag :type="getResultStatusInfo(scope.row.resultStatus).type" size="small" effect="light">
+            <i :class="getResultStatusInfo(scope.row.resultStatus).icon" style="margin-right: 3px"></i>
+            {{ getResultStatusInfo(scope.row.resultStatus).label }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="执行时间" align="center" prop="executeTime" width="150" />
+      <el-table-column label="完成时间" align="center" prop="finishTime" width="150" />
+      <el-table-column label="操作" align="center" fixed="right" width="120" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button size="mini" type="text" icon="el-icon-view" @click="handleView(scope.row)">
+            查看
+          </el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" class="delete-btn"
+                     @click="handleDelete(scope.row)" v-hasPermi="['ems:inspection:report:remove']">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
+                :limit.sync="queryParams.pageSize" @pagination="getList" />
+
+    <!-- 报告详情对话框 -->
+    <report-detail ref="reportDetail" />
+  </div>
+</template>
+
+<script>
+import { listReport, delReport } from '@/api/inspection/report'
+import { areaTreeSelect } from '@/api/basecfg/area'
+import {
+  PLAN_TYPE_OPTIONS,
+  RESULT_STATUS_OPTIONS,
+  getPlanTypeLabel,
+  getPlanTypeColor,
+  getResultStatusInfo
+} from '@/enums/InspectionEnums'
+import ReportDetail from './components/ReportDetail.vue'
+
+export default {
+  name: 'InspectionReport',
+  components: { ReportDetail },
+  data() {
+    return {
+      loading: true,
+      showSearch: true,
+      ids: [],
+      multiple: true,
+      total: 0,
+      reportList: [],
+      areaOptions: [],
+      dateRange: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        areaCode: null,
+        planName: null,
+        planType: null,
+        resultStatus: null
+      },
+      pickerOptions: {
+        shortcuts: [
+          {
+            text: '今天',
+            onClick(picker) {
+              const end = new Date()
+              const start = new Date()
+              picker.$emit('pick', [start, end])
+            }
+          },
+          {
+            text: '最近一周',
+            onClick(picker) {
+              const end = new Date()
+              const start = new Date()
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+              picker.$emit('pick', [start, end])
+            }
+          },
+          {
+            text: '最近一个月',
+            onClick(picker) {
+              const end = new Date()
+              const start = new Date()
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+              picker.$emit('pick', [start, end])
+            }
+          }
+        ]
+      },
+      PLAN_TYPE_OPTIONS,
+      RESULT_STATUS_OPTIONS
+    }
+  },
+  created() {
+    this.getAreaTree()
+    this.getList()
+  },
+  methods: {
+    getPlanTypeLabel,
+    getPlanTypeColor,
+    getResultStatusInfo,
+
+    async getAreaTree() {
+      try {
+        const res = await areaTreeSelect('0', 2)
+        this.areaOptions = res.data || []
+      } catch (e) {
+        console.error('获取区域树失败', e)
+      }
+    },
+
+    getList() {
+      this.loading = true
+      const params = { ...this.queryParams }
+      if (this.dateRange && this.dateRange.length === 2) {
+        params.beginTime = this.dateRange[0]
+        params.endTime = this.dateRange[1]
+      }
+      listReport(params).then(res => {
+        this.reportList = res.rows || []
+        this.total = res.total || 0
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+
+    resetQuery() {
+      this.dateRange = []
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.multiple = !selection.length
+    },
+
+    handleView(row) {
+      this.$refs.reportDetail.open(row.id)
+    },
+
+    handleDelete(row) {
+      const ids = row.id ? [row.id] : this.ids
+      this.$modal.confirm('确认删除选中的巡检报告吗?').then(() => {
+        return delReport(ids)
+      }).then(() => {
+        this.getList()
+        this.$modal.msgSuccess('删除成功')
+      }).catch(() => {})
+    },
+
+    handleExport() {
+      const params = { ...this.queryParams }
+      if (this.dateRange && this.dateRange.length === 2) {
+        params.beginTime = this.dateRange[0]
+        params.endTime = this.dateRange[1]
+      }
+      this.download('/ems/inspection/report/export', params, `inspection_report_${new Date().getTime()}.xlsx`)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.delete-btn {
+  color: #F56C6C !important;
+}
+
+.stat-cell {
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.stat-normal {
+  color: #67C23A;
+}
+
+.stat-abnormal {
+  color: #F56C6C;
+}
+
+.stat-total {
+  color: #409EFF;
+}
+
+.stat-separator {
+  color: #C0C4CC;
+  margin: 0 2px;
+}
+
+.stat-label {
+  font-size: 11px;
+  color: #909399;
+  margin-top: 2px;
+}
+</style>

+ 0 - 476
ems-ui-cloud/src/views/task/AutoInspection.vue

@@ -1,476 +0,0 @@
-<template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="任务名称" prop="jobName">
-        <el-input
-            v-model="queryParams.jobName"
-            placeholder="请输入任务名称"
-            clearable
-            @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="任务状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable>
-          <el-option
-              v-for="dict in dict.type.sys_job_status"
-              :key="dict.value"
-              :label="dict.label"
-              :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
-            type="warning"
-            plain
-            icon="el-icon-download"
-            size="mini"
-            @click="handleExport"
-            v-hasPermi="['ems:auto-task:query']"
-        >导出
-        </el-button>
-      </el-col>
-      <el-col :span="1.5">
-        <el-button
-            type="info"
-            plain
-            icon="el-icon-s-operation"
-            size="mini"
-            @click="handleJobLog"
-            v-hasPermi="['ems:auto-task:query']"
-        >日志
-        </el-button>
-      </el-col>
-      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
-
-    <el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="任务编号" width="100" align="center" prop="jobId" />
-      <el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
-      <el-table-column label="任务组名" align="center" prop="jobGroup">
-        <template slot-scope="scope">
-          <dict-tag :options="dict.type.sys_job_group" :value="scope.row.jobGroup" />
-        </template>
-      </el-table-column>
-      <el-table-column label="cron执行表达式" align="center" prop="cronExpression" :show-overflow-tooltip="true" />
-      <el-table-column label="状态" align="center"  v-hasPermi="['ems:auto-task:exc']" >
-        <template slot-scope="scope">
-          <el-switch
-              v-model="scope.row.status"
-              active-value="0"
-              inactive-value="1"
-              @change="handleStatusChange(scope.row)"
-          ></el-switch>
-        </template>
-      </el-table-column>
-      <el-table-column label="" align="center" class-name="small-padding fixed-width">
-        <template slot-scope="scope">
-          <el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)"
-                       v-hasPermi="['ems:auto-task:exc', 'ems:auto-task:query']">
-            <el-button size="mini" type="text" icon="el-icon-d-arrow-right">操作</el-button>
-            <el-dropdown-menu slot="dropdown">
-              <el-dropdown-item command="handleRun" icon="el-icon-caret-right"
-                                v-hasPermi="['ems:auto-task:exc']">执行一次
-              </el-dropdown-item>
-              <el-dropdown-item command="handleView" icon="el-icon-view"
-                                v-hasPermi="['ems:auto-task:query']">任务详细
-              </el-dropdown-item>
-              <el-dropdown-item command="handleJobLog" icon="el-icon-s-operation"
-                                v-hasPermi="['ems:auto-task:query']">调度日志
-              </el-dropdown-item>
-            </el-dropdown-menu>
-          </el-dropdown>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <pagination
-        v-show="total>0"
-        :total="total"
-        :page.sync="queryParams.pageNum"
-        :limit.sync="queryParams.pageSize"
-        @pagination="getList"
-    />
-
-    <!-- 添加或修改定时任务对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="任务名称" prop="jobName">
-              <el-input v-model="form.jobName" placeholder="请输入任务名称" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="任务分组" prop="jobGroup">
-              <el-select v-model="form.jobGroup" placeholder="请选择任务分组">
-                <el-option
-                    v-for="dict in dict.type.sys_job_group"
-                    :key="dict.value"
-                    :label="dict.label"
-                    :value="dict.value"
-                ></el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item prop="invokeTarget">
-              <span slot="label">
-                调用方法
-                <el-tooltip placement="top">
-                  <div slot="content">
-                    Bean调用示例:ryTask.ryParams('ry')
-                    <br />Class类调用示例:com.ruoyi.quartz.task.RyTask.ryParams('ry')
-                    <br />参数说明:支持字符串,布尔类型,长整型,浮点型,整型
-                  </div>
-                  <i class="el-icon-question"></i>
-                </el-tooltip>
-              </span>
-              <el-input v-model="form.invokeTarget" placeholder="请输入调用目标字符串" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="cron表达式" prop="cronExpression">
-              <el-input v-model="form.cronExpression" placeholder="请输入cron执行表达式">
-                <template slot="append">
-                  <el-button type="primary" @click="handleShowCron">
-                    生成表达式
-                    <i class="el-icon-time el-icon--right"></i>
-                  </el-button>
-                </template>
-              </el-input>
-            </el-form-item>
-          </el-col>
-          <el-col :span="24" v-if="form.jobId !== undefined">
-            <el-form-item label="状态">
-              <el-radio-group v-model="form.status">
-                <el-radio
-                    v-for="dict in dict.type.sys_job_status"
-                    :key="dict.value"
-                    :label="dict.value"
-                >{{ dict.label }}
-                </el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="执行策略" prop="misfirePolicy">
-              <el-radio-group v-model="form.misfirePolicy" size="small">
-                <el-radio-button label="1">立即执行</el-radio-button>
-                <el-radio-button label="2">执行一次</el-radio-button>
-                <el-radio-button label="3">放弃执行</el-radio-button>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="是否并发" prop="concurrent">
-              <el-radio-group v-model="form.concurrent" size="small">
-                <el-radio-button label="0">允许</el-radio-button>
-                <el-radio-button label="1">禁止</el-radio-button>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="cancel">取 消</el-button>
-      </div>
-    </el-dialog>
-
-    <el-dialog title="Cron表达式生成器" :visible.sync="openCron" append-to-body destroy-on-close class="scrollbar">
-      <crontab @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab>
-    </el-dialog>
-
-    <!-- 任务日志详细 -->
-    <el-dialog title="任务详细" :visible.sync="openView" width="700px" append-to-body>
-      <el-form ref="form" :model="form" label-width="120px" size="mini">
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="任务编号:">{{ form.jobId }}</el-form-item>
-            <el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="任务分组:">{{ jobGroupFormat(form) }}</el-form-item>
-            <el-form-item label="创建时间:">{{ form.createTime }}</el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="cron表达式:">{{ form.cronExpression }}</el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="下次执行时间:">{{ parseTime(form.nextValidTime) }}</el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="调用目标方法:">{{ form.invokeTarget }}</el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="任务状态:">
-              <div v-if="form.status == 0">正常</div>
-              <div v-else-if="form.status == 1">暂停</div>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="是否并发:">
-              <div v-if="form.concurrent == 0">允许</div>
-              <div v-else-if="form.concurrent == 1">禁止</div>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="执行策略:">
-              <div v-if="form.misfirePolicy == 0">默认策略</div>
-              <div v-else-if="form.misfirePolicy == 1">立即执行</div>
-              <div v-else-if="form.misfirePolicy == 2">执行一次</div>
-              <div v-else-if="form.misfirePolicy == 3">放弃执行</div>
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="openView = false">关 闭</el-button>
-      </div>
-    </el-dialog>
-  </div>
-</template>
-
-<script>
-import { addJob, changeJobStatus, delJob, getJob, listJob, runJob, updateJob } from '@/api/monitor/job';
-import Crontab from '@/components/Crontab';
-import { JOB_GROUP } from '@/enums/JobEnum';
-
-export default {
-  components: { Crontab },
-  name: 'Job',
-  dicts: ['sys_job_group', 'sys_job_status'],
-  data() {
-    return {
-      // 遮罩层
-      loading: true,
-      // 选中数组
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
-      // 显示搜索条件
-      showSearch: true,
-      // 总条数
-      total: 0,
-      // 定时任务表格数据
-      jobList: [],
-      // 弹出层标题
-      title: '',
-      // 是否显示弹出层
-      open: false,
-      // 是否显示详细弹出层
-      openView: false,
-      // 是否显示Cron表达式弹出层
-      openCron: false,
-      // 传入的表达式
-      expression: '',
-      // 查询参数
-      queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        jobName: undefined,
-        jobGroup: JOB_GROUP.emsAutoTask.value,
-        status: undefined,
-      },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        jobName: [
-          {
-            required: true,
-            message: '任务名称不能为空',
-            trigger: 'blur',
-          },
-        ],
-        invokeTarget: [
-          {
-            required: true,
-            message: '调用目标字符串不能为空',
-            trigger: 'blur',
-          },
-        ],
-        cronExpression: [
-          {
-            required: true,
-            message: 'cron执行表达式不能为空',
-            trigger: 'blur',
-          },
-        ],
-      },
-    };
-  },
-  created() {
-    this.getList();
-  },
-  methods: {
-    /** 查询定时任务列表 */
-    getList() {
-      this.loading = true;
-      listJob(this.queryParams).then(response => {
-        this.jobList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
-    },
-    // 任务组名字典翻译
-    jobGroupFormat(row, column) {
-      return this.selectDictLabel(this.dict.type.sys_job_group, row.jobGroup);
-    },
-    // 取消按钮
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    // 表单重置
-    reset() {
-      this.form = {
-        jobId: undefined,
-        jobName: undefined,
-        jobGroup: JOB_GROUP.emsAutoTask.value,
-        invokeTarget: undefined,
-        cronExpression: undefined,
-        misfirePolicy: 1,
-        concurrent: 1,
-        status: '0',
-      };
-      this.resetForm('form');
-    },
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
-    },
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm('queryForm');
-      this.handleQuery();
-    },
-    // 多选框选中数据
-    handleSelectionChange(selection) {
-      this.ids = selection.map(item => item.jobId);
-      this.single = selection.length != 1;
-      this.multiple = !selection.length;
-    },
-    // 更多操作触发
-    handleCommand(command, row) {
-      switch (command) {
-        case 'handleRun':
-          this.handleRun(row);
-          break;
-        case 'handleView':
-          this.handleView(row);
-          break;
-        case 'handleJobLog':
-          this.handleJobLog(row);
-          break;
-        default:
-          break;
-      }
-    },
-    // 任务状态修改
-    handleStatusChange(row) {
-      let text = row.status === '0' ? '启用' : '停用';
-      this.$modal.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?').then(function () {
-        return changeJobStatus(row.jobId, row.status);
-      }).then(() => {
-        this.$modal.msgSuccess(text + '成功');
-      }).catch(function () {
-        row.status = row.status === '0' ? '1' : '0';
-      });
-    },
-    /* 立即执行一次 */
-    handleRun(row) {
-      this.$modal.confirm('确认要立即执行一次"' + row.jobName + '"任务吗?').then(function () {
-        return runJob(row.jobId, row.jobGroup);
-      }).then(() => {
-        this.$modal.msgSuccess('执行成功');
-      }).catch(() => {});
-    },
-    /** 任务详细信息 */
-    handleView(row) {
-      getJob(row.jobId).then(response => {
-        this.form = response.data;
-        this.openView = true;
-      });
-    },
-    /** cron表达式按钮操作 */
-    handleShowCron() {
-      this.expression = this.form.cronExpression;
-      this.openCron = true;
-    },
-    /** 确定后回传值 */
-    crontabFill(value) {
-      this.form.cronExpression = value;
-    },
-    /** 任务日志列表查询 */
-    handleJobLog(row) {
-      const jobId = row.jobId || 0;
-      this.$router.push('/monitor/job-log/index/' + jobId);
-    },
-    /** 新增按钮操作 */
-    handleAdd() {
-      this.reset();
-      this.open = true;
-      this.title = '添加任务';
-    },
-    /** 修改按钮操作 */
-    handleUpdate(row) {
-      this.reset();
-      const jobId = row.jobId || this.ids;
-      getJob(jobId).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = '修改任务';
-      });
-    },
-    /** 提交按钮 */
-    submitForm: function () {
-      this.$refs['form'].validate(valid => {
-        if (valid) {
-          if (this.form.jobId != undefined) {
-            updateJob(this.form).then(response => {
-              this.$modal.msgSuccess('修改成功');
-              this.open = false;
-              this.getList();
-            });
-          } else {
-            addJob(this.form).then(response => {
-              this.$modal.msgSuccess('新增成功');
-              this.open = false;
-              this.getList();
-            });
-          }
-        }
-      });
-    },
-    /** 删除按钮操作 */
-    handleDelete(row) {
-      const jobIds = row.jobId || this.ids;
-      this.$modal.confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?').then(function () {
-        return delJob(jobIds);
-      }).then(() => {
-        this.getList();
-        this.$modal.msgSuccess('删除成功');
-      }).catch(() => {});
-    },
-    /** 导出按钮操作 */
-    handleExport() {
-      this.download('ems/monitor/job/export', {
-        ...this.queryParams,
-      }, `job_${new Date().getTime()}.xlsx`);
-    },
-  },
-};
-</script>

+ 0 - 497
ems-ui-cloud/src/views/task/ManualInspection.vue

@@ -1,497 +0,0 @@
-<template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="计划名称" prop="taskName">
-        <el-input
-            v-model="queryParams.taskName"
-            placeholder="请输入计划名称"
-            clearable
-            @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="计划状态" prop="taskStatus">
-        <el-select v-model="queryParams.taskStatus" placeholder="请选择任务状态" clearable>
-          <el-option
-              v-for="dict in dict.type.task_status"
-              :key="dict.value"
-              :label="dict.label"
-              :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="执行人" prop="executor">
-        <el-input
-            v-model="queryParams.executor"
-            placeholder="请输入执行人"
-            clearable
-            @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="对象类型" prop="objType">
-        <el-select v-model="queryParams.objType" placeholder="请选择巡检对象" clearable>
-          <el-option
-              v-for="dict in dict.type.obj_type"
-              :key="dict.value"
-              :label="dict.label"
-              :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
-            type="primary"
-            plain
-            icon="el-icon-plus"
-            size="mini"
-            @click="handleAdd"
-            v-hasPermi="['ems:inspection-task:add']"
-        >新增
-        </el-button>
-      </el-col>
-      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
-
-    <el-table v-loading="loading" :data="inspectionTaskList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="计划代码" align="left" prop="taskCode" show-overflow-tooltip width="130"/>
-      <el-table-column label="计划名称" align="left" prop="taskName" show-overflow-tooltip width="150"/>
-      <el-table-column label="巡检对象" align="center" prop="objName" show-overflow-tooltip width="200" >
-        <template slot-scope="scope">
-          <div >{{ formatDict(scope.row.objType,dict.type.obj_type)+"-" + scope.row.objName}}</div>
-        </template>
-      </el-table-column>
-      <el-table-column label="开始时间" align="center" prop="startTime" width="100">
-        <template slot-scope="scope">
-          <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="结束时间" align="center" prop="endTime" width="100">
-        <template slot-scope="scope">
-          <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="执行人" align="center" prop="executor" />
-      <el-table-column label="任务状态" align="center" prop="taskStatus">
-        <template slot-scope="scope">
-          <dict-tag :options="dict.type.task_status" :value="scope.row.taskStatus" />
-        </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center" fixed="right" width="180" class-name="small-padding fixed-width">
-        <template slot-scope="scope">
-          <el-button size="mini" type="text" icon="el-icon-info" @click="handleInspection(scope.row)" v-hasPermi="['ems:inspection-task:edit']">
-            巡检</el-button>
-          <el-button size="mini" type="text"  icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems:inspection-task:edit']">
-            修改</el-button>
-          <el-button size="mini" type="text" icon="el-icon-delete" class="deleteBtn" @click="handleDelete(scope.row)" v-hasPermi="['basecfg:emsfacs:remove']">
-            删除</el-button>
-
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <pagination
-        v-show="total>0"
-        :total="total"
-        :page.sync="queryParams.pageNum"
-        :limit.sync="queryParams.pageSize"
-        @pagination="getList"
-    />
-
-    <!-- 添加或修改巡检任务对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="550px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
-        <el-form-item label="任务代码" prop="taskCode" v-if="form.taskCode">
-          <el-input v-model="form.taskCode" placeholder="请输入任务代码" disabled />
-        </el-form-item>
-        <el-form-item label="归属区域" prop="areaCode">
-          <el-select v-model="form.areaCode" placeholder="请选择归属区域" >
-            <el-option v-for="item in areaOptions" :label="item.label" :value="item.id" :key="item.id" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="任务名称" prop="taskName">
-          <el-input v-model="form.taskName" placeholder="请输入任务名称" />
-        </el-form-item>
-        <el-form-item label="开始时间" prop="startTime">
-          <el-date-picker clearable
-                          v-model="form.startTime"
-                          type="datetime"
-                          format="yyyy-MM-dd HH:mm"
-                          value-format="yyyy-MM-dd HH:mm:00"
-                          :style="{width: '100%'}"
-                          placeholder="请选择开始时间">
-          </el-date-picker>
-        </el-form-item>
-        <el-form-item label="执行人" prop="executor">
-          <el-input v-model="form.executor" placeholder="请输入执行人" />
-        </el-form-item>
-        <el-form-item label="巡检对象" prop="objType">
-          <el-radio-group v-model="form.objType" @input="onObjectCheck">
-            <el-radio
-                v-for="dict in taskObjType"
-                :key="dict.value"
-                :label="parseInt(dict.value)"
-            >{{ dict.label }}
-            </el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="选择巡检对象" prop="objCode">
-          <el-select
-              v-model="form.objCode"
-              filterable
-              remote
-              reserve-keyword
-              placeholder="请选择选择巡检对象"
-              :remote-method="remoteMethod"
-              :loading="loading"
-              @change="onObjChange"
-          >
-            <el-option
-                v-for="item in objOptions"
-                :key="item.value"
-                :label="item.label"
-                :value="item.value">
-            </el-option>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="对象名称" prop="objName">
-          <el-input v-model="form.objName" placeholder="请输入对象名称" />
-        </el-form-item>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="cancel">取 消</el-button>
-      </div>
-    </el-dialog>
-    <report-form
-        title="提交巡检报告"
-        :ok-callback="onReportSubmit"
-        :is-add="true"
-        ref="reportForm"
-        :task-info="form"
-    >
-    </report-form>
-  </div>
-</template>
-
-<script>
-import { areaTreeSelect, listArea } from '@/api/basecfg/area'
-import { listFacs } from '@/api/basecfg/emsfacs';
-import { addInspectionPlan, delInspectionPlan, getInspectionPlan, listInspectionPlan, updateInspectionPlan } from '@/api/task/inspectionPlan';
-import { OBJ_TYPE } from '@/enums/DeviceFac';
-import { TASK_TYPES } from '@/enums/TaskEnums';
-import { copyObj } from '@/utils';
-import ReportForm from '@/views/task/report/ReportForm.vue';
-
-export default {
-  name: 'InspectionTask',
-  components: { ReportForm },
-  dicts: ['task_status', 'obj_type', 'task_type', 'sys_job_group'],
-  computed: {
-    taskObjType() {
-      const temp = copyObj(this.dict.type.obj_type);
-      return _.remove(temp, (obj) => obj.value != OBJ_TYPE.devc.value);
-    },
-  },
-  data() {
-    return {
-      // 遮罩层
-      loading: true,
-      // 选中数组
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
-      // 显示搜索条件
-      showSearch: true,
-      // 总条数
-      total: 0,
-      // 巡检任务表格数据
-      inspectionTaskList: [],
-      // 弹出层标题
-      title: '',
-      // 是否显示弹出层
-      open: false,
-      openInspection: false,
-      objOptions: [],
-      areaOptions: [],
-      // 查询参数
-      queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        areaCode: null,
-        taskCode: null,
-        taskName: null,
-        taskType: TASK_TYPES.manual.value,
-        taskStatus: null,
-        executor: null,
-        objType: null,
-        objName: null,
-      },
-      // 表单参数
-      form: {
-        taskType: TASK_TYPES.manual.value,
-      },
-      // 表单校验
-      rules: {
-        areaCode: [
-          {
-            required: true,
-            message: '区域不能为空',
-            trigger: 'blur',
-          },
-        ],
-        executor: [
-          {
-            required: true,
-            message: '执行人不能为空',
-            trigger: 'blur',
-          },
-        ],
-        taskName: [
-          {
-            required: true,
-            message: '任务名称不能为空',
-            trigger: 'blur',
-          },
-        ],
-        taskStatus: [
-          {
-            required: true,
-            message: '任务状态不能为空',
-            trigger: 'change',
-          },
-        ],
-        startTime: [
-          {
-            required: true,
-            message: '开始时间不能为空',
-            trigger: 'blur',
-          },
-        ],
-        endTime: [
-          {
-            required: true,
-            message: '结束时间不能为空',
-            trigger: 'blur',
-          },
-        ],
-        objType: [
-          {
-            required: true,
-            message: '巡检对象不能为空',
-            trigger: 'change',
-          },
-        ],
-        objCode: [
-          {
-            required: true,
-            message: '选择巡检对象不能为空',
-            trigger: 'change',
-          },
-        ],
-      },
-    };
-  },
-  created() {
-    this.getAreaTreeByTag('0', 1)
-    this.getList();
-  },
-  methods: {
-    formatDict (val,options) {
-      let name = ''
-      options.forEach(item => {
-        if (item.value==val) {
-          name= item.label
-        }
-      })
-      return name
-    },
-    objHandle() {
-      return {
-        [OBJ_TYPE.fac.value]: async (param = {}) => {
-          const {
-            key = '',
-            objCode = '',
-          } = param;
-          const { rows } = await listFacs({
-            pageNum: 1,
-            pageSize: 99,
-            facsName: key,
-            facsCode: objCode,
-          });
-          let result = [];
-          if (rows.length > 0) {
-            result = rows.map((item) => {
-              return {
-                value: item.facsCode,
-                label: `${item.facsName}(${item.refAreaName})`,
-              };
-            });
-          }
-          this.objOptions = result;
-        },
-        [OBJ_TYPE.area.value]: async (param = {}) => {
-          const {
-            key = '',
-            objCode = '',
-          } = param;
-          const { rows } = await listArea({
-            pageNum: 1,
-            pageSize: 99,
-            areaName: key,
-            areaCode: objCode,
-          });
-          let result = [];
-          if (rows.length > 0) {
-            result = rows.map((item) => {
-              return {
-                value: item.areaCode,
-                label: item.areaName,
-              };
-            });
-          }
-          this.objOptions = result;
-        },
-        [OBJ_TYPE.devc.value]: function () {},
-      };
-    },
-    /** 查询区域树结构 */
-    async getAreaTreeByTag(areaCode, layer) {
-      await areaTreeSelect(areaCode, layer).then(response => {
-        this.areaOptions = response.data
-      })
-    },
-    remoteMethod(key) {
-      this.objHandle()[this.form.objType]({ key });
-    },
-    onObjChange(val) {
-      const selectedOption = this.objOptions.find(option => option.value === val);
-      this.form.objName = selectedOption ? selectedOption.label : '';
-    },
-    /** 查询巡检任务列表 */
-    getList() {
-      this.loading = true;
-      listInspectionPlan(this.queryParams).then(response => {
-        this.inspectionTaskList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
-    },
-    // 取消按钮
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    // 表单重置
-    reset() {
-      this.form = {
-        id: null,
-        taskName: null,
-        taskStatus: null,
-        startTime: null,
-        taskType: TASK_TYPES.manual.value,
-        endTime: null,
-        executor: null,
-        objType: null,
-        objCode: null,
-        objName: null,
-      };
-      this.objOptions = [];
-      this.resetForm('form');
-    },
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList();
-    },
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm('queryForm');
-      this.handleQuery();
-    },
-    // 多选框选中数据
-    handleSelectionChange(selection) {
-      this.ids = selection.map(item => item.id);
-      this.single = selection.length !== 1;
-      this.multiple = !selection.length;
-    },
-    /** 新增按钮操作 */
-    handleAdd() {
-      this.reset();
-      this.open = true;
-      this.title = '添加巡检计划';
-    },
-    /** 修改按钮操作 */
-    handleUpdate(row) {
-      this.reset();
-      const id = row.id || this.ids;
-      getInspectionPlan(id).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = '修改巡检计划';
-        this.$nextTick(() => {
-          this.objHandle()[this.form.objType]({ objCode: this.form.objCode });
-        });
-      });
-    },
-    /** 提交按钮 */
-    submitForm() {
-      this.$refs['form'].validate(valid => {
-        if (valid) {
-          if (this.form.id != null) {
-            updateInspectionPlan(this.form).then(response => {
-              this.$modal.msgSuccess('修改成功');
-              this.open = false;
-              this.getList();
-            });
-          } else {
-            addInspectionPlan(this.form).then(response => {
-              this.$modal.msgSuccess('新增成功');
-              this.open = false;
-              this.getList();
-            });
-          }
-        }
-      });
-    },
-    /** 删除按钮操作 */
-    handleDelete(row) {
-      const ids = row.id || this.ids;
-      this.$modal.confirm('是否确认删除巡检任务编号为"' + ids + '"的数据项?').then(function () {
-        return delInspectionPlan(ids);
-      }).then(() => {
-        this.getList();
-        this.$modal.msgSuccess('删除成功');
-      }).catch(() => {});
-    },
-
-    onReportSubmit() {
-      this.$modal.msgSuccess('提交成功');
-      this.openInspection = false;
-      this.getList();
-    },
-    handleInspection(row) {
-      const id = row.id;
-      getInspectionPlan(id).then(response => {
-        this.form = response.data;
-        this.$nextTick(() => {
-          this.$refs.reportForm.show();
-        });
-
-      });
-    },
-    onObjectCheck(objType) {
-      debugger
-      this.objHandle()[objType]();
-    },
-  },
-};
-</script>

+ 0 - 3
ems-ui-cloud/src/views/task/index.scss

@@ -1,3 +0,0 @@
-.task-container {
-  margin: 20px;
-}

+ 0 - 68
ems-ui-cloud/src/views/task/index.vue

@@ -1,68 +0,0 @@
-<template>
-  <div class="task-container">
-    <el-row :gutter="10">
-      <el-tabs v-model="activeName" >
-        <el-tab-pane label="手动巡检" name="ManualInspection">
-          <manual-inspection></manual-inspection>
-        </el-tab-pane>
-        <el-tab-pane label="自动巡检" name="second">
-          <AutoInspection></AutoInspection>
-        </el-tab-pane>
-      </el-tabs>
-    </el-row>
-
-  </div>
-</template>
-
-<script>
-import AutoInspection from '@/views/task/AutoInspection.vue';
-import ManualInspection from '@/views/task/ManualInspection.vue';
-import {areaTreeSelect} from '@/api/basecfg/area'
-export default {
-  components: {
-    ManualInspection,
-    AutoInspection,
-  },
-  name: 'task',
-  data () {
-    return {
-      activeName: 'ManualInspection',
-      areaName: undefined,
-      areaOptions: [],
-      areaCode:null
-    };
-  },
-  watch: {
-     // 根据名称筛选区域树
-     areaName (val) {
-      this.$refs.tree.filter(val)
-    }
-  },
-  async created () {
-    await this.getAreaTreeByTag('0', 1)
-  },
-  methods: {
-    /** 查询区域树结构 */
-    async getAreaTreeByTag(areaCode, layer) {
-      await areaTreeSelect(areaCode, layer).then(response => {
-        this.areaOptions = [{
-          id: '-1',
-          label: '全部',
-          children: []
-        }].concat(response.data)
-        this.areaCode = '-1'
-      })
-    },
-    // 筛选节点
-    filterNode (value, data) {
-      if (!value) return true
-      return data.label.indexOf(value) !== -1
-    },
-    handleNodeClick (data, node) {
-      this.areaCode = data.id
-      //this.getList()
-    },
-  },
-};
-</script>
-<style src="./index.scss" lang="scss"></style>

+ 0 - 166
ems-ui-cloud/src/views/task/report/ReportForm.vue

@@ -1,166 +0,0 @@
-<template>
-  <!-- 添加或修改巡检报告对话框 -->
-  <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
-    <el-form ref="form" :model="form" :rules="rules" label-width="100px">
-      <el-form-item label="任务代码" prop="taskCode">
-        <el-input v-model="form.taskCode" placeholder="请输入任务代码" />
-      </el-form-item>
-      <el-form-item label="结果状态" prop="resultStatus">
-        <el-radio-group v-model="form.resultStatus">
-          <el-radio
-              v-for="dict in dict.type.inspection_result"
-              :key="dict.value"
-              :label="parseInt(dict.value)"
-          >{{ dict.label }}
-          </el-radio>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="结果描述">
-        <editor v-model="form.resultMsg" :min-height="192" />
-      </el-form-item>
-      <el-form-item label="完成时间" prop="finishTime">
-        <el-date-picker clearable
-                        v-model="form.finishTime"
-                        type="datetime"
-                        format="yyyy-MM-dd HH:mm"
-                        value-format="yyyy-MM-dd HH:mm:00"
-                        :style="{width: '100%'}"
-                        placeholder="请选择开始时间">
-        </el-date-picker>
-      </el-form-item>
-    </el-form>
-    <div slot="footer" class="dialog-footer">
-      <el-button type="primary" @click="submitForm">确 定</el-button>
-      <el-button @click="cancel">取 消</el-button>
-    </div>
-  </el-dialog>
-</template>
-
-<script>
-import { addInspectionReport, getInspectionReport, updateInspectionReport } from '@/api/task/inspectionReport';
-import { Base64 } from 'js-base64';
-
-export default {
-  name: 'ReportForm',
-  dicts: ['inspection_result'],
-  props: {
-    taskInfo: {
-      type: Object,
-      default: null,
-    },
-    title: {
-      type: String,
-      default: '添加巡检报告',
-    },
-    reportId: {
-      type: Number,
-      default: null,
-    },
-    isAdd: {
-      type: Boolean,
-      default: true,
-    },
-    okCallback: {
-      type: Function,
-      default: () => {},
-    },
-  },
-  watch: {
-    taskInfo(val) {
-      this.form.taskCode = val?.taskCode;
-    },
-  },
-  data() {
-    return {
-      // 选中数组
-      ids: [],
-      // 是否显示弹出层
-      open: false,
-      // 表单参数
-      form: {
-        taskCode: this.taskInfo?.taskCode,
-
-      },
-      // 表单校验
-      rules: {
-        taskCode: [
-          {
-            required: true,
-            message: '任务代码不能为空',
-            trigger: 'blur',
-          },
-        ],
-        resultStatus: [
-          {
-            required: true,
-            message: '结果状态不能为空',
-            trigger: 'change',
-          },
-        ],
-      },
-    };
-  },
-  created() {
-
-  },
-  methods: {
-    // 取消按钮
-    cancel() {
-      this.open = false;
-      this.reset();
-    },
-    // 表单重置
-    reset() {
-      this.form = {
-        id: null,
-        taskCode: null,
-        resultStatus: null,
-        resultMsg: null,
-        finishTime: null,
-      };
-      this.resetForm('form');
-    },
-    /** 修改按钮操作 */
-    handleUpdate() {
-      this.reset();
-      const id = this.reportId;
-      getInspectionReport(id).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = '修改巡检报告';
-      });
-    },
-    /** 提交按钮 */
-    submitForm() {
-      this.$refs['form'].validate(valid => {
-
-        if (valid) {
-
-          if (this.form.resultMsg) {
-            this.form.resultMsg = Base64.encode(this.form.resultMsg);
-          }
-          if (this.form.id != null) {
-            updateInspectionReport(this.form).then(response => {
-              this.$modal.msgSuccess('修改成功');
-              this.open = false;
-              this.okCallback();
-            });
-          } else {
-            addInspectionReport(this.form).then(response => {
-              this.$modal.msgSuccess('新增成功');
-              this.open = false;
-              this.okCallback();
-            });
-          }
-        }
-      });
-    },
-    show() {
-      this.open = true;
-      if (!this.isAdd) {
-        this.handleUpdate();
-      }
-    },
-  },
-};
-</script>

+ 0 - 318
ems-ui-cloud/src/views/task/report/index.vue

@@ -1,318 +0,0 @@
-<template>
-  <div class="app-container">
-    <el-row :gutter="10">
-
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
-          label-width="68px">
-          <el-form-item label="任务代码" prop="taskCode">
-            <el-input v-model="queryParams.taskCode" placeholder="请输入任务代码" clearable
-              @keyup.enter.native="handleQuery" />
-          </el-form-item>
-          <el-form-item label="结果状态" prop="resultStatus">
-            <el-select v-model="queryParams.resultStatus" placeholder="请选择结果状态" clearable>
-              <el-option v-for="dict in dict.type.inspection_result" :key="dict.value" :label="dict.label"
-                :value="dict.value" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="提交人" prop="submitter">
-            <el-input v-model="queryParams.submitter" placeholder="请输入提交人" clearable
-              @keyup.enter.native="handleQuery" />
-          </el-form-item>
-          <el-form-item>
-            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-            <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-          </el-form-item>
-        </el-form>
-
-        <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
-              v-hasPermi="['ems:inspection-report:add']">新增
-            </el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate"
-              v-hasPermi="['ems:inspection-report:edit']">修改
-            </el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete"
-              v-hasPermi="['ems:inspection-report:remove']">删除
-            </el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
-              v-hasPermi="['ems:inspection-report:export']">导出
-            </el-button>
-          </el-col>
-          <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-        </el-row>
-
-        <el-table v-loading="loading" :data="inspectionReportList" @selection-change="handleSelectionChange">
-          <el-table-column type="selection" width="55" align="center" />
-          <el-table-column label="任务代码" align="left" prop="taskCode" />
-          <el-table-column label="结果状态" align="center" prop="resultStatus">
-            <template slot-scope="scope">
-              <dict-tag :options="dict.type.inspection_result" :value="scope.row.resultStatus" />
-            </template>
-          </el-table-column>
-          <!-- <el-table-column label="结果描述" align="center" prop="resultMsg" /> -->
-          <el-table-column label="完成时间" align="center" prop="finishTime" width="180">
-            <template slot-scope="scope">
-              <span>{{ parseTime(scope.row.finishTime, '{y}-{m}-{d}') }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="提交时间" align="center" prop="subTime" width="180">
-            <template slot-scope="scope">
-              <span>{{ parseTime(scope.row.subTime, '{y}-{m}-{d}') }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="提交人" align="center" prop="submitter" />
-          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template slot-scope="scope">
-              <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
-                v-hasPermi="['ems:inspection-report:edit']">修改
-              </el-button>
-
-              <el-button size="mini" type="text" icon="el-icon-delete" class="deleteBtn"
-                @click="handleDelete(scope.row)" v-hasPermi="['ems:inspection-report:remove']">
-                删除</el-button>
-            </template>
-          </el-table-column>
-        </el-table>
-
-        <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
-          @pagination="getList" />
-
-    </el-row>
-
-    <!-- 添加或修改巡检报告对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="任务代码" prop="taskCode">
-          <el-input v-model="form.taskCode" placeholder="请输入任务代码" />
-        </el-form-item>
-        <el-form-item label="结果状态" prop="resultStatus">
-          <el-radio-group v-model="form.resultStatus">
-            <el-radio v-for="dict in dict.type.inspection_result" :key="dict.value" :label="parseInt(dict.value)">{{
-              dict.label }}
-            </el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="结果描述">
-          <editor v-model="form.resultMsg" :min-height="192" />
-        </el-form-item>
-        <el-form-item label="完成时间" prop="finishTime">
-          <el-date-picker clearable v-model="form.finishTime" type="date" value-format="yyyy-MM-dd"
-            placeholder="请选择完成时间">
-          </el-date-picker>
-        </el-form-item>
-        <el-form-item label="提交时间" prop="subTime">
-          <el-date-picker clearable v-model="form.subTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择提交时间">
-          </el-date-picker>
-        </el-form-item>
-        <el-form-item label="提交人" prop="submitter">
-          <el-input v-model="form.submitter" placeholder="请输入提交人" />
-        </el-form-item>
-      </el-form>
-      <div slot="footer" class="dialog-footer">
-        <el-button type="primary" @click="submitForm">确 定</el-button>
-        <el-button @click="cancel">取 消</el-button>
-      </div>
-    </el-dialog>
-  </div>
-</template>
-
-<script>
-
-import {addInspectionReport, delInspectionReport, getInspectionReport, listInspectionReport, updateInspectionReport, } from '@/api/task/inspectionReport';
-import {Base64} from 'js-base64';
-import {areaTreeSelect} from '@/api/basecfg/area'
-
-export default {
-  name: 'InspectionReport',
-  dicts: ['inspection_result'],
-  data () {
-    return {
-      // 遮罩层
-      loading: true,
-      // 选中数组
-      ids: [],
-      // 非单个禁用
-      single: true,
-      // 非多个禁用
-      multiple: true,
-      // 显示搜索条件
-      showSearch: true,
-      // 总条数
-      total: 0,
-      // 巡检报告表格数据
-      inspectionReportList: [],
-      // 弹出层标题
-      title: '',
-      // 是否显示弹出层
-      open: false,
-      // 查询参数
-      queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        taskCode: null,
-        resultStatus: null,
-        submitter: null,
-        areaCode:null,
-      },
-      // 表单参数
-      form: {},
-      // 表单校验
-      rules: {
-        taskCode: [
-          {
-            required: true,
-            message: '任务代码不能为空',
-            trigger: 'blur',
-          },
-        ],
-        resultStatus: [
-          {
-            required: true,
-            message: '结果状态不能为空',
-            trigger: 'change',
-          },
-        ],
-      },
-      areaName: undefined,
-      areaOptions: [],
-    };
-  },
-  watch: {
-     // 根据名称筛选区域树
-     areaName (val) {
-      this.$refs.tree.filter(val)
-    }
-  },
- async created () {
-    await this.getAreaTreeSelect('0', 1)
-    this.getList();
-  },
-  methods: {
-    /** 查询巡检报告列表 */
-    getList () {
-      this.loading = true;
-      listInspectionReport(this.queryParams).then(response => {
-        this.inspectionReportList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      });
-    },
-    /** 查询区域树结构 */
-    async getAreaTreeSelect(areaCode, layer) {
-      await areaTreeSelect(areaCode, layer).then(response => {
-        this.areaOptions = [{
-          id: '-1',
-          label: '全部',
-          children: []
-        }].concat(response.data)
-        this.queryParams.areaCode = '-1'
-      })
-    },
-    // 筛选节点
-    filterNode (value, data) {
-      if (!value) return true
-      return data.label.indexOf(value) !== -1
-    },
-    handleNodeClick (data, node) {
-      this.queryParams.areaCode = data.id
-      this.getList()
-    },
-    // 取消按钮
-    cancel () {
-      this.open = false;
-      this.reset();
-    },
-    // 表单重置
-    reset () {
-      this.form = {
-        id: null,
-        taskCode: null,
-        resultStatus: null,
-        resultMsg: null,
-        finishTime: null,
-        subTime: null,
-        submitter: null,
-      };
-      this.resetForm('form');
-    },
-    /** 搜索按钮操作 */
-    handleQuery () {
-      this.queryParams.pageNum = 1;
-      this.getList();
-    },
-    /** 重置按钮操作 */
-    resetQuery () {
-      this.resetForm('queryForm');
-      this.handleQuery();
-    },
-    // 多选框选中数据
-    handleSelectionChange (selection) {
-      this.ids = selection.map(item => item.id);
-      this.single = selection.length !== 1;
-      this.multiple = !selection.length;
-    },
-    /** 新增按钮操作 */
-    handleAdd () {
-      this.reset();
-      this.open = true;
-      this.title = '添加巡检报告';
-    },
-    /** 修改按钮操作 */
-    handleUpdate (row) {
-      this.reset();
-      const id = row.id || this.ids;
-      getInspectionReport(id).then(response => {
-        this.form = response.data;
-        this.open = true;
-        this.title = '修改巡检报告';
-      });
-    },
-    /** 提交按钮 */
-    submitForm () {
-      this.$refs['form'].validate(valid => {
-        if (valid) {
-          if (this.form.resultMsg) {
-            this.form.resultMsg = Base64.encode(this.form.resultMsg);
-          }
-          if (this.form.id != null) {
-            updateInspectionReport(this.form).then(response => {
-              this.$modal.msgSuccess('修改成功');
-              this.open = false;
-              this.getList();
-            });
-          } else {
-            addInspectionReport(this.form).then(response => {
-              this.$modal.msgSuccess('新增成功');
-              this.open = false;
-              this.getList();
-            });
-          }
-        }
-      });
-    },
-    /** 删除按钮操作 */
-    handleDelete (row) {
-      const ids = row.id || this.ids;
-      this.$modal.confirm('是否确认删除巡检报告编号为"' + ids + '"的数据项?').then(function () {
-        return delInspectionReport(ids);
-      }).then(() => {
-        this.getList();
-        this.$modal.msgSuccess('删除成功');
-      }).catch(() => { });
-    },
-    /** 导出按钮操作 */
-    handleExport () {
-      this.download('ems/inspectionReport/export', {
-        ...this.queryParams,
-      }, `inspectionReport_${new Date().getTime()}.xlsx`);
-    },
-  },
-};
-</script>