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

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

View File

@ -22,6 +22,7 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.2", "@element-plus/icons-vue": "2.3.2",
"@highlightjs/vue-plugin": "2.1.2", "@highlightjs/vue-plugin": "2.1.2",
"@iconify/vue": "^5.0.0",
"@vueup/vue-quill": "1.2.0", "@vueup/vue-quill": "1.2.0",
"@vueuse/core": "14.2.1", "@vueuse/core": "14.2.1",
"animate.css": "4.1.1", "animate.css": "4.1.1",

View File

@ -64,6 +64,8 @@
/*-------------Dialog-------------**/ /*-------------Dialog-------------**/
.el-overlay { .el-overlay {
overflow: hidden; overflow: hidden;
background: var(--app-overlay-mask);
backdrop-filter: blur(10px);
.el-overlay-dialog { .el-overlay-dialog {
display: flex; display: flex;
@ -71,21 +73,72 @@
justify-content: center; justify-content: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 20px;
.el-dialog { .el-dialog {
margin: 0 auto !important; margin: 0 auto !important;
border-radius: var(--app-radius-lg); border-radius: var(--app-radius-lg);
box-shadow: var(--app-shadow-md); box-shadow: var(--app-shadow-md);
overflow: hidden; 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 { .el-dialog__body {
padding: 15px !important; padding: 18px 20px 12px !important;
} }
.el-dialog__header {
padding: 16px 16px 8px 16px; .el-dialog__footer {
box-sizing: border-box; display: flex;
border-bottom: 1px solid var(--brder-color); align-items: center;
margin-right: 0; 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; overflow-x: hidden;
} }
.el-dialog__footer .el-button {
margin-left: 0 !important;
}
// refine element ui upload // refine element ui upload
.upload-container { .upload-container {
.el-upload { .el-upload {
@ -111,8 +168,46 @@
// dropdown // dropdown
.el-dropdown-menu { .el-dropdown-menu {
border-radius: var(--app-radius-md); padding: 8px;
box-shadow: var(--app-shadow-sm); 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 { a {
display: block; display: block;
} }
@ -128,7 +223,7 @@
box-sizing: content-box; 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; display: none;
} }
@ -152,14 +247,104 @@
width: 240px; width: 240px;
} }
.el-form--inline .el-date-editor {
width: 308px;
}
/* 设置 el-message-box 消息弹框内容强制换行 */ /* 设置 el-message-box 消息弹框内容强制换行 */
.el-message-box .el-message-box__message { .el-message-box .el-message-box__message {
word-break: break-word; word-break: break-word;
} }
.el-message-box { .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); 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, .el-message,
@ -169,6 +354,281 @@
box-shadow: var(--app-shadow-sm); 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 // Modern rounded inputs
.el-input__wrapper, .el-input__wrapper,
.el-textarea__inner, .el-textarea__inner,
@ -193,11 +653,10 @@
// Buttons // Buttons
.el-button { .el-button {
border-radius: var(--app-radius-md); 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 { .el-button:not(.is-text):not(.is-link):hover {
transform: translateY(-1px);
box-shadow: var(--app-shadow-sm); box-shadow: var(--app-shadow-sm);
} }
@ -214,32 +673,200 @@
box-shadow: var(--app-shadow-sm); 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 { .el-drawer {
border-radius: var(--app-radius-lg); border-radius: var(--app-radius-lg);
box-shadow: var(--app-shadow-md); box-shadow: var(--app-shadow-md);
border: 1px solid var(--app-surface-border);
} }
.el-drawer__header { .el-drawer__header {
margin-bottom: 0; margin-bottom: 0;
padding: 16px 20px; padding: 18px 20px 16px;
border-bottom: 1px solid var(--el-border-color-light); 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 // Table polish
.el-table { .el-table {
border-radius: var(--app-radius-lg); border-radius: 10px;
overflow: hidden; overflow: hidden;
box-shadow: var(--app-shadow-sm); border: 1px solid var(--app-surface-border);
border: 1px solid var(--el-border-color-lighter);
} }
.el-table__header-wrapper, .el-table__header-wrapper,
.el-table__body-wrapper { .el-table__body-wrapper {
background: var(--el-bg-color); background: transparent;
} }
.el-table__row:hover td.el-table__cell { .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 // Tabs
@ -264,6 +891,27 @@
transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease; 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 { .el-pagination .el-pager li.is-active {
box-shadow: var(--app-shadow-sm); box-shadow: var(--app-shadow-sm);
} }

View File

@ -14,7 +14,7 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
background: var(--el-bg-color-page); background: var(--app-shell-bg);
font-family: font-family:
'MiSans', 'MiSans',
'HarmonyOS Sans SC', 'HarmonyOS Sans SC',
@ -44,6 +44,12 @@ html.dark svg {
height: 100%; height: 100%;
} }
html,
body,
#app {
min-height: 100%;
}
*, *,
*:before, *:before,
*:after { *:after {
@ -147,7 +153,7 @@ aside {
//main-container全局样式 //main-container全局样式
.app-container { .app-container {
padding: 20px; padding: 16px;
background: var(--app-surface-bg); background: var(--app-surface-bg);
border: 1px solid var(--app-surface-border); border: 1px solid var(--app-surface-border);
border-radius: var(--app-radius-lg); border-radius: var(--app-radius-lg);
@ -159,19 +165,62 @@ aside {
.search { .search {
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
border-radius: var(--app-radius-lg); border-radius: var(--app-radius-lg);
border: 1px solid var(--el-border-color-light); border: 1px solid var(--app-surface-border);
background-color: var(--el-bg-color-overlay); background-color: var(--app-surface-bg);
padding: 0.75rem; padding: 0.75rem;
box-shadow: 0 0 0 rgba(0, 0, 0, 0); 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 { &:hover {
box-shadow: var(--app-shadow-sm); box-shadow: var(--app-shadow-sm);
border-color: var(--el-border-color); border-color: rgba(64, 158, 255, 0.2);
transform: translateY(-1px);
} }
} }
.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 { .components-container {
margin: 30px 50px; margin: 30px 50px;
position: relative; position: relative;

View File

@ -76,6 +76,41 @@ h6 {
.el-form .el-form-item__label { .el-form .el-form-item__label {
font-weight: 600; 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) { .el-dialog:not(.is-fullscreen) {
margin-top: 6vh !important; margin-top: 6vh !important;
} }
@ -84,7 +119,7 @@ h6 {
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
max-height: 70vh; max-height: 70vh;
padding: 10px 20px 0; padding: 14px 22px 4px;
} }
.el-table { .el-table {
@ -118,17 +153,18 @@ h6 {
.pagination-container { .pagination-container {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
margin-top: 20px; margin-top: 18px;
padding: 10px 20px !important; padding: 10px 0 0 !important;
} }
/* tree border */ /* tree border */
.tree-border { .tree-border {
margin-top: 5px; margin-top: 8px;
border: 1px solid #e5e6e7; border: 1px solid rgba(148, 163, 184, 0.16);
background: #ffffff none; background: rgba(255, 255, 255, 0.42) none;
border-radius: var(--app-radius-md); border-radius: 22px;
width: 100%; width: 100%;
padding: 12px;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@ -188,27 +224,28 @@ h6 {
} }
.el-card__header { .el-card__header {
padding: 14px 15px 7px !important; padding: 14px 16px 10px !important;
min-height: 40px; min-height: auto;
background: var(--el-fill-color-blank); background: transparent;
border-bottom: 1px solid var(--el-border-color-light); border-bottom: 1px solid rgba(148, 163, 184, 0.14);
} }
.el-card__body { .el-card__body {
padding: 15px 20px 20px 20px !important; padding: 16px !important;
} }
.el-card { .el-card {
border-radius: var(--app-radius-lg); border-radius: var(--app-radius-lg);
box-shadow: var(--app-shadow-sm); box-shadow: var(--app-shadow-sm);
border-color: var(--el-border-color-lighter); border-color: var(--app-surface-border);
overflow: hidden; 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 { .el-card:hover {
box-shadow: var(--app-shadow-md); box-shadow: var(--app-shadow-md);
transform: translateY(-1px); border-color: rgba(64, 158, 255, 0.16);
} }
.card-box { .card-box {
@ -298,6 +335,287 @@ h6 {
margin-left: auto; 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 */ /* horizontal el menu */
.el-menu--horizontal .el-menu-item .svg-icon + span, .el-menu--horizontal .el-menu-item .svg-icon + span,
.el-menu--horizontal .el-sub-menu__title .svg-icon + span { .el-menu--horizontal .el-sub-menu__title .svg-icon + span {

View File

@ -4,9 +4,9 @@
.main-container { .main-container {
height: 100%; height: 100%;
transition: margin-left 0.28s; transition: margin-left 0.28s;
margin-left: $base-sidebar-width; margin-left: calc(#{$base-sidebar-width} + 12px);
position: relative; position: relative;
background: var(--el-bg-color-page); background: transparent;
} }
.sidebarHide { .sidebarHide {
@ -17,17 +17,19 @@
-webkit-transition: width 0.28s; -webkit-transition: width 0.28s;
transition: width 0.28s; transition: width 0.28s;
width: $base-sidebar-width !important; width: $base-sidebar-width !important;
background-color: $base-menu-background; background: transparent;
height: 100%; height: calc(100vh - 24px);
position: fixed; position: fixed;
font-size: 0; font-size: 0;
top: 0; top: 12px;
bottom: 0; left: 12px;
left: 0;
z-index: 1001; z-index: 1001;
overflow: hidden; overflow: hidden;
-webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); box-sizing: border-box;
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1); padding: 0;
border-right: none;
-webkit-box-shadow: none;
box-shadow: none;
// reset element-ui css // reset element-ui css
.horizontal-collapse-transition { .horizontal-collapse-transition {
@ -49,10 +51,24 @@
height: 100%; height: 100%;
} }
&.has-logo { .el-scrollbar.theme-light {
.el-scrollbar { --side-menu-text: #1f2937;
height: calc(100% - 50px); --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 { .is-horizontal {
@ -73,6 +89,11 @@
border: none; border: none;
height: 100%; height: 100%;
width: 100% !important; width: 100% !important;
background: transparent !important;
}
.el-menu--inline {
background: transparent !important;
} }
.el-menu-item, .el-menu-item,
@ -82,89 +103,123 @@
white-space: nowrap !important; 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 { .el-menu-item .el-menu-tooltip__trigger {
display: inline-block !important; display: inline-block !important;
} }
// menu hover // menu hover
.theme-dark .sub-menu-title-noDropdown, .theme-dark .submenu-title-noDropdown,
.theme-dark .el-sub-menu__title { .theme-dark .el-sub-menu__title {
border-radius: var(--app-radius-md); border-radius: var(--app-radius-md);
margin: 1px 5px 1px 5px; margin: 3px 8px;
&:hover { &: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 { .el-sub-menu__title {
border-radius: var(--app-radius-md); border-radius: var(--app-radius-md);
margin: 1px 5px 1px 5px; margin: 3px 8px;
color: var(--side-menu-text) !important;
&:hover { &: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 { & .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, & .nest-menu .el-sub-menu > .el-sub-menu__title,
& .el-sub-menu .el-menu-item { & .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); border-radius: var(--app-radius-md);
height: 45px; height: 40px;
margin: 1px 5px 1px 5px; margin: 3px 8px;
background: transparent !important;
&:not(.is-active):hover { &: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 .nest-menu .el-sub-menu > .el-sub-menu__title,
& .theme-dark .el-sub-menu .el-menu-item { & .theme-dark .el-sub-menu .el-menu-item {
background-color: $base-sub-menu-background !important;
border-radius: var(--app-radius-md); border-radius: var(--app-radius-md);
height: 45px; height: 40px;
margin: 1px 5px 1px 5px; margin: 3px 8px;
background: transparent !important;
&.is-active { &.is-active {
background-color: var(--el-menu-active-color) !important; background: var(--side-menu-active-bg) !important;
color: #fff; 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 { &:not(.is-active):hover {
// you can use $sub-menuHover // 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 .nest-menu .el-sub-menu > .el-sub-menu__title,
& .theme-dark .el-menu-item { & .theme-dark .el-menu-item {
border-radius: var(--app-radius-md); border-radius: var(--app-radius-md);
height: 45px; height: 40px;
margin: 1px 5px 1px 5px; margin: 3px 8px;
background: transparent !important;
&.is-active { &.is-active {
background-color: var(--el-menu-active-color) !important; background: var(--side-menu-active-bg) !important;
color: #fff; 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 { &:not(.is-active):hover {
// you can use $sub-menuHover // 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, & .nest-menu .el-sub-menu > .el-sub-menu__title,
& .el-menu-item { & .el-menu-item {
border-radius: var(--app-radius-md); border-radius: var(--app-radius-md);
height: 45px; height: 40px;
margin: 1px 5px 1px 5px; margin: 3px 8px;
background: transparent !important;
&.is-active { &.is-active {
background-color: var(--el-menu-active-color) !important; background: var(--side-menu-active-bg) !important;
color: #fff; 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 { &:not(.is-active):hover {
// you can use $sub-menuHover // 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,60 +227,124 @@
// 收起菜单后的样式 // 收起菜单后的样式
.hideSidebar { .hideSidebar {
.sidebar-container { .sidebar-container {
width: 54px !important; width: 58px !important;
} }
.main-container { .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; padding: 0 !important;
position: relative; 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 { &.is-active {
background-color: var(--el-menu-active-color) !important; background-color: var(--side-menu-active-bg) !important;
color: #fff !important; color: var(--side-menu-active-text) !important;
box-shadow: inset 3px 0 0 var(--side-menu-active-line);
} }
.el-tooltip { .el-tooltip {
padding: 0 !important; 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 { & .el-sub-menu {
overflow: hidden; overflow: hidden;
border-radius: var(--app-radius-md); border-radius: var(--app-radius-md);
.el-sub-menu__title,
.el-sub-menu__title.el-tooltip__trigger { .el-sub-menu__title.el-tooltip__trigger {
border-radius: var(--app-radius-md); 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 { &.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 { & > .el-sub-menu__title {
padding: 0 !important; padding: 0 !important;
} }
} }
.el-menu--collapse { .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 { .is-active .svg-icon {
fill: #fff; fill: currentColor;
} }
.svg-icon {
display: flex; > div > .el-sub-menu > .el-sub-menu__title .svg-icon {
margin: auto; position: absolute;
height: 100%; left: 50%;
// 这里设置width会跟随sidebar-container的transition 不符合预期 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 { > 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 { & > span {
height: 0; height: 0;
width: 0; width: 0;
@ -243,7 +362,6 @@
} }
} }
} }
}
.el-menu--collapse .el-menu .el-sub-menu { .el-menu--collapse .el-menu .el-sub-menu {
min-width: $base-sidebar-width !important; min-width: $base-sidebar-width !important;
@ -264,7 +382,7 @@
.sidebar-container { .sidebar-container {
pointer-events: none; pointer-events: none;
transition-duration: 0.3s; 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{ .el-popper.is-pure{
border-radius: var(--app-radius-md); border-radius: var(--app-radius-md);
box-shadow: var(--app-shadow-md);
.el-menu--popup{ .el-menu--popup{
border-radius: var(--app-radius-md); border-radius: var(--app-radius-md);
} }

View File

@ -1,14 +1,14 @@
// 全局SCSS变量 // 全局SCSS变量
:root { :root {
--menuBg: #1f2d3d; --menuBg: #ffffff;
--menuColor: #bfcbd9; --menuColor: #1f2937;
--menuActiveText: #f4f4f5; --menuActiveText: #111827;
--menuHover: #263445; --menuHover: rgba(64, 158, 255, 0.12);
--subMenuBg: #1f2d3d; --subMenuBg: #ffffff;
--subMenuActiveText: #f4f4f5; --subMenuActiveText: #111827;
--subMenuHover: #001528; --subMenuHover: rgba(64, 158, 255, 0.12);
--subMenuTitleHover: #293444; --subMenuTitleHover: rgba(64, 158, 255, 0.08);
// 菜单栏缩进 // 菜单栏缩进
--el-menu-base-level-padding: 12px; --el-menu-base-level-padding: 12px;
@ -16,8 +16,8 @@
--el-menu-item-height: 50px; --el-menu-item-height: 50px;
--fixedHeaderBg: #ffffff; --fixedHeaderBg: #ffffff;
--tableHeaderBg: #f8f8f9; --tableHeaderBg: #f8fafc;
--tableHeaderTextColor: #515a6e; --tableHeaderTextColor: #475569;
// ele // ele
--brder-color: #e8e8e8; --brder-color: #e8e8e8;
@ -28,13 +28,28 @@
// Modern rounded style + soft shadows // Modern rounded style + soft shadows
--app-radius-sm: 6px; --app-radius-sm: 6px;
--app-radius-md: 10px; --app-radius-md: 12px;
--app-radius-lg: 14px; --app-radius-lg: 18px;
--app-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08), 0 6px 16px rgba(15, 23, 42, 0.08); --app-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.05);
--app-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.12); --app-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.06);
--app-shadow-lg: 0 12px 32px rgba(15, 23, 42, 0.16); --app-shadow-lg: 0 14px 36px rgba(15, 23, 42, 0.1);
--app-surface-bg: #ffffff; --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 // Element Plus tokens
--el-border-radius-base: var(--app-radius-md); --el-border-radius-base: var(--app-radius-md);
@ -42,21 +57,21 @@
--el-border-radius-round: 999px; --el-border-radius-round: 999px;
--el-box-shadow-light: var(--app-shadow-sm); --el-box-shadow-light: var(--app-shadow-sm);
--el-box-shadow: var(--app-shadow-md); --el-box-shadow: var(--app-shadow-md);
--el-bg-color-page: #f5f7fb; --el-bg-color-page: var(--app-shell-bg);
} }
html.dark { html.dark {
--menuBg: #1d1e1f; --menuBg: #111827;
--menuColor: #bfcbd9; --menuColor: #e5edf8;
--menuActiveText: #f4f4f5; --menuActiveText: #f8fbff;
--menuHover: #171819; --menuHover: rgba(85, 129, 255, 0.2);
--subMenuBg: #1d1e1f; --subMenuBg: #111827;
--subMenuActiveText: #1d1e1f; --subMenuActiveText: #f8fbff;
--subMenuHover: #171819; --subMenuHover: rgba(85, 129, 255, 0.18);
--subMenuTitleHover: #171819; --subMenuTitleHover: rgba(148, 163, 184, 0.12);
--fixedHeaderBg: #171819; --fixedHeaderBg: #0f172a;
--tableHeaderBg: var(--el-bg-color); --tableHeaderBg: var(--el-bg-color);
--tableHeaderTextColor: var(--el-text-color); --tableHeaderTextColor: var(--el-text-color);
@ -204,12 +219,27 @@ html.dark {
--tags-view-active-border-color: var(--el-color-primary-light-2); --tags-view-active-border-color: var(--el-color-primary-light-2);
// Modern rounded style + soft shadows (dark) // 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-sm: 0 1px 2px rgba(0, 0, 0, 0.28);
--app-shadow-md: 0 10px 26px rgba(0, 0, 0, 0.35); --app-shadow-md: 0 12px 28px rgba(0, 0, 0, 0.28);
--app-shadow-lg: 0 14px 34px rgba(0, 0, 0, 0.4); --app-shadow-lg: 0 20px 44px rgba(0, 0, 0, 0.32);
--app-surface-bg: #151922; --app-surface-bg: #111827;
--app-surface-border: var(--el-border-color); --app-surface-border: rgba(71, 85, 105, 0.5);
--el-bg-color-page: #0f1115; --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-table 主题
--vxe-font-color: #98989e; --vxe-font-color: #98989e;
--vxe-primary-color: #2c7ecf; --vxe-primary-color: #2c7ecf;

View File

@ -78,12 +78,18 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb { .app-breadcrumb.el-breadcrumb {
display: inline-block; display: inline-block;
font-size: 14px; font-size: 13px;
line-height: 50px; line-height: 1.2;
margin-left: 8px; margin-left: 4px;
color: var(--app-text-muted);
:deep(.el-breadcrumb__inner) {
color: inherit;
font-weight: 500;
}
.no-redirect { .no-redirect {
color: #97a8be; color: var(--app-text-title);
cursor: text; cursor: text;
} }
} }

View File

@ -16,6 +16,19 @@
<el-input v-model="filterValue" class="p-2" placeholder="搜索图标" clearable @input="filterIcons" /> <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]"> <el-scrollbar height="w-[200px]">
<ul class="icon-list"> <ul class="icon-list">
<el-tooltip v-for="(iconName, index) in iconNames" :key="index" :content="iconName" placement="bottom" effect="light"> <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 iconNames = ref<string[]>(icons);
const filterValue = ref(''); const filterValue = ref('');
const customIcon = ref('');
/** /**
* 筛选图标 * 筛选图标
@ -63,6 +77,21 @@ const selectedIcon = (iconName: string) => {
emit('update:modelValue', iconName); emit('update:modelValue', iconName);
visible.value = false; 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -73,31 +102,59 @@ const selectedIcon = (iconName: string) => {
.el-divider--horizontal { .el-divider--horizontal {
margin: 10px auto !important; 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 { .icon-list {
display: flex; display: grid;
flex-wrap: wrap; grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
padding-left: 10px; gap: 8px;
padding: 4px;
margin-top: 10px; margin-top: 10px;
.icon-item { .icon-item {
cursor: pointer; cursor: pointer;
width: 10%; min-height: 44px;
margin: 0 10px 10px 0; padding: 8px 6px;
padding: 5px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-items: center; justify-content: center;
align-items: 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 { &:hover {
border-color: var(--el-color-primary); border-color: var(--el-color-primary);
background: var(--app-accent-soft);
color: var(--el-color-primary); color: var(--el-color-primary);
transition: all 0.2s; transform: translateY(-1px);
transform: scaleX(1.1);
} }
} }
.active { .active {
border-color: var(--el-color-primary); border-color: var(--el-color-primary);
background: rgba(64, 158, 255, 0.12);
color: var(--el-color-primary); color: var(--el-color-primary);
} }
} }

View File

@ -33,7 +33,15 @@ const handleLanguageChange = (lang: any) => {
<style lang="scss" scoped> <style lang="scss" scoped>
.lang-select--style { .lang-select--style {
font-size: 18px; display: inline-flex;
line-height: 50px; align-items: center;
justify-content: center;
width: 100%;
height: 100%;
:deep(.svg-icon) {
width: 16px;
height: 16px;
}
} }
</style> </style>

View File

@ -68,8 +68,15 @@ function handleCurrentChange(val: number) {
<style lang="scss" scoped> <style lang="scss" scoped>
.pagination-container { .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 { .el-pagination {
float: v-bind(float); float: none;
} }
} }
.pagination-container.hidden { .pagination-container.hidden {

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="top-right-btn" :style="style"> <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-tooltip v-if="search" class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top">
<el-button circle icon="Search" @click="toggleSearch()" /> <el-button circle icon="Search" @click="toggleSearch()" />
</el-tooltip> </el-tooltip>
@ -79,6 +79,27 @@ onMounted(() => {
</script> </script>
<style lang="scss" scoped> <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) { :deep(.el-transfer__button) {
border-radius: 50%; border-radius: 50%;
display: block; display: block;
@ -91,12 +112,27 @@ onMounted(() => {
.my-el-transfer { .my-el-transfer {
text-align: center; text-align: center;
} }
.toolbar-row {
gap: 8px;
}
.tree-header { .tree-header {
width: 100%; width: 100%;
line-height: 24px; line-height: 24px;
text-align: center; text-align: center;
font-weight: 700;
color: var(--app-text-title);
} }
.show-btn { .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> </style>

View File

@ -1,10 +1,11 @@
<template> <template>
<div> <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"> <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]"> <div v-show="showSearch">
<el-card shadow="hover"> <el-card shadow="hover" class="search-panel selector-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="角色名称" prop="roleName"> <el-form-item label="角色名称" prop="roleName">
<el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -21,15 +22,23 @@
</div> </div>
</transition> </transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel selector-card">
<template #header> <template #header>
<el-tag v-for="role in selectRoleList" :key="role.roleId" closable style="margin: 2px" @close="handleCloseTag(role)"> <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 }} {{ role.roleName }}
</el-tag> </el-tag>
</div>
</div>
</template> </template>
<vxe-table <vxe-table
ref="tableRef" ref="tableRef"
class="selector-table"
height="400px" height="400px"
border border
show-overflow show-overflow
@ -66,6 +75,7 @@
@pagination="pageList" @pagination="pageList"
/> />
</el-card> </el-card>
</div>
<template #footer> <template #footer>
<el-button @click="close">取消</el-button> <el-button @click="close">取消</el-button>
<el-button type="primary" @click="confirm">确定</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 emit = defineEmits(['update:modelValue', 'confirmCallBack']);
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable')); const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
@ -248,3 +257,66 @@ defineExpose({
close: roleDialog.closeDialog close: roleDialog.closeDialog
}); });
</script> </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>

View File

@ -34,8 +34,15 @@ const handleSetSize = (size: 'large' | 'default' | 'small') => {
<style lang="scss" scoped> <style lang="scss" scoped>
.size-icon--style { .size-icon--style {
font-size: 18px; display: inline-flex;
line-height: 50px; align-items: center;
padding-right: 7px; justify-content: center;
width: 100%;
height: 100%;
:deep(.svg-icon) {
width: 16px;
height: 16px;
}
} }
</style> </style>

View File

@ -1,24 +1,33 @@
<template> <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" /> <use :xlink:href="iconName" :fill="color" />
</svg> </svg>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Icon } from '@iconify/vue';
import { propTypes } from '@/utils/propTypes'; import { propTypes } from '@/utils/propTypes';
const props = defineProps({ const props = defineProps({
iconClass: propTypes.string.isRequired, iconClass: propTypes.string.isRequired,
className: propTypes.string.def(''), 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(() => { const svgClass = computed(() => {
if (props.className) { if (props.className) {
return `svg-icon ${props.className}`; return `svg-icon ${props.className}`;
} }
return 'svg-icon'; return 'svg-icon';
}); });
const iconStyle = computed(() => ({
color: props.color || undefined,
fontSize: props.size || undefined
}));
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -36,5 +45,6 @@ const svgClass = computed(() => {
position: relative; position: relative;
fill: currentColor; fill: currentColor;
vertical-align: -2px; vertical-align: -2px;
flex-shrink: 0;
} }
</style> </style>

View File

@ -1,14 +1,14 @@
<template> <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"> <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 : ''" /> ><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 {{ item.meta?.title }}</el-menu-item
> >
</template> </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 #title>更多菜单</template>
<template v-for="(item, index) in topMenus"> <template v-for="(item, index) in topMenus">
<el-menu-item v-if="index >= visibleNumber" :key="index" :index="item.path" <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); const visibleNumber = ref<number>(-1);
// index
const currentIndex = ref<string>();
// //
const hideList = ['/index', '/user/profile']; const hideList = ['/index', '/user/profile'];
@ -104,12 +102,14 @@ const activeMenu = computed(() => {
}); });
const setVisibleNumber = () => { const setVisibleNumber = () => {
const width = document.body.getBoundingClientRect().width / 3; let width = document.body.getBoundingClientRect().width;
visibleNumber.value = parseInt(String(width / 85)); if (width >= 1000) {
width -= 420;
}
visibleNumber.value = Math.max(1, Math.floor(width / 3 / 92) + 2);
}; };
const handleSelect = (key: string) => { const handleSelect = (key: string) => {
currentIndex.value = key;
const route = routers.value.find((item) => item.path === key); const route = routers.value.find((item) => item.path === key);
if (isHttp(key)) { if (isHttp(key)) {
// http(s):// // http(s)://
@ -161,40 +161,214 @@ onMounted(() => {
</script> </script>
<style lang="scss"> <style lang="scss">
.topmenu-container.el-menu--horizontal > .el-menu-item { .mix-topnav-menu.el-menu--horizontal {
float: left; --topbar-pill-bg: rgba(255, 255, 255, 0.7);
height: 50px !important; --topbar-pill-hover-bg: rgba(255, 255, 255, 0.9);
line-height: 50px !important; --topbar-pill-active-bg: rgba(255, 255, 255, 0.82);
color: #999093 !important; --topbar-pill-text: var(--app-text-title);
padding: 0 5px !important; --topbar-pill-muted: var(--app-text-muted);
margin: 0 10px !important; --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, #app .mix-topnav-menu.el-menu--horizontal > .el-menu-item,
.el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title { #app .mix-topnav-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title {
border-bottom: 2px solid #{'var(--theme)'} !important; display: inline-flex;
color: #303133; 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 */ #app .mix-topnav-menu.el-menu--horizontal > .el-menu-item:hover,
.topmenu-container.el-menu--horizontal > .el-sub-menu .el-sub-menu__title { #app .mix-topnav-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title:hover {
float: left; background: linear-gradient(180deg, var(--topbar-pill-hover-bg), var(--topbar-pill-bg)) !important;
height: 50px !important; border-color: var(--topbar-pill-border) !important;
line-height: 50px !important; color: v-bind(theme) !important;
color: #999093 !important; transform: translateY(-1px);
padding: 0 5px !important;
margin: 0 10px !important;
} }
/* 背景色隐藏 */ #app .mix-topnav-menu.el-menu--horizontal > .el-menu-item.is-active,
.topmenu-container.el-menu--horizontal > .el-menu-item:not(.is-disabled):focus, #app .mix-topnav-menu.el-menu--horizontal > .el-sub-menu.is-active > .el-sub-menu__title {
.topmenu-container.el-menu--horizontal > .el-menu-item:not(.is-disabled):hover, background: linear-gradient(180deg, var(--topbar-pill-hover-bg), var(--topbar-pill-active-bg)) !important;
.topmenu-container.el-menu--horizontal > .el-submenu .el-submenu__title:hover { border-color: var(--topbar-pill-active-border) !important;
background-color: #ffffff !important; color: v-bind(theme) !important;
box-shadow: none !important;
transform: none !important;
} }
/* 图标右间距 */ #app .mix-topnav-menu.el-menu--horizontal > .el-menu-item .svg-icon,
.topmenu-container .svg-icon { #app .mix-topnav-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title .svg-icon {
margin-right: 4px; 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> </style>

View File

@ -1,14 +1,20 @@
<template> <template>
<div> <div>
<el-dialog v-model="userDialog.visible.value" :title="userDialog.title.value" width="80%" append-to-body> <el-dialog v-model="userDialog.visible.value" :title="userDialog.title.value" width="80%" append-to-body class="user-select-dialog">
<el-row :gutter="20"> <div class="page-shell user-select-shell">
<el-row :gutter="12" class="selector-layout">
<!-- 部门树 --> <!-- 部门树 -->
<el-col :lg="4" :xs="24" style=""> <el-col :lg="5" :xs="24">
<el-card shadow="hover"> <el-card shadow="hover" class="side-panel selector-card selector-side-card">
<el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable /> <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 <el-tree
ref="deptTreeRef" ref="deptTreeRef"
class="mt-2" class="selector-tree"
node-key="id" node-key="id"
:data="deptOptions" :data="deptOptions"
:props="{ label: 'label', children: 'children' } as any" :props="{ label: 'label', children: 'children' } as any"
@ -20,11 +26,12 @@
/> />
</el-card> </el-card>
</el-col> </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"> <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]"> <div v-show="showSearch">
<el-card shadow="hover"> <el-card shadow="hover" class="search-panel selector-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<el-form-item label="用户名称" prop="userName"> <el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -40,15 +47,23 @@
</div> </div>
</transition> </transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel selector-card">
<template v-if="prop.multiple" #header> <template #header>
<el-tag v-for="user in selectUserList" :key="user.userId" closable style="margin: 2px" @close="handleCloseTag(user)"> <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 }} {{ user.nickName }}
</el-tag> </el-tag>
</div>
</div>
</template> </template>
<vxe-table <vxe-table
ref="tableRef" ref="tableRef"
class="selector-table"
height="400px" height="400px"
border border
show-overflow show-overflow
@ -86,8 +101,10 @@
@pagination="pageList" @pagination="pageList"
/> />
</el-card> </el-card>
</div>
</el-col> </el-col>
</el-row> </el-row>
</div>
<template #footer> <template #footer>
<el-button @click="close">取消</el-button> <el-button @click="close">取消</el-button>
@ -309,3 +326,87 @@ defineExpose({
close: userDialog.closeDialog close: userDialog.closeDialog
}); });
</script> </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>

View File

@ -22,11 +22,9 @@ const tagsViewStore = useTagsViewStore();
// //
const animate = ref<string>(''); const animate = ref<string>('');
const animationEnable = ref(useSettingsStore().animationEnable);
watch( watch(
() => useSettingsStore().animationEnable, () => useSettingsStore().animationEnable,
(val: boolean) => { (val: boolean) => {
animationEnable.value = val;
if (val) { if (val) {
animate.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string; animate.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string;
} else { } else {
@ -36,10 +34,6 @@ watch(
{ immediate: true } { immediate: true }
); );
onMounted(() => {
addIframe();
});
watchEffect(() => { watchEffect(() => {
addIframe(); addIframe();
}); });
@ -53,26 +47,21 @@ function addIframe() {
<style lang="scss" scoped> <style lang="scss" scoped>
.app-main { .app-main {
/* 50= navbar 50 */ min-height: 100vh;
min-height: calc(100vh - 50px);
width: 100%; width: 100%;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
padding: 12px;
} }
.fixed-header + .app-main { .app-main.with-fixed-header {
padding-top: 50px; padding-top: 76px;
min-height: calc(100vh - 76px);
} }
.hasTagsView { .app-main.with-fixed-header.with-tags-view {
.app-main { min-height: calc(100vh - 111px);
/* 84 = navbar + tags-view = 50 + 34 */ padding-top: 111px;
min-height: calc(100vh - 84px);
}
.fixed-header + .app-main {
padding-top: 84px;
}
} }
</style> </style>
<style lang="scss"> <style lang="scss">

View File

@ -1,14 +1,22 @@
<template> <template>
<div class="navbar" :class="'nav' + navType"> <div class="navbar" :class="'nav' + navType">
<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" /> <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>
<div class="nav-context">
<breadcrumb v-if="navType == NavTypeEnum.LEFT" id="breadcrumb-container" class="breadcrumb-container" /> <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" /> <top-nav v-if="navType == NavTypeEnum.MIX" id="topmenu-container" class="topmenu-container" />
<template v-if="navType == NavTypeEnum.TOP"> <template v-if="navType == NavTypeEnum.TOP">
<logo v-show="showLogo" :collapse="false"></logo>
<top-bar id="topbar-container" class="topbar-container" /> <top-bar id="topbar-container" class="topbar-container" />
</template> </template>
</div>
</div>
<div class="right-menu flex align-center"> <div class="right-menu flex align-center">
<template v-if="appStore.device !== 'mobile'"> <template v-if="appStore.device !== 'mobile'">
<search-menu ref="searchMenuRef" /> <search-menu ref="searchMenuRef" />
@ -23,7 +31,7 @@
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false"> <el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
<template #reference> <template #reference>
<el-badge :value="newNotice > 0 ? newNotice : ''" :max="99"> <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> </el-badge>
</template> </template>
<template #default> <template #default>
@ -53,10 +61,14 @@
</el-tooltip> </el-tooltip>
</template> </template>
<div class="avatar-container"> <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"> <div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" /> <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> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
@ -87,8 +99,9 @@ import notice from './notice/index.vue';
import router from '@/router'; import router from '@/router';
import type { ElMessageBoxOptions } from 'element-plus'; import type { ElMessageBoxOptions } from 'element-plus';
import { NavTypeEnum } from '@/enums/NavTypeEnum'; import { NavTypeEnum } from '@/enums/NavTypeEnum';
import Logo from "@/layout/components/Sidebar/Logo.vue";
import TopBar from './TopBar/index.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 appStore = useAppStore();
const userStore = useUserStore(); const userStore = useUserStore();
@ -98,9 +111,9 @@ const newNotice = ref(<number>0);
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userId = ref(userStore.userId);
const navType = computed(() => settingsStore.navType); const navType = computed(() => settingsStore.navType);
const showLogo = computed(() => settingsStore.sidebarLogo); const showLogo = computed(() => settingsStore.sidebarLogo);
const displayName = computed(() => userStore.nickname || '管理员');
// //
const searchMenuRef = ref<InstanceType<typeof SearchMenu>>(); const searchMenuRef = ref<InstanceType<typeof SearchMenu>>();
@ -157,8 +170,56 @@ watch(
<style lang="scss" scoped> <style lang="scss" scoped>
.navbar.navtop { .navbar.navtop {
.hamburger-container { .nav-context {
display: none !important; 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) { :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 { .flex {
@ -179,42 +250,71 @@ watch(
} }
.navbar { .navbar {
height: 50px; min-height: 52px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: var(--el-bg-color); background: var(--app-navbar-bg);
border-bottom: 1px solid var(--el-border-color-lighter); border: 1px solid var(--app-navbar-border);
box-shadow: none; box-shadow: var(--app-navbar-shadow);
display: flex; display: flex;
align-items: center; align-items: center;
// padding: 0 8px; justify-content: space-between;
border-radius: 16px;
padding: 6px 12px;
box-sizing: border-box; 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 { .hamburger-container {
line-height: 46px; line-height: 32px;
height: 100%; height: 100%;
//float: left;
cursor: pointer; cursor: pointer;
transition: background 0.3s; transition: background 0.3s;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
display: flex; display: flex;
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
margin-right: 8px; justify-content: center;
&:hover { &: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 { .breadcrumb-container {
//float: left;
flex-shrink: 0; flex-shrink: 0;
} }
.topmenu-container { .topmenu-container {
position: absolute; position: static;
left: 50px; min-width: 0;
} }
.topbar-container { .topbar-container {
@ -226,66 +326,159 @@ watch(
margin-left: 8px; margin-left: 8px;
} }
.errLog-container {
display: inline-block;
vertical-align: top;
}
.right-menu { .right-menu {
//float: right;
height: 100%; height: 100%;
line-height: 50px; line-height: 1;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px;
margin-left: auto; margin-left: auto;
flex-wrap: nowrap;
&:focus { &:focus {
outline: none; outline: none;
} }
> * {
flex-shrink: 0;
}
.right-menu-item { .right-menu-item {
display: inline-block; display: inline-flex;
padding: 0 8px; align-items: center;
height: 100%; justify-content: center;
font-size: 18px; width: 32px;
color: var(--el-text-color-regular); height: 32px;
font-size: 16px;
color: var(--app-text-muted);
border-radius: 12px;
vertical-align: text-bottom; 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 { &.hover-effect {
cursor: pointer; cursor: pointer;
transition: background 0.3s; transition:
background 0.3s,
color 0.3s;
&:hover { &: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 { .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 { .avatar-wrapper {
margin-top: 5px;
position: relative; 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 { .user-avatar {
cursor: pointer; cursor: pointer;
width: 40px; width: 28px;
height: 40px; height: 28px;
border-radius: var(--app-radius-md); border-radius: 12px;
margin-top: 10px; object-fit: cover;
box-shadow: none;
} }
i { .avatar-meta {
cursor: pointer; display: flex;
position: absolute; flex-direction: column;
right: -20px; min-width: 0;
top: 25px; gap: 2px;
}
.avatar-name {
max-width: 88px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--app-text-title);
font-size: 12px; 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> </style>

View File

@ -1,5 +1,5 @@
<template> <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> <h3 class="drawer-title">菜单导航设置</h3>
<div class="nav-wrap"> <div class="nav-wrap">
<el-tooltip content="左侧菜单" placement="bottom"> <el-tooltip content="左侧菜单" placement="bottom">
@ -229,6 +229,15 @@ defineExpose({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.settings-drawer {
:deep(.el-drawer__body) {
display: flex;
flex-direction: column;
gap: 4px;
padding: 18px 18px 20px;
}
}
.setting-drawer-title { .setting-drawer-title {
margin-bottom: 12px; margin-bottom: 12px;
color: rgba(0, 0, 0, 0.85); color: rgba(0, 0, 0, 0.85);
@ -248,12 +257,23 @@ defineExpose({
.setting-drawer-block-checbox-item { .setting-drawer-block-checbox-item {
position: relative; position: relative;
margin-right: 16px; margin-right: 16px;
border-radius: 2px; border-radius: 12px;
cursor: pointer; 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 { img {
width: 48px; width: 48px;
height: 48px; height: 48px;
display: block;
} }
.custom-img { .custom-img {
@ -281,6 +301,8 @@ defineExpose({
.drawer-item { .drawer-item {
padding: 12px 0; padding: 12px 0;
font-size: 14px; font-size: 14px;
color: var(--app-text-title);
border-bottom: 1px solid rgba(148, 163, 184, 0.08);
.comp-style { .comp-style {
float: right; float: right;
@ -306,9 +328,16 @@ defineExpose({
cursor: pointer; cursor: pointer;
width: 56px; width: 56px;
height: 48px; height: 48px;
border-radius: 4px; border-radius: 12px;
background: #f0f2f5; background: var(--app-elevated-soft-bg);
border: 2px solid transparent; border: 2px solid transparent;
transition:
transform 0.2s ease,
border-color 0.2s ease;
&:hover {
transform: translateY(-1px);
}
} }
.left { .left {
@ -317,6 +346,7 @@ defineExpose({
height: 30%; height: 30%;
background: #fff; background: #fff;
} }
b:last-child { b:last-child {
width: 30%; width: 30%;
background: #1b2a47; background: #1b2a47;
@ -326,6 +356,7 @@ defineExpose({
border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px;
} }
} }
.mix { .mix {
b:first-child { b:first-child {
border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0;
@ -333,6 +364,7 @@ defineExpose({
height: 30%; height: 30%;
background: #1b2a47; background: #1b2a47;
} }
b:last-child { b:last-child {
width: 30%; width: 30%;
background: #1b2a47; background: #1b2a47;
@ -341,6 +373,7 @@ defineExpose({
border-radius: 0 0 0 4px; border-radius: 0 0 0 4px;
} }
} }
.top { .top {
b:first-child { b:first-child {
display: block; display: block;
@ -348,6 +381,10 @@ defineExpose({
background: #1b2a47; background: #1b2a47;
border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0;
} }
b:last-child {
display: none;
}
} }
} }
</style> </style>

View File

@ -21,7 +21,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import variables from '@/assets/styles/variables.module.scss';
import logo from '@/assets/logo/logo.png'; import logo from '@/assets/logo/logo.png';
import { useSettingsStore } from '@/store/modules/settings'; import { useSettingsStore } from '@/store/modules/settings';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -37,28 +36,11 @@ defineProps({
const title = import.meta.env.VITE_APP_LOGO_TITLE; const title = import.meta.env.VITE_APP_LOGO_TITLE;
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const sideTheme = computed(() => settingsStore.sideTheme); const sideTheme = computed(() => settingsStore.sideTheme);
const isTopNav = computed(() => settingsStore.navType === NavTypeEnum.TOP);
// Logo const isDarkSide = computed(() => !isTopNav.value && sideTheme.value === 'theme-dark');
const getLogoBackground = computed(() => { const logoSurface = computed(() => (isDarkSide.value ? 'rgba(255, 255, 255, 0.04)' : '#f8fafc'));
if (settingsStore.isDark) { const logoBorder = computed(() => (isDarkSide.value ? 'rgba(148, 163, 184, 0.12)' : '#e5e7eb'));
return 'var(--sidebar-bg)' const logoTextColor = computed(() => (isDarkSide.value ? '#f8fbff' : 'var(--app-text-title)'));
}
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
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -73,39 +55,61 @@ const getLogoTextColor = computed(() => {
.sidebar-logo-container { .sidebar-logo-container {
position: relative; position: relative;
height: 50px; flex-shrink: 0;
line-height: 50px; height: 46px;
background: v-bind(getLogoBackground); line-height: 46px;
padding: 0 8px;
margin-top: 8px;
background: transparent;
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
margin-bottom: 0;
& .sidebar-logo-link { & .sidebar-logo-link {
height: 100%; height: 100%;
width: 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 { & .sidebar-logo {
width: 32px; width: 28px;
height: 32px; height: 28px;
vertical-align: middle; vertical-align: middle;
margin-right: 12px; margin-right: 0;
margin-left: 12px; margin-left: 0;
border-radius: 10px;
box-shadow: none;
} }
& .sidebar-title { & .sidebar-title {
display: inline-block; display: inline-block;
margin: 0; margin: 0;
color: v-bind(getLogoTextColor); color: v-bind(logoTextColor);
font-weight: 600; font-weight: 600;
line-height: 50px; line-height: 1;
font-size: 14px; font-size: 15px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif; letter-spacing: 0.02em;
font-family:
'MiSans',
'HarmonyOS Sans SC',
'PingFang SC',
sans-serif;
vertical-align: middle; vertical-align: middle;
} }
} }
&.collapse { &.collapse {
.sidebar-logo-link {
padding: 0;
}
.sidebar-logo { .sidebar-logo {
margin-right: 0px; margin-right: 0;
} }
} }
} }

View File

@ -11,7 +11,7 @@
</app-link> </app-link>
</template> </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> <template v-if="item.meta" #title>
<svg-icon :icon-class="item.meta ? item.meta.icon : ''" /> <svg-icon :icon-class="item.meta ? item.meta.icon : ''" />
<span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span> <span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
@ -22,6 +22,7 @@
:key="child.path + index" :key="child.path + index"
:is-nest="true" :is-nest="true"
:item="child" :item="child"
:popper-class="popperClass"
:base-path="resolvePath(child.path)" :base-path="resolvePath(child.path)"
class="nest-menu" class="nest-menu"
/> />
@ -47,6 +48,10 @@ const props = defineProps({
basePath: { basePath: {
type: String, type: String,
default: '' default: ''
},
popperClass: {
type: String,
default: ''
} }
}); });

View File

@ -1,5 +1,5 @@
<template> <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" /> <logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper"> <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
<transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in"> <transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
@ -24,7 +24,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Logo from './Logo.vue'; import Logo from './Logo.vue';
import SidebarItem from './SidebarItem.vue'; import SidebarItem from './SidebarItem.vue';
import variables from '@/assets/styles/variables.module.scss';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useSettingsStore } from '@/store/modules/settings'; import { useSettingsStore } from '@/store/modules/settings';
import { usePermissionStore } from '@/store/modules/permission'; import { usePermissionStore } from '@/store/modules/permission';
@ -51,6 +50,37 @@ const activeMenu = computed(() => {
return path; return path;
}); });
const bgColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground)); const bgColor = computed(() => (sideTheme.value === 'theme-dark' ? '#111827' : '#ffffff'));
const textColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuColor : variables.menuLightColor)); const textColor = computed(() => (sideTheme.value === 'theme-dark' ? '#e5edf8' : '#1f2937'));
</script> </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>

View File

@ -1,30 +1,26 @@
<template> <template>
<el-menu class="topbar-menu" :ellipsis="false" :default-active="activeMenu" :active-text-color="theme" mode="horizontal"> <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> <template #title>
<span>更多菜单</span> <span>更多菜单</span>
</template> </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-sub-menu>
</el-menu> </el-menu>
</template> </template>
<script setup> <script setup lang="ts">
import SidebarItem from '../Sidebar/SidebarItem' import SidebarItem from '../Sidebar/SidebarItem'
import {useAppStore} from '@/store/modules/app'
import {useSettingsStore} from '@/store/modules/settings' import {useSettingsStore} from '@/store/modules/settings'
import {usePermissionStore} from '@/store/modules/permission' import {usePermissionStore} from '@/store/modules/permission'
const route = useRoute() const route = useRoute()
const appStore = useAppStore()
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore()
const sidebarRouters = computed(() => permissionStore.sidebarRouters)
const theme = computed(() => settingsStore.theme) const theme = computed(() => settingsStore.theme)
const device = computed(() => appStore.device)
const activeMenu = computed(() => { const activeMenu = computed(() => {
const { meta, path } = route const { meta, path } = route
if (meta.activeMenu) { if (meta.activeMenu) {
@ -38,14 +34,14 @@ const topMenus = computed(() => {
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(0, visibleNumber.value) return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(0, visibleNumber.value)
}) })
const moreRoutes = computed(() => { 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() { function setVisibleNumber() {
let width = document.body.getBoundingClientRect().width let width = document.body.getBoundingClientRect().width
if (width >= 1000) { 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(() => { onMounted(() => {
@ -61,43 +57,234 @@ onMounted(() => {
</script> </script>
<style lang="scss"> <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 */ display: flex;
#app .topbar-menu.el-menu--horizontal .el-sub-menu__title, #app .topbar-menu.el-menu--horizontal .el-menu-item { align-items: center;
padding: 0 10px !important; 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 { #app .topbar-menu.el-menu--horizontal > div > a > .el-menu-item,
float: left; #app .topbar-menu.el-menu--horizontal > div > .el-sub-menu > .el-sub-menu__title,
height: 50px !important; #app .topbar-menu.el-menu--horizontal > .el-sub-menu > .el-sub-menu__title {
line-height: 50px !important; display: inline-flex;
color: #303133 !important; align-items: center;
padding: 0 5px !important; justify-content: center;
margin: 0 10px !important; 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 { #app .topbar-menu.el-menu--horizontal > div > a > .el-menu-item:hover,
color: v-bind(theme); #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 */ #app .topbar-menu.el-menu--horizontal > div > a > .el-menu-item.is-active,
.topbar-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title { #app .topbar-menu.el-menu--horizontal > div > .el-sub-menu.is-active > .el-sub-menu__title,
float: left; #app .topbar-menu.el-menu--horizontal > .el-sub-menu.is-active > .el-sub-menu__title {
line-height: 50px !important; background: linear-gradient(180deg, var(--topbar-pill-hover-bg), var(--topbar-pill-active-bg)) !important;
color: #303133 !important; border-color: var(--topbar-pill-active-border) !important;
margin: 0 15px -3px!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 { .topbar-menu .el-sub-menu .el-sub-menu__icon-arrow {
position: static; position: static;
margin-left: 8px; margin: 0 0 0 2px;
margin-top: 0px;
display: block !important; display: block !important;
color: inherit;
font-size: 11px;
} }
/* menu__title el-menu-item */ .topbar-menu.el-menu--horizontal > div > a > .el-menu-item::after,
.topbar-menu.el-menu--horizontal .el-sub-menu__title, .topbar-menu.el-menu--horizontal .el-menu-item { .topbar-menu.el-menu--horizontal > div > .el-sub-menu > .el-sub-menu__title::after,
height: 60px; .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> </style>

View File

@ -145,6 +145,9 @@ defineExpose({
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
top: -53vh; top: -53vh;
padding: 0;
border-top: none;
background: transparent;
} }
} }
:deep(.el-autocomplete) { :deep(.el-autocomplete) {

View File

@ -66,59 +66,101 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.layout-navbars-breadcrumb-user-news { .layout-navbars-breadcrumb-user-news {
display: flex;
flex-direction: column;
min-width: 0;
.head-box { .head-box {
display: flex; 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; box-sizing: border-box;
color: var(--el-text-color-primary); color: var(--app-text-title);
justify-content: space-between; justify-content: space-between;
height: 35px; height: 40px;
align-items: center; align-items: center;
padding: 0 2px 10px;
.head-box-title {
font-size: 14px;
font-weight: 600;
}
.head-box-btn { .head-box-btn {
color: var(--el-color-primary); color: var(--app-accent-strong);
font-size: 13px; font-size: 12px;
font-weight: 600;
cursor: pointer; cursor: pointer;
opacity: 0.8; opacity: 0.8;
&:hover { &:hover {
opacity: 1; opacity: 1;
} }
} }
} }
.content-box { .content-box {
height: 300px; height: 300px;
overflow: auto; overflow: auto;
font-size: 13px; font-size: 13px;
padding: 8px 0;
.content-box-item { .content-box-item {
padding-top: 12px;
display: flex; display: flex;
&:last-of-type { gap: 10px;
padding-bottom: 12px; 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 { .content-box-msg {
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
margin-top: 5px; margin-top: 5px;
margin-bottom: 5px; margin-bottom: 5px;
} }
.content-box-time { .content-box-time {
color: var(--el-text-color-secondary); color: var(--app-text-muted);
font-size: 12px;
} }
.item-conten { .item-conten {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px;
color: var(--app-text-title);
line-height: 1.6;
}
.read {
flex-shrink: 0;
margin-top: 2px;
} }
} }
} }
.foot-box { .foot-box {
height: 35px; height: 40px;
color: var(--el-color-primary); color: var(--app-accent-strong);
font-size: 13px; font-size: 12px;
font-weight: 600;
cursor: pointer; cursor: pointer;
opacity: 0.8; opacity: 0.8;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: 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 { &:hover {
opacity: 1; opacity: 1;
} }

View File

@ -1,21 +1,13 @@
<template> <template>
<div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }"> <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" /> <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"> <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
<!-- <el-scrollbar> <div :class="{ 'fixed-header': fixedHeader }" class="layout-header">
<div :class="{ 'fixed-header': fixedHeader }"> <navbar @set-layout="setLayout" />
<navbar ref="navbarRef" @setLayout="setLayout" />
<tags-view v-if="needTagsView" /> <tags-view v-if="needTagsView" />
</div> </div>
<app-main /> <app-main :class="{ 'with-fixed-header': fixedHeader, 'with-tags-view': needTagsView }" />
<settings ref="settingRef" />
</el-scrollbar> -->
<div :class="{ 'fixed-header': fixedHeader }">
<navbar ref="navbarRef" @set-layout="setLayout" />
<tags-view v-if="needTagsView" />
</div>
<app-main />
<settings ref="settingRef" /> <settings ref="settingRef" />
</div> </div>
</div> </div>
@ -66,7 +58,6 @@ watchEffect(() => {
} }
}); });
const navbarRef = ref<InstanceType<typeof Navbar>>();
const settingRef = ref<InstanceType<typeof Settings>>(); const settingRef = ref<InstanceType<typeof Settings>>();
onMounted(() => { onMounted(() => {
@ -96,6 +87,7 @@ const setLayout = () => {
position: relative; position: relative;
height: 100%; height: 100%;
width: 100%; width: 100%;
background: var(--app-shell-bg);
&.mobile.openSidebar { &.mobile.openSidebar {
position: fixed; position: fixed;
@ -105,7 +97,7 @@ const setLayout = () => {
.drawer-bg { .drawer-bg {
background: #000; background: #000;
opacity: 0.3; opacity: 0.4;
width: 100%; width: 100%;
top: 0; top: 0;
height: 100%; height: 100%;
@ -113,18 +105,26 @@ const setLayout = () => {
z-index: 999; 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 { .fixed-header {
position: fixed; position: fixed;
top: 0; top: 0;
right: 0; right: 0;
z-index: 9; width: calc(100% - #{$base-sidebar-width} - 12px);
width: calc(100% - #{$base-sidebar-width});
transition: width 0.28s; transition: width 0.28s;
background: $fixed-header-bg;
} }
.hideSidebar .fixed-header { .hideSidebar .fixed-header {
width: calc(100% - 54px); width: calc(100% - 70px);
} }
.sidebarHide .fixed-header { .sidebarHide .fixed-header {
@ -133,5 +133,6 @@ const setLayout = () => {
.mobile .fixed-header { .mobile .fixed-header {
width: 100%; width: 100%;
top: 0;
} }
</style> </style>

View File

@ -71,7 +71,7 @@ export default {
loadingInstance = ElLoading.service({ loadingInstance = ElLoading.service({
lock: true, lock: true,
text: content, text: content,
background: 'rgba(0, 0, 0, 0.7)' background: 'var(--app-overlay-mask)'
}); });
}, },
// 关闭遮罩层 // 关闭遮罩层

View File

@ -31,7 +31,7 @@ const setting: DefaultSettings = {
/** /**
* *
*/ */
tagsIcon: false, tagsIcon: true,
/** /**
* *
@ -46,7 +46,7 @@ const setting: DefaultSettings = {
/** /**
* *
*/ */
dynamicTitle: false, dynamicTitle: true,
/** /**
* *

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell demo-demo-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="key键" prop="testKey">
<el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -17,30 +21,24 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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-button v-hasPermi="['demo:demo:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col> <el-button v-hasPermi="['demo:demo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
<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:export']" type="warning" plain icon="Download" @click="handleExport">导出</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" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column v-if="true" label="主键" align="center" prop="id" /> <el-table-column v-if="true" label="主键" align="center" prop="id" />
<el-table-column label="部门id" align="center" prop="deptId" /> <el-table-column label="部门id" align="center" prop="deptId" />

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell demo-tree-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="树节点名" prop="treeName">
<el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -14,23 +18,24 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </template>
<el-table <el-table
ref="treeTableRef" ref="treeTableRef"
v-loading="loading" v-loading="loading"
class="data-table"
:data="treeList" :data="treeList"
row-key="id" row-key="id"
border border

View File

@ -1,100 +1,101 @@
<template> <template>
<div class="app-container home"> <div class="home">
<el-row :gutter="20"> <section class="hero-panel">
<el-col :sm="24" :lg="12" style="padding-left: 20px"> <div class="hero-copy">
<h2>RuoYi-Vue-Plus后台管理系统</h2> <h1>RuoYi-Vue-Plus 控制台</h1>
<p> <p>企业级后台管理系统 重写 RuoYi-Vue 所有功能 集成 Sa-TokenMybatis-PlusWarmFlowSpringDocHutoolOSS 等组件</p>
RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 分布式集群 场景升级(不兼容原框架) <div class="hero-actions">
<br /> <el-button type="primary" @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">查看源码</el-button>
* 前端开发框架 Vue3TSElement Plus<br /> <el-button plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')">更新日志</el-button>
* 后端开发框架 Spring Boot<br /> </div>
* 容器框架 Undertow 基于 Netty 的高性能容器<br /> </div>
* 权限认证框架 Sa-Token 支持多终端认证系统<br /> </section>
* 关系数据库 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>
<el-col :sm="24" :lg="12" style="padding-left: 20px"> <div class="content-grid">
<h2>RuoYi-Cloud-Plus微服务后台管理系统</h2> <section class="section-card">
<p> <div class="section-head">
RuoYi-Cloud-Plus 微服务通用权限管理系统 重写 RuoYi-Cloud 全方位升级(不兼容原框架) <div>
<br /> <h2>项目矩阵</h2>
* 前端开发框架 Vue3TSElement UI<br /> </div>
* 后端开发框架 Spring Boot<br /> </div>
* 微服务开发框架 Spring CloudSpring Cloud Alibaba<br /> <div class="product-list">
* 容器框架 Undertow 基于 XNIO 的高性能容器<br /> <article v-for="product in products" :key="product.name" class="product-card">
* 权限认证框架 Sa-TokenJwt 支持多终端认证系统<br /> <div class="product-top">
* 关系数据库 MySQL 适配 8.X 最低 5.7<br /> <div>
* 关系数据库 Oracle 适配 11g 12c<br /> <h3>{{ product.name }}</h3>
* 关系数据库 PostgreSQL 适配 13 14<br /> <p>{{ product.summary }}</p>
* 关系数据库 SQLServer 适配 2017 2019<br /> </div>
* 缓存数据库 Redis 适配 6.X 最低 5.X<br /> <span class="product-version">{{ product.version }}</span>
* 分布式注册中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br /> </div>
* 分布式配置中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br /> <div class="product-tags">
* 服务网关 Spring Cloud Gateway 响应式高性能网关<br /> <el-tag v-for="tag in product.tags" :key="tag" effect="plain">{{ tag }}</el-tag>
* 负载均衡 Spring Cloud Loadbalancer 负载均衡处理<br /> </div>
* RPC远程调用 Apache Dubbo 原生态使用体验高性能<br /> <div class="product-actions">
* 分布式限流熔断 Alibaba Sentinel 无侵入高扩展<br /> <el-button type="primary" plain @click="goTarget(product.primaryUrl)">{{ product.primaryLabel }}</el-button>
* 分布式事务 Alibaba Seata 无侵入高扩展 支持 四种模式<br /> <el-button plain @click="goTarget(product.secondaryUrl)">{{ product.secondaryLabel }}</el-button>
* 分布式消息队列 Apache Kafka 高性能高速度<br /> </div>
* 分布式消息队列 Apache RocketMQ 高可用功能多样<br /> </article>
* 分布式消息队列 RabbitMQ 支持各种扩展插件功能多样性<br /> </div>
* 分布式搜索引擎 ElasticSearch 业界知名<br /> </section>
* 分布式链路追踪 Apache SkyWalking 链路追踪网格分析度量聚合可视化<br />
* 分布式日志中心 ELK 业界成熟解决方案<br /> <section class="section-card capability-card">
* 分布式监控 PrometheusGrafana 全方位性能监控<br /> <div class="section-head">
* 其余与 Vue 版本一致<br /> <div>
</p> <h2>能力地图</h2>
<p><b>当前版本:</b> <span>v2.5.3</span></p> </div>
<p> </div>
<el-tag type="danger">&yen;免费开源</el-tag> <div class="capability-groups">
</p> <article v-for="group in capabilityGroups" :key="group.title" class="capability-group">
<p> <h3>{{ group.title }}</h3>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Cloud-Plus')">访问码云</el-button> <ul>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Cloud-Plus')">访问GitHub</el-button> <li v-for="item in group.items" :key="item">{{ item }}</li>
<el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-cloud-plus/changlog')" </ul>
>更新日志</el-button </article>
> </div>
</p> </section>
</el-col> </div>
</el-row>
<el-divider />
</div> </div>
</template> </template>
<script setup name="Index" lang="ts"> <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) => { const goTarget = (url: string) => {
window.open(url, '__blank'); window.open(url, '__blank');
}; };
@ -102,64 +103,272 @@ const goTarget = (url: string) => {
<style lang="scss" scoped> <style lang="scss" scoped>
.home { .home {
blockquote { display: flex;
padding: 10px 20px; flex-direction: column;
margin: 0 0 20px; gap: 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;
} }
ul { .hero-panel,
padding: 0; .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; margin: 0;
} font-size: clamp(30px, 4vw, 46px);
line-height: 1.06;
font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; letter-spacing: -0.04em;
font-size: 13px; color: var(--app-text-title);
color: #676a6c;
overflow-x: hidden;
ul {
list-style-type: none;
}
h4 {
margin-top: 0px;
}
h2 {
margin-top: 10px;
font-size: 26px;
font-weight: 100;
} }
p { p {
margin-top: 10px; margin: 0;
max-width: 760px;
color: var(--app-text-muted);
font-size: 15px;
line-height: 1.8;
}
}
b { .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;
}
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; font-weight: 700;
} }
.product-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 18px;
} }
.update-log { .product-actions {
ol { display: flex;
display: block; flex-wrap: wrap;
list-style-type: decimal; gap: 10px;
margin-block-start: 1em; margin-top: 18px;
margin-block-end: 1em;
margin-inline-start: 0;
margin-inline-end: 0;
padding-inline-start: 40px;
} }
.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> </style>

View File

@ -1,15 +1,40 @@
<template> <template>
<div class="login"> <div class="login">
<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>
<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="loginRef" :model="loginForm" :rules="loginRules" class="login-form"> <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<div class="title-box"> <div class="title-box">
<div>
<p class="eyebrow">Workspace Sign In</p>
<h3 class="title">{{ title }}</h3> <h3 class="title">{{ title }}</h3>
<p class="subtitle">使用当前账号体系登录到业务工作台</p>
</div>
<lang-select /> <lang-select />
</div> </div>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('login.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> <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input <el-input
v-model="loginForm.password" v-model="loginForm.password"
@ -22,23 +47,24 @@
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template> <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="captchaEnabled" prop="code">
<el-input <el-form-item v-if="captchaEnabled" prop="code" class="captcha-row">
v-model="loginForm.code" <el-input v-model="loginForm.code" size="large" auto-complete="off" :placeholder="proxy.$t('login.code')" @keyup.enter="handleLogin">
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> <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
</el-input> </el-input>
<div class="login-code"> <div class="login-code">
<img :src="codeUrl" class="login-code-img" @click="getCode" /> <img :src="codeUrl" class="login-code-img" @click="getCode" />
</div> </div>
</el-form-item> </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"> <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')"> <el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">
<svg-icon icon-class="wechat" /> <svg-icon icon-class="wechat" />
</el-button> </el-button>
@ -54,20 +80,20 @@
<el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')"> <el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">
<svg-icon icon-class="github" /> <svg-icon icon-class="github" />
</el-button> </el-button>
</el-form-item> </div>
<el-form-item style="width: 100%"> </div>
<el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
<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-if="!loading">{{ proxy.$t('login.login') }}</span>
<span v-else>{{ proxy.$t('login.logging') }}</span> <span v-else>{{ proxy.$t('login.logging') }}</span>
</el-button> </el-button>
<div v-if="register" style="float: right">
<router-link class="link-type" :to="'/register'">{{ proxy.$t('login.switchRegisterPage') }}</router-link>
</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 底部 --> </div>
<div class="el-login-footer"> <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>
</div> </div>
</template> </template>
@ -84,6 +110,13 @@ import { useI18n } from 'vue-i18n';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const title = import.meta.env.VITE_APP_TITLE; 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 userStore = useUserStore();
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
@ -104,10 +137,8 @@ const loginRules: ElFormRules = {
const codeUrl = ref(''); const codeUrl = ref('');
const loading = ref(false); const loading = ref(false);
//
const captchaEnabled = ref(true); const captchaEnabled = ref(true);
// const register = ref(true);
const register = ref(false);
const redirect = ref('/'); const redirect = ref('/');
const loginRef = ref<ElFormInstance>(); const loginRef = ref<ElFormInstance>();
@ -123,18 +154,15 @@ const handleLogin = () => {
loginRef.value?.validate(async (valid: boolean, fields: any) => { loginRef.value?.validate(async (valid: boolean, fields: any) => {
if (valid) { if (valid) {
loading.value = true; loading.value = true;
// localStorage
if (loginForm.value.rememberMe) { if (loginForm.value.rememberMe) {
localStorage.setItem('username', String(loginForm.value.username)); localStorage.setItem('username', String(loginForm.value.username));
localStorage.setItem('password', String(loginForm.value.password)); localStorage.setItem('password', String(loginForm.value.password));
localStorage.setItem('rememberMe', String(loginForm.value.rememberMe)); localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
} else { } else {
//
localStorage.removeItem('username'); localStorage.removeItem('username');
localStorage.removeItem('password'); localStorage.removeItem('password');
localStorage.removeItem('rememberMe'); localStorage.removeItem('rememberMe');
} }
// action
const [err] = await to(userStore.login(loginForm.value)); const [err] = await to(userStore.login(loginForm.value));
if (!err) { if (!err) {
const redirectUrl = redirect.value || '/'; const redirectUrl = redirect.value || '/';
@ -142,7 +170,6 @@ const handleLogin = () => {
loading.value = false; loading.value = false;
} else { } else {
loading.value = false; loading.value = false;
//
if (captchaEnabled.value) { if (captchaEnabled.value) {
await getCode(); await getCode();
} }
@ -153,15 +180,11 @@ const handleLogin = () => {
}); });
}; };
/**
* 获取验证码
*/
const getCode = async () => { const getCode = async () => {
const res = await getCodeImg(); const res = await getCodeImg();
const { data } = res; const { data } = res;
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled; captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
if (captchaEnabled.value) { if (captchaEnabled.value) {
//
loginForm.value.code = ''; loginForm.value.code = '';
codeUrl.value = 'data:image/gif;base64,' + data.img; codeUrl.value = 'data:image/gif;base64,' + data.img;
loginForm.value.uuid = data.uuid; loginForm.value.uuid = data.uuid;
@ -179,14 +202,9 @@ const getLoginData = () => {
} as LoginData; } as LoginData;
}; };
/**
* 第三方登录
* @param type
*/
const doSocialLogin = (type: string) => { const doSocialLogin = (type: string) => {
authRouterUrl(type).then((res: any) => { authRouterUrl(type).then((res: any) => {
if (res.code === HttpStatus.SUCCESS) { if (res.code === HttpStatus.SUCCESS) {
//
window.location.href = res.data; window.location.href = res.data;
} else { } else {
ElMessage.error(res.msg); ElMessage.error(res.msg);
@ -202,105 +220,266 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.login { .login {
min-height: 100%;
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
align-items: center; padding: 40px 24px 88px;
height: 100%; background:
background-image: url('../assets/images/login-background.jpg'); radial-gradient(circle at 12% 12%, rgba(53, 109, 255, 0.22), transparent 24%),
background-size: cover; radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.18), transparent 24%), linear-gradient(135deg, #071120 0%, #0f1b33 42%, #15345f 100%);
background-position: center;
} }
.title-box { .login-shell {
width: min(1180px, 100%);
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(360px, 440px);
gap: 26px;
align-items: stretch;
}
.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; display: flex;
align-items: center; flex-direction: column;
gap: 8px; justify-content: space-between;
color: #eef4ff;
.title { background:
margin: 0px auto 26px auto; linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)),
text-align: center; linear-gradient(135deg, rgba(53, 109, 255, 0.32), rgba(15, 23, 42, 0.24));
color: var(--el-text-color-primary);
font-weight: 600;
letter-spacing: 0.5px;
} }
:deep(.lang-select--style) { .brand-pill {
line-height: 0; display: inline-flex;
color: var(--el-text-color-secondary); 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;
}
span {
color: rgba(226, 232, 240, 0.72);
font-size: 13px;
} }
} }
.login-form { .login-form {
border-radius: var(--app-radius-lg); width: 100%;
background: rgba(255, 255, 255, 0.94); padding: 34px 30px 26px;
border: 1px solid rgba(255, 255, 255, 0.5);
width: min(420px, 90vw);
padding: 32px 30px 12px 30px;
z-index: 1; z-index: 1;
box-shadow: var(--app-shadow-lg); background: rgba(248, 250, 252, 0.92);
backdrop-filter: blur(6px); }
-webkit-backdrop-filter: blur(6px);
.el-input { .title-box {
height: 40px; display: flex;
input { align-items: flex-start;
height: 40px; 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;
}
.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);
} }
} }
.input-icon { .login-form .el-input {
height: 39px; height: 48px;
}
.login-form .input-icon {
height: 46px;
width: 14px; width: 14px;
margin-left: 0px; margin-left: 0;
}
.captcha-row {
:deep(.el-form-item__content) {
display: grid;
grid-template-columns: minmax(0, 1fr) 122px;
gap: 12px;
} }
} }
.login-tip { .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; font-size: 13px;
text-align: center; font-weight: 600;
color: #bfbfbf; }
.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) { .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) { .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) { .login-form :deep(.el-checkbox__label) {
border-radius: var(--app-radius-md); color: var(--app-text-muted);
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.25);
} }
.login-form :deep(.el-button.is-circle) { .login-form :deep(.el-button.is-circle) {
background: rgba(15, 23, 42, 0.04); background: rgba(255, 255, 255, 0.82);
border: 1px solid rgba(15, 23, 42, 0.08); border: 1px solid rgba(148, 163, 184, 0.16);
color: var(--el-text-color-regular); color: var(--app-text-muted);
} }
.login-form :deep(.el-button.is-circle:hover) { .login-form :deep(.el-button.is-circle:hover) {
background: rgba(59, 130, 246, 0.1); background: rgba(53, 109, 255, 0.12);
border-color: rgba(59, 130, 246, 0.2); border-color: rgba(53, 109, 255, 0.2);
color: var(--app-accent-strong);
} }
.login-code { .login-code {
width: calc(37% - 10px); height: 48px;
height: 40px;
float: right;
margin-left: 10px;
box-sizing: border-box; box-sizing: border-box;
border-radius: var(--app-radius-sm); border-radius: 16px;
overflow: hidden; overflow: hidden;
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.88);
border: 1px solid var(--el-border-color-light); border: 1px solid rgba(148, 163, 184, 0.14);
img { img {
cursor: pointer; cursor: pointer;
vertical-align: middle; vertical-align: middle;
display: block; display: block;
width: 100%; width: 100%;
height: 40px; height: 100%;
object-fit: cover; object-fit: cover;
} }
} }
@ -312,35 +491,74 @@ onMounted(() => {
bottom: 0; bottom: 0;
width: 100%; width: 100%;
text-align: center; text-align: center;
color: rgba(255, 255, 255, 0.75); color: rgba(226, 232, 240, 0.68);
font-family: Arial, serif;
font-size: 12px; font-size: 12px;
letter-spacing: 1px; letter-spacing: 0.08em;
} }
.login-code-img { .login-code-img {
height: 40px; height: 48px;
padding-left: 0; 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) { :global(html.dark) {
.login-form { .login-form {
background: rgba(17, 24, 39, 0.9); background: rgba(12, 18, 30, 0.88);
border-color: rgba(148, 163, 184, 0.2); border-color: rgba(100, 116, 139, 0.2);
}
.login-form :deep(.el-input__wrapper) {
background-color: rgba(17, 24, 39, 0.7);
} }
.login-form :deep(.el-input__wrapper),
.login-code,
.title-box :deep(.lang-select--style),
.social-panel,
.login-form :deep(.el-button.is-circle) { .login-form :deep(.el-button.is-circle) {
background: rgba(148, 163, 184, 0.12); background: rgba(15, 23, 42, 0.56);
border-color: rgba(148, 163, 184, 0.25); border-color: rgba(100, 116, 139, 0.2);
color: #e5e7eb;
}
.el-login-footer {
color: rgba(226, 232, 240, 0.65);
} }
} }
</style> </style>

View File

@ -1,9 +1,20 @@
<template> <template>
<div> <div class="p-2 page-shell iframe-page">
<div class="iframe-page__inner">
<i-frame v-model:src="url"></i-frame> <i-frame v-model:src="url"></i-frame>
</div> </div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const url = ref(import.meta.env.VITE_APP_MONITOR_ADMIN); const url = ref(import.meta.env.VITE_APP_MONITOR_ADMIN);
</script> </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>

View File

@ -1,14 +1,18 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell monitor-cache-page">
<el-row :gutter="10"> <el-row :gutter="12" class="cache-grid">
<el-col :span="24" class="card-box"> <el-col :span="24">
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<Monitor style="width: 1em; height: 1em; vertical-align: middle" /> <div class="toolbar-shell">
<span style="vertical-align: middle">基本信息</span> <div class="table-heading">
<h3>缓存概览</h3>
<p>Redis 运行状态资源消耗与实时统计</p>
</div>
</div>
</template> </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%"> <table style="width: 100%">
<tbody> <tbody>
<tr> <tr>
@ -97,24 +101,31 @@
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="12" class="card-box"> <el-col :xs="24" :lg="12">
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<PieChart style="width: 1em; height: 1em; vertical-align: middle" /> <div class="toolbar-shell">
<span style="vertical-align: middle">命令统计</span> <div class="table-heading">
<h3>命令统计</h3>
</div>
</div>
</template> </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 ref="commandstats" style="height: 420px" />
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="12" class="card-box"> <el-col :xs="24" :lg="12">
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <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> </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 ref="usedmemory" style="height: 420px" />
</div> </div>
</el-card> </el-card>
@ -190,3 +201,25 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,9 +1,16 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell monitor-logininfo-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="登录地址" prop="ipaddr">
<el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -33,35 +40,34 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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 v-hasPermi="['monitor:logininfo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除 删除
</el-button> </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-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 v-hasPermi="['monitor:logininfo:unlock']" type="primary" plain icon="Unlock" :disabled="single" @click="handleUnlock">
解锁 解锁
</el-button> </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-button v-hasPermi="['monitor:logininfo:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </template>
<el-table <el-table
ref="loginInfoTableRef" ref="loginInfoTableRef"
v-loading="loading" v-loading="loading"
:data="loginInfoList" :data="loginInfoList"
class="data-table"
:default-sort="defaultSort" :default-sort="defaultSort"
border border
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
@ -207,3 +213,25 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,8 +1,16 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell monitor-online-page">
<div class="mb-[10px]"> <div class="search-wrap">
<el-card shadow="hover"> <el-card shadow="hover" class="search-panel">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="登录地址" prop="ipaddr">
<el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -16,10 +24,20 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </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 <el-table
v-loading="loading" v-loading="loading"
border border
class="data-table"
:data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)" :data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)"
style="width: 100%" style="width: 100%"
> >
@ -115,3 +133,19 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,9 +1,16 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell monitor-operlog-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="操作地址" prop="operIp">
<el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -41,30 +48,31 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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 v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除 删除
</el-button> </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-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-button v-hasPermi="['monitor:operlog:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </template>
<el-table <el-table
ref="operLogTableRef" ref="operLogTableRef"
v-loading="loading" v-loading="loading"
:data="operlogList" :data="operlogList"
class="data-table"
border border
:default-sort="defaultSort" :default-sort="defaultSort"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
@ -259,3 +267,25 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,9 +1,20 @@
<template> <template>
<div> <div class="p-2 page-shell iframe-page">
<div class="iframe-page__inner">
<i-frame v-model:src="url"></i-frame> <i-frame v-model:src="url"></i-frame>
</div> </div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const url = ref(import.meta.env.VITE_APP_SNAILJOB_ADMIN); const url = ref(import.meta.env.VITE_APP_SNAILJOB_ADMIN);
</script> </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>

View File

@ -1,10 +1,34 @@
<template> <template>
<div class="register"> <div class="register">
<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>
<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"> <el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
<div class="title-box"> <div class="title-box">
<div>
<p class="eyebrow">Workspace Register</p>
<h3 class="title">{{ title }}</h3> <h3 class="title">{{ title }}</h3>
<p class="subtitle">创建新的业务工作台账号接入当前系统权限与登录体系</p>
</div>
<lang-select /> <lang-select />
</div> </div>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="registerForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('register.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> <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
@ -34,34 +58,31 @@
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template> <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="captchaEnabled" prop="code"> <el-form-item v-if="captchaEnabled" prop="code" class="captcha-row">
<el-input <el-input v-model="registerForm.code" size="large" auto-complete="off" :placeholder="proxy.$t('register.code')" @keyup.enter="handleRegister">
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> <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
</el-input> </el-input>
<div class="register-code"> <div class="register-code">
<img :src="codeUrl" class="register-code-img" @click="getCode" /> <img :src="codeUrl" class="register-code-img" @click="getCode" />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item style="width: 100%">
<el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleRegister"> <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 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-if="!loading">{{ proxy.$t('register.register') }}</span>
<span v-else>{{ proxy.$t('register.registering') }}</span> <span v-else>{{ proxy.$t('register.registering') }}</span>
</el-button> </el-button>
<div style="float: right">
<router-link class="link-type" :to="'/login'">{{ proxy.$t('register.switchLoginPage') }}</router-link>
</div>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 底部 --> </div>
<div class="el-register-footer"> <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>
</div> </div>
</template> </template>
@ -75,6 +96,13 @@ import { useI18n } from 'vue-i18n';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const title = import.meta.env.VITE_APP_TITLE; 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 router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
@ -157,67 +185,234 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.register { .register {
min-height: 100%;
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
height: 100%; justify-content: center;
background-image: url('../assets/images/login-background.jpg'); padding: 40px 24px 88px;
background-size: cover; 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 { .register-shell {
width: min(1180px, 100%);
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(360px, 440px);
gap: 26px;
align-items: stretch;
}
.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; display: flex;
flex-direction: column;
.title { justify-content: space-between;
margin: 0px auto 30px auto; color: #eef4ff;
text-align: center; background:
color: #707070; 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));
} }
:deep(.lang-select--style) { .brand-pill {
line-height: 0; display: inline-flex;
color: #7483a3; 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;
}
span {
color: rgba(226, 232, 240, 0.72);
font-size: 13px;
} }
} }
.register-form { .register-form {
border-radius: 6px; width: 100%;
background: #ffffff; padding: 34px 30px 26px;
width: 400px; z-index: 1;
padding: 25px 25px 5px 25px; background: rgba(248, 250, 252, 0.92);
}
.el-input { .title-box {
height: 40px; display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 28px;
input { .eyebrow {
height: 40px; margin: 0 0 8px;
color: var(--app-accent-strong);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.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);
} }
} }
.input-icon { .register-form .el-input {
height: 39px; height: 48px;
}
.register-form .input-icon {
height: 46px;
width: 14px; width: 14px;
margin-left: 0; 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 { .register-tip {
color: var(--app-text-muted);
font-size: 13px; font-size: 13px;
text-align: center;
color: #bfbfbf;
} }
.register-code { .register-code {
width: 33%; height: 48px;
height: 40px; box-sizing: border-box;
float: right; border-radius: 16px;
overflow: hidden;
background: rgba(255, 255, 255, 0.88);
border: 1px solid rgba(148, 163, 184, 0.14);
img { img {
cursor: pointer; cursor: pointer;
vertical-align: middle; 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 { .el-register-footer {
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
@ -225,14 +420,68 @@ onMounted(() => {
bottom: 0; bottom: 0;
width: 100%; width: 100%;
text-align: center; text-align: center;
color: #fff; color: rgba(226, 232, 240, 0.68);
font-family: Arial, serif;
font-size: 12px; font-size: 12px;
letter-spacing: 1px; letter-spacing: 0.08em;
} }
.register-code-img { .register-code-img {
height: 40px; height: 48px;
padding-left: 12px; 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> </style>

View File

@ -1,8 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell system-client-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="search"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="85px"> <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-form-item label="客户端key" prop="clientKey">
<el-input v-model="queryParams.clientKey" placeholder="请输入客户端key" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.clientKey" placeholder="请输入客户端key" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -19,33 +24,30 @@
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card>
</div> </div>
</transition>
<el-card shadow="never"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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 v-hasPermi="['system:client:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改 修改
</el-button> </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 v-hasPermi="['system:client:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除 删除
</el-button> </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-button v-hasPermi="['system:client:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column v-if="true" label="id" align="center" prop="id" /> <el-table-column v-if="true" label="id" align="center" prop="id" />
<el-table-column label="客户端id" align="center" prop="clientId" /> <el-table-column label="客户端id" align="center" prop="clientId" />

View File

@ -1,9 +1,16 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell system-config-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="参数名称" prop="configName">
<el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -33,34 +40,31 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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 v-hasPermi="['system:config:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
修改 修改
</el-button> </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 v-hasPermi="['system:config:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除 删除
</el-button> </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-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-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column v-if="false" label="参数主键" align="center" prop="configId" /> <el-table-column v-if="false" label="参数主键" align="center" prop="configId" />
<el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" /> <el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
@ -259,3 +263,25 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,9 +1,16 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell system-dept-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="部门名称" prop="deptName">
<el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -22,24 +29,27 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10"> <div class="toolbar-shell">
<el-col :span="1.5"> <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 v-hasPermi="['system:dept: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-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </template>
<el-table <el-table
ref="deptTableRef" ref="deptTableRef"
v-loading="loading" v-loading="loading"
class="data-table"
:data="deptList" :data="deptList"
row-key="deptId" row-key="deptId"
border border
@ -318,3 +328,25 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,35 +1,32 @@
<template> <template>
<div class="p-2 dict-page"> <div class="p-2 page-shell dict-page">
<el-row :gutter="16" class="dict-grid"> <el-row :gutter="16" class="dict-grid">
<!-- 字典类型 --> <!-- 字典类型 -->
<el-col :xs="24" :lg="12"> <el-col :xs="24" :lg="12">
<el-card shadow="hover" class="dict-card"> <el-card shadow="hover" class="dict-card table-panel">
<template #header> <template #header>
<div class="dict-card__header"> <div class="toolbar-shell dict-card__header">
<div class="dict-card__title">字典管理</div> <div class="table-heading">
<right-toolbar v-model:show-search="showTypeSearch" @query-table="getTypeList" /> <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> </div>
</template> </template>
<div v-show="showTypeSearch" class="dict-form-scroll"> <div class="dict-search" :class="{ 'is-collapsed': !showTypeSearch }">
<el-form ref="typeQueryFormRef" :model="typeQueryParams" :inline="true"> <el-form ref="typeQueryFormRef" :model="typeQueryParams" :inline="true" class="query-form">
<el-form-item label="字典名称" prop="dictName"> <el-form-item label="字典名称" prop="dictName">
<el-input v-model="typeQueryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleTypeQuery" /> <el-input v-model="typeQueryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleTypeQuery" />
</el-form-item> </el-form-item>
<el-form-item label="字典类型" prop="dictType"> <el-form-item label="字典类型" prop="dictType">
<el-input v-model="typeQueryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleTypeQuery" /> <el-input v-model="typeQueryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleTypeQuery" />
</el-form-item> </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-form-item>
<el-button type="primary" icon="Search" @click="handleTypeQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleTypeQuery">搜索</el-button>
<el-button icon="Refresh" @click="handleTypeResetQuery">重置</el-button> <el-button icon="Refresh" @click="handleTypeResetQuery">重置</el-button>
@ -37,7 +34,7 @@
</el-form> </el-form>
</div> </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: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 v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="typeSingle" @click="handleTypeUpdate()"
>修改</el-button >修改</el-button
@ -54,6 +51,7 @@
ref="typeTableRef" ref="typeTableRef"
v-loading="typeLoading" v-loading="typeLoading"
border border
class="data-table"
:data="typeList" :data="typeList"
highlight-current-row highlight-current-row
@row-click="handleTypeRowClick" @row-click="handleTypeRowClick"
@ -98,19 +96,25 @@
<!-- 字典数据 --> <!-- 字典数据 -->
<el-col :xs="24" :lg="12"> <el-col :xs="24" :lg="12">
<el-card shadow="hover" class="dict-card"> <el-card shadow="hover" class="dict-card table-panel">
<template #header> <template #header>
<div class="dict-card__header"> <div class="toolbar-shell dict-card__header">
<div class="dict-card__title"> <div class="table-heading">
字典数据 <div class="panel-heading search-panel-toggle dict-title-toggle" @click.stop="showDataSearch = !showDataSearch">
<span class="dict-card__subtitle">{{ currentDictLabel }}</span> <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> </div>
<right-toolbar v-model:show-search="showDataSearch" @query-table="getDataList" />
</div> </div>
</template> </template>
<div v-show="showDataSearch" class="dict-form-scroll"> <div class="dict-search" :class="{ 'is-collapsed': !showDataSearch }">
<el-form ref="dataQueryFormRef" :model="dataQueryParams" :inline="true"> <el-form ref="dataQueryFormRef" :model="dataQueryParams" :inline="true" class="query-form">
<el-form-item label="字典标签" prop="dictLabel"> <el-form-item label="字典标签" prop="dictLabel">
<el-input <el-input
v-model="dataQueryParams.dictLabel" v-model="dataQueryParams.dictLabel"
@ -127,7 +131,7 @@
</el-form> </el-form>
</div> </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 v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" :disabled="!hasCurrentDict" @click="handleDataAdd"
>新增</el-button >新增</el-button
> >
@ -155,10 +159,10 @@
</div> </div>
<div class="dict-table-wrap"> <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 type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" /> <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"> <template #default="scope">
<span <span
v-if=" v-if="
@ -174,7 +178,7 @@
> >
</template> </template>
</el-table-column> </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="dictSort" width="80" />
<el-table-column label="备注" align="center" prop="remark" width="100" /> <el-table-column label="备注" align="center" prop="remark" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180"> <el-table-column label="创建时间" align="center" prop="createTime" width="180">
@ -213,13 +217,15 @@
<el-input v-model="typeForm.dictName" placeholder="请输入字典名称" /> <el-input v-model="typeForm.dictName" placeholder="请输入字典名称" />
</el-form-item> </el-form-item>
<el-form-item prop="dictType"> <el-form-item prop="dictType">
<el-input v-model="typeForm.dictType" placeholder="请输入字典类型" maxlength="100" /> <template #label>
<span slot="label"> <span>
<el-tooltip content="数据存储中的Key值sys_user_sex" placement="top"> <el-tooltip content="数据存储中的Key值sys_user_sex" placement="top">
<i class="el-icon-question"></i> <i class="el-icon-question"></i>
</el-tooltip> </el-tooltip>
字典类型 字典类型
</span> </span>
</template>
<el-input v-model="typeForm.dictType" placeholder="请输入字典类型" maxlength="100" />
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="remark"> <el-form-item label="备注" prop="remark">
<el-input v-model="typeForm.remark" type="textarea" placeholder="请输入内容"></el-input> <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 typeSingle = ref(true);
const typeMultiple = ref(true); const typeMultiple = ref(true);
const typeTotal = ref(0); const typeTotal = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const typeFormRef = ref<ElFormInstance>(); const typeFormRef = ref<ElFormInstance>();
const typeQueryFormRef = ref<ElFormInstance>(); const typeQueryFormRef = ref<ElFormInstance>();
@ -387,7 +392,7 @@ const { queryParams: dataQueryParams, form: dataForm, rules: dataRules } = toRef
const getTypeList = () => { const getTypeList = () => {
typeLoading.value = true; typeLoading.value = true;
listType(proxy?.addDateRange(typeQueryParams.value, dateRange.value)).then((res) => { listType(typeQueryParams.value).then((res) => {
typeList.value = res.rows; typeList.value = res.rows;
typeTotal.value = res.total; typeTotal.value = res.total;
typeLoading.value = false; typeLoading.value = false;
@ -438,7 +443,6 @@ const handleTypeQuery = () => {
}; };
const handleTypeResetQuery = () => { const handleTypeResetQuery = () => {
dateRange.value = ['', ''];
typeQueryFormRef.value?.resetFields(); typeQueryFormRef.value?.resetFields();
handleTypeQuery(); handleTypeQuery();
}; };
@ -614,54 +618,55 @@ onMounted(() => {
row-gap: 16px; row-gap: 16px;
} }
.dict-card__header { .dict-card {
display: flex; height: 100%;
align-items: center;
justify-content: space-between;
gap: 8px;
} }
.dict-card__title { .dict-card__header {
display: inline-flex; gap: 12px;
align-items: baseline; }
gap: 8px;
font-weight: 600; .dict-title-toggle {
padding: 0 !important;
} }
.dict-card__subtitle { .dict-card__subtitle {
margin: 4px 0 0;
font-size: 12px; font-size: 12px;
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
} }
.dict-form-scroll { .dict-search {
max-height: 200px; overflow: hidden;
overflow: auto; max-height: 240px;
opacity: 1;
margin-bottom: 12px; margin-bottom: 12px;
padding-right: 6px; transition:
padding-bottom: 4px; 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) { .dict-search.is-collapsed {
display: flex; max-height: 0;
flex-wrap: wrap; opacity: 0;
column-gap: 12px; margin-bottom: 0;
row-gap: 10px; pointer-events: none;
} }
.dict-form-scroll :deep(.el-form-item) { .dict-search :deep(.el-form-item) {
margin: 0; margin-bottom: 0;
} }
.dict-actions { .dict-actions {
display: flex; margin: 0 0 12px;
flex-wrap: wrap; padding-bottom: 0;
gap: 10px;
margin: 8px 0 12px;
} }
.dict-actions :deep(.el-button) { .dict-actions :deep(.el-button) {
height: 32px; height: 32px;
padding: 0 14px; padding: 0 14px;
border-radius: 10px !important;
} }
.dict-actions :deep(.el-button + .el-button) { .dict-actions :deep(.el-button + .el-button) {
@ -671,5 +676,4 @@ onMounted(() => {
.dict-table-wrap { .dict-table-wrap {
overflow-x: auto; overflow-x: auto;
} }
</style> </style>

View File

@ -1,9 +1,16 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell system-menu-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="菜单名称" prop="menuName">
<el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -19,24 +26,27 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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-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" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </template>
<el-table <el-table
ref="menuTableRef" ref="menuTableRef"
v-loading="loading" v-loading="loading"
class="data-table"
:data="menuList" :data="menuList"
row-key="menuId" row-key="menuId"
border border
@ -532,6 +542,28 @@ onMounted(() => {
}); });
</script> </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"> <style scoped lang="scss">
.tree-border { .tree-border {
height: 300px; height: 300px;

View File

@ -1,9 +1,16 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell system-notice-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="queryParams.noticeTitle" placeholder="请输入公告标题" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.noticeTitle" placeholder="请输入公告标题" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -22,29 +29,29 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-button v-hasPermi="['system:notice:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col> <el-button v-hasPermi="['system:notice:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
<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
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:notice:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"> <el-button v-hasPermi="['system:notice:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除 删除
</el-button> </el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column v-if="false" label="序号" align="center" prop="noticeId" width="100" /> <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" /> <el-table-column label="公告标题" align="center" prop="noticeTitle" :show-overflow-tooltip="true" />
@ -241,3 +248,25 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,9 +1,16 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell system-oss-config-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="配置key" prop="configKey">
<el-input v-model="queryParams.configKey" placeholder="配置key" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.configKey" placeholder="配置key" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -23,29 +30,29 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-button v-hasPermi="['system:ossConfig:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col> <el-button v-hasPermi="['system:ossConfig:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
<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
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:ossConfig:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"> <el-button v-hasPermi="['system:ossConfig:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除 删除
</el-button> </el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 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[0].visible" label="主建" align="center" prop="ossConfigId" />
<el-table-column v-if="columns[1].visible" label="配置key" align="center" prop="configKey" /> <el-table-column v-if="columns[1].visible" label="配置key" align="center" prop="configKey" />
@ -342,3 +349,25 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,9 +1,16 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell system-oss-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="文件名" prop="fileName">
<el-input v-model="queryParams.fileName" placeholder="请输入文件名" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.fileName" placeholder="请输入文件名" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -34,42 +41,40 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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-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 v-hasPermi="['system:oss:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除 删除
</el-button> </el-button>
</el-col>
<el-col :span="1.5">
<el-button <el-button
v-hasPermi="['system:oss:edit']" v-hasPermi="['system:oss:edit']"
:type="previewListResource ? 'danger' : 'warning'" :type="previewListResource ? 'danger' : 'warning'"
plain plain
@click="handlePreviewListResource(!previewListResource)" @click="handlePreviewListResource(!previewListResource)"
>预览开关 : {{ previewListResource ? '禁用' : '启用' }}</el-button
> >
</el-col> 预览开关 : {{ previewListResource ? '禁用' : '启用' }}
<el-col :span="1.5"> </el-button>
<el-button v-hasPermi="['system:ossConfig:list']" type="info" plain icon="Operation" @click="handleOssConfig">配置管理</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" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </template>
<el-table <el-table
v-if="showTable" v-if="showTable"
v-loading="loading" v-loading="loading"
:data="ossList" :data="ossList"
class="data-table"
border border
:header-cell-class-name="handleHeaderClass" :header-cell-class-name="handleHeaderClass"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
@ -307,12 +312,10 @@ const handleDownload = (row: OssVO) => {
}; };
/** 预览开关按钮 */ /** 预览开关按钮 */
const handlePreviewListResource = async (preview: boolean) => { const handlePreviewListResource = async (preview: boolean) => {
const text = preview ? '启用' : '停用';
try { try {
await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?');
await proxy?.updateConfigByKey('sys.oss.previewListResource', preview); await proxy?.updateConfigByKey('sys.oss.previewListResource', preview);
await getList(); await getList();
proxy?.$modal.msgSuccess(text + '成功'); proxy?.$modal.msgSuccess((preview ? '启用' : '停用') + '成功');
} catch { } catch {
return; return;
} }
@ -331,3 +334,25 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,13 +1,21 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell system-post-page">
<el-row :gutter="20"> <el-row :gutter="20" class="content-grid">
<!-- 部门树 --> <!-- 部门树 -->
<el-col :lg="4" :xs="24" style=""> <el-col :lg="5" :xs="24">
<el-card shadow="hover"> <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-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree <el-tree
ref="deptTreeRef" ref="deptTreeRef"
class="mt-2" class="mt-2 dept-tree"
node-key="id" node-key="id"
:data="deptOptions" :data="deptOptions"
:props="{ label: 'label', children: 'children' } as any" :props="{ label: 'label', children: 'children' } as any"
@ -19,11 +27,18 @@
/> />
</el-card> </el-card>
</el-col> </el-col>
<el-col :lg="20" :xs="24"> <el-col :lg="19" :xs="24" class="content-main">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="岗位编码" prop="postCode">
<el-input v-model="queryParams.postCode" placeholder="请输入岗位编码" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.postCode" placeholder="请输入岗位编码" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -61,30 +76,28 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition> <el-card shadow="hover" class="table-panel">
<el-card shadow="hover">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-button v-hasPermi="['system:post:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col> <el-button v-hasPermi="['system:post:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
<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
>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:post:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"> <el-button v-hasPermi="['system:post:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除 删除
</el-button> </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-button v-hasPermi="['system:post:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column v-if="false" label="岗位编号" align="center" prop="postId" /> <el-table-column v-if="false" label="岗位编号" align="center" prop="postId" />
<el-table-column label="岗位编码" align="center" prop="postCode" /> <el-table-column label="岗位编码" align="center" prop="postCode" />
@ -359,3 +372,39 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,8 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell role-auth-user-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="search"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -14,26 +19,25 @@
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card>
</div> </div>
</transition> <el-card shadow="hover" class="table-panel">
<el-card shadow="never">
<template #header> <template #header>
<el-row :gutter="10"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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 v-hasPermi="['system:role:remove']" type="danger" plain icon="CircleClose" :disabled="multiple" @click="cancelAuthUserAll">
批量取消授权 批量取消授权
</el-button> </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button> <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" :search="true" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" /> <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="nickName" :show-overflow-tooltip="true" />

View File

@ -1,9 +1,16 @@
<template> <template>
<div class="p-2"> <div class="page-shell system-role-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="角色名称" prop="roleName">
<el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -34,28 +41,26 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
</el-col> <el-button v-hasPermi="['system:role:remove']" type="danger" plain :disabled="ids.length === 0" icon="Delete" @click="handleDelete()">删除</el-button>
<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:export']" type="warning" plain icon="Download" @click="handleExport">导出</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" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column v-if="false" label="角色编号" prop="roleId" width="120" /> <el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" /> <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
@ -501,3 +506,25 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,7 +1,9 @@
<template> <template>
<el-row> <el-row>
<el-dialog v-model="visible" title="选择用户" width="800px" top="5vh" append-to-body> <el-dialog v-model="visible" title="选择用户" width="800px" top="5vh" append-to-body class="select-user-dialog">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -13,8 +15,10 @@
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-row> </el-card>
<el-table ref="tableRef" border :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
<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 type="selection" width="55"></el-table-column>
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" /> <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="nickName" :show-overflow-tooltip="true" />
@ -32,7 +36,8 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-if="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> <pagination v-if="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-row> </el-card>
</div>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" @click="handleSelectUser"> </el-button> <el-button type="primary" @click="handleSelectUser"> </el-button>
@ -128,3 +133,17 @@ defineExpose({
show show
}); });
</script> </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>

View File

@ -1,29 +1,38 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell auth-role-page">
<div class="panel"> <el-card shadow="hover" class="search-panel auth-role-info">
<h4 class="panel-title">基本信息</h4> <template #header>
<el-form :model="form" :inline="true"> <div class="panel-heading">
<el-row :gutter="10"> <div><h3>基本信息</h3></div>
<el-col :span="2.5"> </div>
</template>
<el-form :model="form" :inline="true" class="query-form auth-role-form">
<el-form-item label="用户昵称" prop="nickName"> <el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" disabled /> <el-input v-model="form.nickName" disabled />
</el-form-item> </el-form-item>
</el-col>
<el-col :span="2.5">
<el-form-item label="登录账号" prop="userName"> <el-form-item label="登录账号" prop="userName">
<el-input v-model="form.userName" disabled /> <el-input v-model="form.userName" disabled />
</el-form-item> </el-form-item>
</el-col>
</el-row>
</el-form> </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>
<div class="panel"> <div class="toolbar-actions">
<h4 class="panel-title">角色信息</h4> <el-button type="primary" @click="submitForm()">提交</el-button>
<div> <el-button @click="close()">返回</el-button>
</div>
</div>
</template>
<el-table <el-table
ref="tableRef" ref="tableRef"
v-loading="loading" v-loading="loading"
border border
class="data-table"
:row-key="getRowKey" :row-key="getRowKey"
:data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)" :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
@row-click="clickRow" @row-click="clickRow"
@ -45,13 +54,7 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total > 0" v-model:page="pageNum" v-model:limit="pageSize" :total="total" /> <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-card>
<el-button type="primary" @click="submitForm()">提交</el-button>
<el-button @click="close()">返回</el-button>
</div>
<div></div>
</div>
</div>
</div> </div>
</template> </template>
@ -144,3 +147,17 @@ onMounted(() => {
getList(); getList();
}); });
</script> </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>

View File

@ -1,13 +1,21 @@
<template> <template>
<div class="p-2"> <div class="page-shell system-user-page">
<el-row :gutter="20"> <el-row :gutter="20" class="content-grid">
<!-- 部门树 --> <!-- 部门树 -->
<el-col :lg="4" :xs="24" style=""> <el-col :lg="5" :xs="24">
<el-card shadow="hover"> <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-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
<el-tree <el-tree
ref="deptTreeRef" ref="deptTreeRef"
class="mt-2" class="mt-2 dept-tree"
node-key="id" node-key="id"
:data="deptOptions" :data="deptOptions"
:props="{ label: 'label', children: 'children' } as any" :props="{ label: 'label', children: 'children' } as any"
@ -19,11 +27,18 @@
/> />
</el-card> </el-card>
</el-col> </el-col>
<el-col :lg="20" :xs="24"> <el-col :lg="19" :xs="24" class="content-main">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -57,25 +72,23 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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 v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
修改 修改
</el-button> </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 v-has-permi="['system:user:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">
删除 删除
</el-button> </el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown class="mt-[1px]"> <el-dropdown class="mt-[1px]">
<el-button plain type="info"> <el-button plain type="info">
更多 更多
@ -90,12 +103,12 @@
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</el-col> <right-toolbar v-model:show-search="showSearch" :columns="columns" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 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[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" /> <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 = []; form.value.postIds = [];
} }
</script> </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>

View File

@ -1,54 +1,76 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell profile-page">
<el-row :gutter="20"> <el-row :gutter="20" class="profile-grid">
<el-col :span="6" :xs="24"> <el-col :span="6" :xs="24">
<el-card class="box-card"> <el-card shadow="hover" class="side-panel profile-summary-card">
<template #header> <template #header>
<div class="clearfix"> <div class="panel-heading">
<span>个人信息</span> <div><h3>个人信息</h3></div>
</div> </div>
</template> </template>
<div> <div class="profile-summary">
<div class="text-center"> <div class="text-center profile-avatar">
<userAvatar /> <userAvatar />
</div> </div>
<ul class="list-group list-group-striped"> <ul class="list-group list-group-striped">
<li class="list-group-item"> <li class="list-group-item">
<svg-icon icon-class="user" />用户名称 <div class="profile-item-label">
<div class="pull-right">{{ state.user.userName }}</div> <svg-icon icon-class="user" />
<span>用户名称</span>
</div>
<span class="profile-item-value">{{ state.user.userName || '-' }}</span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<svg-icon icon-class="phone" />手机号码 <div class="profile-item-label">
<div class="pull-right">{{ state.user.phonenumber }}</div> <svg-icon icon-class="phone" />
<span>手机号码</span>
</div>
<span class="profile-item-value">{{ state.user.phonenumber || '-' }}</span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<svg-icon icon-class="email" />用户邮箱 <div class="profile-item-label">
<div class="pull-right">{{ state.user.email }}</div> <svg-icon icon-class="email" />
<span>用户邮箱</span>
</div>
<span class="profile-item-value">{{ state.user.email || '-' }}</span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<svg-icon icon-class="tree" />所属部门 <div class="profile-item-label">
<div v-if="state.user.deptName" class="pull-right">{{ state.user.deptName }} / {{ state.postGroup }}</div> <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>
<li class="list-group-item"> <li class="list-group-item">
<svg-icon icon-class="peoples" />所属角色 <div class="profile-item-label">
<div class="pull-right">{{ state.roleGroup }}</div> <svg-icon icon-class="peoples" />
<span>所属角色</span>
</div>
<span class="profile-item-value">{{ state.roleGroup || '-' }}</span>
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<svg-icon icon-class="date" />创建日期 <div class="profile-item-label">
<div class="pull-right">{{ state.user.createTime }}</div> <svg-icon icon-class="date" />
<span>创建日期</span>
</div>
<span class="profile-item-value">{{ state.user.createTime || '-' }}</span>
</li> </li>
</ul> </ul>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="18" :xs="24"> <el-col :span="18" :xs="24">
<el-card> <el-card shadow="hover" class="table-panel profile-main-card">
<template #header> <template #header>
<div class="clearfix"> <div class="toolbar-shell">
<span>基本资料</span> <div class="table-heading">
<h3>个人设置</h3>
<p>维护个人资料密码第三方应用和在线设备</p>
</div>
</div> </div>
</template> </template>
<el-tabs v-model="activeTab"> <el-tabs v-model="activeTab" class="profile-tabs">
<el-tab-pane label="基本资料" name="userinfo"> <el-tab-pane label="基本资料" name="userinfo">
<userInfo :user="userForm" /> <userInfo :user="userForm" />
</el-tab-pane> </el-tab-pane>
@ -120,3 +142,88 @@ onMounted(() => {
getOnlines(); getOnlines();
}); });
</script> </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>

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div class="profile-table-wrap">
<el-table :data="devices" border style="width: 100%; height: 100%; font-size: 14px"> <el-table :data="devices" border class="data-table profile-device-table">
<el-table-column label="设备类型" align="center"> <el-table-column label="设备类型" align="center">
<template #default="scope"> <template #default="scope">
<dict-tag :options="sys_device_type" :value="scope.row.deviceType" /> <dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
@ -55,3 +55,9 @@ const handldDelOnline = (row: any) => {
.catch(() => {}); .catch(() => {});
}; };
</script> </script>
<style lang="scss" scoped>
.profile-table-wrap {
width: 100%;
}
</style>

View File

@ -1,5 +1,5 @@
<template> <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-form-item label="旧密码" prop="oldPassword">
<el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password /> <el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
</el-form-item> </el-form-item>
@ -9,9 +9,9 @@
<el-form-item label="确认密码" prop="confirmPassword"> <el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password /> <el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item class="profile-form__actions">
<el-button type="primary" @click="submit">保存</el-button> <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-item>
</el-form> </el-form>
</template> </template>
@ -71,3 +71,22 @@ const close = () => {
proxy?.$tab.closePage(); proxy?.$tab.closePage();
}; };
</script> </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>

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div class="profile-auth">
<el-table :data="auths" border style="width: 100%; height: 100%; font-size: 14px"> <el-table :data="auths" border class="data-table profile-auth-table">
<el-table-column label="序号" width="50" type="index" /> <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="140" align="center" prop="source" show-overflow-tooltip />
<el-table-column label="头像" width="120" align="center" prop="avatar"> <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="180" align="center" prop="createTime" />
<el-table-column label="操作" width="80" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" width="80" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div id="git-user-binding"> <div class="provider-section">
<h4 class="provider-desc">你可以绑定以下第三方帐号</h4> <div class="provider-heading">
<div id="authlist" class="user-bind"> <h4 class="provider-desc">可绑定的第三方应用</h4>
<p>点击下方平台完成账号绑定</p>
</div>
<div class="user-bind">
<a class="third-app" href="#" title="使用 微信 账号授权登录" @click="authUrl('wechat')"> <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" /> <svg-icon icon-class="wechat" />
</div> </div>
<span class="app-name">WeiXin</span> <span class="app-name">微信</span>
</a> </a>
<a class="third-app" href="#" title="使用 MaxKey 账号授权登录" @click="authUrl('maxkey')"> <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" /> <svg-icon icon-class="maxkey" />
</div> </div>
<span class="app-name">MaxKey</span> <span class="app-name">MaxKey</span>
</a> </a>
<a class="third-app" href="#" title="使用 TopIam 账号授权登录" @click="authUrl('topiam')"> <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" /> <svg-icon icon-class="topiam" />
</div> </div>
<span class="app-name">TopIam</span> <span class="app-name">TopIam</span>
</a> </a>
<a class="third-app" href="#" title="使用 Gitee 账号授权登录" @click="authUrl('gitee')"> <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" /> <svg-icon icon-class="gitee" />
</div> </div>
<span class="app-name">Gitee</span> <span class="app-name">Gitee</span>
</a> </a>
<a class="third-app" href="#" title="使用 GitHub 账号授权登录" @click="authUrl('github')"> <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" /> <svg-icon icon-class="github" />
</div> </div>
<span class="app-name">Github</span> <span class="app-name">GitHub</span>
</a> </a>
</div> </div>
</div> </div>
@ -94,44 +99,77 @@ const authUrl = (source: string) => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.user-bind .third-app { .profile-auth {
display: -webkit-box; display: flex;
display: -ms-flexbox; 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; display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column; flex-direction: column;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center; align-items: center;
min-width: 80px; justify-content: center;
float: left; 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 { .user-bind {
font-size: 1rem; display: grid;
text-align: start; grid-template-columns: repeat(auto-fill, minmax(96px, 108px));
height: 50px; gap: 10px;
margin-top: 10px; justify-content: start;
} }
.git-other-login-icon > img { .third-app:hover {
height: 32px; 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 { a {
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
color: #005980; color: inherit;
} }
.provider-desc { .provider-desc {
font-family: margin: 0;
-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Liberation Sans', font-size: 16px;
'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;
} }
td > img { td > img {
@ -141,4 +179,18 @@ td > img {
border-radius: 50%; border-radius: 50%;
margin-right: 5px; 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> </style>

View File

@ -159,7 +159,18 @@ const closeDialog = () => {
.user-info-head { .user-info-head {
position: relative; position: relative;
display: inline-block; display: inline-block;
width: 120px;
height: 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 { .user-info-head:hover:after {
@ -176,7 +187,21 @@ const closeDialog = () => {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
cursor: pointer; cursor: pointer;
line-height: 110px; line-height: 120px;
border-radius: 50%; 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> </style>

View File

@ -1,5 +1,5 @@
<template> <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-form-item label="用户昵称" prop="nickName">
<el-input v-model="userForm.nickName" maxlength="30" /> <el-input v-model="userForm.nickName" maxlength="30" />
</el-form-item> </el-form-item>
@ -15,9 +15,9 @@
<el-radio value="1"></el-radio> <el-radio value="1"></el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item class="profile-form__actions">
<el-button type="primary" @click="submit">保存</el-button> <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-item>
</el-form> </el-form>
</template> </template>
@ -67,3 +67,26 @@ const close = () => {
proxy?.$tab.closePage(); proxy?.$tab.closePage();
}; };
</script> </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>

View File

@ -1,9 +1,16 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell tool-gen-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="数据源" prop="dataName">
<el-select v-model="queryParams.dataName" filterable clearable placeholder="请选择/输入数据源名称"> <el-select v-model="queryParams.dataName" filterable clearable placeholder="请选择/输入数据源名称">
<el-option key="" label="全部" value="" /> <el-option key="" label="全部" value="" />
@ -33,30 +40,28 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="hover"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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-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-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 v-hasPermi="['tool:gen:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除 删除
</el-button> </el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 type="selection" align="center" width="55"></el-table-column>
<el-table-column label="序号" type="index" width="50" align="center"> <el-table-column label="序号" type="index" width="50" align="center">
<template #default="scope"> <template #default="scope">
@ -250,10 +255,30 @@ onMounted(() => {
</script> </script>
<style lang="scss" scoped> <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 { .el-tab-pane {
background-color: #282c34; background-color: #282c34;
.el-link { .el-link {
color: #fff; color: #fff;
} }
} }
@media (max-width: 900px) {
.toolbar-shell {
align-items: flex-start;
}
}
</style> </style>

View File

@ -1,8 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell workflow-category-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="search"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="分类名称" prop="categoryName">
<el-input v-model="queryParams.categoryName" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.categoryName" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -11,24 +16,26 @@
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card>
</div> </div>
</transition>
<el-card shadow="never"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col> <right-toolbar v-model:showSearch="showSearch" :search="false" @queryTable="getList"></right-toolbar>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> </div>
</el-row> </div>
</template> </template>
<el-table <el-table
ref="categoryTableRef" ref="categoryTableRef"
v-loading="loading" v-loading="loading"
class="data-table"
:data="categoryList" :data="categoryList"
row-key="categoryId" row-key="categoryId"
border border

View File

@ -1,8 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell workflow-leave-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="search"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="请假天数" prop="startLeaveDays">
<el-input v-model="queryParams.startLeaveDays" placeholder="请输入请假天数" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.startLeaveDays" placeholder="请输入请假天数" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -15,23 +20,24 @@
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card>
</div> </div>
</transition>
<el-card shadow="never"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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-button v-hasPermi="['workflow:leave:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column v-if="false" label="主键" align="center" prop="id" /> <el-table-column v-if="false" label="主键" align="center" prop="id" />
<el-table-column label="请假类型" align="center"> <el-table-column label="请假类型" align="center">

View File

@ -1,9 +1,14 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell workflow-process-definition-page">
<el-row :gutter="20"> <el-row :gutter="20" class="content-grid">
<!-- 流程分类树 --> <!-- 流程分类树 -->
<el-col :lg="4" :xs="24" style=""> <el-col :lg="4" :xs="24">
<el-card shadow="hover"> <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-input v-model="categoryName" placeholder="请输入流程分类名" prefix-icon="Search" clearable />
<el-tree <el-tree
ref="categoryTreeRef" ref="categoryTreeRef"
@ -19,11 +24,15 @@
></el-tree> ></el-tree>
</el-card> </el-card>
</el-col> </el-col>
<el-col :lg="20" :xs="24"> <el-col :lg="20" :xs="24" class="content-main">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px"> <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-form-item label="流程定义名称" prop="flowName">
<el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -37,32 +46,26 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition> <el-card shadow="hover" class="table-panel">
<el-card shadow="hover">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <div class="table-heading">
<h3>流程定义</h3>
</div>
<div class="toolbar-actions">
<el-button type="primary" icon="Plus" @click="handleAdd()">添加</el-button> <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-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-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-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-button type="warning" icon="Download" :disabled="single" @click="handleExportDef">导出</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar> </div>
</el-row> </div>
</template> </template>
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick"> <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="0"></el-tab-pane>
<el-tab-pane label="未发布" name="1"></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 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="id" label="主键" v-if="false"></el-table-column>
<el-table-column align="center" prop="flowName" label="流程定义名称" :show-overflow-tooltip="true"></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> <el-tag v-else type="danger">失效</el-tag>
</template> </template>
</el-table-column> </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"> <template #default="scope">
<el-row :gutter="10" class="mb8"> <div class="process-action-group">
<el-col :span="1.5">
<el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button> <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-button link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
</el-col> <el-button link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)">
</el-row> 流程设计
<el-row :gutter="10" class="mb8"> </el-button>
<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-button link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
</el-col> <el-button link type="primary" v-if="scope.row.isPublish !== 1" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">
<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-button>
</el-col> </div>
</el-row>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -239,11 +234,6 @@ const uploadDialog = reactive<DialogOption>({
title: '部署流程文件' title: '部署流程文件'
}); });
const processDefinitionDialog = reactive<DialogOption>({
visible: false,
title: '历史版本'
});
const modelDialog = reactive<DialogOption>({ const modelDialog = reactive<DialogOption>({
visible: false, visible: false,
title: '' title: ''
@ -395,7 +385,6 @@ const handlePublish = async (row?: FlowDefinitionVo) => {
); );
loading.value = true; loading.value = true;
await publish(row.id).finally(() => (loading.value = false)); await publish(row.id).finally(() => (loading.value = false));
processDefinitionDialog.visible = false;
activeName.value = '0'; activeName.value = '0';
await handleQuery(); await handleQuery();
proxy?.$modal.msgSuccess('发布成功'); proxy?.$modal.msgSuccess('发布成功');
@ -559,3 +548,40 @@ const handleExportDef = () => {
proxy?.download(`/workflow/definition/exportDef/${ids.value[0]}`, {}, `${flowCodeList.value[0]}.json`); proxy?.download(`/workflow/definition/exportDef/${ids.value[0]}`, {}, `${flowCodeList.value[0]}.json`);
}; };
</script> </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>

View File

@ -1,9 +1,14 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell workflow-process-instance-page">
<el-row :gutter="20"> <el-row :gutter="20" class="content-grid">
<!-- 流程分类树 --> <!-- 流程分类树 -->
<el-col :lg="4" :xs="24" style=""> <el-col :lg="4" :xs="24">
<el-card shadow="hover"> <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-input v-model="categoryName" placeholder="请输入流程分类名" prefix-icon="Search" clearable />
<el-tree <el-tree
ref="categoryTreeRef" ref="categoryTreeRef"
@ -19,11 +24,15 @@
></el-tree> ></el-tree>
</el-card> </el-card>
</el-col> </el-col>
<el-col :lg="20" :xs="24"> <el-col :lg="20" :xs="24" class="content-main">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item>
<el-badge :value="userSelectCount" :max="10" class="item"> <el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button> <el-button type="primary" @click="openUserSelect">选择申请人</el-button>
@ -45,20 +54,22 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition> <el-card shadow="hover" class="table-panel">
<el-card shadow="hover">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-col> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar> </div>
</el-row> </div>
</template> </template>
<el-tabs v-model="tab" @tab-click="changeTab"> <el-tabs v-model="tab" @tab-click="changeTab">
<el-tab-pane name="running" label="运行中"></el-tab-pane> <el-tab-pane name="running" label="运行中"></el-tab-pane>
<el-tab-pane name="finish" 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 type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column> <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> <el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
@ -476,3 +487,15 @@ onMounted(() => {
getTreeselect(); getTreeselect();
}); });
</script> </script>
<style lang="scss" scoped>
.content-grid {
margin: 0 !important;
}
.content-main {
display: flex;
flex-direction: column;
gap: 12px;
}
</style>

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell workflow-spel-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="组件名称" prop="componentName">
<el-input v-model="queryParams.componentName" placeholder="请输入组件名称" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.componentName" placeholder="请输入组件名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -17,25 +21,23 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition>
<el-card shadow="never"> <el-card shadow="hover" class="table-panel">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5"> <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-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-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-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" :search="false" @queryTable="getList"></right-toolbar>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> </div>
</el-row> </div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="60" align="center"> <el-table-column label="序号" type="index" width="60" align="center">
<template #default="scope"> <template #default="scope">

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell workflow-all-task-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item>
<el-badge :value="userSelectCount" :max="10" class="item"> <el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button> <el-button type="primary" @click="openUserSelect">选择申请人</el-button>
@ -22,25 +26,29 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition> <el-card shadow="hover" class="table-panel">
<el-card shadow="hover">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<el-col :span="1.5" v-if="tab === 'waiting'"> <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 class="todo-action-btn todo-action-btn--primary" type="primary" plain icon="Edit" :disabled="multiple" @click="handleUserOpen()"
>修改办理人 >修改办理人
</el-button> </el-button>
<el-button class="todo-action-btn todo-action-btn--warning" type="warning" plain icon="Bell" :disabled="multiple" @click="handleUrgeTaskOpen()" <el-button class="todo-action-btn todo-action-btn--warning" type="warning" plain icon="Bell" :disabled="multiple" @click="handleUrgeTaskOpen()"
>催办 >催办
</el-button> </el-button>
</el-col> </template>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar> <right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</el-row> </div>
</div>
</template> </template>
<el-tabs v-model="tab" @tab-click="changeTab"> <el-tabs v-model="tab" @tab-click="changeTab">
<el-tab-pane name="waiting" label="待办任务"> </el-tab-pane> <el-tab-pane name="waiting" label="待办任务"> </el-tab-pane>
<el-tab-pane name="finish" 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 type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column> <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> <el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>

View File

@ -1,9 +1,14 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell workflow-my-document-page">
<el-row :gutter="20"> <el-row :gutter="20" class="content-grid">
<!-- 流程分类树 --> <!-- 流程分类树 -->
<el-col :lg="4" :xs="24" style=""> <el-col :lg="4" :xs="24">
<el-card shadow="hover"> <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-input v-model="categoryName" placeholder="请输入流程分类名" prefix-icon="Search" clearable />
<el-tree <el-tree
ref="categoryTreeRef" ref="categoryTreeRef"
@ -19,11 +24,15 @@
></el-tree> ></el-tree>
</el-card> </el-card>
</el-col> </el-col>
<el-col :lg="20" :xs="24"> <el-col :lg="20" :xs="24" class="content-main">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px"> <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-form-item label="流程定义编码" prop="flowCode">
<el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" /> <el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -34,15 +43,19 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition> <el-card shadow="hover" class="table-panel">
<el-card shadow="hover">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar> <div class="table-heading">
</el-row> <h3>我的单据</h3>
</div>
<div class="toolbar-actions">
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column> <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> <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); workflowCommon.routerJump(routerJumpVo, proxy);
}; };
</script> </script>
<style lang="scss" scoped>
.content-grid {
margin: 0 !important;
}
.content-main {
display: flex;
flex-direction: column;
gap: 12px;
}
</style>

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell workflow-task-copy-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item label="任务名称" prop="nodeName">
<el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" /> <el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
@ -20,15 +24,19 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition> <el-card shadow="hover" class="table-panel">
<el-card shadow="hover">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar> <div class="table-heading">
</el-row> <h3>抄送任务</h3>
</div>
<div class="toolbar-actions">
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column> <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> <el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell workflow-task-finish-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item>
<el-badge :value="userSelectCount" :max="10" class="item"> <el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button> <el-button type="primary" @click="openUserSelect">选择申请人</el-button>
@ -25,15 +29,19 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition> <el-card shadow="hover" class="table-panel">
<el-card shadow="hover">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar> <div class="table-heading">
</el-row> <h3>已办任务</h3>
</div>
<div class="toolbar-actions">
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column> <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> <el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="p-2"> <div class="p-2 page-shell workflow-task-waiting-page">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search-wrap">
<div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<el-card shadow="hover"> <template #header>
<el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true"> <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-form-item>
<el-badge :value="userSelectCount" :max="10" class="item"> <el-badge :value="userSelectCount" :max="10" class="item">
<el-button type="primary" @click="openUserSelect">选择申请人</el-button> <el-button type="primary" @click="openUserSelect">选择申请人</el-button>
@ -25,15 +29,19 @@
</el-form> </el-form>
</el-card> </el-card>
</div> </div>
</transition> <el-card shadow="hover" class="table-panel">
<el-card shadow="hover">
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <div class="toolbar-shell">
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar> <div class="table-heading">
</el-row> <h3>待办任务</h3>
</div>
<div class="toolbar-actions">
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="handleQuery"></right-toolbar>
</div>
</div>
</template> </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 type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column> <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> <el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>