Compare commits
5 Commits
33c94fb885
...
cb861bc955
| Author | SHA1 | Date | |
|---|---|---|---|
| cb861bc955 | |||
| 1704a7b5c1 | |||
| 98f978484c | |||
| de7c03a7a9 | |||
| e158ec8cf5 |
@ -6,18 +6,19 @@
|
|||||||
class="document-card"
|
class="document-card"
|
||||||
>
|
>
|
||||||
<div class="document-info">
|
<div class="document-info">
|
||||||
<h3>{{ doc.caption || doc.name }}</h3>
|
<h3>{{ doc.title }}</h3>
|
||||||
<div class="document-content">
|
<div class="document-content">
|
||||||
<span v-if="doc.size" class="document-meta"
|
<span v-if="doc.size" class="document-meta"
|
||||||
>大小: {{ formatFileSize(doc.size) }}
|
>大小: {{ formatFileSize(doc.size) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="doc.ext" class="document-meta"
|
<span v-if="doc.filename" class="document-meta"
|
||||||
>格式: {{ formatFileExtension(doc.ext) }}</span
|
>格式:
|
||||||
|
{{ formatFileExtension(getFileExtension(doc.filename)) }}</span
|
||||||
>
|
>
|
||||||
<el-button
|
<el-button
|
||||||
class="download-button"
|
class="download-button"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleDownload(doc.name, doc.url)"
|
@click="handleDownload(doc.title, doc.url)"
|
||||||
>
|
>
|
||||||
下载
|
下载
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -30,7 +31,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps({
|
defineProps({
|
||||||
documents: {
|
documents: {
|
||||||
type: Array as () => Array<StrapiMedia>,
|
type: Array as () => Array<ProductDocumentView>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
<el-collapse class="question-collapse" accordion>
|
<el-collapse class="question-collapse" accordion>
|
||||||
<el-collapse-item
|
<el-collapse-item
|
||||||
v-for="question in questions"
|
v-for="question in questions"
|
||||||
:key="question.documentId"
|
:key="question.id"
|
||||||
:title="question.title"
|
:title="question.title"
|
||||||
:name="question.documentId"
|
:name="question.id"
|
||||||
>
|
>
|
||||||
<markdown-renderer :content="question.content || ''" />
|
<markdown-renderer :content="question.content || ''" />
|
||||||
</el-collapse-item>
|
</el-collapse-item>
|
||||||
@ -16,11 +16,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps({
|
defineProps({
|
||||||
questions: {
|
questions: {
|
||||||
type: Array as () => Array<{
|
type: Array as PropType<QuestionView[]>,
|
||||||
title: string;
|
|
||||||
content: string;
|
|
||||||
documentId: string;
|
|
||||||
}>,
|
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,15 +3,15 @@
|
|||||||
<el-collapse v-model="activeName">
|
<el-collapse v-model="activeName">
|
||||||
<el-collapse-item
|
<el-collapse-item
|
||||||
v-for="item in data"
|
v-for="item in data"
|
||||||
:key="item.title"
|
:key="item.name"
|
||||||
:title="item.title"
|
:title="item.name"
|
||||||
:name="item.title"
|
:name="item.name"
|
||||||
>
|
>
|
||||||
<el-descriptions :column="1" border>
|
<el-descriptions :column="1" border>
|
||||||
<el-descriptions-item
|
<el-descriptions-item
|
||||||
v-for="subItem in item.items"
|
v-for="subItem in item.specs"
|
||||||
:key="subItem.label"
|
:key="subItem.key"
|
||||||
:label="subItem.label"
|
:label="subItem.value"
|
||||||
>
|
>
|
||||||
{{ subItem.value }}
|
{{ subItem.value }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
@ -24,15 +24,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: Object as () => ProductionSpecGroup[],
|
type: Object as () => ProductSpecGroupView[],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 默认全部展开
|
// 默认全部展开
|
||||||
const activeName = ref<string[]>(
|
const activeName = ref<string[]>(
|
||||||
props.data.map((item: ProductionSpecGroup) => {
|
props.data.map((item: ProductSpecGroupView) => {
|
||||||
return item.title;
|
return item.name;
|
||||||
}) || []
|
}) || []
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
22
app/composables/useDirectusFiles.ts
Normal file
22
app/composables/useDirectusFiles.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export const useDirectusFiles = () => {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const baseUrl = config.public.directus.url;
|
||||||
|
|
||||||
|
const getFileUrl = (
|
||||||
|
id?: string | null,
|
||||||
|
options?: Record<string, string | number | boolean>
|
||||||
|
): string => {
|
||||||
|
if (!id) return '';
|
||||||
|
const query = options
|
||||||
|
? '?' +
|
||||||
|
new URLSearchParams(
|
||||||
|
Object.entries(options).map(([key, value]) => [key, String(value)])
|
||||||
|
).toString()
|
||||||
|
: '';
|
||||||
|
return `${baseUrl}/assets/${id}${query}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getFileUrl,
|
||||||
|
};
|
||||||
|
};
|
||||||
32
app/composables/useDirectusImage.ts
Normal file
32
app/composables/useDirectusImage.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export const useDirectusImage = () => {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const baseUrl = config.public.directus.url;
|
||||||
|
const token = config.public.directus.token;
|
||||||
|
|
||||||
|
type DirectusAssetParams = {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
fit?: 'cover' | 'contain' | 'inside' | 'outside';
|
||||||
|
quality?: number;
|
||||||
|
format?: 'webp' | 'jpg' | 'png' | 'auto';
|
||||||
|
} & Record<string, string | number | boolean>;
|
||||||
|
|
||||||
|
const getImageUrl = (id?: string | null, options?: DirectusAssetParams) => {
|
||||||
|
if (!id) return '';
|
||||||
|
const queryToken = token ? 'access_token=' + token : '';
|
||||||
|
const queryOptions = options
|
||||||
|
? new URLSearchParams(
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(options).map(([key, value]) => [key, String(value)])
|
||||||
|
)
|
||||||
|
).toString
|
||||||
|
: '';
|
||||||
|
const query =
|
||||||
|
queryToken || queryOptions
|
||||||
|
? `?${[queryToken, queryOptions].filter(Boolean).join('&')}`
|
||||||
|
: '';
|
||||||
|
return `${baseUrl}/assets/${id}${query}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { getImageUrl };
|
||||||
|
};
|
||||||
29
app/models/mappers/documentMapper.ts
Normal file
29
app/models/mappers/documentMapper.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 将 Directus 返回的 Document 数据转换为 DocumentView 视图模型
|
||||||
|
*
|
||||||
|
* @param raw: 原始的 Document 数据
|
||||||
|
* @returns 转换后的 DocumentView 对象
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const view = toDocumentView(rawDocument);
|
||||||
|
*/
|
||||||
|
export function toDocumentView(raw: ProductDocument): ProductDocumentView {
|
||||||
|
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);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: raw.id,
|
||||||
|
filename: file.filename_download,
|
||||||
|
title: trans.title,
|
||||||
|
url: url,
|
||||||
|
size: file.filesize,
|
||||||
|
};
|
||||||
|
}
|
||||||
134
app/models/mappers/productMapper.ts
Normal file
134
app/models/mappers/productMapper.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* 将 Directus返回的 Product 数据转换为 ProductListView 视图模型
|
||||||
|
*
|
||||||
|
* @param raw: 原始的 Product 数据
|
||||||
|
* @returns 转换后的 ProductListView 对象
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const view = toProductListView(rawProduct);
|
||||||
|
*/
|
||||||
|
export function toProductListView(raw: Product): ProductListView {
|
||||||
|
const trans = raw.translations?.[0] ?? { name: '', summary: '' };
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: raw.id,
|
||||||
|
product_type:
|
||||||
|
typeof raw.product_type === 'string'
|
||||||
|
? raw.product_type
|
||||||
|
: typeof raw.product_type === 'object' && raw.product_type
|
||||||
|
? raw.product_type.translations[0].name || ''
|
||||||
|
: '',
|
||||||
|
name: trans.name,
|
||||||
|
summary: trans.summary,
|
||||||
|
cover: raw.cover.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 Directus 返回的 ProductSpecGroup 数据转换为 ProductSpecGroupView 视图模型
|
||||||
|
*
|
||||||
|
* @param raw: 原始的 ProductSpecGroup 数据
|
||||||
|
* @returns 转换后的 ProductSpecGroupView 对象
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const view = toProductSpecGroupView(rawSpecGroup);
|
||||||
|
*/
|
||||||
|
export function toProductSpecGroupView(
|
||||||
|
raw: ProductSpecGroup
|
||||||
|
): ProductSpecGroupView {
|
||||||
|
const trans = raw.translations?.[0] ?? {
|
||||||
|
name: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: raw.id,
|
||||||
|
name: trans.name,
|
||||||
|
specs: raw.specs
|
||||||
|
.filter(isObject<ProductSpec>)
|
||||||
|
.map((item) => toProductSpecView(item)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 Directus 返回的 ProductSpec 数据转换为 ProductSpecView 视图模型
|
||||||
|
*
|
||||||
|
* @param raw: 原始的 ProductSpec 数据
|
||||||
|
* @returns 转换后的 ProductSpecView 对象
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const view = toProductSpecView(rawSpecGroup);
|
||||||
|
*/
|
||||||
|
export function toProductSpecView(raw: ProductSpec): ProductSpecView {
|
||||||
|
const trans = raw.translations?.[0] ?? {
|
||||||
|
key: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: raw.id,
|
||||||
|
key: trans.key,
|
||||||
|
value: raw.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 Directus 返回的 Product 数据转换为 ProductView 视图模型
|
||||||
|
*
|
||||||
|
* @param raw: 原始的 Product 数据
|
||||||
|
* @returns 转换后的 ProductView 对象
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const view = toProductView(rawProduct);
|
||||||
|
*/
|
||||||
|
export function toProductView(raw: Product): ProductView {
|
||||||
|
const trans = raw.translations?.[0] ?? {
|
||||||
|
name: '',
|
||||||
|
summary: '',
|
||||||
|
description: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const images = (raw.images ?? [])
|
||||||
|
.filter(isObject<ProductsProductImage>)
|
||||||
|
.map((item) => item.product_images_id)
|
||||||
|
.filter(isObject<ProductImage>)
|
||||||
|
.map((item) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
image: item.image.toString(),
|
||||||
|
caption: item.translations?.[0]?.caption || '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const specs = (raw.specs ?? [])
|
||||||
|
.filter(isObject<ProductSpecGroup>)
|
||||||
|
.map((item) => toProductSpecGroupView(item));
|
||||||
|
|
||||||
|
const faqs = (raw.faqs ?? [])
|
||||||
|
.filter(isObject<ProductsQuestion>)
|
||||||
|
.map((item) => item.questions_id)
|
||||||
|
.filter(isObject<Question>)
|
||||||
|
.map((item) => toQuestionView(item));
|
||||||
|
|
||||||
|
const documents = (raw.documents ?? [])
|
||||||
|
.filter(isObject<ProductsProductDocument>)
|
||||||
|
.map((item) => item.product_documents_id)
|
||||||
|
.filter(isObject<ProductDocument>)
|
||||||
|
.map((item) => toDocumentView(item));
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: raw.id,
|
||||||
|
|
||||||
|
product_type:
|
||||||
|
typeof raw.product_type === 'string'
|
||||||
|
? raw.product_type
|
||||||
|
: typeof raw.product_type === 'object' && raw.product_type
|
||||||
|
? raw.product_type.translations[0].name || ''
|
||||||
|
: '',
|
||||||
|
name: trans.name,
|
||||||
|
summary: trans.summary,
|
||||||
|
images: images,
|
||||||
|
description: trans.description,
|
||||||
|
specs: specs,
|
||||||
|
faqs: faqs,
|
||||||
|
documents: documents,
|
||||||
|
};
|
||||||
|
}
|
||||||
18
app/models/mappers/questionMapper.ts
Normal file
18
app/models/mappers/questionMapper.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* 将 Directus 返回的 Question 数据转换为 QuestionView 视图模型
|
||||||
|
*
|
||||||
|
* @param raw: 原始的 Question 数据
|
||||||
|
* @returns 转换后的 QuestionView 对象
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const view = toQuestionView(rawQuestion);
|
||||||
|
*/
|
||||||
|
export function toQuestionView(raw: Question): QuestionView {
|
||||||
|
const trans = raw.translations?.[0] ?? { title: '', content: '' };
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: raw.id,
|
||||||
|
title: trans.title,
|
||||||
|
content: trans.content,
|
||||||
|
};
|
||||||
|
}
|
||||||
30
app/models/utils/object.ts
Normal file
30
app/models/utils/object.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 判断某一值是否为非null对象
|
||||||
|
*
|
||||||
|
* @template T 泛型类型,用于推断目标对象的类型
|
||||||
|
* @param value: 需要判断的值
|
||||||
|
* @returns 如果值是非null对象则返回true,否则返回false
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* if (isObject<Product>(value)) value.id
|
||||||
|
*/
|
||||||
|
export const isObject = <T extends object>(value: unknown): value is T =>
|
||||||
|
typeof value === 'object' && value !== null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断某一值是否为非null对象组成的数组
|
||||||
|
*
|
||||||
|
* @template T 泛型类型,用于推断目标对象的类型
|
||||||
|
* @param value: 需要判断的值
|
||||||
|
* @returns 如果值是非null对象组成的数组则返回true,否则返回false
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const data: unknown = [{ id: 1 }, { id: 2 }];
|
||||||
|
* if (isArrayOfObject)<{ id: number }>(data) {
|
||||||
|
* // TypeScript 知道 data 是 { id: number }[] 类型
|
||||||
|
* console.log(data[0].id);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const isArrayOfObject = <T extends object>(arr: unknown): arr is T[] => {
|
||||||
|
return Array.isArray(arr) && arr.every(isObject<T>);
|
||||||
|
};
|
||||||
20
app/models/views/ProductDocumentView.ts
Normal file
20
app/models/views/ProductDocumentView.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 文档视图模型
|
||||||
|
* 用于文档页(/support/documents)渲染的数据结构
|
||||||
|
*/
|
||||||
|
export interface ProductDocumentView {
|
||||||
|
/** 唯一标识符 **/
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/** 文件名 **/
|
||||||
|
filename: string;
|
||||||
|
|
||||||
|
/** 文档标题 **/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/** 文档大小 **/
|
||||||
|
size: number;
|
||||||
|
|
||||||
|
/** 文档链接 **/
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
20
app/models/views/ProductListView.ts
Normal file
20
app/models/views/ProductListView.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 产品列表视图模型
|
||||||
|
* 用于产品列表(/products)渲染的数据结构
|
||||||
|
*/
|
||||||
|
export interface ProductListView {
|
||||||
|
/** 唯一标识符 **/
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/** 产品名称 **/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/** 产品简介 **/
|
||||||
|
summary: string;
|
||||||
|
|
||||||
|
/** 产品类型 **/
|
||||||
|
product_type: string;
|
||||||
|
|
||||||
|
/** 产品封面(图片的id) **/
|
||||||
|
cover: string;
|
||||||
|
}
|
||||||
29
app/models/views/ProductSpecGroupView.ts
Normal file
29
app/models/views/ProductSpecGroupView.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 产品规格模型
|
||||||
|
* 用于产品规格渲染的数据结构
|
||||||
|
*/
|
||||||
|
export interface ProductSpecView {
|
||||||
|
/** 唯一标识符 **/
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/** 规格名称 **/
|
||||||
|
key: string;
|
||||||
|
|
||||||
|
/** 规格值 **/
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品规格表模型
|
||||||
|
* 用于产品规格表渲染的数据结构
|
||||||
|
*/
|
||||||
|
export interface ProductSpecGroupView {
|
||||||
|
/** 唯一标识符 **/
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/** 规格组名称 **/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/** 规格组 **/
|
||||||
|
specs: ProductSpecView[];
|
||||||
|
}
|
||||||
38
app/models/views/ProductView.ts
Normal file
38
app/models/views/ProductView.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
interface ImageView {
|
||||||
|
id: number;
|
||||||
|
image: string;
|
||||||
|
caption: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品视图模型
|
||||||
|
* 用于产品详情页(/products/[slug])渲染的数据结构
|
||||||
|
*/
|
||||||
|
export interface ProductView {
|
||||||
|
/** 唯一标识符 **/
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/** 产品名称 **/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/** 产品简介 **/
|
||||||
|
summary: string;
|
||||||
|
|
||||||
|
/** 产品类型 **/
|
||||||
|
product_type: string;
|
||||||
|
|
||||||
|
/** 产品图片 **/
|
||||||
|
images: ImageView[];
|
||||||
|
|
||||||
|
/** 产品详情 **/
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
/** 产品规格 **/
|
||||||
|
specs: ProductSpecGroupView[];
|
||||||
|
|
||||||
|
/** 产品常见问题 **/
|
||||||
|
faqs: QuestionView[];
|
||||||
|
|
||||||
|
/** 产品文档 **/
|
||||||
|
documents: ProductDocumentView[];
|
||||||
|
}
|
||||||
14
app/models/views/QuestionView.ts
Normal file
14
app/models/views/QuestionView.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 常见问题视图模型
|
||||||
|
* 用于常见问题(/support/faqs)渲染的数据结构
|
||||||
|
*/
|
||||||
|
export interface QuestionView {
|
||||||
|
/** 唯一标识符 **/
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/** 问题标题 **/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/** 问题内容 **/
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
}}</NuxtLink>
|
}}</NuxtLink>
|
||||||
</el-breadcrumb-item>
|
</el-breadcrumb-item>
|
||||||
<el-breadcrumb-item class="text-md opactiy-50">{{
|
<el-breadcrumb-item class="text-md opactiy-50">{{
|
||||||
production.title
|
production.name
|
||||||
}}</el-breadcrumb-item>
|
}}</el-breadcrumb-item>
|
||||||
</el-breadcrumb>
|
</el-breadcrumb>
|
||||||
|
|
||||||
@ -23,9 +23,9 @@
|
|||||||
<div class="production-header">
|
<div class="production-header">
|
||||||
<div class="production-image">
|
<div class="production-image">
|
||||||
<el-image
|
<el-image
|
||||||
v-if="production.production_images.length <= 1"
|
v-if="production.images.length <= 1"
|
||||||
:src="useStrapiMedia(production?.cover?.url || '')"
|
:src="getImageUrl(production.images[0].image)"
|
||||||
:alt="production.title"
|
:alt="production.name"
|
||||||
fit="contain"
|
fit="contain"
|
||||||
/>
|
/>
|
||||||
<el-carousel
|
<el-carousel
|
||||||
@ -37,13 +37,13 @@
|
|||||||
arrow="always"
|
arrow="always"
|
||||||
>
|
>
|
||||||
<el-carousel-item
|
<el-carousel-item
|
||||||
v-for="(item, index) in production.production_images || []"
|
v-for="(item, index) in production.images || []"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
<div class="production-carousel-item">
|
<div class="production-carousel-item">
|
||||||
<el-image
|
<el-image
|
||||||
:src="useStrapiMedia(item.url || '')"
|
:src="getImageUrl(item.image || '')"
|
||||||
:alt="item.alternativeText || production.title"
|
:alt="production.name"
|
||||||
fit="contain"
|
fit="contain"
|
||||||
lazy
|
lazy
|
||||||
/>
|
/>
|
||||||
@ -56,7 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="production-info">
|
<div class="production-info">
|
||||||
<h1>{{ production.title }}</h1>
|
<h1>{{ production.name }}</h1>
|
||||||
<p class="summary">{{ production.summary }}</p>
|
<p class="summary">{{ production.summary }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -65,24 +65,16 @@
|
|||||||
<div class="production-content">
|
<div class="production-content">
|
||||||
<el-tabs v-model="activeName" class="production-tabs" stretch>
|
<el-tabs v-model="activeName" class="production-tabs" stretch>
|
||||||
<el-tab-pane label="产品详情" name="details">
|
<el-tab-pane label="产品详情" name="details">
|
||||||
<markdown-renderer
|
<markdown-renderer :content="production.description || ''" />
|
||||||
:content="production.production_details || ''"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="技术规格" name="specs">
|
<el-tab-pane label="技术规格" name="specs">
|
||||||
<spec-table :data="production.production_specs" />
|
<spec-table :data="production.specs" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="常见问题" name="faq">
|
<el-tab-pane label="常见问题" name="faq">
|
||||||
<question-list :questions="production.questions" />
|
<question-list :questions="production.faqs" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="相关文档" name="documents">
|
<el-tab-pane label="相关文档" name="documents">
|
||||||
<document-list
|
<document-list :documents="production.documents" />
|
||||||
:documents="
|
|
||||||
production.production_documents.map(
|
|
||||||
(item) => item.document
|
|
||||||
) || []
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
@ -114,40 +106,130 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { readItem } from '@directus/sdk';
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { findOne } = useStrapi();
|
|
||||||
const { getStrapiLocale } = useLocalizations();
|
const { getStrapiLocale } = useLocalizations();
|
||||||
const strapiLocale = getStrapiLocale();
|
const strapiLocale = getStrapiLocale();
|
||||||
|
|
||||||
|
const { $directus } = useNuxtApp();
|
||||||
|
const { getImageUrl } = useDirectusImage();
|
||||||
|
|
||||||
// 获取路由参数(slug 或 id)
|
// 获取路由参数(slug 或 id)
|
||||||
const documentId = computed(() => route.params.slug as string);
|
const documentId = computed(() => route.params.slug as string);
|
||||||
|
|
||||||
const { data, pending, error } = useAsyncData(
|
const { data, pending, error } = await useAsyncData(
|
||||||
() => `production-${documentId.value}`,
|
() => `production-${documentId.value}`,
|
||||||
() =>
|
() =>
|
||||||
findOne<Production>('productions', documentId.value, {
|
$directus.request(
|
||||||
populate: {
|
readItem('products', documentId.value, {
|
||||||
production_specs: {
|
fields: [
|
||||||
populate: '*',
|
'id',
|
||||||
|
{ translations: ['id', 'name', 'summary', 'description'] },
|
||||||
|
{
|
||||||
|
images: [
|
||||||
|
'id',
|
||||||
|
{
|
||||||
|
product_images_id: [
|
||||||
|
'id',
|
||||||
|
'image',
|
||||||
|
{ translations: ['id', 'caption'] },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
production_images: {
|
],
|
||||||
populate: '*',
|
},
|
||||||
|
{
|
||||||
|
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: strapiLocale },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
product_images_id: {
|
||||||
|
translations: {
|
||||||
|
_filter: {
|
||||||
|
languages_code: { _eq: strapiLocale },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
faqs: {
|
||||||
|
questions_id: {
|
||||||
|
translations: {
|
||||||
|
_filter: {
|
||||||
|
languages_code: { _eq: strapiLocale },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
documents: {
|
||||||
|
documents_id: {
|
||||||
|
translations: {
|
||||||
|
_filter: {
|
||||||
|
languages_code: { _eq: strapiLocale },
|
||||||
},
|
},
|
||||||
cover: {
|
|
||||||
populate: '*',
|
|
||||||
},
|
},
|
||||||
questions: {
|
|
||||||
populate: '*',
|
|
||||||
},
|
},
|
||||||
production_documents: {
|
|
||||||
populate: 'document',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
locale: strapiLocale,
|
|
||||||
})
|
})
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const production = computed(() => data.value?.data ?? null);
|
console.log('Raw Data: ', data.value);
|
||||||
|
|
||||||
|
const rawProduction = computed(() => data.value ?? null);
|
||||||
|
const production = computed(() => {
|
||||||
|
return toProductView(rawProduction.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('View Data: ', production.value);
|
||||||
|
|
||||||
const activeName = ref('details'); // 默认选中概览标签
|
const activeName = ref('details'); // 默认选中概览标签
|
||||||
|
|
||||||
@ -159,7 +241,7 @@
|
|||||||
|
|
||||||
// SEO
|
// SEO
|
||||||
useHead({
|
useHead({
|
||||||
title: computed(() => production.value?.title || 'Product Detail'),
|
title: computed(() => production.value?.name || 'Product Detail'),
|
||||||
meta: [
|
meta: [
|
||||||
{
|
{
|
||||||
name: 'description',
|
name: 'description',
|
||||||
|
|||||||
@ -27,10 +27,10 @@
|
|||||||
<div class="group-list">
|
<div class="group-list">
|
||||||
<production-card
|
<production-card
|
||||||
v-for="production in group"
|
v-for="production in group"
|
||||||
:key="production.documentId || production.id"
|
:key="production.id"
|
||||||
:slug="production.documentId"
|
:slug="production.id.toString()"
|
||||||
:image-url="useStrapiMedia(production?.cover?.url || '')"
|
:image-url="getImageUrl(production.cover.toString())"
|
||||||
:name="production.title"
|
:name="production.name"
|
||||||
:description="production.summary || ''"
|
:description="production.summary || ''"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -45,29 +45,47 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { find } = useStrapi();
|
import { readItems } from '@directus/sdk';
|
||||||
const { getStrapiLocale } = useLocalizations();
|
const { getStrapiLocale } = useLocalizations();
|
||||||
const strapiLocale = getStrapiLocale();
|
const strapiLocale = getStrapiLocale();
|
||||||
|
|
||||||
|
const { $directus } = useNuxtApp();
|
||||||
|
const { getImageUrl } = useDirectusImage();
|
||||||
|
|
||||||
const { data, pending, error } = useAsyncData(
|
const { data, pending, error } = useAsyncData(
|
||||||
'productions',
|
'products',
|
||||||
() =>
|
() =>
|
||||||
find<Production>('productions', {
|
$directus.request(
|
||||||
populate: {
|
readItems('products', {
|
||||||
cover: {
|
fields: [
|
||||||
populate: '*',
|
'id',
|
||||||
|
{ translations: ['id', 'name', 'summary'] },
|
||||||
|
'cover',
|
||||||
|
{
|
||||||
|
product_type: [
|
||||||
|
'id',
|
||||||
|
{
|
||||||
|
translations: ['id', 'name'],
|
||||||
},
|
},
|
||||||
production_type: {
|
],
|
||||||
populate: '*',
|
},
|
||||||
|
],
|
||||||
|
deep: {
|
||||||
|
translations: {
|
||||||
|
_filter: {
|
||||||
|
languages_code: { _eq: strapiLocale },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
filters: {
|
product_type: {
|
||||||
show_in_production_list: {
|
translations: {
|
||||||
$eq: true,
|
_filter: {
|
||||||
|
languages_code: { _eq: strapiLocale },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
locale: strapiLocale,
|
},
|
||||||
}),
|
},
|
||||||
|
})
|
||||||
|
),
|
||||||
{
|
{
|
||||||
lazy: true,
|
lazy: true,
|
||||||
}
|
}
|
||||||
@ -75,26 +93,31 @@
|
|||||||
|
|
||||||
const activeNames = ref<string[]>([]);
|
const activeNames = ref<string[]>([]);
|
||||||
|
|
||||||
const productions = computed(() => data.value?.data ?? []);
|
// const productions = computed(() => data.value?.data ?? []);
|
||||||
|
const productionsRaw = computed(() => data.value ?? []);
|
||||||
|
const productions = computed(() =>
|
||||||
|
productionsRaw.value.map((item) => toProductListView(item))
|
||||||
|
);
|
||||||
|
|
||||||
// 按类型分组
|
// 按类型分组
|
||||||
// 兼容 production_type 既可能为对象也可能为字符串
|
// 兼容 production_type 既可能为对象也可能为字符串
|
||||||
const groupedProductions = computed(() => {
|
const groupedProductions = computed(() => {
|
||||||
const groups: Record<string, Production[]> = {};
|
const groups: Record<string, ProductListView[]> = {};
|
||||||
for (const prod of productions.value) {
|
for (const prod of productions.value) {
|
||||||
let typeKey = '';
|
let typeKey = '';
|
||||||
if (typeof prod.production_type === 'string') {
|
if (typeof prod.product_type === 'string') {
|
||||||
typeKey = prod.production_type;
|
typeKey = prod.product_type;
|
||||||
} else if (
|
} else if (
|
||||||
prod.production_type &&
|
prod.product_type &&
|
||||||
typeof prod.production_type === 'object' &&
|
typeof prod.product_type === 'object' &&
|
||||||
'type' in prod.production_type
|
'name' in prod.product_type
|
||||||
) {
|
) {
|
||||||
typeKey = prod.production_type.type || '';
|
typeKey = prod.product_type || '';
|
||||||
}
|
}
|
||||||
if (!groups[typeKey]) groups[typeKey] = [];
|
if (!groups[typeKey]) groups[typeKey] = [];
|
||||||
groups[typeKey]?.push(prod);
|
groups[typeKey]?.push(prod);
|
||||||
}
|
}
|
||||||
|
console.log(groups);
|
||||||
return groups;
|
return groups;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -113,7 +136,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
watch(pending, (value) => {
|
||||||
|
if (!value) {
|
||||||
|
console.log('AsyncData: ', data.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
if (groupedProductions.value) {
|
if (groupedProductions.value) {
|
||||||
activeNames.value = [
|
activeNames.value = [
|
||||||
...Object.keys(groupedProductions.value),
|
...Object.keys(groupedProductions.value),
|
||||||
|
|||||||
12
app/plugins/directus.ts
Normal file
12
app/plugins/directus.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { createDirectus, rest, staticToken } from '@directus/sdk';
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(() => {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
|
||||||
|
const directus = createDirectus<Schema>(config.public.directus.url)
|
||||||
|
.with(rest())
|
||||||
|
.with(staticToken(config.public.directus.token || ''));
|
||||||
|
return {
|
||||||
|
provide: { directus },
|
||||||
|
};
|
||||||
|
});
|
||||||
@ -1,5 +1,3 @@
|
|||||||
import type { JsonValue } from '../common';
|
|
||||||
|
|
||||||
export interface CompanyProfile {
|
export interface CompanyProfile {
|
||||||
/** @primaryKey */
|
/** @primaryKey */
|
||||||
id: number;
|
id: number;
|
||||||
@ -28,20 +26,9 @@ export interface ContactInfoTranslation {
|
|||||||
content?: 'json' | null;
|
content?: 'json' | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Document {
|
|
||||||
/** @primaryKey */
|
|
||||||
id: number;
|
|
||||||
status?: 'published' | 'draft' | 'archived';
|
|
||||||
file?: DirectusFile | string | null;
|
|
||||||
/** @description i18n字段 */
|
|
||||||
translations?: DocumentsTranslation[] | null;
|
|
||||||
products?: ProductsDocument[] | string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DocumentsTranslation {
|
export interface DocumentsTranslation {
|
||||||
/** @primaryKey */
|
/** @primaryKey */
|
||||||
id: number;
|
id: number;
|
||||||
documents_id?: Document | string | null;
|
|
||||||
languages_code?: Language | string | null;
|
languages_code?: Language | string | null;
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
}
|
}
|
||||||
@ -68,6 +55,22 @@ export interface Language {
|
|||||||
direction?: 'ltr' | 'rtl' | null;
|
direction?: 'ltr' | 'rtl' | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductDocument {
|
||||||
|
/** @primaryKey */
|
||||||
|
id: number;
|
||||||
|
status?: 'published' | 'draft' | 'archived';
|
||||||
|
file?: DirectusFile | string | null;
|
||||||
|
translations?: ProductDocumentsTranslation[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductDocumentsTranslation {
|
||||||
|
/** @primaryKey */
|
||||||
|
id: number;
|
||||||
|
product_documents_id?: ProductDocument | string | null;
|
||||||
|
languages_code?: Language | string | null;
|
||||||
|
title?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductImage {
|
export interface ProductImage {
|
||||||
/** @primaryKey */
|
/** @primaryKey */
|
||||||
id: number;
|
id: number;
|
||||||
@ -146,21 +149,20 @@ export interface Product {
|
|||||||
cover?: DirectusFile | string | null;
|
cover?: DirectusFile | string | null;
|
||||||
homepage_recommend?: Homepage | string | null;
|
homepage_recommend?: Homepage | string | null;
|
||||||
recommend_sort?: number | null;
|
recommend_sort?: number | null;
|
||||||
faqs?: ProductsQuestion[] | string[];
|
|
||||||
/** @description i18n字段 */
|
/** @description i18n字段 */
|
||||||
translations?: ProductsTranslation[] | null;
|
translations?: ProductsTranslation[] | null;
|
||||||
|
faqs?: ProductsQuestion[] | string[];
|
||||||
/** @description 在产品详情页中展示 */
|
/** @description 在产品详情页中展示 */
|
||||||
images?: ProductsProductImage[] | string[];
|
images?: ProductsProductImage[] | string[];
|
||||||
documents?: ProductsDocument[] | string[];
|
|
||||||
specs?: ProductSpecGroup[] | string[];
|
specs?: ProductSpecGroup[] | string[];
|
||||||
|
documents?: ProductsProductDocument[] | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductsDocument {
|
export interface ProductsProductDocument {
|
||||||
/** @primaryKey */
|
/** @primaryKey */
|
||||||
id: number;
|
id: number;
|
||||||
products_id?: Product | string | null;
|
products_id?: Product | string | null;
|
||||||
documents_id?: Document | string | null;
|
product_documents_id?: ProductDocument | string | null;
|
||||||
sort?: number | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductsProductImage {
|
export interface ProductsProductImage {
|
||||||
@ -187,7 +189,7 @@ export interface ProductsTranslation {
|
|||||||
/** @required */
|
/** @required */
|
||||||
name: string;
|
name: string;
|
||||||
summary?: string | null;
|
summary?: string | null;
|
||||||
description?: JsonValue | null;
|
description?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Question {
|
export interface Question {
|
||||||
@ -206,7 +208,7 @@ export interface QuestionsTranslation {
|
|||||||
languages_code?: Language | string | null;
|
languages_code?: Language | string | null;
|
||||||
/** @required */
|
/** @required */
|
||||||
title: string;
|
title: string;
|
||||||
content?: 'json' | null;
|
content?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SolutionType {
|
export interface SolutionType {
|
||||||
@ -723,11 +725,12 @@ export interface Schema {
|
|||||||
company_profile_translations: CompanyProfileTranslation[];
|
company_profile_translations: CompanyProfileTranslation[];
|
||||||
contact_info: ContactInfo;
|
contact_info: ContactInfo;
|
||||||
contact_info_translations: ContactInfoTranslation[];
|
contact_info_translations: ContactInfoTranslation[];
|
||||||
documents: Document[];
|
|
||||||
documents_translations: DocumentsTranslation[];
|
documents_translations: DocumentsTranslation[];
|
||||||
homepage: Homepage;
|
homepage: Homepage;
|
||||||
homepage_files: HomepageFile[];
|
homepage_files: HomepageFile[];
|
||||||
languages: Language[];
|
languages: Language[];
|
||||||
|
product_documents: ProductDocument[];
|
||||||
|
product_documents_translations: ProductDocumentsTranslation[];
|
||||||
product_images: ProductImage[];
|
product_images: ProductImage[];
|
||||||
product_images_translations: ProductImagesTranslation[];
|
product_images_translations: ProductImagesTranslation[];
|
||||||
product_spec_groups: ProductSpecGroup[];
|
product_spec_groups: ProductSpecGroup[];
|
||||||
@ -737,7 +740,7 @@ export interface Schema {
|
|||||||
product_types: ProductType[];
|
product_types: ProductType[];
|
||||||
product_types_translations: ProductTypesTranslation[];
|
product_types_translations: ProductTypesTranslation[];
|
||||||
products: Product[];
|
products: Product[];
|
||||||
products_documents: ProductsDocument[];
|
products_product_documents: ProductsProductDocument[];
|
||||||
products_product_images: ProductsProductImage[];
|
products_product_images: ProductsProductImage[];
|
||||||
products_questions: ProductsQuestion[];
|
products_questions: ProductsQuestion[];
|
||||||
products_translations: ProductsTranslation[];
|
products_translations: ProductsTranslation[];
|
||||||
@ -781,11 +784,12 @@ export enum CollectionNames {
|
|||||||
company_profile_translations = 'company_profile_translations',
|
company_profile_translations = 'company_profile_translations',
|
||||||
contact_info = 'contact_info',
|
contact_info = 'contact_info',
|
||||||
contact_info_translations = 'contact_info_translations',
|
contact_info_translations = 'contact_info_translations',
|
||||||
documents = 'documents',
|
|
||||||
documents_translations = 'documents_translations',
|
documents_translations = 'documents_translations',
|
||||||
homepage = 'homepage',
|
homepage = 'homepage',
|
||||||
homepage_files = 'homepage_files',
|
homepage_files = 'homepage_files',
|
||||||
languages = 'languages',
|
languages = 'languages',
|
||||||
|
product_documents = 'product_documents',
|
||||||
|
product_documents_translations = 'product_documents_translations',
|
||||||
product_images = 'product_images',
|
product_images = 'product_images',
|
||||||
product_images_translations = 'product_images_translations',
|
product_images_translations = 'product_images_translations',
|
||||||
product_spec_groups = 'product_spec_groups',
|
product_spec_groups = 'product_spec_groups',
|
||||||
@ -795,7 +799,7 @@ export enum CollectionNames {
|
|||||||
product_types = 'product_types',
|
product_types = 'product_types',
|
||||||
product_types_translations = 'product_types_translations',
|
product_types_translations = 'product_types_translations',
|
||||||
products = 'products',
|
products = 'products',
|
||||||
products_documents = 'products_documents',
|
products_product_documents = 'products_product_documents',
|
||||||
products_product_images = 'products_product_images',
|
products_product_images = 'products_product_images',
|
||||||
products_questions = 'products_questions',
|
products_questions = 'products_questions',
|
||||||
products_translations = 'products_translations',
|
products_translations = 'products_translations',
|
||||||
|
|||||||
@ -1,12 +1,19 @@
|
|||||||
export function formatFileSize(sizeInKB: number): string {
|
export function formatFileSize(sizeInBytes: number): string {
|
||||||
if (sizeInKB < 1024) {
|
if (sizeInBytes < 1024) {
|
||||||
|
return `${sizeInBytes.toFixed(2)} B`;
|
||||||
|
} else if (sizeInBytes < 1024 * 1024) {
|
||||||
|
const sizeInKB = sizeInBytes / 1024;
|
||||||
return `${sizeInKB.toFixed(2)} KB`;
|
return `${sizeInKB.toFixed(2)} KB`;
|
||||||
} else {
|
} else {
|
||||||
const sizeInMB = sizeInKB / 1024;
|
const sizeInMB = sizeInBytes / 1024 / 1024;
|
||||||
return `${sizeInMB.toFixed(2)} MB`;
|
return `${sizeInMB.toFixed(2)} MB`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFileExtension(filename: string): string {
|
||||||
|
return filename.split('.').pop() || '';
|
||||||
|
}
|
||||||
|
|
||||||
export function formatFileExtension(ext: string): string {
|
export function formatFileExtension(ext: string): string {
|
||||||
return ext.startsWith('.') ? ext.slice(1).toUpperCase() : ext.toUpperCase();
|
return ext.startsWith('.') ? ext.slice(1).toUpperCase() : ext.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,7 +118,7 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
imports: {
|
imports: {
|
||||||
dirs: ['types/**'],
|
dirs: ['types/**', 'models/**'],
|
||||||
},
|
},
|
||||||
|
|
||||||
modules: [
|
modules: [
|
||||||
@ -133,6 +133,5 @@ export default defineNuxtConfig({
|
|||||||
'@element-plus/nuxt',
|
'@element-plus/nuxt',
|
||||||
'@nuxtjs/i18n',
|
'@nuxtjs/i18n',
|
||||||
'@nuxtjs/strapi',
|
'@nuxtjs/strapi',
|
||||||
'nuxt-directus',
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user