lint:fix all
This commit is contained in:
100
.github/nuxt.config.ts
vendored
100
.github/nuxt.config.ts
vendored
@@ -1,11 +1,10 @@
|
|||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url'
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
const currentDir = dirname(fileURLToPath(import.meta.url));
|
const currentDir = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
devtools: { enabled: true },
|
|
||||||
modules: [
|
modules: [
|
||||||
'shadcn-nuxt',
|
'shadcn-nuxt',
|
||||||
'@vueuse/nuxt',
|
'@vueuse/nuxt',
|
||||||
@@ -16,42 +15,33 @@ export default defineNuxtConfig({
|
|||||||
'nuxt-og-image',
|
'nuxt-og-image',
|
||||||
'@nuxt/scripts',
|
'@nuxt/scripts',
|
||||||
'@nuxtjs/i18n',
|
'@nuxtjs/i18n',
|
||||||
'@nuxt/fonts',
|
'@nuxt/fonts'
|
||||||
],
|
],
|
||||||
shadcn: {
|
|
||||||
prefix: 'Ui',
|
|
||||||
componentDir: join(currentDir, './components/ui'),
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
dirs: [
|
dirs: [
|
||||||
{
|
{
|
||||||
path: './components',
|
path: './components',
|
||||||
ignore: ['**/*.ts'],
|
ignore: ['**/*.ts']
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
|
||||||
i18n: {
|
|
||||||
bundle: {
|
|
||||||
optimizeTranslationDirective: false,
|
|
||||||
},
|
|
||||||
strategy: 'prefix_except_default',
|
|
||||||
},
|
|
||||||
colorMode: {
|
|
||||||
classSuffix: '',
|
|
||||||
disableTransition: true,
|
|
||||||
},
|
},
|
||||||
|
devtools: { enabled: true },
|
||||||
css: [
|
css: [
|
||||||
join(currentDir, './assets/css/themes.css'),
|
join(currentDir, './assets/css/themes.css'),
|
||||||
'~/assets/css/tailwind.css',
|
'~/assets/css/tailwind.css'
|
||||||
],
|
],
|
||||||
|
colorMode: {
|
||||||
|
classSuffix: '',
|
||||||
|
disableTransition: true
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
documentDriven: true,
|
documentDriven: true,
|
||||||
highlight: {
|
highlight: {
|
||||||
theme: {
|
theme: {
|
||||||
default: 'github-light',
|
default: 'github-light',
|
||||||
dark: 'github-dark',
|
dark: 'github-dark'
|
||||||
},
|
},
|
||||||
preload: ['json', 'js', 'ts', 'html', 'css', 'vue', 'diff', 'shell', 'markdown', 'mdc', 'yaml', 'bash', 'ini', 'dotenv'],
|
preload: ['json', 'js', 'ts', 'html', 'css', 'vue', 'diff', 'shell', 'markdown', 'mdc', 'yaml', 'bash', 'ini', 'dotenv']
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
fields: [
|
fields: [
|
||||||
@@ -65,40 +55,50 @@ export default defineNuxtConfig({
|
|||||||
'editLink',
|
'editLink',
|
||||||
'prevNext',
|
'prevNext',
|
||||||
'breadcrumb',
|
'breadcrumb',
|
||||||
'fullpage',
|
'fullpage'
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
experimental: {
|
experimental: {
|
||||||
search: {
|
search: {
|
||||||
indexed: true,
|
indexed: true
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
clientBundle: {
|
|
||||||
scan: true,
|
|
||||||
sizeLimitKb: 512,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fonts: {
|
|
||||||
defaults: {
|
|
||||||
weights: ['300 800'],
|
|
||||||
},
|
},
|
||||||
|
compatibilityDate: '2025-05-13',
|
||||||
|
vite: {
|
||||||
|
plugins: [
|
||||||
|
tailwindcss()
|
||||||
|
],
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['debug']
|
||||||
|
}
|
||||||
},
|
},
|
||||||
typescript: {
|
typescript: {
|
||||||
tsConfig: {
|
tsConfig: {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
baseUrl: '.',
|
baseUrl: '.'
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
fonts: {
|
||||||
|
defaults: {
|
||||||
|
weights: ['300 800']
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
i18n: {
|
||||||
|
bundle: {
|
||||||
|
optimizeTranslationDirective: false
|
||||||
},
|
},
|
||||||
vite: {
|
strategy: 'prefix_except_default'
|
||||||
plugins: [
|
|
||||||
tailwindcss(),
|
|
||||||
],
|
|
||||||
optimizeDeps: {
|
|
||||||
include: ['debug'],
|
|
||||||
},
|
},
|
||||||
|
icon: {
|
||||||
|
clientBundle: {
|
||||||
|
scan: true,
|
||||||
|
sizeLimitKb: 512
|
||||||
|
}
|
||||||
},
|
},
|
||||||
compatibilityDate: '2025-05-13',
|
shadcn: {
|
||||||
});
|
prefix: 'Ui',
|
||||||
|
componentDir: join(currentDir, './components/ui')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@@ -65,4 +65,3 @@ provide('navigation', navigation)
|
|||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</UApp>
|
</UApp>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -1,49 +1,58 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// import type { NavigationMenuItem } from '@nuxt/ui'
|
// import type { NavigationMenuItem } from '@nuxt/ui'
|
||||||
import type { ContentNavigationItem } from "@nuxt/content";
|
import type { ContentNavigationItem } from '@nuxt/content'
|
||||||
const navigation = inject<Ref<ContentNavigationItem[]>>("navigation");
|
|
||||||
|
|
||||||
const isSettingsOpen = ref(false);
|
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||||
const isLoginModalOpen = ref(false);
|
|
||||||
const isRegisterModalOpen = ref(false);
|
const isSettingsOpen = ref(false)
|
||||||
|
const isLoginModalOpen = ref(false)
|
||||||
|
const isRegisterModalOpen = ref(false)
|
||||||
|
|
||||||
// const { header } = useAppConfig();
|
// const { header } = useAppConfig();
|
||||||
|
|
||||||
|
|
||||||
// 定义 props 来接收侧边栏状态和切换函数
|
// 定义 props 来接收侧边栏状态和切换函数
|
||||||
interface Props {
|
interface Props {
|
||||||
isSidebarOpen?: boolean;
|
isSidebarOpen?: boolean
|
||||||
toggleSidebar?: () => void;
|
toggleSidebar?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Props = withDefaults(defineProps<Props>(), {
|
const Props = withDefaults(defineProps<Props>(), {
|
||||||
isSidebarOpen: false,
|
isSidebarOpen: false,
|
||||||
toggleSidebar: () => { },
|
toggleSidebar: () => { }
|
||||||
});
|
})
|
||||||
|
|
||||||
// 登录注册函数
|
// 登录注册函数
|
||||||
const handleLoginRegister = (type: "login" | "register") => {
|
const handleLoginRegister = (type: 'login' | 'register') => {
|
||||||
if (type === "login") {
|
if (type === 'login') {
|
||||||
isLoginModalOpen.value = true;
|
isLoginModalOpen.value = true
|
||||||
} else if (type === "register") {
|
} else if (type === 'register') {
|
||||||
isRegisterModalOpen.value = true;
|
isRegisterModalOpen.value = true
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UHeader toggle-side="left" title="Estel Docs" mode="modal" class="bg-gray-50 dark:bg-gray-900">
|
<UHeader
|
||||||
|
toggle-side="left"
|
||||||
|
title="Estel Docs"
|
||||||
|
mode="modal"
|
||||||
|
class="bg-gray-50 dark:bg-gray-900"
|
||||||
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<h6></h6>
|
<h6 />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
<LogoPro class="h-5 w-auto mb-4" />
|
<LogoPro class="h-5 w-auto mb-4" />
|
||||||
<DocsAsideLeftTop />
|
<DocsAsideLeftTop />
|
||||||
<div class="mt-4 mb-4 border-t border-gray-200 dark:border-gray-700 w-9/10 mx-5" />
|
<div class="mt-4 mb-4 border-t border-gray-200 dark:border-gray-700 w-9/10 mx-5" />
|
||||||
<UContentNavigation highlight :navigation="navigation" color="primary" type="single" variant="pill" />
|
<UContentNavigation
|
||||||
|
highlight
|
||||||
|
:navigation="navigation"
|
||||||
|
color="primary"
|
||||||
|
type="single"
|
||||||
|
variant="pill"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #right>
|
<template #right>
|
||||||
@@ -51,16 +60,34 @@ const handleLoginRegister = (type: "login" | "register") => {
|
|||||||
<UColorModeButton />
|
<UColorModeButton />
|
||||||
<button
|
<button
|
||||||
class=" p-2 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
class=" p-2 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
||||||
title="页面设置" @click="isSettingsOpen = !isSettingsOpen">
|
title="页面设置"
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
@click="isSettingsOpen = !isSettingsOpen"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
>
|
||||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
class="w-5 h-5"
|
||||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<UTooltip text="Open on GitHub" :kbds="['meta', 'G']">
|
<UTooltip
|
||||||
|
text="Open on GitHub"
|
||||||
|
:kbds="['meta', 'G']"
|
||||||
|
>
|
||||||
<UButton
|
<UButton
|
||||||
color="neutral"
|
color="neutral"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -71,32 +98,45 @@ const handleLoginRegister = (type: "login" | "register") => {
|
|||||||
/>
|
/>
|
||||||
</UTooltip>
|
</UTooltip>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<button @click="handleLoginRegister('login')"
|
<button
|
||||||
class="px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors">
|
class="px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
|
||||||
|
@click="handleLoginRegister('login')"
|
||||||
|
>
|
||||||
登录
|
登录
|
||||||
</button>
|
</button>
|
||||||
<button @click="handleLoginRegister('register')"
|
<button
|
||||||
class="text-sm font-medium text-white bg-primary rounded-md px-3 py-2 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition-colors">
|
class="text-sm font-medium text-white bg-primary rounded-md px-3 py-2 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition-colors"
|
||||||
|
@click="handleLoginRegister('register')"
|
||||||
|
>
|
||||||
注册
|
注册
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
</UHeader>
|
</UHeader>
|
||||||
|
|
||||||
<!-- Theme Settings Panel -->
|
<!-- Theme Settings Panel -->
|
||||||
<ThemeSettings :is-open="isSettingsOpen" @close="isSettingsOpen = false" />
|
<ThemeSettings
|
||||||
|
:is-open="isSettingsOpen"
|
||||||
|
@close="isSettingsOpen = false"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 登录模态框 -->
|
<!-- 登录模态框 -->
|
||||||
<UModal v-model:open="isLoginModalOpen" title="登录" :dismissible="false">
|
<UModal
|
||||||
|
v-model:open="isLoginModalOpen"
|
||||||
|
title="登录"
|
||||||
|
:dismissible="false"
|
||||||
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<authLogin />
|
<authLogin />
|
||||||
</template>
|
</template>
|
||||||
</UModal>
|
</UModal>
|
||||||
|
|
||||||
<!-- 注册模态框 -->
|
<!-- 注册模态框 -->
|
||||||
<UModal v-model:open="isRegisterModalOpen" title="注册" :dismissible="false">
|
<UModal
|
||||||
|
v-model:open="isRegisterModalOpen"
|
||||||
|
title="注册"
|
||||||
|
:dismissible="false"
|
||||||
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<authRegister />
|
<authRegister />
|
||||||
</template>
|
</template>
|
||||||
|
@@ -3,7 +3,8 @@
|
|||||||
<div
|
<div
|
||||||
class="w-64 bg-white dark:bg-gray-900
|
class="w-64 bg-white dark:bg-gray-900
|
||||||
flex flex-col h-screen border-r border-gray-200 dark:border-gray-700
|
flex flex-col h-screen border-r border-gray-200 dark:border-gray-700
|
||||||
overflow-hidden">
|
overflow-hidden"
|
||||||
|
>
|
||||||
<div class="flex-shrink-0 p-4 border-gray-200 dark:border-gray-700">
|
<div class="flex-shrink-0 p-4 border-gray-200 dark:border-gray-700">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<LogoPro />
|
<LogoPro />
|
||||||
@@ -12,24 +13,33 @@
|
|||||||
<!-- Search Box -->
|
<!-- Search Box -->
|
||||||
<div class="p-4 border-gray-200 dark:border-gray-700">
|
<div class="p-4 border-gray-200 dark:border-gray-700">
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<UContentSearchButton :collapsed="false" label="搜索文档" class="w-full"
|
<UContentSearchButton
|
||||||
color="primary" />
|
:collapsed="false"
|
||||||
|
label="搜索文档"
|
||||||
|
class="w-full"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 可滚动的导航区域 -->
|
<!-- 可滚动的导航区域 -->
|
||||||
<div class=" h-full overflow-y-auto ">
|
<div class=" h-full overflow-y-auto ">
|
||||||
|
|
||||||
<!-- 导航 Section -->
|
<!-- 导航 Section -->
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<NuxtLink to="/"
|
<NuxtLink
|
||||||
|
to="/"
|
||||||
class="flex items-center px-4 py-2 text-sm font-medium rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 hover:shadow-sm transition-all duration-200"
|
class="flex items-center px-4 py-2 text-sm font-medium rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 hover:shadow-sm transition-all duration-200"
|
||||||
:class="{
|
:class="{
|
||||||
' text-blue-600 dark:text-blue-400 ':
|
' text-blue-600 dark:text-blue-400 ':
|
||||||
$route.path === '/',
|
$route.path === '/'
|
||||||
}">
|
}"
|
||||||
<Icon name="lucide-home" class="text-primary mr-2" size="20" />
|
>
|
||||||
|
<Icon
|
||||||
|
name="lucide-home"
|
||||||
|
class="text-primary mr-2"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
站点首页
|
站点首页
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div class="px-2">
|
<div class="px-2">
|
||||||
@@ -42,7 +52,13 @@
|
|||||||
|
|
||||||
<!-- 文档目录导航 -->
|
<!-- 文档目录导航 -->
|
||||||
<div class="mt-6 flex items-center justify-start pl-4 w-full">
|
<div class="mt-6 flex items-center justify-start pl-4 w-full">
|
||||||
<UContentNavigation highlight :navigation="navigation" color="primary" type="single" variant="pill" />
|
<UContentNavigation
|
||||||
|
highlight
|
||||||
|
:navigation="navigation"
|
||||||
|
color="primary"
|
||||||
|
type="single"
|
||||||
|
variant="pill"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,6 +66,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ContentNavigationItem } from "@nuxt/content";
|
import type { ContentNavigationItem } from '@nuxt/content'
|
||||||
const navigation = inject<Ref<ContentNavigationItem[]>>("navigation");
|
|
||||||
|
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||||
</script>
|
</script>
|
||||||
|
@@ -19,7 +19,7 @@ const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
|||||||
const directoryNavigation = computed(() => {
|
const directoryNavigation = computed(() => {
|
||||||
if (!navigation?.value) return []
|
if (!navigation?.value) return []
|
||||||
|
|
||||||
return navigation.value.map(item => {
|
return navigation.value.map((item) => {
|
||||||
// 如果是根目录文件(没有 children),直接返回
|
// 如果是根目录文件(没有 children),直接返回
|
||||||
if (!item.children || item.children.length === 0) {
|
if (!item.children || item.children.length === 0) {
|
||||||
return {
|
return {
|
||||||
|
@@ -12,10 +12,12 @@
|
|||||||
- mx-auto:左右自动外边距,使网格居中显示。
|
- mx-auto:左右自动外边距,使网格居中显示。
|
||||||
-->
|
-->
|
||||||
<div class="grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-4 gap-4 w-full mx-auto">
|
<div class="grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-4 gap-4 w-full mx-auto">
|
||||||
<div v-for="item in firstLevelItems" :key="item.path"
|
<div
|
||||||
|
v-for="item in firstLevelItems"
|
||||||
|
:key="item.path"
|
||||||
class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-5 sm:p-6 shadow-sm hover:shadow-md transition-all duration-200 cursor-pointer"
|
class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-5 sm:p-6 shadow-sm hover:shadow-md transition-all duration-200 cursor-pointer"
|
||||||
@click="navigateTo(item.path)">
|
@click="navigateTo(item.path)"
|
||||||
|
>
|
||||||
<!-- 卡片头部 -->
|
<!-- 卡片头部 -->
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<h2 class="font-sans text-xl sm:text-xl lg:text-2xl font-bold text-gray-900 dark:text-white leading-tight">
|
<h2 class="font-sans text-xl sm:text-xl lg:text-2xl font-bold text-gray-900 dark:text-white leading-tight">
|
||||||
@@ -27,19 +29,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 子页面列表 -->
|
<!-- 子页面列表 -->
|
||||||
<div v-if="getChildrenTitles(item).length > 0" class="">
|
<div
|
||||||
<div v-for="(child, index) in getChildrenWithIcons(item).slice(0, 5)" :key="index"
|
v-if="getChildrenTitles(item).length > 0"
|
||||||
|
class=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(child, index) in getChildrenWithIcons(item).slice(0, 5)"
|
||||||
|
:key="index"
|
||||||
class="flex items-center justify-between
|
class="flex items-center justify-between
|
||||||
text-sm sm:text-base lg:text-lg text-gray-700 dark:text-gray-300
|
text-sm sm:text-base lg:text-lg text-gray-700 dark:text-gray-300
|
||||||
hover:text-blue-600 dark:hover:text-blue-400
|
hover:text-blue-600 dark:hover:text-blue-400
|
||||||
transition-colors duration-200 py-1
|
transition-colors duration-200 py-1
|
||||||
border-b border-gray-200 dark:border-gray-700 pt-2">
|
border-b border-gray-200 dark:border-gray-700 pt-2"
|
||||||
|
>
|
||||||
<div class="flex items-center flex-1 min-w-0 pl-1 ">
|
<div class="flex items-center flex-1 min-w-0 pl-1 ">
|
||||||
<UIcon :name="(typeof child.icon === 'string' ? child.icon : 'lucide-file-text')" class="mr-2 text-gray-400" size="14" />
|
<UIcon
|
||||||
|
:name="(typeof child.icon === 'string' ? child.icon : 'lucide-file-text')"
|
||||||
|
class="mr-2 text-gray-400"
|
||||||
|
size="14"
|
||||||
|
/>
|
||||||
<span class="text-base font-medium font-sans">{{ child.title }}</span>
|
<span class="text-base font-medium font-sans">{{ child.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<UIcon name="lucide-chevron-right" class="text-gray-400 flex-shrink-0 ml-2" size="16" />
|
<UIcon
|
||||||
|
name="lucide-chevron-right"
|
||||||
|
class="text-gray-400 flex-shrink-0 ml-2"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,51 +63,51 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ContentNavigationItem } from "@nuxt/content";
|
import type { ContentNavigationItem } from '@nuxt/content'
|
||||||
|
|
||||||
const navigation = inject<Ref<ContentNavigationItem[]>>("navigation");
|
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||||
|
|
||||||
// 计算属性:提取第一级数据
|
// 计算属性:提取第一级数据
|
||||||
const firstLevelItems = computed(() => {
|
const firstLevelItems = computed(() => {
|
||||||
if (!navigation?.value) return [];
|
if (!navigation?.value) return []
|
||||||
return navigation.value
|
return navigation.value
|
||||||
.filter(item => item.title && item.path)
|
.filter(item => item.title && item.path)
|
||||||
.map(item => ({
|
.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
icon: item.icon || 'lucide-book-open'
|
icon: item.icon || 'lucide-book-open'
|
||||||
}));
|
}))
|
||||||
});
|
})
|
||||||
|
|
||||||
// 获取子页面数量
|
// 获取子页面数量
|
||||||
const getChildrenCount = (item: ContentNavigationItem) => {
|
const getChildrenCount = (item: ContentNavigationItem) => {
|
||||||
return item.children?.length || 0;
|
return item.children?.length || 0
|
||||||
};
|
}
|
||||||
|
|
||||||
// 获取子页面标题列表
|
// 获取子页面标题列表
|
||||||
const getChildrenTitles = (item: ContentNavigationItem) => {
|
const getChildrenTitles = (item: ContentNavigationItem) => {
|
||||||
if (!item.children) return [];
|
if (!item.children) return []
|
||||||
return item.children.map(child => child.title).filter(Boolean);
|
return item.children.map(child => child.title).filter(Boolean)
|
||||||
};
|
}
|
||||||
|
|
||||||
// 获取子页面数据(包含图标)
|
// 获取子页面数据(包含图标)
|
||||||
const getChildrenWithIcons = (item: ContentNavigationItem) => {
|
const getChildrenWithIcons = (item: ContentNavigationItem) => {
|
||||||
if (!item.children) return [];
|
if (!item.children) return []
|
||||||
return item.children.filter(child => child.title);
|
return item.children.filter(child => child.title)
|
||||||
};
|
}
|
||||||
|
|
||||||
// 获取描述信息
|
// 获取描述信息
|
||||||
const getDescription = (item: ContentNavigationItem) => {
|
const getDescription = (item: ContentNavigationItem) => {
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
return `${item.children.length} 个子页面`;
|
return `${item.children.length} 个子页面`
|
||||||
}
|
}
|
||||||
return "文档页面";
|
return '文档页面'
|
||||||
};
|
}
|
||||||
|
|
||||||
// 导航方法
|
// 导航方法
|
||||||
const navigateTo = (path: string) => {
|
const navigateTo = (path: string) => {
|
||||||
if (path) {
|
if (path) {
|
||||||
window.location.href = path;
|
window.location.href = path
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
@@ -6,8 +6,8 @@
|
|||||||
<div class="w-full top-0 left-0 bg-gray-50 dark:bg-gray-900">
|
<div class="w-full top-0 left-0 bg-gray-50 dark:bg-gray-900">
|
||||||
<!-- 欢迎卡片 -->
|
<!-- 欢迎卡片 -->
|
||||||
<div
|
<div
|
||||||
class="bg-slate-200 dark:bg-slate-600 rounded-lg border border-gray-200 dark:border-gray-700 p-2 sm:p-4 shadow-sm">
|
class="bg-slate-200 dark:bg-slate-600 rounded-lg border border-gray-200 dark:border-gray-700 p-2 sm:p-4 shadow-sm"
|
||||||
|
>
|
||||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white mb-2">
|
<h1 class="text-xl font-bold text-gray-900 dark:text-white mb-2">
|
||||||
Hi 👋, 欢迎使用简单文档系统
|
Hi 👋, 欢迎使用简单文档系统
|
||||||
</h1>
|
</h1>
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-8">
|
<div class="h-8">
|
||||||
<NuxtLink to="/" class="flex items-center space-x-3">
|
<NuxtLink
|
||||||
|
to="/"
|
||||||
|
class="flex items-center space-x-3"
|
||||||
|
>
|
||||||
<div class="w-8 h-8 bg-primary rounded-xl flex items-center justify-center shadow-md">
|
<div class="w-8 h-8 bg-primary rounded-xl flex items-center justify-center shadow-md">
|
||||||
<span class="text-white font-bold text-lg">E</span>
|
<span class="text-white font-bold text-lg">E</span>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -15,7 +15,9 @@
|
|||||||
<!-- 头部 -->
|
<!-- 头部 -->
|
||||||
<div class="sticky top-0 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 px-6 py-4">
|
<div class="sticky top-0 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 px-6 py-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">页面设置</h2>
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
|
页面设置
|
||||||
|
</h2>
|
||||||
<UButton
|
<UButton
|
||||||
color="neutral"
|
color="neutral"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -30,7 +32,9 @@
|
|||||||
<div class="p-6 space-y-8">
|
<div class="p-6 space-y-8">
|
||||||
<!-- 主题 -->
|
<!-- 主题 -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">主题</h3>
|
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">
|
||||||
|
主题
|
||||||
|
</h3>
|
||||||
<div class="grid grid-cols-3 gap-3">
|
<div class="grid grid-cols-3 gap-3">
|
||||||
<UButton
|
<UButton
|
||||||
v-for="theme in themes"
|
v-for="theme in themes"
|
||||||
@@ -47,7 +51,9 @@
|
|||||||
|
|
||||||
<!-- 字体 -->
|
<!-- 字体 -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">字体</h3>
|
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">
|
||||||
|
字体
|
||||||
|
</h3>
|
||||||
<div class="grid grid-cols-3 gap-3">
|
<div class="grid grid-cols-3 gap-3">
|
||||||
<UButton
|
<UButton
|
||||||
v-for="font in fonts"
|
v-for="font in fonts"
|
||||||
@@ -64,7 +70,9 @@
|
|||||||
|
|
||||||
<!-- 字号 -->
|
<!-- 字号 -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">字号</h3>
|
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">
|
||||||
|
字号
|
||||||
|
</h3>
|
||||||
<div class="grid grid-cols-5 gap-2">
|
<div class="grid grid-cols-5 gap-2">
|
||||||
<UButton
|
<UButton
|
||||||
v-for="size in fontSizes"
|
v-for="size in fontSizes"
|
||||||
@@ -81,7 +89,9 @@
|
|||||||
|
|
||||||
<!-- 主题色 -->
|
<!-- 主题色 -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">主题色</h3>
|
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">
|
||||||
|
主题色
|
||||||
|
</h3>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<UButton
|
<UButton
|
||||||
v-for="color in themeColors"
|
v-for="color in themeColors"
|
||||||
@@ -105,7 +115,9 @@
|
|||||||
|
|
||||||
<!-- 自定义主题色 -->
|
<!-- 自定义主题色 -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">自定义主题色</h3>
|
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">
|
||||||
|
自定义主题色
|
||||||
|
</h3>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
v-model="customColor"
|
v-model="customColor"
|
||||||
@@ -124,7 +136,9 @@
|
|||||||
|
|
||||||
<!-- 代码块主题 -->
|
<!-- 代码块主题 -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">代码块主题</h3>
|
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">
|
||||||
|
代码块主题
|
||||||
|
</h3>
|
||||||
<USelect
|
<USelect
|
||||||
v-model="selectedCodeTheme"
|
v-model="selectedCodeTheme"
|
||||||
:options="codeThemes"
|
:options="codeThemes"
|
||||||
@@ -136,7 +150,9 @@
|
|||||||
|
|
||||||
<!-- 图注格式 -->
|
<!-- 图注格式 -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">图注格式</h3>
|
<h3 class="text-base font-medium text-gray-900 dark:text-white mb-4">
|
||||||
|
图注格式
|
||||||
|
</h3>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<UButton
|
<UButton
|
||||||
v-for="format in captionFormats"
|
v-for="format in captionFormats"
|
||||||
@@ -204,7 +220,7 @@ const {
|
|||||||
selectedCaptionFormat,
|
selectedCaptionFormat,
|
||||||
applyCustomColor,
|
applyCustomColor,
|
||||||
resetSettings
|
resetSettings
|
||||||
} = useTheme();
|
} = useTheme()
|
||||||
|
|
||||||
// 监听键盘事件(只监听 ESC 键)
|
// 监听键盘事件(只监听 ESC 键)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@@ -1,25 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-row items-center mt-2 mb-2">
|
<div class="flex flex-row items-center mt-2 mb-2">
|
||||||
<NuxtLink :to="to || href" :target="(blank && '_blank') || target">
|
<NuxtLink
|
||||||
<UButton :variant="variant" :size="size" :icon="icon" :trailing-icon="trailingIcon" class="min-h-10 max-h-12">
|
:to="to || href"
|
||||||
<slot/>
|
:target="(blank && '_blank') || target"
|
||||||
|
>
|
||||||
|
<UButton
|
||||||
|
:variant="variant"
|
||||||
|
:size="size"
|
||||||
|
:icon="icon"
|
||||||
|
:trailing-icon="trailingIcon"
|
||||||
|
class="min-h-10 max-h-12"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
</UButton>
|
</UButton>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
type Target = '_blank' | '_parent' | '_self' | '_top' | (string & object) | null | undefined;
|
type Target = '_blank' | '_parent' | '_self' | '_top' | (string & object) | null | undefined
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
variant?: 'solid' | 'outline' | 'ghost' | 'link' | 'soft';
|
variant?: 'solid' | 'outline' | 'ghost' | 'link' | 'soft'
|
||||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||||
color?: 'gray' | 'red' | 'orange' | 'amber' | 'yellow' | 'lime' | 'green' | 'emerald' | 'teal' | 'cyan' | 'sky' | 'blue' | 'indigo' | 'violet' | 'purple' | 'fuchsia' | 'pink' | 'rose' | 'white' | 'black';
|
color?: 'gray' | 'red' | 'orange' | 'amber' | 'yellow' | 'lime' | 'green' | 'emerald' | 'teal' | 'cyan' | 'sky' | 'blue' | 'indigo' | 'violet' | 'purple' | 'fuchsia' | 'pink' | 'rose' | 'white' | 'black'
|
||||||
icon?: string;
|
icon?: string
|
||||||
to?: string;
|
to?: string
|
||||||
href?: string;
|
href?: string
|
||||||
target?: Target;
|
target?: Target
|
||||||
trailingIcon?: string;
|
trailingIcon?: string
|
||||||
blank?: boolean;
|
blank?: boolean
|
||||||
}>();
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
@@ -7,7 +7,7 @@ const isLight = computed({
|
|||||||
},
|
},
|
||||||
set(_isLight) {
|
set(_isLight) {
|
||||||
colorMode.preference = _isLight ? 'light' : 'dark'
|
colorMode.preference = _isLight ? 'light' : 'dark'
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ const isLight = computed({
|
|||||||
:ui="{
|
:ui="{
|
||||||
base: 'w-70 h-35 rounded-lg rotate-90 data-[state=checked]:bg-[var(--ui-color-neutral-200)]',
|
base: 'w-70 h-35 rounded-lg rotate-90 data-[state=checked]:bg-[var(--ui-color-neutral-200)]',
|
||||||
thumb: 'data-[state=checked]:translate-x-35 data-[state=checked]:rtl:-translate-x-35 rounded-lg size-34',
|
thumb: 'data-[state=checked]:translate-x-35 data-[state=checked]:rtl:-translate-x-35 rounded-lg size-34',
|
||||||
icon: 'rotate-270 size-8',
|
icon: 'rotate-270 size-8'
|
||||||
}"
|
}"
|
||||||
@click="isDark = !isDark"
|
@click="isDark = !isDark"
|
||||||
/>
|
/>
|
||||||
|
@@ -1,6 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<UPageCard :title="title" :description="description" :spotlight="spotlight" :icon="icon" class="">
|
<UPageCard
|
||||||
<slot/>
|
:title="title"
|
||||||
|
:description="description"
|
||||||
|
:spotlight="spotlight"
|
||||||
|
:icon="icon"
|
||||||
|
class=""
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
v-if="img"
|
v-if="img"
|
||||||
:src="img"
|
:src="img"
|
||||||
@@ -8,7 +14,6 @@
|
|||||||
/>
|
/>
|
||||||
<span> {{ content }} </span>
|
<span> {{ content }} </span>
|
||||||
</UPageCard>
|
</UPageCard>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- ::card
|
<!-- ::card
|
||||||
@@ -22,19 +27,17 @@ Image Card
|
|||||||
Beautifully designed **Nuxt Content** template with **shadcn-vue**. _Customizable. Compatible. Open Source._
|
Beautifully designed **Nuxt Content** template with **shadcn-vue**. _Customizable. Compatible. Open Source._
|
||||||
:: -->
|
:: -->
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const {
|
||||||
const {
|
content = ''
|
||||||
content="",
|
|
||||||
|
|
||||||
} =defineProps<{
|
|
||||||
title?: string;
|
|
||||||
content?: string;
|
|
||||||
img?: string;
|
|
||||||
icon?: string;
|
|
||||||
description?: string;
|
|
||||||
spotlight?:boolean;
|
|
||||||
slot?:string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
} = defineProps<{
|
||||||
|
title?: string
|
||||||
|
content?: string
|
||||||
|
img?: string
|
||||||
|
icon?: string
|
||||||
|
description?: string
|
||||||
|
spotlight?: boolean
|
||||||
|
slot?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
@@ -1,6 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<UTabs :items="tabItems" class="w-full" :unmount-on-hide="false" variant="link">
|
<UTabs
|
||||||
<template v-for="item in tabItems" #[item.slot]="{ item: slotItem }">
|
:items="tabItems"
|
||||||
|
class="w-full"
|
||||||
|
:unmount-on-hide="false"
|
||||||
|
variant="link"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="item in tabItems"
|
||||||
|
#[item.slot]="{ item: slotItem }"
|
||||||
|
>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<component :is="getSlotContent(slotItem)" />
|
<component :is="getSlotContent(slotItem)" />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,8 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<UPage class="mt-4 mb-4 bg-white dark:bg-gray-900 rounded-xl border border-gray-300 dark:border-gray-700">
|
<UPage class="mt-4 mb-4 bg-white dark:bg-gray-900 rounded-xl border border-gray-300 dark:border-gray-700">
|
||||||
<div v-if="title" class="flex items-center font-mono text-base m-3 text-gray-700 dark:text-gray-300 rounded-xl ">
|
<div
|
||||||
|
v-if="title"
|
||||||
<Icon v-if="icon" :name="icon" class="mr-2" size="20" />
|
class="flex items-center font-mono text-base m-3 text-gray-700 dark:text-gray-300 rounded-xl "
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
v-if="icon"
|
||||||
|
:name="icon"
|
||||||
|
class="mr-2"
|
||||||
|
size="20"
|
||||||
|
/>
|
||||||
|
|
||||||
<span>{{ title }}</span>
|
<span>{{ title }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +40,10 @@
|
|||||||
class="file-tree-item"
|
class="file-tree-item"
|
||||||
>
|
>
|
||||||
<div class="flex items-center py-1 px-2">
|
<div class="flex items-center py-1 px-2">
|
||||||
<div class="flex items-center" :style="{ marginLeft: '0px' }">
|
<div
|
||||||
|
class="flex items-center"
|
||||||
|
:style="{ marginLeft: '0px' }"
|
||||||
|
>
|
||||||
<Icon
|
<Icon
|
||||||
v-if="showIcon"
|
v-if="showIcon"
|
||||||
:name="item.icon || 'lucide-file'"
|
:name="item.icon || 'lucide-file'"
|
||||||
@@ -61,38 +71,38 @@
|
|||||||
</template>
|
</template>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
</UPage>
|
</UPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
type InputTreeItem = string | {
|
type InputTreeItem = string | {
|
||||||
[key: string]: InputTreeItem[];
|
[key: string]: InputTreeItem[]
|
||||||
};
|
}
|
||||||
|
|
||||||
type FileTreeItemDiff = 'none' | 'addition' | 'deletion';
|
type FileTreeItemDiff = 'none' | 'addition' | 'deletion'
|
||||||
|
|
||||||
interface FileTreeItem {
|
interface FileTreeItem {
|
||||||
title: string;
|
title: string
|
||||||
icon?: string;
|
icon?: string
|
||||||
children?: FileTreeItem[];
|
children?: FileTreeItem[]
|
||||||
highlighted?: boolean;
|
highlighted?: boolean
|
||||||
diff?: FileTreeItemDiff;
|
diff?: FileTreeItemDiff
|
||||||
isFolder?: boolean;
|
isFolder?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
tree,
|
tree,
|
||||||
autoSlash = true,
|
autoSlash = true,
|
||||||
showArrow = false,
|
showArrow = false,
|
||||||
showIcon = true,
|
showIcon = true
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
title?: string;
|
title?: string
|
||||||
icon?: string;
|
icon?: string
|
||||||
autoSlash?: boolean;
|
autoSlash?: boolean
|
||||||
showArrow?: boolean;
|
showArrow?: boolean
|
||||||
showIcon?: boolean;
|
showIcon?: boolean
|
||||||
tree: InputTreeItem[];
|
tree: InputTreeItem[]
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
// 默认图标映射
|
// 默认图标映射
|
||||||
const defaultIcons = {
|
const defaultIcons = {
|
||||||
@@ -103,38 +113,38 @@ const defaultIcons = {
|
|||||||
json: 'simple-icons-json',
|
json: 'simple-icons-json',
|
||||||
folder: 'lucide-folder',
|
folder: 'lucide-folder',
|
||||||
file: 'lucide-file'
|
file: 'lucide-file'
|
||||||
};
|
}
|
||||||
|
|
||||||
function getIcon(filename: string, type: 'folder' | 'file'): string {
|
function getIcon(filename: string, type: 'folder' | 'file'): string {
|
||||||
if (filename === '...') return 'lucide-more-horizontal';
|
if (filename === '...') return 'lucide-more-horizontal'
|
||||||
if (filename.endsWith('/')) return defaultIcons.folder;
|
if (filename.endsWith('/')) return defaultIcons.folder
|
||||||
|
|
||||||
const parts = filename.split('.');
|
const parts = filename.split('.')
|
||||||
const extension = parts.length > 1 ? parts[parts.length - 1]?.toLowerCase() || '' : '';
|
const extension = parts.length > 1 ? parts[parts.length - 1]?.toLowerCase() || '' : ''
|
||||||
|
|
||||||
// 根据扩展名返回对应图标
|
// 根据扩展名返回对应图标
|
||||||
switch (extension) {
|
switch (extension) {
|
||||||
case 'vue': return defaultIcons.vue;
|
case 'vue': return defaultIcons.vue
|
||||||
case 'ts': return defaultIcons.ts;
|
case 'ts': return defaultIcons.ts
|
||||||
case 'js': return defaultIcons.js;
|
case 'js': return defaultIcons.js
|
||||||
case 'md': return defaultIcons.md;
|
case 'md': return defaultIcons.md
|
||||||
case 'json': return defaultIcons.json;
|
case 'json': return defaultIcons.json
|
||||||
default: return type === 'file' ? defaultIcons.file : defaultIcons.folder;
|
default: return type === 'file' ? defaultIcons.file : defaultIcons.folder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItem(key: string, type: 'folder' | 'file', children?: InputTreeItem[]): FileTreeItem {
|
function getItem(key: string, type: 'folder' | 'file', children?: InputTreeItem[]): FileTreeItem {
|
||||||
let title = key;
|
let title = key
|
||||||
let highlighted = false;
|
let highlighted = false
|
||||||
|
|
||||||
if (title.startsWith('^') && title.endsWith('^')) {
|
if (title.startsWith('^') && title.endsWith('^')) {
|
||||||
title = title.substring(1, title.length - 1);
|
title = title.substring(1, title.length - 1)
|
||||||
highlighted = true;
|
highlighted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let diff: FileTreeItemDiff = 'none';
|
let diff: FileTreeItemDiff = 'none'
|
||||||
if (title.startsWith('+')) diff = 'addition';
|
if (title.startsWith('+')) diff = 'addition'
|
||||||
else if (title.startsWith('-')) diff = 'deletion';
|
else if (title.startsWith('-')) diff = 'deletion'
|
||||||
|
|
||||||
if (type === 'file') {
|
if (type === 'file') {
|
||||||
return {
|
return {
|
||||||
@@ -143,7 +153,7 @@ function getItem(key: string, type: 'folder' | 'file', children?: InputTreeItem[
|
|||||||
highlighted,
|
highlighted,
|
||||||
diff,
|
diff,
|
||||||
isFolder: false
|
isFolder: false
|
||||||
};
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
title: `${title}${autoSlash ? '/' : ''}`,
|
title: `${title}${autoSlash ? '/' : ''}`,
|
||||||
@@ -152,35 +162,35 @@ function getItem(key: string, type: 'folder' | 'file', children?: InputTreeItem[
|
|||||||
highlighted,
|
highlighted,
|
||||||
diff,
|
diff,
|
||||||
isFolder: true
|
isFolder: true
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTree(tree: InputTreeItem[]): FileTreeItem[] {
|
function getTree(tree: InputTreeItem[]): FileTreeItem[] {
|
||||||
const res: FileTreeItem[] = [];
|
const res: FileTreeItem[] = []
|
||||||
|
|
||||||
for (const item of tree) {
|
for (const item of tree) {
|
||||||
if (typeof item === 'string') {
|
if (typeof item === 'string') {
|
||||||
res.push(getItem(item, 'file'));
|
res.push(getItem(item, 'file'))
|
||||||
} else if (typeof item === 'object' && item !== null) {
|
} else if (typeof item === 'object' && item !== null) {
|
||||||
for (const key of Object.keys(item)) {
|
for (const key of Object.keys(item)) {
|
||||||
const children = (item as Record<string, InputTreeItem[]>)[key];
|
const children = (item as Record<string, InputTreeItem[]>)[key]
|
||||||
if (children) {
|
if (children) {
|
||||||
res.push(getItem(key, 'folder', children));
|
res.push(getItem(key, 'folder', children))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedTree = computed(() => {
|
const parsedTree = computed(() => {
|
||||||
return getTree(tree);
|
return getTree(tree)
|
||||||
});
|
})
|
||||||
|
|
||||||
// 使用 provide/inject 来管理展开状态
|
// 使用 provide/inject 来管理展开状态
|
||||||
const expandedState = ref(new Set<string>());
|
const expandedState = ref(new Set<string>())
|
||||||
|
|
||||||
provide('expandedState', expandedState);
|
provide('expandedState', expandedState)
|
||||||
</script>
|
</script>
|
||||||
|
@@ -10,7 +10,10 @@
|
|||||||
@click="toggleFolder"
|
@click="toggleFolder"
|
||||||
>
|
>
|
||||||
<!-- 缩进 -->
|
<!-- 缩进 -->
|
||||||
<div class="flex items-center" :style="{ marginLeft: level * 16 + 'px' }">
|
<div
|
||||||
|
class="flex items-center"
|
||||||
|
:style="{ marginLeft: level * 16 + 'px' }"
|
||||||
|
>
|
||||||
<!-- 箭头(仅文件夹且有子项时显示) -->
|
<!-- 箭头(仅文件夹且有子项时显示) -->
|
||||||
<Icon
|
<Icon
|
||||||
v-if="showArrow && item.isFolder && item.children && item.children.length > 0"
|
v-if="showArrow && item.isFolder && item.children && item.children.length > 0"
|
||||||
@@ -18,7 +21,10 @@
|
|||||||
class="mr-1 text-gray-400 w-4 h-4 transition-transform"
|
class="mr-1 text-gray-400 w-4 h-4 transition-transform"
|
||||||
:class="{ 'rotate-90': isExpanded }"
|
:class="{ 'rotate-90': isExpanded }"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="showArrow" class="w-4 mr-1"></div>
|
<div
|
||||||
|
v-else-if="showArrow"
|
||||||
|
class="w-4 mr-1"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 图标 -->
|
<!-- 图标 -->
|
||||||
<Icon
|
<Icon
|
||||||
@@ -47,7 +53,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 子项 -->
|
<!-- 子项 -->
|
||||||
<div v-if="item.children && item.children.length > 0 && isExpanded" class="ml-4">
|
<div
|
||||||
|
v-if="item.children && item.children.length > 0 && isExpanded"
|
||||||
|
class="ml-4"
|
||||||
|
>
|
||||||
<FileTreeItem
|
<FileTreeItem
|
||||||
v-for="child in item.children"
|
v-for="child in item.children"
|
||||||
:key="child.title"
|
:key="child.title"
|
||||||
@@ -61,48 +70,48 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
type FileTreeItemDiff = 'none' | 'addition' | 'deletion';
|
type FileTreeItemDiff = 'none' | 'addition' | 'deletion'
|
||||||
|
|
||||||
interface FileTreeItem {
|
interface FileTreeItem {
|
||||||
title: string;
|
title: string
|
||||||
icon?: string;
|
icon?: string
|
||||||
children?: FileTreeItem[];
|
children?: FileTreeItem[]
|
||||||
highlighted?: boolean;
|
highlighted?: boolean
|
||||||
diff?: FileTreeItemDiff;
|
diff?: FileTreeItemDiff
|
||||||
isFolder?: boolean;
|
isFolder?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: FileTreeItem;
|
item: FileTreeItem
|
||||||
level: number;
|
level: number
|
||||||
showArrow: boolean;
|
showArrow: boolean
|
||||||
showIcon: boolean;
|
showIcon: boolean
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const expandedState = inject('expandedState', ref(new Set<string>()));
|
const expandedState = inject('expandedState', ref(new Set<string>()))
|
||||||
const itemKey = computed(() => `${props.item.title}-${props.level}`);
|
const itemKey = computed(() => `${props.item.title}-${props.level}`)
|
||||||
|
|
||||||
const isExpanded = computed({
|
const isExpanded = computed({
|
||||||
get: () => expandedState.value.has(itemKey.value),
|
get: () => expandedState.value.has(itemKey.value),
|
||||||
set: (value: boolean) => {
|
set: (value: boolean) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
expandedState.value.add(itemKey.value);
|
expandedState.value.add(itemKey.value)
|
||||||
} else {
|
} else {
|
||||||
expandedState.value.delete(itemKey.value);
|
expandedState.value.delete(itemKey.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// 初始化时展开所有文件夹
|
// 初始化时展开所有文件夹
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.item.isFolder && props.item.children && props.item.children.length > 0) {
|
if (props.item.isFolder && props.item.children && props.item.children.length > 0) {
|
||||||
isExpanded.value = true;
|
isExpanded.value = true
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
function toggleFolder() {
|
function toggleFolder() {
|
||||||
if (props.item.isFolder && props.item.children && props.item.children.length > 0) {
|
if (props.item.isFolder && props.item.children && props.item.children.length > 0) {
|
||||||
isExpanded.value = !isExpanded.value;
|
isExpanded.value = !isExpanded.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@@ -7,7 +7,10 @@
|
|||||||
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
|
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
|
||||||
class="w-full h-full min-h-[600px] overflow-hidden rounded-md"
|
class="w-full h-full min-h-[600px] overflow-hidden rounded-md"
|
||||||
/>
|
/>
|
||||||
<USkeleton v-else class="w-full min-h-[600px] rounded-md" />
|
<USkeleton
|
||||||
|
v-else
|
||||||
|
class="w-full min-h-[600px] rounded-md"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -19,31 +22,31 @@ const {
|
|||||||
branch = 'main',
|
branch = 'main',
|
||||||
dir = '',
|
dir = '',
|
||||||
file,
|
file,
|
||||||
title = 'Playground',
|
title = 'Playground'
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
provider: 'stackblitz' | 'codesandbox';
|
provider: 'stackblitz' | 'codesandbox'
|
||||||
id?: string;
|
id?: string
|
||||||
repo?: string;
|
repo?: string
|
||||||
branch?: string;
|
branch?: string
|
||||||
dir?: string;
|
dir?: string
|
||||||
file: string;
|
file: string
|
||||||
title?: string;
|
title?: string
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const url = ref('');
|
const url = ref('')
|
||||||
const colorMode = useColorMode();
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (provider === 'stackblitz') {
|
if (provider === 'stackblitz') {
|
||||||
if (repo)
|
if (repo)
|
||||||
url.value = `https://stackblitz.com/github/${repo}/tree/${branch}/${dir}?embed=1&file=${file}&theme=${colorMode.value}`;
|
url.value = `https://stackblitz.com/github/${repo}/tree/${branch}/${dir}?embed=1&file=${file}&theme=${colorMode.value}`
|
||||||
else if (id)
|
else if (id)
|
||||||
url.value = `https://stackblitz.com/edit/${id}?embed=1&file=${file}&theme=${colorMode.value}`;
|
url.value = `https://stackblitz.com/edit/${id}?embed=1&file=${file}&theme=${colorMode.value}`
|
||||||
} else if (provider === 'codesandbox') {
|
} else if (provider === 'codesandbox') {
|
||||||
if (repo)
|
if (repo)
|
||||||
url.value = `https://codesandbox.io/p/sandbox/github/${repo}/tree/${branch}/${dir}?embed=1&file=${file}`;
|
url.value = `https://codesandbox.io/p/sandbox/github/${repo}/tree/${branch}/${dir}?embed=1&file=${file}`
|
||||||
else if (id)
|
else if (id)
|
||||||
url.value = `https://codesandbox.io/embed/${id}?view=editor+%2B+preview&module=${file}`;
|
url.value = `https://codesandbox.io/embed/${id}?view=editor+%2B+preview&module=${file}`
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1 :id class="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
|
<h1
|
||||||
|
:id
|
||||||
|
class="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl"
|
||||||
|
>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="generate"
|
v-if="generate"
|
||||||
:to="`#${id}`"
|
:to="`#${id}`"
|
||||||
@@ -11,8 +14,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { id } = defineProps<{ id?: string }>();
|
const { id } = defineProps<{ id?: string }>()
|
||||||
|
|
||||||
const { headings } = useRuntimeConfig().public.mdc;
|
const { headings } = useRuntimeConfig().public.mdc
|
||||||
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h1)));
|
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h1)))
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<h2 :id class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors [&:not(:first-child)]:mt-10">
|
<h2
|
||||||
|
:id
|
||||||
|
class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors [&:not(:first-child)]:mt-10"
|
||||||
|
>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="id && generate"
|
v-if="id && generate"
|
||||||
:to="`#${id}`"
|
:to="`#${id}`"
|
||||||
@@ -11,8 +14,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { id } = defineProps<{ id?: string }>();
|
const { id } = defineProps<{ id?: string }>()
|
||||||
|
|
||||||
const { headings } = useRuntimeConfig().public.mdc;
|
const { headings } = useRuntimeConfig().public.mdc
|
||||||
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h2)));
|
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h2)))
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<h3 :id class="scroll-m-20 text-2xl font-semibold tracking-tight [&:not(:first-child)]:mt-8">
|
<h3
|
||||||
|
:id
|
||||||
|
class="scroll-m-20 text-2xl font-semibold tracking-tight [&:not(:first-child)]:mt-8"
|
||||||
|
>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="id && generate"
|
v-if="id && generate"
|
||||||
:to="`#${id}`"
|
:to="`#${id}`"
|
||||||
@@ -11,8 +14,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { id } = defineProps<{ id?: string }>();
|
const { id } = defineProps<{ id?: string }>()
|
||||||
|
|
||||||
const { headings } = useRuntimeConfig().public.mdc;
|
const { headings } = useRuntimeConfig().public.mdc
|
||||||
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h3)));
|
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h3)))
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<h4 :id class="scroll-m-20 text-xl font-semibold tracking-tight [&:not(:first-child)]:mt-6">
|
<h4
|
||||||
|
:id
|
||||||
|
class="scroll-m-20 text-xl font-semibold tracking-tight [&:not(:first-child)]:mt-6"
|
||||||
|
>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="id && generate"
|
v-if="id && generate"
|
||||||
:to="`#${id}`"
|
:to="`#${id}`"
|
||||||
@@ -11,8 +14,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { id } = defineProps<{ id?: string }>();
|
const { id } = defineProps<{ id?: string }>()
|
||||||
|
|
||||||
const { headings } = useRuntimeConfig().public.mdc;
|
const { headings } = useRuntimeConfig().public.mdc
|
||||||
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h4)));
|
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h4)))
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<h5 :id class="scroll-m-20 text-lg font-semibold tracking-tight [&:not(:first-child)]:mt-6">
|
<h5
|
||||||
|
:id
|
||||||
|
class="scroll-m-20 text-lg font-semibold tracking-tight [&:not(:first-child)]:mt-6"
|
||||||
|
>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="id && generate"
|
v-if="id && generate"
|
||||||
:to="`#${id}`"
|
:to="`#${id}`"
|
||||||
@@ -11,8 +14,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { id } = defineProps<{ id?: string }>();
|
const { id } = defineProps<{ id?: string }>()
|
||||||
|
|
||||||
const { headings } = useRuntimeConfig().public.mdc;
|
const { headings } = useRuntimeConfig().public.mdc
|
||||||
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h5)));
|
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h5)))
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<h6 :id class="scroll-m-20 text-lg font-semibold tracking-tight [&:not(:first-child)]:mt-6">
|
<h6
|
||||||
|
:id
|
||||||
|
class="scroll-m-20 text-lg font-semibold tracking-tight [&:not(:first-child)]:mt-6"
|
||||||
|
>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="id && generate"
|
v-if="id && generate"
|
||||||
:to="`#${id}`"
|
:to="`#${id}`"
|
||||||
@@ -11,8 +14,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { id } = defineProps<{ id?: string }>();
|
const { id } = defineProps<{ id?: string }>()
|
||||||
|
|
||||||
const { headings } = useRuntimeConfig().public.mdc;
|
const { headings } = useRuntimeConfig().public.mdc
|
||||||
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h6)));
|
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h6)))
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,10 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<UPage>
|
<UPage>
|
||||||
<NuxtLink v-if="to" :to="to">
|
<NuxtLink
|
||||||
<UAlert :icon :title :color="color" :variant="variant" :description="to"
|
v-if="to"
|
||||||
class="mt-2 mb-3 text-black dark:text-white" :ui="{
|
:to="to"
|
||||||
|
>
|
||||||
|
<UAlert
|
||||||
|
:icon
|
||||||
|
:title
|
||||||
|
:color="color"
|
||||||
|
:variant="variant"
|
||||||
|
:description="to"
|
||||||
|
class="mt-2 mb-3 text-black dark:text-white"
|
||||||
|
:ui="{
|
||||||
icon: iconSize
|
icon: iconSize
|
||||||
}">
|
}"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</UAlert>
|
</UAlert>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
@@ -12,20 +22,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
const {
|
const {
|
||||||
variant = 'subtle',
|
variant = 'subtle',
|
||||||
title,
|
title,
|
||||||
icon = 'lucide:bookmark',
|
icon = 'lucide:bookmark',
|
||||||
color = 'primary',
|
color = 'primary'
|
||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
color?: 'primary' | 'error' | 'secondary' | 'success' | 'info' | 'warning' | 'neutral';
|
color?: 'primary' | 'error' | 'secondary' | 'success' | 'info' | 'warning' | 'neutral'
|
||||||
description?: string;
|
description?: string
|
||||||
title?: string;
|
title?: string
|
||||||
to?: string;
|
to?: string
|
||||||
icon?: string;
|
icon?: string
|
||||||
iconSize?: string;
|
iconSize?: string
|
||||||
variant?: 'subtle' | 'solid' | 'outline' | 'soft';
|
variant?: 'subtle' | 'solid' | 'outline' | 'soft'
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- Iconify Icons -->
|
<!-- Iconify Icons -->
|
||||||
<Icon v-if="checkIcon(name)" :name :size />
|
<Icon
|
||||||
|
v-if="checkIcon(name)"
|
||||||
|
:name
|
||||||
|
:size
|
||||||
|
/>
|
||||||
<!-- Emojis -->
|
<!-- Emojis -->
|
||||||
<span
|
<span
|
||||||
v-else-if="/(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g.test(name)"
|
v-else-if="/(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g.test(name)"
|
||||||
@@ -16,17 +20,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { stringToIcon, validateIconName } from '@iconify/utils';
|
import { stringToIcon, validateIconName } from '@iconify/utils'
|
||||||
|
|
||||||
const { size = 16 } = defineProps<{
|
const { size = 16 } = defineProps<{
|
||||||
name: string;
|
name: string
|
||||||
size?: number;
|
size?: number
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
function checkIcon(name: string): boolean {
|
function checkIcon(name: string): boolean {
|
||||||
if (name.includes('http'))
|
if (name.includes('http'))
|
||||||
return false;
|
return false
|
||||||
|
|
||||||
return validateIconName(stringToIcon(name));
|
return validateIconName(stringToIcon(name))
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -7,7 +7,6 @@
|
|||||||
>
|
>
|
||||||
<component :is="slot" />
|
<component :is="slot" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</UPageCard>
|
</UPageCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NavigationMenuItem } from '@nuxt/ui'
|
import type { NavigationMenuItem } from '@nuxt/ui'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const items = ref<NavigationMenuItem[][]>([
|
const items = ref<NavigationMenuItem[][]>([
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
label: 'Guide',
|
label: 'Guide',
|
||||||
icon: 'lucide-book-open',
|
icon: 'lucide-book-open'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Composables',
|
label: 'Composables',
|
||||||
@@ -74,7 +75,7 @@ const items = ref<NavigationMenuItem[][]>([
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
// [
|
// [
|
||||||
// {
|
// {
|
||||||
// label: 'GitHub',
|
// label: 'GitHub',
|
||||||
@@ -94,5 +95,10 @@ const items = ref<NavigationMenuItem[][]>([
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UNavigationMenu orientation="vertical" :items="items" class="w-full justify-center" color="primary" />
|
<UNavigationMenu
|
||||||
|
orientation="vertical"
|
||||||
|
:items="items"
|
||||||
|
class="w-full justify-center"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -17,28 +17,28 @@ const items = [
|
|||||||
toast.add({
|
toast.add({
|
||||||
title: 'Markdown link copied to clipboard',
|
title: 'Markdown link copied to clipboard',
|
||||||
icon: 'lucide-check-circle',
|
icon: 'lucide-check-circle',
|
||||||
color: 'success',
|
color: 'success'
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'View as Markdown',
|
label: 'View as Markdown',
|
||||||
icon: 'simple-icons:markdown',
|
icon: 'simple-icons:markdown',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
to: markdownLink.value,
|
to: markdownLink.value
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Open in ChatGPT',
|
label: 'Open in ChatGPT',
|
||||||
icon: 'simple-icons:openai',
|
icon: 'simple-icons:openai',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
|
to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Open in Claude',
|
label: 'Open in Claude',
|
||||||
icon: 'simple-icons:anthropic',
|
icon: 'simple-icons:anthropic',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
|
to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ const items = [
|
|||||||
color="neutral"
|
color="neutral"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:ui="{
|
:ui="{
|
||||||
leadingIcon: [copied ? 'text-primary' : 'text-neutral', 'size-3.5'],
|
leadingIcon: [copied ? 'text-primary' : 'text-neutral', 'size-3.5']
|
||||||
}"
|
}"
|
||||||
@click="copy(markdownLink)"
|
@click="copy(markdownLink)"
|
||||||
/>
|
/>
|
||||||
@@ -61,10 +61,10 @@ const items = [
|
|||||||
:content="{
|
:content="{
|
||||||
align: 'end',
|
align: 'end',
|
||||||
side: 'bottom',
|
side: 'bottom',
|
||||||
sideOffset: 8,
|
sideOffset: 8
|
||||||
}"
|
}"
|
||||||
:ui="{
|
:ui="{
|
||||||
content: 'w-48',
|
content: 'w-48'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<UButton
|
<UButton
|
||||||
|
@@ -41,20 +41,20 @@
|
|||||||
* </template>
|
* </template>
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
// 定义各种选项
|
// 定义各种选项
|
||||||
const themes = [
|
const themes = [
|
||||||
{ label: '经典', value: 'classic' },
|
{ label: '经典', value: 'classic' },
|
||||||
{ label: '优雅', value: 'elegant' },
|
{ label: '优雅', value: 'elegant' },
|
||||||
{ label: '简洁', value: 'minimal' }
|
{ label: '简洁', value: 'minimal' }
|
||||||
];
|
]
|
||||||
|
|
||||||
const fonts = [
|
const fonts = [
|
||||||
{ label: '无衬线', value: 'sans-serif' },
|
{ label: '无衬线', value: 'sans-serif' },
|
||||||
{ label: '衬线', value: 'serif' },
|
{ label: '衬线', value: 'serif' },
|
||||||
{ label: '等宽', value: 'monospace' }
|
{ label: '等宽', value: 'monospace' }
|
||||||
];
|
]
|
||||||
|
|
||||||
const fontSizes = [
|
const fontSizes = [
|
||||||
{ label: '更小', value: 'xs' },
|
{ label: '更小', value: 'xs' },
|
||||||
@@ -62,7 +62,7 @@ const fontSizes = [
|
|||||||
{ label: '推荐', value: 'base' },
|
{ label: '推荐', value: 'base' },
|
||||||
{ label: '稍大', value: 'lg' },
|
{ label: '稍大', value: 'lg' },
|
||||||
{ label: '更大', value: 'xl' }
|
{ label: '更大', value: 'xl' }
|
||||||
];
|
]
|
||||||
|
|
||||||
const themeColors = [
|
const themeColors = [
|
||||||
{ label: '经典蓝', value: 'blue', color: '#3B82F6' },
|
{ label: '经典蓝', value: 'blue', color: '#3B82F6' },
|
||||||
@@ -76,7 +76,7 @@ const themeColors = [
|
|||||||
{ label: '石墨黑', value: 'gray', color: '#6B7280' },
|
{ label: '石墨黑', value: 'gray', color: '#6B7280' },
|
||||||
{ label: '雾烟灰', value: 'slate', color: '#64748B' },
|
{ label: '雾烟灰', value: 'slate', color: '#64748B' },
|
||||||
{ label: '樱花粉', value: 'pink', color: '#EC4899' }
|
{ label: '樱花粉', value: 'pink', color: '#EC4899' }
|
||||||
];
|
]
|
||||||
|
|
||||||
const codeThemes = [
|
const codeThemes = [
|
||||||
{ label: 'dark', value: 'dark' },
|
{ label: 'dark', value: 'dark' },
|
||||||
@@ -85,14 +85,14 @@ const codeThemes = [
|
|||||||
{ label: 'github-light', value: 'github-light' },
|
{ label: 'github-light', value: 'github-light' },
|
||||||
{ label: 'one-dark-pro', value: 'one-dark-pro' },
|
{ label: 'one-dark-pro', value: 'one-dark-pro' },
|
||||||
{ label: 'material-theme', value: 'material-theme' }
|
{ label: 'material-theme', value: 'material-theme' }
|
||||||
];
|
]
|
||||||
|
|
||||||
const captionFormats = [
|
const captionFormats = [
|
||||||
{ label: 'title 优先', value: 'title' },
|
{ label: 'title 优先', value: 'title' },
|
||||||
{ label: 'alt 优先', value: 'alt' },
|
{ label: 'alt 优先', value: 'alt' },
|
||||||
{ label: '只显示 title', value: 'title-only' },
|
{ label: '只显示 title', value: 'title-only' },
|
||||||
{ label: '只显示 alt', value: 'alt-only' }
|
{ label: '只显示 alt', value: 'alt-only' }
|
||||||
];
|
]
|
||||||
|
|
||||||
// 默认设置
|
// 默认设置
|
||||||
const defaultSettings = {
|
const defaultSettings = {
|
||||||
@@ -102,68 +102,68 @@ const defaultSettings = {
|
|||||||
themeColor: 'blue',
|
themeColor: 'blue',
|
||||||
codeTheme: 'dark',
|
codeTheme: 'dark',
|
||||||
captionFormat: 'none'
|
captionFormat: 'none'
|
||||||
};
|
}
|
||||||
|
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const selectedTheme = ref<string>(defaultSettings.theme);
|
const selectedTheme = ref<string>(defaultSettings.theme)
|
||||||
const selectedFont = ref<string>(defaultSettings.font);
|
const selectedFont = ref<string>(defaultSettings.font)
|
||||||
const selectedFontSize = ref<string>(defaultSettings.fontSize);
|
const selectedFontSize = ref<string>(defaultSettings.fontSize)
|
||||||
const selectedThemeColor = ref<string>(defaultSettings.themeColor);
|
const selectedThemeColor = ref<string>(defaultSettings.themeColor)
|
||||||
const customColor = ref<string>('#3B82F6');
|
const customColor = ref<string>('#3B82F6')
|
||||||
const selectedCodeTheme = ref<string>(defaultSettings.codeTheme);
|
const selectedCodeTheme = ref<string>(defaultSettings.codeTheme)
|
||||||
const selectedCaptionFormat = ref<string>(defaultSettings.captionFormat);
|
const selectedCaptionFormat = ref<string>(defaultSettings.captionFormat)
|
||||||
|
|
||||||
// 这是一个单例,确保在整个应用中只有一个主题状态实例
|
// 这是一个单例,确保在整个应用中只有一个主题状态实例
|
||||||
let isInitialized = false;
|
let isInitialized = false
|
||||||
|
|
||||||
export function useTheme() {
|
export function useTheme() {
|
||||||
const colorMode = useColorMode();
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const initializeTheme = () => {
|
const initializeTheme = () => {
|
||||||
if (import.meta.client && !isInitialized) {
|
if (import.meta.client && !isInitialized) {
|
||||||
selectedTheme.value = localStorage.getItem('app-theme') || defaultSettings.theme;
|
selectedTheme.value = localStorage.getItem('app-theme') || defaultSettings.theme
|
||||||
selectedFont.value = localStorage.getItem('app-font') || defaultSettings.font;
|
selectedFont.value = localStorage.getItem('app-font') || defaultSettings.font
|
||||||
selectedFontSize.value = localStorage.getItem('app-font-size') || defaultSettings.fontSize;
|
selectedFontSize.value = localStorage.getItem('app-font-size') || defaultSettings.fontSize
|
||||||
selectedThemeColor.value = localStorage.getItem('app-theme-color') || defaultSettings.themeColor;
|
selectedThemeColor.value = localStorage.getItem('app-theme-color') || defaultSettings.themeColor
|
||||||
selectedCodeTheme.value = localStorage.getItem('app-code-theme') || defaultSettings.codeTheme;
|
selectedCodeTheme.value = localStorage.getItem('app-code-theme') || defaultSettings.codeTheme
|
||||||
selectedCaptionFormat.value = localStorage.getItem('app-caption-format') || defaultSettings.captionFormat;
|
selectedCaptionFormat.value = localStorage.getItem('app-caption-format') || defaultSettings.captionFormat
|
||||||
|
|
||||||
// 找到自定义颜色对应的主题色值
|
// 找到自定义颜色对应的主题色值
|
||||||
const initialColor = themeColors.find(c => c.value === selectedThemeColor.value)?.color || '#3B82F6';
|
const initialColor = themeColors.find(c => c.value === selectedThemeColor.value)?.color || '#3B82F6'
|
||||||
customColor.value = localStorage.getItem('app-custom-color') || initialColor;
|
customColor.value = localStorage.getItem('app-custom-color') || initialColor
|
||||||
|
|
||||||
applyThemeVariables();
|
applyThemeVariables()
|
||||||
isInitialized = true;
|
isInitialized = true
|
||||||
console.log('主题已初始化');
|
console.log('主题已初始化')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const applyThemeVariables = () => {
|
const applyThemeVariables = () => {
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
const root = document.documentElement;
|
const root = document.documentElement
|
||||||
|
|
||||||
// 颜色映射到 @nuxt/ui 支持的颜色
|
// 颜色映射到 @nuxt/ui 支持的颜色
|
||||||
const colorMapping: { [key: string]: string } = {
|
const colorMapping: { [key: string]: string } = {
|
||||||
'blue': 'blue',
|
blue: 'blue',
|
||||||
'emerald': 'emerald',
|
emerald: 'emerald',
|
||||||
'orange': 'orange',
|
orange: 'orange',
|
||||||
'yellow': 'yellow',
|
yellow: 'yellow',
|
||||||
'violet': 'violet',
|
violet: 'violet',
|
||||||
'sky': 'sky',
|
sky: 'sky',
|
||||||
'rose': 'rose',
|
rose: 'rose',
|
||||||
'lime': 'lime',
|
lime: 'lime',
|
||||||
'gray': 'gray',
|
gray: 'gray',
|
||||||
'slate': 'slate',
|
slate: 'slate',
|
||||||
'pink': 'pink'
|
pink: 'pink'
|
||||||
};
|
}
|
||||||
|
|
||||||
// 使用 @nuxt/ui 的主题系统
|
// 使用 @nuxt/ui 的主题系统
|
||||||
if (selectedThemeColor.value !== 'custom') {
|
if (selectedThemeColor.value !== 'custom') {
|
||||||
const mappedColor = colorMapping[selectedThemeColor.value];
|
const mappedColor = colorMapping[selectedThemeColor.value]
|
||||||
if (mappedColor) {
|
if (mappedColor) {
|
||||||
const appConfig = useAppConfig();
|
const appConfig = useAppConfig()
|
||||||
if (!appConfig.ui.colors) appConfig.ui.colors = {};
|
if (!appConfig.ui.colors) appConfig.ui.colors = {}
|
||||||
appConfig.ui.colors.primary = mappedColor;
|
appConfig.ui.colors.primary = mappedColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,18 +172,18 @@ export function useTheme() {
|
|||||||
'sans-serif': 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif',
|
'sans-serif': 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif',
|
||||||
'serif': 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
'serif': 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
||||||
'monospace': 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace'
|
'monospace': 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace'
|
||||||
};
|
}
|
||||||
root.style.setProperty('--font-family', fontMapping[selectedFont.value as keyof typeof fontMapping]);
|
root.style.setProperty('--font-family', fontMapping[selectedFont.value as keyof typeof fontMapping])
|
||||||
|
|
||||||
const fontSizeMapping = {
|
const fontSizeMapping = {
|
||||||
'xs': '0.75rem', 'sm': '0.875rem', 'base': '1rem', 'lg': '1.125rem', 'xl': '1.25rem'
|
xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem'
|
||||||
};
|
}
|
||||||
root.style.setProperty('--font-size-base', fontSizeMapping[selectedFontSize.value as keyof typeof fontSizeMapping]);
|
root.style.setProperty('--font-size-base', fontSizeMapping[selectedFontSize.value as keyof typeof fontSizeMapping])
|
||||||
|
|
||||||
root.classList.remove('theme-classic', 'theme-elegant', 'theme-minimal');
|
root.classList.remove('theme-classic', 'theme-elegant', 'theme-minimal')
|
||||||
root.classList.add(`theme-${selectedTheme.value}`);
|
root.classList.add(`theme-${selectedTheme.value}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用自定义主色调
|
* 应用自定义主色调
|
||||||
@@ -195,62 +195,62 @@ export function useTheme() {
|
|||||||
*/
|
*/
|
||||||
const applyCustomColor = () => {
|
const applyCustomColor = () => {
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
selectedThemeColor.value = 'custom';
|
selectedThemeColor.value = 'custom'
|
||||||
const root = document.documentElement;
|
const root = document.documentElement
|
||||||
|
|
||||||
// 使用 Nuxt UI v3 推荐的 CSS 变量覆盖方法
|
// 使用 Nuxt UI v3 推荐的 CSS 变量覆盖方法
|
||||||
// 设置主色调的 CSS 变量,这会影响所有使用 primary 颜色的组件
|
// 设置主色调的 CSS 变量,这会影响所有使用 primary 颜色的组件
|
||||||
root.style.setProperty('--ui-primary', customColor.value);
|
root.style.setProperty('--ui-primary', customColor.value)
|
||||||
|
|
||||||
// 保持 Nuxt UI 使用有效的颜色系统
|
// 保持 Nuxt UI 使用有效的颜色系统
|
||||||
const appConfig = useAppConfig();
|
const appConfig = useAppConfig()
|
||||||
if (!appConfig.ui.colors) appConfig.ui.colors = {};
|
if (!appConfig.ui.colors) appConfig.ui.colors = {}
|
||||||
appConfig.ui.colors.primary = 'blue'; // 使用有效的颜色名称作为基础
|
appConfig.ui.colors.primary = 'blue' // 使用有效的颜色名称作为基础
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const resetSettings = () => {
|
const resetSettings = () => {
|
||||||
selectedTheme.value = defaultSettings.theme;
|
selectedTheme.value = defaultSettings.theme
|
||||||
selectedFont.value = defaultSettings.font;
|
selectedFont.value = defaultSettings.font
|
||||||
selectedFontSize.value = defaultSettings.fontSize;
|
selectedFontSize.value = defaultSettings.fontSize
|
||||||
selectedThemeColor.value = defaultSettings.themeColor;
|
selectedThemeColor.value = defaultSettings.themeColor
|
||||||
const defaultColor = themeColors.find(c => c.value === defaultSettings.themeColor)?.color || '#3B82F6';
|
const defaultColor = themeColors.find(c => c.value === defaultSettings.themeColor)?.color || '#3B82F6'
|
||||||
customColor.value = defaultColor;
|
customColor.value = defaultColor
|
||||||
selectedCodeTheme.value = defaultSettings.codeTheme;
|
selectedCodeTheme.value = defaultSettings.codeTheme
|
||||||
selectedCaptionFormat.value = defaultSettings.captionFormat;
|
selectedCaptionFormat.value = defaultSettings.captionFormat
|
||||||
|
|
||||||
colorMode.preference = 'system';
|
colorMode.preference = 'system'
|
||||||
|
|
||||||
applyThemeVariables();
|
applyThemeVariables()
|
||||||
};
|
}
|
||||||
|
|
||||||
// 监听预设主题色的变化,并同步更新自定义颜色选择器的值
|
// 监听预设主题色的变化,并同步更新自定义颜色选择器的值
|
||||||
watch(selectedThemeColor, (newThemeColor) => {
|
watch(selectedThemeColor, (newThemeColor) => {
|
||||||
if (newThemeColor !== 'custom') {
|
if (newThemeColor !== 'custom') {
|
||||||
const colorObject = themeColors.find(c => c.value === newThemeColor);
|
const colorObject = themeColors.find(c => c.value === newThemeColor)
|
||||||
if (colorObject) {
|
if (colorObject) {
|
||||||
customColor.value = colorObject.color;
|
customColor.value = colorObject.color
|
||||||
//应用颜色
|
// 应用颜色
|
||||||
applyCustomColor();
|
applyCustomColor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
watch([selectedTheme, selectedFont, selectedFontSize, selectedThemeColor, selectedCodeTheme, selectedCaptionFormat, customColor], () => {
|
watch([selectedTheme, selectedFont, selectedFontSize, selectedThemeColor, selectedCodeTheme, selectedCaptionFormat, customColor], () => {
|
||||||
applyThemeVariables();
|
applyThemeVariables()
|
||||||
|
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
localStorage.setItem('app-theme', selectedTheme.value);
|
localStorage.setItem('app-theme', selectedTheme.value)
|
||||||
localStorage.setItem('app-font', selectedFont.value);
|
localStorage.setItem('app-font', selectedFont.value)
|
||||||
localStorage.setItem('app-font-size', selectedFontSize.value);
|
localStorage.setItem('app-font-size', selectedFontSize.value)
|
||||||
localStorage.setItem('app-theme-color', selectedThemeColor.value);
|
localStorage.setItem('app-theme-color', selectedThemeColor.value)
|
||||||
localStorage.setItem('app-code-theme', selectedCodeTheme.value);
|
localStorage.setItem('app-code-theme', selectedCodeTheme.value)
|
||||||
localStorage.setItem('app-caption-format', selectedCaptionFormat.value);
|
localStorage.setItem('app-caption-format', selectedCaptionFormat.value)
|
||||||
if (selectedThemeColor.value === 'custom') {
|
if (selectedThemeColor.value === 'custom') {
|
||||||
localStorage.setItem('app-custom-color', customColor.value);
|
localStorage.setItem('app-custom-color', customColor.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initializeTheme,
|
initializeTheme,
|
||||||
@@ -270,5 +270,5 @@ export function useTheme() {
|
|||||||
applyCustomColor,
|
applyCustomColor,
|
||||||
resetSettings,
|
resetSettings,
|
||||||
colorMode
|
colorMode
|
||||||
};
|
}
|
||||||
}
|
}
|
@@ -1,37 +1,37 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NuxtError } from "#app";
|
import type { NuxtError } from '#app'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: "default",
|
layout: 'default'
|
||||||
});
|
})
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
error: NuxtError;
|
error: NuxtError
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
lang: "zh-CN",
|
lang: 'zh-CN'
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
title: "Page not found",
|
title: 'Page not found',
|
||||||
description: "We are sorry but this page could not be found.",
|
description: 'We are sorry but this page could not be found.'
|
||||||
});
|
})
|
||||||
|
|
||||||
const { data: navigation } = await useAsyncData("navigation", () =>
|
const { data: navigation } = await useAsyncData('navigation', () =>
|
||||||
queryCollectionNavigation("docs"),
|
queryCollectionNavigation('docs')
|
||||||
);
|
)
|
||||||
const { data: files } = useLazyAsyncData(
|
const { data: files } = useLazyAsyncData(
|
||||||
"search",
|
'search',
|
||||||
() => queryCollectionSearchSections("docs"),
|
() => queryCollectionSearchSections('docs'),
|
||||||
{
|
{
|
||||||
server: false,
|
server: false
|
||||||
},
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
provide("navigation", navigation);
|
provide('navigation', navigation)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -43,7 +43,7 @@ provide("navigation", navigation);
|
|||||||
:clear="{
|
:clear="{
|
||||||
size: 'xl',
|
size: 'xl',
|
||||||
icon: 'lucide-arrow-left',
|
icon: 'lucide-arrow-left',
|
||||||
class: 'rounded-full',
|
class: 'rounded-full'
|
||||||
}"
|
}"
|
||||||
redirect="/"
|
redirect="/"
|
||||||
/>
|
/>
|
||||||
@@ -51,7 +51,10 @@ provide("navigation", navigation);
|
|||||||
<AppFooter />
|
<AppFooter />
|
||||||
|
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<LazyUContentSearch :files="files" :navigation="navigation" />
|
<LazyUContentSearch
|
||||||
|
:files="files"
|
||||||
|
:navigation="navigation"
|
||||||
|
/>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</UApp>
|
</UApp>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -35,9 +35,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { watch } from 'vue'
|
|
||||||
|
|
||||||
const isSidebarOpen = ref(false)
|
const isSidebarOpen = ref(false)
|
||||||
|
|
||||||
@@ -45,6 +44,4 @@ const isSidebarOpen = ref(false)
|
|||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
isSidebarOpen.value = !isSidebarOpen.value
|
isSidebarOpen.value = !isSidebarOpen.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
@@ -24,7 +24,7 @@ const { data: page } = await useAsyncData(
|
|||||||
{
|
{
|
||||||
default: () => null // 提供默认值
|
default: () => null // 提供默认值
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
if (!page.value) {
|
if (!page.value) {
|
||||||
throw createError({
|
throw createError({
|
||||||
@@ -32,10 +32,9 @@ if (!page.value) {
|
|||||||
statusMessage: '文档不存在',
|
statusMessage: '文档不存在',
|
||||||
message: '当前页面不存在,请您检查路径是否正确',
|
message: '当前页面不存在,请您检查路径是否正确',
|
||||||
fatal: true
|
fatal: true
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
|
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
|
||||||
return queryCollectionItemSurroundings('docs', route.path, {
|
return queryCollectionItemSurroundings('docs', route.path, {
|
||||||
fields: ['description']
|
fields: ['description']
|
||||||
@@ -69,7 +68,7 @@ const editLink = computed(() => {
|
|||||||
appConfig.github.branch,
|
appConfig.github.branch,
|
||||||
appConfig.github.rootDir,
|
appConfig.github.rootDir,
|
||||||
'content',
|
'content',
|
||||||
`${page.value?.stem}.${page.value?.extension}`,
|
`${page.value?.stem}.${page.value?.extension}`
|
||||||
].filter(Boolean).join('/')
|
].filter(Boolean).join('/')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -89,29 +88,60 @@ const links = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UPage v-if="page" :class="pageFontSizeClass">
|
<UPage
|
||||||
<UPageHeader :title="page.title" :description="page.description" :headline="headline" :ui="{
|
v-if="page"
|
||||||
wrapper: 'flex-row items-center flex-wrap justify-between ',
|
:class="pageFontSizeClass"
|
||||||
}">
|
>
|
||||||
|
<UPageHeader
|
||||||
|
:title="page.title"
|
||||||
|
:description="page.description"
|
||||||
|
:headline="headline"
|
||||||
|
:ui="{
|
||||||
|
wrapper: 'flex-row items-center flex-wrap justify-between '
|
||||||
|
}"
|
||||||
|
>
|
||||||
<template #links>
|
<template #links>
|
||||||
<UButton v-for="(link, index) in page.links" :key="index" size="sm" v-bind="link" />
|
<UButton
|
||||||
|
v-for="(link, index) in page.links"
|
||||||
|
:key="index"
|
||||||
|
size="sm"
|
||||||
|
v-bind="link"
|
||||||
|
/>
|
||||||
|
|
||||||
<DocsPageHeaderLinks />
|
<DocsPageHeaderLinks />
|
||||||
</template>
|
</template>
|
||||||
</UPageHeader>
|
</UPageHeader>
|
||||||
|
|
||||||
<UPageBody>
|
<UPageBody>
|
||||||
<ContentRenderer v-if="page" :value="page" />
|
<ContentRenderer
|
||||||
|
v-if="page"
|
||||||
|
:value="page"
|
||||||
|
/>
|
||||||
|
|
||||||
<USeparator>
|
<USeparator>
|
||||||
<div v-if="editLink" class="flex items-center gap-2 text-sm text-muted">
|
<div
|
||||||
<UButton variant="link" color="neutral" :to="editLink" target="_blank" icon="lucide-pen"
|
v-if="editLink"
|
||||||
:ui="{ leadingIcon: 'size-4' }">
|
class="flex items-center gap-2 text-sm text-muted"
|
||||||
|
>
|
||||||
|
<UButton
|
||||||
|
variant="link"
|
||||||
|
color="neutral"
|
||||||
|
:to="editLink"
|
||||||
|
target="_blank"
|
||||||
|
icon="lucide-pen"
|
||||||
|
:ui="{ leadingIcon: 'size-4' }"
|
||||||
|
>
|
||||||
编辑页面
|
编辑页面
|
||||||
</UButton>
|
</UButton>
|
||||||
or
|
or
|
||||||
<UButton variant="link" color="neutral" :to="`${appConfig.github.url}/issues/new/choose`" target="_blank"
|
<UButton
|
||||||
icon="lucide-alert-circle" :ui="{ leadingIcon: 'size-4' }">
|
variant="link"
|
||||||
|
color="neutral"
|
||||||
|
:to="`${appConfig.github.url}/issues/new/choose`"
|
||||||
|
target="_blank"
|
||||||
|
icon="lucide-alert-circle"
|
||||||
|
:ui="{ leadingIcon: 'size-4' }"
|
||||||
|
>
|
||||||
提交问题
|
提交问题
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,15 +149,32 @@ const links = computed(() => {
|
|||||||
<UContentSurround :surround="surround" />
|
<UContentSurround :surround="surround" />
|
||||||
</UPageBody>
|
</UPageBody>
|
||||||
|
|
||||||
<template v-if="page?.body?.toc?.links?.length" #right>
|
<template
|
||||||
|
v-if="page?.body?.toc?.links?.length"
|
||||||
|
#right
|
||||||
|
>
|
||||||
<div class="fixed top-24 right-15 w-auto">
|
<div class="fixed top-24 right-15 w-auto">
|
||||||
|
<UContentToc
|
||||||
|
:title="appConfig.toc?.title"
|
||||||
|
:links="page.body?.toc?.links"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="appConfig.toc?.bottom"
|
||||||
|
#bottom
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="hidden lg:block space-y-6 "
|
||||||
|
:class="{ '!mt-5': page.body?.toc?.links?.length }"
|
||||||
|
>
|
||||||
|
<USeparator
|
||||||
|
v-if="page.body?.toc?.links?.length"
|
||||||
|
type="dashed"
|
||||||
|
/>
|
||||||
|
|
||||||
<UContentToc :title="appConfig.toc?.title" :links="page.body?.toc?.links">
|
<UPageLinks
|
||||||
<template v-if="appConfig.toc?.bottom" #bottom>
|
:title="appConfig.toc.bottom.title"
|
||||||
<div class="hidden lg:block space-y-6 " :class="{ '!mt-5': page.body?.toc?.links?.length }">
|
:links="links"
|
||||||
<USeparator v-if="page.body?.toc?.links?.length" type="dashed" />
|
/>
|
||||||
|
|
||||||
<UPageLinks :title="appConfig.toc.bottom.title" :links="links" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UContentToc>
|
</UContentToc>
|
||||||
|
@@ -3,8 +3,8 @@ definePageMeta({
|
|||||||
layout: 'default'
|
layout: 'default'
|
||||||
})
|
})
|
||||||
|
|
||||||
const title = "Estel Docs"
|
const title = 'Estel Docs'
|
||||||
const description = "Estel Docs"
|
const description = 'Estel Docs'
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
titleTemplate: '',
|
titleTemplate: '',
|
||||||
|
@@ -26,12 +26,12 @@ export default defineNuxtConfig({
|
|||||||
pathMeta: {
|
pathMeta: {
|
||||||
slugifyOptions: {
|
slugifyOptions: {
|
||||||
// Keep everything except invalid chars, this will preserve Chinese characters
|
// Keep everything except invalid chars, this will preserve Chinese characters
|
||||||
remove: /[$*+~()'"!\-=#?:@.]/g,
|
remove: /[$*+~()'"!\-=#?:@.]/g
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
dev:true,
|
dev: true,
|
||||||
api: 'https://api.nuxt.studio'
|
api: 'https://api.nuxt.studio'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -60,7 +60,7 @@ export default defineNuxtConfig({
|
|||||||
icon: {
|
icon: {
|
||||||
clientBundle: {
|
clientBundle: {
|
||||||
scan: true,
|
scan: true,
|
||||||
sizeLimitKb: 512,
|
sizeLimitKb: 512
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user