feat: 将首页由Strapi迁移至Directus

- 相关路由界面修改
- 增添响应的视图模型与转换方法
This commit is contained in:
2025-10-20 15:07:37 +08:00
parent c156d1414c
commit 05938550e6
5 changed files with 182 additions and 45 deletions

View File

@ -8,3 +8,4 @@ export * from './useQuestionList';
export * from './useDocumentList';
export * from './useContactInfo';
export * from './useCompanyProfile';
export * from './useHomepage';

View File

@ -0,0 +1,54 @@
import { readSingleton } from '@directus/sdk';
export const useHomepage = () => {
const { $directus } = useNuxtApp();
const { getDirectusLocale } = useLocalizations();
const locale = getDirectusLocale();
return useAsyncData(`homepage-${locale}`, async () => {
return await $directus.request(
readSingleton('homepage', {
fields: [
'id',
{
carousel: ['id', 'directus_files_id'],
},
{
recommend_products: [
'id',
{
translations: ['id', 'name', 'summary'],
},
'cover',
],
},
{
recommend_solutions: [
'id',
{
translations: ['id', 'title', 'summary'],
},
'cover',
],
},
],
deep: {
recommend_products: {
translations: {
_filter: {
languages_code: { _eq: locale },
},
},
},
recommend_solutions: {
translations: {
_filter: {
languages_code: { _eq: locale },
},
},
},
},
})
);
});
};

View File

@ -0,0 +1,51 @@
/**
* 将 Directus 返回的 Homepage 数据转换为 HomepageView 视图模型
*
* @param raw: 原始的 Homepage 数据
* @returns 转换后的 HomepageView 对象
*
* @example
* const view = toHomepageView(rawHomepage);
*/
export function toHomepageView(raw: Homepage): HomepageView {
const carousel = (raw.carousel ?? [])
.filter(isObject<HomepageFile>)
.map((item) => item.directus_files_id)
.filter((item) => typeof item === 'string');
const products = (raw.recommend_products ?? [])
.filter(isObject<Product>)
.map((item) => {
const cover = isObject<DirectusFile>(item.cover)
? item.cover.id
: item.cover;
return {
id: item.id,
name: item.translations?.[0].name,
summary: item.translations?.[0].summary,
cover: cover,
} satisfies HomepageProductView;
});
const solutions = (raw.recommend_solutions ?? [])
.filter(isObject<Solution>)
.map((item) => {
const cover = isObject<DirectusFile>(item.cover)
? item.cover.id
: item.cover;
return {
id: item.id,
title: item.translations?.[0].title,
summary: item.translations?.[0].summary,
cover: cover,
} satisfies HomepageSolutionView;
});
return {
id: raw.id,
carousel: carousel ?? [],
recommendProducts: products ?? [],
recommendSolutions: solutions ?? [],
};
}

View File

@ -0,0 +1,50 @@
/**
* 主页推荐产品视图模型
*/
export interface HomepageProductView {
/** 唯一标识符 **/
id: number;
/** 产品名称 **/
name: string;
/** 产品简介 **/
summary: string;
/** 产品封面 **/
cover: string;
}
/**
* 主页推荐解决方案视图模型
*/
export interface HomepageSolutionView {
/** 唯一标识符 **/
id: number;
/** 解决方案标题 **/
title: string;
/** 解决方案摘要 **/
summary: string;
/** 解决方案封面 **/
cover: string;
}
/**
* 主页视图模型
*/
export interface HomepageView {
/** 唯一标识符 **/
id: number;
/** 首页图片 **/
carousel: string[];
/** 首页推荐产品 **/
recommendProducts: HomepageProductView[];
/** 首页推荐解决方案 **/
recommendSolutions: HomepageSolutionView[];
}

View File

@ -12,14 +12,11 @@
<div class="carousel-item">
<el-image
class="carousel-image"
:src="useStrapiMedia(item.url || '')"
:alt="item.alternativeText || `Carousel Image ${index + 1}`"
:src="getImageUrl(item)"
:alt="`Carousel Image ${index + 1}`"
fit="contain"
lazy
/>
<p v-if="item.caption" class="carousel-image-caption">
{{ item.caption }}
</p>
</div>
</el-carousel-item>
</el-carousel>
@ -42,24 +39,24 @@
:autoplay="false"
>
<el-carousel-item
v-for="n in Math.floor(recommend_productions.length / 3) + 1"
v-for="n in Math.floor(recommend_products.length / 3) + 1"
:key="n"
class="recommend-list"
>
<div class="recommend-card-group">
<el-card
v-for="(item, index) in recommend_productions.slice(
v-for="(item, index) in recommend_products.slice(
(n - 1) * 3,
n * 3
)"
:key="index"
class="recommend-card"
@click="handleProductionCardClick(item.documentId || '')"
@click="handleProductionCardClick(item.id.toString() || '')"
>
<template #header>
<el-image
:src="useStrapiMedia(item.cover?.url || '')"
:alt="item.cover?.alternativeText || item.title"
:src="getImageUrl(item.cover)"
:alt="item.name"
fit="cover"
lazy
/>
@ -67,7 +64,7 @@
<div class="recommend-card-body">
<!-- Title -->
<div class="text-center">
<span class="recommend-card-title">{{ item.title }}</span>
<span class="recommend-card-title">{{ item.name }}</span>
</div>
<!-- Description -->
<div class="recommend-card-description text-left opacity-25">
@ -107,12 +104,12 @@
)"
:key="index"
class="recommend-card"
@click="handleSolutionCardClick(item.documentId || '')"
@click="handleSolutionCardClick(item.id.toString() || '')"
>
<template #header>
<el-image
:src="useStrapiMedia(item.cover?.url || '')"
:alt="item.cover?.alternativeText || item.title"
:src="getImageUrl(item.cover)"
:alt="item.title"
fit="cover"
lazy
/>
@ -140,43 +137,27 @@
</template>
<script setup lang="ts">
const { findOne } = useStrapi();
const { getStrapiLocale } = useLocalizations();
const strapiLocale = getStrapiLocale();
const { getImageUrl } = useDirectusImage();
const { data, pending, error } = useAsyncData('homepage', () =>
findOne<StrapiHomepage>('homepage', undefined, {
populate: {
carousel: {
populate: '*',
},
recommend_productions: {
populate: {
cover: {
populate: '*',
},
},
},
recommend_solutions: {
populate: {
cover: {
populate: '*',
},
},
},
},
locale: strapiLocale,
})
);
const { data, pending, error } = await useHomepage();
const carousel = computed(() => data.value?.data.carousel || []);
const recommend_productions = computed(
() => data.value?.data.recommend_productions || []
const homepageData = computed(() => {
return toHomepageView(data.value);
});
const carousel = computed(() => homepageData.value?.carousel);
const recommend_products = computed(
() => homepageData.value?.recommendProducts
);
const recommend_solutions = computed(
() => data.value?.data.recommend_solutions || []
() => homepageData.value?.recommendSolutions
);
watch(pending, () => {
console.log(data.value);
});
watch(error, (value) => {
if (value) {
console.error('数据获取失败: ', value);