| | |
| | | <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> |
| | |
| | | |
| | | </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> |
| | |
| | | <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-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> |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | </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> |
| | |
| | | </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> |
| | |
| | | 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', |
| | |
| | | return { |
| | | period: '', reportType: 'H2032', loading: false, marking: false, results: [], ruleStats: [], statsCollapsed: false, |
| | | enterpriseFilter: '', ruleFilter: '', viewMode: 'merged', batchSelection: [], focusMode: false, |
| | | explainDrafts: {}, savingExplainId: null, expandedExplain: null |
| | | explainDrafts: {}, savingExplainId: null, expandedExplain: null, |
| | | monthVisible: false, monthLoading: false, monthType: '', monthPeriod: '', monthKeyword: '', monthRecords: [], monthColumns: [] |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | methods: { |
| | | 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 } }) |
| | |
| | | .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; } |
| | | </style> |