| | |
| | | </div> |
| | | </el-alert> |
| | | </div> |
| | | <el-dialog :title="'预览数据 - ' + previewModule + '(' + previewPeriod + ')'" :visible.sync="previewVisible" width="760px"> |
| | | <el-dialog :title="'预览数据 - ' + previewModule + '(' + previewPeriod + ')'" :visible.sync="previewVisible" width="760px" |
| | | top="5vh" custom-class="pv-dialog" v-dialog-drag> |
| | | <div class="preview-body"> |
| | | <div v-for="it in previewItems" :key="it.key" class="preview-item"> |
| | | <div class="preview-head"> |
| | |
| | | </div> |
| | | <table class="preview-summary wb-table" v-if="it.key === 'summaryWorkbook' && it.summary && it.summary.length"> |
| | | <thead> |
| | | <tr><th style="width:44%">指标</th><th style="width:30%">数据</th><th>同比</th></tr> |
| | | <tr><th style="width:12%">分类</th><th style="width:40%">指标</th><th style="width:28%">数据</th><th>同比</th></tr> |
| | | </thead> |
| | | <tbody> |
| | | <template v-for="(sec, si) in wbRows(it.summary)"> |
| | |
| | | this.previewVisible = true |
| | | this.loading = '' |
| | | }) |
| | | .catch(err => this.failWith(err)) |
| | | .catch(err => this.failWith(err, '预览')) |
| | | }, |
| | | fmtVal(v) { |
| | | if (v === null || v === undefined || v === '') return '—' |
| | |
| | | }) |
| | | .catch(() => doExport()) |
| | | }, |
| | | failWith(err) { |
| | | // 预览/导出失败统一提示:区分“连不上服务器”与“后端返回的错误原因”。 |
| | | // 导出接口用 responseType: 'blob',报错时 data 也是 Blob,必须先读文本再解析, |
| | | // 否则拿不到后端真实原因,只会显示笼统的“请确认已导入对应数据”。 |
| | | failWith(err, action) { |
| | | if (err && err._toast) { this.loading = ''; return } |
| | | let msg = '' |
| | | const d = err && err.response && err.response.data |
| | | if (d && typeof d === 'object' && d.message) msg = d.message |
| | | this.$message.error('导出失败:' + (msg || '请确认已导入对应数据,或查看后端日志')) |
| | | this.loading = '' |
| | | const act = action || '导出' |
| | | const finish = (reason) => { |
| | | this.$message.error(act + '失败:' + reason) |
| | | this.loading = '' |
| | | } |
| | | if (!err || !err.response) { |
| | | if (err && err.code === 'ECONNABORTED') { finish('请求超时,请稍后重试'); return } |
| | | finish('无法连接服务器,请检查网络连接,或确认后端服务是否已启动') |
| | | return |
| | | } |
| | | const d = err.response.data |
| | | const fallback = '请确认已导入对应数据,或查看后端日志' |
| | | if (d && typeof d.text === 'function') { |
| | | Promise.resolve(d.text()).then(txt => finish(this.errReason(txt) || fallback)).catch(() => finish(fallback)) |
| | | return |
| | | } |
| | | finish(this.errReason(typeof d === 'string' ? d : (d && d.message)) || fallback) |
| | | }, |
| | | // 后端异常响应可能是 JSON/HTML/纯文本,尽量提取出可读的原因 |
| | | errReason(raw) { |
| | | if (!raw) return '' |
| | | let text = String(raw).trim() |
| | | if (text.charAt(0) === '{' || text.charAt(0) === '[') { |
| | | try { |
| | | const j = JSON.parse(text) |
| | | if (j && j.message) return j.message |
| | | } catch (e) { /* 非 JSON,按纯文本处理 */ } |
| | | } |
| | | text = text.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim() |
| | | // 开发代理/网络层报错转成人话,避免直接把 ECONNREFUSED 之类抛给用户 |
| | | if (/ECONNREFUSED|ECONNRESET|Proxy error|socket hang up|Network Error|Failed to fetch/i.test(text)) { |
| | | return '无法连接服务器,请检查网络连接,或确认后端服务是否已启动' |
| | | } |
| | | return text.length > 200 ? text.slice(0, 200) + '…' : text |
| | | }, |
| | | downloadBlob(res, fileName) { |
| | | const isZip = /\.zip$/.test(fileName) |
| | |
| | | .wb-yoy.yoy-empty { color:#C0C4CC; } |
| | | .wb-note { color:#E6A23C; font-size:12px; margin:2px 0 8px; } |
| | | </style> |
| | | |
| | | <style> |
| | | /* 预览数据弹窗:圆角 + 可通过标题栏拖动(v-dialog-drag 指令)。 |
| | | 不能写进 scoped:el-dialog 默认 append-to-body,DOM 挂在 body 上,scoped 选择器匹配不到。 */ |
| | | .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> |