Sfoglia il codice sorgente

创建前端框架,首页部分代码提交

SunZehao 2 anni fa
parent
commit
1b7c322792

+ 23 - 0
.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?

+ 5 - 0
babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ]
+}

File diff suppressed because it is too large
+ 10882 - 0
package-lock.json


+ 66 - 0
package.json

@@ -0,0 +1,66 @@
+{
+  "name": "projectname",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "^2.0.10",
+    "axios": "^0.21.1",
+    "babel-polyfill": "^6.26.0",
+    "core-js": "^3.8.3",
+    "echarts": "^5.3.2",
+    "element-plus": "^2.2.5",
+    "element-ui": "^2.15.5",
+    "exceljs": "^4.3.0",
+    "file-saver": "^2.0.5",
+    "js-cookie": "^3.0.1",
+    "jszip": "^3.10.1",
+    "monaco-editor": "^0.27.0",
+    "monaco-editor-webpack-plugin": "^4.2.0",
+    "vue": "^3.2.13",
+    "vue-router": "^4.1.5",
+    "vuedraggable": "^4.1.0",
+    "xe-utils": "^3.3.1",
+    "xlsx": "^0.17.1",
+    "xlsx-js-style": "^1.2.0",
+    "xlsx-style": "^0.8.13"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.12.16",
+    "@babel/eslint-parser": "^7.12.16",
+    "@vue/cli-plugin-babel": "~5.0.0",
+    "@vue/cli-plugin-eslint": "~5.0.0",
+    "@vue/cli-service": "~5.0.0",
+    "eslint": "^7.32.0",
+    "eslint-plugin-vue": "^8.0.3",
+    "less": "^3.9.0",
+    "less-loader": "^5.0.0",
+    "node-sass": "^7.0.1",
+    "sass-loader": "^13.0.0",
+    "vuex": "^4.0.2"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/vue3-essential",
+      "eslint:recommended"
+    ],
+    "parserOptions": {
+      "parser": "@babel/eslint-parser"
+    },
+    "rules": {}
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead",
+    "not ie 11"
+  ]
+}

BIN
public/favicon.ico


+ 19 - 0
public/index.html

@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="">
+  <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">
+    <!-- <link rel="icon" type="image/png" href="<%= BASE_URL %>comlogo.png"> -->
+    <title><%= htmlWebpackPlugin.options.title %></title>
+
+  </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>

+ 50 - 0
src/App.vue

@@ -0,0 +1,50 @@
+<template>
+   <div id="app" v-loading="loading">
+    <!-- <img src="./assets/logo.png"> -->
+    <router-view />
+  </div>
+</template>
+
+<script>
+import commonHeader from '@/components/commonHeaders.vue'
+export default {
+    name: 'App',
+    components:{
+        commonHeader
+    },
+    data() {
+        return {
+            showHeader:false,
+            loading: true
+        }
+    },
+    watch:{
+        $route: {
+            handler: function(route) {
+                // route.path === '/index' || route.path === '/weather'
+                this.showHeader = route.path === '/login' ? false : true
+            },
+            immediate: true
+        }
+    },
+    mounted() {
+        window.workSpaceVo = {}
+        this.loadPage()
+    },
+    methods:{
+        loadPage() {
+            setTimeout(() => {
+                this.loading = false;
+            }, 1000);
+        }
+    }
+}
+</script>
+
+<style>
+@import "./assets/css/main.css";
+/* @import "./assets/css/color-dark.css"; */
+#app{
+    background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#005777), to(#005777), color-stop(0.2, #007aab),color-stop(0.8, #007aab));
+}
+</style>

File diff suppressed because it is too large
+ 1013 - 0
src/api/api.js


+ 15 - 0
src/api/auth.js

@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'Admin-Token'
+
+export function getToken() {
+  return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+  return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+  return Cookies.remove(TokenKey)
+}

+ 6 - 0
src/api/errorCode.js

@@ -0,0 +1,6 @@
+export default {
+  '401': '认证失败,无法访问系统资源',
+  '403': '当前操作没有权限',
+  '404': '访问资源不存在',
+  'default': '系统未知错误,请反馈给管理员'
+}

+ 33 - 0
src/api/permission.js

@@ -0,0 +1,33 @@
+import router from '../router'
+import { ElMessage } from 'element-plus'
+import { getToken } from './auth'
+import store from '../store'
+
+//  路由判断登录 根据路由配置文件的参数
+router.beforeEach((to, from, next) => {
+    //  to,将要访问的路径
+    // from  从哪个路径跳转过来的
+    // next 是一个函数,代表放行
+    // document.title = `${to.meta.title} | 集中式风电功率预测系统`
+    // if (to.query.markKey === "YXSSO") {
+    //     return next()
+    // } else {
+    //     let token = getToken()
+    //     if(to.path==='/login'){
+    //         return next()
+    //     }else{
+    //         if(!token){
+    //             ElMessage.error('登录已失效,请重新登录');
+    //             store.commit('clearAxiosArr')
+    //             return next('/login')
+    //         } else if (to.path==='/') {
+    //             store.commit('clearAxiosArr')
+    //             return next('/login')
+    //         } else{
+    //             store.commit('clearAxiosArr')
+    //             next()
+    //         }
+    //     }
+    // }
+    return next()
+})

+ 28 - 0
src/assets/css/color-dark.css

@@ -0,0 +1,28 @@
+.header {
+	background-color: #242f42;
+}
+.login-wrap {
+	background: #324157;
+}
+.plugins-tips {
+	background: #eef1f6;
+}
+.plugins-tips a {
+	color: #20a0ff;
+}
+.el-upload--text em {
+	color: #20a0ff;
+}
+.pure-button {
+	background: #20a0ff;
+}
+.tags-li.active {
+	border: 1px solid #409eff;
+	background-color: #409eff;
+}
+.message-title {
+	color: #20a0ff;
+}
+.collapse-btn:hover {
+	background: rgb(40, 52, 70);
+}

+ 4 - 0
src/assets/css/icon.css

@@ -0,0 +1,4 @@
+[class*=' el-icon-lx'],
+[class^='el-icon-lx'] {
+	font-family: lx-iconfont !important;
+}

+ 185 - 0
src/assets/css/main.css

@@ -0,0 +1,185 @@
+* {
+    margin: 0;
+    padding: 0;
+}
+
+html,
+body,
+#app,
+.wrapper {
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+}
+
+body {
+    font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'microsoft yahei', arial, STHeiTi, sans-serif;
+}
+
+a {
+    text-decoration: none;
+}
+
+.content-box {
+    position: absolute;
+    left: 250px;
+    right: 0;
+    top: 70px;
+    bottom: 0;
+    padding-bottom: 30px;
+    -webkit-transition: left 0.3s ease-in-out;
+    transition: left 0.3s ease-in-out;
+    background: #f0f0f0;
+}
+
+.content {
+    width: auto;
+    height: 100%;
+    padding: 5px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    box-sizing: border-box;
+}
+
+.content-collapse {
+    left: 65px;
+}
+
+.container {
+    padding: 30px;
+    background: #fff;
+    border: 1px solid #ddd;
+    border-radius: 5px;
+}
+
+.crumbs {
+    margin: 10px 0;
+}
+
+.el-table th {
+    background-color: #f5f7fa !important;
+    height: 40px;
+}
+.el-table tr {
+    height: 40px;
+}
+
+.el-input .el-input__wrapper{
+    padding: 0 7px;
+}
+
+.el-input .el-input__inner{
+    height: 30px;
+}
+
+.el-dialog .el-dialog__body{
+    padding: 20px 0;
+}
+
+.pagination {
+    margin: 20px 0;
+    text-align: right;
+}
+
+.plugins-tips {
+    padding: 20px 10px;
+    margin-bottom: 20px;
+}
+
+.el-button+.el-tooltip {
+    margin-left: 10px;
+}
+
+.el-table tr:hover {
+    background: #f6faff;
+}
+
+.mgb20 {
+    margin-bottom: 20px;
+}
+
+.move-enter-active,
+.move-leave-active {
+    transition: opacity 0.1s ease;
+}
+
+.move-enter-from,
+.move-leave-to {
+    opacity: 0;
+}
+
+
+/*BaseForm*/
+
+.form-box {
+    width: 600px;
+}
+
+.form-box .line {
+    text-align: center;
+}
+
+.el-time-panel__content::after,
+.el-time-panel__content::before {
+    margin-top: -7px;
+}
+
+.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
+    padding-bottom: 0;
+}
+
+
+/*Upload*/
+
+.pure-button {
+    width: 150px;
+    height: 40px;
+    line-height: 40px;
+    text-align: center;
+    color: #fff;
+    border-radius: 3px;
+}
+
+.g-core-image-corp-container .info-aside {
+    height: 45px;
+}
+
+
+/*VueEditor*/
+
+.ql-container {
+    min-height: 400px;
+}
+
+.ql-snow .ql-tooltip {
+    transform: translateX(117.5px) translateY(10px) !important;
+}
+
+.editor-btn {
+    margin-top: 20px;
+}
+
+
+/*markdown*/
+
+.v-note-wrapper .v-note-panel {
+    min-height: 500px;
+}
+
+
+/* //滚动条的宽度 */
+
+::-webkit-scrollbar {
+    width: 6px;
+    height: 8px;
+    background-color: #e4e4e4;
+    border-radius: 6px;
+}
+
+
+/* //滚动条的滑块 */
+
+::-webkit-scrollbar-thumb {
+    background-color: #a9aaad;
+    border-radius: 6px;
+}

+ 46 - 0
src/assets/font/iconfont.css

@@ -0,0 +1,46 @@
+@font-face {
+    font-family: "iconfont";
+    /* Project id 2906628 */
+    src: url('iconfont.woff2?t=1640660196056') format('woff2'), url('iconfont.woff?t=1640660196056') format('woff'), url('iconfont.ttf?t=1640660196056') format('truetype');
+}
+
+.iconfont {
+    font-family: "iconfont" !important;
+    font-size: 16px;
+    margin-left: 4px;
+    font-style: normal;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+}
+
+.iconIOTtubiao_huabanfuben:before {
+    content: "\e61a";
+}
+
+.iconzidingyi:before {
+    content: "\e61e";
+}
+
+.iconxitongcaidan:before {
+    content: "\e696";
+}
+
+.iconnibianqi:before {
+    content: "\e72c";
+}
+
+.iconbaojingpeizhi:before {
+    content: "\e67d";
+}
+
+.iconguzhangzhenduan:before {
+    content: "\e601";
+}
+
+.iconfengji:before {
+    content: "\e606";
+}
+
+.iconjichushuju:before {
+    content: "\e7d3";
+}

BIN
src/assets/font/iconfont.ttf


BIN
src/assets/font/iconfont.woff


BIN
src/assets/font/iconfont.woff2


BIN
src/assets/indexIcon/Assessment.png


BIN
src/assets/indexIcon/benchmarking.png


BIN
src/assets/indexIcon/configure.png


BIN
src/assets/indexIcon/gateway.png


+ 67 - 0
src/assets/js/dialogDrag.js

@@ -0,0 +1,67 @@
+const dialogDrag = (app) => {
+    app.directive('dialogdrag', {
+        // 渲染完毕
+        mounted(el) {
+            // 可视窗口的宽度
+            const clientWidth = document.documentElement.clientWidth
+                // 可视窗口的高度
+            const clientHeight = document.documentElement.clientHeight
+                // 记录坐标
+            let domset = {
+                x: clientWidth / 4, // 默认width 50%
+                y: clientHeight * 15 / 100 // 根据 15vh 计算
+            }
+
+            // 弹窗的容器
+            const domDrag = el.firstElementChild.firstElementChild
+                // 重新设置上、左距离
+            domDrag.style.marginTop = domset.y + 'px'
+            domDrag.style.marginLeft = domset.x + 'px'
+
+            // 记录拖拽开始的光标坐标,0 表示没有拖拽
+            let start = { x: 0, y: 0 }
+                // 移动中记录偏移量
+            let move = { x: 0, y: 0 }
+
+            // 鼠标按下,开始拖拽
+            document.onmousedown = (e) => {
+                // 判断对话框是否重新打开
+                if (domDrag.style.marginTop === '15vh') {
+                    // 重新打开,设置 domset.y  top
+                    domset.y = clientHeight * 15 / 100
+                }
+                start.x = e.clientX
+                start.y = e.clientY
+                domDrag.style.cursor = 'move' // 改变光标形状
+            }
+
+            // 鼠标移动,实时跟踪
+            document.onmousemove = (e) => {
+                    if (start.x === 0) { // 不是拖拽状态
+                        return
+                    }
+                    move.x = e.clientX - start.x
+                    move.y = e.clientY - start.y
+
+                    // 初始位置 + 拖拽距离
+                    domDrag.style.marginLeft = (domset.x + move.x) + 'px'
+                    domDrag.style.marginTop = (domset.y + move.y) + 'px'
+                }
+                // 鼠标抬起,结束拖拽
+            document.onmouseup = (e) => {
+                move.x = e.clientX - start.x
+                move.y = e.clientY - start.y
+
+                // 记录新坐标,作为下次拖拽的初始位置
+                domset.x += move.x
+                domset.y += move.y
+                domDrag.style.cursor = '' // 恢复光标形状
+                domDrag.style.marginLeft = domset.x + 'px'
+                domDrag.style.marginTop = domset.y + 'px'
+                    // 结束拖拽
+                start.x = 0
+            }
+        }
+    })
+}
+export default dialogDrag

BIN
src/assets/logo.png


BIN
src/assets/logoG.png


BIN
src/assets/logoGuo.png


+ 127 - 0
src/components/Overview.vue

@@ -0,0 +1,127 @@
+<template>
+    <div>
+        <div style="display:flex">
+            <div style="width: 600px">
+                <Editor
+                    :code="toString(dataformParams)"
+                    height="650px"
+                    :showDefaultTips="false"
+                    @change="setXhrParam"
+                />
+            </div>
+            <div style="padding: 20px">
+                <div style="border: 1px solid #d6d6d6; margin-bottom: 10px">
+                    <div id="echarts_test" style="width: 800px;height:500px"></div>
+                </div>
+                <el-button type="primary" @click="ceshisss">刷新</el-button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import Editor from "./editorCode/index.vue";
+export default {
+    components: {
+        Editor
+    },
+    data() {
+        return {
+            dataformParams: {},
+            getData: ''
+        }
+    },
+    mounted() {
+        this.$nextTick(() =>{
+            this.getEcharts()
+        })
+    },
+    methods: {
+        setXhrParam(value) {
+            this.getData = value
+        },
+        ceshisss() {
+            console.log(this.dataformParams)
+            this.getData = eval("(" + this.getData + ")")
+            
+            this.getEcharts(this.getData)
+        },
+        // 数组转字符串
+        toString(obj = {}) {
+            return String(JSON.stringify(obj, null, 2));
+        },
+        getEcharts(val) {
+            let option = {}
+            if (!val) {
+                option = {
+                    tooltip: {
+                        trigger: 'axis'
+                    },
+                    legend: {
+                        data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine']
+                    },
+                    grid: {
+                        left: '3%',
+                        right: '4%',
+                        bottom: '3%',
+                        containLabel: true
+                    },
+                    xAxis: {
+                        type: 'category',
+                        boundaryGap: false,
+                        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+                    },
+                    yAxis: {
+                        type: 'value'
+                    },
+                    series: [
+                        {
+                        name: 'Email',
+                        type: 'line',
+                        stack: 'Total',
+                        data: [120, 132, 101, 134, 90, 230, 210]
+                        },
+                        {
+                        name: 'Union Ads',
+                        type: 'line',
+                        stack: 'Total',
+                        data: [220, 182, 191, 234, 290, 330, 310]
+                        },
+                        {
+                        name: 'Video Ads',
+                        type: 'line',
+                        stack: 'Total',
+                        data: [150, 232, 201, 154, 190, 330, 410]
+                        },
+                        {
+                        name: 'Direct',
+                        type: 'line',
+                        stack: 'Total',
+                        data: [320, 332, 301, 334, 390, 330, 320]
+                        },
+                        {
+                        name: 'Search Engine',
+                        type: 'line',
+                        stack: 'Total',
+                        data: [820, 932, 901, 934, 1290, 1330, 1320]
+                        }
+                    ]
+                };
+            } else {
+                option = val
+            }
+            let dom = document.getElementById('echarts_test');
+            dom.removeAttribute("_echarts_instance_")
+            let myChart = this.$echarts.init(dom);
+            myChart.setOption(option);
+            window.addEventListener("resize", function () {
+                myChart.resize()
+            })
+        }
+    }
+}
+</script>
+
+<style>
+
+</style>

+ 127 - 0
src/components/Overview1.vue

@@ -0,0 +1,127 @@
+<template>
+    <div>
+        <div style="display:flex">
+            <div style="width: 600px">
+                <Editor
+                    :code="toString(dataformParams)"
+                    height="650px"
+                    :showDefaultTips="false"
+                    @change="setXhrParam"
+                />
+            </div>
+            <div style="padding: 20px">
+                <div style="border: 1px solid #d6d6d6; margin-bottom: 10px">
+                    <div id="echarts_test" style="width: 800px;height:500px"></div>
+                </div>
+                <el-button type="primary" @click="ceshisss">刷新</el-button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import Editor from "./editorCode/index.vue";
+export default {
+    components: {
+        Editor
+    },
+    data() {
+        return {
+            dataformParams: {},
+            getData: ''
+        }
+    },
+    mounted() {
+        this.$nextTick(() =>{
+            this.getEcharts()
+        })
+    },
+    methods: {
+        setXhrParam(value) {
+            this.getData = value
+        },
+        ceshisss() {
+            console.log(this.dataformParams)
+            this.getData = eval("(" + this.getData + ")")
+            
+            this.getEcharts(this.getData)
+        },
+        // 数组转字符串
+        toString(obj = {}) {
+            return String(JSON.stringify(obj, null, 2));
+        },
+        getEcharts(val) {
+            let option = {}
+            if (!val) {
+                option = {
+                    tooltip: {
+                        trigger: 'axis'
+                    },
+                    legend: {
+                        data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine']
+                    },
+                    grid: {
+                        left: '3%',
+                        right: '4%',
+                        bottom: '3%',
+                        containLabel: true
+                    },
+                    xAxis: {
+                        type: 'category',
+                        boundaryGap: false,
+                        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+                    },
+                    yAxis: {
+                        type: 'value'
+                    },
+                    series: [
+                        {
+                        name: 'Email',
+                        type: 'line',
+                        stack: 'Total',
+                        data: [120, 132, 101, 134, 90, 230, 210]
+                        },
+                        {
+                        name: 'Union Ads',
+                        type: 'line',
+                        stack: 'Total',
+                        data: [220, 182, 191, 234, 290, 330, 310]
+                        },
+                        {
+                        name: 'Video Ads',
+                        type: 'line',
+                        stack: 'Total',
+                        data: [150, 232, 201, 154, 190, 330, 410]
+                        },
+                        {
+                        name: 'Direct',
+                        type: 'line',
+                        stack: 'Total',
+                        data: [320, 332, 301, 334, 390, 330, 320]
+                        },
+                        {
+                        name: 'Search Engine',
+                        type: 'line',
+                        stack: 'Total',
+                        data: [820, 932, 901, 934, 1290, 1330, 1320]
+                        }
+                    ]
+                };
+            } else {
+                option = val
+            }
+            let dom = document.getElementById('echarts_test');
+            dom.removeAttribute("_echarts_instance_")
+            let myChart = this.$echarts.init(dom);
+            myChart.setOption(option);
+            window.addEventListener("resize", function () {
+                myChart.resize()
+            })
+        }
+    }
+}
+</script>
+
+<style>
+
+</style>

+ 89 - 0
src/components/Overview2.vue

@@ -0,0 +1,89 @@
+<template>
+    <div>
+        <div>
+            <div id="echarts_test" style="width: 700px;height:300px"></div>
+        </div>
+    </div>
+</template>
+
+<script>
+export default {
+    data() {
+        return {}
+    },
+    mounted() {
+        this.$nextTick(() =>{
+            this.getEcharts()
+        })
+    },
+    methods: {
+        getEcharts() {
+            let option = {
+                tooltip: {
+                    trigger: 'axis'
+                },
+                legend: {
+                    data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine']
+                },
+                grid: {
+                    left: '3%',
+                    right: '4%',
+                    bottom: '3%',
+                    containLabel: true
+                },
+                xAxis: {
+                    type: 'category',
+                    boundaryGap: false,
+                    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+                },
+                yAxis: {
+                    type: 'value'
+                },
+                series: [
+                    {
+                    name: 'Email',
+                    type: 'line',
+                    stack: 'Total',
+                    data: [120, 132, 101, 134, 90, 230, 210]
+                    },
+                    {
+                    name: 'Union Ads',
+                    type: 'line',
+                    stack: 'Total',
+                    data: [220, 182, 191, 234, 290, 330, 310]
+                    },
+                    {
+                    name: 'Video Ads',
+                    type: 'line',
+                    stack: 'Total',
+                    data: [150, 232, 201, 154, 190, 330, 410]
+                    },
+                    {
+                    name: 'Direct',
+                    type: 'line',
+                    stack: 'Total',
+                    data: [320, 332, 301, 334, 390, 330, 320]
+                    },
+                    {
+                    name: 'Search Engine',
+                    type: 'line',
+                    stack: 'Total',
+                    data: [820, 932, 901, 934, 1290, 1330, 1320]
+                    }
+                ]
+            };
+            let dom = document.getElementById('echarts_test');
+            dom.removeAttribute("_echarts_instance_")
+            let myChart = this.$echarts.init(dom);
+            myChart.setOption(option);
+            window.addEventListener("resize", function () {
+                myChart.resize()
+            })
+        }
+    }
+}
+</script>
+
+<style>
+
+</style>

+ 300 - 0
src/components/commonHeaders.vue

@@ -0,0 +1,300 @@
+<template>
+  <div class="proheader">
+    <el-container>
+        <el-header :class="getStyle()">
+            <div class="logoSty">
+                <img src="../assets/logoG.png" alt="">
+            </div>
+        </el-header>
+    </el-container>
+  </div>
+</template>
+
+<script>
+import { apiGetpersonalInformation } from '../api/api'
+import { removeToken } from '../api/auth'
+export default {
+    name:'headerCom',
+    data() {
+        return {
+            leftIndex: '1',
+            currentTime: '',
+            showRole: true,
+            menuData: [],
+            rainW: false,
+            userName: ''
+        }
+    },
+    created() {
+        this.menuData = [
+            {
+                index: '/index',
+                icon: 'Menu',
+                name: '概要',
+                showBac: true
+            },
+            {
+                index: '/panoramicPower',
+                icon: 'Histogram',
+                name: '全景功率'
+            },
+            {
+                index: '/powerPrediction',
+                icon: 'TrendCharts',
+                name: '功率预测'
+            },
+            {
+                index: '/batteryDiviner',
+                icon: 'TrendCharts',
+                name: '电量预测'
+            },
+            {
+                index: '/powerControl',
+                icon: 'Platform',
+                name: '功率管控'
+            },
+            {
+                index: '/weather',
+                icon: 'PictureFilled',
+                name: '天气预报'
+            },
+            {
+                index: '/statisticalSummary',
+                icon: 'List',
+                name: '统计汇总'
+            },
+            {
+                // index: 'http://120.46.129.85:7788/dashboard/marketBoundary',
+                index: 'http://10.155.32.7:8084',
+                icon: 'Connection',
+                name: '智能营销'
+            },
+            // {
+            //     index: '/datasExport',
+            //     icon: 'Download',
+            //     name: '数据导出'
+            // },
+            {
+                index: '/draggableTest',
+                icon: 'Download',
+                name: '拖拽测试'
+            }
+        ]
+    },
+    mounted() {
+        let that = this;
+        let userMes = JSON.parse(window.sessionStorage.getItem('userMessage'))
+        that.userName = userMes ? userMes.role : 'administrator'
+        if (this.$route.query.markKey) {
+            that.userName = that.$route.query.userid
+        }
+        if (userMes && userMes.role === 'ordinaryUser') {
+            that.showRole = false
+        }
+        // that.getUserMes(userMes.userId)
+    },
+    watch:{
+        $route: {
+            handler: function(route) {
+                console.log(route)
+                this.menuData.forEach(it =>{
+                    if (it.index === route.path) {
+                        it.showBac = true
+                    } else {
+                        it.showBac = false
+                    }
+                })
+            },
+            immediate: true
+        }
+    },
+    methods:{
+        changeRoute(item) {
+            if (item.name === '智能营销') {
+                let url = ''
+                if (location.origin.indexOf('10.15.32.7') !== -1) {
+                    url = location.origin + '/#/dashboard/marketBoundary' + `?userid=uuu&markKey=YXSSO`
+                } else {
+                    url = item.index
+                }
+                window.open(url, '_self')
+            } else {
+                this.$router.push({ path: item.index})
+            }
+        },
+        async backLogin() {
+            let loginName = this.userName
+            if (loginName) {
+                await this.LogoutInfor(loginName)
+            }
+        },
+        // 插入登出信息
+        LogoutInfor(name) {
+            let that = this
+            let onlyData = JSON.parse(window.sessionStorage.getItem('userDatamsg'))
+            let loginData = JSON.parse(window.sessionStorage.getItem('loginMsg'))
+            if (onlyData) {
+                let paramsLogin = onlyData ? onlyData.identifier : loginData.identific
+                let params={
+                    identifier: paramsLogin,
+                    loginName: name
+                }
+                apiGetinsertLogoutInformation(params).then(datas=>{
+                    if (datas) {
+                        let url = location.origin + '/#/login'
+                        window.open(url, '_self')
+                        location.reload()
+                        removeToken()
+                    }
+                })
+            } else {
+                let url = location.origin + '/#/login'
+                window.open(url, '_self')
+                location.reload()
+                removeToken()
+            }
+            
+        },
+        changeSetting() {
+            this.$router.push({ path: '/systemSettings/Overview'})
+        },
+        changePersonRole() {
+            this.$router.push({ path: '/personnelRole'})
+        },
+        getStyle() {
+            if (this.$route.path === '/weather') {
+                if (this.rainW) {
+                    return 'weatherSty'
+                } else {
+                    return 'headerSty'
+                }
+            }
+        },
+        getColor(val) {
+            let strWea = ''
+            if (val) {
+                strWea = 'changeBacksty'
+            } else {
+                strWea = 'defaultBacksty'
+            }
+            return strWea
+        },
+        //获取个人信息
+        getUserMes(id) {
+            let params = {
+                userId: id
+            }
+            apiGetpersonalInformation(params).then(datas =>{
+                window.sessionStorage.setItem('userInfo', JSON.stringify(datas.data.user))
+            })
+        },
+        changeDate(date) {
+            var y = date.getFullYear();
+            var m = date.getMonth() + 1;  
+            m = m < 10 ? ('0' + m) : m;  
+            var d = date.getDate();  
+            d = d < 10 ? ('0' + d) : d;  
+            var h = date.getHours();  
+            h=h < 10 ? ('0' + h) : h;  
+            var minute = date.getMinutes();  
+            minute = minute < 10 ? ('0' + minute) : minute;  
+            var second=date.getSeconds();  
+            second=second < 10 ? ('0' + second) : second;  
+            return y + '-' + m + '-' + d+' '+h+':'+minute+':'+second
+        },
+        nowTime() {
+            let that = this;
+            let date = new Date()
+            this.statusTimer = setInterval(function () {
+                var y = date.getFullYear();  
+                var m = date.getMonth() + 1;  
+                m = m < 10 ? ('0' + m) : m;  
+                var d = date.getDate();  
+                d = d < 10 ? ('0' + d) : d;  
+                var h = date.getHours();  
+                h=h < 10 ? ('0' + h) : h;  
+                var minute = date.getMinutes();  
+                minute = minute < 10 ? ('0' + minute) : minute;  
+                var second=date.getSeconds();  
+                second=second < 10 ? ('0' + second) : second;  
+                that.currentTime = y + '-' + m + '-' + d+' '+h+':'+minute+':'+second; 
+            }, 1000)
+        },
+        handleSelect(index){
+            if (this.leftIndex === index) return
+            this.leftIndex = index
+            // this.$router.push({path: index})
+            console.log(index)
+        }
+    }
+}
+</script>
+
+<style lang="less">
+.proheader{
+    position: fixed;
+    top: 0;
+    width: 100%;
+    z-index: 1999;
+    .weatherSty{
+        background: rgb(74, 87, 100);
+    }
+    .el-header {
+        padding: 0 10px;
+        display:flex;
+        justify-content: space-between;
+        background: rgb(13, 104, 188);
+        // background: rgba(96,103,105,.75);
+        color: #fff;
+        line-height: 60px;
+        height: 60px !important;
+        .logoSty{
+            padding: 10px 0 0 0;
+            img{
+                width: 310px;
+            }
+        }
+        .proMenu{
+            display: flex;
+            align-items: center;
+            .el-button-group{
+                .changeBacksty{
+                    background-color: #504bb5 !important;
+                }
+                .defaultBacksty{
+                    background-color: #373590 !important;
+                }
+                .defaultweathersty{
+                    background-color: #0d68bc !important;
+                }
+                .defaultweatherRainsty{
+                    background-color: #4a5764 !important;
+                }
+                .el-button{
+                    background-color: #373590;
+                    border-color: #8679df;
+                    padding: 0 20px;
+                    &:hover {
+                        background-color: #504bb5;
+                    }
+                }
+            }
+            .userMsg{
+                margin-left: 150px;
+                .el-icon{
+                    cursor: pointer;
+                }
+            }
+            .divider{
+                position: relative;
+                top: -3px;
+            }
+            .settingStop{
+                .el-icon{
+                    cursor: pointer;
+                }
+            }
+        }
+    }
+}
+</style>

+ 166 - 0
src/components/editorCode/index.vue

@@ -0,0 +1,166 @@
+<template>
+  <div class="codeEditorAlertBox" v-if="showCurrentTips || showDefaultTips">
+    <el-alert
+      :title="
+        tip ||
+        `// 当前输入语言为 ${
+          language === 'get' || language === 'post' ? 'javascript' : language
+        } , 请注意书写规范。`
+      "
+      v-if="showCurrentTips"
+    />
+    <el-alert :title="defaultTips" v-if="showDefaultTips" />
+  </div>
+  <div id="codeEditBox" :style="`height:${height ? height : '500px'}`"></div>
+</template>
+
+<script>
+import * as monaco from "monaco-editor";
+import { toRaw } from "vue";
+export default {
+  props: {
+    height: {
+      type: String,
+      default: "500px",
+    },
+    tip: {
+      type: String,
+      default: () => "",
+    },
+    code: {
+      type: String,
+      default: () => "",
+    },
+    showCurrentTips: {
+      type: Boolean,
+      default: () => true,
+    },
+    showDefaultTips: {
+      type: Boolean,
+      default: () => true,
+    },
+    language: {
+      type: String,
+      default: "javascript",
+    },
+  },
+  data() {
+    return {
+      editorValue: "",
+      monacoEditor: null,
+      defaultTips:
+        "// 每一种类型的事件都携带 2 个内置变量,变量名称为 [e 和 target] ,分别表示 鼠标原生event对象 和 精简后的组件对象(包含事件发生位置在页面内的坐标与触发事件的组件),如若需要,可自行直接使用其名称来进行代码书写。",
+      activePen: {},
+    };
+  },
+  mounted() {
+    this.editorValue = this.code || "";
+    this.init();
+  },
+  unmounted() {
+    this.getEditorValue();
+  },
+  methods: {
+    init() {
+      // 使用 - 创建 monacoEditor 对象
+      const language =
+        this.language === "get" || this.language === "post"
+          ? "javascript"
+          : this.language;
+      this.monacoEditor = monaco.editor.create(
+        document.getElementById("codeEditBox"),
+        {
+          theme: "vs-dark", // 主题
+          value: this.editorValue, // 默认显示的值
+          language: language || "javascript",
+          folding: true, // 是否折叠
+          foldingHighlight: true, // 折叠等高线
+          foldingStrategy: "indentation", // 折叠方式  auto | indentation
+          showFoldingControls: "always", // 是否一直显示折叠 always | mouseover
+          disableLayerHinting: true, // 等宽优化
+          emptySelectionClipboard: false, // 空选择剪切板
+          selectionClipboard: false, // 选择剪切板
+          automaticLayout: true, // 自动布局
+          codeLens: false, // 代码镜头
+          scrollBeyondLastLine: false, // 滚动完最后一行后再滚动一屏幕
+          colorDecorators: true, // 颜色装饰器
+          accessibilitySupport: "off", // 辅助功能支持  "auto" | "off" | "on"
+          lineNumbers: "on", // 行号 取值: "on" | "off" | "relative" | "interval" | function
+          lineNumbersMinChars: 5, // 行号最小字符   number
+          enableSplitViewResizing: false,
+          readOnly: false, //是否只读  取值 true | false
+        }
+      );
+
+        this.monacoEditor.onDidBlurEditorText(() =>{
+            this.getEditorValue()
+        })
+      
+    },
+
+    getEditorValue() {
+      this.$emit("change", toRaw(this.monacoEditor).getValue());
+    },
+
+    setEditorValue(newValue) {
+      toRaw(this.monacoEditor).setValue(newValue);
+      this.getEditorValue();
+    },
+  },
+
+  watch: {
+    code(value) {
+      this.editorValue = value;
+      toRaw(this.monacoEditor).setValue(this.editorValue);
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+#codeEditBox {
+  width: 100%;
+}
+
+.codeEditorAlertBox {
+  .el-alert {
+    font-family: Consolas, "Courier New", monospace;
+    font-weight: normal;
+    font-size: 14px;
+    font-feature-settings: "liga" 0, "calt" 0;
+    line-height: 19px;
+    width: 100%;
+    background: #1e1e1e;
+    color: #608b4e;
+    text-indent: 4em;
+    user-select: none;
+    padding: 0 0 8px 0;
+    border-radius: 0;
+  }
+
+  .el-alert.danger {
+    color: #f25656;
+    font-weight: 700;
+    user-select: auto;
+  }
+
+  .el-alert:first-child {
+    padding-top: 8px;
+  }
+}
+</style>
+
+<style lang="less">
+.codeEditorAlertBox {
+  .el-alert {
+    .el-alert__content {
+      width: calc(100% - 16px);
+    }
+
+    .el-alert__title {
+      width: 98%;
+      display: flex;
+    }
+  }
+}
+</style>

+ 32 - 0
src/components/menuTreeconfig.vue

@@ -0,0 +1,32 @@
+<template>
+    <div>
+    <template v-for="menu in this.menuData">
+      <el-sub-menu :key="menu.index" :index="menu.index" v-if="menu.children">
+          <template #title>
+              <!-- <i :class="menu.icon"></i> -->
+              {{menu.name}}
+              <!-- <span slot="title">{{menu.name}}</span> -->
+          </template>
+          <menu-tree :menuData="menu.children"></menu-tree>
+      </el-sub-menu>
+      <el-menu-item :key="menu.index" :index="menu.index" :route="menu.index" v-else>
+          <!-- <i :class="menu.icon"></i> -->
+          <template #title>
+            {{menu.name}}
+          </template>
+          <!-- <span slot="title">{{menu.name}}</span> -->
+      </el-menu-item>
+    </template>
+  </div>
+</template>
+
+<script>
+export default {
+    props: ['menuData'],
+    name: 'MenuTree'
+}
+</script>
+
+<style>
+
+</style>

+ 24 - 0
src/main.js

@@ -0,0 +1,24 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+// import httpss from './api/http.js'
+import router from './router'
+import ElementPlus from 'element-plus'
+import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
+import 'element-plus/dist/index.css'
+import './assets/css/icon.css'
+import './assets/font/iconfont.css'
+import './api/permission' //路由守卫
+import * as echarts from 'echarts'
+import utils from '@/utils/index.js'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+import store from './store'
+import axios from 'axios'
+
+const app = createApp(App)
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component)
+}
+app.config.globalProperties.$utils = utils;
+app.config.globalProperties.$echarts = echarts;
+app.config.globalProperties.$axios = axios;
+app.use(router).use(store).use(ElementPlus, { size: 'small',locale: zhCn }).mount('#app')

+ 121 - 0
src/request/index.js

@@ -0,0 +1,121 @@
+import axios from 'axios'
+import { getToken } from '../api/auth'
+import { Notification, MessageBox, ElMessage  } from 'element-plus'
+import errorCode from '@/api/errorCode'
+import store from '../store'
+const baseUrl = require('../utils/baseUrl.js')
+
+// 创建一个 axios 实例
+// const token = getToken() ? getToken() : window.sessionStorage.getItem('token')
+const service = axios.create({
+    // baseURL: '/api/', // 所有的请求地址前缀部分
+    baseURL: process.env.NODE_ENV === "production" ? baseUrl.ROOT : baseUrl.ROOT, // 所有的请求地址前缀部分
+    timeout: 60000, // 请求超时时间毫秒
+    // withCredentials: true, // 异步请求携带cookie
+    headers: {
+        // 设置后端需要的传参类型
+        'Content-Type': 'application/json;charset=utf-8',
+        'X-Requested-With': 'XMLHttpRequest',
+    },
+})
+
+// 添加请求拦截器
+service.interceptors.request.use(
+    function (config) {
+        config.headers.token = getToken()
+        // 在发送请求设置cancel token
+        config.cancelToken = new axios.CancelToken(cancel =>{
+            store.commit('setAxiosArr', {cancelToken: cancel})
+        })
+        return config
+    },
+    function (error) {
+        // 对请求错误做些什么
+        console.log(error)
+        return Promise.reject(error)
+    }
+)
+
+// 添加响应拦截器
+service.interceptors.response.use(
+    function (res) {
+        // console.log(response)
+        // // 2xx 范围内的状态码都会触发该函数。
+        // // 对响应数据做点什么
+        // // dataAxios 是 axios 返回数据中的 data
+        // const dataAxios = response.data
+        // // 这个状态码是和后端约定的
+        // const code = dataAxios.reset
+        // return dataAxios
+
+        // 未设置状态码则默认成功状态
+        const code = res.data.code || 200;
+        // 获取错误信息
+        const msg = errorCode[code] || res.data.msg || errorCode['default']
+        if (code === 401) {
+            MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
+                confirmButtonText: '重新登录',
+                cancelButtonText: '取消',
+                type: 'warning'
+                }
+            ).then(() => {
+                this.$route.replace({path: '/login'})
+            })
+        } else if (code === 402) {
+            MessageBox.confirm(msg, '系统提示', {
+                confirmButtonText: '重新登录',
+                cancelButtonText: '取消',
+                type: 'warning'
+            }).then(() => {
+            })
+        } else if (code === 500) {
+            ElMessage ({
+                message: msg,
+                type: 'error'
+            })
+            return Promise.reject(new Error(msg))
+        } else if (code !== 200) {
+            Notification.error({
+                title: msg
+            })
+            return Promise.reject('error')
+        } else {
+            return res.data
+        }
+    },
+    function (error) {
+        // // 超出 2xx 范围的状态码都会触发该函数。
+        // // 对响应错误做点什么
+        // console.log(error)
+        // return Promise.reject(error)
+
+        let { message } = error;
+        if (message) {
+            let code = message.substr(message.length - 3, message.length)
+        if (message == "Network Error") {
+            message = "后端接口连接异常";
+        } else if (message.includes("timeout")) {
+            message = "系统接口请求超时";
+        } else if (message.includes("Request failed with status code")) {
+            message = "系统接口" + message.substr(message.length - 3) + "异常";
+        }
+        if (code !== '401') {
+            ElMessage ({
+                message: message,
+                type: 'error',
+                duration: 5 * 1000
+            })
+        }
+        if (code === '401') {
+            let url = location.origin + '/#/login'
+            window.open(url, '_self')
+        }
+            return Promise.reject(error)
+        } else {
+            return new Promise(() =>{})
+        }
+    }
+)
+
+export default service
+

+ 30 - 0
src/router/index.js

@@ -0,0 +1,30 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+const routes = [
+    {
+        path: '/',
+        name: 'homePage',
+        meta: {
+            title: '首页',
+        },
+        component: () =>
+            import(
+                '../views/homePage.vue'
+            ),
+    },
+    { // 总览
+        path: '/systemSettings/Overview',
+        name:'Overview',
+        component: () =>
+            import(
+                '../components/Overview.vue'
+            ),
+    },
+]
+
+const router = createRouter({
+    history: createWebHashHistory(''),
+    // history: '',
+    routes,
+})
+
+export default router

+ 32 - 0
src/store/index.js

@@ -0,0 +1,32 @@
+import { createStore } from 'vuex'
+
+export default createStore({
+  //state存放状态,
+    state: {
+        version: undefined,//需要共用的数据
+        weatherData: undefined,
+        axiosArr: []        
+    },
+    //getter为state的计算属性
+    getters: {
+    },
+    //mutations可更改状态的逻辑,同步操作
+    mutations: {
+        setAxiosArr (state, cancleAjax) {
+        state.axiosArr.push(cancleAjax.cancelToken)
+        },
+        clearAxiosArr (state) {
+        let message = '路由切换中断异步请求'
+        state.axiosArr.forEach(item =>{
+            item()
+        })
+        state.axiosArr = []
+        }
+    },
+    //提交mutation,异步操作
+    actions: {
+    },
+    // 将store模块化
+    modules: {
+    }
+})

+ 26 - 0
src/utils/baseUrl.js

@@ -0,0 +1,26 @@
+// let URL= ""; //生产环境url 
+// // let URL= "http://10.155.32.7:8086"; //生产环境url 
+// let ROOT = ""; //这里是一个默认的url,可以没有
+
+const baseUrl = {
+    URL: '',
+    ROOT: ''
+}
+switch (process.env.NODE_ENV) { 
+    case 'development': 
+        baseUrl.ROOT = "/api"  //开发环境url
+        // baseUrl.URL = "http://192.168.2.6:8086"
+        baseUrl.URL = "http://120.46.129.85:8086"
+        // baseUrl.URL = "http://10.155.32.7:8086"
+        break
+ 
+    case 'production': 
+        baseUrl.ROOT = "http://120.46.129.85:8086/"
+        // baseUrl.ROOT = "http://10.155.32.7:8086/"
+        break 
+}
+
+module.exports = baseUrl
+// debugger
+// exports.ROOT
+// exports.URL

+ 630 - 0
src/utils/index.js

@@ -0,0 +1,630 @@
+// import http from '@/api/http.js'
+// import { downloadXlsx } from "../utils/xlsx";
+import { saveAs } from 'file-saver'
+import {apiGetExportMsg, apiGetModel,} from '../api/api'
+// import XLSXD from 'xlsx-style'
+// import Papa from 'papaparse'
+// import { isObject } from "xe-utils"
+
+//检查空
+const checkNull = val => val === undefined || val === null
+
+const until = {
+    // 计算比率
+    ratioCalculation(a, b) {
+        let num = null
+        if (a !== '-' && b !== '-') {
+            if (a === '0.00' || a === 0) {
+                num = 0
+            } else if (b === '0.00' || b === 0) {
+                num = '-'
+            } else {
+                num = Math.round((Number(a)/Number(b)*100))+'%'
+            }
+        } else {
+            num = '-'
+        }
+        return num
+    },
+    // 导出所有
+    downloadPer(url,fileName, idss, idsmo) {
+        let params = null
+        if (!idsmo) {
+            params = {
+                ids: idss ? idss.join(',') : ''
+            }
+        } else {
+            params = {
+                idsMv: idss ? idss.join(',') : '',
+                idsBcr: idsmo ? idsmo.join(',') : '',
+            }
+        }
+        apiGetExportMsg(url,params).then(datas =>{
+            let blob = new Blob([datas])
+            saveAs(blob, fileName)
+        }).catch((r) => {
+            console.error(r)
+        })
+    },
+    // 下载模板
+    downloadTemplate(url, params, proName) {
+        apiGetModel(url,params).then(datas =>{
+            let blob = new Blob([datas])
+            saveAs(blob, proName)
+        }).catch((r) => {
+            console.error(r)
+        })
+    },
+    getTime(date){
+        var y = date.getFullYear();  
+        var m = date.getMonth() + 1;  
+        m = m < 10 ? ('0' + m) : m;  
+        var d = date.getDate();  
+        d = d < 10 ? ('0' + d) : d;  
+        var h = date.getHours();  
+        h=h < 10 ? ('0' + h) : h;  
+        var minute = date.getMinutes();  
+        minute = minute < 10 ? ('0' + minute) : minute;  
+        var second=date.getSeconds();  
+        second=second < 10 ? ('0' + second) : second;  
+        return y + '-' + m + '-' + d+' '+h+':'+minute+':'+second; 
+        // timeF = y + '-' + m + '-' + d
+        // return timeF
+    },
+    changePowerPickDate(val) {
+        let endss = val.substring(val.length-2, val.length) * 1
+        let startTime = val.substring(0, val.length-2)
+        let allTime = ''
+        if (0 <= endss &&  endss < 15) {
+            allTime = startTime + '00'
+        } else if (15 <= endss &&  endss  < 30) {
+            allTime = startTime + '15'
+        } else if (30 <= endss &&  endss  < 45) {
+            allTime = startTime + '30'
+        } else if (45 <= endss &&  endss  < 60) {
+            allTime = startTime + '45'
+        }
+        return allTime
+    },
+    changeElectricPickDate(val) {
+        let endss = val.substring(val.length-2, val.length) * 1
+        let startTime = val.substring(0, val.length-2)
+        let allTime = ''
+        if (0 <= endss &&  endss < 10) {
+            allTime = startTime + '00'
+        } else if (10 <= endss &&  endss  < 20) {
+            allTime = startTime + '10'
+        } else if (20 <= endss &&  endss  < 30) {
+            allTime = startTime + '20'
+        } else if (30 <= endss &&  endss  < 40) {
+            allTime = startTime + '30'
+        } else if (40 <= endss &&  endss  < 50) {
+            allTime = startTime + '40'
+        } else if (50 <= endss &&  endss  < 60) {
+            allTime = startTime + '50'
+        }
+        return allTime
+    },
+    oninput(str, limit, type, zero) {
+        debugger
+        if (zero) {
+            if (str.substr(0, 1) === '.' || str.substr(0, 1) === '-') {
+                str = ''
+            }
+        } else {
+            if (str.substr(0, 1) === '.' || str.substr(0, 1) === '-' || str.substr(0, 1) === '0') {
+                str = ''
+            }
+        }
+        if (type === 'float') {
+            str = str.replace(/[^\d^\.]+/g, '') // 保留数字和小数点
+            if (limit === 1) {
+                str = str.replace(/^\D*([0-9]\d*\.?\d{0,1})?.*$/, '$1') // 小数点后只能输 1 位
+            } else if (limit === 2) {
+                str = str.replace(/^\D*([0-9]\d*\.?\d{0,2})?.*$/, '$1') // 小数点后只能输 2 位
+            } else if (limit === 3) {
+                str = str.replace(/^\D*([0-9]\d*\.?\d{0,3})?.*$/, '$1') // 小数点后只能输 3 位
+            } else if (limit === 4) {
+                str = str.replace(/^\D*([0-9]\d*\.?\d{0,4})?.*$/, '$1') // 小数点后只能输 4 位
+            } else if (limit === 6) {
+                str = str.replace(/^\D*([0-9]\d*\.?\d{0,6})?.*$/, '$1') // 小数点后只能输 6 位
+            }
+        } else {
+            str = str.replace(/[^\d^]+/g, '')
+            str = str.substring(0, limit)
+        }
+        return str
+    },
+    sortBy(attr,rev){
+        //第二个参数没有传递 默认升序排列
+        if(rev ==  undefined){
+            rev = 1;
+        }else{
+            rev = (rev) ? 1 : -1;
+        }
+        return function(a,b){
+            a = a[attr];
+            b = b[attr];
+            if(a < b){
+                return rev * -1;
+            }
+            if(a > b){
+                return rev * 1;
+            }
+            return 0;
+        }
+    },
+    /**
+ * 字母大小写切换
+ * @param str 要处理的字符串
+ * @param type 1:首字母大写其余小写 2:首子母小写其余大写 3:大小写转换 4:全部大写 5:全部小写
+ */
+    strChangeCase(str, type) {
+        function ToggleCase(str) {
+            var itemText = ""
+            str.split("").forEach(
+                function (item) {
+                    if (/^([a-z]+)/.test(item)) {
+                        itemText += item.toUpperCase();
+                    } else if (/^([A-Z]+)/.test(item)) {
+                        itemText += item.toLowerCase();
+                    } else {
+                        itemText += item;
+                    }
+                });
+            return itemText;
+        }
+
+        switch (type) {
+            case 1:
+                return str.replace(/^(\w)(\w+)/, function (v, v1, v2) {
+                    return v1.toUpperCase() + v2.toLowerCase();
+                });
+            case 2:
+                return str.replace(/^(\w)(\w+)/, function (v, v1, v2) {
+                    return v1.toLowerCase() + v2.toUpperCase();
+                });
+            case 3:
+                return ToggleCase(str);
+            case 4:
+                return str.toUpperCase();
+            case 5:
+                return str.toLowerCase();
+            default:
+                return str;
+        }
+    },
+    /*
+ *数字每千位加逗号
+ * 
+ */
+    commafy(num) {
+        return num && num.toString()
+        .replace(/\d+/, function(s){
+            return s.replace(/(\d)(?=(\d{3})+$)/g, '$1,')
+        })
+    },
+
+/*
+ *手机号码中间4位隐藏花号(*)显示
+ *
+ */
+    hideMobile(mobile) {
+        return mobile && mobile.toString().replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2")
+    },
+    /*
+ * 验证是否为数字
+ */
+    isNumber(n) {
+        return !isNaN(parseFloat(n)) && isFinite(n);
+    },
+
+    /*
+    * 是否为数组
+    */
+    isArray(obj) {
+        return Object.prototype.toString.call(obj) === '[object Array]';
+    },
+
+    /*
+    * 递归深拷贝
+    */
+    deepCopy(obj) {
+        let result = Array.isArray(obj) ? [] : {};
+        for (let key in obj) {
+            if (obj.hasOwnProperty(key)) {
+            if (typeof obj[key] === 'object' && obj[key] !== null) {
+                result[key] = deepCopy(obj[key]);
+            } else {
+                result[key] = obj[key];
+            }
+            }
+        }
+        return result;
+    },
+    exportTable(name, columnData, tableDatas) {
+        let datalist = [];
+        //表头数据
+        let cluData = ''
+        for(let i=0; i<columnData.length;i++){
+            cluData += columnData[i].label+','
+        }
+        cluData.slice(0, cluData.length-1)
+        datalist.push(cluData.split(','));
+        //这里的tableData为你的表格数据
+        for(let j=0;j<tableDatas.length;j++) {
+            let item = tableDatas[j]
+            let tabelDatas = ''
+            for(let k=0; k<columnData.length;k++){
+                let its = columnData[k]
+                if (item[its.value] !==null) {
+                    tabelDatas += item[its.value] + ','
+                } else {
+                    tabelDatas +=  '-,'
+                }
+            }
+            datalist.push(tabelDatas.split(','));
+        }
+        downloadXlsx(datalist, `${name}.xlsx`);
+        // let blob = new Blob([datalist])
+        // saveAs(blob, `${name}.xlsx`)
+        // this.exportCsv(datalist)
+    },
+
+    exportExcel() {
+        // let time = this.$utils.getTime(new Date())
+        // let titleName = this.title + ' ' + time
+        // this.$utils.exportTable(titleName,this.columData, this.tableData)
+
+        let wb = XLSX.utils.book_new();
+        let headers = {
+            name: '姓名',
+            age: '年龄',
+            sex: '性别'
+        }
+        this.columData.unshift(headers)
+        let contentWs = XLSX.utils.json_to_sheet(this.columData, {
+            header: ['name', 'age', 'sex'], // 可自定义表头顺序
+            skipHeader: true, //是否忽略表头,默认为false
+            origin: 'A2' // 设置插入位置
+        })
+        contentWs['!merges'] = [{s:{r:0,c:0}, e:{r:1,c:0}}, {s:{r:0,c:1}, e:{r:1,c:1}}]
+        // contentWs['!merges'] = [{s:{r:1,c:2}, e:{r:2,c:2}}]
+        // contentWs['A1'] = {
+        //     t: 's',
+        //     v: '人员',
+        //     s: {
+        //         font:{
+        //             name: '微软雅黑',
+        //             sz: 16,
+        //             bold: true,
+        //             color: {rgb: 'ffffff'}
+        //         },
+        //         alignment: {
+        //             horizontal: 'center',
+        //             vertical: 'center'
+        //         },
+        //         fill:{bgcolor: {rgb: '4472c4'}}
+        //     }
+        // }
+        // 设置合并单元格 !merges为一个对象数组,每个对象设定了单元格合并的规则,
+        // {s:{r:0,c:0}, e:{r:0,c:2}}为一个规则,s:起始位置,e:结束位置,r:行,c:列
+        // contentWs['!merges'] = [{s:{r:0,c:0}, e:{r:0,c:2}}]
+
+        
+        // contentWs['A3'] = {
+        //     t: 's',
+        //     v: '人员ss',
+        //     s: {
+        //         font:{
+        //             name: '微软雅黑',
+        //             sz: 16,
+        //             bold: true,
+        //             color: {rgb: 'ffffff'}
+        //         },
+        //         alignment: {
+        //             horizontal: 'center',
+        //             vertical: 'center'
+        //         },
+        //         fill:{bgcolor: {rgb: '4472c4'}}
+        //     }
+        // }
+        // contentWs['!merges'] = [{s:{r:0,c:2}, e:{r:0,c:4}}]
+        //设置列宽
+        contentWs['!cols'] = [{wch: 30},{wch: 20},{wch: 40}]
+        XLSX.utils.book_append_sheet(wb, contentWs, '明细')
+        // XLSX.writeFile(wb, '明细.xlsx')
+        const tmpDown = new Blob([
+            this.s2ab(
+                XLSXD.write(wb, {
+                    bookType: 'xlsx',
+                    bookSST: true,
+                    type: 'binary',
+                    cellStyles: true
+                })
+            )
+        ])
+        this.downExcel(tmpDown, '明细.xlsx')
+    },
+    downExcel(obj, fileName) {
+        const a_node = document.createElement('a')
+        a_node.download = fileName
+        if ('msSaveOrOpenBlob'in navigator) {
+            window.navigator.msSaveOrOpenBlob(obj, fileName)
+        } else {
+            a_node.href = URL.createObjectURL(obj)
+        }
+        a_node.click()
+        setTimeout(() =>{
+            URL.createObjectURL(obj)
+        }, 2000)
+    },
+    s2ab(s) {
+        if (typeof ArrayBuffer !== 'undefined') {
+            const buf = new ArrayBuffer(s.length)
+            const view = new Uint8Array(buf)
+            for(let i =0; i != s.length; ++i) {
+                view[i] = s.charCodeAt(i) & 0xff
+            }
+            return buf
+        } else {
+            const buf = new Array(s.length)
+            for(let i =0; i != s.length; ++i) {
+                buf[i] = s.charCodeAt(i) & 0xff
+            }
+            return buf
+        }
+    },
+    setTooltip(myChart1, myChart2, num) {
+        // const myChart1 = this.$echarts.init(document.getElementById(name1))
+        // const myChart2 = this.$echarts.init(document.getElementById(name2))
+        myChart1.getZr().on('mousemove', (params) => {
+            const pointInPixel = [params.offsetX, params.offsetY];
+            // 判断当前鼠标移动的位置是否在图表中
+            if (myChart1.containPixel('grid', pointInPixel)) {
+                //使用 convertFromPixel方法 转换像素坐标值到逻辑坐标系上的点。获取点击位置对应的x轴数据的索引值
+                const pointInGrid = myChart1.convertFromPixel({ seriesIndex: 0 }, pointInPixel);
+                // x轴数据的索引值
+                const xIndex = pointInGrid[0];
+                // 使用getOption() 获取图表的option
+                const op = myChart1.getOption();
+                // 获取当前点击位置要的数据
+                const xDate = op.xAxis[0].data[xIndex];
+                // 这里不直接用params.dataIndex是因为可能两个图表X轴的月份数据点不一致
+                const dataIndex = op.xAxis[0].data.findIndex(x => x === xDate);
+                myChart2.dispatchAction({
+                    type: 'showTip',
+                    seriesIndex: num,
+                    // 我用的echarts版本是4.8.0,用name而不用dataIndex时,不知道为什么tooltip不显示,所以这里用dataIndex
+                    // name: params.name
+                    dataIndex: dataIndex,
+                    position: '15%'
+                });
+            } else { 
+                myChart2.dispatchAction({
+                    type: 'hideTip'
+                });
+            }
+        })
+        myChart2.getZr().on('mousemove', (params) => {
+            const pointInPixel = [params.offsetX, params.offsetY];
+            // 判断当前鼠标移动的位置是否在图表中
+            if (myChart2.containPixel('grid', pointInPixel)) {
+                //使用 convertFromPixel方法 转换像素坐标值到逻辑坐标系上的点。获取点击位置对应的x轴数据的索引值
+                const pointInGrid = myChart2.convertFromPixel({ seriesIndex: 0 }, pointInPixel);
+                // x轴数据的索引值
+                const xIndex = pointInGrid[0];
+                // 使用getOption() 获取图表的option
+                const op = myChart2.getOption();
+                // 获取当前点击位置要的数据
+                const xDate = op.xAxis[0].data[xIndex];
+                // 这里不直接用params.dataIndex是因为可能两个图表X轴的月份数据点不一致
+                const dataIndex = op.xAxis[0].data.findIndex(x => x === xDate);
+                myChart1.dispatchAction({
+                    type: 'showTip',
+                    seriesIndex: num,
+                    // 我用的echarts版本是4.8.0,用name而不用dataIndex时,不知道为什么tooltip不显示,所以这里用dataIndex
+                    // name: params.name
+                    dataIndex: dataIndex,
+                    position: '15%'
+                });
+            } else { 
+                myChart1.dispatchAction({
+                    type: 'hideTip'
+                });
+            }
+        })
+    },
+    // 数据是否为null,NaN,0的判断
+    isHasNum(data) {
+        let num = null
+        if (data && data !== 0) {
+            if (data !== 'NaN' && data !== null) {
+                num = Number(data).toFixed(2)
+            } else {
+                num = '-'
+            }
+        } else if(data === 0) {
+            num = 0
+        } else {
+            num = '-'
+        }
+        return num
+    },
+    //获取当前月天数
+    hasYearMonthDate(a, b) {
+        let d = new Date(a, b, 0)
+        let de = d.getDate()
+        return de
+    },
+    // ------------------------------------------------------------------------
+    // 冒泡排序
+    sortBubble(arr) {
+        for(let i=0; i<arr.length; i++){
+            for(let j=0; j<arr.length-i; j++){
+                if (arr[j] > arr[j+1]) {
+                    let temp = arr[j]
+                    arr[j] = arr[j+1]
+                    arr[j+1] = temp
+                }
+            }
+        }
+        return arr
+    },
+    //数组去重
+    // 方法一
+    //利用for 循环 搭配 indexOf 去重
+    unique(arr) {
+        let newArr = []
+        for(let i=0; i<arr.length; i++) {
+            if (newArr.indexOf(arr[i]) === -1) {
+                newArr.push(arr[i])
+            }
+        }
+        return arr
+    },
+    //方法二
+    //借助ES6提供的Set结构 new Set() 
+    // var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22];
+    // var arr2 = noRepeat(arr)
+    noRepeatT(arr){
+        var newArr = [...new Set(arr)]; //利用了Set结构不能接收重复数据的特点
+        return newArr
+    },
+    //方法三
+    //利用 filter() 去重
+    //eg: var arr = ['apple','apps','pear','apple','orange','apps']
+    useFilter(arr) {
+        return arr.filter(function(item,index){
+            return arr.indexOf(item) === index;  // 因为indexOf 只能查找到第一个  
+        })
+    },
+    //方法四
+    //将数组的每一个元素依次与其他元素做比较,发现重复元素,删除 
+    //eg: var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22];
+    noRepeatF(arr) {
+        for(var i = 0; i < arr.length-1; i++){
+            for(var j = i+1; j < arr.length; j++){
+                if(arr[i]===arr[j]){
+                    arr.splice(j,1);
+                    j--;
+                }
+            }
+        }
+        return arr;
+    },
+    //方法五
+    //借助新数组 通过 indexOf 方法判断当前元素在数组中的索引,如果与循环的下标相等则添加到新数组中
+    // var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22]
+    noRepeatFi(arr) {
+        var newArr = [];
+        for (var i = 0; i < arr.length; i++) {
+            if (arr.indexOf(arr[i]) == i) {
+            newArr.push(arr[i]);
+            }
+        }
+        return newArr;
+    },
+    //方法六
+    //利用双重for循环
+    // var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22]
+    noRepeatS(arr){
+    for (var i = 0; i < arr.length; i++) {
+        for (var j = 0; j < arr.length; j++) {
+            if (arr[i] == arr[j] && i != j) { //将后面重复的数删掉
+                arr.splice(j, 1);
+                }
+        }
+        }
+        return arr;
+    },
+    //方法七
+    //利用includes实现数组去重
+    // var arr = [1,9,8,8,7,2,5,3,3,3,2,3,1,4,5,444,55,22];
+    noRepeatSe(arr) {
+        let newArr = [];
+        for(i=0; i<arr.length; i++){
+            if(!newArr.includes(arr[i])){
+                newArr.push(arr[i])
+            }
+        }
+        return newArr
+    },
+    //把url中的参数解析为一个对象
+    // var url = 'http://www.demo.cn/index.html?key1=val1&key2=val2'
+    parseQueryString(argu) {
+        let str = argu.split('?')[1]
+        let result = {}
+        let temp = str.split('&')
+        for(let i=0; i<temp.length; i++) {
+            let temp2 = temp[i].split('=')
+            result[temp2[0]] = temp2[1]
+        }
+        return result
+    },
+    //统计字符串中出现最多的字母
+    // let str = 'jdjsajdjasdasdakss'
+    countStr(str) {
+        let json = {}
+        //循环完毕后会得到一个对象 如{a:0,b:1,c:2}
+        for(let i=0; i<str.length; i++) {
+            if (!json[str.charAt(i)]) {
+                json[str.charAt(i)] = 1
+            } else {
+                json[str.charAt(i)]++
+            }
+        }
+    },
+    //对象深拷贝
+    deepClone(obj) {
+        if (obj instanceof Object) {
+            let isArray = Array.isArray(obj)
+            let cloneObj = isArray ? [] : {}
+            for(let key in obj) {
+                cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
+            }
+            return cloneObj
+        } else {
+            throw new Error('obj不是一个对象')
+        }
+    },
+    // 防抖
+    debounce(fn, delay) {
+    
+        var delay = delay || 200;
+        var timer;
+        return function () {
+            var th = this;
+            var args = arguments;
+            if (timer) {
+                clearTimeout(timer);
+            }
+            timer = setTimeout(function () {
+                timer = null;
+                fn.apply(th, args);
+            }, delay);
+        };
+    },
+    // 节流
+    throttle(fn, interval) {
+        var last;
+        var timer;
+        var interval = interval || 200;
+        return function () {
+            var th = this;
+            var args = arguments;
+            var now = +new Date();
+            if (last && now - last < interval) {
+                clearTimeout(timer);
+                timer = setTimeout(function () {
+                    last = now;
+                    fn.apply(th, args);
+                }, interval);
+            } else {
+                last = now;
+                fn.apply(th, args);
+            }
+        }
+    }
+}
+export default until

+ 224 - 0
src/views/homePage.vue

@@ -0,0 +1,224 @@
+<template>
+    <div class="homePage">
+        <div class="leftMenu">
+            <div class="logoSty">
+                <img src="../assets/logoG.png" alt="">
+            </div>
+            <div class="firstLevel">
+                <div class="firstLevel_icon" v-for="item in leftLevelData" :key="item.index">
+                    <div class="firstLevel_img">
+                        <img :src="item.image">
+                    </div>
+                    <div class="firstLevel_title">
+                        <span>{{item.name}}</span>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="frameMain" :style="mainHeight">
+            <div class="topMain">
+                <!-- <div class="topMain_tit">
+                    <span>全业务域对标考评系统</span>
+                </div> -->
+                <p>全业务域对标考评系统</p>
+                <div></div>
+            </div>
+            <div class="bottomMain">
+                <!-- <div class="treeList">
+                    <el-tree :data="forecastData" :props="defaultProps" @node-click="handleNodeClick" />
+                </div>
+                <div class="mainMessage">
+                    <overview v-if="showActive === '1'"></overview>
+                    <overview1 v-if="showActive === '2'"></overview1>
+                </div> -->
+                <router-view></router-view>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import MenuTree from '../components/menuTreeconfig.vue'
+import Overview from '../components/Overview.vue'
+import Overview1 from '../components/Overview1.vue'
+
+import gateway from "../assets/indexIcon/gateway.png"
+import benchmarking from "../assets/indexIcon/benchmarking.png"
+import Assessment from "../assets/indexIcon/Assessment.png"
+import configure from "../assets/indexIcon/configure.png"
+export default {
+    components: {
+        MenuTree,
+        Overview,
+        Overview1,
+    },
+    data() {
+        return {
+            showDia:false,   
+            leftLevelData: [],         
+            forecastData:[],
+            defaultProps: {
+                children: 'children',
+                label: 'label',
+            },
+            openeds:['1'],
+            showActive: '1',
+            preLeftMeun: 1,
+            windowWidth: document.documentElement.clientWidth,  //实时屏幕宽度
+            windowHeight: document.documentElement.clientHeight-60
+        }
+    },
+    created(){
+        this.leftLevelData = [
+            {
+                index: '1',
+                name: '系统门户',
+                image: gateway
+            },
+            {
+                index: '2',
+                name: '对标业务',
+                image: benchmarking
+            },
+            {
+                index: '3',
+                name: '考评业务',
+                image: Assessment
+            },
+            {
+                index: '4',
+                name: '系统配置',
+                image: configure
+            }
+        ]
+        this.forecastData = [
+            {
+                label: '考评启动',
+                children: [
+                    {
+                        label: '目标值设定'
+                    },
+                    {
+                        label: '责任书签订'
+                    },
+                ]
+            },
+            {
+                label: '考评修订',
+                children: [
+                    {
+                        label: '调整建议'
+                    },
+                    {
+                        label: '调整审议'
+                    },
+                ]
+            },
+            {
+                label: '考评监控',
+                children: [
+                    {
+                        label: '考评目标分解'
+                    },
+                    {
+                        label: '考评基层单位监控'
+                    },
+                    {
+                        label: '考评项监控'
+                    },
+                    {
+                        label: '月度完成情况监控'
+                    },
+                    {
+                        label: '季度完成情况监控'
+                    }
+                ]
+            },
+        ]
+    },
+    computed:{
+        mainHeight() {
+            return {
+                'width': '100%',
+                'height': document.documentElement.clientHeight-20 + 'px'
+            }
+        },
+    },
+    mounted() {
+        this.showDia = true
+    },
+    methods:{
+        selectMenu(index) {
+            this.showActive = index
+        }
+    }
+}
+</script>
+
+<style lang="less">
+.homePage{
+    display: flex;
+    padding: 10px 10px;
+    .leftMenu{
+        width: 70px;
+        .logoSty{
+            padding: 10px 0 0 10px;
+            img{
+                width: 35px;
+                height: 35px;
+            }
+        }
+        .firstLevel{
+            margin-top: 30px;
+            .firstLevel_icon{
+                margin-bottom: 30px;
+                .firstLevel_img{
+                    margin: 0 0 10px 17px;
+                    img{
+                        width: 20px;
+                        height: 20px;
+                    }
+                }
+                .firstLevel_title{
+                    margin-left: 5px;
+                    span{
+                        font-size: 12px;
+                        color: #fff;
+                    }
+                }
+            }
+        }
+    }
+    .frameMain{
+        background: #f3f7f8;
+        border-radius: 30px;
+        .topMain{
+            display: flex;
+            height: 60px;
+            p{
+                font-size: 20px;
+                font-family: '微软雅黑';
+                font-weight: bold;
+                color: #171e28;
+                position: relative;
+                left: 30px;
+                top: 15px;
+                letter-spacing: 2px;
+            }
+        }
+        .bottomMain{
+            display: flex;
+            .treeList{
+                width: 13%;
+                .el-tree{
+                    padding: 0 0 0 20px;
+                    background: transparent;
+                }
+            }
+            .mainMessage{
+                width: 87%;
+            }
+        }
+    }
+}
+</style>

+ 48 - 0
vue.config.js

@@ -0,0 +1,48 @@
+const { defineConfig } = require('@vue/cli-service')
+const baseUrl = require('./src/utils/baseUrl.js')
+const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
+module.exports = defineConfig({
+    transpileDependencies: true,
+    lintOnSave: false,
+    // baseUrl: BASE_URL,
+    // chainWebpack: config => {
+    //     config.resolve.alias
+    //         .set('@', resolve('src'))
+    //         .set('_c', resolve('src/components'))
+    //         .set('_conf', resolve('config'))
+    // },
+    publicPath: '/',
+    configureWebpack: {
+        externals: {
+            'fs': require('fs')
+        },
+        plugins: [
+            new MonacoWebpackPlugin()
+          ]
+    },
+    devServer: {
+        port: 8090,
+        proxy: process.env.NODE_ENV === "development" ? {
+            [baseUrl.ROOT]: {
+                target: baseUrl.URL,
+                // target: 'http://192.168.2.4:8086',
+                changeOrigin: true,
+                ws: false,
+                pathRewrite: {
+                    // '^/api': ''
+                    [`^${baseUrl.ROOT}`]: ''
+                }
+            }
+            // '/api': {
+            //     target: 'http://120.46.129.85:8086',
+            //     // target: 'http://192.168.2.4:8086',
+            //     changeOrigin: true,
+            //     ws: false,
+            //     pathRewrite: {
+            //         '^/api': ''
+            //     }
+            // }
+        } : baseUrl.ROOT
+    }
+})
+