LR 3 jaren geleden
bovenliggende
commit
72554c27fd

+ 90 - 0
src/utils/xlsxExport.ts

@@ -0,0 +1,90 @@
+import { ElTableColumn } from 'element-ui/types/table-column'
+import { utils } from 'xlsx'
+import XLSX from 'xlsx-style'
+import { get as getVal } from 'lodash'
+
+function s2ab(s) {
+  var buf = new ArrayBuffer(s.length)
+  var view = new Uint8Array(buf)
+  for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
+  return buf
+}
+
+export const exportXlsx = (
+  name: string = 'download',
+  data: { [x: string]: any }[] = [],
+  cols: Partial<ElTableColumn & { _slot?: boolean }>[] = [],
+  ids: (string | number)[] = []
+) => {
+  const wb = utils.book_new()
+  const validCols = cols.filter((item) => !item._slot && !!item.prop)
+  const header = validCols.map((item) => item.label)
+  const colWidth = header.map((text) => ({ wpx: text.length * 2 * 12 }))
+  const sheetData: { [x: string]: string | number }[] = (() => {
+    const keys = validCols.reduce((acc, { prop, label, formatter }) => {
+      acc[label] = { prop, formatter }
+      return acc
+    }, {} as { [x: string]: { prop: string; formatter: Function } })
+    return data
+      .filter((item) => (ids.length === 0 ? true : ids.includes(item.id)))
+      .map((item) => {
+        const temp = {}
+        Object.keys(keys).forEach((key, index) => {
+          const { prop, formatter } = keys[key]
+          const val = formatter ? formatter(item) : getVal(item, prop)
+          temp[key] = val === null ? '' : val
+
+          const maxLength = 160
+          const chinessCharactersLength = String(temp[key] || '').replace(/[^\u4e00-\u9fa5]/g, '').length
+          const valLength = (String(temp[key] || '').length + chinessCharactersLength) * 12
+
+          const titleLength = colWidth[index].wpx
+
+          const length = valLength > titleLength ? valLength : titleLength
+
+          colWidth[index].wpx = length > maxLength ? maxLength : length
+        })
+        return temp
+      })
+  })()
+
+  const ws = utils.json_to_sheet(sheetData, { header })
+  ws['!cols'] = colWidth
+  ws['!rows'] = header.map(() => ({ hpx: 34 }))
+
+  for (const key in ws) {
+    if (new RegExp('^[A-Za-z0-9]+$').exec(key)) {
+      const isHeader = /^[A-Za-z]+1$/.test(String(key))
+      const border = { style: 'thin', color: { rgb: '666666' } }
+      ws[String(key)].s = {
+        alignment: { horizontal: 'center', vertical: 'center', wrap_text: true },
+        font: {
+          name: 'Microsoft Yahei',
+          sz: isHeader ? 12 : 11,
+          bold: isHeader,
+          color: isHeader ? { rgb: '303133' } : { rgb: '555555' }
+        },
+        fill: isHeader
+          ? { fgColor: { rgb: 'E0EAFB' } }
+          : { fgColor: { rgb: Number(String(key).replace(/\D/g, '')) % 2 === 0 ? 'F3F7FE' : 'FFFFFF' } },
+        border: { top: border, right: border, bottom: border, left: border }
+      }
+    }
+  }
+
+  utils.book_append_sheet(wb, ws, 'Sheet1')
+  const wbout = XLSX.write(wb, { type: 'binary', bookType: 'xlsx', bookSST: false })
+  const blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' })
+  const url = URL.createObjectURL(blob)
+  const a = document.createElement('a')
+  a.download = `${name}.xlsx`
+  a.style.display = 'none'
+  a.href = url
+  document.body.appendChild(a)
+  a.click()
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve(true)
+    }, 2000)
+  })
+}

+ 18 - 4
src/views/spectrum/reform/assessmentReport/widget.vue

@@ -3,20 +3,22 @@
     <div class="content">
       <div class="title">管网健康情况评估报告</div>
       <el-row type="flex" :gutter="15">
-        <el-col :span="12">
+        <el-col :span="12" style="height: 400px">
           <Summary :base="baseData" :health="healthData" :assessment="assessmentData" :loading="loading" />
         </el-col>
-        <el-col :span="12">
+        <el-col :span="12" style="height: 400px">
           <Chart :flaw="flawData" :loading="loading.flaw" />
         </el-col>
       </el-row>
       <Table />
       <div class="table">
         <div class="btns">
-          <el-button type="primary" size="small">下载</el-button>
-          <el-button type="primary" size="small">打印</el-button>
+          <el-button type="primary" size="small" @click="download" :loading="loading.export">下载</el-button>
+          <!-- <el-button type="primary" size="small" v-print="'#assessmentReport'">打印</el-button> -->
         </div>
         <tf-table
+          id="assessmentReport"
+          ref="table"
           :columns="assessmentCols"
           :data="pipes"
           :pagination="pagination"
@@ -51,6 +53,8 @@
     fetchReportPage
   } from '../api/assessment'
   import { assessmentCols } from '../utils'
+  import { exportXlsx } from '@/utils/xlsxExport'
+  import { ElTable } from 'element-ui/types/table'
 
   @Component({ name: 'Statistics', components: { Summary, Chart } })
   export default class Statistics extends Vue {
@@ -69,6 +73,7 @@
     healthData: Partial<IAssessmentHealth>[] = []
     assessmentData: Partial<IAssessmentAssessment>[] = []
     pipes: Partial<IAssessment>[] = []
+    $refs!: { table: ElTable }
 
     async fetchBaseReport() {
       this.loading.base = true
@@ -105,6 +110,9 @@
       try {
         const { result } = await fetchAssessmentReport()
         this.assessmentData = result
+        this.$nextTick(() => {
+          this.$refs.table.doLayout()
+        })
       } catch (error) {
         console.log(error)
       }
@@ -135,6 +143,12 @@
       this.loading.query = false
     }
 
+    async download() {
+      this.loading.export = true
+      await exportXlsx('管网健康情况评估报告', this.pipes, assessmentCols)
+      this.loading.export = false
+    }
+
     onPageChange(pagination) {
       this.pagination = { ...this.pagination, ...pagination }
       this.doQuery()

+ 15 - 9
src/views/spectrum/reform/statistics/ChartItem.vue

@@ -6,9 +6,9 @@
       :init-options="{ height: 400 }"
       autoresize
       style="width: 100%; height: 100%"
-      v-if="option.dataset.some((item) => item.source.length > 0)"
+      v-show="option.dataset.some((item) => item.source.length > 0)"
     />
-    <el-empty :image="emptyImg" v-else>
+    <el-empty :image="emptyImg" v-show="!option.dataset.some((item) => item.source && item.source.length > 0)">
       <template v-slot:description>&nbsp;</template>
     </el-empty>
   </div>
@@ -43,9 +43,9 @@
       const series = this.data
         .map(({ checkCode }, index) => {
           return [
-            { name: 'minValue', displayName: '最大值' },
-            { name: 'maxValue', displayName: '最小值' },
-            { name: 'avgValue', displayName: '平均值' }
+            { name: 'maxValue', displayName: '最大值' },
+            { name: 'avgValue', displayName: '平均值' },
+            { name: 'minValue', displayName: '最小值' }
           ].map((item, typeIndex) => {
             return {
               name: `${this.getCodeName(checkCode)}-${item.displayName}`,
@@ -59,7 +59,7 @@
         })
         .flat()
       return {
-        tooltip: { trigger: 'axis' },
+        tooltip: { trigger: 'axis', valueFormatter: (value) => Math.floor(Number(value) * 100) / 100 },
         calculable: true,
         xAxis: [
           {
@@ -67,7 +67,13 @@
             boundaryGap: false,
             data: [
               ...new Set(dataset.map((item) => item.source.map((item: { xposition: string }) => item.xposition)).flat())
-            ].sort((a, b) => (a > b ? 0 : -1))
+            ].sort((a, b) => {
+              const reg = /(?<=\d{4}年)(\d)(?=月)/
+              const left = a.replace(reg, `0$1`)
+              const right = b.replace(reg, `0$1`)
+
+              return left.localeCompare(right)
+            })
           }
         ],
         yAxis: [{ type: 'value', min: ({ min }) => min }],
@@ -75,8 +81,8 @@
           { type: 'inside', start: 0, end: 100 },
           { start: 0, end: 100 }
         ],
-        legend: this.itemKey === 'monthData' && { show: true, type: 'scroll', width: '80%' },
-        grid: { top: this.itemKey === 'monthData' ? 45 : 15, left: 0, right: 45, bottom: 30, containLabel: true },
+        legend: { show: true, type: 'scroll', width: '80%' },
+        grid: { top: 45, left: 0, right: 45, bottom: 30, containLabel: true },
         toolbox: {
           show: true,
           feature: { saveAsImage: { show: true } },

+ 36 - 14
src/views/spectrum/reform/statistics/DayAndHourChart.vue

@@ -6,17 +6,17 @@
       :init-options="{ height: 400 }"
       autoresize
       style="width: 100%; height: 100%"
-      v-if="option.dataset.some((item) => item.source.length > 0)"
+      v-show="option.dataset.some((item) => item.source.length > 0)"
     />
 
-    <el-empty :image="emptyImg" v-else>
+    <el-empty :image="emptyImg" v-show="!option.dataset.some((item) => item.source && item.source.length > 0)">
       <template v-slot:description>&nbsp;</template>
     </el-empty>
   </div>
 </template>
 
 <script lang="ts">
-  import { Vue, Component, Prop } from 'vue-property-decorator'
+  import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
   import { IStatisticsDayNHour } from '../api/common'
   import emptyImg from '@/assets/icon/null.png'
   import { IPointTarget } from '../../configuration/api/common'
@@ -26,6 +26,7 @@
     @Prop({ type: String }) title!: string
     @Prop({ type: Object, default: () => ({}) }) data!: IStatisticsDayNHour
     @Prop({ type: Array, default: () => [] }) standards!: IPointTarget['showVos']
+    @Prop({ type: String, default: 'rfallTotal' }) rainfallKey!: string
     emptyImg = emptyImg
 
     getCodeName(code) {
@@ -38,29 +39,29 @@
       const dataset = [
         {
           id: 'rainfall',
-          source: (rainFallVos || []).filter((item) => !!item.rfallTotal)
+          source: (rainFallVos || []).filter((item) => !!item[this.rainfallKey])
         },
         ...datas.map(({ checkCode, statisticalData }) => ({
           id: checkCode,
           source: statisticalData
         }))
       ]
-      // 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none'
       const series = dataset
         .map(({ id }, index) => {
           return index === 0
             ? {
                 name: '降雨量',
-                dimensions: ['xposition', 'rfallTotal'],
+                dimensions: ['xposition', this.rainfallKey],
                 datasetIndex: index,
                 type: 'line',
                 symbol: 'circle',
-                smooth: true
+                smooth: true,
+                yAxisIndex: 1
               }
             : [
-                { name: 'minValue', displayName: '最大值' },
-                { name: 'maxValue', displayName: '最小值' },
-                { name: 'avgValue', displayName: '平均值' }
+                { name: 'maxValue', displayName: '最大值' },
+                { name: 'avgValue', displayName: '平均值' },
+                { name: 'minValue', displayName: '最小值' }
               ].map((item, typeIndex) => {
                 return {
                   name: `${this.getCodeName(id)}-${item.displayName}`,
@@ -68,7 +69,8 @@
                   datasetIndex: index,
                   type: 'line',
                   symbol: { '0': 'emptyCircle', '1': 'rect', '2': 'triangle' }[String(typeIndex)],
-                  smooth: true
+                  smooth: true,
+                  yAxisIndex: 0
                 }
               })
         })
@@ -85,21 +87,41 @@
               ...new Set(
                 dataset.map((item) => (item.source as { xposition: string }[]).map((item) => item.xposition)).flat()
               )
-            ].sort((a, b) => (a > b ? 0 : -1))
+            ].sort((a, b) => {
+              const reg = /(?<=\d{4}年)(\d)(?=月)/
+              const left = a.replace(reg, `0$1`)
+              const right = b.replace(reg, `0$1`)
+              return left.localeCompare(right)
+            })
+          }
+        ],
+        yAxis: [
+          { type: 'value', boundaryGap: false },
+          {
+            type: 'value',
+            boundaryGap: false,
+            inverse: true,
+            alignTicks: true,
+            position: 'right',
+            name: '降雨量',
+            nameLocation: 'start'
           }
         ],
-        yAxis: [{ type: 'value', min: ({ min }) => min }],
         dataZoom: dataset.some(({ source }) => source.length > 100) && [
           { type: 'inside', start: 0, end: 100 },
           { start: 0, end: 100 }
         ],
         legend: { show: true, type: 'scroll', width: '80%' },
-        grid: { top: 45, left: 45, right: 45, bottom: 30, containLabel: true },
+        grid: { top: 45, left: 45, right: 45, bottom: 45, containLabel: true },
         toolbox: { show: true, feature: { restore: { show: true }, saveAsImage: { show: true } } },
         dataset,
         series
       }
     }
+    @Watch('option')
+    log(val) {
+      console.log(val)
+    }
   }
 </script>
 

+ 17 - 3
src/views/spectrum/reform/statistics/widget.vue

@@ -59,7 +59,14 @@
       >
         <tf-title slot="title">按月统计</tf-title>
       </ChartItem>
-      <DayAndHourChart type="day" class="full" :data="dayData" v-loading="dataLoading.day" :standards="standards">
+      <DayAndHourChart
+        type="day"
+        class="full"
+        :data="dayData"
+        v-loading="dataLoading.day"
+        :standards="standards"
+        rainfallKey="rfallTotal"
+      >
         <tf-title slot="title">
           <el-row type="flex" align="middle">
             <span style="margin-right: 3em">按天统计</span>
@@ -84,7 +91,14 @@
           </el-row>
         </tf-title>
       </DayAndHourChart>
-      <DayAndHourChart type="day" class="full" :data="hourData" v-loading="dataLoading.hour" :standards="standards">
+      <DayAndHourChart
+        type="day"
+        class="full"
+        :data="hourData"
+        v-loading="dataLoading.hour"
+        :standards="standards"
+        rainfallKey="maxFall"
+      >
         <tf-title slot="title">
           <el-row type="flex" align="middle">
             <span style="margin-right: 3em">按小时统计</span>
@@ -277,7 +291,7 @@
       margin-bottom: 1em;
     }
     .base {
-      font-size: $--font-size-extra-small;
+      font-size: $--font-size-base;
       color: $--color-info;
       span + span {
         margin-left: $gutter * 2;