refactor: 产品页与产品列表的API重构
- 将产品页与产品列表的API由REST重构为GraphQL - 修改Mapper与单元测试
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import { readItem } from '@directus/sdk';
|
import GetProduct from '@/graphql/product.graphql';
|
||||||
|
import { print } from 'graphql';
|
||||||
|
|
||||||
export const useProduct = (id: string) => {
|
export const useProduct = (id: string) => {
|
||||||
const { $directus } = useNuxtApp();
|
const { $directus } = useNuxtApp();
|
||||||
@ -7,104 +8,111 @@ export const useProduct = (id: string) => {
|
|||||||
const locale = getDirectusLocale();
|
const locale = getDirectusLocale();
|
||||||
|
|
||||||
return useAsyncData(`product-${id}-${locale}`, async () => {
|
return useAsyncData(`product-${id}-${locale}`, async () => {
|
||||||
return await $directus.request(
|
return await $directus.query<{ products_by_id: Product }>(
|
||||||
readItem('products', id, {
|
print(GetProduct),
|
||||||
fields: [
|
{
|
||||||
'id',
|
id: id,
|
||||||
{ translations: ['id', 'name', 'summary', 'description'] },
|
locale: locale,
|
||||||
{
|
}
|
||||||
images: [
|
|
||||||
'id',
|
|
||||||
{
|
|
||||||
product_images_id: [
|
|
||||||
'id',
|
|
||||||
'image',
|
|
||||||
{ translations: ['id', 'caption'] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
specs: [
|
|
||||||
'id',
|
|
||||||
{
|
|
||||||
translations: ['*'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
specs: [
|
|
||||||
'id',
|
|
||||||
{
|
|
||||||
translations: ['id', 'key', 'value'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
faqs: [
|
|
||||||
'id',
|
|
||||||
{
|
|
||||||
questions_id: [
|
|
||||||
'id',
|
|
||||||
{
|
|
||||||
translations: ['id', 'title', 'content'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
documents: [
|
|
||||||
'id',
|
|
||||||
{
|
|
||||||
product_documents_id: [
|
|
||||||
'id',
|
|
||||||
{
|
|
||||||
file: ['id', 'filesize', 'filename_download'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
translations: ['id', 'title'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
deep: {
|
|
||||||
translations: {
|
|
||||||
_filter: {
|
|
||||||
languages_code: { _eq: locale },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
images: {
|
|
||||||
product_images_id: {
|
|
||||||
translations: {
|
|
||||||
_filter: {
|
|
||||||
languages_code: { _eq: locale },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
faqs: {
|
|
||||||
questions_id: {
|
|
||||||
translations: {
|
|
||||||
_filter: {
|
|
||||||
languages_code: { _eq: locale },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
documents: {
|
|
||||||
documents_id: {
|
|
||||||
translations: {
|
|
||||||
_filter: {
|
|
||||||
languages_code: { _eq: locale },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
// return await $directus.request(
|
||||||
|
// readItem('products', id, {
|
||||||
|
// fields: [
|
||||||
|
// 'id',
|
||||||
|
// { translations: ['id', 'name', 'summary', 'description'] },
|
||||||
|
// {
|
||||||
|
// images: [
|
||||||
|
// 'id',
|
||||||
|
// {
|
||||||
|
// product_images_id: [
|
||||||
|
// 'id',
|
||||||
|
// 'image',
|
||||||
|
// { translations: ['id', 'caption'] },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// specs: [
|
||||||
|
// 'id',
|
||||||
|
// {
|
||||||
|
// translations: ['*'],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// specs: [
|
||||||
|
// 'id',
|
||||||
|
// {
|
||||||
|
// translations: ['id', 'key', 'value'],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// faqs: [
|
||||||
|
// 'id',
|
||||||
|
// {
|
||||||
|
// questions_id: [
|
||||||
|
// 'id',
|
||||||
|
// {
|
||||||
|
// translations: ['id', 'title', 'content'],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// documents: [
|
||||||
|
// 'id',
|
||||||
|
// {
|
||||||
|
// product_documents_id: [
|
||||||
|
// 'id',
|
||||||
|
// {
|
||||||
|
// file: ['id', 'filesize', 'filename_download'],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// translations: ['id', 'title'],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// deep: {
|
||||||
|
// translations: {
|
||||||
|
// _filter: {
|
||||||
|
// languages_code: { _eq: locale },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// images: {
|
||||||
|
// product_images_id: {
|
||||||
|
// translations: {
|
||||||
|
// _filter: {
|
||||||
|
// languages_code: { _eq: locale },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// faqs: {
|
||||||
|
// questions_id: {
|
||||||
|
// translations: {
|
||||||
|
// _filter: {
|
||||||
|
// languages_code: { _eq: locale },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// documents: {
|
||||||
|
// documents_id: {
|
||||||
|
// translations: {
|
||||||
|
// _filter: {
|
||||||
|
// languages_code: { _eq: locale },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// );
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { readItems } from '@directus/sdk';
|
import GetProductList from '@/graphql/productList.graphql';
|
||||||
|
import { print } from 'graphql';
|
||||||
|
|
||||||
export const useProductList = () => {
|
export const useProductList = () => {
|
||||||
const { $directus } = useNuxtApp();
|
const { $directus } = useNuxtApp();
|
||||||
@ -7,37 +8,11 @@ export const useProductList = () => {
|
|||||||
const locale = getDirectusLocale();
|
const locale = getDirectusLocale();
|
||||||
|
|
||||||
return useAsyncData(`product-list-${locale}`, async () => {
|
return useAsyncData(`product-list-${locale}`, async () => {
|
||||||
return await $directus.request(
|
return await $directus.query<{ products: Product[] }>(
|
||||||
readItems('products', {
|
print(GetProductList),
|
||||||
fields: [
|
{
|
||||||
'id',
|
locale: locale,
|
||||||
{ translations: ['id', 'name', 'summary'] },
|
}
|
||||||
'cover',
|
|
||||||
{
|
|
||||||
product_type: [
|
|
||||||
'id',
|
|
||||||
{
|
|
||||||
translations: ['id', 'name'],
|
|
||||||
},
|
|
||||||
'sort',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
deep: {
|
|
||||||
translations: {
|
|
||||||
_filter: {
|
|
||||||
languages_code: { _eq: locale },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
product_type: {
|
|
||||||
translations: {
|
|
||||||
_filter: {
|
|
||||||
languages_code: { _eq: locale },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
66
app/graphql/product.graphql
Normal file
66
app/graphql/product.graphql
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
query GetProduct($id: ID!, $locale: String!) {
|
||||||
|
products_by_id(id: $id) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
translations(filter: { languages_code: { code: { _eq: $locale } } }) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
summary
|
||||||
|
description
|
||||||
|
}
|
||||||
|
images {
|
||||||
|
id
|
||||||
|
product_images_id {
|
||||||
|
id
|
||||||
|
image {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
translations(filter: { languages_code: { code: { _eq: $locale } } }) {
|
||||||
|
id
|
||||||
|
caption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
specs {
|
||||||
|
id
|
||||||
|
translations(filter: { languages_code: { code: { _eq: $locale } } }) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
specs {
|
||||||
|
translations(filter: { languages_code: { code: { _eq: $locale } } }) {
|
||||||
|
id
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
faqs {
|
||||||
|
id
|
||||||
|
questions_id {
|
||||||
|
id
|
||||||
|
translations(filter: { languages_code: { code: { _eq: $locale } } }) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
documents {
|
||||||
|
id
|
||||||
|
product_documents_id {
|
||||||
|
id
|
||||||
|
file {
|
||||||
|
id
|
||||||
|
filesize
|
||||||
|
filename_download
|
||||||
|
}
|
||||||
|
translations(filter: { languages_code: { code: { _eq: $locale } } }) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
22
app/graphql/productList.graphql
Normal file
22
app/graphql/productList.graphql
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
query GetProductList($locale: String!) {
|
||||||
|
products(filter: { status: { _eq: "in-production" } }) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
translations(filter: { languages_code: { code: { _eq: $locale } } }) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
summary
|
||||||
|
}
|
||||||
|
cover {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
product_type {
|
||||||
|
id
|
||||||
|
translations(filter: { languages_code: { code: { _eq: $locale } } }) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
sort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,9 @@ describe('toProductListView', () => {
|
|||||||
translations: [
|
translations: [
|
||||||
{ id: 10, name: 'Product Name', summary: 'Product Summary' },
|
{ id: 10, name: 'Product Name', summary: 'Product Summary' },
|
||||||
],
|
],
|
||||||
cover: 'cover-file-uuid-1234',
|
cover: {
|
||||||
|
id: 'cover-file-uuid-1234',
|
||||||
|
},
|
||||||
product_type: {
|
product_type: {
|
||||||
id: 1,
|
id: 1,
|
||||||
translations: [{ id: 20, name: 'Type Name' }],
|
translations: [{ id: 20, name: 'Type Name' }],
|
||||||
@ -35,7 +37,9 @@ describe('toProductListView', () => {
|
|||||||
const rawData: Product = {
|
const rawData: Product = {
|
||||||
id: 1,
|
id: 1,
|
||||||
translations: [],
|
translations: [],
|
||||||
cover: 'cover-file-uuid-1234',
|
cover: {
|
||||||
|
id: 'cover-file-uuid-1234',
|
||||||
|
},
|
||||||
product_type: {
|
product_type: {
|
||||||
id: 20,
|
id: 20,
|
||||||
translations: [],
|
translations: [],
|
||||||
|
|||||||
@ -33,12 +33,14 @@ export function toProductListView(raw: Product): ProductListView {
|
|||||||
? toProductTypeView(raw.product_type)
|
? toProductTypeView(raw.product_type)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const cover = isObject<DirectusFile>(raw.cover) ? raw.cover.id : '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: raw.id,
|
id: raw.id,
|
||||||
product_type: type,
|
product_type: type,
|
||||||
name: trans.name,
|
name: trans.name,
|
||||||
summary: trans.summary,
|
summary: trans.summary,
|
||||||
cover: raw.cover.toString(),
|
cover: cover,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,9 +112,10 @@ export function toProductView(raw: Product): ProductView {
|
|||||||
.map((item) => item.product_images_id)
|
.map((item) => item.product_images_id)
|
||||||
.filter(isObject<ProductImage>)
|
.filter(isObject<ProductImage>)
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
|
const image = isObject<DirectusFile>(item.image) ? item.image.id : '';
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
image: item.image.toString(),
|
image: image,
|
||||||
caption: item.translations?.[0]?.caption || '',
|
caption: item.translations?.[0]?.caption || '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -35,11 +35,12 @@
|
|||||||
const localePath = useLocalePath();
|
const localePath = useLocalePath();
|
||||||
|
|
||||||
// 获取路由参数
|
// 获取路由参数
|
||||||
const id = computed(() => route.params.slug as string);
|
const id = route.params.slug as string;
|
||||||
|
|
||||||
const { data, pending, error } = await useProduct(id.value);
|
const { data, pending, error } = await useProduct(id);
|
||||||
|
|
||||||
|
const rawProduct = computed(() => data.value.products_by_id ?? null);
|
||||||
|
|
||||||
const rawProduct = computed(() => data.value ?? null);
|
|
||||||
const product = computed(() => {
|
const product = computed(() => {
|
||||||
if (rawProduct.value === null) {
|
if (rawProduct.value === null) {
|
||||||
return null;
|
return null;
|
||||||
@ -60,10 +61,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// SEO
|
// SEO
|
||||||
usePageSeo({
|
// usePageSeo({
|
||||||
title: product.value.name || $t('page-title.products'),
|
// title: product.value.name || $t('page-title.products'),
|
||||||
description: product.value.summary || '',
|
// description: product.value.summary || '',
|
||||||
});
|
// });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -37,7 +37,7 @@
|
|||||||
const localePath = useLocalePath();
|
const localePath = useLocalePath();
|
||||||
const { getImageUrl } = useDirectusImage();
|
const { getImageUrl } = useDirectusImage();
|
||||||
|
|
||||||
const { data, pending, error } = useProductList();
|
const { data, pending, error } = await useProductList();
|
||||||
|
|
||||||
const activeNames = ref<string[]>([]);
|
const activeNames = ref<string[]>([]);
|
||||||
|
|
||||||
@ -46,12 +46,13 @@
|
|||||||
{ label: $t('navigation.products') },
|
{ label: $t('navigation.products') },
|
||||||
];
|
];
|
||||||
|
|
||||||
const productsRaw = computed(() => data.value ?? []);
|
const productsRaw = computed(() => data.value.products ?? []);
|
||||||
|
|
||||||
const products = computed(() =>
|
const products = computed(() =>
|
||||||
productsRaw.value.map((item) => toProductListView(item))
|
productsRaw.value.map((item) => toProductListView(item))
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.debug('products: ', products.value);
|
logger.debug('产品列表数据: ', products.value);
|
||||||
|
|
||||||
// 按类型分组
|
// 按类型分组
|
||||||
const groupedProducts = computed(() => {
|
const groupedProducts = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user