From 440a46850a31f7b0d453085bfa65bb85000c6e55 Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Mon, 20 Oct 2025 12:30:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=86/support/documents=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E7=9A=84=E6=95=B0=E6=8D=AE=E8=8E=B7=E5=8F=96=E7=94=B1?= =?UTF-8?q?Strapi=E8=BD=AC=E4=B8=BADirectus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改/support/documents.vue,将相关数据获取迁移到Directus - 增添相应的视图模型与映射方法 --- app/composables/directus/index.ts | 1 + app/composables/directus/useDocumentList.ts | 67 ++++++++++++ app/models/mappers/documentMapper.ts | 56 +++++++++- app/models/mappers/productMapper.ts | 4 +- app/models/views/DocumentListView.ts | 42 ++++++++ app/models/views/ProductDocumentView.ts | 2 +- app/pages/support/documents.vue | 112 +++++++++----------- 7 files changed, 213 insertions(+), 71 deletions(-) create mode 100644 app/composables/directus/useDocumentList.ts create mode 100644 app/models/views/DocumentListView.ts diff --git a/app/composables/directus/index.ts b/app/composables/directus/index.ts index cf1c92b..d4a918b 100644 --- a/app/composables/directus/index.ts +++ b/app/composables/directus/index.ts @@ -5,3 +5,4 @@ export * from './useProduct'; export * from './useSolutionList'; export * from './useSolution'; export * from './useQuestionList'; +export * from './useDocumentList'; diff --git a/app/composables/directus/useDocumentList.ts b/app/composables/directus/useDocumentList.ts new file mode 100644 index 0000000..fc21823 --- /dev/null +++ b/app/composables/directus/useDocumentList.ts @@ -0,0 +1,67 @@ +import { readItems } from '@directus/sdk'; + +export const useDocumentList = () => { + const { $directus } = useNuxtApp(); + const { getDirectusLocale } = useLocalizations(); + const locale = getDirectusLocale(); + + return useAsyncData(`document-list-${locale}`, async () => { + return await $directus.request( + readItems('product_documents', { + fields: [ + 'id', + { + file: ['id', 'filesize', 'filename_download'], + }, + { + translations: ['id', 'title'], + }, + { + products: [ + 'id', + { + products_id: [ + 'id', + { + translations: ['id', 'name'], + }, + { + product_type: [ + 'id', + { + translations: ['id', 'name'], + }, + ], + }, + ], + }, + ], + }, + ], + deep: { + translations: { + _filter: { + languages_code: { _eq: locale }, + }, + }, + products: { + products_id: { + translations: { + _filter: { + languages_code: { _eq: locale }, + }, + }, + product_type: { + translations: { + _filter: { + languages_code: { _eq: locale }, + }, + }, + }, + }, + }, + }, + }) + ); + }); +}; diff --git a/app/models/mappers/documentMapper.ts b/app/models/mappers/documentMapper.ts index 01f08ab..cf9dec4 100644 --- a/app/models/mappers/documentMapper.ts +++ b/app/models/mappers/documentMapper.ts @@ -1,13 +1,15 @@ /** - * 将 Directus 返回的 Document 数据转换为 DocumentView 视图模型 + * 将 Directus 返回的 Document 数据转换为 ProductDocumentView 视图模型 * * @param raw: 原始的 Document 数据 - * @returns 转换后的 DocumentView 对象 + * @returns 转换后的 ProductDocumentView 对象 * * @example - * const view = toDocumentView(rawDocument); + * const view = toProductDocumentView(rawDocument); */ -export function toDocumentView(raw: ProductDocument): ProductDocumentView { +export function toProductDocumentView( + raw: ProductDocument +): ProductDocumentView { const trans = raw.translations?.[0] ?? { title: '', }; @@ -27,3 +29,49 @@ export function toDocumentView(raw: ProductDocument): ProductDocumentView { size: file.filesize, }; } + +/** + * 将 Directus 返回的 Document 数据转换为 DocumentListView 视图模型 + * + * @param raw: 原始的 Document 数据 + * @returns 转换后的 DocumentListView 对象 + * + * @example + * const view = toDocumentListView(rawDocument); + */ +export function toDocumentListView(raw: ProductDocument): DocumentListView { + const trans = raw.translations?.[0] ?? { title: '' }; + + const fileId = typeof raw.file === 'string' ? raw.file : raw.file?.id; + const file = raw.file as DirectusFile; + + const { getFileUrl } = useDirectusFiles(); + const url = getFileUrl(fileId); + + const related_products: DocumentListProduct[] = raw.products + ?.filter(isObject) + .map((item) => item.products_id) + .filter(isObject) + .map((item) => { + const productType = + isObject(item.product_type) && + ({ + id: item.product_type.id, + name: item.product_type.translations?.[0]?.name, + } satisfies DocumentListProductType); + return { + id: item.id, + name: item.translations?.[0]?.name, + type: productType, + }; + }); + + return { + id: raw.id, + filename: file.filename_download, + title: trans.title, + url: url, + size: file.filesize, + products: related_products, + }; +} diff --git a/app/models/mappers/productMapper.ts b/app/models/mappers/productMapper.ts index 0ef72aa..a40e284 100644 --- a/app/models/mappers/productMapper.ts +++ b/app/models/mappers/productMapper.ts @@ -106,13 +106,13 @@ export function toProductView(raw: Product): ProductView { .filter(isObject) .map((item) => item.questions_id) .filter(isObject) - .map((item) => toQuestionView(item)); + .map((item) => toProductQuestionView(item)); const documents = (raw.documents ?? []) .filter(isObject) .map((item) => item.product_documents_id) .filter(isObject) - .map((item) => toDocumentView(item)); + .map((item) => toProductDocumentView(item)); return { id: raw.id, diff --git a/app/models/views/DocumentListView.ts b/app/models/views/DocumentListView.ts new file mode 100644 index 0000000..4fd3bae --- /dev/null +++ b/app/models/views/DocumentListView.ts @@ -0,0 +1,42 @@ +/** + * 文档关联产品类型模型 + * 用于在文档库中提供产品筛选功能 + */ +export interface DocumentListProductType { + id: number; + name: string; +} + +/** + * 文档关联产品模型 + * 用于在文档库中提供产品筛选功能 + */ +export interface DocumentListProduct { + id: number; + name: string; + type: DocumentListProductType; +} + +/** + * 文档列表模型 + * 用于文档库(/support/documents)页面渲染的数据结构 + */ +export interface DocumentListView { + /** 唯一标识符 **/ + id: number; + + /** 文件名 **/ + filename: string; + + /** 文档标题 **/ + title: string; + + /** 文档大小 **/ + size: number; + + /** 文档链接 **/ + url: string; + + /** 相关产品 **/ + products: DocumentListProduct[]; +} diff --git a/app/models/views/ProductDocumentView.ts b/app/models/views/ProductDocumentView.ts index 3c72533..dae8aa3 100644 --- a/app/models/views/ProductDocumentView.ts +++ b/app/models/views/ProductDocumentView.ts @@ -1,6 +1,6 @@ /** * 文档视图模型 - * 用于文档页(/support/documents)渲染的数据结构 + * 用于文档列表渲染的数据结构 */ export interface ProductDocumentView { /** 唯一标识符 **/ diff --git a/app/pages/support/documents.vue b/app/pages/support/documents.vue index efd080c..99f66e9 100644 --- a/app/pages/support/documents.vue +++ b/app/pages/support/documents.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 } = await useDocumentList(); - const { data, pending, error } = useAsyncData('documents', () => - find('production-documents', { - populate: ['document', 'related_productions.production_type'], - locale: strapiLocale, - }) + const documents = computed( + () => data?.value.map((item) => toDocumentListView(item)) ?? [] ); - // const documents = computed( - // () => - // data.value?.data.map((item) => ({ - // ...item.document, - // })) || [] - // ); - const documents = computed(() => data.value?.data ?? []); - 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[] = []; - documents.value.forEach((document: ProductionDocument) => { - document.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: DocumentListProductType[] = []; + documents.value.forEach((doc: DocumentListView) => { + doc.products?.forEach((product: DocumentListProduct) => { + const productType = product.type; + if (!types.some((item) => item.id === productType.id)) { + types.push(productType); } }); }); @@ -101,51 +88,48 @@ return types; }); - const productionOptions = computed(() => { + const productOptions = computed(() => { if (!selectedType.value) return []; - const productions: Production[] = []; - documents.value.forEach((document: ProductionDocument) => { - document.related_productions.forEach((production: Production) => { + const products: DocumentListProduct[] = []; + + documents.value.forEach((doc: DocumentListView) => { + doc.products?.forEach((product: DocumentListProduct) => { if ( - production.production_type?.documentId === selectedType.value && - !productions.some((p) => p.documentId === production.documentId) + product.type.id === selectedType.value && + !products.some((item) => item.id === product.id) ) { - productions.push(production); + products.push(product); } }); }); - return productions; + + return products; }); const filteredDocuments = computed(() => - documents.value - .filter((document: ProductionDocument) => { - const matchProduction = selectedProduction.value - ? document.related_productions?.some( - (production: Production) => - production.documentId === selectedProduction.value + documents.value.filter((doc: DocumentListView) => { + const matchProduction = selectedProduct.value + ? doc.products?.some( + (product: DocumentListProduct) => + product.id === selectedProduct.value + ) + : selectedType.value + ? doc.products?.some( + (product: DocumentListProduct) => + product.type?.id === selectedType.value ) - : selectedType.value - ? document.related_productions?.some( - (production: Production) => - production.production_type?.documentId === selectedType.value - ) - : true; - - const matchKeyword = keyword.value - ? document.document.caption && - document.document.caption.includes(keyword.value) : true; - return matchProduction && matchKeyword; - }) - .map((item) => ({ - ...item.document, - })) + const matchKeyword = keyword.value + ? doc.title && doc.title.includes(keyword.value) + : true; + + return matchProduction && matchKeyword; + }) ); watch(selectedType, () => { - selectedProduction.value = null; + selectedProduct.value = null; }); watch(documents, (value) => {