feat: 为各个页面补全标题与SEO
All checks were successful
deploy to server / build-and-deploy (push) Successful in 3m4s
All checks were successful
deploy to server / build-and-deploy (push) Successful in 3m4s
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>
|
||||
</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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -38,6 +38,11 @@
|
||||
statusMessage: '文件未找到',
|
||||
});
|
||||
}
|
||||
|
||||
const pageTitle = $t('page-title.download');
|
||||
usePageSeo({
|
||||
title: file.value.filename_download || pageTitle,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -102,6 +102,11 @@
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
const pageTitle = $t('page-title.products');
|
||||
usePageSeo({
|
||||
title: pageTitle,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -49,6 +49,11 @@
|
||||
console.error('数据获取失败: ', value);
|
||||
}
|
||||
});
|
||||
|
||||
usePageSeo({
|
||||
title: solution.value.title || $t('page-title.solutions'),
|
||||
description: solution.value.summary || '',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -86,6 +86,11 @@
|
||||
console.error('数据获取失败: ', value);
|
||||
}
|
||||
});
|
||||
|
||||
const pageTitle = $t('page-title.solutions');
|
||||
usePageSeo({
|
||||
title: pageTitle,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -31,6 +31,11 @@
|
||||
console.error('数据获取失败: ', value);
|
||||
}
|
||||
});
|
||||
|
||||
const pageTitle = $t('page-title.contact-us');
|
||||
usePageSeo({
|
||||
title: pageTitle,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -111,6 +111,11 @@
|
||||
console.error('数据获取失败: ', value);
|
||||
}
|
||||
});
|
||||
|
||||
const pageTitle = $t('page-title.documents');
|
||||
usePageSeo({
|
||||
title: pageTitle,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -112,6 +112,11 @@
|
||||
console.error('数据获取失败: ', value);
|
||||
}
|
||||
});
|
||||
|
||||
const pageTitle = $t('page-title.faq');
|
||||
usePageSeo({
|
||||
title: pageTitle,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -52,6 +52,11 @@
|
||||
iconComponent: ElIconService,
|
||||
},
|
||||
];
|
||||
|
||||
const pageTitle = $t('page-title.support');
|
||||
usePageSeo({
|
||||
title: pageTitle,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user