Răsfoiți Sursa

运营看板体验版提交

zuy 2 ani în urmă
părinte
comite
9e1447d2b6
4 a modificat fișierele cu 438 adăugiri și 78 ștergeri
  1. 44 12
      src/assets/utils.js
  2. 1 1
      src/pages.json
  3. 391 65
      src/pages/index/index.vue
  4. 2 0
      src/pages/login/index.vue

+ 44 - 12
src/assets/utils.js

@@ -1,22 +1,18 @@
-const env = process.env.NODE_ENV==="development"?"dev":"prd";
+const env = process.env.NODE_ENV === "development" ? "dev" : "prd";
 let apis = {
     dev: {
-        serverUrl: "http://localhost:8080/admin/",
-        fileUrl: "https://zyp-1258963180.cos.ap-guangzhou.myqcloud.com/"
-    },
-    uat: {
-        serverUrl: "http://npt.free.idcfengye.com/",
+        serverUrl: "https://dev-cloud.kuaiyuman.cn/admin/",
         fileUrl: "https://zyp-1258963180.cos.ap-guangzhou.myqcloud.com/"
     },
     prd: {
-        serverUrl: "https://npww.net.cn/cms/",
+        serverUrl: "https://cloud.kuaiyuman.cn/admin/",
         fileUrl: "https://zyp-1258963180.cos.ap-guangzhou.myqcloud.com/",
     },
 };
 const cfg = {
     key: {
         token: 'kuaiyuman.token',
-        user:'kuaiyuman.user',
+        user: 'kuaiyuman.user',
     },
     env: env,
     api: {
@@ -89,7 +85,7 @@ const request = (options) => {
             header: options.header,
             dataType: options.dataType
         }).then(res => {
-            console.log("resp>>>",res)
+            console.log("resp>>>", res)
             let response = res.data;
             if (response.code !== 200) {
                 if (response.code == 10001) {
@@ -99,7 +95,7 @@ const request = (options) => {
                     });
                     setTimeout(() => {
                         uni.navigateTo({
-                            url: `/pages/login/login`
+                            url: `/pages/login/index`
                         })
                     }, 300)
 
@@ -162,7 +158,7 @@ const fillUrl = function (url) {
     }
 };
 
-const formatUrl = v => {
+const fmtUrl = v => {
     if (v == null || v == "") {
         return "/static/missing-face.png";
     }
@@ -172,6 +168,42 @@ const formatUrl = v => {
     return fileUrl + v.replace(/\\/g, "/");
 };
 
+const fmtMoney = function (money) {
+    if (!money) {
+        return "0.00";
+    }
+    return (money / 100).toFixed(2);
+}
+
+const fmtDate = function (time, format) {
+    time = time ? new Date(time) : new Date()
+    format = format || 'YYYY-MM-DD'
+
+    function tf(i) {
+        return (i < 10 ? '0' : '') + i
+    }
+
+    // @ts-ignore
+    return format.replace(/YYYY|MM|DD|hh|mm|ss|WW/g, function (a) {
+        switch (a) {
+            case 'YYYY':
+                return tf(time.getFullYear())
+            case 'MM':
+                return tf(time.getMonth() + 1)
+            case 'DD':
+                return tf(time.getDate())
+            case 'mm':
+                return tf(time.getMinutes())
+            case 'hh':
+                return tf(time.getHours())
+            case 'ss':
+                return tf(time.getSeconds())
+            case 'WW':
+                return ['日', '一', '二', '三', '四', '五', '六'][time.getDay()]
+        }
+    })
+}
+
 
 const msg = (title, icon = 'none', duration = 1800, mask = false) => {
     //统一提示方便全局修改
@@ -187,5 +219,5 @@ const msg = (title, icon = 'none', duration = 1800, mask = false) => {
 };
 
 export {
-    get, post, upload, cfg, serverUrl, fileUrl,formatUrl,msg
+    get, post, upload, cfg, serverUrl, fileUrl, fmtUrl, fmtMoney, msg, fmtDate
 }

+ 1 - 1
src/pages.json

@@ -10,7 +10,7 @@
 		{
 			"path": "pages/index/index",
 			"style": {
-				"navigationBarTitleText": "运营看板"
+				"navigationBarTitleText": "快与慢运营看板"
 			}
 		},
 		{

+ 391 - 65
src/pages/index/index.vue

@@ -4,55 +4,49 @@
         <view class="text-area">
           <text class="title">{{ state.title }}</text>
         </view>-->
-    <div class="header">
-      <uni-row class="w100">
-        <uni-col :span="18" class="header-left">
-<!--          <view >站点:</view>-->
-          <picker mode="selector"  @change="handleStationCheck" :value="state.index" range-key="stationName"  :range="state.stationList">
-            <view class="uni-input">{{state.stationList[state.index].stationName}}</view>
-          </picker>
-        </uni-col>
-        <uni-col :span="6"  >
-          <view class="header-right">{{state.user.username}}</view>
-        </uni-col>
-      </uni-row>
-    </div>
-
-
-    <uni-card title="概要统计" type="line" class="w100">
-        <view class="content-summary">
-          <view v-for="item in state.statList" class="content-summary-item" :key="item.label">
-            <view>{{ item.label }}</view>
-            <view>{{ item.value }}</view>
+    <uni-card class="w100">
+      <view class="header w100">
+          <view class="header-left">
+            <!--          <view >站点:</view>-->
+            <picker mode="selector" @change="handleStationCheck" :value="state.index" range-key="stationName" :range="state.stationList">
+              <view class="uni-input">{{ state.stationList[state.index]?.stationName }}</view>
+            </picker>
           </view>
+          <view class="header-right">
+            <view >{{ state.user?.username }}</view>
+          </view>
+      </view>
+    </uni-card>
+
+
+    <uni-card title="今日概要" type="line" class="w100">
+      <view class="content-summary">
+        <view v-for="item in state.statList" class="content-summary-item" :key="item.label">
+          <view>{{ item.label }}</view>
+          <view>{{ item.value }}</view>
         </view>
-      </uni-card>
+      </view>
+    </uni-card>
     <view class="content1 w100">
 
 
       <block class="w100">
-        <uni-card title="运营数据" padding="10px 0"  >
+        <uni-card title="运营数据" padding="10px 0">
           <uni-segmented-control :current="state.current"
                                  :values="state.items"
                                  @clickItem="handleClickPeriod"
                                  styleType="text" activeColor="#4cd964"></uni-segmented-control>
           <view>
-            <canvas id="charge-ele-total" canvas-id="charge-ele-total" class="charts" @tap="tap"></canvas>
-          </view>
-          <view>
-            <canvas id="charge-order-money" canvas-id="charge-order-money" class="charts" @tap="tap"></canvas>
-          </view>
-          <view>
-            <canvas id="charge-ele-fee" canvas-id="charge-ele-fee" class="charts" @tap="tap"></canvas>
-          </view>
-          <view>
-            <canvas id="charge-service-money" canvas-id="charge-service-money" class="charts" @tap="tap"></canvas>
+            <view class="title">总金额&服务费</view>
+            <canvas id="charge-ele-total" canvas-id="charge-ele-total" class="charts1" @touchend="tap" :style="{'width':state.cWidth+'px','height':state.cHeight+'px'}"></canvas>
           </view>
           <view>
-            <canvas id="charge-order-user" canvas-id="charge-order-user" class="charts" @tap="tap"></canvas>
+            <view class="title">电量&电费</view>
+            <canvas id="elec-power" canvas-id="elec-power" class="charts1" @touchend="tap" :style="{'width':state.cWidth+'px','height':state.cHeight+'px'}"></canvas>
           </view>
           <view>
-            <canvas id="charge-order-num" canvas-id="charge-order-num" class="charts" @tap="tap"></canvas>
+            <view class="title">充电客户&订单</view>
+            <canvas id="charge-user-order" canvas-id="charge-user-order" class="charts1" @touchend="tap" :style="{'width':state.cWidth+'px','height':state.cHeight+'px'}"></canvas>
           </view>
         </uni-card>
       </block>
@@ -63,16 +57,16 @@
 </template>
 
 <script setup>
-import {reactive} from 'vue';
-import {onLoad,onShow} from '@dcloudio/uni-app'
+import {reactive, watch,nextTick} from 'vue';
+import {onLoad, onShow} from '@dcloudio/uni-app'
 
 import uCharts from "../../assets/u-charts.min";
-import {cfg,get} from "../../assets/utils";
+import {cfg, get, fmtMoney, fmtDate, msg} from "../../assets/utils";
 
 const uChartsInstance = {};
 
 const state = reactive({
-  login:false,
+  login: false,
   title: '快与慢运营看板小程序',
   cWidth: 750,
   cHeight: 500,
@@ -84,11 +78,30 @@ const state = reactive({
     {label: '今日充电人数', value: '100'},
     {label: '今日充电订单数', value: '100'},
   ],
-  user:null,
-  stationList:[],
-  index:0,
-  items:['7天','30天','90天'],
-  current:0
+  user: null,
+  stationList: [],
+  index: 0,
+  items: ['7天', '30天', '90天'],
+  current: 0,
+  chartData: null,
+  uChartsInstance:{}
+})
+
+
+/**
+ * 监听站点切换
+ */
+watch(() => state.index, (newVal, oldVal) => {
+  loadTodayStat();
+  loadPeriodStat();
+})
+
+
+/**
+ * 监听周期切换
+ */
+watch(() => state.current, (newVal, oldVal) => {
+  loadPeriodStat();
 })
 
 
@@ -100,45 +113,349 @@ onLoad(() => {
   checkLogin();
 })
 
-onShow(()=>{
-  state.user=uni.getStorageSync(cfg.key.user);
-  if(state.user){
+onShow(() => {
+  state.user = uni.getStorageSync(cfg.key.user);
+  if (state.user) {
     loadStationList();
   }
 })
 
 const loadStationList = () => {
   get(`station/listStation`, {pageNum: 1024}).then((res) => {
-    state.stationList = res;
+    state.stationList = res||[]?.map(k=>{
+      return {
+        stationId:k.stationId,
+        stationName:k.stationName
+      }
+    });
+    nextTick(()=>{
+      loadTodayStat();
+      loadPeriodStat();
+    })
+  }).catch(e => {
+    console.error(e)
+  })
+}
+
+/**
+ * 站点今日的统计数据
+ */
+const loadTodayStat = () => {
+  let station = state.stationList[state.index];
+  if (!station) {
+    return;
+  }
+  let stationId = state.stationList[state.index].stationId;
+  get(`stat/stationTodayStat?stationId=${stationId}`).then((res) => {
+    let {chargeUsers, totalOrders, totalPower, totalMoney, payAmount, elecMoney, serviceMoney, payServiceAmount} = res;
+    state.statList[0].value = totalPower + 'Kwh';
+    state.statList[1].value = fmtMoney(totalMoney) + '元';
+    state.statList[2].value = fmtMoney(elecMoney) + '元';
+    state.statList[3].value = fmtMoney(payServiceAmount) + '元';
+    state.statList[4].value = chargeUsers;
+    state.statList[5].value = totalOrders;
   }).catch(e => {
     console.error(e)
   })
 }
 
+/**
+ * 站点最近趋势的统计数据
+ */
+const loadPeriodStat = () => {
+  let station = state.stationList[state.index];
+  if (!station) {
+    msg('无站点统计数据');
+    return;
+  }
+  let stationId = state.stationList[state.index].stationId;
+  const end = new Date()
+  const start = new Date()
+  let type='day'
+  if (state.current === 0) {
+    start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+  } else if (state.current === 1) {
+    start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+  } else if (state.current === 2) {
+    start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
+  }
+  let params = {
+    stationIds: [stationId],
+    startTime: fmtDate(start),
+    endTime: fmtDate(end),
+    type:type
+  }
+
+  get(`/stat/stationStatDetail`, params).then(res => {
+    console.log(res)
+    state.chartData = res;
+    //总金额和服务费折线图
+    drawServiceAndTotalMoneyChart(stationId);
+    //电费和电量折线图
+    drawElectricAndPowerChart(stationId);
+    //充电人数、订单
+    drawChargeUserChart(stationId);
+  })
+}
+
+/**
+ * 总金额和服务费折线图
+ */
+const drawServiceAndTotalMoneyChart = (stationId) => {
+  let dataList = state.chartData[stationId]
+  if(!dataList || dataList.length==0){
+    return;
+  }
+  let categories = dataList.map(k=>k.statDay);
+  let series =[
+    {
+      name:'总金额',
+      data:dataList.map(k=>fmtMoney(k.totalMoney))
+    },
+    {
+      name:'服务费',
+      data:dataList.map(k=>fmtMoney(k.serviceMoney))
+    }
+  ]
+  let options =  {
+    type: "area",
+    color: ["#1890FF","#91CB74","#FAC858","#EE6666","#73C0DE","#3CA272","#FC8452","#9A60B4","#ea7ccc"],
+    padding: [15,15,0,15],
+    enableScroll: true,
+    width: state.cWidth,
+    height: state.cHeight,
+    categories: categories,
+    series: series,
+    xAxis: {
+      disableGrid: true,
+      itemCount:4,
+      scrollShow:true
+    },
+    yAxis: {
+      gridType: "dash",
+      dashLength: 2
+    },
+    extra: {
+      area: {
+        type: "curve",
+        opacity: 0.2,
+        addLine: true,
+        width: 2,
+        gradient: true,
+        activeType: "hollow"
+      }
+    }
+  }
+
+  let charts = state.uChartsInstance["charge-ele-total"];
+  if (charts) {
+    charts.updateData(options);
+  }else{
+    const ctx = uni.createCanvasContext("charge-ele-total");
+    options.context = ctx;
+    console.log(options)
+    console.log(state.cHeight,state.cWidth)
+    // charts.setChartData(options);
+    state. uChartsInstance["charge-ele-total"] = new uCharts(options);
+  }
+}
+
+
+/**
+ * 电费和电量折线图
+ */
+const drawElectricAndPowerChart = (stationId) => {
+  let dataList = state.chartData[stationId]
+  if(!dataList || dataList.length==0){
+    return;
+  }
+  let categories = dataList.map(k=>k.statDay);
+  let series =[
+    {
+      name:'电量',
+      type: "area",
+      style: "curve",
+      data:dataList.map(k=>k.totalPower)
+    },
+    {
+      name:'电费',
+      type: "line",
+      style: "curve",
+      color: "#1890ff",
+      data:dataList.map(k=>fmtMoney(k.elecMoney))
+    }
+  ]
+  let options =  {
+    type: "mix",
+    color: ["#1890FF","#91CB74","#FAC858","#EE6666","#73C0DE","#3CA272","#FC8452","#9A60B4","#ea7ccc"],
+    padding: [15,15,0,15],
+    enableScroll: true,
+    animation: true,
+    width: state.cWidth,
+    height: state.cHeight,
+    categories: categories,
+    series: series,
+    xAxis: {
+      disableGrid: true,
+      itemCount:4,
+      scrollShow:true
+    },
+    yAxis: {
+      disabled: false,
+      disableGrid: false,
+      splitNumber: 5,
+      gridType: "dash",
+      dashLength: 4,
+      gridColor: "#CCCCCC",
+      padding: 10,
+      showTitle: true,
+      data: [
+        {
+          position: "left",
+          title: "折线"
+        },
+        {
+          position: "right",
+          min: 0,
+          max: 200,
+          title: "柱状图",
+          textAlign: "left"
+        }
+      ]
+    },
+    extra: {
+      mix: {
+        column: {
+          width: 20
+        }
+      }
+    }
+  }
+
+  let charts =state. uChartsInstance["elec-power"];
+  if (charts) {
+    charts.updateData(options);
+  }else{
+    const ctx = uni.createCanvasContext("elec-power", this);
+    options.context = ctx;
+    // charts.setChartData(options);
+    state.uChartsInstance["elec-power"] = new uCharts(options);
+  }
+}
+
+
+/**
+ * 充电人数柱状图
+ */
+const drawChargeUserChart = (stationId) => {
+  let dataList = state.chartData[stationId]
+  if(!dataList || dataList.length==0){
+    return;
+  }
+  let categories = dataList.map(k=>k.statDay);
+  let series =[
+    {
+      name:'人次',
+      data:dataList.map(k=>k.chargeUsers)
+    },
+    {
+      name:'订单',
+      data:dataList.map(k=>k.validOrders)
+    }
+  ]
+  let options =  {
+    type: "column",
+    color: ["#1890FF","#91CB74","#FAC858","#EE6666","#73C0DE","#3CA272","#FC8452","#9A60B4","#ea7ccc"],
+    padding: [15,15,0,15],
+    enableScroll: true,
+    animation: true,
+    width: state.cWidth,
+    height: state.cHeight,
+    categories: categories,
+    series: series,
+    xAxis: {
+      itemCount:4,
+      scrollShow:true,
+      boundaryGap: "justify",
+      disableGrid: false,
+      min: 0,
+      axisLine: false,
+      max: 40
+    },
+    yAxis: {
+      data: [
+        {
+          min: 0
+        }
+      ]
+    },
+    extra: {
+      column: {
+        type: "group",
+        width: 30,
+        activeBgColor: "#000000",
+        activeBgOpacity: 0.08,
+        linearType: "custom",
+        seriesGap: 5,
+        linearOpacity: 0.5,
+        barBorderCircle: true,
+        customColor: [
+          "#1890FF",
+          "#91CB74"
+        ]
+      }
+    }
+  }
+
+  let charts = state.uChartsInstance["charge-user-order"];
+  if (charts) {
+    charts.updateData(options);
+  }else{
+    const ctx = uni.createCanvasContext("charge-user-order", this);
+    options.context = ctx;
+    // charts.setChartData(options);
+    state.uChartsInstance["charge-user-order"] = new uCharts(options);
+  }
+}
+
+
+
 const handleStationCheck = (e) => {
+  console.log(e)
+  state.index = e.detail.value;
+}
 
+const handleClickPeriod = (e) => {
+  console.log(e)
+  state.current = e.currentIndex;
 }
 
 const checkLogin = () => {
   let user = uni.getStorageSync(cfg.key.user);
-  if(user){
+  if (user) {
     state.login = true;
-  }else{
-    state.login=false;
+  } else {
+    state.login = false;
     uni.navigateTo({
-      url:'/pages/login/index'
+      url: '/pages/login/index'
     });
   }
 }
 
 const tap = (e) => {
-  uChartsInstance[e.target.id].touchLegend(e);
-  uChartsInstance[e.target.id].showToolTip(e);
+  state.uChartsInstance[e.target.id].touchLegend(e);
+  state.uChartsInstance[e.target.id].showToolTip(e);
 }
 
+
+/**
+ * DEMO
+ * @param canvasId
+ * @param data
+ */
 const drawCharts = (canvasId, data) => {
   const ctx = uni.createCanvasContext(canvasId, this);
-  uChartsInstance[canvasId] = new uCharts({
+  state.uChartsInstance[canvasId] = new uCharts({
     type: "column",
     context: ctx,
     width: state.cWidth,
@@ -171,20 +488,25 @@ const drawCharts = (canvasId, data) => {
   padding: 15rpx 15rpx;
 }
 
-.header{
+.header {
   width: 100%;
+  padding: 10px;
+  display: inline-flex;
+justify-content: space-between;
 
-  .header-left{
+  .header-left {
     display: inline-flex;
     align-content: center;
+    width: 60%;
   }
 
-  .header-left{
-   text-align: right;
+  .header-right {
+    text-align: right;
+    width: 35%;
   }
 }
 
-.content-summary{
+.content-summary {
   display: flex;
   justify-content: space-between;
   width: 100%;
@@ -192,21 +514,22 @@ const drawCharts = (canvasId, data) => {
   margin-bottom: 30rpx;
   flex-wrap: wrap;
 
-  .content-summary-item{
+  .content-summary-item {
     display: flex;
     flex-direction: column;
     align-items: center;
     justify-content: center;
-    width: 33.33%;
-    box-shadow: 2px   2px  2px 2px #e6e6e6;
+    width: 120px;
+    padding: 10px;
+    //box-shadow: 2px   2px  2px 2px #e6e6e6;
   }
 }
 
-.kanban-title{
+.kanban-title {
   display: inline-flex;
 }
 
-.content-chart{
+.content-chart {
   width: 100%;;
 }
 
@@ -226,7 +549,10 @@ const drawCharts = (canvasId, data) => {
 }
 
 .title {
-  font-size: 36rpx;
+  margin-top: 30rpx;
+  margin-left: 20rpx;
+  font-size: 30rpx;
   color: #8f8f94;
 }
+
 </style>

+ 2 - 0
src/pages/login/index.vue

@@ -170,6 +170,8 @@ const handleInputMobile = (e) => {
   if (reg.test(e)) {
     post(`wx/login`, {openId: state.openid, mobilePhone: e}).then(res1 => {
       loginSuccess(res1);
+    }).catch(e=>{
+      msg('登录失败,请稍后再试')
     })
   } else {
     msg("请输入有效的手机号");