【新增】部署类型七牛云oss、七牛云cdn、百度cdn、腾讯waf、腾讯edgeone、阿里云waf

【新增】解析类型godaddy
【新增】自定义CA授权管理
【调整】优化部署流程,减少代码冗余,提升类型添加效率
This commit is contained in:
chudong
2025-05-23 16:58:34 +08:00
parent 71de397e11
commit e5634d4992
263 changed files with 18348 additions and 14253 deletions

View File

@@ -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;
}
}

View File

@@ -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>
)
},

View File

@@ -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'

View File

@@ -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,

View File

@@ -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) }