zhanghua
2024-11-22 33f7c189aee851636f57cd0de88812332944f185
整体样式+首页
20个文件已修改
6个文件已添加
4972 ■■■■■ 已修改文件
src/api/message/index.js 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/btn.scss 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-ui.scss 117 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/index.scss 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/mixin.scss 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/ruoyi.scss 114 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/sidebar.scss 126 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/transition.scss 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/variables.module.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/variables.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TopNav/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 409 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Notice/index.vue 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/index.vue 629 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TopBar/search.vue 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/index.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/ruoyi.js 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/validate.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/components/noticeTable.vue 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/components/projectOverview.vue 308 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/components/tidingsTable.vue 283 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 1413 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 652 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/message/index.js
New file
@@ -0,0 +1,50 @@
import request from '@/utils/request';
import {
  AxiosPromise
} from 'axios';
// 获取消息详情
export function getMessage(params) {
  return request({
    url: '/audit-message',
    method: 'get',
    params: params
  });
}
//消息数量
export function getMessageCount() {
  return request({
    url: '/message-count',
    method: 'get'
  });
}
//获取阅读
export function getRead(id) {
  return request({
    url: '/read-message',
    method: 'get',
    params: {
      id
    }
  });
}
//代办
export function getTodo(params) {
  return request({
    url: '/getPageByAllTaskWait',
    method: 'get',
    params: params
  });
}
// 异常项目统计
export function getCountExceptionProject(params) {
  return request({
    url: '/countExceptionProject',
    method: 'get',
    params: params
  });
}
src/assets/styles/btn.scss
@@ -1,4 +1,4 @@
@import './variables.scss';
@import './variables.module.scss';
@mixin colorBtn($color) {
  background: $color;
@@ -14,35 +14,35 @@
}
.blue-btn {
  @include colorBtn($blue)
  @include colorBtn($blue);
}
.light-blue-btn {
  @include colorBtn($light-blue)
  @include colorBtn($light-blue);
}
.red-btn {
  @include colorBtn($red)
  @include colorBtn($red);
}
.pink-btn {
  @include colorBtn($pink)
  @include colorBtn($pink);
}
.green-btn {
  @include colorBtn($green)
  @include colorBtn($green);
}
.tiffany-btn {
  @include colorBtn($tiffany)
  @include colorBtn($tiffany);
}
.yellow-btn {
  @include colorBtn($yellow)
  @include colorBtn($yellow);
}
.pan-btn {
  font-size: 14px;
  font-size: 12px;
  color: #fff;
  padding: 14px 36px;
  border-radius: 8px;
@@ -94,6 +94,6 @@
  outline: 0;
  margin: 0;
  padding: 10px 15px;
  font-size: 14px;
  font-size: 12px;
  border-radius: 4px;
}
src/assets/styles/element-ui.scss
@@ -1,4 +1,20 @@
// cover some element-ui styles
.el-collapse {
  .collapse__title {
    font-weight: 600;
    padding: 0 8px;
    font-size: 1.2em;
    line-height: 1.1em;
  }
  .el-collapse-item__content {
    padding: 0 8px;
  }
}
.el-divider--horizontal {
  margin-bottom: 10px;
  margin-top: 10px;
}
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
@@ -6,7 +22,7 @@
}
.el-upload {
  input[type="file"] {
  input[type='file'] {
    display: none !important;
  }
}
@@ -46,12 +62,37 @@
  }
}
// to fixed https://github.com/ElemeFE/element/issues/2461
.el-dialog {
  transform: none;
  left: 0;
  position: relative;
  margin: 0 auto;
/*-------------Dialog-------------**/
.el-overlay {
  overflow: hidden;
  .el-overlay-dialog {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    .el-dialog {
      margin: 0 auto !important;
      .el-dialog__body {
        padding: 15px !important;
      }
      .el-dialog__header {
        padding: 16px 16px 8px 16px;
        box-sizing: border-box;
        border-bottom: 1px solid var(--brder-color);
        margin-right: 0;
      }
    }
  }
}
.el-dialog__body {
  max-height: calc(90vh - 111px) !important;
  overflow-y: auto;
  overflow-x: hidden;
}
// refine element ui upload
@@ -69,7 +110,7 @@
// dropdown
.el-dropdown-menu {
  a {
    display: block
    display: block;
  }
}
@@ -83,19 +124,57 @@
  box-sizing: content-box;
}
.el-menu--collapse
  > div
  > .el-submenu
  > .el-submenu__title
  .el-submenu__icon-arrow {
.el-menu--collapse > div > .el-submenu > .el-submenu__title .el-submenu__icon-arrow {
  display: none;
}
.el-tabs--border-card > .el-tabs__content {
  padding: 0;
.el-dropdown .el-dropdown-link {
  color: var(--el-color-primary) !important;
}
.search-bar .el-button {
  height: 32px;
/* 当 el-form 的 inline 属性为 true 时 */
/* 设置 label 的宽度默认为 68px */
.el-form--inline .el-form-item__label {
  width: 68px;
}
/* 设置 el-select 的宽度默认为 240px */
.el-form--inline .el-select {
  width: 240px;
}
/* 设置 el-input 的宽度默认为 240px */
.el-form--inline .el-input {
  width: 240px;
}
// 弹窗全局样式
.el-dialog__header {
  height: 55px;
  line-height: 55px;
  margin-bottom: 0px !important;
  border-bottom: 1px solid #e6f2fd;
  padding-top: 0 !important;
  justify-content: space-between;
}
.el-dialog__header > :first-child {
  flex: none !important;
  border-bottom: 2px solid #0068ff;
  color: #333333;
}
// 右侧弹窗全局样式
.el-drawer__header {
  height: 55px;
  line-height: 55px;
  margin-bottom: 0px !important;
  border-bottom: 1px solid #e6f2fd;
  padding-top: 0 !important;
  justify-content: space-between;
}
.el-drawer__header > :first-child {
  flex: none !important;
  border-bottom: 2px solid #0068ff;
  color: #333333;
}
src/assets/styles/index.scss
@@ -1,25 +1,41 @@
@import './variables.scss';
@import './variables.module.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';
@import './btn.scss';
@import './ruoyi.scss';
// @import 'animate.css';
// @import 'element-plus/dist/index.css';
:root{--el-color-white:#ffffff;--el-color-black:#000000;--el-color-primary-rgb:64,158,255;--el-color-success-rgb:103,194,58;--el-color-warning-rgb:230,162,60;--el-color-danger-rgb:245,108,108;--el-color-error-rgb:245,108,108;--el-color-info-rgb:144,147,153;--el-font-size-extra-large:20px;--el-font-size-large:18px;--el-font-size-medium:16px;--el-font-size-base:14px;--el-font-size-small:13px;--el-font-size-extra-small:12px;--el-font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;--el-font-weight-primary:500;--el-font-line-height-primary:24px;--el-index-normal:1;--el-index-top:1000;--el-index-popper:2000;--el-border-radius-base:4px;--el-border-radius-small:2px;--el-border-radius-round:20px;--el-border-radius-circle:100%;--el-transition-duration:0.3s;--el-transition-duration-fast:0.2s;--el-transition-function-ease-in-out-bezier:cubic-bezier(0.645,0.045,0.355,1);--el-transition-function-fast-bezier:cubic-bezier(0.23,1,0.32,1);--el-transition-all:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);--el-transition-fade:opacity var(--el-transition-duration) var(--el-transition-function-fast-bezier);--el-transition-md-fade:transform var(--el-transition-duration) var(--el-transition-function-fast-bezier),opacity var(--el-transition-duration) var(--el-transition-function-fast-bezier);--el-transition-fade-linear:opacity var(--el-transition-duration-fast) linear;--el-transition-border:border-color var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-transition-box-shadow:box-shadow var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-transition-color:color var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-component-size-large:40px;--el-component-size:32px;--el-component-size-small:24px;color-scheme:light;--el-color-primary:#409eff;--el-color-primary-light-3:#79bbff;--el-color-primary-light-5:#a0cfff;--el-color-primary-light-7:#c6e2ff;--el-color-primary-light-8:#d9ecff;--el-color-primary-light-9:#ecf5ff;--el-color-primary-dark-2:#337ecc;--el-color-success:#67c23a;--el-color-success-light-3:#95d475;--el-color-success-light-5:#b3e19d;--el-color-success-light-7:#d1edc4;--el-color-success-light-8:#e1f3d8;--el-color-success-light-9:#f0f9eb;--el-color-success-dark-2:#529b2e;--el-color-warning:#e6a23c;--el-color-warning-light-3:#eebe77;--el-color-warning-light-5:#f3d19e;--el-color-warning-light-7:#f8e3c5;--el-color-warning-light-8:#faecd8;--el-color-warning-light-9:#fdf6ec;--el-color-warning-dark-2:#b88230;--el-color-danger:#f56c6c;--el-color-danger-light-3:#f89898;--el-color-danger-light-5:#fab6b6;--el-color-danger-light-7:#fcd3d3;--el-color-danger-light-8:#fde2e2;--el-color-danger-light-9:#fef0f0;--el-color-danger-dark-2:#c45656;--el-color-error:#f56c6c;--el-color-error-light-3:#f89898;--el-color-error-light-5:#fab6b6;--el-color-error-light-7:#fcd3d3;--el-color-error-light-8:#fde2e2;--el-color-error-light-9:#fef0f0;--el-color-error-dark-2:#c45656;--el-color-info:#909399;--el-color-info-light-3:#b1b3b8;--el-color-info-light-5:#c8c9cc;--el-color-info-light-7:#dedfe0;--el-color-info-light-8:#e9e9eb;--el-color-info-light-9:#f4f4f5;--el-color-info-dark-2:#73767a;--el-bg-color:#ffffff;--el-bg-color-page:#f2f3f5;--el-bg-color-overlay:#ffffff;--el-text-color-primary:#303133;--el-text-color-regular:#606266;--el-text-color-secondary:#909399;--el-text-color-placeholder:#a8abb2;--el-text-color-disabled:#c0c4cc;--el-border-color:#dcdfe6;--el-border-color-light:#e4e7ed;--el-border-color-lighter:#ebeef5;--el-border-color-extra-light:#f2f6fc;--el-border-color-dark:#d4d7de;--el-border-color-darker:#cdd0d6;--el-fill-color:#f0f2f5;--el-fill-color-light:#f5f7fa;--el-fill-color-lighter:#fafafa;--el-fill-color-extra-light:#fafcff;--el-fill-color-dark:#ebedf0;--el-fill-color-darker:#e6e8eb;--el-fill-color-blank:#ffffff;--el-box-shadow:0px 12px 32px 4px rgba(0,0,0,0.04),0px 8px 20px rgba(0,0,0,0.08);--el-box-shadow-light:0px 0px 12px rgba(0,0,0,0.12);--el-box-shadow-lighter:0px 0px 6px rgba(0,0,0,0.12);--el-box-shadow-dark:0px 16px 48px 16px rgba(0,0,0,0.08),0px 12px 32px rgba(0,0,0,0.12),0px 8px 16px -8px rgba(0,0,0,0.16);--el-disabled-bg-color:var(--el-fill-color-light);--el-disabled-text-color:var(--el-text-color-placeholder);--el-disabled-border-color:var(--el-border-color-light);--el-overlay-color:rgba(0,0,0,0.8);--el-overlay-color-light:rgba(0,0,0,0.7);--el-overlay-color-lighter:rgba(0,0,0,0.5);--el-mask-color:rgba(255,255,255,0.9);--el-mask-color-extra-light:rgba(255,255,255,0.3);--el-border-width:1px;--el-border-style:solid;--el-border-color-hover:var(--el-text-color-disabled);--el-border:var(--el-border-width) var(--el-border-style) var(--el-border-color);--el-svg-monochrome-grey:var(--el-border-color)}.fade-in-linear-enter-active,.fade-in-linear-leave-active{transition:var(--el-transition-fade-linear)}.fade-in-linear-enter-from,.fade-in-linear-leave-to{opacity:0}.el-fade-in-linear-enter-active,.el-fade-in-linear-leave-active{transition:var(--el-transition-fade-linear)}.el-fade-in-linear-enter-from,.el-fade-in-linear-leave-to{opacity:0}.el-fade-in-enter-active,.el-fade-in-leave-active{transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-fade-in-enter-from,.el-fade-in-leave-active{opacity:0}.el-zoom-in-center-enter-active,.el-zoom-in-center-leave-active{transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-zoom-in-center-enter-from,.el-zoom-in-center-leave-active{opacity:0;transform:scaleX(0)}.el-zoom-in-top-enter-active,.el-zoom-in-top-leave-active{opacity:1;transform:scaleY(1);transform-origin:center top;transition:var(--el-transition-md-fade)}.el-zoom-in-top-enter-active[data-popper-placement^=top],.el-zoom-in-top-leave-active[data-popper-placement^=top]{transform-origin:center bottom}.el-zoom-in-top-enter-from,.el-zoom-in-top-leave-active{opacity:0;transform:scaleY(0)}.el-zoom-in-bottom-enter-active,.el-zoom-in-bottom-leave-active{opacity:1;transform:scaleY(1);transform-origin:center bottom;transition:var(--el-transition-md-fade)}.el-zoom-in-bottom-enter-from,.el-zoom-in-bottom-leave-active{opacity:0;transform:scaleY(0)}.el-zoom-in-left-enter-active,.el-zoom-in-left-leave-active{opacity:1;transform:scale(1);transform-origin:top left;transition:var(--el-transition-md-fade)}.el-zoom-in-left-enter-from,.el-zoom-in-left-leave-active{opacity:0;transform:scale(.45)}.collapse-transition{transition:var(--el-transition-duration) height ease-in-out,var(--el-transition-duration) padding-top ease-in-out,var(--el-transition-duration) padding-bottom ease-in-out}.el-collapse-transition-enter-active,.el-collapse-transition-leave-active{transition:var(--el-transition-duration) max-height ease-in-out,var(--el-transition-duration) padding-top ease-in-out,var(--el-transition-duration) padding-bottom ease-in-out}.horizontal-collapse-transition{transition:var(--el-transition-duration) width ease-in-out,var(--el-transition-duration) padding-left ease-in-out,var(--el-transition-duration) padding-right ease-in-out}.el-list-enter-active,.el-list-leave-active{transition:all 1s}.el-list-enter-from,.el-list-leave-to{opacity:0;transform:translateY(-30px)}.el-list-leave-active{position:absolute!important}.el-opacity-transition{transition:opacity var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-icon-loading{animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@keyframes rotating{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.el-icon{--color:inherit;align-items:center;display:inline-flex;height:1em;justify-content:center;line-height:1em;position:relative;width:1em;fill:currentColor;color:var(--color);font-size:inherit}.el-icon.is-loading{animation:rotating 2s linear infinite}.el-icon svg{height:1em;width:1em}
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;
  font-family: Helvetica Neue,
  Helvetica,
  PingFang SC,
  Hiragino Sans GB,
  Microsoft YaHei,
  Arial,
  sans-serif;
}
label {
  font-weight: 700;
  font-weight: 400;
}
html {
  height: 100%;
  box-sizing: border-box;
}
html.dark .svg-icon,
html.dark svg {
  fill: var(--el-text-color-regular);
}
#app {
@@ -90,7 +106,7 @@
    visibility: hidden;
    display: block;
    font-size: 0;
    content: " ";
    content: ' ';
    clear: both;
    height: 0;
  }
@@ -104,7 +120,8 @@
  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;
  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;
@@ -124,6 +141,22 @@
  padding: 20px;
}
// search面板样式
.panel,
.search {
  margin-bottom: 0.75rem;
  border-radius: 0.25rem;
  border: 1px solid var(--el-border-color-light);
  background-color: var(--el-bg-color-overlay);
  padding: 0.75rem;
  transition: all ease 0.3s;
  &:hover {
    box-shadow: 0 2px 12px #0000001a;
    transition: all ease 0.3s;
  }
}
.components-container {
  margin: 30px 50px;
  position: relative;
@@ -134,7 +167,7 @@
}
.text-center {
  text-align: center
  text-align: center;
}
.sub-navbar {
@@ -180,3 +213,16 @@
    margin-bottom: 10px;
  }
}
.el-drawer__header {
  display: flex;
  justify-content: space-between;
  border-bottom: 1px solid #E9F4FD;
  padding: 100px;
}
.el-drawer__close-btn {
  width: 88%;
  display: flex;
  justify-content: flex-end;
}
src/assets/styles/mixin.scss
@@ -1,6 +1,6 @@
@mixin clearfix {
  &:after {
    content: "";
    content: '';
    display: table;
    clear: both;
  }
@@ -44,21 +44,15 @@
    border-bottom: $color-border-style;
    border-left: $transparent-border-style;
    border-right: $transparent-border-style;
  }
  @else if $direction==right {
  } @else if $direction==right {
    border-left: $color-border-style;
    border-top: $transparent-border-style;
    border-bottom: $transparent-border-style;
  }
  @else if $direction==down {
  } @else if $direction==down {
    border-top: $color-border-style;
    border-left: $transparent-border-style;
    border-right: $transparent-border-style;
  }
  @else if $direction==left {
  } @else if $direction==left {
    border-right: $color-border-style;
    border-top: $transparent-border-style;
    border-bottom: $transparent-border-style;
src/assets/styles/ruoyi.scss
@@ -1,106 +1,108 @@
/**
* 通用css样式布局处理
* Copyright (c) 2019 ruoyi
*/
 * 通用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;
  margin-left: 10px;
}
.mt20 {
  margin-top: 20px;
}
.mr20 {
  margin-right: 20px;
}
.mb20 {
  margin-bottom: 20px;
}
.ml20 {
    margin-left: 20px;
  margin-left: 20px;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
.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-message-box__status + .el-message-box__message{
  word-break: break-word;
.el-form .el-form-item__label {
  font-size: 12px;
  font-weight: 400;
  color: #454B5E;
}
.el-dialog:not(.is-fullscreen) {
  margin-top: 6vh !important;
}
.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body {
.el-dialog.scrollbar .el-dialog__body {
  overflow: auto;
  overflow-x: hidden;
  max-height: 70vh;
  padding: 10px 20px 0;
  .el-form .el-form-item__label{
    color: red;
  }
}
.el-table {
  .el-table__header-wrapper, .el-table__fixed-header-wrapper {
  .el-table__header-wrapper,
  .el-table__fixed-header-wrapper {
    th {
      word-break: break-word;
      background-color: #f8f8f9;
      background-color: #f8f8f9 !important;
      color: #515a6e;
      height: 40px;
      height: 40px !important;
      font-size: 13px;
    }
  }
  .el-table__body-wrapper {
    .el-button [class*="el-icon-"] + span {
    .el-button [class*='el-icon-'] + span {
      margin-left: 1px;
    }
  }
@@ -112,12 +114,12 @@
  color: #6379bb;
  border-bottom: 1px solid #ddd;
  margin: 8px 10px 25px 10px;
  padding-bottom: 5px
  padding-bottom: 5px;
}
/** 表格布局 **/
.pagination-container {
  position: relative;
  // position: relative;
  height: 25px;
  margin-bottom: 10px;
  margin-top: 15px;
@@ -128,13 +130,14 @@
.tree-border {
  margin-top: 5px;
  border: 1px solid #e5e6e7;
  background: #FFFFFF none;
  background: #ffffff none;
  border-radius: 4px;
  width: 100%;
}
.pagination-container .el-pagination {
  right: 0;
  position: absolute;
  //right: 0;
  //position: absolute;
}
@media (max-width: 768px) {
@@ -146,19 +149,21 @@
  }
}
.el-table .fixed-width .el-button--mini {
.el-table .fixed-width .el-button--small {
  padding-left: 0;
  padding-right: 0;
  width: inherit;
}
/** 表格更多操作下拉样式 */
.el-table .el-dropdown-link,.el-table .el-dropdown-selfdefine {
    cursor: pointer;
    margin-left: 5px;
.el-table .el-dropdown-link {
  cursor: pointer;
  color: #409eff;
  margin-left: 10px;
}
.el-table .el-dropdown, .el-icon-arrow-down {
.el-table .el-dropdown,
.el-icon-arrow-down {
  font-size: 12px;
}
@@ -192,12 +197,12 @@
}
.el-card__header {
  padding: 14px 15px 7px;
  padding: 14px 15px 7px !important;
  min-height: 40px;
}
.el-card__body {
  padding: 15px 20px 20px 20px;
  padding: 15px 20px 20px 20px !important;
}
.card-box {
@@ -209,22 +214,22 @@
/* button color */
.el-button--cyan.is-active,
.el-button--cyan:active {
  background: #20B2AA;
  border-color: #20B2AA;
  color: #FFFFFF;
  background: #20b2aa;
  border-color: #20b2aa;
  color: #ffffff;
}
.el-button--cyan:focus,
.el-button--cyan:hover {
  background: #48D1CC;
  border-color: #48D1CC;
  color: #FFFFFF;
  background: #48d1cc;
  border-color: #48d1cc;
  color: #ffffff;
}
.el-button--cyan {
  background-color: #20B2AA;
  border-color: #20B2AA;
  color: #FFFFFF;
  background-color: #20b2aa;
  border-color: #20b2aa;
  color: #ffffff;
}
/* text color */
@@ -267,10 +272,9 @@
}
.avatar-upload-preview {
  position: relative;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  transform: translate(50%, -50%);
  width: 200px;
  height: 200px;
  border-radius: 50%;
@@ -280,12 +284,12 @@
/* 拖拽列样式 */
.sortable-ghost {
  opacity: .8;
  opacity: 0.8;
  color: #fff !important;
  background: #42b983 !important;
}
/* 表格右侧工具栏样式 */
.top-right-btn {
  position: relative;
  float: right;
  margin-left: auto;
}
src/assets/styles/sidebar.scss
@@ -1,32 +1,31 @@
#app {
  .main-container {
    height: 100%;
    transition: margin-left .28s;
    margin-left: $base-sidebar-width;
    // height: 100%;
    transition: padding-left 0.28s;
    padding-left: $base-sidebar-width;
    position: relative;
    background-color: #F3F7FC;
  }
  .sidebarHide {
    margin-left: 0!important;
    margin-left: 0 !important;
  }
  .sidebar-container {
    -webkit-transition: width .28s;
    -webkit-transition: width 0.28s;
    transition: width 0.28s;
    width: $base-sidebar-width !important;
    background-color: $base-menu-background;
    height: 100%;
    position: fixed;
    font-size: 0px;
    position: absolute;
    font-size: 0;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 1001;
    overflow: hidden;
    -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
    box-shadow: 2px 0 6px rgba(0,21,41,.35);
    -webkit-box-shadow: 5px 0 10px -5px #d3d7de;
    box-shadow: 5px 0 10px -5px #d3d7de;
    // 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;
@@ -37,7 +36,7 @@
    }
    .el-scrollbar__bar.is-vertical {
      right: 0px;
      right: 0;
    }
    .el-scrollbar {
@@ -70,39 +69,64 @@
      width: 100% !important;
    }
    .el-menu-item, .el-submenu__title {
    .el-menu-item,
    .menu-title {
      overflow: hidden !important;
      text-overflow: ellipsis !important;
      white-space: nowrap !important;
    }
    .el-menu-item .el-menu-tooltip__trigger {
      display: inline-block !important;
    }
    // menu hover
    .submenu-title-noDropdown,
    .el-submenu__title {
    .theme-dark .sub-menu-title-noDropdown,
    .theme-dark .el-sub-menu__title {
      &:hover {
        background-color: rgba(0, 0, 0, 0.06) !important;
        background-color: $base-sub-menu-title-hover !important;
      }
    }
    .sub-menu-title-noDropdown,
    .el-sub-menu__title {
      &:hover {
        background-color: rgba(0, 0, 0, 0.05) !important;
      }
    }
    & .theme-dark .is-active > .el-submenu__title {
    & .theme-dark .is-active > .el-sub-menu__title {
      color: $base-menu-color-active !important;
    }
    & .nest-menu .el-submenu>.el-submenu__title,
    & .el-submenu .el-menu-item {
    & .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;
        background-color: rgba(0, 0, 0, 0.1) !important;
      }
    }
    & .theme-dark .nest-menu .el-submenu>.el-submenu__title,
    & .theme-dark .el-submenu .el-menu-item {
    & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
    & .theme-dark .el-sub-menu .el-menu-item {
      background-color: $base-sub-menu-background !important;
      &:hover {
        background-color: $base-sub-menu-hover !important;
      }
    }
    & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
    & .theme-dark .el-menu-item {
      &:hover {
        // you can use $sub-menuHover
        background-color: $base-menu-hover !important;
      }
    }
    & .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.04) !important;
      }
    }
  }
@@ -113,10 +137,10 @@
    }
    .main-container {
      margin-left: 54px;
      padding-left: 54px;
    }
    .submenu-title-noDropdown {
    .sub-menu-title-noDropdown {
      padding: 0 !important;
      position: relative;
@@ -129,23 +153,29 @@
      }
    }
    .el-submenu {
    .el-sub-menu {
      overflow: hidden;
      &>.el-submenu__title {
      & > .el-sub-menu__title {
        padding: 0 !important;
        .svg-icon {
          margin-left: 20px;
        }
      }
    }
    .el-menu--collapse {
      .el-submenu {
        &>.el-submenu__title {
          &>span {
      .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;
@@ -157,7 +187,7 @@
    }
  }
  .el-menu--collapse .el-menu .el-submenu {
  .el-menu--collapse .el-menu .el-sub-menu {
    min-width: $base-sidebar-width !important;
  }
@@ -168,7 +198,7 @@
    }
    .sidebar-container {
      transition: transform .28s;
      transition: transform 0.28s;
      width: $base-sidebar-width !important;
    }
@@ -182,7 +212,6 @@
  }
  .withoutAnimation {
    .main-container,
    .sidebar-container {
      transition: none;
@@ -192,36 +221,9 @@
// when menu collapsed
.el-menu--vertical {
  &>.el-menu {
  & > .el-menu {
    .svg-icon {
      margin-right: 16px;
    }
  }
  .nest-menu .el-submenu>.el-submenu__title,
  .el-menu-item {
    &:hover {
      // you can use $subMenuHover
      background-color: rgba(0, 0, 0, 0.06) !important;
    }
  }
  // the scroll bar appears when the subMenu 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;
    }
  }
}
src/assets/styles/transition.scss
@@ -15,7 +15,7 @@
.fade-transform--move,
.fade-transform-leave-active,
.fade-transform-enter-active {
  transition: all .5s;
  transition: all 0.5s;
}
.fade-transform-enter {
@@ -31,7 +31,7 @@
/* breadcrumb transition */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
  transition: all .5s;
  transition: all 0.5s;
}
.breadcrumb-enter,
@@ -41,7 +41,7 @@
}
.breadcrumb-move {
  transition: all .5s;
  transition: all 0.5s;
}
.breadcrumb-leave-active {
src/assets/styles/variables.module.scss
@@ -80,7 +80,7 @@
$base-menu-background: var(--menuBg);
$base-logo-title-color: #283146;
$base-menu-light-color: rgba(0, 0, 0, 0.7);
$base-menu-light-color: rgba(0, 0, 0);
$base-menu-light-background: #ffffff;
$base-logo-light-title-color: #001529;
src/assets/styles/variables.scss
@@ -14,7 +14,7 @@
$base-menu-background:#304156;
$base-logo-title-color: #ffffff;
$base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-color:rgba(0,0,0);
$base-menu-light-background:#ffffff;
$base-logo-light-title-color: #001529;
src/components/TopNav/index.vue
@@ -181,7 +181,7 @@
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-submenu.is-active .el-submenu__title {
  border-bottom: 2px solid #{'var(--theme)'} !important;
  color: #303133;
  color: #000000;
}
/* submenu item */
src/layout/components/Navbar.vue
@@ -1,49 +1,75 @@
<template>
  <div class="navbar">
    <hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
    <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/>
    <top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/>
    <div class="right-menu">
      <template v-if="device!=='mobile'">
        <search id="header-search" class="right-menu-item" />
        <el-tooltip content="源码地址" effect="dark" placement="bottom">
          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
        </el-tooltip>
        <el-tooltip content="文档地址" effect="dark" placement="bottom">
          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
        </el-tooltip>
        <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-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
        <div class="avatar-wrapper">
          <img :src="avatar" class="user-avatar">
          <i class="el-icon-caret-bottom" />
    <div class="navbar">
        <div class="ruoyi-logo">
            <img src="@/assets/logo/logo.png" alt="" />
            <span>射洪项目管理系统</span>
        </div>
        <el-dropdown-menu slot="dropdown">
          <router-link to="/user/profile">
            <el-dropdown-item>个人中心</el-dropdown-item>
          </router-link>
          <el-dropdown-item @click.native="setting = true">
            <span>布局设置</span>
          </el-dropdown-item>
          <el-dropdown-item divided @click.native="logout">
            <span>退出登录</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
        <div class="right-menu flex align-center">
            <template>
                <search-menu ref="searchMenuRef" />
                <el-tooltip content="搜索" effect="dark" placement="bottom">
                    <div
                        class="right-menu-item hover-effect"
                        @click="openSearchMenu"
                    >
                        <svg-icon
                            class-name="search-icon"
                            icon-class="search"
                        />
                    </div>
                </el-tooltip>
                <!-- 消息 -->
                <el-tooltip content="消息" effect="dark" placement="bottom">
                    <div class="right-menu-item hover-effect">
                        <el-popover
                            placement="bottom"
                            trigger="click"
                            transition="el-zoom-in-top"
                            :width="300"
                            :persistent="false"
                        >
                            <template #reference>
                                <el-badge
                                    :value="newNotice > 0 ? newNotice : ''"
                                    :max="99"
                                >
                                    <svg-icon icon-class="message" />
                                </el-badge>
                            </template>
                            <template #default>
                                <notice></notice>
                            </template>
                        </el-popover>
                    </div>
                </el-tooltip>
                <el-tooltip content="全屏" effect="dark" placement="bottom">
                    <screenfull
                        id="screenfull"
                        class="right-menu-item hover-effect"
                    />
                </el-tooltip>
            </template>
            <div class="avatar-container">
                <el-dropdown
                    class="avatar-container right-menu-item hover-effect"
                    trigger="click"
                >
                    <div class="avatar-wrapper">
                        <img :src="avatar" class="user-avatar" />
                        <i class="el-icon-caret-bottom" />
                    </div>
                    <el-dropdown-menu slot="dropdown">
                        <router-link to="/user/profile">
                            <el-dropdown-item>个人中心</el-dropdown-item>
                        </router-link>
                        <el-dropdown-item divided @click.native="logout">
                            <span>退出登录</span>
                        </el-dropdown-item>
                    </el-dropdown-menu>
                </el-dropdown>
            </div>
        </div>
    </div>
  </div>
</template>
<script>
@@ -56,145 +82,198 @@
import Search from '@/components/HeaderSearch'
import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
import SearchMenu from './TopBar/search.vue';
import Notice from './Notice/index.vue';
export default {
  components: {
    Breadcrumb,
    TopNav,
    Hamburger,
    Screenfull,
    SizeSelect,
    Search,
    RuoYiGit,
    RuoYiDoc
  },
  computed: {
    ...mapGetters([
      'sidebar',
      'avatar',
      'device'
    ]),
    setting: {
      get() {
        return this.$store.state.settings.showSettings
      },
      set(val) {
        this.$store.dispatch('settings/changeSetting', {
          key: 'showSettings',
          value: val
        })
      }
    data() {
        return {
            newNotice: 0
        }
    },
    topNav: {
      get() {
        return this.$store.state.settings.topNav
      }
    }
  },
  methods: {
    toggleSideBar() {
      this.$store.dispatch('app/toggleSideBar')
    components: {
        Breadcrumb,
        TopNav,
        Hamburger,
        Screenfull,
        SizeSelect,
        Search,
        RuoYiGit,
        RuoYiDoc,
        SearchMenu,
        Notice
    },
    async logout() {
      this.$confirm('确定注销并退出系统吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$store.dispatch('LogOut').then(() => {
          location.href = '/index';
        })
      }).catch(() => {});
    computed: {
        ...mapGetters([
            'sidebar',
            'avatar',
            'device'
        ]),
        setting: {
            get() {
                return this.$store.state.settings.showSettings
            },
            set(val) {
                this.$store.dispatch('settings/changeSetting', {
                    key: 'showSettings',
                    value: val
                })
            }
        },
        topNav: {
            get() {
                return this.$store.state.settings.topNav
            }
        }
    },
    methods: {
        toggleSideBar() {
            this.$store.dispatch('app/toggleSideBar')
        },
        async logout() {
            this.$confirm('确定注销并退出系统吗?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {
                this.$store.dispatch('LogOut').then(() => {
                    location.href = '/index';
                })
            }).catch(() => { });
        },
        openSearchMenu() {
            this.searchMenuRef.value?.openSearch();
        },
    }
  }
}
</script>
<style lang="scss" scoped>
::v-deep.el-select .el-input__wrapper {
    height: 30px;
}
::v-deep .el-badge__content.is-fixed {
    top: 12px;
}
.flex {
    display: flex;
}
.align-center {
    align-items: center;
}
.ruoyi-logo {
    float: left;
    line-height: 54px;
    font-weight: 400;
    font-size: 24px;
    font-style: italic;
    font-family: PangMenZhengDao;
    // color: #ffffff;
    // width: 220px;
    display: flex;
    align-items: center;
    > img {
        margin: 0 10px 0 21px;
    }
    > div {
        margin: 0 0 0 21px;
    }
    .down {
        margin-top: 22px;
        color: #ffffff;
    }
}
.navbar {
  height: 50px;
  overflow: hidden;
  position: relative;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0,21,41,.08);
    height: 50px;
    overflow: hidden;
    position: relative;
    //background: #fff;
    box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  .hamburger-container {
    line-height: 46px;
    height: 100%;
    float: left;
    cursor: pointer;
    transition: background .3s;
    -webkit-tap-highlight-color:transparent;
    &:hover {
      background: rgba(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;
    &: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 {
    .hamburger-container {
        line-height: 46px;
        height: 100%;
        float: left;
        cursor: pointer;
        transition: background .3s;
        transition: background 0.3s;
        -webkit-tap-highlight-color: transparent;
        &:hover {
          background: rgba(0, 0, 0, .025)
            background: rgba(0, 0, 0, 0.025);
        }
      }
    }
    .avatar-container {
      margin-right: 30px;
      .avatar-wrapper {
        margin-top: 5px;
        position: relative;
        .user-avatar {
          cursor: pointer;
          width: 40px;
          height: 40px;
          border-radius: 10px;
        }
        .el-icon-caret-bottom {
          cursor: pointer;
          position: absolute;
          right: -20px;
          top: 25px;
          font-size: 12px;
        }
      }
    .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;
            margin-top: 10px;
            .avatar-wrapper {
                margin-top: 5px;
                position: relative;
                .user-avatar {
                    cursor: pointer;
                    width: 40px;
                    height: 40px;
                    border-radius: 10px;
                    margin-top: 10px;
                }
                i {
                    cursor: pointer;
                    position: absolute;
                    right: -20px;
                    top: 25px;
                    font-size: 12px;
                }
            }
        }
    }
}
</style>
src/layout/components/Notice/index.vue
New file
@@ -0,0 +1,155 @@
<template>
    <div v-loading="state.loading" class="layout-navbars-breadcrumb-user-news">
        <div class="head-box">
            <div class="head-box-title">通知公告</div>
            <div class="head-box-btn" @click="readAll">全部已读</div>
        </div>
        <div v-loading="state.loading" class="content-box">
            <template v-if="newsList.length > 0">
                <div
                    v-for="(v, k) in newsList"
                    :key="k"
                    class="content-box-item"
                    @click="onNewsClick(k)"
                >
                    <div class="item-conten">
                        <div>{{ v.message }}</div>
                        <div class="content-box-msg"></div>
                        <div class="content-box-time">{{ v.time }}</div>
                    </div>
                    <!-- 已读/未读 -->
                    <span
                        v-if="v.read"
                        class="el-tag el-tag--success el-tag--mini read"
                        >已读</span
                    >
                    <span v-else class="el-tag el-tag--danger el-tag--mini read"
                        >未读</span
                    >
                </div>
            </template>
            <el-empty v-else :description="'消息为空'"></el-empty>
        </div>
    </div>
</template>
<script >
// import { storeToRefs } from 'pinia';
// import useNoticeStore from '@/store/modules/notice';
export default {
    data() {
        return {
            state: {
                loading: false
            },
            newsList: []
        }
    },
    methods: {
        /**
         * 初始化数据
         * @returns
         */
        getTableData() {
            this.state.loading = true;
            // this.newsList.value = noticeStore.state.value.notices;
            this.state.loading = false;
        },
        //点击消息,写入已读
        onNewsClick(item) {
            this.newsList.value[item].read = true;
            //并且写入pinia
            this.noticeStore.state.value.notices = newsList.value;
        },
        readAll() {
        },
        // 前往通知中心点击
        onGoToGiteeClick() {
            window.open('https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/');
        }
    },
    mounted: function () {
        this.$nextTick(() => {
            this.getTableData();
        });
    }
}
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-user-news {
    .head-box {
        display: flex;
        border-bottom: 1px solid var(--el-border-color-lighter);
        box-sizing: border-box;
        color: var(--el-text-color-primary);
        justify-content: space-between;
        height: 35px;
        align-items: center;
        .head-box-btn {
            color: var(--el-color-primary);
            font-size: 13px;
            cursor: pointer;
            opacity: 0.8;
            &:hover {
                opacity: 1;
            }
        }
    }
    .content-box {
        height: 300px;
        overflow: auto;
        font-size: 13px;
        .content-box-item {
            padding-top: 12px;
            display: flex;
            &:last-of-type {
                padding-bottom: 12px;
            }
            .content-box-msg {
                color: var(--el-text-color-secondary);
                margin-top: 5px;
                margin-bottom: 5px;
            }
            .content-box-time {
                color: var(--el-text-color-secondary);
            }
            .item-conten {
                width: 100%;
                display: flex;
                flex-direction: column;
            }
        }
    }
    .foot-box {
        height: 35px;
        color: var(--el-color-primary);
        font-size: 13px;
        cursor: pointer;
        opacity: 0.8;
        display: flex;
        align-items: center;
        justify-content: center;
        border-top: 1px solid var(--el-border-color-lighter);
        &:hover {
            opacity: 1;
        }
    }
    ::v-deep .el-empty__description p {
        font-size: 13px;
    }
}
</style>
src/layout/components/Sidebar/index.vue
@@ -1,12 +1,29 @@
<template>
    <div :class="{'has-logo':showLogo}" :style="{ backgroundColor: settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
        <logo v-if="showLogo" :collapse="isCollapse" />
        <el-scrollbar :class="settings.sideTheme" wrap-class="scrollbar-wrapper">
    <div
        :class="{ 'has-logo': showLogo }"
        :style="{ backgroundColor: variables.menuLightBackground }"
    >
        <!-- <logo v-if="showLogo" :collapse="isCollapse" /> -->
        <div class="hamburger-container">
            <div class="border">
                <div class="menu-tip" v-show="sidebar.opened">菜单管理</div>
                <hamburger
                    id="hamburger-container"
                    :is-active="sidebar.opened"
                    class="hamburger-icon"
                    @toggleClick="toggleSideBar"
                />
            </div>
        </div>
        <el-scrollbar
            :class="settings.sideTheme"
            wrap-class="scrollbar-wrapper"
        >
            <el-menu
                :default-active="activeMenu"
                :collapse="isCollapse"
                :background-color="settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
                :text-color="settings.sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
                :background-color="variables.menuLightBackground"
                :text-color="variables.menuLightColor"
                :unique-opened="true"
                :active-text-color="settings.theme"
                :collapse-transition="false"
@@ -14,7 +31,7 @@
            >
                <sidebar-item
                    v-for="(route, index) in sidebarRouters"
                    :key="route.path  + index"
                    :key="route.path + index"
                    :item="route"
                    :base-path="route.path"
                />
@@ -28,9 +45,10 @@
import Logo from "./Logo";
import SidebarItem from "./SidebarItem";
import variables from "@/assets/styles/variables.scss";
import Hamburger from '@/components/Hamburger'
export default {
    components: { SidebarItem, Logo },
    components: { SidebarItem, Logo, Hamburger },
    computed: {
        ...mapState(["settings"]),
        ...mapGetters(["sidebarRouters", "sidebar"]),
@@ -52,6 +70,45 @@
        isCollapse() {
            return !this.sidebar.opened;
        }
    },
    created() {
        // console.log(this.settings.sideTheme);
    },
    methods: {
        toggleSideBar() {
            this.$store.dispatch('app/toggleSideBar')
        },
    }
};
</script>
<style scoped>
.hamburger-container {
    display: flex;
    color: #fff;
    justify-content: space-between;
    height: 59px;
    line-height: 59px;
    .border {
        margin: 0 auto;
        display: flex;
        justify-content: space-between;
        width: 180px;
        border-bottom: 1px solid #e6eaf5;
    }
    .menu-tip {
        height: 50px;
        font-size: 14px;
        font-weight: 400;
        color: #000000;
        padding-left: 20px;
    }
    .hamburger-icon {
        width: 54px;
        text-align: center;
        cursor: pointer;
        &:hover {
            background: rgba(0, 0, 0, 0.025);
        }
    }
}
</style>
src/layout/components/TagsView/index.vue
@@ -1,31 +1,64 @@
<template>
  <div id="tags-view-container" class="tags-view-container">
    <scroll-pane ref="scrollPane" class="tags-view-wrapper" @scroll="handleScroll">
      <router-link
        v-for="tag in visitedViews"
        ref="tag"
        :key="tag.path"
        :class="isActive(tag)?'active':''"
        :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
        tag="span"
        class="tags-view-item"
        :style="activeStyle(tag)"
        @click.middle.native="!isAffix(tag)?closeSelectedTag(tag):''"
        @contextmenu.prevent.native="openMenu(tag,$event)"
      >
        {{ tag.title }}
        <span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
      </router-link>
    </scroll-pane>
    <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
      <li @click="refreshSelectedTag(selectedTag)"><i class="el-icon-refresh-right"></i> 刷新页面</li>
      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><i class="el-icon-close"></i> 关闭当前</li>
      <li @click="closeOthersTags"><i class="el-icon-circle-close"></i> 关闭其他</li>
      <li v-if="!isFirstView()" @click="closeLeftTags"><i class="el-icon-back"></i> 关闭左侧</li>
      <li v-if="!isLastView()" @click="closeRightTags"><i class="el-icon-right"></i> 关闭右侧</li>
      <li @click="closeAllTags(selectedTag)"><i class="el-icon-circle-close"></i> 全部关闭</li>
    </ul>
  </div>
    <div id="tags-view-container" class="tags-view-container">
        <scroll-pane
            ref="scrollPane"
            class="tags-view-wrapper"
            @scroll="handleScroll"
        >
            <router-link
                v-for="tag in visitedViews"
                ref="tag"
                :key="tag.path"
                :class="isActive(tag) ? 'active' : ''"
                :to="{
                    path: tag.path,
                    query: tag.query,
                    fullPath: tag.fullPath
                }"
                tag="span"
                class="tags-view-item"
                :style="activeStyle(tag)"
                @click.middle.native="
                    !isAffix(tag) ? closeSelectedTag(tag) : ''
                "
                @contextmenu.prevent.native="openMenu(tag, $event)"
            >
                {{ tag.title }}
                <span
                    v-if="!isAffix(tag)"
                    class="el-icon-close"
                    @click.prevent.stop="closeSelectedTag(tag)"
                />
            </router-link>
        </scroll-pane>
        <ul
            v-show="visible"
            :style="{ left: left + 'px', top: top + 'px' }"
            class="contextmenu"
        >
            <li @click="refreshSelectedTag(selectedTag)">
                <i class="el-icon-refresh-right"></i> 刷新页面
            </li>
            <li
                v-if="!isAffix(selectedTag)"
                @click="closeSelectedTag(selectedTag)"
            >
                <i class="el-icon-close"></i> 关闭当前
            </li>
            <li @click="closeOthersTags">
                <i class="el-icon-circle-close"></i> 关闭其他
            </li>
            <li v-if="!isFirstView()" @click="closeLeftTags">
                <i class="el-icon-back"></i> 关闭左侧
            </li>
            <li v-if="!isLastView()" @click="closeRightTags">
                <i class="el-icon-right"></i> 关闭右侧
            </li>
            <li @click="closeAllTags(selectedTag)">
                <i class="el-icon-circle-close"></i> 全部关闭
            </li>
        </ul>
    </div>
</template>
<script>
@@ -33,300 +66,304 @@
import path from 'path'
export default {
  components: { ScrollPane },
  data() {
    return {
      visible: false,
      top: 0,
      left: 0,
      selectedTag: {},
      affixTags: []
    }
  },
  computed: {
    visitedViews() {
      return this.$store.state.tagsView.visitedViews
    },
    routes() {
      return this.$store.state.permission.routes
    },
    theme() {
      return this.$store.state.settings.theme;
    }
  },
  watch: {
    $route() {
      this.addTags()
      this.moveToCurrentTag()
    },
    visible(value) {
      if (value) {
        document.body.addEventListener('click', this.closeMenu)
      } else {
        document.body.removeEventListener('click', this.closeMenu)
      }
    }
  },
  mounted() {
    this.initTags()
    this.addTags()
  },
  methods: {
    isActive(route) {
      return route.path === this.$route.path
    },
    activeStyle(tag) {
      if (!this.isActive(tag)) return {};
      return {
        "background-color": this.theme,
        "border-color": this.theme
      };
    },
    isAffix(tag) {
      return tag.meta && tag.meta.affix
    },
    isFirstView() {
      try {
        return this.selectedTag.fullPath === '/index' || this.selectedTag.fullPath === this.visitedViews[1].fullPath
      } catch (err) {
        return false
      }
    },
    isLastView() {
      try {
        return this.selectedTag.fullPath === this.visitedViews[this.visitedViews.length - 1].fullPath
      } catch (err) {
        return false
      }
    },
    filterAffixTags(routes, basePath = '/') {
      let tags = []
      routes.forEach(route => {
        if (route.meta && route.meta.affix) {
          const tagPath = path.resolve(basePath, route.path)
          tags.push({
            fullPath: tagPath,
            path: tagPath,
            name: route.name,
            meta: { ...route.meta }
          })
    components: { ScrollPane },
    data() {
        return {
            visible: false,
            top: 0,
            left: 0,
            selectedTag: {},
            affixTags: []
        }
        if (route.children) {
          const tempTags = this.filterAffixTags(route.children, route.path)
          if (tempTags.length >= 1) {
            tags = [...tags, ...tempTags]
          }
        }
      })
      return tags
    },
    initTags() {
      const affixTags = this.affixTags = this.filterAffixTags(this.routes)
      for (const tag of affixTags) {
        // Must have tag name
        if (tag.name) {
          this.$store.dispatch('tagsView/addVisitedView', tag)
    computed: {
        visitedViews() {
            return this.$store.state.tagsView.visitedViews
        },
        routes() {
            return this.$store.state.permission.routes
        },
        theme() {
            return this.$store.state.settings.theme;
        }
      }
    },
    addTags() {
      const { name } = this.$route
      if (name) {
        this.$store.dispatch('tagsView/addView', this.$route)
        if (this.$route.meta.link) {
          this.$store.dispatch('tagsView/addIframeView', this.$route)
        }
      }
      return false
    },
    moveToCurrentTag() {
      const tags = this.$refs.tag
      this.$nextTick(() => {
        for (const tag of tags) {
          if (tag.to.path === this.$route.path) {
            this.$refs.scrollPane.moveToTarget(tag)
            // when query is different then update
            if (tag.to.fullPath !== this.$route.fullPath) {
              this.$store.dispatch('tagsView/updateVisitedView', this.$route)
    watch: {
        $route() {
            this.addTags()
            this.moveToCurrentTag()
        },
        visible(value) {
            if (value) {
                document.body.addEventListener('click', this.closeMenu)
            } else {
                document.body.removeEventListener('click', this.closeMenu)
            }
            break
          }
        }
      })
    },
    refreshSelectedTag(view) {
      this.$tab.refreshPage(view);
      if (this.$route.meta.link) {
        this.$store.dispatch('tagsView/delIframeView', this.$route)
      }
    mounted() {
        this.initTags()
        this.addTags()
    },
    closeSelectedTag(view) {
      this.$tab.closePage(view).then(({ visitedViews }) => {
        if (this.isActive(view)) {
          this.toLastView(visitedViews, view)
        }
      })
    },
    closeRightTags() {
      this.$tab.closeRightPage(this.selectedTag).then(visitedViews => {
        if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
          this.toLastView(visitedViews)
        }
      })
    },
    closeLeftTags() {
      this.$tab.closeLeftPage(this.selectedTag).then(visitedViews => {
        if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
          this.toLastView(visitedViews)
        }
      })
    },
    closeOthersTags() {
      this.$router.push(this.selectedTag.fullPath).catch(()=>{});
      this.$tab.closeOtherPage(this.selectedTag).then(() => {
        this.moveToCurrentTag()
      })
    },
    closeAllTags(view) {
      this.$tab.closeAllPage().then(({ visitedViews }) => {
        if (this.affixTags.some(tag => tag.path === this.$route.path)) {
          return
        }
        this.toLastView(visitedViews, view)
      })
    },
    toLastView(visitedViews, view) {
      const latestView = visitedViews.slice(-1)[0]
      if (latestView) {
        this.$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
          this.$router.replace({ path: '/redirect' + view.fullPath })
        } else {
          this.$router.push('/')
        }
      }
    },
    openMenu(tag, e) {
      const menuMinWidth = 105
      const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
      const offsetWidth = this.$el.offsetWidth // container width
      const maxLeft = offsetWidth - menuMinWidth // left boundary
      const left = e.clientX - offsetLeft + 15 // 15: margin right
    methods: {
        isActive(route) {
            return route.path === this.$route.path
        },
        activeStyle(tag) {
            if (!this.isActive(tag)) return {};
            return {
                "background-color": this.theme,
                "border-color": this.theme
            };
        },
        isAffix(tag) {
            return tag.meta && tag.meta.affix
        },
        isFirstView() {
            try {
                return this.selectedTag.fullPath === '/index' || this.selectedTag.fullPath === this.visitedViews[1].fullPath
            } catch (err) {
                return false
            }
        },
        isLastView() {
            try {
                return this.selectedTag.fullPath === this.visitedViews[this.visitedViews.length - 1].fullPath
            } catch (err) {
                return false
            }
        },
        filterAffixTags(routes, basePath = '/') {
            let tags = []
            routes.forEach(route => {
                if (route.meta && route.meta.affix) {
                    const tagPath = path.resolve(basePath, route.path)
                    tags.push({
                        fullPath: tagPath,
                        path: tagPath,
                        name: route.name,
                        meta: { ...route.meta }
                    })
                }
                if (route.children) {
                    const tempTags = this.filterAffixTags(route.children, route.path)
                    if (tempTags.length >= 1) {
                        tags = [...tags, ...tempTags]
                    }
                }
            })
            return tags
        },
        initTags() {
            const affixTags = this.affixTags = this.filterAffixTags(this.routes)
            for (const tag of affixTags) {
                // Must have tag name
                if (tag.name) {
                    this.$store.dispatch('tagsView/addVisitedView', tag)
                }
            }
        },
        addTags() {
            const { name } = this.$route
            if (name) {
                this.$store.dispatch('tagsView/addView', this.$route)
                if (this.$route.meta.link) {
                    this.$store.dispatch('tagsView/addIframeView', this.$route)
                }
            }
            return false
        },
        moveToCurrentTag() {
            const tags = this.$refs.tag
            this.$nextTick(() => {
                for (const tag of tags) {
                    if (tag.to.path === this.$route.path) {
                        this.$refs.scrollPane.moveToTarget(tag)
                        // when query is different then update
                        if (tag.to.fullPath !== this.$route.fullPath) {
                            this.$store.dispatch('tagsView/updateVisitedView', this.$route)
                        }
                        break
                    }
                }
            })
        },
        refreshSelectedTag(view) {
            this.$tab.refreshPage(view);
            if (this.$route.meta.link) {
                this.$store.dispatch('tagsView/delIframeView', this.$route)
            }
        },
        closeSelectedTag(view) {
            this.$tab.closePage(view).then(({ visitedViews }) => {
                if (this.isActive(view)) {
                    this.toLastView(visitedViews, view)
                }
            })
        },
        closeRightTags() {
            this.$tab.closeRightPage(this.selectedTag).then(visitedViews => {
                if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
                    this.toLastView(visitedViews)
                }
            })
        },
        closeLeftTags() {
            this.$tab.closeLeftPage(this.selectedTag).then(visitedViews => {
                if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
                    this.toLastView(visitedViews)
                }
            })
        },
        closeOthersTags() {
            this.$router.push(this.selectedTag.fullPath).catch(() => { });
            this.$tab.closeOtherPage(this.selectedTag).then(() => {
                this.moveToCurrentTag()
            })
        },
        closeAllTags(view) {
            this.$tab.closeAllPage().then(({ visitedViews }) => {
                if (this.affixTags.some(tag => tag.path === this.$route.path)) {
                    return
                }
                this.toLastView(visitedViews, view)
            })
        },
        toLastView(visitedViews, view) {
            const latestView = visitedViews.slice(-1)[0]
            if (latestView) {
                this.$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
                    this.$router.replace({ path: '/redirect' + view.fullPath })
                } else {
                    this.$router.push('/')
                }
            }
        },
        openMenu(tag, e) {
            const menuMinWidth = 105
            const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
            const offsetWidth = this.$el.offsetWidth // container width
            const maxLeft = offsetWidth - menuMinWidth // left boundary
            const left = e.clientX - offsetLeft + 15 // 15: margin right
      if (left > maxLeft) {
        this.left = maxLeft
      } else {
        this.left = left
      }
            if (left > maxLeft) {
                this.left = maxLeft
            } else {
                this.left = left
            }
      this.top = e.clientY
      this.visible = true
      this.selectedTag = tag
    },
    closeMenu() {
      this.visible = false
    },
    handleScroll() {
      this.closeMenu()
            this.top = e.clientY
            this.visible = true
            this.selectedTag = tag
        },
        closeMenu() {
            this.visible = false
        },
        handleScroll() {
            this.closeMenu()
        }
    }
  }
}
</script>
<style lang="scss" scoped>
.tags-view-container {
  height: 34px;
  width: 100%;
  background: #fff;
  border-bottom: 1px solid #d8dce5;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
      position: relative;
      cursor: pointer;
      height: 26px;
      line-height: 26px;
      border: 1px solid #d8dce5;
      color: #495060;
      background: #fff;
      padding: 0 8px;
      font-size: 12px;
      margin-left: 5px;
      margin-top: 4px;
      &: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: 2px;
    height: 44px;
    width: 100%;
    background-color: var(--el-bg-color);
    border: 1px solid var(--el-border-color-light);
    line-height: 44px;
    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: 26px;
            line-height: 23px;
            background-color: var(--el-bg-color);
            border: 1px solid var(--el-border-color-light);
            color: #495060;
            padding: 0 8px;
            font-size: 12px;
            margin-left: 5px;
            margin-top: 4px;
            &:hover {
                color: var(--el-color-primary);
            }
            &: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, .3);
    li {
      margin: 0;
      padding: 7px 16px;
      cursor: pointer;
      &:hover {
        background: #eee;
      }
    .contextmenu {
        margin: 0;
        background: var(--el-bg-color);
        z-index: 3000;
        position: absolute;
        list-style-type: none;
        padding: 5px 0;
        border-radius: 4px;
        font-size: 12px;
        font-weight: 400;
        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 .3s cubic-bezier(.645, .045, .355, 1);
      transform-origin: 100% 50%;
      &:before {
        transform: scale(.6);
        display: inline-block;
        vertical-align: -3px;
      }
      &:hover {
        background-color: #b4bccc;
        color: #fff;
      }
    .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;
            }
        }
    }
  }
}
</style>
src/layout/components/TopBar/search.vue
New file
@@ -0,0 +1,157 @@
<template>
    <div class="layout-search-dialog">
        <el-dialog
            v-model="state.isShowSearch"
            destroy-on-close
            :show-close="false"
        >
            <template #footer>
                <el-autocomplete
                    ref="layoutMenuAutocompleteRef"
                    v-model="state.menuQuery"
                    :fetch-suggestions="menuSearch"
                    placeholder="搜索"
                    :fit-input-width="true"
                    @select="onHandleSelect"
                >
                    <template #prefix>
                        <svg-icon
                            class-name="search-icon"
                            icon-class="search"
                        />
                    </template>
                    <template #default="{ item }">
                        <div>
                            <svg-icon :icon-class="item.icon" class="mr5" />
                            {{ item.title }}
                        </div>
                    </template>
                </el-autocomplete>
            </template>
        </el-dialog>
    </div>
</template>
<script>
import { getNormalPath } from '@/utils/ruoyi';
import { isHttp } from '@/utils/validate';
import usePermissionStore from '@/store/modules/permission';
import { RouteRecordRaw } from 'vue-router';
export default {
    data() {
        return {
            state: {
                isShowSearch: false,
                menuQuery: '',
                menuList: []
            }
        }
    },
    created() {
        // this.openSearch()
    },
    methods: {
        // 搜索弹窗打开
        openSearch() {
            this.state.menuQuery = '';
            this.state.isShowSearch = true;
            this.state.menuList = this.generateRoutes(usePermissionStore.state.routes);
            this.$nextTick(() => {
                setTimeout(() => {
                    this.layoutMenuAutocompleteRef.value.focus();
                });
            });
        },
        // 搜索弹窗关闭
        closeSearch() {
            this.state.isShowSearch = false;
        },
        // 菜单搜索数据过滤
        menuSearch(queryString, cb) {
            let options = state.menuList.filter((item) => {
                return item.title.indexOf(queryString) > -1;
            });
            this.cb(options);
        },
        // Filter out the routes that can be displayed in the sidebar
        // And generate the internationalized title
        generateRoutes(routes, basePath, prefixTitle) {
            let res = [];
            routes.forEach((r) => {
                // skip hidden router
                if (!r.hidden) {
                    const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
                    const data = {
                        path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
                        icon: r.meta?.icon,
                        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);
                        }
                    }
                    // recursive child routes
                    if (r.children) {
                        const tempRoutes = generateRoutes(r.children, data.path, data.title);
                        if (tempRoutes.length >= 1) {
                            res = [...res, ...tempRoutes];
                        }
                    }
                }
            });
            res.forEach((item) => {
                if (item.title instanceof Array) {
                    item.title = item.title.join('/');
                }
            });
            return res;
        },
        // 当前菜单选中时
        onHandleSelect(val) {
            const paths = val.path;
            if (isHttp(paths)) {
                // http(s):// 路径新窗口打开
                const pindex = paths.indexOf('http');
                window.open(paths.substring(pindex, paths.length), '_blank');
            } else {
                router.push(paths);
            }
            state.menuQuery = '';
            closeSearch();
        }
    }
}
</script>
<style scoped lang="scss">
.layout-search-dialog {
    position: relative;
    ::v-deep .el-dialog {
        padding: 0;
        .el-dialog__header,
        .el-dialog__body {
            display: none;
        }
        .el-dialog__footer {
            width: 100%;
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            top: -53vh;
        }
    }
    ::v-deep.el-autocomplete {
        width: 560px;
        position: absolute;
        top: 150px;
        left: 50%;
        transform: translateX(-50%);
    }
}
</style>
src/layout/index.vue
@@ -1,18 +1,36 @@
<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/>
        <tags-view v-if="needTagsView"/>
      </div>
      <app-main/>
      <right-panel>
        <settings/>
      </right-panel>
    <div
        :class="classObj"
        class="app-wrapper"
        :style="{ '--current-color': theme }"
    >
        <div
            v-if="device === 'mobile' && sidebar.opened"
            class="drawer-bg"
            @click="handleClickOutside"
        />
        <navbar ref="navbarRef" @set-layout="setLayout" />
        <div
            :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }"
            class="main-container"
        >
            <!-- <el-scrollbar>
        <div :class="{ 'fixed-header': fixedHeader }">
          <navbar ref="navbarRef" @setLayout="setLayout" />
          <tags-view v-if="needTagsView" />
        </div>
        <app-main />
        <settings ref="settingRef" />
      </el-scrollbar> -->
            <side-bar v-if="!sidebar.hide" class="sidebar-container" />
            <div :class="{ 'fixed-header': fixedHeader }">
                <tags-view v-if="needTagsView" />
            </div>
            <app-main />
            <settings ref="settingRef" />
        </div>
    </div>
  </div>
</template>
<script>
@@ -21,64 +39,69 @@
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
import variables from '@/assets/styles/variables.scss'
import SideBar from './components/Sidebar/index.vue';
export default {
  name: 'Layout',
  components: {
    AppMain,
    Navbar,
    RightPanel,
    Settings,
    Sidebar,
    TagsView
  },
  mixins: [ResizeMixin],
  computed: {
    ...mapState({
      theme: state => state.settings.theme,
      sideTheme: state => state.settings.sideTheme,
      sidebar: state => state.app.sidebar,
      device: state => state.app.device,
      needTagsView: state => state.settings.tagsView,
      fixedHeader: state => state.settings.fixedHeader
    }),
    classObj() {
      return {
        hideSidebar: !this.sidebar.opened,
        openSidebar: this.sidebar.opened,
        withoutAnimation: this.sidebar.withoutAnimation,
        mobile: this.device === 'mobile'
      }
    name: 'Layout',
    components: {
        AppMain,
        Navbar,
        RightPanel,
        Settings,
        Sidebar,
        TagsView,
        SideBar
    },
    variables() {
      return variables;
    mixins: [ResizeMixin],
    computed: {
        ...mapState({
            theme: state => state.settings.theme,
            sideTheme: state => state.settings.sideTheme,
            sidebar: state => state.app.sidebar,
            device: state => state.app.device,
            needTagsView: state => state.settings.tagsView,
            fixedHeader: state => state.settings.fixedHeader
        }),
        classObj() {
            return {
                hideSidebar: !this.sidebar.opened,
                openSidebar: this.sidebar.opened,
                withoutAnimation: this.sidebar.withoutAnimation,
                mobile: this.device === 'mobile'
            }
        },
        variables() {
            return variables;
        }
    },
    methods: {
        setLayout() {
            this.settingRef.value?.openSetting();
        },
        handleClickOutside() {
            this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
        }
    }
  },
  methods: {
    handleClickOutside() {
      this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
    }
  }
}
</script>
<style lang="scss" scoped>
  @import "~@/assets/styles/mixin.scss";
  @import "~@/assets/styles/variables.scss";
@import '~@/assets/styles/mixin.scss';
@import '~@/assets/styles/variables.scss';
  .app-wrapper {
.app-wrapper {
    @include clearfix;
    position: relative;
    height: 100%;
    width: 100%;
    &.mobile.openSidebar {
      position: fixed;
      top: 0;
        position: fixed;
        top: 0;
    }
  }
}
  .drawer-bg {
.drawer-bg {
    background: #000;
    opacity: 0.3;
    width: 100%;
@@ -86,26 +109,26 @@
    height: 100%;
    position: absolute;
    z-index: 999;
  }
}
  .fixed-header {
.fixed-header {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 9;
    width: calc(100% - #{$base-sidebar-width});
    transition: width 0.28s;
  }
}
  .hideSidebar .fixed-header {
.hideSidebar .fixed-header {
    width: calc(100% - 54px);
  }
}
  .sidebarHide .fixed-header {
.sidebarHide .fixed-header {
    width: 100%;
  }
}
  .mobile .fixed-header {
.mobile .fixed-header {
    width: 100%;
  }
}
</style>
src/main.js
@@ -1,9 +1,21 @@
import Vue from 'vue'
import Cookies from 'js-cookie'
// global css
// import 'virtual:uno.css';
import '@/assets/styles/index.scss';
import Element from 'element-ui'
import './assets/styles/element-variables.scss'
// 高亮组件
// import 'highlight.js/styles/a11y-light.css';
// import 'highlight.js/styles/atom-one-dark.css';
// import 'highlight.js/lib/common';
// import HighLight from '@highlightjs/vue-plugin';
// // svg图标
// import 'virtual:svg-icons-register';
// import ElementIcons from '@/plugins/svgicon';
import '@/assets/styles/index.scss' // global css
import '@/assets/styles/ruoyi.scss' // ruoyi css
@@ -61,6 +73,8 @@
Vue.use(directive)
Vue.use(plugins)
Vue.use(VueMeta)
// Vue.use(HighLight);
// Vue.use(ElementIcons);
DictData.install()
/**
src/settings.js
@@ -2,7 +2,7 @@
  /**
   * 侧边栏主题 深色主题theme-dark,浅色主题theme-light
   */
  sideTheme: 'theme-dark',
  sideTheme: 'theme-light',
  /**
   * 是否系统布局配置
src/utils/ruoyi.js
@@ -1,5 +1,3 @@
/**
 * 通用js方法封装处理
 * Copyright (c) 2019 ruoyi
@@ -37,7 +35,9 @@
  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 (key === 'a') {
      return ['日', '一', '二', '三', '四', '五', '六'][value]
    }
    if (result.length > 0 && value < 10) {
      value = '0' + value
    }
@@ -88,7 +88,7 @@
// 回显数据字典(字符串、数组)
export function selectDictLabels(datas, value, separator) {
  if (value === undefined || value.length ===0) {
  if (value === undefined || value.length === 0) {
    return "";
  }
  if (Array.isArray(value)) {
@@ -114,7 +114,9 @@
// 字符串格式化(%s )
export function sprintf(str) {
  var args = arguments, flag = true, i = 1;
  var args = arguments,
    flag = true,
    i = 1;
  str = str.replace(/%s/g, function () {
    var arg = args[i++];
    if (typeof arg === 'undefined') {
@@ -202,9 +204,9 @@
}
/**
* 参数处理
* @param {*} params  参数
*/
 * 参数处理
 * @param {*} params  参数
 */
export function tansParams(params) {
  let result = ''
  for (const propName of Object.keys(params)) {
@@ -231,3 +233,15 @@
export function blobValidate(data) {
  return data.type !== 'application/json'
}
export function getNormalPath(p) {
  if (p.length === 0 || !p || p === 'undefined') {
    return p;
  }
  const res = p.replace('//', '/');
  if (res[res.length - 1] === '/') {
    return res.slice(0, res.length - 1);
  }
  return res;
}
src/utils/validate.js
@@ -78,3 +78,7 @@
  }
  return Array.isArray(arg)
}
export function isHttp(url) {
  return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1;
}
src/views/components/noticeTable.vue
New file
@@ -0,0 +1,183 @@
<template>
    <div>
        <div class="flex justify-between mb-[15px]">
            <div class="block mb-3 font-semibold fonts">待办事项</div>
            <div class="flex text-[12px]">
                <div
                    :class="{ active: currentTab === 'process' }"
                    class="tab"
                    @click="switchTab('process')"
                >
                    流程待办
                </div>
                <!-- <div
                    :class="{ active: currentTab === 'progress' }"
                    class="tab"
                    @click="switchTab('progress')"
                >
                    进度待办
                </div> -->
            </div>
        </div>
        <el-table
            :data="tableData"
            :header-cell-style="{
                background: '#F5F7FC',
                color: '#454B5E',
                fontSize: '12px'
            }"
            height="280"
            max-height="280"
        >
            <el-table-column
                v-for="column in currentTableHeaders"
                :key="column.prop"
                :align="column.align"
                :label="column.label"
                :min-width="column.minWidth"
                :prop="column.prop"
                :show-overflow-tooltip="true"
            >
            </el-table-column>
            <el-table-column
                align="center"
                fixed="right"
                label="操作"
                min-width="150"
            >
                <template #default="scope">
                    <el-button
                        plain
                        size="small"
                        type="primary"
                        @click="handleDetail(scope.row)"
                    >
                        查看</el-button
                    >
                    <el-button
                        plain
                        size="small"
                        type="primary"
                        @click="handleUpdate(scope.row)"
                    >
                        处置</el-button
                    >
                </template>
            </el-table-column>
        </el-table>
        <pagination
            v-show="total >= 0"
            :limit="queryParams.pageSize"
            :page="queryParams.pageNum"
            :total="total"
            @pagination="getList"
        />
    </div>
</template>
<script>
import { getTodo } from '@/api/message';
export default {
    data() {
        return {
            currentTab: "process",
            total: 0,
            queryParams: {
                pageNum: 1,
                pageSize: 10
            },
            tableData: [],
            currentTableHeaders: [
                { label: '流程环节', prop: 'name', minWidth: 150, align: 'left' },
                { label: '申请项目', prop: 'businessName', minWidth: 150, align: 'left' },
                { label: '审批人', prop: 'assigneeName', minWidth: 100, align: 'left' },
                { label: '剩余时间', prop: 'remainingTime', minWidth: 143, align: 'left' }
            ]
        }
    },
    props: {
        calculation: [],
        countExceptionProjectData: {},
    },
    created() {
        this.getList();
    },
    methods: {
        async getList() {
            const resp = await getTodo(this.queryParams);
            if (resp.code === 200) {
                this.total.value = resp.total;
                this.tableData.value = resp.rows;
            }
        },
        handleDetail(row) {
            console.log(111);
            this.$router.push({
                path: '/projectManage/nodeDetails',
                query: {
                    taskId: row.id,
                    id: row.businessKey,
                    disabled: 'true'
                }
            });
        },
        handleUpdate(row) {
            this.$router.push({
                path: '/projectManage/nodeDetails',
                query: {
                    taskId: row.id,
                    id: row.businessKey
                }
            });
            console.log(111);
        },
        switchTab(tab) {
            this.currentTab = tab;
        }
    }
}
</script>
<style lang="scss" scoped>
.tab {
    padding: 8px;
    border: 1px solid #dbdeea;
    cursor: pointer;
    width: 72px;
}
.active {
    border: 1px solid #3369ff;
    color: #3369ff;
}
.fonts {
    font-size: 16px;
    color: #212a40;
    display: flex;
    align-items: center;
}
::v-deep .el-table__row {
    font-size: 12px;
}
::v-deep .el-pagination {
    margin: -15px;
    text-align: end;
}
::v-deep .el-pagination .btn-prev .el-icon,
 ::v-deep .el-pagination .btn-next .el-icon
{
    display: inline;
}
.flex {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
}
</style>
src/views/components/projectOverview.vue
New file
@@ -0,0 +1,308 @@
<template>
    <div class="overview-bottom">
        <div class="abnormal" @click="showAbnormal">
            <div style="margin-bottom: 30px">异常项目情况统计</div>
            <div>
                <div class="abnormal-center">
                    流程异常项目:<span
                        style="font-size: 16px; font-weight: 700"
                        >{{
                            countExceptionProjectData.processExceptionProject
                        }}</span
                    >
                </div>
                <div class="abnormal-center">
                    资金异常项目:<span
                        style="font-size: 16px; font-weight: 700"
                        >0</span
                    >
                </div>
                <div class="abnormal-center">
                    进度异常项目:<span
                        style="font-size: 16px; font-weight: 700"
                        >0</span
                    >
                </div>
            </div>
            <div class="abnormal-img"></div>
        </div>
        <div class="flex gap-[10px] ml-[10px] flex-wrap custom-min-width">
            <div
                v-for="i in calculation"
                :key="i.text"
                :class="setbcStyle(i.text)"
                class="listings"
                @click="showDetail(i.text)"
            >
                <div :class="setTextStyle(i.text)" class="title">
                    {{ i.text }}
                </div>
                <div class="conter">
                    <div :class="setTextColor(i.text)" class="mun">
                        {{ i.mun }}
                    </div>
                    <div class="statistics">
                        <div>{{ i.statistics }}</div>
                        <div :class="setTextColor(i.text)">
                            {{ i.statisticsMun
                            }}<span style="font-size: 18px">亿</span>
                        </div>
                    </div>
                </div>
            </div>
            <!-- <div class="listings">
                <div class="title">
                    储
                </div>
                <div class="conter">
                    <div class="mun">44</div>
                    <div class="statistics">
                        <div>储备项目数量统计</div>
                        <div class="statistics-mun">1233<span style="font-size: 18px;">亿</span></div>
                    </div>
                </div>
            </div>
            <div class="listings">
                <div class="title">
                    储
                </div>
                <div class="conter">
                    <div class="mun">44</div>
                    <div class="statistics">
                        <div>储备项目数量统计</div>
                        <div class="statistics-mun">1233<span style="font-size: 18px;">亿</span></div>
                    </div>
                </div>
            </div>
            <div class="listings">
                <div class="title">
                    储
                </div>
                <div class="conter">
                    <div class="mun">44</div>
                    <div class="statistics">
                        <div>储备项目数量统计</div>
                        <div class="statistics-mun">1233<span style="font-size: 18px;">亿</span></div>
                    </div>
                </div>
            </div>
            <div class="listings">
                <div class="title">
                    储
                </div>
                <div class="conter">
                    <div class="mun">44</div>
                    <div class="statistics">
                        <div>储备项目数量统计</div>
                        <div class="statistics-mun">1233<span style="font-size: 18px;">亿</span></div>
                    </div>
                </div>
            </div>
            <div class="listings">
                <div class="title">
                    储
                </div>
                <div class="conter">
                    <div class="mun">44</div>
                    <div class="statistics">
                        <div>储备项目数量统计</div>
                        <div class="statistics-mun">1233<span style="font-size: 18px;">亿</span></div>
                    </div>
                </div>
            </div>
            <div class="listings">
                <div class="title">
                    储
                </div>
                <div class="conter">
                    <div class="mun">44</div>
                    <div class="statistics">
                        <div>储备项目数量统计</div>
                        <div class="statistics-mun">1233<span style="font-size: 18px;">亿</span></div>
                    </div>
                </div>
            </div>
            <div class="listings">
                <div class="title active">
                    储
                </div>
                <div class="conter">
                    <div class="mun">44</div>
                    <div class="statistics">
                        <div>储备项目数量统计</div>
                        <div class="statistics-mun">1233<span style="font-size: 18px;">亿</span></div>
                    </div>
                </div>
            </div> -->
        </div>
    </div>
</template>
<script >
export default {
    name: "Index",
    data() {
        return {}
    },
    props: {
        calculation: [],
        countExceptionProjectData: {},
    },
    methods: {
        setTextStyle(text) {
            if (text === '储') return 'bg-[#3369FF]';
            if (text === '建') return 'bg-[#64ADFD]';
            if (text === '省') return 'bg-[#FF5E57]';
            if (text === '市') return 'bg-[#FFA83F]';
            if (text === '新') return 'bg-[#5DD1E5]';
            if (text === '竣') return 'bg-[#576BF5]';
            if (text === '县') return 'bg-[#3369FF]';
            if (text === '普') return 'bg-[#64ADFD]';
            return '';
        },
        setTextColor(text) {
            if (text === '储') return 'text-[#3369FF]';
            if (text === '建') return 'text-[#64ADFD]';
            if (text === '省') return 'text-[#FF5E57]';
            if (text === '市') return 'text-[#FFA83F]';
            if (text === '新') return 'text-[#5DD1E5]';
            if (text === '竣') return 'text-[#576BF5]';
            if (text === '县') return 'text-[#3369FF]';
            if (text === '普') return 'text-[#64ADFD]';
            return '';
        },
        // 跳转到异常项目
        showAbnormal() {
            console.log('跳转异常项目');
            this.$router.push({
                path: 'projectEngineering/project/abnormalProject'
            });
        },
        // 跳转到对应的项目库
        showDetail(text) {
            console.log('跳转到对应的项目库', text);
            switch (text) {
                case '储':
                    this.$router.push({
                        path: '/projectEngineering/project/reserveProjects',
                        query: {
                            projectCategory: '1'
                        }
                    });
                    break;
                case '新':
                    this.$router.push({
                        path: '/projectEngineering/project/previousProjects',
                        query: {
                            projectCategory: '2'
                        }
                    });
                    break;
                case '建':
                    this.$router.push({
                        path: '/projectEngineering/project/implementationProject',
                        query: {
                            projectCategory: '3'
                        }
                    });
                    break;
                case '竣':
                    this.$router.push({
                        path: '/projectEngineering/project/completedProjects',
                        query: {
                            projectCategory: '4'
                        }
                    });
                    break;
                default:
                    break;
            }
        }
    }
}
</script>
<style lang="scss" scoped>
.overview-bottom {
    display: flex;
    justify-content: space-between;
    .abnormal {
        position: relative;
        width: 25%;
        min-width: 342px;
        height: 200px;
        background-image: url(../../assets/images/b.png);
        background-size: 100% auto;
        border-radius: 6px;
        font-size: 16px;
        color: #ffffff;
        padding: 20px 0 0 20px;
        .abnormal-center {
            margin-bottom: 15px;
        }
        .abnormal-img {
            position: absolute;
            right: 50px;
            bottom: 20px;
            width: 100px;
            height: 100px;
            background-image: url(../../assets/images/c.png);
        }
    }
    .custom-min-width {
        min-width: 1250px;
        width: 1250px;
    }
    .listings {
        width: 300px;
        height: 90px;
        border-radius: 6px;
        display: flex;
        align-items: center;
        .title {
            width: 45px;
            height: 45px;
            font-size: 24px;
            border-radius: 8px;
            line-height: 45px;
            text-align: center;
            margin-left: 15px;
            color: #ffffff;
        }
        .active {
            background-color: #3369ff;
        }
        .conter {
            margin-left: 10px;
            .mun {
                font-size: 30px;
                line-height: 32px;
            }
            .statistics {
                width: 200px;
                display: flex;
                justify-content: space-between;
                align-items: center;
                // .statistics-mun {
                //     // color: #3369FF;
                // }
            }
        }
    }
}
</style>
src/views/components/tidingsTable.vue
New file
@@ -0,0 +1,283 @@
<template>
    <div>
        <div class="flex justify-between mb-[15px]">
            <div class="block mb-3 font-semibold fonts">消息中心</div>
            <div class="flex text-[12px]">
                <div
                    v-for="tab in tabs"
                    :key="tab.id"
                    :class="{ active: currentTabId === tab.id }"
                    class="tab relative"
                    @click="switchTab(tab.id)"
                >
                    {{ tab.label }}
                    <div v-if="tab.num" class="w-[15px] h-[15px] num">
                        {{ tab.num }}
                    </div>
                </div>
            </div>
        </div>
        <el-table
            :data="tableData"
            :header-cell-style="{
                background: '#F5F7FC',
                color: '#454B5E',
                fontSize: '12px'
            }"
            height="280"
            max-height="280"
        >
            <el-table-column
                v-for="column in currentTableHeaders"
                :key="column.prop"
                :align="column.align"
                :label="column.label"
                :min-width="column.minWidth"
                :prop="column.prop"
            >
                <template v-if="column.slot === 'sort'" #default="scope">
                    <div
                        :class="
                            getSortClass(
                                (queryParams.pageNum - 1) *
                                    queryParams.pageSize +
                                    scope.$index +
                                    1
                            )
                        "
                        class="sort"
                    >
                        <span>{{
                            (queryParams.pageNum - 1) * queryParams.pageSize +
                            scope.$index +
                            1
                        }}</span>
                    </div>
                </template>
            </el-table-column>
            <el-table-column
                align="center"
                class="relative"
                fixed="right"
                label="操作"
                min-width="72"
            >
                <template #default="scope">
                    <el-button
                        plain
                        size="small"
                        type="primary"
                        @click="handleDetail(scope.row)"
                    >
                        查看</el-button
                    >
                    <div v-if="scope.row.isRead === '0'" class="viewRead"></div>
                </template>
            </el-table-column>
        </el-table>
        <pagination
            v-show="total >= 0"
            :limit="queryParams.pageSize"
            :page="queryParams.pageNum"
            :total="total"
            @pagination="getList"
        />
    </div>
</template>
<script>
import { getMessage, getMessageCount, getRead } from '@/api/message';
export default {
    name: "Index",
    data() {
        return {
            total: 0,
            queryParams: {
                pageNum: 1,
                pageSize: 10
            },
            tableData: [],
            tabs: [
                // { id: 'process', label: '通知公告', num: '2' },
                { id: 'review', label: '审核消息', num: '6' }
                // { id: 'supervision', label: '督办消息', num: '9' },
                // { id: 'progress', label: '进度消息', num: '7' }
            ],
            currentTabId: 0,
            //配置表格表头数据
            currentTableHeaders: [
                { label: '排序', prop: 'index', minWidth: 50, align: 'center', slot: 'sort' },
                { label: '发布单位', prop: 'commitDept', minWidth: 150, align: 'left' },
                { label: '内容', prop: 'content', minWidth: 300, align: 'left' },
                { label: '时间', prop: 'createTime', minWidth: 143, align: 'left' }
            ]
        }
    },
    props: {
        calculation: [],
        countExceptionProjectData: {},
    },
    created() {
        this.currentTabId = this.tabs[0].id; // 默认选中的tab的id
        this.getList();
        this.getMessageCountFun();
    },
    methods: {
        async getList() {
            const resp = await getMessage(this.queryParams);
            if (resp.code === 200) {
                this.total = resp.total;
                this.tableData = resp.rows;
            }
        },
        async getMessageCountFun() {
            const resp = await getMessageCount();
            if (resp.code === 200) {
                this.tabs = tabs.map((tab) => {
                    if (tab.label === '审核消息') {
                        tab.num = resp.data.auditCount;
                    }
                    return tab;
                });
            }
        },
        switchTab(tabId) {
            currentTabId = tabId;
        },
        getSortClass(index) {
            if (index === 1) {
                return 'actives';
            } else if (index === 2) {
                return 'two';
            } else if (index === 3) {
                return 'three';
            } else {
                return '';
            }
        },
        async getReadFun(id) {
            const resp = await this.getRead(id);
            if (resp.code === 200) {
                await this.getMessageCountFun();
            }
        },
        handleDetail(row) {
            this.getReadFun(row.id);
            if (row.auditType === '2') {
                this.$router.push({
                    path: '/projectManage/nodeDetails',
                    query: { taskId: row.taskId, id: row.businessKey, auditType: row.auditType, disabled: 'true' }
                });
            } else {
                this.$router.push({
                    path: '/projectManage/nodeDetails',
                    query: { taskId: row.taskId, id: row.businessKey, disabled: 'true' }
                });
            }
        }
    }
}
</script>
<style lang="scss" scoped>
.tab {
    padding: 8px;
    border: 1px solid #dbdeea;
    cursor: pointer;
    width: 72px;
}
.active {
    border: 1px solid #3369ff;
    color: #3369ff;
}
.fonts {
    font-size: 16px;
    color: #212a40;
    display: flex;
    align-items: center;
}
.num {
    position: absolute;
    z-index: 999;
    top: -5px;
    right: -5px;
    border-radius: 50%;
    background: #f63f41;
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
}
::v-deep .el-table__row {
    font-size: 12px;
}
.sort {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background-color: #eaf0ff;
    text-align: center;
    color: #3369ff;
}
.actives {
    background-color: #3369ff;
    color: #fff;
}
.two {
    background-color: #5c87ff;
    color: #fff;
}
.three {
    background-color: #85a5ff;
    color: #fff;
}
::v-deep .el-pagination {
    margin: -15px;
    text-align: end;
}
::v-deep .el-pagination .btn-prev .el-icon,
 ::v-deep .el-pagination .btn-next .el-icon
{
    display: inline;
}
.viewRead {
    width: 4px;
    height: 4px;
    position: absolute;
    z-index: 999;
    top: 10px;
    right: 20px;
    border-radius: 50%;
    background: #f63f41;
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
}
.flex {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
}
</style>
src/views/index.vue
@@ -1,1097 +1,348 @@
<template>
  <div class="app-container home">
    <el-row :gutter="20">
      <el-col :sm="24" :lg="24">
        <blockquote class="text-warning" style="font-size: 14px">
          领取阿里云通用云产品1888优惠券
          <br />
          <el-link
            href="https://www.aliyun.com/minisite/goods?userCode=brki8iof"
            type="primary"
            target="_blank"
            >https://www.aliyun.com/minisite/goods?userCode=brki8iof</el-link
          >
          <br />
          领取腾讯云通用云产品2860优惠券
          <br />
          <el-link
            href="https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console"
            type="primary"
            target="_blank"
            >https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console</el-link
          >
          <br />
          阿里云服务器折扣区
          <el-link href="http://aly.ruoyi.vip" type="primary" target="_blank"
            >>☛☛点我进入☚☚</el-link
          >
          &nbsp;&nbsp;&nbsp; 腾讯云服务器秒杀区
          <el-link href="http://txy.ruoyi.vip" type="primary" target="_blank"
            >>☛☛点我进入☚☚</el-link
          ><br />
          <h4 class="text-danger">
            云产品通用红包,可叠加官网常规优惠使用。(仅限新用户)
          </h4>
        </blockquote>
        <hr />
      </el-col>
    </el-row>
    <el-row :gutter="20">
      <el-col :sm="24" :lg="12" style="padding-left: 20px">
        <h2>射洪项目后台管理框架</h2>
        <p>
          一直想做一款后台管理系统,看了很多优秀的开源项目但是发现没有合适自己的。于是利用空闲休息时间开始自己写一套后台系统。如此有了射洪项目管理系统,她可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA等等,当然,您也可以对她进行深度定制,以做出更强系统。所有前端后台代码封装过后十分精简易上手,出错概率低。同时支持移动客户端访问。系统会陆续更新一些实用功能。
        </p>
        <p>
          <b>当前版本:</b> <span>v{{ version }}</span>
        </p>
        <p>
          <el-tag type="danger">&yen;免费开源</el-tag>
        </p>
        <p>
          <el-button
            type="primary"
            size="mini"
            icon="el-icon-cloudy"
            plain
            @click="goTarget('https://gitee.com/y_project/RuoYi-Vue')"
            >访问码云</el-button
          >
          <el-button
            size="mini"
            icon="el-icon-s-home"
            plain
            @click="goTarget('http://ruoyi.vip')"
            >访问主页</el-button
          >
        </p>
      </el-col>
      <el-col :sm="24" :lg="12" style="padding-left: 50px">
        <el-row>
          <el-col :span="12">
            <h2>技术选型</h2>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="6">
            <h4>后端技术</h4>
            <ul>
              <li>SpringBoot</li>
              <li>Spring Security</li>
              <li>JWT</li>
              <li>MyBatis</li>
              <li>Druid</li>
              <li>Fastjson</li>
              <li>...</li>
            </ul>
          </el-col>
          <el-col :span="6">
            <h4>前端技术</h4>
            <ul>
              <li>Vue</li>
              <li>Vuex</li>
              <li>Element-ui</li>
              <li>Axios</li>
              <li>Sass</li>
              <li>Quill</li>
              <li>...</li>
            </ul>
          </el-col>
        </el-row>
      </el-col>
    </el-row>
    <el-divider />
    <el-row :gutter="20">
      <el-col :xs="24" :sm="24" :md="12" :lg="8">
        <el-card class="update-log">
          <div slot="header" class="clearfix">
            <span>联系信息</span>
          </div>
          <div class="body">
            <p>
              <i class="el-icon-s-promotion"></i> 官网:<el-link
                href="http://www.ruoyi.vip"
                target="_blank"
                >http://www.ruoyi.vip</el-link
              >
            </p>
            <p>
              <i class="el-icon-user-solid"></i> QQ群:<s> 满937441 </s> <s> 满887144332 </s>
              <s> 满180251782 </s> <s> 满104180207 </s> <s> 满186866453 </s> <s> 满201396349 </s>
              <s> 满101456076 </s> <s> 满101539465 </s> <s> 满264312783 </s> <s> 满167385320 </s>
              <s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s> <s> 满108482800 </s>
              <s> 满101046199 </s> <s> 满136919097 </s> <s> 满143961921 </s> <s> 满174951577 </s>
              <s> 满161281055 </s> <s> 满138988063 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=SUc-msaypcqB2UTFif4eqGlBHkKcvMNP&authKey=JdQBouY2PG%2BS%2BCzAfIgbCGNgxyahpfh24IW%2F03rPxGilhqVbisLma%2FFFnt79DHNh&noverify=0&group_code=151450850" target="_blank">151450850</a>
            </p>
            <p>
              <i class="el-icon-chat-dot-round"></i> 微信:<a
                href="javascript:;"
                >/ *射洪项目</a
              >
            </p>
            <p>
              <i class="el-icon-money"></i> 支付宝:<a
                href="javascript:;"
                class="支付宝信息"
                >/ *射洪项目</a
              >
            </p>
          </div>
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="24" :md="12" :lg="8">
        <el-card class="update-log">
          <div slot="header" class="clearfix">
            <span>更新日志</span>
          </div>
          <el-collapse accordion>
            <el-collapse-item title="v3.8.8 - 2024-06-30">
              <ol>
                <li>菜单管理新增路由名称</li>
                <li>新增数据脱敏过滤注解</li>
                <li>用户密码新增非法字符验证</li>
                <li>限制用户操作数据权限范围</li>
                <li>代码生成新增创建表结构功能</li>
                <li>定时任务白名单配置范围缩小</li>
                <li>优化代码生成主子表关联查询方式</li>
                <li>Excel注解新增属性comboReadDict</li>
                <li>Excel注解ColumnType类型新增文本</li>
                <li>新增国际化资源文件配置</li>
                <li>升级oshi到最新版本6.6.1</li>
                <li>升级druid到最新版本1.2.23</li>
                <li>升级core-js到最新版本3.37.1</li>
                <li>更新HttpUtils中的User-Agent</li>
                <li>更新compressionPlugin到6.1.2以兼容node18+</li>
                <li>升级spring-security到安全版本,防止漏洞风险</li>
                <li>升级spring-framework到安全版本,防止漏洞风险</li>
                <li>优化自定义XSS注解匹配方式</li>
                <li>优化缓存监控键名列表排序显示</li>
                <li>优化定时任务日志默认按时间排序</li>
                <li>优化默认文件大小超过2G无效的问题</li>
                <li>优化查表特殊字符使用反斜杠进行转义</li>
                <li>优化定时任务cron表达式小时配置显示错误问题</li>
                <li>优化多个自定数据权限使用in查询,避免多次拼接</li>
                <li>优化导入Excel时设置dictType属性重复查缓存问题</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.8.7 - 2023-12-08">
              <ol>
                <li>操作日志记录部门名称</li>
                <li>全局数据存储用户编号</li>
                <li>新增编程式判断资源访问权限</li>
                <li>操作日志列表新增IP地址查询</li>
                <li>定时任务新增页去除状态选项</li>
                <li>代码生成支持选择前端模板类型</li>
                <li>显隐列组件支持复选框弹出类型</li>
                <li>通用排序属性orderBy参数限制长度</li>
                <li>Excel自定义数据处理器增加单元格/工作簿对象</li>
                <li>升级oshi到最新版本6.4.8</li>
                <li>升级druid到最新版本1.2.20</li>
                <li>升级fastjson到最新版2.0.43</li>
                <li>升级pagehelper到最新版1.4.7</li>
                <li>升级commons.io到最新版本2.13.0</li>
                <li>升级element-ui到最新版本2.15.14</li>
                <li>修复五级路由缓存无效问题</li>
                <li>修复外链带端口出现的异常</li>
                <li>修复树模板父级编码变量错误</li>
                <li>修复字典表详情页面搜索问题</li>
                <li>修复内链iframe没有传递参数问题</li>
                <li>修复自定义字典样式不生效的问题</li>
                <li>修复字典缓存删除方法参数错误问题</li>
                <li>修复Excel导入数据临时文件无法删除问题</li>
                <li>修复未登录带参数访问成功后参数丢失问题</li>
                <li>修复HeaderSearch组件跳转query参数丢失问题</li>
                <li>修复代码生成导入后必填项与数据库不匹配问题</li>
                <li>修复Excels导入时无法获取到dictType字典值问题</li>
                <li>优化下载zip方法新增遮罩层</li>
                <li>优化头像上传参数新增文件名称</li>
                <li>优化字典标签支持自定义分隔符</li>
                <li>优化菜单管理类型为按钮状态可选</li>
                <li>优化前端防重复提交数据大小限制</li>
                <li>优化TopNav菜单没有图标svg不显示</li>
                <li>优化数字金额大写转换精度丢失问题</li>
                <li>优化富文本Editor组件检验图片格式</li>
                <li>优化页签在Firefox浏览器被遮挡的问题</li>
                <li>优化个人中心/基本资料修改时数据显示问题</li>
                <li>优化缓存监控图表支持跟随屏幕大小自适应调整</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.8.6 - 2023-06-30">
              <ol>
                <li>支持登录IP黑名单限制</li>
                <li>新增监控页面图标显示</li>
                <li>操作日志新增消耗时间属性</li>
                <li>屏蔽定时任务bean违规的字符</li>
                <li>日志管理使用索引提升查询性能</li>
                <li>日志注解支持排除指定的请求参数</li>
                <li>支持自定义隐藏属性列过滤子对象</li>
                <li>升级oshi到最新版本6.4.3</li>
                <li>升级druid到最新版本1.2.16</li>
                <li>升级fastjson到最新版2.0.34</li>
                <li>升级spring-boot到最新版本2.5.15</li>
                <li>升级element-ui到最新版本2.15.13</li>
                <li>移除apache/commons-fileupload依赖</li>
                <li>修复页面切换时布局错乱的问题</li>
                <li>修复匿名注解Anonymous空指针问题</li>
                <li>修复路由跳转被阻止时内部产生报错信息问题</li>
                <li>修复isMatchedIp的参数判断产生空指针的问题</li>
                <li>修复用户多角色数据权限可能出现权限抬升的情况</li>
                <li>修复开启TopNav后一级菜单路由参数设置无效问题</li>
                <li>修复DictTag组件value没有匹配的值时则展示value</li>
                <li>优化文件下载出现的异常</li>
                <li>优化选择图标组件高亮回显</li>
                <li>优化弹窗后导航栏偏移的问题</li>
                <li>优化修改密码日志存储明文问题</li>
                <li>优化页签栏关闭其他出现的异常问题</li>
                <li>优化页签关闭左侧选项排除首页选项</li>
                <li>优化关闭当前tab页跳转最右侧tab页</li>
                <li>优化缓存列表清除操作提示不变的问题</li>
                <li>优化字符未使用下划线不进行驼峰式处理</li>
                <li>优化用户导入更新时需获取用户编号问题</li>
                <li>优化侧边栏的平台标题与VUE_APP_TITLE保持同步</li>
                <li>优化导出Excel时设置dictType属性重复查缓存问题</li>
                <li>连接池Druid支持新的配置connectTimeout和socketTimeout</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.8.5 - 2023-01-01">
              <ol>
                <li>定时任务违规的字符</li>
                <li>重置时取消部门选中</li>
                <li>新增返回警告消息提示</li>
                <li>忽略不必要的属性数据返回</li>
                <li>修改参数键名时移除前缓存配置</li>
                <li>导入更新用户数据前校验数据权限</li>
                <li>兼容Excel下拉框内容过多无法显示的问题</li>
                <li>升级echarts到最新版本5.4.0</li>
                <li>升级core-js到最新版本3.25.3</li>
                <li>升级oshi到最新版本6.4.0</li>
                <li>升级kaptcha到最新版2.3.3</li>
                <li>升级druid到最新版本1.2.15</li>
                <li>升级fastjson到最新版2.0.20</li>
                <li>升级pagehelper到最新版1.4.6</li>
                <li>优化弹窗内容过多展示不全问题</li>
                <li>优化swagger-ui静态资源使用缓存</li>
                <li>开启TopNav没有子菜单隐藏侧边栏</li>
                <li>删除fuse无效选项maxPatternLength</li>
                <li>优化导出对象的子列表为空会出现[]问题</li>
                <li>优化编辑头像时透明部分会变成黑色问题</li>
                <li>优化小屏幕上修改头像界面布局错位的问题</li>
                <li>修复代码生成勾选属性无效问题</li>
                <li>修复文件上传组件格式验证问题</li>
                <li>修复回显数据字典数组异常问题</li>
                <li>修复sheet超出最大行数异常问题</li>
                <li>修复Log注解GET请求记录不到参数问题</li>
                <li>修复调度日志点击多次数据不变化的问题</li>
                <li>修复主题颜色在Drawer组件不会加载问题</li>
                <li>修复文件名包含特殊字符的文件无法下载问题</li>
                <li>修复table中更多按钮切换主题色未生效修复问题</li>
                <li>修复某些特性的环境生成代码变乱码TXT文件问题</li>
                <li>修复代码生成图片/文件/单选时选择必填无法校验问题</li>
                <li>修复某些特性的情况用户编辑对话框中角色和部门无法修改问题</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.8.4 - 2022-09-26">
              <ol>
                <li>数据逻辑删除不进行唯一验证</li>
                <li>Excel注解支持导出对象的子列表方法</li>
                <li>Excel注解支持自定义隐藏属性列</li>
                <li>Excel注解支持backgroundColor属性设置背景色</li>
                <li>支持配置密码最大错误次数/锁定时间</li>
                <li>登录日志新增解锁账户功能</li>
                <li>通用下载方法新增config配置选项</li>
                <li>支持多权限字符匹配角色数据权限</li>
                <li>页面内嵌iframe切换tab不刷新数据</li>
                <li>操作日志记录支持排除敏感属性字段</li>
                <li>修复多文件上传报错出现的异常问题</li>
                <li>修复图片预览组件src属性为null值控制台报错问题</li>
                <li>升级oshi到最新版本6.2.2</li>
                <li>升级fastjson到最新版2.0.14</li>
                <li>升级pagehelper到最新版1.4.3</li>
                <li>升级core-js到最新版本3.25.2</li>
                <li>升级element-ui到最新版本2.15.10</li>
                <li>优化任务过期不执行调度</li>
                <li>优化字典数据使用store存取</li>
                <li>优化修改资料头像被覆盖的问题</li>
                <li>优化修改用户登录账号重复验证</li>
                <li>优化代码生成同步后值NULL问题</li>
                <li>优化定时任务支持执行父类方法</li>
                <li>优化用户个人信息接口防止修改部门</li>
                <li>优化布局设置使用el-drawer抽屉显示</li>
                <li>优化没有权限的用户编辑部门缺少数据</li>
                <li>优化日志注解记录限制请求地址的长度</li>
                <li>优化excel/scale属性导出单元格数值类型</li>
                <li>优化日志操作中重置按钮时重复查询的问题</li>
                <li>优化多个相同角色数据导致权限SQL重复问题</li>
                <li>优化表格上右侧工具条(搜索按钮显隐&右侧样式凸出)</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.8.3 - 2022-06-27">
              <ol>
                <li>新增缓存列表菜单功能</li>
                <li>代码生成树表新增(展开/折叠)</li>
                <li>Excel注解支持color字体颜色</li>
                <li>新增Anonymous匿名访问不鉴权注解</li>
                <li>用户头像上传限制只能为图片格式</li>
                <li>接口使用泛型使其看到响应属性字段</li>
                <li>检查定时任务bean所在包名是否为白名单配置</li>
                <li>添加页签openPage支持传递参数</li>
                <li>用户缓存信息添加部门ancestors祖级列表</li>
                <li>升级element-ui到最新版本2.15.8</li>
                <li>升级oshi到最新版本6.1.6</li>
                <li>升级druid到最新版本1.2.11</li>
                <li>升级fastjson到最新版2.0.8</li>
                <li>升级spring-boot到最新版本2.5.14</li>
                <li>降级jsencrypt版本兼容IE浏览器</li>
                <li>删除多余的salt字段</li>
                <li>新增获取不带后缀文件名称方法</li>
                <li>新增获取配置文件中的属性值方法</li>
                <li>新增内容编码/解码方便插件集成使用</li>
                <li>字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)</li>
                <li>优化设置分页参数默认值</li>
                <li>优化对空字符串参数处理的过滤</li>
                <li>优化显示顺序orderNum类型为整型</li>
                <li>优化表单构建按钮不显示正则校验</li>
                <li>优化字典数据回显样式下拉框显示值</li>
                <li>优化R响应成功状态码与全局保持一致</li>
                <li>优化druid开启wall过滤器出现的异常问题</li>
                <li>优化用户管理左侧树型组件增加选中高亮保持</li>
                <li>优化新增用户与角色信息&用户与岗位信息逻辑</li>
                <li>优化默认不启用压缩文件缓存防止node_modules过大</li>
                <li>修复字典数据显示不全问题</li>
                <li>修复操作日志查询类型条件为0时会查到所有数据</li>
                <li>修复Excel注解prompt/combo同时使用不生效问题</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.8.2 - 2022-04-01">
              <ol>
                <li>前端支持设置是否需要防止数据重复提交</li>
                <li>开启TopNav没有子菜单情况隐藏侧边栏</li>
                <li>侧边栏菜单名称过长悬停显示标题</li>
                <li>用户访问控制时校验数据权限,防止越权</li>
                <li>导出Excel时屏蔽公式,防止CSV注入风险</li>
                <li>组件ImagePreview支持多图预览显示</li>
                <li>组件ImageUpload支持多图同时选择上传</li>
                <li>组件FileUpload支持多文件同时选择上传</li>
                <li>服务监控新增运行参数信息显示</li>
                <li>定时任务目标字符串过滤特殊字符</li>
                <li>定时任务目标字符串验证包名白名单</li>
                <li>代码生成列表图片支持预览</li>
                <li>代码生成编辑修改打开新页签</li>
                <li>代码生成新增Java类型Boolean</li>
                <li>代码生成子表支持日期/字典配置</li>
                <li>代码生成同步保留必填/类型选项</li>
                <li>升级oshi到最新版本6.1.2</li>
                <li>升级fastjson到最新版1.2.80</li>
                <li>升级pagehelper到最新版1.4.1</li>
                <li>升级spring-boot到最新版本2.5.11</li>
                <li>升级spring-boot-mybatis到最新版2.2.2</li>
                <li>添加遗漏的分页参数合理化属性</li>
                <li>修改npm即将过期的注册源地址</li>
                <li>修复分页组件请求两次问题</li>
                <li>修复通用文件下载接口跨域问题</li>
                <li>修复Xss注解字段值为空时的异常问题</li>
                <li>修复选项卡点击右键刷新丢失参数问题</li>
                <li>修复表单清除元素位置未垂直居中问题</li>
                <li>修复服务监控中运行参数显示条件错误</li>
                <li>修复导入Excel时字典字段类型为Long转义为空问题</li>
                <li>修复登录超时刷新页面跳转登录页面还提示重新登录问题</li>
                <li>优化加载字典缓存数据</li>
                <li>优化IP地址获取到多个的问题</li>
                <li>优化任务队列满时任务拒绝策略</li>
                <li>优化文件上传兼容Weblogic环境</li>
                <li>优化定时任务默认保存到内存中执行</li>
                <li>优化部门修改缩放后出现的错位问题</li>
                <li>优化Excel格式化不同类型的日期对象</li>
                <li>优化菜单表关键字导致的插件报错问题</li>
                <li>优化Oracle用户头像列为空时不显示问题</li>
                <li>优化页面若未匹配到字典标签则返回原字典值</li>
                <li>优化修复登录失效后多次请求提示多次弹窗问题</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.8.1 - 2022-01-01">
              <ol>
                <li>新增Vue3前端代码生成模板</li>
                <li>新增图片预览组件</li>
                <li>新增压缩插件实现打包Gzip</li>
                <li>自定义xss校验注解实现</li>
                <li>自定义文字复制剪贴指令</li>
                <li>代码生成预览支持复制内容</li>
                <li>路由支持单独配置菜单或角色权限</li>
                <li>用户管理部门查询选择节点后分页参数初始</li>
                <li>修复用户分配角色属性错误</li>
                <li>修复打包后字体图标偶现的乱码问题</li>
                <li>修复菜单管理重置表单出现的错误</li>
                <li>修复版本差异导致的懒加载报错问题</li>
                <li>修复Cron组件中周回显问题</li>
                <li>修复定时任务多参数逗号分隔的问题</li>
                <li>修复根据ID查询列表可能出现的主键溢出问题</li>
                <li>修复tomcat配置参数已过期问题</li>
                <li>升级clipboard到最新版本2.0.8</li>
                <li>升级oshi到最新版本v5.8.6</li>
                <li>升级fastjson到最新版1.2.79</li>
                <li>升级spring-boot到最新版本2.5.8</li>
                <li>升级log4j2到2.17.1,防止漏洞风险</li>
                <li>优化下载解析blob异常提示</li>
                <li>优化代码生成字典组重复问题</li>
                <li>优化查询用户的角色组&岗位组代码</li>
                <li>优化定时任务cron表达式小时设置24</li>
                <li>优化用户导入提示溢出则显示滚动条</li>
                <li>优化防重复提交标识组合为(key+url+header)</li>
                <li>优化分页方法设置成通用方便灵活调用</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.8.0 - 2021-12-01">
              <ol>
                <li>新增配套并同步的Vue3前端版本</li>
                <li>新增通用方法简化模态/缓存/下载/权限/页签使用</li>
                <li>优化导出数据/使用通用下载方法</li>
                <li>Excel注解支持自定义数据处理器</li>
                <li>Excel注解支持导入导出标题信息</li>
                <li>Excel导入支持@Excels注解</li>
                <li>新增组件data-dict,简化数据字典使用</li>
                <li>新增Jaxb依赖,防止jdk8以上出现的兼容错误</li>
                <li>生产环境使用路由懒加载提升页面响应速度</li>
                <li>修复五级以上菜单出现的404问题</li>
                <li>防重提交注解支持配置间隔时间/提示消息</li>
                <li>日志注解新增是否保存响应参数</li>
                <li>任务屏蔽违规字符&参数忽略双引号中的逗号</li>
                <li>升级SpringBoot到最新版本2.5.6</li>
                <li>升级pagehelper到最新版1.4.0</li>
                <li>升级spring-boot-mybatis到最新版2.2.0</li>
                <li>升级oshi到最新版本v5.8.2</li>
                <li>升级druid到最新版1.2.8</li>
                <li>升级velocity到最新版本2.3</li>
                <li>升级fastjson到最新版1.2.78</li>
                <li>升级axios到最新版本0.24.0</li>
                <li>升级dart-sass到版本1.32.13</li>
                <li>升级core-js到最新版本3.19.1</li>
                <li>升级jsencrypt到最新版本3.2.1</li>
                <li>升级js-cookie到最新版本3.0.1</li>
                <li>升级file-saver到最新版本2.0.5</li>
                <li>升级sass-loader到最新版本10.1.1</li>
                <li>升级element-ui到最新版本2.15.6</li>
                <li>新增sendGet无参请求方法</li>
                <li>禁用el-tag组件的渐变动画</li>
                <li>代码生成点击预览重置激活tab</li>
                <li>AjaxResult重写put方法,以方便链式调用</li>
                <li>优化登录/验证码请求headers不设置token</li>
                <li>优化用户个人信息接口防止修改用户名</li>
                <li>优化Cron表达式生成器关闭时销毁避免缓存</li>
                <li>优化注册成功提示消息类型success</li>
                <li>优化aop语法,使用spring自动注入注解</li>
                <li>优化记录登录信息,移除不必要的修改</li>
                <li>优化mybatis全局默认的执行器</li>
                <li>优化Excel导入图片可能出现的异常</li>
                <li>修复代码生成模板主子表删除缺少事务</li>
                <li>修复日志记录可能出现的转换异常</li>
                <li>修复代码生成复选框字典遗漏问题</li>
                <li>修复关闭xss功能导致可重复读RepeatableFilter失效</li>
                <li>修复字符串无法被反转义问题</li>
                <li>修复后端主子表代码模板方法名生成错误问题</li>
                <li>修复xss过滤后格式出现的异常</li>
                <li>修复swagger没有指定dataTypeClass导致启动出现warn日志</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.7.0 - 2021-09-13">
              <ol>
                <li>参数管理支持配置验证码开关</li>
                <li>新增是否开启用户注册功能</li>
                <li>定时任务支持在线生成cron表达式</li>
                <li>菜单管理支持配置路由参数</li>
                <li>支持自定义注解实现接口限流</li>
                <li>Excel注解支持Image图片导入</li>
                <li>自定义弹层溢出滚动样式</li>
                <li>自定义可拖动弹窗宽度指令</li>
                <li>自定义可拖动弹窗高度指令</li>
                <li>修复任意账户越权问题</li>
                <li>修改时检查用户数据权限范围</li>
                <li>修复保存配置主题颜色失效问题</li>
                <li>新增暗色菜单风格主题</li>
                <li>菜单&部门新增展开/折叠功能</li>
                <li>页签新增关闭左侧&添加图标</li>
                <li>顶部菜单排除隐藏的默认路由</li>
                <li>顶部菜单同步系统主题样式</li>
                <li>跳转路由高亮相对应的菜单栏</li>
                <li>代码生成主子表多选行数据</li>
                <li>日期范围支持添加多组</li>
                <li>升级element-ui到最新版本2.15.5</li>
                <li>升级oshi到最新版本v5.8.0</li>
                <li>升级commons.io到最新版本v2.11.0</li>
                <li>定时任务屏蔽ldap远程调用</li>
                <li>定时任务屏蔽http(s)远程调用</li>
                <li>补充定时任务表字段注释</li>
                <li>定时任务对检查异常进行事务回滚</li>
                <li>启用父部门状态排除顶级节点</li>
                <li>富文本新增上传文件大小限制</li>
                <li>默认首页使用keep-alive缓存</li>
                <li>修改代码生成字典回显样式</li>
                <li>自定义分页合理化传入参数</li>
                <li>修复字典组件值为整形不显示问题</li>
                <li>修复定时任务日志执行状态显示</li>
                <li>角色&菜单新增字段属性提示信息</li>
                <li>修复角色分配用户页面参数类型错误提醒</li>
                <li>优化布局设置动画特效</li>
                <li>优化异常处理信息</li>
                <li>优化错误token导致的解析异常</li>
                <li>密码框新增显示切换密码图标</li>
                <li>定时任务新增更多操作</li>
                <li>更多操作按钮添加权限控制</li>
                <li>导入用户样式优化</li>
                <li>提取通用方法到基类控制器</li>
                <li>优化使用权限工具获取用户信息</li>
                <li>优化用户不能删除自己</li>
                <li>优化XSS跨站脚本过滤</li>
                <li>优化代码生成模板</li>
                <li>验证码默认20s超时</li>
                <li>BLOB下载时清除URL对象引用</li>
                <li>代码生成导入表按创建时间排序</li>
                <li>修复代码生成页面数据编辑保存之后总是跳转第一页的问题</li>
                <li>修复带safari浏览器无法格式化utc日期格式yyyy-MM-dd'T'HH:mm:ss.SSS问题</li>
                <li>多图上传组件移除多余的api地址&验证失败导致图片删除问题&无法删除相应图片修复</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.6.0 - 2021-07-12">
              <ol>
                <li>角色管理新增分配用户功能</li>
                <li>用户管理新增分配角色功能</li>
                <li>日志列表支持排序操作</li>
                <li>优化参数&字典缓存操作</li>
                <li>系统布局配置支持动态标题开关</li>
                <li>菜单路由配置支持内链访问</li>
                <li>默认访问后端首页新增提示语</li>
                <li>富文本默认上传返回url类型</li>
                <li>新增自定义弹窗拖拽指令</li>
                <li>全局注册常用通用组件</li>
                <li>全局挂载字典标签组件</li>
                <li>ImageUpload组件支持多图片上传</li>
                <li>FileUpload组件支持多文件上传</li>
                <li>文件上传组件添加数量限制属性</li>
                <li>富文本编辑组件添加类型属性</li>
                <li>富文本组件工具栏配置视频</li>
                <li>封装通用iframe组件</li>
                <li>限制超级管理员不允许操作</li>
                <li>用户信息长度校验限制</li>
                <li>分页组件新增pagerCount属性</li>
                <li>添加bat脚本执行应用</li>
                <li>升级oshi到最新版本v5.7.4</li>
                <li>升级element-ui到最新版本2.15.2</li>
                <li>升级pagehelper到最新版1.3.1</li>
                <li>升级commons.io到最新版本v2.10.0</li>
                <li>升级commons.fileupload到最新版本v1.4</li>
                <li>升级swagger到最新版本v3.0.0</li>
                <li>修复关闭confirm提示框控制台报错问题</li>
                <li>修复存在的SQL注入漏洞问题</li>
                <li>定时任务屏蔽rmi远程调用</li>
                <li>修复用户搜索分页变量错误</li>
                <li>修复导出角色数据范围翻译缺少仅本人</li>
                <li>修复表单构建选择下拉选择控制台报错问题</li>
                <li>优化图片工具类读取文件</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.5.0 - 2021-05-25">
              <ol>
                <li>新增菜单导航显示风格TopNav(false为左侧导航菜单,true为顶部导航菜单)</li>
                <li>布局设置支持保存&重置配置</li>
                <li>修复树表数据显示不全&加载慢问题</li>
                <li>新增IE浏览器版本过低提示页面</li>
                <li>用户登录后记录最后登录IP&时间</li>
                <li>页面导出按钮点击之后添加遮罩</li>
                <li>富文本编辑器支持自定义上传地址</li>
                <li>富文本编辑组件新增readOnly属性</li>
                <li>页签TagsView新增关闭右侧功能</li>
                <li>显隐列组件加载初始默认隐藏列</li>
                <li>关闭头像上传窗口还原默认图片</li>
                <li>个人信息添加手机&邮箱重复验证</li>
                <li>代码生成模板导出按钮点击后添加遮罩</li>
                <li>代码生成模板树表操作列添加新增按钮</li>
                <li>代码生成模板修复主子表字段重名问题</li>
                <li>升级fastjson到最新版1.2.76</li>
                <li>升级druid到最新版本v1.2.6</li>
                <li>升级mybatis到最新版3.5.6 阻止远程代码执行漏洞</li>
                <li>升级oshi到最新版本v5.6.0</li>
                <li>velocity剔除commons-collections版本,防止3.2.1版本的反序列化漏洞</li>
                <li>数据监控页默认账户密码防止越权访问</li>
                <li>修复firefox下表单构建拖拽会新打卡一个选项卡</li>
                <li>修正后端导入表权限标识</li>
                <li>修正前端操作日志&登录日志权限标识</li>
                <li>设置Redis配置HashKey序列化</li>
                <li>删除操作日志记录信息</li>
                <li>上传媒体类型添加视频格式</li>
                <li>修复请求形参未传值记录日志异常问题</li>
                <li>优化xss校验json请求条件</li>
                <li>树级结构更新子节点使用replaceFirst</li>
                <li>优化ExcelUtil空值处理</li>
                <li>日志记录过滤BindingResult对象,防止异常</li>
                <li>修改主题后mini类型按钮无效问题</li>
                <li>优化通用下载完成后删除节点</li>
                <li>通用Controller添加响应返回消息</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.4.0 - 2021-02-22">
              <ol>
                <li>代码生成模板支持主子表</li>
                <li>表格右侧工具栏组件支持显隐列</li>
                <li>图片组件添加预览&移除功能</li>
                <li>Excel注解支持Image图片导出</li>
                <li>操作按钮组调整为朴素按钮样式</li>
                <li>代码生成支持文件上传组件</li>
                <li>代码生成日期控件区分范围</li>
                <li>代码生成数据库文本类型生成表单文本域</li>
                <li>用户手机邮箱&菜单组件修改允许空字符串</li>
                <li>升级SpringBoot到最新版本2.2.13 提升启动速度</li>
                <li>升级druid到最新版本v1.2.4</li>
                <li>升级fastjson到最新版1.2.75</li>
                <li>升级element-ui到最新版本2.15.0</li>
                <li>修复IE11浏览器报错问题</li>
                <li>优化多级菜单之间切换无法缓存的问题</li>
                <li>修复四级菜单无法显示问题</li>
                <li>修正侧边栏静态路由丢失问题</li>
                <li>修复角色管理-编辑角色-功能权限显示异常</li>
                <li>配置文件新增redis数据库索引属性</li>
                <li>权限工具类增加admin判断</li>
                <li>角色非自定义权限范围清空选择值</li>
                <li>修复导入数据为负浮点数时丢失精度问题</li>
                <li>移除path-to-regexp正则匹配插件</li>
                <li>修复生成树表代码异常</li>
                <li>修改ip字段长度防止ipv6地址长度不够</li>
                <li>防止get请求参数值为false或0等特殊值会导致无法正确的传参</li>
                <li>登录后push添加catch防止出现检查错误</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.3.0 - 2020-12-14">
              <ol>
                <li>新增缓存监控功能</li>
                <li>支持主题风格配置</li>
                <li>修复多级菜单之间切换无法缓存的问题</li>
                <li>多级菜单自动配置组件</li>
                <li>代码生成预览支持高亮显示</li>
                <li>支持Get请求映射Params参数</li>
                <li>删除用户和角色解绑关联</li>
                <li>去除用户手机邮箱部门必填验证</li>
                <li>Excel支持注解align对齐方式</li>
                <li>Excel支持导入Boolean型数据</li>
                <li>优化头像样式,鼠标移入悬停遮罩</li>
                <li>代码生成预览提供滚动机制</li>
                <li>代码生成删除多余的数字float类型</li>
                <li>修正转换字符串的目标字符集属性</li>
                <li>回显数据字典防止空值报错</li>
                <li>日志记录增加过滤多文件场景</li>
                <li>修改缓存Set方法可能导致嵌套的问题</li>
                <li>移除前端一些多余的依赖</li>
                <li>防止安全扫描YUI出现的风险提示</li>
                <li>修改node-sass为dart-sass</li>
                <li>升级SpringBoot到最新版本2.1.18</li>
                <li>升级poi到最新版本4.1.2</li>
                <li>升级oshi到最新版本v5.3.6</li>
                <li>升级bitwalker到最新版本1.21</li>
                <li>升级axios到最新版本0.21.0</li>
                <li>升级element-ui到最新版本2.14.1</li>
                <li>升级vue到最新版本2.6.12</li>
                <li>升级vuex到最新版本3.6.0</li>
                <li>升级vue-cli到版本4.5.9</li>
                <li>升级vue-router到最新版本3.4.9</li>
                <li>升级vue-cli到最新版本4.4.6</li>
                <li>升级vue-cropper到最新版本0.5.5</li>
                <li>升级clipboard到最新版本2.0.6</li>
                <li>升级core-js到最新版本3.8.1</li>
                <li>升级echarts到最新版本4.9.0</li>
                <li>升级file-saver到最新版本2.0.4</li>
                <li>升级fuse.js到最新版本6.4.3</li>
                <li>升级js-beautify到最新版本1.13.0</li>
                <li>升级js-cookie到最新版本2.2.1</li>
                <li>升级path-to-regexp到最新版本6.2.0</li>
                <li>升级quill到最新版本1.3.7</li>
                <li>升级screenfull到最新版本5.0.2</li>
                <li>升级sortablejs到最新版本1.10.2</li>
                <li>升级vuedraggable到最新版本2.24.3</li>
                <li>升级chalk到最新版本4.1.0</li>
                <li>升级eslint到最新版本7.15.0</li>
                <li>升级eslint-plugin-vue到最新版本7.2.0</li>
                <li>升级lint-staged到最新版本10.5.3</li>
                <li>升级runjs到最新版本4.4.2</li>
                <li>升级sass-loader到最新版本10.1.0</li>
                <li>升级script-ext-html-webpack-plugin到最新版本2.1.5</li>
                <li>升级svg-sprite-loader到最新版本5.1.1</li>
                <li>升级vue-template-compiler到最新版本2.6.12</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.2.1 - 2020-11-18">
              <ol>
                <li>阻止任意文件下载漏洞</li>
                <li>代码生成支持上传控件</li>
                <li>新增图片上传组件</li>
                <li>调整默认首页</li>
                <li>升级druid到最新版本v1.2.2</li>
                <li>mapperLocations配置支持分隔符</li>
                <li>权限信息调整</li>
                <li>调整sql默认时间</li>
                <li>解决代码生成没有bit类型的问题</li>
                <li>升级pagehelper到最新版1.3.0</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.2.0 - 2020-10-10">
              <ol>
                <li>升级springboot版本到2.1.17 提升安全性</li>
                <li>升级oshi到最新版本v5.2.5</li>
                <li>升级druid到最新版本v1.2.1</li>
                <li>升级jjwt到版本0.9.1</li>
                <li>升级fastjson到最新版1.2.74</li>
                <li>修改sass为node-sass,避免el-icon图标乱码</li>
                <li>代码生成支持同步数据库</li>
                <li>代码生成支持富文本控件</li>
                <li>代码生成页面时不忽略remark属性</li>
                <li>代码生成添加select必填选项</li>
                <li>Excel导出类型NUMERIC支持精度浮点类型</li>
                <li>Excel导出targetAttr优化获取值,防止get方法不规范</li>
                <li>Excel注解支持自动统计数据总和</li>
                <li>Excel注解支持设置BigDecimal精度&舍入规则</li>
                <li>菜单&数据权限新增(展开/折叠 全选/全不选 父子联动)</li>
                <li>允许用户分配到部门父节点</li>
                <li>菜单新增是否缓存keep-alive</li>
                <li>表格操作列间距调整</li>
                <li>限制系统内置参数不允许删除</li>
                <li>富文本组件优化,支持自定义高度&图片冲突问题</li>
                <li>富文本工具栏样式对齐</li>
                <li>导入excel整形值校验优化</li>
                <li>修复页签关闭所有时固定标签路由不刷新问题</li>
                <li>表单构建布局型组件新增按钮</li>
                <li>左侧菜单文字过长显示省略号</li>
                <li>修正根节点为子部门时,树状结构显示问题</li>
                <li>修正调用目标字符串最大长度</li>
                <li>修正菜单提示信息错误</li>
                <li>修正定时任务执行一次权限标识</li>
                <li>修正数据库字符串类型nvarchar</li>
                <li>优化递归子节点</li>
                <li>优化数据权限判断</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.1.0 - 2020-08-13">
              <ol>
                <li>表格工具栏右侧添加刷新&显隐查询组件</li>
                <li>后端支持CORS跨域请求</li>
                <li>代码生成支持选择上级菜单</li>
                <li>代码生成支持自定义路径</li>
                <li>代码生成支持复选框</li>
                <li>Excel导出导入支持dictType字典类型</li>
                <li>Excel支持分割字符串组内容</li>
                <li>验证码类型支持(数组计算、字符验证)</li>
                <li>升级vue-cli版本到4.4.4</li>
                <li>修改 node-sass 为 dart-sass</li>
                <li>表单类型为Integer/Long设置整形默认值</li>
                <li>代码生成器默认mapper路径与默认mapperScan路径不一致</li>
                <li>优化防重复提交拦截器</li>
                <li>优化上级菜单不能选择自己</li>
                <li>修复角色的权限分配后,未实时生效问题</li>
                <li>修复在线用户日志记录类型</li>
                <li>修复富文本空格和缩进保存后不生效问题</li>
                <li>修复在线用户判断逻辑</li>
                <li>唯一限制条件只返回单条数据</li>
                <li>添加获取当前的环境配置方法</li>
                <li>超时登录后页面跳转到首页</li>
                <li>全局异常状态汉化拦截处理</li>
                <li>HTML过滤器改为将html转义</li>
                <li>检查字符支持小数点&降级改成异常提醒</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v3.0.0 - 2020-07-20">
              <ol>
                <li>单应用调整为多模块项目</li>
                <li>升级element-ui版本到2.13.2</li>
                <li>删除babel,提高编译速度。</li>
                <li>新增菜单默认主类目</li>
                <li>编码文件名修改为uuid方式</li>
                <li>定时任务cron表达式验证</li>
                <li>角色权限修改时已有权限未自动勾选异常修复</li>
                <li>防止切换权限用户后登录出现404</li>
                <li>Excel支持sort导出排序</li>
                <li>创建用户不允许选择超级管理员角色</li>
                <li>修复代码生成导入表结构出现异常页面不提醒问题</li>
                <li>修复代码生成点击多次表修改数据不变化的问题</li>
                <li>修复头像上传成功二次打开无法改变裁剪框大小和位置问题</li>
                <li>修复布局为small者mini用户表单显示错位问题</li>
                <li>修复热部署导致的强换异常问题</li>
                <li>修改用户管理复选框宽度,防止部分浏览器出现省略号</li>
                <li>IpUtils工具,清除Xss特殊字符,防止Xff注入攻击</li>
                <li>生成domain 如果是浮点型 统一用BigDecimal</li>
                <li>定时任务调整label-width,防止部署出现错位</li>
                <li>调整表头固定列默认样式</li>
                <li>代码生成模板调整,字段为String并且必填则加空串条件</li>
                <li>代码生成字典Integer/Long使用parseInt</li>
                <li>
                  修复dict_sort不可update为0的问题&查询返回增加dict_sort升序排序
                </li>
                <li>修正岗位导出权限注解</li>
                <li>禁止加密密文返回前端</li>
                <li>修复代码生成页面中的查询条件创建时间未生效的问题</li>
                <li>修复首页搜索菜单外链无法点击跳转问题</li>
                <li>修复菜单管理选择图标,backspace删除时不过滤数据</li>
                <li>用户管理部门分支节点不可检查&显示计数</li>
                <li>数据范围过滤属性调整</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v2.3.0 - 2020-06-01">
              <ol>
                <li>升级fastjson到最新版1.2.70 修复高危安全漏洞</li>
                <li>dev启动默认打开浏览器</li>
                <li>vue-cli使用默认source-map</li>
                <li>slidebar eslint报错优化</li>
                <li>当tags-view滚动关闭右键菜单</li>
                <li>字典管理添加缓存读取</li>
                <li>参数管理支持缓存操作</li>
                <li>支持一级菜单(和主页同级)在main区域显示</li>
                <li>限制外链地址必须以http(s)开头</li>
                <li>tagview & sidebar 主题颜色与element ui(全局)同步</li>
                <li>修改数据源类型优先级,先根据方法,再根据类</li>
                <li>支持是否需要设置token属性,自定义返回码消息。</li>
                <li>swagger请求前缀加入配置。</li>
                <li>登录地点设置内容过长则隐藏显示</li>
                <li>修复定时任务执行一次按钮后不提示消息问题</li>
                <li>修改上级部门(选择项排除本身和下级)</li>
                <li>通用http发送方法增加参数 contentType 编码类型</li>
                <li>更换IP地址查询接口</li>
                <li>修复页签变量undefined</li>
                <li>添加校验部门包含未停用的子部门</li>
                <li>修改定时任务详情下次执行时间日期显示错误</li>
                <li>角色管理查询设置默认排序字段</li>
                <li>swagger添加enable参数控制是否启用</li>
                <li>只对json类型请求构建可重复读取inputStream的request</li>
                <li>修改代码生成字典字段int类型没有自动选中问题</li>
                <li>vuex用户名取值修正</li>
                <li>表格树模板去掉多余的)</li>
                <li>代码生成序号修正</li>
                <li>全屏情况下不调整上外边距</li>
                <li>代码生成Date字段添加默认格式</li>
                <li>用户管理角色选择权限控制</li>
                <li>修复路由懒加载报错问题</li>
                <li>模板sql.vm添加菜单状态</li>
                <li>设置用户名称不能修改</li>
                <li>dialog添加append-to-body属性,防止ie遮罩</li>
                <li>菜单区分状态和显示隐藏功能</li>
                <li>升级fastjson到最新版1.2.68 修复安全加固</li>
                <li>修复代码生成如果选择字典类型缺失逗号问题</li>
                <li>登录请求params更换为data,防止暴露url</li>
                <li>日志返回时间格式处理</li>
                <li>添加handle控制允许拖动的元素</li>
                <li>布局设置点击扩大范围</li>
                <li>代码生成列属性排序查询</li>
                <li>代码生成列支持拖动排序</li>
                <li>修复时间格式不支持ios问题</li>
                <li>表单构建添加父级class,防止冲突</li>
                <li>定时任务并发属性修正</li>
                <li>角色禁用&菜单隐藏不查询权限</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v2.2.0 - 2020-03-18">
              <ol>
                <li>系统监控新增定时任务功能</li>
                <li>添加一个打包Web工程bat</li>
                <li>修复页签鼠标滚轮按下的时候,可以关闭不可关闭的tag</li>
                <li>修复点击退出登录有时会无提示问题</li>
                <li>修复防重复提交注解无效问题</li>
                <li>修复通知公告批量删除异常问题</li>
                <li>添加菜单时路由地址必填限制</li>
                <li>代码生成字段描述可编辑</li>
                <li>修复用户修改个人信息导致缓存不过期问题</li>
                <li>个人信息创建时间获取正确属性值</li>
                <li>操作日志详细显示正确类型</li>
                <li>导入表单击行数据时选中对应的复选框</li>
                <li>批量替换表前缀逻辑调整</li>
                <li>固定重定向路径表达式</li>
                <li>升级element-ui版本到2.13.0</li>
                <li>操作日志排序调整</li>
                <li>修复charts切换侧边栏或者缩放窗口显示bug</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v2.1.0 - 2020-02-24">
              <ol>
                <li>新增表单构建</li>
                <li>代码生成支持树表结构</li>
                <li>新增用户导入</li>
                <li>修复动态加载路由页面刷新问题</li>
                <li>修复地址开关无效问题</li>
                <li>汉化错误提示页面</li>
                <li>代码生成已知问题修改</li>
                <li>修复多数据源下配置关闭出现异常处理</li>
                <li>添加HTML过滤器,用于去除XSS漏洞隐患</li>
                <li>修复上传头像控制台出现异常</li>
                <li>修改用户管理分页不正确的问题</li>
                <li>修复验证码记录提示错误</li>
                <li>修复request.js缺少Message引用</li>
                <li>修复表格时间为空出现的异常</li>
                <li>添加Jackson日期反序列化时区配置</li>
                <li>调整根据用户权限加载菜单数据树形结构</li>
                <li>调整成功登录不恢复按钮,防止多次点击</li>
                <li>修改用户个人资料同步缓存信息</li>
                <li>修复页面同时出现el-upload和Editor不显示处理</li>
                <li>修复在角色管理页修改菜单权限偶尔未选中问题</li>
                <li>配置文件新增redis密码属性</li>
                <li>设置mybatis全局的配置文件</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v2.0.0 - 2019-12-02">
              <ol>
                <li>新增代码生成</li>
                <li>新增@RepeatSubmit注解,防止重复提交</li>
                <li>新增菜单主目录添加/删除操作</li>
                <li>日志记录过滤特殊对象,防止转换异常</li>
                <li>修改代码生成路由脚本错误</li>
                <li>用户上传头像实时同步缓存,无需重新登录</li>
                <li>调整切换页签后不重新加载数据</li>
                <li>添加jsencrypt实现参数的前端加密</li>
                <li>系统退出删除用户缓存记录</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v1.1.0 - 2019-11-11">
              <ol>
                <li>新增在线用户管理</li>
                <li>新增按钮组功能实现(批量删除、导出、清空)</li>
                <li>新增查询条件重置按钮</li>
                <li>新增Swagger全局Token配置</li>
                <li>新增后端参数校验</li>
                <li>修复字典管理页面的日期查询异常</li>
                <li>修改时间函数命名防止冲突</li>
                <li>去除菜单上级校验,默认为顶级</li>
                <li>修复用户密码无法修改问题</li>
                <li>修复菜单类型为按钮时不显示权限标识</li>
                <li>其他细节优化</li>
              </ol>
            </el-collapse-item>
            <el-collapse-item title="v1.0.0 - 2019-10-08">
              <ol>
                <li>射洪项目前后端分离系统正式发布</li>
              </ol>
            </el-collapse-item>
          </el-collapse>
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="24" :md="12" :lg="8">
        <el-card class="update-log">
          <div slot="header" class="clearfix">
            <span>捐赠支持</span>
          </div>
          <div class="body">
            <img
              src="@/assets/images/pay.png"
              alt="donate"
              width="100%"
    <div class="home">
        <div class="overview">
            <div class="overview-top">
                <div class="title">项目总览</div>
                <div class="qurey">
                    <el-form ref="queryFormRef" :model="queryParams">
                        <div class="search_from">
                            <el-form-item label="日期">
                                <el-date-picker
                                    style="width: 300px"
                                    v-model="timeMerge"
                                    clearable
                                    end-placeholder="结束时间"
                                    format="YYYY-MM-DD"
                                    placeholder="请选择日期"
                                    size="default"
                                    start-placeholder="开始时间"
                                    type="daterange"
                                    value-format="YYYY-MM-DD"
                                    @change="dataPickerChange"
                                />
                            </el-form-item>
                            <el-form-item label="行政区划">
                                <el-select
                                    v-model="queryParams.area"
                                    clearable
                                    placeholder="请选择区划"
                                    style="width: 180px"
                                >
                                    <el-option
                                        v-for="item in sys_administrative_divisions"
                                        :key="item.value"
                                        :label="item.label"
                                        :value="item.value"
                                    />
                                </el-select>
                            </el-form-item>
                            <el-form-item
                                label="投资金额"
                                style="margin-right: 50px"
                            >
                                <div class="from_input">
                                    <el-input
                                        v-model="queryParams.start"
                                        clearable
                                        placeholder="请输入"
                                        style="width: 100px"
                                        type="number"
                                    />
                                    至
                                    <el-input
                                        v-model="queryParams.end"
                                        clearable
                                        placeholder="请输入"
                                        style="width: 100px"
                                        type="number"
                                    />
                                </div>
                            </el-form-item>
                            <el-form-item style="margin-right: 20px">
                                <el-button
                                    icon="Search"
                                    type="primary"
                                    @click="handleQuery"
                                    >搜索</el-button
                                >
                                <el-button icon="Refresh" @click="resetQuery"
                                    >重置</el-button
                                >
                            </el-form-item>
                        </div>
                    </el-form>
                </div>
            </div>
            <!-- 统计情况 -->
            <ProjectOverview
                :calculation="calculation"
                :countExceptionProjectData="countExceptionProjectData"
            />
            <span style="display: inline-block; height: 30px; line-height: 30px"
              >你可以请作者喝杯咖啡表示鼓励</span
            >
          </div>
        </el-card>
      </el-col>
    </el-row>
  </div>
        </div>
        <!-- 代办事项 -->
        <div class="flex">
            <div class="flex_card">
                <el-card>
                    <NoticeTable style="height: 360px" />
                </el-card>
            </div>
            <!-- 消息通知 -->
            <div class="flex_card">
                <el-card>
                    <TidingsTable style="height: 360px" />
                </el-card>
            </div>
            <!-- 地图 -->
        </div>
        <div class="mt-[10px] min-w-[1600px]">
            <el-card>
                <div>
                    <div class="search-form">
                        <el-form :model="searchForm" inline>
                            <el-form-item label=" ">
                                <el-input
                                    v-model="searchForm.name"
                                    clearable
                                    placeholder="请输入项目名称或项目代码"
                                    style="width: 180px"
                                />
                            </el-form-item>
                            <el-form-item style="margin-right: 0px">
                                <el-button
                                    icon="Search"
                                    type="primary"
                                    @click="searchList"
                                    >搜索</el-button
                                >
                                <el-button icon="Refresh" @click="mapQuery"
                                    >重置</el-button
                                >
                            </el-form-item>
                        </el-form>
                    </div>
                    <div class="flex w-full h-[500px] border border-[#DBDEEA]">
                        <!-- <Map
                            id="DangerSourceId"
                            ref="mapRef"
                            :is-show-control="true"
                            :list-type="true"
                            :map-list="tableDatas"
                            :map-type="true"
                            class="w-full h-full border-r border-[#DBDEEA]"
                            @label-click="showDetails"
                            @mark-click="showDetails"
                            @current-label-style-change="labelStyleChange"
                        /> -->
                    </div>
                </div>
            </el-card>
        </div>
    </div>
</template>
<script>
import SvgIcon from '@/components/SvgIcon/index.vue';
import ProjectOverview from './components/projectOverview.vue';
import NoticeTable from './components/noticeTable.vue';
import TidingsTable from './components/tidingsTable.vue';
// import Map from './components/Map/index.vue';
export default {
  name: "Index",
  data() {
    return {
      // 版本号
      version: "3.8.8"
    };
  },
  methods: {
    goTarget(href) {
      window.open(href, "_blank");
    name: "Index",
    data() {
        return {
            queryParams: {},
            timeMerge: [],
            sys_administrative_divisions: [],
            calculation: [],
            countExceptionProjectData: {},
            searchForm: {},
            tableDatas: []
        };
    },
    components: {
        SvgIcon,
        ProjectOverview,
        NoticeTable,
        TidingsTable
    },
    methods: {
        dataPickerChange(val) {
            if (!val) {
                this.queryParams.startTime = '';
                this.queryParams.endTime = '';
                return;
            }
            this.queryParams.startTime = timeMerge[0];
            this.queryParams.endTime = timeMerge[1];
        },
        handleQuery() {
            console.log(queryParams);
            const obj = {
                startDate: queryParams.startTime,
                endDate: queryParams.endTime,
                areaCode: queryParams.area,
                minInvestment: queryParams.start,
                maxInvestment: queryParams.end
            };
            this.getCalculatioln(obj).then((res) => {
                const arr = res.data.proPhaseCountVO?.concat(res.data.impTypeCountVO);
                const newArr = arr.map((item) => ({
                    text: item.text,
                    mun: item.count,
                    statistics: item.type,
                    statisticsMun: item.amount
                }));
                // 创建一个对象,以便根据 text 属性快速查找 newArr 中的对象
                const newArrLookup = newArr.reduce((lookup, item) => {
                    lookup[item.text] = item;
                    return lookup;
                }, {});
                // 更新 calculation 数组,保持其原始顺序
                calculation.value = calculation.value.map((item) => {
                    const newItem = newArrLookup[item.text];
                    return newItem ? newItem : item; // 如果 newItem 存在,则返回 newItem,否则返回原始 item
                });
            });
            abnormalData(obj);
        },
        // 获取异常数据
        async abnormalData(obj) {
            const res = await getAbnormalData(obj);
            this.countExceptionProjectData = res.data;
        },
        //地图详情
        showDetails(row) {
            console.log(row);
        },
        labelStyleChange(currentLabel) {
            if (lastLabel) {
                lastLabel.setBackgroundColor('#fff');
                lastLabel.setFontColor('var(--el-color-primary)');
            }
            currentLabel.setBackgroundColor('var(--el-color-primary)');
            currentLabel.setFontColor('#fff');
            lastLabel = currentLabel;
        },
        resetQuery() {
            this.queryParams = {
                area: '',
                start: '',
                end: '',
                startTime: '',
                endTime: ''
            };
            this.timeMerge = [];
            this.handleQuery();
        },
        // 地图搜索
        async searchList() {
            // await search()
            this.mapRef.moveTo(105.37281, 30.87145);
        },
        mapQuery() {
        }
    }
  }
};
}
</script>
<style scoped lang="scss">
.home {
  blockquote {
    padding: 10px 20px;
    margin: 0 0 20px;
    font-size: 17.5px;
    border-left: 5px solid #eee;
  }
  hr {
    margin-top: 20px;
    margin-bottom: 20px;
    border: 0;
    border-top: 1px solid #eee;
  }
  .col-item {
    margin-bottom: 20px;
  }
    padding: 10px;
    background-color: #f3f7fc;
    min-height: calc(100vh - 85px);
    overflow-x: auto;
  ul {
    padding: 0;
    .overview {
        padding: 10px 0 0 10px;
        height: 280px;
        min-width: 1600px;
        background-color: #fff;
        border-radius: 6px;
        .overview-top {
            display: flex;
            justify-content: space-between;
            .title {
                font-size: 16px;
            }
            .qurey {
                font-size: 12px;
                font-weight: 100;
            }
        }
    }
    .search-form {
        display: flex;
        justify-content: flex-end;
    }
}
// ::v-deep .el-form-item {
//     display: inline-block !important;
//     margin-right: 10px;
// }
::v-deep .el-form-item--medium .el-form-item__content {
    display: inline-block !important;
}
::v-deep .el-form-item__label {
    font-size: 12px;
    font-weight: 400;
}
::v-deep .el-button {
    font-size: 12px;
}
::v-deep.el-input__inner {
    font-size: 12px;
    height: 32px;
}
::v-deep.el-range-input {
    font-size: 12px;
}
::v-deep.el-select__placeholder {
    font-size: 12px;
}
::v-deep input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }
  font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 13px;
  color: #676a6c;
  overflow-x: hidden;
  ul {
    list-style-type: none;
  }
  h4 {
    margin-top: 0px;
  }
  h2 {
    margin-top: 10px;
    font-size: 26px;
    font-weight: 100;
  }
  p {
    margin-top: 10px;
    b {
      font-weight: 700;
    }
  }
  .update-log {
    ol {
      display: block;
      list-style-type: decimal;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0;
      margin-inline-end: 0;
      padding-inline-start: 40px;
    }
  }
}
input[type='number'] {
    -moz-appearance: textfield;
}
.search_from {
    display: flex;
    justify-self: justify-end;
    gap: 10px;
}
.flex {
    display: flex;
    gap: 2%;
}
.flex_card {
    width: 49%;
    margin: 10px 0px;
}
.from_input {
    display: flex;
    gap: 10px;
    font-size: 12px;
}
</style>
src/views/login.vue
@@ -1,386 +1,396 @@
<template>
  <div class="login">
    <div class="nav">
      <img alt="" src="../assets/images/bj.png" />
      <div class="ml-[10px]">射洪市项目管理系统</div>
    </div>
    <div class="conter">
      <div class="left">
        <div class="title">射洪市项目管理系统</div>
        <div>
          运用系统的观点、方法和理论,对项目涉及的全部工作进行有效地管理
    <div class="login">
        <div class="nav">
            <img alt="" src="../assets/images/bj.png" />
            <div class="ml-[10px]">射洪市项目管理系统</div>
        </div>
        <div class="img"></div>
      </div>
      <div class="right">
        <div class="tab">
          <div
            :class="{ active: currentTab === 'corporation' }"
            class="login-corporation"
            @click="currentClick('corporation')"
          >
            法人登录
          </div>
          <div
            :class="{ active: currentTab === 'supervise' }"
            class="login-supervise"
            @click="superviseClick('supervise')"
          >
            监管部门登录
          </div>
        </div>
        <el-form
          ref="loginRef"
          :model="loginForm"
          :rules="loginRules"
          class="login-form"
        >
          <!-- <el-form-item v-if="tenantEnabled" prop="tenantId">
        <div class="conter">
            <div class="left">
                <div class="title">射洪市项目管理系统</div>
                <div>
                    运用系统的观点、方法和理论,对项目涉及的全部工作进行有效地管理
                </div>
                <div class="img"></div>
            </div>
            <div class="right">
                <div class="tab">
                    <div
                        :class="{ active: currentTab === 'corporation' }"
                        class="login-corporation"
                        @click="currentClick('corporation')"
                    >
                        法人登录
                    </div>
                    <div
                        :class="{ active: currentTab === 'supervise' }"
                        class="login-supervise"
                        @click="superviseClick('supervise')"
                    >
                        监管部门登录
                    </div>
                </div>
                <el-form
                    ref="loginRef"
                    :model="loginForm"
                    :rules="loginRules"
                    class="login-form"
                >
                    <!-- <el-form-item v-if="tenantEnabled" prop="tenantId">
            <el-select v-model="loginForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">
              <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName"
                :value="item.tenantId"></el-option>
              <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
</el-select>
</el-form-item> -->
          <el-form-item prop="username">
            <el-input
              v-model="loginForm.username"
              auto-complete="off"
              maxlength="20"
              placeholder="请输入账号"
              size="large"
              type="text"
            >
              <template #prefix>
                <svg-icon class="el-input__icon input-icon" icon-class="user" />
              </template>
            </el-input>
          </el-form-item>
          <el-form-item prop="password">
            <el-input
              v-model="loginForm.password"
              auto-complete="off"
              maxlength="20"
              placeholder="请输入密码"
              size="large"
              type="password"
              @keyup.enter="handleLogin"
            >
              <template #prefix>
                <svg-icon
                  class="el-input__icon input-icon"
                  icon-class="password"
                />
              </template>
            </el-input>
          </el-form-item>
          <el-form-item v-if="captchaEnabled" prop="code">
            <el-input
              v-model="loginForm.code"
              auto-complete="off"
              maxlength="20"
              placeholder="验证码"
              size="large"
              style="width: 167px"
              @keyup.enter="handleLogin"
            >
              <template #prefix>
                <svg-icon
                  class="el-input__icon input-icon"
                  icon-class="validCode"
                />
              </template>
            </el-input>
            <div class="login-code">
              <img :src="codeUrl" class="login-code-img" @click="getCode" />
                    <el-form-item prop="username">
                        <el-input
                            v-model="loginForm.username"
                            auto-complete="off"
                            maxlength="20"
                            placeholder="请输入账号"
                            size="large"
                            type="text"
                        >
                            <template #prefix>
                                <svg-icon
                                    class="el-input__icon input-icon"
                                    icon-class="user"
                                />
                            </template>
                        </el-input>
                    </el-form-item>
                    <el-form-item prop="password">
                        <el-input
                            v-model="loginForm.password"
                            auto-complete="off"
                            maxlength="20"
                            placeholder="请输入密码"
                            size="large"
                            type="password"
                            @keyup.enter="handleLogin"
                        >
                            <template #prefix>
                                <svg-icon
                                    class="el-input__icon input-icon"
                                    icon-class="password"
                                />
                            </template>
                        </el-input>
                    </el-form-item>
                    <el-form-item v-if="captchaEnabled" prop="code">
                        <el-input
                            v-model="loginForm.code"
                            auto-complete="off"
                            maxlength="20"
                            placeholder="验证码"
                            size="large"
                            style="width: 167px"
                            @keyup.enter="handleLogin"
                        >
                            <template #prefix>
                                <svg-icon
                                    class="el-input__icon input-icon"
                                    icon-class="validCode"
                                />
                            </template>
                        </el-input>
                        <div class="login-code">
                            <img
                                :src="codeUrl"
                                class="login-code-img"
                                @click="getCode"
                            />
                        </div>
                    </el-form-item>
                    <el-checkbox
                        v-model="loginForm.rememberMe"
                        style="margin: 0 0 25px 0; color: #2d5eff"
                        >记住密码</el-checkbox
                    >
                    <div></div>
                    <el-form-item style="width: 100%">
                        <el-button
                            :loading="loading"
                            size="large"
                            style="width: 100%"
                            type="primary"
                            @click.prevent="handleLogin"
                        >
                            <span v-if="!loading">登 录</span>
                            <span v-else>登 录 中...</span>
                        </el-button>
                        <div v-if="register" style="float: right">
                            <router-link :to="'/register'" class="link-type"
                                >立即注册</router-link
                            >
                        </div>
                    </el-form-item>
                </el-form>
                <div class="other">
                    <div class="other-title">
                        <span style="color: #b3b8c1">其他登录方式:</span
                        ><span style="color: #2d5eff; cursor: pointer">
                            <svg-icon icon-class="phone" />手机验证码登录
                        </span>
                    </div>
                    <div class="forget">忘记密码?</div>
                </div>
            </div>
          </el-form-item>
          <el-checkbox
            v-model="loginForm.rememberMe"
            style="margin: 0 0 25px 0; color: #2d5eff"
            >记住密码</el-checkbox
          >
          <div></div>
          <el-form-item style="width: 100%">
            <el-button
              :loading="loading"
              size="large"
              style="width: 100%"
              type="primary"
              @click.prevent="handleLogin"
            >
              <span v-if="!loading">登 录</span>
              <span v-else>登 录 中...</span>
            </el-button>
            <div v-if="register" style="float: right">
              <router-link :to="'/register'" class="link-type"
                >立即注册</router-link
              >
            </div>
          </el-form-item>
        </el-form>
        <div class="other">
          <div class="other-title">
            <span style="color: #b3b8c1">其他登录方式:</span
            ><span style="color: #2d5eff; cursor: pointer">
              <SvgIcon icon-class="phone" />手机验证码登录
            </span>
          </div>
          <div class="forget">忘记密码?</div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from "@/utils/jsencrypt";
import SvgIcon from '@/components/SvgIcon/index.vue';
export default {
  name: "Login",
  data() {
    return {
      currentTab: "corporation",
      codeUrl: "",
      loginForm: {
        username: "admin",
        password: "admin123",
        rememberMe: false,
        code: "",
        uuid: "",
      },
      loginRules: {
        username: [
          { required: true, trigger: "blur", message: "请输入您的账号" },
        ],
        password: [
          { required: true, trigger: "blur", message: "请输入您的密码" },
        ],
        code: [{ required: true, trigger: "change", message: "请输入验证码" }],
      },
      loading: false,
      // 验证码开关
      captchaEnabled: true,
      // 注册开关
      register: false,
      redirect: undefined,
    };
  },
  watch: {
    $route: {
      handler: function (route) {
        this.redirect = route.query && route.query.redirect;
      },
      immediate: true,
    name: "Login",
    data() {
        return {
            currentTab: "corporation",
            codeUrl: "",
            loginForm: {
                username: "admin",
                password: "admin123",
                rememberMe: false,
                code: "",
                uuid: "",
            },
            loginRules: {
                username: [
                    { required: true, trigger: "blur", message: "请输入您的账号" },
                ],
                password: [
                    { required: true, trigger: "blur", message: "请输入您的密码" },
                ],
                code: [{ required: true, trigger: "change", message: "请输入验证码" }],
            },
            loading: false,
            // 验证码开关
            captchaEnabled: true,
            // 注册开关
            register: false,
            redirect: undefined,
        };
    },
  },
  created() {
    this.getCode();
    this.getCookie();
  },
  methods: {
    getCode() {
      getCodeImg().then((res) => {
        this.captchaEnabled =
          res.captchaEnabled === undefined ? true : res.captchaEnabled;
        if (this.captchaEnabled) {
          this.codeUrl = "data:image/gif;base64," + res.img;
          this.loginForm.uuid = res.uuid;
        }
      });
    comments: {
        SvgIcon
    },
    getCookie() {
      const username = Cookies.get("username");
      const password = Cookies.get("password");
      const rememberMe = Cookies.get("rememberMe");
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        password:
          password === undefined ? this.loginForm.password : decrypt(password),
        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
      };
    watch: {
        $route: {
            handler: function (route) {
                this.redirect = route.query && route.query.redirect;
            },
            immediate: true,
        },
    },
    // 法人登录
    currentClick(tab) {
      this.currentTab = tab;
      this.loginForm.role = "2";
      console.log(this.loginForm);
    created() {
        this.getCode();
        this.getCookie();
    },
    // 监管部门登录
    superviseClick(tab) {
      this.currentTab = tab;
      this.loginForm.role = "1";
      console.log(this.loginForm);
    },
    handleLogin() {
      debugger
      this.$refs.loginRef.validate((valid) => {
        if (valid) {
          this.loading = true;
          if (this.loginForm.rememberMe) {
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            Cookies.set("password", encrypt(this.loginForm.password), {
              expires: 30,
    methods: {
        getCode() {
            getCodeImg().then((res) => {
                this.captchaEnabled =
                    res.captchaEnabled === undefined ? true : res.captchaEnabled;
                if (this.captchaEnabled) {
                    this.codeUrl = "data:image/gif;base64," + res.img;
                    this.loginForm.uuid = res.uuid;
                }
            });
            Cookies.set("rememberMe", this.loginForm.rememberMe, {
              expires: 30,
        },
        getCookie() {
            const username = Cookies.get("username");
            const password = Cookies.get("password");
            const rememberMe = Cookies.get("rememberMe");
            this.loginForm = {
                username: username === undefined ? this.loginForm.username : username,
                password:
                    password === undefined ? this.loginForm.password : decrypt(password),
                rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
            };
        },
        // 法人登录
        currentClick(tab) {
            this.currentTab = tab;
            this.loginForm.role = "2";
            console.log(this.loginForm);
        },
        // 监管部门登录
        superviseClick(tab) {
            this.currentTab = tab;
            this.loginForm.role = "1";
            console.log(this.loginForm);
        },
        handleLogin() {
            this.$refs.loginRef.validate((valid) => {
                if (valid) {
                    this.loading = true;
                    if (this.loginForm.rememberMe) {
                        Cookies.set("username", this.loginForm.username, { expires: 30 });
                        Cookies.set("password", encrypt(this.loginForm.password), {
                            expires: 30,
                        });
                        Cookies.set("rememberMe", this.loginForm.rememberMe, {
                            expires: 30,
                        });
                    } else {
                        Cookies.remove("username");
                        Cookies.remove("password");
                        Cookies.remove("rememberMe");
                    }
                    this.$store
                        .dispatch("Login", this.loginForm)
                        .then(() => {
                            this.$router.push({ path: this.redirect || "/" }).catch(() => { });
                        })
                        .catch(() => {
                            this.loading = false;
                            if (this.captchaEnabled) {
                                this.getCode();
                            }
                        });
                }
            });
          } else {
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove("rememberMe");
          }
          this.$store
            .dispatch("Login", this.loginForm)
            .then(() => {
              this.$router.push({ path: this.redirect || "/" }).catch(() => {});
            })
            .catch(() => {
              this.loading = false;
              if (this.captchaEnabled) {
                this.getCode();
              }
            });
        }
      });
        },
    },
  },
};
</script>
<style lang="scss" scoped>
.login {
  .nav {
    display: flex;
    align-items: center;
    height: 40px;
    margin-left: 24px;
    background-color: #fff;
    font-family: PangMenZhengDao;
    font-size: 20px;
    font-style: italic;
    color: #1e2538;
    line-height: 40px;
  }
  .conter {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 100px;
    height: calc(100vh - 50px);
    background-image: url("../assets/images/login-background.png");
    background-size: cover;
    .left {
      width: 500px;
      height: auto;
      .title {
        font-size: 34px;
        color: #1f2639;
        line-height: 16px;
      }
      .img {
        background-image: url("../assets/images/login.png");
        background-size: cover;
        height: 295px;
        width: 366px;
        margin: auto;
        margin-top: 20px;
      }
    .nav {
        display: flex;
        align-items: center;
        height: 40px;
        margin-left: 24px;
        background-color: #fff;
        font-family: PangMenZhengDao;
        font-size: 20px;
        font-style: italic;
        color: #1e2538;
        line-height: 40px;
    }
    .right {
      border-radius: 6px;
      background: #ffffff;
      width: 500px;
      padding: 0px 25px 5px 25px;
      .tab {
        margin: 20px 0;
    .conter {
        display: flex;
        font-size: 24px;
        line-height: 24px;
        justify-content: center;
        align-items: center;
        gap: 100px;
        height: calc(100vh - 50px);
        background-image: url('../assets/images/login-background.png');
        background-size: cover;
        .login-corporation {
          cursor: pointer;
        .left {
            width: 500px;
            height: auto;
            .title {
                font-size: 34px;
                color: #1f2639;
                line-height: 16px;
            }
            .img {
                background-image: url('../assets/images/login.png');
                background-size: cover;
                height: 295px;
                width: 366px;
                margin: auto;
                margin-top: 20px;
            }
        }
        .login-supervise {
          cursor: pointer;
        }
        .right {
            border-radius: 6px;
            background: #ffffff;
            width: 500px;
            padding: 0px 25px 5px 25px;
        .active {
          color: #2d5eff;
        }
      }
            .tab {
                margin: 20px 0;
                display: flex;
                font-size: 24px;
                line-height: 24px;
                gap: 100px;
      .other {
        display: flex;
        justify-content: space-between;
        font-size: 14px;
        margin-bottom: 10px;
                .login-corporation {
                    cursor: pointer;
                }
        .other-title {
          display: flex;
          gap: 10px;
        }
                .login-supervise {
                    cursor: pointer;
                }
        .forget {
          color: #2d5eff;
          cursor: pointer;
                .active {
                    color: #2d5eff;
                }
            }
            .other {
                display: flex;
                justify-content: space-between;
                font-size: 14px;
                margin-bottom: 10px;
                .other-title {
                    display: flex;
                    gap: 10px;
                }
                .forget {
                    color: #2d5eff;
                    cursor: pointer;
                }
            }
        }
      }
    }
  }
}
.title {
  margin: 0px auto 30px auto;
  text-align: center;
  color: #707070;
    margin: 0px auto 30px auto;
    text-align: center;
    color: #707070;
}
.login-form {
  .el-input {
    height: 40px;
    .el-input {
        height: 40px;
    input {
      height: 40px;
        input {
            height: 40px;
        }
    }
  }
  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 0px;
  }
    .input-icon {
        height: 39px;
        width: 14px;
        margin-left: 0px;
    }
}
.login-tip {
  font-size: 13px;
  text-align: center;
  color: #bfbfbf;
    font-size: 13px;
    text-align: center;
    color: #bfbfbf;
}
.login-code {
  width: 33%;
  height: 40px;
  float: right;
    width: 33%;
    height: 40px;
    float: right;
  img {
    cursor: pointer;
    vertical-align: middle;
  }
    img {
        cursor: pointer;
        vertical-align: middle;
    }
}
.login-code-img {
  height: 40px;
  padding-left: 12px;
    height: 40px;
    padding-left: 12px;
}
</style>