From 259db33b22095891f436b9ed5a81a21496b11c4c Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期一, 12 十月 2026 23:41:55 +0800
Subject: [PATCH] feat(汇总大表): 缺数清单加导出前弹窗并在生成件附《数据缺失清单》页(待验收)
---
traffic-audit-web/src/views/ReportExport.vue | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 102 insertions(+), 10 deletions(-)
diff --git a/traffic-audit-web/src/views/ReportExport.vue b/traffic-audit-web/src/views/ReportExport.vue
index 22e12cf..d689d3c 100644
--- a/traffic-audit-web/src/views/ReportExport.vue
+++ b/traffic-audit-web/src/views/ReportExport.vue
@@ -10,8 +10,13 @@
<el-menu-item index="/explain">浼佷笟瑙i噴</el-menu-item>
<el-menu-item index="/audit">瀹℃牳缁撴灉</el-menu-item>
<el-menu-item index="/report">鎶ヨ〃鐢熸垚</el-menu-item>
+ <el-submenu index="/special-stats">
+ <template slot="title">涓撻」缁熻</template>
+ <el-menu-item index="/holiday-stat">鑺傚亣鏃�</el-menu-item>
+ </el-submenu>
<el-menu-item index="/rules">瑙勫垯绠$悊</el-menu-item>
<el-menu-item index="/users">鐢ㄦ埛绠$悊</el-menu-item>
+ <el-menu-item v-if="operatorIsAdmin" index="/system-config">绯荤粺閰嶇疆</el-menu-item>
</el-menu>
</el-aside>
<el-main>
@@ -162,21 +167,46 @@
</el-dialog>
<!-- 姹囨�诲ぇ琛ㄥ紓姝ョ敓鎴愯繘搴︼紙鐢熸垚涓�娆$害 10~20 绉掞紝閬垮厤鐐瑰嚮鍚庨暱鏃堕棿鏃犲搷搴旓級 -->
- <el-dialog title="姝e湪鐢熸垚姹囨�诲ぇ琛�" :visible.sync="taskVisible" width="460px"
+ <el-dialog :title="taskDialogTitle" :visible.sync="taskVisible" width="460px"
:close-on-click-modal="false" :show-close="taskFinished">
<div class="task-body">
<el-progress :percentage="taskInfo.percent || 0" :status="taskInfo.status === 'FAILED' ? 'exception' : (taskDone ? 'success' : null)"
:stroke-width="14"></el-progress>
<p class="task-step">{{ taskInfo.step || '鎺掗槦涓�' }}</p>
- <p class="task-meta" v-if="taskInfo.period">鎶ヨ〃鏈� {{ taskInfo.period }}銆�{{ taskInfo.fromCache ? '涓婃鐢熸垚鐢ㄦ椂' : '宸茬敤' }} {{ (taskInfo.elapsedMs / 1000).toFixed(1) }} 绉�</p>
- <p class="task-meta" v-if="taskInfo.fromCache">鏈鐩存帴浣跨敤 {{ taskCacheTime }} 鐢熸垚鐨勭粨鏋滐紙10 鍒嗛挓鍐呬笖鏈熼棿娌℃湁鏂板鍏ユ暟鎹級銆傚闇�鎸夋渶鏂版暟鎹噸绠楋紝璇风偣銆岄噸鏂扮敓鎴愩�嶃��</p>
+ <p class="task-meta" v-if="taskInfo.period && !taskInfo.fromCache">鎶ヨ〃鏈� {{ taskInfo.period }}銆�宸茬敤 {{ ((taskInfo.elapsedMs || 0) / 1000).toFixed(1) }} 绉�</p>
+ <p class="task-meta" v-else-if="taskInfo.period">鎶ヨ〃鏈� {{ taskInfo.period }}銆�涓婃鐢熸垚浜� {{ taskCacheTime }}</p>
+ <p class="task-meta" v-if="taskInfo.staleCache">涓婃鐢熸垚鐨勭粨鏋滃凡鍥犳暟鎹垨姣嶇増鍙樺寲澶辨晥锛屾湰娆℃寜鏈�鏂版暟鎹噸绠椼��</p>
+ <p class="task-meta" v-if="taskInfo.fromCache">鏈鐩存帴娌跨敤 {{ taskCacheTime }} 鐢熸垚鐨勭粨鏋滐紙鏈熼棿娌℃湁鏂扮殑鏁版嵁瀵煎叆锛夈�傚闇�鎸夋渶鏂版暟鎹噸绠楋紝璇风偣銆岄噸鏂扮敓鎴愩�嶃��</p>
+ <p class="task-done" v-if="taskDownloaded">
+ 宸茶嚜鍔ㄤ笅杞斤細<b>{{ taskFileName }}</b><span v-if="taskFileSize">锛坽{ taskFileSizeText }}锛�</span>锛屾枃浠跺湪娴忚鍣ㄩ粯璁や笅杞界洰褰曘��
+ </p>
+ <p class="task-error" v-else-if="taskDone">鑷姩涓嬭浇娌℃垚鍔燂紝鍙偣銆屼繚瀛樻枃浠躲�嶅啀瀛樹竴娆°��</p>
<p class="task-error" v-if="taskInfo.status === 'FAILED'">{{ taskInfo.error }}</p>
</div>
<span slot="footer">
- <el-button size="small" type="primary" :disabled="!taskDone" @click="downloadTaskFile">{{ taskInfo.fromCache ? '涓嬭浇' : '淇濆瓨鏂囦欢' }}</el-button>
+ <el-button size="small" v-if="taskDone && !taskDownloaded" @click="downloadTaskFile()">淇濆瓨鏂囦欢</el-button>
<el-button size="small" :disabled="taskRunning" @click="restartTask">閲嶆柊鐢熸垚</el-button>
<el-button size="small" v-if="taskRunning" @click="minimizeTask">鍚庡彴杩愯</el-button>
- <el-button size="small" v-else @click="closeTask">鍏抽棴</el-button>
+ <el-button size="small" v-else type="primary" @click="closeTask">鍏抽棴</el-button>
+ </span>
+ </el-dialog>
+ <!-- 姹囨�诲ぇ琛ㄥ鍑哄墠缂烘暟娓呭崟寮圭獥锛�2026-10-12 鐢ㄦ埛瑕佹眰锛氱己鏁版憜鍒板彴闈笂锛� -->
+ <el-dialog title="鐢熸垚鍓嶆彁绀� - 鏁版嵁缂哄け娓呭崟" :visible.sync="missDialogVisible" width="720px" top="6vh">
+ <div class="miss-body">
+ <p class="miss-lead">鎶ヨ〃鏈� <b>{{ missDialogPeriod }}</b>锛氫互涓嬫暟鎹湰娆$敓鎴愪細缂哄け/寮傚父锛岀敓鎴愪欢鏈熬涔熶細闄勩�婃暟鎹己澶辨竻鍗曘�嬮〉銆�</p>
+ <template v-if="missDialogMissing.length">
+ <div class="miss-sec">涓�銆佺己鏁版竻鍗曪紙璇ユ湁鐨勬暟娌℃湁锛夈��鍏� {{ missDialogMissing.length }} 鏉�</div>
+ <ul class="miss-list"><li v-for="(t, i) in missDialogMissing" :key="'m' + i">{{ t }}</li></ul>
+ </template>
+ <template v-if="missDialogCheck.length">
+ <div class="miss-sec">浜屻�佹暟鎹綋妫�锛堟暟鍊间笉鍚堢悊锛夈��鍏� {{ missDialogCheck.length }} 鏉�</div>
+ <ul class="miss-list"><li v-for="(t, i) in missDialogCheck" :key="'c' + i">{{ t }}</li></ul>
+ </template>
+ <p class="miss-tip">缂烘暟澶氫负婧愭暟鎹皻鏈鍏ワ紝瀵煎叆鍚庨噸鏂扮敓鎴愬嵆鍙紱浣撴椤硅鏍稿婧愭枃浠躲��</p>
+ </div>
+ <span slot="footer">
+ <el-button size="small" @click="missDialogVisible = false">鍙栨秷</el-button>
+ <el-button size="small" type="primary" @click="confirmMissDialog">浠嶈鐢熸垚</el-button>
</span>
</el-dialog>
</el-main>
@@ -212,7 +242,17 @@
taskVisible: false,
taskInfo: {},
taskTimer: null,
+ // 杩涘害寮圭獥锛氬畬鎴愬悗浼氳嚜鍔ㄤ笅杞戒竴娆★紝杩欓噷璁板綍銆屾槸鍚﹀凡涓嬭浇銆嶄笌鏂囦欢淇℃伅锛堝凡涓嬭浇灏变笉鍐嶆樉绀轰笅杞芥寜閽級
+ taskDownloaded: false,
+ taskFileName: '',
+ taskFileSize: 0,
taskKey: 'summaryWorkbook',
+ // 瀵煎嚭鍓嶇己鏁版竻鍗曞脊绐楋紙2026-10-12锛夛細缂烘暟/浣撴鎽嗗埌鍙伴潰涓婏紝纭鍚庡啀鐢熸垚
+ missDialogVisible: false,
+ missDialogPeriod: '',
+ missDialogMissing: [],
+ missDialogCheck: [],
+ missDialogRefresh: false,
modules: [
{
key: 'summary', title: '姹囨�诲ぇ琛�', items: [
@@ -320,6 +360,18 @@
const d = new Date(t)
const p = (n) => String(n).padStart(2, '0')
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' ' + p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds())
+ },
+ // 寮圭獥鏍囬闅忎换鍔$姸鎬佸彉鍖栵紝閬垮厤銆屽凡瀹屾垚銆嶈繕鍐欑潃銆屾鍦ㄧ敓鎴愩��
+ taskDialogTitle() {
+ const s = this.taskInfo && this.taskInfo.status
+ if (s === 'FAILED') return '姹囨�诲ぇ琛ㄧ敓鎴愬け璐�'
+ if (s === 'DONE') return '姹囨�诲ぇ琛ㄥ凡鐢熸垚'
+ return '姝e湪鐢熸垚姹囨�诲ぇ琛�'
+ },
+ taskFileSizeText() {
+ const n = this.taskFileSize || 0
+ if (!n) return ''
+ return n >= 1048576 ? (n / 1048576).toFixed(2) + ' MB' : Math.round(n / 1024) + ' KB'
}
},
watch: {
@@ -524,6 +576,9 @@
this.stopTaskPolling()
this.taskInfo = { status: 'PENDING', percent: 0, step: '鎻愪氦浠诲姟', period: this.period, elapsedMs: 0 }
this.taskVisible = true
+ this.taskDownloaded = false
+ this.taskFileName = ''
+ this.taskFileSize = 0
this.loading = 'gen-' + this.curModule.key
const params = Object.assign({}, this.buildParams(), { refresh: refresh ? 'true' : 'false' })
api.get('/report/exportTask/summaryWorkbook', { params, timeout: 60000 })
@@ -536,7 +591,7 @@
return
}
this.taskInfo = res.data
- if (this.taskInfo.status === 'DONE') { this.loading = ''; this.downloadTaskFile(); return }
+ if (this.taskInfo.status === 'DONE') { this.loading = ''; this.downloadTaskFile(true); return }
if (this.taskInfo.status === 'FAILED') { this.loading = ''; return }
this.pollTask()
})
@@ -568,7 +623,7 @@
if (this.taskInfo.status === 'DONE') {
this.stopTaskPolling()
this.loading = ''
- this.downloadTaskFile()
+ this.downloadTaskFile(true)
} else if (this.taskInfo.status === 'FAILED') {
this.stopTaskPolling()
this.loading = ''
@@ -581,13 +636,42 @@
stopTaskPolling() {
if (this.taskTimer) { clearInterval(this.taskTimer); this.taskTimer = null }
},
- downloadTaskFile() {
+ // auto=true 琛ㄧず銆屼换鍔″畬鎴愬悗鐨勮嚜鍔ㄤ笅杞姐�嶏紱鐢ㄦ埛鐐规寜閽椂涓� false
+ downloadTaskFile(auto) {
const id = this.taskInfo && this.taskInfo.taskId
if (!id) return
const name = this.taskInfo.fileName || ('鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃_' + this.period + '.xlsx')
+ this.taskFileName = name
api.get('/report/exportTask/' + id + '/file', { responseType: 'blob', timeout: 120000 })
- .then(res => { this.downloadBlob(res, name); this.$message.success('鎶ヨ〃瀵煎嚭鎴愬姛') })
+ .then(res => {
+ this.taskFileSize = (res && res.size) || 0
+ this.taskDownloaded = true
+ this.downloadBlob(res, name)
+ this.$message.success(auto ? '鐢熸垚瀹屾垚锛屽凡鑷姩涓嬭浇' : '鎶ヨ〃瀵煎嚭鎴愬姛')
+ })
.catch(err => this.failWith(err))
+ },
+ // 瀵煎嚭鍓嶇己鏁版鏌ワ細璋冨彧璇绘帴鍙f妸缂烘暟/浣撴缁撴灉鎽嗗埌鐢ㄦ埛闈㈠墠锛岀‘璁ゅ悗鍐嶈蛋寮傛鐢熸垚浠诲姟
+ generateSummaryWorkbook(refresh) {
+ if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鍛ㄦ湡鍜屾湀浠�'); return }
+ const params = { period: this.period }
+ Promise.all([
+ api.get('/report/summaryWorkbook/missing', { params, timeout: 60000 }).catch(() => null),
+ api.get('/report/summaryWorkbook/freightCheck', { params, timeout: 60000 }).catch(() => null)
+ ]).then(res => {
+ const miss = (res[0] && res[0].code === 200 && res[0].data) || []
+ const chk = (res[1] && res[1].code === 200 && res[1].data) || []
+ if (!miss.length && !chk.length) { this.startSummaryTask(refresh); return }
+ this.missDialogPeriod = this.period
+ this.missDialogMissing = miss
+ this.missDialogCheck = chk
+ this.missDialogRefresh = refresh
+ this.missDialogVisible = true
+ })
+ },
+ confirmMissDialog() {
+ this.missDialogVisible = false
+ this.startSummaryTask(this.missDialogRefresh)
},
restartTask() { this.startSummaryTask(true) },
closeTask() { this.taskVisible = false; this.stopTaskPolling(); this.loading = '' },
@@ -606,7 +690,7 @@
const doExport = () => {
if (items.length === 1) {
// 姹囨�诲ぇ琛ㄧ敓鎴愪竴娆$害 10~20 绉掞紝璧板紓姝ヤ换鍔★紙杩涘害寮圭獥锛夛紝閬垮厤鐐瑰嚮鍚庨暱鏃堕棿鏃犲搷搴�
- if (items[0].key === 'summaryWorkbook') { this.startSummaryTask(false); return }
+ if (items[0].key === 'summaryWorkbook') { this.generateSummaryWorkbook(false); return }
api.get('/report/export/' + items[0].key, { params, responseType: 'blob', timeout: 120000 })
.then(res => { this.downloadBlob(res, this.fileNameOf(items[0])); this.$message.success('鎶ヨ〃瀵煎嚭鎴愬姛'); this.loading = '' })
.catch(err => this.failWith(err))
@@ -756,6 +840,14 @@
.task-step { margin: 12px 0 4px; font-size: 14px; color:#303133; }
.task-meta { margin: 2px 0; font-size: 12px; color:#909399; line-height: 1.6; }
.task-error { margin: 8px 0 0; font-size: 13px; color:#F56C6C; word-break: break-all; }
+.task-done { margin: 8px 0 0; font-size: 13px; color:#67C23A; line-height: 1.6; }
+/* 瀵煎嚭鍓嶇己鏁版竻鍗曞脊绐� */
+.miss-body { max-height: 62vh; overflow-y: auto; }
+.miss-lead { font-size: 13px; color: #303133; margin: 0 0 10px; line-height: 1.7; }
+.miss-sec { font-size: 13px; font-weight: 600; color: #F56C6C; margin: 10px 0 4px; }
+.miss-list { margin: 0; padding-left: 20px; font-size: 13px; color: #303133; line-height: 1.7; }
+.miss-list li { margin: 2px 0; }
+.miss-tip { font-size: 12px; color: #909399; margin: 10px 0 0; }
</style>
<style>
--
Gitblit v1.9.1