[重大更新] 整体主题样式重写 改为卡片式布局 样式更企业化

This commit is contained in:
疯狂的狮子Li
2026-03-17 12:35:56 +08:00
parent b83755e626
commit 833dfa67fc
70 changed files with 5250 additions and 1568 deletions
+1
View File
@@ -22,6 +22,7 @@
"dependencies": {
"@element-plus/icons-vue": "2.3.2",
"@highlightjs/vue-plugin": "2.1.2",
"@iconify/vue": "^5.0.0",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "14.2.1",
"animate.css": "4.1.1",
+667 -19
View File
@@ -64,6 +64,8 @@
/*-------------Dialog-------------**/
.el-overlay {
overflow: hidden;
background: var(--app-overlay-mask);
backdrop-filter: blur(10px);
.el-overlay-dialog {
display: flex;
@@ -71,21 +73,72 @@
justify-content: center;
width: 100%;
height: 100%;
padding: 20px;
.el-dialog {
margin: 0 auto !important;
border-radius: var(--app-radius-lg);
box-shadow: var(--app-shadow-md);
overflow: hidden;
border: 1px solid var(--app-surface-border);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0)) padding-box,
var(--app-surface-bg);
.el-dialog__header {
padding: 18px 20px 14px;
box-sizing: border-box;
border-bottom: 1px solid rgba(148, 163, 184, 0.14);
margin-right: 0;
background: var(--app-elevated-soft-bg);
}
.el-dialog__title {
font-size: 16px;
font-weight: 600;
line-height: 1.3;
color: var(--app-text-title);
letter-spacing: 0;
}
.el-dialog__headerbtn {
top: 15px;
right: 16px;
width: 32px;
height: 32px;
border-radius: 10px;
background: var(--app-elevated-close-bg);
transition:
background-color 0.2s ease,
transform 0.2s ease,
color 0.2s ease;
.el-dialog__close {
color: var(--app-text-muted);
font-size: 16px;
}
&:hover {
background: var(--app-elevated-close-hover-bg);
transform: translateY(-1px);
.el-dialog__close {
color: var(--app-accent-strong);
}
}
}
.el-dialog__body {
padding: 15px !important;
padding: 18px 20px 12px !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__footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
padding: 14px 20px 20px;
border-top: 1px solid rgba(148, 163, 184, 0.14);
}
}
}
@@ -97,6 +150,10 @@
overflow-x: hidden;
}
.el-dialog__footer .el-button {
margin-left: 0 !important;
}
// refine element ui upload
.upload-container {
.el-upload {
@@ -111,8 +168,46 @@
// dropdown
.el-dropdown-menu {
border-radius: var(--app-radius-md);
box-shadow: var(--app-shadow-sm);
padding: 8px;
border-radius: 14px;
border: 1px solid var(--app-surface-border);
background: var(--app-surface-bg);
box-shadow: var(--app-shadow-md);
.el-dropdown-menu__item {
min-height: 36px;
margin: 2px 0;
border-radius: 10px;
color: var(--app-text-title);
transition:
background-color 0.2s ease,
color 0.2s ease;
&:hover,
&:focus {
background: var(--app-accent-soft);
color: var(--app-accent-strong);
}
}
.el-dropdown-menu__item.is-disabled {
opacity: 0.5;
}
.el-dropdown-menu__item--divided {
margin-top: 8px;
position: relative;
border-top: none;
&::before {
left: 2px;
right: 2px;
top: -6px;
width: auto;
background: rgba(148, 163, 184, 0.14);
}
}
a {
display: block;
}
@@ -128,7 +223,7 @@
box-sizing: content-box;
}
.el-menu--collapse > div > .el-submenu > .el-submenu__title .el-submenu__icon-arrow {
.el-menu--collapse > div > .el-sub-menu > .el-sub-menu__title .el-sub-menu__icon-arrow {
display: none;
}
@@ -152,14 +247,104 @@
width: 240px;
}
.el-form--inline .el-date-editor {
width: 308px;
}
/* 设置 el-message-box 消息弹框内容强制换行 */
.el-message-box .el-message-box__message {
word-break: break-word;
}
.el-message-box {
border-radius: var(--app-radius-lg);
width: min(440px, calc(100vw - 32px));
padding: 0;
overflow: hidden;
border: 1px solid var(--app-surface-border);
background: var(--app-surface-bg);
border-radius: 20px;
box-shadow: var(--app-shadow-md);
.el-message-box__header {
padding: 18px 20px 0;
}
.el-message-box__title {
font-size: 17px;
font-weight: 600;
color: var(--app-text-title);
}
.el-message-box__headerbtn {
top: 14px;
right: 16px;
width: 32px;
height: 32px;
border-radius: 10px;
background: var(--app-elevated-close-bg);
transition:
background-color 0.2s ease,
transform 0.2s ease;
.el-message-box__close {
color: var(--app-text-muted);
font-size: 16px;
}
&:hover {
background: var(--app-elevated-close-hover-bg);
transform: translateY(-1px);
.el-message-box__close {
color: var(--app-accent-strong);
}
}
}
.el-message-box__content {
padding: 16px 20px 18px;
}
.el-message-box__container {
align-items: flex-start;
gap: 12px;
}
.el-message-box__status {
position: static;
margin-top: 1px;
width: 30px;
height: 30px;
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 18px !important;
background: var(--app-elevated-soft-bg);
}
.el-message-box__message {
color: var(--app-text-title);
font-size: 14px;
line-height: 1.7;
}
.el-message-box__btns {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 16px 20px 20px;
border-top: 1px solid rgba(148, 163, 184, 0.14);
}
.el-message-box__btns .el-button {
margin-left: 0;
min-width: 88px;
}
.el-message-box__input {
padding-top: 2px;
}
}
.el-message,
@@ -169,6 +354,281 @@
box-shadow: var(--app-shadow-sm);
}
.el-message {
min-width: 320px;
padding: 12px 14px;
border: 1px solid var(--app-surface-border);
background: var(--app-surface-bg);
gap: 10px;
align-items: center;
.el-message__icon {
font-size: 18px;
}
.el-message__content {
font-size: 14px;
font-weight: 500;
line-height: 1.5;
color: var(--app-text-title);
}
.el-message__closeBtn {
color: var(--app-text-muted);
transition: color 0.2s ease;
&:hover {
color: var(--app-accent-strong);
}
}
}
.el-message--success {
border-color: var(--el-color-success-light-7);
background: var(--el-color-success-light-9);
}
.el-message--warning {
border-color: var(--el-color-warning-light-7);
background: var(--el-color-warning-light-9);
}
.el-message--error {
border-color: var(--el-color-danger-light-7);
background: var(--el-color-danger-light-9);
}
.el-message--info {
border-color: var(--el-color-primary-light-7);
background: var(--el-color-primary-light-9);
}
.el-notification {
width: 340px;
padding: 16px 18px 16px 16px;
border: 1px solid var(--app-surface-border);
background: var(--app-surface-bg);
.el-notification__group {
margin-left: 8px;
}
.el-notification__title {
font-size: 15px;
font-weight: 600;
color: var(--app-text-title);
}
.el-notification__content {
margin-top: 6px;
font-size: 13px;
line-height: 1.65;
color: var(--app-text-muted);
}
.el-notification__closeBtn {
top: 18px;
color: var(--app-text-muted);
transition: color 0.2s ease;
&:hover {
color: var(--app-accent-strong);
}
}
}
.el-notification.right,
.el-notification.left {
margin-top: 12px;
}
.el-notification--success {
border-color: var(--el-color-success-light-7);
}
.el-notification--warning {
border-color: var(--el-color-warning-light-7);
}
.el-notification--error {
border-color: var(--el-color-danger-light-7);
}
.el-notification--info {
border-color: var(--el-color-primary-light-7);
}
.el-select__popper.el-popper,
.el-picker__popper.el-popper,
.el-autocomplete__popper.el-popper,
.el-cascader__dropdown,
.el-dropdown__popper.el-popper,
.el-popover.el-popper {
border: 1px solid var(--app-surface-border);
background: var(--app-surface-bg);
box-shadow: var(--app-shadow-md);
}
.el-select__popper.el-popper,
.el-autocomplete__popper.el-popper,
.el-cascader__dropdown,
.el-dropdown__popper.el-popper,
.el-popover.el-popper {
padding: 8px;
}
.el-select-dropdown__list,
.el-autocomplete-suggestion__list,
.el-cascader-menu {
padding: 0;
}
.el-select-dropdown__item,
.el-autocomplete-suggestion li,
.el-cascader-node {
min-height: 38px;
margin: 2px 0;
border-radius: 10px;
color: var(--app-text-title);
line-height: 38px;
transition:
background-color 0.2s ease,
color 0.2s ease;
}
.el-select-dropdown__item.hover,
.el-select-dropdown__item:hover,
.el-autocomplete-suggestion li:hover,
.el-cascader-node:not(.is-disabled):hover,
.el-cascader-node.in-active-path {
background: var(--app-accent-soft);
color: var(--app-accent-strong);
}
.el-select-dropdown__item.is-selected,
.el-autocomplete-suggestion li.highlighted {
background: rgba(64, 158, 255, 0.12);
color: var(--app-accent-strong);
font-weight: 600;
}
.el-select-dropdown__item.is-disabled,
.el-cascader-node.is-disabled {
opacity: 0.5;
}
.el-autocomplete-suggestion li {
display: flex;
align-items: center;
padding: 0 12px;
}
.el-autocomplete-suggestion li div {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.el-cascader-menu {
min-width: 180px;
border-right-color: rgba(148, 163, 184, 0.12);
}
.el-cascader-node__prefix,
.el-cascader-node__postfix {
color: var(--app-text-muted);
}
.el-popper.is-dark {
border-color: rgba(15, 23, 42, 0.72);
background: rgba(15, 23, 42, 0.92);
color: #f8fafc;
}
.el-popper.is-dark .el-popper__arrow::before {
background: rgba(15, 23, 42, 0.92);
border-color: rgba(15, 23, 42, 0.72);
}
.el-popper.is-light {
border-color: var(--app-surface-border);
background: var(--app-surface-bg);
}
.el-popper.is-light .el-popper__arrow::before {
background: var(--app-surface-bg);
border-color: var(--app-surface-border);
}
.el-picker__popper.el-popper {
padding: 12px;
}
.el-date-picker,
.el-date-range-picker,
.el-time-panel,
.el-time-range-picker,
.el-picker-panel {
background: transparent;
color: var(--app-text-title);
}
.el-picker-panel__body-wrapper,
.el-date-range-picker__content {
background: transparent;
}
.el-picker-panel__icon-btn,
.el-date-picker__header-label,
.el-date-range-picker__header-label {
color: var(--app-text-title);
}
.el-date-table td,
.el-month-table td .cell,
.el-year-table td .cell {
border-radius: 10px;
}
.el-date-table td.today .el-date-table-cell__text,
.el-month-table td.today .cell,
.el-year-table td.today .cell {
color: var(--app-accent-strong);
font-weight: 700;
}
.el-date-table td.available:hover,
.el-month-table td .cell:hover,
.el-year-table td .cell:hover {
color: var(--app-accent-strong);
background: var(--app-accent-soft);
}
.el-date-table td.current:not(.disabled) .el-date-table-cell__text,
.el-month-table td.current:not(.disabled) .cell,
.el-year-table td.current:not(.disabled) .cell {
background: var(--app-accent-strong);
color: #fff;
}
.el-date-editor .el-range-separator {
color: var(--app-text-muted);
}
.el-time-panel__content::before,
.el-time-panel__content::after {
border-color: rgba(148, 163, 184, 0.14);
}
.el-scrollbar__thumb {
background: rgba(148, 163, 184, 0.42);
}
.el-scrollbar__thumb:hover {
background: rgba(100, 116, 139, 0.56);
}
// Modern rounded inputs
.el-input__wrapper,
.el-textarea__inner,
@@ -193,11 +653,10 @@
// Buttons
.el-button {
border-radius: var(--app-radius-md);
transition: transform 0.15s ease, box-shadow 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
transition: box-shadow 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
}
.el-button:not(.is-text):not(.is-link):hover {
transform: translateY(-1px);
box-shadow: var(--app-shadow-sm);
}
@@ -214,32 +673,200 @@
box-shadow: var(--app-shadow-sm);
}
.el-popover {
padding: 10px 12px;
}
.el-tooltip__popper {
max-width: 320px;
padding: 8px 10px;
font-size: 12px;
line-height: 1.5;
}
.el-popconfirm {
max-width: 320px;
}
.el-popconfirm__main {
gap: 10px;
line-height: 1.6;
color: var(--app-text-title);
}
.el-popconfirm__action {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 14px;
}
.el-popconfirm__action .el-button {
margin-left: 0 !important;
}
.el-drawer {
border-radius: var(--app-radius-lg);
box-shadow: var(--app-shadow-md);
border: 1px solid var(--app-surface-border);
}
.el-drawer__header {
margin-bottom: 0;
padding: 16px 20px;
border-bottom: 1px solid var(--el-border-color-light);
padding: 18px 20px 16px;
border-bottom: 1px solid rgba(148, 163, 184, 0.14);
background: var(--app-elevated-soft-bg);
color: var(--app-text-title);
}
.el-drawer__title {
font-size: 16px;
font-weight: 600;
color: var(--app-text-title);
}
.el-drawer__body {
padding: 18px 20px 20px;
}
.el-drawer__close-btn {
width: 32px;
height: 32px;
border-radius: 10px;
background: var(--app-elevated-close-bg);
color: var(--app-text-muted);
transition:
background-color 0.2s ease,
color 0.2s ease,
transform 0.2s ease;
&:hover {
background: var(--app-elevated-close-hover-bg);
color: var(--app-accent-strong);
transform: translateY(-1px);
}
}
.el-loading-mask {
background: rgba(248, 250, 252, 0.76);
backdrop-filter: blur(4px);
}
.el-loading-spinner .path {
stroke: var(--app-accent-strong);
}
.el-descriptions {
--el-descriptions-table-border: rgba(148, 163, 184, 0.14);
}
.el-descriptions__body .el-descriptions__table {
border-radius: 12px;
overflow: hidden;
}
.el-descriptions__label.el-descriptions__cell {
background: var(--app-elevated-soft-bg) !important;
color: var(--app-text-title);
font-weight: 600;
}
.el-descriptions__content.el-descriptions__cell {
color: var(--app-text-title);
}
.el-tree {
color: var(--app-text-title);
background: transparent;
}
.el-tree-node__content {
min-height: 34px;
margin: 2px 0;
border-radius: 10px;
transition:
background-color 0.2s ease,
color 0.2s ease;
}
.el-tree-node__content:hover,
.el-tree-node.is-current > .el-tree-node__content {
background: var(--app-accent-soft);
}
.el-radio-button__inner,
.el-checkbox-button__inner {
border-radius: 10px !important;
box-shadow: none !important;
}
.el-radio-button:first-child .el-radio-button__inner,
.el-checkbox-button:first-child .el-checkbox-button__inner,
.el-radio-button:last-child .el-radio-button__inner,
.el-checkbox-button:last-child .el-checkbox-button__inner {
border-radius: 10px !important;
}
.el-empty__image {
opacity: 0.9;
}
.el-empty__description p {
color: var(--app-text-muted);
}
.el-color-dropdown {
border-radius: 16px;
border: 1px solid var(--app-surface-border);
box-shadow: var(--app-shadow-md);
}
html.dark {
.el-message--success {
background: rgba(31, 138, 90, 0.18);
}
.el-message--warning {
background: rgba(184, 121, 34, 0.18);
}
.el-message--error {
background: rgba(178, 74, 74, 0.18);
}
.el-message--info {
background: rgba(43, 107, 211, 0.18);
}
.el-loading-mask {
background: rgba(6, 11, 22, 0.7);
}
.el-popper.is-dark {
border-color: rgba(30, 41, 59, 0.92);
background: rgba(15, 23, 42, 0.96);
}
.el-popper.is-dark .el-popper__arrow::before {
background: rgba(15, 23, 42, 0.96);
border-color: rgba(30, 41, 59, 0.92);
}
}
// Table polish
.el-table {
border-radius: var(--app-radius-lg);
border-radius: 10px;
overflow: hidden;
box-shadow: var(--app-shadow-sm);
border: 1px solid var(--el-border-color-lighter);
border: 1px solid var(--app-surface-border);
}
.el-table__header-wrapper,
.el-table__body-wrapper {
background: var(--el-bg-color);
background: transparent;
}
.el-table__row:hover td.el-table__cell {
background-color: var(--el-fill-color-light) !important;
background-color: rgba(53, 109, 255, 0.05) !important;
}
// Tabs
@@ -264,6 +891,27 @@
transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}
.el-pagination {
gap: 6px;
flex-wrap: wrap;
color: var(--app-text-muted);
}
.el-pagination .el-pagination__total,
.el-pagination .el-pagination__jump {
color: var(--app-text-muted);
}
.el-pagination .btn-prev,
.el-pagination .btn-next,
.el-pagination .el-pager li,
.el-pagination .el-pagination__sizes .el-input__wrapper {
min-width: 32px;
height: 32px;
border: 1px solid rgba(148, 163, 184, 0.14);
background: var(--app-surface-bg);
}
.el-pagination .el-pager li.is-active {
box-shadow: var(--app-shadow-sm);
}
+56 -7
View File
@@ -14,7 +14,7 @@ body {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
background: var(--el-bg-color-page);
background: var(--app-shell-bg);
font-family:
'MiSans',
'HarmonyOS Sans SC',
@@ -44,6 +44,12 @@ html.dark svg {
height: 100%;
}
html,
body,
#app {
min-height: 100%;
}
*,
*:before,
*:after {
@@ -147,7 +153,7 @@ aside {
//main-container全局样式
.app-container {
padding: 20px;
padding: 16px;
background: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
border-radius: var(--app-radius-lg);
@@ -159,19 +165,62 @@ aside {
.search {
margin-bottom: 0.75rem;
border-radius: var(--app-radius-lg);
border: 1px solid var(--el-border-color-light);
background-color: var(--el-bg-color-overlay);
border: 1px solid var(--app-surface-border);
background-color: var(--app-surface-bg);
padding: 0.75rem;
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
transition: box-shadow 0.2s ease, border-color 0.2s ease;
&:hover {
box-shadow: var(--app-shadow-sm);
border-color: var(--el-border-color);
transform: translateY(-1px);
border-color: rgba(64, 158, 255, 0.2);
}
}
.el-card,
.el-dialog,
.el-drawer,
.el-popover.el-popper,
.el-dropdown__popper .el-dropdown-menu,
.el-picker__popper,
.el-select__popper.el-popper {
border-radius: var(--app-radius-lg);
border-color: var(--app-surface-border);
box-shadow: var(--app-shadow-md);
}
.el-card,
.el-dialog,
.el-drawer {
background: var(--app-surface-bg);
}
.el-button {
font-weight: 500;
}
.el-button--primary {
--el-button-bg-color: var(--app-accent-strong);
--el-button-border-color: var(--app-accent-strong);
--el-button-hover-bg-color: #4a7cff;
--el-button-hover-border-color: #4a7cff;
--el-button-active-bg-color: #2e5ce0;
--el-button-active-border-color: #2e5ce0;
}
.el-table {
--el-table-border-color: var(--app-surface-border);
--el-table-header-bg-color: var(--tableHeaderBg);
--el-table-tr-bg-color: transparent;
background: transparent;
}
.el-input__wrapper,
.el-textarea__inner,
.el-select__wrapper {
box-shadow: 0 0 0 1px transparent inset;
}
.components-container {
margin: 30px 50px;
position: relative;
+333 -15
View File
@@ -76,6 +76,41 @@ h6 {
.el-form .el-form-item__label {
font-weight: 600;
}
.p-2 {
display: flex;
flex-direction: column;
gap: 12px;
}
.page-shell {
display: flex;
flex-direction: column;
gap: 12px;
}
.app-main > .page-shell,
.app-main > .page-shell.p-2 {
padding: 0 !important;
margin: 0 !important;
}
.p-2 > .el-row,
.p-2 > .el-card,
.p-2 > .search-wrap,
.p-2 > div,
.p-2 > section {
margin-bottom: 0 !important;
}
.page-shell > .el-row,
.page-shell > .el-card,
.page-shell > .search-wrap,
.page-shell > div,
.page-shell > section {
margin-bottom: 0 !important;
}
.el-dialog:not(.is-fullscreen) {
margin-top: 6vh !important;
}
@@ -84,7 +119,7 @@ h6 {
overflow: auto;
overflow-x: hidden;
max-height: 70vh;
padding: 10px 20px 0;
padding: 14px 22px 4px;
}
.el-table {
@@ -118,17 +153,18 @@ h6 {
.pagination-container {
display: flex;
justify-content: flex-end;
margin-top: 20px;
padding: 10px 20px !important;
margin-top: 18px;
padding: 10px 0 0 !important;
}
/* tree border */
.tree-border {
margin-top: 5px;
border: 1px solid #e5e6e7;
background: #ffffff none;
border-radius: var(--app-radius-md);
margin-top: 8px;
border: 1px solid rgba(148, 163, 184, 0.16);
background: rgba(255, 255, 255, 0.42) none;
border-radius: 22px;
width: 100%;
padding: 12px;
}
@media (max-width: 768px) {
@@ -188,27 +224,28 @@ h6 {
}
.el-card__header {
padding: 14px 15px 7px !important;
min-height: 40px;
background: var(--el-fill-color-blank);
border-bottom: 1px solid var(--el-border-color-light);
padding: 14px 16px 10px !important;
min-height: auto;
background: transparent;
border-bottom: 1px solid rgba(148, 163, 184, 0.14);
}
.el-card__body {
padding: 15px 20px 20px 20px !important;
padding: 16px !important;
}
.el-card {
border-radius: var(--app-radius-lg);
box-shadow: var(--app-shadow-sm);
border-color: var(--el-border-color-lighter);
border-color: var(--app-surface-border);
overflow: hidden;
transition: box-shadow 0.2s ease, transform 0.2s ease;
transition: box-shadow 0.2s ease, border-color 0.2s ease;
background: var(--app-surface-bg);
}
.el-card:hover {
box-shadow: var(--app-shadow-md);
transform: translateY(-1px);
border-color: rgba(64, 158, 255, 0.16);
}
.card-box {
@@ -298,6 +335,287 @@ h6 {
margin-left: auto;
}
.panel-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.panel-heading h3,
.table-heading h3 {
margin: 0;
font-size: 15px;
letter-spacing: 0;
color: var(--app-text-title);
}
.panel-kicker {
display: none;
color: var(--app-accent-strong);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.toolbar-shell {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
}
.table-heading p {
margin: 4px 0 0;
font-size: 13px;
color: var(--app-text-muted);
line-height: 1.5;
}
.toolbar-actions {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 8px;
margin-left: auto;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
padding-bottom: 2px;
}
.toolbar-actions::-webkit-scrollbar {
height: 0;
}
.toolbar-actions > * {
flex-shrink: 0;
}
.toolbar-actions .top-right-btn {
margin-left: 0;
flex-shrink: 0;
}
.toolbar-actions .el-button + .el-button,
.toolbar-actions .el-dropdown + .el-button,
.toolbar-actions .el-button + .el-dropdown,
.toolbar-actions .el-dropdown + .el-dropdown,
.toolbar-actions .top-right-btn + .el-button,
.toolbar-actions .el-button + .top-right-btn,
.toolbar-actions .top-right-btn + .el-dropdown,
.toolbar-actions .el-dropdown + .top-right-btn {
margin-left: 0 !important;
}
.table-panel .toolbar-actions .el-button:not(.is-circle):not(.is-link) {
border-radius: 10px !important;
}
.query-form {
display: flex;
flex-wrap: wrap;
gap: 10px 14px;
align-items: end;
}
.query-form .el-form-item {
flex: 0 0 auto;
margin-bottom: 0;
margin-right: 0;
display: flex;
align-items: flex-end;
min-width: 0;
}
.query-form .el-form-item__content {
min-width: 0;
}
.query-form .el-form-item:last-child {
margin-left: auto;
align-self: end;
}
.query-form .el-form-item:last-child .el-form-item__content {
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
flex-wrap: nowrap;
}
.query-form .el-form-item:last-child .el-button {
margin-left: 0 !important;
flex-shrink: 0;
}
.query-form .el-form-item:last-child .el-button + .el-button {
margin-left: 0 !important;
}
.query-form .el-input__wrapper,
.query-form .el-select__wrapper,
.query-form .el-textarea__inner,
.query-form .el-date-editor,
.query-form .el-range-editor,
.query-form .el-cascader .el-input__wrapper,
.query-form .el-input-number,
.query-form .el-input-number__decrease,
.query-form .el-input-number__increase {
border-radius: 12px !important;
}
.query-form .el-button {
border-radius: 10px !important;
}
@media (max-width: 900px) {
.query-form {
gap: 10px 12px;
}
}
@media (max-width: 640px) {
.query-form {
display: flex;
flex-direction: column;
align-items: stretch;
}
.query-form .el-form-item {
width: 100%;
}
.query-form .el-input,
.query-form .el-select,
.query-form .el-date-editor,
.query-form .el-cascader,
.query-form .el-input-number {
width: 100% !important;
}
.query-form .el-form-item:last-child {
margin-left: 0;
}
}
.data-table .el-button.is-link {
width: 28px !important;
height: 28px !important;
min-width: 28px !important;
padding: 0 !important;
border-radius: 10px !important;
background: rgba(53, 109, 255, 0.08) !important;
}
.data-table .el-button.is-link + .el-button.is-link {
margin-left: 4px !important;
}
.search-wrap {
margin-bottom: 0 !important;
}
.el-card.search-panel,
.el-card.side-panel {
border-radius: 16px !important;
overflow: hidden;
}
.el-card.table-panel {
border-radius: 14px !important;
overflow: hidden;
}
.el-card.search-panel .el-card__header,
.el-card.side-panel .el-card__header {
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
.el-card.table-panel .el-card__header {
border-top-left-radius: 14px;
border-top-right-radius: 14px;
}
.el-card.search-panel .el-card__body,
.el-card.side-panel .el-card__body {
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
}
.el-card.table-panel .el-card__body {
border-bottom-left-radius: 14px;
border-bottom-right-radius: 14px;
}
.search-panel {
.el-card__header {
display: block;
padding: 12px 16px !important;
}
.el-card__body {
padding-top: 16px !important;
overflow: hidden;
max-height: 560px;
opacity: 1;
transition:
max-height 0.32s cubic-bezier(0.22, 1, 0.36, 1),
opacity 0.24s ease,
padding-top 0.24s ease,
padding-bottom 0.24s ease;
}
}
.search-panel.is-collapsed {
.el-card__body {
max-height: 0;
opacity: 0;
padding-top: 0 !important;
padding-bottom: 0 !important;
pointer-events: none;
}
}
.search-panel-toggle {
cursor: pointer;
user-select: none;
transition: color 0.24s ease;
&:hover {
color: var(--app-accent-strong);
}
&::after {
content: '';
margin-left: auto;
width: 9px;
height: 9px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
color: var(--app-text-muted);
transform: rotate(-135deg);
transition:
transform 0.24s ease,
color 0.24s ease;
}
}
.search-panel.is-collapsed .search-panel-toggle::after {
transform: rotate(45deg);
}
:global(html.dark) {
.tree-border {
background: rgba(15, 23, 42, 0.42);
border-color: rgba(100, 116, 139, 0.22);
}
}
/* horizontal el menu */
.el-menu--horizontal .el-menu-item .svg-icon + span,
.el-menu--horizontal .el-sub-menu__title .svg-icon + span {
+191 -72
View File
@@ -4,9 +4,9 @@
.main-container {
height: 100%;
transition: margin-left 0.28s;
margin-left: $base-sidebar-width;
margin-left: calc(#{$base-sidebar-width} + 12px);
position: relative;
background: var(--el-bg-color-page);
background: transparent;
}
.sidebarHide {
@@ -17,17 +17,19 @@
-webkit-transition: width 0.28s;
transition: width 0.28s;
width: $base-sidebar-width !important;
background-color: $base-menu-background;
height: 100%;
background: transparent;
height: calc(100vh - 24px);
position: fixed;
font-size: 0;
top: 0;
bottom: 0;
left: 0;
top: 12px;
left: 12px;
z-index: 1001;
overflow: hidden;
-webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
box-sizing: border-box;
padding: 0;
border-right: none;
-webkit-box-shadow: none;
box-shadow: none;
// reset element-ui css
.horizontal-collapse-transition {
@@ -49,10 +51,24 @@
height: 100%;
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 50px);
}
.el-scrollbar.theme-light {
--side-menu-text: #1f2937;
--side-menu-hover-bg: rgba(64, 158, 255, 0.08);
--side-menu-hover-text: #111827;
--side-menu-active-bg: rgba(64, 158, 255, 0.12);
--side-menu-active-text: #409eff;
--side-menu-active-border: rgba(64, 158, 255, 0.16);
--side-menu-active-line: #409eff;
}
.el-scrollbar.theme-dark {
--side-menu-text: #e5edf8;
--side-menu-hover-bg: rgba(255, 255, 255, 0.08);
--side-menu-hover-text: #ffffff;
--side-menu-active-bg: rgba(64, 158, 255, 0.22);
--side-menu-active-text: #ffffff;
--side-menu-active-border: rgba(96, 165, 250, 0.24);
--side-menu-active-line: #60a5fa;
}
.is-horizontal {
@@ -73,6 +89,11 @@
border: none;
height: 100%;
width: 100% !important;
background: transparent !important;
}
.el-menu--inline {
background: transparent !important;
}
.el-menu-item,
@@ -82,89 +103,123 @@
white-space: nowrap !important;
}
.el-menu-item,
.el-sub-menu__title,
.el-menu-item .svg-icon,
.el-sub-menu__title .svg-icon,
.el-sub-menu__icon-arrow {
color: var(--side-menu-text) !important;
fill: currentColor;
}
.el-menu-item,
.el-sub-menu__title {
position: relative;
font-weight: 500;
}
.el-menu-item .el-menu-tooltip__trigger {
display: inline-block !important;
}
// menu hover
.theme-dark .sub-menu-title-noDropdown,
.theme-dark .submenu-title-noDropdown,
.theme-dark .el-sub-menu__title {
border-radius: var(--app-radius-md);
margin: 1px 5px 1px 5px;
margin: 3px 8px;
&:hover {
background-color: $base-sub-menu-title-hover !important;
background-color: var(--side-menu-hover-bg) !important;
color: var(--side-menu-hover-text) !important;
}
}
.sub-menu-title-noDropdown,
.submenu-title-noDropdown,
.el-sub-menu__title {
border-radius: var(--app-radius-md);
margin: 1px 5px 1px 5px;
margin: 3px 8px;
color: var(--side-menu-text) !important;
&:hover {
background-color: rgba(0, 0, 0, 0.05) !important;
background-color: var(--side-menu-hover-bg) !important;
color: var(--side-menu-hover-text) !important;
}
}
& .theme-dark .is-active > .el-sub-menu__title {
color: $base-menu-color-active !important;
color: var(--side-menu-active-text) !important;
}
& .nest-menu .el-sub-menu > .el-sub-menu__title,
& .el-sub-menu .el-menu-item {
min-width: calc($base-sidebar-width - 20px) !important;
min-width: calc($base-sidebar-width - 16px) !important;
border-radius: var(--app-radius-md);
height: 45px;
margin: 1px 5px 1px 5px;
height: 40px;
margin: 3px 8px;
background: transparent !important;
&:not(.is-active):hover {
background-color: rgba(0, 0, 0, 0.1) !important;
background-color: var(--side-menu-hover-bg) !important;
color: var(--side-menu-hover-text) !important;
}
}
& .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;
border-radius: var(--app-radius-md);
height: 45px;
margin: 1px 5px 1px 5px;
height: 40px;
margin: 3px 8px;
background: transparent !important;
&.is-active {
background-color: var(--el-menu-active-color) !important;
color: #fff;
background: var(--side-menu-active-bg) !important;
color: var(--side-menu-active-text) !important;
box-shadow:
inset 3px 0 0 var(--side-menu-active-line),
inset 0 0 0 1px var(--side-menu-active-border);
}
&:not(.is-active):hover {
// you can use $sub-menuHover
background-color: $base-sub-menu-hover !important;
background-color: var(--side-menu-hover-bg) !important;
color: var(--side-menu-hover-text) !important;
}
}
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
& .theme-dark .el-menu-item {
border-radius: var(--app-radius-md);
height: 45px;
margin: 1px 5px 1px 5px;
height: 40px;
margin: 3px 8px;
background: transparent !important;
&.is-active {
background-color: var(--el-menu-active-color) !important;
color: #fff;
background: var(--side-menu-active-bg) !important;
color: var(--side-menu-active-text) !important;
box-shadow:
inset 3px 0 0 var(--side-menu-active-line),
inset 0 0 0 1px var(--side-menu-active-border);
}
&:not(.is-active):hover {
// you can use $sub-menuHover
background-color: $base-menu-hover !important;
background-color: var(--side-menu-hover-bg) !important;
color: var(--side-menu-hover-text) !important;
}
}
& .nest-menu .el-sub-menu > .el-sub-menu__title,
& .el-menu-item {
border-radius: var(--app-radius-md);
height: 45px;
margin: 1px 5px 1px 5px;
height: 40px;
margin: 3px 8px;
background: transparent !important;
&.is-active {
background-color: var(--el-menu-active-color) !important;
color: #fff;
background: var(--side-menu-active-bg) !important;
color: var(--side-menu-active-text) !important;
box-shadow:
inset 3px 0 0 var(--side-menu-active-line),
inset 0 0 0 1px var(--side-menu-active-border);
}
&:not(.is-active):hover {
// you can use $sub-menuHover
background-color: rgba(0, 0, 0, 0.04) !important;
background-color: var(--side-menu-hover-bg) !important;
color: var(--side-menu-hover-text) !important;
}
}
}
@@ -172,74 +227,137 @@
// 收起菜单后的样式
.hideSidebar {
.sidebar-container {
width: 54px !important;
width: 58px !important;
}
.main-container {
margin-left: 54px;
margin-left: 70px;
}
.sub-menu-title-noDropdown {
.sidebar-shell {
padding-left: 4px;
padding-right: 4px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
height: 45px;
height: 40px;
width: 40px !important;
min-width: 40px !important;
margin: 3px auto !important;
display: flex;
align-items: center;
justify-content: center !important;
box-sizing: border-box;
// 选中状态的菜单
&.is-active {
background-color: var(--el-menu-active-color) !important;
color: #fff !important;
background-color: var(--side-menu-active-bg) !important;
color: var(--side-menu-active-text) !important;
box-shadow: inset 3px 0 0 var(--side-menu-active-line);
}
.el-tooltip {
padding: 0 !important;
}
.el-menu-tooltip__trigger {
width: 100%;
height: 100%;
display: inline-flex !important;
align-items: center;
justify-content: center;
line-height: 1;
}
.svg-icon {
margin-right: 0 !important;
}
}
& .el-sub-menu {
overflow: hidden;
border-radius: var(--app-radius-md);
.el-sub-menu__title,
.el-sub-menu__title.el-tooltip__trigger {
border-radius: var(--app-radius-md);
height: 45px;
height: 40px;
padding: 0 !important;
display: flex;
align-items: center;
justify-content: center !important;
line-height: 1;
}
// 选中状态的菜单
&.is-active .el-sub-menu__title,
&.is-active .el-sub-menu__title.el-tooltip__trigger {
background-color: var(--el-menu-active-color) !important;
background-color: var(--side-menu-active-bg) !important;
box-shadow: inset 3px 0 0 var(--side-menu-active-line);
}
& > .el-sub-menu__title {
padding: 0 !important;
}
}
.el-menu--collapse {
> div > .el-sub-menu {
width: 40px !important;
min-width: 40px !important;
margin: 3px auto !important;
}
> div > .el-sub-menu > .el-sub-menu__title,
> div > .el-sub-menu > .el-sub-menu__title.el-tooltip__trigger {
width: 100% !important;
min-width: 100% !important;
margin: 0 !important;
box-sizing: border-box;
transform: none;
position: relative;
}
.is-active .svg-icon {
fill: #fff;
fill: currentColor;
}
.svg-icon {
display: flex;
margin: auto;
height: 100%;
// 这里设置width会跟随sidebar-container的transition 不符合预期
> div > .el-sub-menu > .el-sub-menu__title .svg-icon {
position: absolute;
left: 50%;
top: 50%;
width: 16px;
height: 16px;
transform: translate(-50%, -50%);
display: block;
margin: 0 !important;
margin-right: 0 !important;
}
.el-sub-menu {
& > .el-sub-menu__title {
& > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
& > i {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
> div > .el-sub-menu > .el-sub-menu__title {
justify-content: center !important;
.el-sub-menu__icon-arrow {
display: none !important;
width: 0 !important;
margin: 0 !important;
overflow: hidden !important;
}
& > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
& > i {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
@@ -264,7 +382,7 @@
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-$base-sidebar-width, 0, 0);
transform: translate3d(calc(-#{$base-sidebar-width} - 12px), 0, 0);
}
}
}
@@ -288,6 +406,7 @@
// 收起菜单后悬浮的菜单样式
.el-popper.is-pure{
border-radius: var(--app-radius-md);
box-shadow: var(--app-shadow-md);
.el-menu--popup{
border-radius: var(--app-radius-md);
}
+62 -32
View File
@@ -1,14 +1,14 @@
// 全局SCSS变量
:root {
--menuBg: #1f2d3d;
--menuColor: #bfcbd9;
--menuActiveText: #f4f4f5;
--menuHover: #263445;
--menuBg: #ffffff;
--menuColor: #1f2937;
--menuActiveText: #111827;
--menuHover: rgba(64, 158, 255, 0.12);
--subMenuBg: #1f2d3d;
--subMenuActiveText: #f4f4f5;
--subMenuHover: #001528;
--subMenuTitleHover: #293444;
--subMenuBg: #ffffff;
--subMenuActiveText: #111827;
--subMenuHover: rgba(64, 158, 255, 0.12);
--subMenuTitleHover: rgba(64, 158, 255, 0.08);
// 菜单栏缩进
--el-menu-base-level-padding: 12px;
@@ -16,8 +16,8 @@
--el-menu-item-height: 50px;
--fixedHeaderBg: #ffffff;
--tableHeaderBg: #f8f8f9;
--tableHeaderTextColor: #515a6e;
--tableHeaderBg: #f8fafc;
--tableHeaderTextColor: #475569;
// ele
--brder-color: #e8e8e8;
@@ -28,13 +28,28 @@
// Modern rounded style + soft shadows
--app-radius-sm: 6px;
--app-radius-md: 10px;
--app-radius-lg: 14px;
--app-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08), 0 6px 16px rgba(15, 23, 42, 0.08);
--app-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.12);
--app-shadow-lg: 0 12px 32px rgba(15, 23, 42, 0.16);
--app-radius-md: 12px;
--app-radius-lg: 18px;
--app-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.05);
--app-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.06);
--app-shadow-lg: 0 14px 36px rgba(15, 23, 42, 0.1);
--app-surface-bg: #ffffff;
--app-surface-border: var(--el-border-color-lighter);
--app-surface-border: #e5e7eb;
--app-shell-bg: #f5f7fa;
--app-shell-spot-1: rgba(64, 158, 255, 0.08);
--app-shell-spot-2: rgba(64, 158, 255, 0.04);
--app-sidebar-border: #e5e7eb;
--app-navbar-bg: #ffffff;
--app-navbar-border: #e5e7eb;
--app-navbar-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
--app-accent-soft: rgba(64, 158, 255, 0.08);
--app-accent-strong: #409eff;
--app-text-title: #1f2937;
--app-text-muted: #6b7280;
--app-overlay-mask: rgba(15, 23, 42, 0.22);
--app-elevated-soft-bg: #f8fafc;
--app-elevated-close-bg: rgba(148, 163, 184, 0.14);
--app-elevated-close-hover-bg: rgba(64, 158, 255, 0.14);
// Element Plus tokens
--el-border-radius-base: var(--app-radius-md);
@@ -42,21 +57,21 @@
--el-border-radius-round: 999px;
--el-box-shadow-light: var(--app-shadow-sm);
--el-box-shadow: var(--app-shadow-md);
--el-bg-color-page: #f5f7fb;
--el-bg-color-page: var(--app-shell-bg);
}
html.dark {
--menuBg: #1d1e1f;
--menuColor: #bfcbd9;
--menuActiveText: #f4f4f5;
--menuHover: #171819;
--menuBg: #111827;
--menuColor: #e5edf8;
--menuActiveText: #f8fbff;
--menuHover: rgba(85, 129, 255, 0.2);
--subMenuBg: #1d1e1f;
--subMenuActiveText: #1d1e1f;
--subMenuHover: #171819;
--subMenuTitleHover: #171819;
--subMenuBg: #111827;
--subMenuActiveText: #f8fbff;
--subMenuHover: rgba(85, 129, 255, 0.18);
--subMenuTitleHover: rgba(148, 163, 184, 0.12);
--fixedHeaderBg: #171819;
--fixedHeaderBg: #0f172a;
--tableHeaderBg: var(--el-bg-color);
--tableHeaderTextColor: var(--el-text-color);
@@ -204,12 +219,27 @@ html.dark {
--tags-view-active-border-color: var(--el-color-primary-light-2);
// Modern rounded style + soft shadows (dark)
--app-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.28), 0 8px 18px rgba(0, 0, 0, 0.25);
--app-shadow-md: 0 10px 26px rgba(0, 0, 0, 0.35);
--app-shadow-lg: 0 14px 34px rgba(0, 0, 0, 0.4);
--app-surface-bg: #151922;
--app-surface-border: var(--el-border-color);
--el-bg-color-page: #0f1115;
--app-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.28);
--app-shadow-md: 0 12px 28px rgba(0, 0, 0, 0.28);
--app-shadow-lg: 0 20px 44px rgba(0, 0, 0, 0.32);
--app-surface-bg: #111827;
--app-surface-border: rgba(71, 85, 105, 0.5);
--app-shell-bg: #060b16;
--app-shell-spot-1: rgba(69, 120, 255, 0.18);
--app-shell-spot-2: rgba(45, 212, 191, 0.14);
--app-sidebar-border: rgba(148, 163, 184, 0.1);
--app-navbar-bg: #111827;
--app-navbar-border: rgba(71, 85, 105, 0.48);
--app-navbar-shadow: 0 1px 2px rgba(0, 0, 0, 0.22);
--app-accent-soft: rgba(64, 158, 255, 0.14);
--app-accent-strong: #60a5fa;
--app-text-title: #f3f7ff;
--app-text-muted: #8ea0bd;
--app-overlay-mask: rgba(2, 6, 23, 0.58);
--app-elevated-soft-bg: rgba(148, 163, 184, 0.08);
--app-elevated-close-bg: rgba(148, 163, 184, 0.18);
--app-elevated-close-hover-bg: rgba(96, 165, 250, 0.18);
--el-bg-color-page: var(--app-shell-bg);
// vxe-table 主题
--vxe-font-color: #98989e;
--vxe-primary-color: #2c7ecf;
+10 -4
View File
@@ -78,12 +78,18 @@ onMounted(() => {
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
font-size: 13px;
line-height: 1.2;
margin-left: 4px;
color: var(--app-text-muted);
:deep(.el-breadcrumb__inner) {
color: inherit;
font-weight: 500;
}
.no-redirect {
color: #97a8be;
color: var(--app-text-title);
cursor: text;
}
}
+67 -10
View File
@@ -16,6 +16,19 @@
<el-input v-model="filterValue" class="p-2" placeholder="搜索图标" clearable @input="filterIcons" />
<div class="iconify-panel">
<div class="iconify-heading">也可以直接输入 Iconify 图标名</div>
<div class="iconify-form">
<el-input
v-model="customIcon"
placeholder="例如:mdi:account-circle-outline"
clearable
@keyup.enter="applyCustomIcon"
/>
<el-button type="primary" plain @click="applyCustomIcon">使用</el-button>
</div>
</div>
<el-scrollbar height="w-[200px]">
<ul class="icon-list">
<el-tooltip v-for="(iconName, index) in iconNames" :key="index" :content="iconName" placement="bottom" effect="light">
@@ -44,6 +57,7 @@ const { modelValue, width } = toRefs(props);
const iconNames = ref<string[]>(icons);
const filterValue = ref('');
const customIcon = ref('');
/**
* 筛选图标
@@ -63,6 +77,21 @@ const selectedIcon = (iconName: string) => {
emit('update:modelValue', iconName);
visible.value = false;
};
const applyCustomIcon = () => {
const value = customIcon.value.trim();
if (!value) return;
emit('update:modelValue', value);
visible.value = false;
};
watch(
() => props.modelValue,
(value) => {
customIcon.value = value?.includes(':') ? value : '';
},
{ immediate: true }
);
</script>
<style lang="scss" scoped>
@@ -73,31 +102,59 @@ const selectedIcon = (iconName: string) => {
.el-divider--horizontal {
margin: 10px auto !important;
}
.iconify-panel {
padding: 2px 4px 12px;
}
.iconify-heading {
margin-bottom: 8px;
color: var(--app-text-muted);
font-size: 12px;
font-weight: 600;
}
.iconify-form {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 8px;
}
.icon-list {
display: flex;
flex-wrap: wrap;
padding-left: 10px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
gap: 8px;
padding: 4px;
margin-top: 10px;
.icon-item {
cursor: pointer;
width: 10%;
margin: 0 10px 10px 0;
padding: 5px;
min-height: 44px;
padding: 8px 6px;
display: flex;
flex-direction: column;
justify-items: center;
justify-content: center;
align-items: center;
border: 1px solid #ccc;
border: 1px solid rgba(148, 163, 184, 0.16);
border-radius: 12px;
background: var(--app-surface-bg);
transition:
border-color 0.2s ease,
background-color 0.2s ease,
color 0.2s ease,
transform 0.2s ease;
&:hover {
border-color: var(--el-color-primary);
background: var(--app-accent-soft);
color: var(--el-color-primary);
transition: all 0.2s;
transform: scaleX(1.1);
transform: translateY(-1px);
}
}
.active {
border-color: var(--el-color-primary);
background: rgba(64, 158, 255, 0.12);
color: var(--el-color-primary);
}
}
+10 -2
View File
@@ -33,7 +33,15 @@ const handleLanguageChange = (lang: any) => {
<style lang="scss" scoped>
.lang-select--style {
font-size: 18px;
line-height: 50px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
:deep(.svg-icon) {
width: 16px;
height: 16px;
}
}
</style>
+8 -1
View File
@@ -68,8 +68,15 @@ function handleCurrentChange(val: number) {
<style lang="scss" scoped>
.pagination-container {
display: flex;
align-items: center;
justify-content: flex-end;
padding-top: 18px;
margin-top: 18px;
border-top: 1px solid rgba(148, 163, 184, 0.14);
.el-pagination {
float: v-bind(float);
float: none;
}
}
.pagination-container.hidden {
+38 -2
View File
@@ -1,6 +1,6 @@
<template>
<div class="top-right-btn" :style="style">
<el-row>
<el-row class="toolbar-row">
<el-tooltip v-if="search" class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top">
<el-button circle icon="Search" @click="toggleSearch()" />
</el-tooltip>
@@ -79,6 +79,27 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
:deep(.el-button.is-circle) {
width: 38px;
height: 38px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.62);
border: 1px solid rgba(148, 163, 184, 0.16);
color: var(--app-text-muted);
transition:
transform 0.25s ease,
border-color 0.25s ease,
color 0.25s ease,
background 0.25s ease;
&:hover {
transform: translateY(-1px);
color: var(--app-accent-strong);
background: var(--app-accent-soft);
border-color: rgba(53, 109, 255, 0.2);
}
}
:deep(.el-transfer__button) {
border-radius: 50%;
display: block;
@@ -91,12 +112,27 @@ onMounted(() => {
.my-el-transfer {
text-align: center;
}
.toolbar-row {
gap: 8px;
}
.tree-header {
width: 100%;
line-height: 24px;
text-align: center;
font-weight: 700;
color: var(--app-text-title);
}
.show-btn {
margin-left: 12px;
margin-left: 0;
}
:global(html.dark) {
.top-right-btn :deep(.el-button.is-circle) {
background: rgba(15, 23, 42, 0.56);
border-color: rgba(100, 116, 139, 0.22);
}
}
</style>
+81 -9
View File
@@ -1,10 +1,11 @@
<template>
<div>
<el-dialog v-model="roleDialog.visible.value" :title="roleDialog.title.value" width="80%" append-to-body>
<el-dialog v-model="roleDialog.visible.value" :title="roleDialog.title.value" width="80%" append-to-body class="role-select-dialog">
<div class="page-shell role-select-shell">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div v-show="showSearch">
<el-card shadow="hover" class="search-panel selector-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -21,15 +22,23 @@
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel selector-card">
<template #header>
<el-tag v-for="role in selectRoleList" :key="role.roleId" closable style="margin: 2px" @close="handleCloseTag(role)">
{{ role.roleName }}
</el-tag>
<div class="toolbar-shell selector-header">
<div class="table-heading">
<h3>角色列表</h3>
</div>
<div v-if="selectRoleList.length" class="selector-tags">
<el-tag v-for="role in selectRoleList" :key="role.roleId" closable @close="handleCloseTag(role)">
{{ role.roleName }}
</el-tag>
</div>
</div>
</template>
<vxe-table
ref="tableRef"
class="selector-table"
height="400px"
border
show-overflow
@@ -66,6 +75,7 @@
@pagination="pageList"
/>
</el-card>
</div>
<template #footer>
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="confirm">确定</el-button>
@@ -91,7 +101,6 @@ const prop = withDefaults(defineProps<PropType>(), {
});
const emit = defineEmits(['update:modelValue', 'confirmCallBack']);
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
@@ -248,3 +257,66 @@ defineExpose({
close: roleDialog.closeDialog
});
</script>
<style lang="scss" scoped>
.role-select-shell {
gap: 12px;
}
.selector-card {
height: 100%;
}
.selector-header {
align-items: flex-start;
}
.selector-tags {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 6px;
max-width: min(100%, 520px);
}
.selector-tags :deep(.el-tag) {
margin: 0;
}
.role-select-dialog :deep(.el-dialog__body) {
padding-top: 12px;
}
.selector-table {
border-radius: 10px;
overflow: hidden;
}
.selector-table :deep(.vxe-table--render-default) {
border-radius: 10px;
color: var(--app-text-title);
}
.selector-table :deep(.vxe-header--column) {
background: var(--tableHeaderBg);
color: var(--tableHeaderTextColor);
font-weight: 600;
}
.selector-table :deep(.vxe-body--column),
.selector-table :deep(.vxe-header--column) {
border-color: var(--app-surface-border);
}
.selector-table :deep(.vxe-body--row.row--hover),
.selector-table :deep(.vxe-body--row:hover) {
background-color: rgba(53, 109, 255, 0.05);
}
@media (max-width: 768px) {
.selector-tags {
justify-content: flex-start;
max-width: 100%;
}
}
</style>
+10 -3
View File
@@ -34,8 +34,15 @@ const handleSetSize = (size: 'large' | 'default' | 'small') => {
<style lang="scss" scoped>
.size-icon--style {
font-size: 18px;
line-height: 50px;
padding-right: 7px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
:deep(.svg-icon) {
width: 16px;
height: 16px;
}
}
</style>
+13 -3
View File
@@ -1,24 +1,33 @@
<template>
<svg :class="svgClass" aria-hidden="true">
<Icon v-if="isIconify" :icon="normalizedIcon" :class="svgClass" :style="iconStyle" aria-hidden="true" />
<svg v-else :class="svgClass" aria-hidden="true" :style="iconStyle">
<use :xlink:href="iconName" :fill="color" />
</svg>
</template>
<script setup lang="ts">
import { Icon } from '@iconify/vue';
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
iconClass: propTypes.string.isRequired,
className: propTypes.string.def(''),
color: propTypes.string.def('')
color: propTypes.string.def(''),
size: propTypes.string.def('')
});
const iconName = computed(() => `#icon-${props.iconClass}`);
const normalizedIcon = computed(() => props.iconClass.replace(/^i-/, ''));
const isIconify = computed(() => normalizedIcon.value.includes(':'));
const iconName = computed(() => `#icon-${normalizedIcon.value}`);
const svgClass = computed(() => {
if (props.className) {
return `svg-icon ${props.className}`;
}
return 'svg-icon';
});
const iconStyle = computed(() => ({
color: props.color || undefined,
fontSize: props.size || undefined
}));
</script>
<style lang="scss" scoped>
@@ -36,5 +45,6 @@ const svgClass = computed(() => {
position: relative;
fill: currentColor;
vertical-align: -2px;
flex-shrink: 0;
}
</style>
+209 -35
View File
@@ -1,14 +1,14 @@
<template>
<el-menu :default-active="activeMenu" mode="horizontal" :ellipsis="false" @select="handleSelect">
<el-menu class="mix-topnav-menu" :default-active="activeMenu" mode="horizontal" :ellipsis="false" @select="handleSelect">
<template v-for="(item, index) in topMenus">
<el-menu-item v-if="index < visibleNumber" :key="index" :style="{ '--theme': theme }" :index="item.path"
<el-menu-item v-if="index < visibleNumber" :key="index" :index="item.path"
><svg-icon v-if="item.meta && item.meta.icon && item.meta.icon !== '#'" :icon-class="item.meta ? item.meta.icon : ''" />
{{ item.meta?.title }}</el-menu-item
>
</template>
<!-- 顶部菜单超出数量折叠 -->
<el-sub-menu v-if="topMenus.length > visibleNumber" :style="{ '--theme': theme }" index="more">
<el-sub-menu v-if="topMenus.length > visibleNumber" class="el-sub-menu__hide-arrow" popper-class="mix-topnav-popper" index="more">
<template #title>更多菜单</template>
<template v-for="(item, index) in topMenus">
<el-menu-item v-if="index >= visibleNumber" :key="index" :index="item.path"
@@ -29,8 +29,6 @@ import { RouteRecordRaw } from 'vue-router';
//
const visibleNumber = ref<number>(-1);
// index
const currentIndex = ref<string>();
//
const hideList = ['/index', '/user/profile'];
@@ -104,12 +102,14 @@ const activeMenu = computed(() => {
});
const setVisibleNumber = () => {
const width = document.body.getBoundingClientRect().width / 3;
visibleNumber.value = parseInt(String(width / 85));
let width = document.body.getBoundingClientRect().width;
if (width >= 1000) {
width -= 420;
}
visibleNumber.value = Math.max(1, Math.floor(width / 3 / 92) + 2);
};
const handleSelect = (key: string) => {
currentIndex.value = key;
const route = routers.value.find((item) => item.path === key);
if (isHttp(key)) {
// http(s)://
@@ -161,40 +161,214 @@ onMounted(() => {
</script>
<style lang="scss">
.topmenu-container.el-menu--horizontal > .el-menu-item {
float: left;
height: 50px !important;
line-height: 50px !important;
color: #999093 !important;
padding: 0 5px !important;
margin: 0 10px !important;
.mix-topnav-menu.el-menu--horizontal {
--topbar-pill-bg: rgba(255, 255, 255, 0.7);
--topbar-pill-hover-bg: rgba(255, 255, 255, 0.9);
--topbar-pill-active-bg: rgba(255, 255, 255, 0.82);
--topbar-pill-text: var(--app-text-title);
--topbar-pill-muted: var(--app-text-muted);
--topbar-pill-border: rgba(148, 163, 184, 0.12);
--topbar-pill-active-border: rgba(148, 163, 184, 0.14);
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
height: 44px;
padding: 0;
border: none !important;
background: transparent !important;
&::after {
display: none !important;
}
}
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active,
.el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {
border-bottom: 2px solid #{'var(--theme)'} !important;
color: #303133;
#app .mix-topnav-menu.el-menu--horizontal > .el-menu-item,
#app .mix-topnav-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
height: 36px !important;
line-height: 36px !important;
margin: 0 !important;
padding: 0 18px !important;
border: 1px solid transparent !important;
border-radius: 13px;
color: var(--topbar-pill-text) !important;
background: transparent !important;
transition:
background-color 0.2s ease,
color 0.2s ease,
box-shadow 0.2s ease,
border-color 0.2s ease,
transform 0.2s ease;
}
/* sub-menu item */
.topmenu-container.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
float: left;
height: 50px !important;
line-height: 50px !important;
color: #999093 !important;
padding: 0 5px !important;
margin: 0 10px !important;
#app .mix-topnav-menu.el-menu--horizontal > .el-menu-item:hover,
#app .mix-topnav-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title:hover {
background: linear-gradient(180deg, var(--topbar-pill-hover-bg), var(--topbar-pill-bg)) !important;
border-color: var(--topbar-pill-border) !important;
color: v-bind(theme) !important;
transform: translateY(-1px);
}
/* 背景色隐藏 */
.topmenu-container.el-menu--horizontal > .el-menu-item:not(.is-disabled):focus,
.topmenu-container.el-menu--horizontal > .el-menu-item:not(.is-disabled):hover,
.topmenu-container.el-menu--horizontal > .el-submenu .el-submenu__title:hover {
background-color: #ffffff !important;
#app .mix-topnav-menu.el-menu--horizontal > .el-menu-item.is-active,
#app .mix-topnav-menu.el-menu--horizontal > .el-sub-menu.is-active > .el-sub-menu__title {
background: linear-gradient(180deg, var(--topbar-pill-hover-bg), var(--topbar-pill-active-bg)) !important;
border-color: var(--topbar-pill-active-border) !important;
color: v-bind(theme) !important;
box-shadow: none !important;
transform: none !important;
}
/* 图标右间距 */
.topmenu-container .svg-icon {
margin-right: 4px;
#app .mix-topnav-menu.el-menu--horizontal > .el-menu-item .svg-icon,
#app .mix-topnav-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title .svg-icon {
width: 14px;
height: 14px;
margin-right: 0 !important;
flex-shrink: 0;
}
#app .mix-topnav-menu.el-menu--horizontal > .el-menu-item span,
#app .mix-topnav-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title span {
color: inherit !important;
font-size: 13px;
font-weight: 500;
letter-spacing: 0.01em;
}
.mix-topnav-menu .el-sub-menu .el-sub-menu__icon-arrow {
position: static;
margin: 0 0 0 2px;
display: block !important;
color: inherit;
font-size: 11px;
}
.mix-topnav-menu.el-menu--horizontal > .el-menu-item::after,
.mix-topnav-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title::after {
display: none !important;
}
.mix-topnav-menu.el-menu--horizontal > .el-sub-menu.el-sub-menu__hide-arrow > .el-sub-menu__title {
color: var(--topbar-pill-muted) !important;
padding-right: 16px !important;
}
.mix-topnav-popper.el-popper {
border: none !important;
border-radius: 18px !important;
background: transparent !important;
box-shadow: none !important;
padding: 0 !important;
}
.mix-topnav-popper.el-popper .el-popper__arrow {
display: none !important;
}
.mix-topnav-popper .el-menu--popup {
min-width: 188px;
padding: 10px !important;
border: 1px solid rgba(148, 163, 184, 0.14);
border-radius: 18px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.94)) !important;
box-shadow:
0 18px 40px rgba(15, 23, 42, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.72);
}
.mix-topnav-popper .el-menu--popup .el-menu-item,
.mix-topnav-popper .el-menu--popup .el-sub-menu__title {
display: flex;
align-items: center;
height: 38px;
line-height: 38px;
padding: 0 14px !important;
margin: 0 0 4px !important;
border-radius: 12px;
color: var(--app-text-title) !important;
background: transparent !important;
transition:
background-color 0.2s ease,
color 0.2s ease,
box-shadow 0.2s ease,
transform 0.2s ease;
}
.mix-topnav-popper .el-menu--popup .el-menu-item:last-child,
.mix-topnav-popper .el-menu--popup .el-sub-menu:last-child > .el-sub-menu__title {
margin-bottom: 0 !important;
}
.mix-topnav-popper .el-menu--popup .el-menu-item:hover,
.mix-topnav-popper .el-menu--popup .el-sub-menu__title:hover {
background: rgba(64, 158, 255, 0.08) !important;
color: v-bind(theme) !important;
transform: translateX(1px);
}
.mix-topnav-popper .el-menu--popup .el-menu-item.is-active,
.mix-topnav-popper .el-menu--popup .el-sub-menu.is-active > .el-sub-menu__title {
background: linear-gradient(180deg, rgba(64, 158, 255, 0.16), rgba(64, 158, 255, 0.1)) !important;
color: v-bind(theme) !important;
box-shadow: inset 0 0 0 1px rgba(64, 158, 255, 0.12);
}
.mix-topnav-popper .el-menu--popup .svg-icon {
width: 14px;
height: 14px;
margin-right: 10px !important;
}
.mix-topnav-popper .el-menu--popup .el-sub-menu__title > span,
.mix-topnav-popper .el-menu--popup .el-menu-item > span {
display: inline-flex;
align-items: center;
font-size: 13px;
font-weight: 500;
letter-spacing: 0.01em;
}
.mix-topnav-popper .el-menu--popup .el-sub-menu__icon-arrow {
position: static;
margin-left: auto;
margin-top: 0;
font-size: 11px;
align-self: center;
}
html.dark .mix-topnav-menu.el-menu--horizontal {
--topbar-pill-bg: rgba(30, 41, 59, 0.64);
--topbar-pill-hover-bg: rgba(51, 65, 85, 0.88);
--topbar-pill-active-bg: rgba(51, 65, 85, 0.78);
--topbar-pill-border: rgba(71, 85, 105, 0.28);
--topbar-pill-active-border: rgba(71, 85, 105, 0.32);
}
html.dark .mix-topnav-popper .el-menu--popup {
border-color: rgba(71, 85, 105, 0.34);
background: linear-gradient(180deg, rgba(15, 23, 42, 0.98), rgba(30, 41, 59, 0.94)) !important;
box-shadow:
0 20px 42px rgba(0, 0, 0, 0.34),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
html.dark .mix-topnav-popper .el-menu--popup .el-menu-item,
html.dark .mix-topnav-popper .el-menu--popup .el-sub-menu__title {
color: #e5edf8 !important;
}
html.dark .mix-topnav-popper .el-menu--popup .el-menu-item:hover,
html.dark .mix-topnav-popper .el-menu--popup .el-sub-menu__title:hover {
background: rgba(96, 165, 250, 0.14) !important;
}
html.dark .mix-topnav-popper .el-menu--popup .el-menu-item.is-active,
html.dark .mix-topnav-popper .el-menu--popup .el-sub-menu.is-active > .el-sub-menu__title {
background: linear-gradient(180deg, rgba(37, 99, 235, 0.28), rgba(59, 130, 246, 0.18)) !important;
box-shadow: inset 0 0 0 1px rgba(96, 165, 250, 0.16);
}
</style>
+117 -16
View File
@@ -1,14 +1,20 @@
<template>
<div>
<el-dialog v-model="userDialog.visible.value" :title="userDialog.title.value" width="80%" append-to-body>
<el-row :gutter="20">
<el-dialog v-model="userDialog.visible.value" :title="userDialog.title.value" width="80%" append-to-body class="user-select-dialog">
<div class="page-shell user-select-shell">
<el-row :gutter="12" class="selector-layout">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-col :lg="5" :xs="24">
<el-card shadow="hover" class="side-panel selector-card selector-side-card">
<template #header>
<div class="table-heading">
<h3>部门结构</h3>
</div>
</template>
<el-input v-model="deptName" class="selector-dept-input" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree
ref="deptTreeRef"
class="mt-2"
class="selector-tree"
node-key="id"
:data="deptOptions"
:props="{ label: 'label', children: 'children' } as any"
@@ -20,11 +26,12 @@
/>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<el-col :lg="19" :xs="24">
<div class="page-shell user-select-main">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div v-show="showSearch">
<el-card shadow="hover" class="search-panel selector-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -40,15 +47,23 @@
</div>
</transition>
<el-card shadow="hover">
<template v-if="prop.multiple" #header>
<el-tag v-for="user in selectUserList" :key="user.userId" closable style="margin: 2px" @close="handleCloseTag(user)">
{{ user.nickName }}
</el-tag>
<el-card shadow="hover" class="table-panel selector-card">
<template #header>
<div class="toolbar-shell selector-header">
<div class="table-heading">
<h3>用户列表</h3>
</div>
<div v-if="prop.multiple && selectUserList.length" class="selector-tags">
<el-tag v-for="user in selectUserList" :key="user.userId" closable @close="handleCloseTag(user)">
{{ user.nickName }}
</el-tag>
</div>
</div>
</template>
<vxe-table
ref="tableRef"
class="selector-table"
height="400px"
border
show-overflow
@@ -86,8 +101,10 @@
@pagination="pageList"
/>
</el-card>
</div>
</el-col>
</el-row>
</el-row>
</div>
<template #footer>
<el-button @click="close">取消</el-button>
@@ -309,3 +326,87 @@ defineExpose({
close: userDialog.closeDialog
});
</script>
<style lang="scss" scoped>
.user-select-shell,
.user-select-main {
gap: 12px;
}
.selector-layout {
align-items: stretch;
}
.selector-card {
height: 100%;
}
.selector-side-card {
min-height: 100%;
}
.selector-dept-input {
margin-bottom: 12px;
}
.selector-tree {
min-height: 400px;
}
.selector-header {
align-items: flex-start;
}
.selector-tags {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 6px;
max-width: min(100%, 520px);
}
.selector-tags :deep(.el-tag) {
margin: 0;
}
.user-select-dialog :deep(.el-dialog__body) {
padding-top: 12px;
}
.selector-table {
border-radius: 10px;
overflow: hidden;
}
.selector-table :deep(.vxe-table--render-default) {
border-radius: 10px;
color: var(--app-text-title);
}
.selector-table :deep(.vxe-header--column) {
background: var(--tableHeaderBg);
color: var(--tableHeaderTextColor);
font-weight: 600;
}
.selector-table :deep(.vxe-body--column),
.selector-table :deep(.vxe-header--column) {
border-color: var(--app-surface-border);
}
.selector-table :deep(.vxe-body--row.row--hover),
.selector-table :deep(.vxe-body--row:hover) {
background-color: rgba(53, 109, 255, 0.05);
}
@media (max-width: 992px) {
.selector-tree {
min-height: 220px;
}
.selector-tags {
justify-content: flex-start;
max-width: 100%;
}
}
</style>
+8 -19
View File
@@ -22,11 +22,9 @@ const tagsViewStore = useTagsViewStore();
//
const animate = ref<string>('');
const animationEnable = ref(useSettingsStore().animationEnable);
watch(
() => useSettingsStore().animationEnable,
(val: boolean) => {
animationEnable.value = val;
if (val) {
animate.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string;
} else {
@@ -36,10 +34,6 @@ watch(
{ immediate: true }
);
onMounted(() => {
addIframe();
});
watchEffect(() => {
addIframe();
});
@@ -53,26 +47,21 @@ function addIframe() {
<style lang="scss" scoped>
.app-main {
/* 50= navbar 50 */
min-height: calc(100vh - 50px);
min-height: 100vh;
width: 100%;
position: relative;
overflow: hidden;
padding: 12px;
}
.fixed-header + .app-main {
padding-top: 50px;
.app-main.with-fixed-header {
padding-top: 76px;
min-height: calc(100vh - 76px);
}
.hasTagsView {
.app-main {
/* 84 = navbar + tags-view = 50 + 34 */
min-height: calc(100vh - 84px);
}
.fixed-header + .app-main {
padding-top: 84px;
}
.app-main.with-fixed-header.with-tags-view {
min-height: calc(100vh - 111px);
padding-top: 111px;
}
</style>
<style lang="scss">
+246 -53
View File
@@ -1,14 +1,22 @@
<template>
<div class="navbar" :class="'nav' + navType">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" />
<div class="navbar-left">
<div v-if="navType !== NavTypeEnum.TOP" class="hamburger-shell">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" />
</div>
<router-link v-else-if="showLogo" to="/" class="navtop-logo-shell">
<img :src="appLogo" class="navtop-logo-icon" alt="logo" />
</router-link>
<breadcrumb v-if="navType == NavTypeEnum.LEFT" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="navType == NavTypeEnum.MIX" id="topmenu-container" class="topmenu-container" />
<div class="nav-context">
<breadcrumb v-if="navType == NavTypeEnum.LEFT" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="navType == NavTypeEnum.MIX" id="topmenu-container" class="topmenu-container" />
<template v-if="navType == NavTypeEnum.TOP">
<logo v-show="showLogo" :collapse="false"></logo>
<top-bar id="topbar-container" class="topbar-container" />
</template>
<template v-if="navType == NavTypeEnum.TOP">
<top-bar id="topbar-container" class="topbar-container" />
</template>
</div>
</div>
<div class="right-menu flex align-center">
<template v-if="appStore.device !== 'mobile'">
<search-menu ref="searchMenuRef" />
@@ -23,7 +31,7 @@
<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">
<div class="right-menu-item hover-effect" style="display: block"><svg-icon icon-class="message" /></div>
<div class="right-menu-item hover-effect message-trigger"><svg-icon icon-class="message" /></div>
</el-badge>
</template>
<template #default>
@@ -53,10 +61,14 @@
</el-tooltip>
</template>
<div class="avatar-container">
<el-dropdown class="right-menu-item hover-effect" trigger="click" @command="handleCommand">
<el-dropdown class="avatar-dropdown" trigger="click" @command="handleCommand">
<div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" />
<el-icon><caret-bottom /></el-icon>
<div class="avatar-meta">
<span class="avatar-name">{{ displayName }}</span>
<span class="avatar-role">Workspace</span>
</div>
<el-icon class="avatar-arrow"><caret-bottom /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
@@ -87,8 +99,9 @@ import notice from './notice/index.vue';
import router from '@/router';
import type { ElMessageBoxOptions } from 'element-plus';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
import Logo from "@/layout/components/Sidebar/Logo.vue";
import TopBar from './TopBar/index.vue';
import { CaretBottom } from '@element-plus/icons-vue';
import appLogo from '@/assets/logo/logo.png';
const appStore = useAppStore();
const userStore = useUserStore();
@@ -98,9 +111,9 @@ const newNotice = ref(<number>0);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userId = ref(userStore.userId);
const navType = computed(() => settingsStore.navType);
const showLogo = computed(() => settingsStore.sidebarLogo);
const displayName = computed(() => userStore.nickname || '管理员');
//
const searchMenuRef = ref<InstanceType<typeof SearchMenu>>();
@@ -157,8 +170,56 @@ watch(
<style lang="scss" scoped>
.navbar.navtop {
.hamburger-container {
display: none !important;
.nav-context {
flex: 1;
}
.navtop-logo-shell {
width: 48px;
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 12px;
flex-shrink: 0;
border-radius: 14px;
border: 1px solid rgba(148, 163, 184, 0.18);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(248, 250, 252, 0.82));
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.72),
0 6px 14px rgba(15, 23, 42, 0.04);
transition:
transform 0.2s ease,
border-color 0.2s ease,
box-shadow 0.2s ease;
&:hover {
transform: translateY(-1px);
border-color: rgba(64, 158, 255, 0.22);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.76),
0 10px 22px rgba(15, 23, 42, 0.08);
}
}
.navtop-logo-icon {
width: 32px;
height: 32px;
display: block;
border-radius: 11px;
}
.topbar-container {
flex: 1;
min-width: 0;
margin-left: 0;
padding: 4px 8px;
border-radius: 16px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(248, 250, 252, 0.58));
border: 1px solid rgba(148, 163, 184, 0.14);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.72),
0 6px 18px rgba(15, 23, 42, 0.04);
}
}
@@ -167,7 +228,17 @@ watch(
}
:deep(.el-badge__content.is-fixed) {
top: 12px;
top: 8px;
right: 6px;
}
:deep(.el-badge) {
display: inline-flex;
align-items: center;
}
:deep(.el-dropdown) {
outline: none;
}
.flex {
@@ -179,42 +250,71 @@ watch(
}
.navbar {
height: 50px;
min-height: 52px;
overflow: hidden;
position: relative;
background: var(--el-bg-color);
border-bottom: 1px solid var(--el-border-color-lighter);
box-shadow: none;
background: var(--app-navbar-bg);
border: 1px solid var(--app-navbar-border);
box-shadow: var(--app-navbar-shadow);
display: flex;
align-items: center;
// padding: 0 8px;
justify-content: space-between;
border-radius: 16px;
padding: 6px 12px;
box-sizing: border-box;
.navbar-left {
display: flex;
align-items: center;
min-width: 0;
gap: 10px;
flex: 1;
}
.hamburger-shell {
width: 36px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
background: transparent;
color: var(--app-accent-strong);
flex-shrink: 0;
border: 1px solid var(--app-surface-border);
}
.hamburger-container {
line-height: 46px;
line-height: 32px;
height: 100%;
//float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
display: flex;
align-items: center;
flex-shrink: 0;
margin-right: 8px;
justify-content: center;
&:hover {
background: var(--el-fill-color-lighter);
background: transparent;
}
}
.nav-context {
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
gap: 0;
}
.breadcrumb-container {
//float: left;
flex-shrink: 0;
}
.topmenu-container {
position: absolute;
left: 50px;
position: static;
min-width: 0;
}
.topbar-container {
@@ -226,66 +326,159 @@ watch(
margin-left: 8px;
}
.errLog-container {
display: inline-block;
vertical-align: top;
}
.right-menu {
//float: right;
height: 100%;
line-height: 50px;
line-height: 1;
display: flex;
align-items: center;
gap: 6px;
margin-left: auto;
flex-wrap: nowrap;
&:focus {
outline: none;
}
> * {
flex-shrink: 0;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: var(--el-text-color-regular);
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
font-size: 16px;
color: var(--app-text-muted);
border-radius: 12px;
vertical-align: text-bottom;
background: transparent;
border: 1px solid transparent;
flex-shrink: 0;
:deep(.svg-icon),
:deep(svg),
:deep(.el-icon) {
width: 16px;
height: 16px;
font-size: 16px;
display: block;
}
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
transition:
background 0.3s,
color 0.3s;
&:hover {
background: var(--el-fill-color-lighter);
background: var(--app-accent-soft);
color: var(--app-accent-strong);
border-color: rgba(64, 158, 255, 0.16);
}
}
}
.message-trigger {
display: inline-flex;
}
.avatar-container {
margin-right: 40px;
margin-left: 6px;
margin-right: 0;
flex-shrink: 0;
.avatar-dropdown {
display: block;
width: auto;
height: auto;
border: none;
background: transparent;
}
.avatar-wrapper {
margin-top: 5px;
position: relative;
display: flex;
align-items: center;
gap: 12px;
padding: 4px 8px 4px 4px;
border-radius: 16px;
background: #fff;
border: 1px solid var(--app-surface-border);
min-width: 0;
cursor: pointer;
transition:
background 0.3s,
border-color 0.3s;
&:hover {
background: var(--app-accent-soft);
border-color: rgba(64, 158, 255, 0.16);
}
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: var(--app-radius-md);
margin-top: 10px;
width: 28px;
height: 28px;
border-radius: 12px;
object-fit: cover;
box-shadow: none;
}
i {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
.avatar-meta {
display: flex;
flex-direction: column;
min-width: 0;
gap: 2px;
}
.avatar-name {
max-width: 88px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--app-text-title);
font-size: 12px;
font-weight: 600;
}
.avatar-role {
color: var(--app-text-muted);
font-size: 11px;
}
.avatar-arrow {
color: var(--app-text-muted);
font-size: 12px;
flex-shrink: 0;
}
}
}
}
}
:global(html.dark) {
.navbar.navtop .navtop-logo-shell {
background: linear-gradient(180deg, rgba(15, 23, 42, 0.92), rgba(30, 41, 59, 0.82));
border-color: rgba(71, 85, 105, 0.42);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.06),
0 8px 18px rgba(0, 0, 0, 0.24);
}
.navbar.navtop .topbar-container {
background: linear-gradient(180deg, rgba(15, 23, 42, 0.82), rgba(15, 23, 42, 0.7));
border-color: rgba(71, 85, 105, 0.34);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.05),
0 8px 22px rgba(0, 0, 0, 0.2);
}
.navbar .right-menu .right-menu-item,
.navbar .right-menu .avatar-wrapper {
background: var(--app-navbar-bg);
border-color: var(--app-navbar-border);
}
}
</style>
+41 -4
View File
@@ -1,5 +1,5 @@
<template>
<el-drawer v-model="showSettings" :with-header="false" direction="rtl" size="300px" close-on-click-modal>
<el-drawer v-model="showSettings" class="settings-drawer" :with-header="false" direction="rtl" size="300px" close-on-click-modal>
<h3 class="drawer-title">菜单导航设置</h3>
<div class="nav-wrap">
<el-tooltip content="左侧菜单" placement="bottom">
@@ -229,6 +229,15 @@ defineExpose({
</script>
<style lang="scss" scoped>
.settings-drawer {
:deep(.el-drawer__body) {
display: flex;
flex-direction: column;
gap: 4px;
padding: 18px 18px 20px;
}
}
.setting-drawer-title {
margin-bottom: 12px;
color: rgba(0, 0, 0, 0.85);
@@ -248,12 +257,23 @@ defineExpose({
.setting-drawer-block-checbox-item {
position: relative;
margin-right: 16px;
border-radius: 2px;
border-radius: 12px;
cursor: pointer;
overflow: hidden;
border: 1px solid rgba(148, 163, 184, 0.16);
transition:
border-color 0.2s ease,
transform 0.2s ease;
&:hover {
border-color: rgba(64, 158, 255, 0.28);
transform: translateY(-1px);
}
img {
width: 48px;
height: 48px;
display: block;
}
.custom-img {
@@ -281,6 +301,8 @@ defineExpose({
.drawer-item {
padding: 12px 0;
font-size: 14px;
color: var(--app-text-title);
border-bottom: 1px solid rgba(148, 163, 184, 0.08);
.comp-style {
float: right;
@@ -306,9 +328,16 @@ defineExpose({
cursor: pointer;
width: 56px;
height: 48px;
border-radius: 4px;
background: #f0f2f5;
border-radius: 12px;
background: var(--app-elevated-soft-bg);
border: 2px solid transparent;
transition:
transform 0.2s ease,
border-color 0.2s ease;
&:hover {
transform: translateY(-1px);
}
}
.left {
@@ -317,6 +346,7 @@ defineExpose({
height: 30%;
background: #fff;
}
b:last-child {
width: 30%;
background: #1b2a47;
@@ -326,6 +356,7 @@ defineExpose({
border-radius: 4px 0 0 4px;
}
}
.mix {
b:first-child {
border-radius: 4px 4px 0 0;
@@ -333,6 +364,7 @@ defineExpose({
height: 30%;
background: #1b2a47;
}
b:last-child {
width: 30%;
background: #1b2a47;
@@ -341,6 +373,7 @@ defineExpose({
border-radius: 0 0 0 4px;
}
}
.top {
b:first-child {
display: block;
@@ -348,6 +381,10 @@ defineExpose({
background: #1b2a47;
border-radius: 4px 4px 0 0;
}
b:last-child {
display: none;
}
}
}
</style>
+39 -35
View File
@@ -21,7 +21,6 @@
</template>
<script setup lang="ts">
import variables from '@/assets/styles/variables.module.scss';
import logo from '@/assets/logo/logo.png';
import { useSettingsStore } from '@/store/modules/settings';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -37,28 +36,11 @@ defineProps({
const title = import.meta.env.VITE_APP_LOGO_TITLE;
const settingsStore = useSettingsStore();
const sideTheme = computed(() => settingsStore.sideTheme);
// Logo
const getLogoBackground = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-bg)'
}
if (settingsStore.navType == NavTypeEnum.TOP) {
return variables.menuLightBackground
}
return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBackground
})
// Logo
const getLogoTextColor = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-text)'
}
if (settingsStore.navType == NavTypeEnum.TOP) {
return variables.logoLightTitleColor
}
return sideTheme.value === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor
})
const isTopNav = computed(() => settingsStore.navType === NavTypeEnum.TOP);
const isDarkSide = computed(() => !isTopNav.value && sideTheme.value === 'theme-dark');
const logoSurface = computed(() => (isDarkSide.value ? 'rgba(255, 255, 255, 0.04)' : '#f8fafc'));
const logoBorder = computed(() => (isDarkSide.value ? 'rgba(148, 163, 184, 0.12)' : '#e5e7eb'));
const logoTextColor = computed(() => (isDarkSide.value ? '#f8fbff' : 'var(--app-text-title)'));
</script>
<style lang="scss" scoped>
@@ -73,39 +55,61 @@ const getLogoTextColor = computed(() => {
.sidebar-logo-container {
position: relative;
height: 50px;
line-height: 50px;
background: v-bind(getLogoBackground);
flex-shrink: 0;
height: 46px;
line-height: 46px;
padding: 0 8px;
margin-top: 8px;
background: transparent;
text-align: center;
overflow: hidden;
margin-bottom: 0;
& .sidebar-logo-link {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
border-radius: 14px;
background: v-bind(logoSurface);
border: 1px solid v-bind(logoBorder);
& .sidebar-logo {
width: 32px;
height: 32px;
width: 28px;
height: 28px;
vertical-align: middle;
margin-right: 12px;
margin-left: 12px;
margin-right: 0;
margin-left: 0;
border-radius: 10px;
box-shadow: none;
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: v-bind(getLogoTextColor);
color: v-bind(logoTextColor);
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
line-height: 1;
font-size: 15px;
letter-spacing: 0.02em;
font-family:
'MiSans',
'HarmonyOS Sans SC',
'PingFang SC',
sans-serif;
vertical-align: middle;
}
}
&.collapse {
.sidebar-logo-link {
padding: 0;
}
.sidebar-logo {
margin-right: 0px;
margin-right: 0;
}
}
}
@@ -11,7 +11,7 @@
</app-link>
</template>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" :popper-offset="isNest ? 4 : 12" :popper-class="popperClass" teleported>
<template v-if="item.meta" #title>
<svg-icon :icon-class="item.meta ? item.meta.icon : ''" />
<span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
@@ -22,6 +22,7 @@
:key="child.path + index"
:is-nest="true"
:item="child"
:popper-class="popperClass"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
@@ -47,6 +48,10 @@ const props = defineProps({
basePath: {
type: String,
default: ''
},
popperClass: {
type: String,
default: ''
}
});
+34 -4
View File
@@ -1,5 +1,5 @@
<template>
<div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: bgColor }">
<div class="sidebar-shell" :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: bgColor }">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
<transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
@@ -24,7 +24,6 @@
<script setup lang="ts">
import Logo from './Logo.vue';
import SidebarItem from './SidebarItem.vue';
import variables from '@/assets/styles/variables.module.scss';
import { useAppStore } from '@/store/modules/app';
import { useSettingsStore } from '@/store/modules/settings';
import { usePermissionStore } from '@/store/modules/permission';
@@ -51,6 +50,37 @@ const activeMenu = computed(() => {
return path;
});
const bgColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground));
const textColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuColor : variables.menuLightColor));
const bgColor = computed(() => (sideTheme.value === 'theme-dark' ? '#111827' : '#ffffff'));
const textColor = computed(() => (sideTheme.value === 'theme-dark' ? '#e5edf8' : '#1f2937'));
</script>
<style lang="scss" scoped>
.sidebar-shell {
height: 100%;
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px 8px 12px;
border: 1px solid var(--app-sidebar-border);
border-radius: 22px;
box-shadow: var(--app-shadow-sm);
background: v-bind(bgColor) !important;
overflow: hidden;
}
:deep(.el-scrollbar__view) {
min-height: 0;
padding-bottom: 12px;
}
:deep(.el-scrollbar) {
flex: 1;
min-height: 0;
height: auto !important;
}
:deep(.el-scrollbar__wrap) {
height: 100%;
overflow-x: hidden;
}
</style>
+222 -35
View File
@@ -1,30 +1,26 @@
<template>
<el-menu class="topbar-menu" :ellipsis="false" :default-active="activeMenu" :active-text-color="theme" mode="horizontal">
<sidebar-item :key="route.path + index" v-for="(route, index) in topMenus" :item="route" :base-path="route.path" />
<sidebar-item :key="route.path + index" v-for="(route, index) in topMenus" :item="route" :base-path="route.path" popper-class="topbar-menu-popper" />
<el-sub-menu index="more" class="el-sub-menu__hide-arrow" v-if="moreRoutes.length > 0">
<el-sub-menu index="more" class="el-sub-menu__hide-arrow" popper-class="topbar-menu-popper" v-if="moreRoutes.length > 0">
<template #title>
<span>更多菜单</span>
</template>
<sidebar-item :key="route.path + index" v-for="(route, index) in moreRoutes" :item="route" :base-path="route.path" />
<sidebar-item :key="route.path + index" v-for="(route, index) in moreRoutes" :item="route" :base-path="route.path" popper-class="topbar-menu-popper" />
</el-sub-menu>
</el-menu>
</template>
<script setup>
<script setup lang="ts">
import SidebarItem from '../Sidebar/SidebarItem'
import {useAppStore} from '@/store/modules/app'
import {useSettingsStore} from '@/store/modules/settings'
import {usePermissionStore} from '@/store/modules/permission'
const route = useRoute()
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const sidebarRouters = computed(() => permissionStore.sidebarRouters)
const theme = computed(() => settingsStore.theme)
const device = computed(() => appStore.device)
const activeMenu = computed(() => {
const { meta, path } = route
if (meta.activeMenu) {
@@ -38,14 +34,14 @@ const topMenus = computed(() => {
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(0, visibleNumber.value)
})
const moreRoutes = computed(() => {
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(visibleNumber.value, sidebarRouters.value.length - visibleNumber.value)
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(visibleNumber.value)
})
function setVisibleNumber() {
let width = document.body.getBoundingClientRect().width
if (width >= 1000) {
width -= 500
width -= 420
}
visibleNumber.value = parseInt(width / 3 / 85)
visibleNumber.value = Math.max(1, Math.floor(width / 3 / 92) + 2)
}
onMounted(() => {
@@ -61,43 +57,234 @@ onMounted(() => {
</script>
<style lang="scss">
.topbar-menu.el-menu--horizontal {
--topbar-pill-bg: rgba(255, 255, 255, 0.7);
--topbar-pill-hover-bg: rgba(255, 255, 255, 0.9);
--topbar-pill-active-bg: rgba(255, 255, 255, 0.82);
--topbar-pill-text: var(--app-text-title);
--topbar-pill-muted: var(--app-text-muted);
--topbar-pill-border: rgba(148, 163, 184, 0.12);
--topbar-pill-active-border: rgba(148, 163, 184, 0.14);
/* menu item */
#app .topbar-menu.el-menu--horizontal .el-sub-menu__title, #app .topbar-menu.el-menu--horizontal .el-menu-item {
padding: 0 10px !important;
display: flex;
align-items: center;
gap: 6px;
flex: 1;
min-width: 0;
height: 44px;
padding: 0;
border: none !important;
background: transparent !important;
&::after {
display: none !important;
}
}
.topbar-menu.el-menu--horizontal > .el-menu-item {
float: left;
height: 50px !important;
line-height: 50px !important;
color: #303133 !important;
padding: 0 5px !important;
margin: 0 10px !important;
#app .topbar-menu.el-menu--horizontal > div > a > .el-menu-item,
#app .topbar-menu.el-menu--horizontal > div > .el-sub-menu > .el-sub-menu__title,
#app .topbar-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
height: 36px !important;
line-height: 36px !important;
margin: 0 !important;
padding: 0 18px !important;
border: 1px solid transparent !important;
border-radius: 13px;
color: var(--topbar-pill-text) !important;
background: transparent !important;
transition:
background-color 0.2s ease,
color 0.2s ease,
box-shadow 0.2s ease,
border-color 0.2s ease,
transform 0.2s ease;
}
.el-sub-menu.is-active .svg-icon, .el-menu-item.is-active .svg-icon + span, .el-sub-menu.is-active .svg-icon + span, .el-sub-menu.is-active .el-sub-menu__title span {
color: v-bind(theme);
#app .topbar-menu.el-menu--horizontal > div > a > .el-menu-item:hover,
#app .topbar-menu.el-menu--horizontal > div > .el-sub-menu > .el-sub-menu__title:hover,
#app .topbar-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title:hover {
background: linear-gradient(180deg, var(--topbar-pill-hover-bg), var(--topbar-pill-bg)) !important;
border-color: var(--topbar-pill-border) !important;
color: v-bind(theme) !important;
transform: translateY(-1px);
}
/* sub-menu item */
.topbar-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
float: left;
line-height: 50px !important;
color: #303133 !important;
margin: 0 15px -3px!important;
#app .topbar-menu.el-menu--horizontal > div > a > .el-menu-item.is-active,
#app .topbar-menu.el-menu--horizontal > div > .el-sub-menu.is-active > .el-sub-menu__title,
#app .topbar-menu.el-menu--horizontal > .el-sub-menu.is-active > .el-sub-menu__title {
background: linear-gradient(180deg, var(--topbar-pill-hover-bg), var(--topbar-pill-active-bg)) !important;
border-color: var(--topbar-pill-active-border) !important;
color: v-bind(theme) !important;
box-shadow: none !important;
transform: none !important;
}
#app .topbar-menu.el-menu--horizontal > div > a > .el-menu-item .svg-icon,
#app .topbar-menu.el-menu--horizontal > div > .el-sub-menu > .el-sub-menu__title .svg-icon,
#app .topbar-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title .svg-icon {
width: 14px;
height: 14px;
margin-right: 0 !important;
flex-shrink: 0;
}
#app .topbar-menu.el-menu--horizontal > div > a > .el-menu-item span,
#app .topbar-menu.el-menu--horizontal > div > .el-sub-menu > .el-sub-menu__title span,
#app .topbar-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title span {
color: inherit !important;
font-size: 13px;
font-weight: 500;
letter-spacing: 0.01em;
}
.topbar-menu.el-menu--horizontal > div > a > .el-menu-item.is-active span,
.topbar-menu.el-menu--horizontal > div > .el-sub-menu.is-active > .el-sub-menu__title span,
.topbar-menu.el-menu--horizontal > .el-sub-menu.is-active > .el-sub-menu__title span {
font-weight: 500;
}
/* topbar more arrow */
.topbar-menu .el-sub-menu .el-sub-menu__icon-arrow {
position: static;
margin-left: 8px;
margin-top: 0px;
margin: 0 0 0 2px;
display: block !important;
color: inherit;
font-size: 11px;
}
/* menu__title el-menu-item */
.topbar-menu.el-menu--horizontal .el-sub-menu__title, .topbar-menu.el-menu--horizontal .el-menu-item {
height: 60px;
.topbar-menu.el-menu--horizontal > div > a > .el-menu-item::after,
.topbar-menu.el-menu--horizontal > div > .el-sub-menu > .el-sub-menu__title::after,
.topbar-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title::after {
display: none !important;
}
.topbar-menu.el-menu--horizontal > .el-sub-menu.el-sub-menu__hide-arrow > .el-sub-menu__title {
color: var(--topbar-pill-muted) !important;
padding-right: 16px !important;
}
html.dark .topbar-menu.el-menu--horizontal {
--topbar-pill-bg: rgba(30, 41, 59, 0.64);
--topbar-pill-hover-bg: rgba(51, 65, 85, 0.88);
--topbar-pill-active-bg: rgba(51, 65, 85, 0.78);
--topbar-pill-border: rgba(71, 85, 105, 0.28);
--topbar-pill-active-border: rgba(71, 85, 105, 0.32);
}
html.dark #app .topbar-menu.el-menu--horizontal > div > a > .el-menu-item.is-active,
html.dark #app .topbar-menu.el-menu--horizontal > div > .el-sub-menu.is-active > .el-sub-menu__title,
html.dark #app .topbar-menu.el-menu--horizontal > .el-sub-menu.is-active > .el-sub-menu__title {
box-shadow: none !important;
}
.topbar-menu-popper.el-popper {
border: none !important;
border-radius: 18px !important;
background: transparent !important;
box-shadow: none !important;
padding: 0 !important;
}
.topbar-menu-popper.el-popper .el-popper__arrow {
display: none !important;
}
.topbar-menu-popper .el-menu--popup {
min-width: 188px;
padding: 10px !important;
border: 1px solid rgba(148, 163, 184, 0.14);
border-radius: 18px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(248, 250, 252, 0.94)) !important;
box-shadow:
0 18px 40px rgba(15, 23, 42, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.72);
}
.topbar-menu-popper .el-menu--popup .el-menu-item,
.topbar-menu-popper .el-menu--popup .el-sub-menu__title {
display: flex;
align-items: center;
height: 38px;
line-height: 38px;
padding: 0 14px !important;
margin: 0 0 4px !important;
border-radius: 12px;
color: var(--app-text-title) !important;
background: transparent !important;
transition:
background-color 0.2s ease,
color 0.2s ease,
box-shadow 0.2s ease,
transform 0.2s ease;
}
.topbar-menu-popper .el-menu--popup .el-menu-item:last-child,
.topbar-menu-popper .el-menu--popup .el-sub-menu:last-child > .el-sub-menu__title {
margin-bottom: 0 !important;
}
.topbar-menu-popper .el-menu--popup .el-menu-item:hover,
.topbar-menu-popper .el-menu--popup .el-sub-menu__title:hover {
background: rgba(64, 158, 255, 0.08) !important;
color: v-bind(theme) !important;
transform: translateX(1px);
}
.topbar-menu-popper .el-menu--popup .el-menu-item.is-active,
.topbar-menu-popper .el-menu--popup .el-sub-menu.is-active > .el-sub-menu__title {
background: linear-gradient(180deg, rgba(64, 158, 255, 0.16), rgba(64, 158, 255, 0.1)) !important;
color: v-bind(theme) !important;
box-shadow: inset 0 0 0 1px rgba(64, 158, 255, 0.12);
}
.topbar-menu-popper .el-menu--popup .svg-icon {
width: 14px;
height: 14px;
margin-right: 10px !important;
}
.topbar-menu-popper .el-menu--popup .menu-title,
.topbar-menu-popper .el-menu--popup .el-sub-menu__title > span,
.topbar-menu-popper .el-menu--popup .el-menu-item > span {
display: inline-flex;
align-items: center;
font-size: 13px;
font-weight: 500;
letter-spacing: 0.01em;
}
.topbar-menu-popper .el-menu--popup .el-sub-menu__icon-arrow {
position: static;
margin-left: auto;
margin-top: 0;
font-size: 11px;
align-self: center;
}
html.dark .topbar-menu-popper .el-menu--popup {
border-color: rgba(71, 85, 105, 0.34);
background: linear-gradient(180deg, rgba(15, 23, 42, 0.98), rgba(30, 41, 59, 0.94)) !important;
box-shadow:
0 20px 42px rgba(0, 0, 0, 0.34),
inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
html.dark .topbar-menu-popper .el-menu--popup .el-menu-item,
html.dark .topbar-menu-popper .el-menu--popup .el-sub-menu__title {
color: #e5edf8 !important;
}
html.dark .topbar-menu-popper .el-menu--popup .el-menu-item:hover,
html.dark .topbar-menu-popper .el-menu--popup .el-sub-menu__title:hover {
background: rgba(96, 165, 250, 0.14) !important;
}
html.dark .topbar-menu-popper .el-menu--popup .el-menu-item.is-active,
html.dark .topbar-menu-popper .el-menu--popup .el-sub-menu.is-active > .el-sub-menu__title {
background: linear-gradient(180deg, rgba(37, 99, 235, 0.28), rgba(59, 130, 246, 0.18)) !important;
box-shadow: inset 0 0 0 1px rgba(96, 165, 250, 0.16);
}
</style>
+3
View File
@@ -145,6 +145,9 @@ defineExpose({
left: 50%;
transform: translateX(-50%);
top: -53vh;
padding: 0;
border-top: none;
background: transparent;
}
}
:deep(.el-autocomplete) {
+55 -13
View File
@@ -66,59 +66,101 @@ onMounted(() => {
<style lang="scss" scoped>
.layout-navbars-breadcrumb-user-news {
display: flex;
flex-direction: column;
min-width: 0;
.head-box {
display: flex;
border-bottom: 1px solid var(--el-border-color-lighter);
border-bottom: 1px solid rgba(148, 163, 184, 0.14);
box-sizing: border-box;
color: var(--el-text-color-primary);
color: var(--app-text-title);
justify-content: space-between;
height: 35px;
height: 40px;
align-items: center;
padding: 0 2px 10px;
.head-box-title {
font-size: 14px;
font-weight: 600;
}
.head-box-btn {
color: var(--el-color-primary);
font-size: 13px;
color: var(--app-accent-strong);
font-size: 12px;
font-weight: 600;
cursor: pointer;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
.content-box {
height: 300px;
overflow: auto;
font-size: 13px;
padding: 8px 0;
.content-box-item {
padding-top: 12px;
display: flex;
&:last-of-type {
padding-bottom: 12px;
gap: 10px;
align-items: flex-start;
padding: 12px;
margin: 4px 0;
border-radius: 12px;
cursor: pointer;
transition:
background-color 0.2s ease,
transform 0.2s ease;
&:hover {
background: var(--app-accent-soft);
transform: translateY(-1px);
}
.content-box-msg {
color: var(--el-text-color-secondary);
margin-top: 5px;
margin-bottom: 5px;
}
.content-box-time {
color: var(--el-text-color-secondary);
color: var(--app-text-muted);
font-size: 12px;
}
.item-conten {
width: 100%;
display: flex;
flex-direction: column;
gap: 6px;
color: var(--app-text-title);
line-height: 1.6;
}
.read {
flex-shrink: 0;
margin-top: 2px;
}
}
}
.foot-box {
height: 35px;
color: var(--el-color-primary);
font-size: 13px;
height: 40px;
color: var(--app-accent-strong);
font-size: 12px;
font-weight: 600;
cursor: pointer;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid var(--el-border-color-lighter);
border-top: 1px solid rgba(148, 163, 184, 0.14);
margin-top: 4px;
&:hover {
opacity: 1;
}
+19 -18
View File
@@ -1,21 +1,13 @@
<template>
<div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<side-bar v-if="!sidebar.hide" class="sidebar-container" />
<side-bar v-if="showSidebar" class="sidebar-container" />
<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> -->
<div :class="{ 'fixed-header': fixedHeader }">
<navbar ref="navbarRef" @set-layout="setLayout" />
<div :class="{ 'fixed-header': fixedHeader }" class="layout-header">
<navbar @set-layout="setLayout" />
<tags-view v-if="needTagsView" />
</div>
<app-main />
<app-main :class="{ 'with-fixed-header': fixedHeader, 'with-tags-view': needTagsView }" />
<settings ref="settingRef" />
</div>
</div>
@@ -66,7 +58,6 @@ watchEffect(() => {
}
});
const navbarRef = ref<InstanceType<typeof Navbar>>();
const settingRef = ref<InstanceType<typeof Settings>>();
onMounted(() => {
@@ -96,6 +87,7 @@ const setLayout = () => {
position: relative;
height: 100%;
width: 100%;
background: var(--app-shell-bg);
&.mobile.openSidebar {
position: fixed;
@@ -105,7 +97,7 @@ const setLayout = () => {
.drawer-bg {
background: #000;
opacity: 0.3;
opacity: 0.4;
width: 100%;
top: 0;
height: 100%;
@@ -113,18 +105,26 @@ const setLayout = () => {
z-index: 999;
}
.layout-header {
position: relative;
z-index: 9;
display: flex;
flex-direction: column;
gap: 3px;
padding: 12px 12px 0;
background: transparent;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$base-sidebar-width});
width: calc(100% - #{$base-sidebar-width} - 12px);
transition: width 0.28s;
background: $fixed-header-bg;
}
.hideSidebar .fixed-header {
width: calc(100% - 54px);
width: calc(100% - 70px);
}
.sidebarHide .fixed-header {
@@ -133,5 +133,6 @@ const setLayout = () => {
.mobile .fixed-header {
width: 100%;
top: 0;
}
</style>
+1 -1
View File
@@ -71,7 +71,7 @@ export default {
loadingInstance = ElLoading.service({
lock: true,
text: content,
background: 'rgba(0, 0, 0, 0.7)'
background: 'var(--app-overlay-mask)'
});
},
// 关闭遮罩层
+2 -2
View File
@@ -31,7 +31,7 @@ const setting: DefaultSettings = {
/**
*
*/
tagsIcon: false,
tagsIcon: true,
/**
*
@@ -46,7 +46,7 @@ const setting: DefaultSettings = {
/**
*
*/
dynamicTitle: false,
dynamicTitle: true,
/**
*
+20 -22
View File
@@ -1,9 +1,13 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell demo-demo-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="key键" prop="testKey">
<el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -17,30 +21,24 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<h3>测试单列表</h3>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['demo:demo:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['demo:demo:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['demo:demo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['demo:demo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
<el-button v-hasPermi="['demo:demo:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="demoList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="demoList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="true" label="主键" align="center" prop="id" />
<el-table-column label="部门id" align="center" prop="deptId" />
+19 -14
View File
@@ -1,9 +1,13 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell demo-tree-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="树节点名" prop="treeName">
<el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -14,23 +18,24 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<h3>测试树列表</h3>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['demo:tree:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table
ref="treeTableRef"
v-loading="loading"
class="data-table"
:data="treeList"
row-key="id"
border
+349 -140
View File
@@ -1,100 +1,101 @@
<template>
<div class="app-container home">
<el-row :gutter="20">
<el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>RuoYi-Vue-Plus后台管理系统</h2>
<p>
RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 分布式集群 场景升级(不兼容原框架)
<br />
* 前端开发框架 Vue3TSElement Plus<br />
* 后端开发框架 Spring Boot<br />
* 容器框架 Undertow 基于 Netty 的高性能容器<br />
* 权限认证框架 Sa-Token 支持多终端认证系统<br />
* 关系数据库 MySQL 适配 8.X 最低 5.7<br />
* 缓存数据库 Redis 适配 6.X 最低 4.X<br />
* 数据库框架 Mybatis-Plus 快速 CRUD 增加开发效率<br />
* 数据库框架 p6spy 更强劲的 SQL 分析<br />
* 多数据源框架 dynamic-datasource 支持主从与多种类数据库异构<br />
* 序列化框架 Jackson 统一使用 jackson 高效可靠<br />
* Redis客户端 Redisson 性能强劲API丰富<br />
* 分布式限流 Redisson 全局请求IP集群ID 多种限流<br />
* 分布式锁 Lock4j 注解锁工具锁 多种多样<br />
* 分布式幂等 Lock4j 基于分布式锁实现<br />
* 分布式链路追踪 SkyWalking 支持链路追踪网格分析度量聚合可视化<br />
* 分布式任务调度 SnailJob 高性能 高可靠 易扩展<br />
* 文件存储 Minio 本地存储<br />
* 文件存储 七牛阿里腾讯 云存储<br />
* 监控框架 SpringBoot-Admin 全方位服务监控<br />
* 校验框架 Validation 增强接口安全性 严谨性<br />
* Excel框架 FastExcel(原Alibaba EasyExcel) 性能优异 扩展性强<br />
* 文档框架 SpringDocjavadoc 无注解零入侵基于java注释<br />
* 工具类框架 HutoolLombok 减少代码冗余 增加安全性<br />
* 代码生成器 适配MPSpringDoc规范化代码 一键生成前后端代码<br />
* 部署方式 Docker 容器编排 一键部署业务集群<br />
* 国际化 SpringMessage Spring标准国际化方案<br />
</p>
<p><b>当前版本:</b> <span>v5.5.3</span></p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>
<p>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">访问码云</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Vue-Plus')">访问GitHub</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')"
>更新日志</el-button
>
</p>
</el-col>
<div class="home">
<section class="hero-panel">
<div class="hero-copy">
<h1>RuoYi-Vue-Plus 控制台</h1>
<p>企业级后台管理系统 重写 RuoYi-Vue 所有功能 集成 Sa-TokenMybatis-PlusWarmFlowSpringDocHutoolOSS 等组件</p>
<div class="hero-actions">
<el-button type="primary" @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">查看源码</el-button>
<el-button plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')">更新日志</el-button>
</div>
</div>
</section>
<el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>RuoYi-Cloud-Plus微服务后台管理系统</h2>
<p>
RuoYi-Cloud-Plus 微服务通用权限管理系统 重写 RuoYi-Cloud 全方位升级(不兼容原框架)
<br />
* 前端开发框架 Vue3TSElement UI<br />
* 后端开发框架 Spring Boot<br />
* 微服务开发框架 Spring CloudSpring Cloud Alibaba<br />
* 容器框架 Undertow 基于 XNIO 的高性能容器<br />
* 权限认证框架 Sa-TokenJwt 支持多终端认证系统<br />
* 关系数据库 MySQL 适配 8.X 最低 5.7<br />
* 关系数据库 Oracle 适配 11g 12c<br />
* 关系数据库 PostgreSQL 适配 13 14<br />
* 关系数据库 SQLServer 适配 2017 2019<br />
* 缓存数据库 Redis 适配 6.X 最低 5.X<br />
* 分布式注册中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
* 分布式配置中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
* 服务网关 Spring Cloud Gateway 响应式高性能网关<br />
* 负载均衡 Spring Cloud Loadbalancer 负载均衡处理<br />
* RPC远程调用 Apache Dubbo 原生态使用体验高性能<br />
* 分布式限流熔断 Alibaba Sentinel 无侵入高扩展<br />
* 分布式事务 Alibaba Seata 无侵入高扩展 支持 四种模式<br />
* 分布式消息队列 Apache Kafka 高性能高速度<br />
* 分布式消息队列 Apache RocketMQ 高可用功能多样<br />
* 分布式消息队列 RabbitMQ 支持各种扩展插件功能多样性<br />
* 分布式搜索引擎 ElasticSearch 业界知名<br />
* 分布式链路追踪 Apache SkyWalking 链路追踪网格分析度量聚合可视化<br />
* 分布式日志中心 ELK 业界成熟解决方案<br />
* 分布式监控 PrometheusGrafana 全方位性能监控<br />
* 其余与 Vue 版本一致<br />
</p>
<p><b>当前版本:</b> <span>v2.5.3</span></p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>
<p>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Cloud-Plus')">访问码云</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Cloud-Plus')">访问GitHub</el-button>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-cloud-plus/changlog')"
>更新日志</el-button
>
</p>
</el-col>
</el-row>
<el-divider />
<div class="content-grid">
<section class="section-card">
<div class="section-head">
<div>
<h2>项目矩阵</h2>
</div>
</div>
<div class="product-list">
<article v-for="product in products" :key="product.name" class="product-card">
<div class="product-top">
<div>
<h3>{{ product.name }}</h3>
<p>{{ product.summary }}</p>
</div>
<span class="product-version">{{ product.version }}</span>
</div>
<div class="product-tags">
<el-tag v-for="tag in product.tags" :key="tag" effect="plain">{{ tag }}</el-tag>
</div>
<div class="product-actions">
<el-button type="primary" plain @click="goTarget(product.primaryUrl)">{{ product.primaryLabel }}</el-button>
<el-button plain @click="goTarget(product.secondaryUrl)">{{ product.secondaryLabel }}</el-button>
</div>
</article>
</div>
</section>
<section class="section-card capability-card">
<div class="section-head">
<div>
<h2>能力地图</h2>
</div>
</div>
<div class="capability-groups">
<article v-for="group in capabilityGroups" :key="group.title" class="capability-group">
<h3>{{ group.title }}</h3>
<ul>
<li v-for="item in group.items" :key="item">{{ item }}</li>
</ul>
</article>
</div>
</section>
</div>
</div>
</template>
<script setup name="Index" lang="ts">
const products = [
{
name: 'RuoYi-Vue-Plus',
version: 'v5.5.3',
summary: '面向分布式集群场景的后台管理系统,保持现有业务接口与权限逻辑,适合先完成前端壳升级。',
tags: ['Vue 3', 'Element Plus', 'Spring Boot', 'Sa-Token'],
primaryLabel: '访问 GitHub',
primaryUrl: 'https://github.com/dromara/RuoYi-Vue-Plus',
secondaryLabel: '查看更新日志',
secondaryUrl: 'https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog'
},
{
name: 'RuoYi-Cloud-Plus',
version: 'v2.5.3',
summary: '微服务通用权限管理系统,适合更复杂的服务治理场景,也可以沿用同样的前端升级思路。',
tags: ['Spring Cloud', 'Gateway', 'Nacos', 'Dubbo'],
primaryLabel: '访问 GitHub',
primaryUrl: 'https://github.com/dromara/RuoYi-Cloud-Plus',
secondaryLabel: '查看文档',
secondaryUrl: 'https://plus-doc.dromara.org/#/ruoyi-cloud-plus/changlog'
}
];
const capabilityGroups = [
{
title: '后端基建',
items: ['Spring Boot / Spring Cloud', 'Sa-Token 认证与权限', 'MySQL / Redis', '代码生成器与国际化']
},
{
title: '平台能力',
items: ['动态菜单与按钮权限', '监控、日志、在线用户', '任务调度与工作流', '文件存储与多云适配']
},
{
title: '前端方向',
items: ['UI 壳层重构', '主题与布局统一', '通用页面容器规范化', '逐模块迁移业务页']
}
];
const goTarget = (url: string) => {
window.open(url, '__blank');
};
@@ -102,64 +103,272 @@ const goTarget = (url: string) => {
<style lang="scss" scoped>
.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;
}
display: flex;
flex-direction: column;
gap: 20px;
}
ul {
padding: 0;
.hero-panel,
.section-card {
border-radius: 28px;
border: 1px solid var(--app-surface-border);
background: var(--app-surface-bg);
box-shadow: var(--app-shadow-sm);
backdrop-filter: blur(18px);
}
.hero-panel {
display: grid;
grid-template-columns: minmax(0, 1.3fr) minmax(280px, 0.7fr);
gap: 18px;
padding: 30px;
background:
radial-gradient(circle at top left, rgba(53, 109, 255, 0.16), transparent 30%),
linear-gradient(135deg, rgba(255, 255, 255, 0.68), rgba(255, 255, 255, 0.42));
}
.hero-copy {
display: flex;
flex-direction: column;
gap: 14px;
h1 {
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;
font-size: clamp(30px, 4vw, 46px);
line-height: 1.06;
letter-spacing: -0.04em;
color: var(--app-text-title);
}
p {
margin-top: 10px;
margin: 0;
max-width: 760px;
color: var(--app-text-muted);
font-size: 15px;
line-height: 1.8;
}
}
b {
font-weight: 700;
}
.hero-badge {
display: inline-flex;
width: fit-content;
padding: 8px 14px;
border-radius: 999px;
background: rgba(53, 109, 255, 0.12);
color: var(--app-accent-strong);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 8px;
}
.hero-stats {
display: grid;
gap: 12px;
}
.stat-card {
padding: 18px 20px;
border-radius: 22px;
background: rgba(255, 255, 255, 0.54);
border: 1px solid rgba(148, 163, 184, 0.14);
display: flex;
flex-direction: column;
gap: 6px;
strong {
color: var(--app-text-title);
font-size: 24px;
letter-spacing: -0.03em;
}
.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;
span {
color: var(--app-text-muted);
font-size: 13px;
}
}
.content-grid {
display: grid;
grid-template-columns: minmax(0, 1.15fr) minmax(300px, 0.85fr);
gap: 20px;
}
.section-card {
padding: 24px;
}
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 18px;
h2 {
margin: 6px 0 0;
color: var(--app-text-title);
font-size: 26px;
letter-spacing: -0.03em;
}
}
.section-kicker {
color: var(--app-text-muted);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.product-list,
.capability-groups {
display: grid;
gap: 16px;
}
.product-card {
padding: 22px;
border-radius: 24px;
background: rgba(255, 255, 255, 0.5);
border: 1px solid rgba(148, 163, 184, 0.14);
transition:
transform 0.25s ease,
box-shadow 0.25s ease,
border-color 0.25s ease;
&:hover {
transform: translateY(-2px);
box-shadow: var(--app-shadow-sm);
border-color: rgba(53, 109, 255, 0.2);
}
}
.product-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
h3 {
margin: 0 0 8px;
color: var(--app-text-title);
font-size: 22px;
letter-spacing: -0.03em;
}
p {
margin: 0;
color: var(--app-text-muted);
line-height: 1.8;
}
}
.product-version {
flex-shrink: 0;
padding: 8px 12px;
border-radius: 999px;
background: rgba(53, 109, 255, 0.12);
color: var(--app-accent-strong);
font-size: 12px;
font-weight: 700;
}
.product-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 18px;
}
.product-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 18px;
}
.capability-card {
background: radial-gradient(circle at top right, rgba(14, 165, 233, 0.12), transparent 28%), var(--app-surface-bg);
}
.capability-group {
padding: 18px 18px 18px 20px;
border-radius: 22px;
background: rgba(255, 255, 255, 0.46);
border: 1px solid rgba(148, 163, 184, 0.14);
h3 {
margin: 0 0 12px;
color: var(--app-text-title);
font-size: 18px;
}
ul {
margin: 0;
padding: 0;
list-style: none;
display: grid;
gap: 10px;
}
li {
position: relative;
padding-left: 16px;
color: var(--app-text-muted);
line-height: 1.7;
&::before {
content: '';
position: absolute;
left: 0;
top: 10px;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--app-accent-strong);
box-shadow: 0 0 0 5px rgba(53, 109, 255, 0.12);
}
}
}
@media (max-width: 960px) {
.hero-panel,
.content-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.hero-panel,
.section-card {
padding: 20px;
border-radius: 22px;
}
.product-top {
flex-direction: column;
}
}
:global(html.dark) {
.hero-panel {
background:
radial-gradient(circle at top left, rgba(53, 109, 255, 0.18), transparent 30%),
linear-gradient(135deg, rgba(15, 23, 42, 0.76), rgba(15, 23, 42, 0.48));
}
.stat-card,
.product-card,
.capability-group {
background: rgba(15, 23, 42, 0.5);
border-color: rgba(100, 116, 139, 0.16);
}
}
</style>
+372 -154
View File
@@ -1,73 +1,99 @@
<template>
<div class="login">
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<div class="title-box">
<h3 class="title">{{ title }}</h3>
<lang-select />
</div>
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('login.username')">
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
size="large"
auto-complete="off"
:placeholder="proxy.$t('login.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 v-if="captchaEnabled" prop="code">
<el-input
v-model="loginForm.code"
size="large"
auto-complete="off"
:placeholder="proxy.$t('login.code')"
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" class="login-code-img" @click="getCode" />
<div class="login-shell">
<section class="login-brand">
<span class="brand-pill">Plus UI Workspace</span>
<h1 class="brand-title">企业级后台管理系统</h1>
<p class="brand-desc">
真正面向企业级的应用框架 组件化 模块化 轻耦合 高扩展 针对企业痛点 业界一流技术栈<br />
重写 RuoYi-Vue 所有功能 集成 Sa-TokenMybatis-PlusWarmFlowSpringDocHutoolOSS 定期同步
</p>
<div class="brand-highlights">
<span v-for="item in highlights" :key="item" class="highlight-chip">{{ item }}</span>
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">{{ proxy.$t('login.rememberPassword') }}</el-checkbox>
<el-form-item style="float: right">
<el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">
<svg-icon icon-class="wechat" />
</el-button>
<el-button circle :title="proxy.$t('login.social.maxkey')" @click="doSocialLogin('maxkey')">
<svg-icon icon-class="maxkey" />
</el-button>
<el-button circle :title="proxy.$t('login.social.topiam')" @click="doSocialLogin('topiam')">
<svg-icon icon-class="topiam" />
</el-button>
<el-button circle :title="proxy.$t('login.social.gitee')" @click="doSocialLogin('gitee')">
<svg-icon icon-class="gitee" />
</el-button>
<el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">
<svg-icon icon-class="github" />
</el-button>
</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">{{ proxy.$t('login.login') }}</span>
<span v-else>{{ proxy.$t('login.logging') }}</span>
</el-button>
<div v-if="register" style="float: right">
<router-link class="link-type" :to="'/register'">{{ proxy.$t('login.switchRegisterPage') }}</router-link>
<div class="brand-metrics">
<article v-for="item in quickStats" :key="item.label" class="metric-card">
<strong>{{ item.value }}</strong>
<span>{{ item.label }}</span>
</article>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
</section>
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<div class="title-box">
<div>
<p class="eyebrow">Workspace Sign In</p>
<h3 class="title">{{ title }}</h3>
<p class="subtitle">使用当前账号体系登录到业务工作台</p>
</div>
<lang-select />
</div>
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('login.username')">
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
size="large"
auto-complete="off"
:placeholder="proxy.$t('login.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 v-if="captchaEnabled" prop="code" class="captcha-row">
<el-input v-model="loginForm.code" size="large" auto-complete="off" :placeholder="proxy.$t('login.code')" @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" class="login-code-img" @click="getCode" />
</div>
</el-form-item>
<div class="form-meta">
<el-checkbox v-model="loginForm.rememberMe">{{ proxy.$t('login.rememberPassword') }}</el-checkbox>
<router-link v-if="register" class="link-type" :to="'/register'">{{ proxy.$t('login.switchRegisterPage') }}</router-link>
</div>
<div class="social-panel">
<span class="social-label">第三方登录</span>
<div class="social-actions">
<el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">
<svg-icon icon-class="wechat" />
</el-button>
<el-button circle :title="proxy.$t('login.social.maxkey')" @click="doSocialLogin('maxkey')">
<svg-icon icon-class="maxkey" />
</el-button>
<el-button circle :title="proxy.$t('login.social.topiam')" @click="doSocialLogin('topiam')">
<svg-icon icon-class="topiam" />
</el-button>
<el-button circle :title="proxy.$t('login.social.gitee')" @click="doSocialLogin('gitee')">
<svg-icon icon-class="gitee" />
</el-button>
<el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">
<svg-icon icon-class="github" />
</el-button>
</div>
</div>
<el-form-item class="submit-row">
<el-button :loading="loading" size="large" type="primary" class="submit-button" @click.prevent="handleLogin">
<span v-if="!loading">{{ proxy.$t('login.login') }}</span>
<span v-else>{{ proxy.$t('login.logging') }}</span>
</el-button>
</el-form-item>
</el-form>
</div>
<div class="el-login-footer">
<span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>
<span>Copyright © 2018-{{ currentYear }} 疯狂的狮子Li All Rights Reserved.</span>
</div>
</div>
</template>
@@ -84,6 +110,13 @@ import { useI18n } from 'vue-i18n';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const title = import.meta.env.VITE_APP_TITLE;
const currentYear = new Date().getFullYear();
const quickStats = [
{ label: '细粒度权限管理', value: '动态权限控制' },
{ label: '主流技术栈', value: '全栈技术集成' },
{ label: 'UI样式', value: '卡片式' }
];
const highlights = ['技术栈全面升级', '动态菜单', '多主题布局', '深浅色主题'];
const userStore = useUserStore();
const router = useRouter();
const { t } = useI18n();
@@ -104,10 +137,8 @@ const loginRules: ElFormRules = {
const codeUrl = ref('');
const loading = ref(false);
//
const captchaEnabled = ref(true);
//
const register = ref(false);
const register = ref(true);
const redirect = ref('/');
const loginRef = ref<ElFormInstance>();
@@ -123,18 +154,15 @@ const handleLogin = () => {
loginRef.value?.validate(async (valid: boolean, fields: any) => {
if (valid) {
loading.value = true;
// localStorage
if (loginForm.value.rememberMe) {
localStorage.setItem('username', String(loginForm.value.username));
localStorage.setItem('password', String(loginForm.value.password));
localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
} else {
//
localStorage.removeItem('username');
localStorage.removeItem('password');
localStorage.removeItem('rememberMe');
}
// action
const [err] = await to(userStore.login(loginForm.value));
if (!err) {
const redirectUrl = redirect.value || '/';
@@ -142,7 +170,6 @@ const handleLogin = () => {
loading.value = false;
} else {
loading.value = false;
//
if (captchaEnabled.value) {
await getCode();
}
@@ -153,15 +180,11 @@ const handleLogin = () => {
});
};
/**
* 获取验证码
*/
const getCode = async () => {
const res = await getCodeImg();
const { data } = res;
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
if (captchaEnabled.value) {
//
loginForm.value.code = '';
codeUrl.value = 'data:image/gif;base64,' + data.img;
loginForm.value.uuid = data.uuid;
@@ -179,14 +202,9 @@ const getLoginData = () => {
} as LoginData;
};
/**
* 第三方登录
* @param type
*/
const doSocialLogin = (type: string) => {
authRouterUrl(type).then((res: any) => {
if (res.code === HttpStatus.SUCCESS) {
//
window.location.href = res.data;
} else {
ElMessage.error(res.msg);
@@ -202,105 +220,266 @@ onMounted(() => {
<style lang="scss" scoped>
.login {
min-height: 100%;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url('../assets/images/login-background.jpg');
background-size: cover;
background-position: center;
justify-content: center;
padding: 40px 24px 88px;
background:
radial-gradient(circle at 12% 12%, rgba(53, 109, 255, 0.22), transparent 24%),
radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.18), transparent 24%), linear-gradient(135deg, #071120 0%, #0f1b33 42%, #15345f 100%);
}
.title-box {
display: flex;
align-items: center;
gap: 8px;
.login-shell {
width: min(1180px, 100%);
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(360px, 440px);
gap: 26px;
align-items: stretch;
}
.title {
margin: 0px auto 26px auto;
text-align: center;
color: var(--el-text-color-primary);
font-weight: 600;
letter-spacing: 0.5px;
.login-brand,
.login-form {
border-radius: 28px;
border: 1px solid rgba(255, 255, 255, 0.14);
box-shadow: 0 30px 80px rgba(2, 8, 23, 0.32);
backdrop-filter: blur(18px);
}
.login-brand {
padding: 42px;
display: flex;
flex-direction: column;
justify-content: space-between;
color: #eef4ff;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)),
linear-gradient(135deg, rgba(53, 109, 255, 0.32), rgba(15, 23, 42, 0.24));
}
.brand-pill {
display: inline-flex;
align-items: center;
width: fit-content;
padding: 8px 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.9);
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.brand-title {
margin: 22px 0 14px;
font-size: clamp(34px, 4vw, 52px);
line-height: 1.08;
letter-spacing: -0.03em;
}
.brand-desc {
margin: 0;
max-width: 580px;
color: rgba(226, 232, 240, 0.88);
font-size: 15px;
line-height: 1.8;
}
.brand-highlights {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 28px 0 34px;
}
.highlight-chip {
padding: 9px 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
color: #f8fbff;
font-size: 13px;
}
.brand-metrics {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.metric-card {
padding: 18px 16px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
flex-direction: column;
gap: 6px;
strong {
font-size: 24px;
color: #fff;
}
:deep(.lang-select--style) {
line-height: 0;
color: var(--el-text-color-secondary);
span {
color: rgba(226, 232, 240, 0.72);
font-size: 13px;
}
}
.login-form {
border-radius: var(--app-radius-lg);
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(255, 255, 255, 0.5);
width: min(420px, 90vw);
padding: 32px 30px 12px 30px;
width: 100%;
padding: 34px 30px 26px;
z-index: 1;
box-shadow: var(--app-shadow-lg);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
.el-input {
height: 40px;
input {
height: 40px;
}
background: rgba(248, 250, 252, 0.92);
}
.title-box {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 28px;
.eyebrow {
margin: 0 0 8px;
color: var(--app-accent-strong);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 0px;
.title {
margin: 0;
color: var(--app-text-title);
font-weight: 700;
font-size: 30px;
letter-spacing: -0.03em;
}
.subtitle {
margin: 8px 0 0;
color: var(--app-text-muted);
font-size: 14px;
line-height: 1.7;
}
:deep(.lang-select--style) {
line-height: 0;
color: var(--app-text-muted);
padding: 10px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.72);
border: 1px solid rgba(148, 163, 184, 0.14);
}
}
.login-tip {
.login-form .el-input {
height: 48px;
}
.login-form .input-icon {
height: 46px;
width: 14px;
margin-left: 0;
}
.captcha-row {
:deep(.el-form-item__content) {
display: grid;
grid-template-columns: minmax(0, 1fr) 122px;
gap: 12px;
}
}
.form-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin: -2px 0 18px;
}
.social-panel {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 14px 16px;
margin-bottom: 22px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.58);
border: 1px solid rgba(148, 163, 184, 0.14);
}
.social-label {
color: var(--app-text-muted);
font-size: 13px;
text-align: center;
color: #bfbfbf;
font-weight: 600;
}
.social-actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
}
.submit-row {
margin-bottom: 0;
}
.submit-button {
width: 100%;
height: 50px;
border-radius: 16px;
box-shadow: 0 18px 34px rgba(53, 109, 255, 0.22);
}
.login-form :deep(.el-input__wrapper) {
background-color: rgba(255, 255, 255, 0.9);
min-height: 48px;
background-color: rgba(255, 255, 255, 0.88);
border-radius: 16px;
box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.12) inset;
}
.login-form :deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
box-shadow:
0 0 0 1px rgba(53, 109, 255, 0.24) inset,
0 0 0 4px rgba(53, 109, 255, 0.12);
}
.login-form :deep(.el-button--primary) {
border-radius: var(--app-radius-md);
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.25);
.login-form :deep(.el-checkbox__label) {
color: var(--app-text-muted);
}
.login-form :deep(.el-button.is-circle) {
background: rgba(15, 23, 42, 0.04);
border: 1px solid rgba(15, 23, 42, 0.08);
color: var(--el-text-color-regular);
background: rgba(255, 255, 255, 0.82);
border: 1px solid rgba(148, 163, 184, 0.16);
color: var(--app-text-muted);
}
.login-form :deep(.el-button.is-circle:hover) {
background: rgba(59, 130, 246, 0.1);
border-color: rgba(59, 130, 246, 0.2);
background: rgba(53, 109, 255, 0.12);
border-color: rgba(53, 109, 255, 0.2);
color: var(--app-accent-strong);
}
.login-code {
width: calc(37% - 10px);
height: 40px;
float: right;
margin-left: 10px;
height: 48px;
box-sizing: border-box;
border-radius: var(--app-radius-sm);
border-radius: 16px;
overflow: hidden;
background: rgba(255, 255, 255, 0.9);
border: 1px solid var(--el-border-color-light);
background: rgba(255, 255, 255, 0.88);
border: 1px solid rgba(148, 163, 184, 0.14);
img {
cursor: pointer;
vertical-align: middle;
display: block;
width: 100%;
height: 40px;
height: 100%;
object-fit: cover;
}
}
@@ -312,35 +491,74 @@ onMounted(() => {
bottom: 0;
width: 100%;
text-align: center;
color: rgba(255, 255, 255, 0.75);
font-family: Arial, serif;
color: rgba(226, 232, 240, 0.68);
font-size: 12px;
letter-spacing: 1px;
letter-spacing: 0.08em;
}
.login-code-img {
height: 40px;
height: 48px;
padding-left: 0;
}
@media (max-width: 960px) {
.login {
padding: 24px 14px 80px;
}
.login-shell {
grid-template-columns: 1fr;
}
.login-brand {
padding: 28px 24px;
}
.brand-metrics {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.login-brand {
display: none;
}
.login-form {
padding: 26px 18px 20px;
}
.title-box {
flex-direction: column;
}
.social-panel {
flex-direction: column;
align-items: flex-start;
}
.social-actions {
justify-content: flex-start;
}
.captcha-row :deep(.el-form-item__content) {
grid-template-columns: 1fr;
}
}
:global(html.dark) {
.login-form {
background: rgba(17, 24, 39, 0.9);
border-color: rgba(148, 163, 184, 0.2);
}
.login-form :deep(.el-input__wrapper) {
background-color: rgba(17, 24, 39, 0.7);
background: rgba(12, 18, 30, 0.88);
border-color: rgba(100, 116, 139, 0.2);
}
.login-form :deep(.el-input__wrapper),
.login-code,
.title-box :deep(.lang-select--style),
.social-panel,
.login-form :deep(.el-button.is-circle) {
background: rgba(148, 163, 184, 0.12);
border-color: rgba(148, 163, 184, 0.25);
color: #e5e7eb;
}
.el-login-footer {
color: rgba(226, 232, 240, 0.65);
background: rgba(15, 23, 42, 0.56);
border-color: rgba(100, 116, 139, 0.2);
}
}
</style>
+13 -2
View File
@@ -1,9 +1,20 @@
<template>
<div>
<i-frame v-model:src="url"></i-frame>
<div class="p-2 page-shell iframe-page">
<div class="iframe-page__inner">
<i-frame v-model:src="url"></i-frame>
</div>
</div>
</template>
<script setup lang="ts">
const url = ref(import.meta.env.VITE_APP_MONITOR_ADMIN);
</script>
<style lang="scss" scoped>
.iframe-page__inner {
overflow: hidden;
border-radius: 14px;
background: var(--app-surface-bg);
box-shadow: var(--app-shadow-sm);
}
</style>
+49 -16
View File
@@ -1,14 +1,18 @@
<template>
<div class="p-2">
<el-row :gutter="10">
<el-col :span="24" class="card-box">
<el-card shadow="hover">
<div class="p-2 page-shell monitor-cache-page">
<el-row :gutter="12" class="cache-grid">
<el-col :span="24">
<el-card shadow="hover" class="table-panel">
<template #header>
<Monitor style="width: 1em; height: 1em; vertical-align: middle" />
<span style="vertical-align: middle">基本信息</span>
<div class="toolbar-shell">
<div class="table-heading">
<h3>缓存概览</h3>
<p>Redis 运行状态资源消耗与实时统计</p>
</div>
</div>
</template>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div class="el-table el-table--enable-row-hover el-table--medium cache-table">
<table style="width: 100%">
<tbody>
<tr>
@@ -97,24 +101,31 @@
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card shadow="hover">
<el-col :xs="24" :lg="12">
<el-card shadow="hover" class="table-panel">
<template #header>
<PieChart style="width: 1em; height: 1em; vertical-align: middle" />
<span style="vertical-align: middle">命令统计</span>
<div class="toolbar-shell">
<div class="table-heading">
<h3>命令统计</h3>
</div>
</div>
</template>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div class="el-table el-table--enable-row-hover el-table--medium cache-chart">
<div ref="commandstats" style="height: 420px" />
</div>
</el-card>
</el-col>
<el-col :span="12" class="card-box">
<el-card shadow="hover">
<el-col :xs="24" :lg="12">
<el-card shadow="hover" class="table-panel">
<template #header>
<Odometer style="width: 1em; height: 1em; vertical-align: middle" /> <span style="vertical-align: middle">内存信息</span>
<div class="toolbar-shell">
<div class="table-heading">
<h3>内存信息</h3>
</div>
</div>
</template>
<div class="el-table el-table--enable-row-hover el-table--medium">
<div class="el-table el-table--enable-row-hover el-table--medium cache-chart">
<div ref="usedmemory" style="height: 420px" />
</div>
</el-card>
@@ -190,3 +201,25 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.cache-grid {
margin: 0 !important;
row-gap: 12px;
}
.cache-table {
overflow: hidden;
border: 1px solid var(--app-surface-border);
border-radius: 10px;
}
.cache-table table {
border-collapse: collapse;
}
.cache-chart {
overflow: hidden;
border-radius: 10px;
}
</style>
+46 -18
View File
@@ -1,9 +1,16 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell monitor-logininfo-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="登录地址" prop="ipaddr">
<el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -33,35 +40,34 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Access Logs</span>
<h3>登录日志</h3>
<p> {{ total }} 条记录支持排序批量清理导出和账号解锁</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['monitor:logininfo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:logininfo:remove']" type="danger" plain icon="Delete" @click="handleClean">清空</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:logininfo:unlock']" type="primary" plain icon="Unlock" :disabled="single" @click="handleUnlock">
解锁
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:logininfo:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table
ref="loginInfoTableRef"
v-loading="loading"
:data="loginInfoList"
class="data-table"
:default-sort="defaultSort"
border
@selection-change="handleSelectionChange"
@@ -207,3 +213,25 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+39 -5
View File
@@ -1,8 +1,16 @@
<template>
<div class="p-2">
<div class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell monitor-online-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel">
<template #header>
<div class="panel-heading">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="登录地址" prop="ipaddr">
<el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -16,10 +24,20 @@
</el-form>
</el-card>
</div>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Online Sessions</span>
<h3>在线用户</h3>
<p> {{ total }} 条记录支持按账号或地址检索并执行会话强退</p>
</div>
</div>
</template>
<el-table
v-loading="loading"
border
class="data-table"
:data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)"
style="width: 100%"
>
@@ -115,3 +133,19 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
</style>
+46 -16
View File
@@ -1,9 +1,16 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell monitor-operlog-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="操作地址" prop="operIp">
<el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -41,30 +48,31 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Operation Logs</span>
<h3>操作日志</h3>
<p> {{ total }} 条记录支持类型过滤详情查看批量清空和导出</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="WarnTriangleFilled" @click="handleClean">清空</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['monitor:operlog:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table
ref="operLogTableRef"
v-loading="loading"
:data="operlogList"
class="data-table"
border
:default-sort="defaultSort"
@selection-change="handleSelectionChange"
@@ -259,3 +267,25 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+13 -2
View File
@@ -1,9 +1,20 @@
<template>
<div>
<i-frame v-model:src="url"></i-frame>
<div class="p-2 page-shell iframe-page">
<div class="iframe-page__inner">
<i-frame v-model:src="url"></i-frame>
</div>
</div>
</template>
<script setup lang="ts">
const url = ref(import.meta.env.VITE_APP_SNAILJOB_ADMIN);
</script>
<style lang="scss" scoped>
.iframe-page__inner {
overflow: hidden;
border-radius: 14px;
background: var(--app-surface-bg);
box-shadow: var(--app-shadow-sm);
}
</style>
+343 -94
View File
@@ -1,67 +1,88 @@
<template>
<div class="register">
<el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
<div class="title-box">
<h3 class="title">{{ title }}</h3>
<lang-select />
</div>
<el-form-item prop="username">
<el-input v-model="registerForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('register.username')">
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="registerForm.password"
type="password"
size="large"
auto-complete="off"
:placeholder="proxy.$t('register.password')"
@keyup.enter="handleRegister"
>
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
size="large"
auto-complete="off"
:placeholder="proxy.$t('register.confirmPassword')"
@keyup.enter="handleRegister"
>
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item v-if="captchaEnabled" prop="code">
<el-input
v-model="registerForm.code"
size="large"
auto-complete="off"
:placeholder="proxy.$t('register.code')"
style="width: 63%"
@keyup.enter="handleRegister"
>
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
</el-input>
<div class="register-code">
<img :src="codeUrl" class="register-code-img" @click="getCode" />
<div class="register-shell">
<section class="register-brand">
<span class="brand-pill">Plus UI Workspace</span>
<h1 class="brand-title">企业级后台管理系统</h1>
<p class="brand-desc">
真正面向企业级的应用框架 组件化 模块化 轻耦合 高扩展 针对企业痛点 业界一流技术栈<br />
重写 RuoYi-Vue 所有功能 集成 Sa-TokenMybatis-PlusWarmFlowSpringDocHutoolOSS 定期同步
</p>
<div class="brand-highlights">
<span v-for="item in highlights" :key="item" class="highlight-chip">{{ item }}</span>
</div>
</el-form-item>
<el-form-item style="width: 100%">
<el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleRegister">
<span v-if="!loading">{{ proxy.$t('register.register') }}</span>
<span v-else>{{ proxy.$t('register.registering') }}</span>
</el-button>
<div style="float: right">
<div class="brand-metrics">
<article v-for="item in quickStats" :key="item.label" class="metric-card">
<strong>{{ item.value }}</strong>
<span>{{ item.label }}</span>
</article>
</div>
</section>
<el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
<div class="title-box">
<div>
<p class="eyebrow">Workspace Register</p>
<h3 class="title">{{ title }}</h3>
<p class="subtitle">创建新的业务工作台账号接入当前系统权限与登录体系</p>
</div>
<lang-select />
</div>
<el-form-item prop="username">
<el-input v-model="registerForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('register.username')">
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="registerForm.password"
type="password"
size="large"
auto-complete="off"
:placeholder="proxy.$t('register.password')"
@keyup.enter="handleRegister"
>
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
size="large"
auto-complete="off"
:placeholder="proxy.$t('register.confirmPassword')"
@keyup.enter="handleRegister"
>
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item v-if="captchaEnabled" prop="code" class="captcha-row">
<el-input v-model="registerForm.code" size="large" auto-complete="off" :placeholder="proxy.$t('register.code')" @keyup.enter="handleRegister">
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
</el-input>
<div class="register-code">
<img :src="codeUrl" class="register-code-img" @click="getCode" />
</div>
</el-form-item>
<div class="form-meta">
<span class="register-tip">注册后将返回登录页继续完成认证</span>
<router-link class="link-type" :to="'/login'">{{ proxy.$t('register.switchLoginPage') }}</router-link>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<el-form-item class="submit-row">
<el-button :loading="loading" size="large" type="primary" class="submit-button" @click.prevent="handleRegister">
<span v-if="!loading">{{ proxy.$t('register.register') }}</span>
<span v-else>{{ proxy.$t('register.registering') }}</span>
</el-button>
</el-form-item>
</el-form>
</div>
<div class="el-register-footer">
<span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>
<span>Copyright © 2018-{{ currentYear }} 疯狂的狮子Li All Rights Reserved.</span>
</div>
</div>
</template>
@@ -75,6 +96,13 @@ import { useI18n } from 'vue-i18n';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const title = import.meta.env.VITE_APP_TITLE;
const currentYear = new Date().getFullYear();
const quickStats = [
{ label: '细粒度权限管理', value: '动态权限控制' },
{ label: '主流技术栈', value: '全栈技术集成' },
{ label: 'UI样式', value: '卡片式' }
];
const highlights = ['技术栈全面升级', '动态菜单', '多主题布局', '深浅色主题'];
const router = useRouter();
const { t } = useI18n();
@@ -157,67 +185,234 @@ onMounted(() => {
<style lang="scss" scoped>
.register {
min-height: 100%;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url('../assets/images/login-background.jpg');
background-size: cover;
justify-content: center;
padding: 40px 24px 88px;
background:
radial-gradient(circle at 12% 12%, rgba(53, 109, 255, 0.22), transparent 24%),
radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.18), transparent 24%), linear-gradient(135deg, #071120 0%, #0f1b33 42%, #15345f 100%);
}
.title-box {
display: flex;
.register-shell {
width: min(1180px, 100%);
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(360px, 440px);
gap: 26px;
align-items: stretch;
}
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
.register-brand,
.register-form {
border-radius: 28px;
border: 1px solid rgba(255, 255, 255, 0.14);
box-shadow: 0 30px 80px rgba(2, 8, 23, 0.32);
backdrop-filter: blur(18px);
}
.register-brand {
padding: 42px;
display: flex;
flex-direction: column;
justify-content: space-between;
color: #eef4ff;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)),
linear-gradient(135deg, rgba(53, 109, 255, 0.32), rgba(15, 23, 42, 0.24));
}
.brand-pill {
display: inline-flex;
align-items: center;
width: fit-content;
padding: 8px 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.9);
font-size: 12px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.brand-title {
margin: 22px 0 14px;
font-size: clamp(34px, 4vw, 52px);
line-height: 1.08;
letter-spacing: -0.03em;
}
.brand-desc {
margin: 0;
max-width: 580px;
color: rgba(226, 232, 240, 0.88);
font-size: 15px;
line-height: 1.8;
}
.brand-highlights {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 28px 0 34px;
}
.highlight-chip {
padding: 9px 14px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
color: #f8fbff;
font-size: 13px;
}
.brand-metrics {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.metric-card {
padding: 18px 16px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
flex-direction: column;
gap: 6px;
strong {
font-size: 24px;
color: #fff;
}
:deep(.lang-select--style) {
line-height: 0;
color: #7483a3;
span {
color: rgba(226, 232, 240, 0.72);
font-size: 13px;
}
}
.register-form {
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
width: 100%;
padding: 34px 30px 26px;
z-index: 1;
background: rgba(248, 250, 252, 0.92);
}
.el-input {
height: 40px;
.title-box {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 28px;
input {
height: 40px;
}
.eyebrow {
margin: 0 0 8px;
color: var(--app-accent-strong);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 0;
.title {
margin: 0;
color: var(--app-text-title);
font-weight: 700;
font-size: 30px;
letter-spacing: -0.03em;
}
.subtitle {
margin: 8px 0 0;
color: var(--app-text-muted);
font-size: 14px;
line-height: 1.7;
}
:deep(.lang-select--style) {
line-height: 0;
color: var(--app-text-muted);
padding: 10px;
border-radius: 14px;
background: rgba(255, 255, 255, 0.72);
border: 1px solid rgba(148, 163, 184, 0.14);
}
}
.register-form .el-input {
height: 48px;
}
.register-form .input-icon {
height: 46px;
width: 14px;
margin-left: 0;
}
.captcha-row {
:deep(.el-form-item__content) {
display: grid;
grid-template-columns: minmax(0, 1fr) 122px;
gap: 12px;
}
}
.form-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin: -2px 0 18px;
}
.register-tip {
color: var(--app-text-muted);
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.register-code {
width: 33%;
height: 40px;
float: right;
height: 48px;
box-sizing: border-box;
border-radius: 16px;
overflow: hidden;
background: rgba(255, 255, 255, 0.88);
border: 1px solid rgba(148, 163, 184, 0.14);
img {
cursor: pointer;
vertical-align: middle;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
}
.submit-row {
margin-bottom: 0;
}
.submit-button {
width: 100%;
height: 50px;
border-radius: 16px;
box-shadow: 0 18px 34px rgba(53, 109, 255, 0.22);
}
.register-form :deep(.el-input__wrapper) {
min-height: 48px;
background-color: rgba(255, 255, 255, 0.88);
border-radius: 16px;
box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.12) inset;
}
.register-form :deep(.el-input__wrapper.is-focus) {
box-shadow:
0 0 0 1px rgba(53, 109, 255, 0.24) inset,
0 0 0 4px rgba(53, 109, 255, 0.12);
}
.el-register-footer {
height: 40px;
line-height: 40px;
@@ -225,14 +420,68 @@ onMounted(() => {
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial, serif;
color: rgba(226, 232, 240, 0.68);
font-size: 12px;
letter-spacing: 1px;
letter-spacing: 0.08em;
}
.register-code-img {
height: 40px;
padding-left: 12px;
height: 48px;
padding-left: 0;
}
@media (max-width: 960px) {
.register {
padding: 24px 14px 80px;
}
.register-shell {
grid-template-columns: 1fr;
}
.register-brand {
padding: 28px 24px;
}
.brand-metrics {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.register-brand {
display: none;
}
.register-form {
padding: 26px 18px 20px;
}
.title-box {
flex-direction: column;
}
.form-meta {
flex-direction: column;
align-items: flex-start;
}
.captcha-row :deep(.el-form-item__content) {
grid-template-columns: 1fr;
}
}
:global(html.dark) {
.register-form {
background: rgba(12, 18, 30, 0.88);
border-color: rgba(100, 116, 139, 0.2);
}
.register-form :deep(.el-input__wrapper),
.register-code,
.title-box :deep(.lang-select--style) {
background: rgba(15, 23, 42, 0.56);
border-color: rgba(100, 116, 139, 0.2);
}
}
</style>
+36 -34
View File
@@ -1,51 +1,53 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="85px">
<el-form-item label="客户端key" prop="clientKey">
<el-input v-model="queryParams.clientKey" placeholder="请输入客户端key" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="客户端秘钥" prop="clientSecret">
<el-input v-model="queryParams.clientSecret" placeholder="请输入客户端秘钥" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="p-2 page-shell system-client-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="85px" class="query-form">
<el-form-item label="客户端key" prop="clientKey">
<el-input v-model="queryParams.clientKey" placeholder="请输入客户端key" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="客户端秘钥" prop="clientSecret">
<el-input v-model="queryParams.clientSecret" placeholder="请输入客户端秘钥" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<h3>客户端列表</h3>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['system:client:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:client:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" :data="clientList" border @selection-change="handleSelectionChange">
<el-table v-loading="loading" :data="clientList" border class="data-table" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="true" label="id" align="center" prop="id" />
<el-table-column label="客户端id" align="center" prop="clientId" />
+47 -21
View File
@@ -1,9 +1,16 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell system-config-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="参数名称" prop="configName">
<el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -33,34 +40,31 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Config Dataset</span>
<h3>参数列表</h3>
<p> {{ total }} 条记录支持键值维护导出和缓存刷新</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['system:config:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="configList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="configList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="参数主键" align="center" prop="configId" />
<el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
@@ -259,3 +263,25 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+47 -15
View File
@@ -1,9 +1,16 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell system-dept-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="部门名称" prop="deptName">
<el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -22,24 +29,27 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:dept:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
</el-col>
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Department Dataset</span>
<h3>部门列表</h3>
<p>支持树形层级维护负责人绑定和部门状态管理</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['system:dept:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table
ref="deptTableRef"
v-loading="loading"
class="data-table"
:data="deptList"
row-key="deptId"
border
@@ -318,3 +328,25 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+71 -67
View File
@@ -1,35 +1,32 @@
<template>
<div class="p-2 dict-page">
<div class="p-2 page-shell dict-page">
<el-row :gutter="16" class="dict-grid">
<!-- 字典类型 -->
<el-col :xs="24" :lg="12">
<el-card shadow="hover" class="dict-card">
<el-card shadow="hover" class="dict-card table-panel">
<template #header>
<div class="dict-card__header">
<div class="dict-card__title">字典管理</div>
<right-toolbar v-model:show-search="showTypeSearch" @query-table="getTypeList" />
<div class="toolbar-shell dict-card__header">
<div class="table-heading">
<div class="panel-heading search-panel-toggle dict-title-toggle" @click.stop="showTypeSearch = !showTypeSearch">
<div>
<h3>字典管理</h3>
</div>
</div>
</div>
<div class="toolbar-actions">
<right-toolbar v-model:show-search="showTypeSearch" :search="false" @query-table="getTypeList" />
</div>
</div>
</template>
<div v-show="showTypeSearch" class="dict-form-scroll">
<el-form ref="typeQueryFormRef" :model="typeQueryParams" :inline="true">
<div class="dict-search" :class="{ 'is-collapsed': !showTypeSearch }">
<el-form ref="typeQueryFormRef" :model="typeQueryParams" :inline="true" class="query-form">
<el-form-item label="字典名称" prop="dictName">
<el-input v-model="typeQueryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleTypeQuery" />
</el-form-item>
<el-form-item label="字典类型" prop="dictType">
<el-input v-model="typeQueryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleTypeQuery" />
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleTypeQuery">搜索</el-button>
<el-button icon="Refresh" @click="handleTypeResetQuery">重置</el-button>
@@ -37,7 +34,7 @@
</el-form>
</div>
<div class="dict-actions">
<div class="toolbar-actions dict-actions">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleTypeAdd">新增</el-button>
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="typeSingle" @click="handleTypeUpdate()"
>修改</el-button
@@ -54,6 +51,7 @@
ref="typeTableRef"
v-loading="typeLoading"
border
class="data-table"
:data="typeList"
highlight-current-row
@row-click="handleTypeRowClick"
@@ -98,19 +96,25 @@
<!-- 字典数据 -->
<el-col :xs="24" :lg="12">
<el-card shadow="hover" class="dict-card">
<el-card shadow="hover" class="dict-card table-panel">
<template #header>
<div class="dict-card__header">
<div class="dict-card__title">
字典数据
<span class="dict-card__subtitle">{{ currentDictLabel }}</span>
<div class="toolbar-shell dict-card__header">
<div class="table-heading">
<div class="panel-heading search-panel-toggle dict-title-toggle" @click.stop="showDataSearch = !showDataSearch">
<div>
<h3>字典数据</h3>
<p v-if="hasCurrentDict" class="dict-card__subtitle">{{ currentDictLabel }}</p>
</div>
</div>
</div>
<div class="toolbar-actions">
<right-toolbar v-model:show-search="showDataSearch" :search="false" @query-table="getDataList" />
</div>
<right-toolbar v-model:show-search="showDataSearch" @query-table="getDataList" />
</div>
</template>
<div v-show="showDataSearch" class="dict-form-scroll">
<el-form ref="dataQueryFormRef" :model="dataQueryParams" :inline="true">
<div class="dict-search" :class="{ 'is-collapsed': !showDataSearch }">
<el-form ref="dataQueryFormRef" :model="dataQueryParams" :inline="true" class="query-form">
<el-form-item label="字典标签" prop="dictLabel">
<el-input
v-model="dataQueryParams.dictLabel"
@@ -127,7 +131,7 @@
</el-form>
</div>
<div class="dict-actions">
<div class="toolbar-actions dict-actions">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" :disabled="!hasCurrentDict" @click="handleDataAdd"
>新增</el-button
>
@@ -155,10 +159,10 @@
</div>
<div class="dict-table-wrap">
<el-table v-loading="dataLoading" border :data="dataList" @selection-change="handleDataSelectionChange">
<el-table v-loading="dataLoading" border class="data-table" :data="dataList" @selection-change="handleDataSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
<el-table-column label="字典标签" align="center" prop="dictLabel" width="80">
<el-table-column label="字典标签" align="center" prop="dictLabel" width="100">
<template #default="scope">
<span
v-if="
@@ -174,7 +178,7 @@
>
</template>
</el-table-column>
<el-table-column label="字典键值" align="center" prop="dictValue" width="80" />
<el-table-column label="字典键值" align="center" prop="dictValue" width="100" />
<el-table-column label="字典排序" align="center" prop="dictSort" width="80" />
<el-table-column label="备注" align="center" prop="remark" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
@@ -213,13 +217,15 @@
<el-input v-model="typeForm.dictName" placeholder="请输入字典名称" />
</el-form-item>
<el-form-item prop="dictType">
<template #label>
<span>
<el-tooltip content="数据存储中的Key值,如:sys_user_sex" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
字典类型
</span>
</template>
<el-input v-model="typeForm.dictType" placeholder="请输入字典类型" maxlength="100" />
<span slot="label">
<el-tooltip content="数据存储中的Key值,如:sys_user_sex" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
字典类型
</span>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="typeForm.remark" type="textarea" placeholder="请输入内容"></el-input>
@@ -291,7 +297,6 @@ const typeIds = ref<Array<number | string>>([]);
const typeSingle = ref(true);
const typeMultiple = ref(true);
const typeTotal = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const typeFormRef = ref<ElFormInstance>();
const typeQueryFormRef = ref<ElFormInstance>();
@@ -387,7 +392,7 @@ const { queryParams: dataQueryParams, form: dataForm, rules: dataRules } = toRef
const getTypeList = () => {
typeLoading.value = true;
listType(proxy?.addDateRange(typeQueryParams.value, dateRange.value)).then((res) => {
listType(typeQueryParams.value).then((res) => {
typeList.value = res.rows;
typeTotal.value = res.total;
typeLoading.value = false;
@@ -438,7 +443,6 @@ const handleTypeQuery = () => {
};
const handleTypeResetQuery = () => {
dateRange.value = ['', ''];
typeQueryFormRef.value?.resetFields();
handleTypeQuery();
};
@@ -614,54 +618,55 @@ onMounted(() => {
row-gap: 16px;
}
.dict-card__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
.dict-card {
height: 100%;
}
.dict-card__title {
display: inline-flex;
align-items: baseline;
gap: 8px;
font-weight: 600;
.dict-card__header {
gap: 12px;
}
.dict-title-toggle {
padding: 0 !important;
}
.dict-card__subtitle {
margin: 4px 0 0;
font-size: 12px;
color: var(--el-text-color-secondary);
}
.dict-form-scroll {
max-height: 200px;
overflow: auto;
.dict-search {
overflow: hidden;
max-height: 240px;
opacity: 1;
margin-bottom: 12px;
padding-right: 6px;
padding-bottom: 4px;
transition:
max-height 0.32s cubic-bezier(0.22, 1, 0.36, 1),
opacity 0.24s ease,
margin-bottom 0.24s ease;
}
.dict-form-scroll :deep(.el-form) {
display: flex;
flex-wrap: wrap;
column-gap: 12px;
row-gap: 10px;
.dict-search.is-collapsed {
max-height: 0;
opacity: 0;
margin-bottom: 0;
pointer-events: none;
}
.dict-form-scroll :deep(.el-form-item) {
margin: 0;
.dict-search :deep(.el-form-item) {
margin-bottom: 0;
}
.dict-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 8px 0 12px;
margin: 0 0 12px;
padding-bottom: 0;
}
.dict-actions :deep(.el-button) {
height: 32px;
padding: 0 14px;
border-radius: 10px !important;
}
.dict-actions :deep(.el-button + .el-button) {
@@ -671,5 +676,4 @@ onMounted(() => {
.dict-table-wrap {
overflow-x: auto;
}
</style>
+46 -14
View File
@@ -1,9 +1,16 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell system-menu-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="菜单名称" prop="menuName">
<el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -19,24 +26,27 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Menu Dataset</span>
<h3>菜单列表</h3>
<p>支持树形加载图标选择级联删除和目录/菜单/按钮三级维护</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:menu:remove']" type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table
ref="menuTableRef"
v-loading="loading"
class="data-table"
:data="menuList"
row-key="menuId"
border
@@ -532,6 +542,28 @@ onMounted(() => {
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
<style scoped lang="scss">
.tree-border {
height: 300px;
+49 -20
View File
@@ -1,9 +1,16 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell system-notice-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="queryParams.noticeTitle" placeholder="请输入公告标题" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -22,29 +29,29 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Notice Dataset</span>
<h3>公告列表</h3>
<p> {{ total }} 条记录支持类型筛选内容编辑和状态管理</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['system:notice:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:notice:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:notice:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
<el-button v-hasPermi="['system:notice:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="noticeList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="noticeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="序号" align="center" prop="noticeId" width="100" />
<el-table-column label="公告标题" align="center" prop="noticeTitle" :show-overflow-tooltip="true" />
@@ -241,3 +248,25 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+49 -20
View File
@@ -1,9 +1,16 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell system-oss-config-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="配置key" prop="configKey">
<el-input v-model="queryParams.configKey" placeholder="配置key" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -23,29 +30,29 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Storage Config</span>
<h3>OSS 配置</h3>
<p> {{ total }} 条记录支持默认桶切换权限策略维护和站点配置</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['system:ossConfig:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
<el-button v-hasPermi="['system:ossConfig:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="ossConfigList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="ossConfigList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="columns[0].visible" label="主建" align="center" prop="ossConfigId" />
<el-table-column v-if="columns[1].visible" label="配置key" align="center" prop="configKey" />
@@ -342,3 +349,25 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+49 -24
View File
@@ -1,9 +1,16 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell system-oss-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="文件名" prop="fileName">
<el-input v-model="queryParams.fileName" placeholder="请输入文件名" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -34,42 +41,40 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Object Storage</span>
<h3>文件列表</h3>
<p> {{ total }} 条记录支持文件上传预览切换和 OSS 配置跳转</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['system:oss:upload']" type="primary" plain icon="Upload" @click="handleFile">上传文件</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:oss:upload']" type="primary" plain icon="Upload" @click="handleImage">上传图片</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:oss:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['system:oss:edit']"
:type="previewListResource ? 'danger' : 'warning'"
plain
@click="handlePreviewListResource(!previewListResource)"
>预览开关 : {{ previewListResource ? '禁用' : '启用' }}</el-button
>
</el-col>
<el-col :span="1.5">
预览开关 : {{ previewListResource ? '禁用' : '启用' }}
</el-button>
<el-button v-hasPermi="['system:ossConfig:list']" type="info" plain icon="Operation" @click="handleOssConfig">配置管理</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table
v-if="showTable"
v-loading="loading"
:data="ossList"
class="data-table"
border
:header-cell-class-name="handleHeaderClass"
@selection-change="handleSelectionChange"
@@ -307,12 +312,10 @@ const handleDownload = (row: OssVO) => {
};
/** 预览开关按钮 */
const handlePreviewListResource = async (preview: boolean) => {
const text = preview ? '启用' : '停用';
try {
await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?');
await proxy?.updateConfigByKey('sys.oss.previewListResource', preview);
await getList();
proxy?.$modal.msgSuccess(text + '成功');
proxy?.$modal.msgSuccess((preview ? '启用' : '停用') + '成功');
} catch {
return;
}
@@ -331,3 +334,25 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+76 -27
View File
@@ -1,13 +1,21 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<div class="p-2 page-shell system-post-page">
<el-row :gutter="20" class="content-grid">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-col :lg="5" :xs="24">
<el-card shadow="hover" class="side-panel">
<template #header>
<div class="panel-heading">
<div>
<span class="panel-kicker">Department Filter</span>
<h3>部门结构</h3>
</div>
</div>
</template>
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree
ref="deptTreeRef"
class="mt-2"
class="mt-2 dept-tree"
node-key="id"
:data="deptOptions"
:props="{ label: 'label', children: 'children' } as any"
@@ -19,11 +27,18 @@
/>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-col :lg="19" :xs="24" class="content-main">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="岗位编码" prop="postCode">
<el-input v-model="queryParams.postCode" placeholder="请输入岗位编码" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -61,30 +76,28 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Post Dataset</span>
<h3>岗位列表</h3>
<p> {{ total }} 条记录支持按部门筛选岗位维护和导出</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['system:post:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改
</el-button>
<el-button v-hasPermi="['system:post:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="postList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="postList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="岗位编号" align="center" prop="postId" />
<el-table-column label="岗位编码" align="center" prop="postCode" />
@@ -359,3 +372,39 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.content-grid {
margin: 0 !important;
}
.content-main {
display: flex;
flex-direction: column;
gap: 12px;
}
.dept-tree {
padding-top: 6px;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+21 -17
View File
@@ -1,8 +1,13 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell role-auth-user-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -13,27 +18,26 @@
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<h3>已授权用户</h3>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="openSelectUser">添加用户</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:remove']" type="danger" plain icon="CircleClose" :disabled="multiple" @click="cancelAuthUserAll">
批量取消授权
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" :search="true" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
+47 -20
View File
@@ -1,9 +1,16 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="page-shell system-role-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -34,28 +41,26 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Role Dataset</span>
<h3>角色列表</h3>
<p> {{ total }} 条记录支持菜单权限数据权限和分配用户等完整配置流程</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:remove']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:remove']" type="danger" plain :disabled="ids.length === 0" icon="Delete" @click="handleDelete()">删除</el-button>
<el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table ref="roleTableRef" border v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table ref="roleTableRef" border class="data-table" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
@@ -501,3 +506,25 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+52 -33
View File
@@ -1,38 +1,43 @@
<template>
<el-row>
<el-dialog v-model="visible" title="选择用户" width="800px" top="5vh" append-to-body>
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row>
<el-table ref="tableRef" border :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-if="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-row>
<el-dialog v-model="visible" title="选择用户" width="800px" top="5vh" append-to-body class="select-user-dialog">
<div class="page-shell select-user-shell">
<el-card shadow="hover" class="search-panel select-user-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="hover" class="table-panel select-user-card">
<el-table ref="tableRef" border class="data-table" :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-if="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSelectUser"> </el-button>
@@ -128,3 +133,17 @@ defineExpose({
show
});
</script>
<style lang="scss" scoped>
.select-user-shell {
gap: 12px;
}
.select-user-card :deep(.el-card__body) {
padding: 14px 16px !important;
}
.select-user-dialog :deep(.el-dialog__body) {
padding-top: 12px;
}
</style>
+69 -52
View File
@@ -1,57 +1,60 @@
<template>
<div class="p-2">
<div class="panel">
<h4 class="panel-title">基本信息</h4>
<el-form :model="form" :inline="true">
<el-row :gutter="10">
<el-col :span="2.5">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" disabled />
</el-form-item>
</el-col>
<el-col :span="2.5">
<el-form-item label="登录账号" prop="userName">
<el-input v-model="form.userName" disabled />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div class="panel">
<h4 class="panel-title">角色信息</h4>
<div>
<el-table
ref="tableRef"
v-loading="loading"
border
:row-key="getRowKey"
:data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
@row-click="clickRow"
@selection-change="handleSelectionChange"
>
<el-table-column label="序号" width="55" type="index" align="center">
<template #default="scope">
<span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column type="selection" :reserve-selection="true" :selectable="checkSelectable" width="55"></el-table-column>
<el-table-column label="角色编号" align="center" prop="roleId" />
<el-table-column label="角色名称" align="center" prop="roleName" />
<el-table-column label="权限字符" align="center" prop="roleKey" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="pageNum" v-model:limit="pageSize" :total="total" />
<div style="text-align: center; margin-left: -120px; margin-top: 30px">
<el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="close()">返回</el-button>
<div class="p-2 page-shell auth-role-page">
<el-card shadow="hover" class="search-panel auth-role-info">
<template #header>
<div class="panel-heading">
<div><h3>基本信息</h3></div>
</div>
<div></div>
</div>
</div>
</template>
<el-form :model="form" :inline="true" class="query-form auth-role-form">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" disabled />
</el-form-item>
<el-form-item label="登录账号" prop="userName">
<el-input v-model="form.userName" disabled />
</el-form-item>
</el-form>
</el-card>
<el-card shadow="hover" class="table-panel">
<template #header>
<div class="toolbar-shell">
<div class="table-heading">
<h3>角色信息</h3>
</div>
<div class="toolbar-actions">
<el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="close()">返回</el-button>
</div>
</div>
</template>
<el-table
ref="tableRef"
v-loading="loading"
border
class="data-table"
:row-key="getRowKey"
:data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
@row-click="clickRow"
@selection-change="handleSelectionChange"
>
<el-table-column label="序号" width="55" type="index" align="center">
<template #default="scope">
<span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column type="selection" :reserve-selection="true" :selectable="checkSelectable" width="55"></el-table-column>
<el-table-column label="角色编号" align="center" prop="roleId" />
<el-table-column label="角色名称" align="center" prop="roleName" />
<el-table-column label="权限字符" align="center" prop="roleKey" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="pageNum" v-model:limit="pageSize" :total="total" />
</el-card>
</div>
</template>
@@ -144,3 +147,17 @@ onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.auth-role-form :deep(.el-form-item) {
margin-bottom: 0;
}
.auth-role-form :deep(.el-input) {
width: 220px;
}
.auth-role-info :deep(.el-card__body) {
padding-top: 14px !important;
}
</style>
+73 -24
View File
@@ -1,13 +1,21 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<div class="page-shell system-user-page">
<el-row :gutter="20" class="content-grid">
<!-- 部门树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-col :lg="5" :xs="24">
<el-card shadow="hover" class="side-panel">
<template #header>
<div class="panel-heading">
<div>
<span class="panel-kicker">Department Filter</span>
<h3>部门结构</h3>
</div>
</div>
</template>
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree
ref="deptTreeRef"
class="mt-2"
class="mt-2 dept-tree"
node-key="id"
:data="deptOptions"
:props="{ label: 'label', children: 'children' } as any"
@@ -19,11 +27,18 @@
/>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-col :lg="19" :xs="24" class="content-main">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -57,25 +72,23 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">User Dataset</span>
<h3>用户列表</h3>
<p> {{ total }} 条记录支持部门筛选状态切换导入导出和角色分配</p>
</div>
<div class="toolbar-actions">
<el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-has-permi="['system:user:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown class="mt-[1px]">
<el-button plain type="info">
更多
@@ -90,12 +103,12 @@
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
<right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :columns="columns" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" />
<el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
@@ -677,3 +690,39 @@ async function handleDeptChange(value: number | string) {
form.value.postIds = [];
}
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.content-grid {
margin: 0 !important;
}
.content-main {
display: flex;
flex-direction: column;
gap: 12px;
}
.dept-tree {
padding-top: 6px;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+130 -23
View File
@@ -1,54 +1,76 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<div class="p-2 page-shell profile-page">
<el-row :gutter="20" class="profile-grid">
<el-col :span="6" :xs="24">
<el-card class="box-card">
<el-card shadow="hover" class="side-panel profile-summary-card">
<template #header>
<div class="clearfix">
<span>个人信息</span>
<div class="panel-heading">
<div><h3>个人信息</h3></div>
</div>
</template>
<div>
<div class="text-center">
<div class="profile-summary">
<div class="text-center profile-avatar">
<userAvatar />
</div>
<ul class="list-group list-group-striped">
<li class="list-group-item">
<svg-icon icon-class="user" />用户名称
<div class="pull-right">{{ state.user.userName }}</div>
<div class="profile-item-label">
<svg-icon icon-class="user" />
<span>用户名称</span>
</div>
<span class="profile-item-value">{{ state.user.userName || '-' }}</span>
</li>
<li class="list-group-item">
<svg-icon icon-class="phone" />手机号码
<div class="pull-right">{{ state.user.phonenumber }}</div>
<div class="profile-item-label">
<svg-icon icon-class="phone" />
<span>手机号码</span>
</div>
<span class="profile-item-value">{{ state.user.phonenumber || '-' }}</span>
</li>
<li class="list-group-item">
<svg-icon icon-class="email" />用户邮箱
<div class="pull-right">{{ state.user.email }}</div>
<div class="profile-item-label">
<svg-icon icon-class="email" />
<span>用户邮箱</span>
</div>
<span class="profile-item-value">{{ state.user.email || '-' }}</span>
</li>
<li class="list-group-item">
<svg-icon icon-class="tree" />所属部门
<div v-if="state.user.deptName" class="pull-right">{{ state.user.deptName }} / {{ state.postGroup }}</div>
<div class="profile-item-label">
<svg-icon icon-class="tree" />
<span>所属部门</span>
</div>
<span v-if="state.user.deptName" class="profile-item-value">{{ state.user.deptName }} / {{ state.postGroup }}</span>
<span v-else class="profile-item-value">-</span>
</li>
<li class="list-group-item">
<svg-icon icon-class="peoples" />所属角色
<div class="pull-right">{{ state.roleGroup }}</div>
<div class="profile-item-label">
<svg-icon icon-class="peoples" />
<span>所属角色</span>
</div>
<span class="profile-item-value">{{ state.roleGroup || '-' }}</span>
</li>
<li class="list-group-item">
<svg-icon icon-class="date" />创建日期
<div class="pull-right">{{ state.user.createTime }}</div>
<div class="profile-item-label">
<svg-icon icon-class="date" />
<span>创建日期</span>
</div>
<span class="profile-item-value">{{ state.user.createTime || '-' }}</span>
</li>
</ul>
</div>
</el-card>
</el-col>
<el-col :span="18" :xs="24">
<el-card>
<el-card shadow="hover" class="table-panel profile-main-card">
<template #header>
<div class="clearfix">
<span>基本资料</span>
<div class="toolbar-shell">
<div class="table-heading">
<h3>个人设置</h3>
<p>维护个人资料密码第三方应用和在线设备</p>
</div>
</div>
</template>
<el-tabs v-model="activeTab">
<el-tabs v-model="activeTab" class="profile-tabs">
<el-tab-pane label="基本资料" name="userinfo">
<userInfo :user="userForm" />
</el-tab-pane>
@@ -120,3 +142,88 @@ onMounted(() => {
getOnlines();
});
</script>
<style lang="scss" scoped>
.profile-grid {
row-gap: 12px;
}
.profile-summary-card,
.profile-main-card {
height: 100%;
}
.profile-summary {
display: flex;
flex-direction: column;
gap: 18px;
}
.profile-avatar {
margin-bottom: 4px;
}
.profile-item-label {
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 600;
}
.profile-item-value {
max-width: 58%;
text-align: right;
color: var(--el-text-color-secondary);
word-break: break-word;
}
.profile-summary :deep(.list-group-item) {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 0;
}
.profile-tabs :deep(.el-tabs__header) {
margin-bottom: 18px;
}
.profile-tabs :deep(.el-tabs__nav-wrap::after),
.profile-tabs :deep(.el-tabs__active-bar) {
display: none;
}
.profile-tabs :deep(.el-tabs__nav) {
gap: 8px;
}
.profile-tabs :deep(.el-tabs__item) {
display: inline-flex;
align-items: center;
justify-content: center;
height: 36px;
min-width: 88px;
padding: 0 16px;
border-radius: 10px;
color: var(--el-text-color-regular);
}
.profile-tabs :deep(.el-tabs__item:nth-child(2)),
.profile-tabs :deep(.el-tabs__item.is-top:first-child),
.profile-tabs :deep(.el-tabs__item.is-top:last-child) {
padding-left: 16px;
padding-right: 16px;
}
.profile-tabs :deep(.el-tabs__item.is-active) {
background: rgba(53, 109, 255, 0.1);
color: var(--el-color-primary);
}
@media (max-width: 768px) {
.profile-item-value {
max-width: 50%;
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<div>
<el-table :data="devices" border style="width: 100%; height: 100%; font-size: 14px">
<div class="profile-table-wrap">
<el-table :data="devices" border class="data-table profile-device-table">
<el-table-column label="设备类型" align="center">
<template #default="scope">
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
@@ -52,6 +52,12 @@ const handldDelOnline = (row: any) => {
proxy?.$modal.msgError(res.msg);
}
})
.catch(() => {});
.catch(() => {});
};
</script>
<style lang="scss" scoped>
.profile-table-wrap {
width: 100%;
}
</style>
+22 -3
View File
@@ -1,5 +1,5 @@
<template>
<el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
<el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px" class="profile-form">
<el-form-item label="旧密码" prop="oldPassword">
<el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
</el-form-item>
@@ -9,9 +9,9 @@
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password />
</el-form-item>
<el-form-item>
<el-form-item class="profile-form__actions">
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
<el-button @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
@@ -71,3 +71,22 @@ const close = () => {
proxy?.$tab.closePage();
};
</script>
<style lang="scss" scoped>
.profile-form {
max-width: 520px;
}
.profile-form :deep(.el-input__wrapper) {
border-radius: 12px;
}
.profile-form :deep(.el-button) {
border-radius: 10px;
}
.profile-form__actions :deep(.el-form-item__content) {
display: flex;
gap: 8px;
}
</style>
+87 -35
View File
@@ -1,6 +1,6 @@
<template>
<div>
<el-table :data="auths" border style="width: 100%; height: 100%; font-size: 14px">
<div class="profile-auth">
<el-table :data="auths" border class="data-table profile-auth-table">
<el-table-column label="序号" width="50" type="index" />
<el-table-column label="绑定账号平台" width="140" align="center" prop="source" show-overflow-tooltip />
<el-table-column label="头像" width="120" align="center" prop="avatar">
@@ -12,43 +12,48 @@
<el-table-column label="绑定时间" width="180" align="center" prop="createTime" />
<el-table-column label="操作" width="80" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="text" @click="unlockAuth(scope.row)">解绑</el-button>
<el-tooltip content="解绑" placement="top">
<el-button link type="primary" icon="CircleClose" @click="unlockAuth(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div id="git-user-binding">
<h4 class="provider-desc">你可以绑定以下第三方帐号</h4>
<div id="authlist" class="user-bind">
<div class="provider-section">
<div class="provider-heading">
<h4 class="provider-desc">可绑定的第三方应用</h4>
<p>点击下方平台完成账号绑定</p>
</div>
<div class="user-bind">
<a class="third-app" href="#" title="使用 微信 账号授权登录" @click="authUrl('wechat')">
<div class="git-other-login-icon">
<div class="third-app__icon">
<svg-icon icon-class="wechat" />
</div>
<span class="app-name">WeiXin</span>
<span class="app-name">微信</span>
</a>
<a class="third-app" href="#" title="使用 MaxKey 账号授权登录" @click="authUrl('maxkey')">
<div class="git-other-login-icon">
<div class="third-app__icon">
<svg-icon icon-class="maxkey" />
</div>
<span class="app-name">MaxKey</span>
</a>
<a class="third-app" href="#" title="使用 TopIam 账号授权登录" @click="authUrl('topiam')">
<div class="git-other-login-icon">
<div class="third-app__icon">
<svg-icon icon-class="topiam" />
</div>
<span class="app-name">TopIam</span>
</a>
<a class="third-app" href="#" title="使用 Gitee 账号授权登录" @click="authUrl('gitee')">
<div class="git-other-login-icon">
<div class="third-app__icon">
<svg-icon icon-class="gitee" />
</div>
<span class="app-name">Gitee</span>
</a>
<a class="third-app" href="#" title="使用 GitHub 账号授权登录" @click="authUrl('github')">
<div class="git-other-login-icon">
<div class="third-app__icon">
<svg-icon icon-class="github" />
</div>
<span class="app-name">Github</span>
<span class="app-name">GitHub</span>
</a>
</div>
</div>
@@ -94,44 +99,77 @@ const authUrl = (source: string) => {
</script>
<style lang="scss" scoped>
.user-bind .third-app {
display: -webkit-box;
display: -ms-flexbox;
.profile-auth {
display: flex;
flex-direction: column;
gap: 18px;
}
.profile-auth-table {
width: 100%;
}
.provider-section {
display: flex;
flex-direction: column;
gap: 14px;
}
.provider-heading p {
margin: 6px 0 0;
color: var(--el-text-color-secondary);
}
.user-bind .third-app {
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
min-width: 80px;
float: left;
justify-content: center;
gap: 8px;
min-height: 84px;
padding: 12px 10px;
border: 1px solid var(--app-surface-border);
border-radius: 12px;
background: rgba(248, 250, 252, 0.82);
transition:
transform 0.2s ease,
border-color 0.2s ease,
box-shadow 0.2s ease;
}
.user-bind {
font-size: 1rem;
text-align: start;
height: 50px;
margin-top: 10px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(96px, 108px));
gap: 10px;
justify-content: start;
}
.git-other-login-icon > img {
height: 32px;
.third-app:hover {
transform: translateY(-1px);
border-color: rgba(53, 109, 255, 0.3);
box-shadow: var(--app-shadow-sm);
}
.third-app__icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
font-size: 18px;
}
a {
text-decoration: none;
cursor: pointer;
color: #005980;
color: inherit;
}
.provider-desc {
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Liberation Sans',
'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', 'Wenquanyi Micro Hei', 'WenQuanYi Zen Hei', 'ST Heiti', SimHei, SimSun,
'WenQuanYi Zen Hei Sharp', sans-serif;
font-size: 1.071rem;
margin: 0;
font-size: 16px;
}
td > img {
@@ -141,4 +179,18 @@ td > img {
border-radius: 50%;
margin-right: 5px;
}
.app-name {
font-weight: 600;
font-size: 13px;
line-height: 1.2;
text-align: center;
word-break: break-word;
}
:global(html.dark) {
.user-bind .third-app {
background: rgba(15, 23, 42, 0.64);
}
}
</style>
+26 -1
View File
@@ -159,7 +159,18 @@ const closeDialog = () => {
.user-info-head {
position: relative;
display: inline-block;
width: 120px;
height: 120px;
cursor: pointer;
}
.user-info-head :deep(.img-circle) {
width: 120px;
height: 120px;
object-fit: cover;
border-radius: 50%;
border: 4px solid rgba(255, 255, 255, 0.92);
box-shadow: var(--app-shadow-sm);
}
.user-info-head:hover:after {
@@ -176,7 +187,21 @@ const closeDialog = () => {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
line-height: 110px;
line-height: 120px;
border-radius: 50%;
}
.avatar-upload-preview {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
border-radius: 18px;
background: rgba(248, 250, 252, 0.9);
}
.avatar-upload-preview img {
max-width: 100%;
}
</style>
+26 -3
View File
@@ -1,5 +1,5 @@
<template>
<el-form ref="userRef" :model="userForm" :rules="rules" label-width="80px">
<el-form ref="userRef" :model="userForm" :rules="rules" label-width="80px" class="profile-form">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="userForm.nickName" maxlength="30" />
</el-form-item>
@@ -15,9 +15,9 @@
<el-radio value="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-form-item class="profile-form__actions">
<el-button type="primary" @click="submit">保存</el-button>
<el-button type="danger" @click="close">关闭</el-button>
<el-button @click="close">关闭</el-button>
</el-form-item>
</el-form>
</template>
@@ -67,3 +67,26 @@ const close = () => {
proxy?.$tab.closePage();
};
</script>
<style lang="scss" scoped>
.profile-form {
max-width: 520px;
}
.profile-form :deep(.el-input__wrapper) {
border-radius: 12px;
}
.profile-form :deep(.el-radio-group) {
gap: 16px;
}
.profile-form :deep(.el-button) {
border-radius: 10px;
}
.profile-form__actions :deep(.el-form-item__content) {
display: flex;
gap: 8px;
}
</style>
+44 -19
View File
@@ -1,9 +1,16 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell tool-gen-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div>
<span class="panel-kicker">Search Filters</span>
<h3>筛选条件</h3>
</div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="数据源" prop="dataName">
<el-select v-model="queryParams.dataName" filterable clearable placeholder="请选择/输入数据源名称">
<el-option key="" label="全部" value="" />
@@ -33,30 +40,28 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<span class="panel-kicker">Code Generator</span>
<h3>数据表列表</h3>
<p> {{ total }} 条记录支持导入表结构同步数据库和代码预览生成</p>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['tool:gen:code']" type="primary" plain icon="Download" @click="handleGenTable()">生成</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['tool:gen:import']" type="info" plain icon="Upload" @click="openImportTable">导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['tool:gen:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleEditTable()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['tool:gen:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="tableList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="tableList" @selection-change="handleSelectionChange">
<el-table-column type="selection" align="center" width="55"></el-table-column>
<el-table-column label="序号" type="index" width="50" align="center">
<template #default="scope">
@@ -250,10 +255,30 @@ onMounted(() => {
</script>
<style lang="scss" scoped>
.page-shell {
display: flex;
flex-direction: column;
}
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: rgba(53, 109, 255, 0.08);
}
}
.el-tab-pane {
background-color: #282c34;
.el-link {
color: #fff;
}
}
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style>
+28 -21
View File
@@ -1,34 +1,41 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="分类名称" prop="categoryName">
<el-input v-model="queryParams.categoryName" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="p-2 page-shell workflow-category-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="分类名称" prop="categoryName">
<el-input v-model="queryParams.categoryName" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<h3>流程分类</h3>
</div>
<div class="toolbar-actions">
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['workflow:category:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:showSearch="showSearch" :search="false" @queryTable="getList"></right-toolbar>
</div>
</div>
</template>
<el-table
ref="categoryTableRef"
v-loading="loading"
class="data-table"
:data="categoryList"
row-key="categoryId"
border
+21 -15
View File
@@ -1,8 +1,13 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="search">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell workflow-leave-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="请假天数" prop="startLeaveDays">
<el-input v-model="queryParams.startLeaveDays" placeholder="请输入请假天数" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -14,24 +19,25 @@
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<h3>请假列表</h3>
</div>
<div class="toolbar-actions">
<el-button v-hasPermi="['workflow:leave:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['workflow:leave:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="leaveList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="leaveList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="主键" align="center" prop="id" />
<el-table-column label="请假类型" align="center">
+90 -64
View File
@@ -1,9 +1,14 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<div class="p-2 page-shell workflow-process-definition-page">
<el-row :gutter="20" class="content-grid">
<!-- 流程分类树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-col :lg="4" :xs="24">
<el-card shadow="hover" class="side-panel">
<template #header>
<div class="table-heading">
<h3>流程分类</h3>
</div>
</template>
<el-input v-model="categoryName" placeholder="请输入流程分类名" prefix-icon="Search" clearable />
<el-tree
ref="categoryTreeRef"
@@ -19,50 +24,48 @@
></el-tree>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
<el-form-item label="流程定义名称" prop="flowName">
<el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义编码" prop="flowCode">
<el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-col :lg="20" :xs="24" class="content-main">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px" class="query-form">
<el-form-item label="流程定义名称" prop="flowName">
<el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="流程定义编码" prop="flowCode">
<el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<h3>流程定义</h3>
</div>
<div class="toolbar-actions">
<el-button type="primary" icon="Plus" @click="handleAdd()">添加</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">部署流程文件</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" icon="Download" :disabled="single" @click="handleExportDef">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="已发布" name="0"></el-tab-pane>
<el-tab-pane label="未发布" name="1"></el-tab-pane>
<el-table v-loading="loading" border :data="processDefinitionList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="processDefinitionList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" prop="id" label="主键" v-if="false"></el-table-column>
<el-table-column align="center" prop="flowName" label="流程定义名称" :show-overflow-tooltip="true"></el-table-column>
@@ -88,27 +91,19 @@
<el-tag v-else type="danger">失效</el-tag>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" align="center" width="170" class-name="small-padding fixed-width">
<el-table-column fixed="right" label="操作" align="center" width="236" class-name="small-padding fixed-width">
<template #default="scope">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button>
</el-col>
<el-col :span="1.5">
<el-button link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
</el-col>
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)"
>流程设计</el-button
>
<el-button link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
</el-col>
<el-col v-if="scope.row.isPublish !== 1" :span="1.5">
<el-button link type="primary" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">发布流程</el-button>
</el-col>
</el-row>
<div class="process-action-group">
<el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button>
<el-button link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
<el-button link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)">
流程设计
</el-button>
<el-button link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
<el-button link type="primary" v-if="scope.row.isPublish !== 1" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">
发布流程
</el-button>
</div>
</template>
</el-table-column>
</el-table>
@@ -239,11 +234,6 @@ const uploadDialog = reactive<DialogOption>({
title: '部署流程文件'
});
const processDefinitionDialog = reactive<DialogOption>({
visible: false,
title: '历史版本'
});
const modelDialog = reactive<DialogOption>({
visible: false,
title: ''
@@ -395,7 +385,6 @@ const handlePublish = async (row?: FlowDefinitionVo) => {
);
loading.value = true;
await publish(row.id).finally(() => (loading.value = false));
processDefinitionDialog.visible = false;
activeName.value = '0';
await handleQuery();
proxy?.$modal.msgSuccess('发布成功');
@@ -559,3 +548,40 @@ const handleExportDef = () => {
proxy?.download(`/workflow/definition/exportDef/${ids.value[0]}`, {}, `${flowCodeList.value[0]}.json`);
};
</script>
<style lang="scss" scoped>
.content-grid {
margin: 0 !important;
}
.content-main {
display: flex;
flex-direction: column;
gap: 12px;
}
.process-action-group {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
padding: 2px 0;
}
.process-action-group :deep(.el-button.is-link) {
width: auto !important;
min-width: 0 !important;
height: 30px !important;
padding: 0 10px !important;
border-radius: 10px !important;
background: rgba(53, 109, 255, 0.08) !important;
}
.process-action-group :deep(.el-button.is-link + .el-button.is-link) {
margin-left: 0 !important;
}
.process-action-group :deep(.el-button .el-icon) {
margin-right: 4px;
}
</style>
+40 -17
View File
@@ -1,9 +1,14 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<div class="p-2 page-shell workflow-process-instance-page">
<el-row :gutter="20" class="content-grid">
<!-- 流程分类树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-col :lg="4" :xs="24">
<el-card shadow="hover" class="side-panel">
<template #header>
<div class="table-heading">
<h3>流程分类</h3>
</div>
</template>
<el-input v-model="categoryName" placeholder="请输入流程分类名" prefix-icon="Search" clearable />
<el-tree
ref="categoryTreeRef"
@@ -19,11 +24,15 @@
></el-tree>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
<el-col :lg="20" :xs="24" class="content-main">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item>
<el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button>
@@ -45,20 +54,22 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<h3>流程实例</h3>
</div>
<div class="toolbar-actions">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template>
<el-tabs v-model="tab" @tab-click="changeTab">
<el-tab-pane name="running" label="运行中"></el-tab-pane>
<el-tab-pane name="finish" label="已完成"></el-tab-pane>
<el-table v-loading="loading" border :data="processInstanceList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="processInstanceList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
@@ -476,3 +487,15 @@ onMounted(() => {
getTreeselect();
});
</script>
<style lang="scss" scoped>
.content-grid {
margin: 0 !important;
}
.content-main {
display: flex;
flex-direction: column;
gap: 12px;
}
</style>
+19 -17
View File
@@ -1,9 +1,13 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell workflow-spel-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="组件名称" prop="componentName">
<el-input v-model="queryParams.componentName" placeholder="请输入组件名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
@@ -17,25 +21,23 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<div class="toolbar-shell">
<div class="table-heading">
<h3>流程表达式</h3>
</div>
<div class="toolbar-actions">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['workflow:spel:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['workflow:spel:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['workflow:spel:remove']">删除</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<right-toolbar v-model:showSearch="showSearch" :search="false" @queryTable="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="spelList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="spelList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="60" align="center">
<template #default="scope">
+21 -13
View File
@@ -1,9 +1,13 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell workflow-all-task-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item>
<el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button>
@@ -22,25 +26,29 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5" v-if="tab === 'waiting'">
<div class="toolbar-shell">
<div class="table-heading">
<h3>全部任务</h3>
</div>
<div class="toolbar-actions">
<template v-if="tab === 'waiting'">
<el-button class="todo-action-btn todo-action-btn--primary" type="primary" plain icon="Edit" :disabled="multiple" @click="handleUserOpen()"
>修改办理人
</el-button>
<el-button class="todo-action-btn todo-action-btn--warning" type="warning" plain icon="Bell" :disabled="multiple" @click="handleUrgeTaskOpen()"
>催办
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
</template>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template>
<el-tabs v-model="tab" @tab-click="changeTab">
<el-tab-pane name="waiting" label="待办任务"> </el-tab-pane>
<el-tab-pane name="finish" label="已办任务"> </el-tab-pane>
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
+40 -15
View File
@@ -1,9 +1,14 @@
<template>
<div class="p-2">
<el-row :gutter="20">
<div class="p-2 page-shell workflow-my-document-page">
<el-row :gutter="20" class="content-grid">
<!-- 流程分类树 -->
<el-col :lg="4" :xs="24" style="">
<el-card shadow="hover">
<el-col :lg="4" :xs="24">
<el-card shadow="hover" class="side-panel">
<template #header>
<div class="table-heading">
<h3>流程分类</h3>
</div>
</template>
<el-input v-model="categoryName" placeholder="请输入流程分类名" prefix-icon="Search" clearable />
<el-tree
ref="categoryTreeRef"
@@ -19,11 +24,15 @@
></el-tree>
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
<el-col :lg="20" :xs="24" class="content-main">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px" class="query-form">
<el-form-item label="流程定义编码" prop="flowCode">
<el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
</el-form-item>
@@ -34,15 +43,19 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
<div class="toolbar-shell">
<div class="table-heading">
<h3>我的单据</h3>
</div>
<div class="toolbar-actions">
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="processInstanceList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="processInstanceList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column v-if="false" align="center" prop="id" label="id"></el-table-column>
@@ -244,3 +257,15 @@ const handleOpen = async (row, type) => {
workflowCommon.routerJump(routerJumpVo, proxy);
};
</script>
<style lang="scss" scoped>
.content-grid {
margin: 0 !important;
}
.content-main {
display: flex;
flex-direction: column;
gap: 12px;
}
</style>
+19 -11
View File
@@ -1,9 +1,13 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell workflow-task-copy-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="任务名称" prop="nodeName">
<el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
</el-form-item>
@@ -20,15 +24,19 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
<div class="toolbar-shell">
<div class="table-heading">
<h3>抄送任务</h3>
</div>
<div class="toolbar-actions">
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
+19 -11
View File
@@ -1,9 +1,13 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell workflow-task-finish-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item>
<el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button>
@@ -25,15 +29,19 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
<div class="toolbar-shell">
<div class="table-heading">
<h3>已办任务</h3>
</div>
<div class="toolbar-actions">
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
+19 -11
View File
@@ -1,9 +1,13 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
<div class="p-2 page-shell workflow-task-waiting-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item>
<el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button>
@@ -25,15 +29,19 @@
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<el-card shadow="hover" class="table-panel">
<template #header>
<el-row :gutter="10" class="mb8">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
<div class="toolbar-shell">
<div class="table-heading">
<h3>待办任务</h3>
</div>
<div class="toolbar-actions">
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table v-loading="loading" border class="data-table" :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>