1516 lines
46 KiB
Vue
1516 lines
46 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref } from 'vue';
|
||
import { useRouter } from 'vue-router';
|
||
|
||
import { $t } from '@easyflow/locales';
|
||
|
||
import { CopyDocument } from '@element-plus/icons-vue';
|
||
import { ElButton, ElCard, ElIcon, ElInput, ElMessage } from 'element-plus';
|
||
|
||
import { api } from '#/api/request';
|
||
import { copyTextWithFeedback } from '#/utils/clipboard-feedback';
|
||
import { buildAbsoluteAppRouteUrl } from '#/utils/share-route-context';
|
||
|
||
type EndpointEnumValue = {
|
||
description: string;
|
||
value: string;
|
||
};
|
||
|
||
type EndpointParam = {
|
||
enumValues?: readonly EndpointEnumValue[];
|
||
location: 'body' | 'header' | 'metadata' | 'multipart' | 'query';
|
||
name: string;
|
||
note?: string;
|
||
required?: boolean;
|
||
type?: string;
|
||
};
|
||
|
||
type EndpointExample = {
|
||
content: string;
|
||
title: string;
|
||
};
|
||
|
||
type EndpointDoc = {
|
||
contentType?: string;
|
||
examples?: EndpointExample[];
|
||
hint: string;
|
||
method: 'GET' | 'POST';
|
||
notes?: string[];
|
||
openByDefault?: boolean;
|
||
params: EndpointParam[];
|
||
path: string;
|
||
permission: '知识导入' | '知识库维护' | '知识库读取';
|
||
};
|
||
|
||
const props = defineProps({
|
||
knowledgeId: {
|
||
type: String,
|
||
required: true,
|
||
},
|
||
collectionType: {
|
||
type: String,
|
||
default: 'DOCUMENT',
|
||
},
|
||
manageable: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
});
|
||
|
||
const router = useRouter();
|
||
const createLoading = ref(false);
|
||
const generatedUrl = ref('');
|
||
const generatedExpireAt = ref('');
|
||
|
||
const apiBaseUrl = computed(() => {
|
||
const appBasePath = (import.meta.env.BASE_URL || '/').replace(/\/$/, '');
|
||
if (typeof window === 'undefined') {
|
||
return `${appBasePath}/public-api/knowledge-share`;
|
||
}
|
||
return `${window.location.origin}${appBasePath}/public-api/knowledge-share`;
|
||
});
|
||
|
||
const formatJson = (value: unknown) => JSON.stringify(value, null, 2);
|
||
|
||
const retrievalModeEnumValues: readonly EndpointEnumValue[] = [
|
||
{ value: 'VECTOR', description: '向量语义召回' },
|
||
{ value: 'KEYWORD', description: '关键词全文召回' },
|
||
{ value: 'HYBRID', description: '混合召回,默认值' },
|
||
];
|
||
|
||
const chunkStrategyEnumValues: readonly EndpointEnumValue[] = [
|
||
{ value: 'AUTO', description: '自动选择分块策略,默认值' },
|
||
{ value: 'MARKDOWN_SECTION', description: '按 Markdown 标题层级分块' },
|
||
{ value: 'OUTLINE_SECTION', description: '按文档大纲或章节分块' },
|
||
{ value: 'QA_PAIR', description: '按问答对分块' },
|
||
{ value: 'PARAGRAPH_LENGTH', description: '按段落和长度分块' },
|
||
{ value: 'CUSTOM_REGEX', description: '按自定义正则表达式分块' },
|
||
];
|
||
|
||
const duplicatePolicyEnumValues: readonly EndpointEnumValue[] = [
|
||
{ value: 'SKIP', description: '跳过历史重复文件,默认值' },
|
||
{ value: 'OVERWRITE', description: '导入成功后覆盖历史文档' },
|
||
{ value: 'REIMPORT', description: '保留历史文档并重新导入一份' },
|
||
];
|
||
|
||
const importItemStatusEnumValues: readonly EndpointEnumValue[] = [
|
||
{ value: 'PENDING', description: '等待处理' },
|
||
{ value: 'UPLOADING', description: '文件正在上传' },
|
||
{ value: 'RUNNING', description: '正在处理' },
|
||
{ value: 'UPLOADED', description: '文件已上传' },
|
||
{ value: 'FAILED', description: '处理失败' },
|
||
{ value: 'COMPLETED', description: '处理完成' },
|
||
{ value: 'SKIPPED', description: '因重复而跳过' },
|
||
{ value: 'CANCELLED', description: '文件随未启动批次一并取消' },
|
||
];
|
||
|
||
const parameterTypes: Record<string, string> = {
|
||
answerHtml: 'string',
|
||
categoryId: 'string (ID)',
|
||
collectionId: 'string (ID)',
|
||
content: 'string',
|
||
documentId: 'string (ID)',
|
||
file: 'File',
|
||
fileKeys: 'string[]',
|
||
files: 'File[]',
|
||
id: 'string (ID)',
|
||
itemStatus: 'enum string',
|
||
keyword: 'string',
|
||
knowledgeId: 'string (ID)',
|
||
metadata: 'JSON',
|
||
'metadata.chunkStrategy': 'object',
|
||
'metadata.chunkStrategy.strategyCode': 'enum string',
|
||
'metadata.duplicatePolicy': 'enum string',
|
||
'metadata.files': 'object[]',
|
||
'metadata.files[].clientFileKey': 'string',
|
||
'metadata.files[].fileName': 'string',
|
||
'metadata.files[].relativePath': 'string',
|
||
'metadata.knowledgeId': 'string (ID)',
|
||
pageNumber: 'integer',
|
||
pageSize: 'integer',
|
||
question: 'string',
|
||
retrievalMode: 'enum string',
|
||
taskId: 'string (ID)',
|
||
};
|
||
|
||
const parameterDescriptions: Record<string, string> = {
|
||
answerHtml: 'FAQ 答案 HTML',
|
||
categoryId: 'FAQ 分类 ID',
|
||
collectionId: '知识库 ID',
|
||
content: '分块正文',
|
||
documentId: '文档 ID',
|
||
file: '待上传文件',
|
||
fileKeys: '需要重试的文件键',
|
||
files: '待导入文件列表',
|
||
id: '数据 ID',
|
||
itemStatus: '文件处理状态筛选',
|
||
keyword: '检索关键词',
|
||
knowledgeId: '知识库 ID',
|
||
metadata: '导入元数据与文件清单',
|
||
'metadata.chunkStrategy': '分块策略,省略时默认 AUTO',
|
||
'metadata.chunkStrategy.strategyCode': '分块策略编码',
|
||
'metadata.duplicatePolicy': '重复策略,默认 SKIP',
|
||
'metadata.files': '与文件 Part 一一对应的清单',
|
||
'metadata.files[].clientFileKey': '批次内唯一的文件键,最长 64 字符',
|
||
'metadata.files[].fileName': '文件名,须与对应文件 Part 一致',
|
||
'metadata.files[].relativePath': '文件夹内相对路径',
|
||
'metadata.knowledgeId': '目标知识库 ID',
|
||
pageNumber: '页码,默认 1',
|
||
pageSize: '每页条数',
|
||
question: 'FAQ 问题',
|
||
retrievalMode: '召回方式',
|
||
taskId: '导入任务 ID',
|
||
};
|
||
|
||
const parameterLocationLabels: Record<EndpointParam['location'], string> = {
|
||
body: 'JSON Body',
|
||
header: 'Header',
|
||
metadata: 'metadata JSON',
|
||
multipart: 'Multipart',
|
||
query: 'Query',
|
||
};
|
||
|
||
const buildJsonPostExample = (
|
||
path: string,
|
||
body: unknown,
|
||
extraHeaders: string[] = [],
|
||
) => {
|
||
const headers = [
|
||
" -H 'ApiKey: 你的访问令牌' \\",
|
||
...extraHeaders.map((header) => ` -H '${header}' \\`),
|
||
" -H 'Content-Type: application/json' \\",
|
||
];
|
||
return [
|
||
`curl -X POST '${apiBaseUrl.value}${path}' \\`,
|
||
...headers,
|
||
` --data '${formatJson(body)}'`,
|
||
].join('\n');
|
||
};
|
||
|
||
const detailExample = computed(() => {
|
||
return [
|
||
`curl -X GET '${apiBaseUrl.value}/detail?knowledgeId=${props.knowledgeId}&pageNumber=1&pageSize=50' \\`,
|
||
" -H 'ApiKey: 你的访问令牌'",
|
||
].join('\n');
|
||
});
|
||
|
||
const searchExample = computed(() => {
|
||
return [
|
||
`curl -G '${apiBaseUrl.value}/search' \\`,
|
||
" -H 'ApiKey: 你的访问令牌' \\",
|
||
` --data-urlencode 'knowledgeId=${props.knowledgeId}' \\`,
|
||
" --data-urlencode 'keyword=测试问题'",
|
||
].join('\n');
|
||
});
|
||
|
||
const endpointDocs = computed(() => {
|
||
const documentRemoveBody = {
|
||
knowledgeId: props.knowledgeId,
|
||
id: '文档ID',
|
||
};
|
||
const chunkUpdateBody = {
|
||
knowledgeId: props.knowledgeId,
|
||
id: '分块ID',
|
||
content: '更新后的分块内容',
|
||
};
|
||
const chunkRemoveBody = {
|
||
knowledgeId: props.knowledgeId,
|
||
id: '分块ID',
|
||
};
|
||
const faqSaveBody = {
|
||
collectionId: props.knowledgeId,
|
||
question: '如何申请账号?',
|
||
answerHtml: '<p>请联系管理员开通账号。</p>',
|
||
};
|
||
const faqUpdateBody = {
|
||
id: 'FAQ ID',
|
||
collectionId: props.knowledgeId,
|
||
question: '如何修改账号信息?',
|
||
answerHtml: '<p>请在个人中心修改。</p>',
|
||
};
|
||
const faqRemoveBody = {
|
||
id: 'FAQ ID',
|
||
};
|
||
const faqImportResponseExample = formatJson({
|
||
errorCode: 0,
|
||
message: '成功',
|
||
data: {
|
||
totalCount: 100,
|
||
successCount: 98,
|
||
errorCount: 2,
|
||
errorRows: [
|
||
{
|
||
rowNumber: 12,
|
||
categoryPath: '默认分类',
|
||
question: '示例问题',
|
||
reason: '问题不能为空或格式不正确',
|
||
},
|
||
],
|
||
},
|
||
});
|
||
const detailDocumentRecords =
|
||
props.collectionType === 'FAQ'
|
||
? []
|
||
: [
|
||
{
|
||
id: '文档ID',
|
||
title: 'manual.pdf',
|
||
documentType: 'pdf',
|
||
contentType: 'application/pdf',
|
||
processStatus: 'INDEXED',
|
||
chunkCount: 12,
|
||
progressPercent: 100,
|
||
created: '2026-08-02 16:30:00',
|
||
modified: '2026-08-02 16:31:00',
|
||
},
|
||
];
|
||
const detailResponseExample = formatJson({
|
||
errorCode: 0,
|
||
message: '成功',
|
||
data: {
|
||
id: props.knowledgeId,
|
||
title: '示例知识库',
|
||
collectionType: props.collectionType,
|
||
documents: {
|
||
pageNumber: 1,
|
||
pageSize: 50,
|
||
totalPage: detailDocumentRecords.length > 0 ? 1 : 0,
|
||
totalRow: detailDocumentRecords.length,
|
||
records: detailDocumentRecords,
|
||
},
|
||
},
|
||
});
|
||
const searchResponseExample = formatJson({
|
||
errorCode: 0,
|
||
message: '成功',
|
||
data:
|
||
props.collectionType === 'FAQ'
|
||
? [
|
||
{
|
||
resultType: 'FAQ',
|
||
faqId: 'FAQ ID',
|
||
question: '如何申请账号?',
|
||
answerText: '请联系管理员开通账号。',
|
||
categoryId: 'FAQ 分类 ID',
|
||
content: '问题:如何申请账号?\n答案:请联系管理员开通账号。',
|
||
score: 0.8732,
|
||
hitSource: 'HYBRID',
|
||
},
|
||
]
|
||
: [
|
||
{
|
||
resultType: 'DOCUMENT',
|
||
documentId: '文档ID',
|
||
documentName: 'manual.pdf',
|
||
sourceFileName: 'manual.pdf',
|
||
content: '命中的文档分块内容',
|
||
score: 0.8732,
|
||
hitSource: 'HYBRID',
|
||
},
|
||
],
|
||
});
|
||
const retryBody = {
|
||
taskId: '导入接口返回的 taskId',
|
||
fileKeys: ['file-1'],
|
||
};
|
||
const importMetadata = {
|
||
knowledgeId: props.knowledgeId,
|
||
chunkStrategy: {
|
||
strategyCode: 'AUTO',
|
||
},
|
||
duplicatePolicy: 'SKIP',
|
||
files: [
|
||
{
|
||
clientFileKey: 'file-1',
|
||
fileName: 'manual.pdf',
|
||
relativePath: 'docs/manual.pdf',
|
||
},
|
||
],
|
||
};
|
||
const importCurlExample = [
|
||
"FILE='./docs/manual.pdf'",
|
||
`KNOWLEDGE_ID='${props.knowledgeId}'`,
|
||
'FILE_NAME=$(basename "$FILE")',
|
||
'METADATA=$(printf \'{"knowledgeId":"%s","chunkStrategy":{"strategyCode":"AUTO"},"duplicatePolicy":"SKIP","files":[{"clientFileKey":"file-1","fileName":"%s","relativePath":"%s"}]}\' "$KNOWLEDGE_ID" "$FILE_NAME" "$FILE_NAME")',
|
||
'',
|
||
`curl -X POST '${apiBaseUrl.value}/document/import/batch' \\`,
|
||
" -H 'ApiKey: 你的访问令牌' \\",
|
||
' -F "metadata=$METADATA;type=application/json" \\',
|
||
' -F "files=@$FILE"',
|
||
].join('\n');
|
||
const importResponseExample = formatJson({
|
||
errorCode: 0,
|
||
message: '成功',
|
||
data: {
|
||
taskId: '异步任务ID',
|
||
status: 'RUNNING',
|
||
totalCount: 1,
|
||
totalBytes: 123_456,
|
||
createdAt: '2026-08-02T16:30:00+08:00',
|
||
},
|
||
});
|
||
const importStatusExample = [
|
||
`curl -G '${apiBaseUrl.value}/document/import/batch/status' \\`,
|
||
" -H 'ApiKey: 你的访问令牌' \\",
|
||
" --data-urlencode 'taskId=导入接口返回的 taskId' \\",
|
||
" --data-urlencode 'pageNumber=1' \\",
|
||
" --data-urlencode 'pageSize=20'",
|
||
].join('\n');
|
||
const importStatusResponseExample = formatJson({
|
||
errorCode: 0,
|
||
message: '成功',
|
||
data: {
|
||
taskId: '异步任务ID',
|
||
knowledgeId: props.knowledgeId,
|
||
status: 'PARTIAL_SUCCEEDED',
|
||
progressPercent: 100,
|
||
counts: {
|
||
total: 2,
|
||
completed: 1,
|
||
processing: 0,
|
||
pending: 0,
|
||
failed: 1,
|
||
skipped: 0,
|
||
retryableFailed: 1,
|
||
},
|
||
canRetry: true,
|
||
items: {
|
||
pageNumber: 1,
|
||
pageSize: 20,
|
||
total: 2,
|
||
records: [
|
||
{
|
||
fileKey: 'file-1',
|
||
relativePath: 'docs/manual.pdf',
|
||
documentId: '文档ID',
|
||
stage: 'PARSE',
|
||
status: 'FAILED',
|
||
attemptCount: 1,
|
||
retryable: true,
|
||
error: {
|
||
code: 'parse_service_unavailable',
|
||
message: '文档解析服务暂时不可用',
|
||
},
|
||
},
|
||
],
|
||
},
|
||
},
|
||
});
|
||
const retryResponseExample = formatJson({
|
||
errorCode: 0,
|
||
message: '成功',
|
||
data: {
|
||
taskId: '异步任务ID',
|
||
status: 'RUNNING',
|
||
retriedCount: 1,
|
||
},
|
||
});
|
||
|
||
const commonEndpoints: EndpointDoc[] = [
|
||
{
|
||
method: 'GET',
|
||
path: '/detail',
|
||
hint: '获取知识库详情',
|
||
permission: '知识库读取',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
{ name: 'pageNumber', location: 'query', note: '默认 1' },
|
||
{ name: 'pageSize', location: 'query', note: '默认 50,最大 100' },
|
||
],
|
||
notes: [
|
||
'知识库原有字段保持在 data 顶层;documents 返回已上传文档的分页摘要。',
|
||
'文档摘要包含 ID、标题、类型、处理状态、分块数、处理进度和时间;FAQ 知识库返回空文档页。',
|
||
],
|
||
examples: [
|
||
{ title: 'cURL 示例', content: detailExample.value },
|
||
{ title: '响应示例', content: detailResponseExample },
|
||
],
|
||
},
|
||
{
|
||
method: 'GET',
|
||
path: '/search',
|
||
hint: '知识检索',
|
||
permission: '知识库读取',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
{ name: 'keyword', location: 'query', required: true },
|
||
{
|
||
name: 'retrievalMode',
|
||
location: 'query',
|
||
enumValues: retrievalModeEnumValues,
|
||
note: '召回方式,不传时默认 HYBRID',
|
||
},
|
||
],
|
||
notes: [
|
||
'resultType 用于区分 DOCUMENT 与 FAQ。',
|
||
'文档命中返回 documentId、documentName;FAQ 命中返回 faqId、question、answerText、categoryId。',
|
||
'sourceFileName 作为文档名称兼容字段继续保留;FAQ 完整 HTML 可通过 /faq/detail 查询。',
|
||
],
|
||
examples: [
|
||
{ title: 'cURL 示例', content: searchExample.value },
|
||
{ title: '响应示例', content: searchResponseExample },
|
||
],
|
||
},
|
||
];
|
||
const typeEndpoints: EndpointDoc[] =
|
||
props.collectionType === 'FAQ'
|
||
? [
|
||
{
|
||
method: 'GET',
|
||
path: '/faq/page',
|
||
hint: 'FAQ 分页',
|
||
permission: '知识库读取',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
{ name: 'question', location: 'query', note: '问题关键字' },
|
||
{ name: 'categoryId', location: 'query', note: '分类 ID' },
|
||
{ name: 'pageNumber', location: 'query', note: '默认 1' },
|
||
{ name: 'pageSize', location: 'query', note: '默认 10' },
|
||
],
|
||
},
|
||
{
|
||
method: 'GET',
|
||
path: '/faq/detail',
|
||
hint: 'FAQ 详情',
|
||
permission: '知识库读取',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
{ name: 'id', location: 'query', required: true, note: 'FAQ ID' },
|
||
],
|
||
},
|
||
{
|
||
method: 'POST',
|
||
path: '/faq/save',
|
||
hint: '新增 FAQ',
|
||
permission: '知识库维护',
|
||
contentType: 'application/json',
|
||
params: [
|
||
{ name: 'collectionId', location: 'body', required: true },
|
||
{ name: 'question', location: 'body', required: true },
|
||
{ name: 'answerHtml', location: 'body', required: true },
|
||
{ name: 'categoryId', location: 'body', note: '分类 ID' },
|
||
],
|
||
examples: [
|
||
{ title: '请求体示例', content: formatJson(faqSaveBody) },
|
||
{
|
||
title: 'cURL 示例',
|
||
content: buildJsonPostExample('/faq/save', faqSaveBody),
|
||
},
|
||
],
|
||
},
|
||
{
|
||
method: 'POST',
|
||
path: '/faq/update',
|
||
hint: '修改 FAQ',
|
||
permission: '知识库维护',
|
||
contentType: 'application/json',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
{ name: 'id', location: 'body', required: true, note: 'FAQ ID' },
|
||
{
|
||
name: 'collectionId',
|
||
location: 'body',
|
||
note: '不传时保持原知识库 ID',
|
||
},
|
||
{ name: 'question', location: 'body', required: true },
|
||
{ name: 'answerHtml', location: 'body', required: true },
|
||
{ name: 'categoryId', location: 'body', note: '分类 ID' },
|
||
],
|
||
examples: [
|
||
{ title: '请求体示例', content: formatJson(faqUpdateBody) },
|
||
{
|
||
title: 'cURL 示例',
|
||
content: buildJsonPostExample(
|
||
`/faq/update?knowledgeId=${props.knowledgeId}`,
|
||
faqUpdateBody,
|
||
),
|
||
},
|
||
],
|
||
},
|
||
{
|
||
method: 'POST',
|
||
path: '/faq/remove',
|
||
hint: '删除 FAQ',
|
||
permission: '知识库维护',
|
||
contentType: 'application/json',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
{ name: 'id', location: 'body', required: true, note: 'FAQ ID' },
|
||
],
|
||
examples: [
|
||
{ title: '请求体示例', content: formatJson(faqRemoveBody) },
|
||
{
|
||
title: 'cURL 示例',
|
||
content: buildJsonPostExample(
|
||
`/faq/remove?knowledgeId=${props.knowledgeId}`,
|
||
faqRemoveBody,
|
||
),
|
||
},
|
||
],
|
||
},
|
||
{
|
||
method: 'POST',
|
||
path: '/faq/importExcel',
|
||
hint: '导入 FAQ Excel',
|
||
permission: '知识导入',
|
||
contentType: 'multipart/form-data',
|
||
params: [
|
||
{
|
||
name: 'collectionId',
|
||
location: 'multipart',
|
||
required: true,
|
||
},
|
||
{ name: 'file', location: 'multipart', required: true },
|
||
],
|
||
notes: [
|
||
'仅支持 xlsx、xls 文件,单个文件不超过 10 MB,最多导入 5000 条。',
|
||
'请先下载导入模板并保持模板列结构。',
|
||
'由调用端自动生成 multipart boundary,不要手动设置 Content-Type 请求头。',
|
||
],
|
||
examples: [
|
||
{
|
||
title: 'cURL 示例',
|
||
content: [
|
||
`curl -X POST '${apiBaseUrl.value}/faq/importExcel' \\`,
|
||
" -H 'ApiKey: 你的访问令牌' \\",
|
||
` -F 'collectionId=${props.knowledgeId}' \\`,
|
||
" -F 'file=@./faq_import.xlsx'",
|
||
].join('\n'),
|
||
},
|
||
{
|
||
title: '响应示例',
|
||
content: faqImportResponseExample,
|
||
},
|
||
],
|
||
},
|
||
{
|
||
method: 'GET',
|
||
path: '/faq/downloadImportTemplate',
|
||
hint: '下载 FAQ 导入模板',
|
||
permission: '知识导入',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
],
|
||
},
|
||
{
|
||
method: 'GET',
|
||
path: '/faq/exportExcel',
|
||
hint: '导出 FAQ Excel',
|
||
permission: '知识库读取',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
],
|
||
},
|
||
]
|
||
: [
|
||
{
|
||
method: 'GET',
|
||
path: '/document/page',
|
||
hint: '文档分页',
|
||
permission: '知识库读取',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
{ name: 'documentId', location: 'query', note: '文档 ID' },
|
||
{ name: 'pageNumber', location: 'query', note: '默认 1' },
|
||
{ name: 'pageSize', location: 'query', note: '默认 10' },
|
||
],
|
||
},
|
||
{
|
||
method: 'GET',
|
||
path: '/document/download',
|
||
hint: '下载文档',
|
||
permission: '知识库读取',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
{ name: 'documentId', location: 'query', required: true },
|
||
],
|
||
},
|
||
{
|
||
method: 'GET',
|
||
path: '/documentChunk/page',
|
||
hint: '文档分块分页',
|
||
permission: '知识库读取',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'query', required: true },
|
||
{ name: 'documentId', location: 'query', required: true },
|
||
{ name: 'pageNumber', location: 'query', note: '默认 1' },
|
||
{ name: 'pageSize', location: 'query', note: '默认 10' },
|
||
],
|
||
},
|
||
{
|
||
method: 'POST',
|
||
path: '/document/import/batch',
|
||
hint: '批量异步导入',
|
||
permission: '知识导入',
|
||
contentType: 'multipart/form-data',
|
||
openByDefault: true,
|
||
params: [
|
||
{
|
||
name: 'metadata',
|
||
location: 'multipart',
|
||
type: 'JSON',
|
||
required: true,
|
||
note: 'application/json,含知识库 ID、文件清单和可选分块策略',
|
||
},
|
||
{
|
||
name: 'metadata.knowledgeId',
|
||
location: 'metadata',
|
||
required: true,
|
||
},
|
||
{
|
||
name: 'metadata.chunkStrategy',
|
||
location: 'metadata',
|
||
},
|
||
{
|
||
name: 'metadata.chunkStrategy.strategyCode',
|
||
location: 'metadata',
|
||
enumValues: chunkStrategyEnumValues,
|
||
note: '分块策略编码,不传时默认 AUTO',
|
||
},
|
||
{
|
||
name: 'metadata.duplicatePolicy',
|
||
location: 'metadata',
|
||
enumValues: duplicatePolicyEnumValues,
|
||
note: '重复文件处理策略,不传时默认 SKIP',
|
||
},
|
||
{
|
||
name: 'metadata.files',
|
||
location: 'metadata',
|
||
required: true,
|
||
},
|
||
{
|
||
name: 'metadata.files[].clientFileKey',
|
||
location: 'metadata',
|
||
required: true,
|
||
},
|
||
{
|
||
name: 'metadata.files[].fileName',
|
||
location: 'metadata',
|
||
required: true,
|
||
},
|
||
{
|
||
name: 'metadata.files[].relativePath',
|
||
location: 'metadata',
|
||
note: '可省略,默认使用 fileName',
|
||
},
|
||
{
|
||
name: 'files',
|
||
location: 'multipart',
|
||
type: 'File[]',
|
||
required: true,
|
||
note: '同名多文件 Part,总计不超过 200 MiB',
|
||
},
|
||
],
|
||
notes: [
|
||
'metadata 是 Content-Type 为 application/json 的 multipart Part,不是普通表单字符串。',
|
||
'metadata.knowledgeId 填当前知识库 ID;chunkStrategy 可省略;duplicatePolicy 默认 SKIP。',
|
||
'metadata.files 中每项填写 clientFileKey、fileName 和可选 relativePath;文件大小由服务端实际读取并统计。',
|
||
'支持 txt、md、pdf、docx、pptx、xlsx、csv;单文件不超过 100 MiB,单批不超过 200 MiB,最多 200 个文件。',
|
||
'metadata 最大 1 MiB;metadata.files 必须与 files Part 数量、顺序和文件名一致。',
|
||
'chunkStrategy 可不传,默认 AUTO;duplicatePolicy 支持 SKIP、OVERWRITE、REIMPORT。',
|
||
'服务端会按调用者、元数据和文件内容生成请求指纹,任务完成后 10 分钟内的重复提交会返回原 taskId。',
|
||
'由调用端自动生成 multipart boundary,不要手动设置 Content-Type 请求头。',
|
||
],
|
||
examples: [
|
||
{
|
||
title: 'metadata Part 填写示例',
|
||
content: formatJson(importMetadata),
|
||
},
|
||
{ title: '可执行 cURL 示例', content: importCurlExample },
|
||
{
|
||
title: 'HTTP 202 响应示例',
|
||
content: importResponseExample,
|
||
},
|
||
],
|
||
},
|
||
{
|
||
method: 'GET',
|
||
path: '/document/import/batch/status',
|
||
hint: '查询导入状态',
|
||
permission: '知识导入',
|
||
params: [
|
||
{ name: 'taskId', location: 'query', required: true },
|
||
{
|
||
name: 'itemStatus',
|
||
location: 'query',
|
||
enumValues: importItemStatusEnumValues,
|
||
note: '文件处理状态筛选,不传时查询全部状态',
|
||
},
|
||
{ name: 'pageNumber', location: 'query', note: '默认 1' },
|
||
{
|
||
name: 'pageSize',
|
||
location: 'query',
|
||
note: '默认 20,最大 100',
|
||
},
|
||
],
|
||
notes: [
|
||
'公开状态包括 QUEUED、RUNNING、SUCCEEDED、FAILED、PARTIAL_SUCCEEDED、INTERRUPTED、CANCELLED;进入终态后停止轮询。',
|
||
'只有 canRetry=true 的任务允许重试。',
|
||
],
|
||
examples: [
|
||
{ title: 'cURL 示例', content: importStatusExample },
|
||
{
|
||
title: '响应示例',
|
||
content: importStatusResponseExample,
|
||
},
|
||
],
|
||
},
|
||
{
|
||
method: 'POST',
|
||
path: '/document/import/batch/retry',
|
||
hint: '重试异常文件',
|
||
permission: '知识导入',
|
||
contentType: 'application/json',
|
||
params: [
|
||
{ name: 'taskId', location: 'body', required: true },
|
||
{
|
||
name: 'fileKeys',
|
||
location: 'body',
|
||
type: 'string[]',
|
||
note: '省略时重试全部;最多 200 个,每项最长 64 字符',
|
||
},
|
||
],
|
||
notes: [
|
||
'省略 fileKeys 时重试全部可恢复失败项;指定时只重试对应文件。',
|
||
'fileKeys 不允许传空数组、空值、重复值或超过 64 字符的值,最多 200 个。',
|
||
'任务正在运行时不能重复领取重试,请继续使用 taskId 查询状态。',
|
||
],
|
||
examples: [
|
||
{ title: '请求体示例', content: formatJson(retryBody) },
|
||
{
|
||
title: 'cURL 示例',
|
||
content: buildJsonPostExample(
|
||
'/document/import/batch/retry',
|
||
retryBody,
|
||
),
|
||
},
|
||
{ title: '响应示例', content: retryResponseExample },
|
||
],
|
||
},
|
||
{
|
||
method: 'POST',
|
||
path: '/document/remove',
|
||
hint: '删除文档',
|
||
permission: '知识库维护',
|
||
contentType: 'application/json',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'body', required: true },
|
||
{ name: 'id', location: 'body', required: true, note: '文档 ID' },
|
||
],
|
||
examples: [
|
||
{ title: '请求体示例', content: formatJson(documentRemoveBody) },
|
||
{
|
||
title: 'cURL 示例',
|
||
content: buildJsonPostExample(
|
||
'/document/remove',
|
||
documentRemoveBody,
|
||
),
|
||
},
|
||
],
|
||
},
|
||
{
|
||
method: 'POST',
|
||
path: '/documentChunk/update',
|
||
hint: '更新文档分块',
|
||
permission: '知识库维护',
|
||
contentType: 'application/json',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'body', required: true },
|
||
{ name: 'id', location: 'body', required: true, note: '分块 ID' },
|
||
{ name: 'content', location: 'body', required: true },
|
||
],
|
||
examples: [
|
||
{ title: '请求体示例', content: formatJson(chunkUpdateBody) },
|
||
{
|
||
title: 'cURL 示例',
|
||
content: buildJsonPostExample(
|
||
'/documentChunk/update',
|
||
chunkUpdateBody,
|
||
),
|
||
},
|
||
],
|
||
},
|
||
{
|
||
method: 'POST',
|
||
path: '/documentChunk/remove',
|
||
hint: '删除文档分块',
|
||
permission: '知识库维护',
|
||
contentType: 'application/json',
|
||
params: [
|
||
{ name: 'knowledgeId', location: 'body', required: true },
|
||
{ name: 'id', location: 'body', required: true, note: '分块 ID' },
|
||
],
|
||
examples: [
|
||
{ title: '请求体示例', content: formatJson(chunkRemoveBody) },
|
||
{
|
||
title: 'cURL 示例',
|
||
content: buildJsonPostExample(
|
||
'/documentChunk/remove',
|
||
chunkRemoveBody,
|
||
),
|
||
},
|
||
],
|
||
},
|
||
];
|
||
return [...commonEndpoints, ...typeEndpoints].map((item) => ({
|
||
...item,
|
||
url: `${apiBaseUrl.value}${item.path}`,
|
||
}));
|
||
});
|
||
|
||
const getParameterType = (param: EndpointParam) =>
|
||
param.type || parameterTypes[param.name] || 'string';
|
||
|
||
const getParameterDescription = (param: EndpointParam) =>
|
||
param.note || parameterDescriptions[param.name] || '接口参数';
|
||
|
||
const createShare = async () => {
|
||
if (!props.manageable) {
|
||
ElMessage.warning($t('documentCollection.managePermissionHint'));
|
||
return;
|
||
}
|
||
createLoading.value = true;
|
||
try {
|
||
const res = await api.post('/api/v1/knowledgeShare/url/create', {
|
||
knowledgeId: props.knowledgeId,
|
||
});
|
||
if (res.errorCode === 0) {
|
||
const shareKey = String(res.data?.shareKey || '').trim();
|
||
generatedUrl.value = shareKey
|
||
? buildAbsoluteAppRouteUrl(
|
||
router.resolve({
|
||
name: 'KnowledgeShare',
|
||
query: { shareKey },
|
||
}).href,
|
||
)
|
||
: '';
|
||
generatedExpireAt.value = res.data?.expiresAt || '';
|
||
ElMessage.success('已创建分享链接');
|
||
}
|
||
} finally {
|
||
createLoading.value = false;
|
||
}
|
||
};
|
||
|
||
const copyGeneratedUrl = async () => {
|
||
if (!generatedUrl.value) {
|
||
ElMessage.warning('请先生成分享链接');
|
||
return;
|
||
}
|
||
await copyTextWithFeedback(generatedUrl.value, '已复制分享链接');
|
||
};
|
||
|
||
const copyApiExample = async (content: string) => {
|
||
await copyTextWithFeedback(content, '已复制调用示例');
|
||
};
|
||
|
||
const copyEndpointUrl = async (url: string) => {
|
||
await copyTextWithFeedback(url, '已复制接口地址');
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<div class="share-management">
|
||
<ElCard shadow="never" class="share-card">
|
||
<template #header>
|
||
<div class="share-card__header">
|
||
<div>
|
||
<div class="share-card__title">当前分享链接</div>
|
||
<div class="share-card__desc">
|
||
默认有效期 30 分钟。重新生成后旧链接立即失效。
|
||
</div>
|
||
</div>
|
||
<ElButton
|
||
type="primary"
|
||
:loading="createLoading"
|
||
:disabled="!props.manageable"
|
||
@click="createShare"
|
||
>
|
||
{{ generatedUrl ? '重新生成链接' : '生成分享链接' }}
|
||
</ElButton>
|
||
</div>
|
||
</template>
|
||
|
||
<div v-if="generatedUrl" class="share-result">
|
||
<div class="share-result__row">
|
||
<ElInput v-model="generatedUrl" readonly />
|
||
<ElButton
|
||
text
|
||
circle
|
||
:icon="CopyDocument"
|
||
aria-label="复制分享链接"
|
||
title="复制分享链接"
|
||
@click="copyGeneratedUrl"
|
||
/>
|
||
</div>
|
||
<div class="share-result__meta">过期时间:{{ generatedExpireAt }}</div>
|
||
</div>
|
||
<div v-else class="share-empty">暂未生成分享链接</div>
|
||
</ElCard>
|
||
|
||
<ElCard shadow="never" class="share-card">
|
||
<template #header>
|
||
<div class="share-card__header share-card__header--stack">
|
||
<div>
|
||
<div class="share-card__title">API 调用方式</div>
|
||
<div class="share-card__desc">
|
||
使用已开通对应知识库权限的访问令牌,通过
|
||
<code>ApiKey</code> 请求头调用。示例已自动带入当前知识库 ID。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="api-doc">
|
||
<div class="api-doc__meta">
|
||
<div class="api-doc__item">
|
||
<span class="api-doc__label">接口前缀</span>
|
||
<code class="api-doc__value">{{ apiBaseUrl }}</code>
|
||
</div>
|
||
<div class="api-doc__item">
|
||
<span class="api-doc__label">请求头</span>
|
||
<code class="api-doc__value">ApiKey: 你的访问令牌</code>
|
||
</div>
|
||
<div class="api-doc__item">
|
||
<span class="api-doc__label">JSON 请求</span>
|
||
<code class="api-doc__value">Content-Type: application/json</code>
|
||
</div>
|
||
<div class="api-doc__item">
|
||
<span class="api-doc__label">文件上传</span>
|
||
<code class="api-doc__value">multipart/form-data</code>
|
||
<span class="api-doc__helper">由调用端自动生成 boundary</span>
|
||
</div>
|
||
<div class="api-doc__item">
|
||
<span class="api-doc__label">当前知识库 ID</span>
|
||
<code class="api-doc__value">{{ props.knowledgeId }}</code>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
v-if="props.collectionType === 'DOCUMENT'"
|
||
class="api-doc__section api-doc__import-flow"
|
||
>
|
||
<div class="api-doc__section-title">批量导入调用流程</div>
|
||
<ol class="api-doc__steps">
|
||
<li>提交 multipart 请求,保存 HTTP 202 响应中的 taskId。</li>
|
||
<li>使用 taskId 查询任务状态,按需分页查看每个文件的结果。</li>
|
||
<li>
|
||
当 canRetry=true 时,使用 taskId 重试全部异常文件,或通过 fileKeys
|
||
指定部分文件。
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<div class="api-doc__section">
|
||
<div class="api-doc__section-title">接口列表</div>
|
||
<div class="api-doc__endpoint-list">
|
||
<div
|
||
v-for="endpoint in endpointDocs"
|
||
:key="endpoint.path"
|
||
class="api-doc__endpoint"
|
||
>
|
||
<div class="api-doc__endpoint-main">
|
||
<span class="api-doc__method">{{ endpoint.method }}</span>
|
||
<code>{{ endpoint.path }}</code>
|
||
<span class="api-doc__hint">{{ endpoint.hint }}</span>
|
||
<span class="api-doc__permission">{{
|
||
endpoint.permission
|
||
}}</span>
|
||
<ElButton
|
||
text
|
||
circle
|
||
:icon="CopyDocument"
|
||
class="api-doc__copy"
|
||
:aria-label="`复制 ${endpoint.path}`"
|
||
:title="`复制 ${endpoint.url}`"
|
||
@click="copyEndpointUrl(endpoint.url)"
|
||
/>
|
||
</div>
|
||
<div class="api-doc__parameter-table-wrap">
|
||
<table
|
||
class="api-doc__parameter-table"
|
||
:aria-label="`${endpoint.hint}参数说明`"
|
||
>
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">参数</th>
|
||
<th scope="col">位置</th>
|
||
<th scope="col">类型</th>
|
||
<th scope="col">必填</th>
|
||
<th scope="col">说明</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr
|
||
v-for="param in endpoint.params"
|
||
:key="`${endpoint.path}-${param.location}-${param.name}`"
|
||
>
|
||
<td>
|
||
<code>{{ param.name }}</code>
|
||
</td>
|
||
<td>{{ parameterLocationLabels[param.location] }}</td>
|
||
<td>{{ getParameterType(param) }}</td>
|
||
<td>{{ param.required ? '是' : '否' }}</td>
|
||
<td>
|
||
<div>{{ getParameterDescription(param) }}</div>
|
||
<ul
|
||
v-if="param.enumValues?.length"
|
||
class="api-doc__enum-list"
|
||
>
|
||
<li
|
||
v-for="enumValue in param.enumValues"
|
||
:key="enumValue.value"
|
||
>
|
||
<code>{{ enumValue.value }}</code>
|
||
<span>{{ enumValue.description }}</span>
|
||
</li>
|
||
</ul>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<details
|
||
v-if="
|
||
endpoint.contentType ||
|
||
endpoint.notes?.length ||
|
||
endpoint.examples?.length
|
||
"
|
||
class="api-doc__endpoint-details"
|
||
:open="endpoint.openByDefault"
|
||
>
|
||
<summary>
|
||
{{
|
||
endpoint.openByDefault
|
||
? '必看:metadata、文件 Part 与完整示例'
|
||
: '查看请求与响应说明'
|
||
}}
|
||
</summary>
|
||
<div class="api-doc__endpoint-details-content">
|
||
<div
|
||
v-if="endpoint.contentType"
|
||
class="api-doc__content-type"
|
||
>
|
||
<span>Content-Type</span>
|
||
<code>{{ endpoint.contentType }}</code>
|
||
</div>
|
||
<ul v-if="endpoint.notes?.length" class="api-doc__notes">
|
||
<li v-for="note in endpoint.notes" :key="note">
|
||
{{ note }}
|
||
</li>
|
||
</ul>
|
||
<div
|
||
v-for="example in endpoint.examples"
|
||
:key="`${endpoint.path}-${example.title}`"
|
||
class="api-doc__example"
|
||
>
|
||
<div class="api-doc__example-title">
|
||
{{ example.title }}
|
||
</div>
|
||
<div class="api-doc__code-wrap">
|
||
<button
|
||
type="button"
|
||
class="api-doc__code-copy"
|
||
:title="`复制${example.title}`"
|
||
:aria-label="`复制${endpoint.hint}${example.title}`"
|
||
@click="copyApiExample(example.content)"
|
||
>
|
||
<ElIcon><CopyDocument /></ElIcon>
|
||
</button>
|
||
<pre class="api-doc__code">{{ example.content }}</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="api-doc__section">
|
||
<div class="api-doc__section-title">详情示例</div>
|
||
<div class="api-doc__code-wrap">
|
||
<button
|
||
type="button"
|
||
class="api-doc__code-copy"
|
||
title="复制详情示例"
|
||
aria-label="复制详情示例"
|
||
@click="copyApiExample(detailExample)"
|
||
>
|
||
<ElIcon><CopyDocument /></ElIcon>
|
||
</button>
|
||
<pre class="api-doc__code">{{ detailExample }}</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="api-doc__section">
|
||
<div class="api-doc__section-title">检索示例</div>
|
||
<div class="api-doc__code-wrap">
|
||
<button
|
||
type="button"
|
||
class="api-doc__code-copy"
|
||
title="复制检索示例"
|
||
aria-label="复制检索示例"
|
||
@click="copyApiExample(searchExample)"
|
||
>
|
||
<ElIcon><CopyDocument /></ElIcon>
|
||
</button>
|
||
<pre class="api-doc__code">{{ searchExample }}</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ElCard>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.share-management {
|
||
display: grid;
|
||
gap: 16px;
|
||
}
|
||
|
||
.share-card {
|
||
border-radius: 16px;
|
||
}
|
||
|
||
.share-card__header {
|
||
display: flex;
|
||
gap: 16px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.share-card__header--stack {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.share-card__title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.share-card__desc,
|
||
.share-result__meta {
|
||
margin-top: 6px;
|
||
font-size: 13px;
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
|
||
.share-result {
|
||
display: grid;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.share-result__row {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
}
|
||
|
||
.share-empty {
|
||
font-size: 13px;
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
|
||
.api-doc {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr);
|
||
gap: 18px;
|
||
}
|
||
|
||
.api-doc__meta {
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
|
||
.api-doc__item {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.api-doc__label {
|
||
min-width: 84px;
|
||
font-size: 13px;
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
|
||
.api-doc__value {
|
||
padding: 4px 10px;
|
||
font-size: 13px;
|
||
color: var(--el-text-color-primary);
|
||
background: var(--el-fill-color-light);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.api-doc__helper {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
|
||
.api-doc__section {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr);
|
||
gap: 10px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.api-doc__import-flow {
|
||
padding: 16px;
|
||
background: var(--el-fill-color-extra-light);
|
||
border-radius: var(--el-border-radius-base);
|
||
}
|
||
|
||
.api-doc__steps {
|
||
display: grid;
|
||
gap: 8px;
|
||
padding-left: 24px;
|
||
margin: 0;
|
||
font-size: 13px;
|
||
line-height: 1.7;
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
|
||
.api-doc__section-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
.api-doc__endpoint-list {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr);
|
||
gap: 8px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.api-doc__endpoint {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr);
|
||
gap: 6px;
|
||
min-width: 0;
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.api-doc__endpoint-main {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.api-doc__copy {
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.api-doc__method {
|
||
min-width: 42px;
|
||
font-weight: 600;
|
||
color: var(--el-color-primary);
|
||
}
|
||
|
||
.api-doc__hint {
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
|
||
.api-doc__permission {
|
||
padding: 2px 8px;
|
||
font-size: 12px;
|
||
color: var(--el-color-primary);
|
||
background: var(--el-color-primary-light-9);
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.api-doc__parameter-table-wrap {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
min-width: 0;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.api-doc__parameter-table {
|
||
width: 100%;
|
||
min-width: 640px;
|
||
font-size: 12px;
|
||
color: var(--el-text-color-regular);
|
||
table-layout: fixed;
|
||
border-spacing: 0;
|
||
border-collapse: separate;
|
||
border: 1px solid var(--el-border-color-lighter);
|
||
border-radius: var(--el-border-radius-base);
|
||
}
|
||
|
||
.api-doc__parameter-table th,
|
||
.api-doc__parameter-table td {
|
||
padding: 8px 12px;
|
||
text-align: left;
|
||
vertical-align: top;
|
||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
.api-doc__parameter-table th {
|
||
font-weight: 500;
|
||
color: var(--el-text-color-secondary);
|
||
white-space: nowrap;
|
||
background: var(--el-fill-color-extra-light);
|
||
}
|
||
|
||
.api-doc__parameter-table th:first-child,
|
||
.api-doc__parameter-table td:first-child {
|
||
width: 28%;
|
||
}
|
||
|
||
.api-doc__parameter-table th:nth-child(2),
|
||
.api-doc__parameter-table td:nth-child(2) {
|
||
width: 14%;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.api-doc__parameter-table th:nth-child(3),
|
||
.api-doc__parameter-table td:nth-child(3) {
|
||
width: 12%;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.api-doc__parameter-table th:nth-child(4),
|
||
.api-doc__parameter-table td:nth-child(4) {
|
||
width: 8%;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.api-doc__parameter-table tbody tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.api-doc__parameter-table td:last-child {
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.api-doc__parameter-table code {
|
||
white-space: normal;
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.api-doc__enum-list {
|
||
display: grid;
|
||
gap: 4px;
|
||
padding: 0;
|
||
margin: 6px 0 0;
|
||
list-style: none;
|
||
}
|
||
|
||
.api-doc__enum-list li {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.api-doc__enum-list code {
|
||
flex: 0 0 auto;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
.api-doc__endpoint-details {
|
||
margin-top: 4px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.api-doc__endpoint-details > summary {
|
||
width: fit-content;
|
||
color: var(--el-color-primary);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.api-doc__endpoint-details > summary:focus-visible {
|
||
border-radius: var(--el-border-radius-small);
|
||
outline: 2px solid var(--el-color-primary-light-5);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.api-doc__endpoint-details-content {
|
||
display: grid;
|
||
gap: 16px;
|
||
padding-top: 16px;
|
||
}
|
||
|
||
.api-doc__content-type {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
color: var(--el-text-color-secondary);
|
||
}
|
||
|
||
.api-doc__content-type code {
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
.api-doc__notes {
|
||
display: grid;
|
||
gap: 8px;
|
||
padding-left: 24px;
|
||
margin: 0;
|
||
line-height: 1.6;
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
|
||
.api-doc__example {
|
||
display: grid;
|
||
gap: 8px;
|
||
}
|
||
|
||
.api-doc__example-title {
|
||
font-weight: 500;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
.api-doc__code {
|
||
margin: 0;
|
||
padding: 14px 16px;
|
||
overflow-x: auto;
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
color: var(--el-text-color-primary);
|
||
white-space: pre;
|
||
background: var(--el-fill-color-light);
|
||
border-radius: 14px;
|
||
}
|
||
|
||
.api-doc__code-wrap {
|
||
position: relative;
|
||
}
|
||
|
||
.api-doc__code-copy {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 10px;
|
||
z-index: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 28px;
|
||
height: 28px;
|
||
padding: 0;
|
||
color: var(--el-text-color-secondary);
|
||
cursor: pointer;
|
||
background: transparent;
|
||
border: none;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.api-doc__code-copy:hover {
|
||
color: var(--el-color-primary);
|
||
background: var(--el-fill-color);
|
||
}
|
||
|
||
.api-doc__code-copy:focus-visible {
|
||
outline: 2px solid var(--el-color-primary-light-5);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.api-doc__code-wrap .api-doc__code {
|
||
padding-top: 40px;
|
||
}
|
||
</style>
|