init: 整合项目(dashboard + www + docs)
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
.output/
|
||||
|
||||
# Nuxt generated
|
||||
.nuxt/
|
||||
|
||||
# Env local files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Editor
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import zhCN from '~~/i18n/locales/zh.json';
|
||||
import enUS from '~~/i18n/locales/en.json';
|
||||
|
||||
const { locale } = useI18n();
|
||||
|
||||
useHead({
|
||||
htmlAttrs: {
|
||||
lang: () => (locale.value === 'zh' ? 'zh-CN' : 'en-US')
|
||||
}
|
||||
});
|
||||
|
||||
// 根据当前语言动态设置页面标题
|
||||
const head = useLocaleHead({
|
||||
addDirAttribute: true,
|
||||
identifierAttribute: 'id',
|
||||
addSeoAttributes: true
|
||||
});
|
||||
|
||||
useHead({
|
||||
title: () => (locale.value === 'zh' ? zhCN.app.name : enUS.app.name),
|
||||
htmlAttrs: head.value.htmlAttrs,
|
||||
link: [...(head.value.link || [])],
|
||||
meta: [...(head.value.meta || [])]
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,20 @@
|
||||
/* 全局基础样式 */
|
||||
html {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family:
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
Arial,
|
||||
'PingFang SC',
|
||||
'Hiragino Sans GB',
|
||||
'Microsoft YaHei',
|
||||
sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n();
|
||||
const currentYear = new Date().getFullYear();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="border-t border-gray-100 bg-gray-50 dark:bg-dark-900 dark:border-dark-700">
|
||||
<div class="section-container py-32px flex flex-col items-center gap-12px">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('footer.copyright', { year: currentYear }) }}
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
const { t, locale, locales } = useI18n();
|
||||
const switchLocalePath = useSwitchLocalePath();
|
||||
|
||||
const navItems = computed(() => [
|
||||
{ label: t('nav.home'), path: '/' },
|
||||
{ label: t('nav.products'), path: '/products' },
|
||||
{ label: t('nav.news'), path: '/news' },
|
||||
{ label: t('nav.about'), path: '/about' }
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-100 dark:bg-dark-900/80 dark:border-dark-700">
|
||||
<div class="section-container h-64px flex items-center justify-between">
|
||||
<!-- Logo -->
|
||||
<NuxtLink :to="switchLocalePath(locale)" class="flex items-center gap-8px text-xl font-bold text-primary">
|
||||
{{ t('app.name') }}
|
||||
</NuxtLink>
|
||||
|
||||
<!-- 导航 -->
|
||||
<nav class="hidden md:flex items-center gap-24px">
|
||||
<NuxtLink
|
||||
v-for="item in navItems"
|
||||
:key="item.path"
|
||||
:to="switchLocalePath(locale) === '/' ? item.path : switchLocalePath(locale) + item.path"
|
||||
class="text-sm text-gray-600 hover:text-primary transition-colors dark:text-gray-300"
|
||||
>
|
||||
{{ item.label }}
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
|
||||
<!-- 语言切换 -->
|
||||
<div class="flex items-center gap-12px">
|
||||
<NuxtLink
|
||||
v-for="loc in locales"
|
||||
:key="loc.code"
|
||||
:to="switchLocalePath(loc.code)"
|
||||
class="px-8px py-4px rounded text-sm transition-colors"
|
||||
:class="locale === loc.code ? 'bg-primary text-white' : 'text-gray-500 hover:text-primary'"
|
||||
>
|
||||
{{ loc.code === 'zh' ? '中' : 'EN' }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 统一请求封装(基于 Nuxt 内置 ofetch)
|
||||
* 响应格式约定:{ code, data, message },成功码 '0000'
|
||||
*/
|
||||
|
||||
interface ApiResponse<T = unknown> {
|
||||
code: string;
|
||||
data: T;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export function useRequest() {
|
||||
const config = useRuntimeConfig();
|
||||
const baseURL = config.public.apiBaseUrl as string;
|
||||
|
||||
async function request<T = unknown>(url: string, options?: Parameters<typeof $fetch>[1]): Promise<T> {
|
||||
const res = await $fetch<ApiResponse<T>>(url, {
|
||||
baseURL,
|
||||
...options
|
||||
});
|
||||
|
||||
if (res.code !== '0000') {
|
||||
throw new Error(res.message || 'Request failed');
|
||||
}
|
||||
|
||||
return res.data;
|
||||
}
|
||||
|
||||
function get<T = unknown>(url: string, params?: Record<string, unknown>) {
|
||||
return request<T>(url, { method: 'GET', params });
|
||||
}
|
||||
|
||||
function post<T = unknown>(url: string, body?: Record<string, unknown>) {
|
||||
return request<T>(url, { method: 'POST', body });
|
||||
}
|
||||
|
||||
return { request, get, post };
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen flex flex-col">
|
||||
<AppHeader />
|
||||
<main class="flex-1">
|
||||
<slot />
|
||||
</main>
|
||||
<AppFooter />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
const { t, locale } = useI18n();
|
||||
const switchLocalePath = useSwitchLocalePath();
|
||||
|
||||
definePageMeta({
|
||||
layout: 'blank'
|
||||
});
|
||||
|
||||
useHead({
|
||||
title: () => `404 - ${t('common.notFound')}`
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen flex-col-center">
|
||||
<h1 class="text-7xl font-bold text-gray-200 dark:text-gray-700">404</h1>
|
||||
<h2 class="mt-16px text-xl font-medium text-gray-700 dark:text-gray-200">
|
||||
{{ t('common.notFound') }}
|
||||
</h2>
|
||||
<p class="mt-8px text-sm text-gray-400">
|
||||
{{ t('common.notFoundDesc') }}
|
||||
</p>
|
||||
<NuxtLink
|
||||
:to="switchLocalePath(locale)"
|
||||
class="mt-32px px-24px py-10px rounded-6px bg-primary text-white text-sm hover:opacity-90 transition-opacity"
|
||||
>
|
||||
{{ t('common.backHome') }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
const { t } = useI18n();
|
||||
|
||||
useHead({
|
||||
title: () => `${t('app.name')} - ${t('nav.home')}`
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- Hero 区块 -->
|
||||
<section class="section-container py-96px flex-col-center text-center">
|
||||
<h1 class="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white">
|
||||
{{ t('app.name') }}
|
||||
</h1>
|
||||
<p class="mt-16px text-lg text-gray-500 dark:text-gray-400">
|
||||
{{ t('app.slogan') }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 页面内容由后台 CMS 配置的区块(Section)组合渲染 -->
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,18 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
/**
|
||||
* 全局应用状态
|
||||
*/
|
||||
export const useAppStore = defineStore('app', () => {
|
||||
/** 移动端菜单展开状态 */
|
||||
const mobileMenuOpen = ref(false);
|
||||
|
||||
function toggleMobileMenu(open?: boolean) {
|
||||
mobileMenuOpen.value = open ?? !mobileMenuOpen.value;
|
||||
}
|
||||
|
||||
return {
|
||||
mobileMenuOpen,
|
||||
toggleMobileMenu
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "Luxsin",
|
||||
"slogan": "Luxsin Official Website"
|
||||
},
|
||||
"nav": {
|
||||
"home": "Home",
|
||||
"products": "Products",
|
||||
"news": "News",
|
||||
"about": "About Us",
|
||||
"contact": "Contact"
|
||||
},
|
||||
"footer": {
|
||||
"copyright": "© {year} Luxsin. All rights reserved."
|
||||
},
|
||||
"common": {
|
||||
"learnMore": "Learn More",
|
||||
"backHome": "Back to Home",
|
||||
"notFound": "Page Not Found",
|
||||
"notFoundDesc": "Sorry, the page you visited does not exist or has been removed."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"app": {
|
||||
"name": "乐笙",
|
||||
"slogan": "乐笙产品官网"
|
||||
},
|
||||
"nav": {
|
||||
"home": "首页",
|
||||
"products": "产品",
|
||||
"news": "新闻",
|
||||
"about": "关于我们",
|
||||
"contact": "联系我们"
|
||||
},
|
||||
"footer": {
|
||||
"copyright": "© {year} 乐笙 Luxsin. 保留所有权利。"
|
||||
},
|
||||
"common": {
|
||||
"learnMore": "了解更多",
|
||||
"backHome": "返回首页",
|
||||
"notFound": "页面不存在",
|
||||
"notFoundDesc": "抱歉,您访问的页面不存在或已被移除。"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
export default defineNuxtConfig({
|
||||
// SPA 模式渲染,预留 SSR/SSG 升级能力
|
||||
ssr: false,
|
||||
|
||||
modules: ['@unocss/nuxt', '@nuxtjs/i18n', '@pinia/nuxt', '@vueuse/nuxt'],
|
||||
|
||||
// 国际化:中文默认无前缀,英文 /en 前缀
|
||||
// v10 自动检测 i18n/locales/ 目录,无需 langDir / lazy
|
||||
i18n: {
|
||||
locales: [
|
||||
{ code: 'zh', language: 'zh-CN', name: '中文' },
|
||||
{ code: 'en', language: 'en-US', name: 'English' }
|
||||
],
|
||||
defaultLocale: 'zh',
|
||||
strategy: 'prefix_except_default',
|
||||
detectBrowserLanguage: {
|
||||
useCookie: true,
|
||||
cookieKey: 'i18n_redirected',
|
||||
redirectOn: 'root'
|
||||
}
|
||||
},
|
||||
|
||||
app: {
|
||||
head: {
|
||||
title: '乐笙 Luxsin',
|
||||
meta: [{ name: 'description', content: '乐笙(Luxsin)产品官网' }]
|
||||
}
|
||||
},
|
||||
|
||||
devServer: {
|
||||
port: 3100
|
||||
},
|
||||
|
||||
telemetry: false,
|
||||
|
||||
compatibilityDate: '2026-07-29'
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "web",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "nuxt dev",
|
||||
"build": "nuxt build",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"typecheck": "nuxt typecheck"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxtjs/i18n": "^10.5.0",
|
||||
"@pinia/nuxt": "^1.0.1",
|
||||
"@vueuse/nuxt": "^14.4.0",
|
||||
"nuxt": "^4.5.1",
|
||||
"pinia": "^4.0.2",
|
||||
"vue": "^3.5.0",
|
||||
"vue-router": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unocss/nuxt": "^66.0.0",
|
||||
"typescript": "^5.8.0",
|
||||
"unocss": "^66.0.0",
|
||||
"vue-tsc": "^3.0.0"
|
||||
}
|
||||
}
|
||||
Generated
+9825
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
minimumReleaseAgeExclude:
|
||||
- '@vueuse/core@14.4.0'
|
||||
- '@vueuse/metadata@14.4.0'
|
||||
- '@vueuse/nuxt@14.4.0'
|
||||
- '@vueuse/shared@14.4.0'
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.1 MiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { defineConfig, presetWind3, transformerDirectives, transformerVariantGroup } from 'unocss';
|
||||
|
||||
export default defineConfig({
|
||||
content: {
|
||||
pipeline: {
|
||||
exclude: ['node_modules', 'dist', '.nuxt']
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
colors: {
|
||||
primary: '#2563eb'
|
||||
},
|
||||
fontSize: {
|
||||
'icon-xs': '0.875rem',
|
||||
'icon-small': '1rem',
|
||||
icon: '1.125rem',
|
||||
'icon-large': '1.5rem',
|
||||
'icon-xl': '2rem'
|
||||
}
|
||||
},
|
||||
shortcuts: {
|
||||
'flex-center': 'flex justify-center items-center',
|
||||
'flex-col-center': 'flex flex-col justify-center items-center',
|
||||
'section-container': 'mx-auto max-w-1200px px-16px'
|
||||
},
|
||||
transformers: [transformerDirectives(), transformerVariantGroup()],
|
||||
presets: [presetWind3({ dark: 'class' })]
|
||||
});
|
||||
Reference in New Issue
Block a user