gaoluyang
2 天以前 31bc616ad716daf2ba0ec7fa38352644b6293dfc
1.整体样式修改
已修改15个文件
2031 ■■■■■ 文件已修改
src/assets/styles/element-ui.scss 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/index.scss 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/sidebar.scss 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/variables.module.scss 446 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Breadcrumb/index.vue 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PageHeader/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/AppMain.vue 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Logo.vue 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/ScrollPane.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/index.vue 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/index.vue 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 633 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-ui.scss
@@ -47,50 +47,57 @@
}
// to fixed https://github.com/ElemeFE/element/issues/2461
.el-dialog {
  transform: none;
  left: 0;
  position: relative;
  margin: 0 auto;
  border-radius: 8px;
  padding: 0 !important;
}
.el-dialog__header {
  background: #f5f6f7;
  padding: 12px 16px;
  border-radius: 8px 8px 0 0;
}
.el-dialog__title {
  font-weight: 400;
  font-size: 16px;
  color: #2e3033;
}
.el-dialog__body {
  padding: 16px 40px 0 40px;
  max-height: 74vh;
  overflow-y: auto;
}
.el-dialog__footer {
  text-align: center;
  padding: 16px;
}
.el-message-box {
  padding: 0 !important;
  border-radius: 8px;
}
.el-message-box__header {
  background: #f5f6f7;
  padding: 12px 16px;
  border-radius: 8px 8px 0 0;
}
.el-message-box__title {
  font-weight: 400;
  font-size: 16px;
  color: #2e3033;
}
.el-message-box__content {
  padding: 16px 40px 0 40px;
}
.el-dialog {
  transform: none;
  left: 0;
  position: relative;
  margin: 0 auto;
  border-radius: 24px;
  padding: 0 !important;
  border: 1px solid var(--surface-border);
  box-shadow: var(--shadow-md);
  background: rgba(255, 255, 255, 0.96);
}
.el-dialog__header {
  background: linear-gradient(180deg, rgba(247, 250, 248, 0.98), rgba(242, 247, 244, 0.88));
  padding: 18px 24px 14px;
  border-bottom: 1px solid var(--surface-border);
  border-radius: 24px 24px 0 0;
}
.el-dialog__title {
  font-weight: 600;
  font-size: 17px;
  color: var(--text-primary);
}
.el-dialog__body {
  padding: 24px 24px 0;
  max-height: 74vh;
  overflow-y: auto;
}
.el-dialog__footer {
  text-align: center;
  padding: 18px 24px 24px;
}
.el-message-box {
  padding: 0 !important;
  border-radius: 22px;
  border: 1px solid var(--surface-border);
  box-shadow: var(--shadow-md);
}
.el-message-box__header {
  background: linear-gradient(180deg, rgba(247, 250, 248, 0.98), rgba(242, 247, 244, 0.88));
  padding: 18px 24px 14px;
  border-bottom: 1px solid var(--surface-border);
  border-radius: 22px 22px 0 0;
}
.el-message-box__title {
  font-weight: 600;
  font-size: 17px;
  color: var(--text-primary);
}
.el-message-box__content {
  padding: 24px 24px 0;
}
.el-message-box__container {
  justify-content: center;
}
@@ -105,12 +112,12 @@
    margin-right: 12px;
  }
}
.el-table__expanded-cell {
  padding: 0 !important;
  .el-table__header-wrapper {
    background-color: #f5f8ff !important;
  }
}
.el-table__expanded-cell {
  padding: 0 !important;
  .el-table__header-wrapper {
    background-color: var(--surface-soft) !important;
  }
}
// refine element ui upload
.upload-container {
@@ -149,6 +156,62 @@
  display: none;
}
.el-dropdown .el-dropdown-link {
  color: var(--el-color-primary) !important;
}
.el-dropdown .el-dropdown-link {
  color: var(--el-color-primary) !important;
}
.el-button {
  border-radius: 12px;
  font-weight: 600;
}
.el-button--primary {
  --el-button-bg-color: var(--el-color-primary);
  --el-button-border-color: var(--el-color-primary);
  --el-button-hover-bg-color: var(--el-color-primary-light-3);
  --el-button-hover-border-color: var(--el-color-primary-light-3);
  --el-button-active-bg-color: var(--el-color-primary-dark-2);
  --el-button-active-border-color: var(--el-color-primary-dark-2);
  box-shadow: 0 10px 24px color-mix(in srgb, var(--el-color-primary) 18%, transparent);
}
.el-input__wrapper,
.el-textarea__inner,
.el-select__wrapper,
.el-date-editor.el-input__wrapper,
.el-date-editor .el-input__wrapper {
  border-radius: 12px;
  box-shadow: 0 0 0 1px rgba(216, 225, 219, 0.92) inset !important;
  background: rgba(255, 255, 255, 0.9);
}
.el-input__wrapper.is-focus,
.el-select__wrapper.is-focused,
.el-textarea__inner:focus {
  box-shadow: 0 0 0 1px rgba(0, 47, 167, 0.28) inset !important;
}
.el-card {
  border: 1px solid var(--surface-border);
  box-shadow: var(--shadow-sm);
  background: rgba(255, 255, 255, 0.88);
}
.el-table {
  --el-table-border-color: var(--surface-border);
  --el-table-header-bg-color: var(--surface-soft);
  --el-table-row-hover-bg-color: rgba(0, 47, 167, 0.04);
  --el-table-current-row-bg-color: rgba(0, 47, 167, 0.08);
  border-radius: 18px;
  overflow: hidden;
}
.el-table th.el-table__cell {
  background: var(--surface-soft) !important;
  color: var(--text-secondary);
  font-weight: 600;
}
.el-pagination {
  margin-top: 18px;
}
src/assets/styles/index.scss
@@ -6,27 +6,38 @@
@import './btn.scss';
@import './ruoyi.scss';
body {
  height: 100%;
  margin: 0;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}
body {
  height: 100%;
  margin: 0;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
  background:
    radial-gradient(circle at top left, rgba(214, 226, 219, 0.8), transparent 28%),
    linear-gradient(180deg, #f7faf8 0%, var(--app-bg) 100%);
  color: var(--text-primary);
}
label {
  font-weight: 700;
}
label {
  font-weight: 600;
  color: var(--text-secondary);
}
html {
  height: 100%;
  box-sizing: border-box;
}
#app {
  height: 100%;
}
#app {
  height: 100%;
}
html,
body,
#app {
  background-color: var(--app-bg);
}
*,
*:before,
@@ -122,25 +133,29 @@
}
//main-container全局样式
.app-container {
  padding: 20px;
}
.search_form {
  display: flex;
  align-items: center;
  justify-content: space-between;
  .search_title {
    font-size: 14px;
    font-weight: 700;
    color: #333333;
  }
}
.table_list {
  height: calc(100vh - 11em);
  margin-top: 20px;
  background: #fff;
  padding: 18px
}
.app-container {
  padding: 20px 24px 24px;
}
.search_form {
  display: flex;
  align-items: center;
  justify-content: space-between;
  .search_title {
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
  }
}
.table_list {
  height: calc(100vh - 11em);
  margin-top: 20px;
  background: rgba(255, 255, 255, 0.88);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  padding: 18px;
}
.components-container {
  margin: 30px 50px;
  position: relative;
@@ -174,22 +189,36 @@
  }
}
.link-type,
.link-type:focus {
  color: #337ab7;
  cursor: pointer;
.link-type,
.link-type:focus {
  color: var(--el-color-primary);
  cursor: pointer;
  &:hover {
    color: #165e57;
  }
}
  &:hover {
    color: rgb(32, 160, 255);
  }
}
.filter-container {
  padding-bottom: 10px;
.filter-container {
  padding-bottom: 10px;
  .filter-item {
    display: inline-block;
    vertical-align: middle;
    margin-bottom: 10px;
  }
}
  }
}
.app-container,
.table_list,
.components-container {
  .el-card,
  .el-dialog,
  .el-drawer,
  .el-table,
  .el-descriptions,
  .el-collapse-item__wrap,
  .el-tabs__content {
    border-radius: var(--radius-md);
  }
}
src/assets/styles/sidebar.scss
@@ -4,7 +4,7 @@
    transition: margin-left 0.28s;
    margin-left: $base-sidebar-width;
    position: relative;
    background: #f5f7fb;
    background: transparent;
  }
  .sidebarHide {
@@ -22,8 +22,9 @@
    left: 0;
    z-index: 1001;
    overflow: hidden;
    -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
    box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
    padding: 12px 0 16px 16px;
    background: transparent;
    box-shadow: none;
    // reset element-ui css
    .horizontal-collapse-transition {
@@ -45,7 +46,8 @@
    &.has-logo {
      .el-scrollbar {
        height: calc(100% - 50px);
        height: calc(100% - 72px);
        margin-top: 10px;
      }
    }
@@ -67,6 +69,11 @@
      border: none;
      height: 100%;
      width: 100% !important;
      padding: 10px 8px 18px;
      border-radius: 0 28px 28px 0;
      background: var(--menu-surface);
      backdrop-filter: blur(18px);
      box-shadow: var(--shadow-sm);
    }
    .el-menu-item,
@@ -84,11 +91,11 @@
    .sub-menu-title-noDropdown,
    .el-sub-menu__title {
      &:hover {
        background-color: rgba(212, 221, 255, 0.8) !important;
        background-color: var(--menu-hover) !important;
      }
    }
    & .theme-light .is-active > .el-sub-menu__title {
      color: #fff !important;
      color: var(--current-color) !important;
    }
    & .nest-menu .el-sub-menu > .el-sub-menu__title,
@@ -96,10 +103,10 @@
      min-width: $base-sidebar-width !important;
      &:hover {
        background-color: rgba(212, 221, 255, 0.8) !important;
        background-color: var(--menu-hover) !important;
      }
      &.is-active {
        background-color: #fff !important;
        background-color: var(--menu-active-bg) !important;
      }
    }
@@ -108,7 +115,7 @@
      //background-color: transparent;
      &:hover {
        background-color: rgba(212, 221, 255, 0.8) !important;
        background-color: var(--menu-hover) !important;
      }
    }
  }
@@ -212,10 +219,10 @@
  .el-menu-item {
    &:hover {
      // you can use $sub-menuHover
      background-color: rgba(212, 221, 255, 0.56) !important;
      background-color: var(--menu-hover) !important;
    }
    &.is-active {
      background-color: rgba(212, 221, 255, 0.56) !important;
      background-color: var(--menu-active-bg) !important;
    }
  }
@@ -223,9 +230,13 @@
  > .el-menu--popup {
    max-height: 100vh;
    overflow-y: auto;
    padding: 8px;
    border-radius: 18px;
    border: 1px solid var(--surface-border);
    box-shadow: var(--shadow-md);
    &::-webkit-scrollbar-track-piece {
      background: #d3dce6;
      background: #dfe7e1;
    }
    &::-webkit-scrollbar {
@@ -233,7 +244,7 @@
    }
    &::-webkit-scrollbar-thumb {
      background: #99a9bf;
      background: #9aa79e;
      border-radius: 20px;
    }
  }
src/assets/styles/variables.module.scss
@@ -1,221 +1,225 @@
// base color
$blue: #324157;
$light-blue: #333c46;
$red: #c03639;
$pink: #e65d6e;
$green: #30b08f;
$tiffany: #4ab7bd;
$yellow: #fec171;
$panGreen: #30b08f;
// 默认主题变量
$menuText: #bfcbd9;
$menuActiveText: #409eff;
$menuBg: #304156;
$menuHover: #263445;
// 浅色主题theme-light
$menuLightBg: #002fa7;
$menuLightHover: #f0f1f5;
$menuLightText: #fff;
$menuLightActiveText: #002fa7;
// 基础变量
$base-sidebar-width: 200px;
$sideBarWidth: 200px;
// 菜单暗色变量
$base-menu-color: #bfcbd9;
$base-menu-color-active: #f4f4f5;
$base-menu-background: #304156;
$base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover: #fff;
// 组件变量
$--color-primary: #409eff;
$--color-success: #67c23a;
$--color-warning: #e6a23c;
$--color-danger: #f56c6c;
$--color-info: #909399;
:export {
  menuText: $menuText;
  menuActiveText: $menuActiveText;
  menuBg: $menuBg;
  menuHover: $menuHover;
  menuLightBg: $menuLightBg;
  menuLightHover: $menuLightHover;
  menuLightText: $menuLightText;
  menuLightActiveText: $menuLightActiveText;
  sideBarWidth: $sideBarWidth;
  // 导出基础颜色
  blue: $blue;
  lightBlue: $light-blue;
  red: $red;
  pink: $pink;
  green: $green;
  tiffany: $tiffany;
  yellow: $yellow;
  panGreen: $panGreen;
  // 导出组件颜色
  colorPrimary: $--color-primary;
  colorSuccess: $--color-success;
  colorWarning: $--color-warning;
  colorDanger: $--color-danger;
  colorInfo: $--color-info;
}
// CSS变量定义
:root {
  /* 亮色模式变量 */
  --sidebar-bg: #{$menuBg};
  --sidebar-text: #{$menuText};
  --menu-hover: #{$menuHover};
  --navbar-bg: #ffffff;
  --navbar-text: #303133;
  /* splitpanes default-theme 变量 */
  --splitpanes-default-bg: #ffffff;
}
// 暗黑模式变量
html.dark {
  /* 默认通用 */
  --el-bg-color: #141414;
  --el-bg-color-overlay: #1d1e1f;
  --el-text-color-primary: #ffffff;
  --el-text-color-regular: #d0d0d0;
  --el-border-color: #434343;
  --el-border-color-light: #434343;
  /* 侧边栏 */
  --sidebar-bg: #141414;
  --sidebar-text: #ffffff;
  --menu-hover: #2d2d2d;
  --menu-active-text: #{$menuActiveText};
  /* 顶部导航栏 */
  --navbar-bg: #141414;
  --navbar-text: #ffffff;
  --navbar-hover: #141414;
  /* 标签栏 */
  --tags-bg: #141414;
  --tags-item-bg: #1d1e1f;
  --tags-item-border: #303030;
  --tags-item-text: #d0d0d0;
  --tags-item-hover: #2d2d2d;
  --tags-close-hover: #64666a;
  /* splitpanes 组件暗黑模式变量 */
  --splitpanes-bg: #141414;
  --splitpanes-border: #303030;
  --splitpanes-splitter-bg: #1d1e1f;
  --splitpanes-splitter-hover-bg: #2d2d2d;
  /* blockquote 暗黑模式变量 */
  --blockquote-bg: #1d1e1f;
  --blockquote-border: #303030;
  --blockquote-text: #d0d0d0;
  /* Cron 时间表达式 模式变量 */
  --cron-border: #303030;
  /* splitpanes default-theme 暗黑模式变量 */
  --splitpanes-default-bg: #141414;
  /* 侧边栏菜单覆盖 */
  .sidebar-container {
    .el-menu-item,
    .menu-title {
      color: var(--el-text-color-regular);
    }
    & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
    & .theme-dark .el-sub-menu .el-menu-item {
      background-color: var(--el-bg-color) !important;
    }
  }
  /* 顶部栏栏菜单覆盖 */
  .el-menu--horizontal {
    .el-menu-item {
      &:not(.is-disabled) {
        &:hover,
        &:focus {
          background-color: var(--navbar-hover) !important;
        }
      }
    }
  }
  /* 分割窗格覆盖 */
  .splitpanes {
    background-color: var(--splitpanes-bg);
    .splitpanes__pane {
      background-color: var(--splitpanes-bg);
      border-color: var(--splitpanes-border);
    }
    .splitpanes__splitter {
      background-color: var(--splitpanes-splitter-bg);
      border-color: var(--splitpanes-border);
      &:hover {
        background-color: var(--splitpanes-splitter-hover-bg);
      }
      &:before,
      &:after {
        background-color: var(--splitpanes-border);
      }
    }
  }
  /* 表格样式覆盖 */
  .el-table {
    --el-table-header-bg-color: var(--el-bg-color-overlay) !important;
    --el-table-header-text-color: var(--el-text-color-regular) !important;
    --el-table-border-color: var(--el-border-color-light) !important;
    --el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important;
    .el-table__header-wrapper,
    .el-table__fixed-header-wrapper {
      th {
        background-color: var(--el-bg-color-overlay, #f0f1f5) !important;
        color: var(--el-text-color-regular, #515a6e);
      }
    }
  }
  /* 树组件高亮样式覆盖 */
  .el-tree {
    .el-tree-node.is-current > .el-tree-node__content {
      background-color: var(--el-bg-color-overlay) !important;
      color: var(--el-color-primary);
    }
    .el-tree-node__content:hover {
      background-color: var(--el-bg-color-overlay);
    }
  }
  /* 下拉菜单样式覆盖 */
  .el-dropdown-menu__item:not(.is-disabled):focus,
  .el-dropdown-menu__item:not(.is-disabled):hover {
    background-color: var(--navbar-hover) !important;
  }
  /* blockquote样式覆盖 */
  blockquote {
    background-color: var(--blockquote-bg) !important;
    border-left-color: var(--blockquote-border) !important;
    color: var(--blockquote-text) !important;
  }
  /* 时间表达式标题样式覆盖 */
  .popup-result .title {
    background: var(--cron-border);
  }
}
// base color
$blue: #324157;
$light-blue: #333c46;
$red: #c03639;
$pink: #e65d6e;
$green: #30b08f;
$tiffany: #4ab7bd;
$yellow: #fec171;
$panGreen: #30b08f;
// menu palette
$menuText: #677287;
$menuActiveText: #1f7a72;
$menuBg: #f4f7f4;
$menuHover: #e7eeea;
// light theme
$menuLightBg: #f4f7f4;
$menuLightHover: #e7eeea;
$menuLightText: #3b4658;
$menuLightActiveText: #1f7a72;
// layout
$base-sidebar-width: 200px;
$sideBarWidth: 200px;
// sidebar
$base-menu-color: #677287;
$base-menu-color-active: #1f7a72;
$base-menu-background: #f4f7f4;
$base-sub-menu-background: #eef3ef;
$base-sub-menu-hover: #ffffff;
// component
$--color-primary: #1f7a72;
$--color-success: #67c23a;
$--color-warning: #d89b41;
$--color-danger: #d25b52;
$--color-info: #7d8797;
:export {
  menuText: $menuText;
  menuActiveText: $menuActiveText;
  menuBg: $menuBg;
  menuHover: $menuHover;
  menuLightBg: $menuLightBg;
  menuLightHover: $menuLightHover;
  menuLightText: $menuLightText;
  menuLightActiveText: $menuLightActiveText;
  sideBarWidth: $sideBarWidth;
  blue: $blue;
  lightBlue: $light-blue;
  red: $red;
  pink: $pink;
  green: $green;
  tiffany: $tiffany;
  yellow: $yellow;
  panGreen: $panGreen;
  colorPrimary: $--color-primary;
  colorSuccess: $--color-success;
  colorWarning: $--color-warning;
  colorDanger: $--color-danger;
  colorInfo: $--color-info;
}
:root {
  --sidebar-bg: #{$menuBg};
  --sidebar-text: #{$menuText};
  --sidebar-muted: #93a0b1;
  --menu-hover: #{$menuHover};
  --menu-active-bg: #dfe9e4;
  --menu-surface: rgba(255, 255, 255, 0.72);
  --app-bg: #eef2ee;
  --app-bg-accent: #dfe8e2;
  --surface-base: #ffffff;
  --surface-soft: #f7faf8;
  --surface-muted: #eff4f1;
  --surface-border: #d8e1db;
  --surface-border-strong: #c9d5ce;
  --text-primary: #21313f;
  --text-secondary: #5f6d7e;
  --text-tertiary: #8a98a8;
  --shadow-sm: 0 10px 30px rgba(31, 49, 38, 0.06);
  --shadow-md: 0 18px 50px rgba(31, 49, 38, 0.1);
  --radius-lg: 24px;
  --radius-md: 18px;
  --radius-sm: 12px;
  --navbar-bg: rgba(255, 255, 255, 0.78);
  --navbar-text: #21313f;
  --navbar-hover: rgba(31, 122, 114, 0.08);
  --tags-bg: transparent;
  --tags-item-bg: rgba(255, 255, 255, 0.74);
  --tags-item-border: rgba(201, 213, 206, 0.88);
  --tags-item-text: #5f6d7e;
  --tags-item-hover: rgba(31, 122, 114, 0.08);
  --tags-close-hover: rgba(31, 122, 114, 0.18);
  --splitpanes-default-bg: #ffffff;
}
html.dark {
  --el-bg-color: #141414;
  --el-bg-color-overlay: #1d1e1f;
  --el-text-color-primary: #ffffff;
  --el-text-color-regular: #d0d0d0;
  --el-border-color: #434343;
  --el-border-color-light: #434343;
  --sidebar-bg: #141414;
  --sidebar-text: #ffffff;
  --menu-hover: #2d2d2d;
  --menu-active-text: #{$menuActiveText};
  --navbar-bg: #141414;
  --navbar-text: #ffffff;
  --navbar-hover: #141414;
  --tags-bg: #141414;
  --tags-item-bg: #1d1e1f;
  --tags-item-border: #303030;
  --tags-item-text: #d0d0d0;
  --tags-item-hover: #2d2d2d;
  --tags-close-hover: #64666a;
  --splitpanes-bg: #141414;
  --splitpanes-border: #303030;
  --splitpanes-splitter-bg: #1d1e1f;
  --splitpanes-splitter-hover-bg: #2d2d2d;
  --blockquote-bg: #1d1e1f;
  --blockquote-border: #303030;
  --blockquote-text: #d0d0d0;
  --cron-border: #303030;
  --splitpanes-default-bg: #141414;
  .sidebar-container {
    .el-menu-item,
    .menu-title {
      color: var(--el-text-color-regular);
    }
    & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
    & .theme-dark .el-sub-menu .el-menu-item {
      background-color: var(--el-bg-color) !important;
    }
  }
  .el-menu--horizontal {
    .el-menu-item {
      &:not(.is-disabled) {
        &:hover,
        &:focus {
          background-color: var(--navbar-hover) !important;
        }
      }
    }
  }
  .splitpanes {
    background-color: var(--splitpanes-bg);
    .splitpanes__pane {
      background-color: var(--splitpanes-bg);
      border-color: var(--splitpanes-border);
    }
    .splitpanes__splitter {
      background-color: var(--splitpanes-splitter-bg);
      border-color: var(--splitpanes-border);
      &:hover {
        background-color: var(--splitpanes-splitter-hover-bg);
      }
      &:before,
      &:after {
        background-color: var(--splitpanes-border);
      }
    }
  }
  .el-table {
    --el-table-header-bg-color: var(--el-bg-color-overlay) !important;
    --el-table-header-text-color: var(--el-text-color-regular) !important;
    --el-table-border-color: var(--el-border-color-light) !important;
    --el-table-row-hover-bg-color: var(--el-bg-color-overlay) !important;
    .el-table__header-wrapper,
    .el-table__fixed-header-wrapper {
      th {
        background-color: var(--el-bg-color-overlay, #f0f1f5) !important;
        color: var(--el-text-color-regular, #515a6e);
      }
    }
  }
  .el-tree {
    .el-tree-node.is-current > .el-tree-node__content {
      background-color: var(--el-bg-color-overlay) !important;
      color: var(--el-color-primary);
    }
    .el-tree-node__content:hover {
      background-color: var(--el-bg-color-overlay);
    }
  }
  .el-dropdown-menu__item:not(.is-disabled):focus,
  .el-dropdown-menu__item:not(.is-disabled):hover {
    background-color: var(--navbar-hover) !important;
  }
  blockquote {
    background-color: var(--blockquote-bg) !important;
    border-left-color: var(--blockquote-border) !important;
    color: var(--blockquote-text) !important;
  }
  .popup-result .title {
    background: var(--cron-border);
  }
}
src/components/Breadcrumb/index.vue
@@ -85,15 +85,34 @@
</script>
<style lang='scss' scoped>
.app-breadcrumb.el-breadcrumb {
  display: inline-block;
  font-size: 14px;
  line-height: 50px;
  margin-left: 8px;
  .no-redirect {
    color: #002FA7;
    cursor: text;
  }
}
</style>
.app-breadcrumb.el-breadcrumb {
  display: inline-block;
  font-size: 14px;
  line-height: 56px;
  margin-left: 8px;
  :deep(.el-breadcrumb__inner) {
    color: var(--text-secondary);
    font-weight: 500;
    transition: color 0.2s ease;
  }
  :deep(.el-breadcrumb__separator) {
    color: var(--text-tertiary);
  }
  a {
    color: var(--text-secondary);
    &:hover {
      color: var(--current-color);
    }
  }
  .no-redirect {
    color: var(--current-color);
    font-weight: 600;
    cursor: text;
  }
}
</style>
src/components/PIMTable/PIMTable.vue
@@ -4,7 +4,7 @@
    v-loading="tableLoading"
    :border="border"
    :data="tableData"
    :header-cell-style="{ background: '#F0F1F5', color: '#333333' }"
    :header-cell-style="mergedHeaderCellStyle"
    :height="height"
    :highlight-current-row="highlightCurrentRow"
    :row-class-name="rowClassName"
@@ -226,7 +226,7 @@
<script setup>
import pagination from "./Pagination.vue";
import { ref, inject, getCurrentInstance } from "vue";
import { computed, ref, inject, getCurrentInstance } from "vue";
import { ElMessage } from "element-plus";
// 获取全局的 uploadHeader
@@ -333,6 +333,13 @@
    default: () => ({ width: "100%" }),
  },
});
const mergedHeaderCellStyle = computed(() => ({
  background: "var(--surface-soft)",
  color: "var(--text-secondary)",
  fontWeight: 600,
  ...props.headerCellStyle,
}));
// Data
const uploadRefs = ref([]);
@@ -507,6 +514,12 @@
</script>
<style scoped lang="scss">
.lims-table {
  border: 1px solid var(--surface-border);
  border-radius: 18px;
  background: rgba(255, 255, 255, 0.9);
}
.cell {
  white-space: nowrap;
  overflow: hidden;
@@ -519,4 +532,15 @@
.pim-table-header-extra :deep(.el-select) {
  width: 100%;
}
.pim-table-header-cell {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.pim-table-header-title {
  font-weight: 600;
}
</style>
src/components/PageHeader/index.vue
@@ -43,6 +43,11 @@
<style scoped>
.page-header-wrapper {
  margin-bottom: 16px;
  padding: 16px 18px;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.82);
  box-shadow: var(--shadow-sm);
}
.page-header-wrapper :deep(.el-page-header__extra) {
@@ -50,4 +55,9 @@
  align-items: center;
  gap: 8px;
}
.page-header-wrapper :deep(.el-page-header__content) {
  font-weight: 600;
  color: var(--text-primary);
}
</style>
src/layout/components/AppMain.vue
@@ -37,23 +37,24 @@
</script>
<style lang="scss" scoped>
.app-main {
.app-main {
  /* 50= navbar  50  */
  min-height: calc(100vh - 50px);
  width: 100%;
  position: relative;
  overflow: hidden;
  background: #F5F7FB;
}
.route-view-wrapper {
  width: 100%;
  height: 100%;
}
.fixed-header + .app-main {
  padding-top: 50px;
}
  background: transparent;
}
.route-view-wrapper {
  width: 100%;
  height: 100%;
  padding: 108px 16px 24px 0;
}
.fixed-header + .app-main {
  padding-top: 0;
}
.hasTagsView {
  .app-main {
@@ -61,10 +62,10 @@
    min-height: calc(100vh - 84px);
  }
  .fixed-header + .app-main {
    padding-top: 84px;
  }
}
  .fixed-header + .app-main {
    padding-top: 0;
  }
}
</style>
<style lang="scss">
@@ -80,13 +81,13 @@
  height: 6px;
}
::-webkit-scrollbar-track {
  background-color: #f1f1f1;
}
::-webkit-scrollbar-thumb {
  background-color: #c0c0c0;
  border-radius: 3px;
}
::-webkit-scrollbar-track {
  background-color: rgba(218, 225, 220, 0.8);
}
::-webkit-scrollbar-thumb {
  background-color: #b2bdb5;
  border-radius: 3px;
}
</style>
src/layout/components/Navbar.vue
@@ -158,15 +158,19 @@
</script>
<style lang='scss' scoped>
.navbar {
  height: 50px;
  overflow: hidden;
  position: relative;
  background: var(--navbar-bg);
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.navbar {
  height: 56px;
  overflow: hidden;
  position: relative;
  background: var(--navbar-bg);
  border: 1px solid rgba(216, 225, 219, 0.9);
  border-radius: 22px;
  backdrop-filter: blur(18px);
  box-shadow: var(--shadow-sm);
  padding: 0 18px;
  .hamburger-container {
    line-height: 46px;
    line-height: 52px;
    height: 100%;
    float: left;
    cursor: pointer;
@@ -174,7 +178,7 @@
    -webkit-tap-highlight-color: transparent;
    &:hover {
      background: rgba(0, 0, 0, 0.025);
      background: var(--navbar-hover);
    }
  }
@@ -192,30 +196,32 @@
    vertical-align: top;
  }
  .right-menu {
    float: right;
    height: 100%;
    line-height: 50px;
    display: flex;
  .right-menu {
    float: right;
    height: 100%;
    align-items: center;
    display: flex;
    &:focus {
      outline: none;
    }
    .right-menu-item {
      display: inline-block;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: var(--navbar-text);
      vertical-align: text-bottom;
    .right-menu-item {
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 0 8px;
      height: 100%;
      font-size: 18px;
      color: var(--navbar-text);
      border-radius: 14px;
      &.hover-effect {
        cursor: pointer;
        transition: background 0.3s;
        &:hover {
          background: rgba(0, 0, 0, 0.025);
          background: var(--navbar-hover);
        }
      }
@@ -234,7 +240,7 @@
    }
    .notification-container {
      margin-right: 20px;
      margin-right: 12px;
      display: flex;
      align-items: center;
      cursor: pointer;
@@ -246,28 +252,43 @@
      }
    }
    .avatar-container {
      margin-right: 40px;
      .avatar-wrapper {
        margin-top: 5px;
        position: relative;
        .user-avatar {
          cursor: pointer;
          width: 40px;
          height: 40px;
          border-radius: 50px;
        }
        i {
          cursor: pointer;
          position: absolute;
          right: -20px;
          top: 14px;
          font-size: 12px;
        }
      }
    .avatar-container {
      margin-right: 4px;
      height: 100%;
      display: flex;
      align-items: center;
      :deep(.el-dropdown) {
        height: 100%;
        display: flex;
        align-items: center;
      }
      .avatar-wrapper {
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 10px;
        padding: 6px 10px 6px 6px;
        height: 44px;
        border-radius: 999px;
        background: rgba(247, 250, 248, 0.92);
        border: 1px solid var(--surface-border);
        .user-avatar {
          cursor: pointer;
          width: 34px;
          height: 34px;
          border-radius: 50px;
        }
        i {
          cursor: pointer;
          position: static;
          font-size: 12px;
        }
      }
    }
  }
}
@@ -275,8 +296,11 @@
</style>
<style lang="scss">
.notification-popover {
  padding: 0 !important;
.notification-popover {
  padding: 0 !important;
  border-radius: 20px !important;
  border: 1px solid var(--surface-border) !important;
  box-shadow: var(--shadow-md) !important;
  
  .el-popover__title {
    display: none;
src/layout/components/Sidebar/Logo.vue
@@ -3,11 +3,11 @@
    <transition name="sidebarLogoFade">
      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
        <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="公司Logo" />
        <h1 class="sidebar-title">{{ title }}</h1>
        <h1 v-if="!logoUrl" class="sidebar-title">{{ title }}</h1>
      </router-link>
      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
        <img v-if="logoUrl" :src="logoUrl" class="sidebar-logo" @error="handleImageError" alt="公司Logo" />
        <h1 class="sidebar-title">{{ title }}</h1>
        <h1 v-if="!logoUrl" class="sidebar-title">{{ title }}</h1>
      </router-link>
    </transition>
  </div>
@@ -87,43 +87,57 @@
  opacity: 0;
}
.sidebar-logo-container {
  position: relative;
  width: 100% !important;
  height: 50px !important;
  line-height: 50px;
  background: #fff;
  text-align: center;
  overflow: hidden;
  & .sidebar-logo-link {
    height: 100%;
    width: 100%;
    & .sidebar-logo {
      width: 100%;
      height: 100%;
      // height: 32px;
      vertical-align: middle;
      margin-right: 12px;
    }
    & .sidebar-title {
      display: inline-block;
      margin: 0;
      color: v-bind(getLogoTextColor);
      font-weight: 600;
      line-height: 50px;
      font-size: 14px;
      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
      vertical-align: middle;
    }
  }
.sidebar-logo-container {
  position: relative;
  width: 100% !important;
  height: 56px !important;
  line-height: 56px;
  background: rgba(255, 255, 255, 0.78);
  border: 1px solid var(--surface-border);
  border-radius: 0 22px 22px 0;
  text-align: center;
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  & .sidebar-logo-link {
    height: 100%;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 18px 0 14px;
    & .sidebar-logo {
      width: auto;
      max-width: 132px;
      max-height: 34px;
      height: auto;
      vertical-align: middle;
      object-fit: contain;
      object-position: center;
    }
    & .sidebar-title {
      display: inline-block;
      margin: 0;
      color: var(--text-primary);
      font-weight: 600;
      line-height: 1.2;
      font-size: 14px;
      font-family: "Segoe UI", "PingFang SC", sans-serif;
      vertical-align: middle;
    }
  }
  &.collapse {
    .sidebar-logo {
      margin-right: 0px;
    }
  }
}
</style>
    .sidebar-logo-link {
      padding: 0;
    }
    .sidebar-logo {
      max-width: 30px;
      max-height: 30px;
    }
  }
}
</style>
src/layout/components/Sidebar/index.vue
@@ -70,25 +70,40 @@
    height: 100%;
    width: 100% !important;
    .el-menu-item,
    .el-sub-menu__title {
      &:hover {
        background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
      }
    }
    .el-menu-item,
    .el-sub-menu__title {
      margin-bottom: 6px;
      border-radius: 14px;
      color: v-bind(getMenuTextColor);
      &:hover {
        background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
      }
    }
    .el-menu-item {
      &.is-active {
        color: v-bind(theme);
        background-color: var(--menu-active-bg, rgba(0, 0, 0, 0.06)) !important;
        font-weight: 600;
      }
    }
    .el-menu-item {
      color: v-bind(getMenuTextColor);
      &.is-active {
        color: var(--menu-active-text, #409eff);
        background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
      }
    }
    .el-sub-menu__title {
      color: v-bind(getMenuTextColor);
    }
  }
}
</style>
    .el-sub-menu__title {
      color: v-bind(getMenuTextColor);
    }
    :deep(.el-sub-menu.is-active > .el-sub-menu__title) {
      color: v-bind(theme) !important;
      font-weight: 600;
    }
    :deep(.el-sub-menu.is-active > .el-sub-menu__title .menu-title),
    :deep(.el-sub-menu.is-active > .el-sub-menu__title .svg-icon),
    :deep(.el-menu-item.is-active .menu-title),
    :deep(.el-menu-item.is-active .svg-icon) {
      color: v-bind(theme) !important;
    }
  }
}
</style>
src/layout/components/TagsView/ScrollPane.vue
@@ -101,7 +101,7 @@
    bottom: 0px;
  }
  :deep(.el-scrollbar__wrap) {
    height: 39px;
    height: 42px;
  }
}
</style>
src/layout/components/TagsView/index.vue
@@ -13,9 +13,9 @@
        @contextmenu.prevent="openMenu(tag, $event)"
      >
        {{ tag.title }}
        <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
          <close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
        </span>
        <span v-if="!isAffix(tag)" class="tags-view-close" @click.prevent.stop="closeSelectedTag(tag)">
          <close class="el-icon-close" />
        </span>
      </router-link>
    </scroll-pane>
    <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
@@ -258,44 +258,54 @@
</script>
<style lang="scss" scoped>
.tags-view-container {
  height: 34px;
  width: 100%;
  background: transparent;
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
      position: relative;
      cursor: pointer;
      height: 32px;
      line-height: 32px;
      //border: 1px solid var(--tags-item-border, #d8dce5);
      color: #4E5463;
      background: #E5E7EA;
      padding: 0 16px;
      font-size: 12px;
      //margin-left: 5px;
      //margin-top: 4px;
      //&:first-of-type {
      //  margin-left: 8px;
      //}
      //
      //&:last-of-type {
      //  margin-right: 15px;
      //}
      &.active {
        background-color: #FFFFFF !important;
        color: #2C51D9;
      }
    }
    //.tags-view-item div {
    //  transform: skew(12deg);
    //  display: inline-block;
    //}
  }
.tags-view-container {
  height: 42px;
  width: 100%;
  margin-top: 10px;
  padding: 4px 10px;
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid rgba(216, 225, 219, 0.92);
  border-radius: 20px;
  backdrop-filter: blur(18px);
  box-shadow: var(--shadow-sm);
  .tags-view-wrapper {
    display: flex;
    align-items: center;
    min-height: 42px;
    .tags-view-item {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      position: relative;
      cursor: pointer;
      height: 34px;
      line-height: 1;
      color: var(--tags-item-text, #4E5463);
      background: var(--tags-item-bg, #E5E7EA);
      border: 1px solid var(--tags-item-border, #d8dce5);
      border-radius: 999px;
      padding: 0 16px;
      font-size: 12px;
      margin-right: 8px;
      flex-shrink: 0;
      gap: 6px;
      transition: all 0.24s ease;
      &:hover {
        background: var(--tags-item-hover, #eee);
        border-color: rgba(31, 122, 114, 0.18);
      }
      &.active {
        background-color: #FFFFFF !important;
        color: var(--el-color-primary);
        box-shadow: 0 10px 24px rgba(31, 122, 114, 0.12);
        border-color: rgba(31, 122, 114, 0.2) !important;
      }
    }
  }
  .contextmenu {
    margin: 0;
@@ -304,12 +314,12 @@
    position: absolute;
    list-style-type: none;
    padding: 5px 0;
    border-radius: 4px;
    border-radius: 16px;
    font-size: 12px;
    font-weight: 400;
    color: var(--tags-item-text, #333);
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
    border: 1px solid var(--el-border-color-light, #e4e7ed);
    box-shadow: var(--shadow-md);
    border: 1px solid var(--surface-border, #e4e7ed);
    li {
      margin: 0;
@@ -326,30 +336,56 @@
<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: var(--tags-close-hover, #b4bccc);
        color: #fff;
        width: 12px !important;
        height: 12px !important;
      }
    }
  }
}
</style>
.tags-view-wrapper {
  .el-scrollbar__view {
    display: flex;
    align-items: center;
  }
  .tags-view-item {
    .tags-view-close {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 12px;
      height: 12px;
      line-height: 1;
      align-self: center;
      transform: translateY(1px);
    }
    .el-icon-close {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 12px;
      height: 12px;
      line-height: 1;
      vertical-align: initial !important;
      border-radius: 50%;
      text-align: center;
      transition: all .3s cubic-bezier(.645, .045, .355, 1);
      transform-origin: 100% 50%;
      align-self: center;
      &:before {
        transform: scale(.6);
        display: inline-flex;
        align-items: center;
        justify-content: center;
      }
      svg {
        display: block;
        width: 10px;
        height: 10px;
      }
      &:hover {
        background-color: var(--tags-close-hover, #b4bccc);
        color: #fff;
      }
    }
  }
}
</style>
src/layout/index.vue
@@ -69,11 +69,14 @@
  @import "@/assets/styles/mixin.scss";
  @import "@/assets/styles/variables.module.scss";
.app-wrapper {
  @include clearfix;
  position: relative;
  height: 100%;
  width: 100%;
.app-wrapper {
  @include clearfix;
  position: relative;
  height: 100%;
  width: 100%;
  background:
    radial-gradient(circle at top, rgba(223, 232, 226, 0.95), transparent 32%),
    linear-gradient(180deg, #f7faf8 0%, var(--app-bg) 100%);
  &.mobile.openSidebar {
    position: fixed;
@@ -91,24 +94,25 @@
  z-index: 999;
}
.fixed-header {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 9;
  width: calc(100% - #{$base-sidebar-width});
  transition: width 0.28s;
}
.hideSidebar .fixed-header {
  width: calc(100% - 54px);
}
.sidebarHide .fixed-header {
  width: 100%;
}
.fixed-header {
  position: fixed;
  top: 12px;
  right: 16px;
  z-index: 9;
  width: calc(100% - #{$base-sidebar-width} - 32px);
  transition: width 0.28s, right 0.28s;
  padding-bottom: 8px;
}
.hideSidebar .fixed-header {
  width: calc(100% - 70px);
}
.sidebarHide .fixed-header {
  width: calc(100% - 32px);
}
.mobile .fixed-header {
  width: 100%;
}
</style>
</style>
src/views/login.vue
@@ -1,232 +1,401 @@
<template>
  <div class="login">
    <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">{{ title }}</h3>
      <el-divider />
      <el-form-item prop="username">
        <el-input
          v-model="loginForm.username"
          type="text"
          size="large"
          auto-complete="off"
          placeholder="账号"
        >
          <template #prefix><el-icon><User /></el-icon></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input
          v-model="loginForm.password"
          type="password"
          size="large"
          auto-complete="off"
          placeholder="密码"
          show-password
          @keyup.enter="handleLogin"
        >
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
<!--      <el-form-item prop="code" v-if="captchaEnabled">-->
<!--        <el-input-->
<!--          v-model="loginForm.code"-->
<!--          size="large"-->
<!--          auto-complete="off"-->
<!--          placeholder="验证码"-->
<!--          style="width: 63%"-->
<!--          @keyup.enter="handleLogin"-->
<!--        >-->
<!--          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>-->
<!--        </el-input>-->
<!--        <div class="login-code">-->
<!--          <img :src="codeUrl" @click="getCode" class="login-code-img"/>-->
<!--        </div>-->
<!--      </el-form-item>-->
      <el-form-item style="width:100%;">
        <el-button
          :loading="loading"
          size="large"
          type="primary"
          style="width:100%;"
          @click.prevent="handleLogin"
        >
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
        </el-button>
        <div style="float: right;" v-if="register">
          <router-link class="link-type" :to="'/register'">立即注册</router-link>
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
    </el-form>
    <!--  底部  -->
<!--    <div class="el-login-footer">-->
<!--      <span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span>-->
<!--    </div>-->
  </div>
</template>
<script setup>
import {getCodeImg} from "@/api/login"
import Cookies from "js-cookie"
import { encrypt, decrypt } from "@/utils/jsencrypt"
import useUserStore from '@/store/modules/user'
const title = import.meta.env.VITE_APP_TITLE
const userStore = useUserStore()
const route = useRoute()
const router = useRouter()
const { proxy } = getCurrentInstance()
const loginForm = ref({
  username: "",
  password: "",
  rememberMe: false,
})
const loginRules = {
  username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
  password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
  // code: [{ required: true, trigger: "change", message: "请输入验证码" }]
}
const codeUrl = ref("")
const loading = ref(false)
// 验证码开关
const captchaEnabled = ref(true)
// 注册开关
const register = ref(false)
const redirect = ref(undefined)
watch(route, (newRoute) => {
    redirect.value = newRoute.query && newRoute.query.redirect
}, { immediate: true })
function handleLogin() {
  proxy.$refs.loginRef.validate(valid => {
    if (valid) {
      loading.value = true
      // 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
      Cookies.set("username", loginForm.value.username, { expires: 30 })
      Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
      Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
      userStore.loginCheckFactory(loginForm.value).then(res => {
        const query = route.query
        const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
          if (cur !== "redirect") {
            acc[cur] = query[cur]
          }
          return acc
        }, {})
        router.push({ path: redirect.value || "/", query: otherQueryParams })
      }).catch(() => {
        loading.value = false
        // 重新获取验证码
        if (captchaEnabled.value) {
          getCode()
        }
      })
    }
  })
}
function getCode() {
  getCodeImg().then(res => {
    captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
    if (captchaEnabled.value) {
      codeUrl.value = "data:image/gif;base64," + res.img
      loginForm.value.uuid = res.uuid
    }
  })
}
function getCookie() {
  const username = Cookies.get("username")
  const password = Cookies.get("password")
  const rememberMe = Cookies.get("rememberMe")
  loginForm.value = {
    username: username === undefined ? loginForm.value.username : username,
    password: password === undefined ? loginForm.value.password : decrypt(password),
    rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
  }
}
getCode()
getCookie()
</script>
<style lang='scss' scoped>
.login {
  height: 100%;
  background-image: url("../assets/images/login-background.png");
  background-size: cover;
  position: relative;
}
.title {
  margin: 20px auto 14px auto;
  text-align: center;
  color: #2C51D9;
  font-size: 28px;
  font-weight: 700;
}
.login-form {
  position: absolute;
  top: 50%;
  right: 15%;
  transform: translate(0, -50%);
  border-radius: 6px;
  background: #ffffff;
  width: 420px;
  height: 500px;
  padding: 40px;
  z-index: 1;
    box-shadow: 0 0 5px 1px #ccc;
  .el-input {
    height: 40px;
    input {
      height: 40px;
    }
  }
  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 0px;
  }
}
.login-tip {
  font-size: 13px;
  text-align: center;
  color: #bfbfbf;
}
.login-code {
  width: 33%;
  height: 40px;
  float: right;
  img {
    cursor: pointer;
    vertical-align: middle;
  }
}
.el-login-footer {
  height: 40px;
  line-height: 40px;
  position: fixed;
  bottom: 0;
  width: 100%;
  text-align: center;
  color: #fff;
  font-family: Arial;
  font-size: 12px;
  letter-spacing: 1px;
}
.login-code-img {
  height: 40px;
  padding-left: 12px;
}
:deep() {
  .el-form-item--default {
    margin-bottom: 36px;
  }
}
</style>
<template>
  <div class="login-page">
    <div class="login-shell">
      <section class="login-brand">
        <div class="brand-badge">PRODUCT INVENTORY</div>
        <img :src="brandLogo" alt="brand logo" class="brand-logo" />
        <h1 class="brand-title">{{ title }}</h1>
        <p class="brand-copy">
          统一管理库存、流程与业务数据,让系统入口和后台主界面保持同一套简约视觉语言。
        </p>
        <div class="brand-points">
          <div class="brand-point">
            <span class="point-dot"></span>
            <span>清晰的数据入口</span>
          </div>
          <div class="brand-point">
            <span class="point-dot"></span>
            <span>更轻的界面层次</span>
          </div>
          <div class="brand-point">
            <span class="point-dot"></span>
            <span>稳定的业务协同体验</span>
          </div>
        </div>
      </section>
      <section class="login-panel">
        <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
          <div class="panel-head">
            <p class="panel-kicker">WELCOME BACK</p>
            <h2 class="panel-title">登录系统</h2>
            <p class="panel-subtitle">输入账号和密码进入工作台。</p>
          </div>
          <el-form-item prop="username">
            <el-input
              v-model="loginForm.username"
              type="text"
              size="large"
              auto-complete="off"
              placeholder="账号"
            >
              <template #prefix><el-icon><User /></el-icon></template>
            </el-input>
          </el-form-item>
          <el-form-item prop="password">
            <el-input
              v-model="loginForm.password"
              type="password"
              size="large"
              auto-complete="off"
              placeholder="密码"
              show-password
              @keyup.enter="handleLogin"
            >
              <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
            </el-input>
          </el-form-item>
          <div class="login-options">
            <el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
            <router-link v-if="register" class="register-link" :to="'/register'">立即注册</router-link>
          </div>
          <el-button
            :loading="loading"
            size="large"
            type="primary"
            class="login-submit"
            @click.prevent="handleLogin"
          >
            <span v-if="!loading">登录</span>
            <span v-else>登录中...</span>
          </el-button>
        </el-form>
      </section>
    </div>
  </div>
</template>
<script setup>
import { getCodeImg } from "@/api/login"
import Cookies from "js-cookie"
import { encrypt, decrypt } from "@/utils/jsencrypt"
import useUserStore from "@/store/modules/user"
import brandLogo from "@/assets/logo/logo.png"
const title = import.meta.env.VITE_APP_TITLE
const userStore = useUserStore()
const route = useRoute()
const router = useRouter()
const { proxy } = getCurrentInstance()
const loginForm = ref({
  username: "",
  password: "",
  rememberMe: false,
})
const loginRules = {
  username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
  password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
}
const codeUrl = ref("")
const loading = ref(false)
const captchaEnabled = ref(true)
const register = ref(false)
const redirect = ref(undefined)
watch(
  route,
  (newRoute) => {
    redirect.value = newRoute.query && newRoute.query.redirect
  },
  { immediate: true }
)
function handleLogin() {
  proxy.$refs.loginRef.validate((valid) => {
    if (valid) {
      loading.value = true
      Cookies.set("username", loginForm.value.username, { expires: 30 })
      Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
      Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
      userStore
        .loginCheckFactory(loginForm.value)
        .then(() => {
          const query = route.query
          const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
            if (cur !== "redirect") {
              acc[cur] = query[cur]
            }
            return acc
          }, {})
          router.push({ path: redirect.value || "/", query: otherQueryParams })
        })
        .catch(() => {
          loading.value = false
          if (captchaEnabled.value) {
            getCode()
          }
        })
    }
  })
}
function getCode() {
  getCodeImg().then((res) => {
    captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
    if (captchaEnabled.value) {
      codeUrl.value = "data:image/gif;base64," + res.img
      loginForm.value.uuid = res.uuid
    }
  })
}
function getCookie() {
  const username = Cookies.get("username")
  const password = Cookies.get("password")
  const rememberMe = Cookies.get("rememberMe")
  loginForm.value = {
    username: username === undefined ? loginForm.value.username : username,
    password: password === undefined ? loginForm.value.password : decrypt(password),
    rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
  }
}
getCode()
getCookie()
</script>
<style lang="scss" scoped>
.login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  background:
    radial-gradient(circle at top left, rgba(207, 223, 214, 0.95), transparent 30%),
    radial-gradient(circle at bottom right, rgba(222, 232, 227, 0.9), transparent 28%),
    linear-gradient(180deg, #f7faf8 0%, #eef2ee 100%);
}
.login-shell {
  width: min(1120px, 100%);
  min-height: 680px;
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  border: 1px solid rgba(216, 225, 219, 0.9);
  border-radius: 32px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.76);
  box-shadow: 0 26px 80px rgba(31, 49, 38, 0.12);
  backdrop-filter: blur(24px);
}
.login-brand {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 56px 64px;
  background:
    linear-gradient(180deg, rgba(244, 248, 245, 0.9), rgba(233, 240, 236, 0.9)),
    linear-gradient(135deg, rgba(31, 122, 114, 0.05), rgba(255, 255, 255, 0));
  &::after {
    content: "";
    position: absolute;
    inset: 28px;
    border: 1px solid rgba(31, 122, 114, 0.08);
    border-radius: 28px;
    pointer-events: none;
  }
}
.brand-badge {
  width: fit-content;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(31, 122, 114, 0.1);
  color: #1f7a72;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
}
.brand-logo {
  width: 160px;
  height: auto;
  margin: 30px 0 24px;
  object-fit: contain;
}
.brand-title {
  margin: 0;
  font-size: 42px;
  line-height: 1.12;
  color: #21313f;
  letter-spacing: -0.03em;
}
.brand-copy {
  max-width: 460px;
  margin: 18px 0 0;
  font-size: 16px;
  line-height: 1.75;
  color: #5f6d7e;
}
.brand-points {
  margin-top: 34px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.brand-point {
  display: flex;
  align-items: center;
  gap: 12px;
  color: #3d4b59;
  font-size: 15px;
  font-weight: 500;
}
.point-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: linear-gradient(135deg, #1f7a72, #5ca39c);
  box-shadow: 0 0 0 6px rgba(31, 122, 114, 0.08);
}
.login-panel {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px;
  background: rgba(255, 255, 255, 0.7);
}
.login-form {
  width: min(420px, 100%);
  padding: 38px 34px 34px;
  border: 1px solid rgba(216, 225, 219, 0.92);
  border-radius: 28px;
  background: rgba(255, 255, 255, 0.88);
  box-shadow: 0 18px 52px rgba(31, 49, 38, 0.1);
}
.panel-head {
  margin-bottom: 28px;
}
.panel-kicker {
  margin: 0 0 10px;
  color: #8a98a8;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.16em;
}
.panel-title {
  margin: 0;
  color: #21313f;
  font-size: 30px;
  font-weight: 700;
}
.panel-subtitle {
  margin: 10px 0 0;
  color: #6b7888;
  font-size: 14px;
}
.login-options {
  margin: -4px 0 22px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: #5f6d7e;
}
.register-link {
  color: var(--el-color-primary);
  font-weight: 600;
}
.login-submit {
  width: 100%;
  height: 48px;
}
.input-icon {
  width: 14px;
}
:deep(.el-form-item) {
  margin-bottom: 22px;
}
:deep(.el-input__wrapper) {
  min-height: 42px;
  height: 42px;
  padding-top: 0;
  padding-bottom: 0;
}
:deep(.el-input__inner) {
  height: 42px;
  line-height: 42px;
}
:deep(.el-checkbox) {
  color: #5f6d7e;
}
@media (max-width: 960px) {
  .login-page {
    padding: 18px;
  }
  .login-shell {
    min-height: auto;
    grid-template-columns: 1fr;
  }
  .login-brand {
    padding: 40px 28px 22px;
  }
  .login-brand::after {
    inset: 16px;
  }
  .brand-title {
    font-size: 32px;
  }
  .brand-copy {
    font-size: 14px;
  }
  .brand-points {
    margin-top: 24px;
  }
  .login-panel {
    padding: 12px 18px 24px;
  }
  .login-form {
    width: 100%;
    padding: 28px 22px 24px;
  }
}
</style>