feat(document): 为文档添加下载与预览界面:
All checks were successful
deploy to server / build-and-deploy (push) Successful in 2m58s

- 添加路由: download路由用于下载文件, preview路由用于文件预览
- 添加组件: FilePreviewer封装了若干格式文件的预览功能(目前支持pdf,
  image, text, video等)
- 服务端API: 添加download和file分别用于处理文件下载请求与元数据获取请求
- 类型标注调整:
  将原先位于app/types内的类型标注文件移至shared/types内,让app与server共享类型标注
- 国际化文本添加: 为相关页面添加国际化文本
This commit is contained in:
2025-10-28 14:41:54 +08:00
21 changed files with 468 additions and 18 deletions

View File

@ -4,6 +4,7 @@
v-for="(doc, index) in documents"
:key="index"
class="document-card"
@click="handleClick(doc.fileId)"
>
<div class="document-info">
<h3>{{ doc.title }}</h3>
@ -15,13 +16,6 @@
>格式:
{{ formatFileExtension(getFileExtension(doc.filename)) }}</span
>
<el-button
class="download-button"
type="primary"
@click="handleDownload(doc.title, doc.url)"
>
下载
</el-button>
</div>
</div>
</el-card>
@ -36,18 +30,13 @@
},
});
const handleDownload = async (fileName: string, fileUrl: string) => {
const response = await fetch(fileUrl);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const localePath = useLocalePath();
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
const handleClick = (id: string) => {
// 获取路由参数
if (id) {
navigateTo(localePath(`/download/${id}`));
}
};
</script>
@ -59,6 +48,16 @@
width: 100%;
}
.document-card {
cursor: pointer;
transition: all 0.3s ease;
}
.document-card:hover {
transform: translateY(-1px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.document-meta {
font-size: 0.8rem;
color: var(--el-text-color-secondary);

View File

@ -0,0 +1,177 @@
<template>
<section class="h-screen flex flex-col">
<!-- 头部工具栏 -->
<header
v-if="showToolbar && fileMeta"
class="p-3 border-b flex items-center justify-between"
>
<div class="min-w-0">
<h2 class="truncate font-medium" :title="fileMeta.filename_download">
{{ fileMeta.filename_download }}
</h2>
<p class="text-xs text-gray-500">
{{ fileMeta.type }} · {{ formatedSize }} · {{ formatedDate }}
</p>
</div>
<div class="shrink-0 flex items-center gap-2">
<button
class="px-3 py-1.5 rounded border hover:bg-gray-50"
type="button"
:disabled="!fileMeta"
@click="openInNewTab"
>
在新标签打开
</button>
<button
class="px-3 py-1.5 rounded bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50"
type="button"
:disabled="!fileMeta"
@click="download"
>
下载
</button>
</div>
</header>
<div class="flex-1 overflow-hidden">
<!-- 加载状态 -->
<div v-if="pending" class="h-48 grid place-items-center border rounded">
正在加载...
</div>
<div
v-else-if="errorText"
class="h-48 grid place-items-center border rounded text-red-600"
>
{{ errorText }}
</div>
<!-- 文件预览 -->
<ClientOnly v-else>
<div
v-if="fileMeta && previewable"
class="h-full w-full flex justify-center bg-gray-50"
>
<!-- 图片 -->
<el-image
v-if="isImage"
fit="contain"
class="max-w-full max-h-full select-none"
:src="src"
:alt="fileMeta.title || fileMeta.filename_download"
/>
<!-- PDF -->
<iframe
v-else-if="isPdf"
:src="src"
title="PDF 预览"
class="w-full h-full border-0"
/>
<!-- 视频 -->
<video
v-else-if="isVideo"
:src="src"
controls
class="w-full bg-black"
/>
<!-- 文本简单方式用 iframe如需代码高亮可改为拉取文本并渲染 <pre> -->
<iframe
v-else-if="isText"
:src="src"
title="文本预览"
class="w-full h-full border-0"
/>
</div>
</ClientOnly>
</div>
</section>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
/** 预览的文件 ID */
fileId?: string;
file?: FileMeta;
/** 是否显示上方工具栏(文件名、大小、按钮) */
showToolbar?: boolean;
/** 下载 API 基础路径(你的后端流接口),用于“下载”按钮 */
downloadApiBase?: string;
/** 追加到 file.url 的查询(如临时 token形如 { token: 'xxx' } */
extraQuery?: Record<string, string | number | boolean>;
}>(),
{
fileId: undefined,
file: undefined,
showToolbar: true,
downloadApiBase: '/api/download',
extraQuery: undefined,
}
);
const { data, pending, error } = await useFetch<FileMeta>(
() => (props.file ? null : `/api/file/${props.fileId}`),
{ server: true }
);
const errorText = computed(() => error.value?.message ?? null);
const fileMeta = computed(() => props.file ?? data.value ?? null);
/** 预览源地址:支持在 file.url 上追加额外 query如临时 token、inline */
const src = computed<string>(() => {
if (!fileMeta.value) return '';
const url = new URL(fileMeta.value.url, window?.location?.origin);
if (props.extraQuery) {
Object.entries(props.extraQuery).forEach(([k, v]) =>
url.searchParams.set(k, String(v))
);
}
return url.toString();
});
/** 类型判定 */
const isImage = computed(
() => fileMeta.value?.type.startsWith('image/') === true
);
const isPdf = computed(() => fileMeta.value?.type === 'application/pdf');
const isVideo = computed(
() => fileMeta.value?.type.startsWith('video/') === true
);
const isText = computed(
() => fileMeta.value?.type.startsWith('text/') === true
);
const previewable = computed(() => fileMeta.value?.previewable === true);
const formatedSize = computed(() => {
const size = fileMeta.value?.filesize ?? 0;
return formatFileSize(size);
});
const formatedDate = computed(() => {
if (!fileMeta.value?.uploaded_on) return '';
return new Date(fileMeta.value.uploaded_on).toLocaleDateString();
});
/** 下载动作:走你自己的流式后端,避免直链暴露(便于权限与统计) */
function download(): void {
if (!fileMeta.value) return;
const id = fileMeta.value.id;
const a = document.createElement('a');
a.href = `${props.downloadApiBase}/${encodeURIComponent(id)}`;
a.download = fileMeta.value.filename_download;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
/** 新标签打开(直接访问直链,适合预览失败时的兜底体验) */
function openInNewTab(): void {
if (!src.value) return;
window.open(src.value, '_blank', 'noopener,noreferrer');
}
</script>

5
app/layouts/preview.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<div>
<slot />
</div>
</template>

View File

@ -23,6 +23,7 @@ export function toProductDocumentView(
return {
id: raw.id,
fileId: fileId,
filename: file.filename_download,
title: trans.title,
url: url,
@ -68,6 +69,7 @@ export function toDocumentListView(raw: ProductDocument): DocumentListView {
return {
id: raw.id,
fileId: fileId,
filename: file.filename_download,
title: trans.title,
url: url,

View File

@ -25,6 +25,9 @@ export interface DocumentListView {
/** 唯一标识符 **/
id: number;
/** 文件UUID **/
fileId: string;
/** 文件名 **/
filename: string;

View File

@ -6,6 +6,9 @@ export interface ProductDocumentView {
/** 唯一标识符 **/
id: number;
/** 文件UUID **/
fileId: string;
/** 文件名 **/
filename: string;

133
app/pages/download/[id].vue Normal file
View File

@ -0,0 +1,133 @@
<template>
<div class="page-container">
<div class="page-header">
<h1 class="page-title">文件下载</h1>
<el-breadcrumb class="breadcrumb">
<el-breadcrumb-item class="text-md opacity-50">
<NuxtLink :to="$localePath('/')">{{
$t('navigation.home')
}}</NuxtLink>
</el-breadcrumb-item>
<el-breadcrumb-item class="text-md opacity-50">
<NuxtLink :to="$localePath('/products')">{{
$t('navigation.downloads')
}}</NuxtLink>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div v-if="!pending" class="page-content">
<el-card shadow="hover" class="p-4">
<template #header>
<div class="header-content">
<el-icon class="header-icon"><ElIconDocument /></el-icon>
<span class="truncate font-medium">{{
file.filename_download
}}</span>
</div>
</template>
<dl class="text-gray-600 space-y-1 mb-6">
<div>
<dt class="font-semibold inline">类型</dt>
<dd class="inline">{{ file.type }}</dd>
</div>
<div>
<dt class="font-semibold inline">大小</dt>
<dd class="inline">{{ formatFileSize(file.filesize) }}</dd>
</div>
<div>
<dt class="font-semibold inline">上传时间</dt>
<dd class="inline">
{{ new Date(file.uploaded_on).toLocaleDateString() }}
</dd>
</div>
</dl>
<template #footer>
<div class="button-group">
<el-button type="primary" @click="handleDownload">下载</el-button>
<el-button v-if="file.previewable" @click="handlePreview"
>预览</el-button
>
</div>
</template>
</el-card>
</div>
<div v-else>
<el-skeleton :rows="6" animated />
</div>
</div>
</template>
<script setup lang="ts">
const route = useRoute();
const router = useRouter();
// 获取路由参数
const id = computed(() => route.params.id as string);
const {
data: file,
pending,
error,
} = await useFetch<FileMeta>(`/api/file/${id.value}`);
if (error.value || !file.value) {
throw createError({
statusCode: 404,
statusMessage: '文件未找到',
});
}
function handleDownload() {
const link = document.createElement('a');
link.href = `/api/download/${id.value}`;
link.download = file.value?.filename_download ?? 'download';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function handlePreview() {
router.push(`/preview/${id.value}`);
}
</script>
<style scoped>
.page-container {
padding: 2rem;
margin: 0 auto;
max-width: 1200px;
}
.page-header {
display: flex;
}
.page-title {
font-size: 2rem;
font-weight: bold;
color: var(--el-color-primary);
margin-bottom: 1rem;
}
.breadcrumb {
margin-left: auto;
}
.header-content {
display: flex;
align-items: center;
gap: 3px;
margin-bottom: 4px;
}
.header-icon {
font-size: 1.5rem;
margin-right: 0.5rem;
}
.button-group {
display: flex;
justify-content: flex-end;
align-items: baseline;
gap: 1rem;
}
</style>

View File

@ -0,0 +1,13 @@
<template>
<file-previewer :file-id="id" />
</template>
<script setup lang="ts">
definePageMeta({
layout: 'preview',
});
const route = useRoute();
const id = computed(() => route.params.id as string);
</script>

View File

@ -1,7 +0,0 @@
export type JsonPrimitive = string | number | boolean | null;
export type JsonArray = JsonValue[];
export type JsonObject = { [key: string]: JsonValue };
export type JsonValue = JsonPrimitive | JsonArray | JsonObject;

View File

@ -1 +0,0 @@
export * from './my-schema';

View File

@ -1,842 +0,0 @@
export interface CompanyProfile {
/** @primaryKey */
id: number;
translations?: CompanyProfileTranslation[] | null;
}
export interface CompanyProfileTranslation {
/** @primaryKey */
id: number;
company_profile_id?: CompanyProfile | string | null;
languages_code?: Language | string | null;
content?: string | null;
}
export interface ContactInfo {
/** @primaryKey */
id: number;
translations?: ContactInfoTranslation[] | null;
}
export interface ContactInfoTranslation {
/** @primaryKey */
id: number;
contact_info_id?: ContactInfo | string | null;
languages_code?: Language | string | null;
content?: string | null;
}
export interface DocumentsTranslation {
/** @primaryKey */
id: number;
languages_code?: Language | string | null;
title?: string | null;
}
export interface Homepage {
/** @primaryKey */
id: number;
carousel?: HomepageFile[] | string[];
recommend_products?: Product[] | string[];
recommend_solutions?: Solution[] | string[];
}
export interface HomepageFile {
/** @primaryKey */
id: number;
homepage_id?: Homepage | string | null;
directus_files_id?: DirectusFile | string | null;
}
export interface Language {
/** @primaryKey */
code: string;
name?: string | null;
direction?: 'ltr' | 'rtl' | null;
}
export interface ProductDocument {
/** @primaryKey */
id: number;
status?: 'published' | 'draft' | 'archived';
file?: DirectusFile | string | null;
products?: ProductsProductDocument[] | string[];
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 {
/** @primaryKey */
id: number;
image?: DirectusFile | string | null;
translations?: ProductImagesTranslation[] | null;
products?: ProductsProductImage[] | string[];
}
export interface ProductImagesTranslation {
/** @primaryKey */
id: number;
product_images_id?: ProductImage | string | null;
languages_code?: Language | string | null;
caption?: string | null;
}
export interface ProductSpecGroup {
/** @primaryKey */
id: number;
product?: Product | string | null;
sort?: number | null;
translations?: ProductSpecGroupsTranslation[] | null;
specs?: ProductSpec[] | string[];
}
export interface ProductSpecGroupsTranslation {
/** @primaryKey */
id: number;
product_spec_groups_id?: ProductSpecGroup | string | null;
languages_code?: Language | string | null;
name?: string | null;
}
export interface ProductSpec {
/** @primaryKey */
id: number;
group?: ProductSpecGroup | string | null;
sort?: number | null;
value?: string | null;
translations?: ProductSpecsTranslation[] | null;
}
export interface ProductSpecsTranslation {
/** @primaryKey */
id: number;
product_specs_id?: ProductSpec | string | null;
languages_code?: Language | string | null;
key?: string | null;
}
export interface ProductType {
/** @primaryKey */
id: number;
/** @description 当前产品条目的状态 */
status?: 'published' | 'draft' | 'archived';
/** @description i18n文本 */
translations?: ProductTypesTranslation[] | null;
}
export interface ProductTypesTranslation {
/** @primaryKey */
id: number;
product_types_id?: ProductType | string | null;
languages_code?: Language | string | null;
/** @required */
name: string;
}
export interface Product {
/** @primaryKey */
id: number;
/** @description 当前产品条目的状态 */
status?: 'published' | 'draft' | 'archived';
product_type?: ProductType | string | null;
/** @description 在产品列表中显示 */
cover?: DirectusFile | string | null;
homepage_recommend?: Homepage | string | null;
recommend_sort?: number | null;
/** @description i18n字段 */
translations?: ProductsTranslation[] | null;
faqs?: ProductsQuestion[] | string[];
documents?: ProductsProductDocument[] | string[];
/** @description 在产品详情页中展示 */
images?: ProductsProductImage[] | string[];
specs?: ProductSpecGroup[] | string[];
}
export interface ProductsProductDocument {
/** @primaryKey */
id: number;
products_id?: Product | string | null;
product_documents_id?: ProductDocument | string | null;
}
export interface ProductsProductImage {
/** @primaryKey */
id: number;
products_id?: Product | string | null;
product_images_id?: ProductImage | string | null;
sort?: number | null;
}
export interface ProductsQuestion {
/** @primaryKey */
id: number;
products_id?: Product | string | null;
questions_id?: Question | string | null;
sort?: number | null;
}
export interface ProductsTranslation {
/** @primaryKey */
id: number;
products_id?: Product | string | null;
languages_code?: Language | string | null;
/** @required */
name: string;
summary?: string | null;
description?: string | null;
}
export interface Question {
/** @primaryKey */
id: number;
status?: 'published' | 'draft' | 'archived';
/** @description i18n字段 */
translations?: QuestionsTranslation[] | null;
products?: ProductsQuestion[] | string[];
}
export interface QuestionsTranslation {
/** @primaryKey */
id: number;
questions_id?: Question | string | null;
languages_code?: Language | string | null;
/** @required */
title: string;
content?: string | null;
}
export interface SolutionType {
/** @primaryKey */
id: number;
status?: 'published' | 'draft' | 'archived';
/** @description i18n字段 */
translations?: SolutionTypesTranslation[] | null;
}
export interface SolutionTypesTranslation {
/** @primaryKey */
id: number;
solution_types_id?: SolutionType | string | null;
languages_code?: Language | string | null;
name?: string | null;
}
export interface Solution {
/** @primaryKey */
id: number;
status?: 'published' | 'draft' | 'archived';
type?: SolutionType | string | null;
cover?: DirectusFile | string | null;
homepage_recommend?: Homepage | string | null;
recommend_sort?: number | null;
/** @description 条目创建时自动生成 */
create_at?: string | null;
translations?: SolutionsTranslation[] | null;
}
export interface SolutionsTranslation {
/** @primaryKey */
id: number;
solutions_id?: Solution | string | null;
languages_code?: Language | string | null;
title?: string | null;
summary?: string | null;
content?: string | null;
}
export interface DirectusAccess {
/** @primaryKey */
id: string;
role?: DirectusRole | string | null;
user?: DirectusUser | string | null;
policy?: DirectusPolicy | string;
sort?: number | null;
}
export interface DirectusActivity {
/** @primaryKey */
id: number;
action?: string;
user?: DirectusUser | string | null;
timestamp?: string;
ip?: string | null;
user_agent?: string | null;
collection?: string;
item?: string;
origin?: string | null;
revisions?: DirectusRevision[] | string[];
}
export interface DirectusCollection {
/** @primaryKey */
collection: string;
icon?: string | null;
note?: string | null;
display_template?: string | null;
hidden?: boolean;
singleton?: boolean;
translations?: Array<{
language: string;
translation: string;
singular: string;
plural: string;
}> | null;
archive_field?: string | null;
archive_app_filter?: boolean;
archive_value?: string | null;
unarchive_value?: string | null;
sort_field?: string | null;
accountability?: 'all' | 'activity' | null | null;
color?: string | null;
item_duplication_fields?: 'json' | null;
sort?: number | null;
group?: DirectusCollection | string | null;
collapse?: string;
preview_url?: string | null;
versioning?: boolean;
}
export interface DirectusComment {
/** @primaryKey */
id: string;
collection?: DirectusCollection | string;
item?: string;
comment?: string;
date_created?: string | null;
date_updated?: string | null;
user_created?: DirectusUser | string | null;
user_updated?: DirectusUser | string | null;
}
export interface DirectusField {
/** @primaryKey */
id: number;
collection?: DirectusCollection | string;
field?: string;
special?: string[] | null;
interface?: string | null;
options?: 'json' | null;
display?: string | null;
display_options?: 'json' | null;
readonly?: boolean;
hidden?: boolean;
sort?: number | null;
width?: string | null;
translations?: 'json' | null;
note?: string | null;
conditions?: 'json' | null;
required?: boolean | null;
group?: DirectusField | string | null;
validation?: 'json' | null;
validation_message?: string | null;
}
export interface DirectusFile {
/** @primaryKey */
id: string;
storage?: string;
filename_disk?: string | null;
filename_download?: string;
title?: string | null;
type?: string | null;
folder?: DirectusFolder | string | null;
uploaded_by?: DirectusUser | string | null;
created_on?: string;
modified_by?: DirectusUser | string | null;
modified_on?: string;
charset?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
duration?: number | null;
embed?: string | null;
description?: string | null;
location?: string | null;
tags?: string[] | null;
metadata?: 'json' | null;
focal_point_x?: number | null;
focal_point_y?: number | null;
tus_id?: string | null;
tus_data?: 'json' | null;
uploaded_on?: string | null;
}
export interface DirectusFolder {
/** @primaryKey */
id: string;
name?: string;
parent?: DirectusFolder | string | null;
}
export interface DirectusMigration {
/** @primaryKey */
version: string;
name?: string;
timestamp?: string | null;
}
export interface DirectusPermission {
/** @primaryKey */
id: number;
collection?: string;
action?: string;
permissions?: 'json' | null;
validation?: 'json' | null;
presets?: 'json' | null;
fields?: string[] | null;
policy?: DirectusPolicy | string;
}
export interface DirectusPolicy {
/** @primaryKey */
id: string;
/** @required */
name: string;
icon?: string;
description?: string | null;
ip_access?: string[] | null;
enforce_tfa?: boolean;
admin_access?: boolean;
app_access?: boolean;
permissions?: DirectusPermission[] | string[];
users?: DirectusAccess[] | string[];
roles?: DirectusAccess[] | string[];
}
export interface DirectusPreset {
/** @primaryKey */
id: number;
bookmark?: string | null;
user?: DirectusUser | string | null;
role?: DirectusRole | string | null;
collection?: string | null;
search?: string | null;
layout?: string | null;
layout_query?: 'json' | null;
layout_options?: 'json' | null;
refresh_interval?: number | null;
filter?: 'json' | null;
icon?: string | null;
color?: string | null;
}
export interface DirectusRelation {
/** @primaryKey */
id: number;
many_collection?: string;
many_field?: string;
one_collection?: string | null;
one_field?: string | null;
one_collection_field?: string | null;
one_allowed_collections?: string[] | null;
junction_field?: string | null;
sort_field?: string | null;
one_deselect_action?: string;
}
export interface DirectusRevision {
/** @primaryKey */
id: number;
activity?: DirectusActivity | string;
collection?: string;
item?: string;
data?: 'json' | null;
delta?: 'json' | null;
parent?: DirectusRevision | string | null;
version?: DirectusVersion | string | null;
}
export interface DirectusRole {
/** @primaryKey */
id: string;
/** @required */
name: string;
icon?: string;
description?: string | null;
parent?: DirectusRole | string | null;
children?: DirectusRole[] | string[];
policies?: DirectusAccess[] | string[];
users?: DirectusUser[] | string[];
}
export interface DirectusSession {
/** @primaryKey */
token: string;
user?: DirectusUser | string | null;
expires?: string;
ip?: string | null;
user_agent?: string | null;
share?: DirectusShare | string | null;
origin?: string | null;
next_token?: string | null;
}
export interface DirectusSettings {
/** @primaryKey */
id: number;
project_name?: string;
project_url?: string | null;
project_color?: string;
project_logo?: DirectusFile | string | null;
public_foreground?: DirectusFile | string | null;
public_background?: DirectusFile | string | null;
public_note?: string | null;
auth_login_attempts?: number | null;
auth_password_policy?:
| null
| `/^.{8,}$/`
| `/(?=^.{8,}$)(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{';'?>.<,])(?!.*\\s).*$/`
| null;
storage_asset_transform?: 'all' | 'none' | 'presets' | null;
storage_asset_presets?: Array<{
key: string;
fit: 'contain' | 'cover' | 'inside' | 'outside';
width: number;
height: number;
quality: number;
withoutEnlargement: boolean;
format: 'auto' | 'jpeg' | 'png' | 'webp' | 'tiff' | 'avif';
transforms: 'json';
}> | null;
custom_css?: string | null;
storage_default_folder?: DirectusFolder | string | null;
basemaps?: Array<{
name: string;
type: 'raster' | 'tile' | 'style';
url: string;
tileSize: number;
attribution: string;
}> | null;
mapbox_key?: string | null;
module_bar?: 'json' | null;
project_descriptor?: string | null;
default_language?: string;
custom_aspect_ratios?: Array<{ text: string; value: number }> | null;
public_favicon?: DirectusFile | string | null;
default_appearance?: 'auto' | 'light' | 'dark';
default_theme_light?: string | null;
theme_light_overrides?: 'json' | null;
default_theme_dark?: string | null;
theme_dark_overrides?: 'json' | null;
report_error_url?: string | null;
report_bug_url?: string | null;
report_feature_url?: string | null;
public_registration?: boolean;
public_registration_verify_email?: boolean;
public_registration_role?: DirectusRole | string | null;
public_registration_email_filter?: 'json' | null;
visual_editor_urls?: Array<{ url: string }> | null;
accepted_terms?: boolean | null;
project_id?: string | null;
mcp_enabled?: boolean;
mcp_allow_deletes?: boolean;
mcp_prompts_collection?: string | null;
mcp_system_prompt_enabled?: boolean;
mcp_system_prompt?: string | null;
}
export interface DirectusUser {
/** @primaryKey */
id: string;
first_name?: string | null;
last_name?: string | null;
email?: string | null;
password?: string | null;
location?: string | null;
title?: string | null;
description?: string | null;
tags?: string[] | null;
avatar?: DirectusFile | string | null;
language?: string | null;
tfa_secret?: string | null;
status?:
| 'draft'
| 'invited'
| 'unverified'
| 'active'
| 'suspended'
| 'archived';
role?: DirectusRole | string | null;
token?: string | null;
last_access?: string | null;
last_page?: string | null;
provider?: string;
external_identifier?: string | null;
auth_data?: 'json' | null;
email_notifications?: boolean | null;
appearance?: null | 'auto' | 'light' | 'dark' | null;
theme_dark?: string | null;
theme_light?: string | null;
theme_light_overrides?: 'json' | null;
theme_dark_overrides?: 'json' | null;
text_direction?: 'auto' | 'ltr' | 'rtl';
policies?: DirectusAccess[] | string[];
}
export interface DirectusWebhook {
/** @primaryKey */
id: number;
name?: string;
method?: null;
url?: string;
status?: 'active' | 'inactive';
data?: boolean;
actions?: 'create' | 'update' | 'delete';
collections?: string[];
headers?: Array<{ header: string; value: string }> | null;
was_active_before_deprecation?: boolean;
migrated_flow?: DirectusFlow | string | null;
}
export interface DirectusDashboard {
/** @primaryKey */
id: string;
name?: string;
icon?: string;
note?: string | null;
date_created?: string | null;
user_created?: DirectusUser | string | null;
color?: string | null;
panels?: DirectusPanel[] | string[];
}
export interface DirectusPanel {
/** @primaryKey */
id: string;
dashboard?: DirectusDashboard | string;
name?: string | null;
icon?: string | null;
color?: string | null;
show_header?: boolean;
note?: string | null;
type?: string;
position_x?: number;
position_y?: number;
width?: number;
height?: number;
options?: 'json' | null;
date_created?: string | null;
user_created?: DirectusUser | string | null;
}
export interface DirectusNotification {
/** @primaryKey */
id: number;
timestamp?: string | null;
status?: string | null;
recipient?: DirectusUser | string;
sender?: DirectusUser | string | null;
subject?: string;
message?: string | null;
collection?: string | null;
item?: string | null;
}
export interface DirectusShare {
/** @primaryKey */
id: string;
name?: string | null;
collection?: DirectusCollection | string;
item?: string;
role?: DirectusRole | string | null;
password?: string | null;
user_created?: DirectusUser | string | null;
date_created?: string | null;
date_start?: string | null;
date_end?: string | null;
times_used?: number | null;
max_uses?: number | null;
}
export interface DirectusFlow {
/** @primaryKey */
id: string;
name?: string;
icon?: string | null;
color?: string | null;
description?: string | null;
status?: string;
trigger?: string | null;
accountability?: string | null;
options?: 'json' | null;
operation?: DirectusOperation | string | null;
date_created?: string | null;
user_created?: DirectusUser | string | null;
operations?: DirectusOperation[] | string[];
}
export interface DirectusOperation {
/** @primaryKey */
id: string;
name?: string | null;
key?: string;
type?: string;
position_x?: number;
position_y?: number;
options?: 'json' | null;
resolve?: DirectusOperation | string | null;
reject?: DirectusOperation | string | null;
flow?: DirectusFlow | string;
date_created?: string | null;
user_created?: DirectusUser | string | null;
}
export interface DirectusTranslation {
/** @primaryKey */
id: string;
/** @required */
language: string;
/** @required */
key: string;
/** @required */
value: string;
}
export interface DirectusVersion {
/** @primaryKey */
id: string;
key?: string;
name?: string | null;
collection?: DirectusCollection | string;
item?: string;
hash?: string | null;
date_created?: string | null;
date_updated?: string | null;
user_created?: DirectusUser | string | null;
user_updated?: DirectusUser | string | null;
delta?: 'json' | null;
}
export interface DirectusExtension {
enabled?: boolean;
/** @primaryKey */
id: string;
folder?: string;
source?: string;
bundle?: string | null;
}
export interface Schema {
company_profile: CompanyProfile;
company_profile_translations: CompanyProfileTranslation[];
contact_info: ContactInfo;
contact_info_translations: ContactInfoTranslation[];
documents_translations: DocumentsTranslation[];
homepage: Homepage;
homepage_files: HomepageFile[];
languages: Language[];
product_documents: ProductDocument[];
product_documents_translations: ProductDocumentsTranslation[];
product_images: ProductImage[];
product_images_translations: ProductImagesTranslation[];
product_spec_groups: ProductSpecGroup[];
product_spec_groups_translations: ProductSpecGroupsTranslation[];
product_specs: ProductSpec[];
product_specs_translations: ProductSpecsTranslation[];
product_types: ProductType[];
product_types_translations: ProductTypesTranslation[];
products: Product[];
products_product_documents: ProductsProductDocument[];
products_product_images: ProductsProductImage[];
products_questions: ProductsQuestion[];
products_translations: ProductsTranslation[];
questions: Question[];
questions_translations: QuestionsTranslation[];
solution_types: SolutionType[];
solution_types_translations: SolutionTypesTranslation[];
solutions: Solution[];
solutions_translations: SolutionsTranslation[];
directus_access: DirectusAccess[];
directus_activity: DirectusActivity[];
directus_collections: DirectusCollection[];
directus_comments: DirectusComment[];
directus_fields: DirectusField[];
directus_files: DirectusFile[];
directus_folders: DirectusFolder[];
directus_migrations: DirectusMigration[];
directus_permissions: DirectusPermission[];
directus_policies: DirectusPolicy[];
directus_presets: DirectusPreset[];
directus_relations: DirectusRelation[];
directus_revisions: DirectusRevision[];
directus_roles: DirectusRole[];
directus_sessions: DirectusSession[];
directus_settings: DirectusSettings;
directus_users: DirectusUser[];
directus_webhooks: DirectusWebhook[];
directus_dashboards: DirectusDashboard[];
directus_panels: DirectusPanel[];
directus_notifications: DirectusNotification[];
directus_shares: DirectusShare[];
directus_flows: DirectusFlow[];
directus_operations: DirectusOperation[];
directus_translations: DirectusTranslation[];
directus_versions: DirectusVersion[];
directus_extensions: DirectusExtension[];
}
export enum CollectionNames {
company_profile = 'company_profile',
company_profile_translations = 'company_profile_translations',
contact_info = 'contact_info',
contact_info_translations = 'contact_info_translations',
documents_translations = 'documents_translations',
homepage = 'homepage',
homepage_files = 'homepage_files',
languages = 'languages',
product_documents = 'product_documents',
product_documents_translations = 'product_documents_translations',
product_images = 'product_images',
product_images_translations = 'product_images_translations',
product_spec_groups = 'product_spec_groups',
product_spec_groups_translations = 'product_spec_groups_translations',
product_specs = 'product_specs',
product_specs_translations = 'product_specs_translations',
product_types = 'product_types',
product_types_translations = 'product_types_translations',
products = 'products',
products_product_documents = 'products_product_documents',
products_product_images = 'products_product_images',
products_questions = 'products_questions',
products_translations = 'products_translations',
questions = 'questions',
questions_translations = 'questions_translations',
solution_types = 'solution_types',
solution_types_translations = 'solution_types_translations',
solutions = 'solutions',
solutions_translations = 'solutions_translations',
directus_access = 'directus_access',
directus_activity = 'directus_activity',
directus_collections = 'directus_collections',
directus_comments = 'directus_comments',
directus_fields = 'directus_fields',
directus_files = 'directus_files',
directus_folders = 'directus_folders',
directus_migrations = 'directus_migrations',
directus_permissions = 'directus_permissions',
directus_policies = 'directus_policies',
directus_presets = 'directus_presets',
directus_relations = 'directus_relations',
directus_revisions = 'directus_revisions',
directus_roles = 'directus_roles',
directus_sessions = 'directus_sessions',
directus_settings = 'directus_settings',
directus_users = 'directus_users',
directus_webhooks = 'directus_webhooks',
directus_dashboards = 'directus_dashboards',
directus_panels = 'directus_panels',
directus_notifications = 'directus_notifications',
directus_shares = 'directus_shares',
directus_flows = 'directus_flows',
directus_operations = 'directus_operations',
directus_translations = 'directus_translations',
directus_versions = 'directus_versions',
directus_extensions = 'directus_extensions',
}

View File

@ -1,2 +0,0 @@
export * from './meili-index';
export * from './search-result';

View File

@ -1,88 +0,0 @@
/**
* 产品索引文档结构
*/
export interface MeiliProductIndex {
/** 唯一标识符 **/
id: number;
/** 产品名称 **/
name: string;
/** 产品简介 **/
summary: string;
/** 产品详情 **/
description: string;
/** 产品类型 **/
type: string;
}
/**
* 解决方案索引文档结构
*/
export interface MeiliSolutionIndex {
/** 唯一标识符 **/
id: number;
/** 解决方案标题 **/
title: string;
/** 解决方案摘要 **/
summary: string;
/** 解决方案内容 **/
content: string;
/** 解决方案类型 **/
type: string;
}
/**
* 相关问题索引文档结构
*/
export interface MeiliQuestionIndex {
/** 唯一标识符 **/
id: number;
/** 问题标题 **/
title: string;
/** 问题内容 **/
content: string;
/** 相关产品 **/
products: string[];
/** 相关产品类型 **/
product_types: string[];
}
/**
* 相关文档索引文档结构
*/
export interface MeiliProductDocumentIndex {
/** 唯一标识符 **/
id: number;
/** 文档标题 **/
title: string;
/** 相关产品 **/
products: string[];
/** 相关产品类型 **/
product_types: string[];
}
/**
* 索引名与类型映射
*/
export interface MeiliIndexMap {
products: MeiliProductIndex;
solutions: MeiliSolutionIndex;
questions: MeiliQuestionIndex;
product_documents: MeiliProductDocumentIndex;
}
export type MeiliSearchItemType = keyof MeiliIndexMap;

View File

@ -1,42 +0,0 @@
import type { SearchResponse } from 'meilisearch';
/**
* 原始搜索分段结果
* @template T 索引类型
*/
export interface RawSearchSection<T> {
/** 索引名 **/
indexUid: string;
/** 响应数据 **/
response: SearchResponse<T>;
}
/**
* 搜索结果
*/
export interface SearchHit extends Record<string, unknown> {
objectID?: string | number;
}
/**
* 搜索分段结果
* @template T 索引类型
*/
export interface SearchSection<T> {
/** 索引名 **/
indexUid: string;
/** 原始索引名 **/
rawIndex: MeiliSearchItemType;
/** 命中条目 **/
hits: T[];
// hits: SearchHit[];
/** 条目总数 **/
estimatedTotalHits: number;
/** 处理时间 **/
processingTimeMs: number;
}