refactor(frontend): 重构前端样式体系,统一资源路径与代码规范

1.  重构SCSS样式结构,将原有分散的样式文件整合到custom目录下,统一使用@use替代@import
2.  删除冗余的全局样式文件,将样式逻辑内联到对应组件或集中到core目录
3.  新增DOMPurify依赖,对富文本内容添加XSS防护
4.  修复metrics端点暴露的兼容性问题,优化启动banner样式
5.  移除无用的权限校验代码,重构工作流组件路径与命名
6.  优化样式混合宏与响应式布局,修复部分组件样式冲突
This commit is contained in:
zhangtao
2026-06-01 00:58:21 +08:00
parent c2135bb456
commit c0733d9861
50 changed files with 1880 additions and 1792 deletions
@@ -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>