feat: 为各个页面补全标题与SEO #66

Manually merged
remilia merged 1 commits from feat/seo into master 2025-11-07 15:40:19 +08:00
16 changed files with 150 additions and 10 deletions

View File

@ -0,0 +1,47 @@
/**
* 设置页面级 SEO 元数据(包含 title、description、OG/Twitter 等常用字段)。
*
* 该函数基于 `useSeoMeta` 封装,可用于 Nuxt 3 / Nuxt 4
* 配合 SSR/CSR 动态更新,可安全用于异步数据场景(如 CMS 内容页)。
*
* @param {Object} meta - 页面 SEO 配置对象
* @param {string} meta.title - 页面标题(会同时应用到 title / og:title
* @param {string} [meta.description] - 页面描述(会应用到 description / og:description
* @param {string} [meta.image] - 用于分享卡片的预览图og:image / twitter:image
*
* @example
* // 用于普通页面
* usePageSeo({
* title: '产品中心 - 金申机械',
* description: '查看全系列纸管机械产品',
* image: '/images/og/products.png'
* })
*
* @example
* // 用于动态内容(如产品详情页)
* const product = await fetchProduct()
* usePageSeo({
* title: product.name,
* description: product.summary,
* image: product.coverImage
* })
*
* @remarks
* - 自动生成以下 meta`title`, `description`, `og:title`, `og:description`, `og:image`, `twitter:card`
* - 默认使用 `summary_large_image` 作为 Twitter 卡片类型
* - 推荐与 `useHead()` 配合增加 canonical / alternate hreflang 等额外 SEO 标签
*/
export function usePageSeo(meta: {
title: string;
description?: string;
image?: string;
}) {
useSeoMeta({
title: meta.title,
ogTitle: meta.title,
description: meta.description,
ogDescription: meta.description,
ogImage: meta.image,
twitterCard: 'summary_large_image',
});
}

View File

@ -12,6 +12,17 @@
</el-container>
</template>
<script setup lang="ts">
const { t } = useI18n();
useHead(() => {
const siteTitle = t('company-name');
return {
titleTemplate: (title) => (title ? `${title} - ${siteTitle}` : siteTitle),
};
});
</script>
<style scoped>
.app-container {
display: flex;

View File

@ -3,3 +3,14 @@
<slot />
</div>
</template>
<script setup lang="ts">
const { t } = useI18n();
useHead(() => {
const siteTitle = `${$t('page-title.preview')} - ${t('company-name')}`;
return {
title: siteTitle,
};
});
</script>

View File

@ -38,6 +38,11 @@
statusMessage: '文件未找到',
});
}
const pageTitle = $t('page-title.download');
usePageSeo({
title: file.value.filename_download || pageTitle,
});
</script>
<style scoped>

View File

@ -19,9 +19,16 @@
return toHomepageView(data.value);
});
const pageTilte = $t('page-title.homepage');
watch(error, (value) => {
if (value) {
console.error('数据获取失败: ', value);
}
});
useSeoMeta({
title: pageTilte,
description: $t('company-description'),
});
</script>

View File

@ -60,14 +60,9 @@
});
// SEO
useHead({
title: computed(() => product.value?.name || 'Product Detail'),
meta: [
{
name: 'description',
content: computed(() => product.value?.summary || ''),
},
],
usePageSeo({
title: product.value.name || $t('page-title.products'),
description: product.value.summary || '',
});
</script>

View File

@ -102,6 +102,11 @@
];
}
});
const pageTitle = $t('page-title.products');
usePageSeo({
title: pageTitle,
});
</script>
<style scoped>

View File

@ -49,6 +49,11 @@
console.error('数据获取失败: ', value);
}
});
usePageSeo({
title: solution.value.title || $t('page-title.solutions'),
description: solution.value.summary || '',
});
</script>
<style scoped>

View File

@ -86,6 +86,11 @@
console.error('数据获取失败: ', value);
}
});
const pageTitle = $t('page-title.solutions');
usePageSeo({
title: pageTitle,
});
</script>
<style scoped>

View File

@ -31,6 +31,11 @@
console.error('数据获取失败: ', value);
}
});
const pageTitle = $t('page-title.contact-us');
usePageSeo({
title: pageTitle,
});
</script>
<style scoped>

View File

@ -111,6 +111,11 @@
console.error('数据获取失败: ', value);
}
});
const pageTitle = $t('page-title.documents');
usePageSeo({
title: pageTitle,
});
</script>
<style scoped>

View File

@ -112,6 +112,11 @@
console.error('数据获取失败: ', value);
}
});
const pageTitle = $t('page-title.faq');
usePageSeo({
title: pageTitle,
});
</script>
<style scoped>

View File

@ -52,6 +52,11 @@
iconComponent: ElIconService,
},
];
const pageTitle = $t('page-title.support');
usePageSeo({
title: pageTitle,
});
</script>
<style scoped>

View File

@ -100,5 +100,17 @@
"recommended-products-desc": "Explore our curated selection of products to meet your diverse needs. Whether it's innovative technology or classic designs, we offer quality choices for you.",
"recommended-solutions": "Recommended Solutions",
"recommended-solutions-desc": "Learn about our tailored solutions designed to help your business thrive in a competitive market."
},
"page-title": {
"homepage": "Homepage",
"products": "Products",
"solutions": "Solutions",
"support": "Support",
"faq": "FAQ",
"documents": "Documents",
"contact-us": "Contact Us",
"download": "Downloads",
"preview": "Preview",
"about-us": "About Us"
}
}

View File

@ -99,5 +99,17 @@
"recommended-products-desc": "探索我们的精选产品,满足您的各种需求。无论是创新技术还是经典设计,我们都为您提供优质选择。",
"recommended-solutions": "推荐解决方案",
"recommended-solutions-desc": "了解我们的定制解决方案,帮助您优化业务流程,提高效率。"
},
"page-title": {
"homepage": "首页",
"products": "产品中心",
"solutions": "解决方案",
"support": "服务支持",
"faq": "常见问题",
"documents": "文档资料",
"contact-us": "联系我们",
"download": "文档下载",
"preview": "文档预览",
"about-us": "关于我们"
}
}

View File

@ -6,14 +6,18 @@ export default defineNuxtConfig({
app: {
// head
head: {
title: '金申机械制造有限公司',
titleTemplate: '金申机械制造有限公司',
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
name: 'description',
content: 'Jinshen Website',
content:
'浙江金申机械制造有限公司,专业生产一系列纸管、纸罐设备,是一家集设计、制造、销售、服务于一体的企业。公司主要 产品有原纸分切机、数控纸管机、纸管精切机及纸管后加工设备等 三十多个品种,产品在造纸、印刷、包装、纺织及文具等行业得到 广泛应用。公司依靠科技进步,引进高新技术,致力于新产品开发及技术改造,并配备完善的销售网络和售后服务体系,产品销往全国各地及全球上百个国家和地区,真正做到让客户买的放心,用的安心。',
},
],
htmlAttrs: {
lang: 'zh',
},
link: [{ rel: 'icon', type: 'image/x-icon', href: '/jinshen-logo.ico' }],
},
},
@ -100,6 +104,7 @@ export default defineNuxtConfig({
},
i18n: {
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n_redirected',