-
@@ -37,8 +38,9 @@
const route = useRoute();
const filters = reactive({
- selectedType: null as string | null,
+ selectedQuestionType: null as string | null,
selectedProduct: null as string | null,
+ selectedProductType: null as string | null,
keyword: '',
});
@@ -57,6 +59,23 @@
const { data: questions, pending, error } = await useQuestionList();
+ const questionTypeOptions = computed(() => {
+ const types: QuestionTypeView[] = [];
+ questions.value.forEach((q: QuestionListView) => {
+ if (!types.some((t) => t.id === q.type.id)) {
+ if (q.type.id === '-1') {
+ types.push({
+ id: '-1',
+ name: $t('product-filter.misc'),
+ });
+ } else {
+ types.push(q.type);
+ }
+ }
+ });
+ return types;
+ });
+
const productTypeOptions = computed(() => {
const types: QuestionListProductType[] = [];
questions.value.forEach((q: QuestionListView) => {
@@ -71,12 +90,12 @@
});
const productOptions = computed(() => {
- if (!filters.selectedType) return [];
+ if (!filters.selectedProductType) return [];
const products: QuestionListProduct[] = [];
questions.value.forEach((q: QuestionListView) => {
q.products.forEach((product: QuestionListProduct) => {
if (
- product.type.id === filters.selectedType &&
+ product.type.id === filters.selectedProductType &&
!products.some((p) => p.id === product.id)
) {
products.push(product);
@@ -98,14 +117,18 @@
(product: QuestionListProduct) =>
product.id === filters.selectedProduct
)
- : filters.selectedType
+ : filters.selectedProductType
? question.products?.some(
(product: QuestionListProduct) =>
- product.type.id === filters.selectedType
+ product.type.id === filters.selectedProductType
)
: true;
- return matchProduct;
+ const matchQuestionType = filters.selectedQuestionType
+ ? question.type.id === filters.selectedQuestionType
+ : true;
+
+ return matchProduct && matchQuestionType;
});
});
@@ -138,7 +161,7 @@
);
watch(
- () => filters.selectedType,
+ () => filters.selectedProductType,
() => {
filters.selectedProduct = null;
}
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 1112bc2..ad02c64 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -79,7 +79,12 @@
"keyword": "Keyword",
"select-product-type": "Select product type",
"select-product-model": "Select product model",
- "enter-keyword": "Enter keyword"
+ "enter-keyword": "Enter keyword",
+ "question-type": "Question Type",
+ "select-question-type": "Select question type",
+ "document-type": "Document Type",
+ "select-document-type": "Select document type",
+ "misc": "Misc"
},
"document-meta": {
"size": "Size",
diff --git a/i18n/locales/es.json b/i18n/locales/es.json
index adea164..64c5316 100644
--- a/i18n/locales/es.json
+++ b/i18n/locales/es.json
@@ -72,14 +72,18 @@
"documents": "Proporcionamos manuales de productos, especificaciones técnicas y otros documentos para la comodidad del usuario.",
"contact-info": "Contáctenos por teléfono o correo electrónico, y le brindaremos servicio presencial."
},
-
"product-filter": {
"product-type": "Tipo de producto",
"product-model": "Modelo del producto",
"keyword": "Palabra clave",
"select-product-type": "Seleccione el tipo de producto",
"select-product-model": "Seleccione modelo de producto",
- "enter-keyword": "Ingrese palabra clave"
+ "enter-keyword": "Ingrese palabra clave",
+ "misc": "Varios",
+ "document-type": "Tipo de documento",
+ "select-document-type": "Seleccionar tipo de documento",
+ "question-type": "Tipo de pregunta",
+ "select-question-type": "Seleccionar tipo de pregunta"
},
"document-meta": {
"size": "Tamaño",
diff --git a/i18n/locales/ru.json b/i18n/locales/ru.json
index 22462c9..33d549f 100644
--- a/i18n/locales/ru.json
+++ b/i18n/locales/ru.json
@@ -72,14 +72,18 @@
"documents": "Предоставляем документацию, такую как руководства по продуктам, технические спецификации, для удобства пользователей.",
"contact-info": "Свяжитесь с нами по телефону или электронной почте, и мы оперативно вам поможем."
},
-
"product-filter": {
"product-type": "Тип продукта",
"product-model": "Модель продукта",
"keyword": "Ключевое слово",
"select-product-type": "Выберите тип продукта",
"select-product-model": "Выберите модель продукта",
- "enter-keyword": "Введите ключевое слово"
+ "enter-keyword": "Введите ключевое слово",
+ "misc": "разное",
+ "document-type": "Тип документа",
+ "question-type": "Тип вопроса",
+ "select-document-type": "Выберите тип документа",
+ "select-question-type": "Выберите тип вопроса"
},
"document-meta": {
"size": "Размер",
diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json
index 50f27ce..f07808a 100644
--- a/i18n/locales/zh.json
+++ b/i18n/locales/zh.json
@@ -78,7 +78,12 @@
"keyword": "关键词",
"select-product-type": "选择产品类型",
"select-product-model": "选择产品系列",
- "enter-keyword": "输入关键词"
+ "enter-keyword": "输入关键词",
+ "question-type": "问题类型",
+ "select-question-type": "选择问题类型",
+ "document-type": "文档类型",
+ "select-document-type": "选择文档类型",
+ "misc": "其他"
},
"document-meta": {
"size": "大小",
diff --git a/server/assets/graphql/documentList.graphql b/server/assets/graphql/documentList.graphql
index 194fea6..3a64458 100644
--- a/server/assets/graphql/documentList.graphql
+++ b/server/assets/graphql/documentList.graphql
@@ -10,6 +10,13 @@ query GetDocumentList($locale: String!) {
id
title
}
+ type {
+ id
+ translations(filter: { languages_code: { code: { _eq: $locale } } }) {
+ id
+ name
+ }
+ }
products {
id
products_id {
diff --git a/server/assets/graphql/questionList.graphql b/server/assets/graphql/questionList.graphql
index 273ed77..bdad9c7 100644
--- a/server/assets/graphql/questionList.graphql
+++ b/server/assets/graphql/questionList.graphql
@@ -1,6 +1,13 @@
query GetQuestionList($locale: String!) {
questions(filter: { status: { _eq: "published" } }) {
id
+ type {
+ id
+ translations(filter: { languages_code: { code: { _eq: $locale } } }) {
+ id
+ name
+ }
+ }
translations(filter: { languages_code: { code: { _eq: $locale } } }) {
id
title
diff --git a/server/mappers/documentMapper.test.ts b/server/mappers/documentMapper.test.ts
index 50b1efc..d62e449 100644
--- a/server/mappers/documentMapper.test.ts
+++ b/server/mappers/documentMapper.test.ts
@@ -1,6 +1,51 @@
import { describe, test, expect } from 'vitest';
-import { toProductDocumentView, toDocumentListView } from './documentMapper';
+import {
+ toProductDocumentView,
+ toDocumentListView,
+ toDocumentTypeView,
+} from './documentMapper';
+/**
+ * 单元测试: toDocumentTypeView
+ */
+describe('toDocumentTypeView', () => {
+ const baseData: DocumentType = {
+ id: 1,
+ translations: [{ id: 1, name: 'Type Name' }],
+ };
+
+ test('convert raw data to DocumentTypeView correctly', () => {
+ const rawData: DocumentType = {
+ ...baseData,
+ };
+
+ expect(toDocumentTypeView(rawData)).toEqual({
+ id: '1',
+ name: 'Type Name',
+ });
+ });
+
+ test('convert raw data with missing translations', () => {
+ const rawData: DocumentType = {
+ ...baseData,
+ translations: [],
+ };
+
+ expect(toDocumentTypeView(rawData)).toEqual({
+ id: '1',
+ name: '',
+ });
+ });
+
+ test('convert null input to default DocumentTypeView', () => {
+ const rawData: DocumentType | null = null;
+
+ expect(toDocumentTypeView(rawData)).toEqual({
+ id: '-1',
+ name: '',
+ });
+ });
+});
/**
* 单元测试: toProductDocumentView
*/
@@ -65,6 +110,15 @@ describe('toProductDocumentView', () => {
describe('toDocumentListView', () => {
const baseData: ProductDocument = {
id: 1,
+ type: {
+ id: 1,
+ translations: [
+ {
+ id: 1,
+ name: 'Type A',
+ },
+ ],
+ },
file: {
id: 'rand-om__-uuid-1234',
filename_download: 'document.pdf',
@@ -94,6 +148,10 @@ describe('toDocumentListView', () => {
title: 'Document Title',
url: '/api/assets/rand-om__-uuid-1234',
size: 2048,
+ type: {
+ id: '1',
+ name: 'Type A',
+ },
products: [
{
id: '1',
diff --git a/server/mappers/documentMapper.ts b/server/mappers/documentMapper.ts
index 9982dee..42ee54d 100644
--- a/server/mappers/documentMapper.ts
+++ b/server/mappers/documentMapper.ts
@@ -1,5 +1,31 @@
import { isObject } from '../../server/utils/object';
+/**
+ * 将 Directus 返回的 DocumentType 数据转换为 DocumentTypeView 视图模型
+ *
+ * @param raw: 原始的 DocumentType 数据
+ * @returns 转换后的 DocumentTypeView 对象
+ *
+ * @example
+ * const view = toDocumentTypeView(rawDocumentType);
+ */
+export function toDocumentTypeView(
+ raw: DocumentType | string | null
+): DocumentTypeView {
+ if (typeof raw === 'string' || raw === null) {
+ return {
+ id: '-1',
+ name: '',
+ } satisfies DocumentTypeView;
+ }
+ const trans = raw.translations?.[0];
+
+ return {
+ id: raw.id.toString(),
+ name: trans?.name ?? '',
+ };
+}
+
/**
* 将 Directus 返回的 Document 数据转换为 ProductDocumentView 视图模型
*
@@ -40,6 +66,8 @@ export function toProductDocumentView(
export function toDocumentListView(raw: ProductDocument): DocumentListView {
const trans = raw.translations?.[0];
+ const type = toDocumentTypeView(raw.type ?? null);
+
const file = isObject
(raw.file) ? raw.file : undefined;
const fileId = file?.id ?? '';
@@ -73,6 +101,7 @@ export function toDocumentListView(raw: ProductDocument): DocumentListView {
title: trans?.title ?? '',
url: url,
size: file?.filesize ?? 0,
+ type: type,
products: related_products,
};
}
diff --git a/server/mappers/questionMapper.test.ts b/server/mappers/questionMapper.test.ts
index b6d45e7..a043db1 100644
--- a/server/mappers/questionMapper.test.ts
+++ b/server/mappers/questionMapper.test.ts
@@ -1,5 +1,51 @@
import { describe, expect, test } from 'vitest';
-import { toProductQuestionView, toQuestionListView } from './questionMapper';
+import {
+ toProductQuestionView,
+ toQuestionListView,
+ toQuestionTypeView,
+} from './questionMapper';
+
+/**
+ * 单元测试: toQuestionTypeView
+ */
+describe('toQuestionTypeView', () => {
+ const baseData: QuestionType = {
+ id: 1,
+ translations: [{ id: 1, name: 'Type Name' }],
+ };
+
+ test('convert raw data to QuestionTypeView correctly', () => {
+ const rawData: QuestionType = {
+ ...baseData,
+ };
+
+ expect(toQuestionTypeView(rawData)).toEqual({
+ id: '1',
+ name: 'Type Name',
+ });
+ });
+
+ test('convert raw data with missing translations', () => {
+ const rawData: QuestionType = {
+ ...baseData,
+ translations: [],
+ };
+
+ expect(toQuestionTypeView(rawData)).toEqual({
+ id: '1',
+ name: '',
+ });
+ });
+
+ test('convert null input to default QuestionTypeView', () => {
+ const rawData: QuestionType | null = null;
+
+ expect(toQuestionTypeView(rawData)).toEqual({
+ id: '-1',
+ name: '',
+ });
+ });
+});
/**
* 单元测试: toProductQuestionView
@@ -43,6 +89,10 @@ describe('toProductQuestionView', () => {
describe('toQuestionListView', () => {
const baseData: Question = {
id: 1,
+ type: {
+ id: 1,
+ translations: [{ id: 1, name: 'Type Name' }],
+ },
translations: [
{ id: 1, title: 'Question Title', content: 'Question Answer' },
],
@@ -68,6 +118,10 @@ describe('toQuestionListView', () => {
expect(toQuestionListView(rawData)).toEqual({
id: '1',
+ type: {
+ id: '1',
+ name: 'Type Name',
+ },
title: 'Question Title',
content: 'Question Answer',
products: [
@@ -104,6 +158,10 @@ describe('toQuestionListView', () => {
expect(toQuestionListView(rawData)).toEqual({
id: '1',
+ type: {
+ id: '1',
+ name: 'Type Name',
+ },
title: '',
content: '',
products: [
diff --git a/server/mappers/questionMapper.ts b/server/mappers/questionMapper.ts
index f30def6..041c21e 100644
--- a/server/mappers/questionMapper.ts
+++ b/server/mappers/questionMapper.ts
@@ -1,5 +1,31 @@
import { isObject } from '../../server/utils/object';
+/**
+ * 将 Directus 返回的 QuestionType 类型转换为 QuestionTypeView 视图模型
+ *
+ * @param raw: 原始的 QuestionType 数据
+ * @returns 转换后的 QuestionTypeView 对象
+ *
+ * @example
+ * const view = toQuestionTypeView(rawQuestionType);
+ */
+export function toQuestionTypeView(
+ raw: QuestionType | string | null
+): QuestionTypeView {
+ if (typeof raw === 'string' || raw === null) {
+ return {
+ id: '-1',
+ name: '',
+ } satisfies QuestionTypeView;
+ }
+ const trans = raw.translations?.[0];
+
+ return {
+ id: raw.id.toString(),
+ name: trans?.name ?? '',
+ };
+}
+
/**
* 将 Directus 返回的 Question 数据转换为 ProductQuestionView 视图模型
*
@@ -31,6 +57,8 @@ export function toProductQuestionView(raw: Question): ProductQuestionView {
export function toQuestionListView(raw: Question): QuestionListView {
const trans = raw.translations?.[0];
+ const type = toQuestionTypeView(raw.type ?? null);
+
const related_products: QuestionListProduct[] = (raw.products ?? [])
.filter(isObject)
.map((item) => item.products_id)
@@ -57,6 +85,7 @@ export function toQuestionListView(raw: Question): QuestionListView {
return {
id: raw.id.toString(),
+ type: type,
title: trans?.title ?? '',
content: trans?.content ?? '',
products: related_products,
diff --git a/shared/types/directus/my-schema.ts b/shared/types/directus/my-schema.ts
index ef138fb..2292e8c 100644
--- a/shared/types/directus/my-schema.ts
+++ b/shared/types/directus/my-schema.ts
@@ -1,3 +1,19 @@
+export interface AiPrompt {
+ /** @primaryKey */
+ id: string;
+ sort?: number | null;
+ date_created?: string | null;
+ user_created?: DirectusUser | string | null;
+ date_updated?: string | null;
+ user_updated?: DirectusUser | string | null;
+ /** @required */
+ name: string;
+ status?: 'published' | 'draft' | 'archived' | null;
+ description?: string | null;
+ system_prompt?: string | null;
+ messages?: Array<{ role: 'user' | 'assistant'; text: string }> | null;
+}
+
export interface CompanyProfile {
/** @primaryKey */
id: number;
@@ -26,6 +42,20 @@ export interface ContactInfoTranslation {
content?: string | null;
}
+export interface DocumentType {
+ /** @primaryKey */
+ id: number;
+ translations?: DocumentTypesTranslation[] | null;
+}
+
+export interface DocumentTypesTranslation {
+ /** @primaryKey */
+ id: number;
+ document_types_id?: DocumentType | string | null;
+ languages_code?: Language | string | null;
+ name?: string | null;
+}
+
export interface Homepage {
/** @primaryKey */
id: number;
@@ -75,8 +105,9 @@ export interface ProductDocument {
id: number;
status?: 'published' | 'draft' | 'archived';
file?: DirectusFile | string | null;
- products?: ProductsProductDocument[] | string[];
+ type?: DocumentType | string | null;
translations?: ProductDocumentsTranslation[] | null;
+ products?: ProductsProductDocument[] | string[];
}
export interface ProductDocumentsTranslation {
@@ -209,10 +240,25 @@ export interface ProductsTranslation {
description?: string | null;
}
+export interface QuestionType {
+ /** @primaryKey */
+ id: number;
+ translations?: QuestionTypesTranslation[] | null;
+}
+
+export interface QuestionTypesTranslation {
+ /** @primaryKey */
+ id: number;
+ question_types_id?: QuestionType | string | null;
+ languages_code?: Language | string | null;
+ name?: string | null;
+}
+
export interface Question {
/** @primaryKey */
id: number;
status?: 'published' | 'draft' | 'archived';
+ type?: QuestionType | string | null;
/** @description i18n字段 */
translations?: QuestionsTranslation[] | null;
products?: ProductsQuestion[] | string[];
@@ -741,10 +787,13 @@ export interface DirectusExtension {
}
export interface Schema {
+ ai_prompts: AiPrompt[];
company_profile: CompanyProfile;
company_profile_translations: CompanyProfileTranslation[];
contact_info: ContactInfo;
contact_info_translations: ContactInfoTranslation[];
+ document_types: DocumentType[];
+ document_types_translations: DocumentTypesTranslation[];
homepage: Homepage;
homepage_files: HomepageFile[];
languages: Language[];
@@ -765,6 +814,8 @@ export interface Schema {
products_product_images: ProductsProductImage[];
products_questions: ProductsQuestion[];
products_translations: ProductsTranslation[];
+ question_types: QuestionType[];
+ question_types_translations: QuestionTypesTranslation[];
questions: Question[];
questions_translations: QuestionsTranslation[];
solution_types: SolutionType[];
@@ -801,10 +852,13 @@ export interface Schema {
}
export enum CollectionNames {
+ ai_prompts = 'ai_prompts',
company_profile = 'company_profile',
company_profile_translations = 'company_profile_translations',
contact_info = 'contact_info',
contact_info_translations = 'contact_info_translations',
+ document_types = 'document_types',
+ document_types_translations = 'document_types_translations',
homepage = 'homepage',
homepage_files = 'homepage_files',
languages = 'languages',
@@ -825,6 +879,8 @@ export enum CollectionNames {
products_product_images = 'products_product_images',
products_questions = 'products_questions',
products_translations = 'products_translations',
+ question_types = 'question_types',
+ question_types_translations = 'question_types_translations',
questions = 'questions',
questions_translations = 'questions_translations',
solution_types = 'solution_types',
diff --git a/shared/types/views/document-list-view.ts b/shared/types/views/document-list-view.ts
index a3cdefe..bab3047 100644
--- a/shared/types/views/document-list-view.ts
+++ b/shared/types/views/document-list-view.ts
@@ -1,3 +1,14 @@
+/**
+ * 文档类型视图模型
+ * 用于在文档库中提供类型筛选功能
+ */
+export interface DocumentTypeView {
+ /** 唯一标识符 **/
+ id: string;
+ /** 类型名 **/
+ name: string;
+}
+
/**
* 文档关联产品类型模型
* 用于在文档库中提供产品筛选功能
@@ -40,6 +51,9 @@ export interface DocumentListView {
/** 文档链接 **/
url: string;
+ /** 文档类型 **/
+ type: DocumentTypeView;
+
/** 相关产品 **/
products: DocumentListProduct[];
}
diff --git a/shared/types/views/question-list-view.ts b/shared/types/views/question-list-view.ts
index 4ea843c..362fd93 100644
--- a/shared/types/views/question-list-view.ts
+++ b/shared/types/views/question-list-view.ts
@@ -7,6 +7,18 @@ export interface QuestionListProductType {
name: string;
}
+/**
+ * 问题类型
+ * 用于在常见问题列表中提供产品筛选功能
+ */
+export interface QuestionTypeView {
+ /** 唯一标识符 **/
+ id: string;
+
+ /** 类型名 **/
+ name: string;
+}
+
/**
* 问题关联产品模型
* 用于在常见问题列表中提供产品筛选功能
@@ -25,6 +37,9 @@ export interface QuestionListView {
/** 唯一标识符 **/
id: string;
+ /** 问题类型 **/
+ type: QuestionTypeView;
+
/** 问题标题 **/
title: string;