traffic-audit-web/src/views/AuditResult.vue
@@ -3,7 +3,7 @@
    <app-header title="交通统计报表审核系统 - 审核结果"></app-header>
    <el-container>
      <el-aside width="200px">
        <el-menu router default-active="/audit">
        <el-menu ref="sideMenu" router :default-active="$route.path" @select="onMenuSelect">
          <el-menu-item index="/dashboard">工作台</el-menu-item>
          <el-menu-item index="/import">数据导入</el-menu-item>
          <el-menu-item index="/data">数据查看</el-menu-item>
@@ -17,6 +17,9 @@
      <el-main :class="{ 'main-focus': focusMode }" style="display:flex;flex-direction:column;min-height:0">
        <h3 v-show="!focusMode" style="flex:none;margin:0 0 10px">审核结果</h3>
        <el-form :inline="true" style="flex:none">
          <el-form-item label="报表期">
            <el-date-picker v-model="period" type="month" placeholder="选择月份" value-format="yyyy-MM" @change="onPeriodChange"></el-date-picker>
          </el-form-item>
          <el-form-item label="报表类型">
            <el-radio-group v-model="reportType" @change="onTypeChange">
              <el-radio label="H2032">货运月报</el-radio>
@@ -30,9 +33,7 @@
            
            </el-radio-group>
          </el-form-item>
          <el-form-item label="报表期">
            <el-date-picker v-model="period" type="month" placeholder="选择月份" value-format="yyyy-MM" @change="onPeriodChange"></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="executeAudit" :loading="loading">执行审核</el-button>
            <el-button type="success" plain @click="markAudited" :loading="marking">标记已审核</el-button>
@@ -49,13 +50,29 @@
          <div class="sev-card sev-pending"><b>{{ severityStats.pending }}</b> 待处理</div>
          <div class="sev-card sev-done"><b>{{ severityStats.done }}</b> 已处理</div>
        </div>
        <el-form v-if="!focusMode" :inline="true" style="margin-top:8px;flex:none">
        <div v-if="!focusMode" class="audit-filter-bar">
        <el-form :inline="true">
          <el-form-item label="筛选"></el-form-item>
          <el-form-item label="企业名称">
            <el-input v-model="enterpriseFilter" placeholder="输入企业名称关键字" clearable style="width:200px" @keyup.enter.native="queryResults"></el-input>
          </el-form-item>
          <el-form-item label="审核规则">
            <el-select v-model="ruleFilter" placeholder="全部规则" clearable style="width:180px">
              <el-option v-for="rn in ruleOptions" :key="rn" :label="rn" :value="rn"></el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="问题范围">
            <el-radio-group v-model="issueScope" size="small" @change="updateBatchSelection">
              <el-radio-button label="verify">仅需核实</el-radio-button>
              <el-radio-button label="all">含提醒型</el-radio-button>
            </el-radio-group>
            <span class="hint">提醒型仅提示变化、不需处理,导出默认不含</span>
          </el-form-item>
          <el-form-item label="状态">
            <el-select v-model="statusScope" style="width:110px" @change="updateBatchSelection">
              <el-option label="全部" value="all"></el-option>
              <el-option label="待处理" value="pending"></el-option>
              <el-option label="已处理" value="handled"></el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="视图">
@@ -73,9 +90,11 @@
            <el-button size="small" plain @click="statsCollapsed = !statsCollapsed">{{ statsCollapsed ? '展开' : '收起' }}规则统计</el-button>
          </el-form-item>
          <el-form-item>
            <span style="color:#909399;font-size:12px">共 {{ results.length }} 条问题,{{ mergedResults.length }} 家企业</span>
            <span style="color:#303133;font-size:13px;font-weight:600">当前筛选 {{ filteredResults.length }} 条 / {{ mergedResults.length }} 家企业</span>
            <span style="color:#909399;font-size:12px;margin-left:8px">(共 {{ results.length }} 条问题)</span>
          </el-form-item>
        </el-form>
        </div>
        <div v-if="ruleStats.length && !statsCollapsed && !focusMode" class="rule-stats" style="flex:none">
          <div class="stats-cards">
            <div v-for="rs in sortedRuleStats" :key="rs.ruleId" class="stat-card"
@@ -97,65 +116,52 @@
          共 {{ results.length }} 条问题,{{ mergedResults.length }} 家企业
        </div>
        <div class="table-area" style="flex:1;min-height:0">
        <!-- 明细视图:一条问题一张卡片 -->
        <!-- 明细视图:一条问题一行(与导出模板 Sheet“审核结果”同款) -->
        <div v-if="viewMode === 'detail'" class="detail-cards-wrap" v-loading="loading">
          <div class="detail-toolbar">
            <el-checkbox :value="allSelected" :indeterminate="someSelected" @change="toggleSelectAll">全选当前 {{ filteredResults.length }} 条</el-checkbox>
            <span class="toolbar-tip">勾选后可批量确认 / 忽略</span>
          </div>
          <div v-for="row in filteredResults" :key="row.id" class="detail-card"
               :class="{ 'card-red': row.alertLevel === 'RED', 'card-yellow': row.alertLevel === 'YELLOW', 'card-done': row.status !== 'PENDING' }">
            <div class="card-head">
              <el-checkbox v-model="row._selected" @change="updateBatchSelection"></el-checkbox>
              <div class="card-ent">
                <div class="ent-name">{{ row.enterpriseName || row.city || '未知企业' }}</div>
                <div class="ent-sub">{{ row.reportPeriod }}</div>
              </div>
              <el-tag :type="row.alertLevel === 'RED' ? 'danger' : 'warning'" size="small" effect="dark">{{ row.alertLevel }}</el-tag>
              <el-tag :type="statusType(row.status)" size="small" effect="plain" class="status-tag">
                <i :class="statusIcon(row.status)"></i> {{ statusText(row.status) }}
              </el-tag>
              <span class="spacer"></span>
              <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="goData(row)">数据</el-link>
              <el-tag v-if="explainType(reportType)" size="mini" :type="row.verifyExplanation ? 'success' : 'warning'"
                       style="cursor:pointer" :title="expandedExplain === row.reportId ? '收起解释' : '展开填写/查看解释'"
                       @click="toggleExplain(row.reportId)">
                {{ row.verifyExplanation ? '解释:已填写' : '解释:未填写' }}
              </el-tag>
              <el-button v-if="row.status === 'PENDING'" size="mini" type="success" icon="el-icon-check" round @click="review(row, 'CONFIRMED')">确认</el-button>
              <el-button v-if="row.status === 'PENDING'" size="mini" type="info" icon="el-icon-close" round plain @click="review(row, 'IGNORED')">忽略</el-button>
              <span v-else class="done-text">已处理</span>
          <el-table :data="filteredResults" border stripe height="100%"
            :header-cell-style="{ background: '#F5F7FA', color: '#303133', fontWeight: 600 }">
            <el-table-column width="40">
              <template slot-scope="{ row }">
                <el-checkbox v-model="row._selected" @change="updateBatchSelection"></el-checkbox>
              </template>
            </el-table-column>
            <el-table-column prop="reportPeriod" label="报表期" width="90"></el-table-column>
            <el-table-column label="企业/市州" min-width="200" show-overflow-tooltip>
              <template slot-scope="{ row }">{{ row.enterpriseName || row.city || '未知企业' }}</template>
            </el-table-column>
            <el-table-column prop="ruleCode" label="规则编码" width="100"></el-table-column>
            <el-table-column prop="ruleName" label="审核规则" min-width="150" show-overflow-tooltip></el-table-column>
            <el-table-column label="等级" width="76">
              <template slot-scope="{ row }">
                <el-tag :type="row.alertLevel === 'RED' ? 'danger' : 'warning'" size="mini">{{ row.alertLevel }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="actualValue" label="实际值" min-width="230" show-overflow-tooltip></el-table-column>
            <el-table-column prop="thresholdValue" label="阈值/基准" min-width="140" show-overflow-tooltip></el-table-column>
            <el-table-column prop="deviation" label="偏差说明" min-width="170" show-overflow-tooltip></el-table-column>
            <el-table-column label="状态" width="92">
              <template slot-scope="{ row }">
                <el-tag :type="statusType(row.status)" size="mini">
                  <i :class="statusIcon(row.status)"></i> {{ statusText(row.status) }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="185" align="right">
              <template slot-scope="{ row }">
                <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="openMonthReport(row)">数据</el-link>
                <el-button v-if="row.status === 'PENDING'" size="mini" type="success" icon="el-icon-check" round @click="review(row, 'CONFIRMED')">确认</el-button>
                <el-button v-if="row.status === 'PENDING'" size="mini" type="info" icon="el-icon-close" round plain @click="review(row, 'IGNORED')">忽略</el-button>
                <span v-else class="done-text">已处理</span>
              </template>
            </el-table-column>
            <div slot="empty">
              <el-empty description="当前筛选条件下没有问题数据"></el-empty>
            </div>
            <div class="card-body">
              <div class="rule-title">
                <b>{{ row.ruleName }}</b>
                <span class="rule-code">{{ row.ruleCode }}</span>
              </div>
              <div class="card-details">
                <div class="detail-line"><span class="dt-label">实际</span><span class="dt-val">{{ row.actualValue }}</span></div>
                <div class="detail-line"><span class="dt-label">基准</span><span class="dt-val">{{ row.thresholdValue }}</span></div>
                <div class="detail-line"><span class="dt-label">差异</span><span class="dt-val dt-warn">{{ row.deviation }}</span></div>
              </div>
            </div>
            <div v-if="explainType(reportType) && expandedExplain === row.reportId" class="explain-side" style="margin:0 12px 10px">
              <div class="explain-head">
                <span class="explain-title">企业解释</span>
                <el-tag size="mini" :type="row.verifyExplanation ? 'success' : 'warning'">{{ row.verifyExplanation ? '已填写' : '未填写' }}</el-tag>
                <span style="flex:1"></span>
                <el-button size="mini" text @click="expandedExplain = null">收起</el-button>
              </div>
              <span v-if="row.verifyExplanation" class="explain-cur">{{ row.verifyExplanation }}</span>
              <el-input v-model="explainDrafts[row.reportId]" type="textarea" :rows="2" resize="none"
                        placeholder="填写/修改该企业的解释说明…"></el-input>
              <div class="explain-actions">
                <el-button size="mini" type="primary" plain :loading="savingExplainId === row.reportId" @click="saveExplain(row)">保存解释</el-button>
                <el-button v-if="(explainDrafts[row.reportId] || '') !== (row.verifyExplanation || '')" size="mini" text @click="resetDraft(row.reportId)">重置</el-button>
              </div>
            </div>
          </div>
          <div v-if="!filteredResults.length" class="empty-tip">
            <el-empty description="当前筛选条件下没有问题数据"></el-empty>
          </div>
          </el-table>
        </div>
        <!-- 合并视图 -->
        <el-table v-else :data="mergedResults" border stripe v-loading="loading" height="100%"
@@ -175,7 +181,7 @@
                  <b>{{ item.ruleName }}</b>
                  <el-tag :type="item.alertLevel === 'RED' ? 'danger' : 'warning'" size="mini">{{ item.alertLevel }}</el-tag>
                  <el-tag :type="statusType(item.status)" size="mini">{{ statusText(item.status) }}</el-tag>
                  <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="goData(item)">数据</el-link>
                  <el-link v-if="dataType(reportType)" type="primary" :underline="false" size="mini" @click="openMonthReport(item)">数据</el-link>
                  <span style="flex:1"></span>
                  <el-button v-if="item.status === 'PENDING'" size="mini" type="success" @click="review(item, 'CONFIRMED')">确认</el-button>
                  <el-button v-if="item.status === 'PENDING'" size="mini" type="info" @click="review(item, 'IGNORED')">忽略</el-button>
@@ -188,32 +194,6 @@
              </div>
            </template>
          </el-table-column>
          <el-table-column label="企业解释" width="340">
            <template slot-scope="scope">
              <div v-if="explainType(reportType) && scope.row.items[0]" class="explain-cell">
                <div v-if="expandedExplain !== scope.row.items[0].reportId" class="explain-compact">
                  <el-tag size="mini" :type="scope.row.items[0].verifyExplanation ? 'success' : 'warning'">{{ scope.row.items[0].verifyExplanation ? '已填写' : '未填写' }}</el-tag>
                  <div v-if="scope.row.items[0].verifyExplanation" class="explain-preview">{{ scope.row.items[0].verifyExplanation }}</div>
                  <el-button size="mini" text type="primary" @click="expandedExplain = scope.row.items[0].reportId">填写/修改</el-button>
                </div>
                <div v-else class="explain-side">
                  <div class="explain-head">
                    <span class="explain-title">企业解释</span>
                    <span style="flex:1"></span>
                    <el-button size="mini" text @click="expandedExplain = null">收起</el-button>
                  </div>
                  <span v-if="scope.row.items[0].verifyExplanation" class="explain-cur">{{ scope.row.items[0].verifyExplanation }}</span>
                  <el-input v-model="explainDrafts[scope.row.items[0].reportId]" type="textarea" :rows="2" resize="none"
                            placeholder="填写/修改该企业的解释说明…"></el-input>
                  <div class="explain-actions">
                    <el-button size="mini" type="primary" plain :loading="savingExplainId === scope.row.items[0].reportId" @click="saveExplain(scope.row.items[0])">保存解释</el-button>
                    <el-button v-if="(explainDrafts[scope.row.items[0].reportId] || '') !== (scope.row.items[0].verifyExplanation || '')" size="mini" text @click="resetDraft(scope.row.items[0].reportId)">重置</el-button>
                  </div>
                </div>
              </div>
              <span v-else style="color:#C0C4CC;font-size:12px">—</span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="130">
            <template slot-scope="scope">
              <el-button v-if="hasPending(scope.row)" size="mini" type="success" plain @click="reviewAll(scope.row, 'CONFIRMED')">全部确认</el-button>
@@ -224,6 +204,50 @@
        </div>
      </el-main>
    </el-container>
        <el-dialog title="数据查看(月报样式)" :visible.sync="monthVisible" width="880px" top="4vh" custom-class="mr-dialog">
          <div v-if="monthLoading" style="padding:40px 0;text-align:center;color:#909399">加载中…</div>
          <div v-else-if="!monthRecords.length" style="padding:30px 0;text-align:center;color:#909399">
            未查询到该企业、该报表期的填报数据(可能还未导入)
          </div>
          <div v-else class="mr-body">
            <div v-for="(rec, ri) in monthRecords" :key="ri" class="mr-card">
              <div class="mr-title">{{ monthTitle(rec) }}</div>
              <table class="mr-meta">
                <tbody>
                  <tr v-for="(m, mi) in monthMeta(rec)" :key="mi">
                    <td class="mr-meta-k">{{ m.label }}</td>
                    <td class="mr-meta-v">{{ fmtVal(m.value) }}</td>
                  </tr>
                </tbody>
              </table>
              <div v-for="sec in monthSections(rec)" :key="sec.title" class="mr-sec">
                <div class="mr-sec-title">{{ sec.title }}</div>
                <table class="mr-sec-tab" border="1">
                  <thead>
                    <tr>
                      <th style="width:34%">指标名称</th>
                      <th v-for="(h, hi) in sec.head" :key="hi" style="width:16.5%">{{ h }}</th>
                    </tr>
                  </thead>
                  <tbody>
                    <tr v-for="(rw, rwi) in sec.rows" :key="rwi">
                      <td>{{ rw.label }}</td>
                      <td v-for="(c, ci) in rw.cells" :key="ci">{{ fmtVal(c.v) }}</td>
                    </tr>
                  </tbody>
                </table>
              </div>
              <div v-if="rec.verifyExplanation || rec.reportNotes" class="mr-note">
                <div v-if="rec.verifyExplanation" class="mr-note-line"><b>核实解释:</b>{{ rec.verifyExplanation }}</div>
                <div v-if="rec.reportNotes" class="mr-note-line"><b>备注:</b>{{ rec.reportNotes }}</div>
              </div>
            </div>
          </div>
          <span slot="footer">
            <el-button size="small" @click="monthVisible = false">关闭</el-button>
          </span>
        </el-dialog>
  </el-container>
</template>
<script>
@@ -231,14 +255,113 @@
import api from '@/api'
const DATA_TYPE_MAP = { H2032: 'h2032', H2031: 'h2031', CITY_BUS: 'cityBus', CITY_TAXI: 'cityTaxi' }
const EXPLAIN_TYPES = ['H2032', 'H2031', 'CITY_BUS', 'CITY_TAXI']
const MONTH_CFG = {
  h2032: {
    meta: [['报表期', 'reportPeriod'], ['企业名称', 'enterpriseName'], ['统一社会信用代码', 'unifiedCreditCode'], ['填报单位', 'reportUnit']],
    sections: [
      { title: '一、运力情况', head: ['车辆数(辆)', '标记吨位数(吨)'], rows: [
        ['总计', ['vehicleTotal', 'tonsTotal']],
        ['其中:牵引车', ['vehicleTractor', null]],
        ['挂车', ['vehicleTrailer', 'tonsTrailer']],
        ['其中:集装箱车', ['vehicleContainer', 'tonsContainer']],
        ['整车', ['vehicleWhole', 'tonsWhole']]
      ]},
      { title: '二、运输生产情况', head: ['货运量(吨)', '货物周转量(吨公里)'], rows: [
        ['运输量', ['freightTotal', 'turnoverTotal']],
        ['其中:集装箱', ['freightContainer', null]],
        ['其中:煤炭及制品', ['freightCoal', null]],
        ['石油、天然气及制品', ['freightOilGas', null]],
        ['其中:原油', ['freightCrudeOil', null]],
        ['金属矿石', ['freightMetalOre', null]],
        ['其中:铁矿石', ['freightIronOre', null]],
        ['矿建性建筑材料', ['freightBuilding', null]],
        ['粮食', ['freightGrain', null]]
      ]},
      { title: '平均指标', head: [], rows: [
        ['货车平均吨位', ['avgTonnage', null]],
        ['货车平均运距', ['avgDistance', null]]
      ]},
      { title: '填报人信息', head: [], rows: [
        ['单位负责人', ['unitLeader', null]], ['统计负责人', ['statsLeader', null]],
        ['填表人', ['contactPerson', null]], ['联系电话', ['contactPhone', null]], ['报出日期', ['reportDate', null]]
      ]}
    ]
  },
  h2031: {
    meta: [['报表期', 'reportPeriod'], ['企业名称', 'enterpriseName']],
    sections: [
      { title: '一、运力情况', head: ['车辆数(辆)', '核定载客位(客位)'], rows: [
        ['总计', ['vehicleTotal', 'seatTotal']],
        ['客运班车(含定线旅游客运客车)', ['vehicleSchedule', 'seatSchedule']],
        ['客运包车(含非定线旅游客运客车)', ['vehicleCharter', 'seatCharter']]
      ]},
      { title: '二、运输生产情况', head: ['客运量(人次)', '旅客周转量(人公里)'], rows: [
        ['总计', ['passengerTotal', 'turnoverTotal']],
        ['一类客运班线(含定线旅游)', ['passengerClass1', 'turnoverClass1']],
        ['二类客运班线(含定线旅游)', ['passengerClass2', 'turnoverClass2']],
        ['三类客运班线(含定线旅游)', ['passengerClass3', 'turnoverClass3']],
        ['四类客运班线(含定线旅游)', ['passengerClass4', 'turnoverClass4']],
        ['客运包车(含非定线旅游)', ['passengerCharter', 'turnoverCharter']]
      ]},
      { title: '客运平均运距', head: [], rows: [
        ['客运平均运距', ['avgDistanceTotal', null]],
        ['一类客运班线平均运距', ['avgDistanceClass1', null]],
        ['二类客运班线平均运距', ['avgDistanceClass2', null]],
        ['三类客运班线平均运距', ['avgDistanceClass3', null]],
        ['四类客运班线平均运距', ['avgDistanceClass4', null]],
        ['客运包车平均运距', ['avgDistanceCharter', null]]
      ]}
    ]
  },
  cityBus: {
    meta: [['报表期', 'reportPeriod'], ['市州', 'city'], ['企业名称', 'enterpriseName']],
    sections: [
      { title: '一、公共汽电车运营', head: [], rows: [
        ['运营车数(辆)', ['opVehicles', null]], ['运营站点总个数(个)', ['stopTotal', null]],
        ['其中:城市运营站点', ['stopCity', null]], ['客运量(万人次)', ['passengerVolume', null]],
        ['其中:城市客运量', ['passengerCity', null]], ['旅客周转量(万人公里)', ['turnover', null]],
        ['其中:城市旅客周转量', ['turnoverCity', null]]
      ]},
      { title: '二、派生指标与增速', head: [], rows: [
        ['平均运距(公里)', ['avgDistance', null]], ['城市内平均运距', ['avgDistanceCity', null]],
        ['城际城乡客运量', ['passengerChengxiang', null]], ['城际城乡周转量', ['turnoverChengxiang', null]],
        ['城际城乡平均运距', ['avgDistanceChengxiang', null]], ['客运量环比', ['ratePassengerMom', null]],
        ['城市内客运量环比', ['ratePassengerCityMom', null]], ['城际城乡客运量环比', ['rateChengxiangMom', null]],
        ['客运量同比', ['ratePassengerYoy', null]], ['城市内客运量占比', ['passengerCityRatio', null]],
        ['城际城乡客运量占比', ['chengxiangRatio', null]], ['填表人', ['contactPerson', null]],
        ['报出日期', ['reportDate', null]]
      ]}
    ]
  },
  cityTaxi: {
    meta: [['报表期', 'reportPeriod'], ['市州', 'city'], ['企业名称', 'enterpriseName']],
    sections: [
      { title: '一、巡游出租运营服务', head: [], rows: [
        ['载客车次总数(万车次)', ['tripTotal', null]], ['其中:城市载客车次', ['tripCity', null]],
        ['客运量(万人次)', ['passengerVolume', null]], ['其中:城市客运量', ['passengerCity', null]],
        ['旅客周转量(万人公里)', ['turnover', null]], ['其中:城市旅客周转量', ['turnoverCity', null]],
        ['运营车辆数(辆)', ['opVehicles', null]]
      ]},
      { title: '二、派生指标与增速', head: [], rows: [
        ['平均运距(公里)', ['avgDistance', null]], ['城市内平均运距', ['avgDistanceCity', null]],
        ['单车次载客人数', ['passengersPerTrip', null]], ['载客车次环比', ['rateTripMom', null]],
        ['载客车次同比', ['rateTripYoy', null]], ['客运量环比', ['ratePassengerMom', null]],
        ['客运量同比', ['ratePassengerYoy', null]], ['城市内客运量占比', ['passengerCityRatio', null]],
        ['城际城乡平均运距', ['avgDistanceChengxiang', null]]
      ]}
    ]
  }
}
export default {
  components: { AppHeader },
  name: 'AuditResult',
  data() {
    return {
      period: '', reportType: 'H2032', loading: false, marking: false, results: [], ruleStats: [], statsCollapsed: false,
      enterpriseFilter: '', ruleFilter: '', viewMode: 'merged', batchSelection: [], focusMode: false,
      explainDrafts: {}, savingExplainId: null, expandedExplain: null
      enterpriseFilter: '', ruleFilter: '', viewMode: 'merged', issueScope: 'verify', statusScope: 'all', batchSelection: [], focusMode: false,
      explainDrafts: {}, savingExplainId: null, expandedExplain: null,
      monthVisible: false, monthLoading: false, monthType: '', monthPeriod: '', monthKeyword: '', monthRecords: [], monthColumns: []
    }
  },
  computed: {
@@ -249,7 +372,7 @@
    },
    severityStats() {
      const s = { red: 0, yellow: 0, pending: 0, done: 0 }
      this.results.forEach(r => {
      this.filteredResults.forEach(r => {
        if (r.alertLevel === 'RED') s.red++
        else if (r.alertLevel === 'YELLOW') s.yellow++
        if (r.status === 'PENDING') s.pending++
@@ -269,10 +392,14 @@
    filteredResults() {
      const kw = (this.enterpriseFilter || '').trim()
      const rule = this.ruleFilter
      if (!kw && !rule) return this.results
      const scope = this.issueScope || 'verify'
      const st = this.statusScope || 'all'
      return this.results.filter(r => {
        if (kw && !(r.enterpriseName || '').includes(kw)) return false
        if (rule && r.ruleName !== rule) return false
        if (scope === 'verify' && this.isReminderRow(r)) return false
        if (st === 'pending' && r.status !== 'PENDING') return false
        if (st === 'handled' && (!r.status || r.status === 'PENDING')) return false
        return true
      })
    },
@@ -299,12 +426,61 @@
    }
  },
  methods: {
    isReminderRow(r) {
      return !!(r && r.ruleCode === 'RULE_004')
    },
    dataType(t) { return DATA_TYPE_MAP[t] || '' },
    explainType(t) { return EXPLAIN_TYPES.indexOf(t) >= 0 },
    goData(row) {
    openMonthReport(row) {
      const type = DATA_TYPE_MAP[this.reportType]
      if (!type) return
      this.$router.push({ path: '/data', query: { type, period: row.reportPeriod, keyword: row.enterpriseName || row.city || '' } })
      this.monthType = type
      this.monthPeriod = row.reportPeriod
      this.monthKeyword = row.enterpriseName || row.city || ''
      this.monthRecords = []
      this.monthColumns = []
      this.monthVisible = true
      this.monthLoading = true
      api.get('/data/list', {
        params: {
          type, period: row.reportPeriod,
          keyword: row.enterpriseName || row.city || '',
          page: 1, size: 50
        }, timeout: 60000
      }).then(res => {
        const d = res.data || {}
        this.monthRecords = d.records || []
        this.monthColumns = d.columns || []
        if (!this.monthRecords.length) this.$message.warning('未查询到该企业该报表期的填报数据(可能尚未导入)')
      }).catch(e => {
        if (e && e._toast) return
        this.$message.error('月报数据查询失败')
      }).finally(() => { this.monthLoading = false })
    },
    monthTitle(rec) {
      const cfg = MONTH_CFG[this.monthType] || null
      if (!cfg) return (this.monthKeyword || '') + '(' + this.monthPeriod + ')'
      const name = rec.enterpriseName || rec.city || this.monthKeyword || ''
      return name + ' ' + (rec.reportPeriod || this.monthPeriod || '') + ' 月报数据'
    },
    monthMeta(rec) {
      const cfg = MONTH_CFG[this.monthType] || null
      if (!cfg) return []
      return cfg.meta.map(m => ({ label: m[0], value: rec[m[1]] })).filter(m => m.value !== undefined)
    },
    monthSections(rec) {
      const cfg = MONTH_CFG[this.monthType]
      if (!cfg) return []
      return cfg.sections.map(sec => ({
        title: sec.title, head: sec.head || [],
        rows: sec.rows.map(r => ({ label: r[0], cells: (r[1] || []).map(prop => ({ prop, v: prop ? rec[prop] : null })) }))
          .filter(rw => rw.cells.some(c => c.v !== null && c.v !== undefined && c.v !== ''))
      })).filter(sec => sec.rows.length)
    },
    fmtVal(v) {
      if (v === null || v === undefined || v === '') return '—'
      if (typeof v === 'number') return String(v)
      return String(v)
    },
    goExplain(row) {
      this.$router.push({ path: '/explain', query: { reportType: this.reportType, keyword: row.enterpriseName || row.city || '', period: row.reportPeriod } })
@@ -433,7 +609,26 @@
    },
    exportExcel() {
      if (!this.period) { this.$message.warning('请选择报表期'); return }
      window.open('/api/audit/export?period=' + this.period + '&reportType=' + this.reportType)
      const params = {
        period: this.period,
        reportType: this.reportType,
        onlyVerify: this.issueScope !== 'all',
        status: this.statusScope || 'all'
      }
      api.get('/audit/export', { params, responseType: 'blob' })
        .then(res => this.downloadBlob(res, '审核结果_' + this.period + '.xlsx'))
        .catch(() => this.$message.error('导出失败:请确认该报表期已有审核结果'))
    },
    downloadBlob(res, fileName) {
      const blob = res instanceof Blob ? res : new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
      const url = window.URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = url
      a.download = fileName
      document.body.appendChild(a)
      a.click()
      window.URL.revokeObjectURL(url)
      document.body.removeChild(a)
    },
    review(row, status) {
      if (status === 'IGNORED') {
@@ -581,4 +776,25 @@
.stat-num { color: #909399; }
.stat-num b { font-weight: 700; }
.hit-num { color: #E6A23C; }
.mr-dialog .el-dialog__body { padding: 12px 18px; }
.mr-body { max-height: 66vh; overflow-y: auto; }
.mr-card { border: 1px solid #DCDFE6; border-radius: 6px; margin-bottom: 14px; overflow: hidden; }
.mr-title { background: #409EFF; color: #fff; font-weight: 600; padding: 8px 12px; font-size: 15px; }
.mr-meta { width: 100%; border-collapse: collapse; background: #F7F9FC; }
.mr-meta td { border: 1px solid #EBEEF5; padding: 6px 10px; font-size: 13px; }
.mr-meta-k { width: 120px; color: #909399; background: #F0F4FA; }
.mr-meta-v { color: #303133; font-weight: 600; }
.mr-sec { padding: 10px 12px 4px; }
.mr-sec-title { font-weight: 600; color: #303133; margin: 4px 0 8px; }
.mr-sec-tab { width: 100%; border-collapse: collapse; font-size: 13px; }
.mr-sec-tab th, .mr-sec-tab td { border: 1px solid #DCDFE6; padding: 5px 8px; text-align: left; color: #303133; }
.mr-sec-tab th { background: #F5F7FA; font-weight: 600; }
.mr-note { margin: 6px 12px 12px; background: #FDF6EC; border: 1px solid #FAECD8; border-radius: 4px; padding: 8px 10px; font-size: 12px; color: #8a6d3b; white-space: pre-wrap; word-break: break-all; }
.mr-note-line { margin: 2px 0; }
.audit-filter-bar { background:#FAFAFA; border:1px solid #EBEEF5; border-radius:8px; padding:2px 12px 0; margin-top:8px; flex:none; }
.audit-filter-bar .el-form-item { margin-bottom:6px; }
.audit-filter-bar .el-form-item__label { color:#606266; font-weight:600; }
.hint { color:#909399; font-size:12px; margin-left:6px; white-space:nowrap; }
</style>