mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-16 19:32:00 +08:00
【新增】部署类型七牛云oss、七牛云cdn、百度cdn、腾讯waf、腾讯edgeone、阿里云waf
【新增】解析类型godaddy 【新增】自定义CA授权管理 【调整】优化部署流程,减少代码冗余,提升类型添加效率
This commit is contained in:
@@ -1,77 +1,120 @@
|
||||
/* 布局容器样式 */
|
||||
.layoutContainer {
|
||||
@apply min-h-screen flex flex-col;
|
||||
background-color: var(--n-color); /* Naive UI 主题背景色 */
|
||||
}
|
||||
|
||||
/* 侧边栏样式 */
|
||||
.sider {
|
||||
@apply h-screen shadow-lg z-10 transition-all duration-300 ease-in-out;
|
||||
@apply h-screen shadow-lg z-10 transition-all duration-300 ease-in-out;
|
||||
/* 移动端默认行为通过 NLayoutSider 的 collapsed 属性和 TSX 逻辑控制 */
|
||||
}
|
||||
|
||||
/* Logo容器样式 */
|
||||
.logoContainer {
|
||||
@apply flex items-center px-[2rem] h-[var(--n-sider-login-height)] border-b relative;
|
||||
@apply flex items-center h-[var(--n-sider-login-height)] border-b relative;
|
||||
@apply px-3 sm:px-4 md:px-6; /* 统一响应式内边距 */
|
||||
border-color: var(--n-border-color);
|
||||
}
|
||||
|
||||
/* Logo容器激活样式 */
|
||||
.logoContainer span {
|
||||
@apply text-nowrap overflow-hidden overflow-ellipsis w-[10rem];
|
||||
/* Logo容器内文字样式 (用于控制展开时的文字) */
|
||||
.logoContainer span.logoText { /* 更具体的选择器 */
|
||||
@apply text-nowrap overflow-hidden overflow-ellipsis;
|
||||
@apply w-full sm:w-auto; /* 在小屏幕上允许文字占据更多空间或换行,大屏幕自适应 */
|
||||
color: var(--n-text-color-base);
|
||||
/* 响应式字体大小 */
|
||||
@apply text-base sm:text-lg md:text-xl; /* 例如: 1rem, 1.125rem, 1.25rem */
|
||||
}
|
||||
|
||||
/* Logo容器文本样式 */
|
||||
/* Logo容器文本整体 (当侧边栏展开时) */
|
||||
.logoContainerText {
|
||||
@apply flex items-center w-[20rem];
|
||||
@apply flex items-center w-full; /* 确保在各种情况下都能正确对齐 */
|
||||
}
|
||||
|
||||
/* Logo容器激活样式 */
|
||||
/* Logo容器激活(折叠时)样式 */
|
||||
.logoContainerActive {
|
||||
@apply flex items-center justify-center px-[0];
|
||||
@apply flex items-center justify-center px-0; /* 折叠时无内边距,确保图标居中 */
|
||||
}
|
||||
|
||||
/* 折叠图标激活样式 */
|
||||
.collapsedIconActive {
|
||||
@apply !relative left-0 px-[1rem];
|
||||
/* 折叠/展开图标容器 (原 .collapsedIcon) */
|
||||
.menuToggleButton {
|
||||
@apply h-[3.6rem] absolute rounded-[.4rem] flex items-center justify-center cursor-pointer transition-all duration-300;
|
||||
@apply right-2 sm:right-3 md:right-4 px-2; /* 统一响应式定位和内边距 */
|
||||
color: var(--n-text-color-2); /* Naive UI 次要文字颜色 */
|
||||
}
|
||||
.menuToggleButton:hover {
|
||||
background-color: var(--n-action-color); /* Naive UI 交互元素背景色 */
|
||||
color: var(--n-text-color-1); /* Naive UI 主要文字颜色 */
|
||||
}
|
||||
|
||||
/* 折叠按钮样式 */
|
||||
.collapsedIcon {
|
||||
@apply w-[4.2rem] h-[3.6rem] hover:bg-gray-200 hover:text-gray-500 absolute right-[1rem] rounded-[.4rem] flex items-center justify-center cursor-pointer transition-all duration-300;
|
||||
/* 新增:头部菜单切换按钮样式 */
|
||||
.headerMenuToggleButton {
|
||||
@apply flex items-center justify-center cursor-pointer rounded-md p-2; /* p-2 提供 8px 内边距 */
|
||||
color: var(--n-text-color-2); /* 默认图标颜色 (由 NIcon 继承) */
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.headerMenuToggleButton:hover {
|
||||
background-color: var(--n-action-color); /* 悬浮背景色 */
|
||||
color: var(--n-text-color-1); /* 悬浮图标颜色 (由 NIcon 继承) */
|
||||
}
|
||||
|
||||
/* 折叠图标激活(折叠时)样式 (原 .collapsedIconActive) - 如果还需要特殊处理折叠时的按钮样式 */
|
||||
/* .menuToggleButtonActive { ... } */
|
||||
|
||||
|
||||
/* 头部样式 */
|
||||
.header {
|
||||
@apply h-[var(--n-header-height)] bg-[var(--n-header-color)] border-b shadow-sm z-10 transition-all duration-300 ease-in-out flex items-center justify-end px-6;
|
||||
@apply h-[var(--n-header-height)] border-b shadow-sm z-10 transition-all duration-300 ease-in-out flex items-center justify-end;
|
||||
background-color: var(--n-header-color);
|
||||
border-color: var(--n-border-color);
|
||||
@apply px-3 sm:px-4 md:px-6; /* 统一响应式内边距 */
|
||||
}
|
||||
|
||||
/* 系统信息样式 */
|
||||
.systemInfo {
|
||||
@apply flex items-center space-x-4 text-[1.2rem] text-gray-600 ;
|
||||
@apply flex items-center space-x-2 sm:space-x-3 md:space-x-4 text-[1.4rem];
|
||||
color: var(--n-text-color-secondary); /* Naive UI 次级文字颜色 */
|
||||
}
|
||||
|
||||
|
||||
/* 内容区域样式 */
|
||||
.content {
|
||||
@apply flex-1 transition-all duration-300 ease-in-out p-[var(--n-content-padding)] h-[calc(100vh-var(--n-main-diff-height))] bg-[var(--n-layout-content-background-color)] overflow-y-auto;
|
||||
@apply flex-1 transition-all duration-300 ease-in-out h-[calc(100vh-var(--n-main-diff-height))] overflow-y-auto p-[var(--n-content-padding)] sm:p-0 md:p-0;
|
||||
background-color: var(--n-layout-content-background-color);
|
||||
transition: padding 0s;
|
||||
}
|
||||
|
||||
/* 折叠按钮样式 */
|
||||
.collapseButton {
|
||||
@apply absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 rounded-full p-2 shadow-lg cursor-pointer hover:bg-gray-100 transition-all duration-300;
|
||||
/* 移动端视图 */
|
||||
.siderMobileOpen {
|
||||
@apply fixed top-0 left-0 h-full shadow-xl;
|
||||
z-index: 1050; /* 确保在顶层 */
|
||||
background-color: var(--n-sider-color, var(--n-body-color)); /* 匹配侧边栏背景色 */
|
||||
transform: translateX(0);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
/* 宽度由 NLayoutSider 的 width prop 控制 */
|
||||
}
|
||||
|
||||
/* 子路由导航样式 */
|
||||
.subRouteNav {
|
||||
@apply mb-4 p-4 rounded-lg shadow-sm;
|
||||
.siderMobileClosed {
|
||||
@apply fixed top-0 left-0 h-full;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
/* 宽度由 NLayoutSider 的 collapsedWidth prop 控制,但由于移出屏幕,实际宽度不重要 */
|
||||
}
|
||||
|
||||
/* 子路由标题样式 */
|
||||
.subRouteTitle {
|
||||
@apply text-lg font-medium mb-2 text-gray-700 ;
|
||||
/* Mobile Menu Backdrop */
|
||||
.mobileMenuBackdrop {
|
||||
@apply fixed inset-0 bg-black bg-opacity-50;
|
||||
z-index: 1040; /* 在侧边栏下方,内容区域上方 */
|
||||
}
|
||||
|
||||
/* 子路由面包屑样式 */
|
||||
.breadcrumb {
|
||||
@apply p-3 rounded-lg shadow-sm mb-4;
|
||||
|
||||
/* 针对 1100px 以下屏幕的样式调整 */
|
||||
@media (max-width: 1100px) {
|
||||
.header {
|
||||
@apply px-2 sm:px-3;
|
||||
}
|
||||
.content {
|
||||
@apply p-2 sm:p-3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +1,207 @@
|
||||
import { Transition, type Component as ComponentType, h } from 'vue'
|
||||
// 外部库依赖
|
||||
import { Transition, type Component as ComponentType, h, defineComponent, ref, onMounted, computed, watch } from 'vue' // 添加 watch
|
||||
import { NBadge, NIcon, NLayout, NLayoutContent, NLayoutHeader, NLayoutSider, NMenu, NTooltip } from 'naive-ui'
|
||||
import { RouterView } from 'vue-router'
|
||||
|
||||
import { $t } from '@locales/index'
|
||||
import { useThemeCssVar } from '@baota/naive-ui/theme'
|
||||
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@vicons/antd'
|
||||
// import ThemeTips from '@baota/naive-ui/components/themeTips'
|
||||
import { useMediaQuery } from '@vueuse/core' // 引入 useMediaQuery
|
||||
|
||||
// 内部模块导入 - Hooks/Composables
|
||||
import { useThemeCssVar } from '@baota/naive-ui/theme'
|
||||
import { useController } from './useController'
|
||||
// 内部模块导入 - 工具函数
|
||||
import { $t } from '@locales/index'
|
||||
// 内部模块导入 - 样式
|
||||
import styles from './index.module.css'
|
||||
|
||||
|
||||
/**
|
||||
* @description 基础布局组件,包含侧边栏导航、头部信息和内容区域。
|
||||
* @component LayoutView
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'LayoutView',
|
||||
setup() {
|
||||
// 获取控制器中的状态和方法
|
||||
const { menuItems, menuActive, isCollapsed, toggleCollapse, handleExpand, handleCollapse, updateMenuActive } =
|
||||
useController()
|
||||
// 获取主题变量
|
||||
const cssVars = useThemeCssVar(['cardColor', 'headerColor', 'contentColor'])
|
||||
// 确保所有需要的颜色变量都已在 useThemeCssVar 中声明,或直接在 CSS Module 中使用 var(--n-...)
|
||||
const cssVars = useThemeCssVar([
|
||||
'bodyColor', // --n-color 通常是 bodyColor
|
||||
'headerColor',
|
||||
'borderColor',
|
||||
'textColorBase',
|
||||
'textColor1',
|
||||
'textColor2',
|
||||
'textColorSecondary',
|
||||
'actionColor',
|
||||
'layoutContentBackgroundColor',
|
||||
'siderLoginHeight', // 确保这个变量在 Naive UI 主题中存在或已自定义
|
||||
'contentPadding'
|
||||
])
|
||||
|
||||
const siderWidth = ref(200)
|
||||
const siderCollapsedWidth = ref(60)
|
||||
|
||||
// 将断点从 768px 调整为 1100px
|
||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||
const isNarrowScreen = useMediaQuery('(max-width: 1100px)')
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化时根据屏幕宽度设置菜单状态
|
||||
if (isMobile.value || isNarrowScreen.value) {
|
||||
isCollapsed.value = true
|
||||
}
|
||||
})
|
||||
|
||||
// 监听屏幕宽度变化,自动折叠/展开菜单
|
||||
watch(isNarrowScreen, (newValue) => {
|
||||
if (newValue && !isMobile.value) { // 仅在非移动设备且宽度小于1100px时处理
|
||||
isCollapsed.value = true
|
||||
} else if (!newValue && !isMobile.value) { // 宽度大于1100px且非移动设备时
|
||||
isCollapsed.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// 控制 NLayoutSider 组件的 'collapsed' prop
|
||||
// 在移动端,我们希望 NLayoutSider 始终保持其展开时的宽度,
|
||||
// 通过 CSS transform 控制其显示/隐藏,以避免 Naive UI 自身的宽度过渡动画。
|
||||
const nLayoutSiderCollapsedProp = computed(() => {
|
||||
if (isMobile.value) {
|
||||
return false // 在移动端,阻止 NLayoutSider 因 collapsed 变化而产生的宽度动画
|
||||
}
|
||||
return isCollapsed.value // 桌面端按正常逻辑处理
|
||||
})
|
||||
|
||||
// 控制 NLayoutSider 内部 NMenu 组件的 'collapsed' prop
|
||||
// 这决定了菜单项是显示为图标还是图标加文字。
|
||||
// 在移动端,当侧边栏滑出隐藏时 (isCollapsed.value 为 true),菜单也应处于折叠状态。
|
||||
// 当侧边栏滑入显示时 (!isCollapsed.value 为 true),菜单应处于展开状态。
|
||||
// 桌面端则直接跟随 isCollapsed.value。
|
||||
// 因此,此计算属性直接返回 isCollapsed.value 即可。
|
||||
const nMenuCollapsedProp = computed(() => {
|
||||
return isCollapsed.value
|
||||
})
|
||||
|
||||
// 动态计算 NLayoutSider 的 class,用于移动端的滑入滑出动画
|
||||
const siderDynamicClass = computed(() => {
|
||||
if (isMobile.value) {
|
||||
// 当 !isCollapsed.value (菜单逻辑上应为打开状态) 时,应用滑入样式
|
||||
// 当 isCollapsed.value (菜单逻辑上应为关闭状态) 时,应用滑出样式
|
||||
return !isCollapsed.value ? styles.siderMobileOpen : styles.siderMobileClosed
|
||||
}
|
||||
return '' // 桌面端不需要此动态 class
|
||||
})
|
||||
|
||||
const showBackdrop = computed(() => isMobile.value && !isCollapsed.value)
|
||||
|
||||
// NMenu 的折叠状态 (此处的 menuCollapsedState 变量名可以替换为 nMenuCollapsedProp)
|
||||
// const menuCollapsedState = computed(() => { ... }) // 旧的,将被 nMenuCollapsedProp 替代
|
||||
|
||||
return () => (
|
||||
<NLayout class={styles.layoutContainer} hasSider style={cssVars.value}>
|
||||
<NLayoutSider
|
||||
width={200}
|
||||
collapsed={isCollapsed.value}
|
||||
collapse-mode="width"
|
||||
collapsed-width={60}
|
||||
width={siderWidth.value} // 在移动端,宽度始终是展开时的宽度
|
||||
collapsed={nLayoutSiderCollapsedProp.value} // 使用新的计算属性
|
||||
showTrigger={false}
|
||||
collapseMode="width"
|
||||
collapsedWidth={siderCollapsedWidth.value} // 桌面端折叠宽度及 NMenu 折叠宽度参考
|
||||
onCollapse={handleCollapse}
|
||||
onExpand={handleExpand}
|
||||
class={styles.sider}
|
||||
class={[styles.sider, siderDynamicClass.value].join(' ')}
|
||||
bordered
|
||||
>
|
||||
<div class={styles.logoContainer + ' ' + (isCollapsed.value ? styles.logoContainerActive : '')}>
|
||||
{!isCollapsed.value ? (
|
||||
<div class={styles.logoContainerText}>
|
||||
<div class={`${styles.logoContainer} ${
|
||||
// Logo 容器的 'active' 状态 (仅在桌面端且折叠时应用)
|
||||
// 在移动端,由于 NLayoutSider 自身宽度不变,不应用 active 样式来改变 Logo 区域布局
|
||||
(isMobile.value ? false : isCollapsed.value)
|
||||
? styles.logoContainerActive
|
||||
: ''
|
||||
}`}>
|
||||
{/* Logo 显示逻辑 */}
|
||||
{(isMobile.value ? false : isCollapsed.value) ? (
|
||||
// 折叠时的 Logo (仅桌面端)
|
||||
<div class="flex items-center justify-center w-full h-full">
|
||||
<img src="/static/images/logo.png" alt="logo" class="h-8 w-8" />
|
||||
<span class="ml-4 text-[1.6rem] font-bold">{$t('t_1_1744164835667')}</span>
|
||||
</div>
|
||||
) : null}
|
||||
<NTooltip placement="right" trigger="hover">
|
||||
{{
|
||||
trigger: () => (
|
||||
<div
|
||||
class={styles.collapsedIcon + ' ' + (isCollapsed.value ? styles.collapsedIconActive : '')}
|
||||
onClick={() => toggleCollapse()}
|
||||
>
|
||||
<NIcon size={18}>{isCollapsed.value ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}</NIcon>
|
||||
</div>
|
||||
),
|
||||
default: () => <span>{isCollapsed.value ? $t('t_3_1744098802647') : $t('t_4_1744098802046')}</span>,
|
||||
}}
|
||||
</NTooltip>
|
||||
) : (
|
||||
// 展开时的 Logo (桌面端展开时,或移动端侧边栏可见时)
|
||||
<div class={styles.logoContainerText}>
|
||||
<img src="/static/images/logo.png" alt="logo" class="h-8 w-8 mr-2 sm:mr-3" />
|
||||
<span class={`${styles.logoText} ml-0 font-bold`}>{$t('t_1_1744164835667')}</span>
|
||||
</div>
|
||||
)}
|
||||
{/* 桌面端展开状态下的内部折叠按钮 */}
|
||||
{!isCollapsed.value && !isMobile.value && (
|
||||
<NTooltip placement="right" trigger="hover">
|
||||
{{
|
||||
trigger: () => (
|
||||
<div
|
||||
class={styles.menuToggleButton}
|
||||
onClick={() => toggleCollapse()}
|
||||
>
|
||||
<NIcon size={20}><MenuFoldOutlined /></NIcon> {/* 图标大小调整为 20 */}
|
||||
</div>
|
||||
),
|
||||
default: () => <span>{$t('t_4_1744098802046')}</span>,
|
||||
}}
|
||||
</NTooltip>
|
||||
)}
|
||||
</div>
|
||||
<NMenu
|
||||
value={menuActive.value}
|
||||
onUpdateValue={updateMenuActive}
|
||||
onUpdateValue={(key: string, item: any) => {
|
||||
updateMenuActive(key as any) // 保留原有的菜单激活逻辑
|
||||
// 如果是移动端并且菜单当前是展开状态,则关闭菜单
|
||||
if (isMobile.value && !isCollapsed.value) {
|
||||
isCollapsed.value = true // 直接设置 isCollapsed 为 true 来关闭菜单
|
||||
}
|
||||
}}
|
||||
options={menuItems.value}
|
||||
class="border-none"
|
||||
collapsed={isCollapsed.value}
|
||||
collapsed-width={60}
|
||||
collapsed-icon-size={20}
|
||||
collapsed={nMenuCollapsedProp.value} // NMenu 的折叠状态
|
||||
collapsedWidth={siderCollapsedWidth.value}
|
||||
collapsedIconSize={22}
|
||||
/>
|
||||
</NLayoutSider>
|
||||
|
||||
<NLayout>
|
||||
<NLayoutHeader class={styles.header}>
|
||||
{/* 移动端或桌面端侧边栏折叠时,在头部左侧显示展开/收起按钮 */}
|
||||
{(isMobile.value || (!isMobile.value && isCollapsed.value)) && (
|
||||
<div class="mr-auto">
|
||||
<NTooltip placement="right" trigger="hover">
|
||||
{{
|
||||
trigger: () => (
|
||||
<div class={styles.headerMenuToggleButton} onClick={() => toggleCollapse()}>
|
||||
<NIcon size={20}>
|
||||
{isCollapsed.value ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</NIcon>
|
||||
</div>
|
||||
),
|
||||
default: () => <span>展开主菜单</span>,
|
||||
}}
|
||||
</NTooltip>
|
||||
</div>
|
||||
)}
|
||||
<div class={styles.systemInfo}>
|
||||
{/* <ThemeTips size="small" /> */}
|
||||
<NBadge value={1} show={false} dot>
|
||||
<span class="px-[.5rem] cursor-pointer">v1.0.2</span>
|
||||
<span class="px-1 sm:px-[.5rem] cursor-pointer">v1.0.2</span>
|
||||
</NBadge>
|
||||
</div>
|
||||
</NLayoutHeader>
|
||||
<NLayoutContent class={styles.content}>
|
||||
<RouterView>
|
||||
{({ Component }: { Component: ComponentType }) => (
|
||||
<Transition name="route-slide" mode="out-in">
|
||||
<Transition name="fade" mode="out-in">
|
||||
{Component && h(Component)}
|
||||
</Transition>
|
||||
)}
|
||||
</RouterView>
|
||||
</NLayoutContent>
|
||||
</NLayout>
|
||||
{/* 移动端菜单展开时的背景遮罩 */}
|
||||
{showBackdrop.value && (
|
||||
<div class={styles.mobileMenuBackdrop} onClick={() => toggleCollapse()}></div>
|
||||
)}
|
||||
</NLayout>
|
||||
)
|
||||
},
|
||||
|
||||
146
frontend/apps/allin-ssl/src/views/layout/types.d.ts
vendored
146
frontend/apps/allin-ssl/src/views/layout/types.d.ts
vendored
@@ -1,146 +0,0 @@
|
||||
/**
|
||||
* @file 布局组件类型定义文件
|
||||
* @description 此文件包含布局组件及相关接口的 TypeScript 类型定义,
|
||||
* 包括布局属性、系统信息、公司信息、菜单项以及布局状态管理等类型定义。
|
||||
* @module views/layout/types
|
||||
*/
|
||||
|
||||
/**
|
||||
* 布局组件的Props接口定义
|
||||
* @interface LayoutProps
|
||||
* @property {VNode[]} [children] - 子节点列表
|
||||
*/
|
||||
export interface LayoutProps {
|
||||
children?: VNode[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统信息接口定义
|
||||
* @interface SystemInfo
|
||||
* @property {string} version - 系统版本号
|
||||
* @property {boolean} updateAvailable - 是否有可用更新
|
||||
* @property {boolean} isPro - 是否为专业版
|
||||
*/
|
||||
export interface SystemInfo {
|
||||
username: string
|
||||
version: string
|
||||
secret: string
|
||||
id: string
|
||||
server_id: string
|
||||
uid: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付信息接口定义
|
||||
* @interface PayAuthInfo
|
||||
* @property {string} auth - 支付类型
|
||||
* @property {number} count - 支付数量
|
||||
* @property {string} endtime - 支付结束时间
|
||||
*/
|
||||
export interface PayAuthInfo {
|
||||
auth: string
|
||||
count: number
|
||||
endtime: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新信息接口定义
|
||||
* @interface UpdateInfo
|
||||
* @property {string} currentVersion - 当前版本
|
||||
* @property {string} currentVersionDate - 当前版本发布时间
|
||||
* @property {string} newVersion - 新版本
|
||||
* @property {string} newVersionDate - 新版本发布时间
|
||||
* @property {string[]} upgradeLog - 更新日志
|
||||
*/
|
||||
export interface UpdateInfo {
|
||||
currentVersion: string
|
||||
currentVersionDate: string
|
||||
newVersion: string
|
||||
newVersionDate: string
|
||||
upgradeLog: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 公司信息接口定义
|
||||
* @interface CompanyInfo
|
||||
* @property {string} name - 公司名称
|
||||
* @property {string} copyright - 版权信息
|
||||
* @property {number} year - 年份
|
||||
*/
|
||||
export interface CompanyInfo {
|
||||
name: string
|
||||
copyright: string
|
||||
year: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单项接口定义
|
||||
* @interface MenuItem
|
||||
* @property {string} key - 菜单项唯一标识
|
||||
* @property {() => VNode} [icon] - 菜单图标渲染函数
|
||||
* @property {string} label - 菜单显示文本
|
||||
* @property {MenuItem[]} [children] - 子菜单项列表
|
||||
* @property {string} [path] - 菜单路由路径
|
||||
*/
|
||||
export interface MenuItem {
|
||||
key: string
|
||||
icon?: () => VNode
|
||||
label: string
|
||||
children?: MenuItem[]
|
||||
path?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 布局状态存储接口定义
|
||||
* @interface LayoutStoreState
|
||||
* @property {Ref<boolean>} collapsed - 侧边栏折叠状态
|
||||
* @property {Ref<SystemInfo>} systemInfo - 系统信息
|
||||
* @property {Ref<CompanyInfo>} companyInfo - 公司信息
|
||||
* @property {Ref<MenuItem[]>} menuItems - 菜单项列表
|
||||
* @property {Ref<string>} menuActive - 当前激活的菜单项
|
||||
* @property {Ref<string>} title - 页面标题
|
||||
* @property {() => void} openPayModal - 打开支付弹窗方法
|
||||
*/
|
||||
export interface LayoutStoreState {
|
||||
collapsed: Ref<boolean>
|
||||
systemInfo: Ref<SystemInfo>
|
||||
companyInfo: Ref<CompanyInfo>
|
||||
menuItems: Ref<MenuItem[]>
|
||||
menuActive: Ref<string>
|
||||
title: Ref<string>
|
||||
openPayModal: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 布局状态管理方法接口定义
|
||||
* @interface LayoutStoreMethods
|
||||
* @property {() => void} toggleCollapse - 切换侧边栏折叠状态
|
||||
* @property {() => Promise<void>} fetchSystemInfo - 获取系统信息
|
||||
* @property {(info: Partial<CompanyInfo>) => void} updateCompanyInfo - 更新公司信息
|
||||
* @property {(title: string) => void} updateTitle - 更新页面标题
|
||||
*/
|
||||
export interface LayoutStoreMethods {
|
||||
toggleCollapse: () => void
|
||||
fetchSystemInfo: () => Promise<void>
|
||||
updateCompanyInfo: (info: Partial<CompanyInfo>) => void
|
||||
updateTitle: (title: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 布局状态管理完整类型定义
|
||||
* @type {LayoutStoreState & LayoutStoreMethods}
|
||||
*/
|
||||
export type LayoutStoreType = LayoutStoreState & LayoutStoreMethods
|
||||
|
||||
/**
|
||||
* @description 路由名称类型定义
|
||||
*/
|
||||
export type RouteName =
|
||||
| 'logout'
|
||||
| 'settings'
|
||||
| 'home'
|
||||
| 'monitor'
|
||||
| 'certApply'
|
||||
| 'autoDeploy'
|
||||
| 'authApiManage'
|
||||
| 'certManage'
|
||||
@@ -1,37 +1,47 @@
|
||||
// 外部库依赖
|
||||
import { ref, computed, watch, onMounted, h } from 'vue' // 从 vue 导入
|
||||
import { NIcon } from 'naive-ui'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
import { RouterLink, useRoute, useRouter, type RouteRecordRaw } from 'vue-router'
|
||||
|
||||
// 类型导入 - 从全局类型文件导入
|
||||
import type { MenuOption } from 'naive-ui/es/menu/src/interface'
|
||||
import type {
|
||||
RouteName,
|
||||
IconMap, // 导入 IconMap
|
||||
LayoutControllerExposes, // 导入 LayoutControllerExposes
|
||||
} from '../../types/layout' // 调整路径
|
||||
|
||||
// 内部模块导入 - Hooks
|
||||
import { useMessage, useDialog } from '@baota/naive-ui/hooks'
|
||||
import { useError } from '@baota/hooks/error'
|
||||
// 内部模块导入 - API
|
||||
import { signOut } from '@api/public'
|
||||
import { routes } from '@router/index'
|
||||
import { $t } from '@locales/index'
|
||||
// 内部模块导入 - Store
|
||||
import { useStore } from './useStore'
|
||||
// 内部模块导入 - 配置
|
||||
import { routes } from '@router/index' // 假设 routes 是 RouteRecordRaw[]
|
||||
// 内部模块导入 - 工具函数
|
||||
import { $t } from '@locales/index'
|
||||
|
||||
// 图标导入
|
||||
import { SettingsOutline, LogOutOutline } from '@vicons/ionicons5'
|
||||
import { CloudMonitoring, Home, Flow } from '@vicons/carbon'
|
||||
import { Certificate20Regular, AddSquare24Regular } from '@vicons/fluent'
|
||||
import { ApiOutlined } from '@vicons/antd'
|
||||
|
||||
import type { MenuOption } from 'naive-ui/es/menu/src/interface'
|
||||
import type { RouteName } from './types'
|
||||
|
||||
/**
|
||||
* @description 图标映射类型
|
||||
*/
|
||||
type IconMap = Record<RouteName, Component>
|
||||
|
||||
/**
|
||||
* @description 布局控制器
|
||||
* @returns 返回布局相关状态和方法
|
||||
*/
|
||||
export const useController = () => {
|
||||
export const useController = (): LayoutControllerExposes => { // 使用导入的 LayoutControllerExposes
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const router = useRouter() // 从 vue-router 导入
|
||||
const route = useRoute()
|
||||
const message = useMessage()
|
||||
// const { useFormInput } = useFormHooks()
|
||||
const { handleError } = useError()
|
||||
const { resetDataInfo, menuActive, updateMenuActive } = store
|
||||
// 从 store 中解构需要的状态和方法
|
||||
const { isCollapsed, menuActive, updateMenuActive, toggleCollapse, handleCollapse, handleExpand, resetDataInfo } = store
|
||||
|
||||
/**
|
||||
* 当前路由是否为子路由
|
||||
@@ -41,11 +51,12 @@ export const useController = () => {
|
||||
/**
|
||||
* 当前子路由配置
|
||||
*/
|
||||
const childRouteConfig = ref<Record<string, any>>({})
|
||||
const childRouteConfig = ref<Partial<RouteRecordRaw>>({}) // 替换 any
|
||||
|
||||
/**
|
||||
* ==================== 弹窗相关功能 ====================
|
||||
*/
|
||||
// (此处无弹窗相关功能直接定义,而是通过 useDialog hook 使用)
|
||||
|
||||
// ==============================
|
||||
// 图标渲染方法
|
||||
@@ -57,7 +68,7 @@ export const useController = () => {
|
||||
* @returns 对应的图标组件
|
||||
*/
|
||||
const renderIcon = (name: RouteName) => {
|
||||
const iconObj: IconMap = {
|
||||
const iconObj: IconMap = { // IconMap 类型来自导入
|
||||
certManage: Certificate20Regular,
|
||||
autoDeploy: Flow,
|
||||
home: Home,
|
||||
@@ -73,12 +84,14 @@ export const useController = () => {
|
||||
// ==============================
|
||||
// 菜单相关方法
|
||||
// ==============================
|
||||
const menuItems = computed(() => {
|
||||
const routeMenuItems: MenuOption[] = routes.map((route) => ({
|
||||
key: route.name as RouteName,
|
||||
label: () => <RouterLink to={route.path}>{route?.meta?.title as string}</RouterLink>,
|
||||
icon: renderIcon(route.name as RouteName),
|
||||
}))
|
||||
const menuItems = computed<MenuOption[]>(() => { // 添加显式返回类型
|
||||
const routeMenuItems: MenuOption[] = routes
|
||||
.filter((r) => r.meta?.title) // 过滤掉没有 title 的路由,避免渲染空标签
|
||||
.map((r) => ({
|
||||
key: r.name as RouteName,
|
||||
label: () => <RouterLink to={r.path}>{r?.meta?.title as string}</RouterLink>,
|
||||
icon: renderIcon(r.name as RouteName),
|
||||
}))
|
||||
return [
|
||||
...routeMenuItems,
|
||||
{
|
||||
@@ -93,20 +106,14 @@ export const useController = () => {
|
||||
* @description 检查当前路由是否为子路由
|
||||
* @returns {void}
|
||||
*/
|
||||
const checkIsChildRoute = () => {
|
||||
// 获取当前路由路径
|
||||
const checkIsChildRoute = (): void => {
|
||||
const currentPath = route.path
|
||||
// 检查路由是否包含 /children/ 标识子路由
|
||||
isChildRoute.value = currentPath.includes('/children/')
|
||||
|
||||
// 如果是子路由,获取子路由配置
|
||||
if (isChildRoute.value) {
|
||||
// 获取当前激活的主路由
|
||||
const parentRoute = routes.find((route) => route.name === menuActive.value)
|
||||
// 如果找到了父路由,且父路由有子路由配置
|
||||
const parentRoute = routes.find((r) => r.name === menuActive.value)
|
||||
if (parentRoute && parentRoute.children) {
|
||||
// 查找当前的子路由
|
||||
const currentChild = parentRoute.children.find((child) => route.path.includes(child.path))
|
||||
const currentChild = parentRoute.children.find((child: RouteRecordRaw) => route.path.includes(child.path))
|
||||
childRouteConfig.value = currentChild || {}
|
||||
} else {
|
||||
childRouteConfig.value = {}
|
||||
@@ -118,12 +125,10 @@ export const useController = () => {
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
() => {
|
||||
if (route.name !== menuActive.value) {
|
||||
// 更新当前激活的菜单项
|
||||
updateMenuActive(route.name as RouteName)
|
||||
(newName) => { // route.name 可能为 null 或 undefined
|
||||
if (newName && newName !== menuActive.value) {
|
||||
updateMenuActive(newName as RouteName)
|
||||
}
|
||||
// 检查是否为子路由
|
||||
checkIsChildRoute()
|
||||
},
|
||||
{ immediate: true },
|
||||
@@ -137,7 +142,7 @@ export const useController = () => {
|
||||
* @description 退出登录
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const handleLogout = async () => {
|
||||
const handleLogout = async (): Promise<void> => {
|
||||
try {
|
||||
await useDialog({
|
||||
title: $t('t_15_1745457484292'),
|
||||
@@ -147,11 +152,8 @@ export const useController = () => {
|
||||
message.success($t('t_17_1745457488251'))
|
||||
await signOut().fetch()
|
||||
setTimeout(() => {
|
||||
// 重置数据信息
|
||||
resetDataInfo()
|
||||
// 删除会话存储
|
||||
sessionStorage.clear()
|
||||
// 路由跳转
|
||||
router.push('/login')
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
@@ -160,7 +162,8 @@ export const useController = () => {
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
// useDialog 拒绝时会抛出错误,这里可以捕获不处理,或者记录日志
|
||||
// handleError(error) // 如果 useDialog 的拒绝也需要统一处理
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,12 +172,19 @@ export const useController = () => {
|
||||
*/
|
||||
|
||||
onMounted(async () => {
|
||||
// 初始化时检查是否为子路由
|
||||
checkIsChildRoute()
|
||||
})
|
||||
|
||||
return {
|
||||
...store,
|
||||
// 从 store 暴露
|
||||
isCollapsed,
|
||||
menuActive,
|
||||
updateMenuActive,
|
||||
toggleCollapse,
|
||||
handleCollapse,
|
||||
handleExpand,
|
||||
resetDataInfo,
|
||||
// controller 自身逻辑
|
||||
handleLogout,
|
||||
menuItems,
|
||||
isChildRoute,
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
import { useError } from '@baota/hooks/error'
|
||||
// 外部库依赖
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useLocalStorage, useSessionStorage } from '@vueuse/core'
|
||||
|
||||
import type { RouteName } from './types'
|
||||
import { DnsProviderOption, NotifyProviderOption } from '@/types/setting'
|
||||
// 类型导入 - 从全局类型文件导入
|
||||
import type {
|
||||
RouteName,
|
||||
LayoutStoreInterface, // 替换 LayoutStoreExposes
|
||||
PushSourceTypeItem, // 导入 PushSourceTypeItem
|
||||
} from '@/types/layout' // 调整路径
|
||||
import type { DnsProviderOption, NotifyProviderOption } from '@/types/setting'
|
||||
|
||||
// 内部模块导入 - Hooks
|
||||
import { useError } from '@baota/hooks/error'
|
||||
// 内部模块导入 - API
|
||||
import { getReportList } from '@api/setting'
|
||||
import { getAccessAllList } from '@api/index'
|
||||
// 内部模块导入 - 工具函数
|
||||
import { $t } from '@locales/index'
|
||||
|
||||
/**
|
||||
* @description 布局相关的状态管理
|
||||
* @warn 包含部分硬编码的业务数据,需要从API获取
|
||||
*/
|
||||
export const useLayoutStore = defineStore('layout-store', () => {
|
||||
export const useLayoutStore = defineStore('layout-store', (): LayoutStoreInterface => {
|
||||
// 使用导入的 LayoutStoreInterface
|
||||
const { handleError } = useError()
|
||||
|
||||
// ==============================
|
||||
@@ -40,7 +54,7 @@ export const useLayoutStore = defineStore('layout-store', () => {
|
||||
/**
|
||||
* @description 布局内边距
|
||||
*/
|
||||
const layoutPadding = computed(() => {
|
||||
const layoutPadding = computed<string>(() => {
|
||||
return menuActive.value !== 'home' ? 'var(--n-content-padding)' : '0'
|
||||
})
|
||||
|
||||
@@ -50,42 +64,9 @@ export const useLayoutStore = defineStore('layout-store', () => {
|
||||
const locales = useLocalStorage<string>('locales-active', 'zhCN')
|
||||
|
||||
/**
|
||||
* @description 主机提供商
|
||||
* @description 推送消息提供商 (保持 PushSourceTypeItem 和 pushSourceType)
|
||||
*/
|
||||
const sourceTypes = ref({
|
||||
// 主机提供商
|
||||
ssh: { name: 'SSH', access: ['host'] },
|
||||
btpanel: { name: $t('t_10_1745735765165'), access: ['host'] },
|
||||
btwaf: { name: '宝塔WAF', access: ['host'] },
|
||||
'1panel': { name: '1Panel', access: ['host'] },
|
||||
aliyun: { name: $t('t_2_1747019616224'), access: ['dns', 'host'] },
|
||||
tencentcloud: { name: $t('t_3_1747019616129'), access: ['dns', 'host'] },
|
||||
huaweicloud: { name: '华为云', access: ['dns'] },
|
||||
cloudflare: { name: 'Cloudflare', access: ['dns'] },
|
||||
baidu: { name: '百度云', access: ['dns'] },
|
||||
safeline: { name: '雷池WAF', access: ['host'] },
|
||||
volcengine: { name: '火山引擎', access: ['dns'] },
|
||||
westcn: { name: '西部数码', access: ['dns'] },
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 主机提供商衍生类型
|
||||
*/
|
||||
const sourceDerivationTypes = ref({
|
||||
// 网站
|
||||
'btpanel-site': { name: $t('t_11_1745735766456') },
|
||||
'1panel-site': { name: $t('t_13_1745735766084') },
|
||||
// 云服务
|
||||
'aliyun-cdn': { name: $t('t_16_1745735766712') },
|
||||
'aliyun-oss': { name: $t('t_2_1746697487164') },
|
||||
'tencentcloud-cdn': { name: $t('t_14_1745735766121') },
|
||||
'tencentcloud-cos': { name: $t('t_15_1745735768976') },
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 消息通知提供商
|
||||
*/
|
||||
const pushSourceType = ref({
|
||||
const pushSourceType = ref<Record<string, PushSourceTypeItem>>({
|
||||
mail: { name: $t('t_68_1745289354676') },
|
||||
dingtalk: { name: $t('t_32_1746773348993') },
|
||||
wecom: { name: $t('t_33_1746773350932') },
|
||||
@@ -93,8 +74,6 @@ export const useLayoutStore = defineStore('layout-store', () => {
|
||||
webhook: { name: 'WebHook' },
|
||||
})
|
||||
|
||||
// ==============================
|
||||
// UI 交互方法
|
||||
// ==============================
|
||||
// UI 交互方法
|
||||
// ==============================
|
||||
@@ -106,33 +85,19 @@ export const useLayoutStore = defineStore('layout-store', () => {
|
||||
isCollapsed.value = !isCollapsed.value
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 展开侧边栏
|
||||
*/
|
||||
const handleCollapse = () => {
|
||||
const handleCollapse = (): void => {
|
||||
isCollapsed.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 收起侧边栏
|
||||
*/
|
||||
|
||||
const handleExpand = () => {
|
||||
const handleExpand = (): void => {
|
||||
isCollapsed.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新菜单激活状态
|
||||
* @param active - 激活状态
|
||||
*/
|
||||
const updateMenuActive = (active: RouteName): void => {
|
||||
if (active === 'logout') return
|
||||
menuActive.value = active
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 重置数据信息
|
||||
*/
|
||||
const resetDataInfo = (): void => {
|
||||
menuActive.value = 'home'
|
||||
sessionStorage.removeItem('menu-active')
|
||||
@@ -150,13 +115,14 @@ export const useLayoutStore = defineStore('layout-store', () => {
|
||||
try {
|
||||
notifyProvider.value = []
|
||||
const { data } = await getReportList({ p: 1, search: '', limit: 1000 }).fetch()
|
||||
notifyProvider.value = data?.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
value: item.id.toString(),
|
||||
type: item.type,
|
||||
}
|
||||
})
|
||||
notifyProvider.value =
|
||||
data?.map((item) => {
|
||||
return {
|
||||
label: item.name,
|
||||
value: item.id.toString(),
|
||||
type: item.type,
|
||||
}
|
||||
}) || [] // 添加空数组作为备选,以防 data 为 null/undefined
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
@@ -164,24 +130,10 @@ export const useLayoutStore = defineStore('layout-store', () => {
|
||||
|
||||
/**
|
||||
* @description 获取DNS提供商
|
||||
* @param type - 类型
|
||||
* @param type - 类型 (简化了联合类型,实际使用时可根据需要定义更精确的类型别名)
|
||||
* @returns DNS提供商
|
||||
*/
|
||||
const fetchDnsProvider = async (
|
||||
type:
|
||||
| 'btpanel'
|
||||
| 'aliyun'
|
||||
| 'ssh'
|
||||
| 'tencentcloud'
|
||||
| '1panel'
|
||||
| 'dns'
|
||||
| 'baidu'
|
||||
| 'huaweicloud'
|
||||
| 'cloudflare'
|
||||
| 'baidu'
|
||||
| ''
|
||||
| 'btwaf' = '',
|
||||
): Promise<void> => {
|
||||
const fetchDnsProvider = async (type: string = ''): Promise<void> => {
|
||||
try {
|
||||
dnsProvider.value = []
|
||||
const { data } = await getAccessAllList({ type }).fetch()
|
||||
@@ -199,43 +151,31 @@ export const useLayoutStore = defineStore('layout-store', () => {
|
||||
|
||||
/**
|
||||
* @description 重置DNS提供商
|
||||
*
|
||||
*/
|
||||
const resetDnsProvider = (): void => {
|
||||
dnsProvider.value = []
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// 表单处理方法
|
||||
// ==============================
|
||||
|
||||
return {
|
||||
// 状态
|
||||
locales,
|
||||
isCollapsed,
|
||||
notifyProvider,
|
||||
dnsProvider,
|
||||
isCollapsed,
|
||||
layoutPadding,
|
||||
menuActive,
|
||||
sourceTypes,
|
||||
sourceDerivationTypes,
|
||||
layoutPadding,
|
||||
locales,
|
||||
pushSourceType,
|
||||
|
||||
// 方法
|
||||
resetDataInfo,
|
||||
updateMenuActive,
|
||||
toggleCollapse,
|
||||
handleCollapse,
|
||||
handleExpand,
|
||||
updateMenuActive,
|
||||
resetDataInfo,
|
||||
fetchNotifyProvider,
|
||||
fetchDnsProvider,
|
||||
resetDnsProvider,
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 辅助函数:获取布局相关的状态和方法
|
||||
* @returns 组合了store实例和响应式引用的对象
|
||||
*/
|
||||
export const useStore = () => {
|
||||
const store = useLayoutStore()
|
||||
return { ...store, ...storeToRefs(store) }
|
||||
|
||||
Reference in New Issue
Block a user