12 Commits

Author SHA1 Message Date
483e2995db chore(deps): update playwright packages
Some checks are pending
renovate/stability-days Updates have not met minimum release age requirement
/ lint-build-and-check (push) Successful in 6m20s
/ surge (push) Successful in 3m0s
/ playwright (push) Successful in 2m24s
/ cleanup_surge (push) Successful in 13s
2025-07-12 05:57:03 +08:00
383d8deead chore(deps): update dependency @types/node to ^22.16.3
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
/ build-and-deploy-to-vercel (push) Successful in 3m44s
/ lint-build-and-check (push) Successful in 6m3s
/ surge (push) Successful in 3m11s
/ playwright (push) Successful in 1m5s
/ cleanup_surge (push) Successful in 11s
2025-07-11 03:15:34 +08:00
fcf09d887b chore(deps): update dependency @types/three to ^0.178.1
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
/ build-and-deploy-to-vercel (push) Successful in 3m25s
/ lint-build-and-check (push) Successful in 7m5s
/ playwright (push) Successful in 1m0s
/ cleanup_surge (push) Successful in 19s
/ surge (push) Successful in 3m19s
2025-07-11 02:48:12 +08:00
145baf5e16 chore(deps): update dependency vite-plugin-checker to ^0.10.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
/ build-and-deploy-to-vercel (push) Successful in 3m16s
/ lint-build-and-check (push) Successful in 5m57s
/ playwright (push) Successful in 1m0s
/ cleanup_surge (push) Successful in 14s
/ surge (push) Successful in 3m22s
2025-07-10 18:56:25 +08:00
866145591f chore(deps): update lint dependencies to ^1.6.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
/ build-and-deploy-to-vercel (push) Successful in 3m10s
/ lint-build-and-check (push) Successful in 6m7s
/ cleanup_surge (push) Successful in 11s
/ surge (push) Successful in 4m32s
/ playwright (push) Successful in 1m32s
2025-07-10 16:30:47 +08:00
33528d64a7 chore(deps): update dependency @types/node to ^22.16.2
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
/ build-and-deploy-to-vercel (push) Successful in 3m17s
/ lint-build-and-check (push) Successful in 6m22s
/ surge (push) Successful in 4m6s
/ playwright (push) Successful in 2m36s
/ cleanup_surge (push) Successful in 17s
2025-07-09 08:59:11 +08:00
30559d749e chore(deps): update vite packages
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
/ build-and-deploy-to-vercel (push) Successful in 3m17s
/ lint-build-and-check (push) Successful in 6m16s
/ surge (push) Successful in 3m3s
/ cleanup_surge (push) Successful in 19s
/ playwright (push) Successful in 1m0s
2025-07-07 08:42:16 +08:00
57d07d3cd0 chore(deps): update yanhao98/composite-actions digest to b4a2caa
All checks were successful
/ build-and-deploy-to-vercel (push) Successful in 4m2s
/ playwright (push) Successful in 1m38s
/ cleanup_surge (push) Successful in 21s
/ surge (push) Successful in 3m32s
/ lint-build-and-check (push) Successful in 7m6s
2025-07-07 07:29:42 +08:00
11f4587681 chore(deps): update all non-major dependencies
Some checks are pending
renovate/stability-days Updates have not met minimum release age requirement
/ build-and-deploy-to-vercel (push) Successful in 3m35s
/ surge (push) Successful in 3m7s
/ playwright (push) Successful in 1m12s
/ cleanup_surge (push) Successful in 8s
/ lint-build-and-check (push) Successful in 6m7s
2025-07-06 16:21:06 +08:00
669cd7070f chore(deps): update lint dependencies
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
/ build-and-deploy-to-vercel (push) Successful in 3m41s
/ lint-build-and-check (push) Successful in 5m58s
/ surge (push) Successful in 3m0s
/ playwright (push) Successful in 59s
/ cleanup_surge (push) Successful in 20s
2025-07-04 13:41:31 +08:00
5487dc321e feat: enhance responsive design for sidebar and drawer in AppLayout
All checks were successful
/ build-and-deploy-to-vercel (push) Successful in 3m12s
/ cleanup_surge (push) Successful in 21s
/ surge (push) Successful in 3m4s
/ playwright (push) Successful in 1m43s
/ lint-build-and-check (push) Successful in 6m5s
2025-07-04 12:34:55 +08:00
ec4906f441 feat: update layout configuration to use naive-ui/AppLayout
All checks were successful
/ build-and-deploy-to-vercel (push) Successful in 3m15s
/ surge (push) Successful in 3m32s
/ playwright (push) Successful in 1m3s
/ cleanup_surge (push) Successful in 13s
/ lint-build-and-check (push) Successful in 6m2s
2025-07-04 12:06:40 +08:00
8 changed files with 772 additions and 448 deletions

View File

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 🛠️ 设置Node环境
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
uses: yanhao98/composite-actions/setup-node-environment@b4a2caa64aca72f8aeada59d0df3181a12df8268
- name: 🔍 静态代码分析
run: pnpm run lint
- name: 📦 构建项目

View File

@ -22,7 +22,7 @@ jobs:
url: ${{ steps.surge_deploy.outputs.url }}
steps:
- name: ⚙️ 设置 Node 环境
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
uses: yanhao98/composite-actions/setup-node-environment@b4a2caa64aca72f8aeada59d0df3181a12df8268
- name: 🔨 构建项目
run: pnpm run build-only
env:
@ -30,15 +30,15 @@ jobs:
- name: 🚀 部署到 Surge
id: surge_deploy
if: ${{ github.actor != 'nektos/act' }} # https://nektosact.com/usage/index.html#skipping-steps
uses: yanhao98/composite-actions/deploy-dist-to-surge@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
uses: yanhao98/composite-actions/deploy-dist-to-surge@b4a2caa64aca72f8aeada59d0df3181a12df8268
playwright:
needs: surge
runs-on: ubuntu-latest
container: mcr.microsoft.com/playwright:v1.53.2-noble
container: mcr.microsoft.com/playwright:v1.54.1-noble
steps:
- name: ⚙️ 设置 Node 环境
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
uses: yanhao98/composite-actions/setup-node-environment@b4a2caa64aca72f8aeada59d0df3181a12df8268
# - name: 📥 安装 Playwright 浏览器
# run: pnpm exec playwright install --with-deps
- name: ▶️ 运行 Playwright 测试

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: ⚙️ 设置 Node 环境
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
uses: yanhao98/composite-actions/setup-node-environment@b4a2caa64aca72f8aeada59d0df3181a12df8268
- name: 📥 拉取 Vercel 环境信息
run: pnpm dlx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

View File

@ -49,12 +49,12 @@
"@intlify/unplugin-vue-i18n": "^6.0.8",
"@pinia/colada": "^0.17.1",
"@primeuix/themes": "^1.1.2",
"@splinetool/runtime": "^1.10.19",
"@splinetool/runtime": "^1.10.22",
"@types/p5": "^1.7.6",
"@types/sortablejs": "^1.15.8",
"@unhead/vue": "^2.0.11",
"@unhead/vue": "^2.0.12",
"@vant/use": "^1.6.0",
"@vueuse/core": "^13.4.0",
"@vueuse/core": "^13.5.0",
"alova": "^3.3.4",
"ant-design-vue": "~4.2.6",
"axios": "^1.10.0",
@ -78,8 +78,8 @@
"primevue": "^4.3.5",
"radash": "^12.1.1",
"radix-vue": "^1.9.17",
"reka-ui": "^2.3.1",
"satellite.js": "^6.0.0",
"reka-ui": "^2.3.2",
"satellite.js": "^6.0.1",
"sortablejs": "^1.15.6",
"tailwind-merge": "^3.3.1",
"tdesign-icons-vue-next": "^0.3.6",
@ -90,7 +90,7 @@
"vue": "^3.5.17",
"vue-data-ui": "^2.12.7",
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "^11.1.7",
"vue-i18n": "^11.1.9",
"vue-page-stack": "^3.2.0",
"vue-router": "^4.5.1"
},
@ -103,15 +103,15 @@
"@iconify-json/logos": "^1.2.4",
"@iconify-json/mdi": "^1.2.3",
"@iconify/utils": "^2.3.0",
"@playwright/test": "^1.53.2",
"@playwright/test": "^1.54.1",
"@primevue/auto-import-resolver": "^4.3.5",
"@tsconfig/node22": "^22.0.2",
"@types/archiver": "^6.0.3",
"@types/mockjs": "^1.0.10",
"@types/node": "^22.16.0",
"@types/node": "^22.16.3",
"@types/nprogress": "^0.2.3",
"@types/plotly.js-dist-min": "^2.3.4",
"@types/three": "^0.178.0",
"@types/three": "^0.178.1",
"@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vitejs/plugin-vue-jsx": "^5.0.1",
@ -125,7 +125,7 @@
"eruda": "^3.4.3",
"eslint": "^9.30.1",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-oxlint": "^1.5.0",
"eslint-plugin-oxlint": "^1.6.0",
"eslint-plugin-perfectionist": "^4.15.0",
"eslint-plugin-unicorn": "^59.0.1",
"eslint-plugin-vue": "^10.3.0",
@ -135,10 +135,10 @@
"mockjs": "^1.1.0",
"naive-ui": "^2.42.0",
"npm-run-all2": "^8.0.4",
"oxlint": "^1.5.0",
"oxlint": "^1.6.0",
"prettier": "3.6.2",
"typescript": "~5.8.3",
"unocss": "66.3.2",
"unocss": "66.3.3",
"unocss-preset-animations": "^1.2.1",
"unocss-preset-chinese": "^0.3.3",
"unocss-preset-shadcn": "^0.5.0",
@ -149,18 +149,18 @@
"unplugin-vue-markdown": "^29.1.0",
"unplugin-vue-router": "^0.14.0",
"vfonts": "^0.0.3",
"vite": "^7.0.0",
"vite-plugin-checker": "^0.9.3",
"vite": "^7.0.2",
"vite-plugin-checker": "^0.10.0",
"vite-plugin-fake-server": "^2.2.0",
"vite-plugin-image-tools": "^2.0.2",
"vite-plugin-purgecss-updated-v5": "^1.2.6",
"vite-plugin-singlefile": "^2.2.0",
"vite-plugin-singlefile": "^2.3.0",
"vite-plugin-static-copy": "^3.1.0",
"vite-plugin-vue-devtools": "^7.7.7",
"vite-plugin-vue-layouts": "^0.11.0",
"vite-plugin-vue-meta-layouts": "^0.5.1",
"vite-plugin-webfont-dl": "^3.10.5",
"vue-component-type-helpers": "^3.0.0",
"vue-tsc": "^3.0.0"
"vue-component-type-helpers": "^3.0.1",
"vue-tsc": "^3.0.1"
}
}

842
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -15,9 +15,11 @@ const themeConfig = computed(() => {
</script>
<template>
<a-config-provider :theme="themeConfig">
<RouterView />
</a-config-provider>
<n-config-provider preflight-style-disabled>
<a-config-provider :theme="themeConfig">
<RouterView />
</a-config-provider>
</n-config-provider>
<DynamicDialog /> <ConfirmDialog /> <Toast />
</template>

View File

@ -0,0 +1,321 @@
<script setup lang="ts">
import type { MenuOption } from 'naive-ui';
import { createGetRoutes } from '@/plugins/router';
const router = useRouter();
// 响应式断点检测
const isMobile = ref(false);
const isTablet = ref(false);
// 检测屏幕尺寸
const updateScreenSize = () => {
const width = window.innerWidth;
const wasMobile = isMobile.value;
isMobile.value = width < 768;
isTablet.value = width >= 768 && width < 1024;
// 当从移动端切换到桌面端时,关闭抽屉并重置折叠状态
if (wasMobile && !isMobile.value) {
drawerVisible.value = false;
collapsed.value = false;
}
// 当从桌面端切换到移动端时,关闭抽屉
else if (!wasMobile && isMobile.value) {
drawerVisible.value = false;
}
};
// 侧边栏状态管理
const collapsed = ref(false);
const drawerVisible = ref(false);
// 初始化屏幕尺寸检测
onMounted(() => {
updateScreenSize();
window.addEventListener('resize', updateScreenSize);
// 移动端默认收起侧边栏
if (isMobile.value) {
collapsed.value = true;
drawerVisible.value = false;
}
});
onUnmounted(() => {
window.removeEventListener('resize', updateScreenSize);
});
// 菜单项类型定义
type MenuItemWithRoute = MenuOption & {
routeName?: string;
parentId?: string;
originalPath?: string;
};
// 生成菜单项
const menuOptions = computed(() => {
let flatArray: MenuItemWithRoute[] = createGetRoutes(router)()
.filter((route) => !route.path.includes('/:'))
.filter((route) => !route.meta.hidden)
.map((route) => ({
key: route.path,
label: route.meta.title || `${(route.name as string) || route.path}`,
routeName: route.name as string,
}));
flatArray = flatArray.map((item) => {
const originalPath = item.key as string; // 保存原始路径
let id = item.key as string;
if (flatArray.some((item) => (item.key as string).startsWith(`${id}/`))) {
id = `${id}/index`;
}
// 去掉最前面的 /
id = id.replace(/^\//, '');
let parentId = id.replace(/\/[^/]+$/, '');
if (parentId === id) {
parentId = '_ROOT_';
}
return {
...item,
key: id,
parentId,
originalPath, // 保存原始路径用于后续映射
};
});
const groupItems: MenuItemWithRoute[] = [];
for (const flatArrayItem of flatArray) {
if (!groupItems.some((item) => item.key === flatArrayItem.parentId) && flatArrayItem.parentId !== '_ROOT_') {
let groupItemParentId = flatArrayItem.parentId!.replace(/\/[^/]+$/, '');
if (groupItemParentId === flatArrayItem.parentId) groupItemParentId = '_ROOT_';
groupItems.push({
key: flatArrayItem.parentId!,
label: `Group ${flatArrayItem.parentId}`,
parentId: groupItemParentId,
});
}
}
const tree = arrayToTree([...flatArray, ...groupItems], {
id: 'key',
parentId: 'parentId',
rootId: '_ROOT_',
});
// 递归转换树形结构为 naive-ui menu 格式
function convertToMenuOptions(tree: MenuItemWithRoute[]): MenuOption[] {
return tree.map((item) => {
const menuItem: MenuOption = {
key: item.key,
label: item.label,
};
if (item.children && item.children.length > 0) {
menuItem.children = convertToMenuOptions(item.children);
} else if (item.routeName) {
// 叶子节点,存储路由映射
menuRouteMap.set(item.key as string, item.routeName);
// 同时存储路径到 key 的映射(用于高亮显示)
if (item.originalPath) {
pathToKeyMap.set(item.originalPath, item.key as string);
}
(menuItem as MenuItemWithRoute).routeName = item.routeName;
}
return menuItem;
});
}
// 清空之前的映射
menuRouteMap.clear();
pathToKeyMap.clear();
const result = convertToMenuOptions(tree);
// 菜单生成后,重新设置当前选中的菜单项
nextTick(() => {
const currentPath = router.currentRoute.value.path;
const menuKey = pathToKeyMap.get(currentPath);
if (menuKey) {
selectedKey.value = menuKey;
} else {
const pathWithoutSlash = currentPath.replace(/^\//, '');
selectedKey.value = pathWithoutSlash;
}
});
return result;
});
// 当前选中的菜单项
const selectedKey = ref<string>();
// 存储菜单项与路由名称的映射
const menuRouteMap = new Map<string, string>();
// 存储路由路径与菜单 key 的映射(用于高亮显示)
const pathToKeyMap = new Map<string, string>();
// 处理菜单点击
const handleMenuSelect = (key: string, item: MenuOption) => {
const routeName = menuRouteMap.get(key) || (item as MenuItemWithRoute).routeName;
if (routeName) {
router.push({ name: routeName as never });
// 移动端点击菜单项后自动收起侧边栏
if (isMobile.value) {
drawerVisible.value = false;
}
}
};
// 监听路由变化,更新选中的菜单项
watch(
() => router.currentRoute.value.path,
(newPath) => {
// 使用路径到 key 的映射来找到对应的菜单项
const menuKey = pathToKeyMap.get(newPath);
if (menuKey) {
selectedKey.value = menuKey;
} else {
// 如果没有找到精确匹配,尝试去掉前面的 / 再匹配
const pathWithoutSlash = newPath.replace(/^\//, '');
selectedKey.value = pathWithoutSlash;
}
},
{ immediate: true },
);
// 切换侧边栏状态
const toggleSidebar = () => {
if (isMobile.value) {
// 移动端使用抽屉模式
drawerVisible.value = !drawerVisible.value;
} else {
// 桌面端使用折叠模式
collapsed.value = !collapsed.value;
}
};
</script>
<template>
<n-layout :has-sider="!isMobile">
<!-- 移动端抽屉 -->
<n-drawer
v-if="isMobile"
v-model:show="drawerVisible"
:width="280"
placement="left"
:trap-focus="false"
:block-scroll="false"
>
<n-drawer-content title="菜单" :native-scrollbar="false">
<n-menu :options="menuOptions" :value="selectedKey" @update:value="handleMenuSelect" />
</n-drawer-content>
</n-drawer>
<!-- 桌面端侧边栏 -->
<n-layout-sider
v-if="!isMobile"
:collapsed="collapsed"
:native-scrollbar="false"
bordered
collapse-mode="width"
:collapsed-width="64"
:width="240"
show-trigger
@collapse="collapsed = true"
@expand="collapsed = false"
>
<n-menu
:collapsed="collapsed"
:collapsed-width="64"
:collapsed-icon-size="22"
:options="menuOptions"
:value="selectedKey"
@update:value="handleMenuSelect"
/>
</n-layout-sider>
<n-layout>
<n-layout-header
bordered
:style="{
height: '64px',
padding: isMobile ? '0 16px' : '0 24px',
display: 'flex',
alignItems: 'center',
}"
>
<n-button
quaternary
@click="toggleSidebar"
:style="{
marginRight: isMobile ? '8px' : '12px',
padding: isMobile ? '8px' : '6px',
}"
:size="isMobile ? 'medium' : 'small'"
>
<template #icon>
<n-icon :size="isMobile ? 20 : 18">
<svg viewBox="0 0 24 24">
<path
v-if="!isMobile && collapsed"
fill="currentColor"
d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"
/>
<path
v-else-if="!isMobile && !collapsed"
fill="currentColor"
d="M3 18h13v-2H3v2zm0-5h10v-2H3v2zm0-7v2h13V6H3zm18 9.59L17.42 12 21 8.41 19.59 7l-5 5 5 5L21 15.59z"
/>
<!-- 移动端始终显示菜单图标 -->
<path v-else fill="currentColor" d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />
</svg>
</n-icon>
</template>
</n-button>
<span
:style="{
fontSize: isMobile ? '16px' : '18px',
fontWeight: '500',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}"
>
Vue TS Example
</span>
</n-layout-header>
<n-layout-content
:content-style="{
padding: isMobile ? '16px' : '24px',
minHeight: 'calc(100vh - 64px - 72px)', // 减去头部和底部高度
}"
>
<router-view />
</n-layout-content>
<n-layout-footer
bordered
:style="{
padding: isMobile ? '16px' : '24px',
textAlign: 'center',
}"
>
<span
:style="{
color: 'var(--n-text-color-disabled)',
fontSize: isMobile ? '12px' : '14px',
}"
>
© 2025 Vue TS Example. All rights reserved.
</span>
</n-layout-footer>
</n-layout>
</n-layout>
</template>

View File

@ -66,7 +66,8 @@ export function Plugins() {
// https://github.com/dishait/vite-plugin-vue-meta-layouts
MetaLayouts({
defaultLayout: 'sakai-vue/AppLayout',
// defaultLayout: 'sakai-vue/AppLayout',
defaultLayout: 'naive-ui/AppLayout',
skipTopLevelRouteLayout: false, // 打开修复 https://github.com/JohnCampionJr/vite-plugin-vue-layouts/issues/134默认为 false 关闭
}),