123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- <template>
- <div class="statstics-wrapper">
- <div
- ref="headerWrapper"
- class="statstics-header"
- >
- <StatisticsHeader
- :title="`${chartsTitle}统计`"
- :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
- },
- customFormItems: {
- type: Array,
- default: () => []
- }
- },
- data() {
- return {
- myChart: null,
- debounceTime: 300,
- chartHeight: '70vh',
- hasChartData: false,
- seriesKey: 'seriesData',
- filters: [],
- tableData: [],
- params: [],
- options: {
- backgroundColor: '#fff',
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'cross',
- crossStyle: {
- color: '#999'
- }
- }
- },
- legend: {
- top: '5%',
- right: '5%',
- icon: 'rect',
- height: 14,
- itemWidth: 14,
- itemHeight: 14,
- itemGap: 30,
- data: [
- this.chartsTitle.replace('量', '数量'),
- // `${this.chartsTitle}量同比`,
- `${this.chartsTitle}环比`
- ],
- textStyle: {
- fontFamily: 'Helvetica, "Microsoft YaHei"',
- color: '#101116'
- }
- },
- grid: {
- top: '15%',
- left: '5%',
- right: '5%',
- bottom: '5%'
- },
- xAxis: {
- data: [],
- axisLine: {
- show: true,
- lineStyle: {
- color: '#000000'
- }
- },
- axisTick: {
- show: false // 隐藏X轴刻度
- },
- axisLabel: {
- fontFamily: 'Helvetica, "Microsoft YaHei"',
- color: '#101116'
- },
- axisPointer: {
- type: 'shadow'
- }
- },
- yAxis: [
- {
- min: 0,
- max: 60000,
- splitLine: {
- lineStyle: {
- type: 'dashed',
- color: '#B0B3C3',
- opacity: 0.5
- }
- },
- axisPointer: {
- label: {
- formatter: ({ value }) => value.toFixed()
- }
- },
- axisLabel: {
- fontFamily: 'Helvetica, "Microsoft YaHei"',
- color: '#101116'
- }
- },
- {
- min: -0.3,
- max: 0.5,
- axisLabel: {
- formatter: value => (value * 100).toFixed(2) + '%',
- fontFamily: 'Helvetica, "Microsoft YaHei"',
- color: '#101116'
- },
- axisPointer: {
- label: {
- formatter: ({ value }) => (value * 100).toFixed(2) + '%'
- }
- },
- splitLine: {
- show: false
- }
- }
- ],
- series: [
- {
- name: this.chartsTitle.replace('量', '数量'),
- type: 'bar',
- z: 2,
- itemStyle: {
- color: '#6682B5'
- },
- barWidth: 40,
- label: {
- show: true,
- position: 'top'
- },
- data: []
- },
- {
- name: `${this.chartsTitle}同比`,
- type: 'line',
- z: 4,
- yAxisIndex: 1,
- symbol: 'circle',
- itemStyle: {
- color: '#F2B849',
- borderColor: '#ffffff',
- borderWidth: 4
- },
- lineStyle: {
- width: 4,
- color: '#F2B849'
- },
- symbolSize: 32,
- tooltip: {
- valueFormatter: value => (value * 100).toFixed(2) + '%'
- },
- data: []
- },
- {
- name: `${this.chartsTitle}环比`,
- type: 'line',
- z: 3,
- yAxisIndex: 1,
- symbol: 'circle',
- itemStyle: {
- color: '#E33D3D',
- borderColor: '#ffffff',
- borderWidth: 4
- },
- lineStyle: {
- width: 4,
- color: '#E33D3D'
- },
- symbolSize: 32,
- tooltip: {
- valueFormatter: value => (value * 100).toFixed(2) + '%'
- },
- data: []
- }
- ]
- }
- }
- },
- computed: {
- ...mapGetters(['sidebar'])
- },
- watch: {
- // 监听数据变化 重绘图形
- options: {
- handler(obj) {
- this.myChart.setOption(obj)
- this.resizeHandler()
- },
- deep: true
- },
- 'sidebar.expand'() {
- this.setChartHeight()
- },
- querySettings: {
- handler({ seriesKey, filters }) {
- if (seriesKey) {
- this.seriesKey = seriesKey
- }
- if (filters?.length) {
- this.filters = filters
- }
- },
- deep: true,
- immediate: true
- }
- },
- mounted() {
- this.setChartHeight()
- this.myChart = this.$echarts.init(document.getElementById('chart'))
- this.myChart.setOption(this.options)
- // 监听页面缩放
- 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: {
- resetDatas() {
- this.hasChartData = false
- this.options.yAxis[0].max = 60000
- this.options.xAxis.data = []
- this.options.series[0].data = []
- this.options.series[2].data = []
- this.options.yAxis[1].min = -0.3
- this.options.yAxis[1].max = 0.5
- },
- getFormData(formData) {
- this.resetDatas()
- let id
- let params = []
- if (formData.range === '基地分公司') {
- if (formData.flightType === '有行李') {
- id = this.querySettings.withBaggageByArea
- } else if (formData.baggageType === '不包含DEL') {
- id = this.querySettings.notDelByArea
- } else if (formData.passengerType[0] === '要客类型') {
- id = this.querySettings.importantByArea
- } else {
- id = this.querySettings.byArea
- }
- params = [formData.interval, formData.area, formData.inOrOut, formData.dateTime[0], formData.dateTime[1]]
- } else {
- if (formData.flightType === '有行李') {
- id = this.querySettings.withBaggageByOther
- } else if (formData.baggageType === '不包含DEL') {
- id = this.querySettings.notDelByOther
- } else if (formData.passengerType[0] === '要客类型') {
- id = this.querySettings.importantByOther
- } else {
- id = this.querySettings.byOther
- }
- params = [formData.interval, formData.range, formData.inOrOut, formData.dateTime[0], formData.dateTime[1]]
- if (formData.airline.length) {
- params.splice(2, 0, formData.airline)
- } else if (formData.airport.length) {
- params.splice(2, 0, formData.airport)
- } else if (formData.terminal !== '') {
- params.splice(2, 0, formData.terminal)
- } else {
- params.splice(2, 0, '全部')
- }
- }
- if (formData.passengerType.length) {
- this.filters = [
- {
- key: formData.passengerType[0],
- value: formData.passengerType[1]
- }
- ]
- }
- this.params = [...params, ...this.filters.map(({ value }) => value)]
- if (params[2] instanceof Array) {
- const paramsList = params[2].map(param => [...params.slice(0, 2), param, ...params.slice(3)])
- this.getMultipleChartsData(id, paramsList)
- } else {
- this.getSingleChartsData(id, params)
- }
- },
- async getMultipleChartsData(id, paramsList) {
- try {
- const listValuesArray = await Promise.all(paramsList.map(params => this.getChartsData(id, params)))
- const listValues = listValuesArray.reduce((preValues, currentValues) => {
- currentValues.forEach(value => {
- const preValue = preValues.find(preValue => preValue.A === value.A)
- if (preValue) {
- preValue[this.seriesKey] += value[this.seriesKey]
- } else {
- preValues.push({
- A: value.A,
- [this.seriesKey]: value[this.seriesKey]
- })
- }
- })
- return preValues
- }, [])
- this.setChartsData(this._.sortBy(listValues, 'A'))
- } catch (error) {
- this.$message.error(error.message)
- }
- },
- async getSingleChartsData(id, params) {
- try {
- const listValues = await this.getChartsData(id, params)
- this.setChartsData(listValues)
- } catch (error) {
- this.$message.error(error.message)
- }
- },
- async getChartsData(id, params) {
- try {
- const {
- code,
- returnData: { listValues },
- message
- } = await TempQuery({
- id,
- dataContent: params
- })
- if (Number(code) === 0) {
- return listValues
- } else {
- return Promise.reject(message || '失败')
- }
- } catch (error) {
- return Promise.reject(error.message || '失败')
- }
- },
- setChartsData(listValues) {
- const xAxisData = []
- const yAxisData = [0]
- const seriesDatas = []
- const filteredList = listValues.filter(element =>
- this.filters.every((key, value) => {
- if (key && value && element[key] !== value) {
- return false
- } else {
- return true
- }
- })
- )
- if (filteredList.length === 0) {
- this.$message.info('未查询到对应数据')
- return
- }
- for (let i = 0; i < filteredList.length; i++) {
- xAxisData.push(filteredList[i].A)
- seriesDatas.push(filteredList[i][this.seriesKey])
- if (i > 0) {
- if (filteredList[i - 1][this.seriesKey] > 0) {
- yAxisData.push(
- (filteredList[i][this.seriesKey] - filteredList[i - 1][this.seriesKey]) /
- filteredList[i - 1][this.seriesKey]
- )
- } else {
- yAxisData.push(0)
- }
- }
- }
- let max = Math.max(...seriesDatas)
- max = Math.ceil(max / 10) * 10
- this.options.yAxis[0].max = max
- this.options.xAxis.data = xAxisData
- this.options.series[0].data = seriesDatas
- this.options.series[2].data = yAxisData
- this.options.yAxis[1].min = (Math.min(...yAxisData) - 0.1).toFixed(2)
- this.options.yAxis[1].max = (Math.max(...yAxisData) + 0.1).toFixed(2)
- this.tableData = [xAxisData, seriesDatas, yAxisData]
- 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 = [['时间', this.chartsTitle.replace('量', '数量'), `${this.chartsTitle}环比`]]
- const transposition = this.tableData[0].map((col, colIndex) => {
- return this.tableData.map((row, rowIndex) => {
- return rowIndex === 2 ? (row[colIndex] * 100).toFixed(2) + '%' : row[colIndex]
- })
- })
- xlsxDatas.push(...transposition)
- // 添加合计行
- if (xlsxDatas.length > 2) {
- const summaryRow = ['合计']
- const colNum = xlsxDatas[0].length
- for (let colIndex = 1; colIndex < colNum; colIndex++) {
- summaryRow[colIndex] = xlsxDatas.reduce((pre, currentRow, rowIndex) => {
- if (colIndex === 1) {
- if (rowIndex === 0) {
- return 0
- } else {
- return pre + currentRow[colIndex]
- }
- } else {
- return pre
- }
- }, '')
- }
- xlsxDatas.push(summaryRow)
- }
- // 计算列宽
- 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 = key.match(reg)
- if (match) {
- const rowIndex = match[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'
- }
- const cellValue = sheet[key].v
- const isNumber = /^[^0]/.test(cellValue) && !isNaN(parseFloat(cellValue)) && isFinite(cellValue)
- const isPercentage = /^[0-9]+(\.[0-9]+){0,1}\%$/.test(cellValue)
- if (isNumber) {
- sheet[key] = {
- ...sheet[key],
- t: 'n',
- z: '0',
- v: Number(cellValue)
- }
- }
- if (isPercentage) {
- sheet[key] = {
- ...sheet[key],
- t: 'n',
- z: '0.00%',
- v: parseFloat(cellValue)
- }
- }
- 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>
|