123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589 |
- <template>
- <div
- v-loading="loading"
- element-loading-text="拼命加载中"
- element-loading-spinner="el-icon-loading"
- element-loading-background="rgba(0, 0, 0, 0.8)"
- class="statstics-wrapper"
- >
- <div
- ref="headerWrapper"
- class="statstics-header"
- >
- <StatisticsHeader
- :title="`${chartsTitle}统计`"
- :data="formData"
- :items="formItems"
- :custom-items="customFormItems"
- @getFormData="getFormData"
- @export="exportHandler"
- />
- </div>
- <div class="statstics-content">
- <div
- id="chart"
- class="statistics-chart"
- :style="{ height: chartHeight }"
- />
- </div>
- </div>
- </template>
- <script>
- import StatisticsHeader from './statisticsHeader.vue'
- import { TempQuery } from '@/api/temp'
- import { mapGetters } from 'vuex'
- import * as XLSX from 'xlsx'
- import XLSX_STYLE from 'xlsx-style'
- import FileSaver from 'file-saver'
- export default {
- name: 'CommonBarStatisticsCharts',
- components: { StatisticsHeader },
- props: {
- chartsTitle: {
- type: String,
- required: true,
- },
- querySettings: {
- type: Object,
- required: true,
- },
- categories: {
- type: Array,
- validator(arr) {
- return (
- arr &&
- arr.every(
- category =>
- (category instanceof Object && 'name' in category) ||
- typeof category === 'string'
- )
- )
- },
- },
- formData: {
- type: Object,
- required: true,
- },
- formItems: {
- type: Array,
- default: () => [],
- },
- customFormItems: {
- type: Array,
- default: () => [],
- },
- pieTitle: {
- type: String,
- default: '总件数',
- },
- },
- data() {
- return {
- loading: false,
- myChart: null,
- debounceTime: 300,
- chartHeight: '70vh',
- hasChartData: false,
- tableData: [],
- params: [],
- options: {
- backgroundColor: '#ffffff',
- tooltip: {
- trigger: 'item',
- },
- title: {
- text: '',
- // 副标题
- subtext: '0',
- // 主副标题间距
- itemGap: 24,
- x: 'left',
- y: 'center',
- left: '30%',
- top: '40%',
- textAlign: 'center',
- // 主标题样式
- textStyle: {
- fontSize: '48',
- color: '#ffffff',
- fontWeight: 'bold',
- fontFamily: 'Microsoft YaHei',
- },
- // 副标题样式
- subtextStyle: {
- fontSize: '80',
- color: '#ffffff',
- fontWeight: 'bold',
- },
- },
- legend: {
- show: true,
- left: '60%',
- x: 'right',
- y: 'center',
- icon: 'rect',
- itemWidth: 20,
- itemHeight: 20,
- formatter: name => this.legendFormatter(name),
- textStyle: {
- backgroundColor: 'transparent',
- lineHeight: 0,
- rich: {
- chartsTitle: {
- width: 200,
- lineHeight: 100,
- fontSize: 32,
- fontFamily: 'Microsoft YaHei',
- fontWeight: 'bold',
- color: '#101116',
- padding: [0, 1000, 0, -20],
- },
- name: {
- fontSize: 20,
- fontFamily: 'Microsoft YaHei',
- fontWeight: 'bold',
- color: '#101116',
- lineHeight: 100,
- },
- label: {
- fontSize: 16,
- fontFamily: 'Microsoft YaHei',
- color: '#101116',
- },
- value: {
- width: 96,
- fontSize: 16,
- fontFamily: 'Helvetica',
- fontWeight: 'bold',
- color: '#101116',
- },
- ratio: {
- width: 80,
- fontSize: 16,
- fontFamily: 'Helvetica',
- fontWeight: 'bold',
- color: '#101116',
- },
- wrap: {
- padding: [0, 40, 0, 0],
- },
- },
- },
- selected: {
- [this.chartsTitle]: false,
- },
- },
- series: [
- {
- name: '',
- type: 'pie',
- left: '30%',
- width: 560,
- height: 560,
- center: [0, '60%'],
- radius: ['60%', '90%'],
- avoidLabelOverlap: false,
- label: {
- show: false,
- position: 'center',
- },
- emphasis: {
- label: {
- show: false,
- fontSize: '40',
- fontWeight: 'bold',
- },
- },
- labelLine: {
- show: false,
- },
- data: [],
- },
- {
- name: '总数',
- type: 'pie',
- left: '30%',
- width: 560,
- height: 560,
- center: [0, '60%'],
- radius: ['0%', '50%'],
- avoidLabelOverlap: false,
- itemStyle: {
- normal: {
- color: '#101116',
- },
- },
- label: {
- show: false,
- position: 'center',
- },
- // 自定义中心内容的话需要把这个关闭
- emphasis: {
- label: {
- show: false,
- },
- },
- labelLine: {
- show: false,
- },
- data: [],
- },
- ],
- },
- totalCount: [{ value: 0 }],
- categoryDatas: [],
- }
- },
- computed: {
- ...mapGetters(['sidebar']),
- },
- watch: {
- pieTitle: {
- handler(val) {
- this.options.title.text = val
- },
- immediate: true,
- },
- // 监听数据变化 重绘图形
- options: {
- handler(obj) {
- this.myChart.setOption(obj)
- this.resizeHandler()
- },
- deep: true,
- },
- categories: {
- handler(arr) {
- this.categoryDatas = arr.map(category => {
- if (category instanceof Object) {
- return {
- ...category,
- value: 0,
- }
- } else {
- return {
- name: category,
- key: category,
- value: 0,
- }
- }
- })
- },
- deep: true,
- immediate: true,
- },
- 'sidebar.expand'() {
- this.setChartHeight()
- },
- },
- mounted() {
- this.setChartHeight()
- this.myChart = this.$echarts.init(document.getElementById('chart'))
- this.options.series[0].data = [
- {
- name: this.chartsTitle,
- key: this.chartsTitle,
- value: null,
- },
- ...this.categoryDatas,
- ]
- this.options.legend.data = [
- {
- name: this.chartsTitle,
- icon: 'none',
- },
- ...this.categoryDatas.map(({ name }, index) => ({ name })),
- ]
- this.options.series[1].data = this.totalCount
- this.myChart.setOption(this.options)
- this.myChart.on('legendselectchanged', ({ name }) => {
- if (name === this.chartsTitle) {
- this.myChart.dispatchAction({
- type: 'legendUnSelect',
- name,
- })
- }
- })
- // 监听页面缩放
- this.debouncedChartHeightSetter = this._.debounce(
- this.setChartHeight,
- this.debounceTime
- )
- window.addEventListener('resize', this.debouncedChartHeightSetter)
- },
- beforeDestroy() {
- // 销毁实例和移除监听
- window.removeEventListener('resize', this.debouncedChartHeightSetter)
- if (this.myChart) {
- this.myChart.dispose()
- this.myChart = null
- }
- },
- methods: {
- legendFormatter(name) {
- const index = this.categoryDatas.findIndex(
- category => category.name === name
- )
- if (index === -1) {
- return `{chartsTitle|${name}}`
- } else {
- const value = this.categoryDatas[index].value
- const ratio =
- value && this.totalCount.value
- ? ((value / this.totalCount.value) * 100).toFixed(2) + '%'
- : '0%'
- const richString = `{name|${name}}\n{label|数量:}{value|${value}}{label|占比:}{ratio|${ratio}}`
- return index % 2 ? richString + '{wrap| }' : richString
- }
- },
- resetDatas() {
- this.hasChartData = false
- this.categoryDatas.forEach(category => {
- category && (category.value = 0)
- })
- this.options.title.subtext = '0'
- this.options.series[1].data[0].value = 0
- },
- getFormData(formData) {
- this.resetDatas()
- const params = JSON.parse(JSON.stringify(formData))
- ;[params.fd1, params.fd2] = params.dateTime
- delete params.dateTime
- this.params = Object.values(params)
- const paramsList = [params]
- this.getMultipleChartsData(this.querySettings.serviceId, paramsList)
- },
- async getMultipleChartsData(serviceId, paramsList) {
- this.loading = true
- try {
- const listValuesArray = await Promise.all(
- paramsList.map(params => this.getChartsData(serviceId, params))
- )
- this.tableData = this._.sortBy(listValuesArray.flat(), 'fdt')
- const categories = this.categoryDatas.map(category => category.key)
- const listValues = listValuesArray.reduce(
- (preValues, currentValues) => {
- currentValues.forEach(value => {
- const preValue = preValues.find(
- preValue => preValue.fdt === value.fdt
- )
- if (preValue) {
- categories.forEach(key => {
- preValue[key] += value[key] ?? 0
- })
- } else {
- const valuesObj = {
- location: value.location,
- fdt: value.fdt,
- }
- categories.forEach(key => {
- valuesObj[key] = value[key] ?? 0
- })
- preValues.push(valuesObj)
- }
- })
- return preValues
- },
- []
- )
- // console.log(listValues)
- this.setChartsData(this._.sortBy(listValues, 'fdt'))
- } catch (error) {
- this.$message.error(error.message)
- }
- this.loading = false
- },
- async getChartsData(serviceId, params) {
- try {
- const {
- code,
- returnData: listValues,
- message,
- } = await TempQuery({
- serviceId,
- dataContent: params,
- })
- if (String(code) === '0') {
- return listValues.map(obj => ({
- ...obj,
- location: params.air_line || params.airport || '',
- }))
- } else {
- throw new Error(message)
- }
- } catch (error) {
- return Promise.reject(error.message || '失败')
- }
- },
- setChartsData(listValues) {
- if (listValues.length === 0) {
- this.$message.info('未查询到对应数据')
- return
- }
- let totalCount = 0
- listValues.forEach(element => {
- this.categoryDatas.forEach(category => {
- if (element[category.key]) {
- category.value += element[category.key]
- totalCount += element[category.key]
- }
- })
- })
- this.options.title.subtext = totalCount.toString()
- this.totalCount.value = totalCount
- this.hasChartData = true
- },
- setChartHeight() {
- const topBarHeight = 80
- const headerBlankHeight = 24
- const tabsWrapperHeight = 62
- const headerHeight = this.$refs['headerWrapper'].offsetHeight
- const footerBlankHeight = 24
- this.chartHeight = `calc(100vh - ${
- topBarHeight +
- headerBlankHeight +
- tabsWrapperHeight +
- headerHeight +
- footerBlankHeight
- }px)`
- this.$nextTick(() => {
- this.resizeHandler()
- })
- },
- resizeHandler() {
- if (this.myChart) {
- this.myChart.resize()
- }
- },
- exportHandler() {
- if (!this.hasChartData) {
- this.$message.warning('请查询后再进行导出')
- return
- }
- // const myCanvas = this.myChart._dom.querySelectorAll('canvas')[0]
- // const image = myCanvas.toDataURL('image/png')
- // const $a = document.createElement('a')
- // $a.setAttribute('href', image)
- // $a.setAttribute('download', `${this.chartsTitle}统计.png`)
- // $a.click()
- const xlsxDatas = [['时间', '位置', '分类', '数量', '占比']]
- xlsxDatas.push(
- ...this.tableData
- .map(element =>
- this.categoryDatas.map(category => [
- element['fdt'],
- element['location'],
- category.name,
- element[category.key],
- `${(
- (element[category.key] / this.totalCount.value) *
- 100
- ).toFixed(2)}%`,
- ])
- )
- .flat(1)
- )
- xlsxDatas.push(['合计', '', '', this.totalCount.value, ''])
- // 计算列宽
- const columnWidths = []
- xlsxDatas.forEach((row, rowIndex) => {
- // 计算每一列宽度,考虑换行
- row.forEach((cell, columnIndex) => {
- const cellWidth = Math.max(
- ...cell
- .toString()
- .split('\n')
- .map(cellRow =>
- cellRow.split('').reduce((pre, curr) => {
- const letterSize = curr.charCodeAt(0) > 255 ? 2 : 1
- return pre + letterSize
- }, 0)
- )
- )
- if (
- (!columnWidths[columnIndex] && cellWidth > 0) ||
- cellWidth > columnWidths[columnIndex]
- ) {
- columnWidths[columnIndex] = cellWidth
- }
- })
- })
- // 生成表格
- const sheet = XLSX.utils.aoa_to_sheet(xlsxDatas)
- // 添加列宽度
- sheet['!cols'] = columnWidths.map(width => ({
- wch: width + 2,
- }))
- // 样式
- const borderStyle = {
- style: 'medium',
- color: {
- rgb: 'FFFFFF',
- },
- }
- const reg = /^[A-Z]+([\d]+$)/
- for (const key in sheet) {
- const match = reg.test(key)
- if (match) {
- const rowIndex = reg.exec(key)[1]
- let cellStyle = {
- alignment: {
- horizontal: 'center',
- vertical: 'center',
- wrapText: true,
- },
- }
- if (Number(rowIndex) === 1) {
- cellStyle = {
- ...cellStyle,
- border: {
- top: borderStyle,
- right: borderStyle,
- bottom: borderStyle,
- left: borderStyle,
- },
- font: {
- color: {
- rgb: 'FFFFFF',
- },
- },
- fill: {
- fgColor: {
- rgb: '3366FF',
- },
- },
- }
- } else {
- cellStyle.alignment.horizontal = 'left'
- }
- sheet[key].s = cellStyle
- }
- }
- // 表格数据转换
- const workBook = XLSX.utils.book_new()
- XLSX.utils.book_append_sheet(workBook, sheet, this.chartsTitle)
- const tableWrite = XLSX_STYLE.write(workBook, {
- bookType: 'xlsx',
- bookSST: true,
- type: 'buffer',
- cellStyles: true,
- })
- // 下载表格
- const fileName = `${this.chartsTitle}统计-${this.params.join('-')}.xlsx`
- FileSaver.saveAs(
- new Blob([tableWrite], { type: 'application/octet-stream' }),
- fileName
- )
- },
- },
- }
- </script>
- <style lang="scss" scoped>
- .statistics-chart {
- width: 100%;
- }
- </style>
|