charging.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. <template>
  2. <div class="app-container">
  3. <el-row :gutter="20">
  4. <el-col :span="4" :xs="24">
  5. <div class="head-container">
  6. <el-input v-model="areaName" placeholder="请输入服务区名称" clearable size="small" prefix-icon="el-icon-search"
  7. style="margin-bottom: 20px" />
  8. </div>
  9. <div class="head-container">
  10. <el-tree ref="tree" :data="areaOptions" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode"
  11. node-key="id" :default-expanded-keys="this.defalutArr" highlight-current @node-click="handleNodeClick" />
  12. </div>
  13. </el-col>
  14. <el-col :span="20" :xs="24">
  15. <el-tabs v-model="activeTab" @tab-click="handleTabChange">
  16. <el-tab-pane label="总览" name="first">
  17. <div class="custom-form">
  18. <el-select v-model="queryParams.meterCls" size="mini" @change="meterClsChange">
  19. <el-option v-for="item in emsClsOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
  20. </el-select>
  21. </div>
  22. <div class="panel">
  23. <div class="panel-title">当月数据</div>
  24. <div class="panel-body">
  25. <BaseChart width="100%" height="300px" :option="pieOptions" />
  26. </div>
  27. </div>
  28. <div class="panel">
  29. <div class="panel-title">历史数据</div>
  30. <div class="panel-body">
  31. <div class="custom-form">
  32. <el-date-picker v-model="queryParams.historyRange" size="mini" type="monthrange" align="right" :clearable="false"
  33. range-separator="至" start-placeholder="开始月份" end-placeholder="结束月份" value-format="yyyyMM"
  34. :picker-options="pickerOptions" @change="getList">
  35. </el-date-picker>
  36. </div>
  37. <el-table :data="historyData" style="width: 100%;margin-bottom:20px" max-height="300px">
  38. <el-table-column prop="meteredTime" align="center" label="账单月份">
  39. </el-table-column>
  40. <el-table-column prop="meteredValue" align="center" label="商户用量">
  41. </el-table-column>
  42. <el-table-column prop="meteredPrice" align="center" label="商户金额">
  43. </el-table-column>
  44. <el-table-column prop="sharedValue" align="center" label="公摊用量">
  45. </el-table-column>
  46. <el-table-column prop="sharedPrice" align="center" label="公摊金额">
  47. </el-table-column>
  48. <el-table-column prop="totalValue" align="center" label="总用量">
  49. </el-table-column>
  50. <el-table-column prop="totalPrice" align="center" label="总金额">
  51. </el-table-column>
  52. </el-table>
  53. <BaseChart width="100%" height="300px" :option="overviewOptions" />
  54. </div>
  55. </div>
  56. </el-tab-pane>
  57. <el-tab-pane label="个户" name="second">
  58. <div class="custom-form">
  59. <el-select v-model="queryParams.meterCls" size="mini" @change="meterClsChange">
  60. <el-option v-for="item in emsClsOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
  61. </el-select>
  62. </div>
  63. <div class="panel">
  64. <div class="panel-title">当月数据</div>
  65. <div class="panel-body">
  66. <el-table :data="currentData" style="width: 100%">
  67. <el-table-column prop="meteredTime" align="center" label="账单月份">
  68. </el-table-column>
  69. <el-table-column prop="meteredValue" align="center" label="用量">
  70. </el-table-column>
  71. <el-table-column prop="meteredPrice" align="center" label="金额">
  72. </el-table-column>
  73. <el-table-column prop="sharedPrice" align="center" label="公摊价格">
  74. </el-table-column>
  75. <el-table-column prop="sharedComputeType" align="center" label="公摊类型">
  76. <template slot-scope="scope">
  77. {{formatDict(scope.row.sharedComputeType,'computeTypeOptions')}}
  78. </template>
  79. </el-table-column>
  80. <el-table-column prop="totalPrice" align="center" label="总金额">
  81. </el-table-column>
  82. </el-table>
  83. </div>
  84. </div>
  85. <div class="panel">
  86. <div class="panel-title">历史数据</div>
  87. <div class="panel-body">
  88. <div class="custom-form">
  89. <el-date-picker v-model="queryParams.historyRange" size="mini" type="monthrange" align="right" :clearable="false"
  90. range-separator="至" start-placeholder="开始月份" end-placeholder="结束月份" value-format="yyyyMM"
  91. :picker-options="pickerOptions" @change="getList">
  92. </el-date-picker>
  93. </div>
  94. <el-table :data="historyData" style="width: 100%;margin-bottom:20px" max-height="300px">
  95. <el-table-column prop="meteredTime" align="center" label="账单月份">
  96. </el-table-column>
  97. <el-table-column prop="meteredValue" align="center" label="用量">
  98. </el-table-column>
  99. <el-table-column prop="meteredPrice" align="center" label="金额">
  100. </el-table-column>
  101. <!-- <el-table-column prop="sharedPrice" align="center" label="公摊价格">
  102. </el-table-column>
  103. <el-table-column prop="sharedComputeType" align="center" label="公摊类型">
  104. <template slot-scope="scope">
  105. {{formatDict(scope.row.sharedComputeType,'computeTypeOptions')}}
  106. </template>
  107. </el-table-column> -->
  108. <el-table-column prop="totalPrice" align="center" label="总价">
  109. </el-table-column>
  110. </el-table>
  111. <BaseChart width="100%" height="300px" :option="elecOptions" />
  112. </div>
  113. </div>
  114. </el-tab-pane>
  115. </el-tabs>
  116. </el-col>
  117. </el-row>
  118. </div>
  119. </template>
  120. <script>
  121. import { areaTreeSelect, areaTreeSelectByTag } from '@/api/basecfg/area'
  122. import Treeselect from '@riophae/vue-treeselect'
  123. import '@riophae/vue-treeselect/dist/vue-treeselect.css'
  124. import { chargingList, overviewInfo } from '@/api/mgr/charging.js'
  125. import { dateFormat } from '@/utils/index.js'
  126. import BaseChart from '@/components/BaseChart'
  127. export default {
  128. name: 'Device',
  129. components: { Treeselect, BaseChart },
  130. data() {
  131. const nowDay = new Date()
  132. const lastMonth = new Date(nowDay.getFullYear(), nowDay.getMonth() - 1)
  133. return {
  134. activeTab: 'first',
  135. // 总条数
  136. areaName: undefined,
  137. areaOptions: [],
  138. defaultProps: {
  139. children: 'children',
  140. label: 'label'
  141. },
  142. emsClsOptions: [
  143. { code: 45, name: '电表' },
  144. { code: 70, name: '水表' }
  145. ],
  146. // 查询参数
  147. queryParams: {
  148. pageNum: 1,
  149. pageSize: 10,
  150. areaCode: null,
  151. meterCls: 45,
  152. historyRange: []
  153. },
  154. lastMonth: dateFormat(lastMonth, 'yyyyMM'),
  155. currentData: [],
  156. historyData: [],
  157. defalutArr: [],
  158. computeTypeOptions: [
  159. { name: '不计入', code: 0 },
  160. { name: '个户平摊', code: 1 },
  161. { name: '面积公摊)', code: 2 }
  162. ],
  163. pickerOptions: {
  164. disabledDate(time) {
  165. const t = new Date().getDate()
  166. return time.getTime() > Date.now() - 8.64e7 * t
  167. }
  168. }
  169. }
  170. },
  171. created() {
  172. this.setDefaultMonthRange()
  173. this.handleTabChange()
  174. },
  175. computed: {
  176. pieOptions() {
  177. let options = {}
  178. if (this.activeTab == 'first') {
  179. const { meterCls } = this.queryParams
  180. let pieData = []
  181. let total = ''
  182. if (this.currentData.length) {
  183. const { meteredValue, sharedValue, totalValue, meteredPrice, sharedPrice, totalPrice } = this.currentData[0]
  184. total = totalValue
  185. pieData = [
  186. {
  187. value: meteredValue,
  188. price: meteredPrice,
  189. name: '商户用量',
  190. totalValue,
  191. totalPrice,
  192. itemStyle: {
  193. color: '#8d7fec'
  194. }
  195. },
  196. {
  197. value: sharedValue,
  198. price: sharedPrice,
  199. name: '公摊用量',
  200. totalValue,
  201. totalPrice,
  202. itemStyle: {
  203. color: '#6BD9BC'
  204. }
  205. }
  206. ]
  207. }
  208. options = {
  209. title: [
  210. {
  211. text: '总用量',
  212. subtext: total + `${meterCls === 45 ? '度' : '吨'}`,
  213. textStyle: {
  214. fontSize: 15,
  215. color: 'black'
  216. },
  217. subtextStyle: {
  218. fontSize: 20,
  219. color: 'black'
  220. },
  221. textAlign: 'center',
  222. x: '34.5%',
  223. y: '40%'
  224. }
  225. ],
  226. tooltip: {
  227. trigger: 'item',
  228. formatter: function(parms) {
  229. var str =
  230. parms.data.name +
  231. '</br>' +
  232. '用量:' +
  233. parms.data.value +
  234. `${meterCls === 45 ? '度' : '吨'}` +
  235. '</br>' +
  236. '金额:' +
  237. parms.data.price +
  238. '元'
  239. return str
  240. }
  241. },
  242. legend: {
  243. type: 'scroll',
  244. orient: 'vertical',
  245. left: '65%',
  246. align: 'left',
  247. top: 'middle',
  248. textStyle: {
  249. color: '#8C8C8C'
  250. }
  251. },
  252. series: [
  253. {
  254. type: 'pie',
  255. center: ['35%', '50%'],
  256. radius: ['40%', '65%'],
  257. clockwise: false, // 饼图的扇区是否是顺时针排布
  258. avoidLabelOverlap: false,
  259. itemStyle: {
  260. // 图形样式
  261. normal: {
  262. borderColor: '#ffffff',
  263. borderWidth: 1
  264. }
  265. },
  266. label: {
  267. formatter: function(val) {
  268. const unit = meterCls === 45 ? '度' : '吨'
  269. return '{a|' + val.name + '}{b|\n' + val.value + unit + '}{b|\n' + val.data.price + '元}'
  270. },
  271. textStyle: {
  272. rich: {
  273. a: {
  274. color: '#333333',
  275. fontSize: '12'
  276. },
  277. b: {
  278. color: '#0086FF',
  279. fontSize: '12',
  280. padding: [4, 0, 0, 0]
  281. }
  282. }
  283. }
  284. },
  285. labelLine: {
  286. length: 10,
  287. length2: 50
  288. },
  289. labelLayout: {
  290. verticalAlign: 'bottom',
  291. },
  292. data: pieData
  293. }
  294. ]
  295. }
  296. }
  297. return options
  298. },
  299. overviewOptions() {
  300. let option = {}
  301. if (this.activeTab === 'first') {
  302. const xData = this.historyData.map(item => item.meteredTime)
  303. const quantity = this.historyData.map(item => item.meteredValue)
  304. const shareQuantity = this.historyData.map(item => item.sharedValue)
  305. const cost = this.historyData.map(item => item.meteredPrice)
  306. const shareCost = this.historyData.map(item => item.sharedPrice)
  307. const { meterCls } = this.queryParams
  308. option = {
  309. tooltip: {
  310. trigger: 'axis',
  311. axisPointer: {
  312. type: 'cross',
  313. crossStyle: {
  314. color: '#999'
  315. }
  316. }
  317. },
  318. grid: {
  319. left: '5%'
  320. },
  321. legend: {
  322. data:['商户用量', '商户金额', '公摊用量', '公摊金额']
  323. },
  324. xAxis: {
  325. type: 'category',
  326. data: xData,
  327. axisPointer: {
  328. type: 'shadow'
  329. }
  330. },
  331. yAxis: [
  332. {
  333. name: meterCls === 45 ? 'kW-h(千瓦时)' : '吨',
  334. type: 'value'
  335. },
  336. {
  337. name: '¥(元)',
  338. type: 'value'
  339. }
  340. ],
  341. series: [
  342. {
  343. name: '商户用量',
  344. type: 'bar',
  345. data: quantity,
  346. barWidth: 30,
  347. itemStyle: {
  348. normal: {
  349. color: '#6395FA'
  350. }
  351. }
  352. },
  353. {
  354. name: '公摊用量',
  355. type: 'bar',
  356. data: shareQuantity,
  357. barWidth: 30,
  358. itemStyle: {
  359. normal: {
  360. color: '#8CDF6C'
  361. }
  362. }
  363. },
  364. {
  365. name: '商户金额',
  366. type: 'line',
  367. yAxisIndex: 1,
  368. data: cost,
  369. showSymbol: true,
  370. itemStyle: {
  371. normal: {
  372. color: '#5BD9A5'
  373. }
  374. }
  375. },
  376. {
  377. name: '公摊金额',
  378. type: 'line',
  379. yAxisIndex: 1,
  380. data: shareCost,
  381. showSymbol: true,
  382. }
  383. ]
  384. }
  385. }
  386. return option
  387. },
  388. elecOptions() {
  389. const xData = this.historyData.map(item => item.meteredTime)
  390. const quantity = this.historyData.map(item => item.meteredValue)
  391. const cost = this.historyData.map(item => item.meteredPrice)
  392. const { meterCls } = this.queryParams
  393. const option = {
  394. tooltip: {
  395. trigger: 'axis',
  396. axisPointer: {
  397. type: 'cross',
  398. crossStyle: {
  399. color: '#999'
  400. }
  401. }
  402. },
  403. grid: {
  404. left: '5%'
  405. },
  406. legend: {
  407. data: ['用量', '金额']
  408. },
  409. xAxis: {
  410. type: 'category',
  411. data: xData,
  412. axisPointer: {
  413. type: 'shadow'
  414. }
  415. },
  416. yAxis: [
  417. {
  418. name: meterCls === 45 ? 'kW-h(千瓦时)' : '吨',
  419. type: 'value'
  420. },
  421. {
  422. name: '¥(元)',
  423. type: 'value'
  424. }
  425. ],
  426. series: [
  427. {
  428. name: '用量',
  429. type: 'bar',
  430. data: quantity,
  431. barWidth: 30,
  432. itemStyle: {
  433. normal: {
  434. color: '#6395FA'
  435. }
  436. }
  437. },
  438. {
  439. name: '金额',
  440. type: 'line',
  441. yAxisIndex: 1,
  442. data: cost,
  443. showSymbol: true,
  444. itemStyle: {
  445. normal: {
  446. color: '#5BD9A5'
  447. }
  448. }
  449. }
  450. ]
  451. }
  452. return option
  453. }
  454. },
  455. methods: {
  456. meterClsChange() {
  457. this.getList('current')
  458. this.getList()
  459. },
  460. setDefaultMonthRange() {
  461. const nowDay = new Date()
  462. const lastMonth = new Date(nowDay.getFullYear(), nowDay.getMonth() - 1)
  463. const months = this.getPreviousFiveMonthsYearMonth(lastMonth)
  464. const { year, month } = months[months.length - 1]
  465. const lastFiveMonth = month < 10 ? `0${month}` : month
  466. this.queryParams.historyRange = [`${year}${lastFiveMonth}`, this.lastMonth]
  467. },
  468. getPreviousFiveMonthsYearMonth(date) {
  469. const months = [...Array(5).keys()].map(i => {
  470. const year = date.getFullYear()
  471. const month = date.getMonth() - i
  472. if (month < 0) {
  473. return {
  474. year: year - 1,
  475. month: 12 + month
  476. }
  477. }
  478. return { year, month: month + 1 }
  479. })
  480. return months
  481. },
  482. formatDict(val, options, key = 'code', text = 'name') {
  483. let name = null
  484. this[options].forEach(item => {
  485. if (item[key] === val) {
  486. name = item[text]
  487. }
  488. })
  489. return name
  490. },
  491. getList(type) {
  492. let startTime = null
  493. let endTime = null
  494. const { areaCode, meterCls, historyRange } = this.queryParams
  495. if (type === 'current') {
  496. startTime = this.lastMonth
  497. endTime = this.lastMonth
  498. } else {
  499. startTime = historyRange[0]
  500. endTime = historyRange[1]
  501. }
  502. // 个户
  503. if (this.activeTab === 'second') {
  504. chargingList({
  505. areaPath: areaCode,
  506. tier: 'Zoning',
  507. meterCls,
  508. startTime,
  509. endTime
  510. }).then(({ data }) => {
  511. if (type === 'current') {
  512. this.currentData = data || []
  513. } else {
  514. this.historyData = data || []
  515. }
  516. })
  517. } else {
  518. overviewInfo({
  519. areaPath: areaCode,
  520. tier: 'Area',
  521. meterCls,
  522. startTime,
  523. endTime
  524. }).then(({ data }) => {
  525. if (type === 'current') {
  526. this.currentData = data || []
  527. } else {
  528. this.historyData = data || []
  529. }
  530. })
  531. }
  532. },
  533. async handleTabChange() {
  534. // 根据newTabName给someParam赋值
  535. if (this.activeTab === 'first') {
  536. await this.getAreaTree('Area')
  537. this.queryParams.areaCode = this.areaOptions[0].id
  538. this.$refs['tree'].setCurrentKey(this.queryParams.areaCode)
  539. } else if (this.activeTab === 'second') {
  540. await this.getAreaTreeByTag('Zoning', 'Area_01')
  541. this.defalutArr = []
  542. this.recursion(this.areaOptions[0], this.defalutArr)
  543. this.queryParams.areaCode = this.defalutArr.join('/')
  544. this.$refs['tree'].setCurrentKey(this.defalutArr[this.defalutArr.length - 1])
  545. }
  546. this.meterClsChange()
  547. },
  548. recursion(data, defalutArr) {
  549. defalutArr.push(data.id)
  550. if (data.children && data.children.length) {
  551. this.recursion(data.children[0], defalutArr)
  552. }
  553. },
  554. /** 查询区域树结构 */
  555. async getAreaTree(tier) {
  556. await areaTreeSelect(tier).then(response => {
  557. this.areaOptions = response.data
  558. })
  559. },
  560. /** 查询区域树结构 */
  561. async getAreaTreeByTag(tier, tagCode) {
  562. await areaTreeSelectByTag(tier, tagCode).then(response => {
  563. this.areaOptions = response.data
  564. })
  565. },
  566. // 筛选节点
  567. filterNode(value, data) {
  568. if (!value) return true
  569. return data.label.indexOf(value) !== -1
  570. },
  571. handleNodeClick(data, node) {
  572. // 商户树形结构只能点击最后一级
  573. if (this.activeTab === 'second' && node.level < 3) return this.$refs['tree'].setCurrentKey(null)
  574. const nodeArr = []
  575. // 获取点击当节点的dom的信息
  576. const selectNode = this.$refs.tree.getNode(data)
  577. // 调用递归函数
  578. this.platform(selectNode, nodeArr)
  579. this.queryParams.areaCode = nodeArr.map(item => item.id).join('/')
  580. this.meterClsChange()
  581. },
  582. // 递归函数
  583. platform(node, array) {
  584. if (!node.parent) {
  585. return
  586. }
  587. array.unshift(node.data)
  588. // 调用递归
  589. this.platform(node.parent, array)
  590. }
  591. }
  592. }
  593. </script>
  594. <style lang="scss" scoped>
  595. .app-container {
  596. ::v-deep .el-tabs__content {
  597. overflow: initial;
  598. }
  599. }
  600. .custom-form {
  601. padding-bottom: 10px;
  602. }
  603. .panel {
  604. display: flex;
  605. flex-direction: column;
  606. .panel-title {
  607. display: flex;
  608. align-items: center;
  609. font-size: 14px;
  610. font-weight: 500;
  611. &::before {
  612. content: '';
  613. display: inline-block;
  614. height: 14px;
  615. width: 3px;
  616. border-radius: 6px;
  617. background: #409eff;
  618. margin-right: 5px;
  619. }
  620. }
  621. .panel-body {
  622. flex: 1;
  623. background: #fff;
  624. padding: 10px 0;
  625. }
  626. }
  627. </style>
  628. </style>