feat: 为各个页面补全标题与SEO
All checks were successful
deploy to server / build-and-deploy (push) Successful in 2m54s
All checks were successful
deploy to server / build-and-deploy (push) Successful in 2m54s
- 添加Composable API: usePageSeo用于为路由页面添加SEO - 为各个页面补全路由标题与SEO ISSUE: resolve #52
This commit is contained in:
47
app/composables/usePageSeo.ts
Normal file
47
app/composables/usePageSeo.ts
Normal 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',
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -12,6 +12,17 @@
|
|||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
useHead(() => {
|
||||||
|
const siteTitle = t('company-name');
|
||||||
|
return {
|
||||||
|
titleTemplate: (title) => (title ? `${title} - ${siteTitle}` : siteTitle),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.app-container {
|
.app-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -3,3 +3,14 @@
|
|||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
useHead(() => {
|
||||||
|
const siteTitle = `${$t('page-title.preview')} - ${t('company-name')}`;
|
||||||
|
return {
|
||||||
|
title: siteTitle,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@ -38,6 +38,11 @@
|
|||||||
statusMessage: '文件未找到',
|
statusMessage: '文件未找到',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pageTitle = $t('page-title.download');
|
||||||
|
usePageSeo({
|
||||||
|
title: file.value.filename_download || pageTitle,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -19,9 +19,16 @@
|
|||||||
return toHomepageView(data.value);
|
return toHomepageView(data.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pageTilte = $t('page-title.homepage');
|
||||||
|
|
||||||
watch(error, (value) => {
|
watch(error, (value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
console.error('数据获取失败: ', value);
|
console.error('数据获取失败: ', value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useSeoMeta({
|
||||||
|
title: pageTilte,
|
||||||
|
description: $t('company-description'),
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -60,14 +60,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// SEO
|
// SEO
|
||||||
useHead({
|
usePageSeo({
|
||||||
title: computed(() => product.value?.name || 'Product Detail'),
|
title: product.value.name || $t('page-title.products'),
|
||||||
meta: [
|
description: product.value.summary || '',
|
||||||
{
|
|
||||||
name: 'description',
|
|
||||||
content: computed(() => product.value?.summary || ''),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -102,6 +102,11 @@
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pageTitle = $t('page-title.products');
|
||||||
|
usePageSeo({
|
||||||
|
title: pageTitle,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -49,6 +49,11 @@
|
|||||||
console.error('数据获取失败: ', value);
|
console.error('数据获取失败: ', value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
usePageSeo({
|
||||||
|
title: solution.value.title || $t('page-title.solutions'),
|
||||||
|
description: solution.value.summary || '',
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -86,6 +86,11 @@
|
|||||||
console.error('数据获取失败: ', value);
|
console.error('数据获取失败: ', value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pageTitle = $t('page-title.solutions');
|
||||||
|
usePageSeo({
|
||||||
|
title: pageTitle,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -31,6 +31,11 @@
|
|||||||
console.error('数据获取失败: ', value);
|
console.error('数据获取失败: ', value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pageTitle = $t('page-title.contact-us');
|
||||||
|
usePageSeo({
|
||||||
|
title: pageTitle,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -111,6 +111,11 @@
|
|||||||
console.error('数据获取失败: ', value);
|
console.error('数据获取失败: ', value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pageTitle = $t('page-title.documents');
|
||||||
|
usePageSeo({
|
||||||
|
title: pageTitle,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -112,6 +112,11 @@
|
|||||||
console.error('数据获取失败: ', value);
|
console.error('数据获取失败: ', value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pageTitle = $t('page-title.faq');
|
||||||
|
usePageSeo({
|
||||||
|
title: pageTitle,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -52,6 +52,11 @@
|
|||||||
iconComponent: ElIconService,
|
iconComponent: ElIconService,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const pageTitle = $t('page-title.support');
|
||||||
|
usePageSeo({
|
||||||
|
title: pageTitle,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -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-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": "Recommended Solutions",
|
||||||
"recommended-solutions-desc": "Learn about our tailored solutions designed to help your business thrive in a competitive market."
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,5 +99,17 @@
|
|||||||
"recommended-products-desc": "探索我们的精选产品,满足您的各种需求。无论是创新技术还是经典设计,我们都为您提供优质选择。",
|
"recommended-products-desc": "探索我们的精选产品,满足您的各种需求。无论是创新技术还是经典设计,我们都为您提供优质选择。",
|
||||||
"recommended-solutions": "推荐解决方案",
|
"recommended-solutions": "推荐解决方案",
|
||||||
"recommended-solutions-desc": "了解我们的定制解决方案,帮助您优化业务流程,提高效率。"
|
"recommended-solutions-desc": "了解我们的定制解决方案,帮助您优化业务流程,提高效率。"
|
||||||
|
},
|
||||||
|
"page-title": {
|
||||||
|
"homepage": "首页",
|
||||||
|
"products": "产品中心",
|
||||||
|
"solutions": "解决方案",
|
||||||
|
"support": "服务支持",
|
||||||
|
"faq": "常见问题",
|
||||||
|
"documents": "文档资料",
|
||||||
|
"contact-us": "联系我们",
|
||||||
|
"download": "文档下载",
|
||||||
|
"preview": "文档预览",
|
||||||
|
"about-us": "关于我们"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,14 +6,18 @@ export default defineNuxtConfig({
|
|||||||
app: {
|
app: {
|
||||||
// head
|
// head
|
||||||
head: {
|
head: {
|
||||||
title: '金申机械制造有限公司',
|
titleTemplate: '金申机械制造有限公司',
|
||||||
meta: [
|
meta: [
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||||
{
|
{
|
||||||
name: 'description',
|
name: 'description',
|
||||||
content: 'Jinshen Website',
|
content:
|
||||||
|
'浙江金申机械制造有限公司,专业生产一系列纸管、纸罐设备,是一家集设计、制造、销售、服务于一体的企业。公司主要 产品有原纸分切机、数控纸管机、纸管精切机及纸管后加工设备等 三十多个品种,产品在造纸、印刷、包装、纺织及文具等行业得到 广泛应用。公司依靠科技进步,引进高新技术,致力于新产品开发及技术改造,并配备完善的销售网络和售后服务体系,产品销往全国各地及全球上百个国家和地区,真正做到让客户买的放心,用的安心。',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
htmlAttrs: {
|
||||||
|
lang: 'zh',
|
||||||
|
},
|
||||||
link: [{ rel: 'icon', type: 'image/x-icon', href: '/jinshen-logo.ico' }],
|
link: [{ rel: 'icon', type: 'image/x-icon', href: '/jinshen-logo.ico' }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -100,6 +104,7 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
i18n: {
|
i18n: {
|
||||||
|
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
|
||||||
detectBrowserLanguage: {
|
detectBrowserLanguage: {
|
||||||
useCookie: true,
|
useCookie: true,
|
||||||
cookieKey: 'i18n_redirected',
|
cookieKey: 'i18n_redirected',
|
||||||
|
|||||||
Reference in New Issue
Block a user