|
@@ -0,0 +1,422 @@
|
|
|
+<template>
|
|
|
+ <div class="analysis-chart">
|
|
|
+ <!-- 类似项目标书编制周期分布 -->
|
|
|
+ <div v-if="bidCycle.show">
|
|
|
+ <div class="chart-title">类似项目标书编制周期分布</div>
|
|
|
+ <div class="chart-main">
|
|
|
+ <column-bar-chart :options="bidCycle.options" :datas="bidCycle.data" :onReady="bidCycle.onReady"></column-bar-chart>
|
|
|
+ <div class="chart-tips">注:标书编制周期是指招标文件发放之日至投标截止时间(开标)之间的时间间隔,标书编制周期越长,准备时间越充分。</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 类似项目预算分布 -->
|
|
|
+ <div v-if="budget.show">
|
|
|
+ <div class="chart-title">类似项目预算分布</div>
|
|
|
+ <div class="chart-main">
|
|
|
+ <hot-chart :datas="budget.data" :cycle="info.budget" :options="budget.options"></hot-chart>
|
|
|
+ <div class="chart-tips">注:少数缺失的项目预算,用中标金额补充;平均折扣率=(全部项目预算-全部中标金额)/全部项目预算,仅统计预算和中标金额同时存在的项目。</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 类似项目区域分布 -->
|
|
|
+ <div v-if="area.show">
|
|
|
+ <div class="chart-title">类似项目区域分布</div>
|
|
|
+ <div class="chart-main">
|
|
|
+ <map-chart :datas="area.data" :options="area.options"></map-chart>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 类似项目采购单位类型分布 -->
|
|
|
+ <div v-if="buyclass.show">
|
|
|
+ <div class="chart-title">类似项目采购单位类型分布</div>
|
|
|
+ <pie-chart :height="'328px'" :options="buyclass.options" :datas="buyclass.data"></pie-chart>
|
|
|
+ <div class="chart-tips">注:各采购单位类型占比以采购规模来计算,最多展示占比排名前十的采购单位类型。</div>
|
|
|
+ </div>
|
|
|
+ <!-- 类似项目热点中标企业 -->
|
|
|
+ <div v-if="hotWin.show">
|
|
|
+ <div class="chart-title">类似项目热点中标企业</div>
|
|
|
+ <hot-win-list v-if="hotWin.flag" :datas="hotWin.data"></hot-win-list>
|
|
|
+ </div>
|
|
|
+ <!-- 类似项目评标专家 -->
|
|
|
+ <div v-if="expert.show">
|
|
|
+ <div class="chart-title">类似项目评标专家</div>
|
|
|
+ <blue-progress-chart v-if="expert.flag" from="result" :datas="expert.data"></blue-progress-chart>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script>
|
|
|
+import ColumnBarChart from '@/components/chart/ColumnBarChart'
|
|
|
+import HotChart from '@/components/chart/HotChart'
|
|
|
+import MapChart from '@/components/chart/MapChart'
|
|
|
+import PieChart from '@/components/chart/PieChart'
|
|
|
+import HotWinList from './AnalysisList'
|
|
|
+import BlueProgressChart from '@/components/chart/BlueProgressChart'
|
|
|
+import { moneyUnit } from '@/utils/'
|
|
|
+export default {
|
|
|
+ name: 'analysis-chart',
|
|
|
+ props: ['active', 'datas', 'info'],
|
|
|
+ components: {
|
|
|
+ ColumnBarChart,
|
|
|
+ HotChart,
|
|
|
+ MapChart,
|
|
|
+ PieChart,
|
|
|
+ HotWinList,
|
|
|
+ BlueProgressChart
|
|
|
+ },
|
|
|
+ data () {
|
|
|
+ return {
|
|
|
+ // 类似项目标书编制周期分布
|
|
|
+ bidCycle: {
|
|
|
+ show: false,
|
|
|
+ data: {
|
|
|
+ columns: ['编制周期', '项目数量'],
|
|
|
+ rows: []
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ height: '320px',
|
|
|
+ colors: ['#05A6F3'],
|
|
|
+ setting: {
|
|
|
+ xAxisName: ['标书编制周期'],
|
|
|
+ yAxisName: ['类似项目数量(个)']
|
|
|
+ },
|
|
|
+ config: this.bidCycleConfig,
|
|
|
+ onReady: this.onBarReady
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 类似项目预算分布
|
|
|
+ budget: {
|
|
|
+ show: false,
|
|
|
+ flag: false,
|
|
|
+ data: [],
|
|
|
+ options: {
|
|
|
+ tooltip: {
|
|
|
+ formatter: this.budgetConfig
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 类似项目区域分布
|
|
|
+ area: {
|
|
|
+ show: false,
|
|
|
+ data: {
|
|
|
+ columns: ['省份', '类似项目数量', '类似项目规模'],
|
|
|
+ rows: []
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ height: '570px',
|
|
|
+ colors: ['#05a6f3'],
|
|
|
+ config: this.areaConfig
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 类似项目采购单位类型
|
|
|
+ buyclass: {
|
|
|
+ show: false,
|
|
|
+ data: [],
|
|
|
+ options: {
|
|
|
+ tooltip: {
|
|
|
+ formatter: this.buyClassConfig
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 类似项目热点中标企业
|
|
|
+ hotWin: {
|
|
|
+ show: false,
|
|
|
+ flag: false,
|
|
|
+ data: []
|
|
|
+ },
|
|
|
+ // 类似项目评标专家
|
|
|
+ expert: {
|
|
|
+ show: false,
|
|
|
+ flag: false,
|
|
|
+ data: []
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ active: {
|
|
|
+ handler (newVal) {
|
|
|
+ if (newVal === '2') {
|
|
|
+ this.initChartData()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ deep: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {},
|
|
|
+ created () {},
|
|
|
+ mounted () {},
|
|
|
+ methods: {
|
|
|
+ // 处理图表数据
|
|
|
+ initChartData () {
|
|
|
+ this.formatBidCycleData(this.datas.bidcycle_ranges)
|
|
|
+ this.formatBudgetData(this.datas.budgetAnalysis)
|
|
|
+ this.formatAreaData(this.datas.group_area)
|
|
|
+ this.formatBuyClassData(this.datas.group_buyerclass)
|
|
|
+ this.formatHotWinData(this.datas.winnerAmount)
|
|
|
+ this.formatExpertData(this.datas.reviewExperts)
|
|
|
+ },
|
|
|
+ // 处理编制周期数据
|
|
|
+ formatBidCycleData (data) {
|
|
|
+ if (!data) return
|
|
|
+ const arr = []
|
|
|
+ data.forEach((item) => {
|
|
|
+ arr.push({
|
|
|
+ '编制周期': item.key, // eslint-disable-line
|
|
|
+ '项目数量': item.doc_count // eslint-disable-line
|
|
|
+ })
|
|
|
+ })
|
|
|
+ this.bidCycle.data.rows = arr
|
|
|
+ // 数据都为0 隐藏
|
|
|
+ var countArr = this.bidCycle.data.rows.map((v) => {
|
|
|
+ return v['项目数量']
|
|
|
+ })
|
|
|
+ const max = Math.max.apply(null, countArr)
|
|
|
+ if (max === 0) {
|
|
|
+ this.bidCycle.show = false
|
|
|
+ }
|
|
|
+ this.bidCycle.show = true
|
|
|
+ },
|
|
|
+ // 处理类似项目预算分布数据
|
|
|
+ formatBudgetData (arr) {
|
|
|
+ if (!arr) return
|
|
|
+ let data = []
|
|
|
+ data = arr.map((v, i) => {
|
|
|
+ v.avg = v.avg === null ? '0' : v.avg
|
|
|
+ const x = i % 10
|
|
|
+ const y = 10 - Math.ceil((i + 1) / 10)
|
|
|
+ return [x, y, v.avg, v.doc_count, v.key]
|
|
|
+ })
|
|
|
+ this.budget.data = data
|
|
|
+ this.budget.show = true
|
|
|
+ this.budget.flag = true
|
|
|
+ },
|
|
|
+ formatAreaData (data) {
|
|
|
+ if (!data) return
|
|
|
+ const rows = []
|
|
|
+ data.forEach((item) => {
|
|
|
+ rows.push({
|
|
|
+ /* eslint-disable */
|
|
|
+ '省份': item.key,
|
|
|
+ '类似项目数量': item.doc_count,
|
|
|
+ '类似项目规模': moneyUnit(item.bidamount_sum)
|
|
|
+ /* eslint-enable */
|
|
|
+ })
|
|
|
+ })
|
|
|
+ this.area.data.rows = rows
|
|
|
+ this.area.show = true
|
|
|
+ },
|
|
|
+ // 处理类似项目采购单位类型分布数据
|
|
|
+ formatBuyClassData (data) {
|
|
|
+ if (!data) return
|
|
|
+ const arr = []
|
|
|
+ // 降序排列
|
|
|
+ data.sort((a, b) => {
|
|
|
+ return b.doc_money - a.doc_money
|
|
|
+ })
|
|
|
+ data.forEach((item) => {
|
|
|
+ arr.push(item.key, item.doc_money, item.doc_count, item.avg, item.main)
|
|
|
+ })
|
|
|
+ const normal = ['行业', '类似项目规模', '类似项目数量', '平均折扣率', '是否当前项目']
|
|
|
+ const newArr = this.arrTrans(5, arr)
|
|
|
+ newArr.unshift(normal)
|
|
|
+ this.buyclass.data = newArr
|
|
|
+ this.buyclass.show = true
|
|
|
+ },
|
|
|
+ // 处理类似项目热点中标企业数据
|
|
|
+ formatHotWinData (data) {
|
|
|
+ if (!data) return
|
|
|
+ this.hotWin.show = true
|
|
|
+ this.hotWin.data = data
|
|
|
+ this.hotWin.flag = true
|
|
|
+ },
|
|
|
+ formatExpertData (data) {
|
|
|
+ if (!data) return
|
|
|
+ const arr = []
|
|
|
+ data.forEach((v) => {
|
|
|
+ v.parent = v.doc_count / data[0].doc_count * 100 + '%'
|
|
|
+ arr.push({
|
|
|
+ name: v.key,
|
|
|
+ count: v.doc_count,
|
|
|
+ parent: v.parent
|
|
|
+ })
|
|
|
+ })
|
|
|
+ this.expert.show = true
|
|
|
+ this.expert.data = arr
|
|
|
+ this.expert.flag = true
|
|
|
+ },
|
|
|
+ /* *********** 配置项部分 *********** */
|
|
|
+ // 类似项目标书编制周期分布配置
|
|
|
+ bidCycleConfig (options) {
|
|
|
+ // 处理一下当前项目编制周期>=60天时,不显示当前编制周期
|
|
|
+ // console.log(this.info.bidcycle, '传来的编制周期')
|
|
|
+ var curCycle = this.info.bidcycle >= 60 ? null : this.info.bidcycle
|
|
|
+ var data = this.bidCycle.data.rows
|
|
|
+ if (!data || data.length <= 0) return
|
|
|
+ const arr = []
|
|
|
+ for (var i = 0; i < data.length; i++) {
|
|
|
+ arr.push(data[i]['编制周期'].replace('天', '').split('-'))
|
|
|
+ }
|
|
|
+ const curIndex = this.getArrayIndex(arr, curCycle, 'cycle')
|
|
|
+ console.log('当前编制周期所在下标:', curIndex, curCycle)
|
|
|
+ options.yAxis[0].name = ''
|
|
|
+ options.series[0].markPoint.data = curCycle ? [{ name: '当前项目标书编制周期', coord: [curIndex, 0.5], value: arr[curIndex].join('-') }] : []
|
|
|
+ options.yAxis[0].minInterval = 1
|
|
|
+ options.tooltip.axisPointer = {
|
|
|
+ type: 'line',
|
|
|
+ lineStyle: {
|
|
|
+ color: '#FF9F40',
|
|
|
+ width: 2
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return options
|
|
|
+ },
|
|
|
+ // 类似项目预算分布配置
|
|
|
+ budgetConfig (params) {
|
|
|
+ const data = params.value[4]
|
|
|
+ const newRange = data.split('-')
|
|
|
+ const sRange = newRange[0]
|
|
|
+ const mRange = newRange[1]
|
|
|
+ let totalRange
|
|
|
+ if (sRange === '100.0亿元以上') {
|
|
|
+ totalRange = sRange
|
|
|
+ } else {
|
|
|
+ totalRange = sRange + '-' + mRange
|
|
|
+ }
|
|
|
+ let tip = ''
|
|
|
+ const count = '<span>项目数量:' + params.value[3] + '个</span></br>'
|
|
|
+ const rate = (typeof params.value[2] === 'number' && !isNaN(params.value[2])) ? '<span>平均节支率:' + (params.value[2] * 100).fixed(2) + '%</span></br>' : ''
|
|
|
+ const budget = '<span>采购规模:' + totalRange + '</span></br>'
|
|
|
+ tip = budget + count + rate
|
|
|
+ return tip
|
|
|
+ },
|
|
|
+ // 类似项目区域分布配置
|
|
|
+ areaConfig (options) {
|
|
|
+ const arr = this.area.data.rows
|
|
|
+ const maxNum = Math.max.apply(Math, arr.map((o) => { return o['类似项目数量'] }))
|
|
|
+ options.graphic[options.graphic.length - 1].children[0].style.text = maxNum > 100 ? maxNum : 100
|
|
|
+ options.graphic[options.graphic.length - 1].children[1].style.text = 1
|
|
|
+ options.graphic[0].children[0].style.text = '项目数量(个)'
|
|
|
+ options.visualMap.min = 1
|
|
|
+ options.visualMap.max = maxNum < 100 ? 100 : maxNum
|
|
|
+ options.tooltip.formatter = (params) => {
|
|
|
+ const data = options.series
|
|
|
+ let scaleVal = ''
|
|
|
+ let counts = ''
|
|
|
+ for (let i = 0; i < data.length; i++) {
|
|
|
+ for (let j = 0; j < data[i].data.length; j++) {
|
|
|
+ if (data[i].name === '类似项目数量') {
|
|
|
+ if (params.name === data[i].data[j].name) {
|
|
|
+ counts = data[i].data[j].value
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (data[i].name === '类似项目规模') {
|
|
|
+ if (params.name === data[i].data[j].name) {
|
|
|
+ scaleVal = data[i].data[j].value
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let tip = ''
|
|
|
+ const area = counts && counts !== '' ? params.name + '</br>' : ''
|
|
|
+ const count = counts && counts !== '' ? '<span>类似项目数量:' + counts + '个</span></br>' : ''
|
|
|
+ const scale = scaleVal && scaleVal !== '' ? '<span>类似项目规模:' + scaleVal + '</span></br>' : ''
|
|
|
+ tip = area + count + scale
|
|
|
+ return tip
|
|
|
+ }
|
|
|
+ return options
|
|
|
+ },
|
|
|
+ buyClassConfig (params) {
|
|
|
+ let tip = ''
|
|
|
+ const data = params.data
|
|
|
+ params.marker = '<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:' + params.color + '"></span>'
|
|
|
+ const percent = '<span style="padding-left:13px;">采购规模占比:' + params.percent + '%</span></br>'
|
|
|
+ const scale = '<span style="padding-left:13px;">类似项目规模:' + (data[1] / 10000).fixed(2) + '万元</span></br>'
|
|
|
+ const count = '<span style="padding-left:13px;">类似项目数量:' + data[2] + '个</span></br>'
|
|
|
+ const rate = (typeof data[3] === 'number' && !isNaN(data[3])) ? '<span style="padding-left:13px;">平均折扣率:' + (data[3] * 100).fixed(2) + '%</span></br>' : ''
|
|
|
+ tip = params.marker + params.name + '<br/>' + percent + scale + count + rate
|
|
|
+ return tip
|
|
|
+ },
|
|
|
+ /* 公共函数 */
|
|
|
+ // 获取数组下标
|
|
|
+ getArrayIndex (arr, dst, type) {
|
|
|
+ let i = arr.length
|
|
|
+ switch (type) {
|
|
|
+ case 'cycle':
|
|
|
+ while (i--) {
|
|
|
+ if (dst >= arr[i][0] && dst < arr[i][1]) {
|
|
|
+ return i
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'count':
|
|
|
+ while (i--) {
|
|
|
+ if (dst === arr[i]) {
|
|
|
+ return i
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break
|
|
|
+ case 'pie':
|
|
|
+ while (i--) {
|
|
|
+ for (const key in arr[i]) {
|
|
|
+ if (dst === arr[i][key]) {
|
|
|
+ return i
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ },
|
|
|
+ // 一维数组转换为二维数组
|
|
|
+ arrTrans (num, arr) {
|
|
|
+ const newArr = []
|
|
|
+ arr.forEach((item, index) => {
|
|
|
+ // 计算该元素为第几个素组内
|
|
|
+ const page = Math.floor(index / num)
|
|
|
+ // 判断是否存在
|
|
|
+ if (!newArr[page]) {
|
|
|
+ newArr[page] = []
|
|
|
+ }
|
|
|
+ newArr[page].push(item)
|
|
|
+ })
|
|
|
+ return newArr
|
|
|
+ },
|
|
|
+ onBarReady ($event, name) {
|
|
|
+ const data = this.bidCycle.data.rows
|
|
|
+ if (!data || data.length <= 0) return
|
|
|
+ const arr = []
|
|
|
+ for (var i = 0; i < data.length; i++) {
|
|
|
+ arr.push(data[i]['项目数量'])
|
|
|
+ }
|
|
|
+ const maxNum = Math.max.apply(null, arr)
|
|
|
+ const curIndex = this.getArrayIndex(arr, maxNum, 'count')
|
|
|
+ console.log('最大值:' + maxNum + ',索引:' + curIndex)
|
|
|
+ setTimeout(() => {
|
|
|
+ $event.dispatchAction({
|
|
|
+ type: 'showTip',
|
|
|
+ seriesIndex: 0,
|
|
|
+ dataIndex: curIndex ? curIndex : 0 // eslint-disable-line
|
|
|
+ })
|
|
|
+ }, 20)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+<style lang="scss" scoped>
|
|
|
+.analysis-chart{
|
|
|
+ background: #fff;
|
|
|
+ .chart-title{
|
|
|
+ padding: 32px 0 16px;
|
|
|
+ font-size: 18px;
|
|
|
+ color: #1d1d1d;
|
|
|
+ line-height: 28px;
|
|
|
+ }
|
|
|
+ .chart-main{
|
|
|
+ width: 840px;
|
|
|
+ margin: 0 auto;
|
|
|
+ }
|
|
|
+ .chart-tips{
|
|
|
+ padding: 10px 0 32px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999999;
|
|
|
+ line-height: 20px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|