mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-22 05:02:57 +00:00
refactor(frontend): 重构前端样式体系,统一资源路径与代码规范
1. 重构SCSS样式结构,将原有分散的样式文件整合到custom目录下,统一使用@use替代@import 2. 删除冗余的全局样式文件,将样式逻辑内联到对应组件或集中到core目录 3. 新增DOMPurify依赖,对富文本内容添加XSS防护 4. 修复metrics端点暴露的兼容性问题,优化启动banner样式 5. 移除无用的权限校验代码,重构工作流组件路径与命名 6. 优化样式混合宏与响应式布局,修复部分组件样式冲突
This commit is contained in:
@@ -258,5 +258,277 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@styles/fa-wang-editor";
|
||||
$box-radius: calc(var(--custom-radius) / 3 + 2px);
|
||||
|
||||
/* 全屏容器 z-index 调整 */
|
||||
.w-e-full-screen-container {
|
||||
z-index: 100 !important;
|
||||
}
|
||||
|
||||
/* 编辑器容器 */
|
||||
.editor-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid var(--fa-gray-300);
|
||||
border-radius: $box-radius !important;
|
||||
|
||||
.w-e-bar {
|
||||
border-radius: $box-radius $box-radius 0 0 !important;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 工具栏 */
|
||||
.editor-toolbar {
|
||||
border-bottom: 1px solid var(--default-border);
|
||||
}
|
||||
|
||||
/* 下拉选择框配置 */
|
||||
.w-e-select-list {
|
||||
min-width: 140px;
|
||||
padding: 5px 10px 10px;
|
||||
border: none;
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 下拉选择框元素配置 */
|
||||
.w-e-select-list ul li {
|
||||
margin-top: 5px;
|
||||
font-size: 15px !important;
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 下拉选择框 正文文字大小调整 */
|
||||
.w-e-select-list ul li:last-of-type {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
/* 下拉选择框 hover 样式调整 */
|
||||
.w-e-select-list ul li:hover {
|
||||
background-color: var(--fa-gray-200);
|
||||
}
|
||||
|
||||
:root {
|
||||
/* 激活颜色 */
|
||||
--w-e-toolbar-active-bg-color: var(--fa-gray-200);
|
||||
|
||||
/* toolbar 图标和文字颜色 */
|
||||
--w-e-toolbar-color: #000;
|
||||
|
||||
/* 表格选中时候的边框颜色 */
|
||||
--w-e-textarea-selected-border-color: #ddd;
|
||||
|
||||
/* 表格头背景颜色 */
|
||||
--w-e-textarea-slight-bg-color: var(--fa-gray-200);
|
||||
}
|
||||
|
||||
/* 工具栏按钮样式 */
|
||||
.w-e-bar-item svg {
|
||||
fill: var(--fa-gray-800);
|
||||
}
|
||||
|
||||
.w-e-bar-item button {
|
||||
color: var(--fa-gray-800);
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 工具栏 hover 按钮背景颜色 */
|
||||
.w-e-bar-item button:hover {
|
||||
background-color: var(--fa-gray-200);
|
||||
}
|
||||
|
||||
/* 工具栏分割线 */
|
||||
.w-e-bar-divider {
|
||||
height: 20px;
|
||||
margin-top: 10px;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
/* 工具栏菜单 */
|
||||
.w-e-bar-item-group .w-e-bar-item-menus-container {
|
||||
min-width: 120px;
|
||||
padding: 10px 0;
|
||||
border: none;
|
||||
border-radius: $box-radius;
|
||||
|
||||
.w-e-bar-item {
|
||||
button {
|
||||
width: 100%;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 代码块 */
|
||||
.w-e-text-container [data-slate-editor] pre > code {
|
||||
padding: 0.6rem 1rem;
|
||||
background-color: var(--fa-gray-50);
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 弹出框 */
|
||||
.w-e-drop-panel {
|
||||
border: 0;
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #318ef4;
|
||||
}
|
||||
|
||||
.w-e-text-container {
|
||||
[data-slate-editor] {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 0.8em 0 0.4em;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.125em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 1.5em;
|
||||
margin: 0.8em 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
ul ul {
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
ul ul ul {
|
||||
list-style: square;
|
||||
}
|
||||
}
|
||||
|
||||
strong,
|
||||
b {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
i,
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
.w-e-text-container [data-slate-editor] .table-container th {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.w-e-text-container [data-slate-editor] .table-container th:last-of-type {
|
||||
border-right: 1px solid #ccc !important;
|
||||
}
|
||||
|
||||
/* 引用 */
|
||||
.w-e-text-container [data-slate-editor] blockquote {
|
||||
background-color: var(--fa-gray-200);
|
||||
border-left: 4px solid var(--fa-gray-300);
|
||||
}
|
||||
|
||||
/* 输入区域弹出 bar */
|
||||
.w-e-hover-bar {
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 超链接弹窗 */
|
||||
.w-e-modal {
|
||||
border: none;
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 图片样式调整 */
|
||||
.w-e-text-container [data-slate-editor] .w-e-selected-image-container {
|
||||
overflow: inherit;
|
||||
|
||||
&:hover {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 1px solid transparent;
|
||||
transition: border 0.3s;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid #318ef4 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.w-e-image-dragger {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #318ef4;
|
||||
border: 2px solid #fff;
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
.left-top {
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
}
|
||||
|
||||
.right-top {
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
}
|
||||
|
||||
.left-bottom {
|
||||
bottom: -6px;
|
||||
left: -6px;
|
||||
}
|
||||
|
||||
.right-bottom {
|
||||
right: -6px;
|
||||
bottom: -6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -362,19 +362,526 @@ watch(menuOpen, (isMenuOpen: boolean) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@styles/fa-sidebar-menu";
|
||||
.layout-sidebar {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
user-select: none;
|
||||
scrollbar-width: none;
|
||||
border-right: 1px solid var(--fa-card-border);
|
||||
|
||||
&.no-border {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-scrollbar__bar.is-vertical) {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-scrollbar__thumb) {
|
||||
right: -2px;
|
||||
background-color: #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.dual-menu-left {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--fa-card-border) !important;
|
||||
transition: width 0.25s;
|
||||
|
||||
.logo {
|
||||
margin: auto;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul {
|
||||
li {
|
||||
> div {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 8px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
|
||||
.art-svg-icon {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: -webkit-box;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
font-size: 12px;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: var(--el-color-primary-light-9);
|
||||
|
||||
.art-svg-icon,
|
||||
span {
|
||||
color: var(--theme-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.switch-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 15px;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: 100vh;
|
||||
|
||||
@media only screen and (width <= 640px) {
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.dual-menu-collapse-btn {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dual-menu-collapse-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -11px;
|
||||
z-index: 10;
|
||||
width: 11px;
|
||||
height: 50px;
|
||||
cursor: pointer;
|
||||
background-color: var(--default-box-color);
|
||||
border: 1px solid var(--fa-card-border);
|
||||
border-radius: 0 15px 15px 0;
|
||||
opacity: 0;
|
||||
transform: translateY(-50%);
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
.art-svg-icon {
|
||||
color: var(--fa-gray-800) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.art-svg-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -4px;
|
||||
margin: auto;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
line-height: 60px;
|
||||
cursor: pointer;
|
||||
|
||||
.logo {
|
||||
margin-left: 22px;
|
||||
}
|
||||
|
||||
p {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 58px;
|
||||
box-sizing: border-box;
|
||||
margin-left: 10px;
|
||||
font-size: 18px;
|
||||
|
||||
&.is-dual-menu-name {
|
||||
left: 25px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
box-sizing: border-box;
|
||||
|
||||
/* 防止菜单内的滚动影响整个页面滚动 */
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: none;
|
||||
border-right: 0;
|
||||
-ms-scroll-chaining: contain;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-model {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (width <= 800px) {
|
||||
.layout-sidebar {
|
||||
width: 0;
|
||||
|
||||
.header {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.el-menu--collapse {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
/* 折叠状态下的header样式 */
|
||||
.menu-left-close .header {
|
||||
.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
p {
|
||||
left: 16px;
|
||||
font-size: 0;
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-model {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: rgba($color: #000, $alpha: 50%);
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (width <= 640px) {
|
||||
.layout-sidebar {
|
||||
border-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.layout-sidebar {
|
||||
border-right: 1px solid rgb(255 255 255 / 13%);
|
||||
|
||||
:deep(.el-scrollbar__thumb) {
|
||||
background-color: #777;
|
||||
}
|
||||
|
||||
.dual-menu-left {
|
||||
border-right: 1px solid rgb(255 255 255 / 9%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@styles/fa-sidebar-menu-theme";
|
||||
@use "@styles/core/mixin.scss" as *;
|
||||
|
||||
/* 菜单样式变量 */
|
||||
$menu-height: 42px;
|
||||
$menu-icon-size: 20px;
|
||||
$menu-font-size: 14px;
|
||||
$hover-bg-color: var(--fa-gray-200);
|
||||
$popup-menu-height: 40px;
|
||||
$popup-menu-padding: 8px;
|
||||
$popup-menu-margin: 5px;
|
||||
$popup-menu-radius: 6px;
|
||||
|
||||
/* 通用菜单项样式 */
|
||||
@mixin menu-item-base {
|
||||
width: calc(100% - 16px);
|
||||
margin-left: 8px;
|
||||
border-radius: 6px;
|
||||
|
||||
.menu-icon {
|
||||
margin-left: -7px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 通用 hover 样式 */
|
||||
@mixin menu-hover($bg-color) {
|
||||
.el-sub-menu__title:hover,
|
||||
.el-menu-item:not(.is-active):hover {
|
||||
background: $bg-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 通用选中样式 */
|
||||
@mixin menu-active($color, $bg-color, $icon-color: var(--theme-color)) {
|
||||
.el-menu-item.is-active {
|
||||
color: $color !important;
|
||||
background-color: $bg-color;
|
||||
|
||||
.menu-icon {
|
||||
.art-svg-icon {
|
||||
color: $icon-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹窗菜单项样式 */
|
||||
@mixin popup-menu-item {
|
||||
height: $popup-menu-height;
|
||||
margin-bottom: $popup-menu-margin;
|
||||
border-radius: $popup-menu-radius;
|
||||
|
||||
.menu-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 主题菜单通用样式(合并 design 和 dark 主题的共同逻辑) */
|
||||
@mixin theme-menu-base {
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
@include menu-item-base;
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹窗菜单通用样式 */
|
||||
@mixin popup-menu-base($hover-bg, $active-color, $active-bg) {
|
||||
.el-menu--popup {
|
||||
padding: $popup-menu-padding;
|
||||
|
||||
.el-sub-menu__title:hover,
|
||||
.el-menu-item:hover {
|
||||
background-color: $hover-bg !important;
|
||||
border-radius: $popup-menu-radius;
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
@include popup-menu-item;
|
||||
|
||||
&.is-active {
|
||||
color: $active-color !important;
|
||||
background-color: $active-bg !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu {
|
||||
@include popup-menu-item;
|
||||
|
||||
height: $popup-menu-height !important;
|
||||
|
||||
.el-sub-menu__title {
|
||||
height: $popup-menu-height !important;
|
||||
border-radius: $popup-menu-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-sidebar {
|
||||
// 展开的宽度
|
||||
/* ---------------------- Modify default style ---------------------- */
|
||||
|
||||
/* 菜单折叠样式 */
|
||||
.menu-left-close {
|
||||
.header {
|
||||
.logo {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 菜单图标 */
|
||||
.menu-icon {
|
||||
margin-right: 8px;
|
||||
font-size: $menu-icon-size;
|
||||
}
|
||||
|
||||
/* 菜单高度 */
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
height: $menu-height !important;
|
||||
margin-bottom: 4px;
|
||||
line-height: $menu-height !important;
|
||||
|
||||
span {
|
||||
font-size: $menu-font-size !important;
|
||||
|
||||
@include ellipsis();
|
||||
}
|
||||
}
|
||||
|
||||
/* 右侧箭头 */
|
||||
.el-sub-menu__icon-arrow {
|
||||
width: 13px !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
/* 菜单折叠 */
|
||||
.el-menu--collapse {
|
||||
.el-sub-menu.is-active {
|
||||
.el-sub-menu__title {
|
||||
.menu-icon {
|
||||
.art-svg-icon {
|
||||
// 选中菜单图标颜色
|
||||
color: var(--theme-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------- Design theme menu ---------------------- */
|
||||
.el-menu-design {
|
||||
@include theme-menu-base;
|
||||
@include menu-active(var(--theme-color), var(--el-color-primary-light-9));
|
||||
@include menu-hover($hover-bg-color);
|
||||
|
||||
.el-sub-menu__icon-arrow {
|
||||
color: var(--fa-gray-600);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------- Dark theme menu ---------------------- */
|
||||
.el-menu-dark {
|
||||
@include theme-menu-base;
|
||||
@include menu-active(#fff, #27282d, #fff);
|
||||
@include menu-hover(#0f1015);
|
||||
|
||||
.el-sub-menu__icon-arrow {
|
||||
color: var(--fa-gray-400);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------- Light theme menu ---------------------- */
|
||||
.el-menu-light {
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
.menu-icon {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item.is-active {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
|
||||
.art-svg-icon {
|
||||
color: var(--theme-color) !important;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
content: "";
|
||||
background: var(--theme-color);
|
||||
}
|
||||
}
|
||||
|
||||
@include menu-hover($hover-bg-color);
|
||||
|
||||
.el-sub-menu__icon-arrow {
|
||||
color: var(--fa-gray-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (width <= 640px) {
|
||||
.layout-sidebar {
|
||||
.el-menu-design {
|
||||
> .el-sub-menu {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.el-sub-menu {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 菜单折叠 hover 弹窗样式(浅色主题) */
|
||||
.el-menu--vertical,
|
||||
.el-menu--popup-container {
|
||||
@include popup-menu-base(var(--fa-gray-200), var(--fa-gray-900), var(--fa-gray-200));
|
||||
}
|
||||
|
||||
/* 暗黑模式菜单样式 */
|
||||
.dark {
|
||||
.el-menu--vertical,
|
||||
.el-menu--popup-container {
|
||||
@include popup-menu-base(var(--fa-gray-200), var(--fa-gray-900), #292a2e);
|
||||
}
|
||||
|
||||
.layout-sidebar {
|
||||
/* 图标颜色、文字颜色 */
|
||||
.menu-icon .art-svg-icon,
|
||||
.menu-name {
|
||||
color: var(--fa-gray-800) !important;
|
||||
}
|
||||
|
||||
/* 选中的文字颜色跟图标颜色 */
|
||||
.el-menu-item.is-active {
|
||||
span,
|
||||
.menu-icon .art-svg-icon {
|
||||
color: var(--theme-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 右侧箭头颜色 */
|
||||
.el-sub-menu__icon-arrow {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-sidebar {
|
||||
/* 展开的宽度 */
|
||||
.el-menu:not(.el-menu--collapse) {
|
||||
width: v-bind(menuopenwidth);
|
||||
}
|
||||
|
||||
// 折叠后宽度
|
||||
/* 折叠后宽度 */
|
||||
.el-menu--collapse {
|
||||
width: v-bind(menuclosewidth);
|
||||
}
|
||||
|
||||
@@ -57,5 +57,96 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@styles/fa-settings-panel";
|
||||
@use "@styles/core/mixin.scss" as *;
|
||||
|
||||
/* 设置抽屉模态框样式 */
|
||||
.setting-modal {
|
||||
background: transparent !important;
|
||||
|
||||
.el-drawer {
|
||||
// 背景滤镜效果
|
||||
background: rgba($color: #fff, $alpha: 50%) !important;
|
||||
box-shadow: 0 0 30px rgb(0 0 0 / 10%) !important;
|
||||
|
||||
@include backdropBlur();
|
||||
|
||||
.setting-box-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
width: calc(100% + 15px);
|
||||
margin-bottom: 10px;
|
||||
|
||||
.setting-item {
|
||||
box-sizing: border-box;
|
||||
width: calc(33.333% - 15px);
|
||||
margin-right: 15px;
|
||||
text-align: center;
|
||||
|
||||
.box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 52px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border: 2px solid var(--default-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 8px 0 rgb(0 0 0 / 10%);
|
||||
transition: box-shadow 0.1s;
|
||||
|
||||
&.mt-16 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border: 2px solid var(--theme-color);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 去除滚动条
|
||||
.el-drawer__body::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.setting-modal {
|
||||
.el-drawer {
|
||||
background: rgba($color: #000, $alpha: 50%) !important;
|
||||
|
||||
.setting-item {
|
||||
.box {
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 去除火狐浏览器滚动条 */
|
||||
:deep(.el-drawer__body) {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
/* // 移动端隐藏 */
|
||||
@media screen and (width <= 800px) {
|
||||
.mobile-hide {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -64,5 +64,5 @@ function onGuideFinished(): void {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@styles/layouts";
|
||||
@use "@styles/custom/fa-layouts";
|
||||
</style>
|
||||
|
||||
@@ -352,8 +352,6 @@ const popoverContentRef = ref();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@styles/fa-table";
|
||||
|
||||
.reference :deep(.el-input__wrapper),
|
||||
.reference :deep(.el-input__inner) {
|
||||
cursor: pointer;
|
||||
@@ -364,7 +362,7 @@ const popoverContentRef = ref();
|
||||
justify-content: flex-end;
|
||||
margin-top: 6px;
|
||||
}
|
||||
// 隐藏全选按钮
|
||||
|
||||
.radio :deep(.el-table__header th.el-table__cell:nth-child(1) .el-checkbox) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@@ -494,5 +494,119 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@styles/fa-table";
|
||||
.fa-table {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
|
||||
.fa-table__main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding-top: 10px;
|
||||
|
||||
/* VueDraggable 透传高度,确保 ElTable height: 100% 能正确解析 */
|
||||
> * {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-loading-mask) {
|
||||
z-index: 100;
|
||||
background-color: var(--default-box-color) !important;
|
||||
}
|
||||
|
||||
/* Loading 过渡动画 - 消失时淡出 */
|
||||
.loading-fade-leave-active {
|
||||
transition: opacity 0.3s ease-out;
|
||||
}
|
||||
|
||||
.loading-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 空状态垂直居中 */
|
||||
&.is-empty {
|
||||
:deep(.el-table__body-wrapper) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
padding-top: 13px;
|
||||
|
||||
:deep(.el-select) {
|
||||
width: 102px !important;
|
||||
}
|
||||
|
||||
/* 分页对齐方式 */
|
||||
&.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 自定义分页组件样式 */
|
||||
&.custom-pagination {
|
||||
:deep(.el-pagination) {
|
||||
.btn-prev,
|
||||
.btn-next {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--fa-gray-300);
|
||||
transition: border-color 0.15s;
|
||||
|
||||
&:hover:not(.is-disabled) {
|
||||
color: var(--theme-color);
|
||||
border-color: var(--theme-color);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
box-sizing: border-box;
|
||||
font-weight: 400 !important;
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--fa-gray-300);
|
||||
transition: border-color 0.15s;
|
||||
|
||||
&.is-active {
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
background-color: var(--theme-color);
|
||||
border: 1px solid var(--theme-color);
|
||||
}
|
||||
|
||||
&:hover:not(.is-disabled) {
|
||||
border-color: var(--theme-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端分页 */
|
||||
@media (width <= 640px) {
|
||||
:deep(.el-pagination) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watchEffect } from "vue";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
interface Props {
|
||||
size?: string | number;
|
||||
@@ -68,7 +69,7 @@ const loadSvgContent = async () => {
|
||||
}
|
||||
|
||||
const content = await response.text();
|
||||
svgContent.value = applyThemeToSvg(content);
|
||||
svgContent.value = DOMPurify.sanitize(applyThemeToSvg(content));
|
||||
} catch (error) {
|
||||
console.error("Failed to load SVG:", error);
|
||||
svgContent.value = "";
|
||||
|
||||
@@ -222,6 +222,6 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "@styles/fa-login.css";
|
||||
<style scoped lang="scss">
|
||||
@use "@styles/custom/fa-login";
|
||||
</style>
|
||||
|
||||
@@ -111,6 +111,6 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "@styles/fa-login.css";
|
||||
<style scoped lang="scss">
|
||||
@use "@styles/custom/fa-login";
|
||||
</style>
|
||||
|
||||
@@ -196,64 +196,5 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@styles/fa-login.css";
|
||||
|
||||
.login-mobile-flow {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.login-mobile-otp-cell {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 48px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
text-align: center;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
background: var(--el-fill-color-blank);
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
|
||||
&:focus {
|
||||
border-color: var(--el-color-primary);
|
||||
box-shadow: 0 0 0 1px var(--el-color-primary) inset;
|
||||
}
|
||||
}
|
||||
|
||||
.login-mobile-sms-btn {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
.login-mobile-otp-cell {
|
||||
background: rgb(255 255 255 / 6%);
|
||||
border-color: rgb(255 255 255 / 14%);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* EP 全局规则:相邻 .el-button { margin-left: 12px },纵向叠放时仍会命中,导致第二颗按钮横向错位 */
|
||||
.login-mobile-actions {
|
||||
:deep(.el-button + .el-button) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.el-button) {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
@use "@styles/custom/fa-login";
|
||||
</style>
|
||||
|
||||
@@ -65,23 +65,5 @@ const qrPayload = computed(() => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@styles/fa-login.css";
|
||||
|
||||
.login-qr-flow {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.login-qr-card {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* EP:相邻按钮横向间距;单列返回按钮无需处理 */
|
||||
.login-mobile-actions {
|
||||
:deep(.el-button) {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
@use "@styles/custom/fa-login";
|
||||
</style>
|
||||
|
||||
@@ -133,6 +133,6 @@ defineExpose({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "@styles/fa-login.css";
|
||||
<style scoped lang="scss">
|
||||
@use "@styles/custom/fa-login";
|
||||
</style>
|
||||
|
||||
@@ -302,6 +302,15 @@
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
// 表格按钮悬浮效果
|
||||
.hover-btn {
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
|
||||
filter: brightness(1.1);
|
||||
transform: translateY(-2px) scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
// 去除移动端点击背景色
|
||||
@media screen and (width <= 1180px) {
|
||||
* {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// CRUD 页面通用样式
|
||||
// 从各 module_system 视图文件中提取的重复样式
|
||||
|
||||
// FaForm 在 Dialog 中的布局优化:隐藏最后一列操作按钮、内容区域自适应宽度
|
||||
.crud-dialog-art-form {
|
||||
:deep(.el-row > .el-col:last-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 表格操作列按钮垂直居中
|
||||
:deep(.inline-flex) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss
|
||||
// 自定义Element 亮色主题
|
||||
|
||||
@forward "element-plus/theme-chalk/src/common/var.scss" with (
|
||||
$colors: (
|
||||
"white": #ffffff,
|
||||
"black": #000000,
|
||||
"primary": (
|
||||
"base": #4080FF,
|
||||
),
|
||||
"success": (
|
||||
"base": #13deb9,
|
||||
),
|
||||
"warning": (
|
||||
"base": #ffae1f,
|
||||
),
|
||||
"danger": (
|
||||
"base": #ff4d4f,
|
||||
),
|
||||
"error": (
|
||||
"base": #fa896b,
|
||||
),
|
||||
"info": (
|
||||
"base": #909399,
|
||||
),
|
||||
),
|
||||
$button: (
|
||||
"hover-bg-color": var(--el-color-primary-light-9),
|
||||
"hover-border-color": var(--el-color-primary),
|
||||
"border-color": var(--el-color-primary),
|
||||
"text-color": var(--el-color-primary),
|
||||
),
|
||||
$messagebox: (
|
||||
"border-radius": "12px",
|
||||
),
|
||||
$popover: (
|
||||
"padding": "14px",
|
||||
"border-radius": "10px",
|
||||
)
|
||||
);
|
||||
@@ -55,7 +55,6 @@
|
||||
-o-animation: $animate;
|
||||
animation: $animate;
|
||||
|
||||
/* stylelint-disable-next-line at-rule-prelude-no-invalid */
|
||||
@keyframes #{$name} {
|
||||
from {
|
||||
@each $key, $value in $from {
|
||||
@@ -69,6 +68,20 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes #{$name} {
|
||||
from {
|
||||
@each $key, $value in $from {
|
||||
$key: $value;
|
||||
}
|
||||
}
|
||||
|
||||
to {
|
||||
@each $key, $value in $to {
|
||||
$key: $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 圆形盒子
|
||||
@@ -83,13 +96,18 @@
|
||||
|
||||
// placeholder
|
||||
@mixin placeholder($color: #bbb) {
|
||||
// Firefox
|
||||
&::placeholder {
|
||||
color: $color;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// Safari and Chrome (legacy)
|
||||
/* stylelint-disable-next-line selector-no-vendor-prefix */
|
||||
// Internet Explorer 10+
|
||||
&:-ms-input-placeholder {
|
||||
color: $color;
|
||||
}
|
||||
|
||||
// Safari and Chrome
|
||||
&::-webkit-input-placeholder {
|
||||
color: $color;
|
||||
}
|
||||
@@ -99,6 +117,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
//背景透明,文字不透明。兼容IE8
|
||||
@mixin betterTransparentize($color, $alpha) {
|
||||
$c: rgba($color, $alpha);
|
||||
$ie_c: ie_hex_str($c);
|
||||
|
||||
zoom: 1;
|
||||
background: rgba($color, 1);
|
||||
background: $c;
|
||||
background: transparent \9;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$ie_c}, endColorstr=#{$ie_c})";
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$ie_c}, endColorstr=#{$ie_c});
|
||||
}
|
||||
|
||||
//添加浏览器前缀
|
||||
@mixin browserPrefix($propertyName, $value) {
|
||||
@each $prefix in -webkit-, -moz-, -ms-, -o-, "" {
|
||||
#{$prefix}#{$propertyName}: $value;
|
||||
}
|
||||
}
|
||||
|
||||
// 边框
|
||||
@mixin border($color: red) {
|
||||
border: 1px solid $color;
|
||||
|
||||
@@ -6,6 +6,10 @@ $bg-animation-duration: 0.5s;
|
||||
html {
|
||||
--bg-animation-color: #{$bg-animation-color-light};
|
||||
|
||||
&.dark {
|
||||
--bg-animation-color: #{$bg-animation-color-dark};
|
||||
}
|
||||
|
||||
// View transition styles
|
||||
&::view-transition-old(*) {
|
||||
animation: none;
|
||||
@@ -24,8 +28,6 @@ html {
|
||||
}
|
||||
|
||||
&.dark {
|
||||
--bg-animation-color: #{$bg-animation-color-dark};
|
||||
|
||||
&::view-transition-old(*) {
|
||||
animation: clip $bg-animation-duration ease-in reverse both;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Element Plus 功能性颜色覆写 CSS 变量。
|
||||
*
|
||||
* 默认主色请与 src/config/setting.ts 中 themeColorPresets 首项保持一致。
|
||||
*
|
||||
* 注意:primary 颜色由运行时 setElementThemeColor() 动态写入,此处只覆写
|
||||
* success / warning / danger / error / info 等编译期确定的功能性颜色。
|
||||
* 这些变量无法通过 SCSS `@forward ... with()` 传递到 Element Plus 组件编译链,
|
||||
* 因为 Dart Sass 模块隔离机制下 Element Plus 组件内部 `@use 'common/var'` 获得的是独立模块实例。
|
||||
*/
|
||||
|
||||
@use "sass:map";
|
||||
@use "element-plus/theme-chalk/src/common/var.scss" as ep with (
|
||||
$colors: (
|
||||
"white": #ffffff,
|
||||
"black": #000000,
|
||||
"primary": (
|
||||
"base": #4080ff,
|
||||
),
|
||||
"success": (
|
||||
"base": #13deb9,
|
||||
),
|
||||
"warning": (
|
||||
"base": #ffae1f,
|
||||
),
|
||||
"danger": (
|
||||
"base": #ff4d4f,
|
||||
),
|
||||
"error": (
|
||||
"base": #fa896b,
|
||||
),
|
||||
"info": (
|
||||
"base": #909399,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
:root {
|
||||
@each $type in (success, warning, danger, error, info) {
|
||||
--el-color-#{$type}: #{map.get(ep.$colors, $type, "base")} !important;
|
||||
|
||||
@each $i in (3, 5, 7, 8, 9) {
|
||||
--el-color-#{$type}-light-#{$i}: #{map.get(ep.$colors, $type, "light-#{$i}")} !important;
|
||||
}
|
||||
|
||||
--el-color-#{$type}-dark-2: #{map.get(ep.$colors, $type, "dark-2")} !important;
|
||||
}
|
||||
}
|
||||
|
||||
/** 暗黑主题 */
|
||||
html.dark {
|
||||
--el-color-primary: #{map.get(ep.$colors, "primary", "base")} !important;
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
/**
|
||||
* 登录根布局
|
||||
* - 顶栏 fixed 透明叠在页面上(不占一条实体顶区);仅插画列 / 表单区随布局切换
|
||||
* - left / right:插画列与表单列横向排列(约 65% / 35%)
|
||||
* - center:全宽 + 视口级背景;登录卡居中、阴影高亮
|
||||
*/
|
||||
.login-auth-split {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.login-auth-split__col--illustration {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.login-auth-split__col--form {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 横向:插画 ~65%,表单列占剩余 */
|
||||
.login-auth-split--left,
|
||||
.login-auth-split--right {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.login-auth-split--left .login-auth-split__col--illustration,
|
||||
.login-auth-split--right .login-auth-split__col--illustration {
|
||||
flex: 0 0 65%;
|
||||
}
|
||||
|
||||
.login-auth-split--left .login-auth-split__col--form,
|
||||
.login-auth-split--right .login-auth-split__col--form {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.login-auth-split--left {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
@media only screen and (width <= 1600px) {
|
||||
.login-auth-split--left .login-auth-split__col--illustration,
|
||||
.login-auth-split--right .login-auth-split__col--illustration {
|
||||
flex: 0 0 60%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 居中模式:仅一列表单壳占满屏宽 */
|
||||
.login-auth-split--center {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-auth-split--center .login-auth-split__col--form {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* 顶栏与页脚之间的区域:纵向居中承载登录卡 */
|
||||
.login-auth-split--center .login-page-panel__scroll.login-page-panel__scroll--centered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
/* 盖过 Tailwind overflow-y-auto:仅卡片内滚动,外层不出滚动条 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-page-panel__scroll--centered .login-panel-align-row {
|
||||
flex: 0 1 auto;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
/* 悬浮卡片:居中模式尺寸与原先一致(520 宽、720/视口上限),无边框 + 主色高亮阴影 */
|
||||
.login-auth-split--center .auth-right-wrap {
|
||||
width: min(520px, calc(100vw - 2rem)) !important;
|
||||
max-width: min(520px, calc(100vw - 2rem));
|
||||
height: auto;
|
||||
max-height: calc(100dvh - 6.5rem);
|
||||
padding: 1.75rem 2rem 2rem;
|
||||
overflow: hidden auto;
|
||||
background-color: var(--el-bg-color);
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 6px 20px rgb(15 23 42 / 9%),
|
||||
0 14px 36px rgb(15 23 42 / 10%),
|
||||
0 0 18px color-mix(in srgb, var(--el-color-primary) 16%, transparent),
|
||||
0 0 32px color-mix(in srgb, var(--el-color-primary) 7%, transparent);
|
||||
}
|
||||
|
||||
.login-auth-split--center .auth-right-wrap .form {
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
html.dark .login-auth-split--center .auth-right-wrap {
|
||||
border: none;
|
||||
box-shadow:
|
||||
0 8px 28px rgb(0 0 0 / 42%),
|
||||
0 16px 44px rgb(0 0 0 / 46%),
|
||||
0 0 22px color-mix(in srgb, var(--el-color-primary) 20%, transparent),
|
||||
0 0 36px color-mix(in srgb, var(--el-color-primary) 9%, transparent);
|
||||
}
|
||||
|
||||
/* 居中模式下页脚弱化,减少「分区后台」感 */
|
||||
.login-page-footer--floating-layout {
|
||||
border-top-color: color-mix(in srgb, var(--el-border-color-lighter) 55%, transparent);
|
||||
}
|
||||
|
||||
html.dark .login-page-footer--floating-layout {
|
||||
border-top-color: rgb(255 255 255 / 8%);
|
||||
}
|
||||
|
||||
@media only screen and (width <= 1180px) {
|
||||
.login-auth-split {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.login-auth-split__col--illustration {
|
||||
flex: 0 0 auto !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-auth-split__col--form {
|
||||
flex: 1 1 auto !important;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 窄屏横排切竖排:恢复插画;居中模式本身无插画,不受影响 */
|
||||
.login-auth-split--center .auth-right-wrap {
|
||||
width: min(520px, calc(100vw - 1.5rem)) !important;
|
||||
max-width: min(520px, calc(100vw - 1.5rem));
|
||||
max-height: calc(100dvh - 6rem);
|
||||
}
|
||||
}
|
||||
|
||||
/* 授权页表单区域(左右分栏时) */
|
||||
.auth-right-wrap {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 440px;
|
||||
height: 720px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
overflow: hidden;
|
||||
animation: slideInRight 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
|
||||
|
||||
@media (width <= 640px) {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: calc(100vh - 10rem);
|
||||
padding-right: 1.75rem;
|
||||
padding-left: 1.75rem;
|
||||
}
|
||||
|
||||
@media (width <= 768px) {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.form {
|
||||
height: 100%;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 600;
|
||||
line-height: 2.5rem;
|
||||
color: var(--fa-gray-900);
|
||||
|
||||
@media (width <= 768px) {
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem;
|
||||
}
|
||||
|
||||
@media (width <= 640px) {
|
||||
padding-top: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
margin-top: 10px;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
color: var(--fa-gray-600);
|
||||
}
|
||||
|
||||
.custom-height {
|
||||
height: 40px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.login-page-panel {
|
||||
.btn {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
html.dark .login-page-panel {
|
||||
.text-g-800 {
|
||||
color: rgb(255 255 255 / 78%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.login-page-form {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.login-page-form .el-form-item {
|
||||
margin-bottom: 1.1rem;
|
||||
}
|
||||
|
||||
.el-select__wrapper {
|
||||
min-height: 42px !important;
|
||||
}
|
||||
|
||||
.login-secondary-actions .login-secondary-btn {
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
.login-page-form .el-input__wrapper {
|
||||
background-color: rgb(255 255 255 / 6%) !important;
|
||||
box-shadow: 0 0 0 1px rgb(255 255 255 / 10%) inset !important;
|
||||
}
|
||||
|
||||
.login-page-form .el-input__inner {
|
||||
color: rgb(255 255 255 / 92%);
|
||||
}
|
||||
|
||||
.login-page-form .el-input__inner::placeholder {
|
||||
color: rgb(255 255 255 / 35%);
|
||||
}
|
||||
|
||||
.login-page-form .el-select .el-select__wrapper {
|
||||
background-color: rgb(255 255 255 / 6%) !important;
|
||||
box-shadow: 0 0 0 1px rgb(255 255 255 / 10%) inset !important;
|
||||
}
|
||||
|
||||
.login-page-form .el-select__placeholder {
|
||||
color: rgb(255 255 255 / 40%);
|
||||
}
|
||||
|
||||
.login-page-form .el-select__selected-item {
|
||||
color: rgb(255 255 255 / 92%);
|
||||
}
|
||||
|
||||
.login-remember .el-checkbox__label {
|
||||
color: rgb(255 255 255 / 65%);
|
||||
}
|
||||
|
||||
.login-secondary-btn {
|
||||
color: rgb(255 255 255 / 85%) !important;
|
||||
background: transparent !important;
|
||||
border-color: rgb(255 255 255 / 22%) !important;
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
border-color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.login-secondary-actions .login-secondary-btn {
|
||||
height: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-page-footer {
|
||||
margin-top: 0;
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
.login-footer-text {
|
||||
line-height: 1.65;
|
||||
color: var(--el-text-color-secondary) !important;
|
||||
}
|
||||
|
||||
.login-page-footer__link {
|
||||
color: var(--el-text-color-regular);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.login-page-footer__sep {
|
||||
margin: 0 0.35rem;
|
||||
color: var(--el-border-color);
|
||||
}
|
||||
|
||||
.login-page-footer__record {
|
||||
display: inline-block;
|
||||
margin-left: 0.35rem;
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
html.dark .login-page-footer {
|
||||
border-top-color: rgb(255 255 255 / 8%);
|
||||
|
||||
.login-footer-text {
|
||||
color: rgb(255 255 255 / 42%) !important;
|
||||
}
|
||||
|
||||
.login-page-footer__link {
|
||||
color: rgb(255 255 255 / 48%);
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.login-page-footer__sep {
|
||||
color: rgb(255 255 255 / 25%);
|
||||
}
|
||||
|
||||
.login-page-footer__record {
|
||||
color: rgb(255 255 255 / 35%);
|
||||
}
|
||||
}
|
||||
|
||||
.login-mobile-otp-cell {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 48px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
text-align: center;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
background: var(--el-fill-color-blank);
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
|
||||
&:focus {
|
||||
border-color: var(--el-color-primary);
|
||||
box-shadow: 0 0 0 1px var(--el-color-primary) inset;
|
||||
}
|
||||
}
|
||||
|
||||
.login-mobile-sms-btn {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.login-mobile-actions {
|
||||
.el-button + .el-button {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.login-qr-card {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
.login-mobile-otp-cell {
|
||||
background: rgb(255 255 255 / 6%);
|
||||
border-color: rgb(255 255 255 / 14%);
|
||||
|
||||
&:focus {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
@reference '@styles/core/tailwind.css';
|
||||
|
||||
/**
|
||||
* 登录根布局
|
||||
* - 顶栏 fixed 透明叠在页面上(不占一条实体顶区);仅插画列 / 表单区随布局切换
|
||||
* - left / right:插画列与表单列横向排列(约 65% / 35%)
|
||||
* - center:全宽 + 视口级背景;登录卡居中、阴影高亮
|
||||
*/
|
||||
.login-auth-split {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.login-auth-split__col--illustration {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.login-auth-split__col--form {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 横向:插画 ~65%,表单列占剩余 */
|
||||
.login-auth-split--left,
|
||||
.login-auth-split--right {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.login-auth-split--left .login-auth-split__col--illustration,
|
||||
.login-auth-split--right .login-auth-split__col--illustration {
|
||||
flex: 0 0 65%;
|
||||
}
|
||||
|
||||
.login-auth-split--left .login-auth-split__col--form,
|
||||
.login-auth-split--right .login-auth-split__col--form {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.login-auth-split--left {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
@media only screen and (width <= 1600px) {
|
||||
.login-auth-split--left .login-auth-split__col--illustration,
|
||||
.login-auth-split--right .login-auth-split__col--illustration {
|
||||
flex: 0 0 60%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 居中模式:仅一列表单壳占满屏宽 */
|
||||
.login-auth-split--center {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-auth-split--center .login-auth-split__col--form {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* 顶栏与页脚之间的区域:纵向居中承载登录卡 */
|
||||
.login-auth-split--center .login-page-panel__scroll.login-page-panel__scroll--centered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
/* 盖过 Tailwind overflow-y-auto:仅卡片内滚动,外层不出滚动条 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-page-panel__scroll--centered .login-panel-align-row {
|
||||
flex: 0 1 auto;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
/* 悬浮卡片:居中模式尺寸与原先一致(520 宽、720/视口上限),无边框 + 主色高亮阴影 */
|
||||
.login-auth-split--center .auth-right-wrap {
|
||||
width: min(520px, calc(100vw - 2rem)) !important;
|
||||
max-width: min(520px, calc(100vw - 2rem));
|
||||
height: auto;
|
||||
max-height: calc(100dvh - 6.5rem);
|
||||
padding: 1.75rem 2rem 2rem;
|
||||
overflow: hidden auto;
|
||||
background-color: var(--el-bg-color);
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 6px 20px rgb(15 23 42 / 9%),
|
||||
0 14px 36px rgb(15 23 42 / 10%),
|
||||
0 0 18px color-mix(in srgb, var(--el-color-primary) 16%, transparent),
|
||||
0 0 32px color-mix(in srgb, var(--el-color-primary) 7%, transparent);
|
||||
}
|
||||
|
||||
.login-auth-split--center .auth-right-wrap .form {
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
html.dark .login-auth-split--center .auth-right-wrap {
|
||||
border: none;
|
||||
box-shadow:
|
||||
0 8px 28px rgb(0 0 0 / 42%),
|
||||
0 16px 44px rgb(0 0 0 / 46%),
|
||||
0 0 22px color-mix(in srgb, var(--el-color-primary) 20%, transparent),
|
||||
0 0 36px color-mix(in srgb, var(--el-color-primary) 9%, transparent);
|
||||
}
|
||||
|
||||
/* 居中模式下页脚弱化,减少「分区后台」感 */
|
||||
.login-page-footer--floating-layout {
|
||||
border-top-color: color-mix(in srgb, var(--el-border-color-lighter) 55%, transparent);
|
||||
}
|
||||
|
||||
html.dark .login-page-footer--floating-layout {
|
||||
border-top-color: rgb(255 255 255 / 8%);
|
||||
}
|
||||
|
||||
@media only screen and (width <= 1180px) {
|
||||
.login-auth-split {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.login-auth-split__col--illustration {
|
||||
flex: 0 0 auto !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-auth-split__col--form {
|
||||
flex: 1 1 auto !important;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 窄屏横排切竖排:恢复插画;居中模式本身无插画,不受影响 */
|
||||
.login-auth-split--center .auth-right-wrap {
|
||||
width: min(520px, calc(100vw - 1.5rem)) !important;
|
||||
max-width: min(520px, calc(100vw - 1.5rem));
|
||||
max-height: calc(100dvh - 6rem);
|
||||
}
|
||||
}
|
||||
|
||||
/* 授权页表单区域(左右分栏时) */
|
||||
.auth-right-wrap {
|
||||
@apply relative w-[440px] shrink-0 h-[720px] py-[5px] overflow-hidden
|
||||
max-sm:px-7 max-sm:w-full max-sm:h-auto max-sm:max-h-[calc(100vh-10rem)]
|
||||
animate-[slideInRight_0.6s_cubic-bezier(0.25,0.46,0.45,0.94)_forwards]
|
||||
max-md:animate-none;
|
||||
|
||||
.form {
|
||||
@apply h-full py-[40px];
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-g-900 text-4xl font-semibold max-md:text-3xl max-sm:pt-10;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
@apply mt-[10px] text-g-600 text-sm;
|
||||
}
|
||||
|
||||
.custom-height {
|
||||
@apply !h-[40px];
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
@use "@styles/core/mixin.scss" as *;
|
||||
|
||||
// 设置抽屉模态框样式
|
||||
.setting-modal {
|
||||
background: transparent !important;
|
||||
|
||||
.el-drawer {
|
||||
// 背景滤镜效果
|
||||
background: rgba($color: #fff, $alpha: 50%) !important;
|
||||
box-shadow: 0 0 30px rgb(0 0 0 / 10%) !important;
|
||||
|
||||
@include backdropBlur();
|
||||
|
||||
.setting-box-wrap {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
width: calc(100% + 15px);
|
||||
margin-bottom: 10px;
|
||||
|
||||
.setting-item {
|
||||
box-sizing: border-box;
|
||||
width: calc(33.333% - 15px);
|
||||
margin-right: 15px;
|
||||
text-align: center;
|
||||
|
||||
.box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 52px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border: 2px solid var(--default-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 8px 0 rgb(0 0 0 / 10%);
|
||||
transition: box-shadow 0.1s;
|
||||
|
||||
&.mt-16 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
border: 2px solid var(--theme-color);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 去除滚动条
|
||||
.el-drawer__body::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.setting-modal {
|
||||
.el-drawer {
|
||||
background: rgba($color: #000, $alpha: 50%) !important;
|
||||
|
||||
.setting-item {
|
||||
.box {
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 去除火狐浏览器滚动条
|
||||
:deep(.el-drawer__body) {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
// 移动端隐藏
|
||||
@media screen and (width <= 800px) {
|
||||
.mobile-hide {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
@use "@styles/core/mixin.scss" as *;
|
||||
|
||||
// 菜单样式变量
|
||||
$menu-height: 42px;
|
||||
$menu-icon-size: 20px;
|
||||
$menu-font-size: 14px;
|
||||
$hover-bg-color: var(--fa-gray-200);
|
||||
$popup-menu-height: 40px;
|
||||
$popup-menu-padding: 8px;
|
||||
$popup-menu-margin: 5px;
|
||||
$popup-menu-radius: 6px;
|
||||
|
||||
// 通用菜单项样式
|
||||
@mixin menu-item-base {
|
||||
width: calc(100% - 16px);
|
||||
margin-left: 8px;
|
||||
border-radius: 6px;
|
||||
|
||||
.menu-icon {
|
||||
margin-left: -7px;
|
||||
}
|
||||
}
|
||||
|
||||
// 通用 hover 样式
|
||||
@mixin menu-hover($bg-color) {
|
||||
.el-sub-menu__title:hover,
|
||||
.el-menu-item:not(.is-active):hover {
|
||||
background: $bg-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 通用选中样式
|
||||
@mixin menu-active($color, $bg-color, $icon-color: var(--theme-color)) {
|
||||
.el-menu-item.is-active {
|
||||
color: $color !important;
|
||||
background-color: $bg-color;
|
||||
|
||||
.menu-icon {
|
||||
.art-svg-icon {
|
||||
color: $icon-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗菜单项样式
|
||||
@mixin popup-menu-item {
|
||||
height: $popup-menu-height;
|
||||
margin-bottom: $popup-menu-margin;
|
||||
border-radius: $popup-menu-radius;
|
||||
|
||||
.menu-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 主题菜单通用样式(合并 design 和 dark 主题的共同逻辑)
|
||||
@mixin theme-menu-base {
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
@include menu-item-base;
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗菜单通用样式
|
||||
@mixin popup-menu-base($hover-bg, $active-color, $active-bg) {
|
||||
.el-menu--popup {
|
||||
padding: $popup-menu-padding;
|
||||
|
||||
.el-sub-menu__title:hover,
|
||||
.el-menu-item:hover {
|
||||
background-color: $hover-bg !important;
|
||||
border-radius: $popup-menu-radius;
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
@include popup-menu-item;
|
||||
|
||||
&.is-active {
|
||||
color: $active-color !important;
|
||||
background-color: $active-bg !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu {
|
||||
@include popup-menu-item;
|
||||
|
||||
height: $popup-menu-height !important;
|
||||
|
||||
.el-sub-menu__title {
|
||||
height: $popup-menu-height !important;
|
||||
border-radius: $popup-menu-radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-sidebar {
|
||||
// ---------------------- Modify default style ----------------------
|
||||
|
||||
// 菜单折叠样式
|
||||
.menu-left-close {
|
||||
.header {
|
||||
.logo {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单图标
|
||||
.menu-icon {
|
||||
margin-right: 8px;
|
||||
font-size: $menu-icon-size;
|
||||
}
|
||||
|
||||
// 菜单高度
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
height: $menu-height !important;
|
||||
margin-bottom: 4px;
|
||||
line-height: $menu-height !important;
|
||||
|
||||
span {
|
||||
font-size: $menu-font-size !important;
|
||||
|
||||
@include ellipsis();
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧箭头
|
||||
.el-sub-menu__icon-arrow {
|
||||
width: 13px !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
// 菜单折叠
|
||||
.el-menu--collapse {
|
||||
.el-sub-menu.is-active {
|
||||
.el-sub-menu__title {
|
||||
.menu-icon {
|
||||
.art-svg-icon {
|
||||
// 选中菜单图标颜色
|
||||
color: var(--theme-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- Design theme menu ----------------------
|
||||
.el-menu-design {
|
||||
@include theme-menu-base;
|
||||
@include menu-active(var(--theme-color), var(--el-color-primary-light-9));
|
||||
@include menu-hover($hover-bg-color);
|
||||
|
||||
.el-sub-menu__icon-arrow {
|
||||
color: var(--fa-gray-600);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- Dark theme menu ----------------------
|
||||
.el-menu-dark {
|
||||
@include theme-menu-base;
|
||||
@include menu-active(#fff, #27282d, #fff);
|
||||
@include menu-hover(#0f1015);
|
||||
|
||||
.el-sub-menu__icon-arrow {
|
||||
color: var(--fa-gray-400);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- Light theme menu ----------------------
|
||||
.el-menu-light {
|
||||
.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
.menu-icon {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item.is-active {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
|
||||
.art-svg-icon {
|
||||
color: var(--theme-color) !important;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
content: "";
|
||||
background: var(--theme-color);
|
||||
}
|
||||
}
|
||||
|
||||
@include menu-hover($hover-bg-color);
|
||||
|
||||
.el-sub-menu__icon-arrow {
|
||||
color: var(--fa-gray-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (width <= 640px) {
|
||||
.layout-sidebar {
|
||||
.el-menu-design {
|
||||
> .el-sub-menu {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.el-sub-menu {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单折叠 hover 弹窗样式(浅色主题)
|
||||
.el-menu--vertical,
|
||||
.el-menu--popup-container {
|
||||
@include popup-menu-base(var(--fa-gray-200), var(--fa-gray-900), var(--fa-gray-200));
|
||||
}
|
||||
|
||||
// 暗黑模式菜单样式
|
||||
.dark {
|
||||
.el-menu--vertical,
|
||||
.el-menu--popup-container {
|
||||
@include popup-menu-base(var(--fa-gray-200), var(--fa-gray-900), #292a2e);
|
||||
}
|
||||
|
||||
.layout-sidebar {
|
||||
// 图标颜色、文字颜色
|
||||
.menu-icon .art-svg-icon,
|
||||
.menu-name {
|
||||
color: var(--fa-gray-800) !important;
|
||||
}
|
||||
|
||||
// 选中的文字颜色跟图标颜色
|
||||
.el-menu-item.is-active {
|
||||
span,
|
||||
.menu-icon .art-svg-icon {
|
||||
color: var(--theme-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧箭头颜色
|
||||
.el-sub-menu__icon-arrow {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
.layout-sidebar {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
user-select: none;
|
||||
scrollbar-width: none;
|
||||
border-right: 1px solid var(--fa-card-border);
|
||||
|
||||
&.no-border {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
// 自定义滚动条宽度
|
||||
:deep(.el-scrollbar__bar.is-vertical) {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-scrollbar__thumb) {
|
||||
right: -2px;
|
||||
background-color: #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.dual-menu-left {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--fa-card-border) !important;
|
||||
transition: width 0.25s;
|
||||
|
||||
.logo {
|
||||
margin: auto;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ul {
|
||||
li {
|
||||
> div {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 8px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
|
||||
.art-svg-icon {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: -webkit-box;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
font-size: 12px;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: var(--el-color-primary-light-9);
|
||||
|
||||
.art-svg-icon,
|
||||
span {
|
||||
color: var(--theme-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.switch-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 15px;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: 100vh;
|
||||
|
||||
@media only screen and (width <= 640px) {
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.dual-menu-collapse-btn {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dual-menu-collapse-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -11px;
|
||||
z-index: 10;
|
||||
width: 11px;
|
||||
height: 50px;
|
||||
cursor: pointer;
|
||||
background-color: var(--default-box-color);
|
||||
border: 1px solid var(--fa-card-border);
|
||||
border-radius: 0 15px 15px 0;
|
||||
opacity: 0;
|
||||
transform: translateY(-50%);
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
.art-svg-icon {
|
||||
color: var(--fa-gray-800) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.art-svg-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -4px;
|
||||
margin: auto;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
line-height: 60px;
|
||||
cursor: pointer;
|
||||
|
||||
.logo {
|
||||
margin-left: 22px;
|
||||
}
|
||||
|
||||
p {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 58px;
|
||||
box-sizing: border-box;
|
||||
margin-left: 10px;
|
||||
font-size: 18px;
|
||||
|
||||
&.is-dual-menu-name {
|
||||
left: 25px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
box-sizing: border-box;
|
||||
// 防止菜单内的滚动影响整个页面滚动
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: none;
|
||||
border-right: 0;
|
||||
-ms-scroll-chaining: contain;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-model {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (width <= 800px) {
|
||||
.layout-sidebar {
|
||||
width: 0;
|
||||
|
||||
.header {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.el-menu--collapse {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
// 折叠状态下的header样式
|
||||
.menu-left-close .header {
|
||||
.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
p {
|
||||
left: 16px;
|
||||
font-size: 0;
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-model {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: rgba($color: #000, $alpha: 50%);
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (width <= 640px) {
|
||||
.layout-sidebar {
|
||||
border-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.layout-sidebar {
|
||||
border-right: 1px solid rgb(255 255 255 / 13%);
|
||||
|
||||
:deep(.el-scrollbar__thumb) {
|
||||
background-color: #777;
|
||||
}
|
||||
|
||||
.dual-menu-left {
|
||||
border-right: 1px solid rgb(255 255 255 / 9%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
.fa-table {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
|
||||
.fa-table__main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding-top: 10px;
|
||||
|
||||
// VueDraggable 透传高度,确保 ElTable height: 100% 能正确解析
|
||||
> * {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-loading-mask) {
|
||||
z-index: 100;
|
||||
background-color: var(--default-box-color) !important;
|
||||
}
|
||||
|
||||
// Loading 过渡动画 - 消失时淡出
|
||||
.loading-fade-leave-active {
|
||||
transition: opacity 0.3s ease-out;
|
||||
}
|
||||
|
||||
.loading-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// 空状态垂直居中
|
||||
&.is-empty {
|
||||
:deep(.el-table__body-wrapper) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
padding-top: 13px;
|
||||
|
||||
:deep(.el-select) {
|
||||
width: 102px !important;
|
||||
}
|
||||
|
||||
// 分页对齐方式
|
||||
&.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
// 自定义分页组件样式
|
||||
&.custom-pagination {
|
||||
:deep(.el-pagination) {
|
||||
.btn-prev,
|
||||
.btn-next {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--fa-gray-300);
|
||||
transition: border-color 0.15s;
|
||||
|
||||
&:hover:not(.is-disabled) {
|
||||
color: var(--theme-color);
|
||||
border-color: var(--theme-color);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
box-sizing: border-box;
|
||||
font-weight: 400 !important;
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--fa-gray-300);
|
||||
transition: border-color 0.15s;
|
||||
|
||||
&.is-active {
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
background-color: var(--theme-color);
|
||||
border: 1px solid var(--theme-color);
|
||||
}
|
||||
|
||||
&:hover:not(.is-disabled) {
|
||||
border-color: var(--theme-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 表格按钮悬浮效果
|
||||
.hover-btn {
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
|
||||
filter: brightness(1.1);
|
||||
transform: translateY(-2px) scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端分页
|
||||
@media (width <= 640px) {
|
||||
:deep(.el-pagination) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
$box-radius: calc(var(--custom-radius) / 3 + 2px);
|
||||
|
||||
// 全屏容器 z-index 调整
|
||||
.w-e-full-screen-container {
|
||||
z-index: 100 !important;
|
||||
}
|
||||
|
||||
/* 编辑器容器 */
|
||||
.editor-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid var(--fa-gray-300);
|
||||
border-radius: $box-radius !important;
|
||||
|
||||
.w-e-bar {
|
||||
border-radius: $box-radius $box-radius 0 0 !important;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 工具栏 */
|
||||
.editor-toolbar {
|
||||
border-bottom: 1px solid var(--default-border);
|
||||
}
|
||||
|
||||
/* 下拉选择框配置 */
|
||||
.w-e-select-list {
|
||||
min-width: 140px;
|
||||
padding: 5px 10px 10px;
|
||||
border: none;
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 下拉选择框元素配置 */
|
||||
.w-e-select-list ul li {
|
||||
margin-top: 5px;
|
||||
font-size: 15px !important;
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 下拉选择框 正文文字大小调整 */
|
||||
.w-e-select-list ul li:last-of-type {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
/* 下拉选择框 hover 样式调整 */
|
||||
.w-e-select-list ul li:hover {
|
||||
background-color: var(--fa-gray-200);
|
||||
}
|
||||
|
||||
:root {
|
||||
/* 激活颜色 */
|
||||
--w-e-toolbar-active-bg-color: var(--fa-gray-200);
|
||||
|
||||
/* toolbar 图标和文字颜色 */
|
||||
--w-e-toolbar-color: #000;
|
||||
|
||||
/* 表格选中时候的边框颜色 */
|
||||
--w-e-textarea-selected-border-color: #ddd;
|
||||
|
||||
/* 表格头背景颜色 */
|
||||
--w-e-textarea-slight-bg-color: var(--fa-gray-200);
|
||||
}
|
||||
|
||||
/* 工具栏按钮样式 */
|
||||
.w-e-bar-item svg {
|
||||
fill: var(--fa-gray-800);
|
||||
}
|
||||
|
||||
.w-e-bar-item button {
|
||||
color: var(--fa-gray-800);
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 工具栏 hover 按钮背景颜色 */
|
||||
.w-e-bar-item button:hover {
|
||||
background-color: var(--fa-gray-200);
|
||||
}
|
||||
|
||||
/* 工具栏分割线 */
|
||||
.w-e-bar-divider {
|
||||
height: 20px;
|
||||
margin-top: 10px;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
/* 工具栏菜单 */
|
||||
.w-e-bar-item-group .w-e-bar-item-menus-container {
|
||||
min-width: 120px;
|
||||
padding: 10px 0;
|
||||
border: none;
|
||||
border-radius: $box-radius;
|
||||
|
||||
.w-e-bar-item {
|
||||
button {
|
||||
width: 100%;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 代码块 */
|
||||
.w-e-text-container [data-slate-editor] pre > code {
|
||||
padding: 0.6rem 1rem;
|
||||
background-color: var(--fa-gray-50);
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 弹出框 */
|
||||
.w-e-drop-panel {
|
||||
border: 0;
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #318ef4;
|
||||
}
|
||||
|
||||
.w-e-text-container {
|
||||
[data-slate-editor] {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 0.8em 0 0.4em;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.125em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 1.5em;
|
||||
margin: 0.8em 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
ul ul {
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
ul ul ul {
|
||||
list-style: square;
|
||||
}
|
||||
}
|
||||
|
||||
strong,
|
||||
b {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
i,
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
.w-e-text-container [data-slate-editor] .table-container th {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.w-e-text-container [data-slate-editor] .table-container th:last-of-type {
|
||||
border-right: 1px solid #ccc !important;
|
||||
}
|
||||
|
||||
/* 引用 */
|
||||
.w-e-text-container [data-slate-editor] blockquote {
|
||||
background-color: var(--fa-gray-200);
|
||||
border-left: 4px solid var(--fa-gray-300);
|
||||
}
|
||||
|
||||
/* 输入区域弹出 bar */
|
||||
.w-e-hover-bar {
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 超链接弹窗 */
|
||||
.w-e-modal {
|
||||
border: none;
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
/* 图片样式调整 */
|
||||
.w-e-text-container [data-slate-editor] .w-e-selected-image-container {
|
||||
overflow: inherit;
|
||||
|
||||
&:hover {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 1px solid transparent;
|
||||
transition: border 0.3s;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid #318ef4 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.w-e-image-dragger {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #318ef4;
|
||||
border: 2px solid #fff;
|
||||
border-radius: $box-radius;
|
||||
}
|
||||
|
||||
.left-top {
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
}
|
||||
|
||||
.right-top {
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
}
|
||||
|
||||
.left-bottom {
|
||||
bottom: -6px;
|
||||
left: -6px;
|
||||
}
|
||||
|
||||
.right-bottom {
|
||||
right: -6px;
|
||||
bottom: -6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
// Element Plus 样式优化
|
||||
@use "./core/el-ui";
|
||||
|
||||
// Element Plus 功能性颜色覆写 CSS 变量
|
||||
@use "./core/variables";
|
||||
|
||||
// Element Plus 暗黑主题
|
||||
@use "./core/el-dark";
|
||||
|
||||
@@ -21,24 +24,3 @@
|
||||
|
||||
// 主题切换圆形扩散动画
|
||||
@use "./core/theme-animation";
|
||||
|
||||
// CRUD 页面通用样式
|
||||
@use "./core/crud";
|
||||
|
||||
// 布局组件样式
|
||||
@use "./layouts";
|
||||
|
||||
// 侧边栏菜单样式
|
||||
@use "./fa-sidebar-menu";
|
||||
|
||||
// 侧边栏菜单主题样式
|
||||
@use "./fa-sidebar-menu-theme";
|
||||
|
||||
// 设置面板样式
|
||||
@use "./fa-settings-panel";
|
||||
|
||||
// 表格组件样式
|
||||
@use "./fa-table";
|
||||
|
||||
// 富文本编辑器样式
|
||||
@use "./fa-wang-editor";
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* 全局 SCSS 变量(由 vite.config.ts → css.preprocessorOptions.scss.additionalData 注入到每个 `.vue` 的 `lang="scss"`)
|
||||
*
|
||||
* 默认主色请与 `src/config/setting.ts` 中 `themeColorPresets` 首项、主题切换逻辑保持一致。
|
||||
*/
|
||||
|
||||
// 供组件内 `$primary-base` 等引用(可选)
|
||||
$primary-base: #4080ff !default;
|
||||
@@ -9,11 +9,10 @@ import { StorageConfig } from "@utils";
|
||||
import { BANNER } from "../../../build/banner";
|
||||
|
||||
// -----------------------------
|
||||
// Console banner
|
||||
// Console banner:ANSI 转义码生成网站 https://patorjk.com/software/taag/#p=testall&f=Fire+Font-k&t=fastapiadmin%0A&x=none&v=4&h=4&w=80&we=false
|
||||
// -----------------------------
|
||||
|
||||
export function printConsoleBanner(): void {
|
||||
|
||||
const asciiArt = `
|
||||
\x1b[32m欢迎使用 ${StorageConfig.appName}-v${StorageConfig.CURRENT_VERSION}!
|
||||
\x1b[0m
|
||||
|
||||
@@ -265,13 +265,14 @@ export const getTabConfig = (style: string) =>
|
||||
|
||||
// -----------------------------
|
||||
// EmojiText (default export-compatible)
|
||||
// const EmojiIcon = ['🟢', '🔴', '🟡 ', '🚀', '✨', '💡', '🛠️', '🔥', '🎉', '🌟', '🌈']
|
||||
// -----------------------------
|
||||
|
||||
export const EmojiText: { [key: string]: string } = {
|
||||
"0": "O_O",
|
||||
"200": "^_^",
|
||||
"400": "T_T",
|
||||
"500": "X_X",
|
||||
"0": "O_O", // 空
|
||||
"200": "^_^", // 成功
|
||||
"400": "T_T", // 错误请求
|
||||
"500": "X_X", // 服务器内部错误,无法完成请求
|
||||
};
|
||||
|
||||
// -----------------------------
|
||||
|
||||
@@ -25,9 +25,7 @@
|
||||
留言讨论
|
||||
</ElButton>
|
||||
<ElButton @click="visible = false">关闭</ElButton>
|
||||
<ElButton v-if="articleId != null" v-auth="'edit'" type="primary" @click="onEditClick">
|
||||
编辑
|
||||
</ElButton>
|
||||
<ElButton v-if="articleId != null" type="primary" @click="onEditClick"> 编辑 </ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDrawer>
|
||||
@@ -37,6 +35,7 @@
|
||||
import "@styles/core/md.scss";
|
||||
import "@styles/custom/one-dark-pro.scss";
|
||||
import axios from "axios";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
defineOptions({ name: "ArticleDetailDrawer" });
|
||||
|
||||
@@ -91,7 +90,7 @@ const fetchDetail = async () => {
|
||||
|
||||
if (data.code === 200) {
|
||||
articleTitle.value = data.data.title;
|
||||
articleHtml.value = data.data.html_content;
|
||||
articleHtml.value = DOMPurify.sanitize(data.data.html_content);
|
||||
} else {
|
||||
error.value = "文章加载失败";
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<FaSvgIcon icon="ri:message-3-line" class="mr-1" />
|
||||
留言墙
|
||||
</ElButton>
|
||||
<ElButton type="primary" @click="toAddArticle" v-auth="'add'">新增文章</ElButton>
|
||||
<ElButton type="primary" @click="toAddArticle">新增文章</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
</div>
|
||||
<ElButton
|
||||
class="opacity-0 group-hover:opacity-100"
|
||||
v-auth="'edit'"
|
||||
size="small"
|
||||
@click.stop="toEdit(item)"
|
||||
>
|
||||
|
||||
@@ -670,144 +670,6 @@ async function submitForget() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "@styles/fa-login.css";
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-page-panel {
|
||||
:deep(.btn) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
html.dark .login-page-panel {
|
||||
:deep(.text-g-800) {
|
||||
color: rgb(255 255 255 / 78%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 副标题与表单块间距(login-page-form 在子组件内,须 :deep) */
|
||||
:deep(.login-page-form) {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
:deep(.login-page-form .el-form-item) {
|
||||
margin-bottom: 1.1rem;
|
||||
}
|
||||
|
||||
:deep(.el-select__wrapper) {
|
||||
min-height: 42px !important;
|
||||
}
|
||||
|
||||
/* 仅账号登录底部「手机号 / 扫码」双列按钮需要 42px;手机号登录页的返回钮与主按钮同为 h-11,勿全局压高度 */
|
||||
:deep(.login-secondary-actions .login-secondary-btn) {
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
:deep(.login-page-form .el-input__wrapper) {
|
||||
background-color: rgb(255 255 255 / 6%) !important;
|
||||
box-shadow: 0 0 0 1px rgb(255 255 255 / 10%) inset !important;
|
||||
}
|
||||
|
||||
:deep(.login-page-form .el-input__inner) {
|
||||
color: rgb(255 255 255 / 92%);
|
||||
}
|
||||
|
||||
:deep(.login-page-form .el-input__inner::placeholder) {
|
||||
color: rgb(255 255 255 / 35%);
|
||||
}
|
||||
|
||||
:deep(.login-page-form .el-select .el-select__wrapper) {
|
||||
background-color: rgb(255 255 255 / 6%) !important;
|
||||
box-shadow: 0 0 0 1px rgb(255 255 255 / 10%) inset !important;
|
||||
}
|
||||
|
||||
:deep(.login-page-form .el-select__placeholder) {
|
||||
color: rgb(255 255 255 / 40%);
|
||||
}
|
||||
|
||||
:deep(.login-page-form .el-select__selected-item) {
|
||||
color: rgb(255 255 255 / 92%);
|
||||
}
|
||||
|
||||
:deep(.login-remember .el-checkbox__label) {
|
||||
color: rgb(255 255 255 / 65%);
|
||||
}
|
||||
|
||||
:deep(.login-secondary-btn) {
|
||||
color: rgb(255 255 255 / 85%) !important;
|
||||
background: transparent !important;
|
||||
border-color: rgb(255 255 255 / 22%) !important;
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
border-color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.login-secondary-actions .login-secondary-btn) {
|
||||
height: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-page-footer {
|
||||
margin-top: 0;
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
.login-footer-text {
|
||||
line-height: 1.65;
|
||||
color: var(--el-text-color-secondary) !important;
|
||||
}
|
||||
|
||||
.login-page-footer__link {
|
||||
color: var(--el-text-color-regular);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.login-page-footer__sep {
|
||||
margin: 0 0.35rem;
|
||||
color: var(--el-border-color);
|
||||
}
|
||||
|
||||
.login-page-footer__record {
|
||||
display: inline-block;
|
||||
margin-left: 0.35rem;
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
html.dark .login-page-footer {
|
||||
border-top-color: rgb(255 255 255 / 8%);
|
||||
|
||||
.login-footer-text {
|
||||
color: rgb(255 255 255 / 42%) !important;
|
||||
}
|
||||
|
||||
.login-page-footer__link {
|
||||
color: rgb(255 255 255 / 48%);
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.login-page-footer__sep {
|
||||
color: rgb(255 255 255 / 25%);
|
||||
}
|
||||
|
||||
.login-page-footer__record {
|
||||
color: rgb(255 255 255 / 35%);
|
||||
}
|
||||
}
|
||||
<style scoped lang="scss">
|
||||
@use "@styles/custom/fa-login";
|
||||
</style>
|
||||
|
||||
@@ -170,6 +170,7 @@ import FaUserTableSelect from "@/components/forms/fa-search-bar/FaUserTableSelec
|
||||
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
|
||||
import FaForm from "@/components/forms/fa-form/index.vue";
|
||||
import { ElTag, ElMessage } from "element-plus";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
defineOptions({
|
||||
name: "Notice",
|
||||
@@ -307,9 +308,12 @@ const noticeDetailItems: import("@/components/others/fa-descriptions/index.vue")
|
||||
{ label: "更新时间", prop: "updated_time" },
|
||||
];
|
||||
|
||||
/** 详情富文本 HTML(用于预览) */
|
||||
/** 详情富文本 HTML(用于预览,已做 XSS 净化) */
|
||||
const detailContentHtml = computed({
|
||||
get: () => detailFormData.value.notice_content ?? "",
|
||||
get: () => {
|
||||
const raw = detailFormData.value.notice_content ?? "";
|
||||
return DOMPurify.sanitize(raw);
|
||||
},
|
||||
set: (v: string) => {
|
||||
detailFormData.value.notice_content = v;
|
||||
},
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
</template>
|
||||
<template #reply_content>
|
||||
<div v-if="detailFormData.reply" class="ticket-html-preview">
|
||||
<div v-html="detailFormData.reply" />
|
||||
<div v-html="sanitizedReply" />
|
||||
</div>
|
||||
<p v-else class="ticket-html-empty">暂无回复</p>
|
||||
</template>
|
||||
@@ -217,6 +217,7 @@ import FaForm from "@/components/forms/fa-form/index.vue";
|
||||
import FaWangEditor from "@/components/forms/fa-wang-editor/index.vue";
|
||||
import { ElTag, ElMessage, ElSelect, ElOption, ElRadioGroup, ElRadio } from "element-plus";
|
||||
import { h } from "vue";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
defineOptions({
|
||||
name: "Ticket",
|
||||
@@ -384,14 +385,23 @@ const ticketDetailItems: import("@/components/others/fa-descriptions/index.vue")
|
||||
{ label: "更新时间", prop: "updated_time" },
|
||||
];
|
||||
|
||||
/** 详情富文本 HTML(用于预览) */
|
||||
/** 详情富文本 HTML(用于预览,已做 XSS 净化) */
|
||||
const detailContentHtml = computed({
|
||||
get: () => detailFormData.value.ticket_content ?? "",
|
||||
get: () => {
|
||||
const raw = detailFormData.value.ticket_content ?? "";
|
||||
return DOMPurify.sanitize(raw);
|
||||
},
|
||||
set: (v: string) => {
|
||||
detailFormData.value.ticket_content = v;
|
||||
},
|
||||
});
|
||||
|
||||
/** 回复富文本 HTML(已做 XSS 净化) */
|
||||
const sanitizedReply = computed(() => {
|
||||
const raw = detailFormData.value.reply ?? "";
|
||||
return raw ? DOMPurify.sanitize(raw) : "";
|
||||
});
|
||||
|
||||
/** 详情是否有可视文本 */
|
||||
const detailHasRenderableContent = computed(() => {
|
||||
const raw = detailFormData.value.ticket_content ?? "";
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
/>
|
||||
|
||||
<ElCard
|
||||
class="flex flex-1 min-h-0 flex-col fa-table-card"
|
||||
class="flex flex-1 min-h-0 flex-col fa-table-card job-page-card"
|
||||
:style="{ 'margin-top': showSearchBar ? '12px' : '0' }"
|
||||
>
|
||||
<FaTableHeader
|
||||
@@ -29,30 +29,43 @@
|
||||
@refresh="refreshJobList"
|
||||
>
|
||||
<template #left>
|
||||
<div class="status-content">
|
||||
<span>调度器监控</span>
|
||||
<div class="status-item">
|
||||
<span class="label">状态:</span>
|
||||
<ElTag :type="getSchedulerStatusType(schedulerStatus.status)" size="large">
|
||||
{{ schedulerStatus.status }}
|
||||
</ElTag>
|
||||
<div class="scheduler-inline">
|
||||
<div class="scheduler-metrics">
|
||||
<div class="scheduler-metric">
|
||||
<span class="scheduler-metric__label">调度器</span>
|
||||
<ElTag
|
||||
:type="getSchedulerStatusType(schedulerStatus.status)"
|
||||
size="small"
|
||||
effect="dark"
|
||||
>
|
||||
{{ schedulerStatus.status }}
|
||||
</ElTag>
|
||||
</div>
|
||||
<ElDivider direction="vertical" />
|
||||
<div class="scheduler-metric">
|
||||
<span class="scheduler-metric__label">运行中</span>
|
||||
<ElTag
|
||||
:type="schedulerStatus.is_running ? 'success' : 'info'"
|
||||
size="small"
|
||||
effect="dark"
|
||||
>
|
||||
{{ schedulerStatus.is_running ? "是" : "否" }}
|
||||
</ElTag>
|
||||
</div>
|
||||
<ElDivider direction="vertical" />
|
||||
<div class="scheduler-metric">
|
||||
<span class="scheduler-metric__label">任务</span>
|
||||
<span class="scheduler-metric__count">{{ schedulerStatus.job_count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="label">运行中:</span>
|
||||
<ElTag :type="schedulerStatus.is_running ? 'success' : 'danger'" size="large">
|
||||
{{ schedulerStatus.is_running ? "是" : "否" }}
|
||||
</ElTag>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="label">任务数量:</span>
|
||||
<ElTag type="warning" size="large">{{ schedulerStatus.job_count }}</ElTag>
|
||||
</div>
|
||||
<div class="status-actions">
|
||||
<ElDivider direction="vertical" />
|
||||
<div class="scheduler-actions">
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:cronjob:job:scheduler']"
|
||||
type="success"
|
||||
icon="VideoPlay"
|
||||
:disabled="schedulerStatus.status !== '停止'"
|
||||
size="small"
|
||||
@click="handleStartScheduler"
|
||||
>
|
||||
启动
|
||||
@@ -62,6 +75,7 @@
|
||||
type="warning"
|
||||
icon="VideoPause"
|
||||
:disabled="schedulerStatus.status !== '运行中'"
|
||||
size="small"
|
||||
@click="handlePauseScheduler"
|
||||
>
|
||||
暂停
|
||||
@@ -71,6 +85,7 @@
|
||||
type="primary"
|
||||
icon="RefreshRight"
|
||||
:disabled="schedulerStatus.status !== '暂停'"
|
||||
size="small"
|
||||
@click="handleResumeScheduler"
|
||||
>
|
||||
恢复
|
||||
@@ -80,15 +95,19 @@
|
||||
type="danger"
|
||||
icon="SwitchButton"
|
||||
:disabled="schedulerStatus.status === '停止'"
|
||||
size="small"
|
||||
@click="handleShutdownScheduler"
|
||||
>
|
||||
关闭
|
||||
</ElButton>
|
||||
<ElDivider direction="vertical" />
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:cronjob:job:task']"
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
:disabled="schedulerStatus.job_count === 0"
|
||||
size="small"
|
||||
plain
|
||||
@click="handleClearAllJobs"
|
||||
>
|
||||
清空任务
|
||||
@@ -97,6 +116,7 @@
|
||||
v-hasPerm="['module_task:cronjob:job:query']"
|
||||
type="info"
|
||||
icon="Monitor"
|
||||
size="small"
|
||||
@click="handleOpenConsole"
|
||||
>
|
||||
控制台
|
||||
@@ -105,24 +125,18 @@
|
||||
v-hasPerm="['module_task:cronjob:job:scheduler']"
|
||||
type="primary"
|
||||
icon="Refresh"
|
||||
size="small"
|
||||
plain
|
||||
@click="handleSyncJobs"
|
||||
>
|
||||
同步
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:cronjob:job:update']"
|
||||
type="warning"
|
||||
icon="Refresh"
|
||||
@click="refreshJobList"
|
||||
>
|
||||
刷新
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</FaTableHeader>
|
||||
|
||||
<div v-loading="jobLoading" class="job-cards-container min-h-0 flex-1 overflow-auto">
|
||||
<div v-loading="jobLoading" class="job-cards-container mt-3 min-h-0 flex-1 overflow-auto">
|
||||
<ElEmpty
|
||||
v-if="!jobLoading && (!jobList || jobList.length === 0)"
|
||||
:image-size="80"
|
||||
@@ -130,123 +144,99 @@
|
||||
/>
|
||||
<ElRow v-else :gutter="16">
|
||||
<ElCol
|
||||
v-for="(job, index) in jobList"
|
||||
v-for="job in jobList"
|
||||
:key="job.id"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="8"
|
||||
:lg="6"
|
||||
:md="6"
|
||||
:lg="4"
|
||||
class="job-card-col"
|
||||
>
|
||||
<ElCard class="job-card" shadow="hover">
|
||||
<ElCard shadow="hover" :class="`job-card job-card--${getJobStatusClass(job.status)}`">
|
||||
<template #header>
|
||||
<div class="job-card-header">
|
||||
<div class="job-card-title">
|
||||
<span class="job-index">{{ index + 1 }}</span>
|
||||
<span class="job-name" :title="job.name">{{ job.name }}</span>
|
||||
</div>
|
||||
<ElTag :type="getJobStatusType(job.status)" size="small">
|
||||
<div class="job-card-title">
|
||||
<span
|
||||
class="job-card-dot"
|
||||
:class="`job-card-dot--${getJobStatusClass(job.status)}`"
|
||||
/>
|
||||
<span class="job-card-name" :title="job.name">{{ job.name }}</span>
|
||||
<ElTag :type="getJobStatusType(job.status)" size="small" effect="dark">
|
||||
{{ getJobStatusLabel(job.status) }}
|
||||
</ElTag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="job-card-body">
|
||||
<div class="job-info-item">
|
||||
<span class="job-info-label">任务ID:</span>
|
||||
<span class="job-info-value">{{ job.id }}</span>
|
||||
<div class="job-card-body-row">
|
||||
<FaSvgIcon :icon="getTriggerIcon(job.trigger)" class="job-card-meta-icon" />
|
||||
<span class="job-card-meta-text">{{ formatTrigger(job.trigger) }}</span>
|
||||
</div>
|
||||
<div class="job-info-item">
|
||||
<span class="job-info-label">触发器:</span>
|
||||
<ElTag
|
||||
v-if="String(job.trigger ?? '').includes('cron')"
|
||||
type="primary"
|
||||
size="small"
|
||||
class="job-trigger-tag"
|
||||
>
|
||||
<ElIcon><Clock /></ElIcon>
|
||||
{{ formatTrigger(job.trigger) }}
|
||||
</ElTag>
|
||||
<ElTag
|
||||
v-else-if="String(job.trigger ?? '').includes('interval')"
|
||||
type="success"
|
||||
size="small"
|
||||
class="job-trigger-tag"
|
||||
>
|
||||
<ElIcon><Timer /></ElIcon>
|
||||
{{ formatTrigger(job.trigger) }}
|
||||
</ElTag>
|
||||
<ElTag
|
||||
v-else-if="String(job.trigger ?? '').includes('date')"
|
||||
type="warning"
|
||||
size="small"
|
||||
class="job-trigger-tag"
|
||||
>
|
||||
<ElIcon><Calendar /></ElIcon>
|
||||
{{ formatTrigger(job.trigger) }}
|
||||
</ElTag>
|
||||
<ElTag v-else type="info" size="small" class="job-trigger-tag">
|
||||
{{ formatTrigger(job.trigger) }}
|
||||
</ElTag>
|
||||
</div>
|
||||
<div class="job-info-item">
|
||||
<span class="job-info-label">下次执行:</span>
|
||||
<span class="job-info-value">{{ job.next_run_time || "无" }}</span>
|
||||
<div class="job-card-body-row">
|
||||
<FaSvgIcon icon="ri:time-line" class="job-card-meta-icon" />
|
||||
<span class="job-card-meta-text">{{ job.next_run_time || "暂无" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="job-card-footer">
|
||||
<ElButton
|
||||
v-if="job.status === '暂停中'"
|
||||
v-hasPerm="['module_task:cronjob:job:task']"
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="VideoPlay"
|
||||
@click="handleResumeJob(job.id)"
|
||||
>
|
||||
恢复
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="job.status === '运行中'"
|
||||
v-hasPerm="['module_task:cronjob:job:task']"
|
||||
type="warning"
|
||||
size="small"
|
||||
icon="VideoPause"
|
||||
@click="handlePauseJob(job.id)"
|
||||
>
|
||||
暂停
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="job.status !== '已停止' && job.status !== '未知'"
|
||||
v-hasPerm="['module_task:cronjob:job:task']"
|
||||
type="success"
|
||||
size="small"
|
||||
icon="CaretRight"
|
||||
@click="handleRunJobNow(job.id)"
|
||||
>
|
||||
调试
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="job.status !== '未知'"
|
||||
v-hasPerm="['module_task:cronjob:job:task']"
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="Close"
|
||||
@click="handleRemoveJob(job.id)"
|
||||
>
|
||||
移除
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:cronjob:job:query']"
|
||||
type="info"
|
||||
size="small"
|
||||
icon="List"
|
||||
@click="handleOpenExecutionLogDrawer(job)"
|
||||
>
|
||||
记录
|
||||
</ElButton>
|
||||
</div>
|
||||
<ElRow :gutter="8">
|
||||
<ElCol :span="6">
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:cronjob:job:task']"
|
||||
:type="job.status === '暂停中' ? 'primary' : 'warning'"
|
||||
size="small"
|
||||
plain
|
||||
class="w-full"
|
||||
:icon="job.status === '暂停中' ? 'VideoPlay' : 'VideoPause'"
|
||||
:disabled="job.status !== '暂停中' && job.status !== '运行中'"
|
||||
@click="
|
||||
job.status === '暂停中' ? handleResumeJob(job.id) : handlePauseJob(job.id)
|
||||
"
|
||||
>
|
||||
{{ job.status === "暂停中" ? "恢复" : "暂停" }}
|
||||
</ElButton>
|
||||
</ElCol>
|
||||
<ElCol :span="6">
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:cronjob:job:task']"
|
||||
type="success"
|
||||
size="small"
|
||||
plain
|
||||
class="w-full"
|
||||
icon="CaretRight"
|
||||
:disabled="job.status === '已停止' || job.status === '未知'"
|
||||
@click="handleRunJobNow(job.id)"
|
||||
>
|
||||
调试
|
||||
</ElButton>
|
||||
</ElCol>
|
||||
<ElCol :span="6">
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:cronjob:job:query']"
|
||||
type="info"
|
||||
size="small"
|
||||
plain
|
||||
class="w-full"
|
||||
icon="List"
|
||||
@click="handleOpenExecutionLogDrawer(job)"
|
||||
>
|
||||
记录
|
||||
</ElButton>
|
||||
</ElCol>
|
||||
<ElCol :span="6">
|
||||
<ElButton
|
||||
v-hasPerm="['module_task:cronjob:job:task']"
|
||||
type="danger"
|
||||
size="small"
|
||||
plain
|
||||
class="w-full"
|
||||
icon="Close"
|
||||
:disabled="job.status === '未知'"
|
||||
@click="handleRemoveJob(job.id)"
|
||||
>
|
||||
移除
|
||||
</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</template>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
@@ -362,8 +352,7 @@ import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vu
|
||||
import FaJsonPretty from "@/components/others/fa-json-pretty/index.vue";
|
||||
import { useTable } from "@/hooks/core/useTable";
|
||||
import type { ColumnOption } from "@/types/component";
|
||||
import { Calendar, Clock, Timer } from "@element-plus/icons-vue";
|
||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
||||
import { ElDivider, ElMessage, ElMessageBox, ElTag } from "element-plus";
|
||||
import { computed, h, nextTick, onMounted, ref } from "vue";
|
||||
import { Terminal, TerminalApi } from "vue-web-terminal";
|
||||
|
||||
@@ -996,6 +985,27 @@ function getTriggerTypeLabel(type: string | undefined) {
|
||||
return map[type || ""] || type || "-";
|
||||
}
|
||||
|
||||
function getJobStatusClass(status: string) {
|
||||
switch (status) {
|
||||
case "运行中":
|
||||
return "running";
|
||||
case "暂停中":
|
||||
return "paused";
|
||||
case "已停止":
|
||||
return "stopped";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
function getTriggerIcon(trigger: string | undefined) {
|
||||
const t = (trigger ?? "").toLowerCase();
|
||||
if (t.includes("cron")) return "ri:timer-line";
|
||||
if (t.includes("interval")) return "ri:repeat-line";
|
||||
if (t.includes("date")) return "ri:calendar-event-line";
|
||||
return "ri:flashlight-line";
|
||||
}
|
||||
|
||||
function getLogStatusType(status: string): "primary" | "success" | "warning" | "info" | "danger" {
|
||||
const map: Record<string, "primary" | "success" | "warning" | "info" | "danger"> = {
|
||||
pending: "info",
|
||||
@@ -1033,110 +1043,165 @@ function handleViewJobState(row: JobLogTable) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.job-page :deep(.data-table) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.status-content {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-item .label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.terminal-wrapper {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.job-card-header {
|
||||
.scheduler-inline {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.el-divider--vertical {
|
||||
height: 18px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.scheduler-metrics {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.scheduler-metric {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
&__label {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
&__count {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
}
|
||||
|
||||
.scheduler-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
.el-divider--vertical {
|
||||
height: 18px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.job-page-card {
|
||||
:deep(.el-card__body) {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.job-card-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.job-card {
|
||||
transition: box-shadow 0.25s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 6%);
|
||||
}
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 8px 14px;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 8px 14px;
|
||||
}
|
||||
|
||||
:deep(.el-card__footer) {
|
||||
padding: 8px 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.job-card-title {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.job-index {
|
||||
display: inline-flex;
|
||||
.job-card-dot {
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--el-color-primary) 0%,
|
||||
var(--el-color-primary-dark-2) 100%
|
||||
);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
&--running {
|
||||
background: var(--el-color-success);
|
||||
}
|
||||
|
||||
&--paused {
|
||||
background: var(--el-color-warning);
|
||||
}
|
||||
|
||||
&--stopped {
|
||||
background: var(--el-color-danger);
|
||||
}
|
||||
|
||||
&--unknown {
|
||||
background: var(--el-color-info);
|
||||
}
|
||||
}
|
||||
|
||||
.job-name {
|
||||
.job-card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&-row {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.job-card-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.job-info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.job-info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.job-info-label {
|
||||
.job-card-meta-icon {
|
||||
flex-shrink: 0;
|
||||
width: 70px;
|
||||
font-size: 13px;
|
||||
color: var(--el-color-primary-light-3);
|
||||
}
|
||||
|
||||
.job-info-value {
|
||||
flex: 1;
|
||||
.job-card-meta-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.job-card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.job-card-footer .el-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.execution-log-drawer {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -932,7 +932,8 @@ onMounted(async () => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<!-- popover 挂载到 body,需单独写;修复 vue3-cron-plus 全局 .el-tag--info { margin-left: -60px } 误伤多选下拉里 tag -->
|
||||
<style scoped lang="scss">
|
||||
.code-editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1004,10 +1005,7 @@ onMounted(async () => {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- popover 挂载到 body,需单独写;修复 vue3-cron-plus 全局 .el-tag--info { margin-left: -60px } 误伤多选下拉里 tag -->
|
||||
<style lang="scss">
|
||||
.node-cron-popover-fix {
|
||||
.vue3-cron-plus-container .el-select .el-tag {
|
||||
margin-left: 0 !important;
|
||||
|
||||
+21
-17
@@ -6,16 +6,16 @@
|
||||
@mouseleave="showHandles = false"
|
||||
>
|
||||
<div class="node-content">
|
||||
<span class="node-label">{{ data.label }}</span>
|
||||
<span v-if="data.config && Object.keys(data.config).length > 0" class="node-badge">
|
||||
{{ Object.keys(data.config).length }}
|
||||
<span class="node-label">{{ nodeData.label }}</span>
|
||||
<span v-if="nodeData.config && Object.keys(nodeData.config).length > 0" class="node-badge">
|
||||
{{ Object.keys(nodeData.config).length }}
|
||||
</span>
|
||||
</div>
|
||||
<Handle
|
||||
v-if="nodeType.code !== 'input'"
|
||||
:id="'top-' + id"
|
||||
type="target"
|
||||
position="top"
|
||||
:position="Position.Top"
|
||||
:class="{ 'handle-visible': showHandles }"
|
||||
:style="{ background: nodeType.color || '#3b82f6' }"
|
||||
/>
|
||||
@@ -23,7 +23,7 @@
|
||||
v-if="nodeType.code !== 'input'"
|
||||
:id="'left-' + id"
|
||||
type="target"
|
||||
position="left"
|
||||
:position="Position.Left"
|
||||
:class="{ 'handle-visible': showHandles }"
|
||||
:style="{ background: nodeType.color || '#3b82f6' }"
|
||||
/>
|
||||
@@ -31,7 +31,7 @@
|
||||
v-if="nodeType.code !== 'output'"
|
||||
:id="'right-' + id"
|
||||
type="source"
|
||||
position="right"
|
||||
:position="Position.Right"
|
||||
:class="{ 'handle-visible': showHandles }"
|
||||
:style="{ background: nodeType.color || '#3b82f6' }"
|
||||
/>
|
||||
@@ -39,16 +39,16 @@
|
||||
v-if="nodeType.code !== 'output'"
|
||||
:id="'bottom-' + id"
|
||||
type="source"
|
||||
position="bottom"
|
||||
:position="Position.Bottom"
|
||||
:class="{ 'handle-visible': showHandles }"
|
||||
:style="{ background: nodeType.color || '#3b82f6' }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { Handle } from "@vue-flow/core";
|
||||
import { Handle, Position } from "@vue-flow/core";
|
||||
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
@@ -58,36 +58,40 @@ const props = defineProps({
|
||||
|
||||
const showHandles = ref(false);
|
||||
|
||||
const nodeData = computed(
|
||||
() => props.data ?? { label: "", config: {}, type: undefined, category: undefined }
|
||||
);
|
||||
|
||||
const nodeType = computed(() => {
|
||||
return {
|
||||
code: props.data?.type || "custom",
|
||||
name: props.data?.label || "自定义节点",
|
||||
color: getCategoryColor(props.data?.category),
|
||||
code: nodeData.value?.type || "custom",
|
||||
name: nodeData.value?.label || "自定义节点",
|
||||
color: getCategoryColor(nodeData.value?.category),
|
||||
};
|
||||
});
|
||||
|
||||
function getCategoryColor(category) {
|
||||
function getCategoryColor(category: string | undefined) {
|
||||
const colorMap = {
|
||||
trigger: "#e6a23c",
|
||||
action: "#409eff",
|
||||
condition: "#67c23a",
|
||||
control: "#909399",
|
||||
};
|
||||
return colorMap[category] || "#409eff";
|
||||
return colorMap[category as keyof typeof colorMap] || "#409eff";
|
||||
}
|
||||
|
||||
const nodeClass = computed(() => {
|
||||
if (props.data?.type === "input") {
|
||||
if (nodeData.value?.type === "input") {
|
||||
return "start-node";
|
||||
}
|
||||
if (props.data?.type === "output") {
|
||||
if (nodeData.value?.type === "output") {
|
||||
return "end-node";
|
||||
}
|
||||
return "custom-node";
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style scoped lang="scss">
|
||||
.vue-flow__node-input {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
+2
-2
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from "vue";
|
||||
import { ElButton, ElColorPicker, ElMessage, ElIcon } from "element-plus";
|
||||
import { Close } from "@element-plus/icons-vue";
|
||||
@@ -156,7 +156,7 @@ function handleDelete() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.edge-config-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
+1
-1
@@ -207,7 +207,7 @@ onMounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.node-config-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -114,7 +114,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineOptions({
|
||||
name: "WorkflowDefinition",
|
||||
name: "Workflow",
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
@@ -124,7 +124,7 @@ import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue"
|
||||
import FaTable from "@/components/tables/fa-table/index.vue";
|
||||
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
|
||||
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
|
||||
import FaWorkflowDesignDrawer from "../components/FaWorkflowDesignDrawer.vue";
|
||||
import FaWorkflowDesignDrawer from "./components/FaWorkflowDesignDrawer.vue";
|
||||
import { useTable } from "@/hooks/core/useTable";
|
||||
import type { ColumnOption } from "@/types/component";
|
||||
import { ArrowDown } from "@element-plus/icons-vue";
|
||||
|
||||
@@ -521,12 +521,4 @@ async function submitForm() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
Reference in New Issue
Block a user