vbea 3 роки тому
батько
коміт
4a157fb348

+ 1 - 1
Strides-Admin/package.json

@@ -18,7 +18,7 @@
     "babel-preset-es2015": "^6.24.1",
     "clipboardy": "^2.3.0",
     "core-js": "^3.14.0",
-    "echarts": "4.2.1",
+    "echarts": "^5.2.0",
     "element-resize-detector": "^1.2.3",
     "element-ui": "^2.15.2",
     "fuse.js": "^3.4.4",

+ 4 - 1
Strides-Admin/src/http/api/dashboard.js

@@ -5,7 +5,10 @@ const dashboard = {
   getHourlyUtilization: (providerPk) => get('dashboard/getHourlyUtilization', {providerPk}),
   getCardSection: (providerPk) => get('dashboard/getCardSection', {providerPk}),
   getRangeTab: () => get('dashboard/getBottomTab'),
-  getBarChart: (params) => get('dashboard/getBottomChart', params)
+  getBarChart: (params) => get('dashboard/getBottomChart', params),
+  getSummaryInfo: (providerPk) => get('dashboard/summary/card', {providerPk}),
+  getHourlyUtilizationV2: (providerPk) => get('dashboard/hourly/usage', {providerPk}),
+  getBarChartV2: (params) => get('dashboard/bottom/chart', params),
 }
 
 export default dashboard

+ 2 - 2
Strides-Admin/src/router/SettingsRouter.js

@@ -14,7 +14,7 @@ export default {
   children: [
     {
       path: '/system-settings/mail-settings',
-      component: () => import('@/views/Administrator'),
+      component: () => import('@/views/zetting/Administrator'),
       name: 'system-mail-settings',
       meta: {
         title: 'Mail Settings',
@@ -24,7 +24,7 @@ export default {
     },
     {
       path: '/system-settings/charge-type-configuration',
-      component: () => import('@/views/Administrator'),
+      component: () => import('@/views/dashboard/Dashboard'),
       name: 'system-charge-type-configuration',
       meta: {
         title: 'Charge Type Configuration',

+ 2 - 2
Strides-Admin/src/router/index.js

@@ -79,7 +79,7 @@ const constantRoutes = [
     children: [
       {
         path: '/dashboard',
-        component: () => import('@/views/dashboard/Dashboard'),
+        component: () => import('@/views/dashboard/index'),
         name: 'dashboard',
         meta: {
           title: 'Dashboard',
@@ -89,7 +89,7 @@ const constantRoutes = [
       },
       {
         path: '/maps',
-        component: () => import('@/views/dashboard/Maps'),
+        component: () => import('@/views/dashboard/components/Maps'),
         name: 'maps',
         meta: {
           title: 'Maps',

+ 6 - 0
Strides-Admin/src/styles/element-ui.scss

@@ -1,5 +1,6 @@
 /* 改变主题色变量 */
 $--color-primary: #001489;
+$--color-accent: #A4C834;
 
 /* 改变 icon 字体路径变量,必需 */
 $--font-path: '~element-ui/lib/theme-chalk/fonts';
@@ -93,3 +94,8 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
 .el-range-separator {
   box-sizing: content-box;
 }
+
+:export {
+  colorPrimary: $--color-primary;
+  colorAccent: $--color-accent;
+}

+ 8 - 6
Strides-Admin/src/views/dashboard/chart/BarChart.vue

@@ -6,13 +6,14 @@
 </template>
 
 <script>
+  import theme from '../../../styles/element-ui.scss';
   import elementResizeDetectorMaker from 'element-resize-detector'
-  import echarts from 'echarts'
+  import * as echarts from 'echarts';
   export default {
     name: "BarChart",
     props: {
       index: {
-        type: Number,
+        type: Number|String,
         default: 0
       },
       chartData: {
@@ -70,7 +71,7 @@
           const p = [ydata[0], {
             value: ydata[1],
             itemStyle: {
-              color: '#001489'
+              color: theme.colorPrimary
             }
           }]
           ydata = p
@@ -122,7 +123,7 @@
           series: [{
             type: 'bar',
             itemStyle: {
-              color: '#0BD373',
+              color: theme.colorAccent,
               barBorderRadius: [4, 4, 0, 0]
             },
             label: {
@@ -148,7 +149,8 @@
   }
 </script>
 
-<style scoped="scoped">
+<style scoped="scoped" lang="scss">
+  @import '../../../styles/element-ui.scss';
   .title {
     color: #333;
     text-align: center;
@@ -164,7 +166,7 @@
     content: '';
     border-radius: 10px;
     margin-left: -25px;
-    background-color: #001489;
+    background-color: $--color-primary;
     position: absolute;
   }
   .barChart {

+ 138 - 0
Strides-Admin/src/views/dashboard/chart/CircleChart.vue

@@ -0,0 +1,138 @@
+<template>
+  <div id="circleChart" class="chart-view"></div>
+</template>
+
+<script>
+import theme from '../../../styles/element-ui.scss';
+import elementResizeDetectorMaker from 'element-resize-detector'
+import * as echarts from 'echarts'
+export default {
+  name: "CircleChart",
+  props: {
+    chartData: {
+      type: Object,
+      default: () => ({
+        usedConnectorCount: 0,
+        unusedConnectorCount: 0,
+        allConnectorCount: 1
+      })
+    }
+  },
+  data() {
+    return {
+      chart: null,
+    };
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.$nextTick(() => {
+          this.setOptions();
+        })
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+    const _this = this;
+    const erd = elementResizeDetectorMaker()
+    erd.listenTo(document.getElementById('circleChart'), (element) => {
+      _this.$nextTick(() => {
+        _this.chart.resize();
+      })
+    });
+  },
+  methods: {
+    initChart() {
+      console.log(echarts);
+      this.chart = echarts.init(document.getElementById('circleChart'), 'macarons')
+      this.setOptions()
+    },
+    setOptions() {
+      let percent = 0;
+      if (this.chartData.allConnectorCount) {
+        percent = (this.chartData.usedConnectorCount * 100 / this.chartData.allConnectorCount).toFixed(0);
+      }
+      const data =[{ 
+        value: this.chartData.usedConnectorCount,
+        name: 'In Use',
+        itemStyle: {
+          color: theme.colorAccent
+        }
+      }, {
+        value: this.chartData.unusedConnectorCount,
+        name: 'Unused',
+        itemStyle: {
+          color: "#001489"
+        }
+      }]
+      if (!this.chartData.usedConnectorCount) {
+        data[0].value = undefined;
+      }
+      this.chart.setOption({
+        title: {
+          text: percent + "%",
+          left: "center",
+          bottom: "41%",
+          textStyle: {
+            color: "#333",
+            fontSize: 30,
+            fontWeight: 'bold'
+          }
+        },
+        tooltip: {
+          trigger: 'item'
+        },
+        legend: {
+          bottom: '0%',
+          left: 'center',
+          itemWidth: 14,
+          selectedMode: false
+        },
+        series: [ {
+          name: 'Connector',
+          type: 'pie',
+          radius: ['55%', '70%'],
+          avoidLabelOverlap: false,
+          stillShowZeroSum: true,
+          label: {
+            show: false,
+            position: 'center'
+          },
+          startAngle: -90,
+          center: ['50%', '46%'],
+          emphasis: {
+            scale: false,
+            label: {
+              show: false
+            }
+          },
+          labelLine: {
+            show: false
+          },
+          data: data,
+          showEmptyCircle: true,
+          emptyCircleStyle: {
+            color: "#A0A0A0"
+          },
+          animationDuration: 2800,
+          animationEasing: 'quadraticInOut'
+        }]
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.chart-view {
+  min-width: 200px;
+  min-height: 200px;
+  text-align: center;
+  padding-bottom: 10px;
+  pointer-events: auto;
+}
+</style>

+ 4 - 3
Strides-Admin/src/views/dashboard/chart/LineChart.vue

@@ -3,8 +3,9 @@
 </template>
 
 <script>
+import theme from '../../../styles/element-ui.scss';
 import elementResizeDetectorMaker from 'element-resize-detector'
-import echarts from 'echarts'
+import * as echarts from 'echarts'
 
 export default {
   props: {
@@ -109,13 +110,13 @@ export default {
           smooth: true,
           type: 'line',
           itemStyle: {
-            color: '#0BD373',
+            color: theme.colorAccent, //'#0BD373'
             borderWidth: 4,
             shadowColor: '#dfe3ff',
             shadowBlur: 20
           },
           lineStyle: {
-            color: '#0BD373',
+            color: theme.colorAccent, //'#0BD373'
             width: 3
           },
           data: this.chartData.ydata,

+ 81 - 140
Strides-Admin/src/views/dashboard/Maps.vue → Strides-Admin/src/views/dashboard/components/Maps.vue

@@ -1,103 +1,51 @@
 <template>
   <div
     v-loading="isLoading"
-    class="maps-container">
+    :class='provider ? "maps-components" : "maps-container"'>
+    
+    <div class="filter-container">
+      <el-form class="filter-view">
+        <el-form-item
+          class="flex1" style="min-width: 200px; max-width: 320px;">
+          <el-input
+            placeholder="Search using keywords"
+            v-model="criteria"/>
+        </el-form-item>
+        <div>
+          <el-button
+            type="primary"
+            icon="el-icon-search"
+            @click="handleFilter">
+            Search
+          </el-button>
+        </div>
+      </el-form>
+    </div>
     <div class="card-group">
       <div class="maps-card-view">
-        <div class="card-view-content">
-          <div class="card-title">
-            In Use
-          </div>
-        </div>
-        <div class="card-value-container">
-          <div class="in-user-card-value">
-            {{ statistics.inUse }}
-          </div>
-          <div class="in-user-card-flag"/>
-        </div>
+        <div class="card-title">Available</div>
+        <div class="avaliable-card-value">{{ statistics.available || 0 }}</div>
       </div>
       <div class="maps-card-view">
-        <div class="card-view-content">
-          <div class="card-title">
-            Reserved
-          </div>
-        </div>
-        <div class="card-value-container">
-          <div class="reserved-card-value">
-            {{ statistics.reserved }}
-          </div>
-          <div class="reserved-card-flag"/>
-        </div>
+        <div class="card-title">In Use</div>
+        <div class="in-user-card-value">{{ statistics.inUse || 0}}</div>
       </div>
       <div class="maps-card-view">
-        <div class="card-view-content">
-          <div class="card-title">
-            Faulted
-          </div>
-        </div>
-        <div class="card-value-container">
-          <div class="faulted-card-value">
-            {{ statistics.faulted }}
-          </div>
-          <div class="faulted-card-flag"/>
-        </div>
+        <div class="card-title">Reserved</div>
+        <div class="reserved-card-value">{{ statistics.reserved || 0}}</div>
       </div>
       <div class="maps-card-view">
-        <div class="card-view-content">
-          <div class="card-title">
-            Available
-          </div>
-        </div>
-        <div class="card-value-container">
-          <div class="avaliable-card-value">
-            {{ statistics.available }}
-          </div>
-          <div class="avaliable-card-flag"/>
-        </div>
+        <div class="card-title">Faulted</div>
+        <div class="faulted-card-value">{{ statistics.faulted || 0 }}</div>
       </div>
       <div class="maps-card-view">
-        <div class="card-view-content">
-          <div class="card-title">
-            Unavailable
-          </div>
-        </div>
-        <div class="card-value-container">
-          <div class="unavaliable-card-value">
-            {{ statistics.unavailable }}
-          </div>
-          <div class="unavaliable-card-flag"/>
-        </div>
+        <div class="card-title">Unavailable</div>
+        <div class="unavaliable-card-value">{{ statistics.unavailable }}</div>
       </div>
     </div>
 
-    <div class="filter-container">
-      <el-row :gutter="36">
-        <el-form>
-          <el-col :span="7">
-            <el-form-item>
-              <el-input
-                placeholder="Search using keywords"
-                v-model="criteria"
-              >
-              </el-input>
-            </el-form-item>
-          </el-col>
-          <el-col :span="2">
-            <el-button
-              type="primary"
-              icon="el-icon-search"
-              @click="handleFilter">
-              Search
-            </el-button>
-          </el-col>
-          </el-form>
-      </el-row>
-    </div>
-
     <div class="google-map-container">
-      <div
-        class="google-map"
-        ref="googleMap"></div>
+      <div class="google-map" ref="googleMap"></div>
     </div>
   </div>
 </template>
@@ -106,14 +54,15 @@
 
 import { Loader } from "@googlemaps/js-api-loader"
 import MarkerClusterer from '@googlemaps/markerclustererplus';
-import MapModule from '../../http/api/map'
-
-const {
-  getConnectorStatusCount,
-  getSiteInfoList,
-} = MapModule
+import api from '@/http/api/map'
 
 export default {
+  props: {
+    provider: {
+      type: String|Number,
+      default: ""
+    }
+  },
   data() {
     return {
       isLoading: false,
@@ -137,11 +86,9 @@ export default {
   },
   async mounted() {
     this.getConnectorStatusCount()
-    await this.initializeMap()
+    this.initializeMap()
     this.getCurrentLocation()
-    this.getSiteInfoList().then(() => {
-      this.refreshMap()
-    })
+    this.getSiteInfoList()
   },
   methods: {
     getMarkerImageSize() {
@@ -150,7 +97,7 @@ export default {
     },
     getMarkerImage() {
       const size = this.getMarkerImageSize()
-      const markerImageUrl = require('../../assets/ic_marker.png')
+      const markerImageUrl = require('@/assets/ic_marker.png')
       const markerImage = {
         url: markerImageUrl,
         size,
@@ -160,7 +107,7 @@ export default {
     },
     getUnavailableMarkerImage() {
       const size = this.getMarkerImageSize()
-      const unavailableMarkerImageUrl = require('../../assets/ic_marker_un.png')
+      const unavailableMarkerImageUrl = require('@/assets/ic_marker_un.png')
       const unavailableMarkerImage = {
         url: unavailableMarkerImageUrl,
         size,
@@ -217,8 +164,8 @@ export default {
             })
             return {
               url: hasAvailableSite
-                ? require("../../assets/ic_cluster.png")
-                : require("../../assets/ic_cluster_un.png"),
+                ? require("@/assets/ic_cluster.png")
+                : require("@/assets/ic_cluster_un.png"),
               text: markers.length,
               index: markers.length < 10 ? hasAvailableSite ? 3 : 4 : hasAvailableSite ? 1 : 2
             }
@@ -266,33 +213,28 @@ export default {
         }
       )
     },
-    async getSiteInfoList() {
-      return getSiteInfoList(this.criteria)
-        .then(({ data }) => {
-          this.siteInfoList = data
-        }).catch(() => {
+    getSiteInfoList() {
+      api.getSiteInfoList(this.criteria).then(res => {
+        if (res.data) {
+          this.siteInfoList = res.data
+        } else {
+          this.siteInfoList = []
+        }
+        this.refreshMap()
+      }).catch(err => {
 
-        })
+      })
     },
     getConnectorStatusCount() {
-      getConnectorStatusCount().then(({data}) => {
-        const {
-          inUse,
-          reserved,
-          faulted,
-          available,
-          unavailable
-        } = data
-        this.statistics.inUse = inUse
-        this.statistics.reserved = reserved
-        this.statistics.faulted = faulted
-        this.statistics.available = available
-        this.statistics.unavailable = unavailable
+      api.getConnectorStatusCount().then(res => {
+        if (res.data) {
+          this.statistics = res.data
+        }
       }).catch(() => {
 
       })
     },
-    async initializeMap() {
+    initializeMap() {
       const mapContainer = this.$refs.googleMap
       const loader = new Loader({
         apiKey: this.apiKey,
@@ -333,38 +275,46 @@ export default {
 </script>
 
 <style lang="scss" scoped="scoped">
-  @import '../../styles/variables.scss';
+  @import '../../../styles/variables.scss';
 
   .maps-container {
     width: 100%;
+    padding: 20px;
     min-height: $mainAppMinHeight;
     background-color: #F0F5FC;
   }
-
-  .filter-container {
-    padding: 0px 30px;
+  
+  .maps-components {
+    margin: 0px;
   }
 
   .card-group {
+    padding: 0 5px;
     display: flex;
     flex-wrap: wrap;
     flex-flow: wrap;
     align-items: flex-start;
-    padding-top: 10px;
-    padding-left: 30px;
   }
 
   .maps-card-view {
+    flex: 1;
     display: flex;
-    flex: auto;
+    min-width: 200px;
+    max-width: 320px;
+    flex-direction: column;
     background-color: white;
     justify-content: center;
     align-items: center;
-    margin-bottom: 20px;
-    margin-right: 30px;
+    padding: 10px 10px;
+    margin-right: 10px;
+    margin-bottom: 10px;
     border-radius: 6px;
   }
   
+  .maps-card-view:last-child {
+    margin-right: 0px;
+  }
+  
   .card-view-content {
     display: inline;
   }
@@ -372,11 +322,10 @@ export default {
   .card-title {
     font-family: sans-serif;
     font-style: normal;
-    font-weight: 500;
+    font-weight: bold;
     font-size: 16px;
     line-height: 24px;
     color: #333333;
-    margin: 20px;
   }
 
   .card-value-container {
@@ -387,13 +336,11 @@ export default {
   }
 
   %card-value {
-    display: inline;
     font-family: sans-serif;
     font-style: normal;
     font-weight: 500;
     font-size: 42px;
     line-height: 63px;
-    letter-spacing: -0.03em;
   }
 
   .in-user-card-value {
@@ -413,7 +360,7 @@ export default {
 
   .avaliable-card-value {
     @extend %card-value;
-    color: #82CF08;
+    color: #1ABD00;
   }
 
   .unavaliable-card-value {
@@ -456,7 +403,9 @@ export default {
   }
 
   .google-map-container {
-    padding: 0px 30px 30px;
+    margin: 0 5px;
+    padding: 0px;
+    background-color: white;
   }
 
   .google-map {
@@ -492,12 +441,4 @@ export default {
 
   }
 
-  @media screen and (min-width: 1185px) {
-
-    .maps-card-view {
-      flex: initial;
-    }
-
-  }
-
 </style>

+ 168 - 0
Strides-Admin/src/views/dashboard/components/Site.vue

@@ -0,0 +1,168 @@
+<template>
+  <div v-loading="changeLoading">
+    <div class="card-group">
+      <div class="card-item-view">
+        <div class="card-title">Total Sites</div>
+        <div class="card-value">{{ statistics.available || 0 }}</div>
+      </div>
+      <div class="card-item-view">
+        <div class="card-title">Total Active Transactions</div>
+        <div class="card-value">{{ statistics.inUse || 0 }}</div>
+      </div>
+      <div class="card-item-view">
+        <div class="card-title">Total Completed Transactions</div>
+        <div class="card-value">{{ statistics.reserved || 0 }}</div>
+      </div>
+      <div class="card-item-view">
+        <div class="card-title">Total Reservations</div>
+        <div class="card-value">{{ statistics.faulted || 0 }}</div>
+      </div>
+      <div class="card-item-view">
+        <div class="card-title">Idling Sessions</div>
+        <div class="card-value">{{ statistics.unavailable || 0 }}</div>
+      </div>
+    </div>
+    <div class="card-group">
+      <div class="card-item-view">
+        <div class="title-underline">
+          <span>SITE NAME</span>
+        </div>
+        <el-skeleton
+          :rows="15"
+          animated
+          style="width: 100%; flex: 1"/>
+      </div>
+      <div class="card-item-view">
+        <div class="title-underline">
+          <span>SITE NAME</span>
+          <span>COUNT</span>
+        </div>
+        <el-skeleton
+          :rows="15"
+          animated
+          style="width: 100%; flex: 1"/>
+      </div>
+      <div class="card-item-view">
+        <div class="title-underline">
+          <span>SITE NAME</span>
+          <span>COUNT</span>
+        </div>
+        <el-skeleton
+          :rows="15"
+          animated
+          style="width: 100%; flex: 1"/>
+      </div>
+      <div class="card-item-view">
+        <div class="title-underline">
+          <span>SITE NAME</span>
+          <span>COUNT</span>
+        </div>
+        <el-skeleton
+          :rows="15"
+          animated
+          style="width: 100%; flex: 1"/>
+      </div>
+      <div class="card-item-view">
+        <div class="title-underline">
+          <span>SITE NAME</span>
+          <span>COUNT</span>
+        </div>
+        <el-skeleton
+          :rows="15"
+          animated
+          style="width: 100%; flex: 1"/>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "Site",
+    props: {
+      provider: {
+        type: String|Number,
+        default: ""
+      }
+    },
+    data() {
+      return {
+        statistics: {},
+        changeLoading: false
+      };
+    },
+    watch: {
+      provider(n, o) {
+        this.getData();
+      }
+    },
+    mounted() {
+      this.getData()
+    },
+    methods: {
+      getData() {
+        
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .card-group {
+    padding: 0 5px;
+    display: flex;
+    flex-wrap: wrap;
+    flex-flow: wrap;
+    align-items: flex-start;
+  }
+
+  .card-item-view {
+    flex: 1;
+    display: flex;
+    min-width: 250px;
+    max-width: 320px;
+    flex-direction: column;
+    background-color: white;
+    justify-content: center;
+    align-items: center;
+    padding: 10px 10px;
+    margin-right: 10px;
+    margin-bottom: 10px;
+    border-radius: 6px;
+  }
+  
+  .card-item-view:last-child {
+    margin-right: 0px;
+  }
+  
+  .card-title {
+    color: #333333;
+    font-size: 16px;
+    line-height: 24px;
+    font-family: sans-serif;
+    font-weight: bold;
+    white-space: nowrap;
+  }
+  
+  .card-value {
+    font-family: sans-serif;
+    font-style: normal;
+    font-weight: 500;
+    font-size: 42px;
+  }
+  
+  .title-underline {
+    width: 100%;
+    color: #333333;
+    font-size: 14px;
+    line-height: 21px;
+    display: flex;
+    align-items: center;
+    font-weight: bold;
+    white-space: nowrap;
+    padding-bottom: 20px;
+    font-family: sans-serif;
+    text-decoration: underline;
+    justify-content: space-between;
+  }
+</style>

+ 184 - 0
Strides-Admin/src/views/dashboard/components/Stations.vue

@@ -0,0 +1,184 @@
+<template>
+  <div v-loading="changeLoading">
+    <div class="card-group">
+      <div class="card-item-view">
+        <div class="card-title">Available</div>
+        <div class="card-value avaliable">{{ statistics.available || 0 }}</div>
+      </div>
+      <div class="card-item-view">
+        <div class="card-title">In Use</div>
+        <div class="card-value in-use">{{ statistics.inUse || 0 }}</div>
+      </div>
+      <div class="card-item-view">
+        <div class="card-title">Reserved</div>
+        <div class="card-value reserved">{{ statistics.reserved || 0 }}</div>
+      </div>
+      <div class="card-item-view">
+        <div class="card-title">Faulted</div>
+        <div class="card-value faulted">{{ statistics.faulted || 0 }}</div>
+      </div>
+      <div class="card-item-view">
+        <div class="card-title">Unavailable</div>
+        <div class="card-value unavailable">{{ statistics.unavailable || 0 }}</div>
+      </div>
+    </div>
+    <div class="card-group">
+      <div class="card-item-view">
+        <div class="title-underline">
+          <span>STATION</span>
+          <span>CONNECTOR</span>
+        </div>
+        <el-skeleton
+          :rows="15"
+          animated
+          style="width: 100%; flex: 1"/>
+      </div>
+      <div class="card-item-view">
+        <div class="title-underline">
+          <span>STATION</span>
+          <span>CONNECTOR</span>
+        </div>
+        <el-skeleton
+          :rows="15"
+          animated
+          style="width: 100%; flex: 1"/>
+      </div>
+      <div class="card-item-view">
+        <div class="title-underline">
+          <span>STATION</span>
+          <span>CONNECTOR</span>
+        </div>
+        <el-skeleton
+          :rows="15"
+          animated
+          style="width: 100%; flex: 1"/>
+      </div>
+      <div class="card-item-view">
+        <div class="title-underline">
+          <span>STATION</span>
+          <span>CONNECTOR</span>
+        </div>
+        <el-skeleton
+          :rows="15"
+          animated
+          style="width: 100%; flex: 1"/>
+      </div>
+      <div class="card-item-view">
+        <div class="title-underline">
+          <span>STATION</span>
+          <span>CONNECTOR</span>
+        </div>
+        <el-skeleton
+          :rows="15"
+          animated
+          style="width: 100%; flex: 1"/>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "Stations",
+    props: {
+      provider: {
+        type: String|Number,
+        default: ""
+      }
+    },
+    data() {
+      return {
+        statistics: {},
+        changeLoading: false
+      };
+    },
+    watch: {
+      provider(n, o) {
+        this.getData();
+      }
+    },
+    mounted() {
+      this.getData()
+    },
+    methods: {
+      getData() {
+        
+      }
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .card-group {
+    padding: 0 5px;
+    display: flex;
+    flex-wrap: wrap;
+    flex-flow: wrap;
+    align-items: flex-start;
+  }
+
+  .card-item-view {
+    flex: 1;
+    display: flex;
+    min-width: 250px;
+    max-width: 320px;
+    flex-direction: column;
+    background-color: white;
+    justify-content: center;
+    align-items: center;
+    padding: 10px 10px;
+    margin-right: 10px;
+    margin-bottom: 10px;
+    border-radius: 6px;
+  }
+  
+  .card-item-view:last-child {
+    margin-right: 0px;
+  }
+  
+  .card-title {
+    color: #333333;
+    font-size: 16px;
+    line-height: 24px;
+    font-family: sans-serif;
+    font-weight: bold;
+    white-space: nowrap;
+  }
+  
+  .card-value {
+    font-family: sans-serif;
+    font-style: normal;
+    font-weight: 500;
+    font-size: 42px;
+    &.avaliable {
+      color: #1ABD00;
+    }
+    &.in-use {
+      color: #236EDE;
+    }
+    &.reserved {
+      color: #FDA802;
+    }
+    &.faulted {
+      color: #EF3340;
+    }
+    &.unavaliable {
+      color: #000000;
+    }
+  }
+  
+  .title-underline {
+    width: 100%;
+    color: #333333;
+    font-size: 14px;
+    line-height: 21px;
+    display: flex;
+    align-items: center;
+    font-weight: bold;
+    white-space: nowrap;
+    padding-bottom: 20px;
+    font-family: sans-serif;
+    text-decoration: underline;
+    justify-content: space-between;
+  }
+</style>

+ 383 - 0
Strides-Admin/src/views/dashboard/components/Summary.vue

@@ -0,0 +1,383 @@
+<template>
+  <div v-loading="changeLoading">
+    <div class="summary-view flexr">
+      <div class="summary-view flexl flex1">
+        <div class="summary-card">
+          <div class="summary-title">Total Sites</div>
+          <div class="summary-max-value">{{summaryData.siteCount}}</div>
+        </div>
+        <div class="summary-card">
+          <div class="summary-title">Total Stations</div>
+          <div class="summary-max-value">{{summaryData.boxCount}}</div>
+        </div>
+      </div>
+      
+      <div class="summary-card flex1">
+        <div class="summary-title">Current Connector Status</div>
+        <div class="connector-status-layout" v-if="summaryData.connectorStatus">
+          <div>Available   <span>{{summaryData.connectorStatus.available}}</span></div>
+          <div>In use      <span>{{summaryData.connectorStatus.inUse}}</span></div>
+          <div>Faulted     <span>{{summaryData.connectorStatus.faulted}}</span></div>
+          <div>Reserved    <span>{{summaryData.connectorStatus.reserved}}</span></div>
+          <div>Finishing   <span>{{summaryData.connectorStatus.finishing}}</span></div>
+          <div>Unavailable <span>{{summaryData.connectorStatus.unavailable}}</span></div>
+        </div>
+      </div>
+      
+      <div class="summary-card flex1">
+        <div class="summary-title">Connector Utilization Rate Today</div>
+        <CircleChart
+          v-if="summaryData.connectorUtilization"
+          :chartData="summaryData.connectorUtilization"/>
+      </div>
+      
+      <div class="summary-view flexl flex2">
+        <div class="summary-view flexr flex1">
+          <div class="summary-card flex1">
+            <div class="summary-title">Today’s Revenue</div>
+            <div class="total-transaction-layout">
+              <div v-for="(item,index) in summaryData.todaySummary">
+                <div>{{item.country}}</div>
+                <span>{{item.todayRevenue}}</span>
+              </div>
+            </div>
+          </div>
+          <div class="summary-card flex1">
+            <div class="summary-title">Today’s Transactions</div>
+            <div class="total-transaction-layout">
+              <div v-for="(item,index) in summaryData.todaySummary">
+                <div>{{item.country}}</div>
+                <span>{{item.todayTransaction}}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="summary-view flexr flex1">
+          <div class="summary-card flex1">
+            <div class="summary-title">Total No. of Transactions</div>
+            <div class="summary-max-value">{{summaryData.totalTrans || "0"}}</div>
+          </div>
+          <div class="summary-card flex1">
+            <div class="summary-title">Ongoing Transactions</div>
+            <div class="summary-max-value">{{summaryData.ongoingTrans || "0"}}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <div class="charts-card">
+      <div class="chart-title">Hourly Utilization(kWH)</div>
+      <LineChart :chartData="hourlyData"></LineChart>
+    </div>
+    <div class="charts-card">
+      <div class="radio-group">
+        <el-radio-group
+          v-model="dateTabIndex"
+          size="small"
+          @change="changeTab">
+          <el-radio-button
+            v-for="(item,index) in dateRangeTab"
+            :key="index"
+            :label="item.value">
+            {{item.name}}
+          </el-radio-button>
+        </el-radio-group>
+      </div>
+      <el-row
+        class="barGroup"
+        v-loading="barLoading"
+        v-for="(bc, index) in barChartData"
+        :key="index">
+        <el-col :span="24">
+          <div class="bar-chart-country">
+            <div>Country:</div>
+            <span>{{bc.country}}</span>
+          </div>
+        </el-col>
+        <el-col
+          class="barView"
+          :sm="12"
+          :md="8"
+          v-if="bc.utilization">
+          <BarChart 
+            :index="index + '-0'"
+            :chartData="bc.utilization"
+            unit="%"
+            title="Utilization Rate so far"/>
+        </el-col>
+        <el-col
+          class="barView"
+          :sm="12"
+          :md="8"
+          v-if="bc.consumption">
+          <BarChart
+            :index="index + '-1'"
+            :chartData="bc.consumption"
+            unit="kWh"
+            title="Consumption so far"/>
+        </el-col>
+        <el-col
+          class="barView"
+          :sm="12"
+          :md="8"
+          v-if="bc.revenue">
+          <BarChart
+            :index="index + '-2'"
+            :chartData="bc.revenue"
+            unit="$"
+            :leftUnit="true"
+            title="Revenue so far"/>
+        </el-col>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script>
+import BarChart from '../chart/BarChart.vue'
+import LineChart from '../chart/LineChart.vue'
+import CircleChart from '../chart/CircleChart.vue'
+import api from '@/http/api/dashboard'
+export default {
+  name: "Summary",
+  props: {
+    provider: {
+      type: String|Number,
+      default: ""
+    }
+  },
+  components: {
+    BarChart, LineChart, CircleChart
+  },
+  data() {
+    return {
+      barLoading: true,
+      hourlyData: {
+        xdata: [],
+        ydata: []
+      },
+      summaryData: {},
+      dateTabIndex: 0,
+      dateRangeTab: [],
+      barChartData: [],
+      changeLoading: false
+    };
+  },
+  watch: {
+    provider(n, o) {
+      this.getData();
+    }
+  },
+  mounted() {
+    this.getData()
+  },
+  methods: {
+    getData() {
+      this.changeLoading = true
+      this.getSummaryData()
+      this.getRangeTabList()
+      this.getLineChart()
+    },
+    changeTab() {
+      setTimeout(() => {
+        if (this.changeLoading)
+          this.barLoading = true
+      }, 300)
+      this.changeLoading = true
+      this.getBarChartData()
+    },
+    getSummaryData() {
+      api.getSummaryInfo(this.provider).then(res => {
+        if (res.data) {
+          this.summaryData = res.data
+        }
+      }).catch(err => {
+        this.changeLoading = false
+        this.$message({
+          type: 'error',
+          message: err
+        })
+        this.summaryData = {}
+      })
+    },
+    getLineChart() {
+      api.getHourlyUtilizationV2(this.provider).then(res => {
+        if (res.data.xdata && res.data.ydata) {
+          this.hourlyData = res.data
+        } else {
+          this.hourlyData = undefined
+        }
+      }).catch(err => {
+        this.changeLoading = false
+        this.$message({
+          type: 'error',
+          message: err
+        })
+        this.hourlyData = undefined
+      })
+    },
+    getRangeTabList() {
+      api.getRangeTab().then(res => {
+        if (res.data) {
+          this.dateRangeTab = res.data
+          if (res.data.length > 0) {
+            this.dateTabIndex = res.data[0].value
+            this.getBarChartData()
+          }
+        } else {
+          this.dateRangeTab = [];
+        }
+      }).catch(err => {
+        this.changeLoading = false
+        this.$message({
+          type: 'error',
+          message: err
+        })
+        this.dateRangeTab = [];
+      })
+    },
+    getBarChartData() {
+      api.getBarChartV2({
+        tabType: this.dateTabIndex,
+        providerPk: this.provider
+      }).then(res => {
+        this.changeLoading = false
+        if (res.data) {
+          this.barChartData = res.data
+        } else {
+          this.barChartData = []
+        }
+      }).catch(err => {
+        this.changeLoading = false
+        this.$message({
+          type: 'error',
+          message: err
+        })
+        this.barChartData = []
+      }).finally(() => {
+        this.barLoading = false
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+  @import '../../../styles/element-ui.scss';
+  //主题色 $--color-primary
+  //强调色 $--color-accent
+  .summary-view {
+    margin: 5px -5px 0;
+  }
+  .summary-view > div {
+    margin: 5px;
+  }
+  .summary-view > div.summary-view {
+    margin: 0;
+  }
+  .summary-view > .summary-card {
+    margin: 5px;
+    min-width: 240px;
+  }
+  .summary-card {
+    padding: 10px 20px;
+    border-radius: 6px;
+    background-color: white;
+  }
+  .summary-title {
+    color: #333333;
+    font-size: 16px;
+    font-weight: bold;
+    text-align: center;
+  }
+  .summary-max-value {
+    color: $--color-accent;
+    font-size: 42px;
+    line-height: 63px;
+    text-align: center;
+    padding: 10px 0;
+  }
+  .connector-status-layout {
+    color: #333;
+    font-size: 16px;
+    line-height: 30px;
+    padding-top: 20px;
+    > div {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      span {
+        font-size: 20px;
+        font-weight: bold;
+      }
+    }
+  }
+  .total-transaction-layout {
+    color: #333;
+    font-size: 16px;
+    padding-top: 20px;
+    padding-bottom: 10px;
+    > div {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      span {
+        color: $--color-accent;
+      }
+    }
+  }
+  .bar-chart-country {
+    display: flex;
+    font-size: 18px;
+    align-items: center;
+    padding-bottom: 15px;
+    div {
+      font-weight: bold;
+      padding-right: 5px;
+    }
+  }
+  .charts-card {
+    display: flex;
+    flex-direction: column;
+    margin-top: 15px;
+    padding: 10px 20px;
+    border-radius: 6px;
+    background-color: white;
+  }
+  .chart-title {
+    color: #333;
+    font-size: 16px;
+    padding-top: 5px;
+    padding-bottom: 15px;
+  }
+  .radio-group {
+    padding-top: 5px;
+    text-align: center;
+  }
+  .barView {
+    padding-bottom: 10px;
+  }
+  @media screen and (max-width: 520px) {
+    .charts-card {
+      margin: 10px 0 0;
+    }
+  }
+  @media screen and (min-width: 768px) {
+    .barView + .barView {
+      border-left: 10px solid #F0F5FC;
+    }
+    .barView + .barView + .barView {
+      border-left: none;
+    }
+  }
+  @media screen and (min-width: 992px) {
+    .barView + .barView + .barView {
+      border-left: 10px solid #F0F5FC;
+    }
+  }
+  .barGroup {
+    margin-top: 15px;
+    padding-top: 18px;
+    border-top: 1px solid #F0F5FC;
+  }
+</style>

+ 138 - 0
Strides-Admin/src/views/dashboard/index.vue

@@ -0,0 +1,138 @@
+<template>
+  <div class="dashboard-container" v-loading="loading">
+    <div class="filter-view">
+      <el-select
+        class="sp-filter"
+        v-model="providerPk"
+        @change="changeProvider"
+        placeholder="Select Service Provider">
+        <el-option 
+          v-for="(item, index) in providerList"
+          :key="index"
+          :value="item.value"
+          :label="item.name"/>
+      </el-select>
+      <div class="radio-group">
+        <el-radio-group
+          v-model="filterTab"
+          @change="changeTab">
+          <el-radio-button
+            v-for="(item,index) in filterTabs"
+            :key="index"
+            :label="item.value">
+            {{item.name}}
+          </el-radio-button>
+        </el-radio-group>
+      </div>
+    </div>
+    <Summary :provider="providerPk" v-show="filterTab=='summary'"/>
+    <Site :provider="providerPk" v-show="filterTab=='site'"/>
+    <Stations :provider="providerPk" v-show="filterTab=='station'"/>
+    <Maps provider="all" v-show="filterTab=='maps'"/>
+  </div>
+</template>
+
+<script>
+import api from '../../http/api/dashboard'
+import Summary from './components/Summary'
+import Site from './components/Site'
+import Stations from './components/Stations'
+import Maps from './components/Maps'
+export default {
+  data() {
+    return {
+      loading: false,
+      providerPk: "",
+      providerList: [],
+      filterTab: "summary",
+      filterTabs: [{
+        name: "Summary",
+        value: "summary"
+      },{
+        name: "Site",
+        value: "site"
+      },{
+        name: "Stations",
+        value: "station"
+      },/*{
+        name: "Utilization",
+        value: "utilization"
+      },{
+        name: "Revenue",
+        value: "revenue"
+      },*/{
+        name: "Map",
+        value: "maps"
+      }]
+    }
+  },
+  components: {
+    Summary, Site, Stations, Maps
+  },
+  created() {
+    //this.loading = true;
+    this.getProvider();
+  },
+  methods: {
+    getProvider() {
+      api.getProviderList().then(res => {
+        if (res.data) {
+          this.providerList = res.data
+          if (res.data.length > 0) {
+            this.providerPk = res.data[0].value
+            //this.getLineChart()
+            //this.getCardData()
+            //this.getRangeTabList()
+          } else {
+            this.loading = false
+          }
+        }
+      }).catch(err => {
+        this.loading = false
+        this.$message({
+          type: 'error',
+          message: err
+        })
+      })
+    },
+    changeProvider() {
+      
+    },
+    changeTab() {
+      
+    }
+  }
+}
+</script>
+
+<style scoped lang='scss'>
+  @import '../../styles/variables.scss';
+  @import '../../styles/element-ui.scss';
+  .dashboard-container {
+    padding: 10px 20px;
+    min-height: $mainAppMinHeight;
+    background-color: #F0F5FC;
+    .sp-filter {
+      flex: 1;
+      min-width: 200px;
+      max-width: 320px;
+    }
+    
+    .radio-group {
+      padding-top: 0px;
+      user-select: none;
+      
+      ::v-deep  .el-radio-button {
+        padding: 5px;
+        min-width: 150px;
+        box-shadow: none !important;
+        
+        .el-radio-button__inner {
+          width: 100%;
+          border-radius: 4px;
+          border: 1px solid $--color-primary;
+        }
+      }
+    }
+  }
+</style>

+ 1 - 0
Strides-Admin/src/views/incident/connectivity.vue

@@ -181,6 +181,7 @@ export default {
 }
 .radio-group ::v-deep .el-radio-button {
   padding: 5px;
+  box-shadow: none !important;
   .el-radio-button__inner {
     border-radius: 4px;
     border: 1px solid $--color-primary;

+ 5 - 5
Strides-Admin/src/views/Administrator.vue → Strides-Admin/src/views/zetting/Administrator.vue

@@ -269,12 +269,12 @@
           <img
             class="list-item-icon"
             @click="subChargeType(index)"
-            src="../assets/form-list-sub.png"
+            src="@/assets/form-list-sub.png"
             v-if="settingsForm.chargeTypes.length > 1"/>
           <img
             class="list-item-icon"
             @click="addChargeType"
-            src="../assets/form-list-add.png"
+            src="@/assets/form-list-add.png"
             v-if="index == settingsForm.chargeTypes.length - 1"/>
         </div>
         <div class="hr-full"></div>
@@ -299,8 +299,8 @@
 </template>
 
 <script>
-  import api from '../http/api/settings'
-  import site from '../http/api/site'
+  import api from '@/http/api/settings'
+  import site from '@/http/api/site'
   export default {
     data() {
       return {
@@ -570,7 +570,7 @@
   }
 </script>
 <style lang='scss' scoped="scoped">
-  @import '../styles/variables.scss';
+  @import '../../styles/variables.scss';
   .card-container {
     width: 100%;
     padding: 20px 60px;