UnitChart.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909
  1. <template>
  2. <div class="unit-chart">
  3. <!-- 年度项目统计 -->
  4. <div v-if="years.show">
  5. <div class="chart-title">年度项目统计</div>
  6. <bar-chart id="years" :options="years.options" :datas="years.data"></bar-chart>
  7. </div>
  8. <!-- 月度采购规模 -->
  9. <div v-if="monthScale.show">
  10. <div class="chart-title">月度采购规模统计</div>
  11. <line-chart ref="monthScale" :options="monthScale.options" :datas="monthScale.data"></line-chart>
  12. <div class="chart-tips text-center">注:采购规模指中标金额,少数缺失的中标金额,用项目预算补充。</div>
  13. </div>
  14. <!-- 采购规模分布 -->
  15. <div v-if="buyScaleFb.show">
  16. <div class="chart-title">采购规模分布</div>
  17. <hot-chart v-if="buyScaleFb.flag" ref="hotChart" :datas="buyScaleFb.data"></hot-chart>
  18. <div class="chart-tips">注:平均节支率=(全部项目预算-全部中标金额)/全部项目预算,是指价格减让部分与原价的比率,仅统计预算和中标金额同时存在的项目。</div>
  19. </div>
  20. <!-- 各类招标方式占比 -->
  21. <div v-if="bidType.show">
  22. <div class="chart-title">各类招标方式占比</div>
  23. <double-bar-chart :datas="bidType.data"></double-bar-chart>
  24. </div>
  25. <!-- 各行业项目平均节支率 -->
  26. <div v-if="savingsRate.show">
  27. <div class="chart-title">各行业项目平均节支率</div>
  28. <line-chart v-if="savingsRate.flag" :options="savingsRate.options" :datas="savingsRate.data"></line-chart>
  29. </div>
  30. <!-- 各行业项目规模占比 -->
  31. <div v-if="projectScale.show">
  32. <div class="chart-title">各行业项目规模占比</div>
  33. <pie-chart v-if="projectScale.flag" :height="'326px'" :options="projectScale.options" :datas="projectScale.data"></pie-chart>
  34. </div>
  35. <!-- 重点合作企业 -->
  36. <div v-if="client.data.length > 0">
  37. <div class="chart-title">重点合作企业</div>
  38. <progress-chart ref="clientChart" :datas="client.data"></progress-chart>
  39. </div>
  40. <!-- 合作企业注册资本分布 -->
  41. <div v-if="capital.show">
  42. <div class="chart-title">合作企业注册资本分布</div>
  43. <bar-chart v-if="capital.flag" id="capital" :options="capital.options" :datas="capital.data"></bar-chart>
  44. </div>
  45. <!-- 合作企业年龄分布 -->
  46. <div v-if="age.show">
  47. <div class="chart-title">合作企业年龄分布</div>
  48. <bar-chart v-if="age.flag" id="age" :options="age.options" :datas="age.data"></bar-chart>
  49. </div>
  50. <!-- 合作企业注册地分布 -->
  51. <div v-if="area.show">
  52. <div class="chart-title">合作企业注册地分布</div>
  53. <map-chart v-if="area.flag" id="area" :options="area.options" :datas="area.data"></map-chart>
  54. </div>
  55. <!-- 重点合作代理机构 -->
  56. <div v-if="agency.show">
  57. <div class="chart-title">重点合作代理机构</div>
  58. <agency-chart v-if="agency.flag" :datas="agency.data"></agency-chart>
  59. </div>
  60. </div>
  61. </template>
  62. <script>
  63. import BarChart from '@/components/chart/BarLineChart'
  64. import HotChart from '@/components/chart/HotChart'
  65. import LineChart from '@/components/chart/LineChart'
  66. import DoubleBarChart from '@/components/chart/DoubleBarChart'
  67. import pieChart from '@/components/chart/PieChart'
  68. import ProgressChart from '@/components/chart/ProgressChart'
  69. import MapChart from '@/components/chart/MapChart'
  70. import AgencyChart from '@/components/chart/AgencyChart'
  71. import { getUnitChart } from '@/api/modules/'
  72. import { getParam, bSort, moneyUnit } from '@/utils/'
  73. export default {
  74. name: 'unit-chart',
  75. components: {
  76. BarChart,
  77. HotChart,
  78. LineChart,
  79. DoubleBarChart,
  80. pieChart,
  81. ProgressChart,
  82. MapChart,
  83. AgencyChart
  84. },
  85. data () {
  86. return {
  87. // 年度项目统计
  88. years: {
  89. show: true,
  90. data: {
  91. columns: [],
  92. rows: []
  93. },
  94. options: {
  95. height: '326px',
  96. colors: [new this.$echarts.graphic.LinearGradient(
  97. 0, 1, 0, 0,
  98. [
  99. { offset: 1, color: '#2ABED1' },
  100. { offset: 0.5, color: '#2ABED1' },
  101. { offset: 0, color: '#8DE0EB' }
  102. ], false
  103. ), '#FF9F40'],
  104. config: this.configYears,
  105. settings: {
  106. showLine: ['项目金额'],
  107. axisSite: { right: ['项目金额'] }
  108. }
  109. }
  110. },
  111. // 月度采购规模
  112. monthScale: {
  113. show: true,
  114. data: {
  115. columns: [],
  116. rows: []
  117. },
  118. options: {
  119. height: '326px',
  120. colors: ['#05A6F3', '#0BD991', '#FF9F40'],
  121. config: this.configMonthScle
  122. }
  123. },
  124. // 采购规模分布
  125. buyScaleFb: {
  126. show: true,
  127. flag: false,
  128. data: []
  129. },
  130. // 各类招标方式占比
  131. bidType: {
  132. show: true,
  133. data: []
  134. },
  135. // 各行业项目平均节支率
  136. savingsRate: {
  137. show: true,
  138. flag: false,
  139. data: {
  140. columns: [],
  141. rows: []
  142. },
  143. options: {
  144. height: '326px',
  145. colors: ['#FB483D', '#05A6F3', '#0BD991', '#FF9F40', '#8E6DF2', '#C0C4CC'],
  146. config: this.configSavingsRate
  147. }
  148. },
  149. // 各行业项目规模占比(饼图)
  150. projectScale: {
  151. show: true,
  152. flag: false,
  153. data: [],
  154. // 传入的配置
  155. options: {
  156. tooltip: {
  157. formatter: event
  158. }
  159. }
  160. },
  161. // 重点合作企业
  162. client: {
  163. show: true,
  164. flag: false,
  165. data: []
  166. },
  167. // 合作企业注册资本分布
  168. capital: {
  169. show: true,
  170. flag: false,
  171. data: {
  172. columns: [],
  173. rows: [],
  174. customData: []
  175. },
  176. options: {
  177. height: '320px',
  178. colors: ['#05a6f3', '#FF9F40'],
  179. config: this.configCapital,
  180. settings: {
  181. showLine: ['累计采购规模'],
  182. axisSite: { right: ['累计采购规模'] }
  183. }
  184. }
  185. },
  186. // 合作企业年龄分布
  187. age: {
  188. show: true,
  189. flag: false,
  190. data: {
  191. columns: ['企业年龄', '企业数量'],
  192. rows: [],
  193. customData: []
  194. },
  195. options: {
  196. height: '320px',
  197. colors: ['#05a6f3'],
  198. config: this.configAge
  199. }
  200. },
  201. // 合作企业年龄分布
  202. area: {
  203. show: false,
  204. flag: false,
  205. data: {
  206. columns: ['企业注册地', '企业数量', '累计采购规模', '采购项目数量', '平均节支率'],
  207. rows: [],
  208. customData: []
  209. },
  210. options: {
  211. height: '570px',
  212. colors: ['#05a6f3'],
  213. config: this.configArea
  214. }
  215. },
  216. // 重点合作代理机构
  217. agency: {
  218. show: false,
  219. flag: false,
  220. data: []
  221. },
  222. reqCount: 0,
  223. timer: null
  224. }
  225. },
  226. computed: {},
  227. mounted () {
  228. this.getChartData()
  229. },
  230. methods: {
  231. // 画像数据
  232. async getChartData () {
  233. const res = await getUnitChart({
  234. buyer: decodeURIComponent(getParam('entName'))
  235. })
  236. if (res.error_code === 0) {
  237. this.reqCount++
  238. if (res.data && Object.keys(res.data).length > 0) {
  239. const info = {}
  240. info.province = res.data.province ? res.data.province.replace(/省|市|自治区|特别行政区|壮族|回族|维吾尔/g, '') : '--'
  241. info.city = res.data.city
  242. info.buyerClass = res.data.buyerclass ? res.data.buyerclass : '--'
  243. info.start = new Date(Number(res.data.timeRange.start + '000')).pattern('yyyy/MM/dd')
  244. info.end = new Date(Number(res.data.timeRange.end + '000')).pattern('yyyy/MM/dd')
  245. info.buyerCount = res.data.project_count ? res.data.project_count + '个' : '--'
  246. info.winnerCount = res.data.winner_count ? res.data.winner_count + '个' : '--'
  247. info.otherWinner = res.data.otherProvincesWinnerCount ? res.data.otherProvincesWinnerCount + '个' : '--'
  248. info.buyerScale = res.data.bidamount_count ? moneyUnit(res.data.bidamount_count) : '--'
  249. info.fail_count = res.data.fail_count ? res.data.fail_count + '条' : '--'
  250. console.log(info, 'info')
  251. this.$emit('base-info', info)
  252. // 年度项目统计图表数据
  253. if (res.data.yearData && Object.keys(res.data.yearData).length > 0) {
  254. this.formatYearsData(res.data.yearData)
  255. } else {
  256. this.years.show = false
  257. }
  258. // 月度采购规模
  259. if (res.data.monthData && Object.keys(res.data.monthData).length > 0) {
  260. this.formatMonthScaleData(res.data.monthData)
  261. } else {
  262. this.monthScale.show = false
  263. }
  264. // 采购规模分布
  265. if (res.data.moneyRange && res.data.moneyRange.length > 0) {
  266. const countArr = res.data.moneyRange.map((v) => {
  267. return v.project_count
  268. })
  269. const max = Math.max.apply(null, countArr)
  270. if (max === 0) {
  271. this.buyScaleFb.show = false
  272. }
  273. this.buyScaleFb.data = res.data.moneyRange
  274. this.buyScaleFb.flag = true
  275. // this.formatHotChartData(res.data.moneyRange)
  276. } else {
  277. this.buyScaleFb.show = false
  278. }
  279. // 各类招标方式占比
  280. if (res.data.bidtypeData && res.data.bidtypeData.length > 0) {
  281. this.bidType.data = this.formatterBarChart(res.data.bidtypeData)
  282. } else {
  283. this.bidType.show = false
  284. }
  285. // 各行业项目平均节支率
  286. if (res.data.rate && Object.keys(res.data.rate).length > 0) {
  287. this.formatSavingsRateData(res.data.rate)
  288. this.savingsRate.flag = true
  289. } else {
  290. this.savingsRate.show = false
  291. }
  292. // 各行业项目规模占比
  293. if (res.data.top12 && res.data.top12.length > 0) {
  294. this.formatProjectScaleData(res.data.top12)
  295. this.projectScale.flag = true
  296. } else {
  297. this.projectScale.show = false
  298. }
  299. // 重点合作企业
  300. if (res.data.topShow && res.data.topShow.length > 0) {
  301. this.client.data = this.formatClientData(res.data.topShow)
  302. this.client.flag = true
  303. this.client.show = true
  304. } else {
  305. this.client.show = false
  306. }
  307. // 合作企业注册资本分布
  308. if (res.data.withCapitalData && res.data.withCapitalData.length > 0) {
  309. this.formatCapitalData(res.data.withCapitalData)
  310. this.capital.flag = true
  311. } else {
  312. this.capital.show = false
  313. }
  314. // 合作企业年龄分布
  315. if (res.data.withEstablishData && res.data.withEstablishData) {
  316. this.formatAgeData(res.data.withEstablishData)
  317. this.age.flag = true
  318. } else {
  319. this.age.show = false
  320. }
  321. // 合作企业注册地分布
  322. if (res.data.withAreaData && res.data.withAreaData.length > 0) {
  323. this.formatAreaData(res.data.withAreaData)
  324. this.area.flag = true
  325. this.area.show = true
  326. } else {
  327. this.area.show = false
  328. }
  329. // 重点合作代理机构
  330. if (res.data.topAgencyData && res.data.topAgencyData.length > 0) {
  331. const data = res.data.topAgencyData
  332. data.forEach((v, i) => {
  333. v.last_with_time = new Date(Number(v.last_with_time + '000')).pattern('yyyy/MM/dd')
  334. v.parent = v.project_count / data[0].project_count * 100 + '%'
  335. })
  336. this.agency.data = data
  337. this.agency.flag = true
  338. this.agency.show = true
  339. } else {
  340. this.agency.show = false
  341. }
  342. } else {
  343. if (this.reqCount < 3) {
  344. this.timer = setTimeout(() => {
  345. this.getChartData()
  346. })
  347. } else {
  348. console.log('请求了仍获取不到图表数据,不再请求')
  349. clearTimeout(this.timer)
  350. }
  351. }
  352. }
  353. },
  354. /* ******** 配置项部分 ******* */
  355. configYears (options) {
  356. options.yAxis[1].axisLabel.formatter = (value, index) => {
  357. return value.toString().replace(/,/, '')
  358. }
  359. options.legend.show = true
  360. options.yAxis[1].axisLabel.show = true // 显示右侧y轴刻度
  361. var maxCountList = this.years.data.rows.map((v) => {
  362. return v['项目数量']
  363. })
  364. var maxPriceList = this.years.data.rows.map((v) => {
  365. return v['项目金额']
  366. })
  367. let maxCount = Math.ceil(Math.max.apply(null, maxCountList)).toString()
  368. let maxPrice = Math.ceil(Math.max.apply(null, maxPriceList)).toString()
  369. maxPrice = Math.ceil(maxPrice / (Math.pow(10, maxPrice.length - 1))) * Math.pow(10, maxPrice.length - 1)
  370. maxCount = Math.ceil(maxCount / (Math.pow(10, maxCount.length - 1))) * Math.pow(10, maxCount.length - 1)
  371. const item = options.yAxis
  372. item[0].min = 0
  373. item[1].min = 0
  374. item[0].max = maxCount
  375. item[0].interval = Math.ceil(maxCount / 5)
  376. item[1].max = maxPrice
  377. item[1].interval = Math.ceil((maxPrice - 0) / 5)
  378. options.tooltip.formatter = (params) => {
  379. let tip = ''
  380. for (let i = 0; i < params.length; i++) {
  381. // 因柱状图颜色为渐变色,此处获取到的柱状图颜色,css不能识别,需单独设置小圆点的颜色
  382. params[0].marker = '<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:#2ABED1"></span>'
  383. if (i === 0) {
  384. tip = tip + params[i].marker + params[i].seriesName + ':' + params[i].value + '个' + '<br/>'
  385. } else if (i === 1) {
  386. tip = tip + params[i].marker + params[i].seriesName + ':' + params[i].value + '万元' + '<br/>'
  387. } else {}
  388. }
  389. tip += '<div style="padding-top:2px;text-align:center;color:#9B9CA3;">' + ' - ' + params[0].name + ' - ' + '</div>'
  390. return tip
  391. }
  392. options.legend.formatter = (name) => {
  393. if (name === '项目金额') {
  394. name = name + '(万元)-右轴'
  395. }
  396. if (name === '项目数量') {
  397. name = name + '(个)'
  398. }
  399. return name
  400. }
  401. return options
  402. },
  403. // 月度采购规模数据及修改配置
  404. configMonthScle (options) {
  405. options.tooltip.formatter = (params) => {
  406. let tip = ''
  407. for (let i = 0; i < params.length; i++) {
  408. params[i].marker = '<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:' + params[i].color + ';"></span>'
  409. if (!params[i].value[1] && params[i].value[1] !== 0) {
  410. tip = tip + ''
  411. } else {
  412. tip = tip + params[i].marker + params[i].seriesName + params[i].name + '采购规模:' + params[i].value[1].toString().replace(/,/, '') + '万元' + '<br/>'
  413. }
  414. }
  415. tip += '<div style="padding-top:2px;text-align:center;color:#9B9CA3;">' + ' - ' + params[0].name + ' - ' + '</div>'
  416. return tip
  417. }
  418. return options
  419. },
  420. configSavingsRate (options) {
  421. const data = this.savingsRate.data.rows
  422. const arr = []
  423. for (let i = 0; i < data.length; i++) {
  424. var obj = data[i]
  425. for (var key in obj) {
  426. if (key !== '日期' && obj[key]) {
  427. arr.push(obj[key])
  428. }
  429. }
  430. }
  431. let maxRate = Math.ceil(Math.max.apply(null, arr)).toString()
  432. maxRate = Math.ceil(maxRate / (Math.pow(10, maxRate.length - 1))) * Math.pow(10, maxRate.length - 1)
  433. options.xAxis[0].axisLabel.margin = 12
  434. options.yAxis[0].axisLabel.formatter = '{value}%'
  435. options.yAxis[0].min = 0
  436. options.yAxis[0].max = maxRate
  437. options.yAxis[0].interval = Math.ceil(maxRate / 5)
  438. options.grid = {
  439. top: 20,
  440. right: 16,
  441. bottom: 70,
  442. left: 10
  443. }
  444. options.legend.textStyle.padding = [0, 18, 0, 0]
  445. options.legend.bottom = 10
  446. options.tooltip.formatter = (params) => {
  447. let tip = ''
  448. for (let i = 0; i < params.length; i++) {
  449. params[i].marker = '<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:' + params[i].color + ';"></span>'
  450. if (params[i].value[1] === undefined || params[i].value[1] === null || isNaN(params[i].value[1])) {
  451. tip = tip + params[i].marker + params[i].seriesName + ':--<br/>'
  452. } else {
  453. tip = tip + params[i].marker + params[i].seriesName + ':' + params[i].value[1] + '%<br/>'
  454. }
  455. }
  456. tip += '<div style="padding-top:2px;text-align:center;color:#9B9CA3;">' + ' - ' + params[0].name + ' - ' + '</div>'
  457. return tip
  458. }
  459. return options
  460. },
  461. configCapital (options) {
  462. const customData = this.capital.data.customData
  463. var maxCountList = this.capital.data.rows.map((v) => {
  464. return v['企业数量']
  465. })
  466. var maxPriceList = this.capital.data.rows.map((v) => {
  467. return v['累计采购规模']
  468. })
  469. var maxLeft = Math.max.apply(null, maxCountList)
  470. var maxRight = Math.max.apply(null, maxPriceList)
  471. maxLeft = Math.ceil(maxLeft).toString()
  472. maxRight = Math.ceil(maxRight).toString()
  473. var l = Math.ceil(maxLeft / (Math.pow(10, maxLeft.length - 1))) * Math.pow(10, maxLeft.length - 1)
  474. var r = Math.ceil(maxRight / (Math.pow(10, maxRight.length - 1))) * Math.pow(10, maxRight.length - 1)
  475. var item = options.yAxis
  476. item[0].min = 0
  477. item[1].min = 0
  478. item[0].max = l
  479. item[0].interval = Math.ceil(l / 5)
  480. item[1].max = Math.ceil(r)
  481. item[1].interval = Math.ceil((r - 0) / 5)
  482. item[0].minInterval = 1
  483. options.tooltip.axisPointer.shadowStyle.color = 'rgba(5, 166, 243,0.1)'
  484. options.tooltip.formatter = (params) => {
  485. const obj = {}
  486. let tip = ''
  487. customData.forEach((v) => {
  488. if (v['注册资本'] === params[0].name) {
  489. for (const key in v) {
  490. obj[key] = v[key]
  491. }
  492. }
  493. })
  494. const regMoney = '<span>注册资本:' + obj['注册资本'] + '</span></br>'
  495. const count = '<span>企业数量:' + obj['企业数量'] + '个</span></br>'
  496. const scale = '<span>累计采购规模:' + obj['累计采购规模'] + '万元</span></br>'
  497. const pCount = '<span>采购项目数量:' + obj['采购项目数量'] + '个</span></br>'
  498. const rate = (typeof obj['平均节支率'] === 'number' && !isNaN(obj['平均节支率'])) ? '<span>平均节支率:' + (obj['平均节支率'] * 100).fixed(2) + '%</span></br>' : ''
  499. tip = regMoney + count + scale + pCount + rate
  500. return tip
  501. }
  502. options.series.forEach((item) => {
  503. if (item.name === '累计采购规模') {
  504. item.type = 'line'
  505. } else {
  506. item.type = 'bar'
  507. item.barWidth = 12
  508. }
  509. })
  510. options.legend.formatter = (name) => {
  511. if (name === '累计采购规模') {
  512. name = '累计采购规模(万元)-右轴'
  513. }
  514. if (name === '企业数量') {
  515. name = '企业数量(个)'
  516. }
  517. return name
  518. }
  519. return options
  520. },
  521. configAge (options) {
  522. for (let i = 0; i < options.series.length; i++) {
  523. options.series[i].barWidth = 20
  524. options.series[i].stack = '企业年龄分布'
  525. }
  526. const arr = this.age.data.customData
  527. options.yAxis[0].minInterval = 1
  528. options.xAxis[0].name = '企业年龄(年)'
  529. options.xAxis[0].axisLabel.interval = 0
  530. options.tooltip.axisPointer.shadowStyle.color = 'rgba(5, 166, 243,0.1)'
  531. options.tooltip.formatter = (params) => {
  532. const obj = {}
  533. let tip = ''
  534. arr.forEach((v) => {
  535. if (v['企业年龄'] === params[0].name) {
  536. for (const key in v) {
  537. obj[key] = v[key]
  538. }
  539. }
  540. })
  541. const regMoney = '<span>企业年龄:' + obj['企业年龄'] + '</span></br>'
  542. const count = '<span>企业数量:' + obj['企业数量'] + '个</span></br>'
  543. const scale = '<span>累计采购规模:' + obj['累计采购规模'] + '万元</span></br>'
  544. const pCount = '<span>采购项目数量:' + obj['采购项目数量'] + '个</span></br>'
  545. const rate = (typeof obj['平均节支率'] === 'number' && !isNaN(obj['平均节支率'])) ? '<span>平均节支率:' + (obj['平均节支率'] * 100).fixed(2) + '%</span></br>' : ''
  546. tip = regMoney + count + scale + pCount + rate
  547. return tip
  548. }
  549. return options
  550. },
  551. configArea (options) {
  552. const arr = this.area.data.customData
  553. const maxNum = Math.max.apply(Math, arr.map((o) => { return o['企业数量'] }))
  554. options.visualMap.min = 1
  555. options.visualMap.max = maxNum < 100 ? 100 : maxNum
  556. options.graphic[0].children[0].style.text = '企业数量(个)'
  557. options.graphic[options.graphic.length - 1].children[0].style.text = maxNum > 100 ? maxNum : 100
  558. options.graphic[options.graphic.length - 1].children[1].style.text = 1
  559. options.tooltip.formatter = (params) => {
  560. const obj = {}
  561. let tip = ''
  562. arr.forEach((v) => {
  563. if (v['企业注册地'] === params.name) {
  564. for (const key in v) {
  565. obj[key] = v[key]
  566. }
  567. }
  568. })
  569. if (Object.keys(obj).length > 0) {
  570. const regArea = '<span>企业注册地:' + obj['企业注册地'] + '</span></br>'
  571. const count = '<span>企业数量:' + obj['企业数量'] + '个</span></br>'
  572. const scale = '<span>累计采购规模:' + obj['累计采购规模'] + '万元</span></br>'
  573. const pCount = '<span>采购项目数量:' + obj['采购项目数量'] + '个</span></br>'
  574. const rate = (typeof obj['平均节支率'] === 'number' && !isNaN(obj['平均节支率'])) ? '<span>平均节支率:' + (obj['平均节支率'] * 100).fixed(2) + '%</span></br>' : ''
  575. tip = regArea + count + scale + pCount + rate
  576. } else {
  577. tip = ''
  578. }
  579. return tip
  580. }
  581. return options
  582. },
  583. /* ******** 数据格式化部分 ******* */
  584. // 格式化年度项目统计数据
  585. formatYearsData (data) {
  586. if (!data) return
  587. const rows = []
  588. let count = 0
  589. /* eslint-disable */
  590. for (const key in data) {
  591. rows.push({
  592. '年份': key,
  593. '项目数量': data[key].Count,
  594. '项目金额': (data[key].Money / 10000).fixed(0)
  595. })
  596. count += data[key].Count
  597. }
  598. /* eslint-enable */
  599. if (count > 0) {
  600. this.years.data.columns = ['年份', '项目数量', '项目金额']
  601. this.years.data.rows = rows
  602. } else {
  603. this.years.show = false
  604. }
  605. },
  606. // 格式化月度采购规模数据
  607. formatMonthScaleData (data) {
  608. if (!data) return
  609. const rows = []
  610. const columns = ['月份']
  611. let count = 0
  612. const years = []
  613. for (var key in data) {
  614. years.push(parseInt(key))
  615. }
  616. years.sort((a, b) => { return a - b })
  617. years.forEach(function (item) {
  618. columns.push(item + '年')
  619. })
  620. for (let i = 1; i <= 12; i++) {
  621. const columnsItem = {}
  622. columns.forEach((item) => {
  623. let value = ''
  624. if (item === '月份') {
  625. value = i + '月'
  626. } else if (/年/g.test(item)) {
  627. if (data[item.slice(0, -1)]) {
  628. value = data[item.slice(0, -1)][i]
  629. value = (value / 10000).fixed(2)
  630. count += value
  631. }
  632. }
  633. columnsItem[item] = value
  634. })
  635. rows.push(columnsItem)
  636. }
  637. if (count > 0) {
  638. this.monthScale.data.columns = columns
  639. this.monthScale.data.rows = rows
  640. } else {
  641. this.monthScale.show = false
  642. }
  643. },
  644. // 格式化采购规模分布(热力图)数据
  645. // 格式化各类招标方式占比数据(双向柱图)
  646. formatterBarChart (data) {
  647. if (!data) return
  648. data.forEach((item) => {
  649. item.money = (item.money / 10000).fixed(2)
  650. item.moneyShare = (item.moneyShare * 100).fixed(2)
  651. item.numShare = (item.numShare * 100).fixed(2)
  652. switch (item.type) {
  653. case '招标':
  654. item.type = '公开招标'
  655. break
  656. case '邀标':
  657. item.type = '邀请招标'
  658. break
  659. case '询价':
  660. item.type = '询价采购'
  661. break
  662. case '单一':
  663. item.type = '单一来源采购'
  664. break
  665. case '竞价':
  666. item.type = '竞价采购'
  667. break
  668. case '竞谈':
  669. item.type = '竞争性谈判'
  670. break
  671. }
  672. })
  673. const newData = data
  674. const maxLeftNum = Math.max.apply(Math, data.map((o) => { return o.numShare }))
  675. const maxRightNum = Math.max.apply(Math, data.map((o) => { return o.moneyShare }))
  676. newData.forEach((v) => {
  677. v.lPercent = (v.numShare / maxLeftNum * 100).fixed(2)
  678. v.rPercent = (v.moneyShare / maxRightNum * 100).fixed(2)
  679. })
  680. return newData
  681. },
  682. // 格式化平均节支率数据
  683. formatSavingsRateData (data) {
  684. if (!data) return
  685. const rows = []
  686. const columns = ['日期', '全部行业']
  687. let count = 0
  688. for (const key in data) {
  689. if (key !== '全部行业') {
  690. columns.push(key)
  691. }
  692. }
  693. const industryArr = []
  694. for (const key in data['全部行业']) {
  695. industryArr.push(key)
  696. }
  697. industryArr.sort((a, b) => { return a - b })
  698. industryArr.forEach((item) => {
  699. const rowsItem = {
  700. '日期': item + '年' // eslint-disable-line
  701. }
  702. for (const k in data) {
  703. count += (data[k][item] * 100)
  704. rowsItem[k] = data[k][item] == null ? null : (data[k][item] * 100).fixed(2)
  705. }
  706. rows.push(rowsItem)
  707. })
  708. if (count > 0) {
  709. this.savingsRate.data.columns = columns
  710. this.savingsRate.data.rows = rows
  711. } else {
  712. this.savingsRate.show = false
  713. }
  714. },
  715. // 格式化各行业项目规模占比数据
  716. formatProjectScaleData (data) {
  717. if (!data) return
  718. const arr = []
  719. // 降序排列
  720. data.sort((a, b) => {
  721. return b.bidamount_share - a.bidamount_share
  722. })
  723. data.forEach((item) => {
  724. arr.push(item.scopeclassName, item.bidamount_share, item.bidamount_count, item.project_count, item.rate_avg)
  725. })
  726. const normal = ['行业', '采购规模占比', '采购规模', '采购项目数量', '平均节支率']
  727. var newArr = this.arrTrans(5, arr)
  728. newArr.unshift(normal)
  729. this.projectScale.data = newArr
  730. this.projectScale.options.tooltip.formatter = (params) => {
  731. var tip = ''
  732. var data = params.data
  733. params.marker = '<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:' + params.color + '"></span>'
  734. var percent = '<span style="padding-left:13px;">采购规模占比:' + (data[1] * 100).fixed(2) + '%</span></br>'
  735. var scale = '<span style="padding-left:13px;">采购规模:' + moneyUnit(data[2].fixed(2)) + '</span></br>'
  736. var count = '<span style="padding-left:13px;">采购项目数量:' + data[3] + '个</span></br>'
  737. var rate = (typeof data[4] === 'number' && !isNaN(data[4])) ? '<span style="padding-left:13px;">平均节支率:' + (data[4] * 100).fixed(2) + '%</span></br>' : ''
  738. tip = params.marker + params.name + '<br/>' + percent + scale + count + rate
  739. return tip
  740. }
  741. },
  742. // 格式化注册资本数据
  743. formatCapitalData (data) {
  744. if (!data) return
  745. const columns = ['注册资本', '企业数量', '累计采购规模']
  746. const rows = []
  747. const capData = []
  748. data.forEach((item) => {
  749. /* eslint-disable */
  750. rows.push({
  751. '注册资本': this.formatMoneyRange(item.key),
  752. '企业数量': item.ent_count,
  753. '累计采购规模': (item.money_count /10000).fixed(2)
  754. })
  755. capData.push({
  756. '注册资本': this.formatMoneyRange(item.key),
  757. '企业数量': item.ent_count,
  758. '累计采购规模': (item.money_count /10000).fixed(2),
  759. '采购项目数量': item.project_count,
  760. '平均节支率': item.rate_avg
  761. })
  762. /* eslint-enable */
  763. })
  764. this.capital.data.columns = columns
  765. this.capital.data.rows = rows
  766. this.capital.data.customData = capData
  767. },
  768. // 格式化企业年龄分布数据
  769. formatAgeData (data) {
  770. if (!data) return
  771. const ageRows = []
  772. const customRows = []
  773. data.forEach((item) => {
  774. if (item.key === '0_1') {
  775. item.key = '<1'
  776. }
  777. if (item.key === '40') {
  778. item.key = '≥40'
  779. }
  780. /* eslint-disable */
  781. ageRows.push({
  782. '企业年龄': item.key.replace('_', '-'),
  783. '企业数量': item.ent_count
  784. })
  785. customRows.push({
  786. '企业年龄': item.key.replace('_', '-'),
  787. '企业数量': item.ent_count,
  788. '累计采购规模': (item.money_count /10000).fixed(2),
  789. '采购项目数量': item.project_count,
  790. '平均节支率': item.rate_avg
  791. })
  792. /* eslint-enable */
  793. })
  794. this.age.data.rows = ageRows
  795. this.age.data.customData = customRows
  796. },
  797. // 格式化企业注册地分布数据
  798. formatAreaData (data) {
  799. if (!data) return
  800. const rows = []
  801. const custom = []
  802. data.forEach((item) => {
  803. /* eslint-disable */
  804. rows.push({
  805. '企业注册地': item.area_name,
  806. '企业数量': item.ent_count
  807. })
  808. custom.push({
  809. '企业注册地': item.area_name,
  810. '企业数量': item.ent_count,
  811. '累计采购规模': (item.money_count / 10000).fixed(2),
  812. '采购项目数量': item.project_count,
  813. '平均节支率': item.rate_avg
  814. })
  815. /* eslint-enable */
  816. })
  817. this.area.data.rows = rows
  818. this.area.data.customData = custom
  819. },
  820. // 格式化重点合作企业数据
  821. formatClientData (data) {
  822. if (!data) return
  823. data.forEach((v, i) => {
  824. v.topData.forEach((s, j) => {
  825. s.parent = s.countMoney / v.topData[0].countMoney * 100 + '%'
  826. s.lastTime = new Date(Number(s.lastTime + '000')).pattern('yyyy/MM/dd')
  827. s.countMoney = s.countMoney ? moneyUnit(s.countMoney) : ''
  828. })
  829. })
  830. const newData = data.map((v) => {
  831. v.topData = v.topData.filter((s) => {
  832. return s.winnerName && s.winnerName.trim().length
  833. })
  834. return v
  835. }).filter(function (v, i) {
  836. return v.topData.length
  837. })
  838. return newData
  839. },
  840. /* ******** 公共函数部分 ******* */
  841. // 求最大项目数量
  842. getMaxProjectCount (analysisArr) {
  843. if (!analysisArr || !$.isArray(analysisArr)) return
  844. const itemMaxKey = 3 // 每一项中要比较大小的索引
  845. const arr = JSON.parse(JSON.stringify(analysisArr))
  846. bSort(arr, itemMaxKey)
  847. const maxCount = arr[arr.length - 1][itemMaxKey]
  848. let maxIndex = -1
  849. analysisArr.some((item, index) => {
  850. const gotIt = item[itemMaxKey] === maxCount
  851. if (gotIt) maxIndex = index
  852. return gotIt
  853. })
  854. return maxIndex
  855. },
  856. // 一维数组转换为二维数组
  857. arrTrans (num, arr) {
  858. const newArr = []
  859. arr.forEach((item, index) => {
  860. // 计算该元素为第几个素组内
  861. const page = Math.floor(index / num)
  862. // 判断是否存在
  863. if (!newArr[page]) {
  864. newArr[page] = []
  865. }
  866. newArr[page].push(item)
  867. })
  868. return newArr
  869. },
  870. // 处理金额区间转换
  871. formatMoneyRange (key) {
  872. if (!key) return
  873. if (key.indexOf('_')) {
  874. key = key.split('_')
  875. } else {
  876. key = key.split('-')
  877. }
  878. if (!key[1]) return '>' + (key[0] / 100000000).fixed(2) + '亿'
  879. key[0] = key[0] >= 100000000 ? (key[0] / 100000000).fixed(2) : (key[0] / 10000).fixed(2)
  880. key[1] = key[1] >= 100000000 ? (key[1] / 100000000).fixed(2) + '亿' : (key[1] / 10000).fixed(2) + '万'
  881. return key[0] + '-' + key[1]
  882. }
  883. }
  884. }
  885. </script>
  886. <style lang="scss" scoped>
  887. .unit-chart{
  888. width: 1200px;
  889. padding: 32px 40px;
  890. margin: 0 auto;
  891. background: #fff;
  892. .chart-title{
  893. padding: 32px 0 16px;
  894. font-size: 18px;
  895. color: #1d1d1d;
  896. line-height: 28px;
  897. font-family: 'Microsoft YaHei, Microsoft YaHei-Regular';
  898. }
  899. .chart-tips{
  900. padding: 0 0 32px 46px;
  901. font-size: 12px;
  902. color: #999999;
  903. line-height: 20px;
  904. }
  905. .text-center{
  906. text-align: center;
  907. }
  908. }
  909. </style>