From d5ca37c6301ac1178702e0334c4feee6abf3ba9e Mon Sep 17 00:00:00 2001
From: whychdw <496960745@qq.com>
Date: 星期五, 11 十月 2024 14:19:09 +0800
Subject: [PATCH] 内容提交

---
 src/components/HdwCard/index.vue             |   57 ++++
 src/components/HdwTree/index.vue             |   69 ++++
 src/assets/images/user.png                   |    0 
 src/api/user.js                              |    2 
 components.d.ts                              |   11 
 src/router/modules/system.ts                 |    2 
 src/icons/svg/safe-lock-hdw.svg              |    1 
 src/views/dashboard/components/LineChart.vue |  119 ++++++++
 src/utils/resize.js                          |   56 ++++
 src/views/dashboard/components/MenuCard.vue  |   56 ++++
 vite.config.ts                               |    3 
 .env.dev_prod                                |    4 
 mock/demo/user.js                            |    2 
 src/views/dashboard/index.vue                |  295 ++++++++++++++++++++
 src/icons/svg/people-hdw.svg                 |    1 
 src/views/device/lock/index.vue              |  152 ++++++++++
 .env.build_prod                              |    2 
 src/icons/svg/key-hdw.svg                    |    2 
 18 files changed, 819 insertions(+), 15 deletions(-)

diff --git a/.env.build_prod b/.env.build_prod
index a368e4c..24d10ff 100644
--- a/.env.build_prod
+++ b/.env.build_prod
@@ -2,4 +2,4 @@
 NODE_ENV = 'production'
 
 # 鎵撳寘妯″紡杩炵敓浜ф湇
-VUE_APP_BASE_API = '/api-prod'
\ No newline at end of file
+VUE_APP_BASE_API = '/'
diff --git a/.env.dev_prod b/.env.dev_prod
index bb64344..87cce62 100644
--- a/.env.dev_prod
+++ b/.env.dev_prod
@@ -2,5 +2,5 @@
 NODE_ENV = 'development'
 
 # 寮�鍙戞ā寮忚繛鐢熶骇鏈�
-VUE_APP_BASE_API = '/api-prod'
-VUE_APP_COOKIE = ''
\ No newline at end of file
+VUE_APP_BASE_API = '/'
+VUE_APP_COOKIE = ''
diff --git a/components.d.ts b/components.d.ts
index be9f069..015ae34 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -13,12 +13,13 @@
     DropdownMenu: typeof import('./src/components/Share/DropdownMenu.vue')['default']
     Dropzone: typeof import('./src/components/Dropzone/index.vue')['default']
     EditorImage: typeof import('./src/components/Tinymce/components/EditorImage.vue')['default']
+    ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
     ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
-    ElCard: typeof import('element-plus/es')['ElCard']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
+    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@@ -29,18 +30,22 @@
     ElInput: typeof import('element-plus/es')['ElInput']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
-    ElProgress: typeof import('element-plus/es')['ElProgress']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
-    ElTag: typeof import('element-plus/es')['ElTag']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElTree: typeof import('element-plus/es')['ElTree']
     ErrorLog: typeof import('./src/components/ErrorLog/index.vue')['default']
     GithubCorner: typeof import('./src/components/GithubCorner/index.vue')['default']
     Hamburger: typeof import('./src/components/Hamburger/index.vue')['default']
+    HdwCard: typeof import('./src/components/HdwCard/index.vue')['default']
+    HdwTree: typeof import('./src/components/HdwTree/index.vue')['default']
     HeaderSearch: typeof import('./src/components/HeaderSearch/index.vue')['default']
     ImageCropper: typeof import('./src/components/ImageCropper/index.vue')['default']
     Keyboard: typeof import('./src/components/Charts/Keyboard.vue')['default']
diff --git a/mock/demo/user.js b/mock/demo/user.js
index 2fe385e..e9ecb00 100644
--- a/mock/demo/user.js
+++ b/mock/demo/user.js
@@ -26,7 +26,7 @@
 export default [
   // user login
   {
-    url: '/vue-element-admin/user/login',
+    url: '/user/login',
     method: 'post',
     response: config => {
       const { username } = config.body;
diff --git a/src/api/user.js b/src/api/user.js
index 37bc37f..a23c066 100644
--- a/src/api/user.js
+++ b/src/api/user.js
@@ -2,7 +2,7 @@
 
 export function login(data) {
   return request({
-    url: '/vue-element-admin/user/login',
+    url: '/user/login',
     method: 'post',
     data
   });
diff --git a/src/assets/images/user.png b/src/assets/images/user.png
new file mode 100644
index 0000000..e16488e
--- /dev/null
+++ b/src/assets/images/user.png
Binary files differ
diff --git a/src/components/HdwCard/index.vue b/src/components/HdwCard/index.vue
new file mode 100644
index 0000000..17d8a2a
--- /dev/null
+++ b/src/components/HdwCard/index.vue
@@ -0,0 +1,57 @@
+<script lang="ts">
+import { defineComponent } from 'vue';
+
+export default defineComponent({
+  name: 'HdwCard',
+  props: {
+    isFull: {
+      type: Boolean,
+      default: false
+    },
+    title: {
+      type: String,
+      default: ''
+    }
+  }
+});
+</script>
+
+<template>
+  <div class="hdw-card" :class="[isFull && 'is-full', title && 'is-has-title']">
+    <div v-if="title" class="hdw-card-title">
+      {{title}}
+    </div>
+    <div class="hdw-card-content">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.hdw-card {
+  box-shadow: 0 0 12px rgba(0,0,0,0.2);
+  padding: 8px;
+  border-radius: 4px;
+  &.is-full {
+    height: 100%;
+    .hdw-card-content {
+      padding-top: 8px;
+      height: 100%;
+    }
+    &.is-has-title {
+      .hdw-card-content {
+        height: calc(100% - 30px);
+      }
+    }
+  }
+
+  .hdw-card-title {
+    margin-left: -8px;
+    margin-right: -8px;
+    padding: 4px 16px;
+    border-bottom: 1px solid rgba(128, 128, 128, 0.2);
+    font-weight: 700;
+    font-size: 14px;
+  }
+}
+</style>
diff --git a/src/components/HdwTree/index.vue b/src/components/HdwTree/index.vue
new file mode 100644
index 0000000..9dba1fa
--- /dev/null
+++ b/src/components/HdwTree/index.vue
@@ -0,0 +1,69 @@
+<script lang="ts">
+import { defineComponent } from 'vue';
+
+export default defineComponent({
+  name: 'HdwTree',
+  data() {
+    return {
+      defaultProps: {
+        children: 'children',
+        label: 'label'
+      },
+      data: [
+        {
+          label: '娌冲崡鐪�',
+          children: [
+            {
+              label: '椹婚┈搴楀競',
+              children: [
+                {
+                  label: '涓婅敗鍘�'
+                },
+                {
+                  label: '纭北鍘�'
+                }
+              ]
+            }
+          ]
+        },
+        {
+          label: '婀栧寳鐪�',
+          children: [
+            {
+              label: '姝︽眽甯�',
+              children: [
+                {
+                  label: '涓滆タ婀栧尯'
+                },
+                {
+                  label: '闈掑北鍖�'
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    };
+  },
+  methods: {
+    handleNodeClick(data) {
+      console.log(data);
+    }
+  }
+});
+</script>
+
+<template>
+  <el-scrollbar>
+    <el-tree
+        style="max-width: 600px"
+        :data="data"
+        :props="defaultProps"
+        @node-click="handleNodeClick"
+    />
+  </el-scrollbar>
+</template>
+
+<style scoped lang="scss">
+
+</style>
diff --git a/src/icons/svg/key-hdw.svg b/src/icons/svg/key-hdw.svg
index f5ecd94..57f0e35 100644
--- a/src/icons/svg/key-hdw.svg
+++ b/src/icons/svg/key-hdw.svg
@@ -1 +1 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1728439454077" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="865" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M632.527307 498.500942q39.532957 14.463277 72.316384 39.532957t56.888889 58.335217 37.122411 73.280603 13.016949 83.404896q0 56.888889-21.212806 106.06403t-58.335217 85.815443-86.297552 57.853107-105.099812 21.212806-105.099812-21.212806-86.297552-57.853107-58.335217-85.815443-21.212806-106.06403q0-44.354049 13.499058-84.369115t37.60452-73.280603 57.370998-58.335217 72.798493-39.532957l0-404.971751q0-17.355932 6.26742-33.747646t17.838041-29.408663 27.480226-21.212806 34.229755-8.195857 35.676083 8.195857 30.854991 21.212806 21.212806 29.408663 7.713748 33.747646l0 405.93597zM542.854991 846.583804q38.568738 0 65.566855-26.998117t26.998117-66.531073q0-38.568738-26.998117-65.566855t-65.566855-26.998117q-39.532957 0-66.531073 26.998117t-26.998117 65.566855q0 39.532957 26.998117 66.531073t66.531073 26.998117zM751.126177 121.491525q61.709981 0 61.709981 57.853107l0 62.6742q0 33.747646-16.873823 46.764595t-37.122411 13.016949l-66.531073 0 0-180.308851 58.817326 0z" p-id="866"></path></svg>
\ No newline at end of file
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1728524053330" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1585" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M446.680407 513.704726h136.078739v510.295274h-136.078739z" p-id="1586"></path><path d="M532.410013 666.793308h115.666928v136.07874H532.410013zM532.410013 843.69567h149.686613v136.07874H532.410013zM511.998202 585.146065A290.528109 290.528109 0 0 1 219.428912 292.576775 290.528109 290.528109 0 0 1 511.998202 0.007484a290.528109 290.528109 0 0 1 292.56929 292.569291 290.528109 290.528109 0 0 1-292.56929 292.56929z m0-449.059841A153.768976 153.768976 0 0 0 355.507651 292.576775a153.768976 153.768976 0 0 0 156.490551 156.49055 153.768976 153.768976 0 0 0 156.49055-156.49055A153.768976 153.768976 0 0 0 511.998202 136.086224z" p-id="1587"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/people-hdw.svg b/src/icons/svg/people-hdw.svg
new file mode 100644
index 0000000..107944f
--- /dev/null
+++ b/src/icons/svg/people-hdw.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1728459304110" class="icon" viewBox="0 0 1028 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1466" xmlns:xlink="http://www.w3.org/1999/xlink" width="128.5" height="128"><path d="M815.814506 299.350645c0 165.306834-134.011812 299.350645-299.350645 299.350645s-299.350645-134.011812-299.350645-299.350645c0-165.306834 134.011812-299.350645 299.350645-299.350645s299.350645 134.011812 299.350645 299.350645z" p-id="1467"></path><path d="M763.52814 612.780851c-69.75782 55.070279-156.219118 85.661323-247.064279 85.661323-91.901128 0-179.1944-31.295022-249.27221-87.421268-184.698228 67.805881-267.19165 304.758476-267.19165 412.979094l1027.711884 0c0-107.260648-83.133402-342.549295-264.183744-411.18715z" p-id="1468"></path></svg>
\ No newline at end of file
diff --git a/src/icons/svg/safe-lock-hdw.svg b/src/icons/svg/safe-lock-hdw.svg
new file mode 100644
index 0000000..c35e18e
--- /dev/null
+++ b/src/icons/svg/safe-lock-hdw.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1728521944946" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1525" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M514.062027 1018.052384h-12.821041c-57.694685-18.936986-230.764712-113.621918-307.690959-195.682192-76.912219-88.372603-115.375342-170.432877-115.375342-265.117808v-378.739726c0-12.624658 6.410521-25.249315 19.217534-31.561644l403.848767-138.871233h19.217535l397.424219 138.871233c12.821041 6.312329 19.231562 18.936986 19.231561 31.561644v366.115068c0 100.99726-32.038575 183.057534-115.375342 265.117808-70.515726 94.684932-243.585753 189.369863-294.855891 208.30685h-12.821041z m161.52548-586.906302h-35.629589v-80.938082c0-67.766356-56.348055-122.82389-125.713534-122.82389-69.295342 0-125.727562 55.127671-125.727562 122.837917v80.938083h-35.629589c-17.099397 0-31.014575 13.606575-31.014575 30.299178v221.548712c0 16.776767 13.915178 30.383342 31.014575 30.383342h322.700274c17.169534 0 31.028603-13.606575 31.028603-30.383342V461.459288c0.070137-16.692603-13.859068-30.299178-31.028603-30.299178zM531.736548 578.026959v48.352438a4.067945 4.067945 0 0 1-4.081973 3.983781h-26.694137a4.067945 4.067945 0 0 1-4.067945-3.983781v-48.352438c-12.568548-6.256219-21.251507-18.852822-21.251507-33.52548 0-20.830685 17.295781-37.789808 38.673535-37.789808 21.363726 0 38.659507 16.959123 38.659506 37.775781 0 14.686685-8.668932 27.283288-21.237479 33.539507z m60.03726-146.866849H436.841205V352.227945c0-41.773589 34.787945-75.747945 77.529425-75.747945 42.755507 0 77.473315 33.974356 77.473315 75.747945l-0.070137 78.90411z" p-id="1526"></path></svg>
\ No newline at end of file
diff --git a/src/router/modules/system.ts b/src/router/modules/system.ts
index 51459b6..ccd81a2 100644
--- a/src/router/modules/system.ts
+++ b/src/router/modules/system.ts
@@ -20,7 +20,7 @@
       path: 'user',
       component: () => import('@/views/system/user/index.vue'),
       name: 'UserManage',
-      meta: { title: '鐢ㄦ埛绠$悊', icon: 'people', noCache: false }
+      meta: { title: '鐢ㄦ埛绠$悊', icon: 'people-hdw', noCache: false }
     }
   ]
 };
diff --git a/src/utils/resize.js b/src/utils/resize.js
new file mode 100644
index 0000000..4126fb0
--- /dev/null
+++ b/src/utils/resize.js
@@ -0,0 +1,56 @@
+import { defineComponent } from 'vue';
+import { debounce } from '@/utils';
+
+export default defineComponent({
+  data() {
+    return {
+      sidebarElm: null,
+      resizeHandler: null
+    };
+  },
+  mounted() {
+    this.resizeHandler = debounce(() => {
+      if (this.chart) {
+        this.chart.resize();
+      }
+    }, 100);
+    this.initResizeEvent();
+    this.initSidebarResizeEvent();
+  },
+  beforeUnmount() {
+    this.destroyResizeEvent();
+    this.destroySidebarResizeEvent();
+  },
+  // to fixed bug when cached by keep-alive
+  // https://github.com/PanJiaChen/vue-element-admin/issues/2116
+  activated() {
+    this.initResizeEvent();
+    this.initSidebarResizeEvent();
+  },
+  deactivated() {
+    this.destroyResizeEvent();
+    this.destroySidebarResizeEvent();
+  },
+  methods: {
+    // do not use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    initResizeEvent() {
+      window.addEventListener('resize', this.resizeHandler);
+    },
+    destroyResizeEvent() {
+      window.removeEventListener('resize', this.resizeHandler);
+    },
+    sidebarResizeHandler(e) {
+      if (e.propertyName === 'width') {
+        this.resizeHandler();
+      }
+    },
+    initSidebarResizeEvent() {
+      this.sidebarElm = document.getElementsByClassName('sidebar-container')[0];
+      this.sidebarElm && this.sidebarElm.addEventListener('transitionend', this.sidebarResizeHandler);
+    },
+    destroySidebarResizeEvent() {
+      this.sidebarElm && this.sidebarElm.removeEventListener('transitionend', this.sidebarResizeHandler);
+    }
+  }
+});
diff --git a/src/views/dashboard/components/LineChart.vue b/src/views/dashboard/components/LineChart.vue
new file mode 100644
index 0000000..ebc8545
--- /dev/null
+++ b/src/views/dashboard/components/LineChart.vue
@@ -0,0 +1,119 @@
+<template>
+  <div :class="className" :style="{ height: height, width: width }" />
+</template>
+
+<script>
+import * as echarts from 'echarts';
+import { defineComponent, shallowRef } from 'vue';
+import resize from '@/utils/resize';
+
+export default defineComponent({
+  name: 'LineChart',
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '100%'
+    },
+    autoResize: {
+      type: Boolean,
+      default: true
+    },
+    chartData: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      chart: null
+    };
+  },
+  watch: {
+    chartData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val);
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart();
+    });
+  },
+  beforeUnmount() {
+    if (!this.chart) {
+      return;
+    }
+    this.chart.dispose();
+    this.chart = null;
+  },
+  methods: {
+    initChart() {
+      this.chart = shallowRef(echarts.init(this.$el));
+      this.setOptions(this.chartData);
+    },
+    setOptions() {
+      this.chart.setOption({
+        grid: {
+          top: 20,
+          right: 20,
+          bottom: 20,
+          left: 20,
+          containLabel: true
+        },
+        xAxis: {
+          type: 'category',
+          boundaryGap: false,
+          splitLine: {
+            show: true,
+            lineStyle: {
+              width: 1
+            }
+          }
+        },
+        yAxis: {
+          type: 'value',
+          axisLine: {
+            show: true
+          },
+          splitLine: {
+            show: true,
+            lineStyle: {
+              width: 1,
+              type: 'dashed'
+            }
+          }
+        },
+        series: [
+          {
+            type: 'line',
+            smooth: 0.6,
+            symbol: 'none',
+            data: [
+              ['2019-10-10', 2],
+              ['2019-10-11', 5],
+              ['2019-10-12', 7],
+              ['2019-10-13', 5],
+              ['2019-10-14', 2],
+              ['2019-10-15', 3],
+              ['2019-10-16', 4],
+              ['2019-10-17', 3],
+              ['2019-10-18', 1]
+            ]
+          }
+        ]
+      });
+    }
+  }
+});
+</script>
diff --git a/src/views/dashboard/components/MenuCard.vue b/src/views/dashboard/components/MenuCard.vue
new file mode 100644
index 0000000..54c2502
--- /dev/null
+++ b/src/views/dashboard/components/MenuCard.vue
@@ -0,0 +1,56 @@
+<script lang="ts">
+import { defineComponent } from 'vue';
+import SvgIcon from '@/components/SvgIcon/index.vue';
+
+export default defineComponent({
+  name: 'MenuCard',
+  components: { SvgIcon },
+  props: {
+    title: {
+      type: String,
+      default: '鍖哄煙绠$悊'
+    },
+    icon: {
+      type: String,
+      default: 'tree'
+    },
+    url: {
+      type: String,
+      default: '/system/region'
+    }
+  },
+  data() {
+    return {};
+  },
+  methods: {
+    changeRouter() {
+      this.$router.push(this.url);
+    }
+  }
+});
+</script>
+
+<template>
+  <div class="menu-card" @click.prevent="changeRouter">
+    <svg-icon style="font-size: 40px;" :icon-class="icon"></svg-icon>
+    <br />
+    <br />
+    <span class="menu-title">{{ title }}</span>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.menu-card {
+  display: inline-block;
+  color: #F6F6F6;
+  background-color: #9f80f5;
+  text-align: center;
+  min-width: 160px;
+  padding: 8px 16px;
+  border-radius: 4px;
+  cursor: pointer;
+  .menu-title {
+    font-weight: 700;
+  }
+}
+</style>
diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue
index f6cce01..6e7da68 100644
--- a/src/views/dashboard/index.vue
+++ b/src/views/dashboard/index.vue
@@ -1,18 +1,307 @@
 <template>
-  <div class="dashboard-container"></div>
+  <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>
+
+    </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>
+              </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>
+            </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 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>
+          </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>
+          </hdw-card>
+        </el-col>
+      </el-row>
+    </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'
+  name: 'Dashboard',
+  components: { LineChart, SvgIcon, HdwCard, MenuCard },
+  data() {
+    return {
+      unlockingData: [],
+      userImg,
+      timeType: 0,
+      rangeTime: ''
+    };
+  }
 });
 </script>
 
-<style scoped>
+<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 {
+  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;
 }
 </style>
diff --git a/src/views/device/lock/index.vue b/src/views/device/lock/index.vue
index 0125370..4305d58 100644
--- a/src/views/device/lock/index.vue
+++ b/src/views/device/lock/index.vue
@@ -1,15 +1,163 @@
 <script lang="ts">
 import { defineComponent } from 'vue';
+import HdwCard from '@/components/HdwCard/index.vue';
 
 export default defineComponent({
-  name: 'LockManage'
+  name: 'LockManage',
+  components: { HdwCard },
+  data() {
+    return {
+      tableData: [
+        {
+          type: '钃濈墮',
+          lockName: '鏈烘煖钃濈墮閿�3',
+          associatedArea: '姝︽眽婧愮晠绉戞妧',
+          installState: '鏈畨瑁�'
+        }
+      ],
+      page: {
+        currentPage: 1,
+        pageSize: 20,
+        total: 0
+      }
+    };
+  },
+  methods: {
+
+  }
 });
 </script>
 
+<script lang="ts" setup>
+import {
+  Search,
+  Plus
+} from '@element-plus/icons-vue';
+import HdwTree from '@/components/HdwTree/index.vue';
+</script>
+
 <template>
-  <div>閿佸叿绠$悊</div>
+  <div class="lock-wrapper">
+    <div class="lock-header">
+      <div class="hdw-card-container">
+        <hdw-card title="鍖哄煙鍒楄〃" is-full>
+          <hdw-tree></hdw-tree>
+        </hdw-card>
+      </div>
+    </div>
+    <div class="lock-content">
+      <hdw-card is-full>
+        <div class="lock-content-wrapper">
+          <div class="lock-content-tools">
+            <el-button type="primary" size="small" :icon="Search">鏌ヨ</el-button>
+            <el-button type="success" size="small" :icon="Plus">娣诲姞</el-button>
+            <el-button type="success" size="small" :icon="Plus">鎵归噺娣诲姞</el-button>
+            <div class="tools-filter">
+              <div class="tools-filter-item">
+                <div class="filter-label"></div>
+                <div class="filter-content"></div>
+              </div>
+            </div>
+          </div>
+          <div class="lock-content-table">
+            <div class="pos-rel">
+              <div class="pos-abs">
+                <el-table :data="tableData" border style="width: 100%; height: 100%">
+                  <el-table-column type="index" width="50" />
+                  <el-table-column prop="type" label="绫诲瀷" width="180" />
+                  <el-table-column prop="lockName" label="閿佸叿鍚嶇О" width="180" />
+                  <el-table-column prop="associatedArea" label="鍏宠仈鍖哄煙" width="180" />
+                  <el-table-column prop="installState" label="瀹夎鐘舵��" width="180" />
+                  <el-table-column prop="lockImg" label="閿佸叿鍥剧墖" width="180" />
+                  <el-table-column prop="address" label="鍦板潃" />
+                  <el-table-column align="center" fixed="right" label="鎿嶄綔" width="180">
+                    <template #default>
+                      <el-button type="primary" size="small">缂栬緫</el-button>
+                      <el-button type="danger" size="small">鍒犻櫎</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </div>
+            </div>
+          </div>
+          <div class="lock-content-page">
+            <div class="page-tool"></div>
+            <div class="el-page-container">
+              <el-pagination
+                  v-model:current-page="page.currentPage"
+                  v-model:page-size="page.pageSize"
+                  :page-sizes="[20, 40, 60, 80, 100, 200, 300, 400]"
+                  size="small"
+                  :disabled="disabled"
+                  :background="background"
+                  layout="total, sizes, prev, pager, next, jumper"
+                  :total="page.total"
+                  @size-change="handleSizeChange"
+                  @current-change="handleCurrentChange"
+              />
+            </div>
+            <div class="page-tool"></div>
+          </div>
+        </div>
+      </hdw-card>
+    </div>
+    <div class="lock-footer"></div>
+  </div>
 </template>
 
 <style scoped lang="scss">
+.lock-wrapper {
+  display: flex;
+  flex-direction: row;
+  padding: 8px;
+  height: 100%;
+  .lock-content {
+    flex: 1;
+  }
+}
 
+.lock-content-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  .lock-content-tools {
+    padding-bottom: 8px;
+  }
+  .lock-content-table {
+    flex: 1;
+    margin-left: -8px;
+    margin-right: -8px;
+  }
+  .lock-content-page {
+    padding: 8px 8px 0 8px;
+    text-align: center;
+    .el-page-container {
+      display: inline-block;
+      padding: 0 16px;
+    }
+    .page-tool {
+      display: inline-block;
+    }
+  }
+}
+
+.hdw-card-container {
+  width: 320px;
+  padding-right: 8px;
+  height: 100%;
+}
+.pos-rel {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  .pos-abs {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
 </style>
diff --git a/vite.config.ts b/vite.config.ts
index 9b74277..ef69852 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -24,6 +24,9 @@
 
   return {
     base: '/', // 娉ㄦ剰锛屽繀椤讳互"/"缁撳熬锛孊ASE_URL閰嶇疆
+    build: {
+      outDir: '../hm-lock-sys/public'
+    },
     define: {
       'process.env': env
     },

--
Gitblit v1.9.1