From 46e79f0b5c3de6e1b7f8ea3044e595eea559f9ac Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Sat, 18 Oct 2025 16:24:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=86/support/faq=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E8=87=B3Directus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 路由界面脚本修改 - 视图模型相关定义 --- app/composables/directus/index.ts | 1 + app/composables/directus/useQuestionList.ts | 63 +++++++++++++ app/models/mappers/questionMapper.ts | 49 ++++++++++- ...QuestionView.ts => ProductQuestionView.ts} | 4 +- app/models/views/QuestionListVuew.ts | 36 ++++++++ app/pages/support/faq.vue | 88 ++++++++----------- 6 files changed, 186 insertions(+), 55 deletions(-) create mode 100644 app/composables/directus/useQuestionList.ts rename app/models/views/{QuestionView.ts => ProductQuestionView.ts} (64%) create mode 100644 app/models/views/QuestionListVuew.ts diff --git a/app/composables/directus/index.ts b/app/composables/directus/index.ts index f30f5e1..cf1c92b 100644 --- a/app/composables/directus/index.ts +++ b/app/composables/directus/index.ts @@ -4,3 +4,4 @@ export * from './useProductList'; export * from './useProduct'; export * from './useSolutionList'; export * from './useSolution'; +export * from './useQuestionList'; diff --git a/app/composables/directus/useQuestionList.ts b/app/composables/directus/useQuestionList.ts new file mode 100644 index 0000000..76e1350 --- /dev/null +++ b/app/composables/directus/useQuestionList.ts @@ -0,0 +1,63 @@ +import { readItems } from '@directus/sdk'; + +export const useQuestionList = () => { + const { $directus } = useNuxtApp(); + + const { getDirectusLocale } = useLocalizations(); + const locale = getDirectusLocale(); + + return useAsyncData(`question-list-${locale}`, async () => { + return await $directus.request( + readItems('questions', { + fields: [ + 'id', + { + translations: ['*'], + }, + { + products: [ + 'id', + { + products_id: [ + 'id', + { + product_type: [ + 'id', + { + translations: ['id', 'name'], + }, + ], + }, + { translations: ['id', 'name'] }, + ], + }, + ], + }, + ], + deep: { + translations: { + _filter: { + languages_code: { _eq: locale }, + }, + }, + products: { + products_id: { + product_type: { + translations: { + _filter: { + languages_code: { _eq: locale }, + }, + }, + }, + translations: { + _filter: { + languages_code: { _eq: locale }, + }, + }, + }, + }, + }, + }) + ); + }); +}; diff --git a/app/models/mappers/questionMapper.ts b/app/models/mappers/questionMapper.ts index 45ec955..f84ac6e 100644 --- a/app/models/mappers/questionMapper.ts +++ b/app/models/mappers/questionMapper.ts @@ -1,13 +1,13 @@ /** - * 将 Directus 返回的 Question 数据转换为 QuestionView 视图模型 + * 将 Directus 返回的 Question 数据转换为 ProductQuestionView 视图模型 * * @param raw: 原始的 Question 数据 - * @returns 转换后的 QuestionView 对象 + * @returns 转换后的 ProductQuestionView 对象 * * @example - * const view = toQuestionView(rawQuestion); + * const view = toProductQuestionView(rawQuestion); */ -export function toQuestionView(raw: Question): QuestionView { +export function toProductQuestionView(raw: Question): ProductQuestionView { const trans = raw.translations?.[0] ?? { title: '', content: '' }; return { @@ -16,3 +16,44 @@ export function toQuestionView(raw: Question): QuestionView { content: trans.content, }; } + +/** + * 将 Directus 返回的 Question 数据转换为 QuestionListView 视图模型 + * + * @param raw: 原始的 Question 数据 + * @returns 转换后的 QuestionListView 对象 + * --- + * @example + * const view = toQuestionListView(rawQuestion); + */ +export function toQuestionListView(raw: Question): QuestionListView { + const trans = raw.translations?.[0] ?? { title: '', content: '' }; + + const related_products: QuestionListProduct[] = (raw.products ?? []) + .filter(isObject) + .map((item) => item.products_id) + .filter(isObject) + .map((item) => { + const translations = item.translations[0] ?? { name: '' }; + + const product_type = + isObject(item.product_type) && + ({ + id: item.product_type.id, + name: item.product_type.translations[0]?.name, + } satisfies QuestionListProductType); + + return { + id: item.id, + name: translations.name, + type: product_type, + }; + }); + + return { + id: raw.id, + title: trans.title, + content: trans.content, + products: related_products, + }; +} diff --git a/app/models/views/QuestionView.ts b/app/models/views/ProductQuestionView.ts similarity index 64% rename from app/models/views/QuestionView.ts rename to app/models/views/ProductQuestionView.ts index 310afd0..c8a85de 100644 --- a/app/models/views/QuestionView.ts +++ b/app/models/views/ProductQuestionView.ts @@ -1,8 +1,8 @@ /** * 常见问题视图模型 - * 用于常见问题(/support/faqs)渲染的数据结构 + * 用于产品页常见问题渲染的数据结构 */ -export interface QuestionView { +export interface ProductQuestionView { /** 唯一标识符 **/ id: number; diff --git a/app/models/views/QuestionListVuew.ts b/app/models/views/QuestionListVuew.ts new file mode 100644 index 0000000..b839bd2 --- /dev/null +++ b/app/models/views/QuestionListVuew.ts @@ -0,0 +1,36 @@ +/** + * 问题关联产品类型模型 + * 用于在常见问题列表中提供产品筛选功能 + */ +export interface QuestionListProductType { + id: number; + name: string; +} + +/** + * 问题关联产品模型 + * 用于在常见问题列表中提供产品筛选功能 + */ +export interface QuestionListProduct { + id: number; + name: string; + type: QuestionListProductType; +} + +/** + * 常见问题列表视图模型 + * 用于常见问题列表(/support/faq)页面渲染的数据结构 + */ +export interface QuestionListView { + /** 唯一标识符 **/ + id: number; + + /** 问题标题 **/ + title: string; + + /** 问题内容 **/ + content: string; + + /** 关联产品 **/ + products: QuestionListProduct[]; +} diff --git a/app/pages/support/faq.vue b/app/pages/support/faq.vue index da59242..216802a 100644 --- a/app/pages/support/faq.vue +++ b/app/pages/support/faq.vue @@ -28,22 +28,22 @@
import { Search } from '@element-plus/icons-vue'; - const { find } = useStrapi(); - const { getStrapiLocale } = useLocalizations(); - const strapiLocale = getStrapiLocale(); - const { data, pending, error } = useAsyncData('questions', () => - find('questions', { - populate: { - related_productions: { - populate: ['production_type'], - }, - }, - locale: strapiLocale, - }) + const { data, pending, error } = await useQuestionList(); + + const questions = computed( + () => data.value.map((item) => toQuestionListView(item)) ?? null ); - const questions = computed(() => data.value?.data ?? null); - const keyword = ref(''); - const selectedType = ref(null); - const selectedProduction = ref(null); + const selectedType = ref(null); + const selectedProduct = ref(null); - const productionTypeOptions = computed(() => { - const types: ProductionType[] = []; - questions.value.forEach((q: Question) => { - q.related_productions?.forEach((production: Production) => { - const productionType = production?.production_type; - if (!types.some((p) => p.documentId === productionType.documentId)) { - types.push(productionType); + const productTypeOptions = computed(() => { + const types: QuestionListProductType[] = []; + questions.value.forEach((q: QuestionListView) => { + q.products.forEach((product: QuestionListProduct) => { + const productType = product.type; + if (!types.some((p) => p.id === productType.id)) { + types.push(productType); } }); }); return types; }); - const productionOptions = computed(() => { + const productOptions = computed(() => { if (!selectedType.value) return []; - const productions: Production[] = []; - questions.value.forEach((question: Question) => { - question.related_productions.forEach((production: Production) => { + const products: QuestionListProduct[] = []; + questions.value.forEach((q: QuestionListView) => { + q.products.forEach((product: QuestionListProduct) => { if ( - production.production_type?.documentId === selectedType.value && - !productions.some((p) => p.documentId === production.documentId) + product.type.id === selectedType.value && + !products.some((p) => p.id === product.id) ) { - productions.push(production); + products.push(product); } }); }); - return productions; + return products; }); const filteredQuestions = computed(() => { - return questions.value.filter((question: Question) => { - const matchProduction = selectedProduction.value - ? question.related_productions?.some( - (production: Production) => - production.documentId === selectedProduction.value + return questions.value.filter((question: QuestionListView) => { + const matchProduction = selectedProduct.value + ? question.products?.some( + (product: QuestionListProduct) => + product.id === selectedProduct.value ) : selectedType.value - ? question.related_productions?.some( - (production: Production) => - production.production_type?.documentId === selectedType.value + ? question.products?.some( + (product: QuestionListProduct) => + product.type.id === selectedType.value ) : true; @@ -137,7 +127,7 @@ }); watch(selectedType, () => { - selectedProduction.value = null; + selectedProduct.value = null; }); watch(data, (newVal) => {