123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504 |
- <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 { Query } from '@/api/dataIntegration'
- 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,
- required: true
- },
- customFormItems: {
- type: Array,
- default: () => []
- },
- pieTitle: {
- type: String,
- default: '总件数'
- }
- },
- data () {
- return {
- 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: [],
- categoryKey: 'specialnum',
- seriesKey: 'special'
- }
- },
- 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(categoryName => ({
- name: categoryName,
- value: 0
- }))
- this.categoryDatas.unshift({
- name: this.chartsTitle,
- value: null
- })
- },
- deep: true,
- immediate: true
- },
- querySettings: {
- handler ({ categoryKey, seriesKey }) {
- if (seriesKey) {
- this.seriesKey = seriesKey
- }
- if (categoryKey) {
- this.categoryKey = categoryKey
- }
- },
- deep: true,
- immediate: true
- },
- 'sidebar.expand' () {
- this.setChartHeight()
- }
- },
- mounted () {
- this.setChartHeight()
- this.myChart = this.$echarts.init(document.getElementById('chart'))
- this.options.series[0].data = this.categoryDatas
- this.options.legend.data = this.categoryDatas.map(({ name }, index) => {
- if (index === 0) {
- return {
- name,
- icon: 'none'
- }
- } else {
- return {
- 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 === 0) {
- 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()
- let id
- let params = []
- if (formData.range === '基地分公司') {
- id = this.querySettings.byArea
- params = [formData.interval, formData.area, formData.inOrOut, formData.dateTime[0], formData.dateTime[1]]
- } else if (formData.range !== '基地分公司' && formData.range !== '') {
- id = this.querySettings.byOther
- params = [formData.interval, formData.range, formData.inOrOut, formData.dateTime[0], formData.dateTime[1]]
- if (formData.airline === '' && formData.airport === '' && formData.terminal === '') {
- params.splice(2, 0, '全部')
- }
- if (formData.airline !== '') {
- params.splice(2, 0, formData.airline)
- }
- if (formData.airport !== '') {
- params.splice(2, 0, formData.airport)
- }
- if (formData.terminal !== '') {
- params.splice(2, 0, formData.terminal)
- }
- }
- this.params = params
- this.getChartsData(id, params)
- },
- async getChartsData (id, params) {
- try {
- const {
- code,
- returnData: { listValues },
- message
- } = await Query({
- id,
- dataContent: params
- })
- if (Number(code) === 0) {
- if (listValues.length === 0) {
- this.$message.info('未查询到对应数据')
- return
- }
- let totalCount = 0
- listValues.forEach(element => {
- this.categoryDatas.forEach(category => {
- if (element[this.categoryKey]?.includes(category.name)) {
- category.value += element[this.seriesKey]
- totalCount += element[this.seriesKey]
- }
- })
- })
- this.options.title.subtext = totalCount.toString()
- this.totalCount.value = totalCount
- this.tableData = listValues
- this.hasChartData = true
- } else {
- this.$message.error(message || '失败')
- }
- } catch (error) {
- this.$message.error("失败");
- }
- },
- 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 => [
- element['A'],
- element['location'],
- element[this.categoryKey],
- element[this.seriesKey],
- ((element[this.seriesKey] / this.totalCount.value) * 100).toFixed(2) + '%'
- ])
- )
- 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>
|