Compare commits
14 Commits
0f1f8282db
...
renovate/a
Author | SHA1 | Date | |
---|---|---|---|
c2700ec516 | |||
383d8deead | |||
fcf09d887b | |||
145baf5e16 | |||
866145591f | |||
33528d64a7 | |||
30559d749e | |||
57d07d3cd0 | |||
11f4587681 | |||
669cd7070f | |||
5487dc321e | |||
ec4906f441 | |||
ad8c187edd | |||
997df3a4d4 |
9
.github/copilot-instructions.md
vendored
9
.github/copilot-instructions.md
vendored
@ -1,4 +1,11 @@
|
|||||||
# Project Conventions and Technical Guidelines
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
# GitHub Copilot Instructions
|
||||||
|
|
||||||
|
本文件定义了项目的代码生成规范,GitHub Copilot 和其他 AI 助手应遵循这些指令。
|
||||||
|
|
||||||
This document outlines the core technical choices, coding conventions, and configuration details for this project. Adhering to these guidelines ensures consistency and leverages the project's setup effectively.
|
This document outlines the core technical choices, coding conventions, and configuration details for this project. Adhering to these guidelines ensures consistency and leverages the project's setup effectively.
|
||||||
|
|
||||||
|
2
.github/workflows/lint.yaml
vendored
2
.github/workflows/lint.yaml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 🛠️ 设置Node环境
|
- name: 🛠️ 设置Node环境
|
||||||
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
|
uses: yanhao98/composite-actions/setup-node-environment@b4a2caa64aca72f8aeada59d0df3181a12df8268
|
||||||
- name: 🔍 静态代码分析
|
- name: 🔍 静态代码分析
|
||||||
run: pnpm run lint
|
run: pnpm run lint
|
||||||
- name: 📦 构建项目
|
- name: 📦 构建项目
|
||||||
|
6
.github/workflows/playwright.yaml
vendored
6
.github/workflows/playwright.yaml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
url: ${{ steps.surge_deploy.outputs.url }}
|
url: ${{ steps.surge_deploy.outputs.url }}
|
||||||
steps:
|
steps:
|
||||||
- name: ⚙️ 设置 Node 环境
|
- name: ⚙️ 设置 Node 环境
|
||||||
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
|
uses: yanhao98/composite-actions/setup-node-environment@b4a2caa64aca72f8aeada59d0df3181a12df8268
|
||||||
- name: 🔨 构建项目
|
- name: 🔨 构建项目
|
||||||
run: pnpm run build-only
|
run: pnpm run build-only
|
||||||
env:
|
env:
|
||||||
@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: 🚀 部署到 Surge
|
- name: 🚀 部署到 Surge
|
||||||
id: surge_deploy
|
id: surge_deploy
|
||||||
if: ${{ github.actor != 'nektos/act' }} # https://nektosact.com/usage/index.html#skipping-steps
|
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:
|
playwright:
|
||||||
needs: surge
|
needs: surge
|
||||||
@ -38,7 +38,7 @@ jobs:
|
|||||||
container: mcr.microsoft.com/playwright:v1.53.2-noble
|
container: mcr.microsoft.com/playwright:v1.53.2-noble
|
||||||
steps:
|
steps:
|
||||||
- name: ⚙️ 设置 Node 环境
|
- name: ⚙️ 设置 Node 环境
|
||||||
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
|
uses: yanhao98/composite-actions/setup-node-environment@b4a2caa64aca72f8aeada59d0df3181a12df8268
|
||||||
# - name: 📥 安装 Playwright 浏览器
|
# - name: 📥 安装 Playwright 浏览器
|
||||||
# run: pnpm exec playwright install --with-deps
|
# run: pnpm exec playwright install --with-deps
|
||||||
- name: ▶️ 运行 Playwright 测试
|
- name: ▶️ 运行 Playwright 测试
|
||||||
|
2
.github/workflows/vercel.yaml
vendored
2
.github/workflows/vercel.yaml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: ⚙️ 设置 Node 环境
|
- name: ⚙️ 设置 Node 环境
|
||||||
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
|
uses: yanhao98/composite-actions/setup-node-environment@b4a2caa64aca72f8aeada59d0df3181a12df8268
|
||||||
|
|
||||||
- name: 📥 拉取 Vercel 环境信息
|
- name: 📥 拉取 Vercel 环境信息
|
||||||
run: pnpm dlx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
|
run: pnpm dlx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
|
||||||
|
44
package.json
44
package.json
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"packageManager": "pnpm@10.12.4",
|
"packageManager": "pnpm@10.13.1",
|
||||||
"name": "vue-ts-example",
|
"name": "vue-ts-example",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -49,12 +49,12 @@
|
|||||||
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
||||||
"@pinia/colada": "^0.17.1",
|
"@pinia/colada": "^0.17.1",
|
||||||
"@primeuix/themes": "^1.1.2",
|
"@primeuix/themes": "^1.1.2",
|
||||||
"@splinetool/runtime": "^1.10.19",
|
"@splinetool/runtime": "^1.10.27",
|
||||||
"@types/p5": "^1.7.6",
|
"@types/p5": "^1.7.6",
|
||||||
"@types/sortablejs": "^1.15.8",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@unhead/vue": "^2.0.11",
|
"@unhead/vue": "^2.0.12",
|
||||||
"@vant/use": "^1.6.0",
|
"@vant/use": "^1.6.0",
|
||||||
"@vueuse/core": "^13.4.0",
|
"@vueuse/core": "^13.5.0",
|
||||||
"alova": "^3.3.4",
|
"alova": "^3.3.4",
|
||||||
"ant-design-vue": "~4.2.6",
|
"ant-design-vue": "~4.2.6",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
@ -78,19 +78,19 @@
|
|||||||
"primevue": "^4.3.5",
|
"primevue": "^4.3.5",
|
||||||
"radash": "^12.1.1",
|
"radash": "^12.1.1",
|
||||||
"radix-vue": "^1.9.17",
|
"radix-vue": "^1.9.17",
|
||||||
"reka-ui": "^2.3.1",
|
"reka-ui": "^2.3.2",
|
||||||
"satellite.js": "^6.0.0",
|
"satellite.js": "^6.0.1",
|
||||||
"sortablejs": "^1.15.6",
|
"sortablejs": "^1.15.6",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tdesign-icons-vue-next": "^0.3.6",
|
"tdesign-icons-vue-next": "^0.3.6",
|
||||||
"three": "^0.178.0",
|
"three": "^0.178.0",
|
||||||
"ts-enum-util": "^4.1.0",
|
"ts-enum-util": "^4.1.0",
|
||||||
"utils4u": "^4.2.3",
|
"utils4u": "^4.2.3",
|
||||||
"vant": "^4.9.20",
|
"vant": "^4.9.21",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.5.17",
|
||||||
"vue-data-ui": "^2.12.7",
|
"vue-data-ui": "^2.15.3",
|
||||||
"vue-draggable-plus": "^0.6.0",
|
"vue-draggable-plus": "^0.6.0",
|
||||||
"vue-i18n": "^11.1.7",
|
"vue-i18n": "^11.1.9",
|
||||||
"vue-page-stack": "^3.2.0",
|
"vue-page-stack": "^3.2.0",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
@ -108,16 +108,16 @@
|
|||||||
"@tsconfig/node22": "^22.0.2",
|
"@tsconfig/node22": "^22.0.2",
|
||||||
"@types/archiver": "^6.0.3",
|
"@types/archiver": "^6.0.3",
|
||||||
"@types/mockjs": "^1.0.10",
|
"@types/mockjs": "^1.0.10",
|
||||||
"@types/node": "^22.16.0",
|
"@types/node": "^22.16.3",
|
||||||
"@types/nprogress": "^0.2.3",
|
"@types/nprogress": "^0.2.3",
|
||||||
"@types/plotly.js-dist-min": "^2.3.4",
|
"@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",
|
"@vant/auto-import-resolver": "^1.3.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.0",
|
"@vitejs/plugin-vue": "^6.0.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.0.1",
|
"@vitejs/plugin-vue-jsx": "^5.0.1",
|
||||||
"@vitest/eslint-plugin": "^1.3.4",
|
"@vitest/eslint-plugin": "^1.3.4",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
"@vue/eslint-config-typescript": "^14.5.1",
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
@ -125,19 +125,20 @@
|
|||||||
"eruda": "^3.4.3",
|
"eruda": "^3.4.3",
|
||||||
"eslint": "^9.30.1",
|
"eslint": "^9.30.1",
|
||||||
"eslint-plugin-import-x": "^4.16.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-perfectionist": "^4.15.0",
|
||||||
"eslint-plugin-unicorn": "^59.0.1",
|
"eslint-plugin-unicorn": "^59.0.1",
|
||||||
"eslint-plugin-vue": "^10.2.0",
|
"eslint-plugin-vue": "^10.3.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"less": "^4.3.0",
|
"less": "^4.3.0",
|
||||||
"lint-staged": "^16.1.2",
|
"lint-staged": "^16.1.2",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
|
"naive-ui": "^2.42.0",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
"oxlint": "^1.5.0",
|
"oxlint": "^1.6.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"unocss": "66.3.2",
|
"unocss": "66.3.3",
|
||||||
"unocss-preset-animations": "^1.2.1",
|
"unocss-preset-animations": "^1.2.1",
|
||||||
"unocss-preset-chinese": "^0.3.3",
|
"unocss-preset-chinese": "^0.3.3",
|
||||||
"unocss-preset-shadcn": "^0.5.0",
|
"unocss-preset-shadcn": "^0.5.0",
|
||||||
@ -147,18 +148,19 @@
|
|||||||
"unplugin-vue-macros": "^2.14.5",
|
"unplugin-vue-macros": "^2.14.5",
|
||||||
"unplugin-vue-markdown": "^29.1.0",
|
"unplugin-vue-markdown": "^29.1.0",
|
||||||
"unplugin-vue-router": "^0.14.0",
|
"unplugin-vue-router": "^0.14.0",
|
||||||
"vite": "^7.0.0",
|
"vfonts": "^0.0.3",
|
||||||
"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-fake-server": "^2.2.0",
|
||||||
"vite-plugin-image-tools": "^2.0.2",
|
"vite-plugin-image-tools": "^2.0.2",
|
||||||
"vite-plugin-purgecss-updated-v5": "^1.2.6",
|
"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-static-copy": "^3.1.0",
|
||||||
"vite-plugin-vue-devtools": "^7.7.7",
|
"vite-plugin-vue-devtools": "^7.7.7",
|
||||||
"vite-plugin-vue-layouts": "^0.11.0",
|
"vite-plugin-vue-layouts": "^0.11.0",
|
||||||
"vite-plugin-vue-meta-layouts": "^0.5.1",
|
"vite-plugin-vue-meta-layouts": "^0.5.1",
|
||||||
"vite-plugin-webfont-dl": "^3.10.5",
|
"vite-plugin-webfont-dl": "^3.10.5",
|
||||||
"vue-component-type-helpers": "^3.0.0",
|
"vue-component-type-helpers": "^3.0.1",
|
||||||
"vue-tsc": "^3.0.0"
|
"vue-tsc": "^3.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1202
pnpm-lock.yaml
generated
1202
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -15,9 +15,11 @@ const themeConfig = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-config-provider :theme="themeConfig">
|
<n-config-provider preflight-style-disabled>
|
||||||
<RouterView />
|
<a-config-provider :theme="themeConfig">
|
||||||
</a-config-provider>
|
<RouterView />
|
||||||
|
</a-config-provider>
|
||||||
|
</n-config-provider>
|
||||||
|
|
||||||
<DynamicDialog /> <ConfirmDialog /> <Toast />
|
<DynamicDialog /> <ConfirmDialog /> <Toast />
|
||||||
</template>
|
</template>
|
||||||
|
321
src/layouts/naive-ui/AppLayout.vue
Normal file
321
src/layouts/naive-ui/AppLayout.vue
Normal 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>
|
11
src/pages/UI-components/NaiveUI/index.page.vue
Normal file
11
src/pages/UI-components/NaiveUI/index.page.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-card>
|
||||||
|
<n-divider title-placement="left">
|
||||||
|
<n-text depth="3"> Naive UI Components Divider </n-text>
|
||||||
|
</n-divider>
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -13,4 +13,9 @@ import 'primeicons/primeicons.css';
|
|||||||
import './reset/reset-primevue.css';
|
import './reset/reset-primevue.css';
|
||||||
import './reset/reset-antdv.less';
|
import './reset/reset-antdv.less';
|
||||||
|
|
||||||
|
// 通用字体
|
||||||
|
import 'vfonts/Lato.css';
|
||||||
|
// 等宽字体
|
||||||
|
import 'vfonts/FiraCode.css';
|
||||||
|
//
|
||||||
import 'virtual:uno.css';
|
import 'virtual:uno.css';
|
||||||
|
1
typed-router.d.ts
vendored
1
typed-router.d.ts
vendored
@ -47,6 +47,7 @@ declare module 'vue-router/auto-routes' {
|
|||||||
'UIComponentsInfiniteLoading': RouteRecordInfo<'UIComponentsInfiniteLoading', '/UI-components/infinite-loading', Record<never, never>, Record<never, never>>,
|
'UIComponentsInfiniteLoading': RouteRecordInfo<'UIComponentsInfiniteLoading', '/UI-components/infinite-loading', Record<never, never>, Record<never, never>>,
|
||||||
'UIComponentsInfiniteLoadingDetail': RouteRecordInfo<'UIComponentsInfiniteLoadingDetail', '/UI-components/infinite-loading/detail', Record<never, never>, Record<never, never>>,
|
'UIComponentsInfiniteLoadingDetail': RouteRecordInfo<'UIComponentsInfiniteLoadingDetail', '/UI-components/infinite-loading/detail', Record<never, never>, Record<never, never>>,
|
||||||
'UIComponentsInspiraUI': RouteRecordInfo<'UIComponentsInspiraUI', '/UI-components/InspiraUI', Record<never, never>, Record<never, never>>,
|
'UIComponentsInspiraUI': RouteRecordInfo<'UIComponentsInspiraUI', '/UI-components/InspiraUI', Record<never, never>, Record<never, never>>,
|
||||||
|
'UIComponentsNaiveUI': RouteRecordInfo<'UIComponentsNaiveUI', '/UI-components/NaiveUI', Record<never, never>, Record<never, never>>,
|
||||||
'UIComponentsPrimeVue': RouteRecordInfo<'UIComponentsPrimeVue', '/UI-components/PrimeVue', Record<never, never>, Record<never, never>>,
|
'UIComponentsPrimeVue': RouteRecordInfo<'UIComponentsPrimeVue', '/UI-components/PrimeVue', Record<never, never>, Record<never, never>>,
|
||||||
'UIComponentsShadcnVue': RouteRecordInfo<'UIComponentsShadcnVue', '/UI-components/ShadcnVue', Record<never, never>, Record<never, never>>,
|
'UIComponentsShadcnVue': RouteRecordInfo<'UIComponentsShadcnVue', '/UI-components/ShadcnVue', Record<never, never>, Record<never, never>>,
|
||||||
'VueMacrosDefineRender': RouteRecordInfo<'VueMacrosDefineRender', '/VueMacros/DefineRender', Record<never, never>, Record<never, never>>,
|
'VueMacrosDefineRender': RouteRecordInfo<'VueMacrosDefineRender', '/VueMacros/DefineRender', Record<never, never>, Record<never, never>>,
|
||||||
|
@ -11,11 +11,11 @@ import Vue from '@vitejs/plugin-vue';
|
|||||||
import VueJsx from '@vitejs/plugin-vue-jsx';
|
import VueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import UnoCSS from 'unocss/vite';
|
import UnoCSS from 'unocss/vite';
|
||||||
import UnpluginAutoImport from 'unplugin-auto-import/vite';
|
import AutoImport from 'unplugin-auto-import/vite';
|
||||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
||||||
import IconsResolver from 'unplugin-icons/resolver';
|
import IconsResolver from 'unplugin-icons/resolver';
|
||||||
import Icons from 'unplugin-icons/vite';
|
import Icons from 'unplugin-icons/vite';
|
||||||
import { AntDesignVueResolver, TDesignResolver } from 'unplugin-vue-components/resolvers';
|
import { AntDesignVueResolver, NaiveUiResolver, TDesignResolver } from 'unplugin-vue-components/resolvers';
|
||||||
import Components from 'unplugin-vue-components/vite';
|
import Components from 'unplugin-vue-components/vite';
|
||||||
import VueMacros from 'unplugin-vue-macros/vite';
|
import VueMacros from 'unplugin-vue-macros/vite';
|
||||||
import Markdown from 'unplugin-vue-markdown/vite';
|
import Markdown from 'unplugin-vue-markdown/vite';
|
||||||
@ -66,7 +66,8 @@ export function Plugins() {
|
|||||||
|
|
||||||
// https://github.com/dishait/vite-plugin-vue-meta-layouts
|
// https://github.com/dishait/vite-plugin-vue-meta-layouts
|
||||||
MetaLayouts({
|
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 关闭
|
skipTopLevelRouteLayout: false, // 打开修复 https://github.com/JohnCampionJr/vite-plugin-vue-layouts/issues/134,默认为 false 关闭
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@ -80,7 +81,7 @@ export function Plugins() {
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
// https://github.com/antfu/unplugin-auto-import
|
// https://github.com/antfu/unplugin-auto-import
|
||||||
UnpluginAutoImport({
|
AutoImport({
|
||||||
dirs: [
|
dirs: [
|
||||||
// 'src/composables',
|
// 'src/composables',
|
||||||
'src/stores',
|
'src/stores',
|
||||||
@ -98,6 +99,7 @@ export function Plugins() {
|
|||||||
{
|
{
|
||||||
'consola/browser': ['consola'],
|
'consola/browser': ['consola'],
|
||||||
'vue-router/auto': ['useLink'],
|
'vue-router/auto': ['useLink'],
|
||||||
|
'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
resolvers: [TDesignResolver({ esm: true, library: 'mobile-vue' }), VantResolver({ importStyle: true })],
|
resolvers: [TDesignResolver({ esm: true, library: 'mobile-vue' }), VantResolver({ importStyle: true })],
|
||||||
@ -124,6 +126,7 @@ export function Plugins() {
|
|||||||
TDesignResolver({ esm: true, library: 'mobile-vue' }),
|
TDesignResolver({ esm: true, library: 'mobile-vue' }),
|
||||||
VantResolver({ importStyle: true }),
|
VantResolver({ importStyle: true }),
|
||||||
PrimeVueResolver(/* { components: { prefix: 'P' } } */),
|
PrimeVueResolver(/* { components: { prefix: 'P' } } */),
|
||||||
|
NaiveUiResolver(),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user