From e58b27d9b5b6b3d63267ca89d28ebe9d3363f94b Mon Sep 17 00:00:00 2001
From: ustcyc <yincun@163.com>
Date: 星期三, 08 一月 2025 11:02:14 +0800
Subject: [PATCH] 添加前端代码

---
 zhitan-vue/src/assets/images/basecard/content_bg.png                                                                                      |    0 
 zhitan-vue/src/plugins/modal.js                                                                                                           |   82 
 zhitan-vue/src/utils/scroll-to.js                                                                                                         |   58 
 zhitan-vue/src/utils/auth.js                                                                                                              |   15 
 zhitan-vue/src/api/modelConfiguration/indexWarehouse.js                                                                                   |  176 
 zhitan-vue/src/api/basicsetting/energyindex.js                                                                                            |   69 
 zhitan-vue/src/views/register.vue                                                                                                         |  219 
 zhitan-vue/src/layout/components/IframeToggle/index.vue                                                                                   |   25 
 zhitan-vue/src/components/Screenfull/index.vue                                                                                            |   22 
 zhitan-vue/html/ie.html                                                                                                                   |   46 
 zhitan-vue/src/views/costmanage/deviation/components/InfoModal.vue                                                                        |  142 
 zhitan-vue/src/components/DictTag/index.vue                                                                                               |   82 
 zhitan-vue/src/views/modelconfiguration/collectindicator/collectIndicator.vue                                                             |  202 
 zhitan-vue/src/views/spikeconfig/externalcompany/externalcompany.vue                                                                      |   83 
 zhitan-vue/src/plugins/index.js                                                                                                           |   18 
 zhitan-vue/src/components/HeaderCard/index.vue                                                                                            |   82 
 zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/components/InfoModal.vue                                                       |   37 
 zhitan-vue/src/api/realTimeMonitor/realTimeMonitor.js                                                                                     |   54 
 zhitan-vue/src/views/error/401.vue                                                                                                        |   82 
 zhitan-vue/vite/plugins/svg-icon.js                                                                                                       |   10 
 zhitan-vue/src/assets/icons/svg/star.svg                                                                                                  |    1 
 zhitan-vue/src/directive/permission/hasPermi.js                                                                                           |   28 
 zhitan-vue/src/api/basicsetting/state.js                                                                                                  |   12 
 zhitan-vue/src/assets/images/home/index-card-4.png                                                                                        |    0 
 zhitan-vue/src/views/energyefficiency/benchmarkmanage/components/EditModal.vue                                                            |  129 
 zhitan-vue/src/assets/styles/variables.module.scss                                                                                        |   65 
 zhitan-vue/vite/plugins/compression.js                                                                                                    |   27 
 zhitan-vue/src/plugins/cache.js                                                                                                           |   77 
 zhitan-vue/src/assets/images/home/card-icon2-4.png                                                                                        |    0 
 zhitan-vue/src/views/login.vue                                                                                                            |  290 
 zhitan-vue/src/assets/styles/btn.scss                                                                                                     |   99 
 zhitan-vue/src/components/Crontab/hour.vue                                                                                                |  127 
 zhitan-vue/src/utils/ruoyi.js                                                                                                             |  246 
 zhitan-vue/src/api/auxiliaryEntry/energyIndicators.js                                                                                     |   33 
 zhitan-vue/src/views/auxiliaryentry/electricityinput/components/EditModal.vue                                                             |  141 
 zhitan-vue/src/views/carbonemission/carbonEmission.vue                                                                                    |  588 
 zhitan-vue/src/assets/logo/logo-3.png                                                                                                     |    0 
 zhitan-vue/src/assets/images/nav-btn2.png                                                                                                 |    0 
 zhitan-vue/src/assets/styles/element-ui.scss                                                                                              |  111 
 zhitan-vue/src/assets/images/energy/center/bg1.png                                                                                        |    0 
 zhitan-vue/src/assets/images/header.png                                                                                                   |    0 
 zhitan-vue/src/assets/icons/svg/dict.svg                                                                                                  |    1 
 zhitan-vue/src/views/realtimemonitor/topologymap/topologymap.vue                                                                          |  240 
 zhitan-vue/src/utils/jsencrypt.js                                                                                                         |   30 
 zhitan-vue/src/assets/images/energy/left/center/icon02.png                                                                                |    0 
 zhitan-vue/src/assets/images/period/icon1.png                                                                                             |    0 
 zhitan-vue/src/assets/icons/svg/message.svg                                                                                               |    1 
 zhitan-vue/src/assets/icons/svg/server.svg                                                                                                |    1 
 zhitan-vue/src/views/costmanage/unitpricestrategy/components/EditModal.vue                                                                |  156 
 zhitan-vue/src/views/consumptionanalysis/consumptionAnalysis.vue                                                                          |  641 
 zhitan-vue/src/store/modules/tagsView.js                                                                                                  |  182 
 zhitan-vue/src/assets/icons/svg/eye-open.svg                                                                                              |    1 
 zhitan-vue/src/assets/icons/svg/redis.svg                                                                                                 |    1 
 zhitan-vue/vite/plugins/auto-import.js                                                                                                    |   12 
 zhitan-vue/src/assets/images/login-background.jpg                                                                                         |    0 
 zhitan-vue/src/views/costmanage/unitpricecorrelation/unitpricecorrelation.vue                                                             |  177 
 zhitan-vue/src/plugins/download.js                                                                                                        |   79 
 zhitan-vue/src/views/system/dict/data.vue                                                                                                 |  350 
 zhitan-vue/src/layout/components/TagsView/ScrollPane.vue                                                                                  |  105 
 zhitan-vue/src/assets/icons/svg/code.svg                                                                                                  |    1 
 zhitan-vue/src/assets/icons/svg/user.svg                                                                                                  |    1 
 zhitan-vue/src/views/tool/gen/basicInfoForm.vue                                                                                           |   48 
 zhitan-vue/.env.staging                                                                                                                   |   11 
 zhitan-vue/src/assets/styles/index.scss                                                                                                   |  207 
 zhitan-vue/src/directive/index.js                                                                                                         |    9 
 zhitan-vue/src/api/businessConfiguration/businessConfiguration.js                                                                         |   48 
 zhitan-vue/src/assets/icons/svg/pdf.svg                                                                                                   |    1 
 zhitan-vue/src/views/system/name/name.vue                                                                                                 |  102 
 zhitan-vue/src/views/powerbalance/powerbalance.vue                                                                                        |  369 
 zhitan-vue/src/api/basicsetting/modelNode.js                                                                                              |  185 
 zhitan-vue/src/assets/images/nav-btn.png                                                                                                  |    0 
 zhitan-vue/src/assets/images/home/card-icon-1.png                                                                                         |    0 
 zhitan-vue/src/api/basicsetting/alarm.js                                                                                                  |    9 
 zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/collectionpointmanage/CollectAlarmModal.vue                          |  246 
 zhitan-vue/src/views/monitor/job/log.vue                                                                                                  |  277 
 zhitan-vue/src/views/costmanage/unitpricecorrelation/components/EditModal.vue                                                             |  105 
 zhitan-vue/src/components/HeaderSearch/index.vue                                                                                          |  187 
 zhitan-vue/src/api/businessConfiguration/gatewayLedger.js                                                                                 |   75 
 zhitan-vue/src/settings.js                                                                                                                |   47 
 zhitan-vue/src/views/system/user/authRole.vue                                                                                             |  112 
 zhitan-vue/src/assets/icons/svg/education.svg                                                                                             |    1 
 zhitan-vue/src/assets/images/energy/left/bottom/pie-bg.png                                                                                |    0 
 zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/EditModal.vue                 |  169 
 zhitan-vue/src/views/tool/build/index.vue                                                                                                 |    3 
 zhitan-vue/src/views/tool/gen/importTable.vue                                                                                             |  118 
 zhitan-vue/src/assets/images/home/index-card-3.png                                                                                        |    0 
 zhitan-vue/src/assets/icons/svg/job.svg                                                                                                   |    1 
 zhitan-vue/src/api/auxiliaryEntry/energyMeter.js                                                                                          |   33 
 zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/collectionpointmanage/CollectionPointManage.vue                      |  175 
 zhitan-vue/src/store/modules/permission.js                                                                                                |  142 
 zhitan-vue/src/views/modelconfiguration/businessmodel/businessModel.vue                                                                   |  396 
 zhitan-vue/src/components/Crontab/week.vue                                                                                                |  197 
 zhitan-vue/src/assets/icons/svg/nested.svg                                                                                                |    1 
 zhitan-vue/src/assets/images/energy/left/center/icon01.png                                                                                |    0 
 zhitan-vue/src/layout/components/InnerLink/index.vue                                                                                      |   24 
 zhitan-vue/src/views/energyconservation/projectmanage/projectmanage/components/EditModal.vue                                              |  124 
 zhitan-vue/src/api/modelConfiguration/energyVarieties.js                                                                                  |   49 
 zhitan-vue/src/assets/icons/svg/exit-fullscreen.svg                                                                                       |    1 
 zhitan-vue/src/assets/styles/transition.scss                                                                                              |   49 
 zhitan-vue/src/assets/icons/svg/slider.svg                                                                                                |    1 
 zhitan-vue/src/api/energyAnalysis/energyAnalysis.js                                                                                       |   54 
 zhitan-vue/src/assets/logo/logo-2.png                                                                                                     |    0 
 zhitan-vue/src/assets/images/home/index-card-bg2.png                                                                                      |    0 
 zhitan-vue/src/api/system/config.js                                                                                                       |   60 
 zhitan-vue/src/views/monitor/operlog/index.vue                                                                                            |  301 
 zhitan-vue/src/views/system/user/profile/resetPwd.vue                                                                                     |   57 
 zhitan-vue/src/views/monitor/cache/index.vue                                                                                              |  132 
 zhitan-vue/src/assets/images/home/card-icon2-3.png                                                                                        |    0 
 zhitan-vue/src/api/monitor/operlog.js                                                                                                     |   26 
 zhitan-vue/src/assets/icons/svg/row.svg                                                                                                   |    1 
 zhitan-vue/src/components/SvgIcon/svgicon.js                                                                                              |   10 
 zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/storage/StorageModal.vue      |  257 
 zhitan-vue/src/assets/icons/svg/logininfor.svg                                                                                            |    1 
 zhitan-vue/src/store/index.js                                                                                                             |    3 
 zhitan-vue/src/assets/images/energy/center/icon2.png                                                                                      |    0 
 zhitan-vue/src/api/basicsetting/template.js                                                                                               |   53 
 zhitan-vue/src/views/system/notice/index.vue                                                                                              |  283 
 zhitan-vue/src/assets/images/period/icon2.png                                                                                             |    0 
 zhitan-vue/src/views/modelconfiguration/businessmodel/components/modelManagement/AddModelManagement.vue                                   |  103 
 zhitan-vue/src/views/energyanalysis/indicatorassessment/indicatorassessment.vue                                                           |  355 
 zhitan-vue/src/views/system/config/index.vue                                                                                              |  305 
 zhitan-vue/src/components/ImageUpload/index.vue                                                                                           |  213 
 zhitan-vue/src/views/modelconfiguration/businessmodel/components/deviceConfig/DeviceModal.vue                                             |  176 
 zhitan-vue/src/api/menu.js                                                                                                                |    9 
 zhitan-vue/src/components/iFrame/index.vue                                                                                                |   31 
 zhitan-vue/src/views/system/user/index.vue                                                                                                |  612 
 zhitan-vue/src/layout/components/Settings/index.vue                                                                                       |  205 
 zhitan-vue/src/assets/icons/svg/switch.svg                                                                                                |    1 
 zhitan-vue/.env.development                                                                                                               |   15 
 zhitan-vue/src/assets/icons/svg/date-range.svg                                                                                            |    1 
 zhitan-vue/src/views/energyconservation/policyrule/components/EditModal copy.vue                                                          |   92 
 zhitan-vue/src/assets/images/energy/left/center/icon04.png                                                                                |    0 
 zhitan-vue/src/store/modules/dict.js                                                                                                      |   57 
 zhitan-vue/src/assets/images/energy/right/center/icon5.png                                                                                |    0 
 zhitan-vue/src/api/monitor/job.js                                                                                                         |   71 
 zhitan-vue/src/assets/images/light.svg                                                                                                    |   39 
 zhitan-vue/src/views/businessconfiguration/prealarmmanage/prealarmmanage.vue                                                              |  140 
 zhitan-vue/src/api/monitor/jobLog.js                                                                                                      |   26 
 zhitan-vue/src/assets/images/home/card-icon-2.png                                                                                         |    0 
 zhitan-vue/src/views/tool/gen/editTable.vue                                                                                               |  198 
 zhitan-vue/src/assets/images/basecard/goBack.png                                                                                          |    0 
 zhitan-vue/src/components/Crontab/min.vue                                                                                                 |  126 
 zhitan-vue/src/assets/icons/svg/validCode.svg                                                                                             |    1 
 zhitan-vue/src/assets/images/login-logo.png                                                                                               |    0 
 zhitan-vue/src/views/realtimemonitor/realtimemonitor/components/chart-modal.vue                                                           |  210 
 zhitan-vue/src/views/businessconfiguration/alarmmaintenance/components/EditModal.vue                                                      |  113 
 zhitan-vue/src/assets/icons/svg/checkbox.svg                                                                                              |    1 
 zhitan-vue/package.json                                                                                                                   |   44 
 zhitan-vue/src/api/system/notice.js                                                                                                       |   44 
 zhitan-vue/src/assets/icons/svg/post.svg                                                                                                  |    1 
 zhitan-vue/src/api/system/menu.js                                                                                                         |   60 
 zhitan-vue/src/views/tool/swagger/index.vue                                                                                               |    9 
 zhitan-vue/src/api/auxiliaryEntry/electricityInput.js                                                                                     |   42 
 zhitan-vue/src/components/RightToolbar/index.vue                                                                                          |  134 
 zhitan-vue/src/utils/permission.js                                                                                                        |   51 
 zhitan-vue/src/assets/images/energy/right/center/icon8.png                                                                                |    0 
 zhitan-vue/src/views/spikeconfig/logistics/logistics.vue                                                                                  |   71 
 zhitan-vue/src/assets/icons/svg/404.svg                                                                                                   |    1 
 zhitan-vue/src/api/monitor/logininfor.js                                                                                                  |   34 
 zhitan-vue/src/assets/icons/svg/guide.svg                                                                                                 |    1 
 zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/benchmarkinganalysis.vue                                                       |  234 
 zhitan-vue/src/components/TopNav/index.vue                                                                                                |  214 
 zhitan-vue/src/assets/icons/svg/download.svg                                                                                              |    1 
 zhitan-vue/src/utils/errorCode.js                                                                                                         |    6 
 zhitan-vue/src/assets/fonts/YouSheBiaoTiHei.ttf                                                                                           |    0 
 zhitan-vue/src/assets/icons/svg/github.svg                                                                                                |    1 
 zhitan-vue/src/views/energyconservation/policyrule/components/EditModal.vue                                                               |   92 
 zhitan-vue/src/layout/mixin/ResizeHandler.js                                                                                              |   45 
 zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/TreeNodeModal.vue                                                       |  157 
 zhitan-vue/src/views/monitor/online/index.vue                                                                                             |  106 
 zhitan-vue/src/api/businessConfiguration/preAlarmManage.js                                                                                |   69 
 zhitan-vue/src/views/system/post/index.vue                                                                                                |  277 
 zhitan-vue/src/assets/icons/svg/edit.svg                                                                                                  |    1 
 zhitan-vue/src/utils/theme.js                                                                                                             |   49 
 zhitan-vue/src/views/deepanalysis/deepAnalysis.vue                                                                                        |  363 
 zhitan-vue/src/assets/styles/page.scss                                                                                                    |  145 
 zhitan-vue/src/assets/icons/svg/lock.svg                                                                                                  |    1 
 zhitan-vue/src/views/monitor/logininfor/index.vue                                                                                         |  225 
 zhitan-vue/src/assets/icons/svg/time.svg                                                                                                  |    1 
 zhitan-vue/src/assets/logo/logo-1.png                                                                                                     |    0 
 zhitan-vue/src/api/measuringInstruments/measuringInstruments.js                                                                           |   90 
 zhitan-vue/src/components/Crontab/second.vue                                                                                              |  128 
 zhitan-vue/src/views/error/404.vue                                                                                                        |  227 
 zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/statisticalIndicatorManagement.vue       |  180 
 zhitan-vue/src/views/monitor/druid/index.vue                                                                                              |   13 
 zhitan-vue/src/views/energyefficiency/benchmarkmanage/benchmarkmanage.vue                                                                 |  123 
 zhitan-vue/src/assets/images/energy/left/center/icon03.png                                                                                |    0 
 zhitan-vue/src/api/basicsetting/indexStorage.js                                                                                           |   38 
 zhitan-vue/src/assets/icons/svg/system.svg                                                                                                |    2 
 zhitan-vue/src/main.js                                                                                                                    |   95 
 zhitan-vue/src/assets/icons/svg/example.svg                                                                                               |    1 
 zhitan-vue/src/assets/images/energy/center/icon1.png                                                                                      |    0 
 zhitan-vue/src/assets/icons/svg/input.svg                                                                                                 |    1 
 zhitan-vue/src/assets/icons/svg/question.svg                                                                                              |    1 
 zhitan-vue/src/views/auxiliaryentry/electricityinput/electricityinput.vue                                                                 |  119 
 zhitan-vue/src/assets/images/period/icon3.png                                                                                             |    0 
 zhitan-vue/src/assets/icons/svg/skill.svg                                                                                                 |    1 
 zhitan-vue/src/views/system/dict/index.vue                                                                                                |  312 
 zhitan-vue/src/views/businessconfiguration/gatewayledger/components/EditModal.vue                                                         |  104 
 zhitan-vue/src/api/basicsetting/deviceStorage.js                                                                                          |   35 
 zhitan-vue/src/assets/images/home/index-card-5.png                                                                                        |    0 
 zhitan-vue/src/layout/components/TagsView/index.vue                                                                                       |  408 
 zhitan-vue/src/views/auxiliaryentry/energymeter/energyMeter.vue                                                                           |  461 
 zhitan-vue/src/api/auxiliaryEntry/productOutput.js                                                                                        |   34 
 zhitan-vue/src/assets/images/energy/right/center/icon6.png                                                                                |    0 
 zhitan-vue/src/store/modules/app.js                                                                                                       |   46 
 zhitan-vue/src/assets/icons/svg/eye.svg                                                                                                   |    1 
 zhitan-vue/src/components/SvgIcon/index.vue                                                                                               |   53 
 zhitan-vue/src/assets/images/home/card-icon-3.png                                                                                         |    0 
 zhitan-vue/src/views/system/user/profile/userAvatar.vue                                                                                   |  171 
 zhitan-vue/src/api/system/user.js                                                                                                         |  135 
 zhitan-vue/src/assets/icons/svg/radio.svg                                                                                                 |    1 
 zhitan-vue/src/api/modelConfiguration/collectIndicator.js                                                                                 |   45 
 zhitan-vue/src/assets/icons/svg/textarea.svg                                                                                              |    1 
 zhitan-vue/src/assets/images/home/card-icon2-5.png                                                                                        |    0 
 zhitan-vue/src/api/benchmarkManage/index.js                                                                                               |   47 
 zhitan-vue/vite.config.js                                                                                                                 |   58 
 zhitan-vue/src/api/modelConfiguration/businessModel.js                                                                                    |  119 
 zhitan-vue/src/assets/images/energy/right/center/icon7.png                                                                                |    0 
 zhitan-vue/src/assets/icons/svg/password.svg                                                                                              |    1 
 zhitan-vue/src/api/login.js                                                                                                               |   68 
 zhitan-vue/src/assets/images/energy/right/center/bg.png                                                                                   |    0 
 zhitan-vue/src/views/measuringinstruments/maintain/components/EditModal.vue                                                               |  244 
 zhitan-vue/src/assets/icons/svg/drag.svg                                                                                                  |    1 
 zhitan-vue/src/components/CardHeader/CardHeader.vue                                                                                       |  121 
 zhitan-vue/src/views/system/user/profile/index.vue                                                                                        |   87 
 zhitan-vue/src/assets/icons/svg/tab.svg                                                                                                   |    1 
 zhitan-vue/src/views/modelconfiguration/businessmodel/components/deviceConfig/DeviceConfig.vue                                            |   84 
 zhitan-vue/src/views/energyanalysis/department/department.vue                                                                             |  595 
 zhitan-vue/src/assets/icons/svg/dashboard.svg                                                                                             |    1 
 zhitan-vue/src/views/costmanage/unitpricestrategy/unitpricestrategy.vue                                                                   |  147 
 zhitan-vue/src/components/Crontab/result.vue                                                                                              |  540 
 zhitan-vue/src/components/Crontab/month.vue                                                                                               |  141 
 zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/deviceConfig/DeviceModal.vue                                            |  176 
 zhitan-vue/src/assets/images/energy/left/top/icon4.png                                                                                    |    0 
 zhitan-vue/src/assets/icons/svg/button.svg                                                                                                |    1 
 zhitan-vue/src/components/Crontab/index.vue                                                                                               |  310 
 zhitan-vue/src/layout/components/Navbar.vue                                                                                               |  309 
 zhitan-vue/src/assets/icons/svg/swagger.svg                                                                                               |    1 
 zhitan-vue/src/views/alarmmanage/measuremen/measuremen.vue                                                                                |  399 
 zhitan-vue/src/api/system/name.js                                                                                                         |   18 
 zhitan-vue/src/assets/images/energy/right/center/icon3.png                                                                                |    0 
 zhitan-vue/.env.production                                                                                                                |   11 
 zhitan-vue/src/layout/components/Sidebar/SidebarItem.vue                                                                                  |  102 
 zhitan-vue/src/api/auxiliaryEntry/energyIndexInput.js                                                                                     |   33 
 zhitan-vue/src/views/modelconfiguration/businessmodel/components/modelManagement/ModelManagement.vue                                      |   80 
 zhitan-vue/src/assets/images/period/icon4.png                                                                                             |    0 
 zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/deviceConfig/DeviceConfig.vue                                           |   83 
 zhitan-vue/src/api/system/dept.js                                                                                                         |   52 
 zhitan-vue/src/api/basicsetting/model.js                                                                                                  |   53 
 zhitan-vue/src/api/monitor/cache.js                                                                                                       |   57 
 zhitan-vue/src/assets/icons/svg/fullscreen.svg                                                                                            |    1 
 zhitan-vue/src/views/modelconfiguration/businessmodel/components/statisticalIndicators/statisticalIndicators.vue                          |  106 
 zhitan-vue/src/api/system/dict/data.js                                                                                                    |   52 
 zhitan-vue/src/assets/images/energy/left/center/icon06.png                                                                                |    0 
 zhitan-vue/src/views/system/role/authUser.vue                                                                                             |  172 
 zhitan-vue/src/assets/icons/svg/table.svg                                                                                                 |    1 
 zhitan-vue/src/assets/icons/svg/color.svg                                                                                                 |    1 
 zhitan-vue/src/views/monitor/server/index.vue                                                                                             |  187 
 zhitan-vue/src/components/Editor/index.vue                                                                                                |  251 
 zhitan-vue/src/assets/images/home/card-icon-4.png                                                                                         |    0 
 zhitan-vue/src/assets/icons/svg/language.svg                                                                                              |    1 
 zhitan-vue/src/api/peakValley/timeSharing.js                                                                                              |   10 
 zhitan-vue/src/api/modelConfiguration/energyType.js                                                                                       |   49 
 zhitan-vue/src/views/measuringinstruments/distributionroom/distributionroom.vue                                                           |  215 
 zhitan-vue/src/assets/icons/svg/email.svg                                                                                                 |    1 
 zhitan-vue/src/assets/icons/svg/shopping.svg                                                                                              |    1 
 zhitan-vue/src/views/modelconfiguration/energyvarieties/energyVarieties.vue                                                               |  192 
 zhitan-vue/src/utils/dict.js                                                                                                              |   24 
 zhitan-vue/src/views/redirect/index.vue                                                                                                   |   14 
 zhitan-vue/index.html                                                                                                                     |  215 
 zhitan-vue/src/api/energy/energy.js                                                                                                       |   79 
 zhitan-vue/src/api/basicsetting/limitType.js                                                                                              |   53 
 zhitan-vue/src/api/monitor/online.js                                                                                                      |   18 
 zhitan-vue/src/api/alarmManage/alarmManage.js                                                                                             |   25 
 zhitan-vue/src/components/SizeSelect/index.vue                                                                                            |   45 
 zhitan-vue/src/views/auxiliaryentry/energyindicators/energyIndicators.vue                                                                 |  454 
 zhitan-vue/src/views/businessconfiguration/alarmmaintenance/alarmMaintenance.vue                                                          |  133 
 zhitan-vue/src/assets/icons/svg/online.svg                                                                                                |    1 
 zhitan-vue/src/assets/images/energy/center/icon3.png                                                                                      |    0 
 zhitan-vue/src/views/spikeconfig/components/EditModal.vue                                                                                 |  118 
 zhitan-vue/src/api/system/post.js                                                                                                         |   44 
 zhitan-vue/src/views/energyanalysis/equipment/equipment.vue                                                                               |  597 
 zhitan-vue/src/assets/icons/svg/phone.svg                                                                                                 |    1 
 zhitan-vue/src/views/modelconfiguration/businessmodel/components/statisticalIndicators/StatisticModal.vue                                 |  231 
 zhitan-vue/src/assets/icons/svg/clipboard.svg                                                                                             |    1 
 zhitan-vue/src/views/spikeconfig/components/UserModal.vue                                                                                 |   37 
 zhitan-vue/src/components/ParentView/index.vue                                                                                            |    3 
 zhitan-vue/src/assets/icons/svg/qq.svg                                                                                                    |    1 
 zhitan-vue/src/layout/components/Sidebar/index.vue                                                                                        |   58 
 zhitan-vue/src/views/energyanalysis/comprehensive/comprehensive.vue                                                                       |  903 +
 zhitan-vue/src/assets/icons/svg/component.svg                                                                                             |    1 
 zhitan-vue/src/views/costmanage/deviation/deviation.vue                                                                                   |  687 
 zhitan-vue/src/assets/images/energy/left/top/icon3.png                                                                                    |    0 
 zhitan-vue/src/assets/images/profile.jpg                                                                                                  |    0 
 zhitan-vue/src/views/tool/gen/genInfoForm.vue                                                                                             |  297 
 zhitan-vue/src/api/basicsetting/statetype.js                                                                                              |   53 
 zhitan-vue/src/assets/icons/svg/excel.svg                                                                                                 |    1 
 zhitan-vue/src/assets/styles/ruoyi.scss                                                                                                   | 1104 +
 zhitan-vue/src/views/measuringinstruments/maintain/components/AnnexModal.vue                                                              |   95 
 zhitan-vue/src/layout/components/AppMain.vue                                                                                              |  100 
 zhitan-vue/src/layout/components/Sidebar/Logo.vue                                                                                         |  136 
 zhitan-vue/src/api/measuringInstruments/distributionroom.js                                                                               |   51 
 zhitan-vue/src/assets/icons/svg/build.svg                                                                                                 |    1 
 zhitan-vue/src/api/system/dict/type.js                                                                                                    |   60 
 zhitan-vue/src/assets/images/energy/left/center/icon05.png                                                                                |    0 
 zhitan-vue/src/views/measuringinstruments/maintain/maintain.vue                                                                           |  165 
 zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/statisticalindicatorsmanage/StatisticalIndicatorsManage.vue          |  175 
 zhitan-vue/src/views/energyconservation/policyrule/policyRule.vue                                                                         |  123 
 zhitan-vue/src/assets/images/energy/right/center/icon4.png                                                                                |    0 
 zhitan-vue/src/assets/icons/svg/redis-list.svg                                                                                            |    2 
 zhitan-vue/src/assets/images/period/icon5.png                                                                                             |    0 
 zhitan-vue/src/components/TreeSelect/index.vue                                                                                            |  156 
 zhitan-vue/src/assets/images/home/card-icon-5.png                                                                                         |    0 
 zhitan-vue/src/api/basicsetting/historyAlarm.js                                                                                           |   35 
 zhitan-vue/src/views/modelconfiguration/businessmodel/components/TreeNodeModal.vue                                                        |  176 
 zhitan-vue/src/assets/icons/svg/list.svg                                                                                                  |    1 
 zhitan-vue/src/plugins/tab.js                                                                                                             |   69 
 zhitan-vue/src/views/modelconfiguration/setpeakvalley/setPeakValley.vue                                                                   |  367 
 zhitan-vue/src/assets/icons/svg/log.svg                                                                                                   |    1 
 zhitan-vue/src/assets/icons/svg/time-range.svg                                                                                            |    1 
 zhitan-vue/src/views/alarmmanage/energyconsumption/energyConsumption.vue                                                                  |  364 
 zhitan-vue/src/assets/icons/svg/cascader.svg                                                                                              |    1 
 zhitan-vue/src/views/modelconfiguration/energytype/energyType.vue                                                                         |  423 
 zhitan-vue/src/views/system/role/selectUser.vue                                                                                           |  140 
 zhitan-vue/src/assets/fonts/YouSheBiaoTiHei.scss                                                                                          |    7 
 zhitan-vue/src/App.vue                                                                                                                    |   25 
 zhitan-vue/src/assets/icons/svg/select.svg                                                                                                |    1 
 zhitan-vue/src/api/basicsetting/function.js                                                                                               |   53 
 zhitan-vue/src/views/modelconfiguration/calculationformula/calculationFormula.vue                                                         |   89 
 zhitan-vue/src/store/modules/settings.js                                                                                                  |   38 
 zhitan-vue/src/utils/validate.js                                                                                                          |   93 
 zhitan-vue/src/assets/images/energy/center/icon4.png                                                                                      |    0 
 zhitan-vue/src/views/businessconfiguration/gatewaystatus/gatewayStatus.vue                                                                |  532 
 zhitan-vue/src/views/peakvalley/period/period.vue                                                                                         |  776 
 zhitan-vue/src/assets/styles/mixin.scss                                                                                                   |   66 
 zhitan-vue/src/assets/images/home/line@2x.png                                                                                             |    0 
 zhitan-vue/src/assets/images/pay.png                                                                                                      |    0 
 zhitan-vue/src/assets/icons/svg/international.svg                                                                                         |    1 
 zhitan-vue/src/assets/logo/logo.png                                                                                                       |    0 
 zhitan-vue/src/api/consumptionanalysis/consumptionanalysis.js                                                                             |   10 
 zhitan-vue/src/assets/images/energy/right/center/icon9.png                                                                                |    0 
 zhitan-vue/src/store/modules/user.js                                                                                                      |   89 
 zhitan-vue/src/assets/icons/svg/icon.svg                                                                                                  |    1 
 zhitan-vue/src/assets/icons/svg/size.svg                                                                                                  |    1 
 zhitan-vue/src/assets/icons/svg/rate.svg                                                                                                  |    1 
 zhitan-vue/src/views/energyanalysis/region/region.vue                                                                                     |  495 
 zhitan-vue/src/components/BaseCard/BaseCard.vue                                                                                           |   99 
 zhitan-vue/src/directive/common/copyText.js                                                                                               |   66 
 zhitan-vue/src/views/modelconfiguration/metertopologychart/metertopologychart.vue                                                         |  240 
 zhitan-vue/src/api/home.js                                                                                                                |   33 
 zhitan-vue/src/assets/images/basecard/title_bg.png                                                                                        |    0 
 zhitan-vue/src/assets/images/energy/right/center/icon10.png                                                                               |    0 
 zhitan-vue/src/layout/components/Sidebar/Link.vue                                                                                         |   40 
 zhitan-vue/src/api/system/role.js                                                                                                         |  119 
 zhitan-vue/src/components/LeftTree/index.vue                                                                                              |  141 
 zhitan-vue/.gitignore                                                                                                                     |   23 
 zhitan-vue/src/assets/icons/svg/link.svg                                                                                                  |    1 
 zhitan-vue/src/assets/images/energy/left/top/icon2.png                                                                                    |    0 
 zhitan-vue/src/assets/images/energy/energy_bg.png                                                                                         |    0 
 zhitan-vue/src/views/index.vue                                                                                                            | 1238 +
 zhitan-vue/src/layout/mixin/getHeight.js                                                                                                  |   24 
 zhitan-vue/src/directive/permission/hasRole.js                                                                                            |   28 
 zhitan-vue/vite/plugins/index.js                                                                                                          |   15 
 zhitan-vue/src/views/modelconfiguration/indexwarehouse/indexWarehouse.vue                                                                 |  326 
 zhitan-vue/src/assets/icons/svg/documentation.svg                                                                                         |    1 
 zhitan-vue/src/permission.js                                                                                                              |   65 
 zhitan-vue/src/views/businessconfiguration/gatewayledger/gatewayLedger.vue                                                                |  131 
 zhitan-vue/src/views/modelconfiguration/businessmodel/components/collectIndicators/CollectModal.vue                                       |  156 
 zhitan-vue/src/components/Breadcrumb/index.vue                                                                                            |   66 
 zhitan-vue/src/views/system/role/index.vue                                                                                                |  559 
 zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/storage/CalculateFunction.vue |   83 
 zhitan-vue/src/api/tool/gen.js                                                                                                            |   85 
 zhitan-vue/src/assets/images/home/index-card-2.png                                                                                        |    0 
 zhitan-vue/src/views/tool/gen/index.vue                                                                                                   |  299 
 zhitan-vue/src/views/businessconfiguration/datacollection/dataCollection.vue                                                              |  145 
 zhitan-vue/src/assets/icons/svg/zip.svg                                                                                                   |    1 
 zhitan-vue/src/views/realtimemonitor/realtimemonitor/realtimemonitor.vue                                                                  |  435 
 zhitan-vue/src/utils/index.js                                                                                                             |  390 
 zhitan-vue/src/layout/index.vue                                                                                                           |  111 
 zhitan-vue/src/views/measuringinstruments/maintain/components/TargetModal.vue                                                             |   83 
 zhitan-vue/src/api/modelConfiguration/setPeakValley.js                                                                                    |   56 
 zhitan-vue/src/assets/404_images/404_cloud.png                                                                                            |    0 
 zhitan-vue/src/assets/icons/svg/upload.svg                                                                                                |    1 
 zhitan-vue/src/assets/icons/svg/date.svg                                                                                                  |    1 
 zhitan-vue/src/components/IconSelect/requireIcons.js                                                                                      |    8 
 zhitan-vue/src/components/FileUpload/index.vue                                                                                            |  207 
 zhitan-vue/src/assets/icons/svg/bug.svg                                                                                                   |    1 
 zhitan-vue/src/views/system/dept/index.vue                                                                                                |  274 
 zhitan-vue/src/views/auxiliaryentry/productoutput/productOutput.vue                                                                       |  503 
 zhitan-vue/src/assets/images/home/card-icon2-2.png                                                                                        |    0 
 zhitan-vue/src/assets/icons/svg/druid.svg                                                                                                 |    1 
 zhitan-vue/src/assets/icons/svg/monitor.svg                                                                                               |    2 
 zhitan-vue/src/assets/404_images/404.png                                                                                                  |    0 
 zhitan-vue/src/api/costManage/costManage.js                                                                                               |  122 
 zhitan-vue/src/assets/icons/svg/chart.svg                                                                                                 |    1 
 zhitan-vue/src/assets/images/energy/right/center/icon1.png                                                                                |    0 
 zhitan-vue/src/layout/components/index.js                                                                                                 |    4 
 zhitan-vue/public/favicon.ico                                                                                                             |    0 
 zhitan-vue/src/assets/styles/sidebar.scss                                                                                                 |  568 
 zhitan-vue/src/assets/icons/svg/tool.svg                                                                                                  |    1 
 zhitan-vue/src/components/IconSelect/index.vue                                                                                            |  111 
 zhitan-vue/src/assets/icons/svg/wechat.svg                                                                                                |    1 
 zhitan-vue/src/components/Crontab/day.vue                                                                                                 |  174 
 zhitan-vue/src/assets/401_images/401.gif                                                                                                  |    0 
 zhitan-vue/src/views/tool/gen/createTable.vue                                                                                             |   46 
 zhitan-vue/src/assets/icons/svg/tree-table.svg                                                                                            |    1 
 zhitan-vue/src/assets/images/energy/right/center/icon2.png                                                                                |    0 
 zhitan-vue/src/views/monitor/cache/list.vue                                                                                               |  246 
 zhitan-vue/src/utils/dynamicTitle.js                                                                                                      |   15 
 zhitan-vue/src/components/Hamburger/index.vue                                                                                             |   51 
 zhitan-vue/src/assets/images/dark.svg                                                                                                     |   39 
 zhitan-vue/src/api/monitor/server.js                                                                                                      |    9 
 zhitan-vue/vite/plugins/setup-extend.js                                                                                                   |    5 
 zhitan-vue/src/assets/images/energy/left/top/icon1.png                                                                                    |    0 
 zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/benchmarkinganalysisTable.vue                                                  |  228 
 zhitan-vue/src/assets/icons/svg/theme.svg                                                                                                 |    1 
 zhitan-vue/src/views/peakvalley/timeSharing/timeSharing.vue                                                                               |  609 
 zhitan-vue/src/views/system/menu/index.vue                                                                                                |  427 
 zhitan-vue/src/views/energyconservation/projectmanage/projectmanage/projectManage.vue                                                     |  135 
 zhitan-vue/src/views/alarmmanage/alarmrecord/alarmRecord.vue                                                                              |  193 
 zhitan-vue/src/views/modelconfiguration/businessmodel/components/collectIndicators/CollectIndicators.vue                                  |  105 
 zhitan-vue/src/assets/icons/svg/tree.svg                                                                                                  |    1 
 zhitan-vue/src/router/index.js                                                                                                            |  189 
 zhitan-vue/src/components/ImagePreview/index.vue                                                                                          |   92 
 zhitan-vue/src/assets/images/login-bg.jpg                                                                                                 |    0 
 zhitan-vue/src/assets/icons/svg/people.svg                                                                                                |    1 
 zhitan-vue/src/components/Crontab/year.vue                                                                                                |  149 
 zhitan-vue/src/plugins/auth.js                                                                                                            |   60 
 zhitan-vue/src/views/auxiliaryentry/energyindexinput/energyIndexInput.vue                                                                 |  348 
 zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/statisticalindicatorsmanage/StatisticsAlarmModal.vue                 |  222 
 zhitan-vue/src/views/businessconfiguration/statusmonitor/statusMonitor.vue                                                                |  124 
 zhitan-vue/src/assets/icons/svg/search.svg                                                                                                |    1 
 zhitan-vue/src/assets/images/home/index-card-1.png                                                                                        |    0 
 zhitan-vue/src/views/energy/energy.vue                                                                                                    | 1555 +
 zhitan-vue/src/api/carbonemission/carbonemission.js                                                                                       |   18 
 zhitan-vue/src/views/monitor/job/index.vue                                                                                                |  483 
 zhitan-vue/src/assets/icons/svg/number.svg                                                                                                |    1 
 zhitan-vue/src/assets/images/home/card-icon2-1.png                                                                                        |    0 
 zhitan-vue/src/utils/request.js                                                                                                           |  152 
 zhitan-vue/src/api/peakValley/period.js                                                                                                   |   10 
 zhitan-vue/src/assets/icons/svg/peoples.svg                                                                                               |    1 
 zhitan-vue/src/assets/icons/svg/money.svg                                                                                                 |    1 
 zhitan-vue/src/assets/images/energy/center/bg2.png                                                                                        |    0 
 zhitan-vue/src/components/Pagination/index.vue                                                                                            |  110 
 zhitan-vue/src/views/system/user/profile/userInfo.vue                                                                                     |   67 
 zhitan-vue/src/views/spikeconfig/configuration/configuration.vue                                                                          |   71 
 zhitan-vue/src/components/BaseCard/index.vue                                                                                              |   67 
 zhitan-vue/src/assets/icons/svg/form.svg                                                                                                  |    1 
 449 files changed, 46,793 insertions(+), 0 deletions(-)

diff --git a/zhitan-vue/.env.development b/zhitan-vue/.env.development
new file mode 100644
index 0000000..74bd43c
--- /dev/null
+++ b/zhitan-vue/.env.development
@@ -0,0 +1,15 @@
+# 椤甸潰鏍囬
+VITE_APP_TITLE = 鑳芥簮绠$悊绯荤粺
+
+# 寮�鍙戠幆澧冮厤缃�
+VITE_APP_ENV = 'development'
+
+# 绯荤粺/寮�鍙戠幆澧�
+# wangxiang
+# VITE_APP_BASE_API = 'http://127.0.0.1:8080'
+# hangmingjun
+# VITE_APP_BASE_API = 'http://127.0.0.1:8080'
+# weishuaishuai
+# VITE_APP_BASE_API = 'http://127.0.0.1:8080'
+# test
+VITE_APP_BASE_API = 'http://127.0.0.1:8080'
diff --git a/zhitan-vue/.env.production b/zhitan-vue/.env.production
new file mode 100644
index 0000000..bac2aa3
--- /dev/null
+++ b/zhitan-vue/.env.production
@@ -0,0 +1,11 @@
+# 椤甸潰鏍囬
+VITE_APP_TITLE = 鑳芥簮绠$悊绯荤粺
+
+# 鐢熶骇鐜閰嶇疆
+VITE_APP_ENV = 'production'
+
+# 绯荤粺/鐢熶骇鐜
+VITE_APP_BASE_API = '/prod-api'
+
+# 鏄惁鍦ㄦ墦鍖呮椂寮�鍚帇缂╋紝鏀寔 gzip 鍜� brotli
+VITE_BUILD_COMPRESS = gzip
diff --git a/zhitan-vue/.env.staging b/zhitan-vue/.env.staging
new file mode 100644
index 0000000..90598c0
--- /dev/null
+++ b/zhitan-vue/.env.staging
@@ -0,0 +1,11 @@
+# 椤甸潰鏍囬
+VITE_APP_TITLE = 鍚庡彴绠$悊绯荤粺
+
+# 鐢熶骇鐜閰嶇疆
+VITE_APP_ENV = 'staging'
+
+# 绯荤粺/鐢熶骇鐜
+VITE_APP_BASE_API = '/stage-api'
+
+# 鏄惁鍦ㄦ墦鍖呮椂寮�鍚帇缂╋紝鏀寔 gzip 鍜� brotli
+VITE_BUILD_COMPRESS = gzip
\ No newline at end of file
diff --git a/zhitan-vue/.gitignore b/zhitan-vue/.gitignore
new file mode 100644
index 0000000..78a752d
--- /dev/null
+++ b/zhitan-vue/.gitignore
@@ -0,0 +1,23 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+**/*.log
+
+tests/**/coverage/
+tests/e2e/reports
+selenium-debug.log
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.local
+
+package-lock.json
+yarn.lock
diff --git a/zhitan-vue/html/ie.html b/zhitan-vue/html/ie.html
new file mode 100644
index 0000000..052ffcd
--- /dev/null
+++ b/zhitan-vue/html/ie.html
@@ -0,0 +1,46 @@
+
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8" />
+    <title>璇峰崌绾ф偍鐨勬祻瑙堝櫒</title>
+    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" >
+    <meta name="renderer" content="webkit">
+    <base target="_blank" />
+    <style type="text/css">
+        html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0}
+        a{text-decoration:none;color:#0072c6;}a:hover{text-decoration:none;color:#004d8c;}
+        body{width:960px;margin:0 auto;padding:10px;font-size:14px;line-height:24px;color:#454545;font-family:'Microsoft YaHei UI','Microsoft YaHei',DengXian,SimSun,'Segoe UI',Tahoma,Helvetica,sans-serif;overflow-y:scroll}
+        h1{font-size:40px;line-height:80px;font-weight:100;margin-bottom:10px;}
+        h2{font-size:20px;line-height:25px;font-weight:100;margin:10px 0;}
+        em{color:red}
+        p{margin-bottom:10px;}
+        hr{margin:20px 0;border:0;border-top:1px solid #dadada}
+        span{display:block;font-size:12px;line-height:12px;}
+        .clean{clear:both;}
+        .browser{padding:10px 10px;}
+        .browser li{width:auto;padding:0 80px;margin-top:30px;height:34px;line-height:22px;float:left;list-style:none;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAADMCAYAAAAWCXEwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAC7ESURBVHja5Lx5dFRV1rBfgHwYRQQVtB26ZWhtabtfeUGxGxFbUGZF8RMHGkVbRkekVYiKisicVhE0gEwBokgDAhEMMSSQkAECwcxkrlRSqVTqJqnxzs/vj5t7qUyAvr9e37fWV2vtleSm6p6n9t5nn733OVU2RaUaEP5PiqJSbeMXPBTA5/Xhzk9Vnd9vo3HFx21E2LYJX9IRgh6npvyCe9uaqS4K4C3IpXHFx9S99CTuJ8Z0KLVjRlA7ZgTuJ8ZgXxmJL+kIlwAkXBQk6HFq9pWRVA8fSvXwodYgdS892a6EA1UNvouqwXdR99KTeAtyfz2IL+kI1cOHYh9wqwVwKWJqpXbMCOv19gG3Imzb1JF2OgZxfr/NukH4jcNVfyEAE8IU+4BbKet1PfaVke3BtA/i/H6b8aIBt7a4mWmaC0nr55vmqRp8F5V33Mm5LhHtwbQF8SUdsSDCb1I1+K42g1xIWgOYYh9wK+e6RCBs29QxSIWus37aJM51iWjx4so77mwD1d5AHQ1eecedlN9yuyVlva6nrNf14Q7cEmRn4W7u3T2E9ME3UX7L7W1uZg5Weced1s3sA2613ql5LXzQjuRclwjcT4wxTXQeRHC7GLdnHPeensiCVwa3e0PznZk3EbZtwluQa0kofz8NcVNxr++Ce30XnNuv61Bcu7viXt8Fvyu7JYipjfGHxzD+8Bh2j+7fAiZcC+Y0zPDIbCyD6DyV6DyVeDcIQR2C39J4oieNJ3oSOnkVcnZ35Ozu6MVdDHF0N6S4C43OqJYg/0ydzb27hzDx0FjuPT2R+asfa6OVsl7X40s6QoWus/CQk6fWZPHChhxe3lbMCxtyrN9TyxSQSwidvMoC0XK6tRGybPjSRmOuNUKVo4Zxe8YxIu4+Jh4ay/jDY7j39MQWWjnXJYLGFR9Toes8tSaLiavTrIHDxfxfapkCwW8hy9YuhCmhk1fR1FRnaCS1NM4yy8RDYy2tjIkZRXq/HtYsCnqc2sJDTkYsTrU00J6YkEJQR7M/eEGY0MmrcOenqjZA2JmyzTJLuJiOe65LBHUvPUmGR2bE4lQmrk7jqTVZHcrE1WkMWpRIdJ4KnpUXBCHLRl3e16EWIOEaMU00/vAY9na/gsYVH/NdgYe+8w9bMBeSQYsSWXjICcFvL2ga+dhlFwcJ10rjio/ZklprgbSWiavTWvzdd/5hXt5W/OtATC201sq9u4eQ+PVijmSW0nf+YQYtSmTQosR2gUYsTmXQokT6zj9saeRCpmkJ0hxD2gOZeGgsI+Lu45+ps7FXlFmDmDDtSd/5h+k7/zCpZQpa9cwOQciyIR+77LyzFhXlMyZmFOP2jLP8orVWRsTdR2ppHFtSa+k6ZZM1WHvSdcomwyxySceayO4OWTY88TdirygzUkWf18eL2//RQiutYcwYE/Q4tagDOUQ8uo6uUzbRZ3qMJV2nbCLi0XU8tSbrolNXzu6OfOyylgEN4NOkaO5acw/j9ozr0ET37h5imehIZimPL91rAfSZHsOQBfuISS7E7vaTETeX0MmrOoQInbwK+dhlNKWsahni0zPSuGvNPW1M1BrI1NrOwt0WkCn2ijJSS+MYt2ccuQk3oxd36RCi8URPY+HLT1VbgGiSzPsx71laCddMe2Yygf6ZOtuScXvG0XfJn/n8YL+LQnjibyQ34WZ8Xl/bfKSoKL+FVi4EYwKZcu/uIQzaPoExMaPQcrq1ADFX33AI1+6u1OV9HVI6ShU/TYqm75I/dwjTHtDEQ2MZt2ccg7ZPaGGScIDWEBlxc42UoSMQ00StYdoDCgcbtH0Cbx+8p40ZTIBwiFM7RmB3+y+exZvT2YRpDdR6ZoVrw1xRWwN44m/Euf06A6Ki7NLrmnDNmH7TEdSg7RP4/GA/yLK1GdwEKNzSk1M7RlDlqPl1JefOlG2MXTGmXaAxMaMsB/XE34h4tH+7ANlrB7T2iV8OAlDlqOH9mPcsIBPKlF3R16Ad7GwlxoVberYAKCrKv1ghfmkg5sPldLIzZVsLqLErxpC9doAlp3aMICNurlGyVpRdSAu/HqS1Q58rd1JUlI87P1UtKsrHXlGG3e1HCOoov+x2wiX3RxT+o49L1IgutXxVUCfDIxNfLraQDI+M3e3/NdCXbhohqBNfLrIsVzZqmoT6dmXG0SBLTrmJLxd/CVRLECXcDGFaSC1TmHE0yKg4B0P2uxiy38WoOAePHaptAfHYoVqG7HcxcGc5o+IcfFfgsbQUPoYoSa213BbE78oGucTSwpJTbobFFjNgbQHdvi6g8/Z6Om+vZ8h+VxsQE7T/97UMWFvA+Og0UvIryfDIZBQ4CeXvt8a5IAhAY/RImlJWUaHrPHaolhuXFXHN+8e58qNcbomq5P6t3xG973WePLzPgnnsUG0LiP7f1zJwZzk3LisyctfSOFxOJ4lfLzYToQubxu/KpmpWBFWzInguOokrP8ql7/zDRMxLpFfUabasHwlZNnITbmbgznI6b6+3Bu7/fa2lrW5fF9Ar6jQD1hYwLLaYx5fupdi+EiGok748koa4qa010xKkKWUV2UM7kd6vB7tH9yfpnUFkLzQiZOGWnmgHO9N4oie9ok5bA4YPbkqvqNNc8/5xIuYl8tSaLOLLRXambENXF+PxNJD0ziAanVHhYaEliH1lJD/1iqD0qSsIzu2M/N550TZ3QjvYmS3rR1qDtwdhgpgwnabGMj46zRjQsxJdXYw7P1X1pY0GuaRjkMKxPah5qxuV8y6nct7l1LzVDfdyo6miHexM+ou9mblwKfdv/Y77t37HNe8fbwMQDhIxL5FOU2PZklqLJjUYdU7wWxBuN+ricBAF0KQG6pcNovZpw0fCQao/MEBcu7tSOLYHjnu7EZzbmeDczqyfNokrP8ptMXi4XDnzAJ0n72TIgn1oUoMB4VlpgIjj24I0payi9KkrqHj+Ssth2wM5c38f8p68D2nbHKRtc3h86d42A/eZHsOVMw9Y0nXKJmxDvyS1NA70z8Gz0qh5hNvbzpr6ZYMofzyiBUwLkOVdjfR/eVcao0dSl/d1aHx0GhHzEi0TXDnzAJ2mxtJpaixdp2yypM/0GLrcs5D3Y94ztNDsK7qjuxmzDBBz2rYGqZoVQc1b3dr4yfppk+g8eWeLd91aAxGPrqPbyKV0G7mUiEfXMWdz+nmQ0Jsgn1AbT/SkMXrkeZC6vK9DpU9d0S5I5bzLqf6gq6UV7WBn5q9+zDJBuEQ8us4SE6LLPQvpcs9CjmSW4ndlo1XPNBxWLiE34WbSX+wNapEBEsrfT/njERSO7WGBmDA1b3Wj9KkrSO/Xg1WjBjJl/CT+8sQ8a0BT/eGDhwN0uWchXe94ia07YkE+oSLc3gxyQt2yfiSrRg0E+YRqgRSO7UHh2B4UT7ragqmcdznFk67mp14ROO7txpTxk7AN/bLFgN1GLsU29EvrejiACdG59xQjKgu3GzVP9UwIvcmCVwYb102NmBHVBDFNVDUrgjP39yF98E0E5xox5Dcj5lsDhwOYQObg4dK59xR2RV8D4njEo/0NIEd3dkVfgy9t9HkfMTWSO6pXG63kjupF8aSrqXj+SoJzO1M573KmjJ/Eb0bM5y9PzGPBK4Mp3GKUEFvWj+Q3I+a3AOjcewp/eWKesUQ0T1mz2att7oSU9+F5EE2SqXvpSbKHdrIGNmHCoapmRVgh33LezZ3QNncyloGDnVnwyuA2IFvWj0Q+dplREzu6Wy0r9/KubVvg9pWRpPfrwZn7+1haMSHCxdSM/J4RWWufjiC9Xw/m9PgtN9w0uo1JbrhpNI0njAXTrAIbT/TEvb4LjdEj2641vqQjpPfrQfrgm1qYKHxKlz51BbmjerFj4G2WtAYwtWDKglcGG2ZoXrldu43AWDUrAmnbnLaRVZMayHvyPn7qZThoa38pfeoKap+OIDi3M6tGDeSGm0a3GTT82g03jeaGm0bj3H4d8rHLrN0I93LDpDsG3kb68si2a425hfZTrwjSB9/UBiZcM+YM6ghoyvhJpL/Ym+yFhknc67tYQVF+z3gjc3r8Fuf32zpOFTMeHXpRGDNfMYF2j+7PqlEDWTVqIOkv9rZ8SNvcCff6LlTOu9yK1Okv9mZOj9+S8ehQNKmBDhs17vxU9adeES1gwoHKH49oFyhcwhfKynmXWzOu4vkryR7aieyhnQjl7+84QzNNJGzbxN7uV1gw7WmntYZaLw2mmNdrn44ge2gnztzfx9od7zBnDa9t0pdHtgsTDhRustaaCndwEyLj0aG481PVS9r3FSUJj6eBrConMZHvnodpntrh2gkHCgcLl/TBN7G3+xXGLMlIo0LXjU7ixeoaUZIQ3C7OlTtJya8kJvJddgy8DctvWgGFaylcHPd2Y2/3K5jT47esGjWQrTtiyapy4nI6jUrvUmpfUytFRfmkZ6SxdUcs66dNYsfA2ywNtQBrJeb/dgy8jZjId/kx4YgF4fP6Ln1L3uyhhWvnSGYpOw6lEBP5LuunTWLDAw+x4YGHrAi74YGHWD9tEuunTSIm8l227ohtAyBK0i8/pNDagTVJxuf1YXf7OVfuJKvKMF16RhrpGWkcySwlJb+SrCqn1awRgjqaJP9nO0b/Zxo1v+ahS0ZqKJ9QCX5rJMyhN42aRj6h/udB5BKjiAp+i64uNrJ2M0Vs3rUiy4aU92G42X49iCYZDZjUMoX4ctFIcILfGgVU6E0LwEyCxKP98aWNxpc2GvFof+RjlyHlfdjxWnOxh93tJya5kIWHnDx2qJbnopP4NCmaYvtKC0LL6WYkQps70RA3laaUVbjzU1V7RRn2ijK8BbkWUJsM7VIAog7k8MyuPKtD1AJA/9zQQpYN9/oubFk/kpkLl7J4a0KbtrdZa/vSRrfMWS8GcSSzlGd25TH5VIjptTpR9T5SS+OMsrHZD3RHd7SDnTm1YwSzY2KsTtL46DSei07iSGZpm/tKeR8a5gnf0+vI8zfE5zAstpjptTrvifBJeeZ5LTQDkGXDtbsr0fte59mjDmaWaUyv1ZlZpvH3XJlRcQ6Grj5OTHJhy/t7VhrpwMVAog7kMCrOwcs+nZWaccak2L7S0oLpC6d2jGDJiUyWN8E6FVZqsLwJ5ruwYO5O9jFoUSIb4nPOT+/gtxf3kZjkQobFFreAaHRGGZoQbm+hhWd25fHsUQevHilgbo7bAmoNM2S/i6Grj3Mks9Tolcgn1Hb39MzHuXInw9edZrJd4z3xPISuLrYgCrf0ZOuOWKLzVFLLFDIKmlfr5EJmHMxhfoWvDczkUyELxl5RduFUUZNkIvdm8+BpkZd9eocQPyYc6XDnocpRQ+TebObmuFmptdTK5FMhBqwt4K1vMi4cWTMKnIyKczDZrvFJeWaHEBdrbVc5aphxMIflTR1rJaPA2TFI1IEc7k72tZwdYRCLtyZc6h4MMcmF7WrlwRSRAWsLiNyb3T6Iz+vjmV15jIpztIHwxN/I7JgY4svFS47CHk9DG62Y5hm4s5zx0Wntb0CnlikMiy3m06ToFpFSO9iZnSnbeGZXHkcyS8kocF6SHMksZc7m9AuaJyW/si3IltRaZsfEGNM09KZVs2bEzWV5EyzLlXn1SEG7MuNgTruy5JS73dlzd7IvPMi1BIlJLmRnyjbLJFawar7ZHi5NdrSS9jRyd7KPXlGnzQDXyjSlcYY2mk1SuKUnS05kslI7f9M9/HKgdaoh74nn/cR02NV7M9t2A9A/t/qf2uZOvB/zHvNdxk3Mm0bV+36VzK8wxHTWVutPmEbkE6q1hjQ3/yefCvGeeB7k1SPGlLsUeeubDOtnezJnczpvfZPBuXJnGEjzAqSri9FyulG4pSf3b/3OCvErNQNmxsEczpU70ST5kuWXJc9yiZXemQ3du5N9TK/VedmnW1qZm+M+v3r+gpTS42nA42nA5XRa4vE0hFd8zSDBb63cInvtAAYtSuTuZB+T7ZoFYy7tz+zK6+igQZtHRoGTyL3ZLab4M7vyGB+dxpAF+1i8NaEliLmWyNndsa+MZPi60/T/vpaJhTKT7ZqllZWaoZW3vsnA42m4IMS5cifPRScxN8fNeyK87NOZXqszsdDITa55/3i4dgVb0OPUTG2IR/vjzk9Vt6Qau5R3J/uYWCi3MJEJM2dzOkcyS80Q3WKrPia50IIIX2cmnwrxYIpIr6jTPBed1Mo0apFgpv0NcVMR3C5ESWLO5nS6fV3Ag6fFdmHmV/iYcTCHyL3ZRB3IsSRybzbP7MpjfoWvXYj+39cyZME+c7aEgTQ36smy0RA31dostrv9DF193IIJ9xcTxgSam+O2xAQwg9fMMo2JhTIPnjYgBi1KbC+RPq8REyR8iT9X7rRgWptpvssYLBwqHGB6rc7fc2ULYsh+F4MWJbLjUErH09c8ytcaxNTMCxtyGLC2oIUDT6/VO5TJdkMLJsTAneUMWpTYNotvE0eaj3rKxy6zun2t69mdKdt4fOley4lN35ls11pIOIC51D8XnWQu9xcGUQCteibyscuM5n31TKNqD5fm1H9DfA7PRScxdPVxhsUWMyy22Dq4MGS/i2GxxQxfd9oC2HEopb1WVcdtCU2Sqcv7OmTWpGbRLOV9SCh/P0GPUwvPvDIKnMQkFxK5N5s5m9N5LjqJ56KTeOubDFbvzSQlv7LN1P5FxzZ8Xp918v8SWk5WsWStLbr0a5oLHRdY/+GjPP8vtq7+0yCiJOHz+hDcLlxOJ2bzxeV0Irhdlk/9x0B8Xh9VjhoEt6s5rZTaFU1qQHC7qHLU/PpZ05EGqhw1uJxO0CVESSIlv5KoAznM2ZxufTJgzuZ0og7kkJJfaR1mcjmdVDlqflkc6ahSs1eUWdMzJrmQQYsSrYMJNy4raiHmYQWzD2IC2SvKLpa/dAzi8/qsc6cZBU6GLNjHlTMPcEtUJVMSdd45qRGdp7KxDOvDPu+c1JhxNMgtUZVcOfMAQxbss0K7vaLsQqbq+GCtCbEhPodOU2O58qNcZhwNsrMK4t0Xlp1VMONokCs/yqXT1FgrE7sATPvbJK0hblxWxDsnNWugvc7zcqFry3JlbomqbANzSdskpk9kFDjpOmWTpQnzne6sMgbbWWWYY8kpN0tOuYnOU1v8z9TcOyc1blxWRNcpmwwz6dLFjxr7vD5rY+eO13YSMS+Rh/co1iAby4wBluXKLDnl5rsCD1lVxk7FdwUelpxysyxXbvHcjWUwYb9CxLxE7nhtp7X10spELUHMMiHqQA6dJ+9k8KYaJh1u6ZRLTrnZklrb+hS3lURtSa1lySm39fyNZTAlUWfwpho6T95p1rqtS5LzICapJsmWNkbEBpiSqLMs1/gY3DsntfAuT4tDlkrYtci92bxzUmNjmaG9KYk6I2IDbbTStsBqjhma1EBKfiVdp2xiwNoCHt6jMOmwxjsnNev46KWUkaIksfCQk2W5Mu+c1Jh0WGPCfoUBawvoOmWT1d4Miy3nQczIuXpvJp2mxjJ4Uw0T9hsg09KM6fhcdBIxyYWXJM9FJzHjaJBpaTDpsAEzeFMNnabGGhVec+RtA1LlqAFd4vGley0Q8wZTEnWmpWGdWX3sUC3PHnW0K+b/n0qoZ1oaTEszfCQc5PGle0GXwv0k7PxI87S9EMjMMo35rvMdILPDbErrzlA4iOmw4SBh0/iXgUxLg8mnQvw9V2Zmmdau/D1XtpoxpiYe3qPw8B6FW6IqreOCvwpkWhqMinMwaFEi46PTfrFMXG38HLr6OHe8ttPykXZNYzrr4q0JdJoay4C1BS2cdfCmGuZsTrd6Hv/T5ozZJ7no9L1xWZE1fU0bD193unXx3GESFZNcyIb4nDazaUN8Dh6PkTy1O307CmgT9itM2K9YWnkuOumi26wTV6dZR43NXOXKj3LpPHknEY+us0DaDWiWnwCr92bSdcomBm+q4eE9ShsThTXh2jRn5mxOZ/CmmjYzZkRsgE5TY40Q33bhu/iiF66VcJjh604TuTfbUnnk3myGrzttQZgzZtJhzQrvfabHWGNccNELnz2tfSUcJjxADVhbwIC1BdYsMyOp+fyH9yhWGnAks/TS0gDTV4qK8q2NxU5TY7klqrIFTDhQ6+gZ/hwzdoSbpKgo/9LPj5hnR8yUwEwVw810MRkRG7BSRXPpLyrKv/RUsT2YI5mlLZLnEbEBK1q2lhGxASt5vuO1nZY5ioryL5TJX7icENwuioryjV1rr4+oAzkMWbDvouXEkAX7iDqQg8/rQ5MaLgZxaQWWJslWSWkWWBkFzl9UYP2PvgjFPNrj8/osM/2YcIQfE46QnpFmfL7K7SLocWpBj1Mz6+D0jLQWzzPb3b/6aI8SVnCbvXTTVOZxno6kqCjfKlPNUH4pIP9XPGz/N319UFnrf2iKLGi6LmggqCBoIOi6JuiqIqCrgqIrgqyrgoYu6JpiiK4LKgigCpquCCEdQdVVAU0VdP2iMGW29tplmtbcQNQ1QEXXNDQdQGsWHZBbvdQsKkTQfaiaBJrc/PyLPpQ2zqqbL9U10GV0TUbTZUCyQAoaJPaVinx5RmbVKZnVWRpf56r8WKlQFww2Q4bf8VdMXwsEtfkdGb97xSAb8yRG7df4zYYQ3deEsK2WsK1UsK1U6LIqxJWfKQzcEODVw0GS7KbG1F8Pout6C7WuL5Dpv1PBtlLEFgWXfyHTY61Ery91rvkiwLWfB7h6jcxV/5LoskLF9gl0+tjLI7FesuuxzKnrHeqneQdL143Bjacj6wqg4ZFUph8JYvusCdsXIldvhGvXi/T+SuS6dQrXrZO4fp3Ib76UuH5NiD6fi1z/mcgNnwa5epWMbbHG1StEvsoSjbeoq2i60h6MYNN1XTAhNF1vdlBoVFSG7/Nh+1Ti2o1Brl8v03uDyDVfN3DDVz5u+FKh15cKvdbp9FoHvT5X6PW5wjVr4LrPda6NkugTJdL1EwXbIpkVx5sdGaXZ8S9gGgNIJ6ipPHgghO3TED23h+ixTafXZpmb1ofos0ml+9dw1VcaV3wapMvKIF1WSVz+qULPzxV6faZw9Wc613yq0Xt1iN9Ehei+WMG2QObz03JHDtxsGk07P2XRmZ/hx7ZG5rqtMjdubqTHFonrNov8doPMZRvA9pmPqz8X+MNWhb/tkrg/VuGWaJXLPmmk85Imen6m0+sz6BMlcsNqP9etVujysU63jwIcrwy1N6UFm6Zrgma4KKBxrE7lyq999PnaT58dcMNWjV5bFa7d6sP2lcj/+szP6/FNHK2SqQtpSKqIKItUN2psyJH52yYXtkV+uq9UuP5fMj1XqVy9WuWGFSE6LQgxbHMQv6kVXW92B12wKZouSEjGNNMVJvwgYdugcGOsym+2q/TZqnD9dh3bVz5u3h4guVJtnpJa808zkJlBMMS7SQG6vB/gimUKvVdK9Fmu0nu5zLXLZGzvaWzLDhggmoysqwYIKoKqG+rKqVO5douP62JUfvutxg2xCn1iZTpv0rgpRuF0XQAIgRJElSUURUWWZWRZRpFlgrIKeIEg7yaC7X2FXkslei+XDVkmY1sQ4pFNDaA3hwcdNF0XbGjNZwNQWXZaxrZV5XexMjftFLnpW4ne34rYNvjZUywBQUJqEEkMoEk6oqIgySqipCCKEt6Qis8fRNEaAB+TtijYInV6Lwtx7VKRPstkIj5S6PGBRGFtwFCgApquCDYFTQANXZeZkiARsVPnlu9kfhcr0/cbiYivA4w94DM0oet4VQVJUQiJGiFRIiTKBEMSAX+QhoBIvU/C1SQCfpIKGrl8kZerFitcu0Tkuk9ErlsiYXtDYuMpYyobE0gVbIouC6DiDsgMiwtx406Z/rs0+u6WGPCNSI8tIZbnSoCCEvITkBRkWSMUkAgEJbz+EE2+IA3eAPUNjTR6fNTWSni9PuoFN/d8KtBpkcg1n3jp82GQ3h/6sc33seAHb/P6pYOmCTY0VQCNEkHhrgMhfrdL5k/fafT/XqT/boU+sRI/2r0AhESFYFDCF1TwBSWa/CE8TQHcjQFcDX6cdQGq63w43PWU1AoEmup4emMjtvl+enzop/d7Aa57N4Btvo/Z37jCHBzBpuqaAHDOHWDo/iD99in8+XuZO/er/H6fxsB/h0irDgGqoYGAhOAL0eALUd/oo87TRK2nCUddI3anQKXTTUl1DUVVNXga6nh2mwvb6066L3Bz3btOekU2YXtd5MVNDmuVVtEFm6brAmiUu4OMPODnjv0idx+UGHpQ4q6DEnf928+h0iCg0egN0OgXqW8MUCd4cXm81LgbqHIJlDs9lFd5KK90U1hWQ3GlgLOqlrs/rsQ2q45rFjq57q0yukc6sL3iYc62akBDR0fRNcGmq5oAQYSAyuQEibsPhnjgkMYD8T4ePOTnv/ZrfJrtBTWE4A3ibvRTJ3hx1jdRXddApbOeMoebEruL3Ao3p8vqOVVSR1JuDZkFtbywvgDb0zl0eqmanm+Wct2bFdhmlvP2Po/hH6qIrmiCTdNUAVVElTVeyfTz10My435UGHNE5JGfJIYf1ZiV4kFo8uILBKirD+LwBHC43Dhq6ymurqfAUU9ORS05RSU0NHmQVRW/JCMqOho6354U6DEri04z8+nxWim2fxSx8ZgLEAlJCqoiCzZZUwVZVECDjUVNDD8s8sRRlSmJOs8mwbPHZJ466iO2yI8aDNJU56a8tpGqaicOZx2FVfWcLa8lq7CMBn8IHfAGZQKSik/SQAoBOjEZtdiezqTTS/l0fzmPrFIBNB9CUCcoSYJN0TTBKymgS5TXBXn8pwCTj8lMT1WZmarx+nGR2Rk680+GOFleh9/bgMtZR3V1HYWVLvJKajiTV0pVjRsV8IVEgrLaLApeERSCAAx5/xS2+48zZvlZAmKIQFMTHq+PppAi2DRdFQIyyKIfRImoMz6ePO7lpUyJl08r/PN0iMjTEh9kS6zNEUgp92GvaaDAXstZh4DLG0JoChAMyviDMn5Jxi/K+EISIVnFr0h4JWPZ33a8mNteSCI6vhpZbMDhaqChyYfHHxRsmhYURBECoRDoMvkukVfTFN7IlHk7W+aDXIlVOSHW5ob4qhi2F4v8WNLIiSov5wLgkVVERSUYMqa2LyTjF1UCkkpQ1vGLImJAxCsai2SdKFJQ6aG0ooqK+gBuVxOCTxBsuq4IkqQSFCVCkgyqzg8lXt5J9/H+WViVJ7G+KMSOEoVdJSp77DJxdRrH3Rq5goLDJyMERRqCIt6QbPiHqBAQFSRJJSCrhGSZJklDUs/nIefsNRRXe3DWefE0NjUf21BURFEiGDRWVH9I5Nu8Rt7Pk/lXocbWIpFvKzT2VSr8YJdIcEqk1Svke2TsPhV3SMYTEmkISngDCr6QTFBSCUkqQUnFL2kEJUNLflFF1aGuyUepow6HuxG34DdyVkVRkCQFUVLxBWR0ScEfFPmuuIG1hTIxpSr/rpA46FBIqJHJdGmcqVPJa1Co9MrUBiTcQQlPQKYhoNAUUvCJCn5JJSApBCTZEr8oEVJU/IpKiaOOmnov9Q1+QyOqqiErGqKiIYk6/mAATQ4QalRItPvZU+EnvkrmxxqJRJdIVp1KTr1GQaNChVei2idTE9BwBRTqAzKeoEyjKNMkKvglhaCkNAMZogAeX4DS6npcDQE8jYHmM0aajqLqyLJOSNbwSTJev0woEKCxyU9OdZCEkgAJ1UGSBYWsBo3cRihq0qj0KVT5ZBwBjdqQRn1IRhBVGiWVRlklqOiIikZQ1hAV4ytjJE2n0ummqt6LU/AjNAYEm64jaBqoqo6iaEiKhiirBESVhkAQr9eH0ChSUu3nVGkdGY4mUmt8ZLoC5DWoFDUplHpVKnw6VT6ZWn+IuqCEJ6TQEFINzUgSflXFJ8nUe304XALVdQ3UNwaob/TT5A0ZILoO4TCyrBKSZHxBGcEfxNPgpdETwO32U+ZoIKesnrPlHrLtbn6urCfPXk+B3U2R3cO5qgbOVTVQUilwrkKgtEqguLKe4sp6yhwNlNg9VLkEhKYgjd4QTX6RYFA+X2Dpuo6maaiqiqqqKIqGKKn4QwrekERjIIC70YenMUBjk0S9EKK23our3ovb48Xj8SI0BfD4ROq9IdyNQeoa/Lg8AZxuPzV1PuobRASfguAN0egP4Q1KBEMykqwKNkAxMnpDNM1oSxhQGrKiI6oqTapIkyTiDYUIiDLBkEwoICOGjHghKxqKqqCoEooqEVJFgkqIkBIiKIsEpBB+MYA/FMAXkgiICiHRmK2KoilWo6bZRIKu61bjRdd1QdEQVBVBkzRBlVRBFhVBVTRBUXRBknVB1hAUECQQNF0XUHVB13RB0XRBVDRBUjRBUTVBUlRBlBRBlGQhJGuCJOuCouiCpuqCqqpl/7Eemqor5HnS2Ja/hPezpvCP1PuYlfo3vvo5EnfA0baH9qs+CKZpBIIh7DUuyuw1lNprqHDU4mnwoqoamq5xyn2YVTkv8cKJO3n+TH+eTB7Ao/H9eSr+TnbmrfyfgdiddZzKKaK0yklhuYN6oWVfvabay+6Tu3gzaSJPpPZm9E9XMmnvH1n60wKSanZypuEg35WuZlrCMLb9vPSXgzicdWTkFLX7vya5Dq/spk62s8v1AW+cu53ns29kSd6z/Fi9mZ/L8tpqVFfZeHYxBe7MSwdJy85v8Xd1oJwDFRtZlTeTD88+wcKsMSzMGsv8rL8wNbMnc7LuJN6xg6AcsF6TW1xBkzfQct9P8pDrSkfT1QuDKKrKz8UV1t+V3kKi89/m1YyhvHlyMPOz/ouFZ4fwYe59fJAzjLfO3s66wuep8p7jbF0iUTkzOe76/rzZ6jxUVteGtch06gL2C4PIikJFtcv6e3/ZeuamDOHNU//NivwxfFY8jnXlE/iyYiKflz/Eh4WD2Gv/CL/YQIJjI2+dvJvXTt7FtJS+LPt5OvVBY383KEoUlFaGzSz5wqb5ubC0WSsyG3PfZUbKnXzw8wOsKX6EdWUT+NI+nq8cY1nrGMnikjuJd0Xhld1sr3iTt37+IyuLHmZN0WMszxnPzLSBvJnxMMWNPxv7vUITLrdw8VlzMswnNud+xD+O3cGy3LF8ce5R1pZN4IuKsXzlGM0X1SP4uPJ2jgpraJAcfFb+CJHnbuOz8pF8UT6OL0om8nnRJFblPcrLaXfxxolROHzGd2idq7xIHBEavTQFQwAcLNvMP5Lu5JOcsawpmsRnJROIKnuYtVWjWVP9Vz6q7McRz0pUTSa2Zh6LSgeytOJPfGa/j3UVY1lTMoFPz01kdcEjLM95hNmp/8UHmU+j6MYnlrJyz3UMknHW0IbDW8rLyfexIGs4nxU8zqqi8Xx07gGiKkfyheN+ltnvJEFYGdYOFWlUqjniWcGK8iFElQ1jTek4Pi2awOqCR1iZ9wgfnx3Hs4l9+aHc+BqH2voGRFFqC+JpaEKSjOR2Y84iZqX8majcx1ieN57Xc+/hvXPD+aziAZaX30VGY0yH0/1s00E+KR7KquL7+ezceFbnT2BFzkSW5Uzg7VP38UbKQ3hCdc1aKWoLktHsG06/nbnJ9/H+6VGsyJnIC9l38kreMNaUPsKSkkHsdy26aABMcK3lw4L/5l9FY1mdP56lOeP55Ox4Psh+mOeT7+BAyUZj17O8qiWIKMkUlNoBOFQaw4zkQSw+M5bZp+7in7mPsKnkFVade4DPSsfTJNVeFCSk+lhbPIVl+Q+wMnccS8+OY/GZsXxwZjTTj9/OkqwXACi3O/H5A+dBKhy1lFQac33t2bf5R/KdvJnxFxadnkSyYzuf5j3BssIR/Kt4DBvLp/NF2dOsqXiSNRVPsKbyCeNnxZN8XjaFz4ufJrr4Bf5V8Agr8h5iWc5YPs4ey4enR/P+6YeYnfZn3kh9CAUfqgz2Gtd5kLOFpZTYjUMHH516jmlJA3jjxHCO2XexteBtFpwZyqqC0awo+huLCv7Eu4W38V7x73mvtD/vl/Xl/bJ+vFfye94tuo2F+X/g3dw/szT/b6zIHcMnZ0fz0ZmHWXT6ISKzRvJq5mBeSh5MSeNZyzyyrBggWTlFlNsNssiMKYz9oQe7i/9FmmM/r6bezZKfx7Is5yGW5f+NFYUjWHXuflaXDmN12V+JKhtGVNkwVpX9lZXFw1lRNILl+Q/ySc6DfHRmFIuyRhF5ciRvZ/6NNzPvZ3baIJ5N+AM/1xsfXcg9V47XH2wLMidpFE/9eAcVQg7Lsp7j9fShfHTmIT4+M4rIrKG8ljGAeSf78eaZfszP7sc/z/bln9n9mH+mH29m9eO1jP7MPfF7ZibfxvSE3zP1UD+eiruVxw/cxIT9fRj+764Mje3M6bqjAOQVl+MPhgyQvHPllFQapnkhfgRf5y7haNV3PJvwe945+QDvnnyAf2bcQ0zR22S7fySzbj+Z7n2cdO/jZP1eTtbvI9O9j8y6fWS49pHm3Edq9T6OV+0luXIPRyt2k1C+i/jybzhYupUfSrfjV40wX1zhQNN0A8RR66bEbjjr5p+Xc9IRz9snJvJ88h94O/N+3s64j1dS7mJLXuT/v0e/vT6qa93nnVXXdXLOlRtJi6qSWLmL8Yd682rGvcxLG8qbJ4byRuoQXj56L+UNuRcdoDHk5kDJNvaXbuZA2Rb2l21hX9nX7C3byNaCKJKr4pqnbw3+QLBlQDttxn4dPsh4hseP3sjcjP/m5dRBvJYymNdTBjMtvh8rT865KMja0wsZvqsr4/f3ZNyBnjx88CpGxV3BiAM2bt5iY8PPKwz/KKlsG1lDooTgCRJAYPKR/jyb2pcZaQOZdfyPzDn+J145/l/MSfojU364lW05yzuE2F30FU/80JcZSQN5+fifmH38Tmam3MGM1Dt4LOE6pv90DyHFCGLZ+SXtL3pn88rJCR5hbPy1TEq6jqnJv2XGsduZdfwPzD52By8n/5FZSX9g8sGbeDflGU7VHMUTqKMhVM/Z2hMsSZ/JY3G38I/E25l77I/MOv4HZhy/nRkptzE1+Rbu+beNhMrvjLEKSi+cj0T+8AaPZfTi2eQ/8Gj89fz96C3MSB7AjOTfMzPpNmYn3c7MowN4/IdrmXKoPy8l3MtLP/2Fpw7fxiMHr+HFxH7MTrqNmUm/56XkAbyY3I/pyb/jr/tsRJ542hqnOGydaRdkxv6J/DXBxvflX/Fd0Rru2W3jmYTrmZnUnxlJ/ZhxtB+zjg5g1tH+vJBwM1Pjr+fZ+Ot5PuFmZiX2Y9ZR43kvJfXlpeR+PJ90M3/da2Nm4gME5MZ2c5F2QV5OeYA/7rZxrOYgANE/f8S933ViTFxXZiX1ZfbRvsxK7MusxFuZnXgrs8JkZuKtzEi8lZlHf8espL48Gd+Lu3fbeDVpLA1BY+kvc7T7ZTktQUQlyLQjg/nzv20cyo+zrsdX7OKR/bcybLeNp368hpd+uok5ib9lbuKtzfI75ib+jtmJv2PGT7fwfMJveOj7zty/O4JPs+YjKsYUdTc04Wloav/YRusLz/04lAeTIsgsPENewfnc0is1EH32Qx47MICH913F+O//F+O/t/H4wW7877gIHtnfhXHfd2Hs91cyZl9v3k19lgLPaev15TV1NDR6Oz4/0vrC26ceYVhcL45X/GB4d2Eljf7Q+cJI9pHqiGPVqVeZd+wRZicOZ0bCvbyS9DAfpD3PnnNfUuO3ny9NVI2T+eVI8oVPGrUB2ZsfzX1HehJTtMK6FgyJZOYW0+gXf1EIz8wro9LhvKTn2lrugkMoFOS5n/7C0APXYK8tb3GepMrh5HB8Cmknz5JbXEpBSQVlFbVU2N0UlVWRW1RK1s95/JCQzMkzPyPLMpqm4ff7CQQChEIhJElCURQ0TcPsVOm6fn6tCT+oUOkq4bGE27n/qzv4KeMIwVCQQCBAbV0ttXW1VFRWkJ19lrS0DJKSj5F4NInk5OOcPHmK/Px8amtrcbvd1NTU4HQ6cbvdNDU1WTCyLKOqaguYDmvfgNzE4bIYdpWv4UT5EezuMkQl9B877PT/DQC7cLwx8LR3hQAAAABJRU5ErkJggg==) no-repeat;padding-left:40px}
+        .browser .browser-firefox{background-position:0 -34px}
+        .browser .browser-ie{background-position:0 -68px;margin-left:0px}
+        .browser .browser-360{background-position:0 -170px;margin-left: -27px}
+    </style>
+</head>
+<body style="margin-top:50px">
+<h1>璇峰崌绾ф偍鐨勬祻瑙堝櫒锛屼互渚挎垜浠洿濂界殑涓烘偍鎻愪緵鏈嶅姟锛�</h1>
+<p>鎮ㄦ鍦ㄤ娇鐢� Internet Explorer 鐨勬棭鏈熺増鏈紙IE11浠ヤ笅鐗堟湰鎴栦娇鐢ㄨ鍐呮牳鐨勬祻瑙堝櫒锛夈�傝繖鎰忓懗鐫�鍦ㄥ崌绾ф祻瑙堝櫒鍓嶏紝鎮ㄥ皢鏃犳硶璁块棶姝ょ綉绔欍��</p>
+<hr>
+<h2>璇锋敞鎰忥細寰蒋鍏徃瀵筗indows XP 鍙� Internet Explorer 鏃╂湡鐗堟湰鐨勬敮鎸佸凡缁忕粨鏉�</h2>
+<p>鑷� 2016 骞� 1 鏈� 12 鏃ヨ捣锛孧icrosoft 涓嶅啀涓� IE 11 浠ヤ笅鐗堟湰鎻愪緵鐩稿簲鏀寔鍜屾洿鏂般�傛病鏈夊叧閿殑娴忚鍣ㄥ畨鍏ㄦ洿鏂帮紝鎮ㄧ殑鐢佃剳鍙兘鏄撳彈鏈夊鐥呮瘨銆侀棿璋嶈蒋浠跺拰鍏朵粬鎭舵剰杞欢鐨勬敾鍑伙紝瀹冧滑鍙互绐冨彇鎴栨崯瀹虫偍鐨勪笟鍔℃暟鎹拰淇℃伅銆傝鍙傞槄 <a href="https://www.microsoft.com/zh-cn/WindowsForBusiness/End-of-IE-support">寰蒋瀵� Internet Explorer 鏃╂湡鐗堟湰鐨勬敮鎸佸皢浜� 2016 骞� 1 鏈� 12 鏃ョ粨鏉熺殑璇存槑</a> 銆�</p>
+<hr>
+<h2>鎮ㄥ彲浠ラ�夋嫨鏇村厛杩涚殑娴忚鍣�</h2>
+<p>鎺ㄨ崘浣跨敤浠ヤ笅娴忚鍣ㄧ殑鏈�鏂扮増鏈�傚鏋滄偍鐨勭數鑴戝凡鏈変互涓嬫祻瑙堝櫒鐨勬渶鏂扮増鏈垯鐩存帴浣跨敤璇ユ祻瑙堝櫒璁块棶鍗冲彲銆�</p>
+<ul class="browser">
+    <li class="browser-chrome"><a href="https://www.google.cn/chrome/browser/desktop/index.html?hl=zh-CN&standalone=1"> 璋锋瓕娴忚鍣�<span>Google Chrome</span></a></li>
+    <li class="browser-firefox"><a href="https://www.mozilla.org/zh-CN/firefox/new/"> 鐏嫄娴忚鍣�<span>Mozilla Firefox</span></a></li>
+    <li class="browser-ie"><a href="https://windows.microsoft.com/zh-cn/internet-explorer/download-ie"> IE 11 娴忚鍣�<span>Internet Explorer</span></a></li>
+    <li class="browser-360"><a href="http://se.360.cn/"> 360瀹夊叏娴忚鍣�<span>360 Chrome</span></a></li>
+    <div class="clean"></div>
+</ul>
+<hr>
+</body>
+</html>
\ No newline at end of file
diff --git a/zhitan-vue/index.html b/zhitan-vue/index.html
new file mode 100644
index 0000000..3b22332
--- /dev/null
+++ b/zhitan-vue/index.html
@@ -0,0 +1,215 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <meta name="renderer" content="webkit">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+  <link rel="icon" href="/favicon.ico">
+  <title>绠$悊绯荤粺</title>
+  <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
+  <style>
+    html,
+    body,
+    #app {
+      height: 100%;
+      margin: 0px;
+      padding: 0px;
+    }
+
+    .chromeframe {
+      margin: 0.2em 0;
+      background: #ccc;
+      color: #000;
+      padding: 0.2em 0;
+    }
+
+    #loader-wrapper {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      z-index: 999999;
+    }
+
+    #loader {
+      display: block;
+      position: relative;
+      left: 50%;
+      top: 50%;
+      width: 150px;
+      height: 150px;
+      margin: -75px 0 0 -75px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -webkit-animation: spin 2s linear infinite;
+      -ms-animation: spin 2s linear infinite;
+      -moz-animation: spin 2s linear infinite;
+      -o-animation: spin 2s linear infinite;
+      animation: spin 2s linear infinite;
+      z-index: 1001;
+    }
+
+    #loader:before {
+      content: "";
+      position: absolute;
+      top: 5px;
+      left: 5px;
+      right: 5px;
+      bottom: 5px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -webkit-animation: spin 3s linear infinite;
+      -moz-animation: spin 3s linear infinite;
+      -o-animation: spin 3s linear infinite;
+      -ms-animation: spin 3s linear infinite;
+      animation: spin 3s linear infinite;
+    }
+
+    #loader:after {
+      content: "";
+      position: absolute;
+      top: 15px;
+      left: 15px;
+      right: 15px;
+      bottom: 15px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -moz-animation: spin 1.5s linear infinite;
+      -o-animation: spin 1.5s linear infinite;
+      -ms-animation: spin 1.5s linear infinite;
+      -webkit-animation: spin 1.5s linear infinite;
+      animation: spin 1.5s linear infinite;
+    }
+
+
+    @-webkit-keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+
+      100% {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      }
+    }
+
+    @keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+
+      100% {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      }
+    }
+
+
+    #loader-wrapper .loader-section {
+      position: fixed;
+      top: 0;
+      width: 51%;
+      height: 100%;
+      background: #7171C6;
+      z-index: 1000;
+      -webkit-transform: translateX(0);
+      -ms-transform: translateX(0);
+      transform: translateX(0);
+    }
+
+    #loader-wrapper .loader-section.section-left {
+      left: 0;
+    }
+
+    #loader-wrapper .loader-section.section-right {
+      right: 0;
+    }
+
+
+    .loaded #loader-wrapper .loader-section.section-left {
+      -webkit-transform: translateX(-100%);
+      -ms-transform: translateX(-100%);
+      transform: translateX(-100%);
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+
+    .loaded #loader-wrapper .loader-section.section-right {
+      -webkit-transform: translateX(100%);
+      -ms-transform: translateX(100%);
+      transform: translateX(100%);
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+
+    .loaded #loader {
+      opacity: 0;
+      -webkit-transition: all 0.3s ease-out;
+      transition: all 0.3s ease-out;
+    }
+
+    .loaded #loader-wrapper {
+      visibility: hidden;
+      -webkit-transform: translateY(-100%);
+      -ms-transform: translateY(-100%);
+      transform: translateY(-100%);
+      -webkit-transition: all 0.3s 1s ease-out;
+      transition: all 0.3s 1s ease-out;
+    }
+
+    .no-js #loader-wrapper {
+      display: none;
+    }
+
+    .no-js h1 {
+      color: #222222;
+    }
+
+    #loader-wrapper .load_title {
+      font-family: 'Open Sans';
+      color: #FFF;
+      font-size: 19px;
+      width: 100%;
+      text-align: center;
+      z-index: 9999999999999;
+      position: absolute;
+      top: 60%;
+      opacity: 1;
+      line-height: 30px;
+    }
+
+    #loader-wrapper .load_title span {
+      font-weight: normal;
+      font-style: italic;
+      font-size: 13px;
+      color: #FFF;
+      opacity: 0.5;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="app">
+    <div id="loader-wrapper">
+      <div id="loader"></div>
+      <div class="loader-section section-left"></div>
+      <div class="loader-section section-right"></div>
+      <div class="load_title">姝e湪鍔犺浇,璇疯�愬績绛夊緟</div>
+    </div>
+  </div>
+  <script type="module" src="/src/main.js"></script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/zhitan-vue/package.json b/zhitan-vue/package.json
new file mode 100644
index 0000000..c9e26a4
--- /dev/null
+++ b/zhitan-vue/package.json
@@ -0,0 +1,44 @@
+{
+  "name": "zhitan",
+  "version": "3.8.7",
+  "description": "绠$悊绯荤粺",
+  "author": "admin",
+  "license": "MIT",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build:prod": "vite build",
+    "build:stage": "vite build --mode staging",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@element-plus/icons-vue": "2.3.1",
+    "@vueup/vue-quill": "1.2.0",
+    "@vueuse/core": "10.6.1",
+    "axios": "0.27.2",
+    "dayjs": "^1.11.13",
+    "echarts": "5.4.3",
+    "element-plus": "2.4.3",
+    "file-saver": "2.0.5",
+    "fuse.js": "6.6.2",
+    "js-cookie": "3.0.5",
+    "jsencrypt": "3.3.2",
+    "nanoid": "^5.0.7",
+    "nprogress": "0.2.0",
+    "pinia": "2.1.7",
+    "vue": "3.3.9",
+    "vue-cropper": "1.1.1",
+    "vue-router": "4.2.5",
+    "yarn": "^1.22.22"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "4.5.0",
+    "@vue/compiler-sfc": "3.3.9",
+    "sass": "1.69.5",
+    "unplugin-auto-import": "0.17.1",
+    "unplugin-vue-setup-extend-plus": "1.0.0",
+    "vite": "5.0.4",
+    "vite-plugin-compression": "0.5.1",
+    "vite-plugin-svg-icons": "2.0.1"
+  }
+}
diff --git a/zhitan-vue/public/favicon.ico b/zhitan-vue/public/favicon.ico
new file mode 100644
index 0000000..e263760
--- /dev/null
+++ b/zhitan-vue/public/favicon.ico
Binary files differ
diff --git a/zhitan-vue/src/App.vue b/zhitan-vue/src/App.vue
new file mode 100644
index 0000000..59e8fa6
--- /dev/null
+++ b/zhitan-vue/src/App.vue
@@ -0,0 +1,25 @@
+<template>
+  <router-view />
+</template>
+
+<script setup>
+// import Cookies from 'js-cookie'
+import useSettingsStore from '@/store/modules/settings'
+import { handleThemeStyle } from '@/utils/theme'
+// import { systemName } from '@/api/system/name'
+onMounted(() => {
+  nextTick(() => {
+    // 鍒濆鍖栦富棰樻牱寮�
+    handleThemeStyle(useSettingsStore().theme)
+    document.querySelector('body').className = 'themeDark';
+  })
+  // systemName().then(res => {
+  //   if (res.code == 200) {
+  //     sessionStorage.setItem('SystemInfo', JSON.stringify(res.data))
+  //     // sessionStorage.removeItem('SystemInfo')
+  //   }
+  // })
+
+
+})
+</script>
diff --git a/zhitan-vue/src/api/alarmManage/alarmManage.js b/zhitan-vue/src/api/alarmManage/alarmManage.js
new file mode 100644
index 0000000..c592922
--- /dev/null
+++ b/zhitan-vue/src/api/alarmManage/alarmManage.js
@@ -0,0 +1,25 @@
+import request from '@/utils/request'
+export function historicalAlarm(query) {
+    return request({
+        url: '/energyAlarm/historicalAlarm/list',
+        method: 'get',
+        params: query
+    })
+}
+
+export function getByNodeId(query) {
+    return request({
+        url: '/alarmAnalysis/getByNodeId',
+        method: 'get',
+        params: query
+    })
+}
+
+export function getCountInfo(query) {
+    return request({
+        url: '/alarmAnalysis/getCountInfo',
+        method: 'get',
+        params: query
+    })
+}
+
diff --git a/zhitan-vue/src/api/auxiliaryEntry/electricityInput.js b/zhitan-vue/src/api/auxiliaryEntry/electricityInput.js
new file mode 100644
index 0000000..d1b2eb2
--- /dev/null
+++ b/zhitan-vue/src/api/auxiliaryEntry/electricityInput.js
@@ -0,0 +1,42 @@
+import request from '@/utils/request'
+
+
+// 杈呭姪褰曞叆-鐢ㄧ數褰曞叆-鍒楄〃
+export function electricityInputList(query) {
+    return request({
+      url: '/cost/input/list',
+      method: 'get',
+      params: query
+    })
+  }
+  // 杈呭姪褰曞叆-鐢ㄧ數褰曞叆-鏂板
+  export function electricityInputAdd(data) {
+    return request({
+      url: '/cost/input',
+      method: 'post',
+      data: data
+    })
+  }
+  // 杈呭姪褰曞叆-鐢ㄧ數褰曞叆-缂栬緫
+  export function electricityInputEdit(data) {
+    return request({
+      url: '/cost/input',
+      method: 'put',
+      data: data
+    })
+  }
+  // 杈呭姪褰曞叆-鐢ㄧ數褰曞叆-鍒犻櫎
+  export function electricityInputDel(id) {
+    return request({
+      url: '/cost/input/' + id,
+      method: 'delete'
+    })
+  }
+
+   // 杈呭姪褰曞叆-鐢ㄧ數褰曞叆-璇︽儏
+   export function electricityInputInfo(id) {
+    return request({
+      url: '/cost/input/' + id,
+      method: 'get'
+    })
+  }
diff --git a/zhitan-vue/src/api/auxiliaryEntry/energyIndexInput.js b/zhitan-vue/src/api/auxiliaryEntry/energyIndexInput.js
new file mode 100644
index 0000000..534485e
--- /dev/null
+++ b/zhitan-vue/src/api/auxiliaryEntry/energyIndexInput.js
@@ -0,0 +1,33 @@
+import request from '@/utils/request'
+
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鍒楄〃
+export function listEnergyindexinput(query) {
+  return request({
+    url: '/energyIndicators/list',
+    method: 'get',
+    params: query
+  })
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鏂板
+export function addEnergyindexinput(data) {
+  return request({
+    url: '/energyIndicators',
+    method: 'post',
+    data: data
+  })
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-缂栬緫
+export function updateEnergyindexinput(data) {
+  return request({
+    url: '/energyIndicators',
+    method: 'put',
+    data: data
+  })
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鍒犻櫎
+export function delEnergyindexinput(id) {
+  return request({
+    url: '/energyIndicators/' + id,
+    method: 'delete'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/auxiliaryEntry/energyIndicators.js b/zhitan-vue/src/api/auxiliaryEntry/energyIndicators.js
new file mode 100644
index 0000000..8138f4e
--- /dev/null
+++ b/zhitan-vue/src/api/auxiliaryEntry/energyIndicators.js
@@ -0,0 +1,33 @@
+import request from '@/utils/request'
+
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鍒楄〃
+export function listEnergyIndicators(query) {
+  return request({
+    url: '/energyindex/list',
+    method: 'get',
+    params: query
+  })
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鏂板
+export function addEnergyIndicators(data) {
+  return request({
+    url: '/energyindex',
+    method: 'post',
+    data: data
+  })
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-缂栬緫
+export function updateEnergyIndicators(data) {
+  return request({
+    url: '/energyindex',
+    method: 'put',
+    data: data
+  })
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鍒犻櫎
+export function delEnergyIndicators(id) {
+  return request({
+    url: '/energyindex/' + id,
+    method: 'delete'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/auxiliaryEntry/energyMeter.js b/zhitan-vue/src/api/auxiliaryEntry/energyMeter.js
new file mode 100644
index 0000000..96eafc8
--- /dev/null
+++ b/zhitan-vue/src/api/auxiliaryEntry/energyMeter.js
@@ -0,0 +1,33 @@
+import request from '@/utils/request'
+
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-鍒楄〃
+export function listEnergyMeter(query) {
+  return request({
+    url: '/meter/list',
+    method: 'get',
+    params: query
+  })
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-鏂板
+export function addEnergyMeter(data) {
+  return request({
+    url: '/meter',
+    method: 'post',
+    data: data
+  })
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-缂栬緫
+export function updateEnergyMeter(data) {
+  return request({
+    url: '/meter',
+    method: 'put',
+    data: data
+  })
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-鍒犻櫎
+export function delEnergyMeter(id) {
+  return request({
+    url: '/meter/' + id,
+    method: 'delete'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/auxiliaryEntry/productOutput.js b/zhitan-vue/src/api/auxiliaryEntry/productOutput.js
new file mode 100644
index 0000000..3e454dc
--- /dev/null
+++ b/zhitan-vue/src/api/auxiliaryEntry/productOutput.js
@@ -0,0 +1,34 @@
+import request from '@/utils/request'
+import { parseStrEmpty } from "@/utils/ruoyi";
+
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-鍒楄〃
+export function listProductoutput(query) {
+  return request({
+    url: '/productoutput/list',
+    method: 'get',
+    params: query
+  })
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-鏂板
+export function addProductoutput(data) {
+  return request({
+    url: '/productoutput',
+    method: 'post',
+    data: data
+  })
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-缂栬緫
+export function updateProductoutput(data) {
+  return request({
+    url: '/productoutput',
+    method: 'put',
+    data: data
+  })
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-鍒犻櫎
+export function delProductoutput(id) {
+  return request({
+    url: '/productoutput/' + id,
+    method: 'delete'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/basicsetting/alarm.js b/zhitan-vue/src/api/basicsetting/alarm.js
new file mode 100644
index 0000000..c8cc259
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/alarm.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鍒楄〃
+export function List(indexType,nodeId) {
+  return request({
+    url: '/basicsetting/modelnode/energyIndex/'+indexType+"/" + nodeId,
+    method: 'get',
+  })
+}
diff --git a/zhitan-vue/src/api/basicsetting/deviceStorage.js b/zhitan-vue/src/api/basicsetting/deviceStorage.js
new file mode 100644
index 0000000..b73743c
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/deviceStorage.js
@@ -0,0 +1,35 @@
+import request from '@/utils/request'
+
+export function getDeviceStorage(nodeId,stateId) {
+  return request({
+    url: '/basicsetting/deviceStorage/'+nodeId+"/" + stateId,
+    method: 'GET'
+  })
+}
+
+
+export function saveDeviceStorage(nodeId,stateId, data) {
+  return request({
+    url: '/basicsetting/deviceStorage/' + nodeId+'/'+stateId,
+    method: 'post',
+    data: data
+  });
+}
+
+export function getDeviceStateByNodeIds(query) {
+  return request({
+    url: '/basicsetting/deviceStorage/getDeviceStateByNodeIds',
+    method: 'GET',
+    params: query
+  })
+}
+
+// 淇敼棰勬姤璀﹁缃�
+export function setIsEnable(nodeId,isEnbale,data) {
+  return request({
+    url: '/basicsetting/deviceStorage/setIsEnable/'+nodeId+'/'+isEnbale,
+    method: 'post',
+    data: data
+  })
+}
+
diff --git a/zhitan-vue/src/api/basicsetting/energyindex.js b/zhitan-vue/src/api/basicsetting/energyindex.js
new file mode 100644
index 0000000..9d119b9
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/energyindex.js
@@ -0,0 +1,69 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鎸囨爣淇℃伅鍒楄〃
+export function listEnergyindex(query) {
+  return request({
+    url: '/basicsetting/energyindex/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ鎸囨爣淇℃伅璇︾粏
+export function getEnergyindex(indexId) {
+  return request({
+    url: '/basicsetting/energyindex/' + indexId,
+    method: 'get'
+  })
+}
+
+// 鏂板鎸囨爣淇℃伅
+export function addEnergyindex(nodeId, data) {
+  return request({
+    url: '/basicsetting/energyindex/' + nodeId,
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼鎸囨爣淇℃伅
+export function updateEnergyindex(data) {
+  return request({
+    url: '/basicsetting/energyindex',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎鎸囨爣淇℃伅
+export function delEnergyindex(nodeId, indexId) {
+  return request({
+    url: '/basicsetting/energyindex/' + nodeId + '/' + indexId,
+    method: 'delete'
+  })
+}
+
+// 瀵煎嚭鎸囨爣淇℃伅
+export function exportEnergyindex(query) {
+  return request({
+    url: '/basicsetting/energyindex/export',
+    method: 'get',
+    params: query
+  })
+}
+
+export function listCollectIndex(query) {
+  return request({
+    url: '/basicsetting/energyindex/collectIndex',
+    method: 'get',
+    params: query
+  })
+}
+
+// 涓嬭浇瀵煎叆妯℃澘
+export function importTemplate() {
+  return request({
+    url: '/basicsetting/energyindex/importTemplate',
+    method: 'get'
+  })
+}
diff --git a/zhitan-vue/src/api/basicsetting/function.js b/zhitan-vue/src/api/basicsetting/function.js
new file mode 100644
index 0000000..e34d318
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/function.js
@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 鏌ヨ璁$畻鍑芥暟鍒楄〃
+export function listFunction(query) {
+  return request({
+    url: '/basicsetting/function/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ璁$畻鍑芥暟璇︾粏
+export function getFunction(info) {
+  return request({
+    url: '/basicsetting/function/' + info,
+    method: 'get'
+  })
+}
+
+// 鏂板璁$畻鍑芥暟
+export function addFunction(data) {
+  return request({
+    url: '/basicsetting/function',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼璁$畻鍑芥暟
+export function updateFunction(data) {
+  return request({
+    url: '/basicsetting/function',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎璁$畻鍑芥暟
+export function delFunction(info) {
+  return request({
+    url: '/basicsetting/function/' + info,
+    method: 'delete'
+  })
+}
+
+// 瀵煎嚭璁$畻鍑芥暟
+export function exportFunction(query) {
+  return request({
+    url: '/basicsetting/function/export',
+    method: 'get',
+    params: query
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/basicsetting/historyAlarm.js b/zhitan-vue/src/api/basicsetting/historyAlarm.js
new file mode 100644
index 0000000..2bfbbcc
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/historyAlarm.js
@@ -0,0 +1,35 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鍒楄〃
+export function listHistoryAlarm(query) {
+  return request({
+    url: 'energyAlarm/historicalAlarm/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 瀵煎嚭璁惧妗f
+export function exportHistoricalAlarm(query) {
+  return request({
+    url: 'energyAlarm/historicalAlarm/export',
+    method: 'get',
+    params: query
+  })
+}
+
+
+// 鏌ヨ鍒楄〃
+export function listHistoryAlarmNote(query) {
+  return request({
+    url: 'energyAlarm/historicalAlarm/listNote',
+    method: 'get',
+    params: query
+  })
+}
+
+
+
+
+
+
diff --git a/zhitan-vue/src/api/basicsetting/indexStorage.js b/zhitan-vue/src/api/basicsetting/indexStorage.js
new file mode 100644
index 0000000..6b0814e
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/indexStorage.js
@@ -0,0 +1,38 @@
+import request from '@/utils/request'
+
+export function parseFormula(data) {
+  return request({
+    url: '/basicsetting/indexStorage/parseFormula',
+    method: 'post',
+    data: data
+  })
+}
+
+export function getNodeIndex(nodeId) {
+  return request({
+    url: '/basicsetting/energyindex/includeChildrenNode/' + nodeId,
+    method: 'GET'
+  })
+}
+
+export function getCalcPeriod() {
+  return request({
+    url: '/basicsetting/indexStorage/calcPeriod/',
+    method: 'GET'
+  })
+}
+
+export function saveIndexStorage(indexId, data) {
+  return request({
+    url: '/basicsetting/indexStorage/' + indexId,
+    method: 'post',
+    data: data
+  });
+}
+
+export function getIndexStorage(indexId) {
+  return request({
+    url: '/basicsetting/indexStorage/' + indexId,
+    method: 'GET'
+  })
+}
diff --git a/zhitan-vue/src/api/basicsetting/limitType.js b/zhitan-vue/src/api/basicsetting/limitType.js
new file mode 100644
index 0000000..3954bbc
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/limitType.js
@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鎶ヨ闄愬�肩被鍨嬬淮鎶ゅ垪琛�
+export function listLimitType(query) {
+  return request({
+    url: '/basicsetting/limitType/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ鎶ヨ闄愬�肩被鍨嬬淮鎶よ缁�
+export function getLimitType(id) {
+  return request({
+    url: '/basicsetting/limitType/' + id,
+    method: 'get'
+  })
+}
+
+// 鏂板鎶ヨ闄愬�肩被鍨嬬淮鎶�
+export function addLimitType(data) {
+  return request({
+    url: '/basicsetting/limitType',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼鎶ヨ闄愬�肩被鍨嬬淮鎶�
+export function updateLimitType(data) {
+  return request({
+    url: '/basicsetting/limitType',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎鎶ヨ闄愬�肩被鍨嬬淮鎶�
+export function delLimitType(id) {
+  return request({
+    url: '/basicsetting/limitType/' + id,
+    method: 'delete'
+  })
+}
+
+// 瀵煎嚭鎶ヨ闄愬�肩被鍨嬬淮鎶�
+export function exportLimitType(query) {
+  return request({
+    url: '/basicsetting/limitType/export',
+    method: 'get',
+    params: query
+  })
+}
diff --git a/zhitan-vue/src/api/basicsetting/model.js b/zhitan-vue/src/api/basicsetting/model.js
new file mode 100644
index 0000000..da23492
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/model.js
@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 鏌ヨ妯″瀷淇℃伅鍒楄〃
+export function listModel(query) {
+  return request({
+    url: '/basicsetting/model/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ妯″瀷淇℃伅璇︾粏
+export function getModel(id) {
+  return request({
+    url: '/basicsetting/model/' + id,
+    method: 'get'
+  })
+}
+
+// 鏂板妯″瀷淇℃伅
+export function addModel(data) {
+  return request({
+    url: '/basicsetting/model',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼妯″瀷淇℃伅
+export function updateModel(data) {
+  return request({
+    url: '/basicsetting/model',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎妯″瀷淇℃伅
+export function delModel(id) {
+  return request({
+    url: '/basicsetting/model/' + id,
+    method: 'delete'
+  })
+}
+
+// 瀵煎嚭妯″瀷淇℃伅
+export function exportModel(query) {
+  return request({
+    url: '/basicsetting/model/export',
+    method: 'get',
+    params: query
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/basicsetting/modelNode.js b/zhitan-vue/src/api/basicsetting/modelNode.js
new file mode 100644
index 0000000..a3675f4
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/modelNode.js
@@ -0,0 +1,185 @@
+import request from '@/utils/request'
+
+// 鏌ヨ妯″瀷鑺傜偣鍒楄〃
+export function modelNodeTree(query, withAuth) {
+  if (withAuth) {
+    return request({
+      url: '/basicsetting/modelnode/treelist/withAuth',
+      method: 'get',
+      params: query
+    })
+  } else {
+    return request({
+      url: '/basicsetting/modelnode/treelist',
+      method: 'get',
+      params: query
+    })
+  }
+}
+
+// 鏌ヨ妯″瀷鑺傜偣鍒楄〃
+export function listModelNode(query) {
+  return request({
+    url: '/basicsetting/modelnode/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ妯″瀷鑺傜偣璇︾粏
+export function getModelNode(nodeId) {
+  return request({
+    url: '/basicsetting/modelnode/' + nodeId,
+    method: 'get'
+  })
+}
+
+// 鏂板妯″瀷鑺傜偣
+export function addModelNode(data) {
+  return request({
+    url: '/basicsetting/modelnode',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼妯″瀷鑺傜偣
+export function updateModelNode(data) {
+  return request({
+    url: '/basicsetting/modelnode',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎妯″瀷鑺傜偣
+export function delModelNode(nodeId) {
+  return request({
+    url: '/basicsetting/modelnode/' + nodeId,
+    method: 'delete'
+  })
+}
+
+// 瀵煎嚭妯″瀷鑺傜偣
+export function exportModelNode(query) {
+  return request({
+    url: '/basicsetting/modelnode/export',
+    method: 'get',
+    params: query
+  })
+}
+
+export function updateModelNodeOrder(param) {
+  return request({
+    url: '/basicsetting/modelnode/order',
+    method: 'put',
+    data: param
+  })
+}
+
+export function hasEnergyIndex(nodeId) {
+  return request({
+    url: '/basicsetting/modelnode/hasEnergyIndex',
+    method: 'get',
+    params: {nodeId: nodeId}
+  })
+}
+
+export function getSettingDevice(nodeId) {
+  return request({
+    url: '/basicsetting/modelnode/device/' + nodeId,
+    method: 'get'
+  })
+}
+
+export function setDevice(nodeId, deviceIds) {
+  return request({
+    url: '/basicsetting/modelnode/device/' + nodeId,
+    method: 'post',
+    data: deviceIds
+  })
+}
+
+export function delDevice(nodeId, deviceId) {
+  return request({
+    url: '/basicsetting/modelnode/device/' + nodeId,
+    method: 'delete',
+    data: [deviceId]
+  })
+}
+//鑾峰彇妯″瀷涓嬬殑鑳芥簮鍝佺
+export function getSettingEnergy(nodeId) {
+  return request({
+    url: '/basicsetting/modelnode/energy/' + nodeId,
+    method: 'get'
+  })
+}
+
+export function setEnergy(nodeId, energyIds) {
+  return request({
+    url: '/basicsetting/modelnode/energy/' + nodeId,
+    method: 'post',
+    data: energyIds
+  })
+}
+
+export function delEnergy(nodeId, energyId) {
+  return request({
+    url: '/basicsetting/modelnode/energy/' + nodeId,
+    method: 'delete',
+    data: [energyId]
+  })
+}
+
+export function getSettingProduct(nodeId) {
+  return request({
+    url: '/basicsetting/modelnode/product/' + nodeId,
+    method: 'get'
+  })
+}
+
+export function setProduct(nodeId, productIds) {
+  return request({
+    url: '/basicsetting/modelnode/product/' + nodeId,
+    method: 'post',
+    data: productIds
+  })
+}
+
+export function delProduct(nodeId, productId) {
+  return request({
+    url: '/basicsetting/modelnode/product/' + nodeId,
+    method: 'delete',
+    data: [productId]
+  })
+}
+//鑾峰彇妯″瀷涓嬬殑鎸囨爣
+export function getSettingIndex(nodeId) {
+  return request({
+    url: '/basicsetting/modelnode/energyIndex/' + nodeId,
+    method: 'get'
+  })
+}
+
+export function getSettingCollectIndex(nodeId) {
+  return request({
+    url: '/basicsetting/modelnode/energyIndex/COLLECT/' + nodeId,
+    method: 'get'
+  })
+}
+
+export function setNodeToIndex(nodeId, indexIds, indexType) {
+  return request({
+    url: '/basicsetting/modelnode/energyIndex/' + nodeId + "/" + indexType,
+    method: 'post',
+    data: indexIds
+  })
+}
+
+export function delIndex(nodeId, indexId) {
+  return request({
+    url: '/basicsetting/modelnode/energyIndex/' + nodeId,
+    method: 'delete',
+    data: [indexId]
+  })
+}
diff --git a/zhitan-vue/src/api/basicsetting/state.js b/zhitan-vue/src/api/basicsetting/state.js
new file mode 100644
index 0000000..030e34b
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/state.js
@@ -0,0 +1,12 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鍒楄〃
+export function List(indexType,nodeId) {
+  return request({
+    url: '/basicsetting/modelnode/energyIndex/'+indexType+"/" + nodeId,
+    method: 'get',
+  })
+
+
+
+}
diff --git a/zhitan-vue/src/api/basicsetting/statetype.js b/zhitan-vue/src/api/basicsetting/statetype.js
new file mode 100644
index 0000000..f38de5e
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/statetype.js
@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 鏌ヨ绯荤粺鐘舵�佺淮鎶ゅ垪琛�
+export function listStatetype(query) {
+  return request({
+    url: '/basicsetting/statetype/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ绯荤粺鐘舵�佺淮鎶よ缁�
+export function getStatetype(stateId) {
+  return request({
+    url: '/basicsetting/statetype/' + stateId,
+    method: 'get'
+  })
+}
+
+// 鏂板绯荤粺鐘舵�佺淮鎶�
+export function addStatetype(data) {
+  return request({
+    url: '/basicsetting/statetype',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼绯荤粺鐘舵�佺淮鎶�
+export function updateStatetype(data) {
+  return request({
+    url: '/basicsetting/statetype',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎绯荤粺鐘舵�佺淮鎶�
+export function delStatetype(stateId) {
+  return request({
+    url: '/basicsetting/statetype/' + stateId,
+    method: 'delete'
+  })
+}
+
+// 瀵煎嚭绯荤粺鐘舵�佺淮鎶�
+export function exportStatetype(query) {
+  return request({
+    url: '/basicsetting/statetype/export',
+    method: 'get',
+    params: query
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/basicsetting/template.js b/zhitan-vue/src/api/basicsetting/template.js
new file mode 100644
index 0000000..3be4dcc
--- /dev/null
+++ b/zhitan-vue/src/api/basicsetting/template.js
@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 鏌ヨ閲囬泦鍙傛暟妯℃澘鍒楄〃
+export function listTemplate(query) {
+  return request({
+    url: '/system/template/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ閲囬泦鍙傛暟妯℃澘璇︾粏
+export function getTemplate(id) {
+  return request({
+    url: '/system/template/' + id,
+    method: 'get'
+  })
+}
+
+// 鏂板閲囬泦鍙傛暟妯℃澘
+export function addTemplate(data) {
+  return request({
+    url: '/system/template',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼閲囬泦鍙傛暟妯℃澘
+export function updateTemplate(data) {
+  return request({
+    url: '/system/template',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎閲囬泦鍙傛暟妯℃澘
+export function delTemplate(id) {
+  return request({
+    url: '/system/template/' + id,
+    method: 'delete'
+  })
+}
+
+// 瀵煎嚭閲囬泦鍙傛暟妯℃澘
+export function exportTemplate(query) {
+  return request({
+    url: '/system/template/export',
+    method: 'get',
+    params: query
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/benchmarkManage/index.js b/zhitan-vue/src/api/benchmarkManage/index.js
new file mode 100644
index 0000000..7f305bf
--- /dev/null
+++ b/zhitan-vue/src/api/benchmarkManage/index.js
@@ -0,0 +1,47 @@
+import request from '@/utils/request'
+
+// 鍒楄〃
+export function valueManageList(query) {
+  return request({
+    url: '/benchmarkManage/list',
+    method: 'get',
+    params: query
+  })
+}
+
+
+// 璇︽儏
+export function valueManageInfo(id) {
+  return request({
+    url: '/benchmarkManage/' + id,
+    method: 'get'
+  })
+}
+
+
+// 鏂板缓
+export function valueManageAdd(data) {
+  return request({
+    url: '/benchmarkManage',
+    method: 'post',
+    data
+  })
+}
+
+// 淇敼 
+export function valueManageEdit(data) {
+  return request({
+    url: '/benchmarkManage',
+    method: 'put',
+    data
+  })
+}
+
+
+// 鍒犻櫎 
+export function valueManageDel(id) {
+  return request({
+    url: '/benchmarkManage/' + id,
+    method: 'delete'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/businessConfiguration/businessConfiguration.js b/zhitan-vue/src/api/businessConfiguration/businessConfiguration.js
new file mode 100644
index 0000000..1754eb3
--- /dev/null
+++ b/zhitan-vue/src/api/businessConfiguration/businessConfiguration.js
@@ -0,0 +1,48 @@
+import request from '@/utils/request'
+
+
+
+
+// 鑾峰彇鍒楄〃
+export function alarmList(params) {
+    return request({
+        url: '/alarm/limit/list',
+        method: 'get',
+        params
+    })
+}
+
+// 鏂板
+export function alarmAdd(data) {
+    return request({
+        url: '/alarm/limit/add',
+        method: 'post',
+        data
+    })
+}
+
+// 鏌ヨ鎯�
+export function alarmInfo(params) {
+    return request({
+        url: `/alarm/limit/${params.id}`,
+        method: 'get',
+        params
+    })
+}
+
+// 缂栬緫
+export function alarmEdit(data) {
+    return request({
+        url: '/alarm/limit',
+        method: 'put',
+        data
+    })
+}
+
+//鍒犻櫎
+export function alarmDel(id) {
+    return request({
+        url: '/alarm/limit/' + id,
+        method: 'delete',
+    })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/businessConfiguration/gatewayLedger.js b/zhitan-vue/src/api/businessConfiguration/gatewayLedger.js
new file mode 100644
index 0000000..924c31a
--- /dev/null
+++ b/zhitan-vue/src/api/businessConfiguration/gatewayLedger.js
@@ -0,0 +1,75 @@
+import request from '@/utils/request'
+
+
+
+// 缃戝叧鍙拌处缁熻
+export function gatewayStatistics(params) {
+    return request({
+        url: '/productoutput/ptNum',
+        method: 'get',
+        params
+    })
+}
+
+
+
+// 缃戝叧鍙拌处鍒楄〃
+export function gatewayList(params) {
+    return request({
+        url: '/gatewaySetting/list',
+        method: 'get',
+        params
+
+    })
+}
+
+//缃戝叧鍙拌处鏂板
+export function gatewayAdd(data) {
+    return request({
+        url: '/gatewaySetting',
+        method: 'post',
+        data
+    })
+}
+
+// 缃戝叧鍙拌处缂栬緫
+export function gatewayEdit(data) {
+    return request({
+        url: '/gatewaySetting',
+        method: 'put',
+        data
+    })
+}
+
+
+
+// 缃戝叧鍙拌处鍒犻櫎
+export function gatewayDel(id) {
+    return request({
+        url: '/gatewaySetting/' + id,
+        method: 'delete',
+    })
+}
+ 
+
+
+// 缃戝叧鍙拌处璇︽儏
+export function gatewayInfo(id) {
+    return request({
+        url: '/gatewaySetting/' + id,
+        method: 'get',
+    })
+}
+
+
+// 缃戝叧鍙拌处涓嬫媺鍒楄〃
+export function gatewayBaseList() {
+    return request({
+        url: '/gatewaySetting/baseList',
+        method: 'get',
+    })
+}
+
+
+
+
diff --git a/zhitan-vue/src/api/businessConfiguration/preAlarmManage.js b/zhitan-vue/src/api/businessConfiguration/preAlarmManage.js
new file mode 100644
index 0000000..22f6d7d
--- /dev/null
+++ b/zhitan-vue/src/api/businessConfiguration/preAlarmManage.js
@@ -0,0 +1,69 @@
+import request from '@/utils/request'
+
+// 鑾峰彇鍒楄〃
+//瀵屽伐鎺ュ彛
+export function getSettingIndex(query) {
+    return request({
+        url: '/basicsetting/modelnode/energyIndex/' + query.indexType + "/" + query.nodeId,
+        method: 'get',
+        params:query,
+    })
+}
+
+export function getStartStop(indexid) {
+    return request({
+        url: '/system/alarmitem/getStartStop/' + indexid,
+        async: false,
+        method: 'get'
+    })
+}
+
+// 鏌ヨ棰刬d涓嬭缃檺鍊肩殑鏁伴噺-閲囬泦鐐圭鐞�
+export function getSetting(id) {
+    return request({
+        url: '/system/alarmitem/getSettingInfo/' + id,
+        method: 'get',
+    })
+}
+
+
+
+// 鏌ヨ鎶ヨ闄愬�肩被鍨嬬淮鎶ゅ垪琛�
+export function listLimitType(query) {
+    return request({
+        url: '/basicsetting/limitType/list',
+        method: 'get',
+        params: query
+    })
+}
+
+
+// 淇敼琛ㄥ崟鏁版嵁
+export function updateDialogForm(data) {
+    return request({
+        url: '/system/alarmitem/editLimitVal',
+        method: 'put',
+        data: data
+    })
+}
+
+
+// 淇敼棰勬姤璀﹁缃�
+export function updateSet(data, type) {
+    return request({
+        url: '/system/alarmitem/startstop/' + type,
+        method: 'post',
+        data: data
+    })
+}
+
+
+
+// 鏌ヨ棰刬d涓嬭缃檺鍊肩殑鏁伴噺-缁熻鎸囨爣绠$悊
+export function getSettingCount(id) {
+    return request({
+        url: '/system/alarmitem/getSettingCount/' + id,
+        method: 'get'
+    })
+}
+
diff --git a/zhitan-vue/src/api/carbonemission/carbonemission.js b/zhitan-vue/src/api/carbonemission/carbonemission.js
new file mode 100644
index 0000000..6d8e909
--- /dev/null
+++ b/zhitan-vue/src/api/carbonemission/carbonemission.js
@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+// 纰虫帓鏀剧鐞�-纰虫帓鏀鹃噺鏍哥畻-澶撮儴
+export function listUpCarbonemission(query) {
+  return request({
+    url: '/carbonEmission/up',
+    method: 'get',
+    params: query
+  })
+}
+// 纰虫帓鏀剧鐞�-纰虫帓鏀鹃噺鏍哥畻-纰虫帓鏀鹃噺鍚岀幆姣�
+export function listMiddleCarbonemission(query) {
+  return request({
+    url: '/carbonEmission/middle',
+    method: 'get',
+    params: query
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/consumptionanalysis/consumptionanalysis.js b/zhitan-vue/src/api/consumptionanalysis/consumptionanalysis.js
new file mode 100644
index 0000000..c54f692
--- /dev/null
+++ b/zhitan-vue/src/api/consumptionanalysis/consumptionanalysis.js
@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 浜у搧鍗曡�楀垎鏋�-浜у搧鍗曡�楀垎鏋�-鍒楄〃
+export function listConsumptionanalysis(query) {
+  return request({
+    url: '/consumptionanalysis/getProdEnergy',
+    method: 'get',
+    params: query
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/costManage/costManage.js b/zhitan-vue/src/api/costManage/costManage.js
new file mode 100644
index 0000000..ab31cc7
--- /dev/null
+++ b/zhitan-vue/src/api/costManage/costManage.js
@@ -0,0 +1,122 @@
+import request from '@/utils/request'
+
+
+// 鎴愭湰绠$悊-鍗曚环绛栫暐-鍒楄〃
+export function unitPriceStrategyList(query) {
+    return request({
+        url: '/cost/tactics/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 鎴愭湰绠$悊-鍗曚环绛栫暐-鍒楄〃锛堜笉鍒嗛〉锛�
+export function unitPriceStrategyAllList() {
+    return request({
+        url: '/cost/tactics/allList',
+        method: 'get',
+    })
+}
+
+// 鎴愭湰绠$悊-鍗曚环绛栫暐-鏂板
+export function unitPriceStrategyAdd(data) {
+    return request({
+        url: '/cost/tactics',
+        method: 'post',
+        data: data
+    })
+}
+// 鎴愭湰绠$悊-鍗曚环绛栫暐-缂栬緫
+export function unitPriceStrategyEdit(data) {
+    return request({
+        url: '/cost/tactics',
+        method: 'put',
+        data: data
+    })
+}
+// 鎴愭湰绠$悊-鍗曚环绛栫暐-鍒犻櫎
+export function unitPriceStrategyDel(id) {
+    return request({
+        url: '/cost/tactics/' + id,
+        method: 'delete'
+    })
+}
+
+// 鎴愭湰绠$悊-鍗曚环绛栫暐-璇︽儏
+export function unitPriceStrategyInfo(id) {
+    return request({
+        url: '/cost/tactics/' + id,
+        method: 'get'
+    })
+}
+
+
+
+
+// 鎴愭湰绠$悊-鍗曚环鍏宠仈-鍒楄〃
+export function unitPriceCorrelationList(query) {
+    return request({
+        url: '/cost/relevancy/list',
+        method: 'get',
+        params: query
+    })
+}
+// 鎴愭湰绠$悊-鍗曚环鍏宠仈-鏂板
+export function unitPriceCorrelationAdd(data) {
+    return request({
+        url: '/cost/relevancy',
+        method: 'post',
+        data: data
+    })
+}
+// 鎴愭湰绠$悊-鍗曚环鍏宠仈-缂栬緫
+export function unitPriceCorrelationEdit(data) {
+    return request({
+        url: '/cost/relevancy',
+        method: 'put',
+        data: data
+    })
+}
+// 鎴愭湰绠$悊-鍗曚环鍏宠仈-鍒犻櫎
+export function unitPriceCorrelationDel(id) {
+    return request({
+        url: '/cost/relevancy/' + id,
+        method: 'delete'
+    })
+}
+
+// 鎴愭湰绠$悊-鍗曚环鍏宠仈-璇︽儏
+export function unitPriceCorrelationInfo(id) {
+    return request({
+        url: '/cost/relevancy/' + id,
+        method: 'get'
+    })
+}
+
+
+
+// 鎴愭湰绠$悊-鐢佃垂鍋忓樊鍒嗘瀽-鐢靛姏鏁版嵁銆佺粺璁℃暟鎹�
+export function deviationCardList(params) {
+    return request({
+        url: '/cost/statistics/statistics',
+        method: 'get',
+        params
+    })
+}
+// 鎴愭湰绠$悊-鐢佃垂鍋忓樊鍒嗘瀽-鑰楃數鏄庣粏
+export function deviationTableList(params) {
+    return request({
+        url: '/cost/statistics/getStatisticsInfoList',
+        method: 'get',
+        params
+    })
+}
+
+// 鎴愭湰绠$悊-鐢佃垂鍋忓樊鍒嗘瀽-鑰楃數鏄庣粏璇︽儏
+export function deviationInfo(params) {
+    return request({
+        url: '/cost/statistics/getStatisticsInfoEcharts',
+        method: 'get',
+        params
+    })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/energy/energy.js b/zhitan-vue/src/api/energy/energy.js
new file mode 100644
index 0000000..14a04e9
--- /dev/null
+++ b/zhitan-vue/src/api/energy/energy.js
@@ -0,0 +1,79 @@
+import request from '@/utils/request'
+// 褰撴棩鑳借��
+export function listDay(query) {
+  return request({
+    url: '/energydatahomepage/energyConsumptionSummation/day',
+    method: 'get',
+    params: query
+  })
+}
+// 鍚岀幆姣斿垎鏋�
+export function listEnergyConsumptionSummation(query) {
+  return request({
+    url: '/energydatahomepage/energyConsumptionSummation',
+    method: 'get',
+    params: query
+  })
+}
+// 鑰楃數鍗犳瘮
+export function listPeakValley(query) {
+  return request({
+    url: '/energydatahomepage/peakValley',
+    method: 'get',
+    params: query
+  })
+}
+// 鏈湀缁煎悎鑳借��/鏈湀鑳借�楄垂鐢�/鏈湀纰虫帓鏀�/鏈湀鐢ㄨ兘瀹屾垚鎸囨爣
+export function listFactoryEnergyConsumption(query) {
+  return request({
+    url: '/energydatahomepage/factoryEnergyConsumption',
+    method: 'get',
+    params: query
+  })
+}
+// 鐢ㄧ數瓒嬪娍鍒嗘瀽
+export function listSegmentAnalysisYear(query) {
+  return request({
+    url: '/energydatahomepage/segmentAnalysis/year',
+    method: 'get',
+    params: query
+  })
+}
+export function listSegmentAnalysisMonth(query) {
+  return request({
+    url: '/energydatahomepage/segmentAnalysis/month',
+    method: 'get',
+    params: query
+  })
+}
+export function listSegmentAnalysisDay(query) {
+  return request({
+    url: '/energydatahomepage/segmentAnalysis/day',
+    method: 'get',
+    params: query
+  })
+}
+// 鑳芥簮璐叆涓庢秷鑰楀姣�
+export function listPurchaseConsumption(query) {
+  return request({
+    url: '/energydatahomepage/purchaseConsumption',
+    method: 'get',
+    params: query
+  })
+}
+// 鑳借�楁帓鍚�
+export function listEnergyConsumptionRanking(query) {
+  return request({
+    url: '/energydatahomepage/energyConsumptionRanking',
+    method: 'get',
+    params: query
+  })
+}
+// 鑳芥簮鎴愭湰鍗犳瘮
+export function listCostProp(query) {
+  return request({
+    url: '/energydatahomepage/costProp',
+    method: 'get',
+    params: query
+  })
+}
diff --git a/zhitan-vue/src/api/energyAnalysis/energyAnalysis.js b/zhitan-vue/src/api/energyAnalysis/energyAnalysis.js
new file mode 100644
index 0000000..510703c
--- /dev/null
+++ b/zhitan-vue/src/api/energyAnalysis/energyAnalysis.js
@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 鑳借�楀姣斿垎鏋�-鍖哄煙鑳借�楀垎鏋�-鑳借�楄秼鍔�/鍖哄煙鑳借�楃粺璁″垎鏋愯〃-鍒楄〃
+// 鑳借�楀姣斿垎鏋�-绉戝鑳借�楀垎鏋�-鑳借�楄秼鍔�/鑳借�楃粺璁″垎鏋愯〃-鍒楄〃
+// 鑳借�楀姣斿垎鏋�-璁惧鑳借�楀垎鏋�-鑳借�楄秼鍔�/鑳借�楃粺璁″垎鏋愯〃-鍒楄〃
+export function listRegion(query) {
+  return request({
+    url: '/consumptionanalysis/getByArea',
+    method: 'get',
+    params: query
+  })
+}
+// 鑳借�楀姣斿垎鏋�-绉戝鑳借�楀垎鏋�-鑳借�楁帓鍚�-鍒楄〃
+// 鑳借�楀姣斿垎鏋�-璁惧鑳借�楀垎鏋�-鑳借�楁帓鍚�-鍒楄〃
+export function listDepartment(query) {
+  return request({
+    url: '/consumptionanalysis/getByDepartment',
+    method: 'get',
+    params: query
+  })
+}
+// 鑳借�楀姣斿垎鏋�-鑳借�楁寚鏍囪�冩牳-鑳借�楄秼鍔�/鍖哄煙鑳借�楃粺璁″垎鏋愯〃
+export function listIndicatorassessment(query) {
+  return request({
+    url: '/consumptionanalysis/getPlanAndProdCount',
+    method: 'get',
+    params: query
+  })
+}
+// 鑳借�楀姣斿垎鏋�-缁煎悎鑳借�楀垎鏋�-缁煎悎鑳借�楄秼鍔�/鍚勪粙璐ㄨ兘鑰楀崰姣�/鍖哄煙鑳借�楃粺璁″垎鏋愯〃-鍒楄〃
+export function listComprehensive(query) {
+  return request({
+    url: '/consumptionanalysis/getComprehensiveEnergy',
+    method: 'get',
+    params: query
+  })
+}
+// 鑳借�楀姣斿垎鏋�-缁煎悎鑳借�楀垎鏋�-鑳借�楀悓姣旂幆姣�-鍒楄〃
+export function listYoY(query) {
+  return request({
+    url: '/consumptionanalysis/getYOY',
+    method: 'get',
+    params: query
+  })
+}
+// 鑳借�楀姣斿垎鏋�-缁煎悎鑳借�楀垎鏋�-鐢ㄨ兘鍗曞厓鑳借�楁帓鍚�-鍒楄〃
+export function listEnergyRanking(query) {
+  return request({
+    url: '/consumptionanalysis/getEnergyRanking',
+    method: 'get',
+    params: query
+  })
+}
+
diff --git a/zhitan-vue/src/api/home.js b/zhitan-vue/src/api/home.js
new file mode 100644
index 0000000..8d5f6b0
--- /dev/null
+++ b/zhitan-vue/src/api/home.js
@@ -0,0 +1,33 @@
+import request from '@/utils/request'
+// 棣栭〉-鍏ㄥ巶鑳借�楃粺璁�/鍏ㄥ巶鑳借�楀崰姣�-鍒楄〃
+export function listEnergyConsumptionSummation(query) {
+  return request({
+    url: '/homepage/energyConsumptionSummation',
+    method: 'get',
+    params: query
+  })
+}
+// 棣栭〉-鍏ㄥ巶鑳借�楄秼鍔�-鍒楄〃
+export function listEnergyConsumptionTrend(query) {
+  return request({
+    url: '/homepage/energyConsumptionTrend',
+    method: 'get',
+    params: query
+  })
+}
+// 棣栭〉-绉戝鑳借�楁帓鍚峊OP-鍒楄〃
+export function listEnergyConsumptionRanking(query) {
+  return request({
+    url: '/homepage/energyConsumptionRanking',
+    method: 'get',
+    params: query
+  })
+}
+// 棣栭〉-灏栧嘲骞宠胺鍗犳瘮-鍒楄〃
+export function listPeakValley(query) {
+  return request({
+    url: '/homepage/peakValley',
+    method: 'get',
+    params: query
+  })
+}
diff --git a/zhitan-vue/src/api/login.js b/zhitan-vue/src/api/login.js
new file mode 100644
index 0000000..82250eb
--- /dev/null
+++ b/zhitan-vue/src/api/login.js
@@ -0,0 +1,68 @@
+import request from '@/utils/request'
+
+// 鐧诲綍鏂规硶
+export function login(username, password, code, uuid) {
+  const data = {
+    username,
+    password,
+    code,
+    uuid
+  }
+  return request({
+    url: '/login',
+    headers: {
+      isToken: false,
+      repeatSubmit: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 娉ㄥ唽鏂规硶
+export function register(data) {
+  return request({
+    url: '/register',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 鑾峰彇鐢ㄦ埛璇︾粏淇℃伅
+export function getInfo() {
+  return request({
+    url: '/getInfo',
+    method: 'get'
+  })
+}
+
+// 閫�鍑烘柟娉�
+export function logout() {
+  return request({
+    url: '/logout',
+    method: 'post'
+  })
+}
+
+// 鑾峰彇楠岃瘉鐮�
+export function getCodeImg() {
+  return request({
+    url: '/captchaImage',
+    headers: {
+      isToken: false
+    },
+    method: 'get',
+    timeout: 20000
+  })
+}
+//骞冲彴甯︾潃tonken杩涜鐧诲綍
+export function getSingleLogin(query) {
+  return request({
+    url: '/singleLogin',
+    method: 'get',
+    params: query
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/measuringInstruments/distributionroom.js b/zhitan-vue/src/api/measuringInstruments/distributionroom.js
new file mode 100644
index 0000000..a861d70
--- /dev/null
+++ b/zhitan-vue/src/api/measuringInstruments/distributionroom.js
@@ -0,0 +1,51 @@
+import request from '@/utils/request'
+import { parseStrEmpty } from "@/utils/ruoyi";
+
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鍒嗛〉鍒楄〃
+export function listDistributionRoom(query) {
+  return request({
+    url: '/powerDistribution/pageList',
+    method: 'get',
+    params: query
+  })
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鍏ㄩ儴鍒楄〃
+export function listDistributionRoomList(query) {
+  return request({
+    url: '/powerDistribution/list',
+    method: 'get',
+    params: query
+  })
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鏂板
+export function addDistributionRoom(data) {
+  return request({
+    url: '/powerDistribution/add',
+    method: 'post',
+    data: data
+  })
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鏌ョ湅
+export function getDistributionRoom(query) {
+  return request({
+    url: '/powerDistribution/getDetail',
+    method: 'get',
+    params: query
+  })
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-缂栬緫
+export function updateDistributionRoom(data) {
+  return request({
+    url: '/powerDistribution/edit',
+    method: 'post',
+    data: data
+  })
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鍒犻櫎
+export function delDistributionRoom(query) {
+  return request({
+    url: '/powerDistribution/remove',
+    method: 'delete',
+    params: query
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/measuringInstruments/measuringInstruments.js b/zhitan-vue/src/api/measuringInstruments/measuringInstruments.js
new file mode 100644
index 0000000..bf8a37c
--- /dev/null
+++ b/zhitan-vue/src/api/measuringInstruments/measuringInstruments.js
@@ -0,0 +1,90 @@
+import request from '@/utils/request'
+
+
+// 鑾峰彇鍒楄〃
+export function getMaintainList(data) {
+  return request({
+    url: '/meter/implement/list',
+    method: 'get',
+    params: data
+  })
+}
+
+//鏂板
+export function addMaintain(data) {
+  return request({
+    url: '/meter/implement',
+    method: 'post',
+    data
+  })
+}
+
+
+// 淇敼
+export function updateMaintain(data) {
+  return request({
+    url: '/meter/implement',
+    method: 'put',
+    data: data
+  })
+}
+
+
+// 鍒犻櫎鍙傛暟閰嶇疆
+export function delMaintain(id) {
+  return request({
+    url: '/meter/implement/' + id,
+    method: 'delete'
+  })
+}
+
+// 鎸囨爣鍒楄〃  
+export function getTargetList(id) {
+  return request({
+    url: '/basicsetting/energyindex/meterIndex/' + id,
+    method: 'get',
+  })
+}
+
+// 鐢熸垚鎸囨爣
+export function addTarget(id) {
+  return request({
+    url: '/basicsetting/energyindex/meterIndex/' + id,
+    method: 'post',
+  })
+}
+
+// 鍒犻櫎鎸囨爣
+export function delTarget(id) {
+  return request({
+    url: '/basicsetting/energyindex/' + id,
+    method: 'delete',
+  })
+}
+
+
+
+// 鑾峰彇闄勪欢鍒楄〃
+export function getAnnexList(data) {
+  return request({
+    url: '/meter/annex/list',
+    method: 'get',
+    params: data
+  })
+}
+
+ 
+
+// // 鑾峰彇鐩戞祴鑳芥簮绫诲瀷鍒楄〃
+// export function energyTypeList() {
+//   return request({
+//     url: '/statisticsAnalysis/queryEnergyTypeList',
+//     method: 'get',
+//   })
+// }
+
+ 
+
+
+
+
diff --git a/zhitan-vue/src/api/menu.js b/zhitan-vue/src/api/menu.js
new file mode 100644
index 0000000..faef101
--- /dev/null
+++ b/zhitan-vue/src/api/menu.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+// 鑾峰彇璺敱
+export const getRouters = () => {
+  return request({
+    url: '/getRouters',
+    method: 'get'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/modelConfiguration/businessModel.js b/zhitan-vue/src/api/modelConfiguration/businessModel.js
new file mode 100644
index 0000000..6fa106b
--- /dev/null
+++ b/zhitan-vue/src/api/modelConfiguration/businessModel.js
@@ -0,0 +1,119 @@
+import request from '@/utils/request'
+import { da } from 'element-plus/es/locales.mjs'
+
+
+
+// 鏌ヨ妯″瀷淇℃伅鍒楄〃
+export function listModel(query) {
+    return request({
+        url: '/basicsetting/model/list',
+        method: 'get',
+        params: query
+    })
+}
+
+
+// 鏂板妯″瀷淇℃伅
+export function addModel(data) {
+    return request({
+        url: '/basicsetting/model',
+        method: 'post',
+        data: data
+    })
+}
+
+// 淇敼妯″瀷淇℃伅
+export function updateModel(data) {
+    return request({
+        url: '/basicsetting/model',
+        method: 'put',
+        data: data
+    })
+}
+
+// 鏌ヨ妯″瀷淇℃伅璇︾粏
+export function getModel(id) {
+    return request({
+        url: '/basicsetting/model/' + id,
+        method: 'get'
+    })
+}
+
+// 鍒犻櫎妯″瀷淇℃伅
+export function delModel(id) {
+    return request({
+        url: '/basicsetting/model/' + id,
+        method: 'delete'
+    })
+}
+
+//鑾峰彇妯″瀷涓嬬殑璁惧
+export function getSettingDevice(nodeId) {
+    return request({
+        url: '/basicsetting/modelnode/device/' + nodeId,
+        method: 'get'
+    })
+}
+//淇濆瓨妯″瀷涓嬬殑璁惧
+export function setDevice(nodeId, deviceIds) {
+    return request({
+        url: '/basicsetting/modelnode/device/' + nodeId,
+        method: 'post',
+        data: deviceIds
+    })
+}
+
+//鍒犻櫎妯″瀷涓嬬殑璁惧
+export function delDevice(nodeId, deviceId) {
+    return request({
+        url: '/basicsetting/modelnode/device/' + nodeId,
+        method: 'delete',
+        data: [deviceId]
+    })
+}
+
+//鑾峰彇妯″瀷涓嬬殑鎸囨爣
+export function getSettingIndex(nodeId) {
+    return request({
+        url: '/basicsetting/modelnode/energyIndex/' + nodeId,
+        method: 'get'
+    })
+}
+
+// 鏌ヨ璁¢噺鍣ㄥ叿妗f缁存姢鍒楄〃
+export function listImplement(query) {
+    return request({
+        url: '/meter/implement/list',
+        method: 'get',
+        params: query
+    })
+}
+
+//淇濆瓨涓氬姟涓嬬殑缁熻鎸囨爣
+export function setNodeToIndex(nodeId, indexIds, indexType) {
+    return request({
+        url: '/basicsetting/modelnode/energyIndex/' + nodeId + '/' + indexType,
+        method: 'post',
+        data: indexIds
+    })
+}
+
+//鍒犻櫎鎸囨爣
+export function delIndex(nodeId, indexId) {
+    return request({
+        url: '/basicsetting/modelnode/energyIndex/' + nodeId,
+        method: 'delete',
+        data:  indexId 
+    })
+}
+
+
+//閲囬泦鎸囨爣鍒楄〃
+export function collectList(nodeId, query) {
+    return request({
+        url: '/basicsetting/modelnode/energyIndex/meterImplement/' + nodeId,
+        method: 'get',
+        params: query
+    })
+}
+
diff --git a/zhitan-vue/src/api/modelConfiguration/collectIndicator.js b/zhitan-vue/src/api/modelConfiguration/collectIndicator.js
new file mode 100644
index 0000000..4b70788
--- /dev/null
+++ b/zhitan-vue/src/api/modelConfiguration/collectIndicator.js
@@ -0,0 +1,45 @@
+import request from '@/utils/request'
+
+// 鑾峰彇鍒楄〃
+export function listTemplate(params) {
+  return request({
+    url: '/system/template/list',
+    method: 'get',
+    params
+  })
+}
+
+// 鏂板
+export function addTemplate(data) {
+  return request({
+    url: '/system/template',
+    method: 'post',
+    data
+  })
+}
+
+// 鏌ヨ鎯�
+export function getTempById(params) {
+  return request({
+    url: `/system/template/${params.id}`,
+    method: 'get',
+    params
+  })
+}
+
+// 缂栬緫
+export function putTemplate(data) {
+  return request({
+    url: '/system/template',
+    method: 'put',
+    data
+  })
+}
+
+//鍒犻櫎
+export function delTempById(id) {
+  return request({
+    url: '/system/template/' + id,
+    method: 'delete',
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/modelConfiguration/energyType.js b/zhitan-vue/src/api/modelConfiguration/energyType.js
new file mode 100644
index 0000000..aa47699
--- /dev/null
+++ b/zhitan-vue/src/api/modelConfiguration/energyType.js
@@ -0,0 +1,49 @@
+import request from '@/utils/request'
+import { parseStrEmpty } from "@/utils/ruoyi";
+
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鍒嗛〉鍒楄〃
+export function listEnergyType(query) {
+  return request({
+    url: '/enerInfoManage/energy/page',
+    method: 'get',
+    params: query
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鍏ㄩ儴鍒楄〃
+export function listEnergyTypeList(query) {
+  return request({
+    url: '/enerInfoManage/energy/list',
+    method: 'get',
+    params: query
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鏂板
+export function addEnergyType(data) {
+  return request({
+    url: '/enerInfoManage/energy',
+    method: 'post',
+    data: data
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鏌ョ湅
+export function getEnergyType(id) {
+  return request({
+    url: '/enerInfoManage/energy/' + parseStrEmpty(id),
+    method: 'get'
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-缂栬緫
+export function updateEnergyType(data) {
+  return request({
+    url: '/enerInfoManage/energy',
+    method: 'put',
+    data: data
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鍒犻櫎
+export function delEnergyType(id) {
+  return request({
+    url: '/enerInfoManage/energy/' + id,
+    method: 'delete'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/modelConfiguration/energyVarieties.js b/zhitan-vue/src/api/modelConfiguration/energyVarieties.js
new file mode 100644
index 0000000..734484e
--- /dev/null
+++ b/zhitan-vue/src/api/modelConfiguration/energyVarieties.js
@@ -0,0 +1,49 @@
+import request from '@/utils/request'
+import { parseStrEmpty } from "@/utils/ruoyi";
+
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺绠$悊-鍒嗛〉鍒楄〃
+export function listEnergyVarieties(query) {
+  return request({
+    url: '/enerInfoManage/enerclass/page',
+    method: 'get',
+    params: query
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺绠$悊-鍏ㄩ儴鍒楄〃
+export function listEnergyVarietiesList(query) {
+  return request({
+    url: '/enerInfoManage/enerclass/list',
+    method: 'get',
+    params: query
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺绠$悊-鏂板
+export function addEnergyVarieties(data) {
+  return request({
+    url: '/enerInfoManage/enerclass',
+    method: 'post',
+    data: data
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺绠$悊-鏌ョ湅
+export function getEnergyVarieties(id) {
+  return request({
+    url: '/enerInfoManage/enerclass/' + parseStrEmpty(id),
+    method: 'get'
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺绠$悊-缂栬緫
+export function updateEnergyVarieties(data) {
+  return request({
+    url: '/enerInfoManage/enerclass',
+    method: 'put',
+    data: data
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺绠$悊-鍒犻櫎
+export function delEnergyVarieties(id) {
+  return request({
+    url: '/enerInfoManage/enerclass/' + id,
+    method: 'delete'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/modelConfiguration/indexWarehouse.js b/zhitan-vue/src/api/modelConfiguration/indexWarehouse.js
new file mode 100644
index 0000000..b8339df
--- /dev/null
+++ b/zhitan-vue/src/api/modelConfiguration/indexWarehouse.js
@@ -0,0 +1,176 @@
+import request from '@/utils/request'
+
+// 鑾峰彇鏍戝垪琛�
+export function treeList(params) {
+    return request({
+        url: '/basicsetting/modelnode/treelist',
+        method: 'get',
+        params
+    })
+}
+
+// 鐐瑰嚮鏍戣妭鐐�
+export function updateModelNodeOrder(param) {
+    return request({
+        url: '/basicsetting/modelnode/order',
+        method: 'put',
+        data: param
+    })
+}
+
+
+//閲嶇偣璁惧
+export function getPointFacility() {
+    return request({
+        url: '/statisticalData/comprehensiveStatistics/getPointFacility',
+        method: 'get'
+    })
+}
+
+// 鏌ヨ鎸囨爣淇℃伅鍒楄〃
+export function listEnergyindex(query) {
+    return request({
+        url: '/basicsetting/energyindex/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 鏂板鎸囨爣淇℃伅
+export function addEnergyindex(nodeId, data) {
+    return request({
+        url: '/basicsetting/energyindex/' + nodeId,
+        method: 'post',
+        data: data
+    })
+}
+
+// 淇敼鎸囨爣淇℃伅
+export function updateEnergyindex(data) {
+    return request({
+        url: '/basicsetting/energyindex',
+        method: 'put',
+        data: data
+    })
+}
+
+// 鏌ヨ鎸囨爣淇℃伅璇︾粏
+export function getEnergyindex(indexId) {
+    return request({
+        url: '/basicsetting/energyindex/' + indexId,
+        method: 'get'
+    })
+}
+
+// 鍒犻櫎鎸囨爣淇℃伅
+export function delEnergyindex(nodeId, indexId) {
+    return request({
+        url: '/basicsetting/energyindex/' + nodeId + '/' + indexId,
+        method: 'delete'
+    })
+}
+
+// 鏌ヨ妯″瀷鑺傜偣璇︾粏
+export function getModelNode(nodeId) {
+    return request({
+        url: '/basicsetting/modelnode/' + nodeId,
+        method: 'get'
+    })
+}
+
+
+// 鏂板妯″瀷鑺傜偣
+export function addModelNode(data) {
+    return request({
+        url: '/basicsetting/modelnode',
+        method: 'post',
+        data: data
+    })
+}
+
+// 淇敼妯″瀷鑺傜偣
+export function updateModelNode(data) {
+    return request({
+        url: '/basicsetting/modelnode',
+        method: 'put',
+        data: data
+    })
+}
+
+// 鍒犻櫎妯″瀷鑺傜偣
+export function delModelNode(nodeId) {
+    return request({
+        url: '/basicsetting/modelnode/' + nodeId,
+        method: 'delete'
+    })
+}
+
+export function hasEnergyIndex(nodeId) {
+    return request({
+        url: '/basicsetting/modelnode/hasEnergyIndex',
+        method: 'get',
+        params: { nodeId: nodeId }
+    })
+}
+
+export function parseFormula(data) {
+    return request({
+        url: '/basicsetting/indexStorage/parseFormula',
+        method: 'post',
+        data: data
+    })
+}
+
+// 鏌ヨ璁$畻鍑芥暟鍒楄〃
+export function listFunction(query) {
+    return request({
+        url: '/basicsetting/function/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 鍒犻櫎璁$畻鍑芥暟
+export function delFunction(info) {
+    return request({
+        url: '/basicsetting/function/' + info,
+        method: 'delete'
+    })
+}
+
+
+export function getNodeIndex(nodeId) {
+    return request({
+        url: '/basicsetting/energyindex/includeChildrenNode/' + nodeId,
+        method: 'GET'
+    })
+}
+
+
+export function includeChildrenNode(query) {
+    return request({
+        url: '/basicsetting/energyindex/getIndexByCode',
+        method: 'GET',
+        params: query
+    })
+}
+
+
+
+
+
+
+export function getIndexStorage(indexId) {
+    return request({
+        url: '/basicsetting/indexStorage/' + indexId,
+        method: 'GET'
+    })
+}
+
+export function saveIndexStorage(indexId, data) {
+    return request({
+        url: '/basicsetting/indexStorage/' + indexId,
+        method: 'post',
+        data: data
+    });
+}
diff --git a/zhitan-vue/src/api/modelConfiguration/setPeakValley.js b/zhitan-vue/src/api/modelConfiguration/setPeakValley.js
new file mode 100644
index 0000000..1380ada
--- /dev/null
+++ b/zhitan-vue/src/api/modelConfiguration/setPeakValley.js
@@ -0,0 +1,56 @@
+import request from '@/utils/request'
+
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鍒楄〃
+export function listPeakValleyConfiguration(query) {
+  return request({
+    url: '/electricitypricedate/list',
+    method: 'get',
+    params: query
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏂板
+export function addPeakValleyConfiguration(data) {
+  return request({
+    url: '/electricitypricedate',
+    method: 'post',
+    data: data
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏌ョ湅
+export function getPeakValleyConfiguration(id) {
+  return request({
+    url: '/electricitypricedate/' + id,
+    method: 'get',
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-缂栬緫
+export function updatePeakValleyConfiguration(data) {
+  return request({
+    url: '/electricitypricedate',
+    method: 'put',
+    data: data
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鍒犻櫎
+export function delPeakValleyConfiguration(id) {
+  return request({
+    url: '/electricitypricedate/' + id,
+    method: 'delete'
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏃舵鐢典环閰嶇疆-鍒楄〃
+export function listPeakValleyConfigurationChild(query) {
+  return request({
+    url: '/electricityprice/list',
+    method: 'get',
+    params: query
+  })
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏃舵鐢典环閰嶇疆-淇濆瓨
+export function updatePeakValleyConfigurationChild(data) {
+  return request({
+    url: '/electricityprice/save',
+    method: 'put',
+    data: data
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/monitor/cache.js b/zhitan-vue/src/api/monitor/cache.js
new file mode 100644
index 0000000..72c5f6a
--- /dev/null
+++ b/zhitan-vue/src/api/monitor/cache.js
@@ -0,0 +1,57 @@
+import request from '@/utils/request'
+
+// 鏌ヨ缂撳瓨璇︾粏
+export function getCache() {
+  return request({
+    url: '/monitor/cache',
+    method: 'get'
+  })
+}
+
+// 鏌ヨ缂撳瓨鍚嶇О鍒楄〃
+export function listCacheName() {
+  return request({
+    url: '/monitor/cache/getNames',
+    method: 'get'
+  })
+}
+
+// 鏌ヨ缂撳瓨閿悕鍒楄〃
+export function listCacheKey(cacheName) {
+  return request({
+    url: '/monitor/cache/getKeys/' + cacheName,
+    method: 'get'
+  })
+}
+
+// 鏌ヨ缂撳瓨鍐呭
+export function getCacheValue(cacheName, cacheKey) {
+  return request({
+    url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,
+    method: 'get'
+  })
+}
+
+// 娓呯悊鎸囧畾鍚嶇О缂撳瓨
+export function clearCacheName(cacheName) {
+  return request({
+    url: '/monitor/cache/clearCacheName/' + cacheName,
+    method: 'delete'
+  })
+}
+
+// 娓呯悊鎸囧畾閿悕缂撳瓨
+export function clearCacheKey(cacheKey) {
+  return request({
+    url: '/monitor/cache/clearCacheKey/' + cacheKey,
+    method: 'delete'
+  })
+}
+
+// 娓呯悊鍏ㄩ儴缂撳瓨
+export function clearCacheAll() {
+  return request({
+    url: '/monitor/cache/clearCacheAll',
+    method: 'delete'
+  })
+}
diff --git a/zhitan-vue/src/api/monitor/job.js b/zhitan-vue/src/api/monitor/job.js
new file mode 100644
index 0000000..3815569
--- /dev/null
+++ b/zhitan-vue/src/api/monitor/job.js
@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 鏌ヨ瀹氭椂浠诲姟璋冨害鍒楄〃
+export function listJob(query) {
+  return request({
+    url: '/monitor/job/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ瀹氭椂浠诲姟璋冨害璇︾粏
+export function getJob(jobId) {
+  return request({
+    url: '/monitor/job/' + jobId,
+    method: 'get'
+  })
+}
+
+// 鏂板瀹氭椂浠诲姟璋冨害
+export function addJob(data) {
+  return request({
+    url: '/monitor/job',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼瀹氭椂浠诲姟璋冨害
+export function updateJob(data) {
+  return request({
+    url: '/monitor/job',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎瀹氭椂浠诲姟璋冨害
+export function delJob(jobId) {
+  return request({
+    url: '/monitor/job/' + jobId,
+    method: 'delete'
+  })
+}
+
+// 浠诲姟鐘舵�佷慨鏀�
+export function changeJobStatus(jobId, status) {
+  const data = {
+    jobId,
+    status
+  }
+  return request({
+    url: '/monitor/job/changeStatus',
+    method: 'put',
+    data: data
+  })
+}
+
+
+// 瀹氭椂浠诲姟绔嬪嵆鎵ц涓�娆�
+export function runJob(jobId, jobGroup) {
+  const data = {
+    jobId,
+    jobGroup
+  }
+  return request({
+    url: '/monitor/job/run',
+    method: 'put',
+    data: data
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/monitor/jobLog.js b/zhitan-vue/src/api/monitor/jobLog.js
new file mode 100644
index 0000000..6e0be61
--- /dev/null
+++ b/zhitan-vue/src/api/monitor/jobLog.js
@@ -0,0 +1,26 @@
+import request from '@/utils/request'
+
+// 鏌ヨ璋冨害鏃ュ織鍒楄〃
+export function listJobLog(query) {
+  return request({
+    url: '/monitor/jobLog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鍒犻櫎璋冨害鏃ュ織
+export function delJobLog(jobLogId) {
+  return request({
+    url: '/monitor/jobLog/' + jobLogId,
+    method: 'delete'
+  })
+}
+
+// 娓呯┖璋冨害鏃ュ織
+export function cleanJobLog() {
+  return request({
+    url: '/monitor/jobLog/clean',
+    method: 'delete'
+  })
+}
diff --git a/zhitan-vue/src/api/monitor/logininfor.js b/zhitan-vue/src/api/monitor/logininfor.js
new file mode 100644
index 0000000..4d112b7
--- /dev/null
+++ b/zhitan-vue/src/api/monitor/logininfor.js
@@ -0,0 +1,34 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鐧诲綍鏃ュ織鍒楄〃
+export function list(query) {
+  return request({
+    url: '/monitor/logininfor/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鍒犻櫎鐧诲綍鏃ュ織
+export function delLogininfor(infoId) {
+  return request({
+    url: '/monitor/logininfor/' + infoId,
+    method: 'delete'
+  })
+}
+
+// 瑙i攣鐢ㄦ埛鐧诲綍鐘舵��
+export function unlockLogininfor(userName) {
+  return request({
+    url: '/monitor/logininfor/unlock/' + userName,
+    method: 'get'
+  })
+}
+
+// 娓呯┖鐧诲綍鏃ュ織
+export function cleanLogininfor() {
+  return request({
+    url: '/monitor/logininfor/clean',
+    method: 'delete'
+  })
+}
diff --git a/zhitan-vue/src/api/monitor/online.js b/zhitan-vue/src/api/monitor/online.js
new file mode 100644
index 0000000..bd22137
--- /dev/null
+++ b/zhitan-vue/src/api/monitor/online.js
@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鍦ㄧ嚎鐢ㄦ埛鍒楄〃
+export function list(query) {
+  return request({
+    url: '/monitor/online/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 寮洪��鐢ㄦ埛
+export function forceLogout(tokenId) {
+  return request({
+    url: '/monitor/online/' + tokenId,
+    method: 'delete'
+  })
+}
diff --git a/zhitan-vue/src/api/monitor/operlog.js b/zhitan-vue/src/api/monitor/operlog.js
new file mode 100644
index 0000000..a04bca8
--- /dev/null
+++ b/zhitan-vue/src/api/monitor/operlog.js
@@ -0,0 +1,26 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鎿嶄綔鏃ュ織鍒楄〃
+export function list(query) {
+  return request({
+    url: '/monitor/operlog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鍒犻櫎鎿嶄綔鏃ュ織
+export function delOperlog(operId) {
+  return request({
+    url: '/monitor/operlog/' + operId,
+    method: 'delete'
+  })
+}
+
+// 娓呯┖鎿嶄綔鏃ュ織
+export function cleanOperlog() {
+  return request({
+    url: '/monitor/operlog/clean',
+    method: 'delete'
+  })
+}
diff --git a/zhitan-vue/src/api/monitor/server.js b/zhitan-vue/src/api/monitor/server.js
new file mode 100644
index 0000000..e1f9ca2
--- /dev/null
+++ b/zhitan-vue/src/api/monitor/server.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+// 鑾峰彇鏈嶅姟淇℃伅
+export function getServer() {
+  return request({
+    url: '/monitor/server',
+    method: 'get'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/peakValley/period.js b/zhitan-vue/src/api/peakValley/period.js
new file mode 100644
index 0000000..9219b4c
--- /dev/null
+++ b/zhitan-vue/src/api/peakValley/period.js
@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 宄板钩璋疯兘鑰楀垎鏋�-宄板钩璋锋椂娈电粺璁�-鍒楄〃
+export function listPeriod(query) {
+  return request({
+    url: '/peakValley/segmentAnalysis/day',
+    method: 'get',
+    params: query
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/peakValley/timeSharing.js b/zhitan-vue/src/api/peakValley/timeSharing.js
new file mode 100644
index 0000000..99910f6
--- /dev/null
+++ b/zhitan-vue/src/api/peakValley/timeSharing.js
@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 宄板钩璋疯兘鑰楀垎鏋�-宄板钩璋锋椂娈电粺璁�-鍒楄〃
+export function listTimeSharing(query) {
+  return request({
+    url: '/peakValley/segmentAnalysis/hour',
+    method: 'get',
+    params: query
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/realTimeMonitor/realTimeMonitor.js b/zhitan-vue/src/api/realTimeMonitor/realTimeMonitor.js
new file mode 100644
index 0000000..d029cd4
--- /dev/null
+++ b/zhitan-vue/src/api/realTimeMonitor/realTimeMonitor.js
@@ -0,0 +1,54 @@
+import request from '@/utils/request'
+
+// 鑳芥簮瀹炴椂鐩戞帶-鑳芥簮瀹炴椂鐩戞帶-鍒楄〃
+export function listEnergyRealTimeMonitor(query) {
+  return request({
+    url: '/rtdb/realtimeTrend/list',
+    method: 'get',
+    params: query
+  })
+}
+export function getEnergyRealTimeMonitor(query) {
+  return request({
+    url: '/rtdb/realtimeTrend/chartByDay',
+    method: 'get',
+    params: query
+  })
+}
+
+//鑾峰彇鎷撴墤鍥�
+export function getConfigure(nodeId) {
+  return request({
+    url: '/equipmentFile/configure/' + nodeId,
+    method: 'get'
+  })
+}
+
+
+// 淇濆瓨鎷撴墤鍥惧浘鐗�
+export function updateEquipmentfile(data) {
+  return request({
+    url: '/equipmentFile',
+    method: 'put',
+    data: data
+  })
+}
+
+
+export function getAllCollectTag(data) {
+  return request({
+    url: '/basicsetting/energyindex/filter',
+    method: 'get',
+    params: data
+  })
+}
+
+
+//淇濆瓨鎷撴墤鍥�
+export function saveSetting(nodeId, data) {
+  return request({
+    url: '/equipmentFile/setting/' + nodeId,
+    method: 'put',
+    data: data
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/system/config.js b/zhitan-vue/src/api/system/config.js
new file mode 100644
index 0000000..a404d82
--- /dev/null
+++ b/zhitan-vue/src/api/system/config.js
@@ -0,0 +1,60 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鍙傛暟鍒楄〃
+export function listConfig(query) {
+  return request({
+    url: '/system/config/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ鍙傛暟璇︾粏
+export function getConfig(configId) {
+  return request({
+    url: '/system/config/' + configId,
+    method: 'get'
+  })
+}
+
+// 鏍规嵁鍙傛暟閿悕鏌ヨ鍙傛暟鍊�
+export function getConfigKey(configKey) {
+  return request({
+    url: '/system/config/configKey/' + configKey,
+    method: 'get'
+  })
+}
+
+// 鏂板鍙傛暟閰嶇疆
+export function addConfig(data) {
+  return request({
+    url: '/system/config',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼鍙傛暟閰嶇疆
+export function updateConfig(data) {
+  return request({
+    url: '/system/config',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎鍙傛暟閰嶇疆
+export function delConfig(configId) {
+  return request({
+    url: '/system/config/' + configId,
+    method: 'delete'
+  })
+}
+
+// 鍒锋柊鍙傛暟缂撳瓨
+export function refreshCache() {
+  return request({
+    url: '/system/config/refreshCache',
+    method: 'delete'
+  })
+}
diff --git a/zhitan-vue/src/api/system/dept.js b/zhitan-vue/src/api/system/dept.js
new file mode 100644
index 0000000..fc943cd
--- /dev/null
+++ b/zhitan-vue/src/api/system/dept.js
@@ -0,0 +1,52 @@
+import request from '@/utils/request'
+
+// 鏌ヨ閮ㄩ棬鍒楄〃
+export function listDept(query) {
+  return request({
+    url: '/system/dept/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ閮ㄩ棬鍒楄〃锛堟帓闄よ妭鐐癸級
+export function listDeptExcludeChild(deptId) {
+  return request({
+    url: '/system/dept/list/exclude/' + deptId,
+    method: 'get'
+  })
+}
+
+// 鏌ヨ閮ㄩ棬璇︾粏
+export function getDept(deptId) {
+  return request({
+    url: '/system/dept/' + deptId,
+    method: 'get'
+  })
+}
+
+// 鏂板閮ㄩ棬
+export function addDept(data) {
+  return request({
+    url: '/system/dept',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼閮ㄩ棬
+export function updateDept(data) {
+  return request({
+    url: '/system/dept',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎閮ㄩ棬
+export function delDept(deptId) {
+  return request({
+    url: '/system/dept/' + deptId,
+    method: 'delete'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/system/dict/data.js b/zhitan-vue/src/api/system/dict/data.js
new file mode 100644
index 0000000..6c9eb79
--- /dev/null
+++ b/zhitan-vue/src/api/system/dict/data.js
@@ -0,0 +1,52 @@
+import request from '@/utils/request'
+
+// 鏌ヨ瀛楀吀鏁版嵁鍒楄〃
+export function listData(query) {
+  return request({
+    url: '/system/dict/data/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ瀛楀吀鏁版嵁璇︾粏
+export function getData(dictCode) {
+  return request({
+    url: '/system/dict/data/' + dictCode,
+    method: 'get'
+  })
+}
+
+// 鏍规嵁瀛楀吀绫诲瀷鏌ヨ瀛楀吀鏁版嵁淇℃伅
+export function getDicts(dictType) {
+  return request({
+    url: '/system/dict/data/type/' + dictType,
+    method: 'get'
+  })
+}
+
+// 鏂板瀛楀吀鏁版嵁
+export function addData(data) {
+  return request({
+    url: '/system/dict/data',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼瀛楀吀鏁版嵁
+export function updateData(data) {
+  return request({
+    url: '/system/dict/data',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎瀛楀吀鏁版嵁
+export function delData(dictCode) {
+  return request({
+    url: '/system/dict/data/' + dictCode,
+    method: 'delete'
+  })
+}
diff --git a/zhitan-vue/src/api/system/dict/type.js b/zhitan-vue/src/api/system/dict/type.js
new file mode 100644
index 0000000..a0254ba
--- /dev/null
+++ b/zhitan-vue/src/api/system/dict/type.js
@@ -0,0 +1,60 @@
+import request from '@/utils/request'
+
+// 鏌ヨ瀛楀吀绫诲瀷鍒楄〃
+export function listType(query) {
+  return request({
+    url: '/system/dict/type/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ瀛楀吀绫诲瀷璇︾粏
+export function getType(dictId) {
+  return request({
+    url: '/system/dict/type/' + dictId,
+    method: 'get'
+  })
+}
+
+// 鏂板瀛楀吀绫诲瀷
+export function addType(data) {
+  return request({
+    url: '/system/dict/type',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼瀛楀吀绫诲瀷
+export function updateType(data) {
+  return request({
+    url: '/system/dict/type',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎瀛楀吀绫诲瀷
+export function delType(dictId) {
+  return request({
+    url: '/system/dict/type/' + dictId,
+    method: 'delete'
+  })
+}
+
+// 鍒锋柊瀛楀吀缂撳瓨
+export function refreshCache() {
+  return request({
+    url: '/system/dict/type/refreshCache',
+    method: 'delete'
+  })
+}
+
+// 鑾峰彇瀛楀吀閫夋嫨妗嗗垪琛�
+export function optionselect() {
+  return request({
+    url: '/system/dict/type/optionselect',
+    method: 'get'
+  })
+}
diff --git a/zhitan-vue/src/api/system/menu.js b/zhitan-vue/src/api/system/menu.js
new file mode 100644
index 0000000..f6415c6
--- /dev/null
+++ b/zhitan-vue/src/api/system/menu.js
@@ -0,0 +1,60 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鑿滃崟鍒楄〃
+export function listMenu(query) {
+  return request({
+    url: '/system/menu/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ鑿滃崟璇︾粏
+export function getMenu(menuId) {
+  return request({
+    url: '/system/menu/' + menuId,
+    method: 'get'
+  })
+}
+
+// 鏌ヨ鑿滃崟涓嬫媺鏍戠粨鏋�
+export function treeselect() {
+  return request({
+    url: '/system/menu/treeselect',
+    method: 'get'
+  })
+}
+
+// 鏍规嵁瑙掕壊ID鏌ヨ鑿滃崟涓嬫媺鏍戠粨鏋�
+export function roleMenuTreeselect(roleId) {
+  return request({
+    url: '/system/menu/roleMenuTreeselect/' + roleId,
+    method: 'get'
+  })
+}
+
+// 鏂板鑿滃崟
+export function addMenu(data) {
+  return request({
+    url: '/system/menu',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼鑿滃崟
+export function updateMenu(data) {
+  return request({
+    url: '/system/menu',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎鑿滃崟
+export function delMenu(menuId) {
+  return request({
+    url: '/system/menu/' + menuId,
+    method: 'delete'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/system/name.js b/zhitan-vue/src/api/system/name.js
new file mode 100644
index 0000000..0bb6d18
--- /dev/null
+++ b/zhitan-vue/src/api/system/name.js
@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+// 鑾峰彇绯荤粺鍚嶇О淇℃伅
+export function systemName(query) {
+    return request({
+        url: '/system/nameconfig',
+        method: 'get',
+    })
+}
+
+// 淇濆瓨绯荤粺鍚嶇О璁剧疆
+export function systemNameSave(data) {
+    return request({
+        url: '/system/nameconfig',
+        method: 'post',
+        data: data
+    })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/system/notice.js b/zhitan-vue/src/api/system/notice.js
new file mode 100644
index 0000000..c274ea5
--- /dev/null
+++ b/zhitan-vue/src/api/system/notice.js
@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鍏憡鍒楄〃
+export function listNotice(query) {
+  return request({
+    url: '/system/notice/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ鍏憡璇︾粏
+export function getNotice(noticeId) {
+  return request({
+    url: '/system/notice/' + noticeId,
+    method: 'get'
+  })
+}
+
+// 鏂板鍏憡
+export function addNotice(data) {
+  return request({
+    url: '/system/notice',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼鍏憡
+export function updateNotice(data) {
+  return request({
+    url: '/system/notice',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎鍏憡
+export function delNotice(noticeId) {
+  return request({
+    url: '/system/notice/' + noticeId,
+    method: 'delete'
+  })
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/api/system/post.js b/zhitan-vue/src/api/system/post.js
new file mode 100644
index 0000000..1a8e9ca
--- /dev/null
+++ b/zhitan-vue/src/api/system/post.js
@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 鏌ヨ宀椾綅鍒楄〃
+export function listPost(query) {
+  return request({
+    url: '/system/post/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ宀椾綅璇︾粏
+export function getPost(postId) {
+  return request({
+    url: '/system/post/' + postId,
+    method: 'get'
+  })
+}
+
+// 鏂板宀椾綅
+export function addPost(data) {
+  return request({
+    url: '/system/post',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼宀椾綅
+export function updatePost(data) {
+  return request({
+    url: '/system/post',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎宀椾綅
+export function delPost(postId) {
+  return request({
+    url: '/system/post/' + postId,
+    method: 'delete'
+  })
+}
diff --git a/zhitan-vue/src/api/system/role.js b/zhitan-vue/src/api/system/role.js
new file mode 100644
index 0000000..f13e6f4
--- /dev/null
+++ b/zhitan-vue/src/api/system/role.js
@@ -0,0 +1,119 @@
+import request from '@/utils/request'
+
+// 鏌ヨ瑙掕壊鍒楄〃
+export function listRole(query) {
+  return request({
+    url: '/system/role/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ瑙掕壊璇︾粏
+export function getRole(roleId) {
+  return request({
+    url: '/system/role/' + roleId,
+    method: 'get'
+  })
+}
+
+// 鏂板瑙掕壊
+export function addRole(data) {
+  return request({
+    url: '/system/role',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼瑙掕壊
+export function updateRole(data) {
+  return request({
+    url: '/system/role',
+    method: 'put',
+    data: data
+  })
+}
+
+// 瑙掕壊鏁版嵁鏉冮檺
+export function dataScope(data) {
+  return request({
+    url: '/system/role/dataScope',
+    method: 'put',
+    data: data
+  })
+}
+
+// 瑙掕壊鐘舵�佷慨鏀�
+export function changeRoleStatus(roleId, status) {
+  const data = {
+    roleId,
+    status
+  }
+  return request({
+    url: '/system/role/changeStatus',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎瑙掕壊
+export function delRole(roleId) {
+  return request({
+    url: '/system/role/' + roleId,
+    method: 'delete'
+  })
+}
+
+// 鏌ヨ瑙掕壊宸叉巿鏉冪敤鎴峰垪琛�
+export function allocatedUserList(query) {
+  return request({
+    url: '/system/role/authUser/allocatedList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ瑙掕壊鏈巿鏉冪敤鎴峰垪琛�
+export function unallocatedUserList(query) {
+  return request({
+    url: '/system/role/authUser/unallocatedList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鍙栨秷鐢ㄦ埛鎺堟潈瑙掕壊
+export function authUserCancel(data) {
+  return request({
+    url: '/system/role/authUser/cancel',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鎵归噺鍙栨秷鐢ㄦ埛鎺堟潈瑙掕壊
+export function authUserCancelAll(data) {
+  return request({
+    url: '/system/role/authUser/cancelAll',
+    method: 'put',
+    params: data
+  })
+}
+
+// 鎺堟潈鐢ㄦ埛閫夋嫨
+export function authUserSelectAll(data) {
+  return request({
+    url: '/system/role/authUser/selectAll',
+    method: 'put',
+    params: data
+  })
+}
+
+// 鏍规嵁瑙掕壊ID鏌ヨ閮ㄩ棬鏍戠粨鏋�
+export function deptTreeSelect(roleId) {
+  return request({
+    url: '/system/role/deptTree/' + roleId,
+    method: 'get'
+  })
+}
diff --git a/zhitan-vue/src/api/system/user.js b/zhitan-vue/src/api/system/user.js
new file mode 100644
index 0000000..f2f76ef
--- /dev/null
+++ b/zhitan-vue/src/api/system/user.js
@@ -0,0 +1,135 @@
+import request from '@/utils/request'
+import { parseStrEmpty } from "@/utils/ruoyi";
+
+// 鏌ヨ鐢ㄦ埛鍒楄〃
+export function listUser(query) {
+  return request({
+    url: '/system/user/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ鐢ㄦ埛璇︾粏
+export function getUser(userId) {
+  return request({
+    url: '/system/user/' + parseStrEmpty(userId),
+    method: 'get'
+  })
+}
+
+// 鏂板鐢ㄦ埛
+export function addUser(data) {
+  return request({
+    url: '/system/user',
+    method: 'post',
+    data: data
+  })
+}
+
+// 淇敼鐢ㄦ埛
+export function updateUser(data) {
+  return request({
+    url: '/system/user',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鍒犻櫎鐢ㄦ埛
+export function delUser(userId) {
+  return request({
+    url: '/system/user/' + userId,
+    method: 'delete'
+  })
+}
+
+// 鐢ㄦ埛瀵嗙爜閲嶇疆
+export function resetUserPwd(userId, password) {
+  const data = {
+    userId,
+    password
+  }
+  return request({
+    url: '/system/user/resetPwd',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鐢ㄦ埛鐘舵�佷慨鏀�
+export function changeUserStatus(userId, status) {
+  const data = {
+    userId,
+    status
+  }
+  return request({
+    url: '/system/user/changeStatus',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鏌ヨ鐢ㄦ埛涓汉淇℃伅
+export function getUserProfile() {
+  return request({
+    url: '/system/user/profile',
+    method: 'get'
+  })
+}
+
+// 淇敼鐢ㄦ埛涓汉淇℃伅
+export function updateUserProfile(data) {
+  return request({
+    url: '/system/user/profile',
+    method: 'put',
+    data: data
+  })
+}
+
+// 鐢ㄦ埛瀵嗙爜閲嶇疆
+export function updateUserPwd(oldPassword, newPassword) {
+  const data = {
+    oldPassword,
+    newPassword
+  }
+  return request({
+    url: '/system/user/profile/updatePwd',
+    method: 'put',
+    params: data
+  })
+}
+
+// 鐢ㄦ埛澶村儚涓婁紶
+export function uploadAvatar(data) {
+  return request({
+    url: '/system/user/profile/avatar',
+    method: 'post',
+    data: data
+  })
+}
+
+// 鏌ヨ鎺堟潈瑙掕壊
+export function getAuthRole(userId) {
+  return request({
+    url: '/system/user/authRole/' + userId,
+    method: 'get'
+  })
+}
+
+// 淇濆瓨鎺堟潈瑙掕壊
+export function updateAuthRole(data) {
+  return request({
+    url: '/system/user/authRole',
+    method: 'put',
+    params: data
+  })
+}
+
+// 鏌ヨ閮ㄩ棬涓嬫媺鏍戠粨鏋�
+export function deptTreeSelect() {
+  return request({
+    url: '/system/user/deptTree',
+    method: 'get'
+  })
+}
diff --git a/zhitan-vue/src/api/tool/gen.js b/zhitan-vue/src/api/tool/gen.js
new file mode 100644
index 0000000..2075677
--- /dev/null
+++ b/zhitan-vue/src/api/tool/gen.js
@@ -0,0 +1,85 @@
+import request from '@/utils/request'
+
+// 鏌ヨ鐢熸垚琛ㄦ暟鎹�
+export function listTable(query) {
+  return request({
+    url: '/tool/gen/list',
+    method: 'get',
+    params: query
+  })
+}
+// 鏌ヨdb鏁版嵁搴撳垪琛�
+export function listDbTable(query) {
+  return request({
+    url: '/tool/gen/db/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 鏌ヨ琛ㄨ缁嗕俊鎭�
+export function getGenTable(tableId) {
+  return request({
+    url: '/tool/gen/' + tableId,
+    method: 'get'
+  })
+}
+
+// 淇敼浠g爜鐢熸垚淇℃伅
+export function updateGenTable(data) {
+  return request({
+    url: '/tool/gen',
+    method: 'put',
+    data: data
+  })
+}
+
+// 瀵煎叆琛�
+export function importTable(data) {
+  return request({
+    url: '/tool/gen/importTable',
+    method: 'post',
+    params: data
+  })
+}
+
+// 鍒涘缓琛�
+export function createTable(data) {
+  return request({
+    url: '/tool/gen/createTable',
+    method: 'post',
+    params: data
+  })
+}
+
+// 棰勮鐢熸垚浠g爜
+export function previewTable(tableId) {
+  return request({
+    url: '/tool/gen/preview/' + tableId,
+    method: 'get'
+  })
+}
+
+// 鍒犻櫎琛ㄦ暟鎹�
+export function delTable(tableId) {
+  return request({
+    url: '/tool/gen/' + tableId,
+    method: 'delete'
+  })
+}
+
+// 鐢熸垚浠g爜锛堣嚜瀹氫箟璺緞锛�
+export function genCode(tableName) {
+  return request({
+    url: '/tool/gen/genCode/' + tableName,
+    method: 'get'
+  })
+}
+
+// 鍚屾鏁版嵁搴�
+export function synchDb(tableName) {
+  return request({
+    url: '/tool/gen/synchDb/' + tableName,
+    method: 'get'
+  })
+}
diff --git a/zhitan-vue/src/assets/401_images/401.gif b/zhitan-vue/src/assets/401_images/401.gif
new file mode 100644
index 0000000..cd6e0d9
--- /dev/null
+++ b/zhitan-vue/src/assets/401_images/401.gif
Binary files differ
diff --git a/zhitan-vue/src/assets/404_images/404.png b/zhitan-vue/src/assets/404_images/404.png
new file mode 100644
index 0000000..3d8e230
--- /dev/null
+++ b/zhitan-vue/src/assets/404_images/404.png
Binary files differ
diff --git a/zhitan-vue/src/assets/404_images/404_cloud.png b/zhitan-vue/src/assets/404_images/404_cloud.png
new file mode 100644
index 0000000..c6281d0
--- /dev/null
+++ b/zhitan-vue/src/assets/404_images/404_cloud.png
Binary files differ
diff --git a/zhitan-vue/src/assets/fonts/YouSheBiaoTiHei.scss b/zhitan-vue/src/assets/fonts/YouSheBiaoTiHei.scss
new file mode 100644
index 0000000..3f06084
--- /dev/null
+++ b/zhitan-vue/src/assets/fonts/YouSheBiaoTiHei.scss
@@ -0,0 +1,7 @@
+@font-face {
+    font-family: 'YouSheBiaoTiHei';
+    src: url('./YouSheBiaoTiHei.ttf');
+    Content-Type: 'application/x-font-ttf';
+    font-weight: normal;
+    font-style: normal;
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/fonts/YouSheBiaoTiHei.ttf b/zhitan-vue/src/assets/fonts/YouSheBiaoTiHei.ttf
new file mode 100644
index 0000000..3729151
--- /dev/null
+++ b/zhitan-vue/src/assets/fonts/YouSheBiaoTiHei.ttf
Binary files differ
diff --git a/zhitan-vue/src/assets/icons/svg/404.svg b/zhitan-vue/src/assets/icons/svg/404.svg
new file mode 100644
index 0000000..6df5019
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/404.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 0 1-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 0 1 2.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 0 0-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 0 0-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 0 1-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 0 1-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 0 1-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 0 0 .665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/bug.svg b/zhitan-vue/src/assets/icons/svg/bug.svg
new file mode 100644
index 0000000..05a150d
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/bug.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M127.88 73.143c0 1.412-.506 2.635-1.518 3.669-1.011 1.033-2.209 1.55-3.592 1.55h-17.887c0 9.296-1.783 17.178-5.35 23.645l16.609 17.044c1.011 1.034 1.517 2.257 1.517 3.67 0 1.412-.506 2.635-1.517 3.668-.958 1.033-2.155 1.55-3.593 1.55-1.438 0-2.635-.517-3.593-1.55l-15.811-16.063a15.49 15.49 0 0 1-1.196 1.06c-.532.434-1.65 1.208-3.353 2.322a50.104 50.104 0 0 1-5.192 2.974c-1.758.87-3.94 1.658-6.546 2.364-2.607.706-5.189 1.06-7.748 1.06V47.044H58.89v73.062c-2.716 0-5.417-.367-8.106-1.102-2.688-.734-5.003-1.631-6.945-2.692a66.769 66.769 0 0 1-5.268-3.179c-1.571-1.057-2.73-1.94-3.476-2.65L33.9 109.34l-14.611 16.877c-1.066 1.14-2.344 1.711-3.833 1.711-1.277 0-2.422-.434-3.434-1.304-1.012-.978-1.557-2.187-1.635-3.627-.079-1.44.333-2.705 1.236-3.794l16.129-18.51c-3.087-6.197-4.63-13.644-4.63-22.342H5.235c-1.383 0-2.58-.517-3.592-1.55S.125 74.545.125 73.132c0-1.412.506-2.635 1.518-3.668 1.012-1.034 2.21-1.55 3.592-1.55h17.887V43.939L9.308 29.833c-1.012-1.033-1.517-2.256-1.517-3.669 0-1.412.505-2.635 1.517-3.668 1.012-1.034 2.21-1.55 3.593-1.55s2.58.516 3.593 1.55l13.813 14.106h67.396l13.814-14.106c1.012-1.034 2.21-1.55 3.592-1.55 1.384 0 2.581.516 3.593 1.55 1.012 1.033 1.518 2.256 1.518 3.668 0 1.413-.506 2.636-1.518 3.67l-13.814 14.105v23.975h17.887c1.383 0 2.58.516 3.593 1.55 1.011 1.033 1.517 2.256 1.517 3.668l-.005.01zM89.552 26.175H38.448c0-7.23 2.489-13.386 7.466-18.469C50.892 2.623 56.92.082 64 .082c7.08 0 13.108 2.541 18.086 7.624 4.977 5.083 7.466 11.24 7.466 18.469z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/build.svg b/zhitan-vue/src/assets/icons/svg/build.svg
new file mode 100644
index 0000000..97c4688
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/build.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1568899741379" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2054" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M960 591.424V368.96c0-0.288 0.16-0.512 0.16-0.768S960 367.68 960 367.424V192a32 32 0 0 0-32-32H96a32 32 0 0 0-32 32v175.424c0 0.288-0.16 0.512-0.16 0.768s0.16 0.48 0.16 0.768v222.464c0 0.288-0.16 0.512-0.16 0.768s0.16 0.48 0.16 0.768V864a32 32 0 0 0 32 32h832a32 32 0 0 0 32-32v-271.04c0-0.288 0.16-0.512 0.16-0.768S960 591.68 960 591.424z m-560-31.232v-160H608v160h-208z m208 64V832h-208v-207.808H608z m-480-224h208v160H128v-160z m544 0h224v160h-224v-160zM896 224v112.192H128V224h768zM128 624.192h208V832H128v-207.808zM672 832v-207.808h224V832h-224z" p-id="2055"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/button.svg b/zhitan-vue/src/assets/icons/svg/button.svg
new file mode 100644
index 0000000..904fddc
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/button.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1588670460195" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1314" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M230.4 307.712c13.824 0 25.088-11.264 25.088-25.088 0-100.352 81.92-182.272 182.272-182.272s182.272 81.408 182.272 182.272c0 13.824 11.264 25.088 25.088 25.088s25.088-11.264 24.576-25.088c0-127.488-103.936-231.936-231.936-231.936S205.824 154.624 205.824 282.624c-0.512 14.336 10.752 25.088 24.576 25.088z m564.736 234.496c-11.264 0-21.504 2.048-31.232 6.144 0-44.544-40.448-81.92-88.064-81.92-14.848 0-28.16 3.584-39.936 10.24-13.824-28.16-44.544-48.128-78.848-48.128-12.288 0-24.576 2.56-35.328 7.68V284.16c0-45.568-37.888-81.92-84.48-81.92s-84.48 36.864-84.48 81.92v348.672l-69.12-112.64c-18.432-28.16-58.368-36.864-91.136-19.968-26.624 14.336-46.592 47.104-30.208 88.064 3.072 8.192 76.8 205.312 171.52 311.296 0 0 28.16 24.576 43.008 58.88 4.096 9.728 13.312 15.36 22.528 15.36 3.072 0 6.656-0.512 9.728-2.048 12.288-5.12 18.432-19.968 12.8-32.256-19.456-44.544-53.76-74.752-53.76-74.752C281.6 768 209.408 573.44 208.384 570.88c-5.12-12.8-2.56-20.992 7.168-26.112 9.216-4.608 21.504-4.608 26.112 2.56l113.152 184.32c4.096 8.704 12.8 14.336 22.528 14.336 13.824 0 25.088-10.752 25.088-25.088V284.16c0-17.92 15.36-32.256 34.816-32.256s34.816 14.336 34.816 32.256v284.16c0 13.824 10.24 25.088 24.576 25.088 13.824 0 25.088-11.264 25.088-25.088v-57.344c0-17.92 15.36-32.768 34.816-32.768 19.968 0 37.376 15.36 37.376 32.768v95.232c0 7.168 3.072 13.312 7.68 17.92 4.608 4.608 10.752 7.168 17.92 7.168 13.824 0 24.576-11.264 24.576-25.088V547.84c0-18.432 13.824-32.256 32.256-32.256 20.48 0 38.912 15.36 38.912 32.256v95.232c0 13.824 11.264 25.088 25.088 25.088s24.576-11.264 25.088-25.088v-18.944c0-18.944 12.8-32.256 30.72-32.256 18.432 0 22.528 18.944 22.528 31.744 0 1.024-11.776 99.84-50.688 173.056-30.72 58.368-45.056 112.128-51.2 146.944-2.56 13.312 6.656 26.112 19.968 28.672 1.536 0 3.072 0.512 4.608 0.512 11.776 0 22.016-8.192 24.064-20.48 5.632-31.232 18.432-79.36 46.08-132.608 43.52-81.92 55.808-186.88 56.32-193.536-0.512-50.688-29.696-83.968-72.704-83.968z"></path></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/cascader.svg b/zhitan-vue/src/assets/icons/svg/cascader.svg
new file mode 100644
index 0000000..e256024
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/cascader.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576153230908" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="971" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M772.87036133 734.06115723c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714843H475.90991211c-56.60705567 0-102.66723633-46.06018067-102.66723633-102.66723633V600.82446289h305.859375c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012S827.9942627 467.50537109 772.87036133 467.50537109c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714844H373.24267578V401.01062011h321.92687989c55.12390137 0 99.94812012-44.82421875 99.94812011-99.94812011V190.07312011C795.11767578 134.94921875 750.29345703 90.125 695.16955567 90.125H251.12963867C196.0057373 90.125 151.18151855 134.94921875 151.18151855 190.07312011V301.0625c0 55.12390137 44.82421875 99.94812012 99.94812012 99.94812012h55.53588867v296.96044921c0 93.35632325 75.97045898 169.32678223 169.32678224 169.32678223h203.19213866c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012s-44.90661622-99.86572266-100.03051758-99.86572265z m0-199.89624024c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857423s-14.91394043 33.28857422-33.28857422 33.28857421-33.28857422-14.91394043-33.28857422-33.28857421 14.91394043-33.28857422 33.28857422-33.28857422zM217.75866699 301.0625V190.07312011c0-18.37463379 14.91394043-33.28857422 33.28857423-33.28857421h444.03991698c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857422V301.0625c0 18.37463379-14.91394043 33.28857422-33.28857422 33.28857422H251.12963867c-18.37463379 0-33.37097168-14.91394043-33.37097168-33.28857422z m555.11169434 566.23535156c-18.37463379 0-33.28857422-14.91394043-33.28857422-33.28857422 0-18.37463379 14.91394043-33.28857422 33.28857422-33.28857422s33.28857422 14.91394043 33.28857422 33.28857422c0.08239747 18.29223633-14.91394043 33.28857422-33.28857422 33.28857422z" p-id="972"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/chart.svg b/zhitan-vue/src/assets/icons/svg/chart.svg
new file mode 100644
index 0000000..27728fb
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/chart.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h36.571V128H0V54.857zM91.429 27.43H128V128H91.429V27.429zM45.714 0h36.572v128H45.714V0z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/checkbox.svg b/zhitan-vue/src/assets/icons/svg/checkbox.svg
new file mode 100644
index 0000000..013fd3a
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/checkbox.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575982282951" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="902" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M828.40625 90.125H195.59375C137.375 90.125 90.125 137.375 90.125 195.59375v632.8125c0 58.21875 47.25 105.46875 105.46875 105.46875h632.8125c58.21875 0 105.46875-47.25 105.46875-105.46875V195.59375c0-58.21875-47.25-105.46875-105.46875-105.46875z m52.734375 738.28125c0 29.16-23.57015625 52.734375-52.734375 52.734375H195.59375c-29.109375 0-52.734375-23.574375-52.734375-52.734375V195.59375c0-29.109375 23.625-52.734375 52.734375-52.734375h632.8125c29.16 0 52.734375 23.625 52.734375 52.734375v632.8125z" p-id="903"></path><path d="M421.52890625 709.55984375a36.28125 36.28125 0 0 1-27.55265625-12.66890625L205.17453125 476.613125a36.28546875 36.28546875 0 0 1 55.10109375-47.22890625l164.986875 192.4846875 342.16171875-298.48078125a36.2896875 36.2896875 0 0 1 47.70984375 54.68765625L445.3859375 700.6203125a36.3234375 36.3234375 0 0 1-23.85703125 8.93953125z" p-id="904"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/clipboard.svg b/zhitan-vue/src/assets/icons/svg/clipboard.svg
new file mode 100644
index 0000000..90923ff
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/clipboard.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.857 118.857h64V73.143H89.143c-1.902 0-3.52-.668-4.855-2.002-1.335-1.335-2.002-2.954-2.002-4.855V36.57H54.857v82.286zM73.143 16v-4.571a2.2 2.2 0 0 0-.677-1.61 2.198 2.198 0 0 0-1.609-.676H20.571c-.621 0-1.158.225-1.609.676a2.198 2.198 0 0 0-.676 1.61V16a2.2 2.2 0 0 0 .676 1.61c.451.45.988.676 1.61.676h50.285c.622 0 1.158-.226 1.61-.677.45-.45.676-.987.676-1.609zm18.286 48h21.357L91.43 42.642V64zM128 73.143v48c0 1.902-.667 3.52-2.002 4.855-1.335 1.335-2.953 2.002-4.855 2.002H52.57c-1.901 0-3.52-.667-4.854-2.002-1.335-1.335-2.003-2.953-2.003-4.855v-11.429H6.857c-1.902 0-3.52-.667-4.855-2.002C.667 106.377 0 104.759 0 102.857v-96c0-1.902.667-3.52 2.002-4.855C3.337.667 4.955 0 6.857 0h77.714c1.902 0 3.52.667 4.855 2.002 1.335 1.335 2.003 2.953 2.003 4.855V30.29c1 .622 1.856 1.29 2.569 2.003l29.147 29.147c1.335 1.335 2.478 3.145 3.429 5.43.95 2.287 1.426 4.383 1.426 6.291v-.018z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/code.svg b/zhitan-vue/src/assets/icons/svg/code.svg
new file mode 100644
index 0000000..5f9c5ab
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/code.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1546567861908" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2422" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M318.577778 819.2L17.066667 512l301.511111-307.2 45.511111 45.511111L96.711111 512l267.377778 261.688889zM705.422222 819.2l-45.511111-45.511111L927.288889 512l-267.377778-261.688889 45.511111-45.511111L1006.933333 512zM540.785778 221.866667l55.751111 11.150222L483.157333 802.133333l-55.751111-11.093333z" p-id="2423"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/color.svg b/zhitan-vue/src/assets/icons/svg/color.svg
new file mode 100644
index 0000000..44a81aa
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/color.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577252187056" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2508" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M747.59340925 691.12859384c11.51396329 0.25305413 22.43746719-0.21087818 40.74171707-1.51832482 29.35428085-2.10878421 35.84933734-2.36183835 46.47761114-0.8856895 24.71495444 3.37405491 41.12129828 21.76265671 32.47528161 47.95376084-85.57447632 258.19957947-442.00123984 249.76444099-628.67084683 50.73735554-153.47733892-159.33976008-153.09775772-414.41833795 0.92786545-573.42069196 159.71934128-162.67163983 424.03439521-166.59397897 565.78689185 0.63263534 80.38686649 94.81095318 108.34934958 169.16669549 89.11723508 230.57450162-15.01454608 47.99593598-50.61082928 77.68762207-119.77896259 114.63352789-4.89237973 2.65706845-29.35428085 15.52065436-35.84933652 19.02123633-46.94154346 25.30541465-63.51659033 41.20565021-62.20914449 58.45550757 2.95229856 39.13904114 24.16667102 52.7196135 70.98168823 53.81618115z m44.41100207 50.10472101c-19.82257471 1.43397372-32.05352527 1.940082-45.63409763 1.6448519-70.34905207-1.60267593-115.98314969-30.91478165-121.38163769-101.64341492-3.45840683-46.05585397 24.7571304-73.13264758 89.24376132-107.96976837 6.7902866-3.66928501 31.37871396-16.57504688 36.06021551-19.06341229 57.69634516-30.83042972 85.15271997-53.73183005 94.76877722-84.47790866 12.77923398-40.78389304-9.10994898-98.94417051-79.24812286-181.6507002-121.17075953-142.97559219-350.14258521-139.60153647-489.2380134 2.06660824-134.49827774 138.84237405-134.79350784 362.12048163-0.42175717 501.637667 158.53842169 168.99799328 451.9968783 181.18676788 534.57688175-11.80919339-4.68150156 0.2952301-10.71262573 0.67481131-18.72600705 1.26527069z" p-id="2509"></path><path d="M346.03865637 637.18588562a78.82636652 78.82636652 0 0 0 78.32025825-79.29029883c0-43.69401562-35.005823-79.29029883-78.32025825-79.29029882a78.82636652 78.82636652 0 0 0-78.36243338 79.29029882c0 43.69401562 35.005823 79.29029883 78.36243338 79.29029883z m0-51.7495729a27.07679361 27.07679361 0 0 1-26.5706845-27.54072593c0-15.30977536 11.97789643-27.54072593 26.5706845-27.54072592 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072592a27.07679361 27.07679361 0 0 1-26.57068533 27.54072593zM475.7289063 807.11174353a78.82636652 78.82636652 0 0 0 78.3624334-79.29029882c0-43.69401562-34.96364785-79.29029883-78.32025825-79.29029883a78.82636652 78.82636652 0 0 0-78.32025742 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029882z m0-51.74957208a27.07679361 27.07679361 0 0 1-26.57068532-27.54072674c0-15.30977536 12.06224753-27.54072593 26.57068532-27.54072593 14.59278892 0 26.57068533 12.23095057 26.57068453 27.54072593a27.07679361 27.07679361 0 0 1-26.57068453 27.54072674zM601.24376214 377.21492718a78.82636652 78.82636652 0 0 0 78.32025742-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025742-79.29029882a78.82636652 78.82636652 0 0 0-78.32025823 79.29029883c0 43.69401562 34.96364785 79.29029883 78.32025824 79.29029883z m1e-8-51.74957208a27.07679361 27.07679361 0 0 1-26.57068534-27.54072675c0-15.30977536 11.97789643-27.54072593 26.57068534-27.54072591 14.55061295 0 26.57068533 12.23095057 26.57068451 27.54072592a27.07679361 27.07679361 0 0 1-26.57068451 27.54072674zM378.80916809 433.85687983a78.82636652 78.82636652 0 0 0 78.32025824-79.29029883c0-43.69401562-34.96364785-79.29029883-78.32025824-79.29029802a78.82636652 78.82636652 0 0 0-78.32025742 79.29029802c0 43.69401562 34.96364785 79.29029883 78.32025742 79.29029883z m0-51.74957209a27.07679361 27.07679361 0 0 1-26.57068451-27.54072674c0-15.30977536 11.97789643-27.54072593 26.57068451-27.54072593 14.55061295 0 26.57068533 12.23095057 26.57068533 27.54072593a27.07679361 27.07679361 0 0 1-26.57068533 27.54072674z" p-id="2510"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/component.svg b/zhitan-vue/src/assets/icons/svg/component.svg
new file mode 100644
index 0000000..29c3458
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/component.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575804206892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3145" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M826.56 470.016c-32.896 0-64.384 12.288-89.984 35.52l0-104.96c0-62.208-50.496-112.832-112.64-113.088L623.936 287.04 519.552 287.104C541.824 262.72 554.56 230.72 554.56 197.12c0-73.536-59.904-133.44-133.504-133.44-73.472 0-133.376 59.904-133.376 133.44 0 32.896 12.224 64.256 35.52 89.984L175.232 287.104l0 0.576C113.728 288.704 64 338.88 64 400.576l0.32 0 0.32 116.48C60.864 544.896 70.592 577.728 100.8 588.48c12.736 4.608 37.632 7.488 60.864-25.28 12.992-18.368 34.24-29.248 56.64-29.248 38.336 0 69.504 31.104 69.504 69.312 0 38.4-31.168 69.504-69.504 69.504-22.656 0-44.032-11.264-57.344-30.4C138.688 610.112 112.576 615.36 102.464 619.136c-29.824 10.752-39.104 43.776-38.144 67.392l0 160.384L64 846.912C64 909.248 114.752 960 177.216 960l446.272 0c62.4 0 113.152-50.752 113.152-113.152l0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.536 0 133.44-59.904 133.44-133.504C960 529.92 900.096 470.016 826.56 470.016zM826.56 672.896c-22.72 0-44.032-11.264-57.344-30.4-22.272-32.384-48.448-27.136-58.56-23.36-29.824 10.752-39.04 43.776-38.08 67.392l0 160.384c0 27.136-22.016 49.152-49.152 49.152L177.216 896.064C150.08 896 128 873.984 128 846.848l0.32 0 0-145.024c24.384 22.272 56.384 35.008 89.984 35.008 73.6 0 133.504-59.904 133.504-133.504 0-73.472-59.904-133.376-133.504-133.376-32.896 0-64.32 12.288-89.984 35.52l0-104.96L128 400.512c0-27.072 22.08-49.152 49.216-49.152L177.216 351.04 334.656 350.72c3.776 0.512 7.616 0.832 11.52 0.832 24.896 0 50.752-10.816 60.032-37.056 4.544-12.736 7.424-37.568-25.344-60.736C362.624 240.768 351.68 219.52 351.68 197.12c0-38.272 31.104-69.44 69.376-69.44 38.336 0 69.504 31.168 69.504 69.44 0 22.72-11.264 44.032-30.528 57.472C427.968 276.736 433.088 302.784 436.8 313.024c10.752 29.888 43.072 39.232 67.392 38.08l119.232 0 0 0.384c27.136 0 49.152 22.08 49.152 49.152l0.256 116.48c-3.776 27.84 6.016 60.736 36.224 71.488 12.736 4.608 37.632 7.488 60.8-25.28 13.056-18.368 34.24-29.248 56.704-29.248C864.832 534.016 896 565.12 896 603.392 896 641.728 864.832 672.896 826.56 672.896z" p-id="3146"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/dashboard.svg b/zhitan-vue/src/assets/icons/svg/dashboard.svg
new file mode 100644
index 0000000..5317d37
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/dashboard.svg
@@ -0,0 +1 @@
+<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/date-range.svg b/zhitan-vue/src/assets/icons/svg/date-range.svg
new file mode 100644
index 0000000..fda571e
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/date-range.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579774833889" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1376" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M887.466667 192.853333h-100.693334V119.466667c0-10.24-6.826667-17.066667-17.066666-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H303.786667V119.466667c0-10.24-6.826667-17.066667-17.066667-17.066667s-17.066667 6.826667-17.066667 17.066667v73.386666H168.96c-46.08 0-85.333333 37.546667-85.333333 85.333334V836.266667c0 46.08 37.546667 85.333333 85.333333 85.333333H887.466667c46.08 0 85.333333-37.546667 85.333333-85.333333V278.186667c0-47.786667-37.546667-85.333333-85.333333-85.333334z m-718.506667 34.133334h100.693333v66.56c0 10.24 6.826667 17.066667 17.066667 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56h450.56v66.56c0 10.24 6.826667 17.066667 17.066666 17.066666s17.066667-6.826667 17.066667-17.066666v-66.56H887.466667c27.306667 0 51.2 22.186667 51.2 51.2v88.746666H117.76v-88.746666c0-29.013333 22.186667-51.2 51.2-51.2zM887.466667 887.466667H168.96c-27.306667 0-51.2-22.186667-51.2-51.2V401.066667H938.666667V836.266667c0 27.306667-22.186667 51.2-51.2 51.2z" p-id="1377"></path><path d="M858.453333 493.226667H327.68c-10.24 0-17.066667 6.826667-17.066667 17.066666v114.346667h-116.053333c-10.24 0-17.066667 6.826667-17.066667 17.066667v133.12c0 10.24 6.826667 17.066667 17.066667 17.066666H460.8c10.24 0 17.066667-6.826667 17.066667-17.066666v-114.346667h380.586666c10.24 0 17.066667-6.826667 17.066667-17.066667v-133.12c0-10.24-6.826667-17.066667-17.066667-17.066666z m-413.013333 34.133333v97.28h-98.986667v-97.28h98.986667z m-230.4 131.413333h98.986667v98.986667h-98.986667v-98.986667z m131.413333 97.28v-97.28h98.986667v97.28h-98.986667z m133.12-228.693333h97.28v98.986667h-97.28v-98.986667z m131.413334 0h98.986666v98.986667h-98.986666v-98.986667z m230.4 97.28h-98.986667v-98.986667h98.986667v98.986667z" p-id="1378"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/date.svg b/zhitan-vue/src/assets/icons/svg/date.svg
new file mode 100644
index 0000000..52dc73e
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/date.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577186573535" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1068" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M479.85714249 608.42857168h64.28571502c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285751s-12.85714249-32.14285751-32.14285664-32.14285664h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285662s12.85714249 32.14285751 32.14285664 32.14285753z m-2e-8 122.14285665h64.28571504c19.28571417 0 32.14285751-12.85714249 32.14285664-32.14285665s-12.85714249-32.14285751-32.14285664-32.14285751h-64.28571504c-19.28571417 0-32.14285751 12.85714249-32.14285664 32.14285751s12.85714249 32.14285751 32.14285664 32.14285664z m353.57142921-559.28571416h-128.57142921v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285664-32.14285753s-32.14285751 12.85714249-32.14285751 32.14285753v32.14285664h-257.14285665v-32.14285664c0-19.28571417-12.85714249-32.14285751-32.14285752-32.14285753s-32.14285751 12.85714249-32.14285664 32.14285753v32.14285664h-128.57142919c-70.71428585 0-128.57142832 57.85714249-128.57142832 122.14285751v501.42857081c0 70.71428585 57.85714249 128.57142832 128.57142832 122.14285751h642.85714335c70.71428585 0 128.57142832-57.85714249 128.57142833-122.14285751v-501.42857081c0-70.71428585-57.85714249-122.14285753-128.57142833-122.14285751z m64.28571415 623.57142832c0 32.14285751-32.14285751 64.28571415-64.28571416 64.28571504h-642.85714335c-32.14285751 0-64.28571415-25.71428583-64.28571417-64.28571504v-372.85714249h771.42857168v372.85714249z m0-437.14285664h-771.42857168v-64.28571417c0-32.14285751 32.14285751-64.28571415 64.28571417-64.28571415h128.57142919v32.14285664c0 19.28571417 12.85714249 32.14285751 32.14285664 32.14285751s32.14285751-12.85714249 32.14285753-32.14285751v-32.14285664h257.14285665v32.14285664c0 19.28571417 12.85714249 32.14285751 32.1428575 32.14285751s32.14285751-12.85714249 32.14285664-32.14285751v-32.14285664h128.57142921c32.14285751 0 64.28571415 25.71428583 64.28571415 64.28571415v64.28571417z m-610.71428583 372.85714247h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285664s-12.85714249-32.14285751-32.14285753-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285751s12.85714249 32.14285751 32.14285751 32.14285665z m385.71428583-122.14285664h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285751s-12.85714249-32.14285751-32.14285751-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285664s12.85714249 32.14285751 32.14285753 32.14285751z m-385.71428583 0h64.28571415c19.28571417 0 32.14285751-12.85714249 32.14285753-32.14285751s-12.85714249-32.14285751-32.14285753-32.14285664h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285751 32.14285664s12.85714249 32.14285751 32.14285751 32.14285751z m385.71428583 122.14285665h64.28571417c19.28571417 0 32.14285751-12.85714249 32.14285751-32.14285665s-12.85714249-32.14285751-32.14285751-32.14285751h-64.28571415c-19.28571417 0-32.14285751 12.85714249-32.14285753 32.14285751s12.85714249 32.14285751 32.14285753 32.14285665z" p-id="1069"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/dict.svg b/zhitan-vue/src/assets/icons/svg/dict.svg
new file mode 100644
index 0000000..4849377
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/dict.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566035680909" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3601" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1002.0848 744.672l-33.568 10.368c0.96 7.264 2.144 14.304 2.144 21.76 0 7.328-1.184 14.432-2.368 21.568l33.792 10.56c7.936 2.24 14.496 7.616 18.336 14.752 3.84 7.328 4.672 15.808 1.952 23.552-5.376 16-23.168 24.672-39.936 19.68l-34.176-10.624c-7.136 12.8-15.776 24.672-26.208 35.2l20.8 27.488a28.96 28.96 0 0 1 5.824 22.816 29.696 29.696 0 0 1-12.704 19.616 32.544 32.544 0 0 1-44.416-6.752l-20.8-27.552c-13.696 6.56-28.192 11.2-43.008 13.888v33.632c0 16.736-14.112 30.432-31.648 30.432-17.6 0-31.872-13.696-31.872-30.432v-33.632a167.616 167.616 0 0 1-42.88-13.888l-20.928 27.552c-10.72 13.76-30.08 16.64-44.288 6.752a29.632 29.632 0 0 1-12.704-19.616 29.28 29.28 0 0 1 5.696-22.816l20.896-27.808a166.72 166.72 0 0 1-27.008-34.688l-33.376 10.432c-16.8 5.184-34.56-3.552-39.936-19.616a29.824 29.824 0 0 1 20.224-38.24l33.472-10.432c-0.8-7.264-2.016-14.304-2.016-21.824 0-7.36 1.184-14.496 2.304-21.632l-33.792-10.368c-16.672-5.376-25.632-22.496-20.224-38.432 5.376-16 23.136-24.672 39.936-19.68l34.016 10.752c7.328-12.672 15.84-24.8 26.336-35.328l-20.8-27.552a29.44 29.44 0 0 1 6.944-42.432 32.704 32.704 0 0 1 44.384 6.752l20.832 27.616c13.696-6.432 28.224-11.2 43.104-13.952v-33.568c0-16.736 14.048-30.432 31.648-30.432 17.536 0 31.808 13.568 31.808 30.432v33.504c15.072 2.688 29.344 7.808 42.848 14.016l20.992-27.616a32.48 32.48 0 0 1 44.224-6.752 29.568 29.568 0 0 1 7.136 42.432l-21.024 27.808c10.432 10.432 19.872 21.888 27.04 34.752l33.376-10.432c16.768-5.12 34.56 3.68 39.936 19.68 5.536 15.936-3.712 33.056-20.32 38.304z m-206.016-74.432c-61.344 0-111.136 47.808-111.136 106.56 0 58.88 49.792 106.496 111.136 106.496 61.312 0 111.104-47.616 111.104-106.496 0-58.752-49.792-106.56-111.104-106.56z" p-id="3602"></path><path d="M802.7888 57.152h-76.448c0-22.08-21.024-38.24-42.848-38.24H39.3968a39.68 39.68 0 0 0-39.36 40.032v795.616s41.888 120.192 110.752 120.192H673.2848a227.488 227.488 0 0 1-107.04-97.44H117.6368s-40.608-13.696-40.608-41.248l470.304-0.256 1.664 3.36a227.68 227.68 0 0 1-12.64-73.632c0-60.576 24-118.624 66.88-161.44a228.352 228.352 0 0 1 123.552-63.392l-3.2 0.288 2.144-424.672h38.208l0.576 421.024c27.04 0 52.672 4.8 76.64 13.344V101.536c0.032 0-6.304-44.384-38.368-44.384zM149.7648 514.336H72.3888v-77.408H149.7648v77.408z m0-144.32H72.3888v-77.44H149.7648v77.44z m0-137.248H72.3888v-77.44H149.7648v77.44z m501.856 281.568H206.0848v-77.408h445.536v77.408z m0-144.32H206.0848v-77.44h445.536v77.44z m0-137.248H206.0848v-77.44h445.536v77.44z" p-id="3603"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/documentation.svg b/zhitan-vue/src/assets/icons/svg/documentation.svg
new file mode 100644
index 0000000..7043122
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/documentation.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M71.984 44.815H115.9L71.984 9.642v35.173zM16.094.05h63.875l47.906 38.37v76.74c0 3.392-1.682 6.645-4.677 9.044-2.995 2.399-7.056 3.746-11.292 3.746H16.094c-4.236 0-8.297-1.347-11.292-3.746-2.995-2.399-4.677-5.652-4.677-9.044V12.84C.125 5.742 7.23.05 16.094.05zm71.86 102.32V89.58h-71.86v12.79h71.86zm23.952-25.58V64H16.094v12.79h95.812z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/download.svg b/zhitan-vue/src/assets/icons/svg/download.svg
new file mode 100644
index 0000000..c896951
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/download.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1569915748289" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3062" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M768.35456 416a256 256 0 1 0-512 0 192 192 0 1 0 0 384v64a256 256 0 0 1-58.88-505.216 320.128 320.128 0 0 1 629.76 0A256.128 256.128 0 0 1 768.35456 864v-64a192 192 0 0 0 0-384z m-512 384h64v64H256.35456v-64z m448 0h64v64h-64v-64z" fill="#333333" p-id="3063"></path><path d="M539.04256 845.248V512.192a32.448 32.448 0 0 0-32-32.192c-17.664 0-32 14.912-32 32.192v333.056l-36.096-36.096a32.192 32.192 0 0 0-45.056 0.192 31.616 31.616 0 0 0-0.192 45.056l90.88 90.944a31.36 31.36 0 0 0 22.528 9.088 30.08 30.08 0 0 0 22.4-9.088l90.88-90.88a32.192 32.192 0 0 0-0.192-45.12 31.616 31.616 0 0 0-45.056-0.192l-36.096 36.096z" fill="#333333" p-id="3064"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/drag.svg b/zhitan-vue/src/assets/icons/svg/drag.svg
new file mode 100644
index 0000000..4185d3c
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/drag.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M73.137 29.08h-9.209 29.7L63.886.093 34.373 29.08h20.49v27.035H27.238v17.948h27.625v27.133h18.274V74.063h27.41V56.115h-27.41V29.08zm-9.245 98.827l27.518-26.711H36.59l27.302 26.71zM.042 64.982l27.196 27.029V38.167L.042 64.982zm100.505-26.815V92.01l27.41-27.029-27.41-26.815z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/druid.svg b/zhitan-vue/src/assets/icons/svg/druid.svg
new file mode 100644
index 0000000..a2b4b4e
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/druid.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036347051" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5853" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M832 128H192a64.19 64.19 0 0 0-64 64v640a64.19 64.19 0 0 0 64 64h640a64.19 64.19 0 0 0 64-64V192a64.19 64.19 0 0 0-64-64z m0 703.89l-0.11 0.11H192.11l-0.11-0.11V768h640zM832 544H720L605.6 696.54 442.18 435.07 333.25 544H192v-64h114.75l147.07-147.07L610.4 583.46 688 480h144z m0-288H192v-63.89l0.11-0.11h639.78l0.11 0.11z" p-id="5854"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/edit.svg b/zhitan-vue/src/assets/icons/svg/edit.svg
new file mode 100644
index 0000000..d26101f
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/edit.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M106.133 67.2a4.797 4.797 0 0 0-4.8 4.8c0 .187.014.36.027.533h-.027V118.4H9.6V26.667h50.133c2.654 0 4.8-2.147 4.8-4.8 0-2.654-2.146-4.8-4.8-4.8H9.6a9.594 9.594 0 0 0-9.6 9.6V118.4c0 5.307 4.293 9.6 9.6 9.6h91.733c5.307 0 9.6-4.293 9.6-9.6V72.533h-.026c.013-.173.026-.346.026-.533 0-2.653-2.146-4.8-4.8-4.8z"/><path d="M125.16 13.373L114.587 2.8c-3.747-3.747-9.854-3.72-13.6.027l-52.96 52.96a4.264 4.264 0 0 0-.907 1.36L33.813 88.533c-.746 1.76-.226 3.534.907 4.68 1.133 1.147 2.92 1.667 4.693.92l31.4-13.293c.507-.213.96-.52 1.36-.907l52.96-52.96c3.747-3.746 3.774-9.853.027-13.6zM66.107 72.4l-18.32 7.76 7.76-18.32L92.72 24.667l10.56 10.56L66.107 72.4zm52.226-52.227l-8.266 8.267-10.56-10.56 8.266-8.267.027-.026 10.56 10.56-.027.026z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/education.svg b/zhitan-vue/src/assets/icons/svg/education.svg
new file mode 100644
index 0000000..7bfb01d
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/education.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M88.883 119.565c-7.284 0-19.434 2.495-21.333 8.25v.127c-4.232.13-5.222 0-7.108 0-1.895-5.76-14.045-8.256-21.333-8.256H0V0h42.523c9.179 0 17.109 5.47 21.47 13.551C68.352 5.475 76.295 0 85.478 0H128v119.57l-39.113-.005h-.004zM60.442 24.763c0-9.651-8.978-16.507-17.777-16.507H7.108V111.43H39.11c7.054-.14 18.177.082 21.333 6.12v-4.628c-.134-5.722-.004-13.522 0-13.832V27.413l.004-2.655-.004.005zm60.442-16.517h-35.55c-8.802 0-17.78 6.856-17.78 16.493v74.259c.004.32.138 8.115 0 13.813v4.627c3.155-6.022 14.279-6.26 21.333-6.114h32V8.25l-.003-.005z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/email.svg b/zhitan-vue/src/assets/icons/svg/email.svg
new file mode 100644
index 0000000..74d25e2
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/email.svg
@@ -0,0 +1 @@
+<svg width="128" height="96" xmlns="http://www.w3.org/2000/svg"><path d="M64.125 56.975L120.188.912A12.476 12.476 0 0 0 115.5 0h-103c-1.588 0-3.113.3-4.513.838l56.138 56.137z"/><path d="M64.125 68.287l-62.3-62.3A12.42 12.42 0 0 0 0 12.5v71C0 90.4 5.6 96 12.5 96h103c6.9 0 12.5-5.6 12.5-12.5v-71a12.47 12.47 0 0 0-1.737-6.35L64.125 68.287z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/example.svg b/zhitan-vue/src/assets/icons/svg/example.svg
new file mode 100644
index 0000000..46f42b5
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/example.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/excel.svg b/zhitan-vue/src/assets/icons/svg/excel.svg
new file mode 100644
index 0000000..74d97b8
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/excel.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.208 16.576v8.384h38.72v5.376h-38.72v8.704h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.576h38.72v5.376h-38.72v8.512h38.72v5.376h-38.72v11.136H128v-94.72H78.208zM0 114.368L72.128 128V0L0 13.632v100.736z"/><path d="M28.672 82.56h-11.2l14.784-23.488-14.08-22.592h11.52l8.192 14.976 8.448-14.976h11.136l-14.08 22.208L58.368 82.56H46.656l-8.768-15.68z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/exit-fullscreen.svg b/zhitan-vue/src/assets/icons/svg/exit-fullscreen.svg
new file mode 100644
index 0000000..485c128
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/exit-fullscreen.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M49.217 41.329l-.136-35.24c-.06-2.715-2.302-4.345-5.022-4.405h-3.65c-2.712-.06-4.866 2.303-4.806 5.016l.152 19.164-24.151-23.79a6.698 6.698 0 0 0-9.499 0 6.76 6.76 0 0 0 0 9.526l23.93 23.713-18.345.074c-2.712-.069-5.228 1.813-5.64 5.02v3.462c.069 2.721 2.31 4.97 5.022 5.03l35.028-.207c.052.005.087.025.133.025l2.457.054a4.626 4.626 0 0 0 3.436-1.38c.88-.874 1.205-2.096 1.169-3.462l-.262-2.465c0-.048.182-.081.182-.136h.002zm52.523 51.212l18.32-.073c2.713.06 5.224-1.609 5.64-4.815v-3.462c-.068-2.722-2.317-4.97-5.021-5.04l-34.58.21c-.053 0-.086-.021-.138-.021l-2.451-.06a4.64 4.64 0 0 0-3.445 1.381c-.885.868-1.201 2.094-1.174 3.46l.27 2.46c.005.06-.177.095-.177.141l.141 34.697c.069 2.713 2.31 4.338 5.022 4.397l3.45.006c2.705.062 4.867-2.31 4.8-5.026l-.153-18.752 24.151 23.946a6.69 6.69 0 0 0 9.494 0 6.747 6.747 0 0 0 0-9.523L101.74 92.54v.001zM48.125 80.662a4.636 4.636 0 0 0-3.437-1.382l-2.457.06c-.05 0-.082.022-.137.022l-35.025-.21c-2.712.07-4.957 2.318-5.022 5.04v3.462c.409 3.206 2.925 4.874 5.633 4.814l18.554.06-24.132 23.928c-2.62 2.626-2.62 6.89 0 9.524a6.694 6.694 0 0 0 9.496 0l24.155-23.79-.155 18.866c-.06 2.722 2.094 5.093 4.801 5.025h3.65c2.72-.069 4.962-1.685 5.022-4.406l.141-34.956c0-.05-.182-.082-.182-.136l.262-2.46c.03-1.366-.286-2.592-1.166-3.46h-.001zM80.08 47.397a4.62 4.62 0 0 0 3.443 1.374l2.45-.054c.055 0 .088-.02.143-.028l35.08.21c2.712-.062 4.953-2.312 5.021-5.033l.009-3.463c-.417-3.211-2.937-5.084-5.64-5.025l-18.615-.073 23.917-23.715c2.63-2.623 2.63-6.879.008-9.513a6.691 6.691 0 0 0-9.494 0L92.251 26.016l.155-19.312c.065-2.713-2.097-5.085-4.802-5.025h-3.45c-2.713.069-4.954 1.693-5.022 4.406l-.139 35.247c0 .054.18.088.18.136l-.267 2.465c-.028 1.366.288 2.588 1.174 3.463v.001z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/eye-open.svg b/zhitan-vue/src/assets/icons/svg/eye-open.svg
new file mode 100644
index 0000000..88dcc98
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/eye-open.svg
@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/eye.svg b/zhitan-vue/src/assets/icons/svg/eye.svg
new file mode 100644
index 0000000..16ed2d8
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/eye.svg
@@ -0,0 +1 @@
+<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/form.svg b/zhitan-vue/src/assets/icons/svg/form.svg
new file mode 100644
index 0000000..dcbaa18
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/form.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/fullscreen.svg b/zhitan-vue/src/assets/icons/svg/fullscreen.svg
new file mode 100644
index 0000000..0e86b6f
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/fullscreen.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M38.47 52L52 38.462l-23.648-23.67L43.209 0H.035L0 43.137l14.757-14.865L38.47 52zm74.773 47.726L89.526 76 76 89.536l23.648 23.672L84.795 128h43.174L128 84.863l-14.757 14.863zM89.538 52l23.668-23.648L128 43.207V.038L84.866 0 99.73 14.76 76 38.472 89.538 52zM38.46 76L14.792 99.651 0 84.794v43.173l43.137.033-14.865-14.757L52 89.53 38.46 76z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/github.svg b/zhitan-vue/src/assets/icons/svg/github.svg
new file mode 100644
index 0000000..db0a0d4
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/github.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1581238998885" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4187" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M511.542857 14.057143C228.914286 13.942857 0 242.742857 0 525.142857 0 748.457143 143.2 938.285714 342.628571 1008c26.857143 6.742857 22.742857-12.342857 22.742858-25.371429v-88.571428c-155.085714 18.171429-161.371429-84.457143-171.771429-101.6C172.571429 756.571429 122.857143 747.428571 137.714286 730.285714c35.314286-18.171429 71.314286 4.571429 113.028571 66.171429 30.171429 44.685714 89.028571 37.142857 118.857143 29.714286 6.514286-26.857143 20.457143-50.857143 39.657143-69.485715-160.685714-28.8-227.657143-126.857143-227.657143-243.428571 0-56.571429 18.628571-108.571429 55.2-150.514286-23.314286-69.142857 2.171429-128.342857 5.6-137.142857 66.4-5.942857 135.428571 47.542857 140.8 51.771429 37.714286-10.171429 80.8-15.542857 129.028571-15.542858 48.457143 0 91.657143 5.6 129.714286 15.885715 12.914286-9.828571 76.914286-55.771429 138.628572-50.171429 3.314286 8.8 28.228571 66.628571 6.285714 134.857143 37.028571 42.057143 55.885714 94.514286 55.885714 151.2 0 116.8-67.428571 214.971429-228.571428 243.314286a145.714286 145.714286 0 0 1 43.542857 104v128.571428c0.914286 10.285714 0 20.457143 17.142857 20.457143 202.4-68.228571 348.114286-259.428571 348.114286-484.685714 0-282.514286-229.028571-511.2-511.428572-511.2z" p-id="4188"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/guide.svg b/zhitan-vue/src/assets/icons/svg/guide.svg
new file mode 100644
index 0000000..b271001
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/guide.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.482 70.131l36.204 16.18 69.932-65.485-61.38 70.594 46.435 18.735c1.119.425 2.397-.17 2.797-1.363v-.085L127.998.047 1.322 65.874c-1.12.597-1.519 1.959-1.04 3.151.32.511.72.937 1.2 1.107zm44.676 57.821L64.22 107.26l-18.062-7.834v28.527z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/icon.svg b/zhitan-vue/src/assets/icons/svg/icon.svg
new file mode 100644
index 0000000..82be8ee
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/icon.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.147.062a13 13 0 0 1 4.94.945c1.55.63 2.907 1.526 4.069 2.688a13.148 13.148 0 0 1 2.761 4.069c.678 1.55 1.017 3.245 1.017 5.086v102.3c0 3.681-1.187 6.733-3.56 9.155-2.373 2.422-5.352 3.633-8.937 3.633H12.992c-3.875 0-7-1.26-9.373-3.779-2.373-2.518-3.56-5.667-3.56-9.445V12.704c0-3.39 1.163-6.345 3.488-8.863C5.872 1.32 8.972.062 12.847.062h102.3zM81.434 109.047c1.744 0 3.003-.412 3.778-1.235.775-.824 1.163-1.914 1.163-3.27 0-1.26-.388-2.325-1.163-3.197-.775-.872-2.034-1.307-3.778-1.307H72.57c.097-.194.145-.485.145-.872V27.09h9.01c1.743 0 2.954-.436 3.633-1.308.678-.872 1.017-1.938 1.017-3.197 0-1.26-.34-2.325-1.017-3.197-.679-.872-1.89-1.308-3.633-1.308H46.268c-1.743 0-2.954.436-3.632 1.308-.678.872-1.018 1.938-1.018 3.197 0 1.26.34 2.325 1.018 3.197.678.872 1.889 1.308 3.632 1.308h8.138v72.075c0 .193.024.339.073.436.048.096.072.242.072.436H46.56c-1.744 0-3.003.435-3.778 1.307-.775.872-1.163 1.938-1.163 3.197 0 1.356.388 2.446 1.163 3.27.775.823 2.034 1.235 3.778 1.235h34.875z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/input.svg b/zhitan-vue/src/assets/icons/svg/input.svg
new file mode 100644
index 0000000..ab91381
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/input.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802859706" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3102" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 224H128c-35.2 0-64 28.8-64 64v448c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z m0 480c0 19.2-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V320c0-19.2 12.8-32 32-32h704c19.2 0 32 12.8 32 32v384z" p-id="3103"></path><path d="M224 352c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V384c0-16-12.8-32-32-32z" p-id="3104"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/international.svg b/zhitan-vue/src/assets/icons/svg/international.svg
new file mode 100644
index 0000000..e9b56ee
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/international.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M83.287 103.01c-1.57-3.84-6.778-10.414-15.447-19.548-2.327-2.444-2.182-4.306-1.338-9.862v-.64c.553-3.81 1.513-6.05 14.313-8.087 6.516-1.018 8.203 1.57 10.589 5.178l.785 1.193a12.625 12.625 0 0 0 6.43 5.207c1.134.524 2.53 1.164 4.421 2.24 4.596 2.53 4.596 5.41 4.596 11.753v.727a26.91 26.91 0 0 1-5.178 17.454 59.055 59.055 0 0 1-19.025 11.026c3.49-6.546.814-14.313 0-16.553l-.146-.087zM64 5.12a58.502 58.502 0 0 1 25.484 5.818 54.313 54.313 0 0 0-12.859 10.327c-.93 1.28-1.716 2.473-2.472 3.579-2.444 3.694-3.637 5.352-5.818 5.614a25.105 25.105 0 0 1-4.219 0c-4.276-.29-10.094-.64-11.956 4.422-1.193 3.23-1.396 11.956 2.444 16.495.66 1.077.778 2.4.32 3.578a7.01 7.01 0 0 1-2.066 3.229 18.938 18.938 0 0 1-2.909-2.91 18.91 18.91 0 0 0-8.32-6.603c-1.25-.349-2.647-.64-3.985-.93-3.782-.786-8.03-1.688-9.019-3.812a14.895 14.895 0 0 1-.727-5.818 21.935 21.935 0 0 0-1.396-9.25 8.873 8.873 0 0 0-5.557-4.946A58.705 58.705 0 0 1 64 5.12zM0 64c0 35.346 28.654 64 64 64 35.346 0 64-28.654 64-64 0-35.346-28.654-64-64-64C28.654 0 0 28.654 0 64z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/job.svg b/zhitan-vue/src/assets/icons/svg/job.svg
new file mode 100644
index 0000000..2a93a25
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/job.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036191400" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5472" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M934.912 1016.832H192c-14.336 0-25.6-11.264-25.6-25.6v-189.44c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v163.84h691.712V64H217.6v148.48c0 14.336-11.264 25.6-25.6 25.6s-25.6-11.264-25.6-25.6v-174.08c0-14.336 11.264-25.6 25.6-25.6h742.912c14.336 0 25.6 11.264 25.6 25.6v952.832c0 14.336-11.264 25.6-25.6 25.6z" p-id="5473"></path><path d="M232.96 371.2h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM232.96 540.16h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM232.96 698.88h-117.76c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h117.76c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6zM574.464 762.88c-134.144 0-243.2-109.056-243.2-243.2S440.32 276.48 574.464 276.48s243.2 109.056 243.2 243.2-109.056 243.2-243.2 243.2z m0-435.2c-105.984 0-192 86.016-192 192S468.48 711.68 574.464 711.68s192-86.016 192-192S680.448 327.68 574.464 327.68z" p-id="5474"></path><path d="M663.04 545.28h-87.04c-14.336 0-25.6-11.264-25.6-25.6s11.264-25.6 25.6-25.6h87.04c14.336 0 25.6 11.264 25.6 25.6s-11.264 25.6-25.6 25.6z" p-id="5475"></path><path d="M576 545.28c-14.336 0-25.6-11.264-25.6-25.6v-87.04c0-14.336 11.264-25.6 25.6-25.6s25.6 11.264 25.6 25.6v87.04c0 14.336-11.264 25.6-25.6 25.6z" p-id="5476"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/language.svg b/zhitan-vue/src/assets/icons/svg/language.svg
new file mode 100644
index 0000000..0082b57
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/language.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.742 36.8c2.398 7.2 5.595 12.8 11.19 18.4 4.795-4.8 7.992-11.2 10.39-18.4h-21.58zm-52.748 40h20.78l-10.39-28-10.39 28z"/><path d="M111.916 0H16.009C7.218 0 .025 7.2.025 16v96c0 8.8 7.193 16 15.984 16h95.907c8.791 0 15.984-7.2 15.984-16V16c0-8.8-6.394-16-15.984-16zM72.754 103.2c-1.598 1.6-3.197 1.6-4.795 1.6-.8 0-2.398 0-3.197-.8-.8-.8-1.599 0-1.599-.8s-.799-1.6-1.598-3.2c-.8-1.6-.8-2.4-1.599-4l-3.196-8.8H28.797L25.6 96c-1.598 3.2-2.398 5.6-3.197 7.2-.8 1.6-2.398 1.6-4.795 1.6-1.599 0-3.197-.8-4.796-1.6-1.598-1.6-2.397-2.4-2.397-4 0-.8 0-1.6.799-3.2.8-1.6.8-2.4 1.598-4l17.583-44.8c.8-1.6.8-3.2 1.599-4.8.799-1.6 1.598-3.2 2.397-4 .8-.8 1.599-2.4 3.197-3.2 1.599-.8 3.197-.8 4.796-.8 1.598 0 3.196 0 4.795.8 1.598.8 2.398 1.6 3.197 3.2.799.8 1.598 2.4 2.397 4 .8 1.6 1.599 3.2 2.398 5.6l17.583 44c1.598 3.2 2.398 5.6 2.398 7.2-.8.8-1.599 2.4-2.398 4zM116.711 72c-8.791-3.2-15.185-7.2-20.78-12-5.594 5.6-12.787 9.6-21.579 12l-2.397-4c8.791-2.4 15.984-5.6 21.579-11.2C87.939 51.2 83.144 44 81.545 36h-7.992v-3.2h21.58c-1.6-2.4-3.198-5.6-4.796-8l2.397-.8c1.599 2.4 3.997 5.6 5.595 8.8h19.98v4h-7.992c-2.397 8-6.393 15.2-11.189 20 5.595 4.8 11.988 8.8 20.78 11.2l-3.197 4z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/link.svg b/zhitan-vue/src/assets/icons/svg/link.svg
new file mode 100644
index 0000000..48197ba
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/link.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/list.svg b/zhitan-vue/src/assets/icons/svg/list.svg
new file mode 100644
index 0000000..20259ed
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/list.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M1.585 12.087c0 6.616 3.974 11.98 8.877 11.98 4.902 0 8.877-5.364 8.877-11.98 0-6.616-3.975-11.98-8.877-11.98-4.903 0-8.877 5.364-8.877 11.98zM125.86.107H35.613c-1.268 0-2.114 1.426-2.114 2.852v18.255c0 1.712 1.057 2.853 2.114 2.853h90.247c1.268 0 2.114-1.426 2.114-2.853V2.96c0-1.711-1.057-2.852-2.114-2.852zM.106 62.86c0 6.615 3.974 11.979 8.876 11.979 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zM124.17 50.88H33.921c-1.268 0-2.114 1.425-2.114 2.851v18.256c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852V53.73c0-1.426-.846-2.852-2.114-2.852zM.106 115.913c0 6.616 3.974 11.98 8.876 11.98 4.903 0 8.877-5.364 8.877-11.98 0-6.616-3.974-11.98-8.877-11.98-4.902 0-8.876 5.364-8.876 11.98zm124.064-11.98H33.921c-1.268 0-2.114 1.426-2.114 2.853v18.255c0 1.711 1.057 2.852 2.114 2.852h90.247c1.268 0 2.114-1.426 2.114-2.852v-18.255c0-1.427-.846-2.853-2.114-2.853z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/lock.svg b/zhitan-vue/src/assets/icons/svg/lock.svg
new file mode 100644
index 0000000..74fee54
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/lock.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M119.88 49.674h-7.987V39.52C111.893 17.738 90.45.08 63.996.08 37.543.08 16.1 17.738 16.1 39.52v10.154H8.113c-4.408 0-7.987 2.94-7.987 6.577v65.13c0 3.637 3.57 6.577 7.987 6.577H119.88c4.407 0 7.987-2.94 7.987-6.577v-65.13c-.008-3.636-3.58-6.577-7.987-6.577zm-23.953 0H32.065V39.52c0-14.524 14.301-26.295 31.931-26.295 17.63 0 31.932 11.777 31.932 26.295v10.153z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/log.svg b/zhitan-vue/src/assets/icons/svg/log.svg
new file mode 100644
index 0000000..d879d33
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/log.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566035943711" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4805" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M208.736 566.336H64.384v59.328h144.352v-59.328z m0-336.096H165.44V74.592c0-7.968 4.896-14.848 10.464-14.848h502.016V0.448H175.936c-38.72 1.248-69.248 34.368-68.192 74.144v155.648H64.384V289.6h144.352V230.24z m0 168.096H64.384v59.328h144.352v-59.328z m714.656 76.576h-57.76v474.496c0 7.936-4.896 14.848-10.464 14.848H175.936c-5.568 0-10.464-6.912-10.464-14.848v-155.68h43.296v-59.296H64.384v59.296h43.328v155.68c-1.024 39.776 29.472 72.896 68.192 74.144h679.232c38.72-1.184 69.248-34.368 68.256-74.144V474.912z m14.944-290.336l-83.072-85.312a71.264 71.264 0 0 0-52.544-21.728 71.52 71.52 0 0 0-51.616 23.872L386.528 507.264a30.496 30.496 0 0 0-6.176 10.72L308.16 740.512a30.016 30.016 0 0 0 6.976 30.24c7.712 7.968 19.2 10.752 29.568 7.2l216.544-74.112a28.736 28.736 0 0 0 12.128-7.936L940.448 287.456a75.552 75.552 0 0 0-2.112-102.88z m-557.12 518.272l39.104-120.64 78.336 80.416-117.44 40.224z m170.048-70.016l-103.552-106.016 200.16-222.4 103.52 106.304-200.128 222.112zM897.952 247.072l-0.256 0.224-107.136 119.168-103.52-106.528 106.432-118.624a14.144 14.144 0 0 1 10.304-4.736 13.44 13.44 0 0 1 10.464 4.288l83.264 85.696c5.472 5.6 5.664 14.72 0.448 20.512z" p-id="4806"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/logininfor.svg b/zhitan-vue/src/assets/icons/svg/logininfor.svg
new file mode 100644
index 0000000..267f844
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/logininfor.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036016814" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5261" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 128h-85.333333a42.666667 42.666667 0 0 0 0 85.333333h42.666666v640H170.666667V213.333333h42.666666a42.666667 42.666667 0 0 0 0-85.333333H128a42.666667 42.666667 0 0 0-42.666667 42.666667v725.333333a42.666667 42.666667 0 0 0 42.666667 42.666667h768a42.666667 42.666667 0 0 0 42.666667-42.666667V170.666667a42.666667 42.666667 0 0 0-42.666667-42.666667z" p-id="5262"></path><path d="M341.333333 298.666667a42.666667 42.666667 0 0 0 42.666667-42.666667V128a42.666667 42.666667 0 0 0-85.333333 0v128a42.666667 42.666667 0 0 0 42.666666 42.666667zM512 298.666667a42.666667 42.666667 0 0 0 42.666667-42.666667V128a42.666667 42.666667 0 0 0-85.333334 0v128a42.666667 42.666667 0 0 0 42.666667 42.666667zM682.666667 298.666667a42.666667 42.666667 0 0 0 42.666666-42.666667V128a42.666667 42.666667 0 0 0-85.333333 0v128a42.666667 42.666667 0 0 0 42.666667 42.666667zM341.333333 768a42.666667 42.666667 0 0 0 42.666667-42.666667 128 128 0 0 1 256 0 42.666667 42.666667 0 0 0 85.333333 0 213.333333 213.333333 0 0 0-107.52-184.32A128 128 0 0 0 640 469.333333a128 128 0 0 0-256 0 128 128 0 0 0 22.186667 71.68A213.333333 213.333333 0 0 0 298.666667 725.333333a42.666667 42.666667 0 0 0 42.666666 42.666667z m128-298.666667a42.666667 42.666667 0 1 1 42.666667 42.666667 42.666667 42.666667 0 0 1-42.666667-42.666667z" p-id="5263"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/message.svg b/zhitan-vue/src/assets/icons/svg/message.svg
new file mode 100644
index 0000000..14ca817
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/message.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 20.967v59.59c0 11.59 8.537 20.966 19.075 20.966h28.613l1 26.477L76.8 101.523h32.125c10.538 0 19.075-9.377 19.075-20.966v-59.59C128 9.377 119.463 0 108.925 0h-89.85C8.538 0 0 9.377 0 20.967zm82.325 33.1c0-5.524 4.013-9.935 9.037-9.935 5.026 0 9.038 4.41 9.038 9.934 0 5.524-4.025 9.934-9.038 9.934-5.024 0-9.037-4.41-9.037-9.934zm-27.613 0c0-5.524 4.013-9.935 9.038-9.935s9.037 4.41 9.037 9.934c0 5.524-4.025 9.934-9.037 9.934-5.025 0-9.038-4.41-9.038-9.934zm-27.1 0c0-5.524 4.013-9.935 9.038-9.935s9.038 4.41 9.038 9.934c0 5.524-4.026 9.934-9.05 9.934-5.013 0-9.025-4.41-9.025-9.934z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/money.svg b/zhitan-vue/src/assets/icons/svg/money.svg
new file mode 100644
index 0000000..c1580de
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/money.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/monitor.svg b/zhitan-vue/src/assets/icons/svg/monitor.svg
new file mode 100644
index 0000000..bc308cb
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/monitor.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
+</style></defs><path d="M64 64V640H896V64H64zM0 0h960v704H0V0z" p-id="4696"></path><path d="M192 896H768v64H192zM448 640H512v256h-64z" p-id="4697"></path><path d="M479.232 561.604267l309.9904-348.330667-47.803733-42.5472-259.566934 291.669333L303.957333 240.008533 163.208533 438.6048l52.224 37.009067 91.6224-129.28z" p-id="4698"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/nested.svg b/zhitan-vue/src/assets/icons/svg/nested.svg
new file mode 100644
index 0000000..06713a8
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/nested.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/number.svg b/zhitan-vue/src/assets/icons/svg/number.svg
new file mode 100644
index 0000000..ad5ce9a
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/number.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802851180" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2867" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M279.272727 791.272727h512a46.545455 46.545455 0 0 1 0 93.090909H279.272727a46.545455 46.545455 0 0 1 0-93.090909z m33.838546-617.984V651.636364H193.722182V395.170909c0-37.003636-0.884364-59.298909-2.653091-66.746182a24.948364 24.948364 0 0 0-14.615273-16.989091c-8.005818-3.863273-25.786182-5.771636-53.341091-5.771636h-11.822545v-55.854545c57.716364-12.381091 101.562182-37.888 131.490909-76.520728h70.283636z m303.709091 396.8V651.636364H354.164364v-68.235637c77.777455-127.255273 124.043636-206.010182 138.705454-236.218182 14.661818-30.254545 22.016-53.853091 22.016-70.74909 0-13.032727-2.234182-22.714182-6.656-29.137455-4.421818-6.376727-11.170909-9.588364-20.247273-9.588364a22.248727 22.248727 0 0 0-20.200727 10.612364c-4.468364 7.121455-6.656 21.178182-6.656 42.263273v45.521454H354.164364v-17.454545c0-26.763636 1.396364-47.941818 4.142545-63.348364 2.746182-15.499636 9.541818-30.72 20.386909-45.661091 10.798545-14.987636 24.901818-26.298182 42.216727-33.978182 17.361455-7.68 38.167273-11.543273 62.37091-11.543272 47.476364 0 83.316364 11.776 107.706181 35.328 24.296727 23.552 36.445091 53.341091 36.445091 89.367272 0 27.368727-6.842182 56.32-20.48 86.853819-13.730909 30.533818-54.039273 95.325091-121.018182 194.420363h130.885819z m270.615272-189.393454c18.152727 6.097455 31.650909 16.104727 40.494546 29.975272 8.843636 13.917091 13.312 46.452364 13.312 97.652364 0 38.027636-4.328727 67.490909-13.032727 88.529455-8.657455 20.945455-23.598545 36.910545-44.869819 47.848727-21.271273 10.938182-48.593455 16.384-81.873454 16.384-37.794909 0-67.490909-6.330182-89.088-19.083636-21.550545-12.660364-35.746909-28.253091-42.542546-46.638546-6.795636-18.432-10.193455-50.362182-10.193454-95.883636v-37.841455h119.389091v77.730909c0 20.666182 1.210182 33.838545 3.723636 39.424 2.420364 5.585455 7.912727 8.424727 16.337455 8.424728 9.309091 0 15.36-3.537455 18.338909-10.612364 2.932364-7.121455 4.421818-25.6 4.421818-55.575273v-33.047273c0-18.338909-2.048-31.744-6.190546-40.215272a30.72 30.72 0 0 0-18.338909-16.709818c-8.052364-2.653091-23.738182-4.189091-46.964363-4.561455V357.050182c28.392727 0 45.893818-1.070545 52.596363-3.258182a22.946909 22.946909 0 0 0 14.475637-14.149818c2.932364-7.307636 4.421818-18.711273 4.421818-34.257455v-26.624c0-16.756364-1.722182-27.741091-5.12-33.047272-3.490909-5.352727-8.843636-8.005818-16.151273-8.005819-8.285091 0-13.963636 2.792727-16.989091 8.378182-3.025455 5.632-4.561455 17.640727-4.561454 35.933091v39.284364h-119.389091v-40.773818c0-45.661091 10.472727-76.567273 31.325091-92.625455 20.898909-16.058182 54.085818-24.064 99.607272-24.064 56.878545 0 95.511273 11.170909 115.805091 33.373091 20.293818 22.248727 30.394182 53.201455 30.394182 92.765091 0 26.810182-3.630545 46.173091-10.891636 58.088727-7.307636 11.915636-20.107636 22.807273-38.446546 32.628364z" p-id="2868"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/online.svg b/zhitan-vue/src/assets/icons/svg/online.svg
new file mode 100644
index 0000000..330a202
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/online.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1568899557259" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="535" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M356.246145 681.56286c-68.156286-41.949414-107.246583-103.84102-107.246583-169.805384 0-65.966411 39.090297-127.860063 107.246583-169.809477 12.046361-7.414877 15.800871-23.190165 8.385994-35.236526-7.413853-12.046361-23.191188-15.801894-35.236526-8.387018-39.640836 24.399713-72.539106 56.044434-95.137801 91.515297-23.86657 37.461193-36.481889 79.620385-36.481889 121.917724 0 42.297338 12.615319 84.454484 36.481889 121.914654 22.598694 35.469839 55.496965 67.11456 95.137801 91.51325 4.185322 2.576685 8.821923 3.804652 13.400195 3.804652 8.598842 0 16.998139-4.329609 21.836331-12.190647C372.047016 704.752002 368.291482 688.976714 356.246145 681.56286zM263.943926 754.580874c-92.603071-61.111846-145.713686-149.623739-145.713686-242.840794 0-93.195565 53.094242-181.682899 145.667637-242.774279 11.805884-7.79043 15.061021-23.677259 7.269567-35.483142-7.79043-11.805884-23.677259-15.062044-35.483142-7.269567C128.487861 296.954249 67.006602 401.024489 67.006602 511.74008c0 110.73708 61.496609 214.830857 168.721703 285.593504 4.343935 2.867304 9.240455 4.238534 14.08274 4.238534 8.317433 0 16.476253-4.046153 21.400403-11.507078C279.003923 778.258133 275.748786 762.372328 263.943926 754.580874zM788.660552 226.213092c-11.80486-7.791453-27.692712-4.536316-35.483142 7.269567-7.79043 11.805884-4.536316 27.692712 7.269567 35.483142 92.575442 61.092403 145.670707 149.579737 145.670707 242.774279 0 93.216032-53.111638 181.727924-145.715733 242.840794-11.805884 7.79043-15.059997 23.678282-7.269567 35.484166 4.925173 7.461949 13.081946 11.507078 21.400403 11.507078 4.841262 0 9.739828-1.37123 14.083763-4.238534 107.22714-70.761624 168.724773-174.857447 168.724773-285.593504C957.341323 401.025513 895.860063 296.955272 788.660552 226.213092zM790.090111 633.67213c23.865547-37.459147 36.480866-79.617315 36.480866-121.914654 0-42.298362-12.615319-84.45653-36.480866-121.917724-22.598694-35.470863-55.496965-67.115584-95.139847-91.515297-12.047384-7.413853-27.821649-3.659343-35.236526 8.387018-7.414877 12.045337-3.659343 27.821649 8.385994 35.236526 68.156286 41.949414 107.247606 103.842043 107.247606 169.809477 0 65.964364-39.090297 127.85597-107.247606 169.804361-12.045337 7.414877-15.800871 23.190165-8.385994 35.237549 4.838192 7.861038 13.236466 12.190647 21.835308 12.190647 4.579295 0 9.215896-1.227967 13.400195-3.804652C734.591099 700.786691 767.490394 669.142993 790.090111 633.67213zM567.129086 518.274914c24.12342-17.150612 39.887452-45.305859 39.887452-77.07133 0-52.128241-42.452881-94.538143-94.634334-94.538143-52.18043 0-94.633311 42.408879-94.633311 94.538143 0 31.695886 15.696494 59.797921 39.730886 76.958766-49.875944 21.128203-84.917018 70.234621-84.917018 127.301338 0 2.366907 0.061398 4.762467 0.182149 7.119141l1.249457 24.296359 276.373515 0 1.238201-24.308639c0.119727-2.358721 0.181125-4.750187 0.181125-7.106862C651.786185 588.497255 616.865861 539.465538 567.129086 518.274914zM512.381182 397.889079c23.937179 0 43.411719 19.430538 43.411719 43.314505 0 23.882943-19.47454 43.313481-43.411719 43.313481-23.936155 0-43.409672-19.430538-43.409672-43.313481C468.971509 417.320641 488.445026 397.889079 512.381182 397.889079zM426.08884 625.656573c9.119705-38.542828 44.254923-67.337641 86.085634-67.337641s76.966952 28.794813 86.085634 67.337641L426.08884 625.656573z" p-id="536"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/password.svg b/zhitan-vue/src/assets/icons/svg/password.svg
new file mode 100644
index 0000000..6c64def
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/password.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802846045" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2750" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M868.593046 403.832442c-30.081109-28.844955-70.037123-44.753273-112.624057-44.753273L265.949606 359.079168c-42.554188 0-82.510202 15.908318-112.469538 44.690852-30.236652 28.782533-46.857191 67.222007-46.857191 108.198258l0 294.079782c0 40.977273 16.619516 79.414701 46.702672 108.136859 29.959336 28.844955 70.069869 44.814672 112.624057 44.814672l490.019383 0c42.585911 0 82.696444-15.969717 112.624057-44.814672 30.082132-28.844955 46.579875-67.222007 46.579875-108.136859L915.172921 511.968278C915.171897 471.053426 898.675178 432.677397 868.593046 403.832442zM841.821309 806.049083c0 22.098297-8.882298 42.772152-25.099654 58.306964-16.154935 15.661701-37.81935 24.203238-60.752666 24.203238L265.949606 888.559285c-22.934339 0-44.567032-8.54256-60.877509-24.264637-16.186657-15.474436-25.067932-36.148291-25.067932-58.246589L180.004165 511.968278c0-22.035876 8.881274-42.772152 25.192775-58.307987 16.186657-15.536858 37.81935-24.139793 60.753689-24.139793l490.019383 0c22.933315 0 44.597731 8.602935 60.752666 24.139793 16.21838 15.535835 25.099654 36.272112 25.099654 58.307987L841.822332 806.049083zM510.974136 135.440715c114.914216 0 208.318536 89.75214 208.318536 200.055338l73.350588 0c0-149.113109-126.366036-270.496667-281.669124-270.496667-155.333788 0-281.699824 121.383558-281.699824 270.496667l73.350588 0C302.623877 225.193879 396.059919 135.440715 510.974136 135.440715zM474.299865 747.244792l73.350588 0L547.650453 629.576859l-73.350588 0L474.299865 747.244792z" p-id="2751"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/pdf.svg b/zhitan-vue/src/assets/icons/svg/pdf.svg
new file mode 100644
index 0000000..957aa0c
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/pdf.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path d="M869.073 277.307H657.111V65.344l211.962 211.963zm-238.232 26.27V65.344l-476.498-.054v416.957h714.73v-178.67H630.841zm-335.836 360.57c-5.07-3.064-10.944-5.133-17.61-6.201-6.67-1.064-13.603-1.6-20.81-1.6h-48.821v85.641h48.822c7.206 0 14.14-.532 20.81-1.6 6.665-1.065 12.54-3.133 17.609-6.202 5.064-3.063 9.134-7.406 12.208-13.007 3.065-5.602 4.6-12.937 4.6-22.011 0-9.07-1.535-16.408-4.6-22.01-3.074-5.603-7.144-9.94-12.208-13.01zM35.82 541.805v416.904h952.358V541.805H35.821zm331.421 191.179c-3.6 11.071-9.343 20.879-17.209 29.413-7.874 8.542-18.078 15.408-30.617 20.61-12.544 5.206-27.747 7.807-45.621 7.807h-66.036v102.45h-62.831V607.517h128.867c17.874 0 33.077 2.6 45.62 7.802 12.541 5.207 22.745 12.076 30.618 20.615 7.866 8.538 13.604 18.277 17.21 29.212 3.6 10.943 5.401 22.278 5.401 34.018 0 11.477-1.8 22.752-5.402 33.819zM644.9 806.417c-5.343 17.61-13.408 32.818-24.212 45.627-10.807 12.803-24.283 22.879-40.423 30.213-16.146 7.343-35.155 11.007-57.03 11.007h-123.26V607.518h123.26c18.41 0 35.552 2.941 51.428 8.808 15.873 5.869 29.618 14.671 41.22 26.412 11.608 11.744 20.674 26.411 27.217 44.02 6.535 17.61 9.803 38.288 9.803 62.035 0 20.81-2.67 40.02-8.003 57.624zm245.362-146.07h-138.07v66.03h119.66v48.829h-119.66v118.058h-62.83V607.518h200.9v52.829h-.001zm-318.2 25.611c-6.402-8.266-14.877-14.604-25.412-19.01-10.544-4.402-23.551-6.602-39.019-6.602h-44.825v180.088h56.029c9.07 0 17.872-1.463 26.415-4.401 8.535-2.932 16.14-7.802 22.812-14.609 6.665-6.8 12.007-15.667 16.007-26.61 4.003-10.94 6.003-24.275 6.003-40.021 0-14.408-1.4-27.416-4.202-39.019-2.8-11.607-7.406-21.542-13.808-29.816zm0 0"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/people.svg b/zhitan-vue/src/assets/icons/svg/people.svg
new file mode 100644
index 0000000..2bd54ae
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/people.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M104.185 95.254c8.161 7.574 13.145 17.441 13.145 28.28 0 1.508-.098 2.998-.285 4.466h-10.784c.238-1.465.403-2.948.403-4.465 0-8.983-4.36-17.115-11.419-23.216C86 104.66 75.355 107.162 64 107.162c-11.344 0-21.98-2.495-31.22-6.83-7.064 6.099-11.444 14.218-11.444 23.203 0 1.517.165 3 .403 4.465H10.955a35.444 35.444 0 0 1-.285-4.465c0-10.838 4.974-20.713 13.127-28.291C9.294 85.42.003 70.417.003 53.58.003 23.99 28.656.001 64 .001s63.997 23.988 63.997 53.58c0 16.842-9.299 31.85-23.812 41.673zM64 36.867c-29.454 0-53.33-10.077-53.33 15.342 0 25.418 23.876 46.023 53.33 46.023 29.454 0 53.33-20.605 53.33-46.023 0-25.419-23.876-15.342-53.33-15.342zm24.888 25.644c-3.927 0-7.111-2.665-7.111-5.953 0-3.288 3.184-5.954 7.11-5.954 3.928 0 7.111 2.666 7.111 5.954s-3.183 5.953-7.11 5.953zm-3.556 16.372c0 4.11-9.55 7.442-21.332 7.442-11.781 0-21.332-3.332-21.332-7.442 0-1.06.656-2.064 1.8-2.976 3.295 2.626 10.79 4.465 19.532 4.465 8.743 0 16.237-1.84 19.531-4.465 1.145.912 1.801 1.916 1.801 2.976zm-46.22-16.372c-3.927 0-7.11-2.665-7.11-5.953 0-3.288 3.183-5.954 7.11-5.954 3.927 0 7.111 2.666 7.111 5.954s-3.184 5.953-7.11 5.953z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/peoples.svg b/zhitan-vue/src/assets/icons/svg/peoples.svg
new file mode 100644
index 0000000..aab852e
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/peoples.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M95.648 118.762c0 5.035-3.563 9.121-7.979 9.121H7.98c-4.416 0-7.979-4.086-7.979-9.121C0 100.519 15.408 83.47 31.152 76.75c-9.099-6.43-15.216-17.863-15.216-30.987v-9.128c0-20.16 14.293-36.518 31.893-36.518s31.894 16.358 31.894 36.518v9.122c0 13.137-6.123 24.556-15.216 30.993 15.738 6.726 31.141 23.769 31.141 42.012z"/><path d="M106.032 118.252h15.867c3.376 0 6.101-3.125 6.101-6.972 0-13.957-11.787-26.984-23.819-32.123 6.955-4.919 11.638-13.66 11.638-23.704v-6.985c0-15.416-10.928-27.926-24.39-27.926-1.674 0-3.306.193-4.89.561 1.936 4.713 3.018 9.974 3.018 15.526v9.121c0 13.137-3.056 23.111-11.066 30.993 14.842 4.41 27.312 23.42 27.541 41.509z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/phone.svg b/zhitan-vue/src/assets/icons/svg/phone.svg
new file mode 100644
index 0000000..ab8e8c4
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/phone.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1567417214476" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2266" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M761.503029 2.90619 242.121921 2.90619c-32.405037 0-58.932204 26.060539-58.932204 58.527998l0 902.302287c0 32.156374 26.217105 58.216913 58.932204 58.216913l519.381108 0c32.344662 0 58.591443-26.060539 58.591443-58.216913L820.094472 61.123103C820.094472 28.966729 793.847691 2.90619 761.503029 2.90619M452.878996 61.123103l98.147344 0c6.780427 0 12.31549 5.536087 12.31549 12.253068 0 6.748704-5.535063 12.253068-12.31549 12.253068l-98.147344 0c-6.779404 0-12.345166-5.504364-12.345166-12.253068C440.532807 66.659189 446.099592 61.123103 452.878996 61.123103M501.641583 980.593398c-29.636994 0-53.987588-23.946388-53.987588-53.677527 0-29.356608 24.039509-53.614082 53.987588-53.614082 29.91738 0 53.987588 23.883967 53.987588 53.614082C555.629171 956.647009 531.559986 980.593398 501.641583 980.593398M766.35657 803.142893c0 16.23373-13.186324 29.107945-29.233811 29.107945l-470.618521 0c-16.35755 0-29.325909-13.186324-29.325909-29.107945L237.178329 163.500794c0-16.232706 13.279445-29.138644 29.325909-29.138644l470.246037 0c16.420995 0 29.357632 13.1853 29.357632 29.138644l0 639.642099L766.35657 803.142893zM766.35657 803.142893" p-id="2267"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/post.svg b/zhitan-vue/src/assets/icons/svg/post.svg
new file mode 100644
index 0000000..2922c61
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/post.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566035724641" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3998" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M136.4 434.3h77.7c21.5 0 38.9-17.4 38.9-38.9s-17.4-38.9-38.9-38.9h-77.7c-21.5 0-38.9 17.4-38.9 38.9s17.4 38.9 38.9 38.9zM252.9 628.6c0-21.5-17.4-38.9-38.9-38.9h-77.7c-21.5 0-38.9 17.4-38.9 38.9s17.4 38.9 38.9 38.9H214c21.5-0.1 38.9-17.5 38.9-38.9z" p-id="3999"></path><path d="M874.7 97.5H227c-28.6 0-51.8 23.2-51.8 51.8v194.3h38.9c28.6 0 51.8 23.2 51.8 51.8 0 28.6-23.2 51.8-51.8 51.8h-38.9v129.5h38.9c28.6 0 51.8 23.2 51.8 51.8 0 28.6-23.2 51.8-51.8 51.8h-38.9v194.3c0 28.6 23.2 51.8 51.8 51.8h647.7c28.6 0 51.8-23.2 51.8-51.8V149.3c0-28.6-23.2-51.8-51.8-51.8z m-311.3 723c-15.6 0-146.7-71.6-146.7-91 0-19.4 102-368.6 102-368.6l-83.6-104s-12.3-23.1 24.6-23.1h208.9c36.9 0 18.4 23.1 18.4 23.1l-79 104s102 351.3 102 368.6c0.1 17.3-131 91-146.6 91z m169.2-253.6l-27.9 40.2-74.5-240 103.4 171.7c4.6 7.9 4.2 20.6-1 28.1z" p-id="4000"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/qq.svg b/zhitan-vue/src/assets/icons/svg/qq.svg
new file mode 100644
index 0000000..ee13d4e
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/qq.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M18.448 57.545l-.244-.744-.198-.968-.132-.53v-2.181l.236-.859.24-.908.317-.953.428-1.06.561-1.103.794-1.104v-.773l.077-.724.123-.984.34-1.106.313-1.194.25-.548.289-.511.371-.569.405-.423v-2.73l.234-1.407.236-1.633.42-1.955.577-2.035.43-1.118.426-1.217.468-1.135.559-1.216.57-1.332.655-1.247.737-1.331.929-1.33.43-.762.457-.624.995-1.406 1.025-1.403 1.163-1.444 1.246-1.405 1.352-1.384 1.41-1.423 1.708-1.536 1.083-.934 1.322-1.008 1.34-.89 1.448-.855 1.392-.76 1.57-.63 1.667-.775 1.657-.532 1.653-.552 1.787-.548 1.785-.417 1.876-.347L59.128.68l1.879-.245 1.876-.252 2.002-.106h5.912l1.97.243 1.981.231 2.019.207 1.874.441 1.979.413 1.857.475 2.035.53 1.862.646 1.782.738 1.904.78 1.736.853 1.689.95 1.655 1.044 1.425.971.662.548.693.401 1.323 1.1 1.115 1.064 1.112 1.1 1.083 1.214.894 1.178 1.064 1.217.74 1.306.752 1.162.798 1.352.661 1.175 1.113 2.489.546 1.286.428 1.192.428 1.294.384 1.217.267 1.047.347 1.231.607 2.198.388 1.924.253 1.861.217 1.497.342 2.28.077.362.274.41.737 1.18.473.8.42.832.534.892.472 1.07.307 1.093.334 1.2.252 1.232.115.605.106.746v.648l-.106.643v.8l-.192.774-.35 1.5-.403.76-.299.852v.213l.142.264.4.623 1.746 2.53 1.377 1.9.66 1.267.889 1.389.774 1.52.893 1.627.894 1.828 1.006 2.069.567 1.268.518 1.239.447 1.307.44 1.175.336 1.235.342 1.16.432 2.261.343 2.31.235 2.05v2.891l-.158 1.025-.226 1.768-.308 1.59-.48 1.44-.18.588-.336.707-.28.493-.375.607-.33.383-.42.494-.375.4-.401.34-.48.207-.432.207-.355.114h-.543l-.346-.114-.66-.32-.302-.212-.317-.223-.347-.304-.35-.342-.579-.63-.684-.89-.539-.917-.538-.734-.526-.855-.741-1.517-.833-1.579-.098-.055h-.138l-.338.247-.196.415-.326.516-.567 1.533-.856 2.182-1.096 2.626-.824 1.308-.864 1.366-1.027 1.536-1.09 1.503-.557.68-.676.743-1.555 1.497.136.135.21.214.777.446 3.235 1.524 1.41.779 1.347.756 1.332.953 1.187.982.574.443.432.511.445.593.367.643.198.533.242.64.105.554.115.647-.115.433v.44l-.105.454-.242.415-.092.325-.22.394-.587.784-.543.627-.42.47-.35.348-.893.638-1.01.556-1.077.532-1.155.511-1.287.495-.693.207-.608.167-1.496.342-1.545.325-1.552.323-1.689.27-1.74.072-1.785.21h-5.539l-1.998-.114-1.86-.168-2.005-.27-1.99-.209-2.095-.286-2.03-.495-1.981-.374-1.968-.552-2.019-.707-1.98-.585-1.044-.342-.927-.323-.586-.223-.582-.12h-1.647l-1.904-.131-.962-.096-1.24-.135-.795.705-1.085.665-1.471.701-1.628.875-.99.475-1.033.376-2.281.914-1.24.305-1.3.343-1.803.344-1.13.086-1.193.1-1.246.135-1.45.053h-5.926l-3.346-.053-3.25-.321-1.644-.23-1.589-.23-1.546-.227-1.547-.305-1.442-.456-1.434-.325-1.294-.51-1.223-.474-1.142-.533-.99-.583-.984-.71-.336-.343-.44-.415-.334-.362-.3-.417-.278-.415-.215-.42-.311-.89-.109-.46-.138-.51v-.473l.138-.533v-.53l.109-.53v-1.069l.052-.564.259-.647.215-.646.39-.779.286-.3.236-.348.615-.738.49-.38.464-.266.428-.338.676-.21.543-.324.676-.341.77-.227.775-.231.897-.192.85-.11 1.008-.13 1.093-.081.284-.092h.063l.137-.115v-.13l-.2-.266-.58-.27-1.45-1.231-.975-.761-1.127-.967-1.136-1.082-1.181-1.382-1.36-1.558-.508-.843-.672-.87-.58-1.007-.522-1.1-.704-1.047-.459-1.194-.547-1.192-.546-1.33-.397-1.273-.378-1.575-.112-.057h-.115l-.059-.113h-.14l-.23.113-.114.057-.158.264-.057.321-.119.286-.206.477-.664 1.157-.345.701-.546.612-.58.736-.641.816-.677.724-.795.701-.734.658-.814.524-.89.546-.855.325-1.008.247-.99.095h-.233l-.228-.095-.18-.384-.29-.188-.38-.912-.237-.493-.255-.707-.21-.734-.113-.724-.313-1.648-.12-.972v-3.185l.12-2.379.196-1.214.23-1.252.21-1.347.374-1.254.42-1.443.431-1.407.578-1.448.545-1.38.754-1.4.699-1.52.855-1.425 1.006-1.538 1.023-1.382 1.069-1.538.891-1.071 1.142-1.227 1.202-1.237.56-.59.678-.662.985-.836 1.012-.853 1.647-1.446 1.242-.889z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/question.svg b/zhitan-vue/src/assets/icons/svg/question.svg
new file mode 100644
index 0000000..cf75bd4
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/question.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1581238842264" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1409" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 0C229.233778 0 0 229.233778 0 512s229.233778 512 512 512 512-229.233778 512-512A512 512 0 0 0 512 0z m0 938.666667C276.366222 938.666667 85.333333 747.633778 85.333333 512 85.333333 276.366222 276.366222 85.333333 512 85.333333c235.633778 0 426.666667 191.032889 426.666667 426.666667a426.666667 426.666667 0 0 1-426.666667 426.666667z m0-717.653334a170.666667 170.666667 0 0 0-170.666667 170.666667 42.666667 42.666667 0 0 0 85.333334 0 85.333333 85.333333 0 1 1 85.333333 85.333333 42.666667 42.666667 0 0 0-42.666667 42.666667v111.36a42.666667 42.666667 0 0 0 85.333334 0v-74.24A170.666667 170.666667 0 0 0 512 221.013333z m-42.666667 542.293334a42.666667 42.666667 0 1 0 85.333334 0 42.666667 42.666667 0 0 0-85.333334 0z" p-id="1410"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/radio.svg b/zhitan-vue/src/assets/icons/svg/radio.svg
new file mode 100644
index 0000000..0cde345
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/radio.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575966775973" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="879" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M507.39346659 71.84873358c241.53533667 0 437.39770766 195.85422109 437.39770767 437.37442191 0 241.53766571-195.86237099 437.38955776-437.39770767 437.38955776-241.50040803 0-437.34997219-195.85189205-437.34997219-437.38955776C70.0434944 267.70295467 265.89189347 71.84873358 507.39346659 71.84873358L507.39346659 71.84873358zM507.39346659 282.81899805c-125.00686734 0-226.37039389 101.38914133-226.37039388 226.41813048 0 125.01268821 101.36352768 226.39717262 226.37039388 226.39717262 125.04295993 0 226.42395136-101.38448441 226.42395136-226.39717262C733.81625401 384.20813938 632.43642653 282.81899805 507.39346659 282.81899805L507.39346659 282.81899805zM507.39346659 120.78172615c-214.46664192 0-388.42047261 173.95150279-388.4204726 388.44026539 0 214.51204949 173.95499463 388.46122325 388.4204726 388.46122325 214.52369237 0 388.46005817-173.94800981 388.46005818-388.46122325C895.85236082 294.73322894 721.91715897 120.78172615 507.39346659 120.78172615z" p-id="880"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/rate.svg b/zhitan-vue/src/assets/icons/svg/rate.svg
new file mode 100644
index 0000000..aa3b14d
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/rate.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577246781606" class="icon" viewBox="0 0 1069 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1098" xmlns:xlink="http://www.w3.org/1999/xlink" width="84.5595703125" height="81"><defs><style type="text/css"></style></defs><path d="M633.72929961 378.02038203l9.49872568 18.68789795 20.78025469 2.79745225 206.61592412 27.33248408a11.46496817 11.46496817 0 0 1 6.6095543 19.47324902l-147.2675168 147.35350284-14.89299345 14.89299345 3.8006376 20.68280244 37.84585956 204.89044571a11.46496817 11.46496817 0 0 1-16.4808914 12.2961788L554.68980898 751.84713388l-18.68789794-9.49299345-18.48726123 9.99171915-183.23885392 99.34968163a11.46496817 11.46496817 0 0 1-16.78471347-11.8662416l32.5433127-205.79617881 3.29617793-20.78598692-15.19108243-14.49172002-151.03375839-143.48407587a11.46496817 11.46496817 0 0 1 6.09936328-19.63949062l205.79617881-32.63503185 20.78598691-3.2961788L428.87898125 380.72038203 518.59235674 192.64331182a11.46496817 11.46496817 0 0 1 20.56815264-0.26369385l94.56879023 185.63503183zM496.64840732 85.52038203l-121.75796162 254.98089229L95.76433145 384.76178369A34.3949045 34.3949045 0 0 0 77.46050938 443.66879023l204.87324901 194.66369385-44.16879023 279.1146498a34.3949045 34.3949045 0 0 0 50.36560489 35.61592325l248.4-134.67898038 251.84522285 128.27579591a34.3949045 34.3949045 0 0 0 49.43694287-36.89426777l-51.30573223-277.85350284 199.73120977-199.90891758a34.3949045 34.3949045 0 0 0-19.82866201-58.40827998l-280.11783428-37.03184736L558.32993633 84.71210205a34.3949045 34.3949045 0 0 0-61.68152901 0.80254775z" p-id="1099"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/redis-list.svg b/zhitan-vue/src/assets/icons/svg/redis-list.svg
new file mode 100644
index 0000000..98a15b2
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/redis-list.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1656035183065" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3395" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
+</style></defs><path d="M958.88 730.06H65.12c-18.28 0-33.12-14.82-33.12-33.12V68.91c0-18.29 14.83-33.12 33.12-33.12h893.77c18.28 0 33.12 14.82 33.12 33.12v628.03c-0.01 18.3-14.84 33.12-33.13 33.12zM98.23 663.83h827.53v-561.8H98.23v561.8z" p-id="3396"></path><path d="M512 954.55c-18.28 0-33.12-14.82-33.12-33.12V733.92c0-18.29 14.83-33.12 33.12-33.12s33.12 14.82 33.12 33.12v187.51c0 18.3-14.84 33.12-33.12 33.12z" p-id="3397"></path><path d="M762.01 988.21H261.99c-18.28 0-33.12-14.82-33.12-33.12 0-18.29 14.83-33.12 33.12-33.12h500.03c18.28 0 33.12 14.82 33.12 33.12-0.01 18.29-14.84 33.12-33.13 33.12zM514.74 578.55c-21.63 0-43.31-3.87-64.21-11.65-45.95-17.13-82.49-51.13-102.86-95.74-5.07-11.08-0.19-24.19 10.89-29.26 11.08-5.09 24.19-0.18 29.26 10.91 15.5 33.88 43.25 59.7 78.14 72.71 34.93 12.99 72.79 11.64 106.66-3.85 33.22-15.17 58.8-42.26 72.03-76.3 4.42-11.37 17.21-17.01 28.57-12.58 11.36 4.42 16.99 17.22 12.57 28.58-17.42 44.82-51.1 80.5-94.82 100.47-24.34 11.12-50.25 16.71-76.23 16.71z" p-id="3398"></path><path d="M325.27 528.78c-1.66 0-3.34-0.18-5.02-0.57-11.88-2.77-19.28-14.63-16.49-26.51l18.84-81c1.34-5.82 5-10.84 10.13-13.92 5.09-3.09 11.3-3.96 17.03-2.41l80.51 21.43c11.79 3.14 18.8 15.23 15.67 27.02-3.15 11.79-15.42 18.75-27.02 15.65l-58.49-15.57-13.69 58.81c-2.37 10.2-11.45 17.07-21.47 17.07zM360.8 351.01c-2.65 0-5.37-0.49-8-1.51-11.36-4.41-16.99-17.21-12.59-28.57 17.4-44.79 51.06-80.47 94.8-100.48 92.15-42.06 201.25-1.39 243.31 90.68 5.07 11.08 0.19 24.19-10.89 29.26-11.13 5.07-24.19 0.17-29.26-10.91-31.97-69.91-114.9-100.82-184.79-68.86-33.22 15.19-58.8 42.28-71.99 76.29-3.41 8.74-11.75 14.1-20.59 14.1z" p-id="3399"></path><path d="M684.68 376.74c-1.47 0-2.95-0.15-4.42-0.44l-81.61-16.68c-11.94-2.45-19.64-14.11-17.21-26.06 2.44-11.96 14.1-19.64 26.04-17.22l59.29 12.12 10.23-59.5c2.05-12 13.52-20.19 25.48-18.01 12.03 2.06 20.09 13.48 18.02 25.5l-14.08 81.96a22.089 22.089 0 0 1-9.29 14.49c-3.7 2.51-8.03 3.84-12.45 3.84z" p-id="3400"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/redis.svg b/zhitan-vue/src/assets/icons/svg/redis.svg
new file mode 100644
index 0000000..2f1d62d
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/redis.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1605865043777" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="856" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1023.786667 611.84c-0.426667 9.770667-13.354667 20.693333-39.893334 34.56-54.613333 28.458667-337.749333 144.896-397.994666 176.298667-60.288 31.402667-93.738667 31.104-141.354667 8.32-47.616-22.741333-348.842667-144.469333-403.114667-170.368-27.093333-12.970667-40.917333-23.893333-41.386666-34.218667v103.509333c0 10.325333 14.250667 21.290667 41.386666 34.261334 54.272 25.941333 355.541333 147.626667 403.114667 170.368 47.616 22.784 81.066667 23.082667 141.354667-8.362667 60.245333-31.402667 343.338667-147.797333 397.994666-176.298667 27.776-14.464 40.106667-25.728 40.106667-35.925333v-102.058667l-0.213333-0.085333z m0-168.746667c-0.512 9.770667-13.397333 20.650667-39.893334 34.517334-54.613333 28.458667-337.749333 144.896-397.994666 176.298666-60.288 31.402667-93.738667 31.104-141.354667 8.362667-47.616-22.741333-348.842667-144.469333-403.114667-170.410667-27.093333-12.928-40.917333-23.893333-41.386666-34.176v103.509334c0 10.325333 14.250667 21.248 41.386666 34.218666 54.272 25.941333 355.498667 147.626667 403.114667 170.368 47.616 22.784 81.066667 23.082667 141.354667-8.32 60.245333-31.402667 343.338667-147.84 397.994666-176.298666 27.776-14.506667 40.106667-25.770667 40.106667-35.968v-102.058667l-0.256-0.042667z m0-175.018666c0.469333-10.410667-13.141333-19.541333-40.533334-29.610667-53.248-19.498667-334.634667-131.498667-388.522666-151.253333-53.888-19.712-75.818667-18.901333-139.093334 3.84C392.234667 113.706667 92.629333 231.253333 39.338667 252.074667c-26.666667 10.496-39.68 20.181333-39.253334 30.506666V386.133333c0 10.325333 14.250667 21.248 41.386667 34.218667 54.272 25.941333 355.498667 147.669333 403.114667 170.410667 47.616 22.741333 81.066667 23.04 141.354666-8.362667 60.245333-31.402667 343.338667-147.84 397.994667-176.298667 27.776-14.506667 40.106667-25.770667 40.106667-35.968V268.074667h-0.341334zM366.677333 366.08l237.269334-36.437333-71.68 105.088-165.546667-68.650667z m524.8-94.634667l-140.330666 55.466667-15.232 5.973333-140.245334-55.466666 155.392-61.44 140.373334 55.466666z m-411.989333-101.674666l-22.954667-42.325334 71.594667 27.989334 67.498667-22.101334-18.261334 43.733334 68.778667 25.770666-88.704 9.216-19.882667 47.786667-32.085333-53.290667-102.4-9.216 76.416-27.562666z m-176.768 59.733333c70.058667 0 126.805333 21.973333 126.805333 49.109333s-56.746667 49.152-126.805333 49.152-126.848-22.058667-126.848-49.152c0-27.136 56.789333-49.152 126.848-49.152z" p-id="857"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/row.svg b/zhitan-vue/src/assets/icons/svg/row.svg
new file mode 100644
index 0000000..0780992
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/row.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579339929870" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1182" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M152 854.856875h325.7146875V237.715625H134.856875v600q0 6.99375 5.0746875 12.0684375T152 854.856875z m737.143125-17.1421875v-600H546.284375v617.1421875H872q6.99375 0 12.0684375-5.07375t5.0746875-12.0684375z m68.5715625-651.429375V837.715625q0 35.3821875-25.16625 60.5484375T872 923.4284375H152q-35.383125 0-60.5484375-25.1653125T66.284375 837.7146875V186.284375q0-35.3821875 25.16625-60.5484375T152 100.5715625h720q35.383125 0 60.5484375 25.1653125t25.16625 60.5484375z" p-id="1183"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/search.svg b/zhitan-vue/src/assets/icons/svg/search.svg
new file mode 100644
index 0000000..84233dd
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/search.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/select.svg b/zhitan-vue/src/assets/icons/svg/select.svg
new file mode 100644
index 0000000..d628382
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/select.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575803481213" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="804" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M62 511.97954521C62 263.86590869 263.90681826 62 511.97954521 62s449.97954521 201.825 449.97954521 449.97954521c0 248.19545479-201.90681826 449.97954521-449.97954521 449.97954521C263.90681826 962 62 760.175 62 511.97954521M901.98636348 511.97954521c0-215.24318174-175.00909131-390.41590869-390.00681827-390.41590869-215.03863652 0-389.96590869 175.17272695-389.96590868 390.41590869 0 215.28409131 175.00909131 390.45681826 389.96590868 390.45681826C727.01818174 902.47727305 901.98636348 727.30454521 901.98636348 511.97954521M264.17272695 430.28409131c0-5.76818174 2.12727305-11.51590869 6.64772696-15.87272696 8.71363652-8.75454521 22.88863652-8.75454521 31.725 0l209.4340913 208.22727305L721.45454521 414.53409131c8.75454521-8.71363652 22.97045479-8.71363652 31.90909132 0 8.71363652 8.75454521 8.71363652 22.88863652 0 31.60227304L511.97954521 685.74090869 270.71818174 446.01363653C266.27954521 441.77954521 264.17272695 436.05227305 264.17272695 430.28409131" p-id="805"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/server.svg b/zhitan-vue/src/assets/icons/svg/server.svg
new file mode 100644
index 0000000..eb287e3
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/server.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547360688278" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M890 120H134a70 70 0 0 0-70 70v500a70 70 0 0 0 70 70h756a70 70 0 0 0 70-70V190a70 70 0 0 0-70-70z m-10 520a40 40 0 0 1-40 40H712V448a40 40 0 0 0-80 0v232h-80V368a40 40 0 0 0-80 0v312h-80V512a40 40 0 0 0-80 0v168H184a40 40 0 0 1-40-40V240a40 40 0 0 1 40-40h656a40 40 0 0 1 40 40zM696 824H328a40 40 0 0 0 0 80h368a40 40 0 0 0 0-80z" p-id="6718"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/shopping.svg b/zhitan-vue/src/assets/icons/svg/shopping.svg
new file mode 100644
index 0000000..87513e7
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/shopping.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 0 1 3.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 0 1-3.889 2.843 10.582 10.582 0 0 1-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 0 1-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 0 1-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 0 1 3.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 0 1 3.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 0 1-3.89 2.843 11 11 0 0 1-4.732 1.066 10.58 10.58 0 0 1-4.667-1.066 12.478 12.478 0 0 1-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 0 1-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 0 1 3.824-2.772 11.212 11.212 0 0 1 4.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 0 1 .778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 0 1-2.788 8.743 1236.373 1236.373 0 0 0-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 0 1-1.88-4.478 44.128 44.128 0 0 1-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 0 1-2.14-2.558 10.416 10.416 0 0 1-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 0 1 1.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/size.svg b/zhitan-vue/src/assets/icons/svg/size.svg
new file mode 100644
index 0000000..ddb25b8
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/size.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h54.796v18.286H36.531V128H18.265V73.143H0V54.857zm127.857-36.571H91.935V128H72.456V18.286H36.534V0h91.326l-.003 18.286z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/skill.svg b/zhitan-vue/src/assets/icons/svg/skill.svg
new file mode 100644
index 0000000..a3b7312
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/skill.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M31.652 93.206h33.401c1.44 2.418 3.077 4.663 4.93 6.692h-38.33v-6.692zm0-10.586h28.914a44.8 44.8 0 0 1-1.264-6.688h-27.65v6.688zm0-17.27H59.39c.288-2.286.714-4.532 1.34-6.687H31.65v6.687h.003zm53.913 44.84v5.85c0 2.798-2.095 5.075-4.667 5.075h-70.07c-2.576 0-4.663-2.277-4.663-5.075V31.26l23.22-20.96v22.25H17.16v6.688h18.39V6.688h45.348c2.576 0 4.667 2.277 4.667 5.066v20.009c1.987-.675 4.053-1.128 6.17-1.445v-18.56C91.738 5.28 86.874 0 80.902 0H31.15L0 28.118v87.917c0 6.48 4.859 11.759 10.832 11.759h70.07c5.974 0 10.837-5.27 10.837-11.759v-4.41c-2.117-.312-4.183-.765-6.17-1.435h-.004zM23.279 58.667h-7.96v6.688h7.96v-6.688zm-7.956 41.23h7.96v-6.691h-7.96v6.692zm7.956-23.96h-7.96v6.687h7.96v-6.688zm89.718-15.042l-4.896-4.07-12.447 17.613-11.19-9.305-3.762 5.311 16.091 13.38 16.204-22.929zM128 70.978c0-18.632-13.97-33.782-31.147-33.782-17.168 0-31.135 15.155-31.135 33.782 0 18.628 13.97 33.783 31.135 33.783 17.172 0 31.143-15.15 31.143-33.783H128zm-6.17 0c0 14.933-11.203 27.1-24.981 27.1-13.77 0-24.987-12.158-24.987-27.1 0-14.941 11.195-27.099 24.987-27.099 13.778 0 24.982 12.158 24.982 27.1z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/slider.svg b/zhitan-vue/src/assets/icons/svg/slider.svg
new file mode 100644
index 0000000..fbe4f39
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/slider.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577185310368" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1238" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M951.453125 476.84375H523.671875a131.8359375 131.8359375 0 0 0-254.1796875 0H72.546875v70.3125h196.9453125a131.8359375 131.8359375 0 0 0 254.1796875 0H951.453125z" p-id="1239"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/star.svg b/zhitan-vue/src/assets/icons/svg/star.svg
new file mode 100644
index 0000000..6cf86e6
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/star.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M70.66 4.328l14.01 29.693c1.088 2.29 3.177 3.882 5.603 4.25l31.347 4.76c6.087.926 8.528 8.756 4.117 13.247L103.05 79.395c-1.75 1.78-2.544 4.352-2.132 6.867l5.352 32.641c1.043 6.337-5.33 11.182-10.778 8.19l-28.039-15.409a7.13 7.13 0 0 0-6.91 0l-28.039 15.41c-5.448 2.99-11.821-1.854-10.777-8.19l5.352-32.642c.415-2.515-.387-5.088-2.136-6.867L2.264 56.278C-2.146 51.787.286 43.957 6.38 43.031l31.343-4.76c2.419-.368 4.51-1.96 5.595-4.25L57.334 4.328c2.728-5.77 10.605-5.77 13.325 0z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/swagger.svg b/zhitan-vue/src/assets/icons/svg/swagger.svg
new file mode 100644
index 0000000..05d4e7b
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/swagger.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1566036776944" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6463" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M64 223.995345h168.001164v47.997673c0 26.428509 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.569164 41.984-47.997673v-47.997673h504.003491a32.004655 32.004655 0 0 0 0-64.009309H455.996509V111.988364c0-26.428509-18.878836-47.997673-41.984-47.997673H273.985164c-23.095855 0-41.984 21.569164-41.984 47.997673v47.997672H64a32.004655 32.004655 0 0 0 0 64.009309zM288.004655 128h111.997672V256H288.004655V128zM960 479.995345H791.998836v-47.997672c0-26.372655-18.878836-47.997673-41.984-47.997673H609.978182c-23.095855 0-41.984 21.634327-41.984 47.997673v47.997672H64a32.004655 32.004655 0 0 0 0 64.00931h504.003491v47.997672c0 26.363345 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.634327 41.984-47.997673v-47.997672h168.001164a32.004655 32.004655 0 1 0-0.009309-64.00931zM735.995345 576H623.997673v-128h111.997672v128zM960 800.293236v-0.288581H455.996509v-47.997673c0-26.363345-18.878836-47.997673-41.984-47.997673H274.050327c-23.105164 0-41.984 21.634327-41.984 47.997673v47.997673H64v0.288581a32.004655 32.004655 0 0 0 0 64.009309c0.986764 0 1.917673-0.195491 2.885818-0.288581h165.115346v47.997672c0 26.363345 18.878836 47.997673 41.984 47.997673h140.036654c23.095855 0 41.984-21.634327 41.984-47.997673v-47.997672h501.108364c0.968145 0.093091 1.899055 0.288582 2.895127 0.288581a32.004655 32.004655 0 1 0-0.009309-64.009309zM400.002327 896H288.004655V768h111.997672v128z" fill="" p-id="6464"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/switch.svg b/zhitan-vue/src/assets/icons/svg/switch.svg
new file mode 100644
index 0000000..0ba61e3
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/switch.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576042673958" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1110" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M692 792H332c-150 0-270-120-270-270s120-270 270-270h360c150 0 270 120 270 270 0 147-120 270-270 270zM332 312c-117 0-210 93-210 210s93 210 210 210h360c117 0 210-93 210-210s-93-210-210-210H332z" p-id="1111"></path><path d="M341 522m-150 0a150 150 0 1 0 300 0 150 150 0 1 0-300 0Z" p-id="1112"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/system.svg b/zhitan-vue/src/assets/icons/svg/system.svg
new file mode 100644
index 0000000..5992593
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/system.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827724451" class="icon" style="" viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10233" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.71875" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
+</style></defs><path d="M1080.09609 434.500756c-4.216302-23.731757-26.9241-47.945376-50.595623-53.185637l-17.648235-4.095836a175.940257 175.940257 0 0 1-101.612877-80.832531 177.807476 177.807476 0 0 1-18.732427-129.801867l5.541425-16.684509c7.10748-23.129428-2.108151-54.992624-20.599646-70.833873 0 0-16.624276-14.094495-63.244529-41.199293-46.800951-26.984332-66.858502-34.513443-66.858502-34.513443-22.76803-8.372371-54.631227-0.361397-71.255503 17.407304l-12.287509 13.251234a173.470708 173.470708 0 0 1-120.465769 48.065842A174.13327 174.13327 0 0 1 421.329029 33.590675L409.583617 20.761071C393.140039 2.99237 361.096144-4.898138 338.267881 3.353767c0 0-20.358715 7.529111-67.099434 34.513443-46.800951 27.34573-63.244529 41.440225-63.244529 41.440225-18.431263 15.66055-27.646894 47.222582-20.539413 70.592941l5.059562 16.865207a178.048407 178.048407 0 0 1-18.672194 129.621169 174.916297 174.916297 0 0 1-102.275439 81.073463l-17.045906 3.854904c-23.310126 5.42096-46.258856 29.333415-50.595623 53.185637 0 0-3.854905 21.382674-3.854905 75.712737 0 54.330062 3.854905 75.712736 3.854905 75.712736 4.216302 23.972688 26.9241 47.945376 50.595623 53.185637l16.624276 3.854905a174.253736 174.253736 0 0 1 102.395904 81.314394c23.310126 40.837896 28.911785 87.337683 18.732427 129.801867l-4.81863 16.443578c-7.10748 23.129428 2.108151 54.992624 20.599646 70.833872 0 0 16.624276 14.094495 63.244529 41.199293 46.800951 27.104798 66.918735 34.513443 66.918735 34.513443 22.707798 8.372371 54.631227 0.361397 71.255503-17.407303l11.624947-12.588673a175.096996 175.096996 0 0 1 242.256662 0.120465l11.624947 12.648906c16.383345 17.708468 48.427239 25.598976 71.255503 17.347071 0 0 20.358715-7.529111 67.159666-34.513443 46.740719-27.104798 63.124063-41.199293 63.124064-41.199293 18.491496-15.600317 27.707127-47.463513 20.599646-70.833873l-5.059562-17.106139a176.723284 176.723284 0 0 1 18.672194-129.139305 176.060722 176.060722 0 0 1 102.395904-81.314394l16.68451-3.854905c23.310126-5.42096 46.258856-29.333415 50.595623-53.185637 0 0 3.854905-21.382674 3.854904-75.712737-0.240932-54.330062-4.095836-75.833202-4.095836-75.833202z m-537.819428 293.334149c-119.261112 0-216.175824-97.336342-216.175824-217.621412a216.657687 216.657687 0 0 1 216.236057-217.320249c119.200879 0 216.115591 97.276109 216.11559 217.56118-0.240932 120.044139-96.974945 217.320248-216.175823 217.320249z" p-id="10234"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/tab.svg b/zhitan-vue/src/assets/icons/svg/tab.svg
new file mode 100644
index 0000000..b4b48e4
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/tab.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.921.052H49.08c-1.865 0-3.198 1.599-3.198 3.464v6.661c0 1.865 1.6 3.464 3.198 3.464h29.84c1.865 0 3.198-1.599 3.198-3.464V3.516C82.385 1.65 80.786.052 78.92.052zm45.563 0H94.642c-1.865 0-3.464 1.599-3.464 3.464v6.661c0 1.865 1.599 3.464 3.464 3.464h29.842c1.865-.266 3.464-1.599 3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464zm0 22.382H40.02c-1.866 0-3.464-1.599-3.464-3.464V3.516c0-1.865-1.599-3.464-3.464-3.464H3.516C1.65.052.052 1.651.052 3.516V124.75c0 1.598 1.599 3.197 3.464 3.197h120.968c1.865 0 3.464-1.599 3.464-3.464V25.898c0-1.865-1.599-3.464-3.464-3.464z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/table.svg b/zhitan-vue/src/assets/icons/svg/table.svg
new file mode 100644
index 0000000..0e3dc9d
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/table.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/textarea.svg b/zhitan-vue/src/assets/icons/svg/textarea.svg
new file mode 100644
index 0000000..2709f29
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/textarea.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1575802855098" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2984" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 160H128c-35.2 0-64 28.8-64 64v576c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V224c0-35.2-28.8-64-64-64z m0 608c0 16-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V256c0-16 12.8-32 32-32h704c19.2 0 32 12.8 32 32v512z" p-id="2985"></path><path d="M224 288c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V320c0-16-12.8-32-32-32z m608 480c19.2 0 32-12.8 32-32V608L704 768h128z" p-id="2986"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/theme.svg b/zhitan-vue/src/assets/icons/svg/theme.svg
new file mode 100644
index 0000000..5982a2f
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/theme.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M125.5 36.984L95.336 2.83C93.735 1.018 91.565 0 89.3 0c-2.263 0-4.433 1.018-6.033 2.83l-3.786 4.286c-1.6 1.812-3.77 2.83-6.032 2.831H54.553c-2.263 0-4.434-1.018-6.033-2.83L44.734 2.83C43.134 1.018 40.964 0 38.701 0c-2.263 0-4.434 1.018-6.034 2.83L2.5 36.984C.9 38.796 0 41.254 0 43.815c0 2.562.899 5.02 2.5 6.831L14.565 64.31c2.178 2.468 5.367 3.403 8.33 2.444 1.35-.435 2.709.592 2.709 2.18v49.407c0 5.313 3.84 9.66 8.532 9.66h59.726c4.693 0 8.532-4.347 8.532-9.66V68.934c0-1.59 1.36-2.616 2.71-2.181 2.962.96 6.15.024 8.329-2.444L125.5 50.646c1.6-1.811 2.499-4.269 2.499-6.83 0-2.563-.899-5.02-2.5-6.832z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/time-range.svg b/zhitan-vue/src/assets/icons/svg/time-range.svg
new file mode 100644
index 0000000..13c1202
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/time-range.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1579774825624" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1248" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M498.595712 482.290351 345.420077 482.290351l0 57.307194 210.477712 0L555.897789 274.196942l-57.301054 0L498.596735 482.290351zM498.595712 482.290351" p-id="1249"></path><path d="M577.685002 644.98478l379.879913 0 0 57.302077L577.685002 702.286858 577.685002 644.98478 577.685002 644.98478zM577.685002 644.98478" p-id="1250"></path><path d="M577.685002 773.764795l379.879913 0 0 57.307194L577.685002 831.071989 577.685002 773.764795 577.685002 773.764795zM577.685002 773.764795" p-id="1251"></path><path d="M577.685002 902.549927l379.879913 0 0 57.307194L577.685002 959.857121 577.685002 902.549927 577.685002 902.549927zM577.685002 902.549927" p-id="1252"></path><path d="M102.523001 382.290823c4.450359 2.615571 9.470699 3.954055 14.530948 3.954055 2.969635 0 5.952572-0.461511 8.836249-1.394766l190.809767-61.886489c15.052834-4.882194 23.297612-21.040199 18.415418-36.08894-4.882194-15.052834-21.040199-23.297612-36.093033-18.415418L175.676092 308.458257c15.994276-26.115797 35.170011-50.537 57.370639-72.743768 73.767074-73.767074 171.845857-114.388237 276.16783-114.388237 104.32095 0 202.39564 40.622186 276.16169 114.388237s114.393353 171.845857 114.393353 276.16783c0 26.427906-2.615571 52.449559-7.709589 77.780481l58.302871 0c4.464685-25.499767 6.708795-51.470255 6.708795-77.780481 0-60.449767-11.845793-119.102608-35.204803-174.336584-22.559808-53.334719-54.850236-101.226472-95.968725-142.349055-41.122583-41.122583-89.017406-73.408917-142.348032-95.968725C628.317169 75.866898 569.659211 64.021106 509.215584 64.021106c-60.448744 0-119.106702 11.845793-174.336584 35.207873-53.334719 22.559808-101.230566 54.846142-142.349055 95.968725-23.980157 23.980157-44.934398 50.278103-62.727647 78.601172l-20.738323-105.655342c-3.043313-15.527648-18.105357-25.642007-33.631982-22.599717-15.527648 3.048429-25.64303 18.105357-22.599717 33.637098l36.102243 183.932126C90.51348 371.153158 95.460142 378.13313 102.523001 382.290823L102.523001 382.290823zM102.523001 382.290823" p-id="1253"></path><path d="M126.020158 587.9416 67.768453 587.9416c5.759167 33.679054 15.368012 66.544579 28.789697 98.278327 22.559808 53.333696 54.850236 101.225449 95.971795 142.348032 41.122583 41.122583 89.014336 73.408917 142.349055 95.968725 54.112432 22.88829 111.517863 34.71157 170.668031 35.18229L505.547031 902.395408c-102.94972-0.941442-199.594851-41.445948-272.499277-114.349351C177.545672 732.543975 140.810003 663.275355 126.020158 587.9416L126.020158 587.9416zM126.020158 587.9416" p-id="1254"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/time.svg b/zhitan-vue/src/assets/icons/svg/time.svg
new file mode 100644
index 0000000..b376e32
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/time.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577099827399" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1008" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M520 559h204c17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32H488c-17.673 0-32-14.327-32-32 0-0.167 0.001-0.334 0.004-0.5a32.65 32.65 0 0 1-0.004-0.5V277c0-17.673 14.327-32 32-32 17.673 0 32 14.327 32 32v282z m-8 401C264.576 960 64 759.424 64 512S264.576 64 512 64s448 200.576 448 448-200.576 448-448 448z m0-64c212.077 0 384-171.923 384-384S724.077 128 512 128 128 299.923 128 512s171.923 384 384 384z" p-id="1009"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/tool.svg b/zhitan-vue/src/assets/icons/svg/tool.svg
new file mode 100644
index 0000000..48e0e35
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/tool.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1553828490559" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1684" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M898.831744 900.517641 103.816972 900.517641c-36.002982 0-65.363683-29.286-65.363683-65.313541l0-554.949184c0-36.041868 29.361725-65.326844 65.363683-65.326844l795.015795 0c36.002982 0 65.198931 29.284977 65.198931 65.326844l0 554.949184C964.030675 871.231641 934.834726 900.517641 898.831744 900.517641L898.831744 900.517641zM103.816972 255.593236c-13.576203 0-24.711821 11.085476-24.711821 24.662703l0 554.949184c0 13.576203 11.136641 24.662703 24.711821 24.662703l795.015795 0c13.577227 0 24.547069-11.086499 24.547069-24.662703l0-554.949184c0-13.577227-10.970866-24.662703-24.547069-24.662703L103.816972 255.593236 103.816972 255.593236zM664.346245 251.774257c-11.161201 0-20.332071-9.080819-20.332071-20.332071l0-101.278661c0-13.576203-11.047614-24.623817-24.699542-24.623817L383.181611 105.539708c-13.576203 0-24.712845 11.04659-24.712845 24.623817l0 101.278661c0 11.252275-9.041934 20.332071-20.332071 20.332071-11.20111 0-20.319791-9.080819-20.319791-20.332071l0-101.278661c0-35.989679 29.323862-65.275679 65.364707-65.275679l236.133022 0c36.06745 0 65.402569 29.284977 65.402569 65.275679l0 101.278661C684.717202 242.694461 675.636383 251.774257 664.346245 251.774257L664.346245 251.774257zM413.233044 521.725502 75.694471 521.725502c-11.163247 0-20.333094-9.117658-20.333094-20.35663 0-11.252275 9.169847-20.332071 20.333094-20.332071l337.538573 0c11.277858 0 20.319791 9.080819 20.319791 20.332071C433.552835 512.607844 424.510902 521.725502 413.233044 521.725502L413.233044 521.725502zM912.894018 521.725502 575.367725 521.725502c-11.213389 0-20.332071-9.117658-20.332071-20.35663 0-11.252275 9.118682-20.332071 20.332071-20.332071l337.526293 0c11.290137 0 20.332071 9.080819 20.332071 20.332071C933.226089 512.607844 924.184155 521.725502 912.894018 521.725502L912.894018 521.725502zM557.56322 634.217552 445.085496 634.217552c-11.213389 0-20.332071-9.079796-20.332071-20.331048l0-168.763658c0-11.251252 9.118682-20.332071 20.332071-20.332071l112.478747 0c11.290137 0 20.370956 9.080819 20.370956 20.332071l0 168.763658C577.934177 625.137757 568.853357 634.217552 557.56322 634.217552L557.56322 634.217552zM465.417567 593.514525l71.827909 0L537.245476 465.454918l-71.827909 0L465.417567 593.514525 465.417567 593.514525z" p-id="1685"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/tree-table.svg b/zhitan-vue/src/assets/icons/svg/tree-table.svg
new file mode 100644
index 0000000..8aafdb8
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/tree-table.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M44.8 0h79.543C126.78 0 128 1.422 128 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H44.8c-2.438 0-3.657-1.422-3.657-4.267V4.267C41.143 1.422 42.362 0 44.8 0zm22.857 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 80 64 78.578 64 75.733V52.267C64 49.422 65.219 48 67.657 48zm0 48h56.686c2.438 0 3.657 1.422 3.657 4.267v23.466c0 2.845-1.219 4.267-3.657 4.267H67.657C65.22 128 64 126.578 64 123.733v-23.466C64 97.422 65.219 96 67.657 96zM50.286 68.267c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V32h6.4c2.02 0 3.658-1.91 3.658-4.267V4.267C27.429 1.91 25.79 0 23.77 0H3.657C1.637 0 0 1.91 0 4.267v23.466C0 30.09 1.637 32 3.657 32h6.4v80c0 2.356 1.638 4.267 3.657 4.267h36.572c2.02 0 3.657-1.91 3.657-4.267 0-2.356-1.638-4.267-3.657-4.267H17.37V68.267h32.915z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/tree.svg b/zhitan-vue/src/assets/icons/svg/tree.svg
new file mode 100644
index 0000000..dd4b7dd
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/tree.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/upload.svg b/zhitan-vue/src/assets/icons/svg/upload.svg
new file mode 100644
index 0000000..bae49c0
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/upload.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1577540289643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7922" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M530.944 458.24l4.8 3.456 122.176 106.816a32 32 0 0 1-37.44 51.584l-4.672-3.392L546.56 556.16v280.704a32 32 0 0 1-26.24 31.488l-5.76 0.512a32 32 0 0 1-31.424-26.24l-0.512-5.76-0.064-280.704-69.12 60.48a32 32 0 0 1-40.96 0.896l-4.16-3.968a32 32 0 0 1-0.96-40.96l4.032-4.16 122.176-106.816a32 32 0 0 1 37.312-3.456zM497.92 128c128.128 0 239.168 82.304 275.52 199.04 123.968 11.264 221.312 113.088 221.312 237.44 0 128.128-103.68 232.96-234.88 238.272h-5.888l-35.52 0.192a32 32 0 0 1-0.192-64l35.264-0.128 4.672-0.064c96.384-3.84 172.544-80.896 172.544-174.272 0-96.128-80.512-174.464-179.584-174.464h-1.984a32 32 0 0 1-32-25.28C695.872 264.96 604.736 192 497.92 192 381.824 192 285.44 277.76 274.816 388.48a32 32 0 0 1-28.352 28.8c-83.968 9.152-147.84 78.208-147.84 159.552l0.192 7.936c3.84 85.76 77.056 154.112 166.592 154.112h45.632a32 32 0 0 1 0 64h-45.632C142.016 802.944 40.32 708.032 34.88 586.88l-0.192-9.28c0-106.88 76.352-197.184 179.968-219.904C239.488 226.112 357.76 128 497.856 128z" p-id="7923"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/user.svg b/zhitan-vue/src/assets/icons/svg/user.svg
new file mode 100644
index 0000000..0ba0716
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/user.svg
@@ -0,0 +1 @@
+<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/validCode.svg b/zhitan-vue/src/assets/icons/svg/validCode.svg
new file mode 100644
index 0000000..cfb1021
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/validCode.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1569580729849" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1939" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M513.3 958.5c-142.2 0-397.9-222.1-401.6-440.5V268c1.7-39.6 31.7-72.3 71.1-77.3 49-4.6 97.1-16.5 142.7-35.3 47.8-14 91.9-38.3 129.4-71.1 30.3-24.4 72.9-26.3 105.3-4.6 39.9 30.7 83.8 55.9 130.5 74.6 48.6 14.7 98.2 25.9 148.4 33.7 38.5 7.6 67.1 40.3 69.5 79.5 3.3 84.9 2.5 169.9-2.6 254.7-33.7 281.6-253.7 436.4-392.7 436.3z m-0.1-813.7c-7.2-0.2-14.3 2-20 6.4-39.7 35.2-86.8 61.1-137.7 75.7-46.8 19.2-96.2 31-146.6 35.2-11 3.2-18.8 13-19.5 24.4v230.1c3.5 180.3 223.3 361 323.9 361s287.3-120.2 317.6-360.5c7.3-142.7 0-228.6 0-229.6-1.3-13.3-11-24.3-24-27.3-49.6-7.7-98.6-19-146.5-33.7-46.3-19.5-89.7-45.3-129-76.7-5.8-3.8-12.7-5.5-19.5-4.9l1.3-0.1z" fill="#C6CCDA" p-id="1940"></path><path d="M750.1 428L490.7 673.2c-11.7 11.1-29.5 12.9-43.1 4.2l-6.8-5.8-141.2-149.4c-9.3-9.3-12.7-22.9-9-35.5 3.8-12.6 14.1-22.1 27-24.8 12.9-2.7 26.1 1.9 34.6 11.9L469 597.5l233.7-221c14.6-12.8 36.8-11.6 49.9 2.7 13.2 14.2 11.5 35.3-2.5 48.8" fill="#C6CCDA" p-id="1941"></path></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/wechat.svg b/zhitan-vue/src/assets/icons/svg/wechat.svg
new file mode 100644
index 0000000..c586e55
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/wechat.svg
@@ -0,0 +1 @@
+<svg width="128" height="110" xmlns="http://www.w3.org/2000/svg"><path d="M86.635 33.334c1.467 0 2.917.113 4.358.283C87.078 14.392 67.58.111 45.321.111 20.44.111.055 17.987.055 40.687c0 13.104 6.781 23.863 18.115 32.209l-4.527 14.352 15.82-8.364c5.666 1.182 10.207 2.395 15.858 2.395 1.42 0 2.829-.073 4.227-.189-.886-3.19-1.398-6.53-1.398-9.996 0-20.845 16.98-37.76 38.485-37.76zm-24.34-12.936c3.407 0 5.665 2.363 5.665 5.954 0 3.576-2.258 5.97-5.666 5.97-3.392 0-6.795-2.395-6.795-5.97 0-3.591 3.403-5.954 6.795-5.954zM30.616 32.323c-3.393 0-6.818-2.395-6.818-5.971 0-3.591 3.425-5.954 6.818-5.954 3.392 0 5.65 2.363 5.65 5.954 0 3.576-2.258 5.97-5.65 5.97z"/><path d="M127.945 70.52c0-19.075-18.108-34.623-38.448-34.623-21.537 0-38.5 15.548-38.5 34.623 0 19.108 16.963 34.622 38.5 34.622 4.508 0 9.058-1.2 13.584-2.395l12.414 7.167-3.404-11.923c9.087-7.184 15.854-16.712 15.854-27.471zm-50.928-5.97c-2.254 0-4.53-2.362-4.53-4.773 0-2.378 2.276-4.771 4.53-4.771 3.422 0 5.665 2.393 5.665 4.771 0 2.41-2.243 4.773-5.665 4.773zm24.897 0c-2.24 0-4.498-2.362-4.498-4.773 0-2.378 2.258-4.771 4.498-4.771 3.392 0 5.665 2.393 5.665 4.771 0 2.41-2.273 4.773-5.665 4.773z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/icons/svg/zip.svg b/zhitan-vue/src/assets/icons/svg/zip.svg
new file mode 100644
index 0000000..f806fc4
--- /dev/null
+++ b/zhitan-vue/src/assets/icons/svg/zip.svg
@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M78.527 116.793c.178.008.348.024.527.024h40.233c4.711-.005 8.53-3.677 8.534-8.21V18.895c-.004-4.532-3.823-8.204-8.534-8.209H79.054c-.179 0-.353.016-.527.024V0L0 10.082v107.406l78.527 10.342v-11.037zm0-101.362c.174-.024.348-.052.527-.052h40.233c2.018 0 3.659 1.578 3.659 3.52v89.713c-.003 1.942-1.64 3.517-3.659 3.519H79.054c-.179 0-.353-.028-.527-.052V15.431zM30.262 75.757l-18.721-.46V72.37l11.3-16.673v-.148l-10.266.164v-4.51l17.504-.44v3.264L18.696 70.76v.144l11.566.176v4.678zm9.419.231l-5.823-.144V50.671l5.823-.144v25.461zm22.255-11.632c-2.168 1.922-5.353 2.76-9.02 2.736-.702.004-1.402-.04-2.097-.131v9.303l-5.997-.148V50.743c1.852-.352 4.473-.647 8.218-.743 3.838-.096 6.608.539 8.48 1.913 1.807 1.306 3.032 3.5 3.032 6.112s-.926 4.833-2.612 6.331h-.004zM53.36 54.45c-.856-.01-1.71.083-2.541.275v7.682c.523.116 1.167.152 2.06.152 3.301-.004 5.36-1.614 5.36-4.314 0-2.425-1.772-3.843-4.875-3.791l-.004-.004zm39.847-37.066h9.564v3.795h-9.564v-3.795zm-9.568 5.68h9.564v3.8h-9.564v-3.8zm9.568 6.216h9.564v3.799h-9.564V29.28zm0 12h9.564v3.794h-9.564V41.28zm-9.568-6.096h9.564v3.795h-9.564v-3.795zm9.472 47.064c2.512 0 4.921-.96 6.697-2.67 1.776-1.708 2.773-4.026 2.772-6.442l-1.748-15.263c0-5.033-2.492-9.112-7.725-9.112-5.232 0-7.72 4.079-7.72 9.112l-1.752 15.263c-.001 2.417.996 4.735 2.773 6.444 1.777 1.71 4.187 2.669 6.7 2.668h.003zm-3.135-16.75h6.27v12.743h-6.27V65.5z"/></svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/images/basecard/content_bg.png b/zhitan-vue/src/assets/images/basecard/content_bg.png
new file mode 100644
index 0000000..331eb01
--- /dev/null
+++ b/zhitan-vue/src/assets/images/basecard/content_bg.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/basecard/goBack.png b/zhitan-vue/src/assets/images/basecard/goBack.png
new file mode 100644
index 0000000..ec48c6e
--- /dev/null
+++ b/zhitan-vue/src/assets/images/basecard/goBack.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/basecard/title_bg.png b/zhitan-vue/src/assets/images/basecard/title_bg.png
new file mode 100644
index 0000000..050b52c
--- /dev/null
+++ b/zhitan-vue/src/assets/images/basecard/title_bg.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/dark.svg b/zhitan-vue/src/assets/images/dark.svg
new file mode 100644
index 0000000..f646bd7
--- /dev/null
+++ b/zhitan-vue/src/assets/images/dark.svg
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" 
+    xmlns="http://www.w3.org/2000/svg" 
+    xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="閰嶇疆闈㈡澘" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/images/energy/center/bg1.png b/zhitan-vue/src/assets/images/energy/center/bg1.png
new file mode 100644
index 0000000..8b7f8bf
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/center/bg1.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/center/bg2.png b/zhitan-vue/src/assets/images/energy/center/bg2.png
new file mode 100644
index 0000000..a7ec96f
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/center/bg2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/center/icon1.png b/zhitan-vue/src/assets/images/energy/center/icon1.png
new file mode 100644
index 0000000..e8eb9e1
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/center/icon1.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/center/icon2.png b/zhitan-vue/src/assets/images/energy/center/icon2.png
new file mode 100644
index 0000000..5a7b7df
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/center/icon2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/center/icon3.png b/zhitan-vue/src/assets/images/energy/center/icon3.png
new file mode 100644
index 0000000..5f27d39
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/center/icon3.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/center/icon4.png b/zhitan-vue/src/assets/images/energy/center/icon4.png
new file mode 100644
index 0000000..565eb11
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/center/icon4.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/energy_bg.png b/zhitan-vue/src/assets/images/energy/energy_bg.png
new file mode 100644
index 0000000..24bd60e
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/energy_bg.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/bottom/pie-bg.png b/zhitan-vue/src/assets/images/energy/left/bottom/pie-bg.png
new file mode 100644
index 0000000..f9efd91
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/bottom/pie-bg.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/center/icon01.png b/zhitan-vue/src/assets/images/energy/left/center/icon01.png
new file mode 100644
index 0000000..60052a3
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/center/icon01.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/center/icon02.png b/zhitan-vue/src/assets/images/energy/left/center/icon02.png
new file mode 100644
index 0000000..7c5d81b
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/center/icon02.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/center/icon03.png b/zhitan-vue/src/assets/images/energy/left/center/icon03.png
new file mode 100644
index 0000000..d507360
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/center/icon03.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/center/icon04.png b/zhitan-vue/src/assets/images/energy/left/center/icon04.png
new file mode 100644
index 0000000..c1509e9
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/center/icon04.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/center/icon05.png b/zhitan-vue/src/assets/images/energy/left/center/icon05.png
new file mode 100644
index 0000000..b8e02ac
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/center/icon05.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/center/icon06.png b/zhitan-vue/src/assets/images/energy/left/center/icon06.png
new file mode 100644
index 0000000..9a3d805
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/center/icon06.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/top/icon1.png b/zhitan-vue/src/assets/images/energy/left/top/icon1.png
new file mode 100644
index 0000000..d2b7ea4
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/top/icon1.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/top/icon2.png b/zhitan-vue/src/assets/images/energy/left/top/icon2.png
new file mode 100644
index 0000000..2560aec
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/top/icon2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/top/icon3.png b/zhitan-vue/src/assets/images/energy/left/top/icon3.png
new file mode 100644
index 0000000..4504fbf
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/top/icon3.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/left/top/icon4.png b/zhitan-vue/src/assets/images/energy/left/top/icon4.png
new file mode 100644
index 0000000..d8b09e0
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/left/top/icon4.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/bg.png b/zhitan-vue/src/assets/images/energy/right/center/bg.png
new file mode 100644
index 0000000..d960eeb
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/bg.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/icon1.png b/zhitan-vue/src/assets/images/energy/right/center/icon1.png
new file mode 100644
index 0000000..f3091d1
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/icon1.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/icon10.png b/zhitan-vue/src/assets/images/energy/right/center/icon10.png
new file mode 100644
index 0000000..fc564e3
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/icon10.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/icon2.png b/zhitan-vue/src/assets/images/energy/right/center/icon2.png
new file mode 100644
index 0000000..7764255
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/icon2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/icon3.png b/zhitan-vue/src/assets/images/energy/right/center/icon3.png
new file mode 100644
index 0000000..33b7044
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/icon3.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/icon4.png b/zhitan-vue/src/assets/images/energy/right/center/icon4.png
new file mode 100644
index 0000000..77d546b
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/icon4.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/icon5.png b/zhitan-vue/src/assets/images/energy/right/center/icon5.png
new file mode 100644
index 0000000..65585b8
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/icon5.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/icon6.png b/zhitan-vue/src/assets/images/energy/right/center/icon6.png
new file mode 100644
index 0000000..e537529
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/icon6.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/icon7.png b/zhitan-vue/src/assets/images/energy/right/center/icon7.png
new file mode 100644
index 0000000..55e5f83
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/icon7.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/icon8.png b/zhitan-vue/src/assets/images/energy/right/center/icon8.png
new file mode 100644
index 0000000..ebf5feb
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/icon8.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/energy/right/center/icon9.png b/zhitan-vue/src/assets/images/energy/right/center/icon9.png
new file mode 100644
index 0000000..bc5cdb5
--- /dev/null
+++ b/zhitan-vue/src/assets/images/energy/right/center/icon9.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/header.png b/zhitan-vue/src/assets/images/header.png
new file mode 100644
index 0000000..718d136
--- /dev/null
+++ b/zhitan-vue/src/assets/images/header.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/card-icon-1.png b/zhitan-vue/src/assets/images/home/card-icon-1.png
new file mode 100644
index 0000000..3174da0
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/card-icon-1.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/card-icon-2.png b/zhitan-vue/src/assets/images/home/card-icon-2.png
new file mode 100644
index 0000000..b20940c
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/card-icon-2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/card-icon-3.png b/zhitan-vue/src/assets/images/home/card-icon-3.png
new file mode 100644
index 0000000..1811806
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/card-icon-3.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/card-icon-4.png b/zhitan-vue/src/assets/images/home/card-icon-4.png
new file mode 100644
index 0000000..0baf51b
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/card-icon-4.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/card-icon-5.png b/zhitan-vue/src/assets/images/home/card-icon-5.png
new file mode 100644
index 0000000..74c35e9
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/card-icon-5.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/card-icon2-1.png b/zhitan-vue/src/assets/images/home/card-icon2-1.png
new file mode 100644
index 0000000..befc3e8
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/card-icon2-1.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/card-icon2-2.png b/zhitan-vue/src/assets/images/home/card-icon2-2.png
new file mode 100644
index 0000000..274f8a4
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/card-icon2-2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/card-icon2-3.png b/zhitan-vue/src/assets/images/home/card-icon2-3.png
new file mode 100644
index 0000000..5963c6d
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/card-icon2-3.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/card-icon2-4.png b/zhitan-vue/src/assets/images/home/card-icon2-4.png
new file mode 100644
index 0000000..b45bb49
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/card-icon2-4.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/card-icon2-5.png b/zhitan-vue/src/assets/images/home/card-icon2-5.png
new file mode 100644
index 0000000..ba42fda
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/card-icon2-5.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/index-card-1.png b/zhitan-vue/src/assets/images/home/index-card-1.png
new file mode 100644
index 0000000..2cf100e
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/index-card-1.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/index-card-2.png b/zhitan-vue/src/assets/images/home/index-card-2.png
new file mode 100644
index 0000000..cddf0b2
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/index-card-2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/index-card-3.png b/zhitan-vue/src/assets/images/home/index-card-3.png
new file mode 100644
index 0000000..8d040bf
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/index-card-3.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/index-card-4.png b/zhitan-vue/src/assets/images/home/index-card-4.png
new file mode 100644
index 0000000..26cdab6
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/index-card-4.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/index-card-5.png b/zhitan-vue/src/assets/images/home/index-card-5.png
new file mode 100644
index 0000000..c170fee
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/index-card-5.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/index-card-bg2.png b/zhitan-vue/src/assets/images/home/index-card-bg2.png
new file mode 100644
index 0000000..01e19aa
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/index-card-bg2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/home/line@2x.png b/zhitan-vue/src/assets/images/home/line@2x.png
new file mode 100644
index 0000000..b844c7c
--- /dev/null
+++ b/zhitan-vue/src/assets/images/home/line@2x.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/light.svg b/zhitan-vue/src/assets/images/light.svg
new file mode 100644
index 0000000..ab7cc08
--- /dev/null
+++ b/zhitan-vue/src/assets/images/light.svg
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" 
+    xmlns="http://www.w3.org/2000/svg" 
+    xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="閰嶇疆闈㈡澘" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
+                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/images/login-background.jpg b/zhitan-vue/src/assets/images/login-background.jpg
new file mode 100644
index 0000000..8a89eb8
--- /dev/null
+++ b/zhitan-vue/src/assets/images/login-background.jpg
Binary files differ
diff --git a/zhitan-vue/src/assets/images/login-bg.jpg b/zhitan-vue/src/assets/images/login-bg.jpg
new file mode 100644
index 0000000..d3ab6e6
--- /dev/null
+++ b/zhitan-vue/src/assets/images/login-bg.jpg
Binary files differ
diff --git a/zhitan-vue/src/assets/images/login-logo.png b/zhitan-vue/src/assets/images/login-logo.png
new file mode 100644
index 0000000..29deeaa
--- /dev/null
+++ b/zhitan-vue/src/assets/images/login-logo.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/nav-btn.png b/zhitan-vue/src/assets/images/nav-btn.png
new file mode 100644
index 0000000..cd2bd09
--- /dev/null
+++ b/zhitan-vue/src/assets/images/nav-btn.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/nav-btn2.png b/zhitan-vue/src/assets/images/nav-btn2.png
new file mode 100644
index 0000000..5628372
--- /dev/null
+++ b/zhitan-vue/src/assets/images/nav-btn2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/pay.png b/zhitan-vue/src/assets/images/pay.png
new file mode 100644
index 0000000..bb8b967
--- /dev/null
+++ b/zhitan-vue/src/assets/images/pay.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/period/icon1.png b/zhitan-vue/src/assets/images/period/icon1.png
new file mode 100644
index 0000000..c5e6bfb
--- /dev/null
+++ b/zhitan-vue/src/assets/images/period/icon1.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/period/icon2.png b/zhitan-vue/src/assets/images/period/icon2.png
new file mode 100644
index 0000000..1f2e262
--- /dev/null
+++ b/zhitan-vue/src/assets/images/period/icon2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/period/icon3.png b/zhitan-vue/src/assets/images/period/icon3.png
new file mode 100644
index 0000000..7ac7e0e
--- /dev/null
+++ b/zhitan-vue/src/assets/images/period/icon3.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/period/icon4.png b/zhitan-vue/src/assets/images/period/icon4.png
new file mode 100644
index 0000000..fefa233
--- /dev/null
+++ b/zhitan-vue/src/assets/images/period/icon4.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/period/icon5.png b/zhitan-vue/src/assets/images/period/icon5.png
new file mode 100644
index 0000000..0ed1c1e
--- /dev/null
+++ b/zhitan-vue/src/assets/images/period/icon5.png
Binary files differ
diff --git a/zhitan-vue/src/assets/images/profile.jpg b/zhitan-vue/src/assets/images/profile.jpg
new file mode 100644
index 0000000..b3a940b
--- /dev/null
+++ b/zhitan-vue/src/assets/images/profile.jpg
Binary files differ
diff --git a/zhitan-vue/src/assets/logo/logo-1.png b/zhitan-vue/src/assets/logo/logo-1.png
new file mode 100644
index 0000000..bca633a
--- /dev/null
+++ b/zhitan-vue/src/assets/logo/logo-1.png
Binary files differ
diff --git a/zhitan-vue/src/assets/logo/logo-2.png b/zhitan-vue/src/assets/logo/logo-2.png
new file mode 100644
index 0000000..c365f5e
--- /dev/null
+++ b/zhitan-vue/src/assets/logo/logo-2.png
Binary files differ
diff --git a/zhitan-vue/src/assets/logo/logo-3.png b/zhitan-vue/src/assets/logo/logo-3.png
new file mode 100644
index 0000000..afff531
--- /dev/null
+++ b/zhitan-vue/src/assets/logo/logo-3.png
Binary files differ
diff --git a/zhitan-vue/src/assets/logo/logo.png b/zhitan-vue/src/assets/logo/logo.png
new file mode 100644
index 0000000..e263760
--- /dev/null
+++ b/zhitan-vue/src/assets/logo/logo.png
Binary files differ
diff --git a/zhitan-vue/src/assets/styles/btn.scss b/zhitan-vue/src/assets/styles/btn.scss
new file mode 100644
index 0000000..3590d8d
--- /dev/null
+++ b/zhitan-vue/src/assets/styles/btn.scss
@@ -0,0 +1,99 @@
+@import './variables.module.scss';
+
+@mixin colorBtn($color) {
+  background: $color;
+
+  &:hover {
+    color: $color;
+
+    &:before,
+    &:after {
+      background: $color;
+    }
+  }
+}
+
+.blue-btn {
+  @include colorBtn($blue)
+}
+
+.light-blue-btn {
+  @include colorBtn($light-blue)
+}
+
+.red-btn {
+  @include colorBtn($red)
+}
+
+.pink-btn {
+  @include colorBtn($pink)
+}
+
+.green-btn {
+  @include colorBtn($green)
+}
+
+.tiffany-btn {
+  @include colorBtn($tiffany)
+}
+
+.yellow-btn {
+  @include colorBtn($yellow)
+}
+
+.pan-btn {
+  font-size: 14px;
+  color: #fff;
+  padding: 14px 36px;
+  border-radius: 8px;
+  border: none;
+  outline: none;
+  transition: 600ms ease all;
+  position: relative;
+  display: inline-block;
+
+  &:hover {
+    background: #fff;
+
+    &:before,
+    &:after {
+      width: 100%;
+      transition: 600ms ease all;
+    }
+  }
+
+  &:before,
+  &:after {
+    content: '';
+    position: absolute;
+    top: 0;
+    right: 0;
+    height: 2px;
+    width: 0;
+    transition: 400ms ease all;
+  }
+
+  &::after {
+    right: inherit;
+    top: inherit;
+    left: 0;
+    bottom: 0;
+  }
+}
+
+.custom-button {
+  display: inline-block;
+  line-height: 1;
+  white-space: nowrap;
+  cursor: pointer;
+  background: #fff;
+  color: #fff;
+  -webkit-appearance: none;
+  text-align: center;
+  box-sizing: border-box;
+  outline: 0;
+  margin: 0;
+  padding: 10px 15px;
+  font-size: 14px;
+  border-radius: 4px;
+}
diff --git a/zhitan-vue/src/assets/styles/element-ui.scss b/zhitan-vue/src/assets/styles/element-ui.scss
new file mode 100644
index 0000000..a456a20
--- /dev/null
+++ b/zhitan-vue/src/assets/styles/element-ui.scss
@@ -0,0 +1,111 @@
+// cover some element-ui styles
+
+.themeDark {
+  .el-breadcrumb__inner,
+  .el-breadcrumb__inner a {
+    font-weight: bold !important;
+    color: #97a8be !important;
+    font-size: 24px !important;
+  }
+}
+.themeLight {
+  .el-breadcrumb__inner,
+  .el-breadcrumb__inner a {
+    font-weight: bold !important;
+    color: #000103 !important;
+    font-size: 24px !important;
+  }
+  .app-breadcrumb.el-breadcrumb .no-redirect{
+    color: #000103 !important;
+  }
+}
+
+.el-upload {
+  input[type="file"] {
+    display: none !important;
+  }
+}
+
+.el-upload__input {
+  display: none;
+}
+
+.cell {
+  .el-tag {
+    margin-right: 0px;
+  }
+}
+
+.small-padding {
+  .cell {
+    padding-left: 5px;
+    padding-right: 5px;
+  }
+}
+
+.fixed-width {
+  .el-button--mini {
+    padding: 7px 10px;
+    width: 60px;
+  }
+}
+
+.status-col {
+  .cell {
+    padding: 0 10px;
+    text-align: center;
+
+    .el-tag {
+      margin-right: 0px;
+    }
+  }
+}
+
+// to fixed https://github.com/ElemeFE/element/issues/2461
+.el-dialog {
+  transform: none;
+  left: 0;
+  position: relative;
+  margin: 0 auto;
+}
+
+// refine element ui upload
+.upload-container {
+  .el-upload {
+    width: 100%;
+
+    .el-upload-dragger {
+      width: 100%;
+      height: 200px;
+    }
+  }
+}
+
+// dropdown
+.el-dropdown-menu {
+  a {
+    display: block;
+  }
+}
+
+// fix date-picker ui bug in filter-item
+.el-range-editor.el-input__inner {
+  display: inline-flex !important;
+}
+
+// to fix el-date-picker css style
+.el-range-separator {
+  box-sizing: content-box;
+}
+
+.el-menu--collapse
+  > div
+  > .el-submenu
+  > .el-submenu__title
+  .el-submenu__icon-arrow {
+  display: none;
+}
+
+.el-dropdown .el-dropdown-link {
+  color: var(--el-color-primary) !important;
+}
diff --git a/zhitan-vue/src/assets/styles/index.scss b/zhitan-vue/src/assets/styles/index.scss
new file mode 100644
index 0000000..a99aaef
--- /dev/null
+++ b/zhitan-vue/src/assets/styles/index.scss
@@ -0,0 +1,207 @@
+@import './variables.module.scss';
+@import './mixin.scss';
+@import './transition.scss';
+@import './element-ui.scss';
+@import './sidebar.scss';
+@import './btn.scss';
+@import './ruoyi.scss';
+
+body {
+  height: 100%;
+  margin: 0;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  text-rendering: optimizeLegibility;
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
+}
+
+label {
+  font-weight: 700;
+}
+
+html {
+  height: 100%;
+  box-sizing: border-box;
+}
+
+#app {
+  height: 100%;
+}
+
+*,
+*:before,
+*:after {
+  box-sizing: inherit;
+}
+
+.no-padding {
+  padding: 0px !important;
+}
+
+.padding-content {
+  padding: 4px 0;
+}
+
+a:focus,
+a:active {
+  outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+  cursor: pointer;
+  color: inherit;
+  text-decoration: none;
+}
+
+div:focus {
+  outline: none;
+}
+
+.fr {
+  float: right;
+}
+
+.fl {
+  float: left;
+}
+
+.pr-5 {
+  padding-right: 5px;
+}
+
+.pl-5 {
+  padding-left: 5px;
+}
+
+.text-right {
+  text-align: right;
+}
+
+.text-center {
+  text-align: center;
+}
+
+.text-left {
+  text-align: left;
+}
+
+.block {
+  display: block;
+}
+
+.pointer {
+  cursor: pointer;
+}
+
+.inlineBlock {
+  display: block;
+}
+
+.clearfix {
+  &:after {
+    visibility: hidden;
+    display: block;
+    font-size: 0;
+    content: " ";
+    clear: both;
+    height: 0;
+  }
+}
+
+aside {
+  background: #eef1f6;
+  padding: 8px 24px;
+  margin-bottom: 20px;
+  border-radius: 2px;
+  display: block;
+  line-height: 32px;
+  font-size: 16px;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+  color: #2c3e50;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+
+  a {
+    color: #337ab7;
+    cursor: pointer;
+
+    &:hover {
+      color: rgb(32, 160, 255);
+    }
+  }
+}
+
+//main-container鍏ㄥ眬鏍峰紡
+.app-container {
+  padding: 20px;
+}
+
+.components-container {
+  margin: 30px 50px;
+  position: relative;
+}
+
+.pagination-container {
+  margin-top: 30px;
+}
+
+.text-center {
+  text-align: center
+}
+
+.sub-navbar {
+  height: 50px;
+  line-height: 50px;
+  position: relative;
+  width: 100%;
+  text-align: right;
+  padding-right: 20px;
+  transition: 600ms ease position;
+  background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
+
+  .subtitle {
+    font-size: 20px;
+    color: #fff;
+  }
+
+  &.draft {
+    background: #d0d0d0;
+  }
+
+  &.deleted {
+    background: #d0d0d0;
+  }
+}
+
+.link-type,
+.link-type:focus {
+  color: #337ab7;
+  cursor: pointer;
+
+  &:hover {
+    color: rgb(32, 160, 255);
+  }
+}
+
+.filter-container {
+  padding-bottom: 10px;
+
+  .filter-item {
+    display: inline-block;
+    vertical-align: middle;
+    margin-bottom: 10px;
+  }
+}
+
+.el-picker-panel__icon-btn {
+  color: #999 !important;
+}
+
+.themeDark {
+  .el-range-separator {
+    // ::v-deep(.el-range-separator) {
+    color: #999 !important;
+  }
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/styles/mixin.scss b/zhitan-vue/src/assets/styles/mixin.scss
new file mode 100644
index 0000000..06fa061
--- /dev/null
+++ b/zhitan-vue/src/assets/styles/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/zhitan-vue/src/assets/styles/page.scss b/zhitan-vue/src/assets/styles/page.scss
new file mode 100644
index 0000000..c4c8e50
--- /dev/null
+++ b/zhitan-vue/src/assets/styles/page.scss
@@ -0,0 +1,145 @@
+.themeDark {
+  .page {
+    .page-container {
+      display: flex;
+
+      .page-container-left {
+        width: 280px;
+        min-height: calc(100vh - 120px);
+        border-right: 1px solid #1a235d;
+
+        .tree {
+          height: calc(100vh - 170px);
+          max-height: calc(100vh - 170px);
+          overflow-y: auto;
+        }
+      }
+
+      .page-container-right {
+        flex: 1;
+        border-radius: 0px 0px 0px 0px;
+        overflow: hidden;
+        padding-bottom: 15px;
+      }
+    }
+
+    .form-card {
+      background: #1a235d;
+      border-radius: 0px 0px 0px 0px;
+      border: 1px solid #000000;
+      padding: 18px 0 0 15px;
+    }
+
+    .tree {
+      // padding: 20px;
+      margin-top: 25px;
+      // margin-left: 20px;
+      overflow: hidden;
+    }
+  }
+
+  .chart-box {
+    width: 100%;
+    height: 320px;
+
+    div {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  .table-box {
+    margin: 20px;
+
+    .table-title-box {
+      font-weight: bold;
+      font-size: 22px;
+      color: #fffefe;
+      line-height: 29px;
+      text-align: center;
+      display: flex;
+      justify-content: space-between;
+      margin-top: 23px;
+
+      font-family: OPPOSans, OPPOSans;
+      font-weight: 500;
+      font-size: 16px;
+    }
+  }
+
+}
+
+.themeLight {
+  .page {
+    .page-container {
+      display: flex;
+
+      .page-container-left {
+        width: 280px;
+
+        // border-right: 1px solid #1a235d;
+        .tree {
+          height: calc(100vh - 170px) !important;
+          max-height: calc(100vh - 170px) !important;
+          overflow-y: auto;
+        }
+      }
+
+      .page-container-right {
+        flex: 1;
+        border-radius: 0px 0px 0px 0px;
+        overflow: hidden;
+        padding-bottom: 15px;
+        background-color: #fff;
+      }
+    }
+
+    .form-card {
+      background: #fff;
+      border-radius: 0px 0px 0px 0px;
+      // border: 1px solid #000000;
+      padding: 18px 0 0 15px;
+    }
+
+    .tree {
+      // padding: 20px;
+      margin-top: 25px;
+      // margin-left: 20px;
+      overflow: hidden;
+    }
+  }
+
+  .chart-box {
+    width: 100%;
+    height: 320px;
+
+    div {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  .table-box {
+    margin: 20px;
+
+    .table-title-box {
+      font-weight: bold;
+      font-size: 22px;
+      color: #000;
+      line-height: 29px;
+      text-align: center;
+      display: flex;
+      justify-content: space-between;
+      margin-top: 23px;
+
+      font-family: OPPOSans, OPPOSans;
+      font-weight: 500;
+      font-size: 16px;
+    }
+  }
+
+}
+
+.padding {
+  padding: 15px;
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/styles/ruoyi.scss b/zhitan-vue/src/assets/styles/ruoyi.scss
new file mode 100644
index 0000000..4479086
--- /dev/null
+++ b/zhitan-vue/src/assets/styles/ruoyi.scss
@@ -0,0 +1,1104 @@
+.themeDark {
+  /**
+ * 閫氱敤css鏍峰紡甯冨眬澶勭悊
+ * Copyright (c) 2019 ruoyi
+ */
+
+  /** 鍩虹閫氱敤 **/
+  .pt5 {
+    padding-top: 5px;
+  }
+
+  .pr5 {
+    padding-right: 5px;
+  }
+
+  .pb5 {
+    padding-bottom: 5px;
+  }
+
+  .mt5 {
+    margin-top: 5px;
+  }
+
+  .mr5 {
+    margin-right: 5px;
+  }
+
+  .mb5 {
+    margin-bottom: 5px;
+  }
+
+  .mb8 {
+    margin-bottom: 8px;
+  }
+
+  .ml5 {
+    margin-left: 5px;
+  }
+
+  .mt10 {
+    margin-top: 10px;
+  }
+
+  .mr10 {
+    margin-right: 10px;
+  }
+
+  .mb10 {
+    margin-bottom: 10px;
+  }
+
+  .ml10 {
+    margin-left: 10px;
+  }
+
+  .mt20 {
+    margin-top: 20px;
+  }
+
+  .mr20 {
+    margin-right: 20px;
+  }
+
+  .mb20 {
+    margin-bottom: 20px;
+  }
+
+  .ml20 {
+    margin-left: 20px;
+  }
+
+  .h1,
+  .h2,
+  .h3,
+  .h4,
+  .h5,
+  .h6,
+  h1,
+  h2,
+  h3,
+  h4,
+  h5,
+  h6 {
+    font-family: inherit;
+    font-weight: 500;
+    line-height: 1.1;
+    color: inherit;
+  }
+
+  // 寮圭獥
+  .el-dialog {
+    background-color: #1d3778;
+    border-radius: 10px;
+
+    .el-dialog__header {
+      background-color: #1d3778;
+      border-radius: 10px 10px 0 0;
+
+      .el-dialog__title {
+        color: #fff;
+      }
+    }
+
+    .el-dialog__body {
+      background-color: #1a235c;
+    }
+
+    .el-dialog__footer {
+      background-color: #1a235c;
+      border-radius: 0 0 10px 10px;
+    }
+  }
+
+  // 鍗曢��
+  .el-radio__label {
+    color: #fff;
+  }
+
+  // 澶氶��
+  .el-checkbox__label {
+    color: #fff;
+  }
+
+  // 琛ㄥ崟
+  .el-form .el-form-item__label ,.el-form-item__content {
+    font-family: OPPOSans, OPPOSans;
+    font-weight: 500;
+    font-size: 16px;
+    color: #ffffff;
+  }
+
+  .el-dialog:not(.is-fullscreen) {
+    margin-top: 6vh !important;
+  }
+
+  // 杈撳叆妗�
+  .el-input__wrapper {
+    background-color: #1b2255;
+    border: 1px solid #2d67d6;
+    box-shadow: none;
+  }
+
+  .el-input__inner {
+    font-family: OPPOSans, OPPOSans;
+    font-weight: 500;
+    font-size: 16px;
+    color: #a7bfeb;
+  }
+
+  .el-textarea__inner {
+    background-color: #1b2255;
+    border: 1px solid #2d67d6;
+    box-shadow: none;
+    font-family: OPPOSans, OPPOSans;
+    font-weight: 500;
+    color: #a7bfeb;
+    font-size: 16px;
+    // color: #ffffff;
+  }
+
+  // 涓嬫媺姣忔潯
+  .el-select-dropdown {
+    background: #000;
+    border: none;
+    border-radius: 0px;
+    box-shadow: none;
+  }
+
+  .el-select-dropdown__item {
+    // background-color: #000;
+    color: #a7bfeb;
+  }
+
+  // 涓嬫媺hover
+  .el-select-dropdown__item.hover,
+  .el-select-dropdown__item:hover {
+    background-color: #3371eb;
+    color: #fff;
+  }
+
+  // 涓嬫媺閫夐」鑳屾櫙
+  .el-popper {
+    /* 鑳屾櫙鑹� */
+    border: none !important;
+  }
+
+  // 涓嬫媺涓夎
+  .el-popper .el-popper__arrow::before {
+    border: none !important; // 绠ご鎸夐挳杈规
+    background-color: transparent !important; // 绠ご鎸夐挳鑳屾櫙鑹�
+  }
+
+  // 鏃堕棿閫夋嫨
+  .el-date-range-picker .el-picker-panel__body {
+    background-color: #000;
+  }
+
+  // .el-month-table td .cell {
+  // 	color: #fff;
+  // }
+  //鏃堕棿閫夋嫨鍣ㄥ脊妗�-琛ㄥご 
+  .el-date-picker__header {
+    .el-picker-panel__icon-btn {
+      color: rgb(223, 223, 223);
+    }
+
+    .el-date-picker__header-label {
+      color: #a2a2a2;
+    }
+
+  }
+
+  .el-date-picker__header--bordered {
+    border-bottom-color: #8d8d8d;
+  }
+
+  //鏃堕棿閫夋嫨鍣ㄥ脊妗�-鏃ユ湡琛ㄥご
+  .el-date-table th {
+    color: #b1b1b1;
+    border-bottom-color: #8d8d8d;
+  }
+
+  // 鏈�-鏃� 鑳屾櫙瀛�
+  .el-date-table td .el-date-table-cell .el-date-table-cell__text {
+    color: #a7bfeb;
+  }
+
+  .el-year-table td .cell {
+    color: #a7bfeb;
+  }
+
+  .el-month-table td .cell {
+    color: #a7bfeb;
+  }
+
+  .el-popper {
+    background: #000 !important;
+  }
+
+  .el-picker-panel {
+    background: #000 !important;
+  }
+
+  .el-date-editor,
+  .el-range-input {
+    box-shadow: none !important;
+    color: #a7bfeb !important;
+  }
+  .el-dialog.scrollbar .el-dialog__body {
+    overflow: auto;
+    overflow-x: hidden;
+    max-height: 70vh;
+    padding: 10px 20px 0;
+  }
+
+  .el-loading-mask {
+    background-color: rgba(0, 0, 0, 0.8) !important;
+  }
+
+  // 琛ㄦ牸
+  .el-table {
+    background-color: transparent !important;
+    font-family: OPPOSans, OPPOSans;
+    font-weight: 500;
+    font-size: 16px;
+    color: #ffffff;
+
+    .el-table__header-wrapper,
+    .el-table__fixed-header-wrapper,
+    .el-table__footer-wrapper {
+
+      th,
+      td {
+        word-break: break-word;
+        background-color: #1d3778 !important;
+        // color: #515a6e;
+        height: 48px !important;
+        // font-size: 13px;
+
+        border-radius: 0px 0px 0px 0px;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: 500;
+        font-size: 16px;
+        color: #ffffff;
+        border-bottom: none !important;
+      }
+    }
+
+    .el-table__body-wrapper {
+      background: transparent !important;
+
+      .el-button [class*="el-icon-"]+span {
+        margin-left: 1px;
+      }
+      tr {
+        // background: #141E4A;
+        border-radius: 0px 0px 0px 0px;
+        border: none;
+        
+        td {
+          background: transparent !important;
+          border-top: none;
+          height: 48px !important;
+          border-bottom: none;
+        }
+      }
+
+      tr:nth-child(odd) {
+        background: transparent;
+      }
+
+      tr:nth-child(even) {
+        background: #141e4a;
+      }
+
+      // tr:hover > td {
+      // 	background-color: #141E4A;
+      // }
+    }
+  }
+
+  .el-table__body {
+    tr:hover>td {
+      background-color: #4e8bf6 !important;
+    }
+  }
+
+  // 琛ㄦ牸搴曢儴鐧界嚎
+  .el-table__inner-wrapper::before {
+    background-color: transparent;
+  }
+
+  /** 琛ㄥ崟甯冨眬 **/
+  .form-header {
+    font-size: 15px;
+    color: #6379bb;
+    border-bottom: 1px solid #ddd;
+    margin: 8px 10px 25px 10px;
+    padding-bottom: 5px;
+  }
+
+  /** 琛ㄦ牸甯冨眬 **/
+  .pagination-container {
+    // position: relative;
+    // height: 25px;
+    // margin-bottom: 10px;
+    // margin-top: 15px;
+    // padding: 10px 20px !important;
+  }
+
+  .el-dialog .pagination-container {
+    position: static !important;
+  }
+
+  /* tree border */
+  .tree-border {
+    margin-top: 5px;
+    border: 1px solid #e5e6e7;
+    background: #ffffff none;
+    border-radius: 4px;
+    width: 100%;
+  }
+
+  .pagination-container .el-pagination {
+    right: 20px;
+    position: absolute;
+  }
+
+  @media (max-width: 768px) {
+    .pagination-container .el-pagination>.el-pagination__jump {
+      display: none !important;
+    }
+
+    .pagination-container .el-pagination>.el-pagination__sizes {
+      display: none !important;
+    }
+  }
+
+  .el-table .fixed-width .el-button--small {
+    padding-left: 0;
+    padding-right: 0;
+    width: inherit;
+  }
+
+  /** 琛ㄦ牸鏇村鎿嶄綔涓嬫媺鏍峰紡 */
+  .el-table .el-dropdown-link {
+    cursor: pointer;
+    color: #409eff;
+    margin-left: 10px;
+  }
+
+  .el-table .el-dropdown,
+  .el-icon-arrow-down {
+    font-size: 12px;
+  }
+
+  // 鏍�
+  .el-tree {
+    background: transparent;
+    font-family: Inter, Inter;
+    font-weight: 500;
+    font-size: 16px;
+    color: #ffffff;
+
+    .el-tree-node__content {
+      height: 50px;
+    }
+
+    .el-tree-node__expand-icon {
+      color: #3371eb;
+      font-size: 18px;
+    }
+
+    .el-tree-node:focus>.el-tree-node__content {
+      background: rgba(51, 113, 235, 0.5);
+    }
+
+    .el-tree-node__content:hover {
+      background: rgba(51, 113, 235, 0.5);
+    }
+
+    .el-tree--highlight-current,
+    .el-tree-node.is-current>.el-tree-node__content {
+      background: rgba(51, 113, 235, 0.5);
+    }
+  }
+
+  .el-tree-node__content>.el-checkbox {
+    margin-right: 8px;
+  }
+
+  .list-group-striped>.list-group-item {
+    border-left: 0;
+    border-right: 0;
+    border-radius: 0;
+    padding-left: 0;
+    padding-right: 0;
+  }
+
+  .list-group {
+    padding-left: 0px;
+    list-style: none;
+  }
+
+  .list-group-item {
+    border-bottom: 1px solid #e7eaec;
+    border-top: 1px solid #e7eaec;
+    margin-bottom: -1px;
+    padding: 11px 0px;
+    font-size: 13px;
+  }
+
+  .pull-right {
+    float: right !important;
+  }
+
+  // 鍗$墖
+  .el-card {
+    border: none;
+    border-radius: 8px 8px 8px 8px;
+    background-color: transparent;
+  }
+
+  .el-card__header {
+    padding: 14px 15px 7px !important;
+    min-height: 40px;
+    background: #1a235d;
+    border: none;
+  }
+
+  .el-card__body {
+    padding: 15px 20px 20px 20px !important;
+    background: #1a235d;
+    border: none;
+  }
+
+  .card-box {
+    padding-right: 15px;
+    padding-left: 15px;
+    margin-bottom: 10px;
+  }
+
+  /* button color */
+  .el-button--cyan.is-active,
+  .el-button--cyan:active {
+    background: #20b2aa;
+    border-color: #20b2aa;
+    color: #ffffff;
+  }
+
+  .el-button--cyan:focus,
+  .el-button--cyan:hover {
+    background: #48d1cc;
+    border-color: #48d1cc;
+    color: #ffffff;
+  }
+
+  .el-button--cyan {
+    background-color: #20b2aa;
+    border-color: #20b2aa;
+    color: #ffffff;
+  }
+
+  /* text color */
+  .text-navy {
+    color: #1ab394;
+  }
+
+  .text-primary {
+    color: inherit;
+  }
+
+  .text-success {
+    color: #1c84c6;
+  }
+
+  .text-info {
+    color: #23c6c8;
+  }
+
+  .text-warning {
+    color: #f8ac59;
+  }
+
+  .text-danger {
+    color: #ed5565;
+  }
+
+  .text-muted {
+    color: #888888;
+  }
+
+  /* image */
+  .img-circle {
+    border-radius: 50%;
+  }
+
+  .img-lg {
+    width: 120px;
+    height: 120px;
+  }
+
+  .avatar-upload-preview {
+    position: absolute;
+    top: 50%;
+    transform: translate(50%, -50%);
+    width: 200px;
+    height: 200px;
+    border-radius: 50%;
+    box-shadow: 0 0 4px #ccc;
+    overflow: hidden;
+  }
+
+  /* 鎷栨嫿鍒楁牱寮� */
+  .sortable-ghost {
+    opacity: 0.8;
+    color: #fff !important;
+    background: #42b983 !important;
+  }
+
+  /* 琛ㄦ牸鍙充晶宸ュ叿鏍忔牱寮� */
+  .top-right-btn {
+    margin-left: auto;
+  }
+}
+
+
+
+.themeLight {
+  /**
+ * 閫氱敤css鏍峰紡甯冨眬澶勭悊
+ * Copyright (c) 2019 ruoyi
+ */
+
+  /** 鍩虹閫氱敤 **/
+  .pt5 {
+    padding-top: 5px;
+  }
+
+  .pr5 {
+    padding-right: 5px;
+  }
+
+  .pb5 {
+    padding-bottom: 5px;
+  }
+
+  .mt5 {
+    margin-top: 5px;
+  }
+
+  .mr5 {
+    margin-right: 5px;
+  }
+
+  .mb5 {
+    margin-bottom: 5px;
+  }
+
+  .mb8 {
+    margin-bottom: 8px;
+  }
+
+  .ml5 {
+    margin-left: 5px;
+  }
+
+  .mt10 {
+    margin-top: 10px;
+  }
+
+  .mr10 {
+    margin-right: 10px;
+  }
+
+  .mb10 {
+    margin-bottom: 10px;
+  }
+
+  .ml10 {
+    margin-left: 10px;
+  }
+
+  .mt20 {
+    margin-top: 20px;
+  }
+
+  .mr20 {
+    margin-right: 20px;
+  }
+
+  .mb20 {
+    margin-bottom: 20px;
+  }
+
+  .ml20 {
+    margin-left: 20px;
+  }
+
+  .h1,
+  .h2,
+  .h3,
+  .h4,
+  .h5,
+  .h6,
+  h1,
+  h2,
+  h3,
+  h4,
+  h5,
+  h6 {
+    font-family: inherit;
+    font-weight: 500;
+    line-height: 1.1;
+    color: inherit;
+  }
+
+  // // 寮圭獥
+  // .el-dialog {
+  //   background-color: #1d3778;
+  //   border-radius: 10px;
+  //   .el-dialog__header {
+  //     background-color: #1d3778;
+  //     border-radius: 10px 10px 0 0;
+  //     .el-dialog__title {
+  //       color: #fff;
+  //     }
+  //   }
+  //   .el-dialog__body {
+  //     background-color: #1a235c;
+  //   }
+  //   .el-dialog__footer {
+  //     background-color: #1a235c;
+  //     border-radius: 0 0 10px 10px;
+  //   }
+  // }
+
+  // // 鍗曢��
+  // .el-radio__label {
+  //   color: #fff;
+  // }
+  // // 澶氶��
+  // .el-checkbox__label {
+  //   color: #fff;
+  // }
+
+  // // 琛ㄥ崟
+  // .el-form .el-form-item__label {
+  //   font-family: OPPOSans, OPPOSans;
+  //   font-weight: 500;
+  //   font-size: 16px;
+  //   color: #ffffff;
+  // }
+  .el-dialog:not(.is-fullscreen) {
+    margin-top: 6vh !important;
+  }
+
+  // // 杈撳叆妗�
+  // .el-input__wrapper {
+  //   background-color: #1b2255;
+  //   border: 1px solid #2d67d6;
+  //   box-shadow: none;
+  // }
+
+  // .el-input__inner {
+  //   font-family: OPPOSans, OPPOSans;
+  //   font-weight: 500;
+  //   font-size: 16px;
+  //   color: #a7bfeb;
+  // }
+
+  // .el-textarea__inner {
+  //   background-color: #1b2255;
+  //   border: 1px solid #2d67d6;
+  //   box-shadow: none;
+  //   font-family: OPPOSans, OPPOSans;
+  //   font-weight: 500;
+  //   font-size: 16px;
+  //   // color: #ffffff;
+  // }
+
+  // // 涓嬫媺姣忔潯
+  // .el-select-dropdown {
+  //   background: #000;
+  //   border: none;
+  //   border-radius: 0px;
+  //   box-shadow: none;
+  // }
+
+  // .el-select-dropdown__item {
+  //   // background-color: #000;
+  //   color: #a7bfeb;
+  // }
+
+  // // 涓嬫媺hover
+  // .el-select-dropdown__item.hover,
+  // .el-select-dropdown__item:hover {
+  //   background-color: #3371eb;
+  //   color: #fff;
+  // }
+
+  // // 涓嬫媺閫夐」鑳屾櫙
+  // .el-popper {
+  //   /* 鑳屾櫙鑹� */
+  //   border: none !important;
+  // }
+
+  // // 涓嬫媺涓夎
+  // .el-popper .el-popper__arrow::before {
+  //   border: none !important; // 绠ご鎸夐挳杈规
+  //   background-color: transparent !important; // 绠ご鎸夐挳鑳屾櫙鑹�
+  // }
+
+  // // 鏃堕棿閫夋嫨
+  // .el-date-range-picker .el-picker-panel__body {
+  //   background-color: #000;
+  // }
+
+  // // .el-month-table td .cell {
+  // // 	color: #fff;
+  // // }
+
+  // // 鏈�-鏃� 鑳屾櫙瀛�
+  // .el-date-table td .el-date-table-cell .el-date-table-cell__text {
+  //   color: #a7bfeb;
+  // }
+
+  // .el-year-table td .cell {
+  //   color: #a7bfeb;
+  // }
+
+  // .el-month-table td .cell {
+  //   color: #a7bfeb;
+  // }
+
+  // .el-popper {
+  //   background: #000 !important;
+  // }
+
+  // .el-picker-panel {
+  //   background: #000 !important;
+  // }
+
+  // .el-date-editor,
+  // .el-range-input {
+  //   box-shadow: none !important;
+  //   color: #80879e !important;
+  // }
+
+  // .el-dialog.scrollbar .el-dialog__body {
+  //   overflow: auto;
+  //   overflow-x: hidden;
+  //   max-height: 70vh;
+  //   padding: 10px 20px 0;
+  // }
+
+  // .el-loading-mask {
+  //   background-color: rgba(50, 50, 50, 0.8) !important;
+  // }
+
+  // 琛ㄦ牸
+  .el-table {
+    background-color: transparent !important;
+    font-family: OPPOSans, OPPOSans;
+    font-weight: 500;
+    font-size: 16px;
+    color: #ffffff;
+
+    .el-table__header-wrapper,
+    .el-table__fixed-header-wrapper {
+      th {
+        word-break: break-word;
+        background-color: #F7F8FA !important;
+        // color: #515a6e;
+        height: 48px !important;
+        // font-size: 13px;
+
+        border-radius: 0px 0px 0px 0px;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: 500;
+        font-size: 16px;
+        color: #222222;
+        border-bottom: none !important;
+      }
+    }
+
+    .el-table__body-wrapper {
+      background: transparent !important;
+
+      .el-button [class*="el-icon-"]+span {
+        margin-left: 1px;
+      }
+
+      tr {
+        // background: #141E4A;
+        border-radius: 0px 0px 0px 0px;
+        border: none;
+
+        td {
+          border-top: none;
+          height: 48px !important;
+          border-bottom: none;
+          color: #4B4B4B;
+        }
+      }
+
+      tr:nth-child(odd) {
+        background: transparent;
+      }
+
+      tr:nth-child(even) {
+        background: #F7F8FA;
+      }
+
+      // tr:hover > td {
+      // 	background-color: #141E4A;
+      // }
+    }
+  }
+
+  .el-table__body {
+    tr:hover>td {
+      background-color: #E7EEFD !important;
+    }
+  }
+
+  // 琛ㄦ牸搴曢儴鐧界嚎
+  .el-table__inner-wrapper::before {
+    background-color: transparent;
+  }
+
+  /** 琛ㄥ崟甯冨眬 **/
+  .form-header {
+    font-size: 15px;
+    color: #6379bb;
+    border-bottom: 1px solid #ddd;
+    margin: 8px 10px 25px 10px;
+    padding-bottom: 5px;
+  }
+
+  /** 琛ㄦ牸甯冨眬 **/
+  .pagination-container {
+    // position: relative;
+    // height: 25px;
+    // margin-bottom: 10px;
+    // margin-top: 15px;
+    // padding: 10px 20px !important;
+  }
+
+  .el-dialog .pagination-container {
+    position: static !important;
+  }
+
+  /* tree border */
+  .tree-border {
+    margin-top: 5px;
+    border: 1px solid #e5e6e7;
+    background: #ffffff none;
+    border-radius: 4px;
+    width: 100%;
+  }
+
+  .pagination-container .el-pagination {
+    right: 20px;
+    position: absolute;
+  }
+
+  @media (max-width: 768px) {
+    .pagination-container .el-pagination>.el-pagination__jump {
+      display: none !important;
+    }
+
+    .pagination-container .el-pagination>.el-pagination__sizes {
+      display: none !important;
+    }
+  }
+
+  .el-table .fixed-width .el-button--small {
+    padding-left: 0;
+    padding-right: 0;
+    width: inherit;
+  }
+
+  /** 琛ㄦ牸鏇村鎿嶄綔涓嬫媺鏍峰紡 */
+  .el-table .el-dropdown-link {
+    cursor: pointer;
+    color: #409eff;
+    margin-left: 10px;
+  }
+
+  .el-table .el-dropdown,
+  .el-icon-arrow-down {
+    font-size: 12px;
+  }
+
+  // 鏍�
+  .el-tree {
+    background: transparent;
+    font-family: Inter, Inter;
+    font-weight: 500;
+    font-size: 16px;
+    color: #333333;
+
+    .el-tree-node__content {
+      height: 50px;
+    }
+
+    .el-tree-node__expand-icon {
+      color: #393939;
+      font-size: 18px;
+    }
+
+    .el-tree-node:focus>.el-tree-node__content {
+      background: #DAE3F8;
+    }
+
+    .el-tree-node__content:hover {
+      background: #DAE3F8;
+    }
+
+    .el-tree--highlight-current,
+    .el-tree-node.is-current>.el-tree-node__content {
+      background: #DAE3F8;
+    }
+  }
+
+  .el-tree-node__content>.el-checkbox {
+    margin-right: 8px;
+  }
+
+  .list-group-striped>.list-group-item {
+    border-left: 0;
+    border-right: 0;
+    border-radius: 0;
+    padding-left: 0;
+    padding-right: 0;
+  }
+
+  .list-group {
+    padding-left: 0px;
+    list-style: none;
+  }
+
+  .list-group-item {
+    border-bottom: 1px solid #e7eaec;
+    border-top: 1px solid #e7eaec;
+    margin-bottom: -1px;
+    padding: 11px 0px;
+    font-size: 13px;
+  }
+
+  .pull-right {
+    float: right !important;
+  }
+
+  // 鍗$墖
+  .el-card {
+    border: none;
+    border-radius: 8px 8px 8px 8px;
+    background-color: transparent;
+  }
+
+  .el-card__header {
+    padding: 14px 15px 7px !important;
+    min-height: 40px;
+    background: #1a235d;
+    border: none;
+  }
+
+  .el-card__body {
+    padding: 15px 20px 20px 20px !important;
+    // background: #1a235d;
+    border: none;
+  }
+
+  .card-box {
+    padding-right: 15px;
+    padding-left: 15px;
+    margin-bottom: 10px;
+  }
+
+  /* button color */
+  .el-button--cyan.is-active,
+  .el-button--cyan:active {
+    background: #20b2aa;
+    border-color: #20b2aa;
+    color: #ffffff;
+  }
+
+  .el-button--cyan:focus,
+  .el-button--cyan:hover {
+    background: #48d1cc;
+    border-color: #48d1cc;
+    color: #ffffff;
+  }
+
+  .el-button--cyan {
+    background-color: #20b2aa;
+    border-color: #20b2aa;
+    color: #ffffff;
+  }
+
+  /* text color */
+  .text-navy {
+    color: #1ab394;
+  }
+
+  .text-primary {
+    color: inherit;
+  }
+
+  .text-success {
+    color: #1c84c6;
+  }
+
+  .text-info {
+    color: #23c6c8;
+  }
+
+  .text-warning {
+    color: #f8ac59;
+  }
+
+  .text-danger {
+    color: #ed5565;
+  }
+
+  .text-muted {
+    color: #888888;
+  }
+
+  /* image */
+  .img-circle {
+    border-radius: 50%;
+  }
+
+  .img-lg {
+    width: 120px;
+    height: 120px;
+  }
+
+  .avatar-upload-preview {
+    position: absolute;
+    top: 50%;
+    transform: translate(50%, -50%);
+    width: 200px;
+    height: 200px;
+    border-radius: 50%;
+    box-shadow: 0 0 4px #ccc;
+    overflow: hidden;
+  }
+
+  /* 鎷栨嫿鍒楁牱寮� */
+  .sortable-ghost {
+    opacity: 0.8;
+    color: #fff !important;
+    background: #42b983 !important;
+  }
+
+  /* 琛ㄦ牸鍙充晶宸ュ叿鏍忔牱寮� */
+  .top-right-btn {
+    margin-left: auto;
+  }
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/assets/styles/sidebar.scss b/zhitan-vue/src/assets/styles/sidebar.scss
new file mode 100644
index 0000000..ea9eb7e
--- /dev/null
+++ b/zhitan-vue/src/assets/styles/sidebar.scss
@@ -0,0 +1,568 @@
+.themeDark {
+  #app {
+    .el-menu-item.is-active {
+      background: #3271eb !important;
+      border-radius: 30px 30px 30px 30px !important;
+    }
+
+    .el-scrollbar {
+      width: 100%;
+      margin: 0 auto;
+    }
+
+    // .is-active .menu-title {
+    //   color: #fff !important;
+    // }
+
+    .main-container {
+      height: 100%;
+      transition: margin-left 0.28s;
+      margin-left: $base-sidebar-width;
+      position: relative;
+    }
+
+    .sidebarHide {
+      margin-left: 0 !important;
+    }
+
+    .sidebar-container {
+      -webkit-transition: width 0.28s;
+      transition: width 0.28s;
+      width: $base-sidebar-width !important;
+      background-color: $base-menu-background;
+      height: 100%;
+      position: fixed;
+      top: 0;
+      bottom: 0;
+      left: 0;
+      z-index: 1001;
+      overflow: hidden;
+      -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
+      box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
+
+      font-family: OPPOSans, OPPOSans;
+      font-weight: 500;
+      font-size: 18px;
+      color: #FAFAFB;
+
+      // reset element-ui css
+      .horizontal-collapse-transition {
+        transition: 0s width ease-in-out, 0s padding-left ease-in-out,
+          0s padding-right ease-in-out;
+      }
+
+      .scrollbar-wrapper {
+        overflow-x: hidden !important;
+      }
+
+      .el-scrollbar__bar.is-vertical {
+        right: 0px;
+      }
+
+      .el-scrollbar {
+        height: 100%;
+      }
+
+      &.has-logo {
+        .el-scrollbar {
+          height: calc(100% - 100px);
+        }
+      }
+
+      .is-horizontal {
+        display: none;
+      }
+
+      a {
+        display: inline-block;
+        width: 100%;
+        overflow: hidden;
+      }
+
+      .svg-icon {
+        margin-right: 16px;
+      }
+
+      .el-menu {
+        border: none;
+        height: 100%;
+        width: 95%;
+        margin: 0 auto;
+      }
+
+      .el-menu-item,
+      .menu-title {
+        overflow: hidden !important;
+        font-weight: 400 !important;
+        font-size: 18px !important;
+      }
+
+      // @media (min-width: 1440px) {
+      //   .el-menu-item, .menu-title {
+      //     overflow: hidden !important;
+      //     text-overflow: ellipsis !important;
+      //     white-space: nowrap !important;
+      //     font-family: OPPOSans, OPPOSans;
+      //     font-weight: 400 !important;
+      //     font-size: 16px !important;
+      //   }
+      // }
+
+      // @media (min-width: 1920px) {
+      //   .el-menu-item, .menu-title {
+      //     overflow: hidden !important;
+      //     text-overflow: ellipsis !important;
+      //     white-space: nowrap !important;
+      //     font-family: OPPOSans, OPPOSans;
+      //     font-weight: 400 !important;
+      //     font-size: 18px !important;
+      //   }
+      // }
+
+      .el-menu-item .el-menu-tooltip__trigger {
+        display: inline-block !important;
+      }
+
+      // menu hover
+      .sub-menu-title-noDropdown,
+      .el-sub-menu__title {
+        &:hover {
+          background-color: rgba(0, 0, 0, 0.06) !important;
+        }
+      }
+
+      & .theme-dark .is-active > .el-sub-menu__title {
+        color: $base-menu-color-active !important;
+      }
+
+      & .nest-menu .el-sub-menu > .el-sub-menu__title,
+      & .el-sub-menu .el-menu-item {
+        // min-width: $base-sidebar-width !important;
+
+        &:hover {
+          background-color: rgba(0, 0, 0, 0.06) !important;
+          border-radius: 30px 30px 30px 30px !important;
+        }
+      }
+
+      // 瀛愮骇item
+      & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
+      & .theme-dark .el-sub-menu .el-menu-item {
+        border-radius: 30px 30px 30px 30px !important;
+
+        &:hover {
+          background-color: $base-sub-menu-hover !important;
+          border-radius: 30px 30px 30px 30px !important;
+        }
+      }
+    }
+
+    .hideSidebar {
+      .sidebar-container {
+        width: 54px !important;
+      }
+
+      .main-container {
+        margin-left: 54px;
+      }
+
+      .sub-menu-title-noDropdown {
+        padding: 0 !important;
+        position: relative;
+
+        .el-tooltip {
+          padding: 0 !important;
+
+          .svg-icon {
+            margin-left: 20px;
+          }
+        }
+      }
+
+      .el-sub-menu {
+        overflow: hidden;
+
+        & > .el-sub-menu__title {
+          padding: 0 !important;
+
+          .svg-icon {
+            margin-left: 20px;
+          }
+        }
+      }
+
+      .el-menu--collapse {
+        .el-sub-menu {
+          & > .el-sub-menu__title {
+            & > span {
+              height: 0;
+              width: 0;
+              overflow: hidden;
+              visibility: hidden;
+              display: inline-block;
+            }
+            & > i {
+              height: 0;
+              width: 0;
+              overflow: hidden;
+              visibility: hidden;
+              display: inline-block;
+            }
+          }
+        }
+      }
+    }
+
+    .el-menu--collapse .el-menu .el-sub-menu {
+      min-width: $base-sidebar-width !important;
+    }
+
+    // mobile responsive
+    .mobile {
+      .main-container {
+        margin-left: 0px;
+      }
+
+      .sidebar-container {
+        transition: transform 0.28s;
+        width: $base-sidebar-width !important;
+      }
+
+      &.hideSidebar {
+        .sidebar-container {
+          pointer-events: none;
+          transition-duration: 0.3s;
+          transform: translate3d(-$base-sidebar-width, 0, 0);
+        }
+      }
+    }
+
+    .withoutAnimation {
+      .main-container,
+      .sidebar-container {
+        transition: none;
+      }
+    }
+  }
+
+  // when menu collapsed
+  .el-menu--vertical {
+    & > .el-menu {
+      .svg-icon {
+        margin-right: 16px;
+      }
+    }
+
+    .nest-menu .el-sub-menu > .el-sub-menu__title,
+    .el-menu-item {
+      &:hover {
+        // you can use $sub-menuHover
+        background-color: rgba(0, 0, 0, 0.06) !important;
+      }
+    }
+
+    // the scroll bar appears when the sub-menu is too long
+    > .el-menu--popup {
+      max-height: 100vh;
+      overflow-y: auto;
+
+      &::-webkit-scrollbar-track-piece {
+        background: #d3dce6;
+      }
+
+      &::-webkit-scrollbar {
+        width: 6px;
+      }
+
+      &::-webkit-scrollbar-thumb {
+        background: #99a9bf;
+        border-radius: 20px;
+      }
+    }
+  }
+}
+
+.themeLight {
+  #app {
+    .el-menu-item.is-active {
+      background: #e0eafc !important;
+      border-radius: 30px 30px 30px 30px !important;
+    }
+
+    .el-scrollbar {
+      width: 100%;
+      margin: 0 auto;
+    }
+
+    .main-container {
+      height: 100%;
+      transition: margin-left 0.28s;
+      margin-left: $base-sidebar-width;
+      position: relative;
+    }
+
+    .sidebarHide {
+      margin-left: 0 !important;
+    }
+
+    .sidebar-container {
+      -webkit-transition: width 0.28s;
+      transition: width 0.28s;
+      width: $base-sidebar-width !important;
+      background-color: $base-menu-background;
+      height: 100%;
+      position: fixed;
+      top: 0;
+      bottom: 0;
+      left: 0;
+      z-index: 1001;
+      overflow: hidden;
+      -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
+      box-shadow: none;
+      border-right: 1px solid #E6E7E8;
+
+      font-family: OPPOSans, OPPOSans;
+      font-weight: 500;
+      font-size: 18px;
+      color: #1d1d1d;
+
+      // reset element-ui css
+      .horizontal-collapse-transition {
+        transition: 0s width ease-in-out, 0s padding-left ease-in-out,
+          0s padding-right ease-in-out;
+      }
+
+      .scrollbar-wrapper {
+        overflow-x: hidden !important;
+      }
+
+      .el-scrollbar__bar.is-vertical {
+        right: 0px;
+      }
+
+      .el-scrollbar {
+        height: 100%;
+      }
+
+      &.has-logo {
+        .el-scrollbar {
+          height: calc(100% - 100px);
+        }
+      }
+
+      .is-horizontal {
+        display: none;
+      }
+
+      a {
+        display: inline-block;
+        width: 100%;
+        overflow: hidden;
+        border-radius: 30px;
+      }
+
+      .svg-icon {
+        margin-right: 16px;
+        font-size: 18px;
+        color: #C3C3C3;
+      }
+
+      .el-menu {
+        border: none;
+        height: 100%;
+        width: 95%;
+        margin: 0 auto;
+      }
+
+      .el-menu-item,
+      .menu-title {
+        overflow: hidden !important;
+        font-weight: 400 !important;
+        font-size: 18px !important;
+      }
+
+      // @media (min-width: 1440px) {
+      //   .el-menu-item, .menu-title {
+      //     overflow: hidden !important;
+      //     text-overflow: ellipsis !important;
+      //     white-space: nowrap !important;
+      //     font-family: OPPOSans, OPPOSans;
+      //     font-weight: 400 !important;
+      //     font-size: 16px !important;
+      //   }
+      // }
+
+      // @media (min-width: 1920px) {
+      //   .el-menu-item, .menu-title {
+      //     overflow: hidden !important;
+      //     text-overflow: ellipsis !important;
+      //     white-space: nowrap !important;
+      //     font-family: OPPOSans, OPPOSans;
+      //     font-weight: 400 !important;
+      //     font-size: 18px !important;
+      //   }
+      // }
+
+      .el-menu-item .el-menu-tooltip__trigger {
+        display: inline-block !important;
+      }
+
+      // menu hover
+      .sub-menu-title-noDropdown,
+      .el-sub-menu__title {
+        &:hover {
+          background-color: rgba(0, 0, 0, 0.06) !important;
+        }
+      }
+
+      & .theme-dark .is-active > .el-sub-menu__title {
+        // color: $base-menu-color-active !important;
+      }
+
+      & .nest-menu .el-sub-menu > .el-sub-menu__title,
+      & .el-sub-menu .el-menu-item {
+        // min-width: $base-sidebar-width !important;
+
+        &:hover {
+          background-color: #E0EAFC !important;
+          border-radius: 30px 30px 30px 30px !important;
+        }
+      }
+
+      // 瀛愮骇item
+      & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
+      & .theme-dark .el-sub-menu .el-menu-item {
+        background-color: #E0EAFC !important;
+        border-radius: 30px 30px 30px 30px !important;
+
+        &:hover {
+          background-color: #E0EAFC !important;
+          border-radius: 30px 30px 30px 30px !important;
+        }
+      }
+    }
+
+    .hideSidebar {
+      .sidebar-container {
+        width: 54px !important;
+      }
+
+      .main-container {
+        margin-left: 54px;
+      }
+
+      .sub-menu-title-noDropdown {
+        padding: 0 !important;
+        position: relative;
+
+        .el-tooltip {
+          padding: 0 !important;
+
+          .svg-icon {
+            margin-left: 20px;
+          }
+        }
+      }
+
+      .el-sub-menu {
+        overflow: hidden;
+
+        & > .el-sub-menu__title {
+          padding: 0 !important;
+
+          .svg-icon {
+            margin-left: 20px;
+          }
+        }
+      }
+
+      .el-menu--collapse {
+        .el-sub-menu {
+          & > .el-sub-menu__title {
+            & > span {
+              height: 0;
+              width: 0;
+              overflow: hidden;
+              visibility: hidden;
+              display: inline-block;
+            }
+            & > i {
+              height: 0;
+              width: 0;
+              overflow: hidden;
+              visibility: hidden;
+              display: inline-block;
+            }
+          }
+        }
+      }
+    }
+
+    .el-menu--collapse .el-menu .el-sub-menu {
+      min-width: $base-sidebar-width !important;
+    }
+
+    // mobile responsive
+    .mobile {
+      .main-container {
+        margin-left: 0px;
+      }
+
+      .sidebar-container {
+        transition: transform 0.28s;
+        width: $base-sidebar-width !important;
+      }
+
+      &.hideSidebar {
+        .sidebar-container {
+          pointer-events: none;
+          transition-duration: 0.3s;
+          transform: translate3d(-$base-sidebar-width, 0, 0);
+        }
+      }
+    }
+
+    .withoutAnimation {
+      .main-container,
+      .sidebar-container {
+        transition: none;
+      }
+    }
+  }
+
+  // when menu collapsed
+  .el-menu--vertical {
+    & > .el-menu {
+      .svg-icon {
+        margin-right: 16px;
+      }
+    }
+
+    .nest-menu .el-sub-menu > .el-sub-menu__title,
+    .el-menu-item {
+      &:hover {
+        // you can use $sub-menuHover
+        background-color: rgba(0, 0, 0, 0.06) !important;
+      }
+    }
+
+    // the scroll bar appears when the sub-menu is too long
+    > .el-menu--popup {
+      max-height: 100vh;
+      overflow-y: auto;
+
+      &::-webkit-scrollbar-track-piece {
+        background: #d3dce6;
+      }
+
+      &::-webkit-scrollbar {
+        width: 6px;
+      }
+
+      &::-webkit-scrollbar-thumb {
+        background: #99a9bf;
+        border-radius: 20px;
+      }
+    }
+  }
+}
diff --git a/zhitan-vue/src/assets/styles/transition.scss b/zhitan-vue/src/assets/styles/transition.scss
new file mode 100644
index 0000000..073f8c6
--- /dev/null
+++ b/zhitan-vue/src/assets/styles/transition.scss
@@ -0,0 +1,49 @@
+// global transition css
+
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+  opacity: 0;
+}
+
+/* fade-transform */
+.fade-transform--move,
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+  transition: all .5s;
+}
+
+.fade-transform-enter {
+  opacity: 0;
+  transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+  transform: translateX(30px);
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+  transition: all .5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+  opacity: 0;
+  transform: translateX(20px);
+}
+
+.breadcrumb-move {
+  transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+  position: absolute;
+}
diff --git a/zhitan-vue/src/assets/styles/variables.module.scss b/zhitan-vue/src/assets/styles/variables.module.scss
new file mode 100644
index 0000000..85780cf
--- /dev/null
+++ b/zhitan-vue/src/assets/styles/variables.module.scss
@@ -0,0 +1,65 @@
+// base color
+$blue: #324157;
+$light-blue: #3A71A8;
+$red: #C03639;
+$pink: #E65D6E;
+$green: #30B08F;
+$tiffany: #4AB7BD;
+$yellow: #FEC171;
+$panGreen: #30B08F;
+
+// 榛樿鑿滃崟涓婚椋庢牸
+$base-menu-color: #fff;
+$base-menu-color-active: #f4f4f5;
+$base-menu-background: #1A235D;
+$base-logo-title-color: #ffffff;
+
+$base-menu-light-color: rgba(0, 0, 0, 0.7);
+$base-menu-light-background: #ffffff;
+$base-logo-light-title-color: #001529;
+
+$base-sub-menu-background: #1A235D;
+$base-sub-menu-hover: #3371EB; //榧犳爣鎮仠
+
+// 鑷畾涔夋殫鑹茶彍鍗曢鏍�
+/**
+$base-menu-color:hsla(0,0%,100%,.65);
+$base-menu-color-active:#fff;
+$base-menu-background:#001529;
+$base-logo-title-color: #ffffff;
+
+$base-menu-light-color:rgba(0,0,0,.70);
+$base-menu-light-background:#ffffff;
+$base-logo-light-title-color: #001529;
+
+$base-sub-menu-background:#000c17;
+$base-sub-menu-hover:#001528;
+*/
+
+$--color-primary: #409EFF;
+$--color-success: #67C23A;
+$--color-warning: #E6A23C;
+$--color-danger: #F56C6C;
+$--color-info: #909399;
+
+$base-sidebar-width: 280px;
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+  menuColor: $base-menu-color;
+  menuLightColor: $base-menu-light-color;
+  menuColorActive: $base-menu-color-active;
+  menuBackground: $base-menu-background;
+  menuLightBackground: $base-menu-light-background;
+  subMenuBackground: $base-sub-menu-background;
+  subMenuHover: $base-sub-menu-hover;
+  sideBarWidth: $base-sidebar-width;
+  logoTitleColor: $base-logo-title-color;
+  logoLightTitleColor: $base-logo-light-title-color;
+  primaryColor: $--color-primary;
+  successColor: $--color-success;
+  dangerColor: $--color-danger;
+  infoColor: $--color-info;
+  warningColor: $--color-warning;
+}
diff --git a/zhitan-vue/src/components/BaseCard/BaseCard.vue b/zhitan-vue/src/components/BaseCard/BaseCard.vue
new file mode 100644
index 0000000..f0bc7f3
--- /dev/null
+++ b/zhitan-vue/src/components/BaseCard/BaseCard.vue
@@ -0,0 +1,99 @@
+<template>
+  <div class="mycard" :style="{ width: props.width }">
+    <div class="mycard-title" v-if="!!title">
+      <div class="name">{{ props.title }}</div>
+    </div>
+    <div class="mycard-content">
+      <div class="data-box" v-if="props.tabArray.length > 0">
+        <div v-for="(item, i) in props.tabArray" :key="i" class="li-box" :class="isActive == item.value ? 'is-li' : ''"
+          @click="changeActive(item.value)">
+          {{ item.label }}
+        </div>
+      </div>
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script setup>
+const emit = defineEmits();
+const props = defineProps({
+  title: {
+    type: String,
+    default: "",
+  },
+  width: {
+    type: String,
+    default: "100%",
+  },
+  tabArray: {
+    type: Array,
+    default: () => [],
+  },
+});
+const data = reactive({
+  isActive: "DAY",
+});
+const { isActive } = toRefs(data);
+changeActive(isActive.value);
+
+function changeActive(value) {
+  isActive.value = value;
+  emit("changeActive", isActive.value);
+}
+</script>
+
+<style lang="scss" scoped>
+.mycard {
+  //13
+  margin-top: 0;
+  padding-bottom: 1.2963vh; //14
+
+  .mycard-title {
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    height: 3.7037vh; //40
+    padding-left: 2.1354vw; //41px;
+    background: url('../../assets/images/basecard/title_bg.png') no-repeat;
+    background-size: auto 100%;
+
+    .name {
+      font-family: YouSheBiaoTiHei;
+      font-size: 1.2500vw; //24px;
+      color: #fff;
+    }
+  }
+
+  .mycard-content {
+    border: 1px solid;
+    border-image: linear-gradient(0deg, #0A3C86, #000) 1;
+    background: linear-gradient(0deg, rgba(18, 111, 216, 0.2) 0%, rgba(18, 111, 216, 0) 100%);
+    position: relative;
+
+    .data-box {
+      position: absolute;
+      right: 0.6771vw;
+      top: 0.9259vh;
+      color: #fff;
+      display: flex;
+      justify-content: flex-start;
+      align-items: center;
+      z-index: 1;
+
+      .li-box {
+        border: 1px solid #2E86EA;
+        cursor: pointer;
+        text-align: center;
+        padding: 0.3704vh 0.3125vw;
+        font-size: 0.6771vw;
+      }
+
+      .is-li {
+        background: #2E86EA;
+        border: 1px solid #2E86EA;
+      }
+    }
+  }
+}
+</style>
diff --git a/zhitan-vue/src/components/BaseCard/index.vue b/zhitan-vue/src/components/BaseCard/index.vue
new file mode 100644
index 0000000..398a771
--- /dev/null
+++ b/zhitan-vue/src/components/BaseCard/index.vue
@@ -0,0 +1,67 @@
+<template>
+  <div class="mycard">
+    <div class="mycard-title">
+      <div class="name">{{ title }}</div>
+    </div>
+    <slot></slot>
+  </div>
+</template>
+
+<script setup>
+defineProps(['title'])
+
+</script>
+
+<style lang="scss" scoped>
+.themeDark {
+  .mycard {
+    margin: 0 13px;
+    margin-top: 10px;
+    border-radius: 10px 10px 10px 10px;
+    border: 1px solid #22408c;
+    padding-bottom: 10px;
+    
+    .mycard-title {
+      display: flex;
+      justify-content: flex-start;
+      align-items: center;
+      height: 50px;
+      border-radius: 7px 7px 0 0;
+      padding: 20px;
+      background-color: #22408c;
+      .name {
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 18px;
+        color: #fff;
+      }
+    }
+  }
+}
+
+.themeLight {
+  .mycard {
+    margin: 0 13px;
+    margin-top: 10px;
+    border-radius: 10px 10px 10px 10px;
+    border: 1px solid #ebebeb;
+    padding-bottom: 10px;
+    background-color: #fff;
+    .mycard-title {
+      display: flex;
+      justify-content: flex-start;
+      align-items: center;
+      height: 50px;
+      border-radius: 7px 7px 0 0;
+      padding: 20px;
+      background-color: #e7eefd;
+      .name {
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 18px;
+        color: #2d2e31;
+      }
+    }
+  }
+}
+</style>
diff --git a/zhitan-vue/src/components/Breadcrumb/index.vue b/zhitan-vue/src/components/Breadcrumb/index.vue
new file mode 100644
index 0000000..729370a
--- /dev/null
+++ b/zhitan-vue/src/components/Breadcrumb/index.vue
@@ -0,0 +1,66 @@
+<template>
+  <el-breadcrumb class="app-breadcrumb" separator="/">
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
+        <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta.title }}</span>
+        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<script setup>
+const route = useRoute();
+const router = useRouter();
+const levelList = ref([])
+
+function getBreadcrumb() {
+  // only show routes with meta.title
+  let matched = route.matched.filter(item => item.meta && item.meta.title);
+  const first = matched[0]
+  // 鍒ゆ柇鏄惁涓洪椤�
+  if (!isDashboard(first)) {
+    matched = [{ path: '/index', meta: { title: '棣栭〉' } }].concat(matched)
+  }
+
+  levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
+}
+function isDashboard(route) {
+  const name = route && route.name
+  if (!name) {
+    return false
+  }
+  return name.trim() === 'Index'
+}
+function handleLink(item) {
+  const { redirect, path } = item
+  if (redirect) {
+    router.push(redirect)
+    return
+  }
+  router.push(path)
+}
+
+watchEffect(() => {
+  // if you go to the redirect page, do not update the breadcrumbs
+  if (route.path.startsWith('/redirect/')) {
+    return
+  }
+  getBreadcrumb()
+})
+getBreadcrumb();
+</script>
+
+<style lang='scss' scoped>
+.app-breadcrumb.el-breadcrumb {
+  display: inline-block;
+  font-size: 14px;
+  line-height: 50px;
+  margin-left: 8px;
+
+  .no-redirect {
+    color: #fff;
+    cursor: text;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/CardHeader/CardHeader.vue b/zhitan-vue/src/components/CardHeader/CardHeader.vue
new file mode 100644
index 0000000..1456322
--- /dev/null
+++ b/zhitan-vue/src/components/CardHeader/CardHeader.vue
@@ -0,0 +1,121 @@
+<template>
+  <div class="header">
+    <slot></slot>
+    <div class="btn-list" v-if="props.showBtn">
+      <div class="btn-list-item" :class="{ active: timeType == dict.value }" v-for="dict in props.period"
+        :key="dict.value" @click="handleClick(dict.value)">
+        {{ dict.label }}
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+const emit = defineEmits();
+const props = defineProps(["showBtn", "period", "active"]);
+const data = reactive({
+  timeType: 'DAY',
+});
+const { timeType } = toRefs(data);
+handleClick(timeType.value);
+
+function handleClick(value) {
+  timeType.value = value;
+  emit("handleClick", timeType.value, props.active);
+}
+</script>
+
+<style lang="scss" scoped>
+.themeDark {
+  .header {
+    // width: 88px;
+    height: 29px;
+    font-family: OPPOSans, OPPOSans;
+    font-weight: bold;
+    font-size: 22px;
+    color: #fffefe;
+    line-height: 29px;
+    text-align: left;
+    font-style: normal;
+    text-transform: none;
+    display: flex;
+    justify-content: space-between;
+
+    .btn-list {
+      display: flex;
+
+      .btn-list-item {
+        width: 46px;
+        height: 32px;
+        line-height: 32px;
+        background: #3041ab;
+        font-family: OPPOSans, OPPOSans;
+        text-align: center;
+        font-weight: 500;
+        font-size: 14px;
+        color: #5996f9;
+        cursor: pointer;
+      }
+
+      :first-child {
+        border-radius: 4px 0px 0px 4px;
+      }
+
+      :last-child {
+        border-radius: 0px 4px 4px 0px;
+      }
+
+      .active {
+        background: #1d6aff;
+        color: #fff;
+      }
+    }
+  }
+}
+
+.themeLight {
+  .header {
+    // width: 88px;
+    height: 29px;
+    font-family: OPPOSans, OPPOSans;
+    font-weight: bold;
+    font-size: 22px;
+    color: #000;
+    line-height: 29px;
+    text-align: left;
+    font-style: normal;
+    text-transform: none;
+    display: flex;
+    justify-content: space-between;
+
+    .btn-list {
+      display: flex;
+
+      .btn-list-item {
+        width: 46px;
+        height: 32px;
+        line-height: 32px;
+        background: #f7f8fa;
+        font-family: OPPOSans, OPPOSans;
+        text-align: center;
+        font-weight: 500;
+        font-size: 14px;
+        color: #5996f9;
+        cursor: pointer;
+      }
+
+      :first-child {
+        border-radius: 4px 0px 0px 4px;
+      }
+
+      :last-child {
+        border-radius: 0px 4px 4px 0px;
+      }
+
+      .active {
+        background: #1d6aff;
+        color: #fff;
+      }
+    } }
+}
+</style>
diff --git a/zhitan-vue/src/components/Crontab/day.vue b/zhitan-vue/src/components/Crontab/day.vue
new file mode 100644
index 0000000..25c4f79
--- /dev/null
+++ b/zhitan-vue/src/components/Crontab/day.vue
@@ -0,0 +1,174 @@
+<template>
+    <el-form>
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="1">
+                鏃ワ紝鍏佽鐨勯�氶厤绗, - * ? / L W]
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="2">
+                涓嶆寚瀹�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="3">
+                鍛ㄦ湡浠�
+                <el-input-number v-model='cycle01' :min="1" :max="30" /> -
+                <el-input-number v-model='cycle02' :min="cycle01 + 1" :max="31" /> 鏃�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="4">
+                浠�
+                <el-input-number v-model='average01' :min="1" :max="30" /> 鍙峰紑濮嬶紝姣�
+                <el-input-number v-model='average02' :min="1" :max="31 - average01" /> 鏃ユ墽琛屼竴娆�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="5">
+                姣忔湀
+                <el-input-number v-model='workday' :min="1" :max="31" /> 鍙锋渶杩戠殑閭d釜宸ヤ綔鏃�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="6">
+                鏈湀鏈�鍚庝竴澶�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="7">
+                鎸囧畾
+                <el-select clearable v-model="checkboxList" placeholder="鍙閫�" multiple :multiple-limit="10">
+                    <el-option v-for="item in 31" :key="item" :label="item" :value="item" />
+                </el-select>
+            </el-radio>
+        </el-form-item>
+    </el-form>
+</template>
+<script setup>
+const emit = defineEmits(['update'])
+const props = defineProps({
+    cron: {
+        type: Object,
+        default: {
+            second: "*",
+            min: "*",
+            hour: "*",
+            day: "*",
+            month: "*",
+            week: "?",
+            year: "",
+        }
+    },
+    check: {
+        type: Function,
+        default: () => {
+        }
+    }
+})
+const radioValue = ref(1)
+const cycle01 = ref(1)
+const cycle02 = ref(2)
+const average01 = ref(1)
+const average02 = ref(1)
+const workday = ref(1)
+const checkboxList = ref([])
+const checkCopy = ref([1])
+const cycleTotal = computed(() => {
+    cycle01.value = props.check(cycle01.value, 1, 30)
+    cycle02.value = props.check(cycle02.value, cycle01.value + 1, 31)
+    return cycle01.value + '-' + cycle02.value
+})
+const averageTotal = computed(() => {
+    average01.value = props.check(average01.value, 1, 30)
+    average02.value = props.check(average02.value, 1, 31 - average01.value)
+    return average01.value + '/' + average02.value
+})
+const workdayTotal = computed(() => {
+    workday.value = props.check(workday.value, 1, 31)
+    return workday.value + 'W'
+})
+const checkboxString = computed(() => {
+    return checkboxList.value.join(',')
+})
+watch(() => props.cron.day, value => changeRadioValue(value))
+watch([radioValue, cycleTotal, averageTotal, workdayTotal, checkboxString], () => onRadioChange())
+function changeRadioValue(value) {
+    if (value === "*") {
+        radioValue.value = 1
+    } else if (value === "?") {
+        radioValue.value = 2
+    } else if (value.indexOf("-") > -1) {
+        const indexArr = value.split('-')
+        cycle01.value = Number(indexArr[0])
+        cycle02.value = Number(indexArr[1])
+        radioValue.value = 3
+    } else if (value.indexOf("/") > -1) {
+        const indexArr = value.split('/')
+        average01.value = Number(indexArr[0])
+        average02.value = Number(indexArr[1])
+        radioValue.value = 4
+    } else if (value.indexOf("W") > -1) {
+        const indexArr = value.split("W")
+        workday.value = Number(indexArr[0])
+        radioValue.value = 5
+    } else if (value === "L") {
+        radioValue.value = 6
+    } else {
+        checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
+        radioValue.value = 7
+    }
+}
+// 鍗曢�夋寜閽�煎彉鍖栨椂
+function onRadioChange() {
+    if (radioValue.value === 2 && props.cron.week === '?') {
+        emit('update', 'week', '*', 'day')
+    }
+    if (radioValue.value !== 2 && props.cron.week !== '?') {
+        emit('update', 'week', '?', 'day')
+    }
+    switch (radioValue.value) {
+        case 1:
+            emit('update', 'day', '*', 'day')
+            break
+        case 2:
+            emit('update', 'day', '?', 'day')
+            break
+        case 3:
+            emit('update', 'day', cycleTotal.value, 'day')
+            break
+        case 4:
+            emit('update', 'day', averageTotal.value, 'day')
+            break
+        case 5:
+            emit('update', 'day', workdayTotal.value, 'day')
+            break
+        case 6:
+            emit('update', 'day', 'L', 'day')
+            break
+        case 7:
+            if (checkboxList.value.length === 0) {
+                checkboxList.value.push(checkCopy.value[0])
+            } else {
+                checkCopy.value = checkboxList.value
+            }
+            emit('update', 'day', checkboxString.value, 'day')
+            break
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-input-number--small, .el-select, .el-select--small {
+    margin: 0 0.2rem;
+}
+.el-select, .el-select--small {
+    width: 18.8rem;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/Crontab/hour.vue b/zhitan-vue/src/components/Crontab/hour.vue
new file mode 100644
index 0000000..9f052ad
--- /dev/null
+++ b/zhitan-vue/src/components/Crontab/hour.vue
@@ -0,0 +1,127 @@
+<template>
+    <el-form>
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="1">
+                灏忔椂锛屽厑璁哥殑閫氶厤绗, - * /]
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="2">
+                鍛ㄦ湡浠�
+                <el-input-number v-model='cycle01' :min="0" :max="22" /> -
+                <el-input-number v-model='cycle02' :min="cycle01 + 1" :max="23" /> 鏃�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="3">
+                浠�
+                <el-input-number v-model='average01' :min="0" :max="22" /> 鏃跺紑濮嬶紝姣�
+                <el-input-number v-model='average02' :min="1" :max="23 - average01" /> 灏忔椂鎵ц涓�娆�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="4">
+                鎸囧畾
+                <el-select clearable v-model="checkboxList" placeholder="鍙閫�" multiple :multiple-limit="10">
+                    <el-option v-for="item in 24" :key="item" :label="item - 1" :value="item - 1" />
+                </el-select>
+            </el-radio>
+        </el-form-item>
+    </el-form>
+</template>
+
+<script setup>
+const emit = defineEmits(['update'])
+const props = defineProps({
+    cron: {
+        type: Object,
+        default: {
+            second: "*",
+            min: "*",
+            hour: "*",
+            day: "*",
+            month: "*",
+            week: "?",
+            year: "",
+        }
+    },
+    check: {
+        type: Function,
+        default: () => {
+        }
+    }
+})
+const radioValue = ref(1)
+const cycle01 = ref(0)
+const cycle02 = ref(1)
+const average01 = ref(0)
+const average02 = ref(1)
+const checkboxList = ref([])
+const checkCopy = ref([0])
+const cycleTotal = computed(() => {
+    cycle01.value = props.check(cycle01.value, 0, 22)
+    cycle02.value = props.check(cycle02.value, cycle01.value + 1, 23)
+    return cycle01.value + '-' + cycle02.value
+})
+const averageTotal = computed(() => {
+    average01.value = props.check(average01.value, 0, 22)
+    average02.value = props.check(average02.value, 1, 23 - average01.value)
+    return average01.value + '/' + average02.value
+})
+const checkboxString = computed(() => {
+    return checkboxList.value.join(',')
+})
+watch(() => props.cron.hour, value => changeRadioValue(value))
+watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
+function changeRadioValue(value) {
+    if (value === '*') {
+        radioValue.value = 1
+    } else if (value.indexOf('-') > -1) {
+        const indexArr = value.split('-')
+        cycle01.value = Number(indexArr[0])
+        cycle02.value = Number(indexArr[1])
+        radioValue.value = 2
+    } else if (value.indexOf('/') > -1) {
+        const indexArr = value.split('/')
+        average01.value = Number(indexArr[0])
+        average02.value = Number(indexArr[1])
+        radioValue.value = 3
+    } else {
+        checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
+        radioValue.value = 4
+    }
+}
+function onRadioChange() {
+    switch (radioValue.value) {
+        case 1:
+            emit('update', 'hour', '*', 'hour')
+            break
+        case 2:
+            emit('update', 'hour', cycleTotal.value, 'hour')
+            break
+        case 3:
+            emit('update', 'hour', averageTotal.value, 'hour')
+            break
+        case 4:
+            if (checkboxList.value.length === 0) {
+                checkboxList.value.push(checkCopy.value[0])
+            } else {
+                checkCopy.value = checkboxList.value
+            }
+            emit('update', 'hour', checkboxString.value, 'hour')
+            break
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-input-number--small, .el-select, .el-select--small {
+    margin: 0 0.2rem;
+}
+.el-select, .el-select--small {
+    width: 18.8rem;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/Crontab/index.vue b/zhitan-vue/src/components/Crontab/index.vue
new file mode 100644
index 0000000..910c9b3
--- /dev/null
+++ b/zhitan-vue/src/components/Crontab/index.vue
@@ -0,0 +1,310 @@
+<template>
+    <div>
+        <el-tabs type="border-card">
+            <el-tab-pane label="绉�" v-if="shouldHide('second')">
+                <CrontabSecond
+                    @update="updateCrontabValue"
+                    :check="checkNumber"
+                    :cron="crontabValueObj"
+                    ref="cronsecond"
+                />
+            </el-tab-pane>
+
+            <el-tab-pane label="鍒嗛挓" v-if="shouldHide('min')">
+                <CrontabMin
+                    @update="updateCrontabValue"
+                    :check="checkNumber"
+                    :cron="crontabValueObj"
+                    ref="cronmin"
+                />
+            </el-tab-pane>
+
+            <el-tab-pane label="灏忔椂" v-if="shouldHide('hour')">
+                <CrontabHour
+                    @update="updateCrontabValue"
+                    :check="checkNumber"
+                    :cron="crontabValueObj"
+                    ref="cronhour"
+                />
+            </el-tab-pane>
+
+            <el-tab-pane label="鏃�" v-if="shouldHide('day')">
+                <CrontabDay
+                    @update="updateCrontabValue"
+                    :check="checkNumber"
+                    :cron="crontabValueObj"
+                    ref="cronday"
+                />
+            </el-tab-pane>
+
+            <el-tab-pane label="鏈�" v-if="shouldHide('month')">
+                <CrontabMonth
+                    @update="updateCrontabValue"
+                    :check="checkNumber"
+                    :cron="crontabValueObj"
+                    ref="cronmonth"
+                />
+            </el-tab-pane>
+
+            <el-tab-pane label="鍛�" v-if="shouldHide('week')">
+                <CrontabWeek
+                    @update="updateCrontabValue"
+                    :check="checkNumber"
+                    :cron="crontabValueObj"
+                    ref="cronweek"
+                />
+            </el-tab-pane>
+
+            <el-tab-pane label="骞�" v-if="shouldHide('year')">
+                <CrontabYear
+                    @update="updateCrontabValue"
+                    :check="checkNumber"
+                    :cron="crontabValueObj"
+                    ref="cronyear"
+                />
+            </el-tab-pane>
+        </el-tabs>
+
+        <div class="popup-main">
+            <div class="popup-result">
+                <p class="title">鏃堕棿琛ㄨ揪寮�</p>
+                <table>
+                    <thead>
+                        <th v-for="item of tabTitles" :key="item">{{item}}</th>
+                        <th>Cron 琛ㄨ揪寮�</th>
+                    </thead>
+                    <tbody>
+                        <td>
+                            <span v-if="crontabValueObj.second.length < 10">{{crontabValueObj.second}}</span>
+                            <el-tooltip v-else :content="crontabValueObj.second" placement="top"><span>{{crontabValueObj.second}}</span></el-tooltip>
+                        </td>
+                        <td>
+                            <span v-if="crontabValueObj.min.length < 10">{{crontabValueObj.min}}</span>
+                            <el-tooltip v-else :content="crontabValueObj.min" placement="top"><span>{{crontabValueObj.min}}</span></el-tooltip>
+                        </td>
+                        <td>
+                            <span v-if="crontabValueObj.hour.length < 10">{{crontabValueObj.hour}}</span>
+                            <el-tooltip v-else :content="crontabValueObj.hour" placement="top"><span>{{crontabValueObj.hour}}</span></el-tooltip>
+                        </td>
+                        <td>
+                            <span v-if="crontabValueObj.day.length < 10">{{crontabValueObj.day}}</span>
+                            <el-tooltip v-else :content="crontabValueObj.day" placement="top"><span>{{crontabValueObj.day}}</span></el-tooltip>
+                        </td>
+                        <td>
+                            <span v-if="crontabValueObj.month.length < 10">{{crontabValueObj.month}}</span>
+                            <el-tooltip v-else :content="crontabValueObj.month" placement="top"><span>{{crontabValueObj.month}}</span></el-tooltip>
+                        </td>
+                        <td>
+                            <span v-if="crontabValueObj.week.length < 10">{{crontabValueObj.week}}</span>
+                            <el-tooltip v-else :content="crontabValueObj.week" placement="top"><span>{{crontabValueObj.week}}</span></el-tooltip>
+                        </td>
+                        <td>
+                            <span v-if="crontabValueObj.year.length < 10">{{crontabValueObj.year}}</span>
+                            <el-tooltip v-else :content="crontabValueObj.year" placement="top"><span>{{crontabValueObj.year}}</span></el-tooltip>
+                        </td>
+                        <td class="result">
+                            <span v-if="crontabValueString.length < 90">{{crontabValueString}}</span>
+                            <el-tooltip v-else :content="crontabValueString" placement="top"><span>{{crontabValueString}}</span></el-tooltip>
+                        </td>
+                    </tbody>
+                </table>
+            </div>
+            <CrontabResult :ex="crontabValueString"></CrontabResult>
+
+            <div class="pop_btn">
+                <el-button type="primary" @click="submitFill">纭畾</el-button>
+                <el-button type="warning" @click="clearCron">閲嶇疆</el-button>
+                <el-button @click="hidePopup">鍙栨秷</el-button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import CrontabSecond from "./second.vue"
+import CrontabMin from "./min.vue"
+import CrontabHour from "./hour.vue"
+import CrontabDay from "./day.vue"
+import CrontabMonth from "./month.vue"
+import CrontabWeek from "./week.vue"
+import CrontabYear from "./year.vue"
+import CrontabResult from "./result.vue"
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['hide', 'fill'])
+const props = defineProps({
+    hideComponent: {
+        type: Array,
+        default: () => [],
+    },
+    expression: {
+        type: String,
+        default: ""
+    }
+})
+const tabTitles = ref(["绉�", "鍒嗛挓", "灏忔椂", "鏃�", "鏈�", "鍛�", "骞�"])
+const tabActive = ref(0)
+const hideComponent = ref([])
+const expression = ref('')
+const crontabValueObj = ref({
+    second: "*",
+    min: "*",
+    hour: "*",
+    day: "*",
+    month: "*",
+    week: "?",
+    year: "",
+})
+const crontabValueString = computed(() => {
+    const obj = crontabValueObj.value
+    return obj.second
+        + " "
+        + obj.min
+        + " "
+        + obj.hour
+        + " "
+        + obj.day
+        + " "
+        + obj.month
+        + " "
+        + obj.week
+        + (obj.year === "" ? "" : " " + obj.year)
+})
+watch(expression, () => resolveExp())
+function shouldHide(key) {
+    return !(hideComponent.value && hideComponent.value.includes(key))
+}
+function resolveExp() {
+    // 鍙嶈В鏋� 琛ㄨ揪寮�
+    if (expression.value) {
+        const arr = expression.value.split(/\s+/)
+        if (arr.length >= 6) {
+            //6 浣嶄互涓婃槸鍚堟硶琛ㄨ揪寮�
+            let obj = {
+                second: arr[0],
+                min: arr[1],
+                hour: arr[2],
+                day: arr[3],
+                month: arr[4],
+                week: arr[5],
+                year: arr[6] ? arr[6] : ""
+            }
+            crontabValueObj.value = {
+                ...obj,
+            }
+        }
+    } else {
+        // 娌℃湁浼犲叆鐨勮〃杈惧紡 鍒欒繕鍘�
+        clearCron()
+    }
+}
+// tab鍒囨崲鍊�
+function tabCheck(index) {
+    tabActive.value = index
+}
+// 鐢卞瓙缁勪欢瑙﹀彂锛屾洿鏀硅〃杈惧紡缁勬垚鐨勫瓧娈靛��
+function updateCrontabValue(name, value, from) {
+    crontabValueObj.value[name] = value
+}
+// 琛ㄥ崟閫夐」鐨勫瓙缁勪欢鏍¢獙鏁板瓧鏍煎紡锛堥�氳繃-props浼犻�掞級
+function checkNumber(value, minLimit, maxLimit) {
+    // 妫�鏌ュ繀椤讳负鏁存暟
+    value = Math.floor(value)
+    if (value < minLimit) {
+        value = minLimit
+    } else if (value > maxLimit) {
+        value = maxLimit
+    }
+    return value
+}
+// 闅愯棌寮圭獥
+function hidePopup() {
+    emit("hide")
+}
+// 濉厖琛ㄨ揪寮�
+function submitFill() {
+    emit("fill", crontabValueString.value)
+    hidePopup()
+}
+function clearCron() {
+    // 杩樺師閫夋嫨椤�
+    crontabValueObj.value = {
+        second: "*",
+        min: "*",
+        hour: "*",
+        day: "*",
+        month: "*",
+        week: "?",
+        year: "",
+    }
+}
+onMounted(() => {
+    expression.value = props.expression
+    hideComponent.value = props.hideComponent
+})
+</script>
+
+<style lang="scss" scoped>
+.pop_btn {
+    text-align: center;
+    margin-top: 20px;
+}
+.popup-main {
+    position: relative;
+    margin: 10px auto;
+    background: #fff;
+    border-radius: 5px;
+    font-size: 12px;
+    overflow: hidden;
+}
+.popup-title {
+    overflow: hidden;
+    line-height: 34px;
+    padding-top: 6px;
+    background: #f2f2f2;
+}
+.popup-result {
+    box-sizing: border-box;
+    line-height: 24px;
+    margin: 25px auto;
+    padding: 15px 10px 10px;
+    border: 1px solid #ccc;
+    position: relative;
+}
+.popup-result .title {
+    position: absolute;
+    top: -28px;
+    left: 50%;
+    width: 140px;
+    font-size: 14px;
+    margin-left: -70px;
+    text-align: center;
+    line-height: 30px;
+    background: #fff;
+}
+.popup-result table {
+    text-align: center;
+    width: 100%;
+    margin: 0 auto;
+}
+.popup-result table td:not(.result) {
+    width: 3.5rem;
+    min-width: 3.5rem;
+    max-width: 3.5rem;
+}
+.popup-result table span {
+    display: block;
+    width: 100%;
+    font-family: arial;
+    line-height: 30px;
+    height: 30px;
+    white-space: nowrap;
+    overflow: hidden;
+    border: 1px solid #e8e8e8;
+}
+.popup-result-scroll {
+    font-size: 12px;
+    line-height: 24px;
+    height: 10em;
+    overflow-y: auto;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/Crontab/min.vue b/zhitan-vue/src/components/Crontab/min.vue
new file mode 100644
index 0000000..5d80cd2
--- /dev/null
+++ b/zhitan-vue/src/components/Crontab/min.vue
@@ -0,0 +1,126 @@
+<template>
+    <el-form>
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="1">
+                鍒嗛挓锛屽厑璁哥殑閫氶厤绗, - * /]
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="2">
+                鍛ㄦ湡浠�
+                <el-input-number v-model='cycle01' :min="0" :max="58" /> -
+                <el-input-number v-model='cycle02' :min="cycle01 + 1" :max="59" /> 鍒嗛挓
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="3">
+                浠�
+                <el-input-number v-model='average01' :min="0" :max="58" /> 鍒嗛挓寮�濮嬶紝 姣�
+                <el-input-number v-model='average02' :min="1" :max="59 - average01" /> 鍒嗛挓鎵ц涓�娆�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="4">
+                鎸囧畾
+                <el-select clearable v-model="checkboxList" placeholder="鍙閫�" multiple :multiple-limit="10">
+                    <el-option v-for="item in 60" :key="item" :label="item - 1" :value="item - 1" />
+                </el-select>
+            </el-radio>
+        </el-form-item>
+    </el-form>
+</template>
+<script setup>
+const emit = defineEmits(['update'])
+const props = defineProps({
+    cron: {
+        type: Object,
+        default: {
+            second: "*",
+            min: "*",
+            hour: "*",
+            day: "*",
+            month: "*",
+            week: "?",
+            year: "",
+        }
+    },
+    check: {
+        type: Function,
+        default: () => {
+        }
+    }
+})
+const radioValue = ref(1)
+const cycle01 = ref(0)
+const cycle02 = ref(1)
+const average01 = ref(0)
+const average02 = ref(1)
+const checkboxList = ref([])
+const checkCopy = ref([0])
+const cycleTotal = computed(() => {
+    cycle01.value = props.check(cycle01.value, 0, 58)
+    cycle02.value = props.check(cycle02.value, cycle01.value + 1, 59)
+    return cycle01.value + '-' + cycle02.value
+})
+const averageTotal = computed(() => {
+    average01.value = props.check(average01.value, 0, 58)
+    average02.value = props.check(average02.value, 1, 59 - average01.value)
+    return average01.value + '/' + average02.value
+})
+const checkboxString = computed(() => {
+    return checkboxList.value.join(',')
+})
+watch(() => props.cron.min, value => changeRadioValue(value))
+watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
+function changeRadioValue(value) {
+    if (value === '*') {
+        radioValue.value = 1
+    } else if (value.indexOf('-') > -1) {
+        const indexArr = value.split('-')
+        cycle01.value = Number(indexArr[0])
+        cycle02.value = Number(indexArr[1])
+        radioValue.value = 2
+    } else if (value.indexOf('/') > -1) {
+        const indexArr = value.split('/')
+        average01.value = Number(indexArr[0])
+        average02.value = Number(indexArr[1])
+        radioValue.value = 3
+    } else {
+        checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
+        radioValue.value = 4
+    }
+}
+function onRadioChange() {
+    switch (radioValue.value) {
+        case 1:
+            emit('update', 'min', '*', 'min')
+            break
+        case 2:
+            emit('update', 'min', cycleTotal.value, 'min')
+            break
+        case 3:
+            emit('update', 'min', averageTotal.value, 'min')
+            break
+        case 4:
+            if (checkboxList.value.length === 0) {
+                checkboxList.value.push(checkCopy.value[0])
+            } else {
+                checkCopy.value = checkboxList.value
+            }
+            emit('update', 'min', checkboxString.value, 'min')
+            break
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-input-number--small, .el-select, .el-select--small {
+    margin: 0 0.2rem;
+}
+.el-select, .el-select--small {
+    width: 19.8rem;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/Crontab/month.vue b/zhitan-vue/src/components/Crontab/month.vue
new file mode 100644
index 0000000..657d3f2
--- /dev/null
+++ b/zhitan-vue/src/components/Crontab/month.vue
@@ -0,0 +1,141 @@
+<template>
+    <el-form>
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="1">
+                鏈堬紝鍏佽鐨勯�氶厤绗, - * /]
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="2">
+                鍛ㄦ湡浠�
+                <el-input-number v-model='cycle01' :min="1" :max="11" /> -
+                <el-input-number v-model='cycle02' :min="cycle01 + 1" :max="12" /> 鏈�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="3">
+                浠�
+                <el-input-number v-model='average01' :min="1" :max="11" /> 鏈堝紑濮嬶紝姣�
+                <el-input-number v-model='average02' :min="1" :max="12 - average01" /> 鏈堟湀鎵ц涓�娆�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="4">
+                鎸囧畾
+                <el-select clearable v-model="checkboxList" placeholder="鍙閫�" multiple :multiple-limit="8">
+                    <el-option v-for="item in monthList" :key="item.key" :label="item.value" :value="item.key" />
+                </el-select>
+            </el-radio>
+        </el-form-item>
+    </el-form>
+</template>
+
+<script setup>
+const emit = defineEmits(['update'])
+const props = defineProps({
+    cron: {
+        type: Object,
+        default: {
+            second: "*",
+            min: "*",
+            hour: "*",
+            day: "*",
+            month: "*",
+            week: "?",
+            year: "",
+        }
+    },
+    check: {
+        type: Function,
+        default: () => {
+        }
+    }
+})
+const radioValue = ref(1)
+const cycle01 = ref(1)
+const cycle02 = ref(2)
+const average01 = ref(1)
+const average02 = ref(1)
+const checkboxList = ref([])
+const checkCopy = ref([1])
+const monthList = ref([
+    {key: 1, value: '涓�鏈�'},
+    {key: 2, value: '浜屾湀'},
+    {key: 3, value: '涓夋湀'},
+    {key: 4, value: '鍥涙湀'},
+    {key: 5, value: '浜旀湀'},
+    {key: 6, value: '鍏湀'},
+    {key: 7, value: '涓冩湀'},
+    {key: 8, value: '鍏湀'},
+    {key: 9, value: '涔濇湀'},
+    {key: 10, value: '鍗佹湀'},
+    {key: 11, value: '鍗佷竴鏈�'},
+    {key: 12, value: '鍗佷簩鏈�'}
+])
+const cycleTotal = computed(() => {
+    cycle01.value = props.check(cycle01.value, 1, 11)
+    cycle02.value = props.check(cycle02.value, cycle01.value + 1, 12)
+    return cycle01.value + '-' + cycle02.value
+})
+const averageTotal = computed(() => {
+    average01.value = props.check(average01.value, 1, 11)
+    average02.value = props.check(average02.value, 1, 12 - average01.value)
+    return average01.value + '/' + average02.value
+})
+const checkboxString = computed(() => {
+    return checkboxList.value.join(',')
+})
+watch(() => props.cron.month, value => changeRadioValue(value))
+watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
+function changeRadioValue(value) {
+    if (value === '*') {
+        radioValue.value = 1
+    } else if (value.indexOf('-') > -1) {
+        const indexArr = value.split('-')
+        cycle01.value = Number(indexArr[0])
+        cycle02.value = Number(indexArr[1])
+        radioValue.value = 2
+    } else if (value.indexOf('/') > -1) {
+        const indexArr = value.split('/')
+        average01.value = Number(indexArr[0])
+        average02.value = Number(indexArr[1])
+        radioValue.value = 3
+    } else {
+        checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
+        radioValue.value = 4
+    }
+}
+function onRadioChange() {
+    switch (radioValue.value) {
+        case 1:
+            emit('update', 'month', '*', 'month')
+            break
+        case 2:
+            emit('update', 'month', cycleTotal.value, 'month')
+            break
+        case 3:
+            emit('update', 'month', averageTotal.value, 'month')
+            break
+        case 4:
+            if (checkboxList.value.length === 0) {
+                checkboxList.value.push(checkCopy.value[0])
+            } else {
+                checkCopy.value = checkboxList.value
+            }
+            emit('update', 'month', checkboxString.value, 'month')
+            break
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-input-number--small, .el-select, .el-select--small {
+    margin: 0 0.2rem;
+}
+.el-select, .el-select--small {
+    width: 18.8rem;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/Crontab/result.vue b/zhitan-vue/src/components/Crontab/result.vue
new file mode 100644
index 0000000..5a812ee
--- /dev/null
+++ b/zhitan-vue/src/components/Crontab/result.vue
@@ -0,0 +1,540 @@
+<template>
+	<div class="popup-result">
+		<p class="title">鏈�杩�5娆¤繍琛屾椂闂�</p>
+		<ul class="popup-result-scroll">
+			<template v-if='isShow'>
+				<li v-for='item in resultList' :key="item">{{item}}</li>
+			</template>
+			<li v-else>璁$畻缁撴灉涓�...</li>
+		</ul>
+	</div>
+</template>
+
+<script setup>
+const props = defineProps({
+    ex: {
+        type: String,
+        default: ''
+    }
+})
+const dayRule = ref('')
+const dayRuleSup = ref('')
+const dateArr = ref([])
+const resultList = ref([])
+const isShow = ref(false)
+watch(() => props.ex, () => expressionChange())
+// 琛ㄨ揪寮忓�煎彉鍖栨椂锛屽紑濮嬪幓璁$畻缁撴灉
+function expressionChange() {
+    // 璁$畻寮�濮�-闅愯棌缁撴灉
+    isShow.value = false;
+    // 鑾峰彇瑙勫垯鏁扮粍[0绉掋��1鍒嗐��2鏃躲��3鏃ャ��4鏈堛��5鏄熸湡銆�6骞碷
+    let ruleArr = props.ex.split(' ');
+    // 鐢ㄤ簬璁板綍杩涘叆寰幆鐨勬鏁�
+    let nums = 0;
+    // 鐢ㄤ簬鏆傛椂瀛樼鍙锋椂闂磋鍒欑粨鏋滅殑鏁扮粍
+    let resultArr = [];
+    // 鑾峰彇褰撳墠鏃堕棿绮剧‘鑷砙骞淬�佹湀銆佹棩銆佹椂銆佸垎銆佺]
+    let nTime = new Date();
+    let nYear = nTime.getFullYear();
+    let nMonth = nTime.getMonth() + 1;
+    let nDay = nTime.getDate();
+    let nHour = nTime.getHours();
+    let nMin = nTime.getMinutes();
+    let nSecond = nTime.getSeconds();
+    // 鏍规嵁瑙勫垯鑾峰彇鍒拌繎100骞村彲鑳藉勾鏁扮粍銆佹湀鏁扮粍绛夌瓑
+    getSecondArr(ruleArr[0]);
+    getMinArr(ruleArr[1]);
+    getHourArr(ruleArr[2]);
+    getDayArr(ruleArr[3]);
+    getMonthArr(ruleArr[4]);
+    getWeekArr(ruleArr[5]);
+    getYearArr(ruleArr[6], nYear);
+    // 灏嗚幏鍙栧埌鐨勬暟缁勮祴鍊�-鏂逛究浣跨敤
+    let sDate = dateArr.value[0];
+    let mDate = dateArr.value[1];
+    let hDate = dateArr.value[2];
+    let DDate = dateArr.value[3];
+    let MDate = dateArr.value[4];
+    let YDate = dateArr.value[5];
+    // 鑾峰彇褰撳墠鏃堕棿鍦ㄦ暟缁勪腑鐨勭储寮�
+    let sIdx = getIndex(sDate, nSecond);
+    let mIdx = getIndex(mDate, nMin);
+    let hIdx = getIndex(hDate, nHour);
+    let DIdx = getIndex(DDate, nDay);
+    let MIdx = getIndex(MDate, nMonth);
+    let YIdx = getIndex(YDate, nYear);
+    // 閲嶇疆鏈堟棩鏃跺垎绉掔殑鍑芥暟(鍚庨潰鐢ㄧ殑姣旇緝澶�)
+    const resetSecond = function () {
+        sIdx = 0;
+        nSecond = sDate[sIdx]
+    }
+    const resetMin = function () {
+        mIdx = 0;
+        nMin = mDate[mIdx]
+        resetSecond();
+    }
+    const resetHour = function () {
+        hIdx = 0;
+        nHour = hDate[hIdx]
+        resetMin();
+    }
+    const resetDay = function () {
+        DIdx = 0;
+        nDay = DDate[DIdx]
+        resetHour();
+    }
+    const resetMonth = function () {
+        MIdx = 0;
+        nMonth = MDate[MIdx]
+        resetDay();
+    }
+    // 濡傛灉褰撳墠骞翠唤涓嶄负鏁扮粍涓綋鍓嶅��
+    if (nYear !== YDate[YIdx]) {
+        resetMonth();
+    }
+    // 濡傛灉褰撳墠鏈堜唤涓嶄负鏁扮粍涓綋鍓嶅��
+    if (nMonth !== MDate[MIdx]) {
+        resetDay();
+    }
+    // 濡傛灉褰撳墠鈥滄棩鈥濅笉涓烘暟缁勪腑褰撳墠鍊�
+    if (nDay !== DDate[DIdx]) {
+        resetHour();
+    }
+    // 濡傛灉褰撳墠鈥滄椂鈥濅笉涓烘暟缁勪腑褰撳墠鍊�
+    if (nHour !== hDate[hIdx]) {
+        resetMin();
+    }
+    // 濡傛灉褰撳墠鈥滃垎鈥濅笉涓烘暟缁勪腑褰撳墠鍊�
+    if (nMin !== mDate[mIdx]) {
+        resetSecond();
+    }
+    // 寰幆骞翠唤鏁扮粍
+    goYear: for (let Yi = YIdx; Yi < YDate.length; Yi++) {
+        let YY = YDate[Yi];
+        // 濡傛灉鍒拌揪鏈�澶у�兼椂
+        if (nMonth > MDate[MDate.length - 1]) {
+            resetMonth();
+            continue;
+        }
+        // 寰幆鏈堜唤鏁扮粍
+        goMonth: for (let Mi = MIdx; Mi < MDate.length; Mi++) {
+            // 璧嬪�笺�佹柟渚垮悗闈㈣繍绠�
+            let MM = MDate[Mi];
+            MM = MM < 10 ? '0' + MM : MM;
+            // 濡傛灉鍒拌揪鏈�澶у�兼椂
+            if (nDay > DDate[DDate.length - 1]) {
+                resetDay();
+                if (Mi === MDate.length - 1) {
+                    resetMonth();
+                    continue goYear;
+                }
+                continue;
+            }
+            // 寰幆鏃ユ湡鏁扮粍
+            goDay: for (let Di = DIdx; Di < DDate.length; Di++) {
+                // 璧嬪�笺�佹柟渚垮悗闈㈣繍绠�
+                let DD = DDate[Di];
+                let thisDD = DD < 10 ? '0' + DD : DD;
+                // 濡傛灉鍒拌揪鏈�澶у�兼椂
+                if (nHour > hDate[hDate.length - 1]) {
+                    resetHour();
+                    if (Di === DDate.length - 1) {
+                        resetDay();
+                        if (Mi === MDate.length - 1) {
+                            resetMonth();
+                            continue goYear;
+                        }
+                        continue goMonth;
+                    }
+                    continue;
+                }
+                // 鍒ゆ柇鏃ユ湡鐨勫悎娉曟�э紝涓嶅悎娉曠殑璇濅篃鏄烦鍑哄綋鍓嶅惊鐜�
+                if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true && dayRule.value !== 'workDay' && dayRule.value !== 'lastWeek' && dayRule.value !== 'lastDay') {
+                    resetDay();
+                    continue goMonth;
+                }
+                // 濡傛灉鏃ユ湡瑙勫垯涓湁鍊兼椂
+                if (dayRule.value === 'lastDay') {
+                    // 濡傛灉涓嶆槸鍚堟硶鏃ユ湡鍒欓渶瑕佸皢鍓嶅皢鏃ユ湡璋冨埌鍚堟硶鏃ユ湡鍗虫湀鏈渶鍚庝竴澶�
+                    if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
+                        while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
+                            DD--;
+                            thisDD = DD < 10 ? '0' + DD : DD;
+                        }
+                    }
+                } else if (dayRule.value === 'workDay') {
+                    // 鏍¢獙骞惰皟鏁村鏋滄槸2鏈�30鍙疯繖绉嶆棩鏈熶紶杩涙潵鏃堕渶璋冩暣鑷虫甯告湀搴�
+                    if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
+                        while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
+                            DD--;
+                            thisDD = DD < 10 ? '0' + DD : DD;
+                        }
+                    }
+                    // 鑾峰彇杈惧埌鏉′欢鐨勬棩鏈熸槸鏄熸湡X
+                    let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
+                    // 褰撴槦鏈熸棩鏃�
+                    if (thisWeek === 1) {
+                        // 鍏堟壘涓嬩竴涓棩锛屽苟鍒ゆ柇鏄惁涓烘湀搴�
+                        DD++;
+                        thisDD = DD < 10 ? '0' + DD : DD;
+                        // 鍒ゆ柇涓嬩竴鏃ュ凡缁忎笉鏄悎娉曟棩鏈�
+                        if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
+                            DD -= 3;
+                        }
+                    } else if (thisWeek === 7) {
+                        // 褰撴槦鏈�6鏃跺彧闇�鍒ゆ柇涓嶆槸1鍙峰氨鍙繘琛屾搷浣�
+                        if (dayRuleSup.value !== 1) {
+                            DD--;
+                        } else {
+                            DD += 2;
+                        }
+                    }
+                } else if (dayRule.value === 'weekDay') {
+                    // 濡傛灉鎸囧畾浜嗘槸鏄熸湡鍑�
+                    // 鑾峰彇褰撳墠鏃ユ湡鏄睘浜庢槦鏈熷嚑
+                    let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
+                    // 鏍¢獙褰撳墠鏄熸湡鏄惁鍦ㄦ槦鏈熸睜锛坉ayRuleSup锛変腑
+                    if (dayRuleSup.value.indexOf(thisWeek) < 0) {
+                        // 濡傛灉鍒拌揪鏈�澶у�兼椂
+                        if (Di === DDate.length - 1) {
+                            resetDay();
+                            if (Mi === MDate.length - 1) {
+                                resetMonth();
+                                continue goYear;
+                            }
+                            continue goMonth;
+                        }
+                        continue;
+                    }
+                } else if (dayRule.value === 'assWeek') {
+                    // 濡傛灉鎸囧畾浜嗘槸绗嚑鍛ㄧ殑鏄熸湡鍑�
+                    // 鑾峰彇姣忔湀1鍙锋槸灞炰簬鏄熸湡鍑�
+                    let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
+                    if (dayRuleSup.value[1] >= thisWeek) {
+                        DD = (dayRuleSup.value[0] - 1) * 7 + dayRuleSup.value[1] - thisWeek + 1;
+                    } else {
+                        DD = dayRuleSup.value[0] * 7 + dayRuleSup.value[1] - thisWeek + 1;
+                    }
+                } else if (dayRule.value === 'lastWeek') {
+                    // 濡傛灉鎸囧畾浜嗘瘡鏈堟渶鍚庝竴涓槦鏈熷嚑
+                    // 鏍¢獙骞惰皟鏁村鏋滄槸2鏈�30鍙疯繖绉嶆棩鏈熶紶杩涙潵鏃堕渶璋冩暣鑷虫甯告湀搴�
+                    if (checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
+                        while (DD > 0 && checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
+                            DD--;
+                            thisDD = DD < 10 ? '0' + DD : DD;
+                        }
+                    }
+                    // 鑾峰彇鏈堟湯鏈�鍚庝竴澶╂槸鏄熸湡鍑�
+                    let thisWeek = formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
+                    // 鎵惧埌瑕佹眰涓渶杩戠殑閭d釜鏄熸湡鍑�
+                    if (dayRuleSup.value < thisWeek) {
+                        DD -= thisWeek - dayRuleSup.value;
+                    } else if (dayRuleSup.value > thisWeek) {
+                        DD -= 7 - (dayRuleSup.value - thisWeek)
+                    }
+                }
+                // 鍒ゆ柇鏃堕棿鍊兼槸鍚﹀皬浜�10缃崲鎴愨��05鈥濊繖绉嶆牸寮�
+                DD = DD < 10 ? '0' + DD : DD;
+                // 寰幆鈥滄椂鈥濇暟缁�
+                goHour: for (let hi = hIdx; hi < hDate.length; hi++) {
+                    let hh = hDate[hi] < 10 ? '0' + hDate[hi] : hDate[hi]
+                    // 濡傛灉鍒拌揪鏈�澶у�兼椂
+                    if (nMin > mDate[mDate.length - 1]) {
+                        resetMin();
+                        if (hi === hDate.length - 1) {
+                            resetHour();
+                            if (Di === DDate.length - 1) {
+                                resetDay();
+                                if (Mi === MDate.length - 1) {
+                                    resetMonth();
+                                    continue goYear;
+                                }
+                                continue goMonth;
+                            }
+                            continue goDay;
+                        }
+                        continue;
+                    }
+                    // 寰幆"鍒�"鏁扮粍
+                    goMin: for (let mi = mIdx; mi < mDate.length; mi++) {
+                        let mm = mDate[mi] < 10 ? '0' + mDate[mi] : mDate[mi];
+                        // 濡傛灉鍒拌揪鏈�澶у�兼椂
+                        if (nSecond > sDate[sDate.length - 1]) {
+                            resetSecond();
+                            if (mi === mDate.length - 1) {
+                                resetMin();
+                                if (hi === hDate.length - 1) {
+                                    resetHour();
+                                    if (Di === DDate.length - 1) {
+                                        resetDay();
+                                        if (Mi === MDate.length - 1) {
+                                            resetMonth();
+                                            continue goYear;
+                                        }
+                                        continue goMonth;
+                                    }
+                                    continue goDay;
+                                }
+                                continue goHour;
+                            }
+                            continue;
+                        }
+                        // 寰幆"绉�"鏁扮粍
+                        goSecond: for (let si = sIdx; si <= sDate.length - 1; si++) {
+                            let ss = sDate[si] < 10 ? '0' + sDate[si] : sDate[si];
+                            // 娣诲姞褰撳墠鏃堕棿锛堟椂闂村悎娉曟�у湪鏃ユ湡寰幆鏃跺凡缁忓垽鏂級
+                            if (MM !== '00' && DD !== '00') {
+                                resultArr.push(YY + '-' + MM + '-' + DD + ' ' + hh + ':' + mm + ':' + ss)
+                                nums++;
+                            }
+                            // 濡傛灉鏉℃暟婊′簡灏遍��鍑哄惊鐜�
+                            if (nums === 5) break goYear;
+                            // 濡傛灉鍒拌揪鏈�澶у�兼椂
+                            if (si === sDate.length - 1) {
+                                resetSecond();
+                                if (mi === mDate.length - 1) {
+                                    resetMin();
+                                    if (hi === hDate.length - 1) {
+                                        resetHour();
+                                        if (Di === DDate.length - 1) {
+                                            resetDay();
+                                            if (Mi === MDate.length - 1) {
+                                                resetMonth();
+                                                continue goYear;
+                                            }
+                                            continue goMonth;
+                                        }
+                                        continue goDay;
+                                    }
+                                    continue goHour;
+                                }
+                                continue goMin;
+                            }
+                        } //goSecond
+                    } //goMin
+                }//goHour
+            }//goDay
+        }//goMonth
+    }
+    // 鍒ゆ柇100骞村唴鐨勭粨鏋滄潯鏁�
+    if (resultArr.length === 0) {
+        resultList.value = ['娌℃湁杈惧埌鏉′欢鐨勭粨鏋滐紒'];
+    } else {
+        resultList.value = resultArr;
+        if (resultArr.length !== 5) {
+            resultList.value.push('鏈�杩�100骞村唴鍙湁涓婇潰' + resultArr.length + '鏉$粨鏋滐紒')
+        }
+    }
+    // 璁$畻瀹屾垚-鏄剧ず缁撴灉
+    isShow.value = true;
+}
+// 鐢ㄤ簬璁$畻鏌愪綅鏁板瓧鍦ㄦ暟缁勪腑鐨勭储寮�
+function getIndex(arr, value) {
+    if (value <= arr[0] || value > arr[arr.length - 1]) {
+        return 0;
+    } else {
+        for (let i = 0; i < arr.length - 1; i++) {
+            if (value > arr[i] && value <= arr[i + 1]) {
+                return i + 1;
+            }
+        }
+    }
+}
+// 鑾峰彇"骞�"鏁扮粍
+function getYearArr(rule, year) {
+    dateArr.value[5] = getOrderArr(year, year + 100);
+    if (rule !== undefined) {
+        if (rule.indexOf('-') >= 0) {
+            dateArr.value[5] = getCycleArr(rule, year + 100, false)
+        } else if (rule.indexOf('/') >= 0) {
+            dateArr.value[5] = getAverageArr(rule, year + 100)
+        } else if (rule !== '*') {
+            dateArr.value[5] = getAssignArr(rule)
+        }
+    }
+}
+// 鑾峰彇"鏈�"鏁扮粍
+function getMonthArr(rule) {
+    dateArr.value[4] = getOrderArr(1, 12);
+    if (rule.indexOf('-') >= 0) {
+        dateArr.value[4] = getCycleArr(rule, 12, false)
+    } else if (rule.indexOf('/') >= 0) {
+        dateArr.value[4] = getAverageArr(rule, 12)
+    } else if (rule !== '*') {
+        dateArr.value[4] = getAssignArr(rule)
+    }
+}
+// 鑾峰彇"鏃�"鏁扮粍-涓昏涓烘棩鏈熻鍒�
+function getWeekArr(rule) {
+    // 鍙湁褰撴棩鏈熻鍒欑殑涓や釜鍊煎潎涓衡�溾�濇椂鍒欒〃杈炬棩鏈熸槸鏈夐�夐」鐨�
+    if (dayRule.value === '' && dayRuleSup.value === '') {
+        if (rule.indexOf('-') >= 0) {
+            dayRule.value = 'weekDay';
+            dayRuleSup.value = getCycleArr(rule, 7, false)
+        } else if (rule.indexOf('#') >= 0) {
+            dayRule.value = 'assWeek';
+            let matchRule = rule.match(/[0-9]{1}/g);
+            dayRuleSup.value = [Number(matchRule[1]), Number(matchRule[0])];
+            dateArr.value[3] = [1];
+            if (dayRuleSup.value[1] === 7) {
+                dayRuleSup.value[1] = 0;
+            }
+        } else if (rule.indexOf('L') >= 0) {
+            dayRule.value = 'lastWeek';
+            dayRuleSup.value = Number(rule.match(/[0-9]{1,2}/g)[0]);
+            dateArr.value[3] = [31];
+            if (dayRuleSup.value === 7) {
+                dayRuleSup.value = 0;
+            }
+        } else if (rule !== '*' && rule !== '?') {
+            dayRule.value = 'weekDay';
+            dayRuleSup.value = getAssignArr(rule)
+        }
+    }
+}
+// 鑾峰彇"鏃�"鏁扮粍-灏戦噺涓烘棩鏈熻鍒�
+function getDayArr(rule) {
+    dateArr.value[3] = getOrderArr(1, 31);
+    dayRule.value = '';
+    dayRuleSup.value = '';
+    if (rule.indexOf('-') >= 0) {
+        dateArr.value[3] = getCycleArr(rule, 31, false)
+        dayRuleSup.value = 'null';
+    } else if (rule.indexOf('/') >= 0) {
+        dateArr.value[3] = getAverageArr(rule, 31)
+        dayRuleSup.value = 'null';
+    } else if (rule.indexOf('W') >= 0) {
+        dayRule.value = 'workDay';
+        dayRuleSup.value = Number(rule.match(/[0-9]{1,2}/g)[0]);
+        dateArr.value[3] = [dayRuleSup.value];
+    } else if (rule.indexOf('L') >= 0) {
+        dayRule.value = 'lastDay';
+        dayRuleSup.value = 'null';
+        dateArr.value[3] = [31];
+    } else if (rule !== '*' && rule !== '?') {
+        dateArr.value[3] = getAssignArr(rule)
+        dayRuleSup.value = 'null';
+    } else if (rule === '*') {
+        dayRuleSup.value = 'null';
+    }
+}
+// 鑾峰彇"鏃�"鏁扮粍
+function getHourArr(rule) {
+    dateArr.value[2] = getOrderArr(0, 23);
+    if (rule.indexOf('-') >= 0) {
+        dateArr.value[2] = getCycleArr(rule, 24, true)
+    } else if (rule.indexOf('/') >= 0) {
+        dateArr.value[2] = getAverageArr(rule, 23)
+    } else if (rule !== '*') {
+        dateArr.value[2] = getAssignArr(rule)
+    }
+}
+// 鑾峰彇"鍒�"鏁扮粍
+function getMinArr(rule) {
+    dateArr.value[1] = getOrderArr(0, 59);
+    if (rule.indexOf('-') >= 0) {
+        dateArr.value[1] = getCycleArr(rule, 60, true)
+    } else if (rule.indexOf('/') >= 0) {
+        dateArr.value[1] = getAverageArr(rule, 59)
+    } else if (rule !== '*') {
+        dateArr.value[1] = getAssignArr(rule)
+    }
+}
+// 鑾峰彇"绉�"鏁扮粍
+function getSecondArr(rule) {
+    dateArr.value[0] = getOrderArr(0, 59);
+    if (rule.indexOf('-') >= 0) {
+        dateArr.value[0] = getCycleArr(rule, 60, true)
+    } else if (rule.indexOf('/') >= 0) {
+        dateArr.value[0] = getAverageArr(rule, 59)
+    } else if (rule !== '*') {
+        dateArr.value[0] = getAssignArr(rule)
+    }
+}
+// 鏍规嵁浼犺繘鏉ョ殑min-max杩斿洖涓�涓『搴忕殑鏁扮粍
+function getOrderArr(min, max) {
+    let arr = [];
+    for (let i = min; i <= max; i++) {
+        arr.push(i);
+    }
+    return arr;
+}
+// 鏍规嵁瑙勫垯涓寚瀹氱殑闆舵暎鍊艰繑鍥炰竴涓暟缁�
+function getAssignArr(rule) {
+    let arr = [];
+    let assiginArr = rule.split(',');
+    for (let i = 0; i < assiginArr.length; i++) {
+        arr[i] = Number(assiginArr[i])
+    }
+    arr.sort(compare)
+    return arr;
+}
+// 鏍规嵁涓�瀹氱畻鏈鍒欒绠楄繑鍥炰竴涓暟缁�
+function getAverageArr(rule, limit) {
+    let arr = [];
+    let agArr = rule.split('/');
+    let min = Number(agArr[0]);
+    let step = Number(agArr[1]);
+    while (min <= limit) {
+        arr.push(min);
+        min += step;
+    }
+    return arr;
+}
+// 鏍规嵁瑙勫垯杩斿洖涓�涓叿鏈夊懆鏈熸�х殑鏁扮粍
+function getCycleArr(rule, limit, status) {
+    // status--琛ㄧず鏄惁浠�0寮�濮嬶紙鍒欎粠1寮�濮嬶級
+    let arr = [];
+    let cycleArr = rule.split('-');
+    let min = Number(cycleArr[0]);
+    let max = Number(cycleArr[1]);
+    if (min > max) {
+        max += limit;
+    }
+    for (let i = min; i <= max; i++) {
+        let add = 0;
+        if (status === false && i % limit === 0) {
+            add = limit;
+        }
+        arr.push(Math.round(i % limit + add))
+    }
+    arr.sort(compare)
+    return arr;
+}
+// 姣旇緝鏁板瓧澶у皬锛堢敤浜嶢rray.sort锛�
+function compare(value1, value2) {
+    if (value2 - value1 > 0) {
+        return -1;
+    } else {
+        return 1;
+    }
+}
+// 鏍煎紡鍖栨棩鏈熸牸寮忓锛�2017-9-19 18:04:33
+function formatDate(value, type) {
+    // 璁$畻鏃ユ湡鐩稿叧鍊�
+    let time = typeof value == 'number' ? new Date(value) : value;
+    let Y = time.getFullYear();
+    let M = time.getMonth() + 1;
+    let D = time.getDate();
+    let h = time.getHours();
+    let m = time.getMinutes();
+    let s = time.getSeconds();
+    let week = time.getDay();
+    // 濡傛灉浼犻�掍簡type鐨勮瘽
+    if (type === undefined) {
+        return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
+    } else if (type === 'week') {
+        // 鍦╭uartz涓� 1涓烘槦鏈熸棩
+        return week + 1;
+    }
+}
+// 妫�鏌ユ棩鏈熸槸鍚﹀瓨鍦�
+function checkDate(value) {
+    let time = new Date(value);
+    let format = formatDate(time)
+    return value === format;
+}
+onMounted(() => {
+    expressionChange()
+})
+</script>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/Crontab/second.vue b/zhitan-vue/src/components/Crontab/second.vue
new file mode 100644
index 0000000..a7e5798
--- /dev/null
+++ b/zhitan-vue/src/components/Crontab/second.vue
@@ -0,0 +1,128 @@
+<template>
+    <el-form>
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="1">
+                绉掞紝鍏佽鐨勯�氶厤绗, - * /]
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="2">
+                鍛ㄦ湡浠�
+                <el-input-number v-model='cycle01' :min="0" :max="58" /> -
+                <el-input-number v-model='cycle02' :min="cycle01 + 1" :max="59" /> 绉�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="3">
+                浠�
+                <el-input-number v-model='average01' :min="0" :max="58" /> 绉掑紑濮嬶紝姣�
+                <el-input-number v-model='average02' :min="1" :max="59 - average01" /> 绉掓墽琛屼竴娆�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="4">
+                鎸囧畾
+                <el-select clearable v-model="checkboxList" placeholder="鍙閫�" multiple :multiple-limit="10">
+                    <el-option v-for="item in 60" :key="item" :label="item - 1" :value="item - 1" />
+                </el-select>
+            </el-radio>
+        </el-form-item>
+    </el-form>
+</template>
+
+<script setup>
+const emit = defineEmits(['update'])
+const props = defineProps({
+    cron: {
+        type: Object,
+        default: {
+            second: "*",
+            min: "*",
+            hour: "*",
+            day: "*",
+            month: "*",
+            week: "?",
+            year: "",
+        }
+    },
+    check: {
+        type: Function,
+        default: () => {
+        }
+    }
+})
+const radioValue = ref(1)
+const cycle01 = ref(0)
+const cycle02 = ref(1)
+const average01 = ref(0)
+const average02 = ref(1)
+const checkboxList = ref([])
+const checkCopy = ref([0])
+const cycleTotal = computed(() => {
+    cycle01.value = props.check(cycle01.value, 0, 58)
+    cycle02.value = props.check(cycle02.value, cycle01.value + 1, 59)
+    return cycle01.value + '-' + cycle02.value
+})
+const averageTotal = computed(() => {
+    average01.value = props.check(average01.value, 0, 58)
+    average02.value = props.check(average02.value, 1, 59 - average01.value)
+    return average01.value + '/' + average02.value
+})
+const checkboxString = computed(() => {
+    return checkboxList.value.join(',')
+})
+watch(() => props.cron.second, value => changeRadioValue(value))
+watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
+function changeRadioValue(value) {
+    if (value === '*') {
+        radioValue.value = 1
+    } else if (value.indexOf('-') > -1) {
+        const indexArr = value.split('-')
+        cycle01.value = Number(indexArr[0])
+        cycle02.value = Number(indexArr[1])
+        radioValue.value = 2
+    } else if (value.indexOf('/') > -1) {
+        const indexArr = value.split('/')
+        average01.value = Number(indexArr[0])
+        average02.value = Number(indexArr[1])
+        radioValue.value = 3
+    } else {
+        checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
+        radioValue.value = 4
+    }
+}
+// 鍗曢�夋寜閽�煎彉鍖栨椂
+function onRadioChange() {
+    switch (radioValue.value) {
+        case 1:
+            emit('update', 'second', '*', 'second')
+            break
+        case 2:
+            emit('update', 'second', cycleTotal.value, 'second')
+            break
+        case 3:
+            emit('update', 'second', averageTotal.value, 'second')
+            break
+        case 4:
+            if (checkboxList.value.length === 0) {
+                checkboxList.value.push(checkCopy.value[0])
+            } else {
+                checkCopy.value = checkboxList.value
+            }
+            emit('update', 'second', checkboxString.value, 'second')
+            break
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-input-number--small, .el-select, .el-select--small {
+    margin: 0 0.2rem;
+}
+.el-select, .el-select--small {
+    width: 18.8rem;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/Crontab/week.vue b/zhitan-vue/src/components/Crontab/week.vue
new file mode 100644
index 0000000..105a3be
--- /dev/null
+++ b/zhitan-vue/src/components/Crontab/week.vue
@@ -0,0 +1,197 @@
+<template>
+    <el-form>
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="1">
+                鍛紝鍏佽鐨勯�氶厤绗, - * ? / L #]
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="2">
+                涓嶆寚瀹�
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="3">
+                鍛ㄦ湡浠�
+                <el-select clearable v-model="cycle01">
+                    <el-option
+                        v-for="(item,index) of weekList"
+                        :key="index"
+                        :label="item.value"
+                        :value="item.key"
+                        :disabled="item.key === 7"
+                    >{{item.value}}</el-option>
+                </el-select>
+                -
+                <el-select clearable v-model="cycle02">
+                    <el-option
+                        v-for="(item,index) of weekList"
+                        :key="index"
+                        :label="item.value"
+                        :value="item.key"
+                        :disabled="item.key <= cycle01"
+                    >{{item.value}}</el-option>
+                </el-select>
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="4">
+                绗�
+                <el-input-number v-model='average01' :min="1" :max="4" /> 鍛ㄧ殑
+                <el-select clearable v-model="average02">
+                    <el-option v-for="item in weekList" :key="item.key" :label="item.value" :value="item.key" />
+                </el-select>
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="5">
+                鏈湀鏈�鍚庝竴涓�
+                <el-select clearable v-model="weekday">
+                    <el-option v-for="item in weekList" :key="item.key" :label="item.value" :value="item.key" />
+                </el-select>
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio v-model='radioValue' :label="6">
+                鎸囧畾
+                <el-select class="multiselect" clearable v-model="checkboxList" placeholder="鍙閫�" multiple :multiple-limit="6">
+                    <el-option v-for="item in weekList" :key="item.key" :label="item.value" :value="item.key" />
+                </el-select>
+            </el-radio>
+        </el-form-item>
+
+    </el-form>
+</template>
+
+<script setup>
+const emit = defineEmits(['update'])
+const props = defineProps({
+    cron: {
+        type: Object,
+        default: {
+            second: "*",
+            min: "*",
+            hour: "*",
+            day: "*",
+            month: "*",
+            week: "?",
+            year: ""
+        }
+    },
+    check: {
+        type: Function,
+        default: () => {
+        }
+    }
+})
+const radioValue = ref(2)
+const cycle01 = ref(2)
+const cycle02 = ref(3)
+const average01 = ref(1)
+const average02 = ref(2)
+const weekday = ref(2)
+const checkboxList = ref([])
+const checkCopy = ref([2])
+const weekList = ref([
+    {key: 1, value: '鏄熸湡鏃�'},
+    {key: 2, value: '鏄熸湡涓�'},
+    {key: 3, value: '鏄熸湡浜�'},
+    {key: 4, value: '鏄熸湡涓�'},
+    {key: 5, value: '鏄熸湡鍥�'},
+    {key: 6, value: '鏄熸湡浜�'},
+    {key: 7, value: '鏄熸湡鍏�'}
+])
+const cycleTotal = computed(() => {
+    cycle01.value = props.check(cycle01.value, 1, 6)
+    cycle02.value = props.check(cycle02.value, cycle01.value + 1, 7)
+    return cycle01.value + '-' + cycle02.value
+})
+const averageTotal = computed(() => {
+    average01.value = props.check(average01.value, 1, 4)
+    average02.value = props.check(average02.value, 1, 7)
+    return average02.value + '#' + average01.value
+})
+const weekdayTotal = computed(() => {
+    weekday.value = props.check(weekday.value, 1, 7)
+    return weekday.value + 'L'
+})
+const checkboxString = computed(() => {
+    return checkboxList.value.join(',')
+})
+watch(() => props.cron.week, value => changeRadioValue(value))
+watch([radioValue, cycleTotal, averageTotal, weekdayTotal, checkboxString], () => onRadioChange())
+function changeRadioValue(value) {
+    if (value === "*") {
+        radioValue.value = 1
+    } else if (value === "?") {
+        radioValue.value = 2
+    } else if (value.indexOf("-") > -1) {
+        const indexArr = value.split('-')
+        cycle01.value = Number(indexArr[0])
+        cycle02.value = Number(indexArr[1])
+        radioValue.value = 3
+    } else if (value.indexOf("#") > -1) {
+        const indexArr = value.split('#')
+        average01.value = Number(indexArr[1])
+        average02.value = Number(indexArr[0])
+        radioValue.value = 4
+    } else if (value.indexOf("L") > -1) {
+        const indexArr = value.split("L")
+        weekday.value = Number(indexArr[0])
+        radioValue.value = 5
+    } else {
+        checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
+        radioValue.value = 6
+    }
+}
+function onRadioChange() {
+    if (radioValue.value === 2 && props.cron.day === '?') {
+        emit('update', 'day', '*', 'week')
+    }
+    if (radioValue.value !== 2 && props.cron.day !== '?') {
+        emit('update', 'day', '?', 'week')
+    }
+    switch (radioValue.value) {
+        case 1:
+            emit('update', 'week', '*', 'week')
+            break
+        case 2:
+            emit('update', 'week', '?', 'week')
+            break
+        case 3:
+            emit('update', 'week', cycleTotal.value, 'week')
+            break
+        case 4:
+            emit('update', 'week', averageTotal.value, 'week')
+            break
+        case 5:
+            emit('update', 'week', weekdayTotal.value, 'week')
+            break
+        case 6:
+            if (checkboxList.value.length === 0) {
+                checkboxList.value.push(checkCopy.value[0])
+            } else {
+                checkCopy.value = checkboxList.value
+            }
+            emit('update', 'week', checkboxString.value, 'week')
+            break
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-input-number--small, .el-select, .el-select--small {
+    margin: 0 0.5rem;
+}
+.el-select, .el-select--small {
+    width: 8rem;
+}
+.el-select.multiselect, .el-select--small.multiselect {
+    width: 17.8rem;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/Crontab/year.vue b/zhitan-vue/src/components/Crontab/year.vue
new file mode 100644
index 0000000..b26bdae
--- /dev/null
+++ b/zhitan-vue/src/components/Crontab/year.vue
@@ -0,0 +1,149 @@
+<template>
+    <el-form>
+        <el-form-item>
+            <el-radio :label="1" v-model='radioValue'>
+                涓嶅~锛屽厑璁哥殑閫氶厤绗, - * /]
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio :label="2" v-model='radioValue'>
+                姣忓勾
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio :label="3" v-model='radioValue'>
+                鍛ㄦ湡浠�
+                <el-input-number v-model='cycle01' :min='fullYear' :max="2098"/> -
+                <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : fullYear + 1" :max="2099"/>
+            </el-radio>
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio :label="4" v-model='radioValue'>
+                浠�
+                <el-input-number v-model='average01' :min='fullYear' :max="2098"/> 骞村紑濮嬶紝姣�
+                <el-input-number v-model='average02' :min="1" :max="2099 - average01 || fullYear"/> 骞存墽琛屼竴娆�
+            </el-radio>
+
+        </el-form-item>
+
+        <el-form-item>
+            <el-radio :label="5" v-model='radioValue'>
+                鎸囧畾
+                <el-select clearable v-model="checkboxList" placeholder="鍙閫�" multiple :multiple-limit="8">
+                    <el-option v-for="item in 9" :key="item" :value="item - 1 + fullYear" :label="item -1 + fullYear" />
+                </el-select>
+            </el-radio>
+        </el-form-item>
+    </el-form>
+</template>
+
+<script setup>
+const emit = defineEmits(['update'])
+const props = defineProps({
+    cron: {
+        type: Object,
+        default: {
+            second: "*",
+            min: "*",
+            hour: "*",
+            day: "*",
+            month: "*",
+            week: "?",
+            year: ""
+        }
+    },
+    check: {
+        type: Function,
+        default: () => {
+        }
+    }
+})
+const fullYear = ref(0)
+const maxFullYear = ref(0)
+const radioValue = ref(1)
+const cycle01 = ref(0)
+const cycle02 = ref(0)
+const average01 = ref(0)
+const average02 = ref(1)
+const checkboxList = ref([])
+const checkCopy = ref([])
+const cycleTotal = computed(() => {
+    cycle01.value = props.check(cycle01.value, fullYear.value, maxFullYear.value - 1)
+    cycle02.value = props.check(cycle02.value, cycle01.value + 1, maxFullYear.value)
+    return cycle01.value + '-' + cycle02.value
+})
+const averageTotal = computed(() => {
+    average01.value = props.check(average01.value, fullYear.value, maxFullYear.value - 1)
+    average02.value = props.check(average02.value, 1, 10)
+    return average01.value + '/' + average02.value
+})
+const checkboxString = computed(() => {
+    return checkboxList.value.join(',')
+})
+watch(() => props.cron.year, value => changeRadioValue(value))
+watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
+function changeRadioValue(value) {
+    if (value === '') {
+        radioValue.value = 1
+    } else if (value === "*") {
+        radioValue.value = 2
+    } else if (value.indexOf("-") > -1) {
+        const indexArr = value.split('-')
+        cycle01.value = Number(indexArr[0])
+        cycle02.value = Number(indexArr[1])
+        radioValue.value = 3
+    } else if (value.indexOf("/") > -1) {
+        const indexArr = value.split('/')
+        average01.value = Number(indexArr[1])
+        average02.value = Number(indexArr[0])
+        radioValue.value = 4
+    } else {
+        checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
+        radioValue.value = 5
+    }
+}
+function onRadioChange() {
+    switch (radioValue.value) {
+        case 1:
+            emit('update', 'year', '', 'year')
+            break
+        case 2:
+            emit('update', 'year', '*', 'year')
+            break
+        case 3:
+            emit('update', 'year', cycleTotal.value, 'year')
+            break
+        case 4:
+            emit('update', 'year', averageTotal.value, 'year')
+            break
+        case 5:
+            if (checkboxList.value.length === 0) {
+                checkboxList.value.push(checkCopy.value[0])
+            } else {
+                checkCopy.value = checkboxList.value
+            }
+            emit('update', 'year', checkboxString.value, 'year')
+            break
+    }
+}
+onMounted(() => {
+    fullYear.value = Number(new Date().getFullYear())
+    maxFullYear.value = fullYear.value + 10
+    cycle01.value = fullYear.value
+    cycle02.value = cycle01.value + 1
+    average01.value = fullYear.value
+    checkCopy.value = [fullYear.value]
+})
+</script>
+
+<style lang="scss" scoped>
+.el-input-number--small, .el-select, .el-select--small {
+    margin: 0 0.2rem;
+}
+.el-select, .el-select--small {
+    width: 18.8rem;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/DictTag/index.vue b/zhitan-vue/src/components/DictTag/index.vue
new file mode 100644
index 0000000..7d0888c
--- /dev/null
+++ b/zhitan-vue/src/components/DictTag/index.vue
@@ -0,0 +1,82 @@
+<template>
+  <div>
+    <template v-for="(item, index) in options">
+      <template v-if="values.includes(item.value)">
+        <span
+          v-if="(item.elTagType == 'default' || item.elTagType == '') && (item.elTagClass == '' || item.elTagClass == null)"
+          :key="item.value"
+          :index="index"
+          :class="item.elTagClass"
+        >{{ item.label + " " }}</span>
+        <el-tag
+          v-else
+          :disable-transitions="true"
+          :key="item.value + ''"
+          :index="index"
+          :type="item.elTagType === 'primary' ? '' : item.elTagType"
+          :class="item.elTagClass"
+        >{{ item.label + " " }}</el-tag>
+      </template>
+    </template>
+    <template v-if="unmatch && showValue">
+      {{ unmatchArray | handleArray }}
+    </template>
+  </div>
+</template>
+
+<script setup>
+// 璁板綍鏈尮閰嶇殑椤�
+const unmatchArray = ref([]);
+
+const props = defineProps({
+  // 鏁版嵁
+  options: {
+    type: Array,
+    default: null,
+  },
+  // 褰撳墠鐨勫��
+  value: [Number, String, Array],
+  // 褰撴湭鎵惧埌鍖归厤鐨勬暟鎹椂锛屾樉绀簐alue
+  showValue: {
+    type: Boolean,
+    default: true,
+  },
+  separator: {
+    type: String,
+    default: ",",
+  }
+});
+
+const values = computed(() => {
+  if (props.value === null || typeof props.value === 'undefined' || props.value === '') return [];
+  return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator);
+});
+
+const unmatch = computed(() => {
+  unmatchArray.value = [];
+  // 娌℃湁value涓嶆樉绀�
+  if (props.value === null || typeof props.value === 'undefined' || props.value === '' || props.options.length === 0) return false
+  // 浼犲叆鍊间负鏁扮粍
+  let unmatch = false // 娣诲姞涓�涓爣蹇楁潵鍒ゆ柇鏄惁鏈夋湭鍖归厤椤�
+  values.value.forEach(item => {
+    if (!props.options.some(v => v.value === item)) {
+      unmatchArray.value.push(item)
+      unmatch = true // 濡傛灉鏈夋湭鍖归厤椤癸紝灏嗘爣蹇楄缃负true
+    }
+  })
+  return unmatch // 杩斿洖鏍囧織鐨勫��
+});
+
+function handleArray(array) {
+  if (array.length === 0) return "";
+  return array.reduce((pre, cur) => {
+    return pre + " " + cur;
+  });
+}
+</script>
+
+<style scoped>
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+</style>
diff --git a/zhitan-vue/src/components/Editor/index.vue b/zhitan-vue/src/components/Editor/index.vue
new file mode 100644
index 0000000..0a696f2
--- /dev/null
+++ b/zhitan-vue/src/components/Editor/index.vue
@@ -0,0 +1,251 @@
+<template>
+  <div>
+    <el-upload
+      :action="uploadUrl"
+      :before-upload="handleBeforeUpload"
+      :on-success="handleUploadSuccess"
+      :on-error="handleUploadError"
+      name="file"
+      :show-file-list="false"
+      :headers="headers"
+      class="editor-img-uploader"
+      v-if="type == 'url'"
+    >
+      <i ref="uploadRef" class="editor-img-uploader"></i>
+    </el-upload>
+  </div>
+  <div class="editor">
+    <quill-editor
+      ref="quillEditorRef"
+      v-model:content="content"
+      contentType="html"
+      @textChange="(e) => $emit('update:modelValue', content)"
+      :options="options"
+      :style="styles"
+    />
+  </div>
+</template>
+
+<script setup>
+import { QuillEditor } from "@vueup/vue-quill";
+import "@vueup/vue-quill/dist/vue-quill.snow.css";
+import { getToken } from "@/utils/auth";
+
+const { proxy } = getCurrentInstance();
+
+const quillEditorRef = ref();
+const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 涓婁紶鐨勫浘鐗囨湇鍔″櫒鍦板潃
+const headers = ref({
+  Authorization: "Bearer " + getToken()
+});
+
+const props = defineProps({
+  /* 缂栬緫鍣ㄧ殑鍐呭 */
+  modelValue: {
+    type: String,
+  },
+  /* 楂樺害 */
+  height: {
+    type: Number,
+    default: null,
+  },
+  /* 鏈�灏忛珮搴� */
+  minHeight: {
+    type: Number,
+    default: null,
+  },
+  /* 鍙 */
+  readOnly: {
+    type: Boolean,
+    default: false,
+  },
+  /* 涓婁紶鏂囦欢澶у皬闄愬埗(MB) */
+  fileSize: {
+    type: Number,
+    default: 5,
+  },
+  /* 绫诲瀷锛坆ase64鏍煎紡銆乽rl鏍煎紡锛� */
+  type: {
+    type: String,
+    default: "url",
+  }
+});
+
+const options = ref({
+  theme: "snow",
+  bounds: document.body,
+  debug: "warn",
+  modules: {
+    // 宸ュ叿鏍忛厤缃�
+    toolbar: [
+      ["bold", "italic", "underline", "strike"],      // 鍔犵矖 鏂滀綋 涓嬪垝绾� 鍒犻櫎绾�
+      ["blockquote", "code-block"],                   // 寮曠敤  浠g爜鍧�
+      [{ list: "ordered" }, { list: "bullet" }],      // 鏈夊簭銆佹棤搴忓垪琛�
+      [{ indent: "-1" }, { indent: "+1" }],           // 缂╄繘
+      [{ size: ["small", false, "large", "huge"] }],  // 瀛椾綋澶у皬
+      [{ header: [1, 2, 3, 4, 5, 6, false] }],        // 鏍囬
+      [{ color: [] }, { background: [] }],            // 瀛椾綋棰滆壊銆佸瓧浣撹儗鏅鑹�
+      [{ align: [] }],                                // 瀵归綈鏂瑰紡
+      ["clean"],                                      // 娓呴櫎鏂囨湰鏍煎紡
+      ["link", "image", "video"]                      // 閾炬帴銆佸浘鐗囥�佽棰�
+    ],
+  },
+  placeholder: "璇疯緭鍏ュ唴瀹�",
+  readOnly: props.readOnly
+});
+
+const styles = computed(() => {
+  let style = {};
+  if (props.minHeight) {
+    style.minHeight = `${props.minHeight}px`;
+  }
+  if (props.height) {
+    style.height = `${props.height}px`;
+  }
+  return style;
+});
+
+const content = ref("");
+watch(() => props.modelValue, (v) => {
+  if (v !== content.value) {
+    content.value = v === undefined ? "<p></p>" : v;
+  }
+}, { immediate: true });
+
+// 濡傛灉璁剧疆浜嗕笂浼犲湴鍧�鍒欒嚜瀹氫箟鍥剧墖涓婁紶浜嬩欢
+onMounted(() => {
+  if (props.type == 'url') {
+    let quill = quillEditorRef.value.getQuill();
+    let toolbar = quill.getModule("toolbar");
+    toolbar.addHandler("image", (value) => {
+      if (value) {
+        proxy.$refs.uploadRef.click();
+      } else {
+        quill.format("image", false);
+      }
+    });
+  }
+});
+
+// 涓婁紶鍓嶆牎妫�鏍煎紡鍜屽ぇ灏�
+function handleBeforeUpload(file) {
+  const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
+  const isJPG = type.includes(file.type);
+  //妫�楠屾枃浠舵牸寮�
+  if (!isJPG) {
+    proxy.$modal.msgError(`鍥剧墖鏍煎紡閿欒!`);
+    return false;
+  }
+  // 鏍℃鏂囦欢澶у皬
+  if (props.fileSize) {
+    const isLt = file.size / 1024 / 1024 < props.fileSize;
+    if (!isLt) {
+      proxy.$modal.msgError(`涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃 ${props.fileSize} MB!`);
+      return false;
+    }
+  }
+  return true;
+}
+
+// 涓婁紶鎴愬姛澶勭悊
+function handleUploadSuccess(res, file) {
+  // 濡傛灉涓婁紶鎴愬姛
+  if (res.code == 200) {
+    // 鑾峰彇瀵屾枃鏈疄渚�
+    let quill = toRaw(quillEditorRef.value).getQuill();
+    // 鑾峰彇鍏夋爣浣嶇疆
+    let length = quill.selection.savedRange.index;
+    // 鎻掑叆鍥剧墖锛宺es.url涓烘湇鍔″櫒杩斿洖鐨勫浘鐗囬摼鎺ュ湴鍧�
+    quill.insertEmbed(length, "image", import.meta.env.VITE_APP_BASE_API + res.fileName);
+    // 璋冩暣鍏夋爣鍒版渶鍚�
+    quill.setSelection(length + 1);
+  } else {
+    proxy.$modal.msgError("鍥剧墖鎻掑叆澶辫触");
+  }
+}
+
+// 涓婁紶澶辫触澶勭悊
+function handleUploadError() {
+  proxy.$modal.msgError("鍥剧墖鎻掑叆澶辫触");
+}
+</script>
+
+<style>
+.editor-img-uploader {
+  display: none;
+}
+.editor, .ql-toolbar {
+  white-space: pre-wrap !important;
+  line-height: normal !important;
+}
+.quill-img {
+  display: none;
+}
+.ql-snow .ql-tooltip[data-mode="link"]::before {
+  content: "璇疯緭鍏ラ摼鎺ュ湴鍧�:";
+}
+.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
+  border-right: 0px;
+  content: "淇濆瓨";
+  padding-right: 0px;
+}
+.ql-snow .ql-tooltip[data-mode="video"]::before {
+  content: "璇疯緭鍏ヨ棰戝湴鍧�:";
+}
+.ql-snow .ql-picker.ql-size .ql-picker-label::before,
+.ql-snow .ql-picker.ql-size .ql-picker-item::before {
+  content: "14px";
+}
+.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
+.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
+  content: "10px";
+}
+.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
+.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
+  content: "18px";
+}
+.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
+.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
+  content: "32px";
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item::before {
+  content: "鏂囨湰";
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
+  content: "鏍囬1";
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
+  content: "鏍囬2";
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
+  content: "鏍囬3";
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
+  content: "鏍囬4";
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
+  content: "鏍囬5";
+}
+.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
+.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
+  content: "鏍囬6";
+}
+.ql-snow .ql-picker.ql-font .ql-picker-label::before,
+.ql-snow .ql-picker.ql-font .ql-picker-item::before {
+  content: "鏍囧噯瀛椾綋";
+}
+.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
+.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
+  content: "琛嚎瀛椾綋";
+}
+.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
+.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
+  content: "绛夊瀛椾綋";
+}
+</style>
diff --git a/zhitan-vue/src/components/FileUpload/index.vue b/zhitan-vue/src/components/FileUpload/index.vue
new file mode 100644
index 0000000..2af9672
--- /dev/null
+++ b/zhitan-vue/src/components/FileUpload/index.vue
@@ -0,0 +1,207 @@
+<template>
+  <div class="upload-file">
+    <el-upload
+      multiple
+      :action="uploadFileUrl"
+      :before-upload="handleBeforeUpload"
+      :file-list="fileList"
+      :limit="limit"
+      :on-error="handleUploadError"
+      :on-exceed="handleExceed"
+      :on-success="handleUploadSuccess"
+      :show-file-list="false"
+      :headers="headers"
+      class="upload-file-uploader"
+      ref="fileUpload"
+    >
+      <!-- 涓婁紶鎸夐挳 -->
+      <el-button type="primary">閫夊彇鏂囦欢</el-button>
+    </el-upload>
+    <!-- 涓婁紶鎻愮ず -->
+    <div class="el-upload__tip" v-if="showTip">
+      璇蜂笂浼�
+      <template v-if="fileSize"> 澶у皬涓嶈秴杩� <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
+      <template v-if="fileType"> 鏍煎紡涓� <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
+      鐨勬枃浠�
+    </div>
+    <!-- 鏂囦欢鍒楄〃 -->
+    <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
+      <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
+        <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank">
+          <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
+        </el-link>
+        <div class="ele-upload-list__item-content-action">
+          <el-link :underline="false" @click="handleDelete(index)" type="danger">鍒犻櫎</el-link>
+        </div>
+      </li>
+    </transition-group>
+  </div>
+</template>
+
+<script setup>
+import { getToken } from "@/utils/auth";
+
+const props = defineProps({
+  modelValue: [String, Object, Array],
+  // 鏁伴噺闄愬埗
+  limit: {
+    type: Number,
+    default: 5,
+  },
+  // 澶у皬闄愬埗(MB)
+  fileSize: {
+    type: Number,
+    default: 5,
+  },
+  // 鏂囦欢绫诲瀷, 渚嬪['png', 'jpg', 'jpeg']
+  fileType: {
+    type: Array,
+    default: () => ["doc", "xls", "ppt", "txt", "pdf"],
+  },
+  // 鏄惁鏄剧ず鎻愮ず
+  isShowTip: {
+    type: Boolean,
+    default: true
+  }
+});
+
+const { proxy } = getCurrentInstance();
+const emit = defineEmits();
+const number = ref(0);
+const uploadList = ref([]);
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 涓婁紶鏂囦欢鏈嶅姟鍣ㄥ湴鍧�
+const headers = ref({ Authorization: "Bearer " + getToken() });
+const fileList = ref([]);
+const showTip = computed(
+  () => props.isShowTip && (props.fileType || props.fileSize)
+);
+
+watch(() => props.modelValue, val => {
+  if (val) {
+    let temp = 1;
+    // 棣栧厛灏嗗�艰浆涓烘暟缁�
+    const list = Array.isArray(val) ? val : props.modelValue.split(',');
+    // 鐒跺悗灏嗘暟缁勮浆涓哄璞℃暟缁�
+    fileList.value = list.map(item => {
+      if (typeof item === "string") {
+        item = { name: item, url: item };
+      }
+      item.uid = item.uid || new Date().getTime() + temp++;
+      return item;
+    });
+  } else {
+    fileList.value = [];
+    return [];
+  }
+},{ deep: true, immediate: true });
+
+// 涓婁紶鍓嶆牎妫�鏍煎紡鍜屽ぇ灏�
+function handleBeforeUpload(file) {
+  // 鏍℃鏂囦欢绫诲瀷
+  if (props.fileType.length) {
+    const fileName = file.name.split('.');
+    const fileExt = fileName[fileName.length - 1];
+    const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
+    if (!isTypeOk) {
+      proxy.$modal.msgError(`鏂囦欢鏍煎紡涓嶆纭�, 璇蜂笂浼�${props.fileType.join("/")}鏍煎紡鏂囦欢!`);
+      return false;
+    }
+  }
+  // 鏍℃鏂囦欢澶у皬
+  if (props.fileSize) {
+    const isLt = file.size / 1024 / 1024 < props.fileSize;
+    if (!isLt) {
+      proxy.$modal.msgError(`涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃 ${props.fileSize} MB!`);
+      return false;
+    }
+  }
+  proxy.$modal.loading("姝e湪涓婁紶鏂囦欢锛岃绋嶅��...");
+  number.value++;
+  return true;
+}
+
+// 鏂囦欢涓暟瓒呭嚭
+function handleExceed() {
+  proxy.$modal.msgError(`涓婁紶鏂囦欢鏁伴噺涓嶈兘瓒呰繃 ${props.limit} 涓�!`);
+}
+
+// 涓婁紶澶辫触
+function handleUploadError(err) {
+  proxy.$modal.msgError("涓婁紶鏂囦欢澶辫触");
+}
+
+// 涓婁紶鎴愬姛鍥炶皟
+function handleUploadSuccess(res, file) {
+  if (res.code === 200) {
+    uploadList.value.push({ name: res.fileName, url: res.fileName });
+    uploadedSuccessfully();
+  } else {
+    number.value--;
+    proxy.$modal.closeLoading();
+    proxy.$modal.msgError(res.msg);
+    proxy.$refs.fileUpload.handleRemove(file);
+    uploadedSuccessfully();
+  }
+}
+
+// 鍒犻櫎鏂囦欢
+function handleDelete(index) {
+  fileList.value.splice(index, 1);
+  emit("update:modelValue", listToString(fileList.value));
+}
+
+// 涓婁紶缁撴潫澶勭悊
+function uploadedSuccessfully() {
+  if (number.value > 0 && uploadList.value.length === number.value) {
+    fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
+    uploadList.value = [];
+    number.value = 0;
+    emit("update:modelValue", listToString(fileList.value));
+    proxy.$modal.closeLoading();
+  }
+}
+
+// 鑾峰彇鏂囦欢鍚嶇О
+function getFileName(name) {
+  // 濡傛灉鏄痷rl閭d箞鍙栨渶鍚庣殑鍚嶅瓧 濡傛灉涓嶆槸鐩存帴杩斿洖
+  if (name.lastIndexOf("/") > -1) {
+    return name.slice(name.lastIndexOf("/") + 1);
+  } else {
+    return name;
+  }
+}
+
+// 瀵硅薄杞垚鎸囧畾瀛楃涓插垎闅�
+function listToString(list, separator) {
+  let strs = "";
+  separator = separator || ",";
+  for (let i in list) {
+    if (list[i].url) {
+      strs += list[i].url + separator;
+    }
+  }
+  return strs != '' ? strs.substr(0, strs.length - 1) : '';
+}
+</script>
+
+<style scoped lang="scss">
+.upload-file-uploader {
+  margin-bottom: 5px;
+}
+.upload-file-list .el-upload-list__item {
+  border: 1px solid #e4e7ed;
+  line-height: 2;
+  margin-bottom: 10px;
+  position: relative;
+}
+.upload-file-list .ele-upload-list__item-content {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  color: inherit;
+}
+.ele-upload-list__item-content-action .el-link {
+  margin-right: 10px;
+}
+</style>
diff --git a/zhitan-vue/src/components/Hamburger/index.vue b/zhitan-vue/src/components/Hamburger/index.vue
new file mode 100644
index 0000000..7e05d03
--- /dev/null
+++ b/zhitan-vue/src/components/Hamburger/index.vue
@@ -0,0 +1,51 @@
+<template>
+  <div class="nav-btn" @click="toggleClick">
+    <!-- <svg
+      :class="{'is-active':isActive}"
+      class="hamburger"
+      viewBox="0 0 1024 1024"
+      xmlns="http://www.w3.org/2000/svg"
+      width="64"
+      height="64"
+    >
+      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
+    </svg> -->
+    
+    <img src="/src/assets/images/nav-btn.png" width="26" v-if="settingsStore.sideTheme == 'theme-dark'">
+    <img src="/src/assets/images/nav-btn2.png" width="26" v-else>
+  </div>
+</template>
+
+<script setup>
+import useSettingsStore from '@/store/modules/settings'
+const settingsStore = useSettingsStore()
+defineProps({
+  isActive: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emit = defineEmits()
+const toggleClick = () => {
+  emit('toggleClick');
+}
+</script>
+
+<style scoped>
+.nav-btn {
+  width: 26px;
+  height: 24px;
+  margin-left: 16px;
+}
+.hamburger {
+  display: inline-block;
+  vertical-align: middle;
+  width: 20px;
+  height: 20px;
+}
+
+.hamburger.is-active {
+  transform: rotate(180deg);
+}
+</style>
diff --git a/zhitan-vue/src/components/HeaderCard/index.vue b/zhitan-vue/src/components/HeaderCard/index.vue
new file mode 100644
index 0000000..3413ca5
--- /dev/null
+++ b/zhitan-vue/src/components/HeaderCard/index.vue
@@ -0,0 +1,82 @@
+<template>
+  <div class="HeaderCard">
+    <div class="headerTitle">
+      <div class="pbgTitle">
+        <div class="title-font">
+          {{ title }}
+        </div>
+      </div>
+      <div class="icon-box">
+        <router-link to='/index'>
+          <img src="@/assets/images/basecard/goBack.png" width="30" />
+        </router-link>
+        <!-- <el-dropdown @command="handleCommand" trigger="click">
+                    <div class="name-wrapper">
+                        娆㈣繋鎮紝{{ userStore.name }}
+                        <el-icon><caret-bottom /></el-icon>
+                    </div>
+                    <template #dropdown>
+                        <el-dropdown-menu>
+                            <el-dropdown-item command="logout">
+                                <span>閫�鍑虹櫥褰�</span>
+                            </el-dropdown-item>
+                        </el-dropdown-menu>
+                    </template>
+</el-dropdown> -->
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineProps(['title'])
+import { useRoute } from "vue-router";
+import useUserStore from '@/store/modules/user'
+const userStore = useUserStore()
+
+</script>
+
+<style lang="scss" scoped>
+.HeaderCard {}
+
+.headerTitle {
+  width: 100%;
+  font-size: 2.3438vw; //45px;
+  color: #fff;
+  font-weight: 700;
+  text-align: center;
+  // background: #110f2e;
+  position: relative;
+
+  .pbgTitle {
+    background: url("@/assets/images/header.png") no-repeat;
+    background-size: 100% 100%;
+    margin: 0;
+    height: 17.3148vh;
+    opacity: 1;
+    position: absolute;
+    left: 0;
+    width: 100%;
+    z-index: 1;
+
+    .title-font {
+      font-family: YouSheBiaoTiHei;
+      font-size: 2.3958vw; //46px;
+      color: #FFFFFF;
+      padding-top: 3.5185vh; //38px;
+      background: linear-gradient(180deg, #FFFFFF 60%, #7EAEFF 100%);
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+      font-weight: normal;
+    }
+
+  }
+
+  .icon-box {
+    position: absolute;
+    top: 3.2407vh; //35px;
+    right: 1.0417vw; //20px;
+    z-index: 2;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/HeaderSearch/index.vue b/zhitan-vue/src/components/HeaderSearch/index.vue
new file mode 100644
index 0000000..6ef6d2c
--- /dev/null
+++ b/zhitan-vue/src/components/HeaderSearch/index.vue
@@ -0,0 +1,187 @@
+<template>
+  <div :class="{ 'show': show }" class="header-search">
+    <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
+    <el-select
+      ref="headerSearchSelectRef"
+      v-model="search"
+      :remote-method="querySearch"
+      filterable
+      default-first-option
+      remote
+      placeholder="Search"
+      class="header-search-select"
+      @change="change"
+    >
+      <el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
+    </el-select>
+  </div>
+</template>
+
+<script setup>
+import Fuse from 'fuse.js'
+import { getNormalPath } from '@/utils/ruoyi'
+import { isHttp } from '@/utils/validate'
+import usePermissionStore from '@/store/modules/permission'
+
+const search = ref('');
+const options = ref([]);
+const searchPool = ref([]);
+const show = ref(false);
+const fuse = ref(undefined);
+const headerSearchSelectRef = ref(null);
+const router = useRouter();
+const routes = computed(() => usePermissionStore().routes);
+
+function click() {
+  show.value = !show.value
+  if (show.value) {
+    headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
+  }
+};
+function close() {
+  headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
+  options.value = []
+  show.value = false
+}
+function change(val) {
+  const path = val.path;
+  const query = val.query;
+  if (isHttp(path)) {
+    // http(s):// 璺緞鏂扮獥鍙f墦寮�
+    const pindex = path.indexOf("http");
+    window.open(path.substr(pindex, path.length), "_blank");
+  } else {
+    if (query) {
+      router.push({ path: path, query: JSON.parse(query) });
+    } else {
+      router.push(path)
+    }
+  }
+
+  search.value = ''
+  options.value = []
+  nextTick(() => {
+    show.value = false
+  })
+}
+function initFuse(list) {
+  fuse.value = new Fuse(list, {
+    shouldSort: true,
+    threshold: 0.4,
+    location: 0,
+    distance: 100,
+    minMatchCharLength: 1,
+    keys: [{
+      name: 'title',
+      weight: 0.7
+    }, {
+      name: 'path',
+      weight: 0.3
+    }]
+  })
+}
+// Filter out the routes that can be displayed in the sidebar
+// And generate the internationalized title
+function generateRoutes(routes, basePath = '', prefixTitle = []) {
+  let res = []
+
+  for (const r of routes) {
+    // skip hidden router
+    if (r.hidden) { continue }
+    const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
+    const data = {
+      path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
+      title: [...prefixTitle]
+    }
+
+    if (r.meta && r.meta.title) {
+      data.title = [...data.title, r.meta.title]
+
+      if (r.redirect !== 'noRedirect') {
+        // only push the routes with title
+        // special case: need to exclude parent router without redirect
+        res.push(data)
+      }
+    }
+    if (r.query) {
+      data.query = r.query
+    }
+
+    // recursive child routes
+    if (r.children) {
+      const tempRoutes = generateRoutes(r.children, data.path, data.title)
+      if (tempRoutes.length >= 1) {
+        res = [...res, ...tempRoutes]
+      }
+    }
+  }
+  return res
+}
+function querySearch(query) {
+  if (query !== '') {
+    options.value = fuse.value.search(query)
+  } else {
+    options.value = []
+  }
+}
+
+onMounted(() => {
+  searchPool.value = generateRoutes(routes.value);
+})
+
+watchEffect(() => {
+  searchPool.value = generateRoutes(routes.value)
+})
+
+watch(show, (value) => {
+  if (value) {
+    document.body.addEventListener('click', close)
+  } else {
+    document.body.removeEventListener('click', close)
+  }
+})
+
+watch(searchPool, (list) => {
+  initFuse(list)
+})
+</script>
+
+<style lang='scss' scoped>
+.header-search {
+  font-size: 0 !important;
+
+  .search-icon {
+    cursor: pointer;
+    font-size: 18px;
+    vertical-align: middle;
+  }
+
+  .header-search-select {
+    font-size: 18px;
+    transition: width 0.2s;
+    width: 0;
+    overflow: hidden;
+    background: transparent;
+    border-radius: 0;
+    display: inline-block;
+    vertical-align: middle;
+
+    :deep(.el-input__inner) {
+      border-radius: 0;
+      border: 0;
+      padding-left: 0;
+      padding-right: 0;
+      box-shadow: none !important;
+      border-bottom: 1px solid #d9d9d9;
+      vertical-align: middle;
+    }
+  }
+
+  &.show {
+    .header-search-select {
+      width: 210px;
+      margin-left: 10px;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/IconSelect/index.vue b/zhitan-vue/src/components/IconSelect/index.vue
new file mode 100644
index 0000000..517a4af
--- /dev/null
+++ b/zhitan-vue/src/components/IconSelect/index.vue
@@ -0,0 +1,111 @@
+<template>
+  <div class="icon-body">
+    <el-input
+      v-model="iconName"
+      class="icon-search"
+      clearable
+      placeholder="璇疯緭鍏ュ浘鏍囧悕绉�"
+      @clear="filterIcons"
+      @input="filterIcons"
+    >
+      <template #suffix><i class="el-icon-search el-input__icon" /></template>
+    </el-input>
+    <div class="icon-list">
+      <div class="list-container">
+        <div v-for="(item, index) in iconList" class="icon-item-wrapper" :key="index" @click="selectedIcon(item)">
+          <div :class="['icon-item', { active: activeIcon === item }]">
+            <svg-icon :icon-class="item" class-name="icon" style="height: 25px;width: 16px;"/>
+            <span>{{ item }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import icons from './requireIcons'
+
+const props = defineProps({
+  activeIcon: {
+    type: String
+  }
+});
+
+const iconName = ref('');
+const iconList = ref(icons);
+const emit = defineEmits(['selected']);
+
+function filterIcons() {
+  iconList.value = icons
+  if (iconName.value) {
+    iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1)
+  }
+}
+
+function selectedIcon(name) {
+  emit('selected', name)
+  document.body.click()
+}
+
+function reset() {
+  iconName.value = ''
+  iconList.value = icons
+}
+
+defineExpose({
+  reset
+})
+</script>
+
+<style lang='scss' scoped>
+   .icon-body {
+    width: 100%;
+    padding: 10px;
+    .icon-search {
+      position: relative;
+      margin-bottom: 5px;
+    }
+    .icon-list {
+      height: 200px;
+      overflow: auto;
+      .list-container {
+        display: flex;
+        flex-wrap: wrap;
+        .icon-item-wrapper {
+          width: calc(100% / 3);
+          height: 25px;
+          line-height: 25px;
+          cursor: pointer;
+          display: flex;
+          .icon-item {
+            display: flex;
+            max-width: 100%;
+            height: 100%;
+            padding: 0 5px;
+            &:hover {
+              background: #ececec;
+              border-radius: 5px;
+            }
+            .icon {
+              flex-shrink: 0;
+            }
+            span {
+              display: inline-block;
+              vertical-align: -0.15em;
+              fill: currentColor;
+              padding-left: 2px;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+            }
+          }
+          .icon-item.active {
+            background: #ececec;
+            border-radius: 5px;
+          }
+        }
+      }
+    }
+  }
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/IconSelect/requireIcons.js b/zhitan-vue/src/components/IconSelect/requireIcons.js
new file mode 100644
index 0000000..ac22fd7
--- /dev/null
+++ b/zhitan-vue/src/components/IconSelect/requireIcons.js
@@ -0,0 +1,8 @@
+let icons = []
+const modules = import.meta.glob('./../../assets/icons/svg/*.svg');
+for (const path in modules) {
+  const p = path.split('assets/icons/svg/')[1].split('.svg')[0];
+  icons.push(p);
+}
+
+export default icons
\ No newline at end of file
diff --git a/zhitan-vue/src/components/ImagePreview/index.vue b/zhitan-vue/src/components/ImagePreview/index.vue
new file mode 100644
index 0000000..7e3d2b6
--- /dev/null
+++ b/zhitan-vue/src/components/ImagePreview/index.vue
@@ -0,0 +1,92 @@
+<template>
+  <el-image
+    :src="`${realSrc}`"
+    fit="cover"
+    :style="`width:${realWidth};height:${realHeight};`"
+    :preview-src-list="realSrcList"
+    preview-teleported
+  >
+    <template #error>
+      <div class="image-slot">
+        <el-icon><picture-filled /></el-icon>
+      </div>
+    </template>
+  </el-image>
+</template>
+
+<script setup>
+import { isExternal } from "@/utils/validate";
+
+const props = defineProps({
+  src: {
+    type: String,
+    default: ""
+  },
+  width: {
+    type: [Number, String],
+    default: ""
+  },
+  height: {
+    type: [Number, String],
+    default: ""
+  }
+});
+
+const realSrc = computed(() => {
+  if (!props.src) {
+    return;
+  }
+  let real_src = props.src.split(",")[0];
+  if (isExternal(real_src)) {
+    return real_src;
+  }
+  return import.meta.env.VITE_APP_BASE_API + real_src;
+});
+
+const realSrcList = computed(() => {
+  if (!props.src) {
+    return;
+  }
+  let real_src_list = props.src.split(",");
+  let srcList = [];
+  real_src_list.forEach(item => {
+    if (isExternal(item)) {
+      return srcList.push(item);
+    }
+    return srcList.push(import.meta.env.VITE_APP_BASE_API + item);
+  });
+  return srcList;
+});
+
+const realWidth = computed(() =>
+  typeof props.width == "string" ? props.width : `${props.width}px`
+);
+
+const realHeight = computed(() =>
+  typeof props.height == "string" ? props.height : `${props.height}px`
+);
+</script>
+
+<style lang="scss" scoped>
+.el-image {
+  border-radius: 5px;
+  background-color: #ebeef5;
+  box-shadow: 0 0 5px 1px #ccc;
+  :deep(.el-image__inner) {
+    transition: all 0.3s;
+    cursor: pointer;
+    &:hover {
+      transform: scale(1.2);
+    }
+  }
+  :deep(.image-slot) {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 100%;
+    height: 100%;
+    color: #909399;
+    font-size: 30px;
+  }
+}
+</style>
diff --git a/zhitan-vue/src/components/ImageUpload/index.vue b/zhitan-vue/src/components/ImageUpload/index.vue
new file mode 100644
index 0000000..55dafb8
--- /dev/null
+++ b/zhitan-vue/src/components/ImageUpload/index.vue
@@ -0,0 +1,213 @@
+<template>
+  <div class="component-upload-image">
+    <el-upload
+      multiple
+      :action="uploadImgUrl"
+      list-type="picture-card"
+      :on-success="handleUploadSuccess"
+      :before-upload="handleBeforeUpload"
+      :limit="limit"
+      :on-error="handleUploadError"
+      :on-exceed="handleExceed"
+      ref="imageUpload"
+      :before-remove="handleDelete"
+      :show-file-list="true"
+      :headers="headers"
+      :file-list="fileList"
+      :on-preview="handlePictureCardPreview"
+      :class="{ hide: fileList.length >= limit }"
+    >
+      <el-icon class="avatar-uploader-icon"><plus /></el-icon>
+    </el-upload>
+    <!-- 涓婁紶鎻愮ず -->
+    <div class="el-upload__tip" v-if="showTip">
+      璇蜂笂浼�
+      <template v-if="fileSize">
+        澶у皬涓嶈秴杩� <b style="color: #f56c6c">{{ fileSize }}MB</b>
+      </template>
+      <template v-if="fileType">
+        鏍煎紡涓� <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
+      </template>
+      鐨勬枃浠�
+    </div>
+
+    <el-dialog
+      v-model="dialogVisible"
+      title="棰勮"
+      width="800px"
+      append-to-body
+    >
+      <img
+        :src="dialogImageUrl"
+        style="display: block; max-width: 100%; margin: 0 auto"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { getToken } from "@/utils/auth";
+
+const props = defineProps({
+  modelValue: [String, Object, Array],
+  // 鍥剧墖鏁伴噺闄愬埗
+  limit: {
+    type: Number,
+    default: 5,
+  },
+  // 澶у皬闄愬埗(MB)
+  fileSize: {
+    type: Number,
+    default: 5,
+  },
+  // 鏂囦欢绫诲瀷, 渚嬪['png', 'jpg', 'jpeg']
+  fileType: {
+    type: Array,
+    default: () => ["png", "jpg", "jpeg"],
+  },
+  // 鏄惁鏄剧ず鎻愮ず
+  isShowTip: {
+    type: Boolean,
+    default: true
+  },
+});
+
+const { proxy } = getCurrentInstance();
+const emit = defineEmits();
+const number = ref(0);
+const uploadList = ref([]);
+const dialogImageUrl = ref("");
+const dialogVisible = ref(false);
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 涓婁紶鐨勫浘鐗囨湇鍔″櫒鍦板潃
+const headers = ref({ Authorization: "Bearer " + getToken() });
+const fileList = ref([]);
+const showTip = computed(
+  () => props.isShowTip && (props.fileType || props.fileSize)
+);
+
+watch(() => props.modelValue, val => {
+  if (val) {
+    // 棣栧厛灏嗗�艰浆涓烘暟缁�
+    const list = Array.isArray(val) ? val : props.modelValue.split(",");
+    // 鐒跺悗灏嗘暟缁勮浆涓哄璞℃暟缁�
+    fileList.value = list.map(item => {
+      if (typeof item === "string") {
+        if (item.indexOf(baseUrl) === -1) {
+          item = { name: baseUrl + item, url: baseUrl + item };
+        } else {
+          item = { name: item, url: item };
+        }
+      }
+      return item;
+    });
+  } else {
+    fileList.value = [];
+    return [];
+  }
+},{ deep: true, immediate: true });
+
+// 涓婁紶鍓峫oading鍔犺浇
+function handleBeforeUpload(file) {
+  let isImg = false;
+  if (props.fileType.length) {
+    let fileExtension = "";
+    if (file.name.lastIndexOf(".") > -1) {
+      fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
+    }
+    isImg = props.fileType.some(type => {
+      if (file.type.indexOf(type) > -1) return true;
+      if (fileExtension && fileExtension.indexOf(type) > -1) return true;
+      return false;
+    });
+  } else {
+    isImg = file.type.indexOf("image") > -1;
+  }
+  if (!isImg) {
+    proxy.$modal.msgError(
+      `鏂囦欢鏍煎紡涓嶆纭�, 璇蜂笂浼�${props.fileType.join("/")}鍥剧墖鏍煎紡鏂囦欢!`
+    );
+    return false;
+  }
+  if (props.fileSize) {
+    const isLt = file.size / 1024 / 1024 < props.fileSize;
+    if (!isLt) {
+      proxy.$modal.msgError(`涓婁紶澶村儚鍥剧墖澶у皬涓嶈兘瓒呰繃 ${props.fileSize} MB!`);
+      return false;
+    }
+  }
+  proxy.$modal.loading("姝e湪涓婁紶鍥剧墖锛岃绋嶅��...");
+  number.value++;
+}
+
+// 鏂囦欢涓暟瓒呭嚭
+function handleExceed() {
+  proxy.$modal.msgError(`涓婁紶鏂囦欢鏁伴噺涓嶈兘瓒呰繃 ${props.limit} 涓�!`);
+}
+
+// 涓婁紶鎴愬姛鍥炶皟
+function handleUploadSuccess(res, file) {
+  if (res.code === 200) {
+    uploadList.value.push({ name: res.fileName, url: res.fileName });
+    uploadedSuccessfully();
+  } else {
+    number.value--;
+    proxy.$modal.closeLoading();
+    proxy.$modal.msgError(res.msg);
+    proxy.$refs.imageUpload.handleRemove(file);
+    uploadedSuccessfully();
+  }
+}
+
+// 鍒犻櫎鍥剧墖
+function handleDelete(file) {
+  const findex = fileList.value.map(f => f.name).indexOf(file.name);
+  if (findex > -1 && uploadList.value.length === number.value) {
+    fileList.value.splice(findex, 1);
+    emit("update:modelValue", listToString(fileList.value));
+    return false;
+  }
+}
+
+// 涓婁紶缁撴潫澶勭悊
+function uploadedSuccessfully() {
+  if (number.value > 0 && uploadList.value.length === number.value) {
+    fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
+    uploadList.value = [];
+    number.value = 0;
+    emit("update:modelValue", listToString(fileList.value));
+    proxy.$modal.closeLoading();
+  }
+}
+
+// 涓婁紶澶辫触
+function handleUploadError() {
+  proxy.$modal.msgError("涓婁紶鍥剧墖澶辫触");
+  proxy.$modal.closeLoading();
+}
+
+// 棰勮
+function handlePictureCardPreview(file) {
+  dialogImageUrl.value = file.url;
+  dialogVisible.value = true;
+}
+
+// 瀵硅薄杞垚鎸囧畾瀛楃涓插垎闅�
+function listToString(list, separator) {
+  let strs = "";
+  separator = separator || ",";
+  for (let i in list) {
+    if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
+      strs += list[i].url.replace(baseUrl, "") + separator;
+    }
+  }
+  return strs != "" ? strs.substr(0, strs.length - 1) : "";
+}
+</script>
+
+<style scoped lang="scss">
+// .el-upload--picture-card 鎺у埗鍔犲彿閮ㄥ垎
+:deep(.hide .el-upload--picture-card) {
+    display: none;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/LeftTree/index.vue b/zhitan-vue/src/components/LeftTree/index.vue
new file mode 100644
index 0000000..5841bdd
--- /dev/null
+++ b/zhitan-vue/src/components/LeftTree/index.vue
@@ -0,0 +1,141 @@
+<template>
+  <div class="tree">
+    <el-tree :data="nodeOptions" :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false"
+      :filter-node-method="filterNode" ref="nodeTreeRef" node-key="id" highlight-current
+      :default-expanded-keys="defaultExpandedKeys" @node-click="nodeClick" accordion>
+      <template #default="{ node, data }">
+        <span class="item" style="" :title="node.label">
+          {{ node.label }}
+        </span>
+      </template>
+    </el-tree>
+  </div>
+</template>
+
+<script setup>
+const { proxy } = getCurrentInstance();
+const emit = defineEmits();
+const props = defineProps({
+  ParentModelCode: {
+    // 鍙傛暟绫诲瀷
+    type: String,
+    // 鍙傛暟榛樿鍊�
+    default: "",
+  },
+});
+
+watch(
+  () => props.ParentModelCode,
+  (newValue, oldValue) => {
+    if (newValue) {
+      getTree();
+    }
+  }
+);
+
+const data = reactive({
+  query: {
+    modelCode: null,
+  },
+});
+const { query } = toRefs(data);
+import { useRoute } from "vue-router";
+import { treeList } from "@/api/modelConfiguration/indexWarehouse";
+import { el } from "element-plus/es/locales.mjs";
+const nodeOptions = ref(undefined);
+let defaultExpandedKeys = ref([]);
+defineExpose({ getTree });
+/** 閫氳繃鏉′欢杩囨护鑺傜偣  */
+const filterNode = (value, data) => {
+  if (!value) return true;
+  return data.label.indexOf(value) !== -1;
+};
+getTree();
+/** 鏌ヨ閮ㄩ棬涓嬫媺鏍戠粨鏋� */
+function getTree() {
+  if (props.ParentModelCode) {
+    query.value = { modelCode: props.ParentModelCode };
+  } else {
+    query.value = { ...useRoute().query };
+  }
+  treeList(query.value).then((response) => {
+    nodeOptions.value = response.data;
+    if (response.data.length > 0) {
+      response.data.map((item) => {
+        defaultExpandedKeys.value.push(item.id);
+      });
+      nextTick(() => {
+        nodeClick(response.data[0]);
+      });
+    }
+  });
+}
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function nodeClick(data) {
+  proxy.$refs["nodeTreeRef"].setCurrentKey(data.id, true);
+  emit("handleNodeClick", data, nodeOptions.value);
+}
+</script>
+
+<style lang="scss" scoped>
+.themeDark {
+  .mycard {
+    margin: 0 13px;
+    margin-top: 10px;
+    border-radius: 10px 10px 10px 10px;
+    border: 1px solid #22408c;
+    padding-bottom: 10px;
+
+    .mycard-title {
+      display: flex;
+      justify-content: flex-start;
+      align-items: center;
+      height: 50px;
+      border-radius: 7px 7px 0 0;
+      padding: 20px;
+      background-color: #22408c;
+
+      .name {
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 18px;
+        color: #fff;
+      }
+    }
+  }
+}
+
+.themeLight {
+  .mycard {
+    margin: 0 13px;
+    margin-top: 10px;
+    border-radius: 10px 10px 10px 10px;
+    border: 1px solid #ebebeb;
+    padding-bottom: 10px;
+    background-color: #fff;
+
+    .mycard-title {
+      display: flex;
+      justify-content: flex-start;
+      align-items: center;
+      height: 50px;
+      border-radius: 7px 7px 0 0;
+      padding: 20px;
+      background-color: #e7eefd;
+
+      .name {
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 18px;
+        color: #2d2e31;
+      }
+    }
+  }
+}
+
+.item {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>
diff --git a/zhitan-vue/src/components/Pagination/index.vue b/zhitan-vue/src/components/Pagination/index.vue
new file mode 100644
index 0000000..4967380
--- /dev/null
+++ b/zhitan-vue/src/components/Pagination/index.vue
@@ -0,0 +1,110 @@
+<template>
+  <div :class="{ 'hidden': hidden }" class="pagination-container">
+    <el-pagination :background="background" v-model:current-page="currentPage" v-model:page-size="pageSize"
+      :layout="layout" :page-sizes="pageSizes" :pager-count="pagerCount" :total="total" @size-change="handleSizeChange"
+      @current-change="handleCurrentChange" />
+  </div>
+</template>
+
+<script setup>
+import { scrollTo } from '@/utils/scroll-to'
+
+const props = defineProps({
+  total: {
+    required: true,
+    type: Number
+  },
+  page: {
+    type: Number,
+    default: 1
+  },
+  limit: {
+    type: Number,
+    default: 20
+  },
+  pageSizes: {
+    type: Array,
+    default() {
+      return [10, 20, 30, 50]
+    }
+  },
+  // 绉诲姩绔〉鐮佹寜閽殑鏁伴噺绔粯璁ゅ��5
+  pagerCount: {
+    type: Number,
+    default: document.body.clientWidth < 992 ? 5 : 7
+  },
+  layout: {
+    type: String,
+    default: 'total, sizes, prev, pager, next, jumper'
+  },
+  background: {
+    type: Boolean,
+    default: true
+  },
+  autoScroll: {
+    type: Boolean,
+    default: true
+  },
+  hidden: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emit = defineEmits();
+const currentPage = computed({
+  get() {
+    return props.page
+  },
+  set(val) {
+    emit('update:page', val)
+  }
+})
+const pageSize = computed({
+  get() {
+    return props.limit
+  },
+  set(val) {
+    emit('update:limit', val)
+  }
+})
+function handleSizeChange(val) {
+  if (currentPage.value * val > props.total) {
+    currentPage.value = 1
+  }
+  emit('pagination', { page: currentPage.value, limit: val })
+  if (props.autoScroll) {
+    scrollTo(0, 800)
+  }
+}
+function handleCurrentChange(val) {
+  emit('pagination', { page: val, limit: pageSize.value })
+  if (props.autoScroll) {
+    scrollTo(0, 800)
+  }
+}
+
+</script>
+
+<style lang="scss" scoped>
+.pagination-container {
+  /* background: #fff; */
+  padding: 0 16px  62px;
+  :deep  .el-pagination__total {
+    color: #fff;
+  }
+
+  :deep  .el-pagination__goto {
+    color: #fff;
+  }
+
+  :deep  .el-pagination__classifier {
+    color: #fff;
+  }
+
+}
+
+.pagination-container.hidden {
+  display: none;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/ParentView/index.vue b/zhitan-vue/src/components/ParentView/index.vue
new file mode 100644
index 0000000..7bf6148
--- /dev/null
+++ b/zhitan-vue/src/components/ParentView/index.vue
@@ -0,0 +1,3 @@
+<template >
+  <router-view />
+</template>
diff --git a/zhitan-vue/src/components/RightToolbar/index.vue b/zhitan-vue/src/components/RightToolbar/index.vue
new file mode 100644
index 0000000..5a53dd4
--- /dev/null
+++ b/zhitan-vue/src/components/RightToolbar/index.vue
@@ -0,0 +1,134 @@
+<template>
+  <div class="top-right-btn" :style="style">
+    <el-row>
+      <el-tooltip class="item" effect="dark" :content="showSearch ? '闅愯棌鎼滅储' : '鏄剧ず鎼滅储'" placement="top" v-if="search">
+        <el-button circle icon="Search" @click="toggleSearch()" />
+      </el-tooltip>
+      <el-tooltip class="item" effect="dark" content="鍒锋柊" placement="top">
+        <el-button circle icon="Refresh" @click="refresh()" />
+      </el-tooltip>
+      <el-tooltip class="item" effect="dark" content="鏄鹃殣鍒�" placement="top" v-if="columns">
+        <el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
+        <el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
+          <el-button circle icon="Menu" />
+          <template #dropdown>
+            <el-dropdown-menu>
+              <template v-for="item in columns" :key="item.key">
+                <el-dropdown-item>
+                  <el-checkbox :checked="item.visible" @change="checkboxChange($event, item.label)" :label="item.label" />
+                </el-dropdown-item>
+              </template>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </el-tooltip>
+    </el-row>
+    <el-dialog :title="title" v-model="open" append-to-body>
+      <el-transfer
+        :titles="['鏄剧ず', '闅愯棌']"
+        v-model="value"
+        :data="columns"
+        @change="dataChange"
+      ></el-transfer>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  /* 鏄惁鏄剧ず妫�绱㈡潯浠� */
+  showSearch: {
+    type: Boolean,
+    default: true,
+  },
+  /* 鏄鹃殣鍒椾俊鎭� */
+  columns: {
+    type: Array,
+  },
+  /* 鏄惁鏄剧ず妫�绱㈠浘鏍� */
+  search: {
+    type: Boolean,
+    default: true,
+  },
+  /* 鏄鹃殣鍒楃被鍨嬶紙transfer绌挎妗嗐�乧heckbox澶嶉�夋锛� */
+  showColumnsType: {
+    type: String,
+    default: "checkbox",
+  },
+  /* 鍙冲杈硅窛 */
+  gutter: {
+    type: Number,
+    default: 10,
+  },
+})
+
+const emits = defineEmits(['update:showSearch', 'queryTable']);
+
+// 鏄鹃殣鏁版嵁
+const value = ref([]);
+// 寮瑰嚭灞傛爣棰�
+const title = ref("鏄剧ず/闅愯棌");
+// 鏄惁鏄剧ず寮瑰嚭灞�
+const open = ref(false);
+
+const style = computed(() => {
+  const ret = {};
+  if (props.gutter) {
+    ret.marginRight = `${props.gutter / 2}px`;
+  }
+  return ret;
+});
+
+// 鎼滅储
+function toggleSearch() {
+  emits("update:showSearch", !props.showSearch);
+}
+
+// 鍒锋柊
+function refresh() {
+  emits("queryTable");
+}
+
+// 鍙充晶鍒楄〃鍏冪礌鍙樺寲
+function dataChange(data) {
+  for (let item in props.columns) {
+    const key = props.columns[item].key;
+    props.columns[item].visible = !data.includes(key);
+  }
+}
+
+// 鎵撳紑鏄鹃殣鍒梔ialog
+function showColumn() {
+  open.value = true;
+}
+
+if (props.showColumnsType == 'transfer') {
+  // 鏄鹃殣鍒楀垵濮嬮粯璁ら殣钘忓垪
+  for (let item in props.columns) {
+    if (props.columns[item].visible === false) {
+      value.value.push(parseInt(item));
+    }
+  }
+}
+
+// 鍕鹃��
+function checkboxChange(event, label) {
+  props.columns.filter(item => item.label == label)[0].visible = event;
+}
+
+</script>
+
+<style lang='scss' scoped>
+:deep(.el-transfer__button) {
+  border-radius: 50%;
+  display: block;
+  margin-left: 0px;
+}
+:deep(.el-transfer__button:first-child) {
+  margin-bottom: 10px;
+}
+:deep(.el-dropdown-menu__item) {
+  line-height: 30px;
+  padding: 0 17px;
+}
+</style>
diff --git a/zhitan-vue/src/components/Screenfull/index.vue b/zhitan-vue/src/components/Screenfull/index.vue
new file mode 100644
index 0000000..7ad28ea
--- /dev/null
+++ b/zhitan-vue/src/components/Screenfull/index.vue
@@ -0,0 +1,22 @@
+<template>
+  <div>
+    <svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" @click="toggle" />
+  </div>
+</template>
+
+<script setup>
+import { useFullscreen } from '@vueuse/core'
+
+const { isFullscreen, enter, exit, toggle } = useFullscreen();
+</script>
+
+<style lang='scss' scoped>
+.screenfull-svg {
+  display: inline-block;
+  cursor: pointer;
+  fill: #5a5e66;
+  width: 20px;
+  height: 20px;
+  vertical-align: 10px;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/SizeSelect/index.vue b/zhitan-vue/src/components/SizeSelect/index.vue
new file mode 100644
index 0000000..4c2e7e9
--- /dev/null
+++ b/zhitan-vue/src/components/SizeSelect/index.vue
@@ -0,0 +1,45 @@
+<template>
+  <div>
+    <el-dropdown trigger="click" @command="handleSetSize">
+      <div class="size-icon--style">
+        <svg-icon class-name="size-icon" icon-class="size" />
+      </div>
+      <template #dropdown>
+        <el-dropdown-menu>
+          <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size === item.value" :command="item.value">
+            {{ item.label }}
+          </el-dropdown-item>
+        </el-dropdown-menu>
+      </template>
+    </el-dropdown>
+  </div>
+</template>
+
+<script setup>
+import useAppStore from "@/store/modules/app";
+
+const appStore = useAppStore();
+const size = computed(() => appStore.size);
+const route = useRoute();
+const router = useRouter();
+const { proxy } = getCurrentInstance();
+const sizeOptions = ref([
+  { label: "杈冨ぇ", value: "large" },
+  { label: "榛樿", value: "default" },
+  { label: "绋嶅皬", value: "small" },
+]);
+
+function handleSetSize(size) {
+  proxy.$modal.loading("姝e湪璁剧疆甯冨眬澶у皬锛岃绋嶅��...");
+  appStore.setSize(size);
+  setTimeout("window.location.reload()", 1000);
+}
+</script>
+
+<style lang='scss' scoped>
+.size-icon--style {
+  font-size: 18px;
+  line-height: 50px;
+  padding-right: 7px;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/SvgIcon/index.vue b/zhitan-vue/src/components/SvgIcon/index.vue
new file mode 100644
index 0000000..8c101f6
--- /dev/null
+++ b/zhitan-vue/src/components/SvgIcon/index.vue
@@ -0,0 +1,53 @@
+<template>
+  <svg :class="svgClass" aria-hidden="true">
+    <use :xlink:href="iconName" :fill="color" />
+  </svg>
+</template>
+
+<script>
+export default defineComponent({
+  props: {
+    iconClass: {
+      type: String,
+      required: true
+    },
+    className: {
+      type: String,
+      default: ''
+    },
+    color: {
+      type: String,
+      default: ''
+    },
+  },
+  setup(props) {
+    return {
+      iconName: computed(() => `#icon-${props.iconClass}`),
+      svgClass: computed(() => {
+        if (props.className) {
+          return `svg-icon ${props.className}`
+        }
+        return 'svg-icon'
+      })
+    }
+  }
+})
+</script>
+
+<style scope lang="scss">
+.sub-el-icon,
+.nav-icon {
+  display: inline-block;
+  font-size: 15px;
+  margin-right: 12px;
+  position: relative;
+}
+
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  position: relative;
+  fill: currentColor;
+  vertical-align: -2px;
+}
+</style>
diff --git a/zhitan-vue/src/components/SvgIcon/svgicon.js b/zhitan-vue/src/components/SvgIcon/svgicon.js
new file mode 100644
index 0000000..4431719
--- /dev/null
+++ b/zhitan-vue/src/components/SvgIcon/svgicon.js
@@ -0,0 +1,10 @@
+import * as components from '@element-plus/icons-vue'
+
+export default {
+    install: (app) => {
+        for (const key in components) {
+            const componentConfig = components[key];
+            app.component(componentConfig.name, componentConfig);
+        }
+    },
+};
diff --git a/zhitan-vue/src/components/TopNav/index.vue b/zhitan-vue/src/components/TopNav/index.vue
new file mode 100644
index 0000000..52b40ea
--- /dev/null
+++ b/zhitan-vue/src/components/TopNav/index.vue
@@ -0,0 +1,214 @@
+<template>
+  <el-menu
+    :default-active="activeMenu"
+    mode="horizontal"
+    @select="handleSelect"
+    :ellipsis="false"
+  >
+    <template v-for="(item, index) in topMenus">
+      <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber">
+        <svg-icon
+        v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
+        :icon-class="item.meta.icon"/>
+        {{ item.meta.title }}
+      </el-menu-item>
+    </template>
+
+    <!-- 椤堕儴鑿滃崟瓒呭嚭鏁伴噺鎶樺彔 -->
+    <el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
+      <template #title>鏇村鑿滃崟</template>
+      <template v-for="(item, index) in topMenus">
+        <el-menu-item
+          :index="item.path"
+          :key="index"
+          v-if="index >= visibleNumber">
+        <svg-icon
+          v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
+          :icon-class="item.meta.icon"/>
+        {{ item.meta.title }}
+        </el-menu-item>
+      </template>
+    </el-sub-menu>
+  </el-menu>
+</template>
+
+<script setup>
+import { constantRoutes } from "@/router"
+import { isHttp } from '@/utils/validate'
+import useAppStore from '@/store/modules/app'
+import useSettingsStore from '@/store/modules/settings'
+import usePermissionStore from '@/store/modules/permission'
+
+// 椤堕儴鏍忓垵濮嬫暟
+const visibleNumber = ref(null);
+// 褰撳墠婵�娲昏彍鍗曠殑 index
+const currentIndex = ref(null);
+// 闅愯棌渚ц竟鏍忚矾鐢�
+const hideList = ['/index', '/user/profile'];
+
+const appStore = useAppStore()
+const settingsStore = useSettingsStore()
+const permissionStore = usePermissionStore()
+const route = useRoute();
+const router = useRouter();
+
+// 涓婚棰滆壊
+const theme = computed(() => settingsStore.theme);
+// 鎵�鏈夌殑璺敱淇℃伅
+const routers = computed(() => permissionStore.topbarRouters);
+
+// 椤堕儴鏄剧ず鑿滃崟
+const topMenus = computed(() => {
+  let topMenus = [];
+  routers.value.map((menu) => {
+    if (menu.hidden !== true) {
+      // 鍏煎椤堕儴鏍忎竴绾ц彍鍗曞唴閮ㄨ烦杞�
+      if (menu.path === "/") {
+          topMenus.push(menu.children[0]);
+      } else {
+          topMenus.push(menu);
+      }
+    }
+  })
+  return topMenus;
+})
+
+// 璁剧疆瀛愯矾鐢�
+const childrenMenus = computed(() => {
+  let childrenMenus = [];
+  routers.value.map((router) => {
+    for (let item in router.children) {
+      if (router.children[item].parentPath === undefined) {
+        if(router.path === "/") {
+          router.children[item].path = "/" + router.children[item].path;
+        } else {
+          if(!isHttp(router.children[item].path)) {
+            router.children[item].path = router.path + "/" + router.children[item].path;
+          }
+        }
+        router.children[item].parentPath = router.path;
+      }
+      childrenMenus.push(router.children[item]);
+    }
+  })
+  return constantRoutes.concat(childrenMenus);
+})
+
+// 榛樿婵�娲荤殑鑿滃崟
+const activeMenu = computed(() => {
+  const path = route.path;
+  let activePath = path;
+  if (path !== undefined && path.lastIndexOf("/") > 0 && hideList.indexOf(path) === -1) {
+    const tmpPath = path.substring(1, path.length);
+    activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"));
+    if (!route.meta.link) {
+        appStore.toggleSideBarHide(false);
+    }
+  } else if(!route.children) {
+    activePath = path;
+    appStore.toggleSideBarHide(true);
+  }
+  activeRoutes(activePath);
+  return activePath;
+})
+
+function setVisibleNumber() {
+  const width = document.body.getBoundingClientRect().width / 3;
+  visibleNumber.value = parseInt(width / 85);
+}
+
+function handleSelect(key, keyPath) {
+  currentIndex.value = key;
+  const route = routers.value.find(item => item.path === key);
+  if (isHttp(key)) {
+    // http(s):// 璺緞鏂扮獥鍙f墦寮�
+    window.open(key, "_blank");
+  } else if (!route || !route.children) {
+    // 娌℃湁瀛愯矾鐢辫矾寰勫唴閮ㄦ墦寮�
+    const routeMenu = childrenMenus.value.find(item => item.path === key);
+    if (routeMenu && routeMenu.query) {
+      let query = JSON.parse(routeMenu.query);
+      router.push({ path: key, query: query });
+    } else {
+      router.push({ path: key });
+    }
+    appStore.toggleSideBarHide(true);
+  } else {
+    // 鏄剧ず宸︿晶鑱斿姩鑿滃崟
+    activeRoutes(key);
+    appStore.toggleSideBarHide(false);
+  }
+}
+
+function activeRoutes(key) {
+  let routes = [];
+  if (childrenMenus.value && childrenMenus.value.length > 0) {
+    childrenMenus.value.map((item) => {
+      if (key == item.parentPath || (key == "index" && "" == item.path)) {
+        routes.push(item);
+      }
+    });
+  }
+  if(routes.length > 0) {
+    permissionStore.setSidebarRouters(routes);
+  } else {
+    appStore.toggleSideBarHide(true);
+  }
+  return routes;
+}
+
+onMounted(() => {
+  window.addEventListener('resize', setVisibleNumber)
+})
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', setVisibleNumber)
+})
+
+onMounted(() => {
+  setVisibleNumber()
+})
+</script>
+
+<style lang="scss">
+.topmenu-container.el-menu--horizontal > .el-menu-item {
+  float: left;
+  height: 50px !important;
+  line-height: 50px !important;
+  color: #999093 !important;
+  padding: 0 5px !important;
+  margin: 0 10px !important;
+}
+
+.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {
+  border-bottom: 2px solid #{'var(--theme)'} !important;
+  color: #303133;
+}
+
+/* sub-menu item */
+.topmenu-container.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
+  float: left;
+  height: 50px !important;
+  line-height: 50px !important;
+  color: #999093 !important;
+  padding: 0 5px !important;
+  margin: 0 10px !important;
+}
+
+/* 鑳屾櫙鑹查殣钘� */
+.topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, .topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, .topmenu-container.el-menu--horizontal>.el-submenu .el-submenu__title:hover {
+  background-color: #ffffff !important;
+}
+
+/* 鍥炬爣鍙抽棿璺� */
+.topmenu-container .svg-icon {
+  margin-right: 4px;
+}
+
+/* topmenu more arrow */
+.topmenu-container .el-sub-menu .el-sub-menu__icon-arrow {
+  position: static;
+  vertical-align: middle;
+  margin-left: 8px;
+  margin-top: 0px;
+}
+</style>
diff --git a/zhitan-vue/src/components/TreeSelect/index.vue b/zhitan-vue/src/components/TreeSelect/index.vue
new file mode 100644
index 0000000..4ff0e76
--- /dev/null
+++ b/zhitan-vue/src/components/TreeSelect/index.vue
@@ -0,0 +1,156 @@
+<template>
+  <div class="el-tree-select">
+    <el-select
+      style="width: 100%"
+      v-model="valueId"
+      ref="treeSelect"
+      :filterable="true"
+      :clearable="true"
+      @clear="clearHandle"
+      :filter-method="selectFilterData"
+      :placeholder="placeholder"
+    >
+      <el-option :value="valueId" :label="valueTitle">
+        <el-tree
+          id="tree-option"
+          ref="selectTree"
+          :accordion="accordion"
+          :data="options"
+          :props="objMap"
+          :node-key="objMap.value"
+          :expand-on-click-node="false"
+          :default-expanded-keys="defaultExpandedKey"
+          :filter-node-method="filterNode"
+          @node-click="handleNodeClick"
+        ></el-tree>
+      </el-option>
+    </el-select>
+  </div>
+</template>
+
+<script setup>
+
+const { proxy } = getCurrentInstance();
+
+const props = defineProps({
+  /* 閰嶇疆椤� */
+  objMap: {
+    type: Object,
+    default: () => {
+      return {
+        value: 'id', // ID瀛楁鍚�
+        label: 'label', // 鏄剧ず鍚嶇О
+        children: 'children' // 瀛愮骇瀛楁鍚�
+      }
+    }
+  },
+  /* 鑷姩鏀惰捣 */
+  accordion: {
+    type: Boolean,
+    default: () => {
+      return false
+    }
+  },
+  /**褰撳墠鍙屽悜鏁版嵁缁戝畾鐨勫�� */
+  value: {
+    type: [String, Number],
+    default: ''
+  },
+  /**褰撳墠鐨勬暟鎹� */
+  options: {
+    type: Array,
+    default: () => []
+  },
+  /**杈撳叆妗嗗唴閮ㄧ殑鏂囧瓧 */
+  placeholder: {
+    type: String,
+    default: ''
+  }
+})
+
+const emit = defineEmits(['update:value']);
+
+const valueId = computed({
+  get: () => props.value,
+  set: (val) => {
+    emit('update:value', val)
+  }
+});
+const valueTitle = ref('');
+const defaultExpandedKey = ref([]);
+
+function initHandle() {
+  nextTick(() => {
+    const selectedValue = valueId.value;
+    if(selectedValue !== null && typeof (selectedValue) !== 'undefined') {
+      const node = proxy.$refs.selectTree.getNode(selectedValue)
+      if (node) {
+        valueTitle.value = node.data[props.objMap.label]
+        proxy.$refs.selectTree.setCurrentKey(selectedValue) // 璁剧疆榛樿閫変腑
+        defaultExpandedKey.value = [selectedValue] // 璁剧疆榛樿灞曞紑
+      }
+    } else {
+      clearHandle()
+    }
+  })
+}
+function handleNodeClick(node) {
+  valueTitle.value = node[props.objMap.label]
+  valueId.value = node[props.objMap.value];
+  defaultExpandedKey.value = [];
+  proxy.$refs.treeSelect.blur()
+  selectFilterData('')
+}
+function selectFilterData(val) {
+  proxy.$refs.selectTree.filter(val)
+}
+function filterNode(value, data) {
+  if (!value) return true
+  return data[props.objMap['label']].indexOf(value) !== -1
+}
+function clearHandle() {
+  valueTitle.value = ''
+  valueId.value = ''
+  defaultExpandedKey.value = [];
+  clearSelected()
+}
+function clearSelected() {
+  const allNode = document.querySelectorAll('#tree-option .el-tree-node')
+  allNode.forEach((element) => element.classList.remove('is-current'))
+}
+
+onMounted(() => {
+  initHandle()
+})
+
+watch(valueId, () => {
+  initHandle();
+})
+</script>
+
+<style lang='scss' scoped>
+@import "@/assets/styles/variables.module.scss";
+.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
+  padding: 0;
+  background-color: #fff;
+  height: auto;
+}
+
+.el-select-dropdown__item.selected {
+  font-weight: normal;
+}
+
+ul li .el-tree .el-tree-node__content {
+  height: auto;
+  padding: 0 20px;
+  box-sizing: border-box;
+}
+
+:deep(.el-tree-node__content:hover),
+:deep(.el-tree-node__content:active),
+:deep(.is-current > div:first-child),
+:deep(.el-tree-node__content:focus) {
+  background-color: mix(#fff, $--color-primary, 90%);
+  color: $--color-primary;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/components/iFrame/index.vue b/zhitan-vue/src/components/iFrame/index.vue
new file mode 100644
index 0000000..091b1a2
--- /dev/null
+++ b/zhitan-vue/src/components/iFrame/index.vue
@@ -0,0 +1,31 @@
+<template>
+  <div v-loading="loading" :style="'height:' + height">
+    <iframe 
+      :src="url" 
+      frameborder="no" 
+      style="width: 100%; height: 100%" 
+      scrolling="auto" />
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  src: {
+    type: String,
+    required: true
+  }
+})
+
+const height = ref(document.documentElement.clientHeight - 94.5 + "px;")
+const loading = ref(true)
+const url = computed(() => props.src)
+
+onMounted(() => {
+  setTimeout(() => {
+    loading.value = false;
+  }, 300);
+  window.onresize = function temp() {
+    height.value = document.documentElement.clientHeight - 94.5 + "px;";
+  };
+})
+</script>
diff --git a/zhitan-vue/src/directive/common/copyText.js b/zhitan-vue/src/directive/common/copyText.js
new file mode 100644
index 0000000..7063df8
--- /dev/null
+++ b/zhitan-vue/src/directive/common/copyText.js
@@ -0,0 +1,66 @@
+/**
+* v-copyText 澶嶅埗鏂囨湰鍐呭
+* Copyright (c) 2022 ruoyi
+*/
+
+export default {
+  beforeMount(el, { value, arg }) {
+    if (arg === "callback") {
+      el.$copyCallback = value;
+    } else {
+      el.$copyValue = value;
+      const handler = () => {
+        copyTextToClipboard(el.$copyValue);
+        if (el.$copyCallback) {
+          el.$copyCallback(el.$copyValue);
+        }
+      };
+      el.addEventListener("click", handler);
+      el.$destroyCopy = () => el.removeEventListener("click", handler);
+    }
+  }
+}
+
+function copyTextToClipboard(input, { target = document.body } = {}) {
+  const element = document.createElement('textarea');
+  const previouslyFocusedElement = document.activeElement;
+
+  element.value = input;
+
+  // Prevent keyboard from showing on mobile
+  element.setAttribute('readonly', '');
+
+  element.style.contain = 'strict';
+  element.style.position = 'absolute';
+  element.style.left = '-9999px';
+  element.style.fontSize = '12pt'; // Prevent zooming on iOS
+
+  const selection = document.getSelection();
+  const originalRange = selection.rangeCount > 0 && selection.getRangeAt(0);
+
+  target.append(element);
+  element.select();
+
+  // Explicit selection workaround for iOS
+  element.selectionStart = 0;
+  element.selectionEnd = input.length;
+
+  let isSuccess = false;
+  try {
+    isSuccess = document.execCommand('copy');
+  } catch { }
+
+  element.remove();
+
+  if (originalRange) {
+    selection.removeAllRanges();
+    selection.addRange(originalRange);
+  }
+
+  // Get the focus back on the previously focused element, if any
+  if (previouslyFocusedElement) {
+    previouslyFocusedElement.focus();
+  }
+
+  return isSuccess;
+}
diff --git a/zhitan-vue/src/directive/index.js b/zhitan-vue/src/directive/index.js
new file mode 100644
index 0000000..86b8f88
--- /dev/null
+++ b/zhitan-vue/src/directive/index.js
@@ -0,0 +1,9 @@
+import hasRole from './permission/hasRole'
+import hasPermi from './permission/hasPermi'
+import copyText from './common/copyText'
+
+export default function directive(app){
+  app.directive('hasRole', hasRole)
+  app.directive('hasPermi', hasPermi)
+  app.directive('copyText', copyText)
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/directive/permission/hasPermi.js b/zhitan-vue/src/directive/permission/hasPermi.js
new file mode 100644
index 0000000..44ef3f8
--- /dev/null
+++ b/zhitan-vue/src/directive/permission/hasPermi.js
@@ -0,0 +1,28 @@
+ /**
+ * v-hasPermi 鎿嶄綔鏉冮檺澶勭悊
+ * Copyright (c) 2019 ruoyi
+ */
+ 
+import useUserStore from '@/store/modules/user'
+
+export default {
+  mounted(el, binding, vnode) {
+    const { value } = binding
+    const all_permission = "*:*:*";
+    const permissions = useUserStore().permissions
+
+    if (value && value instanceof Array && value.length > 0) {
+      const permissionFlag = value
+
+      const hasPermissions = permissions.some(permission => {
+        return all_permission === permission || permissionFlag.includes(permission)
+      })
+
+      if (!hasPermissions) {
+        el.parentNode && el.parentNode.removeChild(el)
+      }
+    } else {
+      throw new Error(`璇疯缃搷浣滄潈闄愭爣绛惧�糮)
+    }
+  }
+}
diff --git a/zhitan-vue/src/directive/permission/hasRole.js b/zhitan-vue/src/directive/permission/hasRole.js
new file mode 100644
index 0000000..8774bdd
--- /dev/null
+++ b/zhitan-vue/src/directive/permission/hasRole.js
@@ -0,0 +1,28 @@
+ /**
+ * v-hasRole 瑙掕壊鏉冮檺澶勭悊
+ * Copyright (c) 2019 ruoyi
+ */
+ 
+import useUserStore from '@/store/modules/user'
+
+export default {
+  mounted(el, binding, vnode) {
+    const { value } = binding
+    const super_admin = "admin";
+    const roles = useUserStore().roles
+
+    if (value && value instanceof Array && value.length > 0) {
+      const roleFlag = value
+
+      const hasRole = roles.some(role => {
+        return super_admin === role || roleFlag.includes(role)
+      })
+
+      if (!hasRole) {
+        el.parentNode && el.parentNode.removeChild(el)
+      }
+    } else {
+      throw new Error(`璇疯缃鑹叉潈闄愭爣绛惧�糮)
+    }
+  }
+}
diff --git a/zhitan-vue/src/layout/components/AppMain.vue b/zhitan-vue/src/layout/components/AppMain.vue
new file mode 100644
index 0000000..328df52
--- /dev/null
+++ b/zhitan-vue/src/layout/components/AppMain.vue
@@ -0,0 +1,100 @@
+<template>
+  <section class="app-main">
+    <router-view v-slot="{ Component, route }">
+      <transition name="fade-transform" mode="out-in">
+        <keep-alive :include="tagsViewStore.cachedViews">
+          <component v-if="!route.meta.link" :is="Component" :key="route.path" />
+        </keep-alive>
+      </transition>
+    </router-view>
+    <iframe-toggle />
+  </section>
+</template>
+
+<script setup>
+import iframeToggle from "./IframeToggle/index"
+import useTagsViewStore from '@/store/modules/tagsView'
+
+const tagsViewStore = useTagsViewStore()
+</script>
+
+<style lang="scss" scoped>
+.themeDark {
+  .app-main {
+    /* 50= navbar  50  */
+    min-height: calc(100vh - 50px);
+    width: 100%;
+    position: relative;
+    overflow: hidden;
+    background: #110f2e;
+  }
+
+  .fixed-header + .app-main {
+    padding-top: 50px;
+  }
+
+  .hasTagsView {
+    .app-main {
+      /* 84 = navbar + tags-view = 50 + 34 */
+      min-height: calc(100vh - 120px);
+      // padding: 20px;
+    }
+
+    .fixed-header + .app-main {
+      padding-top: 84px;
+    }
+  }
+}
+
+.themeLight {
+  .app-main {
+    /* 50= navbar  50  */
+    min-height: calc(100vh - 50px);
+    width: 100%;
+    position: relative;
+    overflow: hidden;
+    background: #F7F8FA;
+  }
+
+  .fixed-header + .app-main {
+    padding-top: 50px;
+  }
+
+  .hasTagsView {
+    .app-main {
+      /* 84 = navbar + tags-view = 50 + 34 */
+      // 70+50
+      min-height: calc(100vh - 120px);
+      // padding: 20px;
+    }
+
+    .fixed-header + .app-main {
+      padding-top: 84px;
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+// fix css style bug in open el-dialog
+.el-popup-parent--hidden {
+  .fixed-header {
+    padding-right: 6px;
+  }
+}
+
+::-webkit-scrollbar {
+  width: 6px;
+  height: 6px;
+}
+
+::-webkit-scrollbar-track {
+  background-color: #f1f1f1;
+}
+
+::-webkit-scrollbar-thumb {
+  background-color: #c0c0c0;
+  border-radius: 3px;
+}
+</style>
+
diff --git a/zhitan-vue/src/layout/components/IframeToggle/index.vue b/zhitan-vue/src/layout/components/IframeToggle/index.vue
new file mode 100644
index 0000000..a407b74
--- /dev/null
+++ b/zhitan-vue/src/layout/components/IframeToggle/index.vue
@@ -0,0 +1,25 @@
+<template>
+  <inner-link
+    v-for="(item, index) in tagsViewStore.iframeViews"
+    :key="item.path"
+    :iframeId="'iframe' + index"
+    v-show="route.path === item.path"
+    :src="iframeUrl(item.meta.link, item.query)"
+  ></inner-link>
+</template>
+
+<script setup>
+import InnerLink from "../InnerLink/index";
+import useTagsViewStore from "@/store/modules/tagsView";
+
+const route = useRoute();
+const tagsViewStore = useTagsViewStore();
+
+function iframeUrl(url, query) {
+  if (Object.keys(query).length > 0) {
+    let params = Object.keys(query).map((key) => key + "=" + query[key]).join("&");
+    return url + "?" + params;
+  }
+  return url;
+}
+</script>
diff --git a/zhitan-vue/src/layout/components/InnerLink/index.vue b/zhitan-vue/src/layout/components/InnerLink/index.vue
new file mode 100644
index 0000000..53a903c
--- /dev/null
+++ b/zhitan-vue/src/layout/components/InnerLink/index.vue
@@ -0,0 +1,24 @@
+<template>
+  <div :style="'height:' + height">
+    <iframe
+      :id="iframeId"
+      style="width: 100%; height: 100%"
+      :src="src"
+      frameborder="no"
+    ></iframe>
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  src: {
+    type: String,
+    default: "/"
+  },
+  iframeId: {
+    type: String
+  }
+});
+
+const height = ref(document.documentElement.clientHeight - 94.5 + "px");
+</script>
diff --git a/zhitan-vue/src/layout/components/Navbar.vue b/zhitan-vue/src/layout/components/Navbar.vue
new file mode 100644
index 0000000..acb1951
--- /dev/null
+++ b/zhitan-vue/src/layout/components/Navbar.vue
@@ -0,0 +1,309 @@
+<template>
+  <div class="navbar">
+    <div class="navbar-left">
+      <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
+      <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" />
+      <top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
+    </div>
+
+    <div class="right-menu">
+      <!-- <template v-if="appStore.device !== 'mobile'">
+        <header-search id="header-search" class="right-menu-item" />
+
+        <screenfull id="screenfull" class="right-menu-item hover-effect" />
+
+        <el-tooltip content="甯冨眬澶у皬" effect="dark" placement="bottom">
+          <size-select id="size-select" class="right-menu-item hover-effect" />
+        </el-tooltip>
+      </template> -->
+      <!-- <el-button @click="toggleTheme">鍒囨崲</el-button> -->
+      <div class="avatar-container">
+        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
+          <div class="avatar-wrapper">
+            <img :src="userStore.avatar" class="user-avatar" />
+            <el-icon><caret-bottom /></el-icon>
+          </div>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <router-link to="/user/profile">
+                <el-dropdown-item>涓汉涓績</el-dropdown-item>
+              </router-link>
+              <el-dropdown-item command="toggleTheme">
+                <span>椋庢牸鍒囨崲</span>
+              </el-dropdown-item>
+<!--               
+              <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
+                <span>甯冨眬璁剧疆</span>
+              </el-dropdown-item> -->
+              <el-dropdown-item divided command="logout">
+                <span>閫�鍑虹櫥褰�</span>
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ElMessageBox } from 'element-plus'
+import Breadcrumb from '@/components/Breadcrumb'
+import TopNav from '@/components/TopNav'
+import Hamburger from '@/components/Hamburger'
+import Screenfull from '@/components/Screenfull'
+import SizeSelect from '@/components/SizeSelect'
+import HeaderSearch from '@/components/HeaderSearch'
+import useAppStore from '@/store/modules/app'
+import useUserStore from '@/store/modules/user'
+import useSettingsStore from '@/store/modules/settings'
+
+
+const appStore = useAppStore()
+const userStore = useUserStore()
+const settingsStore = useSettingsStore()
+
+function toggleTheme() {
+  if (settingsStore.sideTheme == 'theme-dark') {
+    settingsStore.sideTheme = 'theme-light'
+    document.querySelector('body').className = 'themeLight';
+  } else {
+    settingsStore.sideTheme = 'theme-dark'
+    document.querySelector('body').className = 'themeDark';
+  }
+
+}
+
+function toggleSideBar() {
+  appStore.toggleSideBar()
+}
+
+function handleCommand(command) {
+  switch (command) {
+    case "toggleTheme":
+      toggleTheme();
+      break;
+    case "setLayout":
+      setLayout();
+      break;
+    case "logout":
+      logout();
+      break;
+    default:
+      break;
+  }
+}
+
+function logout() {
+  ElMessageBox.confirm('纭畾娉ㄩ攢骞堕��鍑虹郴缁熷悧锛�', '鎻愮ず', {
+    confirmButtonText: '纭畾',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning'
+  }).then(() => {
+    userStore.logOut().then(() => {
+      location.href = '/index';
+    })
+  }).catch(() => { });
+}
+
+const emits = defineEmits(['setLayout'])
+function setLayout() {
+  emits('setLayout');
+}
+</script>
+
+<style lang='scss' scoped>
+.themeDark {
+  .navbar {
+    height: 70px;
+    background: #1a235d;
+    border-radius: 0px 0px 0px 0px;
+    border: 1px solid #000000;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .navbar-left {
+      display: flex;
+      align-items: center;
+    }
+
+    .hamburger-container {
+      margin-right: 6px;
+      // line-height: 46px;
+      // height: 100%;
+      // float: left;
+      cursor: pointer;
+      transition: background 0.3s;
+      -webkit-tap-highlight-color: transparent;
+
+      &:hover {
+        background: rgba(0, 0, 0, 0.025);
+      }
+    }
+
+    .breadcrumb-container {
+      float: left;
+    }
+
+    .topmenu-container {
+      position: absolute;
+      left: 50px;
+    }
+
+    .errLog-container {
+      display: inline-block;
+      vertical-align: top;
+    }
+
+    .right-menu {
+      // float: right;
+      // height: 100%;
+      // line-height: 50px;
+      display: flex;
+
+      &:focus {
+        outline: none;
+      }
+
+      .right-menu-item {
+        display: inline-block;
+        padding: 0 8px;
+        height: 100%;
+        font-size: 18px;
+        color: #5a5e66;
+        vertical-align: text-bottom;
+
+        &.hover-effect {
+          cursor: pointer;
+          transition: background 0.3s;
+
+          &:hover {
+            background: rgba(0, 0, 0, 0.025);
+          }
+        }
+      }
+
+      .avatar-container {
+        margin-right: 40px;
+
+        .avatar-wrapper {
+          margin-top: 5px;
+          position: relative;
+
+          .user-avatar {
+            cursor: pointer;
+            width: 40px;
+            height: 40px;
+            border-radius: 10px;
+          }
+
+          i {
+            cursor: pointer;
+            position: absolute;
+            right: -20px;
+            top: 25px;
+            font-size: 12px;
+          }
+        }
+      }
+    }
+  }
+  
+}
+.themeLight {
+  .navbar {
+    height: 70px;
+    background: #FEFEFE;
+    border-radius: 0px 0px 0px 0px;
+    border: 1px solid#f7f8fa;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .navbar-left {
+      display: flex;
+      align-items: center;
+    }
+
+    .hamburger-container {
+      margin-right: 6px;
+      // line-height: 46px;
+      // height: 100%;
+      // float: left;
+      cursor: pointer;
+      transition: background 0.3s;
+      -webkit-tap-highlight-color: #000;
+      color: #000;
+      &:hover {
+        background: #000;
+      }
+    }
+
+    .breadcrumb-container {
+      float: left;
+    }
+
+    .topmenu-container {
+      position: absolute;
+      left: 50px;
+    }
+
+    .errLog-container {
+      display: inline-block;
+      vertical-align: top;
+    }
+
+    .right-menu {
+      // float: right;
+      // height: 100%;
+      // line-height: 50px;
+      display: flex;
+
+      &:focus {
+        outline: none;
+      }
+
+      .right-menu-item {
+        display: inline-block;
+        padding: 0 8px;
+        height: 100%;
+        font-size: 18px;
+        color: #5a5e66;
+        vertical-align: text-bottom;
+
+        &.hover-effect {
+          cursor: pointer;
+          transition: background 0.3s;
+
+          &:hover {
+            background: rgba(0, 0, 0, 0.025);
+          }
+        }
+      }
+
+      .avatar-container {
+        margin-right: 40px;
+
+        .avatar-wrapper {
+          margin-top: 5px;
+          position: relative;
+
+          .user-avatar {
+            cursor: pointer;
+            width: 40px;
+            height: 40px;
+            border-radius: 10px;
+          }
+
+          i {
+            cursor: pointer;
+            position: absolute;
+            right: -20px;
+            top: 25px;
+            font-size: 12px;
+          }
+        }
+      }
+    }
+  }
+}
+</style>
diff --git a/zhitan-vue/src/layout/components/Settings/index.vue b/zhitan-vue/src/layout/components/Settings/index.vue
new file mode 100644
index 0000000..d0a0e88
--- /dev/null
+++ b/zhitan-vue/src/layout/components/Settings/index.vue
@@ -0,0 +1,205 @@
+<template>
+  <el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px">
+    <div class="setting-drawer-title">
+      <h3 class="drawer-title">涓婚椋庢牸璁剧疆</h3>
+    </div>
+    <div class="setting-drawer-block-checbox">
+      <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
+        <img src="@/assets/images/dark.svg" alt="dark" />
+        <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
+          <i aria-label="鍥炬爣: check" class="anticon anticon-check">
+            <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
+              <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z" />
+            </svg>
+          </i>
+        </div>
+      </div>
+      <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-light')">
+        <img src="@/assets/images/light.svg" alt="light" />
+        <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
+          <i aria-label="鍥炬爣: check" class="anticon anticon-check">
+            <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
+              <path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z" />
+            </svg>
+          </i>
+        </div>
+      </div>
+    </div>
+    <div class="drawer-item">
+      <span>涓婚棰滆壊</span>
+      <span class="comp-style">
+        <el-color-picker v-model="theme" :predefine="predefineColors" @change="themeChange"/>
+      </span>
+    </div>
+    <el-divider />
+
+    <h3 class="drawer-title">绯荤粺甯冨眬閰嶇疆</h3>
+
+    <div class="drawer-item">
+      <span>寮�鍚� TopNav</span>
+      <span class="comp-style">
+        <el-switch v-model="settingsStore.topNav" @change="topNavChange" class="drawer-switch" />
+      </span>
+    </div>
+
+    <div class="drawer-item">
+      <span>寮�鍚� Tags-Views</span>
+      <span class="comp-style">
+        <el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
+      </span>
+    </div>
+
+    <div class="drawer-item">
+      <span>鍥哄畾 Header</span>
+      <span class="comp-style">
+        <el-switch v-model="settingsStore.fixedHeader" class="drawer-switch" />
+      </span>
+    </div>
+
+    <div class="drawer-item">
+      <span>鏄剧ず Logo</span>
+      <span class="comp-style">
+        <el-switch v-model="settingsStore.sidebarLogo" class="drawer-switch" />
+      </span>
+    </div>
+
+    <div class="drawer-item">
+      <span>鍔ㄦ�佹爣棰�</span>
+      <span class="comp-style">
+        <el-switch v-model="settingsStore.dynamicTitle" class="drawer-switch" />
+      </span>
+    </div>
+
+    <el-divider />
+
+    <el-button type="primary" plain icon="DocumentAdd" @click="saveSetting">淇濆瓨閰嶇疆</el-button>
+    <el-button plain icon="Refresh" @click="resetSetting">閲嶇疆閰嶇疆</el-button>
+  </el-drawer>
+
+</template>
+
+<script setup>
+import variables from '@/assets/styles/variables.module.scss'
+import axios from 'axios'
+import { ElLoading, ElMessage } from 'element-plus'
+import { useDynamicTitle } from '@/utils/dynamicTitle'
+import useAppStore from '@/store/modules/app'
+import useSettingsStore from '@/store/modules/settings'
+import usePermissionStore from '@/store/modules/permission'
+import { handleThemeStyle } from '@/utils/theme'
+
+const { proxy } = getCurrentInstance();
+const appStore = useAppStore()
+const settingsStore = useSettingsStore()
+const permissionStore = usePermissionStore()
+const showSettings = ref(false);
+const theme = ref(settingsStore.theme);
+const sideTheme = ref(settingsStore.sideTheme);
+const storeSettings = computed(() => settingsStore);
+const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"]);
+
+/** 鏄惁闇�瑕乼opnav */
+function topNavChange(val) {
+  if (!val) {
+    appStore.toggleSideBarHide(false);
+    permissionStore.setSidebarRouters(permissionStore.defaultRoutes);
+  }
+}
+
+function themeChange(val) {
+  settingsStore.theme = val;
+  handleThemeStyle(val);
+}
+function handleTheme(val) {
+  settingsStore.sideTheme = val;
+  sideTheme.value = val;
+}
+function saveSetting() {
+  proxy.$modal.loading("姝e湪淇濆瓨鍒版湰鍦帮紝璇风◢鍊�...");
+  let layoutSetting = {
+    "topNav": storeSettings.value.topNav,
+    "tagsView": storeSettings.value.tagsView,
+    "fixedHeader": storeSettings.value.fixedHeader,
+    "sidebarLogo": storeSettings.value.sidebarLogo,
+    "dynamicTitle": storeSettings.value.dynamicTitle,
+    "sideTheme": storeSettings.value.sideTheme,
+    "theme": storeSettings.value.theme
+  };
+  localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));
+  setTimeout(proxy.$modal.closeLoading(), 1000)
+}
+function resetSetting() {
+  proxy.$modal.loading("姝e湪娓呴櫎璁剧疆缂撳瓨骞跺埛鏂帮紝璇风◢鍊�...");
+  localStorage.removeItem("layout-setting")
+  setTimeout("window.location.reload()", 1000)
+}
+function openSetting() {
+  showSettings.value = true;
+}
+
+defineExpose({
+  openSetting,
+})
+</script>
+
+<style lang='scss' scoped>
+.setting-drawer-title {
+  margin-bottom: 12px;
+  color: rgba(0, 0, 0, 0.85);
+  line-height: 22px;
+  font-weight: bold;
+  .drawer-title {
+    font-size: 14px;
+  }
+}
+.setting-drawer-block-checbox {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  margin-top: 10px;
+  margin-bottom: 20px;
+
+  .setting-drawer-block-checbox-item {
+    position: relative;
+    margin-right: 16px;
+    border-radius: 2px;
+    cursor: pointer;
+
+    img {
+      width: 48px;
+      height: 48px;
+    }
+
+    .custom-img {
+      width: 48px;
+      height: 38px;
+      border-radius: 5px;
+      box-shadow: 1px 1px 2px #898484;
+    }
+
+    .setting-drawer-block-checbox-selectIcon {
+      position: absolute;
+      top: 0;
+      right: 0;
+      width: 100%;
+      height: 100%;
+      padding-top: 15px;
+      padding-left: 24px;
+      color: #1890ff;
+      font-weight: 700;
+      font-size: 14px;
+    }
+  }
+}
+
+.drawer-item {
+  color: rgba(0, 0, 0, 0.65);
+  padding: 12px 0;
+  font-size: 14px;
+
+  .comp-style {
+    float: right;
+    margin: -3px 8px 0px 0px;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/layout/components/Sidebar/Link.vue b/zhitan-vue/src/layout/components/Sidebar/Link.vue
new file mode 100644
index 0000000..8011431
--- /dev/null
+++ b/zhitan-vue/src/layout/components/Sidebar/Link.vue
@@ -0,0 +1,40 @@
+<template>
+  <component :is="type" v-bind="linkProps()">
+    <slot />
+  </component>
+</template>
+
+<script setup>
+import { isExternal } from '@/utils/validate'
+
+const props = defineProps({
+  to: {
+    type: [String, Object],
+    required: true
+  }
+})
+
+const isExt = computed(() => {
+  return isExternal(props.to)
+})
+
+const type = computed(() => {
+  if (isExt.value) {
+    return 'a'
+  }
+  return 'router-link'
+})
+
+function linkProps() {
+  if (isExt.value) {
+    return {
+      href: props.to,
+      target: '_blank',
+      rel: 'noopener'
+    }
+  }
+  return {
+    to: props.to
+  }
+}
+</script>
diff --git a/zhitan-vue/src/layout/components/Sidebar/Logo.vue b/zhitan-vue/src/layout/components/Sidebar/Logo.vue
new file mode 100644
index 0000000..86a54a6
--- /dev/null
+++ b/zhitan-vue/src/layout/components/Sidebar/Logo.vue
@@ -0,0 +1,136 @@
+<template>
+  <div class="sidebar-logo-container" :class="{ 'collapse': collapse }"
+       :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
+    <!-- <transition name="sidebarLogoFade">
+      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo" />
+        <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }}</h1>
+      </router-link>
+      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo" />
+        <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }}</h1>
+      </router-link>
+    </transition> -->
+    <!-- <div class="logo">
+      <img v-if=" sideTheme === 'theme-dark'" :src="logo" class="sidebar-logo" />
+      <img v-else :src="logo2" class="sidebar-logo" />
+    </div> -->
+
+    <div class="name" v-if="!collapse" :style="{color: sideTheme === 'theme-dark' ? '#fff' : '#487FEE'}">{{ title }}</div>
+  </div>
+</template>
+
+<script setup>
+// import Cookies from 'js-cookie'
+import variables from '@/assets/styles/variables.module.scss'
+import logo from '@/assets/logo/logo-2.png'
+import logo2 from '@/assets/logo/logo-3.png'
+import useSettingsStore from '@/store/modules/settings'
+import { color } from 'echarts';
+// const systemInfo = JSON.parse(sessionStorage.getItem('SystemInfo'))
+defineProps({
+  collapse: {
+    type: Boolean,
+    required: true
+  }
+})
+
+const title = import.meta.env.VITE_APP_TITLE;
+const settingsStore = useSettingsStore();
+const sideTheme = computed(() => settingsStore.sideTheme);
+</script>
+
+<style lang="scss" scoped>
+.sidebarLogoFade-enter-active {
+  transition: opacity 1.5s;
+}
+
+.sidebarLogoFade-enter,
+.sidebarLogoFade-leave-to {
+  opacity: 0;
+}
+
+.sidebar-logo-container {
+  position: relative;
+  width: 100%;
+  height: 50px;
+  line-height: 50px;
+  background: #2b2f3a;
+  text-align: center;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  margin-top: 24px;
+  margin-bottom: 15px;
+  .logo {
+    width: 87px;
+    height: 30px;
+    margin-left: 13px;
+    .sidebar-logo {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  .name {
+    width: 100%;
+    margin-top: 2px;
+    margin-left: 6px;
+    font-family: OPPOSans, OPPOSans;
+    font-weight: bold;
+    font-size: 24px;
+    color: #ffffff;
+  }
+
+  // @media (min-width: 1440px) {
+  //   .name {
+  //     font-size: 18px;
+  //   }
+  // }
+
+  // @media (min-width: 1600px) {
+  //   .name {
+  //     font-size: 20px;
+  //   }
+  // }
+
+  // @media (min-width: 1800px) {
+  //   .name {
+  //     font-size: 22px;
+  //   }
+  // }
+  // @media (min-width: 1920px) {
+  //   .name {
+  //     font-size: 24px;
+  //   }
+  // }
+
+  & .sidebar-logo-link {
+    height: 100%;
+    width: 100%;
+
+    & .sidebar-logo {
+      width: 101px;
+      height: 32px;
+      vertical-align: middle;
+      margin-right: 12px;
+    }
+
+    & .sidebar-title {
+      display: inline-block;
+      margin: 0;
+      color: #fff;
+      font-weight: 600;
+      line-height: 50px;
+      font-size: 14px;
+      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
+      vertical-align: middle;
+    }
+  }
+
+  &.collapse {
+    .sidebar-logo {
+      margin-right: 0px;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/layout/components/Sidebar/SidebarItem.vue b/zhitan-vue/src/layout/components/Sidebar/SidebarItem.vue
new file mode 100644
index 0000000..3a85e7e
--- /dev/null
+++ b/zhitan-vue/src/layout/components/Sidebar/SidebarItem.vue
@@ -0,0 +1,102 @@
+<template>
+  <div v-if="!item.hidden">
+    <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
+      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
+        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
+          <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>
+          <template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template>
+        </el-menu-item>
+      </app-link>
+    </template>
+
+    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
+      <template v-if="item.meta" #title>
+        <svg-icon :icon-class="item.meta && item.meta.icon" />
+        <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
+      </template>
+
+      <sidebar-item
+        v-for="(child, index) in item.children"
+        :key="child.path + index"
+        :is-nest="true"
+        :item="child"
+        :base-path="resolvePath(child.path)"
+        class="nest-menu"
+      />
+    </el-sub-menu>
+  </div>
+</template>
+
+<script setup>
+import { isExternal } from '@/utils/validate'
+import AppLink from './Link'
+import { getNormalPath } from '@/utils/ruoyi'
+
+const props = defineProps({
+  // route object
+  item: {
+    type: Object,
+    required: true
+  },
+  isNest: {
+    type: Boolean,
+    default: false
+  },
+  basePath: {
+    type: String,
+    default: ''
+  }
+})
+
+const onlyOneChild = ref({});
+
+function hasOneShowingChild(children = [], parent) {
+  if (!children) {
+    children = [];
+  }
+  const showingChildren = children.filter(item => {
+    if (item.hidden) {
+      return false
+    } else {
+      // Temp set(will be used if only has one showing child)
+      onlyOneChild.value = item
+      return true
+    }
+  })
+
+  // When there is only one child router, the child router is displayed by default
+  if (showingChildren.length === 1) {
+    return true
+  }
+
+  // Show parent if there are no child router to display
+  if (showingChildren.length === 0) {
+    onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
+    return true
+  }
+
+  return false
+};
+
+function resolvePath(routePath, routeQuery) {
+  if (isExternal(routePath)) {
+    return routePath
+  }
+  if (isExternal(props.basePath)) {
+    return props.basePath
+  }
+  if (routeQuery) {
+    let query = JSON.parse(routeQuery);
+    return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
+  }
+  return getNormalPath(props.basePath + '/' + routePath)
+}
+
+function hasTitle(title){
+  if (title.length > 5) {
+    return title;
+  } else {
+    return "";
+  }
+}
+</script>
diff --git a/zhitan-vue/src/layout/components/Sidebar/index.vue b/zhitan-vue/src/layout/components/Sidebar/index.vue
new file mode 100644
index 0000000..ce46e57
--- /dev/null
+++ b/zhitan-vue/src/layout/components/Sidebar/index.vue
@@ -0,0 +1,58 @@
+<template>
+  <div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
+    <logo v-if="showLogo" :collapse="isCollapse" />
+    <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
+      <el-menu
+        :default-active="activeMenu"
+        :collapse="isCollapse"
+        :background-color="sideTheme === 'theme-dark' ? '#1A235D' : '#fff'"
+        :text-color="sideTheme === 'theme-dark' ? '#fff' : '#000'"
+        :unique-opened="true"
+        :active-text-color="theme"
+        :collapse-transition="false"
+        mode="vertical"
+      >
+        <sidebar-item
+          v-for="(route, index) in sidebarRouters"
+          :key="route.path + index"
+          :item="route"
+          :base-path="route.path"
+        />
+      </el-menu>
+    </el-scrollbar>
+  </div>
+</template>
+
+<script setup>
+import Logo from './Logo'
+import SidebarItem from './SidebarItem'
+import variables from '@/assets/styles/variables.module.scss'
+import useAppStore from '@/store/modules/app'
+import useSettingsStore from '@/store/modules/settings'
+import usePermissionStore from '@/store/modules/permission'
+
+const route = useRoute();
+const appStore = useAppStore()
+const settingsStore = useSettingsStore()
+const permissionStore = usePermissionStore()
+
+const sidebarRouters =  computed(() => permissionStore.sidebarRouters);
+const showLogo = computed(() => settingsStore.sidebarLogo);
+const sideTheme = computed(() => settingsStore.sideTheme);
+const theme = computed(() => settingsStore.theme);
+const isCollapse = computed(() => !appStore.sidebar.opened);
+
+const activeMenu = computed(() => {
+  const { meta, path } = route;
+  // if set path, the sidebar will highlight the path you set
+  if (meta.activeMenu) {
+    return meta.activeMenu;
+  }
+  return path;
+})
+
+</script>
+<style lang="scss" scoped>
+
+
+</style>
diff --git a/zhitan-vue/src/layout/components/TagsView/ScrollPane.vue b/zhitan-vue/src/layout/components/TagsView/ScrollPane.vue
new file mode 100644
index 0000000..516b5d2
--- /dev/null
+++ b/zhitan-vue/src/layout/components/TagsView/ScrollPane.vue
@@ -0,0 +1,105 @@
+<template>
+  <el-scrollbar
+    ref="scrollContainer"
+    :vertical="false"
+    class="scroll-container"
+    @wheel.prevent="handleScroll"
+  >
+    <slot />
+  </el-scrollbar>
+</template>
+
+<script setup>
+import useTagsViewStore from '@/store/modules/tagsView'
+
+const tagAndTagSpacing = ref(4);
+const { proxy } = getCurrentInstance();
+
+const scrollWrapper = computed(() => proxy.$refs.scrollContainer.$refs.wrapRef);
+
+onMounted(() => {
+  scrollWrapper.value.addEventListener('scroll', emitScroll, true)
+})
+onBeforeUnmount(() => {
+  scrollWrapper.value.removeEventListener('scroll', emitScroll)
+})
+
+function handleScroll(e) {
+  const eventDelta = e.wheelDelta || -e.deltaY * 40
+  const $scrollWrapper = scrollWrapper.value;
+  $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
+}
+const emits = defineEmits()
+const emitScroll = () => {
+  emits('scroll')
+}
+
+const tagsViewStore = useTagsViewStore()
+const visitedViews = computed(() => tagsViewStore.visitedViews);
+
+function moveToTarget(currentTag) {
+  const $container = proxy.$refs.scrollContainer.$el
+  const $containerWidth = $container.offsetWidth
+  const $scrollWrapper = scrollWrapper.value;
+
+  let firstTag = null
+  let lastTag = null
+
+  // find first tag and last tag
+  if (visitedViews.value.length > 0) {
+    firstTag = visitedViews.value[0]
+    lastTag = visitedViews.value[visitedViews.value.length - 1]
+  }
+
+  if (firstTag === currentTag) {
+    $scrollWrapper.scrollLeft = 0
+  } else if (lastTag === currentTag) {
+    $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
+  } else {
+    const tagListDom = document.getElementsByClassName('tags-view-item');
+    const currentIndex = visitedViews.value.findIndex(item => item === currentTag)
+    let prevTag = null
+    let nextTag = null
+    for (const k in tagListDom) {
+      if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
+        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1].path) {
+          prevTag = tagListDom[k];
+        }
+        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1].path) {
+          nextTag = tagListDom[k];
+        }
+      }
+    }
+
+    // the tag's offsetLeft after of nextTag
+    const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value
+
+    // the tag's offsetLeft before of prevTag
+    const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value
+    if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
+      $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
+    } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
+      $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
+    }
+  }
+}
+
+defineExpose({
+  moveToTarget,
+})
+</script>
+
+<style lang='scss' scoped>
+.scroll-container {
+  white-space: nowrap;
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+  :deep(.el-scrollbar__bar) {
+    bottom: 0px;
+  }
+  :deep(.el-scrollbar__wrap) {
+    height: 39px;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/layout/components/TagsView/index.vue b/zhitan-vue/src/layout/components/TagsView/index.vue
new file mode 100644
index 0000000..02baf0f
--- /dev/null
+++ b/zhitan-vue/src/layout/components/TagsView/index.vue
@@ -0,0 +1,408 @@
+<template>
+  <div id="tags-view-container" class="tags-view-container">
+    <scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
+      <router-link v-for="tag in visitedViews" :key="tag.path" :data-path="tag.path" :class="isActive(tag) ? 'active' : ''"
+                   :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" class="tags-view-item" :style="activeStyle(tag)"
+                   @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''" @contextmenu.prevent="openMenu(tag, $event)">
+        {{ tag.title }}
+        <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
+          <close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
+        </span>
+      </router-link>
+    </scroll-pane>
+    <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
+      <li @click="refreshSelectedTag(selectedTag)">
+        <refresh-right style="width: 1em; height: 1em;" /> 鍒锋柊椤甸潰
+      </li>
+      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
+        <close style="width: 1em; height: 1em;" /> 鍏抽棴褰撳墠
+      </li>
+      <li @click="closeOthersTags">
+        <circle-close style="width: 1em; height: 1em;" /> 鍏抽棴鍏朵粬
+      </li>
+      <li v-if="!isFirstView()" @click="closeLeftTags">
+        <back style="width: 1em; height: 1em;" /> 鍏抽棴宸︿晶
+      </li>
+      <li v-if="!isLastView()" @click="closeRightTags">
+        <right style="width: 1em; height: 1em;" /> 鍏抽棴鍙充晶
+      </li>
+      <li @click="closeAllTags(selectedTag)">
+        <circle-close style="width: 1em; height: 1em;" /> 鍏ㄩ儴鍏抽棴
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script setup>
+import ScrollPane from './ScrollPane'
+import { getNormalPath } from '@/utils/ruoyi'
+import useTagsViewStore from '@/store/modules/tagsView'
+import useSettingsStore from '@/store/modules/settings'
+import usePermissionStore from '@/store/modules/permission'
+
+const visible = ref(false);
+const top = ref(0);
+const left = ref(0);
+const selectedTag = ref({});
+const affixTags = ref([]);
+const scrollPaneRef = ref(null);
+
+const { proxy } = getCurrentInstance();
+const route = useRoute();
+const router = useRouter();
+
+const visitedViews = computed(() => useTagsViewStore().visitedViews);
+const routes = computed(() => usePermissionStore().routes);
+const theme = computed(() => useSettingsStore().theme);
+
+watch(route, () => {
+  addTags()
+  moveToCurrentTag()
+})
+watch(visible, (value) => {
+  if (value) {
+    document.body.addEventListener('click', closeMenu)
+  } else {
+    document.body.removeEventListener('click', closeMenu)
+  }
+})
+onMounted(() => {
+  initTags()
+  addTags()
+})
+
+function isActive(r) {
+  return r.path === route.path
+}
+function activeStyle(tag) {
+  if (!isActive(tag)) return {};
+  return {
+    "background-color": theme.value,
+    "border-color": theme.value
+  };
+}
+function isAffix(tag) {
+  return tag.meta && tag.meta.affix
+}
+function isFirstView() {
+  try {
+    return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath
+  } catch (err) {
+    return false
+  }
+}
+function isLastView() {
+  try {
+    return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
+  } catch (err) {
+    return false
+  }
+}
+function filterAffixTags(routes, basePath = '') {
+  let tags = []
+  routes.forEach(route => {
+    if (route.meta && route.meta.affix) {
+      const tagPath = getNormalPath(basePath + '/' + route.path)
+      tags.push({
+        fullPath: tagPath,
+        path: tagPath,
+        name: route.name,
+        meta: { ...route.meta }
+      })
+    }
+    if (route.children) {
+      const tempTags = filterAffixTags(route.children, route.path)
+      if (tempTags.length >= 1) {
+        tags = [...tags, ...tempTags]
+      }
+    }
+  })
+  return tags
+}
+function initTags() {
+  const res = filterAffixTags(routes.value);
+  affixTags.value = res;
+  for (const tag of res) {
+    // Must have tag name
+    if (tag.name) {
+      useTagsViewStore().addVisitedView(tag)
+    }
+  }
+}
+function addTags() {
+  const { name } = route
+  if (name) {
+    useTagsViewStore().addView(route)
+    if (route.meta.link) {
+      useTagsViewStore().addIframeView(route);
+    }
+  }
+  return false
+}
+function moveToCurrentTag() {
+  nextTick(() => {
+    for (const r of visitedViews.value) {
+      if (r.path === route.path) {
+        scrollPaneRef.value.moveToTarget(r);
+        // when query is different then update
+        if (r.fullPath !== route.fullPath) {
+          useTagsViewStore().updateVisitedView(route)
+        }
+      }
+    }
+  })
+}
+function refreshSelectedTag(view) {
+  proxy.$tab.refreshPage(view);
+  if (route.meta.link) {
+    useTagsViewStore().delIframeView(route);
+  }
+}
+function closeSelectedTag(view) {
+  proxy.$tab.closePage(view).then(({ visitedViews }) => {
+    if (isActive(view)) {
+      toLastView(visitedViews, view)
+    }
+  })
+}
+function closeRightTags() {
+  proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
+    if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
+      toLastView(visitedViews)
+    }
+  })
+}
+function closeLeftTags() {
+  proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
+    if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
+      toLastView(visitedViews)
+    }
+  })
+}
+function closeOthersTags() {
+  router.push(selectedTag.value).catch(() => { });
+  proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
+    moveToCurrentTag()
+  })
+}
+function closeAllTags(view) {
+  proxy.$tab.closeAllPage().then(({ visitedViews }) => {
+    if (affixTags.value.some(tag => tag.path === route.path)) {
+      return
+    }
+    toLastView(visitedViews, view)
+  })
+}
+function toLastView(visitedViews, view) {
+  const latestView = visitedViews.slice(-1)[0]
+  if (latestView) {
+    router.push(latestView.fullPath)
+  } else {
+    // now the default is to redirect to the home page if there is no tags-view,
+    // you can adjust it according to your needs.
+    if (view.name === 'Dashboard') {
+      // to reload home page
+      router.replace({ path: '/redirect' + view.fullPath })
+    } else {
+      router.push('/')
+    }
+  }
+}
+function openMenu(tag, e) {
+  const menuMinWidth = 105
+  const offsetLeft = proxy.$el.getBoundingClientRect().left // container margin left
+  const offsetWidth = proxy.$el.offsetWidth // container width
+  const maxLeft = offsetWidth - menuMinWidth // left boundary
+  const l = e.clientX - offsetLeft + 15 // 15: margin right
+
+  if (l > maxLeft) {
+    left.value = maxLeft
+  } else {
+    left.value = l
+  }
+
+  top.value = e.clientY
+  visible.value = true
+  selectedTag.value = tag
+}
+function closeMenu() {
+  visible.value = false
+}
+function handleScroll() {
+  closeMenu()
+}
+</script>
+
+<style lang='scss' scoped>
+.themeDark {
+  .tags-view-container {
+    height: 50px;
+    width: 100%;
+    background: #1a235d;
+    // border-bottom: 1px solid #d8dce5;
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
+    .tags-view-wrapper {
+      .tags-view-item {
+        display: inline-block;
+        position: relative;
+        cursor: pointer;
+        height: 34px;
+        line-height: 34px;
+        border: 1px solid #203a81;
+        color: #c1c1c1;
+        background: #3271eb;
+        padding: 0 8px;
+        font-size: 15px;
+        margin-left: 5px;
+        margin-top: 6px;
+        border-radius: 5px;
+        font-family: OPPOSans, OPPOSans;
+        &:first-of-type {
+          margin-left: 15px;
+        }
+        &:last-of-type {
+          margin-right: 15px;
+        }
+        &.active {
+          background-color: #42b983;
+          color: #fff;
+          border-color: #42b983;
+          &::before {
+            content: "";
+            background: #fff;
+            display: inline-block;
+            width: 8px;
+            height: 8px;
+            border-radius: 50%;
+            position: relative;
+            margin-right: 5px;
+          }
+        }
+      }
+    }
+    .contextmenu {
+      margin: 0;
+      background: #fff;
+      z-index: 3000;
+      position: absolute;
+      list-style-type: none;
+      padding: 5px 0;
+      border-radius: 4px;
+      font-size: 12px;
+      font-weight: 400;
+      color: #333;
+      box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
+      li {
+        margin: 0;
+        padding: 7px 16px;
+        cursor: pointer;
+        &:hover {
+          background: #eee;
+        }
+      }
+    }
+  }
+}
+
+.themeLight {
+  .tags-view-container {
+    height: 50px;
+    width: 100%;
+    background: #fff;
+    // border-bottom: 1px solid #d8dce5;
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
+    .tags-view-wrapper {
+      .tags-view-item {
+        display: inline-block;
+        position: relative;
+        cursor: pointer;
+        height: 34px;
+        line-height: 34px;
+        border: 1px solid #d8dce5;
+        color: #495060;
+        background: #fff;
+        padding: 0 8px;
+        font-size: 15px;
+        margin-left: 5px;
+        margin-top: 6px;
+        border-radius: 5px;
+        font-family: OPPOSans, OPPOSans;
+        &:first-of-type {
+          margin-left: 15px;
+        }
+        &:last-of-type {
+          margin-right: 15px;
+        }
+        &.active {
+          background-color: #42b983;
+          color: #fff;
+          border-color: #42b983;
+          &::before {
+            content: "";
+            background: #fff;
+            display: inline-block;
+            width: 8px;
+            height: 8px;
+            border-radius: 50%;
+            position: relative;
+            margin-right: 5px;
+          }
+        }
+      }
+    }
+    .contextmenu {
+      margin: 0;
+      background: #fff;
+      z-index: 3000;
+      position: absolute;
+      list-style-type: none;
+      padding: 5px 0;
+      border-radius: 4px;
+      font-size: 12px;
+      font-weight: 400;
+      color: #333;
+      box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
+      li {
+        margin: 0;
+        padding: 7px 16px;
+        cursor: pointer;
+        &:hover {
+          background: #eee;
+        }
+      }
+    }
+  }
+}
+</style>
+
+<style lang="scss">
+//reset element css of el-icon-close
+.tags-view-wrapper {
+  .tags-view-item {
+    .el-icon-close {
+      width: 16px;
+      height: 16px;
+      vertical-align: 2px;
+      border-radius: 50%;
+      text-align: center;
+      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+      transform-origin: 100% 50%;
+      &:before {
+        transform: scale(0.6);
+        display: inline-block;
+        vertical-align: -3px;
+      }
+      &:hover {
+        background-color: #b4bccc;
+        color: #fff;
+        width: 12px !important;
+        height: 12px !important;
+      }
+    }
+  }
+}
+
+.scroll-container .el-scrollbar__wrap {
+  height: 50px !important;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/layout/components/index.js b/zhitan-vue/src/layout/components/index.js
new file mode 100644
index 0000000..fd57731
--- /dev/null
+++ b/zhitan-vue/src/layout/components/index.js
@@ -0,0 +1,4 @@
+export { default as AppMain } from './AppMain'
+export { default as Navbar } from './Navbar'
+export { default as Settings } from './Settings'
+export { default as TagsView } from './TagsView/index.vue'
diff --git a/zhitan-vue/src/layout/index.vue b/zhitan-vue/src/layout/index.vue
new file mode 100644
index 0000000..444d714
--- /dev/null
+++ b/zhitan-vue/src/layout/index.vue
@@ -0,0 +1,111 @@
+<template>
+  <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
+    <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
+    <sidebar v-if="!sidebar.hide" class="sidebar-container" />
+    <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
+      <div :class="{ 'fixed-header': fixedHeader }">
+        <navbar @setLayout="setLayout" v-if="!sidebar.hide"/>
+        <tags-view v-if="needTagsView" v-show="!sidebar.hide"/>
+      </div>
+      <app-main />
+      <settings ref="settingRef" />
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { useWindowSize } from '@vueuse/core'
+import Sidebar from './components/Sidebar/index.vue'
+import { AppMain, Navbar, Settings, TagsView } from './components'
+import defaultSettings from '@/settings'
+
+import useAppStore from '@/store/modules/app'
+import useSettingsStore from '@/store/modules/settings'
+
+const settingsStore = useSettingsStore()
+const theme = computed(() => settingsStore.theme);
+const sideTheme = computed(() => settingsStore.sideTheme);
+const sidebar = computed(() => useAppStore().sidebar);
+const device = computed(() => useAppStore().device);
+const needTagsView = computed(() => settingsStore.tagsView);
+const fixedHeader = computed(() => settingsStore.fixedHeader);
+
+const classObj = computed(() => ({
+  hideSidebar: !sidebar.value.opened,
+  openSidebar: sidebar.value.opened,
+  withoutAnimation: sidebar.value.withoutAnimation,
+  mobile: device.value === 'mobile'
+}))
+
+const { width, height } = useWindowSize();
+const WIDTH = 992; // refer to Bootstrap's responsive design
+
+watchEffect(() => {
+  if (device.value === 'mobile' && sidebar.value.opened) {
+    useAppStore().closeSideBar({ withoutAnimation: false })
+  }
+  if (width.value - 1 < WIDTH) {
+    useAppStore().toggleDevice('mobile')
+    useAppStore().closeSideBar({ withoutAnimation: true })
+  } else {
+    useAppStore().toggleDevice('desktop')
+  }
+})
+
+function handleClickOutside() {
+  useAppStore().closeSideBar({ withoutAnimation: false })
+}
+
+const settingRef = ref(null);
+function setLayout() {
+  settingRef.value.openSetting();
+}
+</script>
+
+<style lang="scss" scoped>
+  @import "@/assets/styles/mixin.scss";
+  @import "@/assets/styles/variables.module.scss";
+
+.app-wrapper {
+  @include clearfix;
+  position: relative;
+  height: 100%;
+  width: 100%;
+
+  &.mobile.openSidebar {
+    position: fixed;
+    top: 0;
+  }
+}
+
+.drawer-bg {
+  background: #000;
+  opacity: 0.3;
+  width: 100%;
+  top: 0;
+  height: 100%;
+  position: absolute;
+  z-index: 999;
+}
+
+.fixed-header {
+  position: fixed;
+  top: 0;
+  right: 0;
+  z-index: 9;
+  width: calc(100% - #{$base-sidebar-width});
+  transition: width 0.28s;
+}
+
+.hideSidebar .fixed-header {
+  width: calc(100% - 54px);
+}
+
+.sidebarHide .fixed-header {
+  width: 100%;
+}
+
+.mobile .fixed-header {
+  width: 100%;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/layout/mixin/ResizeHandler.js b/zhitan-vue/src/layout/mixin/ResizeHandler.js
new file mode 100644
index 0000000..e8d0df8
--- /dev/null
+++ b/zhitan-vue/src/layout/mixin/ResizeHandler.js
@@ -0,0 +1,45 @@
+import store from '@/store'
+
+const { body } = document
+const WIDTH = 992 // refer to Bootstrap's responsive design
+
+export default {
+  watch: {
+    $route(route) {
+      if (this.device === 'mobile' && this.sidebar.opened) {
+        store.dispatch('app/closeSideBar', { withoutAnimation: false })
+      }
+    }
+  },
+  beforeMount() {
+    window.addEventListener('resize', this.$_resizeHandler)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.$_resizeHandler)
+  },
+  mounted() {
+    const isMobile = this.$_isMobile()
+    if (isMobile) {
+      store.dispatch('app/toggleDevice', 'mobile')
+      store.dispatch('app/closeSideBar', { withoutAnimation: true })
+    }
+  },
+  methods: {
+    // use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    $_isMobile() {
+      const rect = body.getBoundingClientRect()
+      return rect.width - 1 < WIDTH
+    },
+    $_resizeHandler() {
+      if (!document.hidden) {
+        const isMobile = this.$_isMobile()
+        store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
+
+        if (isMobile) {
+          store.dispatch('app/closeSideBar', { withoutAnimation: true })
+        }
+      }
+    }
+  }
+}
diff --git a/zhitan-vue/src/layout/mixin/getHeight.js b/zhitan-vue/src/layout/mixin/getHeight.js
new file mode 100644
index 0000000..59417fc
--- /dev/null
+++ b/zhitan-vue/src/layout/mixin/getHeight.js
@@ -0,0 +1,24 @@
+/**
+ * 鎶樼嚎鍥剧殑鑷�傚簲
+ * 褰撶獥鍙e彂鐢熷彉鍖栫殑鏃跺�� 浼氶噸鏂扮殑鏇存柊瑙嗗浘鐨勪綅缃� 杈惧埌鑷�傚簲
+ */
+export default {
+    data() {
+        return {
+            bodyStyle: {
+                height: '',
+                overflow: 'auto'
+            }
+        }
+    },
+    mounted() {
+        this.setCharts();
+        window.addEventListener('resize', this.setCharts);
+    },
+    beforeDestroy() {
+        window.removeEventListener('resize', this.setCharts);
+    },
+    methods: {
+
+    }
+};
diff --git a/zhitan-vue/src/main.js b/zhitan-vue/src/main.js
new file mode 100644
index 0000000..4c25d88
--- /dev/null
+++ b/zhitan-vue/src/main.js
@@ -0,0 +1,95 @@
+import { createApp } from 'vue'
+
+import Cookies from 'js-cookie'
+import dayjs from 'dayjs';
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import locale from 'element-plus/es/locale/lang/zh-cn'
+import '@/assets/fonts/YouSheBiaoTiHei.scss'
+import '@/assets/styles/index.scss' // global css
+
+import App from './App'
+import store from './store'
+import router from './router'
+import directive from './directive' // directive
+
+// 娉ㄥ唽鎸囦护
+import plugins from './plugins' // plugins
+import { download } from '@/utils/request'
+
+// svg鍥炬爣
+import 'virtual:svg-icons-register'
+import SvgIcon from '@/components/SvgIcon'
+import elementIcons from '@/components/SvgIcon/svgicon'
+
+import './permission' // permission control
+
+import { useDict } from '@/utils/dict'
+import { parseTime, resetForm, addDateRange, handleTree, selectDictLabel, selectDictLabels } from '@/utils/ruoyi'
+
+// 鍒嗛〉缁勪欢
+import Pagination from '@/components/Pagination'
+// 鑷畾涔夎〃鏍煎伐鍏风粍浠�
+import RightToolbar from '@/components/RightToolbar'
+// 瀵屾枃鏈粍浠�
+import Editor from "@/components/Editor"
+// 鏂囦欢涓婁紶缁勪欢
+import FileUpload from "@/components/FileUpload"
+// 鍥剧墖涓婁紶缁勪欢
+import ImageUpload from "@/components/ImageUpload"
+// 鍥剧墖棰勮缁勪欢
+import ImagePreview from "@/components/ImagePreview"
+// 鑷畾涔夋爲閫夋嫨缁勪欢
+import TreeSelect from '@/components/TreeSelect'
+// 瀛楀吀鏍囩缁勪欢
+import DictTag from '@/components/DictTag'
+// 鍗$墖
+import BaseCard from '@/components/BaseCard'
+// 澶у睆鍗$墖
+import BaseCardBaseCard from '@/components/BaseCard/BaseCard'
+// 宸︿晶鏍�
+import LeftTree from '@/components/LeftTree'
+// 澶撮儴
+import HeaderCard from '@/components/HeaderCard'
+const app = createApp(App)
+// 鍏ㄥ眬鏂规硶鎸傝浇
+app.config.globalProperties.dayjs = dayjs;
+app.config.globalProperties.useDict = useDict
+app.config.globalProperties.download = download
+app.config.globalProperties.parseTime = parseTime
+app.config.globalProperties.resetForm = resetForm
+app.config.globalProperties.handleTree = handleTree
+app.config.globalProperties.addDateRange = addDateRange
+app.config.globalProperties.selectDictLabel = selectDictLabel
+app.config.globalProperties.selectDictLabels = selectDictLabels
+
+// 鍏ㄥ眬缁勪欢鎸傝浇
+app.component('DictTag', DictTag)
+app.component('Pagination', Pagination)
+app.component('TreeSelect', TreeSelect)
+app.component('FileUpload', FileUpload)
+app.component('ImageUpload', ImageUpload)
+app.component('ImagePreview', ImagePreview)
+app.component('RightToolbar', RightToolbar)
+app.component('Editor', Editor)
+app.component('BaseCard', BaseCard)
+app.component('BaseCardBaseCard', BaseCardBaseCard)
+app.component('HeaderCard', HeaderCard)
+app.component('LeftTree', LeftTree)
+
+app.use(router)
+app.use(store)
+app.use(plugins)
+app.use(elementIcons)
+app.component('svg-icon', SvgIcon)
+
+directive(app)
+
+// 浣跨敤element-plus 骞朵笖璁剧疆鍏ㄥ眬鐨勫ぇ灏�
+app.use(ElementPlus, {
+  locale: locale,
+  // 鏀寔 large銆乨efault銆乻mall
+  size: sessionStorage.getItem('size') || 'default'
+})
+
+app.mount('#app')
diff --git a/zhitan-vue/src/permission.js b/zhitan-vue/src/permission.js
new file mode 100644
index 0000000..30b428a
--- /dev/null
+++ b/zhitan-vue/src/permission.js
@@ -0,0 +1,65 @@
+import router from './router'
+import { ElMessage } from 'element-plus'
+import NProgress from 'nprogress'
+import 'nprogress/nprogress.css'
+import { getToken } from '@/utils/auth'
+import { isHttp } from '@/utils/validate'
+import { isRelogin } from '@/utils/request'
+import useUserStore from '@/store/modules/user'
+import useSettingsStore from '@/store/modules/settings'
+import usePermissionStore from '@/store/modules/permission'
+
+NProgress.configure({ showSpinner: false });
+
+const whiteList = ['/login', '/register', '/energy']
+
+router.beforeEach((to, from, next) => {
+  NProgress.start()
+  if (getToken()) {
+    to.meta.title && useSettingsStore().setTitle(to.meta.title)
+    /* has token*/
+    if (to.path === '/login') {
+      next({ path: '/' })
+      NProgress.done()
+    } else if (whiteList.indexOf(to.path) !== -1) {
+      next()
+    } else {
+      if (useUserStore().roles.length === 0) {
+        isRelogin.show = true
+        // 鍒ゆ柇褰撳墠鐢ㄦ埛鏄惁宸叉媺鍙栧畬user_info淇℃伅
+        useUserStore().getInfo().then(() => {
+          isRelogin.show = false
+          usePermissionStore().generateRoutes().then(accessRoutes => {
+            // 鏍规嵁roles鏉冮檺鐢熸垚鍙闂殑璺敱琛�
+            accessRoutes.forEach(route => {
+              if (!isHttp(route.path)) {
+                router.addRoute(route) // 鍔ㄦ�佹坊鍔犲彲璁块棶璺敱琛�
+              }
+            })
+            next({ ...to, replace: true }) // hack鏂规硶 纭繚addRoutes宸插畬鎴�
+          })
+        }).catch(err => {
+          useUserStore().logOut().then(() => {
+            ElMessage.error(err)
+            next({ path: '/' })
+          })
+        })
+      } else {
+        next()
+      }
+    }
+  } else {
+    // 娌℃湁token
+    if (whiteList.indexOf(to.path) !== -1) {
+      // 鍦ㄥ厤鐧诲綍鐧藉悕鍗曪紝鐩存帴杩涘叆
+      next()
+    } else {
+      next(`/login?redirect=${to.fullPath}`) // 鍚﹀垯鍏ㄩ儴閲嶅畾鍚戝埌鐧诲綍椤�
+      NProgress.done()
+    }
+  }
+})
+
+router.afterEach(() => {
+  NProgress.done()
+})
diff --git a/zhitan-vue/src/plugins/auth.js b/zhitan-vue/src/plugins/auth.js
new file mode 100644
index 0000000..5e8c28d
--- /dev/null
+++ b/zhitan-vue/src/plugins/auth.js
@@ -0,0 +1,60 @@
+import useUserStore from '@/store/modules/user'
+
+function authPermission(permission) {
+  const all_permission = "*:*:*";
+  const permissions = useUserStore().permissions
+  if (permission && permission.length > 0) {
+    return permissions.some(v => {
+      return all_permission === v || v === permission
+    })
+  } else {
+    return false
+  }
+}
+
+function authRole(role) {
+  const super_admin = "admin";
+  const roles = useUserStore().roles
+  if (role && role.length > 0) {
+    return roles.some(v => {
+      return super_admin === v || v === role
+    })
+  } else {
+    return false
+  }
+}
+
+export default {
+  // 楠岃瘉鐢ㄦ埛鏄惁鍏峰鏌愭潈闄�
+  hasPermi(permission) {
+    return authPermission(permission);
+  },
+  // 楠岃瘉鐢ㄦ埛鏄惁鍚湁鎸囧畾鏉冮檺锛屽彧闇�鍖呭惈鍏朵腑涓�涓�
+  hasPermiOr(permissions) {
+    return permissions.some(item => {
+      return authPermission(item)
+    })
+  },
+  // 楠岃瘉鐢ㄦ埛鏄惁鍚湁鎸囧畾鏉冮檺锛屽繀椤诲叏閮ㄦ嫢鏈�
+  hasPermiAnd(permissions) {
+    return permissions.every(item => {
+      return authPermission(item)
+    })
+  },
+  // 楠岃瘉鐢ㄦ埛鏄惁鍏峰鏌愯鑹�
+  hasRole(role) {
+    return authRole(role);
+  },
+  // 楠岃瘉鐢ㄦ埛鏄惁鍚湁鎸囧畾瑙掕壊锛屽彧闇�鍖呭惈鍏朵腑涓�涓�
+  hasRoleOr(roles) {
+    return roles.some(item => {
+      return authRole(item)
+    })
+  },
+  // 楠岃瘉鐢ㄦ埛鏄惁鍚湁鎸囧畾瑙掕壊锛屽繀椤诲叏閮ㄦ嫢鏈�
+  hasRoleAnd(roles) {
+    return roles.every(item => {
+      return authRole(item)
+    })
+  }
+}
diff --git a/zhitan-vue/src/plugins/cache.js b/zhitan-vue/src/plugins/cache.js
new file mode 100644
index 0000000..6b5c00b
--- /dev/null
+++ b/zhitan-vue/src/plugins/cache.js
@@ -0,0 +1,77 @@
+const sessionCache = {
+  set (key, value) {
+    if (!sessionStorage) {
+      return
+    }
+    if (key != null && value != null) {
+      sessionStorage.setItem(key, value)
+    }
+  },
+  get (key) {
+    if (!sessionStorage) {
+      return null
+    }
+    if (key == null) {
+      return null
+    }
+    return sessionStorage.getItem(key)
+  },
+  setJSON (key, jsonValue) {
+    if (jsonValue != null) {
+      this.set(key, JSON.stringify(jsonValue))
+    }
+  },
+  getJSON (key) {
+    const value = this.get(key)
+    if (value != null) {
+      return JSON.parse(value)
+    }
+  },
+  remove (key) {
+    sessionStorage.removeItem(key);
+  }
+}
+const localCache = {
+  set (key, value) {
+    if (!localStorage) {
+      return
+    }
+    if (key != null && value != null) {
+      localStorage.setItem(key, value)
+    }
+  },
+  get (key) {
+    if (!localStorage) {
+      return null
+    }
+    if (key == null) {
+      return null
+    }
+    return localStorage.getItem(key)
+  },
+  setJSON (key, jsonValue) {
+    if (jsonValue != null) {
+      this.set(key, JSON.stringify(jsonValue))
+    }
+  },
+  getJSON (key) {
+    const value = this.get(key)
+    if (value != null) {
+      return JSON.parse(value)
+    }
+  },
+  remove (key) {
+    localStorage.removeItem(key);
+  }
+}
+
+export default {
+  /**
+   * 浼氳瘽绾х紦瀛�
+   */
+  session: sessionCache,
+  /**
+   * 鏈湴缂撳瓨
+   */
+  local: localCache
+}
diff --git a/zhitan-vue/src/plugins/download.js b/zhitan-vue/src/plugins/download.js
new file mode 100644
index 0000000..1a89efd
--- /dev/null
+++ b/zhitan-vue/src/plugins/download.js
@@ -0,0 +1,79 @@
+锘縤mport axios from 'axios'
+import { ElLoading, ElMessage } from 'element-plus'
+import { saveAs } from 'file-saver'
+import { getToken } from '@/utils/auth'
+import errorCode from '@/utils/errorCode'
+import { blobValidate } from '@/utils/ruoyi'
+
+const baseURL = import.meta.env.VITE_APP_BASE_API
+let downloadLoadingInstance;
+
+export default {
+  name(name, isDelete = true) {
+    var url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete
+    axios({
+      method: 'get',
+      url: url,
+      responseType: 'blob',
+      headers: { 'Authorization': 'Bearer ' + getToken() }
+    }).then((res) => {
+      const isBlob = blobValidate(res.data);
+      if (isBlob) {
+        const blob = new Blob([res.data])
+        this.saveAs(blob, decodeURIComponent(res.headers['download-filename']))
+      } else {
+        this.printErrMsg(res.data);
+      }
+    })
+  },
+  resource(resource) {
+    var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource);
+    axios({
+      method: 'get',
+      url: url,
+      responseType: 'blob',
+      headers: { 'Authorization': 'Bearer ' + getToken() }
+    }).then((res) => {
+      const isBlob = blobValidate(res.data);
+      if (isBlob) {
+        const blob = new Blob([res.data])
+        this.saveAs(blob, decodeURIComponent(res.headers['download-filename']))
+      } else {
+        this.printErrMsg(res.data);
+      }
+    })
+  },
+  zip(url, name) {
+    var url = baseURL + url
+    downloadLoadingInstance = ElLoading.service({ text: "姝e湪涓嬭浇鏁版嵁锛岃绋嶅��", background: "rgba(0, 0, 0, 0.7)", })
+    axios({
+      method: 'get',
+      url: url,
+      responseType: 'blob',
+      headers: { 'Authorization': 'Bearer ' + getToken() }
+    }).then((res) => {
+      const isBlob = blobValidate(res.data);
+      if (isBlob) {
+        const blob = new Blob([res.data], { type: 'application/zip' })
+        this.saveAs(blob, name)
+      } else {
+        this.printErrMsg(res.data);
+      }
+      downloadLoadingInstance.close();
+    }).catch((r) => {
+      console.error(r)
+      ElMessage.error('涓嬭浇鏂囦欢鍑虹幇閿欒锛岃鑱旂郴绠$悊鍛橈紒')
+      downloadLoadingInstance.close();
+    })
+  },
+  saveAs(text, name, opts) {
+    saveAs(text, name, opts);
+  },
+  async printErrMsg(data) {
+    const resText = await data.text();
+    const rspObj = JSON.parse(resText);
+    const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
+    ElMessage.error(errMsg);
+  }
+}
+
diff --git a/zhitan-vue/src/plugins/index.js b/zhitan-vue/src/plugins/index.js
new file mode 100644
index 0000000..47d1b41
--- /dev/null
+++ b/zhitan-vue/src/plugins/index.js
@@ -0,0 +1,18 @@
+import tab from './tab'
+import auth from './auth'
+import cache from './cache'
+import modal from './modal'
+import download from './download'
+
+export default function installPlugins(app){
+  // 椤电鎿嶄綔
+  app.config.globalProperties.$tab = tab
+  // 璁よ瘉瀵硅薄
+  app.config.globalProperties.$auth = auth
+  // 缂撳瓨瀵硅薄
+  app.config.globalProperties.$cache = cache
+  // 妯℃�佹瀵硅薄
+  app.config.globalProperties.$modal = modal
+  // 涓嬭浇鏂囦欢
+  app.config.globalProperties.$download = download
+}
diff --git a/zhitan-vue/src/plugins/modal.js b/zhitan-vue/src/plugins/modal.js
new file mode 100644
index 0000000..b59e14d
--- /dev/null
+++ b/zhitan-vue/src/plugins/modal.js
@@ -0,0 +1,82 @@
+import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus'
+
+let loadingInstance;
+
+export default {
+  // 娑堟伅鎻愮ず
+  msg(content) {
+    ElMessage.info(content)
+  },
+  // 閿欒娑堟伅
+  msgError(content) {
+    ElMessage.error(content)
+  },
+  // 鎴愬姛娑堟伅
+  msgSuccess(content) {
+    ElMessage.success(content)
+  },
+  // 璀﹀憡娑堟伅
+  msgWarning(content) {
+    ElMessage.warning(content)
+  },
+  // 寮瑰嚭鎻愮ず
+  alert(content) {
+    ElMessageBox.alert(content, "绯荤粺鎻愮ず")
+  },
+  // 閿欒鎻愮ず
+  alertError(content) {
+    ElMessageBox.alert(content, "绯荤粺鎻愮ず", { type: 'error' })
+  },
+  // 鎴愬姛鎻愮ず
+  alertSuccess(content) {
+    ElMessageBox.alert(content, "绯荤粺鎻愮ず", { type: 'success' })
+  },
+  // 璀﹀憡鎻愮ず
+  alertWarning(content) {
+    ElMessageBox.alert(content, "绯荤粺鎻愮ず", { type: 'warning' })
+  },
+  // 閫氱煡鎻愮ず
+  notify(content) {
+    ElNotification.info(content)
+  },
+  // 閿欒閫氱煡
+  notifyError(content) {
+    ElNotification.error(content);
+  },
+  // 鎴愬姛閫氱煡
+  notifySuccess(content) {
+    ElNotification.success(content)
+  },
+  // 璀﹀憡閫氱煡
+  notifyWarning(content) {
+    ElNotification.warning(content)
+  },
+  // 纭绐椾綋
+  confirm(content) {
+    return ElMessageBox.confirm(content, "绯荤粺鎻愮ず", {
+      confirmButtonText: '纭畾',
+      cancelButtonText: '鍙栨秷',
+      type: "warning",
+    })
+  },
+  // 鎻愪氦鍐呭
+  prompt(content) {
+    return ElMessageBox.prompt(content, "绯荤粺鎻愮ず", {
+      confirmButtonText: '纭畾',
+      cancelButtonText: '鍙栨秷',
+      type: "warning",
+    })
+  },
+  // 鎵撳紑閬僵灞�
+  loading(content) {
+    loadingInstance = ElLoading.service({
+      lock: true,
+      text: content,
+      background: "rgba(0, 0, 0, 0.7)",
+    })
+  },
+  // 鍏抽棴閬僵灞�
+  closeLoading() {
+    loadingInstance.close();
+  }
+}
diff --git a/zhitan-vue/src/plugins/tab.js b/zhitan-vue/src/plugins/tab.js
new file mode 100644
index 0000000..7b51cf5
--- /dev/null
+++ b/zhitan-vue/src/plugins/tab.js
@@ -0,0 +1,69 @@
+import useTagsViewStore from '@/store/modules/tagsView'
+import router from '@/router'
+
+export default {
+  // 鍒锋柊褰撳墠tab椤电
+  refreshPage(obj) {
+    const { path, query, matched } = router.currentRoute.value;
+    if (obj === undefined) {
+      matched.forEach((m) => {
+        if (m.components && m.components.default && m.components.default.name) {
+          if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
+            obj = { name: m.components.default.name, path: path, query: query };
+          }
+        }
+      });
+    }
+    return useTagsViewStore().delCachedView(obj).then(() => {
+      const { path, query } = obj
+      router.replace({
+        path: '/redirect' + path,
+        query: query
+      })
+    })
+  },
+  // 鍏抽棴褰撳墠tab椤电锛屾墦寮�鏂伴〉绛�
+  closeOpenPage(obj) {
+    useTagsViewStore().delView(router.currentRoute.value);
+    if (obj !== undefined) {
+      return router.push(obj);
+    }
+  },
+  // 鍏抽棴鎸囧畾tab椤电
+  closePage(obj) {
+    if (obj === undefined) {
+      return useTagsViewStore().delView(router.currentRoute.value).then(({ visitedViews }) => {
+        const latestView = visitedViews.slice(-1)[0]
+        if (latestView) {
+          return router.push(latestView.fullPath)
+        }
+        return router.push('/');
+      });
+    }
+    return useTagsViewStore().delView(obj);
+  },
+  // 鍏抽棴鎵�鏈塼ab椤电
+  closeAllPage() {
+    return useTagsViewStore().delAllViews();
+  },
+  // 鍏抽棴宸︿晶tab椤电
+  closeLeftPage(obj) {
+    return useTagsViewStore().delLeftTags(obj || router.currentRoute.value);
+  },
+  // 鍏抽棴鍙充晶tab椤电
+  closeRightPage(obj) {
+    return useTagsViewStore().delRightTags(obj || router.currentRoute.value);
+  },
+  // 鍏抽棴鍏朵粬tab椤电
+  closeOtherPage(obj) {
+    return useTagsViewStore().delOthersViews(obj || router.currentRoute.value);
+  },
+  // 鎵撳紑tab椤电
+  openPage(url) {
+    return router.push(url);
+  },
+  // 淇敼tab椤电
+  updatePage(obj) {
+    return useTagsViewStore().updateVisitedView(obj);
+  }
+}
diff --git a/zhitan-vue/src/router/index.js b/zhitan-vue/src/router/index.js
new file mode 100644
index 0000000..dff5256
--- /dev/null
+++ b/zhitan-vue/src/router/index.js
@@ -0,0 +1,189 @@
+import { createWebHistory, createRouter } from 'vue-router'
+/* Layout */
+import Layout from '@/layout'
+
+/**
+ * Note: 璺敱閰嶇疆椤�
+ *
+ * hidden: true                     // 褰撹缃� true 鐨勬椂鍊欒璺敱涓嶄細鍐嶄晶杈规爮鍑虹幇 濡�401锛宭ogin绛夐〉闈紝鎴栬�呭涓�浜涚紪杈戦〉闈�/edit/1
+ * alwaysShow: true                 // 褰撲綘涓�涓矾鐢变笅闈㈢殑 children 澹版槑鐨勮矾鐢卞ぇ浜�1涓椂锛岃嚜鍔ㄤ細鍙樻垚宓屽鐨勬ā寮�--濡傜粍浠堕〉闈�
+ *                                  // 鍙湁涓�涓椂锛屼細灏嗛偅涓瓙璺敱褰撳仛鏍硅矾鐢辨樉绀哄湪渚ц竟鏍�--濡傚紩瀵奸〉闈�
+ *                                  // 鑻ヤ綘鎯充笉绠¤矾鐢变笅闈㈢殑 children 澹版槑鐨勪釜鏁伴兘鏄剧ず浣犵殑鏍硅矾鐢�
+ *                                  // 浣犲彲浠ヨ缃� alwaysShow: true锛岃繖鏍峰畠灏变細蹇界暐涔嬪墠瀹氫箟鐨勮鍒欙紝涓�鐩存樉绀烘牴璺敱
+ * redirect: noRedirect             // 褰撹缃� noRedirect 鐨勬椂鍊欒璺敱鍦ㄩ潰鍖呭睉瀵艰埅涓笉鍙鐐瑰嚮
+ * name:'router-name'               // 璁惧畾璺敱鐨勫悕瀛楋紝涓�瀹氳濉啓涓嶇劧浣跨敤<keep-alive>鏃朵細鍑虹幇鍚勭闂
+ * query: '{"id": 1, "name": "ry"}' // 璁块棶璺敱鐨勯粯璁や紶閫掑弬鏁�
+ * roles: ['admin', 'common']       // 璁块棶璺敱鐨勮鑹叉潈闄�
+ * permissions: ['a:a:a', 'b:b:b']  // 璁块棶璺敱鐨勮彍鍗曟潈闄�
+ * meta : {
+    noCache: true                   // 濡傛灉璁剧疆涓簍rue锛屽垯涓嶄細琚� <keep-alive> 缂撳瓨(榛樿 false)
+    title: 'title'                  // 璁剧疆璇ヨ矾鐢卞湪渚ц竟鏍忓拰闈㈠寘灞戜腑灞曠ず鐨勫悕瀛�
+    icon: 'svg-name'                // 璁剧疆璇ヨ矾鐢辩殑鍥炬爣锛屽搴旇矾寰剆rc/assets/icons/svg
+    breadcrumb: false               // 濡傛灉璁剧疆涓篺alse锛屽垯涓嶄細鍦╞readcrumb闈㈠寘灞戜腑鏄剧ず
+    activeMenu: '/system/user'      // 褰撹矾鐢辫缃簡璇ュ睘鎬э紝鍒欎細楂樹寒鐩稿搴旂殑渚ц竟鏍忋��
+  }
+ */
+
+// 鍏叡璺敱
+export const constantRoutes = [
+  {
+    path: '/redirect',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: '/redirect/:path(.*)',
+        component: () => import('@/views/redirect/index.vue')
+      }
+    ]
+  },
+  {
+    path: '/login',
+    component: () => import('@/views/login'),
+    hidden: true
+  },
+  {
+    path: '/register',
+    component: () => import('@/views/register'),
+    hidden: true
+  },
+  {
+    path: "/:pathMatch(.*)*",
+    component: () => import('@/views/error/404'),
+    hidden: true
+  },
+  {
+    path: '/401',
+    component: () => import('@/views/error/401'),
+    hidden: true
+  },
+  {
+    path: '',
+    component: Layout,
+    redirect: '/index',
+    children: [
+      {
+        path: '/index',
+        component: () => import('@/views/index'),
+        name: 'Index',
+        meta: { title: '棣栭〉', icon: 'dashboard', affix: true }
+      }
+    ]
+  },
+  {
+    path: '/user',
+    component: Layout,
+    hidden: true,
+    redirect: 'noredirect',
+    children: [
+      {
+        path: 'profile',
+        component: () => import('@/views/system/user/profile/index'),
+        name: 'Profile',
+        meta: { title: '涓汉涓績', icon: 'user' }
+      }
+    ]
+  },
+  {
+    path: '',
+    component: Layout,
+    hidden: true,
+    redirect: 'noredirect',
+    children: [
+      {
+        path: '/energy',
+        component: () => import('@/views/energy/energy'),
+        name: 'energy',
+        meta: { title: '鑳芥簮鐩戞帶椹鹃┒鑸�', icon: 'dashboard', affix: false }
+      }
+    ]
+  }
+]
+
+// 鍔ㄦ�佽矾鐢憋紝鍩轰簬鐢ㄦ埛鏉冮檺鍔ㄦ�佸幓鍔犺浇
+export const dynamicRoutes = [
+  {
+    path: '/system/user-auth',
+    component: Layout,
+    hidden: true,
+    permissions: ['system:user:edit'],
+    children: [
+      {
+        path: 'role/:userId(\\d+)',
+        component: () => import('@/views/system/user/authRole'),
+        name: 'AuthRole',
+        meta: { title: '鍒嗛厤瑙掕壊', activeMenu: '/system/user' }
+      }
+    ]
+  },
+  {
+    path: '/system/role-auth',
+    component: Layout,
+    hidden: true,
+    permissions: ['system:role:edit'],
+    children: [
+      {
+        path: 'user/:roleId(\\d+)',
+        component: () => import('@/views/system/role/authUser'),
+        name: 'AuthUser',
+        meta: { title: '鍒嗛厤鐢ㄦ埛', activeMenu: '/system/role' }
+      }
+    ]
+  },
+  {
+    path: '/system/dict-data',
+    component: Layout,
+    hidden: true,
+    permissions: ['system:dict:list'],
+    children: [
+      {
+        path: 'index/:dictId(\\d+)',
+        component: () => import('@/views/system/dict/data'),
+        name: 'Data',
+        meta: { title: '瀛楀吀鏁版嵁', activeMenu: '/system/dict' }
+      }
+    ]
+  },
+  {
+    path: '/monitor/job-log',
+    component: Layout,
+    hidden: true,
+    permissions: ['monitor:job:list'],
+    children: [
+      {
+        path: 'index/:jobId(\\d+)',
+        component: () => import('@/views/monitor/job/log'),
+        name: 'JobLog',
+        meta: { title: '璋冨害鏃ュ織', activeMenu: '/monitor/job' }
+      }
+    ]
+  },
+  {
+    path: '/tool/gen-edit',
+    component: Layout,
+    hidden: true,
+    permissions: ['tool:gen:edit'],
+    children: [
+      {
+        path: 'index/:tableId(\\d+)',
+        component: () => import('@/views/tool/gen/editTable'),
+        name: 'GenEdit',
+        meta: { title: '淇敼鐢熸垚閰嶇疆', activeMenu: '/tool/gen' }
+      }
+    ]
+  }
+]
+
+const router = createRouter({
+  history: createWebHistory(),
+  routes: constantRoutes,
+  scrollBehavior(to, from, savedPosition) {
+    if (savedPosition) {
+      return savedPosition
+    } else {
+      return { top: 0 }
+    }
+  },
+});
+
+export default router;
diff --git a/zhitan-vue/src/settings.js b/zhitan-vue/src/settings.js
new file mode 100644
index 0000000..08a0108
--- /dev/null
+++ b/zhitan-vue/src/settings.js
@@ -0,0 +1,47 @@
+export default {
+  /**
+   * 缃戦〉鏍囬
+   */
+  title: import.meta.env.VITE_APP_TITLE,
+  /**
+   * 渚ц竟鏍忎富棰� 娣辫壊涓婚theme-dark锛屾祬鑹蹭富棰榯heme-light
+   */
+  sideTheme: 'theme-dark',
+  /**
+   * 鏄惁绯荤粺甯冨眬閰嶇疆
+   */
+  showSettings: true,
+
+  /**
+   * 鏄惁鏄剧ず椤堕儴瀵艰埅
+   */
+  topNav: false,
+
+  /**
+   * 鏄惁鏄剧ず tagsView
+   */
+  tagsView: true,
+
+  /**
+   * 鏄惁鍥哄畾澶撮儴
+   */
+  fixedHeader: false,
+
+  /**
+   * 鏄惁鏄剧ずlogo
+   */
+  sidebarLogo: true,
+
+  /**
+   * 鏄惁鏄剧ず鍔ㄦ�佹爣棰�
+   */
+  dynamicTitle: false,
+
+  /**
+   * @type {string | array} 'production' | ['production', 'development']
+   * @description Need show err logs component.
+   * The default is only used in the production env
+   * If you want to also use it in dev, you can pass ['production', 'development']
+   */
+  errorLog: 'production'
+}
diff --git a/zhitan-vue/src/store/index.js b/zhitan-vue/src/store/index.js
new file mode 100644
index 0000000..f10f389
--- /dev/null
+++ b/zhitan-vue/src/store/index.js
@@ -0,0 +1,3 @@
+const store = createPinia()
+
+export default store
\ No newline at end of file
diff --git a/zhitan-vue/src/store/modules/app.js b/zhitan-vue/src/store/modules/app.js
new file mode 100644
index 0000000..387e57e
--- /dev/null
+++ b/zhitan-vue/src/store/modules/app.js
@@ -0,0 +1,46 @@
+import Cookies from 'js-cookie'
+
+const useAppStore = defineStore(
+  'app',
+  {
+    state: () => ({
+      sidebar: {
+        opened: sessionStorage.getItem('sidebarStatus') ? !!+sessionStorage.getItem('sidebarStatus') : true,
+        withoutAnimation: false,
+        hide: false
+      },
+      device: 'desktop',
+      size: sessionStorage.getItem('size') || 'default'
+    }),
+    actions: {
+      toggleSideBar(withoutAnimation) {
+        if (this.sidebar.hide) {
+          return false;
+        }
+        this.sidebar.opened = !this.sidebar.opened
+        this.sidebar.withoutAnimation = withoutAnimation
+        if (this.sidebar.opened) {
+          sessionStorage.setItem('sidebarStatus', 1)
+        } else {
+          sessionStorage.setItem('sidebarStatus', 0)
+        }
+      },
+      closeSideBar({ withoutAnimation }) {
+        sessionStorage.setItem('sidebarStatus', 0)
+        this.sidebar.opened = false
+        this.sidebar.withoutAnimation = withoutAnimation
+      },
+      toggleDevice(device) {
+        this.device = device
+      },
+      setSize(size) {
+        this.size = size;
+        sessionStorage.setItem('size', size)
+      },
+      toggleSideBarHide(status) {
+        this.sidebar.hide = status
+      }
+    }
+  })
+
+export default useAppStore
diff --git a/zhitan-vue/src/store/modules/dict.js b/zhitan-vue/src/store/modules/dict.js
new file mode 100644
index 0000000..27fc308
--- /dev/null
+++ b/zhitan-vue/src/store/modules/dict.js
@@ -0,0 +1,57 @@
+const useDictStore = defineStore(
+  'dict',
+  {
+    state: () => ({
+      dict: new Array()
+    }),
+    actions: {
+      // 鑾峰彇瀛楀吀
+      getDict(_key) {
+        if (_key == null && _key == "") {
+          return null;
+        }
+        try {
+          for (let i = 0; i < this.dict.length; i++) {
+            if (this.dict[i].key == _key) {
+              return this.dict[i].value;
+            }
+          }
+        } catch (e) {
+          return null;
+        }
+      },
+      // 璁剧疆瀛楀吀
+      setDict(_key, value) {
+        if (_key !== null && _key !== "") {
+          this.dict.push({
+            key: _key,
+            value: value
+          });
+        }
+      },
+      // 鍒犻櫎瀛楀吀
+      removeDict(_key) {
+        var bln = false;
+        try {
+          for (let i = 0; i < this.dict.length; i++) {
+            if (this.dict[i].key == _key) {
+              this.dict.splice(i, 1);
+              return true;
+            }
+          }
+        } catch (e) {
+          bln = false;
+        }
+        return bln;
+      },
+      // 娓呯┖瀛楀吀
+      cleanDict() {
+        this.dict = new Array();
+      },
+      // 鍒濆瀛楀吀
+      initDict() {
+      }
+    }
+  })
+
+export default useDictStore
diff --git a/zhitan-vue/src/store/modules/permission.js b/zhitan-vue/src/store/modules/permission.js
new file mode 100644
index 0000000..958fe63
--- /dev/null
+++ b/zhitan-vue/src/store/modules/permission.js
@@ -0,0 +1,142 @@
+import auth from '@/plugins/auth'
+import router, { constantRoutes, dynamicRoutes } from '@/router'
+import { getRouters } from '@/api/menu'
+import Layout from '@/layout/index'
+import ParentView from '@/components/ParentView'
+import InnerLink from '@/layout/components/InnerLink'
+
+// 鍖归厤views閲岄潰鎵�鏈夌殑.vue鏂囦欢
+const modules = import.meta.glob('./../../views/**/*.vue')
+
+const usePermissionStore = defineStore(
+  'permission',
+  {
+    state: () => ({
+      routes: [],
+      addRoutes: [],
+      defaultRoutes: [],
+      topbarRouters: [],
+      sidebarRouters: []
+    }),
+    actions: {
+      setRoutes(routes) {
+        this.addRoutes = routes
+        this.routes = constantRoutes.concat(routes)
+      },
+      setDefaultRoutes(routes) {
+        this.defaultRoutes = constantRoutes.concat(routes)
+      },
+      setTopbarRoutes(routes) {
+        this.topbarRouters = routes
+      },
+      setSidebarRouters(routes) {
+        this.sidebarRouters = routes
+      },
+      generateRoutes(roles) {
+        return new Promise(resolve => {
+          // 鍚戝悗绔姹傝矾鐢辨暟鎹�
+          getRouters().then(res => {
+            const sdata = JSON.parse(JSON.stringify(res.data))
+            const rdata = JSON.parse(JSON.stringify(res.data))
+            const defaultData = JSON.parse(JSON.stringify(res.data))
+            const sidebarRoutes = filterAsyncRouter(sdata)
+            const rewriteRoutes = filterAsyncRouter(rdata, false, true)
+            const defaultRoutes = filterAsyncRouter(defaultData)
+            const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
+            asyncRoutes.forEach(route => { router.addRoute(route) })
+            this.setRoutes(rewriteRoutes)
+            this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
+            this.setDefaultRoutes(sidebarRoutes)
+            this.setTopbarRoutes(defaultRoutes)
+            resolve(rewriteRoutes)
+          })
+        })
+      }
+    }
+  })
+
+// 閬嶅巻鍚庡彴浼犳潵鐨勮矾鐢卞瓧绗︿覆锛岃浆鎹负缁勪欢瀵硅薄
+function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
+  return asyncRouterMap.filter(route => {
+    if (type && route.children) {
+      route.children = filterChildren(route.children)
+    }
+    if (route.component) {
+      // Layout ParentView 缁勪欢鐗规畩澶勭悊
+      if (route.component === 'Layout') {
+        route.component = Layout
+      } else if (route.component === 'ParentView') {
+        route.component = ParentView
+      } else if (route.component === 'InnerLink') {
+        route.component = InnerLink
+      } else {
+        route.component = loadView(route.component)
+      }
+    }
+    if (route.children != null && route.children && route.children.length) {
+      route.children = filterAsyncRouter(route.children, route, type)
+    } else {
+      delete route['children']
+      delete route['redirect']
+    }
+    return true
+  })
+}
+
+function filterChildren(childrenMap, lastRouter = false) {
+  var children = []
+  childrenMap.forEach((el, index) => {
+    if (el.children && el.children.length) {
+      if (el.component === 'ParentView' && !lastRouter) {
+        el.children.forEach(c => {
+          c.path = el.path + '/' + c.path
+          if (c.children && c.children.length) {
+            children = children.concat(filterChildren(c.children, c))
+            return
+          }
+          children.push(c)
+        })
+        return
+      }
+    }
+    if (lastRouter) {
+      el.path = lastRouter.path + '/' + el.path
+      if (el.children && el.children.length) {
+        children = children.concat(filterChildren(el.children, el))
+        return
+      }
+    }
+    children = children.concat(el)
+  })
+  return children
+}
+
+// 鍔ㄦ�佽矾鐢遍亶鍘嗭紝楠岃瘉鏄惁鍏峰鏉冮檺
+export function filterDynamicRoutes(routes) {
+  const res = []
+  routes.forEach(route => {
+    if (route.permissions) {
+      if (auth.hasPermiOr(route.permissions)) {
+        res.push(route)
+      }
+    } else if (route.roles) {
+      if (auth.hasRoleOr(route.roles)) {
+        res.push(route)
+      }
+    }
+  })
+  return res
+}
+
+export const loadView = (view) => {
+  let res;
+  for (const path in modules) {
+    const dir = path.split('views/')[1].split('.vue')[0];
+    if (dir === view) {
+      res = () => modules[path]();
+    }
+  }
+  return res;
+}
+
+export default usePermissionStore
diff --git a/zhitan-vue/src/store/modules/settings.js b/zhitan-vue/src/store/modules/settings.js
new file mode 100644
index 0000000..22b7336
--- /dev/null
+++ b/zhitan-vue/src/store/modules/settings.js
@@ -0,0 +1,38 @@
+import defaultSettings from '@/settings'
+import { useDynamicTitle } from '@/utils/dynamicTitle'
+
+const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings
+
+const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
+
+const useSettingsStore = defineStore(
+  'settings',
+  {
+    state: () => ({
+      title: '',
+      theme: storageSetting.theme || '#409EFF',
+      sideTheme: storageSetting.sideTheme || sideTheme,
+      showSettings: showSettings,
+      topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
+      tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView,
+      fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader,
+      sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo,
+      dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle
+    }),
+    actions: {
+      // 淇敼甯冨眬璁剧疆
+      changeSetting(data) {
+        const { key, value } = data
+        if (this.hasOwnProperty(key)) {
+          this[key] = value
+        }
+      },
+      // 璁剧疆缃戦〉鏍囬
+      setTitle(title) {
+        this.title = title
+        useDynamicTitle();
+      }
+    }
+  })
+
+export default useSettingsStore
diff --git a/zhitan-vue/src/store/modules/tagsView.js b/zhitan-vue/src/store/modules/tagsView.js
new file mode 100644
index 0000000..9d07f33
--- /dev/null
+++ b/zhitan-vue/src/store/modules/tagsView.js
@@ -0,0 +1,182 @@
+const useTagsViewStore = defineStore(
+  'tags-view',
+  {
+    state: () => ({
+      visitedViews: [],
+      cachedViews: [],
+      iframeViews: []
+    }),
+    actions: {
+      addView(view) {
+        this.addVisitedView(view)
+        this.addCachedView(view)
+      },
+      addIframeView(view) {
+        if (this.iframeViews.some(v => v.path === view.path)) return
+        this.iframeViews.push(
+          Object.assign({}, view, {
+            title: view.meta.title || 'no-name'
+          })
+        )
+      },
+      addVisitedView(view) {
+        if (this.visitedViews.some(v => v.path === view.path)) return
+        this.visitedViews.push(
+          Object.assign({}, view, {
+            title: view.meta.title || 'no-name'
+          })
+        )
+      },
+      addCachedView(view) {
+        if (this.cachedViews.includes(view.name)) return
+        if (!view.meta.noCache) {
+          this.cachedViews.push(view.name)
+        }
+      },
+      delView(view) {
+        return new Promise(resolve => {
+          this.delVisitedView(view)
+          this.delCachedView(view)
+          resolve({
+            visitedViews: [...this.visitedViews],
+            cachedViews: [...this.cachedViews]
+          })
+        })
+      },
+      delVisitedView(view) {
+        return new Promise(resolve => {
+          for (const [i, v] of this.visitedViews.entries()) {
+            if (v.path === view.path) {
+              this.visitedViews.splice(i, 1)
+              break
+            }
+          }
+          this.iframeViews = this.iframeViews.filter(item => item.path !== view.path)
+          resolve([...this.visitedViews])
+        })
+      },
+      delIframeView(view) {
+        return new Promise(resolve => {
+          this.iframeViews = this.iframeViews.filter(item => item.path !== view.path)
+          resolve([...this.iframeViews])
+        })
+      },
+      delCachedView(view) {
+        return new Promise(resolve => {
+          const index = this.cachedViews.indexOf(view.name)
+          index > -1 && this.cachedViews.splice(index, 1)
+          resolve([...this.cachedViews])
+        })
+      },
+      delOthersViews(view) {
+        return new Promise(resolve => {
+          this.delOthersVisitedViews(view)
+          this.delOthersCachedViews(view)
+          resolve({
+            visitedViews: [...this.visitedViews],
+            cachedViews: [...this.cachedViews]
+          })
+        })
+      },
+      delOthersVisitedViews(view) {
+        return new Promise(resolve => {
+          this.visitedViews = this.visitedViews.filter(v => {
+            return v.meta.affix || v.path === view.path
+          })
+          this.iframeViews = this.iframeViews.filter(item => item.path === view.path)
+          resolve([...this.visitedViews])
+        })
+      },
+      delOthersCachedViews(view) {
+        return new Promise(resolve => {
+          const index = this.cachedViews.indexOf(view.name)
+          if (index > -1) {
+            this.cachedViews = this.cachedViews.slice(index, index + 1)
+          } else {
+            this.cachedViews = []
+          }
+          resolve([...this.cachedViews])
+        })
+      },
+      delAllViews(view) {
+        return new Promise(resolve => {
+          this.delAllVisitedViews(view)
+          this.delAllCachedViews(view)
+          resolve({
+            visitedViews: [...this.visitedViews],
+            cachedViews: [...this.cachedViews]
+          })
+        })
+      },
+      delAllVisitedViews(view) {
+        return new Promise(resolve => {
+          const affixTags = this.visitedViews.filter(tag => tag.meta.affix)
+          this.visitedViews = affixTags
+          this.iframeViews = []
+          resolve([...this.visitedViews])
+        })
+      },
+      delAllCachedViews(view) {
+        return new Promise(resolve => {
+          this.cachedViews = []
+          resolve([...this.cachedViews])
+        })
+      },
+      updateVisitedView(view) {
+        for (let v of this.visitedViews) {
+          if (v.path === view.path) {
+            v = Object.assign(v, view)
+            break
+          }
+        }
+      },
+      delRightTags(view) {
+        return new Promise(resolve => {
+          const index = this.visitedViews.findIndex(v => v.path === view.path)
+          if (index === -1) {
+            return
+          }
+          this.visitedViews = this.visitedViews.filter((item, idx) => {
+            if (idx <= index || (item.meta && item.meta.affix)) {
+              return true
+            }
+            const i = this.cachedViews.indexOf(item.name)
+            if (i > -1) {
+              this.cachedViews.splice(i, 1)
+            }
+            if(item.meta.link) {
+              const fi = this.iframeViews.findIndex(v => v.path === item.path)
+              this.iframeViews.splice(fi, 1)
+            }
+            return false
+          })
+          resolve([...this.visitedViews])
+        })
+      },
+      delLeftTags(view) {
+        return new Promise(resolve => {
+          const index = this.visitedViews.findIndex(v => v.path === view.path)
+          if (index === -1) {
+            return
+          }
+          this.visitedViews = this.visitedViews.filter((item, idx) => {
+            if (idx >= index || (item.meta && item.meta.affix)) {
+              return true
+            }
+            const i = this.cachedViews.indexOf(item.name)
+            if (i > -1) {
+              this.cachedViews.splice(i, 1)
+            }
+            if(item.meta.link) {
+              const fi = this.iframeViews.findIndex(v => v.path === item.path)
+              this.iframeViews.splice(fi, 1)
+            }
+            return false
+          })
+          resolve([...this.visitedViews])
+        })
+      }
+    }
+  })
+
+export default useTagsViewStore
diff --git a/zhitan-vue/src/store/modules/user.js b/zhitan-vue/src/store/modules/user.js
new file mode 100644
index 0000000..788e5d9
--- /dev/null
+++ b/zhitan-vue/src/store/modules/user.js
@@ -0,0 +1,89 @@
+import { login, getSingleLogin, logout, getInfo } from '@/api/login'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import defAva from '@/assets/images/profile.jpg'
+
+const useUserStore = defineStore(
+  'user',
+  {
+    state: () => ({
+      token: getToken(),
+      id: '',
+      name: '',
+      avatar: '',
+      roles: [],
+      permissions: []
+    }),
+    actions: {
+      // 鐢ㄤ簬绗笁鏂圭櫥闄�
+      LoginJHaveToken(userInfo) {
+        const token = userInfo.token;
+        const queryParams = {
+          'token': token
+        };
+        return new Promise((resolve, reject) => {
+          getSingleLogin(queryParams).then(res => {
+            setToken(token)
+            setToken(res.token)
+            this.token = res.token
+            resolve()
+          }).catch(error => {
+            reject(error)
+          })
+        })
+      },
+      // 鐧诲綍
+      login(userInfo) {
+        const username = userInfo.username.trim()
+        const password = userInfo.password
+        const code = userInfo.code
+        const uuid = userInfo.uuid
+        return new Promise((resolve, reject) => {
+          login(username, password, code, uuid).then(res => {
+            setToken(res.token)
+            this.token = res.token
+            resolve()
+          }).catch(error => {
+            reject(error)
+          })
+        })
+      },
+      // 鑾峰彇鐢ㄦ埛淇℃伅
+      getInfo() {
+        return new Promise((resolve, reject) => {
+          getInfo().then(res => {
+            const user = res.user
+            const avatar = (user.avatar == "" || user.avatar == null) ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
+
+            if (res.roles && res.roles.length > 0) { // 楠岃瘉杩斿洖鐨剅oles鏄惁鏄竴涓潪绌烘暟缁�
+              this.roles = res.roles
+              this.permissions = res.permissions
+            } else {
+              this.roles = ['ROLE_DEFAULT']
+            }
+            this.id = user.userId
+            this.name = user.userName
+            this.avatar = avatar
+            resolve(res)
+          }).catch(error => {
+            reject(error)
+          })
+        })
+      },
+      // 閫�鍑虹郴缁�
+      logOut() {
+        return new Promise((resolve, reject) => {
+          logout(this.token).then(() => {
+            this.token = ''
+            this.roles = []
+            this.permissions = []
+            removeToken()
+            resolve()
+          }).catch(error => {
+            reject(error)
+          })
+        })
+      }
+    }
+  })
+
+export default useUserStore
diff --git a/zhitan-vue/src/utils/auth.js b/zhitan-vue/src/utils/auth.js
new file mode 100644
index 0000000..4241626
--- /dev/null
+++ b/zhitan-vue/src/utils/auth.js
@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'Admin-Token'
+
+export function getToken() {
+  return sessionStorage.getItem(TokenKey)
+}
+
+export function setToken(token) {
+  return sessionStorage.setItem(TokenKey, token)
+}
+
+export function removeToken() {
+  return sessionStorage.removeItem(TokenKey)
+}
diff --git a/zhitan-vue/src/utils/dict.js b/zhitan-vue/src/utils/dict.js
new file mode 100644
index 0000000..9648f14
--- /dev/null
+++ b/zhitan-vue/src/utils/dict.js
@@ -0,0 +1,24 @@
+import useDictStore from '@/store/modules/dict'
+import { getDicts } from '@/api/system/dict/data'
+
+/**
+ * 鑾峰彇瀛楀吀鏁版嵁
+ */
+export function useDict(...args) {
+  const res = ref({});
+  return (() => {
+    args.forEach((dictType, index) => {
+      res.value[dictType] = [];
+      const dicts = useDictStore().getDict(dictType);
+      if (dicts) {
+        res.value[dictType] = dicts;
+      } else {
+        getDicts(dictType).then(resp => {
+          res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
+          useDictStore().setDict(dictType, res.value[dictType]);
+        })
+      }
+    })
+    return toRefs(res.value);
+  })()
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/utils/dynamicTitle.js b/zhitan-vue/src/utils/dynamicTitle.js
new file mode 100644
index 0000000..64404b2
--- /dev/null
+++ b/zhitan-vue/src/utils/dynamicTitle.js
@@ -0,0 +1,15 @@
+import store from '@/store'
+import defaultSettings from '@/settings'
+import useSettingsStore from '@/store/modules/settings'
+
+/**
+ * 鍔ㄦ�佷慨鏀规爣棰�
+ */
+export function useDynamicTitle() {
+  const settingsStore = useSettingsStore();
+  if (settingsStore.dynamicTitle) {
+    document.title = settingsStore.title + ' - ' + defaultSettings.title;
+  } else {
+    document.title = defaultSettings.title;
+  }
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/utils/errorCode.js b/zhitan-vue/src/utils/errorCode.js
new file mode 100644
index 0000000..d2111ee
--- /dev/null
+++ b/zhitan-vue/src/utils/errorCode.js
@@ -0,0 +1,6 @@
+export default {
+  '401': '璁よ瘉澶辫触锛屾棤娉曡闂郴缁熻祫婧�',
+  '403': '褰撳墠鎿嶄綔娌℃湁鏉冮檺',
+  '404': '璁块棶璧勬簮涓嶅瓨鍦�',
+  'default': '绯荤粺鏈煡閿欒锛岃鍙嶉缁欑鐞嗗憳'
+}
diff --git a/zhitan-vue/src/utils/index.js b/zhitan-vue/src/utils/index.js
new file mode 100644
index 0000000..4e65504
--- /dev/null
+++ b/zhitan-vue/src/utils/index.js
@@ -0,0 +1,390 @@
+import { parseTime } from './ruoyi'
+
+/**
+ * 琛ㄦ牸鏃堕棿鏍煎紡鍖�
+ */
+export function formatDate(cellValue) {
+  if (cellValue == null || cellValue == "") return "";
+  var date = new Date(cellValue) 
+  var year = date.getFullYear()
+  var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
+  var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() 
+  var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() 
+  var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() 
+  var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
+  return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
+}
+
+/**
+ * @param {number} time
+ * @param {string} option
+ * @returns {string}
+ */
+export function formatTime(time, option) {
+  if (('' + time).length === 10) {
+    time = parseInt(time) * 1000
+  } else {
+    time = +time
+  }
+  const d = new Date(time)
+  const now = Date.now()
+
+  const diff = (now - d) / 1000
+
+  if (diff < 30) {
+    return '鍒氬垰'
+  } else if (diff < 3600) {
+    // less 1 hour
+    return Math.ceil(diff / 60) + '鍒嗛挓鍓�'
+  } else if (diff < 3600 * 24) {
+    return Math.ceil(diff / 3600) + '灏忔椂鍓�'
+  } else if (diff < 3600 * 24 * 2) {
+    return '1澶╁墠'
+  }
+  if (option) {
+    return parseTime(time, option)
+  } else {
+    return (
+      d.getMonth() +
+      1 +
+      '鏈�' +
+      d.getDate() +
+      '鏃�' +
+      d.getHours() +
+      '鏃�' +
+      d.getMinutes() +
+      '鍒�'
+    )
+  }
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function getQueryObject(url) {
+  url = url == null ? window.location.href : url
+  const search = url.substring(url.lastIndexOf('?') + 1)
+  const obj = {}
+  const reg = /([^?&=]+)=([^?&=]*)/g
+  search.replace(reg, (rs, $1, $2) => {
+    const name = decodeURIComponent($1)
+    let val = decodeURIComponent($2)
+    val = String(val)
+    obj[name] = val
+    return rs
+  })
+  return obj
+}
+
+/**
+ * @param {string} input value
+ * @returns {number} output value
+ */
+export function byteLength(str) {
+  // returns the byte length of an utf8 string
+  let s = str.length
+  for (var i = str.length - 1; i >= 0; i--) {
+    const code = str.charCodeAt(i)
+    if (code > 0x7f && code <= 0x7ff) s++
+    else if (code > 0x7ff && code <= 0xffff) s += 2
+    if (code >= 0xDC00 && code <= 0xDFFF) i--
+  }
+  return s
+}
+
+/**
+ * @param {Array} actual
+ * @returns {Array}
+ */
+export function cleanArray(actual) {
+  const newArray = []
+  for (let i = 0; i < actual.length; i++) {
+    if (actual[i]) {
+      newArray.push(actual[i])
+    }
+  }
+  return newArray
+}
+
+/**
+ * @param {Object} json
+ * @returns {Array}
+ */
+export function param(json) {
+  if (!json) return ''
+  return cleanArray(
+    Object.keys(json).map(key => {
+      if (json[key] === undefined) return ''
+      return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
+    })
+  ).join('&')
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function param2Obj(url) {
+  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+  if (!search) {
+    return {}
+  }
+  const obj = {}
+  const searchArr = search.split('&')
+  searchArr.forEach(v => {
+    const index = v.indexOf('=')
+    if (index !== -1) {
+      const name = v.substring(0, index)
+      const val = v.substring(index + 1, v.length)
+      obj[name] = val
+    }
+  })
+  return obj
+}
+
+/**
+ * @param {string} val
+ * @returns {string}
+ */
+export function html2Text(val) {
+  const div = document.createElement('div')
+  div.innerHTML = val
+  return div.textContent || div.innerText
+}
+
+/**
+ * Merges two objects, giving the last one precedence
+ * @param {Object} target
+ * @param {(Object|Array)} source
+ * @returns {Object}
+ */
+export function objectMerge(target, source) {
+  if (typeof target !== 'object') {
+    target = {}
+  }
+  if (Array.isArray(source)) {
+    return source.slice()
+  }
+  Object.keys(source).forEach(property => {
+    const sourceProperty = source[property]
+    if (typeof sourceProperty === 'object') {
+      target[property] = objectMerge(target[property], sourceProperty)
+    } else {
+      target[property] = sourceProperty
+    }
+  })
+  return target
+}
+
+/**
+ * @param {HTMLElement} element
+ * @param {string} className
+ */
+export function toggleClass(element, className) {
+  if (!element || !className) {
+    return
+  }
+  let classString = element.className
+  const nameIndex = classString.indexOf(className)
+  if (nameIndex === -1) {
+    classString += '' + className
+  } else {
+    classString =
+      classString.substr(0, nameIndex) +
+      classString.substr(nameIndex + className.length)
+  }
+  element.className = classString
+}
+
+/**
+ * @param {string} type
+ * @returns {Date}
+ */
+export function getTime(type) {
+  if (type === 'start') {
+    return new Date().getTime() - 3600 * 1000 * 24 * 90
+  } else {
+    return new Date(new Date().toDateString())
+  }
+}
+
+/**
+ * @param {Function} func
+ * @param {number} wait
+ * @param {boolean} immediate
+ * @return {*}
+ */
+export function debounce(func, wait, immediate) {
+  let timeout, args, context, timestamp, result
+
+  const later = function() {
+    // 鎹笂涓�娆¤Е鍙戞椂闂撮棿闅�
+    const last = +new Date() - timestamp
+
+    // 涓婃琚寘瑁呭嚱鏁拌璋冪敤鏃堕棿闂撮殧 last 灏忎簬璁惧畾鏃堕棿闂撮殧 wait
+    if (last < wait && last > 0) {
+      timeout = setTimeout(later, wait - last)
+    } else {
+      timeout = null
+      // 濡傛灉璁惧畾涓篿mmediate===true锛屽洜涓哄紑濮嬭竟鐣屽凡缁忚皟鐢ㄨ繃浜嗘澶勬棤闇�璋冪敤
+      if (!immediate) {
+        result = func.apply(context, args)
+        if (!timeout) context = args = null
+      }
+    }
+  }
+
+  return function(...args) {
+    context = this
+    timestamp = +new Date()
+    const callNow = immediate && !timeout
+    // 濡傛灉寤舵椂涓嶅瓨鍦紝閲嶆柊璁惧畾寤舵椂
+    if (!timeout) timeout = setTimeout(later, wait)
+    if (callNow) {
+      result = func.apply(context, args)
+      context = args = null
+    }
+
+    return result
+  }
+}
+
+/**
+ * This is just a simple version of deep copy
+ * Has a lot of edge cases bug
+ * If you want to use a perfect deep copy, use lodash's _.cloneDeep
+ * @param {Object} source
+ * @returns {Object}
+ */
+export function deepClone(source) {
+  if (!source && typeof source !== 'object') {
+    throw new Error('error arguments', 'deepClone')
+  }
+  const targetObj = source.constructor === Array ? [] : {}
+  Object.keys(source).forEach(keys => {
+    if (source[keys] && typeof source[keys] === 'object') {
+      targetObj[keys] = deepClone(source[keys])
+    } else {
+      targetObj[keys] = source[keys]
+    }
+  })
+  return targetObj
+}
+
+/**
+ * @param {Array} arr
+ * @returns {Array}
+ */
+export function uniqueArr(arr) {
+  return Array.from(new Set(arr))
+}
+
+/**
+ * @returns {string}
+ */
+export function createUniqueString() {
+  const timestamp = +new Date() + ''
+  const randomNum = parseInt((1 + Math.random()) * 65536) + ''
+  return (+(randomNum + timestamp)).toString(32)
+}
+
+/**
+ * Check if an element has a class
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ * @returns {boolean}
+ */
+export function hasClass(ele, cls) {
+  return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
+}
+
+/**
+ * Add class to element
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ */
+export function addClass(ele, cls) {
+  if (!hasClass(ele, cls)) ele.className += ' ' + cls
+}
+
+/**
+ * Remove class from element
+ * @param {HTMLElement} elm
+ * @param {string} cls
+ */
+export function removeClass(ele, cls) {
+  if (hasClass(ele, cls)) {
+    const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
+    ele.className = ele.className.replace(reg, ' ')
+  }
+}
+
+export function makeMap(str, expectsLowerCase) {
+  const map = Object.create(null)
+  const list = str.split(',')
+  for (let i = 0; i < list.length; i++) {
+    map[list[i]] = true
+  }
+  return expectsLowerCase
+    ? val => map[val.toLowerCase()]
+    : val => map[val]
+}
+ 
+export const exportDefault = 'export default '
+
+export const beautifierConf = {
+  html: {
+    indent_size: '2',
+    indent_char: ' ',
+    max_preserve_newlines: '-1',
+    preserve_newlines: false,
+    keep_array_indentation: false,
+    break_chained_methods: false,
+    indent_scripts: 'separate',
+    brace_style: 'end-expand',
+    space_before_conditional: true,
+    unescape_strings: false,
+    jslint_happy: false,
+    end_with_newline: true,
+    wrap_line_length: '110',
+    indent_inner_html: true,
+    comma_first: false,
+    e4x: true,
+    indent_empty_lines: true
+  },
+  js: {
+    indent_size: '2',
+    indent_char: ' ',
+    max_preserve_newlines: '-1',
+    preserve_newlines: false,
+    keep_array_indentation: false,
+    break_chained_methods: false,
+    indent_scripts: 'normal',
+    brace_style: 'end-expand',
+    space_before_conditional: true,
+    unescape_strings: false,
+    jslint_happy: true,
+    end_with_newline: true,
+    wrap_line_length: '110',
+    indent_inner_html: true,
+    comma_first: false,
+    e4x: true,
+    indent_empty_lines: true
+  }
+}
+
+// 棣栧瓧姣嶅ぇ灏�
+export function titleCase(str) {
+  return str.replace(/( |^)[a-z]/g, L => L.toUpperCase())
+}
+
+// 涓嬪垝杞┘宄�
+export function camelCase(str) {
+  return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase())
+}
+
+export function isNumberStr(str) {
+  return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
+}
+ 
diff --git a/zhitan-vue/src/utils/jsencrypt.js b/zhitan-vue/src/utils/jsencrypt.js
new file mode 100644
index 0000000..78d9523
--- /dev/null
+++ b/zhitan-vue/src/utils/jsencrypt.js
@@ -0,0 +1,30 @@
+import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
+
+// 瀵嗛挜瀵圭敓鎴� http://web.chacuo.net/netrsakeypair
+
+const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +
+  'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
+
+const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
+  '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
+  'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
+  'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
+  'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
+  'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
+  'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
+  'UP8iWi1Qw0Y='
+
+// 鍔犲瘑
+export function encrypt(txt) {
+  const encryptor = new JSEncrypt()
+  encryptor.setPublicKey(publicKey) // 璁剧疆鍏挜
+  return encryptor.encrypt(txt) // 瀵规暟鎹繘琛屽姞瀵�
+}
+
+// 瑙e瘑
+export function decrypt(txt) {
+  const encryptor = new JSEncrypt()
+  encryptor.setPrivateKey(privateKey) // 璁剧疆绉侀挜
+  return encryptor.decrypt(txt) // 瀵规暟鎹繘琛岃В瀵�
+}
+
diff --git a/zhitan-vue/src/utils/permission.js b/zhitan-vue/src/utils/permission.js
new file mode 100644
index 0000000..93fee87
--- /dev/null
+++ b/zhitan-vue/src/utils/permission.js
@@ -0,0 +1,51 @@
+import useUserStore from '@/store/modules/user'
+
+/**
+ * 瀛楃鏉冮檺鏍¢獙
+ * @param {Array} value 鏍¢獙鍊�
+ * @returns {Boolean}
+ */
+export function checkPermi(value) {
+  if (value && value instanceof Array && value.length > 0) {
+    const permissions = useUserStore().permissions
+    const permissionDatas = value
+    const all_permission = "*:*:*";
+
+    const hasPermission = permissions.some(permission => {
+      return all_permission === permission || permissionDatas.includes(permission)
+    })
+
+    if (!hasPermission) {
+      return false
+    }
+    return true
+  } else {
+    console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`)
+    return false
+  }
+}
+
+/**
+ * 瑙掕壊鏉冮檺鏍¢獙
+ * @param {Array} value 鏍¢獙鍊�
+ * @returns {Boolean}
+ */
+export function checkRole(value) {
+  if (value && value instanceof Array && value.length > 0) {
+    const roles = useUserStore().roles
+    const permissionRoles = value
+    const super_admin = "admin";
+
+    const hasRole = roles.some(role => {
+      return super_admin === role || permissionRoles.includes(role)
+    })
+
+    if (!hasRole) {
+      return false
+    }
+    return true
+  } else {
+    console.error(`need roles! Like checkRole="['admin','editor']"`)
+    return false
+  }
+}
\ No newline at end of file
diff --git a/zhitan-vue/src/utils/request.js b/zhitan-vue/src/utils/request.js
new file mode 100644
index 0000000..00b910f
--- /dev/null
+++ b/zhitan-vue/src/utils/request.js
@@ -0,0 +1,152 @@
+import axios from 'axios'
+import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus'
+import { getToken } from '@/utils/auth'
+import errorCode from '@/utils/errorCode'
+import { tansParams, blobValidate } from '@/utils/ruoyi'
+import cache from '@/plugins/cache'
+import { saveAs } from 'file-saver'
+import useUserStore from '@/store/modules/user'
+
+let downloadLoadingInstance;
+// 鏄惁鏄剧ず閲嶆柊鐧诲綍
+export let isRelogin = { show: false };
+
+axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
+// 鍒涘缓axios瀹炰緥
+const service = axios.create({
+  // axios涓姹傞厤缃湁baseURL閫夐」锛岃〃绀鸿姹俇RL鍏叡閮ㄥ垎
+  baseURL: import.meta.env.VITE_APP_BASE_API,
+  // 瓒呮椂
+  timeout: 10000
+})
+
+// request鎷︽埅鍣�
+service.interceptors.request.use(config => {
+  // 鏄惁闇�瑕佽缃� token
+  const isToken = (config.headers || {}).isToken === false
+  // 鏄惁闇�瑕侀槻姝㈡暟鎹噸澶嶆彁浜�
+  const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
+  if (getToken() && !isToken) {
+    config.headers['Authorization'] = 'Bearer ' + getToken() // 璁╂瘡涓姹傛惡甯﹁嚜瀹氫箟token 璇锋牴鎹疄闄呮儏鍐佃嚜琛屼慨鏀�
+  }
+  // get璇锋眰鏄犲皠params鍙傛暟
+  if (config.method === 'get' && config.params) {
+    let url = config.url + '?' + tansParams(config.params);
+    url = url.slice(0, -1);
+    config.params = {};
+    config.url = url;
+  }
+  if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
+    const requestObj = {
+      url: config.url,
+      data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
+      time: new Date().getTime()
+    }
+    const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 璇锋眰鏁版嵁澶у皬
+    const limitSize = 5 * 1024 * 1024; // 闄愬埗瀛樻斁鏁版嵁5M
+    if (requestSize >= limitSize) {
+      console.warn(`[${config.url}]: ` + '璇锋眰鏁版嵁澶у皬瓒呭嚭鍏佽鐨�5M闄愬埗锛屾棤娉曡繘琛岄槻閲嶅鎻愪氦楠岃瘉銆�')
+      return config;
+    }
+    const sessionObj = cache.session.getJSON('sessionObj')
+    if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
+      cache.session.setJSON('sessionObj', requestObj)
+    } else {
+      const s_url = sessionObj.url;                // 璇锋眰鍦板潃
+      const s_data = sessionObj.data;              // 璇锋眰鏁版嵁
+      const s_time = sessionObj.time;              // 璇锋眰鏃堕棿
+      const interval = 1000;                       // 闂撮殧鏃堕棿(ms)锛屽皬浜庢鏃堕棿瑙嗕负閲嶅鎻愪氦
+      if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
+        const message = '鏁版嵁姝e湪澶勭悊锛岃鍕块噸澶嶆彁浜�';
+        console.warn(`[${s_url}]: ` + message)
+        return Promise.reject(new Error(message))
+      } else {
+        cache.session.setJSON('sessionObj', requestObj)
+      }
+    }
+  }
+  return config
+}, error => {
+    console.log(error)
+    Promise.reject(error)
+})
+
+// 鍝嶅簲鎷︽埅鍣�
+service.interceptors.response.use(res => {
+    // 鏈缃姸鎬佺爜鍒欓粯璁ゆ垚鍔熺姸鎬�
+    const code = res.data.code || 200;
+    // 鑾峰彇閿欒淇℃伅
+    const msg = errorCode[code] || res.data.msg || errorCode['default']
+    // 浜岃繘鍒舵暟鎹垯鐩存帴杩斿洖
+    if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {
+      return res.data
+    }
+    if (code === 401) {
+      if (!isRelogin.show) {
+        isRelogin.show = true;
+        ElMessageBox.confirm('鐧诲綍鐘舵�佸凡杩囨湡锛屾偍鍙互缁х画鐣欏湪璇ラ〉闈紝鎴栬�呴噸鏂扮櫥褰�', '绯荤粺鎻愮ず', { confirmButtonText: '閲嶆柊鐧诲綍', cancelButtonText: '鍙栨秷', type: 'warning' }).then(() => {
+          isRelogin.show = false;
+          useUserStore().logOut().then(() => {
+            location.href = '/index';
+          })
+      }).catch(() => {
+        isRelogin.show = false;
+      });
+    }
+      return Promise.reject('鏃犳晥鐨勪細璇濓紝鎴栬�呬細璇濆凡杩囨湡锛岃閲嶆柊鐧诲綍銆�')
+    } else if (code === 500) {
+      ElMessage({ message: msg, type: 'error' })
+      return Promise.reject(new Error(msg))
+    } else if (code === 601) {
+      ElMessage({ message: msg, type: 'warning' })
+      return Promise.reject(new Error(msg))
+    } else if (code !== 200) {
+      ElNotification.error({ title: msg })
+      return Promise.reject('error')
+    } else {
+      return  Promise.resolve(res.data)
+    }
+  },
+  error => {
+    console.log('err' + error)
+    let { message } = error;
+    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) + "寮傚父";
+    }
+    ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
+    return Promise.reject(error)
+  }
+)
+
+// 閫氱敤涓嬭浇鏂规硶
+export function download(url, params, filename, config) {
+  downloadLoadingInstance = ElLoading.service({ text: "姝e湪涓嬭浇鏁版嵁锛岃绋嶅��", background: "rgba(0, 0, 0, 0.7)", })
+  return service.post(url, params, {
+    transformRequest: [(params) => { return tansParams(params) }],
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+    responseType: 'blob',
+    ...config
+  }).then(async (data) => {
+    const isBlob = blobValidate(data);
+    if (isBlob) {
+      const blob = new Blob([data])
+      saveAs(blob, filename)
+    } else {
+      const resText = await data.text();
+      const rspObj = JSON.parse(resText);
+      const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
+      ElMessage.error(errMsg);
+    }
+    downloadLoadingInstance.close();
+  }).catch((r) => {
+    console.error(r)
+    ElMessage.error('涓嬭浇鏂囦欢鍑虹幇閿欒锛岃鑱旂郴绠$悊鍛橈紒')
+    downloadLoadingInstance.close();
+  })
+}
+
+export default service
diff --git a/zhitan-vue/src/utils/ruoyi.js b/zhitan-vue/src/utils/ruoyi.js
new file mode 100644
index 0000000..4efca08
--- /dev/null
+++ b/zhitan-vue/src/utils/ruoyi.js
@@ -0,0 +1,246 @@
+
+
+/**
+ * 閫氱敤js鏂规硶灏佽澶勭悊
+ * Copyright (c) 2019 ruoyi
+ */
+
+// 鏃ユ湡鏍煎紡鍖�
+export function parseTime(time, pattern) {
+  if (arguments.length === 0 || !time) {
+    return null
+  }
+  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+      time = parseInt(time)
+    } else if (typeof time === 'string') {
+      time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
+    }
+    if ((typeof time === 'number') && (time.toString().length === 10)) {
+      time = time * 1000
+    }
+    date = new Date(time)
+  }
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  }
+  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+    let value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') { return ['鏃�', '涓�', '浜�', '涓�', '鍥�', '浜�', '鍏�'][value] }
+    if (result.length > 0 && value < 10) {
+      value = '0' + value
+    }
+    return value || 0
+  })
+  return time_str
+}
+
+// 琛ㄥ崟閲嶇疆
+export function resetForm(refName) {
+  if (this.$refs[refName]) {
+    this.$refs[refName].resetFields();
+  }
+}
+
+// 娣诲姞鏃ユ湡鑼冨洿
+export function addDateRange(params, dateRange, propName) {
+  let search = params;
+  search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};
+  dateRange = Array.isArray(dateRange) ? dateRange : [];
+  if (typeof (propName) === 'undefined') {
+    search.params['beginTime'] = dateRange[0];
+    search.params['endTime'] = dateRange[1];
+  } else {
+    search.params['begin' + propName] = dateRange[0];
+    search.params['end' + propName] = dateRange[1];
+  }
+  return search;
+}
+
+// 鍥炴樉鏁版嵁瀛楀吀
+export function selectDictLabel(datas, value) {
+  if (value === undefined) {
+    return "";
+  }
+  var actions = [];
+  Object.keys(datas).some((key) => {
+    if (datas[key].value == ('' + value)) {
+      actions.push(datas[key].label);
+      return true;
+    }
+  })
+  if (actions.length === 0) {
+    actions.push(value);
+  }
+  return actions.join('');
+}
+
+// 鍥炴樉鏁版嵁瀛楀吀锛堝瓧绗︿覆鏁扮粍锛�
+export function selectDictLabels(datas, value, separator) {
+  if (value === undefined || value.length ===0) {
+    return "";
+  }
+  if (Array.isArray(value)) {
+    value = value.join(",");
+  }
+  var actions = [];
+  var currentSeparator = undefined === separator ? "," : separator;
+  var temp = value.split(currentSeparator);
+  Object.keys(value.split(currentSeparator)).some((val) => {
+    var match = false;
+    Object.keys(datas).some((key) => {
+      if (datas[key].value == ('' + temp[val])) {
+        actions.push(datas[key].label + currentSeparator);
+        match = true;
+      }
+    })
+    if (!match) {
+      actions.push(temp[val] + currentSeparator);
+    }
+  })
+  return actions.join('').substring(0, actions.join('').length - 1);
+}
+
+// 瀛楃涓叉牸寮忓寲(%s )
+export function sprintf(str) {
+  var args = arguments, flag = true, i = 1;
+  str = str.replace(/%s/g, function () {
+    var arg = args[i++];
+    if (typeof arg === 'undefined') {
+      flag = false;
+      return '';
+    }
+    return arg;
+  });
+  return flag ? str : '';
+}
+
+// 杞崲瀛楃涓诧紝undefined,null绛夎浆鍖栦负""
+export function parseStrEmpty(str) {
+  if (!str || str == "undefined" || str == "null") {
+    return "";
+  }
+  return str;
+}
+
+// 鏁版嵁鍚堝苟
+export function mergeRecursive(source, target) {
+  for (var p in target) {
+    try {
+      if (target[p].constructor == Object) {
+        source[p] = mergeRecursive(source[p], target[p]);
+      } else {
+        source[p] = target[p];
+      }
+    } catch (e) {
+      source[p] = target[p];
+    }
+  }
+  return source;
+};
+
+/**
+ * 鏋勯�犳爲鍨嬬粨鏋勬暟鎹�
+ * @param {*} data 鏁版嵁婧�
+ * @param {*} id id瀛楁 榛樿 'id'
+ * @param {*} parentId 鐖惰妭鐐瑰瓧娈� 榛樿 'parentId'
+ * @param {*} children 瀛╁瓙鑺傜偣瀛楁 榛樿 'children'
+ */
+export function handleTree(data, id, parentId, children) {
+  let config = {
+    id: id || 'id',
+    parentId: parentId || 'parentId',
+    childrenList: children || 'children'
+  };
+
+  var childrenListMap = {};
+  var nodeIds = {};
+  var tree = [];
+
+  for (let d of data) {
+    let parentId = d[config.parentId];
+    if (childrenListMap[parentId] == null) {
+      childrenListMap[parentId] = [];
+    }
+    nodeIds[d[config.id]] = d;
+    childrenListMap[parentId].push(d);
+  }
+
+  for (let d of data) {
+    let parentId = d[config.parentId];
+    if (nodeIds[parentId] == null) {
+      tree.push(d);
+    }
+  }
+
+  for (let t of tree) {
+    adaptToChildrenList(t);
+  }
+
+  function adaptToChildrenList(o) {
+    if (childrenListMap[o[config.id]] !== null) {
+      o[config.childrenList] = childrenListMap[o[config.id]];
+    }
+    if (o[config.childrenList]) {
+      for (let c of o[config.childrenList]) {
+        adaptToChildrenList(c);
+      }
+    }
+  }
+  return tree;
+}
+
+/**
+* 鍙傛暟澶勭悊
+* @param {*} params  鍙傛暟
+*/
+export function tansParams(params) {
+  let result = ''
+  for (const propName of Object.keys(params)) {
+    const value = params[propName];
+    var part = encodeURIComponent(propName) + "=";
+    if (value !== null && value !== "" && typeof (value) !== "undefined") {
+      if (typeof value === 'object') {
+        for (const key of Object.keys(value)) {
+          if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
+            let params = propName + '[' + key + ']';
+            var subPart = encodeURIComponent(params) + "=";
+            result += subPart + encodeURIComponent(value[key]) + "&";
+          }
+        }
+      } else {
+        result += part + encodeURIComponent(value) + "&";
+      }
+    }
+  }
+  return result
+}
+
+
+// 杩斿洖椤圭洰璺緞
+export function getNormalPath(p) {
+  if (p.length === 0 || !p || p == 'undefined') {
+    return p
+  };
+  let res = p.replace('//', '/')
+  if (res[res.length - 1] === '/') {
+    return res.slice(0, res.length - 1)
+  }
+  return res;
+}
+
+// 楠岃瘉鏄惁涓篵lob鏍煎紡
+export function blobValidate(data) {
+  return data.type !== 'application/json'
+}
diff --git a/zhitan-vue/src/utils/scroll-to.js b/zhitan-vue/src/utils/scroll-to.js
new file mode 100644
index 0000000..c5d8e04
--- /dev/null
+++ b/zhitan-vue/src/utils/scroll-to.js
@@ -0,0 +1,58 @@
+Math.easeInOutQuad = function(t, b, c, d) {
+  t /= d / 2
+  if (t < 1) {
+    return c / 2 * t * t + b
+  }
+  t--
+  return -c / 2 * (t * (t - 2) - 1) + b
+}
+
+// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
+var requestAnimFrame = (function() {
+  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
+})()
+
+/**
+ * Because it's so fucking difficult to detect the scrolling element, just move them all
+ * @param {number} amount
+ */
+function move(amount) {
+  document.documentElement.scrollTop = amount
+  document.body.parentNode.scrollTop = amount
+  document.body.scrollTop = amount
+}
+
+function position() {
+  return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
+}
+
+/**
+ * @param {number} to
+ * @param {number} duration
+ * @param {Function} callback
+ */
+export function scrollTo(to, duration, callback) {
+  const start = position()
+  const change = to - start
+  const increment = 20
+  let currentTime = 0
+  duration = (typeof (duration) === 'undefined') ? 500 : duration
+  var animateScroll = function() {
+    // increment the time
+    currentTime += increment
+    // find the value with the quadratic in-out easing function
+    var val = Math.easeInOutQuad(currentTime, start, change, duration)
+    // move the document.body
+    move(val)
+    // do the animation unless its over
+    if (currentTime < duration) {
+      requestAnimFrame(animateScroll)
+    } else {
+      if (callback && typeof (callback) === 'function') {
+        // the animation is done so lets callback
+        callback()
+      }
+    }
+  }
+  animateScroll()
+}
diff --git a/zhitan-vue/src/utils/theme.js b/zhitan-vue/src/utils/theme.js
new file mode 100644
index 0000000..f4badc6
--- /dev/null
+++ b/zhitan-vue/src/utils/theme.js
@@ -0,0 +1,49 @@
+// 澶勭悊涓婚鏍峰紡
+export function handleThemeStyle(theme) {
+	document.documentElement.style.setProperty('--el-color-primary', theme)
+	for (let i = 1; i <= 9; i++) {
+		document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`)
+	}
+	for (let i = 1; i <= 9; i++) {
+		document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`)
+	}
+}
+
+// hex棰滆壊杞瑀gb棰滆壊
+export function hexToRgb(str) {
+	str = str.replace('#', '')
+	let hexs = str.match(/../g)
+	for (let i = 0; i < 3; i++) {
+		hexs[i] = parseInt(hexs[i], 16)
+	}
+	return hexs
+}
+
+// rgb棰滆壊杞琀ex棰滆壊
+export function rgbToHex(r, g, b) {
+	let hexs = [r.toString(16), g.toString(16), b.toString(16)]
+	for (let i = 0; i < 3; i++) {
+		if (hexs[i].length == 1) {
+			hexs[i] = `0${hexs[i]}`
+		}
+	}
+	return `#${hexs.join('')}`
+}
+
+// 鍙樻祬棰滆壊鍊�
+export function getLightColor(color, level) {
+	let rgb = hexToRgb(color)
+	for (let i = 0; i < 3; i++) {
+		rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i])
+	}
+	return rgbToHex(rgb[0], rgb[1], rgb[2])
+}
+
+// 鍙樻繁棰滆壊鍊�
+export function getDarkColor(color, level) {
+	let rgb = hexToRgb(color)
+	for (let i = 0; i < 3; i++) {
+		rgb[i] = Math.floor(rgb[i] * (1 - level))
+	}
+	return rgbToHex(rgb[0], rgb[1], rgb[2])
+}
diff --git a/zhitan-vue/src/utils/validate.js b/zhitan-vue/src/utils/validate.js
new file mode 100644
index 0000000..702add4
--- /dev/null
+++ b/zhitan-vue/src/utils/validate.js
@@ -0,0 +1,93 @@
+/**
+ * 鍒ゆ柇url鏄惁鏄痟ttp鎴杊ttps 
+ * @param {string} path
+ * @returns {Boolean}
+ */
+ export function isHttp(url) {
+  return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
+}
+
+/**
+ * 鍒ゆ柇path鏄惁涓哄閾�
+ * @param {string} path
+ * @returns {Boolean}
+ */
+ export function isExternal(path) {
+  return /^(https?:|mailto:|tel:)/.test(path)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUsername(str) {
+  const valid_map = ['admin', 'editor']
+  return valid_map.indexOf(str.trim()) >= 0
+}
+
+/**
+ * @param {string} url
+ * @returns {Boolean}
+ */
+export function validURL(url) {
+  const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
+  return reg.test(url)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validLowerCase(str) {
+  const reg = /^[a-z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUpperCase(str) {
+  const reg = /^[A-Z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validAlphabets(str) {
+  const reg = /^[A-Za-z]+$/
+  return reg.test(str)
+}
+
+/**
+ * @param {string} email
+ * @returns {Boolean}
+ */
+export function validEmail(email) {
+  const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+  return reg.test(email)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function isString(str) {
+  if (typeof str === 'string' || str instanceof String) {
+    return true
+  }
+  return false
+}
+
+/**
+ * @param {Array} arg
+ * @returns {Boolean}
+ */
+export function isArray(arg) {
+  if (typeof Array.isArray === 'undefined') {
+    return Object.prototype.toString.call(arg) === '[object Array]'
+  }
+  return Array.isArray(arg)
+}
diff --git a/zhitan-vue/src/views/alarmmanage/alarmrecord/alarmRecord.vue b/zhitan-vue/src/views/alarmmanage/alarmrecord/alarmRecord.vue
new file mode 100644
index 0000000..801fffe
--- /dev/null
+++ b/zhitan-vue/src/views/alarmmanage/alarmrecord/alarmRecord.vue
@@ -0,0 +1,193 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+            <el-form-item prop="eierarchyFlag">
+              <el-radio-group v-model="form.eierarchyFlag">
+                <el-radio label="B" name="eierarchyFlag">鏈骇</el-radio>
+                <el-radio label="ALL" name="eierarchyFlag">鍖呭惈涓嬬骇</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="鏃堕棿閫夋嫨">
+              <el-date-picker v-model="form.dataTime" type="datetimerange" format="YYYY-MM-DD HH:mm:ss"
+                value-format="YYYY-MM-DD HH:mm:ss" placeholder="鏃堕棿" style="width: 370px" unlink-panels
+                time-format="HH:mm:ss" />
+            </el-form-item>
+            <el-form-item label="鎶ヨ绫诲埆" prop="indexType">
+              <el-select v-model="form.indexType" placeholder="璇烽�夋嫨鎶ヨ绫诲埆" style="width: 200px">
+                <el-option v-for="dict in alarm_record_category" :key="dict.value" :label="dict.label"
+                  :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <!-- <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+              <el-select v-model="form.energyType" placeholder="璇烽�夋嫨鑳芥簮绫诲瀷">
+                <el-option :label="item.enername" :value="item.enersno" v-for="item in energyTypeList"
+                  :key="item.enersno" />
+              </el-select>
+            </el-form-item> -->
+            <el-form-item label="鎸囨爣鍚嶇О" prop="indexName">
+              <el-input v-model="form.indexName" placeholder="璇疯緭鍏ユ寚鏍囧悕绉�" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <BaseCard :title="currentNode ? currentNode.label + '--鑺傜偣閰嶇疆' : '鏆傛棤鑺傜偣閰嶇疆'
+          ">
+          <div class="table-box">
+            <el-table :data="tableData" v-loading="loading" height="calc(100vh - 450px)">
+              <el-table-column type="index" label="搴忓彿" width="70" />
+              <el-table-column label="鐢ㄨ兘鍗曞厓" prop="modelName" align="center" show-overflow-tooltip />
+              <el-table-column label="鎸囨爣鍚嶇О" prop="indexName" align="center" show-overflow-tooltip />
+              <el-table-column label="鎶ヨ绫诲埆" prop="indexType" align="center" show-overflow-tooltip :formatter="(row, column) =>
+                proxy.selectDictLabel(alarm_record_category, row.indexType)
+                " />
+              <el-table-column label="鑳芥簮绫诲瀷" prop="energyId" align="center" show-overflow-tooltip :formatter="(row, column) => formatterLabel(energyTypeList, row.energyId)
+                " />
+              <el-table-column label="棰勮鍊�" prop="energyType" align="center" show-overflow-tooltip />
+              <el-table-column label="鎶ヨ鍊�" prop="alarmValue" align="center" show-overflow-tooltip />
+              <el-table-column label="鎶ヨ鏃堕棿" prop="alarmBeginTime" align="center" show-overflow-tooltip />
+            </el-table>
+          </div>
+        </BaseCard>
+
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+          v-model:limit="queryParams.pageSize" @pagination="getList(currentNode)" />
+      </div>
+    </div>
+  </div>
+</template>
+<script setup name="alarmRecord">
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+const { proxy } = getCurrentInstance();
+let { alarm_record_category } = proxy.useDict("alarm_record_category");
+import { historicalAlarm } from "@/api/alarmManage/alarmManage";
+let form = ref({
+  eierarchyFlag: "B",
+  dataTime: [
+    proxy.dayjs(new Date()).format("YYYY-MM-DD 00:00:00"),
+    proxy.dayjs(new Date()).format("YYYY-MM-DD 23:59:59"),
+  ],
+  nodeId: "",
+  indexType: "",
+  // energyType: '',
+  indexName: "",
+});
+const energyTypeList = ref();
+function getEnergyTypeList() {
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+    form.value.indexType = alarm_record_category.value[0].value;
+    // form.value.energyType = energyTypeList.value[0].enersno
+    getList();
+  });
+}
+// getEnergyTypeList()
+let currentNode = ref();
+function handleNodeClick(data) {
+  currentNode.value = data;
+  getEnergyTypeList();
+}
+
+let tableData = ref([]);
+let total = ref(0);
+let loading = ref(false);
+let queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+});
+
+function formatterLabel(list, value) {
+  let dict = list.find((item) => item.enersno == value);
+  return dict ? dict.enername : "";
+}
+
+function getList() {
+  form.value.nodeId = currentNode.value.id;
+  loading.value = true;
+  historicalAlarm({
+    ...form.value,
+    ...queryParams.value,
+    beginTime: form.value.dataTime[0],
+    endTime: form.value.dataTime[1],
+  }).then((response) => {
+    console.log(11, response);
+    if (response.code === 200) {
+      tableData.value = response.rows;
+      total.value = response.total;
+      loading.value = false;
+    } else {
+      proxy.$modal.msgError(response.msg);
+    }
+  });
+}
+
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+function resetQuery() {
+  form.value = {
+    eierarchyFlag: 'B',
+    dataTime: [proxy.dayjs(new Date()).format("YYYY-MM-DD 00:00:00"), proxy.dayjs(new Date()).format("YYYY-MM-DD 23:59:59")],
+    nodeId: '',
+    indexType: alarm_record_category.value[0].value,
+    // energyType: '',
+    indexName: '',
+  }
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+  };
+  getList();
+}
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+.page-box {
+  height: calc(100vh - 145px);
+
+  .tree-box {
+    height: calc(100% - 70px);
+    overflow-y: auto !important;
+  }
+
+  .select-box {
+    display: flex;
+    align-items: center;
+
+    ::v-deep(.el-icon) {
+      color: #fff;
+      margin: 0 10px 0 15px;
+      font-size: 20px;
+      // &:hover{
+      //     color: #3371EB;
+      // }
+    }
+  }
+
+  .node-opt {
+    flex: 1;
+    text-align: right;
+    margin-right: 5px;
+
+    ::v-deep(.el-icon) {
+      color: #fff;
+      margin-right: 5px;
+    }
+  }
+}
+
+.table-box {
+  height: calc(100vh - 464px);
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/alarmmanage/energyconsumption/energyConsumption.vue b/zhitan-vue/src/views/alarmmanage/energyconsumption/energyConsumption.vue
new file mode 100644
index 0000000..38a308a
--- /dev/null
+++ b/zhitan-vue/src/views/alarmmanage/energyconsumption/energyConsumption.vue
@@ -0,0 +1,364 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select v-model="queryParams.timeType" placeholder="鏈熼棿" clearable style="width: 100%"
+                @change="handleTimeType">
+                <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker v-model="queryParams.dataTime" :clearable="false"
+                :type="queryParams.timeType == 'YEAR' ? 'year' : queryParams.timeType == 'MONTH' ? 'month' : 'date'"
+                :format="queryParams.timeType == 'YEAR' ? 'YYYY' : queryParams.timeType == 'MONTH' ? 'YYYY-MM' : 'YYYY-MM-DD'"
+                value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 100%" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <el-row :gutter="24" class="mb20">
+          <el-col :span="12">
+            <BaseCard title="鎶ヨ绫诲瀷鍗犳瘮">
+              <div class="chart-box">
+                <div id="Chart1" />
+              </div>
+            </BaseCard>
+          </el-col>
+          <el-col :span="12">
+            <BaseCard title="鑳芥簮绫诲瀷鍗犳瘮">
+              <div class="chart-box">
+                <div id="Chart2" />
+              </div>
+            </BaseCard>
+          </el-col>
+        </el-row>
+
+        <BaseCard title="鎶ヨ娆℃暟">
+          <div class="table-box">
+            <div class="chart-box">
+              <div id="Chart3" />
+            </div>
+          </div>
+        </BaseCard>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from 'echarts';
+import { onMounted, reactive, ref } from 'vue';
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+let { alarm_record_category } = proxy.useDict("alarm_record_category");
+let energyTypeList = ref([])
+function getEnergyTypeList() {
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+  });
+}
+getEnergyTypeList()
+function formatterLabel(list, value) {
+  console.log(list, value);
+
+  let dict = list.find(item => item.enersno == value)
+  return dict ? dict.enername : ''
+}
+
+
+let queryParams = ref({
+  timeType: null,
+  dataTime: null,
+  nodeId: null
+})
+
+import { getByNodeId, getCountInfo } from "@/api/alarmManage/alarmManage";
+import { el } from 'element-plus/es/locales.mjs';
+let form = ref({});
+let currentNode = ref()
+
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+function handleNodeClick(e) {
+  currentNode.value = e
+  queryParams.value.nodeId = e.id
+  handleTimeType('YEAR')
+  getByNodeIdFun()
+}
+
+function getByNodeIdFun() {
+  getByNodeId(queryParams.value).then(res => {
+    let alarmList = []
+    let energyList = []
+    let alarmNumberList = {
+      data: [],
+      xAxisData: []
+    }
+    console.log(111, res);
+    if (res.code == 200) {
+      let { data } = res
+      alarmList = data.alarmProportion.map(item => {
+        return {
+          name: proxy.selectDictLabel(alarm_record_category.value, item.energyName),
+          value: item.percentage
+        }
+      })
+      energyList = data.energyProportion.map(item => {
+        return {
+          name: formatterLabel(energyTypeList.value, item.energyName),
+          value: item.percentage
+        }
+      })
+      for (const item of data.chartDataList) {
+        alarmNumberList.xAxisData.push(item.xdata)
+        alarmNumberList.data.push(item.yvalue)
+      }
+
+      pieChart('Chart1', alarmList, '鎶ヨ绫诲瀷鍗犳瘮')
+      pieChart('Chart2', energyList, '鑳芥簮绫诲瀷鍗犳瘮')
+      getChart('Chart3', alarmNumberList)
+    }
+  })
+}
+
+// getByNodeIdFun()
+function handleQuery() {
+  getByNodeIdFun()
+}
+function resetQuery() {
+  handleTimeType('YEAR')
+  getByNodeIdFun()
+}
+
+
+function pieChart(Id, data, name) {
+  let total = 0;
+  data.forEach(function (val, idx, arr) {
+    total += val.value;
+  })
+  const myChart = echarts.init(document.getElementById(Id));
+  myChart.setOption({
+    color: ['#4D94FF', '#00C27C', '#F0142F', '#F2D261', '#0E7CE2', '#FF8352', '#E271DE', '#F8456B', '#00FFFF', '#4AEAB0'],
+    grid: {
+      top: '20%',
+      left: '1%',
+      right: '1%',
+      bottom: '0%',
+      containLabel: true
+    },
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'vertical',
+      top: 'center',
+      icon: 'circle',
+      itemWidth: 14,
+      itemHeight: 14,
+      right: '2%',
+      itemGap: 10,
+      textStyle: {
+        align: 'left',
+        verticalAlign: 'middle',
+        rich: {
+          name: {
+            color: '#999',
+            fontSize: 14,
+          },
+          value: {
+            color: '#999',
+            fontSize: 14,
+          },
+          rate: {
+            color: '#999',
+            fontSize: 14,
+          },
+        },
+      },
+      formatter: (name) => {
+        if (data.length) {
+          let target, percent;
+          for (let i = 0; i < data.length; i++) {
+            if (data[i].name === name) {
+              target = data[i].value;
+              percent = ((target / total) * 100).toFixed(2);
+            }
+          }
+          return `{name|${name}  }{value| ${target}} {rate| ${percent}%}`;
+
+        } else {
+          return `{name|${name}  }{value| ${0}} {rate| ${0}%}`;
+
+        }
+      },
+    },
+    series: [{
+      name,
+      type: 'pie',
+      radius: ['45%', '70%'],
+      center: ['35%', '50%'],
+      avoidLabelOverlap: false,
+      label: {
+        show: false,
+        overflow: 'none',
+        formatter: '{b} {d}%  \n {c} tce',
+      },
+      data,
+    }]
+  })
+}
+function getChart(Id, dataList) {
+  const myChart3 = echarts.init(document.getElementById(Id));
+  myChart3.setOption(
+    {
+
+      grid: {
+        left: '3%',
+        right: '2%',
+        bottom: '2%',
+        containLabel: true
+      },
+      tooltip: {
+        trigger: 'axis',
+
+      },
+      xAxis: {
+        type: 'category',
+        // boundaryGap: false,
+        data: dataList.xAxisData,
+        axisPointer: {
+          type: 'shadow'
+        },
+        axisTick: {
+          show: false,
+          alignWithLabel: true,
+          length: 5
+        },
+        // 鍧愭爣杞村埢搴︾嚎鏍峰紡
+        axisTick: {
+          show: false,
+          length: 5,
+          lineStyle: {
+            color: '#ddd'
+          }
+        },
+
+        // 鍒嗗壊绾�
+        splitLine: {
+          show: false,
+          lineStyle: {
+            type: 'dashed',
+            color: 'rgba(220,222,226,0.4)'
+          }
+        },
+        axisLabel: {
+          color: '#999',
+          fontSize: 14,
+          padding: [5, 0, 0, 0],
+          //   formatter: '{value} ml'
+        }
+      },
+      yAxis: {
+        type: 'value',
+        name: '锛堟锛�',
+        // 璁剧疆鍚嶇О鏍峰紡
+        nameTextStyle: {
+          color: ' #CEE3FF',
+          fontSize: 14,
+          padding: [0, 0, 5, 0],
+        },
+        // 鍧愭爣杞村埢搴�
+        axisTick: {
+          show: false,
+          alignWithLabel: true,
+          length: 5
+        },
+        // 鍧愭爣杞村埢搴︾嚎鏍峰紡
+        axisTick: {
+          show: false,
+          length: 5,
+          lineStyle: {
+            color: ''
+          }
+        },
+
+        // 鍒嗗壊绾�
+        splitLine: {
+          show: true,
+          lineStyle: {
+            type: 'dashed',
+            color: 'rgba(220,222,226,0.4)'
+          }
+        },
+
+        // 鍧愭爣杞村埢搴﹂棿闅�
+        // interval: '50',
+        // // 鍧愭爣杞存渶灏忓��
+        // min: 'dataMin',
+        // // 鍧愭爣杞存渶澶у��
+        // max: 'dataMax',
+        // // 鍧愭爣杞村皬鏁扮偣绮惧害
+        // precision: 0,
+        // // 鍧愭爣杞村埢搴︽枃鏈殑甯冨眬鏈濆悜
+        // position: 'left'
+        axisLabel: {
+          color: '#B2B8C2',
+          fontSize: 14,
+          //   formatter: '{value} ml'
+        }
+      },
+      series: [
+        {
+          name: "鎶ヨ娆℃暟",
+          type: "bar",
+          barWidth: '17',
+          stack: 'number',
+          data: dataList.data,
+          tooltip: {
+            show: false,
+          }
+        },
+        {
+          name: '鎶ヨ娆℃暟',
+          type: 'line',
+          symbol: 'none', // 璁剧疆涓� 'none' 鍘绘帀鍦嗙偣
+          lineStyle: {
+            color: '#EE0303'
+          },
+          data: dataList.data,
+        },
+      ]
+    })
+
+  window.addEventListener("resize", () => {
+    myChart1.resize();
+    myChart2.resize();
+    myChart3.resize();
+  }, { passive: true });
+}
+
+
+
+
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+
+.chart-box {
+  height: calc((100vh - 410px)/2) !important;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/alarmmanage/measuremen/measuremen.vue b/zhitan-vue/src/views/alarmmanage/measuremen/measuremen.vue
new file mode 100644
index 0000000..b46e7a2
--- /dev/null
+++ b/zhitan-vue/src/views/alarmmanage/measuremen/measuremen.vue
@@ -0,0 +1,399 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select v-model="queryParams.timeType" placeholder="鏈熼棿" clearable style="width: 120px"
+                @change="handleTimeType">
+                <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker v-model="queryParams.dataTime" :clearable="false"
+                :type="queryParams.timeType == 'YEAR' ? 'year' : queryParams.timeType == 'MONTH' ? 'month' : 'date'"
+                :format="queryParams.timeType == 'YEAR' ? 'YYYY' : queryParams.timeType == 'MONTH' ? 'YYYY-MM' : 'YYYY-MM-DD'"
+                value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 100%" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+
+        <div class="mt20 mb20 text-center data-item">
+          鏈敤鑳藉崟鍏冩寚鏍�<span>{{ dataArray.indexCount }}</span>涓紝
+          鏈勾搴︽姤璀�<span>{{ dataArray.yearCount }}</span>娆★紝鏈湀<span>{{ dataArray.monthCount }}</span>娆�
+        </div>
+        <el-row :gutter="24" class="mb20">
+          <el-col :span="12">
+            <BaseCard title="鎶ヨ绫诲瀷鍗犳瘮">
+              <div class="chart-box">
+                <div id="Chart1" />
+              </div>
+            </BaseCard>
+          </el-col>
+          <el-col :span="12">
+            <BaseCard title="鑳芥簮绫诲瀷鍗犳瘮">
+              <div class="chart-box">
+                <div id="Chart2" />
+              </div>
+            </BaseCard>
+          </el-col>
+        </el-row>
+
+        <BaseCard title="鎶ヨ娆℃暟">
+          <div class="table-box">
+            <div class="chart-box">
+              <div id="Chart3" />
+            </div>
+          </div>
+        </BaseCard>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from 'echarts';
+import { onMounted, reactive, ref } from 'vue';
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+let { alarm_record_category } = proxy.useDict("alarm_record_category");
+let energyTypeList = ref([])
+function getEnergyTypeList() {
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+    queryParams.value.energyType = energyTypeList.value[0].enersno;
+  });
+}
+getEnergyTypeList()
+function formatterLabel(list, value) {
+  let dict = list.find(item => item.enersno == value)
+  return dict ? dict.enername : ''
+}
+
+
+let queryParams = ref({
+  timeType: null,
+  dataTime: null,
+  nodeId: null
+})
+
+import { getByNodeId, getCountInfo } from "@/api/alarmManage/alarmManage";
+import { el } from 'element-plus/es/locales.mjs';
+let form = ref({});
+let currentNode = ref()
+
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+function handleNodeClick(e) {
+  currentNode.value = e
+  queryParams.value.nodeId = e.id
+  handleTimeType(period.value[0].value);
+
+  getByNodeIdFun()
+}
+
+function getByNodeIdFun() {
+  getByNodeId(queryParams.value).then(res => {
+    let alarmList = []
+    let energyList = []
+    let alarmNumberList = {
+      data: [],
+      xAxisData: []
+    }
+    if (res.code == 200) {
+      let { data } = res
+      alarmList = data.alarmProportion.map(item => {
+        return {
+          name: proxy.selectDictLabel(alarm_record_category.value, item.energyName),
+          value: item.count,
+          percentage: item.percentage
+        }
+      })
+      energyList = data.energyProportion.map(item => {
+        return {
+          name: formatterLabel(energyTypeList.value, item.energyName),
+          value: item.count,
+          percentage: item.percentage
+        }
+      })
+      for (const item of data.chartDataList) {
+        alarmNumberList.xAxisData.push(item.xdata)
+        alarmNumberList.data.push(item.yvalue)
+      }
+
+      pieChart('Chart1', alarmList, '鎶ヨ绫诲瀷鍗犳瘮')
+      pieChart('Chart2', energyList, '鑳芥簮绫诲瀷鍗犳瘮')
+      getChart('Chart3', alarmNumberList)
+    }
+  })
+}
+
+// getByNodeIdFun()
+function handleQuery() {
+  getByNodeIdFun()
+}
+function resetQuery() {
+  handleTimeType('YEAR')
+  getByNodeIdFun()
+}
+let dataArray = ref({
+  indexCount: 0,
+  yearCount: 0,
+  monthCount: 0
+})
+function getCountInfoFun() {
+  getCountInfo(queryParams.value).then(res => {
+    if (res.code == 200) {
+      dataArray.value = res.data
+    }
+  })
+}
+
+getCountInfoFun()
+function pieChart(Id, data, name) {
+  console.log(data)
+  let total = 0;
+  data.forEach(function (val, idx, arr) {
+    total += val.value;
+  })
+  const myChart = echarts.init(document.getElementById(Id));
+  myChart.setOption({
+    color: ['#4D94FF', '#00C27C', '#F0142F', '#F2D261', '#0E7CE2', '#FF8352', '#E271DE', '#F8456B', '#00FFFF', '#4AEAB0'],
+    grid: {
+      top: '20%',
+      left: '1%',
+      right: '1%',
+      bottom: '0%',
+      containLabel: true
+    },
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'vertical',
+      top: 'center',
+      icon: 'circle',
+      itemWidth: 14,
+      itemHeight: 14,
+      right: '2%',
+      itemGap: 10,
+      textStyle: {
+        align: 'left',
+        verticalAlign: 'middle',
+        rich: {
+          name: {
+            color: '#999',
+            fontSize: 14,
+          },
+          value: {
+            color: '#999',
+            fontSize: 14,
+          },
+          rate: {
+            color: '#999',
+            fontSize: 14,
+          },
+        },
+      },
+      formatter: (name) => {
+        for (let i = 0; i < data.length; i++) {
+          if (data[i].name === name) {
+            return `{name|${data[i].name}  }{value| ${data[i].value}} {rate| ${data[i].percentage}%}`;
+          }
+        }
+      },
+    },
+    series: [{
+      name,
+      type: 'pie',
+      radius: ['45%', '70%'],
+      center: ['35%', '50%'],
+      avoidLabelOverlap: false,
+      label: {
+        show: false,
+        overflow: 'none',
+        formatter: '{b} {d}%  \n {c} tce',
+      },
+      data,
+    }]
+  })
+}
+function getChart(Id, dataList) {
+  const myChart3 = echarts.init(document.getElementById(Id));
+  myChart3.setOption(
+    {
+
+      grid: {
+        left: '3%',
+        right: '2%',
+        bottom: '2%',
+        containLabel: true
+      },
+      tooltip: {
+        trigger: 'axis',
+
+      },
+      xAxis: {
+        type: 'category',
+        // boundaryGap: false,
+        data: dataList.xAxisData,
+        axisPointer: {
+          type: 'shadow'
+        },
+        axisTick: {
+          show: false,
+          alignWithLabel: true,
+          length: 5
+        },
+        // 鍧愭爣杞村埢搴︾嚎鏍峰紡
+        axisTick: {
+          show: false,
+          length: 5,
+          lineStyle: {
+            color: '#ddd'
+          }
+        },
+
+        // 鍒嗗壊绾�
+        splitLine: {
+          show: false,
+          lineStyle: {
+            type: 'dashed',
+            color: 'rgba(220,222,226,0.4)'
+          }
+        },
+        axisLabel: {
+          color: '#999',
+          fontSize: 14,
+          padding: [5, 0, 0, 0],
+          //   formatter: '{value} ml'
+        }
+      },
+      yAxis: {
+        type: 'value',
+        name: '锛堟锛�',
+        // 璁剧疆鍚嶇О鏍峰紡
+        nameTextStyle: {
+          color: ' #CEE3FF',
+          fontSize: 14,
+          padding: [0, 0, 5, 0],
+        },
+        // 鍧愭爣杞村埢搴�
+        axisTick: {
+          show: false,
+          alignWithLabel: true,
+          length: 5
+        },
+        // 鍧愭爣杞村埢搴︾嚎鏍峰紡
+        axisTick: {
+          show: false,
+          length: 5,
+          lineStyle: {
+            color: ''
+          }
+        },
+
+        // 鍒嗗壊绾�
+        splitLine: {
+          show: true,
+          lineStyle: {
+            type: 'dashed',
+            color: 'rgba(220,222,226,0.4)'
+          }
+        },
+
+        // 鍧愭爣杞村埢搴﹂棿闅�
+        // interval: '50',
+        // // 鍧愭爣杞存渶灏忓��
+        // min: 'dataMin',
+        // // 鍧愭爣杞存渶澶у��
+        // max: 'dataMax',
+        // // 鍧愭爣杞村皬鏁扮偣绮惧害
+        // precision: 0,
+        // // 鍧愭爣杞村埢搴︽枃鏈殑甯冨眬鏈濆悜
+        // position: 'left'
+        axisLabel: {
+          color: '#B2B8C2',
+          fontSize: 14,
+          //   formatter: '{value} ml'
+        }
+      },
+      series: [
+        {
+          name: "鎶ヨ娆℃暟",
+          type: "bar",
+          barWidth: '17',
+          stack: 'number',
+          data: dataList.data,
+          tooltip: {
+            show: false,
+          }
+        },
+        {
+          name: '鎶ヨ娆℃暟',
+          type: 'line',
+          symbol: 'none', // 璁剧疆涓� 'none' 鍘绘帀鍦嗙偣
+          lineStyle: {
+            color: '#EE0303'
+          },
+          data: dataList.data,
+        },
+      ]
+    })
+
+  window.addEventListener("resize", () => {
+    myChart1.resize();
+    myChart2.resize();
+    myChart3.resize();
+  }, { passive: true });
+}
+
+
+
+
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+
+.chart-box {
+  height: calc((100vh - 410px)/2) !important;
+}
+
+.data-item {
+  color: #999;
+  font-size: 22px;
+  font-weight: bold;
+
+  span {
+    color: #397AEE;
+    margin: 0 5px;
+  }
+}
+
+.themeDark {
+  .data-item {
+    color: #f6f5f5;
+    font-size: 22px;
+    font-weight: bold;
+
+    span {
+      color: #397AEE;
+      margin: 0 5px;
+    }
+  }
+
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/auxiliaryentry/electricityinput/components/EditModal.vue b/zhitan-vue/src/views/auxiliaryentry/electricityinput/components/EditModal.vue
new file mode 100644
index 0000000..8e93ec7
--- /dev/null
+++ b/zhitan-vue/src/views/auxiliaryentry/electricityinput/components/EditModal.vue
@@ -0,0 +1,141 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="800" @close="handleClose">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="130px" v-loading="loading">
+            <el-row>
+                <el-col :span="11">
+                    <el-form-item label="鏈熼棿" prop="type">
+                        <el-select v-model="form.type" placeholder="鏈熼棿" style="width: 100%" @change="handleTimeType">
+                            <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value"
+                                v-show="dict.value != 'DAY'" />
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item label="鏃堕棿" prop="time">
+                        <el-date-picker v-model="form.time" type="year" v-if="form.type == 'YEAR'" range-separator="鍒�"
+                            format="YYYY" value-format="YYYY" placeholder="鏃堕棿" style="width: 100%" :clearable="false" />
+                        <el-date-picker v-model="form.time" type="month" v-else format="YYYY-MM" value-format="YYYY-MM"
+                            placeholder="鏃堕棿" style="width: 100%" :clearable="false" />
+                    </el-form-item>
+                    <el-form-item label="鎬荤數閲�(鍗冪摝鏃�)" prop="electricityNum">
+                        <el-input-number v-model="form.electricityNum" :min="1" placeholder="璇疯緭鍏�" style="width: 100%"
+                            controls-position="right" step-strictly />
+                    </el-form-item>
+                    <el-form-item label="鎬荤數璐�(鍏�)" prop="electricityFee">
+                        <el-input-number v-model="form.electricityFee" :min="1" placeholder="璇疯緭鍏�" style="width: 100%"
+                            controls-position="right" step-strictly />
+                    </el-form-item>
+                    <el-form-item label="鍔熺巼鍥犳暟" prop="powerFactor">
+                        <el-input-number v-model="form.powerFactor" :min="1" placeholder="璇疯緭鍏�" style="width: 100%"
+                            controls-position="right" step-strictly />
+                    </el-form-item>
+                </el-col>
+                <el-col :span="11">
+                    <el-form-item label="灏�(鍗冪摝鏃�)" prop="sharpElectricity">
+                        <el-input-number v-model="form.sharpElectricity" :min="1" placeholder="璇疯緭鍏�" style="width: 100%"
+                            controls-position="right" step-strictly />
+                    </el-form-item>
+                    <el-form-item label="宄�(鍗冪摝鏃�)" prop="peakElectricity">
+                        <el-input-number v-model="form.peakElectricity" :min="1" placeholder="璇疯緭鍏�" style="width: 100%"
+                            controls-position="right" step-strictly />
+                    </el-form-item>
+                    <el-form-item label="骞�(鍗冪摝鏃�)" prop="flatElectricity">
+                        <el-input-number v-model="form.flatElectricity" :min="1" placeholder="璇疯緭鍏�" style="width: 100%"
+                            controls-position="right" step-strictly />
+                    </el-form-item>
+                    <el-form-item label="璋�(鍗冪摝鏃�)" prop="valleyElectricity">
+                        <el-input-number v-model="form.valleyElectricity" :min="1" placeholder="璇疯緭鍏�" style="width: 100%"
+                            controls-position="right" step-strictly />
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+        </el-form>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+            <el-button @click="handleClose">鍙� 娑�</el-button>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+import { electricityInputAdd, electricityInputEdit } from '@/api/auxiliaryEntry/electricityInput.js'
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+let emit = defineEmits(['getList'])
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+    type: null,
+    time: null,
+    electricityNum: null,
+    electricityFee: null,
+    powerFactor: null,
+    sharpElectricity: null,
+    peakElectricity: null,
+    flatElectricity: null,
+    valleyElectricity: null,
+})
+
+function handleTimeType(e) {
+    form.value.type = e;
+   form.value.time = '';
+}
+const formRules = ref({
+    type: [{ required: true, trigger: "change", message: "璇烽�夋嫨鏈熼棿" }],
+    time: [{ required: true, trigger: "change", message: "璇烽�夋嫨鏃堕棿" }],
+    electricityNum: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+    electricityFee: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+    powerFactor: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+    sharpElectricity: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+    peakElectricity: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+    flatElectricity: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+    valleyElectricity: [{ required: true, trigger: "blur", message: "璇疯緭鍏�" }],
+});
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            let obj = form.value.id ? electricityInputEdit(form.value) : electricityInputAdd(form.value)
+            obj.then((res) => {
+                if (res.code == 200) {
+                    proxy.$modal.msgSuccess(res.msg);
+                    handleClose()
+                    emit('getList')
+                } else {
+                    proxy.$modal.msgError(res.msg);
+                }
+             
+            }).catch((err) => {
+            }) 
+        }
+    })
+}
+
+
+function handleOpen(row) {
+    if (row && row.id) {
+        title.value = "缂栬緫"
+        form.value = JSON.parse(JSON.stringify(row))
+    } else {
+        console.log(form.value)
+        title.value = "娣诲姞"
+    }
+    visible.value = true
+}
+
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+    }
+}
+
+
+defineExpose({ handleOpen })
+
+</script>
+
+
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/auxiliaryentry/electricityinput/electricityinput.vue b/zhitan-vue/src/views/auxiliaryentry/electricityinput/electricityinput.vue
new file mode 100644
index 0000000..1f73fb4
--- /dev/null
+++ b/zhitan-vue/src/views/auxiliaryentry/electricityinput/electricityinput.vue
@@ -0,0 +1,119 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="form" ref="formRef" :inline="true">
+                <el-form-item label="鏈熼棿" prop="type">
+                    <el-select v-model="form.type" placeholder="鏈熼棿" style="width: 100%" @change="handleTimeType">
+                        <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value"
+                            v-show="dict.value != 'DAY'" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="鏃堕棿">
+                    <el-date-picker v-model="form.time" type="year" v-if="form.type == 'YEAR'" range-separator="鍒�"
+                        format="YYYY" value-format="YYYY" placeholder="鏃堕棿" style="width: 100%" :clearable="false" />
+                    <el-date-picker v-model="form.time" type="month" v-else format="YYYY-MM" value-format="YYYY-MM"
+                        placeholder="鏃堕棿" style="width: 100%" :clearable="false" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+
+        </div>
+        <div class="table-box">
+            <div class="mt20 mb20">
+                <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+            </div>
+            <el-table :data="tableData" v-loading="loading">
+                <el-table-column prop="time" label="鏃堕棿" show-overflow-tooltip align="center" />
+                <el-table-column prop="electricityNum" label="鎬荤數閲�(鍗冪摝鏃�)" show-overflow-tooltip align="center" />
+                <el-table-column prop="electricityFee" label="鎬荤數璐�(鍏�)" show-overflow-tooltip align="center" />
+                <el-table-column prop="powerFactor" label="鍔熺巼鍥犳暟" show-overflow-tooltip align="center" />
+                <el-table-column prop="electricityNum" label="灏�(鍗冪摝鏃�)" show-overflow-tooltip align="center" />
+                <el-table-column prop="peakElectricity" label="宄�(鍗冪摝鏃�)" show-overflow-tooltip align="center" />
+                <el-table-column prop="flatElectricity" label="骞�(鍗冪摝鏃�)" show-overflow-tooltip align="center" />
+                <el-table-column prop="valleyElectricity" label="璋�(鍗冪摝鏃�)" show-overflow-tooltip align="center" />
+                <el-table-column prop="createTime" label="鎻愪氦鏃堕棿" show-overflow-tooltip align="center" />
+                <el-table-column label="鎿嶄綔" width="280" align="center">
+                    <template #default="scope">
+                        <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row)">
+                            淇敼
+                        </el-button>
+                        <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                            鍒犻櫎
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+        </div>
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize" @pagination="getList" />
+        <EditModal ref="EditModalRef" @getList="resetQuery" />
+    </div>
+</template>
+<script setup>
+import { electricityInputList, electricityInputDel } from '@/api/auxiliaryEntry/electricityInput.js'
+import EditModal from './components/EditModal.vue'
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+let form = ref({
+    type: null,
+    time: null
+})
+// handleTimeType('YEAR')
+function handleTimeType(e) {
+    form.value.type = e;
+    form.value.time = proxy.dayjs(new Date()).format(e == "YEAR" ? "YYYY" : "YYYY-MM");
+}
+let loading = ref(false)
+let EditModalRef = ref()
+let tableData = ref([])
+let total = ref(0)
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+})
+
+//鑾峰彇鍒楄〃
+function getList() {
+    loading.value = true
+    electricityInputList({ ...queryParams.value, ...form.value }).then(res => {
+        tableData.value = res.rows
+        total.value = res.total
+        loading.value = false
+    })
+}
+getList()
+function handleAdd(row) {
+    if (EditModalRef.value) {
+        EditModalRef.value.handleOpen(row)
+    }
+}
+
+function handleDel(row) {
+    proxy.$modal
+        .confirm('鏄惁纭鍒犻櫎鏃堕棿涓�"' + row.time + '"鐨勬暟鎹」锛�')
+        .then(function () {
+            return electricityInputDel(row.id);
+        })
+        .then(() => {
+            resetQuery();
+            proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        })
+        .catch(() => { });
+}
+function resetQuery() {
+    form.value = {}
+    queryParams.value.pageNum = 1
+    getList()
+}
+function handleQuery() {
+    queryParams.value.pageNum = 1
+    getList()
+}
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/auxiliaryentry/energyindexinput/energyIndexInput.vue b/zhitan-vue/src/views/auxiliaryentry/energyindexinput/energyIndexInput.vue
new file mode 100644
index 0000000..72dccae
--- /dev/null
+++ b/zhitan-vue/src/views/auxiliaryentry/energyindexinput/energyIndexInput.vue
@@ -0,0 +1,348 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select v-model="queryParams.timeType" placeholder="鏈熼棿" style="width: 120px" @change="handleTimeType">
+                <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value"
+                  v-show="dict.value != 'DAY'" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker v-model="queryParams.dataTime" type="year" v-if="queryParams.timeType == 'YEAR'"
+                range-separator="鍒�" format="YYYY" value-format="YYYY" placeholder="鏃堕棿" style="width: 100%"
+                :clearable="false" />
+              <el-date-picker v-model="queryParams.dataTime" type="month" v-else-if="queryParams.timeType == 'MONTH'"
+                format="YYYY-MM" value-format="YYYY-MM" placeholder="鏃堕棿" style="width: 100%" :clearable="false" />
+              <el-date-picker v-model="queryParams.dataTime" type="date" v-else="queryParams.timeType == 'DAY'"
+                format="YYYY-MM-DD" value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 100%" :clearable="false" />
+            </el-form-item>
+            <el-form-item label="鎸囨爣绫诲瀷" prop="indicatorsType">
+              <el-select v-model="queryParams.indicatorsType" placeholder="鎸囨爣绫诲瀷" style="width: 100%">
+                <el-option v-for="dict in indicator_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+              <el-select v-model="queryParams.energyType" placeholder="鑳芥簮绫诲瀷" style="width: 120px">
+                <el-option :label="item.enername" :value="item.enersno" v-for="item in energyTypeList"
+                  :key="item.enersno" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="table-box">
+          <div class="mt20 mb20">
+            <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+          </div>
+          <el-table v-loading="loading" :data="productoutputList">
+            <el-table-column label="鐢ㄨ兘鍗曞厓" align="center" key="nodeName" prop="nodeName" :show-overflow-tooltip="true" />
+            <el-table-column label="鏈熼棿" align="center" key="timeType" prop="timeType" :show-overflow-tooltip="true"
+              width="100">
+              <template #default="scope">
+                <dict-tag :options="period" :value="scope.row.timeType" />
+              </template>
+            </el-table-column>
+            <el-table-column label="鏃堕棿" align="center" key="dataTime" prop="dataTime" :show-overflow-tooltip="true"
+              width="200" />
+            <el-table-column label="鎸囨爣绫诲瀷" align="center" key="indicatorsType" prop="indicatorsType"
+              :show-overflow-tooltip="true">
+              <template #default="scope">
+                <dict-tag :options="indicator_type" :value="scope.row.indicatorsType" />
+              </template>
+            </el-table-column>
+            <el-table-column label="鑳芥簮绫诲瀷" align="center" key="energyType" prop="energyType"
+              :show-overflow-tooltip="true">
+              <template #default="scope">
+                {{
+                  energyTypeList.find(function (item) {
+                    return item.enersno == scope.row.energyType;
+                  }).enername
+                }}
+              </template>
+            </el-table-column>
+            <el-table-column label="鎸囨爣閲�" align="center" key="number" prop="number" :show-overflow-tooltip="true" />
+            <el-table-column label="鍗曚綅" align="center" key="unit" prop="unit" :show-overflow-tooltip="true" />
+            <el-table-column label="鎻愪氦鏃堕棿" align="center" prop="createTime" :show-overflow-tooltip="true" width="200" />
+            <el-table-column label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width">
+              <template #default="scope">
+                <el-tooltip content="缂栬緫" placement="top">
+                  <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">
+                    缂栬緫
+                  </el-button>
+                </el-tooltip>
+                <el-tooltip content="鍒犻櫎" placement="top">
+                  <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">
+                    鍒犻櫎
+                  </el-button>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+          </el-table>
+          <pagination v-show="queryParams.total > 0" :total="queryParams.total" v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize" @pagination="getList" />
+        </div>
+      </div>
+    </div>
+    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鐢ㄨ兘鍗曞厓" prop="nodeName">
+              <el-input v-model="form.nodeName" placeholder="璇疯緭鍏ョ敤鑳藉崟鍏�" disabled />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select v-model="form.timeType" placeholder="璇烽�夋嫨鏈熼棿" clearable style="width: 100%"
+                @change="handleTimeTypeAdd">
+                <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value"
+                  v-show="dict.value != 'DAY'" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鏃堕棿" prop="dataTime">
+              <el-date-picker v-model="form.dataTime" type="year" placeholder="璇烽�夋嫨骞�" v-if="form.timeType == 'YEAR'"
+                format="YYYY" value-format="YYYY" style="width: 100%" />
+              <el-date-picker v-model="form.dataTime" type="month" placeholder="璇烽�夋嫨鏈�" v-if="form.timeType == 'MONTH'"
+                format="YYYY-MM" value-format="YYYY-MM" style="width: 100%" />
+              <el-date-picker v-model="form.dataTime" type="date" placeholder="璇烽�夋嫨鏃�" v-if="form.timeType == 'DAY'"
+                format="YYYY-MM-DD" value-format="YYYY-MM-DD" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鎸囨爣绫诲瀷" prop="indicatorsType">
+              <el-select v-model="form.indicatorsType" placeholder="璇烽�夋嫨鎸囨爣绫诲瀷" clearable style="width: 100%" disabled>
+                <el-option v-for="dict in indicator_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+              <el-select v-model="form.energyType" placeholder="璇烽�夋嫨鑳芥簮绫诲瀷">
+                <el-option :label="item.enername" :value="item.enersno" v-for="item in energyTypeList"
+                  :key="item.enersno" @click="handleEnergyType(item)" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍗曚綅" prop="unit">
+              <el-select v-model="form.unit" placeholder="璇烽�夋嫨鑳芥簮绫诲瀷" clearable style="width: 100%" disabled>
+                <el-option v-for="dict in sys_unit" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鎸囨爣閲�" prop="number">
+              <el-input-number v-model="form.number" :min="1" placeholder="璇疯緭鍏ユ寚鏍囬噺" style="width: 100%"
+                controls-position="right" step-strictly />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup name="energy-indicators">
+import {
+  listEnergyindexinput,
+  addEnergyindexinput,
+  updateEnergyindexinput,
+  delEnergyindexinput,
+} from "@/api/auxiliaryEntry/energyindexinput";
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+const { proxy } = getCurrentInstance();
+import { useRoute } from "vue-router";
+const { period, sys_unit, indicator_type } = proxy.useDict(
+  "period",
+  "sys_unit",
+  "indicator_type"
+);
+const energyTypeList = ref(undefined);
+const productoutputList = ref([]);
+const open = ref(false);
+const loading = ref(false);
+const showSearch = ref(true);
+const title = ref("");
+const data = reactive({
+  form: {},
+  queryParams: {
+    nodeId: null,
+    nodeName: null,
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+    timeType: null,
+    dataTime: null,
+    name: null,
+  },
+  query: { ...useRoute().query },
+  rules: {
+    timeType: [
+      { required: true, message: "鏈熼棿涓嶈兘涓虹┖", trigger: ["blur", "change"] },
+    ],
+    dataTime: [
+      { required: true, message: "鏃堕棿涓嶈兘涓虹┖", trigger: ["blur", "change"] },
+    ],
+    energyType: [
+      {
+        required: true,
+        message: "鑳芥簮绫诲瀷涓嶈兘涓虹┖",
+        trigger: ["blur", "change"],
+      },
+    ],
+    number: [
+      {
+        required: true,
+        message: "鎸囨爣閲忎笉鑳戒负绌�",
+        trigger: ["blur", "change"],
+      },
+    ],
+  },
+});
+const { queryParams, query, form, rules } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeCategory = data.nodeCategory;
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  handleTimeType(period.value[1].value);
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+    queryParams.value.energyType = energyTypeList.value[0].enersno;
+    handleQuery();
+  });
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy
+    .dayjs(new Date())
+    .format(e == "YEAR" ? "YYYY" : e == "MONTH" ? "YYYY-MM" : "YYYY-MM-DD");
+}
+function handleEnergyType(item) {
+  form.value.unit = item.muid;
+  form.value.energyTypeName = item.enername;
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鍒楄〃
+function getList() {
+  loading.value = true;
+  listEnergyindexinput(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    loading.value = false;
+    productoutputList.value = res.rows;
+    queryParams.value.total = res.total;
+  });
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鎼滅储
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  queryParams.value.total = 0;
+  queryParams.value.timeType = null;
+  queryParams.value.dataTime = null;
+  queryParams.value.energyType = null;
+  handleTimeType(period.value[0].value);
+  handleQuery();
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鏂板
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "鏂板鑳芥簮鎸囨爣褰曞叆";
+  form.value.nodeId = queryParams.value.nodeId;
+  form.value.nodeName = queryParams.value.nodeName;
+  form.value.nodeName = queryParams.value.nodeName;
+  form.value.indicatorsType = queryParams.value.nodeCategory == 0 ? "1" : "0";
+  handleTimeTypeAdd(period.value[0].value);
+}
+function handleTimeTypeAdd(e) {
+  form.value.timeType = e;
+  form.value.dataTime = proxy
+    .dayjs(new Date())
+    .format(e == "YEAR" ? "YYYY" : e == "MONTH" ? "YYYY-MM" : "YYYY-MM-DD");
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-缂栬緫
+function handleUpdate(row) {
+  reset();
+  form.value = { ...row };
+  open.value = true;
+  title.value = "缂栬緫鑳芥簮鎸囨爣褰曞叆";
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鏂板/缂栬緫-淇濆瓨
+function submitForm() {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (valid) {
+      if (form.value.energyIndicatorsId != undefined) {
+        updateEnergyindexinput(form.value).then((response) => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addEnergyindexinput(form.value).then((response) => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鏂板/缂栬緫-鍙栨秷
+function cancel() {
+  open.value = false;
+  reset();
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鏂板/缂栬緫-琛ㄥ崟閲嶇疆
+function reset() {
+  form.value = {
+    dataTime: "",
+    name: "",
+    number: "",
+    timeType: "",
+    unit: "",
+  };
+  proxy.resetForm("formRef");
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鍒犻櫎
+function handleDelete(row) {
+  proxy.$modal
+    .confirm('鏄惁纭鍒犻櫎鏃堕棿涓�"' + row.dataTime + '"鐨勬暟鎹」锛�')
+    .then(function () {
+      return delEnergyindexinput(row.energyIndicatorsId);
+    })
+    .then(() => {
+      getList();
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    })
+    .catch(() => { });
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/auxiliaryentry/energyindicators/energyIndicators.vue b/zhitan-vue/src/views/auxiliaryentry/energyindicators/energyIndicators.vue
new file mode 100644
index 0000000..d1e3efb
--- /dev/null
+++ b/zhitan-vue/src/views/auxiliaryentry/energyindicators/energyIndicators.vue
@@ -0,0 +1,454 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form
+            :model="queryParams"
+            ref="queryRef"
+            :inline="true"
+            v-show="showSearch"
+          >
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select
+                v-model="queryParams.timeType"
+                placeholder="鏈熼棿"
+                clearable
+                style="width: 120px"
+                @change="handleTimeType"
+              >
+                <el-option
+                  v-for="dict in period"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker
+                v-model="queryParams.dataTime"
+                type="year"
+                v-if="queryParams.timeType == 'YEAR'"
+                range-separator="鍒�"
+                format="YYYY"
+                value-format="YYYY"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="queryParams.dataTime"
+                type="month"
+                v-else-if="queryParams.timeType == 'MONTH'"
+                format="YYYY-MM"
+                value-format="YYYY-MM"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="queryParams.dataTime"
+                type="date"
+                v-else="queryParams.timeType == 'DAY'"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery"
+                >鎼滅储</el-button
+              >
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="table-box">
+          <div class="mt20 mb20">
+            <el-button type="primary" icon="Plus" @click="handleAdd"
+              >鏂板</el-button
+            >
+          </div>
+          <el-table v-loading="loading" :data="productoutputList">
+            <el-table-column
+              label="鐢ㄨ兘鍗曞厓"
+              align="center"
+              key="nodeName"
+              prop="nodeName"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="鏈熼棿"
+              align="center"
+              key="timeType"
+              prop="timeType"
+              :show-overflow-tooltip="true"
+              width="100"
+            >
+              <template #default="scope">
+                <dict-tag :options="period" :value="scope.row.timeType" />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="鏃堕棿"
+              align="center"
+              key="dataTime"
+              prop="dataTime"
+              :show-overflow-tooltip="true"
+              width="200"
+            />
+            <el-table-column
+              label="鎸囨爣鍚嶇О"
+              align="center"
+              key="name"
+              prop="name"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="鍗曚綅"
+              align="center"
+              key="unit"
+              prop="unit"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="鏈熼棿鐢ㄩ噺"
+              align="center"
+              key="number"
+              prop="number"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="鎻愪氦鏃堕棿"
+              align="center"
+              prop="createTime"
+              :show-overflow-tooltip="true"
+              width="200"
+            />
+            <el-table-column
+              label="鎿嶄綔"
+              align="center"
+              width="200"
+              class-name="small-padding fixed-width"
+            >
+              <template #default="scope">
+                <el-tooltip content="缂栬緫" placement="top">
+                  <el-button
+                    link
+                    type="primary"
+                    icon="Edit"
+                    @click="handleUpdate(scope.row)"
+                  >
+                    缂栬緫
+                  </el-button>
+                </el-tooltip>
+                <el-tooltip content="鍒犻櫎" placement="top">
+                  <el-button
+                    link
+                    type="primary"
+                    icon="Delete"
+                    @click="handleDelete(scope.row)"
+                  >
+                    鍒犻櫎
+                  </el-button>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+          </el-table>
+          <pagination
+            v-show="queryParams.total > 0"
+            :total="queryParams.total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+          />
+        </div>
+      </div>
+    </div>
+    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鐢ㄨ兘鍗曞厓" prop="nodeName">
+              <el-input
+                v-model="form.nodeName"
+                placeholder="璇疯緭鍏ョ敤鑳藉崟鍏�"
+                disabled
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select
+                v-model="form.timeType"
+                placeholder="璇烽�夋嫨鏈熼棿"
+                clearable
+                style="width: 100%"
+                @change="handleTimeTypeAdd"
+              >
+                <el-option
+                  v-for="dict in period"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鏃堕棿" prop="dataTime">
+              <el-date-picker
+                v-model="form.dataTime"
+                type="year"
+                placeholder="璇烽�夋嫨骞�"
+                v-if="form.timeType == 'YEAR'"
+                format="YYYY"
+                value-format="YYYY"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="form.dataTime"
+                type="month"
+                placeholder="璇烽�夋嫨鏈�"
+                v-if="form.timeType == 'MONTH'"
+                format="YYYY-MM"
+                value-format="YYYY-MM"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="form.dataTime"
+                type="date"
+                placeholder="璇烽�夋嫨鏃�"
+                v-if="form.timeType == 'DAY'"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鎸囨爣鍚嶇О" prop="name">
+              <el-input
+                v-model="form.name"
+                placeholder="璇疯緭鍏ユ寚鏍囧悕绉�"
+                maxlength="30"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鍗曚綅" prop="unit">
+              <el-select
+                v-model="form.unit"
+                placeholder="璇烽�夋嫨鍗曚綅"
+                clearable
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="dict in sys_unit"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鏈熼棿鐢ㄩ噺" prop="number">
+              <el-input-number
+                v-model="form.number"
+                :min="1"
+                placeholder="璇疯緭鍏ユ湡闂寸敤閲�"
+                style="width: 100%"
+                controls-position="right"
+                step-strictly
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup name="energy-indicators">
+import {
+  listEnergyIndicators,
+  addEnergyIndicators,
+  updateEnergyIndicators,
+  delEnergyIndicators,
+} from "@/api/auxiliaryEntry/energyIndicators";
+const { proxy } = getCurrentInstance();
+import { useRoute } from "vue-router";
+const { period, sys_unit } = proxy.useDict("period", "sys_unit");
+const productoutputList = ref([]);
+const open = ref(false);
+const loading = ref(false);
+const showSearch = ref(true);
+const title = ref("");
+const data = reactive({
+  form: {},
+  queryParams: {
+    nodeId: null,
+    nodeName: null,
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+    timeType: null,
+    dataTime: null,
+    name: null,
+  },
+  query: { ...useRoute().query },
+  rules: {
+    timeType: [
+      { required: true, message: "鏈熼棿涓嶈兘涓虹┖", trigger: ["blur", "change"] },
+    ],
+    dataTime: [
+      { required: true, message: "鏃堕棿涓嶈兘涓虹┖", trigger: ["blur", "change"] },
+    ],
+    name: [{ required: true, message: "鎸囨爣鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    unit: [{ required: true, message: "鍗曚綅涓嶈兘涓虹┖", trigger: "blur" }],
+    number: [
+      {
+        required: true,
+        message: "鏈熼棿鐢ㄩ噺涓嶈兘涓虹┖",
+        trigger: ["blur", "change"],
+      },
+    ],
+  },
+});
+const { queryParams, query, form, rules } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  handleTimeType(period.value[0].value);
+  handleQuery();
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy
+    .dayjs(new Date())
+    .format(e == "YEAR" ? "YYYY" : e == "MONTH" ? "YYYY-MM" : "YYYY-MM-DD");
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鍒楄〃
+function getList() {
+  loading.value = true;
+  listEnergyIndicators(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    loading.value = false;
+    productoutputList.value = res.rows;
+    queryParams.value.total = res.total;
+  });
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鎼滅储
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value = {
+    nodeId: null,
+    nodeName: null,
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+    timeType: null,
+    dataTime: null,
+    name: null,
+  };
+  handleTimeType(period.value[0].value);
+  handleQuery();
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鏂板
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "鏂板鑳芥簮鎸囨爣褰曞叆";
+  form.value.nodeId = queryParams.value.nodeId;
+  form.value.nodeName = queryParams.value.nodeName;
+  handleTimeTypeAdd(period.value[0].value);
+}
+function handleTimeTypeAdd(e) {
+  form.value.timeType = e;
+  form.value.dataTime = proxy
+    .dayjs(new Date())
+    .format(e == "YEAR" ? "YYYY" : e == "MONTH" ? "YYYY-MM" : "YYYY-MM-DD");
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-缂栬緫
+function handleUpdate(row) {
+  reset();
+  form.value = { ...row };
+  open.value = true;
+  title.value = "缂栬緫鑳芥簮鎸囨爣褰曞叆";
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鏂板/缂栬緫-淇濆瓨
+function submitForm() {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (valid) {
+      if (form.value.productOutputId != undefined) {
+        updateEnergyIndicators(form.value).then((response) => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addEnergyIndicators(form.value).then((response) => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鏂板/缂栬緫-鍙栨秷
+function cancel() {
+  open.value = false;
+  reset();
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鏂板/缂栬緫-琛ㄥ崟閲嶇疆
+function reset() {
+  form.value = {
+    dataTime: "",
+    name: "",
+    number: "",
+    timeType: "",
+    unit: "",
+  };
+  proxy.resetForm("formRef");
+}
+// 杈呭姪褰曞叆-鑳芥簮鎸囨爣褰曞叆-鍒犻櫎
+function handleDelete(row) {
+  proxy.$modal
+    .confirm('鏄惁纭鍒犻櫎鎸囨爣鍚嶇О涓�"' + row.name + '"鐨勬暟鎹」锛�')
+    .then(function () {
+      return delEnergyIndicators(row.productOutputId);
+    })
+    .then(() => {
+      getList();
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    })
+    .catch(() => {});
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/auxiliaryentry/energymeter/energyMeter.vue b/zhitan-vue/src/views/auxiliaryentry/energymeter/energyMeter.vue
new file mode 100644
index 0000000..6f474ab
--- /dev/null
+++ b/zhitan-vue/src/views/auxiliaryentry/energymeter/energyMeter.vue
@@ -0,0 +1,461 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form
+            :model="queryParams"
+            ref="queryRef"
+            :inline="true"
+            v-show="showSearch"
+          >
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select
+                v-model="queryParams.timeType"
+                placeholder="鏈熼棿"
+                clearable
+                style="width: 120px"
+                @change="handleTimeType"
+              >
+                <el-option
+                  v-for="dict in period"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker
+                v-model="queryParams.dataTime"
+                type="year"
+                v-if="queryParams.timeType == 'YEAR'"
+                range-separator="鍒�"
+                format="YYYY"
+                value-format="YYYY"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="queryParams.dataTime"
+                type="month"
+                v-else-if="queryParams.timeType == 'MONTH'"
+                format="YYYY-MM"
+                value-format="YYYY-MM"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="queryParams.dataTime"
+                type="date"
+                v-else="queryParams.timeType == 'DAY'"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+            </el-form-item>
+            <el-form-item label="浠〃鍚嶇О">
+              <el-input
+                v-model="queryParams.name"
+                placeholder="璇疯緭鍏ヤ华琛ㄥ悕绉�"
+                maxlength="30"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery"
+                >鎼滅储</el-button
+              >
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="table-box">
+          <div class="mt20 mb20">
+            <el-button type="primary" icon="Plus" @click="handleAdd"
+              >鏂板</el-button
+            >
+          </div>
+          <el-table v-loading="loading" :data="productoutputList">
+            <el-table-column
+              label="鐢ㄨ兘鍗曞厓"
+              align="center"
+              key="nodeName"
+              prop="nodeName"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="鏈熼棿"
+              align="center"
+              key="timeType"
+              prop="timeType"
+              :show-overflow-tooltip="true"
+              width="100"
+            >
+              <template #default="scope">
+                <dict-tag :options="period" :value="scope.row.timeType" />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="鏃堕棿"
+              align="center"
+              key="dataTime"
+              prop="dataTime"
+              :show-overflow-tooltip="true"
+              width="200"
+            />
+            <el-table-column
+              label="浠〃鍚嶇О"
+              align="center"
+              key="name"
+              prop="name"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="鍗曚綅"
+              align="center"
+              key="unit"
+              prop="unit"
+              :show-overflow-tooltip="true"
+            >
+              <template #default="scope">
+                <dict-tag :options="sys_unit" :value="scope.row.unit" />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="鏈熼棿鐢ㄩ噺"
+              align="center"
+              key="number"
+              prop="number"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="鎻愪氦鏃堕棿"
+              align="center"
+              prop="createTime"
+              :show-overflow-tooltip="true"
+              width="200"
+            />
+            <el-table-column
+              label="鎿嶄綔"
+              align="center"
+              width="200"
+              class-name="small-padding fixed-width"
+            >
+              <template #default="scope">
+                <el-tooltip content="缂栬緫" placement="top">
+                  <el-button
+                    link
+                    type="primary"
+                    icon="Edit"
+                    @click="handleUpdate(scope.row)"
+                  >
+                    缂栬緫
+                  </el-button>
+                </el-tooltip>
+                <el-tooltip content="鍒犻櫎" placement="top">
+                  <el-button
+                    link
+                    type="primary"
+                    icon="Delete"
+                    @click="handleDelete(scope.row)"
+                  >
+                    鍒犻櫎
+                  </el-button>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+          </el-table>
+          <pagination
+            v-show="queryParams.total > 0"
+            :total="queryParams.total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+          />
+        </div>
+      </div>
+    </div>
+    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鐢ㄨ兘鍗曞厓" prop="nodeName">
+              <el-input
+                v-model="form.nodeName"
+                placeholder="璇疯緭鍏ョ敤鑳藉崟鍏�"
+                disabled
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select
+                v-model="form.timeType"
+                placeholder="璇烽�夋嫨鏈熼棿"
+                clearable
+                style="width: 100%"
+                @change="handleTimeTypeAdd"
+              >
+                <el-option
+                  v-for="dict in period"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鏃堕棿" prop="dataTime">
+              <el-date-picker
+                v-model="form.dataTime"
+                type="year"
+                placeholder="璇烽�夋嫨骞�"
+                v-if="form.timeType == 'YEAR'"
+                format="YYYY"
+                value-format="YYYY"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="form.dataTime"
+                type="month"
+                placeholder="璇烽�夋嫨鏈�"
+                v-if="form.timeType == 'MONTH'"
+                format="YYYY-MM"
+                value-format="YYYY-MM"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="form.dataTime"
+                type="date"
+                placeholder="璇烽�夋嫨鏃�"
+                v-if="form.timeType == 'DAY'"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="浠〃鍚嶇О" prop="name">
+              <el-input
+                v-model="form.name"
+                placeholder="璇疯緭鍏ヤ华琛ㄥ悕绉�"
+                maxlength="30"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鍗曚綅" prop="unit">
+              <el-select
+                v-model="form.unit"
+                placeholder="璇烽�夋嫨鍗曚綅"
+                clearable
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="dict in sys_unit"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鏈熼棿鐢ㄩ噺" prop="number">
+              <el-input-number
+                v-model="form.number"
+                :min="1"
+                placeholder="璇疯緭鍏ユ湡闂寸敤閲�"
+                style="width: 100%"
+                controls-position="right"
+                step-strictly
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup name="energy-meter">
+import {
+  listEnergyMeter,
+  addEnergyMeter,
+  updateEnergyMeter,
+  delEnergyMeter,
+} from "@/api/auxiliaryEntry/energyMeter";
+const { proxy } = getCurrentInstance();
+import { useRoute } from "vue-router";
+const { period, sys_unit } = proxy.useDict("period", "sys_unit");
+const productoutputList = ref([]);
+const open = ref(false);
+const loading = ref(false);
+const showSearch = ref(true);
+const title = ref("");
+const data = reactive({
+  form: {},
+  queryParams: {
+    nodeId: null,
+    nodeName: null,
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+    timeType: null,
+    dataTime: null,
+    name: null,
+  },
+  query: { ...useRoute().query },
+  rules: {
+    timeType: [
+      { required: true, message: "鏈熼棿涓嶈兘涓虹┖", trigger: ["blur", "change"] },
+    ],
+    dataTime: [
+      { required: true, message: "鏃堕棿涓嶈兘涓虹┖", trigger: ["blur", "change"] },
+    ],
+    name: [{ required: true, message: "浠〃鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    unit: [{ required: true, message: "鍗曚綅涓嶈兘涓虹┖", trigger: "blur" }],
+    number: [
+      {
+        required: true,
+        message: "鏈熼棿鐢ㄩ噺涓嶈兘涓虹┖",
+        trigger: ["blur", "change"],
+      },
+    ],
+  },
+});
+const { queryParams, query, form, rules } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  handleTimeType(period.value[0].value);
+  handleQuery();
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy
+    .dayjs(new Date())
+    .format(e == "YEAR" ? "YYYY" : e == "MONTH" ? "YYYY-MM" : "YYYY-MM-DD");
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-鍒楄〃
+function getList() {
+  loading.value = true;
+  listEnergyMeter(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    loading.value = false;
+    productoutputList.value = res.rows;
+    queryParams.value.total = res.total;
+  });
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-鎼滅储
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  queryParams.value.total = 0;
+  queryParams.value.timeType = null;
+  queryParams.value.dataTime = null;
+  queryParams.value.name = null;
+  handleTimeType(period.value[0].value);
+  handleQuery();
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-鏂板
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "鏂板鑳芥簮浠〃褰曞叆";
+  form.value.nodeId = queryParams.value.nodeId;
+  form.value.nodeName = queryParams.value.nodeName;
+  handleTimeTypeAdd(period.value[0].value);
+}
+function handleTimeTypeAdd(e) {
+  form.value.timeType = e;
+  form.value.dataTime = proxy
+    .dayjs(new Date())
+    .format(e == "YEAR" ? "YYYY" : e == "MONTH" ? "YYYY-MM" : "YYYY-MM-DD");
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-缂栬緫
+function handleUpdate(row) {
+  reset();
+  form.value = { ...row };
+  open.value = true;
+  title.value = "缂栬緫鑳芥簮浠〃褰曞叆";
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-鏂板/缂栬緫-淇濆瓨
+function submitForm() {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (valid) {
+      if (form.value.productOutputId != undefined) {
+        updateEnergyMeter(form.value).then((response) => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addEnergyMeter(form.value).then((response) => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-鏂板/缂栬緫-鍙栨秷
+function cancel() {
+  open.value = false;
+  reset();
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-鏂板/缂栬緫-琛ㄥ崟閲嶇疆
+function reset() {
+  form.value = {
+    dataTime: "",
+    name: "",
+    number: "",
+    timeType: "",
+    unit: "",
+  };
+  proxy.resetForm("formRef");
+}
+// 杈呭姪褰曞叆-鑳芥簮浠〃褰曞叆-鍒犻櫎
+function handleDelete(row) {
+  proxy.$modal
+    .confirm('鏄惁纭鍒犻櫎浠〃鍚嶇О涓�"' + row.name + '"鐨勬暟鎹」锛�')
+    .then(function () {
+      return delEnergyMeter(row.productOutputId);
+    })
+    .then(() => {
+      getList();
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    })
+    .catch(() => {});
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/auxiliaryentry/productoutput/productOutput.vue b/zhitan-vue/src/views/auxiliaryentry/productoutput/productOutput.vue
new file mode 100644
index 0000000..8f3de9a
--- /dev/null
+++ b/zhitan-vue/src/views/auxiliaryentry/productoutput/productOutput.vue
@@ -0,0 +1,503 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form
+            :model="queryParams"
+            ref="queryRef"
+            :inline="true"
+            v-show="showSearch"
+          >
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select
+                v-model="queryParams.timeType"
+                placeholder="鏈熼棿"
+                style="width: 120px"
+                @change="handleTimeType"
+              >
+                <el-option
+                  v-for="dict in period"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker
+                v-model="queryParams.dataTime"
+                type="year"
+                v-if="queryParams.timeType == 'YEAR'"
+                range-separator="鍒�"
+                format="YYYY"
+                value-format="YYYY"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="queryParams.dataTime"
+                type="month"
+                v-else-if="queryParams.timeType == 'MONTH'"
+                format="YYYY-MM"
+                value-format="YYYY-MM"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="queryParams.dataTime"
+                type="date"
+                v-else="queryParams.timeType == 'DAY'"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+            </el-form-item>
+            <el-form-item label="浜у搧绫诲瀷">
+              <el-select
+                v-model="queryParams.productType"
+                placeholder="浜у搧绫诲瀷"
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="dict in product_type"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery"> 閲嶇疆 </el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="table-box">
+          <div class="mt20 mb20">
+            <el-button type="primary" icon="Plus" @click="handleAdd">
+              鏂板
+            </el-button>
+          </div>
+          <el-table v-loading="loading" :data="productoutputList">
+            <el-table-column
+              label="鐢ㄨ兘鍗曞厓"
+              align="center"
+              key="nodeName"
+              prop="nodeName"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="鏈熼棿"
+              align="center"
+              key="timeType"
+              prop="timeType"
+              :show-overflow-tooltip="true"
+            >
+              <template #default="scope">
+                <dict-tag :options="period" :value="scope.row.timeType" />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="鏃堕棿"
+              align="center"
+              key="dataTime"
+              prop="dataTime"
+              :show-overflow-tooltip="true"
+              width="200"
+            />
+            <el-table-column
+              label="浜у搧绫诲瀷"
+              align="center"
+              key="productType"
+              prop="productType"
+              :show-overflow-tooltip="true"
+            >
+              <template #default="scope">
+                <dict-tag
+                  :options="product_type"
+                  :value="scope.row.productType"
+                />
+              </template>
+            </el-table-column>
+            <!-- <el-table-column
+              label="浜у搧鍚嶇О"
+              align="center"
+              key="name"
+              prop="name"
+              :show-overflow-tooltip="true"
+            /> -->
+            <el-table-column
+              label="鍗曚綅"
+              align="center"
+              key="unit"
+              prop="unit"
+              :show-overflow-tooltip="true"
+            >
+              <template #default="scope">
+                <dict-tag :options="sys_unit" :value="scope.row.unit" />
+              </template>
+            </el-table-column>
+            <el-table-column
+              label="浜ч噺"
+              align="center"
+              key="number"
+              prop="number"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="鎻愪氦鏃堕棿"
+              align="center"
+              prop="createTime"
+              :show-overflow-tooltip="true"
+              width="200"
+            />
+            <el-table-column
+              label="鎿嶄綔"
+              align="center"
+              class-name="small-padding fixed-width"
+              width="200"
+            >
+              <template #default="scope">
+                <el-tooltip content="缂栬緫" placement="top">
+                  <el-button
+                    link
+                    type="primary"
+                    icon="Edit"
+                    @click="handleUpdate(scope.row)"
+                  >
+                    缂栬緫
+                  </el-button>
+                </el-tooltip>
+                <el-tooltip content="鍒犻櫎" placement="top">
+                  <el-button
+                    link
+                    type="primary"
+                    icon="Delete"
+                    @click="handleDelete(scope.row)"
+                  >
+                    鍒犻櫎
+                  </el-button>
+                </el-tooltip>
+              </template>
+            </el-table-column>
+          </el-table>
+          <pagination
+            v-show="queryParams.total > 0"
+            :total="queryParams.total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+          />
+        </div>
+      </div>
+    </div>
+    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鐢ㄨ兘鍗曞厓" prop="nodeName">
+              <el-input
+                v-model="form.nodeName"
+                placeholder="璇疯緭鍏ョ敤鑳藉崟鍏�"
+                disabled
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select
+                v-model="form.timeType"
+                placeholder="璇烽�夋嫨鏈熼棿"
+                clearable
+                style="width: 100%"
+                @change="handleTimeTypeAdd"
+              >
+                <el-option
+                  v-for="dict in period"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鏃堕棿" prop="dataTime">
+              <el-date-picker
+                v-model="form.dataTime"
+                type="year"
+                placeholder="璇烽�夋嫨骞�"
+                v-if="form.timeType == 'YEAR'"
+                format="YYYY"
+                value-format="YYYY"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="form.dataTime"
+                type="month"
+                placeholder="璇烽�夋嫨鏈�"
+                v-if="form.timeType == 'MONTH'"
+                format="YYYY-MM"
+                value-format="YYYY-MM"
+                style="width: 100%"
+              />
+              <el-date-picker
+                v-model="form.dataTime"
+                type="date"
+                placeholder="璇烽�夋嫨鏃�"
+                v-if="form.timeType == 'DAY'"
+                format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="浜у搧绫诲瀷" prop="productType">
+              <el-select
+                v-model="form.productType"
+                placeholder="浜у搧绫诲瀷"
+                clearable
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="dict in product_type"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <!-- <el-col :span="12">
+            <el-form-item label="浜у搧鍚嶇О" prop="name">
+              <el-input
+                v-model="form.name"
+                placeholder="璇疯緭鍏ヤ骇鍝佸悕绉�"
+                maxlength="30"
+              />
+            </el-form-item>
+          </el-col> -->
+          <el-col :span="12">
+            <el-form-item label="鍗曚綅" prop="unit">
+              <el-select
+                v-model="form.unit"
+                placeholder="璇烽�夋嫨鍗曚綅"
+                clearable
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="dict in sys_unit"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="浜ч噺" prop="number">
+              <el-input-number
+                v-model="form.number"
+                :min="1"
+                placeholder="璇疯緭鍏ヤ骇閲�"
+                style="width: 100%"
+                controls-position="right"
+                step-strictly
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup name="product-output">
+import {
+  listProductoutput,
+  addProductoutput,
+  updateProductoutput,
+  delProductoutput,
+} from "@/api/auxiliaryEntry/productOutput";
+const { proxy } = getCurrentInstance();
+import { useRoute } from "vue-router";
+const { period, sys_unit, product_type } = proxy.useDict(
+  "period",
+  "sys_unit",
+  "product_type"
+);
+const productoutputList = ref([]);
+const open = ref(false);
+const loading = ref(false);
+const showSearch = ref(true);
+const title = ref("");
+const data = reactive({
+  form: {},
+  queryParams: {
+    nodeId: null,
+    nodeName: null,
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+    timeType: null,
+    dataTime: null,
+    productType: null,
+  },
+  query: { ...useRoute().query },
+  rules: {
+    timeType: [
+      { required: true, message: "鏈熼棿涓嶈兘涓虹┖", trigger: ["blur", "change"] },
+    ],
+    dataTime: [
+      { required: true, message: "鏃堕棿涓嶈兘涓虹┖", trigger: ["blur", "change"] },
+    ],
+    productType: [
+      {
+        required: true,
+        message: "浜у搧绫诲瀷涓嶈兘涓虹┖",
+        trigger: ["blur", "change"],
+      },
+    ],
+    // name: [{ required: true, message: "浜у搧鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    unit: [{ required: true, message: "鍗曚綅涓嶈兘涓虹┖", trigger: "blur" }],
+    number: [
+      { required: true, message: "浜ч噺涓嶈兘涓虹┖", trigger: ["blur", "change"] },
+    ],
+  },
+});
+const { queryParams, query, form, rules } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  queryParams.value.productType = null;
+  handleTimeType(period.value[0].value);
+  handleQuery();
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy
+    .dayjs(new Date())
+    .format(e == "YEAR" ? "YYYY" : e == "MONTH" ? "YYYY-MM" : "YYYY-MM-DD");
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-鍒楄〃
+function getList() {
+  loading.value = true;
+  listProductoutput(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    loading.value = false;
+    productoutputList.value = res.rows;
+    queryParams.value.total = res.total;
+  });
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-鎼滅储
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  queryParams.value.total = 0;
+  queryParams.value.timeType = null;
+  queryParams.value.dataTime = null;
+  queryParams.value.productType = null;
+  handleTimeType(period.value[0].value);
+  handleQuery();
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-鏂板
+function handleAdd() {
+  reset();
+  form.value.nodeId = queryParams.value.nodeId;
+  form.value.nodeName = queryParams.value.nodeName;
+  handleTimeTypeAdd(period.value[0].value);
+  title.value = "鏂板浜у搧浜ч噺褰曞叆";
+  open.value = true;
+}
+function handleTimeTypeAdd(e) {
+  form.value.timeType = e;
+  form.value.dataTime = proxy
+    .dayjs(new Date())
+    .format(e == "YEAR" ? "YYYY" : e == "MONTH" ? "YYYY-MM" : "YYYY-MM-DD");
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-缂栬緫
+function handleUpdate(row) {
+  reset();
+  form.value = { ...row };
+  open.value = true;
+  title.value = "缂栬緫浜у搧浜ч噺褰曞叆";
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-鏂板/缂栬緫-淇濆瓨
+function submitForm() {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (valid) {
+      if (form.value.productOutputId != undefined) {
+        updateProductoutput(form.value).then((response) => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addProductoutput(form.value).then((response) => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-鏂板/缂栬緫-鍙栨秷
+function cancel() {
+  open.value = false;
+  reset();
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-鏂板/缂栬緫-琛ㄥ崟閲嶇疆
+function reset() {
+  form.value = {
+    dataTime: "",
+    productType: "",
+    // name: "",
+    number: "1",
+    timeType: "",
+    unit: "",
+  };
+  proxy.resetForm("formRef");
+}
+// 杈呭姪褰曞叆-浜у搧浜ч噺褰曞叆-鍒犻櫎
+function handleDelete(row) {
+  proxy.$modal
+    .confirm('鏄惁纭鍒犻櫎鏃堕棿涓�"' + row.dataTime + '"鐨勬暟鎹」锛�')
+    // .confirm('鏄惁纭鍒犻櫎浜у搧鍚嶇О涓�"' + row.name + '"鐨勬暟鎹」锛�')
+    .then(function () {
+      return delProductoutput(row.productOutputId);
+    })
+    .then(() => {
+      getList();
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    })
+    .catch(() => {});
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/businessconfiguration/alarmmaintenance/alarmMaintenance.vue b/zhitan-vue/src/views/businessconfiguration/alarmmaintenance/alarmMaintenance.vue
new file mode 100644
index 0000000..51682f1
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/alarmmaintenance/alarmMaintenance.vue
@@ -0,0 +1,133 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="120px">
+                <el-form-item label="闄愬�肩被鍨嬪悕绉�">
+                    <el-input v-model="queryParams.limitName" placeholder="璇疯緭鍏ラ檺鍊肩被鍨嬪悕绉�" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+
+
+        <div class="table-box">
+            <div class="mt20 mb20">
+                <el-button type="primary" icon="plus" @click="handleAdd">鏂板</el-button>
+                <!-- <el-button type="primary" icon="Delete">鍒犻櫎</el-button> -->
+            </div>
+            <el-table :data="tableData" v-loading="loading">
+                <el-table-column prop="limitName" label="闄愬�肩被鍨嬪悕绉�" show-overflow-tooltip align="center" />
+                <el-table-column prop="limitCode" label="闄愬�肩被鍨嬬紪鍙�" show-overflow-tooltip align="center" />
+                <el-table-column prop="alarmType" label="鎶ヨ闄愬埗绫诲瀷" show-overflow-tooltip align="center"
+                    :formatter="(row, column) => proxy.selectDictLabel(alarm_type, row.alarmType)" />
+                <el-table-column prop="colorNumber" label="鑹插彿" show-overflow-tooltip align="center">
+                    <template #default="scope">
+                        <div style="width: 20px;height: 20px;border-radius: 5px; margin: 0 auto"
+                            :style="{ 'background': scope.row.colorNumber }">
+                        </div>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="comparatorOperator" label="姣旇緝杩愮畻绗�" show-overflow-tooltip align="center"
+                    :formatter="(row) => proxy.selectDictLabel(operatorList,row.comparatorOperator)" />
+                <el-table-column label="鎿嶄綔" width="300" align="center">
+                    <template #default="scope">
+                        <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row)">
+                            淇敼
+                        </el-button>
+                        <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                            鍒犻櫎
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                v-model:limit="queryParams.pageSize" @pagination="getList" />
+
+        </div>
+        <edit-modal ref="EditModalRef" @getList="getList" :operatorList="operatorList" :alarmTypeList='alarm_type' />
+    </div>
+</template>
+
+<script setup>
+import EditModal from './components/EditModal.vue'
+import { alarmList, alarmDel } from '@/api/businessConfiguration/businessConfiguration'
+let { proxy } = getCurrentInstance()
+let operatorList = ref([
+    { label: '澶т簬', value: '>' },
+    { label: '澶т簬绛変簬', value: '>=' },
+    { label: '灏忎簬', value: '<' },
+    { label: '灏忎簬绛変簬', value: '<=' },
+    { label: '绛変簬', value: '=' },
+])
+let { alarm_type } = proxy.useDict('alarm_type')
+let loading = ref(false);
+let total = ref(0);
+let tableData = ref([])
+let queryParams = ref({
+    limitName: '',
+    pageNum: 1,
+    pageSize: 10,
+})
+
+function getList() {
+    loading.value = true
+    alarmList(queryParams.value).then(res => {
+        console.log(res.rows)
+        tableData.value = res.rows
+        total.value = res.total
+        loading.value = false
+    })
+}
+
+getList()
+
+let EditModalRef = ref('')
+function handleAdd(row) {
+    if (EditModalRef.value) {
+        EditModalRef.value.handleOpen(row)
+    }
+
+}
+
+function handleDel(row) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+        return alarmDel(row.id);
+    }).then(() => {
+        getList();
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    }).catch(() => { });
+}
+
+function handleQuery() {
+    queryParams.value.pageNum = 1
+    getList()
+
+}
+
+function resetQuery() {
+    queryParams.value = {
+        limitName: '',
+        pageNum: 1,
+        pageSize: 10,
+    }
+    getList()
+}
+
+
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+
+.header-box {
+    :deep .el-form-item__content {
+        color: #fff;
+        font-size: 16px;
+    }
+
+}
+</style>
diff --git a/zhitan-vue/src/views/businessconfiguration/alarmmaintenance/components/EditModal.vue b/zhitan-vue/src/views/businessconfiguration/alarmmaintenance/components/EditModal.vue
new file mode 100644
index 0000000..890174a
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/alarmmaintenance/components/EditModal.vue
@@ -0,0 +1,113 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="600" @close="handleClose">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="120px" v-loading="loading">
+            <el-form-item label="闄愬�肩被鍨嬪悕绉�" prop="limitName">
+                <el-input v-model="form.limitName" placeholder="璇疯緭鍏ラ檺鍊肩被鍨嬪悕绉�" />
+            </el-form-item>
+            <el-form-item label="闄愬�肩被鍨嬬紪鍙�" prop="limitCode">
+                <el-input v-model="form.limitCode" placeholder="璇疯緭鍏ラ檺鍊肩被鍨嬬紪鍙�" />
+            </el-form-item>
+            <el-form-item label="鎶ヨ闄愬埗绫诲瀷" prop="alarmType">
+                <el-select v-model="form.alarmType" placeholder="璇烽�夋嫨">
+                    <el-option v-for="(item, index) in props.alarmTypeList" :key="index" :label="item.label"
+                        :value="item.value">
+                    </el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="鑹插彿" prop="colorNumber">
+                <el-color-picker v-model="form.colorNumber" />
+            </el-form-item>
+            <el-form-item label="姣旇緝杩愮畻绗�" prop="comparatorOperator">
+                <el-select v-model="form.comparatorOperator" placeholder="璇烽�夋嫨">
+                    <el-option v-for="(item, index) in props.operatorList" :key="index" :label="item.name"
+                        :value="item.value" />
+                </el-select>
+            </el-form-item>
+
+        </el-form>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+            <el-button @click="handleClose">鍙� 娑�</el-button>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+import { alarmAdd, alarmEdit } from '@/api/businessConfiguration/businessConfiguration'
+const { proxy } = getCurrentInstance();
+let props = defineProps(['alarmTypeList', 'operatorList'])
+
+
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+    alarmType: null,
+    colorNumber: null,
+    limitCode: null,
+    limitName: null,
+    comparatorOperator: null,
+
+})
+let emit = defineEmits(['getList'])
+const formRules = {
+    limitName: [{ required: true, trigger: "blur", message: "璇疯緭鍏ラ檺鍊肩被鍨嬪悕绉�" }],
+    limitCode: [{ required: true, trigger: "blur", message: "璇疯緭鍏ラ檺鍊肩被鍨嬬紪鍙�" }],
+}
+
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            loading.value = true;
+            let obj = form.value.id ? alarmEdit(form.value) : alarmAdd(form.value)
+            obj.then((res) => {
+                if (res.code == 200) {
+                    proxy.$modal.msgSuccess(res.msg);
+                    emit('getList')
+                } else {
+                    proxy.$modal.msgError(res.msg);
+                }
+
+            }).catch((err) => {
+
+            }).finally(() => {
+                handleClose()
+            });
+        }
+    })
+}
+
+
+
+
+
+function handleOpen(row) {
+    if (row && row.id) {
+        title.value = "缂栬緫鎶ヨ闄愬�肩被鍨嬬淮鎶�"
+        form.value = JSON.parse(JSON.stringify(row))
+    } else {
+        title.value = "娣诲姞鎶ヨ闄愬�肩被鍨嬬淮鎶�"
+    }
+    visible.value = true
+}
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        alarmType: null,
+        colorNumber: null,
+        limitCode: null,
+        limitName: null,
+        comparatorOperator: null,
+    }
+}
+
+defineExpose({ handleOpen })
+
+</script>
+
+
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/businessconfiguration/datacollection/dataCollection.vue b/zhitan-vue/src/views/businessconfiguration/datacollection/dataCollection.vue
new file mode 100644
index 0000000..f147805
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/datacollection/dataCollection.vue
@@ -0,0 +1,145 @@
+<template>
+
+    <div class="page">
+        <div class="page-container">
+            <div class="page-container-left">
+                <div class="tree page-box">
+                    <el-tree :data="data" :props="defaultProps" default-expand-all />
+                </div>
+            </div>
+            <div class="page-container-right">
+                <div class="form-card">
+                    <el-form :model="form" ref="queryRef" :inline="true" label-width="100px">
+                        <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+                            <el-select v-model="form.energyType" placeholder="璇烽�夋嫨鑳芥簮绫诲瀷">
+                                <el-option :label="item.enername" :value="item.enersno" v-for="item in energyTypeList"
+                                    :key="item.enersno" />
+                            </el-select>
+                        </el-form-item>
+
+                        <el-form-item label="鏈熼棿" prop="timeType">
+                            <el-select v-model="form.timeType" placeholder="鏈熼棿" clearable style="width: 120px"
+                                @change="handleTimeType">
+                                <el-option v-for="dict in period" :key="dict.value" :label="dict.label"
+                                    :value="dict.value" />
+                            </el-select>
+                        </el-form-item>
+                        <el-form-item label="鏃堕棿閫夋嫨" prop="dataTime">
+                            <el-date-picker v-if="form.type == 1" v-model="form.dataTime" type="year" />
+                            <el-date-picker v-else-if="form.type == 2" v-model="form.dataTime" type="month" format="YYYY-MM"
+                                value-format="YYYY-MM" />
+                            <el-date-picker v-else v-model="form.dataTime" type="date" format="YYYY-MM-DD"
+                                value-format="YYYY-MM-DD" />
+                        </el-form-item>
+                        <el-form-item>
+                            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                        </el-form-item>
+                    </el-form>
+                </div>
+                <div class="table-box">
+                    <el-table :data="tableData" style="width: 100%">
+                        <el-table-column prop="value1" label="鍖哄煙1" show-overflow-tooltip align="center" />
+                        <el-table-column prop="value2" label="璁惧" show-overflow-tooltip align="center" />
+                        <el-table-column prop="value3" label="浠〃鍚嶇О" show-overflow-tooltip align="center" />
+                        <el-table-column prop="value4" label="浠嬭川" show-overflow-tooltip align="center" />
+                        <el-table-column prop="value5" label="閲囬泦鏃堕棿" show-overflow-tooltip align="center" />
+                        <el-table-column prop="value6" label="鍓嶈鏁�" show-overflow-tooltip align="center" />
+                        <el-table-column prop="value7" label="鍚庤鏁�" show-overflow-tooltip align="center" />
+                        <el-table-column prop="value8" label="鏈熼棿閲�" show-overflow-tooltip align="center" />
+                    </el-table>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+let { proxy } = getCurrentInstance()
+let typeArray = ref([{ id: 1, name: '骞�' }, { id: 2, name: '鏈�' }, { id: 3, name: '鏃�' }])
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+const { period } = proxy.useDict("period");
+
+const energyTypeList = ref();
+function getEnergyTypeList() {
+    listEnergyTypeList().then((res) => {
+        energyTypeList.value = res.data;
+        form.value.energyType = energyTypeList.value[0].enersno
+        handleTimeType(period.value[0].value);
+    });
+}
+
+getEnergyTypeList()
+
+let form = ref({
+    energyType: 1,
+    dataTime: null,
+    energyType: '',
+})
+
+
+function handleTimeType(e) {
+    form.value.timeType = e;
+    form.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+function handleQuery() {
+    console.log(form.value)
+}
+function resetQuery() {
+    form.value = {
+        energyType: 1,
+        dataTime: null,
+        energyType: '',
+    }
+}
+
+const defaultProps = ref({
+    children: 'children',
+    label: 'label',
+})
+
+let data = ref(
+    [{
+        id: 1,
+        label: '缇庢灄',
+        children: [
+            {
+                id: 2,
+                parent: 1,
+                label: '鍖哄煙1',
+            },
+            {
+                id: 3,
+                parent: 1,
+                label: '鍖哄煙2',
+                children: [{
+                    id: 4,
+                    parent: 3,
+                    label: '鍖哄煙2-1',
+                }]
+            },
+            {
+                id: 5,
+                parent: 1,
+                label: '鍖哄煙3',
+                children: [{
+                    id: 6,
+                    parent: 5,
+                    label: '鍖哄煙3-1',
+                }]
+            }
+        ]
+    }])
+
+
+let tableData = ref([
+    { id: 1, value1: '1', value2: '2', value3: '3', value4: '4', value5: '5', value6: '6', value7: '7', value8: '8', },
+    { id: 2, value1: '11', value2: '22', value3: '33', value4: '44', value5: '55', value6: '66', value7: '77', value8: '88', },
+])
+
+
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/businessconfiguration/gatewayledger/components/EditModal.vue b/zhitan-vue/src/views/businessconfiguration/gatewayledger/components/EditModal.vue
new file mode 100644
index 0000000..13ef308
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/gatewayledger/components/EditModal.vue
@@ -0,0 +1,104 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="600" @close="handleClose">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="120px" v-loading="loading">
+            <el-form-item label="缃戝叧缂栧彿" prop="gatewayNum">
+                <el-input v-model="form.gatewayNum" placeholder="璇疯緭鍏ョ綉鍏崇紪鍙�" />
+            </el-form-item>
+            <el-form-item label="缃戝叧鍚嶇О" prop="gatewayName">
+                <el-input v-model="form.gatewayName" placeholder="璇疯緭鍏ョ綉鍏冲悕绉�" />
+            </el-form-item>
+            <el-form-item label="瑙勬牸鍨嬪彿" prop="specsModel">
+                <el-input v-model="form.specsModel" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+            </el-form-item>
+            <el-form-item label="瀹夎浣嶇疆" prop="installLocation">
+                <el-input v-model="form.installLocation" placeholder="璇疯緭鍏ュ畨瑁呬綅缃�" />
+            </el-form-item>
+            <el-form-item label="IP鍦板潃" prop="ipAdd">
+                <el-input v-model="form.ipAdd" placeholder="璇疯緭鍏P鍦板潃" />
+            </el-form-item>
+        </el-form>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+            <el-button @click="handleClose">鍙� 娑�</el-button>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+import { gatewayAdd, gatewayEdit } from "@/api/businessConfiguration/gatewayLedger";
+const { proxy } = getCurrentInstance();
+
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+    gatewayNum: '',
+    gatewayName: '',
+    specsModel: '',
+    installLocation: '',
+    ipAdd: ''
+
+})
+let emit = defineEmits(['getList'])
+const formRules = {
+    gatewayNum: [{ required: true, trigger: "blur", message: "璇疯緭鍏ョ綉鍏崇紪鍙�" }],
+    gatewayName: [{ required: true, trigger: "blur", message: "璇疯緭鍏ョ綉鍏冲悕绉�" }],
+    specsModel: [{ required: true, trigger: "blur", message: "璇疯緭鍏ヨ鏍煎瀷鍙�" }],
+    installLocation: [{ required: true, trigger: "blur", message: "璇疯緭鍏ュ畨瑁呬綅缃�" }],
+    ipAdd: [{ required: true, trigger: "blur", message: "璇疯緭鍏P鍦板潃" }]
+}
+
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            loading.value = true;
+            let obj = form.value.id ? gatewayEdit(form.value) : gatewayAdd(form.value)
+            obj.then((res) => {
+                if (res.code == 200) {
+                    proxy.$modal.msgSuccess(res.msg);
+                    emit('getList')
+                } else {
+                    proxy.$modal.msgError(res.msg);
+                }
+            }).catch((err) => {
+            }).finally(() => {
+                handleClose()
+            });
+        }
+    })
+}
+
+
+function handleOpen(row) {
+    if (row && row.id) {
+        title.value = "缂栬緫缃戝叧"
+        form.value = JSON.parse(JSON.stringify(row))
+    } else {
+        title.value = "娣诲姞缃戝叧"
+    }
+    visible.value = true
+}
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        value1: null,
+        value2: null,
+        value3: null,
+        value4: null,
+        value5: null,
+        value6: null,
+        value7: null,
+        value8: null,
+    }
+}
+
+defineExpose({ handleOpen })
+
+</script>
+
+
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/businessconfiguration/gatewayledger/gatewayLedger.vue b/zhitan-vue/src/views/businessconfiguration/gatewayledger/gatewayLedger.vue
new file mode 100644
index 0000000..1cdeb7f
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/gatewayledger/gatewayLedger.vue
@@ -0,0 +1,131 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :inline="true">
+                <el-form-item label="缃戝叧鏁伴噺:" class="header-box">
+                    {{ total }}
+                </el-form-item>
+                <el-form-item label="璁¢噺鍣ㄥ叿鏁伴噺:" class="header-box">
+                    {{ statistics.deviceNum }}
+                </el-form-item>
+                <el-form-item label="娴嬬偣鏁伴噺:" class="header-box">
+                    {{ statistics.ptNum }}
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="table-box">
+            <div class="mt20 mb20">
+                <el-button type="primary" icon="plus" @click="handleAdd">鏂板</el-button>
+                <el-button type="primary" icon="Download" @click="handleExport">瀵煎嚭</el-button>
+            </div>
+            <el-table :data="tableData" v-loading="loading">
+                <el-table-column prop="gatewayNum" label="缃戝叧缂栧彿" show-overflow-tooltip align="center" />
+                <el-table-column prop="gatewayName" label="缃戝叧鍚嶇О" show-overflow-tooltip align="center" />
+                <el-table-column prop="specsModel" label="瑙勬牸鍨嬪彿" show-overflow-tooltip align="center" />
+                <el-table-column prop="installLocation" label="瀹夎浣嶇疆" show-overflow-tooltip align="center" />
+                <el-table-column prop="ipAdd" label="IP鍦板潃" show-overflow-tooltip align="center" />
+                <el-table-column prop="runStatus" label="杩愯鐘舵��" show-overflow-tooltip align="center" />
+                <el-table-column prop="deviceNum" label="璁¢噺鍣ㄥ叿鏁伴噺" show-overflow-tooltip align="center" />
+                <el-table-column prop="ptNum" label="閲囬泦娴嬬偣鏁伴噺" show-overflow-tooltip align="center" />
+                <el-table-column label="鎿嶄綔" width="300" align="center">
+                    <template #default="scope">
+                        <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row)">
+                            淇敼
+                        </el-button>
+                        <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">
+                            鍒犻櫎
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                v-model:limit="queryParams.pageSize" @pagination="getList" />
+        </div>
+        <edit-modal ref="EditModalRef" @getList="getList(1)" />
+    </div>
+</template>
+
+<script setup>
+import EditModal from './components/EditModal.vue'
+import { gatewayStatistics, gatewayList, gatewayDel } from "@/api/businessConfiguration/gatewayLedger";
+let { proxy } = getCurrentInstance()
+
+let statistics = ref({
+    deviceNum: 0,
+    ptNum: 0
+})
+function getGatewayStatisticsFun() {
+    gatewayStatistics().then(res => {
+        if (res.code == 200) {
+            if (res.data) {
+                statistics.value = res.data
+            }
+        }
+    })
+}
+getGatewayStatisticsFun()
+
+
+let loading = ref(false);
+let total = ref(0);
+let tableData = ref([])
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+})
+
+function getList(arg) {
+    if (arg == 1) {
+        queryParams.value.pageNum = 1
+    }
+    loading.value = true
+    gatewayList(queryParams.value).then(res => {
+        tableData.value = res.rows
+        total.value = res.total
+        loading.value = false
+    })
+}
+getList()
+
+function handleExport() {
+    proxy.download(
+        "gatewaySetting/export",
+        queryParams.value,
+        `缃戝叧鍙拌处${new Date().getTime()}.xlsx`
+    );
+}
+
+let EditModalRef = ref('')
+function handleAdd(row) {
+    if (EditModalRef.value) {
+        EditModalRef.value.handleOpen(row)
+    }
+
+}
+
+function handleDelete(row) {
+    proxy.$modal
+        .confirm('鏄惁纭鍒犻櫎缃戝叧涓�"' + row.gatewayName + '"鐨勬暟鎹」锛�')
+        .then(function () {
+            return gatewayDel(row.id);
+        })
+        .then(() => {
+            getList(1);
+            proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        })
+        .catch(() => { });
+}
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+
+.header-box {
+    :deep .el-form-item__content {
+        color: #fff;
+        font-size: 16px;
+    }
+
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/businessconfiguration/gatewaystatus/gatewayStatus.vue b/zhitan-vue/src/views/businessconfiguration/gatewaystatus/gatewayStatus.vue
new file mode 100644
index 0000000..3f20ab8
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/gatewaystatus/gatewayStatus.vue
@@ -0,0 +1,532 @@
+<template>
+  <div class="page">
+    <div class="table-box">
+      <div class="border">
+        <div class="table" v-for="(item, index) in dataList" :key="index">
+          <div class="num">
+            <div class="li name">{{ item.name }}</div>
+            <div class="firstLi">
+              <div :class="item.list.length < 16 ? 'li hasRightLine' : 'li'" v-for="(i, inde) in item.list" :key="inde">
+                <div class="title_num" v-if="i.title_num">{{ i.title_num }}</div>
+                <!-- v-if="i.dot.length > 0" -->
+                <div class="dot" v-if="i.dot && i.dot.length > 0">
+                  <div class="dot_li" v-for="(j, ind) in 3" :key="ind"></div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+
+
+let dataList = ref([{
+  name: "缃戝叧",
+  list: [
+    {
+      id: 1,
+      title_num: "鏁版嵁搴�",
+      dot: []
+    },
+    {
+      id: 2,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 3,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 4,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 5,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 6,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 7,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 8,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 9,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 10,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 11,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 12,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 13,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 14,
+      title_num: "200331",
+      dot: []
+    },
+    {
+      id: 15,
+      title_num: "200461",
+      dot: []
+    },
+    {
+      id: 16,
+      title_num: "200475",
+      dot: []
+    },
+  ]
+},
+{
+  name: "璁$畻鍣ㄥ叿",
+  list: [{
+    id: 1,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    {
+      id: '003',
+      dot_li: ""
+    },
+    ],
+  },
+  {
+    id: 2,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    ],
+  },
+  {
+    id: 3,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    {
+      id: '003',
+      dot_li: ""
+    },
+    {
+      id: '004',
+      dot_li: ""
+    },
+    ],
+  }
+  ]
+},
+{
+  name: "缃戝叧",
+  list: [{
+    id: 1,
+    title_num: "200505"
+  },
+  {
+    id: 2,
+    title_num: "200331"
+  },
+  {
+    id: 3,
+    title_num: "200331"
+  },
+  {
+    id: 4,
+    title_num: "200331"
+  },
+  {
+    id: 5,
+    title_num: "200331"
+  },
+  {
+    id: 6,
+    title_num: "200331"
+  },
+  {
+    id: 7,
+    title_num: "200331"
+  },
+  {
+    id: 8,
+    title_num: "200331"
+  },
+  {
+    id: 9,
+    title_num: "200331"
+  },
+  {
+    id: 10,
+    title_num: "200331"
+  },
+  {
+    id: 11,
+    title_num: "200331"
+  },
+  {
+    id: 12,
+    title_num: "200331"
+  },
+  {
+    id: 13,
+    title_num: "200331"
+  },
+  {
+    id: 14,
+    title_num: "200331"
+  },
+  {
+    id: 15,
+    title_num: "200461"
+  },
+  {
+    id: 16,
+    title_num: "200604"
+  },
+  ]
+},
+{
+  name: "璁$畻鍣ㄥ叿",
+  list: [{
+    id: 1,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    {
+      id: '003',
+      dot_li: ""
+    },
+    ],
+  },
+  {
+    id: 2,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    ],
+  },
+  {
+    id: 3,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    {
+      id: '003',
+      dot_li: ""
+    },
+    {
+      id: '004',
+      dot_li: ""
+    },
+    ],
+  },
+  {
+    id: 4,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    {
+      id: '003',
+      dot_li: ""
+    },
+    {
+      id: '004',
+      dot_li: ""
+    },
+    {
+      id: '005',
+      dot_li: ""
+    },
+    ],
+  },
+  {
+    id: 5,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    {
+      id: '003',
+      dot_li: ""
+    },
+    {
+      id: '004',
+      dot_li: ""
+    },
+    {
+      id: '005',
+      dot_li: ""
+    },
+    {
+      id: '006',
+      dot_li: ""
+    },
+    ],
+  }
+  ]
+},
+{
+  name: "缃戝叧",
+  list: [{
+    id: 1,
+    title_num: "200605"
+  },
+  {
+    id: 2,
+    title_num: "200331"
+  },
+  {
+    id: 3,
+    title_num: "200331"
+  },
+  {
+    id: 4,
+    title_num: "38081703"
+  },
+  ]
+},
+{
+  name: "璁$畻鍣ㄥ叿",
+  list: [{
+    id: 1,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    {
+      id: '003',
+      dot_li: ""
+    },
+    ],
+  },
+  {
+    id: 2,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    ],
+  },
+  {
+    id: 3,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    {
+      id: '003',
+      dot_li: ""
+    },
+    {
+      id: '004',
+      dot_li: ""
+    },
+    ],
+  },
+  {
+    id: 4,
+    title_num: "",
+    dot: [{
+      id: '001',
+      dot_li: ""
+    },
+    {
+      id: '002',
+      dot_li: ""
+    },
+    {
+      id: '003',
+      dot_li: ""
+    },
+    {
+      id: '004',
+      dot_li: ""
+    },
+    {
+      id: '005',
+      dot_li: ""
+    },
+    ],
+  }
+  ]
+},
+])
+
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+.border {
+  border: 1px solid #fff;
+  color: #fff;
+
+  .table {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+    border-bottom: 1px solid #fff;
+
+    &:last-child {
+      border-bottom: none;
+    }
+
+    .num {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: flex-start;
+
+      .name {
+        width: 6%;
+        min-height: 50px;
+        height: max-content;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        border-right: none !important;
+      }
+
+      .firstLi {
+        width: 94%;
+        flex-shrink: 0;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        border-left: 1px solid #fff;
+
+        .li {
+          width: 6.25%;
+          min-height: 50px;
+          height: max-content;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-right: 1px solid #fff;
+
+          &.hasRightLine {
+            border-right: 1px solid #fff !important;
+          }
+
+          &:last-child {
+            border-right: none;
+          }
+
+          .title_num {}
+
+          .dot {
+            width: 40px;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            flex-wrap: wrap;
+
+            .dot_li {
+              width: 15px;
+              height: 15px;
+              border-radius: 4px;
+              background: #ff0000;
+              margin: 10px auto;
+
+              &:nth-child(2n) {
+                margin-right: 0;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/collectionpointmanage/CollectAlarmModal.vue b/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/collectionpointmanage/CollectAlarmModal.vue
new file mode 100644
index 0000000..827c6c9
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/collectionpointmanage/CollectAlarmModal.vue
@@ -0,0 +1,246 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="1100" @close="handleClose" destroy-on-close
+        :close-on-click-modal="false" :close-on-press-escape="false">
+
+        <div class="ml20">
+            <el-form :model="queryParams" :inline="true" label-width="78px">
+                <el-form-item label="鎶ヨ绾у埆">
+                    <el-select v-model="queryParams.alarmLevel" placeholder="鎶ヨ绾у埆" style="width: 170px;">
+                        <el-option v-for="dict in alarm_level" :key="dict.value" :label="dict.label"
+                            :value="dict.value" />
+                    </el-select>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class='table-box'>
+            <el-form :model="settingModel" ref="settingModelRef" :rules="settingModel.rules" :inline="true"
+                label-width="78px">
+                <el-table v-loading="loading" :data="settingModel.settingOptions" class="table-class"
+                    row-class-name="table-row-class" height="60vh" :default-sort="{ prop: 'date', order: 'descending' }"
+                    :span-method="arraySpanMethod">
+                    <el-table-column label="鏃舵" align="center" prop="timeSlotName" />
+                    <el-table-column label="闄愬�肩被鍨�" align="center" prop="limitTypeName" width="120" />
+                    <el-table-column label="鎶ヨ闄愬埗绫诲瀷" align="center" prop="alarmType" width="120"
+                        :formatter="(row, column) => proxy.selectDictLabel(alarm_level, row.alarmType)" />
+                    <el-table-column label="闄愬��" align="center" prop="limitVal">
+                        <template #default="scope">
+                            <el-form-item :prop="'settingOptions.' + scope.$index + '.limitVal'"
+                                :rules="settingModel.rules.numRule">
+                                <el-input style="width:200px" v-model="scope.row.limitVal" />
+                            </el-form-item>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </el-form>
+        </div>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="handleOk">纭畾</el-button>
+            <el-button @click="handleClose">鍙栨秷</el-button>
+        </div>
+    </el-dialog>
+</template>
+<script setup>
+
+import { alarmList } from '@/api/businessConfiguration/businessConfiguration'
+import { getSetting, updateDialogForm } from '@/api/businessConfiguration/preAlarmManage'
+let { proxy } = getCurrentInstance()
+let { alarm_level } = proxy.useDict('alarm_level')
+let { warn_time_slot } = proxy.useDict('warn_time_slot')
+
+
+let { alarm_type } = proxy.useDict('alarm_type')
+let visible = ref(false)
+let title = ref()
+let loading = ref(false)
+let currentNode = ref()
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+    alarmLevel: '',
+    nodeId: '',
+    indexType: 'COLLECT'
+})
+function handleOpen(modelNode, row) {
+    currentNode.value = modelNode
+    visible.value = true
+    queryParams.value.nodeId = modelNode.id
+    getList(row)
+}
+
+let resPoseList = ref([])
+let limitTypeOptions = ref([])
+const checkNum = (rule, value, callback) => {
+    let reg = /^-?\d+(\.\d{1,2})?$/;
+    if (value !== '' && !reg.test(value)) {
+        callback(new Error('鏁板瓧鍙�2浣嶅皬鏁�'));
+    } else {
+        callback();
+    }
+}
+let settingModel = ref({
+    // 鏃舵鏁扮粍
+    settingOptions: [],
+    stateById: '',
+    rules: {
+        numRule: [
+            { type: 'number', validator: checkNum, trigger: 'change' }
+        ]
+    }
+})
+
+function getList(e) {
+    getSetting(e.indexId).then(response => {
+        resPoseList.value = response.data
+        queryParams.value.alarmLevel = response.data.length > 0 ? response.data[0].alarmLevel : alarm_level.value[0].value
+        var data = {}
+        if ('鍚姩' == e.indexCategory) {
+            settingModel.value.stateById = '1'
+        } else if ('鍋滄' == e.indexCategory) {
+            settingModel.value.stateById = '2'
+        } else {
+            settingModel.value.stateById = ''
+        }
+        for (let j = 0; j < warn_time_slot.value.length; j++) {
+            if ('瀹炴椂' == warn_time_slot.value[j].label) {
+                for (let i = 0; i < limitTypeOptions.value.length; i++) {
+                    data = {
+                        indexId: e.indexId,
+                        //鏃舵鍚嶇О
+                        timeSlotName: warn_time_slot.value[j].label,
+                        //鏃舵鍊�
+                        timeSlotVal: warn_time_slot.value[j].value,
+                        //闄愬�肩被鍨嬪悕绉�
+                        limitTypeName: limitTypeOptions.value[i].limitName,
+                        //闄愬�肩被鍨嬪��
+                        limitTypeVal: limitTypeOptions.value[i].limitCode,
+                        //闄愬�肩殑鍊�
+                        limitVal: '',
+                        //涓婚敭
+                        id: '',
+                        //鍚仠鐘舵��
+                        state: settingModel.value.stateById,
+                        //鎶ヨ闄愬埗绫诲瀷
+                        alarmType: proxy.selectDictLabel(alarm_type.value, limitTypeOptions.value[i].alarmType),
+                        //鎶ヨ绾у埆
+                        alarmLevel: '',
+                        //nodeid
+                        nodeId: currentNode.value.id
+                    }
+                    settingModel.value.settingOptions.push(data)
+                }
+            } else {
+                continue
+            }
+        }
+        let a = settingModel.value.settingOptions
+        let b = response.data
+        for (let j = 0; j < a.length; j++) {
+            for (let i in b) {
+                if (a[j].timeSlotVal == b[i].timeSlot && a[j].limitTypeVal == b[i].limitType) {
+                    a[j].limitVal = b[i].limitVal
+                }
+            }
+        }
+        title.value = '鎸囨爣缂栧彿锛�' + e.code
+    })
+
+}
+
+
+let hbCount = ref(0)
+//鑾峰彇闄愬埗鍊肩被鍨嬪垪琛�
+function getListLimitType() {
+    alarmList(queryParams.value).then(response => {
+        hbCount.value = response.rows.length
+        limitTypeOptions.value = response.rows
+    })
+}
+
+function arraySpanMethod({ row, column, rowIndex, columnIndex }) {
+    if (columnIndex === 0) {
+        if (rowIndex % hbCount.value === 0) {
+            return {
+                rowspan: hbCount.value,
+                colspan: 1
+            }
+        } else {
+            return {
+                rowspan: 0,
+                colspan: 0
+            }
+        }
+    }
+}
+
+
+function handleClose() {
+    settingModel.value.settingOptions = []
+    visible.value = false
+}
+
+
+//琛ㄥ崟鎻愪氦
+function handleOk() {
+    for (let i = 0; i < settingModel.value.settingOptions.length; i++) {
+        settingModel.value.settingOptions[i].alarmLevel = queryParams.value.alarmLevel
+    }
+    proxy.$refs.settingModelRef.validate(valid => {
+        if (valid) {
+            updateDialogForm(settingModel.value.settingOptions || '').then(response => {
+                if (response.code === 200) {
+                    proxy.$modal.msgSuccess('淇敼鎴愬姛')
+                    visible.value = false
+                    settingModel.value.settingOptions = []
+                    visible.value = false
+                } else {
+                    proxy.$modal.msgError(response.msg);
+                }
+            })
+            visible.value = false
+        }
+    })
+}
+
+// 澶氶�夋閫変腑鏁版嵁
+function handleSelectionChange(selection) {
+    //     this.ids = selection.map(item => item.indexId)
+    //     this.names = selection.map(item => item.name)
+    //     this.single = selection.length !== 1
+    //     this.multiple = !selection.length
+    //     this.startStopOptions = selection.map(item => item.indexCategory)
+    //     this.codeOptions = selection.map(item => item.code)
+}
+
+
+getListLimitType()
+defineExpose({ handleOpen })
+</script>
+
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+:deep .table-class {
+    .table-row-class {
+        td {
+            border-bottom: 1px rgba(45, 103, 214, 0.5) solid !important;
+            border-left: 1px rgba(45, 103, 214, 0.5) solid !important;
+
+            &:last-child {
+                border-right: 1px rgba(45, 103, 214, 0.5) solid !important;
+            }
+        }
+
+
+        &:hover {
+            td {
+                background-color: transparent !important;
+            }
+        }
+
+        .el-table__border-left-patch {
+            background-color: rgba(255, 255, 255, 0.5) !important;
+        }
+    }
+}
+</style>
diff --git a/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/collectionpointmanage/CollectionPointManage.vue b/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/collectionpointmanage/CollectionPointManage.vue
new file mode 100644
index 0000000..b86b258
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/collectionpointmanage/CollectionPointManage.vue
@@ -0,0 +1,175 @@
+<template>
+  <div class="table-box">
+    <div class="form-card">
+      <el-form :model="queryParams" ref="queryRef" :inline="true">
+        <el-form-item label="閲囬泦鐐瑰悕绉�">
+          <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ラ噰闆嗙偣鍚嶇О" maxlength="30" />
+        </el-form-item>
+        <el-form-item label="閲囬泦鐐圭紪鐮�">
+          <el-input v-model="queryParams.code" placeholder="璇疯緭鍏ラ噰闆嗙偣缂栫爜" maxlength="30" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="getTabList">
+            鎼滅储
+          </el-button>
+          <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+        </el-form-item>
+        <el-form-item>
+          <el-button :disabled="multiple" type="success" icon="VideoPlay" @click="handleUpdateState('1')">
+            鍚敤
+          </el-button>
+          <el-button :disabled="multiple" type="warning" icon="VideoPause" @click="handleUpdateState('2')">
+            鍋滄
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <el-table v-loading="loading" row-key="indexId" :data="tableData" @selection-change="handleSelectionChange"
+      height="calc(100vh - 430px)" :default-sort="{ prop: 'date', order: 'descending' }">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="閲囬泦鐐瑰悕绉�" align="center" prop="name" />
+      <el-table-column label="閲囬泦鐐圭紪鐮�" align="center" prop="code" />
+      <el-table-column label="鍚仠鐘舵��" align="center" prop="indexCategory" />
+      <el-table-column label="鎿嶄綔" width="150" align="center">
+        <template #default="scope">
+          <el-button link type="primary" @click="handleAlarm(scope.row)">
+            鎶ヨ
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="queryParams.total > 0" :total="queryParams.total" v-model:page="queryParams.pageNum"
+      v-model:limit="queryParams.pageSize" @pagination="getTabList" />
+    <CollectAlarmModal ref="collectAlarmModalRef" />
+  </div>
+</template>
+<script setup>
+import CollectAlarmModal from './CollectAlarmModal.vue'
+import { getSettingIndex, getStartStop, getSettingCount, updateSet } from '@/api/businessConfiguration/preAlarmManage'
+
+let { proxy } = getCurrentInstance()
+const data = reactive({
+  form: {},
+  queryParams: {
+    code: null,
+    name: null,
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+  },
+});
+const { queryParams } = toRefs(data);
+let ids = ref([])
+let names = ref([])
+let single = ref(true)
+let multiple = ref(true)
+let startStopOptions = ref([])
+let codeOptions = ref([])
+
+let collectAlarmModalRef = ref()
+let loading = ref(false)
+let tableData = ref([])
+let currentNode = ref()
+function getList(modelNode) {
+  currentNode.value = modelNode
+  queryParams.value.nodeId = modelNode.id
+  queryParams.value.indexType = 'COLLECT',
+    getTabList()
+}
+function getTabList() {
+  loading.value = true;
+  getSettingIndex(queryParams.value).then(res => {
+    tableData.value = res.data.records;
+    queryParams.value.total = res.data.total
+    loading.value = false;
+    initStartStop()
+  })
+}
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value.code = null;
+  queryParams.value.name = null;
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  queryParams.value.total = 0;
+  getTabList();
+}
+function initStartStop() {
+  for (let i = 0; i < tableData.value.length; i++) {
+    let ndy = ''
+    getStartStop(tableData.value[i].indexId).then(response => {
+      if (response.code == '200') {
+        if (response.msg == '1') {
+          tableData.value[i].indexCategory = '鍚姩'
+        } else if (response.msg == '2') {
+          tableData.value[i].indexCategory = '鍋滄'
+        } else {
+          tableData.value[i].indexCategory = '灏氭湭璁剧疆'
+        }
+      } else {
+        tableData.value[i].indexCategory = ''
+      }
+    })
+  }
+}
+function handleAlarm(row) {
+  if (collectAlarmModalRef.value) {
+    collectAlarmModalRef.value.handleOpen(currentNode.value, row)
+  }
+}
+
+// 澶氶�夋閫変腑鏁版嵁
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.indexId)
+  names.value = selection.map(item => item.name)
+  single.value = selection.length !== 1
+  multiple.value = !selection.length
+  startStopOptions.value = selection.map(item => item.indexCategory)
+  codeOptions.value = selection.map(item => item.code)
+}
+
+function handleUpdateState(flag) {
+  let stateName = ''
+  if (flag == '1') {
+    stateName = '鍚姩'
+  } else {
+    stateName = '鍋滄'
+  }
+  getSettingCount(ids.value).then(response => {
+    let unStartStopArrName = []
+    let startStopArrIds = []
+    for (let i = 0; i < response.data.length; i++) {
+      if (0 == response.data[i]) {
+        unStartStopArrName.push(codeOptions.value[i])
+      } else {
+        startStopArrIds.push(ids.value[i])
+      }
+    }
+    if (unStartStopArrName.length > 0) {
+      var bh = unStartStopArrName.join('锛�')
+      proxy.$modal.confirm('閫変腑瀛樺湪鏈缃檺鍊肩殑浠櫒璁惧' + bh + '锛屾殏鏃犳硶' + stateName + '锛�', '璀﹀憡', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      }).catch(function () {
+      })
+    } else {
+      if (startStopArrIds.length > 0) {
+        updateSet(startStopArrIds || '', flag).then(response => {
+          if (response.code === 200) {
+            initStartStop()
+            proxy.$modal.msgSuccess('鎴愬姛')
+          } else {
+            proxy.$modal.msgError(response.msg)
+          }
+        })
+      }
+    }
+  })
+}
+defineExpose({
+  getList
+});
+</script>
+
+<style lang='scss' scoped></style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/statisticalindicatorsmanage/StatisticalIndicatorsManage.vue b/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/statisticalindicatorsmanage/StatisticalIndicatorsManage.vue
new file mode 100644
index 0000000..5937030
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/statisticalindicatorsmanage/StatisticalIndicatorsManage.vue
@@ -0,0 +1,175 @@
+<template>
+  <div class="table-box">
+    <div class="form-card">
+      <el-form :model="queryParams" ref="queryRef" :inline="true">
+        <el-form-item label="閲囬泦鐐瑰悕绉�">
+          <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ラ噰闆嗙偣鍚嶇О" maxlength="30" />
+        </el-form-item>
+        <el-form-item label="閲囬泦鐐圭紪鐮�">
+          <el-input v-model="queryParams.code" placeholder="璇疯緭鍏ラ噰闆嗙偣缂栫爜" maxlength="30" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="getTabList">
+            鎼滅储
+          </el-button>
+          <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+        </el-form-item>
+        <el-form-item>
+          <el-button :disabled="multiple" type="success" icon="VideoPlay" @click="handleUpdateState('1')">
+            鍚敤
+          </el-button>
+          <el-button :disabled="multiple" type="warning" icon="VideoPause" @click="handleUpdateState('2')">
+            鍋滄
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <el-table v-loading="loading" row-key="indexId" ref="tableRef" :data="tableData"
+      @selection-change="handleSelectionChange" height="calc(100vh - 430px)"
+      :default-sort="{ prop: 'date', order: 'descending' }">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="閲囬泦鐐瑰悕绉�" align="center" prop="name" />
+      <el-table-column label="閲囬泦鐐圭紪鐮�" align="center" prop="code" />
+      <el-table-column label="鍚仠鐘舵��" align="center" prop="indexCategory" />
+      <el-table-column label="鎿嶄綔" width="150" align="center">
+        <template #default="scope">
+          <el-button link type="primary" @click="handleAlarm(scope.row)">
+            鎶ヨ
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="queryParams.total > 0" :total="queryParams.total" v-model:page="queryParams.pageNum"
+      v-model:limit="queryParams.pageSize" @pagination="getTabList" />
+    <StatisticsAlarmModal ref="statisticsAlarmModalRef" />
+  </div>
+</template>
+<script setup>
+import StatisticsAlarmModal from './StatisticsAlarmModal.vue'
+import { getSettingIndex, getStartStop, getSettingCount, updateSet } from '@/api/businessConfiguration/preAlarmManage'
+
+let { proxy } = getCurrentInstance()
+const data = reactive({
+  form: {},
+  queryParams: {
+    code: null,
+    name: null,
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+  },
+});
+const { queryParams } = toRefs(data);
+let ids = ref([])
+let names = ref([])
+let single = ref(true)
+let multiple = ref(true)
+let startStopOptions = ref([])
+let codeOptions = ref([])
+let statisticsAlarmModalRef = ref()
+let loading = ref(false)
+let tableData = ref([])
+let currentNode = ref()
+function getList(modelNode) {
+  currentNode.value = modelNode
+  queryParams.value.nodeId = modelNode.id
+  queryParams.value.indexType = 'STATISTIC',
+    getTabList()
+}
+function getTabList() {
+  loading.value = true;
+  getSettingIndex(queryParams.value).then(res => {
+    tableData.value = res.data.records;
+    queryParams.value.total = res.data.total
+    loading.value = false;
+    initStartStop()
+  })
+}
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value.code = null;
+  queryParams.value.name = null;
+  queryParams.value.pageNum = 1;
+  queryParams.value.pageSize = 10;
+  queryParams.value.total = 0;
+  getTabList();
+}
+function initStartStop() {
+  for (let i = 0; i < tableData.value.length; i++) {
+    let ndy = ''
+    getStartStop(tableData.value[i].indexId).then(response => {
+      if (response.code == '200') {
+        if (response.msg == '1') {
+          tableData.value[i].indexCategory = '鍚姩'
+        } else if (response.msg == '2') {
+          tableData.value[i].indexCategory = '鍋滄'
+        } else {
+          tableData.value[i].indexCategory = '灏氭湭璁剧疆'
+        }
+      } else {
+        tableData.value[i].indexCategory = ''
+      }
+    })
+  }
+}
+function handleAlarm(row) {
+  if (statisticsAlarmModalRef.value) {
+    statisticsAlarmModalRef.value.handleOpen(currentNode.value, row)
+  }
+}
+
+// 澶氶�夋閫変腑鏁版嵁
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.indexId)
+  names.value = selection.map(item => item.name)
+  single.value = selection.length !== 1
+  multiple.value = !selection.length
+  startStopOptions.value = selection.map(item => item.indexCategory)
+  codeOptions.value = selection.map(item => item.code)
+}
+
+function handleUpdateState(flag) {
+  let stateName = ''
+  if (flag == '1') {
+    stateName = '鍚姩'
+  } else {
+    stateName = '鍋滄'
+  }
+  getSettingCount(ids.value).then(response => {
+    let unStartStopArrName = []
+    let startStopArrIds = []
+    for (let i = 0; i < response.data.length; i++) {
+      if (0 == response.data[i]) {
+        unStartStopArrName.push(codeOptions.value[i])
+      } else {
+        startStopArrIds.push(ids.value[i])
+      }
+    }
+    if (unStartStopArrName.length > 0) {
+      var bh = unStartStopArrName.join('锛�')
+      proxy.$modal.confirm('閫変腑瀛樺湪鏈缃檺鍊肩殑浠櫒璁惧' + bh + '锛屾殏鏃犳硶' + stateName + '锛�', '璀﹀憡', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      }).catch(function () {
+      })
+    } else {
+      if (startStopArrIds.length > 0) {
+        updateSet(startStopArrIds || '', flag).then(response => {
+          if (response.code === 200) {
+            initStartStop()
+            proxy.$modal.msgSuccess('鎴愬姛')
+          } else {
+            proxy.$modal.msgError(response.msg)
+          }
+        })
+      }
+    }
+  })
+}
+defineExpose({
+  getList
+});
+</script>
+
+<style lang='scss' scoped></style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/statisticalindicatorsmanage/StatisticsAlarmModal.vue b/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/statisticalindicatorsmanage/StatisticsAlarmModal.vue
new file mode 100644
index 0000000..65e0c49
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/prealarmmanage/components/statisticalindicatorsmanage/StatisticsAlarmModal.vue
@@ -0,0 +1,222 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="1100" @close="handleClose" destroy-on-close
+        :close-on-click-modal="false" :close-on-press-escape="false">
+        <div class='table-box'>
+            <el-form :model="settingModel" ref="settingModelRef" :rules="settingModel.rules" :inline="true"
+                label-width="78px">
+                <el-table v-loading="loading" :data="settingModel.settingOptions" height="60vh"
+                    :default-sort="{ prop: 'date', order: 'descending' }" class="table-class"
+                    row-class-name="table-row-class" :span-method="arraySpanMethod">
+                    <el-table-column label="鏃舵" align="center" prop="timeSlotName" />
+                    <el-table-column label="闄愬�肩被鍨�" align="center" prop="limitTypeName" width="120" />
+                    <el-table-column label="鎶ヨ闄愬埗绫诲瀷" align="center" prop="alarmType" width="120"
+                        :formatter="(row, column) => proxy.selectDictLabel(alarm_type, row.alarmType)" />
+                    <el-table-column label="闄愬��" align="center" prop="limitVal">
+                        <template #default="scope">
+                            <el-form-item :prop="'settingOptions.' + scope.$index + '.limitVal'"
+                                :rules="settingModel.rules.numRule">
+                                <el-input style="width:200px" v-model="scope.row.limitVal" />
+                            </el-form-item>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </el-form>
+        </div>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="handleOk">纭畾</el-button>
+            <el-button @click="handleClose">鍙栨秷</el-button>
+        </div>
+    </el-dialog>
+</template>
+<script setup>
+
+import { alarmList } from '@/api/businessConfiguration/businessConfiguration'
+import { getSetting, updateDialogForm } from '@/api/businessConfiguration/preAlarmManage'
+let { proxy } = getCurrentInstance()
+let { alarm_level } = proxy.useDict('alarm_level')
+let { warn_time_slot } = proxy.useDict('warn_time_slot')
+let { alarm_type } = proxy.useDict('alarm_type')
+
+let visible = ref(false)
+let title = ref()
+let loading = ref(false)
+let currentNode = ref()
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+    nodeId: '',
+    indexType: 'STATISTIC'
+})
+
+
+function handleOpen(modelNode, row) {
+    currentNode.value = modelNode
+    visible.value = true
+    queryParams.value.nodeId = modelNode.id
+    getList(row)
+}
+
+let resPoseList = ref([])
+let limitTypeOptions = ref([])
+const checkNum = (rule, value, callback) => {
+    let reg = /^-?\d+(\.\d{1,2})?$/;
+    if (value !== '' && !reg.test(value)) {
+        callback(new Error('鏁板瓧鍙�2浣嶅皬鏁�'));
+    } else {
+        callback();
+    }
+}
+let settingModel = ref({
+    // 鏃舵鏁扮粍
+    settingOptions: [],
+    stateById: '',
+    rules: {
+        numRule: [
+            { type: 'number', validator: checkNum, trigger: 'change' }
+        ]
+    }
+})
+
+function getList(e) {
+    getSetting(e.indexId).then(response => {
+        resPoseList.value = response.data
+        var data = {}
+        if ('鍚姩' == e.indexCategory) {
+            settingModel.value.stateById = '1'
+        } else if ('鍋滄' == e.indexCategory) {
+            settingModel.value.stateById = '2'
+        } else {
+            settingModel.value.stateById = ''
+        }
+        for (let j = 0; j < warn_time_slot.value.length; j++) {
+            if ('瀹炴椂' != warn_time_slot.value[j].label) {
+                for (let i = 0; i < limitTypeOptions.value.length; i++) {
+                    data = {
+                        indexId: e.indexId,
+                        //鏃舵鍚嶇О
+                        timeSlotName: warn_time_slot.value[j].label,
+                        //鏃舵鍊�
+                        timeSlotVal: warn_time_slot.value[j].value,
+                        //闄愬�肩被鍨嬪悕绉�
+                        limitTypeName: limitTypeOptions.value[i].limitName,
+                        //闄愬�肩被鍨嬪��
+                        limitTypeVal: limitTypeOptions.value[i].limitCode,
+                        //闄愬�肩殑鍊�
+                        limitVal: "",
+                        //涓婚敭
+                        id: "",
+                        //鍚仠鐘舵��
+                        state: settingModel.value.stateById,
+                        //鎶ヨ闄愬埗绫诲瀷
+                        alarmType: proxy.selectDictLabel(alarm_type.value, limitTypeOptions.value[i].alarmType),
+                        //nodeid
+                        nodeId: currentNode.value.id
+
+                    }
+                    settingModel.value.settingOptions.push(data)
+                }
+            } else {
+                continue
+            }
+        }
+        let a = settingModel.value.settingOptions
+        let b = response.data
+        for (let j = 0; j < a.length; j++) {
+            for (let i in b) {
+                if (a[j].timeSlotVal == b[i].timeSlot && a[j].limitTypeVal == b[i].limitType) {
+                    a[j].limitVal = b[i].limitVal
+                }
+            }
+        }
+        title.value = '鎸囨爣缂栧彿锛�' + e.code
+    })
+
+}
+
+
+let hbCount = ref(0)
+//鑾峰彇闄愬埗鍊肩被鍨嬪垪琛�
+function getListLimitType() {
+    alarmList(queryParams.value).then(response => {
+        hbCount.value = response.rows.length
+        limitTypeOptions.value = response.rows
+    })
+}
+
+function arraySpanMethod({ row, column, rowIndex, columnIndex }) {
+    if (columnIndex === 0) {
+        if (rowIndex % hbCount.value === 0) {
+            return {
+                rowspan: hbCount.value,
+                colspan: 1
+            }
+        } else {
+            return {
+                rowspan: 0,
+                colspan: 0
+            }
+        }
+    }
+}
+
+
+function handleClose() {
+    settingModel.value.settingOptions = []
+    visible.value = false
+}
+
+//琛ㄥ崟鎻愪氦
+function handleOk() {
+    proxy.$refs.settingModelRef.validate(valid => {
+        if (valid) {
+            updateDialogForm(settingModel.value.settingOptions || '').then(response => {
+                if (response.code === 200) {
+                    proxy.$modal.msgSuccess('淇敼鎴愬姛')
+                    visible.value = false
+                    settingModel.value.settingOptions = []
+                    visible.value = false
+                } else {
+                    proxy.$modal.msgError(response.msg);
+                }
+            })
+            visible.value = false
+        }
+    })
+}
+
+
+
+
+getListLimitType()
+defineExpose({ handleOpen })
+</script>
+
+
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+:deep .table-class {
+    .table-row-class {
+        td {
+            border-bottom: 1px rgba(45, 103, 214, 0.5) solid !important;
+            border-left: 1px rgba(45, 103, 214, 0.5) solid !important;
+
+            &:last-child {
+                border-right: 1px rgba(45, 103, 214, 0.5) solid !important;
+            }
+        }
+
+
+        &:hover {
+            td {
+                background-color: transparent !important;
+            }
+        }
+
+        .el-table__border-left-patch {
+            background-color: rgba(255, 255, 255, 0.5) !important;
+        }
+    }
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/businessconfiguration/prealarmmanage/prealarmmanage.vue b/zhitan-vue/src/views/businessconfiguration/prealarmmanage/prealarmmanage.vue
new file mode 100644
index 0000000..f8402c3
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/prealarmmanage/prealarmmanage.vue
@@ -0,0 +1,140 @@
+<template>
+    <div class="page">
+        <div class="page-container">
+            <div class="page-container-left">
+                <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+            </div>
+            <div class="page-container-right">
+                <div class="page-container-right">
+                    <div class="mb20 mt20 ml20 tab-box">
+                        <div class="tab-li" :class="tab == 1 ? 'is-tab' : ''" @click="handleTab('1')">
+                            閲囬泦鐐圭鐞�
+                        </div>
+                        <div class="tab-li" :class="tab == 2 ? 'is-tab' : ''" @click="handleTab('2')">
+                            缁熻鎸囨爣绠$悊
+                        </div>
+                    </div>
+                    <BaseCard :title="currentNode ? currentNode.label + '--鑺傜偣閰嶇疆' : '鏆傛棤鑺傜偣閰嶇疆'">
+                        <div>
+                            <div class="content-box" v-if="tab == '1'">
+                                <CollectionPointManage ref="collectionPointManageRef" />
+                            </div>
+                            <div class="content-box" v-if="tab == '2'">
+                                <StatisticalIndicatorsManage ref="statisticalIndicatorsManageRef" />
+                            </div>
+                        </div>
+                    </BaseCard>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</template>
+<script setup name="preAlarmManage">
+import CollectionPointManage from './components/collectionpointmanage/CollectionPointManage.vue'
+import StatisticalIndicatorsManage from './components/statisticalindicatorsmanage/StatisticalIndicatorsManage.vue'
+
+let currentNode = ref()
+let tab = ref('1')
+let collectionPointManageRef = ref()
+let statisticalIndicatorsManageRef = ref('1')
+
+function handleTab(value) {
+    tab.value = value
+    nextTick(() => {
+        if (value == 1 && collectionPointManageRef.value) {
+            collectionPointManageRef.value.getList(currentNode.value)
+        }
+        if (value == 2 && statisticalIndicatorsManageRef.value) {
+            statisticalIndicatorsManageRef.value.getList(currentNode.value)
+        }
+    });
+
+}
+
+function handleNodeClick(data) {
+    currentNode.value = data
+    handleTab(tab.value)
+    // handleQuery();
+}
+
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+
+.page-box {
+    height: calc(100vh - 145px);
+
+    .tree-box {
+        height: calc(100% - 70px);
+        overflow-y: auto !important;
+    }
+
+    .select-box {
+        display: flex;
+        align-items: center;
+
+        :deep .el-icon {
+            color: #fff;
+            margin: 0 10px 0 15px;
+            font-size: 20px;
+            // &:hover{
+            //     color: #3371EB;
+            // }
+        }
+    }
+
+    .node-opt {
+        flex: 1;
+        text-align: right;
+        margin-right: 5px;
+
+        :deep .el-icon {
+            color: #fff;
+            margin-right: 5px;
+        }
+    }
+
+
+}
+
+:deep .el-tabs__nav-wrap:after {
+    background: transparent;
+}
+
+:deep .el-tabs__item {
+    color: #fff;
+    font-size: 20px;
+    padding: 0 20px;
+
+    &.is-active,
+    &:hover {
+        color: #999 !important;
+    }
+}
+
+.tab-box {
+    display: flex;
+    align-items: center;
+    color: #fff;
+    border-bottom: 1px solid #3371EB;
+    margin-right: 20px;
+
+    .tab-li {
+        cursor: pointer;
+        border: 1px solid #3371EB;
+        padding: 10px 25px;
+        border-radius: 5px 5px 0 0;
+    }
+
+    .is-tab {
+        background: #3371EB;
+    }
+}
+
+.content-box {
+    height: calc(100vh - 317px) !important;
+
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/businessconfiguration/statusmonitor/statusMonitor.vue b/zhitan-vue/src/views/businessconfiguration/statusmonitor/statusMonitor.vue
new file mode 100644
index 0000000..6adb0bf
--- /dev/null
+++ b/zhitan-vue/src/views/businessconfiguration/statusmonitor/statusMonitor.vue
@@ -0,0 +1,124 @@
+<template>
+
+    <div class="page">
+        <div class="page-container">
+            <div class="page-container-left">
+                <div class="tree page-box">
+                    <el-tree :data="data" :props="defaultProps" default-expand-all />
+                </div>
+            </div>
+            <div class="page-container-right">
+                <div class="table-box">
+                    <div class="icon-box mb20">
+                        <div class="icon-box mr10">
+                            <div class="icon-div normal mr5"></div>
+                            <div>姝e父</div>
+                        </div>
+                        <div class="icon-box mr10">
+                            <div class="icon-div error mr5"></div>
+                            <div>寮傚父</div>
+                        </div>
+
+                    </div>
+
+                    <el-table :data="tableData" style="width: 100%">
+                        <el-table-column prop="value1" label="鍖哄煙" show-overflow-tooltip align="center" />
+                        <el-table-column prop="value2" label="浠〃缂栧彿" show-overflow-tooltip align="center" />
+                        <el-table-column prop="value3" label="浠〃鍚嶇О" show-overflow-tooltip align="center" />
+                        <el-table-column prop="value4" label="鏁版嵁" show-overflow-tooltip align="center">
+                            <template #default="scope">
+                                <div class="icon-div mr5 " :class="scope.row.value4 == '姝e父' ? 'normal' : 'error'"></div>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="value5" label="閲囬泦棰戠巼" show-overflow-tooltip align="center">
+                            <template #default="scope">
+                                <div class="icon-div mr5 " :class="scope.row.value5 == '姝e父' ? 'normal' : 'error'"></div>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+
+const defaultProps = ref({
+    children: 'children',
+    label: 'label',
+})
+
+let data = ref(
+    [{
+        id: 1,
+        label: '缇庢灄',
+        children: [
+            {
+                id: 2,
+                parent: 1,
+                label: '鍖哄煙1',
+            },
+            {
+                id: 3,
+                parent: 1,
+                label: '鍖哄煙2',
+                children: [{
+                    id: 4,
+                    parent: 3,
+                    label: '鍖哄煙2-1',
+                }]
+            },
+            {
+                id: 5,
+                parent: 1,
+                label: '鍖哄煙3',
+                children: [{
+                    id: 6,
+                    parent: 5,
+                    label: '鍖哄煙3-1',
+                }]
+            }
+        ]
+    }])
+
+
+let tableData = ref([
+    { id: 1, value1: '1', value2: '2', value3: '3', value4: '寮傚父', value5: '姝e父', value6: '6', value7: '7', value8: '8', },
+    { id: 2, value1: '11', value2: '22', value3: '33', value4: '姝e父', value5: '寮傚父', value6: '66', value7: '77', value8: '88', },
+])
+
+
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+.table-box {
+    margin-left: 20px;
+
+    .icon-box {
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        font-size: 14px;
+        color: #fff;
+
+    }
+
+    .icon-div {
+        height: 20px;
+        width: 20px;
+        border-radius: 50%;
+        margin: 0 auto;
+    }
+
+    .error {
+        background: red;
+    }
+
+    .normal {
+        background: green;
+    }
+}
+</style>
diff --git a/zhitan-vue/src/views/carbonemission/carbonEmission.vue b/zhitan-vue/src/views/carbonemission/carbonEmission.vue
new file mode 100644
index 0000000..8d49140
--- /dev/null
+++ b/zhitan-vue/src/views/carbonemission/carbonEmission.vue
@@ -0,0 +1,588 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="queryParams" ref="queryRef" :inline="true">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select v-model="queryParams.timeType" placeholder="鏈熼棿" clearable style="width: 120px"
+                @change="handleTimeType">
+                <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker v-model="queryParams.dataTime" :type="queryParams.timeType == 'YEAR'
+                ? 'year'
+                : queryParams.timeType == 'MONTH'
+                  ? 'month'
+                  : 'date'
+                " :format="queryParams.timeType == 'YEAR'
+                  ? 'YYYY'
+                  : queryParams.timeType == 'MONTH'
+                    ? 'YYYY-MM'
+                    : 'YYYY-MM-DD'
+                  " value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 100%" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Download" @click="handleExport">
+                瀵煎嚭
+              </el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div style="
+            height: calc(100vh - 220px) !important;
+            max-height: calc(100vh - 220px) !important;
+            overflow-y: auto;
+          ">
+          <div class="card-list" v-if="listTop.length > 1">
+            <el-button @click="dialogVisible = true"> 鏌ョ湅鏇村 </el-button>
+          </div>
+          <template v-for="(row, rowIndex) in listTop" :key="rowIndex" v-loading="loading">
+            <div class="card-list" v-if="rowIndex == 0">
+              <template v-for="(item, index) in row" :key="index">
+                <div class="card-list-item">
+                  <div class="item-top">
+                    <div class="top-icon" :style="{
+                      backgroundImage: 'url(' + bgList[index].icon + ')',
+                    }" />
+                    <div class="top-right">
+                      {{ item.allEneryType }}
+                    </div>
+                  </div>
+                  <div class="item-bottom">
+                    <div class="bottom-left">纰虫帓鏀鹃噺/tCO鈧俥</div>
+                    <div class="bottom-right" :style="{ color: bgList[index].color }">
+                      {{ item.totalEnery }}
+                    </div>
+                  </div>
+                  <div class="item-bottom">
+                    <div class="bottom-left">鍚屾瘮</div>
+                    <div class="bottom-right" :style="{ color: bgList[index].color }">
+                      {{ Math.abs(item.yoyEnery) }}%
+                      <el-icon v-if="!!item.yoyEnery" :color="item.yoyEnery > 0
+                        ? 'green'
+                        : item.yoyEnery < 0
+                          ? 'red'
+                          : ''
+                        ">
+                        <Top v-if="item.yoyEnery > 0" />
+                        <Bottom v-if="item.yoyEnery < 0" />
+                      </el-icon>
+                    </div>
+                  </div>
+                </div>
+              </template>
+            </div>
+          </template>
+          <BaseCard :title="queryParams.nodeName +
+            '-纰虫帓鏀鹃噺鍚岀幆姣�(' +
+            queryParams.dataTime +
+            ')'
+            ">
+            <div class="chart-box" v-loading="loading">
+              <div id="Chart1" />
+            </div>
+          </BaseCard>
+          <BaseCard :title="queryParams.nodeName +
+            '-纰虫帓鏀鹃噺缁熻鍒嗘瀽琛�(' +
+            queryParams.dataTime +
+            ')'
+            ">
+            <div class="table-box">
+              <el-table :data="listBottom" v-loading="loading">
+                <el-table-column label="鏃堕棿" align="center" key="xaxis" prop="xaxis" :show-overflow-tooltip="true" />
+                <el-table-column label="鐮存帓鏀鹃噺(tCO鈧俥)
+                  " align="center" key="yaxis" prop="yaxis" :show-overflow-tooltip="true" />
+                <el-table-column label="鍚屾瘮" align="center" key="yoy" prop="yoy" :show-overflow-tooltip="true" />
+                <el-table-column label="鐜瘮" align="center" key="qoq" prop="qoq" :show-overflow-tooltip="true" />
+              </el-table>
+            </div>
+          </BaseCard>
+        </div>
+      </div>
+    </div>
+    <el-dialog v-model="dialogVisible" title="鏌ョ湅" width="80%" v-if="dialogVisible">
+      <template v-for="row in listTop" :key="rowIndex" v-loading="loading">
+        <div class="card-list">
+          <template v-for="(item, index) in row" :key="index">
+            <div class="card-list-item">
+              <div class="item-top">
+                <div class="top-icon" :style="{
+                  backgroundImage: 'url(' + bgList[index].icon + ')',
+                }" />
+                <div class="top-right">
+                  {{ item.allEneryType }}
+                </div>
+              </div>
+              <div class="item-bottom">
+                <div class="bottom-left">纰虫帓鏀鹃噺/tCO鈧俥</div>
+                <div class="bottom-right" :style="{ color: bgList[index].color }">
+                  {{ item.totalEnery }}
+                </div>
+              </div>
+              <div class="item-bottom">
+                <div class="bottom-left">鍚屾瘮</div>
+                <div class="bottom-right" :style="{ color: bgList[index].color }">
+                  {{ Math.abs(item.yoyEnery) }}%
+                  <el-icon v-if="!!item.yoyEnery" :color="item.yoyEnery > 0
+                    ? 'green'
+                    : item.yoyEnery < 0
+                      ? 'red'
+                      : ''
+                    ">
+                    <Top v-if="item.yoyEnery > 0" />
+                    <Bottom v-if="item.yoyEnery < 0" />
+                  </el-icon>
+                </div>
+              </div>
+            </div>
+          </template>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup name="carbonEmission">
+import {
+  listUpCarbonemission,
+  listMiddleCarbonemission,
+} from "@/api/carbonemission/carbonemission";
+import * as echarts from "echarts";
+const { proxy } = getCurrentInstance();
+import { useRoute } from "vue-router";
+const { period } = proxy.useDict("period");
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getList();
+  }
+);
+const loading = ref(false);
+const dialogVisible = ref(false);
+import icon1 from "@/assets/images/period/icon1.png";
+import icon2 from "@/assets/images/period/icon2.png";
+import icon3 from "@/assets/images/period/icon3.png";
+import icon4 from "@/assets/images/period/icon4.png";
+import icon5 from "@/assets/images/period/icon5.png";
+const bgList = ref([
+  {
+    icon: icon1,
+    color: "#3371eb",
+  },
+  {
+    icon: icon2,
+    color: "#f52528",
+  },
+  {
+    icon: icon3,
+    color: "#ff6200",
+  },
+  {
+    icon: icon4,
+    color: "#ffce0c",
+  },
+  {
+    icon: icon5,
+    color: "#78e801",
+  },
+]);
+const listTop = ref([]);
+const listBottom = ref([]);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    nodeName: null,
+    timeType: null,
+    dataTime: null,
+  },
+  query: { ...useRoute().query },
+});
+const { queryParams, query } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  handleTimeType(period.value[0].value);
+  handleQuery();
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+// 纰虫帓鏀剧鐞�-纰虫帓鏀鹃噺鏍哥畻-鍒楄〃
+function getList() {
+  loading.value = true;
+  listTop.value = [];
+  listUpCarbonemission(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    res.data.upData.map((item, index) => {
+      if (index % 5 === 0) {
+        listTop.value.push(res.data.upData.slice(index, index + 5));
+      }
+    });
+  });
+  // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+  if (echarts.getInstanceByDom(document.getElementById("Chart1"))) {
+    echarts.dispose(document.getElementById("Chart1"));
+  }
+  const myChart1 = echarts.init(document.getElementById("Chart1"));
+  listMiddleCarbonemission(
+    proxy.addDateRange({
+      emissionType: "allType",
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    if (!!res.code && res.code == 200) {
+      loading.value = false;
+      let xaxis = [];
+      let yaxis = [];
+      let yoy = [];
+      let qoq = [];
+      if (!!res.data) {
+        res.data.map((item) => {
+          xaxis.push(item.xaxis);
+          yaxis.push(!!item.yaxis ? item.yaxis : 0);
+          yoy.push(!!item.yoy ? item.yoy : 0);
+          qoq.push(!!item.qoq ? item.qoq : 0);
+        });
+        listBottom.value = res.data;
+      }
+      setTimeout(() => {
+        myChart1.setOption({
+          color: ["#2979ff", "#19be6b", "#ff9900", "#fa3534"],
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+              textStyle: {
+                fontSize: 14,
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+          },
+          grid: {
+            top: "45",
+            left: "7%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          xAxis: {
+            type: "category",
+            axisPointer: {
+              type: "shadow",
+            },
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: xaxis,
+          },
+          yAxis: [
+            {
+              name: "tCO鈧俥",
+              type: "value",
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+            {
+              type: "value",
+              name: "%",
+              alignTicks: true,
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              axisTick: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+          ],
+          series: [
+            {
+              name: "纰虫帓鏀鹃噺",
+              type: "bar",
+              barWidth: "27",
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: yaxis,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+            {
+              name: "鍚屾瘮",
+              type: "line",
+              yAxisIndex: 1,
+              symbol: "none", // 璁剧疆涓� 'none' 鍘绘帀鍦嗙偣
+              data: yoy,
+            },
+            {
+              name: "鐜瘮",
+              type: "line",
+              yAxisIndex: 1,
+              symbol: "none", // 璁剧疆涓� 'none' 鍘绘帀鍦嗙偣
+              data: qoq,
+            },
+          ],
+        });
+      }, 100);
+      window.addEventListener(
+        "resize",
+        () => {
+          myChart1.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+}
+// 纰虫帓鏀剧鐞�-纰虫帓鏀鹃噺鏍哥畻-鎼滅储
+function handleQuery() {
+  getList();
+}
+// 纰虫帓鏀剧鐞�-纰虫帓鏀鹃噺鏍哥畻-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value.timeType = null;
+  queryParams.value.dataTime = null;
+  handleTimeType(period.value[0].value);
+  handleQuery();
+}
+// 纰虫帓鏀剧鐞�-纰虫帓鏀鹃噺鏍哥畻-瀵煎嚭
+function handleExport() {
+  proxy.download(
+    "carbonEmission/export",
+    {
+      emissionType: "allType",
+      ...queryParams.value,
+      ...query.value,
+    },
+    `${queryParams.value.nodeName}-纰虫帓鏀鹃噺鏍哥畻_${new Date().getTime()}.xlsx`
+  );
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+.themeDark {
+  .card-list {
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    padding: 18px;
+    color: #fff;
+
+    .card-list-item {
+      width: 19%;
+      margin-right: 1%;
+      height: 157px;
+      background: #223386;
+      border-radius: 5px 5px 5px 5px;
+      border: 1px solid #4868b7;
+      background-size: 100% 100%;
+      box-sizing: border-box;
+      padding: 25px 18px 12px 16px;
+
+      .item-top {
+        display: flex;
+        align-items: center;
+
+        .top-icon {
+          width: 40px;
+          height: 40px;
+          background-size: 100% 100%;
+        }
+
+        .top-right {
+          margin-left: 16px;
+          font-weight: bold;
+          font-size: 16px;
+          font-family: OPPOSans-Bold;
+        }
+      }
+
+      .item-bottom {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 18px;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 14px;
+      }
+    }
+  }
+}
+
+.themeLight {
+  .card-list {
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    padding: 18px;
+
+    .card-list-item {
+      width: 19%;
+      margin-right: 1%;
+      height: 157px;
+      background: #fff;
+      border-radius: 5px 5px 5px 5px;
+      border: 1px solid #e8e8e8;
+      background-size: 100% 100%;
+      box-sizing: border-box;
+      padding: 25px 18px 12px 16px;
+
+      .item-top {
+        display: flex;
+        align-items: center;
+
+        .top-icon {
+          width: 40px;
+          height: 40px;
+          background-size: 100% 100%;
+        }
+
+        .top-right {
+          margin-left: 16px;
+          font-weight: bold;
+          font-size: 16px;
+          color: #000;
+          font-family: OPPOSans-Bold;
+        }
+      }
+
+      .item-bottom {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 18px;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 14px;
+      }
+    }
+  }
+}
+
+.chart-box {
+  width: 100%;
+  height: calc(100vh - 500px) !important;
+
+  div {
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>
diff --git a/zhitan-vue/src/views/consumptionanalysis/consumptionAnalysis.vue b/zhitan-vue/src/views/consumptionanalysis/consumptionAnalysis.vue
new file mode 100644
index 0000000..7752f57
--- /dev/null
+++ b/zhitan-vue/src/views/consumptionanalysis/consumptionAnalysis.vue
@@ -0,0 +1,641 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="queryParams" ref="queryRef" :inline="true">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select v-model="queryParams.timeType" placeholder="鏈熼棿" clearable style="width: 120px"
+                @change="handleTimeType">
+                <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value"
+                  v-show="dict.value != 'DAY'" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker v-model="queryParams.dataTime" :type="queryParams.timeType == 'YEAR'
+                  ? 'year'
+                  : queryParams.timeType == 'MONTH'
+                    ? 'month'
+                    : 'date'
+                " :format="queryParams.timeType == 'YEAR'
+                    ? 'YYYY'
+                    : queryParams.timeType == 'MONTH'
+                      ? 'YYYY-MM'
+                      : 'YYYY-MM-DD'
+                  " value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+              <el-select v-model="queryParams.energyType" placeholder="鑳芥簮绫诲瀷" style="width: 120px">
+                <el-option :label="item.enername" :value="item.enersno" v-for="item in energyTypeList"
+                  :key="item.enersno" @click="handleEnergyType(item)" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="浜у搧绫诲瀷">
+              <el-select v-model="queryParams.prodType" placeholder="浜у搧绫诲瀷" style="width: 100%">
+                <el-option v-for="dict in product_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div style="
+            height: calc(100vh - 220px) !important;
+            max-height: calc(100vh - 220px) !important;
+            overflow-y: auto;
+          ">
+          <BaseCard :title="queryParams.nodeName + '-鎵规鍗曡�楄秼鍔垮垎鏋�'">
+            <el-row v-loading="loading">
+              <el-col :span="6" class="top">
+                <div class="num-box" v-for="(item, index) in consumptionAnalysisList[0]" :key="index">
+                  <el-icon size="50" :color="item.color" :class="item.icon" v-if="!!item.icon">
+                    <component :is="item.icon" />
+                  </el-icon>
+                  <el-icon size="50" v-else :color="item.num > 0 ? '#19be6b' : item.num < 0 ? '#fa3534' : ''
+                    ">
+                    <Top v-if="item.num > 0" />
+                    <SemiSelect v-if="item.num == 0" />
+                    <Bottom v-if="item.num < 0" />
+                  </el-icon>
+                  <dl class="ml10">
+                    <dd>{{ item.title }}({{ item.unit }})</dd>
+                    <dt class="num">{{ item.num }}</dt>
+                  </dl>
+                </div>
+              </el-col>
+              <el-col :span="17">
+                <div class="chart-box">
+                  <div id="Chart1" />
+                </div>
+              </el-col>
+            </el-row>
+          </BaseCard>
+          <BaseCard :title="queryParams.nodeName + '鎵规鑳借�椾笌浜ч噺瓒嬪娍鍒嗘瀽'">
+            <el-row v-loading="loading">
+              <el-col :span="6" class="bottom">
+                <div class="num-box" v-for="item in consumptionAnalysisList[1]">
+                  <el-icon size="50" :color="item.color" :class="item.icon">
+                    <component :is="item.icon" />
+                  </el-icon>
+                  <dl class="ml10">
+                    <dd>{{ item.title }}({{ item.unit }})</dd>
+                    <dt class="num">{{ item.num }}</dt>
+                  </dl>
+                </div>
+              </el-col>
+              <el-col :span="17">
+                <div class="chart-box">
+                  <div id="Chart2" />
+                </div>
+              </el-col>
+            </el-row>
+          </BaseCard>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup name="consumptionAnalysis">
+import { listConsumptionanalysis } from "@/api/consumptionAnalysis/consumptionAnalysis";
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+import * as echarts from "echarts";
+const { proxy } = getCurrentInstance();
+const { period, product_type } = proxy.useDict("period", "product_type");
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getList();
+  }
+);
+const energyTypeList = ref(undefined);
+const loading = ref(false);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    timeType: null,
+    dataTime: null,
+    energyType: null,
+    prodType: null,
+  },
+  query: { ...useRoute().query },
+});
+const { queryParams, query } = toRefs(data);
+const consumptionAnalysisList = ref([
+  [
+    {
+      icon: "Stopwatch",
+      color: "#19be6b",
+      title: "骞冲潎鎵规鍗曡��",
+      unit: null,
+      num: 0,
+    },
+  ],
+  [
+    {
+      icon: "TrendCharts",
+      color: "#19be6b",
+      title: "鎵规鎬昏兘鑰�",
+      unit: null,
+      num: 0,
+    },
+    {
+      icon: "Histogram",
+      color: "#2979ff",
+      title: "鎵规鎬讳骇閲�",
+      unit: "ton",
+      num: 0,
+    },
+  ],
+]);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  handleTimeType(period.value[1].value);
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+    queryParams.value.energyType = energyTypeList.value[0].enersno;
+    queryParams.value.enername = energyTypeList.value[0].enername;
+    queryParams.value.muid = energyTypeList.value[0].muid;
+    queryParams.value.prodType = product_type.value[0].value;
+    consumptionAnalysisList.value[0][0].unit = energyTypeList.value[0].muid + "/kg";
+    consumptionAnalysisList.value[1][0].unit = energyTypeList.value[0].muid;
+    handleQuery();
+  });
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+function handleEnergyType(item) {
+  queryParams.value.enername = item.enername;
+  queryParams.value.muid = item.muid;
+  consumptionAnalysisList.value[0][0].unit = item.muid + "/kg";
+  consumptionAnalysisList.value[1][0].unit = item.muid;
+  handleQuery();
+}
+// 浜у搧鍗曡�楀垎鏋�-浜у搧鍗曡�楀垎鏋�-鍒楄〃
+function getList() {
+  loading.value = true;
+  // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+  if (echarts.getInstanceByDom(document.getElementById("Chart1"))) {
+    echarts.dispose(document.getElementById("Chart1"));
+  }
+  if (echarts.getInstanceByDom(document.getElementById("Chart2"))) {
+    echarts.dispose(document.getElementById("Chart2"));
+  }
+  const myChart1 = echarts.init(document.getElementById("Chart1"));
+  const myChart2 = echarts.init(document.getElementById("Chart2"));
+  listConsumptionanalysis(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    if (!!res.code && res.code == 200) {
+      loading.value = false;
+      consumptionAnalysisList.value[0][0].num = !!res.data.averageEnergy ? res.data.averageEnergy : 0
+      consumptionAnalysisList.value[1][0].num = !!res.data.totalEnergy ? res.data.totalEnergy : 0
+      consumptionAnalysisList.value[1][1].num = !!res.data.totalProduct ? res.data.totalProduct : 0
+      let dateTime = [];
+      let average = [];
+      let energyCount = [];
+      let productCount = [];
+      if (!!res.data.chart) {
+        res.data.chart.map((item) => {
+          dateTime.push(
+            proxy
+              .dayjs(item.dateTime)
+              .format(
+                queryParams.value.timeType == "YEAR"
+                  ? "MM鏈�"
+                  : queryParams.value.timeType == "MONTH"
+                    ? "DD鏃�"
+                    : "HH鏃�"
+              )
+          );
+          average.push(!!item.average ? item.average : 0);
+          energyCount.push(!!item.energyCount ? item.energyCount : 0);
+          productCount.push(!!item.productCount ? item.productCount : 0);
+        });
+      }
+      setTimeout(() => {
+        myChart1.setOption({
+          color: ["#2979ff", "#19be6b", "#ff9900", "#fa3534"],
+          grid: {
+            top: "45",
+            left: "7%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          xAxis: {
+            type: "category",
+            axisPointer: {
+              type: "shadow",
+            },
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: dateTime,
+          },
+          yAxis: [
+            {
+              type: "value",
+              name:
+                "鑰�" +
+                queryParams.value.enername +
+                "閲�(" +
+                queryParams.value.muid +
+                "/kg)",
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+          ],
+          series: [
+            {
+              name: "鑰�" + queryParams.value.enername + "閲�",
+              type: "bar",
+              barWidth: "8",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + queryParams.value.muid;
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: average,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+          ],
+        });
+        myChart2.setOption({
+          color: ["#2979ff", "#ff9900"],
+          grid: {
+            top: "45",
+            left: "7%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          xAxis: {
+            type: "category",
+            axisPointer: {
+              type: "shadow",
+            },
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: dateTime,
+          },
+          yAxis: [
+            {
+              type: "value",
+              name:
+                "鑰�" +
+                queryParams.value.enername +
+                "閲�(" +
+                queryParams.value.muid +
+                ")",
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+            {
+              type: "value",
+              name: "浜ч噺(kg)",
+              alignTicks: true,
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              axisTick: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+          ],
+          series: [
+            {
+              name: "鑰�" + queryParams.value.enername + "閲�",
+              type: "bar",
+              barWidth: "8",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + queryParams.value.muid;
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: energyCount,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+            {
+              name: "浜ч噺",
+              type: "line",
+              yAxisIndex: 1,
+              symbol: "none", // 璁剧疆涓� 'none' 鍘绘帀鍦嗙偣
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + "ton";
+                },
+              },
+              data: productCount,
+            },
+          ],
+        });
+      }, 100);
+      window.addEventListener(
+        "resize",
+        () => {
+          myChart1.resize();
+          myChart2.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+}
+// 浜у搧鍗曡�楀垎鏋�-浜у搧鍗曡�楀垎鏋�-鎼滅储
+function handleQuery() {
+  getList();
+}
+// 浜у搧鍗曡�楀垎鏋�-浜у搧鍗曡�楀垎鏋�-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleTimeType(period.value[1].value);
+  queryParams.value.energyType = energyTypeList.value[0].enersno;
+  queryParams.value.enername = energyTypeList.value[0].enername;
+  queryParams.value.muid = energyTypeList.value[0].muid;
+  queryParams.value.prodType = null;
+  consumptionAnalysisList.value[0][0].unit = energyTypeList.value[0].muid;
+  consumptionAnalysisList.value[1][0].unit = energyTypeList.value[0].muid;
+  handleQuery();
+}
+</script>
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+.themeDark {
+
+  .top,
+  .bottom {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    justify-items: center;
+    flex-wrap: wrap;
+    color: #fff;
+  }
+
+  dl {
+    dd {
+      margin: 0;
+    }
+
+    .num {
+      font-family: OPPOSans, OPPOSans;
+      font-weight: bold;
+      font-size: 22px;
+      color: #ffffff;
+      font-style: normal;
+      text-transform: none;
+    }
+
+    dt {
+      font-size: 20px;
+    }
+  }
+
+  .num-box {
+    display: flex;
+    align-items: center;
+    width: 100%;
+    margin-left: 25%;
+  }
+}
+
+.themeLight {
+
+  .top,
+  .bottom {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    text-align: center;
+    flex-wrap: wrap;
+    color: #222222;
+  }
+
+  dl {
+    line-height: 30px;
+
+    dd {
+      margin: 0;
+    }
+
+    .num {
+      font-family: OPPOSans, OPPOSans;
+      font-weight: bold;
+      font-size: 22px;
+      color: #222222;
+      font-style: normal;
+      text-transform: none;
+    }
+
+    dt {
+      font-size: 20px;
+    }
+  }
+
+  .num-box {
+    display: flex;
+    align-items: center;
+    width: 100%;
+    margin-left: 25%;
+  }
+}
+</style>
diff --git a/zhitan-vue/src/views/costmanage/deviation/components/InfoModal.vue b/zhitan-vue/src/views/costmanage/deviation/components/InfoModal.vue
new file mode 100644
index 0000000..03d24eb
--- /dev/null
+++ b/zhitan-vue/src/views/costmanage/deviation/components/InfoModal.vue
@@ -0,0 +1,142 @@
+<template>
+    <el-dialog v-model="visible" title="璇︽儏" width="1000" @close="handleClose">
+        <div class="chart-box" v-loading="loading">
+            <div id="Chart1" />
+        </div>
+
+    </el-dialog>
+</template>
+
+<script setup>
+import { deviationInfo } from '@/api/costManage/costManage.js'
+import * as echarts from "echarts";
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+import { nextTick } from "vue";
+let emit = defineEmits(['getList'])
+let visible = ref(false)
+let loading = ref(false)
+function Chart(xData, yData) {
+    const myChart1 = echarts.init(document.getElementById("Chart1"));
+    myChart1.setOption({
+        grid: {
+            top: "45",
+            left: "5%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+        },
+        tooltip: {
+            trigger: "axis",
+            axisPointer: {
+                type: "shadow",
+            },
+        },
+        xAxis: {
+            type: "category",
+            axisLine: {
+                show: true,
+                lineStyle: {
+                    color:
+                        settingsStore.sideTheme == "theme-dark"
+                            ? "#FFFFFF"
+                            : "#222222",
+                },
+            },
+            axisTick: {
+                show: false,
+            },
+            splitArea: {
+                show: false,
+            },
+            splitLine: {
+                show: false,
+            },
+            axisLabel: {
+                color:
+                    settingsStore.sideTheme == "theme-dark"
+                        ? "#FFFFFF"
+                        : "#222222",
+                fontSize: 14,
+                interval: 0,
+                padding: [5, 0, 0, 0],
+                //   formatter: '{value} ml'
+            },
+            data: xData
+        },
+        yAxis: {
+            type: "value",
+            name: '鍗冪摝鏃�',
+            nameTextStyle: {
+                color: settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+            },
+            axisLine: {
+                show: false,
+            },
+            splitLine: {
+                show: true,
+                lineStyle: {
+                    type: "dashed",
+                    color: settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+                },
+            },
+            axisTick: {
+                show: false,
+            },
+            splitArea: {
+                show: false,
+            },
+            axisLabel: {
+                color: settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+                fontSize: 14,
+            },
+
+        },
+        series: [
+            {
+                data: yData,
+                type: 'bar'
+            }
+        ]
+    })
+}
+
+
+function handleOpen(row, form) {
+    visible.value = true
+    loading.value = true
+    // {
+    //     modelCode: 'PEAK_VALLEY', timeType: 'MONTH',
+    //     date: '2024-02',
+    //     nodeId: '455b93a7-4b04-441e-bf78-ee882e4a0b93'
+    // } 
+    deviationInfo({ nodeId: row.nodeId, ...form }).then((result) => {
+        if (result.code == 200) {
+            let { xData, yData } = result.data
+            nextTick(() => {
+                Chart(xData, yData)
+            })
+            loading.value = false
+        }
+    }).catch((err) => {
+
+    });
+}
+
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+}
+
+
+defineExpose({ handleOpen })
+
+</script>
+
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/costmanage/deviation/deviation.vue b/zhitan-vue/src/views/costmanage/deviation/deviation.vue
new file mode 100644
index 0000000..2f8dce6
--- /dev/null
+++ b/zhitan-vue/src/views/costmanage/deviation/deviation.vue
@@ -0,0 +1,687 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="form" ref="formRef" :inline="true">
+                <el-form-item label="鏈熼棿" prop="timeType">
+                    <el-select v-model="form.timeType" placeholder="鏈熼棿" style="width: 100%" @change="handleTimeType">
+                        <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value"
+                            v-show="dict.value != 'DAY'" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="鏃堕棿">
+                    <el-date-picker v-model="form.date" type="year" v-if="form.timeType == 'YEAR'" range-separator="鍒�"
+                        format="YYYY" value-format="YYYY" placeholder="鏃堕棿" style="width: 100%" :clearable="false" />
+                    <el-date-picker v-model="form.date" type="month" v-else format="YYYY-MM" value-format="YYYY-MM"
+                        placeholder="鏃堕棿" style="width: 100%" :clearable="false" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <BaseCard title="鐢靛姏鏁版嵁">
+            <div class="card-list">
+                <div class="card-list-item" v-for="item in periodList" :key="item.title">
+                    <div class="item-top">
+                        <div class="icon" :style="{ backgroundImage: 'url(' + item.icon + ')' }"></div>
+                        <div class="name">{{ item.title }}</div>
+                    </div>
+                    <div class="item-bottom" v-for="(node, nodeIndex) in item.data" :key="nodeIndex">
+                        <div class="bottom-left">{{ node.label }}</div>
+                        <div class="bottom-right" :style="{ color: item.color }">
+                            {{ form.timeType == 'YEAR' && nodeIndex == 1 ? '--' : node.value }}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </BaseCard>
+        <BaseCard title="缁熻鏁版嵁">
+            <div class="card-list card-list19 ">
+                <div class="card-list-item" v-for="item in periodList1" :key="item.title">
+                    <div class="item-top">
+                        <div class="icon" :style="{ backgroundImage: 'url(' + item.icon + ')' }"></div>
+                        <div class="name">{{ item.title }}</div>
+                    </div>
+                    <div class="item-bottom" v-for="(node, nodeIndex) in item.data" :key="nodeIndex">
+                        <div class="bottom-left">{{ node.label }}</div>
+                        <div class="bottom-right" :style="{ color: item.color }">
+                            {{ form.timeType == 'YEAR' && nodeIndex == 1 ? '--' : node.value }}
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </BaseCard>
+        <BaseCard title="鑰楃數鏄庣粏">
+            <div class="table-box">
+                <el-table :data="tableData" v-loading="loading"
+                    style="width: 100%; max-height: 500px; margin-bottom: 20px" row-key="id">
+                    <el-table-column prop="nodeId" label="鐢ㄨ兘鍗曚綅" align="center" show-overflow-tooltip
+                        :formatter="(row, column) => findLabelById(treeNode, row.nodeId)" />
+                    <el-table-column prop="electricityNum" label="鎬昏�楃數閲�(鍗冪摝鏃�)" align="center" show-overflow-tooltip />
+                    <el-table-column prop="sharpElectricity" label="灏�(鍗冪摝鏃�)" align="center" show-overflow-tooltip />
+                    <el-table-column prop="peakElectricity" label="宄�(鍗冪摝鏃�)" align="center" show-overflow-tooltip />
+                    <el-table-column prop="flatElectricity" label="骞�(鍗冪摝鏃�)" align="center" show-overflow-tooltip />
+                    <el-table-column prop="valleyElectricity" label="璋�(鍗冪摝鏃�)" align="center" show-overflow-tooltip />
+                    <el-table-column prop="electricityFee" label="鎬荤數璐癸紙鍏冿級" align="center" show-overflow-tooltip />
+                    <el-table-column prop="electricityNumYoy" label="鍚屾瘮" align="center" show-overflow-tooltip />
+                    <el-table-column prop="electricityNumQoq" label="鐜瘮" v-if="form.timeType != 'YEAR'" align="center"
+                        show-overflow-tooltip />
+                    <el-table-column prop="electricityNumRadio" label="鍗犳瘮" align="center" show-overflow-tooltip />
+                    <el-table-column label="鎿嶄綔" width="80" align="center">
+                        <template #default="scope">
+                            <el-button link type="primary" icon="Document" @click="handleInfo(scope.row)">
+                                璇︽儏
+                            </el-button>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </div>
+        </BaseCard>
+        <InfoModal ref="InfoModalRef" />
+    </div>
+</template>
+<script setup>
+import { deviationCardList, deviationTableList } from '@/api/costManage/costManage.js'
+import InfoModal from './components/InfoModal.vue'
+import { useRoute } from "vue-router";
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+import { treeList } from "@/api/modelConfiguration/indexWarehouse";
+let loading = ref(false)
+let form = ref({
+    timeType: '',
+    date: null,
+    ...useRoute().query
+})
+import icon1 from "@/assets/images/period/icon1.png";
+import icon2 from "@/assets/images/period/icon2.png";
+import icon3 from "@/assets/images/period/icon3.png";
+import icon4 from "@/assets/images/period/icon4.png";
+import icon5 from "@/assets/images/period/icon5.png";
+
+
+const periodList = ref([
+    {
+        icon: icon1,
+        color: "#3371eb",
+        title: "鎬荤數閲忥紙鍗冪摝鏃�)",
+        value: 0,
+        data: [
+            {
+                label: "鍚屾瘮",
+                value: '0',
+            },
+            {
+                label: "鐜瘮",
+                value: '0',
+            },
+        ],
+    },
+    {
+        icon: icon2,
+        color: "#f52528",
+        title: "鎬荤數璐癸紙鍏冿級",
+        value: 5566778,
+        data: [
+            {
+                label: "鍚屾瘮",
+                value: '0',
+            },
+            {
+                label: "鐜瘮",
+                value: '0',
+            },
+        ],
+    },
+    {
+        icon: icon3,
+        color: "#ff6200",
+        title: "鍔熺巼鍥犳暟",
+        value: 0.9,
+        data: [
+            {
+                label: "鍚屾瘮",
+                value: '0',
+            },
+            {
+                label: "鐜瘮",
+                value: '0',
+            },
+        ],
+    },
+    {
+        icon: icon4,
+        color: "#ffce0c",
+        title: "灏栧嘲骞宠胺鐢ㄧ數",
+        data: [
+            {
+                label: "灏�",
+                value: 0,
+            },
+            {
+                label: "宄�",
+                value: 0,
+            },
+            {
+                label: "骞�",
+                value: 0,
+            },
+            {
+                label: "璋�",
+                value: 0,
+            },
+        ],
+    },
+]);
+
+
+const periodList1 = ref(
+    [{
+        icon: icon1,
+        color: "#3371eb",
+        title: "鎬荤數閲忥紙鍗冪摝鏃�)",
+        value: 0,
+        data: [
+            {
+                label: "鍚屾瘮",
+                value: '0',
+            },
+            {
+                label: "鐜瘮",
+                value: '0',
+            },
+            {
+                label: "瀵规瘮宸�硷紙鍗冪摝鏃讹級",
+                value: '0',
+            },
+        ],
+    },
+    {
+        icon: icon2,
+        color: "#f52528",
+        title: "鎬荤數璐癸紙鍏冿級",
+        value: 0,
+        data: [
+            {
+                label: "鍚屾瘮",
+                value: '0',
+            },
+            {
+                label: "鐜瘮",
+                value: '0',
+            },
+            {
+                label: "瀵规瘮宸�硷紙鍗冪摝鏃讹級",
+                value: '0',
+            },
+        ],
+    },
+    {
+        icon: icon3,
+        color: "#ff6200",
+        title: "鍔熺巼鍥犳暟",
+        value: 0,
+        data: [
+            {
+                label: "鍚屾瘮",
+                value: '0',
+            },
+            {
+                label: "鐜瘮",
+                value: '0',
+            },
+            {
+                label: "瀵规瘮宸�硷紙鍗冪摝鏃讹級",
+                value: '0',
+            },
+        ],
+    },
+    {
+        icon: icon4,
+        color: "#ffce0c",
+        title: "灏栧嘲骞宠胺鐢ㄧ數",
+        data: [
+            {
+                label: "灏�",
+                value: 0,
+            },
+            {
+                label: "宄�",
+                value: 0,
+            },
+            {
+                label: "骞�",
+                value: 0,
+            },
+            {
+                label: "璋�",
+                value: 0,
+            },
+        ],
+    },
+    {
+        icon: icon5,
+        color: "#ffce0c",
+        title: "灏栧嘲骞宠胺鐢ㄧ數瀵规瘮宸��",
+        data: [
+            {
+                label: "灏�",
+                value: 0,
+            },
+            {
+                label: "宄�",
+                value: 0,
+            },
+            {
+                label: "骞�",
+                value: 0,
+            },
+            {
+                label: "璋�",
+                value: 0,
+            },
+        ],
+    },
+    ]);
+handleTimeType('YEAR')
+function handleTimeType(e) {
+    form.value.timeType = e;
+    form.value.date = proxy.dayjs(new Date()).format(e == "YEAR" ? "YYYY" : "YYYY-MM");
+    getList()
+    getTableList()
+}
+
+let tableData = ref([])
+//鑾峰彇鍒楄〃
+function getList() {
+    deviationCardList(form.value).then(res => {
+        if (res.code == 200) {
+            let { CostElectricityData, CostStatisticsData } = res.data
+            periodList.value = [
+                {
+                    icon: icon1,
+                    color: "#3371eb",
+                    title: "鎬荤數閲忥紙鍗冪摝鏃�)",
+                    value: CostElectricityData.electricityNum ? CostElectricityData.electricityNum : 0,
+                    data: [
+                        {
+                            label: "鍚屾瘮",
+                            value: CostElectricityData.electricityNumYoy ? CostElectricityData.electricityNumYoy + '%' : 0,
+                        },
+                        {
+                            label: "鐜瘮",
+                            value: CostElectricityData.electricityNumQoq ? CostElectricityData.electricityNumQoq + '%' : 0,
+                        },
+                    ],
+                },
+                {
+                    icon: icon2,
+                    color: "#f52528",
+                    title: "鎬荤數璐癸紙鍏冿級",
+                    value: CostElectricityData.electricityFee ? CostElectricityData.electricityFee : 0,
+                    data: [
+                        {
+                            label: "鍚屾瘮",
+                            value: CostElectricityData.electricityFeeYoy ? CostElectricityData.electricityFeeYoy + '%' : 0,
+                        },
+                        {
+                            label: "鐜瘮",
+                            value: CostElectricityData.electricityFeeQoq ? CostElectricityData.electricityFeeQoq + '%' : 0,
+                        },
+                    ],
+                },
+                {
+                    icon: icon3,
+                    color: "#ff6200",
+                    title: "鍔熺巼鍥犳暟",
+                    value: CostElectricityData.powerFactor ? CostElectricityData.powerFactor : 0,
+                    data: [
+                        {
+                            label: "鍚屾瘮",
+                            value: CostElectricityData.powerFactorYoy ? CostElectricityData.powerFactorYoy + '%' : 0,
+                        },
+                        {
+                            label: "鐜瘮",
+                            value: CostElectricityData.powerFactorQoq ? CostElectricityData.powerFactorQoq + '%' : 0,
+                        },
+                    ],
+                },
+                {
+                    icon: icon4,
+                    color: "#ffce0c",
+                    title: "灏栧嘲骞宠胺鐢ㄧ數",
+                    data: [
+                        {
+                            label: "灏栵紙鍗冪摝鏃�)",
+                            value: CostElectricityData.sharpElectricity ? CostElectricityData.sharpElectricity : 0,
+                        },
+                        {
+                            label: "宄帮紙鍗冪摝鏃�)",
+                            value: CostElectricityData.peakElectricity ? CostElectricityData.peakElectricity : 0,
+                        },
+                        {
+                            label: "骞筹紙鍗冪摝鏃�)",
+                            value: CostElectricityData.flatElectricity ? CostElectricityData.flatElectricity : 0,
+                        },
+                        {
+                            label: "璋凤紙鍗冪摝鏃�)",
+                            value: CostElectricityData.valleyElectricity ? CostElectricityData.valleyElectricity : 0,
+                        },
+                    ],
+                },
+            ]
+
+            periodList1.value = [{
+                icon: icon1,
+                color: "#3371eb",
+                title: "鎬荤數閲忥紙鍗冪摝鏃�)",
+                value: CostStatisticsData && CostStatisticsData.electricityNum ? CostStatisticsData.electricityNum : 0,
+                data: [
+                    {
+                        label: "鍚屾瘮",
+                        value: CostStatisticsData && CostStatisticsData.electricityNumYoy ? CostStatisticsData.electricityNumYoy : 0,
+                    },
+                    {
+                        label: "鐜瘮",
+                        value: CostStatisticsData && CostStatisticsData.electricityNumQoq ? CostStatisticsData.electricityNumQoq : 0,
+                    },
+                    {
+                        label: "瀵规瘮宸�硷紙鍗冪摝鏃讹級",
+                        value: CostStatisticsData && CostStatisticsData.electricityNumDiff ? CostStatisticsData.electricityNumDiff : 0,
+                    },
+                ],
+            },
+            {
+                icon: icon2,
+                color: "#f52528",
+                title: "鎬荤數璐癸紙鍏冿級",
+                value: CostStatisticsData && CostStatisticsData.electricityFee ? CostStatisticsData.electricityFee : 0,
+                data: [
+                    {
+                        label: "鍚屾瘮",
+                        value: CostStatisticsData && CostStatisticsData.electricityFeeYoy ? CostStatisticsData.electricityFeeYoy : 0,
+                    },
+                    {
+                        label: "鐜瘮",
+                        value: CostStatisticsData && CostStatisticsData.electricityNumQoq ? CostStatisticsData.electricityNumQoq : 0,
+                    },
+                    {
+                        label: "瀵规瘮宸�硷紙鍗冪摝鏃讹級",
+                        value: CostStatisticsData && CostStatisticsData.electricityNumDiff ? CostStatisticsData.electricityNumDiff : 0,
+                    },
+                ],
+            },
+            {
+                icon: icon3,
+                color: "#ff6200",
+                title: "鍔熺巼鍥犳暟",
+                value: CostStatisticsData && CostStatisticsData.powerFactor ? CostStatisticsData.powerFactor : 0,
+                data: [
+                    {
+                        label: "鍚屾瘮",
+                        value: CostStatisticsData && CostStatisticsData.powerFactorYoy ? CostStatisticsData.powerFactorYoy : 0,
+                    },
+                    {
+                        label: "鐜瘮",
+                        value: CostStatisticsData && CostStatisticsData.powerFactorQoq ? CostStatisticsData.powerFactorQoq : 0,
+                    },
+                    {
+                        label: "瀵规瘮宸��",
+                        value: CostStatisticsData && CostStatisticsData.powerFactorDiff ? CostStatisticsData.powerFactorDiff : 0,
+                    },
+                ],
+            },
+            {
+                icon: icon4,
+                color: "#ffce0c",
+                title: "灏栧嘲骞宠胺鐢ㄧ數",
+                data: [
+                    {
+                        label: "灏�",
+                        value: CostStatisticsData && CostStatisticsData.sharpElectricity ? CostStatisticsData.sharpElectricity : 0,
+                    },
+                    {
+                        label: "宄�",
+                        value: CostStatisticsData && CostStatisticsData.peakElectricity ? CostStatisticsData.peakElectricity : 0,
+                    },
+                    {
+                        label: "骞�",
+                        value: CostStatisticsData && CostStatisticsData.flatElectricity ? CostStatisticsData.flatElectricity : 0,
+                    },
+                    {
+                        label: "璋�",
+                        value: CostStatisticsData && CostStatisticsData.valleyElectricity ? CostStatisticsData.valleyElectricity : 0,
+                    },
+                ],
+            },
+            {
+                icon: icon5,
+                color: "#ffce0c",
+                title: "灏栧嘲骞宠胺鐢ㄧ數瀵规瘮宸��",
+                data: [
+                    {
+                        label: "灏�",
+                        value: CostStatisticsData && CostStatisticsData.sharpElectricityDiff ? CostStatisticsData.sharpElectricityDiff : 0,
+                    },
+                    {
+                        label: "宄�",
+                        value: CostStatisticsData && CostStatisticsData.peakElectricityDiff ? CostStatisticsData.peakElectricityDiff : 0,
+                    },
+                    {
+                        label: "骞�",
+                        value: CostStatisticsData && CostStatisticsData.flatElectricityDiff ? CostStatisticsData.flatElectricityDiff : 0,
+                    },
+                    {
+                        label: "璋�",
+                        value: CostStatisticsData && CostStatisticsData.valleyElectricityDiff ? CostStatisticsData.valleyElectricityDiff : 0,
+                    },
+                ],
+            },
+            ]
+        }
+    })
+
+}
+
+function getTableList() {
+    loading.value = true
+    deviationTableList(form.value).then(res => {
+        tableData.value = []
+        if (res.code == 200) {
+            tableData.value.push(res.data)
+            loading.value = false
+        }
+    })
+}
+
+function resetQuery() {
+    getList()
+    getTableList()
+}
+function handleQuery() {
+    getList()
+    getTableList()
+}
+
+let InfoModalRef = ref()
+function handleInfo(row) {
+    if (InfoModalRef.value) {
+        InfoModalRef.value.handleOpen(row, form.value)
+    }
+
+}
+let treeNode = ref([])
+function handleNodeTree() {
+    treeList(useRoute().query).then((response) => {
+        treeNode.value = response.data;
+    })
+}
+handleNodeTree()
+
+// 閫掑綊鍑芥暟锛屾牴鎹甶d鏌ユ壘label
+function findLabelById(tree, targetId) {
+    for (const node of tree) {
+        if (node.id === targetId) {
+            return node.label;
+        }
+        if (node.children && node.children.length > 0) {
+            const result = findLabelById(node.children, targetId);
+            if (result) {
+                return result;
+            }
+        }
+    }
+    return null;
+}
+
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+:deep(.el-table__expand-icon>.el-icon) {
+    color: #cac9c9 !important;
+}
+
+
+.themeDark {
+    .tree-title-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+    }
+
+    .card-list {
+        display: flex;
+        justify-content: space-between;
+        padding: 18px;
+
+        .card-list-item {
+            width: 24%;
+            height: 187px;
+            background: #223386;
+            border-radius: 5px 5px 5px 5px;
+            border: 1px solid #4868b7;
+            background-size: 100% 100%;
+            box-sizing: border-box;
+            padding: 10px 18px 13px 16px;
+
+            .item-top {
+                display: flex;
+                align-items: center;
+
+                .icon {
+                    width: 40px;
+                    height: 40px;
+                    background-size: 100% 100%;
+                }
+
+                .name {
+                    font-family: OPPOSans, OPPOSans;
+                    font-weight: bold;
+                    font-size: 20px;
+                    color: #fffdfd;
+                    margin-left: 7px;
+                }
+            }
+
+            .item-bottom {
+                display: flex;
+                justify-content: space-between;
+                margin-top: 10px;
+                font-family: OPPOSans, OPPOSans;
+                font-weight: bold;
+                font-size: 16px;
+
+                .bottom-left {
+                    font-family: OPPOSans, OPPOSans;
+                    font-weight: 500;
+                    font-size: 14px;
+                    color: rgba(255, 255, 255, 0.7);
+                }
+
+                .bottom-right {
+                    font-family: OPPOSans, OPPOSans;
+                    font-weight: 800;
+                    font-size: 16px;
+                    color: #f52528;
+                }
+            }
+        }
+    }
+}
+
+.themeLight {
+    .tree-title-box {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+    }
+
+    .card-list {
+        display: flex;
+        justify-content: space-between;
+        padding: 18px;
+
+        .card-list-item {
+            width: 24%;
+            height: 187px;
+            background: #fff;
+            border-radius: 5px 5px 5px 5px;
+            border: 1px solid #e8e8e8;
+            background-size: 100% 100%;
+            box-sizing: border-box;
+            padding: 10px 18px 13px 16px;
+
+            .item-top {
+                display: flex;
+                align-items: center;
+
+                .icon {
+                    width: 40px;
+                    height: 40px;
+                    background-size: 100% 100%;
+                }
+
+                .name {
+                    font-family: OPPOSans, OPPOSans;
+                    font-weight: bold;
+                    font-size: 20px;
+                    color: #000;
+                    margin-left: 7px;
+                }
+            }
+
+            .item-bottom {
+                display: flex;
+                justify-content: space-between;
+                margin-top: 10px;
+                font-family: OPPOSans, OPPOSans;
+                font-weight: bold;
+                font-size: 16px;
+
+                .bottom-left {
+                    font-family: OPPOSans, OPPOSans;
+                    font-weight: 500;
+                    font-size: 14px;
+                    color: #5d5c5c;
+                }
+
+                .bottom-right {
+                    font-family: OPPOSans, OPPOSans;
+                    font-weight: 800;
+                    font-size: 16px;
+                    color: #f52528;
+                }
+            }
+        }
+    }
+}
+
+.card-list19 {
+    .card-list-item {
+        width: 19% !important;
+    }
+}
+</style>
diff --git a/zhitan-vue/src/views/costmanage/unitpricecorrelation/components/EditModal.vue b/zhitan-vue/src/views/costmanage/unitpricecorrelation/components/EditModal.vue
new file mode 100644
index 0000000..1513a33
--- /dev/null
+++ b/zhitan-vue/src/views/costmanage/unitpricecorrelation/components/EditModal.vue
@@ -0,0 +1,105 @@
+<template>
+    <el-dialog v-model="visible" title="鈥嬪崟浠风瓥鐣ュ叧鑱�" width="600" @close="handleClose">
+        <el-form :model="form" ref="queryRef" :rules="formRules" :label-width="120">
+            <el-form-item label="鐢ㄨ兘鍗曚綅" prop="nodeName">
+                <el-input v-model="form.nodeName" placeholder="璇疯緭鍏ョ敤鑳藉崟浣�" :readonly="true" />
+            </el-form-item>
+            <el-form-item label="鑳芥簮鍝佺" prop="energyType" :rules="formRules.energyType">
+                <el-select v-model="form.energyType" placeholder="璇烽�夋嫨鑳芥簮鍝佺" style="width: 100%">
+                    <el-option v-for="dict in props.energyVarietiesList" :key="dict.enerclassid"
+                        :label="dict.enerclassname" :value="dict.enerclassid" />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="绛栫暐鍚嶇О" prop="tacticsId">
+                <el-select v-model="form.tacticsId" placeholder="璇烽�夋嫨绛栫暐鍚嶇О" style="width: 100%">
+                    <el-option v-for="dict in props.allUnitPriceStrategy" :key="dict.id" :label="dict.tacticsName"
+                        :value="dict.id" />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="鏈夋晥鍛ㄦ湡" prop="dataTime">
+                <el-date-picker v-model="form.dataTime" type="daterange" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
+                    placeholder="鏃堕棿" style="width: 260px" unlink-panels />
+            </el-form-item>
+            <el-form-item label="璇存槑" prop="remark">
+                <el-input v-model="form.remark" type="textarea" rows="4" placeholder="璇疯緭鍏ヨ鏄�" />
+            </el-form-item>
+            <div slot="footer" class="text-right">
+                <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+                <el-button @click="handleClose">鍙� 娑�</el-button>
+            </div>
+        </el-form>
+    </el-dialog>
+</template>
+<script setup>
+import { unitPriceCorrelationAdd, unitPriceCorrelationEdit } from '@/api/costManage/costManage.js'
+const { proxy } = getCurrentInstance();
+let props = defineProps(['energyVarietiesList', 'allUnitPriceStrategy'])
+
+let visible = ref(false)
+let title = ref()
+let formRules = {
+    energyType: [{ required: true, trigger: "change", message: "璇烽�夋嫨鑳芥簮鍝佺" }],
+    tacticsId: [{ required: true, trigger: "change", message: "璇烽�夋嫨绛栫暐鍚嶇О" }],
+    dataTime: [{ required: true, trigger: "change", message: "璇烽�夋嫨鏈夋晥鍛ㄦ湡" }],
+}
+let loading = ref(false)
+let form = ref({
+    nodeId: null,
+    nodeName: null,
+    energyType: null,
+    tacticsId: null,
+    dataTime: null,
+    remark: null,
+})
+function handleClose() {
+    visible.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        nodeId: null,
+        nodeName: null,
+        energyType: null,
+        tacticsId: null,
+        dataTime: null,
+        remark: null,
+    }
+}
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            let data = {
+                effectiveBeginTime: form.value.dataTime[0],
+                effectiveEndTime: form.value.dataTime[1],
+                ...form.value
+            }
+            let obj = form.value.id ? unitPriceCorrelationEdit(data) : unitPriceCorrelationAdd(data)
+            obj.then((res) => {
+                if (res.code == 200) {
+                    proxy.$modal.msgSuccess(res.msg);
+                    handleClose()
+                    emit('getList')
+                } else {
+                    proxy.$modal.msgError(res.msg);
+                }
+
+            }).catch((err) => {
+            })
+
+        }
+    })
+
+}
+function handleOpen(row, currentNode) {
+    if (row && row.id) {
+        form.value = JSON.parse(JSON.stringify(row))
+        form.value.dataTime = [form.value.effectiveBeginTime, form.value.effectiveEndTime]
+    }
+    title.value = "鍗曚环绛栫暐鍏宠仈"
+    visible.value = true
+    form.value.nodeId = currentNode.id
+    form.value.nodeName = currentNode.label
+}
+defineExpose({ handleOpen })
+let emit = defineEmits(['getList'])
+</script>
+
+<style lang='scss' scoped></style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/costmanage/unitpricecorrelation/unitpricecorrelation.vue b/zhitan-vue/src/views/costmanage/unitpricecorrelation/unitpricecorrelation.vue
new file mode 100644
index 0000000..b59e40e
--- /dev/null
+++ b/zhitan-vue/src/views/costmanage/unitpricecorrelation/unitpricecorrelation.vue
@@ -0,0 +1,177 @@
+<template>
+    <div class="page">
+        <div class="page-container">
+            <div class="page-container-left">
+                <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+            </div>
+            <div class="page-container-right">
+                <div class="form-card">
+                    <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+                        <el-form-item label="鑳芥簮鍝佺">
+                            <el-select v-model="form.energyType" placeholder="璇烽�夋嫨鑳芥簮鍝佺" style="width: 100%">
+                                <el-option v-for="dict in energyVarietiesList" :key="dict.enerclassid"
+                                    :label="dict.enerclassname" :value="dict.enerclassid" />
+                            </el-select>
+                        </el-form-item>
+                        <el-form-item>
+                            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                        </el-form-item>
+                    </el-form>
+                </div>
+                <div class="mt20 mb20 ml20">
+                    <el-button type="primary" icon="Plus" @click="handleAdd">鍏宠仈</el-button>
+                </div>
+                <div class="table-box">
+                    <el-table :data="tableData" v-loading="loading" height="calc(100vh - 450px)">
+                        <el-table-column type="index" label="搴忓彿" width="100" />
+                        <el-table-column label="鐢ㄨ兘鍗曚綅" prop="nodeId" align="center" show-overflow-tooltip
+                            :formatter="(row, column) => findLabelById(treeNode, row.nodeId)" />
+                        <!-- <el-table-column label="绛栫暐缂栫爜" prop="indexName" align="center" show-overflow-tooltip /> -->
+                        <el-table-column label="绛栫暐鍚嶇О" prop="tacticsId" align="center" show-overflow-tooltip
+                            :formatter="(row, column) => formatterTacticsLabel(allUnitPriceStrategy, row.tacticsId)" />
+                        <el-table-column label="鑳芥簮鍝佺" prop="energyType" align="center" show-overflow-tooltip
+                            :formatter="(row, column) => formatterLabel(energyVarietiesList, row.energyType)" />
+                        <!-- <el-table-column label="鏄惁闃舵浠锋牸" prop="alarmValue" align="center" show-overflow-tooltip /> -->
+                        <el-table-column label="鏈夋晥鏈熻捣濮�" prop="effectiveBeginTime" align="center" show-overflow-tooltip />
+                        <el-table-column label="鏈夋晥鏈熸埅姝�" prop="effectiveEndTime" align="center" show-overflow-tooltip />
+                        <el-table-column label="璇存槑" prop="remark" align="center" show-overflow-tooltip />
+                        <el-table-column label="鎿嶄綔" width="220" align="center">
+                            <template #default="scope">
+                                <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row, scope.$index)">
+                                    缂栬緫
+                                </el-button>
+                                <el-button link type="primary" icon="Delete"
+                                    @click="handleDel(scope.row, scope.$index)">
+                                    鍒犻櫎
+                                </el-button>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+                    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                        v-model:limit="queryParams.pageSize" @pagination="getList" />
+                </div>
+                <EditModal ref="editModalRef" :energyVarietiesList='energyVarietiesList'
+                    :allUnitPriceStrategy='allUnitPriceStrategy' @getList="getList" />
+            </div>
+        </div>
+    </div>
+</template>
+<script setup>
+import { unitPriceCorrelationList, unitPriceCorrelationDel, unitPriceStrategyAllList } from '@/api/costManage/costManage.js'
+const { proxy } = getCurrentInstance();
+import EditModal from './components/EditModal'
+import { listEnergyVarietiesList } from "@/api/modelConfiguration/energyVarieties";
+const energyVarietiesList = ref([]);
+function getEnergyVarietiesList() {
+    listEnergyVarietiesList().then((res) => {
+        energyVarietiesList.value = res.data;
+    });
+}
+getEnergyVarietiesList()
+
+let loading = ref(false);
+let tableData = ref([{}]);
+let editModalRef = ref()
+
+
+function handleAdd(row) {
+    if (editModalRef.value) {
+        editModalRef.value.handleOpen(row, currentNode.value)
+    }
+}
+
+
+function handleQuery() {
+    queryParams.value.pageNum = 1
+    getList()
+}
+function resetQuery() {
+    form.value = {
+        nodeId: currentNode.value.id
+    }
+    queryParams.value.pageNum = 1
+    getList()
+}
+
+let currentNode = ref()
+let treeNode = ref([])
+function handleNodeClick(data, nodeOptions) {
+    currentNode.value = data
+    treeNode.value = nodeOptions
+    form.value.nodeId = data.id
+    getList()
+}
+
+let form = ref({
+    energyType: null
+})
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+})
+let total = ref(0)
+//鑾峰彇鍒楄〃
+function getList() {
+    loading.value = true
+    unitPriceCorrelationList({ ...queryParams.value, ...form.value }).then(res => {
+        tableData.value = res.rows
+        total.value = res.total
+        loading.value = false
+    })
+}
+
+getList()
+function handleDel(row) {
+    proxy.$modal
+        .confirm('鏄惁纭鍒犻櫎璇ユ暟鎹」锛�')
+        .then(function () {
+            return unitPriceCorrelationDel(row.id);
+        })
+        .then(() => {
+            resetQuery();
+            proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        })
+        .catch(() => { });
+}
+function formatterLabel(list, value) {
+    let dict = list.find(item => item.enerclassid == value)
+    return dict ? dict.enerclassname : ''
+}
+function formatterTacticsLabel(list, value) {
+    let dict = list.find(item => item.id == value)
+    return dict ? dict.tacticsName : ''
+}
+
+
+let allUnitPriceStrategy = ref([])
+function getAllUnitPriceStrategy() {
+    unitPriceStrategyAllList().then(res => {
+        if (res.code == 200) {
+            allUnitPriceStrategy.value = res.data
+        }
+    })
+}
+
+getAllUnitPriceStrategy()
+
+// 閫掑綊鍑芥暟锛屾牴鎹甶d鏌ユ壘label
+function findLabelById(tree, targetId) {
+    for (const node of tree) {
+        if (node.id === targetId) {
+            return node.label;
+        }
+        if (node.children && node.children.length > 0) {
+            const result = findLabelById(node.children, targetId);
+            if (result) {
+                return result;
+            }
+        }
+    }
+    return null;
+}
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/costmanage/unitpricestrategy/components/EditModal.vue b/zhitan-vue/src/views/costmanage/unitpricestrategy/components/EditModal.vue
new file mode 100644
index 0000000..cb00a72
--- /dev/null
+++ b/zhitan-vue/src/views/costmanage/unitpricestrategy/components/EditModal.vue
@@ -0,0 +1,156 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="900" @close="handleClose">
+        <el-form :model="form" ref="queryRef">
+            <el-row>
+                <el-col :offset="1" :span="10">
+                    <el-form-item label="绛栫暐缂栫爜" prop="tacticsNumber" :rules="formRules.tacticsNumber">
+                        <el-input v-model="form.tacticsNumber" :disabled="flag" placeholder="璇疯緭鍏ョ瓥鐣ョ紪鐮�" />
+                    </el-form-item>
+                    <el-form-item label="鑳芥簮鍝佺" prop="energyType" :rules="formRules.energyType">
+                        <el-select v-model="form.energyType" placeholder="璇烽�夋嫨鑳芥簮鍝佺" style="width: 100%">
+                            <el-option v-for="dict in props.energyVarietiesList" :key="dict.enerclassid"
+                                :label="dict.enerclassname" :value="dict.enerclassid" />
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :offset="1" :span="10">
+                    <el-form-item label="绛栫暐鍚嶇О" prop="tacticsName" :rules="formRules.tacticsName">
+                        <el-input v-model="form.tacticsName" placeholder="璇疯緭鍏ョ瓥鐣ュ悕绉�" />
+                    </el-form-item>
+                    <el-form-item label="闃舵浠锋牸" prop="type" :rules="formRules.type">
+                        <el-radio-group v-model="form.type">
+                            <el-radio label="1">鏄�</el-radio>
+                            <el-radio label="0">鍚�</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+            <div class="mt20 mb20">
+                <!-- <el-button type="primary" icon="Plus" @click="handleAdd"
+                    :disabled="form.type == 0 ? true : false">鏂板</el-button> -->
+            </div>
+            <el-table :data="form.itemList" style="width: 90%;margin: 0 auto;">
+                <el-table-column prop="electricityType" label="鏃舵" align="center">
+                    <template #default="scope">
+                        <el-form-item :prop="`itemList[${scope.$index}].electricityType`"
+                            :rules="formRules.electricityType">
+                            <el-select v-model="scope.row.electricityType" placeholder="璇烽�夋嫨鏃舵" disabled
+                                style="width: 100%">
+                                <el-option v-for="dict in electricity_price" :key="dict.value" :label="dict.label"
+                                    :value="dict.value" />
+                            </el-select>
+                        </el-form-item>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="price" label="鍗曚环锛堝厓锛�" align="center">
+                    <template #default="scope">
+                        <el-form-item :prop="`itemList[${scope.$index}].price`" :rules="formRules.price">
+                            <el-input-number v-model="scope.row['price']" placeholder="璇疯緭鍏�" style="width: 100%"
+                                controls-position="right" step-strictly @input="collectDate"
+                                :disabled="form.type == 0 && scope.$index != 0 ? true : false" />
+                        </el-form-item>
+                    </template>
+                </el-table-column>
+                <!-- <el-table-column label="鎿嶄綔" width="100" align="center">
+                    <template #default="scope">
+                        <el-form-item>
+                            <el-button link type="primary" icon="Delete" @click="handleDel(scope.row, scope.$index)">
+                                鍒犻櫎
+                            </el-button>
+                        </el-form-item>
+                    </template>
+                </el-table-column> -->
+            </el-table>
+            <div slot="footer" class="text-right">
+                <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+                <el-button @click="handleClose">鍙� 娑�</el-button>
+            </div>
+        </el-form>
+    </el-dialog>
+</template>
+<script setup>
+import { unitPriceStrategyAdd, unitPriceStrategyEdit } from '@/api/costManage/costManage.js'
+const { proxy } = getCurrentInstance();
+let props = defineProps(['energyVarietiesList'])
+let { electricity_price } = proxy.useDict('electricity_price')
+let visible = ref(false)
+let title = ref()
+
+let formRules = {
+    tacticsNumber: [{ required: true, trigger: "change", message: "璇疯緭鍏ョ瓥鐣ョ紪鐮�" }],
+    energyType: [{ required: true, trigger: "change", message: "璇烽�夋嫨鑳芥簮鍝佺" }],
+    tacticsName: [{ required: true, trigger: "blur", message: "璇疯緭鍏ョ瓥鐣ュ悕绉�" }],
+    type: [{ required: true, trigger: "change", message: "璇烽�夋嫨闃舵浠锋牸" }],
+    price: [{ required: true, trigger: "change", message: "璇烽�夋嫨鍗曚环" }],
+}
+let loading = ref(false)
+let form = ref({
+    tacticsNumber: '',
+    energyType: '',
+    tacticsName: '',
+    type: '1',
+    itemList: []
+})
+function handleClose() {
+    visible.value = false
+    flag.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        tacticsNumber: '',
+        energyType: '',
+        tacticsName: '',
+        type: '1',
+        itemList: []
+    }
+}
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            let obj = form.value.id ? unitPriceStrategyEdit(form.value) : unitPriceStrategyAdd(form.value)
+            obj.then((res) => {
+                if (res.code == 200) {
+                    proxy.$modal.msgSuccess(res.msg);
+                    handleClose()
+                    emit('getList')
+                } else {
+                    proxy.$modal.msgError(res.msg);
+                }
+
+            }).catch((err) => {
+            })
+        }
+    })
+}
+
+let flag = ref(false)
+function handleOpen(row, tips) {
+    for (let item of electricity_price.value) {
+        form.value.itemList.push({ electricityType: item.value, price: 0 })
+    }
+    if (row && row.id) {
+        title.value = "鈥嬪崟浠风瓥鐣ョ紪杈�"
+        form.value = JSON.parse(JSON.stringify(row))
+        if (!tips) {
+            delete form.value.id
+        }
+        flag.value = tips
+    } else {
+        console.log(form.value)
+        title.value = "鍗曚环绛栫暐娣诲姞"
+        flag.value = false
+    }
+    visible.value = true
+}
+defineExpose({ handleOpen })
+function collectDate(e) {
+    console.log('form.value.itemList', form.value.itemList);
+    if (form.value.type == 0) {
+        form.value.itemList = form.value.itemList.map((item, key) => {
+            return { ...item, price: e }
+        })
+    }
+}
+let emit = defineEmits(['getList'])
+</script>
+
+<style lang='scss' scoped></style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/costmanage/unitpricestrategy/unitpricestrategy.vue b/zhitan-vue/src/views/costmanage/unitpricestrategy/unitpricestrategy.vue
new file mode 100644
index 0000000..0c2a4e3
--- /dev/null
+++ b/zhitan-vue/src/views/costmanage/unitpricestrategy/unitpricestrategy.vue
@@ -0,0 +1,147 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+                <!-- <el-form-item label="鏈夋晥鍛ㄦ湡">
+                    <el-date-picker v-model="form.index1" type="daterange" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
+                        placeholder="鏃堕棿" style="width: 260px" unlink-panels />
+                </el-form-item> -->
+                <el-form-item label="鑳芥簮鍝佺">
+                    <el-select v-model="form.energyType" placeholder="璇烽�夋嫨鑳芥簮鍝佺" style="width: 100%">
+                        <el-option v-for="dict in energyVarietiesList" :key="dict.enerclassid"
+                            :label="dict.enerclassname" :value="dict.enerclassid" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="绛栫暐鍚嶇О">
+                    <el-input v-model="form.tacticsName" placeholder="璇疯緭鍏ョ瓥鐣ュ悕绉�" />
+                </el-form-item>
+                <el-form-item label="绛栫暐缂栫爜">
+                    <el-input v-model="form.tacticsNumber" placeholder="璇疯緭鍏ョ瓥鐣ョ紪鐮�" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="mt20 mb20 ml20">
+            <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+        </div>
+        <div class="table-box">
+            <el-table :data="tableData" v-loading="loading" height="calc(100vh - 450px)">
+                <el-table-column type="index" label="搴忓彿" align="center" width="100" />
+                <el-table-column label="绛栫暐缂栫爜" prop="tacticsNumber" align="center" show-overflow-tooltip />
+                <el-table-column label="绛栫暐鍚嶇О" prop="tacticsName" align="center" show-overflow-tooltip />
+                <el-table-column label="鑳芥簮鍝佺" prop="energyType" align="center" show-overflow-tooltip
+                    :formatter="(row, column) => formatterLabel(energyVarietiesList, row.energyType)" />
+                <el-table-column label="鏄惁闃舵浠锋牸" prop="type" align="center" show-overflow-tooltip
+                    :formatter="(row, column) => row.type == 0 ? '鍚�' : '鏄�'" />
+                <el-table-column label="鎿嶄綔" width="220" align="center">
+                    <template #default="scope">
+                        <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row, scope.$index)">
+                            缂栬緫
+                        </el-button>
+                        <el-button link type="primary" icon="Delete" @click="handleDel(scope.row, scope.$index)">
+                            鍒犻櫎
+                        </el-button>
+                        <el-button link type="primary" icon="DocumentCopy" @click="handleCopy(scope.row, scope.$index)">
+                            澶嶅埗
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                v-model:limit="queryParams.pageSize" @pagination="getList" />
+        </div>
+        <EditModal ref="editModalRef" :energyVarietiesList='energyVarietiesList' @getList='getList' />
+    </div>
+</template>
+<script setup>
+import { unitPriceStrategyList, unitPriceStrategyDel } from '@/api/costManage/costManage.js'
+const { proxy } = getCurrentInstance();
+import EditModal from './components/EditModal'
+import { listEnergyVarietiesList } from "@/api/modelConfiguration/energyVarieties";
+const energyVarietiesList = ref([]);
+
+getEnergyVarietiesList()
+let loading = ref(false);
+let tableData = ref([{}]);
+let editModalRef = ref()
+let form = ref({
+    // index1: [proxy.dayjs().startOf('year').format("YYYY-MM-DD"), proxy.dayjs().endOf('year').format("YYYY-MM-DD")],
+    energyType: null,
+    tacticsName: null,
+    tacticsNumber: null
+})
+function getEnergyVarietiesList() {
+    listEnergyVarietiesList().then((res) => {
+        energyVarietiesList.value = res.data;
+        form.value.energyType = res.data[0].enerclassid
+
+    });
+}
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+})
+let total = ref(0)
+getList()
+//鑾峰彇鍒楄〃
+function getList() {
+    loading.value = true
+    unitPriceStrategyList({ ...queryParams.value, ...form.value }).then(res => {
+        tableData.value = res.rows
+        total.value = res.total
+        loading.value = false
+    })
+}
+function handleAdd(row) {
+    if (editModalRef.value) {
+        editModalRef.value.handleOpen(row, true)
+    }
+}
+function handleQuery() {
+    queryParams.value.pageNum = 1
+    getList()
+}
+function resetQuery() {
+    form.value = {
+        // index1: [proxy.dayjs().startOf('year').format("YYYY-MM-DD"), proxy.dayjs().endOf('year').format("YYYY-MM-DD")],
+        energyType: null,
+        tacticsName: null,
+        tacticsNumber: null
+    }
+    queryParams.value.pageNum = 1
+    getList()
+}
+
+function handleDel(row) {
+    proxy.$modal
+        .confirm('鏄惁纭鍒犻櫎鏃堕棿涓�"' + row.tacticsName + '"鐨勬暟鎹」锛�')
+        .then(function () {
+            return unitPriceStrategyDel(row.id);
+        })
+        .then(() => {
+            resetQuery();
+            proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        })
+        .catch(() => { });
+}
+
+function handleCopy(row) {
+    if (editModalRef.value) {
+        editModalRef.value.handleOpen(row, false)
+    }
+}
+
+function formatterLabel(list, value) {
+    let dict = list.find(item => item.enerclassid == value)
+    return dict ? dict.enerclassname : ''
+}
+
+
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/deepanalysis/deepAnalysis.vue b/zhitan-vue/src/views/deepanalysis/deepAnalysis.vue
new file mode 100644
index 0000000..11e5ea0
--- /dev/null
+++ b/zhitan-vue/src/views/deepanalysis/deepAnalysis.vue
@@ -0,0 +1,363 @@
+<template>
+  <div class="page">
+    <div class="form-card">
+      <el-form :model="form" ref="queryRef" :inline="true" label-width="80px">
+        <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+          <el-select v-model="form.energyType" placeholder="璇烽�夋嫨鑳芥簮绫诲瀷">
+            <el-option :label="item.enername" :value="item.enersno" v-for="item in energyTypeList"
+              :key="item.enersno" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="鏈熼棿" prop="timeType" >
+          <el-select v-model="form.timeType" placeholder="鏈熼棿" clearable style="width: 120px" @change="handleTimeType">
+            <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="鏃堕棿閫夋嫨" prop="dataTime">
+          <el-date-picker v-if="form.type == 1" v-model="form.dataTime" type="year" />
+          <el-date-picker v-else-if="form.type == 2" v-model="form.dataTime" type="month" format="YYYY-MM"
+            value-format="YYYY-MM" />
+          <el-date-picker v-else v-model="form.dataTime" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+          <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="card-list">
+      <div class="card-list-item" v-for="(item, index) in periodList" :key="index">
+        <div class="item-top">
+          <div class="icon" :style="{ backgroundImage: 'url(' + item.icon + ')' }"></div>
+          <div class="name">{{ item.title }}</div>
+        </div>
+        <div class="item-bottom">
+          <div class="bottom-box text-center" :style="{ color: item.color }">
+            {{ item.data }}
+          </div>
+        </div>
+      </div>
+    </div>
+    <BaseCard title="鑳芥簮娴佸悜鍒嗘瀽">
+      <div class="chart-box">
+        <div id="Chart1" />
+      </div>
+    </BaseCard>
+  </div>
+</template>
+<script setup>
+import useSettingsStore from '@/store/modules/settings'
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+const settingsStore = useSettingsStore()
+import * as echarts from 'echarts';
+let { proxy } = getCurrentInstance()
+import { onMounted } from 'vue';
+const { period } = proxy.useDict("period");
+let form = ref({
+  energyType: null,
+  timeType: null,
+  dataTime: null,
+})
+function handleTimeType(e) {
+  form.value.timeType = e;
+  form.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+const energyTypeList = ref();
+function getEnergyTypeList() {
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+    form.value.energyType = energyTypeList.value[0].enersno
+    handleTimeType(period.value[0].value);
+  });
+}
+getEnergyTypeList()
+
+function handleSelect(e) {
+  console.log(e, form.value.type)
+  if (form.value.type == 1) {
+    form.value.time = proxy.dayjs().format('YYYY')
+  } else if (form.value.type == 2) {
+    form.value.time = proxy.dayjs().format('YYYY-MM')
+  } else {
+    form.value.time = proxy.dayjs().format('YYYY-MM-DD')
+  }
+}
+function handleQuery() { }
+
+function resetQuery() {
+  form.value = {
+    type: 1,
+    time: proxy.dayjs().format('YYYY'),
+  }
+}
+
+import icon1 from '@/assets/images/period/icon1.png'
+import icon2 from '@/assets/images/period/icon2.png'
+import icon3 from '@/assets/images/period/icon3.png'
+import icon4 from '@/assets/images/period/icon4.png'
+import icon5 from '@/assets/images/period/icon5.png'
+const periodList = ref([
+  {
+    icon: icon1,
+    color: "#3371eb",
+    title: "绱鑳借��",
+    data: '1000.1 m鲁'
+  },
+  {
+    icon: icon3,
+    color: "#ff6200",
+    title: "鍒嗚〃鑳借��",
+    data: ' 0 m鲁'
+  },
+  {
+    icon: icon4,
+    color: "#ffce0c",
+    title: "鎹熷け閲�",
+    data: '1000.1 m鲁'
+  },
+  {
+    icon: icon5,
+    color: "#78e801",
+    title: "鎹熷け姣斾緥",
+    data: '100.00%'
+  },
+])
+
+watch(() => settingsStore.sideTheme, (val) => {
+  getChart()
+})
+
+function getChart() {
+
+
+  let colors = ['#FBB4AE', '#B3CDE3', '#CCEBC5', '#DECBE4', '#5470C6'];
+  let mydata = [
+    { name: 'L1', itemStyle: { color: colors[0] }, depth: 0 },
+    { name: 'L1-1', itemStyle: { color: colors[0] }, depth: 0 },
+    { name: 'L1-2', itemStyle: { color: colors[0] }, depth: 0 },
+    { name: 'L1-3', itemStyle: { color: colors[0] }, depth: 0 },
+
+
+    { name: 'L2', itemStyle: { color: colors[1] }, depth: 1 },
+    { name: 'L2-1', itemStyle: { color: colors[1] }, depth: 1 },
+    { name: 'L2-2', itemStyle: { color: colors[1] }, depth: 1 },
+    { name: 'L2-3', itemStyle: { color: colors[1] }, depth: 1 },
+
+
+    { name: 'L3', itemStyle: { color: colors[2] }, depth: 2 },
+    { name: 'L3-1', itemStyle: { color: colors[2] }, depth: 2 },
+    { name: 'L3-2', itemStyle: { color: colors[2] }, depth: 2 },
+
+
+    { name: 'L4', itemStyle: { color: colors[3] }, depth: 3 },
+
+
+
+  ];
+  // mydata.reverse()
+  let mylinks = [
+    // L1鈫扡4	 9720
+    { source: 'L1', target: 'L4', value: 9720 },
+    // L2鈫扡4	 24396
+    { source: 'L2', target: 'L4', value: 24396 },
+    // L3鈫扡4	 1462
+    { source: 'L3', target: 'L4', value: 1462 },
+
+    // L1鈫扡2鈫扡3鈫扡4	 215
+    { source: 'L1-1', target: 'L2-1', value: 215 },
+    { source: 'L2-1', target: 'L3-1', value: 215 },
+    { source: 'L3-1', target: 'L4', value: 215 },
+
+    // L1鈫扡2鈫扡4	 4518
+    { source: 'L1-2', target: 'L2-2', value: 4518 },
+    { source: 'L2-2', target: 'L4', value: 4518 },
+    // L1鈫扡3鈫扡4	 217
+    { source: 'L1-3', target: 'L3-2', value: 217 },
+    { source: 'L3-2', target: 'L4', value: 217 },
+
+    // L2鈫扡3鈫扡4	 893
+    { source: 'L2-3', target: 'L3-3', value: 893 },
+    { source: 'L3-3', target: 'L4', value: 893 },
+
+  ];
+
+
+  const myChart1 = echarts.init(document.getElementById("Chart1"));
+  myChart1.setOption({
+    tooltip: {
+      trigger: 'item',
+      triggerOn: 'mousemove',
+    },
+    series: {
+      type: 'sankey',
+      lineStyle: {
+        opacity: 0.3,
+        color: 'gradient',
+        curveness: 0.7,
+      },
+      // nodeAlign: 'left',
+      nodeGap: 18,
+      layoutIterations: 1,
+      emphasis: {
+        focus: 'adjacency',
+      },
+      data: mydata,
+      links: mylinks,
+    },
+  })
+  window.addEventListener("resize", () => {
+    myChart1.resize();
+  }, { passive: true });
+}
+onMounted(() => {
+  getChart()
+})
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+.themeDark {
+
+  .card-list {
+    display: flex;
+    justify-content: space-between;
+    padding: 18px;
+
+    .card-list-item {
+      width: 24%;
+      height: 167px;
+      background: #223386;
+      border-radius: 5px 5px 5px 5px;
+      border: 1px solid #4868b7;
+      background-size: 100% 100%;
+      box-sizing: border-box;
+      padding: 20px 18px 23px 16px;
+
+      .item-top {
+        display: flex;
+        align-items: center;
+
+        .icon {
+          width: 40px;
+          height: 40px;
+          background-size: 100% 100%;
+        }
+
+        .name {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: bold;
+          font-size: 20px;
+          color: #fffdfd;
+          margin-left: 7px;
+        }
+      }
+
+      .item-bottom {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 15px;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 16px;
+
+        .bottom-box {
+          width: 100%;
+          font-size: 25px;
+          color: rgb(51, 113, 235);
+        }
+
+        .bottom-left {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: 500;
+          font-size: 14px;
+          color: rgba(255, 255, 255, 0.7);
+        }
+
+        .bottom-right {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: 800;
+          font-size: 16px;
+          color: #f52528;
+        }
+      }
+    }
+  }
+}
+
+.themeLight {
+  .card-list {
+    display: flex;
+    justify-content: space-between;
+    padding: 18px;
+
+    .card-list-item {
+      width: 24%;
+      height: 167px;
+      background: #fff;
+      border-radius: 5px 5px 5px 5px;
+      border: 1px solid #E8E8E8;
+      background-size: 100% 100%;
+      box-sizing: border-box;
+      padding: 20px 18px 23px 16px;
+
+      .item-top {
+        display: flex;
+        align-items: center;
+
+        .icon {
+          width: 40px;
+          height: 40px;
+          background-size: 100% 100%;
+        }
+
+        .name {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: bold;
+          font-size: 20px;
+          color: #000;
+          margin-left: 7px;
+        }
+      }
+
+      .item-bottom {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 15px;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 16px;
+
+        .bottom-box {
+          width: 100%;
+          font-size: 25px;
+          color: rgb(51, 113, 235);
+        }
+
+        .bottom-left {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: 500;
+          font-size: 14px;
+          color: #5D5C5C;
+        }
+
+        .bottom-right {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: 800;
+          font-size: 16px;
+          color: #f52528;
+        }
+      }
+    }
+  }
+}
+
+.chart-box {
+  width: 100%;
+  height: calc(100vh - 500px) !important;
+
+  div {
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/energy/energy.vue b/zhitan-vue/src/views/energy/energy.vue
new file mode 100644
index 0000000..bf73684
--- /dev/null
+++ b/zhitan-vue/src/views/energy/energy.vue
@@ -0,0 +1,1555 @@
+<template>
+  <div class="page">
+    <HeaderCard :title="'鎭掗偊鑳芥簮鏁版嵁缁煎悎鍒嗘瀽'" />
+    <div class="content-box">
+      <div class="left-box">
+        <BaseCardBaseCard title="褰撴棩鑳借��">
+          <div class="day-box" v-loading="loadingLeftTop">
+            <div class="day-card-list" v-for="(item, index) in leftTopList" :key="index">
+              <div class="day-card-box">
+                <div class="day-img-box">
+                  <img :src="leftTopIcon[index]" />
+                </div>
+                <div class="day-font-box">
+                  <div class="day-num-unit-box">
+                    <span class="num">{{ item.count }}</span>
+                    <span class="unit">{{ item.energyUnit }}</span>
+                  </div>
+                  <div class="day-font">{{ item.energyName }}</div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </BaseCardBaseCard>
+        <BaseCardBaseCard title="鍚岀幆姣斿垎鏋�" :tabArray="period" @changeActive="getLeftCenterList">
+          <div class="left-analysis-box" v-loading="loadingLeftCenter">
+            <div class="analysis-top-content">
+              <div class="analysis-title-box">
+                <span class="analysis-num-span">
+                  {{ leftCenterList[0].count }}
+                </span>
+                <span class="analysis-unit-span">{{
+                  leftCenterList[0].energyUnit
+                }}</span>
+              </div>
+              <div class="analysis-img-box">
+                <img src="@/assets/images/energy/left/center/icon01.png" />
+              </div>
+              <div class="analysis-title-span">鏈湀褰撳墠鑰楃數閲�</div>
+            </div>
+            <div class="analysis-bottom-content">
+              <div class="analysis-left-box">
+                <div class="analysis-bottom-img-box">
+                  <img src="@/assets/images/energy/left/center/icon02.png" />
+                </div>
+                <div class="analysis-bottom-font-box">
+                  <div class="top-font">鍚屾湡鑰楃數閲�</div>
+                  <div class="bottom-font">
+                    <span class="num-span">222</span>
+                    <span class="unit-span">{{
+                      leftCenterList[0].energyUnit
+                    }}</span>
+                  </div>
+                </div>
+              </div>
+              <div class="analysis-right-box">
+                <div class="right-top-box">
+                  <div class="icon-box icon-box1"></div>
+                  <div class="font-box">鍚屾瘮澧為暱</div>
+                </div>
+                <div class="right-bottom-box">
+                  <div class="font-box green">
+                    {{ leftCenterList[0].tongbi }}%
+                  </div>
+                  <div class="icon-box icon-box2"></div>
+                </div>
+              </div>
+              <div class="analysis-right-box">
+                <div class="right-top-box">
+                  <div class="icon-box icon-box3"></div>
+                  <div class="font-box">鐜瘮澧為暱</div>
+                </div>
+                <div class="right-bottom-box">
+                  <div class="font-box orange">
+                    {{ leftCenterList[0].huanbi }}%
+                  </div>
+                  <div class="icon-box icon-box4"></div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </BaseCardBaseCard>
+        <BaseCardBaseCard title="鑰楃數鍗犳瘮">
+          <div class="left-analysis-box" v-loading="loadingLeftBottom">
+            <div id="leftBottom" style="width: 100%; height: 100%" />
+          </div>
+        </BaseCardBaseCard>
+      </div>
+      <div class="center-box">
+        <div class="center-image-box" v-loading="loadingCenterTopCenter">
+          <div class="image-box">
+            <div class="img-ul-box">
+              <div class="img-li-box" v-for="(item, index) in centerTopList" :key="index">
+                <div class="icon-ul-box">
+                  <img :src="item.icon" />
+                </div>
+                <div class="font-ul-box" :class="item.className">
+                  <div class="font-li-box1">{{ item.number }}</div>
+                  <div class="font-li-box2">{{ item.name }}</div>
+                </div>
+              </div>
+            </div>
+            <div class="absolute-box" :class="item.className" v-for="(item, index) in centerCenterList" :key="index">
+              <dl>
+                <dd>
+                  鍘傚尯鍚嶇О锛�<span class="blue"> {{ item.nodeName }}</span>
+                </dd>
+                <dd v-for="child in item.energyItemList">
+                  {{ child.energyName }}锛�<span class="blue">
+                    {{ child.count }}</span>
+                </dd>
+              </dl>
+            </div>
+          </div>
+        </div>
+        <BaseCardBaseCard title="鐢ㄧ數瓒嬪娍鍒嗘瀽" :tabArray="period" @changeActive="getCenterBottomList">
+          <div class="line-box" v-loading="loadingCenterBottom">
+            <div id="centerBottom" style="width: 100%; height: 100%" />
+          </div>
+        </BaseCardBaseCard>
+      </div>
+      <div class="right-box">
+        <BaseCardBaseCard title="鑳芥簮璐叆涓庢秷鑰楀姣�">
+          <div class="bar-box" v-loading="loadingRightTop">
+            <div id="rightTop" style="width: 100%; height: 100%" />
+          </div>
+        </BaseCardBaseCard>
+        <BaseCardBaseCard title="鑳借�楁帓鍚�">
+          <div class="rank-box" v-loading="loadingRightCenter">
+            <div class="rank-ul-box" v-for="(item, index) in rightCenterList" :key="i">
+              <div class="rank-li-box">
+                <div class="rank-icon-box" :style="{
+                  backgroundImage: 'url(' + rightCenterIcon[index] + ')',
+                  backgroundSize: '100% 100%',
+                }" />
+                <div class="rank-name-box">{{ item.nodeName }}</div>
+                <div class="rank-num-box">{{ item.energyConsumption }}</div>
+              </div>
+            </div>
+          </div>
+        </BaseCardBaseCard>
+        <BaseCardBaseCard title="鑳芥簮鎴愭湰鍗犳瘮">
+          <div class="pie-box" v-loading="loadingRightBottom">
+            <div id="rightBottom" style="width: 100%; height: 100%"></div>
+          </div>
+        </BaseCardBaseCard>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="energy">
+import {
+  listDay,
+  listEnergyConsumptionSummation,
+  listPeakValley,
+  listFactoryEnergyConsumption,
+  listSegmentAnalysisYear,
+  listSegmentAnalysisMonth,
+  listSegmentAnalysisDay,
+  listPurchaseConsumption,
+  listEnergyConsumptionRanking,
+  listCostProp,
+} from "@/api/energy/energy";
+import useAppStore from "@/store/modules/app";
+const appStore = useAppStore();
+appStore.toggleSideBarHide(true);
+import * as echarts from "echarts";
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+let loadingLeftTop = ref(false);
+let loadingLeftCenter = ref(false);
+let loadingLeftBottom = ref(false);
+let loadingCenterTopCenter = ref(false);
+let loadingCenterBottom = ref(false);
+let loadingRightTop = ref(false);
+let loadingRightCenter = ref(false);
+let loadingRightBottom = ref(false);
+import left_top_icon1 from "@/assets/images/energy/left/top/icon1.png";
+import left_top_icon2 from "@/assets/images/energy/left/top/icon2.png";
+import left_top_icon3 from "@/assets/images/energy/left/top/icon3.png";
+import left_top_icon4 from "@/assets/images/energy/left/top/icon4.png";
+let leftTopIcon = ref([
+  left_top_icon1,
+  left_top_icon2,
+  left_top_icon3,
+  left_top_icon4,
+]);
+let leftTopList = ref([]);
+import pieBg from "@/assets/images/energy/left/bottom/pie-bg.png";
+let leftCenterList = ref([
+  {
+    energyName: "鐢�",
+    energyUnit: "鍗冪摝",
+    count: 0,
+    tongbi: 0,
+    huanbi: 0,
+    coefficient: 0,
+    tonCount: 0,
+  },
+]);
+import centerIcon1 from "@/assets/images/energy/center/icon1.png";
+import centerIcon2 from "@/assets/images/energy/center/icon2.png";
+import centerIcon3 from "@/assets/images/energy/center/icon3.png";
+import centerIcon4 from "@/assets/images/energy/center/icon4.png";
+let centerTopList = ref([
+  {
+    name: "鏈湀缁煎悎鑳借��",
+    number: 0,
+    icon: centerIcon1,
+    className: "icon-font-box1",
+  },
+  {
+    name: "鏈湀鑳借�楄垂鐢�",
+    number: 0,
+    icon: centerIcon2,
+    className: "icon-font-box2",
+  },
+  {
+    name: "鏈湀纰虫帓鏀�",
+    number: 0,
+    icon: centerIcon3,
+    className: "icon-font-box3",
+  },
+  {
+    name: "鏈湀鐢ㄨ兘瀹屾垚鎸囨爣",
+    number: 0,
+    icon: centerIcon4,
+    className: "icon-font-box4",
+  },
+]);
+let centerCenterList = ref([]);
+import rankIcon01 from "@/assets/images/energy/right/center/icon1.png";
+import rankIcon02 from "@/assets/images/energy/right/center/icon2.png";
+import rankIcon03 from "@/assets/images/energy/right/center/icon3.png";
+import rankIcon04 from "@/assets/images/energy/right/center/icon4.png";
+import rankIcon05 from "@/assets/images/energy/right/center/icon5.png";
+import rankIcon06 from "@/assets/images/energy/right/center/icon6.png";
+import rankIcon07 from "@/assets/images/energy/right/center/icon7.png";
+import rankIcon08 from "@/assets/images/energy/right/center/icon8.png";
+import rankIcon09 from "@/assets/images/energy/right/center/icon9.png";
+import rankIcon10 from "@/assets/images/energy/right/center/icon10.png";
+let rightCenterIcon = ref([
+  rankIcon01,
+  rankIcon02,
+  rankIcon03,
+  rankIcon04,
+  rankIcon05,
+  rankIcon06,
+  rankIcon07,
+  rankIcon08,
+  rankIcon09,
+  rankIcon10,
+]);
+let rightCenterList = ref([]);
+function getLeftTopList() {
+  loadingLeftTop.value = true;
+  listDay().then((res) => {
+    loadingLeftTop.value = false;
+    if (!!res.code && res.code == 200) {
+      res.data.map((item, index) => {
+        item.icon = "left_top_icon" + (index + 1);
+      });
+      if (res.data.length < 4) {
+        for (let index = 0; index < 3; index++) {
+          res.data.push({
+            energyName: null,
+            count: 0,
+            energyUnit: null,
+          });
+        }
+      }
+      leftTopList.value = res.data;
+    }
+  });
+}
+function getLeftCenterList(e) {
+  loadingLeftCenter.value = true;
+  listEnergyConsumptionSummation({
+    timeType: e,
+  }).then((res) => {
+    loadingLeftCenter.value = false;
+    if (!!res.code && res.code == 200) {
+      leftCenterList.value = res.data;
+    }
+  });
+}
+function getLeftBottomList() {
+  loadingLeftBottom.value = true;
+  listPeakValley().then((res) => {
+    loadingLeftBottom.value = false;
+    if (!!res.code && res.code == 200) {
+      // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+      if (echarts.getInstanceByDom(document.getElementById("leftBottom"))) {
+        echarts.dispose(document.getElementById("leftBottom"));
+      }
+      const leftBottom = echarts.init(document.getElementById("leftBottom"));
+      let total = 0;
+      let seriesData = [];
+      if (!!res.data && res.data.length > 0) {
+        res.data.map((item) => {
+          total += Number(item.count);
+          seriesData.push({
+            name: item.timeName,
+            value: Number(item.count).toFixed(2),
+          });
+        });
+      }
+      setTimeout(() => {
+        leftBottom.setOption({
+          color: ["#02FF7F", "#FF7723", "#8E30FF", "#0ff1ff"],
+          grid: {
+            top: "20%",
+            left: "15%",
+            right: "5%",
+            bottom: "100",
+            containLabel: true,
+          },
+          tooltip: {
+            trigger: "item",
+          },
+          graphic: [
+            {
+              type: "image", //娣诲姞鍥剧墖
+              style: {
+                //璁剧疆鍥剧墖鏍峰紡
+                image: pieBg,
+                width: 150,
+                height: 150,
+              },
+              left: "center",
+              bottom: "center",
+            },
+          ],
+          series: [
+            {
+              name: "鑰楃數鍗犳瘮",
+              type: "pie",
+              center: ["50%", "50%"],
+              radius: ["40", "55"],
+              avoidLabelOverlap: false,
+              label: {
+                fontFamily: "Microsoft YaHei",
+                fontWeight: 400,
+                formatter: "{c|{c}kw} {d|{d}%}\n{b|{b}}",
+                rich: {
+                  b: {
+                    fontSize: 10,
+                    color: "#FFFFFF",
+                    opacity: "0.8",
+                  },
+                  c: {
+                    color: "auto",
+                    fontSize: 13,
+                  },
+                  d: {
+                    color: "auto",
+                    fontSize: 13,
+                  },
+                },
+              },
+              labelLine: {
+                show: true,
+                lineStyle: {
+                  color: "#fff",
+                },
+                length: 20,
+                length2: 10,
+              },
+              data: seriesData,
+            },
+          ],
+        });
+      }, 100);
+      window.addEventListener(
+        "resize",
+        () => {
+          leftBottom.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+}
+function getCenterTopList() {
+  loadingCenterTopCenter.value = true;
+  listFactoryEnergyConsumption().then((res) => {
+    loadingCenterTopCenter.value = false;
+    if (!!res.code && res.code == 200) {
+      if (!!res.data) {
+        centerTopList.value[0].number = !!res.data.monthConsumption
+          ? res.data.monthConsumption
+          : 0;
+        centerTopList.value[1].number = !!res.data.monthEnergyAmount
+          ? res.data.monthEnergyAmount
+          : 0;
+        centerTopList.value[2].number = !!res.data.cemission
+          ? res.data.cemission
+          : 0;
+        centerTopList.value[3].number = !!res.data.useEnergyIndex
+          ? res.data.useEnergyIndex
+          : 0;
+        if (!!res.data.itemVoList) {
+          if (res.data.itemVoList.length < 5) {
+            for (let index = 0; index < 5; index++) {
+              res.data.itemVoList.push({ nodeName: "鏆傛棤鏁版嵁" });
+            }
+            res.data.itemVoList.map((item, index) => {
+              item.className = "absolute" + (index + 1);
+            });
+          }
+        }
+        centerCenterList.value = !!res.data.itemVoList
+          ? res.data.itemVoList
+          : [];
+      }
+    }
+  });
+}
+function getCenterBottomList(e) {
+  loadingCenterBottom.value = true;
+  let xdata = [];
+  let series = [
+    {
+      name: "灏�",
+      type: "bar",
+      stack: "total",
+      barWidth: "18",
+      data: [],
+    },
+    {
+      name: "宄�",
+      type: "bar",
+      stack: "total",
+      barWidth: "18",
+      data: [],
+    },
+    {
+      name: "骞�",
+      type: "bar",
+      stack: "total",
+      barWidth: "18",
+      data: [],
+    },
+    {
+      name: "璋�",
+      type: "bar",
+      stack: "total",
+      barWidth: "18",
+      data: [],
+    },
+  ];
+  if (e == "DAY") {
+    listSegmentAnalysisDay().then((res) => {
+      loadingCenterBottom.value = false;
+      if (!!res.code && res.code == 200) {
+        if (!!res.data.lineChat) {
+          res.data.lineChat.map((item) => {
+            xdata.push(proxy.dayjs(item.xdata).format("HH鏃�"));
+            series[0].data.push(!!item.ytip ? item.ytip : 0);
+            series[1].data.push(!!item.ypeak ? item.ypeak : 0);
+            series[2].data.push(!!item.yflat ? item.yflat : 0);
+            series[3].data.push(!!item.ytrough ? item.ytrough : 0);
+          });
+          getCenterBottomListEchsrts(xdata, series);
+        }
+      }
+    });
+  } else if (e == "MONTH") {
+    listSegmentAnalysisMonth().then((res) => {
+      loadingCenterBottom.value = false;
+      if (!!res.code && res.code == 200) {
+        if (!!res.data.costList) {
+          res.data.costList.map((item) => {
+            xdata.push(proxy.dayjs(item.xdata).format("DD鏃�"));
+            series[0].data.push(!!item.ytip ? item.ytip : 0);
+            series[1].data.push(!!item.ypeak ? item.ypeak : 0);
+            series[2].data.push(!!item.yflat ? item.yflat : 0);
+            series[3].data.push(!!item.ytrough ? item.ytrough : 0);
+          });
+          getCenterBottomListEchsrts(xdata, series);
+        }
+      }
+    });
+  } else {
+    listSegmentAnalysisYear().then((res) => {
+      loadingCenterBottom.value = false;
+      if (!!res.code && res.code == 200) {
+        if (!!res.data.costList) {
+          res.data.costList.map((item) => {
+            xdata.push(proxy.dayjs(item.xdata).format("MM鏈�"));
+            series[0].data.push(!!item.ytip ? item.ytip : 0);
+            series[1].data.push(!!item.ypeak ? item.ypeak : 0);
+            series[2].data.push(!!item.yflat ? item.yflat : 0);
+            series[3].data.push(!!item.ytrough ? item.ytrough : 0);
+          });
+          getCenterBottomListEchsrts(xdata, series);
+        }
+      }
+    });
+  }
+}
+function getCenterBottomListEchsrts(xdata, series) {
+  console.log(xdata, series);
+  // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+  if (echarts.getInstanceByDom(document.getElementById("centerBottom"))) {
+    echarts.dispose(document.getElementById("centerBottom"));
+  }
+  const centerBottom = echarts.init(document.getElementById("centerBottom"));
+  setTimeout(() => {
+    centerBottom.setOption({
+      color: ["#02FF7F", "#D48144", "#8E30FF", "#00B5EC"],
+      tooltip: {
+        trigger: "axis",
+        axisPointer: {
+          type: "shadow",
+        },
+      },
+      legend: {
+        top: 5,
+        left: "5%",
+        // icon: "circle",
+        // itemWidth: 14,
+        itemHeight: 10,
+        textStyle: {
+          color: "#CCE9FF",
+          fontSize: 10,
+        },
+      },
+      grid: {
+        top: "55",
+        left: "45",
+        right: "45",
+        bottom: "20",
+        containLabel: true,
+      },
+      xAxis: {
+        type: "category",
+        axisPointer: {
+          type: "shadow",
+        },
+        axisLine: {
+          show: true,
+          lineStyle: {
+            color: "#0F4884",
+          },
+        },
+        axisTick: {
+          show: false,
+        },
+        splitArea: {
+          show: false,
+        },
+        splitLine: {
+          show: false,
+        },
+        axisLabel: {
+          color: "#0E87F9",
+          fontSize: 12,
+          padding: [5, 0, 0, 0],
+          //   formatter: '{value} ml'
+        },
+        data: xdata,
+      },
+      yAxis: [
+        {
+          type: "value",
+          name: "kw",
+          nameTextStyle: {
+            color: "#0E87F9",
+            fontSize: 12,
+            padding: [10, 0, 0, 0],
+          },
+          axisLine: {
+            show: false,
+          },
+          splitLine: {
+            show: true,
+            lineStyle: {
+              type: "solid",
+              color: "rgba(80, 254, 193,0.2)",
+            },
+          },
+          axisTick: {
+            show: false,
+          },
+          splitArea: {
+            show: false,
+          },
+          axisLabel: {
+            color: "#0E87F9",
+            fontSize: 12,
+          },
+        },
+      ],
+      series,
+    });
+  }, 100);
+  window.addEventListener(
+    "resize",
+    () => {
+      centerBottom.resize();
+    },
+    { passive: true }
+  );
+}
+function getRightTopList() {
+  loadingRightTop.value = true;
+  listPurchaseConsumption().then((res) => {
+    loadingRightTop.value = false;
+    if (!!res.code && res.code == 200) {
+      // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+      if (echarts.getInstanceByDom(document.getElementById("rightTop"))) {
+        echarts.dispose(document.getElementById("rightTop"));
+      }
+      const rightTop = echarts.init(document.getElementById("rightTop"));
+      let xdata = [];
+      let purchaseSum = [];
+      let consumptionSum = [];
+      if (!!res.data && res.data.length > 0) {
+        res.data.map((item) => {
+          xdata.push(item.energyName);
+          purchaseSum.push(item.purchaseSum);
+          consumptionSum.push(item.consumptionSum);
+        });
+      }
+      setTimeout(() => {
+        rightTop.setOption({
+          color: ["#008FF2", "#F2C200"],
+          tooltip: {
+            trigger: "item",
+          },
+          legend: {
+            padding: [10, 0, 0, 0],
+            icon: "rect",
+            itemWidth: 20,
+            itemGap: 40,
+            itemHeight: 7,
+            align: "right",
+            textStyle: {
+              color: "#CCE9FF",
+              fontSize: 10,
+            },
+          },
+          grid: [
+            // 宸︿晶杈规
+            {
+              show: false,
+              left: "7%",
+              top: "30",
+              bottom: "45",
+              containLabel: false,
+              width: "37%",
+            },
+            // 涓棿鏂囧瓧杈规
+            {
+              show: false,
+              left: "52%",
+              top: "30",
+              bottom: "45",
+              containLabel: false,
+              width: "26%",
+            },
+            // 鍙充晶杈规
+            {
+              show: false,
+              right: "7%",
+              top: "30",
+              bottom: "45",
+              containLabel: false,
+              width: "37%",
+            },
+          ],
+          xAxis: [
+            {
+              gridIndex: 0,
+              type: "value",
+              triggerEvent: true,
+              // 缈昏浆
+              name: "鍗曚綅锛氬崈鍏�",
+              nameLocation: "middle",
+              nameTextStyle: {
+                fontSize: 10,
+                padding: [10, 100, 0, 0],
+                verticalAlign: "top",
+                color: "#B5C6ED",
+              },
+              inverse: true,
+              axisLine: {
+                show: false,
+                lineStyle: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#CCE9FF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              position: "bottom",
+              axisLabel: {
+                show: true,
+                textStyle: {
+                  color: "#B5C6ED",
+                  fontWeight: "bold",
+                  fontSize: 10,
+                },
+              },
+              splitLine: {
+                show: false,
+              },
+            },
+            {
+              gridIndex: 1,
+              show: false,
+            },
+            {
+              gridIndex: 2,
+              triggerEvent: true,
+              type: "value",
+              name: "鍗曚綅锛氬崈鍏�",
+              nameLocation: "top",
+              nameTextStyle: {
+                fontSize: 10,
+                padding: [25, 0, 0, 25],
+                verticalAlign: "top",
+                color: "#B5C6ED",
+              },
+              axisLine: {
+                show: false,
+                lineStyle: {
+                  color: "#CCE9FF",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              position: "bottom",
+              axisLabel: {
+                textStyle: {
+                  color: "#B5C6ED",
+                  fontWeight: "bold",
+                  fontSize: 10,
+                },
+              },
+              splitLine: {
+                show: false,
+              },
+            },
+          ],
+          yAxis: [
+            {
+              gridIndex: 0,
+              type: "category",
+              inverse: true,
+              position: "right",
+              axisLine: {
+                show: false,
+                lineStyle: {
+                  width: 1,
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#CCE9FF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+
+              axisLabel: {
+                show: false,
+                textStyle: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#CCE9FF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+              },
+              data: xdata,
+            },
+            {
+              gridIndex: 1,
+              type: "category",
+              inverse: true,
+              position: "left",
+              axisLine: {
+                show: false,
+              },
+              axisTick: {
+                show: false,
+              },
+              axisLabel: {
+                show: true,
+                textStyle: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#CCE9FF"
+                      : "#222222",
+                  fontSize: 10,
+                },
+              },
+              data: xdata.map((value) => {
+                return {
+                  value: value,
+                  textStyle: {
+                    color: "#CCE9FF",
+                    align: "center",
+                    top: "center",
+                  },
+                };
+              }),
+            },
+            {
+              gridIndex: 2,
+              type: "category",
+              inverse: true,
+              position: "left",
+              axisLine: {
+                show: false,
+                lineStyle: {
+                  width: 1,
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#CCE9FF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              axisLabel: {
+                show: false,
+                textStyle: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#CCE9FF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+              },
+              data: xdata,
+            },
+          ],
+          series: [
+            // 宸﹁竟
+            {
+              name: "璐叆",
+              type: "bar",
+              xAxisIndex: 0,
+              yAxisIndex: 0,
+              barWidth: 6,
+              // 宸︿晶鏁版嵁
+              data: purchaseSum,
+              label: {
+                show: true,
+                position: "left",
+                textStyle: {
+                  color: "#00F6FF",
+                  fontSize: 9,
+                },
+              },
+              itemStyle: {
+                emphasis: {
+                  barBorderRadius: 7,
+                },
+                normal: {
+                  color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
+                    { offset: 0, color: "#008EF2" },
+                    { offset: 1, color: "#63B4ED" },
+                  ]),
+                },
+              },
+            },
+            // 鍙宠竟
+            {
+              name: "娑堣��",
+              type: "bar",
+              xAxisIndex: 2,
+              yAxisIndex: 2,
+              barWidth: 6,
+              label: {
+                show: true,
+                position: "right",
+                textStyle: {
+                  color: "#FFF600",
+                  fontSize: 9,
+                },
+              },
+              // 宸︿晶鏁版嵁
+              data: consumptionSum,
+              itemStyle: {
+                emphasis: {
+                  barBorderRadius: 7,
+                },
+                normal: {
+                  color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
+                    { offset: 0, color: "#F3F69E" },
+                    { offset: 1, color: "#F2C200" },
+                  ]),
+                },
+              },
+            },
+          ],
+        });
+      }, 100);
+      window.addEventListener(
+        "resize",
+        () => {
+          rightTop.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+}
+function getRightCenterList() {
+  loadingRightCenter.value = true;
+  listEnergyConsumptionRanking().then((res) => {
+    loadingRightCenter.value = false;
+    if (!!res.code && res.code == 200) {
+      rightCenterList.value =
+        res.data.length > 0
+          ? res.data
+          : [
+            { nodeName: "鏆傛棤鏁版嵁", energyConsumption: 0 },
+            { nodeName: "鏆傛棤鏁版嵁", energyConsumption: 0 },
+            { nodeName: "鏆傛棤鏁版嵁", energyConsumption: 0 },
+            { nodeName: "鏆傛棤鏁版嵁", energyConsumption: 0 },
+            { nodeName: "鏆傛棤鏁版嵁", energyConsumption: 0 },
+            { nodeName: "鏆傛棤鏁版嵁", energyConsumption: 0 },
+            { nodeName: "鏆傛棤鏁版嵁", energyConsumption: 0 },
+            { nodeName: "鏆傛棤鏁版嵁", energyConsumption: 0 },
+            { nodeName: "鏆傛棤鏁版嵁", energyConsumption: 0 },
+            { nodeName: "鏆傛棤鏁版嵁", energyConsumption: 0 },
+          ];
+      console.log(rightCenterList.value, res.data);
+    }
+  });
+}
+function getRightBottomList() {
+  loadingRightBottom.value = true;
+  listCostProp().then((res) => {
+    loadingRightBottom.value = false;
+    if (!!res.code && res.code == 200) {
+      // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+      if (echarts.getInstanceByDom(document.getElementById("rightBottom"))) {
+        echarts.dispose(document.getElementById("rightBottom"));
+      }
+      const rightBottom = echarts.init(document.getElementById("rightBottom"));
+      let total = 0;
+      let seriesData = [];
+      if (!!res.data && res.data.length > 0) {
+        res.data.map((item) => {
+          total += Number(item.count);
+          seriesData.push({
+            name: item.energyName,
+            value: Number(item.consumptionSum).toFixed(2),
+          });
+        });
+      }
+      setTimeout(() => {
+        rightBottom.setOption({
+          color: ["#00d1d4", "#215dd4", "#d7c933", "#9955ff"],
+          grid: {
+            top: "20%",
+            left: "15%",
+            right: "5%",
+            bottom: "0%",
+            containLabel: true,
+          },
+          tooltip: {
+            trigger: "item",
+          },
+          legend: {
+            orient: "vertical",
+            top: "center",
+            icon: `path://M881.387 297.813c38.08 65.387 57.28 136.747 57.28 214.187s-19.094 148.8-57.28 214.187c-38.187 65.28-89.92 117.12-155.2 155.2S589.44 938.667 512 938.667s-148.8-19.094-214.187-57.28c-65.28-38.08-117.013-89.814-155.306-155.307C104.427 660.8 85.333 589.44 85.333 512c0-77.333 19.094-148.693 57.28-214.187 38.08-65.28 89.814-117.013 155.307-155.306C363.2 104.533 434.56 85.333 512 85.333c77.333 0 148.693 19.094 214.187 57.28 65.28 38.187 117.013 89.92 155.2 155.2z m-217.707-47.36C617.387 223.467 566.827 209.92 512 209.92s-105.387 13.547-151.68 40.533-82.987 63.68-109.973 109.974c-26.987 46.293-40.534 96.853-40.534 151.68s13.547 105.386 40.534 151.68c26.986 46.293 63.68 82.986 109.973 109.973 46.293 26.987 96.853 40.533 151.68 40.533s105.387-13.546 151.68-40.533c46.293-26.987 82.987-63.68 109.973-109.973 26.987-46.294 40.534-96.854 40.534-151.68s-13.547-105.387-40.534-151.68c-27.093-46.294-63.786-82.987-109.973-109.974z`,
+            right: "27",
+            itemWidth: 14,
+            itemHeight: 14,
+            itemGap: 20,
+            textStyle: {
+              align: "left",
+              verticalAlign: "middle",
+              color: "auto",
+              fontSize: 10,
+              fontFamily: "DIN",
+              fontWeight: "bold",
+            },
+            data: seriesData,
+            formatter: (name) => {
+              let value, percent;
+              for (let i = 0; i < seriesData.length; i++) {
+                if (seriesData[i].name === name) {
+                  value = seriesData[i].value;
+                  percent =
+                    total != 0 && value != 0
+                      ? ((value / total) * 100).toFixed(2)
+                      : 0;
+                }
+              }
+              return `${name}  ${value}涓�  ${percent}%`;
+            },
+          },
+          series: [
+            {
+              name: "鑳芥簮鎴愭湰鍗犳瘮",
+              type: "pie",
+              center: ["35%", "50%"],
+              radius: ["30%", "70%"],
+              avoidLabelOverlap: false,
+              roseType: "radius",
+              label: false,
+              data: seriesData,
+            },
+          ],
+        });
+      }, 100);
+      window.addEventListener(
+        "resize",
+        () => {
+          rightBottom.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+}
+onMounted(() => {
+  getLeftTopList();
+  getLeftBottomList();
+  getCenterTopList();
+  getRightTopList();
+  getRightCenterList();
+  getRightBottomList();
+});
+</script>
+<style scoped lang="scss">
+.page {
+  height: 100vh;
+  width: 100vw;
+  background: url("@/assets/images/energy/energy_bg.png") no-repeat;
+  background-size: cover;
+  background-position: center bottom;
+
+  .content-box {
+    margin: 0;
+    position: absolute;
+    top: 8.7037vh;
+    left: 0;
+    z-index: 1;
+    display: flex;
+
+    .left-box {
+      margin-left: 2.0834vw;
+      width: 21.875vw;
+
+      .day-box {
+        height: 23.7963vh;
+        padding-top: 1.6667vh;
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+        flex-wrap: wrap;
+        margin-left: 1.0938vw;
+
+        .day-card-list {
+          width: 50%;
+          color: #fff;
+          margin-bottom: 2.963vh;
+
+          .day-card-box {
+            display: flex;
+            align-items: center;
+
+            .day-img-box {
+              width: 4.375vw;
+              height: 8.1481vh;
+
+              img {
+                width: 100%;
+                height: 100%;
+              }
+            }
+
+            .day-font-box {
+              margin-left: 0.6771vw;
+
+              .day-num-unit-box {
+                font-family: OPPOSans;
+                font-weight: bold;
+                color: #ffb400;
+                margin-bottom: 1vh;
+
+                .num {
+                  font-size: 1.1979vw;
+                }
+
+                .unit {
+                  font-size: 0.6771vw;
+                }
+              }
+
+              .day-font {
+                font-family: Source Han Sans CN;
+                font-size: 0.7292vw;
+                color: #8fcfee;
+              }
+            }
+          }
+        }
+      }
+
+      .left-analysis-box {
+        width: 100%;
+        height: 23.7037vh; //420*256
+
+        .analysis-top-content {
+          text-align: center;
+          margin: 0 auto; //29px  33px
+          padding-top: 2.6852vh;
+          height: 16.9444vh;
+          box-sizing: border-box;
+
+          .analysis-title-box {
+            font-family: DINPro;
+            color: #ffb400;
+            margin-bottom: 0.8333vh;
+            font-style: italic;
+
+            .analysis-num-span {
+              font-weight: bold;
+              font-size: 1.1458vw; // 22px;
+            }
+
+            .analysis-unit-span {
+              font-size: 0.8854vw; // 17px;}
+            }
+          }
+
+          .analysis-img-box {
+            width: 5.9896vw; //115* 83
+            height: 7.6852vh;
+            margin: 0 auto;
+
+            img {
+              height: 100%;
+            }
+          }
+
+          .analysis-title-span {
+            font-family: Source Han Sans CN;
+            font-weight: 500;
+            font-size: 0.625vw; //12px;
+            color: #c3a873;
+            letter-spacing: 1px;
+            font-weight: bold;
+          }
+        }
+
+        .analysis-bottom-content {
+          display: flex;
+          justify-content: space-around;
+          align-content: center;
+          height: 5.0926vh;
+
+          .analysis-left-box {
+            display: flex;
+
+            .analysis-bottom-img-box {
+              width: 3.9063vw; //75
+              height: 5.0926vh; //55
+              margin-right: 0.5208vw; //10
+
+              img {
+                // height: 100%;
+                width: 100%;
+              }
+            }
+
+            .analysis-bottom-font-box {
+              font-weight: bolder;
+              padding-top: 0.8333vh;
+
+              .top-font {
+                // margin-bottom: 0.8333vh;
+                font-size: 0.625vw; //12px;
+                color: #02d9fd;
+              }
+
+              .bottom-font {
+                color: #ffffff;
+                font-family: DIN;
+
+                .num-span {
+                  font-size: 1.0938vw; //21px;}
+                }
+
+                .unit-span {
+                  font-size: 0.7292vw; //14px
+                }
+              }
+            }
+          }
+
+          .analysis-right-box {
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            padding-top: 0.8333vh;
+
+            .right-top-box {
+              margin-bottom: 0.7407vh;
+              display: flex;
+              justify-content: center;
+
+              .icon-box {
+                width: 0.625vw;
+                height: 1.1111vh;
+                margin-right: 0.7407vh;
+                min-width: 12px;
+                min-height: 12px;
+
+                img {
+                  height: 100%;
+                  width: 100%;
+                }
+              }
+
+              .font-box {
+                font-family: Source Han Sans SC;
+                font-size: 0.5208vw; // 10px;
+                color: #527fb5;
+              }
+            }
+
+            .right-bottom-box {
+              display: flex;
+              justify-content: center;
+              align-items: center;
+
+              .font-box {
+                font-family: Arial;
+                font-size: 0.6771vw; // 13px;
+              }
+
+              .green {
+                color: #23c222;
+              }
+
+              .orange {
+                color: #f8813d;
+              }
+
+              .icon-box {
+                width: 0.3125vw;
+                height: 0.7407vh;
+                margin-left: 0.8333vh;
+
+                img {
+                  height: 100%;
+                  width: 100%;
+                }
+              }
+            }
+
+            .icon-box1 {
+              background: url("@/assets/images/energy/left/center/icon03.png") no-repeat;
+              background-size: 100% 100%;
+              background-position: center;
+            }
+
+            .icon-box2 {
+              background: url("@/assets/images/energy/left/center/icon05.png") no-repeat;
+              background-size: 100% 100%;
+              background-position: center;
+            }
+
+            .icon-box3 {
+              background: url("@/assets/images/energy/left/center/icon04.png") no-repeat;
+              background-size: 100% 100%;
+              background-position: center;
+            }
+
+            .icon-box4 {
+              background: url("@/assets/images/energy/left/center/icon06.png") no-repeat;
+              background-size: 100% 100%;
+              background-position: center;
+            }
+          }
+        }
+      }
+    }
+
+    .center-box {
+      margin: 0 0.8333vw;
+      width: 50.2083vw;
+
+      .center-image-box {
+        width: 100%;
+        height: 56.5741vh; //56.2037vh;
+        border: 1px solid;
+        border-image: linear-gradient(0deg, #0a3c86, #000) 1;
+        background: linear-gradient(0deg,
+            rgba(18, 111, 216, 0.2) 0%,
+            rgba(18, 111, 216, 0) 100%);
+        border-top: 0;
+        margin-bottom: 1.2963vh;
+
+        .image-box {
+          height: 100%;
+          padding-top: 1.8519vh;
+          background: url("@/assets/images/energy/center/bg1.png");
+          background-size: cover;
+          background-position: center;
+          box-sizing: border-box;
+          position: relative;
+
+          .img-ul-box {
+            margin: 0 2.1354vw 0 3.8021vw;
+            display: flex;
+            align-items: center;
+            justify-content: space-around;
+
+            .img-li-box {
+              display: flex;
+              align-items: center;
+
+              .icon-ul-box {
+                width: 3.6458vw;
+                height: 3.6458vw;
+
+                img {
+                  width: 100%;
+                  height: 100%;
+                }
+              }
+
+              .font-ul-box {
+                margin-left: 0.3125vw;
+
+                .font-li-box1 {
+                  font-family: OPPOSans;
+                  font-weight: 800;
+                  font-size: 1.3542vw; //26px;
+                }
+
+                .font-li-box2 {
+                  font-family: Source Han Sans CN;
+                  font-weight: 400;
+                  font-size: 0.6771vw; //13px;
+                }
+              }
+
+              .icon-font-box1 {
+                .font-li-box1 {
+                  background: linear-gradient(0deg, #1bc0fd 0%, #1eeaf1 100%);
+                  -webkit-background-clip: text;
+                  -webkit-text-fill-color: transparent;
+                }
+
+                .font-li-box2 {
+                  color: #8be7ff;
+                }
+              }
+
+              .icon-font-box2 {
+                .font-li-box1 {
+                  background: linear-gradient(0deg, #f5ab0c 0%, #ffe056 100%);
+                  -webkit-background-clip: text;
+                  -webkit-text-fill-color: transparent;
+                }
+
+                .font-li-box2 {
+                  color: #feea92;
+                }
+              }
+
+              .icon-font-box3 {
+                .font-li-box1 {
+                  background: linear-gradient(0deg, #8d1bf7 0%, #bd76ff 100%);
+                  -webkit-background-clip: text;
+                  -webkit-text-fill-color: transparent;
+                }
+
+                .font-li-box2 {
+                  color: #cd9cff;
+                }
+              }
+
+              .icon-font-box4 {
+                .font-li-box1 {
+                  background: linear-gradient(0deg, #0ccb42 0%, #2bfda6 100%);
+                  -webkit-background-clip: text;
+                  -webkit-text-fill-color: transparent;
+                }
+
+                .font-li-box2 {
+                  color: #96efc3;
+                }
+              }
+            }
+          }
+
+          .absolute-box {
+            width: 8.4896vw; //163* 189
+            height: 14.9074vh;
+            background-image: url("@/assets/images/energy/center/bg2.png");
+            background-size: 100% 100%;
+            background-repeat: no-repeat;
+            background-position: center;
+
+            dl {
+              // padding: 0.4630vh 0;
+              height: 10.5556vh;
+              display: flex;
+              color: #bedaee;
+              font-size: 0.6771vw;
+              flex-direction: column;
+              align-items: center;
+              justify-content: center;
+            }
+
+            .green {
+              color: #23c222;
+            }
+
+            .orange {
+              color: #ff910f;
+            }
+
+            .red {
+              color: #ff0f20;
+            }
+
+            .blue {
+              color: #0ff1ff;
+            }
+          }
+
+          .absolute1 {
+            position: absolute;
+            left: 1.5104vw;
+            bottom: 26.7593vh;
+          }
+
+          .absolute2 {
+            position: absolute;
+            left: 1.5104vw;
+            bottom: 9.4444vh;
+          }
+
+          .absolute3 {
+            position: absolute;
+            left: 13.8542vw;
+            bottom: 1.8519vh;
+          }
+
+          .absolute4 {
+            position: absolute;
+            right: 25.0926vh;
+            bottom: 1.8519vh;
+          }
+
+          .absolute5 {
+            position: absolute;
+            right: 1.5104vw;
+            bottom: 8.8889vh;
+          }
+
+          .absolute6 {
+            position: absolute;
+            right: 1.5104vw;
+            bottom: 26.7593vh;
+          }
+        }
+      }
+
+      .line-box {
+        width: 100%;
+        height: 23.7037vh; //420*256
+      }
+    }
+
+    .right-box {
+      margin-right: 1.0417vw;
+      width: 21.875vw;
+
+      .bar-box {
+        height: 23.7963vh;
+      }
+
+      .rank-box {
+        width: 100%;
+        height: 23.7037vh; //420*256
+        padding-top: 1.2963vh;
+
+        .rank-ul-box {
+          margin-left: 2.1875vw;
+
+          .rank-li-box {
+            display: flex;
+            align-items: center;
+            background: url("@/assets/images/energy/right/center/bg.png") no-repeat;
+            background-size: auto 100%;
+            margin-bottom: 0.8333vh;
+            height: 1.3889vh;
+            color: #fff;
+            min-height: 11px;
+
+            // justify-content: center;
+            .rank-icon-box {
+              width: 2.2917vw;
+              height: 100%;
+
+              img {
+                height: 100%;
+              }
+            }
+
+            .rank-name-box {
+              font-size: 0.5208vw;
+              margin-left: 2vw;
+              width: 5vw;
+              color: rgba(255, 255, 255, 0.9);
+            }
+
+            .rank-num-box {
+              font-size: 0.625vw;
+              color: #cfe3ff;
+            }
+          }
+        }
+      }
+
+      .pie-box {
+        width: 100%;
+        height: 23.7037vh; //420*256
+      }
+    }
+  }
+
+  dt,
+  dd,
+  dl {
+    padding: 0;
+    margin: 0;
+  }
+}
+</style>
diff --git a/zhitan-vue/src/views/energyanalysis/comprehensive/comprehensive.vue b/zhitan-vue/src/views/energyanalysis/comprehensive/comprehensive.vue
new file mode 100644
index 0000000..2983c5b
--- /dev/null
+++ b/zhitan-vue/src/views/energyanalysis/comprehensive/comprehensive.vue
@@ -0,0 +1,903 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="queryParams" ref="queryRef" :inline="true">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select v-model="queryParams.timeType" placeholder="鏈熼棿" clearable style="width: 120px"
+                @change="handleTimeType">
+                <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker v-model="queryParams.dataTime" :type="queryParams.timeType == 'YEAR'
+                ? 'year'
+                : queryParams.timeType == 'MONTH'
+                  ? 'month'
+                  : 'date'
+                " :format="queryParams.timeType == 'YEAR'
+                  ? 'YYYY'
+                  : queryParams.timeType == 'MONTH'
+                    ? 'YYYY-MM'
+                    : 'YYYY-MM-DD'
+                  " value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 100%" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Download" @click="handleExport">
+                瀵煎嚭
+              </el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div style="
+            height: calc(100vh - 220px) !important;
+            max-height: calc(100vh - 220px) !important;
+            overflow-y: auto;
+          ">
+          <BaseCard :title="queryParams.nodeName + '-缁煎悎鑳借�楄秼鍔�'">
+            <div class="chart-box" v-loading="loading">
+              <div id="Chart1" />
+            </div>
+          </BaseCard>
+          <el-row class="mb20">
+            <el-col :span="7">
+              <BaseCard :title="queryParams.nodeName + '-缁煎悎鑳借�楀悓姣旂幆姣�'">
+                <div class="card-list" v-loading="loading">
+                  <div class="card-list-item" v-show="settingsStore.sideTheme == 'theme-dark'"
+                    :style="{ backgroundImage: 'url(' + item.bg + ')' }" v-for="item in comprehensiveTable"
+                    :key="item.title">
+                    <div class="item-left">
+                      {{ Math.abs(item.icon) }}%
+                      <el-icon :color="item.icon > 0 ? 'green' : item.icon < 0 ? 'red' : ''
+                        ">
+                        <Top v-if="item.icon > 0" />
+                        <Bottom v-if="item.icon < 0" />
+                      </el-icon>
+                    </div>
+                    <div class="item-right">
+                      <div class="item-right-title">
+                        {{ item.title }}
+                      </div>
+                      <div class="item-right-bottom" v-for="(node, nodeIndex) in item.data" :key="nodeIndex">
+                        <div class="item-right-bottom-left">
+                          {{ node.label }}
+                        </div>
+                        <div class="item-right-bottom-right">
+                          {{ Number(node.value.toFixed(2)) }} tce
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="card-list-item" v-show="settingsStore.sideTheme == 'theme-light'"
+                    v-for="item in comprehensiveTable" :key="item.title">
+                    <div class="item-left">
+                      {{ Math.abs(item.icon) }} %
+                      <el-icon :color="item.icon > 0 ? 'green' : item.icon < 0 ? 'red' : ''
+                        ">
+                        <Top v-if="item.icon > 0" />
+                        <Bottom v-if="item.icon < 0" />
+                      </el-icon>
+                    </div>
+                    <div class="item-right">
+                      <div class="item-right-title">
+                        {{ item.title }}
+                      </div>
+                      <div class="item-right-bottom" v-for="(node, nodeIndex) in item.data" :key="nodeIndex">
+                        <div class="item-right-bottom-left">
+                          {{ node.label }}
+                        </div>
+                        <div class="item-right-bottom-right">
+                          {{ Number(node.value.toFixed(2)) }}tce
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </BaseCard>
+            </el-col>
+            <el-col :span="8">
+              <BaseCard :title="queryParams.nodeName + '-鍚勪粙璐ㄧ患鍚堣兘鑰楀崰姣�'">
+                <div class="chart-box" v-loading="loading">
+                  <div id="Chart2" />
+                </div>
+              </BaseCard>
+            </el-col>
+            <el-col :span="9">
+              <BaseCard :title="queryParams.nodeName + '-鐢ㄨ兘鍗曞厓缁煎悎鑳借�楁帓鍚�'">
+                <div class="chart-box" v-loading="loading">
+                  <div id="Chart3" />
+                </div>
+              </BaseCard>
+            </el-col>
+          </el-row>
+          <BaseCard :title="queryParams.nodeName + '-缁煎悎鑳借�楃粺璁″垎鏋愯〃'" v-loading="loading">
+            <div class="table-box">
+              <el-table :data="comprehensiveList" show-summary>
+                <el-table-column label="鏃ユ湡" align="center" key="currentTime" prop="currentTime"
+                  :show-overflow-tooltip="true" />
+                <el-table-column label="缁煎悎鑳借�楅噺(tce)" align="center" key="currentValue" prop="currentValue"
+                  :show-overflow-tooltip="true" />
+              </el-table>
+            </div>
+          </BaseCard>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="comprehensive">
+import {
+  listComprehensive,
+  listYoY,
+  listEnergyRanking,
+} from "@/api/energyAnalysis/energyAnalysis";
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+import * as echarts from "echarts";
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+import { useRoute } from "vue-router";
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getList();
+  }
+);
+import index_card_1 from "@/assets/images/home/index-card-3.png";
+import index_card_2 from "@/assets/images/home/index-card-4.png";
+const comprehensiveTable = ref([
+  {
+    bg: index_card_1,
+    icon: 0,
+    title: "鍚屾瘮",
+    data: [
+      {
+        label: null,
+        value: 0,
+      },
+      {
+        label: null,
+        value: 0,
+      },
+    ],
+  },
+  {
+    bg: index_card_2,
+    icon: 0,
+    title: "鐜瘮",
+    data: [
+      {
+        label: null,
+        value: 0,
+      },
+      {
+        label: null,
+        value: 0,
+      },
+    ],
+  },
+]);
+const comprehensiveList = ref([]);
+const loading = ref(false);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    timeType: null,
+    dataTime: null,
+  },
+  query: {
+    modelCode: null,
+  },
+});
+const { queryParams, query } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  handleTimeType(period.value[0].value);
+  handleQuery();
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+// 鑳借�楀姣斿垎鏋�-缁煎悎鑳借�楀垎鏋�-鍒楄〃
+function getList() {
+  loading.value = true;
+  // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+  if (echarts.getInstanceByDom(document.getElementById("Chart1"))) {
+    echarts.dispose(document.getElementById("Chart1"));
+  }
+  if (echarts.getInstanceByDom(document.getElementById("Chart2"))) {
+    echarts.dispose(document.getElementById("Chart2"));
+  }
+  if (echarts.getInstanceByDom(document.getElementById("Chart3"))) {
+    echarts.dispose(document.getElementById("Chart3"));
+  }
+  const myChart1 = echarts.init(document.getElementById("Chart1"));
+  const myChart2 = echarts.init(document.getElementById("Chart2"));
+  const myChart3 = echarts.init(document.getElementById("Chart3"));
+  listComprehensive(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    if (!!res.code && res.code == 200) {
+      loading.value = false;
+      let xdata = [];
+      let yvalue = [];
+      let ycompareValue = [];
+      let yqoq = [];
+      if (!!res.data.chartDataList) {
+        res.data.chartDataList.map((item) => {
+          xdata.push(
+            proxy
+              .dayjs(item.xdata)
+              .format(
+                queryParams.value.timeType == "YEAR"
+                  ? "MM鏈�"
+                  : queryParams.value.timeType == "MONTH"
+                    ? "DD鏃�"
+                    : "HH鏃�"
+              )
+          );
+          yvalue.push(!!item.yvalue ? item.yvalue : 0);
+          ycompareValue.push(!!item.ycompareValue ? item.ycompareValue : 0);
+          yqoq.push(!!item.yqoq ? item.yqoq : 0);
+        });
+      }
+      setTimeout(() => {
+        myChart1.setOption({
+          color: ["#2979ff", "#19be6b", "#ff9900", "#fa3534"],
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          grid: {
+            top: "45",
+            left: "7%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          xAxis: {
+            type: "category",
+            axisPointer: {
+              type: "shadow",
+            },
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: xdata,
+          },
+          yAxis: [
+            {
+              type: "value",
+              name: "tce",
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+          ],
+          series: [
+            {
+              name: "鏈湡鍊�",
+              type: "bar",
+              barWidth: "16",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + "tce";
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: yvalue,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+            {
+              name: "鍚屾湡鍊�",
+              type: "bar",
+              barWidth: "16",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + "tce";
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: ycompareValue,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+          ],
+        });
+      }, 100);
+      let total = 0;
+      let seriesData = [];
+      if (!!res.data.energyProportion && res.data.energyProportion.length > 0) {
+        res.data.energyProportion.map((item) => {
+          seriesData.push({
+            name: item.energyName,
+            value: Number(item.count).toFixed(2),
+          });
+        });
+        seriesData.forEach(function (val) {
+          total += Number(val.value);
+        });
+      } else {
+        listEnergyTypeList().then((resEnergyTypeList) => {
+          resEnergyTypeList.data.map((item) => {
+            seriesData.push({
+              name: item.enername,
+              value: Number(0),
+            });
+          });
+        });
+      }
+      setTimeout(() => {
+        myChart2.setOption({
+          color: ["#2979ff", "#19be6b", "#ff9900", "#fa3534"],
+          grid: {
+            top: "20%",
+            left: "1%",
+            right: "1%",
+            bottom: "0%",
+            containLabel: true,
+          },
+          tooltip: {
+            trigger: "item",
+          },
+          legend: {
+            type: "scroll",
+            orient: "vertical",
+            top: "center",
+            icon: "circle",
+            right: "5%",
+            itemWidth: 14,
+            itemHeight: 14,
+            itemGap: 60,
+            textStyle: {
+              align: "left",
+              verticalAlign: "middle",
+              rich: {
+                name: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+                value: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+                rate: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+              },
+            },
+            data: seriesData,
+            formatter: (name) => {
+              let target, percent, muid;
+              for (let i = 0; i < seriesData.length; i++) {
+                if (seriesData[i].name === name) {
+                  target = seriesData[i].value;
+                  muid = seriesData[i].muid;
+                  percent =
+                    total != 0 ? ((target / total) * 100).toFixed(2) : 0;
+                }
+              }
+              return `{name|${name}(tce)  }{value| ${target}} {rate| ${percent}%}`;
+            },
+          },
+          series: [
+            {
+              name: "鍚勪粙璐ㄨ兘鑰楀崰姣�",
+              type: "pie",
+              radius: ["25%", "50%"],
+              center: ["30%", "50%"],
+              avoidLabelOverlap: false,
+              label: {
+                show: false,
+                overflow: "none",
+                formatter: "{b} {d}%  \n {c} tce",
+              },
+              data: seriesData,
+            },
+          ],
+        });
+      }, 100);
+
+      comprehensiveList.value = !!res.data.dataList ? res.data.dataList : [];
+      window.addEventListener(
+        "resize",
+        () => {
+          myChart1.resize();
+          myChart2.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+  listYoY(
+    proxy.addDateRange({
+      ...queryParams.value,
+    })
+  ).then((res) => {
+    if (!!res.data.tongbi) {
+      comprehensiveTable.value[0].icon = !!res.data.tongbi.ratio
+        ? res.data.tongbi.ratio
+        : 0;
+      comprehensiveTable.value[0].data[0].label = !!res.data.tongbi.currentTime
+        ? res.data.tongbi.currentTime
+        : 0;
+      comprehensiveTable.value[0].data[0].value = !!res.data.tongbi.currentValue
+        ? res.data.tongbi.currentValue
+        : 0;
+      comprehensiveTable.value[0].data[1].label = !!res.data.tongbi.compareTime
+        ? res.data.tongbi.compareTime
+        : 0;
+      comprehensiveTable.value[0].data[1].value = !!res.data.tongbi.compareValue
+        ? res.data.tongbi.compareValue
+        : 0;
+    }
+    if (!!res.data.huanbi) {
+      comprehensiveTable.value[1].icon = !!res.data.huanbi.ratio
+        ? res.data.huanbi.ratio
+        : 0;
+      comprehensiveTable.value[1].data[0].label = !!res.data.huanbi.currentTime
+        ? res.data.huanbi.currentTime
+        : 0;
+      comprehensiveTable.value[1].data[0].value = !!res.data.huanbi.currentValue
+        ? res.data.huanbi.currentValue
+        : 0;
+      comprehensiveTable.value[1].data[1].label = !!res.data.huanbi.compareTime
+        ? res.data.huanbi.compareTime
+        : 0;
+      comprehensiveTable.value[1].data[1].value = !!res.data.huanbi.compareValue
+        ? res.data.huanbi.compareValue
+        : 0;
+    }
+  });
+  listEnergyRanking(
+    proxy.addDateRange({
+      ...queryParams.value,
+    })
+  ).then((res) => {
+    let nodeName = [];
+    let energyConsumption = [];
+    let maxenergyConsumption = [];
+    if (!!res.data) {
+      res.data.map((item, index) => {
+        nodeName.push(item.nodeName);
+        energyConsumption.push(
+          !!item.energyConsumption ? item.energyConsumption : 0
+        );
+        maxenergyConsumption[index] = res.data[0].energyConsumption;
+      });
+    }
+    setTimeout(() => {
+      myChart3.setOption({
+        grid: {
+          left: "2%",
+          right: "2%",
+          top: "4%",
+          bottom: 0,
+          containLabel: true,
+        },
+        tooltip: {
+          trigger: "axis",
+          axisPointer: {
+            type: "none",
+          },
+          formatter: function (params) {
+            return params[0].name + " : " + params[0].value;
+          },
+        },
+        xAxis: {
+          show: false,
+          type: "value",
+        },
+        yAxis: [
+          {
+            type: "category",
+            inverse: true,
+            axisLabel: {
+              interval: 0,
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#000",
+              fontSize: 14,
+              formatter: function (value, index) {
+                if (index == 0) {
+                  return (
+                    "{idx0|" +
+                    (1 + index + 0) +
+                    "}{title|" +
+                    value +
+                    "}"
+                  );
+                } else if (index == 1) {
+                  return (
+                    "{idx1|" +
+                    (1 + index + 0) +
+                    "}{title|" +
+                    value +
+                    "}"
+                  );
+                } else if (index == 2) {
+                  return (
+                    "{idx2|" +
+                    (1 + index + 0) +
+                    "}{title|" +
+                    value +
+                    "}"
+                  );
+                } else {
+                  return (
+                    "{idx|" + (1 + index + 0) + "}{title|" + value + "}"
+                  );
+                }
+              },
+              rich: {
+                idx0: {
+                  color: "#FF0004",
+                  backgroundColor: "#FF000426",
+                  borderRadius: 100,
+                  padding: [5, 8],
+                },
+                idx1: {
+                  color: "#FF8400",
+                  backgroundColor: "#FF84001F",
+                  borderRadius: 100,
+                  padding: [5, 8],
+                },
+                idx2: {
+                  color: "#FFDD00",
+                  backgroundColor: "#FFDD001F",
+                  borderRadius: 100,
+                  padding: [5, 8],
+                },
+                idx: {
+                  color: "#3371EB",
+                  backgroundColor: "#3371EB26",
+                  borderRadius: 100,
+                  padding: [5, 8],
+                },
+                title: {
+                  padding: [5, 8],
+                },
+              },
+            },
+            splitLine: {
+              show: false,
+            },
+            axisTick: {
+              show: false,
+            },
+            axisLine: {
+              show: false,
+            },
+            data: nodeName,
+          },
+          {
+            type: "category",
+            inverse: true,
+            axisTick: "none",
+            axisLine: "none",
+            show: true,
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#000",
+              fontSize: "12",
+            },
+            data: energyConsumption,
+          },
+        ],
+        series: [
+          {
+            type: "bar",
+            showBackground: true,
+            backgroundStyle: {
+              color: "#DCDEE2",
+            },
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                {
+                  offset: 0,
+                  color: "#0437FF",
+                },
+                {
+                  offset: 1,
+                  color: "#55C6FF",
+                },
+              ]),
+            },
+            barWidth: "10",
+            data: energyConsumption,
+          },
+          {
+            type: "pictorialBar",
+            symbol: "rect",
+            symbolSize: [4, 14],
+            symbolPosition: "end",
+            itemStyle: {
+              color: "#488BFF",
+            },
+            data: energyConsumption,
+          },
+        ],
+      });
+    }, 100);
+    window.addEventListener(
+      "resize",
+      () => {
+        myChart3.resize();
+      },
+      { passive: true }
+    );
+  });
+}
+// 鑳借�楀姣斿垎鏋�-缁煎悎鑳借�楀垎鏋�-鎼滅储
+function handleQuery() {
+  getList();
+}
+// 鑳借�楀姣斿垎鏋�-缁煎悎鑳借�楀垎鏋�-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleTimeType(period.value[0].value);
+  queryParams.value.analysisType = "YOY";
+  handleQuery();
+}
+// 鑳借�楀姣斿垎鏋�-缁煎悎鑳借�楀垎鏋�-瀵煎嚭
+function handleExport() {
+  proxy.download(
+    "consumptionanalysis/comprehensiveEnergyExport",
+    {
+      ...queryParams.value,
+      ...query.value,
+    },
+    `${queryParams.value.nodeName}-缁煎悎鑳借�楃粺璁″垎鏋愯〃_${new Date().getTime()}.xlsx`
+  );
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+.themeDark {
+  .card-list {
+    padding: 18px;
+
+    .card-list-item {
+      height: 135px;
+      border-radius: 5px 5px 5px 5px;
+      background-size: 100% 100%;
+      box-sizing: border-box;
+      padding: 10px 18px 13px 16px;
+      color: #fffdfd;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      text-align: center;
+
+      .item-left {
+        width: 30%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        text-align: center;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 20px;
+      }
+
+      .item-right {
+        width: 70%;
+        text-align: left;
+
+        .item-right-title {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: bold;
+          font-size: 20px;
+        }
+
+        .item-right-bottom {
+          display: flex;
+          justify-content: space-between;
+          margin-top: 10px;
+          font-family: OPPOSans, OPPOSans;
+          font-weight: bold;
+          font-size: 16px;
+
+          .item-right-bottom-left {
+            font-family: OPPOSans, OPPOSans;
+            font-weight: 500;
+            font-size: 14px;
+            color: rgba(255, 255, 255, 0.7);
+          }
+
+          .item-right-bottom-right {
+            justify-content: end;
+            font-weight: 800;
+            font-size: 16px;
+            font-family: OPPOSans-Medium;
+          }
+        }
+      }
+
+      &:nth-child(n) {
+        margin-bottom: 18px;
+      }
+
+      &:nth-child(2n) {
+        margin-bottom: 0;
+      }
+    }
+  }
+}
+
+.themeLight {
+  .card-list {
+    padding: 18px;
+
+    .card-list-item {
+      height: 135px;
+      border-radius: 5px 5px 5px 5px;
+      background-size: 100% 100%;
+      box-sizing: border-box;
+      padding: 10px 18px 13px 16px;
+      background: #f7f8fa;
+      color: #3271eb;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      text-align: center;
+
+      .item-left {
+        width: 30%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        text-align: center;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 20px;
+      }
+
+      .item-right {
+        width: 70%;
+        text-align: left;
+
+        .item-right-title {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: bold;
+          font-size: 20px;
+        }
+
+        .item-right-bottom {
+          display: flex;
+          justify-content: space-between;
+          margin-top: 10px;
+          font-family: OPPOSans, OPPOSans;
+          font-weight: bold;
+          font-size: 16px;
+
+          .item-right-bottom-left {
+            font-family: OPPOSans, OPPOSans;
+            font-weight: 500;
+            font-size: 14px;
+          }
+
+          .item-right-bottom-right {
+            font-weight: 800;
+            font-size: 16px;
+            font-family: OPPOSans-Medium;
+          }
+        }
+      }
+
+      &:nth-child(n) {
+        margin-bottom: 18px;
+      }
+
+      &:nth-child(2n) {
+        margin-bottom: 0;
+      }
+    }
+  }
+}
+</style>
diff --git a/zhitan-vue/src/views/energyanalysis/department/department.vue b/zhitan-vue/src/views/energyanalysis/department/department.vue
new file mode 100644
index 0000000..26a605c
--- /dev/null
+++ b/zhitan-vue/src/views/energyanalysis/department/department.vue
@@ -0,0 +1,595 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="queryParams" ref="queryRef" :inline="true">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select v-model="queryParams.timeType" placeholder="鏈熼棿" clearable style="width: 120px"
+                @change="handleTimeType">
+                <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker v-model="queryParams.dataTime" :type="queryParams.timeType == 'YEAR'
+                ? 'year'
+                : queryParams.timeType == 'MONTH'
+                  ? 'month'
+                  : 'date'
+                " :format="queryParams.timeType == 'YEAR'
+                  ? 'YYYY'
+                  : queryParams.timeType == 'MONTH'
+                    ? 'YYYY-MM'
+                    : 'YYYY-MM-DD'
+                  " value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+              <el-select v-model="queryParams.energyType" placeholder="鑳芥簮绫诲瀷" clearable style="width: 120px">
+                <el-option :label="item.enername" :value="item.enersno" v-for="item in energyTypeList"
+                  :key="item.enersno" @click="handleEnergyType(item)" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button :type="queryParams.analysisType == 'YOY' ? 'primary' : ''" @click="handleAnalysisType('YOY')">
+                鍚屾瘮
+              </el-button>
+              <el-button :type="queryParams.analysisType == 'MOM' ? 'primary' : ''" @click="handleAnalysisType('MOM')">
+                鐜瘮
+              </el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Download" @click="handleExport">
+                瀵煎嚭
+              </el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div style="
+            height: calc(100vh - 220px) !important;
+            max-height: calc(100vh - 220px) !important;
+            overflow-y: auto;
+          " v-loading="loading">
+          <!-- <el-row :gutter="24">
+            <el-col :span="16"> -->
+          <BaseCard :title="queryParams.nodeName + '-鍘傚尯鑳借�楀垎鏋�'">
+            <div class="chart-box">
+              <div id="Chart1" />
+            </div>
+          </BaseCard>
+          <!-- </el-col>
+            <el-col :span="8">
+              <BaseCard title="鍘傚尯鑳借�楁帓鍚�">
+                <div class="chart-box">
+                  <div id="Chart2" />
+                </div>
+              </BaseCard>
+            </el-col>
+            <el-col :span="24"> -->
+          <BaseCard :title="queryParams.nodeName +
+            '-鍘傚尯鑳借�楀垎鏋愮粺璁″垎鏋愯〃-' +
+            queryParams.enername
+            ">
+            <div class="table-box">
+              <el-table :data="departmentList" show-summary>
+                <el-table-column label="鏈湡鏃堕棿" align="center" key="currentTime" prop="currentTime"
+                  :show-overflow-tooltip="true" />
+                <el-table-column :label="'鏈湡鑰�' +
+                  queryParams.enername +
+                  '(' +
+                  queryParams.muid +
+                  ')'
+                  " align="center" key="currentValue" prop="currentValue" :show-overflow-tooltip="true" />
+                <el-table-column label="鍚屾湡鏃堕棿" align="center" key="compareTime" prop="compareTime"
+                  :show-overflow-tooltip="true" />
+                <el-table-column :label="'鍚屾湡鑰�' +
+                  queryParams.enername +
+                  '(' +
+                  queryParams.muid +
+                  ')'
+                  " align="center" key="compareValue" prop="compareValue" :show-overflow-tooltip="true" />
+                <el-table-column :label="(queryParams.analysisType == 'YOY' ? '鍚�' : '鐜�') + '姣�(%)'
+                  " align="center" key="ratio" prop="ratio" :show-overflow-tooltip="true" width="200" />
+              </el-table>
+            </div>
+          </BaseCard>
+          <!-- </el-col>
+          </el-row> -->
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="department">
+import {
+  listRegion,
+  listDepartment,
+} from "@/api/energyAnalysis/energyAnalysis";
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+import * as echarts from "echarts";
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+import { useRoute } from "vue-router";
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getList();
+  }
+);
+const energyTypeList = ref(undefined);
+const departmentList = ref([]);
+const loading = ref(false);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    timeType: null,
+    dataTime: null,
+    analysisType: "YOY",
+    energyType: null,
+  },
+  query: {
+    modelCode: null,
+  },
+});
+const { queryParams, query } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  handleTimeType(period.value[0].value);
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+    queryParams.value.energyType = energyTypeList.value[0].enersno;
+    queryParams.value.enername = energyTypeList.value[0].enername;
+    queryParams.value.muid = energyTypeList.value[0].muid;
+    handleQuery();
+  });
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+function handleEnergyType(item) {
+  queryParams.value.enername = item.enername;
+  queryParams.value.muid = item.muid;
+  handleQuery();
+}
+function handleAnalysisType(analysisType) {
+  queryParams.value.analysisType = analysisType;
+  getList();
+}
+// 鑳借�楀姣斿垎鏋�-绉戝鑳借�楀垎鏋�-鍒楄〃
+function getList() {
+  loading.value = true;
+  // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+  if (echarts.getInstanceByDom(document.getElementById("Chart1"))) {
+    echarts.dispose(document.getElementById("Chart1"));
+  }
+  // if (echarts.getInstanceByDom(document.getElementById("Chart2"))) {
+  //   echarts.dispose(document.getElementById("Chart2"));
+  // }
+  const myChart1 = echarts.init(document.getElementById("Chart1"));
+  // const myChart2 = echarts.init(document.getElementById("Chart2"));
+  listRegion(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    if (!!res.code && res.code == 200) {
+      loading.value = false;
+      let xdata = [];
+      let yvalue = [];
+      let ycompareValue = [];
+      let yqoq = [];
+      if (!!res.data.chartDataList) {
+        res.data.chartDataList.map((item) => {
+          xdata.push(
+            proxy
+              .dayjs(item.xdata)
+              .format(
+                queryParams.value.timeType == "YEAR"
+                  ? "MM鏈�"
+                  : queryParams.value.timeType == "MONTH"
+                    ? "DD鏃�"
+                    : "HH鏃�"
+              )
+          );
+          yvalue.push(!!item.yvalue ? item.yvalue : 0);
+          ycompareValue.push(!!item.ycompareValue ? item.ycompareValue : 0);
+          yqoq.push(!!item.yqoq ? item.yqoq : 0);
+        });
+      }
+      setTimeout(() => {
+        myChart1.setOption({
+          color: ["#2979ff", "#19be6b", "#ff9900", "#fa3534"],
+          grid: {
+            top: "45",
+            left: "7%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          xAxis: {
+            type: "category",
+            axisPointer: {
+              type: "shadow",
+            },
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: xdata,
+          },
+          yAxis: [
+            {
+              type: "value",
+              name:
+                "鑰�" +
+                queryParams.value.enername +
+                "閲�(" +
+                queryParams.value.muid +
+                ")",
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+            {
+              type: "value",
+              name: queryParams.value.analysisType == "YOY" ? "鍚屾瘮(%)" : "鐜瘮(%)",
+              alignTicks: true,
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              axisTick: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+          ],
+          series: [
+            {
+              name: "鏈湡鍊�",
+              type: "bar",
+              barWidth: "8",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + queryParams.value.muid;
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: yvalue,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+            {
+              name: "鍚屾湡鍊�",
+              type: "bar",
+              barWidth: "8",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + queryParams.value.muid;
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: ycompareValue,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+            {
+              name: queryParams.value.analysisType == "YOY" ? "鍚屾瘮" : "鐜瘮",
+              type: "line",
+              yAxisIndex: 1,
+              symbol: "none", // 璁剧疆涓� 'none' 鍘绘帀鍦嗙偣
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + "%";
+                },
+              },
+              data: yqoq,
+            },
+          ],
+        });
+      }, 100);
+      departmentList.value = !!res.data.dataList ? res.data.dataList : [];
+      window.addEventListener(
+        "resize",
+        () => {
+          myChart1.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+  // listDepartment(
+  //   proxy.addDateRange({
+  //     ...queryParams.value,
+  //     ...query.value,
+  //   })
+  // ).then((res) => {
+  //   if (!!res.code && res.code == 200) {
+  //     loading.value = false;
+  //     let seriesdata = [];
+  //     let xdata = [];
+  //     if (!!energyTypeList.value) {
+  //       energyTypeList.value.map((item) => {
+  //         seriesdata.push({
+  //           name: item.enername,
+  //           type: "bar",
+  //           barWidth: "16",
+  //           stack: "total",
+  //           data: [],
+  //         });
+  //       });
+  //     }
+  //     if (!!res.data) {
+  //       res.data.map((dataItem) => {
+  //         xdata.push(dataItem.nodeName)
+  //         seriesdata.forEach((seriesdataItem) => {
+  //           if (
+  //             dataItem.data.find(
+  //               (dataItemItem) =>
+  //                 dataItemItem.energyTypeName == seriesdataItem.name
+  //             ) == undefined
+  //           ) {
+  //             dataItem.data.push({
+  //               nodeId: dataItem.nodeId,
+  //               nodeName: dataItem.nodeName,
+  //               energyTypeNo: null,
+  //               energyTypeName: seriesdataItem.name,
+  //               energyConsumption: 0,
+  //             });
+  //           }
+  //           dataItem.data.map(dataItemItem => {
+  //             if (seriesdataItem.name == dataItemItem.energyTypeName) {
+  //               seriesdataItem.data.push(dataItemItem.energyConsumption)
+  //             }
+  //           })
+  //         });
+  //       });
+  //     }
+  //     setTimeout(() => {
+  //       myChart2.setOption({
+  //         color: ["#2979ff", "#19be6b", "#ff9900", "#fa3534"],
+  //         grid: {
+  //           top: "45",
+  //           left: "17%",
+  //           right: "5%",
+  //           bottom: "10",
+  //           containLabel: true,
+  //         },
+  //         tooltip: {
+  //           trigger: "axis",
+  //           axisPointer: {
+  //             type: "shadow",
+  //           },
+  //         },
+  //         legend: {
+  //           icon: "rect",
+  //           itemWidth: 14,
+  //           itemHeight: 10,
+  //           right: 0,
+  //           textStyle: {
+  //             color:
+  //               settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //           },
+  //         },
+  //         xAxis: {
+  //           nameLocation: "start",
+  //           type: "value",
+  //           name: "鍗曚綅tce",
+  //           nameTextStyle: {
+  //             color:
+  //               settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //             fontSize: 14,
+  //             padding: [0, 0, 5, 0],
+  //           },
+  //           axisLine: {
+  //             show: false,
+  //           },
+  //           splitLine: {
+  //             show: true,
+  //             lineStyle: {
+  //               type: "dashed",
+  //               color:
+  //                 settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //             },
+  //           },
+  //           axisTick: {
+  //             show: false,
+  //           },
+  //           splitArea: {
+  //             show: false,
+  //           },
+  //           axisLabel: {
+  //             color:
+  //               settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //             fontSize: 14,
+  //             //   formatter: '{value} ml'
+  //           },
+  //         },
+  //         yAxis: {
+  //           type: "category",
+  //           axisPointer: {
+  //             type: "shadow",
+  //           },
+  //           axisLine: {
+  //             show: true,
+  //             lineStyle: {
+  //               color:
+  //                 settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //             },
+  //           },
+  //           axisTick: {
+  //             show: false,
+  //           },
+  //           splitArea: {
+  //             show: false,
+  //           },
+  //           splitLine: {
+  //             show: false,
+  //           },
+  //           axisLabel: {
+  //             color:
+  //               settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //             fontSize: 14,
+  //             padding: [5, 0, 0, 0],
+  //             //   formatter: '{value} ml'
+  //           },
+  //           data: xdata.splice(0, 6),
+  //         },
+  //         series: seriesdata,
+  //       });
+  //     }, 100);
+  //     window.addEventListener("resize", () => {
+  //       myChart2.resize();
+  //     },{passive: true});
+  //   }
+  // });
+}
+// 鑳借�楀姣斿垎鏋�-绉戝鑳借�楀垎鏋�-鎼滅储
+function handleQuery() {
+  getList();
+}
+// 鑳借�楀姣斿垎鏋�-绉戝鑳借�楀垎鏋�-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleTimeType(period.value[0].value);
+  queryParams.value.energyType = energyTypeList.value[0].enersno;
+  queryParams.value.enername = energyTypeList.value[0].enername;
+  queryParams.value.muid = energyTypeList.value[0].muid;
+  queryParams.value.analysisType = "YOY";
+  handleQuery();
+}
+// 鑳借�楀姣斿垎鏋�-绉戝鑳借�楀垎鏋�-瀵煎嚭
+function handleExport() {
+  proxy.download(
+    "consumptionanalysis/energyExport",
+    {
+      ...queryParams.value,
+      ...query.value,
+    },
+    `${queryParams.value.nodeName}-鍘傚尯鑳借�楀垎鏋恄${new Date().getTime()}.xlsx`
+  );
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/energyanalysis/equipment/equipment.vue b/zhitan-vue/src/views/energyanalysis/equipment/equipment.vue
new file mode 100644
index 0000000..a829067
--- /dev/null
+++ b/zhitan-vue/src/views/energyanalysis/equipment/equipment.vue
@@ -0,0 +1,597 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="queryParams" ref="queryRef" :inline="true">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select v-model="queryParams.timeType" placeholder="鏈熼棿" clearable style="width: 120px"
+                @change="handleTimeType">
+                <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker v-model="queryParams.dataTime" :type="queryParams.timeType == 'YEAR'
+                ? 'year'
+                : queryParams.timeType == 'MONTH'
+                  ? 'month'
+                  : 'date'
+                " :format="queryParams.timeType == 'YEAR'
+                  ? 'YYYY'
+                  : queryParams.timeType == 'MONTH'
+                    ? 'YYYY-MM'
+                    : 'YYYY-MM-DD'
+                  " value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="鑳芥簮绫诲瀷" prop="timeType">
+              <el-select v-model="queryParams.energyType" placeholder="鑳芥簮绫诲瀷" clearable style="width: 120px">
+                <el-option :label="item.enername" :value="item.enersno" v-for="item in energyTypeList"
+                  :key="item.enersno" @click="handleEnergyType(item)" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button :type="queryParams.analysisType == 'YOY' ? 'primary' : ''" @click="handleAnalysisType('YOY')">
+                鍚屾瘮
+              </el-button>
+              <el-button :type="queryParams.analysisType == 'MOM' ? 'primary' : ''" @click="handleAnalysisType('MOM')">
+                鐜瘮
+              </el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Download" @click="handleExport">
+                瀵煎嚭
+              </el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div style="
+            height: calc(100vh - 220px) !important;
+            max-height: calc(100vh - 220px) !important;
+            overflow-y: auto;
+          " v-loading="loading">
+          <!-- <el-row :gutter="24">
+            <el-col :span="16"> -->
+          <BaseCard :title="queryParams.nodeName + '-璁惧鑳借�楄秼鍔�'">
+            <div class="chart-box">
+              <div id="Chart1" />
+            </div>
+          </BaseCard>
+          <!-- </el-col>
+            <el-col :span="8">
+              <BaseCard title="璁惧鑳借�楄秼鍔�">
+                <div class="chart-box">
+                  <div id="Chart2" />
+                </div>
+              </BaseCard>
+            </el-col>
+            <el-col :span="24"> -->
+
+          <BaseCard :title="queryParams.nodeName +
+            '-璁惧鑳借�楃粺璁″垎鏋愯〃-' +
+            queryParams.enername
+            ">
+            <div class="table-box">
+              <el-table :data="departmentList" show-summary>
+                <el-table-column label="鏈湡鏃堕棿" align="center" key="currentTime" prop="currentTime"
+                  :show-overflow-tooltip="true" />
+                <el-table-column :label="'鏈湡鑰�' +
+                  queryParams.enername +
+                  '(' +
+                  queryParams.muid +
+                  ')'
+                  " align="center" key="currentValue" prop="currentValue" :show-overflow-tooltip="true" />
+                <el-table-column label="鍚屾湡鏃堕棿" align="center" key="compareTime" prop="compareTime"
+                  :show-overflow-tooltip="true" />
+                <el-table-column :label="'鍚屾湡鑰�' +
+                  queryParams.enername +
+                  '(' +
+                  queryParams.muid +
+                  ')'
+                  " align="center" key="compareValue" prop="compareValue" :show-overflow-tooltip="true" />
+                <el-table-column :label="(queryParams.analysisType == 'YOY' ? '鍚�' : '鐜�') + '姣�(%)'
+                  " align="center" key="ratio" prop="ratio" :show-overflow-tooltip="true" width="200" />
+              </el-table>
+            </div>
+          </BaseCard>
+          <!-- </el-col>
+          </el-row> -->
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="equipment">
+import {
+  listRegion,
+  listDepartment,
+} from "@/api/energyAnalysis/energyAnalysis";
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+import * as echarts from "echarts";
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+import { useRoute } from "vue-router";
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getList();
+  }
+);
+const energyTypeList = ref(undefined);
+const departmentList = ref([]);
+const loading = ref(false);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    timeType: null,
+    dataTime: null,
+    analysisType: "YOY",
+    energyType: null,
+  },
+  query: {
+    modelCode: null,
+  },
+});
+const { queryParams, query } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  handleTimeType(period.value[0].value);
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+    queryParams.value.energyType = energyTypeList.value[0].enersno;
+    queryParams.value.enername = energyTypeList.value[0].enername;
+    queryParams.value.muid = energyTypeList.value[0].muid;
+    handleQuery();
+  });
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+function handleEnergyType(item) {
+  queryParams.value.enername = item.enername;
+  queryParams.value.muid = item.muid;
+  handleQuery();
+}
+function handleAnalysisType(analysisType) {
+  queryParams.value.analysisType = analysisType;
+  getList();
+}
+// 鑳借�楀姣斿垎鏋�-璁惧鑳借�楀垎鏋�-鍒楄〃
+function getList() {
+  loading.value = true;
+  // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+  if (echarts.getInstanceByDom(document.getElementById("Chart1"))) {
+    echarts.dispose(document.getElementById("Chart1"));
+  }
+  // if (echarts.getInstanceByDom(document.getElementById("Chart2"))) {
+  //   echarts.dispose(document.getElementById("Chart2"));
+  // }
+  const myChart1 = echarts.init(document.getElementById("Chart1"));
+  // const myChart2 = echarts.init(document.getElementById("Chart2"));
+  listRegion(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    if (!!res.code && res.code == 200) {
+      loading.value = false;
+      let xdata = [];
+      let yvalue = [];
+      let ycompareValue = [];
+      let yqoq = [];
+      if (!!res.data.chartDataList) {
+        res.data.chartDataList.map((item) => {
+          xdata.push(
+            proxy
+              .dayjs(item.xdata)
+              .format(
+                queryParams.value.timeType == "YEAR"
+                  ? "MM鏈�"
+                  : queryParams.value.timeType == "MONTH"
+                    ? "DD鏃�"
+                    : "HH鏃�"
+              )
+          );
+          yvalue.push(!!item.yvalue ? item.yvalue : 0);
+          ycompareValue.push(!!item.ycompareValue ? item.ycompareValue : 0);
+          yqoq.push(!!item.yqoq ? item.yqoq : 0);
+        });
+      }
+      setTimeout(() => {
+        myChart1.setOption({
+          color: ["#2979ff", "#19be6b", "#ff9900", "#fa3534"],
+          grid: {
+            top: "45",
+            left: "7%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          xAxis: {
+            type: "category",
+            axisPointer: {
+              type: "shadow",
+            },
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: xdata,
+          },
+          yAxis: [
+            {
+              type: "value",
+              name:
+                "鑰�" +
+                queryParams.value.enername +
+                "閲�(" +
+                queryParams.value.muid +
+                ")",
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+            {
+              type: "value",
+              name:
+                queryParams.value.analysisType == "YOY" ? "鍚屾瘮(%)" : "鐜瘮(%)",
+              alignTicks: true,
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              axisTick: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+          ],
+          series: [
+            {
+              name: "鏈湡鍊�",
+              type: "bar",
+              barWidth: "8",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + queryParams.value.muid;
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: yvalue,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+            {
+              name: "鍚屾湡鍊�",
+              type: "bar",
+              barWidth: "8",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + queryParams.value.muid;
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: ycompareValue,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+            {
+              name: queryParams.value.analysisType == "YOY" ? "鍚屾瘮" : "鐜瘮",
+              type: "line",
+              yAxisIndex: 1,
+              symbol: "none", // 璁剧疆涓� 'none' 鍘绘帀鍦嗙偣
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + "%";
+                },
+              },
+              data: yqoq,
+            },
+          ],
+        });
+      }, 100);
+      departmentList.value = !!res.data.dataList ? res.data.dataList : [];
+      window.addEventListener(
+        "resize",
+        () => {
+          myChart1.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+  // listDepartment(
+  //   proxy.addDateRange({
+  //     ...queryParams.value,
+  //     ...query.value,
+  //   })
+  // ).then((res) => {
+  //   if (!!res.code && res.code == 200) {
+  //     loading.value = false;
+  //     let seriesdata = [];
+  //     let xdata = [];
+  //     if (!!energyTypeList.value) {
+  //       energyTypeList.value.map((item) => {
+  //         seriesdata.push({
+  //           name: item.enername,
+  //           type: "bar",
+  //           barWidth: "16",
+  //           stack: "total",
+  //           data: [],
+  //         });
+  //       });
+  //     }
+  //     if (!!res.data) {
+  //       res.data.map((dataItem) => {
+  //         xdata.push(dataItem.nodeName)
+  //         seriesdata.forEach((seriesdataItem) => {
+  //           if (
+  //             dataItem.data.find(
+  //               (dataItemItem) =>
+  //                 dataItemItem.energyTypeName == seriesdataItem.name
+  //             ) == undefined
+  //           ) {
+  //             dataItem.data.push({
+  //               nodeId: dataItem.nodeId,
+  //               nodeName: dataItem.nodeName,
+  //               energyTypeNo: null,
+  //               energyTypeName: seriesdataItem.name,
+  //               energyConsumption: 0,
+  //             });
+  //           }
+  //           dataItem.data.map(dataItemItem => {
+  //             if (seriesdataItem.name == dataItemItem.energyTypeName) {
+  //               seriesdataItem.data.push(dataItemItem.energyConsumption)
+  //             }
+  //           })
+  //         });
+  //       });
+  //     }
+  //     setTimeout(() => {
+  //       myChart2.setOption({
+  //         color: ["#2979ff", "#19be6b", "#ff9900", "#fa3534"],
+  //         grid: {
+  //           top: "45",
+  //           left: "17%",
+  //           right: "5%",
+  //           bottom: "10",
+  //           containLabel: true,
+  //         },
+  //         tooltip: {
+  //           trigger: "axis",
+  //           axisPointer: {
+  //             type: "shadow",
+  //           },
+  //         },
+  //         legend: {
+  //           icon: "rect",
+  //           itemWidth: 14,
+  //           itemHeight: 10,
+  //           right: 0,
+  //           textStyle: {
+  //             color:
+  //               settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //           },
+  //         },
+  //         xAxis: {
+  //           nameLocation: "start",
+  //           type: "value",
+  //           name: "鍗曚綅tce",
+  //           nameTextStyle: {
+  //             color:
+  //               settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //             fontSize: 14,
+  //             padding: [0, 0, 5, 0],
+  //           },
+  //           axisLine: {
+  //             show: false,
+  //           },
+  //           splitLine: {
+  //             show: true,
+  //             lineStyle: {
+  //               type: "dashed",
+  //               color:
+  //                 settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //             },
+  //           },
+  //           axisTick: {
+  //             show: false,
+  //           },
+  //           splitArea: {
+  //             show: false,
+  //           },
+  //           axisLabel: {
+  //             color:
+  //               settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //             fontSize: 14,
+  //             //   formatter: '{value} ml'
+  //           },
+  //         },
+  //         yAxis: {
+  //           type: "category",
+  //           axisPointer: {
+  //             type: "shadow",
+  //           },
+  //           axisLine: {
+  //             show: true,
+  //             lineStyle: {
+  //               color:
+  //                 settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //             },
+  //           },
+  //           axisTick: {
+  //             show: false,
+  //           },
+  //           splitArea: {
+  //             show: false,
+  //           },
+  //           splitLine: {
+  //             show: false,
+  //           },
+  //           axisLabel: {
+  //             color:
+  //               settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+  //             fontSize: 14,
+  //             padding: [5, 0, 0, 0],
+  //             //   formatter: '{value} ml'
+  //           },
+  //           data: xdata.splice(0, 6),
+  //         },
+  //         series: seriesdata,
+  //       });
+  //     }, 100);
+  //     window.addEventListener("resize", () => {
+  //       myChart2.resize();
+  //     },{passive: true});
+  //   }
+  // });
+}
+// 鑳借�楀姣斿垎鏋�-璁惧鑳借�楀垎鏋�-鎼滅储
+function handleQuery() {
+  getList();
+}
+// 鑳借�楀姣斿垎鏋�-璁惧鑳借�楀垎鏋�-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleTimeType(period.value[0].value);
+  queryParams.value.energyType = energyTypeList.value[0].enersno;
+  queryParams.value.enername = energyTypeList.value[0].enername;
+  queryParams.value.muid = energyTypeList.value[0].muid;
+  queryParams.value.analysisType = "YOY";
+  handleQuery();
+}
+// 鑳借�楀姣斿垎鏋�-璁惧鑳借�楀垎鏋�-瀵煎嚭
+function handleExport() {
+  proxy.download(
+    "consumptionanalysis/energyExport",
+    {
+      ...queryParams.value,
+      ...query.value,
+    },
+    `${queryParams.value.nodeName}-璁惧鑳借�楃粺璁″垎鏋愯〃-${queryParams.enername}_${new Date().getTime()}.xlsx`
+  );
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/energyanalysis/indicatorassessment/indicatorassessment.vue b/zhitan-vue/src/views/energyanalysis/indicatorassessment/indicatorassessment.vue
new file mode 100644
index 0000000..e6e4c36
--- /dev/null
+++ b/zhitan-vue/src/views/energyanalysis/indicatorassessment/indicatorassessment.vue
@@ -0,0 +1,355 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="queryParams" ref="queryRef" :inline="true">
+            <el-form-item label="鏈熼棿" prop="timeType">
+              <el-select v-model="queryParams.timeType" placeholder="鏈熼棿" clearable style="width:120px"
+                @change="handleTimeType">
+                <el-option v-for="dict in period" :key="dict.value" :label="dict.label" :value="dict.value"
+                  v-show="dict.value != 'DAY'" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker v-model="queryParams.dataTime" :type="queryParams.timeType == 'YEAR'
+                  ? 'year'
+                  : queryParams.timeType == 'MONTH'
+                    ? 'month'
+                    : 'date'
+                " :format="queryParams.timeType == 'YEAR'
+                    ? 'YYYY'
+                    : queryParams.timeType == 'MONTH'
+                      ? 'YYYY-MM'
+                      : 'YYYY-MM-DD'
+                  " value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 100%" />
+            </el-form-item>
+            <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+              <el-select v-model="queryParams.energyType" placeholder="鑳芥簮绫诲瀷" style="width: 120px;">
+                <el-option :label="item.enername" :value="item.enersno" v-for="item in energyTypeList"
+                  :key="item.enersno" @click="handleEnergyType(item)" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <!-- <div class="table-box">
+          <div class="mt20 mb20">
+            <el-button type="primary" icon="Download" @click="handleExport">
+              瀵煎嚭
+            </el-button>
+          </div>
+        </div> -->
+        <div style="
+            height: calc(100vh - 220px) !important;
+            max-height: calc(100vh - 220px) !important;
+            overflow-y: auto;
+          " v-loading="loading">
+          <BaseCard :title="queryParams.nodeName + '-鑳借�楁寚鏍囪秼鍔�'">
+            <div class="chart-box">
+              <div id="Chart1" />
+            </div>
+          </BaseCard>
+          <BaseCard :title="queryParams.nodeName +
+            '-鑳借�楁寚鏍囪秼鍔跨粺璁″垎鏋愯〃-' +
+            queryParams.enername
+            ">
+            <div class="table-box">
+              <el-table :data="regionList" show-summary>
+                <el-table-column label="鏃堕棿" align="center" key="xdata" prop="xdata" :show-overflow-tooltip="true" />
+                <el-table-column :label="'鏈湡鑰�' +
+                  queryParams.enername +
+                  '(' +
+                  queryParams.muid +
+                  ')'
+                  " align="center" key="yvalue" prop="yvalue" :show-overflow-tooltip="true" />
+                <el-table-column :label="'璁″垝鑰�' +
+                  queryParams.enername +
+                  '(' +
+                  queryParams.muid +
+                  ')'
+                  " align="center" key="planCount" prop="planCount" :show-overflow-tooltip="true" />
+              </el-table>
+            </div>
+          </BaseCard>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="indicatorassessment">
+import { listIndicatorassessment } from "@/api/energyAnalysis/energyAnalysis";
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+import * as echarts from "echarts";
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+import { useRoute } from "vue-router";
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getList();
+  }
+);
+const energyTypeList = ref(undefined);
+const regionList = ref([]);
+const loading = ref(false);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    timeType: null,
+    dataTime: null,
+    analysisType: "YOY",
+    energyType: null,
+  },
+  query: {
+    modelCode: null,
+  },
+});
+const { queryParams, query } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  handleTimeType(period.value[1].value);
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+    queryParams.value.energyType = energyTypeList.value[0].enersno;
+    queryParams.value.enername = energyTypeList.value[0].enername;
+    queryParams.value.muid = energyTypeList.value[0].muid;
+    handleQuery();
+  });
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+function handleEnergyType(item) {
+  queryParams.value.enername = item.enername;
+  queryParams.value.muid = item.muid;
+  handleQuery();
+}
+// 鑳借�楀姣斿垎鏋�-鑳借�楁寚鏍囪�冩牳-鍒楄〃
+function getList() {
+  if (echarts.getInstanceByDom(document.getElementById("Chart1"))) {
+    echarts.dispose(document.getElementById("Chart1"));
+  }
+  const myChart1 = echarts.init(document.getElementById("Chart1"));
+  loading.value = true;
+  listIndicatorassessment(
+    proxy.addDateRange({
+      ...queryParams.value,
+    })
+  ).then((res) => {
+    if (!!res.code && res.code == 200) {
+      loading.value = false;
+      let xdata = [];
+      let yvalue = [];
+      let planCount = [];
+      if (!!res.data.chartDataList) {
+        res.data.chartDataList.map((item) => {
+          item.planCount = !!res.data.planCount ? res.data.planCount : 0;
+          xdata.push(
+            proxy
+              .dayjs(item.xdata)
+              .format(
+                queryParams.value.timeType == "YEAR"
+                  ? "MM鏈�"
+                  : queryParams.value.timeType == "MONTH"
+                    ? "DD鏃�"
+                    : "HH鏃�"
+              )
+          );
+          yvalue.push(!!item.yvalue ? item.yvalue : 0);
+          planCount.push(!!res.data.planCount ? res.data.planCount : 0);
+        });
+        regionList.value = !!res.data.chartDataList
+          ? res.data.chartDataList
+          : [];
+      }
+      setTimeout(() => {
+        myChart1.setOption({
+          color: ["#2979ff", "#ff9900"],
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          grid: {
+            top: "45",
+            left: "7%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          xAxis: {
+            type: "category",
+            axisPointer: {
+              type: "shadow",
+            },
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: xdata,
+          },
+          yAxis: [
+            {
+              type: "value",
+              name:
+                "鑰�" +
+                queryParams.value.enername +
+                "閲�(" +
+                queryParams.value.muid +
+                ")",
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+          ],
+          series: [
+            {
+              name: "鏈湡鍊�",
+              type: "bar",
+              barWidth: "16",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + queryParams.value.muid;
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: yvalue,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+            {
+              name: "璁″垝鍊�",
+              type: "line",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + queryParams.value.muid;
+                },
+              },
+              data: planCount,
+            },
+          ],
+        });
+      }, 100);
+      window.addEventListener(
+        "resize",
+        () => {
+          myChart1.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+}
+// 鑳借�楀姣斿垎鏋�-鑳借�楁寚鏍囪�冩牳-鎼滅储
+function handleQuery() {
+  getList();
+}
+// 鑳借�楀姣斿垎鏋�-鑳借�楁寚鏍囪�冩牳-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleTimeType(period.value[0].value);
+  queryParams.value.energyType = energyTypeList.value[0].enersno;
+  queryParams.value.enername = energyTypeList.value[0].enername;
+  queryParams.value.muid = energyTypeList.value[0].muid;
+  queryParams.value.analysisType = "YOY";
+  handleQuery();
+}
+
+// 鑳借�楀姣斿垎鏋�-鑳借�楁寚鏍囪�冩牳-瀵煎嚭
+function handleExport() {
+  proxy.download(
+    "consumptionanalysis/energyExport",
+    {
+      ...queryParams.value,
+      ...query.value,
+    },
+    `${queryParams.value.nodeName}-鑳借�楁寚鏍囩粺璁″垎鏋愯〃_${new Date().getTime()}.xlsx`
+  );
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/energyanalysis/region/region.vue b/zhitan-vue/src/views/energyanalysis/region/region.vue
new file mode 100644
index 0000000..a0a306b
--- /dev/null
+++ b/zhitan-vue/src/views/energyanalysis/region/region.vue
@@ -0,0 +1,495 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form
+            :model="queryParams"
+            ref="queryRef"
+            :inline="true"
+          >
+          <el-form-item label="鏈熼棿" prop="timeType" style="width: 150px;">
+              <el-select
+                v-model="queryParams.timeType"
+                placeholder="鏈熼棿"
+                clearable
+                style="width: 100%"
+                @change="handleTimeType"
+              >
+                <el-option
+                  v-for="dict in period"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="鏃堕棿">
+              <el-date-picker
+                v-model="queryParams.dataTime"
+                :type="
+                  queryParams.timeType == 'YEAR'
+                    ? 'year'
+                    : queryParams.timeType == 'MONTH'
+                    ? 'month'
+                    : 'date'
+                "
+                :format="
+                  queryParams.timeType == 'YEAR'
+                    ? 'YYYY'
+                    : queryParams.timeType == 'MONTH'
+                    ? 'YYYY-MM'
+                    : 'YYYY-MM-DD'
+                "
+                value-format="YYYY-MM-DD"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+            </el-form-item>
+            <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+              <el-select
+                v-model="queryParams.energyType"
+                placeholder="鑳芥簮绫诲瀷"
+              >
+                <el-option
+                  :label="item.enername"
+                  :value="item.enersno"
+                  v-for="item in energyTypeList"
+                  :key="item.enersno"
+                  @click="handleEnergyType(item)"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="table-box">
+          <div class="mt20 mb20">
+            <el-button
+              :type="queryParams.analysisType == 'YOY' ? 'primary' : ''"
+              @click="handleAnalysisType('YOY')"
+            >
+              鍚屾瘮
+            </el-button>
+            <el-button
+              :type="queryParams.analysisType == 'MOM' ? 'primary' : ''"
+              @click="handleAnalysisType('MOM')"
+            >
+              鐜瘮
+            </el-button>
+            <el-button type="primary" icon="Download" @click="handleExport">
+              瀵煎嚭
+            </el-button>
+          </div>
+        </div>
+        <div
+          style="
+            height: calc(100vh - 290px) !important;
+            max-height: calc(100vh - 290px) !important;
+            overflow-y: auto;
+          "
+          v-loading="loading"
+        >
+          <BaseCard title="鑳借�楄秼鍔�">
+            <div class="chart-box">
+              <div id="Chart1" />
+            </div>
+          </BaseCard>
+          <BaseCard :title="'鍖哄煙鑳借�楃粺璁″垎鏋愯〃-' + queryParams.enername">
+            <div class="table-box">
+              <el-table :data="regionList" show-summary>
+                <el-table-column
+                  label="鏈湡鏃堕棿"
+                  align="center"
+                  key="currentTime"
+                  prop="currentTime"
+                  :show-overflow-tooltip="true"
+                />
+                <el-table-column
+                  :label="
+                    '鏈湡鑰�' +
+                    queryParams.enername +
+                    '(' +
+                    queryParams.muid +
+                    ')'
+                  "
+                  align="center"
+                  key="currentValue"
+                  prop="currentValue"
+                  :show-overflow-tooltip="true"
+                />
+                <el-table-column
+                  label="鍚屾湡鏃堕棿"
+                  align="center"
+                  key="compareTime"
+                  prop="compareTime"
+                  :show-overflow-tooltip="true"
+                />
+                <el-table-column
+                  :label="
+                    '鍚屾湡鑰�' +
+                    queryParams.enername +
+                    '(' +
+                    queryParams.muid +
+                    ')'
+                  "
+                  align="center"
+                  key="compareValue"
+                  prop="compareValue"
+                  :show-overflow-tooltip="true"
+                />
+                <el-table-column
+                  :label="
+                    (queryParams.analysisType == 'YOY' ? '鍚�' : '鐜�') + '姣�(%)'
+                  "
+                  align="center"
+                  key="ratio"
+                  prop="ratio"
+                  :show-overflow-tooltip="true"
+                  width="200"
+                />
+              </el-table>
+            </div>
+          </BaseCard>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="region">
+import { listRegion } from "@/api/energyAnalysis/energyAnalysis";
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+import * as echarts from "echarts";
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+import { useRoute } from "vue-router";
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getList();
+  }
+);
+const energyTypeList = ref(undefined);
+const regionList = ref([]);
+const loading = ref(false);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    timeType: null,
+    dataTime: null,
+    analysisType: "YOY",
+    energyType: null,
+  },
+  query: {
+    modelCode: null,
+  },
+});
+const { queryParams, query } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  handleTimeType(period.value[0].value);
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+    queryParams.value.energyType = energyTypeList.value[0].enersno;
+    queryParams.value.enername = energyTypeList.value[0].enername;
+    queryParams.value.muid = energyTypeList.value[0].muid;
+    handleQuery();
+  });
+}
+function handleTimeType(e) {
+  queryParams.value.timeType = e;
+  queryParams.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+}
+function handleEnergyType(item) {
+  queryParams.value.enername = item.enername;
+  queryParams.value.muid = item.muid;
+}
+function handleAnalysisType(analysisType) {
+  queryParams.value.analysisType = analysisType;
+  getList();
+}
+// 鑳借�楀姣斿垎鏋�-鍖哄煙鑳借�楀垎鏋�-鍒楄〃
+function getList() {
+  if (echarts.getInstanceByDom(document.getElementById("Chart1"))) {
+    echarts.dispose(document.getElementById("Chart1"));
+  }
+  const myChart1 = echarts.init(document.getElementById("Chart1"));
+  loading.value = true;
+  listRegion(
+    proxy.addDateRange({
+      ...queryParams.value,
+    })
+  ).then((res) => {
+    if (!!res.code && res.code == 200) {
+      loading.value = false;
+      let xdata = [];
+      let yvalue = [];
+      let ycompareValue = [];
+      let yqoq = [];
+      if (!!res.data.chartDataList) {
+        res.data.chartDataList.map((item) => {
+          xdata.push(
+            proxy
+              .dayjs(item.xdata)
+              .format(
+                queryParams.value.timeType == "YEAR"
+                  ? "MM鏈�"
+                  : queryParams.value.timeType == "MONTH"
+                  ? "DD鏃�"
+                  : "HH鏃�"
+              )
+          );
+          yvalue.push(!!item.yvalue ? item.yvalue : 0);
+          ycompareValue.push(!!item.ycompareValue ? item.ycompareValue : 0);
+          yqoq.push(!!item.yqoq ? item.yqoq : 0);
+        });
+      }
+      setTimeout(() => {
+        myChart1.setOption({
+          color: ["#2979ff", "#19be6b", "#ff9900", "#fa3534"],
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          grid: {
+            top: "45",
+            left: "7%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          xAxis: {
+            type: "category",
+            axisPointer: {
+              type: "shadow",
+            },
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: xdata,
+          },
+          yAxis: [
+            {
+              type: "value",
+              name:
+                "鑰�" +
+                queryParams.value.enername +
+                "閲�" +
+                (queryParams.value.analysisType == "YOY" ? "鍚屾瘮" : "鐜瘮") +
+                "(鍗曚綅" +
+                queryParams.value.muid +
+                ")",
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+            {
+              type: "value",
+              name: "鍗曚綅%",
+              alignTicks: true,
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              axisTick: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+          ],
+          series: [
+            {
+              name: "鏈湡鍊�",
+              type: "bar",
+              barWidth: "16",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + queryParams.value.muid;
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: yvalue,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+            {
+              name: "鍚屾湡鍊�",
+              type: "bar",
+              barWidth: "16",
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + queryParams.value.muid;
+                },
+              },
+              itemStyle: {
+                borderRadius: [15, 15, 0, 0],
+              },
+              data: ycompareValue,
+              markPoint: {
+                data: [
+                  { type: "max", name: "Max" },
+                  { type: "min", name: "Min" },
+                ],
+              },
+            },
+            {
+              name: queryParams.value.analysisType == "YOY" ? "鍚屾瘮" : "鐜瘮",
+              type: "line",
+              yAxisIndex: 1,
+              symbol: "none", // 璁剧疆涓� 'none' 鍘绘帀鍦嗙偣
+              tooltip: {
+                valueFormatter: function (value) {
+                  return value + "%";
+                },
+              },
+              data: yqoq,
+            },
+          ],
+        });
+      }, 100);
+      regionList.value = !!res.data.dataList ? res.data.dataList : [];
+      window.addEventListener("resize", () => {
+        myChart1.resize();
+      },{passive: true});
+    }
+  });
+}
+// 鑳借�楀姣斿垎鏋�-鍖哄煙鑳借�楀垎鏋�-鎼滅储
+function handleQuery() {
+  getList();
+}
+// 鑳借�楀姣斿垎鏋�-鍖哄煙鑳借�楀垎鏋�-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleTimeType(period.value[0].value);
+  queryParams.value.energyType = energyTypeList.value[0].enersno;
+  queryParams.value.enername = energyTypeList.value[0].enername;
+  queryParams.value.muid = energyTypeList.value[0].muid;
+  queryParams.value.analysisType = "YOY";
+  handleQuery();
+}
+
+// 鑳借�楀姣斿垎鏋�-鍖哄煙鑳借�楀垎鏋�-瀵煎嚭
+function handleExport() {
+  proxy.download(
+    "consumptionanalysis/getByArea/export",
+    {
+      ...queryParams.value,
+      ...query.value,
+    },
+    `${queryParams.value.nodeName}-缁煎悎鑳借�楃粺璁″垎鏋愯〃_${new Date().getTime()}.xlsx`
+  );
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/energyconservation/policyrule/components/EditModal copy.vue b/zhitan-vue/src/views/energyconservation/policyrule/components/EditModal copy.vue
new file mode 100644
index 0000000..fc5f899
--- /dev/null
+++ b/zhitan-vue/src/views/energyconservation/policyrule/components/EditModal copy.vue
@@ -0,0 +1,92 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="600" @close="handleClose">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="120px" v-loading="loading">
+            <el-form-item label="鏂囦欢鏍囬" prop="limitName">
+                <el-input v-model="form.value1" placeholder="璇疯緭鍏ユ枃浠舵爣棰�" />
+            </el-form-item>
+            <el-form-item label="鏂囦欢绫诲埆">
+                <el-select v-model="form.value2" placeholder="璇烽�夋嫨">
+                    <el-option v-for="item in 6" :key="item" :label="item" :value="item">
+                    </el-option>
+                </el-select>
+            </el-form-item>
+
+        </el-form>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+            <el-button @click="handleClose">鍙� 娑�</el-button>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+const { proxy } = getCurrentInstance();
+
+
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+    value1: '',
+    value2: '',
+})
+let emit = defineEmits(['get-list'])
+const formRules = {
+    value1: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ枃浠舵爣棰�" }],
+    value2: [{ required: true, trigger: "change", message: "璇烽�夋嫨鏂囦欢绫诲瀷" }],
+}
+
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            // loading.value = true;
+            // let obj = form.value.id ? alarmEdit(form.value) : alarmAdd(form.value)
+            // obj.then((res) => {
+            //     if (res.code == 200) {
+            //         proxy.$modal.msgSuccess(res.message);
+            //         emit('get-list')
+            //     } else {
+            //         proxy.$modal.msgError(res.message);
+            //     }
+
+            // }).catch((err) => {
+
+            // }).finally(() => {
+            //     handleClose()
+            // });
+        }
+    })
+}
+
+
+
+
+
+function handleOpen(row) {
+    if (row && row.id) {
+        title.value = "缂栬緫鏀跨瓥娉曡"
+        form.value = JSON.parse(JSON.stringify(row))
+    } else {
+        title.value = "娣诲姞鏀跨瓥娉曡"
+    }
+    visible.value = true
+}
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        value1: '',
+        value2: '',
+
+    }
+}
+
+defineExpose({ handleOpen })
+
+</script>
+
+
+
+<style lang="scss" scoped></style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/energyconservation/policyrule/components/EditModal.vue b/zhitan-vue/src/views/energyconservation/policyrule/components/EditModal.vue
new file mode 100644
index 0000000..fc5f899
--- /dev/null
+++ b/zhitan-vue/src/views/energyconservation/policyrule/components/EditModal.vue
@@ -0,0 +1,92 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="600" @close="handleClose">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="120px" v-loading="loading">
+            <el-form-item label="鏂囦欢鏍囬" prop="limitName">
+                <el-input v-model="form.value1" placeholder="璇疯緭鍏ユ枃浠舵爣棰�" />
+            </el-form-item>
+            <el-form-item label="鏂囦欢绫诲埆">
+                <el-select v-model="form.value2" placeholder="璇烽�夋嫨">
+                    <el-option v-for="item in 6" :key="item" :label="item" :value="item">
+                    </el-option>
+                </el-select>
+            </el-form-item>
+
+        </el-form>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+            <el-button @click="handleClose">鍙� 娑�</el-button>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+const { proxy } = getCurrentInstance();
+
+
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+    value1: '',
+    value2: '',
+})
+let emit = defineEmits(['get-list'])
+const formRules = {
+    value1: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ枃浠舵爣棰�" }],
+    value2: [{ required: true, trigger: "change", message: "璇烽�夋嫨鏂囦欢绫诲瀷" }],
+}
+
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            // loading.value = true;
+            // let obj = form.value.id ? alarmEdit(form.value) : alarmAdd(form.value)
+            // obj.then((res) => {
+            //     if (res.code == 200) {
+            //         proxy.$modal.msgSuccess(res.message);
+            //         emit('get-list')
+            //     } else {
+            //         proxy.$modal.msgError(res.message);
+            //     }
+
+            // }).catch((err) => {
+
+            // }).finally(() => {
+            //     handleClose()
+            // });
+        }
+    })
+}
+
+
+
+
+
+function handleOpen(row) {
+    if (row && row.id) {
+        title.value = "缂栬緫鏀跨瓥娉曡"
+        form.value = JSON.parse(JSON.stringify(row))
+    } else {
+        title.value = "娣诲姞鏀跨瓥娉曡"
+    }
+    visible.value = true
+}
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        value1: '',
+        value2: '',
+
+    }
+}
+
+defineExpose({ handleOpen })
+
+</script>
+
+
+
+<style lang="scss" scoped></style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/energyconservation/policyrule/policyRule.vue b/zhitan-vue/src/views/energyconservation/policyrule/policyRule.vue
new file mode 100644
index 0000000..f6dbeb1
--- /dev/null
+++ b/zhitan-vue/src/views/energyconservation/policyrule/policyRule.vue
@@ -0,0 +1,123 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="120px">
+                <el-form-item label="鏂囦欢绫诲埆">
+                    <el-select v-model="queryParams.value1" placeholder="璇烽�夋嫨">
+                        <el-option v-for="item in 6" :key="item" :label="item" :value="item">
+                        </el-option>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="鏂囦欢鏍囬">
+                    <el-input v-model="queryParams.value2" placeholder="璇疯緭鍏ユ枃浠舵爣棰�" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="table-box">
+            <div class="mt20 mb20">
+                <el-button type="primary" icon="plus" @click="handleAdd">鏂板</el-button>
+                <!-- <el-button type="primary" icon="Delete">鍒犻櫎</el-button> -->
+            </div>
+            <el-table :data="tableData" v-loading="loading">
+                <el-table-column prop="value1" label="鏂囦欢鏍囬" show-overflow-tooltip align="center" />
+                <el-table-column prop="value2" label="鏂囦欢绫诲埆" show-overflow-tooltip align="center" />
+                <el-table-column prop="value3" label="鍗板彂鏃堕棿" show-overflow-tooltip align="center" />
+                <el-table-column label="鎿嶄綔" width="300" align="center">
+                    <template #default="scope">
+                        <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row)">
+                            淇敼
+                        </el-button>
+                        <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                            鍒犻櫎
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                v-model:limit="queryParams.pageSize" @pagination="getList" />
+
+        </div>
+        <EditModal ref="editModalRef" @get-list="getList" />
+    </div>
+</template>
+
+<script setup>
+import EditModal from './components/EditModal'
+
+let { proxy } = getCurrentInstance()
+let loading = ref(false);
+let total = ref(2);
+let tableData = ref([
+    { id: 1, value1: '1111', value2: 'value2', value3: '2024骞�10鏈�17鏃�11:15:39' },
+    { id: 2, value1: '222', value2: '222', value3: '2024骞�10鏈�17鏃�11:16:39' }])
+let queryParams = ref({
+    value1: '',
+    value2: '',
+    pageNum: 1,
+    pageSize: 10,
+})
+
+function getList() {
+    // loading.value = true
+    // alarmList(queryParams.value).then(res => {
+    //     console.log(res.rows)
+    //     tableData.value = res.rows
+    //     total.value = res.total
+    //     loading.value = false
+    // })
+}
+
+getList()
+
+let editModalRef = ref('')
+function handleAdd(row) {
+    if (editModalRef.value) {
+        editModalRef.value.handleOpen(row)
+    }
+
+}
+
+function handleDel(row) {
+    // proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+    //     return alarmDel(row.id);
+    // }).then(() => {
+    //     getList();
+    //     proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    // }).catch(() => { });
+}
+
+function handleQuery() {
+    queryParams.value.pageNum = 1
+    getList()
+
+}
+
+function resetQuery() {
+    queryParams.value = {
+        value1: '',
+        value2: '',
+        pageNum: 1,
+        pageSize: 10,
+    }
+    getList()
+}
+
+
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+
+.header-box {
+    :deep .el-form-item__content {
+        color: #fff;
+        font-size: 16px;
+    }
+
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/energyconservation/projectmanage/projectmanage/components/EditModal.vue b/zhitan-vue/src/views/energyconservation/projectmanage/projectmanage/components/EditModal.vue
new file mode 100644
index 0000000..b0068a9
--- /dev/null
+++ b/zhitan-vue/src/views/energyconservation/projectmanage/projectmanage/components/EditModal.vue
@@ -0,0 +1,124 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="600" @close="handleClose">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="120px" v-loading="loading">
+            <el-form-item label="椤圭洰鍚嶇О" prop="value1">
+                <el-input v-model="form.value1" placeholder="璇疯緭鍏ラ」鐩悕绉�" />
+            </el-form-item>
+            <el-form-item label="鑺傝兘璁″垝" prop="value2">
+                <el-input v-model="form.value2" placeholder="璇疯緭鍏ヨ妭鑳借鍒�" />
+            </el-form-item>
+            <el-form-item label="鑺傝兘鐩爣" prop="value3">
+                <el-input v-model="form.value3" placeholder="璇疯緭鍏ヨ妭鑳界洰鏍�" />
+            </el-form-item>
+            <el-form-item label="寮�濮嬫椂闂�" prop="value4">
+                <el-date-picker v-model="form.value4" type="date" placeholder="璇烽�夋嫨寮�濮嬫椂闂�" format="YYYY-MM-DD"
+                    date-format="YYYY/MM/DD" />
+            </el-form-item>
+            <el-form-item label="缁撴潫鏃堕棿" prop="value5">
+                <el-date-picker v-model="form.value5" type="date" placeholder="璇烽�夋嫨缁撴潫鏃堕棿" format="YYYY-MM-DD"
+                    date-format="YYYY/MM/DD" />
+            </el-form-item>
+            <el-form-item label="璐熻矗浜�" prop="value6">
+                <el-input v-model="form.value6" placeholder="璇疯緭鍏ヨ礋璐d汉" />
+            </el-form-item>
+            <el-form-item label="鍒跺畾鏃舵" prop="value7">
+                <el-date-picker v-model="form.value7" type="date" placeholder="璇烽�夋嫨鍒跺畾鏃舵" format="YYYY-MM-DD"
+                    date-format="YYYY/MM/DD" />
+            </el-form-item>
+        </el-form>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+            <el-button @click="handleClose">鍙� 娑�</el-button>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+import { alarmAdd, alarmEdit } from '@/api/businessConfiguration/businessConfiguration'
+const { proxy } = getCurrentInstance();
+let props = defineProps(['alarmTypeList', 'operatorList'])
+
+
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+    value1: null,
+    value2: null,
+    value3: null,
+    value4: null,
+    value5: null,
+    value6: null,
+    value7: null,
+
+})
+let emit = defineEmits(['getList'])
+const formRules = {
+    value1: [{ required: true, trigger: "blur", message: "璇疯緭鍏ラ」鐩悕绉�" }],
+    value2: [{ required: true, trigger: "blur", message: "璇疯緭鍏ヨ妭鑳借鍒�" }],
+    value3: [{ required: true, trigger: "blur", message: "璇疯緭鍏ヨ妭鑳界洰鏍�" }],
+    value4: [{ required: true, trigger: "blur", message: "璇烽�夋嫨寮�濮嬫椂闂�" }],
+    value5: [{ required: true, trigger: "blur", message: "璇烽�夋嫨缁撴潫鏃堕棿" }],
+    value6: [{ required: true, trigger: "blur", message: "璇疯緭鍏ヨ礋璐d汉" }],
+    value7: [{ required: true, trigger: "blur", message: "璇烽�夋嫨鍒跺畾鏃舵" }],
+
+}
+
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            // loading.value = true;
+            // let obj = form.value.id ? alarmEdit(form.value) : alarmAdd(form.value)
+            // obj.then((res) => {
+            //     if (res.code == 200) {
+            //         proxy.$modal.msgSuccess(res.message);
+            //         emit('getList')
+            //     } else {
+            //         proxy.$modal.msgError(res.message);
+            //     }
+
+            // }).catch((err) => {
+
+            // }).finally(() => {
+            //     handleClose()
+            // });
+        }
+    })
+}
+
+
+
+
+
+function handleOpen(row) {
+    if (row && row.id) {
+        title.value = "缂栬緫鑺傝兘椤圭洰绠$悊"
+        form.value = JSON.parse(JSON.stringify(row))
+    } else {
+        title.value = "娣诲姞鑺傝兘椤圭洰绠$悊"
+    }
+    visible.value = true
+}
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        value1: null,
+        value2: null,
+        value3: null,
+        value4: null,
+        value5: null,
+        value6: null,
+        value7: null,
+    }
+}
+
+defineExpose({ handleOpen })
+
+</script>
+
+
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/energyconservation/projectmanage/projectmanage/projectManage.vue b/zhitan-vue/src/views/energyconservation/projectmanage/projectmanage/projectManage.vue
new file mode 100644
index 0000000..164cb19
--- /dev/null
+++ b/zhitan-vue/src/views/energyconservation/projectmanage/projectmanage/projectManage.vue
@@ -0,0 +1,135 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="120px">
+                <el-form-item label="椤圭洰鍚嶇О">
+                    <el-input v-model="queryParams.value1" placeholder="璇疯緭鍏ラ」鐩悕绉�" />
+                </el-form-item>
+                <el-form-item label="缁熻鏃堕棿">
+                    <el-date-picker v-model="queryParams.value2" type="daterange" start-placeholder="閫夋嫨寮�濮嬫椂闂�"
+                        end-placeholder="閫夋嫨缁撴潫鏃堕棿" format="YYYY-MM-DD" date-format="YYYY/MM/DD" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="table-box">
+            <div class="mt20 mb20">
+                <el-button type="primary" icon="plus" @click="handleAdd">鏂板</el-button>
+                <el-button type="primary" icon="Download" @click="handleAdd"> 瀵煎嚭 </el-button>
+            </div>
+            <el-table :data="tableData" v-loading="loading">
+                <el-table-column prop="value1" label="椤圭洰鍚嶇О" show-overflow-tooltip align="center" />
+                <el-table-column prop="value2" label="鑺傝兘璁″垝" show-overflow-tooltip align="center" />
+                <el-table-column prop="value3" label="鑺傝兘鐩爣" show-overflow-tooltip align="center" />
+                <el-table-column prop="value4" label="寮�濮嬫椂闂�" show-overflow-tooltip align="center" />
+                <el-table-column prop="value5" label="缁撴潫鏃堕棿" show-overflow-tooltip align="center" />
+                <el-table-column prop="value6" label="璐熻矗浜�" show-overflow-tooltip align="center" />
+                <el-table-column prop="value7" label="鍒跺畾鏃舵" show-overflow-tooltip align="center" />
+                <el-table-column prop="value8" label="鍒涘缓浜�" show-overflow-tooltip align="center" />
+
+                <el-table-column label="鎿嶄綔" width="300" align="center">
+                    <template #default="scope">
+                        <el-button link type="primary" icon="Files" @click=" ">
+                            闄勪欢
+                        </el-button>
+                        <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row)">
+                            淇敼
+                        </el-button>
+                        <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                            鍒犻櫎
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                v-model:limit="queryParams.pageSize" @pagination="getList" />
+
+        </div>
+        <edit-modal ref="EditModalRef" @getList="getList" />
+    </div>
+</template>
+
+<script setup>
+import EditModal from './components/EditModal.vue'
+
+
+let { proxy } = getCurrentInstance()
+let loading = ref(false);
+let total = ref(0);
+let tableData = ref([
+    {id:1, value1: '1', value2: '2', value3: '3', value4: '4', value5: '5', value6: '6', value7: '7', value8: '8' },
+    { id:2, value1: '1', value2: '2', value3: '3', value4: '4', value5: '5', value6: '6', value7: '7', value8: '8' },
+    { id:3, value1: '1', value2: '2', value3: '3', value4: '4', value5: '5', value6: '6', value7: '7', value8: '8' },
+    { id:4, value1: '1', value2: '2', value3: '3', value4: '4', value5: '5', value6: '6', value7: '7', value8: '8' },
+])
+let queryParams = ref({
+    value1: '',
+    value2: [],
+    pageNum: 1,
+    pageSize: 10,
+})
+
+function getList() {
+    // loading.value = true
+    // alarmList(queryParams.value).then(res => {
+    //     console.log(res.rows)
+    //     tableData.value = res.rows
+    //     total.value = res.total
+    //     loading.value = false
+    // })
+}
+
+getList()
+
+
+let EditModalRef = ref('')
+function handleAdd(row) {
+    if (EditModalRef.value) {
+        EditModalRef.value.handleOpen(row)
+    }
+
+}
+
+function handleDel(row) {
+    // proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+    //     return alarmDel(row.id);
+    // }).then(() => {
+    //     getList();
+    //     proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    // }).catch(() => { });
+}
+
+function handleQuery() {
+    queryParams.value.pageNum = 1
+    getList()
+
+}
+
+function resetQuery() {
+    queryParams.value = {
+        value1: '',
+        value2: [],
+        pageNum: 1,
+        pageSize: 10,
+    }
+    getList()
+}
+
+
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+
+.header-box {
+    :deep .el-form-item__content {
+        color: #fff;
+        font-size: 16px;
+    }
+
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/benchmarkinganalysis.vue b/zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/benchmarkinganalysis.vue
new file mode 100644
index 0000000..07a3feb
--- /dev/null
+++ b/zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/benchmarkinganalysis.vue
@@ -0,0 +1,234 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+                <el-form-item label="鏈堜唤" prop="index1">
+                    <el-date-picker v-model="form.index1" :clearable="false" type='month' format='YYYY-MM'
+                        value-format="YYYY-MM " style="width: 100%" />
+                </el-form-item>
+                <el-form-item label="宸ュ簭/宸ヨ壓" prop="index2">
+                    <el-select v-model="form.index2"> <el-option v-for="i in 6" :key="i" :label="i" /></el-select>
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="table-box">
+            <div class="table-header-box">
+                <div v-for=" (item, i) in tableHeaderData" :key="i">
+                    <div class="cell">{{ item }}</div>
+                </div>
+            </div>
+            <div class="table-content-box">
+                <div class="content-ul" v-for=" items in ListData" :key="items.id">
+                    <div class="content-li"> {{ items.name }}</div>
+
+                    <div class="content-li-box">
+                        <template v-if="items.ListData.length > 0">
+                            <div v-for=" (item, i)  in items.ListData" class="data-list-ul" :key="i">
+                                <div class="data-list-li ">{{ item.index2 }}</div>
+                                <div class="data-list-li ">{{ item.index3 }}</div>
+                                <div class="data-list-li ">{{ item.index4 }}</div>
+                                <div class="data-list-li ">{{ item.index5 }}</div>
+                            </div>
+                        </template>
+                    </div>
+
+                    <div class="content-li"> {{ items.index6 }}</div>
+                    <div class="content-li"> {{ items.index7 }}</div>
+                    <div class="content-li"> {{ items.index8 }}</div>
+                    <div class="content-li"> {{ items.index9 }}</div>
+                    <div class="content-li"> {{ items.index10 }}</div>
+                    <div class="content-li"> {{ items.index11 }}</div>
+                    <div class="content-li"> {{ items.index12 }}</div>
+                    <div class="content-li"> {{ items.index13 }}</div>
+                    <div class="content-li">
+                        <el-button link type="primary" icon="Edit" @click="handleInfo(items)">
+                            璇︽儏
+                        </el-button>
+                    </div>
+                </div>
+            </div>
+
+        </div>
+        <InfoModal ref="InfoModalRef" />
+    </div>
+</template>
+<script setup>
+import InfoModal from './components/InfoModal.vue'
+let form = ref({ index1: null, index2: null })
+let loading = ref(false);
+
+// let tableData = ref([ ])
+function handleQuery() {
+    queryParams.value.pageNum = 1
+    getList()
+}
+
+function resetQuery() {
+    proxy.resetForm("queryRef");
+    handleQuery();
+}
+
+function getList() {
+}
+
+
+let InfoModalRef = ref('')
+function handleInfo(row) {
+    if (InfoModalRef.value) {
+        InfoModalRef.value.handleOpen(row)
+    }
+
+}
+
+const tableHeaderData = ['宸ュ簭/宸ヨ壓', '鑳芥簮绉嶇被', '瀹炵墿閲�', '鎶樻爣绯绘暟', '鏍囩叅閲�(tce)', '宸ヨ壓鑳芥簮娑堣垂閲�(tce)', '缁煎悎鑳芥簮娑堣垂閲�(tce)', '浜ч噺(t)', '宸ヨ壓鍗曚綅浜у搧鑳借��(kgce/t)', '缁煎悎鍗曚綅浜у搧鑳借��(kgce/t)', '涓�绾�', '浜岀骇', '涓夌骇', '鎿嶄綔']
+const ListData = ref([
+    {
+        id: 1,
+        name: '閾滃喍鐐煎伐鑹�',
+        ListData: [
+            { index2: '鐢碉紙涓噆Wh锛�', index3: "1841.286766", index4: '1.229', index5: '2262.94' },
+            { index2: '涓�鑸儫鐓� (t)', index3: "61.2", index4: '0.7143', index5: '132.23', },
+            { index2: '鏃犵儫鐓� (t)', index3: "181.2", index4: '0.7143', index5: '132.23', },
+            { index2: '鍏朵粬鐒﹀寲浜у搧 (t)', index3: "11.2", index4: '0.143', index5: '12.23', },
+            { index2: '鐢碉紙涓噆Wh锛�', index3: "1841.286766", index4: '1.229', index5: '2262.94' },
+            { index2: '鏌存补(t)', index3: "1821.2", index4: '1.73143', index5: '1332.23' },
+            { index2: '澶╃劧姘旓紙涓噈3))', index3: "981.22", index4: '10.143', index5: '32.23' },
+        ],
+        index6: '3569.26',
+        index7: '3702.53',
+        index8: '17156.059',
+        index9: '208.05',
+        index10: '215.81',
+        index11: '鈮�210',
+        index12: '鈮�230',
+        index13: '鈮�340',
+    },
+    {
+        id: 2,
+        name: '绮楅摐宸ヨ壓',
+        ListData: [],
+        index6: '3569.26',
+        index7: '3702.53',
+        index8: '17156.059',
+        index9: '208.05',
+        index10: '215.81',
+        index11: '鈮�210',
+        index12: '鈮�230',
+        index13: '鈮�340',
+    }
+
+]);
+
+
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+ul,
+li {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+}
+
+.table-header-box {
+    width: 100%;
+    background-color: #1d3778 !important;
+    display: flex;
+    color: #fff;
+    font-weight: 500;
+    font-size: 16px;
+    font-family: OPPOSans, OPPOSans;
+    padding: 8px 0;
+    align-items: center;
+
+    &>div {
+        width: calc(100% / 14);
+        word-break: break-word;
+        background-color: #1d3778 !important;
+        border-radius: 0px 0px 0px 0px;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: 500;
+        font-size: 16px;
+        color: #ffffff;
+        border-bottom: none !important;
+        display: flex;
+        align-items: center;
+        text-align: center;
+
+        .cell {
+            width: 100%;
+            box-sizing: border-box;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: normal;
+            word-break: break-all;
+            line-height: 23px;
+            padding: 0 12px;
+        }
+
+
+    }
+}
+
+.table-content-box {
+    color: #fff;
+    padding: 8px 0;
+    width: 100%;
+
+    .content-ul {
+        display: flex;
+        align-items: center;
+        width: 100%;
+
+        &:nth-child(2n) {
+            background: #141E4A;
+        }
+
+        .content-li {
+            text-align: center;
+            width: calc(100% / 14);
+            height: 48px !important;
+            line-height: 48px;
+
+
+        }
+
+        .content-li-box {
+            text-align: center;
+
+            width: calc((100% / 14) * 4);
+
+            .data-list-ul {
+                width: 100%;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                height: 48px !important;
+                line-height: 48px;
+
+                &:nth-child(2n) {
+                    background: #141E4A;
+                }
+
+                .data-list-li {
+                    width: calc(100% /4);
+                    white-space: nowrap;
+                    /* 绂佹鏂囨湰鎹㈣ */
+                    overflow: hidden;
+                    /* 闅愯棌瓒呭嚭瀹瑰櫒鐨勬枃鏈� */
+                    text-overflow: ellipsis;
+                    /* 浣跨敤鐪佺暐鍙疯〃绀烘孩鍑虹殑鏂囨湰 */
+                }
+            }
+
+
+        }
+    }
+}
+</style>
diff --git a/zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/benchmarkinganalysisTable.vue b/zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/benchmarkinganalysisTable.vue
new file mode 100644
index 0000000..c979842
--- /dev/null
+++ b/zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/benchmarkinganalysisTable.vue
@@ -0,0 +1,228 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+                <el-form-item label="缂栫爜" prop="code">
+                    <el-input v-model="form.code" placeholder="璇疯緭鍏ョ紪鐮�" />
+                </el-form-item>
+                <el-form-item label="鍣ㄥ叿鍚嶇О" prop="meterName">
+                    <el-input v-model="form.meterName" placeholder="璇疯緭鍏ュ櫒鍏峰悕绉�" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="table-box">
+            <el-table :data="tableData" v-loading="loading" :span-method="mergeCells">
+                <el-table-column prop="index1" label="宸ュ簭/宸ヨ壓" show-overflow-tooltip align="center" />
+                <el-table-column prop="index2" label="鑳芥簮绉嶇被" show-overflow-tooltip align="center" />
+                <el-table-column prop="index3" label="瀹炵墿閲�" show-overflow-tooltip align="center" />
+                <el-table-column prop="index4" label="鎶樻爣绯绘暟" show-overflow-tooltip align="center" />
+                <el-table-column prop="index5" label="鏍囩叅閲�(tce)" show-overflow-tooltip align="center" />
+                <el-table-column prop="index6" label="宸ヨ壓鑳芥簮娑堣垂閲�(tce)" show-overflow-tooltip align="center" />
+                <el-table-column prop="index7" label="缁煎悎鑳芥簮娑堣垂閲�(tce)" show-overflow-tooltip align="center" />
+                <el-table-column prop="index8" label="浜ч噺(t)" show-overflow-tooltip align="center" />
+                <el-table-column prop="index9" label="宸ヨ壓鍗曚綅浜у搧鑳借��(kgce/t)" show-overflow-tooltip align="center" />
+                <el-table-column prop="index10" label="缁煎悎鍗曚綅浜у搧鑳借��(kgce/t)" show-overflow-tooltip align="center" />
+                <el-table-column prop="index11" label="涓�绾�" show-overflow-tooltip align="center" />
+                <el-table-column prop="index12" label="浜岀骇" show-overflow-tooltip align="center" />
+                <el-table-column prop="index13" label="涓夌骇" show-overflow-tooltip align="center" /> -->
+                <el-table-column prop="active" label="鎿嶄綔" width="80" align="center">
+                    <template #default="scope">
+                        <el-button link type="primary" icon="Edit" @click="handleInfo(scope.row)">
+                            璇︽儏
+                        </el-button>
+
+                    </template>
+                </el-table-column>
+            </el-table>
+        </div>
+        <InfoModal ref="InfoModalRef" />
+    </div>
+</template>
+<script setup>
+import InfoModal from './components/InfoModal.vue'
+let form = ref({ code: null, meterName: null })
+let loading = ref(false);
+
+// let tableData = ref([ ])
+
+function handleQuery() {
+    queryParams.value.pageNum = 1
+    getList()
+}
+
+function resetQuery() {
+    proxy.resetForm("queryRef");
+    handleQuery();
+}
+
+function getList() {
+}
+
+
+let InfoModalRef = ref('')
+function handleInfo(row) {
+    if (InfoModalRef.value) {
+        InfoModalRef.value.handleOpen(row)
+    }
+
+}
+const tableData = ref([
+    {
+        id: 1,
+        index1: "閾滃喍鐐煎伐鑹�",
+        index2: '鐢碉紙涓噆Wh锛�',
+        index3: "1841.286766",
+        index4: '1.229',
+        index5: '2262.94',
+        index6: '3569.26',
+        index7: '3702.53',
+        index8: '17156.059',
+        index9: '208.05',
+        index10: '215.81',
+        index11: '鈮�210',
+        index12: '鈮�230',
+        index13: '鈮�340',
+    },
+    {
+        id: 2,
+        index1: "閾滃喍鐐煎伐鑹�",
+        index2: '涓�鑸儫鐓� (t)',
+        index3: "61.2",
+        index4: '0.7143',
+        index5: '132.23',
+        index6: '3569.26',
+        index7: '3702.53',
+        index8: '17156.059',
+        index9: '208.05',
+        index10: '215.81',
+        index11: '鈮�210',
+        index12: '鈮�230',
+        index13: '鈮�340',
+    },
+    {
+        id: 3,
+        index1: "閾滃喍鐐煎伐鑹�",
+        index2: '鏃犵儫鐓� (t)',
+        index3: "181.2",
+        index4: '0.7143',
+        index5: '132.23',
+        index6: '3569.26',
+        index7: '3702.53',
+        index8: '17156.059',
+        index9: '208.05',
+        index10: '215.81',
+        index11: '鈮�210',
+        index12: '鈮�230',
+        index13: '鈮�340',
+    },
+    {
+        id: 4,
+        index1: "閾滃喍鐐煎伐鑹�",
+        index2: '鍏朵粬鐒﹀寲浜у搧 (t)',
+        index3: "11.2",
+        index4: '0.143',
+        index5: '12.23',
+        index6: '3569.26',
+        index7: '3702.53',
+        index8: '17156.059',
+        index9: '208.05',
+        index10: '215.81',
+        index11: '鈮�210',
+        index12: '鈮�230',
+        index13: '鈮�340',
+    },
+    {
+        id: 5,
+        index1: "閾滃喍鐐煎伐鑹�",
+        index2: '鏌存补(t)',
+        index3: "1821.2",
+        index4: '1.73143',
+        index5: '1332.23',
+        index6: '3569.26',
+        index7: '3702.53',
+        index8: '17156.059',
+        index9: '208.05',
+        index10: '215.81',
+        index11: '鈮�210',
+        index12: '鈮�230',
+        index13: '鈮�340',
+    },
+    {
+        id: 6,
+        index1: "閾滃喍鐐煎伐鑹�",
+        index2: '澶╃劧姘旓紙涓噈3))',
+        index3: "981.22",
+        index4: '10.143',
+        index5: '32.23',
+        index6: '3569.26',
+        index7: '3702.53',
+        index8: '17156.059',
+        index9: '208.05',
+        index10: '215.81',
+        index11: '鈮�210',
+        index12: '鈮�230',
+        index13: '鈮�340',
+    },
+    {
+        id: 6,
+        index1: "绮楅摐宸ヨ壓",
+        index2: '鏈煡',
+        index3: "98",
+        index4: '143',
+        index5: '23',
+        index6: '6926',
+        index7: '3753',
+        index8: '17159',
+        index9: '2085',
+        index10: '21.81',
+        index11: '鈮�10',
+        index12: '鈮�23',
+        index13: '鈮�30',
+    },
+]);
+
+const mergeCells = ({ row, column, rowIndex, columnIndex }) => {
+    console.log(111, row, column, rowIndex, columnIndex)
+
+    if (columnIndex === 0 || columnIndex === 5 || columnIndex === 6 || columnIndex === 7 || columnIndex === 8 || columnIndex === 9 || columnIndex === 10 || columnIndex === 11 || columnIndex === 12 || columnIndex === 13) { // 鍙鐞� index1, name, age 鍒�
+        let currentId = null;
+        let rowSpanCount = 1;
+
+        // 閬嶅巻鏁版嵁锛岃绠楀悎骞剁殑璺ㄥ害
+        tableData.value.forEach((item, index) => {
+            if (index === rowIndex) {
+                // 褰撳墠琛岋紝涓嶆敼鍙� rowSpanCount
+            } else if (item.index1 === row.index1) {
+                // 濡傛灉鎵惧埌鐩稿悓 id 鐨勮锛屽鍔� rowSpanCount
+                rowSpanCount++;
+            } else if (currentId !== null && item.index1 !== currentId) {
+                // 濡傛灉褰撳墠 id 鏀瑰彉涓斾箣鍓嶅凡鏈夎绠楄繃鐨� id锛屽垯缁撴潫寰幆锛堝洜涓烘垜浠凡缁忕煡閬撲簡褰撳墠琛岀殑鍚堝苟璺ㄥ害锛�
+                return false; // 娉ㄦ剰锛氳繖閲屽疄闄呬笂涓嶄細鍋滄 forEach 寰幆锛屼絾鍙互浣滀负鏍囪鐐�
+            }
+
+            // 鏇存柊褰撳墠 id锛堜粎鍦ㄦ壘鍒版柊琛屾垨棣栨杩唬鏃讹級
+            if (index === 0 || item.index1 !== tableData.value[index - 1].index1) {
+                currentId = item.index1;
+            }
+        });
+
+        // 濡傛灉鏄涓�琛屼笖 id 鐩稿悓锛岃繑鍥炲悎骞惰法搴︼紱鍚﹀垯杩斿洖 [0, 1] 浠ラ殣钘忓叾浠栬鐨勫崟鍏冩牸
+        if (rowIndex === tableData.value.findIndex(item => item.index1 === row.index1)) {
+            return [rowSpanCount, 1];
+        } else {
+            return [0, 1];
+        }
+    } else {
+        // 鍏朵粬鍒椾笉鍚堝苟锛岃繑鍥� [1, 1]
+        return [1, 1];
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/components/InfoModal.vue b/zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/components/InfoModal.vue
new file mode 100644
index 0000000..3a89d84
--- /dev/null
+++ b/zhitan-vue/src/views/energyefficiency/benchmarkinganalysis/components/InfoModal.vue
@@ -0,0 +1,37 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="1000" @close="handleClose">
+
+    </el-dialog>
+</template>
+
+<script setup>
+const { proxy } = getCurrentInstance();
+let emit = defineEmits(['getList'])
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+
+})
+
+function handleOpen(row) {
+    title.value = "璇︽儏"
+    form.value = JSON.parse(JSON.stringify(row))
+    visible.value = true
+}
+
+
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+}
+
+
+defineExpose({ handleOpen })
+
+</script>
+
+
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/energyefficiency/benchmarkmanage/benchmarkmanage.vue b/zhitan-vue/src/views/energyefficiency/benchmarkmanage/benchmarkmanage.vue
new file mode 100644
index 0000000..aee4ffb
--- /dev/null
+++ b/zhitan-vue/src/views/energyefficiency/benchmarkmanage/benchmarkmanage.vue
@@ -0,0 +1,123 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+                <el-form-item label="鏍囨潌缂栧彿" prop="code">
+                    <el-input v-model="form.code" placeholder="璇疯緭鍏ユ爣鏉嗙紪鍙�" />
+                </el-form-item>
+                <el-form-item label="鏍囨潌绫诲瀷" prop="type">
+                    <el-select v-model="form.type" clearable>
+                        <el-option v-for="dict in benchmark_type" :key="dict.value" :label="dict.label"
+                            :value="dict.value" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="鏍囨潌绛夌骇" prop="grade">
+                    <el-select v-model="form.grade" clearable>
+                        <el-option v-for="dict in benchmark_grade" :key="dict.value" :label="dict.label"
+                            :value="dict.value" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="table-box">
+            <div class=" mt20 mb20">
+                <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+            </div>
+            <el-table :data="tableData" v-loading="loading">
+                <el-table-column prop="code" label="鏍囨潌缂栧彿" show-overflow-tooltip align="center" />
+                <el-table-column prop="type" label="鏍囨潌绫诲瀷" show-overflow-tooltip align="center"
+                    :formatter="(row, c) => proxy.selectDictLabel(benchmark_type, row.type)" />
+                <el-table-column prop="grade" label="鏍囨潌绛夌骇" show-overflow-tooltip align="center"
+                    :formatter="(row, c) => proxy.selectDictLabel(benchmark_grade, row.grade)" />
+                <el-table-column prop="value" label="鏍囨潌鍊�" show-overflow-tooltip align="center" />
+                <el-table-column prop="nationalNum" label="鍥芥爣缂栧彿" show-overflow-tooltip align="center" />
+                <el-table-column prop="createBy" label="褰曞叆浜�" show-overflow-tooltip align="center" />
+                <el-table-column prop="createTime" label="褰曞叆鏃堕棿" show-overflow-tooltip align="center" />
+                <el-table-column prop="remark" label="澶囨敞" show-overflow-tooltip align="center" />
+                <el-table-column prop="active" label="鎿嶄綔" width="150" align="center">
+                    <template #default="scope">
+                        <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row)">
+                            淇敼
+                        </el-button>
+                        <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                            鍒犻櫎
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                v-model:limit="queryParams.pageSize" @pagination="getList" />
+        </div>
+        <EditModal ref="EditModalRef" :benchmark_grade="benchmark_grade" :benchmark_type="benchmark_type"
+            @getList="getList" />
+
+    </div>
+</template>
+<script setup>
+import EditModal from './components/EditModal.vue'
+import { valueManageList, valueManageDel } from '@/api/benchmarkManage'
+let proxy = getCurrentInstance().proxy
+
+let { benchmark_grade, benchmark_type } = proxy.useDict("benchmark_grade", 'benchmark_type')
+let form = ref({
+    code: null,
+    type: null,
+    grade: null,
+})
+function handleQuery() {
+    queryParams.value.pageNum = 1
+    getList()
+}
+
+function resetQuery() {
+    queryParams.value.pageNum = 1
+    form.value = {}
+    handleQuery();
+}
+
+
+let loading = ref(false);
+let total = ref(0);
+let tableData = ref([])
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+})
+function getList(flag) {
+    loading.value = true
+    valueManageList({ ...queryParams.value, ...form.value }).then(res => {
+        tableData.value = res.rows
+        total.value = res.total
+        loading.value = false
+    })
+
+}
+
+getList()
+let EditModalRef = ref('')
+function handleAdd(row) {
+    if (EditModalRef.value) {
+        EditModalRef.value.handleOpen(row)
+    }
+
+}
+
+function handleDel(row) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+        return valueManageDel(row.id);
+    }).then(() => {
+        getList();
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    }).catch(() => { });
+}
+
+</script>
+
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/energyefficiency/benchmarkmanage/components/EditModal.vue b/zhitan-vue/src/views/energyefficiency/benchmarkmanage/components/EditModal.vue
new file mode 100644
index 0000000..e75da5a
--- /dev/null
+++ b/zhitan-vue/src/views/energyefficiency/benchmarkmanage/components/EditModal.vue
@@ -0,0 +1,129 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="800" @close="handleClose">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="120px" v-loading="loading">
+            <el-row>
+                <el-col :span="11">
+                    <el-form-item label="鏍囨潌缂栧彿" prop="code">
+                        <el-input v-model="form.code" placeholder="璇疯緭鍏ユ爣鏉嗙紪鍙�" />
+                    </el-form-item>
+
+                    <el-form-item label="鏍囨潌绫诲瀷" prop="type">
+                        <el-select v-model="form.type" clearable>
+                            <el-option v-for="dict in props.benchmark_type" :key="dict.value" :label="dict.label"
+                                :value="dict.value" />
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item label="鍥芥爣缂栧彿" prop="nationalNum">
+                        <el-input v-model="form.nationalNum" placeholder="璇疯緭鍏ュ浗鏍囩紪鍙�" />
+                    </el-form-item>
+
+                </el-col>
+                <el-col :span="11">
+                    <el-form-item label="鏍囨潌绛夌骇" prop="grade">
+                        <el-select v-model="form.grade" clearable>
+                            <el-option v-for="dict in props.benchmark_grade" :key="dict.value" :label="dict.label"
+                                :value="dict.value" />
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item label="鏍囨潌鍊�" prop="value">
+                        <el-input v-model="form.value" placeholder="璇疯緭鍏ユ爣鏉嗗��" />
+                    </el-form-item>
+                </el-col>
+            </el-row>
+            <el-row>
+                <el-col :span="22">
+                    <el-form-item label="澶囨敞" prop="remark">
+                        <el-input v-model="form.remark" placeholder="璇疯緭鍏ュ唴瀹�" type="textarea" />
+                    </el-form-item>
+                </el-col>
+            </el-row>
+        </el-form>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+            <el-button @click="handleClose">鍙� 娑�</el-button>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+const { proxy } = getCurrentInstance();
+import { valueManageAdd, valueManageEdit } from '@/api/benchmarkManage'
+let emit = defineEmits(['getList'])
+let props = defineProps(['benchmark_grade', 'benchmark_type'])
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+    code: null,
+    type: null,
+    grade: null,
+    nationalNum: null,
+    value: null,
+    remark: null
+})
+
+const formRules = ref({
+    code: [{ required: true, message: '璇疯緭鍏ユ爣鏉嗙紪鍙�', trigger: 'blur' }],
+    type: [{ required: true, message: '璇烽�夋嫨鏍囨潌绫诲瀷', trigger: 'change' }],
+    grade: [{ required: true, message: '璇烽�夋嫨鏍囨潌绛夌骇', trigger: 'change' }],
+    nationalNum: [{ required: true, message: '璇疯緭鍏ュ浗鏍囩紪鍙�', trigger: 'blur' }],
+    value: [{ required: true, message: '璇疯緭鍏ユ爣鏉嗗��', trigger: 'blur' }],
+});
+
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            loading.value = true;
+            let obj = form.value.id ? valueManageEdit(form.value) : valueManageAdd(form.value)
+            obj.then((res) => {
+                if (res.code == 200) {
+                    proxy.$modal.msgSuccess(res.msg);
+                    emit('getList')
+                } else {
+                    proxy.$modal.msgError(res.msg);
+                }
+
+            }).catch((err) => {
+            }).finally(() => {
+                handleClose()
+            });
+        }
+    })
+}
+
+
+function handleOpen(row) {
+    if (row && row.id) {
+        title.value = "缂栬緫鏍囨潌"
+        form.value = JSON.parse(JSON.stringify(row))
+    } else {
+        console.log(form.value)
+        title.value = "娣诲姞鏍囨潌"
+    }
+    visible.value = true
+}
+
+
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        code: null,
+        type: null,
+        grade: null,
+        nationalNum: null,
+        value: null,
+        remark: null
+    }
+}
+
+
+defineExpose({ handleOpen })
+
+</script>
+
+
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/error/401.vue b/zhitan-vue/src/views/error/401.vue
new file mode 100644
index 0000000..1ba3792
--- /dev/null
+++ b/zhitan-vue/src/views/error/401.vue
@@ -0,0 +1,82 @@
+<template>
+  <div class="errPage-container">
+    <el-button icon="arrow-left" class="pan-back-btn" @click="back">
+      杩斿洖
+    </el-button>
+    <el-row>
+      <el-col :span="12">
+        <h1 class="text-jumbo text-ginormous">
+          401閿欒!
+        </h1>
+        <h2>鎮ㄦ病鏈夎闂潈闄愶紒</h2>
+        <h6>瀵逛笉璧凤紝鎮ㄦ病鏈夎闂潈闄愶紝璇蜂笉瑕佽繘琛岄潪娉曟搷浣滐紒鎮ㄥ彲浠ヨ繑鍥炰富椤甸潰</h6>
+        <ul class="list-unstyled">
+          <li class="link-type">
+            <router-link to="/">
+              鍥為椤�
+            </router-link>
+          </li>
+        </ul>
+      </el-col>
+      <el-col :span="12">
+        <img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import errImage from "@/assets/401_images/401.gif";
+
+let { proxy } = getCurrentInstance();
+
+const errGif = ref(errImage + "?" + +new Date());
+
+function back() {
+  if (proxy.$route.query.noGoBack) {
+    proxy.$router.push({ path: "/" });
+  } else {
+    proxy.$router.go(-1);
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.errPage-container {
+  width: 800px;
+  max-width: 100%;
+  margin: 100px auto;
+  .pan-back-btn {
+    background: #008489;
+    color: #fff;
+    border: none !important;
+  }
+  .pan-gif {
+    margin: 0 auto;
+    display: block;
+  }
+  .pan-img {
+    display: block;
+    margin: 0 auto;
+    width: 100%;
+  }
+  .text-jumbo {
+    font-size: 60px;
+    font-weight: 700;
+    color: #484848;
+  }
+  .list-unstyled {
+    font-size: 14px;
+    li {
+      padding-bottom: 5px;
+    }
+    a {
+      color: #008489;
+      text-decoration: none;
+      &:hover {
+        text-decoration: underline;
+      }
+    }
+  }
+}
+</style>
diff --git a/zhitan-vue/src/views/error/404.vue b/zhitan-vue/src/views/error/404.vue
new file mode 100644
index 0000000..f205303
--- /dev/null
+++ b/zhitan-vue/src/views/error/404.vue
@@ -0,0 +1,227 @@
+<template>
+  <div class="wscn-http404-container">
+    <div class="wscn-http404">
+      <div class="pic-404">
+        <img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
+        <img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
+        <img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
+        <img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
+      </div>
+      <div class="bullshit">
+        <div class="bullshit__oops">
+          404閿欒!
+        </div>
+        <div class="bullshit__headline">
+          {{ message }}
+        </div>
+        <div class="bullshit__info">
+          瀵逛笉璧凤紝鎮ㄦ鍦ㄥ鎵剧殑椤甸潰涓嶅瓨鍦ㄣ�傚皾璇曟鏌RL鐨勯敊璇紝鐒跺悗鎸夋祻瑙堝櫒涓婄殑鍒锋柊鎸夐挳鎴栧皾璇曞湪鎴戜滑鐨勫簲鐢ㄧ▼搴忎腑鎵惧埌鍏朵粬鍐呭銆�
+        </div>
+        <router-link to="/index" class="bullshit__return-home">
+          杩斿洖棣栭〉
+        </router-link>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+let message = computed(() => {
+  return '鎵句笉鍒扮綉椤碉紒'
+})
+</script>
+
+<style lang="scss" scoped>
+.wscn-http404-container{
+  transform: translate(-50%,-50%);
+  position: absolute;
+  top: 40%;
+  left: 50%;
+}
+.wscn-http404 {
+  position: relative;
+  width: 1200px;
+  padding: 0 50px;
+  overflow: hidden;
+  .pic-404 {
+    position: relative;
+    float: left;
+    width: 600px;
+    overflow: hidden;
+    &__parent {
+      width: 100%;
+    }
+    &__child {
+      position: absolute;
+      &.left {
+        width: 80px;
+        top: 17px;
+        left: 220px;
+        opacity: 0;
+        animation-name: cloudLeft;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1s;
+      }
+      &.mid {
+        width: 46px;
+        top: 10px;
+        left: 420px;
+        opacity: 0;
+        animation-name: cloudMid;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1.2s;
+      }
+      &.right {
+        width: 62px;
+        top: 100px;
+        left: 500px;
+        opacity: 0;
+        animation-name: cloudRight;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1s;
+      }
+      @keyframes cloudLeft {
+        0% {
+          top: 17px;
+          left: 220px;
+          opacity: 0;
+        }
+        20% {
+          top: 33px;
+          left: 188px;
+          opacity: 1;
+        }
+        80% {
+          top: 81px;
+          left: 92px;
+          opacity: 1;
+        }
+        100% {
+          top: 97px;
+          left: 60px;
+          opacity: 0;
+        }
+      }
+      @keyframes cloudMid {
+        0% {
+          top: 10px;
+          left: 420px;
+          opacity: 0;
+        }
+        20% {
+          top: 40px;
+          left: 360px;
+          opacity: 1;
+        }
+        70% {
+          top: 130px;
+          left: 180px;
+          opacity: 1;
+        }
+        100% {
+          top: 160px;
+          left: 120px;
+          opacity: 0;
+        }
+      }
+      @keyframes cloudRight {
+        0% {
+          top: 100px;
+          left: 500px;
+          opacity: 0;
+        }
+        20% {
+          top: 120px;
+          left: 460px;
+          opacity: 1;
+        }
+        80% {
+          top: 180px;
+          left: 340px;
+          opacity: 1;
+        }
+        100% {
+          top: 200px;
+          left: 300px;
+          opacity: 0;
+        }
+      }
+    }
+  }
+  .bullshit {
+    position: relative;
+    float: left;
+    width: 300px;
+    padding: 30px 0;
+    overflow: hidden;
+    &__oops {
+      font-size: 32px;
+      font-weight: bold;
+      line-height: 40px;
+      color: #1482f0;
+      opacity: 0;
+      margin-bottom: 20px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-fill-mode: forwards;
+    }
+    &__headline {
+      font-size: 20px;
+      line-height: 24px;
+      color: #222;
+      font-weight: bold;
+      opacity: 0;
+      margin-bottom: 10px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.1s;
+      animation-fill-mode: forwards;
+    }
+    &__info {
+      font-size: 13px;
+      line-height: 21px;
+      color: grey;
+      opacity: 0;
+      margin-bottom: 30px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.2s;
+      animation-fill-mode: forwards;
+    }
+    &__return-home {
+      display: block;
+      float: left;
+      width: 110px;
+      height: 36px;
+      background: #1482f0;
+      border-radius: 100px;
+      text-align: center;
+      color: #ffffff;
+      opacity: 0;
+      font-size: 14px;
+      line-height: 36px;
+      cursor: pointer;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.3s;
+      animation-fill-mode: forwards;
+    }
+    @keyframes slideUp {
+      0% {
+        transform: translateY(60px);
+        opacity: 0;
+      }
+      100% {
+        transform: translateY(0);
+        opacity: 1;
+      }
+    }
+  }
+}
+</style>
diff --git a/zhitan-vue/src/views/index.vue b/zhitan-vue/src/views/index.vue
new file mode 100644
index 0000000..2d94da3
--- /dev/null
+++ b/zhitan-vue/src/views/index.vue
@@ -0,0 +1,1238 @@
+<template>
+  <div class="page">
+    <CardHeader
+      :showBtn="true"
+      :active="'0'"
+      :period="period"
+      @handleClick="handleTimeType"
+    >
+      <span>
+        鍏ㄥ巶鑳借�楃粺璁�
+        <el-button @click="dialogVisible = true" v-if="list.length > 1">
+          鏌ョ湅鏇村
+        </el-button>
+      </span>
+    </CardHeader>
+    <template
+      v-for="(row, rowIndex) in list"
+      :key="rowIndex"
+      v-loading="loading02"
+    >
+      <div
+        class="card-list"
+        v-if="settingsStore.sideTheme == 'theme-dark' && rowIndex == 0"
+      >
+        <template v-for="(item, index) in row" :key="index">
+          <div
+            class="card-list-item"
+            :style="{
+              backgroundImage: 'url(' + bgList[index].bg + ')',
+            }"
+          >
+            <div class="item-top">
+              <div
+                class="top-icon"
+                :style="{ backgroundImage: 'url(' + bgList[index].icon + ')' }"
+              />
+              <div class="top-right">
+                <div class="right-name">
+                  {{ item.energyName }}
+                </div>
+                <div class="right-value">
+                  <span> {{ item.count }}</span>
+                  <span class="unit">{{ item.energyUnit }}</span>
+                </div>
+              </div>
+            </div>
+            <div class="item-bottom">
+              <div class="bottom-left">
+                <span>
+                  鍚屾瘮: {{ Math.abs(item.tongbi) }}
+                  <el-icon
+                    :color="
+                      item.tongbi > 0 ? 'green' : item.tongbi < 0 ? 'red' : ''
+                    "
+                  >
+                    <Top v-if="item.tongbi > 0" />
+                    <Bottom v-if="item.tongbi < 0" />
+                  </el-icon>
+                </span>
+              </div>
+              <div class="bottom-right">
+                <span
+                  >鐜瘮: {{ Math.abs(item.huanbi) }}
+                  <el-icon
+                    :color="
+                      item.huanbi > 0 ? 'green' : item.huanbi < 0 ? 'red' : ''
+                    "
+                  >
+                    <Top v-if="item.huanbi > 0" />
+                    <Bottom v-if="item.huanbi < 0" />
+                  </el-icon>
+                </span>
+              </div>
+            </div>
+          </div>
+        </template>
+      </div>
+      <div
+        class="card-list"
+        v-if="settingsStore.sideTheme != 'theme-dark' && rowIndex == 0"
+      >
+        <template
+          v-for="(item, index) in row"
+          :key="index"
+          v-show="rowIndex == 0"
+        >
+          <div
+            class="card-list-item"
+          >
+            <div class="item-top">
+              <div
+                class="top-icon"
+                :style="{ backgroundImage: 'url(' + bgList[index].icon2 + ')' }"
+              />
+              <div class="top-right">
+                <div class="right-name">
+                  {{ item.energyName }}
+                </div>
+                <div class="right-value">
+                  <span>{{ item.count }}</span>
+                  <span class="unit">{{ item.energyUnit }}</span>
+                </div>
+              </div>
+            </div>
+            <div class="item-bottom">
+              <div class="bottom-left">
+                <span>
+                  鍚屾瘮: {{ Math.abs(item.tongbi) }}
+                  <el-icon
+                    :color="
+                      item.tongbi > 0 ? 'green' : item.tongbi < 0 ? 'red' : ''
+                    "
+                  >
+                    <Top v-if="item.tongbi > 0" />
+                    <Bottom v-if="item.tongbi < 0" />
+                  </el-icon>
+                </span>
+              </div>
+              <div class="bottom-right">
+                <span
+                  >鐜瘮: {{ Math.abs(item.huanbi) }}
+                  <el-icon
+                    :color="
+                      item.huanbi > 0 ? 'green' : item.huanbi < 0 ? 'red' : ''
+                    "
+                  >
+                    <Top v-if="item.huanbi > 0" />
+                    <Bottom v-if="item.huanbi < 0" />
+                  </el-icon>
+                </span>
+              </div>
+            </div>
+          </div>
+          <div class="line"></div>
+        </template>
+      </div>
+    </template>
+    <!-- 鍥捐〃 -->
+    <div class="page-main">
+      <el-row :gutter="9" style="margin-bottom: 27px">
+        <el-col :span="12">
+          <el-card>
+            <CardHeader :period="period" @handleClick="handleTimeType">
+              鑳借�楄秼鍔�
+            </CardHeader>
+            <div id="Chart1" class="chart" v-loading="loading1" />
+          </el-card>
+        </el-col>
+        <el-col :span="12">
+          <el-card>
+            <CardHeader :period="period" @handleClick="handleTimeType">
+              鍏ㄥ巶鑳借�楀崰姣�
+            </CardHeader>
+            <div id="Chart2" class="chart" v-loading="loading02" />
+          </el-card>
+        </el-col>
+      </el-row>
+      <el-row :gutter="9">
+        <el-col :span="12">
+          <el-card>
+            <CardHeader
+              :showBtn="true"
+              :period="period"
+              :active="'3'"
+              @handleClick="handleTimeType"
+            >
+              鍘傚尯鑳借�楁帓鍚峊OP{{ listEnergyConsumptionRankingLength }}
+            </CardHeader>
+            <div class="top-header">
+              <div class="header-left">
+                <div class="name">鎺掑悕</div>
+                <div class="device">璁惧</div>
+              </div>
+              <div class="header-right">鑳借�楅噺(tce)</div>
+            </div>
+            <div
+              id="Chart3"
+              class="chart"
+              v-loading="loading3"
+              style="height: 254px"
+            />
+          </el-card>
+        </el-col>
+        <el-col :span="12">
+          <el-card>
+            <CardHeader
+              :showBtn="true"
+              :period="period"
+              :active="'4'"
+              @handleClick="handleTimeType"
+            >
+              灏栧嘲骞宠胺鍗犳瘮
+            </CardHeader>
+            <div id="Chart4" class="chart" v-loading="loading4" />
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+    <el-dialog v-model="dialogVisible" title="鏌ョ湅鍏ㄥ巶鑳借�楃粺璁�" width="80%" v-if='dialogVisible'>
+      <template v-for="(row, rowIndex) in list" :key="rowIndex">
+        <div class="card-list" v-if="settingsStore.sideTheme == 'theme-dark'">
+          <template v-for="(item, index) in row" :key="index">
+            <div
+              class="card-list-item"
+              :style="{
+                backgroundImage: 'url(' + bgList[index].bg + ')',
+              }"
+            >
+              <div class="item-top">
+                <div
+                  class="top-icon"
+                  :style="{
+                    backgroundImage: 'url(' + bgList[index].icon + ')',
+                  }"
+                />
+                <div class="top-right">
+                  <div class="right-name">
+                    {{ item.energyName }}
+                  </div>
+                  <div class="right-value">
+                    <span> {{ item.count }}</span>
+                    <span class="unit">{{ item.energyUnit }}</span>
+                  </div>
+                </div>
+              </div>
+              <div class="item-bottom">
+                <div class="bottom-left">
+                  <span>
+                    鍚屾瘮: {{ Math.abs(item.tongbi) }}
+                    <el-icon
+                      :color="
+                        item.tongbi > 0 ? 'green' : item.tongbi < 0 ? 'red' : ''
+                      "
+                    >
+                      <Top v-if="item.tongbi > 0" />
+                      <Bottom v-if="item.tongbi < 0" />
+                    </el-icon>
+                  </span>
+                </div>
+                <div class="bottom-right">
+                  <span
+                    >鐜瘮: {{ Math.abs(item.huanbi) }}
+                    <el-icon
+                      :color="
+                        item.huanbi > 0 ? 'green' : item.huanbi < 0 ? 'red' : ''
+                      "
+                    >
+                      <Top v-if="item.huanbi > 0" />
+                      <Bottom v-if="item.huanbi < 0" />
+                    </el-icon>
+                  </span>
+                </div>
+              </div>
+            </div>
+          </template>
+        </div>
+        <div class="card-list" v-if="settingsStore.sideTheme != 'theme-dark'">
+          <template
+            v-for="(item, index) in row"
+            :key="index"
+            v-show="rowIndex == 0"
+          >
+            <div
+              class="card-list-item"
+            >
+              <div class="item-top">
+                <div
+                  class="top-icon"
+                  :style="{
+                    backgroundImage: 'url(' + bgList[index].icon2 + ')',
+                  }"
+                />
+                <div class="top-right">
+                  <div class="right-name">
+                    {{ item.energyName }}
+                  </div>
+                  <div class="right-value">
+                    <span>{{ item.count }}</span>
+                    <span class="unit">{{ item.energyUnit }}</span>
+                  </div>
+                </div>
+              </div>
+              <div class="item-bottom">
+                <div class="bottom-left">
+                  <span>
+                    鍚屾瘮: {{ Math.abs(item.tongbi) }}
+                    <el-icon
+                      :color="
+                        item.tongbi > 0 ? 'green' : item.tongbi < 0 ? 'red' : ''
+                      "
+                    >
+                      <Top v-if="item.tongbi > 0" />
+                      <Bottom v-if="item.tongbi < 0" />
+                    </el-icon>
+                  </span>
+                </div>
+                <div class="bottom-right">
+                  <span
+                    >鐜瘮: {{ Math.abs(item.huanbi) }}
+                    <el-icon
+                      :color="
+                        item.huanbi > 0 ? 'green' : item.huanbi < 0 ? 'red' : ''
+                      "
+                    >
+                      <Top v-if="item.huanbi > 0" />
+                      <Bottom v-if="item.huanbi < 0" />
+                    </el-icon>
+                  </span>
+                </div>
+              </div>
+            </div>
+            <div class="line"></div>
+          </template>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup name="Index">
+import * as echarts from "echarts";
+import CardHeader from "@/components/CardHeader/CardHeader.vue";
+import {
+  listEnergyConsumptionSummation,
+  listEnergyConsumptionTrend,
+  listEnergyConsumptionRanking,
+  listPeakValley,
+} from "@/api/home";
+const { proxy } = getCurrentInstance();
+const { period } = proxy.useDict("period");
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getListEnergyConsumptionSummation();
+    getListEnergyConsumptionTrend();
+    getListEnergyConsumptionRanking();
+    getListPeakValley();
+  }
+);
+import index_card_1 from "@/assets/images/home/index-card-1.png";
+import index_card_2 from "@/assets/images/home/index-card-2.png";
+import index_card_3 from "@/assets/images/home/index-card-3.png";
+import index_card_4 from "@/assets/images/home/index-card-4.png";
+import index_card_5 from "@/assets/images/home/index-card-5.png";
+import card_icon_1 from "@/assets/images/home/card-icon-1.png";
+import card_icon_2 from "@/assets/images/home/card-icon-2.png";
+import card_icon_3 from "@/assets/images/home/card-icon-3.png";
+import card_icon_4 from "@/assets/images/home/card-icon-4.png";
+import card_icon_5 from "@/assets/images/home/card-icon-5.png";
+import card_icon2_1 from "@/assets/images/home/card-icon2-1.png";
+import card_icon2_2 from "@/assets/images/home/card-icon2-2.png";
+import card_icon2_3 from "@/assets/images/home/card-icon2-3.png";
+import card_icon2_4 from "@/assets/images/home/card-icon2-4.png";
+import card_icon2_5 from "@/assets/images/home/card-icon2-5.png";
+import { fa } from "element-plus/es/locales.mjs";
+const bgList = ref([
+  {
+    bg: index_card_1,
+    icon: card_icon_1,
+    icon2: card_icon2_1,
+  },
+  {
+    bg: index_card_2,
+    icon: card_icon_2,
+    icon2: card_icon2_2,
+  },
+  {
+    bg: index_card_3,
+    icon: card_icon_3,
+    icon2: card_icon2_3,
+  },
+  {
+    bg: index_card_4,
+    icon: card_icon_4,
+    icon2: card_icon2_4,
+  },
+  {
+    bg: index_card_5,
+    icon: card_icon_5,
+    icon2: card_icon2_5,
+  },
+]);
+const list = ref([[{}, {}, {}, {}, {}]]);
+const listEnergyConsumptionRankingLength = ref(0);
+const dialogVisible = ref(false);
+const loading02 = ref(false);
+const loading1 = ref(false);
+const loading3 = ref(false);
+const loading4 = ref(false);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    timeType: null,
+    dataTime: null,
+    nodeName: null,
+  },
+});
+const { queryParams } = toRefs(data);
+function handleTimeType(item, type) {
+  queryParams.value.timeType = item;
+  queryParams.value.type = type;
+  queryParams.value.dataTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+  if (type == 0) {
+    getListEnergyConsumptionSummation();
+    getListEnergyConsumptionTrend();
+  } else if (type == 3) {
+    getListEnergyConsumptionRanking();
+  } else if (type == 4) {
+    getListPeakValley();
+  }
+}
+let myChart1 = null;
+let myChart2 = null;
+let myChart3 = null;
+let myChart4 = null;
+onMounted(() => {
+  myChart1 = echarts.init(document.getElementById("Chart1"));
+  myChart2 = echarts.init(document.getElementById("Chart2"));
+  myChart3 = echarts.init(document.getElementById("Chart3"));
+  myChart4 = echarts.init(document.getElementById("Chart4"));
+});
+// 棣栭〉-鍏ㄥ巶鑳借�楃粺璁�/鍏ㄥ巶鑳借�楀崰姣�-鍒楄〃
+function getListEnergyConsumptionSummation() {
+  loading02.value = true;
+  list.value = [];
+  listEnergyConsumptionSummation(
+    proxy.addDateRange({
+      ...queryParams.value,
+    })
+  ).then((res) => {
+    loading02.value = false;
+    if (!!res.code && res.code == 200) {
+      let total = 0;
+      let seriesData = [];
+      if (!!res.data && res.data.length > 0) {
+        res.data.map((item, index) => {
+          total += Number(item.tonCount);
+          item.name = item.energyName;
+          item.value = Number(item.tonCount).toFixed(2);
+          if (index % 5 === 0) {
+            list.value.push(res.data.slice(index, index + 5));
+          }
+        });
+        seriesData = res.data;
+      }
+      setTimeout(() => {
+        myChart2.setOption({
+          // color: ["#3371eb", "#78e801", "#ffce0c", "#ff6200", "#f52528"],
+          grid: {
+            top: "20%",
+            left: "15%",
+            right: "5%",
+            bottom: "0%",
+            containLabel: true,
+          },
+          tooltip: {
+            trigger: "item",
+          },
+          legend: {
+            type: "scroll",
+            orient: "vertical",
+            top: "center",
+            icon: "circle",
+            right: "5%",
+            itemWidth: 14,
+            itemHeight: 14,
+            itemGap: 60,
+            textStyle: {
+              align: "left",
+              verticalAlign: "middle",
+              rich: {
+                name: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+                value: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+                rate: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+              },
+            },
+            data: seriesData,
+            formatter: (name) => {
+              let target, percent, energyUnit;
+              for (let i = 0; i < seriesData.length; i++) {
+                if (seriesData[i].name === name) {
+                  target = seriesData[i].value;
+                  energyUnit = seriesData[i].energyUnit;
+                  percent =
+                    total != 0 ? ((target / total) * 100).toFixed(2) : 0;
+                }
+              }
+              return `{name|${name}(${energyUnit})  }{value| ${target}} {rate| ${percent}%}`;
+            },
+          },
+          series: [
+            {
+              name: "鍏ㄥ巶鑳借�楃被鍨嬪崰姣�",
+              type: "pie",
+              center: ["30%", "50%"],
+              radius: ["50%", "70%"],
+              label: {
+                show: false,
+                fontSize: 11,
+                color:
+                  settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#000",
+              },
+              labelLine: {
+                show: false,
+                length: 50,
+              },
+              data: seriesData,
+            },
+          ],
+        });
+      }, 100);
+      window.addEventListener(
+        "resize",
+        () => {
+          myChart2.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+}
+// 棣栭〉-鍏ㄥ巶鑳借�楄秼鍔�-鍒楄〃
+function getListEnergyConsumptionTrend() {
+  loading1.value = true;
+  listEnergyConsumptionTrend(
+    proxy.addDateRange({
+      ...queryParams.value,
+    })
+  ).then((res) => {
+    loading1.value = false;
+    if (!!res.code && res.code == 200) {
+      let xdata = [];
+      let series = [];
+      if (!!res.data.xdata) {
+        res.data.xdata.map((item) => {
+          xdata.push(
+            proxy
+              .dayjs(item)
+              .format(
+                queryParams.value.timeType == "YEAR"
+                  ? "MM鏈�"
+                  : queryParams.value.timeType == "MONTH"
+                  ? "DD鏃�"
+                  : "HH鏃�"
+              )
+          );
+        });
+      }
+      if (!!res.data.legend && !!res.data.ydata) {
+        series = res.data.legend.map((item, index) => {
+          return {
+            name: item,
+            type: "bar",
+            stack: "total",
+            barWidth: "16",
+            data: !!res.data.ydata ? res.data.ydata[index] : [],
+          };
+        });
+      }
+      setTimeout(() => {
+        myChart1.setOption({
+          color: ["#3371eb", "#78e801", "#ffce0c", "#ff6200", "#f52528"],
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          grid: {
+            top: "45",
+            left: "7%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          xAxis: {
+            type: "category",
+            axisPointer: {
+              type: "shadow",
+            },
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: xdata,
+          },
+          yAxis: [
+            {
+              type: "value",
+              name: "tce",
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+          ],
+          series,
+        });
+      }, 100);
+    }
+  });
+  window.addEventListener(
+    "resize",
+    () => {
+      myChart1.resize();
+    },
+    { passive: true }
+  );
+}
+// 棣栭〉-绉戝鑳借�楁帓鍚峊OP-鍒楄〃
+function getListEnergyConsumptionRanking() {
+  loading3.value = true;
+  let opt = {
+    index: 0,
+  };
+  listEnergyConsumptionRanking(
+    proxy.addDateRange({
+      ...queryParams.value,
+    })
+  ).then((res) => {
+    loading3.value = false;
+    let nodeName = [];
+    let energyConsumption = [];
+    let maxenergyConsumption = [];
+    
+    if (!!res.data) {
+      res.data.map((item, index) => {
+        nodeName.push(item.nodeName);
+        energyConsumption.push(
+          !!item.energyConsumption ? item.energyConsumption : 0
+        );
+        maxenergyConsumption[index] = res.data[0].energyConsumption;
+      });
+      listEnergyConsumptionRankingLength.value = res.data.length;
+    }
+    setTimeout(() => {
+      myChart3.setOption({
+        grid: {
+          left: "1%",
+          right: "2%",
+          bottom: "2%",
+          top: "2%",
+          containLabel: true,
+        },
+        tooltip: {
+          trigger: "axis",
+          axisPointer: {
+            type: "none",
+          },
+          formatter: function (params) {
+            return params[0].name + " : " + params[0].value;
+          },
+        },
+        xAxis: {
+          show: false,
+          type: "value",
+        },
+        yAxis: [
+          {
+            type: "category",
+            inverse: true,
+            splitLine: {
+              show: false,
+            },
+            axisTick: {
+              show: false,
+            },
+            axisLine: {
+              show: false,
+            },
+            axisLabel: {
+              interval: 0,
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#000",
+              fontSize: 14,
+              formatter: function (value, index) {
+                if (index == 0) {
+                  return (
+                    "{idx0|" +
+                    (1 + index + opt.index) +
+                    "}{title|" +
+                    value +
+                    "}"
+                  );
+                } else if (index == 1) {
+                  return (
+                    "{idx1|" +
+                    (1 + index + opt.index) +
+                    "}{title|" +
+                    value +
+                    "}"
+                  );
+                } else if (index == 2) {
+                  return (
+                    "{idx2|" +
+                    (1 + index + opt.index) +
+                    "}{title|" +
+                    value +
+                    "}"
+                  );
+                } else {
+                  return (
+                    "{idx|" + (1 + index + opt.index) + "}{title|" + value + "}"
+                  );
+                }
+              },
+              rich: {
+                idx0: {
+                  color: "#FF0004",
+                  backgroundColor: "#FF000426",
+                  borderRadius: 100,
+                  padding: [5, 8],
+                },
+                idx1: {
+                  color: "#FF8400",
+                  backgroundColor: "#FF84001F",
+                  borderRadius: 100,
+                  padding: [5, 8],
+                },
+                idx2: {
+                  color: "#FFDD00",
+                  backgroundColor: "#FFDD001F",
+                  borderRadius: 100,
+                  padding: [5, 8],
+                },
+                idx: {
+                  color: "#3371EB",
+                  backgroundColor: "#3371EB26",
+                  borderRadius: 100,
+                  padding: [5, 8],
+                },
+                title: {
+                  padding: [5, 8],
+                },
+              },
+            },
+            data: nodeName,
+          },
+          {
+            type: "category",
+            inverse: true,
+            axisTick: "none",
+            axisLine: "none",
+            show: true,
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#000",
+              fontSize: "12",
+            },
+            data: energyConsumption,
+          },
+        ],
+        series: [
+          {
+            type: "bar",
+            showBackground: true,
+            showBackground: true,
+            backgroundStyle: {
+              color: "#DCDEE2",
+            },
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                {
+                  offset: 0,
+                  color: "#0437FF",
+                },
+                {
+                  offset: 1,
+                  color: "#55C6FF",
+                },
+              ]),
+            },
+            barWidth: "10",
+            data: energyConsumption,
+          },
+          {
+            type: "pictorialBar",
+            symbol: "rect",
+            symbolSize: [4, 14],
+            symbolPosition: "end",
+            itemStyle: {
+              color: "#488BFF",
+            },
+            data: energyConsumption,
+          },
+        ],
+      });
+    }, 100);
+    window.addEventListener(
+      "resize",
+      () => {
+        myChart3.resize();
+      },
+      { passive: true }
+    );
+  });
+}
+// 棣栭〉-灏栧嘲骞宠胺鍗犳瘮-鍒楄〃
+function getListPeakValley() {
+  loading4.value = true;
+  listPeakValley(
+    proxy.addDateRange({
+      ...queryParams.value,
+    })
+  ).then((res) => {
+    loading4.value = false;
+    if (!!res.code && res.code == 200) {
+      let total = 0;
+      let seriesData = [];
+      if (!!res.data && res.data.length > 0) {
+        res.data.map((item, index) => {
+          total += Number(item.count);
+          seriesData.push({
+            name: item.timeName,
+            value: Number(item.count).toFixed(2),
+          });
+        });
+      }
+      setTimeout(() => {
+        myChart4.setOption({
+          color: [
+            "#8B33FF",
+            "#00DBFF",
+            "#002ADB",
+            "#5C92F7",
+            "#76D2F1",
+            "#8FADF9",
+          ],
+          grid: {
+            top: "20%",
+            left: "15%",
+            right: "5%",
+            bottom: "0%",
+            containLabel: true,
+          },
+          tooltip: {
+            trigger: "item",
+          },
+          legend: {
+            type: "scroll",
+            orient: "vertical",
+            top: "center",
+            icon: "circle",
+            right: "10%",
+            itemWidth: 14,
+            itemHeight: 14,
+            itemGap: 50,
+            textStyle: {
+              align: "left",
+              verticalAlign: "middle",
+              rich: {
+                name: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+                value: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+                rate: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                  fontSize: 14,
+                },
+              },
+            },
+            data: seriesData,
+            formatter: (name) => {
+              let target, percent;
+              for (let i = 0; i < seriesData.length; i++) {
+                if (seriesData[i].name === name) {
+                  target = seriesData[i].value;
+                  percent =
+                    total != 0 ? ((target / total) * 100).toFixed(2) : 0;
+                }
+              }
+              return `{name|${name}(kWh)  }{value| ${target}} {rate| ${percent}%}`;
+            },
+          },
+          series: [
+            {
+              name: "灏栧嘲骞宠胺鍗犳瘮鍥�",
+              type: "pie",
+              center: ["30%", "50%"],
+              radius: ["0%", "50%"],
+              avoidLabelOverlap: false,
+              label: {
+                fontSize: 11,
+                color:
+                  settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#000",
+              },
+              labelLine: {
+                show: true,
+                length: 50,
+              },
+              data: seriesData,
+            },
+            {
+              name: "灏栧嘲骞宠胺鍗犳瘮鍥�",
+              type: "pie",
+              center: ["30%", "50%"],
+              radius: ["60%", "70%"],
+              avoidLabelOverlap: false,
+              label: {
+                position: "inner",
+                fontSize: 11,
+                show: false,
+              },
+              labelLine: {
+                show: false,
+              },
+              data: seriesData,
+            },
+          ],
+        });
+      }, 100);
+      window.addEventListener(
+        "resize",
+        () => {
+          myChart4.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+}
+</script>
+<style scoped lang="scss">
+.themeDark {
+  .page {
+    padding: 20px;
+    background: #120f2e;
+
+    .card-title {
+      width: 132px;
+      height: 29px;
+      font-weight: bold;
+      font-size: 22px;
+      color: #ffffff;
+    }
+
+    .card-list {
+      margin-top: 14px;
+      display: flex;
+      // justify-content: space-between;
+      width: 100%;
+      flex-wrap: wrap;
+
+      .card-list-item {
+        width: 19%;
+        margin-right: 1%;
+        height: 157px;
+        background-size: 100% 100%;
+        box-sizing: border-box;
+        padding: 25px 18px 12px 16px;
+        color: #fff;
+
+        .item-top {
+          display: flex;
+
+          .top-icon {
+            width: 69px;
+            height: 69px;
+            background-size: 100% 100%;
+          }
+
+          .top-right {
+            margin-left: 16px;
+
+            .right-name {
+              font-weight: bold;
+              font-size: 16px;
+              font-family: OPPOSans-Bold;
+            }
+
+            .right-value {
+              font-weight: 800;
+              font-size: 25px;
+              margin-top: 10px;
+              font-family: OPPOSans-Medium;
+
+              .unit {
+                margin-left: 5px;
+                font-size: 16px;
+                font-weight: normal;
+              }
+            }
+          }
+        }
+
+        .item-bottom {
+          display: flex;
+          justify-content: space-between;
+          margin-top: 18px;
+          font-family: OPPOSans, OPPOSans;
+          font-weight: bold;
+          font-size: 14px;
+        }
+      }
+    }
+
+    .page-main {
+      margin-top: 23px;
+    }
+  }
+
+  .chart {
+    width: 100%;
+    height: 292px;
+    margin-top: 10px;
+  }
+
+  .top-header {
+    margin-top: 15px;
+    height: 23px;
+    font-family: OPPOSans, OPPOSans;
+    font-weight: 500;
+    font-size: 14px;
+    color: rgba(196, 213, 255, 0.6);
+    border-bottom: 1px solid rgba(196, 213, 255, 0.6);
+    display: flex;
+    justify-content: space-between;
+
+    .header-left {
+      display: flex;
+
+      .name {
+        margin-left: 7px;
+        margin-right: 7px;
+      }
+    }
+  }
+}
+
+.themeLight {
+  .page {
+    padding: 20px;
+    background: #f7f8fa;
+
+    .card-title {
+      width: 132px;
+      height: 29px;
+      font-weight: bold;
+      font-size: 22px;
+      color: #ffffff;
+    }
+
+    .card-list {
+      width: 100%;
+      margin-top: 14px;
+      display: flex;
+      // justify-content: space-between;
+      align-items: center;
+      background-image: url("@/assets/images/home/index-card-bg2.png");
+      background-size: 100% 100%;
+      flex-wrap: wrap;
+      border-radius: 20px;
+
+      .card-list-item {
+        width: 19%;
+        margin-right: 0.5%;
+        height: 157px;
+        background-size: 100% 100%;
+        box-sizing: border-box;
+        padding: 25px 18px 12px 16px;
+        color: #fff;
+
+        .item-top {
+          display: flex;
+
+          .top-icon {
+            width: 69px;
+            height: 69px;
+            background-size: 100% 100%;
+          }
+
+          .top-right {
+            margin-left: 16px;
+
+            .right-name {
+              font-weight: bold;
+              font-size: 16px;
+              font-family: OPPOSans-Bold;
+            }
+
+            .right-value {
+              font-weight: 800;
+              font-size: 30px;
+              margin-top: 10px;
+              font-family: OPPOSans-Medium;
+
+              .unit {
+                margin-left: 5px;
+                font-size: 16px;
+                font-weight: normal;
+              }
+            }
+          }
+        }
+
+        .item-bottom {
+          display: flex;
+          justify-content: space-between;
+          margin-top: 18px;
+          font-family: OPPOSans, OPPOSans;
+          font-weight: bold;
+          font-size: 14px;
+        }
+      }
+
+      .line {
+        width: 1px;
+        height: 64px;
+        background-image: url("@/assets/images/home/line@2x.png");
+        background-size: 100% 100%;
+      }
+    }
+
+    .page-main {
+      margin-top: 23px;
+    }
+  }
+
+  .chart {
+    width: 100%;
+    height: 292px;
+    margin-top: 10px;
+  }
+
+  .top-header {
+    margin-top: 15px;
+    height: 23px;
+    font-family: OPPOSans, OPPOSans;
+    font-weight: 500;
+    font-size: 14px;
+    color: rgba(29, 29, 29, 0.6);
+    border-bottom: 1px solid rgba(196, 213, 255, 0.6);
+    display: flex;
+    justify-content: space-between;
+
+    .header-left {
+      display: flex;
+
+      .name {
+        margin-left: 7px;
+        margin-right: 7px;
+      }
+    }
+  }
+}
+</style>
diff --git a/zhitan-vue/src/views/login.vue b/zhitan-vue/src/views/login.vue
new file mode 100644
index 0000000..c89c954
--- /dev/null
+++ b/zhitan-vue/src/views/login.vue
@@ -0,0 +1,290 @@
+<template>
+  <div class="login">
+    <!-- <div class="login-logo" v-if="systemInfo && systemInfo.homeLogo"
+      :style="{ backgroundImage: 'url(' + systemInfo.homeLogo + ')', backgroundSize: '100% 100%' }"></div>
+    <div class="login-font" v-else>鑳芥簮绯荤粺</div> -->
+    <h1 class="logo">灞变笢鎭掗偊鍐剁偧鑲′唤鏈夐檺鍏徃</h1>
+    <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
+      <!-- <h3 class="title">鍏呯數妗╁悗鍙扮鐞嗙郴缁�</h3> -->
+      <el-form-item prop="username">
+        <el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" placeholder="璐﹀彿">
+          <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
+        </el-input>
+      </el-form-item>
+      <el-form-item prop="password">
+        <el-input v-model="loginForm.password" type="password" size="large" auto-complete="off" placeholder="瀵嗙爜"
+          @keyup.enter="handleLogin">
+          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
+        </el-input>
+      </el-form-item>
+      <el-form-item prop="code" v-if="captchaEnabled">
+        <el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="楠岃瘉鐮�" style="width: 63%"
+          @keyup.enter="handleLogin">
+          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
+        </el-input>
+        <div class="login-code">
+          <img :src="codeUrl" @click="getCode" class="login-code-img" />
+        </div>
+      </el-form-item>
+      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">璁颁綇瀵嗙爜</el-checkbox>
+      <el-form-item style="width:100%;">
+        <el-button :loading="loading" size="large" type="primary" style="width:100%;" color="#626aef" :dark="isDark"
+          @click.prevent="handleLogin">
+          <span v-if="!loading">鐧� 褰�</span>
+          <span v-else>鐧� 褰� 涓�...</span>
+        </el-button>
+      </el-form-item>
+    </el-form>
+    <!--  搴曢儴  -->
+    <div class="el-login-footer">
+      <span>Copyright 漏 2021-2024 ZhiTanCloud All Rights Reserved.</span>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { getCodeImg } from "@/api/login";
+import Cookies from "js-cookie";
+import { encrypt, decrypt } from "@/utils/jsencrypt";
+import useUserStore from "@/store/modules/user";
+const userStore = useUserStore();
+import { useRoute } from "vue-router";
+const route = useRoute();
+const router = useRouter();
+const { proxy } = getCurrentInstance();
+// if(Cookies.get('SystemInfo')){
+// const systemInfo = JSON.parse(Cookies.get('SystemInfo'))
+// }
+const loginForm = ref({
+  username: "admin",
+  password: "admin123",
+  rememberMe: false,
+  code: "",
+  uuid: ""
+});
+
+const loginRules = {
+  username: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ偍鐨勮处鍙�" }],
+  password: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ偍鐨勫瘑鐮�" }],
+  code: [{ required: true, trigger: "change", message: "璇疯緭鍏ラ獙璇佺爜" }]
+};
+
+const codeUrl = ref("");
+const loading = ref(false);
+// 楠岃瘉鐮佸紑鍏�
+const captchaEnabled = ref(true);
+// 娉ㄥ唽寮�鍏�
+const register = ref(false);
+const redirect = ref(undefined);
+watch(route, (newRoute) => {
+  redirect.value = newRoute.query && newRoute.query.redirect;
+}, { immediate: true });
+if (!!useRoute().query.token) {
+  //骞冲彴鍗曠嫭鐨勭櫥褰�
+  getLoginByNameAndTokenJ();
+} else {
+  getCode();
+  getCookie();
+}
+/**
+ * 涓夋柟骞冲彴鍗曠偣鐧婚檰
+ * 鍙紶閫抰oken
+ */
+function getLoginByNameAndTokenJ() {
+  //鑾峰彇鍦板潃鏍忎腑鐨則oken
+  var token = useRoute().query.token;
+  //璋冪敤鐧诲綍鐨勬帴鍙�
+  console.log('token2', token);
+  //杞湀鍦堬紝涓嶈鐪嬪埌鐧婚檰椤甸潰锛屾棤鎰熶綋楠�
+  loading.value = true;
+  // 璋冪敤action鐨勭櫥褰曟柟娉�
+  userStore
+    .LoginJHaveToken({
+      "token": token
+    })
+    .then(() => {
+      loading.value = true;
+      const query = route.query;
+      const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
+        if (cur !== "redirect") {
+          acc[cur] = query[cur];
+        }
+        return acc;
+      }, {});
+      router.push({ path: redirect.value || "/", query: otherQueryParams });
+    })
+    .catch(() => {
+      loading.value = true;
+    });
+}
+function handleLogin() {
+  proxy.$refs.loginRef.validate(valid => {
+    if (valid) {
+      loading.value = true;
+      // 鍕鹃�変簡闇�瑕佽浣忓瘑鐮佽缃湪 cookie 涓缃浣忕敤鎴峰悕鍜屽瘑鐮�
+      if (loginForm.value.rememberMe) {
+        Cookies.set("username", loginForm.value.username, { expires: 30 });
+        Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 });
+        Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 });
+      } else {
+        // 鍚﹀垯绉婚櫎
+        Cookies.remove("username");
+        Cookies.remove("password");
+        Cookies.remove("rememberMe");
+      }
+      // 璋冪敤action鐨勭櫥褰曟柟娉�
+      userStore.login(loginForm.value).then(() => {
+        const query = route.query;
+        const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
+          if (cur !== "redirect") {
+            acc[cur] = query[cur];
+          }
+          return acc;
+        }, {});
+        router.push({ path: redirect.value || "/", query: otherQueryParams });
+      }).catch(() => {
+        loading.value = false;
+        // 閲嶆柊鑾峰彇楠岃瘉鐮�
+        if (captchaEnabled.value) {
+          getCode();
+        }
+      });
+    }
+  });
+}
+function getCode() {
+  getCodeImg().then(res => {
+    captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled;
+    if (captchaEnabled.value) {
+      codeUrl.value = "data:image/gif;base64," + res.img;
+      loginForm.value.uuid = res.uuid;
+    }
+  });
+}
+
+function getCookie() {
+  const username = Cookies.get("username");
+  const password = Cookies.get("password");
+  const rememberMe = Cookies.get("rememberMe");
+  loginForm.value = {
+    username: username === undefined ? loginForm.value.username : username,
+    password: password === undefined ? loginForm.value.password : decrypt(password),
+    rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
+  };
+}
+</script>
+
+<style lang='scss' scoped>
+.login {
+  display: flex;
+  align-items: center;
+  height: 100%;
+  background-image: url("@/assets/images/login-bg.jpg");
+  background-size: 100% 100%;
+  flex-direction: column;
+  position: relative;
+  min-width: 700px;
+  min-height: 700px;
+}
+
+.title {
+  margin: 0px auto 30px auto;
+  text-align: center;
+  color: #707070;
+}
+
+.login-form {
+  position: absolute;
+  left: 50%;
+  top: 60%;
+  transform: translate(-50%, -50%);
+  border-radius: 6px;
+  // background: #ffffff;
+  width: 400px;
+  padding: 25px 25px 5px 25px;
+
+  .el-input {
+    height: 40px;
+
+    input {
+      height: 40px;
+    }
+  }
+
+  .input-icon {
+    height: 39px;
+    width: 14px;
+    margin-left: 0px;
+  }
+}
+
+.login-tip {
+  font-size: 13px;
+  text-align: center;
+  color: #bfbfbf;
+}
+
+.login-code {
+  width: 33%;
+  height: 40px;
+  float: right;
+
+  img {
+    cursor: pointer;
+    vertical-align: middle;
+  }
+}
+
+.el-login-footer {
+  height: 40px;
+  line-height: 40px;
+  position: fixed;
+  bottom: 0;
+  width: 100%;
+  text-align: center;
+  color: #fff;
+  font-family: Arial;
+  font-size: 12px;
+  letter-spacing: 1px;
+}
+
+.login-code-img {
+  height: 40px;
+  padding-left: 12px;
+}
+
+.login-logo {
+  width: 514px;
+  height: 177px;
+  // background-image: url('@/assets/images/login-logo.png');
+  // background-size: 100% 100%;
+  position: absolute;
+  left: 50%;
+  top: 22%;
+  transform: translate(-50%, -50%);
+}
+
+.login-font {
+  font-size: 50px;
+  color: #fff;
+  top: 32%;
+  position: absolute;
+  left: 50%;
+  width: 514px;
+  text-align: center;
+  transform: translate(-50%, -50%);
+}
+
+
+.logo {
+  color: #fff;
+  width: 514px;
+  height: 177px;
+  line-height: 177px;
+  position: absolute;
+  left: 50%;
+  top: 22%;
+  transform: translate(-50%, -50%);
+  text-align: center;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/measuringinstruments/distributionroom/distributionroom.vue b/zhitan-vue/src/views/measuringinstruments/distributionroom/distributionroom.vue
new file mode 100644
index 0000000..0b2d98f
--- /dev/null
+++ b/zhitan-vue/src/views/measuringinstruments/distributionroom/distributionroom.vue
@@ -0,0 +1,215 @@
+<template>
+  <div class="page">
+    <div class="form-card">
+      <el-form :model="queryParams" ref="queryRef" :inline="true">
+        <el-form-item label="缂栧彿">
+          <el-input v-model="queryParams.code" placeholder="缂栧彿" maxlength="30" />
+        </el-form-item>
+        <el-form-item label="鍚嶇О">
+          <el-input v-model="queryParams.name" placeholder="鍚嶇О" maxlength="30" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">
+            鎼滅储
+          </el-button>
+          <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="table-box">
+      <div class="mt20 mb20">
+        <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+      </div>
+      <el-table v-loading="loading" :data="energyTypeList">
+        <el-table-column label="缂栧彿" align="center" key="code" prop="code" :show-overflow-tooltip="true" />
+        <el-table-column label="鍚嶇О" align="center" key="name" prop="name" :show-overflow-tooltip="true" />
+        <el-table-column label="璐熻矗浜�" align="center" key="principals" prop="principals" :show-overflow-tooltip="true" />
+        <el-table-column label="璐熻矗浜虹數璇�" align="center" key="principalsTel" prop="principalsTel"
+          :show-overflow-tooltip="true" />
+        <el-table-column label="澶囨敞" align="center" key="remark" prop="remark" :show-overflow-tooltip="true" />
+        <el-table-column label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="缂栬緫" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">
+                缂栬緫
+              </el-button>
+            </el-tooltip>
+            <el-tooltip content="鍒犻櫎" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">
+                鍒犻櫎
+              </el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination v-show="queryParams.total > 0" :total="queryParams.total" v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </div>
+    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="缂栧彿" prop="code">
+              <el-input v-model="form.code" placeholder="璇疯緭鍏ョ紪鍙�" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍚嶇О" prop="name">
+              <el-input v-model="form.name" placeholder="璇疯緭鍏ュ悕绉�" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="璐熻矗浜�">
+              <el-input v-model="form.principals" placeholder="璇疯緭鍏ュ悕绉�" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="璐熻矗浜虹數璇�" prop="principalsTel">
+              <el-input v-model="form.principalsTel" placeholder="璇疯緭鍏ヨ礋璐d汉鐢佃瘽" maxlength="11" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="澶囨敞" prop="remark">
+              <el-input v-model="form.remark" placeholder="璇疯緭鍏ュ娉�" type="textarea" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup name="distribution-room">
+import {
+  listDistributionRoom,
+  addDistributionRoom,
+  getDistributionRoom,
+  updateDistributionRoom,
+  delDistributionRoom,
+} from "@/api/measuringinstruments/distributionroom";
+const { proxy } = getCurrentInstance();
+const energyTypeList = ref([]);
+const open = ref(false);
+const loading = ref(false);
+const title = ref("");
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNo: 1,
+    pageSize: 10,
+    total: 0,
+    name: null,
+    code: null
+  },
+  rules: {
+    name: [
+      { required: true, message: "鍚嶇О涓嶈兘涓虹┖", trigger: "blur" },
+    ],
+    code: [
+      { required: true, message: "缂栧彿涓嶈兘涓虹┖", trigger: "blur" },
+      // { pattern: /^[a-z0-9]*$/, message: "缂栧彿蹇呴』涓烘暟瀛�/灏忓啓瀛楁瘝" },
+    ],
+    principalsTel: [{ required: false, pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "璇疯緭鍏ユ纭殑鎵嬫満鍙风爜", trigger: "blur" }]
+  },
+});
+const { queryParams, form, rules } = toRefs(data);
+getList();
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鍒楄〃
+function getList() {
+  loading.value = true;
+  listDistributionRoom(proxy.addDateRange(queryParams.value)).then((res) => {
+    loading.value = false;
+    energyTypeList.value = res.data.records;
+    queryParams.value.total = res.data.total;
+  });
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鎼滅储
+function handleQuery() {
+  queryParams.value.pageNo = 1;
+  getList();
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value = {
+    pageNo: 1,
+    pageSize: 10,
+    total: 0,
+    name: null,
+    code: null
+  };
+  getList();
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鏂板
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "鏂板閰嶇數瀹�";
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-缂栬緫
+function handleUpdate(row) {
+  reset();
+  getDistributionRoom({ id: row.id }).then((response) => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "缂栬緫閰嶇數瀹�";
+  });
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鏂板/缂栬緫-淇濆瓨
+function submitForm() {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (valid) {
+      if (form.value.id != undefined) {
+        updateDistributionRoom(form.value).then((response) => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addDistributionRoom(form.value).then((response) => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鏂板/缂栬緫-鍙栨秷
+function cancel() {
+  open.value = false;
+  reset();
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鏂板/缂栬緫-琛ㄥ崟閲嶇疆
+function reset() {
+  form.value = {
+    name: null,
+    code: null,
+    remark: null,
+  };
+  proxy.resetForm("formRef");
+}
+// 璁¢噺鍣ㄥ叿妗f-閰嶇數瀹ょ鐞�-鍒犻櫎
+function handleDelete(row) {
+  proxy.$modal
+    .confirm('鏄惁纭鍒犻櫎閰嶇數瀹ゅ悕绉颁负"' + row.name + '"鐨勬暟鎹」锛�')
+    .then(function () {
+      return delDistributionRoom({ ids: row.id });
+    })
+    .then(() => {
+      getList();
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    })
+    .catch(() => { });
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/measuringinstruments/maintain/components/AnnexModal.vue b/zhitan-vue/src/views/measuringinstruments/maintain/components/AnnexModal.vue
new file mode 100644
index 0000000..cf9022c
--- /dev/null
+++ b/zhitan-vue/src/views/measuringinstruments/maintain/components/AnnexModal.vue
@@ -0,0 +1,95 @@
+<template>
+  <el-dialog v-model="visible" :title="title" width="850" @close="handleClose">
+
+
+    <el-form :model="form" ref="queryRef" :inline="true" label-width="70px">
+      <el-form-item label="鏂囦欢鍚�" prop="fileName">
+        <el-input v-model="form.fileName" placeholder="璇疯緭鍏ユ枃浠跺悕" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+        <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+      </el-form-item>
+    </el-form>
+
+    <div class="mb8">
+      <el-button type="primary" icon="Plus">閫夊彇鏂囦欢</el-button>
+      <el-button type="primary" icon="Plus">涓婁紶鍒版湇鍔″櫒</el-button>
+    </div>
+
+
+
+    <el-table :data="tableData" v-loading="loading" height="500">
+      <el-table-column prop="code" label="鏂囦欢鍚�" width="180" show-overflow-tooltip align="center" />
+      <el-table-column prop="name" label="涓婁紶鏃堕棿" width="180" show-overflow-tooltip align="center" />
+      <el-table-column label="鎿嶄綔" align="center">
+        <template #default="scope">
+          <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+            鍒犻櫎
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+      v-model:limit="queryParams.pageSize" @pagination="getList" />
+  </el-dialog>
+</template>
+
+<script setup>
+import { getAnnexList } from '@/api/measuringInstruments/measuringInstruments'
+
+
+let visible = ref(false)
+let title = ref('')
+let form = ref({
+  fileName: '',
+  implementId: ''
+})
+function handleOpen(row) {
+  title.value = '缂栧彿 ' + row.code + ' 闄勪欢'
+  visible.value = true
+  form.value.implementId = row.id
+  getList()
+}
+
+function handleClose(value) {
+  visible.value = false
+  loading.value = false
+}
+
+let loading = ref(false);
+let total = ref(0);
+let tableData = ref([])
+let queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+})
+
+function getList() {
+  getAnnexList({ ...queryParams.value, ...form.value }).then(res => {
+    console.log(res)
+  })
+}
+function handleQuery() {
+  getList()
+}
+
+function resetQuery() {
+  getList()
+}
+
+function handleDel() {
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+    return delTarget(row.indexId);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => { });
+}
+
+
+
+defineExpose({ handleOpen })
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/measuringinstruments/maintain/components/EditModal.vue b/zhitan-vue/src/views/measuringinstruments/maintain/components/EditModal.vue
new file mode 100644
index 0000000..e9ae6f0
--- /dev/null
+++ b/zhitan-vue/src/views/measuringinstruments/maintain/components/EditModal.vue
@@ -0,0 +1,244 @@
+<template>
+  <el-dialog v-model="visible" :title="title" width="1000" @close="handleClose">
+    <el-form :model="form" ref="queryRef" :rules="formRules" label-width="120px" v-loading="loading">
+      <el-row>
+        <el-col :span="8">
+          <el-form-item label="缂栫爜" prop="code">
+            <el-input v-model="form.code" placeholder="璇疯緭鍏ョ紪鐮�" />
+          </el-form-item>
+          <el-form-item label="瑙勬牸鍨嬪彿" prop="modelNumber">
+            <el-input v-model="form.modelNumber" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+          </el-form-item>
+          <el-form-item label="璐熻矗浜�" prop="personCharge">
+            <el-input v-model="form.personCharge" placeholder="璇疯緭鍏ヨ礋璐d汉" />
+          </el-form-item>
+          <el-form-item label="妫�瀹氬懆鏈�(澶�)" prop="checkCycle">
+            <el-input-number v-model="form.checkCycle" placeholder="璇疯緭鍏�" :min="0" :precision="0" :step="1"
+              style="width: 100%" />
+          </el-form-item>
+          <el-form-item label="鎻愰啋鍛ㄦ湡(澶�)" prop="reminderCycle">
+            <el-input-number v-model="form.reminderCycle" placeholder="璇疯緭鍏�" :min="0" :precision="0" :step="1"
+              style="width: 100%" />
+          </el-form-item>
+          <!-- <el-form-item label="鍏佽鏈�澶у姛鐜�" prop="maxAllowablePower">
+                        <el-input v-model="form.maxAllowablePower" placeholder="璇疯緭鍏�" />
+                    </el-form-item> -->
+
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="鍣ㄥ叿鍚嶇О" prop="meterName">
+            <el-input v-model="form.meterName" placeholder="璇疯緭鍏ュ櫒鍏峰悕绉�" />
+          </el-form-item>
+          <el-form-item label="娴嬮噺鑼冨洿" prop="measureRange">
+            <el-input v-model="form.measureRange" placeholder="璇疯緭鍏ユ祴閲忚寖鍥�" />
+          </el-form-item>
+          <el-form-item label="閰嶇數瀹�" prop="installactionLocation">
+            <el-select v-model="form.installactionLocation" placeholder="璇烽�夋嫨閰嶇數瀹�" style="width: 100%">
+              <el-option :label="item.name" :value="item.id" v-for="(item, index) in installLocationList"
+                :key="index" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="鐘舵��" prop="meterStatus">
+            <el-select v-model="form.meterStatus" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%;">
+              <el-option :label="item.label" :value="item.value" v-for="item in meter_status" :key="item.value" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+            <el-select v-model="form.energyType" placeholder="璇烽�夋嫨鑳芥簮绫诲瀷" style="width: 100%;">
+              <el-option :label="item.enername" :value="item.enersno" v-for="(item, index) in energyTypeArr"
+                :key="index" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
+          <el-form-item label="绉嶇被" prop="meterType">
+            <el-select v-model="form.meterType" placeholder="璇烽�夋嫨绉嶇被" style="width: 100%;">
+              <!-- @change="changeMeterType" -->
+              <el-option :label="item.label" :value="item.value" v-for="item in props.typeArray" :key="item.value" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="鐢熶骇鍘傚晢" prop="manufacturer">
+            <el-input v-model="form.manufacturer" placeholder="璇疯緭鍏ョ敓浜у巶鍟�" />
+          </el-form-item>
+
+          <el-form-item label="璧峰鏃堕棿" prop="startTime">
+            <el-date-picker v-model="form.startTime" value-format="YYYY-MM-DD" type="date" placeholder="鏃ユ湡绛涢��"
+              style="width: 100%" />
+          </el-form-item>
+          <el-form-item label="鎶曡繍鏃堕棿" prop="putrunTime">
+            <el-date-picker v-model="form.putrunTime" value-format="YYYY-MM-DD" type="date" placeholder="鏃ユ湡绛涢��"
+              style="width: 100%" />
+          </el-form-item>
+          <el-form-item label="缃戝叧鍚嶇О" prop="gatewayId">
+            <el-select v-model="form.gatewayId" placeholder="璇烽�夋嫨绉嶇被" style="width: 100%;" filterable>
+              <el-option :label="item.gatewayName" :value="item.id" v-for="item in gatewayBaseArray" :key="item.id" />
+            </el-select>
+          </el-form-item>
+          <!-- <el-form-item label="绾垮緞" prop="wireDiameter">
+                        <el-input v-model="form.wireDiameter" placeholder="璇疯緭鍏�" />
+                    </el-form-item> -->
+        </el-col>
+      </el-row>
+
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="澶囨敞" prop="remark">
+            <el-input v-model="form.remark" placeholder="璇疯緭鍏ュ唴瀹�" type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div slot="footer" class="text-right">
+      <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+      <el-button @click="handleClose">鍙� 娑�</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup>
+import { addMaintain, updateMaintain } from '@/api/measuringInstruments/measuringInstruments'
+import { gatewayBaseList } from "@/api/businessConfiguration/gatewayLedger";
+import {
+  listDistributionRoomList
+} from "@/api/measuringinstruments/distributionroom";
+import { nextTick } from 'vue';
+let props = defineProps(['typeArray', 'energyTypeArr'])
+const { proxy } = getCurrentInstance();
+const { meter_status } = proxy.useDict("meter_status");
+let emit = defineEmits(['getList'])
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+const installLocationList = ref([]);
+let form = ref({
+  code: null,
+  meterName: null,
+  meterType: null,
+  modelNumber: null,
+  measureRange: null,
+  manufacturer: null,
+  personCharge: null,
+  installactionLocation: null,
+  startTime: null,
+  checkCycle: null,
+  reminderCycle: null,
+  meterStatus: null,
+  remark: null,
+  putrunTime: null,
+  maxAllowablePower: null,
+  gatewayId: null,
+  gatewayName: null
+})
+const formRules = ref({
+  code: [{ required: true, trigger: "blur", message: "璇疯緭鍏ョ紪鐮�" }],
+  meterName: [{ required: true, trigger: "blur", message: "璇疯緭鍏ュ櫒鍏峰悕绉�" }],
+  installactionLocation: [{ required: true, trigger: "blur", message: "璇疯緭鍏ュ畨瑁呬綅缃�" }],
+  meterType: [{ required: true, trigger: "change", message: "璇烽�夋嫨绉嶇被" }],
+  startTime: [{ required: true, trigger: "change", message: "璇烽�夋嫨璧峰鏃堕棿" }],
+  putrunTime: [{ required: true, trigger: "change", message: "璇烽�夋嫨鎶曡繍鏃堕棿" }],
+  checkCycle: [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ瀹氬懆鏈�" }],
+  reminderCycle: [{ required: true, trigger: "change", message: "璇疯緭鍏ユ彁閱掑懆鏈�" }],
+  energyType: [{ required: true, trigger: "change", message: "璇烽�夋嫨鑳芥簮绫诲瀷" }],
+  // wireDiameter: [],
+  // maxAllowablePower: [],
+});
+getDistributionRoomListList()
+function getDistributionRoomListList() {
+  listDistributionRoomList().then((res) => {
+    installLocationList.value = res.data;
+  });
+}
+// function changeMeterType(value) {
+//     if (value == 1) {
+//         formRules.value.wireDiameter = [{ required: true, trigger: "blur", message: "璇疯緭鍏ョ嚎寰�" }]
+//         formRules.value.maxAllowablePower = [{ required: true, trigger: "blur", message: "璇疯緭鍏ユ渶澶у厑璁稿姛鐜�" }]
+
+//     } else {
+//         formRules.value.wireDiameter = []
+//         formRules.value.maxAllowablePower = []
+//     }
+// }
+function submitForm() {
+  proxy.$refs.queryRef.validate(valid => {
+    if (valid) {
+      loading.value = true;
+      if (form.value.gatewayId) {
+        form.value.gatewayName = gatewayBaseArray.value.find(item => item.id == form.value.gatewayId).gatewayName
+      }
+      let obj = form.value.id ? updateMaintain(form.value) : addMaintain(form.value)
+      obj.then((res) => {
+        if (res.code == 200) {
+          proxy.$modal.msgSuccess(res.msg);
+          emit('getList')
+        } else {
+          proxy.$modal.msgError(res.msg);
+        }
+
+      }).catch((err) => {
+
+      }).finally(() => {
+        handleClose()
+      });
+    }
+  })
+}
+
+
+function handleOpen(row) {
+  if (row && row.id) {
+    title.value = "缂栬緫璁¢噺鍣ㄥ叿妗f缁存姢"
+    form.value = JSON.parse(JSON.stringify(row))
+  } else {
+    console.log(form.value)
+    title.value = "娣诲姞璁¢噺鍣ㄥ叿妗f缁存姢"
+    form.value.meterType = props.typeArray[0].value
+    form.value.energyType = props.energyTypeArr[0].enersno
+  }
+  visible.value = true
+}
+
+let gatewayBaseArray = ref([])
+function gatewayBaseListFun() {
+  gatewayBaseList().then(res => {
+    if (res.code == 200) {
+      gatewayBaseArray.value = res.data
+      console.log(111, gatewayBaseArray.value)
+    }
+  })
+}
+gatewayBaseListFun()
+
+function handleClose(value) {
+  visible.value = false
+  loading.value = false
+  proxy.$refs.queryRef.resetFields()
+  form.value = {
+    code: null,
+    meterName: null,
+    meterType: null,
+    modelNumber: null,
+    measureRange: null,
+    manufacturer: null,
+    personCharge: null,
+    installactionLocation: null,
+    startTime: null,
+    checkCycle: null,
+    reminderCycle: null,
+    meterStatus: null,
+    remark: null,
+    putrunTime: null,
+    // wireDiameter: null,
+    // maxAllowablePower: null,
+    gatewayId: null,
+    gatewayName: null
+  }
+}
+
+
+defineExpose({ handleOpen })
+
+</script>
+
+
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/measuringinstruments/maintain/components/TargetModal.vue b/zhitan-vue/src/views/measuringinstruments/maintain/components/TargetModal.vue
new file mode 100644
index 0000000..51b341b
--- /dev/null
+++ b/zhitan-vue/src/views/measuringinstruments/maintain/components/TargetModal.vue
@@ -0,0 +1,83 @@
+<template>
+  <el-dialog v-model="visible" :title="title" width="900" @close="handleClose">
+    <div class="mb8">
+      <el-button type="primary" icon="Refresh" @click="handleAdd">鎸囨爣鐢熸垚</el-button>
+    </div>
+    <el-table :data="tableData" v-loading="loading" height="500">
+      <el-table-column prop="code" label="鎸囨爣缂栫爜" show-overflow-tooltip align="center" />
+      <el-table-column prop="name" label="鎸囨爣鍚嶇О" show-overflow-tooltip align="center" />
+      <el-table-column prop="indexType" label="鎸囨爣绫诲瀷" show-overflow-tooltip align="center"
+        :formatter="(row, column) => proxy.selectDictLabel(sys_index_type, row.meterStatus)" />
+      <el-table-column prop="unitId" label="鍗曚綅" show-overflow-tooltip align="center"
+        :formatter="(row, column) => proxy.selectDictLabel(sys_unit, row.meterStatus)" />
+      <el-table-column label="鎿嶄綔" width="80">
+        <template #default="scope">
+          <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+            鍒犻櫎
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </el-dialog>
+</template>
+
+<script setup>
+import { getTargetList, addTarget, delTarget } from '@/api/measuringInstruments/measuringInstruments'
+const { proxy } = getCurrentInstance();
+const { sys_index_type } = proxy.useDict("sys_index_type");
+const { sys_unit } = proxy.useDict("sys_unit");
+let visible = ref(false)
+let loading = ref(false)
+let title = ref('')
+let id = ref('')
+function handleOpen(row) {
+  title.value = '缂栧彿 ' + row.code + ' 鎸囨爣'
+  id = row.id
+  getList()
+  visible.value = true
+}
+
+function handleClose(value) {
+  visible.value = false
+  loading.value = false
+}
+
+let tableData = ref([])
+
+function getList() {
+  getTargetList(id).then(res => {
+    tableData.value = res.data
+  })
+}
+
+function handleAdd() {
+  proxy.$modal.confirm('鏄惁纭閲嶆柊鐢熸垚鍣ㄥ叿缂栫爜涓�"AHU1553"鐨勬寚鏍囧悧?閲嶆柊鐢熸垚鍚庢墍鏈夋寚鏍囦細杩樺師鍒版ā鏉跨姸鎬�?').then(function () {
+    return addTarget(id);
+  }).then(() => {
+    getList();
+  }).catch(() => { });
+}
+
+// function handleAdd() {
+//   addTarget(id).then(res => {
+//     if (res.code == 200) {
+//       proxy.$modal.msgSuccess(res.message);
+//       getList()
+//     } else {
+//       proxy.$modal.msgError(res.message);
+//     }
+//   })
+// }
+function handleDel(row) {
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+    return delTarget(row.indexId);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => { });
+}
+
+defineExpose({ handleOpen })
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/measuringinstruments/maintain/maintain.vue b/zhitan-vue/src/views/measuringinstruments/maintain/maintain.vue
new file mode 100644
index 0000000..fced560
--- /dev/null
+++ b/zhitan-vue/src/views/measuringinstruments/maintain/maintain.vue
@@ -0,0 +1,165 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+                <el-form-item label="缂栫爜" prop="code">
+                    <el-input v-model="form.code" placeholder="璇疯緭鍏ョ紪鐮�" />
+                </el-form-item>
+                <el-form-item label="鍣ㄥ叿鍚嶇О" prop="meterName">
+                    <el-input v-model="form.meterName" placeholder="璇疯緭鍏ュ櫒鍏峰悕绉�" />
+                </el-form-item>
+                <el-form-item label="绉嶇被" prop="meterType">
+                    <el-select v-model="form.meterType" placeholder="璇烽�夋嫨绉嶇被" style="width: 170px;">
+                        <el-option :label="item.label" :value="item.value" v-for="item in sys_device_type"
+                            :key="item.value" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="table-box">
+            <div class=" mt20 mb20">
+                <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+            </div>
+            <el-table :data="tableData" v-loading="loading">
+                <el-table-column prop="code" label="缂栫爜" show-overflow-tooltip align="center" />
+                <el-table-column prop="meterName" label="鍣ㄥ叿鍚嶇О" show-overflow-tooltip align="center" />
+                <el-table-column prop="meterType" label="绉嶇被" show-overflow-tooltip align="center"
+                    :formatter="(row, column) => proxy.selectDictLabel(sys_device_type, row.meterType)" />
+                <el-table-column prop="energyType" label="鑳芥簮绫诲瀷" show-overflow-tooltip align="center"
+                    :formatter="energyTypeFormatter" />
+                <el-table-column prop="modelNumber" label="瑙勬牸鍨嬪彿" show-overflow-tooltip align="center" />
+                <el-table-column prop="measureRange" label="娴嬮噺鑼冨洿" show-overflow-tooltip align="center" />
+                <el-table-column prop="manufacturer" label="鐢熶骇鍘傚晢" show-overflow-tooltip align="center" />
+                <el-table-column prop="installLocationName" label="閰嶇數瀹�" show-overflow-tooltip align="center" />
+                <el-table-column prop="meterStatus" label="鐘舵��" width="90" show-overflow-tooltip align="center"
+                    :formatter="(row, column) => proxy.selectDictLabel(meter_status, row.meterStatus)" />
+                <el-table-column label="鎿嶄綔" width="280" align="center">
+                    <template #default="scope">
+                        <!-- <el-button link type="primary" icon="Files" @click="handleAnnexModal(scope.row)">
+                                        闄勪欢
+                                    </el-button> -->
+                        <el-button link type="primary" icon="Document" @click="handleTargetModal(scope.row)">
+                            鎸囨爣
+                        </el-button>
+                        <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row)">
+                            淇敼
+                        </el-button>
+                        <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                            鍒犻櫎
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+        </div>
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize" @pagination="getList" />
+        <EditModal ref="EditModalRef" :typeArray="sys_device_type" :energyTypeArr="energyTypeArr" @getList="getList" />
+        <AnnexModal ref="AnnexModalRef" />
+        <TargetModal ref="TargetModalRef" />
+
+
+    </div>
+</template>
+
+<script setup name="measuring">
+import EditModal from './components/EditModal.vue'
+import AnnexModal from './components/AnnexModal.vue'
+import TargetModal from './components/TargetModal.vue'
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+import { getMaintainList, delMaintain } from '@/api/measuringInstruments/measuringInstruments'
+const { proxy } = getCurrentInstance();
+const { sys_device_type } = proxy.useDict("sys_device_type");
+const { meter_status } = proxy.useDict("meter_status");
+
+let form = ref({
+    code: '',
+    meterName: '',
+    meterType: ''
+})
+
+
+function handleQuery() {
+    queryParams.value.pageNum = 1
+    getList()
+}
+
+function resetQuery() {
+    proxy.resetForm("queryRef");
+    handleQuery();
+}
+
+
+let loading = ref(false);
+let total = ref(0);
+let tableData = ref([])
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+})
+
+//鑾峰彇鍒楄〃
+function getList() {
+    loading.value = true
+    getMaintainList({ ...queryParams.value, ...form.value }).then(res => {
+        tableData.value = res.rows
+        total.value = res.total
+        loading.value = false
+    })
+}
+
+getList()
+
+let EditModalRef = ref('')
+function handleAdd(row) {
+    if (EditModalRef.value) {
+        EditModalRef.value.handleOpen(row)
+    }
+
+}
+
+function handleDel(row) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+        return delMaintain(row.id);
+    }).then(() => {
+        getList();
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    }).catch(() => { });
+}
+
+let AnnexModalRef = ref('')
+
+function handleAnnexModal(row) {
+    if (AnnexModalRef.value) {
+        AnnexModalRef.value.handleOpen(row)
+    }
+}
+
+let TargetModalRef = ref('')
+
+function handleTargetModal(row) {
+    if (TargetModalRef.value) {
+        TargetModalRef.value.handleOpen(row)
+    }
+
+}
+let energyTypeArr = ref([])
+function energyTypeFun() {
+    listEnergyTypeList().then((res) => {
+        energyTypeArr.value = res.data;
+    });
+}
+energyTypeFun()
+
+function energyTypeFormatter(value) {
+    let dict = energyTypeArr.value.find(item => item.enersno == value.energyType)
+    return dict ? dict.enername : ''
+}
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/businessmodel/businessModel.vue b/zhitan-vue/src/views/modelconfiguration/businessmodel/businessModel.vue
new file mode 100644
index 0000000..9ae49db
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/businessmodel/businessModel.vue
@@ -0,0 +1,396 @@
+<template>
+    <div class="page">
+        <div class="page-container">
+            <div class="page-container-left">
+                <div class="tree page-box">
+                    <!-- <CardHeader class="mb20">鏁版嵁妯″瀷绠$悊</CardHeader> -->
+                    <div class="select-box mb20">
+                        <el-select v-model="modelData" placeholder="璇烽�夋嫨妯″瀷" filterable @change="changeModel">
+                            <el-option v-for="model in modelInfoOptions" :key="model.modelCode" :label="model.modelName"
+                                :value="model.modelCode" />
+                        </el-select>
+                        <el-icon @click="handleModel">
+                            <Setting />
+                        </el-icon>
+                    </div>
+                    <!-- <el-input v-model="filterText" style="width: 240px" placeholder="杈撳叆鍏抽敭瀛楄繘琛岃繃婊�" class="mb10" /> -->
+                    <div class="tree-box" v-loading="treeLoading">
+                        <el-link icon="el-icon-plus" style="margin-top: 8px;width:100%" @click="addNode"
+                            v-if="treeData.length === 0">娣诲姞鏍硅妭鐐�
+                        </el-link>
+                        <template v-else>
+                            <el-tree ref="treeRef" :props="defaultProps" :data="treeData" node-key="id"
+                                highlight-current :filter-node-method="filterNode"
+                                :default-expanded-keys="treeExpandData" :expand-on-click-node="false"
+                                @node-click="changeNode" accordion>
+                                <template #default="{ node, data }">
+                                    <span>
+                                        <el-tooltip v-if="node.label.length > 6" class="item" effect="dark"
+                                            :content="node.label" placement="top-end">
+                                            <span>{{ node.label.slice(0, 6) + "..." }}</span>
+                                        </el-tooltip>
+                                        <span v-else id="b">{{ node.label }}</span>
+                                    </span>
+                                    <span class="node-opt">
+                                        <el-link title="鏂板涓嬬骇鑺傜偣" icon="Plus" @click="() => addNode(node, data)">
+                                        </el-link>
+                                        <el-link title="淇敼鑺傜偣" icon="EditPen" @click="() => editNode(node, data)">
+                                        </el-link>
+                                        <el-link title="鍒犻櫎鑺傜偣" icon="Delete" @click="() => delNode(node, data)">
+                                        </el-link>
+                                    </span>
+                                </template>
+                            </el-tree>
+                        </template>
+                    </div>
+                </div>
+            </div>
+            <div class="page-container-right">
+                <div class="mb20 mt20 ml20 tab-box">
+                    <div class="tab-li" :class="tab == 1 ? 'is-tab' : ''" @click="handleTab('1')">
+                        璁¢噺鍣ㄥ叿閰嶇疆淇℃伅
+                    </div>
+                    <div class="tab-li" :class="tab == 2 ? 'is-tab' : ''" @click="handleTab('2')">
+                        缁熻鎸囨爣
+                    </div>
+                    <div class="tab-li" :class="tab == 3 ? 'is-tab' : ''" @click="handleTab('3')">
+                        閲囬泦鎸囨爣
+                    </div>
+                    <!-- <el-radio-group v-model="tab">
+                                <el-radio-button label="1" @click="handleTab('1')"> 璁惧閰嶇疆</el-radio-button>
+                                <el-radio-button label="2" @click="handleTab('2')"> 閲囬泦鎸囨爣</el-radio-button>
+                            </el-radio-group>-->
+                </div>
+                <BaseCard :title="currentNode ? currentNode.label + '--鑺傜偣閰嶇疆' : '鏆傛棤鑺傜偣閰嶇疆'">
+                    <div>
+                        <div class="content-box" v-if="tab == '1'">
+                            <deviceConfig ref='deviceConfigRef' />
+                        </div>
+                        <div class="content-box" v-if="tab == '2'">
+                            <statisticalIndicators ref='statisticalIndicatorsRef' />
+                        </div>
+                        <div class="content-box" v-if="tab == '3'">
+                            <collectIndicators ref='collectIndicatorsRef' />
+                        </div>
+                    </div>
+                </BaseCard>
+            </div>
+        </div>
+
+        <treeNodeModal ref="treeNodeModalRef" @getList="getTreeList" :modelCode="modelData"
+            @addTreeList="addTreeList" @addTreeSelectList="addTreeSelectList" />
+        <modelManagementModal ref="modelManagementModalRef" @selectTreeFun="searchList" />
+
+    </div>
+</template>
+
+<script setup>
+import treeNodeModal from './components/TreeNodeModal.vue'
+import modelManagementModal from './components/modelManagement/modelManagement'
+import { listModel } from '@/api/modelConfiguration/businessModel'
+import { treeList, delModelNode, hasEnergyIndex, } from '@/api/modelConfiguration/indexWarehouse'
+import deviceConfig from './components/deviceConfig/DeviceConfig.vue'
+import collectIndicators from './components/collectIndicators/CollectIndicators.vue'
+import statisticalIndicators from './components/statisticalIndicators/statisticalIndicators.vue'
+import { nextTick } from 'vue';
+let { proxy } = getCurrentInstance()
+let modelManagementModalRef = ref(null)
+let treeLoading = ref(true)
+//妯″瀷绠$悊
+function handleModel() {
+    if (modelManagementModalRef.value) {
+        modelManagementModalRef.value.handleOpen()
+    }
+}
+let modelData = ref(null)
+let modelInfoOptions = ref([]) //涓嬫媺鍒楄〃
+//鑾峰彇涓嬫媺鍒楄〃
+function searchList(flag) {
+    listModel({ isShow: 1 }).then(response => {
+        modelInfoOptions.value = response.data;
+        if (flag) {
+            if (modelInfoOptions.value.length > 0) {
+                modelData.value = modelInfoOptions.value[0].modelCode;
+                getTreeList(modelData.value)
+            }
+        }
+    });
+}
+
+searchList(true)
+
+
+//涓嬫媺閫変腑瑙﹀彂鏍戝垪琛�
+function changeModel(item) {
+    // console.log('changeModel', item)
+    getTreeList(item)
+}
+
+let treeRef = ref(null)
+let treeData = ref([])
+
+const defaultProps = ref({
+    children: 'children',
+    label: 'label',
+})
+
+//妫�绱㈡爲
+let filterText = ref('')
+
+const filterNode = (value, data) => {
+    if (!value) return true
+    return data.label.includes(value)
+}
+watch(filterText, (val) => {
+    // 妫�鏌reeRef.value鏄惁鏄竴涓湁鏁堢殑ElTree瀹炰緥
+    if (treeRef.value && typeof treeRef.value.filter === 'function') {
+        // 璋冪敤filter鏂规硶
+        treeRef.value.filter(val);
+    } else {
+        // treeRef.value鏃犳晥锛屽鐞嗛敊璇�
+        console.error('error');
+    }
+});
+
+
+
+let currentNode = ref(null)
+let treeExpandData = ref([])
+let isFirstLeafNode = ref(false)
+let deviceConfigRef = ref(null)
+let collectIndicatorsRef = ref(null)
+let statisticalIndicatorsRef = ref(null)
+
+//鑾峰彇鏍戝垪琛�
+function getTreeList(modelCode) {
+    treeLoading.value = true
+    treeList({ modelCode }).then(res => {
+        treeLoading.value = false
+        let { data } = res
+        treeData.value = data;
+        let chooseNode = null;
+        if (data.length > 0) {
+            if (data[0].children && data[0].children.length !== 0 && isFirstLeafNode.value) {
+                if (data[0].children[0].children && data[0].children[0].children.length !== 0) {
+                    chooseNode = data[0].children[0].children[0];
+                } else {
+                    chooseNode = data[0].children[0];
+                }
+            } else {
+                chooseNode = data[0];
+            }
+            currentNode.value = chooseNode;
+            treeExpandData.value.push(chooseNode.id);
+            nextTick(() => {
+                treeRef.value.setCurrentKey(chooseNode.id);
+                if (tab.value == 1 && deviceConfigRef.value) {
+                    deviceConfigRef.value.getList(chooseNode)
+                }
+                if (tab.value == 2 && statisticalIndicatorsRef.value) {
+                    statisticalIndicatorsRef.value.getList(chooseNode)
+                }
+                if (tab.value == 3 && collectIndicatorsRef.value) {
+                    collectIndicatorsRef.value.getList(chooseNode)
+                }
+            });
+        }
+    })
+}
+//鏂板鍚庢洿鏂版爲鍒楄〃
+function addTreeList(newChild) {
+    treeData.value.push(newChild)
+}
+//鏂板鎴愬姛鍚庢洿鏂伴粯璁ら�変腑,骞惰Е鍙戞柟娉�
+function addTreeSelectList(addedNode, newChild) {
+    treeExpandData.value.push(addedNode.nodeId)
+    nextTick(() => {
+        treeRef.value.setCurrentNode(newChild)
+        currentNode.value = newChild;
+        if (tab.value == 1) {
+            if (deviceConfigRef.value) {
+                deviceConfigRef.value.getList(newChild)
+            }
+        }
+        if (tab.value == 2) {
+            if (statisticalIndicatorsRef.value) {
+                statisticalIndicatorsRef.value.getList(newChild)
+            }
+        }
+        if (tab.value == 3) {
+
+            if (collectIndicatorsRef.value) {
+                collectIndicatorsRef.value.getList(newChild)
+            }
+        }
+    })
+
+}
+//鏍戠偣鍑�
+function changeNode(data, node, ev) {
+    console.log('鏍戠偣鍑�', data)
+    currentNode.value = data;
+    if (tab.value == 1) {
+        if (deviceConfigRef.value) {
+            deviceConfigRef.value.getList(data)
+        }
+    }
+    if (tab.value == 2) {
+        if (statisticalIndicatorsRef.value) {
+            statisticalIndicatorsRef.value.getList(data)
+        }
+    }
+    if (tab.value == 3) {
+        if (collectIndicatorsRef.value) {
+            collectIndicatorsRef.value.getList(data)
+        }
+    }
+}
+
+let treeNodeModalRef = ref(null)
+function addNode(node, data) {
+    if (treeNodeModalRef.value) {
+        console.log('treeNodeModalRef', node, data)
+        treeNodeModalRef.value.handleOpen(node, data, true)
+    }
+}
+function editNode(node, data) {
+    if (treeNodeModalRef.value) {
+        treeNodeModalRef.value.handleOpen(node, data, false)
+    }
+}
+
+function delNode(node, data) {
+    if (data.children && data.children.length > 0) {
+        proxy.$modal.msgWarning('鍖呭惈瀛愯妭鐐癸紝涓嶈兘杩涜鍒犻櫎锛�')
+        return
+    }
+
+    hasEnergyIndex(data.id).then(response => {
+        if (response.data) {
+            proxy.$modal.msgWarning('褰撳墠鑺傜偣涓嬪瓨鍦ㄦ寚鏍囷紝涓嶈兘杩涜鍒犻櫎锛�')
+        } else {
+            proxy.$modal.confirm('鏄惁纭鍒犻櫎鍚嶄负"' + data.label + '"鐨勮妭鐐�?', '璀﹀憡', {
+                confirmButtonText: '纭畾',
+                cancelButtonText: '鍙栨秷',
+                type: 'warning'
+            })
+                .then(function () {
+                    return delModelNode(data.id)
+                })
+                .then(() => {
+                    let parent = node.parent
+                    let children = parent.data.children || parent.data
+                    let index = children.findIndex(d => d.id === data.id)
+                    children.splice(index, 1)
+
+                    nextTick(() => {
+                        if (parent.data && parent.data.id) {
+                            changeNode(parent.data)
+                        } else {
+                            changeNode(null)
+                        }
+                    })
+                    proxy.$modal.msgSuccess('鍒犻櫎鎴愬姛')
+                })
+                .catch(function () {
+                })
+        }
+    })
+}
+
+let tab = ref('1')
+function handleTab(value) {
+    tab.value = value
+    nextTick(() => {
+        if (value == 1 && deviceConfigRef.value) {
+            deviceConfigRef.value.getList(currentNode.value)
+        }
+        if (value == 2 && statisticalIndicatorsRef.value) {
+            statisticalIndicatorsRef.value.getList(currentNode.value)
+        }
+        if (value == 3 && collectIndicatorsRef.value) {
+            collectIndicatorsRef.value.getList(currentNode.value)
+        }
+    });
+
+}
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+.page-box {
+    height: calc(100vh - 145px);
+
+    .tree-box {
+        height: calc(100% - 70px);
+        overflow-y: auto !important;
+    }
+
+    .select-box {
+        display: flex;
+        align-items: center;
+
+        :deep .el-icon {
+            color: #fff;
+            margin: 0 10px 0 15px;
+            font-size: 20px;
+            // &:hover{
+            //     color: #3371EB;
+            // }
+        }
+    }
+
+    .node-opt {
+        flex: 1;
+        text-align: right;
+        margin-right: 5px;
+
+        :deep .el-icon {
+            color: #fff;
+            margin-right: 5px;
+        }
+    }
+
+
+}
+
+:deep .el-tabs__nav-wrap:after {
+    background: transparent;
+}
+
+:deep .el-tabs__item {
+    color: #fff;
+    font-size: 20px;
+    padding: 0 20px;
+
+    &.is-active,
+    &:hover {
+        color: #999 !important;
+    }
+}
+
+.tab-box {
+    display: flex;
+    align-items: center;
+    color: #fff;
+    border-bottom: 1px solid #3371EB;
+    margin-right: 20px;
+
+    .tab-li {
+        cursor: pointer;
+        border: 1px solid #3371EB;
+        padding: 10px 25px;
+        border-radius: 5px 5px 0 0;
+    }
+
+    .is-tab {
+        background: #3371EB;
+    }
+}
+
+.content-box {
+    height: calc(100vh - 306px) !important;
+
+}
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/businessmodel/components/TreeNodeModal.vue b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/TreeNodeModal.vue
new file mode 100644
index 0000000..7c2c138
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/TreeNodeModal.vue
@@ -0,0 +1,176 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="600" @close="handleClose" :close-on-click-modal="false">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="130px" v-loading="loading">
+            <el-form-item label="鐖剁骇鑺傜偣" v-if="addNode">
+                <span>{{ currentNode ? currentNode.name : "" }}</span>
+            </el-form-item>
+            <el-form-item label="鎸囨爣鑺傜偣缂栫爜" prop="code">
+                <el-input v-if="addNode" v-model="form.code" placeholder="璇疯緭鍏ョ紪鐮�" />
+                <span v-if="!addNode">{{ form.code }}</span>
+            </el-form-item>
+            <el-form-item label="鎸囨爣鑺傜偣鍚嶇О" prop="name">
+                <el-input v-model="form.name" placeholder="璇疯緭鍏ュ悕绉�" />
+            </el-form-item>
+            <el-form-item label="鑺傜偣绫诲瀷" prop="nodeCategory">
+                <span v-if="disabledSelect">{{ form.nodeCategory ? dictFilter(form.nodeCategory) : "" }}</span>
+                <template v-else>
+                    <el-select v-model="form.nodeCategory" placeholder="璇烽�夎妭鐐圭被鍨�" prop="nodeCategory" clearable>
+                        <el-option v-for="dict in nodeCategoryArr" :key="dict.value" :label="dict.label"
+                            :value="dict.value" />
+                    </el-select></template>
+            </el-form-item>
+            <div slot="footer" class="text-right">
+                <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+                <el-button @click="handleClose">鍙� 娑�</el-button>
+            </div>
+        </el-form>
+    </el-dialog>
+</template>
+<script setup>
+const { proxy } = getCurrentInstance();
+import { getModelNode, updateModelNode, addModelNode } from '@/api/modelConfiguration/indexWarehouse'
+const { sys_node_category } = proxy.useDict("sys_node_category");
+
+const props = defineProps(['modelCode'])
+function dictFilter(value) {
+    //鏍规嵁value杩囨护sys_node_category瀵硅薄
+    const items = sys_node_category.value.find(item => item.value === value);
+    return items ? items.label : ''
+}
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+    name: null,
+    code: null,
+    nodeCategory: null,
+
+})
+
+const formRules = {
+    code: [
+        { required: true, message: '缂栫爜涓嶈兘涓虹┖', trigger: 'blur' },
+        {
+            min: 2,
+            max: 20,
+            message: '闀垮害鍦� 2 鍒� 20 涓瓧绗�',
+            trigger: 'blur'
+        },
+        {
+            pattern: /^[a-zA-Z][A-Za-z0-9_-]+$/,
+            message: '蹇呴』涓烘暟瀛椼�佸瓧姣嶃��- 鎴朹 锛屼笖棣栧瓧绗﹀彧鑳戒负瀛楁瘝'
+        }
+    ],
+    name: [
+        { required: true, message: '鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' },
+        { min: 2, max: 15, message: '闀垮害鍦� 2 鍒� 15 涓瓧绗�', trigger: 'blur' }
+    ],
+    nodeCategory: [
+        { required: true, message: '璇烽�夋嫨鑺傜偣绫诲瀷涓嶈兘涓虹┖', trigger: 'change' }
+    ]
+}
+
+let addNode = ref(false)
+let currentSelectNode = ref(null)
+let currentNode = ref(null)
+let nodeCategoryArr = ref(null)
+let disabledSelect = ref(false)
+function handleOpen(node, data, addNodeValue) {
+    addNode.value = addNodeValue
+    disabledSelect.value = false;
+    console.log(node, data);
+    if (data) {
+        nodeCategoryArr.value = sys_node_category.value.filter(item => (item.value * 1) > (data.nodeCategory * 1));
+        console.log(2222, data.nodeCategory, nodeCategoryArr.value);
+    } else {
+        nodeCategoryArr.value = sys_node_category.value.filter(item => (item.value * 1) == 0);
+        console.log(1111, nodeCategoryArr.value);
+    }
+    if (addNodeValue) {
+        if (data) {
+            currentSelectNode.value = data;
+            getModelNode(data.id).then(response => {
+                visible.value = true;
+                title.value = "娣诲姞妯″瀷鑺傜偣";
+                currentNode.value = response.data;
+            });
+        } else {
+            visible.value = true;
+        }
+
+
+    } else {
+        currentSelectNode.value = data;
+        getModelNode(data.id).then(response => {
+            form.value = response.data;
+            visible.value = true;
+            title.value = "淇敼妯″瀷鑺傜偣";
+            disabledSelect.value = true;
+        });
+    }
+}
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            if (form.value.nodeId !== undefined) {
+                updateModelNode(form.value).then(response => {
+                    if (response.code === 200) {
+                        currentSelectNode.value.label = form.value.name;
+                        proxy.$modal.msgSuccess('淇敼鎴愬姛');
+                        visible.value = false;
+
+                    } else {
+                        proxy.$modal.msgError(response.msg);
+                    }
+                });
+            } else {
+                {
+                    form.value.parentId = currentNode.value ? currentNode.value.nodeId : ''
+                    form.value.address = (currentNode.value ? currentNode.value.address : '') + form.value.code + ','
+                    form.value.modelCode = props.modelCode
+                    addModelNode(form.value).then(response => {
+                        if (response.code === 200) {
+                            proxy.$modal.msgSuccess('鏂板鎴愬姛')
+                            visible.value = false
+                            const addedNode = response.data
+                            const newChild = {
+                                id: addedNode.nodeId,
+                                label: addedNode.name,
+                                nodeCategory:addedNode.nodeCategory,
+                                children: []
+                            }
+                            if (currentSelectNode.value) {
+                                if (!currentSelectNode.value.children) {
+                                    currentSelectNode.value.children = []
+                                }
+                                currentSelectNode.value.children.push(newChild)
+                            } else {
+                                emit("addTreeList", newChild)
+                            }
+                        } else {
+                            proxy.$modal.msgError(response.msg)
+                        }
+                    })
+                }
+            }
+        }
+    })
+}
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        name: null,
+        code: null,
+        nodeCategory: null,
+    }
+}
+
+defineExpose({ handleOpen })
+
+let emit = defineEmits(['addTreeList', 'addTreeSelectList'])
+
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/modelconfiguration/businessmodel/components/collectIndicators/CollectIndicators.vue b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/collectIndicators/CollectIndicators.vue
new file mode 100644
index 0000000..aa288e9
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/collectIndicators/CollectIndicators.vue
@@ -0,0 +1,105 @@
+<template>
+    <div class="table-box">
+        <el-button class="mb20 " type="primary" icon="Plus" @click="handleAdd">
+            鏂板
+        </el-button>
+        <el-button class="mb20 " icon="Delete" :disabled="multiple" @click="handleDel">
+            鎵归噺鍒犻櫎
+        </el-button>
+        <el-table v-loading="loading" :data="tableData" height="calc(100vh - 370px)"
+            @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" show-overflow-tooltip />
+            <el-table-column label="鎸囨爣缂栫爜" prop="code" show-overflow-tooltip align="center" />
+            <el-table-column label="鎸囨爣鍚嶇О" prop="name" show-overflow-tooltip align="center" />
+            <el-table-column label="鎿嶄綔" align="center">
+                <template #default="scope">
+                    <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                        鍒犻櫎
+                    </el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <collectModal ref="collectModalRef" @selectedCollect='selectedCollect' />
+    </div>
+</template>
+<script setup>
+let { proxy } = getCurrentInstance()
+import { getSettingIndex, setNodeToIndex, delIndex } from '@/api/modelConfiguration/businessModel'
+import collectModal from './CollectModal.vue'
+let tableData = ref([])
+let loading = ref(false)
+let currentNode = ref(null)
+function getList(modelNode) {
+    if (!modelNode) {
+        tableData.value = []
+        return
+    }
+    currentNode.value = modelNode
+    loading.value = true;
+    getSettingIndex(modelNode.id).then(response => {
+        if (response.code === 200) {
+            let result = response.data;
+            tableData.value = result.filter(f => f.indexType === 'COLLECT');
+            loading.value = false
+        } else {
+            proxy.$modal.msgError(response.msg);
+        }
+        this.deviceLoading = false;
+    });
+}
+
+let collectModalRef = ref(null)
+function handleAdd() {
+    if (collectModalRef.value) {
+        collectModalRef.value.currentNode = currentNode.value
+        collectModalRef.value.handleOpen(tableData.value)
+    }
+}
+
+function selectedCollect(selectedIndex) {
+    tableData.value = selectedIndex
+    let indexIds = selectedIndex.map(item => item.indexId)
+    setNodeToIndex(currentNode.value.id, indexIds, 'COLLECT').then((response) => {
+        if (response.code !== 200) {
+            proxy.$modal.msgError(response.msg)
+        }
+    })
+}
+// 閫変腑鏁扮粍
+let ids = ref([])
+let names = ref([])
+// 闈炲涓鐢�
+let multiple = ref(true)
+function handleDel(row) {
+    const indexIds = row && row.indexId ? [row.indexId] : ids.value
+    const indexNames = row.name || names.value
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鎸囨爣鍚嶄负"' + indexNames + '"鐨勬暟鎹」?', '璀﹀憡').then(function () {
+        return delIndex(currentNode.value.id, indexIds);
+    }).then((response) => {
+        if (response.code === 200) {
+            // 鍒犻櫎鎴愬姛锛屾洿鏂拌〃鏍兼暟鎹�
+            getList(currentNode.value)
+            // tableData.value = tableData.value.filter(f => f.indexId !== row.indexId)
+            proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        } else {
+            // 鍒犻櫎澶辫触锛屾樉绀洪敊璇秷鎭�
+            proxy.$modal.msgError(response.msg);
+        }
+    }).catch(() => {
+    });
+}
+
+
+function handleSelectionChange(selection) {
+    ids.value = selection.map(item => item.indexId);
+    names.value = selection.map(item => item.name);
+    multiple.value = !selection.length
+}
+defineExpose({
+    getList
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/businessmodel/components/collectIndicators/CollectModal.vue b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/collectIndicators/CollectModal.vue
new file mode 100644
index 0000000..b245aa8
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/collectIndicators/CollectModal.vue
@@ -0,0 +1,156 @@
+<template>
+    <el-dialog v-model="visible" title="閫夋嫨閲囬泦鎸囨爣" width="900" @close="handleClose" destroy-on-close
+        :close-on-click-modal="false" :close-on-press-escape="false">
+        <el-form :model="form" :inline="true" label-width="80px">
+            <el-form-item label="鎸囨爣缂栫爜" prop="code">
+                <el-input v-model="form.code" placeholder="鎸囨爣缂栫爜" />
+            </el-form-item>
+            <el-form-item label="鎸囨爣鍚嶇О" prop="name">
+                <el-input v-model="form.name" placeholder="璇疯緭鍏ユ寚鏍囧悕绉�" />
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+        </el-form>
+        <el-table v-loading="loading" :data="tableData" row-key="indexId" @select="handleSelectChange"
+            @select-all="handleSelectionAllChange" height="50vh" ref="collectListTable">
+            <el-table-column type="selection" width="55" align="center" row-key="indexId" :reserve-selection="true" />
+            <el-table-column label="鎸囨爣缂栫爜" align="center" prop="code" />
+            <el-table-column label="鎸囨爣鍚嶇О" align="center" prop="name" />
+            <el-table-column label="鍗曚綅" align="center" prop="unitId"
+                :formatter="(row, c) => proxy.selectDictLabel(sys_unit, row.unitId)" />
+        </el-table>
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize" @pagination="getList" />
+
+
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="handleOk">纭畾</el-button>
+            <el-button @click="handleClose">鍙栨秷</el-button>
+        </div>
+    </el-dialog>
+</template>
+<script setup>
+
+import { collectList } from '@/api/modelConfiguration/businessModel'
+let { proxy } = getCurrentInstance()
+let { sys_unit } = proxy.useDict('sys_unit')
+
+let visible = ref(false)
+let tableData = ref([])
+let loading = ref(false)
+let total = ref(0)
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+
+})
+let form = ref({
+    name: null,
+    code: null
+})
+let currentNode = ref(null)
+let collectListTable = ref(null)
+function getList() {
+    loading.value = true;
+    collectList(currentNode.value.id, { ...queryParams.value, ...form.value },).then(response => {
+        tableData.value = response.rows
+        total.value = response.total
+        loading.value = false;
+        nextTick(function () {
+            collectListTable.value.data.forEach(rowData => {
+                let exist = selectedCollect.value.filter(
+                    f => f.indexId === rowData.indexId).length;
+                if (exist > 0) {
+                    collectListTable.value.toggleRowSelection(rowData, true);
+                }
+            });
+        });
+    })
+}
+
+
+let selectedCollect = ref([])
+function handleOpen(selectRow) {
+    selectedCollect.value = JSON.parse(JSON.stringify(selectRow))
+    visible.value = true
+    queryParams.value = {
+        pageNum: 1,
+        pageSize: 10,
+    }
+    resetQuery()
+}
+// collectList
+
+function handleClose() {
+    visible.value = false
+}
+
+//鍒ゆ柇涓�涓� 鏄叏閫夎繕鏄叏閫夊彇娑�
+function formatterSelect(selection, data) {
+    let idMap = {};
+    selection.forEach(item => {
+        idMap[item.indexId] = true;
+    });
+    let allIn = data.every(item => idMap.hasOwnProperty(item.indexId));
+    return allIn;
+}
+function handleSelectionAllChange(selection) {
+    if (formatterSelect(selection, tableData.value)) {
+        selection.forEach(row => {
+            let rowStatus = selectedCollect.value.filter(f => f.indexId == row.indexId).length;
+            if (rowStatus === 0) {
+                selectedCollect.value.push({
+                    "indexId": row.indexId,
+                    "code": row.code,
+                    "name": row.name
+                });
+            }
+        });
+    } else {
+        selectedCollect.value = selectedCollect.value.filter(f => {
+            return !tableData.value.some(s => s.indexId == f.indexId);
+        });
+    }
+}
+
+function handleSelectChange(selection, row) {
+    let rowStatus = selection.filter(f => f.indexId === row.indexId).length;
+    if (rowStatus > 0) {
+        selectedCollect.value.push({
+            "indexId": row.indexId,
+            "code": row.code,
+            "name": row.name
+        });
+    } else {
+        selectedCollect.value = selectedCollect.value.filter(
+            f => f.indexId !== row.indexId);
+    }
+}
+
+function handleOk() {
+    emit('selectedCollect', selectedCollect.value)
+    visible.value = false
+}
+
+
+
+function handleQuery() {
+    queryParams.value.pageNum = 1;
+    getList();
+}
+function resetQuery() {
+    queryParams.value.pageNum = 1;
+    form.value = {
+        code: null,
+        name: null,
+    }
+    getList();
+}
+
+defineExpose({ handleOpen, currentNode })
+let emit = defineEmits(['selectedCollect'])
+</script>
+
+<style lang='scss' scoped></style>
diff --git a/zhitan-vue/src/views/modelconfiguration/businessmodel/components/deviceConfig/DeviceConfig.vue b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/deviceConfig/DeviceConfig.vue
new file mode 100644
index 0000000..e45d77a
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/deviceConfig/DeviceConfig.vue
@@ -0,0 +1,84 @@
+<template>
+
+    <div class="table-box">
+        <el-button class="mb20 " type="primary" icon="Plus" @click="handleAdd">
+            鏂板
+        </el-button>
+        <el-table v-loading="loading" :data="tableData" height="calc(100vh - 370px)">
+            <el-table-column label="鍣ㄥ叿缂栫爜" prop="code" show-overflow-tooltip align="center" />
+            <el-table-column label="鍣ㄥ叿鍚嶇О" prop="meterName" show-overflow-tooltip align="center" />
+            <el-table-column label="閰嶇數瀹�" align="center" show-overflow-tooltip prop="installactionLocation" />
+            <el-table-column label="鎿嶄綔" class-name="small-padding fixed-width" align="center">
+                <template #default="scope">
+                    <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                        鍒犻櫎
+                    </el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <deviceModal ref="deviceModalRef" @deviceConfirmSelect='deviceConfirmSelect' />
+    </div>
+</template>
+<script setup>
+let { proxy } = getCurrentInstance()
+import { getSettingDevice, setDevice, delDevice } from '@/api/modelConfiguration/businessModel'
+import deviceModal from './DeviceModal.vue'
+let tableData = ref([])
+let loading = ref(false)
+let currentNode = ref(null)
+function getList(modelNode) {
+    if (!modelNode) {
+        tableData.value = []
+        return
+    }
+    currentNode.value = modelNode
+    loading.value = true;
+    getSettingDevice(modelNode.id).then((response) => {
+        if (response.code === 200) {
+            tableData.value = response.data;
+            loading.value = false;
+        } else {
+            proxy.$modal.msgError(response.msg);
+        }
+    });
+}
+
+function handleDel(row) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+        return delDevice(currentNode.value.id, row.id); // 鐩存帴璋冪敤delDevice锛岃�屼笉鏄痟andleDelDevice
+    }).then((response) => {
+        if (response.code === 200) {
+            // 鍒犻櫎鎴愬姛锛屾洿鏂拌〃鏍兼暟鎹�
+            tableData.value = tableData.value.filter(f => f.id !== row.id);
+            proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        } else {
+            // 鍒犻櫎澶辫触锛屾樉绀洪敊璇秷鎭�
+            proxy.$modal.msgError(response.msg);
+        }
+    }).catch(() => {
+    });
+}
+
+let deviceModalRef = ref(null)
+function handleAdd() {
+    if (deviceModalRef.value) {
+        deviceModalRef.value.handleOpen(tableData.value)
+    }
+}
+function deviceConfirmSelect(selectedDevice) {
+    tableData.value = selectedDevice;
+    let deviceIds = selectedDevice.map(item => item.id);
+    setDevice(currentNode.value.id, deviceIds).then((response) => {
+        if (response.code !== 200) {
+            proxy.$modal.msgError(response.msg)
+        }
+    });
+}
+defineExpose({
+    getList
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/businessmodel/components/deviceConfig/DeviceModal.vue b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/deviceConfig/DeviceModal.vue
new file mode 100644
index 0000000..bca55bc
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/deviceConfig/DeviceModal.vue
@@ -0,0 +1,176 @@
+<template>
+    <el-dialog v-model="visible" title="閫夋嫨璁惧" width="1100" @close="handleClose" destroy-on-close
+        :close-on-click-modal="false" :close-on-press-escape="false">
+        <div class="form-card ">
+            <el-form :model="queryParams" :inline="true" label-width="48px">
+                <el-form-item label="缂栫爜" prop="code">
+                    <el-input v-model="queryParams.code" placeholder="杈撳叆缂栫爜" />
+                </el-form-item>
+                <el-form-item label="鍚嶇О" prop="meterName">
+                    <el-input v-model="queryParams.meterName" placeholder="杈撳叆鍚嶇О" />
+                </el-form-item>
+                <el-form-item label="绉嶇被" prop="meterType">
+                    <el-select v-model="queryParams.meterType" placeholder="閫夋嫨绉嶇被" style="width: 170px;">
+                        <el-option v-for="dict in sys_device_type" :key="dict.value" :label="dict.label"
+                            :value="dict.value" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class='table-box'>
+            <el-table v-loading="loading" row-key="id" :data="tableData" @select="handleSelectChange"
+                @select-all="handleSelectionAllChange" height="50vh" ref="deviceSettingTable">
+                <el-table-column type="selection" width="55" align="center" row-key="id" :reserve-selection="true" />
+                <el-table-column label="鍣ㄥ叿缂栫爜" prop="code" show-overflow-tooltip align="center" />
+                <el-table-column label="鍣ㄥ叿鍚嶇О" prop="meterName" show-overflow-tooltip align="center" />
+                <el-table-column label="鍣ㄥ叿绉嶇被" prop="meterType" :formatter="meterTypeFormat" show-overflow-tooltip
+                    align="center" />
+                <!-- <el-table-column label="瑙勬牸鍨嬪彿" prop="modelNumber" show-overflow-tooltip align="center" /> -->
+                <el-table-column label="閰嶇數瀹�" prop="installactionLocation" show-overflow-tooltip align="center" />
+                <!-- <el-table-column label="鐘舵��" prop="meterStatus" :formatter="meterStatusFormat" show-overflow-tooltip
+                    align="center" /> -->
+            </el-table>
+        </div>
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize" @pagination="getList" />
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="handleOk">纭畾</el-button>
+            <el-button @click="handleClose">鍙栨秷</el-button>
+        </div>
+
+    </el-dialog>
+
+</template>
+<script setup>
+import { listImplement } from '@/api/modelConfiguration/businessModel'
+import { ref } from 'vue';
+let visible = ref(false)
+let { proxy } = getCurrentInstance()
+const { sys_device_type } = proxy.useDict("sys_device_type");
+const { meter_status } = proxy.useDict("meter_status");
+
+// 绉嶇被瀛楀吀缈昏瘧
+function meterTypeFormat(row, column) {
+    return proxy.selectDictLabel(sys_device_type.value, row.meterType);
+}
+// 鐘舵�佸瓧鍏哥炕璇�
+function meterStatusFormat(row, column) {
+    return proxy.selectDictLabel(meter_status.value, row.meterStatus);
+}
+
+let tableData = ref([])
+let total = ref(0);
+let queryParams = ref({
+    code: null,
+    meterName:null,
+    meterType: null,
+    pageNum: 1,
+    pageSize: 10,
+})
+let loading = ref(false)
+let deviceSettingTable = ref(null)
+
+function getList() {
+    loading.value = true;
+    listImplement(queryParams.value).then(response => {
+        tableData.value = response.rows
+        total.value = response.total
+        loading.value = false
+        nextTick(function () {
+            deviceSettingTable.value.data.forEach(rowData => {
+                let exist = selectedDevice.value.filter(f => f.id === rowData.id).length;
+                if (exist > 0) {
+                    deviceSettingTable.value.toggleRowSelection(rowData, true);
+                }
+            });
+        });
+    })
+}
+
+function handleClose() {
+    visible.value = false
+}
+function handleQuery() {
+    queryParams.value.pageNum = 1;
+    getList();
+}
+function resetQuery() {
+    queryParams.value = {
+        code: null,
+        meterName:null,
+        meterType: null,
+        pageNum: 1,
+        pageSize: 10,
+    }
+    getList();
+}
+let selectedDevice = ref([])
+function handleOpen(selectRow) {
+    selectedDevice.value = JSON.parse(JSON.stringify(selectRow))
+    visible.value = true
+    queryParams.value = {
+        pageNum: 1,
+        pageSize: 10,
+
+    }
+    getList()
+}
+
+//鍒ゆ柇涓�涓� 鏄叏閫夎繕鏄叏閫夊彇娑�
+function formatterSelect(selection, data) {
+    let idMap = {};
+    selection.forEach(item => {
+        idMap[item.id] = true;
+    });
+    let allIn = data.every(item => idMap.hasOwnProperty(item.id));
+    return allIn;
+}
+function handleSelectionAllChange(selection) {
+    if (formatterSelect(selection, tableData.value)) {
+        selection.forEach(row => {
+            let rowStatus = selectedDevice.value.filter(f => f.id == row.id).length;
+            if (rowStatus === 0) {
+                selectedDevice.value.push({
+                    "id": row.id,
+                    "code": row.code,
+                    "meterName": row.meterName
+                });
+            }
+        });
+    } else {
+        selectedDevice.value = selectedDevice.value.filter(f => {
+            return !tableData.value.some(s => s.id == f.id);
+        });
+    }
+}
+
+function handleSelectChange(selection, row) {
+    let rowStatus = selection.filter(f => f.id === row.id).length;
+    if (rowStatus > 0) {
+        selectedDevice.value.push({
+            "id": row.id,
+            "code": row.code,
+            "meterName": row.meterName
+        });
+    } else {
+        selectedDevice.value = selectedDevice.value.filter(f => f.id !== row.id);
+    }
+}
+
+function handleOk() {
+    emit('deviceConfirmSelect', selectedDevice.value)
+    visible.value = false
+}
+defineExpose({ handleOpen });
+let emit = defineEmits(['deviceConfirmSelect'])
+</script>
+
+
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/businessmodel/components/modelManagement/AddModelManagement.vue b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/modelManagement/AddModelManagement.vue
new file mode 100644
index 0000000..47748e4
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/modelManagement/AddModelManagement.vue
@@ -0,0 +1,103 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="500" @close="handleClose" destroy-on-close
+        :close-on-click-modal="false" :close-on-press-escape="false">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="110px">
+            <el-form-item label="妯″瀷缂栫爜" prop="modelCode">
+                <el-input v-model="form.modelCode" placeholder="璇疯緭鍏ユā鍨嬬紪鐮�" v-if="addOpt" />
+                <span v-if="!addOpt">{{ form.modelCode }}</span>
+            </el-form-item>
+            <el-form-item label="妯″瀷鍚嶇О" prop="modelName">
+                <el-input v-model="form.modelName" placeholder="璇疯緭鍏ユā鍨嬪悕绉�" />
+            </el-form-item>
+            <el-form-item label="妯″瀷绫诲瀷" prop="modelType">
+                <el-select v-model="form.modelType" placeholder="璇烽�夋嫨妯″瀷绫诲瀷" v-if="addOpt">
+                    <el-option v-for="dict in props.sys_model_type" :key="dict.value" :label="dict.label"
+                        :value="dict.value" />
+                </el-select>
+                <span v-if="!addOpt">{{ proxy.selectDictLabel(props.sys_model_type, form.modelType) }}</span>
+            </el-form-item>
+        </el-form>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+            <el-button @click="handleClose">鍙� 娑�</el-button>
+        </div>
+    </el-dialog>
+</template>
+<script setup>
+import { getModel, addModel, updateModel } from '@/api/modelConfiguration/businessModel'
+let { proxy } = getCurrentInstance()
+import { inject } from "vue";
+
+let props = defineProps(['sys_model_type'])
+let visible = ref(false)
+let title = ref(null)
+let addOpt = ref(false)
+let loading = ref(false)
+let emit = defineEmits(['getList'])
+let form = ref({ modelCode: null, modelName: null, modelType: null })
+const formRules = {
+    modelCode: [
+        { required: true, message: "妯″瀷缂栫爜涓嶈兘涓虹┖", trigger: "blur" },
+        { min: 2, max: 20, message: '闀垮害鍦� 2 鍒� 20 涓瓧绗�', trigger: 'blur' },
+        { pattern: /^[a-zA-Z][A-Za-z0-9_-]+$/, message: '蹇呴』涓烘暟瀛椼�佸瓧姣嶃��- 鎴朹 锛屼笖棣栧瓧绗﹀彧鑳戒负瀛楁瘝' }
+    ],
+    modelName: [
+        { required: true, message: "妯″瀷鍚嶇О涓嶈兘涓虹┖", trigger: "blur" },
+        { min: 2, max: 20, message: '闀垮害鍦� 2 鍒� 20 涓瓧绗�', trigger: 'blur' }
+    ],
+    modelType: [
+        { required: true, message: "蹇呴』閫夋嫨妯″瀷绫诲瀷", trigger: "blur" }
+    ],
+}
+function handleOpen(row) {
+    if (row && row.modelCode) {
+        addOpt.value = false
+        getModel(row.modelCode).then(response => {
+            form.value = response.data;
+            visible.value = true;
+            title.value = "淇敼妯″瀷";
+        });
+    } else {
+        addOpt.value = true
+        title.value = "娣诲姞妯″瀷";
+        visible.value = true
+    }
+
+}
+function handleClose() {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        modelCode: null, modelName: null, modelType: null
+    }
+}
+
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            loading.value = true
+            let obj = addOpt.value ? addModel(form.value) : updateModel(form.value)
+            obj.then((res) => {
+                if (res.code == 200) {
+                    proxy.$modal.msgSuccess(res.msg);
+                    emit('getList', false) //inject鏃犳晥鎹釜鏂瑰紡鍒锋柊绁栫粍浠跺垪琛�
+                } else {
+                    proxy.$modal.msgError(res.msg);
+                }
+
+            }).catch((err) => {
+
+            }).finally(() => {
+                handleClose()
+            });
+        }
+    })
+}
+
+
+
+defineExpose({ handleOpen })
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/modelconfiguration/businessmodel/components/modelManagement/ModelManagement.vue b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/modelManagement/ModelManagement.vue
new file mode 100644
index 0000000..ac26d6c
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/modelManagement/ModelManagement.vue
@@ -0,0 +1,80 @@
+<template>
+    <el-dialog v-model="visible" title="妯″瀷绠$悊" width="1000" @close="handleClose" destroy-on-close
+        :close-on-click-modal="false" :close-on-press-escape="false">
+        <el-button class="mt10 mb10 ml10" type="primary" icon="plus" @click="handleAdd">鏂板</el-button>
+        <el-table :data="tableData" v-loading="loading" style="width: 100%;height: 70vh">
+            <el-table-column label="搴忓彿" type="index" width="80" align="center" />
+            <el-table-column label="妯″瀷缂栫爜" align="center" prop="modelCode" />
+            <el-table-column label="妯″瀷鍚嶇О" align="center" prop="modelName" />
+            <el-table-column label="绫诲瀷" align="center" prop="modelType" :formatter="modelTypeFormat" />
+            <el-table-column fixed="right" label="鎿嶄綔" align="center" width="150">
+                <template #default="scope">
+                    <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row)">
+                        淇敼
+                    </el-button>
+                    <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                        鍒犻櫎
+                    </el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <addModelManagement ref="addRef" :sys_model_type="sys_model_type" @getList="getList" />
+    </el-dialog>
+</template>
+<script setup>
+let { proxy } = getCurrentInstance()
+import { listModel, delModel } from '@/api/modelConfiguration/businessModel'
+import addModelManagement from './addModelManagement'
+let { sys_model_type } = proxy.useDict("sys_model_type");
+
+let visible = ref(false)
+let loading = ref(false)
+function handleOpen() {
+    visible.value = true
+    getList()
+}
+
+function handleClose() {
+    visible.value = false
+}
+
+let tableData = ref([])
+function getList(flag) {
+    if (!flag) {
+        emit('selectTreeFun', false)
+    }
+    loading.value = true
+    listModel().then(res => {
+        loading.value = false
+        tableData.value = res.data
+    })
+}
+let addRef = ref(null)
+function handleAdd(row) {
+    if (addRef.value) {
+        addRef.value.handleOpen(row)
+    }
+}
+
+function handleDel(row) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎妯″瀷缂栧彿涓�"' + row.modelCode + '"鐨勬暟鎹」?', "璀﹀憡", {
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        type: "warning"
+    }).then(function () {
+        return delModel(row.modelCode);
+    }).then(() => {
+        getList();
+        emit('selectTreeFun', true)
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    }).catch(function () {
+    });
+}
+function modelTypeFormat(row, column) {
+    return proxy.selectDictLabel(sys_model_type.value, row.modelType);
+}
+let emit = defineEmits(['selectTreeFun'])
+defineExpose({ handleOpen })
+</script>
+
+<style lang='scss' scoped></style>
diff --git a/zhitan-vue/src/views/modelconfiguration/businessmodel/components/statisticalIndicators/StatisticModal.vue b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/statisticalIndicators/StatisticModal.vue
new file mode 100644
index 0000000..a7c36f8
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/statisticalIndicators/StatisticModal.vue
@@ -0,0 +1,231 @@
+<template>
+    <el-dialog v-model="visible" title="閫夋嫨缁熻鎸囨爣" width="1100" @close="handleClose" destroy-on-close
+        :close-on-click-modal="false" :close-on-press-escape="false">
+        <el-row :gutter="24">
+            <el-col :span="6">
+                <el-tree ref="treeRef" :props="defaultProps" :data="treeData" node-key="id" highlight-current
+                    :default-expanded-keys="treeExpandData" :expand-on-click-node="false" @node-click="changeNode"
+                    accordion>
+                    <template #default="{ node, data }">
+                        <span>
+                            <el-tooltip v-if="node.label.length > 6" class="item" effect="dark" :content="node.label"
+                                placement="top-end">
+                                <span>{{ node.label.slice(0, 6) + "..." }}</span>
+                            </el-tooltip>
+                            <span v-else id="b">{{ node.label }}</span>
+                        </span>
+
+                    </template>
+                </el-tree>
+            </el-col>
+            <el-col :span="18">
+                <el-form :model="queryParams" :inline="true" label-width="120px">
+                    <el-form-item label="鎸囨爣鍚嶇О/缂栫爜" prop="name">
+                        <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ユ寚鏍囧悕绉�/缂栫爜" />
+                    </el-form-item>
+                    <el-form-item label="鎸囨爣鍒嗙被" prop="indexCategory">
+                        <el-select v-model="queryParams.indexCategory" clearable placeholder="閫夋嫨绉嶇被"
+                            style="width: 170px;">
+                            <el-option v-for="dict in sys_index_category" :key="dict.value" :label="dict.label"
+                                :value="dict.value" />
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item>
+                        <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                        <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                    </el-form-item>
+                </el-form>
+
+                <el-table v-loading="loading" :data="tableData" row-key="indexId" @select="handleSelectChange"
+                    @select-all="handleSelectionAllChange" height="50vh" ref="energySettingTable">
+                    <el-table-column type="selection" width="55" align="center" row-key="indexId"
+                        :reserve-selection="true" />
+                    <el-table-column label="鎸囨爣缂栫爜" align="center" prop="code" />
+                    <el-table-column label="鎸囨爣鍚嶇О" align="center" prop="name" />
+                    <el-table-column label="鍗曚綅" align="center" prop="unitId"
+                        :formatter="(row, c) => proxy.selectDictLabel(sys_unit, row.unitId)" />
+                </el-table>
+                <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                    v-model:limit="queryParams.pageSize" @pagination="getList" />
+            </el-col>
+        </el-row>
+
+
+
+
+
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="handleOk">纭畾</el-button>
+            <el-button @click="handleClose">鍙栨秷</el-button>
+        </div>
+
+
+
+    </el-dialog>
+</template>
+<script setup>
+let { proxy } = getCurrentInstance()
+let { sys_index_category } = proxy.useDict('sys_index_category')
+let { sys_unit } = proxy.useDict('sys_unit')
+import { listEnergyindex, treeList } from '@/api/modelConfiguration/indexWarehouse'
+let treeRef = ref(null)
+let treeData = ref([])
+let treeExpandData = ref([])
+const defaultProps = ref({
+    children: 'children',
+    label: 'label',
+})
+
+//鏍戠偣鍑�
+function changeNode(data, node, ev) {
+    getList(data)
+    //   if (statisticalIndicatorManagementRef.value) {
+    //     statisticalIndicatorManagementRef.value.getList(data)
+    //   }
+}
+
+let isFirstLeafNode = ref(false)
+//鑾峰彇鏍戝垪琛�
+function getTreeList() {
+    treeList({ modelCode: "JCZBK_CODE" }).then(res => {
+        let { data } = res
+        treeData.value = data;
+        let chooseNode = null;
+        if (data.length > 0) {
+            if (data[0].children && data[0].children.length !== 0 && isFirstLeafNode.value) {
+                if (data[0].children[0].children && data[0].children[0].children.length !== 0) {
+                    chooseNode = data[0].children[0].children[0];
+                } else {
+                    chooseNode = data[0].children[0];
+                }
+            } else {
+                chooseNode = data[0];
+            }
+            treeExpandData.value.push(chooseNode.id);
+            nextTick(() => {
+                treeRef.value.setCurrentKey(chooseNode.id);
+                getList(chooseNode)
+            });
+        }
+    })
+}
+
+
+let loading = ref(false)
+let visible = ref(false)
+let tableData = ref([])
+let total = ref(0);
+let queryParams = ref({
+    name: null,
+    indexCategory: null,
+    pageNum: 1,
+    pageSize: 10,
+})
+
+let selectedStatistic = ref([])
+function handleOpen(selectRow) {
+    getTreeList()
+    selectedStatistic.value = JSON.parse(JSON.stringify(selectRow))
+    visible.value = true
+}
+
+let energySettingTable = ref(null)
+let currentNode = ref(null)
+function getList(modelNode) {
+    currentNode.value = modelNode;
+    if (modelNode) {
+        loading.value = true;
+        queryParams.value.nodeId = modelNode.id;
+        queryParams.value.indexType = 'STATISTIC';
+        listEnergyindex(queryParams.value).then(response => {
+            tableData.value = response.rows;
+            total.value = response.total;
+            loading.value = false;
+            nextTick(function () {
+                energySettingTable.value.data.forEach(rowData => {
+                    let exist = selectedStatistic.value.filter(
+                        f => f.indexId === rowData.indexId).length;
+                    if (exist > 0) {
+                        energySettingTable.value.toggleRowSelection(rowData, true);
+                    }
+                });
+            });
+        });
+    } else {
+        tableData.value = [];
+    }
+}
+function handleQuery() {
+    queryParams.value.pageNum = 1;
+    getList(currentNode.value)
+}
+function resetQuery() {
+    queryParams.value = {
+        name: null,
+        indexCategory: null,
+        pageNum: 1,
+        pageSize: 10,
+    }
+    getList(currentNode.value)
+}
+
+function handleClose() {
+    visible.value = false
+}
+
+
+
+//鍒ゆ柇涓�涓� 鏄叏閫夎繕鏄叏閫夊彇娑�
+function formatterSelect(selection, data) {
+    let idMap = {};
+    selection.forEach(item => {
+        idMap[item.indexId] = true;
+    });
+    let allIn = data.every(item => idMap.hasOwnProperty(item.indexId));
+    return allIn;
+}
+function handleSelectionAllChange(selection) {
+    if (formatterSelect(selection, tableData.value)) {
+        selection.forEach(row => {
+            let rowStatus = selectedStatistic.value.filter(f => f.indexId == row.indexId).length;
+            if (rowStatus === 0) {
+                selectedStatistic.value.push({
+                    "indexId": row.indexId,
+                    "code": row.code,
+                    "name": row.name
+                });
+            }
+        });
+    } else {
+        selectedStatistic.value = selectedStatistic.value.filter(f => {
+            return !tableData.value.some(s => s.indexId == f.indexId);
+        });
+    }
+}
+function handleSelectChange(selection, row) {
+    let rowStatus = selection.filter(f => f.indexId === row.indexId).length;
+    if (rowStatus > 0) {
+        selectedStatistic.value.push({
+            "indexId": row.indexId,
+            "code": row.code,
+            "name": row.name
+        });
+    } else {
+        selectedStatistic.value = selectedStatistic.value.filter(
+            f => f.indexId !== row.indexId);
+    }
+
+}
+
+function handleOk() {
+    emit('statisticIndexConfirmSelect', selectedStatistic.value)
+    visible.value = false
+}
+
+defineExpose({ handleOpen });
+let emit = defineEmits(['statisticIndexConfirmSelect'])
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/businessmodel/components/statisticalIndicators/statisticalIndicators.vue b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/statisticalIndicators/statisticalIndicators.vue
new file mode 100644
index 0000000..076edcf
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/businessmodel/components/statisticalIndicators/statisticalIndicators.vue
@@ -0,0 +1,106 @@
+<template>
+    <div class="table-box">
+        <el-button class="mb20 " type="primary" icon="Plus" @click="handleAdd">
+            鏂板
+        </el-button>
+        <el-button class="mb20 " icon="Delete" :disabled="multiple" @click="handleDel">
+            鎵归噺鍒犻櫎
+        </el-button>
+        <el-table v-loading="loading" :data="tableData" height="calc(100vh - 370px)"
+            @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="55" align="center" show-overflow-tooltip />
+            <el-table-column label="鎸囨爣缂栫爜" align="center" prop="code" />
+            <el-table-column label="鎸囨爣鍚嶇О" align="center" prop="name" />
+            <el-table-column label="鎿嶄綔" align="center">
+                <template #default="scope">
+                    <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                        鍒犻櫎
+                    </el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <statisticModal ref="statisticModalRef" @statisticIndexConfirmSelect="statisticIndexConfirmSelect" />
+    </div>
+</template>
+<script setup>
+let { proxy } = getCurrentInstance()
+import { getSettingIndex, setNodeToIndex, delIndex } from '@/api/modelConfiguration/businessModel'
+import statisticModal from './StatisticModal.vue'
+let tableData = ref([])
+let loading = ref(false)
+let currentNode = ref(null)
+
+function getList(modelNode) {
+    if (!modelNode) {
+        tableData.value = []
+        return
+    }
+    currentNode.value = modelNode
+    loading.value = true;
+    getSettingIndex(modelNode.id).then((response) => {
+        if (response.code === 200) {
+            let result = response.data;
+            tableData.value = result.filter(f => f.indexType === 'STATISTIC');
+            loading.value = false
+        } else {
+            proxy.$modal.msgError(response.msg);
+        }
+    });
+}
+
+let statisticModalRef = ref(null)
+function handleAdd() {
+    if (statisticModalRef.value) {
+        statisticModalRef.value.handleOpen(tableData.value)
+    }
+}
+
+
+function statisticIndexConfirmSelect(selectedIndex) {
+    tableData.value = selectedIndex
+    let indexIds = selectedIndex.map(item => item.indexId)
+    setNodeToIndex(currentNode.value.id, indexIds, 'STATISTIC').then((response) => {
+        if (response.code !== 200) {
+            proxy.$modal.msgError(response.msg)
+        }
+    })
+}
+
+// 閫変腑鏁扮粍
+let ids = ref([])
+let names = ref([])
+// 闈炲涓鐢�
+let multiple = ref(true)
+function handleDel(row) {
+    const indexIds = row && row.indexId ? [row.indexId] : ids.value
+    const indexNames = row.name || names.value
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鎸囨爣鍚嶄负"' + indexNames + '"鐨勬暟鎹」?', '璀﹀憡').then(function () {
+        return delIndex(currentNode.value.id, indexIds);
+    }).then((response) => {
+        if (response.code === 200) {
+            getList(currentNode.value)
+            // 鍒犻櫎鎴愬姛锛屾洿鏂拌〃鏍兼暟鎹�
+            // tableData.value = tableData.value.filter(f => f.indexId !== row.indexId)
+            proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        } else {
+            // 鍒犻櫎澶辫触锛屾樉绀洪敊璇秷鎭�
+            proxy.$modal.msgError(response.msg);
+        }
+    }).catch(() => {
+    });
+}
+
+
+defineExpose({
+    getList
+});
+function handleSelectionChange(selection) {
+    ids.value = selection.map(item => item.indexId);
+    names.value = selection.map(item => item.name);
+    multiple.value = !selection.length
+}
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/calculationformula/calculationFormula.vue b/zhitan-vue/src/views/modelconfiguration/calculationformula/calculationFormula.vue
new file mode 100644
index 0000000..1439337
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/calculationformula/calculationFormula.vue
@@ -0,0 +1,89 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form ref="form" :inline="true" :model="queryParams" @submit.prevent label-width="80px">
+                <el-form-item label="鍑芥暟鍚�">
+                    <el-input v-model="queryParams.funcName" placeholder="璇疯緭鍏ュ嚱鏁板悕" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="table-box">
+            <el-table :data="tableData" style="width: 100%">
+                <el-table-column prop="funcName" label="鍑芥暟鍚�" show-overflow-tooltip align="center">
+                </el-table-column>
+                <el-table-column prop="funcText" label="鍑芥暟鏂囨湰" show-overflow-tooltip align="center">
+                </el-table-column>
+                <el-table-column prop="info" label="浠嬬粛" show-overflow-tooltip align="center">
+                </el-table-column>
+                <el-table-column prop="prop" label="鎿嶄綔" width="150" align="center">
+                    <template #default="scope">
+                        <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                            鍒犻櫎
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+                v-model:limit="queryParams.pageSize" @pagination="getList" />
+        </div>
+    </div>
+</template>
+<script setup>
+
+import { delFunction, listFunction } from '@/api/modelConfiguration/indexWarehouse';
+
+let { proxy } = getCurrentInstance()
+
+let queryParams = ref({
+    funcName: '',
+    pageNum: 1,
+    pageSize: 10,
+})
+let tableData = ref([])
+let total = ref(0)
+let loading = ref(false)
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+    queryParams.value.pageNum = 1;
+    getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+    queryParams.value={}
+    handleQuery();
+}
+
+function getList() {
+    loading.value = true;
+    listFunction(queryParams.value).then(response => {
+        tableData.value = response.rows;
+        total.value = response.total;
+        loading.value = false;
+    });
+}
+getList()
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDel(row) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎璁$畻鍑芥暟涓�"' + row.funcName + '"鐨勬暟鎹」?', "璀﹀憡", {
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        type: "warning"
+    }).then(function () {
+        return delFunction(row.id);
+    }).then(() => {
+        getList();
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    }).catch(function () {
+    });
+}
+</script>
+
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/collectindicator/collectIndicator.vue b/zhitan-vue/src/views/modelconfiguration/collectindicator/collectIndicator.vue
new file mode 100644
index 0000000..7ba4ec1
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/collectindicator/collectIndicator.vue
@@ -0,0 +1,202 @@
+<template>
+  <div class="page">
+    <div class="form-card">
+      <el-form :inline="true" :model="queryForm" class="demo-form-inline">
+        <el-form-item label="鍙傛暟缂栫爜">
+          <el-input v-model="queryForm.code" placeholder="璇疯緭鍏ュ弬鏁扮紪鐮�" clearable />
+        </el-form-item>
+        <el-form-item label="鍙傛暟鍚嶇О">
+          <el-input v-model="queryForm.name" placeholder="璇疯緭鍏ュ弬鏁板悕绉�" clearable />
+        </el-form-item>
+        <el-form-item label="璁惧绫诲瀷">
+          <el-select v-model="queryForm.deviceType" placeholder="璇疯緭鍏ヨ澶囩被鍨�" clearable style="width: 200px">
+            <el-option v-for="dict in sys_device_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+          <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="table-box">
+      <div class=" mt20 mb20">
+        <el-button type="primary" icon="Plus" @click="handleDialog('add')">鏂板</el-button>
+      </div>
+      <el-table :data="tableData" style="width: 100%">
+        <el-table-column prop="code" label="鍙傛暟缂栫爜" align="center" show-overflow-tooltip />
+        <el-table-column prop="name" label="鍙傛暟鍚嶇О" align="center" show-overflow-tooltip />
+        <el-table-column prop="unit" label="鍗曚綅" align="center" show-overflow-tooltip
+          :formatter="(row, column) => proxy.selectDictLabel(sys_unit, row.unit)" />
+        <el-table-column prop="deviceType" label="璁惧绫诲瀷" align="center" show-overflow-tooltip>
+          <template #default="scope">
+            <dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
+          </template>
+        </el-table-column>
+        <el-table-column fixed="right" label="鎿嶄綔" width="180" align="center" show-overflow-tooltip>
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleEdit(scope.row.id)">
+              淇敼
+            </el-button>
+
+            <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+              鍒犻櫎
+            </el-button>
+          </template>
+
+        </el-table-column>
+      </el-table>
+      <pagination v-show="pageTotal > 0" :total="pageTotal" v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </div>
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="auto">
+        <el-form-item label="鍙傛暟缂栫爜" prop="code">
+          <el-input v-model="form.code" placeholder="璇疯緭鍏ュ弬鏁扮紪鐮�" clearable />
+        </el-form-item>
+        <el-form-item label="鍙傛暟鍚嶇О" prop="name">
+          <el-input v-model="form.name" placeholder="璇疯緭鍏ュ弬鏁板悕绉�" clearable />
+        </el-form-item>
+        <el-form-item label="鍗曚綅" prop="unit">
+          <el-select v-model="form.unit" placeholder="璇疯緭鍏ヨ澶囩被鍨�" clearable style="width: 100%">
+            <el-option v-for="dict in sys_unit" :key="dict.value" :label="dict.label" :value="dict.value" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="璁惧绫诲瀷" prop="deviceType">
+          <el-select v-model="form.deviceType" placeholder="璇疯緭鍏ヨ澶囩被鍨�" clearable style="width: 100%">
+            <el-option v-for="dict in sys_device_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="handleClose">鍙栨秷</el-button>
+          <el-button type="primary" @click="handleOk">
+            纭
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="collect">
+import { listTemplate, addTemplate, getTempById, putTemplate, delTempById } from '@/api/modelConfiguration/collectIndicator.js'
+import { reactive, ref } from 'vue';
+import { Delete, Edit, Search, Share, Upload } from '@element-plus/icons-vue'
+const { proxy } = getCurrentInstance();
+const { sys_device_type } = proxy.useDict("sys_device_type");
+const { sys_unit } = proxy.useDict("sys_unit");
+
+
+
+let queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+})
+let pageTotal = ref(0)
+let dialogVisible = ref(false)
+let dialogTitle = ref('')
+
+let queryForm = ref({})
+let form = ref({})
+let tableData = ref([])
+const rules = {
+  code: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' },],
+  name: [{ required: true, message: '璇疯緭鍏�', trigger: 'blur' },],
+  unit: [{ required: true, message: '璇烽�夋嫨', trigger: 'change' },],
+  deviceType: [{ required: true, message: '璇烽�夋嫨', trigger: 'change' },],
+}
+
+function handleQuery() {
+  queryParams.value.pageNum = 1
+  getList()
+}
+
+
+function resetQuery() {
+  queryForm.value = {}
+  queryParams.value.pageNum = 1
+  getList()
+}
+function handleEdit(id) {
+  getTempById({ id }).then(res => {
+    if (res.code == 200) {
+      handleDialog()
+      form.value = JSON.parse(JSON.stringify(res.data))
+    }
+  })
+}
+
+function handleDel(row) {
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎鎸囨爣鍚嶄负"' + row.name + '"鐨勬暟鎹」?', '璀﹀憡', {
+    confirmButtonText: '纭畾',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning'
+  }).then(() => {
+    return delTempById(row.id)
+  }).then(() => {
+    getList()
+    proxy.$modal.msgSuccess('鍒犻櫎鎴愬姛')
+  }).catch(function () {
+  })
+
+}
+
+function getList() {
+  let params = {
+    ...queryParams.value,
+    ...queryForm.value
+  }
+  listTemplate(params).then(res => {
+    tableData.value = res.rows;
+    pageTotal.value = res.total
+  });
+}
+
+function handleDialog(type) {
+  if (type == 'add') {
+    dialogTitle = '鏂板閲囬泦鍙傛暟妯$増'
+  } else {
+    dialogTitle = '淇敼閲囬泦鍙傛暟妯$増'
+  }
+  dialogVisible.value = true
+}
+
+function handleOk() {
+  proxy.$refs.formRef.validate(valid => {
+    console.log('valid===>', valid);
+    if (valid) {
+      if (form.value.id) {
+        putTemplate(form.value).then(res => {
+          proxy.$modal.msgSuccess(res.msg)
+          dialogVisible.value = false
+          getList()
+        })
+      } else {
+        addTemplate(form.value).then(res => {
+          console.log('res===>鏂板', res);
+          proxy.$modal.msgSuccess(res.msg)
+          dialogVisible.value = false
+          getList()
+        })
+      }
+
+    }
+  })
+
+}
+
+function handleClose() {
+  form.value = {}
+  proxy.$refs.formRef.resetFields()
+  dialogVisible.value = false
+}
+
+getList()
+
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/energytype/energyType.vue b/zhitan-vue/src/views/modelconfiguration/energytype/energyType.vue
new file mode 100644
index 0000000..8319cfa
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/energytype/energyType.vue
@@ -0,0 +1,423 @@
+<template>
+  <div class="page">
+    <div class="form-card">
+      <el-form
+        :model="queryParams"
+        ref="queryRef"
+        :inline="true"
+        label-width="80px"
+      >
+        <el-form-item label="鑳芥簮绫诲瀷">
+          <el-input
+            v-model="queryParams.enername"
+            placeholder="鑳芥簮绫诲瀷"
+            maxlength="30"
+          />
+        </el-form-item>
+        <el-form-item label="鑳芥簮鍝佺">
+          <el-select
+            v-model="queryParams.enerclassid"
+            placeholder="鑳芥簮鍝佺"
+            style="width: 100%"
+          >
+            <el-option
+              v-for="dict in energyVarietiesList"
+              :key="dict.enerclassid"
+              :label="dict.enerclassname"
+              :value="dict.enerclassid"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery"
+            >鎼滅储</el-button
+          >
+          <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="table-box">
+      <div class="mt20 mb20">
+        <el-button type="primary" icon="Plus" @click="handleAdd"
+          >鏂板</el-button
+        >
+      </div>
+      <el-table v-loading="loading" :data="energyTypeList">
+        <el-table-column
+          label="鑳芥簮绫诲瀷"
+          align="center"
+          key="enername"
+          prop="enername"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column
+          label="璁¢噺鍗曚綅"
+          align="center"
+          key="muid"
+          prop="muid"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column
+          label="鑳芥簮鍝佺"
+          align="center"
+          key="enerclassname"
+          prop="enerclassname"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column
+          label="鑳芥簮缂栧彿"
+          align="center"
+          key="enersno"
+          prop="enersno"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column
+          label="鏄惁瀛樺偍"
+          align="center"
+          key="isstorage"
+          prop="isstorage"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column
+          label="鍗曚环"
+          align="center"
+          key="price"
+          prop="price"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column
+          label="鎶樻爣绯绘暟"
+          align="center"
+          key="coefficient"
+          prop="coefficient"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column
+          label="鎺掓斁鍥犲瓙"
+          align="center"
+          key="emissionFactors"
+          prop="emissionFactors"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column
+          label="澶囨敞"
+          align="center"
+          key="note"
+          prop="note"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column
+          label="鎿嶄綔"
+          align="center"
+          width="200"
+          class-name="small-padding fixed-width"
+        >
+          <template #default="scope">
+            <el-tooltip content="缂栬緫" placement="top">
+              <el-button
+                link
+                type="primary"
+                icon="Edit"
+                @click="handleUpdate(scope.row)"
+              >
+                缂栬緫
+              </el-button>
+            </el-tooltip>
+            <el-tooltip content="鍒犻櫎" placement="top">
+              <el-button
+                link
+                type="primary"
+                icon="Delete"
+                @click="handleDelete(scope.row)"
+              >
+                鍒犻櫎
+              </el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="queryParams.total > 0"
+        :total="queryParams.total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </div>
+    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="90px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鑳芥簮绫诲瀷" prop="enername">
+              <el-input v-model="form.enername" placeholder="璇疯緭鍏ヨ兘婧愮被鍨�" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="璁¢噺鍗曚綅" prop="muid">
+              <el-select
+                v-model="form.muid"
+                placeholder="璇烽�夐」璁¢噺鍗曚綅"
+                clearable
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="dict in sys_unit"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                  @click="handleClick('muidString', dict.label)"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鑳芥簮缂栧彿" prop="enersno">
+              <el-input v-model="form.enersno" placeholder="璇疯緭鍏ヨ兘婧愮紪鍙�" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鑳芥簮鍝佺" prop="enerclassid">
+              <el-select
+                v-model="form.enerclassid"
+                placeholder="璇烽�夐」鑳芥簮鍝佺"
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="dict in energyVarietiesList"
+                  :key="dict.enerclassid"
+                  :label="dict.enerclassname"
+                  :value="dict.enerclassid"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鏄惁瀛樺偍" prop="isstorage">
+              <el-select
+                v-model="form.isstorage"
+                placeholder="璇烽�夐」鏄惁瀛樺偍"
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="dict in sys_yes_no"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+                  @click="handleClick('isstorageString', dict.label)"
+                />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鍗曚环" prop="price">
+              <el-input-number
+                v-model="form.price"
+                :min="0"
+                :precision="2"
+                controls-position="right"
+                placeholder="璇疯緭鍏ュ崟浠�"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鎶樻爣绯绘暟" prop="coefficient">
+              <el-input-number
+                v-model="form.coefficient"
+                :min="0"
+                :precision="4"
+                controls-position="right"
+                placeholder="璇疯緭鍏ユ姌鏍囩郴鏁�"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="鎺掓斁鍥犲瓙">
+              <el-input-number
+                v-model="form.emissionFactors"
+                :min="0"
+                :precision="4"
+                controls-position="right"
+                placeholder="璇疯緭鍏ユ帓鏀惧洜瀛�"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="澶囨敞" prop="note">
+              <el-input
+                v-model="form.note"
+                placeholder="璇疯緭鍏ュ娉�"
+                type="textarea"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup name="energy-Type">
+import {
+  listEnergyType,
+  addEnergyType,
+  getEnergyType,
+  updateEnergyType,
+  delEnergyType,
+} from "@/api/modelConfiguration/energyType";
+import { listEnergyVarietiesList } from "@/api/modelConfiguration/energyVarieties";
+const { proxy } = getCurrentInstance();
+const { sys_unit, sys_yes_no } = proxy.useDict("sys_unit", "sys_yes_no");
+const energyVarietiesList = ref([]);
+const energyTypeList = ref([]);
+const open = ref(false);
+const loading = ref(false);
+const title = ref("");
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+    enername: null,
+    enerclassname: null,
+  },
+  rules: {
+    enername: [
+      { required: true, message: "鑳芥簮绫诲瀷涓嶈兘涓虹┖", trigger: "blur" },
+    ],
+    enersno: [
+      { required: true, message: "鑳芥簮缂栧彿涓嶈兘涓虹┖", trigger: "blur" },
+      { pattern: /^[a-z0-9]*$/, message: "鑳芥簮缂栧彿蹇呴』涓烘暟瀛�/灏忓啓瀛楁瘝" },
+    ],
+    enerclassid: [
+      { required: true, message: "鑳芥簮鍝佺涓嶈兘涓虹┖", trigger: "blur" },
+    ],
+    isstorage: [
+      { required: true, message: "鏄惁瀛樺偍涓嶈兘涓虹┖", trigger: "blur" },
+    ],
+    price: [{ required: true, message: "鍗曚环涓嶈兘涓虹┖", trigger: "blur" }],
+    coefficient: [
+      { required: true, message: "鎶樻爣绯绘暟涓嶈兘涓虹┖", trigger: "blur" },
+    ],
+  },
+});
+const { queryParams, form, rules } = toRefs(data);
+getList();
+getEnergyVarietiesList();
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鍒楄〃
+function getList() {
+  loading.value = true;
+  listEnergyType(proxy.addDateRange(queryParams.value)).then((res) => {
+    loading.value = false;
+    energyTypeList.value = res.rows;
+    queryParams.value.total = res.total;
+  });
+}
+function getEnergyVarietiesList() {
+  listEnergyVarietiesList().then((res) => {
+    energyVarietiesList.value = res.data;
+  });
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鎼滅储
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+    enerclassname: null,
+  };
+  getList();
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鏂板
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "鏂板鑳芥簮绫诲瀷";
+}
+function handleClick(label, value) {
+  form.value[label] = value;
+  console.log(form.value, label, value);
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-缂栬緫
+function handleUpdate(row) {
+  reset();
+  getEnergyType(row.enerid).then((response) => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "缂栬緫鑳芥簮绫诲瀷";
+  });
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鏂板/缂栬緫-淇濆瓨
+function submitForm() {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (valid) {
+      if (form.value.enerid != undefined) {
+        updateEnergyType(form.value).then((response) => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addEnergyType(form.value).then((response) => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鏂板/缂栬緫-鍙栨秷
+function cancel() {
+  open.value = false;
+  reset();
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鏂板/缂栬緫-琛ㄥ崟閲嶇疆
+function reset() {
+  form.value = {
+    coefficient: null,
+    emissionFactors: null,
+    enerclassid: null,
+    enerclassname: null,
+    enername: null,
+    enersno: null,
+    isstorageString: null,
+    muidString: null,
+    note: null,
+    price: null,
+  };
+  proxy.resetForm("formRef");
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮绫诲瀷绠$悊-鍒犻櫎
+function handleDelete(row) {
+  proxy.$modal
+    .confirm('鏄惁纭鍒犻櫎鑳芥簮绫诲瀷涓�"' + row.enername + '"鐨勬暟鎹」锛�')
+    .then(function () {
+      return delEnergyType(row.enerid);
+    })
+    .then(() => {
+      getList();
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    })
+    .catch(() => {});
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/energyvarieties/energyVarieties.vue b/zhitan-vue/src/views/modelconfiguration/energyvarieties/energyVarieties.vue
new file mode 100644
index 0000000..d1ba49c
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/energyvarieties/energyVarieties.vue
@@ -0,0 +1,192 @@
+<template>
+  <div class="page">
+
+    <div class="form-card">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="80px" @submit.prevent>
+        <el-form-item label="鑳芥簮鍝佺">
+          <el-input v-model="queryParams.enerclassname" placeholder="鑳芥簮鍝佺" maxlength="30" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+          <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="table-box">
+      <div class="mt20 mb20">
+        <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+      </div>
+      <el-table v-loading="loading" :data="energyVarietiesList">
+        <el-table-column label="鑳芥簮鍝佺" align="center" key="enerclassname" prop="enerclassname" :show-overflow-tooltip="true" />
+        <el-table-column label="澶囨敞" align="center" key="note" prop="note" :show-overflow-tooltip="true" />
+        <el-table-column label="鍒涘缓浜�" align="center" key="createBy" prop="createBy" :show-overflow-tooltip="true" width="100"/>
+        <el-table-column label="鍒涘缓鏃堕棿" align="center" key="createTime" prop="createTime" :show-overflow-tooltip="true" width="200"/>
+        <el-table-column label="鏇存柊浜�" align="center" key="updateBy" prop="updateBy" :show-overflow-tooltip="true" width="100"/>
+        <el-table-column label="鏇存柊鏃堕棿" align="center" key="updateTime" prop="updateTime" :show-overflow-tooltip="true" width="200"/>
+        <el-table-column label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="缂栬緫" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">
+                缂栬緫
+              </el-button>
+            </el-tooltip>
+            <el-tooltip content="鍒犻櫎" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">
+                鍒犻櫎
+              </el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination v-show="queryParams.total > 0" :total="queryParams.total" v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </div>
+    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="鑳芥簮鍝佺" prop="enerclassname">
+              <el-input v-model="form.enerclassname" placeholder="璇疯緭鍏ヨ兘婧愬搧绉�" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="澶囨敞" prop="note">
+              <el-input v-model="form.note" placeholder="璇疯緭鍏ュ娉�" type="textarea" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup name="energy-varieties">
+import {
+  listEnergyVarieties,
+  addEnergyVarieties,
+  getEnergyVarieties,
+  updateEnergyVarieties,
+  delEnergyVarieties,
+} from "@/api/modelConfiguration/energyVarieties";
+const { proxy } = getCurrentInstance();
+const energyVarietiesList = ref([]);
+const open = ref(false);
+const loading = ref(false);
+const title = ref("");
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+    enerclassname: null,
+  },
+  rules: {
+    enerclassname: [{ required: true, message: "鑳芥簮鍝佺涓嶈兘涓虹┖", trigger: "blur" }],
+  },
+});
+const { queryParams, form, rules } = toRefs(data);
+getList();
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺璁剧疆-鍒楄〃
+function getList() {
+  loading.value = true;
+  listEnergyVarieties(
+    proxy.addDateRange(queryParams.value)
+  ).then((res) => {
+    loading.value = false;
+    energyVarietiesList.value = res.rows;
+    queryParams.value.total = res.total;
+  });
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺璁剧疆-鎼滅储
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺璁剧疆-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    total: 0,
+    enerclassname: null,
+  }
+  getList();
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺璁剧疆-鏂板
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "鏂板鑳芥簮鍝佺绠$悊";
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺璁剧疆-缂栬緫
+// function handleUpdate(row) {
+//   reset();
+//   form.value = { ...row }
+//   open.value = true;
+//   title.value = "缂栬緫鑳芥簮鍝佺绠$悊";
+// }
+function handleUpdate(row) {
+  reset();
+  getEnergyVarieties(row.enerclassid).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "缂栬緫鑳芥簮鍝佺绠$悊";
+  });
+};
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺璁剧疆-鏂板/缂栬緫-淇濆瓨
+function submitForm() {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (valid) {
+      if (form.value.enerclassid != undefined) {
+        updateEnergyVarieties(form.value).then((response) => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addEnergyVarieties(form.value).then((response) => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺璁剧疆-鏂板/缂栬緫-鍙栨秷
+function cancel() {
+  open.value = false;
+  reset();
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺璁剧疆-鏂板/缂栬緫-琛ㄥ崟閲嶇疆
+function reset() {
+  form.value = {
+    enerclassname: '',
+    note: ''
+  };
+  proxy.resetForm("formRef");
+}
+// 妯″瀷閰嶇疆绠$悊-鑳芥簮鍝佺璁剧疆-鍒犻櫎
+function handleDelete(row) {
+  proxy.$modal
+    .confirm('鏄惁纭鍒犻櫎鑳芥簮鍝佺涓�"' + row.enerclassname + '"鐨勬暟鎹」锛�')
+    .then(function () {
+      return delEnergyVarieties(row.enerclassid);
+    })
+    .then(() => {
+      getList();
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    })
+    .catch(() => { });
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/TreeNodeModal.vue b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/TreeNodeModal.vue
new file mode 100644
index 0000000..8dda35b
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/TreeNodeModal.vue
@@ -0,0 +1,157 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="600" @close="handleClose" :close-on-click-modal="false">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="130px" v-loading="loading">
+            <el-form-item label="鐖剁骇鑺傜偣" v-if="addNode">
+                <span>{{ currentNode ? currentNode.name : "" }}</span>
+            </el-form-item>
+            <el-form-item label="鎸囨爣鑺傜偣缂栫爜" prop="code">
+                <el-input v-if="addNode" v-model="form.code" placeholder="璇疯緭鍏ョ紪鐮�" />
+                <span v-if="!addNode">{{ form.code }}</span>
+            </el-form-item>
+            <el-form-item label="鎸囨爣鑺傜偣鍚嶇О" prop="name">
+                <el-input v-model="form.name" placeholder="璇疯緭鍏ュ悕绉�" />
+            </el-form-item>
+            <el-form-item label="鑺傜偣绫诲瀷" prop="nodeCategory">
+                <el-select v-model="form.nodeCategory" placeholder="璇烽�夎妭鐐圭被鍨�" prop="nodeCategory" clearable>
+                    <el-option v-for="dict in sys_node_category" :key="dict.value" :label="dict.label"
+                        :value="dict.value" />
+                </el-select>
+            </el-form-item>
+            <div slot="footer" class="text-right">
+                <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+                <el-button @click="handleClose">鍙� 娑�</el-button>
+            </div>
+        </el-form>
+    </el-dialog>
+</template>
+<script setup>
+const { proxy } = getCurrentInstance();
+import { getModelNode, updateModelNode, addModelNode } from '@/api/modelConfiguration/indexWarehouse'
+const { sys_node_category } = proxy.useDict("sys_node_category");
+
+const props = defineProps(['modelCode'])
+
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+    name: null,
+    code: null,
+    nodeCategory: null,
+
+})
+
+const formRules = {
+    code: [
+        { required: true, message: '缂栫爜涓嶈兘涓虹┖', trigger: 'blur' },
+        {
+            min: 2,
+            max: 20,
+            message: '闀垮害鍦� 2 鍒� 20 涓瓧绗�',
+            trigger: 'blur'
+        },
+        {
+            pattern: /^[a-zA-Z][A-Za-z0-9_-]+$/,
+            message: '蹇呴』涓烘暟瀛椼�佸瓧姣嶃��- 鎴朹 锛屼笖棣栧瓧绗﹀彧鑳戒负瀛楁瘝'
+        }
+    ],
+    name: [
+        { required: true, message: '鍚嶇О涓嶈兘涓虹┖', trigger: 'blur' },
+        { min: 2, max: 15, message: '闀垮害鍦� 2 鍒� 15 涓瓧绗�', trigger: 'blur' }
+    ]
+}
+
+let addNode = ref(false)
+let currentSelectNode = ref(null)
+let currentNode = ref(null)
+function handleOpen(node, data, addNodeValue) {
+    addNode.value = addNodeValue
+    if (addNodeValue) {
+        if (data) {
+            currentSelectNode.value = data;
+            getModelNode(data.id).then(response => {
+                visible.value = true;
+                title.value = "娣诲姞妯″瀷鑺傜偣";
+                currentNode.value = response.data;
+            });
+        } else {
+            visible.value = true;
+        }
+
+
+    } else {
+        currentSelectNode.value = data;
+        getModelNode(data.id).then(response => {
+            form.value = response.data;
+            visible.value = true;
+            title.value = "淇敼妯″瀷鑺傜偣";
+        });
+    }
+}
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            if (form.value.nodeId !== undefined) {
+                updateModelNode(form.value).then(response => {
+                    if (response.code === 200) {
+                        currentSelectNode.value.label = form.value.name;
+                        proxy.$modal.msgSuccess('淇敼鎴愬姛');
+                        visible.value = false;
+                    } else {
+                        proxy.$modal.msgError(response.msg);
+                    }
+                });
+            } else {
+                {
+                    form.value.parentId = currentNode.value ? currentNode.value.nodeId : ''
+                    form.value.address = (currentNode.value ? currentNode.value.address : '') + form.value.code + ','
+                    form.value.modelCode = props.modelCode
+                    addModelNode(form.value).then(response => {
+                        if (response.code === 200) {
+                            proxy.$modal.msgSuccess('鏂板鎴愬姛')
+                            visible.value = false
+                            const addedNode = response.data
+                            const newChild = {
+                                id: addedNode.nodeId,
+                                label: addedNode.name,
+                                children: []
+                            }
+                            if (currentSelectNode.value) {
+                                if (!currentSelectNode.value.children) {
+                                    currentSelectNode.value.children = []
+                                }
+                                currentSelectNode.value.children.push(newChild)
+                            } else {
+                                emit("addTreeList", newChild)
+                            }
+                            emit("addTreeSelectList", addedNode, newChild)
+                        } else {
+                            proxy.$modal.msgError(response.msg)
+                        }
+                    })
+                }
+            }
+        }
+    })
+}
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        name: null,
+        code: null,
+        nodeCategory: null,
+    }
+}
+
+defineExpose({ handleOpen })
+
+let emit = defineEmits(['addTreeList', 'addTreeSelectList'])
+
+</script>
+
+<style lang="scss" scoped>
+
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/deviceConfig/DeviceConfig.vue b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/deviceConfig/DeviceConfig.vue
new file mode 100644
index 0000000..0e81e80
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/deviceConfig/DeviceConfig.vue
@@ -0,0 +1,83 @@
+<template>
+
+    <div class="table-box">
+        <el-button class="mb20 " type="primary" icon="Plus" @click="handleAdd">
+            鏂板
+        </el-button>
+        <el-table v-loading="loading" :data="tableData" height="calc(100vh - 370px)">
+            <el-table-column label="鍣ㄥ叿缂栫爜" prop="code" show-overflow-tooltip align="center" />
+            <el-table-column label="鍣ㄥ叿鍚嶇О" prop="meterName" show-overflow-tooltip align="center" />
+            <el-table-column label="閰嶇數瀹�" prop="installactionLocation" show-overflow-tooltip align="center" />
+            <el-table-column label="鎿嶄綔" class-name="small-padding fixed-width" align="center">
+                <template #default="scope">
+                    <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                        鍒犻櫎
+                    </el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <deviceModal ref="deviceModalRef" @deviceConfirmSelect='deviceConfirmSelect' />
+    </div>
+</template>
+<script setup>
+let { proxy } = getCurrentInstance()
+import { getSettingDevice, setDevice, delDevice } from '@/api/modelConfiguration/businessModel'
+import deviceModal from './DeviceModal.vue'
+let tableData = ref([])
+let loading = ref(false)
+let currentNode = ref(null)
+function getList(modelNode) {
+    if (!modelNode) {
+        tableData.value = []
+        return
+    }
+    currentNode.value = modelNode
+    loading.value = true;
+    getSettingDevice(modelNode.id).then((response) => {
+        if (response.code === 200) {
+            tableData.value = response.data;
+            loading.value = false;
+        } else {
+            proxy.$modal.msgError(response.msg);
+        }
+    });
+}
+
+function handleDel(row) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+        return delDevice(currentNode.value.id, row.id); // 鐩存帴璋冪敤delDevice锛岃�屼笉鏄痟andleDelDevice
+    }).then((response) => {
+        if (response.code === 200) {
+            // 鍒犻櫎鎴愬姛锛屾洿鏂拌〃鏍兼暟鎹�
+            tableData.value = tableData.value.filter(f => f.id !== row.id);
+            proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        } else {
+            // 鍒犻櫎澶辫触锛屾樉绀洪敊璇秷鎭�
+            proxy.$modal.msgError(response.msg);
+        }
+    }).catch(() => {
+    });
+}
+let deviceModalRef = ref(null)
+function handleAdd() {
+    if (deviceModalRef.value) {
+        deviceModalRef.value.handleOpen(tableData.value)
+    }
+}
+function deviceConfirmSelect(selectedDevice) {
+    tableData.value = selectedDevice;
+    let deviceIds = selectedDevice.map(item => item.id);
+    setDevice(currentNode.value.id, deviceIds).then((response) => {
+        if (response.code !== 200) {
+            proxy.$modal.msgError(response.msg)
+        }
+    });
+}
+defineExpose({
+    getList
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/deviceConfig/DeviceModal.vue b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/deviceConfig/DeviceModal.vue
new file mode 100644
index 0000000..b8c0766
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/deviceConfig/DeviceModal.vue
@@ -0,0 +1,176 @@
+<template>
+    <el-dialog v-model="visible" title="閫夋嫨璁惧" width="1100" @close="handleClose" destroy-on-close
+        :close-on-click-modal="false" :close-on-press-escape="false">
+        <div class="form-card ">
+            <el-form :model="queryParams" :inline="true" label-width="48px">
+                <el-form-item label="缂栫爜" prop="code">
+                    <el-input v-model="queryParams.code" placeholder="杈撳叆缂栫爜" />
+                </el-form-item>
+                <el-form-item label="鍚嶇О" prop="meterName">
+                    <el-input v-model="queryParams.meterName" placeholder="杈撳叆鍚嶇О" />
+                </el-form-item>
+                <el-form-item label="绉嶇被" prop="meterType">
+                    <el-select v-model="queryParams.meterType" placeholder="閫夋嫨绉嶇被" style="width: 170px;">
+                        <el-option v-for="dict in sys_device_type" :key="dict.value" :label="dict.label"
+                            :value="dict.value" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class='table-box'>
+            <el-table v-loading="loading" row-key="id" :data="tableData" @select="handleSelectChange"
+                @select-all="handleSelectionAllChange" height="50vh" ref="deviceSettingTable">
+                <el-table-column type="selection" width="55" align="center" row-key="id" :reserve-selection="true" />
+                <el-table-column label="鍣ㄥ叿缂栫爜" prop="code" show-overflow-tooltip align="center" />
+                <el-table-column label="鍣ㄥ叿鍚嶇О" prop="meterName" show-overflow-tooltip align="center" />
+                <el-table-column label="鍣ㄥ叿绉嶇被" prop="meterType" :formatter="meterTypeFormat" show-overflow-tooltip
+                    align="center" />
+                <!-- <el-table-column label="瑙勬牸鍨嬪彿" prop="modelNumber" show-overflow-tooltip align="center" /> -->
+                <el-table-column label="閰嶇數瀹�" prop="installactionLocation" show-overflow-tooltip align="center" />
+                <!-- <el-table-column label="鐘舵��" width="85" prop="meterStatus" :formatter="meterStatusFormat" show-overflow-tooltip
+                    align="center" /> -->
+            </el-table>
+        </div>
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize" @pagination="getList" />
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="handleOk">纭畾</el-button>
+            <el-button @click="handleClose">鍙栨秷</el-button>
+        </div>
+
+    </el-dialog>
+
+</template>
+<script setup>
+import { listImplement } from '@/api/modelConfiguration/businessModel'
+import { ref } from 'vue';
+let visible = ref(false)
+let { proxy } = getCurrentInstance()
+const { sys_device_type } = proxy.useDict("sys_device_type");
+const { meter_status } = proxy.useDict("meter_status");
+
+// 绉嶇被瀛楀吀缈昏瘧
+function meterTypeFormat(row, column) {
+    return proxy.selectDictLabel(sys_device_type.value, row.meterType);
+}
+// 鐘舵�佸瓧鍏哥炕璇�
+function meterStatusFormat(row, column) {
+    return proxy.selectDictLabel(meter_status.value, row.meterStatus);
+}
+
+let tableData = ref([])
+let total = ref(0);
+let queryParams = ref({
+    code: null,
+    meterName:null,
+    meterType: null,
+    pageNum: 1,
+    pageSize: 10,
+})
+let loading = ref(false)
+let deviceSettingTable = ref(null)
+
+function getList() {
+    loading.value = true;
+    listImplement(queryParams.value).then(response => {
+        tableData.value = response.rows
+        total.value = response.total
+        loading.value = false
+        nextTick(function () {
+            deviceSettingTable.value.data.forEach(rowData => {
+                let exist = selectedDevice.value.filter(f => f.id === rowData.id).length;
+                if (exist > 0) {
+                    deviceSettingTable.value.toggleRowSelection(rowData, true);
+                }
+            });
+        });
+    })
+}
+
+function handleClose() {
+    visible.value = false
+}
+function handleQuery() {
+    queryParams.value.pageNum = 1;
+    getList();
+}
+function resetQuery() {
+    queryParams.value = {
+        code: null,
+        meterType: null,
+        meterName:null,
+        pageNum: 1,
+        pageSize: 10,
+    }
+    getList();
+}
+let selectedDevice = ref([])
+function handleOpen(selectRow) {
+    selectedDevice.value = JSON.parse(JSON.stringify(selectRow))
+    visible.value = true
+    queryParams.value = {
+        pageNum: 1,
+        pageSize: 10,
+
+    }
+    getList()
+}
+
+//鍒ゆ柇涓�涓� 鏄叏閫夎繕鏄叏閫夊彇娑�
+function formatterSelect(selection, data) {
+    let idMap = {};
+    selection.forEach(item => {
+        idMap[item.id] = true;
+    });
+    let allIn = data.every(item => idMap.hasOwnProperty(item.id));
+    return allIn;
+}
+function handleSelectionAllChange(selection) {
+    if (formatterSelect(selection, tableData.value)) {
+        selection.forEach(row => {
+            let rowStatus = selectedDevice.value.filter(f => f.id == row.id).length;
+            if (rowStatus === 0) {
+                selectedDevice.value.push({
+                    "id": row.id,
+                    "code": row.code,
+                    "meterName": row.meterName
+                });
+            }
+        });
+    } else {
+        selectedDevice.value = selectedDevice.value.filter(f => {
+            return !tableData.value.some(s => s.id == f.id);
+        });
+    }
+}
+
+function handleSelectChange(selection, row) {
+    let rowStatus = selection.filter(f => f.id === row.id).length;
+    if (rowStatus > 0) {
+        selectedDevice.value.push({
+            "id": row.id,
+            "code": row.code,
+            "meterName": row.meterName
+        });
+    } else {
+        selectedDevice.value = selectedDevice.value.filter(f => f.id !== row.id);
+    }
+}
+
+function handleOk() {
+    emit('deviceConfirmSelect', selectedDevice.value)
+    visible.value = false
+}
+defineExpose({ handleOpen });
+let emit = defineEmits(['deviceConfirmSelect'])
+</script>
+
+
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/EditModal.vue b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/EditModal.vue
new file mode 100644
index 0000000..038db90
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/EditModal.vue
@@ -0,0 +1,169 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="700" @close="handleClose">
+        <el-form :model="form" ref="queryRef" :rules="formRules" label-width="130px" v-loading="loading">
+            <el-form-item label="鎸囨爣鍚嶇О" prop="name">
+                <el-input v-model="form.name" placeholder="璇疯緭鍏ユ寚鏍囧悕绉�" />
+            </el-form-item>
+            <el-form-item label="鎸囨爣缂栫爜" prop="code">
+                <el-input v-model="form.code" placeholder="璇疯緭鍏ユ寚鏍囩紪鐮�" />
+            </el-form-item>
+            <el-form-item label="鎸囨爣鍒嗙被" prop="indexCategory">
+                <el-select v-model="form.indexCategory" placeholder="璇烽�夋嫨鎸囨爣鍒嗙被" style="width: 100%">
+                    <el-option v-for="dict in props.sys_index_category" :key="dict.value" :label="dict.label"
+                        :value="dict.value" />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="鍗曚綅" prop="unitId">
+                <el-select v-model="form.unitId" placeholder="璇烽�夋嫨鍗曚綅" style="width: 100%">
+                    <el-option v-for="dict in props.sys_unit" :key="dict.value" :label="dict.label"
+                        :value="dict.value" />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="椤哄簭鍙�" prop="orderNum">
+                <el-input-number v-model="form.orderNum" placeholder="璇疯緭鍏�" :min="0" :precision="0" :step="1"
+                    style="width: 100%" controls-position="right" />
+            </el-form-item>
+            <el-form-item label="璁惧" prop="equipment">
+                <el-select v-model="form.equipment" placeholder="璇烽�夋嫨璁惧" style="width: 100%">
+                    <el-option v-for="dict in fcilityArchives" :key="dict.id" :label="dict.facilityName"
+                        :value="dict.id"></el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="鑳芥簮绫诲瀷" prop="energyId">
+                <el-select v-model="form.energyId" placeholder="璇烽�夋嫨鑳芥簮绫诲瀷" style="width: 100%;">
+                    <el-option :label="item.enername" :value="item.enersno"
+                        v-for="(item, index) in energyTypeArr" :key="index" />
+                </el-select>
+            </el-form-item>
+        </el-form>
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+            <el-button @click="handleClose">鍙� 娑�</el-button>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+import { getPointFacility, updateEnergyindex, addEnergyindex, getEnergyindex } from '@/api/modelConfiguration/indexWarehouse'
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+const { proxy } = getCurrentInstance();
+
+//璁惧
+let fcilityArchives = []
+function getFacilityArchivesList() {
+    getPointFacility().then(response => {
+        fcilityArchives = response.data;
+        console.log(fcilityArchives);
+    });
+}
+getFacilityArchivesList()
+
+const props = defineProps(['sys_index_category', 'sys_unit', 'indexType'])
+
+let emit = defineEmits(['getList'])
+
+let currentNode = ref(null)
+let visible = ref(false)
+let title = ref('')
+let loading = ref(false)
+let form = ref({
+    name: null,
+    code: null,
+    indexType: props.indexType,
+    indexCategory: null,
+    unitId: null,
+    orderNum: null,
+    equipment: null,
+    energyId: null
+})
+
+const formRules = {
+    name: [
+        { required: true, message: "鎸囨爣鍚嶇О涓嶈兘涓虹┖", trigger: "blur" },
+        { min: 2, max: 20, message: '闀垮害鍦� 2 鍒� 20 涓瓧绗�', trigger: 'blur' }
+    ],
+    code: [
+        { required: true, message: "鎸囨爣缂栫爜涓嶈兘涓虹┖", trigger: "blur" },
+        { min: 2, max: 20, message: '闀垮害鍦� 2 鍒� 30 涓瓧绗�', trigger: 'blur' },
+        { pattern: /^[a-zA-Z][A-Za-z0-9#_-]+$/, message: '蹇呴』涓烘暟瀛椼�佸瓧姣嶃��#銆�- 鎴朹 锛屼笖棣栧瓧绗﹀彧鑳戒负瀛楁瘝' }
+    ],
+    indexCategory: [
+        { required: true, message: "璇烽�夋嫨鎸囨爣鍒嗙被", trigger: "change" }
+    ],
+    energyId: [
+        { required: true, message: "璇烽�夋嫨鑳芥簮鍒嗙被", trigger: "change" }
+    ]
+}
+
+
+function handleOpen(row, modelNode) {
+    currentNode.value = modelNode
+    if (row && row.indexId) {
+        const indexId = row.indexId
+        title.value = "缂栬緫鎸囨爣淇℃伅"
+        getEnergyindex(indexId).then(response => {
+            form.value = response.data
+            title.value = '淇敼' + response.data.name + '鎸囨爣淇℃伅'
+        })
+    } else {
+        title.value = "娣诲姞鎸囨爣淇℃伅"
+    }
+    visible.value = true
+}
+
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            console.log('form.value', form.value)
+            loading.value = true;
+            let obj = form.value.indexId ? updateEnergyindex(form.value) : addEnergyindex(currentNode.value.id, form.value)
+            obj.then((res) => {
+                if (res.code == 200) {
+                    proxy.$modal.msgSuccess(res.msg);
+                    emit('getList')
+                } else {
+                    proxy.$modal.msgError(res.msg);
+                }
+
+            }).catch((err) => {
+
+            }).finally(() => {
+                handleClose()
+            });
+        }
+    })
+}
+
+function handleClose(value) {
+    visible.value = false
+    loading.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        name: null,
+        code: null,
+        indexType: props.indexType,
+        indexCategory: null,
+        unitId: null,
+        orderNum: null,
+        equipment: null,
+        energyId: null
+    }
+}
+
+defineExpose({ handleOpen })
+
+let energyTypeArr = ref([])
+function energyTypeFun() {
+    listEnergyTypeList().then((res) => {
+        console.log(res);
+        energyTypeArr.value = res.data;
+    });
+}
+energyTypeFun()
+
+
+</script>
+
+
+
+<style lang="scss" scoped></style>
diff --git a/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/storage/CalculateFunction.vue b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/storage/CalculateFunction.vue
new file mode 100644
index 0000000..7fd9401
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/storage/CalculateFunction.vue
@@ -0,0 +1,83 @@
+<template>
+    <el-dialog v-model="visible" title="閫夋嫨璁$畻鍑芥暟" width="1000" @close="handleClose">
+        <el-form label-width="110px" :model="queryParams" :inline="true">
+            <el-form-item label="鍑芥暟鍚�" prop="funcName">
+                <el-input v-model="queryParams.funcName" placeholder="璇疯緭鍏ュ嚱鏁板悕" />
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+
+        </el-form>
+
+        <el-table v-loading="loading" :data="functionList" @row-click="handleCurrentChange" highlight-current-row>
+            <el-table-column label="鍑芥暟鍚�" align="center" show-overflow-tooltip prop="funcName" width="200" />
+            <el-table-column label="浠嬬粛" align="center" show-overflow-tooltip prop="info" />
+        </el-table>
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-dialog>
+</template>
+<script setup>
+const { proxy } = getCurrentInstance();
+import { listFunction } from '@/api/modelConfiguration/indexWarehouse'
+let emit = defineEmits(['confirmCalcFunction'])
+
+let visible = ref(false)
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+    funcName: null
+})
+function handleQuery() {
+    queryParams.value = {
+        pageNum: 1,
+        pageSize: 10,
+        funcName: null
+    }
+    getList()
+}
+
+function resetQuery() {
+    handleQuery();
+}
+
+let functionList = ref([])
+let total = ref(0)
+let loading = ref(false)
+/** 鏌ヨ璁$畻鍑芥暟鍒楄〃 */
+function getList() {
+    loading.value = true;
+    listFunction(queryParams.value).then(response => {
+        functionList.value = response.rows;
+        total.value = response.total;
+        loading.value = false;
+    });
+}
+
+
+
+
+// 澶氶�夋閫変腑鏁版嵁
+function handleCurrentChange(currentRow) {
+    emit('confirmCalcFunction', currentRow.funcText);
+    visible.value = false
+
+}
+
+
+function handleOpen() {
+    visible.value = true
+    getList()
+}
+
+
+function handleClose() {
+    visible.value = false
+}
+
+defineExpose({ handleOpen })
+</script>
+
+<style scoped lang="scss"></style>
diff --git a/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/storage/StorageModal.vue b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/storage/StorageModal.vue
new file mode 100644
index 0000000..e64a6c7
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/components/storage/StorageModal.vue
@@ -0,0 +1,257 @@
+<template>
+    <el-dialog v-model="visible" title="閰嶇疆鎸囨爣瀛樺偍" width="1000" @close="handleClose">
+        <el-form label-position="top" label-width="110px" :model="indexStorageData">
+            <el-form-item>
+                <div slot="label" class="custom-box mb20">
+                    <div class="label-box mr20"> 鎸囨爣璁$畻鍏紡</div>
+                    <el-button @click="selectCalcFunction" type="primary">閫夋嫨璁$畻鍑芥暟</el-button>
+                </div>
+                <el-input type="textarea" :rows="3" v-model="indexStorageData.indexFormula.formulaText" id="formulaText"
+                    @blur="parseFormulaFun"></el-input>
+            </el-form-item>
+            <el-form-item>
+                <el-table :data="indexStorageData.indexFormula.indexFormulaParams" style="width: 100%">
+                    <el-table-column label="鍙傛暟鍚�" prop="paramName" width="200" show-overflow-tooltip align="center" />
+                    <el-table-column label="鍙傛暟鍊�" prop="paramValue" show-overflow-tooltip>
+                        <template #default="scope">
+                            <el-autocomplete popper-class="my-autocomplete" v-model="scope.row.paramValue"
+                                :fetch-suggestions="debouncedSearch" placeholder="璇疯緭鍏ユ寚鏍囩紪鐮�">
+                                <template #default="{ item }">
+                                    <div class="name"> {{ item.value }}</div>
+                                    <span class="addr">{{ item.name }}</span>
+                                    <div class="addr">{{ item.powerRoom }}</div>
+                                </template>
+                            </el-autocomplete>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </el-form-item>
+        </el-form>
+
+        <BaseCard title="鏁版嵁瀛樺偍">
+            <el-form class=" mt20 text-right">
+                <el-form-item v-for="period in indexStorageData.indexStorage" :key="period.id">
+                    <div slot="label" class="label-box">{{ getTimeLabel(period.timeType) }} 鏁版嵁</div>
+                    <el-radio-group v-model="period.calcType">
+                        <el-radio-button label="CALC">鑷姩璁$畻</el-radio-button>
+                        <el-radio-button label="INPUT">鎵嬪伐褰曞叆</el-radio-button>
+                    </el-radio-group>
+                </el-form-item>
+                <el-form-item>
+                    <div slot="label" class="label-box">鏄惁璁$畻灏栧嘲骞宠胺</div>
+                    <el-radio-group v-model="indexStorageData.indexFormula.isPvCalc">
+                        <el-radio-button :label="1">鏄�</el-radio-button>
+                        <el-radio-button :label="0">鍚�</el-radio-button>
+                    </el-radio-group>
+                </el-form-item>
+            </el-form>
+        </BaseCard>
+        <div slot="footer" class="text-right mt20">
+            <el-button @click="saveIndexStorageFun" type="primary">淇濆瓨鏁版嵁瀛樺偍 </el-button>
+        </div>
+    </el-dialog>
+
+    <calculateFunction ref="calculateFunctionRef" @confirmCalcFunction='confirmCalcFunction' />
+</template>
+<script setup>
+import { debounce } from 'lodash';
+const { proxy } = getCurrentInstance()
+import { parseFormula, getNodeIndex, includeChildrenNode, getIndexStorage, saveIndexStorage } from '@/api/modelConfiguration/indexWarehouse'
+import calculateFunction from './CalculateFunction.vue'
+import { el } from 'element-plus/es/locales.mjs';
+
+let visible = ref(false)
+let currentNode = ref(null)
+let indexStorageData = ref({
+    indexFormula: {
+        id: '',
+        indexId: '',
+        formulaText: '',
+        indexFormulaParams: [],
+        isPvCalc: 0
+    },
+    indexStorage: []
+})
+function handleOpen(row, selectNode) {
+    visible.value = true
+    currentNode.value = selectNode
+}
+function handleClose() {
+    visible.value = false
+}
+
+
+let calculateFunctionRef = ref(null)
+function selectCalcFunction() {
+    if (calculateFunctionRef.value) {
+        calculateFunctionRef.value.handleOpen()
+    }
+}
+
+function parseFormulaFun() {
+    parseFormula({ "calcText": indexStorageData.value.indexFormula.formulaText }).then(response => {
+        let newParamData = response.data;
+        let paramData = indexStorageData.value.indexFormula.indexFormulaParams.filter(f => {
+            let exist = newParamData.find(param => param === f.paramName);
+            return exist && exist.length > 0;
+        });
+
+        newParamData.forEach(item => {
+            let exist = paramData.find(param => param.paramName === item);
+            if (!exist || exist.length === 0) {
+                paramData.push({
+                    "paramName": item,
+                    "paramValue": ''
+                })
+            }
+        });
+
+        indexStorageData.value.indexFormula.indexFormulaParams = paramData;
+    });
+}
+
+defineExpose({ handleOpen, getIndexStorageFun })
+
+function confirmCalcFunction(funcText) {
+    const elInput = document.getElementById('formulaText'); //鏍规嵁id閫夋嫨鍣ㄩ�変腑瀵硅薄
+    const startPos = elInput.selectionStart;// input 绗�0涓瓧绗﹀埌閫変腑鐨勫瓧绗�
+    const endPos = elInput.selectionEnd;// 閫変腑鐨勫瓧绗﹀埌鏈�鍚庣殑瀛楃
+    if (startPos === undefined || endPos === undefined) {
+        return;
+    }
+    const txt = elInput.value;
+    // 灏嗚〃鎯呮坊鍔犲埌閫変腑鐨勫厜鏍囦綅缃�
+    const result = txt.substring(0, startPos) + funcText + txt.substring(endPos);
+    elInput.value = result;// 璧嬪�肩粰input鐨剉alue
+    // 閲嶆柊瀹氫箟鍏夋爣浣嶇疆
+    elInput.focus();
+    elInput.selectionStart = startPos + funcText.length;
+    elInput.selectionEnd = startPos + funcText.length;
+    indexStorageData.value.indexFormula.formulaText = result;
+}
+
+function getTimeLabel(timeType) {
+    return selectTimeTypeLabel(timeType);
+}
+function selectTimeTypeLabel(timeType) {
+    switch (timeType) {
+        case 'HOUR':
+            return "灏忔椂";
+        case 'SCHEDULING':
+            return "鐝粍";
+        case 'DAY':
+            return "澶�";
+        case 'MONTH':
+            return "鏈�";
+        case 'YEAR':
+            return "骞�";
+        default:
+            return "";
+    }
+}
+
+let rowId = ref()
+let nodeIndexArray = ref([])
+let code = ref()
+function getIndexStorageFun(indexId) {
+    rowId.value = indexId;
+    indexStorageData.value = {
+        indexFormula: {
+            id: '',
+            indexId: '',
+            formulaText: '',
+            indexFormulaParams: [],
+            isPvCalc: 0
+        },
+        indexStorage: []
+    }
+    // includeChildrenNode({ code: code.value }).then(res => {
+    //     nodeIndexArray.value = res.data;
+    // })
+    // getNodeIndex(currentNode.value.id).then((response) => {
+    //     nodeIndexArray.value = response.data;
+    // });
+
+    getIndexStorage(indexId).then(response => {
+        indexStorageData.value = response.data
+        if (!response.data.indexFormula.isPvCalc && response.data.indexFormula.isPvCalc != 0) {
+            indexStorageData.value.indexFormula.isPvCalc = 1
+        }
+    });
+
+    // getCalcPeriod().then(response => this.indexStorageData.indexStorage = response.data);
+}
+
+
+const debouncedSearch = debounce(async (queryString, cb) => {
+    try {
+        const res = await includeChildrenNode({ code: queryString, nodeId: currentNode.value.id });
+        if (res.code === 200) {
+            const values = res.data.map(item => ({
+                value: item.code,
+                name: item.name,
+                powerRoom: item.powerRoom
+            }));
+            cb(values);
+        }
+    } catch (error) {
+        cb([])
+        // 璋冪敤 cb([]) 鎴栬�呭叾浠栭敊璇鐞嗛�昏緫
+    }
+}, 1000);
+// function querySearch(queryString, cb) {
+//     let nodeIndex = nodeIndexArray.value;
+//     let results = queryString ? nodeIndex.filter((index) => {
+//         return index.code.toLowerCase().indexOf(queryString.toLowerCase()) === 0 ||
+//             index.name.toLowerCase().indexOf(queryString.toLowerCase()) === 0
+//     }) : nodeIndex;
+//     // 璋冪敤 callback 杩斿洖寤鸿鍒楄〃鐨勬暟鎹�
+//     let values = [];
+//     results.forEach(item => {
+//         values.push({
+//             value: item.code,
+//             name: item.name
+//         })
+//     });
+//     cb(values);
+// }
+
+function saveIndexStorageFun() {
+
+    saveIndexStorage(rowId.value, indexStorageData.value).then((response) => {
+        if (response.code === 200) {
+            proxy.$modal.msgSuccess("鏁版嵁瀛樺偍鎴愬姛锛�");
+            visible.value = false;
+        } else {
+            proxy.$modal.msgError("鏁版嵁瀛樺偍澶辫触锛�");
+        }
+    });
+}
+</script>
+
+<style scoped lang="scss">
+.custom-box {
+    display: flex;
+    align-items: center;
+
+}
+
+:deep .el-textarea__inner {
+    color: #fff;
+}
+
+.label-box {
+    width: 170px;
+    font-size: 16px;
+    color: #fff;
+    margin-right: 10px;
+}
+
+.name {
+    color: #a4a4a4;
+}
+
+.addr {
+    color: #a4a4a4;
+}
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/statisticalIndicatorManagement.vue b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/statisticalIndicatorManagement.vue
new file mode 100644
index 0000000..d2e49b8
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/components/statisticalIndicatorManagement/statisticalIndicatorManagement.vue
@@ -0,0 +1,180 @@
+<template>
+    <div class="page-box">
+        <div class="form-card">
+            <el-form :model="form" ref="queryRef" :inline="true" label-width="70px">
+                <el-form-item label="缂栫爜" prop="indexCategory">
+                    <el-select v-model="form.indexCategory" placeholder="璇烽�夋嫨鎸囨爣鍒嗙被" style="width: 200px">
+                        <el-option v-for="dict in sys_index_category" :key="dict.value" :label="dict.label"
+                            :value="dict.value" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="鍏抽敭瀛�" prop="name">
+                    <el-input v-model="form.name" placeholder="璇疯緭鍏ユ寚鏍囧悕绉�/缂栫爜" />
+                </el-form-item>
+
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                    <el-button icon="Plus" type="primary" @click="handleAdd">鏂板</el-button>
+                    <el-button icon="Delete" :disabled="multiple" @click="handleDel">鍒犻櫎</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <!-- <div class="ml20 mt20 mb20">
+            <el-button icon="Plus" type="primary" @click="handleAdd">鏂板</el-button>
+            <el-button icon="Delete" :disabled="multiple" @click="handleDel">鍒犻櫎</el-button>
+            <el-button icon="Upload">瀵煎叆</el-button>
+        </div> -->
+        <div class="table-box">
+            <el-table :data="tableData" v-loading="loading" height="calc(100vh - 450px)"
+                @selection-change="handleSelectionChange">
+                <el-table-column type="selection" width="55" align="center" show-overflow-tooltip />
+                <el-table-column prop="code" label="鎸囨爣缂栫爜" align="center" show-overflow-tooltip />
+                <el-table-column prop="name" label="鎸囨爣鍚嶇О" align="center" show-overflow-tooltip />
+                <el-table-column prop="indexCategory" label="绯荤粺鎸囨爣鍒嗙被" align="center" show-overflow-tooltip
+                    :formatter="(row, column) => proxy.selectDictLabel(sys_index_category, row.indexCategory)" />
+                <el-table-column prop="unitId" label="鍗曚綅" align="center" show-overflow-tooltip
+                    :formatter="(row, column) => proxy.selectDictLabel(sys_unit, row.unitId)" />
+                <el-table-column label="鎿嶄綔" width="230" align="center">
+                    <template #default="scope">
+                        <el-button link type="primary" icon="Edit" @click="handleAdd(scope.row)">
+                            淇敼
+                        </el-button>
+                        <el-button link type="primary" icon="Edit" @click="handleSave(scope.row)">
+                            瀛樺偍
+                        </el-button>
+                        <el-button link type="primary" icon="Delete" @click="handleDel(scope.row)">
+                            鍒犻櫎
+                        </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+        </div>
+        <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize" @pagination="getList(currentNode)" />
+        <edit-modal ref="EditModalRef" :indexType="indexType" :sys_index_category='sys_index_category'
+            :sys_unit='sys_unit' @getList="getList(currentNode)" />
+        <storageModal ref="storageModalRef" />
+
+
+    </div>
+</template>
+<script setup>
+import storageModal from './components/storage/StorageModal.vue';
+import EditModal from './components/EditModal.vue'
+import { listEnergyindex, delEnergyindex } from '@/api/modelConfiguration/indexWarehouse'
+const { proxy } = getCurrentInstance()
+const { sys_index_category } = proxy.useDict("sys_index_category");
+const { sys_unit } = proxy.useDict("sys_unit");
+
+const props = defineProps(['indexType'])
+let loading = ref(false)
+let form = ref({
+    indexCategory: null,
+    name: null
+})
+let tableData = ref([])
+let total = ref(0);
+let queryParams = ref({
+    pageNum: 1,
+    pageSize: 10,
+})
+let currentNode = ref(null)
+function getList(modelNode) {
+    currentNode.value = modelNode;
+
+    console.log(111, currentNode.value)
+    if (modelNode) {
+        loading.value = true;
+        form.value.nodeId = modelNode.id;
+        form.value.indexType = props.indexType;
+        listEnergyindex({ ...queryParams.value, ...form.value }).then(response => {
+            tableData.value = response.rows;
+            total.value = response.total;
+            loading.value = false;
+        });
+    } else {
+        tableData.value = [];
+    }
+}
+
+// // 鍥炴樉鏁版嵁瀛楀吀
+// function selectDictLabel(datas, value) {
+//     var actions = [];
+//     Object.keys(datas).map((key) => {
+//         if (datas[key].value == ('' + value)) {
+//             actions.push(datas[key].label);
+//             return false;
+//         }
+//     })
+//     return actions.join('');
+// }
+
+// 閫変腑鏁扮粍
+let ids = ref([])
+let names = ref([])
+// 闈炲涓鐢�
+let multiple = ref(true)
+function handleSelectionChange(selection) {
+    ids.value = selection.map(item => item.indexId);
+    names.value = selection.map(item => item.name);
+    multiple.value = !selection.length
+}
+function handleQuery() {
+    getList(currentNode.value)
+}
+
+function resetQuery() {
+    form.value = {
+        indexCategory: null,
+        name: null
+    }
+    getList(currentNode.value)
+}
+
+let EditModalRef = ref('')
+function handleAdd(row) {
+    if (EditModalRef.value) {
+        EditModalRef.value.handleOpen(row, currentNode.value)
+    }
+}
+
+function handleDel(row) {
+    const indexIds = row.indexId || ids.value
+    const indexNames = row.name || names.value
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鎸囨爣鍚嶄负"' + indexNames + '"鐨勬暟鎹」?', '璀﹀憡', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+    }).then(() => {
+        return delEnergyindex(currentNode.value.id, indexIds)
+    }).then(() => {
+        getList(currentNode.value)
+        proxy.$modal.msgError('鍒犻櫎鎴愬姛')
+    }).catch(function () {
+    })
+}
+
+let storageModalRef = ref('')
+function handleSave(row) {
+    if (storageModalRef.value) {
+        storageModalRef.value.handleOpen(row, currentNode.value)
+        storageModalRef.value.getIndexStorageFun(row.indexId)
+    }
+}
+
+defineExpose({ getList })
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+.page-box {
+    height: calc(100vh - 115px);
+
+}
+
+.table-box {
+    height: calc(100vh - 455px);
+}
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/indexwarehouse/indexWarehouse.vue b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/indexWarehouse.vue
new file mode 100644
index 0000000..a42b60d
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/indexwarehouse/indexWarehouse.vue
@@ -0,0 +1,326 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <div class="tree page-box">
+          <!-- <CardHeader class="mb20">鐢ㄨ兘鍗曞厓缁撴瀯</CardHeader> -->
+          <!-- <el-input v-model="filterText" style="width: 240px" placeholder="杈撳叆鍏抽敭瀛楄繘琛岃繃婊�" class="mb10" /> -->
+          <el-tree ref="treeRef" :props="defaultProps" :data="treeData" node-key="id" highlight-current
+            :filter-node-method="filterNode" :default-expanded-keys="treeExpandData" :expand-on-click-node="false"
+            @node-click="changeNode" accordion>
+            <template #default="{ node, data }">
+              <span>
+                <el-tooltip v-if="node.label.length > 6" class="item" effect="dark" :content="node.label"
+                  placement="top-end">
+                  <span>{{ node.label.slice(0, 6) + "..." }}</span>
+                </el-tooltip>
+                <span v-else id="b">{{ node.label }}</span>
+              </span>
+              <span class="node-opt">
+                <el-link title="鏂板涓嬬骇鑺傜偣" icon="Plus" @click="() => addNode(node, data)">
+                </el-link>
+                <el-link title="淇敼鑺傜偣" icon="EditPen" @click="() => editNode(node, data)">
+                </el-link>
+                <el-link title="鍒犻櫎鑺傜偣" icon="Delete" @click="() => delNode(node, data)">
+                </el-link>
+              </span>
+            </template>
+          </el-tree>
+        </div>
+      </div>
+      <div class="page-container-right">
+
+        <div class="mb20 mt20 ml20 tab-box">
+          <div class="tab-li" :class="tab == 1 ? 'is-tab' : ''" @click="handleTab('1')">
+            璁¢噺鍣ㄥ叿閰嶇疆淇℃伅
+          </div>
+          <div class="tab-li" :class="tab == 2 ? 'is-tab' : ''" @click="handleTab('2')">
+            缁熻鎸囨爣
+          </div>
+          <!-- <el-radio-group v-model="tab">
+                                <el-radio-button label="1" @click="handleTab('1')"> 璁惧閰嶇疆</el-radio-button>
+                                <el-radio-button label="2" @click="handleTab('2')"> 閲囬泦鎸囨爣</el-radio-button>
+                            </el-radio-group>-->
+        </div>
+        <BaseCard :title="currentNode ? currentNode.label + '--鑺傜偣閰嶇疆' : '鏆傛棤鑺傜偣閰嶇疆'">
+          <div>
+            <div class="content-box" v-if="tab == '1'">
+              <deviceConfig ref='deviceConfigRef' />
+            </div>
+            <div class="content-box1" v-if="tab == '2'">
+              <statisticalIndicatorManagement ref='statisticalIndicatorManagementRef' :indexType='"STATISTIC"' />
+            </div>
+          </div>
+        </BaseCard>
+
+        <!-- <statisticalIndicatorManagement ref="statisticalIndicatorManagementRef" :indexType='"STATISTIC"' /> -->
+      </div>
+    </div>
+    <treeNodeModal ref="treeNodeModalRef" @getList="getTreeList" modelCode="JCZBK_CODE" @addTreeList="addTreeList"
+      @addTreeSelectList="addTreeSelectList" />
+  </div>
+
+</template>
+
+<script setup>
+import treeNodeModal from './components/TreeNodeModal.vue'
+import deviceConfig from './components/deviceConfig/DeviceConfig.vue'
+import statisticalIndicatorManagement from './components/statisticalIndicatorManagement/statisticalIndicatorManagement.vue';
+import { treeList, delModelNode, hasEnergyIndex, } from '@/api/modelConfiguration/indexWarehouse'
+const { proxy } = getCurrentInstance();
+
+let currentNode = ref()
+let treeRef = ref()
+let tab = ref(1)
+let treeData = ref([])
+
+const defaultProps = ref({
+  children: 'children',
+  label: 'label',
+})
+
+//妫�绱㈡爲
+let filterText = ref('')
+
+const filterNode = (value, data) => {
+  if (!value) return true
+  return data.label.includes(value)
+}
+watch(filterText, (val) => {
+  // 妫�鏌reeRef.value鏄惁鏄竴涓湁鏁堢殑ElTree瀹炰緥
+  if (treeRef.value && typeof treeRef.value.filter === 'function') {
+    // 璋冪敤filter鏂规硶
+    treeRef.value.filter(val);
+  } else {
+    // treeRef.value鏃犳晥锛屽鐞嗛敊璇�
+    console.error('error');
+  }
+});
+
+let treeExpandData = ref([])
+let isFirstLeafNode = ref(false)
+let deviceConfigRef = ref(null)
+let statisticalIndicatorManagementRef = ref(null)
+//鑾峰彇鏍戝垪琛�
+function getTreeList() {
+  treeList({ modelCode: 'JCZBK_CODE' }).then(res => {
+    let { data } = res
+    treeData.value = data;
+    let chooseNode = null;
+    if (data.length > 0) {
+      if (data[0].children && data[0].children.length !== 0 && isFirstLeafNode.value) {
+        if (data[0].children[0].children && data[0].children[0].children.length !== 0) {
+          chooseNode = data[0].children[0].children[0];
+        } else {
+          chooseNode = data[0].children[0];
+        }
+      } else {
+        chooseNode = data[0];
+      }
+      currentNode.value = chooseNode;
+      treeExpandData.value.push(chooseNode.id);
+      nextTick(() => {
+        treeRef.value.setCurrentKey(chooseNode.id);
+        if (tab.value == 1 && deviceConfigRef.value) {
+          deviceConfigRef.value.getList(chooseNode)
+        }
+        if (tab.value == 2 && statisticalIndicatorManagementRef.value) {
+          statisticalIndicatorManagementRef.value.getList(chooseNode)
+        }
+
+      });
+    }
+  })
+}
+getTreeList()
+
+//鏂板鍚庢洿鏂版爲鍒楄〃
+function addTreeList(newChild) {
+  treeData.value.push(newChild)
+}
+//鏂板鎴愬姛鍚庢洿鏂伴粯璁ら�変腑,骞惰Е鍙戞柟娉�
+function addTreeSelectList(addedNode, newChild) {
+  treeExpandData.value.push(addedNode.nodeId)
+  nextTick(() => {
+    treeRef.value.setCurrentNode(newChild)
+    currentNode.value = newChild;
+    if (tab.value == 1) {
+      if (deviceConfigRef.value) {
+        deviceConfigRef.value.getList(newChild)
+      }
+    }
+    if (tab.value == 2) {
+      if (statisticalIndicatorManagementRef.value) {
+        statisticalIndicatorManagementRef.value.getList(newChild)
+      }
+    }
+  })
+
+}
+//鏍戠偣鍑�
+function changeNode(data, node, ev) {
+  currentNode.value = data;
+  if (tab.value == 1) {
+    if (deviceConfigRef.value) {
+      deviceConfigRef.value.getList(data)
+    }
+  }
+  if (tab.value == 2) {
+    if (statisticalIndicatorManagementRef.value) {
+      statisticalIndicatorManagementRef.value.getList(data)
+    }
+  }
+}
+
+
+let treeNodeModalRef = ref(null)
+
+function addNode(node, data) {
+  if (treeNodeModalRef.value) {
+    treeNodeModalRef.value.handleOpen(node, data, true)
+  }
+}
+function editNode(node, data) {
+  if (treeNodeModalRef.value) {
+    treeNodeModalRef.value.handleOpen(node, data, false)
+  }
+}
+
+function delNode(node, data) {
+  if (data.children && data.children.length > 0) {
+    proxy.$modal.msgWarning('鍖呭惈瀛愯妭鐐癸紝涓嶈兘杩涜鍒犻櫎锛�')
+    return
+  }
+
+  hasEnergyIndex(data.id).then(response => {
+    if (response.data) {
+      proxy.$modal.msgWarning('褰撳墠鑺傜偣涓嬪瓨鍦ㄦ寚鏍囷紝涓嶈兘杩涜鍒犻櫎锛�')
+    } else {
+      proxy.$modal.confirm('鏄惁纭鍒犻櫎鍚嶄负"' + data.label + '"鐨勮妭鐐�?', '璀﹀憡', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+        .then(function () {
+          return delModelNode(data.id)
+        })
+        .then(() => {
+          let parent = node.parent
+          let children = parent.data.children || parent.data
+          let index = children.findIndex(d => d.id === data.id)
+          children.splice(index, 1)
+
+          nextTick(() => {
+            if (parent.data && parent.data.id) {
+              changeNode(parent.data)
+            } else {
+              changeNode(null)
+            }
+          })
+          proxy.$modal.msgSuccess('鍒犻櫎鎴愬姛')
+        })
+        .catch(function () {
+        })
+    }
+  })
+}
+
+function handleTab(value) {
+  tab.value = value
+  nextTick(() => {
+    if (value == 1 && deviceConfigRef.value) {
+      deviceConfigRef.value.getList(currentNode.value)
+    }
+    if (value == 2 && statisticalIndicatorManagementRef.value) {
+      statisticalIndicatorManagementRef.value.getList(currentNode.value)
+    }
+
+  });
+
+}
+</script>
+
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+
+.page-box {
+  height: calc(100vh - 145px);
+
+  .tree-box {
+    height: calc(100% - 70px);
+    overflow-y: auto !important;
+  }
+
+  .select-box {
+    display: flex;
+    align-items: center;
+
+    :deep .el-icon {
+      color: #fff;
+      margin: 0 10px 0 15px;
+      font-size: 20px;
+      // &:hover{
+      //     color: #3371EB;
+      // }
+    }
+  }
+
+  .node-opt {
+    flex: 1;
+    text-align: right;
+    margin-right: 5px;
+
+    :deep .el-icon {
+      color: #fff;
+      margin-right: 5px;
+    }
+  }
+
+
+}
+
+:deep .el-tabs__nav-wrap:after {
+  background: transparent;
+}
+
+:deep .el-tabs__item {
+  color: #fff;
+  font-size: 20px;
+  padding: 0 20px;
+
+  &.is-active,
+  &:hover {
+    color: #999 !important;
+  }
+}
+
+.tab-box {
+  display: flex;
+  align-items: center;
+  color: #fff;
+  border-bottom: 1px solid #3371EB;
+  margin-right: 20px;
+
+  .tab-li {
+    cursor: pointer;
+    border: 1px solid #3371EB;
+    padding: 10px 25px;
+    border-radius: 5px 5px 0 0;
+  }
+
+  .is-tab {
+    background: #3371EB;
+  }
+}
+
+.content-box {
+  height: calc(100vh - 310px) !important;
+
+}
+
+.content-box1 {
+  height: calc(100vh - 290px) !important;
+
+}
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/metertopologychart/metertopologychart.vue b/zhitan-vue/src/views/modelconfiguration/metertopologychart/metertopologychart.vue
new file mode 100644
index 0000000..7b43852
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/metertopologychart/metertopologychart.vue
@@ -0,0 +1,240 @@
+<template>
+    <div class="page">
+        <!-- <div class="page-container"> -->
+            <!-- <div class="page-container-left"> -->
+                <!-- <div class="select-box mt20">
+                    <el-form :inline="true" label-width="85px">
+                        <el-form-item label="鏁版嵁妯″瀷" prop="energyType">
+                            <el-select v-model="modelData" placeholder="璇烽�夋嫨妯″瀷" filterable @change="changeModel">
+                                <el-option v-for="model in modelInfoOptions" :key="model.modelCode"
+                                    :label="model.modelName" :value="model.modelCode" />
+                            </el-select>
+                        </el-form-item>
+                    </el-form>
+                </div> -->
+                <!-- <LeftTree ref="leftTreeRef"  @handleNodeClick="handleNodeClick" /> -->
+            <!-- </div> -->
+            <!-- <div class="page-container-right"> -->
+                <BaseCard :title="currentNode ? currentNode.label + '--鑺傜偣閰嶇疆' : '鏆傛棤鑺傜偣閰嶇疆'">
+                    <div class="text-right mt10 mb10 mr10">
+                        <el-button type="primary" icon="Setting" @click="reset">閲嶆柊閫夋嫨鍦板浘</el-button>
+                        <el-button type="primary" icon="CircleCheck" @click="handleSaveSetting">淇濆瓨閰嶇疆</el-button>
+                    </div>
+                    <div class="content-box">
+                        <div class="svg-box">
+                            <el-upload v-if="filePath === '绌鸿妭鐐�'" class="configure-upload" drag ref="upload" :limit="1"
+                                :headers="uploadData.headers" :action="uploadData.url" :with-credentials="true"
+                                :on-success="handleFileSuccess" :show-file-list="false">
+                                <i class="el-icon-upload"></i>
+                                <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+                            </el-upload>
+                            <div id="svgHtml" v-if="filePath !== '绌鸿妭鐐�'">
+                                <div v-html="svgHtml" />
+                            </div>
+                        </div>
+                        <div class="table-box">
+                            <el-table :data="tags" height="100%">
+                                <el-table-column label="鍙傛暟" align="center" prop="param" />
+                                <el-table-column label="閲囬泦鐐�" align="center" prop="tag">
+                                    <template #default="scope">
+                                        <el-autocomplete popper-class="my-autocomplete" v-model="scope.row['tag']"
+                                            :fetch-suggestions="querySearch" placeholder="璇疯緭鍏ユ寚鏍囩紪鐮�">
+                                            <template #default="{ item }">
+                                                <div class="name">{{ item.value }}</div>
+                                                <span class="addr">{{ item.name }}</span>
+                                            </template>
+                                        </el-autocomplete>
+                                    </template>
+                                </el-table-column>
+                            </el-table>
+                        </div>
+                    </div>
+                </BaseCard>
+            <!-- </div> -->
+        <!-- </div> -->
+    </div>
+</template>
+
+<script setup>
+import { listModel } from '@/api/modelConfiguration/businessModel'
+import { getConfigure, updateEquipmentfile, saveSetting, getAllCollectTag } from "@/api/realTimeMonitor/realTimeMonitor";
+import { getToken } from "@/utils/auth";
+const { proxy } = getCurrentInstance();
+let modelData = ref(null)
+let modelInfoOptions = ref([]) //涓嬫媺鍒楄〃
+
+let uploadData = {
+    headers: {
+        Authorization: "Bearer " + getToken()
+    },
+    url: import.meta.env.VITE_APP_BASE_API + "/equipmentFile/upload"
+}
+
+//鑾峰彇涓嬫媺鍒楄〃
+function searchList() {
+    listModel({ isShow: 1 }).then(response => {
+        modelInfoOptions.value = response.data;
+        if (modelInfoOptions.value.length > 0) {
+            modelData.value = modelInfoOptions.value[0].modelCode;
+        }
+    });
+}
+searchList()
+//閫変腑涓嬫媺
+function changeModel(e) {
+    modelData.value = e
+}
+
+//閫変腑鏍�
+let currentNode = reactive({
+    id: "ec7330c3-8294-4214-8aa3-4a6e84837cc4",
+    label: "缇庢灄",
+    nodeCategory: "0"
+})
+getConfigureList(currentNode.id)
+
+// //閫変腑鏍�
+// let currentNode = ref()
+// function handleNodeClick(data) {
+//     currentNode.value = data
+//     getConfigureList(data.id)
+// }
+
+let filePath = ref()
+let tags = ref([])
+let svgHtml = ref()
+function getConfigureList(id) {
+    getConfigure(id).then(response => {
+        filePath.value = '绌鸿妭鐐�'
+        console.log(111, response)
+        tags.value = [];
+        svgHtml.value = ''
+        if (response.code === 200) {
+            if (response.data) {
+                filePath.value = import.meta.env.VITE_APP_BASE_API + response.data.filePath;
+                tags.value = response.data.infoList;
+                getSvg();
+            }
+        } else {
+            proxy.$modal.msgError(response.msg)
+        }
+    });
+
+}
+let from = ref({
+    nodeId: '',
+    fileName: '',
+    svgType: ''
+})
+function handleFileSuccess(response, file, fileList) {
+    console.log('handleFileSuccess-->', response, file, fileList)
+    if (response.code === 200) {
+        from.value.nodeId = currentNode.value.id;
+        from.value.filePath = response.msg;
+        from.value.svgType = 'COLLECT';
+        updateEquipmentfile(from.value).then(result => {
+            if (result.code === 200) {
+                filePath.value = import.meta.env.VITE_APP_BASE_API + response.msg;
+                tags.value = [];
+                getSvg();
+            } else {
+                proxy.$modal.msgError(result.msg)
+            }
+        });
+    } else {
+        proxy.$modal.msgError(response.msg)
+    }
+}
+function getSvg() {
+    const xhr = new XMLHttpRequest();
+    xhr.open("GET", filePath.value, true);
+    xhr.send();
+    /* 鐩戝惉xhr瀵硅薄 */
+    xhr.addEventListener("load", () => {
+        svgHtml.value = xhr.responseText;
+        let values = xhr.responseXML.getElementsByTagName('text');
+        let tagTemps = [];
+        for (let i = 0; i < values.length; i++) {
+            if (values[i].getAttribute("id") != undefined)
+                tagTemps.push({
+                    "param": values[i].textContent,
+                    "tag": "",
+                    "tagType": "COLLECT"
+                });
+        }
+        console.log(tags.value.length, tagTemps.length);
+        if (tags.value.length === 0 || tags.value.length != tagTemps.length) {
+            tags.value = [];
+            tags.value = tagTemps;
+            console.log("222", tags.value.length, tagTemps.length);
+        }
+    });
+}
+function reset() {
+    filePath.value = '绌鸿妭鐐�';
+}
+function handleSaveSetting() {
+    saveSetting(currentNode.value.id, tags.value).then(response => {
+        if (response.code === 200) {
+            proxy.$modal.msgSuccess(response.msg);
+        } else {
+            proxy.$modal.msgError(response.msg);
+        }
+    });
+}
+
+function querySearch(queryString, cb) {
+    if (queryString) {
+        getAllCollectTag({ codeOrName: queryString, indexType: 'COLLECT' }).then(response => {
+            // 璋冪敤 callback 杩斿洖寤鸿鍒楄〃鐨勬暟鎹�
+            let result = response.data;
+            let values = [];
+            result.forEach(item => {
+                values.push({
+                    value: item.code,
+                    name: item.name
+                })
+            });
+            cb(values);
+        });
+    }
+}
+</script>
+
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+.tree-box {
+    height: calc(100vh - 210px) !important;
+    max-height: calc(100vh - 210px) !important;
+    margin-top: 0 !important;
+}
+
+
+
+.content-box {
+    height: calc(100vh - 260px);
+    display: flex;
+
+    .svg-box {
+        flex: 1;
+        height: 100%;
+        overflow: auto;
+
+        .el-upload__text {
+            height: calc(100vh - 350px);
+        }
+
+        img {
+            height: 100%;
+        }
+    }
+
+    .table-box {
+        width: 300px;
+        margin: 0;
+    }
+
+}
+</style>
diff --git a/zhitan-vue/src/views/modelconfiguration/setpeakvalley/setPeakValley.vue b/zhitan-vue/src/views/modelconfiguration/setpeakvalley/setPeakValley.vue
new file mode 100644
index 0000000..5eef2db
--- /dev/null
+++ b/zhitan-vue/src/views/modelconfiguration/setpeakvalley/setPeakValley.vue
@@ -0,0 +1,367 @@
+<template>
+  <div class="page">
+    <div class="form-card">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input v-model="queryParams.remark" placeholder="璇疯緭鍏ュ娉�" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="Search" @click="handleQuery">
+            鎼滅储
+          </el-button>
+          <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="table-box">
+      <div class="mt20 mb20">
+        <el-button type="primary" icon="Plus" @click="handleAdd">
+          鏃舵閰嶇疆
+        </el-button>
+      </div>
+      <el-table v-loading="loading" :data="productoutputList" style="width: 100%" row-key="id">
+        <el-table-column label="鐢熸晥寮�濮嬫棩鏈�" align="center" key="beginDate" prop="beginDate" :show-overflow-tooltip="true" />
+        <el-table-column label="鐢熸晥缁撴潫鏃ユ湡" align="center" key="endDate" prop="endDate" :show-overflow-tooltip="true" />
+        <el-table-column label="澶囨敞" align="center" key="remark" prop="remark" :show-overflow-tooltip="true" />
+        <el-table-column label="鍒涘缓鏃堕棿" align="center" key="createTime" prop="createTime" :show-overflow-tooltip="true" />
+        <el-table-column label="鎿嶄綔" align="center" width="300" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="鐢典环閰嶇疆" placement="top">
+              <el-button link type="primary" icon="Plus" @click="handleExpandChange(scope.row)">
+                鐢典环閰嶇疆
+              </el-button>
+            </el-tooltip>
+            <el-tooltip content="缂栬緫" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">
+                缂栬緫
+              </el-button>
+            </el-tooltip>
+            <el-tooltip content="鍒犻櫎" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">
+                鍒犻櫎
+              </el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="鐢熸晥鏃ユ湡" prop="beginEndDate">
+              <el-date-picker v-model="form.beginEndDate" type="daterange" range-separator="鍒�" format="YYYY-MM-DD"
+                value-format="YYYY-MM-DD" start-placeholder="鐢熸晥寮�濮嬫棩鏈�" end-placeholder="鐢熸晥缁撴潫鏃ユ湡" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <el-form-item label="澶囨敞" prop="remark">
+              <el-input v-model="form.remark" placeholder="璇疯緭鍏ュ娉�" type="textarea" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+          <el-button @click="cancel">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <el-dialog :title="titleChild" v-model="openChild" width="80%" append-to-body>
+      <el-form :model="formChild" ref="formChildRef" label-width="100px">
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="鐢熸晥鏃ユ湡" prop="beginEndDate">
+              {{ formChild.beginDate }} 鍒� {{ formChild.endDate }}
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row v-for="(item, index) in formChild.data" :key="item.id">
+          <el-col :span="4">
+            <el-form-item label="绫诲埆鍚嶇О" :prop="'data.' + index + '.type'" :rules="{
+              required: true,
+              message: '绫诲埆鍚嶇О涓嶈兘涓虹┖',
+              trigger: ['change'],
+            }">
+              <el-select v-model="item.type" placeholder="绫诲埆鍚嶇О" clearable style="width: 100%">
+                <el-option v-for="dict in electricity_price" :key="dict.value" :label="dict.label"
+                  :value="dict.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="鏃舵鐢典环" :prop="'data.' + index + '.effecticityPrice'" :rules="{
+              required: true,
+              message: '鏃舵鐢典环涓嶈兘涓虹┖',
+              trigger: ['blur', 'change'],
+            }">
+              <el-input-number v-model="item.effecticityPrice" :min="0.0" :precision="2" :step="0.1"
+                placeholder="鏃舵鐢典环" style="width: 100%" controls-position="right" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="寮�濮嬫椂闂�" :prop="'data.' + index + '.startTime'" :rules="{
+              required: true,
+              message: '寮�濮嬫椂闂翠笉鑳戒负绌�',
+              trigger: ['blur', 'change'],
+            }">
+              <el-time-select v-model="item.startTime" placeholder="寮�濮嬫椂闂�" :max-time="item.stopTime" start="00:00:00"
+                step="00:30:00" end="24:00:00" style="width: 100%" format="HH:mm:00" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="缁撴潫鏃堕棿" :prop="'data.' + index + '.stopTime'" :rules="{
+              required: true,
+              message: '缁撴潫鏃堕棿涓嶈兘涓虹┖',
+              trigger: ['blur', 'change'],
+            }">
+              <el-time-select v-model="item.stopTime" placeholder="缁撴潫鏃堕棿" :min-time="item.startTime" start="00:00:00"
+                step="00:30:00" end="24:00:00" style="width: 100%" format="HH:mm:00" :disabled="item.startTime==null" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item label="澶囨敞" prop="remark">
+              <el-input v-model="item.remark" placeholder="澶囨敞" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="4">
+            <el-form-item>
+              <el-button link type="primary" icon="Plus" @click="handleAddChildChild">
+                鏂板
+              </el-button>
+              <el-button link type="primary" icon="Delete" @click="handleDeleteChildChild(item, index)"
+                v-if="formChild.data.length > 1">
+                鍒犻櫎
+              </el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitFormChild">纭� 瀹�</el-button>
+          <el-button @click="cancelChild">鍙� 娑�</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="product-output">
+import {
+  listPeakValleyConfiguration,
+  addPeakValleyConfiguration,
+  getPeakValleyConfiguration,
+  updatePeakValleyConfiguration,
+  delPeakValleyConfiguration,
+  listPeakValleyConfigurationChild,
+  updatePeakValleyConfigurationChild,
+} from "@/api/modelConfiguration/setPeakValley";
+import { nanoid } from "nanoid";
+const { proxy } = getCurrentInstance();
+const { electricity_price } = proxy.useDict("electricity_price");
+const productoutputList = ref([]);
+const open = ref(false);
+const loading = ref(false);
+const showSearch = ref(true);
+const title = ref("");
+const loadingChild = ref(true);
+const openChild = ref(false);
+const titleChild = ref("");
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    timeType: "YEAR",
+    dataTime: null,
+    total: 0,
+  },
+  rules: {
+    beginEndDate: [
+      {
+        required: true,
+        message: "鏈夋晥鏃堕棿涓嶈兘涓虹┖",
+        trigger: ["blur", "change"],
+      },
+    ],
+  },
+  formChild: {
+    data: [
+      {
+        id: nanoid(),
+        type: "",
+      },
+    ],
+  },
+});
+
+const { queryParams, form, rules, formChild } = toRefs(data);
+getList();
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鍒楄〃
+function getList() {
+  loading.value = true;
+  listPeakValleyConfiguration(proxy.addDateRange(queryParams.value)).then(
+    (res) => {
+      loading.value = false;
+      productoutputList.value = res.rows;
+      queryParams.value.total = res.total;
+    }
+  );
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鎼滅储
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  (queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    timeType: "YEAR",
+    dataTime: null,
+    total: 0,
+  }),
+    handleQuery();
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏂板
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "鏂板灏栧嘲骞宠胺鏃舵閰嶇疆";
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-缂栬緫
+function handleUpdate(row) {
+  reset();
+  getPeakValleyConfiguration(row.id).then((response) => {
+    form.value = response.data;
+    form.value.beginEndDate = [response.data.beginDate, response.data.endDate];
+    open.value = true;
+    title.value = "缂栬緫灏栧嘲骞宠胺鏃舵閰嶇疆";
+  });
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏂板/缂栬緫-淇濆瓨
+function submitForm() {
+  proxy.$refs["formRef"].validate((valid) => {
+    if (valid) {
+      form.value.beginDate = form.value.beginEndDate[0];
+      form.value.endDate = form.value.beginEndDate[1];
+      delete form.value.beginEndDate;
+      if (form.value.id != undefined) {
+        updatePeakValleyConfiguration(form.value).then((response) => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addPeakValleyConfiguration(form.value).then((response) => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏂板/缂栬緫-鍙栨秷
+function cancel() {
+  open.value = false;
+  proxy.$refs.formRef.resetFields();
+  reset();
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏂板/缂栬緫-琛ㄥ崟閲嶇疆
+function reset() {
+  form.value = {
+    beginDate: null,
+    endDate: null,
+    remark: null,
+  };
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鍒犻櫎
+function handleDelete(row) {
+  proxy.$modal
+    .confirm(
+      '鏄惁纭鍒犻櫎鐢熸晥鏃ユ湡涓�"' +
+      row.beginDate +
+      "鍒�" +
+      row.endDate +
+      '"鐨勬暟鎹」锛�'
+    )
+    .then(function () {
+      return delPeakValleyConfiguration(row.id);
+    })
+    .then(() => {
+      getList();
+      proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    })
+    .catch(() => { });
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏃舵鐢典环閰嶇疆-鏂板
+function handleExpandChange(row) {
+  formChild.value.parentId = row.id;
+  formChild.value.beginEndDate = [row.beginDate, row.endDate];
+  formChild.value.beginDate = row.beginDate;
+  formChild.value.endDate = row.endDate;
+  listPeakValleyConfigurationChild(
+    proxy.addDateRange({
+      parentId: row.id,
+    })
+  ).then((res) => {
+    loadingChild.value = false;
+    formChild.value.data =
+      res.rows.length > 0
+        ? res.rows
+        : [
+          {
+            parentId: row.id,
+            type: null,
+            startTime: null,
+            stopTime: null,
+            effecticityPrice: 0.0,
+          },
+        ];
+  });
+  openChild.value = true;
+  titleChild.value = "灏栧嘲骞宠胺鏃舵鐢典环閰嶇疆";
+}
+function handleAddChildChild() {
+  console.log(formChild.value);
+  formChild.value.data.push({
+    parentId: formChild.value.parentId,
+    type: null,
+    startTime: null,
+    stopTime: null,
+    effecticityPrice: 0.0,
+  });
+  openChild.value = true;
+  titleChild.value = "灏栧嘲骞宠胺鏃舵鐢典环閰嶇疆";
+}
+function handleDeleteChildChild(item, index) {
+  formChild.value.data.splice(index, 1);
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏃舵鐢典环閰嶇疆-鏂板/缂栬緫-淇濆瓨
+function submitFormChild() {
+  proxy.$refs["formChildRef"].validate((valid) => {
+    if (valid) {
+      updatePeakValleyConfigurationChild(formChild.value.data).then(
+        (response) => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          openChild.value = false;
+          getList();
+        }
+      );
+    }
+  });
+}
+// 妯″瀷閰嶇疆绠$悊-灏栧嘲骞宠胺閰嶇疆-鏂板/缂栬緫-鍙栨秷
+function cancelChild() {
+  openChild.value = false;
+  proxy.$refs.formChildRef.resetFields();
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/monitor/cache/index.vue b/zhitan-vue/src/views/monitor/cache/index.vue
new file mode 100644
index 0000000..aa46bf2
--- /dev/null
+++ b/zhitan-vue/src/views/monitor/cache/index.vue
@@ -0,0 +1,132 @@
+<template>
+  <div class="app-container">
+    <el-row>
+      <el-col :span="24" class="card-box">
+        <el-card>
+          <template #header><Monitor style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">鍩烘湰淇℃伅</span></template>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%">
+              <tbody>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">Redis鐗堟湰</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">杩愯妯″紡</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_mode == "standalone" ? "鍗曟満" : "闆嗙兢" }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">绔彛</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">瀹㈡埛绔暟</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div></td>
+                </tr>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">杩愯鏃堕棿(澶�)</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">浣跨敤鍐呭瓨</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">浣跨敤CPU</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">鍐呭瓨閰嶇疆</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div></td>
+                </tr>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">AOF鏄惁寮�鍚�</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.aof_enabled == "0" ? "鍚�" : "鏄�" }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">RDB鏄惁鎴愬姛</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">Key鏁伴噺</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.dbSize">{{ cache.dbSize }} </div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">缃戠粶鍏ュ彛/鍑哄彛</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.instantaneous_input_kbps }}kps/{{cache.info.instantaneous_output_kbps}}kps</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="12" class="card-box">
+        <el-card>
+          <template #header><PieChart style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">鍛戒护缁熻</span></template>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <div ref="commandstats" style="height: 420px" />
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="12" class="card-box">
+        <el-card>
+          <template #header><Odometer style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">鍐呭瓨淇℃伅</span></template>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <div ref="usedmemory" style="height: 420px" />
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup name="Cache">
+import { getCache } from '@/api/monitor/cache';
+import * as echarts from 'echarts';
+
+const cache = ref([]);
+const commandstats = ref(null);
+const usedmemory = ref(null);
+const { proxy } = getCurrentInstance();
+
+function getList() {
+  proxy.$modal.loading("姝e湪鍔犺浇缂撳瓨鐩戞帶鏁版嵁锛岃绋嶅�欙紒");
+  getCache().then(response => {
+    proxy.$modal.closeLoading();
+    cache.value = response.data;
+
+    const commandstatsIntance = echarts.init(commandstats.value, "macarons");
+    commandstatsIntance.setOption({
+      tooltip: {
+        trigger: "item",
+        formatter: "{a} <br/>{b} : {c} ({d}%)"
+      },
+      series: [
+        {
+          name: "鍛戒护",
+          type: "pie",
+          roseType: "radius",
+          radius: [15, 95],
+          center: ["50%", "38%"],
+          data: response.data.commandStats,
+          animationEasing: "cubicInOut",
+          animationDuration: 1000
+        }
+      ]
+    });
+    const usedmemoryInstance = echarts.init(usedmemory.value, "macarons");
+    usedmemoryInstance.setOption({
+      tooltip: {
+        formatter: "{b} <br/>{a} : " + cache.value.info.used_memory_human
+      },
+      series: [
+        {
+          name: "宄板��",
+          type: "gauge",
+          min: 0,
+          max: 1000,
+          detail: {
+            formatter: cache.value.info.used_memory_human
+          },
+          data: [
+            {
+              value: parseFloat(cache.value.info.used_memory_human),
+              name: "鍐呭瓨娑堣��"
+            }
+          ]
+        }
+      ]
+    })
+    window.addEventListener("resize", () => {
+      commandstatsIntance.resize();
+      usedmemoryInstance.resize();
+    },{passive: true});
+  })
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/monitor/cache/list.vue b/zhitan-vue/src/views/monitor/cache/list.vue
new file mode 100644
index 0000000..1696f3a
--- /dev/null
+++ b/zhitan-vue/src/views/monitor/cache/list.vue
@@ -0,0 +1,246 @@
+<template>
+  <div class="app-container">
+    <el-row :gutter="10">
+      <el-col :span="8">
+        <el-card style="height: calc(100vh - 125px)">
+          <template #header>
+            <Collection style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">缂撳瓨鍒楄〃</span>
+            <el-button
+              style="float: right; padding: 3px 0"
+              link
+              type="primary"
+              icon="Refresh"
+              @click="refreshCacheNames()"
+            ></el-button>
+          </template>
+          <el-table
+            v-loading="loading"
+            :data="cacheNames"
+            :height="tableHeight"
+            highlight-current-row
+            @row-click="getCacheKeys"
+            style="width: 100%"
+          >
+            <el-table-column
+              label="搴忓彿"
+              width="60"
+              type="index"
+            ></el-table-column>
+
+            <el-table-column
+              label="缂撳瓨鍚嶇О"
+              align="center"
+              prop="cacheName"
+              :show-overflow-tooltip="true"
+              :formatter="nameFormatter"
+            ></el-table-column>
+
+            <el-table-column
+              label="澶囨敞"
+              align="center"
+              prop="remark"
+              :show-overflow-tooltip="true"
+            />
+            <el-table-column
+              label="鎿嶄綔"
+              width="60"
+              align="center"
+              class-name="small-padding fixed-width"
+            >
+              <template #default="scope">
+                <el-button
+                  link
+                  type="primary"
+                  icon="Delete"
+                  @click="handleClearCacheName(scope.row)"
+                ></el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+      </el-col>
+
+      <el-col :span="8">
+        <el-card style="height: calc(100vh - 125px)">
+          <template #header>
+            <Key style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">閿悕鍒楄〃</span>
+            <el-button
+              style="float: right; padding: 3px 0"
+              link
+              type="primary"
+              icon="Refresh"
+              @click="refreshCacheKeys()"
+            ></el-button>
+          </template>
+          <el-table
+            v-loading="subLoading"
+            :data="cacheKeys"
+            :height="tableHeight"
+            highlight-current-row
+            @row-click="handleCacheValue"
+            style="width: 100%"
+          >
+            <el-table-column
+              label="搴忓彿"
+              width="60"
+              type="index"
+            ></el-table-column>
+            <el-table-column
+              label="缂撳瓨閿悕"
+              align="center"
+              :show-overflow-tooltip="true"
+              :formatter="keyFormatter"
+            >
+            </el-table-column>
+            <el-table-column
+              label="鎿嶄綔"
+              width="60"
+              align="center"
+              class-name="small-padding fixed-width"
+            >
+              <template #default="scope">
+                <el-button
+                  link
+                  type="primary"
+                  icon="Delete"
+                  @click="handleClearCacheKey(scope.row)"
+                ></el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+      </el-col>
+
+      <el-col :span="8">
+        <el-card :bordered="false" style="height: calc(100vh - 125px)">
+          <template #header>
+            <Document style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">缂撳瓨鍐呭</span>
+            <el-button
+              style="float: right; padding: 3px 0"
+              link
+              type="primary"
+              icon="Refresh"
+              @click="handleClearCacheAll()"
+              >娓呯悊鍏ㄩ儴</el-button
+            >
+          </template>
+          <el-form :model="cacheForm">
+            <el-row :gutter="32">
+              <el-col :offset="1" :span="22">
+                <el-form-item label="缂撳瓨鍚嶇О:" prop="cacheName">
+                  <el-input v-model="cacheForm.cacheName" :readOnly="true" />
+                </el-form-item>
+              </el-col>
+              <el-col :offset="1" :span="22">
+                <el-form-item label="缂撳瓨閿悕:" prop="cacheKey">
+                  <el-input v-model="cacheForm.cacheKey" :readOnly="true" />
+                </el-form-item>
+              </el-col>
+              <el-col :offset="1" :span="22">
+                <el-form-item label="缂撳瓨鍐呭:" prop="cacheValue">
+                  <el-input
+                    v-model="cacheForm.cacheValue"
+                    type="textarea"
+                    :rows="8"
+                    :readOnly="true"
+                  />
+                </el-form-item>
+              </el-col>
+            </el-row>
+          </el-form>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup name="CacheList">
+import { listCacheName, listCacheKey, getCacheValue, clearCacheName, clearCacheKey, clearCacheAll } from "@/api/monitor/cache";
+
+const { proxy } = getCurrentInstance();
+
+const cacheNames = ref([]);
+const cacheKeys = ref([]);
+const cacheForm = ref({});
+const loading = ref(true);
+const subLoading = ref(false);
+const nowCacheName = ref("");
+const tableHeight = ref(window.innerHeight - 200);
+
+/** 鏌ヨ缂撳瓨鍚嶇О鍒楄〃 */
+function getCacheNames() {
+  loading.value = true;
+  listCacheName().then(response => {
+    cacheNames.value = response.data;
+    loading.value = false;
+  });
+}
+
+/** 鍒锋柊缂撳瓨鍚嶇О鍒楄〃 */
+function refreshCacheNames() {
+  getCacheNames();
+  proxy.$modal.msgSuccess("鍒锋柊缂撳瓨鍒楄〃鎴愬姛");
+}
+
+/** 娓呯悊鎸囧畾鍚嶇О缂撳瓨 */
+function handleClearCacheName(row) {
+  clearCacheName(row.cacheName).then(response => {
+    proxy.$modal.msgSuccess("娓呯悊缂撳瓨鍚嶇О[" + row.cacheName + "]鎴愬姛");
+    getCacheKeys();
+  });
+}
+
+/** 鏌ヨ缂撳瓨閿悕鍒楄〃 */
+function getCacheKeys(row) {
+  const cacheName = row !== undefined ? row.cacheName : nowCacheName.value;
+  if (cacheName === "") {
+    return;
+  }
+  subLoading.value = true;
+  listCacheKey(cacheName).then(response => {
+    cacheKeys.value = response.data;
+    subLoading.value = false;
+    nowCacheName.value = cacheName;
+  });
+}
+
+/** 鍒锋柊缂撳瓨閿悕鍒楄〃 */
+function refreshCacheKeys() {
+  getCacheKeys();
+  proxy.$modal.msgSuccess("鍒锋柊閿悕鍒楄〃鎴愬姛");
+}
+
+/** 娓呯悊鎸囧畾閿悕缂撳瓨 */
+function handleClearCacheKey(cacheKey) {
+  clearCacheKey(cacheKey).then(response => {
+    proxy.$modal.msgSuccess("娓呯悊缂撳瓨閿悕[" + cacheKey + "]鎴愬姛");
+    getCacheKeys();
+  });
+}
+
+/** 鍒楄〃鍓嶇紑鍘婚櫎 */
+function nameFormatter(row) {
+  return row.cacheName.replace(":", "");
+}
+
+/** 閿悕鍓嶇紑鍘婚櫎 */
+function keyFormatter(cacheKey) {
+  return cacheKey.replace(nowCacheName.value, "");
+}
+
+/** 鏌ヨ缂撳瓨鍐呭璇︾粏 */
+function handleCacheValue(cacheKey) {
+  getCacheValue(nowCacheName.value, cacheKey).then(response => {
+    cacheForm.value = response.data;
+  });
+}
+
+/** 娓呯悊鍏ㄩ儴缂撳瓨 */
+function handleClearCacheAll() {
+  clearCacheAll().then(response => {
+    proxy.$modal.msgSuccess("娓呯悊鍏ㄩ儴缂撳瓨鎴愬姛");
+  });
+}
+
+getCacheNames();
+</script>
diff --git a/zhitan-vue/src/views/monitor/druid/index.vue b/zhitan-vue/src/views/monitor/druid/index.vue
new file mode 100644
index 0000000..13736ec
--- /dev/null
+++ b/zhitan-vue/src/views/monitor/druid/index.vue
@@ -0,0 +1,13 @@
+<template>
+   <div>
+      <i-frame v-model:src="url"></i-frame>
+   </div>
+</template>
+
+<script setup>
+import iFrame from '@/components/iFrame'
+
+import { ref } from 'vue';
+
+const url = ref(import.meta.env.VITE_APP_BASE_API + '/druid/login.html');
+</script>
diff --git a/zhitan-vue/src/views/monitor/job/index.vue b/zhitan-vue/src/views/monitor/job/index.vue
new file mode 100644
index 0000000..071626a
--- /dev/null
+++ b/zhitan-vue/src/views/monitor/job/index.vue
@@ -0,0 +1,483 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+         <el-form-item label="浠诲姟鍚嶇О" prop="jobName">
+            <el-input
+               v-model="queryParams.jobName"
+               placeholder="璇疯緭鍏ヤ换鍔″悕绉�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="浠诲姟缁勫悕" prop="jobGroup">
+            <el-select v-model="queryParams.jobGroup" placeholder="璇烽�夋嫨浠诲姟缁勫悕" clearable style="width: 200px">
+               <el-option
+                  v-for="dict in sys_job_group"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item label="浠诲姟鐘舵��" prop="status">
+            <el-select v-model="queryParams.status" placeholder="璇烽�夋嫨浠诲姟鐘舵��" clearable style="width: 200px">
+               <el-option
+                  v-for="dict in sys_job_status"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Plus"
+               @click="handleAdd"
+               v-hasPermi="['monitor:job:add']"
+            >鏂板</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="success"
+               plain
+               icon="Edit"
+               :disabled="single"
+               @click="handleUpdate"
+               v-hasPermi="['monitor:job:edit']"
+            >淇敼</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               :disabled="multiple"
+               @click="handleDelete"
+               v-hasPermi="['monitor:job:remove']"
+            >鍒犻櫎</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="warning"
+               plain
+               icon="Download"
+               @click="handleExport"
+               v-hasPermi="['monitor:job:export']"
+            >瀵煎嚭</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="info"
+               plain
+               icon="Operation"
+               @click="handleJobLog"
+               v-hasPermi="['monitor:job:query']"
+            >鏃ュ織</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange">
+         <el-table-column type="selection" width="55" align="center" />
+         <el-table-column label="浠诲姟缂栧彿" width="100" align="center" prop="jobId" />
+         <el-table-column label="浠诲姟鍚嶇О" align="center" prop="jobName" :show-overflow-tooltip="true" />
+         <el-table-column label="浠诲姟缁勫悕" align="center" prop="jobGroup">
+            <template #default="scope">
+               <dict-tag :options="sys_job_group" :value="scope.row.jobGroup" />
+            </template>
+         </el-table-column>
+         <el-table-column label="璋冪敤鐩爣瀛楃涓�" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
+         <el-table-column label="cron鎵ц琛ㄨ揪寮�" align="center" prop="cronExpression" :show-overflow-tooltip="true" />
+         <el-table-column label="鐘舵��" align="center">
+            <template #default="scope">
+               <el-switch
+                  v-model="scope.row.status"
+                  active-value="0"
+                  inactive-value="1"
+                  @change="handleStatusChange(scope.row)"
+               ></el-switch>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" width="200" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-tooltip content="淇敼" placement="top">
+                  <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['monitor:job:edit']"></el-button>
+               </el-tooltip>
+               <el-tooltip content="鍒犻櫎" placement="top">
+                  <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['monitor:job:remove']"></el-button>
+               </el-tooltip>
+               <el-tooltip content="鎵ц涓�娆�" placement="top">
+                  <el-button link type="primary" icon="CaretRight" @click="handleRun(scope.row)" v-hasPermi="['monitor:job:changeStatus']"></el-button>
+               </el-tooltip>
+               <el-tooltip content="浠诲姟璇︾粏" placement="top">
+                  <el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['monitor:job:query']"></el-button>
+               </el-tooltip>
+               <el-tooltip content="璋冨害鏃ュ織" placement="top">
+                  <el-button link type="primary" icon="Operation" @click="handleJobLog(scope.row)" v-hasPermi="['monitor:job:query']"></el-button>
+               </el-tooltip>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+
+      <!-- 娣诲姞鎴栦慨鏀瑰畾鏃朵换鍔″璇濇 -->
+      <el-dialog :title="title" v-model="open" width="820px" append-to-body>
+         <el-form ref="jobRef" :model="form" :rules="rules" label-width="120px">
+            <el-row>
+               <el-col :span="12">
+                  <el-form-item label="浠诲姟鍚嶇О" prop="jobName">
+                     <el-input v-model="form.jobName" placeholder="璇疯緭鍏ヤ换鍔″悕绉�" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="浠诲姟鍒嗙粍" prop="jobGroup">
+                     <el-select v-model="form.jobGroup" placeholder="璇烽�夋嫨">
+                        <el-option
+                           v-for="dict in sys_job_group"
+                           :key="dict.value"
+                           :label="dict.label"
+                           :value="dict.value"
+                        ></el-option>
+                     </el-select>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item prop="invokeTarget">
+                     <template #label>
+                        <span>
+                           璋冪敤鏂规硶
+                           <el-tooltip placement="top">
+                              <template #content>
+                                 <div>
+                                    Bean璋冪敤绀轰緥锛歳yTask.ryParams('ry')
+                                    <br />Class绫昏皟鐢ㄧず渚嬶細com.ruoyi.quartz.task.RyTask.ryParams('ry')
+                                    <br />鍙傛暟璇存槑锛氭敮鎸佸瓧绗︿覆锛屽竷灏旂被鍨嬶紝闀挎暣鍨嬶紝娴偣鍨嬶紝鏁村瀷
+                                 </div>
+                              </template>
+                              <el-icon><question-filled /></el-icon>
+                           </el-tooltip>
+                        </span>
+                     </template>
+                     <el-input v-model="form.invokeTarget" placeholder="璇疯緭鍏ヨ皟鐢ㄧ洰鏍囧瓧绗︿覆" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="cron琛ㄨ揪寮�" prop="cronExpression">
+                     <el-input v-model="form.cronExpression" placeholder="璇疯緭鍏ron鎵ц琛ㄨ揪寮�">
+                        <template #append>
+                           <el-button type="primary" @click="handleShowCron">
+                              鐢熸垚琛ㄨ揪寮�
+                              <i class="el-icon-time el-icon--right"></i>
+                           </el-button>
+                        </template>
+                     </el-input>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="24" v-if="form.jobId !== undefined">
+                  <el-form-item label="鐘舵��">
+                     <el-radio-group v-model="form.status">
+                        <el-radio
+                           v-for="dict in sys_job_status"
+                           :key="dict.value"
+                           :label="dict.value"
+                        >{{ dict.label }}</el-radio>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="鎵ц绛栫暐" prop="misfirePolicy">
+                     <el-radio-group v-model="form.misfirePolicy">
+                        <el-radio-button label="1">绔嬪嵆鎵ц</el-radio-button>
+                        <el-radio-button label="2">鎵ц涓�娆�</el-radio-button>
+                        <el-radio-button label="3">鏀惧純鎵ц</el-radio-button>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="鏄惁骞跺彂" prop="concurrent">
+                     <el-radio-group v-model="form.concurrent">
+                        <el-radio-button label="0">鍏佽</el-radio-button>
+                        <el-radio-button label="1">绂佹</el-radio-button>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+            </el-row>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+               <el-button @click="cancel">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+
+     <el-dialog title="Cron琛ㄨ揪寮忕敓鎴愬櫒" v-model="openCron" append-to-body destroy-on-close>
+       <crontab ref="crontabRef" @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab>
+     </el-dialog>
+
+      <!-- 浠诲姟鏃ュ織璇︾粏 -->
+      <el-dialog title="浠诲姟璇︾粏" v-model="openView" width="700px" append-to-body>
+         <el-form :model="form" label-width="120px">
+            <el-row>
+               <el-col :span="12">
+                  <el-form-item label="浠诲姟缂栧彿锛�">{{ form.jobId }}</el-form-item>
+                  <el-form-item label="浠诲姟鍚嶇О锛�">{{ form.jobName }}</el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="浠诲姟鍒嗙粍锛�">{{ jobGroupFormat(form) }}</el-form-item>
+                  <el-form-item label="鍒涘缓鏃堕棿锛�">{{ form.createTime }}</el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="cron琛ㄨ揪寮忥細">{{ form.cronExpression }}</el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="涓嬫鎵ц鏃堕棿锛�">{{ parseTime(form.nextValidTime) }}</el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="璋冪敤鐩爣鏂规硶锛�">{{ form.invokeTarget }}</el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="浠诲姟鐘舵�侊細">
+                     <div v-if="form.status == 0">姝e父</div>
+                     <div v-else-if="form.status == 1">鏆傚仠</div>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="鏄惁骞跺彂锛�">
+                     <div v-if="form.concurrent == 0">鍏佽</div>
+                     <div v-else-if="form.concurrent == 1">绂佹</div>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="鎵ц绛栫暐锛�">
+                     <div v-if="form.misfirePolicy == 0">榛樿绛栫暐</div>
+                     <div v-else-if="form.misfirePolicy == 1">绔嬪嵆鎵ц</div>
+                     <div v-else-if="form.misfirePolicy == 2">鎵ц涓�娆�</div>
+                     <div v-else-if="form.misfirePolicy == 3">鏀惧純鎵ц</div>
+                  </el-form-item>
+               </el-col>
+            </el-row>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button @click="openView = false">鍏� 闂�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="Job">
+import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job";
+import Crontab from '@/components/Crontab'
+const router = useRouter();
+const { proxy } = getCurrentInstance();
+const { sys_job_group, sys_job_status } = proxy.useDict("sys_job_group", "sys_job_status");
+
+const jobList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+const openView = ref(false);
+const openCron = ref(false);
+const expression = ref("");
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    jobName: undefined,
+    jobGroup: undefined,
+    status: undefined
+  },
+  rules: {
+    jobName: [{ required: true, message: "浠诲姟鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    invokeTarget: [{ required: true, message: "璋冪敤鐩爣瀛楃涓蹭笉鑳戒负绌�", trigger: "blur" }],
+    cronExpression: [{ required: true, message: "cron鎵ц琛ㄨ揪寮忎笉鑳戒负绌�", trigger: "change" }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ瀹氭椂浠诲姟鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listJob(queryParams.value).then(response => {
+    jobList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 浠诲姟缁勫悕瀛楀吀缈昏瘧 */
+function jobGroupFormat(row, column) {
+  return proxy.selectDictLabel(sys_job_group.value, row.jobGroup);
+}
+/** 鍙栨秷鎸夐挳 */
+function cancel() {
+  open.value = false;
+  reset();
+}
+/** 琛ㄥ崟閲嶇疆 */
+function reset() {
+  form.value = {
+    jobId: undefined,
+    jobName: undefined,
+    jobGroup: undefined,
+    invokeTarget: undefined,
+    cronExpression: undefined,
+    misfirePolicy: 1,
+    concurrent: 1,
+    status: "0"
+  };
+  proxy.resetForm("jobRef");
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+// 澶氶�夋閫変腑鏁版嵁
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.jobId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+// 鏇村鎿嶄綔瑙﹀彂
+function handleCommand(command, row) {
+  switch (command) {
+    case "handleRun":
+      handleRun(row);
+      break;
+    case "handleView":
+      handleView(row);
+      break;
+    case "handleJobLog":
+      handleJobLog(row);
+      break;
+    default:
+      break;
+  }
+}
+// 浠诲姟鐘舵�佷慨鏀�
+function handleStatusChange(row) {
+  let text = row.status === "0" ? "鍚敤" : "鍋滅敤";
+  proxy.$modal.confirm('纭瑕�"' + text + '""' + row.jobName + '"浠诲姟鍚�?').then(function () {
+    return changeJobStatus(row.jobId, row.status);
+  }).then(() => {
+    proxy.$modal.msgSuccess(text + "鎴愬姛");
+  }).catch(function () {
+    row.status = row.status === "0" ? "1" : "0";
+  });
+}
+/* 绔嬪嵆鎵ц涓�娆� */
+function handleRun(row) {
+  proxy.$modal.confirm('纭瑕佺珛鍗虫墽琛屼竴娆�"' + row.jobName + '"浠诲姟鍚�?').then(function () {
+    return runJob(row.jobId, row.jobGroup);
+  }).then(() => {
+    proxy.$modal.msgSuccess("鎵ц鎴愬姛");})
+  .catch(() => {});
+}
+/** 浠诲姟璇︾粏淇℃伅 */
+function handleView(row) {
+  getJob(row.jobId).then(response => {
+    form.value = response.data;
+    openView.value = true;
+  });
+}
+/** cron琛ㄨ揪寮忔寜閽搷浣� */
+function handleShowCron() {
+  expression.value = form.value.cronExpression;
+  openCron.value = true;
+}
+/** 纭畾鍚庡洖浼犲�� */
+function crontabFill(value) {
+  form.value.cronExpression = value;
+}
+/** 浠诲姟鏃ュ織鍒楄〃鏌ヨ */
+function handleJobLog(row) {
+  const jobId = row.jobId || 0;
+  router.push('/monitor/job-log/index/' + jobId)
+}
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "娣诲姞浠诲姟";
+}
+/** 淇敼鎸夐挳鎿嶄綔 */
+function handleUpdate(row) {
+  reset();
+  const jobId = row.jobId || ids.value;
+  getJob(jobId).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "淇敼浠诲姟";
+  });
+}
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["jobRef"].validate(valid => {
+    if (valid) {
+      if (form.value.jobId != undefined) {
+        updateJob(form.value).then(response => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addJob(form.value).then(response => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const jobIds = row.jobId || ids.value;
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎瀹氭椂浠诲姟缂栧彿涓�"' + jobIds + '"鐨勬暟鎹」?').then(function () {
+    return delJob(jobIds);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download("monitor/job/export", {
+    ...queryParams.value,
+  }, `job_${new Date().getTime()}.xlsx`);
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/monitor/job/log.vue b/zhitan-vue/src/views/monitor/job/log.vue
new file mode 100644
index 0000000..1607a28
--- /dev/null
+++ b/zhitan-vue/src/views/monitor/job/log.vue
@@ -0,0 +1,277 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
+         <el-form-item label="浠诲姟鍚嶇О" prop="jobName">
+            <el-input
+               v-model="queryParams.jobName"
+               placeholder="璇疯緭鍏ヤ换鍔″悕绉�"
+               clearable
+               style="width: 240px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="浠诲姟缁勫悕" prop="jobGroup">
+            <el-select
+               v-model="queryParams.jobGroup"
+               placeholder="璇烽�夋嫨浠诲姟缁勫悕"
+               clearable
+               style="width: 240px"
+            >
+               <el-option
+                  v-for="dict in sys_job_group"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item label="鎵ц鐘舵��" prop="status">
+            <el-select
+               v-model="queryParams.status"
+               placeholder="璇烽�夋嫨鎵ц鐘舵��"
+               clearable
+               style="width: 240px"
+            >
+               <el-option
+                  v-for="dict in sys_common_status"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item label="鎵ц鏃堕棿" style="width: 308px">
+            <el-date-picker
+               v-model="dateRange"
+               value-format="YYYY-MM-DD"
+               type="daterange"
+               range-separator="-"
+               start-placeholder="寮�濮嬫棩鏈�"
+               end-placeholder="缁撴潫鏃ユ湡"
+            ></el-date-picker>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               :disabled="multiple"
+               @click="handleDelete"
+               v-hasPermi="['monitor:job:remove']"
+            >鍒犻櫎</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               @click="handleClean"
+               v-hasPermi="['monitor:job:remove']"
+            >娓呯┖</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="warning"
+               plain
+               icon="Download"
+               @click="handleExport"
+               v-hasPermi="['monitor:job:export']"
+            >瀵煎嚭</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button 
+               type="warning" 
+               plain 
+               icon="Close"
+               @click="handleClose"
+            >鍏抽棴</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange">
+         <el-table-column type="selection" width="55" align="center" />
+         <el-table-column label="鏃ュ織缂栧彿" width="80" align="center" prop="jobLogId" />
+         <el-table-column label="浠诲姟鍚嶇О" align="center" prop="jobName" :show-overflow-tooltip="true" />
+         <el-table-column label="浠诲姟缁勫悕" align="center" prop="jobGroup" :show-overflow-tooltip="true">
+            <template #default="scope">
+               <dict-tag :options="sys_job_group" :value="scope.row.jobGroup" />
+            </template>
+         </el-table-column>
+         <el-table-column label="璋冪敤鐩爣瀛楃涓�" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
+         <el-table-column label="鏃ュ織淇℃伅" align="center" prop="jobMessage" :show-overflow-tooltip="true" />
+         <el-table-column label="鎵ц鐘舵��" align="center" prop="status">
+            <template #default="scope">
+               <dict-tag :options="sys_common_status" :value="scope.row.status" />
+            </template>
+         </el-table-column>
+         <el-table-column label="鎵ц鏃堕棿" align="center" prop="createTime" width="180">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['monitor:job:query']">璇︾粏</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+
+      <!-- 璋冨害鏃ュ織璇︾粏 -->
+      <el-dialog title="璋冨害鏃ュ織璇︾粏" v-model="open" width="700px" append-to-body>
+         <el-form :model="form" label-width="100px">
+            <el-row>
+               <el-col :span="12">
+                  <el-form-item label="鏃ュ織搴忓彿锛�">{{ form.jobLogId }}</el-form-item>
+                  <el-form-item label="浠诲姟鍚嶇О锛�">{{ form.jobName }}</el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="浠诲姟鍒嗙粍锛�">{{ form.jobGroup }}</el-form-item>
+                  <el-form-item label="鎵ц鏃堕棿锛�">{{ form.createTime }}</el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="璋冪敤鏂规硶锛�">{{ form.invokeTarget }}</el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="鏃ュ織淇℃伅锛�">{{ form.jobMessage }}</el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="鎵ц鐘舵�侊細">
+                     <div v-if="form.status == 0">姝e父</div>
+                     <div v-else-if="form.status == 1">澶辫触</div>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="寮傚父淇℃伅锛�" v-if="form.status == 1">{{ form.exceptionInfo }}</el-form-item>
+               </el-col>
+            </el-row>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button @click="open = false">鍏� 闂�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="JobLog">
+import { getJob } from "@/api/monitor/job";
+import { listJobLog, delJobLog, cleanJobLog } from "@/api/monitor/jobLog";
+
+const { proxy } = getCurrentInstance();
+const { sys_common_status, sys_job_group } = proxy.useDict("sys_common_status", "sys_job_group");
+
+const jobLogList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const multiple = ref(true);
+const total = ref(0);
+const dateRange = ref([]);
+const route = useRoute();
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    dictName: undefined,
+    dictType: undefined,
+    status: undefined
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ璋冨害鏃ュ織鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listJobLog(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
+    jobLogList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+// 杩斿洖鎸夐挳
+function handleClose() {
+  const obj = { path: "/monitor/job" };
+  proxy.$tab.closeOpenPage(obj);
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  dateRange.value = [];
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+// 澶氶�夋閫変腑鏁版嵁
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.jobLogId);
+  multiple.value = !selection.length;
+}
+/** 璇︾粏鎸夐挳鎿嶄綔 */
+function handleView(row) {
+  open.value = true;
+  form.value = row;
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎璋冨害鏃ュ織缂栧彿涓�"' + ids.value + '"鐨勬暟鎹」?').then(function () {
+    return delJobLog(ids.value);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+/** 娓呯┖鎸夐挳鎿嶄綔 */
+function handleClean() {
+  proxy.$modal.confirm("鏄惁纭娓呯┖鎵�鏈夎皟搴︽棩蹇楁暟鎹」?").then(function () {
+    return cleanJobLog();
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("娓呯┖鎴愬姛");
+  }).catch(() => {});
+}
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download("monitor/jobLog/export", {
+    ...queryParams.value,
+  }, `job_log_${new Date().getTime()}.xlsx`);
+}
+
+(() => {
+  const jobId = route.params && route.params.jobId;
+  if (jobId !== undefined && jobId != 0) {
+    getJob(jobId).then(response => {
+      queryParams.value.jobName = response.data.jobName;
+      queryParams.value.jobGroup = response.data.jobGroup;
+      getList();
+    });
+  } else {
+    getList();
+  }
+})();
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/monitor/logininfor/index.vue b/zhitan-vue/src/views/monitor/logininfor/index.vue
new file mode 100644
index 0000000..9afb585
--- /dev/null
+++ b/zhitan-vue/src/views/monitor/logininfor/index.vue
@@ -0,0 +1,225 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
+         <el-form-item label="鐧诲綍鍦板潃" prop="ipaddr">
+            <el-input
+               v-model="queryParams.ipaddr"
+               placeholder="璇疯緭鍏ョ櫥褰曞湴鍧�"
+               clearable
+               style="width: 240px;"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鐢ㄦ埛鍚嶇О" prop="userName">
+            <el-input
+               v-model="queryParams.userName"
+               placeholder="璇疯緭鍏ョ敤鎴峰悕绉�"
+               clearable
+               style="width: 240px;"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鐘舵��" prop="status">
+            <el-select
+               v-model="queryParams.status"
+               placeholder="鐧诲綍鐘舵��"
+               clearable
+               style="width: 240px"
+            >
+               <el-option
+                  v-for="dict in sys_common_status"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item label="鐧诲綍鏃堕棿" style="width: 308px">
+            <el-date-picker
+               v-model="dateRange"
+               value-format="YYYY-MM-DD HH:mm:ss"
+               type="daterange"
+               range-separator="-"
+               start-placeholder="寮�濮嬫棩鏈�"
+               end-placeholder="缁撴潫鏃ユ湡"
+               :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+            ></el-date-picker>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               :disabled="multiple"
+               @click="handleDelete"
+               v-hasPermi="['monitor:logininfor:remove']"
+            >鍒犻櫎</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               @click="handleClean"
+               v-hasPermi="['monitor:logininfor:remove']"
+            >娓呯┖</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Unlock"
+               :disabled="single"
+               @click="handleUnlock"
+               v-hasPermi="['monitor:logininfor:unlock']"
+            >瑙i攣</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="warning"
+               plain
+               icon="Download"
+               @click="handleExport"
+               v-hasPermi="['monitor:logininfor:export']"
+            >瀵煎嚭</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table ref="logininforRef" v-loading="loading" :data="logininforList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange">
+         <el-table-column type="selection" width="55" align="center" />
+         <el-table-column label="璁块棶缂栧彿" align="center" prop="infoId" />
+         <el-table-column label="鐢ㄦ埛鍚嶇О" align="center" prop="userName" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" />
+         <el-table-column label="鍦板潃" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
+         <el-table-column label="鐧诲綍鍦扮偣" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
+         <el-table-column label="鎿嶄綔绯荤粺" align="center" prop="os" :show-overflow-tooltip="true" />
+         <el-table-column label="娴忚鍣�" align="center" prop="browser" :show-overflow-tooltip="true" />
+         <el-table-column label="鐧诲綍鐘舵��" align="center" prop="status">
+            <template #default="scope">
+               <dict-tag :options="sys_common_status" :value="scope.row.status" />
+            </template>
+         </el-table-column>
+         <el-table-column label="鎻忚堪" align="center" prop="msg" :show-overflow-tooltip="true" />
+         <el-table-column label="璁块棶鏃堕棿" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.loginTime) }}</span>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+   </div>
+</template>
+
+<script setup name="Logininfor">
+import { list, delLogininfor, cleanLogininfor, unlockLogininfor } from "@/api/monitor/logininfor";
+
+const { proxy } = getCurrentInstance();
+const { sys_common_status } = proxy.useDict("sys_common_status");
+
+const logininforList = ref([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const selectName = ref("");
+const total = ref(0);
+const dateRange = ref([]);
+const defaultSort = ref({ prop: "loginTime", order: "descending" });
+
+// 鏌ヨ鍙傛暟
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  ipaddr: undefined,
+  userName: undefined,
+  status: undefined,
+  orderByColumn: undefined,
+  isAsc: undefined
+});
+
+/** 鏌ヨ鐧诲綍鏃ュ織鍒楄〃 */
+function getList() {
+  loading.value = true;
+  list(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
+    logininforList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  dateRange.value = [];
+  proxy.resetForm("queryRef");
+  queryParams.value.pageNum = 1;
+  proxy.$refs["logininforRef"].sort(defaultSort.value.prop, defaultSort.value.order);
+}
+/** 澶氶�夋閫変腑鏁版嵁 */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.infoId);
+  multiple.value = !selection.length;
+  single.value = selection.length != 1;
+  selectName.value = selection.map(item => item.userName);
+}
+/** 鎺掑簭瑙﹀彂浜嬩欢 */
+function handleSortChange(column, prop, order) {
+  queryParams.value.orderByColumn = column.prop;
+  queryParams.value.isAsc = column.order;
+  getList();
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const infoIds = row.infoId || ids.value;
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎璁块棶缂栧彿涓�"' + infoIds + '"鐨勬暟鎹」?').then(function () {
+    return delLogininfor(infoIds);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+/** 娓呯┖鎸夐挳鎿嶄綔 */
+function handleClean() {
+  proxy.$modal.confirm("鏄惁纭娓呯┖鎵�鏈夌櫥褰曟棩蹇楁暟鎹」?").then(function () {
+    return cleanLogininfor();
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("娓呯┖鎴愬姛");
+  }).catch(() => {});
+}
+/** 瑙i攣鎸夐挳鎿嶄綔 */
+function handleUnlock() {
+  const username = selectName.value;
+  proxy.$modal.confirm('鏄惁纭瑙i攣鐢ㄦ埛"' + username + '"鏁版嵁椤�?').then(function () {
+    return unlockLogininfor(username);
+  }).then(() => {
+    proxy.$modal.msgSuccess("鐢ㄦ埛" + username + "瑙i攣鎴愬姛");
+  }).catch(() => {});
+}
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download("monitor/logininfor/export", {
+    ...queryParams.value,
+  }, `config_${new Date().getTime()}.xlsx`);
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/monitor/online/index.vue b/zhitan-vue/src/views/monitor/online/index.vue
new file mode 100644
index 0000000..eb17ebc
--- /dev/null
+++ b/zhitan-vue/src/views/monitor/online/index.vue
@@ -0,0 +1,106 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true">
+         <el-form-item label="鐧诲綍鍦板潃" prop="ipaddr">
+            <el-input
+               v-model="queryParams.ipaddr"
+               placeholder="璇疯緭鍏ョ櫥褰曞湴鍧�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鐢ㄦ埛鍚嶇О" prop="userName">
+            <el-input
+               v-model="queryParams.userName"
+               placeholder="璇疯緭鍏ョ敤鎴峰悕绉�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+      <el-table
+         v-loading="loading"
+         :data="onlineList.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
+         style="width: 100%;"
+      >
+         <el-table-column label="搴忓彿" width="50" type="index" align="center">
+            <template #default="scope">
+               <span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="浼氳瘽缂栧彿" align="center" prop="tokenId" :show-overflow-tooltip="true" />
+         <el-table-column label="鐧诲綍鍚嶇О" align="center" prop="userName" :show-overflow-tooltip="true" />
+         <el-table-column label="鎵�灞為儴闂�" align="center" prop="deptName" :show-overflow-tooltip="true" />
+         <el-table-column label="涓绘満" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
+         <el-table-column label="鐧诲綍鍦扮偣" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
+         <el-table-column label="鎿嶄綔绯荤粺" align="center" prop="os" :show-overflow-tooltip="true" />
+         <el-table-column label="娴忚鍣�" align="center" prop="browser" :show-overflow-tooltip="true" />
+         <el-table-column label="鐧诲綍鏃堕棿" align="center" prop="loginTime" width="180">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.loginTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="Delete" @click="handleForceLogout(scope.row)" v-hasPermi="['monitor:online:forceLogout']">寮洪��</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize" />
+   </div>
+</template>
+
+<script setup name="Online">
+import { forceLogout, list as initData } from "@/api/monitor/online";
+
+const { proxy } = getCurrentInstance();
+
+const onlineList = ref([]);
+const loading = ref(true);
+const total = ref(0);
+const pageNum = ref(1);
+const pageSize = ref(10);
+
+const queryParams = ref({
+  ipaddr: undefined,
+  userName: undefined
+});
+
+/** 鏌ヨ鐧诲綍鏃ュ織鍒楄〃 */
+function getList() {
+  loading.value = true;
+  initData(queryParams.value).then(response => {
+    onlineList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  pageNum.value = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+/** 寮洪��鎸夐挳鎿嶄綔 */
+function handleForceLogout(row) {
+    proxy.$modal.confirm('鏄惁纭寮洪��鍚嶇О涓�"' + row.userName + '"鐨勭敤鎴�?').then(function () {
+  return forceLogout(row.tokenId);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/monitor/operlog/index.vue b/zhitan-vue/src/views/monitor/operlog/index.vue
new file mode 100644
index 0000000..c580dac
--- /dev/null
+++ b/zhitan-vue/src/views/monitor/operlog/index.vue
@@ -0,0 +1,301 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
+         <el-form-item label="鎿嶄綔鍦板潃" prop="operIp">
+            <el-input
+               v-model="queryParams.operIp"
+               placeholder="璇疯緭鍏ユ搷浣滃湴鍧�"
+               clearable
+               style="width: 240px;"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="绯荤粺妯″潡" prop="title">
+            <el-input
+               v-model="queryParams.title"
+               placeholder="璇疯緭鍏ョ郴缁熸ā鍧�"
+               clearable
+               style="width: 240px;"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鎿嶄綔浜哄憳" prop="operName">
+            <el-input
+               v-model="queryParams.operName"
+               placeholder="璇疯緭鍏ユ搷浣滀汉鍛�"
+               clearable
+               style="width: 240px;"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="绫诲瀷" prop="businessType">
+            <el-select
+               v-model="queryParams.businessType"
+               placeholder="鎿嶄綔绫诲瀷"
+               clearable
+               style="width: 240px"
+            >
+               <el-option
+                  v-for="dict in sys_oper_type"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item label="鐘舵��" prop="status">
+            <el-select
+               v-model="queryParams.status"
+               placeholder="鎿嶄綔鐘舵��"
+               clearable
+               style="width: 240px"
+            >
+               <el-option
+                  v-for="dict in sys_common_status"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item label="鎿嶄綔鏃堕棿" style="width: 308px">
+            <el-date-picker
+               v-model="dateRange"
+               value-format="YYYY-MM-DD HH:mm:ss"
+               type="daterange"
+               range-separator="-"
+               start-placeholder="寮�濮嬫棩鏈�"
+               end-placeholder="缁撴潫鏃ユ湡"
+               :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+            ></el-date-picker>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               :disabled="multiple"
+               @click="handleDelete"
+               v-hasPermi="['monitor:operlog:remove']"
+            >鍒犻櫎</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               @click="handleClean"
+               v-hasPermi="['monitor:operlog:remove']"
+            >娓呯┖</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="warning"
+               plain
+               icon="Download"
+               @click="handleExport"
+               v-hasPermi="['monitor:operlog:export']"
+            >瀵煎嚭</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table ref="operlogRef" v-loading="loading" :data="operlogList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange">
+         <el-table-column type="selection" width="50" align="center" />
+         <el-table-column label="鏃ュ織缂栧彿" align="center" prop="operId" />
+         <el-table-column label="绯荤粺妯″潡" align="center" prop="title" :show-overflow-tooltip="true" />
+         <el-table-column label="鎿嶄綔绫诲瀷" align="center" prop="businessType">
+            <template #default="scope">
+               <dict-tag :options="sys_oper_type" :value="scope.row.businessType" />
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔浜哄憳" align="center" width="110" prop="operName" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" />
+         <el-table-column label="鎿嶄綔鍦板潃" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
+         <el-table-column label="鎿嶄綔鐘舵��" align="center" prop="status">
+            <template #default="scope">
+               <dict-tag :options="sys_common_status" :value="scope.row.status" />
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔鏃ユ湡" align="center" prop="operTime" width="180" sortable="custom" :sort-orders="['descending', 'ascending']">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.operTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="娑堣�楁椂闂�" align="center" prop="costTime" width="110" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']">
+            <template #default="scope">
+               <span>{{ scope.row.costTime }}姣</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="View" @click="handleView(scope.row, scope.index)" v-hasPermi="['monitor:operlog:query']">璇︾粏</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+
+      <!-- 鎿嶄綔鏃ュ織璇︾粏 -->
+      <el-dialog title="鎿嶄綔鏃ュ織璇︾粏" v-model="open" width="800px" append-to-body>
+         <el-form :model="form" label-width="100px">
+            <el-row>
+               <el-col :span="12">
+                  <el-form-item label="鎿嶄綔妯″潡锛�">{{ form.title }} / {{ typeFormat(form) }}</el-form-item>
+                  <el-form-item
+                    label="鐧诲綍淇℃伅锛�"
+                  >{{ form.operName }} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="璇锋眰鍦板潃锛�">{{ form.operUrl }}</el-form-item>
+                  <el-form-item label="璇锋眰鏂瑰紡锛�">{{ form.requestMethod }}</el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="鎿嶄綔鏂规硶锛�">{{ form.method }}</el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="璇锋眰鍙傛暟锛�">{{ form.operParam }}</el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="杩斿洖鍙傛暟锛�">{{ form.jsonResult }}</el-form-item>
+               </el-col>
+               <el-col :span="8">
+                  <el-form-item label="鎿嶄綔鐘舵�侊細">
+                     <div v-if="form.status === 0">姝e父</div>
+                     <div v-else-if="form.status === 1">澶辫触</div>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="8">
+                  <el-form-item label="娑堣�楁椂闂达細">{{ form.costTime }}姣</el-form-item>
+               </el-col>
+               <el-col :span="8">
+                  <el-form-item label="鎿嶄綔鏃堕棿锛�">{{ parseTime(form.operTime) }}</el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="寮傚父淇℃伅锛�" v-if="form.status === 1">{{ form.errorMsg }}</el-form-item>
+               </el-col>
+            </el-row>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button @click="open = false">鍏� 闂�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="Operlog">
+import { list, delOperlog, cleanOperlog } from "@/api/monitor/operlog";
+
+const { proxy } = getCurrentInstance();
+const { sys_oper_type, sys_common_status } = proxy.useDict("sys_oper_type","sys_common_status");
+
+const operlogList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+const dateRange = ref([]);
+const defaultSort = ref({ prop: "operTime", order: "descending" });
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    operIp: undefined,
+    title: undefined,
+    operName: undefined,
+    businessType: undefined,
+    status: undefined
+  }
+});
+
+const { queryParams, form } = toRefs(data);
+
+/** 鏌ヨ鐧诲綍鏃ュ織 */
+function getList() {
+  loading.value = true;
+  list(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
+    operlogList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 鎿嶄綔鏃ュ織绫诲瀷瀛楀吀缈昏瘧 */
+function typeFormat(row, column) {
+  return proxy.selectDictLabel(sys_oper_type.value, row.businessType);
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  dateRange.value = [];
+  proxy.resetForm("queryRef");
+  queryParams.value.pageNum = 1;
+  proxy.$refs["operlogRef"].sort(defaultSort.value.prop, defaultSort.value.order);
+}
+/** 澶氶�夋閫変腑鏁版嵁 */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.operId);
+  multiple.value = !selection.length;
+}
+/** 鎺掑簭瑙﹀彂浜嬩欢 */
+function handleSortChange(column, prop, order) {
+  queryParams.value.orderByColumn = column.prop;
+  queryParams.value.isAsc = column.order;
+  getList();
+}
+/** 璇︾粏鎸夐挳鎿嶄綔 */
+function handleView(row) {
+  open.value = true;
+  form.value = row;
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const operIds = row.operId || ids.value;
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎鏃ュ織缂栧彿涓�"' + operIds + '"鐨勬暟鎹」?').then(function () {
+    return delOperlog(operIds);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+/** 娓呯┖鎸夐挳鎿嶄綔 */
+function handleClean() {
+  proxy.$modal.confirm("鏄惁纭娓呯┖鎵�鏈夋搷浣滄棩蹇楁暟鎹」?").then(function () {
+    return cleanOperlog();
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("娓呯┖鎴愬姛");
+  }).catch(() => {});
+}
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download("monitor/operlog/export",{
+    ...queryParams.value,
+  }, `config_${new Date().getTime()}.xlsx`);
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/monitor/server/index.vue b/zhitan-vue/src/views/monitor/server/index.vue
new file mode 100644
index 0000000..a1178b1
--- /dev/null
+++ b/zhitan-vue/src/views/monitor/server/index.vue
@@ -0,0 +1,187 @@
+<template>
+  <div class="app-container">
+    <el-row>
+      <el-col :span="12" class="card-box">
+        <el-card>
+          <template #header><Cpu style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">CPU</span></template>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;">
+              <thead>
+                <tr>
+                  <th class="el-table__cell is-leaf"><div class="cell">灞炴��</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">鍊�</div></th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">鏍稿績鏁�</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.cpuNum }}</div></td>
+                </tr>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">鐢ㄦ埛浣跨敤鐜�</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.used }}%</div></td>
+                </tr>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">绯荤粺浣跨敤鐜�</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.sys }}%</div></td>
+                </tr>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">褰撳墠绌洪棽鐜�</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.free }}%</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="12" class="card-box">
+        <el-card>
+          <template #header><Tickets style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">鍐呭瓨</span></template>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;">
+              <thead>
+                <tr>
+                  <th class="el-table__cell is-leaf"><div class="cell">灞炴��</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">鍐呭瓨</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">JVM</div></th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">鎬诲唴瀛�</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.total }}G</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.total }}M</div></td>
+                </tr>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">宸茬敤鍐呭瓨</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.used}}G</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.used}}M</div></td>
+                </tr>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">鍓╀綑鍐呭瓨</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.free }}G</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.free }}M</div></td>
+                </tr>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">浣跨敤鐜�</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem" :class="{'text-danger': server.mem.usage > 80}">{{ server.mem.usage }}%</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm" :class="{'text-danger': server.jvm.usage > 80}">{{ server.jvm.usage }}%</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="24" class="card-box">
+        <el-card>
+          <template #header><Monitor style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">鏈嶅姟鍣ㄤ俊鎭�</span></template>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;">
+              <tbody>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">鏈嶅姟鍣ㄥ悕绉�</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.computerName }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">鎿嶄綔绯荤粺</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.osName }}</div></td>
+                </tr>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">鏈嶅姟鍣↖P</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.computerIp }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">绯荤粺鏋舵瀯</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.osArch }}</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="24" class="card-box">
+        <el-card>
+          <template #header><CoffeeCup style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">Java铏氭嫙鏈轰俊鎭�</span></template>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;table-layout:fixed;">
+              <tbody>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">Java鍚嶇О</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.name }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">Java鐗堟湰</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.version }}</div></td>
+                </tr>
+                <tr>
+                  <td class="el-table__cell is-leaf"><div class="cell">鍚姩鏃堕棿</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.startTime }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">杩愯鏃堕暱</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.runTime }}</div></td>
+                </tr>
+                <tr>
+                  <td colspan="1" class="el-table__cell is-leaf"><div class="cell">瀹夎璺緞</div></td>
+                  <td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.home }}</div></td>
+                </tr>
+                <tr>
+                  <td colspan="1" class="el-table__cell is-leaf"><div class="cell">椤圭洰璺緞</div></td>
+                  <td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.userDir }}</div></td>
+                </tr>
+                <tr>
+                  <td colspan="1" class="el-table__cell is-leaf"><div class="cell">杩愯鍙傛暟</div></td>
+                  <td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.inputArgs }}</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col :span="24" class="card-box">
+        <el-card>
+          <template #header><MessageBox style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">纾佺洏鐘舵��</span></template>
+          <div class="el-table el-table--enable-row-hover el-table--medium">
+            <table cellspacing="0" style="width: 100%;">
+              <thead>
+                <tr>
+                  <th class="el-table__cell el-table__cell is-leaf"><div class="cell">鐩樼璺緞</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">鏂囦欢绯荤粺</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">鐩樼绫诲瀷</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">鎬诲ぇ灏�</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">鍙敤澶у皬</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">宸茬敤澶у皬</div></th>
+                  <th class="el-table__cell is-leaf"><div class="cell">宸茬敤鐧惧垎姣�</div></th>
+                </tr>
+              </thead>
+              <tbody v-if="server.sysFiles">
+                <tr v-for="(sysFile, index) in server.sysFiles" :key="index">
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.dirName }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.sysTypeName }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.typeName }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.total }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.free }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.used }}</div></td>
+                  <td class="el-table__cell is-leaf"><div class="cell" :class="{'text-danger': sysFile.usage > 80}">{{ sysFile.usage }}%</div></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import { getServer } from '@/api/monitor/server'
+
+const server = ref([]);
+const { proxy } = getCurrentInstance();
+
+function getList() {
+  proxy.$modal.loading("姝e湪鍔犺浇鏈嶅姟鐩戞帶鏁版嵁锛岃绋嶅�欙紒");
+  getServer().then(response => {
+    server.value = response.data;
+    proxy.$modal.closeLoading();
+  });
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/peakvalley/period/period.vue b/zhitan-vue/src/views/peakvalley/period/period.vue
new file mode 100644
index 0000000..c550828
--- /dev/null
+++ b/zhitan-vue/src/views/peakvalley/period/period.vue
@@ -0,0 +1,776 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form
+            :model="queryParams"
+            ref="queryRef"
+            :inline="true"
+          >
+            <el-form-item label="鏃堕棿" prop="queryTime">
+              <el-date-picker
+                v-model="queryParams.queryTime"
+                format="YYYY-MM"
+                value-format="YYYY-MM"
+                type="month"
+                placeholder="鏃堕棿"
+                style="width: 100%"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div
+          style="
+            height: calc(100vh - 205px) !important;
+            max-height: calc(100vh - 205px) !important;
+            overflow-y: auto;
+          "
+          v-loading="loading"
+        >
+          <div class="card-list">
+            <div
+              class="card-list-item"
+              v-for="item in periodList"
+              :key="item.title"
+            >
+              <div class="item-top">
+                <div
+                  class="icon"
+                  :style="{ backgroundImage: 'url(' + item.icon + ')' }"
+                ></div>
+                <div class="name">{{ item.title }}</div>
+              </div>
+              <div
+                class="item-bottom"
+                v-for="(node, nodeIndex) in item.data"
+                :key="nodeIndex"
+              >
+                <div class="bottom-left">{{ node.label }}</div>
+                <div class="bottom-right" :style="{ color: item.color }">
+                  {{ node.value }}
+                </div>
+              </div>
+            </div>
+          </div>
+          <BaseCard :title="queryParams.nodeName+'-灏栧嘲骞宠胺鐢甸噺鍥�'">
+            <div class="chart-box">
+              <div id="Chart1" />
+            </div>
+          </BaseCard>
+          <BaseCard :title="queryParams.nodeName+'-灏栧嘲骞宠胺鐢佃垂鍥�'">
+            <div class="chart-box">
+              <div id="Chart2" />
+            </div>
+          </BaseCard>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup name="period">
+import { listPeriod } from "@/api/peakValley/period";
+import * as echarts from "echarts";
+const { proxy } = getCurrentInstance();
+import { useRoute } from "vue-router";
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getList();
+  }
+);
+const loading = ref(false);
+import icon1 from "@/assets/images/period/icon1.png";
+import icon2 from "@/assets/images/period/icon2.png";
+import icon3 from "@/assets/images/period/icon3.png";
+import icon4 from "@/assets/images/period/icon4.png";
+import icon5 from "@/assets/images/period/icon5.png";
+const periodList = ref([
+  {
+    icon: icon1,
+    color: "#3371eb",
+    title: "鍚堣",
+    data: [
+      {
+        label: "鑰楃數閲�/Kwh",
+        value: 0,
+      },
+      {
+        label: "鐢ㄧ數璐圭敤/楼",
+        value: 0,
+      },
+    ],
+  },
+  {
+    icon: icon2,
+    color: "#f52528",
+    title: "灏栨椂娈�",
+    data: [
+      {
+        label: "鑰楃數閲�/Kwh",
+        value: 0,
+      },
+      {
+        label: "鑰楃數鍗犳瘮",
+        value: 0 + "%",
+      },
+      {
+        label: "鐢ㄧ數璐圭敤/楼",
+        value: 0,
+      },
+      {
+        label: "璐圭敤鍗犳瘮",
+        value: 0 + "%",
+      },
+    ],
+  },
+  {
+    icon: icon3,
+    color: "#ff6200",
+    title: "宄版椂娈�",
+    data: [
+      {
+        label: "鑰楃數閲�/Kwh",
+        value: 0,
+      },
+      {
+        label: "鑰楃數鍗犳瘮",
+        value: 0 + "%",
+      },
+      {
+        label: "鐢ㄧ數璐圭敤/楼",
+        value: 0,
+      },
+      {
+        label: "璐圭敤鍗犳瘮",
+        value: 0 + "%",
+      },
+    ],
+  },
+  {
+    icon: icon4,
+    color: "#ffce0c",
+    title: "骞虫椂娈�",
+    data: [
+      {
+        label: "鑰楃數閲�/Kwh",
+        value: 0,
+      },
+      {
+        label: "鑰楃數鍗犳瘮",
+        value: 0 + "%",
+      },
+      {
+        label: "鐢ㄧ數璐圭敤/楼",
+        value: 0,
+      },
+      {
+        label: "璐圭敤鍗犳瘮",
+        value: 0 + "%",
+      },
+    ],
+  },
+  {
+    icon: icon5,
+    color: "#78e801",
+    title: "璋锋椂娈�",
+    data: [
+      {
+        label: "鑰楃數閲�/Kwh",
+        value: 0,
+      },
+      {
+        label: "鑰楃數鍗犳瘮",
+        value: 0 + "%",
+      },
+      {
+        label: "鐢ㄧ數璐圭敤/楼",
+        value: 0,
+      },
+      {
+        label: "璐圭敤鍗犳瘮",
+        value: 0 + "%",
+      },
+    ],
+  },
+]);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    nodeName: null,
+    timeType: "MONTH",
+    queryTime: null,
+  },
+  query: { ...useRoute().query },
+});
+const { queryParams, query } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  queryParams.value.queryTime = proxy.dayjs(new Date()).format("YYYY-MM");
+  handleQuery();
+}
+// 宄板钩璋疯兘鑰楀垎鏋�-宄板钩璋锋椂娈电粺璁�-鍒楄〃
+function getList() {
+  loading.value = true;
+  listPeriod(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    loading.value = false;
+    if (!!res.code && res.code == 200) {
+      if (!!res.data.totalVO) {
+        periodList.value[0].data[0].value = !!res.data.totalVO
+          .totalPowerConsumption
+          ? res.data.totalVO.totalPowerConsumption
+          : 0;
+        periodList.value[0].data[1].value = !!res.data.totalVO.totalCost
+          ? res.data.totalVO.totalCost
+          : 0;
+
+        periodList.value[1].data[0].value = !!res.data.totalVO
+          .tipPowerConsumption
+          ? res.data.totalVO.tipPowerConsumption
+          : 0;
+        periodList.value[1].data[1].value =
+          (!!res.data.totalVO.tipPowerProportion
+            ? res.data.totalVO.tipPowerProportion
+            : 0) + "%";
+        periodList.value[1].data[2].value = !!res.data.totalVO.tipPowerCost
+          ? res.data.totalVO.tipPowerCost
+          : 0;
+        periodList.value[1].data[3].value =
+          (!!res.data.totalVO.tipPowerCostProportion
+            ? res.data.totalVO.tipPowerCostProportion
+            : 0) + "%";
+
+        periodList.value[2].data[0].value = !!res.data.totalVO
+          .peakPowerConsumption
+          ? res.data.totalVO.peakPowerConsumption
+          : 0;
+        periodList.value[2].data[1].value =
+          (!!res.data.totalVO.peakPowerProportion
+            ? res.data.totalVO.peakPowerProportion
+            : 0) + "%";
+        periodList.value[2].data[2].value = !!res.data.totalVO.peakPowerCost
+          ? res.data.totalVO.peakPowerCost
+          : 0;
+        periodList.value[2].data[3].value =
+          (!!res.data.totalVO.peakPowerCostProportion
+            ? res.data.totalVO.peakPowerCostProportion
+            : 0) + "%";
+
+        periodList.value[3].data[0].value = !!res.data.totalVO
+          .flatPowerConsumption
+          ? res.data.totalVO.flatPowerConsumption
+          : 0;
+        periodList.value[3].data[1].value =
+          (!!res.data.totalVO.flatPowerProportion
+            ? res.data.totalVO.flatPowerProportion
+            : 0) + "%";
+        periodList.value[3].data[2].value = !!res.data.totalVO.flatPowerCost
+          ? res.data.totalVO.flatPowerCost
+          : 0;
+        periodList.value[3].data[3].value =
+          (!!res.data.totalVO.flatPowerCostProportion
+            ? res.data.totalVO.flatPowerCostProportion
+            : 0) + "%";
+
+        periodList.value[4].data[0].value = !!res.data.totalVO
+          .troughPowerConsumption
+          ? res.data.totalVO.troughPowerConsumption
+          : 0;
+        periodList.value[4].data[1].value =
+          (!!res.data.totalVO.troughPowerProportion
+            ? res.data.totalVO.troughPowerProportion
+            : 0) + "%";
+        periodList.value[4].data[2].value = !!res.data.totalVO.troughPowerCost
+          ? res.data.totalVO.troughPowerCost
+          : 0;
+        periodList.value[4].data[3].value =
+          (!!res.data.totalVO.troughPowerCostProportion
+            ? res.data.totalVO.troughPowerCostProportion
+            : 0) + "%";
+      }
+      // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+      if (echarts.getInstanceByDom(document.getElementById("Chart1"))) {
+        echarts.dispose(document.getElementById("Chart1"));
+      }
+      if (echarts.getInstanceByDom(document.getElementById("Chart2"))) {
+        echarts.dispose(document.getElementById("Chart2"));
+      }
+      const myChart1 = echarts.init(document.getElementById("Chart1"));
+      const myChart2 = echarts.init(document.getElementById("Chart2"));
+      if (!!res.data.powerConsumptionList) {
+        let xdata = [];
+        let ytip = [];
+        let ypeak = [];
+        let yflat = [];
+        let ytrough = [];
+        res.data.powerConsumptionList.map((item) => {
+          xdata.push(Number(item.xdata.slice(-2)) + "鏃�");
+          ytip.push(!!item.ytip ? item.ytip : 0);
+          ypeak.push(!!item.ypeak ? item.ypeak : 0);
+          yflat.push(!!item.yflat ? item.yflat : 0);
+          ytrough.push(!!item.ytrough ? item.ytrough : 0);
+        });
+        myChart1.setOption({
+          color: ["#f52528", "#ff6200", "#ffce0c", "#78e801"],
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+              textStyle: {
+                fontSize: 14,
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+          },
+          grid: {
+            top: "45",
+            left: "5%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            // right: 0,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          xAxis: {
+            type: "category",
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: xdata,
+          },
+          yAxis: {
+            name: "鑰楃數閲�(KWH)",
+            type: "value",
+            nameTextStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [0, 0, 5, 0],
+            },
+            axisLine: {
+              show: false,
+            },
+            splitLine: {
+              show: true,
+              lineStyle: {
+                type: "dashed",
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              //   formatter: '{value} ml'
+            },
+          },
+          series: [
+            {
+              name: "灏�",
+              type: "bar",
+              stack: "total",
+              barWidth: "27",
+              data: ytip,
+            },
+            {
+              name: "宄�",
+              type: "bar",
+              stack: "total",
+              barWidth: "27",
+              data: ypeak,
+            },
+            {
+              name: "骞�",
+              type: "bar",
+              stack: "total",
+              barWidth: "27",
+              data: yflat,
+            },
+            {
+              name: "璋�",
+              type: "bar",
+              stack: "total",
+              barWidth: "27",
+              data: ytrough,
+            },
+          ],
+        });
+      }
+      if (!!res.data.costList) {
+        let xdata = [];
+        let ytip = [];
+        let ypeak = [];
+        let yflat = [];
+        let ytrough = [];
+        res.data.costList.map((item) => {
+          xdata.push(Number(item.xdata.slice(-2)) + "鏃�");
+          ytip.push(!!item.ytip ? item.ytip : 0);
+          ypeak.push(!!item.ypeak ? item.ypeak : 0);
+          yflat.push(!!item.yflat ? item.yflat : 0);
+          ytrough.push(!!item.ytrough ? item.ytrough : 0);
+        });
+        myChart2.setOption({
+          color: ["#f52528", "#ff6200", "#ffce0c", "#78e801"],
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+              textStyle: {
+                fontSize: 14,
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+          },
+          grid: {
+            top: "45",
+            left: "5%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            // right: 0,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          xAxis: {
+            type: "category",
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: xdata,
+          },
+          yAxis: {
+            name: "鐢佃垂(鍏�)",
+            type: "value",
+            nameTextStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [0, 0, 5, 0],
+            },
+            axisLine: {
+              show: false,
+            },
+            splitLine: {
+              show: true,
+              lineStyle: {
+                type: "dashed",
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              //   formatter: '{value} ml'
+            },
+          },
+          series: [
+            {
+              name: "灏�",
+              type: "bar",
+              stack: "total",
+              barWidth: "27",
+              data: ytip,
+            },
+            {
+              name: "宄�",
+              type: "bar",
+              stack: "total",
+              barWidth: "27",
+              data: ypeak,
+            },
+            {
+              name: "骞�",
+              type: "bar",
+              stack: "total",
+              barWidth: "27",
+              data: yflat,
+            },
+            {
+              name: "璋�",
+              type: "bar",
+              stack: "total",
+              barWidth: "27",
+              data: ytrough,
+            },
+          ],
+        });
+      }
+      window.addEventListener("resize", () => {
+        myChart1.resize();
+        myChart2.resize();
+      },{passive: true});
+    }
+  });
+}
+// 宄板钩璋疯兘鑰楀垎鏋�-宄板钩璋锋椂娈电粺璁�-鎼滅储
+function handleQuery() {
+  getList();
+}
+// 宄板钩璋疯兘鑰楀垎鏋�-宄板钩璋锋椂娈电粺璁�-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value.queryTime = proxy.dayjs(new Date()).format("YYYY-MM");
+  handleQuery();
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+.themeDark {
+  .tree-title-box {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+
+  .card-list {
+    display: flex;
+    justify-content: space-between;
+    padding: 18px;
+
+    .card-list-item {
+      width: 19%;
+      height: 187px;
+      background: #223386;
+      border-radius: 5px 5px 5px 5px;
+      border: 1px solid #4868b7;
+      background-size: 100% 100%;
+      box-sizing: border-box;
+      padding: 10px 18px 13px 16px;
+
+      .item-top {
+        display: flex;
+        align-items: center;
+
+        .icon {
+          width: 40px;
+          height: 40px;
+          background-size: 100% 100%;
+        }
+
+        .name {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: bold;
+          font-size: 20px;
+          color: #fffdfd;
+          margin-left: 7px;
+        }
+      }
+
+      .item-bottom {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 10px;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 16px;
+
+        .bottom-left {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: 500;
+          font-size: 14px;
+          color: rgba(255, 255, 255, 0.7);
+        }
+
+        .bottom-right {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: 800;
+          font-size: 16px;
+          color: #f52528;
+        }
+      }
+    }
+  }
+}
+
+.themeLight {
+  .tree-title-box {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+
+  .card-list {
+    display: flex;
+    justify-content: space-between;
+    padding: 18px;
+
+    .card-list-item {
+      width: 19%;
+      height: 187px;
+      background: #fff;
+      border-radius: 5px 5px 5px 5px;
+      border: 1px solid #e8e8e8;
+      background-size: 100% 100%;
+      box-sizing: border-box;
+      padding: 10px 18px 13px 16px;
+
+      .item-top {
+        display: flex;
+        align-items: center;
+
+        .icon {
+          width: 40px;
+          height: 40px;
+          background-size: 100% 100%;
+        }
+
+        .name {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: bold;
+          font-size: 20px;
+          color: #000;
+          margin-left: 7px;
+        }
+      }
+
+      .item-bottom {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 10px;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: bold;
+        font-size: 16px;
+
+        .bottom-left {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: 500;
+          font-size: 14px;
+          color: #5d5c5c;
+        }
+
+        .bottom-right {
+          font-family: OPPOSans, OPPOSans;
+          font-weight: 800;
+          font-size: 16px;
+          color: #f52528;
+        }
+      }
+    }
+  }
+}
+
+.chart-box {
+  width: 100%;
+  height: 450px;
+
+  div {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.hamburger {
+  display: inline-block;
+  vertical-align: middle;
+  width: 20px;
+  height: 20px;
+  color: #fff;
+}
+
+.hamburger.is-active {
+  transform: rotate(180deg);
+}
+</style>
diff --git a/zhitan-vue/src/views/peakvalley/timeSharing/timeSharing.vue b/zhitan-vue/src/views/peakvalley/timeSharing/timeSharing.vue
new file mode 100644
index 0000000..7acba9e
--- /dev/null
+++ b/zhitan-vue/src/views/peakvalley/timeSharing/timeSharing.vue
@@ -0,0 +1,609 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="queryParams" ref="queryRef" :inline="true">
+            <el-form-item label="鏃ユ湡绛涢��" prop="dateRange">
+              <el-date-picker
+                v-model="queryParams.queryTime"
+                value-format="YYYY-MM-DD"
+                type="date"
+                placeholder="鏃ユ湡绛涢��"
+                style="width: 100%"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Download" @click="handleExport">
+                瀵煎嚭
+              </el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div
+          style="
+            height: calc(100vh - 205px) !important;
+            max-height: calc(100vh - 205px) !important;
+            overflow-y: auto;
+          "
+          v-loading="loading"
+        >
+          <el-row>
+            <el-col :span="16">
+              <BaseCard :title="queryParams.nodeName + '-宄板钩璋风數閲忓浘'">
+                <div class="chart-box">
+                  <div id="Chart1" />
+                </div>
+              </BaseCard>
+            </el-col>
+            <el-col :span="8">
+              <BaseCard :title="queryParams.nodeName + '-宄板钩璋峰崰姣斿浘'">
+                <div class="chart-box">
+                  <div id="Chart2" />
+                </div>
+              </BaseCard>
+            </el-col>
+            <el-col :span="24">
+              <BaseCard :title="queryParams.nodeName + '-宄板钩璋峰垎鏃剁粺璁�'">
+                <div class="table-box">
+                  <el-table
+                    :data="timeSharingList"
+                    style="width: 100%"
+                    show-summary
+                  >
+                    <el-table-column
+                      label="鏃堕棿"
+                      align="center"
+                      show-overflow-tooltip
+                    >
+                      <template #default="scope">
+                        {{ proxy.dayjs(scope.row.time).format("HH鏃�") }}
+                      </template>
+                    </el-table-column>
+                    <el-table-column label="灏�" align="center">
+                      <el-table-column
+                        prop="sharpPower"
+                        label="鑰楃數閲�(kWh)"
+                        align="center"
+                        show-overflow-tooltip
+                        width="120"
+                      />
+                      <el-table-column
+                        prop="sharpFee"
+                        label="鐢佃垂(鍏�)"
+                        align="center"
+                        show-overflow-tooltip
+                      />
+                    </el-table-column>
+                    <el-table-column label="宄�" align="center">
+                      <el-table-column
+                        prop="peakPower"
+                        label="鑰楃數閲�(kWh)"
+                        align="center"
+                        show-overflow-tooltip
+                        width="120"
+                      />
+                      <el-table-column
+                        prop="peakFee"
+                        label="鐢佃垂(鍏�)"
+                        align="center"
+                        show-overflow-tooltip
+                      />
+                    </el-table-column>
+                    <el-table-column label="骞�" align="center">
+                      <el-table-column
+                        prop="flatPower"
+                        label="鑰楃數閲�(kWh)"
+                        align="center"
+                        show-overflow-tooltip
+                        width="120"
+                      />
+                      <el-table-column
+                        prop="flatFee"
+                        label="鐢佃垂(鍏�)"
+                        align="center"
+                        show-overflow-tooltip
+                      />
+                    </el-table-column>
+                    <el-table-column label="璋�" align="center">
+                      <el-table-column
+                        prop="valleyPower"
+                        label="鑰楃數閲�(kWh)"
+                        align="center"
+                        show-overflow-tooltip
+                        width="120"
+                      />
+                      <el-table-column
+                        prop="valleyFee"
+                        label="鐢佃垂(鍏�)"
+                        align="center"
+                        show-overflow-tooltip
+                      />
+                    </el-table-column>
+                    <el-table-column label="鍚堣" align="center">
+                      <el-table-column
+                        prop="totalPower"
+                        label="鎬荤數閲�(kWh)"
+                        align="center"
+                        show-overflow-tooltip
+                        width="120"
+                      />
+                      <el-table-column
+                        prop="totalFee"
+                        label="鎬荤數璐�(鍏�)"
+                        align="center"
+                        show-overflow-tooltip
+                      />
+                    </el-table-column>
+                    <!-- <el-table-column label="鎿嶄綔" align="center">
+                    <template #default="scope">
+                      <el-button link icon="view" @click="handleView(scope.row)" type="primary">
+                        鏌ョ湅
+                      </el-button>
+                    </template>
+                  </el-table-column> -->
+                  </el-table>
+                </div>
+              </BaseCard>
+            </el-col>
+          </el-row>
+        </div>
+      </div>
+    </div>
+    <el-dialog
+      v-model="timeSharingDialogTableVisible"
+      title="鏌ョ湅鍒嗘椂缁熻璇︽儏"
+      width="1300"
+    >
+      <div class="table-box">
+        <el-table :data="viewTimeSharingList" style="width: 100%" height="420">
+          <el-table-column
+            label="鏃堕棿"
+            align="center"
+            show-overflow-tooltip
+            width="200"
+          >
+            <template #default="scope"> {{ scope.row.time }}鏃� </template>
+          </el-table-column>
+          <el-table-column label="灏�" align="center">
+            <el-table-column
+              prop="sharpPower"
+              label="鑰楃數閲�(kWh)"
+              align="center"
+              show-overflow-tooltip
+              width="120"
+            />
+            <el-table-column
+              prop="sharpFee"
+              label="鐢佃垂(鍏�)"
+              align="center"
+              show-overflow-tooltip
+              width="90"
+            />
+          </el-table-column>
+          <el-table-column label="宄�" align="center">
+            <el-table-column
+              prop="peakPower"
+              label="鑰楃數閲�(kWh)"
+              align="center"
+              show-overflow-tooltip
+              width="120"
+            />
+            <el-table-column
+              prop="peakFee"
+              label="鐢佃垂(鍏�)"
+              align="center"
+              show-overflow-tooltip
+              width="90"
+            />
+          </el-table-column>
+          <el-table-column label="骞�" align="center">
+            <el-table-column
+              prop="flatPower"
+              label="鑰楃數閲�(kWh)"
+              align="center"
+              show-overflow-tooltip
+              width="120"
+            />
+            <el-table-column
+              prop="flatFee"
+              label="鐢佃垂(鍏�)"
+              align="center"
+              show-overflow-tooltip
+              width="90"
+            />
+          </el-table-column>
+          <el-table-column label="璋�" align="center">
+            <el-table-column
+              prop="valleyPower"
+              label="鑰楃數閲�(kWh)"
+              align="center"
+              show-overflow-tooltip
+              width="120"
+            />
+            <el-table-column
+              prop="valleyFee"
+              label="鐢佃垂(鍏�)"
+              align="center"
+              show-overflow-tooltip
+              width="90"
+            />
+          </el-table-column>
+          <el-table-column label="鍚堣" align="center">
+            <el-table-column
+              prop="totalPowerConsumption"
+              label="鎬荤數閲�(kWh)"
+              align="center"
+              show-overflow-tooltip
+              width="120"
+            >
+              <template #default="scope">
+                {{
+                  scope.row.sharpPower +
+                  scope.row.peakPower +
+                  scope.row.flatPower +
+                  scope.row.valleyPower
+                }}
+              </template>
+            </el-table-column>
+            <el-table-column
+              prop="totalCost"
+              label="鎬荤數璐�(鍏�)"
+              align="center"
+              show-overflow-tooltip
+              width="100"
+            >
+              <template #default="scope">
+                {{
+                  scope.row.sharpFee +
+                  scope.row.peakFee +
+                  scope.row.flatFee +
+                  scope.row.valleyFee
+                }}
+              </template>
+            </el-table-column>
+          </el-table-column>
+          <el-table-column
+            prop="nodeName"
+            label="鐢ㄨ兘鍗曞厓"
+            align="center"
+            show-overflow-tooltip
+            width="200"
+          />
+        </el-table>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="timeSharing">
+import { listTimeSharing } from "@/api/peakValley/timeSharing";
+import * as echarts from "echarts";
+const { proxy } = getCurrentInstance();
+import { useRoute } from "vue-router";
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getList();
+  }
+);
+const loading = ref(false);
+const timeSharingList = ref([]);
+const timeSharingDialogTableVisible = ref(false);
+const viewTimeSharingList = ref([]);
+const nodeOptions = ref(undefined);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    nodeName: null,
+    timeType: "DAY",
+    queryTime: null,
+  },
+  query: { ...useRoute().query },
+});
+const { queryParams, query } = toRefs(data);
+/** 閫氳繃鏉′欢杩囨护鑺傜偣  */
+const filterNode = (value, data) => {
+  if (!value) return true;
+  return data.label.indexOf(value) !== -1;
+};
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  queryParams.value.queryTime = proxy.dayjs(new Date()).format("YYYY-MM-DD");
+  handleQuery();
+}
+// 宄板钩璋疯兘鑰楀垎鏋�-宄板钩璋峰垎鏃剁粺璁�-鍒楄〃
+function getList() {
+  loading.value = true;
+  listTimeSharing(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    loading.value = false;
+    if (!!res.code && res.code == 200) {
+      // 鍦ㄥ垵濮嬪寲涔嬪墠锛屽厛dispose鏃х殑瀹炰緥
+      if (echarts.getInstanceByDom(document.getElementById("Chart1"))) {
+        echarts.dispose(document.getElementById("Chart1"));
+      }
+      if (echarts.getInstanceByDom(document.getElementById("Chart2"))) {
+        echarts.dispose(document.getElementById("Chart2"));
+      }
+      const myChart1 = echarts.init(document.getElementById("Chart1"));
+      const myChart2 = echarts.init(document.getElementById("Chart2"));
+      if (!!res.data.lineChat) {
+        let xdata = [];
+        let ytip = [];
+        let ypeak = [];
+        let yflat = [];
+        let ytrough = [];
+        res.data.lineChat.map((item) => {
+          xdata.push(Number(item.xdata.slice(-8).substring(0, 2)) + "鏃�");
+          ytip.push(!!item.ytip ? item.ytip : 0);
+          ypeak.push(!!item.ypeak ? item.ypeak : 0);
+          yflat.push(!!item.yflat ? item.yflat : 0);
+          ytrough.push(!!item.ytrough ? item.ytrough : 0);
+        });
+        myChart1.setOption({
+          color: ["#f52528", "#ff6200", "#ffce0c", "#78e801"],
+          grid: {
+            top: "45",
+            left: "5%",
+            right: "5%",
+            bottom: "10",
+            containLabel: true,
+          },
+          tooltip: {
+            trigger: "axis",
+            axisPointer: {
+              type: "shadow",
+            },
+          },
+          legend: {
+            icon: "rect",
+            itemWidth: 14,
+            itemHeight: 10,
+            // right: 0,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+            // data: ['灏�', '宄�', '骞�', '璋�']
+          },
+          xAxis: {
+            type: "category",
+            axisLine: {
+              show: true,
+              lineStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            splitLine: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [5, 0, 0, 0],
+              //   formatter: '{value} ml'
+            },
+            data: xdata,
+          },
+          yAxis: {
+            name: "鑰楃數閲�(KWH)",
+            type: "value",
+            nameTextStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              padding: [0, 0, 5, 0],
+            },
+            axisLine: {
+              show: false,
+            },
+            splitLine: {
+              show: true,
+              lineStyle: {
+                type: "dashed",
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+            },
+            axisTick: {
+              show: false,
+            },
+            splitArea: {
+              show: false,
+            },
+            axisLabel: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+              fontSize: 14,
+              //   formatter: '{value} ml'
+            },
+          },
+          series: [
+            {
+              name: "灏�", // ytip
+              type: "bar",
+              barWidth: "27",
+              stack: "total",
+              data: ytip,
+            },
+            {
+              name: "宄�", // ypeak
+              type: "bar",
+              barWidth: "27",
+              stack: "total",
+              data: ypeak,
+            },
+            {
+              name: "骞�", // yflat
+              type: "bar",
+              barWidth: "27",
+              stack: "total",
+              data: yflat,
+            },
+            {
+              name: "璋�", // ytrough
+              type: "bar",
+              barWidth: "27",
+              stack: "total",
+              data: ytrough,
+            },
+          ],
+        });
+      }
+      if (!!res.data.pieChat) {
+        myChart2.setOption({
+          color: ["#f52528", "#ff6200", "#ffce0c", "#78e801"],
+          tooltip: {
+            trigger: "item",
+          },
+          legend: {
+            icon: "circle",
+            itemWidth: 14,
+            itemHeight: 14,
+            itemGap: 10,
+            // right: 0,
+            textStyle: {
+              color:
+                settingsStore.sideTheme == "theme-dark" ? "#FFFFFF" : "#222222",
+            },
+          },
+          series: [
+            {
+              name: "宄板钩璋峰崰姣斿浘",
+              type: "pie",
+              radius: ["30%", "50%"],
+              avoidLabelOverlap: false,
+              label: {
+                overflow: "none",
+                formatter: "{b} {c}%  ",
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+              },
+              data: [
+                {
+                  value: !!res.data.pieChat.tip
+                    ? Number(res.data.pieChat.tip)
+                    : 0,
+                  name: "灏�",
+                },
+                {
+                  value: !!res.data.pieChat.peak
+                    ? Number(res.data.pieChat.peak)
+                    : 0,
+                  name: "宄�",
+                },
+                {
+                  value: !!res.data.pieChat.flat
+                    ? Number(res.data.pieChat.flat)
+                    : 0,
+                  name: "骞�",
+                },
+                {
+                  value: !!res.data.pieChat.trough
+                    ? Number(res.data.pieChat.trough)
+                    : 0,
+                  name: "璋�",
+                },
+              ],
+            },
+          ],
+        });
+      }
+      if (!!res.data.dataList) {
+        timeSharingList.value = !!res.data.dataList ? res.data.dataList : [];
+      }
+      window.addEventListener(
+        "resize",
+        () => {
+          myChart1.resize();
+          myChart2.resize();
+        },
+        { passive: true }
+      );
+    }
+  });
+}
+// 宄板钩璋疯兘鑰楀垎鏋�-宄板钩璋峰垎鏃剁粺璁�-鏌ョ湅
+function handleView(row) {
+  queryParams.value.key = row.timeStr;
+  timeSharingDialogTableVisible.value = true;
+  viewTimeSharingList.value = [row];
+}
+// 宄板钩璋疯兘鑰楀垎鏋�-宄板钩璋峰垎鏃剁粺璁�-鎼滅储
+function handleQuery() {
+  getList();
+}
+// 宄板钩璋疯兘鑰楀垎鏋�-宄板钩璋峰垎鏃剁粺璁�-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  (queryParams.value.queryTime = proxy.dayjs(new Date()).format("YYYY-MM-DD")),
+    handleQuery();
+}
+// 宄板钩璋疯兘鑰楀垎鏋�-宄板钩璋峰垎鏃剁粺璁�-瀵煎嚭
+function handleExport() {
+  proxy.download(
+    "peakValley/segmentAnalysis/hour/export",
+    {
+      ...queryParams.value,
+      ...query.value,
+    },
+    `${queryParams.value.nodeName}_宄板钩璋峰垎鏃剁粺璁${new Date().getTime()}.xlsx`
+  );
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+.table-box {
+  :deep .el-table--border .el-table__inner-wrapper:after {
+    height: 0;
+  }
+
+  :deep .el-table--border:after {
+    width: 0;
+  }
+
+  :deep .el-table--border:before {
+    width: 0;
+  }
+
+  :deep .el-table__border-left-patch {
+    background: transparent;
+  }
+
+  :deep .el-table--border .el-table__cell {
+    border-right: none;
+  }
+}
+</style>
diff --git a/zhitan-vue/src/views/powerbalance/powerbalance.vue b/zhitan-vue/src/views/powerbalance/powerbalance.vue
new file mode 100644
index 0000000..d559c09
--- /dev/null
+++ b/zhitan-vue/src/views/powerbalance/powerbalance.vue
@@ -0,0 +1,369 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="table-box">
+          <div class="table-header-box">
+            <div>
+              <div class="cell"> </div>
+            </div>
+            <div v-for=" (item, i) in tableHeaderData" :key="i">
+              <div class="cell">{{ item }}</div>
+            </div>
+          </div>
+
+          <div class="table-content-box" v-for="(items, i) in ListData" :key="i">
+            <div class="calc6_4 ">
+              <div style="display: flex;align-items: center; width: 100%;border: 1px solid #666;">
+                <div class="calc4">
+                  {{ items.jinList.jinName }}
+                </div>
+                <div class="width50">
+                  <div v-for="  (item, k) in items.jinList.list" :key="k" class="data-list-ul">
+                    <div class="width50" style="border-right: 1px solid #666;">{{ item.title }}</div>
+                    <div class="width50">{{ item.num }}</div>
+                  </div>
+                </div>
+                <div class="calc4">
+                  {{ items.jinList.total }}
+                </div>
+              </div>
+              <div style="display: flex;align-items: center; width: 100%;border: 1px solid #666;">
+                <div class="calc4">
+                  {{ items.chuList.chuName }}
+                </div>
+                <div class="width50">
+                  <div v-for="  (item, k) in items.chuList.list" :key="k"
+                    style="display: flex;align-items: center; width: 100%;" class="data-list-ul">
+                    <div class="width50" style="border-right: 1px solid #666;">{{ item.title }}</div>
+                    <div class="width50">{{ item.num }}</div>
+
+                  </div>
+                </div>
+                <div class="calc4">
+                  {{ items.chuList.total }}
+                </div>
+              </div>
+            </div>
+            <div class="calc6"
+              style="border: 1px solid #666;display: flex;align-items: center;justify-content: center;">
+              <div>
+                {{ items.cha }}
+              </div>
+            </div>
+            <div class="calc6"
+              style="border: 1px solid #666;display: flex;align-items: center;justify-content: center;">
+              <div>
+                {{ items.baifenbi }}
+              </div>
+
+            </div>
+          </div>
+
+        </div>
+        <BaseCard title="鑳芥簮娴佸悜鍒嗘瀽">
+          <div class="chart-box">
+            <div id="Chart1" />
+          </div>
+        </BaseCard>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup>
+import useSettingsStore from '@/store/modules/settings'
+const settingsStore = useSettingsStore()
+import * as echarts from 'echarts';
+import { onMounted } from 'vue';
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+}
+
+
+const tableHeaderData = ['绾胯矾鍚嶇О', '鑰楃數閲�', '灏忚', '宸��', '鐧惧垎姣�']
+const ListData = ref([
+  {
+    id: 1,
+    jinList: {
+      jinName: '杩涚嚎',
+      list: [
+        { num: 24726900, title: '1#涓诲彉' },
+        { num: 0, title: '鍐朵笁浣欑儹鍙戠數鍙嶅悜' },
+        { num: 958880, title: '鍚夐『鍏変紡1#绾�' },
+        { num: 874720, title: '鍚夐『鍏変紡2#绾�' }
+      ],
+      total: 39375820
+    },
+
+    chuList: {
+      chuName: '鍑虹嚎',
+      list: [
+        { num: 1766800, title: '鍐朵笁榧撻鏈烘埧1#绾�' },
+        { num: 1046400, title: '鍐朵笁榧撻鏈烘埧2#绾�' },
+        { num: 1790400, title: '鍐朵笁纭吀1#绾�' },
+        { num: 1385700, title: '鍐朵笁纭吀2#绾�' },
+        { num: 5058600, title: '鍐朵笁鍒舵哀1#绾�' },
+        { num: 3921600, title: '鍐朵笁鍒舵哀2#绾�' },
+        { num: 215760, title: '鐢熷寲2#绾�' },
+        { num: 0, title: '渚у惞绮剧偧1#绾�' },
+        { num: 5555000, title: '渚у惞鐔旂偧1#绾�' },
+        { num: 4786530, title: '1#35kV鐢熷寲绾�' },
+        { num: 973980, title: '2#35kV鐢熷寲绾�' },
+        { num: 7680, title: '鍚夐『鍏変紡1#绾�' },
+        { num: 5280, title: '鍚夐『鍏変紡2#绾�' }
+
+      ],
+      total: 39173060
+    },
+    cha: 202760,
+    baifenbi: '0.51%'
+  }
+
+]);
+
+
+
+watch(() => settingsStore.sideTheme, (val) => {
+  getChart()
+})
+
+function getChart() {
+
+
+  let colors = ['#FBB4AE', '#B3CDE3', '#CCEBC5', '#DECBE4', '#5470C6'];
+  let mydata = [
+    { id: 1, name: '1#涓诲彉', itemStyle: { color: colors[0] }, depth: 0 },
+    { id: 2, name: '鍚夐『鍏変紡1#绾�', itemStyle: { color: colors[0] }, depth: 0 },
+    { id: 3, name: '鍚夐『鍏変紡2#绾�', itemStyle: { color: colors[0] }, depth: 0 },
+    { id: 4, name: '鍐朵笁浣欑儹鍙戠數鍙嶅悜', itemStyle: { color: colors[0] }, depth: 0 },
+
+
+    { id: 5, name: '杩涘嚭绾�', itemStyle: { color: colors[1] }, depth: 1 },
+    // { name: 'L2-1', itemStyle: { color: colors[1] }, depth: 1 },
+    // { name: 'L2-2', itemStyle: { color: colors[1] }, depth: 1 },
+    // { name: 'L2-3', itemStyle: { color: colors[1] }, depth: 1 },
+
+
+    // { name: 'L3', itemStyle: { color: colors[2] }, depth: 2 },
+    // { name: 'L3-1', itemStyle: { color: colors[2] }, depth: 2 },
+    // { name: 'L3-2', itemStyle: { color: colors[2] }, depth: 2 },
+
+    { id: 6, name: '鍐朵笁榧撻鏈烘埧1#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 7, name: '鍐朵笁榧撻鏈烘埧2#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 8, name: '鍐朵笁纭吀1#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 9, name: '鍐朵笁纭吀2#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 10, name: '鍐朵笁鍒舵哀1#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 11, name: '鍐朵笁鍒舵哀2#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 12, name: '鐢熷寲2#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 13, name: '渚у惞绮剧偧1#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 14, name: '渚у惞鐔旂偧1#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 15, name: '1#35kV鐢熷寲绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 16, name: '2#35kV鐢熷寲绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 17, name: '鍚夐『鍏変紡1#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+    { id: 18, name: '鍚夐『鍏変紡2#绾�', itemStyle: { color: colors[3] }, depth: 3 },
+  ];
+  // mydata.reverse()
+  let mylinks = [
+    // L1鈫扡4	 9720
+    { source: '1', target: '5', value: 24726900 },
+    { source: '2', target: '5', value: 958880 },
+    { source: '3', target: '5', value: 874720 },
+    { source: '4', target: '5', value: 0 },
+    // L2鈫扡4	 24396
+    { source: '5', target: '6', value: 1766800 },
+    { source: '5', target: '7', value: 1046400 },
+    { source: '5', target: '8', value: 1790400 },
+    { source: '5', target: '9', value: 1385700 },
+    { source: '5', target: '10', value: 5058600 },
+    { source: '5', target: '11', value: 3921600 },
+    { source: '5', target: '12', value: 215760 },
+    { source: '5', target: '13', value: 0 },
+    { source: '5', target: '14', value: 5555000 },
+    { source: '5', target: '15', value: 4786530 },
+    { source: '5', target: '16', value: 973980 },
+    { source: '5', target: '17', value: 7680 },
+    { source: '5', target: '18', value: 5280 },
+
+
+    // // L3鈫扡4	 1462
+    // { source: 'L3', target: 'L4', value: 1462 },
+
+    // // L1鈫扡2鈫扡3鈫扡4	 215
+    // { source: 'L1-1', target: 'L2-1', value: 215 },
+    // { source: 'L2-1', target: 'L3-1', value: 215 },
+    // { source: 'L3-1', target: 'L4', value: 215 },
+
+    // // L1鈫扡2鈫扡4	 4518
+    // { source: 'L1-2', target: 'L2-2', value: 4518 },
+    // { source: 'L2-2', target: 'L4', value: 4518 },
+    // // L1鈫扡3鈫扡4	 217
+    // { source: 'L1-3', target: 'L3-2', value: 217 },
+    // { source: 'L3-2', target: 'L4', value: 217 },
+
+    // // L2鈫扡3鈫扡4	 893
+    // { source: 'L2-3', target: 'L3-3', value: 893 },
+    // { source: 'L3-3', target: 'L4', value: 893 },
+
+  ];
+
+
+  const myChart1 = echarts.init(document.getElementById("Chart1"));
+  myChart1.setOption({
+    tooltip: {
+      trigger: 'item',
+      triggerOn: 'mousemove',
+    },
+    series: {
+      type: 'sankey',
+      lineStyle: {
+        opacity: 0.3,
+        color: 'gradient',
+        curveness: 0.7,
+      },
+      // nodeAlign: 'left',
+      nodeGap: 18,
+      layoutIterations: 1,
+      emphasis: {
+        focus: 'adjacency',
+      },
+      data: mydata,
+      links: mylinks,
+      label: {
+        normal: {
+          show: true, // 鏄剧ず鏍囩
+          // position: 'left', // 鏍囩浣嶇疆
+          formatter: function (node) {
+            // 鑷畾涔夋爣绛惧唴瀹�
+            return node.data.name;
+          }
+        }
+      },
+    },
+  })
+  window.addEventListener("resize", () => {
+    myChart1.resize();
+  }, { passive: true });
+}
+onMounted(() => {
+  getChart()
+})
+</script>
+<style lang="scss" scoped>
+@import "@/assets/styles/page.scss";
+
+ul,
+li {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+.table-header-box {
+  width: 100%;
+  background-color: #1d3778 !important;
+  display: flex;
+  color: #fff;
+  font-weight: 500;
+  font-size: 16px;
+  font-family: OPPOSans, OPPOSans;
+  padding: 8px 0;
+  align-items: center;
+
+  &>div {
+    width: calc(100% / 6);
+    word-break: break-word;
+    background-color: #1d3778 !important;
+    border-radius: 0px 0px 0px 0px;
+    font-family: OPPOSans, OPPOSans;
+    font-weight: 500;
+    font-size: 16px;
+    color: #ffffff;
+    border-bottom: none !important;
+    display: flex;
+    align-items: center;
+    text-align: center;
+
+    .cell {
+      width: 100%;
+      box-sizing: border-box;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: normal;
+      word-break: break-all;
+      line-height: 23px;
+      padding: 0 12px;
+    }
+
+
+  }
+}
+
+.table-content-box {
+  color: #fff;
+  padding: 8px 0;
+  width: 100%;
+  display: flex;
+  // align-items: center;
+  text-align: center;
+  align-items: stretch;
+
+  .calc6_4 {
+    width: calc((100% / 6) * 4)
+  }
+
+
+
+  .calc6 {
+    width: calc(100% / 6)
+  }
+
+  .calc4 {
+    width: calc(100% / 4)
+  }
+
+  .width50 {
+    width: 50%;
+  }
+
+
+
+  .data-list-ul {
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 38px !important;
+    line-height: 38px;
+    border: 1px solid #666;
+
+    // &:nth-child(2n) {
+    //   // background: #141E4A;
+    // }
+
+    // .data-list-li {
+    //   width: calc(100% /4);
+    //   white-space: nowrap;
+    //   /* 绂佹鏂囨湰鎹㈣ */
+    //   overflow: hidden;
+    //   /* 闅愯棌瓒呭嚭瀹瑰櫒鐨勬枃鏈� */
+    //   text-overflow: ellipsis;
+    //   /* 浣跨敤鐪佺暐鍙疯〃绀烘孩鍑虹殑鏂囨湰 */
+    // }
+  }
+
+
+}
+
+.chart-box {
+  width: 100%;
+  height: calc(100vh - 500px) !important;
+
+  div {
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/realtimemonitor/realtimemonitor/components/chart-modal.vue b/zhitan-vue/src/views/realtimemonitor/realtimemonitor/components/chart-modal.vue
new file mode 100644
index 0000000..c1fde4f
--- /dev/null
+++ b/zhitan-vue/src/views/realtimemonitor/realtimemonitor/components/chart-modal.vue
@@ -0,0 +1,210 @@
+<template>
+  <div>
+    <el-dialog v-model="dialogTableVisible" :title="title + '-鍘嗗彶鏁版嵁鏌ヨ'" width="1300">
+      <div class="page">
+        <el-form :model="queryParams" ref="queryRef" :inline="true" label-width="80px">
+          <el-form-item label="鏃ユ湡绛涢��" prop="dataTime">
+            <el-date-picker v-model="queryParams.dataTime" value-format="YYYY-MM-DD 00:00:00" type="date"
+              placeholder="鏃ユ湡绛涢��" style="width: 100%" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">
+              鎼滅储
+            </el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Download" @click="handleExport">
+              瀵煎嚭
+            </el-button>
+          </el-form-item>
+        </el-form>
+        <div class="chart-box" v-loading="loading">
+          <div id="Chart1" />
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="chartModal">
+import * as echarts from "echarts";
+import { getEnergyRealTimeMonitor } from "@/api/realTimeMonitor/realTimeMonitor";
+const { proxy } = getCurrentInstance();
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+const dialogTableVisible = ref(false);
+const data = reactive({
+  title: null,
+  queryParams: {
+    tagCode: null,
+    dataTime: null,
+  },
+});
+const { queryParams, title } = toRefs(data);
+const loading = ref(false);
+defineExpose({ handleOpen });
+function handleOpen(row) {
+  title.value = row.name;
+  dialogTableVisible.value = true;
+  queryParams.value.nodeName = row.nodeName;
+  queryParams.value.tagCode = row.indexCode;
+  queryParams.value.unit = row.unit;
+  queryParams.value.name = row.name;
+  queryParams.value.dataTime = proxy
+    .dayjs(new Date())
+    .format("YYYY-MM-DD 00:00:00");
+  getList();
+}
+function handleClose() {
+  dialogTableVisible.value = false;
+}
+// 鑳芥簮瀹炴椂鐩戞帶-鑳芥簮瀹炴椂鐩戞帶-鍒楄〃
+function getList() {
+  loading.value = true;
+  getEnergyRealTimeMonitor(proxy.addDateRange(queryParams.value)).then(
+    (res) => {
+      const myChart1 = echarts.init(document.getElementById("Chart1"));
+      if (!!res.code && res.code == 200) {
+        if (!!res.data) {
+          let xdata = [];
+          let ydata = [];
+          res.data.map((item) => {
+            xdata.push(proxy.dayjs(item.name).format("HH:mm"));
+            ydata.push(!!item.value ? item.value : 0);
+          });
+          loading.value = false;
+          myChart1.setOption({
+            color: ["#2979ff", "#19be6b", "#ff9900", "#fa3534"],
+            grid: {
+              top: "45",
+              left: "6%",
+              right: "5%",
+              bottom: "10",
+              containLabel: true,
+            },
+            tooltip: {
+              trigger: "axis",
+              axisPointer: {
+                type: "shadow",
+              },
+            },
+            xAxis: {
+              type: "category",
+              axisLine: {
+                show: true,
+                lineStyle: {
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              splitLine: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [5, 0, 0, 0],
+                //   formatter: '{value} ml'
+              },
+              data: xdata,
+            },
+            yAxis: {
+              type: "value",
+              name:
+                queryParams.value.name +
+                (!!queryParams.value.unit
+                  ? "(" + queryParams.value.unit + ")"
+                  : ""),
+              nameTextStyle: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+                padding: [0, 0, 5, 0],
+              },
+              axisLine: {
+                show: false,
+              },
+              splitLine: {
+                show: true,
+                lineStyle: {
+                  type: "dashed",
+                  color:
+                    settingsStore.sideTheme == "theme-dark"
+                      ? "#FFFFFF"
+                      : "#222222",
+                },
+              },
+              axisTick: {
+                show: false,
+              },
+              splitArea: {
+                show: false,
+              },
+              axisLabel: {
+                color:
+                  settingsStore.sideTheme == "theme-dark"
+                    ? "#FFFFFF"
+                    : "#222222",
+                fontSize: 14,
+              },
+            },
+            series: [
+              {
+                name: title.value + "鍘嗗彶鏁版嵁鏌ヨ", // ytip
+                type: "line",
+                barWidth: "27",
+                stack: "total",
+                data: ydata,
+              },
+            ],
+          });
+        }
+        window.addEventListener(
+          "resize",
+          () => {
+            myChart1.resize();
+          },
+          { passive: true }
+        );
+      }
+    }
+  );
+}
+// 鑳芥簮瀹炴椂鐩戞帶-鑳芥簮瀹炴椂鐩戞帶-鎼滅储
+function handleQuery() {
+  getList();
+}
+// 鑳芥簮瀹炴椂鐩戞帶-鑳芥簮瀹炴椂鐩戞帶-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  (queryParams.value.dataTime = proxy
+    .dayjs(new Date())
+    .format("YYYY-MM-DD 00:00:00")),
+    handleNodeClick(nodeOptions.value[0]);
+}
+// 鑳芥簮瀹炴椂鐩戞帶-鑳芥簮瀹炴椂鐩戞帶-瀵煎嚭
+function handleExport() {
+  proxy.download(
+    "rtdb/realtimeTrend/export",
+    queryParams.value,
+    `${queryParams.value.nodeName}-${queryParams.value.name}_鑳芥簮瀹炴椂鐩戞帶_${new Date().getTime()}.xlsx`
+  );
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
diff --git a/zhitan-vue/src/views/realtimemonitor/realtimemonitor/realtimemonitor.vue b/zhitan-vue/src/views/realtimemonitor/realtimemonitor/realtimemonitor.vue
new file mode 100644
index 0000000..bab0dc2
--- /dev/null
+++ b/zhitan-vue/src/views/realtimemonitor/realtimemonitor/realtimemonitor.vue
@@ -0,0 +1,435 @@
+<template>
+  <div class="page">
+    <div class="page-container">
+      <div class="page-container-left">
+        <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" />
+      </div>
+      <div class="page-container-right">
+        <div class="form-card">
+          <el-form :model="queryParams" ref="queryRef" :inline="true">
+            <el-form-item label="鑳芥簮绫诲瀷" prop="energyType">
+              <el-select v-model="queryParams.energyType" placeholder="鑳芥簮绫诲瀷" @change="handleQuery">
+                <el-option :label="item.enername" :value="item.enersno" v-for="item in energyTypeList" :key="item.enersno" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">
+                鎼滅储
+              </el-button>
+              <el-button icon="Refresh" @click="resetQuery"> 閲嶇疆 </el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Refresh" @click="handleQuery" circle />
+            </el-form-item>
+          </el-form>
+        </div>
+        <div style="
+            height: calc(100vh - 220px) !important;
+            max-height: calc(100vh - 220px) !important;
+            overflow-y: auto;
+          " v-loading="loading">
+          <div v-for="(item, index) in energyRealTimeMonitorList" :key="index" v-show="!!item.deviceArray">
+            <BaseCard :title="queryParams.nodeName + '-' + item.energyTypeName" v-if="item.deviceArray.length > 0">
+              <el-scrollbar>
+                <div class="scrollbar-flex-content">
+                  <p></p>
+                  <div class="scrollbar-demo-item item-tag" @click="handleClick(item, index1)" v-for="item1,index1 in item.deviceArray" :key="index1" :style="{
+                    backgroundColor:
+                      index1 == item.activeIndex ? '#5EC894' : '#9841FC',
+                  }">
+                    {{ item1.deviceName }}
+                  </div>
+                </div>
+              </el-scrollbar>
+              <!-- <div class="card-box" v-if="item.energyTypeName.includes('鐢佃〃')">
+                <div class="form-card">
+                  <el-form :inline="true">
+                    <el-form-item label="绾垮緞">
+                      <el-button link type="primary">
+                        {{
+                          !!item.deviceArray[item.activeIndex].wireDiameter
+                            ? item.deviceArray[item.activeIndex].wireDiameter
+                            : 0
+                        }}
+                      </el-button>
+                    </el-form-item>
+                    <el-form-item label="鍏佽鏈�澶у姛鐜�">
+                      <el-button link type="primary">
+                        {{
+                          !!item.deviceArray[item.activeIndex].maxAllowablePower
+                            ? item.deviceArray[item.activeIndex]
+                              .maxAllowablePower
+                            : 0
+                        }}
+                      </el-button>
+                    </el-form-item>
+                  </el-form>
+                </div>
+              </div> -->
+              <div class="card-box">
+                <div class="card-box-title">
+                  {{ item.deviceArray[item.activeIndex].energyTypeName }}
+                </div>
+                <div class="card-box-ul">
+                  <div class="card-box-li" v-for="(item2, index2) in item.deviceArray[item.activeIndex]
+                    .energyIndexArray">
+                    <el-tooltip class="box-item" effect="dark" content="鏌ョ湅鍘嗗彶鏁版嵁" placement="top">
+                      <dl @click="handleChartModal(item2)">
+                        <dd class="title">
+                          {{ item2.name }}
+                          <template v-if="!!item2.unit">
+                            ({{ item2.unit }})
+                          </template>
+                        </dd>
+                        <dd class="num">
+                          {{ item2.value!=null ? item2.value.toFixed(2) : '--' }}
+                        </dd>
+                        <dd class="time">
+                          <el-icon>
+                            <Timer />
+                          </el-icon>
+                          {{ item2.dataTime }}
+                        </dd>
+                      </dl>
+                    </el-tooltip>
+                  </div>
+                </div>
+              </div>
+            </BaseCard>
+          </div>
+          <el-table :data="energyRealTimeMonitorList" style="width: 100%" />
+        </div>
+        <chartModal ref="chartRef" />
+      </div>
+    </div>
+  </div>
+</template>
+<script setup name="energy-real-time-monitor">
+import chartModal from "./components/chart-modal.vue";
+import { listEnergyRealTimeMonitor } from "@/api/realTimeMonitor/realTimeMonitor";
+import { listEnergyTypeList } from "@/api/modelConfiguration/energyType";
+const { proxy } = getCurrentInstance();
+import { useRoute } from "vue-router";
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+watch(
+  () => settingsStore.sideTheme,
+  (val) => {
+    getList();
+  }
+);
+const energyTypeList = ref(undefined);
+let energyRealTimeMonitorList = ref([]);
+const loading = ref(false);
+const data = reactive({
+  queryParams: {
+    nodeId: null,
+    nodeName: null,
+    energyType: null,
+  },
+  query: { ...useRoute().query },
+});
+const { queryParams, query } = toRefs(data);
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.nodeId = data.id;
+  queryParams.value.nodeName = data.label;
+  listEnergyTypeList().then((res) => {
+    energyTypeList.value = res.data;
+    queryParams.value.energyType = energyTypeList.value[0].enersno;
+    handleQuery();
+  });
+}
+function handleClick(item, index) {
+  item.activeIndex = index;
+}
+// 鑳芥簮瀹炴椂鐩戞帶-鑳芥簮瀹炴椂鐩戞帶-鍒楄〃
+function getList() {
+  loading.value = true;
+  listEnergyRealTimeMonitor(
+    proxy.addDateRange({
+      ...queryParams.value,
+      ...query.value,
+    })
+  ).then((res) => {
+    if (!!res.code && res.code == 200) {
+      res.data.map((item) => {
+        item.activeIndex = 0;
+      });
+      loading.value = false;
+      energyRealTimeMonitorList.value = res.data;
+    }
+  });
+}
+// 鑳芥簮瀹炴椂鐩戞帶-鑳芥簮瀹炴椂鐩戞帶-鎼滅储
+function handleQuery() {
+  energyRealTimeMonitorList.value = [];
+  getList();
+}
+// 鑳芥簮瀹炴椂鐩戞帶-鑳芥簮瀹炴椂鐩戞帶-閲嶇疆
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value.energyType = null;
+  energyRealTimeMonitorList.value = [];
+  handleQuery();
+}
+let chartRef = ref();
+
+function handleChartModal(row) {
+  if (chartRef.value) {
+    row.nodeName = queryParams.value.nodeName
+    chartRef.value.handleOpen(row);
+  }
+}
+</script>
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+.themeDark {
+  .card-box {
+    margin: 0 18px;
+    padding: 10px 18px;
+    box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.12);
+    border-radius: 8px 8px 8px 8px;
+    border: 1px solid #22408c;
+    margin-bottom: 20px;
+
+    &-title {
+      // margin: 10px 0 10px;
+      color: rgba(255, 255, 255, 0.8);
+      text-align: left;
+      font-weight: bold;
+      font-family: OPPOSans, OPPOSans;
+      font-weight: 500;
+      font-size: 16px;
+      font-style: normal;
+      text-transform: none;
+    }
+
+    &-ul {
+      display: flex;
+      flex-wrap: wrap;
+    }
+
+    &-li {
+      width: 18%;
+      margin: 1%;
+      border-radius: 5px;
+      border: 1px solid #22408c;
+      margin-bottom: 5px;
+      background: #1a235d;
+      box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.12);
+      border-radius: 8px 8px 8px 8px;
+      padding: 0 10px;
+
+      .title {
+        font-size: 14px;
+        color: rgba(255, 255, 255, 0.8);
+        font-family: OPPOSans, OPPOSans;
+        font-weight: 500;
+        font-size: 16px;
+        line-height: 19px;
+        text-align: left;
+        font-style: normal;
+        text-transform: none;
+      }
+
+      .num {
+        font-size: 22px;
+        color: #36d3ff;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: 800;
+        font-size: 32px;
+        text-align: left;
+        font-style: normal;
+        text-transform: none;
+      }
+
+      .time {
+        color: rgba(255, 253, 253, 0.7);
+        font-family: Poppins, Poppins;
+        font-weight: 400;
+        text-align: left;
+        font-style: normal;
+        text-transform: none;
+      }
+    }
+
+    dd {
+      padding: 0;
+      margin-left: 5px;
+      margin-bottom: 10px;
+    }
+  }
+
+  .hamburger {
+    display: inline-block;
+    vertical-align: middle;
+    width: 20px;
+    height: 20px;
+  }
+
+  .hamburger.is-active {
+    transform: rotate(180deg);
+  }
+
+  .item-box {
+    display: flex;
+    justify-items: center;
+    align-items: center;
+    flex-wrap: wrap;
+    margin: 10px 18px;
+
+    .item-tag {
+      // width: 13%;
+      text-align: center;
+      margin: 5px 8px;
+      border-radius: 8px;
+      padding: 7px 10px;
+      font-family: OPPOSans, OPPOSans;
+      font-weight: 500;
+      font-size: 16px;
+      color: #ffffff;
+    }
+  }
+}
+
+.themeLight {
+  .card-box {
+    margin: 0 18px;
+    padding: 10px 18px;
+    box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.12);
+    border-radius: 8px 8px 8px 8px;
+    margin-bottom: 20px;
+
+    &-title {
+      // margin: 10px 0 10px;
+      text-align: left;
+      font-weight: bold;
+      font-family: OPPOSans, OPPOSans;
+      font-weight: 500;
+      font-size: 16px;
+      font-style: normal;
+      text-transform: none;
+    }
+
+    &-ul {
+      display: flex;
+      flex-wrap: wrap;
+    }
+
+    &-li {
+      width: 240px;
+      margin-right: 10px;
+      margin-top: 16px;
+      border-radius: 5px;
+      border: 1px solid #ebebeb;
+      margin-bottom: 5px;
+      background: #fff;
+      // box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.12);
+      border-radius: 8px 8px 8px 8px;
+      padding: 0 10px;
+
+      .title {
+        font-size: 14px;
+        color: #676767;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: 500;
+        font-size: 16px;
+        line-height: 19px;
+        text-align: left;
+        font-style: normal;
+        text-transform: none;
+      }
+
+      .num {
+        font-size: 22px;
+        color: #3271eb;
+        font-family: OPPOSans, OPPOSans;
+        font-weight: 800;
+        font-size: 32px;
+        text-align: left;
+        font-style: normal;
+        text-transform: none;
+      }
+
+      .time {
+        color: #676767;
+        font-family: Poppins, Poppins;
+        font-weight: 400;
+        text-align: left;
+        font-style: normal;
+        text-transform: none;
+      }
+    }
+
+    dd {
+      padding: 0;
+      margin-left: 5px;
+      margin-bottom: 10px;
+    }
+  }
+
+  .hamburger {
+    display: inline-block;
+    vertical-align: middle;
+    width: 20px;
+    height: 20px;
+  }
+
+  .hamburger.is-active {
+    transform: rotate(180deg);
+  }
+
+  .item-box {
+    display: flex;
+    justify-items: center;
+    align-items: center;
+    flex-wrap: wrap;
+    margin: 10px 18px;
+
+    .item-tag {
+      // width: 13%;
+      text-align: center;
+      margin: 5px 8px;
+      border-radius: 8px;
+      padding: 7px 3px;
+      font-family: OPPOSans, OPPOSans;
+      font-weight: 500;
+      font-size: 16px;
+      color: #ffffff;
+    }
+  }
+}
+
+.scrollbar-flex-content {
+  display: flex;
+  margin: 10px 18px;
+}
+.scrollbar-demo-item {
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  // width: 100px;
+  // height: 50px;
+  // margin: 10px;
+  text-align: center;
+  border-radius: 4px;
+  background: var(--el-color-danger-light-9);
+  color: var(--el-color-danger);
+}
+
+.item-tag {
+  // width: 13%;
+  text-align: center;
+  margin: 5px 8px;
+  border-radius: 8px;
+  padding: 7px 10px;
+  font-family: OPPOSans, OPPOSans;
+  font-weight: 500;
+  font-size: 16px;
+  color: #ffffff;
+  cursor: pointer;
+}
+</style>
diff --git a/zhitan-vue/src/views/realtimemonitor/topologymap/topologymap.vue b/zhitan-vue/src/views/realtimemonitor/topologymap/topologymap.vue
new file mode 100644
index 0000000..c54a64a
--- /dev/null
+++ b/zhitan-vue/src/views/realtimemonitor/topologymap/topologymap.vue
@@ -0,0 +1,240 @@
+<template>
+    <div class="page">
+        <!-- <div class="page-container">
+            <div class="page-container-left"> -->
+        <!-- <div class="select-box mt20">
+                    <el-form :inline="true" label-width="85px">
+                        <el-form-item label="鏁版嵁妯″瀷" prop="energyType">
+                            <el-select v-model="modelData" placeholder="璇烽�夋嫨妯″瀷" filterable @change="changeModel">
+                                <el-option v-for="model in modelInfoOptions" :key="model.modelCode"
+                                    :label="model.modelName" :value="model.modelCode" />
+                            </el-select>
+                        </el-form-item>
+                    </el-form>
+                </div> -->
+        <!-- <LeftTree ref="leftTreeRef" @handleNodeClick="handleNodeClick" /> -->
+        <!-- </div>
+            <div class="page-container-right"> -->
+        <BaseCard :title="currentNode ? currentNode.label + '--鑺傜偣閰嶇疆' : '鏆傛棤鑺傜偣閰嶇疆'">
+            <!-- <div class="text-right mt10 mb10 mr10">
+                        <el-button type="primary" icon="Setting" @click="reset">閲嶆柊閫夋嫨鍦板浘</el-button>
+                        <el-button type="primary" icon="CircleCheck" @click="handleSaveSetting">淇濆瓨閰嶇疆</el-button>
+                    </div> -->
+            <div class="content-box">
+                <div class="svg-box">
+                    <el-upload v-if="filePath === '绌鸿妭鐐�'" class="configure-upload" drag ref="upload" :limit="1"
+                        :headers="uploadData.headers" :action="uploadData.url" :with-credentials="true"
+                        :on-success="handleFileSuccess" :show-file-list="false">
+                        <i class="el-icon-upload"></i>
+                        <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+                    </el-upload>
+                    <div id="svgHtml" v-if="filePath !== '绌鸿妭鐐�'">
+                        <div v-html="svgHtml" />
+                    </div>
+                </div>
+                <!-- <div class="table-box">
+                            <el-table :data="tags" height="100%">
+                                <el-table-column label="鍙傛暟" align="center" prop="param" />
+                                <el-table-column label="閲囬泦鐐�" align="center" prop="tag">
+                                    <template #default="scope">
+                                        <el-autocomplete popper-class="my-autocomplete" v-model="scope.row['tag']"
+                                            :fetch-suggestions="querySearch" placeholder="璇疯緭鍏ユ寚鏍囩紪鐮�">
+                                            <template #default="{ item }">
+                                                <div class="name">{{ item.value }}</div>
+                                                <span class="addr">{{ item.name }}</span>
+                                            </template>
+</el-autocomplete>
+</template>
+</el-table-column>
+</el-table>
+</div> -->
+            </div>
+        </BaseCard>
+        <!-- </div> -->
+        <!-- </div> -->
+    </div>
+</template>
+
+<script setup>
+import { listModel } from '@/api/modelConfiguration/businessModel'
+import { getConfigure, updateEquipmentfile, saveSetting, getAllCollectTag } from "@/api/realTimeMonitor/realTimeMonitor";
+import { getToken } from "@/utils/auth";
+const { proxy } = getCurrentInstance();
+let modelData = ref(null)
+let modelInfoOptions = ref([]) //涓嬫媺鍒楄〃
+
+let uploadData = {
+    headers: {
+        Authorization: "Bearer " + getToken()
+    },
+    url: import.meta.env.VITE_APP_BASE_API + "/equipmentFile/upload"
+}
+
+//鑾峰彇涓嬫媺鍒楄〃
+function searchList() {
+    listModel({ isShow: 1 }).then(response => {
+        modelInfoOptions.value = response.data;
+        if (modelInfoOptions.value.length > 0) {
+            modelData.value = modelInfoOptions.value[0].modelCode;
+        }
+    });
+}
+searchList()
+//閫変腑涓嬫媺
+function changeModel(e) {
+    modelData.value = e
+}
+
+//閫変腑鏍�
+let currentNode = reactive({
+    id: "ec7330c3-8294-4214-8aa3-4a6e84837cc4",
+    label: "缇庢灄",
+    nodeCategory: "0"
+})
+getConfigureList(currentNode.id)
+
+// let currentNode = ref()
+// function handleNodeClick(data) {
+//     console.log('handleNodeClick-->', data)
+//     currentNode.value = data
+//     getConfigureList(data.id)
+// }
+
+let filePath = ref()
+let tags = ref([])
+let svgHtml = ref()
+function getConfigureList(id) {
+    getConfigure(id).then(response => {
+        filePath.value = '绌鸿妭鐐�'
+        console.log(111, response)
+        tags.value = [];
+        svgHtml.value = ''
+        if (response.code === 200) {
+            if (response.data) {
+                filePath.value = import.meta.env.VITE_APP_BASE_API + response.data.filePath;
+                tags.value = response.data.infoList;
+                getSvg();
+            }
+        } else {
+            proxy.$modal.msgError(response.msg)
+        }
+    });
+
+}
+let from = ref({
+    nodeId: '',
+    fileName: '',
+    svgType: ''
+})
+function handleFileSuccess(response, file, fileList) {
+    console.log('handleFileSuccess-->', response, file, fileList)
+    if (response.code === 200) {
+        from.value.nodeId = currentNode.value.id;
+        from.value.filePath = response.msg;
+        from.value.svgType = 'COLLECT';
+        updateEquipmentfile(from.value).then(result => {
+            if (result.code === 200) {
+                filePath.value = import.meta.env.VITE_APP_BASE_API + response.msg;
+                tags.value = [];
+                getSvg();
+            } else {
+                proxy.$modal.msgError(result.msg)
+            }
+        });
+    } else {
+        proxy.$modal.msgError(response.msg)
+    }
+}
+function getSvg() {
+    const xhr = new XMLHttpRequest();
+    xhr.open("GET", filePath.value, true);
+    xhr.send();
+    /* 鐩戝惉xhr瀵硅薄 */
+    xhr.addEventListener("load", () => {
+        svgHtml.value = xhr.responseText;
+        let values = xhr.responseXML.getElementsByTagName('text');
+        let tagTemps = [];
+        for (let i = 0; i < values.length; i++) {
+            if (values[i].getAttribute("id") != undefined)
+                tagTemps.push({
+                    "param": values[i].textContent,
+                    "tag": "",
+                    "tagType": "COLLECT"
+                });
+        }
+        console.log(tags.value.length, tagTemps.length);
+        if (tags.value.length === 0 || tags.value.length != tagTemps.length) {
+            tags.value = [];
+            tags.value = tagTemps;
+            console.log("222", tags.value.length, tagTemps.length);
+        }
+    });
+}
+function reset() {
+    filePath.value = '绌鸿妭鐐�';
+}
+function handleSaveSetting() {
+    saveSetting(currentNode.value.id, tags.value).then(response => {
+        if (response.code === 200) {
+            proxy.$modal.msgSuccess(response.msg);
+        } else {
+            proxy.$modal.msgError(response.msg);
+        }
+    });
+}
+
+function querySearch(queryString, cb) {
+    if (queryString) {
+        getAllCollectTag({ codeOrName: queryString, indexType: 'COLLECT' }).then(response => {
+            // 璋冪敤 callback 杩斿洖寤鸿鍒楄〃鐨勬暟鎹�
+            let result = response.data;
+            let values = [];
+            result.forEach(item => {
+                values.push({
+                    value: item.code,
+                    name: item.name
+                })
+            });
+            cb(values);
+        });
+    }
+}
+</script>
+
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+
+.tree-box {
+    height: calc(100vh - 210px) !important;
+    max-height: calc(100vh - 210px) !important;
+    margin-top: 0 !important;
+}
+
+
+
+.content-box {
+    height: calc(100vh - 260px);
+    display: flex;
+
+    .svg-box {
+        flex: 1;
+        height: 100%;
+        overflow: auto;
+
+        .el-upload__text {
+            height: calc(100vh - 350px);
+        }
+
+        img {
+            height: 100%;
+        }
+    }
+
+    .table-box {
+        width: 300px;
+        margin: 0;
+    }
+
+}
+</style>
diff --git a/zhitan-vue/src/views/redirect/index.vue b/zhitan-vue/src/views/redirect/index.vue
new file mode 100644
index 0000000..a469960
--- /dev/null
+++ b/zhitan-vue/src/views/redirect/index.vue
@@ -0,0 +1,14 @@
+<template>
+  <div></div>
+</template>
+
+<script setup>
+import { useRoute, useRouter } from 'vue-router'
+
+const route = useRoute();
+const router = useRouter();
+const { params, query } = route
+const { path } = params
+
+router.replace({ path: '/' + path, query })
+</script>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/register.vue b/zhitan-vue/src/views/register.vue
new file mode 100644
index 0000000..9c6f26c
--- /dev/null
+++ b/zhitan-vue/src/views/register.vue
@@ -0,0 +1,219 @@
+<template>
+  <div class="register">
+    <el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
+      <h3 class="title">鍏呯數妗╁悗鍙扮鐞嗙郴缁�</h3>
+      <el-form-item prop="username">
+        <el-input 
+          v-model="registerForm.username" 
+          type="text" 
+          size="large" 
+          auto-complete="off" 
+          placeholder="璐﹀彿"
+        >
+          <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
+        </el-input>
+      </el-form-item>
+      <el-form-item prop="password">
+        <el-input
+          v-model="registerForm.password"
+          type="password"
+          size="large" 
+          auto-complete="off"
+          placeholder="瀵嗙爜"
+          @keyup.enter="handleRegister"
+        >
+          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
+        </el-input>
+      </el-form-item>
+      <el-form-item prop="confirmPassword">
+        <el-input
+          v-model="registerForm.confirmPassword"
+          type="password"
+          size="large" 
+          auto-complete="off"
+          placeholder="纭瀵嗙爜"
+          @keyup.enter="handleRegister"
+        >
+          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
+        </el-input>
+      </el-form-item>
+      <el-form-item prop="code" v-if="captchaEnabled">
+        <el-input
+          size="large" 
+          v-model="registerForm.code"
+          auto-complete="off"
+          placeholder="楠岃瘉鐮�"
+          style="width: 63%"
+          @keyup.enter="handleRegister"
+        >
+          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
+        </el-input>
+        <div class="register-code">
+          <img :src="codeUrl" @click="getCode" class="register-code-img"/>
+        </div>
+      </el-form-item>
+      <el-form-item style="width:100%;">
+        <el-button
+          :loading="loading"
+          size="large" 
+          type="primary"
+          style="width:100%;"
+          @click.prevent="handleRegister"
+        >
+          <span v-if="!loading">娉� 鍐�</span>
+          <span v-else>娉� 鍐� 涓�...</span>
+        </el-button>
+        <div style="float: right;">
+          <router-link class="link-type" :to="'/login'">浣跨敤宸叉湁璐︽埛鐧诲綍</router-link>
+        </div>
+      </el-form-item>
+    </el-form>
+    <!--  搴曢儴  -->
+    <div class="el-register-footer">
+      <span>Copyright 漏 2018-2024 ruoyi.vip All Rights Reserved.</span>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ElMessageBox } from "element-plus";
+import { getCodeImg, register } from "@/api/login";
+
+const router = useRouter();
+const { proxy } = getCurrentInstance();
+
+const registerForm = ref({
+  username: "",
+  password: "",
+  confirmPassword: "",
+  code: "",
+  uuid: ""
+});
+
+const equalToPassword = (rule, value, callback) => {
+  if (registerForm.value.password !== value) {
+    callback(new Error("涓ゆ杈撳叆鐨勫瘑鐮佷笉涓�鑷�"));
+  } else {
+    callback();
+  }
+};
+
+const registerRules = {
+  username: [
+    { required: true, trigger: "blur", message: "璇疯緭鍏ユ偍鐨勮处鍙�" },
+    { min: 2, max: 20, message: "鐢ㄦ埛璐﹀彿闀垮害蹇呴』浠嬩簬 2 鍜� 20 涔嬮棿", trigger: "blur" }
+  ],
+  password: [
+    { required: true, trigger: "blur", message: "璇疯緭鍏ユ偍鐨勫瘑鐮�" },
+    { min: 5, max: 20, message: "鐢ㄦ埛瀵嗙爜闀垮害蹇呴』浠嬩簬 5 鍜� 20 涔嬮棿", trigger: "blur" },
+    { pattern: /^[^<>"'|\\]+$/, message: "涓嶈兘鍖呭惈闈炴硶瀛楃锛�< > \" ' \\\ |", trigger: "blur" }
+  ],
+  confirmPassword: [
+    { required: true, trigger: "blur", message: "璇峰啀娆¤緭鍏ユ偍鐨勫瘑鐮�" },
+    { required: true, validator: equalToPassword, trigger: "blur" }
+  ],
+  code: [{ required: true, trigger: "change", message: "璇疯緭鍏ラ獙璇佺爜" }]
+};
+
+const codeUrl = ref("");
+const loading = ref(false);
+const captchaEnabled = ref(true);
+
+function handleRegister() {
+  proxy.$refs.registerRef.validate(valid => {
+    if (valid) {
+      loading.value = true;
+      register(registerForm.value).then(res => {
+        const username = registerForm.value.username;
+        ElMessageBox.alert("<font color='red'>鎭枩浣狅紝鎮ㄧ殑璐﹀彿 " + username + " 娉ㄥ唽鎴愬姛锛�</font>", "绯荤粺鎻愮ず", {
+          dangerouslyUseHTMLString: true,
+          type: "success",
+        }).then(() => {
+          router.push("/login");
+        }).catch(() => {});
+      }).catch(() => {
+        loading.value = false;
+        if (captchaEnabled) {
+          getCode();
+        }
+      });
+    }
+  });
+}
+
+function getCode() {
+  getCodeImg().then(res => {
+    captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled;
+    if (captchaEnabled.value) {
+      codeUrl.value = "data:image/gif;base64," + res.img;
+      registerForm.value.uuid = res.uuid;
+    }
+  });
+}
+
+getCode();
+</script>
+
+<style lang='scss' scoped>
+.register {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  background-image: url("../assets/images/login-background.jpg");
+  background-size: cover;
+}
+.title {
+  margin: 0px auto 30px auto;
+  text-align: center;
+  color: #707070;
+}
+
+.register-form {
+  border-radius: 6px;
+  background: #ffffff;
+  width: 400px;
+  padding: 25px 25px 5px 25px;
+  .el-input {
+    height: 40px;
+    input {
+      height: 40px;
+    }
+  }
+  .input-icon {
+    height: 39px;
+    width: 14px;
+    margin-left: 0px;
+  }
+}
+.register-tip {
+  font-size: 13px;
+  text-align: center;
+  color: #bfbfbf;
+}
+.register-code {
+  width: 33%;
+  height: 40px;
+  float: right;
+  img {
+    cursor: pointer;
+    vertical-align: middle;
+  }
+}
+.el-register-footer {
+  height: 40px;
+  line-height: 40px;
+  position: fixed;
+  bottom: 0;
+  width: 100%;
+  text-align: center;
+  color: #fff;
+  font-family: Arial;
+  font-size: 12px;
+  letter-spacing: 1px;
+}
+.register-code-img {
+  height: 40px;
+  padding-left: 12px;
+}
+</style>
diff --git a/zhitan-vue/src/views/spikeconfig/components/EditModal.vue b/zhitan-vue/src/views/spikeconfig/components/EditModal.vue
new file mode 100644
index 0000000..197caa5
--- /dev/null
+++ b/zhitan-vue/src/views/spikeconfig/components/EditModal.vue
@@ -0,0 +1,118 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="900" @close="handleClose">
+
+        <el-form :model="form" ref="queryRef">
+            <el-table :data="form.tableData" style="width: 100%;">
+                <el-table-column prop="index1" label="鏃舵" width="180">
+                    <template #default="scope">
+                        <el-form-item :prop="`tableData[${scope.$index}].index1`" :rules="formRules.index1">
+                            <el-select v-model="scope.row.index1" placeholder="璇烽�夋嫨鏃舵" style="width: 100%">
+                                <el-option v-for="dict in spikesAndValleys" :key="dict.value" :label="dict.label"
+                                    :value="dict.value" />
+                            </el-select>
+                        </el-form-item>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="index2" label="鐢典环锛堝厓锛�" width="180">
+                    <template #default="scope">
+                        <el-form-item :prop="`tableData[${scope.$index}].index2`" :rules="formRules.index2">
+                            <el-input-number v-model="scope.row.index2" />
+                        </el-form-item>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="index3" label="寮�濮嬫椂闂�" width="200">
+                    <template #default="scope">
+                        <el-form-item :prop="`tableData[${scope.$index}].index3`" :rules="formRules.index3">
+                            <el-time-picker v-model="scope.row['index3']" placeholder="璇烽�夋嫨寮�濮嬫椂闂�" style="width: 100%" />
+                        </el-form-item>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="index4" label="缁撴潫鏃堕棿" width="200">
+                    <template #default="scope">
+                        <el-form-item :prop="`tableData[${scope.$index}].index4`" :rules="formRules.index4">
+                            <el-time-picker v-model="scope.row['index4']" placeholder="璇烽�夋嫨缁撴潫鏃堕棿" style="width: 100%" />
+                        </el-form-item>
+                    </template>
+                </el-table-column>
+                <el-table-column label="鎿嶄綔" width="100">
+                    <template #default="scope">
+                        <el-form-item>
+                            <el-button link type="primary" icon="Delete" @click="handleDel(scope.row, scope.$index)">
+                                鍒犻櫎
+                            </el-button>
+                        </el-form-item>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <div class="mt20 mb20 ml20 text-right">
+                <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+            </div>
+            <div slot="footer" class="text-right">
+                <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+                <el-button @click="handleClose">鍙� 娑�</el-button>
+            </div>
+        </el-form>
+    </el-dialog>
+</template>
+<script setup>
+const { proxy } = getCurrentInstance();
+let props = defineProps(['spikesAndValleys'])
+let visible = ref(false)
+let title = ref()
+let formRules = {
+    index1: [{ required: true, trigger: "change", message: "璇烽�夋嫨鏃舵" }],
+    index2: [{ required: true, trigger: "blur", message: "璇疯緭鍏ョ數浠�" }],
+    index3: [{ required: true, trigger: "change", message: "璇烽�夋嫨寮�濮嬫椂闂�" }],
+    index4: [{ required: true, trigger: "change", message: "璇烽�夋嫨缁撴潫鏃堕棿" }],
+}
+let loading = ref(false)
+let form = ref({
+    tableData: [
+        { index1: '', index2: 0, index3: '', index4: '' }
+    ]
+})
+function handleClose() {
+    visible.value = false
+    proxy.$refs.queryRef.resetFields()
+    form.value = {
+        tableData: [
+            { index1: '', index2: 0, index3: '', index4: '' }
+        ]
+    }
+}
+function submitForm() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) { }
+    })
+
+}
+function handleAdd() {
+    proxy.$refs.queryRef.validate(valid => {
+        if (valid) {
+            form.value.tableData.push({ index1: '', index2: 0, index3: '', index4: '' })
+        }
+    })
+
+}
+
+function handleDel(row, index) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+        form.value.tableData.splice(index, 1);
+    }).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    }).catch(() => { });
+}
+function handleOpen(row) {
+    if (row && row.id) {
+        title.value = "缂栬緫"
+        form.value = JSON.parse(JSON.stringify(row))
+    } else {
+        console.log(form.value)
+        title.value = "娣诲姞"
+    }
+    visible.value = true
+}
+defineExpose({ handleOpen })
+</script>
+
+<style lang='scss' scoped></style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/spikeconfig/components/UserModal.vue b/zhitan-vue/src/views/spikeconfig/components/UserModal.vue
new file mode 100644
index 0000000..795a8f1
--- /dev/null
+++ b/zhitan-vue/src/views/spikeconfig/components/UserModal.vue
@@ -0,0 +1,37 @@
+<template>
+    <el-dialog v-model="visible" :title="title" width="900" @close="handleClose">
+        <div slot="footer" class="text-right">
+            <el-button type="primary" @click="submitForm" :loading="loading">纭� 瀹�</el-button>
+            <el-button @click="handleClose">鍙� 娑�</el-button>
+        </div>
+    </el-dialog>
+</template>
+<script setup>
+const { proxy } = getCurrentInstance();
+let visible = ref(false)
+let title = ref()
+function handleClose() {
+    visible.value = false
+    // proxy.$refs.queryRef.resetFields()
+
+}
+function submitForm() {
+    // proxy.$refs.queryRef.validate(valid => {
+    //     if (valid) { }
+    // })
+
+}
+
+function handleOpen(row) {
+    if (row && row.id) {
+        title.value = "鍏宠仈鐢ㄦ埛"
+        form.value = JSON.parse(JSON.stringify(row))
+    } else {
+        title.value = "鍏宠仈鐢ㄦ埛"
+    }
+    visible.value = true
+}
+defineExpose({ handleOpen })
+</script>
+
+<style lang='scss' scoped></style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/spikeconfig/configuration/configuration.vue b/zhitan-vue/src/views/spikeconfig/configuration/configuration.vue
new file mode 100644
index 0000000..cdf0638
--- /dev/null
+++ b/zhitan-vue/src/views/spikeconfig/configuration/configuration.vue
@@ -0,0 +1,71 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+                <el-form-item label="鏂规鍚嶇О" prop="name">
+                    <el-input v-model="form.name" placeholder="璇疯緭鍏ユ柟妗堝悕绉�" />
+                </el-form-item>
+                <el-form-item label="鏃堕棿閫夋嫨">
+                    <el-date-picker v-model="form.dataTime" type="daterange" format="YYYY-MM-DD"
+                        value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 260px" unlink-panels />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="mt20 mb20 ml20">
+            <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+        </div>
+        <BaseCard title="鏂规涓� " v-for="i in 3" :key="i">
+            <div class="table-box">
+                <div class="mt20 mb20 ml20 text-right">
+                    <el-button type="primary" icon="Edit">淇敼</el-button>
+                    <el-button type="primary" icon="Delete" @click="handleDel(i)">鍒犻櫎</el-button>
+                </div>
+                <el-table :data="tableData" v-loading="loading" height="calc(100vh - 450px)">
+                    <el-table-column type="index" label="搴忓彿" width="100" />
+                    <el-table-column label="鏃舵" prop="modelName" align="center" show-overflow-tooltip />
+                    <el-table-column label="鐢典环锛堝厓锛�" prop="indexName" align="center" show-overflow-tooltip />
+                    <el-table-column label="寮�濮嬫椂闂�" prop="alarmValue" align="center" show-overflow-tooltip />
+                    <el-table-column label="缁撴潫鏃堕棿" prop="alarmBeginTime" align="center" show-overflow-tooltip />
+                </el-table>
+            </div>
+        </BaseCard>
+
+        <EditModal ref="editModalRef" :spikesAndValleys="spikes_and_valleys" />
+    </div>
+</template>
+<script setup>
+import EditModal from '../components/EditModal.vue'
+const { proxy } = getCurrentInstance();
+const { spikes_and_valleys } = proxy.useDict('spikes_and_valleys')
+
+let loading = ref(false)
+let tableData = ref([])
+let form = ref({ name: '', dataTime: [] })
+
+function handleQuery() {
+
+}
+function resetQuery() { }
+let editModalRef = ref()
+function handleAdd(row) {
+    if (editModalRef.value) {
+        editModalRef.value.handleOpen(row)
+    }
+}
+
+function handleDel(row, index) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+        // form.value.splice(index, 1);
+    }).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    }).catch(() => { });
+}
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/spikeconfig/externalcompany/externalcompany.vue b/zhitan-vue/src/views/spikeconfig/externalcompany/externalcompany.vue
new file mode 100644
index 0000000..7260f2d
--- /dev/null
+++ b/zhitan-vue/src/views/spikeconfig/externalcompany/externalcompany.vue
@@ -0,0 +1,83 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+                <el-form-item label="鏂规鍚嶇О" prop="name">
+                    <el-input v-model="form.name" placeholder="璇疯緭鍏ユ柟妗堝悕绉�" />
+                </el-form-item>
+                <el-form-item label="鏃堕棿閫夋嫨">
+                    <el-date-picker v-model="form.dataTime" type="daterange" format="YYYY-MM-DD"
+                        value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 260px" unlink-panels />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="mt20 mb20 ml20">
+            <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+        </div>
+        <BaseCard title="鏂规涓� " v-for="i in 3" :key="i">
+            <div class="table-box">
+                <div class="mt20 mb20 ml20 text-right">
+                    <el-button type="primary" icon="User" @click="handleUser">鍏宠仈鐢ㄦ埛</el-button>
+                    <el-button type="primary" icon="Edit">淇敼</el-button>
+                    <el-button type="primary" icon="Delete" @click="handleDel(i)">鍒犻櫎</el-button>
+                </div>
+                <el-table :data="tableData" v-loading="loading" height="calc(100vh - 450px)">
+                    <el-table-column type="index" label="搴忓彿" width="100" />
+                    <el-table-column label="鏃舵" prop="modelName" align="center" show-overflow-tooltip />
+                    <el-table-column label="鐢典环锛堝厓锛�" prop="indexName" align="center" show-overflow-tooltip />
+                    <el-table-column label="寮�濮嬫椂闂�" prop="alarmValue" align="center" show-overflow-tooltip />
+                    <el-table-column label="缁撴潫鏃堕棿" prop="alarmBeginTime" align="center" show-overflow-tooltip />
+                </el-table>
+            </div>
+        </BaseCard>
+        <EditModal ref="editModalRef" :spikesAndValleys="spikes_and_valleys" />
+        <UserModal ref="userModalRef" />
+    </div>
+</template>
+<script setup>
+import EditModal from '../components/EditModal.vue'
+import UserModal from '../components/UserModal.vue'
+const { proxy } = getCurrentInstance();
+const { spikes_and_valleys } = proxy.useDict('spikes_and_valleys')
+
+let loading = ref(false)
+let tableData = ref([])
+let form = ref({ name: '', dataTime: [] })
+
+function handleQuery() {
+
+}
+function resetQuery() { }
+let editModalRef = ref()
+function handleAdd(row) {
+    if (editModalRef.value) {
+        editModalRef.value.handleOpen(row)
+    }
+}
+
+
+
+function handleDel(row, index) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+        // form.value.splice(index, 1);
+    }).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    }).catch(() => { });
+}
+
+
+let userModalRef = ref()
+function handleUser(row) {
+    if (userModalRef.value) {
+        userModalRef.value.handleOpen(row)
+    }
+}
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/spikeconfig/logistics/logistics.vue b/zhitan-vue/src/views/spikeconfig/logistics/logistics.vue
new file mode 100644
index 0000000..cdf0638
--- /dev/null
+++ b/zhitan-vue/src/views/spikeconfig/logistics/logistics.vue
@@ -0,0 +1,71 @@
+<template>
+    <div class="page">
+        <div class="form-card">
+            <el-form :model="form" ref="queryRef" :inline="true" label-width="85px">
+                <el-form-item label="鏂规鍚嶇О" prop="name">
+                    <el-input v-model="form.name" placeholder="璇疯緭鍏ユ柟妗堝悕绉�" />
+                </el-form-item>
+                <el-form-item label="鏃堕棿閫夋嫨">
+                    <el-date-picker v-model="form.dataTime" type="daterange" format="YYYY-MM-DD"
+                        value-format="YYYY-MM-DD" placeholder="鏃堕棿" style="width: 260px" unlink-panels />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                    <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="mt20 mb20 ml20">
+            <el-button type="primary" icon="Plus" @click="handleAdd">鏂板</el-button>
+        </div>
+        <BaseCard title="鏂规涓� " v-for="i in 3" :key="i">
+            <div class="table-box">
+                <div class="mt20 mb20 ml20 text-right">
+                    <el-button type="primary" icon="Edit">淇敼</el-button>
+                    <el-button type="primary" icon="Delete" @click="handleDel(i)">鍒犻櫎</el-button>
+                </div>
+                <el-table :data="tableData" v-loading="loading" height="calc(100vh - 450px)">
+                    <el-table-column type="index" label="搴忓彿" width="100" />
+                    <el-table-column label="鏃舵" prop="modelName" align="center" show-overflow-tooltip />
+                    <el-table-column label="鐢典环锛堝厓锛�" prop="indexName" align="center" show-overflow-tooltip />
+                    <el-table-column label="寮�濮嬫椂闂�" prop="alarmValue" align="center" show-overflow-tooltip />
+                    <el-table-column label="缁撴潫鏃堕棿" prop="alarmBeginTime" align="center" show-overflow-tooltip />
+                </el-table>
+            </div>
+        </BaseCard>
+
+        <EditModal ref="editModalRef" :spikesAndValleys="spikes_and_valleys" />
+    </div>
+</template>
+<script setup>
+import EditModal from '../components/EditModal.vue'
+const { proxy } = getCurrentInstance();
+const { spikes_and_valleys } = proxy.useDict('spikes_and_valleys')
+
+let loading = ref(false)
+let tableData = ref([])
+let form = ref({ name: '', dataTime: [] })
+
+function handleQuery() {
+
+}
+function resetQuery() { }
+let editModalRef = ref()
+function handleAdd(row) {
+    if (editModalRef.value) {
+        editModalRef.value.handleOpen(row)
+    }
+}
+
+function handleDel(row, index) {
+    proxy.$modal.confirm('鏄惁纭鍒犻櫎鏁版嵁椤�?').then(function () {
+        // form.value.splice(index, 1);
+    }).then(() => {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    }).catch(() => { });
+}
+</script>
+
+<style scoped lang="scss">
+@import "@/assets/styles/page.scss";
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/system/config/index.vue b/zhitan-vue/src/views/system/config/index.vue
new file mode 100644
index 0000000..4aa86e0
--- /dev/null
+++ b/zhitan-vue/src/views/system/config/index.vue
@@ -0,0 +1,305 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
+         <el-form-item label="鍙傛暟鍚嶇О" prop="configName">
+            <el-input
+               v-model="queryParams.configName"
+               placeholder="璇疯緭鍏ュ弬鏁板悕绉�"
+               clearable
+               style="width: 240px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鍙傛暟閿悕" prop="configKey">
+            <el-input
+               v-model="queryParams.configKey"
+               placeholder="璇疯緭鍏ュ弬鏁伴敭鍚�"
+               clearable
+               style="width: 240px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="绯荤粺鍐呯疆" prop="configType">
+            <el-select v-model="queryParams.configType" placeholder="绯荤粺鍐呯疆" clearable>
+               <el-option
+                  v-for="dict in sys_yes_no"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item label="鍒涘缓鏃堕棿" style="width: 340px;">
+            <el-date-picker
+               v-model="dateRange"
+               value-format="YYYY-MM-DD"
+               type="daterange"
+               range-separator="-"
+               start-placeholder="寮�濮嬫棩鏈�"
+               end-placeholder="缁撴潫鏃ユ湡"
+            ></el-date-picker>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Plus"
+               @click="handleAdd"
+               v-hasPermi="['system:config:add']"
+            >鏂板</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="success"
+               plain
+               icon="Edit"
+               :disabled="single"
+               @click="handleUpdate"
+               v-hasPermi="['system:config:edit']"
+            >淇敼</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               :disabled="multiple"
+               @click="handleDelete"
+               v-hasPermi="['system:config:remove']"
+            >鍒犻櫎</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="warning"
+               plain
+               icon="Download"
+               @click="handleExport"
+               v-hasPermi="['system:config:export']"
+            >瀵煎嚭</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Refresh"
+               @click="handleRefreshCache"
+               v-hasPermi="['system:config:remove']"
+            >鍒锋柊缂撳瓨</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
+         <el-table-column type="selection" width="55" align="center" />
+         <el-table-column label="鍙傛暟涓婚敭" align="center" prop="configId" />
+         <el-table-column label="鍙傛暟鍚嶇О" align="center" prop="configName" :show-overflow-tooltip="true" />
+         <el-table-column label="鍙傛暟閿悕" align="center" prop="configKey" :show-overflow-tooltip="true" />
+         <el-table-column label="鍙傛暟閿��" align="center" prop="configValue" :show-overflow-tooltip="true" />
+         <el-table-column label="绯荤粺鍐呯疆" align="center" prop="configType">
+            <template #default="scope">
+               <dict-tag :options="sys_yes_no" :value="scope.row.configType" />
+            </template>
+         </el-table-column>
+         <el-table-column label="澶囨敞" align="center" prop="remark" :show-overflow-tooltip="true" />
+         <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="180">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" width="150" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:config:edit']" >淇敼</el-button>
+               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:config:remove']">鍒犻櫎</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+
+      <!-- 娣诲姞鎴栦慨鏀瑰弬鏁伴厤缃璇濇 -->
+      <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+         <el-form ref="configRef" :model="form" :rules="rules" label-width="100px">
+            <el-form-item label="鍙傛暟鍚嶇О" prop="configName">
+               <el-input v-model="form.configName" placeholder="璇疯緭鍏ュ弬鏁板悕绉�" />
+            </el-form-item>
+            <el-form-item label="鍙傛暟閿悕" prop="configKey">
+               <el-input v-model="form.configKey" placeholder="璇疯緭鍏ュ弬鏁伴敭鍚�" />
+            </el-form-item>
+            <el-form-item label="鍙傛暟閿��" prop="configValue">
+               <el-input v-model="form.configValue" placeholder="璇疯緭鍏ュ弬鏁伴敭鍊�" />
+            </el-form-item>
+            <el-form-item label="绯荤粺鍐呯疆" prop="configType">
+               <el-radio-group v-model="form.configType">
+                  <el-radio
+                     v-for="dict in sys_yes_no"
+                     :key="dict.value"
+                     :label="dict.value"
+                  >{{ dict.label }}</el-radio>
+               </el-radio-group>
+            </el-form-item>
+            <el-form-item label="澶囨敞" prop="remark">
+               <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ唴瀹�" />
+            </el-form-item>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+               <el-button @click="cancel">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="Config">
+import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from "@/api/system/config";
+
+const { proxy } = getCurrentInstance();
+const { sys_yes_no } = proxy.useDict("sys_yes_no");
+
+const configList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+const dateRange = ref([]);
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    configName: undefined,
+    configKey: undefined,
+    configType: undefined
+  },
+  rules: {
+    configName: [{ required: true, message: "鍙傛暟鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    configKey: [{ required: true, message: "鍙傛暟閿悕涓嶈兘涓虹┖", trigger: "blur" }],
+    configValue: [{ required: true, message: "鍙傛暟閿�间笉鑳戒负绌�", trigger: "blur" }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ鍙傛暟鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listConfig(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
+    configList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 鍙栨秷鎸夐挳 */
+function cancel() {
+  open.value = false;
+  reset();
+}
+/** 琛ㄥ崟閲嶇疆 */
+function reset() {
+  form.value = {
+    configId: undefined,
+    configName: undefined,
+    configKey: undefined,
+    configValue: undefined,
+    configType: "Y",
+    remark: undefined
+  };
+  proxy.resetForm("configRef");
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  dateRange.value = [];
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+/** 澶氶�夋閫変腑鏁版嵁 */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.configId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "娣诲姞鍙傛暟";
+}
+/** 淇敼鎸夐挳鎿嶄綔 */
+function handleUpdate(row) {
+  reset();
+  const configId = row.configId || ids.value;
+  getConfig(configId).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "淇敼鍙傛暟";
+  });
+}
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["configRef"].validate(valid => {
+    if (valid) {
+      if (form.value.configId != undefined) {
+        updateConfig(form.value).then(response => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addConfig(form.value).then(response => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const configIds = row.configId || ids.value;
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎鍙傛暟缂栧彿涓�"' + configIds + '"鐨勬暟鎹」锛�').then(function () {
+    return delConfig(configIds);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download("system/config/export", {
+    ...queryParams.value
+  }, `config_${new Date().getTime()}.xlsx`);
+}
+/** 鍒锋柊缂撳瓨鎸夐挳鎿嶄綔 */
+function handleRefreshCache() {
+  refreshCache().then(() => {
+    proxy.$modal.msgSuccess("鍒锋柊缂撳瓨鎴愬姛");
+  });
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/system/dept/index.vue b/zhitan-vue/src/views/system/dept/index.vue
new file mode 100644
index 0000000..d073a26
--- /dev/null
+++ b/zhitan-vue/src/views/system/dept/index.vue
@@ -0,0 +1,274 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+         <el-form-item label="閮ㄩ棬鍚嶇О" prop="deptName">
+            <el-input
+               v-model="queryParams.deptName"
+               placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鐘舵��" prop="status">
+            <el-select v-model="queryParams.status" placeholder="閮ㄩ棬鐘舵��" clearable style="width: 200px">
+               <el-option
+                  v-for="dict in sys_normal_disable"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Plus"
+               @click="handleAdd"
+               v-hasPermi="['system:dept:add']"
+            >鏂板</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="info"
+               plain
+               icon="Sort"
+               @click="toggleExpandAll"
+            >灞曞紑/鎶樺彔</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table
+         v-if="refreshTable"
+         v-loading="loading"
+         :data="deptList"
+         row-key="deptId"
+         :default-expand-all="isExpandAll"
+         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+      >
+         <el-table-column prop="deptName" label="閮ㄩ棬鍚嶇О" width="260"></el-table-column>
+         <el-table-column prop="orderNum" label="鎺掑簭" width="200"></el-table-column>
+         <el-table-column prop="status" label="鐘舵��" width="100">
+            <template #default="scope">
+               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+            </template>
+         </el-table-column>
+         <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="200">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']">淇敼</el-button>
+               <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']">鏂板</el-button>
+               <el-button v-if="scope.row.parentId != 0" link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']">鍒犻櫎</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <!-- 娣诲姞鎴栦慨鏀归儴闂ㄥ璇濇 -->
+      <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+         <el-form ref="deptRef" :model="form" :rules="rules" label-width="80px">
+            <el-row>
+               <el-col :span="24" v-if="form.parentId !== 0">
+                  <el-form-item label="涓婄骇閮ㄩ棬" prop="parentId">
+                     <el-tree-select
+                        v-model="form.parentId"
+                        :data="deptOptions"
+                        :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
+                        value-key="deptId"
+                        placeholder="閫夋嫨涓婄骇閮ㄩ棬"
+                        check-strictly
+                     />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="閮ㄩ棬鍚嶇О" prop="deptName">
+                     <el-input v-model="form.deptName" placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="鏄剧ず鎺掑簭" prop="orderNum">
+                     <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="璐熻矗浜�" prop="leader">
+                     <el-input v-model="form.leader" placeholder="璇疯緭鍏ヨ礋璐d汉" maxlength="20" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="鑱旂郴鐢佃瘽" prop="phone">
+                     <el-input v-model="form.phone" placeholder="璇疯緭鍏ヨ仈绯荤數璇�" maxlength="11" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="閭" prop="email">
+                     <el-input v-model="form.email" placeholder="璇疯緭鍏ラ偖绠�" maxlength="50" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="閮ㄩ棬鐘舵��">
+                     <el-radio-group v-model="form.status">
+                        <el-radio
+                           v-for="dict in sys_normal_disable"
+                           :key="dict.value"
+                           :label="dict.value"
+                        >{{ dict.label }}</el-radio>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+            </el-row>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+               <el-button @click="cancel">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="Dept">
+import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept";
+
+const { proxy } = getCurrentInstance();
+const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
+
+const deptList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const title = ref("");
+const deptOptions = ref([]);
+const isExpandAll = ref(true);
+const refreshTable = ref(true);
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    deptName: undefined,
+    status: undefined
+  },
+  rules: {
+    parentId: [{ required: true, message: "涓婄骇閮ㄩ棬涓嶈兘涓虹┖", trigger: "blur" }],
+    deptName: [{ required: true, message: "閮ㄩ棬鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    orderNum: [{ required: true, message: "鏄剧ず鎺掑簭涓嶈兘涓虹┖", trigger: "blur" }],
+    email: [{ type: "email", message: "璇疯緭鍏ユ纭殑閭鍦板潃", trigger: ["blur", "change"] }],
+    phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "璇疯緭鍏ユ纭殑鎵嬫満鍙风爜", trigger: "blur" }]
+  },
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ閮ㄩ棬鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listDept(queryParams.value).then(response => {
+    deptList.value = proxy.handleTree(response.data, "deptId");
+    loading.value = false;
+  });
+}
+/** 鍙栨秷鎸夐挳 */
+function cancel() {
+  open.value = false;
+  reset();
+}
+/** 琛ㄥ崟閲嶇疆 */
+function reset() {
+  form.value = {
+    deptId: undefined,
+    parentId: undefined,
+    deptName: undefined,
+    orderNum: 0,
+    leader: undefined,
+    phone: undefined,
+    email: undefined,
+    status: "0"
+  };
+  proxy.resetForm("deptRef");
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd(row) {
+  reset();
+  listDept().then(response => {
+    deptOptions.value = proxy.handleTree(response.data, "deptId");
+  });
+  if (row != undefined) {
+    form.value.parentId = row.deptId;
+  }
+  open.value = true;
+  title.value = "娣诲姞閮ㄩ棬";
+}
+/** 灞曞紑/鎶樺彔鎿嶄綔 */
+function toggleExpandAll() {
+  refreshTable.value = false;
+  isExpandAll.value = !isExpandAll.value;
+  nextTick(() => {
+    refreshTable.value = true;
+  });
+}
+/** 淇敼鎸夐挳鎿嶄綔 */
+function handleUpdate(row) {
+  reset();
+  listDeptExcludeChild(row.deptId).then(response => {
+    deptOptions.value = proxy.handleTree(response.data, "deptId");
+  });
+  getDept(row.deptId).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "淇敼閮ㄩ棬";
+  });
+}
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["deptRef"].validate(valid => {
+    if (valid) {
+      if (form.value.deptId != undefined) {
+        updateDept(form.value).then(response => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addDept(form.value).then(response => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎鍚嶇О涓�"' + row.deptName + '"鐨勬暟鎹」?').then(function() {
+    return delDept(row.deptId);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/system/dict/data.vue b/zhitan-vue/src/views/system/dict/data.vue
new file mode 100644
index 0000000..f817fd0
--- /dev/null
+++ b/zhitan-vue/src/views/system/dict/data.vue
@@ -0,0 +1,350 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+         <el-form-item label="瀛楀吀鍚嶇О" prop="dictType">
+            <el-select v-model="queryParams.dictType" style="width: 200px">
+               <el-option
+                  v-for="item in typeOptions"
+                  :key="item.dictId"
+                  :label="item.dictName"
+                  :value="item.dictType"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item label="瀛楀吀鏍囩" prop="dictLabel">
+            <el-input
+               v-model="queryParams.dictLabel"
+               placeholder="璇疯緭鍏ュ瓧鍏告爣绛�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鐘舵��" prop="status">
+            <el-select v-model="queryParams.status" placeholder="鏁版嵁鐘舵��" clearable style="width: 200px">
+               <el-option
+                  v-for="dict in sys_normal_disable"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Plus"
+               @click="handleAdd"
+               v-hasPermi="['system:dict:add']"
+            >鏂板</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="success"
+               plain
+               icon="Edit"
+               :disabled="single"
+               @click="handleUpdate"
+               v-hasPermi="['system:dict:edit']"
+            >淇敼</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               :disabled="multiple"
+               @click="handleDelete"
+               v-hasPermi="['system:dict:remove']"
+            >鍒犻櫎</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="warning"
+               plain
+               icon="Download"
+               @click="handleExport"
+               v-hasPermi="['system:dict:export']"
+            >瀵煎嚭</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="warning"
+               plain
+               icon="Close"
+               @click="handleClose"
+            >鍏抽棴</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
+         <el-table-column type="selection" width="55" align="center" />
+         <el-table-column label="瀛楀吀缂栫爜" align="center" prop="dictCode" />
+         <el-table-column label="瀛楀吀鏍囩" align="center" prop="dictLabel">
+            <template #default="scope">
+               <span v-if="(scope.row.listClass == '' || scope.row.listClass == 'default') && (scope.row.cssClass == '' || scope.row.cssClass == null)">{{ scope.row.dictLabel }}</span>
+               <el-tag v-else :type="scope.row.listClass == 'primary' ? '' : scope.row.listClass" :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag>
+            </template>
+         </el-table-column>
+         <el-table-column label="瀛楀吀閿��" align="center" prop="dictValue" />
+         <el-table-column label="瀛楀吀鎺掑簭" align="center" prop="dictSort" />
+         <el-table-column label="鐘舵��" align="center" prop="status">
+            <template #default="scope">
+               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+            </template>
+         </el-table-column>
+         <el-table-column label="澶囨敞" align="center" prop="remark" :show-overflow-tooltip="true" />
+         <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="180">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']">淇敼</el-button>
+               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']">鍒犻櫎</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+
+      <!-- 娣诲姞鎴栦慨鏀瑰弬鏁伴厤缃璇濇 -->
+      <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+         <el-form ref="dataRef" :model="form" :rules="rules" label-width="80px">
+            <el-form-item label="瀛楀吀绫诲瀷">
+               <el-input v-model="form.dictType" :disabled="true" />
+            </el-form-item>
+            <el-form-item label="鏁版嵁鏍囩" prop="dictLabel">
+               <el-input v-model="form.dictLabel" placeholder="璇疯緭鍏ユ暟鎹爣绛�" />
+            </el-form-item>
+            <el-form-item label="鏁版嵁閿��" prop="dictValue">
+               <el-input v-model="form.dictValue" placeholder="璇疯緭鍏ユ暟鎹敭鍊�" />
+            </el-form-item>
+            <el-form-item label="鏍峰紡灞炴��" prop="cssClass">
+               <el-input v-model="form.cssClass" placeholder="璇疯緭鍏ユ牱寮忓睘鎬�" />
+            </el-form-item>
+            <el-form-item label="鏄剧ず鎺掑簭" prop="dictSort">
+               <el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
+            </el-form-item>
+            <el-form-item label="鍥炴樉鏍峰紡" prop="listClass">
+               <el-select v-model="form.listClass">
+                  <el-option
+                     v-for="item in listClassOptions"
+                     :key="item.value"
+                     :label="item.label + '(' + item.value + ')'"
+                     :value="item.value"
+                  ></el-option>
+               </el-select>
+            </el-form-item>
+            <el-form-item label="鐘舵��" prop="status">
+               <el-radio-group v-model="form.status">
+                  <el-radio
+                     v-for="dict in sys_normal_disable"
+                     :key="dict.value"
+                     :label="dict.value"
+                  >{{ dict.label }}</el-radio>
+               </el-radio-group>
+            </el-form-item>
+            <el-form-item label="澶囨敞" prop="remark">
+               <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ唴瀹�"></el-input>
+            </el-form-item>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+               <el-button @click="cancel">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="Data">
+import useDictStore from '@/store/modules/dict'
+import { optionselect as getDictOptionselect, getType } from "@/api/system/dict/type";
+import { listData, getData, delData, addData, updateData } from "@/api/system/dict/data";
+
+const { proxy } = getCurrentInstance();
+const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
+
+const dataList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+const defaultDictType = ref("");
+const typeOptions = ref([]);
+const route = useRoute();
+// 鏁版嵁鏍囩鍥炴樉鏍峰紡
+const listClassOptions = ref([
+  { value: "default", label: "榛樿" }, 
+  { value: "primary", label: "涓昏" }, 
+  { value: "success", label: "鎴愬姛" },
+  { value: "info", label: "淇℃伅" },
+  { value: "warning", label: "璀﹀憡" },
+  { value: "danger", label: "鍗遍櫓" }
+]);
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    dictType: undefined,
+    dictLabel: undefined,
+    status: undefined
+  },
+  rules: {
+    dictLabel: [{ required: true, message: "鏁版嵁鏍囩涓嶈兘涓虹┖", trigger: "blur" }],
+    dictValue: [{ required: true, message: "鏁版嵁閿�间笉鑳戒负绌�", trigger: "blur" }],
+    dictSort: [{ required: true, message: "鏁版嵁椤哄簭涓嶈兘涓虹┖", trigger: "blur" }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ瀛楀吀绫诲瀷璇︾粏 */
+function getTypes(dictId) {
+  getType(dictId).then(response => {
+    queryParams.value.dictType = response.data.dictType;
+    defaultDictType.value = response.data.dictType;
+    getList();
+  });
+}
+
+/** 鏌ヨ瀛楀吀绫诲瀷鍒楄〃 */
+function getTypeList() {
+  getDictOptionselect().then(response => {
+    typeOptions.value = response.data;
+  });
+}
+/** 鏌ヨ瀛楀吀鏁版嵁鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listData(queryParams.value).then(response => {
+    dataList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 鍙栨秷鎸夐挳 */
+function cancel() {
+  open.value = false;
+  reset();
+}
+/** 琛ㄥ崟閲嶇疆 */
+function reset() {
+  form.value = {
+    dictCode: undefined,
+    dictLabel: undefined,
+    dictValue: undefined,
+    cssClass: undefined,
+    listClass: "default",
+    dictSort: 0,
+    status: "0",
+    remark: undefined
+  };
+  proxy.resetForm("dataRef");
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 杩斿洖鎸夐挳鎿嶄綔 */
+function handleClose() {
+  const obj = { path: "/system/dict" };
+  proxy.$tab.closeOpenPage(obj);
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  queryParams.value.dictType = defaultDictType.value;
+  handleQuery();
+}
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "娣诲姞瀛楀吀鏁版嵁";
+  form.value.dictType = queryParams.value.dictType;
+}
+/** 澶氶�夋閫変腑鏁版嵁 */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.dictCode);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+/** 淇敼鎸夐挳鎿嶄綔 */
+function handleUpdate(row) {
+  reset();
+  const dictCode = row.dictCode || ids.value;
+  getData(dictCode).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "淇敼瀛楀吀鏁版嵁";
+  });
+}
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["dataRef"].validate(valid => {
+    if (valid) {
+      if (form.value.dictCode != undefined) {
+        updateData(form.value).then(response => {
+          useDictStore().removeDict(queryParams.value.dictType);
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addData(form.value).then(response => {
+          useDictStore().removeDict(queryParams.value.dictType);
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const dictCodes = row.dictCode || ids.value;
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎瀛楀吀缂栫爜涓�"' + dictCodes + '"鐨勬暟鎹」锛�').then(function() {
+    return delData(dictCodes);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+    useDictStore().removeDict(queryParams.value.dictType);
+  }).catch(() => {});
+}
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download("system/dict/data/export", {
+    ...queryParams.value
+  }, `dict_data_${new Date().getTime()}.xlsx`);
+}
+
+getTypes(route.params && route.params.dictId);
+getTypeList();
+</script>
diff --git a/zhitan-vue/src/views/system/dict/index.vue b/zhitan-vue/src/views/system/dict/index.vue
new file mode 100644
index 0000000..574955b
--- /dev/null
+++ b/zhitan-vue/src/views/system/dict/index.vue
@@ -0,0 +1,312 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
+         <el-form-item label="瀛楀吀鍚嶇О" prop="dictName">
+            <el-input
+               v-model="queryParams.dictName"
+               placeholder="璇疯緭鍏ュ瓧鍏稿悕绉�"
+               clearable
+               style="width: 240px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="瀛楀吀绫诲瀷" prop="dictType">
+            <el-input
+               v-model="queryParams.dictType"
+               placeholder="璇疯緭鍏ュ瓧鍏哥被鍨�"
+               clearable
+               style="width: 240px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鐘舵��" prop="status">
+            <el-select
+               v-model="queryParams.status"
+               placeholder="瀛楀吀鐘舵��"
+               clearable
+               style="width: 240px"
+            >
+               <el-option
+                  v-for="dict in sys_normal_disable"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item label="鍒涘缓鏃堕棿" style="width: 308px">
+            <el-date-picker
+               v-model="dateRange"
+               value-format="YYYY-MM-DD"
+               type="daterange"
+               range-separator="-"
+               start-placeholder="寮�濮嬫棩鏈�"
+               end-placeholder="缁撴潫鏃ユ湡"
+            ></el-date-picker>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Plus"
+               @click="handleAdd"
+               v-hasPermi="['system:dict:add']"
+            >鏂板</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="success"
+               plain
+               icon="Edit"
+               :disabled="single"
+               @click="handleUpdate"
+               v-hasPermi="['system:dict:edit']"
+            >淇敼</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               :disabled="multiple"
+               @click="handleDelete"
+               v-hasPermi="['system:dict:remove']"
+            >鍒犻櫎</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="warning"
+               plain
+               icon="Download"
+               @click="handleExport"
+               v-hasPermi="['system:dict:export']"
+            >瀵煎嚭</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Refresh"
+               @click="handleRefreshCache"
+               v-hasPermi="['system:dict:remove']"
+            >鍒锋柊缂撳瓨</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
+         <el-table-column type="selection" width="55" align="center" />
+         <el-table-column label="瀛楀吀缂栧彿" align="center" prop="dictId" />
+         <el-table-column label="瀛楀吀鍚嶇О" align="center" prop="dictName" :show-overflow-tooltip="true"/>
+         <el-table-column label="瀛楀吀绫诲瀷" align="center" :show-overflow-tooltip="true">
+            <template #default="scope">
+               <router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
+                  <span>{{ scope.row.dictType }}</span>
+               </router-link>
+            </template>
+         </el-table-column>
+         <el-table-column label="鐘舵��" align="center" prop="status">
+            <template #default="scope">
+               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+            </template>
+         </el-table-column>
+         <el-table-column label="澶囨敞" align="center" prop="remark" :show-overflow-tooltip="true" />
+         <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="180">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" width="160" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']">淇敼</el-button>
+               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']">鍒犻櫎</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+
+      <!-- 娣诲姞鎴栦慨鏀瑰弬鏁伴厤缃璇濇 -->
+      <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+         <el-form ref="dictRef" :model="form" :rules="rules" label-width="80px">
+            <el-form-item label="瀛楀吀鍚嶇О" prop="dictName">
+               <el-input v-model="form.dictName" placeholder="璇疯緭鍏ュ瓧鍏稿悕绉�" />
+            </el-form-item>
+            <el-form-item label="瀛楀吀绫诲瀷" prop="dictType">
+               <el-input v-model="form.dictType" placeholder="璇疯緭鍏ュ瓧鍏哥被鍨�" />
+            </el-form-item>
+            <el-form-item label="鐘舵��" prop="status">
+               <el-radio-group v-model="form.status">
+                  <el-radio
+                     v-for="dict in sys_normal_disable"
+                     :key="dict.value"
+                     :label="dict.value"
+                  >{{ dict.label }}</el-radio>
+               </el-radio-group>
+            </el-form-item>
+            <el-form-item label="澶囨敞" prop="remark">
+               <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ唴瀹�"></el-input>
+            </el-form-item>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+               <el-button @click="cancel">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="Dict">
+import useDictStore from '@/store/modules/dict'
+import { listType, getType, delType, addType, updateType, refreshCache } from "@/api/system/dict/type";
+
+const { proxy } = getCurrentInstance();
+const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
+
+const typeList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+const dateRange = ref([]);
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    dictName: undefined,
+    dictType: undefined,
+    status: undefined
+  },
+  rules: {
+    dictName: [{ required: true, message: "瀛楀吀鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    dictType: [{ required: true, message: "瀛楀吀绫诲瀷涓嶈兘涓虹┖", trigger: "blur" }]
+  },
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ瀛楀吀绫诲瀷鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listType(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
+    typeList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 鍙栨秷鎸夐挳 */
+function cancel() {
+  open.value = false;
+  reset();
+}
+/** 琛ㄥ崟閲嶇疆 */
+function reset() {
+  form.value = {
+    dictId: undefined,
+    dictName: undefined,
+    dictType: undefined,
+    status: "0",
+    remark: undefined
+  };
+  proxy.resetForm("dictRef");
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  dateRange.value = [];
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "娣诲姞瀛楀吀绫诲瀷";
+}
+/** 澶氶�夋閫変腑鏁版嵁 */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.dictId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+/** 淇敼鎸夐挳鎿嶄綔 */
+function handleUpdate(row) {
+  reset();
+  const dictId = row.dictId || ids.value;
+  getType(dictId).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "淇敼瀛楀吀绫诲瀷";
+  });
+}
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["dictRef"].validate(valid => {
+    if (valid) {
+      if (form.value.dictId != undefined) {
+        updateType(form.value).then(response => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addType(form.value).then(response => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const dictIds = row.dictId || ids.value;
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎瀛楀吀缂栧彿涓�"' + dictIds + '"鐨勬暟鎹」锛�').then(function() {
+    return delType(dictIds);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download("system/dict/type/export", {
+    ...queryParams.value
+  }, `dict_${new Date().getTime()}.xlsx`);
+}
+/** 鍒锋柊缂撳瓨鎸夐挳鎿嶄綔 */
+function handleRefreshCache() {
+  refreshCache().then(() => {
+    proxy.$modal.msgSuccess("鍒锋柊鎴愬姛");
+    useDictStore().cleanDict();
+  });
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/system/menu/index.vue b/zhitan-vue/src/views/system/menu/index.vue
new file mode 100644
index 0000000..bb486e9
--- /dev/null
+++ b/zhitan-vue/src/views/system/menu/index.vue
@@ -0,0 +1,427 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+         <el-form-item label="鑿滃崟鍚嶇О" prop="menuName">
+            <el-input
+               v-model="queryParams.menuName"
+               placeholder="璇疯緭鍏ヨ彍鍗曞悕绉�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鐘舵��" prop="status">
+            <el-select v-model="queryParams.status" placeholder="鑿滃崟鐘舵��" clearable style="width: 200px">
+               <el-option
+                  v-for="dict in sys_normal_disable"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Plus"
+               @click="handleAdd"
+               v-hasPermi="['system:menu:add']"
+            >鏂板</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button 
+               type="info"
+               plain
+               icon="Sort"
+               @click="toggleExpandAll"
+            >灞曞紑/鎶樺彔</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table
+         v-if="refreshTable"
+         v-loading="loading"
+         :data="menuList"
+         row-key="menuId"
+         :default-expand-all="isExpandAll"
+         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+      >
+         <el-table-column prop="menuName" label="鑿滃崟鍚嶇О" :show-overflow-tooltip="true" width="160"></el-table-column>
+         <el-table-column prop="icon" label="鍥炬爣" align="center" width="100">
+            <template #default="scope">
+               <svg-icon :icon-class="scope.row.icon" />
+            </template>
+         </el-table-column>
+         <el-table-column prop="orderNum" label="鎺掑簭" width="60"></el-table-column>
+         <el-table-column prop="perms" label="鏉冮檺鏍囪瘑" :show-overflow-tooltip="true"></el-table-column>
+         <el-table-column prop="component" label="缁勪欢璺緞" :show-overflow-tooltip="true"></el-table-column>
+         <el-table-column prop="status" label="鐘舵��" width="80">
+            <template #default="scope">
+               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+            </template>
+         </el-table-column>
+         <el-table-column label="鍒涘缓鏃堕棿" align="center" width="160" prop="createTime">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" width="210" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:menu:edit']">淇敼</el-button>
+               <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:menu:add']">鏂板</el-button>
+               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:menu:remove']">鍒犻櫎</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <!-- 娣诲姞鎴栦慨鏀硅彍鍗曞璇濇 -->
+      <el-dialog :title="title" v-model="open" width="680px" append-to-body>
+         <el-form ref="menuRef" :model="form" :rules="rules" label-width="100px">
+            <el-row>
+               <el-col :span="24">
+                  <el-form-item label="涓婄骇鑿滃崟">
+                     <el-tree-select
+                        v-model="form.parentId"
+                        :data="menuOptions"
+                        :props="{ value: 'menuId', label: 'menuName', children: 'children' }"
+                        value-key="menuId"
+                        placeholder="閫夋嫨涓婄骇鑿滃崟"
+                        check-strictly
+                     />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="鑿滃崟绫诲瀷" prop="menuType">
+                     <el-radio-group v-model="form.menuType">
+                        <el-radio label="M">鐩綍</el-radio>
+                        <el-radio label="C">鑿滃崟</el-radio>
+                        <el-radio label="F">鎸夐挳</el-radio>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="24" v-if="form.menuType != 'F'">
+                  <el-form-item label="鑿滃崟鍥炬爣" prop="icon">
+                     <el-popover
+                        placement="bottom-start"
+                        :width="540"
+                        trigger="click"
+                     >
+                        <template #reference>
+                           <el-input v-model="form.icon" placeholder="鐐瑰嚮閫夋嫨鍥炬爣" @blur="showSelectIcon" readonly>
+                              <template #prefix>
+                                 <svg-icon
+                                    v-if="form.icon"
+                                    :icon-class="form.icon"
+                                    class="el-input__icon"
+                                    style="height: 32px;width: 16px;"
+                                 />
+                                 <el-icon v-else style="height: 32px;width: 16px;"><search /></el-icon>
+                              </template>
+                           </el-input>
+                        </template>
+                        <icon-select ref="iconSelectRef" @selected="selected" :active-icon="form.icon" />
+                     </el-popover>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="鑿滃崟鍚嶇О" prop="menuName">
+                     <el-input v-model="form.menuName" placeholder="璇疯緭鍏ヨ彍鍗曞悕绉�" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="鏄剧ず鎺掑簭" prop="orderNum">
+                     <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12" v-if="form.menuType != 'F'">
+                  <el-form-item>
+                     <template #label>
+                        <span>
+                           <el-tooltip content="閫夋嫨鏄閾惧垯璺敱鍦板潃闇�瑕佷互`http(s)://`寮�澶�" placement="top">
+                              <el-icon><question-filled /></el-icon>
+                           </el-tooltip>鏄惁澶栭摼
+                        </span>
+                     </template>
+                     <el-radio-group v-model="form.isFrame">
+                        <el-radio label="0">鏄�</el-radio>
+                        <el-radio label="1">鍚�</el-radio>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12" v-if="form.menuType != 'F'">
+                  <el-form-item prop="path">
+                     <template #label>
+                        <span>
+                           <el-tooltip content="璁块棶鐨勮矾鐢卞湴鍧�锛屽锛歚user`锛屽澶栫綉鍦板潃闇�鍐呴摼璁块棶鍒欎互`http(s)://`寮�澶�" placement="top">
+                              <el-icon><question-filled /></el-icon>
+                           </el-tooltip>
+                           璺敱鍦板潃
+                        </span>
+                     </template>
+                     <el-input v-model="form.path" placeholder="璇疯緭鍏ヨ矾鐢卞湴鍧�" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12" v-if="form.menuType == 'C'">
+                  <el-form-item prop="component">
+                     <template #label>
+                        <span>
+                           <el-tooltip content="璁块棶鐨勭粍浠惰矾寰勶紝濡傦細`system/user/index`锛岄粯璁ゅ湪`views`鐩綍涓�" placement="top">
+                              <el-icon><question-filled /></el-icon>
+                           </el-tooltip>
+                           缁勪欢璺緞
+                        </span>
+                     </template>
+                     <el-input v-model="form.component" placeholder="璇疯緭鍏ョ粍浠惰矾寰�" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12" v-if="form.menuType != 'M'">
+                  <el-form-item>
+                     <el-input v-model="form.perms" placeholder="璇疯緭鍏ユ潈闄愭爣璇�" maxlength="100" />
+                     <template #label>
+                        <span>
+                           <el-tooltip content="鎺у埗鍣ㄤ腑瀹氫箟鐨勬潈闄愬瓧绗︼紝濡傦細@PreAuthorize(`@ss.hasPermi('system:user:list')`)" placement="top">
+                              <el-icon><question-filled /></el-icon>
+                           </el-tooltip>
+                           鏉冮檺瀛楃
+                        </span>
+                     </template>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12" v-if="form.menuType == 'C'">
+                  <el-form-item>
+                     <el-input v-model="form.query" placeholder="璇疯緭鍏ヨ矾鐢卞弬鏁�" maxlength="255" />
+                     <template #label>
+                        <span>
+                           <el-tooltip content='璁块棶璺敱鐨勯粯璁や紶閫掑弬鏁帮紝濡傦細`{"id": 1, "name": "ry"}`' placement="top">
+                              <el-icon><question-filled /></el-icon>
+                           </el-tooltip>
+                           璺敱鍙傛暟
+                        </span>
+                     </template>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12" v-if="form.menuType == 'C'">
+                  <el-form-item>
+                     <template #label>
+                        <span>
+                           <el-tooltip content="閫夋嫨鏄垯浼氳`keep-alive`缂撳瓨锛岄渶瑕佸尮閰嶇粍浠剁殑`name`鍜屽湴鍧�淇濇寔涓�鑷�" placement="top">
+                              <el-icon><question-filled /></el-icon>
+                           </el-tooltip>
+                           鏄惁缂撳瓨
+                        </span>
+                     </template>
+                     <el-radio-group v-model="form.isCache">
+                        <el-radio label="0">缂撳瓨</el-radio>
+                        <el-radio label="1">涓嶇紦瀛�</el-radio>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12" v-if="form.menuType != 'F'">
+                  <el-form-item>
+                     <template #label>
+                        <span>
+                           <el-tooltip content="閫夋嫨闅愯棌鍒欒矾鐢卞皢涓嶄細鍑虹幇鍦ㄤ晶杈规爮锛屼絾浠嶇劧鍙互璁块棶" placement="top">
+                              <el-icon><question-filled /></el-icon>
+                           </el-tooltip>
+                           鏄剧ず鐘舵��
+                        </span>
+                     </template>
+                     <el-radio-group v-model="form.visible">
+                        <el-radio
+                           v-for="dict in sys_show_hide"
+                           :key="dict.value"
+                           :label="dict.value"
+                        >{{ dict.label }}</el-radio>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item>
+                     <template #label>
+                        <span>
+                           <el-tooltip content="閫夋嫨鍋滅敤鍒欒矾鐢卞皢涓嶄細鍑虹幇鍦ㄤ晶杈规爮锛屼篃涓嶈兘琚闂�" placement="top">
+                              <el-icon><question-filled /></el-icon>
+                           </el-tooltip>
+                           鑿滃崟鐘舵��
+                        </span>
+                     </template>
+                     <el-radio-group v-model="form.status">
+                        <el-radio
+                           v-for="dict in sys_normal_disable"
+                           :key="dict.value"
+                           :label="dict.value"
+                        >{{ dict.label }}</el-radio>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+            </el-row>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+               <el-button @click="cancel">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="Menu">
+import { addMenu, delMenu, getMenu, listMenu, updateMenu } from "@/api/system/menu";
+import SvgIcon from "@/components/SvgIcon";
+import IconSelect from "@/components/IconSelect";
+
+const { proxy } = getCurrentInstance();
+const { sys_show_hide, sys_normal_disable } = proxy.useDict("sys_show_hide", "sys_normal_disable");
+
+const menuList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const title = ref("");
+const menuOptions = ref([]);
+const isExpandAll = ref(false);
+const refreshTable = ref(true);
+const iconSelectRef = ref(null);
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    menuName: undefined,
+    visible: undefined
+  },
+  rules: {
+    menuName: [{ required: true, message: "鑿滃崟鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    orderNum: [{ required: true, message: "鑿滃崟椤哄簭涓嶈兘涓虹┖", trigger: "blur" }],
+    path: [{ required: true, message: "璺敱鍦板潃涓嶈兘涓虹┖", trigger: "blur" }]
+  },
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ鑿滃崟鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listMenu(queryParams.value).then(response => {
+    menuList.value = proxy.handleTree(response.data, "menuId");
+    loading.value = false;
+  });
+}
+/** 鏌ヨ鑿滃崟涓嬫媺鏍戠粨鏋� */
+function getTreeselect() {
+  menuOptions.value = [];
+  listMenu().then(response => {
+    const menu = { menuId: 0, menuName: "涓荤被鐩�", children: [] };
+    menu.children = proxy.handleTree(response.data, "menuId");
+    menuOptions.value.push(menu);
+  });
+}
+/** 鍙栨秷鎸夐挳 */
+function cancel() {
+  open.value = false;
+  reset();
+}
+/** 琛ㄥ崟閲嶇疆 */
+function reset() {
+  form.value = {
+    menuId: undefined,
+    parentId: 0,
+    menuName: undefined,
+    icon: undefined,
+    menuType: "M",
+    orderNum: undefined,
+    isFrame: "1",
+    isCache: "0",
+    visible: "0",
+    status: "0"
+  };
+  proxy.resetForm("menuRef");
+}
+/** 灞曠ず涓嬫媺鍥炬爣 */
+function showSelectIcon() {
+  iconSelectRef.value.reset();
+}
+/** 閫夋嫨鍥炬爣 */
+function selected(name) {
+  form.value.icon = name;
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd(row) {
+  reset();
+  getTreeselect();
+  if (row != null && row.menuId) {
+    form.value.parentId = row.menuId;
+  } else {
+    form.value.parentId = 0;
+  }
+  open.value = true;
+  title.value = "娣诲姞鑿滃崟";
+}
+/** 灞曞紑/鎶樺彔鎿嶄綔 */
+function toggleExpandAll() {
+  refreshTable.value = false;
+  isExpandAll.value = !isExpandAll.value;
+  nextTick(() => {
+    refreshTable.value = true;
+  });
+}
+/** 淇敼鎸夐挳鎿嶄綔 */
+async function handleUpdate(row) {
+  reset();
+  await getTreeselect();
+  getMenu(row.menuId).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "淇敼鑿滃崟";
+  });
+}
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["menuRef"].validate(valid => {
+    if (valid) {
+      if (form.value.menuId != undefined) {
+        updateMenu(form.value).then(response => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addMenu(form.value).then(response => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎鍚嶇О涓�"' + row.menuName + '"鐨勬暟鎹」?').then(function() {
+    return delMenu(row.menuId);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/system/name/name.vue b/zhitan-vue/src/views/system/name/name.vue
new file mode 100644
index 0000000..803a941
--- /dev/null
+++ b/zhitan-vue/src/views/system/name/name.vue
@@ -0,0 +1,102 @@
+<template>
+  <div class="app-container">
+    <el-form :model="form" label-width="80px">
+      <el-row class="mb20 mt20">
+        <el-col :offset="1" :span="18">
+          <el-form-item label="绯荤粺鍚嶇О" prop="systemName">
+            <el-input v-model="form.systemName" placeholder="璇疯緭鍏ョ郴缁熷悕绉�" maxlength="64" show-word-limit />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row class="mb20 mt20">
+        <el-col :offset="1" :span="18">
+          <el-form-item label="鐧诲綍logo" prop="homeLogo">
+            <ImageUpload v-model="form.homeLogo" :fileType="['png']" :limit="1" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row class="mb20 mt20">
+        <el-col :offset="1" :span="18">
+          <el-form-item label="鍚庡彴logo" prop="leftLogo">
+            <ImageUpload v-model="form.leftLogo" :fileType="['png']" :limit="1" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row class="mb20 mt20">
+        <el-col :offset="1" :span="18">
+          <el-form-item label="copyRight" prop="copyRight">
+            <el-input v-model="form.copyRight" placeholder="璇疯緭鍏�" type="textarea" maxlength="200" :rows="6"
+              show-word-limit />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row class="mb20 mt20">
+        <el-col :offset="2" :span="18">
+          <el-button type="primary" @click="handleSave">淇濆瓨</el-button>
+        </el-col>
+      </el-row>
+    </el-form>
+  </div>
+</template>
+<script setup>
+import Cookies from 'js-cookie'
+import { systemName, systemNameSave } from '@/api/system/name'
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+const { proxy } = getCurrentInstance()
+let form = ref({
+  systemName: null,
+  copyRight: null,
+  leftLogo: null,
+  homeLogo: null,
+})
+
+function getSystemName() {
+  systemName().then(res => {
+    if (res.code == 200) {
+      form.value = res.data
+      sessionStorage.setItem('SystemInfo', JSON.stringify(res.data))
+    }
+  })
+}
+getSystemName()
+function handleSave() {
+
+  if (!form.value.leftLogo.includes(baseUrl)) {
+    form.value.leftLogo = baseUrl + form.value.leftLogo
+  }
+
+  if (!form.value.homeLogo.includes(baseUrl)) {
+    form.value.homeLogo = baseUrl + form.value.homeLogo
+  }
+
+  systemNameSave(form.value).then(res => {
+    if (res.code == 200) {
+      proxy.$modal.msgSuccess(res.msg);
+      getSystemName()
+    }
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+:deep .avatar-uploader .el-upload {
+  border: 1px dashed var(--el-border-color);
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  transition: var(--el-transition-duration-fast);
+}
+
+:deep .avatar-uploader .el-upload:hover {
+  border-color: var(--el-color-primary);
+}
+
+:deep .el-icon.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  text-align: center;
+}
+</style>
diff --git a/zhitan-vue/src/views/system/notice/index.vue b/zhitan-vue/src/views/system/notice/index.vue
new file mode 100644
index 0000000..c26b8c4
--- /dev/null
+++ b/zhitan-vue/src/views/system/notice/index.vue
@@ -0,0 +1,283 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+         <el-form-item label="鍏憡鏍囬" prop="noticeTitle">
+            <el-input
+               v-model="queryParams.noticeTitle"
+               placeholder="璇疯緭鍏ュ叕鍛婃爣棰�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鎿嶄綔浜哄憳" prop="createBy">
+            <el-input
+               v-model="queryParams.createBy"
+               placeholder="璇疯緭鍏ユ搷浣滀汉鍛�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="绫诲瀷" prop="noticeType">
+            <el-select v-model="queryParams.noticeType" placeholder="鍏憡绫诲瀷" clearable style="width: 200px">
+               <el-option
+                  v-for="dict in sys_notice_type"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Plus"
+               @click="handleAdd"
+               v-hasPermi="['system:notice:add']"
+            >鏂板</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="success"
+               plain
+               icon="Edit"
+               :disabled="single"
+               @click="handleUpdate"
+               v-hasPermi="['system:notice:edit']"
+            >淇敼</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               :disabled="multiple"
+               @click="handleDelete"
+               v-hasPermi="['system:notice:remove']"
+            >鍒犻櫎</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange">
+         <el-table-column type="selection" width="55" align="center" />
+         <el-table-column label="搴忓彿" align="center" prop="noticeId" width="100" />
+         <el-table-column
+            label="鍏憡鏍囬"
+            align="center"
+            prop="noticeTitle"
+            :show-overflow-tooltip="true"
+         />
+         <el-table-column label="鍏憡绫诲瀷" align="center" prop="noticeType" width="100">
+            <template #default="scope">
+               <dict-tag :options="sys_notice_type" :value="scope.row.noticeType" />
+            </template>
+         </el-table-column>
+         <el-table-column label="鐘舵��" align="center" prop="status" width="100">
+            <template #default="scope">
+               <dict-tag :options="sys_notice_status" :value="scope.row.status" />
+            </template>
+         </el-table-column>
+         <el-table-column label="鍒涘缓鑰�" align="center" prop="createBy" width="100" />
+         <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="100">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:notice:edit']">淇敼</el-button>
+               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:notice:remove']" >鍒犻櫎</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+
+      <!-- 娣诲姞鎴栦慨鏀瑰叕鍛婂璇濇 -->
+      <el-dialog :title="title" v-model="open" width="780px" append-to-body>
+         <el-form ref="noticeRef" :model="form" :rules="rules" label-width="80px">
+            <el-row>
+               <el-col :span="12">
+                  <el-form-item label="鍏憡鏍囬" prop="noticeTitle">
+                     <el-input v-model="form.noticeTitle" placeholder="璇疯緭鍏ュ叕鍛婃爣棰�" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="鍏憡绫诲瀷" prop="noticeType">
+                     <el-select v-model="form.noticeType" placeholder="璇烽�夋嫨">
+                        <el-option
+                           v-for="dict in sys_notice_type"
+                           :key="dict.value"
+                           :label="dict.label"
+                           :value="dict.value"
+                        ></el-option>
+                     </el-select>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="鐘舵��">
+                     <el-radio-group v-model="form.status">
+                        <el-radio
+                           v-for="dict in sys_notice_status"
+                           :key="dict.value"
+                           :label="dict.value"
+                        >{{ dict.label }}</el-radio>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="24">
+                  <el-form-item label="鍐呭">
+                    <editor v-model="form.noticeContent" :min-height="192"/>
+                  </el-form-item>
+               </el-col>
+            </el-row>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+               <el-button @click="cancel">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="Notice">
+import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice";
+
+const { proxy } = getCurrentInstance();
+const { sys_notice_status, sys_notice_type } = proxy.useDict("sys_notice_status", "sys_notice_type");
+
+const noticeList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    noticeTitle: undefined,
+    createBy: undefined,
+    status: undefined
+  },
+  rules: {
+    noticeTitle: [{ required: true, message: "鍏憡鏍囬涓嶈兘涓虹┖", trigger: "blur" }],
+    noticeType: [{ required: true, message: "鍏憡绫诲瀷涓嶈兘涓虹┖", trigger: "change" }]
+  },
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ鍏憡鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listNotice(queryParams.value).then(response => {
+    noticeList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 鍙栨秷鎸夐挳 */
+function cancel() {
+  open.value = false;
+  reset();
+}
+/** 琛ㄥ崟閲嶇疆 */
+function reset() {
+  form.value = {
+    noticeId: undefined,
+    noticeTitle: undefined,
+    noticeType: undefined,
+    noticeContent: undefined,
+    status: "0"
+  };
+  proxy.resetForm("noticeRef");
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+/** 澶氶�夋閫変腑鏁版嵁 */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.noticeId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "娣诲姞鍏憡";
+}
+/**淇敼鎸夐挳鎿嶄綔 */
+function handleUpdate(row) {
+  reset();
+  const noticeId = row.noticeId || ids.value;
+  getNotice(noticeId).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "淇敼鍏憡";
+  });
+}
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["noticeRef"].validate(valid => {
+    if (valid) {
+      if (form.value.noticeId != undefined) {
+        updateNotice(form.value).then(response => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addNotice(form.value).then(response => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const noticeIds = row.noticeId || ids.value
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎鍏憡缂栧彿涓�"' + noticeIds + '"鐨勬暟鎹」锛�').then(function() {
+    return delNotice(noticeIds);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/system/post/index.vue b/zhitan-vue/src/views/system/post/index.vue
new file mode 100644
index 0000000..6fd1b64
--- /dev/null
+++ b/zhitan-vue/src/views/system/post/index.vue
@@ -0,0 +1,277 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+         <el-form-item label="宀椾綅缂栫爜" prop="postCode">
+            <el-input
+               v-model="queryParams.postCode"
+               placeholder="璇疯緭鍏ュ矖浣嶇紪鐮�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="宀椾綅鍚嶇О" prop="postName">
+            <el-input
+               v-model="queryParams.postName"
+               placeholder="璇疯緭鍏ュ矖浣嶅悕绉�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鐘舵��" prop="status">
+            <el-select v-model="queryParams.status" placeholder="宀椾綅鐘舵��" clearable style="width: 200px">
+               <el-option
+                  v-for="dict in sys_normal_disable"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Plus"
+               @click="handleAdd"
+               v-hasPermi="['system:post:add']"
+            >鏂板</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="success"
+               plain
+               icon="Edit"
+               :disabled="single"
+               @click="handleUpdate"
+               v-hasPermi="['system:post:edit']"
+            >淇敼</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               :disabled="multiple"
+               @click="handleDelete"
+               v-hasPermi="['system:post:remove']"
+            >鍒犻櫎</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="warning"
+               plain
+               icon="Download"
+               @click="handleExport"
+               v-hasPermi="['system:post:export']"
+            >瀵煎嚭</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
+         <el-table-column type="selection" width="55" align="center" />
+         <el-table-column label="宀椾綅缂栧彿" align="center" prop="postId" />
+         <el-table-column label="宀椾綅缂栫爜" align="center" prop="postCode" />
+         <el-table-column label="宀椾綅鍚嶇О" align="center" prop="postName" />
+         <el-table-column label="宀椾綅鎺掑簭" align="center" prop="postSort" />
+         <el-table-column label="鐘舵��" align="center" prop="status">
+            <template #default="scope">
+               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+            </template>
+         </el-table-column>
+         <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="180">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" width="180" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:post:edit']">淇敼</el-button>
+               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:post:remove']">鍒犻櫎</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+
+      <!-- 娣诲姞鎴栦慨鏀瑰矖浣嶅璇濇 -->
+      <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+         <el-form ref="postRef" :model="form" :rules="rules" label-width="80px">
+            <el-form-item label="宀椾綅鍚嶇О" prop="postName">
+               <el-input v-model="form.postName" placeholder="璇疯緭鍏ュ矖浣嶅悕绉�" />
+            </el-form-item>
+            <el-form-item label="宀椾綅缂栫爜" prop="postCode">
+               <el-input v-model="form.postCode" placeholder="璇疯緭鍏ョ紪鐮佸悕绉�" />
+            </el-form-item>
+            <el-form-item label="宀椾綅椤哄簭" prop="postSort">
+               <el-input-number v-model="form.postSort" controls-position="right" :min="0" />
+            </el-form-item>
+            <el-form-item label="宀椾綅鐘舵��" prop="status">
+               <el-radio-group v-model="form.status">
+                  <el-radio
+                     v-for="dict in sys_normal_disable"
+                     :key="dict.value"
+                     :label="dict.value"
+                  >{{ dict.label }}</el-radio>
+               </el-radio-group>
+            </el-form-item>
+            <el-form-item label="澶囨敞" prop="remark">
+               <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ唴瀹�" />
+            </el-form-item>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+               <el-button @click="cancel">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="Post">
+import { listPost, addPost, delPost, getPost, updatePost } from "@/api/system/post";
+
+const { proxy } = getCurrentInstance();
+const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
+
+const postList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    postCode: undefined,
+    postName: undefined,
+    status: undefined
+  },
+  rules: {
+    postName: [{ required: true, message: "宀椾綅鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    postCode: [{ required: true, message: "宀椾綅缂栫爜涓嶈兘涓虹┖", trigger: "blur" }],
+    postSort: [{ required: true, message: "宀椾綅椤哄簭涓嶈兘涓虹┖", trigger: "blur" }],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ宀椾綅鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listPost(queryParams.value).then(response => {
+    postList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 鍙栨秷鎸夐挳 */
+function cancel() {
+  open.value = false;
+  reset();
+}
+/** 琛ㄥ崟閲嶇疆 */
+function reset() {
+  form.value = {
+    postId: undefined,
+    postCode: undefined,
+    postName: undefined,
+    postSort: 0,
+    status: "0",
+    remark: undefined
+  };
+  proxy.resetForm("postRef");
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+/** 澶氶�夋閫変腑鏁版嵁 */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.postId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "娣诲姞宀椾綅";
+}
+/** 淇敼鎸夐挳鎿嶄綔 */
+function handleUpdate(row) {
+  reset();
+  const postId = row.postId || ids.value;
+  getPost(postId).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "淇敼宀椾綅";
+  });
+}
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["postRef"].validate(valid => {
+    if (valid) {
+      if (form.value.postId != undefined) {
+        updatePost(form.value).then(response => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addPost(form.value).then(response => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const postIds = row.postId || ids.value;
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎宀椾綅缂栧彿涓�"' + postIds + '"鐨勬暟鎹」锛�').then(function() {
+    return delPost(postIds);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download("system/post/export", {
+    ...queryParams.value
+  }, `post_${new Date().getTime()}.xlsx`);
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/system/role/authUser.vue b/zhitan-vue/src/views/system/role/authUser.vue
new file mode 100644
index 0000000..66b5f5e
--- /dev/null
+++ b/zhitan-vue/src/views/system/role/authUser.vue
@@ -0,0 +1,172 @@
+
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true">
+         <el-form-item label="鐢ㄦ埛鍚嶇О" prop="userName">
+            <el-input
+               v-model="queryParams.userName"
+               placeholder="璇疯緭鍏ョ敤鎴峰悕绉�"
+               clearable
+               style="width: 240px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鎵嬫満鍙风爜" prop="phonenumber">
+            <el-input
+               v-model="queryParams.phonenumber"
+               placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�"
+               clearable
+               style="width: 240px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Plus"
+               @click="openSelectUser"
+               v-hasPermi="['system:role:add']"
+            >娣诲姞鐢ㄦ埛</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="CircleClose"
+               :disabled="multiple"
+               @click="cancelAuthUserAll"
+               v-hasPermi="['system:role:remove']"
+            >鎵归噺鍙栨秷鎺堟潈</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button 
+               type="warning" 
+               plain 
+               icon="Close"
+               @click="handleClose"
+            >鍏抽棴</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
+         <el-table-column type="selection" width="55" align="center" />
+         <el-table-column label="鐢ㄦ埛鍚嶇О" prop="userName" :show-overflow-tooltip="true" />
+         <el-table-column label="鐢ㄦ埛鏄电О" prop="nickName" :show-overflow-tooltip="true" />
+         <el-table-column label="閭" prop="email" :show-overflow-tooltip="true" />
+         <el-table-column label="鎵嬫満" prop="phonenumber" :show-overflow-tooltip="true" />
+         <el-table-column label="鐘舵��" align="center" prop="status">
+            <template #default="scope">
+               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+            </template>
+         </el-table-column>
+         <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="180">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+               <el-button link type="primary" icon="CircleClose" @click="cancelAuthUser(scope.row)" v-hasPermi="['system:role:remove']">鍙栨秷鎺堟潈</el-button>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+      <select-user ref="selectRef" :roleId="queryParams.roleId" @ok="handleQuery" />
+   </div>
+</template>
+
+<script setup name="AuthUser">
+import selectUser from "./selectUser";
+import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role";
+
+const route = useRoute();
+const { proxy } = getCurrentInstance();
+const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
+
+const userList = ref([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const userIds = ref([]);
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  roleId: route.params.roleId,
+  userName: undefined,
+  phonenumber: undefined,
+});
+
+/** 鏌ヨ鎺堟潈鐢ㄦ埛鍒楄〃 */
+function getList() {
+  loading.value = true;
+  allocatedUserList(queryParams).then(response => {
+    userList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+// 杩斿洖鎸夐挳
+function handleClose() {
+  const obj = { path: "/system/role" };
+  proxy.$tab.closeOpenPage(obj);
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+// 澶氶�夋閫変腑鏁版嵁
+function handleSelectionChange(selection) {
+  userIds.value = selection.map(item => item.userId);
+  multiple.value = !selection.length;
+}
+/** 鎵撳紑鎺堟潈鐢ㄦ埛琛ㄥ脊绐� */
+function openSelectUser() {
+  proxy.$refs["selectRef"].show();
+}
+/** 鍙栨秷鎺堟潈鎸夐挳鎿嶄綔 */
+function cancelAuthUser(row) {
+  proxy.$modal.confirm('纭瑕佸彇娑堣鐢ㄦ埛"' + row.userName + '"瑙掕壊鍚楋紵').then(function () {
+    return authUserCancel({ userId: row.userId, roleId: queryParams.roleId });
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍙栨秷鎺堟潈鎴愬姛");
+  }).catch(() => {});
+}
+/** 鎵归噺鍙栨秷鎺堟潈鎸夐挳鎿嶄綔 */
+function cancelAuthUserAll(row) {
+  const roleId = queryParams.roleId;
+  const uIds = userIds.value.join(",");
+  proxy.$modal.confirm("鏄惁鍙栨秷閫変腑鐢ㄦ埛鎺堟潈鏁版嵁椤�?").then(function () {
+    return authUserCancelAll({ roleId: roleId, userIds: uIds });
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍙栨秷鎺堟潈鎴愬姛");
+  }).catch(() => {});
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/system/role/index.vue b/zhitan-vue/src/views/system/role/index.vue
new file mode 100644
index 0000000..07b083c
--- /dev/null
+++ b/zhitan-vue/src/views/system/role/index.vue
@@ -0,0 +1,559 @@
+<template>
+   <div class="app-container">
+      <el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true" label-width="80px">
+         <el-form-item label="瑙掕壊鍚嶇О" prop="roleName">
+            <el-input
+               v-model="queryParams.roleName"
+               placeholder="璇疯緭鍏ヨ鑹插悕绉�"
+               clearable
+               style="width: 240px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鏉冮檺瀛楃" prop="roleKey">
+            <el-input
+               v-model="queryParams.roleKey"
+               placeholder="璇疯緭鍏ユ潈闄愬瓧绗�"
+               clearable
+               style="width: 240px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鐘舵��" prop="status">
+            <el-select
+               v-model="queryParams.status"
+               placeholder="瑙掕壊鐘舵��"
+               clearable
+               style="width: 240px"
+            >
+               <el-option
+                  v-for="dict in sys_normal_disable"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+               />
+            </el-select>
+         </el-form-item>
+         <el-form-item label="鍒涘缓鏃堕棿" style="width: 308px">
+            <el-date-picker
+               v-model="dateRange"
+               value-format="YYYY-MM-DD"
+               type="daterange"
+               range-separator="-"
+               start-placeholder="寮�濮嬫棩鏈�"
+               end-placeholder="缁撴潫鏃ユ湡"
+            ></el-date-picker>
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+      <el-row :gutter="10" class="mb8">
+         <el-col :span="1.5">
+            <el-button
+               type="primary"
+               plain
+               icon="Plus"
+               @click="handleAdd"
+               v-hasPermi="['system:role:add']"
+            >鏂板</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="success"
+               plain
+               icon="Edit"
+               :disabled="single"
+               @click="handleUpdate"
+               v-hasPermi="['system:role:edit']"
+            >淇敼</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="danger"
+               plain
+               icon="Delete"
+               :disabled="multiple"
+               @click="handleDelete"
+               v-hasPermi="['system:role:remove']"
+            >鍒犻櫎</el-button>
+         </el-col>
+         <el-col :span="1.5">
+            <el-button
+               type="warning"
+               plain
+               icon="Download"
+               @click="handleExport"
+               v-hasPermi="['system:role:export']"
+            >瀵煎嚭</el-button>
+         </el-col>
+         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+
+      <!-- 琛ㄦ牸鏁版嵁 -->
+      <el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
+         <el-table-column type="selection" width="55" align="center" />
+         <el-table-column label="瑙掕壊缂栧彿" prop="roleId" width="120" />
+         <el-table-column label="瑙掕壊鍚嶇О" prop="roleName" :show-overflow-tooltip="true" width="150" />
+         <el-table-column label="鏉冮檺瀛楃" prop="roleKey" :show-overflow-tooltip="true" width="150" />
+         <el-table-column label="鏄剧ず椤哄簭" prop="roleSort" width="100" />
+         <el-table-column label="鐘舵��" align="center" width="100">
+            <template #default="scope">
+               <el-switch
+                  v-model="scope.row.status"
+                  active-value="0"
+                  inactive-value="1"
+                  @change="handleStatusChange(scope.row)"
+               ></el-switch>
+            </template>
+         </el-table-column>
+         <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column label="鎿嶄綔" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+              <el-tooltip content="淇敼" placement="top" v-if="scope.row.roleId !== 1">
+                <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
+              </el-tooltip>
+              <el-tooltip content="鍒犻櫎" placement="top" v-if="scope.row.roleId !== 1">
+                <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']"></el-button>
+              </el-tooltip>
+              <el-tooltip content="鏁版嵁鏉冮檺" placement="top" v-if="scope.row.roleId !== 1">
+                <el-button link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
+              </el-tooltip>
+              <el-tooltip content="鍒嗛厤鐢ㄦ埛" placement="top" v-if="scope.row.roleId !== 1">
+                <el-button link type="primary" icon="User" @click="handleAuthUser(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
+              </el-tooltip>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination
+         v-show="total > 0"
+         :total="total"
+         v-model:page="queryParams.pageNum"
+         v-model:limit="queryParams.pageSize"
+         @pagination="getList"
+      />
+
+      <!-- 娣诲姞鎴栦慨鏀硅鑹查厤缃璇濇 -->
+      <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+         <el-form ref="roleRef" :model="form" :rules="rules" label-width="110px">
+            <el-form-item label="瑙掕壊鍚嶇О" prop="roleName">
+               <el-input v-model="form.roleName" placeholder="璇疯緭鍏ヨ鑹插悕绉�" />
+            </el-form-item>
+            <el-form-item prop="roleKey">
+               <template #label>
+                  <span>
+                     <el-tooltip content="鎺у埗鍣ㄤ腑瀹氫箟鐨勬潈闄愬瓧绗︼紝濡傦細@PreAuthorize(`@ss.hasRole('admin')`)" placement="top">
+                        <el-icon><question-filled /></el-icon>
+                     </el-tooltip>
+                     鏉冮檺瀛楃
+                  </span>
+               </template>
+               <el-input v-model="form.roleKey" placeholder="璇疯緭鍏ユ潈闄愬瓧绗�" />
+            </el-form-item>
+            <el-form-item label="瑙掕壊椤哄簭" prop="roleSort">
+               <el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
+            </el-form-item>
+            <el-form-item label="鐘舵��">
+               <el-radio-group v-model="form.status">
+                  <el-radio
+                     v-for="dict in sys_normal_disable"
+                     :key="dict.value"
+                     :label="dict.value"
+                  >{{ dict.label }}</el-radio>
+               </el-radio-group>
+            </el-form-item>
+            <el-form-item label="鑿滃崟鏉冮檺">
+               <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">灞曞紑/鎶樺彔</el-checkbox>
+               <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">鍏ㄩ��/鍏ㄤ笉閫�</el-checkbox>
+               <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">鐖跺瓙鑱斿姩</el-checkbox>
+               <el-tree
+                  class="tree-border"
+                  :data="menuOptions"
+                  show-checkbox
+                  ref="menuRef"
+                  node-key="id"
+                  :check-strictly="!form.menuCheckStrictly"
+                  empty-text="鍔犺浇涓紝璇风◢鍊�"
+                  :props="{ label: 'label', children: 'children' }"
+               ></el-tree>
+            </el-form-item>
+            <el-form-item label="澶囨敞">
+               <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ唴瀹�"></el-input>
+            </el-form-item>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+               <el-button @click="cancel">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+
+      <!-- 鍒嗛厤瑙掕壊鏁版嵁鏉冮檺瀵硅瘽妗� -->
+      <el-dialog :title="title" v-model="openDataScope" width="500px" append-to-body>
+         <el-form :model="form" label-width="100px">
+            <el-form-item label="瑙掕壊鍚嶇О">
+               <el-input v-model="form.roleName" :disabled="true" />
+            </el-form-item>
+            <el-form-item label="鏉冮檺瀛楃">
+               <el-input v-model="form.roleKey" :disabled="true" />
+            </el-form-item>
+            <el-form-item label="鏉冮檺鑼冨洿">
+               <el-select v-model="form.dataScope" @change="dataScopeSelectChange">
+                  <el-option
+                     v-for="item in dataScopeOptions"
+                     :key="item.value"
+                     :label="item.label"
+                     :value="item.value"
+                  ></el-option>
+               </el-select>
+            </el-form-item>
+            <el-form-item label="鏁版嵁鏉冮檺" v-show="form.dataScope == 2">
+               <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">灞曞紑/鎶樺彔</el-checkbox>
+               <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">鍏ㄩ��/鍏ㄤ笉閫�</el-checkbox>
+               <el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">鐖跺瓙鑱斿姩</el-checkbox>
+               <el-tree
+                  class="tree-border"
+                  :data="deptOptions"
+                  show-checkbox
+                  default-expand-all
+                  ref="deptRef"
+                  node-key="id"
+                  :check-strictly="!form.deptCheckStrictly"
+                  empty-text="鍔犺浇涓紝璇风◢鍊�"
+                  :props="{ label: 'label', children: 'children' }"
+               ></el-tree>
+            </el-form-item>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitDataScope">纭� 瀹�</el-button>
+               <el-button @click="cancelDataScope">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="Role">
+import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from "@/api/system/role";
+import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu";
+
+const router = useRouter();
+const { proxy } = getCurrentInstance();
+const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
+
+const roleList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+const dateRange = ref([]);
+const menuOptions = ref([]);
+const menuExpand = ref(false);
+const menuNodeAll = ref(false);
+const deptExpand = ref(true);
+const deptNodeAll = ref(false);
+const deptOptions = ref([]);
+const openDataScope = ref(false);
+const menuRef = ref(null);
+const deptRef = ref(null);
+
+/** 鏁版嵁鑼冨洿閫夐」*/
+const dataScopeOptions = ref([
+  { value: "1", label: "鍏ㄩ儴鏁版嵁鏉冮檺" },
+  { value: "2", label: "鑷畾鏁版嵁鏉冮檺" },
+  { value: "3", label: "鏈儴闂ㄦ暟鎹潈闄�" },
+  { value: "4", label: "鏈儴闂ㄥ強浠ヤ笅鏁版嵁鏉冮檺" },
+  { value: "5", label: "浠呮湰浜烘暟鎹潈闄�" }
+]);
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    roleName: undefined,
+    roleKey: undefined,
+    status: undefined
+  },
+  rules: {
+    roleName: [{ required: true, message: "瑙掕壊鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }],
+    roleKey: [{ required: true, message: "鏉冮檺瀛楃涓嶈兘涓虹┖", trigger: "blur" }],
+    roleSort: [{ required: true, message: "瑙掕壊椤哄簭涓嶈兘涓虹┖", trigger: "blur" }]
+  },
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 鏌ヨ瑙掕壊鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listRole(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
+    roleList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  dateRange.value = [];
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const roleIds = row.roleId || ids.value;
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎瑙掕壊缂栧彿涓�"' + roleIds + '"鐨勬暟鎹」?').then(function () {
+    return delRole(roleIds);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download("system/role/export", {
+    ...queryParams.value,
+  }, `role_${new Date().getTime()}.xlsx`);
+}
+/** 澶氶�夋閫変腑鏁版嵁 */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.roleId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+/** 瑙掕壊鐘舵�佷慨鏀� */
+function handleStatusChange(row) {
+  let text = row.status === "0" ? "鍚敤" : "鍋滅敤";
+  proxy.$modal.confirm('纭瑕�"' + text + '""' + row.roleName + '"瑙掕壊鍚�?').then(function () {
+    return changeRoleStatus(row.roleId, row.status);
+  }).then(() => {
+    proxy.$modal.msgSuccess(text + "鎴愬姛");
+  }).catch(function () {
+    row.status = row.status === "0" ? "1" : "0";
+  });
+}
+/** 鏇村鎿嶄綔 */
+function handleCommand(command, row) {
+  switch (command) {
+    case "handleDataScope":
+      handleDataScope(row);
+      break;
+    case "handleAuthUser":
+      handleAuthUser(row);
+      break;
+    default:
+      break;
+  }
+}
+/** 鍒嗛厤鐢ㄦ埛 */
+function handleAuthUser(row) {
+  router.push("/system/role-auth/user/" + row.roleId);
+}
+/** 鏌ヨ鑿滃崟鏍戠粨鏋� */
+function getMenuTreeselect() {
+  menuTreeselect().then(response => {
+    menuOptions.value = response.data;
+  });
+}
+/** 鎵�鏈夐儴闂ㄨ妭鐐规暟鎹� */
+function getDeptAllCheckedKeys() {
+  // 鐩墠琚�変腑鐨勯儴闂ㄨ妭鐐�
+  let checkedKeys = deptRef.value.getCheckedKeys();
+  // 鍗婇�変腑鐨勯儴闂ㄨ妭鐐�
+  let halfCheckedKeys = deptRef.value.getHalfCheckedKeys();
+  checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
+  return checkedKeys;
+}
+/** 閲嶇疆鏂板鐨勮〃鍗曚互鍙婂叾浠栨暟鎹�  */
+function reset() {
+  if (menuRef.value != undefined) {
+    menuRef.value.setCheckedKeys([]);
+  }
+  menuExpand.value = false;
+  menuNodeAll.value = false;
+  deptExpand.value = true;
+  deptNodeAll.value = false;
+  form.value = {
+    roleId: undefined,
+    roleName: undefined,
+    roleKey: undefined,
+    roleSort: 0,
+    status: "0",
+    menuIds: [],
+    deptIds: [],
+    menuCheckStrictly: true,
+    deptCheckStrictly: true,
+    remark: undefined
+  };
+  proxy.resetForm("roleRef");
+}
+/** 娣诲姞瑙掕壊 */
+function handleAdd() {
+  reset();
+  getMenuTreeselect();
+  open.value = true;
+  title.value = "娣诲姞瑙掕壊";
+}
+/** 淇敼瑙掕壊 */
+function handleUpdate(row) {
+  reset();
+  const roleId = row.roleId || ids.value;
+  const roleMenu = getRoleMenuTreeselect(roleId);
+  getRole(roleId).then(response => {
+    form.value = response.data;
+    form.value.roleSort = Number(form.value.roleSort);
+    open.value = true;
+    nextTick(() => {
+      roleMenu.then((res) => {
+        let checkedKeys = res.checkedKeys;
+        checkedKeys.forEach((v) => {
+          nextTick(() => {
+            menuRef.value.setChecked(v, true, false);
+          });
+        });
+      });
+    });
+    title.value = "淇敼瑙掕壊";
+  });
+}
+/** 鏍规嵁瑙掕壊ID鏌ヨ鑿滃崟鏍戠粨鏋� */
+function getRoleMenuTreeselect(roleId) {
+  return roleMenuTreeselect(roleId).then(response => {
+    menuOptions.value = response.menus;
+    return response;
+  });
+}
+/** 鏍规嵁瑙掕壊ID鏌ヨ閮ㄩ棬鏍戠粨鏋� */
+function getDeptTree(roleId) {
+  return deptTreeSelect(roleId).then(response => {
+    deptOptions.value = response.depts;
+    return response;
+  });
+}
+/** 鏍戞潈闄愶紙灞曞紑/鎶樺彔锛�*/
+function handleCheckedTreeExpand(value, type) {
+  if (type == "menu") {
+    let treeList = menuOptions.value;
+    for (let i = 0; i < treeList.length; i++) {
+      menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
+    }
+  } else if (type == "dept") {
+    let treeList = deptOptions.value;
+    for (let i = 0; i < treeList.length; i++) {
+      deptRef.value.store.nodesMap[treeList[i].id].expanded = value;
+    }
+  }
+}
+/** 鏍戞潈闄愶紙鍏ㄩ��/鍏ㄤ笉閫夛級 */
+function handleCheckedTreeNodeAll(value, type) {
+  if (type == "menu") {
+    menuRef.value.setCheckedNodes(value ? menuOptions.value : []);
+  } else if (type == "dept") {
+    deptRef.value.setCheckedNodes(value ? deptOptions.value : []);
+  }
+}
+/** 鏍戞潈闄愶紙鐖跺瓙鑱斿姩锛� */
+function handleCheckedTreeConnect(value, type) {
+  if (type == "menu") {
+    form.value.menuCheckStrictly = value ? true : false;
+  } else if (type == "dept") {
+    form.value.deptCheckStrictly = value ? true : false;
+  }
+}
+/** 鎵�鏈夎彍鍗曡妭鐐规暟鎹� */
+function getMenuAllCheckedKeys() {
+  // 鐩墠琚�変腑鐨勮彍鍗曡妭鐐�
+  let checkedKeys = menuRef.value.getCheckedKeys();
+  // 鍗婇�変腑鐨勮彍鍗曡妭鐐�
+  let halfCheckedKeys = menuRef.value.getHalfCheckedKeys();
+  checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
+  return checkedKeys;
+}
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["roleRef"].validate(valid => {
+    if (valid) {
+      if (form.value.roleId != undefined) {
+        form.value.menuIds = getMenuAllCheckedKeys();
+        updateRole(form.value).then(response => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        form.value.menuIds = getMenuAllCheckedKeys();
+        addRole(form.value).then(response => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+}
+/** 鍙栨秷鎸夐挳 */
+function cancel() {
+  open.value = false;
+  reset();
+}
+/** 閫夋嫨瑙掕壊鏉冮檺鑼冨洿瑙﹀彂 */
+function dataScopeSelectChange(value) {
+  if (value !== "2") {
+    deptRef.value.setCheckedKeys([]);
+  }
+}
+/** 鍒嗛厤鏁版嵁鏉冮檺鎿嶄綔 */
+function handleDataScope(row) {
+  reset();
+  const deptTreeSelect = getDeptTree(row.roleId);
+  getRole(row.roleId).then(response => {
+    form.value = response.data;
+    openDataScope.value = true;
+    nextTick(() => {
+      deptTreeSelect.then(res => {
+        nextTick(() => {
+          if (deptRef.value) {
+            deptRef.value.setCheckedKeys(res.checkedKeys);
+          }
+        });
+      });
+    });
+    title.value = "鍒嗛厤鏁版嵁鏉冮檺";
+  });
+}
+/** 鎻愪氦鎸夐挳锛堟暟鎹潈闄愶級 */
+function submitDataScope() {
+  if (form.value.roleId != undefined) {
+    form.value.deptIds = getDeptAllCheckedKeys();
+    dataScope(form.value).then(response => {
+      proxy.$modal.msgSuccess("淇敼鎴愬姛");
+      openDataScope.value = false;
+      getList();
+    });
+  }
+}
+/** 鍙栨秷鎸夐挳锛堟暟鎹潈闄愶級*/
+function cancelDataScope() {
+  openDataScope.value = false;
+  reset();
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/system/role/selectUser.vue b/zhitan-vue/src/views/system/role/selectUser.vue
new file mode 100644
index 0000000..9be1ec9
--- /dev/null
+++ b/zhitan-vue/src/views/system/role/selectUser.vue
@@ -0,0 +1,140 @@
+<template>
+   <!-- 鎺堟潈鐢ㄦ埛 -->
+   <el-dialog title="閫夋嫨鐢ㄦ埛" v-model="visible" width="800px" top="5vh" append-to-body>
+      <el-form :model="queryParams" ref="queryRef" :inline="true">
+         <el-form-item label="鐢ㄦ埛鍚嶇О" prop="userName">
+            <el-input
+               v-model="queryParams.userName"
+               placeholder="璇疯緭鍏ョ敤鎴峰悕绉�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item label="鎵嬫満鍙风爜" prop="phonenumber">
+            <el-input
+               v-model="queryParams.phonenumber"
+               placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�"
+               clearable
+               style="width: 200px"
+               @keyup.enter="handleQuery"
+            />
+         </el-form-item>
+         <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+            <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+         </el-form-item>
+      </el-form>
+      <el-row>
+         <el-table @row-click="clickRow" ref="refTable" :data="userList" @selection-change="handleSelectionChange" height="260px">
+            <el-table-column type="selection" width="55"></el-table-column>
+            <el-table-column label="鐢ㄦ埛鍚嶇О" prop="userName" :show-overflow-tooltip="true" />
+            <el-table-column label="鐢ㄦ埛鏄电О" prop="nickName" :show-overflow-tooltip="true" />
+            <el-table-column label="閭" prop="email" :show-overflow-tooltip="true" />
+            <el-table-column label="鎵嬫満" prop="phonenumber" :show-overflow-tooltip="true" />
+            <el-table-column label="鐘舵��" align="center" prop="status">
+               <template #default="scope">
+                  <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
+               </template>
+            </el-table-column>
+            <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="180">
+               <template #default="scope">
+                  <span>{{ parseTime(scope.row.createTime) }}</span>
+               </template>
+            </el-table-column>
+         </el-table>
+         <pagination
+            v-show="total > 0"
+            :total="total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+         />
+      </el-row>
+      <template #footer>
+         <div class="dialog-footer">
+            <el-button type="primary" @click="handleSelectUser">纭� 瀹�</el-button>
+            <el-button @click="visible = false">鍙� 娑�</el-button>
+         </div>
+      </template>
+   </el-dialog>
+</template>
+
+<script setup name="SelectUser">
+import { authUserSelectAll, unallocatedUserList } from "@/api/system/role";
+
+const props = defineProps({
+  roleId: {
+    type: [Number, String]
+  }
+});
+
+const { proxy } = getCurrentInstance();
+const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
+
+const userList = ref([]);
+const visible = ref(false);
+const total = ref(0);
+const userIds = ref([]);
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  roleId: undefined,
+  userName: undefined,
+  phonenumber: undefined
+});
+
+// 鏄剧ず寮规
+function show() {
+  queryParams.roleId = props.roleId;
+  getList();
+  visible.value = true;
+}
+/**閫夋嫨琛� */
+function clickRow(row) {
+  proxy.$refs["refTable"].toggleRowSelection(row);
+}
+// 澶氶�夋閫変腑鏁版嵁
+function handleSelectionChange(selection) {
+  userIds.value = selection.map(item => item.userId);
+}
+// 鏌ヨ琛ㄦ暟鎹�
+function getList() {
+  unallocatedUserList(queryParams).then(res => {
+    userList.value = res.rows;
+    total.value = res.total;
+  });
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+const emit = defineEmits(["ok"]);
+/** 閫夋嫨鎺堟潈鐢ㄦ埛鎿嶄綔 */
+function handleSelectUser() {
+  const roleId = queryParams.roleId;
+  const uIds = userIds.value.join(",");
+  if (uIds == "") {
+    proxy.$modal.msgError("璇烽�夋嫨瑕佸垎閰嶇殑鐢ㄦ埛");
+    return;
+  }
+  authUserSelectAll({ roleId: roleId, userIds: uIds }).then(res => {
+    proxy.$modal.msgSuccess(res.msg);
+    if (res.code === 200) {
+      visible.value = false;
+      emit("ok");
+    }
+  });
+}
+
+defineExpose({
+  show,
+});
+</script>
diff --git a/zhitan-vue/src/views/system/user/authRole.vue b/zhitan-vue/src/views/system/user/authRole.vue
new file mode 100644
index 0000000..0b5c14b
--- /dev/null
+++ b/zhitan-vue/src/views/system/user/authRole.vue
@@ -0,0 +1,112 @@
+<template>
+   <div class="app-container">
+      <h4 class="form-header h4">鍩烘湰淇℃伅</h4>
+      <el-form :model="form" label-width="80px">
+         <el-row>
+            <el-col :span="8" :offset="2">
+               <el-form-item label="鐢ㄦ埛鏄电О" prop="nickName">
+                  <el-input v-model="form.nickName" disabled />
+               </el-form-item>
+            </el-col>
+            <el-col :span="8" :offset="2">
+               <el-form-item label="鐧诲綍璐﹀彿" prop="userName">
+                  <el-input v-model="form.userName" disabled />
+               </el-form-item>
+            </el-col>
+         </el-row>
+      </el-form>
+
+      <h4 class="form-header h4">瑙掕壊淇℃伅</h4>
+      <el-table v-loading="loading" :row-key="getRowKey" @row-click="clickRow" ref="roleRef" @selection-change="handleSelectionChange" :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)">
+         <el-table-column label="搴忓彿" width="55" type="index" align="center">
+            <template #default="scope">
+               <span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
+            </template>
+         </el-table-column>
+         <el-table-column type="selection" :reserve-selection="true" width="55"></el-table-column>
+         <el-table-column label="瑙掕壊缂栧彿" align="center" prop="roleId" />
+         <el-table-column label="瑙掕壊鍚嶇О" align="center" prop="roleName" />
+         <el-table-column label="鏉冮檺瀛楃" align="center" prop="roleKey" />
+         <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="180">
+            <template #default="scope">
+               <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+         </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize" />
+
+      <el-form label-width="100px">
+         <div style="text-align: center;margin-left:-120px;margin-top:30px;">
+            <el-button type="primary" @click="submitForm()">鎻愪氦</el-button>
+            <el-button @click="close()">杩斿洖</el-button>
+         </div>
+      </el-form>
+   </div>
+</template>
+
+<script setup name="AuthRole">
+import { getAuthRole, updateAuthRole } from "@/api/system/user";
+
+const route = useRoute();
+const { proxy } = getCurrentInstance();
+
+const loading = ref(true);
+const total = ref(0);
+const pageNum = ref(1);
+const pageSize = ref(10);
+const roleIds = ref([]);
+const roles = ref([]);
+const form = ref({
+  nickName: undefined,
+  userName: undefined,
+  userId: undefined
+});
+
+/** 鍗曞嚮閫変腑琛屾暟鎹� */
+function clickRow(row) {
+  proxy.$refs["roleRef"].toggleRowSelection(row);
+};
+/** 澶氶�夋閫変腑鏁版嵁 */
+function handleSelectionChange(selection) {
+  roleIds.value = selection.map(item => item.roleId);
+};
+/** 淇濆瓨閫変腑鐨勬暟鎹紪鍙� */
+function getRowKey(row) {
+  return row.roleId;
+};
+/** 鍏抽棴鎸夐挳 */
+function close() {
+  const obj = { path: "/system/user" };
+  proxy.$tab.closeOpenPage(obj);
+};
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  const userId = form.value.userId;
+  const rIds = roleIds.value.join(",");
+  updateAuthRole({ userId: userId, roleIds: rIds }).then(response => {
+    proxy.$modal.msgSuccess("鎺堟潈鎴愬姛");
+    close();
+  });
+};
+
+(() => {
+  const userId = route.params && route.params.userId;
+  if (userId) {
+    loading.value = true;
+    getAuthRole(userId).then(response => {
+      form.value = response.user;
+      roles.value = response.roles;
+      total.value = roles.value.length;
+      nextTick(() => {
+        roles.value.forEach(row => {
+          if (row.flag) {
+            proxy.$refs["roleRef"].toggleRowSelection(row);
+          }
+        });
+      });
+      loading.value = false;
+    });
+  }
+})();
+</script>
diff --git a/zhitan-vue/src/views/system/user/index.vue b/zhitan-vue/src/views/system/user/index.vue
new file mode 100644
index 0000000..40681a8
--- /dev/null
+++ b/zhitan-vue/src/views/system/user/index.vue
@@ -0,0 +1,612 @@
+<template>
+   <div class="app-container">
+      <el-row :gutter="20">
+         <!--閮ㄩ棬鏁版嵁-->
+         <el-col :span="4" :xs="24">
+            <div class="head-container">
+               <el-input
+                  v-model="deptName"
+                  placeholder="璇疯緭鍏ラ儴闂ㄥ悕绉�"
+                  clearable
+                  prefix-icon="Search"
+                  style="margin-bottom: 20px"
+               />
+            </div>
+            <div class="head-container">
+               <el-tree
+                  :data="deptOptions"
+                  :props="{ label: 'label', children: 'children' }"
+                  :expand-on-click-node="false"
+                  :filter-node-method="filterNode"
+                  ref="deptTreeRef"
+                  node-key="id"
+                  highlight-current
+                  default-expand-all
+                  @node-click="handleNodeClick"
+               />
+            </div>
+         </el-col>
+         <!--鐢ㄦ埛鏁版嵁-->
+         <el-col :span="20" :xs="24">
+            <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
+               <el-form-item label="鐢ㄦ埛鍚嶇О" prop="userName">
+                  <el-input
+                     v-model="queryParams.userName"
+                     placeholder="璇疯緭鍏ョ敤鎴峰悕绉�"
+                     clearable
+                     style="width: 240px"
+                     @keyup.enter="handleQuery"
+                  />
+               </el-form-item>
+               <el-form-item label="鎵嬫満鍙风爜" prop="phoneNumber">
+                  <el-input
+                     v-model="queryParams.phoneNumber"
+                     placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�"
+                     clearable
+                     style="width: 240px"
+                     @keyup.enter="handleQuery"
+                  />
+               </el-form-item>
+               <el-form-item label="鐘舵��" prop="status">
+                  <el-select
+                     v-model="queryParams.status"
+                     placeholder="鐢ㄦ埛鐘舵��"
+                     clearable
+                     style="width: 240px"
+                  >
+                     <el-option
+                        v-for="dict in sys_normal_disable"
+                        :key="dict.value"
+                        :label="dict.label"
+                        :value="dict.value"
+                     />
+                  </el-select>
+               </el-form-item>
+               <el-form-item label="鍒涘缓鏃堕棿" style="width: 308px;">
+                  <el-date-picker
+                     v-model="dateRange"
+                     value-format="YYYY-MM-DD"
+                     type="daterange"
+                     range-separator="-"
+                     start-placeholder="寮�濮嬫棩鏈�"
+                     end-placeholder="缁撴潫鏃ユ湡"
+                  ></el-date-picker>
+               </el-form-item>
+               <el-form-item>
+                  <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+                  <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+               </el-form-item>
+            </el-form>
+
+            <el-row :gutter="10" class="mb8">
+               <el-col :span="1.5">
+                  <el-button
+                     type="primary"
+                     plain
+                     icon="Plus"
+                     @click="handleAdd"
+                     v-hasPermi="['system:user:add']"
+                  >鏂板</el-button>
+               </el-col>
+               <el-col :span="1.5">
+                  <el-button
+                     type="success"
+                     plain
+                     icon="Edit"
+                     :disabled="single"
+                     @click="handleUpdate"
+                     v-hasPermi="['system:user:edit']"
+                  >淇敼</el-button>
+               </el-col>
+               <el-col :span="1.5">
+                  <el-button
+                     type="danger"
+                     plain
+                     icon="Delete"
+                     :disabled="multiple"
+                     @click="handleDelete"
+                     v-hasPermi="['system:user:remove']"
+                  >鍒犻櫎</el-button>
+               </el-col>
+               <el-col :span="1.5">
+                  <el-button
+                     type="info"
+                     plain
+                     icon="Upload"
+                     @click="handleImport"
+                     v-hasPermi="['system:user:import']"
+                  >瀵煎叆</el-button>
+               </el-col>
+               <el-col :span="1.5">
+                  <el-button
+                     type="warning"
+                     plain
+                     icon="Download"
+                     @click="handleExport"
+                     v-hasPermi="['system:user:export']"
+                  >瀵煎嚭</el-button>
+               </el-col>
+               <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
+            </el-row>
+
+            <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
+               <el-table-column type="selection" width="50" align="center" />
+               <el-table-column label="鐢ㄦ埛缂栧彿" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
+               <el-table-column label="鐢ㄦ埛鍚嶇О" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
+               <el-table-column label="鐢ㄦ埛鏄电О" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
+               <el-table-column label="閮ㄩ棬" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />
+               <el-table-column label="鎵嬫満鍙风爜" align="center" key="phoneNumber" prop="phoneNumber" v-if="columns[4].visible" width="120" />
+               <el-table-column label="鐘舵��" align="center" key="status" v-if="columns[5].visible">
+                  <template #default="scope">
+                     <el-switch
+                        v-model="scope.row.status"
+                        active-value="0"
+                        inactive-value="1"
+                        @change="handleStatusChange(scope.row)"
+                     ></el-switch>
+                  </template>
+               </el-table-column>
+               <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" v-if="columns[6].visible" width="160">
+                  <template #default="scope">
+                     <span>{{ parseTime(scope.row.createTime) }}</span>
+                  </template>
+               </el-table-column>
+               <el-table-column label="鎿嶄綔" align="center" width="150" class-name="small-padding fixed-width">
+                  <template #default="scope">
+                     <el-tooltip content="淇敼" placement="top" v-if="scope.row.userId !== 1">
+                        <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
+                     </el-tooltip>
+                     <el-tooltip content="鍒犻櫎" placement="top" v-if="scope.row.userId !== 1">
+                        <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']"></el-button>
+                     </el-tooltip>
+                     <el-tooltip content="閲嶇疆瀵嗙爜" placement="top" v-if="scope.row.userId !== 1">
+                         <el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)" v-hasPermi="['system:user:resetPwd']"></el-button>
+                     </el-tooltip>
+                     <el-tooltip content="鍒嗛厤瑙掕壊" placement="top" v-if="scope.row.userId !== 1">
+                        <el-button link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
+                     </el-tooltip>
+                  </template>
+               </el-table-column>
+            </el-table>
+            <pagination
+               v-show="total > 0"
+               :total="total"
+               v-model:page="queryParams.pageNum"
+               v-model:limit="queryParams.pageSize"
+               @pagination="getList"
+            />
+         </el-col>
+      </el-row>
+
+      <!-- 娣诲姞鎴栦慨鏀圭敤鎴烽厤缃璇濇 -->
+      <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+         <el-form :model="form" :rules="rules" ref="userRef" label-width="100px">
+            <el-row>
+               <el-col :span="12">
+                  <el-form-item label="鐢ㄦ埛鏄电О" prop="nickName">
+                     <el-input v-model="form.nickName" placeholder="璇疯緭鍏ョ敤鎴锋樀绉�" maxlength="30" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="褰掑睘閮ㄩ棬" prop="deptId">
+                     <el-tree-select
+                        v-model="form.deptId"
+                        :data="deptOptions"
+                        :props="{ value: 'id', label: 'label', children: 'children' }"
+                        value-key="id"
+                        placeholder="璇烽�夋嫨褰掑睘閮ㄩ棬"
+                        check-strictly
+                     />
+                  </el-form-item>
+               </el-col>
+            </el-row>
+            <el-row>
+               <el-col :span="12">
+                  <el-form-item label="鎵嬫満鍙风爜" prop="phoneNumber">
+                     <el-input v-model="form.phoneNumber" placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�" maxlength="11" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="閭" prop="email">
+                     <el-input v-model="form.email" placeholder="璇疯緭鍏ラ偖绠�" maxlength="50" />
+                  </el-form-item>
+               </el-col>
+            </el-row>
+            <el-row>
+               <el-col :span="12">
+                  <el-form-item v-if="form.userId == undefined" label="鐢ㄦ埛鍚嶇О" prop="userName">
+                     <el-input v-model="form.userName" placeholder="璇疯緭鍏ョ敤鎴峰悕绉�" maxlength="30" />
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item v-if="form.userId == undefined" label="鐢ㄦ埛瀵嗙爜" prop="password">
+                     <el-input v-model="form.password" placeholder="璇疯緭鍏ョ敤鎴峰瘑鐮�" type="password" maxlength="20" show-password />
+                  </el-form-item>
+               </el-col>
+            </el-row>
+            <el-row>
+               <el-col :span="12">
+                  <el-form-item label="鐢ㄦ埛鎬у埆">
+                     <el-select v-model="form.sex" placeholder="璇烽�夋嫨">
+                        <el-option
+                           v-for="dict in sys_user_sex"
+                           :key="dict.value"
+                           :label="dict.label"
+                           :value="dict.value"
+                        ></el-option>
+                     </el-select>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="鐘舵��">
+                     <el-radio-group v-model="form.status">
+                        <el-radio
+                           v-for="dict in sys_normal_disable"
+                           :key="dict.value"
+                           :label="dict.value"
+                        >{{ dict.label }}</el-radio>
+                     </el-radio-group>
+                  </el-form-item>
+               </el-col>
+            </el-row>
+            <el-row>
+               <el-col :span="12">
+                  <el-form-item label="宀椾綅">
+                     <el-select v-model="form.postIds" multiple placeholder="璇烽�夋嫨">
+                        <el-option
+                           v-for="item in postOptions"
+                           :key="item.postId"
+                           :label="item.postName"
+                           :value="item.postId"
+                           :disabled="item.status == 1"
+                        ></el-option>
+                     </el-select>
+                  </el-form-item>
+               </el-col>
+               <el-col :span="12">
+                  <el-form-item label="瑙掕壊">
+                     <el-select v-model="form.roleIds" multiple placeholder="璇烽�夋嫨">
+                        <el-option
+                           v-for="item in roleOptions"
+                           :key="item.roleId"
+                           :label="item.roleName"
+                           :value="item.roleId"
+                           :disabled="item.status == 1"
+                        ></el-option>
+                     </el-select>
+                  </el-form-item>
+               </el-col>
+            </el-row>
+            <el-row>
+               <el-col :span="24">
+                  <el-form-item label="澶囨敞">
+                     <el-input v-model="form.remark" type="textarea" placeholder="璇疯緭鍏ュ唴瀹�"></el-input>
+                  </el-form-item>
+               </el-col>
+            </el-row>
+         </el-form>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitForm">纭� 瀹�</el-button>
+               <el-button @click="cancel">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+
+      <!-- 鐢ㄦ埛瀵煎叆瀵硅瘽妗� -->
+      <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
+         <el-upload
+            ref="uploadRef"
+            :limit="1"
+            accept=".xlsx, .xls"
+            :headers="upload.headers"
+            :action="upload.url + '?updateSupport=' + upload.updateSupport"
+            :disabled="upload.isUploading"
+            :on-progress="handleFileUploadProgress"
+            :on-success="handleFileSuccess"
+            :auto-upload="false"
+            drag
+         >
+            <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+            <div class="el-upload__text">灏嗘枃浠舵嫋鍒版澶勶紝鎴�<em>鐐瑰嚮涓婁紶</em></div>
+            <template #tip>
+               <div class="el-upload__tip text-center">
+                  <div class="el-upload__tip">
+                     <el-checkbox v-model="upload.updateSupport" />鏄惁鏇存柊宸茬粡瀛樺湪鐨勭敤鎴锋暟鎹�
+                  </div>
+                  <span>浠呭厑璁稿鍏ls銆亁lsx鏍煎紡鏂囦欢銆�</span>
+                  <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">涓嬭浇妯℃澘</el-link>
+               </div>
+            </template>
+         </el-upload>
+         <template #footer>
+            <div class="dialog-footer">
+               <el-button type="primary" @click="submitFileForm">纭� 瀹�</el-button>
+               <el-button @click="upload.open = false">鍙� 娑�</el-button>
+            </div>
+         </template>
+      </el-dialog>
+   </div>
+</template>
+
+<script setup name="User">
+import { getToken } from "@/utils/auth";
+import { changeUserStatus, listUser, resetUserPwd, delUser, getUser, updateUser, addUser, deptTreeSelect } from "@/api/system/user";
+
+const router = useRouter();
+const { proxy } = getCurrentInstance();
+const { sys_normal_disable, sys_user_sex } = proxy.useDict("sys_normal_disable", "sys_user_sex");
+
+const userList = ref([]);
+const open = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const title = ref("");
+const dateRange = ref([]);
+const deptName = ref("");
+const deptOptions = ref(undefined);
+const initPassword = ref(undefined);
+const postOptions = ref([]);
+const roleOptions = ref([]);
+/*** 鐢ㄦ埛瀵煎叆鍙傛暟 */
+const upload = reactive({
+  // 鏄惁鏄剧ず寮瑰嚭灞傦紙鐢ㄦ埛瀵煎叆锛�
+  open: false,
+  // 寮瑰嚭灞傛爣棰橈紙鐢ㄦ埛瀵煎叆锛�
+  title: "",
+  // 鏄惁绂佺敤涓婁紶
+  isUploading: false,
+  // 鏄惁鏇存柊宸茬粡瀛樺湪鐨勭敤鎴锋暟鎹�
+  updateSupport: 0,
+  // 璁剧疆涓婁紶鐨勮姹傚ご閮�
+  headers: { Authorization: "Bearer " + getToken() },
+  // 涓婁紶鐨勫湴鍧�
+  url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData"
+});
+// 鍒楁樉闅愪俊鎭�
+const columns = ref([
+  { key: 0, label: `鐢ㄦ埛缂栧彿`, visible: true },
+  { key: 1, label: `鐢ㄦ埛鍚嶇О`, visible: true },
+  { key: 2, label: `鐢ㄦ埛鏄电О`, visible: true },
+  { key: 3, label: `閮ㄩ棬`, visible: true },
+  { key: 4, label: `鎵嬫満鍙风爜`, visible: true },
+  { key: 5, label: `鐘舵�乣, visible: true },
+  { key: 6, label: `鍒涘缓鏃堕棿`, visible: true }
+]);
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    userName: undefined,
+    phoneNumber: undefined,
+    status: undefined,
+    deptId: undefined
+  },
+  rules: {
+    userName: [{ required: true, message: "鐢ㄦ埛鍚嶇О涓嶈兘涓虹┖", trigger: "blur" }, { min: 2, max: 20, message: "鐢ㄦ埛鍚嶇О闀垮害蹇呴』浠嬩簬 2 鍜� 20 涔嬮棿", trigger: "blur" }],
+    nickName: [{ required: true, message: "鐢ㄦ埛鏄电О涓嶈兘涓虹┖", trigger: "blur" }],
+    password: [{ required: true, message: "鐢ㄦ埛瀵嗙爜涓嶈兘涓虹┖", trigger: "blur" }, { min: 5, max: 20, message: "鐢ㄦ埛瀵嗙爜闀垮害蹇呴』浠嬩簬 5 鍜� 20 涔嬮棿", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "涓嶈兘鍖呭惈闈炴硶瀛楃锛�< > \" ' \\\ |", trigger: "blur" }],
+    email: [{ type: "email", message: "璇疯緭鍏ユ纭殑閭鍦板潃", trigger: ["blur", "change"] }],
+    phoneNumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "璇疯緭鍏ユ纭殑鎵嬫満鍙风爜", trigger: "blur" }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 閫氳繃鏉′欢杩囨护鑺傜偣  */
+const filterNode = (value, data) => {
+  if (!value) return true;
+  return data.label.indexOf(value) !== -1;
+};
+/** 鏍规嵁鍚嶇О绛涢�夐儴闂ㄦ爲 */
+watch(deptName, val => {
+  proxy.$refs["deptTreeRef"].filter(val);
+});
+/** 鏌ヨ閮ㄩ棬涓嬫媺鏍戠粨鏋� */
+function getDeptTree() {
+  deptTreeSelect().then(response => {
+    deptOptions.value = response.data;
+  });
+};
+/** 鏌ヨ鐢ㄦ埛鍒楄〃 */
+function getList() {
+  loading.value = true;
+  listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => {
+    loading.value = false;
+    userList.value = res.rows;
+    total.value = res.total;
+  });
+};
+/** 鑺傜偣鍗曞嚮浜嬩欢 */
+function handleNodeClick(data) {
+  queryParams.value.deptId = data.id;
+  handleQuery();
+};
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  dateRange.value = [];
+  proxy.resetForm("queryRef");
+  queryParams.value.deptId = undefined;
+  proxy.$refs.deptTreeRef.setCurrentKey(null);
+  handleQuery();
+};
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const userIds = row.userId || ids.value;
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎鐢ㄦ埛缂栧彿涓�"' + userIds + '"鐨勬暟鎹」锛�').then(function () {
+    return delUser(userIds);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+};
+/** 瀵煎嚭鎸夐挳鎿嶄綔 */
+function handleExport() {
+  proxy.download("system/user/export", {
+    ...queryParams.value,
+  },`user_${new Date().getTime()}.xlsx`);
+};
+/** 鐢ㄦ埛鐘舵�佷慨鏀�  */
+function handleStatusChange(row) {
+  let text = row.status === "0" ? "鍚敤" : "鍋滅敤";
+  proxy.$modal.confirm('纭瑕�"' + text + '""' + row.userName + '"鐢ㄦ埛鍚�?').then(function () {
+    return changeUserStatus(row.userId, row.status);
+  }).then(() => {
+    proxy.$modal.msgSuccess(text + "鎴愬姛");
+  }).catch(function () {
+    row.status = row.status === "0" ? "1" : "0";
+  });
+};
+/** 鏇村鎿嶄綔 */
+function handleCommand(command, row) {
+  switch (command) {
+    case "handleResetPwd":
+      handleResetPwd(row);
+      break;
+    case "handleAuthRole":
+      handleAuthRole(row);
+      break;
+    default:
+      break;
+  }
+};
+/** 璺宠浆瑙掕壊鍒嗛厤 */
+function handleAuthRole(row) {
+  const userId = row.userId;
+  router.push("/system/user-auth/role/" + userId);
+};
+/** 閲嶇疆瀵嗙爜鎸夐挳鎿嶄綔 */
+function handleResetPwd(row) {
+  proxy.$prompt('璇疯緭鍏�"' + row.userName + '"鐨勬柊瀵嗙爜', "鎻愮ず", {
+    confirmButtonText: "纭畾",
+    cancelButtonText: "鍙栨秷",
+    closeOnClickModal: false,
+    inputPattern: /^.{5,20}$/,
+    inputErrorMessage: "鐢ㄦ埛瀵嗙爜闀垮害蹇呴』浠嬩簬 5 鍜� 20 涔嬮棿",
+    inputValidator: (value) => {
+      if (/<|>|"|'|\||\\/.test(value)) {
+        return "涓嶈兘鍖呭惈闈炴硶瀛楃锛�< > \" ' \\\ |"
+      }
+    },
+  }).then(({ value }) => {
+    resetUserPwd(row.userId, value).then(response => {
+      proxy.$modal.msgSuccess("淇敼鎴愬姛锛屾柊瀵嗙爜鏄細" + value);
+    });
+  }).catch(() => {});
+};
+/** 閫夋嫨鏉℃暟  */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.userId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+/** 瀵煎叆鎸夐挳鎿嶄綔 */
+function handleImport() {
+  upload.title = "鐢ㄦ埛瀵煎叆";
+  upload.open = true;
+};
+/** 涓嬭浇妯℃澘鎿嶄綔 */
+function importTemplate() {
+  proxy.download("system/user/importTemplate", {
+  }, `user_template_${new Date().getTime()}.xlsx`);
+};
+/**鏂囦欢涓婁紶涓鐞� */
+const handleFileUploadProgress = (event, file, fileList) => {
+  upload.isUploading = true;
+};
+/** 鏂囦欢涓婁紶鎴愬姛澶勭悊 */
+const handleFileSuccess = (response, file, fileList) => {
+  upload.open = false;
+  upload.isUploading = false;
+  proxy.$refs["uploadRef"].handleRemove(file);
+  proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "瀵煎叆缁撴灉", { dangerouslyUseHTMLString: true });
+  getList();
+};
+/** 鎻愪氦涓婁紶鏂囦欢 */
+function submitFileForm() {
+  proxy.$refs["uploadRef"].submit();
+};
+/** 閲嶇疆鎿嶄綔琛ㄥ崟 */
+function reset() {
+  form.value = {
+    userId: undefined,
+    deptId: undefined,
+    userName: undefined,
+    nickName: undefined,
+    password: undefined,
+    phoneNumber: undefined,
+    email: undefined,
+    sex: undefined,
+    status: "0",
+    remark: undefined,
+    postIds: [],
+    roleIds: []
+  };
+  proxy.resetForm("userRef");
+};
+/** 鍙栨秷鎸夐挳 */
+function cancel() {
+  open.value = false;
+  reset();
+};
+/** 鏂板鎸夐挳鎿嶄綔 */
+function handleAdd() {
+  reset();
+  getUser().then(response => {
+    postOptions.value = response.posts;
+    roleOptions.value = response.roles;
+    open.value = true;
+    title.value = "娣诲姞鐢ㄦ埛";
+    form.value.password = initPassword.value;
+  });
+};
+/** 淇敼鎸夐挳鎿嶄綔 */
+function handleUpdate(row) {
+  reset();
+  const userId = row.userId || ids.value;
+  getUser(userId).then(response => {
+    form.value = response.data;
+    postOptions.value = response.posts;
+    roleOptions.value = response.roles;
+    form.value.postIds = response.postIds;
+    form.value.roleIds = response.roleIds;
+    open.value = true;
+    title.value = "淇敼鐢ㄦ埛";
+    form.password = "";
+  });
+};
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  proxy.$refs["userRef"].validate(valid => {
+    if (valid) {
+      if (form.value.userId != undefined) {
+        updateUser(form.value).then(response => {
+          proxy.$modal.msgSuccess("淇敼鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addUser(form.value).then(response => {
+          proxy.$modal.msgSuccess("鏂板鎴愬姛");
+          open.value = false;
+          getList();
+        });
+      }
+    }
+  });
+};
+
+getDeptTree();
+getList();
+</script>
diff --git a/zhitan-vue/src/views/system/user/profile/index.vue b/zhitan-vue/src/views/system/user/profile/index.vue
new file mode 100644
index 0000000..e34c98b
--- /dev/null
+++ b/zhitan-vue/src/views/system/user/profile/index.vue
@@ -0,0 +1,87 @@
+<template>
+   <div class="app-container">
+      <el-row :gutter="20">
+         <el-col :span="6" :xs="24">
+            <el-card class="box-card">
+               <template v-slot:header>
+                 <div class="clearfix">
+                   <span>涓汉淇℃伅</span>
+                 </div>
+               </template>
+               <div>
+                  <div class="text-center">
+                     <userAvatar />
+                  </div>
+                  <ul class="list-group list-group-striped">
+                     <li class="list-group-item">
+                        <svg-icon icon-class="user" />鐢ㄦ埛鍚嶇О
+                        <div class="pull-right">{{ state.user.userName }}</div>
+                     </li>
+                     <li class="list-group-item">
+                        <svg-icon icon-class="phone" />鎵嬫満鍙风爜
+                        <div class="pull-right">{{ state.user.phoneNumber }}</div>
+                     </li>
+                     <li class="list-group-item">
+                        <svg-icon icon-class="email" />鐢ㄦ埛閭
+                        <div class="pull-right">{{ state.user.email }}</div>
+                     </li>
+                     <li class="list-group-item">
+                        <svg-icon icon-class="tree" />鎵�灞為儴闂�
+                        <div class="pull-right" v-if="state.user.dept">{{ state.user.dept.deptName }} / {{ state.postGroup }}</div>
+                     </li>
+                     <li class="list-group-item">
+                        <svg-icon icon-class="peoples" />鎵�灞炶鑹�
+                        <div class="pull-right">{{ state.roleGroup }}</div>
+                     </li>
+                     <li class="list-group-item">
+                        <svg-icon icon-class="date" />鍒涘缓鏃ユ湡
+                        <div class="pull-right">{{ state.user.createTime }}</div>
+                     </li>
+                  </ul>
+               </div>
+            </el-card>
+         </el-col>
+         <el-col :span="18" :xs="24">
+            <el-card>
+               <template v-slot:header>
+                 <div class="clearfix">
+                   <span>鍩烘湰璧勬枡</span>
+                 </div>
+               </template>
+               <el-tabs v-model="activeTab">
+                  <el-tab-pane label="鍩烘湰璧勬枡" name="userinfo">
+                     <userInfo :user="state.user" />
+                  </el-tab-pane>
+                  <el-tab-pane label="淇敼瀵嗙爜" name="resetPwd">
+                     <resetPwd />
+                  </el-tab-pane>
+               </el-tabs>
+            </el-card>
+         </el-col>
+      </el-row>
+   </div>
+</template>
+
+<script setup name="Profile">
+import userAvatar from "./userAvatar";
+import userInfo from "./userInfo";
+import resetPwd from "./resetPwd";
+import { getUserProfile } from "@/api/system/user";
+
+const activeTab = ref("userinfo");
+const state = reactive({
+  user: {},
+  roleGroup: {},
+  postGroup: {}
+});
+
+function getUser() {
+  getUserProfile().then(response => {
+    state.user = response.data;
+    state.roleGroup = response.roleGroup;
+    state.postGroup = response.postGroup;
+  });
+};
+
+getUser();
+</script>
diff --git a/zhitan-vue/src/views/system/user/profile/resetPwd.vue b/zhitan-vue/src/views/system/user/profile/resetPwd.vue
new file mode 100644
index 0000000..96daef3
--- /dev/null
+++ b/zhitan-vue/src/views/system/user/profile/resetPwd.vue
@@ -0,0 +1,57 @@
+<template>
+   <el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
+      <el-form-item label="鏃у瘑鐮�" prop="oldPassword">
+         <el-input v-model="user.oldPassword" placeholder="璇疯緭鍏ユ棫瀵嗙爜" type="password" show-password />
+      </el-form-item>
+      <el-form-item label="鏂板瘑鐮�" prop="newPassword">
+         <el-input v-model="user.newPassword" placeholder="璇疯緭鍏ユ柊瀵嗙爜" type="password" show-password />
+      </el-form-item>
+      <el-form-item label="纭瀵嗙爜" prop="confirmPassword">
+         <el-input v-model="user.confirmPassword" placeholder="璇风‘璁ゆ柊瀵嗙爜" type="password" show-password/>
+      </el-form-item>
+      <el-form-item>
+      <el-button type="primary" @click="submit">淇濆瓨</el-button>
+      <el-button type="danger" @click="close">鍏抽棴</el-button>
+      </el-form-item>
+   </el-form>
+</template>
+
+<script setup>
+import { updateUserPwd } from "@/api/system/user";
+
+const { proxy } = getCurrentInstance();
+
+const user = reactive({
+  oldPassword: undefined,
+  newPassword: undefined,
+  confirmPassword: undefined
+});
+
+const equalToPassword = (rule, value, callback) => {
+  if (user.newPassword !== value) {
+    callback(new Error("涓ゆ杈撳叆鐨勫瘑鐮佷笉涓�鑷�"));
+  } else {
+    callback();
+  }
+};
+const rules = ref({
+  oldPassword: [{ required: true, message: "鏃у瘑鐮佷笉鑳戒负绌�", trigger: "blur" }],
+  newPassword: [{ required: true, message: "鏂板瘑鐮佷笉鑳戒负绌�", trigger: "blur" }, { min: 6, max: 20, message: "闀垮害鍦� 6 鍒� 20 涓瓧绗�", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "涓嶈兘鍖呭惈闈炴硶瀛楃锛�< > \" ' \\\ |", trigger: "blur" }],
+  confirmPassword: [{ required: true, message: "纭瀵嗙爜涓嶈兘涓虹┖", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }]
+});
+
+/** 鎻愪氦鎸夐挳 */
+function submit() {
+  proxy.$refs.pwdRef.validate(valid => {
+    if (valid) {
+      updateUserPwd(user.oldPassword, user.newPassword).then(response => {
+        proxy.$modal.msgSuccess("淇敼鎴愬姛");
+      });
+    }
+  });
+};
+/** 鍏抽棴鎸夐挳 */
+function close() {
+  proxy.$tab.closePage();
+};
+</script>
diff --git a/zhitan-vue/src/views/system/user/profile/userAvatar.vue b/zhitan-vue/src/views/system/user/profile/userAvatar.vue
new file mode 100644
index 0000000..3b39636
--- /dev/null
+++ b/zhitan-vue/src/views/system/user/profile/userAvatar.vue
@@ -0,0 +1,171 @@
+<template>
+  <div class="user-info-head" @click="editCropper()">
+    <img :src="options.img" title="鐐瑰嚮涓婁紶澶村儚" class="img-circle img-lg" />
+    <el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
+      <el-row>
+        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
+          <vue-cropper
+            ref="cropper"
+            :img="options.img"
+            :info="true"
+            :autoCrop="options.autoCrop"
+            :autoCropWidth="options.autoCropWidth"
+            :autoCropHeight="options.autoCropHeight"
+            :fixedBox="options.fixedBox"
+            :outputType="options.outputType"
+            @realTime="realTime"
+            v-if="visible"
+          />
+        </el-col>
+        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
+          <div class="avatar-upload-preview">
+            <img :src="options.previews.url" :style="options.previews.img" />
+          </div>
+        </el-col>
+      </el-row>
+      <br />
+      <el-row>
+        <el-col :lg="2" :md="2">
+          <el-upload
+            action="#"
+            :http-request="requestUpload"
+            :show-file-list="false"
+            :before-upload="beforeUpload"
+          >
+            <el-button>
+              閫夋嫨
+              <el-icon class="el-icon--right"><Upload /></el-icon>
+            </el-button>
+          </el-upload>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 2 }" :md="2">
+          <el-button icon="Plus" @click="changeScale(1)"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <el-button icon="Minus" @click="changeScale(-1)"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <el-button icon="RefreshRight" @click="rotateRight()"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 2, offset: 6 }" :md="2">
+          <el-button type="primary" @click="uploadImg()">鎻� 浜�</el-button>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import "vue-cropper/dist/index.css";
+import { VueCropper } from "vue-cropper";
+import { uploadAvatar } from "@/api/system/user";
+import useUserStore from "@/store/modules/user";
+
+const userStore = useUserStore();
+const { proxy } = getCurrentInstance();
+
+const open = ref(false);
+const visible = ref(false);
+const title = ref("淇敼澶村儚");
+
+//鍥剧墖瑁佸壀鏁版嵁
+const options = reactive({
+  img: userStore.avatar,     // 瑁佸壀鍥剧墖鐨勫湴鍧�
+  autoCrop: true,            // 鏄惁榛樿鐢熸垚鎴浘妗�
+  autoCropWidth: 200,        // 榛樿鐢熸垚鎴浘妗嗗搴�
+  autoCropHeight: 200,       // 榛樿鐢熸垚鎴浘妗嗛珮搴�
+  fixedBox: true,            // 鍥哄畾鎴浘妗嗗ぇ灏� 涓嶅厑璁告敼鍙�
+  outputType: "png",         // 榛樿鐢熸垚鎴浘涓篜NG鏍煎紡
+  filename: 'avatar',        // 鏂囦欢鍚嶇О
+  previews: {}               //棰勮鏁版嵁
+});
+
+/** 缂栬緫澶村儚 */
+function editCropper() {
+  open.value = true;
+}
+/** 鎵撳紑寮瑰嚭灞傜粨鏉熸椂鐨勫洖璋� */
+function modalOpened() {
+  visible.value = true;
+}
+/** 瑕嗙洊榛樿涓婁紶琛屼负 */
+function requestUpload() {}
+/** 鍚戝乏鏃嬭浆 */
+function rotateLeft() {
+  proxy.$refs.cropper.rotateLeft();
+}
+/** 鍚戝彸鏃嬭浆 */
+function rotateRight() {
+  proxy.$refs.cropper.rotateRight();
+}
+/** 鍥剧墖缂╂斁 */
+function changeScale(num) {
+  num = num || 1;
+  proxy.$refs.cropper.changeScale(num);
+}
+/** 涓婁紶棰勫鐞� */
+function beforeUpload(file) {
+  if (file.type.indexOf("image/") == -1) {
+    proxy.$modal.msgError("鏂囦欢鏍煎紡閿欒锛岃涓婁紶鍥剧墖绫诲瀷,濡傦細JPG锛孭NG鍚庣紑鐨勬枃浠躲��");
+  } else {
+    const reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.onload = () => {
+      options.img = reader.result;
+      options.filename = file.name;
+    };
+  }
+}
+/** 涓婁紶鍥剧墖 */
+function uploadImg() {
+  proxy.$refs.cropper.getCropBlob(data => {
+    let formData = new FormData();
+    formData.append("avatarfile", data, options.filename);
+    uploadAvatar(formData).then(response => {
+      open.value = false;
+      options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl;
+      userStore.avatar = options.img;
+      proxy.$modal.msgSuccess("淇敼鎴愬姛");
+      visible.value = false;
+    });
+  });
+}
+/** 瀹炴椂棰勮 */
+function realTime(data) {
+  options.previews = data;
+}
+/** 鍏抽棴绐楀彛 */
+function closeDialog() {
+  options.img = userStore.avatar;
+  options.visible = false;
+}
+</script>
+
+<style lang='scss' scoped>
+.user-info-head {
+  position: relative;
+  display: inline-block;
+  height: 120px;
+}
+
+.user-info-head:hover:after {
+  content: "+";
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  color: #eee;
+  background: rgba(0, 0, 0, 0.5);
+  font-size: 24px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  cursor: pointer;
+  line-height: 110px;
+  border-radius: 50%;
+}
+</style>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/system/user/profile/userInfo.vue b/zhitan-vue/src/views/system/user/profile/userInfo.vue
new file mode 100644
index 0000000..f1d67b5
--- /dev/null
+++ b/zhitan-vue/src/views/system/user/profile/userInfo.vue
@@ -0,0 +1,67 @@
+<template>
+   <el-form ref="userRef" :model="form" :rules="rules" label-width="80px">
+      <el-form-item label="鐢ㄦ埛鏄电О" prop="nickName">
+         <el-input v-model="form.nickName" maxlength="30" />
+      </el-form-item>
+      <el-form-item label="鎵嬫満鍙风爜" prop="phoneNumber">
+         <el-input v-model="form.phoneNumber" maxlength="11" />
+      </el-form-item>
+      <el-form-item label="閭" prop="email">
+         <el-input v-model="form.email" maxlength="50" />
+      </el-form-item>
+      <el-form-item label="鎬у埆">
+         <el-radio-group v-model="form.sex">
+            <el-radio label="0">鐢�</el-radio>
+            <el-radio label="1">濂�</el-radio>
+         </el-radio-group>
+      </el-form-item>
+      <el-form-item>
+      <el-button type="primary" @click="submit">淇濆瓨</el-button>
+      <el-button type="danger" @click="close">鍏抽棴</el-button>
+      </el-form-item>
+   </el-form>
+</template>
+
+<script setup>
+import { updateUserProfile } from "@/api/system/user";
+
+const props = defineProps({
+  user: {
+    type: Object
+  }
+});
+
+const { proxy } = getCurrentInstance();
+
+const form = ref({});
+const rules = ref({
+  nickName: [{ required: true, message: "鐢ㄦ埛鏄电О涓嶈兘涓虹┖", trigger: "blur" }],
+  email: [{ required: true, message: "閭鍦板潃涓嶈兘涓虹┖", trigger: "blur" }, { type: "email", message: "璇疯緭鍏ユ纭殑閭鍦板潃", trigger: ["blur", "change"] }],
+  phoneNumber: [{ required: true, message: "鎵嬫満鍙风爜涓嶈兘涓虹┖", trigger: "blur" }, { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "璇疯緭鍏ユ纭殑鎵嬫満鍙风爜", trigger: "blur" }],
+});
+
+/** 鎻愪氦鎸夐挳 */
+function submit() {
+  proxy.$refs.userRef.validate(valid => {
+    if (valid) {
+      updateUserProfile(form.value).then(response => {
+        proxy.$modal.msgSuccess("淇敼鎴愬姛");
+        props.user.phoneNumber = form.value.phoneNumber;
+        props.user.email = form.value.email;
+      });
+    }
+  });
+};
+
+/** 鍏抽棴鎸夐挳 */
+function close() {
+  proxy.$tab.closePage();
+};
+
+// 鍥炴樉褰撳墠鐧诲綍鐢ㄦ埛淇℃伅
+watch(() => props.user, user => {
+  if (user) {
+    form.value = { nickName: user.nickName, phoneNumber: user.phoneNumber, email: user.email, sex: user.sex };
+  }
+},{ immediate: true });
+</script>
diff --git a/zhitan-vue/src/views/tool/build/index.vue b/zhitan-vue/src/views/tool/build/index.vue
new file mode 100644
index 0000000..c3543a9
--- /dev/null
+++ b/zhitan-vue/src/views/tool/build/index.vue
@@ -0,0 +1,3 @@
+<template>
+   <div> 琛ㄥ崟鏋勫缓 <svg-icon icon-class="build" /> </div>
+</template>
\ No newline at end of file
diff --git a/zhitan-vue/src/views/tool/gen/basicInfoForm.vue b/zhitan-vue/src/views/tool/gen/basicInfoForm.vue
new file mode 100644
index 0000000..39c8515
--- /dev/null
+++ b/zhitan-vue/src/views/tool/gen/basicInfoForm.vue
@@ -0,0 +1,48 @@
+<template>
+  <el-form ref="basicInfoForm" :model="info" :rules="rules" label-width="150px">
+    <el-row>
+      <el-col :span="12">
+        <el-form-item label="琛ㄥ悕绉�" prop="tableName">
+          <el-input placeholder="璇疯緭鍏ヤ粨搴撳悕绉�" v-model="info.tableName" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="琛ㄦ弿杩�" prop="tableComment">
+          <el-input placeholder="璇疯緭鍏�" v-model="info.tableComment" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="瀹炰綋绫诲悕绉�" prop="className">
+          <el-input placeholder="璇疯緭鍏�" v-model="info.className" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="浣滆��" prop="functionAuthor">
+          <el-input placeholder="璇疯緭鍏�" v-model="info.functionAuthor" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="24">
+        <el-form-item label="澶囨敞" prop="remark">
+          <el-input type="textarea" :rows="3" v-model="info.remark"></el-input>
+        </el-form-item>
+      </el-col>
+    </el-row>
+  </el-form>
+</template>
+
+<script setup>
+defineProps({
+  info: {
+    type: Object,
+    default: null
+  }
+});
+
+// 琛ㄥ崟鏍¢獙
+const rules = ref({
+  tableName: [{ required: true, message: "璇疯緭鍏ヨ〃鍚嶇О", trigger: "blur" }],
+  tableComment: [{ required: true, message: "璇疯緭鍏ヨ〃鎻忚堪", trigger: "blur" }],
+  className: [{ required: true, message: "璇疯緭鍏ュ疄浣撶被鍚嶇О", trigger: "blur" }],
+  functionAuthor: [{ required: true, message: "璇疯緭鍏ヤ綔鑰�", trigger: "blur" }]
+});
+</script>
diff --git a/zhitan-vue/src/views/tool/gen/createTable.vue b/zhitan-vue/src/views/tool/gen/createTable.vue
new file mode 100644
index 0000000..00fdfdb
--- /dev/null
+++ b/zhitan-vue/src/views/tool/gen/createTable.vue
@@ -0,0 +1,46 @@
+<template>
+  <!-- 鍒涘缓琛� -->
+  <el-dialog title="鍒涘缓琛�" v-model="visible" width="800px" top="5vh" append-to-body>
+    <span>鍒涘缓琛ㄨ鍙�(鏀寔澶氫釜寤鸿〃璇彞)锛�</span>
+    <el-input type="textarea" :rows="10" placeholder="璇疯緭鍏ユ枃鏈�" v-model="content"></el-input>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="handleImportTable">纭� 瀹�</el-button>
+        <el-button @click="visible = false">鍙� 娑�</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { createTable } from "@/api/tool/gen";
+
+const visible = ref(false);
+const content = ref("");
+const { proxy } = getCurrentInstance();
+const emit = defineEmits(["ok"]);
+
+/** 鏄剧ず寮规 */
+function show() {
+  visible.value = true;
+}
+
+/** 瀵煎叆鎸夐挳鎿嶄綔 */
+function handleImportTable() {
+  if (content.value === "") {
+    proxy.$modal.msgError("璇疯緭鍏ュ缓琛ㄨ鍙�");
+    return;
+  }
+  createTable({ sql: content.value }).then(res => {
+    proxy.$modal.msgSuccess(res.msg);
+    if (res.code === 200) {
+      visible.value = false;
+      emit("ok");
+    }
+  });
+}
+
+defineExpose({
+  show,
+});
+</script>
diff --git a/zhitan-vue/src/views/tool/gen/editTable.vue b/zhitan-vue/src/views/tool/gen/editTable.vue
new file mode 100644
index 0000000..ddcbfd5
--- /dev/null
+++ b/zhitan-vue/src/views/tool/gen/editTable.vue
@@ -0,0 +1,198 @@
+<template>
+  <el-card>
+    <el-tabs v-model="activeName">
+      <el-tab-pane label="鍩烘湰淇℃伅" name="basic">
+        <basic-info-form ref="basicInfo" :info="info" />
+      </el-tab-pane>
+      <el-tab-pane label="瀛楁淇℃伅" name="columnInfo">
+        <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
+          <el-table-column label="搴忓彿" type="index" min-width="5%"/>
+          <el-table-column
+            label="瀛楁鍒楀悕"
+            prop="columnName"
+            min-width="10%"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="瀛楁鎻忚堪" min-width="10%">
+            <template #default="scope">
+              <el-input v-model="scope.row.columnComment"></el-input>
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="鐗╃悊绫诲瀷"
+            prop="columnType"
+            min-width="10%"
+            :show-overflow-tooltip="true"
+          />
+          <el-table-column label="Java绫诲瀷" min-width="11%">
+            <template #default="scope">
+              <el-select v-model="scope.row.javaType">
+                <el-option label="Long" value="Long" />
+                <el-option label="String" value="String" />
+                <el-option label="Integer" value="Integer" />
+                <el-option label="Double" value="Double" />
+                <el-option label="BigDecimal" value="BigDecimal" />
+                <el-option label="Date" value="Date" />
+                <el-option label="Boolean" value="Boolean" />
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column label="java灞炴��" min-width="10%">
+            <template #default="scope">
+              <el-input v-model="scope.row.javaField"></el-input>
+            </template>
+          </el-table-column>
+
+          <el-table-column label="鎻掑叆" min-width="5%">
+            <template #default="scope">
+              <el-checkbox true-label="1" false-label="0" v-model="scope.row.isInsert"></el-checkbox>
+            </template>
+          </el-table-column>
+          <el-table-column label="缂栬緫" min-width="5%">
+            <template #default="scope">
+              <el-checkbox true-label="1" false-label="0" v-model="scope.row.isEdit"></el-checkbox>
+            </template>
+          </el-table-column>
+          <el-table-column label="鍒楄〃" min-width="5%">
+            <template #default="scope">
+              <el-checkbox true-label="1" false-label="0" v-model="scope.row.isList"></el-checkbox>
+            </template>
+          </el-table-column>
+          <el-table-column label="鏌ヨ" min-width="5%">
+            <template #default="scope">
+              <el-checkbox true-label="1" false-label="0" v-model="scope.row.isQuery"></el-checkbox>
+            </template>
+          </el-table-column>
+          <el-table-column label="鏌ヨ鏂瑰紡" min-width="10%">
+            <template #default="scope">
+              <el-select v-model="scope.row.queryType">
+                <el-option label="=" value="EQ" />
+                <el-option label="!=" value="NE" />
+                <el-option label=">" value="GT" />
+                <el-option label=">=" value="GTE" />
+                <el-option label="<" value="LT" />
+                <el-option label="<=" value="LTE" />
+                <el-option label="LIKE" value="LIKE" />
+                <el-option label="BETWEEN" value="BETWEEN" />
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column label="蹇呭~" min-width="5%">
+            <template #default="scope">
+              <el-checkbox true-label="1" false-label="0" v-model="scope.row.isRequired"></el-checkbox>
+            </template>
+          </el-table-column>
+          <el-table-column label="鏄剧ず绫诲瀷" min-width="12%">
+            <template #default="scope">
+              <el-select v-model="scope.row.htmlType">
+                <el-option label="鏂囨湰妗�" value="input" />
+                <el-option label="鏂囨湰鍩�" value="textarea" />
+                <el-option label="涓嬫媺妗�" value="select" />
+                <el-option label="鍗曢�夋" value="radio" />
+                <el-option label="澶嶉�夋" value="checkbox" />
+                <el-option label="鏃ユ湡鎺т欢" value="datetime" />
+                <el-option label="鍥剧墖涓婁紶" value="imageUpload" />
+                <el-option label="鏂囦欢涓婁紶" value="fileUpload" />
+                <el-option label="瀵屾枃鏈帶浠�" value="editor" />
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column label="瀛楀吀绫诲瀷" min-width="12%">
+            <template #default="scope">
+              <el-select v-model="scope.row.dictType" clearable filterable placeholder="璇烽�夋嫨">
+                <el-option
+                  v-for="dict in dictOptions"
+                  :key="dict.dictType"
+                  :label="dict.dictName"
+                  :value="dict.dictType">
+                  <span style="float: left">{{ dict.dictName }}</span>
+                  <span style="float: right; color: #8492a6; font-size: 13px">{{ dict.dictType }}</span>
+              </el-option>
+              </el-select>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-tab-pane>
+      <el-tab-pane label="鐢熸垚淇℃伅" name="genInfo">
+        <gen-info-form ref="genInfo" :info="info" :tables="tables" />
+      </el-tab-pane>
+    </el-tabs>
+    <el-form label-width="100px">
+      <div style="text-align: center;margin-left:-100px;margin-top:10px;">
+        <el-button type="primary" @click="submitForm()">鎻愪氦</el-button>
+        <el-button @click="close()">杩斿洖</el-button>
+      </div>
+    </el-form>
+  </el-card>
+</template>
+
+<script setup name="GenEdit">
+import { getGenTable, updateGenTable } from "@/api/tool/gen";
+import { optionselect as getDictOptionselect } from "@/api/system/dict/type";
+import basicInfoForm from "./basicInfoForm";
+import genInfoForm from "./genInfoForm";
+
+const route = useRoute();
+const { proxy } = getCurrentInstance();
+
+const activeName = ref("columnInfo");
+const tableHeight = ref(document.documentElement.scrollHeight - 245 + "px");
+const tables = ref([]);
+const columns = ref([]);
+const dictOptions = ref([]);
+const info = ref({});
+
+/** 鎻愪氦鎸夐挳 */
+function submitForm() {
+  const basicForm = proxy.$refs.basicInfo.$refs.basicInfoForm;
+  const genForm = proxy.$refs.genInfo.$refs.genInfoForm;
+  Promise.all([basicForm, genForm].map(getFormPromise)).then(res => {
+    const validateResult = res.every(item => !!item);
+    if (validateResult) {
+      const genTable = Object.assign({}, info.value);
+      genTable.columns = columns.value;
+      genTable.params = {
+        treeCode: info.value.treeCode,
+        treeName: info.value.treeName,
+        treeParentCode: info.value.treeParentCode,
+        parentMenuId: info.value.parentMenuId
+      };
+      updateGenTable(genTable).then(res => {
+        proxy.$modal.msgSuccess(res.msg);
+        if (res.code === 200) {
+          close();
+        }
+      });
+    } else {
+      proxy.$modal.msgError("琛ㄥ崟鏍¢獙鏈�氳繃锛岃閲嶆柊妫�鏌ユ彁浜ゅ唴瀹�");
+    }
+  });
+}
+function getFormPromise(form) {
+  return new Promise(resolve => {
+    form.validate(res => {
+      resolve(res);
+    });
+  });
+}
+function close() {
+  const obj = { path: "/tool/gen", query: { t: Date.now(), pageNum: route.query.pageNum } };
+  proxy.$tab.closeOpenPage(obj);
+}
+
+(() => {
+  const tableId = route.params && route.params.tableId;
+  if (tableId) {
+    // 鑾峰彇琛ㄨ缁嗕俊鎭�
+    getGenTable(tableId).then(res => {
+      columns.value = res.data.rows;
+      info.value = res.data.info;
+      tables.value = res.data.tables;
+    });
+    /** 鏌ヨ瀛楀吀涓嬫媺鍒楄〃 */
+    getDictOptionselect().then(response => {
+      dictOptions.value = response.data;
+    });
+  }
+})();
+</script>
diff --git a/zhitan-vue/src/views/tool/gen/genInfoForm.vue b/zhitan-vue/src/views/tool/gen/genInfoForm.vue
new file mode 100644
index 0000000..1856eae
--- /dev/null
+++ b/zhitan-vue/src/views/tool/gen/genInfoForm.vue
@@ -0,0 +1,297 @@
+<template>
+  <el-form ref="genInfoForm" :model="info" :rules="rules" label-width="150px">
+    <el-row>
+      <el-col :span="12">
+        <el-form-item prop="tplCategory">
+          <template #label>鐢熸垚妯℃澘</template>
+          <el-select v-model="info.tplCategory" @change="tplSelectChange">
+            <el-option label="鍗曡〃锛堝鍒犳敼鏌ワ級" value="crud" />
+            <el-option label="鏍戣〃锛堝鍒犳敼鏌ワ級" value="tree" />
+            <el-option label="涓诲瓙琛紙澧炲垹鏀规煡锛�" value="sub" />
+          </el-select>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item prop="tplWebType">
+          <template #label>鍓嶇绫诲瀷</template>
+          <el-select v-model="info.tplWebType">
+            <el-option label="Vue2 Element UI 妯$増" value="element-ui" />
+            <el-option label="Vue3 Element Plus 妯$増" value="element-plus" />
+          </el-select>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item prop="packageName">
+          <template #label>
+            鐢熸垚鍖呰矾寰�
+            <el-tooltip content="鐢熸垚鍦ㄥ摢涓猨ava鍖呬笅锛屼緥濡� com.ruoyi.system" placement="top">
+              <el-icon><question-filled /></el-icon>
+            </el-tooltip>
+          </template>
+          <el-input v-model="info.packageName" />
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item prop="moduleName">
+          <template #label>
+            鐢熸垚妯″潡鍚�
+            <el-tooltip content="鍙悊瑙d负瀛愮郴缁熷悕锛屼緥濡� system" placement="top">
+              <el-icon><question-filled /></el-icon>
+            </el-tooltip>
+          </template>
+          <el-input v-model="info.moduleName" />
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item prop="businessName">
+          <template #label>
+            鐢熸垚涓氬姟鍚�
+            <el-tooltip content="鍙悊瑙d负鍔熻兘鑻辨枃鍚嶏紝渚嬪 user" placement="top">
+              <el-icon><question-filled /></el-icon>
+            </el-tooltip>
+          </template>
+          <el-input v-model="info.businessName" />
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item prop="functionName">
+          <template #label>
+            鐢熸垚鍔熻兘鍚�
+            <el-tooltip content="鐢ㄤ綔绫绘弿杩帮紝渚嬪 鐢ㄦ埛" placement="top">
+              <el-icon><question-filled /></el-icon>
+            </el-tooltip>
+          </template>
+          <el-input v-model="info.functionName" />
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item prop="genType">
+          <template #label>
+            鐢熸垚浠g爜鏂瑰紡
+            <el-tooltip content="榛樿涓簔ip鍘嬬缉鍖呬笅杞斤紝涔熷彲浠ヨ嚜瀹氫箟鐢熸垚璺緞" placement="top">
+              <el-icon><question-filled /></el-icon>
+            </el-tooltip>
+          </template>
+          <el-radio v-model="info.genType" label="0">zip鍘嬬缉鍖�</el-radio>
+          <el-radio v-model="info.genType" label="1">鑷畾涔夎矾寰�</el-radio>
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="12">
+        <el-form-item>
+          <template #label>
+            涓婄骇鑿滃崟
+            <el-tooltip content="鍒嗛厤鍒版寚瀹氳彍鍗曚笅锛屼緥濡� 绯荤粺绠$悊" placement="top">
+              <el-icon><question-filled /></el-icon>
+            </el-tooltip>
+          </template>
+          <tree-select
+            v-model:value="info.parentMenuId"
+            :options="menuOptions"
+            :objMap="{ value: 'menuId', label: 'menuName', children: 'children' }"
+            placeholder="璇烽�夋嫨绯荤粺鑿滃崟"
+          />
+        </el-form-item>
+      </el-col>
+
+      <el-col :span="24" v-if="info.genType == '1'">
+        <el-form-item prop="genPath">
+          <template #label>
+            鑷畾涔夎矾寰�
+            <el-tooltip content="濉啓纾佺洏缁濆璺緞锛岃嫢涓嶅~鍐欙紝鍒欑敓鎴愬埌褰撳墠Web椤圭洰涓�" placement="top">
+              <el-icon><question-filled /></el-icon>
+            </el-tooltip>
+          </template>
+          <el-input v-model="info.genPath">
+            <template #append>
+              <el-dropdown>
+                <el-button type="primary">
+                  鏈�杩戣矾寰勫揩閫熼�夋嫨
+                  <i class="el-icon-arrow-down el-icon--right"></i>
+                </el-button>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item @click="info.genPath = '/'">鎭㈠榛樿鐨勭敓鎴愬熀纭�璺緞</el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+            </template>
+          </el-input>
+        </el-form-item>
+      </el-col>
+    </el-row>
+    
+    <template v-if="info.tplCategory == 'tree'">
+      <h4 class="form-header">鍏朵粬淇℃伅</h4>
+      <el-row v-show="info.tplCategory == 'tree'">
+        <el-col :span="12">
+          <el-form-item>
+            <template #label>
+              鏍戠紪鐮佸瓧娈�
+              <el-tooltip content="鏍戞樉绀虹殑缂栫爜瀛楁鍚嶏紝 濡傦細dept_id" placement="top">
+                <el-icon><question-filled /></el-icon>
+              </el-tooltip>
+            </template>
+            <el-select v-model="info.treeCode" placeholder="璇烽�夋嫨">
+              <el-option
+                v-for="(column, index) in info.columns"
+                :key="index"
+                :label="column.columnName + '锛�' + column.columnComment"
+                :value="column.columnName"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item>
+            <template #label>
+              鏍戠埗缂栫爜瀛楁
+              <el-tooltip content="鏍戞樉绀虹殑鐖剁紪鐮佸瓧娈靛悕锛� 濡傦細parent_Id" placement="top">
+                <el-icon><question-filled /></el-icon>
+              </el-tooltip>
+            </template>
+            <el-select v-model="info.treeParentCode" placeholder="璇烽�夋嫨">
+              <el-option
+                v-for="(column, index) in info.columns"
+                :key="index"
+                :label="column.columnName + '锛�' + column.columnComment"
+                :value="column.columnName"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item>
+            <template #label>
+              鏍戝悕绉板瓧娈�
+              <el-tooltip content="鏍戣妭鐐圭殑鏄剧ず鍚嶇О瀛楁鍚嶏紝 濡傦細dept_name" placement="top">
+                <el-icon><question-filled /></el-icon>
+              </el-tooltip>
+            </template>
+            <el-select v-model="info.treeName" placeholder="璇烽�夋嫨">
+              <el-option
+                v-for="(column, index) in info.columns"
+                :key="index"
+                :label="column.columnName + '锛�' + column.columnComment"
+                :value="column.columnName"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </template>
+
+    <template v-if="info.tplCategory == 'sub'">
+      <h4 class="form-header">鍏宠仈淇℃伅</h4>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item>
+            <template #label>
+              鍏宠仈瀛愯〃鐨勮〃鍚�
+              <el-tooltip content="鍏宠仈瀛愯〃鐨勮〃鍚嶏紝 濡傦細sys_user" placement="top">
+                <el-icon><question-filled /></el-icon>
+              </el-tooltip>
+            </template>
+            <el-select v-model="info.subTableName" placeholder="璇烽�夋嫨" @change="subSelectChange">
+              <el-option
+                v-for="(table, index) in tables"
+                :key="index"
+                :label="table.tableName + '锛�' + table.tableComment"
+                :value="table.tableName"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item>
+            <template #label>
+              瀛愯〃鍏宠仈鐨勫閿悕
+              <el-tooltip content="瀛愯〃鍏宠仈鐨勫閿悕锛� 濡傦細user_id" placement="top">
+                <el-icon><question-filled /></el-icon>
+              </el-tooltip>
+            </template>
+            <el-select v-model="info.subTableFkName" placeholder="璇烽�夋嫨">
+              <el-option
+                v-for="(column, index) in subColumns"
+                :key="index"
+                :label="column.columnName + '锛�' + column.columnComment"
+                :value="column.columnName"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </template>
+
+  </el-form>
+</template>
+
+<script setup>
+import { listMenu } from "@/api/system/menu";
+
+const subColumns = ref([]);
+const menuOptions = ref([]);
+const { proxy } = getCurrentInstance();
+
+const props = defineProps({
+  info: {
+    type: Object,
+    default: null
+  },
+  tables: {
+    type: Array,
+    default: null
+  }
+});
+
+// 琛ㄥ崟鏍¢獙
+const rules = ref({
+  tplCategory: [{ required: true, message: "璇烽�夋嫨鐢熸垚妯℃澘", trigger: "blur" }],
+  packageName: [{ required: true, message: "璇疯緭鍏ョ敓鎴愬寘璺緞", trigger: "blur" }],
+  moduleName: [{ required: true, message: "璇疯緭鍏ョ敓鎴愭ā鍧楀悕", trigger: "blur" }],
+  businessName: [{ required: true, message: "璇疯緭鍏ョ敓鎴愪笟鍔″悕", trigger: "blur" }],
+  functionName: [{ required: true, message: "璇疯緭鍏ョ敓鎴愬姛鑳藉悕", trigger: "blur" }]
+});
+function subSelectChange(value) {
+  props.info.subTableFkName = "";
+}
+function tplSelectChange(value) {
+  if (value !== "sub") {
+    props.info.subTableName = "";
+    props.info.subTableFkName = "";
+  }
+}
+function setSubTableColumns(value) {
+  for (var item in props.tables) {
+    const name = props.tables[item].tableName;
+    if (value === name) {
+      subColumns.value = props.tables[item].columns;
+      break;
+    }
+  }
+}
+/** 鏌ヨ鑿滃崟涓嬫媺鏍戠粨鏋� */
+function getMenuTreeselect() {
+  listMenu().then(response => {
+    menuOptions.value = proxy.handleTree(response.data, "menuId");
+  });
+}
+
+watch(() => props.info.subTableName, val => {
+  setSubTableColumns(val);
+});
+
+watch(() => props.info.tplWebType, val => {
+  if (val === '') {
+    props.info.tplWebType = "element-plus";
+  }
+});
+
+getMenuTreeselect();
+</script>
diff --git a/zhitan-vue/src/views/tool/gen/importTable.vue b/zhitan-vue/src/views/tool/gen/importTable.vue
new file mode 100644
index 0000000..33b5633
--- /dev/null
+++ b/zhitan-vue/src/views/tool/gen/importTable.vue
@@ -0,0 +1,118 @@
+<template>
+  <!-- 瀵煎叆琛� -->
+  <el-dialog title="瀵煎叆琛�" v-model="visible" width="800px" top="5vh" append-to-body>
+    <el-form :model="queryParams" ref="queryRef" :inline="true">
+      <el-form-item label="琛ㄥ悕绉�" prop="tableName">
+        <el-input
+          v-model="queryParams.tableName"
+          placeholder="璇疯緭鍏ヨ〃鍚嶇О"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="琛ㄦ弿杩�" prop="tableComment">
+        <el-input
+          v-model="queryParams.tableComment"
+          placeholder="璇疯緭鍏ヨ〃鎻忚堪"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+        <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+      </el-form-item>
+    </el-form>
+    <el-row>
+      <el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px">
+        <el-table-column type="selection" width="55"></el-table-column>
+        <el-table-column prop="tableName" label="琛ㄥ悕绉�" :show-overflow-tooltip="true"></el-table-column>
+        <el-table-column prop="tableComment" label="琛ㄦ弿杩�" :show-overflow-tooltip="true"></el-table-column>
+        <el-table-column prop="createTime" label="鍒涘缓鏃堕棿"></el-table-column>
+        <el-table-column prop="updateTime" label="鏇存柊鏃堕棿"></el-table-column>
+      </el-table>
+      <pagination
+        v-show="total>0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </el-row>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="handleImportTable">纭� 瀹�</el-button>
+        <el-button @click="visible = false">鍙� 娑�</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { listDbTable, importTable } from "@/api/tool/gen";
+
+const total = ref(0);
+const visible = ref(false);
+const tables = ref([]);
+const dbTableList = ref([]);
+const { proxy } = getCurrentInstance();
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  tableName: undefined,
+  tableComment: undefined
+});
+
+const emit = defineEmits(["ok"]);
+
+/** 鏌ヨ鍙傛暟鍒楄〃 */
+function show() {
+  getList();
+  visible.value = true;
+}
+/** 鍗曞嚮閫夋嫨琛� */
+function clickRow(row) {
+  proxy.$refs.table.toggleRowSelection(row);
+}
+/** 澶氶�夋閫変腑鏁版嵁 */
+function handleSelectionChange(selection) {
+  tables.value = selection.map(item => item.tableName);
+}
+/** 鏌ヨ琛ㄦ暟鎹� */
+function getList() {
+  listDbTable(queryParams).then(res => {
+    dbTableList.value = res.rows;
+    total.value = res.total;
+  });
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.pageNum = 1;
+  getList();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+/** 瀵煎叆鎸夐挳鎿嶄綔 */
+function handleImportTable() {
+  const tableNames = tables.value.join(",");
+  if (tableNames == "") {
+    proxy.$modal.msgError("璇烽�夋嫨瑕佸鍏ョ殑琛�");
+    return;
+  }
+  importTable({ tables: tableNames }).then(res => {
+    proxy.$modal.msgSuccess(res.msg);
+    if (res.code === 200) {
+      visible.value = false;
+      emit("ok");
+    }
+  });
+}
+
+defineExpose({
+  show,
+});
+</script>
diff --git a/zhitan-vue/src/views/tool/gen/index.vue b/zhitan-vue/src/views/tool/gen/index.vue
new file mode 100644
index 0000000..bea6531
--- /dev/null
+++ b/zhitan-vue/src/views/tool/gen/index.vue
@@ -0,0 +1,299 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
+      <el-form-item label="琛ㄥ悕绉�" prop="tableName">
+        <el-input
+          v-model="queryParams.tableName"
+          placeholder="璇疯緭鍏ヨ〃鍚嶇О"
+          clearable
+          style="width: 200px"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="琛ㄦ弿杩�" prop="tableComment">
+        <el-input
+          v-model="queryParams.tableComment"
+          placeholder="璇疯緭鍏ヨ〃鎻忚堪"
+          clearable
+          style="width: 200px"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="鍒涘缓鏃堕棿" style="width: 308px">
+        <el-date-picker
+          v-model="dateRange"
+          value-format="YYYY-MM-DD"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="寮�濮嬫棩鏈�"
+          end-placeholder="缁撴潫鏃ユ湡"
+        ></el-date-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">鎼滅储</el-button>
+        <el-button icon="Refresh" @click="resetQuery">閲嶇疆</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="Download"
+          :disabled="multiple"
+          @click="handleGenTable"
+          v-hasPermi="['tool:gen:code']"
+        >鐢熸垚</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="Plus"
+          @click="openCreateTable"
+          v-hasRole="['admin']"
+        >鍒涘缓</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="Upload"
+          @click="openImportTable"
+          v-hasPermi="['tool:gen:import']"
+        >瀵煎叆</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="Edit"
+          :disabled="single"
+          @click="handleEditTable"
+          v-hasPermi="['tool:gen:edit']"
+        >淇敼</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="Delete"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['tool:gen:remove']"
+        >鍒犻櫎</el-button>
+      </el-col>
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="tableList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" align="center" width="55"></el-table-column>
+      <el-table-column label="搴忓彿" type="index" width="50" align="center">
+        <template #default="scope">
+          <span>{{(queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1}}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="琛ㄥ悕绉�"
+        align="center"
+        prop="tableName"
+        :show-overflow-tooltip="true"
+      />
+      <el-table-column
+        label="琛ㄦ弿杩�"
+        align="center"
+        prop="tableComment"
+        :show-overflow-tooltip="true"
+      />
+      <el-table-column
+        label="瀹炰綋"
+        align="center"
+        prop="className"
+        :show-overflow-tooltip="true"
+      />
+      <el-table-column label="鍒涘缓鏃堕棿" align="center" prop="createTime" width="160" />
+      <el-table-column label="鏇存柊鏃堕棿" align="center" prop="updateTime" width="160" />
+      <el-table-column label="鎿嶄綔" align="center" width="330" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-tooltip content="棰勮" placement="top">
+            <el-button link type="primary" icon="View" @click="handlePreview(scope.row)" v-hasPermi="['tool:gen:preview']"></el-button>
+          </el-tooltip>
+          <el-tooltip content="缂栬緫" placement="top">
+            <el-button link type="primary" icon="Edit" @click="handleEditTable(scope.row)" v-hasPermi="['tool:gen:edit']"></el-button>
+          </el-tooltip>
+          <el-tooltip content="鍒犻櫎" placement="top">
+            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['tool:gen:remove']"></el-button>
+          </el-tooltip>
+          <el-tooltip content="鍚屾" placement="top">
+            <el-button link type="primary" icon="Refresh" @click="handleSynchDb(scope.row)" v-hasPermi="['tool:gen:edit']"></el-button>
+          </el-tooltip>
+          <el-tooltip content="鐢熸垚浠g爜" placement="top">
+            <el-button link type="primary" icon="Download" @click="handleGenTable(scope.row)" v-hasPermi="['tool:gen:code']"></el-button>
+          </el-tooltip>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination
+      v-show="total>0"
+      :total="total"
+      v-model:page="queryParams.pageNum"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+    <!-- 棰勮鐣岄潰 -->
+    <el-dialog :title="preview.title" v-model="preview.open" width="80%" top="5vh" append-to-body class="scrollbar">
+      <el-tabs v-model="preview.activeName">
+        <el-tab-pane
+          v-for="(value, key) in preview.data"
+          :label="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
+          :name="key.substring(key.lastIndexOf('/')+1,key.indexOf('.vm'))"
+          :key="value"
+        >
+          <el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess" style="float:right">&nbsp;澶嶅埗</el-link>
+          <pre>{{ value }}</pre>
+        </el-tab-pane>
+      </el-tabs>
+    </el-dialog>
+    <import-table ref="importRef" @ok="handleQuery" />
+    <create-table ref="createRef" @ok="handleQuery" />
+  </div>
+</template>
+
+<script setup name="Gen">
+import { listTable, previewTable, delTable, genCode, synchDb } from "@/api/tool/gen";
+import router from "@/router";
+import importTable from "./importTable";
+import createTable from "./createTable";
+
+const route = useRoute();
+const { proxy } = getCurrentInstance();
+
+const tableList = ref([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const tableNames = ref([]);
+const dateRange = ref([]);
+const uniqueId = ref("");
+
+const data = reactive({
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    tableName: undefined,
+    tableComment: undefined
+  },
+  preview: {
+    open: false,
+    title: "浠g爜棰勮",
+    data: {},
+    activeName: "domain.java"
+  }
+});
+
+const { queryParams, preview } = toRefs(data);
+
+onActivated(() => {
+  const time = route.query.t;
+  if (time != null && time != uniqueId.value) {
+    uniqueId.value = time;
+    queryParams.value.pageNum = Number(route.query.pageNum);
+    dateRange.value = [];
+    proxy.resetForm("queryForm");
+    getList();
+  }
+})
+
+/** 鏌ヨ琛ㄩ泦鍚� */
+function getList() {
+  loading.value = true;
+  listTable(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
+    tableList.value = response.rows;
+    total.value = response.total;
+    loading.value = false;
+  });
+}
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 鐢熸垚浠g爜鎿嶄綔 */
+function handleGenTable(row) {
+  const tbNames = row.tableName || tableNames.value;
+  if (tbNames == "") {
+    proxy.$modal.msgError("璇烽�夋嫨瑕佺敓鎴愮殑鏁版嵁");
+    return;
+  }
+  if (row.genType === "1") {
+    genCode(row.tableName).then(response => {
+      proxy.$modal.msgSuccess("鎴愬姛鐢熸垚鍒拌嚜瀹氫箟璺緞锛�" + row.genPath);
+    });
+  } else {
+    proxy.$download.zip("/tool/gen/batchGenCode?tables=" + tbNames, "ruoyi.zip");
+  }
+}
+/** 鍚屾鏁版嵁搴撴搷浣� */
+function handleSynchDb(row) {
+  const tableName = row.tableName;
+  proxy.$modal.confirm('纭瑕佸己鍒跺悓姝�"' + tableName + '"琛ㄧ粨鏋勫悧锛�').then(function () {
+    return synchDb(tableName);
+  }).then(() => {
+    proxy.$modal.msgSuccess("鍚屾鎴愬姛");
+  }).catch(() => {});
+}
+/** 鎵撳紑瀵煎叆琛ㄥ脊绐� */
+function openImportTable() {
+  proxy.$refs["importRef"].show();
+}
+/** 鎵撳紑鍒涘缓琛ㄥ脊绐� */
+function openCreateTable() {
+  proxy.$refs["createRef"].show();
+}
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+function resetQuery() {
+  dateRange.value = [];
+  proxy.resetForm("queryRef");
+  handleQuery();
+}
+/** 棰勮鎸夐挳 */
+function handlePreview(row) {
+  previewTable(row.tableId).then(response => {
+    preview.value.data = response.data;
+    preview.value.open = true;
+    preview.value.activeName = "domain.java";
+  });
+}
+/** 澶嶅埗浠g爜鎴愬姛 */
+function copyTextSuccess() {
+  proxy.$modal.msgSuccess("澶嶅埗鎴愬姛");
+}
+// 澶氶�夋閫変腑鏁版嵁
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.tableId);
+  tableNames.value = selection.map(item => item.tableName);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+/** 淇敼鎸夐挳鎿嶄綔 */
+function handleEditTable(row) {
+  const tableId = row.tableId || ids.value[0];
+  router.push({ path: "/tool/gen-edit/index/" + tableId, query: { pageNum: queryParams.value.pageNum } });
+}
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+function handleDelete(row) {
+  const tableIds = row.tableId || ids.value;
+  proxy.$modal.confirm('鏄惁纭鍒犻櫎琛ㄧ紪鍙蜂负"' + tableIds + '"鐨勬暟鎹」锛�').then(function () {
+    return delTable(tableIds);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+  }).catch(() => {});
+}
+
+getList();
+</script>
diff --git a/zhitan-vue/src/views/tool/swagger/index.vue b/zhitan-vue/src/views/tool/swagger/index.vue
new file mode 100644
index 0000000..a44fa71
--- /dev/null
+++ b/zhitan-vue/src/views/tool/swagger/index.vue
@@ -0,0 +1,9 @@
+<template>
+   <i-frame v-model:src="url"></i-frame>
+</template>
+
+<script setup>
+import iFrame from '@/components/iFrame'
+
+const url = ref(import.meta.env.VITE_APP_BASE_API + "/swagger-ui/index.html")
+</script>
diff --git a/zhitan-vue/vite.config.js b/zhitan-vue/vite.config.js
new file mode 100644
index 0000000..dcba260
--- /dev/null
+++ b/zhitan-vue/vite.config.js
@@ -0,0 +1,58 @@
+import { defineConfig, loadEnv } from 'vite'
+import path from 'path'
+import createVitePlugins from './vite/plugins'
+
+// https://vitejs.dev/config/
+export default defineConfig(({ mode, command }) => {
+  const env = loadEnv(mode, process.cwd())
+  const { VITE_APP_ENV } = env
+  return {
+    // 閮ㄧ讲鐢熶骇鐜鍜屽紑鍙戠幆澧冧笅鐨刄RL銆�
+    // 榛樿鎯呭喌涓嬶紝vite 浼氬亣璁句綘鐨勫簲鐢ㄦ槸琚儴缃插湪涓�涓煙鍚嶇殑鏍硅矾寰勪笂
+    // 渚嬪 https://www.ruoyi.vip/銆傚鏋滃簲鐢ㄨ閮ㄧ讲鍦ㄤ竴涓瓙璺緞涓婏紝浣犲氨闇�瑕佺敤杩欎釜閫夐」鎸囧畾杩欎釜瀛愯矾寰勩�備緥濡傦紝濡傛灉浣犵殑搴旂敤琚儴缃插湪 https://www.ruoyi.vip/admin/锛屽垯璁剧疆 baseUrl 涓� /admin/銆�
+    base: VITE_APP_ENV === 'production' ? '/' : '/',
+    plugins: createVitePlugins(env, command === 'build'),
+    resolve: {
+      // https://cn.vitejs.dev/config/#resolve-alias
+      alias: {
+        // 璁剧疆璺緞
+        '~': path.resolve(__dirname, './'),
+        // 璁剧疆鍒悕
+        '@': path.resolve(__dirname, './src')
+      },
+      // https://cn.vitejs.dev/config/#resolve-extensions
+      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
+    },
+    // vite 鐩稿叧閰嶇疆
+    server: {
+      port: 80,
+      host: true,
+      open: true,
+      proxy: {
+        // https://cn.vitejs.dev/config/#server-proxy
+        '/dev-api': {
+          target: 'http://127.0.0.1:8080',
+          changeOrigin: true,
+          rewrite: (p) => p.replace(/^\/dev-api/, '')
+        }
+      }
+    },
+    //fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file
+    css: {
+      postcss: {
+        plugins: [
+          {
+            postcssPlugin: 'internal:charset-removal',
+            AtRule: {
+              charset: (atRule) => {
+                if (atRule.name === 'charset') {
+                  atRule.remove();
+                }
+              }
+            }
+          }
+        ]
+      }
+    }
+  }
+})
diff --git a/zhitan-vue/vite/plugins/auto-import.js b/zhitan-vue/vite/plugins/auto-import.js
new file mode 100644
index 0000000..a5d3576
--- /dev/null
+++ b/zhitan-vue/vite/plugins/auto-import.js
@@ -0,0 +1,12 @@
+import autoImport from 'unplugin-auto-import/vite'
+
+export default function createAutoImport() {
+    return autoImport({
+        imports: [
+            'vue',
+            'vue-router',
+            'pinia'
+        ],
+        dts: false
+    })
+}
diff --git a/zhitan-vue/vite/plugins/compression.js b/zhitan-vue/vite/plugins/compression.js
new file mode 100644
index 0000000..9dc462a
--- /dev/null
+++ b/zhitan-vue/vite/plugins/compression.js
@@ -0,0 +1,27 @@
+import compression from 'vite-plugin-compression'
+
+export default function createCompression(env) {
+    const { VITE_BUILD_COMPRESS } = env
+    const plugin = []
+    if (VITE_BUILD_COMPRESS) {
+        const compressList = VITE_BUILD_COMPRESS.split(',')
+        if (compressList.includes('gzip')) {
+            plugin.push(
+                compression({
+                    ext: '.gz',
+                    deleteOriginFile: false
+                })
+            )
+        }
+        if (compressList.includes('brotli')) {
+            plugin.push(
+                compression({
+                    ext: '.br',
+                    algorithm: 'brotliCompress',
+                    deleteOriginFile: false
+                })
+            )
+        }
+    }
+    return plugin
+}
diff --git a/zhitan-vue/vite/plugins/index.js b/zhitan-vue/vite/plugins/index.js
new file mode 100644
index 0000000..10e17c3
--- /dev/null
+++ b/zhitan-vue/vite/plugins/index.js
@@ -0,0 +1,15 @@
+import vue from '@vitejs/plugin-vue'
+
+import createAutoImport from './auto-import'
+import createSvgIcon from './svg-icon'
+import createCompression from './compression'
+import createSetupExtend from './setup-extend'
+
+export default function createVitePlugins(viteEnv, isBuild = false) {
+    const vitePlugins = [vue()]
+    vitePlugins.push(createAutoImport())
+	vitePlugins.push(createSetupExtend())
+    vitePlugins.push(createSvgIcon(isBuild))
+	isBuild && vitePlugins.push(...createCompression(viteEnv))
+    return vitePlugins
+}
diff --git a/zhitan-vue/vite/plugins/setup-extend.js b/zhitan-vue/vite/plugins/setup-extend.js
new file mode 100644
index 0000000..ed8342e
--- /dev/null
+++ b/zhitan-vue/vite/plugins/setup-extend.js
@@ -0,0 +1,5 @@
+import setupExtend from 'unplugin-vue-setup-extend-plus/vite'
+
+export default function createSetupExtend() {
+    return setupExtend({})
+}
diff --git a/zhitan-vue/vite/plugins/svg-icon.js b/zhitan-vue/vite/plugins/svg-icon.js
new file mode 100644
index 0000000..30a4140
--- /dev/null
+++ b/zhitan-vue/vite/plugins/svg-icon.js
@@ -0,0 +1,10 @@
+import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
+import path from 'path'
+
+export default function createSvgIcon(isBuild) {
+    return createSvgIconsPlugin({
+		iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/svg')],
+        symbolId: 'icon-[dir]-[name]',
+        svgoOptions: isBuild
+    })
+}

--
Gitblit v1.9.3