diff --git a/.browserslistrc b/.browserslistrc
new file mode 100644
index 0000000000000000000000000000000000000000..214388fe43cdfd7ce1c29cd3e401541ded620dba
--- /dev/null
+++ b/.browserslistrc
@@ -0,0 +1,3 @@
+> 1%
+last 2 versions
+not dead
diff --git a/.env.development b/.env.development
new file mode 100644
index 0000000000000000000000000000000000000000..cf089272862e8b8cdf2dcf55cd9b698aa1c54ea6
--- /dev/null
+++ b/.env.development
@@ -0,0 +1,6 @@
+# just a flag
+ENV = 'development'
+
+# base api
+VUE_APP_BASE_API = '/dev-api'
+VUE_APP_REMOTE_URL = '/'
\ No newline at end of file
diff --git a/.env.production b/.env.production
new file mode 100644
index 0000000000000000000000000000000000000000..631d57a6d855cbc661aa90875c09afbe7024ac19
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,5 @@
+# just a flag
+ENV = 'production'
+
+# base api
+VUE_APP_BASE_API = '/'
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000000000000000000000000000000000000..dd4b0c2b5ba30d4cf449b77fda58fcb1b55e608e
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,14 @@
+module.exports = {
+  root: true,
+  env: {
+    node: true
+  },
+  extends: ["plugin:vue/essential", "eslint:recommended"],
+  parserOptions: {
+    parser: "babel-eslint"
+  },
+  rules: {
+    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
+    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
+  }
+};
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..403adbc1e527906a4aa59558cd582c20bcd1d738
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+.DS_Store
+node_modules
+/dist
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/README.md b/README.md
index 34f07e23d1a97c9a19cb5e7669acd9851f61024c..fd3e0ecd1c745d8a1d82bf2fe18c6bd7d67ca7ce 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,40 @@
 # CountingDreams
 
-鏁版ⅵ鏅烘収涓ゆ睙
\ No newline at end of file
+鏁版ⅵ鏅烘収涓ゆ睙
+
+# vue2.x 妯℃澘浠撳簱
+
+妯℃澘浠撳簱
+
+- [x] vuex 鑷姩娣诲姞妯″潡锛歴tore 涓嬫ā鍧楁枃浠跺す index.js
+- [x] axios
+- [x] vue-router 鑷姩娣诲姞璺敱锛歷iews 鏂囦欢澶逛笅姣忎竴涓枃浠跺す涓嬮兘 index.vue,宓屽璺敱涓嬪繀椤绘湁 index.vue router-view
+- [x] sass && scss
+
+## Project setup
+
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+
+```
+npm run build
+```
+
+### Lints and fixes files
+
+```
+npm run lint
+```
+
+### Customize configuration
+
+See [Configuration Reference](https://cli.vuejs.org/config/).
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..397abca88c5a451f06a407b93469db551efaa6de
--- /dev/null
+++ b/babel.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+  presets: ["@vue/cli-plugin-babel/preset"]
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..aee0ddfb36dec4488837f87ee0e2e1954cb9e32c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,52 @@
+{
+  "name": "templat",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "axios": "^0.21.0",
+    "core-js": "^3.6.5",
+    "dayjs": "^1.9.7",
+    "echarts": "^5.4.3",
+    "element-ui": "^2.14.0",
+    "gho-menu": "^1.0.1",
+    "loadash": "^1.0.0",
+    "mockjs": "^1.1.0",
+    "vue": "^2.6.11",
+    "vue-functional-data-merge": "^3.1.0",
+    "vue-router": "^3.2.0",
+    "vuex": "^3.4.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-plugin-eslint": "~4.5.0",
+    "@vue/cli-plugin-router": "~4.5.0",
+    "@vue/cli-plugin-vuex": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "@vue/eslint-config-prettier": "^6.0.0",
+    "babel-eslint": "^10.1.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-prettier": "^3.1.3",
+    "eslint-plugin-vue": "^6.2.2",
+    "lint-staged": "^9.5.0",
+    "node-sass": "^6.0.0",
+    "prettier": "^1.19.1",
+    "sass-loader": "^10.2.0",
+    "script-ext-html-webpack-plugin": "^2.1.5",
+    "vue-template-compiler": "^2.6.11",
+    "webpack-bundle-analyzer": "^4.1.0"
+  },
+  "gitHooks": {
+    "pre-commit": "lint-staged"
+  },
+  "lint-staged": {
+    "*.{js,jsx,vue}": [
+      "vue-cli-service lint",
+      "git add"
+    ]
+  }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..2e3f165ad40bd20c6b905931a3d27eff125df10d
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+    <script src="http://at.alicdn.com/t/font_2273126_oy1g7zvosu.js"></script>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>
diff --git a/public/js/dept.json b/public/js/dept.json
new file mode 100644
index 0000000000000000000000000000000000000000..96c01cd4c1db2e564a89725da485c49d8b74ee57
--- /dev/null
+++ b/public/js/dept.json
@@ -0,0 +1,24 @@
+{
+	"绉戝鏁版嵁": "",
+	"state": true,
+	"message": "SUCCESS",
+	"body": [
+		{
+			"name": "鑲惧唴绉�",
+			"id": 1
+		},
+		{
+			"name": "鍛煎惛鍐呯",
+			"id": 2
+		},
+		{
+			"name": "鏅绉�",
+			"id": 3
+		},
+		{
+			"name": "娉屽翱澶栫",
+			"id": 4
+		}
+	],
+	"cause": null
+}
diff --git a/public/js/event.json b/public/js/event.json
new file mode 100644
index 0000000000000000000000000000000000000000..2747b9a730158c6056c74bf26d3802012feafcf0
--- /dev/null
+++ b/public/js/event.json
@@ -0,0 +1,64 @@
+{
+	"浜嬩欢鏁版嵁": "",
+	"state": true,
+	"message": "SUCCESS",
+	"body": [
+		{
+			"name": "澶氳仈鎶楃敓绱犱娇鐢�",
+			"id": 1
+		},
+		{
+			"name": "鐗规畩鎶楃敓绱犱娇鐢�",
+			"id": 2
+		},
+		{
+			"name": "鎶㈡晳",
+			"id": 3
+		},
+		{
+			"name": "閲嶅ぇ鍙婄壒娈婃不鐤�",
+			"id": 4
+		},
+		{
+			"name": "鐗规畩绫诲垏鍙f姉鑿岀礌浣跨敤",
+			"id": 5
+		},
+		{
+			"name": "瓒呮湡浣跨敤鎶楄弻绱�",
+			"id": 6
+		},
+		{
+			"name": "鍗辨€ュ€间汉鏁�",
+			"id": 7
+		},
+		{
+			"name": "鍗遍噸鎮h€�",
+			"id": 8
+		},
+		{
+			"name": "杞嵄鎮h€�",
+			"id": 9
+		},
+		{
+			"name": "閲嶇偣鐤剧梾",
+			"id": 10
+		},
+		{
+			"name": "闄㈠唴鎰熸煋",
+			"id": 11
+		},
+		{
+			"name": "浣忛櫌瓒呰繃30澶╀汉鏁�",
+			"id": 12
+		},
+		{
+			"name": "浼犳煋鐥�",
+			"id": 13
+		},
+		{
+			"name": "姝讳骸鎮h€�",
+			"id": 14
+		}
+	],
+	"cause": null
+}
diff --git a/public/js/keyDiseases.json b/public/js/keyDiseases.json
new file mode 100644
index 0000000000000000000000000000000000000000..2f71d66170ce9ea9cc0ae6488761fd5438c5cfec
--- /dev/null
+++ b/public/js/keyDiseases.json
@@ -0,0 +1,206 @@
+{
+	"閲嶇偣鐤剧梾鏁版嵁": "",
+	"state": true,
+	"message": "SUCCESS",
+	"body": {
+		"琛ㄥご鏁版嵁": "",
+		"headers": [
+			{
+				"field": "index",
+				"dataField": "index",
+				"fieldName": "搴忓彿",
+				"fixed": true,
+				"sorted": false,
+				"index": 1
+			},
+			{
+				"field": "name",
+				"dataField": "name",
+				"fieldName": "濮撳悕",
+				"fixed": true,
+				"sorted": false,
+				"index": 2
+			},
+			{
+				"field": "sex",
+				"dataField": "sex",
+				"fieldName": "鎬у埆",
+				"fixed": true,
+				"sorted": false,
+				"index": 3
+			},
+			{
+				"field": "age",
+				"dataField": "age",
+				"fieldName": "骞撮緞",
+				"fixed": true,
+				"sorted": false,
+				"index": 4
+			},
+			{
+				"field": "rtime",
+				"dataField": "rtime",
+				"fieldName": "鍏ラ櫌鏃ユ湡",
+				"fixed": true,
+				"sorted": false,
+				"index": 5
+			},
+			{
+				"field": "ctime",
+				"dataField": "ctime",
+				"fieldName": "鍑洪櫌鏃ユ湡",
+				"fixed": true,
+				"sorted": false,
+				"index": 6
+			},
+			{
+				"field": "rdept",
+				"dataField": "rdept",
+				"fieldName": "鍏ラ櫌绉戝",
+				"fixed": true,
+				"sorted": false,
+				"index": 7
+			},
+			{
+				"field": "cdept",
+				"dataField": "cdept",
+				"fieldName": "鍑洪櫌鏃ユ湡",
+				"fixed": true,
+				"sorted": false,
+				"index": 8
+			},
+			{
+				"field": "infoName",
+				"dataField": "infoName",
+				"fieldName": "璇婃柇鍚嶇О",
+				"fixed": true,
+				"sorted": false,
+				"index": 11
+			},
+			{
+				"field": "infoTime",
+				"dataField": "infoTime",
+				"fieldName": "璇婃柇鏃堕棿",
+				"fixed": true,
+				"sorted": false,
+				"index": 10
+			},
+			{
+				"field": "doctor",
+				"dataField": "doctor",
+				"fieldName": "绠″簥鍖荤敓",
+				"fixed": true,
+				"sorted": false,
+				"index": 12
+			}
+		],
+		"pages": 1,
+		"total": 1,
+		"pageNum": 1,
+		"pageSize": 20,
+		"list": [
+			{
+				"index": "1",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"infoTime": "2020-06-25",
+				"infoName": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "2",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"infoTime": "2020-06-25",
+				"infoName": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "3",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"infoTime": "2020-06-25",
+				"infoName": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "4",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"infoTime": "2020-06-25",
+				"infoName": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "5",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"infoTime": "2020-06-25",
+				"infoName": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "6",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"infoTime": "2020-06-25",
+				"infoName": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "7",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"infoTime": "2020-06-25",
+				"infoName": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			}
+		],
+		"prePage": 0,
+		"nextPage": 0,
+		"extData": {}
+	},
+	"cause": null
+}
diff --git a/public/js/warningPatient.json b/public/js/warningPatient.json
new file mode 100644
index 0000000000000000000000000000000000000000..96e1d1f76803cb4e5dcff240883edb4287a98d61
--- /dev/null
+++ b/public/js/warningPatient.json
@@ -0,0 +1,214 @@
+{
+	"鍗遍噸鎮h€呮暟鎹�": "",
+	"state": true,
+	"message": "SUCCESS",
+	"body": {
+		"琛ㄥご鏁版嵁": "",
+		"headers": [
+			{
+				"field": "index",
+				"dataField": "index",
+				"fieldName": "搴忓彿",
+				"fixed": true,
+				"sorted": false,
+				"index": 1
+			},
+			{
+				"field": "name",
+				"dataField": "name",
+				"fieldName": "濮撳悕",
+				"fixed": true,
+				"sorted": false,
+				"index": 2
+			},
+			{
+				"field": "sex",
+				"dataField": "sex",
+				"fieldName": "鎬у埆",
+				"fixed": true,
+				"sorted": false,
+				"index": 3
+			},
+			{
+				"field": "age",
+				"dataField": "age",
+				"fieldName": "骞撮緞",
+				"fixed": true,
+				"sorted": false,
+				"index": 4
+			},
+			{
+				"field": "rtime",
+				"dataField": "rtime",
+				"fieldName": "鍏ラ櫌鏃ユ湡",
+				"fixed": true,
+				"sorted": false,
+				"index": 5
+			},
+			{
+				"field": "ctime",
+				"dataField": "ctime",
+				"fieldName": "鍑洪櫌鏃ユ湡",
+				"fixed": true,
+				"sorted": false,
+				"index": 6
+			},
+			{
+				"field": "rdept",
+				"dataField": "rdept",
+				"fieldName": "鍏ラ櫌绉戝",
+				"fixed": true,
+				"sorted": false,
+				"index": 7
+			},
+			{
+				"field": "cdept",
+				"dataField": "cdept",
+				"fieldName": "鍑洪櫌鏃ユ湡",
+				"fixed": true,
+				"sorted": false,
+				"index": 8
+			},
+			{
+				"field": "state",
+				"dataField": "state",
+				"fieldName": "鐘舵€�",
+				"fixed": true,
+				"sorted": false,
+				"index": 9
+			},
+			{
+				"field": "startTime",
+				"dataField": "startTime",
+				"fieldName": "寮€濮嬫椂闂�",
+				"fixed": true,
+				"sorted": false,
+				"index": 10
+			},
+			{
+				"field": "info",
+				"dataField": "info",
+				"fieldName": "璇婃柇",
+				"fixed": true,
+				"sorted": false,
+				"index": 11
+			},
+			{
+				"field": "doctor",
+				"dataField": "doctor",
+				"fieldName": "绠″簥鍖荤敓",
+				"fixed": true,
+				"sorted": false,
+				"index": 12
+			}
+		],
+		"pages": 1,
+		"total": 1,
+		"pageNum": 1,
+		"pageSize": 20,
+		"list": [
+			{
+				"index": "1",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"startTime": "2020-06-25",
+				"info": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "2",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"startTime": "2020-06-25",
+				"info": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "3",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"startTime": "2020-06-25",
+				"info": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "4",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"startTime": "2020-06-25",
+				"info": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "5",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"startTime": "2020-06-25",
+				"info": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "6",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"startTime": "2020-06-25",
+				"info": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			},
+			{
+				"index": "7",
+				"name": "璧垫檽宄�",
+				"sex": "鐢�",
+				"age": "42宀�",
+				"rtime": "2020-06-25",
+				"ctime": "2020-06-25",
+				"rdept": "鎬ヨ瘖绉�",
+				"cdept": "鎬ヨ瘖绉�",
+				"state": "绱ф€�",
+				"startTime": "2020-06-25",
+				"info": "鑵块儴楠ㄨ川鍧忔",
+				"doctor": "鐜嬫檽姊�"
+			}
+		],
+		"prePage": 0,
+		"nextPage": 0,
+		"extData": {}
+	},
+	"cause": null
+}
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8d4fcb1e3785296b322bc47f1135f8afdccd1157
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,124 @@
+<template>
+  <div id="app">
+    <InnerView v-if="!isLogin">
+      <AppHeader @changeCollapse="changeCollapse"></AppHeader>
+      <div class="main-view">
+        <SubHeader></SubHeader>
+        <div class="flex content-view">
+          <SideBar :isCollapse="isCollapse"></SideBar>
+          <div class="right-main">
+            <PaddingView class="flex-col">
+              <BreadCrumb ref="BreadCrumb"> </BreadCrumb>
+              <!-- <transition :name="transition"> -->
+              <keep-alive :include="cacheRoutes">
+                <router-view class="child-view"></router-view>
+              </keep-alive>
+              <!-- </transition> -->
+            </PaddingView>
+          </div>
+        </div>
+      </div>
+    </InnerView>
+    <InnerView v-else>
+      <router-view class="child-view"></router-view>
+    </InnerView>
+  </div>
+</template>
+<script>
+import { mapState } from "vuex";
+import {
+  // RouteView,
+  InnerView,
+} from "@/components/Layout";
+
+import { SideBar, AppHeader, SubHeader, BreadCrumb } from "@/components/App";
+export default {
+  name: "App",
+  components: {
+    SideBar,
+    AppHeader,
+    // RouteView,
+    InnerView,
+    SubHeader,
+    BreadCrumb,
+  },
+  data() {
+    return {
+      transition: "",
+      lastRouteIndex: 0,
+      isCollapse: false,
+    };
+  },
+  computed: {
+    ...mapState({
+      cacheRoutes: (state) => state.app.cacheRoutes,
+    }),
+    isLogin() {
+      return this.$route.path == "/login";
+    },
+  },
+  watch: {
+    $route(val) {
+      // console.log(val);
+      const index = val?.meta?.index || 0;
+      if (val.meta.index >= this.lastRouteIndex) {
+        this.transition = "right-slide-in";
+      } else {
+        this.transition = "left-slide-in";
+      }
+      this.lastRouteIndex = index;
+    },
+  },
+  methods: {
+    changeCollapse(val) {
+      this.isCollapse = val;
+    },
+  },
+};
+</script>
+
+<style lang="scss">
+* {
+  box-sizing: border-box;
+}
+
+html,
+body,
+#app {
+  margin: 0;
+  width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+
+  background: #f5f5f5;
+}
+
+#app {
+  display: flex;
+}
+.main-view {
+  display: flex;
+  flex-flow: column;
+  position: relative;
+  z-index: 2;
+  width: 1200px;
+  margin: 0 auto;
+  height: calc(100vh - 136px);
+  margin-top: 22px;
+  overflow: hidden;
+}
+.content-view {
+  width: 100%;
+  height: 100%;
+  overflow-y: hidden;
+  padding-top: 20px;
+}
+.right-main {
+  width: 100%;
+  margin-left: 18px;
+  border-radius: 8px;
+  background-color: #fff;
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+</style>
diff --git a/src/api/auth/index.js b/src/api/auth/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..fa0400e386c68bf5bbfde77e15ae92ff7b34fad7
--- /dev/null
+++ b/src/api/auth/index.js
@@ -0,0 +1,12 @@
+import request from "@/utils/request";
+
+export default {
+  //鑾峰彇鐢ㄦ埛淇℃伅
+  getUserInfo(params) {
+    return request({
+      url: "/mock/getUserInfo",
+      method: "get",
+      params
+    });
+  },
+};
\ No newline at end of file
diff --git a/src/api/common/index.js b/src/api/common/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..7c2d11b1d5948bfd855346dc4aca884eca162e76
--- /dev/null
+++ b/src/api/common/index.js
@@ -0,0 +1,20 @@
+import request from "@/utils/request";
+
+export default {
+  //鐧诲綍
+  login(params) {
+    return request({
+      url: "/mock/login",
+      method: "post",
+      params
+    });
+  },
+  //閫€鍑虹櫥褰�
+  loginOut(params) {
+    return request({
+      url: "/mock/loginOut",
+      method: "post",
+      params
+    });
+  },
+};
diff --git a/src/api/index.js b/src/api/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..6b7a9fe4b51ee9a78255d7ff7d3c89db218bf9b9
--- /dev/null
+++ b/src/api/index.js
@@ -0,0 +1,7 @@
+import common from "./common";
+import auth from "./auth";
+
+export {
+  common,
+  auth
+};
\ No newline at end of file
diff --git a/src/assets/css/app.scss b/src/assets/css/app.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b9eee026e3c43f9794d00d33c1f287b9ee68be76
--- /dev/null
+++ b/src/assets/css/app.scss
@@ -0,0 +1,171 @@
+@import "./variables.scss";
+@import "~element-ui/packages/theme-chalk/src/index";
+
+::-webkit-scrollbar {
+  /*婊氬姩鏉℃暣浣撴牱寮�*/
+  width: 6px;
+  /*楂樺鍒嗗埆瀵瑰簲妯珫婊氬姩鏉$殑灏哄*/
+  height: 2px;
+}
+
+::-webkit-scrollbar-thumb {
+  /*婊氬姩鏉¢噷闈㈠皬鏂瑰潡*/
+  border-radius: 10px;
+  box-shadow: inset 0 0 5px rgba(97, 184, 179, 0.1);
+  background: #2171FF;
+}
+
+::-webkit-scrollbar-track {
+  /*婊氬姩鏉¢噷闈㈣建閬�*/
+  box-shadow: inset 0 0 5px rgba(87, 175, 187, 0.1);
+  border-radius: 10px;
+  background: #ededed;
+}
+
+.flex {
+  display: flex;
+}
+
+.flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.flex-between {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.flex-end {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+}
+
+.hidden-text {
+  opacity: 0;
+}
+
+.flex-col {
+  display: flex;
+  flex-flow: column;
+}
+
+.content-title {
+  font-size: 12px;
+  line-height: 18px;
+  height: 18px;
+  padding-left: 15px;
+  position: relative;
+  color: #2171FF;
+  display: flex;
+  align-items: center;
+  margin-left: 14px;
+  margin-top: 20px;
+
+  &::before {
+    content: '';
+    left: 0;
+    position: absolute;
+    width: 4px;
+    height: 12px;
+    border-radius: 3px;
+    background: #3182FF;
+
+  }
+}
+
+.el-button--primary.is-plain {
+  background-color: transparent;
+  border-color: #2171FF;
+
+  &:hover {
+    background-color: #2171FF;
+    color: #fff;
+  }
+}
+
+.el-button--warning.is-plain {
+  background-color: transparent;
+  border-color: #FF8D1A;
+
+  &:hover {
+    // background-color: #FF8D1A;
+    // color: #fff;
+  }
+}
+
+.el-menu {
+
+  &.el-menu--horizontal {
+    background: transparent;
+    padding-left: 5px;
+    padding-top: 20px;
+    flex-shrink: 0;
+
+    .el-menu-item {
+      padding: 0 0;
+      margin-right: 100px;
+      height: 36px;
+      line-height: 36px;
+      border-bottom: none;
+
+      &::after {
+        content: '';
+        position: absolute;
+        bottom: 0;
+        left: 0;
+        width: 100%;
+        height: 4px;
+        background-color: #2171FF;
+        transition: all .3s;
+        opacity: 0;
+        border-radius: 2px;
+      }
+
+      &.is-active {
+        color: #2171FF;
+        border-bottom: none;
+        position: relative;
+
+        &::after {
+          opacity: 1;
+        }
+      }
+    }
+
+  }
+
+}
+
+.el-descriptions {
+  .el-descriptions__body {
+    background-color: transparent;
+    padding-top: 24px;
+    font-size: 12px;
+    color: #666;
+    line-height: 18px;
+  }
+
+  :not(.is-bordered) .el-descriptions-item__cell {
+    padding-bottom: 22px;
+  }
+
+}
+
+.el-input {
+  width: 325px;
+}
+
+.el-input__inner {
+  background-color: transparent;
+  padding: 0px 9px;
+}
+
+.el-input--small .el-input__inner {
+  height: 30px;
+  line-height: 30px;
+  border-radius: 1px;
+}
\ No newline at end of file
diff --git a/src/assets/css/mixin.scss b/src/assets/css/mixin.scss
new file mode 100644
index 0000000000000000000000000000000000000000..06fa06125834695b9ca9b679faaf46c574168c46
--- /dev/null
+++ b/src/assets/css/mixin.scss
@@ -0,0 +1,66 @@
+@mixin clearfix {
+  &:after {
+    content: "";
+    display: table;
+    clear: both;
+  }
+}
+
+@mixin scrollBar {
+  &::-webkit-scrollbar-track-piece {
+    background: #d3dce6;
+  }
+
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #99a9bf;
+    border-radius: 20px;
+  }
+}
+
+@mixin relative {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}
+
+@mixin pct($pct) {
+  width: #{$pct};
+  position: relative;
+  margin: 0 auto;
+}
+
+@mixin triangle($width, $height, $color, $direction) {
+  $width: $width/2;
+  $color-border-style: $height solid $color;
+  $transparent-border-style: $width solid transparent;
+  height: 0;
+  width: 0;
+
+  @if $direction==up {
+    border-bottom: $color-border-style;
+    border-left: $transparent-border-style;
+    border-right: $transparent-border-style;
+  }
+
+  @else if $direction==right {
+    border-left: $color-border-style;
+    border-top: $transparent-border-style;
+    border-bottom: $transparent-border-style;
+  }
+
+  @else if $direction==down {
+    border-top: $color-border-style;
+    border-left: $transparent-border-style;
+    border-right: $transparent-border-style;
+  }
+
+  @else if $direction==left {
+    border-right: $color-border-style;
+    border-top: $transparent-border-style;
+    border-bottom: $transparent-border-style;
+  }
+}
diff --git a/src/assets/css/variables.scss b/src/assets/css/variables.scss
new file mode 100644
index 0000000000000000000000000000000000000000..48c4f489c499c46e036eda3e5fc246d21070ab01
--- /dev/null
+++ b/src/assets/css/variables.scss
@@ -0,0 +1,177 @@
+/* 鏀瑰彉涓婚鑹插彉閲� */
+/* Color
+-------------------------- */
+/// color|1|Brand Color|0
+$--color-primary: #3182FF;
+/// color|1|Background Color|4
+$--color-white: #f9f9f9;
+/// color|1|Background Color|4
+$--color-black: #333333;
+$--color-primary-light-1: mix(
+  $--color-white,
+  $--color-primary,
+  10%
+); /* 53a8ff */
+$--color-primary-light-2: mix(
+  $--color-white,
+  $--color-primary,
+  20%
+); /* 66b1ff */
+$--color-primary-light-3: mix(
+  $--color-white,
+  $--color-primary,
+  30%
+); /* 79bbff */
+$--color-primary-light-4: mix(
+  $--color-white,
+  $--color-primary,
+  40%
+); /* 8cc5ff */
+$--color-primary-light-5: mix(
+  $--color-white,
+  $--color-primary,
+  50%
+); /* a0cfff */
+$--color-primary-light-6: mix(
+  $--color-white,
+  $--color-primary,
+  60%
+); /* b3d8ff */
+$--color-primary-light-7: mix(
+  $--color-white,
+  $--color-primary,
+  70%
+); /* c6e2ff */
+$--color-primary-light-8: mix(
+  $--color-white,
+  $--color-primary,
+  80%
+); /* d9ecff */
+$--color-primary-light-9: mix(
+  $--color-white,
+  $--color-primary,
+  90%
+); /* ecf5ff */
+/// color|1|Functional Color|1
+$--color-success: #67c23a;
+/// color|1|Functional Color|1
+$--color-warning: #FF8D1A;
+/// color|1|Functional Color|1
+$--color-danger: #f56c6c;
+/// color|1|Functional Color|1
+$--color-info: #dddfd4;
+
+$--color-success-light: mix($--color-white, $--color-success, 80%);
+$--color-warning-light: mix($--color-white, $--color-warning, 80%);
+$--color-danger-light: mix($--color-white, $--color-danger, 80%);
+$--color-info-light: mix($--color-white, $--color-info, 80%);
+
+$--color-success-lighter: mix($--color-white, $--color-success, 90%);
+$--color-warning-lighter: mix($--color-white, $--color-warning, 90%);
+$--color-danger-lighter: mix($--color-white, $--color-danger, 90%);
+$--color-info-lighter: mix($--color-white, $--color-info, 90%);
+/// color|1|Font Color|2
+$--color-text-primary: #303133;
+/// color|1|Font Color|2
+$--color-text-regular: #666666;
+/// color|1|Font Color|2
+$--color-text-secondary: #909399;
+/// color|1|Font Color|2
+$--color-text-placeholder: #c0c4cc;
+/// color|1|Border Color|3
+$--border-color-base: #dcdfe6;
+/// color|1|Border Color|3
+$--border-color-light: #e4e7ed;
+/// color|1|Border Color|3
+$--border-color-lighter: #ebeef5;
+/// color|1|Border Color|3
+$--border-color-extra-light: #f2f6fc;
+
+// Background
+/// color|1|Background Color|4
+$--background-color-base: #f5f7fa;
+
+/* 鏀瑰彉 icon 瀛椾綋璺緞鍙橀噺锛屽繀闇€ */
+$--font-path: "~element-ui/lib/theme-chalk/fonts";
+
+
+$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas,
+  'Liberation Mono', 'Courier New', monospace;
+
+$dialog-content-max-height: 70vh;
+
+$app-header-height: 40px;
+$app-router-view-margin: 6px;
+$app-router-view-padding: 20px;
+/* $app-header-height + router-view margin-top & margin-bottom 20px */
+$router-view-offset-height: $app-header-height + $app-router-view-margin * 2;
+
+
+$--color-text-primary: #333333;
+
+// base color
+$blue: #173e43;
+$light-blue: #3a71a8;
+$red: #c03639;
+$pink: #e65d6e;
+$green: #30b08f;
+$tiffany: #4ab7bd;
+$yellow: #fec171;
+$panGreen: #30b08f;
+
+$--color-blue: #1da1f2 !default;
+$--color-purple: #6c5ce7 !default;
+$--color-green: #0ad300 !default;
+$--color-blue-light: mix($--color-white, $--color-blue, 80%) !default;
+$--color-purple-light: mix($--color-white, $--color-purple, 80%) !default;
+$--color-blue-lighter: mix($--color-white, $--color-blue, 90%) !default;
+$--color-purple-lighter: mix($--color-white, $--color-purple, 90%) !default;
+
+// sidebar
+$menuText: $--color-text-primary;
+$menuActiveText: #ffffff;
+$subMenuActiveText: $--color-primary; // https://github.com/ElemeFE/element/issues/12951
+
+$menuBg: #fff;
+// $menuHover: $--color-primary-light-9;
+$menuHover: #0AB2C1;
+
+$subMenuBg: rgba(0, 0, 0, 0.03);
+$subMenuHover: $--color-primary-light-9;
+
+$sideBarWidth: 130px;
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+  menuText: $menuText;
+  menuActiveText: $menuActiveText;
+  subMenuActiveText: $subMenuActiveText;
+  menuBg: $menuBg;
+  menuHover: $menuHover;
+  subMenuBg: $subMenuBg;
+  subMenuHover: $subMenuHover;
+  sideBarWidth: $sideBarWidth;
+  primary: $--color-primary;
+  primaryLight: $--color-primary-light-8;
+  primaryLighter: $--color-primary-light-9;
+  success: $--color-success;
+  successLight: $--color-success-light;
+  successLighter: $--color-success-lighter;
+  warning: $--color-warning;
+  warningLight: $--color-warning-light;
+  warningLighter: $--color-warning-lighter;
+  danger: $--color-danger;
+  dangerLight: $--color-danger-light;
+  dangerLighter: $--color-danger-lighter;
+  info: $--color-info;
+  infoLight: $--color-info-light;
+  infoLighter: $--color-info-lighter;
+  blue: $--color-blue;
+  blueLight: $--color-blue-light;
+  blueLighter: $--color-blue-lighter;
+  purple: $--color-purple;
+  purpleLight: $--color-purple-light;
+  purpleLighter: $--color-purple-lighter;
+  bottomOffset: $app-router-view-margin + $app-router-view-padding;
+}
\ No newline at end of file
diff --git a/src/assets/img/avatar-test.png b/src/assets/img/avatar-test.png
new file mode 100644
index 0000000000000000000000000000000000000000..0ed19cbd168690709a650dbf6316e125858575eb
Binary files /dev/null and b/src/assets/img/avatar-test.png differ
diff --git a/src/assets/img/avatar.png b/src/assets/img/avatar.png
new file mode 100644
index 0000000000000000000000000000000000000000..93373bc21304e07d8bb0ba343c1c0d78c73e15a3
Binary files /dev/null and b/src/assets/img/avatar.png differ
diff --git a/src/assets/img/common/avatar.png b/src/assets/img/common/avatar.png
new file mode 100644
index 0000000000000000000000000000000000000000..93373bc21304e07d8bb0ba343c1c0d78c73e15a3
Binary files /dev/null and b/src/assets/img/common/avatar.png differ
diff --git a/src/assets/img/common/gohome.png b/src/assets/img/common/gohome.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa4c03cb3815783ac52403198f2d15cc446d1a07
Binary files /dev/null and b/src/assets/img/common/gohome.png differ
diff --git "a/src/assets/img/common/logo - \345\211\257\346\234\254.png" "b/src/assets/img/common/logo - \345\211\257\346\234\254.png"
new file mode 100644
index 0000000000000000000000000000000000000000..e94e4a5f3246cccf274a236b5a294ab065208e45
Binary files /dev/null and "b/src/assets/img/common/logo - \345\211\257\346\234\254.png" differ
diff --git a/src/assets/img/common/top_out.png b/src/assets/img/common/top_out.png
new file mode 100644
index 0000000000000000000000000000000000000000..e0e0a2744d751de3dd78ee7c493cd6bb8787cd87
Binary files /dev/null and b/src/assets/img/common/top_out.png differ
diff --git a/src/assets/img/edit.png b/src/assets/img/edit.png
new file mode 100644
index 0000000000000000000000000000000000000000..a3c3c8c5c13cd107bffb25c7b63d8c26f5b1d1ec
Binary files /dev/null and b/src/assets/img/edit.png differ
diff --git a/src/assets/img/idcard1.png b/src/assets/img/idcard1.png
new file mode 100644
index 0000000000000000000000000000000000000000..733738267d70ca51066be21077310095c4642d69
Binary files /dev/null and b/src/assets/img/idcard1.png differ
diff --git a/src/assets/img/idcard2.png b/src/assets/img/idcard2.png
new file mode 100644
index 0000000000000000000000000000000000000000..9e0b543cfe57b92e3d62a6220ae0232f8e0a8a4f
Binary files /dev/null and b/src/assets/img/idcard2.png differ
diff --git a/src/assets/img/login-out.png b/src/assets/img/login-out.png
new file mode 100644
index 0000000000000000000000000000000000000000..81cc516dcc71bd111a1de599d2ad02cb5463beca
Binary files /dev/null and b/src/assets/img/login-out.png differ
diff --git a/src/assets/img/logo.png b/src/assets/img/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..e94e4a5f3246cccf274a236b5a294ab065208e45
Binary files /dev/null and b/src/assets/img/logo.png differ
diff --git a/src/assets/img/nav/feedback.png b/src/assets/img/nav/feedback.png
new file mode 100644
index 0000000000000000000000000000000000000000..5639ac96e246f876b462f84e93eebe234ef18432
Binary files /dev/null and b/src/assets/img/nav/feedback.png differ
diff --git a/src/assets/img/nav/feedbackActive.png b/src/assets/img/nav/feedbackActive.png
new file mode 100644
index 0000000000000000000000000000000000000000..eaebdce645af36a28fc2243d230d0338c8e235f3
Binary files /dev/null and b/src/assets/img/nav/feedbackActive.png differ
diff --git a/src/assets/img/nav/footprint.png b/src/assets/img/nav/footprint.png
new file mode 100644
index 0000000000000000000000000000000000000000..676ac98933eb53f7002e472b1ab0e335cbad1c19
Binary files /dev/null and b/src/assets/img/nav/footprint.png differ
diff --git a/src/assets/img/nav/footprintActive.png b/src/assets/img/nav/footprintActive.png
new file mode 100644
index 0000000000000000000000000000000000000000..d51b1fac82cb2f27a27e650de0160ccc48299248
Binary files /dev/null and b/src/assets/img/nav/footprintActive.png differ
diff --git a/src/assets/img/nav/photo.png b/src/assets/img/nav/photo.png
new file mode 100644
index 0000000000000000000000000000000000000000..300bddf19c0abef4213d453f6df69fe20d93cad4
Binary files /dev/null and b/src/assets/img/nav/photo.png differ
diff --git a/src/assets/img/nav/photoActive.png b/src/assets/img/nav/photoActive.png
new file mode 100644
index 0000000000000000000000000000000000000000..3737af1e70a527b693d841b5a1b27d5d2a145a3d
Binary files /dev/null and b/src/assets/img/nav/photoActive.png differ
diff --git a/src/assets/img/nav/profile.png b/src/assets/img/nav/profile.png
new file mode 100644
index 0000000000000000000000000000000000000000..f64be8dc18751a65bc812aba520335d0cafdbc6e
Binary files /dev/null and b/src/assets/img/nav/profile.png differ
diff --git a/src/assets/img/nav/profileActive.png b/src/assets/img/nav/profileActive.png
new file mode 100644
index 0000000000000000000000000000000000000000..3d3586edb3897cc9ea8111b58f6f78c4c83fe90d
Binary files /dev/null and b/src/assets/img/nav/profileActive.png differ
diff --git a/src/assets/img/nav/serviceData.png b/src/assets/img/nav/serviceData.png
new file mode 100644
index 0000000000000000000000000000000000000000..668851804eb3886e34e1a2aac67bcd1313df7605
Binary files /dev/null and b/src/assets/img/nav/serviceData.png differ
diff --git a/src/assets/img/nav/serviceDataActive.png b/src/assets/img/nav/serviceDataActive.png
new file mode 100644
index 0000000000000000000000000000000000000000..1036c2fceba7f11a0a214458e37b484c142831d5
Binary files /dev/null and b/src/assets/img/nav/serviceDataActive.png differ
diff --git a/src/assets/img/nav/spaceIndex.png b/src/assets/img/nav/spaceIndex.png
new file mode 100644
index 0000000000000000000000000000000000000000..32e9287ce89ad1c1c675bd8de4b6a24ca5712b86
Binary files /dev/null and b/src/assets/img/nav/spaceIndex.png differ
diff --git a/src/assets/img/nav/spaceIndexActive.png b/src/assets/img/nav/spaceIndexActive.png
new file mode 100644
index 0000000000000000000000000000000000000000..88b485b320f7226c9507751d37b367091a2bb043
Binary files /dev/null and b/src/assets/img/nav/spaceIndexActive.png differ
diff --git a/src/assets/img/property.png b/src/assets/img/property.png
new file mode 100644
index 0000000000000000000000000000000000000000..415bcdd1b01a2208b192a80cf298378d1dba1888
Binary files /dev/null and b/src/assets/img/property.png differ
diff --git a/src/assets/img/save.png b/src/assets/img/save.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d992ce69663e383a77ab775d8d999d63f8ad68d
Binary files /dev/null and b/src/assets/img/save.png differ
diff --git a/src/assets/img/sub-header-bg.png b/src/assets/img/sub-header-bg.png
new file mode 100644
index 0000000000000000000000000000000000000000..3a41cd0ae4ed1ba89a8a789775fd32182f6c74dc
Binary files /dev/null and b/src/assets/img/sub-header-bg.png differ
diff --git a/src/assets/img/test-card-photo.png b/src/assets/img/test-card-photo.png
new file mode 100644
index 0000000000000000000000000000000000000000..f65006142160d81e2f3634baf26871a63d28602c
Binary files /dev/null and b/src/assets/img/test-card-photo.png differ
diff --git a/src/assets/img/user.png b/src/assets/img/user.png
new file mode 100644
index 0000000000000000000000000000000000000000..cd6f71f49099978c777d66aa155f29a1219157da
Binary files /dev/null and b/src/assets/img/user.png differ
diff --git a/src/components/App/AppHeader/index.vue b/src/components/App/AppHeader/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..314a9ac443d33b7815e0fcb5726c0e601f1a9f1b
--- /dev/null
+++ b/src/components/App/AppHeader/index.vue
@@ -0,0 +1,100 @@
+<template>
+  <div class="app-header">
+    <div class="header-left"><img src="@/assets/img/logo.png" alt="" /></div>
+    <div class="header-right">
+      <span class="welcome">娆㈣繋鏉ュ埌鏁板瓧绌洪棿锛�</span>
+
+      <img src="@/assets/img/user.png" alt="" />
+      <span class="user-name">鍒樹簯</span>
+      <img src="@/assets/img/login-out.png" alt="" />
+    </div>
+  </div>
+</template>
+<script>
+import dayjs from "dayjs";
+// import { appName } from "@/config";
+// import { asyncRoutesSort } from "@/router";
+// import cloneDeep from "lodash/cloneDeep";
+// console.log(asyncRoutes);
+
+export default {
+  name: "AppHeader",
+  data() {
+    return {
+      now: {
+        date: "",
+        time: "",
+        week: "",
+      },
+      // appName,
+      timmer: null,
+      // isCollapse: false,
+    };
+  },
+  computed: {
+    // menus() {
+    //   return cloneDeep(asyncRoutesSort);
+    // },
+    // activeRouteName() {
+    //   return this.$route.matched[0]?.name;
+    // },
+  },
+  created() {
+    this.timmer = setInterval(() => this.updateTime(), 1000);
+  },
+  beforeDestroy() {
+    if (this.timmer) clearInterval(this.timmer);
+  },
+  methods: {
+    updateTime() {
+      const now = dayjs();
+      this.now = {
+        date: now.format("YYYY-MM-DD"),
+        time: now.format("HH:mm:ss"),
+        week: now.format("dddd"),
+      };
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+@import "@/assets/css/variables.scss";
+
+.app-header {
+  width: 100%;
+  height: 80px;
+  display: flex;
+  background-color: #2171ff;
+  color: #fff;
+
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  z-index: 0;
+}
+.header-left {
+  padding-left: calc(50% - 600px);
+}
+
+.header-right {
+  position: relative;
+  z-index: 1;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #fff;
+  padding-right: 33px;
+  height: 30px;
+  .avatar {
+    width: 28px;
+    height: 28px;
+    margin-left: 20px;
+  }
+  .user-name {
+    padding: 0 10px;
+  }
+  .date {
+    padding-right: 20px;
+  }
+}
+</style>
diff --git a/src/components/App/BackTitle/index.vue b/src/components/App/BackTitle/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d9bdc2a809a440f8e639e75b6b402600788f2741
--- /dev/null
+++ b/src/components/App/BackTitle/index.vue
@@ -0,0 +1,45 @@
+<template>
+  <div class="back-title">
+    <i class="el-icon-arrow-left" @click="backRouter"></i>
+    <span>{{ title }}</span>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "SubHeader",
+  data() {
+    return {};
+  },
+  props: {
+    title: {
+      type: String,
+      default: () => "鏍囬",
+    },
+  },
+  computed: {},
+  watch: {},
+  methods: {
+    backRouter() {
+      this.$router.back(-1);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/css/variables.scss";
+.back-title {
+  height: 56px;
+  line-height: 36px;
+  padding-top: 20px;
+  // cursor: pointer;
+  flex-shrink: 0;
+  span {
+    padding-left: 5px;
+  }
+  i {
+    cursor: pointer;
+  }
+}
+</style>
diff --git a/src/components/App/BreadCrumb/index.vue b/src/components/App/BreadCrumb/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..fd3b388b382835026cc2a71f12f5671708892108
--- /dev/null
+++ b/src/components/App/BreadCrumb/index.vue
@@ -0,0 +1,166 @@
+<template>
+  <div class="app-bread-crumb">
+    {{ pageName }}
+    <!-- <el-tabs
+      v-model="activeName"
+      @tab-click="handleClick"
+      @edit="handleTabsEdit"
+    >
+      <el-tab-pane
+        :label="item.meta.title"
+        :name="item.path"
+        v-for="item in pathList"
+        closable
+        :key="item.path"
+      ></el-tab-pane>
+    </el-tabs> -->
+    <slot></slot>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "BreadCrumb",
+  data() {
+    return {
+      activeName: "second",
+      pathList: [],
+    };
+  },
+  computed: {
+    paths() {
+      const matched = this.$route.matched.filter(
+        (item) => item.meta && item.meta.title
+      );
+      this.changeRouter(matched);
+      // console.log(matched.filter((item) => item.meta && item.meta.title));
+      return matched.filter((item) => (item.meta ? item.meta.title : ""));
+    },
+    pageName() {
+      return this.$route.matched[this.$route.matched.length - 1]?.meta?.title;
+    },
+  },
+  watch: {
+    paths() {
+      this.$nextTick(() => {
+        // console.log(this.paths);
+      });
+    },
+  },
+  methods: {
+    changeRouter(matched) {
+      // console.log(matched);
+      if (matched.length > 1) {
+        matched = [matched[matched.length - 1]];
+      }
+      if (matched[0].meta.disTab) {
+        return;
+      }
+      //浠庡乏渚у鑸墦寮€椤甸潰鏃舵坊鍔犲湪澶撮儴瀵艰埅锛屾病鏈夊垯鍙畾浣�
+      let paths = this.pathList.map((item) => item.path);
+      if (paths.includes(matched[0].path)) {
+        this.activeName = matched[0].path;
+      } else {
+        this.pathList = [...this.pathList, ...matched];
+        this.activeName = matched[0].path;
+      }
+      // this.pathList = [...this.pathList, ...matched];
+      // console.log(this.pathList);
+    },
+    handleTabsEdit(targetName, action) {
+      // console.log(targetName);
+      if (action === "remove") {
+        if (this.pathList.length == 1) {
+          this.$message.info("宸茬粡鏄渶鍚庣殑椤甸潰浜嗭紝鏃犳硶鍏抽棴");
+          return;
+        }
+        //鍒犻櫎缂撳瓨椤甸潰
+        let findItem = this.pathList.find((item) => item.path == targetName);
+        // console.log(findItem);
+        this.$store.dispatch("app/removeCacheRoutes", findItem.name);
+        if (findItem.parent) {
+          this.$store.dispatch("app/removeCacheRoutes", findItem.parent.name);
+        }
+        //浠庡垪琛ㄥ幓鎺�
+        this.pathList = this.pathList.filter((item) => item.path != targetName);
+        if (this.$route.path == targetName) {
+          //鍏抽棴褰撳墠椤甸潰璺宠浆鍒版渶鍚庝竴涓〉闈�
+          this.$router.replace(this.pathList[this.pathList.length - 1].path);
+          // this.activeName = matched[0].path;
+        }
+      }
+    },
+    handleClick(tab) {
+      // console.log(tab.name, event);
+      this.$router.push(tab.name);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/css/variables.scss";
+
+.app-bread-crumb {
+  display: flex;
+  // margin-bottom: 20px;
+  // margin-bottom: 15px;
+  align-items: center;
+  justify-content: space-between;
+  background-color: #fff;
+  height: 50px;
+
+  height: 28px;
+  line-height: 28px;
+  font-size: 18px;
+  font-weight: 700;
+  color: rgba(49, 130, 255, 1);
+
+  // padding: 0 16px;
+  .app-home {
+    display: flex;
+    font-size: 20px;
+    color: #8d8d8d;
+    margin-right: 20px;
+    &:hover {
+      color: $--color-primary;
+    }
+  }
+  &::v-deep {
+    .el-tabs {
+      max-width: 80%;
+    }
+    .el-tabs__nav-prev,
+    .el-tabs__nav-next {
+      height: 50px;
+      line-height: 50px;
+    }
+    .el-tabs__header {
+      margin: 0;
+      height: 100%;
+    }
+    .el-tabs__nav-wrap {
+      &::after {
+        display: none;
+      }
+    }
+    .el-tabs__item,
+    .el-tabs,
+    .el-tabs__nav {
+      height: 50px;
+      line-height: 50px;
+    }
+    .router-link-active {
+      font-weight: 500;
+      color: #333333;
+      &:hover {
+        color: $--color-primary;
+      }
+    }
+    .el-breadcrumb__separator {
+      color: #333333;
+      margin: 0;
+    }
+  }
+}
+</style>
diff --git a/src/components/App/Empty/index.vue b/src/components/App/Empty/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..94c67716b77a6666d41a7c6c2923e1341d8daf7c
--- /dev/null
+++ b/src/components/App/Empty/index.vue
@@ -0,0 +1,26 @@
+<script>
+export default {
+  functional: true,
+  render(h, { props, children, data }) {
+    return (
+      <div class="c__empty" {...data}>
+        {children || [
+          <svg-icon icon-class="empty" />,
+          <p>{props.tips || "鏆傛棤鏁版嵁"}</p>
+        ]}
+      </div>
+    );
+  }
+};
+</script>
+
+<style lang="scss">
+.c__empty {
+  text-align: center;
+  color: #8c8c8c;
+
+  .svg-icon {
+    font-size: 60px;
+  }
+}
+</style>
diff --git a/src/components/App/FuncComponent/index.vue b/src/components/App/FuncComponent/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8065ee2da90410a51b1c5ab70492a960d37b03f9
--- /dev/null
+++ b/src/components/App/FuncComponent/index.vue
@@ -0,0 +1,25 @@
+<script>
+/*
+@author        
+@name          FuncComponent
+@desc          render鍑芥暟缁勪欢銆愬洜涓簍emplate涓啓jsx涓嶈鑼冦€�
+@props         params       any         render鍑芥暟鎵€闇€鍙傛暟
+               renderFunc   function    render鍑芥暟 
+@emit
+*/
+export default {
+  name: "FuncComponent",
+  props: {
+    params: {
+      type: [String, Number, Boolean, Array, Object, undefined]
+    },
+    renderFunc: {
+      type: Function,
+      required: true
+    }
+  },
+  render() {
+    return this.renderFunc(this.params);
+  }
+};
+</script>
diff --git a/src/components/App/ImgUploader/index.vue b/src/components/App/ImgUploader/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..99e60a67f7bf2129d70cd806cdfc502a248a42a9
--- /dev/null
+++ b/src/components/App/ImgUploader/index.vue
@@ -0,0 +1,172 @@
+<template>
+  <div class="ImgUploader">
+    <div class="img-list">
+      <div class="img-item" v-for="(item, index) in imgList" :key="index">
+        <img :src="item" alt="" />
+        <div class="mask">
+          <i class="el-icon-search" @click="preView(item)"></i>
+          <i class="el-icon-delete" @click="deleteImg(item, index)"></i>
+        </div>
+      </div>
+    </div>
+    <el-upload
+      action=""
+      :before-upload="
+        (file) => {
+          beforeUpload(file);
+          return false;
+        }
+      "
+      v-if="!readOnly"
+    >
+      <i class="el-icon-plus"></i>
+      <span>涓婁紶鍥剧墖</span>
+    </el-upload>
+    <el-image-viewer
+      v-if="dialogVisible"
+      :on-close="
+        () => {
+          dialogVisible = false;
+        }
+      "
+      :url-list="[dialogImageUrl]"
+    />
+    <!-- <el-dialog :visible.sync="dialogVisible" width="600px" append-to-body>
+      <img width="100%" :src="dialogImageUrl" alt="" />
+    </el-dialog> -->
+  </div>
+</template>
+<script>
+import ElImageViewer from "element-ui/packages/image/src/image-viewer";
+export default {
+  name: "ImgUploader",
+  components: { ElImageViewer },
+  data: () => {
+    return {
+      dialogImageUrl: "",
+      dialogVisible: false,
+    };
+  },
+  props: {
+    value: {
+      type: Array,
+      default: () => [],
+    },
+    readOnly: {
+      type: Boolean,
+      default: () => false,
+    },
+  },
+  computed: {
+    imgList: {
+      get: function() {
+        return this.value;
+      },
+      set: function(newValue) {
+        this.$emit("input", newValue);
+      },
+    },
+  },
+  methods: {
+    async beforeUpload(file) {
+      // console.log(file);
+
+      let url = await this.$utils.file2Base64(file);
+
+      this.imgList = [...this.value, url];
+      return false;
+      // console.log(url);
+      // this.$set(this.form, field, url);
+      // return false;
+      // const reader = new FileReader();
+      // reader.readAsDataURL(file);
+      // reader.onload = () => {
+      //   const base64 = reader.result;
+      //   // console.log(base64);
+      //   this.imgList = [...this.value, base64];
+      // };
+      // reader.onerror = (error) => {
+      //   console.log(error);
+      // };
+    },
+    deleteImg(item, index) {
+      this.imgList = this.imgList.filter((fitem, findex) => findex != index);
+    },
+    preView(item) {
+      this.dialogImageUrl = item;
+      this.dialogVisible = true;
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.ImgUploader {
+  display: flex;
+  flex-wrap: wrap;
+  max-width: 100%;
+  // padding-top: 24px;
+  .img-list {
+    display: flex;
+    .img-item {
+      width: 72px;
+      height: 72px;
+      border: 1px solid #e5e5e5;
+      overflow: hidden;
+      margin-right: 20px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding: 8px;
+      position: relative;
+      .mask {
+        display: none;
+        width: 100%;
+        position: absolute;
+        height: 100%;
+        left: 0;
+        top: 0;
+        color: #fff;
+        font-size: 22px;
+        background-color: rgba($color: #000000, $alpha: 0.3);
+        justify-content: space-around;
+        padding: 0 20px;
+        align-items: center;
+        ::v-deep {
+          i {
+            cursor: pointer;
+          }
+        }
+      }
+      &:hover {
+        .mask {
+          display: flex;
+        }
+      }
+      img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+  ::v-deep {
+    .el-upload {
+      width: 72px;
+      height: 72px;
+      border: 1px dashed #e5e5e5;
+      display: flex;
+      flex-flow: column;
+      justify-content: center;
+      align-items: center;
+      font-size: 12px;
+      line-height: 22px;
+      color: #808080;
+      span {
+        padding-top: 6px;
+      }
+      i {
+        font-size: 18px;
+      }
+    }
+  }
+}
+</style>
diff --git a/src/components/App/PubDialog/index.vue b/src/components/App/PubDialog/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3875851fa01aaff7475e57b011a218b7d06d46a8
--- /dev/null
+++ b/src/components/App/PubDialog/index.vue
@@ -0,0 +1,155 @@
+<template>
+  <el-dialog
+    :title="title"
+    width="720px"
+    append-to-body
+    :visible.sync="dialogVisible"
+    :before-close="beforeClose"
+    class="PubDialog"
+  >
+    <div class="dialog-main">
+      <div class="slot-box">
+        <slot></slot>
+      </div>
+    </div>
+    <!-- <span slot="footer" class="dialog-footer">
+      <el-button @click="dialogVisible = false">鍙� 娑�</el-button>
+      <el-button type="primary" @click="dialogVisible = false">纭� 瀹�</el-button>
+    </span> -->
+  </el-dialog>
+</template>
+<script>
+export default {
+  name: "VideoDialog",
+  data: () => {
+    return {
+      // dialogVisible: false,
+    };
+  },
+  props: {
+    value: {
+      type: Boolean,
+      default: () => false,
+    },
+    title: {
+      type: String,
+      default: () => "鏍囬",
+    },
+  },
+  computed: {
+    dialogVisible: {
+      get: function(that) {
+        return that.value;
+      },
+      set: function(newValue) {
+        this.$emit("input", newValue);
+      },
+    },
+  },
+  methods: {
+    beforeClose() {
+      // console.log('beforeClose');
+      // this.dialogVisible = false;
+      this.$emit("beforeClose");
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+// .PubDialog {
+::v-deep {
+  .el-dialog {
+    border-radius: 8px;
+  }
+  .el-dialog__header {
+    height: 52px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    padding: 0 16px 0 20px;
+    border-bottom: 1px solid #e5e5e5;
+    .el-dialog__title {
+      font-size: 14px;
+      color: #000;
+    }
+    .el-dialog__headerbtn .el-dialog__close {
+      font-size: 20px;
+      color: #c4c4c4;
+    }
+  }
+}
+//   .el-dialog__header {
+//     padding: 0;
+//   }
+//   .el-dialog__body {
+//     padding: 24px;
+//     background: #041740;
+//     // border: 3px solid #c6e7ff;
+//     box-shadow: inset 0px 0px 26px #289aff;
+//     color: #fff;
+//     position: relative;
+//     min-height: 460px;
+//     .dialog-back {
+//       position: absolute;
+//       z-index: 1;
+//       width: 100%;
+//       height: 100%;
+//       left: 0;
+//       top: 0;
+//       img {
+//         width: 100%;
+//         height: 100%;
+//       }
+//     }
+//   }
+//   .dialog-btns {
+//     display: flex;
+//     justify-content: flex-end;
+//     padding-top: 24px;
+//   }
+//   .el-button {
+//     height: 28px;
+//     line-height: 28px;
+//     padding: 0 30px;
+//   }
+//   .el-button--primary.is-plain {
+//     background-color: transparent;
+//     border-color: #1e81ff;
+//     &:hover {
+//       background-color: #1e81ff;
+//       color: #fff;
+//     }
+//   }
+// }
+
+// .dialog-main {
+//   position: relative;
+//   z-index: 2;
+//   .header {
+//     width: 100%;
+//     height: 52px;
+//     background: linear-gradient(90deg, #289aff 0%, rgba(#289aff, 0) 100%);
+//     padding: 0 20px;
+//     display: flex;
+//     justify-content: space-between;
+//     align-items: center;
+
+//     font-size: 18px;
+//     font-weight: bold;
+//     letter-spacing: 2px;
+//     .box-icon {
+//       margin-right: 12px;
+//     }
+//     .close {
+//       width: 25px;
+//       height: 25px;
+//       cursor: pointer;
+//     }
+//   }
+// }
+// .slot-box {
+//   padding: 34px 26px;
+// }
+// }
+</style>
diff --git a/src/components/App/SideBar/index.vue b/src/components/App/SideBar/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4f1d27d3143a8b0369761033ca38de0aa2741f71
--- /dev/null
+++ b/src/components/App/SideBar/index.vue
@@ -0,0 +1,326 @@
+<script>
+// import { appName } from "@/config";
+import { asyncRoutesSort } from "@/router";
+import cloneDeep from "lodash/cloneDeep";
+// import logo from "@/assets/img/logo.png";
+
+export default {
+  name: "SideBar",
+  data() {
+    return {
+      // appName,
+    };
+  },
+  props: {
+    isCollapse: {
+      type: Boolean,
+      default: () => false,
+    },
+  },
+  computed: {
+    menus() {
+      let allMenu = this.treeMap(cloneDeep(asyncRoutesSort));
+      console.log(allMenu);
+      // let findFatherItem = allMenu;
+
+      return allMenu;
+      // let findFatherItem = allMenu.find(
+      //   (item) => item.name == this.mainMenuName
+      // );
+      // if (findFatherItem) {
+      //   return findFatherItem.children || [];
+      // } else {
+      //   return [];
+      // }
+    },
+
+    mainMenuName() {
+      return this.$route.matched[0]?.name;
+    },
+    
+    activeRouteName() {
+      const { name } = this.$route;
+      // console.log(name, this.$route);
+      if(this.$route?.meta?.activeName){
+        return this.$route.meta.activeName
+      }
+      return name;
+    },
+  },
+  methods: {
+    treeMap(list) {
+      // console.log(list);
+      let res = list.map((item) => {
+        if (item.children) {
+          return {
+            ...item,
+            children: this.treeMap(item.children),
+          };
+        } else {
+          return {
+            ...item,
+          };
+        }
+      });
+      // console.log(res);
+      res = res.filter((item) => !item.meta.hidden);
+      return res.length ? res : null;
+    },
+    routeTo(indexs) {
+      let path = "";
+      let route = this.menus;
+      indexs = indexs.split("-");
+      indexs.forEach((index) => {
+        route = route[index] || route.children[index];
+        if (/^\//.test(route.path)) {
+          path = path + route.path;
+        } else {
+          path = path + "/" + route.path;
+        }
+      });
+      // path = "/"  + path;
+      // path = "/" + this.mainMenuName + path;
+      // console.log(path);
+      this.$router.push(path);
+    },
+    getImgPath(iconImg) {
+      try {
+        let file = require(`../../../assets/img/nav/${iconImg}.png`);
+        return file;
+      } catch (error) {
+        return "";
+      }
+      // return require(`../../../assets/img/nav/${iconImg}.png`);
+
+      //  <span class="title-span" slot="title">
+      //           <div class="icon-box">
+      //             {item.meta.iconImg ? (
+      //               <img src={getImgPath(item.meta.iconImg)}></img>
+      //             ) : (
+      //               <span></span>
+      //             )}
+      //           </div>
+      //           {item.meta.title}
+      //         </span>
+    },
+
+    renderMenus(menus, propIndex = "") {
+      let getImgPath = this.getImgPath;
+      return menus.map((item, index) => {
+        return item.children ? (
+          <el-submenu index={item.name} key={propIndex + index}>
+            <span class="title-span" slot="title">
+              {item.meta.icon ? <i class={item.meta.icon}></i> : <span></span>}
+              {item.meta.title}
+            </span>
+            {item.children
+              ? this.renderMenus(item.children, propIndex + index + "-")
+              : null}
+          </el-submenu>
+        ) : (
+          <el-menu-item
+            key={propIndex + index}
+            index={item.name}
+            onClick={() => this.routeTo(String(propIndex + index))}
+          >
+            {item.meta.iconImg ? (
+              <span class="title-span" slot="title">
+                <div class="icon-box">
+                  {item.meta.iconImg ? (
+                    <div class="flex-center">
+                      <img
+                        src={getImgPath(item.meta.iconImg)}
+                        class="img-icon"
+                      ></img>
+                      <img
+                        src={getImgPath(
+                          item.meta.iconImgActive
+                            ? item.meta.iconImgActive
+                            : item.meta.iconImg
+                        )}
+                        class="img-icon-active"
+                      ></img>
+                    </div>
+                  ) : (
+                    <span></span>
+                  )}
+                </div>
+                {item.meta.title}
+              </span>
+            ) : (
+              <div>
+                {item.meta.icon ? (
+                  <i class={item.meta.icon}></i>
+                ) : (
+                  <span></span>
+                )}
+                {item.meta.title}
+              </div>
+            )}
+          </el-menu-item>
+        );
+      });
+    },
+  },
+  render() {
+    const menus = this.menus;
+    // const appName = this.appName;
+    return (
+      <div class={menus.length ? "side-bar" : "side-bar hide"}>
+        <div class="side-bar-body">
+          <el-menu
+            collapse={this.isCollapse}
+            defaultActive={this.activeRouteName}
+            // default-active="1-4-1"
+          >
+            {this.renderMenus(menus)}
+          </el-menu>
+        </div>
+      </div>
+    );
+  },
+};
+</script>
+<style lang="scss" scoped>
+@import "@/assets/css/variables.scss";
+
+.side-bar {
+  width: 240px;
+  height: calc(100%);
+  border-radius: 8px;
+  box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
+  display: flex;
+  flex-shrink: 0;
+  flex-direction: column;
+  transition: all 0.2s;
+  background-color: #fff;
+  position: relative;
+  padding-top: 14px;
+  &.hide {
+    width: 0;
+  }
+  ::v-deep {
+    .el-menu {
+      width: 240px;
+      background: transparent;
+
+      &.el-menu--collapse {
+        width: 60px;
+      }
+    }
+  }
+}
+.side-bar-body {
+  width: 100%;
+  height: 100%;
+  display: block;
+  &::v-deep {
+    .el-menu {
+      border-right: none;
+    }
+    .el-submenu__title {
+      transition: all 0.3s;
+      width: 200px;
+      height: 35px;
+      line-height: 35px;
+      background: rgba(82, 162, 255, 0.2);
+      color: #5e5e5e;
+      font-size: 14px;
+      display: flex;
+    }
+    // li {
+    //   line-height: 40px;
+    // }
+    .el-menu-item {
+      transition: all 0.3s;
+      width: 200px;
+      height: 35px;
+      line-height: 35px;
+      // background: rgba(82, 162, 255, 0.2);
+      background: transparent;
+      border-radius: 8px;
+      color: #5e5e5e;
+      font-size: 14px;
+      display: flex;
+      align-items: center;
+
+      margin: 6px auto;
+    }
+
+    .el-menu-item:hover,
+    .el-menu-item:focus,
+    .el-submenu__title:hover,
+    .el-submenu__title:focus {
+      color: #3182ff;
+      position: relative;
+      i {
+        color: #3182ff;
+      }
+
+      .icon-box {
+        img {
+          &.img-icon {
+            display: none;
+          }
+          &.img-icon-active {
+            display: inline-block;
+          }
+        }
+      }
+    }
+    .el-menu-item.is-active,
+    .el-submenu .el-menu-item.is-active,
+    .el-submenu__title.is-active {
+      background: rgba($color: #52a2ff, $alpha: 0.2);
+      color: #3182ff;
+      position: relative;
+      i {
+        color: #3182ff;
+      }
+    }
+    .el-menu-item [class^="el-icon-"] {
+      margin-right: 10px;
+    }
+
+    .el-submenu__title i {
+    }
+  }
+}
+.title-span {
+  display: flex;
+  align-items: center;
+}
+.icon-box {
+  display: flex;
+  align-items: center;
+  vertical-align: middle;
+  // width: 40px;
+  height: 100%;
+  margin-left: 3px;
+  margin-right: 14px;
+  img {
+    width: 18px;
+    height: 18px;
+    &.img-icon-active {
+      display: none;
+    }
+    &.img-icon {
+      display: inline-block;
+    }
+  }
+}
+::v-deep {
+  .is-active {
+    .icon-box {
+      img {
+        &.img-icon {
+          display: none;
+        }
+        &.img-icon-active {
+          display: inline-block;
+        }
+      }
+    }
+  }
+}
+</style>
diff --git a/src/components/App/SubHeader/index.vue b/src/components/App/SubHeader/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f93b913c7dd280012d1a223c6c5da95155d6008f
--- /dev/null
+++ b/src/components/App/SubHeader/index.vue
@@ -0,0 +1,86 @@
+<template>
+  <div class="sub-header">
+    <div class="user-info flex">
+      <div class="avatar">
+        <img src="@/assets/img/avatar-test.png" alt="" />
+      </div>
+      <div class="info">
+        <div>鎮ㄥソ锛屾杩庣櫥褰晘</div>
+        <div class="name">鏉庝簯</div>
+        <div class="level">
+          <span>璁よ瘉绛夌骇锛�</span>
+          <div class="level-box">鍥涚骇璁よ瘉</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "SubHeader",
+  data() {
+    return {};
+  },
+  computed: {},
+  watch: {},
+  methods: {},
+};
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/css/variables.scss";
+
+.sub-header {
+  width: 1200px;
+  height: 196px;
+  flex-shrink: 0;
+  background: url("../../../assets/img/sub-header-bg.png");
+  background-size: 100% 100%;
+  color: #fff;
+  .user-info {
+    align-items: center;
+    height: 100%;
+    width: 100%;
+    padding-left: 54px;
+  }
+  .avatar {
+    width: 80px;
+    height: 80px;
+    border-radius: 50%;
+    overflow: hidden;
+
+    box-shadow: 0px 2px 6px rgba($color: #000000, $alpha: 0.2);
+    img {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  .info {
+    padding-left: 20px;
+    font-size: 18px;
+    line-height: 28px;
+    .name {
+      font-size: 20px;
+    }
+    .level {
+      font-size: 12px;
+      color: rgba($color: #fff, $alpha: 0.7);
+      display: flex;
+      align-items: center;
+      .level-box {
+        width: 67px;
+        height: 20px;
+        opacity: 1;
+        border-radius: 10px;
+        background: #ffffff;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 12px;
+        color: rgba(0, 59, 161, 1);
+      }
+    }
+  }
+}
+</style>
diff --git a/src/components/App/Table/TableHeader/index.vue b/src/components/App/Table/TableHeader/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..71b5f0f9a4bb3be6373b6faf9f356755c7505a4a
--- /dev/null
+++ b/src/components/App/Table/TableHeader/index.vue
@@ -0,0 +1,578 @@
+<template>
+  <el-form
+    class="table-header"
+    ref="form"
+    v-if="paramsGroups || params.length || showHeader"
+  >
+    <div class="table-header-row">
+      <!-- <slot v-if="$slots.default"></slot> -->
+      <template v-if="paramsGroups">
+        <!-- 閫夋嫨 -->
+        <el-form-item class="group-item" v-if="paramsGroups.select">
+          <el-select
+            class="label-select"
+            v-model="select.key"
+            @change="changeHandler($event, 'select')"
+          >
+            <el-option
+              v-for="item in paramsGroups.select"
+              :value="item.value"
+              :label="item.label"
+              :key="item.value"
+            >
+            </el-option>
+          </el-select>
+          <component
+            class="value-input"
+            @change="updateData()"
+            v-model="select.val"
+            :is="select.component"
+            v-if="select.component"
+            v-bind="{ ...select.elAttrs }"
+          >
+          </component>
+          <el-select
+            v-else
+            class="value-input"
+            v-model="select.val"
+            @change="updateData()"
+          >
+            <el-option
+              v-for="item in paramsGroups.options[select.key]"
+              :label="item.label"
+              :value="item.value"
+              :key="item.value"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <!-- 杈撳叆 -->
+        <el-form-item class="group-item" v-if="paramsGroups.inputs">
+          <el-select
+            class="label-select"
+            v-model="inputs.key"
+            @change="changeHandler($event, 'input')"
+          >
+            <el-option
+              v-for="item in paramsGroups.inputs"
+              :label="item.label"
+              :value="item.value"
+              :key="item.value"
+            >
+            </el-option>
+          </el-select>
+          <el-input
+            class="value-input"
+            maxlength="20"
+            v-model="inputs.val"
+            @change="updateData()"
+          ></el-input>
+        </el-form-item>
+        <!-- 鑼冨洿 -->
+        <el-form-item class="group-item" v-if="paramsGroups.ranges">
+          <el-select
+            class="label-select"
+            v-model="ranges.label"
+            @change="changeHandler($event, 'ranges')"
+          >
+            <el-option
+              v-for="item in paramsGroups.ranges"
+              :value="item.label"
+              :label="item.label"
+              :key="item.label"
+            >
+            </el-option>
+          </el-select>
+          <component
+            @change="updateData()"
+            v-model="ranges.val"
+            :is="ranges.component"
+            v-bind="{ ...ranges.elAttrs }"
+          >
+          </component>
+        </el-form-item>
+      </template>
+      <template v-else>
+        <div
+          v-for="(item, index) in params"
+          :key="index"
+          style="display: inline-block;"
+        >
+          <el-form-item :class="{ empty: !item.label }" :label="item.label">
+            <FuncComponent
+              v-if="item.renderFunc"
+              :renderFunc="item.renderFunc"
+            ></FuncComponent>
+            <component
+              v-else
+              :type="item.type"
+              clearable
+              size="small"
+              @change="search(1)"
+              :options="item.options"
+              v-model="data[item._key]"
+              v-bind="{ ...item.elAttrs }"
+              :placeholder="item.placeholder"
+              :is="item.component || 'el-input'"
+            >
+              <template v-if="item.component === 'el-select'">
+                <template v-if="options[item._key]">
+                  <el-option
+                    v-for="(_it, _i) in options[item._key]"
+                    :key="index + '-' + _i"
+                    :label="_it.label"
+                    :value="_it.value"
+                  ></el-option>
+                </template>
+                <template v-else>
+                  <el-option
+                    v-for="(_it, _i) in item.options"
+                    :key="index + '-' + _i"
+                    :label="_it.label"
+                    :value="_it.value"
+                  ></el-option>
+                </template>
+              </template>
+            </component>
+          </el-form-item>
+        </div>
+      </template>
+      <template v-if="paramsGroups || params.length">
+        <el-form-item class="btn-item">
+          <el-button
+            v-if="searchBtn"
+            type="primary"
+            size="small"
+            @click="search()"
+            >鏌ヨ</el-button
+          >
+        </el-form-item>
+        <el-form-item class="btn-item">
+          <el-button
+            v-if="resetBtn"
+            size="small"
+            type="primary"
+            @click="reset"
+            plain
+            >閲嶇疆</el-button
+          >
+        </el-form-item>
+      </template>
+      <slot name="tableHeaderSlot"></slot>
+    </div>
+
+    <!-- <div class="btns">
+      
+    </div> -->
+    <div class="btn-right"><slot name="btnRight"></slot></div>
+  </el-form>
+</template>
+<script>
+/*
+@author        
+@name          TableHeader
+@desc          琛ㄥご绛涢€夌粍浠�
+@props         params     		Array<
+                  	label           string          label
+                  	default         string|number   榛樿鍊�
+                  	key             string          form鐨刱ey
+                  	type            string          缁勪欢浼犻€掔殑type鍊�
+                  	component       string          缁勪欢鍚嶇О锛屽锛歟l-input
+                  	placeholder     string          placeholder
+                  	renderFunc      function:VNode  鑷畾涔夋覆鏌撳嚱鏁�
+                  	elAttrs         object          element瀵瑰簲缁勪欢鐨勫睘鎬э紙閰嶇疆锛�
+                  	options         object<{        el-select鏃剁殑options
+                  	  label         string          閫夐」label
+                  	  value         string          閫夐」value
+                  	}>
+               }>
+			   paramsGroups		 object<			鍙傛暟缁�
+				  	inputs			Array<{				杈撳叆鍙傛暟锛堥粯璁ょ涓€涓�
+						  label			string				閫夋嫨鐨刲abel
+						  value			string				閫夋嫨鐨剉alue
+					}>
+				  	select			Array<{				閫夋嫨鍙傛暟锛堥粯璁ょ涓€涓�
+						  label			string				閫夋嫨鐨刲abel
+						  value			string				閫夋嫨鐨剉alue
+						  elAttrs		object				瀵瑰簲缁勪欢鐨勫睘鎬�
+						  component		string				鎸囧畾缁勪欢鐨勫悕绉帮紝浼樺厛浣跨敤璇ラ厤缃�
+					}>
+				  	ranges			Array<{
+						  label			string				閫夋嫨鐨刲abel
+						  keys			Array<string>		鍙傛暟瀵瑰簲鐨刱ey锛岄『搴忔帓鍒�
+						  elAttrs		object				瀵瑰簲缁勪欢鐨勫睘鎬�
+						  component		string				瀵瑰簲鐨勭粍浠跺悕绉�
+					}>
+					options			object<Val>			select瀵瑰簲鐨勯€夐」瀵硅薄锛宬ey涓簊elect瀵瑰簲鐨剉alue
+						  Array<{							select瀵瑰簲鐨勯€夐」
+								label		string				閫夐」鐨刲abel
+								value		strine				閫夐」鐨剉alue
+						  }>
+			   >
+               searchBtn  boolean   鏄惁鏄剧ず鎼滅储鎸夐挳[true]
+			   resetBtn	  boolean	鏄惁鏄剧ず閲嶇疆鎸夐挳[true]
+         showHeader	boolean	鏄惁鍦ㄦ病鏈塰eaders鐨勬椂鍊欐樉绀哄ご閮�
+@slot          slot               	鎺ュ彈涓€涓粯璁ゆ彃妲�
+@desc		   header鍐呭浼樺厛绾� slot> paramsGroup >params
+@emit
+*/
+// 涓嶄娇鐢� import {} 鏄洜涓篈pp閲岄潰鐨凙reaSelect涓緷璧朙inkageSelect锛屼細浜х敓寰幆寮曠敤
+import cloneDeep from "lodash/cloneDeep";
+import FuncComponent from "@/components/App/FuncComponent";
+
+export default {
+  name: "TableHeader",
+  components: {
+    FuncComponent,
+  },
+  props: {
+    params: {
+      type: Array,
+      default: () => [],
+    },
+    options: {
+      type: Object,
+      default: () => {
+        return {};
+      },
+    },
+
+    showHeader: {
+      type: Boolean,
+      default: true,
+    },
+    searchBtn: {
+      type: Boolean,
+      default: true,
+    },
+    resetBtn: {
+      type: Boolean,
+      default: true,
+    },
+    paramsGroups: {
+      type: Object,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      data: {},
+      inputs: {
+        key: "",
+        val: "",
+      },
+      select: {
+        key: "",
+        val: "",
+        elAttrs: {},
+        component: "",
+      },
+      ranges: {
+        val: [],
+        keys: [],
+        elAttrs: {},
+        component: "",
+      },
+    };
+  },
+  created() {
+    this.initParams();
+    this.initParamsGroups();
+  },
+  methods: {
+    reset() {
+      console.log(this.data);
+      for (let p in this.data) {
+        // if (Array.isArray(this.data[p])) {
+        //   // const index = parseInt(p.substring(6));
+        //   // if (this.params[index] && this.params[index].default) {
+        //   //   this.$set(this.data, p, this.params[index].default);
+        //   // } else {
+        //   //   this.$set(this.data, p, []);
+        //   // }
+        //     this.$set(this.data, p, []);
+        //     }
+        if (/^param-/.test(p)) {
+          const index = parseInt(p.substring(6));
+          if (this.params[index] && this.params[index].default) {
+            this.$set(this.data, p, this.params[index].default);
+          } else {
+            this.$set(this.data, p, []);
+          }
+        } else if (Array.isArray(this.data[p])) {
+          this.$set(this.data, p, []);
+        } else if (typeof this.data[p] === "object") {
+          this.$set(this.data, p, {});
+        } else {
+          this.$set(this.data, p, "");
+        }
+      }
+      this.inputs.val = "";
+      this.select.val = "";
+      this.ranges.val = [];
+      this.$emit("search");
+    },
+    search(force) {
+      // console.log(this.data);
+      if (force) {
+        if (!this.searchBtn) {
+          this.$emit("search");
+        }
+      } else {
+        this.$emit("search");
+      }
+    },
+    initParams() {
+      const data = {};
+      this.params.forEach((item, index) => {
+        item.elAttrs = item.elAttrs || {};
+        if (typeof item.key === "string") {
+          data[item.key] = item.default || "";
+          item._key = item.key;
+        } else {
+          item._key = "param-" + index;
+          data["param-" + index] = item.default || "";
+        }
+      });
+      this.data = data;
+      // console.log(this.params);
+      // console.log(this.data);
+    },
+    initParamsGroups() {
+      if (!this.paramsGroups) return;
+      if (this.paramsGroups.inputs && this.paramsGroups.inputs.length) {
+        this.inputs = {
+          val: "",
+          key: this.paramsGroups.inputs[0].value,
+        };
+      }
+      if (this.paramsGroups.select && this.paramsGroups.select.length) {
+        this.select = {
+          val: "",
+          key: this.paramsGroups.select[0].value,
+          elAttrs: this.paramsGroups.select[0].elAttrs || {},
+          component: this.paramsGroups.select[0].component || "",
+        };
+      }
+      if (this.paramsGroups.ranges && this.paramsGroups.ranges.length) {
+        const range = this.paramsGroups.ranges[0];
+        this.ranges = {
+          keys: range.keys,
+          label: range.label,
+          component: range.component,
+          elAttrs: range.elAttrs || {},
+          val: range.keys.map(() => ""),
+        };
+      }
+      this.updateData(true);
+    },
+    changeHandler(value, type) {
+      if (type === "input") {
+        this.$set(this.inputs, "val", "");
+      } else if (type === "select") {
+        this.$set(this.select, "val", "");
+        const item = this.paramsGroups.select.find((it) => it.value === value);
+        this.$set(this.select, "component", item.component);
+        this.$set(this.select, "elAttrs", item.elAttrs || {});
+      } else {
+        const item = this.paramsGroups.ranges.find((it) => it.label === value);
+        const val = item.keys.map(() => "");
+        this.$set(this.ranges, "val", val);
+        this.$set(this.ranges, "keys", item.keys);
+        this.$set(this.ranges, "component", item.component);
+        this.$set(this.ranges, "elAttrs", item.elAttrs || {});
+      }
+      this.updateData();
+    },
+    // stop:boolean 鏄惁鏆傚仠鎼滅储
+    updateData(stop = false) {
+      const paramsRanges = {};
+      let selectVal = this.select.val;
+      // console.log(this.ranges);
+      // setTimeout(() => {
+      //   console.log(this.ranges);
+      // }, 2000);
+      this.ranges.keys.forEach((key, index) => {
+        paramsRanges[key] = this.ranges.val ? this.ranges.val[index] : "";
+      });
+      if (
+        selectVal === "鍏ㄩ儴" &&
+        this.select.component === "ModelAreaTreeSelect"
+      ) {
+        selectVal = "";
+      }
+
+      this.data = {
+        [this.inputs.key]: this.inputs.val,
+        [this.select.key]: selectVal,
+        ...paramsRanges,
+      };
+      if (stop) return;
+      this.search(1);
+    },
+    setParamsVal(o) {
+      const data = cloneDeep(this.data);
+      for (let p in o) {
+        const index = this.params.findIndex(
+          (item) => JSON.stringify(item.key) === p
+        );
+        if (index === -1) continue;
+        data[this.params[index]._key] = o[p];
+      }
+      this.data = data;
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+@import "@/assets/css/variables.scss";
+.label-select {
+  ::v-deep {
+    .el-input__inner {
+      border: none;
+      padding-left: 0;
+      border-radius: 2px;
+      background-color: transparent;
+    }
+  }
+}
+.table-header {
+  padding: 22px 15px 0 0;
+  ::v-deep {
+    .el-tag.el-tag--info {
+      display: flex;
+      max-width: 90px;
+    }
+  }
+  // display: flex;
+  // .btns {
+  //   margin-top: 15px;
+  // }
+  .table-header-row {
+    display: flex;
+    flex: 1;
+    margin-right: -30px;
+    flex-wrap: wrap;
+  }
+  .table-header-btns {
+    .el-button {
+      width: 56px;
+      height: 32px;
+      background: #0071e3;
+      border-radius: 2px;
+      line-height: 32px;
+      padding: 0;
+    }
+  }
+  .table-header-btns {
+    display: flex;
+  }
+  &::v-deep {
+    .el-form-item {
+      display: flex;
+      margin-bottom: 0;
+      align-items: center;
+      margin-right: 30px;
+      margin-bottom: 27px;
+      height: 32px;
+      &.btn-item {
+        margin-right: 10px;
+      }
+    }
+    .el-form-item__label {
+      // width: 96px;
+      font-size: 12px;
+      height: 32px;
+      line-height: 32px;
+      padding-right: 22px;
+    }
+    .el-form-item__content {
+      .el-input,
+      .el-select {
+        width: 180px;
+        height: 32px;
+        display: flex;
+        .el-input__inner {
+          height: 32px;
+          line-height: 30px;
+          border-radius: 2px;
+          background-color: transparent;
+        }
+        .el-input__suffix {
+          top: 1px;
+          height: 30px;
+          line-height: 30px;
+        }
+      }
+      .el-date-editor {
+        height: 32px;
+        display: flex;
+        padding: 0 10px;
+      }
+    }
+    .el-select .el-input .el-select__caret,
+    .el-cascader .el-input .el-icon-arrow-down {
+      line-height: 30px;
+    }
+    .empty {
+      .el-form-item__label {
+        display: none;
+      }
+    }
+    .area-select {
+      width: 318px;
+    }
+    .flex-input {
+      .el-select {
+        width: 96px;
+      }
+      .el-input {
+        margin-left: 0;
+      }
+    }
+
+    .group-item {
+      // flex: 1;
+      margin-right: 20px;
+      &:last-child {
+        flex: 2;
+      }
+      .el-form-item__content {
+        width: 100%;
+        display: flex;
+      }
+      .el-select:first-child {
+        width: 120px;
+      }
+      .el-slider {
+        flex: 1;
+        height: 32px;
+        margin-left: 20px;
+      }
+      .el-slider__runway {
+        margin: 13px 0;
+      }
+      .el-date-editor--daterange.el-input__inner,
+      .el-date-editor--datetimerange.el-input__inner {
+        // flex: 1;
+        // width: auto;
+        border-radius: 2px;
+        width: 480px;
+      }
+    }
+    .table_select {
+      display: flex;
+      align-items: center;
+      padding-top: 15px;
+      .select_title {
+        font-size: 14px;
+        margin-right: 15px;
+        color: $--color-text-regular;
+      }
+    }
+  }
+}
+.btn-right {
+  margin-bottom: 18px;
+}
+</style>
diff --git a/src/components/App/Table/index.vue b/src/components/App/Table/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..958f675ffa7d7a93e672256c9d4fb955127c90bd
--- /dev/null
+++ b/src/components/App/Table/index.vue
@@ -0,0 +1,546 @@
+<template>
+  <div class="base-table">
+    <div class="base-table-header">
+      <TableHeader
+        :params="params"
+        :options="options"
+        @search="loadData(1)"
+        ref="refTableHeader"
+        :resetBtn="resetBtn"
+        :searchBtn="searchBtn"
+        :paramsGroups="paramsGroups"
+        :showHeader="showHeader"
+      >
+        <slot name="tableHeader"></slot>
+        <template slot="tableHeaderRight">
+          <slot name="tableHeaderRight"></slot>
+        </template>
+        <template slot="btnRight">
+          <slot name="btnRight"></slot>
+        </template>
+        <template slot="tableHeaderSlot">
+          <slot name="tableHeaderSlot"></slot>
+        </template>
+      </TableHeader>
+    </div>
+    <div class="add-line"><slot name="tableAdd"></slot></div>
+    <div class="base-table-body" ref="refTableBody" v-loading="loading">
+      <el-table
+        :height="height"
+        :data="tableData"
+        ref="refInnerTable"
+        size="small"
+        stripe
+        v-bind="{ ...table }"
+        @sort-change="sortChange"
+        @selection-change="$emit('selection-change', $event)"
+      >
+        <el-table-column
+          v-if="table.selection"
+          type="selection"
+          width="55"
+          :selectable="checkboxSelect"
+        >
+        </el-table-column>
+        <template v-for="(column, index) in tableColumns">
+          <el-table-column
+            v-if="column.renderFun"
+            v-bind="{ ...column }"
+            :key="index"
+          >
+            <template slot-scope="scope">
+              <FuncComponent
+                :params="scope"
+                :renderFunc="column.renderFun"
+              ></FuncComponent>
+            </template>
+          </el-table-column>
+
+          <template v-else>
+            <el-table-column
+              :key="index"
+              v-bind="{ ...column }"
+            ></el-table-column>
+          </template>
+        </template>
+      </el-table>
+    </div>
+    <div v-if="pagination" class="base-table-footer" ref="refTableFooter">
+      <slot name="tableFooter"></slot>
+      <el-pagination
+        background
+        :total="total"
+        :current-page="page"
+        :page-size="pageSize"
+        :page-sizes="pageSizes"
+        prev-text="涓婁竴椤�"
+        next-text="涓嬩竴椤�"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        layout="prev,pager,next"
+      >
+      </el-pagination>
+    </div>
+  </div>
+</template>
+<script>
+/*
+@author        
+@name          Table
+@desc          鍩虹琛ㄦ牸缁勪欢[鎼滅储鏉′欢+鍐呭+缈婚〉鍖哄煙]
+@props         table      object<{          琛ㄦ牸閰嶇疆锛屽弬鑰僥l-table;鏃犻渶瀹氫箟data锛屽惁鍒欏皢瑕嗙洊缁勪欢鍐卍ata,浠呮墿灞曚互涓嬪睘鎬�
+                   page         number      榛樿椤电爜[1]
+                   pageSize     number      榛樿椤电爜鏉℃暟[10]
+                   selection    boolean     鏄惁鍙瓫閫�
+               }>
+               checkboxSelect		function    褰搒election涓簍rue鍗冲彲绛涢€夋椂鐨勭鐢�
+
+               params     Array<{           琛ㄦ牸璇锋眰鎵€闇€鍙傛暟锛岀粍浠跺皢鑷姩娣诲姞椤电爜
+                   component    string      鍏ㄥ眬缁勪欢 global-component 鍙� TableHeader 宸插鍏ョ粍浠�
+                   label        string      琛ㄥ崟椤筶abel
+                   type         string      缁勪欢type灞炴€�
+                   key          string      鍙傛暟key
+                   placeholder  string      placeholder
+                   options      array<{
+                       label    string      label
+                       value    string      value
+                   }>
+               }>
+               options object<{          options
+                       label    string      label
+                       value    string      value
+               }>
+
+         paramsGroups		 object<			鍙傛暟缁�
+            inputs			Array<{				杈撳叆鍙傛暟锛堥粯璁ょ涓€涓�
+              label			string				閫夋嫨鐨刲abel
+              value			string				閫夋嫨鐨剉alue
+          }>
+            select			Array<{				閫夋嫨鍙傛暟锛堥粯璁ょ涓€涓�
+              label			string				閫夋嫨鐨刲abel
+              value			string				閫夋嫨鐨剉alue
+          }>
+            ranges			Array<{
+              label			string				閫夋嫨鐨刲abel
+              keys			Array<string>		鍙傛暟瀵瑰簲鐨刱ey锛岄『搴忔帓鍒�
+              component		string				瀵瑰簲鐨勭粍浠跺悕绉�
+          }>
+          options			object<Val>			select瀵瑰簲鐨勯€夐」瀵硅薄锛宬ey涓簊elect瀵瑰簲鐨剉alue
+              Array<{							select瀵瑰簲鐨勯€夐」
+                label		string				閫夐」鐨刲abel
+                value		strine				閫夐」鐨剉alue
+              }>
+         >
+
+          headers    array<{           						鑷畾涔夎〃澶�
+            field         string      						瀛楁鍚�
+            dataField     string      						瀛楁鍚�
+            fieldName     string      						瀛楁涓枃鍚�
+            sort          boolean     						鏄惁鍙帓搴�
+            sortBy		String/Array/Function(row, index)	鍙傝€僥l-table
+            sortMethod	Function(a, b)						鍙傝€僥l-table
+          }>
+
+          columns    object<{          琛ㄦ牸鍒楅厤缃紝鍙傝€僥l-table锛涘鎺ュ彛杩斿洖琛ㄥご锛屽垯灏嗕細瀵硅〃澶磋繘琛屽悎骞讹紝鏃犻渶閲嶅瀹氫箟label锛屽鏋渉eaders涓湭鍖呭惈colums涓殑灞炴€э紝鍒欎細灏嗘湭鍖呭惈鐨勫睘鎬ф斁缃埌鏈€鍚庛€備粎鎵╁睍浠ヤ笅灞炴€�
+              renderFun    function    render鍑芥暟锛宻cope 浣滀负鍏ュ弬
+          }>
+
+          pageKeys   object<{          椤电爜鍜屾瘡椤垫暟閲忕殑key
+            page      string          椤电爜 default page
+            size      string          姣忛〉鏁伴噺 default pageSize
+          }>
+
+         fetcher    	function          鑾峰彇鏁版嵁鐨勬柟娉�
+
+         searchBtn  	boolean           鏄惁鏄剧ず鎼滅储鎸夐挳[true]
+
+         resetBtn	  	boolean			鏄惁鏄剧ず閲嶇疆鎸夐挳[true]
+
+         showHeader	  	boolean			鏄惁鍦ㄦ病鏈塰eaders鐨勬椂鍊欐樉绀哄ご閮�
+         
+         pagination	boolean			鏄惁闇€瑕佸垎椤礫true]
+
+@slot          tableHeader		琛ㄥごslot
+         tableHeaderRight	琛ㄥご鎼滅储鎸夐挳鏃乻lot锛岀敤浜庢坊鍔犳寜閽瓑
+         tableFooter		琛ㄨ剼鍐呭锛岀敤浜庢坊鍔犲簳閮ㄦ寜閽瓑
+@refMethod     setParamsVal     object      璁剧疆params鍙傛暟鍊�,key涓簆arams鐨刱ey(蹇呴』杞琂SON)锛寁al涓簆arams鐨勫€硷紝浼氳嚜鍔ㄦ牴鎹甼ey鐨勭被鍨嬭繘琛岃祴鍊�
+@emit
+@waring        涓婃柟閮ㄥ垎灞炴€ф毚闇插嚭鏉ラ渶瑕佺洿鎺ョ粦瀹氾紝鍒囧嬁鍏ㄩ儴浣跨敤 elAttr ,瀛樺湪灞炴€ц鐩栧鑷存煇浜涘睘鎬х粦瀹氬け璐�
+*/
+import TableHeader from "./TableHeader";
+import FuncComponent from "@/components/App/FuncComponent";
+export default {
+  name: "Table",
+  props: {
+    table: {
+      type: Object,
+      default: () => ({}),
+    },
+    params: {
+      type: Array,
+      default: () => [],
+    },
+    options: {
+      type: Object,
+      default: () => ({}),
+    },
+    headers: {
+      type: Array,
+      default: () => [],
+    },
+    columns: {
+      type: Object,
+      default: () => ({}),
+    },
+    fetcher: {
+      type: Function,
+    },
+    searchBtn: {
+      type: Boolean,
+      default: true,
+    },
+    resetBtn: {
+      type: Boolean,
+      default: true,
+    },
+    pageKeys: {
+      type: Object,
+      default: () => ({
+        page: "current",
+        size: "size",
+      }),
+    },
+    paramsGroups: {
+      type: Object,
+      default: () => null,
+    },
+    pagination: {
+      type: Boolean,
+      default: true,
+    },
+    showHeader: {
+      type: Boolean,
+      default: false,
+    },
+    checkboxSelect: {
+      type: Function,
+      default: () => {
+        return true;
+      },
+    },
+  },
+  components: {
+    TableHeader,
+    FuncComponent,
+  },
+  data() {
+    return {
+      total: 0,
+      fixed: false,
+      height: null,
+      tableData: [],
+      fieldName: "",
+      fieldSort: "",
+      loading: false,
+      tableColumns: [],
+      page: this.table.page || 1,
+      pageSize: this.table.pageSize || 10,
+      pageSizes: this.table.pageSizes || [10, 20, 50, 100],
+    };
+  },
+  activated() {
+    this.calcHeight();
+    this.scrollTop();
+  },
+  mounted() {
+    for (let p in this.columns) {
+      if (this.columns[p].fixed) {
+        this.fixed = true;
+        break;
+      }
+    }
+    if (this.fetcher) this.loadData();
+    if (this.fixed) this.scrollHandler(1);
+    window.addEventListener("resize", this.calcHeight);
+  },
+
+  beforeDestroy() {
+    if (this.fixed) this.scrollHandler(0);
+    window.removeEventListener("resize", this.calcHeight);
+  },
+  methods: {
+    mergeColumns(res) {
+      if (this.headers.length) {
+        const fields = [];
+        const tableColumns = this.headers.map((item) => {
+          fields.push(item.field);
+          // console.log(item);
+          return {
+            ...item,
+            prop: item.field,
+            // sortable: item.sort,
+            label: item.fieldName,
+            "sort-by": item.sortBy,
+            ...this.columns[item.field],
+            "sort-method": item.sortMethod,
+            // sortable: item.sort ? "custom" : false,
+            // sortable:'custom',
+            type: item.field === "index" ? "index" : null,
+            width: item.field === "index" ? "62px" : item.width,
+            index: item.field === "index" ? this.indexMethod : null,
+          };
+        });
+        for (let p in this.columns) {
+          if (!fields.includes(p)) {
+            tableColumns.push(this.columns[p]);
+          }
+        }
+        this.tableColumns = tableColumns;
+        // console.log(this.tableColumns);
+      } else {
+        if (!this.tableColumns.length) {
+          const fields = [];
+          const tableColumns = res.headers.map((item) => {
+            fields.push(item.field);
+            return {
+              ...item,
+              prop: item.field,
+              // sortable: item.sort,
+              label: item.fieldName,
+              "sort-by": item.sortBy,
+              ...this.columns[item.field],
+              "sort-method": item.sortMethod,
+              type: item.field === "index" ? "index" : null,
+              width: item.field === "index" ? "62px" : item.width,
+              index: item.field === "index" ? this.indexMethod : null,
+            };
+          });
+          for (let p in this.columns) {
+            if (!fields.includes(p)) {
+              tableColumns.push(this.columns[p]);
+            }
+          }
+          this.tableColumns = tableColumns;
+        }
+      }
+    },
+
+    async loadData(page) {
+      this.loading = true;
+      if (page) this.page = page;
+      try {
+        const params = {
+          [this.pageKeys.page]: this.page,
+          [this.pageKeys.size]: this.pageSize,
+        };
+        if (this.fieldSort) {
+          params.orderBy = this.fieldName;
+          params.orderByType = this.fieldSort;
+        }
+        if (this.$refs.refTableHeader) {
+          const data = this.$refs.refTableHeader.data;
+          for (let p in data) {
+            if (/^param-/.test(p)) {
+              const index = parseInt(p.substring(6));
+              if (this.params[index] && Array.isArray(this.params[index].key)) {
+                this.params[index].key.forEach((key, _index) => {
+                  params[key] = data[p]?.[_index] || "";
+                });
+              } else {
+                for (let _p in data[p]) {
+                  params[_p] = data[p][_p];
+                }
+              }
+            } else {
+              params[p] = data[p];
+              // eslint-disable-next-line valid-typeof
+              if (typeof data[p] == "object" || typeof data[p] == "array") {
+                params[p] = data[p].join(",");
+              }
+            }
+          }
+          // console.log(data);
+        }
+        //console.log(params);
+        let res = await this.fetcher(params);
+        setTimeout(() => {
+          if (!res) {
+            res = { data: [] };
+          }
+          this.tableData = res.data || res.records;
+          this.total = res.total;
+          this.loading = false;
+          this.mergeColumns(res);
+          this.calcHeight();
+          this.scrollTop();
+        }, 300);
+      } catch (e) {
+        // console.log(e);
+        this.loading = false;
+      }
+    },
+    //鎵嬪姩淇敼data
+    async changeData(data) {
+      this.tableData = data;
+      this.mergeColumns();
+      this.calcHeight();
+      this.scrollTop();
+      // console.log('鎵嬪姩淇敼data',data);
+    },
+
+    sortChange({ column, prop, order }) {
+      // console.log(column, prop, order);
+      this.fieldName = column.sortBy || prop;
+      if (order === "ascending") {
+        this.fieldSort = "ASC";
+      } else if (order === "descending") {
+        this.fieldSort = "DESC";
+      } else {
+        this.fieldSort = order;
+      }
+      this.handleCurrentChange(1);
+    },
+    handleSizeChange(pageSize) {
+      this.pageSize = pageSize;
+      this.loadData();
+    },
+    handleCurrentChange(page) {
+      this.page = page;
+      this.loadData();
+    },
+    // 搴忓彿
+    indexMethod(index) {
+      // return (this.page - 1) * this.pageSize + index + 1;
+      return index + 1;
+    },
+    // 璁剧疆params鍙傛暟鍊�
+    setParamsVal(val) {
+      this.$refs.refTableHeader.setParamsVal(val);
+    },
+    // 璁$畻楂樺害
+    calcHeight() {
+      this.height = "";
+      setTimeout(() => {
+        const _offsetHeight = this.$refs.refTableBody.offsetHeight;
+        this.height = _offsetHeight - 4 + "px";
+      }, 1);
+    },
+    // 婊氬姩鍒伴《閮�
+    scrollTop() {
+      const innerTable = this.$refs.refInnerTable.$el;
+      const bodyWrapper = innerTable.querySelector(".el-table__body-wrapper");
+      bodyWrapper.scrollTop = 0;
+    },
+    // 婊氬姩澶勭悊鍑芥暟
+    scrollHandler(bind) {
+      const _self = this;
+      const innerTable = this.$refs.refInnerTable.$el;
+      const doLayout = () => {
+        _self.$nextTick(() => _self.$refs.refInnerTable.doLayout());
+      };
+      const bodyWrapper = innerTable.querySelector(".el-table__body-wrapper");
+      if (bind) {
+        bodyWrapper.addEventListener("scroll", doLayout);
+      } else {
+        bodyWrapper.removeEventListener("scroll", doLayout);
+      }
+    },
+    clearSelection() {
+      this.$refs.refInnerTable.clearSelection();
+    },
+    toggleSelection(rows) {
+      if (rows) {
+        rows.forEach((row) => {
+          this.$refs.refInnerTable.toggleRowSelection(row);
+        });
+      } else {
+        this.$refs.refInnerTable.clearSelection();
+      }
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.base-table {
+  height: 100%;
+  display: flex;
+  overflow: hidden;
+  position: relative;
+  flex-direction: column;
+
+  ::v-deep {
+    .el-table {
+      background-color: transparent;
+      tr {
+        background-color: transparent;
+      }
+      .el-table__row {
+        td {
+          &:first-child {
+            border-left: 1px solid #e5e5e5;
+          }
+          &:last-child {
+            border-right: 1px solid #e5e5e5;
+          }
+        }
+      }
+    }
+    .el-table th.el-table__cell {
+      background-color: #e3edfc;
+      color: #182233;
+    }
+
+    .el-table__fixed::before,
+    .el-table__fixed-right::before {
+      display: none;
+    }
+
+    .el-table::before {
+      display: none;
+    }
+  }
+}
+
+.base-table-body {
+  flex: 1;
+  overflow: hidden;
+  // padding-bottom: 72px;
+  background-color: #fff;
+  // border-bottom: 1px solid #e4e7ed;
+}
+
+.base-table-footer {
+  bottom: 0;
+  width: 100%;
+  padding: 15px 0;
+  background-color: #fff;
+
+  &::v-deep {
+    .el-pagination {
+      padding: 0px 5px;
+      display: flex;
+      justify-content: flex-start;
+      font-weight: initial;
+    }
+
+    .el-pager li,
+    .el-pagination button {
+      min-width: 28px;
+      margin: 0 4px;
+      border-radius: 2px;
+    }
+
+    .el-pagination .btn-prev,
+    .el-pagination .btn-next {
+      padding-right: 6px;
+      padding-left: 6px;
+    }
+
+    .el-pagination .el-pagination__sizes .el-input__inner {
+      border: none;
+      background: #eee;
+      color: #666666;
+    }
+  }
+}
+</style>
diff --git a/src/components/App/index.js b/src/components/App/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..e7893499311f4132cbb4937554d425288efceecb
--- /dev/null
+++ b/src/components/App/index.js
@@ -0,0 +1,8 @@
+import AppHeader from "./AppHeader";
+import SubHeader from "./SubHeader";
+import BreadCrumb from "./BreadCrumb";
+import Empty from "./Empty";
+import SideBar from "./SideBar";
+import Table from "./Table";
+import ImgUploader from "./ImgUploader";
+export { AppHeader, SubHeader, BreadCrumb, Empty, SideBar, Table, ImgUploader };
diff --git a/src/components/Layout/InnerView/index.vue b/src/components/Layout/InnerView/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..228f290082393f1f57b095427b3c78e9f690c223
--- /dev/null
+++ b/src/components/Layout/InnerView/index.vue
@@ -0,0 +1,18 @@
+<template>
+  <div class="inner-view">
+    <slot></slot>
+  </div>
+</template>
+<script>
+export default {
+  name: "InnerView"
+};
+</script>
+<style lang="scss" scoped>
+.inner-view {
+  width: 100%;
+  height: 100%;
+  // overflow: hidden;
+  position: relative;
+}
+</style>
diff --git a/src/components/Layout/PaddingView/index.vue b/src/components/Layout/PaddingView/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b6c67ea74b844bd31e65bb4524b0a9f079167b1a
--- /dev/null
+++ b/src/components/Layout/PaddingView/index.vue
@@ -0,0 +1,19 @@
+<template>
+  <div class="padding-view">
+    <slot></slot>
+  </div>
+</template>
+<script>
+export default {
+  name: "InnerView"
+};
+</script>
+<style lang="scss" scoped>
+.padding-view {
+  width: 100%;
+  height: 100%;
+  // overflow: hidden;
+  position: relative;
+  padding: 32px 44px;
+}
+</style>
diff --git a/src/components/Layout/RouteView/index.vue b/src/components/Layout/RouteView/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ad81748618cf26d377ec16f2d93806da00745992
--- /dev/null
+++ b/src/components/Layout/RouteView/index.vue
@@ -0,0 +1,21 @@
+<template>
+  <div class="route-view">
+    <slot></slot>
+  </div>
+</template>
+<script>
+export default {};
+</script>
+<style lang="scss" scoped>
+.route-view {
+  flex: 1;
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+  // padding: 12px;
+  padding-top: 0;
+  overflow: hidden;
+  position: relative;
+  // background: #f2f2f2;
+}
+</style>
diff --git a/src/components/Layout/ScrollView/index.vue b/src/components/Layout/ScrollView/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c269201f52177b4bc3e55c077d0c655b5eb24806
--- /dev/null
+++ b/src/components/Layout/ScrollView/index.vue
@@ -0,0 +1,20 @@
+<template>
+  <div class="ScrollView">
+    <slot></slot>
+  </div>
+</template>
+<script>
+export default {};
+</script>
+<style lang="scss" scoped>
+.ScrollView {
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+  // padding: 12px;
+  padding-top: 0;
+  // overflow: hidden;
+  position: relative;
+  // background: #f2f2f2;
+}
+</style>
diff --git a/src/components/Layout/index.js b/src/components/Layout/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..2b8c0967f2aeb8a4a2418885579637cd24252f67
--- /dev/null
+++ b/src/components/Layout/index.js
@@ -0,0 +1,5 @@
+import RouteView from "./RouteView";
+import InnerView from "./InnerView";
+import PaddingView from "./PaddingView";
+
+export { RouteView, InnerView ,PaddingView};
diff --git a/src/config/index.js b/src/config/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..8acf4b5e36b4065c116963b1f5119f17ba1a48bc
--- /dev/null
+++ b/src/config/index.js
@@ -0,0 +1 @@
+export const appName = "";
diff --git a/src/directives/el-table/adaptive.js b/src/directives/el-table/adaptive.js
new file mode 100644
index 0000000000000000000000000000000000000000..c1018664047f30ae666e149c285e45a2e62f69e6
--- /dev/null
+++ b/src/directives/el-table/adaptive.js
@@ -0,0 +1,45 @@
+import {
+  addResizeListener,
+  removeResizeListener
+} from "element-ui/src/utils/resize-event";
+
+/**
+ * How to use
+ * <el-table height="100px" v-el-height-adaptive-table="{bottomOffset: 30}">...</el-table>
+ * el-table height is must be set
+ * bottomOffset: 30(default)   // The height of the table from the bottom of the page.
+ */
+
+const doResize = (el, binding, vnode) => {
+  const { componentInstance: $table } = vnode;
+
+  const { value } = binding;
+
+  if (!$table.height) {
+    throw new Error(`el-$table must set the height. Such as height='100px'`);
+  }
+  const bottomOffset = (value && value.bottomOffset) || 30;
+
+  if (!$table) return;
+
+  const height =
+    window.innerHeight - el.getBoundingClientRect().top - bottomOffset;
+  $table.layout.setHeight(height);
+  $table.doLayout();
+};
+
+export default {
+  bind(el, binding, vnode) {
+    el.resizeListener = () => {
+      doResize(el, binding, vnode);
+    };
+    // parameter 1 is must be "Element" type
+    addResizeListener(el, el.resizeListener);
+  },
+  inserted(el, binding, vnode) {
+    doResize(el, binding, vnode);
+  },
+  unbind(el) {
+    removeResizeListener(el, el.resizeListener);
+  }
+};
diff --git a/src/directives/el-table/index.js b/src/directives/el-table/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..ad0577f7c6117caf5f0a455826c879866c76c255
--- /dev/null
+++ b/src/directives/el-table/index.js
@@ -0,0 +1,13 @@
+import adaptive from "./adaptive";
+
+const install = function(Vue) {
+  Vue.directive("el-height-adaptive-table", adaptive);
+};
+
+if (window.Vue) {
+  window["el-height-adaptive-table"] = adaptive;
+  Vue.use(install); // eslint-disable-line
+}
+
+adaptive.install = install;
+export default adaptive;
diff --git a/src/directives/index.js b/src/directives/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..6b74181301828636f657db16e66170eed31720d0
--- /dev/null
+++ b/src/directives/index.js
@@ -0,0 +1,16 @@
+import Vue from "vue";
+
+Vue.use(Vue => {
+  (requireContext => {
+    const arr = requireContext.keys().map(requireContext);
+    (arr || []).forEach(directive => {
+      directive =
+        directive.__esModule && directive.default
+          ? directive.default
+          : directive;
+      Object.keys(directive).forEach(key => {
+        Vue.directive(key, directive[key]);
+      });
+    });
+  })(require.context("./", true, /^\.\/.*\/index\.js$/));
+});
diff --git a/src/filters/index.js b/src/filters/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..c57c83b2265ddc8dfc81171938c1dada051ff9ad
--- /dev/null
+++ b/src/filters/index.js
@@ -0,0 +1,13 @@
+import Vue from "vue";
+
+Vue.use(Vue => {
+  (requireContext => {
+    const arr = requireContext.keys().map(requireContext);
+    (arr || []).forEach(filter => {
+      filter = filter.__esModule && filter.default ? filter.default : filter;
+      Object.keys(filter).forEach(key => {
+        Vue.filter(key, filter[key]);
+      });
+    });
+  })(require.context("./", true, /^\.\/.*\/index\.js$/));
+});
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..862207284310c3ef4be38c0ed2977074e04bb065
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,45 @@
+import Vue from "vue";
+import Element from "element-ui";
+
+import "./filters";
+import "./directives";
+import App from "./App.vue";
+import store from "./store";
+import router from "./router";
+import "./assets/css/app.scss";
+import "./mock/index.js";
+import plugins from "./plugins";
+import * as utils from "./utils"; //
+import echarts from "./utils/echarts"; // npm install echarts --save
+
+// require("./mock");
+
+import dayjs from "dayjs";
+import "dayjs/locale/zh-cn";
+import updateLocale from "dayjs/plugin/updateLocale";
+
+dayjs.locale("zh-cn");
+dayjs.extend(updateLocale);
+dayjs.updateLocale("zh-cn", {
+  weekdays: [
+    "鏄熸湡澶�",
+    "鏄熸湡涓€",
+    "鏄熸湡浜�",
+    "鏄熸湡涓�",
+    "鏄熸湡鍥�",
+    "鏄熸湡浜�",
+    "鏄熸湡鍏�",
+  ],
+});
+
+Vue.prototype.$utils = utils;
+Vue.prototype.$echarts = echarts;
+Vue.use(plugins);
+Vue.use(Element);
+Vue.config.productionTip = false;
+
+new Vue({
+  store,
+  router,
+  render: (h) => h(App),
+}).$mount("#app");
diff --git a/src/mock/index.js b/src/mock/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..597d768437a14301cf462637bf343b4498b854b0
--- /dev/null
+++ b/src/mock/index.js
@@ -0,0 +1,9 @@
+const Mock = require("mockjs");
+let services = require.context("./services/", true, /\.js$/); //寮曞叆services鍐呮墍鏈塲s
+services.keys().forEach((key) => {
+  services(key); //鐢熸垚modal
+});
+
+Mock.setup({
+  timeout: 200, // setter delay time
+});
diff --git a/src/mock/services/test.js b/src/mock/services/test.js
new file mode 100644
index 0000000000000000000000000000000000000000..c2091ce3dd5edeaecefc922f9d80ba12df3314c1
--- /dev/null
+++ b/src/mock/services/test.js
@@ -0,0 +1,8 @@
+import Mock from "mockjs";
+Mock.mock("/mock/tagCloudData", "get", {
+  status: "0",
+  msg: "璁块棶鎴愬姛",
+  data: {},
+  errCode: null,
+  errMsg: null,
+});
diff --git a/src/mock/util.js b/src/mock/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..41b4e54a54b53b581957adb0585a7fce93ae45d3
--- /dev/null
+++ b/src/mock/util.js
@@ -0,0 +1,27 @@
+const responseBody = {
+  body: "",
+  cause: null,
+  state: true,
+  timestamp: 0,
+  message: "SUCCESS",
+};
+//妯℃嫙鎺ュ彛杩斿洖
+export const builder = (data, message, code = 0, headers = {}) => {
+  responseBody.body = data;
+  if (message !== undefined && message !== null) {
+    responseBody.message = message;
+  }
+  if (code !== undefined && code !== 0) {
+    responseBody.code = code;
+    responseBody._status = code;
+  }
+  if (
+    headers !== null &&
+    typeof headers === "object" &&
+    Object.keys(headers).length > 0
+  ) {
+    responseBody._headers = headers;
+  }
+  responseBody.timestamp = new Date().getTime();
+  return responseBody;
+};
diff --git a/src/plugins/index.js b/src/plugins/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f7d803ff579f5166538dbc05d3f6c1089db0b30
--- /dev/null
+++ b/src/plugins/index.js
@@ -0,0 +1,41 @@
+// import dayjs from 'dayjs';
+
+import Table from "@/components/App/Table";
+import PaddingView from "@/components/Layout/PaddingView";
+import InnerView from "@/components/Layout/InnerView";
+// import TableView from "@/components/Layout/TableView";
+import ScrollView from "@/components/Layout/ScrollView";
+// import PubCard from "@/components/App/PubCard";
+// import AlarmNum from "@/components/App/AlarmNum";
+import BackTitle from "@/components/App/BackTitle";
+
+// import CardView from '@/components/Layout/CardView';
+// import RouteView from '@/components/Layout/RouteView';
+
+/* eslint-disable no-new */
+// export const buildQuery = (data) => qs.stringify(data);
+
+export default {
+  install(app) {
+    // app.config.globalProperties.$filters = {
+    //   formatDate(value = Date.now(), reg = 'YYYY-MM-DD HH:mm:ss') {
+    //     return dayjs(value).format(reg);
+    //   },
+    // };
+    /**
+     * 娉ㄥ唽鍏ㄥ眬閫氱敤缁勪欢
+     */
+    app.component("PaddingView", PaddingView);
+    app.component("InnerView", InnerView);
+    // app.component("TableView", TableView);
+    app.component("ScrollView", ScrollView);
+
+    app.component("pub-table", Table);
+    app.component("BackTitle", BackTitle);
+    // app.component("PubCard", PubCard);
+    // app.component("AlarmNum", AlarmNum);
+
+    // app.component('CardView', CardView);
+    // app.component('RouteView', RouteView);
+  },
+};
diff --git a/src/router/index.js b/src/router/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2619263bf970f0fd24669bb8e065bbdd4275ce0
--- /dev/null
+++ b/src/router/index.js
@@ -0,0 +1,112 @@
+import Vue from "vue";
+import VueRouter from "vue-router";
+import store from "@/store";
+
+const originalPush = VueRouter.prototype.push;
+VueRouter.prototype.push = function push(location) {
+  return originalPush.call(this, location).catch(() => {});
+};
+
+Vue.use(VueRouter);
+
+const recursion = function(paths, root, requireContext) {
+  const dirs = paths.map((item) => item.split("/")[0]);
+  const sets = Array.from(new Set(dirs));
+  const router = sets.map((it) => {
+    const reg = new RegExp("^" + it + "/");
+    const _paths = paths
+      .filter((item) => item !== it)
+      .filter((item) => reg.test(item))
+      .map((item) => item.substring(it.length + 1));
+    const component = requireContext(root + it + "/index.vue").default;
+    const children = recursion(_paths, root + it + "/", requireContext);
+    const routerItem = {
+      path: it,
+      children,
+      component,
+      name: component.name || it,
+      meta: component.meta || {},
+      redirect: component.meta?.redirect,
+    };
+    if (!routerItem.children.length) delete routerItem.children;
+    if (!routerItem.redirect === undefined) delete routerItem.redirect;
+    return routerItem;
+  });
+  return router;
+};
+
+const genRouter = function(requireContext) {
+  // 涓嶈鍙朿omponents鏂囦欢澶逛笅鐨勬枃浠�
+  const paths = requireContext
+    .keys()
+    .map((item) => item.slice(2, -10))
+    .filter((item) => !/components/.test(item));
+  const router = recursion(paths, "./", requireContext).map((item) => ({
+    ...item,
+    path: "/" + item.path,
+  }));
+  return router;
+};
+
+const mapSort = function(menus) {
+  console.log(menus);
+  //閫掑綊锛屽璺敱杩涜鎺掑簭锛屾坊鍔爊ame鍜宨con
+  const _menus = menus
+    .sort((a, b) => {
+      return a.meta.sort - b.meta.sort;
+    })
+    .filter((item) => !item.meta.hideMenu);
+
+  _menus.forEach((element) => {
+    if (element.children && element.children.length) {
+      element.children = element.children.filter((item) => !item.meta.hideMenu);
+      if (element.children.length) {
+        element.children = mapSort(element.children);
+      } else {
+        delete element.children;
+      }
+    }
+  });
+  return _menus;
+};
+const routes = [
+  {
+    path: "",
+    redirect: "/index",
+  },
+  {
+    path: "/login",
+    component: () =>
+      import(/* webpackChunkName: "accout" */ "@/views/login.vue"),
+    // redirect: "/index"
+  },
+];
+
+const requireContext = require.context(
+  "@/views/",
+  true,
+  /^\.\/.*\/index\.vue$/
+);
+
+const asyncRoutes = genRouter(requireContext); //
+const asyncRoutesSort = mapSort(asyncRoutes); //绛涢€夋帓搴忓悗鐨勮矾鐢�
+
+const router = new VueRouter({
+  routes: routes,
+  base: process.env.BASE_URL,
+});
+
+router.beforeEach((to, from, next) => {
+  // console.log(to, from);
+  if (to.meta.keepAlive) {
+    store.dispatch("app/addCacheRoutes", to.name);
+  }
+  next();
+});
+
+router.addRoutes(asyncRoutes);
+console.log(router);
+export default router;
+export { asyncRoutes, asyncRoutesSort };
+
+// this.$store.dispatch("app/removeCacheRoutes", findItem.parent.name);
diff --git a/src/store/app/index.js b/src/store/app/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..d41d5d76849e3362fc9ab3a834f1c8d286113f31
--- /dev/null
+++ b/src/store/app/index.js
@@ -0,0 +1,35 @@
+export default {
+  namespaced: true,
+  state: {
+
+    cacheRoutes: [], // 褰撳墠缂撳瓨鐨勮矾鐢�
+  },
+  actions: {
+
+    //娣诲姞缂撳瓨璺敱
+    addCacheRoutes({
+      state,
+      commit
+    }, paylod) {
+      if (!state.cacheRoutes.includes(paylod)) {
+        state.cacheRoutes.push(paylod);
+        commit("SET_CACHE_ROUTES", state.cacheRoutes);
+      }
+    },
+    //鍒犻櫎闇€瑕佺紦瀛樼殑璺敱
+    removeCacheRoutes({
+      state,
+      commit
+    }, paylod) {
+      const data = state.cacheRoutes.filter(item => item !== paylod);
+      commit("SET_CACHE_ROUTES", data);
+    },
+  },
+  mutations: {
+
+    //璁剧疆缂撳瓨璺敱
+    SET_CACHE_ROUTES(state, data = []) {
+      state.cacheRoutes = data;
+    },
+  }
+};
\ No newline at end of file
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..24f2617c4f60e084054325bae63dd11cb16dcd4d
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,19 @@
+import Vue from "vue";
+import Vuex from "vuex";
+
+Vue.use(Vuex);
+
+const modules = {};
+const requireContext = require.context("./", true, /^\.\/.*\/index\.js$/);
+requireContext.keys().forEach(key => {
+  const _module = requireContext(key);
+  modules[key.slice(2, -9)] =
+    _module.__esModule && _module.default ? _module.default : _module;
+});
+
+export default new Vuex.Store({
+  state: {},
+  actions: {},
+  mutations: {},
+  modules: modules
+});
diff --git a/src/store/tabledata/index.js b/src/store/tabledata/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..b8eb2ece0636e92727942ca1cf36485aaf224d0b
--- /dev/null
+++ b/src/store/tabledata/index.js
@@ -0,0 +1,30 @@
+import testCardPhoto from "@/assets/img/test-card-photo.png";
+export default {
+  namespaced: true,
+  state: {
+    //鎴戠殑璇佺収鍒楄〃鏁版嵁
+    photo: [
+      {
+        id: 1,
+        name: "鐢靛瓙绀句繚鍗�",
+        pic: testCardPhoto,
+        picList: [testCardPhoto],
+        time: "2023-01-13",
+      },
+    ],
+  },
+  actions: {
+    //淇敼鍒楄〃鏁版嵁
+    changeTableData({ commit }, paylod) {
+      const { field, data } = paylod;
+      console.log(field, data);
+      commit("SET_TABLE_DATA", { field, data });
+    },
+  },
+  mutations: {
+    //璁剧疆缂撳瓨璺敱
+    SET_TABLE_DATA(state, { field, data = [] }) {
+      state[field] = data;
+    },
+  },
+};
diff --git a/src/utils/echarts.js b/src/utils/echarts.js
new file mode 100644
index 0000000000000000000000000000000000000000..8f3621612bd8512e8b197ff209794377e8e04583
--- /dev/null
+++ b/src/utils/echarts.js
@@ -0,0 +1,42 @@
+// 寮曞叆 echarts 鏍稿績妯″潡锛屾牳蹇冩ā鍧楁彁渚涗簡 echarts 浣跨敤蹇呴』瑕佺殑鎺ュ彛銆�
+import * as echarts from "echarts/core";
+ 
+/** 寮曞叆浠绘剰鍥捐〃锛岃繖閲屽紩鍏ョ殑鏄煴鐘跺浘and鎶樼嚎鍥惧浘琛紙鍥捐〃鍚庣紑閮戒负 Chart锛�  */
+import { BarChart, LineChart,PieChart ,PictorialBarChart} from "echarts/charts";
+ 
+// 寮曞叆鎻愮ず妗嗭紝鏍囬锛岀洿瑙掑潗鏍囩郴锛屾暟鎹泦锛屽唴缃暟鎹浆鎹㈠櫒缁勪欢锛岀粍浠跺悗缂€閮戒负 Component
+import {
+  TitleComponent,
+  TooltipComponent,
+  GridComponent,
+  DatasetComponent,
+  TransformComponent,
+  LegendComponent,
+  
+} from "echarts/components";
+ 
+// 鏍囩鑷姩甯冨眬锛屽叏灞€杩囨浮鍔ㄧ敾绛夌壒鎬�
+import { LabelLayout, UniversalTransition } from "echarts/features";
+ 
+// 寮曞叆 Canvas 娓叉煋鍣紝娉ㄦ剰寮曞叆 CanvasRenderer 鎴栬€� SVGRenderer 鏄繀椤荤殑涓€姝�
+import { CanvasRenderer } from "echarts/renderers";
+ 
+// 娉ㄥ唽蹇呴』鐨勭粍浠�
+echarts.use([
+  TitleComponent,
+  TooltipComponent,
+  GridComponent,
+  DatasetComponent,
+  TransformComponent,
+  LegendComponent,
+  BarChart,
+  LabelLayout,
+  UniversalTransition,
+  CanvasRenderer,
+  LineChart,
+  PieChart,
+  PictorialBarChart
+]);
+ 
+// 瀵煎嚭
+export default echarts;
\ No newline at end of file
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..4827b8f2d4c3132d2d7baf2e85059e6e1e52e3f0
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,42 @@
+import dayjs from "dayjs";
+//灏嗚幏鍙栫殑鏁版嵁鍥炴樉琛ㄥ崟
+const setFormValue = (form, content) => {
+  let res = {};
+  for (const key in form) {
+    if (Object.hasOwnProperty.call(content, key)) {
+      res[key] = content[key];
+    } else {
+      res[key] = form[key];
+    }
+  }
+  return res;
+};
+//鏍煎紡鍖栨椂闂�
+const formatTimeWithDayjs = (time, formar = "YYYY-MM-DD HH:mm:ss") => {
+  if (time.indexOf) {
+    if (time.indexOf("-") < 0 && time.indexOf(":") < 0) {
+      time = Number(time);
+    } else {
+      return dayjs(time).format(formar);
+    }
+  }
+  if (time <= 10000000000) {
+    time = time * 1000;
+  }
+  return dayjs(time).format(formar);
+};
+//灏嗕笂浼犵殑鏂囦欢杞负base64
+const file2Base64 = (file) => {
+  return new Promise((resolve) => {
+    // console.log(file);
+    let reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.onload = function(e) {
+      // console.log(e.target.result);
+      resolve(e.target.result);
+      // return e.target.result;
+    };
+  });
+};
+
+export { setFormValue, formatTimeWithDayjs, file2Base64 };
diff --git a/src/utils/request.js b/src/utils/request.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd3855255a68b1fd93c3bcd333c65610fcf8e116
--- /dev/null
+++ b/src/utils/request.js
@@ -0,0 +1,97 @@
+import axios from "axios";
+import { MessageBox, Message } from "element-ui";
+import store from "@/store";
+// import { getToken } from "@/utils/auth";
+
+const url = [
+  // "/api/v1/app/uploadAppLogo",
+  // "/api/v1/account/changeAvatar",
+  // "/api/v1/account/importFromExcel",
+  // "/api/v1/account/downloadExcelModel",
+  // "/api/v1/basic/logout"
+]; //涓婁紶鏂囦欢鎺ュ彛
+
+// create an axios instance
+const service = axios.create({
+  timeout: 30000,
+  withCredentials: true,
+  credentials: "include",
+  baseURL: process.env.VUE_APP_BASE_API
+});
+
+service.interceptors.request.use(
+  config => {
+    if (store.getters.token && url.indexOf(config.url) < 0) {
+      // let each request carry token
+      // ['X-Token'] is a custom headers key
+      // please modify it according to the actual situation
+      // config.headers["token"] = getToken();
+    } else {
+      if (config.url != "/api/v1/basic/logout") {
+        config.params = {
+          ...config.params
+          // token: getToken()
+        };
+      }
+    }
+    return config;
+  },
+  error => {
+    console.error(error);
+    return Promise.reject(error);
+  }
+);
+
+service.interceptors.response.use(
+  response => {
+    if (response.config.responseType === "blob") return response;
+    const res = response.data;
+    // if the custom code is not 20000, it is judged as an error.
+    if (res.state !== true) {
+      const [code, message] = res.message.split(":");
+      /* 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; */
+      /* res.code === 50008 || res.code === 50012 || res.code === 50014 */
+      if (code === "AUTH-ERROR-01") {
+        // to re-login
+        MessageBox.confirm("鎮ㄥ凡娉ㄩ攢锛屽彲浠ョ暀鍦ㄦ椤碉紝鎴栭噸鏂扮櫥褰�", "纭娉ㄩ攢", {
+          confirmButtonText: "閲嶆柊鐧诲綍",
+          cancelButtonText: "鐣欏湪姝ら〉",
+          type: "warning"
+        }).then(() => {
+          store.dispatch("logout");
+        });
+      } else {
+        Message({
+          message: message || "Error",
+          type: "error",
+          duration: 5 * 1000,
+          showClose: true
+        });
+      }
+      return Promise.reject(new Error(res.message || "Error"));
+    } else {
+      return res.body;
+    }
+  },
+  error => {
+    console.error(error);
+    Message({
+      message:
+        error.message === "Request failed with status code 404"
+          ? "鏆傛棤璁块棶鏉冮檺锛岃鑱旂郴绠$悊鍛�"
+          : [
+              "timeout of 5000ms exceeded",
+              "Network Error",
+              "Request failed with status code 404"
+            ].includes(error.message)
+          ? "缃戠粶閿欒锛岃绋嶅悗鍐嶈瘯"
+          : error.message,
+      type: "error",
+      duration: 8 * 1000,
+      showClose: true
+    });
+    return Promise.reject(error);
+  }
+);
+
+export default service;
diff --git a/src/views/feedback/index.vue b/src/views/feedback/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b07a5b610b59163f1ad81419e56fb6cff588c169
--- /dev/null
+++ b/src/views/feedback/index.vue
@@ -0,0 +1,20 @@
+<template>
+  <InnerView> </InnerView>
+</template>
+<script>
+export default {
+  name: "feedback",
+  meta: {
+    sort: 5,
+    title: "鎰忚鍙嶉",
+    iconImg: "feedback",
+    iconImgActive: "feedbackActive",
+  },
+  components: {},
+  data() {
+    return {};
+  },
+  watch: {},
+};
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/views/footprint/index.vue b/src/views/footprint/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1d8e2a90b0436d47427de06613cb3becc62bae89
--- /dev/null
+++ b/src/views/footprint/index.vue
@@ -0,0 +1,81 @@
+<template>
+  <InnerView class="InnerView">
+    <pub-table
+      :headers="headers"
+      :fetcher="fetcher"
+      :params="params"
+      ref="table"
+    >
+    </pub-table>
+  </InnerView>
+</template>
+<script>
+export default {
+  name: "footprint",
+  meta: {
+    sort: 3,
+    title: "鎴戠殑瓒宠抗",
+    iconImg: "footprint",
+    iconImgActive: "footprintActive",
+  },
+  components: {},
+  data() {
+    return {
+      headers: [
+        {
+          field: "index",
+          fieldName: "搴忓彿",
+          type: "index",
+        },
+        {
+          field: "footprint",
+          fieldName: "瓒宠抗",
+        },
+        {
+          field: "time",
+          fieldName: "鏃堕棿",
+        },
+      ],
+      params: [],
+      testData: [
+        {
+          footprint: "濂藉樊璇�-鍖哄幙",
+          time: "2023-07-13 17:14:43",
+        },
+        {
+          footprint: "绐佸彂鍏叡鍗敓浜嬩欢棰勮淇℃伅鍙戝竷",
+          time: "2023-04-25 15:28:36",
+        },
+        {
+          footprint: "缁勭粐瀹炴柦鍔ㄧ墿鐤梾寮哄埗鍏嶇柅",
+          time: "2023-03-15 14:22:16",
+        },
+        {
+          footprint: "瀹d紶楗叉枡瀹夊叏鐭ヨ瘑銆佺煡閬撳悎鐞嗕娇鐢ㄩゲ鏂�",
+          time: "2023-02-18 14:28:15",
+        },
+        {
+          footprint: "澶变笟淇濋櫓鏈嶅姟涓殑鑱屼笟浠嬬粛琛ヨ创鐢抽",
+          time: "2023-02-14 12:22:33",
+        },
+      ],
+      fetcher: async () => {
+        // console.log(this);
+        //console.log(params);
+        // let res = await api.publicEquipment.getBiogasDeviceInfoPage(params);
+        let res = {
+          total: this.testData.length,
+          data: this.testData,
+        };
+        return res;
+      },
+    };
+  },
+  watch: {},
+};
+</script>
+<style lang="scss" scoped>
+.InnerView {
+  padding-top: 24px;
+}
+</style>
diff --git a/src/views/login.vue b/src/views/login.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b294627b0a62abb238a329b17f50a8b45739e2cb
--- /dev/null
+++ b/src/views/login.vue
@@ -0,0 +1,144 @@
+<template>
+  <div class="login" v-loading="loading">
+    <div class="login-box">
+      <el-form
+        :model="ruleForm"
+        :rules="rules"
+        ref="ruleForm"
+        label-width="0px"
+        class="demo-ruleForm"
+      >
+        <el-form-item label="" prop="user">
+          <el-input placeholder="璇疯緭鍏ョ敤鎴峰悕" v-model="ruleForm.user">
+            <i slot="prefix" class="el-input__icon el-icon-user"></i>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="" prop="password">
+          <el-input
+            placeholder="璇疯緭鍏ュ瘑鐮�"
+            v-model="ruleForm.password"
+            show-password
+            @keyup.enter.native="login()"
+          >
+            <i slot="prefix" class="el-input__icon el-icon-lock"></i>
+          </el-input>
+        </el-form-item>
+      </el-form>
+
+      <el-button type="primary" style="width: 100%" @click="login"
+        >鐧诲綍</el-button
+      >
+    </div>
+  </div>
+</template>
+<script>
+// import { CardView } from "@/components/Layout";
+import { appName } from "@/config";
+import { common } from "@/api";
+export default {
+  name: "login",
+  meta: {
+    title: "鐧诲綍",
+  },
+  components: {
+    // CardView
+  },
+  data() {
+    return {
+      loading: false,
+      appName,
+      user: "",
+      password: "",
+
+      ruleForm: {
+        user: "",
+        password: "",
+      },
+      rules: {
+        user: [
+          {
+            required: true,
+            message: "璇疯緭鍏ョ敤鎴峰悕",
+            trigger: ["change", "burl"],
+          },
+        ],
+        password: [
+          {
+            required: true,
+            message: "璇疯緭鍏ュ瘑鐮�",
+            trigger: ["change", "burl"],
+          },
+        ],
+      },
+    };
+  },
+  watch: {},
+  methods: {
+    login() {
+      this.$refs.ruleForm.validate((valid) => {
+        if (valid) {
+          this.loading = true;
+          common
+            .login(this.ruleForm)
+            .then((res) => {
+              // console.log(res.token);
+              if (res.token) {
+                localStorage.setItem("token", res.token);
+                this.loading = false;
+                this.$message.success("鐧婚檰鎴愬姛");
+                this.$router.replace("/index");
+              }
+            })
+            .catch(() => {
+              this.loading = false;
+            });
+
+          // alert('submit!');
+        } else {
+          // console.log('error submit!!');
+          return false;
+        }
+      });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.login {
+  // position: fixed;
+  width: 100vw;
+  height: 100vh;
+  background-color: #f5f6fa;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.login-box {
+  width: 500px;
+  height: 360px;
+  border-radius: 4px;
+  background: #fff;
+  padding: 30px 50px;
+}
+.title {
+  display: flex;
+  align-items: center;
+  font-size: 20px;
+  font-weight: bold;
+  padding-bottom: 10px;
+  width: 100%;
+  justify-content: center;
+  span {
+    padding-left: 30px;
+  }
+  img {
+    width: 80px;
+    height: auto;
+  }
+}
+// ::v-deep {
+// .el-input {
+//   margin-bottom: 30px;
+// }
+// }
+</style>
diff --git a/src/views/photo/edit/index.vue b/src/views/photo/edit/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b4aaa3a66ba18884f35f1c2c6aa90f7f1345354a
--- /dev/null
+++ b/src/views/photo/edit/index.vue
@@ -0,0 +1,153 @@
+<template>
+  <div class="pub-form">
+    <el-form
+      ref="form"
+      :model="form"
+      label-width="98px"
+      label-position="right"
+      :rules="rules"
+      size="small"
+    >
+      <el-form-item prop="name" label="璇佺収鍚嶇О:">
+        <el-input v-model="form.name" placeholder="璇疯緭鍏�" clearable></el-input>
+      </el-form-item>
+
+      <el-form-item prop="pic" label="璇佺収鐓х墖:">
+        <div class="tip-text">鍙寮狅紝鏀寔png/jpg</div>
+        <ImgUploader v-model="form.picList"></ImgUploader>
+      </el-form-item>
+
+      <el-form-item prop="time" label="鍔炵悊鏃堕棿:">
+        <div class="flex-between" style="width:300px">
+          <el-date-picker
+            v-model="form.time"
+            type="date"
+            value-format="yyyy-MM-DD"
+            placeholder="閫夋嫨鏃ユ湡"
+          >
+          </el-date-picker>
+        </div>
+      </el-form-item>
+
+      <el-form-item>
+        <el-button
+          type="primary"
+          size="small"
+          @click="submit"
+          :loading="loading"
+          >鎻愪氦</el-button
+        >
+        <el-button type="primary" plain size="small" @click="close"
+          >杩斿洖</el-button
+        >
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+<script>
+import { mapState } from "vuex";
+import ImgUploader from "@/components/App/ImgUploader";
+export default {
+  name: "photoEdit",
+  components: { ImgUploader },
+  meta: {
+    sort: 1,
+    title: "鎴戠殑璇佺収",
+    hidden: true,
+    activeName: "photo",
+  },
+  data: () => {
+    return {
+      id: "",
+      loading: false,
+
+      form: {
+        id: "",
+        name: "",
+        pic: "",
+        picList: [],
+        time: "",
+      },
+      rules: {
+        // name: [
+        //   { required: true, message: "璇疯緭鍏ヨ鑹插悕绉�", trigger: "change" },
+        // ],
+      },
+    };
+  },
+  computed: {
+    // import { mapState } from "vuex";
+    ...mapState({
+      testData: (state) => state.tabledata.photo,
+    }),
+  },
+  mounted() {
+    let { id } = this.$route.query;
+    if (id) {
+      this.$set(this.form, "id", id);
+      this.getDetail(id);
+    }
+  },
+  methods: {
+    close() {
+      this.$router.back(-1);
+    },
+    getDetail(id) {
+      let findItem = this.testData.find((item) => item.id == id);
+
+      if (findItem) {
+        this.form = this.$utils.setFormValue(this.form, findItem);
+      }
+    },
+    submit() {
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          let datas = { ...this.form };
+          // datas.picList = [...datas.pic];
+          datas.pic = datas.picList[0];
+          // console.log(datas);
+          if (datas.id) {
+            let resList = this.testData.map((item) => {
+              if (item.id == datas.id) {
+                return datas;
+              } else {
+                return item;
+              }
+            });
+            // console.log(resList);
+            let query = { field: "photo", data: resList };
+            this.$store.dispatch("tabledata/changeTableData", query);
+          } else {
+            //璁$畻鏂癷d
+            let newId = this.testData[this.testData.length - 1]?.id
+              ? this.testData[this.testData.length - 1]?.id + 2
+              : 2;
+            let resList = [...this.testData, { ...datas, id: newId }];
+            let query = { field: "photo", data: resList };
+            this.$store.dispatch("tabledata/changeTableData", query);
+          }
+          // console.log(this.testData);
+
+          this.$message.success("鎿嶄綔鎴愬姛");
+          this.$router.back(-1);
+        } else {
+          return false;
+        }
+      });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+::v-deep {
+  .el-date-editor.el-input,
+  .el-date-editor.el-input__inner {
+    width: 132px;
+  }
+}
+.tip-text {
+  padding-bottom: 8px;
+  font-size: 12px;
+  color: #a6a6a6;
+}
+</style>
diff --git a/src/views/photo/index.vue b/src/views/photo/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1e721694e12790555857a0745920e6d973c232d1
--- /dev/null
+++ b/src/views/photo/index.vue
@@ -0,0 +1,32 @@
+<template>
+  <!-- <transition :name="transition"> -->
+  <keep-alive :include="cacheRoutes">
+    <router-view class="child-view"></router-view>
+  </keep-alive>
+  <!-- </transition> -->
+</template>
+<script>
+import { mapState } from "vuex";
+// import { InnerView, RouteView } from "@/components/Layout";
+export default {
+  name: "photo",
+  // components: { InnerView, RouteView },
+  meta: {
+    sort: 2,
+    title: "鎴戠殑璇佺収",
+    iconImg: "photo",
+    iconImgActive: "photoActive",
+    redirect: "/photo/list",
+  },
+  computed: {
+    ...mapState({
+      cacheRoutes: (state) => state.app.cacheRoutes,
+    }),
+  },
+  data() {
+    return {};
+  },
+  watch: {},
+};
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/views/photo/list/index.vue b/src/views/photo/list/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..fa9401a677ff91ca3f78e911c383814a950d98f8
--- /dev/null
+++ b/src/views/photo/list/index.vue
@@ -0,0 +1,178 @@
+<template>
+  <InnerView>
+    <pub-table
+      :headers="headers"
+      :fetcher="fetcher"
+      :params="params"
+      :pagination="false"
+      showHeader
+      ref="table"
+    >
+      <div slot="btnRight" class="btnRight">
+        <el-button type="primary" size="small" @click="openEdit"
+          >鏂板</el-button
+        >
+      </div>
+    </pub-table>
+  </InnerView>
+</template>
+
+<script>
+// import testCardPhoto from "@/assets/img/test-card-photo.png";
+import { mapState } from "vuex";
+export default {
+  name: "photoList",
+  components: {},
+  meta: {
+    sort: 0,
+    title: "鎴戠殑璇佺収",
+    hidden: true,
+    activeName: "photo",
+    keepAlive: true,
+  },
+  computed: {
+    // import { mapState } from "vuex";
+    ...mapState({
+      testData: (state) => state.tabledata.photo,
+    }),
+  },
+  data() {
+    return {
+      EditShow: false,
+      headers: [
+        {
+          field: "name",
+          fieldName: "璇佺収鍚嶇О",
+        },
+        {
+          field: "pic",
+          fieldName: "璇佺収鍥剧墖",
+          renderFun: (e) => this.renderPic(e),
+        },
+        {
+          field: "time",
+          fieldName: "鍔炵悊鏃堕棿",
+          width: 100,
+        },
+        {
+          field: "handle",
+          fieldName: "鎿嶄綔",
+          width: 160,
+          renderFun: (e) => this.renderBtns(e),
+        },
+      ],
+      params: [
+        {
+          label: "璇佺収鍚嶇О",
+          key: "name",
+          placeholder: "璇疯緭鍏�",
+        },
+      ],
+      // testData: [
+      //   {
+      //     id: 1,
+      //     name: "鐢靛瓙绀句繚鍗�",
+      //     pic: testCardPhoto,
+      //     time: "2023-01-13",
+      //   },
+      // ],
+      fetcher: async () => {
+        // console.log(this);
+        //console.log(params);
+        // let res = await api.publicEquipment.getBiogasDeviceInfoPage(params);
+        let res = {
+          total: this.testData.length,
+          data: this.testData,
+        };
+
+        return res;
+      },
+    };
+  },
+  activated() {
+    this.$nextTick(() => {
+      this.$refs.table.loadData();
+    });
+  },
+  methods: {
+    renderBtns(e) {
+      let that = this;
+      return (
+        <div>
+          <el-button
+            onClick={() => that.openEdit(e.row)}
+            size="mini"
+            plain
+            type="primary"
+          >
+            缂栬緫
+          </el-button>
+          <el-button
+            onClick={() => that.delItem(e.row)}
+            size="mini"
+            plain
+            type="warning"
+          >
+            鍒犻櫎
+          </el-button>
+        </div>
+      );
+    },
+    renderPic(e) {
+      return (
+        <div>
+          <el-image
+            src={e.row.pic}
+            class="pic-url"
+            preview-src-list={e.row.picList ? e.row.picList : [e.row.pic]}
+          ></el-image>
+        </div>
+      );
+    },
+    openEdit(row) {
+      // console.log(row);
+      let id = row.id || ''
+      this.$router.push(`/photo/edit?id=${id}`);
+    },
+
+    submitEdit(datas) {
+      // console.log(datas);
+      if (datas.id) {
+        this.testData = this.testData.map((item) => {
+          if (item.id == datas.id) {
+            return datas;
+          } else {
+            return item;
+          }
+        });
+      } else {
+        this.testData = [
+          ...this.testData,
+          { ...datas, id: this.testData.length + 2 },
+        ];
+      }
+
+      this.$refs.table.loadData();
+    },
+    delItem(row) {
+      this.$confirm(`纭鍒犻櫎姝ゆ潯鏁版嵁鍚楋紵`, "鎻愮ず", {
+        type: "warning",
+      })
+        .then(() => {
+          this.testData = this.testData.filter((item) => item.id != row.id);
+          this.$refs.table.loadData();
+        })
+        .catch(() => {});
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+  .pic-url {
+    width: 116px;
+    height: 68px;
+  }
+}
+</style>
diff --git a/src/views/profile/EditEducation.vue b/src/views/profile/EditEducation.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2ee46f9998d008d79498ffaafd2ca3970a20abcf
--- /dev/null
+++ b/src/views/profile/EditEducation.vue
@@ -0,0 +1,199 @@
+<template>
+  <PubDialog
+    v-model="dialogVisible"
+    :title="`${id ? '缂栬緫' : '鏂板'}鏁欒偛淇℃伅`"
+    @beforeClose="beforeClose"
+  >
+    <div class="pub-form">
+      <el-form
+        ref="form"
+        :model="form"
+        label-width="98px"
+        label-position="right"
+        :rules="rules"
+        size="small"
+      >
+        <el-form-item prop="school" label="瀛︽牎:">
+          <el-input
+            v-model="form.school"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+
+        <el-form-item prop="education" label="瀛﹀巻:">
+          <el-input
+            v-model="form.education"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+
+        <el-form-item prop="startTime" label="鏃堕棿:">
+          <div class="flex-between" style="width:300px">
+            <el-date-picker
+              v-model="form.startTime"
+              type="date"
+              value-format="yyyy-MM-DD"
+              placeholder="閫夋嫨鏃ユ湡"
+            >
+            </el-date-picker>
+            <span>鈥斺€�</span>
+            <el-date-picker
+              v-model="form.endTime"
+              type="date"
+              value-format="yyyy-MM-DD"
+              placeholder="閫夋嫨鏃ユ湡"
+            >
+            </el-date-picker>
+          </div>
+        </el-form-item>
+
+        <el-form-item prop="college" label="瀛﹂櫌:">
+          <el-input
+            v-model="form.college"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item prop="special" label="涓撲笟:">
+          <el-input
+            v-model="form.special"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item prop="studentID" label="瀛﹀彿:">
+          <el-input
+            v-model="form.studentID"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button
+            type="primary"
+            size="small"
+            @click="submit"
+            :loading="loading"
+            >鎻愪氦</el-button
+          >
+          <el-button type="primary" plain size="small" @click="close"
+            >杩斿洖</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </div>
+  </PubDialog>
+</template>
+<script>
+import PubDialog from "@/components/App/PubDialog";
+export default {
+  name: "VideoDialog",
+  components: { PubDialog },
+  data: () => {
+    return {
+      id: "",
+      loading: false,
+
+      form: {
+        school: "",
+        education: "",
+        startTime: "",
+        endTime: "",
+        college: "",
+        special: "",
+        studentID: "",
+      },
+      rules: {
+        // name: [
+        //   { required: true, message: "璇疯緭鍏ヨ鑹插悕绉�", trigger: "change" },
+        // ],
+      },
+    };
+  },
+  props: {
+    value: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  computed: {
+    dialogVisible: {
+      get: function(that) {
+        return that.value;
+      },
+      set: function(newValue) {
+        this.$emit("input", newValue);
+      },
+    },
+  },
+  methods: {
+    resetForm() {
+      this.form = {
+        id: "",
+        school: "",
+        education: "",
+        startTime: "",
+        endTime: "",
+        college: "",
+        special: "",
+        studentID: "",
+      };
+    },
+    beforeClose() {
+      // console.log(this.$refs.form?.resetFields);
+      // this.$refs.form?.resetFields();
+      this.resetForm();
+      // this.form.endTime = "";
+      this.dialogVisible = false;
+    },
+    open(row) {
+      if (row) {
+        this.id = row.id;
+        this.form = this.$utils.setFormValue(this.form, row);
+      }
+      this.dialogVisible = true;
+    },
+    close() {
+      this.beforeClose();
+    },
+    submit() {
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          let datas = { ...this.form };
+          if (this.id) datas.id = this.id;
+          this.$message.success("鎿嶄綔鎴愬姛");
+          this.beforeClose();
+          this.$emit("submit", datas);
+          // let funName = datas.id ? "editRole" : "addRole";
+          // this.loading = true;
+          // this.$api.system[funName](datas)
+          //   .then((res) => {
+          //     if (res) {
+          //       this.loading = false;
+          //       this.$message.success("鎿嶄綔鎴愬姛");
+          //       this.beforeClose();
+          //       this.$emit("success");
+          //     }
+          //   })
+          //   .catch(() => {
+          //     this.loading = false;
+          //   });
+        } else {
+          return false;
+        }
+      });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+::v-deep {
+  .el-date-editor.el-input,
+  .el-date-editor.el-input__inner {
+    width: 132px;
+  }
+}
+</style>
diff --git a/src/views/profile/EditJob.vue b/src/views/profile/EditJob.vue
new file mode 100644
index 0000000000000000000000000000000000000000..660340de19b6ad8a2e01229e920c21b00da22939
--- /dev/null
+++ b/src/views/profile/EditJob.vue
@@ -0,0 +1,202 @@
+<template>
+  <PubDialog
+    v-model="dialogVisible"
+    :title="`${id ? '缂栬緫' : '鏂板'}鏁欒偛淇℃伅`"
+    @beforeClose="beforeClose"
+  >
+    <div class="pub-form">
+      <el-form
+        ref="form"
+        :model="form"
+        label-width="98px"
+        label-position="right"
+        :rules="rules"
+        size="small"
+      >
+        <el-form-item prop="company" label="鍏徃:">
+          <el-input
+            v-model="form.company"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+
+        <el-form-item prop="startTime" label="鏃堕棿:">
+          <div class="flex-between" style="width:300px">
+            <el-date-picker
+              v-model="form.startTime"
+              type="date"
+              value-format="yyyy-MM-DD"
+              placeholder="閫夋嫨鏃ユ湡"
+            >
+            </el-date-picker>
+            <span>鈥斺€�</span>
+            <el-date-picker
+              v-model="form.endTime"
+              type="date"
+              value-format="yyyy-MM-DD"
+              placeholder="閫夋嫨鏃ユ湡"
+            >
+            </el-date-picker>
+          </div>
+        </el-form-item>
+
+        <el-form-item prop="jobName" label="鑱屼綅:">
+          <el-input
+            v-model="form.jobName"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item prop="jobStatus" label="鑱屼笟鐘舵€�:">
+          <el-input
+            v-model="form.jobStatus"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item prop="dept" label="閮ㄩ棬:">
+          <el-input
+            v-model="form.dept"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+
+        <el-form-item prop="content" label="宸ヤ綔鍐呭:">
+          <el-input
+            v-model="form.content"
+            placeholder="璇疯緭鍏�"
+            clearable
+            type="textarea"
+          ></el-input>
+        </el-form-item>
+        <el-form-item>
+          <el-button
+            type="primary"
+            size="small"
+            @click="submit"
+            :loading="loading"
+            >鎻愪氦</el-button
+          >
+          <el-button type="primary" plain size="small" @click="close"
+            >杩斿洖</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </div>
+  </PubDialog>
+</template>
+<script>
+import PubDialog from "@/components/App/PubDialog";
+export default {
+  name: "VideoDialog",
+  components: { PubDialog },
+  data: () => {
+    return {
+      id: "",
+      loading: false,
+
+      form: {
+        startTime: "",
+        endTime: "",
+        company: "",
+        jobName: "",
+        jobStatus: "",
+        dept: "",
+        content: "",
+      },
+      rules: {
+        // name: [
+        //   { required: true, message: "璇疯緭鍏ヨ鑹插悕绉�", trigger: "change" },
+        // ],
+      },
+    };
+  },
+  props: {
+    value: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  computed: {
+    dialogVisible: {
+      get: function(that) {
+        return that.value;
+      },
+      set: function(newValue) {
+        this.$emit("input", newValue);
+      },
+    },
+  },
+  methods: {
+    resetForm() {
+      this.form = {
+        id: "",
+        startTime: "",
+        endTime: "",
+        company: "",
+        jobName: "",
+        jobStatus: "",
+        dept: "",
+        content: "",
+      };
+    },
+    beforeClose() {
+      // console.log(this.$refs.form?.resetFields);
+      // this.$refs.form?.resetFields();
+      this.resetForm();
+      // this.form.endTime = "";
+      this.dialogVisible = false;
+    },
+    open(row) {
+      if (row) {
+        this.id = row.id;
+        this.form = this.$utils.setFormValue(this.form, row);
+      }
+      this.dialogVisible = true;
+    },
+    close() {
+      this.beforeClose();
+    },
+    submit() {
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          let datas = { ...this.form };
+          if (this.id) datas.id = this.id;
+          this.$message.success("鎿嶄綔鎴愬姛");
+          this.beforeClose();
+          this.$emit("submit", datas);
+          // let funName = datas.id ? "editRole" : "addRole";
+          // this.loading = true;
+          // this.$api.system[funName](datas)
+          //   .then((res) => {
+          //     if (res) {
+          //       this.loading = false;
+          //       this.$message.success("鎿嶄綔鎴愬姛");
+          //       this.beforeClose();
+          //       this.$emit("success");
+          //     }
+          //   })
+          //   .catch(() => {
+          //     this.loading = false;
+          //   });
+        } else {
+          return false;
+        }
+      });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+::v-deep {
+  .el-date-editor.el-input,
+  .el-date-editor.el-input__inner {
+    width: 132px;
+  }
+  .el-textarea {
+    width: 325px;
+  }
+}
+</style>
diff --git a/src/views/profile/PropertyDialog.vue b/src/views/profile/PropertyDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5ef420210a8ef863b5cba8765a3c80b9c5100731
--- /dev/null
+++ b/src/views/profile/PropertyDialog.vue
@@ -0,0 +1,150 @@
+<template>
+  <PubDialog
+    v-model="dialogVisible"
+    :title="`${type == 1 ? '鏂板璧勪骇' : '璧勪骇璇︽儏'}`"
+    @beforeClose="beforeClose"
+  >
+    <div class="pub-form" v-if="type == 1">
+      <el-form
+        ref="form"
+        :model="form"
+        label-width="98px"
+        label-position="right"
+        :rules="rules"
+        size="small"
+      >
+        <el-form-item prop="name" label="璧勪骇绫诲瀷:">
+          <el-input
+            v-model="form.name"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+
+        <el-form-item prop="value" label="閲戦:">
+          <el-input
+            v-model="form.value"
+            placeholder="璇疯緭鍏�"
+            clearable
+          ></el-input>
+        </el-form-item>
+        <el-form-item>
+          <el-button
+            type="primary"
+            size="small"
+            @click="submit"
+            :loading="loading"
+            >鎻愪氦</el-button
+          >
+          <el-button type="primary" plain size="small" @click="close"
+            >杩斿洖</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </div>
+  </PubDialog>
+</template>
+<script>
+import PubDialog from "@/components/App/PubDialog";
+export default {
+  name: "PropertyDialog",
+  components: { PubDialog },
+  data: () => {
+    return {
+      id: "",
+      loading: false,
+      type: 1,
+      form: {
+        value: "",
+        name: "",
+      },
+      rules: {
+        // name: [
+        //   { required: true, message: "璇疯緭鍏ヨ鑹插悕绉�", trigger: "change" },
+        // ],
+      },
+    };
+  },
+  props: {
+    value: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  computed: {
+    dialogVisible: {
+      get: function(that) {
+        return that.value;
+      },
+      set: function(newValue) {
+        this.$emit("input", newValue);
+      },
+    },
+  },
+  methods: {
+    resetForm() {
+      this.form = {
+        value: "",
+        name: "",
+      };
+    },
+    beforeClose() {
+      // console.log(this.$refs.form?.resetFields);
+      // this.$refs.form?.resetFields();
+      this.resetForm();
+      // this.form.endTime = "";
+      this.dialogVisible = false;
+    },
+    open(type) {
+      this.type = type;
+      // if (row) {
+      //   this.id = row.id;
+      //   this.form = this.$utils.setFormValue(this.form, row);
+      // }
+
+      this.dialogVisible = true;
+    },
+    close() {
+      this.beforeClose();
+    },
+    submit() {
+      this.$refs.form.validate((valid) => {
+        if (valid) {
+          let datas = { ...this.form };
+          // if (this.id) datas.id = this.id;
+          this.$message.success("鎿嶄綔鎴愬姛");
+          this.beforeClose();
+          this.$emit("submit", datas);
+          // let funName = datas.id ? "editRole" : "addRole";
+          // this.loading = true;
+          // this.$api.system[funName](datas)
+          //   .then((res) => {
+          //     if (res) {
+          //       this.loading = false;
+          //       this.$message.success("鎿嶄綔鎴愬姛");
+          //       this.beforeClose();
+          //       this.$emit("success");
+          //     }
+          //   })
+          //   .catch(() => {
+          //     this.loading = false;
+          //   });
+        } else {
+          return false;
+        }
+      });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+::v-deep {
+  .el-date-editor.el-input,
+  .el-date-editor.el-input__inner {
+    width: 132px;
+  }
+  .el-textarea {
+    width: 325px;
+  }
+}
+</style>
diff --git a/src/views/profile/education.vue b/src/views/profile/education.vue
new file mode 100644
index 0000000000000000000000000000000000000000..271a6429713cb5e90226cb22c392c79d81307e6f
--- /dev/null
+++ b/src/views/profile/education.vue
@@ -0,0 +1,157 @@
+<template>
+  <InnerView>
+    <pub-table
+      :headers="headers"
+      :fetcher="fetcher"
+      :params="params"
+      :pagination="false"
+      showHeader
+      ref="table"
+    >
+      <div slot="btnRight" class="btnRight">
+        <el-button type="primary" size="small" @click="openEdit"
+          >鏂板</el-button
+        >
+      </div>
+    </pub-table>
+    <EditEducation
+      ref="EditEducation"
+      v-model="EditEducationShow"
+      @submit="submitEdit"
+    ></EditEducation>
+  </InnerView>
+</template>
+
+<script>
+import EditEducation from "./EditEducation";
+export default {
+  name: "Education",
+  components: { EditEducation },
+  meta: {},
+  computed: {},
+  data() {
+    return {
+      EditEducationShow: false,
+      headers: [
+        {
+          field: "school",
+          fieldName: "瀛︽牎",
+        },
+        {
+          field: "education",
+          fieldName: "瀛﹀巻",
+        },
+        {
+          field: "time",
+          fieldName: "鏃堕棿",
+          width: 180,
+          formatter: (row) => {
+            return row.startTime + "~" + row.endTime;
+          },
+        },
+        {
+          field: "college",
+          fieldName: "瀛﹂櫌",
+        },
+        {
+          field: "special",
+          fieldName: "涓撲笟",
+        },
+        {
+          field: "studentID",
+          fieldName: "瀛﹀彿",
+        },
+        {
+          field: "handle",
+          fieldName: "鎿嶄綔",
+          width: 160,
+          renderFun: (e) => this.renderBtns(e),
+        },
+      ],
+      params: [],
+      testData: [
+        {
+          id: 1,
+          school: "閲嶅簡閭數澶у",
+          education: "鏈",
+          startTime: "2015-09-01",
+          endTime: "2019-07-01",
+          college: "鐢靛瓙淇℃伅瀛﹂櫌",
+          special: "鐢靛瓙淇℃伅宸ョ▼",
+          studentID: "******012",
+        },
+      ],
+      fetcher: async () => {
+        // console.log(this);
+        //console.log(params);
+        // let res = await api.publicEquipment.getBiogasDeviceInfoPage(params);
+        let res = {
+          total: this.testData.length,
+          data: this.testData,
+        };
+
+        return res;
+      },
+    };
+  },
+  methods: {
+    renderBtns(e) {
+      let that = this;
+      return (
+        <div>
+          <el-button
+            onClick={() => that.openEdit(e.row)}
+            size="mini"
+            plain
+            type="primary"
+          >
+            缂栬緫
+          </el-button>
+          <el-button
+            onClick={() => that.delItem(e.row)}
+            size="mini"
+            plain
+            type="warning"
+          >
+            鍒犻櫎
+          </el-button>
+        </div>
+      );
+    },
+    openEdit(row) {
+      this.$refs.EditEducation.open(row);
+    },
+    submitEdit(datas) {
+      // console.log(datas);
+      if (datas.id) {
+        this.testData = this.testData.map((item) => {
+          if (item.id == datas.id) {
+            return datas;
+          } else {
+            return item;
+          }
+        });
+      } else {
+        let newId = this.testData[this.testData.length - 1]?.id
+          ? this.testData[this.testData.length - 1]?.id + 2
+          : 2;
+        this.testData = [...this.testData, { ...datas, id: newId }];
+      }
+
+      this.$refs.table.loadData();
+    },
+    delItem(row) {
+      this.$confirm(`纭鍒犻櫎姝ゆ潯鏁版嵁鍚楋紵`, "鎻愮ず", {
+        type: "warning",
+      })
+        .then(() => {
+          this.testData = this.testData.filter((item) => item.id != row.id);
+          this.$refs.table.loadData();
+        })
+        .catch(() => {});
+    },
+  },
+};
+</script>
+
+<style></style>
diff --git a/src/views/profile/index.vue b/src/views/profile/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e533e64ec497ad4e46668168bb814e160d43159a
--- /dev/null
+++ b/src/views/profile/index.vue
@@ -0,0 +1,51 @@
+<template>
+  <InnerView class="flex-col" style="overflow: hidden;">
+    <el-menu
+      :default-active="activeIndex"
+      class="el-menu-demo"
+      mode="horizontal"
+      @select="handleSelect"
+    >
+      <el-menu-item index="1">涓汉璧勬枡</el-menu-item>
+      <el-menu-item index="2">鏁欒偛淇℃伅</el-menu-item>
+      <el-menu-item index="3">宸ヤ綔缁忓巻</el-menu-item>
+      <el-menu-item index="4">璧勪骇淇℃伅</el-menu-item>
+    </el-menu>
+    <ScrollView>
+      <Information v-if="activeIndex == '1'"></Information>
+      <Education v-if="activeIndex == '2'"></Education>
+      <Job v-if="activeIndex == '3'"></Job>
+      <Property v-if="activeIndex == '4'"></Property>
+    </ScrollView>
+  </InnerView>
+</template>
+<script>
+import Information from "./information.vue";
+import Education from "./education.vue";
+import Job from "./job.vue";
+import Property from "./property.vue";
+
+export default {
+  name: "profile",
+  components: { Information, Education, Job, Property },
+  meta: {
+    sort: 1,
+    title: "鎴戠殑妗f",
+    iconImg: "profile",
+    iconImgActive: "profileActive",
+  },
+  computed: {},
+  data() {
+    return {
+      activeIndex: "1",
+    };
+  },
+  watch: {},
+  methods: {
+    handleSelect(index) {
+      this.activeIndex = index;
+    },
+  },
+};
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/views/profile/information.vue b/src/views/profile/information.vue
new file mode 100644
index 0000000000000000000000000000000000000000..85366f4dc3766f9cc0062b8fb4f65083d4423616
--- /dev/null
+++ b/src/views/profile/information.vue
@@ -0,0 +1,325 @@
+<template>
+  <InnerView class="flex-col">
+    <div class="right-btn">
+      <el-button type="primary" plain size="mini" v-if="isEdit" @click="save">
+        <i class="el-icon-folder"></i>淇濆瓨
+      </el-button>
+      <el-button
+        type="primary"
+        plain
+        size="mini"
+        v-else
+        @click="isEdit = !isEdit"
+      >
+        <i class="el-icon-edit-outline"></i>缂栬緫
+      </el-button>
+    </div>
+    <el-form
+      ref="form"
+      :model="form"
+      label-width="0"
+      label-position="left"
+      size="small"
+    >
+      <el-descriptions title="" :column="1">
+        <el-descriptions-item label="褰撳墠澶村儚">
+          <div class="flex-center">
+            <div class="avatar">
+              <img :src="form.avatar" alt="" />
+            </div>
+
+            <el-upload
+              v-if="isEdit"
+              class="avatar-uploader"
+              action=""
+              :show-file-list="false"
+              :before-upload="
+                (file) => {
+                  beforeUpload(file, 'avatar');
+                  return false;
+                }
+              "
+              ><el-button type="primary" plain size="mini">鏇存崲澶村儚</el-button>
+            </el-upload>
+          </div>
+        </el-descriptions-item>
+        <el-descriptions-item label="鐪熷疄濮撳悕">
+          <el-form-item prop="name" label="" v-if="isEdit">
+            <el-input
+              v-model="form.name"
+              placeholder="璇疯緭鍏�"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <span v-else>{{ form.name }}</span>
+        </el-descriptions-item>
+
+        <el-descriptions-item label="">
+          <span slot="label">鎬�<span class="hidden-text">鎬у埆</span>鍒�</span>
+          <el-form-item prop="sex" label="" v-if="isEdit">
+            <el-radio-group v-model="form.sex">
+              <el-radio :label="'鐢�'">鐢�</el-radio>
+              <el-radio :label="'濂�'">濂�</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <span v-else>{{ form.sex }}</span>
+        </el-descriptions-item>
+
+        <el-descriptions-item label="">
+          <span slot="label">姘�<span class="hidden-text">姘戞棌</span>鏃�</span>
+          <el-form-item prop="nation" label="" v-if="isEdit">
+            <el-input
+              v-model="form.nation"
+              placeholder="璇疯緭鍏�"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <span v-else>{{ form.nation }}</span>
+        </el-descriptions-item>
+
+        <el-descriptions-item label="鎵嬫満鍙风爜">
+          <span>{{ form.phone }}</span>
+        </el-descriptions-item>
+
+        <el-descriptions-item label="">
+          <span slot="label">鍦�<span class="hidden-text">鍦板潃</span>鍧€</span>
+          <el-form-item prop="address" label="" v-if="isEdit">
+            <div class="flex-between" style="width:325px">
+              <el-select
+                v-model="form.address"
+                placeholder="璇烽€夋嫨"
+                style="width:155px"
+              >
+                <el-option
+                  label="閲嶅簡甯備簯闃冲幙闈掗緳琛楅亾"
+                  value="閲嶅簡甯備簯闃冲幙闈掗緳琛楅亾"
+                >
+                </el-option>
+              </el-select>
+              <el-input
+                v-model="form.addressDetail"
+                style="width:155px"
+                placeholder="璇疯緭鍏�"
+                clearable
+              ></el-input>
+            </div>
+          </el-form-item>
+          <span v-else>{{
+            hideAddress(form.address, form.addressDetail)
+          }}</span>
+        </el-descriptions-item>
+
+        <el-descriptions-item label="鐢靛瓙閭">
+          <el-form-item prop="email" label="" v-if="isEdit">
+            <el-input
+              v-model="form.email"
+              placeholder="璇疯緭鍏�"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <span v-else>{{ form.email }}</span>
+        </el-descriptions-item>
+
+        <el-descriptions-item label="璇佷欢鍙风爜">
+          <el-form-item prop="cardID" label="" v-if="isEdit">
+            <el-input
+              v-model="form.cardID"
+              placeholder="璇疯緭鍏�"
+              clearable
+            ></el-input>
+          </el-form-item>
+          <span v-else>{{ hideAddress(form.cardID) }}</span>
+        </el-descriptions-item>
+
+        <el-descriptions-item label="璇佷欢鐓х墖">
+          <div class="flex">
+            <div class="photo">
+              <img :src="form.idcardimg1" alt="" />
+              <div class="uploader" v-if="isEdit">
+                <el-upload
+                  class="avatar-uploader"
+                  action=""
+                  :show-file-list="false"
+                  :before-upload="
+                    (file) => {
+                      beforeUpload(file, 'idcardimg1');
+                      return false;
+                    }
+                  "
+                >
+                  <i class="el-icon-upload2"></i>
+                  涓婁紶
+                </el-upload>
+              </div>
+            </div>
+            <div class="photo">
+              <img :src="form.idcardimg2" alt="" />
+              <div class="uploader" v-if="isEdit">
+                <el-upload
+                  class="avatar-uploader"
+                  action=""
+                  :show-file-list="false"
+                  :before-upload="
+                    (file) => {
+                      beforeUpload(file, 'idcardimg2');
+                      return false;
+                    }
+                  "
+                >
+                  <i class="el-icon-upload2"></i>
+                  涓婁紶
+                </el-upload>
+              </div>
+            </div>
+          </div>
+        </el-descriptions-item>
+      </el-descriptions>
+    </el-form>
+  </InnerView>
+</template>
+<script>
+import testAvatar from "@/assets/img/avatar-test.png";
+import idcard1 from "@/assets/img/idcard1.png";
+import idcard2 from "@/assets/img/idcard2.png";
+
+export default {
+  name: "Information",
+  components: {},
+  meta: {},
+  computed: {},
+  data() {
+    return {
+      isEdit: false,
+      form: {
+        avatar: testAvatar,
+        name: "鏉庝簯",
+        sex: "濂�",
+        nation: "姹夋棌",
+        phone: "158****7854",
+        address: "閲嶅簡甯備簯闃冲幙闈掗緳琛楅亾",
+        addressDetail: "閲戠涓栫晫鍩�17-12-5",
+        email: "65874258741@163.com",
+        cardID: "500235199205274588",
+        idcardimg1: idcard1,
+        idcardimg2: idcard2,
+      },
+    };
+  },
+  watch: {},
+  methods: {
+    hideAddress(address = "", addressDetail = "") {
+      let add = "" + address + addressDetail;
+
+      let arr = add.split("");
+
+      let res = arr.map((item, index) => {
+        if (index < 6) {
+          return item;
+        } else {
+          return "*";
+        }
+      });
+      return res.join("");
+    },
+    uploadFile() {},
+    async beforeUpload(file, field) {
+      let url = await this.$utils.file2Base64(file);
+      // console.log(url);
+      this.$set(this.form, field, url);
+      return false;
+    },
+    save() {
+      this.$message.success("鎿嶄綔鎴愬姛");
+      this.isEdit = false;
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.avatar {
+  width: 80px;
+  height: 80px;
+  border-radius: 50%;
+  overflow: hidden;
+  margin-right: 16px;
+
+  box-shadow: 0px 2px 6px rgba($color: #000000, $alpha: 0.2);
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}
+.flex-col {
+  position: relative;
+  .right-btn {
+    position: absolute;
+    right: 64px;
+    top: 34px;
+    ::v-deep {
+      span {
+        display: flex;
+        align-items: center;
+
+        i {
+          font-size: 17px;
+          margin-right: 5px;
+        }
+      }
+    }
+  }
+}
+::v-deep {
+  .el-select .el-input {
+    width: auto;
+  }
+  .el-form {
+    .el-descriptions-item__label:not(.is-bordered-label) {
+      line-height: 30px;
+    }
+    .el-descriptions-item__container .el-descriptions-item__content {
+      line-height: 30px;
+    }
+  }
+  .el-form-item--small.el-form-item {
+    margin-bottom: 0;
+  }
+  .el-descriptions :not(.is-bordered) .el-descriptions-item__cell {
+    padding-bottom: 10px;
+  }
+}
+.photo {
+  width: 141px;
+  height: 93px;
+  margin-right: 15px;
+  position: relative;
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}
+.uploader {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  left: 0;
+  top: 0;
+  background-color: rgba($color: #000000, $alpha: 0.5);
+  color: #fff;
+  .avatar-uploader {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    ::v-deep {
+      .el-upload {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+  }
+}
+</style>
diff --git a/src/views/profile/job.vue b/src/views/profile/job.vue
new file mode 100644
index 0000000000000000000000000000000000000000..35e13fd6ed0475c7e54f96507412460f23286eb5
--- /dev/null
+++ b/src/views/profile/job.vue
@@ -0,0 +1,154 @@
+<template>
+  <InnerView>
+    <pub-table
+      :headers="headers"
+      :fetcher="fetcher"
+      :params="params"
+      :pagination="false"
+      showHeader
+      ref="table"
+    >
+      <div slot="btnRight" class="btnRight">
+        <el-button type="primary" size="small" @click="openEdit"
+          >鏂板</el-button
+        >
+      </div>
+    </pub-table>
+    <EditJob ref="EditJob" v-model="EditJobShow" @submit="submitEdit"></EditJob>
+  </InnerView>
+</template>
+
+<script>
+import EditJob from "./EditJob";
+export default {
+  name: "Job",
+  components: { EditJob },
+  meta: {},
+  computed: {},
+  data() {
+    return {
+      EditJobShow: false,
+      headers: [
+        {
+          field: "company",
+          fieldName: "鍏徃",
+        },
+        {
+          field: "time",
+          fieldName: "鏃堕棿",
+          width: 180,
+          formatter: (row) => {
+            return row.startTime + "~" + row.endTime;
+          },
+        },
+        {
+          field: "jobName",
+          fieldName: "鑱屼綅鍚嶇О",
+        },
+        {
+          field: "jobStatus",
+          fieldName: "鑱屼笟鐘舵€�",
+        },
+        {
+          field: "dept",
+          fieldName: "閮ㄩ棬",
+        },
+        {
+          field: "content",
+          fieldName: "宸ヤ綔鍐呭",
+          width: 140,
+        },
+        {
+          field: "handle",
+          fieldName: "鎿嶄綔",
+          width: 160,
+          renderFun: (e) => this.renderBtns(e),
+        },
+      ],
+      params: [],
+      testData: [
+        {
+          id: 1,
+          startTime: "2015-09-01",
+          endTime: "2019-07-01",
+          company: "涔濆惃绉戞妧",
+          jobName: "浜у搧缁忕悊",
+          jobStatus: "绂昏亴",
+          dept: "浜嬩笟涓€閮�",
+          content: "浜у搧璋冪爺锛屼骇鍝佸師鍨嬶紝浜у搧璁捐锛岄渶姹傛枃妗c€侀渶姹傝瑙�",
+        },
+      ],
+      fetcher: async () => {
+        // console.log(this);
+        //console.log(params);
+        // let res = await api.publicEquipment.getBiogasDeviceInfoPage(params);
+        let res = {
+          total: this.testData.length,
+          data: this.testData,
+        };
+        return res;
+      },
+    };
+  },
+  methods: {
+    renderBtns(e) {
+      let that = this;
+      return (
+        <div>
+          <el-button
+            onClick={() => that.openEdit(e.row)}
+            size="mini"
+            plain
+            type="primary"
+          >
+            缂栬緫
+          </el-button>
+          <el-button
+            onClick={() => that.delItem(e.row)}
+            size="mini"
+            plain
+            type="warning"
+          >
+            鍒犻櫎
+          </el-button>
+        </div>
+      );
+    },
+    openEdit(row) {
+      this.$refs.EditJob.open(row);
+    },
+    submitEdit(datas) {
+      // console.log(datas);
+      
+      if (datas.id) {
+        this.testData = this.testData.map((item) => {
+          if (item.id == datas.id) {
+            return datas;
+          } else {
+            return item;
+          }
+        });
+      } else {
+        let newId = this.testData[this.testData.length - 1]?.id
+          ? this.testData[this.testData.length - 1]?.id + 2
+          : 2;
+        this.testData = [...this.testData, { ...datas, id: newId }];
+      }
+
+      this.$refs.table.loadData();
+    },
+    delItem(row) {
+      this.$confirm(`纭鍒犻櫎姝ゆ潯鏁版嵁鍚楋紵`, "鎻愮ず", {
+        type: "warning",
+      })
+        .then(() => {
+          this.testData = this.testData.filter((item) => item.id != row.id);
+          this.$refs.table.loadData();
+        })
+        .catch(() => {});
+    },
+  },
+};
+</script>
+
+<style></style>
diff --git a/src/views/profile/property.vue b/src/views/profile/property.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7803b29669600f0a2621481b335fe22ef66f9ccb
--- /dev/null
+++ b/src/views/profile/property.vue
@@ -0,0 +1,249 @@
+<template>
+  <InnerView>
+    <ScrollView>
+      <el-button
+        type="primary"
+        size="small"
+        @click="addProperty"
+        style="margin-top:19px"
+        >鏂板璧勪骇</el-button
+      >
+      <div class="total-property">
+        <div class="flex">
+          <img src="@/assets/img/property.png" alt="" />
+          <span>璧勪骇鎬婚</span>
+        </div>
+        <div class="value">{{ allProperty }}</div>
+        <div class="open-detail" @click="openDetail">鏌ョ湅</div>
+      </div>
+
+      <div class="content-title">璧勪骇鍒嗗竷</div>
+      <div class="chart" id="chart"></div>
+    </ScrollView>
+    <PropertyDialog
+      ref="PropertyDialog"
+      v-model="PropertyDialogShow"
+      @submit="submitDialog"
+    ></PropertyDialog>
+
+    <PubDialog
+      v-model="dialogVisible"
+      :title="`璧勪骇璇︽儏`"
+      @beforeClose="dialogVisible = false"
+    >
+      <div class="property-box">
+        <pub-table
+          :headers="headers"
+          :fetcher="fetcher"
+          :pagination="false"
+          ref="table"
+        >
+        </pub-table>
+      </div>
+    </PubDialog>
+  </InnerView>
+</template>
+<script>
+import PropertyDialog from "./PropertyDialog.vue";
+import PubDialog from "@/components/App/PubDialog";
+export default {
+  name: "Property",
+  components: { PropertyDialog, PubDialog },
+  meta: {},
+  data() {
+    return {
+      PropertyDialogShow: false,
+      dialogVisible: false,
+      testData: [
+        {
+          name: "瀛樻",
+          value: "1000",
+          updateTime: "2023-07-01 12:25:12",
+        },
+        {
+          name: "鍩洪噾",
+          value: "253",
+          updateTime: "2023-06-25 16:15:24",
+        },
+        {
+          name: "鑲$エ",
+          value: "800",
+          updateTime: "2023-06-12 14:21:38",
+        },
+      ],
+
+      headers: [
+        {
+          field: "name",
+          fieldName: "璧勪骇绫诲瀷",
+        },
+        {
+          field: "value",
+          fieldName: "閲戦",
+        },
+        {
+          field: "updateTime",
+          fieldName: "涓婁紶鏃堕棿",
+        },
+      ],
+      params: [],
+      fetcher: async () => {
+        console.log("this.testData", this);
+        // console.log(this);
+        //console.log(params);
+        // let res = await api.publicEquipment.getBiogasDeviceInfoPage(params);
+        let res = {
+          total: this?.testData?.length,
+          data: this?.testData,
+        };
+        console.log(res);
+        return res;
+      },
+    };
+  },
+  computed: {
+    allProperty() {
+      let all = 0;
+      for (let i = 0; i < this.testData.length; i++) {
+        const element = this.testData[i];
+        all += parseFloat(element.value);
+      }
+      return all.toFixed(2);
+    },
+  },
+  watch: {},
+  mounted() {
+    this.getCharts();
+  },
+  methods: {
+    addProperty() {
+      this.$refs.PropertyDialog.open(1);
+    },
+    openDetail() {
+      this.dialogVisible = true;
+      // this.$refs.PropertyDialog.open(2, this.testData);
+    },
+    submitDialog(datas) {
+      let time = this.$utils.formatTimeWithDayjs(new Date());
+      this.testData = [
+        ...this.testData,
+        {
+          ...datas,
+          updateTime: time,
+        },
+      ];
+      this.getCharts();
+    },
+    getCharts() {
+      const chartBox = this.$echarts.init(document.getElementById("chart"));
+      const option = {
+        backgroundColor: "white",
+        tooltip: {
+          show: true,
+          trigger: "item",
+          //{a}(绯诲垪鍚�)锛寋b}(鏁版嵁椤瑰悕),{c}(鏁板€�),{d}(鐧惧垎姣�)
+          formatter: "{a} <br/>{b} : {c}",
+          backgroundColor: "rgba(1, 13, 19, 0.5)",
+          borderWidth: 0,
+          textStyle: {
+            color: "rgba(212, 232, 254, 1)",
+            // fontSize: fontChart(0.24),
+          },
+        },
+
+        legend: {
+          top: 10,
+          left:'10%',
+          itemWidth: 25,
+          itemHeight: 14,
+          borderRadius: 8,
+          textStyle: {
+            color: "#000",
+            fontFamily: "Alibaba PuHuiTi",
+            fontSize: 14,
+            fontWeight: 400,
+          },
+        },
+        series: [
+          {
+            name: "璧勪骇鍒嗗竷",
+            type: "pie",
+            radius: "70%",
+            center: ["25%", "54%"],
+            data: this.testData,
+            label: {
+              normal: {
+                formatter: ["{b|{b}}", "{c|{c}}"].join("\n"),
+                rich: {
+                  c: {
+                    color: "inherit",
+                    fontSize: 12,
+                  },
+                  b: {
+                    fontSize: 12,
+                    height: 25,
+                  },
+                },
+              },
+            },
+            emphasis: {
+              itemStyle: {
+                shadowBlur: 10,
+                shadowOffsetX: 0,
+                shadowColor: "rgba(0, 0, 0, 0.5)",
+              },
+            },
+          },
+        ],
+      };
+      chartBox.setOption(option);
+      // 鏍规嵁椤甸潰澶у皬鑷姩鍝嶅簲鍥捐〃澶у皬
+      window.addEventListener("resize", function() {
+        chartBox.resize();
+      });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.property-box {
+  height: 400px;
+  width: 100%;
+}
+.total-property {
+  width: 242px;
+  height: 132px;
+  border-radius: 16px;
+  background: #ffa74f;
+  color: #fff;
+  font-size: 16px;
+  padding-left: 21px;
+  margin-top: 30px;
+  .flex {
+    align-items: center;
+    height: 54px;
+  }
+  img {
+    width: 16px;
+    height: 16px;
+    margin-right: 7px;
+  }
+  .value {
+    font-size: 30px;
+    font-weight: bold;
+    line-height: 36px;
+    margin-bottom: 7px;
+  }
+  .open-detail {
+    font-size: 14px;
+    cursor: pointer;
+  }
+}
+.content-title {
+  margin-top: 37px;
+}
+.chart {
+  width: 700px;
+  height: 300px;
+}
+</style>
diff --git a/src/views/serviceData/index.vue b/src/views/serviceData/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..63e10ead47b7b6e381e9f899dff2ba49479eb08e
--- /dev/null
+++ b/src/views/serviceData/index.vue
@@ -0,0 +1,20 @@
+<template>
+  <InnerView> </InnerView>
+</template>
+<script>
+export default {
+  name: "serviceData",
+  meta: {
+    sort: 4,
+    title: "鎴戠殑鍔炰簨鏁版嵁",
+    iconImg: "serviceData",
+    iconImgActive: "serviceDataActive",
+  },
+  components: {},
+  data() {
+    return {};
+  },
+  watch: {},
+};
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/views/spaceIndex/index.vue b/src/views/spaceIndex/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a0c3ce8a749a28f051e09edd0e9a6efd54f02b44
--- /dev/null
+++ b/src/views/spaceIndex/index.vue
@@ -0,0 +1,32 @@
+<template>
+  <!-- <transition :name="transition"> -->
+  <keep-alive :include="cacheRoutes">
+    <router-view class="child-view"></router-view>
+  </keep-alive>
+  <!-- </transition> -->
+</template>
+<script>
+import { mapState } from "vuex";
+// import { InnerView, RouteView } from "@/components/Layout";
+export default {
+  name: "spaceIndex",
+  // components: { InnerView, RouteView },
+  meta: {
+    sort: 0,
+    title: "绌洪棿棣栭〉",
+    iconImg: "spaceIndex",
+    iconImgActive: "spaceIndexActive",
+    redirect: "/spaceIndex/main",
+  },
+  computed: {
+    ...mapState({
+      cacheRoutes: (state) => state.app.cacheRoutes,
+    }),
+  },
+  data() {
+    return {};
+  },
+  watch: {},
+};
+</script>
+<style lang="scss" scoped></style>
diff --git a/src/views/spaceIndex/main/index.vue b/src/views/spaceIndex/main/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8b0dd2a651661e006f0ea60b50d8d0367132c9d3
--- /dev/null
+++ b/src/views/spaceIndex/main/index.vue
@@ -0,0 +1,169 @@
+<template>
+  <InnerView class="flex-col" style="overflow: hidden;">
+    <el-menu
+      :default-active="activeIndex"
+      class="el-menu-demo"
+      mode="horizontal"
+      @select="handleSelect"
+    >
+      <el-menu-item index="1">涓汉淇℃伅</el-menu-item>
+      <el-menu-item index="2">涓汉鏉愭枡</el-menu-item>
+    </el-menu>
+    <ScrollView>
+      <template v-if="activeIndex == '1'">
+        <el-descriptions title="" :column="1">
+          <el-descriptions-item label="褰撳墠澶村儚">
+            <div class="avatar">
+              <img src="@/assets/img/avatar-test.png" alt="" />
+            </div>
+          </el-descriptions-item>
+          <el-descriptions-item label="鐪熷疄濮撳悕">*浜�</el-descriptions-item>
+          <el-descriptions-item label="璇佷欢鍙风爜"
+            >**************74588</el-descriptions-item
+          >
+          <el-descriptions-item label="瀹炲悕璁よ瘉">鍥涚骇璁よ瘉</el-descriptions-item>
+          <el-descriptions-item label="鎵嬫満鍙风爜"
+            >158****7854</el-descriptions-item
+          >
+          <el-descriptions-item label="">
+            <span slot="label">鍦�<span class="hidden-text">鍦板潃</span>鍧€</span>
+            閲嶅簡甯備簯闃冲幙************
+          </el-descriptions-item>
+          <el-descriptions-item label="鐢靛瓙閭"
+            >65874******@163.com</el-descriptions-item
+          >
+        </el-descriptions>
+      </template>
+
+      <template v-if="activeIndex == '2'">
+        <pub-table
+          :headers="headers"
+          :fetcher="fetcher"
+          :params="params"
+          :pagination="false"
+          ref="table"
+        >
+        </pub-table>
+      </template>
+    </ScrollView>
+  </InnerView>
+</template>
+
+<script>
+// import * as api from "@/api";
+export default {
+  name: "SpaceIndexMain",
+  meta: {
+    sort: 0,
+    title: "绌洪棿棣栭〉",
+    activeName: "spaceIndex",
+    hidden: true,
+    keepAlive: true,
+  },
+  components: {},
+  data() {
+    return {
+      activeIndex: "1",
+      headers: [
+        {
+          field: "index",
+          fieldName: "搴忓彿",
+          type: "index",
+        },
+        {
+          field: "materialName",
+          fieldName: "鏉愭枡鍚嶇О",
+        },
+        {
+          field: "opdateTime",
+          fieldName: "涓婁紶鏃堕棿",
+        },
+        {
+          field: "handle",
+          fieldName: "鎿嶄綔",
+          width: 136,
+          renderFun: (e) => this.renderBtns(e),
+        },
+      ],
+      params: [
+        {
+          label: "鏉愭枡鍚嶇О",
+          key: "materialName",
+          placeholder: "璇疯緭鍏�",
+        },
+      ],
+      testData: [
+        {
+          materialName: "鐢宠鍦ㄦ牎姹傝亴鍒涗笟琛ヨ创鐩稿叧璧勬枡",
+          opdateTime: "2023-01-13 12:25:23",
+        },
+        {
+          materialName: "楂樼瓑瀛︽牎绛夋瘯涓氱敓鎺ユ敹鎵嬬画鍔炵悊鐩稿叧璧勬枡",
+          opdateTime: "2023-01-08 09:23:46",
+        },
+        {
+          materialName: "鍙楃悊鍏垂甯堣寖鐢熻法鐪佷换鏁欑敵璇风浉鍏宠祫鏂�",
+          opdateTime: "2023-01-07 09:15:42",
+        },
+        {
+          materialName: "妗f鐨勬帴鏀跺拰杞€掔殑鐩稿叧璧勬枡",
+          opdateTime: "2023-01-06 14:28:13",
+        },
+        {
+          materialName: "鑱屼笟鎸囧鐩稿叧璧勬枡",
+          opdateTime: "2023-01-04 11:18:23",
+        },
+      ],
+      fetcher: async () => {
+        // console.log(this);
+        //console.log(params);
+        // let res = await api.publicEquipment.getBiogasDeviceInfoPage(params);
+        let res = {
+          total: this.testData.length,
+          data: this.testData,
+        };
+        return res;
+      },
+    };
+  },
+  watch: {},
+  methods: {
+    renderBtns(e) {
+      let that = this;
+      return (
+        <div>
+          <el-button
+            onClick={() => that.openDetail(e.row)}
+            size="mini"
+            plain
+            type="primary"
+          >
+            璇︽儏
+          </el-button>
+        </div>
+      );
+    },
+    openDetail() {
+      this.$router.push("/spaceIndex/materiaDetail");
+    },
+    handleSelect(index) {
+      // console.log(index);
+      this.activeIndex = index;
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.avatar {
+  width: 80px;
+  height: 80px;
+  border-radius: 50%;
+  overflow: hidden;
+
+  box-shadow: 0px 2px 6px rgba($color: #000000, $alpha: 0.2);
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>
diff --git a/src/views/spaceIndex/materiaDetail/index.vue b/src/views/spaceIndex/materiaDetail/index.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ff467f8d2c43fc5b1786bf2153f3acbc383c7b0e
--- /dev/null
+++ b/src/views/spaceIndex/materiaDetail/index.vue
@@ -0,0 +1,65 @@
+<template>
+  <InnerView class="flex-col" style="overflow: hidden;">
+    <BackTitle title="鏉愭枡璇︽儏"></BackTitle>
+    <ScrollView>
+      <div class="content-title">鍩虹淇℃伅</div>
+
+      <el-descriptions title="" :column="1" style="padding-left:30px">
+        <el-descriptions-item label="鏉愭枡鍚嶇О"
+          >鐢宠鍦ㄦ牎姹傝亴鍒涗笟琛ヨ创鐩稿叧璧勬枡</el-descriptions-item
+        >
+        <el-descriptions-item label="涓婁紶鏃堕棿"
+          >2023-01-13 12:25:23</el-descriptions-item
+        >
+      </el-descriptions>
+      <div class="content-title">鏉愭枡鏂囦欢</div>
+      <div class="file-box flex-between">
+        <span>鐢宠鍦ㄦ牎姹傝亴鍒涗笟琛ヨ创鐩稿叧璧勬枡</span>
+        <div class="flex download">
+          <i class="el-icon-download"></i>
+          <span>涓嬭浇</span>
+        </div>
+      </div>
+    </ScrollView>
+  </InnerView>
+</template>
+
+<script>
+// import * as api from "@/api";
+export default {
+  name: "SpaceIndexMateriaDetail",
+  meta: {
+    sort: 1,
+    title: "绌洪棿棣栭〉",
+    activeName: "spaceIndex",
+    hidden: true,
+  },
+  components: {},
+  data() {
+    return {};
+  },
+  watch: {},
+  methods: {},
+};
+</script>
+<style lang="scss" scoped>
+.file-box {
+  width: 529px;
+  height: 36px;
+  border: 1px solid #e5e5e5;
+  padding: 0 22px 0 18px;
+  font-size: 12px;
+  color: #666;
+  margin-top: 38px;
+  margin-left: 30px;
+  .download {
+    color: #3182ff;
+    align-items: center;
+    cursor: pointer;
+    i {
+      font-size: 17px;
+      margin-right: 5px;
+    }
+  }
+}
+</style>
diff --git a/vue.config.js b/vue.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e12205b173a80e052a9119de031d74314301510
--- /dev/null
+++ b/vue.config.js
@@ -0,0 +1,80 @@
+const path = require("path");
+// const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
+
+module.exports = {
+  lintOnSave: process.env.NODE_ENV === "development",
+  productionSourceMap: false,
+  devServer: {
+    open: false,
+    // proxy: {
+    //   [process.env.VUE_APP_BASE_API]: {
+    //     target: process.env.VUE_APP_REMOTE_URL,
+    //     changeOrigin: true,
+    //     pathRewrite: {
+    //       ["^" + process.env.VUE_APP_BASE_API]: ""
+    //     }
+    //   }
+    // },
+    // overlay: {
+    //   errors: true,
+    //   warnings: false
+    // }
+  },
+  configureWebpack: {
+    // plugins: [new BundleAnalyzerPlugin()],
+  },
+  chainWebpack(config) {
+    // it can improve the speed of the first screen, it is recommended to turn on preload
+    // it can improve the speed of the first screen, it is recommended to turn on preload
+    config.plugin("preload").tap(() => [
+      {
+        rel: "preload",
+        // to ignore runtime.js
+        // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
+        fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
+        include: "initial"
+      }
+    ]);
+
+    // when there are many pages, it will cause too many meaningless requests
+    config.plugins.delete("prefetch");
+
+    config.when(process.env.NODE_ENV !== "development", config => {
+      config
+        .plugin("ScriptExtHtmlWebpackPlugin")
+        .after("html")
+        .use("script-ext-html-webpack-plugin", [
+          {
+            // `runtime` must same as runtimeChunk name. default is `runtime`
+            inline: /runtime\..*\.js$/
+          }
+        ])
+        .end();
+      config.optimization.splitChunks({
+        chunks: "all",
+        cacheGroups: {
+          libs: {
+            name: "chunk-libs",
+            test: /[\\/]node_modules[\\/]/,
+            priority: 10,
+            chunks: "initial" // only package third parties that are initially dependent
+          },
+          elementUI: {
+            name: "chunk-elementUI", // split elementUI into a single package
+            priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
+            test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
+          },
+          commons: {
+            name: "chunk-commons",
+            test: path.join(__dirname, "src/components"), // can customize your rules
+            minChunks: 3, //  minimum common number
+            priority: 5,
+            reuseExistingChunk: true
+          }
+        }
+      });
+      // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
+      config.optimization.runtimeChunk("single");
+    });
+  }
+};