鸿蒙智能电子锁前端项目
he wei
2025-03-15 f8fd43f1307b0ae3a55f5a5a1390fd13b506fa88
src/views/dashboard/index.vue
@@ -1,307 +1,492 @@
<template>
  <div class="dashboard-container">
    <hdw-card>
      <div class="data-stats-header">
        <el-row :gutter="16">
          <el-col :span="8">
            <div class="user-info-wrapper">
              <el-avatar class="user-icon" :size="30" :src="userImg" />
              <span class="user-name">武汉源畅</span>
              <span class="user-change">切换</span>
            </div>
          </el-col>
          <el-col :span="8">
            <div class="data-stats-time-wrapper">
              <span class="time-title">统计时间:</span>
              <el-radio-group class="radio-class" v-model="timeType" size="small">
                <el-radio-button label="本周" :value="0" />
                <el-radio-button label="本月" :value="1" />
                <el-radio-button label="本年" :value="2" />
              </el-radio-group>
            </div>
          </el-col>
          <el-col :span="8">
            <div class="data-stats-time-wrapper">
              <span class="time-title">自定义时间:</span>
              <el-date-picker
                  v-model="rangeTime"
                  type="daterange"
                  range-separator="至"
                  start-placeholder="开始时间"
                  end-placeholder="结束时间"
                  size="small"
              />
            </div>
          </el-col>
        </el-row>
      </div>
<script setup name="Dashboard">
   import { defineComponent } from 'vue';
   import HdwCard from '@/components/HdwCard/index.vue';
   import LedNum from '@/components/LedNum/index.vue';
   import mapChart from "@/components/echarts/mapChart.vue";
   import pie from "@/components/echarts/pie.vue";
   import bar from "@/components/echarts/bar.vue";
   import groupBar from "@/components/echarts/groupBar.vue";
    </hdw-card>
    <div class="data-stats">
      <el-row :gutter="8">
        <el-col :span="8">
          <hdw-card>
            <div class="data-stats-wrapper">
              <div class="data-stats-icon">
                <div class="data-stats-icon-content">
                  <svg-icon icon-class="lock-hdw"/>
                  <br/>
                  <span class="icon-text">锁具统计</span>
                </div>
   import { ref, watchEffect } from 'vue';
   import useWebsocket from '@/hooks/useWebsocket';
   const { message: listMessage } = useWebsocket('home');
   const stationNum = ref(0);
   const chartData = ref([]);
   const map = ref();
   const typePie = ref();
   const prodPie = ref();
   const testBar = ref();
   const useageRateChart = ref();
   let lockDatas = ref([]);
   const rateList = ref([]);
   const rateType = ref('month');
   // "offLineNum": 6,
   //       "unLoadNum": 5,
   //       "sumLinf": 7,
   //       "openNum": 0,
   //       "onlineNum": 1,
   //       "closeNum": 2
   const offLine_num = ref(0);
   const unLoad_num = ref(0);
   const sumLinf_num = ref(0);
   const open_num = ref(0);
   const online_num = ref(0);
   const close_num = ref(0);
   const rtData = ref([]);
   const alarmData = ref([]);
   watchEffect(() => {
      if (listMessage.value) {
         const { resScreenBox, resErrorCtlLog, resAllCtlLog, resLockState, resReport, resAllAinf } = JSON.parse(listMessage.value)?.data;
         console.log('resScreenBox, resErrorCtlLog, resAllCtlLog, resLockState, resReport', resScreenBox, resErrorCtlLog, resAllCtlLog, resLockState, resReport, '=============');
         let locks = [];
         let _offLine = 0,
            _unLoad = 0,
            _sumLinf = 0,
            _open = 0,
            _online = 0,
            _close = 0;
         if (resLockState.code && resLockState.data) {
            let _list = resLockState.data2;
            locks = _list.allLinfs.filter(v => v.longitude || v.latitude);
            _offLine = _list.offLineNum;
            _unLoad = _list.unLoadNum;
            _sumLinf = _list.sumLinf;
            _open = _list.openNum;
            _online = _list.onlineNum;
            _close = _list.closeNum;
         }
         offLine_num.value = _offLine;
         unLoad_num.value = _unLoad;
         sumLinf_num.value = _sumLinf;
         open_num.value = _open;
         online_num.value = _online;
         close_num.value = _close;
         lockDatas.value = locks;
         if (resAllCtlLog.code && resAllCtlLog.data) {
            let _list = resAllCtlLog.data2.map(v => ({
               ...v,
               state: v.ctlResult ? '开锁成功' : '开锁失败'
            }));
            rtData.value = _list;
         }
         if (resErrorCtlLog.code && resErrorCtlLog.data) {
            let _list = resErrorCtlLog.data2.map(v => ({
               ...v,
               state: v.ctlResult ? '开锁成功' : '开锁失败'
            }));
            alarmData.value = _list;
         }
         let _rateList = [];
         if (resReport.code && resReport.data) {
            _rateList = resReport.data2;
         }
         rateList.value = _rateList;
         let area_num = 0;
         if (resAllAinf.code && resAllAinf.data) {
            area_num = resAllAinf.data2;
         }
         stationNum.value = area_num;
         if (resScreenBox.code && resScreenBox.data) {
            let { product, type } = resScreenBox.data2;
            let prodData = formatPieData(product);
            let typeData = formatPieData(type);
            typePie.value.updateChart(typeData.sData);
            prodPie.value.updateChart(prodData.sData);
         }
         updateMap();
         updateStateChart();
         updateUseageRateChart();
      }
   });
   function updateStateChart() {
      let labels = ['锁具', '未安装', '开锁', '关锁', '在线', '离线'];
      let datas = [sumLinf_num.value, unLoad_num.value, open_num.value, close_num.value, online_num.value, offLine_num.value];
      testBar.value.updateChart(labels, datas);
   }
   /**
    * 格式化数据  取前4 剩下的为其他
    */
   function formatPieData(data) {
      let res = { sData: [] };
      let arr = Object.keys(data)
         .map((v) => ({ name: v, value: data[v] }))
         .sort((a, b) => {
            return b.value - a.value;
         });
      let total = 0;
      arr.map(v => {
         total += v.value * 1;
      });
      if (arr.length <= 5) {
         res.sData = arr;
      } else {
         let name = "其他";
         let value = 0;
         let otherList = [];
         arr.splice(4).forEach((v) => {
            value += v.value * 1;
            let percent = (total ? v.value / total : 0).toFixed(4);
            otherList.push({
               name: v.name,
               value: v.value,
               percent: (percent * 100).toFixed(2) + "%"
            });
         });
         res.sData = arr;
         res.sData.push({ name, value });
      }
      return res;
   }
   function updateMap() {
      const getColor = (onLine) => ["#aaa", "#0f0"][onLine];
      let data = lockDatas.value;
      map.value.updateChart(
         data.map((v) => {
            return {
               ...v,
               label: v.lockName,
               color: getColor(v.lockOnline),
               points: [v.longitude, v.latitude],
               // 无实际意义
               value: 100,
            };
         })
      );
   }
   function updateUseageRateChart() {
      // 获取当前的日期对象
      let currentDate = new Date();
      // 获取月份数(0 表示一月,11 表示十二月)
      let month = currentDate.getMonth() + 1;
      // 获取季度数
      let quarter = Math.ceil(month / 3);
      let type = rateType.value;
      let _list = rateList.value;
      let list = [];
      switch (type) {
         case 'month':
            list = _list.map(v => ({
               label: v.lockName, value: v['month' + month]
            })).sort((a, b) => b.value - a.value);
            break;
         case 'quarter':
            list = _list.map(v => ({
               label: v.lockName, value: v['quarter' + quarter]
            })).sort((a, b) => b.value - a.value);
            break;
         case 'year':
            list = _list.map(v => ({
               label: v.lockName, value: v.yearCount
            })).sort((a, b) => b.value - a.value);
            break;
      }
      let labels = list.slice(0, 5).map(v => v.label);
      let datas = list.slice(0, 5).map(v => v.value);
      useageRateChart.value.updateChart(labels, datas);
   }
</script>
<template>
  <div class="dashboard-wrapper">
    <div class="dashboard-left">
      <div class="left-content-list">
        <div class="left-content-item">
          <hdw-card is-full title="屏柜类型">
            <pie ref="typePie"></pie>
          </hdw-card>
        </div>
        <div class="left-content-item">
          <hdw-card is-full title="设备工作状态">
            <bar ref="testBar" unit="套"></bar>
          </hdw-card>
        </div>
        <div class="left-content-item last">
          <hdw-card is-full title="环境状态">
            <div class="env">
              <!-- 烟感 -->
              <div class="item">
                <div class="label">烟感</div>
                <div class="value">0</div>
              </div>
              <div class="data-stats-content">
                <el-row :gutter="16">
                  <el-col :span="12">
                    <div class="num-stats-wrapper">
                      <div class="num-title">已安装</div>
                      <div class="num-value">112</div>
                    </div>
                  </el-col>
                  <el-col :span="12">
                    <div class="num-stats-wrapper">
                      <div class="num-title">未安装</div>
                      <div class="num-value">2</div>
                    </div>
                  </el-col>
                  <el-col :span="12">
                    <div class="num-stats-wrapper">
                      <div class="num-title">蓝牙锁</div>
                      <div class="num-value">112</div>
                    </div>
                  </el-col>
                  <el-col :span="12">
                    <div class="num-stats-wrapper">
                      <div class="num-title">无源锁</div>
                      <div class="num-value">2</div>
                    </div>
                  </el-col>
                </el-row>
              <!-- 温度 -->
              <div class="item">
                <div class="label">温度</div>
                <div class="value">0</div>
              </div>
              <!-- 湿度 -->
              <div class="item">
                <div class="label">湿度</div>
                <div class="value">0</div>
              </div>
            </div>
          </hdw-card>
        </el-col>
        <el-col :span="8">
          <hdw-card>
            <div class="data-stats-wrapper">
              <div class="data-stats-icon">
                <div class="data-stats-icon-content">
                  <svg-icon icon-class="key-hdw"/>
                  <br/>
                  <span class="icon-text">钥匙统计</span>
                </div>
              </div>
              <div class="data-stats-content">
                <el-row :gutter="16">
                  <el-col :span="12">
                    <div class="num-stats-wrapper">
                      <div class="num-title">实体钥匙</div>
                      <div class="num-value">0</div>
                    </div>
                  </el-col>
                  <el-col :span="12">
                    <div class="num-stats-wrapper">
                      <div class="num-title">设备钥匙</div>
                      <div class="num-value">0</div>
                    </div>
                  </el-col>
                  <el-col :span="12">
                    <div class="num-stats-wrapper">
                      <div class="num-title">蓝牙钥匙</div>
                      <div class="num-value">6</div>
                    </div>
                  </el-col>
                </el-row>
              </div>
            </div>
          </hdw-card>
        </el-col>
        <el-col :span="8">
          <hdw-card is-full>
            <div class="data-stats-wrapper" style="height: 100%">
              <div class="data-stats-icon">
                <div class="data-stats-icon-content">
                  <svg-icon icon-class="safe-lock-hdw"/>
                  <br/>
                  <span class="icon-text">事件统计</span>
                </div>
              </div>
              <div class="data-stats-content">
                <el-row :gutter="16">
                  <el-col :span="12">
                    <div class="num-stats-wrapper">
                      <div class="num-title">开锁操作</div>
                      <div class="num-value">0</div>
                    </div>
                  </el-col>
                  <el-col :span="12">
                    <div class="num-stats-wrapper">
                      <div class="num-title">报警事件</div>
                      <div class="num-value">0</div>
                    </div>
                  </el-col>
                </el-row>
              </div>
            </div>
          </hdw-card>
        </el-col>
      </el-row>
        </div>
      </div>
    </div>
    <div class="data-stats-footer">
      <el-row :gutter="8" style="height: 100%">
        <el-col :span="12" style="height: 100%">
          <hdw-card title="开锁统计" is-full>
            <div class="chart-wrapper">
              <line-chart :chart-data="unlockingData"></line-chart>
    <div class="dashboard-middle">
      <div class="middle-content-wrapper">
        <hdw-card>
          <div class="number-list-wrapper">
            <div class="number-item">
              <div class="number-label">区域</div>
              <div class="number-value">
                <led-num color="#f00" :num="stationNum"></led-num>
              </div>
            </div>
          </hdw-card>
        </el-col>
        <el-col :span="12" style="height: 100%">
          <hdw-card title="快捷入口" is-full>
            <div class="fast-menu">
              <el-row :gutter="16">
                <el-col :span="6">
                  <menu-card></menu-card>
                </el-col>
                <el-col :span="6">
                  <menu-card url="/system/user" title="用户管理" icon="people-hdw" style="background-color: #56e0c7"></menu-card>
                </el-col>
                <el-col :span="6">
                  <menu-card url="/device/lock" title="锁具管理" icon="lock-hdw" style="background-color: #fe945f"></menu-card>
                </el-col>
                <el-col :span="6">
                  <menu-card url="/device/key" title="钥匙管理" icon="key-hdw" style="background-color: #fdcc1c"></menu-card>
                </el-col>
                <el-col :span="6">
                  <menu-card url="/general/authorize" title="授权管理" icon="auth-hdw" style="background-color: #5a84fd"></menu-card>
                </el-col>
                <el-col :span="6">
                  <menu-card url="/general/log" title="日志管理" icon="log-hdw" style="background-color: #7997b3"></menu-card>
                </el-col>
                <el-col :span="6">
                  <menu-card url="/general/map" title="地图定位" icon="map-hdw" style="background-color: #1ab5b1"></menu-card>
                </el-col>
              </el-row>
            <div class="number-item">
              <div class="number-label">锁具</div>
              <div class="number-value">
                <led-num color="#f00" :num="sumLinf_num"></led-num>
              </div>
            </div>
            <!-- <div class="number-item">
                                  <div class="number-label">电子卡</div>
                                  <div class="number-value">
                                    <led-num color="#f00" :num="cardNum"></led-num>
                                  </div>
                                </div> -->
          </div>
        </hdw-card>
        <div class="map-wrapper">
          <hdw-card is-full>
            <map-chart ref="map" @mapMounted="updateMap">
              <template #tools>
                <div class="map-mark">
                  <div class="mark online">
                    <el-icon class="ico"><icon-dot /></el-icon>在线
                    <div class="count">{{ online_num }}</div>
                  </div>
                  <div class="mark offline">
                    <el-icon class="ico"><icon-dot /></el-icon>离线
                    <div class="count">{{ offLine_num }}</div>
                  </div>
                </div>
              </template>
            </map-chart>
          </hdw-card>
        </el-col>
      </el-row>
        </div>
        <div class="dev-real-info">
          <hdw-card title="实时开锁信息" is-full>
            <el-table :data="rtData" style="width: 100%; height: 100%">
              <el-table-column prop="ctlTime" label="时间" width="180" />
              <el-table-column prop="lockId" label="锁具ID" width="180" />
              <el-table-column prop="lockName" label="锁具名称" />
              <el-table-column prop="state" label="操作状态" />
            </el-table>
          </hdw-card>
        </div>
      </div>
    </div>
    <div class="dashboard-right">
      <div class="left-content-list">
        <div class="left-content-item">
          <hdw-card is-full title="屏柜品牌">
            <pie ref="prodPie"></pie>
          </hdw-card>
        </div>
        <div class="left-content-item">
          <hdw-card is-full title="使用频次">
            <template #tools>
              <el-radio-group v-model="rateType" size="small">
                <el-radio-button value="month" label="本月"></el-radio-button>
                <el-radio-button value="quarter" label="本季度"></el-radio-button>
                <el-radio-button value="year" label="本年"></el-radio-button>
              </el-radio-group>
            </template>
            <bar ref="useageRateChart" :rotate="30" unit="次"></bar>
          </hdw-card>
        </div>
        <div class="left-content-item last">
          <hdw-card is-full title="告警统计">
            <el-table :data="alarmData" style="width: 100%; height: 100%">
              <el-table-column prop="ctlTime" label="时间" width="160" />
              <el-table-column prop="lockId" label="锁具ID" width="90" />
              <el-table-column prop="lockName" label="锁具名称" />
              <el-table-column prop="state" label="操作状态" />
            </el-table>
          </hdw-card>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { defineComponent } from 'vue';
import MenuCard from '@/views/dashboard/components/MenuCard.vue';
import HdwCard from '@/components/HdwCard/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
import LineChart from './components/LineChart.vue';
import userImg from '@/assets/images/user.png';
export default defineComponent({
  name: 'Dashboard',
  components: { LineChart, SvgIcon, HdwCard, MenuCard },
  data() {
    return {
      unlockingData: [],
      userImg,
      timeType: 0,
      rangeTime: ''
    };
  }
});
</script>
<style scoped lang="scss">
.dashboard-container {
  height: 100%;
  overflow: auto;
  padding: 8px;
  .data-stats-footer {
    min-height: 300px;
    height: calc(100% - 265px);
  }
}
.user-info-wrapper {
  display: inline-block;
  .user-icon {
    vertical-align: middle;
  }
  .user-name {
    margin-left: 8px;
    font-size: 14px;
  }
  .user-change {
    margin-left: 8px;
    font-size: 14px;
    color: #409eff;
    font-weight: 700;
    cursor: pointer;
  }
}
.data-stats-time-wrapper {
  .radio-class {
    vertical-align: middle;
  }
  .time-title {
    margin-right: 8px;
    font-size: 14px;
    display: inline-block;
  }
}
.data-stats {
  margin-top: 8px;
  padding-bottom: 8px;
}
.data-stats-wrapper {
.dashboard-wrapper {
  display: flex;
  flex-direction: row;
  padding: 16px;
  .data-stats-icon {
    display: flex;
    justify-content: center;
    align-items: center;
    text-align: center;
    flex-direction: column;
    .svg-icon {
      font-size: 100px;
      color: #5e5e5e;
    }
    .icon-text {
      display: inline-block;
      margin-top: 8px;
      font-size: 14px;
      font-weight: 700;
      color: #5e5e5e;
    }
  }
  .data-stats-content {
    flex: 1;
  }
;
}
.num-stats-wrapper {
  padding: 12px;
  text-align: center;
  .num-title {
    color: gray;
    font-size: 14px;
  }
  .num-value {
    font-size: 30px;
    font-weight: 700;
  }
}
.fast-menu {
  text-align: center;
  .el-col {
    padding: 32px 0;
  }
}
.chart-wrapper {
  height: 100%;
  overflow: hidden;
  .dashboard-left {
    width: 500px;
    height: 100%;
  }
  .dashboard-middle {
    flex: 1;
    height: 100%;
  }
  .dashboard-right {
    width: 500px;
    height: 100%;
  }
}
.left-content-list {
  padding: 8px;
  height: 100%;
  .left-content-item {
    height: 33%;
    padding-bottom: 8px;
    &.last {
      height: 34%;
      padding-bottom: 0;
    }
  }
}
.middle-content-wrapper {
  display: flex;
  flex-direction: column;
  padding: 8px 0;
  height: 100%;
  .number-list-wrapper {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 10px;
    margin-top: 8px;
    .number-item {
      cursor: pointer;
      display: flex;
      // border-radius: 6px;
      align-items: center;
      .number-label {
        white-space: nowrap;
        background: #0ff;
        border-radius: 6px;
        color: #333;
        font-size: 16px;
        font-weight: bold;
        padding: 0 10px;
      }
      .number-value {
        width: 4.5em;
        height: 1.5em;
        padding: 0 10px;
      }
    }
  }
  .map-wrapper {
    flex: 1;
    padding: 8px 0;
  }
  .wrap-chart {
    flex: 1;
    // position: relative;
  }
  .map-mark {
    position: absolute;
    right: 10px;
    bottom: 10px;
    .mark {
      display: flex;
      align-items: center;
      .ico {
        font-size: 14px;
        margin-right: 0.4em;
      }
      .count {
        margin-left: 1em;
      }
      &.online {
        color: #0f0;
      }
      &.offline {
        color: #aaa;
      }
    }
  }
  .dev-real-info {
    height: 34%;
  }
}
.env {
  height: 100%;
  padding: 10px;
  display: flex;
  flex-direction: row;
  align-items: center;
  .item {
    flex: 1;
    border-radius: 8px;
    padding: 4px 8px;
    background: #0ff;
    & + .item {
      margin-left: 20px;
    }
      .label {
        color: #333;
       font-weight: bold;
       margin-bottom: 0.4em;
       text-align: center;
      }
      .value {
        background: #000;
        border-radius: 6px;
        padding: 4px 8px;
        color: #fff;
        font-weight: bold;
        text-align: center;
      }
  }
}
</style>