结构目录优化
This commit is contained in:
@@ -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
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user