50 次代碼提交 e6d87f91df ... c1546f933b

作者 SHA1 備註 提交日期
  wjc c1546f933b fix: 使用梦源字体 1 月之前
  wjc 281ae20aa1 fix: 硬件 月份 1 月之前
  wjc 488a0bc32a fix: ab 样式优化 1 月之前
  wjc 5cda95fe63 fix: 实施 汇总数据 1 月之前
  wjc 192e20c327 fix: grid 布局设置 1 月之前
  wjc fdb33ac605 feat: ab屏 优化边距 1 月之前
  wjc bf6dbc9566 fix: 滚动条不显示 1 月之前
  wjc eb0ef59b55 feat: ab屏 样式优化 1 月之前
  wjc 7f834ccde0 fix: 标题 1 月之前
  wjc f8ebad8233 Merge branch 'dev' of http://git.wisdomcity.com.cn/WisdomCity-Web/SaaS-DataScreen into feat-b2 1 月之前
  wjc 0d7c3f6541 feat: a屏幕 a3,a4屏幕 1 月之前
  mzr 866a5d30f2 fix: a1,a2屏样式优化 1 月之前
  mzr 1ddbbd282b fix: a2屏布局调整及组件resize 1 月之前
  wjc f6f39b4477 fix: ab bug 1 月之前
  wjc e0f7bd8fd3 fix: a1-b2 样式优化 1 月之前
  wjc bcee653d8b Merge branch 'dev' of http://git.wisdomcity.com.cn/WisdomCity-Web/SaaS-DataScreen into feat-b2 1 月之前
  wjc 87e56993b4 fix: 隐藏滚动条 1 月之前
  mzr 5af7da7391 fix: 门禁接口路径冲突 1 月之前
  mzr 99b107647c fix: a1,a2屏接口联调 1 月之前
  wjc 1ccbeb9b79 feat: b2 接口对接 1 月之前
  mzr 4b20cb2439 fix: a1接口联调 1 月之前
  wjc 5fd7a5d3f3 feat: b2 市场数据 1 月之前
  wjc bd26ab2daf fix: b2 实施字体 1 月之前
  wjc 2c85f4af63 feat: b1 实施数据对接 1 月之前
  wjc f9ff43fd41 fix: 登陆 回车 1 月之前
  wjc 2bcde29566 fix: b2 标题样式 2 月之前
  wjc 60b157779b fix: b2 柱状图优化 2 月之前
  wjc 451c8b00a9 fix: b1 背景 2 月之前
  wjc f2d7a58ab4 Merge branch 'dev' of http://git.wisdomcity.com.cn/WisdomCity-Web/SaaS-DataScreen into feat-b2 2 月之前
  wjc 41f2789b4f feat: 接口字段对接 2 月之前
  mzr 6ab7e65a9f fix: 样式调整及整合b1,b2屏 2 月之前
  wjc ab5d229aa1 fix: b1 栅格处理 2 月之前
  wjc 2dd5315d21 fix: b2 市场 优化 2 月之前
  wjc 3caf635af4 wip: b1 部分接口调试 2 月之前
  mzr 5c649e169c Merge branch 'dev' of http://git.wisdomcity.com.cn/WisdomCity-Web/SaaS-DataScreen into mzr-dev 2 月之前
  mzr 4f06643c0e fix: a2屏 2 月之前
  wjc 68052d8741 Merge branch 'dev' of http://git.wisdomcity.com.cn/WisdomCity-Web/SaaS-DataScreen into feat-b2 2 月之前
  wjc db38f9959c fix: vue2 版本冲突 2 月之前
  mzr cdfc9e7d98 fix: a2屏 2 月之前
  mzr 55749e46cb fix: a2屏 2 月之前
  wjc 2bce104c2a Merge branch 'dev' of http://git.wisdomcity.com.cn/WisdomCity-Web/SaaS-DataScreen into feat-b2 2 月之前
  wjc 85b9804bd7 wip: b1/b2 屏幕视频 2 月之前
  mzr f61539df2e fix: a2屏 2 月之前
  wjc 01a543dc21 wip: b2 布局 2 月之前
  mzr 84d2222542 fix: sass依赖安装及eslint效验 2 月之前
  wjc b4baaf6e99 wip: b2 布局 2 月之前
  mzr c97f9d226e feat: 添加a1,a2大屏 2 月之前
  wjc 130b9a159a wip: 图标 2 月之前
  wjc 7bcc5326b6 wip b2 小组件ui开发完成 2 月之前
  wjc 8fd595322d wip: b2 小组件 2 月之前
共有 100 個文件被更改,包括 6773 次插入123 次删除
  1. 7 1
      .eslintrc.js
  2. 1 2
      .npmrc
  3. 10 0
      babel.config.js
  4. 8 4
      package.json
  5. 3 6
      src/App.vue
  6. 135 0
      src/api/hui-jia.js
  7. 5 3
      src/api/index.js
  8. 0 1
      src/assets/css/element-variables.scss
  9. 4 0
      src/assets/css/font.css
  10. 181 0
      src/assets/css/screen.scss
  11. 47 2
      src/assets/css/theme.scss
  12. 1 1
      src/assets/css/theme_1.scss
  13. 2 2
      src/assets/css/theme_2.scss
  14. 二進制
      src/assets/fonts/DreamHanSansCN-W12.ttf
  15. 二進制
      src/assets/images/dblxzhb.png
  16. 二進制
      src/assets/images/ewm.png
  17. 二進制
      src/assets/images/fwzj.png
  18. 二進制
      src/assets/images/glgd.png
  19. 二進制
      src/assets/images/gwxx.png
  20. 二進制
      src/assets/images/hjkj-logo.png
  21. 二進制
      src/assets/images/hjkj1.png
  22. 二進制
      src/assets/images/hjkj2.png
  23. 二進制
      src/assets/images/jgxx.png
  24. 二進制
      src/assets/images/jytj.png
  25. 二進制
      src/assets/images/nz.png
  26. 二進制
      src/assets/images/qfefb.png
  27. 二進制
      src/assets/images/qst.png
  28. 二進制
      src/assets/images/rhys.png
  29. 二進制
      src/assets/images/ryzb.png
  30. 二進制
      src/assets/images/shb.png
  31. 二進制
      src/assets/images/sswz.png
  32. 二進制
      src/assets/images/tj.png
  33. 二進制
      src/assets/images/xlzb.png
  34. 二進制
      src/assets/images/xtxq.png
  35. 二進制
      src/assets/images/zhhb.png
  36. 二進制
      src/assets/images/zhnmj.png
  37. 二進制
      src/assets/images/zhxggd.png
  38. 279 0
      src/assets/js/province-positon.js
  39. 107 0
      src/components/clock.vue
  40. 89 88
      src/components/digit-roll/index.vue
  41. 11 9
      src/components/digit-roll/utils/index.js
  42. 24 0
      src/libs/tools.js
  43. 25 2
      src/main.js
  44. 1 0
      src/route/index.js
  45. 60 2
      src/route/white-list.js
  46. 90 0
      src/views/hui-jia/a1-screen/index.vue
  47. 83 0
      src/views/hui-jia/a2-screen/index.vue
  48. 115 0
      src/views/hui-jia/a3-screen/index.vue
  49. 199 0
      src/views/hui-jia/a4-screen/index.vue
  50. 176 0
      src/views/hui-jia/b1-screen/index.vue
  51. 262 0
      src/views/hui-jia/b2-screen/index.vue
  52. 2 0
      src/views/hui-jia/components/card/index.js
  53. 89 0
      src/views/hui-jia/components/card/index.vue
  54. 226 0
      src/views/hui-jia/components/community-map/components/map-list.vue
  55. 二進制
      src/views/hui-jia/components/community-map/images/icon-title.png
  56. 二進制
      src/views/hui-jia/components/community-map/images/map-mark-icon.png
  57. 2 0
      src/views/hui-jia/components/community-map/index.js
  58. 107 0
      src/views/hui-jia/components/community-map/index.vue
  59. 279 0
      src/views/hui-jia/components/community-map/province-positon.js
  60. 246 0
      src/views/hui-jia/components/community-type/components/bar.vue
  61. 362 0
      src/views/hui-jia/components/community-type/components/three-pie.vue
  62. 二進制
      src/views/hui-jia/components/community-type/images/icon-sy.png
  63. 二進制
      src/views/hui-jia/components/community-type/images/icon-title.png
  64. 二進制
      src/views/hui-jia/components/community-type/images/icon-type.png
  65. 二進制
      src/views/hui-jia/components/community-type/images/icon-zh.png
  66. 二進制
      src/views/hui-jia/components/community-type/images/icon-zz.png
  67. 2 0
      src/views/hui-jia/components/community-type/index.js
  68. 81 0
      src/views/hui-jia/components/community-type/index.vue
  69. 518 0
      src/views/hui-jia/components/hardware.vue
  70. 248 0
      src/views/hui-jia/components/implementary/ing.vue
  71. 232 0
      src/views/hui-jia/components/implementary/now-year.vue
  72. 139 0
      src/views/hui-jia/components/implementary/total.vue
  73. 376 0
      src/views/hui-jia/components/market.vue
  74. 二進制
      src/views/hui-jia/components/online-pay-day/images/icon-title.png
  75. 2 0
      src/views/hui-jia/components/online-pay-day/index.js
  76. 430 0
      src/views/hui-jia/components/online-pay-day/index.vue
  77. 二進制
      src/views/hui-jia/components/online-pay-week/images/icon-title.png
  78. 2 0
      src/views/hui-jia/components/online-pay-week/index.js
  79. 403 0
      src/views/hui-jia/components/online-pay-week/index.vue
  80. 二進制
      src/views/hui-jia/components/online-pay/images/icon-title.png
  81. 二進制
      src/views/hui-jia/components/online-pay/images/icon-total.png
  82. 2 0
      src/views/hui-jia/components/online-pay/index.js
  83. 277 0
      src/views/hui-jia/components/online-pay/index.vue
  84. 二進制
      src/views/hui-jia/components/online-water/images/icon-month.png
  85. 二進制
      src/views/hui-jia/components/online-water/images/icon-once.png
  86. 二進制
      src/views/hui-jia/components/online-water/images/icon-title.png
  87. 二進制
      src/views/hui-jia/components/online-water/images/icon-total.png
  88. 2 0
      src/views/hui-jia/components/online-water/index.js
  89. 212 0
      src/views/hui-jia/components/online-water/index.vue
  90. 二進制
      src/views/hui-jia/components/resident/images/icon-hfw.png
  91. 二進制
      src/views/hui-jia/components/resident/images/icon-pay.png
  92. 二進制
      src/views/hui-jia/components/resident/images/icon-qrcode.png
  93. 二進制
      src/views/hui-jia/components/resident/images/icon-report.png
  94. 二進制
      src/views/hui-jia/components/resident/images/icon-title.png
  95. 2 0
      src/views/hui-jia/components/resident/index.js
  96. 311 0
      src/views/hui-jia/components/resident/index.vue
  97. 272 0
      src/views/hui-jia/components/smart.vue
  98. 3 0
      src/views/hui-jia/components/title/index.js
  99. 21 0
      src/views/hui-jia/components/title/index.vue
  100. 二進制
      src/views/hui-jia/components/user/images/icon-car.png

+ 7 - 1
.eslintrc.js

@@ -1,10 +1,16 @@
+/*
+ * @Author: wjc
+ * @Date: 2021-05-31 09:36:11
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-11-25 10:07:44
+ * @Description: 
+ */
 module.exports = {
   root: true,
   env: {
     node: true
   },
   'extends': [
-    'plugin:vue/essential',
     '@vue/standard'
   ],
   rules: {

+ 1 - 2
.npmrc

@@ -1,2 +1 @@
-# registry = http://nexus.wisdomcity.com.cn/repository/wisdomcity-npm-group/
-# _auth = YWRtaW46V2lzZG9tY2l0eUBAMjAyMg==
+registry = "https://registry.npmmirror.com"

+ 10 - 0
babel.config.js

@@ -1,5 +1,15 @@
+/*
+ * @Author: wjc
+ * @Date: 2021-05-31 09:36:11
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-11-26 15:49:51
+ * @Description:
+ */
 module.exports = {
   presets: [
     '@vue/app'
+  ],
+  plugins: [
+    '@babel/plugin-proposal-optional-chaining'
   ]
 }

+ 8 - 4
package.json

@@ -11,20 +11,23 @@
     "test:unit": "vue-cli-service test:unit"
   },
   "dependencies": {
+    "@vue/composition-api": "^1.7.2",
     "axios": "^0.19.0",
     "babel-polyfill": "^6.26.0",
     "core-js": "^2.6.5",
     "dayjs": "^1.11.11",
-    "echarts": "^4.3.0",
+    "echarts": "^6.0.0",
     "element-ui": "^2.12.0",
     "reset.css": "^2.0.2",
-    "vue": "^2.6.10",
+    "vue": "2.6.11",
     "vue-count-to": "^1.0.13",
     "vue-router": "^3.0.3",
     "vue-seamless-scroll": "^1.1.17",
+    "vue-tianditu": "^2.7.6",
     "vuex": "^3.0.1"
   },
   "devDependencies": {
+    "@babel/plugin-proposal-optional-chaining": "^7.21.0",
     "@vue/cli-plugin-babel": "^3.11.0",
     "@vue/cli-plugin-eslint": "^3.11.0",
     "@vue/cli-service": "^3.11.0",
@@ -32,11 +35,12 @@
     "@vue/test-utils": "^1.0.0-beta.20",
     "babel-eslint": "^10.0.1",
     "eslint": "^5.16.0",
+    "eslint-loader": "^4.0.2",
     "eslint-plugin-cypress": "^2.6.1",
     "eslint-plugin-html": "^6.0.0",
     "eslint-plugin-vue": "^5.2.3",
-    "node-sass": "^4.9.0",
+    "sass": "^1.94.0",
     "sass-loader": "^7.1.0",
-    "vue-template-compiler": "^2.6.10"
+    "vue-template-compiler": "2.6.11"
   }
 }

+ 3 - 6
src/App.vue

@@ -1,8 +1,8 @@
 <!--
  * @Author: WangQiBiao
  * @Date: 2019-09-24 17:50:53
- * @LastEditors: MoZhuangRu
- * @LastEditTime: 2019-12-31 11:26:49
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-11-17 16:41:21
  * @Description:
  -->
 <template>
@@ -10,12 +10,9 @@
     <router-view/>
   </div>
 </template>
-<style lang="scss" scoped>
+<style lang="scss">
 @import '../src/assets/css/theme.scss';
 #app{
-  background: $big-bg;
-  background-repeat: no-repeat;
-  background-size: 100% 100%;
   width: 100vw;
   height: 100vh;
 }

+ 135 - 0
src/api/hui-jia.js

@@ -0,0 +1,135 @@
+/*
+ * @Author: wjc
+ * @Date: 2025-11-21 10:08:08
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-12-01 10:28:17
+ * @Description:
+ */
+import axios from '../assets/js/api.request'
+export default {
+  /**
+   * 部分业务 http://yapi.wisdomcity.com.cn/project/25/interface/api/26531
+   */
+  getBusinessData (data) {
+    return axios.request({
+      url: '/platform/bigData/business/statistics',
+      params: data,
+      method: 'get'
+    })
+  },
+  /**
+   * 智能门禁 http://yapi.wisdomcity.com.cn/project/25/interface/api/26522
+   */
+  getFaceDeviceData (data) {
+    return axios.request({
+      url: '/platform/bigData/faceDeviceManage/faceDoorPassRecordSnapshotSta',
+      params: data,
+      method: 'get'
+    })
+  },
+  /**
+   * 智能电表 http://yapi.wisdomcity.com.cn/project/25/interface/api/26504
+   */
+  getElectricData (data) {
+    return axios.request({
+      url: '/platform/bigData/meterHouse/electricMeter/monthStats',
+      params: data,
+      method: 'get'
+    })
+  },
+  /**
+   * 智能水表 http://yapi.wisdomcity.com.cn/project/25/interface/api/26513
+   */
+  getWaterData (data) {
+    return axios.request({
+      url: '/platform/bigData/meterHouse/waterMeter/monthStats',
+      params: data,
+      method: 'get'
+    })
+  },
+  /**
+   * 实施相关数据 http://yapi.wisdomcity.com.cn/project/25/interface/api/26576
+   */
+  getScreenData (data) {
+    return axios.request({
+      url: '/platform/bigData/screen/statistics',
+      params: data,
+      method: 'get'
+    })
+  },
+  /**
+   * 数据大屏线上流水数据统计 http://yapi.wisdomcity.com.cn/project/25/interface/api/26468
+   */
+  getOnlineWaterStatistics () {
+    return axios.request({
+      url: '/platform/bigData/payRecord/statistics',
+      method: 'get'
+    })
+  },
+  /** 线上缴费金额月统计表(近12月) http://yapi.wisdomcity.com.cn/project/25/interface/api/26477
+   */
+  getOnlinePayMonth () {
+    return axios.request({
+      url: '/platform/bigData/payRecord/monthStats',
+      method: 'get'
+    })
+  },
+  /**
+   * 在线支付-周 线上缴费金额周统计表(近5周)http://yapi.wisdomcity.com.cn/project/25/interface/api/26486
+   */
+  getOnlinePayWeek () {
+    return axios.request({
+      url: '/platform/bigData/payRecord/weekStats',
+      method: 'get'
+    })
+  },
+  /**
+   * 线上缴费金额日统计表(近1月) http://yapi.wisdomcity.com.cn/project/25/interface/api/26495
+   */
+  getOnlinePayDay () {
+    return axios.request({
+      url: '/platform/bigData/payRecord/dayStats',
+      method: 'get'
+    })
+  },
+  /**
+   * 客户数据 http://yapi.wisdomcity.com.cn/project/25/interface/api/26540
+   */
+  getCustomerStatistics () {
+    return axios.request({
+      url: '/platform/bigData/customers/statistics',
+      method: 'get'
+    })
+  },
+
+  /**
+   * 项目体量 http://yapi.wisdomcity.com.cn/project/25/interface/api/26549
+   */
+  getCommunityTypeSizeStatistics () {
+    return axios.request({
+      url: '/platform/bigData/community/typeSize/statistics',
+      method: 'get'
+    })
+  },
+
+  /**
+   * 项目地区分布 http://yapi.wisdomcity.com.cn/project/25/interface/api/26558
+   */
+  getCommunityRegionStatistics () {
+    return axios.request({
+      url: '/platform/bigData/community/region/statistics',
+      method: 'get'
+    })
+  },
+
+  /**
+   * 用户信息统计 http://yapi.wisdomcity.com.cn/project/25/interface/api/26567
+   */
+  getUsersStatistics () {
+    return axios.request({
+      url: '/platform/bigData/users/statistics',
+      method: 'get'
+    })
+  }
+
+}

+ 5 - 3
src/api/index.js

@@ -1,14 +1,16 @@
 /*
  * @Author: mozhuangru
- * @LastEditors: wangjiacheng
+ * @LastEditors: wjc
  * @Description: api
  * @Date: 2019-10-09 09:21:46
- * @LastEditTime: 2021-10-14 12:04:06
+ * @LastEditTime: 2025-11-21 10:10:57
  */
 import api from './api'
 import totalSum from './total-sum'
+import huiJiaApi from './hui-jia'
 
 export {
   api,
-  totalSum
+  totalSum,
+  huiJiaApi
 }

+ 0 - 1
src/assets/css/element-variables.scss

@@ -1,7 +1,6 @@
 @import './theme.scss';
 
 /* 改变主题色变量 */
-$--color-primary: $primary-color;
 
 /* 改变 icon 字体路径变量,必需 */
 $--font-path: '~element-ui/lib/theme-chalk/fonts';

+ 4 - 0
src/assets/css/font.css

@@ -0,0 +1,4 @@
+@font-face {
+  font-family: DreamHanSansCN;
+  src: url("../fonts/DreamHanSansCN-W12.ttf");
+}

+ 181 - 0
src/assets/css/screen.scss

@@ -0,0 +1,181 @@
+@import "./function.scss";
+@import "@/assets/css/theme.scss";
+
+// $gap-padding: halfW(8);
+$title-hd: halfH(109);
+.display-flex{
+    display: flex;
+}
+.flex-1{
+    flex: 1;
+}
+.margin-tg-20{
+    margin: halfH(20) 0;
+}
+// .margin-l-30{
+//     margin-left: halfW(30);
+// }
+// .margin-r-30{
+//     margin-right: halfW(30);
+// }
+.margin-t-20{
+    margin-top: halfH(20);
+}
+.margin-r-20{
+    margin-right: halfW(20);
+}
+.margin-t-10{
+    margin-top: halfH(10);
+}
+.border{
+    border: 1px solid rgba(0, 123, 211, 0.2);
+    border-radius: var(--radius);
+}
+.w-full{
+    width: 100%;
+}
+// .map-con{
+//     width: 100%;
+//     height: halfH(838);
+// }
+.screen{
+    width: 100vw;
+    height: 100vh;
+    background: var(--page-bg);
+    .screen-content{
+      //  margin: 0 halfW(30);
+    }
+}
+ .user .card-body{
+    display: flex;
+    align-items: center;
+  }
+  .community-type .card-body, .resident .card-body{
+    display: flex;
+    align-items: center;
+    align-content: end;
+    flex-wrap: wrap;
+    gap: halfW(50);
+  }
+  .b1-community-type.community-type .card-body, .b1-resident.resident .card-body{
+    display: flex;
+    align-items: center;
+    align-content: center;
+    flex-wrap: wrap;
+    margin-top: 0;
+  }
+.tdt-map-container {
+    .tdt-label {
+      background: transparent;
+      box-shadow: none;
+      border: none;
+      color: #2f61a6ff;
+      font-size: 12px;
+      padding: 0;
+      margin-left: 4px;
+      font-weight: 600;
+    }
+  }
+  .tdt-map-container {
+    width: 100%;
+    height: 100%;
+    .tdt-infowindow-content-wrapper {
+      padding: 0;
+      border-radius: 4px;
+      width: 100%;
+      height: 100%;
+      background: transparent;
+      .tdt-infowindow-content {
+        margin: 0;
+        padding: 2px;
+        width: 100% !important;
+        height: 100%;
+        .map-mark-container {
+          display: flex;
+          justify-content: space-between;
+          width: halfW(400);
+          height: halfH(90);
+          min-height: 80px;
+          border-radius: 6px;
+          // position: absolute;
+          overflow: hidden;
+          cursor: pointer;
+        
+          &.blue {
+            background: rgba(50, 129, 219, 0.6);
+            border: 1px solid rgba(50, 136, 233, 1);
+          }
+        
+          &.yellow {
+            background: rgba(230, 188, 116, 0.6);
+            border: 1px solid rgba(233, 175, 50, 1);
+          }
+        
+          &.green {
+            background: rgba(45, 158, 12, 0.6);
+            border: 1px solid rgba(45, 158, 12, 1);
+          }
+        
+          .left-side {
+            height: 100%;
+            width: halfW(76);
+            position: relative;
+        
+            &.blue {
+              background: #3281db;
+            }
+        
+            &.yellow {
+              background: #e4a537;
+            }
+        
+            &.green {
+              background: #2d9e0c;
+            }
+        
+            img {
+              position: absolute;
+              top: 50%;
+              left: 50%;
+              transform: translate(-50%, -50%);
+            }
+          }
+        
+          .right-side {
+            flex: 1;
+            padding: halfH(20) halfW(20);
+            .name-info {
+              display: flex;
+              justify-content: space-between;
+              align-items: flex-start;
+            }
+        
+            &-name {
+              width: 90%;
+              font-size: 18px;
+              font-weight: bold;
+              color: rgba(235, 244, 255, 1);
+            }
+        
+            &-local {
+              font-size: 16px;
+              font-weight: 400;
+              color: rgba(235, 244, 255, 1);
+              margin-top: halfH(10);
+            }
+        
+            &-time {
+              width: 10%;
+              text-align: right;
+              font-size: 14px;
+              font-weight: 400;
+              color: rgba(235, 244, 255, 1);
+            }
+          }
+        }
+      }
+    }
+    .tdt-infowindow-tip-container {
+      display: none;
+    }
+  }

+ 47 - 2
src/assets/css/theme.scss

@@ -1,8 +1,8 @@
-
 // 主题1:原始主题蓝色系
 // @import 'theme_1.scss';
 // 主题2:暗蓝色系
-@import 'theme_2.scss';
+@import "@/assets/css/function";
+@import "theme_2.scss";
 
 // 菜单颜色
 $menu-color: #072848;
@@ -12,3 +12,48 @@ $bg-color: #f8f8f8;
 $divider-color: #f1f1f1;
 // 菜单宽度
 $left-width: 220px;
+
+$gap-padding: halfW(8);
+
+:root {
+  cursor: none !important;
+  --title-bg: #111c31;
+  --title-primary: #b9e3f4;
+  --title-secondary: #a1a1a1;
+  --content-bg: #0c1425;
+  --primary: #007bd3;
+  --page-bg: #070d19;
+
+  --radius: 4px;
+
+  scrollbar-width: none; /* firefox */
+  -ms-overflow-style: none; /* IE 10+ */
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+:root {
+  ::-webkit-scrollbar {
+    display: none;
+  }
+
+  ::-webkit-scrollbar-track {
+    display: none;
+  }
+
+  ::-webkit-scrollbar-thumb {
+    display: none;
+  }
+}
+
+.title-icon {
+  width: halfW(28);
+  height: halfW(28);
+  margin-right: halfW(8);
+}
+
+.text-overflow {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}

+ 1 - 1
src/assets/css/theme_1.scss

@@ -1,5 +1,5 @@
 $big-bg: #0748A2 url('./assets/images/logo.jpg');
-$primary-color: #0080FF;
+// $primary-color: #0080FF;
 $title: #39D6FE;
 $title-cent: linear-gradient(0deg,rgba(23,101,255,1) 0%, rgba(23,101,255,1) 0%, rgba(73,217,254,1) 54.931640625%, rgba(143,239,255,1) 80.322265625%);
 $title-bg: url('./images/dbl@3x.png');

+ 2 - 2
src/assets/css/theme_2.scss

@@ -1,5 +1,5 @@
-$big-bg: #050E21 url('./assets/images/2_logo.png');
-$primary-color: #050E21;
+$big-bg: #050E21 url('./images/2_logo.png');
+// $primary-color: #050E21;
 $title: #7FA4DF;
 $title-cent: linear-gradient(0deg,rgba(23,101,255,1) 0%, rgba(24,84,222,1) 0%, rgba(58,177,208,1) 54.931640625%, rgba(113,194,208,1) 80.322265625%);
 $title-bg: url(./images/2_dbl@3x.png);

二進制
src/assets/fonts/DreamHanSansCN-W12.ttf


二進制
src/assets/images/dblxzhb.png


二進制
src/assets/images/ewm.png


二進制
src/assets/images/fwzj.png


二進制
src/assets/images/glgd.png


二進制
src/assets/images/gwxx.png


二進制
src/assets/images/hjkj-logo.png


二進制
src/assets/images/hjkj1.png


二進制
src/assets/images/hjkj2.png


二進制
src/assets/images/jgxx.png


二進制
src/assets/images/jytj.png


二進制
src/assets/images/nz.png


二進制
src/assets/images/qfefb.png


二進制
src/assets/images/qst.png


二進制
src/assets/images/rhys.png


二進制
src/assets/images/ryzb.png


二進制
src/assets/images/shb.png


二進制
src/assets/images/sswz.png


二進制
src/assets/images/tj.png


二進制
src/assets/images/xlzb.png


二進制
src/assets/images/xtxq.png


二進制
src/assets/images/zhhb.png


二進制
src/assets/images/zhnmj.png


二進制
src/assets/images/zhxggd.png


+ 279 - 0
src/assets/js/province-positon.js

@@ -0,0 +1,279 @@
+const provincePosition = [
+  // 23省
+  {
+    name: '甘肃',
+    abb: '甘',
+    geoCoord: [
+      103.73,
+      36.03
+    ]
+  },
+  {
+    name: '青海',
+    abb: '青',
+    geoCoord: [
+      101.74,
+      36.56
+    ]
+  },
+  {
+    name: '四川',
+    abb: '川',
+    geoCoord: [
+      104.06,
+      30.67
+    ]
+  },
+  {
+    name: '河北',
+    abb: '冀',
+    geoCoord: [
+      114.48,
+      38.03
+    ]
+  },
+  {
+    name: '云南',
+    abb: '滇',
+    geoCoord: [
+      102.73,
+      25.04
+    ]
+  },
+  {
+    name: '贵州',
+    abb: '黔',
+    geoCoord: [
+      106.71,
+      26.57
+    ]
+  },
+  {
+    name: '湖北',
+    abb: '鄂',
+    geoCoord: [
+      114.31,
+      30.52
+    ]
+  },
+  {
+    name: '河南',
+    abb: '豫',
+    geoCoord: [
+      113.65,
+      34.76
+    ]
+  },
+  {
+    name: '山东',
+    abb: '鲁',
+    geoCoord: [
+      117,
+      36.65
+    ]
+  },
+  {
+    name: '江苏',
+    abb: '苏',
+    geoCoord: [
+      118.78,
+      32.04
+    ]
+  },
+  {
+    name: '安徽',
+    abb: '皖',
+    geoCoord: [
+      117.27,
+      31.86
+    ]
+  },
+  {
+    name: '浙江',
+    abb: '浙',
+    geoCoord: [
+      120.19,
+      30.26
+    ]
+  },
+  {
+    name: '江西',
+    abb: '赣',
+    geoCoord: [
+      115.89,
+      28.68
+    ]
+  },
+  {
+    name: '福建',
+    abb: '闽',
+    geoCoord: [
+      119.3,
+      26.08
+    ]
+  },
+  {
+    name: '广东',
+    abb: '粤',
+    geoCoord: [
+      113.23,
+      23.16
+    ]
+  },
+  {
+    name: '湖南',
+    abb: '湘',
+    geoCoord: [
+      113,
+      28.21
+    ]
+  },
+  {
+    name: '海南',
+    abb: '琼',
+    geoCoord: [
+      110.35,
+      20.02
+    ]
+  },
+  {
+    name: '辽宁',
+    abb: '辽',
+    geoCoord: [
+      123.38,
+      41.8
+    ]
+  },
+  {
+    name: '吉林',
+    abb: '吉',
+    geoCoord: [
+      125.35,
+      43.88
+    ]
+  },
+  {
+    name: '黑龙江',
+    abb: '黑',
+    geoCoord: [
+      126.63,
+      45.75
+    ]
+  },
+  {
+    name: '山西',
+    abb: '晋',
+    geoCoord: [
+      112.53,
+      37.87
+    ]
+  },
+  {
+    name: '陕西',
+    abb: '陕',
+    geoCoord: [
+      108.95,
+      34.27
+    ]
+  },
+  {
+    name: '台湾',
+    abb: '台',
+    geoCoord: [
+      121.30,
+      25.03
+    ]
+  },
+  // 4直辖市
+  {
+    name: '北京',
+    abb: '京',
+    geoCoord: [
+      116.46,
+      39.92
+    ]
+  },
+  {
+    name: '上海',
+    abb: '沪',
+    geoCoord: [
+      121.48,
+      31.22
+    ]
+  },
+  {
+    name: '重庆',
+    abb: '渝',
+    geoCoord: [
+      106.54,
+      29.59
+    ]
+  },
+  {
+    name: '天津',
+    abb: '津',
+    geoCoord: [
+      117.2,
+      39.13
+    ]
+  },
+  // 5自治区
+  {
+    name: '内蒙古',
+    abb: '蒙',
+    geoCoord: [
+      111.65,
+      40.82
+    ]
+  },
+  {
+    name: '广西',
+    abb: '桂',
+    geoCoord: [
+      108.33,
+      22.84
+    ]
+  },
+  {
+    name: '西藏',
+    abb: '藏',
+    geoCoord: [
+      91.11,
+      29.97
+    ]
+  },
+  {
+    name: '宁夏',
+    abb: '宁',
+    geoCoord: [
+      106.27,
+      38.47
+    ]
+  },
+  {
+    name: '新疆',
+    abb: '新',
+    geoCoord: [
+      87.68,
+      43.77
+    ]
+  },
+  // 2特别行政区
+  {
+    name: '香港',
+    abb: '港',
+    geoCoord: [
+      114.17,
+      22.28
+    ]
+  },
+  {
+    name: '澳门',
+    abb: '澳',
+    geoCoord: [
+      113.54,
+      22.19
+    ]
+  }
+]
+export default provincePosition

+ 107 - 0
src/components/clock.vue

@@ -0,0 +1,107 @@
+<!--
+ * @Author: wjc
+ * @Date: 2024-04-29 17:56:36
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-12-12 15:22:31
+ * @Description: 数字时钟组件(Vue2 纯 JS 版本)
+-->
+<template>
+  <div class="digital-clock">
+    <span class="date">{{ date }}</span>
+    <span class="digit">{{ hours }}</span>
+    <span class="colon">:</span>
+    <span class="digit">{{ minutes }}</span>
+    <span class="colon">:</span>
+    <span class="digit">{{ seconds }}</span>
+  </div>
+</template>
+
+<script>
+  import dayjs from "dayjs"
+
+  export default {
+    name: "Clock",
+    data() {
+      return {
+        hours: "", // 小时(响应式)
+        minutes: "", // 分钟(响应式)
+        seconds: "", // 秒数(响应式)
+        prevTime: 0, // 上一次更新的时间戳
+        myReq: null, // requestAnimationFrame 标识
+      }
+    },
+    computed: {
+      date() {
+        return dayjs().format("YYYY-MM-DD")
+      },
+    },
+    methods: {
+      // 补零方法(数字小于10时前面加0)
+      padZero(value) {
+        return value < 10 ? `0${value}` : value.toString()
+      },
+      // 更新时间核心逻辑
+      updateTime() {
+        const date = new Date()
+        this.hours = this.padZero(date.getHours())
+        this.minutes = this.padZero(date.getMinutes())
+        this.seconds = this.padZero(date.getSeconds())
+      },
+      // 高性能时钟刷新(基于requestAnimationFrame,替代setInterval)
+      updateClock() {
+        const currentTime = performance.now()
+        const deltaTime = currentTime - this.prevTime
+
+        // 每1000ms(1秒)仅更新一次,避免高频无效渲染
+        if (deltaTime >= 1000) {
+          this.updateTime()
+          this.prevTime = currentTime
+        }
+
+        // 循环调用requestAnimationFrame(跟随浏览器帧率)
+        this.myReq = requestAnimationFrame(this.updateClock)
+      },
+    },
+    // Vue2 生命周期钩子
+    beforeMount() {
+      // 初始化时间(组件挂载前先渲染一次)
+      this.updateTime()
+    },
+    mounted() {
+      // 初始化时间戳,启动时钟刷新循环
+      this.prevTime = performance.now()
+      this.updateClock()
+    },
+    beforeDestroy() {
+      // 组件销毁时取消动画帧,避免内存泄漏
+      if (this.myReq) {
+        cancelAnimationFrame(this.myReq)
+      }
+    },
+  }
+</script>
+
+<style scoped lang="scss">
+  @import "@/assets/css/theme.scss";
+
+  .digital-clock {
+    font-weight: 500;
+    color: #818181;
+    font-size: halfW(18);
+    width: max-content;
+    text-align: center;
+    padding: 0 halfW(8);
+    .date {
+      margin-right: halfW(10);
+    }
+    .digit {
+      display: inline-block;
+      width: 1.6em;
+    }
+
+    .colon {
+      display: inline-block;
+      width: 4px;
+    }
+  }
+</style>

+ 89 - 88
src/components/digit-roll/index.vue

@@ -7,55 +7,56 @@
       transitionTimingFunction: easing
     }"
   >
-    <template v-for="(item, index) in valueFormat">
-      <span
-        :key="index"
-        :class="[
-          'dr-element',
-          /\d+/.test(item)
-            ? 'dr-digit'
-            : item == resetFormat.radix
-            ? 'dr-radix-mark'
-            : 'dr-formatting-mark'
-        ]"
-      >
-        <span class="dr-spacer">
-          {{ /\d+/.test(item) ? 8 : item }}
-        </span>
-        <template v-if="/\d+/.test(item)">
-          <span
-            class="dr-roll"
-            :style="{
-              transform: state
-                ? 'translateY(' + -item * 10 + '%' + ')'
-                : 'translteY(0)'
-            }"
-          >
+    <template >
+      <div v-for="(item, index) in valueFormat" :key="index">
+        <span
+          :class="[
+            'dr-element',
+            /\d+/.test(item)
+              ? 'dr-digit'
+              : item == resetFormat.radix
+              ? 'dr-radix-mark'
+              : 'dr-formatting-mark'
+          ]"
+        >
+          <span class="dr-spacer">
+            {{ /\d+/.test(item) ? 8 : item }}
+          </span>
+          <template v-if="/\d+/.test(item)">
             <span
-              class="dr-roll-element"
-              v-for="(_item, _index) in 10"
-              :key="_index"
+              class="dr-roll"
+              :style="{
+                transform: state
+                  ? 'translateY(' + -item * 10 + '%' + ')'
+                  : 'translteY(0)'
+              }"
             >
-              {{ _index }}
+              <span
+                class="dr-roll-element"
+                v-for="(_item, _index) in 10"
+                :key="_index"
+              >
+                {{ _index }}
+              </span>
             </span>
-          </span>
-        </template>
-      </span>
+          </template>
+        </span>
+      </div>
     </template>
   </span>
 </template>
 
 <script>
-import _ from "./utils";
+import _ from './utils'
 
-let DIGIT_FORMAT = "(,ddd).dd";
-let FORMAT_PARSER = /^\(?([^)]*)\)?(?:(.)(d+))?$/;
+let DIGIT_FORMAT = '(,ddd).dd'
+let FORMAT_PARSER = /^\(?([^)]*)\)?(?:(.)(d+))?$/
 
 export default {
-  name: "digit-roll",
+  name: 'digit-roll',
   model: {
-    prop: "value",
-    event: "change"
+    prop: 'value',
+    event: 'change'
   },
   props: {
     value: {
@@ -72,7 +73,7 @@ export default {
     },
     easing: {
       type: String,
-      default: "linear"
+      default: 'linear'
     },
     format: {
       type: String,
@@ -82,109 +83,109 @@ export default {
       // 小数位取整函数
       // ceil:向上取整 floor:向下取整 round:四舍五入
       type: String,
-      default: "round"
+      default: 'round'
     }
   },
   computed: {
-    resetFormat() {
-      let f = FORMAT_PARSER.exec(this.format).slice(1);
+    resetFormat () {
+      let f = FORMAT_PARSER.exec(this.format).slice(1)
 
       return {
         repeating: f[0],
         radix: f[1],
         precision: f[2].length
-      };
+      }
     },
-    valueFormat() {
-      const WFLAG = true;
-      let repeating = this.resetFormat.repeating;
-      let _arr = [];
+    valueFormat () {
+      // const WFLAG = true
+      let repeating = this.resetFormat.repeating
+      let _arr = []
       let initArr = this.createRound(this.math)(
         this.value,
         this.resetFormat.precision
       )
         .toString()
-        .split("")
-        .reverse();
-      if (this.value < 0) initArr = initArr.slice(0, -1);
+        .split('')
+        .reverse()
+      if (this.value < 0) initArr = initArr.slice(0, -1)
 
-      let radixFlag = !initArr.includes(this.resetFormat.radix);
+      let radixFlag = !initArr.includes(this.resetFormat.radix)
 
       initArr.forEach(item => {
-        let flag = false;
+        let flag = false
 
-        if (item == this.resetFormat.radix) {
-          radixFlag = true;
-          _arr.unshift(item);
+        if (item === this.resetFormat.radix) {
+          radixFlag = true
+          _arr.unshift(item)
         } else {
           if (radixFlag) {
-            flag = false;
+            flag = false
 
-            while (WFLAG) {
+            while (1 < 2) {
               if (!repeating.length) {
                 if (flag) {
-                  throw new Error("Bad odometer format without digits");
+                  throw new Error('Bad odometer format without digits')
                 }
-                repeating = this.resetFormat.repeating;
-                flag = true;
+                repeating = this.resetFormat.repeating
+                flag = true
               }
 
-              let chr = repeating[repeating.length - 1];
-              repeating = repeating.substring(0, repeating.length - 1);
-              if (chr === "d") {
-                break;
+              let chr = repeating[repeating.length - 1]
+              repeating = repeating.substring(0, repeating.length - 1)
+              if (chr === 'd') {
+                break
               }
 
-              _arr.unshift(chr);
+              _arr.unshift(chr)
             }
           }
 
-          _arr.unshift(item);
+          _arr.unshift(item)
         }
-      });
+      })
 
-      if (this.value < 0) _arr.unshift("-");
-      return _arr;
+      if (this.value < 0) _arr.unshift('-')
+      return _arr
     }
   },
   watch: {
     // 维护state,初始、新生成的dom有动画
     valueFormat: {
-      handler(nVal, oVal) {
-        this.state = false;
+      handler (nVal, oVal) {
+        this.state = false
         _.debounce(() => {
-          this.state = true;
-        }, 1)();
+          this.state = true
+        }, 1)()
       },
       immediate: true
     }
   },
-  data() {
+  data () {
     return {
       state: false
-    };
+    }
   },
-  mounted() {},
+  mounted () {},
   methods: {
-    getTime(time) {
-      return time ? `${parseFloat(time / 1000)}s` : time;
+    getTime (time) {
+      return time ? `${parseFloat(time / 1000)}s` : time
     },
-    createRound(methodName) {
-      let func = Math[methodName];
-      return function(number, precision) {
-        precision = precision == null ? 0 : Math.min(precision, 292);
+    createRound (methodName) {
+      let func = Math[methodName]
+      return function (number, precision) {
+        precision = precision == null ? 0 : Math.min(precision, 292)
         if (precision && number) {
-          let pair = (String(number) + "e").split("e"),
-            value = func(pair[0] + "e" + (+pair[1] + precision));
+          let pair = (String(number) + 'e').split('e')
+          let value = func(pair[0] + 'e' + (+pair[1] + precision))
 
-          pair = (String(value) + "e").split("e");
-          return +(pair[0] + "e" + (+pair[1] - precision));
+          pair = (String(value) + 'e').split('e')
+          return +(pair[0] + 'e' + (+pair[1] - precision))
         }
-        return func(number);
-      };
+        return func(number)
+      }
     }
   }
-};
+}
 </script>
 
 <style scoped>

+ 11 - 9
src/components/digit-roll/utils/index.js

@@ -6,19 +6,21 @@
  * @Description:
  */
 export default class {
-  constructor() { }
+  constructor () {
+    throw new Error('该类不能被实例化')
+  }
 
   // 防抖动
-  static debounce(fn, delay = 200) {
-    let timer = null;
+  static debounce (fn, delay = 200) {
+    let timer = null
     return function () {
-      let context = this;
-      let args = arguments;
+      let context = this
+      let args = arguments
 
-      clearTimeout(timer);
+      clearTimeout(timer)
       timer = setTimeout(function () {
-        fn.apply(context, args);
-      }, delay);
-    };
+        fn.apply(context, args)
+      }, delay)
+    }
   }
 }

+ 24 - 0
src/libs/tools.js

@@ -0,0 +1,24 @@
+/**
+ * @param {Number} money 大额数值
+ * @param {Number} fiexed 保留几位数
+ * @returns {Number} 无单位的数值
+ */
+export const getBigNumber = (money, fiexed = 2) => {
+  if (money >= 10000) {
+    return Number((Number(money) / 10000).toFixed(fiexed))
+  } else {
+    return Number(money)
+  }
+}
+/**
+ * @param {Number} money 大额数值
+ * @param {Number} fiexed 保留几位数
+ * @returns {String} 单位是万
+ */
+export const getBigNumberWithUint = (money, fiexed = 2) => {
+  if (money >= 10000) {
+    return getBigNumber(money, fiexed) + '万'
+  } else {
+    return money
+  }
+}

+ 25 - 2
src/main.js

@@ -2,7 +2,7 @@
  * @Author: WangQiBiao
  * @Date: 2019-09-24 17:50:53
  * @LastEditors: wjc
- * @LastEditTime: 2024-05-11 10:58:33
+ * @LastEditTime: 2025-12-12 16:38:42
  * @Description:
  */
 import Vue from 'vue'
@@ -12,16 +12,39 @@ import store from './store'
 import 'reset.css'
 import 'babel-polyfill'
 import * as filters from './filters'
-
+import VueTianditu from 'vue-tianditu'
 import ElementUI from 'element-ui'
 import 'element-ui/lib/theme-chalk/index.css'
 import '@/assets/css/element-variables.scss'
+import '@/assets/css/font.css'
 
 Vue.use(ElementUI)
 // 引入全局过滤器
 Object.keys(filters).forEach(key => {
   Vue.filter(key, filters[key])
 })
+Vue.filter('safeGet', function (obj, path, defaultValue = '无') {
+  // 分割路径(如 'service.work_template.display_name' 拆分为数组)
+  const keys = path.split('.')
+  let result = obj
+  for (const key of keys) {
+    // 若中间层级为 null/undefined,直接返回默认值
+    if (result === null || result === undefined) return defaultValue
+    result = result[key]
+  }
+  // 最终值为空时返回默认值
+  return result || defaultValue
+})
+Vue.filter('formatNumber', function (num = 0) {
+  return (+num).toLocaleString('zh', {
+    maximumFractionDigits: 2
+  })
+})
+
+Vue.use(VueTianditu, {
+  v: '4.0',
+  tk: 'c1dc742aa22263a258fdc7b2a9ae8172'
+})
 
 Vue.config.productionTip = false
 

+ 1 - 0
src/route/index.js

@@ -7,6 +7,7 @@
  */
 import Vue from 'vue'
 import Router from 'vue-router'
+// import _import from './_import'
 import whiteList from './white-list'
 import { filtersPermisList } from './permis-list'
 import store from '@/store'

+ 60 - 2
src/route/white-list.js

@@ -1,8 +1,8 @@
 /*
  * @Author: WangQiBiao
  * @Date: 2019-09-18 09:40:26
- * @LastEditors: wangjiacheng
- * @LastEditTime: 2021-10-14 12:00:08
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-12-02 16:19:44
  * @Description: 白名单菜单
  */
 import _import from './_import'
@@ -39,6 +39,64 @@ export default [
       icon: ''
     }
   },
+  {
+    path: '/hui-jia',
+    component: () => import('@/components/main/index.vue'),
+    meta: {
+      title: '绘家科技',
+      hideInMenu: false
+    },
+    children: [
+      {
+        path: 'b1',
+        component: () => import('@/views/hui-jia/b1-screen/index.vue'),
+        meta: {
+          title: '绘家科技',
+          hideInMenu: false
+        }
+      },
+      {
+        path: 'b2',
+        component: () => import('@/views/hui-jia/b2-screen/index.vue'),
+        meta: {
+          title: '绘家科技',
+          hideInMenu: false
+        }
+      },
+      {
+        path: 'a1',
+        component: () => import('@/views/hui-jia/a1-screen/index.vue'),
+        meta: {
+          title: '绘家科技',
+          hideInMenu: false
+        }
+      },
+      {
+        path: 'a2',
+        component: () => import('@/views/hui-jia/a2-screen/index.vue'),
+        meta: {
+          title: '绘家科技',
+          hideInMenu: false
+        }
+      },
+      {
+        path: 'a3',
+        component: () => import('@/views/hui-jia/a3-screen/index.vue'),
+        meta: {
+          title: '绘家科技',
+          hideInMenu: false
+        }
+      },
+      {
+        path: 'a4',
+        component: () => import('@/views/hui-jia/a4-screen/index.vue'),
+        meta: {
+          title: '绘家科技',
+          hideInMenu: false
+        }
+      }
+    ]
+  },
   {
     path: '/login',
     component: _import('login'),

+ 90 - 0
src/views/hui-jia/a1-screen/index.vue

@@ -0,0 +1,90 @@
+<!--
+ * @Author: wjc
+ * @Date: 2025-11-25 10:30:54
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-12-12 16:54:23
+ * @Description: 
+-->
+<template>
+    <div class="screen">
+      <Title />
+      <div class="screen-content">
+        <div class="screen-top">
+          <div class="item-l">
+            <OnlineWater />
+          </div>
+          <div class="item-r">
+            <OnlinePay />
+          </div>
+        </div>
+        <div class="screen-bottom">
+          <div class="item-l">
+            <OnlinePayWeek />
+          </div>
+          <div class="item-r">
+            <OnlinePayDay />
+          </div>
+        </div>
+      </div>
+    </div>
+</template>
+<script>
+import Title from '@/views/hui-jia/components/title'
+import OnlineWater from '../components/online-water'
+import OnlinePay from '../components/online-pay'
+import OnlinePayWeek from '../components/online-pay-week'
+import OnlinePayDay from '../components/online-pay-day'
+
+export default {
+  name: 'A1Screen',
+  components: {
+    Title,
+    OnlineWater,
+    OnlinePay,
+    OnlinePayWeek,
+    OnlinePayDay
+  }
+}
+</script>
+<style lang="scss">
+@import '@/assets/css/function.scss';
+@import '@/assets/css/screen.scss';
+
+.screen-content{
+  width: calc(100% - halfW(8) * 2);
+  height: calc(100% - $title-hd - halfH(8));
+  display: flex;
+  flex-direction: column;
+  gap: halfW(16);
+  padding: 0 halfW(8) halfW(8) halfW(8);
+  font-family: DreamHanSansCN;
+  .screen-top{
+    width: 100%;
+    flex: 1 1 0%;
+    display: flex;
+    gap: halfW(16);
+    .item-l{
+      flex: 1 1 0%;
+      // background-color: red;
+    }
+    .item-r{
+      flex: 2 1 0%;
+      // background-color: green;
+    }
+  }
+  .screen-bottom{
+    width: 100%;
+    flex: 1 1 0%;
+    display: flex;
+    gap: halfW(16);
+    .item-l{
+      flex: 1 1 0%;
+      // background-color: blue;
+    }
+    .item-r{
+      flex: 1 1 0%;
+      // background-color: yellow;
+    }
+  }
+}
+</style>

+ 83 - 0
src/views/hui-jia/a2-screen/index.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="screen">
+    <Title />
+    <div class="screen-content">
+      <div class="screen-l">
+        <CommunityMap class="community-map" />
+      </div>
+      <div class="screen-r">
+          <div class="screen-r-top">
+            <User />
+          </div>
+          <div class="screen-r-bottom">
+            <div class="item-l">
+              <Resident />
+            </div>
+            <div class="item-r">
+              <CommunityType />
+            </div>
+          </div>
+       </div>
+    </div>
+  </div>
+</template>
+<script>
+import Title from '@/views/hui-jia/components/title'
+import CommunityType from '@/views/hui-jia/components/community-type'
+import Resident from '@/views/hui-jia/components/resident'
+import User from '@/views/hui-jia/components/user'
+import CommunityMap from '@/views/hui-jia/components/community-map'
+
+export default {
+  name: 'A2Screen',
+  components: {
+    Title,
+    CommunityType,
+    Resident,
+    User,
+    CommunityMap
+
+  }
+}
+</script>
+<style lang="scss" scoped>
+@import '@/assets/css/function.scss';
+@import '@/assets/css/screen.scss';
+
+.screen-content{
+  width: calc(100% - halfW(8) * 2);
+  height: calc(100% - $title-hd - halfH(8));
+  padding: 0 halfW(8) halfW(8) halfW(8);
+  display: flex;
+  flex-direction: row;
+  gap: halfW(16);
+  font-family: DreamHanSansCN;
+  .screen-l{
+    width: calc(35%);
+    flex: 1 1 0%;
+    height: calc(100%);
+  }
+  .screen-r{
+    width: calc(65%);
+    display: flex;
+    flex-direction: column;
+    gap: halfH(16);
+    flex: 2 1 0%;
+    .screen-r-top{
+      flex: 1 1 0%;
+    }
+    .screen-r-bottom{
+      flex: 2 1 0%;
+      display: flex;
+      gap: halfW(16);
+      .item-l{
+        flex: 1 1 0%;
+      }
+      .item-r{
+        flex: 1 1 0%;
+      }
+    }
+  }
+}
+
+</style>

+ 115 - 0
src/views/hui-jia/a3-screen/index.vue

@@ -0,0 +1,115 @@
+<!--
+ * @Author: wjc
+ * @Date: 2025-11-19 16:09:12
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-12-12 16:54:33
+ * @Description: 
+-->
+<template>
+  <div class="a3-screen">
+    <div class="a3-screen-content">
+      <div class="left-wrap">
+        <Hardware />
+      </div>
+      <div class="right-wrap">
+        <div class="top">
+          <Smart />
+        </div>
+        <WorkCard />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import Smart from "../components/smart.vue"
+  import Hardware from "../components/hardware.vue"
+  import WorkCard from "../components/work-card.vue"
+  import OnlineWater from "../components/online-water"
+  import OnlinePay from "../components/online-pay"
+  import OnlinePayWeek from "../components/online-pay-week"
+  import OnlinePayDay from "../components/online-pay-day"
+
+  export default {
+    name: "B1Screen",
+    components: {
+      Smart,
+      Hardware,
+      WorkCard,
+      OnlineWater,
+      OnlinePay,
+      OnlinePayWeek,
+      OnlinePayDay,
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/css/theme.scss";
+  @import "@/assets/css/screen.scss";
+  $gap-padding: halfW(8);
+  .a3-screen {
+    width: calc(100vw - halfW(8) * 2);
+    height: calc(100vh - halfH(8) * 2);
+    padding: halfW(8) halfW(8) halfW(8) halfW(8);
+    background: var(--page-bg);
+    font-family: DreamHanSansCN;
+  }
+  .a3-screen-content {
+    height: 100%;
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-start;
+    gap: halfW(16);
+    .left-wrap {
+      display: flex;
+      flex-direction: column;
+      gap: halfW(16);
+      width: calc(65% - $gap-padding);
+      .item-1 {
+        width: 100%;
+        height: calc(50% - $gap-padding);
+        background: #f5f5f5;
+      }
+      .online-water {
+        width: 100%;
+        height: calc(50% - $gap-padding);
+      }
+      .smart-door {
+        width: 100%;
+        height: calc(50% - $gap-padding);
+      }
+    }
+    .right-wrap {
+      width: calc(35% - $gap-padding);
+      height: 100%;
+      display: flex;
+      flex-wrap: wrap;
+      gap: halfW(16);
+      justify-content: flex-start;
+      .top {
+        flex: 1 1 0%;
+        display: flex;
+        flex-direction: column;
+        gap: calc($gap-padding * 2);
+        width: 100%;
+        height: calc(60% - $gap-padding);
+      }
+      .work-card-container {
+        width: 100%;
+        height: calc(40% - $gap-padding);
+        ::v-deep.work-card {
+            display: grid !important;
+            grid-template-columns: repeat(3, 1fr) !important;
+            grid-template-rows: repeat(2, 1fr) !important;
+              padding: halfH(20) halfW(20);
+            .work-card-item {
+              align-self: stretch;
+              justify-content: center;
+            }
+        }
+      }
+    }
+  }
+</style>

+ 199 - 0
src/views/hui-jia/a4-screen/index.vue

@@ -0,0 +1,199 @@
+<template>
+  <div class="a4-screen">
+    <div class="a4-screen-content">
+      <div class="top-wrap">
+        <implementationTotal :data="state.summary" />
+        <implementationIng :data="state.data" />
+      </div>
+      <div class="bottom-wrap">
+        <implementationNowYear :data="serviceData" />
+        <Market :data="state.market_vs_month" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import implementationTotal from "../components/implementary/total.vue"
+  import implementationIng from "../components/implementary/ing.vue"
+  import implementationNowYear from "../components/implementary/now-year.vue"
+  import Market from "../components/market.vue"
+
+  import { huiJiaApi } from "@/api"
+
+  export default {
+    name: "A4Screen",
+    components: {
+      implementationTotal,
+      implementationIng,
+      implementationNowYear,
+      Market,
+    },
+    data() {
+      return {
+        state: {
+          data: [], // 进行中的实施服务列表
+          market_vs_month: {
+            customer: {
+              // 新增企业
+              last_month: 0,
+              this_month: 0,
+            },
+            followup: {
+              // 新增企业
+              last_month: 0,
+              this_month: 0,
+            },
+            contact: {
+              // 新增联系人
+              last_month: 0,
+              this_month: 0,
+            },
+            residence: {
+              // 新增小区
+              last_month: 0,
+              this_month: 0,
+            },
+            contract: {
+              // 新增合同
+              last_month: 0,
+              this_month: 0,
+            },
+            household: {
+              // 新增户数
+              last_month: 0,
+              this_month: 1,
+            },
+          }, // 市场拓展数据
+          summary: {
+            cities: 0, // 城市总数
+            customers: 0, // 企业总数
+            contracts: 0, // 合同总数
+            perform_times: 0, // 履约次数
+            service_time: 0, // 服务时长
+            residences: 0, // 小区总数
+            contracted_households: 0, // 签约户数
+            actual_households: 0, // 实施户数
+          }, // 实施服务,汇总
+          year_summary: {
+            cities: 0, // 履约城市
+            customers: 0, // 服务企业
+            residences: 0, // 服务小区
+            contracts: 0, // 合同份数
+            contracted_households: "0", // 签约户数
+            actual_households: "0", // 实施户数
+          },
+          service_ing: {
+            // 实施服务,进行中
+            cities: 0,
+            customers: 0,
+            contracts: 0,
+            residences: 0,
+            contracted_households: "0",
+            plan: 0,
+          },
+          service_last_month: {
+            // 实施服务,上月
+            cities: 0,
+            customers: 0,
+            contracts: 0,
+            residences: 0,
+            contracted_households: 0,
+            actual_households: 0,
+          },
+        },
+        serviceData: {},
+      }
+    },
+    mounted() {
+      this.getData()
+    },
+    methods: {
+      getServiceData() {
+        // 图表字段顺序
+        const properties = [
+          "contracted_households",
+          "contracts",
+          "residences",
+          "customers",
+          "cities",
+        ]
+        const result = {
+          year_summary: properties.map((key) => this.state.year_summary[key]),
+          service_ing: properties.map((key) => this.state.service_ing[key]),
+          service_last_month: properties.map(
+            (key) => this.state.service_last_month[key]
+          ),
+        }
+        this.serviceData = result
+      },
+      getData() {
+        huiJiaApi.getScreenData().then((res) => {
+          if (res && res.data) {
+            this.state = res.data.data
+            this.getServiceData()
+          }
+        })
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/css/theme.scss";
+
+  $gap-padding: halfW(8);
+  .a4-screen {
+    width: calc(100vw - halfW(8) * 2);
+    height: calc(100vh - halfH(8) * 2);
+    padding: halfW(8);
+    background: var(--page-bg);
+    font-family: DreamHanSansCN;
+  }
+  .a4-screen-content {
+    height: 100%;
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    align-items: center;
+    gap: halfW(16);
+    .top-wrap {
+      display: flex;
+      justify-content: space-around;
+      gap: halfW(16);
+      width: 100%;
+      height: calc(50% - $gap-padding);
+      .total-container {
+        width: calc(35% - $gap-padding);
+        ::v-deep .total-grid {
+          grid-template-columns: repeat(2, 1fr);
+          grid-template-rows: repeat(4, 1fr);
+          .total-item {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            align-self: stretch;
+            justify-content: center;
+          }
+        }
+      }
+      .implementing-container {
+        width: calc(65% - $gap-padding);
+      }
+    }
+    .bottom-wrap {
+      display: flex;
+      justify-content: space-around;
+      gap: halfW(16);
+      width: 100%;
+      height: calc(50% - $gap-padding);
+      .now-year-container {
+        width: calc(65% - $gap-padding);
+      }
+      .market-container {
+        width: calc(35% - $gap-padding);
+      }
+    }
+  }
+</style>

+ 176 - 0
src/views/hui-jia/b1-screen/index.vue

@@ -0,0 +1,176 @@
+<!--
+ * @Author: wjc
+ * @Date: 2025-11-19 16:09:12
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-12-12 16:55:20
+ * @Description: 
+-->
+<template>
+  <div class="b2-screen">
+    <div class="logo">
+      <Clock />
+      <img src="@/assets/images/hjkj1.png" class="logo-img" />
+    </div>
+    <div class="b2-screen-content">
+      <div class="left-wrap">
+        <OnlineWater :showOnlineInfo="true" />
+        <Smart />
+      </div>
+      <div class="middle-wrap">
+        <div class="top">
+          <div class="top-left">
+            <div class="item-2">
+              <OnlinePay
+                :showLegend="false"
+                :showTitle="false"
+                :showOnlineInfo="true"
+              />
+            </div>
+            <div class="item-3">
+              <OnlinePayWeek :showMarkPoint="false" :showInfo="false" />
+            </div>
+            <div class="item-4">
+              <OnlinePayDay :showMarkPoint="false" :showInfo="false" />
+            </div>
+          </div>
+          <Hardware />
+        </div>
+        <WorkCard />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import Clock from "@/components/clock.vue"
+
+  import Smart from "../components/smart.vue"
+  import Hardware from "../components/hardware.vue"
+  import WorkCard from "../components/work-card.vue"
+  import OnlineWater from "../components/online-water"
+  import OnlinePay from "../components/online-pay"
+  import OnlinePayWeek from "../components/online-pay-week"
+  import OnlinePayDay from "../components/online-pay-day"
+
+  export default {
+    name: "B1Screen",
+    components: {
+      Clock,
+      Smart,
+      Hardware,
+      WorkCard,
+      OnlineWater,
+      OnlinePay,
+      OnlinePayWeek,
+      OnlinePayDay,
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/css/theme.scss";
+  @import "@/assets/css/screen.scss";
+  $gap-padding: halfW(8);
+  .b2-screen {
+    width: calc(100vw - halfW(8) * 2);
+    height: calc(100vh - halfH(8));
+    padding: 0 halfW(8) halfW(8);
+    background: var(--page-bg);
+    font-family: DreamHanSansCN;
+    .logo {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      text-align: right;
+      padding: halfH(10) 0;
+      margin-right: halfW(-8);
+      .logo-img {
+        height: halfH(64);
+      }
+    }
+  }
+  .b2-screen-content {
+    height: calc(100% - halfH(64) - halfH(28));
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: flex-start;
+    gap: halfW(16);
+    .left-wrap {
+      display: flex;
+      flex-direction: column;
+      gap: halfW(16);
+      width: calc(25% - $gap-padding);
+      .item-1 {
+        width: 100%;
+        height: calc(50% - $gap-padding);
+        background: #f5f5f5;
+      }
+      .online-water {
+        width: 100%;
+        height: calc(50% - $gap-padding);
+      }
+      .smart-door {
+        width: 100%;
+        height: calc(50% - $gap-padding);
+      }
+    }
+    .middle-wrap {
+      width: calc(75% - $gap-padding);
+      height: 100%;
+      display: flex;
+      flex-wrap: wrap;
+      gap: halfW(16);
+      justify-content: flex-start;
+      .top {
+        flex: 1 1 0%;
+        display: flex;
+        gap: calc($gap-padding * 2);
+        width: 100%;
+        height: calc(80% - $gap-padding);
+        .top-left {
+          display: flex;
+          flex-direction: column;
+          width: 50%;
+          height: 100%;
+          gap: halfW(16);
+          .item-2 {
+            flex: 1 1 0%;
+            width: 100%;
+            // max-height: halfH(200);
+            // background: #f5f5f5;
+          }
+          .item-3 {
+            flex: 1 1 0%;
+            // background: green;
+            width: 100%;
+            // max-height: halfH(200);
+          }
+          .item-4 {
+            flex: 1 1 0%;
+            width: 100%;
+
+            // max-height: halfH(200);
+            // background: green;
+          }
+        }
+        .hardware-data {
+          width: 50%;
+          height: 100%;
+        }
+      }
+      .work-card-container {
+        width: 100%;
+        height: calc(20% - $gap-padding);
+        ::v-deep .work-card {
+          padding: halfH(10) halfW(10);
+          .work-card-item {
+            align-self: stretch;
+            justify-content: center;
+            padding: halfH(10) halfW(20);
+          }
+        }
+      }
+    }
+  }
+</style>

+ 262 - 0
src/views/hui-jia/b2-screen/index.vue

@@ -0,0 +1,262 @@
+<template>
+  <div class="b1-screen">
+    <div class="logo">
+      <img src="@/assets/images/hjkj2.png" class="logo-img" />
+      <Clock />
+    </div>
+    <div class="b1-screen-content">
+      <div class="item-1">
+        <CommunityMap :center="[105,34]" :zoom="4" />
+      </div>
+      <div class="middle-wrap">
+        <div class="item-2"><User />></div>
+        <div class="item-3">
+          <Resident :showOperate="false" class="b1-resident" />
+        </div>
+        <div class="item-4">
+          <CommunityType :showBar="false" class="b1-community-type" />
+        </div>
+      </div>
+      <div class="right-wrap">
+        <Market :data="state.market_vs_month" />
+        <implementationTotal :data="state.summary" />
+      </div>
+      <div class="bottom-wrap">
+        <implementationIng :data="state.data" />
+        <implementationNowYear :data="serviceData" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import Clock from "@/components/clock.vue"
+  import implementationTotal from "../components/implementary/total.vue"
+  import implementationIng from "../components/implementary/ing.vue"
+  import implementationNowYear from "../components/implementary/now-year.vue"
+  import Market from "../components/market.vue"
+  import CommunityMap from "../components/community-map"
+  import CommunityType from "../components/community-type"
+  import Resident from "../components/resident"
+  import User from "../components/user"
+
+  import { huiJiaApi } from "@/api"
+
+  export default {
+    name: "B1Screen",
+    components: {
+      Clock,
+      implementationTotal,
+      implementationIng,
+      implementationNowYear,
+      Market,
+      CommunityType,
+      Resident,
+      User,
+      CommunityMap,
+    },
+    data() {
+      return {
+        state: {
+          data: [], // 进行中的实施服务列表
+          market_vs_month: {
+            customer: {
+              // 新增企业
+              last_month: 0,
+              this_month: 0,
+            },
+            followup: {
+              // 跟进企业
+              last_month: 0,
+              this_month: 0,
+            },
+            contact: {
+              // 新增联系人
+              last_month: 0,
+              this_month: 0,
+            },
+            residence: {
+              // 新增小区
+              last_month: 0,
+              this_month: 0,
+            },
+            contract: {
+              // 新增合同
+              last_month: 0,
+              this_month: 0,
+            },
+            household: {
+              // 新增户数
+              last_month: 0,
+              this_month: 1,
+            },
+          }, // 市场拓展数据
+          summary: {
+            cities: 0, // 城市总数
+            customers: 0, // 企业总数
+            contracts: 0, // 合同总数
+            perform_times: 0, // 履约次数
+            service_time: 0, // 服务时长
+            residences: 0, // 小区总数
+            contracted_households: 0, // 签约户数
+            actual_households: 0, // 实施户数
+          }, // 实施服务,汇总
+          year_summary: {
+            cities: 0, // 履约城市
+            customers: 0, // 服务企业
+            residences: 0, // 服务小区
+            contracts: 0, // 合同份数
+            contracted_households: "0", // 签约户数
+            actual_households: "0", // 实施户数
+          },
+          service_ing: {
+            // 实施服务,进行中
+            cities: 0,
+            customers: 0,
+            contracts: 0,
+            residences: 0,
+            contracted_households: "0",
+            plan: 0,
+          },
+          service_last_month: {
+            // 实施服务,上月
+            cities: 0,
+            customers: 0,
+            contracts: 0,
+            residences: 0,
+            contracted_households: 0,
+            actual_households: 0,
+          },
+        },
+        serviceData: {},
+      }
+    },
+    mounted() {
+      this.getData()
+    },
+    methods: {
+      getServiceData() {
+        // 图表字段顺序
+        const properties = [
+          "contracted_households",
+          "contracts",
+          "residences",
+          "customers",
+          "cities",
+        ]
+        const result = {
+          year_summary: properties.map((key) => this.state.year_summary[key]),
+          service_ing: properties.map((key) => this.state.service_ing[key]),
+          service_last_month: properties.map(
+            (key) => this.state.service_last_month[key]
+          ),
+        }
+        this.serviceData = result
+      },
+      getData() {
+        huiJiaApi.getScreenData().then((res) => {
+          if (res && res.data) {
+            this.state = res.data.data
+            this.getServiceData()
+          }
+        })
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/css/theme.scss";
+
+  $gap-padding: halfW(8);
+  .b1-screen {
+    height: 100vh;
+    width: 100vw;
+    background: var(--page-bg);
+    font-family: DreamHanSansCN;
+    .logo {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      text-align: left;
+      padding: halfH(10) 0;
+      .logo-img {
+        height: halfH(64);
+      }
+    }
+  }
+  .b1-screen-content {
+    height: calc(100% - halfH(64) - halfH(28));
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    align-items: center;
+    gap: halfW(16);
+    .item-1 {
+      width: calc(25% - $gap-padding);
+      height: calc(64% - $gap-padding - $gap-padding);
+    }
+    .middle-wrap {
+      width: calc(
+        50% - $gap-padding - $gap-padding - $gap-padding - $gap-padding
+      );
+      height: calc(64% - $gap-padding - $gap-padding);
+      display: flex;
+      flex-wrap: wrap;
+      gap: halfW(16);
+      justify-content: flex-start;
+      align-items: center;
+      .item-2 {
+        flex: 0 0 100%;
+        width: 100%;
+        height: calc(37% - $gap-padding);
+      }
+      .item-3 {
+        width: calc(50% - $gap-padding);
+        height: calc(63% - $gap-padding);
+      }
+      .item-4 {
+        width: calc(50% - $gap-padding);
+        height: calc(63% - $gap-padding);
+      }
+    }
+    .right-wrap {
+      display: flex;
+      flex-direction: column;
+      width: calc(25% - $gap-padding);
+      height: calc(64% - $gap-padding - $gap-padding);
+      gap: calc($gap-padding * 2);
+      .total-container {
+        height: calc(50%);
+        ::v-deep .total-grid {
+          .total-item {
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            align-self: stretch;
+          }
+        }
+      }
+      .market-container {
+        height: calc(50%);
+      }
+    }
+    .bottom-wrap {
+      display: flex;
+      justify-content: space-around;
+      gap: halfW(16);
+      width: calc(
+        100% - $gap-padding - $gap-padding - $gap-padding
+      );
+      height: calc(36% - $gap-padding);
+      .now-year-container {
+        width: 50%;
+      }
+      .implementing-container {
+        width: 50%;
+      }
+    }
+  }
+</style>

+ 2 - 0
src/views/hui-jia/components/card/index.js

@@ -0,0 +1,2 @@
+import Card from './index.vue'
+export default Card

+ 89 - 0
src/views/hui-jia/components/card/index.vue

@@ -0,0 +1,89 @@
+<!--
+ * @Author: wjc
+ * @Date: 2025-12-01 17:22:12
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-12-12 15:38:59
+ * @Description: 
+-->
+<template>
+    <div class="card">
+        <div class="card-header">
+            <div class="card-lf">
+              <img :src="icon" alt="icon" class="icon"/>
+              <span class="title">{{ title }}</span>
+              <span v-if="subTitle" class="sub-title">({{subTitle}})</span>
+            </div>
+            <div v-if="unit" class="unit">{{ unit }}</div>
+        </div>
+        <div class="card-body">
+          <slot></slot>
+        </div>
+    </div>
+</template>
+
+<script>
+export default {
+  name: 'card',
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    subTitle: {
+      type: String,
+      default: ''
+    },
+    unit: {
+      type: String,
+      default: ''
+    },
+    icon: {
+      type: String,
+      default: ''
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+@import '@/assets/css/function.scss';
+@import '@/assets/css/theme.scss';
+.card {
+    width: 100%;
+    height: 100%;
+    box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.4);
+    border-radius: var(--radius);
+    background: linear-gradient(180deg, rgba(14,22,41,1) 0%,rgba(14,22,41,0.6) 100%);
+    .card-header{
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      color: rgba(185, 227, 244, 1);
+      font-size: halfW(16);
+      padding:  halfH(4) halfW(10);
+      background: var(--title-bg);
+      border-radius: var(--radius);
+      .title{
+        display: inline-block;
+        vertical-align: middle;
+      }
+      .icon{
+        width: halfW(28);
+        height: halfW(28);
+        vertical-align: middle;
+        margin-right: halfW(8);
+      }
+      .sub-title{
+        font-size: halfW(12);
+      }
+      .unit {
+        font-size: halfW(12);
+        color: #818181;
+      }
+    }
+    .card-body{
+      padding: halfH(16) halfW(10);
+      box-sizing: border-box;
+      height: calc(100% - halfH(8) - halfH(16) - halfW(28));
+    }
+}
+</style>

+ 226 - 0
src/views/hui-jia/components/community-map/components/map-list.vue

@@ -0,0 +1,226 @@
+<!--
+ * @Author: Wzh
+ * @Date: 2020-01-23 10:48:49
+ * @LastEditors: wjc
+ * @LastEditTime: 2025-12-12 16:53:29
+ * @Description: 地图左下列表
+ -->
+<template>
+    <div
+      ref="customOverlay"
+      class="map-list-container"
+      v-show="isShow"
+    >
+      <div class="header">
+          <p class="list-name inline-block" v-text="title"></p>
+          <p class="list-count inline-block right" v-text="extra"></p>
+      </div>
+      <vueSeamless
+        :data="list"
+        :class-option="optionScroll"
+        class="map-scroll"
+      >
+        <div v-for="(info, index) in list" :key="index" class="list">
+          <p class="list-name inline-block" v-text="info.label"></p>
+          <p class="list-count inline-block right" v-text="info.content"></p>
+        </div>
+      </vueSeamless>
+      <!-- <WcScrollList :list="list" class="list">
+          <template v-slot:default="{ info }">
+          <p class="list-name inline-block" v-text="info.label"></p>
+          <p class="list-count inline-block right" v-text="info.content"></p>
+          </template>
+      </WcScrollList> -->
+    </div>
+</template>
+
+<script>
+// import WcScrollList from '_c/wc-scroll-list/wc-scroll-list.vue'
+import vueSeamless from "vue-seamless-scroll"
+
+export default {
+  components: {
+    vueSeamless
+    // WcScrollList
+  },
+  name: 'MapList',
+  props: {
+    title: {
+      default: '省份'
+    },
+    extra: {
+      default: '数量'
+    },
+    list: {
+      default: function () {
+        return [
+          {
+            label: '',
+            content: ''
+          }
+        ]
+      }
+    },
+    /**
+     * 相对父元素左上角的绝对位置
+     */
+    position: {
+      default: function () {
+        return {
+          top: 0,
+          left: 0
+        }
+      }
+    },
+    isShow: {
+      default: true
+    }
+  },
+  data() {
+    return {
+      mapData: [
+        {
+          label: '海南省',
+          content: '100'
+        },
+        {
+          label: '湖北省',
+          content: '100'
+        },
+        {
+          label: '湖南省',
+          content: '100'
+        },
+        {
+          label: '广东省',
+          content: '100'
+        },
+        {
+          label: '广西省',
+          content: '100'
+        },
+        {
+          label: '江西省',
+          content: '100'
+        },
+        {
+          label: '江苏省',
+          content: '100'
+        },
+        {
+          label: '浙江省',
+          content: '100'
+        },
+        {
+          label: '福建省',
+          content: '100'
+        },
+        {
+          label: '河南省',
+          content: '100'
+        },
+        {
+          label: '河北省',
+          content: '100'
+        },
+        {
+          label: '河南省',
+          content: '100'
+        },
+        {
+          label: '河北省',
+          content: '100'
+        }
+      ]
+    }
+  },
+  computed: {
+    optionScroll() {
+        return {
+          step: 0.5,
+        }
+      },
+  },
+  watch: {
+    position: {
+      handler () {
+        this.draw()
+      },
+      deep: true
+    }
+  },
+  mounted () {
+    this.draw()
+  },
+  methods: {
+    draw () {
+      if (this.$refs.customOverlay) {
+        let el = this.$refs.customOverlay
+        for (const key in this.position) {
+          if (this.position.hasOwnProperty(key)) {
+            el.style[key] = this.position[key] + 'px'
+          }
+        }
+      }
+    }
+  },
+  mounted () {
+    this.draw()
+  }
+}
+</script>
+
+<style lang="scss">
+  @import "@/assets/css/theme.scss";
+  @import "@/assets/css/mixin.scss";
+.map-list-container {
+  position: absolute;
+  width: 50%;
+  height: 50%;
+  border-radius: 6px;
+  background:rgba(10,25,43,0.6);
+  border:1px solid rgba(19,51,88,1);
+  overflow: hidden;
+
+  .inline-block {
+    display: inline-block;
+  }
+
+  .right {
+    float: right;
+  }
+
+  .header {
+    // height:halfH(50%);
+    padding: halfH(12) halfW(20);
+    // line-height:halfH(58);
+    background:rgba(34,127,233,0.6);
+    font-size: halfH(18);
+    font-weight:bold;
+    color:rgba(255,255,255,1);
+  }
+  .map-scroll {
+    overflow: auto;
+  }
+  .list {
+    padding: halfH(12) halfW(20);
+    // top: halfH(58);
+    font-size: halfH(16);
+    font-family:Hiragino Sans GB;
+    font-weight:normal;
+    color:rgba(255,255,255,1);
+
+    .list-name {
+      // height: halfH(30);
+      width: 70%;
+      @include text-ellipsis;
+    }
+    .list-count {
+      text-align: right;
+      width: 30%;
+      white-space: nowrap;
+    }
+  }
+
+}
+</style>

二進制
src/views/hui-jia/components/community-map/images/icon-title.png


二進制
src/views/hui-jia/components/community-map/images/map-mark-icon.png


+ 2 - 0
src/views/hui-jia/components/community-map/index.js

@@ -0,0 +1,2 @@
+import CommunityMap from './index.vue'
+export default CommunityMap

+ 107 - 0
src/views/hui-jia/components/community-map/index.vue

@@ -0,0 +1,107 @@
+<template>
+    <Card :title="title" :icon="icon" class="community-map">
+      <div class="map-con">
+        <tdt-map style="position: relative;z-index: 0;" mapStyle="indigo" :center="center"
+          :zoom="zoom"
+          :controls="[
+            {
+              name: 'Scale',
+              position: 'topright'
+            },]"
+          @init="mapInit"
+        >
+        <!-- <div v-if="provinceList.length"> -->
+
+          <tdt-marker-clusterer
+              v-for="item in provinceList"
+              :key="item.label + 'clusterer'"
+              :markers="[{
+                icon: {
+                  iconUrl: require('./images/map-mark-icon.png'),
+                  iconSize: [24, 24]
+                },
+                position: [item.point.lng, item.point.lat]
+              }]"
+              >
+            </tdt-marker-clusterer>
+            <tdt-label
+              v-for="(item, i) in provinceList"
+              :key="`${i}-label`"
+              :position="[item.point.lng, item.point.lat]"
+              :text="item.label">
+            </tdt-label>
+          <!-- </div> -->
+        </tdt-map>
+        <MapList title="省份" :list="provinceList" :position="{left: 30, bottom: 30}"></MapList>
+      </div>
+    </Card>
+  </template>
+<script>
+import provincePosition from './province-positon'
+import { huiJiaApi } from '@/api'
+import Card from '@/views/hui-jia/components/card'
+import icon from './images/icon-title.png'
+import MapList from './components/map-list.vue'
+export default {
+  name: 'CommunityMap',
+  components: {
+    Card,
+    MapList
+  },
+  props: {
+    zoom: {
+      type: Number,
+      default: 5
+    },
+    center: {
+      type: Array,
+      default: () => [
+            107,
+            32,
+          ]
+    }
+  },
+  data () {
+    return {
+      title: '项目地区分布',
+      icon: icon,
+      provinceList: [],
+    }
+  },
+  mounted () {
+  },
+  methods: {
+    mapInit () {
+      this.provinceList = []
+      huiJiaApi.getCommunityRegionStatistics()
+        .then(({ data }) => {
+          if (data.code === '200') {
+            let _data = data.data
+            let arr = []
+            _data.provinceStatistics.forEach((item, i) => {
+              let cName = item.provinceName.substring(0, 2)
+              let province = provincePosition.find(p => p.name.indexOf(cName) !== -1)
+              let ele = {
+                label: province.name,
+                content: item.count,
+                point: {
+                  lng: province.geoCoord[0],
+                  lat: province.geoCoord[1]
+                }
+              }
+              arr.push(ele)
+            })
+            this.provinceList = arr
+          }
+        })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+@import '@/assets/css/screen.scss';
+  .map-con {
+    height: 100%;
+    position: relative;
+  }
+</style>

+ 279 - 0
src/views/hui-jia/components/community-map/province-positon.js

@@ -0,0 +1,279 @@
+const provincePosition = [
+  // 23省
+  {
+    name: '甘肃',
+    abb: '甘',
+    geoCoord: [
+      103.73,
+      36.03
+    ]
+  },
+  {
+    name: '青海',
+    abb: '青',
+    geoCoord: [
+      101.74,
+      36.56
+    ]
+  },
+  {
+    name: '四川',
+    abb: '川',
+    geoCoord: [
+      104.06,
+      30.67
+    ]
+  },
+  {
+    name: '河北',
+    abb: '冀',
+    geoCoord: [
+      114.48,
+      38.03
+    ]
+  },
+  {
+    name: '云南',
+    abb: '滇',
+    geoCoord: [
+      102.73,
+      25.04
+    ]
+  },
+  {
+    name: '贵州',
+    abb: '黔',
+    geoCoord: [
+      106.71,
+      26.57
+    ]
+  },
+  {
+    name: '湖北',
+    abb: '鄂',
+    geoCoord: [
+      114.31,
+      30.52
+    ]
+  },
+  {
+    name: '河南',
+    abb: '豫',
+    geoCoord: [
+      113.65,
+      34.76
+    ]
+  },
+  {
+    name: '山东',
+    abb: '鲁',
+    geoCoord: [
+      117,
+      36.65
+    ]
+  },
+  {
+    name: '江苏',
+    abb: '苏',
+    geoCoord: [
+      118.78,
+      32.04
+    ]
+  },
+  {
+    name: '安徽',
+    abb: '皖',
+    geoCoord: [
+      117.27,
+      31.86
+    ]
+  },
+  {
+    name: '浙江',
+    abb: '浙',
+    geoCoord: [
+      120.19,
+      30.26
+    ]
+  },
+  {
+    name: '江西',
+    abb: '赣',
+    geoCoord: [
+      115.89,
+      28.68
+    ]
+  },
+  {
+    name: '福建',
+    abb: '闽',
+    geoCoord: [
+      119.3,
+      26.08
+    ]
+  },
+  {
+    name: '广东',
+    abb: '粤',
+    geoCoord: [
+      113.23,
+      23.16
+    ]
+  },
+  {
+    name: '湖南',
+    abb: '湘',
+    geoCoord: [
+      113,
+      28.21
+    ]
+  },
+  {
+    name: '海南',
+    abb: '琼',
+    geoCoord: [
+      110.35,
+      20.02
+    ]
+  },
+  {
+    name: '辽宁',
+    abb: '辽',
+    geoCoord: [
+      123.38,
+      41.8
+    ]
+  },
+  {
+    name: '吉林',
+    abb: '吉',
+    geoCoord: [
+      125.35,
+      43.88
+    ]
+  },
+  {
+    name: '黑龙江',
+    abb: '黑',
+    geoCoord: [
+      126.63,
+      45.75
+    ]
+  },
+  {
+    name: '山西',
+    abb: '晋',
+    geoCoord: [
+      112.53,
+      37.87
+    ]
+  },
+  {
+    name: '陕西',
+    abb: '陕',
+    geoCoord: [
+      108.95,
+      34.27
+    ]
+  },
+  {
+    name: '台湾',
+    abb: '台',
+    geoCoord: [
+      121.30,
+      25.03
+    ]
+  },
+  // 4直辖市
+  {
+    name: '北京',
+    abb: '京',
+    geoCoord: [
+      116.46,
+      39.92
+    ]
+  },
+  {
+    name: '上海',
+    abb: '沪',
+    geoCoord: [
+      121.48,
+      31.22
+    ]
+  },
+  {
+    name: '重庆',
+    abb: '渝',
+    geoCoord: [
+      106.54,
+      29.59
+    ]
+  },
+  {
+    name: '天津',
+    abb: '津',
+    geoCoord: [
+      117.2,
+      39.13
+    ]
+  },
+  // 5自治区
+  {
+    name: '内蒙古',
+    abb: '蒙',
+    geoCoord: [
+      111.65,
+      40.82
+    ]
+  },
+  {
+    name: '广西',
+    abb: '桂',
+    geoCoord: [
+      108.33,
+      22.84
+    ]
+  },
+  {
+    name: '西藏',
+    abb: '藏',
+    geoCoord: [
+      91.11,
+      29.97
+    ]
+  },
+  {
+    name: '宁夏',
+    abb: '宁',
+    geoCoord: [
+      106.27,
+      38.47
+    ]
+  },
+  {
+    name: '新疆',
+    abb: '新',
+    geoCoord: [
+      87.68,
+      43.77
+    ]
+  },
+  // 2特别行政区
+  {
+    name: '香港',
+    abb: '港',
+    geoCoord: [
+      114.17,
+      22.28
+    ]
+  },
+  {
+    name: '澳门',
+    abb: '澳',
+    geoCoord: [
+      113.54,
+      22.19
+    ]
+  }
+]
+export default provincePosition

+ 246 - 0
src/views/hui-jia/components/community-type/components/bar.vue

@@ -0,0 +1,246 @@
+<template>
+    <div ref="onlinePayBar" class="online-pay-bar"></div>
+  </template>
+<script>
+import * as echarts from 'echarts'
+
+export default {
+  name: 'bar',
+  props:{
+    xAxisData: {
+      type: Array,
+      default: () => []
+    },
+    seriesData: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data () {
+    return {
+      barInstance: null
+    }
+  },
+  watch: {
+    xAxisData: {
+      handler (newVal, oldVal) {
+        if (newVal.length) {
+          this.initChart()
+        }
+      },
+      deep: true
+    },
+    seriesData: {
+      handler (newVal, oldVal) {
+        if (newVal.length) {
+          this.initChart()
+        }
+      },
+      deep: true
+    }
+  },
+  mounted () {
+    this.initChart()
+    window.addEventListener("resize", this.handleResize)
+  },
+  beforeDestroy() {
+    if (this.barInstance) {
+      this.barInstance.dispose()
+    }
+    window.removeEventListener("resize", this.handleResize)
+  },
+  methods: {
+    /**
+     * 获取一个和max最接近的能被5整除的无零头整数
+     */
+    getMax (max) {
+      if (max <= 10) return 10
+
+      let pow = max.toString().length - 2
+      pow = Math.pow(10, pow)
+      max = Math.ceil(max / pow / 10) * 10 * pow
+      return max
+    },
+    initChart () {
+      this.barInstance = echarts.init(this.$refs.onlinePayBar)
+      const xAxisData = this.xAxisData
+      const seriesData = this.seriesData
+      // let max = 0
+      // max = seriesData.reduce((max, num) => {
+      //   return Math.max(Number(max), Number(num))
+      // })
+      // max = this.getMax(max)
+      const option = {
+        tooltip: {},
+        legend: {
+          right: '0',
+          top: '0',
+          textStyle: {
+            color: '#fff',
+            padding: 6
+          }
+        },
+        grid: {
+          top: '50px',
+          bottom: '10px',
+          left: '0px',
+          right: '0px',
+          containLabel: true
+        },
+        xAxis: {
+          axisLabel: {
+            color: '#fff',
+            margin: 15,
+            fontSize: 14,
+            formatter: function (val, idx) {
+              let value = xAxisData[idx]
+              return value
+            }
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: '#666',
+              opacity: 1,
+              cap: 'round'
+            }
+          },
+          boundaryGap: true,
+          axisTick: {
+            alignWithLabel: true,
+            length: 7,
+            lineStyle: {
+              width: 3,
+              color: '#fff',
+              opacity: 0.5
+            }
+          },
+          splitLine: {
+            show: true,
+            lineStyle: {
+              color: '#fff',
+              opacity: 0.3,
+              cap: 'round',
+              type: [5, 10]
+            }
+          },
+          max: function (value) { // x轴左侧留白
+            return value.max + 0.3
+          },
+          min: function (value) { // x轴左侧留白
+            return -0.3
+          },
+          data: xAxisData
+        },
+        yAxis: [
+          {
+            axisLabel: {
+              color: '#fff',
+              margin: 25,
+              fontSize: 14
+            },
+            axisLine: {
+              show: false,
+              lineStyle: {
+                color: '#fff'
+              }
+            },
+            axisTick: {
+              length: 14,
+              lineStyle: {
+                width: 2,
+                fontSize: 16
+              }
+            },
+            splitLine: {
+              show: true,
+              lineStyle: {
+                color: '#fff',
+                opacity: 0.3,
+                cap: 'round',
+                type: 'dashed',
+                dashOffset: 5
+              }
+            }
+          },
+          {
+            type: 'value',
+            position: 'right',
+            axisLabel: {
+              show: false
+            },
+            axisTick: {
+              show: true,
+              length: 14,
+              lineStyle: {
+                width: 2,
+                fontSize: 16
+              }
+            },
+            axisLine: {
+              show: false
+            }
+          }
+        ],
+        series: [{
+          name: '项目体量(户数)',
+          type: 'bar',
+          barWidth: '20%',
+          labelLine: {
+            show: false
+          },
+          itemStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: '#8E4CB7' },
+              { offset: 1, color: '#3BB6FE' }
+            ])
+          },
+          data: seriesData
+        }]
+      }
+      this.barInstance.setOption(option)
+      this.barInstance.resize()
+    },
+    handleResize() {
+      if (this.barInstance) {
+        this.barInstance.resize()
+      }
+    },
+  }
+}
+</script>
+<style lang="scss">
+@import '@/assets/css/function.scss';
+.online-pay-bar {
+  width: 100%;
+  height: halfH(280);
+}
+.online-pay {
+  .total{
+    height: halfH(24);
+    line-height: halfH(24);
+    padding: halfH(18) halfW(18);
+    color: #fff;
+    float: left;
+    font-size: halfW(14);
+    border-radius: 5px;
+    background-color: rgba(17, 28, 48, 1);
+    text-align: center;
+    border: 1px solid rgba(0, 123, 211, 0.2);
+  }
+  .img{
+    width: halfW(40);
+    height: halfH(40);
+    margin-right: halfW(27);
+    margin-top: halfH(-7);
+    padding: halfH(8) halfW(8);
+    box-sizing: border-box;
+    background: #12223C;
+    border-radius: 50%;
+    .icon{
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+</style>

+ 362 - 0
src/views/hui-jia/components/community-type/components/three-pie.vue

@@ -0,0 +1,362 @@
+<!--
+ * @Author: Wzh
+ * @Date: 2020-01-15 17:18:57
+ * @LastEditors: Wzh
+ * @LastEditTime: 2020-02-11 11:02:12
+ * @Description:
+ -->
+<template>
+    <div ref="dom" class="charts chart-pie"></div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+// import tdTheme from '_c/charts/theme.json'
+// import mixin from '_c/charts/mixin.js'
+// echarts.registerTheme('tdTheme', tdTheme)
+export default {
+  name: 'ChartPie',
+  // mixins: [mixin],
+  props: {
+    value: Array,
+    text: String,
+    subtext: String,
+    legendIcons: {
+      default: function () {
+        return []
+      }
+    }
+  },
+  watch: {
+    value: {
+      handler: function (val) {
+        this.updateValue()
+      },
+      deep: true
+    }
+  },
+  computed: {
+    legendIconsConvert () {
+      let arr = []
+      this.legendIcons.forEach((item, i) => {
+        if (item.indexOf('data:image/') === 0) {
+          arr.push('image://' + item)
+        } else {
+          arr.push(item)
+        }
+      })
+      return arr
+    },
+    all () {
+      return this.value.map(_ => _.value).reduce((all, item) => {
+        return Number(all) + Number(item)
+      })
+    }
+  },
+  data () {
+    return {
+      pieInstance: null,
+      titleStyle: {
+        fontSize: 14,
+        fontFamily: 'Microsoft YaHei',
+        fontWeight: '400',
+        lineHeight: 14,
+        color: '#FFFFFF'
+      },
+      legendStyle: {
+        padding: [5, 0, 0, 20],
+        rich: {
+          title: {
+            fontSize: 12,
+            fontFamily: 'Microsoft YaHei',
+            fontWeight: '400',
+            color: '#82D1F6',
+            align: 'left',
+            lineHeight: 25
+          },
+          detail: {
+            align: 'left',
+            lineHeight: 25,
+            fontSize: 18,
+            fontFamily: 'Microsoft YaHei',
+            fontWeight: 'bold',
+            color: '#fff'
+          }
+        }
+      },
+      dom: {}
+    }
+  },
+  methods: {
+    legendFormatter (name) {
+      // let all = 0
+      // for (const item of this.value) {
+      //   all += Number(item.value)
+      // }
+      let val = this.value.find(item => item.name === name).value
+      // let percent = ((Number(val) / all) * 100).toFixed(2) + '%'
+      return `{title|${name}}\n{detail|${val}}`
+    },
+    updateValue () {
+      let legend = this.value.map((_, i) => {
+        return { name: _.name, icon: this.legendIconsConvert[i] }
+      })
+      let percents = []
+      let radius = this.value.map(_ => _.value).map((num) => {
+        let nums = Math.round(this.all === 0 ? 0 : (Number(num) / this.all) * 100)
+        percents.push(nums)
+        return nums > 40 ? 40 : nums < 5 ? 5 : nums
+      })
+
+      let option = {
+        title: {
+          text: this.text,
+          subtext: this.subtext,
+          x: '19%',
+          y: '50%',
+          textAlign: 'center',
+          textVerticalAlign: 'middle',
+          textStyle: this.titleStyle
+        },
+        tooltip: {
+          trigger: 'item',
+          formatter: '{b} : {c} ({d}%)'
+        },
+        legend: {
+          orient: 'vertical',
+          right: '5%',
+          top: 'center',
+          data: legend,
+          itemHeight: 46,
+          itemWidth: 46,
+          itemGap: 20,
+          selectedMode: false,
+          formatter: this.legendFormatter,
+          textStyle: this.legendStyle
+        },
+        graphic: [
+          {
+            type: 'group',
+            left: '50%',
+            top: 'middle',
+            silent: true,
+            children: [
+              {
+                type: 'circle',
+                top: 'middle',
+                shape: {
+                  r: 2
+                },
+                style: {
+                  fill: '#5A729D'
+                }
+              },
+              {
+                type: 'line',
+                top: 'middle',
+                shape: {
+                  x1: 0,
+                  y1: 0,
+                  x2: 100,
+                  y2: 0
+                },
+                style: {
+                  stroke: '#5A729D'
+                }
+              },
+              {
+                type: 'circle',
+                top: 'middle',
+                position: [100, 0],
+                shape: {
+                  r: 2
+                },
+                style: {
+                  fill: '#5A729D'
+                }
+              }
+            ]
+          },
+          {
+            type: 'group',
+            left: '50%',
+            bottom: '60%',
+            silent: true,
+            rotation: -5.85,
+            children: [
+              {
+                type: 'circle',
+                top: 'middle',
+                shape: {
+                  r: 2
+                },
+                style: {
+                  fill: '#5A729D'
+                }
+              },
+              {
+                type: 'line',
+                top: 'middle',
+                shape: {
+                  x1: 0,
+                  y1: 0,
+                  x2: 110,
+                  y2: 0
+                },
+                style: {
+                  stroke: '#5A729D'
+                }
+              },
+              {
+                type: 'circle',
+                top: 'middle',
+                position: [110, 0],
+                shape: {
+                  r: 2
+                },
+                style: {
+                  fill: '#5A729D'
+                }
+              }
+            ]
+          },
+          {
+            type: 'group',
+            left: '50%',
+            top: '60%',
+            silent: true,
+            rotation: 5.85,
+            children: [
+              {
+                type: 'circle',
+                top: 'middle',
+                shape: {
+                  r: 2
+                },
+                style: {
+                  fill: '#5A729D'
+                }
+              },
+              {
+                type: 'line',
+                top: 'middle',
+                shape: {
+                  x1: 0,
+                  y1: 0,
+                  x2: 110,
+                  y2: 0
+                },
+                style: {
+                  stroke: '#5A729D'
+                }
+              },
+              {
+                type: 'circle',
+                top: 'middle',
+                position: [110, 0],
+                shape: {
+                  r: 2
+                },
+                style: {
+                  fill: '#5A729D'
+                }
+              }
+            ]
+          }
+        ],
+        series: [
+          {
+            type: 'pie',
+            color: ['#0D172A'],
+            radius: [radius[0] + '%', radius[0] + Math.min(percents[0] / 10 + 4, 10) + '%'],
+            center: ['32%', '31%'],
+            label: {
+              show: true,
+              color: '#fff',
+              position: 'center',
+              formatter: percents[0] + '%'
+            },
+            silent: true,
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(
+                0, 0, 0, 1, [
+                  { offset: 0, color: '#43D3C7' },
+                  { offset: 1, color: '#2D32F0' }
+                ])
+            },
+            data: [this.value[0]]
+          },
+          {
+            type: 'pie',
+            color: ['#0D172A'],
+            radius: [radius[1] + '%', radius[1] + Math.min(percents[1] / 10 + 4, 10) + '%'],
+            center: ['13%', '38%'],
+            label: {
+              show: true,
+              color: '#fff',
+              position: 'center',
+              formatter: percents[1] + '%'
+            },
+            silent: true,
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(
+                0, 0, 0, 1, [
+                  { offset: 0, color: '#2E38EE' },
+                  { offset: 1, color: '#EE9178' }
+                ])
+            },
+            data: [this.value[1]]
+          },
+          {
+            type: 'pie',
+            silent: true,
+            startAngle: 45,
+            clockwise: false,
+            minAngle: 10,
+            radius: [radius[2] + '%', radius[2] + Math.min(percents[2] / 10 + 4, 10) + '%'],
+            center: ['25%', '73%'],
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(
+                0, 0, 0, 1, [
+                  { offset: 0, color: '#D6595D' },
+                  { offset: 1, color: '#D0A971' }
+                ])
+            },
+            label: {
+              show: true,
+              color: '#fff',
+              position: 'center',
+              formatter: percents[2] + '%'
+            },
+            data: [this.value[2]]
+          }
+        ]
+      }
+      this.pieInstance.setOption(option)
+      this.pieInstance.resize()
+    },
+
+  },
+  mounted () {
+    this.$nextTick(() => {
+      this.pieInstance = echarts.init(this.$refs.dom)
+      this.updateValue()
+      window.addEventListener("resize", this.handleResize)
+    })
+  },
+  beforeDestroy() {
+    if (this.pieInstance) {
+      this.pieInstance.dispose()
+    }
+    window.removeEventListener("resize", this.handleResize)
+  },
+}
+</script>
+<style lang="scss">
+@import '@/assets/css/function.scss';
+
+.charts {
+  height: halfH(260);
+  // top: halfH(-30);
+}
+</style>

二進制
src/views/hui-jia/components/community-type/images/icon-sy.png


二進制
src/views/hui-jia/components/community-type/images/icon-title.png


二進制
src/views/hui-jia/components/community-type/images/icon-type.png


二進制
src/views/hui-jia/components/community-type/images/icon-zh.png


二進制
src/views/hui-jia/components/community-type/images/icon-zz.png


+ 2 - 0
src/views/hui-jia/components/community-type/index.js

@@ -0,0 +1,2 @@
+import CommunityType from './index.vue'
+export default CommunityType

+ 81 - 0
src/views/hui-jia/components/community-type/index.vue

@@ -0,0 +1,81 @@
+<template>
+    <Card :title="title" :icon="icon" class="community-type">
+      <ThreePie :value="value" :legendIcons="legendIcons" class="w-full"></ThreePie>
+      <Bar :xAxisData="xAxisData" :seriesData="seriesData" v-if="showBar" class="w-full"></Bar>
+    </Card>
+  </template>
+<script>
+// import echarts from 'echarts'
+import Card from '@/views/hui-jia/components/card'
+import icon from './images/icon-title.png'
+import ThreePie from './components/three-pie.vue'
+import iconZz from './images/icon-zz.png'
+import iconSy from './images/icon-sy.png'
+import iconZh from './images/icon-zh.png'
+import Bar from './components/bar.vue'
+import { huiJiaApi } from '@/api'
+
+export default {
+  name: 'CommunityType',
+  components: {
+    Card,
+    ThreePie,
+    Bar
+  },
+  props: {
+    showBar: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data () {
+    return {
+      xAxisData: [],
+      seriesData: [],
+      title: '项目类型',
+      icon: icon,
+      value: [
+        {
+          value: 100,
+          name: '住宅',
+          key: 'flatCount'
+        },
+        {
+          value: 200,
+          name: '商业',
+          key: 'businessCount'
+        },
+        {
+          value: 300,
+          name: '综合',
+          key: 'otherCount'
+        }
+      ],
+      legendIcons: [
+        iconZz,
+        iconSy,
+        iconZh
+      ],
+      text: '项目类型',
+      subtext: '项目类型占比'
+    }
+  },
+  mounted() {
+    this.getCommunityTypeSizeStatistics()
+  },
+  methods: {
+    getCommunityTypeSizeStatistics () {
+      huiJiaApi.getCommunityTypeSizeStatistics().then(res => {
+        if(res && res.data){
+          const _data = res.data.data
+          this.xAxisData = [_data.intervalOneName, _data.intervalTwoName, _data.intervalThreeName, _data.intervalFourName, _data.intervalFiveName]
+          this.seriesData = [_data.intervalOneCount, _data.intervalTwoCount, _data.intervalThreeCount, _data.intervalFourCount, _data.intervalFiveCount]
+          this.value.forEach(item => {
+            item.value = res.data.data[item.key]
+          })
+        }
+      })
+    }
+  }
+}
+</script>

+ 518 - 0
src/views/hui-jia/components/hardware.vue

@@ -0,0 +1,518 @@
+<template>
+  <div class="hardware-data">
+    <!-- 标题 -->
+    <div class="module-title">
+      <img src="@/assets/images/shb.png" class="title-icon" />
+      硬件数据
+    </div>
+
+    <!-- 智能电表部分 -->
+    <div class="data-section">
+      <div class="device-info">
+        <img src="@/assets/images/dblxzhb.png" class="title-icon" />
+        <div>
+          <div class="device-count">{{ electricState.deviceCount | formatNumber }}</div>
+          <div class="device-name">智能电表</div>
+        </div>
+      </div>
+      <div class="data-cards">
+        <div class="data-card">
+          <div class="data-label">年度用电</div>
+          <div>
+            <div class="data-value">{{ electricState.annualUsage | formatNumber }}</div>
+            <div class="data-unit">kW·h</div>
+          </div>
+        </div>
+        <div class="data-card">
+          <div class="data-label">月均用电</div>
+          <div>
+            <div class="data-value">{{ electricState.monthlyAvgUsage | formatNumber }}</div>
+            <div class="data-unit">kW·h</div>
+          </div>
+        </div>
+        <div class="data-card">
+          <div class="data-label">日均用电</div>
+          <div>
+            <div class="data-value">{{ electricState.dailyAvgUsage | formatNumber }}</div>
+            <div class="data-unit">kW·h</div>
+          </div>
+        </div>
+        <div class="data-card">
+          <div class="data-label">开关次数</div>
+          <div>
+            <div class="data-value">{{ electricState.switchCount | formatNumber }}</div>
+            <div class="data-unit">近12个月</div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="charts-container">
+      <div ref="powerChart" class="chart-item"></div>
+      <div ref="waterChart" class="chart-item"></div>
+    </div>
+
+    <!-- 智能水表部分 -->
+    <div class="data-section">
+      <div class="device-info">
+        <img src="@/assets/images/zhnmj.png" class="title-icon" />
+        <div>
+          <div class="device-count">{{ waterState.deviceCount | formatNumber }}</div>
+          <div class="water-device-name">智能水表</div>
+        </div>
+      </div>
+      <div class="data-cards">
+        <div class="data-card">
+          <div class="data-label">年度用水</div>
+          <div>
+            <div class="data-value">{{ waterState.annualUsage | formatNumber }}</div>
+            <div class="water-data-unit">m³</div>
+          </div>
+        </div>
+        <div class="data-card">
+          <div class="data-label">月均用水</div>
+          <div>
+            <div class="data-value">{{ waterState.monthlyAvgUsage | formatNumber }}</div>
+            <div class="water-data-unit">m³</div>
+          </div>
+        </div>
+        <div class="data-card">
+          <div class="data-label">日均用水</div>
+          <div>
+            <div class="data-value">{{ waterState.dailyAvgUsage | formatNumber }}</div>
+            <div class="water-data-unit">m³</div>
+          </div>
+        </div>
+        <div class="data-card">
+          <div class="data-label">开关次数</div>
+          <div>
+            <div class="data-value">{{ waterState.switchCount | formatNumber }}</div>
+            <div class="water-data-unit">近12个月</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import dayjs from "dayjs";
+  import { huiJiaApi } from "@/api"
+  import * as echarts from "echarts"
+
+  export default {
+    name: "HardwareData",
+    data() {
+      return {
+        electricState: {
+          deviceCount: 8921, // 设备数量
+          annualUsage: 42891, // 年度用量 近12月的用量之和
+          monthlyAvgUsage: 18921, // 月均用量 近12月用量月平均数
+          dailyAvgUsage: 8921, // 日均用量 近12月用量日平均数
+          switchCount: 887, // 开关次数 近12月成功开启、关闭次数
+          monthStatisticsList: [],
+        },
+        waterState: {
+          deviceCount: 8921, // 设备数量
+          annualUsage: 42891, // 年度用量 近12月的用量之和
+          monthlyAvgUsage: 18921, // 月均用量 近12月用量月平均数
+          dailyAvgUsage: 8921, // 日均用量 近12月用量日平均数
+          switchCount: 887, // 开关次数 近12月成功开启、关闭次数
+          monthStatisticsList: [],
+        },
+        powerChartInstance: null,
+        waterChartInstance: null,
+      }
+    },
+    mounted() {
+      this.getData()
+      this.initPowerChart()
+      this.initWaterChart()
+      window.addEventListener("resize", this.handleResize)
+    },
+    beforeDestroy() {
+      window.removeEventListener("resize", this.handleResize)
+      if (this.powerChartInstance) {
+        this.powerChartInstance.dispose()
+      }
+      if (this.waterChartInstance) {
+        this.waterChartInstance.dispose()
+      }
+    },
+    methods: {
+      getData() {
+        huiJiaApi.getElectricData().then((res) => {
+          if (res && res.data) {
+            this.electricState = res.data.data
+            this.initPowerChart()
+          }
+        })
+        huiJiaApi.getWaterData().then((res) => {
+          if (res && res.data) {
+            this.waterState = res.data.data
+            this.initWaterChart()
+          }
+        })
+      },
+      // 初始化用电量柱状图
+      initPowerChart() {
+        this.powerChartInstance = echarts.init(this.$refs.powerChart)
+
+        const option = {
+          tooltip: {
+            trigger: "axis",
+            backgroundColor: "rgba(0, 0, 0, 0.8)",
+            borderColor: "rgba(64, 158, 255, 0.3)",
+            textStyle: {
+              color: "#fff",
+            },
+            formatter: (params) => {
+              let result = ``
+              params.forEach((param) => {
+                result += `${param.name}<br/>${
+                  param.seriesName
+                }: ${param.value.toLocaleString()}<br/>`
+              })
+              return result
+            },
+          },
+          grid: {
+            left: "4%",
+            right: "4%",
+            top: "5%",
+            bottom: "2%",
+            containLabel: true,
+          },
+          xAxis: {
+            type: "category",
+            data: this.electricState.monthStatisticsList.map(
+              (item) => item.staData
+            ),
+            axisLine: {
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.1)",
+                type: "dashed",
+              },
+            },
+            axisLabel: {
+              color: "rgba(160, 179, 214, 0.7)",
+              fontSize: 11,
+              interval: 0,
+              formatter: (value) => {
+                return dayjs(value).format("YY-MM")
+              }
+            },
+            axisTick: {
+              show: false,
+            },
+            splitLine: {
+              show: true,
+              // 将坐标轴内的线设置为虚线
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.1)",
+                type: "dashed",
+              },
+            },
+          },
+          yAxis: {
+            type: "value",
+            min: 0,
+            max: 4000,
+            axisLine: {
+              show: false,
+            },
+            axisLabel: {
+              color: "rgba(160, 179, 214, 0.7)",
+              fontSize: 11,
+            },
+            axisTick: {
+              show: false,
+            },
+            splitLine: {
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.1)",
+                type: "dashed",
+                width: 1,
+              },
+            },
+          },
+          series: [
+            {
+              name: "用电量",
+              type: "bar",
+              data: this.electricState.monthStatisticsList.map(
+                (item) => item.count
+              ),
+              barWidth: "30%",
+              itemStyle: {
+                color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
+                  { offset: 0, color: "#FCE2B4" },
+                  { offset: 1, color: "#F66757" },
+                ]),
+                borderRadius: [20, 20, 0, 0],
+              },
+              label: {
+                show: true,
+                position: "top",
+                color: "#fff",
+                fontSize: 10,
+                formatter: (params) =>
+                  params.value >= 10000
+                    ? (params.value / 10000).toFixed(1) + "万"
+                    : params.value,
+              },
+            },
+          ],
+        }
+
+        this.powerChartInstance.setOption(option)
+        this.powerChartInstance.resize()
+      },
+
+      // 初始化用水量柱状图
+      initWaterChart() {
+        this.waterChartInstance = echarts.init(this.$refs.waterChart)
+
+        const option = {
+          tooltip: {
+            trigger: "axis",
+            backgroundColor: "rgba(0, 0, 0, 0.8)",
+            borderColor: "rgba(64, 158, 255, 0.3)",
+            textStyle: {
+              color: "#fff",
+            },
+            formatter: (params) => {
+              let result = ``
+              params.forEach((param) => {
+                result += `${param.name}<br/>${
+                  param.seriesName
+                }: ${param.value.toLocaleString()}<br/>`
+              })
+              return result
+            },
+          },
+          grid: {
+            left: "4%",
+            right: "4%",
+            top: "2%",
+            bottom: "5%",
+            containLabel: true,
+          },
+          xAxis: {
+            type: "category",
+            data: this.waterState.monthStatisticsList.map(
+              (item) => item.staData
+            ),
+            axisLine: {
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.3)",
+                type: "dashed",
+              },
+            },
+            axisLabel: {
+              color: "rgba(160, 179, 214, 0.7)",
+              fontSize: 11,
+              interval: 0,
+              show: false,
+            },
+            axisTick: {
+              show: false,
+            },
+            splitLine: {
+              show: true,
+              // 将坐标轴内的线设置为虚线
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.1)",
+                type: "dashed",
+              },
+            },
+          },
+          yAxis: {
+            type: "value",
+            // 反转 y 轴的 min 和 max,实现“倒立”
+            min: 4000,
+            max: 0,
+            axisLine: {
+              show: false,
+            },
+            axisLabel: {
+              color: "rgba(160, 179, 214, 0.7)",
+              fontSize: 11,
+            },
+            axisTick: {
+              show: false,
+            },
+            splitLine: {
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.1)",
+                type: "dashed",
+                width: 1,
+              },
+            },
+          },
+          series: [
+            {
+              name: "用水量",
+              type: "bar",
+              data: this.waterState.monthStatisticsList.map(
+                (item) => item.count
+              ),
+              barWidth: "30%",
+              itemStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  { offset: 0, color: "#3AB3E3" },
+                  { offset: 1, color: "#1620D4" },
+                ]),
+                borderRadius: [0, 0, 20, 20],
+              },
+              label: {
+                show: true,
+                position: "bottom",
+                color: "#fff",
+                fontSize: 10,
+                formatter: (params) =>
+                  params.value >= 10000
+                    ? (params.value / 10000).toFixed(1) + "万"
+                    : params.value,
+              },
+            },
+          ],
+        }
+
+        this.waterChartInstance.setOption(option)
+        this.waterChartInstance.resize()
+      },
+
+      handleResize() {
+        if (this.powerChartInstance) {
+          this.powerChartInstance.resize()
+        }
+        if (this.waterChartInstance) {
+          this.waterChartInstance.resize()
+        }
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/css/theme.scss";
+
+  .hardware-data {
+    background: var(--content-bg);
+    border-radius: 4px;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    gap: halfW(20);
+  }
+
+  .module-title {
+    display: flex;
+    align-items: center;
+    color: var(--title-primary);
+    font-size: halfW(16);
+    border-radius: 4px;
+    padding: halfH(4) halfW(10);
+    background: var(--title-bg);
+  }
+
+  .data-section {
+    display: flex;
+    justify-content: space-between;
+    gap: halfW(16);
+    padding: halfH(10) halfW(10);
+  }
+
+  .device-info {
+    text-align: right;
+    background: var(--title-bg);
+    display: flex;
+    align-items: center;
+    gap: halfW(20);
+    width: 25%;
+    justify-content: space-between;
+    border: 1px solid rgba(64, 158, 255, 0.1);
+    border-radius: var(--radius);
+    padding: halfH(16) halfW(10);
+  }
+
+  .device-name {
+    color: #fea373;
+    font-size: halfW(14);
+    font-weight: 500;
+  }
+  .water-device-name {
+    color: #3ab3e3;
+    font-size: halfW(14);
+    font-weight: 500;
+  }
+
+  .device-count {
+    color: #fff;
+    font-size: halfW(20);
+    font-weight: 500;
+    margin-bottom: halfH(10);
+  }
+
+  .data-cards {
+    flex: 1;
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    grid-template-rows: repeat(2, 1fr);
+    align-items: center;
+    justify-content: space-between;
+    flex-wrap: wrap;
+    background: var(--title-bg);
+    border: 1px solid rgba(64, 158, 255, 0.1);
+    border-radius: var(--radius);
+  }
+
+  .data-card {
+    flex: 1;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    text-align: right;
+    padding: halfH(10) halfW(20);
+    &:nth-child(odd) {
+      border-right: 1px solid rgba(64, 158, 255, 0.3);
+    }
+  }
+
+  .data-label {
+    color: var(--primary);
+    font-size: halfW(14);
+    font-weight: 500;
+  }
+
+  .data-value {
+    color: #fff;
+    font-size: halfW(18);
+    font-weight: 600;
+    margin-bottom: halfH(2);
+  }
+
+  .data-unit {
+    color: #fea373;
+    font-size: halfW(14);
+  }
+  .water-data-unit {
+    color: #3ab3e3;
+    font-size: halfW(14);
+  }
+
+  /* 修改为两个图表容器 */
+  .charts-container {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    height: 100%;
+  }
+
+  .chart-item {
+    flex: 1;
+    width: 100%;
+    height: 100%;
+  }
+</style>

+ 248 - 0
src/views/hui-jia/components/implementary/ing.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="implementing-container">
+    <div class="implementing-title">
+      <img src="@/assets/images/nz.png" class="title-icon" />
+      进行中的实施服务
+    </div>
+    <vueSeamless
+      :data="data"
+      :class-option="optionScroll"
+      class="implementing-list"
+    >
+      <div
+        v-for="(item, index) in data"
+        :key="index"
+        class="implementing-item"
+        :class="{ 'even-item': index % 2 === 1 }"
+      >
+        <el-row class="item-content">
+          <el-col :span="3" class="item-date">{{
+            dayjsObj(item.created_at)
+          }}</el-col>
+          <el-col :span="3" class="item-city">
+            {{ item | safeGet("residence.location.region_name", "-") }}
+          </el-col>
+          <el-col :span="7" class="project-info">
+            <div class="project-name">
+              {{ item | safeGet("residence.residence_name", "-") }}
+            </div>
+            <div class="company-name">
+              {{ item | safeGet("service.customer.customer_name", "-") }}
+            </div>
+          </el-col>
+          <el-col :span="11" class="project-stats">
+            <el-row class="project-row">
+              <el-col :span="3" class="stat-item" style="text-align: right">
+                <span class="stat-value">{{
+                  item | safeGet("residence_contracted_households", "-")
+                }}</span>
+                <span class="stat-label">户</span>
+              </el-col>
+              <el-col :span="4" class="stat-item">
+                <span class="stat-value">{{
+                  item | safeGet("service.liable_person.real_name", "-")
+                }}</span>
+              </el-col>
+              <el-col :span="3" class="stat-item">
+                <div class="progress-wrapper">
+                  <span class="progress-text"
+                    >{{ item | safeGet("service.perform_ratio", "-") }}%</span
+                  >
+                </div>
+              </el-col>
+              <el-col :span="10" class="stat-item service-item">
+                <span class="service-content">{{
+                  item | safeGet("service.work_template.display_name", "-")
+                }}</span>
+              </el-col>
+              <el-col :span="4" class="stat-item">
+                <span class="period text-overflow">{{ item.updated_at }}</span>
+              </el-col>
+            </el-row>
+          </el-col>
+        </el-row>
+      </div>
+    </vueSeamless>
+  </div>
+</template>
+
+<script>
+  import dayjs from "dayjs"
+  import { mapState } from "vuex"
+  import vueSeamless from "vue-seamless-scroll"
+
+  export default {
+    name: "ImplementingServices",
+    components: {
+      vueSeamless,
+    },
+    props: {
+      data: {
+        type: Array,
+        default: () => [],
+      },
+    },
+    data() {
+      return {}
+    },
+    computed: {
+      ...mapState(["entCode", "communityId"]),
+      optionScroll() {
+        return {
+          singleHeight: 60,
+          waitTime: 5000
+        }
+      },
+    },
+    methods: {
+      dayjsObj(date) {
+        return dayjs(date).format("YYYY-MM-DD")
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/css/theme.scss";
+
+  .implementing-container {
+    background: var(--content-bg);
+    border-radius: var(--radius);
+    display: flex;
+    flex-direction: column;
+  }
+
+  .implementing-title {
+    background: var(--title-bg);
+    font-size: halfW(14);
+    color: var(--title-primary);
+    padding: halfH(4) halfW(10);
+    border-radius: var(--radius);
+    display: flex;
+    align-items: center;
+  }
+
+  .implementing-list {
+    flex: 1;
+    overflow-y: auto;
+    padding: 0px halfW(10);
+    height: calc(100% - halfH(36));
+  }
+
+  .implementing-item {
+    background: var(--content-bg); // 第一个项目使用--content-bg
+    padding: 16px;
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  // 偶数项使用--title-bg作为背景色
+  .implementing-item.even-item {
+    background: var(--title-bg);
+  }
+  .item-content {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap: halfW(10);
+    .item-date {
+      color: var(--title-secondary);
+      font-size: halfW(14);
+    }
+
+    .item-city {
+      color: var(--title-primary);
+      font-size: halfW(14);
+      font-weight: 500;
+      width: halfW(80);
+      padding: halfH(2) 0px;
+      border-radius: var(--radius);
+    }
+
+    .project-info {
+      flex: 1 1 0%;
+      text-align: left;
+
+      .project-name {
+        flex: 0 0 1;
+        color: var(--title-primary);
+        font-size: halfW(14);
+        margin-bottom: halfH(8);
+        font-weight: 400;
+      }
+
+      .company-name {
+        color: var(--title-secondary);
+        font-size: halfW(14);
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+    }
+
+    .project-stats {
+      .project-row {
+        display: flex;
+        align-items: center;
+        flex-wrap: nowrap;
+        gap: halfW(10);
+      }
+    }
+
+    .stat-item {
+      // flex: 1 1 0%;
+      display: flex;
+      align-items: center;
+      text-align: left;
+      // width: halfW(50);
+      &:first-child {
+        .stat-value {
+          color: var(--primary);
+        }
+      }
+      .stat-value {
+        color: #fff;
+        font-size: halfW(14);
+        font-weight: 500;
+        margin-right: halfW(4);
+      }
+
+      .stat-label {
+        color: #fff;
+        font-size: halfW(14);
+      }
+
+      .progress-wrapper {
+        display: flex;
+        align-items: center;
+        gap: halfW(8);
+        .progress-text {
+          color: #40d9ac;
+          font-size: halfW(14);
+          font-weight: 500;
+          width: halfW(35);
+        }
+      }
+
+      .service-content {
+        color: #ffffff;
+        font-size: halfW(14);
+      }
+
+      .period {
+        color: #a0b3d6;
+        font-size: halfW(14);
+        text-align: right;
+      }
+    }
+    .service-item {
+      min-width: halfW(100);
+      .service-content {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+    }
+  }
+</style>

+ 232 - 0
src/views/hui-jia/components/implementary/now-year.vue

@@ -0,0 +1,232 @@
+<template>
+  <div class="now-year-container">
+    <div class="now-year-title">
+      <img src="@/assets/images/qst.png" class="title-icon" />
+      实施服务(今年数据)
+    </div>
+    <div id="nowYearChart" class="chart"></div>
+  </div>
+</template>
+
+<script>
+  import { mapState } from "vuex"
+  import * as echarts from "echarts"
+
+  export default {
+    name: "NowYearServices",
+    props: {
+      data: {
+        type: Object,
+        default: () => ({}),
+      },
+    },
+    data() {
+      return {
+        chartInstance: null,
+        chartData: {
+          categories: [
+            "签约户数",
+            "合同份数",
+            "服务小区",
+            "服务企业",
+            "履约城市",
+          ],
+        },
+      }
+    },
+    computed: {
+      ...mapState(["entCode", "communityId"]),
+    },
+    watch: {
+      data: {
+        handler(newVal, oldVal) {
+          if (newVal) {
+            this.initChart()
+          }
+        },
+        deep: true,
+      },
+    },
+    mounted() {
+      this.init()
+      window.addEventListener("resize", this.handleResize)
+    },
+    beforeDestroy() {
+      if (this.chartInstance) {
+        this.chartInstance.dispose()
+      }
+      window.removeEventListener("resize", this.handleResize)
+    },
+    methods: {
+      // 初始化组件
+      init() {
+        this.initChart()
+      },
+
+      // 初始化图表
+      initChart() {
+        this.chartInstance = echarts.init(
+          document.getElementById("nowYearChart")
+        )
+
+        const option = {
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+            backgroundColor: "rgba(17, 28, 49, 0.9)",
+            borderColor: "rgba(64, 158, 255, 0.2)",
+            textStyle: {
+              color: "#ffffff",
+            },
+          },
+          legend: {
+            data: ["进行中", "上月完成", "今年累计"],
+            textStyle: {
+              color: "#a0b3d6",
+            },
+            itemWidth: 16,
+            itemHeight: 8,
+            top: 10,
+          },
+          grid: {
+            left: "3%",
+            right: "4%",
+            bottom: "3%",
+            top: 50,
+            containLabel: true,
+          },
+          xAxis: {
+            type: "log",
+            show: false, // 隐藏x轴
+            axisLine: {
+              show: false,
+            },
+            axisTick: {
+              show: false,
+            },
+            axisLabel: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+          },
+          yAxis: {
+            type: "category",
+            data: this.chartData.categories,
+            show: true, // 隐藏y轴
+            axisLine: {
+              show: false,
+            },
+            axisTick: {
+              show: false,
+            },
+            axisLabel: {
+              show: true,
+              color: "#a1a1a1",
+              fontSize: 14,
+            },
+          },
+          series: [
+            {
+              name: "进行中",
+              type: "bar",
+              stack: "total",
+              barMaxWidth: 30,
+              itemStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                  { offset: 0, color: "#1620D4" },
+                  { offset: 0.8, color: "#3AB3E3" },
+                ]),
+                borderRadius: [20, 0, 0, 20],
+              },
+              label: {
+                show: true,
+                color: "#fff",
+              },
+              barGap: "0%",
+              barCategoryGap: "20%",
+              data: this.data.service_ing,
+            },
+            {
+              name: "上月完成",
+              type: "bar",
+              stack: "total",
+              barMaxWidth: 30,
+              itemStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                  { offset: 0, color: "#192FC9" },
+                  { offset: 0.8, color: "#6D44BC" },
+                ]),
+                borderRadius: [0, 0, 0, 0],
+              },
+              label: {
+                show: true,
+                color: "#fff",
+              },
+              barGap: "0%",
+              data: this.data.service_last_month,
+            },
+            {
+              name: "今年累计",
+              type: "bar",
+              stack: "total",
+              barMaxWidth: 30,
+              label: {
+                show: true,
+                color: "#fff",
+              },
+              itemStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                  { offset: 0, color: "#F7897D" },
+                  { offset: 0.8, color: "#F66C89" },
+                ]),
+                borderRadius: [0, 20, 20, 0],
+              },
+              barGap: "0%",
+              data: this.data.year_summary,
+            },
+          ],
+        }
+
+        this.chartInstance.setOption(option)
+        this.chartInstance.resize()
+      },
+
+      // 处理窗口大小变化
+      handleResize() {
+        if (this.chartInstance) {
+          this.chartInstance.resize()
+        }
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/css/theme.scss";
+
+  .now-year-container {
+    background: var(--content-bg);
+    border-radius: var(--radius);
+    display: flex;
+    flex-direction: column;
+  }
+
+  .now-year-title {
+    font-size: halfW(16);
+    border-radius: var(--radius);
+    color: var(--title-primary);
+    background: var(--title-bg);
+    padding: halfH(4) halfW(10);
+    display: flex;
+    align-items: center;
+  }
+
+  .chart {
+    width: 100%;
+    height: 100%;
+  }
+</style>

+ 139 - 0
src/views/hui-jia/components/implementary/total.vue

@@ -0,0 +1,139 @@
+<template>
+  <div class="total-container">
+    <div class="total-title">
+      <img src="@/assets/images/tj.png" class="title-icon" />
+      实施服务(汇总数据)
+    </div>
+    <div class="total-grid">
+      <div class="total-item">
+        <img src="@/assets/images/jgxx.png" class="title-icon" />
+        <div class="total-value">{{ data.cities | formatNumber }}</div>
+        <div class="total-label">城市总数</div>
+      </div>
+      <div class="total-item">
+        <img src="@/assets/images/glgd.png" class="title-icon" />
+        <div class="total-value">{{ data.customers }}</div>
+        <div class="total-label">企业总数</div>
+      </div>
+      <div class="total-item">
+        <img src="@/assets/images/ewm.png" class="title-icon" />
+        <div class="total-value">{{ data.contracts | formatNumber }}</div>
+        <div class="total-label">合同总数</div>
+      </div>
+      <div class="total-item">
+        <img src="@/assets/images/jytj.png" class="title-icon" />
+        <div class="total-value">{{ data.perform_times | formatNumber }}</div>
+        <div class="total-label">履约次数</div>
+      </div>
+      <div class="total-item">
+        <img
+          src="@/assets/images/zhxggd.png"
+          class="title-icon fuwu-title-icon"
+        />
+        <div class="total-value">{{ data.service_time | formatNumber }}</div>
+        <div class="total-label">服务时长 (天)</div>
+      </div>
+      <div class="total-item">
+        <img src="@/assets/images/gwxx.png" class="title-icon" />
+        <div class="total-value">{{ data.residences | formatNumber }}</div>
+        <div class="total-label">项目总数</div>
+      </div>
+      <div class="total-item">
+        <img src="@/assets/images/ryzb.png" class="title-icon" />
+        <div class="total-value">
+          {{ data.contracted_households | formatNumber }}
+        </div>
+        <div class="total-label">签约户数</div>
+      </div>
+      <div class="total-item">
+        <img src="@/assets/images/zhnmj.png" class="title-icon" />
+        <div class="total-value">
+          {{ data.actual_households | formatNumber }}
+        </div>
+        <div class="total-label">实施户数</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import { mapState } from "vuex"
+
+  export default {
+    name: "ImplementationTotal",
+    props: {
+      data: {
+        type: Object,
+        default: () => ({}),
+      },
+    },
+    data() {
+      return {}
+    },
+    computed: {
+      ...mapState(["entCode", "communityId"]),
+    },
+    methods: {},
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/css/theme.scss";
+
+  .total-container {
+    background: var(--content-bg);
+    border-radius: var(--radius);
+    position: relative;
+    overflow: hidden;
+
+    .total-title {
+      padding: halfH(4) halfW(10);
+      display: flex;
+      align-items: center;
+      font-size: halfW(16);
+      color: var(--title-primary);
+      background: var(--title-bg);
+      margin-bottom: halfH(20);
+    }
+
+    .total-grid {
+      padding: 0 halfW(10);
+      display: grid;
+      grid-template-columns: repeat(4, 1fr);
+      grid-template-rows: repeat(2, 1fr);
+      gap: halfW(10);
+      height: calc(100% - halfH(66));
+      align-items: center;
+    }
+
+    .total-item {
+      flex: 1 1 0%;
+      background: var(--title-bg);
+      border-radius: var(--radius);
+      text-align: center;
+      padding: halfH(10) halfW(6);
+      .title-icon {
+        width: halfW(24);
+        height: halfW(24);
+        margin-right: 0;
+        margin-bottom: 6px;
+      }
+      .fuwu-title-icon {
+        width: halfW(20);
+        height: halfW(20);
+      }
+      
+      .total-label {
+        font-size: halfW(14);
+        color: #a0b3d6;
+      }
+      
+      .total-value {
+        margin: halfH(10) 0;
+        font-size: halfW(18);
+        font-weight: bold;
+        color: #ffffff;
+      }
+    }
+  }
+</style>

+ 376 - 0
src/views/hui-jia/components/market.vue

@@ -0,0 +1,376 @@
+<template>
+  <div class="market-container">
+    <div class="market-title">
+      <img src="@/assets/images/shb.png" class="title-icon" />
+      市场拓展
+    </div>
+    <div class="legend">
+      <div class="legend-item">上月</div>
+      <div class="legend-item">本月</div>
+    </div>
+    <div class="chart-wrap">
+      <div id="marketChart" class="chart"></div>
+      <div id="marketChart2" class="chart2"></div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import { mapState } from "vuex"
+  import { api } from "@/api"
+  import * as echarts from "echarts"
+
+  export default {
+    name: "MarketExpansion",
+    props: {
+      data: {
+        type: Object,
+        default: () => ({}),
+      },
+    },
+    data() {
+      return {
+        chartInstanceA: null,
+        chartInstanceB: null,
+        chartData: {
+          categories: [
+            "新增户数",
+            "新增合同",
+            "新增项目",
+            "新联系人",
+            "跟进企业",
+            "新增企业",
+          ],
+        },
+      }
+    },
+    computed: {
+      ...mapState(["entCode", "communityId"]),
+    },
+    watch: {
+      data: {
+        handler(newVal, oldVal) {
+          if (newVal) {
+            this.initChart()
+          }
+        },
+        deep: true,
+      },
+    },
+    mounted() {
+      this.init()
+      window.addEventListener("resize", this.handleResize)
+    },
+    beforeDestroy() {
+      if (this.chartInstanceA) {
+        this.chartInstanceA.dispose()
+      }
+      window.removeEventListener("resize", this.handleResize)
+    },
+    methods: {
+      // 初始化组件
+      init() {
+        this.initChart()
+      },
+
+      // 初始化图表
+      initChart() {
+        this.chartInstanceA = echarts.init(
+          document.getElementById("marketChart")
+        )
+        this.chartInstanceB = echarts.init(
+          document.getElementById("marketChart2")
+        )
+
+        const lastMonthData = Object.keys(this.data)
+          .map((key, index) => {
+            // 新增企业
+            if (key == "customer") {
+              return -42
+            }
+            // 跟进企业
+            if (key == "followup") {
+              return -86
+            }
+            // 新增联系人
+            if (key == "contact") {
+              return -230
+            }
+            // 新增合同
+            if (key == "contract") {
+              return -76
+            }
+            // 新增项目
+            if (key == "residence") {
+              return -197
+            }
+            return -this.data[key].last_month
+          })
+          .reverse()
+
+        const thisMonthData = Object.keys(this.data)
+          .map((key, index) => {
+            // 新增企业
+            if (key == "customer") {
+              return 19
+            }
+            // 跟进企业
+            if (key == "followup") {
+              return 68
+            }
+            // 新增联系人
+            if (key == "contact") {
+              return 161
+            }
+            // 新增合同
+            if (key == "contract") {
+              return 46
+            }
+            // 新增项目
+            if (key == "residence") {
+              return 150
+            }
+            return this.data[key].this_month
+          })
+          .reverse()
+
+        const optionA = {
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+            backgroundColor: "rgba(17, 28, 49, 0.9)",
+            borderColor: "rgba(64, 158, 255, 0.2)",
+            textStyle: {
+              color: "#ffffff",
+            },
+            formatter: (params) => {
+              // 自定义tooltip格式
+              let result = params[0].name + "<br/>"
+              params.forEach((item) => {
+                result +=
+                  item.marker +
+                  item.seriesName +
+                  ": " +
+                  Math.abs(item.value) +
+                  "<br/>"
+              })
+              return result
+            },
+          },
+          grid: {
+            left: "10%",
+            right: "1%",
+            bottom: "5%",
+            top: "5%",
+            containLabel: true,
+          },
+          xAxis: {
+            type: "value",
+            axisLabel: {
+              formatter: (value) => {
+                if (value < 0) return -value //这里是针对取负值
+                else return value
+              },
+            },
+            show: false, // 隐藏x轴
+          },
+          yAxis: [
+            {
+              type: "category",
+              data: this.chartData.categories,
+              axisLine: {
+                show: false,
+              },
+              axisTick: {
+                show: false,
+              },
+              axisLabel: {
+                show: false,
+              },
+            },
+          ],
+          series: [
+            {
+              name: "上月",
+              type: "bar",
+              // barMinHeight: 20,
+              data: lastMonthData,
+              itemStyle: {
+                // 左侧渐变色:从#F66757到#FCE2B4
+                color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
+                  { offset: 0, color: "#FCE2B4" },
+                  { offset: 1, color: "#F66757" },
+                ]),
+                borderRadius: [20, 0, 0, 20],
+              },
+              label: {
+                show: true,
+                position: "left",
+                formatter: "{c}",
+                color: "#ffffff",
+                fontSize: 12,
+                formatter: (value) => {
+                  return -value.data
+                },
+              },
+            },
+          ],
+        }
+
+        const optionB = {
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+            backgroundColor: "rgba(17, 28, 49, 0.9)",
+            borderColor: "rgba(64, 158, 255, 0.2)",
+            textStyle: {
+              color: "#ffffff",
+            },
+            formatter: (params) => {
+              // 自定义tooltip格式
+              let result = params[0].name + "<br/>"
+              params.forEach((item) => {
+                result +=
+                  item.marker +
+                  item.seriesName +
+                  ": " +
+                  Math.abs(item.value) +
+                  "<br/>"
+              })
+              return result
+            },
+          },
+          grid: {
+            left: "1%",
+            right: "10%",
+            bottom: "5%",
+            top: "5%",
+            containLabel: true,
+          },
+          xAxis: {
+            type: "value",
+            axisLabel: {
+              formatter: (value) => {
+                if (value < 0) return -value //这里是针对取负值
+                else return value
+              },
+            },
+            show: false, // 隐藏x轴
+          },
+          yAxis: [
+            {
+              type: "category",
+              data: this.chartData.categories,
+              axisLine: {
+                show: false,
+              },
+              axisTick: {
+                show: false,
+              },
+              axisLabel: {
+                show: true,
+                color: "#a0b3d6",
+                fontSize: 14,
+                align: "right",
+              },
+            },
+          ],
+          series: [
+            {
+              name: "本月",
+              type: "bar",
+              // barMinHeight: 20,
+              data: thisMonthData,
+              itemStyle: {
+                // 右侧渐变色:从#1620D4到#3AB3E3
+                color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                  { offset: 0, color: "#3AB3E3" },
+                  { offset: 1, color: "#1620D4" },
+                ]),
+                borderRadius: [0, 20, 20, 0],
+              },
+              label: {
+                show: true,
+                position: "right",
+                formatter: "{c}",
+                color: "#ffffff",
+                fontSize: 12,
+              },
+            },
+          ],
+        }
+
+        this.chartInstanceA.setOption(optionA)
+        this.chartInstanceA.resize()
+        this.chartInstanceB.setOption(optionB)
+        this.chartInstanceB.resize()
+      },
+
+      // 处理窗口大小变化
+      handleResize() {
+        if (this.chartInstanceA) {
+          this.chartInstanceA.resize()
+        }
+        if (this.chartInstanceB) {
+          this.chartInstanceB.resize()
+        }
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/css/theme.scss";
+
+  .market-container {
+    background: #0d1629;
+    border-radius: var(--radius);
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    background: var(--content-bg);
+  }
+
+  .market-title {
+    border-radius: var(--radius);
+    background: var(--title-bg);
+    padding: halfH(4) halfW(10);
+    font-size: halfW(16);
+    color: var(--title-primary);
+    display: flex;
+    align-items: center;
+  }
+  .legend {
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
+    padding: halfH(10) halfW(20);
+    font-size: halfW(14);
+    font-weight: 500;
+    color: #fff;
+    border-bottom: 1px solid rgba(64, 158, 255, 0.2);
+  }
+
+  .chart-wrap {
+    display: flex;
+    align-items: center;
+    width: 100%;
+    height: 100%;
+    .chart {
+      width: 43%;
+      height: 100%;
+      background: var(--content-bg);
+      border-radius: var(--radius);
+    }
+    .chart2 {
+      width: 57%;
+      height: 100%;
+      background: var(--content-bg);
+      border-radius: var(--radius);
+    }
+  }
+</style>

二進制
src/views/hui-jia/components/online-pay-day/images/icon-title.png


+ 2 - 0
src/views/hui-jia/components/online-pay-day/index.js

@@ -0,0 +1,2 @@
+import OnlinePayDay from './index.vue'
+export default OnlinePayDay

+ 430 - 0
src/views/hui-jia/components/online-pay-day/index.vue

@@ -0,0 +1,430 @@
+<template>
+    <Card :title="title" :subTitle="subTitle" :icon="icon" unit="单位(%)" class="online-pay-day">
+      <div class="info margin-r-20" v-if="showInfo">
+        <div class="text">
+          <p class="p1">线上缴费</p>
+          <p class="p2 margin-tg-20">日热度统计</p>
+          <p class="p3">(近12月日平均)</p>
+        </div>
+        <div class="legend">
+          <p class="legend1">
+            <span class="legend1-color"></span>
+            <span>时段缴费</span>
+          </p>
+          <p class="legend2 margin-tg-20">
+            <span class="legend2-color"></span>
+            <span>时段占比</span>
+          </p>
+          <p class="legend3">
+            <span class="legend3-color"><b class="inner"></b></span>
+            <span>时段比例</span>
+          </p>
+        </div>
+      </div>
+      <div ref="onlinePayDayBar" class="online-pay-day-bar flex-1"></div>
+    </Card>
+  </template>
+<script>
+import * as echarts from 'echarts'
+import { huiJiaApi } from '@/api'
+// import { getBigNumberWithUint } from '@/libs/tools.js'
+import Card from '@/views/hui-jia/components/card'
+import icon from './images/icon-title.png'
+
+export default {
+  name: 'OnlinePayDay',
+  components: {
+    Card
+  },
+  props: {
+    showMarkPoint: {
+      type: Boolean,
+      default: () => true
+    },
+    showInfo: {
+      type: Boolean,
+      default: () => true
+    }
+  },
+  data () {
+    return {
+      onlinePayDayBarInstance: null,
+      xAxisData: ['01:00', '04:00', '07:00', '10:00', '13:00', '16:00', '19:00', '22:00'],
+      seriesData: [0, 0, 0, 0, 0, 0, 0, 0],
+      title: '线上缴费日热度',
+      subTitle: '近12个月',
+      icon: icon
+    }
+  },
+  mounted () {
+    this.initChart()
+    this.getOnlinePayDay()
+    window.addEventListener("resize", this.handleResize)
+  },
+  beforeDestroy() {
+    if (this.onlinePayDayBarInstance) {
+      this.onlinePayDayBarInstance.dispose()
+    }
+    window.removeEventListener("resize", this.handleResize)
+  },
+  methods: {
+    getOnlinePayDay () {
+      huiJiaApi.getOnlinePayDay().then(res => {
+        this.xAxisData = (res.data.data.list || []).map(item => item.statData)
+        this.seriesData = (res.data.data.list || []).map(item => item.payAmount)
+        this.initChart()
+      })
+    },
+    /**
+     * 获取一个和max最接近的能被5整除的无零头整数
+     */
+    getMax (max) {
+      if (max <= 10) return 10
+
+      let pow = max.toString().length - 2
+      pow = Math.pow(10, pow)
+      max = Math.ceil(max / pow / 10) * 10 * pow
+      return max
+    },
+    initChart () {
+      this.onlinePayDayBarInstance = echarts.init(this.$refs.onlinePayDayBar)
+      const xAxisData = this.xAxisData
+      const seriesData = this.seriesData
+      let all = seriesData.reduce((total, num) => {
+        return Number(total) + Number(num)
+      }, 0)
+      let markPointData = []
+
+      seriesData.forEach((item, i) => {
+        const value = (all > 0 ? (Number(item) / all) * 100 : 0).toFixed(1) + '%'
+        markPointData.push({
+          value,
+          xAxis: i,
+          yAxis: item,
+          itemStyle: {
+            opacity: xAxisData[i].indexOf('hide') !== -1 ? 0 : 1
+          }
+        })
+      })
+      let lineData = []
+
+      let min = seriesData.reduce((min, num) => {
+        return Math.min(Number(min), Number(num))
+      }, 0)
+
+      let max = seriesData.reduce((min, num) => {
+        return Math.max(Number(min), Number(num))
+      }, 0)
+
+      seriesData.forEach((item, i) => {
+        lineData.push(Number(item) - min * 0.5)
+      }, 0)
+      const textSize = (text, fontSize) => {
+        var span = document.createElement('span')
+        var result = {
+          'width': span.offsetWidth,
+          'height': span.offsetHeight
+        }
+        span.style.visibility = 'hidden'
+        span.style.fontSize = fontSize || '14px'
+        document.body.appendChild(span)
+
+        if (typeof span.textContent !== 'undefined') { span.textContent = text || '国' } else span.innerText = text || '国'
+
+        result.width = span.offsetWidth - result.width
+        result.height = span.offsetHeight - result.height
+        span.parentNode.removeChild(span)
+        return result
+      }
+      const option = {
+        tooltip: {},
+        legend: {
+          show: false
+        },
+        grid: {
+          top: this.showMarkPoint ? '10%' : '6%',
+          bottom: '0px',
+          left: '0px',
+          right: '0px',
+          containLabel: true
+        },
+        xAxis: {
+          axisLabel: {
+            color: '#fff',
+            margin: 15,
+            fontSize: 12,
+            formatter: function (val, idx) {
+              let value = xAxisData[idx]
+              return value
+            }
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: '#666',
+              opacity: 1,
+              cap: 'round'
+            }
+          },
+          boundaryGap: true,
+          axisTick: {
+            alignWithLabel: true,
+            length: 7,
+            lineStyle: {
+              width: 3,
+              color: '#fff',
+              opacity: 0.5
+            }
+          },
+          splitLine: {
+            show: true,
+            lineStyle: {
+              color: "rgba(160, 179, 214, 0.1)",
+              type: "dashed",
+              width: 1,
+            }
+          },
+          max: function (value) { // x轴左侧留白
+            return value.max + 0.3
+          },
+          min: function (value) { // x轴左侧留白
+            return -0.3
+          },
+          data: xAxisData
+        },
+        yAxis: [
+          {
+            axisLabel: {
+              color: 'rgba(160, 179, 214, 0.7)',
+              fontSize: 11,
+              margin: 12
+            },
+            axisLine: {
+              show: false,
+              lineStyle: {
+                color: '#fff'
+              }
+            },
+            axisTick: {
+              length: 14,
+              lineStyle: {
+                width: 2,
+                fontSize: 16
+              }
+            },
+            splitLine: {
+              show: true,
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.1)",
+                type: "dashed",
+                width: 1,
+                dashOffset: 5
+              }
+            }
+          },
+          {
+            type: 'value',
+            position: 'right',
+            axisLabel: {
+              show: false
+            },
+            axisTick: {
+              show: true,
+              length: 14,
+              lineStyle: {
+                width: 2,
+                fontSize: 16
+              }
+            },
+            axisLine: {
+              show: false
+            }
+          }
+        ],
+        series: [
+          {
+            type: 'line',
+            silent: true,
+            symbolSize: 10,
+            yAxisIndex: 0,
+            itemStyle: {
+              color: '#FFE1D4',
+              borderColor: 'rgba(255, 162, 41, 0.7)',
+              borderWidth: 3
+            },
+            lineStyle: {
+              // opacity: 0
+              color: '#3D7BF8',
+              width: 2
+            },
+            markPoint: {
+              // symbol: 'none',
+              symbol: this.showMarkPoint ? 'path://m 0,0 h 48 a 5 5 0 0 1 4 4 v 20 a 5 5 0 0 1 -4 4 h -18 l -5,5 l -5,-5 h -18 a 5 5 0 0 1 -4 -4 v -20 a 5 5 0 0 1 4 -4 z' : 'none', // 带下箭头的圆角矩形
+              symbolSize: function (val) {
+                return [textSize(val, '14px').width + 20, 55] // 设置宽高
+              },
+              itemStyle: {
+                color: '#262BB3' // %值的背景颜色
+              },
+              symbolOffset: ['0', '-40%'], // 相对位置
+              symbolKeepAspect: true, // 是否保持宽高比
+              label: {
+                position: 'insideTop',
+                fontSize: 14,
+                distance: 10
+              },
+              data: markPointData,
+              silent: true
+            },
+            areaStyle: {
+              color: {
+                type: 'linear',
+                x: 0,
+                y: 0,
+                x2: 0,
+                y2: 1,
+                colorStops: [{
+                  offset: 0, color: 'rgba(21, 147, 222, 1)' // 0% 处的颜色
+                }, {
+                  offset: max ? (max - min) / max : 1, color: 'rgba(61,123,248,0)' // 50% 处的颜色
+                }, {
+                  offset: 1, color: 'rgba(61,123,248,0)' // 100% 处的颜色
+                }],
+                global: false // 缺省为 false
+              }
+            },
+            data: lineData
+          },
+          {
+            type: 'bar',
+            barWidth: '25%',
+            labelLine: {
+              show: false
+            },
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: '#491CC7' },
+                { offset: 1, color: '#E29163' }
+              ])
+            },
+            data: seriesData
+          }]
+      }
+      this.onlinePayDayBarInstance.setOption(option)
+      this.onlinePayDayBarInstance.resize()
+    },
+    handleResize() {
+      if (this.onlinePayDayBarInstance) {
+        this.onlinePayDayBarInstance.resize()
+      }
+    },
+  }
+}
+</script>
+<style lang="scss">
+@import '@/assets/css/function.scss';
+.online-pay-day-bar {
+  width: 100%;
+  // height: halfH(350);
+}
+.online-pay-day {
+  .card-body{
+    display: flex;
+  }
+  .total{
+    height: halfH(24);
+    line-height: halfH(24);
+    padding: halfH(18) halfW(18);
+    color: #fff;
+    float: left;
+    font-size: halfW(14);
+    border-radius: 5px;
+    background-color: rgba(17, 28, 48, 1);
+    text-align: center;
+    border: 1px solid rgba(0, 123, 211, 0.2);
+  }
+  .img{
+    width: halfW(40);
+    height: halfH(40);
+    margin-right: halfW(27);
+    margin-top: halfH(-7);
+    padding: halfH(8) halfW(8);
+    box-sizing: border-box;
+    background: #12223C;
+    border-radius: 50%;
+    .icon{
+      width: 100%;
+      height: 100%;
+    }
+  }
+  .info{
+      width: halfW(160);
+      // height: calc(100% - halfH(60));
+      border: 1px solid rgba(0, 123, 211, 0.2);
+      border-radius: halfW(5) halfH(5);
+      display: flex;
+      flex-direction: column;
+      .p1{
+        font-size: halfW(14);
+        color: #B9E3F4;
+      }
+      .p2{
+        font-size: halfW(18);
+        color: #fff;
+      }
+      .p3{
+        font-size: halfW(12);
+        color: #9FA5B7;
+      }
+    }
+    .text,.legend{
+      padding: halfH(30) halfW(16);
+    }
+    .text{
+      // height: halfH(125);
+      flex: 1;
+      // line-height: halfH(40);
+
+    }
+    .legend{
+      color: #808BAD;
+      background: #111C30;
+      font-size: halfW(14);
+      flex: 1;
+      // line-height: halfH(40);
+      .legend1-color, .legend2-color{
+        display: inline-block;
+        width: halfW(20);
+        height: halfH(8);
+        margin-right: halfW(10);
+        vertical-align: middle;
+      }
+      .legend1-color{
+        background: linear-gradient(90deg, #FEA373 0%, #FF678B 100%);
+      }
+      .legend2-color{
+        background: linear-gradient(90deg, #0F67F8 0%, #98D7F1 50%, #77ECED 100%);
+      }
+      .legend3-color{
+        display: inline-block;
+        width: halfW(20);
+        height: halfH(20);
+        background: linear-gradient(90deg, #F6D1A7 0%, #F5BD7C 50%, #F4A850 100%);
+        border-radius: 50%;
+        margin-right: halfW(10);
+        position: relative;
+        .inner{
+          display: inline-block;
+          width: halfW(10);
+          height: halfH(10);
+          background: linear-gradient(90deg, #C9FFBF 0%, #FFB0BE 100%);
+          border-radius: 50%;
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+        }
+      }
+    }
+}
+</style>

二進制
src/views/hui-jia/components/online-pay-week/images/icon-title.png


+ 2 - 0
src/views/hui-jia/components/online-pay-week/index.js

@@ -0,0 +1,2 @@
+import OnlinePayWeek from './index.vue'
+export default OnlinePayWeek

+ 403 - 0
src/views/hui-jia/components/online-pay-week/index.vue

@@ -0,0 +1,403 @@
+<template>
+    <Card :title="title" :subTitle="subTitle" :icon="icon" unit="单位(%)" class="online-pay-week">
+      <div class="info margin-r-20" v-if="showInfo">
+        <div class="title">
+          <p class="p1">线上缴费</p>
+          <p class="p2 margin-tg-20">周热度统计</p>
+          <p class="p3">(近12月周平均)</p>
+        </div>
+        <div class="legend">
+          <p class="legend1">
+            <span class="legend1-color"></span>
+            <span>日线上缴费</span>
+          </p>
+          <p class="legend2 margin-tg-20">
+            <span class="legend2-color"></span>
+            <span>日占比</span>
+          </p>
+          <p class="legend3">
+            <span class="legend3-color"><b class="inner"></b></span>
+            <span>日比例</span>
+          </p>
+        </div>
+      </div>
+      <div ref="onlinePayWeekBar" class="online-pay-week-bar flex-1"></div>
+    </Card>
+  </template>
+<script>
+import * as echarts from 'echarts'
+import { huiJiaApi } from '@/api'
+import Card from '@/views/hui-jia/components/card'
+import icon from './images/icon-title.png'
+
+export default {
+  name: 'OnlinePayWeek',
+  components: {
+    Card
+  },
+  props: {
+    showMarkPoint: {
+      type: Boolean,
+      default: () => true
+    },
+    showInfo: {
+      type: Boolean,
+      default: () => true
+    }
+  },
+  data () {
+    return {
+      onlinePayWeekBarInstance: null,
+      xAxisData: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
+      seriesData: [0, 0, 0, 0, 0, 0, 0],
+      title: '线上缴费周热度',
+      subTitle: '近12个月',
+      icon: icon
+    }
+  },
+  mounted () {
+    this.initChart()
+    this.getOnlinePayWeek()
+    window.addEventListener("resize", this.handleResize)
+  },
+  beforeDestroy() {
+    if (this.onlinePayWeekBarInstance) {
+      this.onlinePayWeekBarInstance.dispose()
+    }
+    window.removeEventListener("resize", this.handleResize)
+  },
+  methods: {
+    getOnlinePayWeek () {
+      huiJiaApi.getOnlinePayWeek().then(res => {
+        this.xAxisData = (res.data.data.list || []).map(item => item.statData)
+        this.seriesData = (res.data.data.list || []).map(item => item.payAmount)
+        this.initChart()
+      })
+    },
+    /**
+     * 获取一个和max最接近的能被5整除的无零头整数
+     */
+    getMax (max) {
+      if (max <= 10) return 10
+
+      let pow = max.toString().length - 2
+      pow = Math.pow(10, pow)
+      max = Math.ceil(max / pow / 10) * 10 * pow
+      return max
+    },
+    initChart () {
+      this.onlinePayWeekBarInstance = echarts.init(this.$refs.onlinePayWeekBar)
+      const xAxisData = this.xAxisData
+      const seriesData = this.seriesData
+      let all = seriesData.reduce((total, num) => {
+        return Number(total) + Number(num)
+      }, 0)
+      let markPointData = []
+
+      seriesData.forEach((item, i) => {
+        const value = (all > 0 ? (Number(item) / all) * 100 : 0).toFixed(1) + '%'
+        markPointData.push({
+          value,
+          xAxis: i,
+          yAxis: item,
+          itemStyle: {
+            opacity: xAxisData[i].indexOf('hide') !== -1 ? 0 : 1
+          }
+        })
+      })
+      let lineData = []
+
+      let min = seriesData.reduce((min, num) => {
+        return Math.min(Number(min), Number(num))
+      }, 0)
+
+      let max = seriesData.reduce((min, num) => {
+        return Math.max(Number(min), Number(num))
+      }, 0)
+
+      seriesData.forEach((item, i) => {
+        lineData.push(Number(item) - min * 0.5)
+      }, 0)
+      const textSize = (text, fontSize) => {
+        var span = document.createElement('span')
+        var result = {
+          'width': span.offsetWidth,
+          'height': span.offsetHeight
+        }
+        span.style.visibility = 'hidden'
+        span.style.fontSize = fontSize || '14px'
+        document.body.appendChild(span)
+
+        if (typeof span.textContent !== 'undefined') { span.textContent = text || '国' } else span.innerText = text || '国'
+
+        result.width = span.offsetWidth - result.width
+        result.height = span.offsetHeight - result.height
+        span.parentNode.removeChild(span)
+        return result
+      }
+      const option = {
+        tooltip: {},
+        legend: {
+          show: false
+        },
+        grid: {
+          top: this.showMarkPoint ? '10%' : '6%',
+          bottom: '0px',
+          left: '0px',
+          right: '0px',
+          containLabel: true
+        },
+        xAxis: {
+          axisLabel: {
+            color: '#fff',
+            margin: 15,
+            fontSize: 12,
+            formatter: function (val, idx) {
+              let value = xAxisData[idx]
+              return value
+            }
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: '#666',
+              opacity: 1,
+              cap: 'round'
+            }
+          },
+          boundaryGap: true,
+          axisTick: {
+            alignWithLabel: true,
+            length: 7,
+            lineStyle: {
+              width: 3,
+              color: '#fff',
+              opacity: 0.5
+            }
+          },
+          splitLine: {
+            show: true,
+            lineStyle: {
+              color: "rgba(160, 179, 214, 0.1)",
+              width: 1,
+              type: [5, 10]
+            }
+          },
+          max: function (value) { // x轴左侧留白
+            return value.max + 0.3
+          },
+          min: function (value) { // x轴左侧留白
+            return -0.3
+          },
+          data: xAxisData
+        },
+        yAxis: [
+          {
+            axisLabel: {
+              color: 'rgba(160, 179, 214, 0.7)',
+              fontSize: 11,
+              margin: 12
+            },
+            axisLine: {
+              show: false,
+              lineStyle: {
+                color: '#fff'
+              }
+            },
+            axisTick: {
+              length: 14,
+              lineStyle: {
+                width: 2,
+                fontSize: 16
+              }
+            },
+            splitLine: {
+              show: true,
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.1)",
+                type: "dashed",
+                width: 1,
+                dashOffset: 5
+              }
+            }
+          },
+          {
+            type: 'value',
+            position: 'right',
+            axisLabel: {
+              show: false
+            },
+            axisTick: {
+              show: true,
+              length: 14,
+              lineStyle: {
+                width: 2,
+                fontSize: 16
+              }
+            },
+            axisLine: {
+              show: false
+            }
+          }
+        ],
+        series: [
+          {
+            type: 'line',
+            silent: true,
+            symbolSize: 10,
+            yAxisIndex: 0,
+            itemStyle: {
+              color: '#FFE1D4',
+              borderColor: 'rgba(255, 162, 41, 0.7)',
+              borderWidth: 3
+            },
+            lineStyle: {
+              // opacity: 0
+              color: '#3D7BF8',
+              width: 2
+            },
+            markPoint: {
+              // symbol: 'none',
+              symbol: this.showMarkPoint ? 'path://m 0,0 h 48 a 5 5 0 0 1 4 4 v 20 a 5 5 0 0 1 -4 4 h -18 l -5,5 l -5,-5 h -18 a 5 5 0 0 1 -4 -4 v -20 a 5 5 0 0 1 4 -4 z' : 'none', // 带下箭头的圆角矩形
+              symbolSize: function (val) {
+                return [textSize(val, '14px').width + 20, 55] // 设置宽高
+              },
+              itemStyle: {
+                color: '#262BB3' // %值的背景颜色
+              },
+              symbolOffset: ['0', '-30%'], // 相对位置
+              symbolKeepAspect: true, // 是否保持宽高比
+              label: {
+                position: 'insideTop',
+                fontSize: 14,
+                distance: 10
+              },
+              data: markPointData,
+              silent: true
+            },
+            areaStyle: {
+              color: {
+                type: 'linear',
+                x: 0,
+                y: 0,
+                x2: 0,
+                y2: 1,
+                colorStops: [{
+                  offset: 0, color: 'rgba(21, 147, 222, 1)' // 0% 处的颜色
+                }, {
+                  offset: max ? (max - min) / max : 1, color: 'rgba(61,123,248,0)' // 50% 处的颜色
+                }, {
+                  offset: 1, color: 'rgba(61,123,248,0)' // 100% 处的颜色
+                }],
+                global: false // 缺省为 false
+              }
+            },
+            data: lineData
+          },
+          {
+            type: 'bar',
+            barWidth: '25%',
+            labelLine: {
+              show: false
+            },
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                { offset: 0, color: '#FEA373' },
+                { offset: 1, color: '#FF678B' }
+              ])
+            },
+            data: seriesData
+          }]
+      }
+      this.onlinePayWeekBarInstance.setOption(option)
+      this.onlinePayWeekBarInstance.resize()
+    },
+    handleResize() {
+      if (this.onlinePayWeekBarInstance) {
+        this.onlinePayWeekBarInstance.resize()
+      }
+    },
+  }
+}
+</script>
+<style lang="scss">
+@import '@/assets/css/function.scss';
+.online-pay-week-bar {
+  width: 100%;
+  // height: halfH(350);
+}
+.online-pay-week {
+  .card-body{
+    display: flex;
+    .info{
+      width: halfW(160);
+      // height: calc(100% - halfH(60));
+      border: 1px solid rgba(0, 123, 211, 0.2);
+      border-radius: halfW(5) halfH(5);
+      display: flex;
+      flex-direction: column;
+      .p1{
+        font-size: halfW(14);
+        color: #B9E3F4;
+      }
+      .p2{
+        font-size: halfW(18);
+        color: #fff;
+      }
+      .p3{
+        font-size: halfW(12);
+        color: #9FA5B7;
+      }
+    }
+    .title,.legend{
+      padding: halfH(30) halfW(16);
+    }
+    .title{
+      // height: halfH(125);
+      flex: 1;
+      // line-height: halfH(40);
+
+    }
+    .legend{
+      color: #808BAD;
+      background: #111C30;
+      font-size: halfW(14);
+      flex: 1;
+      // line-height: halfH(40);
+      .legend1-color, .legend2-color{
+        display: inline-block;
+        width: halfW(20);
+        height: halfH(8);
+        margin-right: halfW(10);
+        vertical-align: middle;
+      }
+      .legend1-color{
+        background: linear-gradient(90deg, #FEA373 0%, #FF678B 100%);
+      }
+      .legend2-color{
+        background: linear-gradient(90deg, #0F67F8 0%, #98D7F1 50%, #77ECED 100%);
+      }
+      .legend3-color{
+        display: inline-block;
+        width: halfW(20);
+        height: halfH(20);
+        background: linear-gradient(90deg, #F6D1A7 0%, #F5BD7C 50%, #F4A850 100%);
+        border-radius: 50%;
+        margin-right: halfW(10);
+        position: relative;
+        .inner{
+          display: inline-block;
+          width: halfW(10);
+          height: halfH(10);
+          background: linear-gradient(90deg, #C9FFBF 0%, #FFB0BE 100%);
+          border-radius: 50%;
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+        }
+      }
+    }
+  }
+}
+</style>

二進制
src/views/hui-jia/components/online-pay/images/icon-title.png


二進制
src/views/hui-jia/components/online-pay/images/icon-total.png


+ 2 - 0
src/views/hui-jia/components/online-pay/index.js

@@ -0,0 +1,2 @@
+import OnlinePay from './index.vue'
+export default OnlinePay

+ 277 - 0
src/views/hui-jia/components/online-pay/index.vue

@@ -0,0 +1,277 @@
+<template>
+    <Card :title="title" :subTitle="subTitle" :icon="icon" unit="单位(万元)" class="online-pay">
+      <div class="total display-flex" v-if="showTitle">
+        <div class="img">
+          <img src="./images/icon-total.png" alt="" class="icon">
+        </div>
+        <div>
+          总缴费(近12个月):¥{{ total }}
+        </div>
+      </div>
+      <div class="bar-con">
+        <div ref="onlinePayBar" class="online-pay-bar"></div>
+      </div>
+    </Card>
+  </template>
+<script>
+import * as echarts from 'echarts'
+// import { getBigNumberWithUint } from '@/libs/tools.js'
+import { huiJiaApi } from '@/api'
+import Card from '@/views/hui-jia/components/card'
+import icon from './images/icon-title.png'
+
+export default {
+  name: 'OnlinePay',
+  components: {
+    Card
+  },
+  props: {
+    showLegend: {
+      type: Boolean,
+      default: true
+    },
+    showTitle: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data () {
+    return {
+      onlinePayInstance: null,
+      xAxisData: ['十二月', '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月'],
+      seriesData: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+      total: 0,
+      title: '线上缴费统计',
+      subTitle: '近12个月',
+      icon: icon
+    }
+  },
+  
+  mounted () {
+    this.initChart()
+    this.getOnlinePayMonth()
+    window.addEventListener("resize", this.handleResize)
+  },
+  beforeDestroy() {
+    if (this.onlinePayInstance) {
+      this.onlinePayInstance.dispose()
+    }
+    window.removeEventListener("resize", this.handleResize)
+  },
+  methods: {
+    getOnlinePayMonth () {
+      huiJiaApi.getOnlinePayMonth().then(res => {
+        if(res && res.data && res.data.data && res.data.data.list.length) {
+          this.total = res.data.data.allSum
+          this.xAxisData = res.data.data.list.map(item => item.statData)
+          this.seriesData = res.data.data.list.map(item => item.payAmount)
+          this.initChart()
+        }
+      })
+    },
+    /**
+     * 获取一个和max最接近的能被5整除的无零头整数
+     */
+    getMax (max) {
+      if (max <= 10) return 10
+
+      let pow = max.toString().length - 2
+      pow = Math.pow(10, pow)
+      max = Math.ceil(max / pow / 10) * 10 * pow
+      return max
+    },
+    initChart () {
+      this.onlinePayInstance = echarts.init(this.$refs.onlinePayBar)
+     
+      const xAxisData = this.xAxisData
+      const seriesData = this.seriesData
+      // let max = 0
+      // max = seriesData.reduce((max, num) => {
+      //   return Math.max(Number(max), Number(num))
+      // })
+      // max = this.getMax(max)
+      const option = {
+        tooltip: {},
+        legend: {
+          show: this.showLegend,
+          right: '0',
+          top: '20px',
+          textStyle: {
+            color: '#fff'
+          }
+        },
+        grid: {
+          top: this.showLegend ? '80px' : '5%',
+          bottom: '0px',
+          left: '0px',
+          right: '0px',
+          containLabel: true
+        },
+        xAxis: {
+          axisLabel: {
+            color: '#fff',
+            margin: 15,
+            fontSize: 12,
+            formatter: function (val, idx) {
+              let value = xAxisData[idx]
+              return value
+            }
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: '#666',
+              opacity: 1,
+              cap: 'round'
+            }
+          },
+          boundaryGap: true,
+          axisTick: {
+            alignWithLabel: true,
+            length: 7,
+            lineStyle: {
+              width: 3,
+              color: '#fff',
+              opacity: 0.5
+            }
+          },
+          splitLine: {
+            show: true,
+            lineStyle: {
+              color: "rgba(160, 179, 214, 0.1)",
+              type: "dashed",
+              width: 1,
+              cap: 'round',
+              type: [5, 10]
+            }
+          },
+          max: function (value) { // x轴左侧留白
+            return value.max + 0.3
+          },
+          min: function (value) { // x轴左侧留白
+            return -0.3
+          },
+          data: xAxisData
+        },
+        yAxis: [
+          {
+            axisLabel: {
+              color: 'rgba(160, 179, 214, 0.7)',
+              fontSize: 11,
+              margin: 12
+            },
+            axisLine: {
+              show: false,
+              lineStyle: {
+                color: '#fff'
+              }
+            },
+            axisTick: {
+              length: 14,
+              lineStyle: {
+                width: 2,
+                fontSize: 16
+              }
+            },
+            splitLine: {
+              show: true,
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.1)",
+                type: "dashed",
+                width: 1,
+                dashOffset: 5
+              }
+            }
+          },
+          {
+            type: 'value',
+            position: 'right',
+            axisLabel: {
+              show: false
+            },
+            axisTick: {
+              show: true,
+              length: 14,
+              lineStyle: {
+                width: 2,
+                fontSize: 16
+              }
+            },
+            axisLine: {
+              show: false
+            }
+          }
+        ],
+        series: [{
+          name: '线上流水',
+          type: 'bar',
+          barWidth: '20%',
+          labelLine: {
+            show: false
+          },
+          itemStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: '#0F67F8' },
+              { offset: 0.5, color: '#98D7F1' },
+              { offset: 1, color: '#77ECED' }
+            ]),
+            barBorderRadius: [10, 10, 0, 0]
+          },
+          data: seriesData
+        }]
+      }
+      this.onlinePayInstance.setOption(option)
+      this.onlinePayInstance.resize()
+    },
+    handleResize() {
+      if (this.onlinePayInstance) {
+        this.onlinePayInstance.resize()
+      }
+    },
+  }
+}
+</script>
+<style lang="scss">
+@import '@/assets/css/function.scss';
+@import '@/assets/css/screen.scss';
+.bar-con{
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  width: 100%;
+  .online-pay-bar {
+    flex: 1;
+    width: 100%;
+    height: 100%;
+  }
+}
+.online-pay {
+
+  .total{
+    position: absolute;
+    height: halfH(24);
+    line-height: halfH(24);
+    padding: halfH(18) halfW(18);
+    color: #fff;
+    font-size: halfW(14);
+    border-radius: 5px;
+    background-color: rgba(17, 28, 48, 1);
+    text-align: center;
+    border: 1px solid rgba(0, 123, 211, 0.2);
+  }
+  .img{
+    width: halfW(40);
+    height: halfH(40);
+    margin-right: halfW(27);
+    margin-top: halfH(-7);
+    padding: halfH(8) halfW(8);
+    box-sizing: border-box;
+    background: #12223C;
+    border-radius: 50%;
+    .icon{
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+</style>

二進制
src/views/hui-jia/components/online-water/images/icon-month.png


二進制
src/views/hui-jia/components/online-water/images/icon-once.png


二進制
src/views/hui-jia/components/online-water/images/icon-title.png


二進制
src/views/hui-jia/components/online-water/images/icon-total.png


+ 2 - 0
src/views/hui-jia/components/online-water/index.js

@@ -0,0 +1,2 @@
+import OnlineWater from './index.vue'
+export default OnlineWater

+ 212 - 0
src/views/hui-jia/components/online-water/index.vue

@@ -0,0 +1,212 @@
+<template>
+  <Card :title="title" :icon="icon" class="online-water">
+    <div class="con-top display-flex border">
+      <div class="img">
+        <img src="./images/icon-total.png" alt="" class="icon">
+      </div>
+      <div class="info">
+        <p class="num">{{ onlineWaterInfo.allSum | formatNumber }}</p>
+        <p class="title">线上流水总额</p>
+      </div>
+    </div>
+    <div class="con-center display-flex">
+      <div class="con-item flex-1 border">
+        <div class="top display-flex">
+          <div class="img">
+            <img src="./images/icon-once.png" alt="" class="icon">
+          </div>
+          <div class="info flex-1">
+            <p class="num">{{ onlineWaterInfo.recentAveAmount || 0 }}</p>
+            <p class="title">单笔缴费金额</p>
+            <p class="desc">(近12个月平均值)</p>
+          </div>
+        </div>
+        <div class="bottom display-flex">
+          <span class="title">单笔最大值</span>
+          <span class="num">{{ onlineWaterInfo.recentMaxAmount || 0 }}</span>
+        </div>
+
+      </div>
+      <div class="con-item flex-1 border">
+        <div class="top display-flex">
+          <div class="img">
+            <img src="./images/icon-month.png" alt="" class="icon">
+          </div>
+          <div class="info flex-1">
+            <p class="num">{{ onlineWaterInfo.avgMonPayCnt || 0 }}</p>
+            <p class="title">月在线缴费次数</p>
+            <p class="desc">(近12个月平均值)</p>
+          </div>
+        </div>
+        <div class="bottom display-flex">
+          <span class="title">单笔最大值</span>
+          <span class="num">{{ onlineWaterInfo.maxMonthlyCnt || 0 }}</span>
+        </div>
+      </div>
+    </div>
+    <div class="con-footer display-flex border" v-if="showOnlineInfo">
+      <div class="info display-flex">
+        <div class="img">
+          <img src="./images/icon-total.png" alt="" class="icon">
+        </div>
+        <div class="text">
+          <p class="title">线上总缴费</p>
+          <p class="desc">(近12个月)</p>
+        </div>
+      </div>
+      <div class="num">
+        {{ onlineWaterInfo.recentlySum || 0 }}
+      </div>
+    </div>
+  </Card>
+</template>
+<script>
+import { huiJiaApi } from '@/api'
+import Card from '@/views/hui-jia/components/card'
+import icon from './images/icon-title.png'
+
+export default {
+  name: 'OnlineWater',
+  components: {
+    Card
+  },
+  props: {
+    showOnlineInfo: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      title: '线上流水数据',
+      icon: icon,
+      onlineWaterInfo: {}
+    }
+  },
+  mounted () {
+    this.getOnlineWaterStatistics()
+  },
+  methods: {
+    getOnlineWaterStatistics () {
+      huiJiaApi.getOnlineWaterStatistics().then(res => {
+        this.onlineWaterInfo = res.data.data
+      })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+@import '@/assets/css/function.scss';
+@import '@/assets/css/screen.scss';
+
+.online-water {
+  ::v-deep .card-body{
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    align-content: center;
+  }
+  .img{
+    width: halfW(50);
+    height: halfW(50);
+    padding: halfH(10) halfW(10);
+    box-sizing: border-box;
+    background: #12223C;
+    border-radius: 50%;
+    .icon{
+      width: halfW(30);
+      height: halfW(30);
+    }
+  }
+  .con-top{
+    flex: 1;
+    width: 100%;
+    padding: halfH(24) halfW(20);
+    background: #0E1629;
+    .info{
+      margin-left: halfW(20);
+      .title{
+        font-size: halfW(16);
+        color: #82D1F6;
+      }
+      .num{
+        font-size: halfW(20);
+        margin-bottom: halfH(10);
+        font-weight: 600;
+        color: #9BE3E0;
+      }
+    }
+  }
+  .con-center{
+    width: 100%;
+    margin-top: halfH(20) ;
+    .con-item{
+      box-sizing: border-box;
+      &:first-child{
+        margin-right: halfW(20);
+      }
+      .top{
+        padding: halfH(24) halfW(20);
+        .info{
+          margin-left: halfW(20);
+          .title{
+            font-size: halfW(14);
+            color: #82D1F6;
+            margin-bottom: halfH(8);
+          }
+          .num{
+            color: #F6F7FC;
+            margin-bottom: halfH(8);
+          }
+          .desc{
+            font-size: halfW(12);
+            color: #9FA5B7;
+          }
+        }
+      }
+      .bottom{
+        justify-content: space-between;
+        align-items: center;
+        background: #111C30;
+        padding: halfH(16) halfW(20);
+        .title{
+          font-size: halfW(14);
+          color: #9FA5B7;
+        }
+        .num{
+          font-size: halfW(18);
+          color: #F6F7FC;
+        }
+      }
+    }
+  }
+  .con-footer{
+    width: 100%;
+    margin-top: halfH(20);
+    padding: halfH(24) halfW(20);
+    background: #0E1629;
+    justify-content: space-between;
+    .info{
+      .text{
+        margin-left: halfW(20);
+        .title{
+          font-size: halfW(16);
+          margin-top: halfH(6);
+          color: #82D1F6;
+        }
+        .desc{
+          font-size: halfW(12);
+          margin-top: halfH(8);
+          color: #9FA5B7;
+        }
+      }
+
+    }
+    .num{
+        font-size: halfW(20);
+        margin-top: halfH(14);
+        color: #fff;
+      }
+  }
+}
+</style>

二進制
src/views/hui-jia/components/resident/images/icon-hfw.png


二進制
src/views/hui-jia/components/resident/images/icon-pay.png


二進制
src/views/hui-jia/components/resident/images/icon-qrcode.png


二進制
src/views/hui-jia/components/resident/images/icon-report.png


二進制
src/views/hui-jia/components/resident/images/icon-title.png


+ 2 - 0
src/views/hui-jia/components/resident/index.js

@@ -0,0 +1,2 @@
+import Resident from './index.vue'
+export default Resident

+ 311 - 0
src/views/hui-jia/components/resident/index.vue

@@ -0,0 +1,311 @@
+<template>
+    <Card :title="title" :icon="icon" class="resident">
+      <div class="display-flex w-full">
+        <div class="gauge margin-r-30">
+          <div ref="dom" class="hfw-gauge"></div>
+        </div>
+        <div class="num display-flex flex-1">
+          <div v-for="item in residentData" :key="item.title" class="resident-item flex-1">
+            <div class="value">{{ item.value | formatNumber }}</div>
+            <div class="title">{{ item.title }}</div>
+          </div>
+        </div>
+      </div>
+      <div class="operate display-flex w-full" v-if="showOperate">
+        <div v-for="item in operateData" :key="item.title" class="operate-item flex-1">
+          <div class="icon margin-t-30">
+            <img :src="item.icon" alt="" class="img">
+          </div>
+          <div class="value">{{ item.value | formatNumber }}</div>
+          <div class="title">{{ item.title }}</div>
+        </div>
+      </div>
+    </Card>
+  </template>
+<script>
+import * as echarts from 'echarts'
+import Card from '@/views/hui-jia/components/card'
+import icon from './images/icon-title.png'
+import iconReport from './images/icon-report.png'
+import iconQrcode from './images/icon-qrcode.png'
+import iconPay from './images/icon-pay.png'
+import { huiJiaApi } from '@/api'
+
+export default {
+  name: 'Resident',
+  components: {
+    Card
+  },
+  props: {
+    showOperate: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data () {
+    return {
+      hfwInstance: null,
+      rate: 0,
+      title: '用户信息',
+      icon: icon,
+      residentData: [
+        {
+          title: '公众号关注数',
+          value: 1000,
+          key: 'mpFollowCount'
+        },
+        {
+          title: '绘服务绑定数',
+          value: 1000,
+          key: 'hfwBindCount'
+        },
+        {
+          title: '日活跃数(近1月)',
+          value: 1000,
+          key: 'userLiveCount'
+        }
+      ],
+      operateData: [
+        {
+          title: '绘服务缴费次数',
+          value: 1000,
+          icon: iconPay,
+          key: 'payCount'
+        },
+        {
+          title: '绘服务扫码次数',
+          value: 1000,
+          icon: iconQrcode,
+          key: 'scanCodeCount'
+        },
+        {
+          title: '绘服务报事次数',
+          value: 1000,
+          icon: iconReport,
+          key: 'workCount'
+        }
+      ],
+      colorArr: ['#182744', '#82D1F6', '#02338C']
+    }
+  },
+  mounted () {
+    this.initChart()
+    this.getUsersStatistics()
+    window.addEventListener("resize", this.handleResize)
+  },
+  beforeDestroy() {
+    if (this.hfwInstance) {
+      this.hfwInstance.dispose()
+    }
+    window.removeEventListener("resize", this.handleResize)
+  },
+  methods: {
+    getUsersStatistics () {
+      huiJiaApi.getUsersStatistics().then(res => {
+        if(res && res.data){
+          this.rate = res.data.data.attentionRate || 0
+          this.residentData.forEach(item => {
+            item.value = res.data.data[item.key] || 0
+          })
+          this.operateData.forEach(item => {
+            item.value = res.data.data[item.key] || 0
+          })
+          this.initChart()
+        }
+       
+      })
+    },
+    initChart () {
+      this.hfwInstance = echarts.init(this.$refs.dom)
+      const option = {
+        title: {
+          show: true,
+          text: [`{titleBg|关注率}`],
+          left: 'center',
+          bottom: '0',
+          textStyle: {
+            rich: {
+              titleBg: {
+                backgroundColor: '#14223C',
+                borderRadius: 4,
+                height: 26,
+                width: 70,
+                color: '#00CAFF',
+                fontSize: 14
+              }
+            }
+          }
+        },
+        angleAxis: {
+          show: false,
+          max: 100 * 360 / 270, // -45度到225度,二者偏移值是270度除360度
+          type: 'value',
+          startAngle: 225, // 极坐标初始角度
+          splitLine: {
+            show: false
+          }
+        },
+        barMaxWidth: 16, // 圆环宽度
+        radiusAxis: {
+          show: false,
+          type: 'category'
+        },
+        // 圆环位置和大小
+        polar: {
+          center: ['50%', '50%'],
+          radius: '170%'
+        },
+        series: [{
+          type: 'bar',
+          data: [{ // 上层圆环,显示数据
+            value: 75,
+            itemStyle: {
+              color: { // 图形渐变颜色方法,四个数字分别代表,右,下,左,上,offset表示0%到100%
+                type: 'linear',
+                x: 0,
+                y: 0,
+                x2: 1, // 从左到右 0-1
+                y2: 0,
+                colorStops: [{
+                  offset: 0,
+                  color: this.colorArr[1]
+                }, {
+                  offset: 1,
+                  color: this.colorArr[2]
+                }]
+              }
+            }
+          }],
+          // barGap: '-100%', // 柱间距离,上下两层圆环重合
+          coordinateSystem: 'polar',
+          roundCap: true // 顶端圆角从 v4.5.0 开始支持
+          // z: 2 // 圆环层级,同zindex
+        },
+        { // 下层圆环,显示最大值
+          type: 'bar',
+          data: [{
+            value: 100,
+            itemStyle: {
+              color: this.colorArr[0]
+            }
+          }],
+          barGap: '-100%',
+          coordinateSystem: 'polar',
+          roundCap: true,
+          z: 1
+        },
+        // 仪表盘
+        {
+          name: '关注率',
+          type: 'gauge',
+          axisLine: {
+            show: false
+          },
+          splitLine: {
+            show: false
+          },
+          axisTick: {
+            show: false
+          },
+          axisLabel: {
+            show: false
+          },
+          splitLabel: {
+            show: false
+          },
+          pointer: {
+            show: false
+          },
+          title: {
+            offsetCenter: [0, '45%'],
+            fontSize: 14,
+            fontFamily: 'Microsoft YaHei',
+            fontWeight: '400',
+            color: '#5774A2'
+          },
+          detail: {
+            formatter: '{value}%',
+            offsetCenter: [0, '-10%'],
+            fontSize: 22,
+            fontFamily: 'DS-Digital',
+            fontWeight: 'bold',
+            color: '#00D2ED'
+          },
+          data: [{
+            value: this.rate,
+            name: '绘服务'
+          }]
+        }
+        ]
+      }
+      this.hfwInstance.setOption(option)
+      this.hfwInstance.resize()
+    },
+    handleResize() {
+      if (this.hfwInstance) {
+        this.hfwInstance.resize()
+      }
+    },
+  }
+}
+</script>
+<style lang="scss">
+@import '@/assets/css/function.scss';
+@import '@/assets/css/screen.scss';
+
+.resident{
+  .gauge{
+    width: 50%;
+    .hfw-gauge{
+      width: halfW(220);
+      height: halfH(220);
+      margin: 0 auto;
+    }
+  }
+  .num{
+    flex-direction: column;
+    text-align: center;
+    gap: halfH(20);
+    .resident-item{
+      align-content: center;
+    }
+    .title{
+      font-size: halfW(14);
+      color: #82D1F6;
+      margin-top: halfH(10);
+    }
+    .value{
+      font-size: halfW(20);
+      color: #FFFFFF;
+    }
+  }
+  .operate{
+    width: 100%;
+    height: calc(45%);
+    text-align: center;
+    border-radius: var(--radius);
+    background-color: rgba(17, 28, 48, 1);
+    border: 1px solid rgba(0, 123, 211, 0.2);
+    align-items: center;
+    .operate-item{
+      border-right: 1px solid rgba(0, 123, 211, 0.2);
+    }
+    .operate-item:last-child{
+      border-right: none;
+    }
+    .img{
+      width: halfW(40);
+      height: halfW(40);
+    }
+    .title{
+      font-size: halfW(14);
+      color: #82D1F6;
+    }
+    .value{
+      margin: halfH(20) 0;
+      font-size: halfW(20);
+      color: #FFFFFF;
+    }
+  }
+}
+</style>

+ 272 - 0
src/views/hui-jia/components/smart.vue

@@ -0,0 +1,272 @@
+<template>
+  <div class="smart-door">
+    <!-- 标题 -->
+    <div class="module-title">
+      <img src="@/assets/images/xlzb.png" class="title-icon" />
+      智能门禁
+    </div>
+
+    <!-- 数据卡片 -->
+    <div class="data-cards">
+      <div class="data-card">
+        <div class="data-value">{{ state.count | formatNumber }}</div>
+        <div class="data-label">门禁设备</div>
+      </div>
+
+      <div class="data-card">
+        <div class="data-value">{{ state.accessControlProjectCount | formatNumber }}</div>
+        <div class="data-label">开通项目</div>
+      </div>
+
+      <div class="data-card">
+        <div class="data-value">{{ state.openCount | formatNumber}}</div>
+        <div class="data-label">出入次数</div>
+      </div>
+    </div>
+
+    <!-- 通行流量趋势图 -->
+    <div class="chart-section">
+      <div class="chart-title">日通行流量趋势</div>
+      <div ref="trafficChart" class="traffic-chart"></div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import { huiJiaApi } from "@/api"
+
+  export default {
+    name: "SmartDoor",
+    data() {
+      return {
+        state: {
+          count: "921",
+          accessControlProjectCount: "92",
+          openCount: "900000",
+          chartInstance: null,
+          // 模拟的通行流量数据
+          list: [
+            { statData: "01:00", count: 150 },
+            { statData: "04:00", count: 250 },
+            { statData: "07:00", count: 420 },
+            { statData: "10:00", count: 380 },
+            { statData: "13:00", count: 320 },
+            { statData: "16:00", count: 230 },
+            { statData: "19:00", count: 220 },
+            { statData: "22:00", count: 300 },
+          ],
+        },
+      }
+    },
+    mounted() {
+      this.getData()
+      this.initChart()
+      window.addEventListener("resize", this.handleResize)
+    },
+    beforeDestroy() {
+      window.removeEventListener("resize", this.handleResize)
+      if (this.chartInstance) {
+        this.chartInstance.dispose()
+      }
+    },
+    methods: {
+      getData() {
+        huiJiaApi.getFaceDeviceData().then((res) => {
+          if (res && res.data) {
+            this.state = res.data.data
+            this.initChart()
+          }
+        })
+      },
+      initChart() {
+        const echarts = require("echarts")
+        this.chartInstance = echarts.init(this.$refs.trafficChart)
+
+        const option = {
+          tooltip: {
+            trigger: "axis",
+            backgroundColor: "rgba(0, 0, 0, 0.8)",
+            borderColor: "rgba(64, 158, 255, 0.3)",
+            textStyle: {
+              color: "#fff",
+            },
+            formatter: (params) => {
+              return `${params[0].name}<br/>${params[0].seriesName}: ${params[0].value}`
+            },
+          },
+          legend: {
+            data: ["开门次数"],
+            textStyle: {
+              color: "#fff",
+            },
+            top: "0%",
+            right: "0%",
+          },
+          grid: {
+            left: "3%",
+            right: "4%",
+            bottom: "10%",
+            top: "34%",
+            containLabel: true,
+          },
+          xAxis: {
+            type: "category",
+            data: this.state.list.map((item) => item.staData),
+            axisLine: {
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.6)",
+              },
+            },
+            axisLabel: {
+              color: "rgba(160, 179, 214, 0.7)",
+              fontSize: 14,
+            },
+            axisTick: {
+              show: false,
+            },
+            splitLine: {
+              show: true,
+              // 将坐标轴内的线设置为虚线
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.1)",
+                type: "dashed",
+              },
+            },
+          },
+          yAxis: {
+            type: "value",
+            axisLine: {
+              show: false,
+            },
+            axisLabel: {
+              color: "rgba(160, 179, 214, 0.7)",
+              fontSize: 12,
+            },
+            axisTick: {
+              show: false,
+            },
+            splitLine: {
+              // 将坐标轴内的线设置为虚线
+              lineStyle: {
+                color: "rgba(160, 179, 214, 0.1)",
+                type: "dashed",
+              },
+            },
+          },
+          series: [
+            {
+              name: "开门次数",
+              type: "bar",
+              data: this.state.list.map((item) => item.count),
+              barWidth: "30%",
+              // 添加标签配置,设置颜色为白色
+              label: {
+                show: true,
+                position: "top",
+                color: "#fff",
+                fontSize: 14,
+              },
+              itemStyle: {
+                color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
+                  { offset: 0, color: "#F6688B" },
+                  { offset: 1, color: "#F89877" },
+                ]),
+              },
+            },
+          ],
+        }
+
+        this.chartInstance.setOption(option)
+      },
+      handleResize() {
+        if (this.chartInstance) {
+          this.chartInstance.resize()
+        }
+      },
+      // 模拟数据更新方法
+      updateData() {
+        // 这里可以根据实际需求从API获取数据
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/css/theme.scss";
+
+  .smart-door {
+    background: var(--content-bg);
+    border-radius: var(--radius);
+    height: 100%;
+  }
+
+  .module-title {
+    display: flex;
+    border-radius: var(--radius);
+    font-size: halfW(16);
+    align-items: center;
+    color: var(--title-primary);
+    background: var(--title-bg);
+    padding: halfH(4) halfW(10);
+  }
+
+  .data-cards {
+    display: flex;
+    gap: halfW(12);
+    flex-wrap: nowrap;
+    margin: halfH(20) halfW(10);
+    background: var(--title-bg);
+    border: 1px solid rgba(64, 158, 255, 0.1);
+    border-radius: var(--radius);
+  }
+
+  .data-card {
+    flex: 1;
+    padding: halfH(10) halfW(6);
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    align-items: center;
+    gap: halfW(10);
+    &:nth-child(1) {
+      border-right: 1px solid rgba(64, 158, 255, 0.1);
+    }
+    &:nth-child(2) {
+      border-right: 1px solid rgba(64, 158, 255, 0.1);
+    }
+
+    .data-label {
+      font-weight: 500;
+      font-size: halfW(14);
+      color: var(--primary);
+    }
+
+    .data-value {
+      font-size: halfW(20);
+      font-weight: 500;
+      color: #fff;
+    }
+  }
+
+  .chart-section {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    padding: halfH(10);
+    height: calc(100% - halfH(200));
+  }
+
+  .chart-title {
+    position: absolute;
+    font-size: halfW(14);
+    font-weight: 500;
+    color: #fff;
+    margin-top: halfH(10);
+  }
+
+  .traffic-chart {
+    flex: 1;
+    width: 100%;
+    // min-height: halfH(250);
+  }
+</style>

+ 3 - 0
src/views/hui-jia/components/title/index.js

@@ -0,0 +1,3 @@
+import Title from './index.vue'
+
+export default Title

+ 21 - 0
src/views/hui-jia/components/title/index.vue

@@ -0,0 +1,21 @@
+<template>
+    <div class="title">
+        <img src="@/assets/images/hjkj-logo.png" alt="绘家科技" class="img" />
+    </div>
+</template>
+<script>
+export default {
+  name: 'Title'
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/css/screen.scss";
+.title{
+  text-align: center;
+  height: $title-hd;
+  .img{
+    width: halfW(346);
+    height: 100%;
+  }
+}
+</style>

二進制
src/views/hui-jia/components/user/images/icon-car.png


Some files were not shown because too many files changed in this diff