traffic-audit-web/src/views/DataView.vue
@@ -17,8 +17,10 @@
      <el-main>
        <h3>导入数据查看</h3>
        <div class="toolbar">
          <span class="toolbar-label">报表期</span>
          <el-date-picker v-model="period" type="month" placeholder="报表期(全部)" value-format="yyyy-MM" clearable style="width:150px" @change="doQuery"></el-date-picker>
          <template v-if="!isRollup">
            <span class="toolbar-label">报表期</span>
            <el-date-picker v-model="period" type="month" placeholder="报表期(全部)" value-format="yyyy-MM" clearable style="width:150px" @change="doQuery"></el-date-picker>
          </template>
          <span class="toolbar-sep"></span>
          <span class="toolbar-label">数据类型</span>
          <el-select v-model="type" size="small" style="width:300px" placeholder="选择数据类型" filterable
@@ -27,11 +29,41 @@
              <el-option v-for="t in g.types" :key="t.value" :label="t.label" :value="t.value"></el-option>
            </el-option-group>
          </el-select>
          <el-input v-model="keyword" placeholder="企业/市州名称" clearable style="width:180px" @keyup.enter.native="doQuery"></el-input>
          <el-button type="primary" size="small" @click="doQuery">查询</el-button>
          <template v-if="canRollup">
            <span class="toolbar-label">视图</span>
            <el-radio-group v-model="viewMode" size="small" @change="onViewChange">
              <el-radio-button label="detail">明细</el-radio-button>
              <el-radio-button label="rollup">按市州汇总</el-radio-button>
            </el-radio-group>
          </template>
          <template v-if="isRollup">
            <span class="toolbar-label">起止月</span>
            <el-date-picker v-model="from" type="month" placeholder="起始月" value-format="yyyy-MM" style="width:130px"></el-date-picker>
            <span>~</span>
            <el-date-picker v-model="to" type="month" placeholder="结束月" value-format="yyyy-MM" style="width:130px"></el-date-picker>
            <span class="toolbar-label">排名</span>
            <el-select v-model="rankMode" size="small" style="width:150px">
              <el-option label="留空" value="blank"></el-option>
              <el-option label="按累计值重排" value="recompute"></el-option>
            </el-select>
            <el-button type="primary" size="small" @click="loadSummary">汇总</el-button>
            <el-button type="success" size="small" icon="el-icon-download" @click="exportSummary">导出 Excel</el-button>
          </template>
          <template v-if="hasPeriodType">
            <span class="toolbar-label">口径</span>
            <el-select v-model="periodType" size="small" style="width:110px" @change="doQuery">
              <el-option label="全部" value=""></el-option>
              <el-option label="当月" value="MONTH"></el-option>
              <el-option label="累计" value="CUMULATIVE"></el-option>
            </el-select>
          </template>
          <template v-if="!isRollup">
            <el-input v-model="keyword" placeholder="企业/市州名称" clearable style="width:180px" @keyup.enter.native="doQuery"></el-input>
            <el-button type="primary" size="small" @click="doQuery">查询</el-button>
          </template>
          <el-button size="small" icon="el-icon-full-screen" @click="toggleFullscreen">全屏查看</el-button>
        </div>
        <div class="load-hint">共 {{ total }} 条记录,已加载 {{ records.length }} 条</div>
        <div class="load-hint">共 {{ total }} 条记录,已加载 {{ records.length }} 条<span v-if="isRollup && summaryHint" class="rollup-hint"> | {{ summaryHint }}</span></div>
        <div class="table-wrap" ref="tableWrap">
          <div class="table-area">
            <el-table :data="records" border v-loading="loading" size="small" style="width:100%" height="100%">
@@ -99,6 +131,13 @@
      activeModule: 'freight',
      type: 'h2032',
      period: '',
      periodType: '',
      viewMode: 'detail',
      rollupTypes: [],
      from: '',
      to: '',
      rankMode: 'blank',
      summaryHint: '',
      keyword: '',
      page: 1,
      size: 50,
@@ -110,6 +149,16 @@
    }
  },
  computed: {
    canRollup() {
      return (this.rollupTypes || []).some(t => t.type === this.type)
    },
    isRollup() {
      return this.viewMode === 'rollup' && this.canRollup
    },
    // 规上规下拆分等表有"口径"列(当月/累计),把它做成上方筛选而不是表格列
    hasPeriodType() {
      return (this.columns || []).some(c => c.prop === 'periodType')
    },
    currentTypeLabel() {
      for (const g of this.modules) {
        const t = g.types.find(x => x.value === this.type)
@@ -140,8 +189,10 @@
    // 数据查看统一列过滤:内部标识列不展示;报表期(由筛选条件体现)与所属地区(区划代码,已被市州列替代)不展示
    keepColumn(c) {
      if (!c) return false
      const hiddenProps = ['enterpriseCode', 'unifiedCreditCode', 'reportUnit', 'projectId', 'regionCode', 'reportPeriod']
      const hiddenLabels = ['企业代码', '统一社会信用代码', '填报单位', '项目ID', '所属地区', '报表期']
      // 口径列上移为筛选;拆分表货运量三列源文件未提供(全空)故不展示
      const hiddenProps = ['enterpriseCode', 'unifiedCreditCode', 'reportUnit', 'projectId', 'regionCode', 'reportPeriod',
        'periodType', 'aboveScaleFreight', 'belowScaleFreight', 'totalFreight']
      const hiddenLabels = ['企业代码', '统一社会信用代码', '填报单位', '项目ID', '所属地区', '报表期', '口径']
      if (hiddenProps.indexOf(c.prop) >= 0 || hiddenLabels.indexOf(c.label) >= 0) return false
      return true
    },
@@ -153,9 +204,67 @@
      }
      this.doQuery()
    },
    loadRollupTypes() {
      api.get('/data/rollupTypes').then(res => {
        // 拦截器已返回响应体,故 res.data 即类型数组
        this.rollupTypes = (res && res.data) || []
      }).catch(() => { this.rollupTypes = [] })
    },
    onViewChange() {
      this.records = []
      this.total = 0
      if (this.isRollup) {
        if (!this.to) this.to = this.period || this.defaultMonth()
        if (!this.from) this.from = this.to.substring(0, 4) + '-01'
        this.loadSummary()
      } else {
        this.summaryHint = ''
        this.doQuery()
      }
    },
    defaultMonth() {
      const now = new Date()
      return now.getFullYear() + '-' + ('0' + (now.getMonth() + 1)).slice(-2)
    },
    loadSummary() {
      if (!this.from || !this.to) { this.$message.warning('请先选择起止月'); return }
      this.loading = true
      api.get('/data/summary', { params: { type: this.type, from: this.from, to: this.to, rankMode: this.rankMode }, timeout: 120000 })
        .then(res => {
          const d = res.data || {}
          this.columns = d.columns || []
          this.records = d.records || []
          this.total = this.records.length
          this.summaryHint = (d.label || '') + ' ' + d.from + ' ~ ' + d.to + ' 累计;同比基数 ' + d.lastFrom + ' ~ ' + d.lastTo
            + ';排名口径:' + (d.rankMode === 'recompute' ? '按累计值重排' : '留空')
        })
        .catch(e => { if (!e || !e._toast) this.$message.error('汇总查询失败') })
        .finally(() => { this.loading = false })
    },
    exportSummary() {
      if (!this.from || !this.to) { this.$message.warning('请先选择起止月'); return }
      api.get('/data/export', {
        params: { type: this.type, from: this.from, to: this.to, rankMode: this.rankMode },
        responseType: 'blob', timeout: 180000
      }).then(res => {
        const blob = 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 = '按市州汇总_' + this.type + '_' + this.from + '_' + this.to + '.xlsx'
        document.body.appendChild(a)
        a.click()
        document.body.removeChild(a)
        window.URL.revokeObjectURL(url)
        this.$message.success('汇总表已导出')
      }).catch(() => this.$message.error('导出失败'))
    },
    onTypeSelect() {
      const g = this.modules.find(x => x.types.some(t => t.value === this.type))
      if (g) this.activeModule = g.key
      this.periodType = ''
      this.viewMode = 'detail'
      this.summaryHint = ''
      this.doQuery()
    },
    doQuery() {
@@ -171,13 +280,14 @@
          type: this.type,
          period: this.period || undefined,
          keyword: this.keyword || undefined,
          periodType: this.periodType || undefined,
          page: this.page,
          size: this.size
        }
      }).then(res => {
        const d = res.data || {}
        this.records = d.records || []
        this.columns = (d.columns || []).filter(c => ['enterpriseCode', 'unifiedCreditCode', 'reportUnit'].indexOf(c.prop) < 0 && ['企业代码', '统一社会信用代码', '填报单位'].indexOf(c.label) < 0)
        this.columns = d.columns || []
        this.total = d.total || 0
        if (this.page > 1 && this.records.length === 0) {
          this.page = 1
@@ -213,6 +323,7 @@
          type: this.type,
          period: this.period || undefined,
          keyword: this.keyword || undefined,
          periodType: this.periodType || undefined,
          page: nextPage,
          size: this.size
        }
@@ -226,7 +337,7 @@
        this.records = this.records.concat(addRows)
        this.page = nextPage
        if (d.total != null) this.total = d.total
        if (d.columns && d.columns.length) this.columns = d.columns.filter(c => ['enterpriseCode', 'unifiedCreditCode', 'reportUnit'].indexOf(c.prop) < 0 && ['企业代码', '统一社会信用代码', '填报单位'].indexOf(c.label) < 0)
        if (d.columns && d.columns.length) this.columns = d.columns
      }).catch(e => {
        if (e && e._toast) return
        this.$message.error('加载更多失败')
@@ -285,6 +396,7 @@
    if (q.type) this.type = q.type
    if (q.period) this.period = q.period
    if (q.keyword) this.keyword = q.keyword
    this.loadRollupTypes()
    this.loadData()
    window.addEventListener('scroll', this.onScroll, { passive: true })
    if (this.$refs.tableWrap) {
@@ -312,6 +424,7 @@
#app .toolbar-label { color:#606266; font-weight:600; white-space:nowrap; }
#app .toolbar-sep { width:1px; height:22px; background:#DCDFE6; margin:0 2px; }
#app .load-hint { color:#909399; font-size:12px; margin-bottom:6px; }
#app .rollup-hint { color:#409EFF; }
#app .table-wrap { display:flex; flex-direction:column; height:calc(100vh - 250px); min-height:300px; }
#app .table-area { flex:1 1 auto; min-height:0; overflow:hidden; }
#app .table-area .el-table { width:100%; height:100%; }