20260921231313
This commit is contained in:
1 parent
ac710c1801
commit
583984c24f
81 files changed
+2523
-288
No files matched your search
@@ -1,7 +1,10 @@
|
||||
<script setup>
|
||||
import { defineAsyncComponent, watch } from 'vue'
|
||||
import { defineAsyncComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { setUIConfig } from '@/components/ui'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useI18nStore } from '@/stores/i18n'
|
||||
import ErrorBoundary from '@/views/fallback/ErrorBoundary.vue'
|
||||
import FallbackPage from '@/views/fallback/FallbackPage.vue'
|
||||
|
||||
// 业务应用入口:仅渲染当前路由页面
|
||||
// FPS 性能监控浮层全环境挂载(生产环境同样用于现场性能排查);
|
||||
@@ -12,6 +15,7 @@ const FpsMonitor = defineAsyncComponent(() => import('@/components/FpsMonitor.vu
|
||||
// 其他语言标签更长,竖排(label 在上)避免 label 挤压控件。
|
||||
// 写在应用根上,locale 已持久化,刷新后由 immediate 立即同步一次。
|
||||
const i18nStore = useI18nStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
function formLayoutOf(locale) {
|
||||
const isChinese = /^zh/i.test(String(locale || ''))
|
||||
@@ -22,14 +26,50 @@ watch(
|
||||
() => i18nStore.locale,
|
||||
(locale) => {
|
||||
setUIConfig({ form: { layout: formLayoutOf(locale) } })
|
||||
// 译文跟着语言走:内置文案兜底,接口失败也不阻断渲染
|
||||
i18nStore.loadMessages(locale).catch(() => {})
|
||||
// 译文跟着语言走:内置文案兜底,接口失败也不阻断渲染。
|
||||
// 未登录(停留在登录页 / 缺省页)不发请求 —— s_i18n 需要鉴权,401 会把尚在
|
||||
// 公共页面的用户一路踢到登录页(登录后的译文由路由守卫统一加载)
|
||||
if (authStore.isAuthenticated) i18nStore.loadMessages(locale).catch(() => {})
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
/* 离线兜底:断网时在应用上层盖一页离线缺省页,恢复后自动移除。
|
||||
* 用覆盖层而非 v-if 替换 —— 原页面组件始终不卸载,断网前后的表单状态、
|
||||
* 滚动位置全部保留;覆盖层 z-index 高于弹窗 / 浮层 / 提示,断网时全域可见。
|
||||
* 局限:navigator.onLine 只反映本机是否连上网络,连着局域网但服务不可达不会触发。 */
|
||||
const offline = ref(!navigator.onLine)
|
||||
|
||||
function syncOnline() {
|
||||
offline.value = !navigator.onLine
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('online', syncOnline)
|
||||
window.addEventListener('offline', syncOnline)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('online', syncOnline)
|
||||
window.removeEventListener('offline', syncOnline)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
<!-- 错误边界包住整个路由视图(含布局):渲染错误就地兜 500 页,不出白屏 -->
|
||||
<ErrorBoundary>
|
||||
<router-view />
|
||||
</ErrorBoundary>
|
||||
<FpsMonitor />
|
||||
|
||||
<!-- 离线覆盖层:断网时盖在最上层(含弹窗 / 提示),恢复网络自动消失 -->
|
||||
<FallbackPage v-if="offline" status="offline" class="fms-app-offline" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fms-app-offline {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 5000;
|
||||
}
|
||||
</style>
|
||||
@@ -397,7 +397,7 @@ const previewText = computed(() => describeAdvancedQuery(props.modelValue, itemB
|
||||
min-width: 34px;
|
||||
height: 22px;
|
||||
padding: 0 6px;
|
||||
border: 1px solid var(--fms-border, #e3e8ee);
|
||||
border: 1px solid var(--fms-border, #dfe5ec);
|
||||
border-radius: 11px;
|
||||
background: var(--fms-card, #fff);
|
||||
color: var(--fms-text-secondary, #6f7784);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
/**
|
||||
* 批量导出弹窗。
|
||||
* 自定义导出弹窗。
|
||||
*
|
||||
* 与「导出」图标按钮的分工:图标按钮一键走人(列与条件完全沿用列表);本弹窗面向
|
||||
* 「要挑列、要切区间、要分卷」的场景。界面刻意保持极简:只留「导出列」「导出方式」两块,
|
||||
@@ -147,7 +147,7 @@ function buildParams() {
|
||||
moduleId: props.moduleId,
|
||||
searchCondition: props.searchCondition,
|
||||
orderBy: props.orderBy,
|
||||
// 批量导出面向整个筛选结果,不针对勾选行
|
||||
// 自定义导出面向整个筛选结果,不针对勾选行
|
||||
columns: selectedFields(),
|
||||
}
|
||||
if (mode.value === 'range') {
|
||||
@@ -203,7 +203,7 @@ async function handleDownload() {
|
||||
<template>
|
||||
<Modal
|
||||
:open="open"
|
||||
:title="moduleName ? `批量导出 · ${moduleName}` : '批量导出'"
|
||||
:title="moduleName ? `自定义导出 · ${moduleName}` : '自定义导出'"
|
||||
width="480"
|
||||
:resizable="false"
|
||||
:fullscreen="false"
|
||||
|
||||
@@ -218,7 +218,7 @@ const columnSaving = ref(false)
|
||||
const exporting = ref(false)
|
||||
// 个人层「我的导出列」用的 schema 类型(s_user_module_pref.b_schema_type)
|
||||
const EXPORT_SCHEMA_TYPE = 'export'
|
||||
// 批量导出弹窗:个人层保存的导出列(s_user_module_pref,b_schema_type='export')
|
||||
// 自定义导出弹窗:个人层保存的导出列(s_user_module_pref,b_schema_type='export')
|
||||
const exportDialogOpen = ref(false)
|
||||
const exportSavedColumns = ref([])
|
||||
const exportPrefRowExists = ref(false)
|
||||
@@ -244,8 +244,8 @@ const effectiveRowDraggable = computed(
|
||||
// 栏位设置不在此菜单:列宽/顺序改动后由工具条按需浮出操作条承载(见 columnDirty)
|
||||
const moreMenuItems = computed(() => [
|
||||
{
|
||||
key: 'batch-export',
|
||||
label: t('common.batchExport', '批量导出'),
|
||||
key: 'custom-export',
|
||||
label: t('common.customExport', '自定义导出'),
|
||||
icon: h(FileSpreadsheet, { size: 14 }),
|
||||
disabled: !dataModule.value,
|
||||
},
|
||||
@@ -1221,7 +1221,7 @@ async function persistRowOrder(changedKeys) {
|
||||
}
|
||||
|
||||
async function handleMoreAction({ key }) {
|
||||
if (key === 'batch-export') {
|
||||
if (key === 'custom-export') {
|
||||
await openExportDialog()
|
||||
return
|
||||
}
|
||||
@@ -1276,7 +1276,7 @@ async function handleExport() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 批量导出弹窗的候选列:列表当前可见列(顺序即列表顺序),标题取字段名称 */
|
||||
/** 自定义导出弹窗的候选列:列表当前可见列(顺序即列表顺序),标题取字段名称 */
|
||||
const exportColumns = computed(() => {
|
||||
const byField = activeFieldById.value
|
||||
return (tableRef.value?.getColumnSettings() || listConfig.value || [])
|
||||
@@ -1287,7 +1287,7 @@ const exportColumns = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
/** 打开批量导出:先读个人层已保存的导出列,避免每次进页面都多查一次 */
|
||||
/** 打开自定义导出:先读个人层已保存的导出列,避免每次进页面都多查一次 */
|
||||
async function openExportDialog() {
|
||||
const moduleId = String(dataModule.value?.b_id || '').trim()
|
||||
if (!moduleId) {
|
||||
@@ -1586,7 +1586,7 @@ defineExpose({
|
||||
@reset="resetQueryLayout"
|
||||
/>
|
||||
|
||||
<!-- 批量导出:条件沿用列表当前条件,弹窗只负责选列 / 区间 / 分卷 -->
|
||||
<!-- 自定义导出:条件沿用列表当前条件,弹窗只负责选列 / 区间 / 分卷 -->
|
||||
<FmsExportDialog
|
||||
v-model:open="exportDialogOpen"
|
||||
:module-id="String(dataModule?.b_id || '')"
|
||||
@@ -1676,12 +1676,12 @@ defineExpose({
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 工具条:左「改数据」业务动作 / 中 状态 / 右「用数据」工具动作 */
|
||||
/* 工具条:左「改数据」业务动作 / 中 状态 / 右「用数据」工具动作。
|
||||
左右贴边由中间状态区的 flex: 1 撑开,无需再配 justify-content。 */
|
||||
.list-toolbar {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,9 @@ const effectiveDraggable = computed(() => props.draggable && !props.keyword.trim
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 灰底画布上的白卡:白底 + 圆角 + 阴影,与右侧内容卡用灰缝分隔。
|
||||
/* 灰底画布上的白卡:白底 + 边框 + 圆角 + 阴影,与右侧内容卡用灰缝分隔。
|
||||
* 边框界出面板边界(照 antd `ant-card-bordered` 的观感),与轻阴影叠加成「线 + 影」双层边界;
|
||||
* 宽度/高度用 border-box,边框占内圈 1px,面板外尺寸不变。
|
||||
* 内部纵向排列:头部(搜索 + 动作)→ 滚动区。 */
|
||||
.fms-tree-panel {
|
||||
position: relative;
|
||||
@@ -90,6 +92,7 @@ const effectiveDraggable = computed(() => props.draggable && !props.keyword.trim
|
||||
min-height: 0;
|
||||
padding: 8px;
|
||||
background: var(--fms-card);
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: var(--fms-control-radius);
|
||||
box-shadow: var(--fms-shadow);
|
||||
overflow: hidden;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Loader } from '@lucide/vue'
|
||||
/* ---------- props / emits / slots ---------- */
|
||||
/**
|
||||
* 按钮视觉变体
|
||||
* @typedef {'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'link' | 'icon'} ButtonType
|
||||
* @typedef {'primary' | 'secondary' | 'outline' | 'ghost' | 'danger' | 'danger-outline' | 'link' | 'icon'} ButtonType
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
@@ -15,7 +15,16 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: 'primary',
|
||||
validator: (value) =>
|
||||
['primary', 'secondary', 'outline', 'ghost', 'danger', 'link', 'icon'].includes(value),
|
||||
[
|
||||
'primary',
|
||||
'secondary',
|
||||
'outline',
|
||||
'ghost',
|
||||
'danger',
|
||||
'danger-outline',
|
||||
'link',
|
||||
'icon',
|
||||
].includes(value),
|
||||
},
|
||||
/** 禁用态 */
|
||||
disabled: {
|
||||
|
||||
@@ -122,6 +122,18 @@
|
||||
background: color-mix(in srgb, var(--fms-danger) 88%, #000);
|
||||
}
|
||||
|
||||
/* 危险操作(描边)—— 工具条里与主操作并排时使用:表达危险语义但不与 primary 抢视觉。
|
||||
实心红(.button-danger)保留给弹窗确认框里的最终提交动作。 */
|
||||
.button-danger-outline {
|
||||
background: transparent;
|
||||
border-color: var(--fms-danger);
|
||||
color: var(--fms-danger);
|
||||
}
|
||||
|
||||
.button-danger-outline:hover:not(:disabled) {
|
||||
background: color-mix(in srgb, var(--fms-danger) 8%, transparent);
|
||||
}
|
||||
|
||||
/* 链接按钮 */
|
||||
.button-link {
|
||||
background: transparent;
|
||||
|
||||
@@ -18,6 +18,7 @@ import Button from '../button/button.vue'
|
||||
<Button type="outline">描边按钮</Button>
|
||||
<Button type="ghost">低强调</Button>
|
||||
<Button type="danger">危险操作</Button>
|
||||
<Button type="danger-outline">危险操作(描边)</Button>
|
||||
<Button type="link">链接按钮</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,13 +18,13 @@ export const BUILTIN_MESSAGES = {
|
||||
'common.advancedQueryEmpty': 'No valid conditions',
|
||||
'common.applyQuery': 'Apply',
|
||||
'common.backToQuick': 'Back to basic query',
|
||||
'common.batchExport': 'Batch export',
|
||||
'common.cancel': 'Cancel',
|
||||
'common.clear': 'Clear',
|
||||
'common.columnSettingsReset': 'Column settings restored',
|
||||
'common.condition': 'Condition',
|
||||
'common.conditionCount': '{n} conditions',
|
||||
'common.conditionGroup': 'Group',
|
||||
'common.customExport': 'Custom export',
|
||||
'common.editAdvanced': 'Edit',
|
||||
'common.enabled': 'Enabled',
|
||||
'common.expand': 'Expand',
|
||||
@@ -56,5 +56,15 @@ export const BUILTIN_MESSAGES = {
|
||||
'common.search': 'Search',
|
||||
'common.selectedRows': '{n} selected',
|
||||
'common.totalRecords': '{n} records total',
|
||||
'fallback.403.desc': 'You do not have permission to access this page.',
|
||||
'fallback.403.title': 'Access denied',
|
||||
'fallback.404.desc': 'The page you visited does not exist or has been removed.',
|
||||
'fallback.404.title': 'Page not found',
|
||||
'fallback.500.desc': 'The service is temporarily unavailable. Please try again later.',
|
||||
'fallback.500.title': 'Page error',
|
||||
'fallback.backHome': 'Back to home',
|
||||
'fallback.offline.desc': 'Please check your network connection and try again.',
|
||||
'fallback.offline.title': 'Network disconnected',
|
||||
'fallback.reload': 'Reload',
|
||||
},
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { useRoute } from 'vue-router'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { Splitter, SplitterPanel } from '@/components/ui'
|
||||
import AppSidebar from './components/AppSidebar.vue'
|
||||
import AppTabs from './components/AppTabs.vue'
|
||||
import AppTopbar from './components/AppTopbar.vue'
|
||||
import DetailPage from '@/views/detail/DetailPage.vue'
|
||||
import { cacheKeyOf } from './layoutUtils'
|
||||
@@ -40,7 +41,11 @@ function getPageWrapper(name, fullPath, innerComponent, cacheByKey) {
|
||||
return () => {
|
||||
const inner = cacheByKey ? h(DetailPage, { id: detailId() }) : innerComponent
|
||||
const innerKey = `${props.fullPath}:${app.getRouteRefreshKey(props.fullPath)}`
|
||||
return h(inner, { key: innerKey })
|
||||
// 包一层单根容器再交给路由过渡:页面组件可能是多根 Fragment(如 Dashboard),
|
||||
// 非元素根无法被 <Transition> 动画化(警告且过渡失效、页面不显示);
|
||||
// 容器承接页面根的高度链(flex:1 + 纵向列),页面自身 flex:1 的语义不变。
|
||||
// 此 vnode 由渲染函数创建、不带 scoped 属性,相关样式须走 :global
|
||||
return h('div', { key: innerKey, class: 'fms-page-holder' }, [h(inner)])
|
||||
}
|
||||
},
|
||||
}),
|
||||
@@ -117,7 +122,13 @@ provide('toggleSidebar', toggleSidebar)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fms-layout" :class="{ 'is-collapsed': appStore.sidebarCollapsed }">
|
||||
<div
|
||||
class="fms-layout"
|
||||
:class="{
|
||||
'is-collapsed': appStore.sidebarCollapsed,
|
||||
'is-maximized': appStore.contentMaximized,
|
||||
}"
|
||||
>
|
||||
<Splitter
|
||||
direction="horizontal"
|
||||
bar-on-hover
|
||||
@@ -127,7 +138,7 @@ provide('toggleSidebar', toggleSidebar)
|
||||
@resize-start="dragging = true"
|
||||
@resize-end="dragging = false"
|
||||
>
|
||||
<!-- 侧栏面板:紧贴左/上/下边缘(无 padding),侧栏卡片右侧阴影与主区分隔。
|
||||
<!-- 侧栏面板:紧贴左/上/下边缘(无 padding),侧栏自身右边框与主区分隔。
|
||||
拖拽无最小宽度限制(可拖到任意窄),仅限制最大宽度 -->
|
||||
<SplitterPanel
|
||||
class="fms-sidebar-panel"
|
||||
@@ -138,18 +149,25 @@ provide('toggleSidebar', toggleSidebar)
|
||||
<AppSidebar />
|
||||
</SplitterPanel>
|
||||
|
||||
<!-- 主区面板:顶栏(含页签)独立在灰底上,下方一张白卡放内容 -->
|
||||
<!-- 主区面板:顶栏 + 页签 + 灰底内容区依次紧贴(贴边框架,vben 式)
|
||||
顶栏/页签各自带底边线与下一层分隔;内容区是灰底画布,页面在其上放白卡 -->
|
||||
<SplitterPanel class="fms-main-panel">
|
||||
<div class="fms-main-col">
|
||||
<AppTopbar />
|
||||
<!-- 顶栏:内容最大化时整条摘除(照 vben 的 header.hidden 语义) -->
|
||||
<AppTopbar v-if="!appStore.contentMaximized" />
|
||||
|
||||
<AppTabs />
|
||||
|
||||
<div class="fms-main">
|
||||
<div class="fms-content">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="page" mode="out-in">
|
||||
<!-- 稳定的包裹层:key 固定,保证 keep-alive 实例长期存活(缓存不随路由切换销毁)。
|
||||
页面缓存键由内层 <component :key="route.fullPath"> 驱动 -->
|
||||
<div key="page-wrap" class="fms-page-wrap">
|
||||
<!-- 稳定的包裹层:key 固定,保证 keep-alive 实例长期存活(缓存不随路由切换销毁)。
|
||||
页面缓存键由内层 <component :key="route.fullPath"> 驱动 -->
|
||||
<div key="page-wrap" class="fms-page-wrap">
|
||||
<!-- 路由切换过渡(照 vben 的 fade-slide):Transition 处于 KeepAlive 外侧,
|
||||
过渡目标由 KeepAlive 的 inner child(内层 component 的 key)驱动。
|
||||
mode="out-in" 串行(先退后进)—— 并行进出时两张白卡交叠滑动会晃眼 -->
|
||||
<Transition name="fade-slide" mode="out-in">
|
||||
<keep-alive :include="appStore.cachedViews" :max="8">
|
||||
<component
|
||||
:is="
|
||||
@@ -166,8 +184,8 @@ provide('toggleSidebar', toggleSidebar)
|
||||
:full-path="route.fullPath"
|
||||
/>
|
||||
</keep-alive>
|
||||
</div>
|
||||
</transition>
|
||||
</Transition>
|
||||
</div>
|
||||
</router-view>
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,16 +206,17 @@ provide('toggleSidebar', toggleSidebar)
|
||||
|
||||
/* ---- 布局骨架 ---- */
|
||||
|
||||
/* 侧栏与主区之间的灰缝宽度:由主区面板的左右 padding 产生(分隔条自身零宽,不占位)。
|
||||
* 因此分割线与命中区默认会落在灰缝左缘,需按「半个灰缝」右移才居中 ——
|
||||
* 这里把灰缝宽度收成一个变量,供下面几处共用,避免以后调 padding 时漏改。 */
|
||||
$layout-gutter: 8px;
|
||||
|
||||
/* 贴边布局(vben 式):侧栏与主区之间没有灰缝,分隔由侧栏自身的右边框承担;
|
||||
* Splitter 的分隔条零宽、不占位,仅保留 hover 命中区用于拖拽调宽 */
|
||||
.fms-layout {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
/* clip 而非 hidden:同样裁剪,但不会成为可程序化滚动的 scroll container ——
|
||||
* 布局容器一旦可滚,scrollIntoView 之类调用(如页签揭示)会连带滚动它,
|
||||
* 在布局瞬时溢出一两像素时带着整个界面平移一下,观感即「闪烁」。
|
||||
* flex 容器本身已是独立格式化上下文,不依赖 overflow 建立 BFC,行为等价 */
|
||||
overflow: clip;
|
||||
background: var(--fms-background);
|
||||
}
|
||||
|
||||
@@ -216,10 +235,21 @@ $layout-gutter: 8px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 非折叠态:分隔条悬浮命中区(width: 0 + margin: 0,不参与 flex 布局)。
|
||||
* 注意:原实现用负 margin(0 -3px)做悬浮命中区,但 flex 中负 margin 会把相邻面板
|
||||
* 拉近 6px,抵消主区面板 padding-left 8px → 侧栏白卡与右侧内容(顶栏/灰画布)只剩
|
||||
* 2px 灰缝,视觉上"紧贴"。命中区改由绝对定位的 ::after 扩展,布局不再受影响。 */
|
||||
/* 内容最大化:侧栏及其拖拽命中区一并隐藏(顶栏由模板 v-if 摘除),页签栏保留 ——
|
||||
* 最大化按钮在页签栏右端,是还原的唯一入口。
|
||||
* 用 display: none 而不是 v-if 摘面板:不动 Splitter 的面板树,
|
||||
* 避免其尺寸 / 拖拽计算受结构变化影响 */
|
||||
.fms-layout.is-maximized .fms-sidebar-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fms-layout.is-maximized .fms-layout-splitter :deep(> .fms-splitter-bar) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 非折叠态:分隔条零宽(不参与 flex 布局),命中区由绝对定位的 ::after 扩展。
|
||||
* 视觉线平时透明(bar-on-hover),悬停 / 拖拽时才显示一条深色提示线,
|
||||
* 松开后由侧栏右边框继续承担分隔职责 */
|
||||
.fms-layout .fms-layout-splitter :deep(> .fms-splitter-bar) {
|
||||
width: 0;
|
||||
margin: 0;
|
||||
@@ -227,20 +257,13 @@ $layout-gutter: 8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 视觉分割线:组件里用 left: 50% 把线居中于分隔条,而本处分隔条零宽、
|
||||
* 灰缝由主区面板 padding 产生,线会落在灰缝左缘 → 按半个灰缝右移,落在灰缝正中 */
|
||||
.fms-layout .fms-layout-splitter :deep(> .fms-splitter-bar)::before {
|
||||
left: calc($layout-gutter / 2);
|
||||
}
|
||||
|
||||
/* 命中区:绝对定位在灰缝正中(分隔条位置两侧各半个命中区宽度),不占 flex 空间,
|
||||
* 且能正常触发 bar 的 pointerdown 拖拽 */
|
||||
/* 命中区:以边界线为中心向两侧各展开半个命中区宽度 */
|
||||
.fms-layout .fms-layout-splitter :deep(> .fms-splitter-bar)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: calc($layout-gutter / 2 - var(--fms-splitter-trigger-size) / 2);
|
||||
left: calc(var(--fms-splitter-trigger-size) * -0.5);
|
||||
width: var(--fms-splitter-trigger-size);
|
||||
}
|
||||
|
||||
@@ -253,33 +276,31 @@ $layout-gutter: 8px;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
/* 主区面板:左右各留一个灰缝宽度的 padding(顶栏/内容区紧贴上边缘)。
|
||||
* 折叠态与展开态一致,避免侧栏折叠时与右侧紧贴 */
|
||||
/* 主区面板:无内边距,顶栏 / 页签 / 内容区依次紧贴(分隔线由各条自身的底边框承担) */
|
||||
.fms-main-panel {
|
||||
padding: 0 $layout-gutter;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 覆盖 Splitter 面板默认 overflow: auto(scoped 同名规则特异性更高,需带 .fms-layout 提权),
|
||||
* 让顶栏/卡片阴影与圆角完整绘制 */
|
||||
* 避免面板成为独立滚动容器(滚动交给内容区,顶栏 / 页签固定不动) */
|
||||
.fms-layout .fms-splitter-panel.fms-main-panel {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* 侧栏面板同样需要 overflow: visible:
|
||||
* 否则默认 overflow: auto 会把侧栏卡片的右侧投影(box-shadow 向右投射 4px)
|
||||
* 裁掉,侧栏与右侧内容之间失去可见分隔,看起来"紧贴" */
|
||||
/* 侧栏面板同样需要 overflow: visible:滚动交给侧栏内部的菜单区,
|
||||
* 面板自身不做滚动容器,避免出现第二根滚动条 */
|
||||
.fms-layout .fms-splitter-panel.fms-sidebar-panel {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* 主区纵向列:顶栏在灰底上独立成条,下方白卡与其留 8px 间距 */
|
||||
/* 主区纵向列:顶栏 / 页签 / 内容区三段紧贴,无间距 */
|
||||
.fms-main-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
gap: 8px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* 主区内容容器:灰底画布(--fms-background),不再是白卡。
|
||||
@@ -312,12 +333,12 @@ $layout-gutter: 8px;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
/* 上下 4px,左右 0 */
|
||||
padding: 4px 0;
|
||||
/* 画布内边距:页面在灰底上放白卡,四周统一留白 */
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/* 页面过渡外层包裹:稳定的 DOM 根,供 transition 的 out-in 模式直接管理,
|
||||
* 避免 keep-alive 直接作 Transition 子节点时在 patch 阶段访问 null.parentNode 报错。
|
||||
/* 页面包裹层:稳定的 DOM 根,keep-alive 与路由过渡(fade-slide)都承载于其中,
|
||||
* 路由切换不重建该节点,缓存实例因此长期存活。
|
||||
* flex:1 撑满内容区:低内容页面占满剩余高度,高内容页面自然增高由 .fms-content 滚动 */
|
||||
.fms-page-wrap {
|
||||
flex: 1;
|
||||
@@ -326,34 +347,57 @@ $layout-gutter: 8px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ---- 页面过渡 ---- */
|
||||
.page-enter-active {
|
||||
/* 页面外壳(由 getPageWrapper 的渲染函数创建):路由过渡的目标容器。
|
||||
* 单根兜底 —— 页面组件可能是多根 Fragment(如 Dashboard),非元素根无法被
|
||||
* <Transition> 动画化(警告且过渡失效、页面不显示);同时承接页面根的高度链
|
||||
* (flex 纵向列、吃满内容区)。渲染函数 vnode 不带 scoped 属性,故用 :global */
|
||||
:global(.fms-page-holder) {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 内容区缺省页(403 / 500)在页面外壳内同样吃满高度,而不是 FallbackPage 默认的
|
||||
* min-height: 100vh(那是给独立路由 404 与离线覆盖层用的,放内容区会撑出滚动) */
|
||||
:global(.fms-page-holder > .fms-fallback) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* ---- 路由切换过渡(照 vben 的 fade-slide,按 FMS 场景收敛)----
|
||||
* 新页面自左侧 30px 处淡入、旧页面向右 30px 淡出;横向位移由 .fms-content 的
|
||||
* overflow-x: hidden 裁掉,不产生横向滚动条。
|
||||
* 与 vben 的差异:用 mode="out-in" 串行(先退后进)——FMS 页面是灰底上的白卡,
|
||||
* 并行进出时两张白卡交叠滑动会晃眼;串行下同一时刻只有一个页面,观感干净。
|
||||
* 时长/缓动走动效 token:进场缓而稳(ease-out),退场干脆(ease-in,降一档),
|
||||
* prefers-reduced-motion 下自动归零。
|
||||
* 过渡类加在 .fms-page-holder(渲染函数 vnode,无 scoped 属性)上,故用 :global */
|
||||
:global(.fade-slide-enter-active) {
|
||||
transition:
|
||||
opacity 160ms ease-out,
|
||||
transform 160ms ease-out;
|
||||
opacity var(--fms-duration-slow) var(--fms-ease-out),
|
||||
transform var(--fms-duration-slow) var(--fms-ease-out);
|
||||
}
|
||||
|
||||
.page-leave-active {
|
||||
transition: opacity 80ms ease-in;
|
||||
:global(.fade-slide-leave-active) {
|
||||
transition:
|
||||
opacity var(--fms-duration-base) var(--fms-ease-in),
|
||||
transform var(--fms-duration-base) var(--fms-ease-in);
|
||||
}
|
||||
|
||||
.page-enter-from {
|
||||
:global(.fade-slide-enter-from) {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
|
||||
.page-leave-to {
|
||||
:global(.fade-slide-leave-to) {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fms-layout-splitter .fms-splitter-panel {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.page-enter-active,
|
||||
.page-leave-active {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -171,20 +171,18 @@ function toMenuItem(node) {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
/* 白色侧栏,紧贴左/上/下边缘(父面板无 padding、无圆角)。
|
||||
* 右侧投影用方向性 token(原硬编码值收编),与主区形成分隔;
|
||||
* 强度克制:2px 右偏移 + 6px 模糊 + 6% 透明度,紧贴右缘自然分隔,不用浓阴影(会显脏)。
|
||||
* 背景用 --fms-surface(比内容白卡沉一档):让框架退后,白卡成为视觉主体。
|
||||
* 深色模式下 --fms-surface 自动变深、--fms-shadow-right 自动加重,无需额外覆盖 */
|
||||
/* 贴边布局(vben 式):侧栏紧贴左/上/下边缘,右侧用 1px 边框与主区分隔。
|
||||
* 背景用 --fms-surface(框架面,与顶栏/页签条同为白),深色模式自动跟随 */
|
||||
background: var(--fms-surface);
|
||||
box-shadow: var(--fms-shadow-right);
|
||||
border-right: 1px solid var(--fms-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 团队切换(对齐 SidebarHeader p-2 + TeamSwitcher h-12) */
|
||||
/* 团队切换:区域总高 48px(4 + 40 + 4),与顶栏等高 ——
|
||||
* 贴边布局下两者相邻,等高才是一条完整的框架横带 */
|
||||
.fms-team {
|
||||
flex-shrink: 0;
|
||||
padding: 4px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.fms-team-trigger {
|
||||
@@ -192,7 +190,7 @@ function toMenuItem(node) {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
min-height: 40px;
|
||||
padding: 0 8px;
|
||||
border: none;
|
||||
border-radius: var(--fms-control-radius);
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
<script setup>
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { ChevronLeft, ChevronRight, X } from '@lucide/vue'
|
||||
import {
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
Fullscreen,
|
||||
LayoutGrid,
|
||||
Minimize2,
|
||||
RefreshCw,
|
||||
X,
|
||||
} from '@lucide/vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { Button, Dropdown } from '@/components/ui'
|
||||
import { Dropdown } from '@/components/ui'
|
||||
import { cacheKeyOf } from '../layoutUtils'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -97,6 +105,12 @@ function tabContextItems(tab) {
|
||||
].map((it) => ({ ...it, onClick: () => handleTabAction(it.key, tab.key) }))
|
||||
}
|
||||
|
||||
/** 「更多」菜单 = 当前激活页签的上下文菜单(照 vben:工具区 More 与页签右键菜单同源) */
|
||||
const activeTabContextItems = computed(() => {
|
||||
const tab = tabs.value.find((t) => t.key === activeKey.value)
|
||||
return tab ? tabContextItems(tab) : []
|
||||
})
|
||||
|
||||
// 批量操作后:若当前路由页签已被关闭,跳转到 fallback(右键项本身或工作台)
|
||||
function afterClose(fallbackKey) {
|
||||
if (tabs.value.some((t) => t.key === route.fullPath)) return
|
||||
@@ -130,10 +144,15 @@ function handleTabAction(action, key) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 滚动区末尾的样式留白(与 .fms-tabs-track::after 的 24px 一致,照 vben 的 pr-6)。
|
||||
* 它是纯占位、不是真实内容,但会计入 scrollWidth;判断"是否需要滚动"时必须把它
|
||||
* 排除,否则页签刚好接近铺满时会被误判为溢出 —— 左侧冒出一个变灰、不可点的滚动按钮 */
|
||||
const TRACK_END_SPACER = 24
|
||||
|
||||
function updateScrollState() {
|
||||
const track = trackRef.value
|
||||
if (!track) return
|
||||
const shouldOverflow = track.scrollWidth > track.clientWidth + 1
|
||||
const shouldOverflow = track.scrollWidth > track.clientWidth + TRACK_END_SPACER + 1
|
||||
overflowed.value = shouldOverflow
|
||||
if (!shouldOverflow) {
|
||||
track.scrollLeft = 0
|
||||
@@ -163,10 +182,18 @@ function revealActiveTab(behavior = 'smooth') {
|
||||
// 元素未挂载(路由过渡/布局重建时可能短暂脱离 DOM,parentNode 为 null)则跳过,
|
||||
// 避免 scrollIntoView 抛出未捕获异常向上冒泡成 Promise 拒绝。
|
||||
if (!activeElement || !activeElement.isConnected) return
|
||||
// 已在可视区内就不滚:每次切换都滚会让整条页签栏平滑移动一下,观感上像闪一下
|
||||
const trackRect = track.getBoundingClientRect()
|
||||
const activeRect = activeElement.getBoundingClientRect()
|
||||
if (activeRect.left >= trackRect.left && activeRect.right <= trackRect.right) return
|
||||
// 只滚页签轨道自身(scrollTo),不用 scrollIntoView —— 后者会连带滚动所有可滚动
|
||||
// 祖先(含 overflow: hidden 的布局容器,它们同样是可程序化滚动的 scroll container),
|
||||
// 布局恰好有一两像素溢出时会带着整个界面平移一下,观感即「闪烁」
|
||||
const offset = activeRect.left - trackRect.left - (trackRect.width - activeRect.width) / 2
|
||||
try {
|
||||
activeElement.scrollIntoView({ behavior, block: 'nearest', inline: 'center' })
|
||||
track.scrollTo({ left: Math.max(0, track.scrollLeft + offset), behavior })
|
||||
} catch {
|
||||
/* 元素正处于卸载/过渡中,忽略 */
|
||||
/* 环境不支持平滑滚动(如测试环境)时忽略 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,192 +213,364 @@ function refreshTab(key) {
|
||||
|
||||
<template>
|
||||
<div v-if="tabs.length" class="fms-tabs">
|
||||
<!-- 左侧滚动按钮 -->
|
||||
<Button
|
||||
v-if="overflowed"
|
||||
type="ghost"
|
||||
class="fms-tabs-arrow"
|
||||
:disabled="!canScrollLeft"
|
||||
<!-- 左侧滚动按钮(照 vben:贴边 chevron 区 + 右边框;到边界时变暗不可点) -->
|
||||
<span
|
||||
v-show="overflowed"
|
||||
class="fms-tabs-arrow fms-tabs-arrow--left"
|
||||
:class="{ 'is-disabled': !canScrollLeft }"
|
||||
aria-label="向左滚动页签"
|
||||
@click="scrollTabs(-1)"
|
||||
>
|
||||
<ChevronLeft />
|
||||
</Button>
|
||||
<ChevronsLeft />
|
||||
</span>
|
||||
|
||||
<!-- 页签轨道 -->
|
||||
<div
|
||||
ref="trackRef"
|
||||
class="fms-tabs-track"
|
||||
role="tablist"
|
||||
@scroll.passive="updateScrollState"
|
||||
>
|
||||
<Dropdown
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
trigger="contextmenu"
|
||||
placement="bottom-start"
|
||||
:items="tabContextItems(tab)"
|
||||
>
|
||||
<button
|
||||
:data-tab-key="tab.key"
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="tab.key === activeKey"
|
||||
class="fms-tab"
|
||||
:class="{ 'is-active': tab.key === activeKey, 'is-closable': tab.closable }"
|
||||
@click="changeTab(tab.key)"
|
||||
@dblclick.prevent="refreshTab(tab.key)"
|
||||
<div ref="trackRef" class="fms-tabs-track" role="tablist" @scroll.passive="updateScrollState">
|
||||
<TransitionGroup name="slide-left">
|
||||
<!-- 页签槽位:TransitionGroup 的直接子必须是元素 —— Dropdown(右键菜单)的根是
|
||||
Fragment(触发器 + Teleport 浮层),直接作过渡子节点会因「非元素根」动画失效并
|
||||
报 Vue 警告;槽位承接 flex item 角色(高度 / 负边距),按钮与菜单保持在内层 -->
|
||||
<div
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="tab.key"
|
||||
class="fms-tab-item"
|
||||
:class="{ 'is-active': tab.key === activeKey }"
|
||||
>
|
||||
<span class="fms-tab-label">{{ tab.label }}</span>
|
||||
<span
|
||||
v-if="tab.closable"
|
||||
class="fms-tab-close"
|
||||
aria-label="关闭页签"
|
||||
@click.stop="closeTab(tab.key)"
|
||||
>
|
||||
<X />
|
||||
</span>
|
||||
</button>
|
||||
</Dropdown>
|
||||
<Dropdown trigger="contextmenu" placement="bottom-start" :items="tabContextItems(tab)">
|
||||
<button
|
||||
:data-tab-key="tab.key"
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="tab.key === activeKey"
|
||||
class="fms-tab"
|
||||
:class="{ 'is-active': tab.key === activeKey, 'is-unclosable': !tab.closable }"
|
||||
@click="changeTab(tab.key)"
|
||||
@dblclick.prevent="refreshTab(tab.key)"
|
||||
>
|
||||
<span class="fms-tab__inner">
|
||||
<!-- 分隔线:非首个、非激活(与 vben 的 chrome 标签一致) -->
|
||||
<span v-if="index !== 0 && tab.key !== activeKey" class="fms-tab__divider" />
|
||||
|
||||
<!-- 背景层:标签块 + 底部两侧外弧(凹角补块),几何照 vben -->
|
||||
<span class="fms-tab__background">
|
||||
<span class="fms-tab__background-content" />
|
||||
<svg
|
||||
class="fms-tab__corner fms-tab__corner--left"
|
||||
height="7"
|
||||
width="7"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z" />
|
||||
</svg>
|
||||
<svg
|
||||
class="fms-tab__corner fms-tab__corner--right"
|
||||
height="7"
|
||||
width="7"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z" />
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<span v-if="tab.closable" class="fms-tab__extra">
|
||||
<span
|
||||
class="fms-tab__close"
|
||||
aria-label="关闭页签"
|
||||
@click.stop="closeTab(tab.key)"
|
||||
>
|
||||
<X />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<!-- 主内容(决定页签宽度) -->
|
||||
<span class="fms-tab__main">
|
||||
<span class="fms-tab__label">{{ tab.label }}</span>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
<!-- 右侧滚动按钮 -->
|
||||
<Button
|
||||
v-if="overflowed"
|
||||
type="ghost"
|
||||
class="fms-tabs-arrow"
|
||||
:disabled="!canScrollRight"
|
||||
<span
|
||||
v-show="overflowed"
|
||||
class="fms-tabs-arrow fms-tabs-arrow--right"
|
||||
:class="{ 'is-disabled': !canScrollRight }"
|
||||
aria-label="向右滚动页签"
|
||||
@click="scrollTabs(1)"
|
||||
>
|
||||
<ChevronRight />
|
||||
</Button>
|
||||
<ChevronsRight />
|
||||
</span>
|
||||
|
||||
<!-- 右侧工具区(照 vben 的 TabsTool*):更多 / 刷新当前页 / 内容最大化。
|
||||
三个贴边按钮各自带左分隔线;最大化按钮是进出内容最大化的唯一开关 -->
|
||||
<div class="fms-tabs-tools">
|
||||
<Dropdown trigger="click" placement="bottom-end" :items="activeTabContextItems">
|
||||
<span class="fms-tabs-tool" role="button" aria-label="页签操作" title="页签操作">
|
||||
<LayoutGrid />
|
||||
</span>
|
||||
</Dropdown>
|
||||
|
||||
<span
|
||||
class="fms-tabs-tool"
|
||||
role="button"
|
||||
aria-label="刷新当前页"
|
||||
title="刷新当前页"
|
||||
@click="refreshTab(activeKey)"
|
||||
>
|
||||
<RefreshCw />
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="fms-tabs-tool"
|
||||
role="button"
|
||||
:aria-label="appStore.contentMaximized ? '退出最大化' : '内容最大化'"
|
||||
:title="appStore.contentMaximized ? '退出最大化' : '内容最大化'"
|
||||
@click="appStore.toggleContentMaximize()"
|
||||
>
|
||||
<Minimize2 v-if="appStore.contentMaximized" />
|
||||
<Fullscreen v-else />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* ---- 页签栏(默认风格):白卡内工具条,底部一条细分隔线(整卡唯一分隔) ---- */
|
||||
/* ---- 页签栏:独立一行(顶栏之下、内容区之上),白底 + 底边线与灰底画布分隔。
|
||||
* 页签为 chrome 风格,结构与几何照 vben 的 tabs-chrome 移植(7px 外弧 / 6px 背景内缩 /
|
||||
* 14px 文字外边距 / 12px 相邻重叠),颜色与字号换 FMS 的 token ---- */
|
||||
.fms-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
flex-shrink: 0;
|
||||
gap: 6px;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
height: 38px;
|
||||
/* 照 vben:tabbar 自身无内边距;3px 顶部留白在滚动区上(见 .fms-tabs-track) */
|
||||
padding: 0;
|
||||
/* clip 而非 hidden:同样裁剪,但不会成为可程序化滚动的 scroll container,
|
||||
* 杜绝 scrollIntoView 之类调用意外滚动本容器的可能(flex 容器无需 BFC,行为等价) */
|
||||
overflow: clip;
|
||||
background: var(--fms-surface);
|
||||
border-bottom: 1px solid var(--fms-border);
|
||||
}
|
||||
|
||||
/* 滚动按钮(照 vben 的 TabsView 箭头区):横向 px-2 的贴边条,
|
||||
* 高度撑满 tabbar;hover 浅灰底,到边界时 30% 透明且不可点 */
|
||||
.fms-tabs-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
padding: 0 8px;
|
||||
color: var(--fms-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: background-color 150ms ease;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
&:hover {
|
||||
background: var(--fms-secondary-hover);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
&.is-disabled {
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fms-tabs-arrow--left {
|
||||
border-right: 1px solid var(--fms-border);
|
||||
}
|
||||
|
||||
.fms-tabs-arrow--right {
|
||||
border-left: 1px solid var(--fms-border);
|
||||
}
|
||||
|
||||
/* ---- 右侧工具区(照 vben 的 TabsTool*):更多 / 刷新 / 内容最大化 ----
|
||||
* 三个贴边按钮(16px 图标 + 左右 8px 内边距),各自带左分隔线,撑满页签栏高度;
|
||||
* hover 浅灰底并加深图标色(对应 vben 的 border-l + px-2 + hover:bg-muted) */
|
||||
.fms-tabs-tools {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fms-tabs-tool {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
padding: 0 8px;
|
||||
border-left: 1px solid var(--fms-border);
|
||||
color: var(--fms-text-secondary);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 150ms ease,
|
||||
color 150ms ease;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--fms-secondary-hover);
|
||||
color: var(--fms-text);
|
||||
}
|
||||
}
|
||||
|
||||
.fms-tabs-track {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
align-items: stretch;
|
||||
min-width: 0;
|
||||
/* 照 vben:标签区顶部留 3px(pt-0.75),chrome 标签从留白下沿延伸到底边线 */
|
||||
padding-top: 3px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none;
|
||||
|
||||
/* 照 vben 内容容器的 pr-6:末尾留 24px,滚动到最右时最后一个标签不贴边 */
|
||||
&::after {
|
||||
content: '';
|
||||
flex: none;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 页签槽位:TransitionGroup 的直接子必须是元素 —— Dropdown(右键菜单)的根是 Fragment
|
||||
* (触发器 + Teleport 浮层),直接作过渡子节点动画会失效并报 Vue 警告。
|
||||
* 槽位承接 flex item 角色:撑满轨道高度 + -12px 负右边距(相邻标签重叠,
|
||||
* 实现「标签块连排、文字有间隔」的 chrome 布局) */
|
||||
.fms-tab-item {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
margin-right: -12px;
|
||||
}
|
||||
|
||||
/* 页签按钮:交互与 chrome 几何的载体(宽度由内容决定) */
|
||||
.fms-tab {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: var(--fms-control-radius);
|
||||
background: var(--fms-card);
|
||||
color: var(--fms-text-secondary);
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition:
|
||||
background 150ms ease,
|
||||
border-color 150ms ease,
|
||||
color 150ms ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--fms-text-secondary);
|
||||
color: var(--fms-text);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--fms-primary);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: var(--fms-primary);
|
||||
border-color: var(--fms-primary);
|
||||
color: var(--fms-primary-text);
|
||||
}
|
||||
|
||||
&.is-active:hover {
|
||||
background: var(--fms-primary);
|
||||
border-color: var(--fms-primary);
|
||||
color: var(--fms-primary-text);
|
||||
}
|
||||
|
||||
&.is-active .fms-tab-close {
|
||||
color: color-mix(in srgb, var(--fms-primary-text) 85%, transparent);
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--fms-primary-text) 22%, transparent);
|
||||
color: var(--fms-primary-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fms-tab-label {
|
||||
max-width: 144px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
/* 激活项提升层级:激活标签的背景块与外弧要压在相邻标签之上 */
|
||||
.fms-tab.is-active {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.fms-tab-close {
|
||||
/* 内层容器:承载绝对定位的背景层与文字区(对应 vben 的 px-1 容器) */
|
||||
.fms-tab__inner {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
/* 分隔线:非首个、非激活的页签左侧一条 16px 竖线 */
|
||||
.fms-tab__divider {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 7px;
|
||||
z-index: 1;
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
transform: translateY(-50%);
|
||||
background: var(--fms-border);
|
||||
transition: opacity 150ms ease;
|
||||
}
|
||||
|
||||
/* 背景层:标签块 + 底部两侧外弧;水平内缩 6px */
|
||||
.fms-tab__background {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
inset: 0;
|
||||
padding: 0 6px;
|
||||
transition: padding-bottom 150ms ease;
|
||||
}
|
||||
|
||||
.fms-tab__background-content {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 7px 7px 0 0;
|
||||
/* 背景色刻意不走过渡(选中/悬停瞬时切换):与侧栏菜单同理,color-mix
|
||||
* 透明底与 transparent 之间的插值有「先亮再灭」的观感(见 NavMenuItem) */
|
||||
transition:
|
||||
margin 150ms ease,
|
||||
border-radius 150ms ease;
|
||||
}
|
||||
|
||||
/* 外弧(凹角补块):位于标签块底部两侧、与底边线平滑衔接;fill 随状态切换 */
|
||||
.fms-tab__corner {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
fill: transparent;
|
||||
/* fill 与背景块同色系,同样瞬时切换(见 .fms-tab__background-content) */
|
||||
}
|
||||
|
||||
.fms-tab__corner--left {
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.fms-tab__corner--right {
|
||||
right: -1px;
|
||||
}
|
||||
|
||||
/* 关闭按钮容器:16px 框 + 内部 12px 图标,图标靠左、垂直居中(对应 vben 的 size-4 框 +
|
||||
* size-3 图标,其 mt-0.5 只是块级流下让 12px 图标在 16px 框内居中的补偿)。
|
||||
* 定位:vben 原值为 right: 7px(X 距标签右缘 11px);FMS 页签是纯文字、普遍比 vben 窄,
|
||||
* 该值观感偏贴边,故往左收 3px —— X 距右缘 14px、距文字 8px,两侧更均衡 */
|
||||
.fms-tab__extra {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 10px;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.fms-tab__close {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: -4px;
|
||||
border-radius: 4px;
|
||||
color: var(--fms-text-secondary);
|
||||
/* 12px 圆底(vben 的 size-3):hover 圆形反馈与图标同尺寸 */
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 999px;
|
||||
color: color-mix(in srgb, var(--fms-text) 80%, transparent);
|
||||
transition:
|
||||
background 150ms ease,
|
||||
background-color 150ms ease,
|
||||
color 150ms ease;
|
||||
|
||||
svg {
|
||||
@@ -380,8 +579,118 @@ function refreshTab(key) {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--fms-text-secondary) 20%, transparent);
|
||||
background: var(--fms-secondary-hover);
|
||||
color: var(--fms-text);
|
||||
}
|
||||
}
|
||||
|
||||
/* 主内容:决定页签宽度(左右 14px 外边距 + 右侧 16px 内边距给关闭按钮留位) */
|
||||
.fms-tab__main {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
margin: 0 14px;
|
||||
padding: 0 16px 0 8px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px 5px 0 0;
|
||||
color: var(--fms-text);
|
||||
/* 照 vben 的 text-sm(14px) */
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
transition: color 150ms ease;
|
||||
}
|
||||
|
||||
/* 不可关闭的页签(工作台):没有关闭按钮,右侧不必预留 16px ——
|
||||
* 左右对称 8px,文字在标签内居中 */
|
||||
.fms-tab.is-unclosable .fms-tab__main {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
/* 照 vben:flex-1 + 溢出隐藏,不设宽度上限(长标题标签自然变宽,依靠滚动区)。
|
||||
* transform 为字形垂直微调:FMS 字体栈(Noto Sans SC / PingFang SC / Microsoft YaHei)
|
||||
* 的中文字形墨迹中心比 20px 行框几何中心低 1px(实测墨迹占 4~18、中心 11),
|
||||
* 行框几何居中的文字看起来整体靠下、与几何居中的关闭图标不在一条线上;
|
||||
* 上移 1px 让字形视觉居中,和关闭图标对齐(vben 的西文优先字体栈墨迹仅偏
|
||||
* 0.5px,几乎不可见,所以它不需要这层补偿) */
|
||||
.fms-tab__label {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* ---- 状态:激活 / 悬停(照 vben 的 group-[.is-active] 与 hover 规则,颜色换 FMS token) ---- */
|
||||
|
||||
/* 激活:浅主色标签块 + 主色文字,底部外弧与标签块同色。
|
||||
* 底色用系统的主色淡底 token(--fms-primary-soft),深色模式会自动提高浓度 */
|
||||
.fms-tab.is-active {
|
||||
.fms-tab__background-content {
|
||||
background: var(--fms-primary-soft);
|
||||
}
|
||||
|
||||
.fms-tab__corner {
|
||||
fill: var(--fms-primary-soft);
|
||||
}
|
||||
|
||||
.fms-tab__main {
|
||||
color: var(--fms-primary);
|
||||
}
|
||||
|
||||
/* 照 vben:激活时关闭按钮颜色加深(/80 → 全额) */
|
||||
.fms-tab__close {
|
||||
color: var(--fms-text);
|
||||
}
|
||||
}
|
||||
|
||||
/* 悬停(非激活):灰色浮块——底部留 2px、左右缩 2px、四角圆角 */
|
||||
.fms-tab:hover:not(.is-active) {
|
||||
.fms-tab__background {
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.fms-tab__background-content {
|
||||
margin: 0 2px;
|
||||
border-radius: 6px;
|
||||
background: var(--fms-secondary-hover);
|
||||
}
|
||||
|
||||
.fms-tab__divider {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 悬停项右侧相邻页签的分隔线一并隐藏(避免竖线贴在被悬停标签边缘);激活项右侧同理。
|
||||
* 相邻关系落在槽位层(.fms-tab-item):按钮的兄弟节点是 Dropdown 的浮层 */
|
||||
.fms-tab-item:hover:not(.is-active) + .fms-tab-item .fms-tab__divider,
|
||||
.fms-tab-item.is-active + .fms-tab-item .fms-tab__divider {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ---- 页签增删 / 移动过渡(照 vben 的 slide-left 过渡)----
|
||||
* 入场只做位移、不做透明度渐变:新增页签往往就是「刚激活的页签」,若透明度从 0
|
||||
* 渐入,选中高亮会跟着闪一下(观感是选中态闪烁);位移与时长也收窄,让新页签
|
||||
* 「轻轻推入」。退场保留淡出(关闭页签柔和消失)*/
|
||||
.slide-left-enter-active {
|
||||
transition: transform 0.18s cubic-bezier(0.25, 0.8, 0.5, 1);
|
||||
}
|
||||
|
||||
.slide-left-leave-active {
|
||||
transition: 0.15s cubic-bezier(0.25, 0.8, 0.5, 1);
|
||||
}
|
||||
|
||||
.slide-left-move {
|
||||
transition: transform 0.2s cubic-bezier(0.25, 0.8, 0.5, 1);
|
||||
}
|
||||
|
||||
.slide-left-enter-from {
|
||||
transform: translateX(-8px);
|
||||
}
|
||||
|
||||
.slide-left-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(-8px);
|
||||
}
|
||||
</style>
|
||||
@@ -21,7 +21,6 @@ import { useI18nStore } from '@/stores/i18n'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { Message, Dropdown, Button } from '@/components/ui'
|
||||
import { PRESETS } from '@/theme/presets'
|
||||
import AppTabs from './AppTabs.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
@@ -133,11 +132,6 @@ onBeforeUnmount(() => document.removeEventListener('fullscreenchange', syncFulls
|
||||
<span class="fms-separator" aria-hidden="true" />
|
||||
</div>
|
||||
|
||||
<!-- 中间:多页签(与顶栏合成一条,不再单独一条) -->
|
||||
<div class="fms-topbar-tabs">
|
||||
<AppTabs />
|
||||
</div>
|
||||
|
||||
<!-- 右侧:语言 / 主题色板 / 深浅色 / 通知 / 用户 -->
|
||||
<div class="fms-topbar-right">
|
||||
<Dropdown
|
||||
@@ -238,14 +232,13 @@ onBeforeUnmount(() => document.removeEventListener('fullscreenchange', syncFulls
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
gap: 8px;
|
||||
height: 40px;
|
||||
/* 高度与侧栏顶部团队区对齐(贴边布局下两者相邻,等高才是一条完整的框架横带) */
|
||||
height: 48px;
|
||||
padding: 0 12px;
|
||||
/* 顶栏紧贴上边缘(主区面板上下无 padding);左下+右下圆角。
|
||||
* 向下投影复用框架 token(原硬编码 0 2px 4px 收编),配合底圆角让白卡从灰画布上
|
||||
* 微微浮起,与侧栏的"右侧投影"(--fms-shadow-right)角色互补、色相一致。
|
||||
* 背景用 --fms-surface(与侧栏同为框架面,比内容白卡沉一档) */
|
||||
/* 贴边布局(vben 式):顶栏贴主区顶部,白底 + 底边框与页签条分隔。
|
||||
* 背景用 --fms-surface(与侧栏同为框架面);分隔不用阴影,由 1px 边框承担 */
|
||||
background: var(--fms-surface);
|
||||
border-radius: 0 0 var(--fms-control-radius) var(--fms-control-radius);
|
||||
border-bottom: 1px solid var(--fms-border);
|
||||
}
|
||||
|
||||
.fms-topbar-left,
|
||||
@@ -263,22 +256,6 @@ onBeforeUnmount(() => document.removeEventListener('fullscreenchange', syncFulls
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 中间页签区:填入剩余宽度,高度撑满顶栏 */
|
||||
.fms-topbar-tabs {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* AppTabs 嵌入顶栏时:填满容器高度,去掉独立底边线和外侧 padding */
|
||||
.fms-topbar-tabs :deep(.fms-tabs) {
|
||||
height: 100%;
|
||||
padding: 0 4px;
|
||||
border-bottom: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 顶栏图标按钮:复用组件库 Button type="ghost"。
|
||||
透明底 / 圆角 / 字号 / 行高 / 光标 / 纵向布局 / 焦点环都已在 .button、.button-ghost 里,
|
||||
这里只保留顶栏自身的差异:32×32 方形、无横向内边距、主文字色(ghost 默认次要色)、
|
||||
|
||||
@@ -122,10 +122,10 @@ function onToggle() {
|
||||
line-height: 20px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 150ms ease,
|
||||
color 150ms ease,
|
||||
box-shadow 150ms ease;
|
||||
/* 背景刻意不走过渡(hover / 选中瞬时切换):选中态的类翻转序列已验证干净
|
||||
* (nav-active-sequence.spec),闪烁来自 color-mix 透明底与 transparent 之间
|
||||
* 颜色插值的视觉(切换帧上观感为「先亮再灭」),消除过渡即消除该观感 */
|
||||
transition: color 150ms ease;
|
||||
}
|
||||
|
||||
/* 叶子链接 hover:仅自身高亮 */
|
||||
@@ -235,11 +235,12 @@ function onToggle() {
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
/* 激活高亮:主色淡底 + 主色文字(与树选中态一致) */
|
||||
/* 激活高亮:主色淡底 + 主色文字(与树选中态一致)。
|
||||
* 刻意不加 font-weight:激活瞬间的字重变化会让文字重排(观感像闪一下),
|
||||
* 背景与颜色已足以表达选中 */
|
||||
.fms-nav-link.is-active {
|
||||
background: color-mix(in srgb, var(--fms-primary) 10%, transparent);
|
||||
color: var(--fms-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ---- 折叠态:仅图标,居中展示(对齐 size-8 icon 模式) ---- */
|
||||
|
||||
@@ -20,7 +20,7 @@ const router = createRouter({
|
||||
})
|
||||
|
||||
// 登录守卫:未登录访问受保护页跳 /login?redirect=xxx;已登录访问 /login 跳 /dashboard
|
||||
// 权限守卫:路由配置 meta.moduleId 时校验访问权限,无权限跳 /dashboard
|
||||
// 权限守卫:路由配置 meta.moduleId 时校验访问权限,无权限跳 /403 缺省页
|
||||
router.beforeEach(async (to) => {
|
||||
NProgress.start()
|
||||
const authStore = useAuthStore()
|
||||
@@ -50,21 +50,21 @@ router.beforeEach(async (to) => {
|
||||
|
||||
const moduleId = to.meta.moduleId
|
||||
if (moduleId && !permissionStore.canAccess(moduleId)) {
|
||||
return '/dashboard'
|
||||
return '/403'
|
||||
}
|
||||
|
||||
// 菜单入口:路由命中 s_menu 里的页面菜单时,必须有该菜单的 menu.* 授权。
|
||||
// 侧栏已按授权裁剪(stores/menus),这里兜住「手输地址直接访问」。
|
||||
const menu = menuStore.findMenuByRoute(to.path)
|
||||
if (menu && !permissionStore.canEnterMenu(menu.b_id)) {
|
||||
return '/dashboard'
|
||||
return '/403'
|
||||
}
|
||||
|
||||
// 系统管理员专用页面(meta.adminOnly,当前为模块中心 /module/*):
|
||||
// 非 g3soft 一律拦回工作台。按路由标记而非路径前缀判断,避免「菜单上看不见、
|
||||
// 非 g3soft 一律拦到 403 页。按路由标记而非路径前缀判断,避免「菜单上看不见、
|
||||
// 手输地址却能进」的越权;侧栏入口同样仅对 g3soft 渲染(AppSidebar)。
|
||||
if (to.matched.some((r) => r.meta.adminOnly) && !permissionStore.isG3soft) {
|
||||
return '/dashboard'
|
||||
return '/403'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -22,8 +22,8 @@ import DefaultLayout from '@/layouts/DefaultLayout.vue'
|
||||
* 切勿给分组节点配「内部再放一个 router-view」的容器组件:那样页面会下沉一层,
|
||||
* 直接脱离 DefaultLayout 的 keep-alive,页面缓存失效。
|
||||
* 分组节点不配 redirect:分组只是归类,不该自带「默认落地页」语义 —— 直接访问 /base、/module
|
||||
* 这类裸前缀时交给表尾的 /:pathMatch(.*)* 兜到工作台,避免出现「菜单上没配、却能落在某个页面」
|
||||
* 的第二入口(业务入口一律由 s_menu + 路由地址维护)。
|
||||
* 这类裸前缀时交给表尾的 /:pathMatch(.*)* 渲染 404 缺省页,避免出现「菜单上没配、却能落在某个
|
||||
* 页面」的第二入口(业务入口一律由 s_menu + 路由地址维护)。
|
||||
* 子路由 path 写相对路径('module-management'),完整路径由父级拼接,
|
||||
* 调整分组前缀时只需改父节点一处。
|
||||
*/
|
||||
@@ -213,16 +213,45 @@ export const routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
// 缺省页(应用内展示:保留侧栏 / 顶栏 / 页签,用户可直接切走或用侧栏导航):
|
||||
// 403 由守卫跳转到达;500 供直接访问排查(渲染崩溃的兜底由 App 层
|
||||
// ErrorBoundary 全屏渲染,不经过本路由)。
|
||||
// componentName 不可省:布局的包装层按 name 缓存命名组件,无名页面会互相顶掉;
|
||||
// 不设 meta.title:AppTabs 按 title 收录页签,缺省页不该出现在页签栏。
|
||||
{
|
||||
path: '/403',
|
||||
name: 'fallback-403',
|
||||
component: () => import('@/views/fallback/Forbidden.vue'),
|
||||
meta: { componentName: 'Fallback403' },
|
||||
},
|
||||
{
|
||||
path: '/500',
|
||||
name: 'fallback-500',
|
||||
component: () => import('@/views/fallback/ServerError.vue'),
|
||||
meta: { componentName: 'Fallback500' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// 404 兜住所有未知路径:独立布局(不挂 DefaultLayout)—— 未知路径与登录态无关,
|
||||
// 未登录也要能看到;保留原地址不重定向,用户能看到自己输错的是什么。
|
||||
// 不设 meta.title:AppTabs 按 title 收录页签,缺省页不该出现在页签栏。
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
redirect: '/dashboard',
|
||||
name: 'fallback-404',
|
||||
component: () => import('@/views/fallback/FallbackPage.vue'),
|
||||
props: { status: '404' },
|
||||
},
|
||||
]
|
||||
|
||||
/** 不作为菜单入口的路由:独立布局页(/login)与详情页(静态路径,无法靠动态段规则排除) */
|
||||
const EXCLUDED_PATHS = new Set(['/login', '/base/othercompany/detail', '/base/contact/detail'])
|
||||
/** 不作为菜单入口的路由:独立布局页(/login 与缺省页 /403 /500)与详情页(静态路径,
|
||||
* 无法靠动态段规则排除)。catch-all 缺省路由含动态段,已被 collectMenuRoutes 的规则滤除 */
|
||||
const EXCLUDED_PATHS = new Set([
|
||||
'/login',
|
||||
'/403',
|
||||
'/500',
|
||||
'/base/othercompany/detail',
|
||||
'/base/contact/detail',
|
||||
])
|
||||
|
||||
/**
|
||||
* 收集可作为菜单入口的页面路由(递归展开布局容器与分组节点)。
|
||||
|
||||
@@ -56,7 +56,7 @@ function saveBlob(blob, fileName) {
|
||||
* @param orderBy 排序子句;缺省用模块默认排序
|
||||
* @param rowKeys 勾选行主键值;非空时只导出这些行
|
||||
* @param keyField 勾选行主键列;缺省用模块默认主键
|
||||
* @param columns 勾选导出的字段;省略表示沿用列表全部列(批量导出用)
|
||||
* @param columns 勾选导出的字段;省略表示沿用列表全部列(自定义导出用)
|
||||
* @param rangeStart 按条数区间起始(1 基,闭区间);与 rangeEnd 同时给出才生效
|
||||
* @param rangeEnd 按条数区间结束
|
||||
* @param volumeSize 分卷行数:> 0 时返回 zip(每卷一个 xlsx),与区间互斥
|
||||
@@ -104,7 +104,7 @@ export async function exportListApi({
|
||||
}
|
||||
|
||||
/**
|
||||
* 符合条件的行数(批量导出弹窗的「共 N 条」)。
|
||||
* 符合条件的行数(自定义导出弹窗的「共 N 条」)。
|
||||
* 与导出走同一条后端权限链路,所以数字与真正导出的行数一致 —— 不能用列表分页的 total 代替。
|
||||
* 返回 ApiResponse,调用方取 `res.data.total`。
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,8 @@ export const useAppStore = defineStore(
|
||||
const primaryColor = ref(DEFAULT_PRIMARY)
|
||||
/** 侧边栏是否折叠为图标栏 */
|
||||
const sidebarCollapsed = ref(false)
|
||||
/** 内容区是否最大化(隐藏侧栏与顶栏;页签栏保留,还原入口在其中) */
|
||||
const contentMaximized = ref(false)
|
||||
/** 已访问页签列表;工作台固定不可关闭 */
|
||||
const visitedTabs = ref([
|
||||
{ key: '/dashboard', label: '工作台', closable: false, name: 'Dashboard' },
|
||||
@@ -38,6 +40,10 @@ export const useAppStore = defineStore(
|
||||
sidebarCollapsed.value = !sidebarCollapsed.value
|
||||
}
|
||||
|
||||
function toggleContentMaximize() {
|
||||
contentMaximized.value = !contentMaximized.value
|
||||
}
|
||||
|
||||
function setSidebar(collapsed) {
|
||||
sidebarCollapsed.value = !!collapsed
|
||||
}
|
||||
@@ -106,6 +112,7 @@ export const useAppStore = defineStore(
|
||||
darkMode,
|
||||
primaryColor,
|
||||
sidebarCollapsed,
|
||||
contentMaximized,
|
||||
visitedTabs,
|
||||
routeRefreshKeys,
|
||||
cachedViews,
|
||||
@@ -114,6 +121,7 @@ export const useAppStore = defineStore(
|
||||
setPrimary,
|
||||
toggleSidebar,
|
||||
setSidebar,
|
||||
toggleContentMaximize,
|
||||
addVisitedTab,
|
||||
removeVisitedTab,
|
||||
closeOthers,
|
||||
@@ -126,7 +134,7 @@ export const useAppStore = defineStore(
|
||||
}
|
||||
},
|
||||
{
|
||||
// 仅持久化主题;侧栏、页签和路由刷新计数只在当前会话有效。
|
||||
// 仅持久化主题;侧栏、内容最大化、页签和路由刷新计数只在当前会话有效。
|
||||
persist: {
|
||||
pick: ['darkMode', 'primaryColor'],
|
||||
},
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
:root {
|
||||
/* 中性色:背景 / 卡片 / 文字 / 次级 / 边框
|
||||
* 层次靠"面"不靠"线":画布(#eef1f6) 与卡片(#fff) 保持 ~7% 亮度差,
|
||||
* 层次靠"面"不靠"线":画布(#f1f3f6) 与卡片(#fff) 保持 ~7% 亮度差,
|
||||
* 白卡无需描边即可被托起;画布带轻微冷蓝倾向,比中性灰更稳、更耐看。 */
|
||||
--fms-background: #eef1f6;
|
||||
--fms-background: #f1f3f6;
|
||||
--fms-card: #ffffff;
|
||||
/* 框架面(侧栏 / 顶栏等大面积容器):与内容白卡同为纯白。
|
||||
* 产品决定框架面不再"沉一档",侧栏/顶栏与内容卡统一纯白;层次由阴影与分隔线承担。
|
||||
@@ -19,7 +19,9 @@
|
||||
--fms-secondary-hover: #f1f5f9;
|
||||
/* 次要文字:由 #86909c 加深,白底对比 ~2.8:1 → ~4.5:1,长时间看表格不费力 */
|
||||
--fms-text-secondary: #6f7784;
|
||||
--fms-border: #e3e8ee;
|
||||
/* 边框:亮度对齐 vben/shadcn 的 --border(HSL 90%,1px 细线在白底上的对比度一致),
|
||||
* 色相保留冷蓝(212°)以配合冷蓝画布;比原 #e3e8ee 深 1.2 个百分点 */
|
||||
--fms-border: #dfe5ec;
|
||||
/* 软分隔线:比 --fms-border 更浅,用于表格行线、面板内分隔等"需要分隔但不想抢戏"的地方。
|
||||
* 之前各页面用 var(--fms-border-soft, #f0f1f3) 引用但本文件未定义(全落 fallback),正式补齐。 */
|
||||
--fms-border-soft: #ebedf2;
|
||||
@@ -52,7 +54,7 @@
|
||||
--fms-line-height: 1.4;
|
||||
|
||||
/* 控件 */
|
||||
--fms-control-height: 26px;
|
||||
--fms-control-height: 28px;
|
||||
--fms-control-padding-x: 8px;
|
||||
--fms-control-radius: 6px;
|
||||
--fms-control-size: 16px;
|
||||
|
||||
@@ -109,7 +109,7 @@ function confirmBatchDelete() {
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canDelete"
|
||||
type="danger"
|
||||
type="danger-outline"
|
||||
:disabled="loading || rowSaving || !selectedRowKeys.length"
|
||||
@click="confirmBatchDelete"
|
||||
>
|
||||
|
||||
@@ -490,7 +490,7 @@ void loadCategories()
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canDeleteRow"
|
||||
type="danger"
|
||||
type="danger-outline"
|
||||
:disabled="loading || !selectedRowKeys.length"
|
||||
@click="confirmBatchDelete"
|
||||
>
|
||||
|
||||
@@ -317,7 +317,7 @@ void loadCategories()
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canDeleteRow"
|
||||
type="danger"
|
||||
type="danger-outline"
|
||||
:disabled="loading || editableSaving || !selectedRowKeys.length"
|
||||
@click="confirmBatchDelete"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
import { onErrorCaptured, ref } from 'vue'
|
||||
import FallbackPage from './FallbackPage.vue'
|
||||
|
||||
/**
|
||||
* 渲染错误边界:捕获子树(布局 + 页面)的未处理错误,就地渲染 500 缺省页,替代白屏。
|
||||
* 500 页的「重新加载」按钮即恢复手段(整页刷新重置一切状态)。
|
||||
*/
|
||||
const hasError = ref(false)
|
||||
|
||||
onErrorCaptured((error) => {
|
||||
hasError.value = true
|
||||
// 已处理:阻止继续冒泡到 app.config.errorHandler,但保留控制台输出便于排查
|
||||
console.error('[ErrorBoundary]', error)
|
||||
return false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FallbackPage v-if="hasError" status="500" />
|
||||
<div v-else>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,126 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ArrowLeft, RefreshCw } from '@lucide/vue'
|
||||
import { Button } from '@/components/ui'
|
||||
import { translate } from '@/stores/i18n'
|
||||
import Icon403 from './icons/Icon403.vue'
|
||||
import Icon404 from './icons/Icon404.vue'
|
||||
import Icon500 from './icons/Icon500.vue'
|
||||
import IconOffline from './icons/IconOffline.vue'
|
||||
|
||||
/**
|
||||
* 缺省页(独立布局,无侧栏 / 顶栏 / 页签):403 / 404 / 500 / 离线。
|
||||
* 插图取自 vben(@vben/common-ui 的 fallback icons),主题变量已替换为 --fms-*。
|
||||
* 按钮分工照 vben:403 / 404 给「返回首页」,500 / 离线给「重新加载」(整页刷新即恢复手段)。
|
||||
* 文案走 i18n(translate 内置中文兜底,英文补在 builtin-messages 的 en-US)。
|
||||
*/
|
||||
const props = defineProps({
|
||||
/** 缺省页类型 */
|
||||
status: {
|
||||
type: String,
|
||||
default: '404',
|
||||
validator: (value) => ['403', '404', '500', 'offline'].includes(value),
|
||||
},
|
||||
/** 403 / 404 的返回目标 */
|
||||
homePath: { type: String, default: '/dashboard' },
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const PAGES = {
|
||||
403: {
|
||||
icon: Icon403,
|
||||
title: () => translate('fallback.403.title', '无权访问'),
|
||||
desc: () => translate('fallback.403.desc', '你没有访问该页面的权限,请联系管理员'),
|
||||
action: 'home',
|
||||
},
|
||||
404: {
|
||||
icon: Icon404,
|
||||
title: () => translate('fallback.404.title', '页面不存在'),
|
||||
desc: () => translate('fallback.404.desc', '你访问的页面不存在或已被移除'),
|
||||
action: 'home',
|
||||
},
|
||||
500: {
|
||||
icon: Icon500,
|
||||
title: () => translate('fallback.500.title', '页面出错了'),
|
||||
desc: () => translate('fallback.500.desc', '服务暂时不可用,请稍后重试'),
|
||||
action: 'reload',
|
||||
},
|
||||
offline: {
|
||||
icon: IconOffline,
|
||||
title: () => translate('fallback.offline.title', '网络已断开'),
|
||||
desc: () => translate('fallback.offline.desc', '请检查网络连接后重试'),
|
||||
action: 'reload',
|
||||
},
|
||||
}
|
||||
|
||||
const page = computed(() => PAGES[props.status] ?? PAGES['404'])
|
||||
const isHome = computed(() => page.value.action === 'home')
|
||||
/** 动作按钮文案:回首页 / 重新加载 */
|
||||
const actionText = computed(() => {
|
||||
return isHome.value
|
||||
? translate('fallback.backHome', '返回首页')
|
||||
: translate('fallback.reload', '重新加载')
|
||||
})
|
||||
|
||||
function onAction() {
|
||||
if (isHome.value) {
|
||||
router.push(props.homePath)
|
||||
} else {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fms-fallback">
|
||||
<component :is="page.icon" class="fms-fallback__icon" />
|
||||
<h1 class="fms-fallback__title">{{ page.title() }}</h1>
|
||||
<p class="fms-fallback__desc">{{ page.desc() }}</p>
|
||||
<Button type="primary" @click="onAction">
|
||||
<component :is="isHome ? ArrowLeft : RefreshCw" />
|
||||
{{ actionText }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 整页居中:灰底画布 + 居中插画 / 文案 / 动作。
|
||||
* min-height: 100vh —— 给独立路由(404)与离线覆盖层撑满视口;
|
||||
* 挂在 DefaultLayout 内时(403 / 500)由布局的 .fms-page-wrap > .fms-fallback
|
||||
* 归零并改为 flex: 1,吃满内容区高度而非视口(否则会撑出滚动) */
|
||||
.fms-fallback {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
background: var(--fms-background);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fms-fallback__icon {
|
||||
width: clamp(200px, 30vw, 380px);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.fms-fallback__title {
|
||||
margin: 24px 0 0;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--fms-text);
|
||||
}
|
||||
|
||||
.fms-fallback__desc {
|
||||
margin: 8px 0 20px;
|
||||
font-size: 14px;
|
||||
color: var(--fms-text-secondary);
|
||||
}
|
||||
|
||||
/* 插图为固定浅灰系(vben 原图无深色适配),深色下压一档透明度融入背景 */
|
||||
.dark .fms-fallback__icon {
|
||||
opacity: 0.75;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
import FallbackPage from './FallbackPage.vue'
|
||||
|
||||
// 403 缺省页:挂在 DefaultLayout 内(保留侧栏 / 顶栏 / 页签,用户可直接切走)。
|
||||
// status 在组件内固定而非走路由 props —— 布局的包装层(getPageWrapper)只传
|
||||
// fullPath,路由的 props 配置不会到达页面组件。
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FallbackPage status="403" />
|
||||
</template>
|
||||
@@ -0,0 +1,10 @@
|
||||
<script setup>
|
||||
import FallbackPage from './FallbackPage.vue'
|
||||
|
||||
// 500 缺省页:挂在 DefaultLayout 内供直接访问排查;
|
||||
// 渲染崩溃的兜底由 App 层的 ErrorBoundary 全屏渲染,不经过本路由。
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FallbackPage status="500" />
|
||||
</template>
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<svg
|
||||
height="659.29778"
|
||||
viewBox="0 0 586 659.29778"
|
||||
width="586"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<circle cx="332.47856" cy="254" fill="#f2f2f2" r="254.00001" />
|
||||
<path
|
||||
d="M498.46363,113.58835H33.17063c-.99774-.02133-1.78931-.84746-1.76797-1.84521,.02069-.96771,.80026-1.74727,1.76797-1.76796H498.46363c.99774,.02133,1.78931,.84746,1.76794,1.84521-.02069,.96771-.80023,1.74727-1.76794,1.76796Z"
|
||||
fill="#cacaca"
|
||||
/>
|
||||
<rect
|
||||
fill="#fff"
|
||||
height="34.98639"
|
||||
rx="17.49318"
|
||||
ry="17.49318"
|
||||
width="163.61147"
|
||||
x="193.77441"
|
||||
y="174.47256"
|
||||
/>
|
||||
<path
|
||||
d="M128.17493,244.44534H422.98542c9.66122,0,17.49316,7.83197,17.49316,17.49319h0c0,9.66122-7.83194,17.49319-17.49316,17.49319H128.17493c-9.66122,0-17.49318-7.83197-17.49318-17.49319h0c0-9.66122,7.83196-17.49319,17.49318-17.49319Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M128.17493,314.41812H422.98542c9.66122,0,17.49316,7.83197,17.49316,17.49319h0c0,9.66122-7.83194,17.49319-17.49316,17.49319H128.17493c-9.66122,0-17.49318-7.83197-17.49318-17.49319h0c0-9.66122,7.83196-17.49319,17.49318-17.49319Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M91.64085,657.75932l-.69385-.06793c-23.54068-2.42871-44.82135-15.08929-58.18845-34.61835-3.66138-5.44159-6.62299-11.32251-8.815-17.50409l-.21069-.58966,.62375-.05048c7.44699-.59924,15.09732-1.86292,18.49585-2.46417l-21.91473-7.42511-.1355-.65033c-1.29926-6.10406,1.24612-12.38458,6.4285-15.86176,5.19641-3.64447,12.08731-3.76111,17.40405-.29449,2.38599,1.52399,4.88162,3.03339,7.29489,4.49359,8.29321,5.01636,16.8688,10.20337,23.29828,17.30121,9.74951,10.97778,14.02298,25.76984,11.63,40.25562l4.7829,17.47595Z"
|
||||
fill="#f2f2f2"
|
||||
/>
|
||||
<polygon
|
||||
fill="#a0616a"
|
||||
points="171.30016 646.86102 182.10017 646.85999 187.23916 605.198 171.29716 605.19897 171.30016 646.86102"
|
||||
/>
|
||||
<path
|
||||
d="M170.9192,658.12816l33.21436-.00122v-.41998c-.00049-7.13965-5.78833-12.92737-12.92798-12.92773h-.00079l-6.06702-4.60278-11.3197,4.60345-2.89941,.00012,.00055,13.34814Z"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<polygon
|
||||
fill="#a0616a"
|
||||
points="84.74116 616.94501 93.38016 623.42603 122.49316 593.185 109.74116 583.61902 84.74116 616.94501"
|
||||
/>
|
||||
<path
|
||||
d="M77.67448,625.72966l26.569,19.93188,.25208-.336c4.2843-5.71136,3.12799-13.81433-2.58279-18.09937l-.00064-.00049-2.09079-7.32275-11.81735-3.11102-2.31931-1.73993-8.01019,10.67767Z"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<path
|
||||
d="M120.64463,451.35271s.59625,16.26422,1.3483,29.30737c.12335,2.13916-4.88821,4.46301-4.75842,6.7901,.08609,1.54395,1.02808,3.04486,1.1156,4.65472,.09235,1.69897-1.20822,3.20282-1.1156,4.95984,.09052,1.71667,1.57422,3.6853,1.66373,5.44244,.96317,18.9093,4.45459,41.54633,.9584,47.87439-1.72299,3.11871-23.68533,46.32446-23.68533,46.32446,0,0,12.23666,18.35498,15.73285,12.23663,4.61771-8.08099,40.20615-45.88745,40.20615-53.10712,0-7.21088,8.23346-61.25323,8.23346-61.25323l5.74103,31.98169,2.63239,6.33655-.82715,3.71997,1.70117,5.02045,.09192,4.96838,1.65619,9.22614s-4.98199,71.88159-2.17633,73.88312c2.81439,2.01038,16.44086,5.62018,18.04901,2.01038,1.59955-3.6098,12.0108-75.01947,12.0108-75.01947,0,0,1.6781-32.72424,3.49622-63.14111,.1048-1.76556,1.34607-3.89825,1.4422-5.63763,.11365-2.01898-.67297-4.64111-.56818-6.599,.11365-2.24628,1.11005-3.82831,1.20618-5.97852,.74292-16.6156-3.42761-36.84912-4.7561-38.84192-4.01202-6.01343-7.62177-10.82074-7.62177-10.82074,0,0-54.03558-17.75403-68.47485,.28625l-3.30185,25.37585Z"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<path
|
||||
d="M174.53779,284.10378l-21.4209-4.28418-9.9964,13.56656h0c-18.65262,18.34058-18.93359,34.52753-15.60379,60.47382v36.41553l-2.41,24.41187s-8.53156,17.84521,.26788,22.00006,66.59857,3.80066,72.117,2.14209,.73517-3.69482-.71399-11.4245c-2.72211-14.51929-.90131-7.51562-.71399-12.13849,2.68585-66.31363-3.57013-93.5379-4.20544-100.69376l-10.89398-19.75858-6.42639-10.71042Z"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<path
|
||||
d="M287.43909,337.57097c-2.23248,4.23007-7.47144,5.84943-11.70148,3.61694-.45099-.23804-.88013-.51541-1.28229-.82895l-46.26044,29.37308,.13336-15.9924,44.93842-26.07846c3.20093-3.58887,8.70514-3.90332,12.29401-.70239,3.00305,2.67844,3.7796,7.0657,1.87842,10.61218Z"
|
||||
fill="#a0616a"
|
||||
/>
|
||||
<path
|
||||
d="M157.62488,302.62425l-5.26666-.55807c-4.86633-.50473-9.64093,1.57941-12.57947,5.491-1.12549,1.48346-1.9339,3.18253-2.37491,4.99164l-.00317,.01447c-1.32108,5.44534,.75095,11.15201,5.25803,14.48117l18.19031,13.41101c12.76544,17.24899,36.75653,28.69272,64.89832,37.98978l43.74274-27.16666-15.47186-18.73843-30.00336,16.0798-44.59833-34.52374-.0257-.02075-16.97424-10.936-4.79169-.5152Z"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<circle cx="167.29993" cy="248.60526" fill="#a0616a" r="24.9798" />
|
||||
<path
|
||||
d="M167.8769,273.59047c-.20135,.00662-.4032,.01108-.6048,.01657-.0863,.22388-.17938,.44583-.2868,.66357l.8916-.68015Z"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<path
|
||||
d="M174.73243,249.29823c.03918,.24612,.09912,.48846,.17914,.72449-.03302-.24731-.09308-.49026-.17914-.72449Z"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<path
|
||||
d="M192.59852,224.6942c-1.0282,3.19272-1.94586-.85715-5.32825-.12869-4.06885,.87625-8.80377,.57532-12.13586-1.91879-4.96478-3.64273-11.39874-4.62335-17.22333-2.62509-5.70154,2.01706-15.25348,3.43933-16.73907,9.30179-.51642,2.03781-.7215,4.24933-1.97321,5.9382-1.09436,1.47662-2.82166,2.31854-4.26608,3.45499-4.87726,3.83743-1.14954,14.73981,1.15881,20.50046,2.30838,5.76065,7.60355,9.95721,13.42526,12.10678,5.63281,2.07977,11.7464,2.44662,17.75531,2.28317,1.04517-2.7106,.59363-5.84137-.26874-8.65134-.93359-3.04199-2.31592-5.97791-2.70593-9.13599s.46643-6.74527,3.11444-8.50986c2.4339-1.62192,6.39465-.63388,7.32062,1.98843-.54028-3.27841,2.7807-6.4509,6.20508-7.00882,3.67651-.599,7.35291,.72833,11.01886,1.38901s2.36475-14.77301,.64209-18.98425Z"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<circle
|
||||
cx="281.3585"
|
||||
cy="285.71051"
|
||||
fill="var(--fms-primary)"
|
||||
r="51.12006"
|
||||
transform="translate(-26.58509 542.54478) rotate(-85.26884)"
|
||||
/>
|
||||
<path
|
||||
d="M294.78675,264.41051l-13.42828,13.42828-13.42828-13.42828c-2.17371-2.17374-5.69806-2.17374-7.87177,0s-2.17371,5.69803,0,7.87177l13.42828,13.42828-13.42828,13.42828c-2.17169,2.17575-2.1684,5.70007,.00739,7.87177,2.17285,2.16879,5.69153,2.16879,7.86438-.00003l13.42828-13.42828,13.42828,13.42828c2.17578,2.17169,5.70007,2.1684,7.87177-.00735,2.16882-2.17288,2.16882-5.6915,0-7.86438l-13.42828-13.42828,13.42828-13.42828c2.17371-2.17374,2.17371-5.69803,0-7.87177s-5.69806-2.17374-7.87177,0h0Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M261.21387,242.74385c1.5069,4.53946-.95154,9.44101-5.49097,10.94791-.48401,.16064-.9812,.27823-1.4859,.35141l-10.83051,53.71692-11.44788-11.16785,12.29266-50.48209c-.37366-4.7944,3.21008-8.98395,8.00452-9.3576,4.01166-.31265,7.71509,2.16425,8.95807,5.9913Z"
|
||||
fill="#a0616a"
|
||||
/>
|
||||
<path
|
||||
d="M146.12519,312.22478l-4.04883,3.41412c-3.73322,3.16214-5.53476,8.05035-4.74649,12.87888,.29129,1.83917,.95773,3.59879,1.95786,5.16949l.00824,.0123c3.01477,4.72311,8.5672,7.17865,14.08978,6.23117l22.27075-3.84171c21.28461,2.72995,46.15155-6.65967,72.34302-20.53055l10.67969-50.37274-24.23297-1.80811-9.16821,32.78271-55.78815,8.28149-.03278,.00415-19.64294,4.67767-3.68896,3.1011Z"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<path
|
||||
d="M272.93684,658.99046l-271.75,.30731c-.65759-.00214-1.18896-.53693-1.18683-1.19452,.00211-.6546,.53223-1.18469,1.18683-1.18683l271.75-.30731c.65759,.00214,1.18896,.53693,1.18683,1.19452-.00208,.6546-.53223,1.18469-1.18683,1.18683Z"
|
||||
fill="#cacaca"
|
||||
/>
|
||||
<g>
|
||||
<ellipse cx="56.77685" cy="82.05834" fill="#3f3d56" rx="8.45661" ry="8.64507" />
|
||||
<ellipse cx="85.9906" cy="82.05834" fill="#3f3d56" rx="8.45661" ry="8.64507" />
|
||||
<ellipse cx="115.20435" cy="82.05834" fill="#3f3d56" rx="8.45661" ry="8.64507" />
|
||||
<path
|
||||
d="M148.51577,88.89113c-.25977,0-.51904-.10059-.71484-.30078l-5.70605-5.83301c-.38037-.38867-.38037-1.00977,0-1.39844l5.70605-5.83252c.38721-.39453,1.021-.40088,1.41406-.01562,.39502,.38623,.40186,1.01953,.01562,1.41406l-5.02197,5.1333,5.02197,5.13379c.38623,.39453,.37939,1.02783-.01562,1.41406-.19434,.19043-.44678,.28516-.69922,.28516Z"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<path
|
||||
d="M158.10415,88.89113c-.25244,0-.50488-.09473-.69922-.28516-.39502-.38623-.40186-1.01904-.01562-1.41406l5.02148-5.13379-5.02148-5.1333c-.38623-.39453-.37939-1.02783,.01562-1.41406,.39404-.38672,1.02783-.37939,1.41406,.01562l5.70557,5.83252c.38037,.38867,.38037,1.00977,0,1.39844l-5.70557,5.83301c-.1958,.2002-.45508,.30078-.71484,.30078Z"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<path
|
||||
d="M456.61398,74.41416h-10.60999c-1.21002,0-2.19,.97998-2.19,2.19v10.62c0,1.21002,.97998,2.19,2.19,2.19h10.60999c1.21002,0,2.20001-.97998,2.20001-2.19v-10.62c0-1.21002-.98999-2.19-2.20001-2.19Z"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<path
|
||||
d="M430.61398,74.41416h-10.60999c-1.21002,0-2.19,.97998-2.19,2.19v10.62c0,1.21002,.97998,2.19,2.19,2.19h10.60999c1.21002,0,2.20001-.97998,2.20001-2.19v-10.62c0-1.21002-.98999-2.19-2.20001-2.19Z"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<path
|
||||
d="M481.11398,74.91416h-10.60999c-1.21002,0-2.19,.97998-2.19,2.19v10.62c0,1.21002,.97998,2.19,2.19,2.19h10.60999c1.21002,0,2.20001-.97998,2.20001-2.19v-10.62c0-1.21002-.98999-2.19-2.20001-2.19Z"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<path
|
||||
d="M321.19229,78.95414h-84.81c-1.48004,0-2.67004,1.20001-2.67004,2.67004s1.19,2.66998,2.67004,2.66998h84.81c1.46997,0,2.66998-1.20001,2.66998-2.66998s-1.20001-2.67004-2.66998-2.67004Z"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<svg
|
||||
height="571"
|
||||
viewBox="0 0 860 571"
|
||||
width="860"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<path
|
||||
d="M605.66974,324.95306c-7.66934-12.68446-16.7572-26.22768-30.98954-30.36953-16.482-4.7965-33.4132,4.73193-47.77473,14.13453a1392.15692,1392.15692,0,0,0-123.89338,91.28311l.04331.49238q46.22556-3.1878,92.451-6.37554c22.26532-1.53546,45.29557-3.2827,64.97195-13.8156,7.46652-3.99683,14.74475-9.33579,23.20555-9.70782,10.51175-.46217,19.67733,6.87923,26.8802,14.54931,42.60731,45.371,54.937,114.75409,102.73817,154.61591A1516.99453,1516.99453,0,0,0,605.66974,324.95306Z"
|
||||
fill="#f2f2f2"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M867.57068,709.78146c-4.71167-5.94958-6.6369-7.343-11.28457-13.34761q-56.7644-73.41638-106.70791-151.79237-33.92354-53.23-64.48275-108.50439-14.54864-26.2781-28.29961-52.96872-10.67044-20.6952-20.8646-41.63793c-1.94358-3.98782-3.8321-7.99393-5.71122-12.00922-4.42788-9.44232-8.77341-18.93047-13.43943-28.24449-5.31686-10.61572-11.789-21.74485-21.55259-28.877a29.40493,29.40493,0,0,0-15.31855-5.89458c-7.948-.51336-15.28184,2.76855-22.17568,6.35295-50.43859,26.301-97.65922,59.27589-140.3696,96.79771A730.77816,730.77816,0,0,0,303.32241,496.24719c-1.008,1.43927-3.39164.06417-2.37419-1.38422q6.00933-8.49818,12.25681-16.81288A734.817,734.817,0,0,1,500.80465,303.06436q18.24824-11.82581,37.18269-22.54245c6.36206-3.60275,12.75188-7.15967,19.25136-10.49653,6.37146-3.27274,13.13683-6.21547,20.41563-6.32547,24.7701-.385,37.59539,27.66695,46.40506,46.54248q4.15283,8.9106,8.40636,17.76626,16.0748,33.62106,33.38729,66.628,10.68453,20.379,21.83683,40.51955,34.7071,62.71816,73.77854,122.897c34.5059,53.1429,68.73651,100.08874,108.04585,149.78472C870.59617,709.21309,868.662,711.17491,867.57068,709.78146Z"
|
||||
fill="#e4e4e4"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M414.91613,355.804c-1.43911-1.60428-2.86927-3.20856-4.31777-4.81284-11.42244-12.63259-23.6788-25.11847-39.3644-32.36067a57.11025,57.11025,0,0,0-23.92679-5.54622c-8.56213.02753-16.93178,2.27348-24.84306,5.41792-3.74034,1.49427-7.39831,3.1902-11.00078,4.99614-4.11634,2.07182-8.15927,4.28118-12.1834,6.50883q-11.33112,6.27044-22.36816,13.09089-21.9606,13.57221-42.54566,29.21623-10.67111,8.11311-20.90174,16.75788-9.51557,8.03054-18.64618,16.492c-1.30169,1.20091-3.24527-.74255-1.94358-1.94347,1.60428-1.49428,3.22691-2.97938,4.84955-4.44613q6.87547-6.21546,13.9712-12.19257,12.93921-10.91827,26.54851-20.99312,21.16293-15.67614,43.78288-29.22541,11.30361-6.76545,22.91829-12.96259c2.33794-1.24675,4.70318-2.466,7.09572-3.6211a113.11578,113.11578,0,0,1,16.86777-6.86632,60.0063,60.0063,0,0,1,25.476-2.50265,66.32706,66.32706,0,0,1,23.50512,8.1314c15.40091,8.60812,27.34573,21.919,38.97,34.90915C418.03337,355.17141,416.09875,357.12405,414.91613,355.804Z"
|
||||
fill="#e4e4e4"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M730.47659,486.71092l36.90462-13.498,18.32327-6.70183c5.96758-2.18267,11.92082-4.66747,18.08988-6.23036a28.53871,28.53871,0,0,1,16.37356.20862,37.73753,37.73753,0,0,1,12.771,7.91666,103.63965,103.63965,0,0,1,10.47487,11.18643c3.98932,4.79426,7.91971,9.63877,11.86772,14.46706q24.44136,29.89094,48.56307,60.04134,24.12117,30.14991,47.91981,60.556,23.85681,30.48041,47.38548,61.21573,2.88229,3.76518,5.75966,7.53415c1.0598,1.38809,3.44949.01962,2.37472-1.38808Q983.582,650.9742,959.54931,620.184q-24.09177-30.86383-48.51647-61.46586-24.42421-30.60141-49.17853-60.93743-6.16706-7.55761-12.35445-15.09858c-3.47953-4.24073-6.91983-8.52718-10.73628-12.47427-7.00539-7.24516-15.75772-13.64794-26.23437-13.82166-6.15972-.10214-12.121,1.85248-17.844,3.92287-6.16968,2.232-12.32455,4.50571-18.48633,6.75941l-37.16269,13.59243-9.29067,3.3981c-1.64875.603-.93651,3.2619.73111,2.652Z"
|
||||
fill="#e4e4e4"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M366.37741,334.52609c-18.75411-9.63866-42.77137-7.75087-60.00508,4.29119a855.84708,855.84708,0,0,1,97.37056,22.72581C390.4603,353.75916,380.07013,341.5635,366.37741,334.52609Z"
|
||||
fill="#f2f2f2"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M306.18775,338.7841l-3.61042,2.93462c1.22123-1.02713,2.4908-1.99013,3.795-2.90144C306.31073,338.80665,306.24935,338.79473,306.18775,338.7841Z"
|
||||
fill="#f2f2f2"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M831.54929,486.84576c-3.6328-4.42207-7.56046-9.05222-12.99421-10.84836l-5.07308.20008A575.436,575.436,0,0,0,966.74929,651.418Q899.14929,569.13192,831.54929,486.84576Z"
|
||||
fill="#f2f2f2"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M516.08388,450.36652A37.4811,37.4811,0,0,0,531.015,471.32518c2.82017,1.92011,6.15681,3.76209,7.12158,7.03463a8.37858,8.37858,0,0,1-.87362,6.1499,24.88351,24.88351,0,0,1-3.86126,5.04137l-.13667.512c-6.99843-4.14731-13.65641-9.3934-17.52227-16.55115s-4.40553-16.53895.34116-23.14544"
|
||||
fill="#f2f2f2"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M749.08388,653.36652A37.4811,37.4811,0,0,0,764.015,674.32518c2.82017,1.92011,6.15681,3.76209,7.12158,7.03463a8.37858,8.37858,0,0,1-.87362,6.1499,24.88351,24.88351,0,0,1-3.86126,5.04137l-.13667.512c-6.99843-4.14731-13.65641-9.3934-17.52227-16.55115s-4.40553-16.53895.34116-23.14544"
|
||||
fill="#f2f2f2"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M284.08388,639.36652A37.4811,37.4811,0,0,0,299.015,660.32518c2.82017,1.92011,6.15681,3.76209,7.12158,7.03463a8.37858,8.37858,0,0,1-.87362,6.1499,24.88351,24.88351,0,0,1-3.86126,5.04137l-.13667.512c-6.99843-4.14731-13.65641-9.3934-17.52227-16.55115s-4.40553-16.53895.34116-23.14544"
|
||||
fill="#f2f2f2"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<circle cx="649.24878" cy="51" fill="var(--fms-primary)" r="51" />
|
||||
<path
|
||||
d="M911.21851,176.29639c-24.7168-3.34094-52.93512,10.01868-59.34131,34.12353a21.59653,21.59653,0,0,0-41.09351,2.10871l2.82972,2.02667a372.27461,372.27461,0,0,0,160.65881-.72638C957.07935,195.76,935.93537,179.63727,911.21851,176.29639Z"
|
||||
fill="#f0f0f0"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M805.21851,244.29639c-24.7168-3.34094-52.93512,10.01868-59.34131,34.12353a21.59653,21.59653,0,0,0-41.09351,2.10871l2.82972,2.02667a372.27461,372.27461,0,0,0,160.65881-.72638C851.07935,263.76,829.93537,247.63727,805.21851,244.29639Z"
|
||||
fill="#f0f0f0"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M1020.94552,257.15423a.98189.98189,0,0,1-.30176-.04688C756.237,173.48919,523.19942,184.42376,374.26388,208.32122c-20.26856,3.251-40.59131,7.00586-60.40381,11.16113-5.05811,1.05957-10.30567,2.19532-15.59668,3.37793-6.31885,1.40723-12.55371,2.85645-18.53223,4.30567q-3.873.917-7.59472,1.84863c-3.75831.92773-7.57178,1.89453-11.65967,2.957-4.56787,1.17774-9.209,2.41309-13.79737,3.67188a.44239.44239,0,0,1-.05127.01465l.00049.001c-5.18261,1.415-10.33789,2.8711-15.32324,4.3252-2.69824.77929-5.30371,1.54785-7.79932,2.30664-.2788.07715-.52587.15136-.77636.22754l-.53614.16308c-.31054.09473-.61718.1875-.92382.27539l-.01953.00586.00048.001-.81152.252c-.96777.293-1.91211.5791-2.84082.86426-24.54492,7.56641-38.03809,12.94922-38.17139,13.00195a1,1,0,1,1-.74414-1.85644c.13428-.05274,13.69336-5.46289,38.32764-13.05762.93213-.28613,1.87891-.57226,2.84961-.86621l.7539-.23438c.02588-.00976.05176-.01757.07813-.02539.30518-.08691.60986-.17968.91943-.27343l.53711-.16309c.26758-.08105.53125-.16113.80127-.23535,2.47852-.75391,5.09278-1.52441,7.79785-2.30664,4.98731-1.45508,10.14746-2.91113,15.334-4.32813.01611-.00586.03271-.00976.04883-.01464v-.001c4.60449-1.2627,9.26269-2.50293,13.84521-3.68457,4.09424-1.06348,7.915-2.03223,11.67969-2.96192q3.73755-.93017,7.60937-1.85253c5.98536-1.45118,12.23291-2.90235,18.563-4.3125,5.29932-1.1836,10.55567-2.32227,15.62207-3.38282,19.84326-4.16211,40.19776-7.92285,60.49707-11.17871C523.09591,182.415,756.46749,171.46282,1021.2463,255.2011a.99974.99974,0,0,1-.30078,1.95313Z"
|
||||
fill="#ccc"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M432.92309,584.266a6.72948,6.72948,0,0,0-1.7-2.67,6.42983,6.42983,0,0,0-.92-.71c-2.61-1.74-6.51-2.13-8.99,0a5.81012,5.81012,0,0,0-.69.71q-1.11,1.365-2.28,2.67c-1.28,1.46-2.59,2.87-3.96,4.24-.39.38-.78.77-1.18,1.15-.23.23-.46.45-.69.67-.88.84-1.78,1.65-2.69,2.45-.48.43-.96.85-1.45,1.26-.73.61-1.46,1.22-2.2,1.81-.07.05-.14.1-.21.16-.02.01-.03.03-.05.04-.01,0-.02,0-.03.02a.17861.17861,0,0,0-.07.05c-.22.15-.37.25-.48.34.04-.01995.08-.05.12-.07-.18.14-.37.28-.55.42-1.75,1.29-3.54,2.53-5.37,3.69a99.21022,99.21022,0,0,1-14.22,7.55c-.33.13-.67.27-1.01.4a85.96993,85.96993,0,0,1-40.85,6.02q-2.13008-.165-4.26-.45c-1.64-.24-3.27-.53-4.89-.86a97.93186,97.93186,0,0,1-18.02-5.44,118.65185,118.65185,0,0,1-20.66-12.12c-1-.71-2.01-1.42-3.02-2.11,1.15-2.82,2.28-5.64,3.38-8.48.55-1.37,1.08-2.74,1.6-4.12,4.09-10.63,7.93-21.36,11.61-32.13q5.58-16.365,10.53-32.92.51-1.68.99-3.36,2.595-8.745,4.98-17.53c.15-.56994.31-1.12994.45-1.7q.68994-2.52,1.35-5.04c1-3.79-1.26-8.32-5.24-9.23a7.63441,7.63441,0,0,0-9.22,5.24c-.43,1.62-.86,3.23-1.3,4.85q-3.165,11.74494-6.66,23.41-.51,1.68-1.02,3.36-7.71,25.41-16.93,50.31-1.11,3.015-2.25,6.01c-.37.98-.74,1.96-1.12,2.94-.73,1.93-1.48,3.86-2.23,5.79-.43006,1.13-.87006,2.26-1.31,3.38-.29.71-.57,1.42-.85,2.12a41.80941,41.80941,0,0,0-8.81-2.12l-.48-.06a27.397,27.397,0,0,0-7.01.06,23.91419,23.91419,0,0,0-17.24,10.66c-4.77,7.51-4.71,18.25,1.98,24.63,6.89,6.57,17.32,6.52,25.43,2.41a28.35124,28.35124,0,0,0,10.52-9.86,50.56939,50.56939,0,0,0,2.74-4.65c.21.14.42.28.63.43.8.56,1.6,1.13,2.39,1.69a111.73777,111.73777,0,0,0,14.51,8.91,108.35887,108.35887,0,0,0,34.62,10.47c.27.03.53.07.8.1,1.33.17,2.67.3,4.01.41a103.78229,103.78229,0,0,0,55.58-11.36q2.175-1.125,4.31-2.36,3.315-1.92,6.48-4.08c1.15-.78,2.27-1.57,3.38-2.4a101.04244,101.04244,0,0,0,13.51-11.95q2.35491-2.475,4.51-5.11005a8.0612,8.0612,0,0,0,2.2-5.3A7.5644,7.5644,0,0,0,432.92309,584.266Zm-165.59,23.82c.21-.15.42-.31.62-.47C267.89312,607.766,267.60308,607.936,267.33312,608.086Zm3.21-3.23c-.23.26-.44.52-.67.78a23.36609,23.36609,0,0,1-2.25,2.2c-.11.1-.23.2-.35.29a.00976.00976,0,0,0-.01.01,3.80417,3.80417,0,0,0-.42005.22q-.645.39-1.31994.72a17.00459,17.00459,0,0,1-2.71.75,16.79925,16.79925,0,0,1-2.13.02h-.02a14.82252,14.82252,0,0,1-1.45-.4c-.24-.12-.47-.25994-.7-.4-.09-.08-.17005-.16-.22-.21a2.44015,2.44015,0,0,1-.26995-.29.0098.0098,0,0,0-.01-.01c-.11005-.2-.23005-.4-.34-.6a.031.031,0,0,1-.01-.02c-.08-.25-.15-.51-.21-.77a12.51066,12.51066,0,0,1,.01-1.37,13.4675,13.4675,0,0,1,.54-1.88,11.06776,11.06776,0,0,1,.69-1.26c.02-.04.12-.2.23-.38.01-.01.01-.01.01-.02.15-.17.3-.35.46-.51.27-.3.56-.56.85-.83a18.02212,18.02212,0,0,1,1.75-1.01,19.48061,19.48061,0,0,1,2.93-.79,24.98945,24.98945,0,0,1,4.41.04,30.30134,30.30134,0,0,1,4.1,1.01,36.94452,36.94452,0,0,1-2.77,4.54C270.6231,604.746,270.58312,604.806,270.54308,604.856Zm-11.12-3.29a2.18029,2.18029,0,0,1-.31.38995A1.40868,1.40868,0,0,1,259.42309,601.566Z"
|
||||
fill="var(--fms-text)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M402.86309,482.136q-.13494,4.71-.27,9.42-.285,10.455-.59,20.92-.315,11.775-.66,23.54-.165,6.07507-.34,12.15-.465,16.365-.92,32.72c-.03,1.13-.07,2.25-.1,3.38q-.225,8.11506-.45,16.23-.255,8.805-.5,17.61-.18,6.59994-.37,13.21-1.34994,47.895-2.7,95.79a7.64844,7.64844,0,0,1-7.5,7.5,7.56114,7.56114,0,0,1-7.5-7.5q.75-26.94,1.52-53.88.675-24.36,1.37-48.72.225-8.025.45-16.06.345-12.09.68-24.18c.03-1.13.07-2.25.1-3.38.02-.99.05-1.97.08-2.96q.66-23.475,1.32-46.96.27-9.24.52-18.49.3-10.545.6-21.08c.09-3.09.17005-6.17.26-9.26a7.64844,7.64844,0,0,1,7.5-7.5A7.56116,7.56116,0,0,1,402.86309,482.136Z"
|
||||
fill="var(--fms-text)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M814.29118,484.2172a893.23753,893.23753,0,0,1-28.16112,87.94127c-3.007,7.94641-6.08319,15.877-9.3715,23.71185l.75606-1.7916a54.58274,54.58274,0,0,1-5.58953,10.61184q-.22935.32119-.46685.63642,1.16559-1.49043.4428-.589c-.25405.30065-.5049.60219-.7676.89546a23.66436,23.66436,0,0,1-2.2489,2.20318q-.30139.25767-.61188.5043l.93783-.729c-.10884.25668-.87275.59747-1.11067.74287a18.25362,18.25362,0,0,1-2.40479,1.21853l1.7916-.75606a19.0859,19.0859,0,0,1-4.23122,1.16069l1.9938-.26791a17.02055,17.02055,0,0,1-4.29785.046l1.99379.2679a14.0022,14.0022,0,0,1-3.40493-.917l1.79159.75606a12.01175,12.01175,0,0,1-1.67882-.89614c-.27135-.17688-1.10526-.80852-.01487.02461,1.13336.86595.14562.07434-.08763-.15584-.19427-.19171-.36962-.4-.55974-.595-.88208-.90454.99637,1.55662.39689.49858a18.18179,18.18179,0,0,1-.87827-1.63672l.75606,1.7916a11.92493,11.92493,0,0,1-.728-2.65143l.26791,1.9938a13.65147,13.65147,0,0,1-.00316-3.40491l-.2679,1.9938a15.96371,15.96371,0,0,1,.99486-3.68011l-.75606,1.7916a16.72914,16.72914,0,0,1,1.17794-2.29848,6.72934,6.72934,0,0,1,.72851-1.0714c.04915.01594-1.26865,1.51278-.56937.757.1829-.19767.354-.40592.539-.602.29617-.31382.61354-.60082.92561-.89791,1.04458-.99442-1.46188.966-.25652.17907a19.0489,19.0489,0,0,1,2.74925-1.49923l-1.79159.75606a20.31136,20.31136,0,0,1,4.99523-1.33984l-1.9938.2679a25.62828,25.62828,0,0,1,6.46062.07647l-1.9938-.2679a33.21056,33.21056,0,0,1,7.89178,2.2199l-1.7916-.75606c5.38965,2.31383,10.16308,5.74926,14.928,9.118a111.94962,111.94962,0,0,0,14.50615,8.9065,108.38849,108.38849,0,0,0,34.62226,10.47371,103.93268,103.93268,0,0,0,92.58557-36.75192,8.07773,8.07773,0,0,0,2.1967-5.3033,7.63232,7.63232,0,0,0-2.1967-5.3033c-2.75154-2.52586-7.94926-3.239-10.6066,0a95.63575,95.63575,0,0,1-8.10664,8.72692q-2.01736,1.914-4.14232,3.70983-1.21364,1.02588-2.46086,2.01121c-.3934.31081-1.61863,1.13807.26309-.19744-.43135.30614-.845.64036-1.27058.95478a99.26881,99.26881,0,0,1-20.33215,11.56478l1.79159-.75606a96.8364,96.8364,0,0,1-24.17119,6.62249l1.99379-.2679a97.64308,97.64308,0,0,1-25.75362-.03807l1.99379.2679a99.79982,99.79982,0,0,1-24.857-6.77027l1.7916.75607a116.02515,116.02515,0,0,1-21.7364-12.59112,86.87725,86.87725,0,0,0-11.113-6.99417,42.8238,42.8238,0,0,0-14.43784-4.38851c-9.43884-1.11076-19.0571,2.56562-24.24624,10.72035-4.77557,7.50482-4.71394,18.24362,1.97369,24.62519,6.8877,6.5725,17.31846,6.51693,25.43556,2.40567,7.81741-3.95946,12.51288-12.18539,15.815-19.94186,7.43109-17.45514,14.01023-35.31364,20.1399-53.263q9.09651-26.63712,16.49855-53.81332.91661-3.36581,1.80683-6.73869c1.001-3.78869-1.26094-8.32-5.23829-9.22589a7.63317,7.63317,0,0,0-9.22589,5.23829Z"
|
||||
fill="var(--fms-text)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M889.12382,482.13557l-2.69954,95.79311-2.68548,95.29418-1.5185,53.88362a7.56465,7.56465,0,0,0,7.5,7.5,7.64923,7.64923,0,0,0,7.5-7.5l2.69955-95.79311,2.68548-95.29418,1.51849-53.88362a7.56465,7.56465,0,0,0-7.5-7.5,7.64923,7.64923,0,0,0-7.5,7.5Z"
|
||||
fill="var(--fms-text)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M629.52566,700.36106h2.32885V594.31942h54.32863v-2.32291H631.85451V547.25214H673.8102q-.92256-1.17339-1.89893-2.31694H631.85451V515.38231c-.7703-.32846-1.54659-.64493-2.32885-.9435V544.9352h-45.652V507.07c-.78227.03583-1.55258.08959-2.3289.15527v37.71h-36.4201V516.68409c-.78227.34636-1.55258.71061-2.31694,1.0928V544.9352h-30.6158v2.31694h30.6158v44.74437h-30.6158v2.32291h30.6158V700.36106h2.31694V594.31942a36.41283,36.41283,0,0,1,36.4201,36.42007v69.62157h2.3289V594.31942h45.652Zm-84.401-108.36455V547.25214h36.4201v44.74437Zm38.749,0V547.25214h.91362a44.74135,44.74135,0,0,1,44.73842,44.74437Z"
|
||||
opacity="0.2"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M615.30309,668.566a63.05854,63.05854,0,0,1-20.05,33.7c-.74.64-1.48,1.26-2.25,1.87q-2.805.25506-5.57.52c-1.53.14-3.04.29-4.54.43l-.27.03-.19-1.64-.76-6.64a37.623,37.623,0,0,1-3.3-32.44c2.64-7.12,7.42-13.41,12.12-19.65,6.49-8.62,12.8-17.14,13.03-27.65a60.54415,60.54415,0,0,1,7.9,13.33,16.432,16.432,0,0,0-5.12,3.76995c-.41.45-.82,1.08-.54,1.62006.24.46.84.57,1.36.62994,1.25.13,2.51.26,3.76.39,1,.11,2,.21,3,.32a63.99025,63.99025,0,0,1,2.45,12.18A61.18851,61.18851,0,0,1,615.30309,668.566Z"
|
||||
fill="var(--fms-text)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M648.50311,642.356c-5.9,4.29-9.35,10.46-12.03,17.26a16.62776,16.62776,0,0,0-7.17,4.58c-.41.45-.82,1.08-.54,1.62006.24.46.84.57,1.36.62994,1.25.13,2.51.26,3.76.39-2.68,8.04-5.14,16.36-9.88,23.15a36.98942,36.98942,0,0,1-12.03,10.91,38.49166,38.49166,0,0,1-4.02,1.99q-7.62.585-14.95,1.25-2.805.25506-5.57.52c-1.53.14-3.04.29-4.54.43q-.015-.825,0-1.65a63.30382,63.30382,0,0,1,15.25-39.86c.45-.52.91-1.03,1.38-1.54a61.7925,61.7925,0,0,1,16.81-12.7A62.65425,62.65425,0,0,1,648.50311,642.356Z"
|
||||
fill="var(--fms-primary)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M589.16308,699.526l-1.15,3.4-.58,1.73c-1.53.14-3.04.29-4.54.43l-.27.03c-1.66.17-3.31.34-4.96.51-.43-.5-.86-1.01-1.28-1.53a62.03045,62.03045,0,0,1,8.07-87.11c-1.32,6.91.22,13.53,2.75,20.1-.27.11-.53.22-.78.34a16.432,16.432,0,0,0-5.12,3.76995c-.41.45-.82,1.08-.54,1.62006.24.46.84.57,1.36.62994,1.25.13,2.51.26,3.76.39,1,.11,2,.21,3,.32q.705.075,1.41.15c.07.15.13.29.2.44,2.85,6.18,5.92,12.39,7.65,18.83a43.66591,43.66591,0,0,1,1.02,4.91A37.604,37.604,0,0,1,589.16308,699.526Z"
|
||||
fill="var(--fms-primary)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M689.82123,554.48655c-8.60876-16.79219-21.94605-30.92088-37.63219-41.30357a114.2374,114.2374,0,0,0-52.5626-18.37992q-3.69043-.33535-7.399-.39281c-2.92141-.04371-46.866,12.63176-61.58712,22.98214a114.29462,114.29462,0,0,0-35.333,39.527,102.49972,102.49972,0,0,0-12.12557,51.6334,113.56387,113.56387,0,0,0,14.70268,51.47577,110.47507,110.47507,0,0,0,36.44425,38.74592C549.66655,708.561,565.07375,734.51,583.1831,735.426c18.24576.923,39.05418-23.55495,55.6951-30.98707a104.42533,104.42533,0,0,0,41.72554-34.005,110.24964,110.24964,0,0,0,19.599-48.94777c2.57368-18.08313,1.37415-36.73271-4.80123-54.01627a111.85969,111.85969,0,0,0-5.58024-12.9833c-1.77961-3.50519-6.996-4.7959-10.26142-2.69063a7.67979,7.67979,0,0,0-2.69064,10.26142q1.56766,3.08773,2.91536,6.27758l-.75606-1.7916a101.15088,101.15088,0,0,1,6.87641,25.53816l-.26791-1.99379a109.2286,109.2286,0,0,1-.06613,28.68252l.26791-1.9938a109.73379,109.73379,0,0,1-7.55462,27.67419l.75606-1.79159a104.212,104.212,0,0,1-6.67151,13.09835q-1.92308,3.18563-4.08062,6.22159c-.63172.8881-1.28287,1.761-1.939,2.63114-.85625,1.13555,1.16691-1.48321.28228-.36941-.15068.18972-.30049.3801-.45182.5693q-.68121.85165-1.3818,1.68765a93.61337,93.61337,0,0,1-10.17647,10.38359q-1.36615,1.19232-2.77786,2.33115c-.46871.37832-.932.77269-1.42079,1.12472.01861-.0134,1.57956-1.19945.65556-.511-.2905.21644-.57851.43619-.86961.65184q-2.90994,2.1558-5.97433,4.092a103.48509,103.48509,0,0,1-14.75565,7.7131l1.7916-.75606a109.21493,109.21493,0,0,1-27.59663,7.55154l1.9938-.26791a108.15361,108.15361,0,0,1-28.58907.0506l1.99379.2679a99.835,99.835,0,0,1-25.09531-6.78448l1.79159.75607a93.64314,93.64314,0,0,1-13.41605-6.99094q-3.17437-2-6.18358-4.24743c-.2862-.21359-.56992-.43038-.855-.64549-.9155-.69088.65765.50965.67021.51787a19.16864,19.16864,0,0,1-1.535-1.22469q-1.45353-1.18358-2.86136-2.4218a101.98931,101.98931,0,0,1-10.49319-10.70945q-1.21308-1.43379-2.37407-2.91054c-.33524-.4263-.9465-1.29026.40424.5289-.17775-.23939-.36206-.47414-.54159-.71223q-.64657-.85751-1.27568-1.72793-2.203-3.048-4.18787-6.24586a109.29037,109.29037,0,0,1-7.8054-15.10831l.75606,1.7916a106.58753,106.58753,0,0,1-7.34039-26.837l.26791,1.9938a97.86589,97.86589,0,0,1-.04843-25.63587l-.2679,1.9938A94.673,94.673,0,0,1,505.27587,570.55l-.75606,1.7916a101.55725,101.55725,0,0,1,7.19519-13.85624q2.0655-3.32328,4.37767-6.4847.52528-.71832,1.06244-1.42786c.324-.4279,1.215-1.49333-.30537.38842.14906-.18449.29252-.37428.43942-.56041q1.26882-1.60756,2.59959-3.1649A107.40164,107.40164,0,0,1,530.772,536.21508q1.47408-1.29171,2.99464-2.52906.6909-.56218,1.39108-1.11284c.18664-.14673.37574-.29073.56152-.43858-1.99743,1.58953-.555.43261-.10157.09288q3.13393-2.34833,6.43534-4.46134a103.64393,103.64393,0,0,1,15.38655-8.10791l-1.7916.75606c7.76008-3.25839,42.14086-10.9492,48.394-10.10973l-1.99379-.26791A106.22471,106.22471,0,0,1,628.768,517.419l-1.7916-.75606a110.31334,110.31334,0,0,1,12.6002,6.32922q3.04344,1.78405,5.96742,3.76252,1.38351.93658,2.73809,1.915.677.48917,1.34626.98885c.24789.185.49386.37253.74135.558,1.03924.779-1.43148-1.1281-.34209-.26655a110.84261,110.84261,0,0,1,10.36783,9.2532q2.401,2.445,4.63686,5.04515,1.14659,1.33419,2.24643,2.70757c.36436.45495,1.60506,2.101.08448.08457.37165.49285.74744.98239,1.11436,1.47884a97.97718,97.97718,0,0,1,8.39161,13.53807c1.79317,3.49775,6.98675,4.80186,10.26142,2.69064A7.67666,7.67666,0,0,0,689.82123,554.48655Z"
|
||||
fill="var(--fms-text)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M602.43116,676.88167a3.77983,3.77983,0,0,1-2.73939-6.55137c.09531-.37882.16368-.65085.259-1.02968q-.05115-.12366-.1029-.24717c-3.47987-8.29769-25.685,14.83336-26.645,22.63179a30.029,30.029,0,0,0,.52714,10.32752A120.39223,120.39223,0,0,1,562.77838,652.01a116.20247,116.20247,0,0,1,.72078-12.96332q.59712-5.293,1.65679-10.51055a121.78667,121.78667,0,0,1,24.1515-51.61646c6.87378.38364,12.898-.66348,13.47967-13.98532.10346-2.36972,1.86113-4.42156,2.24841-6.756-.65621.08607-1.32321.13985-1.97941.18285-.20444.0107-.41958.02149-.624.03228l-.07709.00346a3.745,3.745,0,0,1-3.07566-6.10115q.425-.52305.85054-1.04557c.43036-.53793.87143-1.06507,1.30171-1.60292a1.865,1.865,0,0,0,.13986-.16144c.49494-.61322.98971-1.21564,1.48465-1.82885a10.82911,10.82911,0,0,0-3.55014-3.43169c-4.95941-2.90463-11.80146-.89293-15.38389,3.59313-3.59313,4.486-4.27083,10.77947-3.023,16.3843a43.39764,43.39764,0,0,0,6.003,13.3828c-.269.34429-.54872.67779-.81765,1.02209a122.57366,122.57366,0,0,0-12.79359,20.2681c1.0163-7.93863-11.41159-36.60795-16.21776-42.68052-5.773-7.29409-17.61108-4.11077-18.62815,5.13562q-.01476.13428-.02884.26849,1.07082.60411,2.0964,1.28237a5.12707,5.12707,0,0,1-2.06713,9.33031l-.10452.01613c-9.55573,13.64367,21.07745,49.1547,28.74518,41.18139a125.11045,125.11045,0,0,0-6.73449,31.69282,118.66429,118.66429,0,0,0,.08607,19.15986l-.03231-.22593C558.90163,648.154,529.674,627.51374,521.139,629.233c-4.91675.99041-9.75952.76525-9.01293,5.72484q.01788.11874.03635.2375a34.4418,34.4418,0,0,1,3.862,1.86105q1.07082.60423,2.09639,1.28237a5.12712,5.12712,0,0,1-2.06712,9.33039l-.10464.01606c-.07528.01079-.13987.02157-.21507.03237-4.34967,14.96631,27.90735,39.12,47.5177,31.43461h.01081a125.07484,125.07484,0,0,0,8.402,24.52806H601.679c.10765-.3335.20443-.67779.3013-1.01129a34.102,34.102,0,0,1-8.30521-.49477c2.22693-2.73257,4.45377-5.48664,6.6807-8.21913a1.86122,1.86122,0,0,0,.13986-.16135c1.12956-1.39849,2.26992-2.78627,3.39948-4.18476l.00061-.00173a49.95232,49.95232,0,0,0-1.46367-12.72495Zm-34.37066-67.613.0158-.02133-.0158.04282Zm-6.64832,59.93237-.25822-.58084c.01079-.41957.01079-.83914,0-1.26942,0-.11845-.0215-.23672-.0215-.35508.09678.74228.18285,1.48464.29042,2.22692Z"
|
||||
fill="var(--fms-text)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<circle cx="95.24878" cy="439" fill="var(--fms-text)" r="11" />
|
||||
<circle cx="227.24878" cy="559" fill="var(--fms-text)" r="11" />
|
||||
<circle cx="728.24878" cy="559" fill="var(--fms-text)" r="11" />
|
||||
<circle cx="755.24878" cy="419" fill="var(--fms-text)" r="11" />
|
||||
<circle cx="723.24878" cy="317" fill="var(--fms-text)" r="11" />
|
||||
<path
|
||||
d="M434.1831,583.426a10.949,10.949,0,1,1-.21-2.16A10.9921,10.9921,0,0,1,434.1831,583.426Z"
|
||||
fill="var(--fms-text)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<circle cx="484.24878" cy="349" fill="var(--fms-text)" r="11" />
|
||||
<path
|
||||
d="M545.1831,513.426a10.949,10.949,0,1,1-.21-2.16A10.9921,10.9921,0,0,1,545.1831,513.426Z"
|
||||
fill="var(--fms-text)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<path
|
||||
d="M403.1831,481.426a10.949,10.949,0,1,1-.21-2.16A10.9921,10.9921,0,0,1,403.1831,481.426Z"
|
||||
fill="var(--fms-text)"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
<circle cx="599.24878" cy="443" fill="var(--fms-text)" r="11" />
|
||||
<circle cx="426.24878" cy="338" fill="var(--fms-text)" r="16" />
|
||||
<path
|
||||
d="M1028.875,735.26666l-857.75.30733a1.19068,1.19068,0,1,1,0-2.38136l857.75-.30734a1.19069,1.19069,0,0,1,0,2.38137Z"
|
||||
fill="#cacaca"
|
||||
transform="translate(-169.93432 -164.42601)"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<svg
|
||||
height="699"
|
||||
viewBox="0 0 1119 699"
|
||||
width="1119"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<title>server down</title>
|
||||
<circle cx="292.60911" cy="213" fill="#f2f2f2" r="213" />
|
||||
<path
|
||||
d="M31.39089,151.64237c0,77.49789,48.6181,140.20819,108.70073,140.20819"
|
||||
fill="#2f2e41"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<path
|
||||
d="M140.09162,291.85056c0-78.36865,54.255-141.78356,121.30372-141.78356"
|
||||
fill="var(--fms-primary)"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<path
|
||||
d="M70.77521,158.66768c0,73.61476,31.00285,133.18288,69.31641,133.18288"
|
||||
fill="var(--fms-primary)"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<path
|
||||
d="M140.09162,291.85056c0-100.13772,62.7103-181.16788,140.20819-181.16788"
|
||||
fill="#2f2e41"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<path
|
||||
d="M117.22379,292.83905s15.41555-.47479,20.06141-3.783,23.713-7.2585,24.86553-1.95278,23.16671,26.38821,5.76263,26.5286-40.43935-2.711-45.07627-5.53549S117.22379,292.83905,117.22379,292.83905Z"
|
||||
fill="#a8a8a8"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<path
|
||||
d="M168.224,311.78489c-17.40408.14042-40.43933-2.71094-45.07626-5.53548-3.53126-2.151-4.93843-9.86945-5.40926-13.43043-.32607.014-.51463.02-.51463.02s.97638,12.43276,5.61331,15.2573,27.67217,5.67589,45.07626,5.53547c5.02386-.04052,6.7592-1.82793,6.66391-4.47526C173.87935,310.756,171.96329,311.75474,168.224,311.78489Z"
|
||||
opacity="0.2"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<ellipse cx="198.60911" cy="424.5" fill="#3f3d56" rx="187" ry="25.43993" />
|
||||
<ellipse cx="198.60911" cy="424.5" opacity="0.1" rx="157" ry="21.35866" />
|
||||
<ellipse cx="836.60911" cy="660.5" fill="#3f3d56" rx="283" ry="38.5" />
|
||||
<ellipse cx="310.60911" cy="645.5" fill="#3f3d56" rx="170" ry="23.12721" />
|
||||
<path
|
||||
d="M494,726.5c90,23,263-30,282-90"
|
||||
fill="none"
|
||||
stroke="#2f2e41"
|
||||
stroke-miterlimit="10"
|
||||
stroke-width="2"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<path
|
||||
d="M341,359.5s130-36,138,80-107,149-17,172"
|
||||
fill="none"
|
||||
stroke="#2f2e41"
|
||||
stroke-miterlimit="10"
|
||||
stroke-width="2"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<path
|
||||
d="M215.40233,637.78332s39.0723-10.82,41.47675,24.04449-32.15951,44.78287-5.10946,51.69566"
|
||||
fill="none"
|
||||
stroke="#2f2e41"
|
||||
stroke-miterlimit="10"
|
||||
stroke-width="2"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<path
|
||||
d="M810.09554,663.73988,802.218,714.03505s-38.78182,20.60284-11.51335,21.20881,155.73324,0,155.73324,0,24.84461,0-14.54318-21.81478l-7.87756-52.719Z"
|
||||
fill="#2f2e41"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<path
|
||||
d="M785.21906,734.69812c6.193-5.51039,16.9989-11.252,16.9989-11.252l7.87756-50.2952,113.9216.10717,7.87756,49.582c9.185,5.08711,14.8749,8.987,18.20362,11.97818,5.05882-1.15422,10.58716-5.44353-18.20362-21.38921l-7.87756-52.719-113.9216,3.02983L802.218,714.03506S769.62985,731.34968,785.21906,734.69812Z"
|
||||
opacity="0.1"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<rect
|
||||
fill="#2f2e41"
|
||||
height="357.51989"
|
||||
rx="18.04568"
|
||||
width="513.25314"
|
||||
x="578.43291"
|
||||
y="212.68859"
|
||||
/>
|
||||
<rect fill="#3f3d56" height="267.83694" width="478.71308" x="595.70294" y="231.77652" />
|
||||
<circle cx="835.05948" cy="223.29299" fill="#f2f2f2" r="3.02983" />
|
||||
<path
|
||||
d="M1123.07694,621.32226V652.6628a18.04341,18.04341,0,0,1-18.04568,18.04568H627.86949A18.04341,18.04341,0,0,1,609.8238,652.6628V621.32226Z"
|
||||
fill="#2f2e41"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<polygon
|
||||
fill="#2f2e41"
|
||||
points="968.978 667.466 968.978 673.526 642.968 673.526 642.968 668.678 643.417 667.466 651.452 645.651 962.312 645.651 968.978 667.466"
|
||||
/>
|
||||
<path
|
||||
d="M1125.828,762.03359c-.59383,2.539-2.83591,5.21743-7.90178,7.75032-18.179,9.08949-55.1429-2.42386-55.1429-2.42386s-28.4804-4.84773-28.4804-17.573a22.72457,22.72457,0,0,1,2.49658-1.48459c7.64294-4.04351,32.98449-14.02122,77.9177.42248a18.73921,18.73921,0,0,1,8.54106,5.59715C1125.07908,756.45353,1126.50669,759.15715,1125.828,762.03359Z"
|
||||
fill="#2f2e41"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<path
|
||||
d="M1125.828,762.03359c-22.251,8.526-42.0843,9.1622-62.43871-4.975-10.26507-7.12617-19.59089-8.88955-26.58979-8.75618,7.64294-4.04351,32.98449-14.02122,77.9177.42248a18.73921,18.73921,0,0,1,8.54106,5.59715C1125.07908,756.45353,1126.50669,759.15715,1125.828,762.03359Z"
|
||||
opacity="0.1"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<ellipse cx="1066.53846" cy="654.13477" fill="#f2f2f2" rx="7.87756" ry="2.42386" />
|
||||
<circle cx="835.05948" cy="545.66686" fill="#f2f2f2" r="11.51335" />
|
||||
<polygon
|
||||
opacity="0.1"
|
||||
points="968.978 667.466 968.978 673.526 642.968 673.526 642.968 668.678 643.417 667.466 968.978 667.466"
|
||||
/>
|
||||
<rect fill="#2f2e41" height="242" width="208" x="108.60911" y="159" />
|
||||
<rect fill="#3f3d56" height="86" width="250" x="87.60911" y="135" />
|
||||
<rect fill="#3f3d56" height="86" width="250" x="87.60911" y="237" />
|
||||
<rect fill="#3f3d56" height="86" width="250" x="87.60911" y="339" />
|
||||
<rect fill="#6c63ff" height="16" opacity="0.4" width="16" x="271.60911" y="150" />
|
||||
<rect fill="#6c63ff" height="16" opacity="0.8" width="16" x="294.60911" y="150" />
|
||||
<rect fill="#6c63ff" height="16" width="16" x="317.60911" y="150" />
|
||||
<rect fill="#6c63ff" height="16" opacity="0.4" width="16" x="271.60911" y="251" />
|
||||
<rect fill="#6c63ff" height="16" opacity="0.8" width="16" x="294.60911" y="251" />
|
||||
<rect fill="#6c63ff" height="16" width="16" x="317.60911" y="251" />
|
||||
<rect fill="#6c63ff" height="16" opacity="0.4" width="16" x="271.60911" y="352" />
|
||||
<rect fill="#6c63ff" height="16" opacity="0.8" width="16" x="294.60911" y="352" />
|
||||
<rect fill="#6c63ff" height="16" width="16" x="317.60911" y="352" />
|
||||
<circle cx="316.60911" cy="538" fill="#2f2e41" r="79" />
|
||||
<rect fill="#2f2e41" height="43" width="24" x="280.60911" y="600" />
|
||||
<rect fill="#2f2e41" height="43" width="24" x="328.60911" y="600" />
|
||||
<ellipse cx="300.60911" cy="643.5" fill="#2f2e41" rx="20" ry="7.5" />
|
||||
<ellipse cx="348.60911" cy="642.5" fill="#2f2e41" rx="20" ry="7.5" />
|
||||
<circle cx="318.60911" cy="518" fill="#fff" r="27" />
|
||||
<circle cx="318.60911" cy="518" fill="#3f3d56" r="9" />
|
||||
<path
|
||||
d="M271.36733,565.03228c-6.37889-28.56758,14.01185-57.43392,45.544-64.47477s62.2651,10.41,68.644,38.9776-14.51861,39.10379-46.05075,46.14464S277.74622,593.59986,271.36733,565.03228Z"
|
||||
fill="#6c63ff"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="417.21511"
|
||||
cy="611.34365"
|
||||
fill="#2f2e41"
|
||||
rx="39.5"
|
||||
ry="12.40027"
|
||||
transform="translate(-238.28665 112.98044) rotate(-23.17116)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="269.21511"
|
||||
cy="664.34365"
|
||||
fill="#2f2e41"
|
||||
rx="39.5"
|
||||
ry="12.40027"
|
||||
transform="translate(-271.07969 59.02084) rotate(-23.17116)"
|
||||
/>
|
||||
<path
|
||||
d="M394,661.5c0,7.732-19.90861,23-42,23s-43-14.268-43-22,20.90861-6,43-6S394,653.768,394,661.5Z"
|
||||
fill="#fff"
|
||||
transform="translate(-31.39089 -100.5)"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<svg
|
||||
height="458.68642"
|
||||
viewBox="0 0 656 458.68642"
|
||||
width="656"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<rect fill="#3f3d56" height="2" width="656" y="434.34322" />
|
||||
<g>
|
||||
<path
|
||||
d="M471.97092,210.81397c-6.0733-36.41747-37.72842-64.16942-75.86423-64.16942H240.14931c-38.12099,0-69.76869,27.72972-75.86421,64.12497-.70358,4.16241-1.06653,8.44331-1.06653,12.80573v135.88599c0,4.36237,.36295,8.63589,1.06653,12.79831,4.85126,28.99625,25.92996,52.49686,53.58563,60.84393,7.05095,2.13306,14.53143,3.28104,22.27859,3.28104h155.9574c7.74716,0,15.22763-1.14798,22.27859-3.28104,27.66309-8.35449,48.74921-31.86993,53.58563-60.88837,.6962-4.14758,1.05911-8.40628,1.05911-12.75388V223.57525c0-4.34758-.36292-8.61369-1.05911-12.76128h-.00003Zm-62.66592,222.28954c-4.2883,.76285-8.69516,1.16281-13.19827,1.16281H240.14931c-4.50313,0-8.90997-.39999-13.19829-1.16281-35.01768-6.22885-61.60677-36.83228-61.60677-73.64224v-45.10526c0-127.45004,103.31242-165.58582,230.76244-165.58582,41.31314,0,74.80505,33.49194,74.80505,74.80505v135.88599c-100.29059,13.42047-26.58911,67.41339-61.60678,73.64224l.00003,.00003Z"
|
||||
fill="#3f3d56"
|
||||
/>
|
||||
<polygon
|
||||
fill="var(--fms-primary)"
|
||||
points="349.16196 249.18644 355.16196 288.18642 443.16196 276.18642 434.66196 230.6195 349.16196 249.18644"
|
||||
/>
|
||||
<rect fill="#2f2e41" height="37.66125" width="36.38461" x="381.84177" y="30.34218" />
|
||||
<polygon
|
||||
fill="#ffb6b6"
|
||||
points="385.16196 70.18643 394.16196 43.18643 411.70447 43.18643 412.62653 70.18643 385.16196 70.18643"
|
||||
/>
|
||||
<polygon
|
||||
isolation="isolate"
|
||||
opacity=".1"
|
||||
points="385.16196 70.18643 394.16196 43.18643 411.70447 43.18643 412.62653 70.18643 385.16196 70.18643"
|
||||
/>
|
||||
<path
|
||||
d="M394.66196,310.68642l-1,104-1,8v11.48425l15,1.51575,1-23s16-45,12-80-2-25-2-25l-24,3Z"
|
||||
fill="#ffb6b6"
|
||||
/>
|
||||
<path
|
||||
d="M404.18408,318.85363l-36.90134,97.23831-1.97873,7.81567-4.1777,10.69742-14.52368-4.04477,7.43539-21.78796s1.46619-47.7373,17.92432-78.88422,10.9574-22.5596,10.9574-22.5596l21.26434,11.52512v.00003Z"
|
||||
fill="#ffb6b6"
|
||||
/>
|
||||
<path
|
||||
d="M385.16196,67.18643l-27,12,17.23959,89.01208-2.72385,127.75565-18,38s-3.01575,21.73227,27.98425,7.73227,66-18,66-18l-8.5-58.5-7.5-153.5,1-34-22-14s-26.5,3.5-26.5,3.50001Z"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<path
|
||||
d="M370.1243,335.34322l-29.96231-50.15677,34.23959-116.98792-16.23959-89.01208,28.49045-12.19685s14.74915,14.36248,14.74915,26.20894-31.27728,242.1447-31.27728,242.1447v-.00003Z"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M435.1243,325.34322l-27.19693-233.62811c-.34341-2.94999,.16013-5.93678,1.45178-8.6111l7.78284-16.11441,30.5,8.69685-12.26041,95.51208,32.76041,93.98792-33.03769,60.15677Z"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<path
|
||||
d="M410.66196,433.68642s-19-11-21-5-3,11-3,11c0,0-5,19,10,19s14-8.64172,14-8.64172v-16.35828Z"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<path
|
||||
d="M344.53574,427.60598s21.69977-3.33459,21.3801,2.9819c-.3197,6.31647-1.20709,11.33768-1.20709,11.33768,0,0-2.25433,19.51712-16.22662,14.06046s-9.89713-13.14252-9.89713-13.14252l5.95078-15.23749-.00003-.00003Z"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<circle cx="404.10297" cy="33.02146" fill="#ffb6b6" r="24.85993" />
|
||||
<path
|
||||
d="M423.96469,10.86766c-1.15707-6.12936-7.44913-10.27514-13.66504-10.79501s-12.30453,1.82726-17.90228,4.57921c-3.79456,1.86548-7.53061,3.96811-10.60425,6.87182s-5.46063,6.69692-6.01202,10.88913c-.19507,1.48324-.1698,3.03289-.77692,4.40016-.75845,1.708-2.38654,2.86795-3.36917,4.4576-1.76227,2.85096-.95267,6.99858,1.75238,8.97753-3.40024,1.44912-6.89398,2.96069-9.48602,5.59563s-4.08878,6.70308-2.66644,10.11462c.50323,1.20699,1.33481,2.26349,1.76489,3.49843,.81668,2.34499,.03943,5.00909-1.40924,7.02585s-3.49316,3.51228-5.50174,4.97226c5.16196,1.01177,10.43097,1.80015,15.66992,1.32811s10.49707-2.30805,14.29086-5.95176c3.79379-3.64371,5.88083-9.26437,4.51974-14.34539-1.04269-3.89231-3.95898-7.30301-3.95712-11.33256,.00143-3.09747,1.7431-5.89158,3.4249-8.49271,3.67291-5.68066,7.34579-11.36132,11.01868-17.04197,.66068-1.02183,1.35739-2.07924,2.4014-2.70425,1.77606-1.06326,4.0798-.59568,5.95227,.28683,1.87244,.88252,3.58304,2.14867,5.57941,2.69585,4.07452,1.11677,8.80106-1.44789,10.08575-5.47261"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<path
|
||||
d="M409.27951,61.42523c-2.07159,2.0061-5.05701,2.65225-7.82379,3.46516s-5.70978,2.09141-6.95499,4.69243c-1.22101,2.55043-.33459,5.78793,1.68692,7.76505s4.95816,2.80999,7.78555,2.77077c2.82736-.03922,5.58282-.86796,8.24176-1.8301,7.27054-2.63087,14.15665-6.32148,20.37314-10.919-4.02679-1.11411-6.66107-5.81614-5.50836-9.83205,.93768-3.26677,3.80499-5.54528,5.75616-8.32809,3.35959-4.79151,3.91925-11.10753,2.80676-16.85277-1.11246-5.74524-3.73163-11.07097-6.32358-16.3176-.81934-1.65853-1.65805-3.34513-2.93619-4.68245-1.27814-1.33731-3.08783-2.29539-4.92776-2.10379-3.05334,.31795-5.00302,3.66989-5.02377,6.7397s1.32593,5.95491,2.34732,8.84988c1.05231,2.98259,1.78381,6.14409,1.50146,9.29425-.2366,2.63989-1.19669,5.21132-2.74811,7.36029-1.19809,1.65954-2.72479,3.05223-4.0275,4.63097-1.00714,1.22055-1.90009,2.60309-2.16486,4.16321-.48181,2.83914,1.18356,5.71186,.72714,8.55519-.48248,3.0056-3.6452,5.3067-6.65341,4.84085"
|
||||
fill="#2f2e41"
|
||||
/>
|
||||
<g>
|
||||
<circle cx="333.2486" cy="323.64455" fill="var(--fms-primary)" r="85" />
|
||||
<g>
|
||||
<path
|
||||
d="M384.17838,316.82296h-10.56668c-1.64377-9.68713-6.7168-18.46011-14.2923-24.71729-17.43427-14.39993-43.24109-11.94022-57.64099,5.49411-.04913,.05563-.09644,.11282-.14169,.17151-1.15063,1.49146-.87427,3.63333,.61716,4.784,1.49118,1.1507,3.63306,.87448,4.78394-.61697,6.25537-7.5788,15.72369-12.40167,26.31064-12.40167,16.20853,.00195,30.17899,11.40631,33.42572,27.28629h-9.31805c-.3988,.00012-.78458,.13992-1.09082,.39502-.72375,.60281-.82175,1.6781-.21915,2.40186l13.41125,16.09894c.06577,.07889,.13855,.1517,.21759,.21747,.72324,.60327,1.79871,.50583,2.40186-.21747l13.41125-16.09894c.25504-.30624,.3949-.69223,.39514-1.09082,.00027-.94186-.763-1.70566-1.70486-1.70605v.00003Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<path
|
||||
d="M364.34329,344.7337c-1.49146-1.15063-3.63333-.87433-4.78394,.6171-4.96201,6.00781-11.83066,10.13629-19.46436,11.69922-18.46167,3.77988-36.49231-8.12213-40.27225-26.58392h9.3183c.94186-.0004,1.70514-.76419,1.70486-1.70605-.00027-.39853-.14011-.78452-.39514-1.09082l-13.41125-16.09888c-.60312-.72336-1.67862-.8208-2.40186-.21753-.07904,.06577-.15182,.13855-.21759,.21753l-13.41125,16.09888c-.6026,.72375-.50461,1.7991,.21915,2.40186,.30624,.25516,.69205,.3949,1.09082,.39502h10.56641c1.64404,9.68723,6.7168,18.46011,14.29254,24.71729,17.43427,14.39999,43.24109,11.94022,57.64099-5.49405,.04913-.05569,.09619-.11295,.14142-.17163,1.15088-1.49146,.87454-3.63327-.61691-4.784h.00006Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
id="uuid-da16df1e-5659-4232-96f6-61e8c639a9ec-574"
|
||||
d="M356.98148,237.19363c-1.02939,7.36621-5.66458,12.80598-10.35239,12.15012-4.68781-.65588-7.65225-7.15837-6.62149-14.52707,.37137-2.94914,1.4436-5.76646,3.12701-8.21626l4.75577-31.15587,14.57297,2.54338-6.23553,30.44414c.94736,2.81844,1.20581,5.82278,.75369,8.76157h-.00003Z"
|
||||
fill="#ffb6b6"
|
||||
/>
|
||||
<path
|
||||
d="M369.66196,77.68643s-15-5-17,13-4,39.99999-4,39.99999c0,0-9,21-5,32s11,3.3307,4,12.66534-6.02478,40.04724-6.02478,40.04724l22.52478-1.13387s12.5-82.57875,12.5-84.57875-7-52-7-52v.00004Z"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
id="uuid-6bf35aa9-e432-4b51-af77-8f4eb19e6e42-575"
|
||||
d="M467.16132,233.84998c.27881,7.43257-3.33017,13.60114-8.06033,13.7778s-8.78937-5.70491-9.06732-13.14017c-.15176-2.96857,.40961-5.93028,1.63712-8.63741l-.78369-31.507,14.79315-.05261-.798,31.0659c1.42709,2.60854,2.20859,5.52095,2.27905,8.49347l.00003,.00002Z"
|
||||
fill="#ffb6b6"
|
||||
/>
|
||||
<path
|
||||
d="M444.06961,77.34876s15.08694-4.73121,16.76505,13.30165,3.28473,51.06508,3.28473,51.06508c0,0,8.62338,21.15744,4.42749,32.08421s-11.05774,3.13365-4.22565,12.59187c6.83212,9.45822,4.37997,36.13126,4.37997,36.13126l-22.50095-1.53612s-10.09427-78.77167-10.05853-80.77133,7.92792-62.86664,7.92792-62.86664l-.00003,.00002Z"
|
||||
fill="#e6e6e6"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -182,7 +182,7 @@ async function batchDelete() {
|
||||
<Plus :size="14" /> 新增
|
||||
</Button>
|
||||
<Button
|
||||
type="danger"
|
||||
type="danger-outline"
|
||||
:disabled="loading || saving || !selectedRowKeys.length"
|
||||
@click="batchDelete"
|
||||
>
|
||||
|
||||
@@ -115,7 +115,11 @@ const columns = computed(() => {
|
||||
<Button type="primary" :disabled="loading" @click="addRow"
|
||||
><Plus :size="14" /> 新增多语言</Button
|
||||
>
|
||||
<Button type="danger" :disabled="loading || !selectedRowKeys.length" @click="batchDelete">
|
||||
<Button
|
||||
type="danger-outline"
|
||||
:disabled="loading || !selectedRowKeys.length"
|
||||
@click="batchDelete"
|
||||
>
|
||||
<Trash2 :size="14" /> 批量删除{{
|
||||
selectedRowKeys.length ? `(${selectedRowKeys.length})` : ''
|
||||
}}
|
||||
|
||||
@@ -260,7 +260,11 @@ const columns = shallowRef([
|
||||
<Button type="primary" :disabled="loading" @click="addRow"
|
||||
><Plus :size="14" /> 新增查询条件</Button
|
||||
>
|
||||
<Button type="danger" :disabled="loading || !selectedRowKeys.length" @click="batchDelete">
|
||||
<Button
|
||||
type="danger-outline"
|
||||
:disabled="loading || !selectedRowKeys.length"
|
||||
@click="batchDelete"
|
||||
>
|
||||
<Trash2 :size="14" /> 批量删除
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -364,7 +364,11 @@ defineExpose({ validate })
|
||||
<Button type="primary" :disabled="loading" @click="addRow">
|
||||
<Plus :size="14" /> 新增关联
|
||||
</Button>
|
||||
<Button type="danger" :disabled="loading || !selectedRowKeys.length" @click="batchDelete">
|
||||
<Button
|
||||
type="danger-outline"
|
||||
:disabled="loading || !selectedRowKeys.length"
|
||||
@click="batchDelete"
|
||||
>
|
||||
<Trash2 :size="14" /> 批量删除
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -173,7 +173,11 @@ defineExpose({ validate })
|
||||
<Button type="primary" :disabled="loading" @click="openRuleModal(null)">
|
||||
<Plus :size="14" /> 新增规则
|
||||
</Button>
|
||||
<Button type="danger" :disabled="loading || !selectedRowKeys.length" @click="batchDelete">
|
||||
<Button
|
||||
type="danger-outline"
|
||||
:disabled="loading || !selectedRowKeys.length"
|
||||
@click="batchDelete"
|
||||
>
|
||||
<Trash2 :size="14" /> 批量删除
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
+3
-2
@@ -2351,14 +2351,15 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.mm-fdesign__field-control {
|
||||
min-height: 26px;
|
||||
min-height: var(--fms-control-height);
|
||||
}
|
||||
|
||||
/* 画布内真实控件(纯视觉预览:不置 disabled,避免灰态;pointer-events:none 穿透到卡片本体,
|
||||
tabindex=-1 防止 Tab 聚焦) */
|
||||
.mm-fdesign__ctrl {
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
/* 画布内预览与真实控件同高:高度跟随控件档位 */
|
||||
height: var(--fms-control-height);
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 4px;
|
||||
|
||||
+2
-1
@@ -315,7 +315,8 @@ function removeSelected() {
|
||||
.mm-fdesign__prop-static {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
/* 静态值框是输入框外观的只读替身,高度跟随控件档位 */
|
||||
min-height: var(--fms-control-height);
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 4px;
|
||||
|
||||
+2
-1
@@ -329,7 +329,8 @@ function isOn(field) {
|
||||
.mm-fdesign__prop-static {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
/* 静态值框是输入框外观的只读替身,高度跟随控件档位 */
|
||||
min-height: var(--fms-control-height);
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -594,7 +594,7 @@ void loadDepts()
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canDeleteRow"
|
||||
type="danger"
|
||||
type="danger-outline"
|
||||
:disabled="loading || rowSaving || !selectedRowKeys.length"
|
||||
@click="confirmBatchDelete"
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user