Explorar el Código

#13018 & #13270
Enhance Administrator modules

vbea hace 2 años
padre
commit
edbc072f02

+ 4 - 1
Strides-Admin/babel.config.js

@@ -8,7 +8,10 @@ module.exports = {
       // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
       // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
       // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
-      'plugins': ['dynamic-import-node']
+      'plugins': ['dynamic-import-node', 'syntax-dynamic-import']
+    },
+    'production': {
+      'plugins': ['syntax-dynamic-import']
     }
   }
 }

+ 4 - 2
Strides-Admin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "strides-admin",
-  "version": "0.0.1",
+  "version": "1.0.0",
   "description": "A Vue.js project",
   "author": "vbe",
   "private": true,
@@ -26,11 +26,12 @@
     "js-cookie": "^2.2.0",
     "nprogress": "^0.2.0",
     "path-to-regexp": "^6.2.0",
-	"particles.js": "^2.0.0",
+    "particles.js": "^2.0.0",
     "qrcodejs2": "^0.0.2",
     "screenfull": "^4.2.0",
     "script-loader": "^0.7.2",
     "vue": "^2.6.14",
+    "vue-avatar": "^2.3.3",
     "vue-router": "^3.5.1",
     "vuex": "^3.6.2"
   },
@@ -47,6 +48,7 @@
     "babel-core": "^6.24.1",
     "babel-loader": "^6.4.0",
     "babel-plugin-dynamic-import-node": "^2.3.3",
+    "babel-plugin-syntax-dynamic-import": "^6.18.0",
     "chalk": "^4.1.1",
     "connect": "^3.7.0",
     "css-loader": "^0.27.0",

+ 3 - 1
Strides-Admin/src/http/api/settings.js

@@ -17,8 +17,10 @@ const settings = {
     return get('base/getCountries')
   },
   deleteChargeType: (id) => {
-    return get('administratorSettings/delChargeType', {chargeTypePk: id})
+    return get('chargeType/delChargeType', {chargeTypePk: id})
   },
+  getAllChargeTypes: () => get('chargeType/getAllChargeTypes'),
+  saveChargeTypes: (data) => post('chargeType/saveChargeType', data)
 }
 
 export default settings

+ 34 - 8
Strides-Admin/src/layout/components/Navbar.vue

@@ -15,7 +15,6 @@
       v-show="device !== 'mobile'">
       <el-dropdown
         @command="(v) => handleCommand(v)"
-        placement="top-start"
         trigger="hover">
         <div class="right-menu-item">
           <i class="el-icon-user-solid account"></i>
@@ -27,10 +26,16 @@
           slot="dropdown"
           class="account-dropdown">
           <div class="account-dropdown-item">
+            <avatar
+              :size="45"
+              :lighten="100"
+              :username="username"
+              color="rgba(255,255,255,.9)"
+              v-if="colorfulAvatar"/>
             <img
               class="avatar"
               src="../../assets/avatar-default.png"
-            />
+              v-else/>
             <div class="flexl">
               <div class="flexc">
                 <span class="username">{{username}}</span>
@@ -68,7 +73,17 @@
         @command="(v) => handleCommand(v)"
         trigger="click">
         <div class="avatar-wrapper">
-          <i class="el-icon-menu"/>
+          <!-- <i class="el-icon-menu"/> -->
+          <avatar
+            :size="42"
+            :lighten="100"
+            :username="username"
+            color="rgba(255,255,255,.9)"
+            v-if="colorfulAvatar"/>
+          <img
+            class="avatar"
+            src="../../assets/avatar-default.png"
+            v-else/>
         </div>
         <el-dropdown-menu
           slot="dropdown"
@@ -92,6 +107,7 @@
 </template>
 
 <script>
+import Avatar from 'vue-avatar'
 import { mapGetters } from 'vuex'
 import Breadcrumb from '@/components/Breadcrumb'
 import Hamburger from '@/components/Hamburger'
@@ -99,6 +115,7 @@ import {getUserName, getEmail, getRoleName} from '../../utils/auth.js'
 
 export default {
   components: {
+    Avatar,
     Breadcrumb,
     Hamburger
   },
@@ -113,7 +130,8 @@ export default {
     return {
       username: 'Admin',
       email: '',
-      role: ''
+      role: '',
+      colorfulAvatar: false
     }
   },
   created() {
@@ -157,6 +175,7 @@ export default {
 
 <style lang="scss" scoped>
 @import '../../styles/variables.scss';
+@import '../../styles/element-ui.scss';
 .navbar {
   display: flex;
   height: $navigationBarHeight;
@@ -190,7 +209,7 @@ export default {
   }
   
   .avatar-container {
-    padding-left: 10px;
+    margin-left: 10px;
     .el-icon-menu {
       color: #555;
       font-size: 22px;
@@ -244,6 +263,9 @@ export default {
       .bold {
         font-weight: bold;
       }
+      &:hover {
+        color: #555;
+      }
     }
   }
 }
@@ -261,7 +283,7 @@ export default {
     text-transform: capitalize;
   }
   .user-dropdown {
-    padding: 0 !important;
+    padding: 5px 0px !important;
   }
   .account-dropdown {
     padding: 0 !important;
@@ -278,8 +300,12 @@ export default {
     font-weight: normal;
   }
   .avatar {
-    width: 50px;
-    height: 50px;
+    width: 45px;
+    height: 45px;
+  }
+  .avatar-wrapper .avatar {
+    width: 42px;
+    height: 42px;
   }
   .username {
     padding-left: 10px;

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

@@ -25,7 +25,7 @@ export default {
     },
     {
       path: '/system-settings/charge-type-configuration',
-      component: () => import('@/views/dashboard/Dashboard'),
+      component: () => import('@/views/zetting/ChargeType'),
       name: 'system-charge-type-configuration',
       meta: {
         title: 'Charge Type Configuration',

+ 2 - 5
Strides-Admin/src/router/permission.js

@@ -43,7 +43,7 @@ router.beforeEach(async(to, from, next) => {
     //console.log("权限通过", validPath);
     if (to.path === '/login') {
       // if is logged in, redirect to the home page
-      next({ path: '/' })
+      next({ path: '/redirect' })
       NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
     } else if (!validPath && whiteList.indexOf(to.path) == -1) {
       next({ path: '/404' })
@@ -79,15 +79,12 @@ router.beforeEach(async(to, from, next) => {
       }*/
     }
   } else {
-    //console.log("权限未通过", to.path);
+    //console.log("权限未通过", to.path, hasToken);
     /* has no token*/
     if (whiteList.indexOf(to.path) !== -1) {
       // in the free login whitelist, go directly
       next()
       NProgress.done()
-    } else if (!validPath) {
-      next({ path: hasToken ? '/login' : '/404' })
-      NProgress.done()
     } else {
       // other pages that do not have permission to access are redirected to the login page.
       next(`/login?redirect=${to.path}`)

+ 4 - 3
Strides-Admin/src/styles/index.scss

@@ -23,7 +23,7 @@ label {
   font-weight: 700;
 }
 
-::-webkit-scrollbar-track-piece { 
+::-webkit-scrollbar-track-piece {
   //滚动条凹槽的颜色,还可以设置边框属性
   background-color:#f8f8f8; 
 }
@@ -34,12 +34,13 @@ label {
 }
 ::-webkit-scrollbar-thumb {
   //滚动条的设置
+  transition: all .4s;
   border-radius: 30px;
-  background-color: #ccc;
+  background-color: rgba($--color-primary, 0.3);
   background-clip: padding-box;
 }
 ::-webkit-scrollbar-thumb:hover {
-  background-color: rgba($--color-primary, 0.5);
+  background-color: rgba($--color-primary, 0.6);
 }
 
 #app {

+ 15 - 5
Strides-Admin/src/views/dashboard/chart/BarChart.vue

@@ -37,7 +37,8 @@
     },
     data() {
       return {
-        chart: null
+        chart: null,
+        resize: 0
       }
     },
     watch: {
@@ -52,15 +53,24 @@
       this.$nextTick(() => {
         this.initChart()
       })
-      const _this = this;
       const erd = elementResizeDetectorMaker()
       erd.listenTo(document.getElementById('barChart'+this.index), element => {
-        _this.$nextTick(() => {
-          _this.chart.resize()
-        })
+        const s = this.resize + 1;
+        this.resize = s;
+        setTimeout(() => {
+          this.resizeChart(s);
+        }, 50 + this.index);
       })
     },
     methods: {
+      resizeChart(p) {
+        if (p == this.resize) {
+          if (this.chart) {
+            this.resize = 0;
+            this.chart.resize()
+          }
+        }
+      },
       initChart() {
         this.chart = echarts.init(document.getElementById('barChart'+this.index), 'macarons')
         this.setOptions()

+ 16 - 6
Strides-Admin/src/views/dashboard/chart/CircleChart.vue

@@ -21,6 +21,7 @@ export default {
   data() {
     return {
       chart: null,
+      resize: 0
     };
   },
   watch: {
@@ -37,15 +38,24 @@ export default {
     this.$nextTick(() => {
       this.initChart()
     })
-    const _this = this;
     const erd = elementResizeDetectorMaker()
-    erd.listenTo(document.getElementById('circleChart'), (element) => {
-      _this.$nextTick(() => {
-        _this.chart.resize();
-      })
-    });
+    erd.listenTo(document.getElementById('circleChart'), element => {
+      const s = this.resize + 1;
+      this.resize = s;
+      setTimeout(() => {
+        this.resizeChart(s);
+      }, 50);
+    })
   },
   methods: {
+    resizeChart(p) {
+      if (p == this.resize) {
+        if (this.chart) {
+          this.resize = 0;
+          this.chart.resize()
+        }
+      }
+    },
     initChart() {
       console.log(echarts);
       this.chart = echarts.init(document.getElementById('circleChart'), 'macarons')

+ 15 - 5
Strides-Admin/src/views/dashboard/chart/LineChart.vue

@@ -1,5 +1,5 @@
 <template>
-  <div id="lineChart" />
+  <div id="lineChart" ref="chartBox"/>
 </template>
 
 <script>
@@ -24,6 +24,7 @@ export default {
         xData: ['01:00', '02:00', '03:00', '04:00', '05:00', '06:00', '07:00', '08:00', '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '18:00', '19:00', '20:00', '21:00', '22:00', '23:00', '0:00'],
         yData: [120, 100, 191, 120, 150, 120, 100, 99, 85, 75, 65, 30, 20, 12, 100, 9, 8, 37, 6, 100, 20, 66, 20, 80]
       }*/
+      resize: 0
     }
   },
   watch: {
@@ -38,15 +39,24 @@ export default {
     this.$nextTick(() => {
       this.initChart()
     })
-    const _this = this
     const erd = elementResizeDetectorMaker()
     erd.listenTo(document.getElementById('lineChart'), element => {
-      _this.$nextTick(() => {
-        _this.chart.resize()
-      })
+      const s = this.resize + 1;
+      this.resize = s;
+      setTimeout(() => {
+        this.resizeChart(s);
+      }, 50);
     })
   },
   methods: {
+    resizeChart(p) {
+      if (p == this.resize) {
+        if (this.chart) {
+          this.resize = 0;
+          this.chart.resize()
+        }
+      }
+    },
     initChart() {
       this.chart = echarts.init(document.getElementById('lineChart'), 'macarons')
       this.setOptions()

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

@@ -1,5 +1,5 @@
 <template>
-  <div v-loading="changeLoading">
+  <div style="margin-top: 10px;" v-loading="changeLoading">
     <div class="card-group">
       <div class="card-item-view">
         <div class="card-title">Total Sites</div>

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

@@ -1,5 +1,5 @@
 <template>
-  <div v-loading="changeLoading">
+  <div style="margin-top: 10px;" v-loading="changeLoading">
     <div class="card-group">
       <div class="card-item-view">
         <div class="card-title">Available</div>

+ 21 - 5
Strides-Admin/src/views/dashboard/components/Summary.vue

@@ -35,21 +35,23 @@
         <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 class="total-transaction-layout" v-if="summaryData.todaySummary && summaryData.todaySummary.length">
               <div v-for="(item,index) in summaryData.todaySummary">
                 <div>{{item.country}}</div>
                 <span>{{item.todayRevenue}}</span>
               </div>
             </div>
+            <div class="no-data" v-else>No Data</div>
           </div>
           <div class="summary-card flex1">
             <div class="summary-title">Today’s Transactions</div>
-            <div class="total-transaction-layout">
+            <div class="total-transaction-layout"  v-if="summaryData.todaySummary && summaryData.todaySummary.length">
               <div v-for="(item,index) in summaryData.todaySummary">
                 <div>{{item.country}}</div>
                 <span>{{item.todayTransaction}}</span>
               </div>
             </div>
+            <div class="no-data" v-else>No Data</div>
           </div>
         </div>
         <div class="summary-view flexr flex1">
@@ -156,7 +158,13 @@ export default {
         xdata: [],
         ydata: []
       },
-      summaryData: {},
+      summaryData: {
+        boxCount: 0,
+        siteCount: 0,
+        todaySummary: [],
+        connectorStatus: {},
+        connectorUtilization: {}
+      },
       dateTabIndex: 0,
       dateRangeTab: [],
       barChartData: [],
@@ -267,7 +275,7 @@ export default {
   //主题色 $--color-primary
   //强调色 $--color-accent
   .summary-view {
-    margin: 5px -5px 0;
+    margin: 5px 0px 0;
   }
   .summary-view > div {
     margin: 5px;
@@ -326,6 +334,13 @@ export default {
       }
     }
   }
+  .no-data {
+    color: #999;
+    font-size: 14px;
+    padding: 30px 0 20px;
+    text-align: center;
+    user-select: none;
+  }
   .bar-chart-country {
     display: flex;
     font-size: 18px;
@@ -338,8 +353,9 @@ export default {
   }
   .charts-card {
     display: flex;
+    overflow: hidden;
     flex-direction: column;
-    margin-top: 15px;
+    margin: 15px 5px 0;
     padding: 10px 20px;
     border-radius: 6px;
     background-color: white;

+ 13 - 3
Strides-Admin/src/views/dashboard/index.vue

@@ -26,9 +26,9 @@
       </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'"/>
+    <Site :provider="providerPk" v-if="filterTab=='site'"/>
+    <Stations :provider="providerPk" v-if="filterTab=='station'"/>
+    <Maps provider="all" v-if="filterTab=='maps'"/>
   </div>
 </template>
 
@@ -136,4 +136,14 @@ export default {
       }
     }
   }
+  @media screen and (max-width: 768px) {
+    .dashboard-container {
+      padding: 10px;
+    }
+  }
+  @media screen and (max-width: 520px) {
+    .dashboard-container {
+      padding: 10px 5px;
+    }
+  }
 </style>

+ 1 - 1
Strides-Admin/src/views/redirect/404.vue

@@ -43,7 +43,7 @@ export default {
   methods: {
     backHome() {
       this.$router.replace({
-        path: "/"
+        path: "/redirect"
       })
     }
   }

+ 94 - 1
Strides-Admin/src/views/site/detail.vue

@@ -158,13 +158,58 @@
       
       <div class="view-content">
         <div class="section-title">TIME CONFIGURATION</div>
-        <div style="overflow-x: auto;">
+        <div class="site-type-layout">
           <site-type-with-time
             :siteTypes="siteForm.siteTypes"
             @change="changeSiteTypes"/>
         </div>
       </div>
       
+      <div class="view-content">
+        <div class="section-title">OCPP SETTINGS</div>
+        <div class="flexr">
+          <div class="flex2">
+            <el-form-item
+              label-width="150px"
+              label="Heartbeat Interval:"
+              prop="heartbeat"
+              class="flex1">
+              <div class="flexc">
+                <el-input
+                  class="value-text"
+                  v-model="siteForm.ocppSetting.heartbeatIntervalMinutes"
+                  placeholder="Add text"
+                  maxlength="5"/>
+                <span class="value-unit">Minutes</span>
+              </div>
+            </el-form-item>
+            <div class="tips">The time interval in <i>minutes</i> for how often a charge point should request the current time from the CSMS.</div>
+          </div>
+          <div class="flex1">
+            <p></p>
+          </div>
+          <div class="flex2">
+            <el-form-item
+              label="Expiration:"
+              prop="expiration"
+              label-width="95px"
+              class="flex1">
+              <div class="flexc">
+                <el-input
+                  class="value-text"
+                  v-model="siteForm.ocppSetting.expireHours"
+                  placeholder="Add text"
+                  maxlength="5"/>
+                <span class="value-unit">Hours</span>
+              </div>
+            </el-form-item>
+            <div class="tips" style="padding-left: 95px;">The amount of time in <i>hours</i> for how long a charge point should cache the authorization info of an idTag in its local white list, if an expiry date is not explicitly set. 
+            The value 0 disables this functionality 
+              (i.e.no expiry date will be set).</div>
+          </div>
+        </div>
+      </div>
+      
       <div class="view-content" id="idChargeRates">
         <div class="section-title flexcr">
           CHARGE SITE RATE
@@ -345,6 +390,11 @@ export default {
           street: "",
           zipCode: ""
         },
+        ocppSetting: {
+          expireHours: '',
+          ocppSettingId: '',
+          heartbeatIntervalMinutes: ''
+        },
         free: false,
         endlessService: false,//24/7
         additionalNotes: "",
@@ -832,6 +882,28 @@ export default {
     font-weight: normal;
   }
   
+  .site-type-layout {
+    overflow-x: auto;
+  }
+  
+  .site-type-layout::-webkit-scrollbar-track-piece {
+    background-color:#f8f8f8; 
+  }
+  
+  .site-type-layout::-webkit-scrollbar {
+    width: 7px;
+    height: 10px;
+  }
+  .site-type-layout::-webkit-scrollbar-thumb {
+    transition: all .4s;
+    border-radius: 30px;
+    background-color: rgba($--color-primary, 0.3);
+    background-clip: padding-box;
+  }
+  .site-type-layout::-webkit-scrollbar-thumb:hover {
+    background-color: rgba($--color-primary, 0.6);
+  }
+  
   .input-text {
     width: 100% !important;
     max-width: 300px;
@@ -848,6 +920,16 @@ export default {
     font-family: sans-serif;
   }
   
+  .value-text {
+    width: 100%;
+    min-width: 100px;
+    max-width: 200px;
+  }
+  
+  .value-unit {
+    padding-left: 10px;
+  }
+  
   .cancel-button {
     width: 94px;
     height: 40px;
@@ -871,4 +953,15 @@ export default {
     font-size: 13px;
     text-transform: none;
   }
+  .tips {
+    color: #999;
+    font-size: 12px;
+    padding-left: 150px;
+    padding-bottom: 20px;
+  }
+  .tips i {
+    color: #333;
+    font-weight: 500;
+    font-style: normal;
+  }
 </style>

+ 37 - 127
Strides-Admin/src/views/zetting/Administrator.vue

@@ -1,15 +1,15 @@
 <template>
   <div class="card-container">
-    <div class="card-content">
-      <el-form
-        :model="settingsForm"
-        :rules="rule"
-        v-loading="loading"
-        ref="settingsForm"
-        label-position="left"
-        label-width="150px"
-        style="width: 100%;">
-        <div class="section-title">OCPP Settings</div>
+    <el-form
+      :model="settingsForm"
+      :rules="rule"
+      v-loading="loading"
+      ref="settingsForm"
+      label-position="left"
+      label-width="150px"
+      style="width: 100%;">
+      <div class="card-content">
+        <!-- <div class="section-title">OCPP Settings</div>
         <el-row :gutter="20">
           <el-col :xs="24" :sm="16" :md="12" :lg="9">
             <div class="flexc">
@@ -50,7 +50,7 @@
               (i.e.no expiry date will be set).</div>
           </el-col>
         </el-row>
-        <div class="hr"></div>
+        <div class="hr"></div> -->
         <div class="section-title">
           <span style="padding-right: 30px;">Mail Notification Settings</span>
           <el-checkbox v-model="settingsForm.enabled">Enable notifications</el-checkbox>
@@ -244,46 +244,8 @@
             </el-form-item>
           </el-col>
         </el-row>
-        <div class="hr"></div>
-        <div class="section-title">Charge Type Settings</div>
-        <div class="rate-list-view" v-for="(item, index) in settingsForm.chargeTypes" :key="index">
-          <el-form-item
-            label="Type:"
-            label-width="50px"
-            :prop="'chargeTypes.'+index+'.chargeType'"
-            :rules="rule.chargeTypes.chargeType">
-            <el-select
-              v-model="item.chargeType"
-              class="rate-text">
-              <el-option
-                v-for="i in chargeTypeOptions"
-                :key="i"
-                :label="i"
-                :value="i" />
-            </el-select>
-          </el-form-item>
-          <el-form-item
-            label="Wattage:"
-            label-width="75px"
-            :prop="'chargeTypes.'+index+'.wattage'"
-            :rules="rule.chargeTypes.wattage">
-            <el-input
-              v-model="item.wattage"
-              class="rate-text"
-               maxlength="5"/>
-          </el-form-item>
-          <img
-            class="list-item-icon"
-            @click="subChargeType(index)"
-            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"
-            v-if="index == settingsForm.chargeTypes.length - 1"/>
-        </div>
-        <div class="hr-full"></div>
+      </div>
+      <div class="card-content">
         <div class="buttons">
           <el-button
             type="primary"
@@ -298,8 +260,8 @@
             Update Changes
           </el-button>
         </div>
-      </el-form>
-    </div>
+      </div>
+    </el-form>
   </div>
 </template>
 
@@ -313,14 +275,9 @@
         settingsForm: {
           enabled: false,
           recipients: [],
-          chargeTypes: [],
+          //chargeTypes: [],
           enabledFeatures: []
         },
-        chargeType: {
-          chargeType: '',
-          chargeTypePk: '',
-          wattage: ''
-        },
         rule: {
           "heartbeat": {
             required: true,
@@ -366,22 +323,6 @@
             pattern: /^[a-zA-Z0-9]+[\S]+@[a-zA-Z0-9_-]+[\.][\Sa-zA-Z]+$/,
             trigger: 'blur',
             message: 'Please type a correct recipient'
-          },
-          chargeTypes: {
-            chargeType: {
-              required: true,
-              trigger: 'change',
-              message: 'Charge Type is required',
-            },
-            wattage: [{
-              required: true,
-              trigger: 'change',
-              message: 'Wattage is required',
-            }, {
-              pattern: /^[1-9]+\d*.?\d*$/,
-              trigger: 'blur',
-              message: 'Please type a correct wattage'
-            }]
           }
         },
         recipient: '',
@@ -396,13 +337,13 @@
           "protocol": "",
           "port": '',
           "recipients": [],
-          chargeTypes: [],
+          //chargeTypes: [],
           "enabledFeatures": []
         },
         features: [],
         featuresGroup: 0,
         manualValid: false,
-        chargeTypeOptions: [],
+        //chargeTypeOptions: [],
         passwordType: "password"
       }
     },
@@ -417,7 +358,7 @@
           this.features = res.data;
           this.featuresGroup = res.data.length / 2
         }
-        this.getChargeTypeOptions()
+        //this.getChargeTypeOptions()
         this.getSettings();
       },
       async getSettings() {
@@ -425,21 +366,7 @@
         if (res.data) {
           this.settingsForm = res.data;
         }
-      },
-      getChargeTypeOptions() {
-        site.getTypeList().then(res => {
-          if (res.data) {
-            this.chargeTypeOptions = res.data
-            this.chargeType.chargeType = res.data[0]
-            if (!this.settingsForm.chargeTypes) {
-              this.settingsForm.chargeTypes = []
-              this.addChargeType()
-            }
-          }
-          this.loading = false;
-        }).catch(err => {
-          this.loading = false;
-        });
+        this.loading = false;
       },
       init() {
         this.recipient = "";
@@ -518,35 +445,6 @@
           }
         });
       },
-      deleteChargeType(id, back) {
-        api.deleteChargeType(id).then(res => {
-          back()
-        }).catch(err => {
-          this.$message({
-            message: err,
-            type: 'error'
-          })
-        })
-      },
-      subChargeType(index) {
-        var chargeType = this.settingsForm.chargeTypes[index]
-        if (chargeType.chargeTypePk) {
-          this.$confirm('Confirm delete this charge type?', 'Delete', {
-            confirmButtonText: 'Confirm',
-            cancelButtonText: 'Cancel',
-            type: 'warning'
-          }).then(res => {
-            this.deleteChargeType(chargeType.chargeTypePk, () => {
-              this.settingsForm.chargeTypes.splice(index, 1)
-            })
-          });
-        } else {
-          this.settingsForm.chargeTypes.splice(index, 1)
-        }
-      },
-      addChargeType() {
-        this.settingsForm.chargeTypes.push(JSON.parse(JSON.stringify(this.chargeType)))
-      },
       handleUpateButton() {
         this.$refs['settingsForm'].validate(result => {
           if (this.settingsForm.recipients.length == 0) {
@@ -598,7 +496,8 @@
 <style scoped="scoped">
   .card-content {
     min-width: 500px;
-    padding: 15px 80px;
+    margin: 0 8px 16px;
+    padding: 15px 50px;
     border-radius: 6px;
     background-color: white;
   }
@@ -606,10 +505,10 @@
     color: #333;
     margin-top: 20px;
     margin-bottom: 30px;
-    font-size: 16px;
+    font-size: 14px;
     user-select: none;
     line-height: 24px;
-    font-weight: 500;
+    font-weight: 700;
     font-family: sans-serif;
     /* text-transform: uppercase; */
   }
@@ -655,7 +554,7 @@
   }
   .buttons {
     padding-top: 15px;
-    padding-bottom: 30px;
+    padding-bottom: 15px;
   }
   .tips {
     color: #999;
@@ -704,12 +603,23 @@
   .link-button:hover {
     color: #ff5500;
   }
+  
+  @media screen and (max-width: 800px) {
+    .card-container {
+      padding: 10px 20px;
+    }
+    .card-content {
+      min-width: auto;
+      padding: 15px 30px;
+    }
+  }
+  
   @media screen and (max-width: 500px) {
     .card-container {
       padding: 0px;
     }
     .card-content {
-      padding: 15px 40px;
+      padding: 15px 20px;
     }
   }
   .el-checkbox-group .el-checkbox {

+ 354 - 0
Strides-Admin/src/views/zetting/ChargeType.vue

@@ -0,0 +1,354 @@
+<template>
+  <div class="view-container" v-loading="loading">
+    <el-form
+      ref="addForm"
+      :rules="rules"
+      :model="settingsForm"
+      label-position="right"
+      label-width="80px"
+      style="width: 100%;">
+      <div class="flexr">
+        <div class="view-content flex1">
+          <div class="section-title">CHARGER TYPE SETTINGS</div>
+          <div class="rate-list-view" v-for="(item, index) in settingsForm.chargeTypes" :key="index">
+            <el-form-item
+              label="Type:"
+              :prop="'chargeTypes.'+index+'.chargeType'"
+              :rules="rules.chargeTypes.chargeType">
+              <el-select
+                v-model="item.chargeType"
+                class="rate-text">
+                <el-option
+                  v-for="i in chargeTypeOptions"
+                  :key="i"
+                  :label="i"
+                  :value="i" />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              label="Wattage:"
+              :prop="'chargeTypes.'+index+'.wattage'"
+              :rules="rules.chargeTypes.wattage">
+              <el-input
+                v-model="item.wattage"
+                class="rate-text"
+                maxlength="5"/>
+            </el-form-item>
+            <img
+              class="list-item-icon"
+              @click="subChargeType(index)"
+              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"
+              v-if="index == settingsForm.chargeTypes.length - 1"/>
+          </div>
+        </div>
+        <div class="view-content flex1">
+          <div class="section-title">CHARGER TYPE SUMMARIES</div>
+          <div style="width: 100%; overflow-x: auto;">
+            <el-table
+              class="table-types no-border"
+              :data="settingsForm.summaries"
+              :fit="true">
+              <el-table-column
+                label="Type"
+                prop="chargeType"
+                align="center"/>
+              <el-table-column
+                label="Wattage"
+                prop="wattage"
+                align="center"/>
+              <el-table-column
+                label="Number of Connectors"
+                prop="countConnector"
+                align="center"
+                width="200"/>
+            </el-table>
+          </div>
+        </div>
+      </div>
+      <div class="view-content flexcr">
+        <div class="buttons">
+          <el-button
+            class="cancel-button"
+            type="primary"
+            @click="handleClickCancleButton">
+              Cancel
+          </el-button>
+          <el-button
+            class="confirm-button"
+            type="primary"
+            @click="handleClickSaveButton">
+              Save
+          </el-button>
+        </div>
+        <div
+          class="update-by"
+          v-if="isEdit">
+          <span
+            class="add-text"
+            :title='"CREATED BY " + settingsForm.createdBy + " ON " + settingsForm.createdOn'>
+            LAST UPDATED BY {{settingsForm.updatedBy}} TIMESTAMP: {{settingsForm.updatedOn}}
+          </span>
+        </div>
+      </div>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import api from '@/http/api/settings'
+import site from '@/http/api/site'
+export default {
+  data() {
+    return {
+      isEdit: false,
+      loading: true,
+      settingsForm: {
+        chargeTypes: "",
+        summaryData: []
+      },
+      chargeType: {
+        chargeType: '',
+        chargeTypePk: '',
+        wattage: ''
+      },
+      rules: {
+        chargeTypes: {
+          chargeType: {
+            required: true,
+            trigger: 'change',
+            message: 'Charge Type is required',
+          },
+          wattage: [{
+            required: true,
+            trigger: 'change',
+            message: 'Wattage is required',
+          }, {
+            pattern: /^[1-9]+\d*.?\d*$/,
+            trigger: 'blur',
+            message: 'Please type a correct wattage'
+          }]
+        }
+      },
+      chargeTypeOptions: []
+    }
+  },
+  created() {
+    this.getChargeTypeOptions();
+  },
+  methods: {
+    getChargeTypeOptions() {
+      site.getTypeList().then(res => {
+        if (res.data) {
+          this.chargeTypeOptions = res.data
+          this.chargeType.chargeType = res.data[0]
+          if (!this.settingsForm.chargeTypes) {
+            this.settingsForm.chargeTypes = []
+            this.addChargeType()
+          }
+        }
+        this.getChargeTypeList();
+      }).catch(err => {
+        this.loading = false;
+        this.$message({
+          message: error,
+          type: "error",
+          duration: 5000,
+        })
+      });
+    },
+    getChargeTypeList() {
+      api.getAllChargeTypes().then(res => {
+        if (res.data) {
+          this.settingsForm = res.data
+        }
+      }).catch(err => {
+        this.$message({
+          message: error,
+          type: "error",
+          duration: 5000,
+        })
+      }).finally(() => {
+        this.loading = false;
+      })
+    },
+    subChargeType(index) {
+      var chargeType = this.settingsForm.chargeTypes[index]
+      if (chargeType.chargeTypePk) {
+        this.$confirm('Confirm delete this charge type?', 'Delete', {
+          confirmButtonText: 'Confirm',
+          cancelButtonText: 'Cancel',
+          type: 'warning'
+        }).then(res => {
+          this.deleteChargeType(chargeType.chargeTypePk, () => {
+            this.settingsForm.chargeTypes.splice(index, 1)
+          })
+        });
+      } else {
+        this.settingsForm.chargeTypes.splice(index, 1)
+      }
+    },
+    addChargeType() {
+      this.settingsForm.chargeTypes.push(JSON.parse(JSON.stringify(this.chargeType)))
+    },
+    deleteChargeType(id, back) {
+      api.deleteChargeType(id).then(res => {
+        back()
+      }).catch(err => {
+        this.$message({
+          message: err,
+          type: 'error'
+        })
+      })
+    },
+    handleClickCancleButton() {
+      this.$router.go(-1);
+    },
+    handleClickSaveButton() {
+      this.$refs['addForm'].validate(result => {
+        if (result) {
+          this.loading = true;
+          api.saveChargeTypes(this.settingsForm).then(res => {
+            this.$message({
+              message: 'Save charge types successfully',
+              type: 'success'
+            });
+          }).catch(err => {
+            this.$message({
+              message: err,
+              type: 'error'
+            })
+          }).finally(() => {
+            this.loading = false;
+          })
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  @import '../../styles/element-ui.scss';
+  @import '../../styles/variables.scss';
+  
+  .view-container {
+    width: 100%;
+    padding: 20px 60px;
+    min-height: $mainAppMinHeight;
+    background-color: #F0F5FC;
+  }
+  
+  .view-content {
+    min-width: 35vw;
+    margin: 0 8px 16px;
+    padding: 15px 50px;
+    border-radius: 6px;
+    background-color: white;
+  }
+  
+  .section-title {
+    color: #333333;
+    margin-top: 20px;
+    margin-bottom: 30px;
+    font-size: 15px;
+    line-height: 24px;
+    font-weight: 700;
+    font-family: sans-serif;
+    text-transform: uppercase;
+  }
+  
+  .section-sub-title {
+    font-size: 14px;
+    padding-left: 5px;
+    font-weight: normal;
+  }
+  
+  .input-text {
+    width: 100% !important;
+    max-width: 300px;
+  }
+  
+  .add-text {
+    width: 100%;
+    font-size: 14px;
+    max-width: 300px;
+  }
+  
+  .add-text ::v-deep .el-textarea__inner,
+  .input-text ::v-deep .el-textarea__inner {
+    font-family: sans-serif;
+  }
+  
+  .rate-list-view {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+  }
+  .rate-text {
+    max-width: 100px;
+    padding-right: 14px;
+  }
+  .list-item-icon {
+    width: 30px;
+    height: 30px;
+    cursor: pointer;
+    margin: 0 10px 22px;
+  }
+  
+  .buttons {
+    padding-top: 15px;
+    padding-bottom: 15px;
+  }
+  
+  .cancel-button {
+    width: 94px;
+    height: 40px;
+  }
+  
+  .confirm-button {
+    width: 94px;
+    height: 40px;
+    margin-left: 20px;
+    background: $--color-primary;
+    border: 1px solid $--color-primary;
+    box-sizing: border-box;
+    border-radius: 4px;
+  }
+  
+  @media screen and (max-width: 1000px) {
+    .view-container {
+      padding: 10px 40px;
+    }
+    .view-content {
+      min-width: 90vw;
+      padding: 15px 40px;
+    }
+  }
+  
+  @media screen and (max-width: 800px) {
+    .view-container {
+      padding: 10px 20px;
+    }
+    .view-content {
+      padding: 15px 30px;
+    }
+  }
+  
+  @media screen and (max-width: 500px) {
+    .view-container {
+      padding: 0px;
+    }
+    .view-content {
+      padding: 15px 20px;
+    }
+  }
+  .table-types {
+    width: 100%;
+    min-width: 500px;
+  }
+</style>