【同步】前端项目源码

【修复】工作流兼容问题
This commit is contained in:
chudong
2025-05-10 11:53:11 +08:00
parent c514471adc
commit f1a75afaba
584 changed files with 55714 additions and 110 deletions

View File

@@ -0,0 +1,77 @@
/* 布局容器样式 */
.layoutContainer {
@apply min-h-screen flex flex-col;
}
/* 侧边栏样式 */
.sider {
@apply h-screen dark:bg-gray-800 shadow-lg z-10 transition-all duration-300 ease-in-out;
}
/* Logo容器样式 */
.logoContainer {
@apply flex items-center px-[2rem] h-[var(--n-sider-login-height)] border-b dark:border-gray-700 relative;
border-color: var(--n-border-color);
}
/* Logo容器激活样式 */
.logoContainer span {
@apply text-nowrap overflow-hidden overflow-ellipsis w-[10rem];
}
/* Logo容器文本样式 */
.logoContainerText {
@apply flex items-center w-[20rem];
}
/* Logo容器激活样式 */
.logoContainerActive {
@apply flex items-center justify-center px-[0];
}
/* 折叠图标激活样式 */
.collapsedIconActive {
@apply !relative left-0 px-[1rem];
}
/* 折叠按钮样式 */
.collapsedIcon {
@apply w-[4.2rem] h-[3.6rem] hover:bg-gray-200 hover:text-gray-500 hover:dark:bg-gray-500 absolute right-[1rem] rounded-[.4rem] flex items-center justify-center cursor-pointer transition-all duration-300;
}
/* 头部样式 */
.header {
@apply h-[var(--n-header-height)] bg-[var(--n-header-color)] border-b dark:border-gray-700 shadow-sm z-10 transition-all duration-300 ease-in-out flex items-center justify-end px-6;
border-color: var(--n-border-color);
}
/* 系统信息样式 */
.systemInfo {
@apply flex items-center space-x-4 text-[1.2rem] text-gray-600 dark:text-gray-300;
}
/* 内容区域样式 */
.content {
@apply flex-1 transition-all duration-300 ease-in-out bg-slate-50 dark:bg-gray-900 p-[var(--n-content-padding)] h-[calc(100vh-var(--n-main-diff-height))] overflow-y-auto;
transition: padding 0s;
}
/* 折叠按钮样式 */
.collapseButton {
@apply absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 dark:bg-gray-800 rounded-full p-2 shadow-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 transition-all duration-300;
}
/* 子路由导航样式 */
.subRouteNav {
@apply mb-4 p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm;
}
/* 子路由标题样式 */
.subRouteTitle {
@apply text-lg font-medium mb-2 text-gray-700 dark:text-gray-300;
}
/* 子路由面包屑样式 */
.breadcrumb {
@apply bg-white dark:bg-gray-800 p-3 rounded-lg shadow-sm mb-4;
}

View File

@@ -0,0 +1,86 @@
import { Transition, type Component as ComponentType, h } from 'vue'
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 { useController } from './useController'
import styles from './index.module.css'
export default defineComponent({
setup() {
// 获取控制器中的状态和方法
const { menuItems, menuActive, isCollapsed, toggleCollapse, handleExpand, handleCollapse, updateMenuActive } =
useController()
// 获取主题变量
const cssVars = useThemeCssVar(['cardColor', 'headerColor'])
return () => (
<NLayout class={styles.layoutContainer} hasSider style={cssVars.value}>
<NLayoutSider
width={200}
collapsed={isCollapsed.value}
collapse-mode="width"
collapsed-width={60}
onCollapse={handleCollapse}
onExpand={handleExpand}
class={styles.sider}
bordered
>
<div class={styles.logoContainer + ' ' + (isCollapsed.value ? styles.logoContainerActive : '')}>
{!isCollapsed.value ? (
<div class={styles.logoContainerText}>
<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>
</div>
<NMenu
value={menuActive.value}
onUpdateValue={updateMenuActive}
options={menuItems.value}
class="border-none"
collapsed={isCollapsed.value}
collapsed-width={60}
collapsed-icon-size={20}
/>
</NLayoutSider>
<NLayout>
<NLayoutHeader class={styles.header}>
<div class={styles.systemInfo}>
<NBadge value={1} show={false} dot>
<span class="px-[.5rem] cursor-pointer">v1.0</span>
</NBadge>
</div>
</NLayoutHeader>
<NLayoutContent class={styles.content}>
<RouterView>
{({ Component }: { Component: ComponentType }) => (
<Transition name="route-slide" mode="out-in">
{Component && h(Component)}
</Transition>
)}
</RouterView>
</NLayoutContent>
</NLayout>
</NLayout>
)
},
})

View File

@@ -0,0 +1,146 @@
/**
* @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

@@ -0,0 +1,183 @@
import { NIcon } from 'naive-ui'
import { RouterLink, useRoute } from 'vue-router'
import { useMessage, useDialog } from '@baota/naive-ui/hooks'
import { useError } from '@baota/hooks/error'
import { signOut } from '@api/public'
import { routes } from '@router/index'
import { $t } from '@locales/index'
import { useStore } from './useStore'
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 = () => {
const store = useStore()
const router = useRouter()
const route = useRoute()
const message = useMessage()
// const { useFormInput } = useFormHooks()
const { handleError } = useError()
const { resetDataInfo, menuActive, updateMenuActive } = store
/**
* 当前路由是否为子路由
*/
const isChildRoute = ref(false)
/**
* 当前子路由配置
*/
const childRouteConfig = ref<Record<string, any>>({})
/**
* ==================== 弹窗相关功能 ====================
*/
// ==============================
// 图标渲染方法
// ==============================
/**
* @description 渲染导航图标
* @param name - 路由名称
* @returns 对应的图标组件
*/
const renderIcon = (name: RouteName) => {
const iconObj: IconMap = {
certManage: Certificate20Regular,
autoDeploy: Flow,
home: Home,
certApply: AddSquare24Regular,
monitor: CloudMonitoring,
settings: SettingsOutline,
logout: LogOutOutline,
authApiManage: ApiOutlined,
}
return () => h(NIcon, null, () => h(iconObj[name] || 'div'))
}
// ==============================
// 菜单相关方法
// ==============================
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),
}))
return [
...routeMenuItems,
{
key: 'logout',
label: () => <a onClick={handleLogout}>{$t('t_0_1744168657526')}</a>,
icon: renderIcon('logout'),
},
]
})
/**
* @description 检查当前路由是否为子路由
* @returns {void}
*/
const checkIsChildRoute = () => {
// 获取当前路由路径
const currentPath = route.path
// 检查路由是否包含 /children/ 标识子路由
isChildRoute.value = currentPath.includes('/children/')
// 如果是子路由,获取子路由配置
if (isChildRoute.value) {
// 获取当前激活的主路由
const parentRoute = routes.find((route) => route.name === menuActive.value)
// 如果找到了父路由,且父路由有子路由配置
if (parentRoute && parentRoute.children) {
// 查找当前的子路由
const currentChild = parentRoute.children.find((child) => route.path.includes(child.path))
childRouteConfig.value = currentChild || {}
} else {
childRouteConfig.value = {}
}
} else {
childRouteConfig.value = {}
}
}
watch(
() => route.name,
() => {
if (route.name !== menuActive.value) {
// 更新当前激活的菜单项
updateMenuActive(route.name as RouteName)
}
// 检查是否为子路由
checkIsChildRoute()
},
{ immediate: true },
)
/**
* ==================== 用户操作功能 ====================
*/
/**
* @description 退出登录
* @returns {Promise<void>}
*/
const handleLogout = async () => {
try {
await useDialog({
title: $t('t_15_1745457484292'),
content: $t('t_16_1745457491607'),
onPositiveClick: async () => {
try {
message.success($t('t_17_1745457488251'))
await signOut().fetch()
setTimeout(() => {
// 重置数据信息
resetDataInfo()
// 删除会话存储
sessionStorage.clear()
// 路由跳转
router.push('/login')
}, 1000)
} catch (error) {
handleError(error)
}
},
})
} catch (error) {
handleError(error)
}
}
/**
* ==================== 初始化逻辑 ====================
*/
onMounted(async () => {
// 初始化时检查是否为子路由
checkIsChildRoute()
})
return {
...store,
handleLogout,
menuItems,
isChildRoute,
childRouteConfig,
}
}

View File

@@ -0,0 +1,173 @@
import { useError } from '@baota/hooks/error'
import type { RouteName } from './types'
import { DnsProviderOption, NotifyProviderOption } from '@/types/setting'
import { getReportList } from '@api/setting'
import { getAccessAllList } from '@api/index'
/**
* @description 布局相关的状态管理
* @warn 包含部分硬编码的业务数据需要从API获取
*/
export const useLayoutStore = defineStore('layout-store', () => {
const { handleError } = useError()
// ==============================
// 状态定义
// ==============================
/**
* @description UI 相关状态
*/
const isCollapsed = useLocalStorage<boolean>('layout-collapsed', false)
/**
* @description 消息通知
*/
const notifyProvider = ref<NotifyProviderOption[]>([])
/**
* @description DNS提供商
*/
const dnsProvider = ref<DnsProviderOption[]>([])
/**
* @description 导航状态
*/
const menuActive = useLocalStorage<RouteName>('menu-active', 'home')
/**
* @description 布局内边距
*/
const layoutPadding = computed(() => {
return menuActive.value !== 'home' ? 'var(--n-content-padding)' : '0'
})
/**
* @description 语言
*/
const locales = useLocalStorage<string>('locales-active', 'zhCN')
// ==============================
// UI 交互方法
// ==============================
/**
* @description 切换侧边栏折叠状态
*/
const toggleCollapse = (): void => {
isCollapsed.value = !isCollapsed.value
}
/**
* @description 展开侧边栏
*/
const handleCollapse = () => {
isCollapsed.value = true
}
/**
* @description 收起侧边栏
*/
const handleExpand = () => {
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'
localStorage.removeItem('menu-active')
}
// ==============================
// API 请求方法
// ==============================
/**
* @description 获取消息通知提供商
* @returns 消息通知提供商
*/
const fetchNotifyProvider = async (): Promise<void> => {
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,
}
})
} catch (error) {
handleError(error)
}
}
/**
* @description 获取DNS提供商
* @param type - 类型
* @returns DNS提供商
*/
const fetchDnsProvider = async (
type: 'btpanel' | 'aliyun' | 'ssh' | 'tencentcloud' | '1panel' | 'dns' | '' = '',
): Promise<void> => {
try {
dnsProvider.value = []
const { data } = await getAccessAllList({ type }).fetch()
console.timeEnd('loadDnsProviders')
dnsProvider.value =
data?.map((item) => ({
label: item.name,
value: item.id.toString(),
type: item.type,
})) || []
console.timeEnd('loadDnsProviders')
} catch (error) {
handleError(error)
}
}
// ==============================
// 表单处理方法
// ==============================
return {
// 状态
locales,
notifyProvider,
dnsProvider,
isCollapsed,
layoutPadding,
menuActive,
// 方法
resetDataInfo,
updateMenuActive,
toggleCollapse,
handleCollapse,
handleExpand,
fetchNotifyProvider,
fetchDnsProvider,
}
})
/**
* @description 辅助函数:获取布局相关的状态和方法
* @returns 组合了store实例和响应式引用的对象
*/
export const useStore = () => {
const store = useLayoutStore()
return { ...store, ...storeToRefs(store) }
}