Compare commits

...

5 Commits

Author SHA1 Message Date
cb861bc955 fix: 修改file工具函数
All checks were successful
deploy to server / build-and-deploy (push) Successful in 6m20s
- 新增基于filename获取拓展名的函数
- 将文件大小格式化由以KB为基准修改为以Byte为基准
2025-10-15 16:50:27 +08:00
1704a7b5c1 feat: production页的CMS变更 2025-10-15 16:49:08 +08:00
98f978484c feat: directus视图与转换函数
- views: 用于前端渲染的视图模型
- mapper: 用于视图模型转换的转换函数
- utils: 相关工具函数
2025-10-15 16:48:38 +08:00
de7c03a7a9 feat: directus插件与组合式函数编写
- plugins:Directus插件
- composable:Directus图片/文件的相关组合式函数
2025-10-15 16:47:33 +08:00
e158ec8cf5 fix: directus类型标注更新
- 使用Directus-sdk-typegen生成Directus类型标注
2025-10-15 16:45:50 +08:00
20 changed files with 639 additions and 123 deletions

View File

@ -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: () => [],
}, },
}); });

View File

@ -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: () => [],
}, },
}); });

View File

@ -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>

View 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,
};
};

View 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 };
};

View 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,
};
}

View 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,
};
}

View 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,
};
}

View 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>);
};

View File

@ -0,0 +1,20 @@
/**
* 文档视图模型
* 用于文档页(/support/documents)渲染的数据结构
*/
export interface ProductDocumentView {
/** 唯一标识符 **/
id: number;
/** 文件名 **/
filename: string;
/** 文档标题 **/
title: string;
/** 文档大小 **/
size: number;
/** 文档链接 **/
url: string;
}

View File

@ -0,0 +1,20 @@
/**
* 产品列表视图模型
* 用于产品列表(/products)渲染的数据结构
*/
export interface ProductListView {
/** 唯一标识符 **/
id: number;
/** 产品名称 **/
name: string;
/** 产品简介 **/
summary: string;
/** 产品类型 **/
product_type: string;
/** 产品封面(图片的id) **/
cover: string;
}

View File

@ -0,0 +1,29 @@
/**
* 产品规格模型
* 用于产品规格渲染的数据结构
*/
export interface ProductSpecView {
/** 唯一标识符 **/
id: number;
/** 规格名称 **/
key: string;
/** 规格值 **/
value: string;
}
/**
* 产品规格表模型
* 用于产品规格表渲染的数据结构
*/
export interface ProductSpecGroupView {
/** 唯一标识符 **/
id: number;
/** 规格组名称 **/
name: string;
/** 规格组 **/
specs: ProductSpecView[];
}

View 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[];
}

View File

@ -0,0 +1,14 @@
/**
* 常见问题视图模型
* 用于常见问题(/support/faqs)渲染的数据结构
*/
export interface QuestionView {
/** 唯一标识符 **/
id: number;
/** 问题标题 **/
title: string;
/** 问题内容 **/
content: string;
}

View File

@ -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'] },
],
},
],
},
{
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 },
},
},
},
},
}, },
production_images: { })
populate: '*', )
},
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',

View File

@ -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'],
},
],
},
],
deep: {
translations: {
_filter: {
languages_code: { _eq: strapiLocale },
},
},
product_type: {
translations: {
_filter: {
languages_code: { _eq: strapiLocale },
},
},
},
}, },
production_type: { })
populate: '*', ),
},
},
filters: {
show_in_production_list: {
$eq: true,
},
},
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
View 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 },
};
});

View File

@ -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',

View File

@ -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();
} }

View File

@ -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',
], ],
}); });