From a08c70db67e49bf6cd125df04ebaf68c26f0d8dd Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期日, 20 九月 2026 16:19:16 +0800
Subject: [PATCH] feat(投资): 市州月报统一填报模板 + 县(市、区)列解析 + 表头行判定收紧
---
traffic-audit-web/src/views/ReportExport.vue | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 161 insertions(+), 16 deletions(-)
diff --git a/traffic-audit-web/src/views/ReportExport.vue b/traffic-audit-web/src/views/ReportExport.vue
index 12f42d0..22e12cf 100644
--- a/traffic-audit-web/src/views/ReportExport.vue
+++ b/traffic-audit-web/src/views/ReportExport.vue
@@ -105,7 +105,8 @@
<div v-for="it in previewItems" :key="it.key" class="preview-item">
<div class="preview-head">
<span class="preview-name">{{ it.label }}</span>
- <span class="preview-rows">鍏� {{ it.rowCount || 0 }} 琛�</span>
+ <span class="preview-rows" v-if="it.key === 'summaryWorkbook'">鎸囨爣 {{ (it.summary || []).length }} 椤�</span>
+ <span class="preview-rows" v-else>鍏� {{ it.rowCount || 0 }} 琛屾槑缁�</span>
</div>
<div class="preview-coverage">
<span class="cov-label">鏈堝害瑕嗙洊锛�</span>
@@ -159,6 +160,25 @@
<el-button size="small" type="primary" @click="saveNote">淇濆瓨</el-button>
</span>
</el-dialog>
+
+ <!-- 姹囨�诲ぇ琛ㄥ紓姝ョ敓鎴愯繘搴︼紙鐢熸垚涓�娆$害 10~20 绉掞紝閬垮厤鐐瑰嚮鍚庨暱鏃堕棿鏃犲搷搴旓級 -->
+ <el-dialog title="姝e湪鐢熸垚姹囨�诲ぇ琛�" :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-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" :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>
+ </span>
+ </el-dialog>
</el-main>
</el-container>
</el-container>
@@ -188,6 +208,11 @@
noteDialogVisible: false,
noteEditingKey: '',
noteDraft: '',
+ // 姹囨�诲ぇ琛ㄥ紓姝ョ敓鎴愪换鍔�
+ taskVisible: false,
+ taskInfo: {},
+ taskTimer: null,
+ taskKey: 'summaryWorkbook',
modules: [
{
key: 'summary', title: '姹囨�诲ぇ琛�', items: [
@@ -224,7 +249,9 @@
{ key: 'investPlan', label: '鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽眹鎬�', fileName: '鐢熸垚_鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽儏鍐垫眹鎬�.xls', status: null, statusNote: '' },
{ key: 'investFiveYearLogistics', label: '鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞�', fileName: '鐢熸垚_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls', status: null, statusNote: '' },
{ key: 'investBillion', label: '浜垮厓鎶曡祫椤圭洰', fileName: '鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls', status: null, statusNote: '' },
- { key: 'investCounty', label: '缁忔祹寮哄幙椤圭洰鎶曡祫缁熻', fileName: '鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls', status: null, statusNote: '' }
+ { key: 'investCounty', label: '缁忔祹寮哄幙椤圭洰鎶曡祫缁熻', fileName: '鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls', status: null, statusNote: '' },
+ { key: 'investPassengerDetail', label: '瀹㈣繍绔欐姇璧勬槑缁嗚〃', fileName: '鐢熸垚_X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx', status: null, statusNote: '' },
+ { key: 'investLogisticsDetail', label: '鐗╂祦绔欏満鎶曡祫鏄庣粏', fileName: '鐢熸垚_X鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls', status: null, statusNote: '' }
], checked: []
},
{
@@ -274,6 +301,26 @@
options: months.map(mo => ({ label: y + '骞�' + mo + '鏈�', value: y + '-' + String(mo).padStart(2, '0') }))
}))
},
+ // ===== 浠ヤ笅涓烘眹鎬诲ぇ琛ㄥ紓姝ョ敓鎴愬脊绐楃殑娲剧敓鐘舵�侊紙蹇呴』骞跺叆鍚屼竴涓� computed锛屽彟璧峰悓鍚� key 浼氳鐩栨帀涓婇潰鐨勮绠楀睘鎬э級=====
+ taskRunning() {
+ const s = this.taskInfo && this.taskInfo.status
+ return s === 'PENDING' || s === 'RUNNING'
+ },
+ taskDone() {
+ return this.taskInfo && this.taskInfo.status === 'DONE'
+ },
+ // 鐢熸垚缁撴潫锛堟垚鍔熸垨澶辫触锛夊悗鎵嶅厑璁告敹璧峰脊绐�
+ taskFinished() {
+ const s = this.taskInfo && this.taskInfo.status
+ return s === 'DONE' || s === 'FAILED'
+ },
+ taskCacheTime() {
+ const t = this.taskInfo && this.taskInfo.finishedAt
+ if (!t) return '涓婃'
+ 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())
+ }
},
watch: {
activeKey() {
@@ -286,7 +333,11 @@
this.activeKey = (mine.length ? mine : this.visibleModules.map(m => m.key))[0]
this.ensureCycle()
this.refreshAll()
- }, methods: {
+ },
+ beforeDestroy() {
+ this.stopTaskPolling()
+ },
+ methods: {
moduleTitle(key) {
const m = this.modules.find(x => x.key === key)
return m ? m.title : key
@@ -417,28 +468,28 @@
{
name: '閬撹矾璐ц繍',
rows: mk([
- ['褰撴湀璐ц繍閲�(涓囧惃)', '鍏ㄧ渷璐ц繍閲徛峰綋鏈�'],
- ['褰撴湀璐х墿鍛ㄨ浆閲�(涓囧惃)', '鍏ㄧ渷璐х墿鍛ㄨ浆閲徛峰綋鏈�', '鍏ㄧ渷璐х墿鍛ㄨ浆閲徛峰綋鏈堝悓姣�'],
- ['绱璐ц繍閲�(涓囧惃)', '鍏ㄧ渷璐ц繍閲徛风疮璁�'],
+ ['褰撴湀璐ц繍閲�(涓囧惃)', '鍏ㄧ渷璐ц繍閲徛峰綋鏈�', '鍏ㄧ渷璐ц繍閲徛峰綋鏈堝悓姣�'],
+ ['褰撴湀璐х墿鍛ㄨ浆閲�(涓囧惃鍏噷)', '鍏ㄧ渷璐х墿鍛ㄨ浆閲徛峰綋鏈�', '鍏ㄧ渷璐х墿鍛ㄨ浆閲徛峰綋鏈堝悓姣�'],
+ ['绱璐ц繍閲�(涓囧惃)', '鍏ㄧ渷璐ц繍閲徛风疮璁�', '鍏ㄧ渷璐ц繍閲徛风疮璁″悓姣�'],
['绱璐х墿鍛ㄨ浆閲�(涓囧惃鍏噷)', '鍏ㄧ渷璐х墿鍛ㄨ浆閲徛风疮璁�', '鍏ㄧ渷璐х墿鍛ㄨ浆閲徛风疮璁″悓姣�']
])
},
{
name: '鍩庡競瀹㈣繍',
rows: mk([
- ['褰撴湀鍩庡競瀹㈣繍閲�:', '鍩庡競瀹㈣繍閲徛峰綋鏈�'],
- ['褰撴湀鍩庡競瀹㈣繍鍛ㄨ浆閲�', '鍩庡競瀹㈣繍鍛ㄨ浆閲徛峰綋鏈�'],
- ['绱鍩庡競瀹㈣繍閲�:', '鍩庡競瀹㈣繍閲徛风疮璁�'],
- ['绱鍩庡競瀹㈣繍鍛ㄨ浆閲�', '鍩庡競瀹㈣繍鍛ㄨ浆閲徛风疮璁�']
+ ['褰撴湀鍩庡競瀹㈣繍閲�', '鍩庡競瀹㈣繍閲徛峰綋鏈�', '鍩庡競瀹㈣繍閲徛峰綋鏈堝悓姣�'],
+ ['褰撴湀鍩庡競瀹㈣繍鍛ㄨ浆閲�', '鍩庡競瀹㈣繍鍛ㄨ浆閲徛峰綋鏈�', '鍩庡競瀹㈣繍鍛ㄨ浆閲徛峰綋鏈堝悓姣�'],
+ ['绱鍩庡競瀹㈣繍閲�', '鍩庡競瀹㈣繍閲徛风疮璁�', '鍩庡競瀹㈣繍閲徛风疮璁″悓姣�'],
+ ['绱鍩庡競瀹㈣繍鍛ㄨ浆閲�', '鍩庡競瀹㈣繍鍛ㄨ浆閲徛风疮璁�', '鍩庡競瀹㈣繍鍛ㄨ浆閲徛风疮璁″悓姣�']
])
},
{
name: '涓彛寰�',
rows: mk([
- ['褰撴湀涓彛寰勫杩愰噺', '涓彛寰勫杩愰噺路褰撴湀'],
- ['褰撴湀涓彛寰勫懆杞噺', '涓彛寰勫懆杞噺路褰撴湀'],
- ['绱涓彛寰勫杩愰噺', '涓彛寰勫杩愰噺路绱'],
- ['绱涓彛寰勫懆杞噺', '涓彛寰勫懆杞噺路绱']
+ ['褰撴湀涓彛寰勫杩愰噺', '涓彛寰勫杩愰噺路褰撴湀', '涓彛寰勫杩愰噺路褰撴湀鍚屾瘮'],
+ ['褰撴湀涓彛寰勫懆杞噺', '涓彛寰勫懆杞噺路褰撴湀', '涓彛寰勫懆杞噺路褰撴湀鍚屾瘮'],
+ ['绱涓彛寰勫杩愰噺', '涓彛寰勫杩愰噺路绱', '涓彛寰勫杩愰噺路绱鍚屾瘮'],
+ ['绱涓彛寰勫懆杞噺', '涓彛寰勫懆杞噺路绱', '涓彛寰勫懆杞噺路绱鍚屾瘮']
])
}
]
@@ -465,6 +516,86 @@
if (continuous) return '1~' + cov[cov.length - 1] + '鏈堝潎鏈夋暟鎹�'
return cov.map(x => x + '鏈�').join('銆�') + ' 鏈夋暟鎹�'
},
+ // ==================== 姹囨�诲ぇ琛ㄥ紓姝ョ敓鎴愶紙杩涘害寮圭獥锛� ====================
+ // 鍚庣 ReportExportTaskService锛氭彁浜や换鍔� -> 杞杩涘害 -> 瀹屾垚鍚庝笅杞芥枃浠躲��
+ // 鍚屾姤琛ㄦ湡 10 鍒嗛挓鍐呬笖鏈熼棿娌℃湁鏂板鍏ユ暟鎹椂锛屽悗绔洿鎺ヨ繑鍥炰笂娆$粨鏋滐紙寮圭獥鍐呬細璇存槑鏉ユ簮鏃堕棿锛夈��
+ startSummaryTask(refresh) {
+ if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鍛ㄦ湡鍜屾湀浠�'); return }
+ this.stopTaskPolling()
+ this.taskInfo = { status: 'PENDING', percent: 0, step: '鎻愪氦浠诲姟', period: this.period, elapsedMs: 0 }
+ this.taskVisible = true
+ this.loading = 'gen-' + this.curModule.key
+ const params = Object.assign({}, this.buildParams(), { refresh: refresh ? 'true' : 'false' })
+ api.get('/report/exportTask/summaryWorkbook', { params, timeout: 60000 })
+ .then(res => {
+ // 鍚庣缁熶竴杩斿洖 Result锛歝ode 闈� 200 鏃� data 涓虹┖锛屽繀椤绘彁绀鸿�屼笉鏄┖杞�
+ if (!res || res.code !== 200 || !res.data || !res.data.taskId) {
+ this.taskInfo = { status: 'FAILED', percent: 0, step: '鎻愪氦澶辫触', period: this.period, elapsedMs: 0,
+ error: (res && res.message) || '鍚庣鏈繑鍥炰换鍔″彿' }
+ this.loading = ''
+ return
+ }
+ this.taskInfo = res.data
+ if (this.taskInfo.status === 'DONE') { this.loading = ''; this.downloadTaskFile(); return }
+ if (this.taskInfo.status === 'FAILED') { this.loading = ''; return }
+ this.pollTask()
+ })
+ .catch(err => {
+ this.stopTaskPolling()
+ this.loading = ''
+ this.taskVisible = false
+ this.failWith(err, '鐢熸垚')
+ })
+ },
+ pollTask() {
+ this.stopTaskPolling()
+ const id = this.taskInfo && this.taskInfo.taskId
+ if (!id) return
+ this.taskTimer = setInterval(() => {
+ api.get('/report/exportTask/' + id, { timeout: 30000 })
+ .then(res => {
+ if (!res || res.code !== 200 || !res.data) {
+ // 浠诲姟琚竻鐞�/杩囨湡锛氬仠姝㈣疆璇㈠苟缁欏嚭鍘熷洜锛屼笉璁╁脊绐椾竴鐩寸┖杞�
+ this.stopTaskPolling()
+ this.loading = ''
+ this.taskInfo = Object.assign({}, this.taskInfo, {
+ status: 'FAILED', step: '浠诲姟宸插け鏁�',
+ error: (res && res.message) || '瀵煎嚭浠诲姟宸插け鏁堬紝璇烽噸鏂扮敓鎴�'
+ })
+ return
+ }
+ this.taskInfo = res.data
+ if (this.taskInfo.status === 'DONE') {
+ this.stopTaskPolling()
+ this.loading = ''
+ this.downloadTaskFile()
+ } else if (this.taskInfo.status === 'FAILED') {
+ this.stopTaskPolling()
+ this.loading = ''
+ if (!this.taskVisible) this.$message.error('姹囨�诲ぇ琛ㄧ敓鎴愬け璐ワ細' + (this.taskInfo.error || '鏈煡鍘熷洜'))
+ }
+ })
+ .catch(() => { /* 鍗曟杞澶辫触蹇界暐锛屼笅涓�杞户缁� */ })
+ }, 700)
+ },
+ stopTaskPolling() {
+ if (this.taskTimer) { clearInterval(this.taskTimer); this.taskTimer = null }
+ },
+ downloadTaskFile() {
+ const id = this.taskInfo && this.taskInfo.taskId
+ if (!id) return
+ const name = this.taskInfo.fileName || ('鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃_' + this.period + '.xlsx')
+ api.get('/report/exportTask/' + id + '/file', { responseType: 'blob', timeout: 120000 })
+ .then(res => { this.downloadBlob(res, name); this.$message.success('鎶ヨ〃瀵煎嚭鎴愬姛') })
+ .catch(err => this.failWith(err))
+ },
+ restartTask() { this.startSummaryTask(true) },
+ closeTask() { this.taskVisible = false; this.stopTaskPolling(); this.loading = '' },
+ // 鐢熸垚涓敹璧峰脊绐楋細浠诲姟缁х画鍦ㄥ悗鍙版墽琛岋紝瀹屾垚鍚庝粛浼氳嚜鍔ㄤ笅杞斤紙涓庡師鏉ュ悓姝ュ鍑虹殑銆岀偣瀹屽嵆涓嬭浇銆嶄竴鑷达級
+ minimizeTask() {
+ this.taskVisible = false
+ this.$message.info('宸茶浆鍏ュ悗鍙扮敓鎴愶紝瀹屾垚鍚庝細鑷姩涓嬭浇')
+ },
exportChecked(itemList) {
const m = this.curModule
if (!this.period) { this.$message.warning('璇峰厛閫夋嫨鍛ㄦ湡鍜屾湀浠�'); return }
@@ -474,8 +605,10 @@
const params = this.buildParams()
const doExport = () => {
if (items.length === 1) {
+ // 姹囨�诲ぇ琛ㄧ敓鎴愪竴娆$害 10~20 绉掞紝璧板紓姝ヤ换鍔★紙杩涘害寮圭獥锛夛紝閬垮厤鐐瑰嚮鍚庨暱鏃堕棿鏃犲搷搴�
+ if (items[0].key === 'summaryWorkbook') { this.startSummaryTask(false); return }
api.get('/report/export/' + items[0].key, { params, responseType: 'blob', timeout: 120000 })
- .then(res => { this.downloadBlob(res, items[0].fileName); this.$message.success('鎶ヨ〃瀵煎嚭鎴愬姛'); this.loading = '' })
+ .then(res => { this.downloadBlob(res, this.fileNameOf(items[0])); this.$message.success('鎶ヨ〃瀵煎嚭鎴愬姛'); this.loading = '' })
.catch(err => this.failWith(err))
return
}
@@ -546,6 +679,13 @@
}
return text.length > 200 ? text.slice(0, 200) + '鈥�' : text
},
+ // 鏂囦欢鍚嶅崰浣嶇 X鏈� -> 褰撳墠鎶ヨ〃鏈熸湀浠斤紙濡傘�岀敓鎴恄X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx銆�->銆岀敓鎴恄8鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx銆嶏級
+ fileNameOf(item) {
+ const name = (item && item.fileName) || ''
+ const mm = parseInt((this.period || '').split('-')[1], 10)
+ if (!mm) return name
+ return name.replace(/X鏈�/g, mm + '鏈�')
+ },
downloadBlob(res, fileName) {
const isZip = /\.zip$/.test(fileName)
const isXls = /\.xls$/.test(fileName)
@@ -611,6 +751,11 @@
.wb-yoy { text-align:right; color:#606266; }
.wb-yoy.yoy-empty { color:#C0C4CC; }
.wb-note { color:#E6A23C; font-size:12px; margin:2px 0 8px; }
+/* 寮傛鐢熸垚杩涘害寮圭獥 */
+.task-body { padding: 4px 2px; }
+.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; }
</style>
<style>
@@ -619,4 +764,4 @@
.pv-dialog { border-radius: 10px; overflow: hidden; }
.pv-dialog .el-dialog__header { cursor: move; user-select: none; -webkit-user-select: none; }
.pv-dialog .el-dialog__headerbtn { cursor: pointer; }
-</style>
\ No newline at end of file
+</style>
--
Gitblit v1.9.1