admin 2 weken geleden
bovenliggende
commit
83596e88cf

+ 4 - 2
src/components/page/admin/inspect/InspectManage.vue

@@ -13,6 +13,7 @@
       <el-tab-pane label="巡检任务执行" name="task"><task-index ref="task" /></el-tab-pane>
       <el-tab-pane label="整改流程管理" name="rect"><rectification-index ref="rect" /></el-tab-pane>
       <el-tab-pane label="考核结果排名" name="assess"><assess-index ref="assess" /></el-tab-pane>
+      <el-tab-pane label="考核对比" name="compare"><assess-compare ref="compare" /></el-tab-pane>
     </el-tabs>
   </div>
 </template>
@@ -23,10 +24,11 @@ import PlanIndex from './plan/Index.vue'
 import TaskIndex from './task/Index.vue'
 import RectificationIndex from './rectification/Index.vue'
 import AssessIndex from './assess/Index.vue'
+import AssessCompare from './assess/compare.vue'
 
 export default {
   name: 'InspectManage',
-  components: { IndicatorIndex, PlanIndex, TaskIndex, RectificationIndex, AssessIndex },
+  components: { IndicatorIndex, PlanIndex, TaskIndex, RectificationIndex, AssessIndex, AssessCompare },
   data() {
     return { tab: 'indicator' }
   },
@@ -45,7 +47,7 @@ export default {
 </script>
 
 <style scoped>
-.inspect-page { padding: 20px; background: #f5f7fa; min-height: 100%; color: #303133; }
+.inspect-page { padding: 20px; background: #f0f2f5; min-height: 100%; color: #303133; }
 .head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; }
 .head h2 { margin: 0; font-size: 22px; }
 .head p { margin: 7px 0 0; color: #909399; }

+ 11 - 24
src/components/page/admin/inspect/assess/Detail.vue

@@ -128,31 +128,18 @@ export default {
     load() {
       if (!this.row) return
       this.loading = true
-      const start = this.row.periodStartDate
-        ? slashDate((this.row.periodStartDate || '').slice ? this.row.periodStartDate : fmt(this.row.periodStartDate))
-        : ''
-      const end = this.row.periodEndDate
-        ? slashDate((this.row.periodEndDate || '').slice ? this.row.periodEndDate : fmt(this.row.periodEndDate))
-        : ''
-      Promise.all([
-        post(this.$axios, '/inspectTaskManage/getTaskPage', cleanParams({ storeId: this.row.storeId, taskDateBegin: start, taskDateEnd: end, pageSize: 50, currentPage: 1 })),
-        post(this.$axios, '/rectificationManage/getRectPage', { storeId: this.row.storeId, pageSize: 50, currentPage: 1 }),
-        post(this.$axios, '/assessResultManage/getAssessTrend', { storeIds: [this.row.storeId], periodType: this.row.periodType || '' }),
-        post(this.$axios, '/storeManage/getStoreList', {})
-      ]).then(([r1, r2, r3, r4]) => {
-        this.stores = (r4 && r4.list) || []
-        this.tasks = (r1 && r1.list) || []
-        this.rects = (r2 && r2.list) || []
-        const rows = (r3 && r3.list) || []
-        this.trend = rows.map(row => {
-          const arr = Array.isArray(row) ? row : Object.keys(row).map(k => row[k])
-          return {
-            storeId: String(arr[0]),
-            periodCode: arr[1] === null || arr[1] === undefined ? '' : String(arr[1]),
-            totalScore: Number(arr[2] || 0)
-          }
-        })
+      // 单接口:getAssessDetail 聚合 周期内任务 + 整改记录 + 历史趋势;门店列表仅用于校区名
+      post(this.$axios, '/assessResultManage/getAssessDetail', { id: this.row.assessResultId }).then(res => {
+        const obj = (res && res.obj) || {}
+        this.tasks = obj.tasks || []
+        this.rects = obj.rects || []
+        this.trend = (obj.trend || []).map(r => ({
+          storeId: String(r.storeId),
+          periodCode: String(r.periodCode == null ? '' : r.periodCode),
+          totalScore: Number(r.totalScore || 0)
+        }))
       }).finally(() => { this.loading = false })
+      post(this.$axios, '/storeManage/getStoreList', {}).then(res => { this.stores = (res && res.list) || [] })
     },
     pct(v) {
       const n = Number(v || 0)

+ 1 - 1
src/components/page/admin/inspect/assess/Index.vue

@@ -448,7 +448,7 @@ export default {
 </script>
 
 <style scoped>
-.panel { background: #fff; border: 1px solid #ebeef5; border-radius: 4px; padding: 18px; }
+.panel { background: #fff; border: 1px solid #e4e7ed; border-radius: 6px; padding: 20px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
 .toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
 .toolbar .el-select, .toolbar .el-input { width: 160px; }
 .grow { flex: 1; }

+ 161 - 0
src/components/page/admin/inspect/assess/compare.vue

@@ -0,0 +1,161 @@
+<template>
+  <section class="panel">
+    <div class="toolbar">
+      <el-select v-model="periodType" placeholder="考核周期" clearable style="width: 130px;" @change="loadTrend">
+        <el-option label="周度" :value="1" />
+        <el-option label="月度" :value="2" />
+      </el-select>
+      <el-select v-model="trendStoreIds" multiple filterable placeholder="选择对比门店(可多选)" style="width: 320px;" @change="loadTrend">
+        <el-option v-for="s in stores" :key="s.storeId" :label="s.storeName" :value="s.storeId" />
+      </el-select>
+      <el-button type="primary" @click="loadTrend">对比</el-button>
+    </div>
+
+    <div class="trend-chart" v-if="trendSeries.length">
+      <div class="chart-title">门店考核得分趋势对比</div>
+      <svg :viewBox="'0 0 ' + chartW + ' ' + chartH" class="trend-svg">
+        <g v-for="g in gridLines" :key="g.y">
+          <line :x1="padL" :y1="g.y" :x2="chartW - padR" :y2="g.y" class="grid-line" />
+          <text :x="padL - 6" :y="g.y + 4" class="grid-text">{{ g.label }}</text>
+        </g>
+        <g v-for="cat in trendCats" :key="cat.value">
+          <text :x="cat.x" :y="chartH - padB + 14" class="grid-text cat-text" text-anchor="middle">{{ cat.label }}</text>
+        </g>
+        <polyline
+          v-for="s in trendSeries"
+          :key="s.storeId"
+          :points="s.pointsText"
+          fill="none"
+          :stroke="s.color"
+          stroke-width="2" />
+        <circle
+          v-for="p in allPoints"
+          :key="p.key"
+          :cx="p.x"
+          :cy="p.y"
+          r="3"
+          :fill="p.color" />
+      </svg>
+      <div class="trend-legend">
+        <span v-for="s in trendSeries" :key="s.storeId" class="legend-item">
+          <i :style="{ background: s.color }"></i>{{ s.storeName }}
+        </span>
+      </div>
+    </div>
+    <div class="empty" v-else-if="!loading">
+      <span v-if="!trendStoreIds.length">请选择至少一家门店</span>
+      <span v-else>暂无趋势数据</span>
+    </div>
+  </section>
+</template>
+
+<script>
+import { post } from '../common'
+export default {
+  name: 'InspectAssessCompare',
+  data() {
+    return {
+      chartW: 760, chartH: 320, padL: 44, padR: 18, padT: 16, padB: 30,
+      periodType: '',
+      stores: [],
+      trendStoreIds: [],
+      trendRows: [],
+      loading: false
+    }
+  },
+  computed: {
+    uniqPeriodCodes() {
+      const set = {}
+      this.trendRows.forEach(r => { set[r.periodCode] = 1 })
+      return Object.keys(set).sort()
+    },
+    trendCats() {
+      const cats = this.uniqPeriodCodes
+      const W = this.chartW - this.padL - this.padR
+      return cats.map((c, i) => ({
+        label: c,
+        value: c,
+        x: cats.length === 1 ? this.padL + W / 2 : this.padL + (i / (cats.length - 1)) * W
+      }))
+    },
+    trendSeries() {
+      const cats = this.uniqPeriodCodes
+      const H = this.chartH - this.padT - this.padB
+      const W = this.chartW - this.padL - this.padR
+      const maxY = Math.max(100, ...this.trendRows.map(r => Number(r.totalScore) || 0))
+      const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#9B59B6', '#00BCD4', '#FF9800', '#8BC34A']
+      return this.trendStoreIds.map((sid, si) => {
+        const points = cats.map((c, i) => {
+          const row = this.trendRows.find(r => String(r.storeId) === String(sid) && String(r.periodCode) === String(c))
+          const val = row ? (Number(row.totalScore) || 0) : 0
+          const x = cats.length === 1 ? this.padL + W / 2 : this.padL + (i / (cats.length - 1)) * W
+          const y = this.padT + H - (val / maxY) * H
+          return { x, y, val }
+        })
+        return {
+          storeId: sid,
+          storeName: (this.stores.find(o => String(o.storeId) === String(sid)) || {}).storeName || sid,
+          color: colors[si % colors.length],
+          points,
+          pointsText: points.map(p => p.x.toFixed(1) + ',' + p.y.toFixed(1)).join(' ')
+        }
+      })
+    },
+    allPoints() {
+      const list = []
+      this.trendSeries.forEach(s => {
+        s.points.forEach(p => {
+          list.push({ key: s.storeId + '-' + p.x, x: p.x, y: p.y, color: s.color })
+        })
+      })
+      return list
+    },
+    gridLines() {
+      const lines = []
+      for (let i = 0; i <= 4; i++) {
+        const y = this.padT + (this.chartH - this.padT - this.padB) * (i / 4)
+        lines.push({ y, label: 100 - i * 25 })
+      }
+      return lines
+    }
+  },
+  created() {
+    this.loadStores()
+  },
+  methods: {
+    loadStores() {
+      post(this.$axios, '/storeManage/getStoreList', {}).then(res => {
+        this.stores = (res && res.list) || []
+      })
+    },
+    loadTrend() {
+      if (!this.trendStoreIds.length) {
+        this.trendRows = []
+        return
+      }
+      this.loading = true
+      post(this.$axios, '/assessResultManage/getAssessTrend', { storeIds: this.trendStoreIds, periodType: this.periodType || '' }).then(res => {
+        const list = (res && res.list) || []
+        this.trendRows = list.map(row => {
+          const it = Array.isArray(row) ? { storeId: row[0], periodCode: row[2], totalScore: row[3] } : row
+          return { storeId: String(it.storeId), periodCode: String(it.periodCode == null ? '' : it.periodCode), totalScore: Number(it.totalScore || 0) }
+        })
+      }).finally(() => { this.loading = false })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.panel { background: #fff; border: 1px solid #e4e7ed; border-radius: 6px; padding: 20px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
+.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
+.chart-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 10px; }
+.trend-chart { border: 1px solid #ebeef5; border-radius: 6px; padding: 14px; }
+.trend-svg { width: 100%; height: auto; display: block; }
+.grid-line { stroke: rgba(0,0,0,0.08); stroke-width: 1; }
+.grid-text { fill: #909399; font-size: 11px; }
+.trend-legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 10px; font-size: 12px; color: #606266; }
+.legend-item { display: inline-flex; align-items: center; gap: 5px; }
+.legend-item i { display: inline-block; width: 10px; height: 10px; border-radius: 50%; }
+.empty { padding: 60px 0; text-align: center; color: #909399; }
+</style>

+ 1 - 1
src/components/page/admin/inspect/indicator/Index.vue

@@ -30,4 +30,4 @@ export default {
   }
 }
 </script>
-<style scoped>.panel{background:#fff;border:1px solid #ebeef5;border-radius:4px;padding:18px}.toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:16px}.toolbar .el-input,.toolbar .el-select{width:170px}.grow{flex:1}.danger{color:#f56c6c}.weight-bar{margin-top:16px;padding:10px 16px;border-radius:4px;font-size:13px;display:flex;align-items:center;gap:6px}.weight-bar.ok{background:#f6ffed;border:1px solid #b7eb8f;color:#67c23a}.weight-bar.warn{background:#fff7e6;border:1px solid #ffd591;color:#e6a23c}.weight-bar b{font-size:16px}</style>
+<style scoped>.panel{background:#fff;border:1px solid #e4e7ed;border-radius:6px;padding:20px;box-shadow:0 1px 2px rgba(0,0,0,0.04)}.toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:16px}.toolbar .el-input,.toolbar .el-select{width:170px}.grow{flex:1}.danger{color:#f56c6c}.weight-bar{margin-top:16px;padding:10px 16px;border-radius:4px;font-size:13px;display:flex;align-items:center;gap:6px}.weight-bar.ok{background:#f6ffed;border:1px solid #b7eb8f;color:#67c23a}.weight-bar.warn{background:#fff7e6;border:1px solid #ffd591;color:#e6a23c}.weight-bar b{font-size:16px}</style>

+ 1 - 1
src/components/page/admin/inspect/plan/Index.vue

@@ -205,7 +205,7 @@ export default {
 </script>
 
 <style scoped>
-.panel { background: #fff; border: 1px solid #ebeef5; border-radius: 4px; padding: 18px; }
+.panel { background: #fff; border: 1px solid #e4e7ed; border-radius: 6px; padding: 20px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
 .toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
 .toolbar .el-input, .toolbar .el-select { width: 160px; }
 .grow { flex: 1; }

+ 5 - 8
src/components/page/admin/inspect/rectification/Detail.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-dialog title="整改详情" :visible.sync="dialogVisible" width="760px" @open="load" :close-on-click-modal="false">
+  <el-dialog title="整改详情" :visible.sync="dialogVisible" width="760px" @open="load" :close-on-click-modal="false" append-to-body>
     <div class="detail-wrap" v-loading="loading">
       <div class="head" v-if="rounds.length">
         <div class="head-main">{{ rounds[0].storeName }} · {{ rounds[0].indicatorName }}</div>
@@ -103,13 +103,10 @@ export default {
     load() {
       if (!this.row) return
       this.loading = true
-      // 同任务同指标的多轮整改:通过 taskItemId 查询全部轮次
-      const q = this.row.taskItemId
-        ? { taskItemId: this.row.taskItemId, pageSize: 50, currentPage: 1 }
-        : { taskId: this.row.taskId, indicatorId: this.row.indicatorId, pageSize: 50, currentPage: 1 }
-      post(this.$axios, '/rectificationManage/getRectPage', q).then(res => {
-        const list = (res && res.list) || []
-        this.rounds = list.length ? list.sort((a, b) => (a.rectRound || 1) - (b.rectRound || 1)) : [this.row]
+      // 单接口:getRectDetail 返回该整改所属完整轮次链(时间轴)
+      post(this.$axios, '/rectificationManage/getRectDetail', { id: this.row.rectId }).then(res => {
+        const rounds = (res && res.obj && res.obj.rounds) || []
+        this.rounds = rounds.length ? rounds : [this.row]
       }).finally(() => { this.loading = false })
     },
     pictures(str) { return splitPictures(str) },

+ 31 - 1
src/components/page/admin/inspect/rectification/Index.vue

@@ -33,6 +33,25 @@
       </div>
     </div>
 
+    <!-- 按门店整改统计(I04-A06) -->
+    <div class="store-stat" v-if="storeStats.length">
+      <h4>按门店整改统计(整改次数 / 超期数 / 平均整改时长·天)</h4>
+      <el-table :data="storeStats" border size="mini">
+        <el-table-column prop="storeName" label="门店" min-width="140" />
+        <el-table-column label="整改次数" width="100" align="center">
+          <template slot-scope="s">{{ Number(s.row.rectCount || 0) }}</template>
+        </el-table-column>
+        <el-table-column label="超期数" width="90" align="center">
+          <template slot-scope="s">
+            <span :class="{ 'danger': Number(s.row.overtimeCount) > 0 }">{{ Number(s.row.overtimeCount || 0) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="平均整改时长(天)" width="150" align="center">
+          <template slot-scope="s">{{ Number(s.row.avgDays || 0) }}</template>
+        </el-table-column>
+      </el-table>
+    </div>
+
     <el-table :data="rows" border stripe v-loading="loading" @selection-change="onSelect">
       <el-table-column type="selection" width="45" />
       <el-table-column prop="rectId" label="ID" width="75" />
@@ -119,6 +138,7 @@ export default {
       total: 0,
       loading: false,
       stats: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0 },
+      storeStats: [],
       selected: [],
       stores: [],
       indicators: [],
@@ -147,11 +167,18 @@ export default {
     this.load()
     this.loadOptions()
     this.loadStats()
+    this.loadStoreStats()
   },
   methods: {
     loadAll() {
       this.load()
       this.loadStats()
+      this.loadStoreStats()
+    },
+    loadStoreStats() {
+      post(this.$axios, '/rectificationManage/getRectStatistics', {}).then(res => {
+        this.storeStats = (res && res.list) || []
+      })
     },
     loadStats() {
       post(this.$axios, '/rectificationManage/getRectPage', { pageSize: 500, currentPage: 1 }).then(res => {
@@ -230,7 +257,7 @@ export default {
 </script>
 
 <style scoped>
-.panel { background: #fff; border: 1px solid #ebeef5; border-radius: 4px; padding: 18px; }
+.panel { background: #fff; border: 1px solid #e4e7ed; border-radius: 6px; padding: 20px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
 .toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
 .toolbar .el-select, .toolbar .el-input { width: 160px; }
 .grow { flex: 1; }
@@ -241,4 +268,7 @@ export default {
 .stat-card span { color: #909399; font-size: 13px; }
 .stat-card b { font-size: 24px; color: #303133; }
 .stat-card em { color: #909399; font-style: normal; font-size: 12px; }
+.danger { color: #f56c6c; }
+.store-stat { margin-bottom: 16px; }
+.store-stat h4 { margin: 0 0 10px; font-size: 13px; color: #606266; }
 </style>

+ 4 - 3
src/components/page/admin/inspect/task/Index.vue

@@ -7,6 +7,7 @@
       <el-select v-model="query.storeId" placeholder="所属门店" clearable filterable style="width: 180px;">
         <el-option v-for="s in stores" :key="s.storeId" :label="s.storeName" :value="s.storeId" />
       </el-select>
+      <el-input v-model="query.inspectorId" placeholder="巡检员ID(我的任务)" clearable style="width:150px;" @keyup.enter.native="load()" />
       <el-select v-model="query.status" placeholder="任务状态" clearable>
         <el-option label="待执行" :value="1" />
         <el-option label="执行中" :value="2" />
@@ -100,7 +101,7 @@ export default {
   components: { TaskExecute, TaskReport },
   data() {
     return {
-      query: { planId: '', storeId: '', status: '', passFlag: '', taskDateBegin: '', taskDateEnd: '', pageSize: 10, currentPage: 1 },
+      query: { planId: '', storeId: '', inspectorId: '', status: '', passFlag: '', taskDateBegin: '', taskDateEnd: '', pageSize: 10, currentPage: 1 },
       dateRange: [],
       rows: [],
       total: 0,
@@ -169,7 +170,7 @@ export default {
       })
     },
     reset() {
-      this.query = { planId: '', storeId: '', status: '', passFlag: '', taskDateBegin: '', taskDateEnd: '', pageSize: 10, currentPage: 1 }
+      this.query = { planId: '', storeId: '', inspectorId: '', status: '', passFlag: '', taskDateBegin: '', taskDateEnd: '', pageSize: 10, currentPage: 1 }
       this.dateRange = []
       this.load()
     },
@@ -214,7 +215,7 @@ export default {
 </script>
 
 <style scoped>
-.panel { background: #fff; border: 1px solid #ebeef5; border-radius: 4px; padding: 18px; }
+.panel { background: #fff; border: 1px solid #e4e7ed; border-radius: 6px; padding: 20px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
 .toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
 .toolbar .el-select, .toolbar .el-input { width: 160px; }
 .grow { flex: 1; }

+ 134 - 104
src/components/page/admin/inspect/task/Report.vue

@@ -1,118 +1,140 @@
 <template>
-  <el-dialog title="巡检报告" :visible.sync="dialogVisible" width="920px" @open="load">
-    <div class="report-wrap" v-loading="loading">
-      <!-- 报告头 -->
-      <div class="report-head" v-if="task">
-        <div class="rh-left">
-          <div class="rh-title">{{ task.storeName }}</div>
-          <div class="rh-meta">巡检日期:{{ fmt(task.taskDate) }} | 巡检员:{{ task.inspectorName || '-' }} | 计划:{{ task.planName }}</div>
-        </div>
-        <div class="rh-score">
-          <span class="rh-num" :class="{ fail: task.passFlag !== 1 }">{{ fmtNum(task.totalScore) }}</span>
-          <span class="rh-unit">加权总分 / {{ fmtNum(task.maxScore) }}</span>
-        </div>
-        <el-tag size="small" :type="task.passFlag === 1 ? 'success' : 'danger'" class="rh-tag">{{ task.passFlag === 1 ? '✓ 及格' : '✗ 不及格' }}</el-tag>
-      </div>
-
-      <div class="report-grid">
-        <!-- 雷达图 -->
-        <div class="chart-card">
-          <h4>指标得分雷达图</h4>
-          <canvas ref="radar" width="360" height="300" class="radar-canvas"></canvas>
-          <div class="legend-line">
-            <span class="legend-dot ok"></span>及格项
-            <span class="legend-dot fail"></span>不及格项
+  <div>
+    <el-dialog title="巡检报告" :visible.sync="dialogVisible" width="920px" @open="load">
+      <div class="report-wrap" v-loading="loading">
+        <div class="report-head" v-if="task">
+          <div class="rh-left">
+            <div class="rh-title">{{ task.storeName }}</div>
+            <div class="rh-meta">巡检日期:{{ fmt(task.taskDate) }} | 巡检员:{{ task.inspectorName || '-' }} | 计划:{{ task.planName }}</div>
+          </div>
+          <div class="rh-score">
+            <span class="rh-num" :class="{ fail: task.passFlag !== 1 }">{{ fmtNum(task.totalScore) }}</span>
+            <span class="rh-unit">加权总分 / {{ fmtNum(task.maxScore) }}</span>
           </div>
+          <el-tag size="small" :type="task.passFlag === 1 ? 'success' : 'danger'" class="rh-tag">{{ task.passFlag === 1 ? '✓ 及格' : '✗ 不及格' }}</el-tag>
         </div>
 
-        <!-- 各指标得分明细 -->
-        <div class="chart-card">
-          <h4>各指标得分明细</h4>
-          <div v-for="item in items" :key="item.taskItemId" class="ind-card" :class="{ 'ind-fail': item.passFlag !== 1 }">
-            <div class="ind-head">
-              <span class="ind-name">{{ item.indicatorName }}</span>
-              <el-tag size="mini" :type="item.indicatorType === 1 ? 'primary' : 'warning'">{{ item.indicatorType === 1 ? '自动' : '人工' }}</el-tag>
-              <span class="ind-raw" v-if="item.rawValueText">原始值:{{ item.rawValueText }}</span>
+        <div class="report-grid">
+          <div class="chart-card">
+            <h4>指标得分雷达图</h4>
+            <canvas ref="radar" width="360" height="300" class="radar-canvas"></canvas>
+            <div class="legend-line">
+              <span class="legend-dot ok"></span>及格项
+              <span class="legend-dot fail"></span>不及格项
             </div>
-            <div class="ind-body">
-              <div class="score-bar">
-                <div class="score-track">
-                  <div class="score-fill" :class="item.passFlag === 1 ? 'fill-ok' : 'fill-fail'" :style="{ width: barWidth(item) + '%' }"></div>
+          </div>
+
+          <div class="chart-card">
+            <h4>各指标得分明细</h4>
+            <div v-for="item in items" :key="item.taskItemId" class="ind-card" :class="{ 'ind-fail': item.passFlag !== 1 }">
+              <div class="ind-head">
+                <span class="ind-name">{{ item.indicatorName }}</span>
+                <el-tag size="mini" :type="item.indicatorType === 1 ? 'primary' : 'warning'">{{ item.indicatorType === 1 ? '自动' : '人工' }}</el-tag>
+                <span class="ind-raw" v-if="item.rawValueText">原始值:{{ item.rawValueText }}</span>
+              </div>
+              <div class="ind-body">
+                <div class="score-bar">
+                  <div class="score-track">
+                    <div class="score-fill" :class="item.passFlag === 1 ? 'fill-ok' : 'fill-fail'" :style="{ width: barWidth(item) + '%' }"></div>
+                  </div>
+                  <span class="score-val">{{ fmtNum(item.score) }}/{{ fmtNum(item.maxScore) }}</span>
                 </div>
-                <span class="score-val">{{ fmtNum(item.score) }}/{{ fmtNum(item.maxScore) }}</span>
+                <el-tag size="mini" :type="item.passFlag === 1 ? 'success' : 'danger'">{{ item.passFlag === 1 ? '及格' : '不及格' }}</el-tag>
+                <span class="ind-photo" v-if="pictures(item).length">
+                  <el-image
+                    v-for="(p, i) in pictures(item)"
+                    :key="i"
+                    :src="p"
+                    fit="cover"
+                    :preview-src-list="pictures(item)"
+                    class="thumb-sm" />
+                </span>
               </div>
-              <el-tag size="mini" :type="item.passFlag === 1 ? 'success' : 'danger'">{{ item.passFlag === 1 ? '及格' : '不及格' }}</el-tag>
-              <span class="ind-photo" v-if="pictures(item).length">
+            </div>
+          </div>
+        </div>
+
+        <div class="detail-block">
+          <h4>逐项明细</h4>
+          <el-table :data="items" border size="mini">
+            <el-table-column prop="indicatorName" label="指标" min-width="110">
+              <template slot-scope="s"><span :class="{ 'row-fail': s.row.passFlag !== 1 }">{{ s.row.indicatorName }}</span></template>
+            </el-table-column>
+            <el-table-column label="类型" width="75">
+              <template slot-scope="s"><el-tag size="mini" :type="s.row.indicatorType === 1 ? 'primary' : 'warning'">{{ s.row.indicatorType === 1 ? '自动' : '人工' }}</el-tag></template>
+            </el-table-column>
+            <el-table-column prop="weight" label="权重" width="65" align="center">
+              <template slot-scope="s">{{ fmtNum(s.row.weight) }}%</template>
+            </el-table-column>
+            <el-table-column label="原始值" width="90">
+              <template slot-scope="s">{{ s.row.rawValueText || '-' }}</template>
+            </el-table-column>
+            <el-table-column label="得分" width="90" align="center">
+              <template slot-scope="s">{{ fmtNum(s.row.score) }}/{{ fmtNum(s.row.maxScore) }}</template>
+            </el-table-column>
+            <el-table-column label="加权分" width="70" align="center">
+              <template slot-scope="s">{{ fmtNum(s.row.weightedScore) }}</template>
+            </el-table-column>
+            <el-table-column label="结果" width="75">
+              <template slot-scope="s"><el-tag size="mini" :type="s.row.passFlag === 1 ? 'success' : 'danger'">{{ s.row.passFlag === 1 ? '及格' : '不及格' }}</el-tag></template>
+            </el-table-column>
+            <el-table-column label="照片" width="150">
+              <template slot-scope="s">
                 <el-image
-                  v-for="(p, i) in pictures(item)"
+                  v-for="(p, i) in pictures(s.row)"
                   :key="i"
                   :src="p"
                   fit="cover"
-                  :preview-src-list="pictures(item)"
-                  class="thumb-sm" />
-              </span>
-            </div>
-          </div>
+                  :preview-src-list="pictures(s.row)"
+                  class="thumb" />
+                <span v-if="!pictures(s.row).length" class="muted">-</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="rmk" label="备注" min-width="120">
+              <template slot-scope="s">{{ s.row.rmk || '-' }}</template>
+            </el-table-column>
+          </el-table>
         </div>
-      </div>
 
-      <!-- 逐项明细表 -->
-      <div class="detail-block">
-        <h4>逐项明细</h4>
-        <el-table :data="items" border size="mini">
-          <el-table-column prop="indicatorName" label="指标" min-width="110">
-            <template slot-scope="s"><span :class="{ 'row-fail': s.row.passFlag !== 1 }">{{ s.row.indicatorName }}</span></template>
-          </el-table-column>
-          <el-table-column label="类型" width="75">
-            <template slot-scope="s"><el-tag size="mini" :type="s.row.indicatorType === 1 ? 'primary' : 'warning'">{{ s.row.indicatorType === 1 ? '自动' : '人工' }}</el-tag></template>
-          </el-table-column>
-          <el-table-column prop="weight" label="权重" width="65" align="center">
-            <template slot-scope="s">{{ fmtNum(s.row.weight) }}%</template>
-          </el-table-column>
-          <el-table-column label="原始值" width="90">
-            <template slot-scope="s">{{ s.row.rawValueText || '-' }}</template>
-          </el-table-column>
-          <el-table-column label="得分" width="90" align="center">
-            <template slot-scope="s">{{ fmtNum(s.row.score) }}/{{ fmtNum(s.row.maxScore) }}</template>
-          </el-table-column>
-          <el-table-column label="加权分" width="70" align="center">
-            <template slot-scope="s">{{ fmtNum(s.row.weightedScore) }}</template>
-          </el-table-column>
-          <el-table-column label="结果" width="75">
-            <template slot-scope="s"><el-tag size="mini" :type="s.row.passFlag === 1 ? 'success' : 'danger'">{{ s.row.passFlag === 1 ? '及格' : '不及格' }}</el-tag></template>
-          </el-table-column>
-          <el-table-column label="照片" width="150">
-            <template slot-scope="s">
-              <el-image
-                v-for="(p, i) in pictures(s.row)"
-                :key="i"
-                :src="p"
-                fit="cover"
-                :preview-src-list="pictures(s.row)"
-                class="thumb" />
-              <span v-if="!pictures(s.row).length" class="muted">-</span>
-            </template>
-          </el-table-column>
-          <el-table-column prop="rmk" label="备注" min-width="120">
-            <template slot-scope="s">{{ s.row.rmk || '-' }}</template>
-          </el-table-column>
-        </el-table>
+        <div class="rect-block" v-if="task && task.passFlag !== 1">
+          <h4>整改记录</h4>
+          <el-table :data="rects" border size="mini" v-if="rects.length">
+            <el-table-column prop="indicatorName" label="指标" min-width="100" />
+            <el-table-column label="状态" width="110">
+              <template slot-scope="s">
+                <el-tag size="mini" :style="rectTagStyle(s.row.status)">{{ rectText(s.row.status) }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="轮次" width="60" align="center">
+              <template slot-scope="s">第 {{ s.row.rectRound || 1 }} 轮</template>
+            </el-table-column>
+            <el-table-column label="截止日期" width="120">
+              <template slot-scope="s">{{ fmt(s.row.deadlineDate) }}</template>
+            </el-table-column>
+            <el-table-column label="操作" width="80">
+              <template slot-scope="s">
+                <el-button type="text" size="mini" @click="showRectDetail(s.row)">查看</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+          <div v-else class="muted" style="padding:12px 0">加载中...</div>
+        </div>
       </div>
-
-      <el-alert v-if="task && task.passFlag !== 1" type="warning" :closable="false" class="rect-tip"
-        title="该任务存在不及格项,提交时已自动生成整改记录,请到「整改流程管理」中跟进。" />
-    </div>
-    <span slot="footer">
-      <el-button @click="dialogVisible = false">关闭</el-button>
-    </span>
-  </el-dialog>
+      <span slot="footer">
+        <el-button @click="dialogVisible = false">关闭</el-button>
+      </span>
+    </el-dialog>
+    <rect-detail :visible.sync="rectDetailVisible" :row="rectDetailRow" />
+  </div>
 </template>
 
 <script>
-import { post, fmt, fmtNum, splitPictures } from '../common'
+import { post, fmt, fmtNum, splitPictures, RECT_STATUS } from '../common'
+import RectDetail from '../rectification/Detail.vue'
 
 export default {
   name: 'InspectTaskReport',
+  components: { RectDetail },
   props: {
     visible: Boolean,
     taskId: [Number, String]
@@ -124,7 +146,7 @@ export default {
     }
   },
   data() {
-    return { loading: false, task: null, items: [] }
+    return { loading: false, task: null, items: [], rects: [], rectDetailVisible: false, rectDetailRow: null }
   },
   methods: {
     load() {
@@ -132,10 +154,12 @@ export default {
       this.loading = true
       Promise.all([
         post(this.$axios, '/inspectTaskManage/getTaskDetail', { id: this.taskId }),
-        post(this.$axios, '/inspectTaskManage/getTaskItems', { taskId: this.taskId })
-      ]).then(([r1, r2]) => {
+        post(this.$axios, '/inspectTaskManage/getTaskItems', { taskId: this.taskId }),
+        post(this.$axios, '/rectificationManage/getRectPage', { taskId: this.taskId, pageSize: 50, currentPage: 1 })
+      ]).then(([r1, r2, r3]) => {
         this.task = (r1 && r1.obj) || null
         this.items = (r2 && r2.list) || []
+        this.rects = (r3 && r3.list) || []
         this.$nextTick(() => {
           setTimeout(() => this.drawRadar(), 60)
         })
@@ -147,6 +171,15 @@ export default {
       return Math.max(0, Math.min(100, Math.round(Number(item.score || 0) / max * 100)))
     },
     pictures(row) { return splitPictures(row.pictureUrls) },
+    rectText(v) { return (RECT_STATUS[v] || {}).text || '-' },
+    rectTagStyle(v) {
+      const st = RECT_STATUS[v] || {}
+      return { background: st.color, borderColor: st.color, color: '#fff' }
+    },
+    showRectDetail(row) {
+      this.rectDetailRow = row
+      this.rectDetailVisible = true
+    },
     drawRadar() {
       const canvas = this.$refs.radar
       if (!canvas) return
@@ -165,7 +198,6 @@ export default {
         x: cx + R * ratio * Math.cos(angle(i)),
         y: cy + R * ratio * Math.sin(angle(i))
       })
-      // 网格环
       ctx.lineWidth = 1
       for (let g = 1; g <= 4; g++) {
         ctx.beginPath()
@@ -177,7 +209,6 @@ export default {
         ctx.strokeStyle = 'rgba(0,0,0,0.08)'
         ctx.stroke()
       }
-      // 轴线 + 标签
       ctx.font = '12px Microsoft YaHei, sans-serif'
       ctx.fillStyle = '#606266'
       ctx.textAlign = 'center'
@@ -192,7 +223,6 @@ export default {
         const lp = point(i, 1.18)
         ctx.fillText(items[i].indicatorName, lp.x, lp.y)
       }
-      // 数据多边形
       ctx.beginPath()
       items.forEach((item, i) => {
         const max = Number(item.maxScore) || 1
@@ -207,7 +237,6 @@ export default {
       ctx.strokeStyle = '#1890ff'
       ctx.lineWidth = 2
       ctx.stroke()
-      // 得分点
       items.forEach((item, i) => {
         const max = Number(item.maxScore) || 1
         const ratio = Math.max(0, Math.min(1, (Number(item.score) || 0) / max))
@@ -262,5 +291,6 @@ export default {
 .row-fail { color: #f56c6c; }
 .thumb { width: 40px; height: 40px; margin-right: 6px; border-radius: 4px; }
 .muted { color: #909399; }
-.rect-tip { margin-top: 10px; }
-</style>
+.rect-block { margin-top: 16px; }
+.rect-block h4 { margin: 0 0 10px; font-size: 13px; color: #606266; }
+</style>