重构知识库相关接口,更新数据结构和命名,移除示例文件,调整组件和视图以支持新命名,优化文档和数据集管理功能。

This commit is contained in:
2026-03-30 17:35:05 +08:00
parent 891f8ed776
commit d9b4a012ee
22 changed files with 1783 additions and 1115 deletions

111
src/api/cid/apis/index.ts Normal file
View File

@@ -0,0 +1,111 @@
import { newService } from '/@/utils/request';
// 接口查询参数
export interface ApiInterfaceQueryParams {
keyword?: string;
platformId?: string;
status?: string;
method?: string;
pageNum: number;
pageSize: number;
}
// 创建接口参数
export interface CreateApiInterfaceParams {
platformId: string | number;
name: string;
code: string;
url: string;
method: string;
status?: string;
authType?: string;
requestConfig?: Record<string, any>;
responseConfig?: Record<string, any>;
limitConfig?: Record<string, any>;
}
// 更新接口参数
export interface UpdateApiInterfaceParams extends Partial<CreateApiInterfaceParams> {
id: string;
}
// 更新接口状态参数
export interface UpdateApiInterfaceStatusParams {
id: string;
status: string;
}
// 接口信息(响应字段)
export interface ApiInterfaceInfo {
id: string;
platformId: string | number;
platformName?: string;
name: string;
code: string;
url: string;
method: string;
status: string;
statusName?: string;
authType?: string;
requestConfig?: Record<string, any>;
responseConfig?: Record<string, any>;
limitConfig?: Record<string, any>;
createdBy?: string;
createdAt?: number;
updatedBy?: string;
updatedAt?: number;
}
// 获取接口列表
export function listApiInterfaces(params: ApiInterfaceQueryParams) {
return newService({
url: '/api/interface/controller/listApiInterfaces',
method: 'get',
params,
});
}
// 获取接口详情
export function getApiInterface(id: string) {
return newService({
url: '/api/interface/controller/getApiInterface',
method: 'get',
params: { id },
});
}
// 创建接口
export function createApiInterface(data: CreateApiInterfaceParams) {
return newService({
url: '/api/interface/controller/createApiInterface',
method: 'post',
data,
});
}
// 修改接口
export function updateApiInterface(data: UpdateApiInterfaceParams) {
return newService({
url: '/api/interface/controller/updateApiInterface',
method: 'put',
data,
});
}
// 更新接口状态
export function updateApiInterfaceStatus(data: UpdateApiInterfaceStatusParams) {
return newService({
url: '/api/interface/controller/updateApiInterfaceStatus',
method: 'put',
data,
});
}
// 删除接口
export function deleteApiInterface(id: string) {
return newService({
url: '/api/interface/controller/deleteApiInterface',
method: 'delete',
params: { id },
});
}

View File

@@ -0,0 +1,130 @@
import { newService } from '/@/utils/request';
// 平台查询参数
export interface DatasourcePlatformQueryParams {
keyword?: string;
status?: string;
pageNum: number;
pageSize: number;
}
// 创建平台参数
export interface CreateDatasourcePlatformParams {
platformCode: string;
platformName: string;
description?: string;
apiBaseUrl: string;
authType: string;
status?: string;
token?: string;
apiKey?: string;
clientId?: string;
clientSecret?: string;
rateLimitPerMinute?: number;
rateLimitPerHour?: number;
concurrencyLimit?: number;
requestTimeoutMs?: number;
maxRetries?: number;
retryDelayMs?: number;
createdBy?: string;
updatedBy?: string;
}
// 更新平台参数
export interface UpdateDatasourcePlatformParams extends Partial<CreateDatasourcePlatformParams> {
id: string;
version?: string;
}
// 更新平台状态参数
export interface UpdateDatasourcePlatformStatusParams {
id: string;
Status: string;
updatedBy?: string;
}
// 平台信息
export interface DatasourcePlatformInfo {
id: string;
platformCode: string;
platformName: string;
description?: string;
status: string;
statusName: string;
apiBaseUrl: string;
authType: string;
authTypeName: string;
rateLimitPerMinute?: number;
rateLimitPerHour?: number;
concurrencyLimit?: number;
requestTimeoutMs?: number;
maxRetries?: number;
retryDelayMs?: number;
createdBy?: string;
createdAt?: number;
updatedBy?: string;
updatedAt?: number;
}
// 获取平台列表
export function listDatasourcePlatforms(params: DatasourcePlatformQueryParams) {
return newService({
url: '/datasource/platform/controller/listDatasourcePlatforms',
method: 'get',
params,
});
}
// 创建平台
export function createDatasourcePlatform(data: CreateDatasourcePlatformParams) {
return newService({
url: '/datasource/platform/controller/createDatasourcePlatform',
method: 'post',
data,
});
}
// 更新平台
export function updateDatasourcePlatform(data: UpdateDatasourcePlatformParams) {
return newService({
url: '/datasource/platform/controller/updateDatasourcePlatform',
method: 'put',
data,
});
}
// 删除平台
export function deleteDatasourcePlatform(id: string) {
return newService({
url: '/datasource/platform/controller/deleteDatasourcePlatform',
method: 'delete',
params: { id },
});
}
// 获取平台详情
export function getDatasourcePlatform(id: string) {
return newService({
url: '/datasource/platform/controller/getDatasourcePlatform',
method: 'get',
params: { id },
});
}
// 根据编码获取平台信息
export function getPlatformByCode(platformCode: string) {
return newService({
url: '/datasource/platform/controller/getPlatformByCode',
method: 'get',
params: { platformCode },
});
}
// 更新平台状态
export function updateDatasourcePlatformStatus(data: UpdateDatasourcePlatformStatusParams) {
return newService({
url: '/datasource/platform/controller/updateDatasourcePlatformStatus',
method: 'put',
data,
});
}

162
src/api/cid/field/index.ts Normal file
View File

@@ -0,0 +1,162 @@
import { newService } from '/@/utils/request';
// 字典映射查询参数
export interface FieldMappingQueryParams {
configName?: string;
vendorName?: string;
apiName?: string;
businessDomain?: string;
isActive?: boolean;
pageNum?: number;
pageSize?: number;
}
// 创建字典映射参数
export interface CreateFieldMappingParams {
configName: string;
vendorName: string;
apiName: string;
apiVersion?: string;
sourceField: string;
sourceFieldType?: string;
sourceFieldDesc?: string;
targetField: string;
targetFieldType?: string;
targetFieldDesc?: string;
transformType?: string;
transformParams?: any;
validationRules?: any;
defaultValue?: string;
isRequired?: boolean;
isActive?: boolean;
priority?: number;
businessDomain?: string;
fieldGroup?: string;
configVersion?: number;
effectiveDate?: string;
expiryDate?: string;
createdBy?: string;
}
// 更新字典映射参数
export interface UpdateFieldMappingParams extends Partial<CreateFieldMappingParams> {
id: string;
}
// 字典映射信息(响应字段)
export interface FieldMappingInfo {
id: string;
configName: string;
vendorName: string;
apiName: string;
apiVersion?: string;
sourceField: string;
sourceFieldType?: string;
sourceFieldDesc?: string;
targetField: string;
targetFieldType?: string;
targetFieldDesc?: string;
transformType?: string;
transformTypeName?: string;
transformParams?: any;
validationRules?: any;
defaultValue?: string;
isRequired?: boolean;
isActive?: boolean;
priority?: number;
businessDomain?: string;
businessDomainName?: string;
fieldGroup?: string;
configVersion?: number;
effectiveDate?: string;
expiryDate?: string;
createdBy?: string;
createdTime?: string;
updatedBy?: string;
updatedTime?: string;
}
// 查询字典映射列表
export function listFieldMappingConfigs(params: FieldMappingQueryParams) {
return newService({
url: '/field/mapping/config/controller/field-mapping-configs',
method: 'get',
params,
});
}
// 创建字典映射
export function createFieldMappingConfig(data: CreateFieldMappingParams) {
return newService({
url: '/field/mapping/config/controller/field-mapping-configs',
method: 'post',
data,
});
}
// 根据ID查询字典映射详情
export function getFieldMappingConfig(id: string) {
return newService({
url: `/field/mapping/config/controller/field-mapping-configs/${id}`,
method: 'get',
});
}
// 根据ID修改字典映射
export function updateFieldMappingConfig(id: string, data: UpdateFieldMappingParams) {
return newService({
url: `/field/mapping/config/controller/field-mapping-configs/${id}`,
method: 'put',
data,
});
}
// 根据ID修改字典映射状态
export function updateFieldMappingConfigStatus(id: string, isActive: boolean) {
return newService({
url: `/field/mapping/config/controller/field-mapping-configs/${id}/status`,
method: 'put',
data: { id, isActive },
});
}
// 根据ID删除字典映射
export function deleteFieldMappingConfig(id: string) {
return newService({
url: `/field/mapping/config/controller/field-mapping-configs/${id}`,
method: 'delete',
});
}
// 根据厂商和接口查询字段映射(过滤过期)
export interface QueryFieldMappingParams {
vendorName: string;
apiName: string;
apiVersion?: string;
isActive?: boolean;
}
export function queryFieldMappingConfigs(params: QueryFieldMappingParams) {
return newService({
url: '/field/mapping/config/controller/field-mapping-configs/query',
method: 'get',
params,
});
}
// 验证字段映射配置的有效性
export interface ValidateFieldMappingParams {
configName: string;
vendorName: string;
apiName: string;
sourceField: string;
targetField: string;
}
export function validateFieldMappingConfig(data: ValidateFieldMappingParams) {
return newService({
url: '/field/mapping/config/controller/field-mapping-configs/validate',
method: 'post',
data,
});
}

View File

@@ -1,113 +0,0 @@
// ⚠️ 示例文件:仅用于接口调用演示,不参与生产运行。
// 知识库接口使用示例
import {
createKnowledgeBase,
CreateDatasetParams,
updateKnowledgeBase,
UpdateDatasetParams
} from '/@/api/knowledge/dataset';
// 示例1创建带描述的知识库
const createKnowledgeBaseWithDescription = async () => {
const params: CreateDatasetParams = {
name: '客服知识库',
description: '包含常见问题和答案的知识库'
};
try {
const response = await createKnowledgeBase(params);
console.log('知识库创建成功:', response.data);
} catch (error) {
console.error('创建失败:', error);
}
};
// 示例2创建仅包含名称的知识库
const createKnowledgeBaseOnly = async () => {
const params: CreateDatasetParams = {
name: '产品知识库'
// description 是可选的,可以不传
};
try {
const response = await createKnowledgeBase(params);
console.log('知识库创建成功:', response.data);
} catch (error) {
console.error('创建失败:', error);
}
};
// 示例3更新知识库名称和描述
const updateKnowledgeBaseInfo = async () => {
const params: UpdateDatasetParams = {
id: '1234567890', // 必传
name: '更新后的知识库名称',
description: '更新后的描述信息'
};
try {
const response = await updateKnowledgeBase(params);
console.log('知识库更新成功:', response.data);
} catch (error) {
console.error('更新失败:', error);
}
};
// 示例4仅更新知识库名称
const updateKnowledgeBaseName = async () => {
const params: UpdateDatasetParams = {
id: '1234567890', // 必传
name: '仅更新名称'
// description 是可选的,可以不传
};
try {
const response = await updateKnowledgeBase(params);
console.log('知识库名称更新成功:', response.data);
} catch (error) {
console.error('更新失败:', error);
}
};
// 在Vue组件中使用
/*
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { createKnowledgeBase, CreateDatasetParams } from '/@/api/knowledge/dataset';
export default defineComponent({
setup() {
const formData = ref<CreateDatasetParams>({
name: '',
description: ''
});
const loading = ref(false);
const handleSubmit = async () => {
if (!formData.value.name.trim()) {
ElMessage.error('知识库名称不能为空');
return;
}
loading.value = true;
try {
await createKnowledgeBase(formData.value);
ElMessage.success('知识库创建成功');
// 重置表单或跳转到列表页
formData.value = { name: '', description: '' };
} catch (error) {
ElMessage.error('创建失败,请重试');
} finally {
loading.value = false;
}
};
return {
formData,
loading,
handleSubmit
};
}
});
*/

View File

@@ -1,108 +1,68 @@
import { newService } from '/@/utils/request';
// 数据集查询参数
export interface DatasetQueryParams {
export interface knowledgeQueryParams {
keyword?: string;
status?: string;
pageNum: number;
pageSize: number;
}
// 创建知识库参数
export interface CreateDatasetParams {
export interface CreateknowledgeParams {
name: string; // 必传
description?: string; // 可选
}
// 更新知识库参数
export interface UpdateDatasetParams {
export interface UpdateknowledgeParams {
id: string; // 必传
name?: string; // 可选
description?: string; // 可选
}
// 数据集信息
export interface DatasetInfo {
export interface knowledgeInfo {
id?: string;
name: string;
description?: string;
type: string; // text, qa, table
documentCount?: number;
charCount?: number;
status: string; // enable, disable
embeddingModel?: string;
fileCount?: number;
totalSize?: number;
createdAt?: string;
updatedAt?: string;
}
// 获取数据集列表
export function listDatasets(params: DatasetQueryParams) {
// 获取知识库列表
export function listknowledges(params: knowledgeQueryParams) {
return newService({
url: '/rag-knowledge/knowledge/listKnowledge',
url: '/rag-knowledge/dataset/listDataset',
method: 'get',
params,
});
}
// 获取数据集详情
export function getDataset(id: string) {
// 创建知识库
export function createknowledge(data: CreateknowledgeParams) {
return newService({
url: '/knowledge/dataset/detail',
method: 'get',
params: { id },
});
}
// 创建数据集(简化版)
export function createKnowledgeBase(data: CreateDatasetParams) {
return newService({
url: '/rag-knowledge/knowledge/createKnowledge',
url: '/rag-knowledge/dataset/createDataset',
method: 'post',
data,
});
}
// 创建数据集(完整版)
export function createDataset(data: DatasetInfo) {
// 更新知识库
export function updateknowledge(data: UpdateknowledgeParams) {
return newService({
url: '/knowledge/dataset/create',
method: 'post',
data,
});
}
// 更新知识库(简化版)
export function updateKnowledgeBase(data: UpdateDatasetParams) {
return newService({
url: '/rag-knowledge/knowledge/updateKnowledge',
url: '/rag-knowledge/dataset/updateDataset',
method: 'put',
data,
});
}
// 更新数据集(完整版)
export function updateDataset(data: DatasetInfo) {
// 删除知识库
export function deleteknowledge(id: string) {
return newService({
url: '/knowledge/dataset/update',
method: 'put',
data,
});
}
// 删除数据集
export function deleteDataset(id: string) {
return newService({
url: '/rag-knowledge/knowledge/deleteKnowledge',
url: '/rag-knowledge/dataset/deleteDataset',
method: 'delete',
params: { id },
});
}
// 更新数据集状态
export function updateDatasetStatus(data: { id: string; status: string }) {
return newService({
url: '/knowledge/dataset/updateStatus',
method: 'put',
data,
});
}

View File

@@ -1,238 +0,0 @@
// ⚠️ 示例文件:仅用于接口调用演示,不参与生产运行。
// 文档接口使用示例
import {
createDocument,
CreateDocumentParams,
updateDocument,
UpdateDocumentParams,
uploadDocument,
deleteDocument,
listDocuments,
DocumentQueryParams
} from '/@/api/knowledge/document';
// 示例1创建文档JSON格式
const createDocumentExample = async () => {
const params: CreateDocumentParams = {
KnowledgeId: 12345, // 知识库ID必传
filePath: '/path/to/document.pdf', // 文件路径,必传
fileSize: 1024000, // 文件大小(字节),必传
format: 'pdf', // 文件格式,必传
title: '产品使用手册' // 文档标题,必传
};
try {
const response = await createDocument(params);
console.log('文档创建成功:', response.data);
return response.data;
} catch (error) {
console.error('创建失败:', error);
throw error;
}
};
// 示例2上传文档FormData格式
const uploadDocumentExample = async (file: File, knowledgeId: string) => {
const formData = new FormData();
formData.append('file', file);
formData.append('datasetId', knowledgeId);
try {
const response = await uploadDocument(formData);
console.log('文档上传成功:', response.data);
return response.data;
} catch (error) {
console.error('上传失败:', error);
throw error;
}
};
// 示例3获取文档列表
const getDocumentList = async (knowledgeId: string) => {
const params: DocumentQueryParams = {
datasetId: knowledgeId,
pageNum: 1,
pageSize: 10,
keyword: '', // 可选搜索关键字
status: '', // 可选状态筛选
fileType: '' // 可选文件类型筛选
};
try {
const response = await listDocuments(params);
console.log('文档列表:', response.data);
return response.data;
} catch (error) {
console.error('获取列表失败:', error);
throw error;
}
};
// 示例4更新文档
const updateDocumentExample = async () => {
const params: UpdateDocumentParams = {
id: '1234567890', // 必传
title: '更新后的文档标题' // 可选,只更新标题
};
try {
const response = await updateDocument(params);
console.log('文档更新成功:', response.data);
return response.data;
} catch (error) {
console.error('更新失败:', error);
throw error;
}
};
// 示例5完整更新文档
const updateDocumentFull = async () => {
const params: UpdateDocumentParams = {
id: '1234567890', // 必传
KnowledgeId: 67890, // 可选
filePath: '/new/path/document.pdf', // 可选
fileSize: 2048000, // 可选
format: 'pdf', // 可选
title: '完全更新的文档' // 可选
};
try {
const response = await updateDocument(params);
console.log('文档完整更新成功:', response.data);
return response.data;
} catch (error) {
console.error('更新失败:', error);
throw error;
}
};
// 示例6删除文档
const deleteDocumentExample = async (documentId: string) => {
try {
await deleteDocument(documentId);
console.log('文档删除成功');
} catch (error) {
console.error('删除失败:', error);
throw error;
}
};
// 在Vue组件中使用
/*
import { ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import {
createDocument,
CreateDocumentParams,
uploadDocument,
deleteDocument,
listDocuments,
DocumentQueryParams
} from '/@/api/knowledge/document';
export default defineComponent({
setup() {
const formData = ref<CreateDocumentParams>({
KnowledgeId: 0,
filePath: '',
fileSize: 0,
format: '',
title: ''
});
const loading = ref(false);
const documentList = ref([]);
// 创建文档
const handleCreate = async () => {
// 验证必填字段
if (!formData.value.KnowledgeId || !formData.value.filePath ||
!formData.value.fileSize || !formData.value.format || !formData.value.title) {
ElMessage.error('请填写所有必填字段');
return;
}
loading.value = true;
try {
await createDocument(formData.value);
ElMessage.success('文档创建成功');
// 重置表单
formData.value = {
KnowledgeId: 0,
filePath: '',
fileSize: 0,
format: '',
title: ''
};
// 刷新列表
await getDocuments();
} catch (error) {
ElMessage.error('创建失败,请重试');
} finally {
loading.value = false;
}
};
// 文件上传
const handleFileUpload = async (event: Event, knowledgeId: string) => {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
formData.append('datasetId', knowledgeId);
loading.value = true;
try {
await uploadDocument(formData);
ElMessage.success('文档上传成功');
await getDocuments();
} catch (error) {
ElMessage.error('上传失败,请重试');
} finally {
loading.value = false;
}
};
// 删除文档
const handleDelete = async (id: string) => {
try {
await ElMessageBox.confirm('确定要删除这个文档吗?', '提示', {
type: 'warning',
});
await deleteDocument(id);
ElMessage.success('删除成功');
await getDocuments();
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败');
}
}
};
// 获取文档列表
const getDocuments = async () => {
try {
const response = await listDocuments({
pageNum: 1,
pageSize: 10
});
documentList.value = response.data.list;
} catch (error) {
console.error('获取列表失败:', error);
}
};
return {
formData,
loading,
documentList,
handleCreate,
handleFileUpload,
handleDelete,
getDocuments
};
}
});
*/

View File

@@ -4,15 +4,13 @@ import { newService } from '/@/utils/request';
export interface DocumentQueryParams {
keyword?: string;
datasetId?: string;
status?: string;
fileType?: string;
pageNum: number;
pageSize: number;
}
// 创建文档参数
export interface CreateDocumentParams {
KnowledgeId: number; // 必传
datasetId: string; // 必传
filePath: string; // 必传
fileSize: number; // 必传
format: string; // 必传
@@ -22,11 +20,11 @@ export interface CreateDocumentParams {
// 更新文档参数
export interface UpdateDocumentParams {
id: string; // 必传
KnowledgeId?: number; // 可选
filePath?: string; // 可选
fileSize?: number; // 可选
format?: string; // 可选
title?: string; // 可选
datasetId?: string;
filePath?: string;
fileSize?: number;
format?: string;
title?: string;
}
// 文档信息
@@ -34,35 +32,20 @@ export interface DocumentInfo {
id?: string;
name: string;
datasetId: string;
datasetName?: string;
fileType: string; // pdf, docx, txt, md, html
fileType: string;
fileSize?: number;
filePath?: string;
charCount?: number;
chunkCount?: number;
status: string; // pending, processing, completed, failed
indexStatus?: string; // not_indexed, indexing, indexed, failed
errorMessage?: string;
parseStatus?: string;
enabled?: boolean;
createdAt?: string;
updatedAt?: string;
}
// 文档分段信息
export interface DocumentChunk {
id: string;
documentId: string;
content: string;
chunkIndex: number;
charCount: number;
tokenCount?: number;
embedding?: number[];
createdAt?: string;
}
// 获取文档列表
export function listDocuments(params: DocumentQueryParams) {
return newService({
url: '/knowledge/document/list',
url: '/rag-knowledge/document/listDocument',
method: 'get',
params,
});
@@ -71,16 +54,16 @@ export function listDocuments(params: DocumentQueryParams) {
// 获取文档详情
export function getDocument(id: string) {
return newService({
url: '/knowledge/document/detail',
url: '/rag-knowledge/document/getDocument',
method: 'get',
params: { id },
});
}
// 创建文档JSON格式
// 创建文档
export function createDocument(data: CreateDocumentParams) {
return newService({
url: '/knowledge/document/create',
url: '/rag-knowledge/document/createDocument',
method: 'post',
data,
});
@@ -89,16 +72,28 @@ export function createDocument(data: CreateDocumentParams) {
// 更新文档
export function updateDocument(data: UpdateDocumentParams) {
return newService({
url: '/knowledge/document/update',
url: '/rag-knowledge/document/updateDocument',
method: 'put',
data,
});
}
// 公共文件上传OSS返回文件路径
export function uploadFile(file: File) {
const formData = new FormData();
formData.append('file', file);
return newService({
url: '/oss/file/uploadFile',
method: 'post',
data: formData,
headers: { 'Content-Type': 'multipart/form-data' },
});
}
// 上传文档
export function uploadDocument(data: FormData) {
return newService({
url: '/knowledge/document/upload',
url: '/rag-knowledge/document/createDocument',
method: 'post',
data,
headers: {
@@ -110,61 +105,16 @@ export function uploadDocument(data: FormData) {
// 删除文档
export function deleteDocument(id: string) {
return newService({
url: '/knowledge/document/delete',
url: '/rag-knowledge/document/deleteDocument',
method: 'delete',
params: { id },
});
}
// 批量删除文档
export function batchDeleteDocuments(ids: string[]) {
// 获取文件向量化处理进度
export function getDocumentProcess(id: string) {
return newService({
url: '/knowledge/document/batchDelete',
method: 'delete',
data: { ids },
});
}
// 重新处理文档
export function reprocessDocument(id: string) {
return newService({
url: '/knowledge/document/reprocess',
method: 'post',
params: { id },
});
}
// 获取文档分段列表
export function listDocumentChunks(params: { documentId: string; pageNum: number; pageSize: number }) {
return newService({
url: '/knowledge/document/chunks',
method: 'get',
params,
});
}
// 更新文档分段
export function updateDocumentChunk(data: { id: string; content: string }) {
return newService({
url: '/knowledge/document/chunk/update',
method: 'put',
data,
});
}
// 删除文档分段
export function deleteDocumentChunk(id: string) {
return newService({
url: '/knowledge/document/chunk/delete',
method: 'delete',
params: { id },
});
}
// 预览文档内容
export function previewDocument(id: string) {
return newService({
url: '/knowledge/document/preview',
url: '/rag-knowledge/document/getProcess',
method: 'get',
params: { id },
});

View File

@@ -76,7 +76,7 @@ export const lazyImg = (el: any, arr: any) => {
const io = new IntersectionObserver((res) => {
res.forEach((v: any) => {
if (v.isIntersecting) {
const { img, key } = v.target.dataset;
const { img, key } = v.target.knowledge;
v.target.src = img;
v.target.onload = () => {
io.unobserve(v.target);

View File

@@ -0,0 +1,219 @@
<template>
<div class="cid-apis-container">
<el-card shadow="hover">
<div class="system-user-search mb15">
<el-form :model="tableData.param" ref="queryRef" :inline="true" label-width="68px">
<el-form-item label="关键字" prop="keyword">
<el-input
v-model="tableData.param.keyword"
placeholder="请输入接口名称"
clearable
size="default"
style="width: 240px"
@keyup.enter.native="getList"
/>
</el-form-item>
<el-form-item>
<el-button size="default" type="primary" @click="getList">
<el-icon><ele-Search /></el-icon>
查询
</el-button>
<el-button size="default" @click="resetQuery(queryRef)">
<el-icon><ele-Refresh /></el-icon>
重置
</el-button>
<el-button size="default" type="success" @click="onOpenAdd">
<el-icon><ele-FolderAdd /></el-icon>
新增接口
</el-button>
<el-button size="default" type="danger" @click="onRowDel(null)">
<el-icon><ele-Delete /></el-icon>
删除
</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="name" label="接口名称" show-overflow-tooltip />
<el-table-column prop="path" label="接口路径" show-overflow-tooltip />
<el-table-column prop="method" label="请求方式" width="100" align="center" />
<el-table-column prop="datasourceName" label="所属平台" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'" size="small">
{{ scope.row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" show-overflow-tooltip />
<el-table-column label="操作" width="180">
<template #default="scope">
<el-button size="small" text type="primary" @click="onOpenEdit(scope.row)">修改</el-button>
<el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="tableData.total > 0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="getList"
/>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" :close-on-click-modal="false">
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
<el-form-item label="接口名称" prop="name">
<el-input v-model="form.name" placeholder="请输入接口名称" />
</el-form-item>
<el-form-item label="接口路径" prop="path">
<el-input v-model="form.path" placeholder="请输入接口路径" />
</el-form-item>
<el-form-item label="请求方式" prop="method">
<el-select v-model="form.method" style="width: 100%">
<el-option label="GET" value="GET" />
<el-option label="POST" value="POST" />
<el-option label="PUT" value="PUT" />
<el-option label="DELETE" value="DELETE" />
</el-select>
</el-form-item>
<el-form-item label="所属平台" prop="datasourceId">
<el-input v-model="form.datasourceId" placeholder="请输入所属平台ID" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入描述" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" style="width: 100%">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialog.visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :loading="dialog.saving">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
export default { name: 'cidApis' };
</script>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
const queryRef = ref<FormInstance>();
const formRef = ref<FormInstance>();
const ids = ref<string[]>([]);
const tableData = reactive({
data: [] as any[],
total: 0,
param: {
pageNum: 1,
pageSize: 10,
keyword: '',
},
});
const dialog = reactive({
visible: false,
title: '',
saving: false,
});
const form = reactive({
id: '',
name: '',
path: '',
method: 'GET',
datasourceId: '',
description: '',
status: 1,
});
const rules = {
name: [{ required: true, message: '请输入接口名称', trigger: 'blur' }],
path: [{ required: true, message: '请输入接口路径', trigger: 'blur' }],
method: [{ required: true, message: '请选择请求方式', trigger: 'change' }],
};
const getList = () => {
// TODO: 调用列表接口
};
const resetQuery = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
getList();
};
const handleSelectionChange = (selection: any[]) => {
ids.value = selection.map((item) => item.id);
};
const onOpenAdd = () => {
dialog.title = '新增接口';
dialog.visible = true;
form.id = '';
form.name = '';
form.path = '';
form.method = 'GET';
form.datasourceId = '';
form.description = '';
form.status = 1;
};
const onOpenEdit = (row: any) => {
dialog.title = '修改接口';
dialog.visible = true;
form.id = row.id;
form.name = row.name;
form.path = row.path;
form.method = row.method;
form.datasourceId = row.datasourceId;
form.description = row.description;
form.status = row.status;
};
const onSubmit = () => {
if (!formRef.value) return;
formRef.value.validate((valid) => {
if (!valid) return;
dialog.saving = true;
// TODO: 调用新增/编辑接口
dialog.saving = false;
dialog.visible = false;
getList();
});
};
const onRowDel = (row: any) => {
const delIds = row ? [row.id] : ids.value;
if (delIds.length === 0) {
ElMessage.error('请选择要删除的数据');
return;
}
ElMessageBox.confirm(`确定要删除选中的接口吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
// TODO: 调用删除接口
ElMessage.success('删除成功');
getList();
}).catch(() => {});
};
onMounted(() => {
getList();
});
</script>

View File

@@ -0,0 +1,355 @@
<template>
<div class="cid-datasource-container">
<el-card shadow="hover">
<div class="system-user-search mb15">
<el-form :model="tableData.param" ref="queryRef" :inline="true" label-width="68px">
<el-form-item label="关键字" prop="keyword">
<el-input
v-model="tableData.param.keyword"
placeholder="请输入平台名称"
clearable
size="default"
style="width: 240px"
@keyup.enter.native="getList"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="tableData.param.status" placeholder="平台状态" clearable size="default" style="width: 160px">
<el-option label="启用" value="ACTIVE" />
<el-option label="禁用" value="INACTIVE" />
</el-select>
</el-form-item>
<el-form-item>
<el-button size="default" type="primary" @click="getList">
<el-icon><ele-Search /></el-icon>
查询
</el-button>
<el-button size="default" @click="resetQuery(queryRef)">
<el-icon><ele-Refresh /></el-icon>
重置
</el-button>
<el-button size="default" type="success" @click="onOpenAdd">
<el-icon><ele-FolderAdd /></el-icon>
新增平台
</el-button>
<el-button size="default" type="danger" @click="onRowDel(null)">
<el-icon><ele-Delete /></el-icon>
删除
</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="platformName" label="平台名称" show-overflow-tooltip />
<el-table-column prop="platformCode" label="平台编码" show-overflow-tooltip />
<el-table-column prop="apiBaseUrl" label="接口基础URL" show-overflow-tooltip />
<el-table-column prop="authTypeName" label="认证方式" width="130" show-overflow-tooltip />
<el-table-column prop="rateLimitPerMinute" label="限频(次/分)" width="110" align="center" />
<el-table-column prop="statusName" label="状态" width="90" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === 'ACTIVE' ? 'success' : 'danger'" size="small">
{{ scope.row.status === 'ACTIVE' ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" show-overflow-tooltip />
<el-table-column label="创建时间" width="160">
<template #default="scope">
{{ scope.row.createdAt ? formatTime(scope.row.createdAt) : '' }}
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="scope">
<el-button size="small" text type="primary" @click="onOpenEdit(scope.row)">修改</el-button>
<el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="tableData.total > 0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="getList"
/>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" :close-on-click-modal="false">
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="平台名称" prop="platformName">
<el-input v-model="form.platformName" placeholder="请输入平台名称" />
</el-form-item>
<el-form-item label="平台编码" prop="platformCode">
<el-input v-model="form.platformCode" placeholder="请输入平台编码,如 ALIYUN_API" />
</el-form-item>
<el-form-item label="接口基础URL" prop="apiBaseUrl">
<el-input v-model="form.apiBaseUrl" placeholder="请输入接口基础URL" />
</el-form-item>
<el-form-item label="认证方式" prop="authType">
<el-select v-model="form.authType" placeholder="请选择认证方式" style="width: 100%">
<el-option label="API Key认证" value="API_KEY" />
<el-option label="OAuth2" value="OAUTH2" />
<el-option label="Bearer Token" value="BEARER_TOKEN" />
<el-option label="无认证" value="NONE" />
</el-select>
</el-form-item>
<el-form-item label="API Key" prop="apiKey" v-if="form.authType === 'API_KEY'">
<el-input v-model="form.apiKey" placeholder="请输入 API Key" show-password />
</el-form-item>
<el-form-item label="Token" prop="token" v-if="form.authType === 'BEARER_TOKEN'">
<el-input v-model="form.token" placeholder="请输入 Bearer Token" show-password />
</el-form-item>
<el-form-item label="Client ID" prop="clientId" v-if="form.authType === 'OAUTH2'">
<el-input v-model="form.clientId" placeholder="请输入 Client ID" />
</el-form-item>
<el-form-item label="Client Secret" prop="clientSecret" v-if="form.authType === 'OAUTH2'">
<el-input v-model="form.clientSecret" placeholder="请输入 Client Secret" show-password />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" style="width: 100%">
<el-option label="启用" value="ACTIVE" />
<el-option label="禁用" value="INACTIVE" />
</el-select>
</el-form-item>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="限频(次/分)" prop="rateLimitPerMinute">
<el-input-number v-model="form.rateLimitPerMinute" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="限频(次/时)" prop="rateLimitPerHour">
<el-input-number v-model="form.rateLimitPerHour" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="并发限制" prop="concurrencyLimit">
<el-input-number v-model="form.concurrencyLimit" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="超时(ms)" prop="requestTimeoutMs">
<el-input-number v-model="form.requestTimeoutMs" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="最大重试次数" prop="maxRetries">
<el-input-number v-model="form.maxRetries" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="重试延迟(ms)" prop="retryDelayMs">
<el-input-number v-model="form.retryDelayMs" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="描述" prop="description">
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialog.visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :loading="dialog.saving">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
export default { name: 'cidDatasource' };
</script>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { listDatasourcePlatforms, createDatasourcePlatform, updateDatasourcePlatform, deleteDatasourcePlatform } from '/@/api/cid/datasource';
const queryRef = ref<FormInstance>();
const formRef = ref<FormInstance>();
const ids = ref<string[]>([]);
const tableData = reactive({
data: [] as any[],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
keyword: '',
status: '',
},
});
const dialog = reactive({
visible: false,
title: '',
saving: false,
isEdit: false,
});
const form = reactive({
id: '',
platformName: '',
platformCode: '',
apiBaseUrl: '',
authType: 'API_KEY',
status: 'ACTIVE',
description: '',
token: '',
apiKey: '',
clientId: '',
clientSecret: '',
rateLimitPerMinute: 200,
rateLimitPerHour: 10000,
concurrencyLimit: 50,
requestTimeoutMs: 15000,
maxRetries: 3,
retryDelayMs: 300,
});
const rules = {
platformName: [{ required: true, message: '请输入平台名称', trigger: 'blur' }],
platformCode: [{ required: true, message: '请输入平台编码', trigger: 'blur' }],
apiBaseUrl: [{ required: true, message: '请输入接口基础URL', trigger: 'blur' }],
authType: [{ required: true, message: '请选择认证方式', trigger: 'change' }],
};
const formatTime = (ts: number) => {
if (!ts) return '';
const d = new Date(ts * 1000);
return d.toLocaleString('zh-CN', { hour12: false });
};
const getList = async () => {
tableData.loading = true;
try {
const res = await listDatasourcePlatforms({
pageNum: tableData.param.pageNum,
pageSize: tableData.param.pageSize,
...(tableData.param.keyword ? { keyword: tableData.param.keyword } : {}),
...(tableData.param.status ? { status: tableData.param.status } : {}),
});
tableData.data = res.data?.list || [];
tableData.total = res.data?.total || 0;
} catch (_e) {
ElMessage.error('获取平台列表失败');
} finally {
tableData.loading = false;
}
};
const resetQuery = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
getList();
};
const handleSelectionChange = (selection: any[]) => {
ids.value = selection.map((item) => item.id);
};
const resetForm = () => {
form.id = '';
form.platformName = '';
form.platformCode = '';
form.apiBaseUrl = '';
form.authType = 'API_KEY';
form.status = 'ACTIVE';
form.description = '';
form.token = '';
form.apiKey = '';
form.clientId = '';
form.clientSecret = '';
form.rateLimitPerMinute = 200;
form.rateLimitPerHour = 10000;
form.concurrencyLimit = 50;
form.requestTimeoutMs = 15000;
form.maxRetries = 3;
form.retryDelayMs = 300;
};
const onOpenAdd = () => {
resetForm();
dialog.title = '新增平台';
dialog.isEdit = false;
dialog.visible = true;
};
const onOpenEdit = (row: any) => {
dialog.title = '修改平台';
dialog.isEdit = true;
dialog.visible = true;
form.id = row.id;
form.platformName = row.platformName;
form.platformCode = row.platformCode;
form.apiBaseUrl = row.apiBaseUrl;
form.authType = row.authType;
form.status = row.status;
form.description = row.description || '';
form.token = row.token || '';
form.apiKey = row.apiKey || '';
form.clientId = row.clientId || '';
form.clientSecret = row.clientSecret || '';
form.rateLimitPerMinute = row.rateLimitPerMinute ?? 200;
form.rateLimitPerHour = row.rateLimitPerHour ?? 10000;
form.concurrencyLimit = row.concurrencyLimit ?? 50;
form.requestTimeoutMs = row.requestTimeoutMs ?? 15000;
form.maxRetries = row.maxRetries ?? 3;
form.retryDelayMs = row.retryDelayMs ?? 300;
};
const onSubmit = () => {
if (!formRef.value) return;
formRef.value.validate(async (valid) => {
if (!valid) return;
dialog.saving = true;
try {
if (dialog.isEdit) {
await updateDatasourcePlatform({ ...form });
ElMessage.success('修改成功');
} else {
const { id: _id, ...createParams } = form;
await createDatasourcePlatform(createParams);
ElMessage.success('创建成功');
}
dialog.visible = false;
getList();
} catch (_e) {
ElMessage.error(dialog.isEdit ? '修改失败' : '创建失败');
} finally {
dialog.saving = false;
}
});
};
const onRowDel = (row: any) => {
const delIds = row ? [row.id] : ids.value;
if (delIds.length === 0) {
ElMessage.error('请选择要删除的数据');
return;
}
const msg = row ? `确定要删除平台「${row.platformName}」吗?` : `确定要删除选中的 ${delIds.length} 个平台吗?`;
ElMessageBox.confirm(msg, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
try {
for (const id of delIds) {
await deleteDatasourcePlatform(id);
}
ElMessage.success('删除成功');
getList();
} catch (_e) {
ElMessage.error('删除失败');
}
}).catch(() => {});
};
onMounted(() => {
getList();
});
</script>

View File

@@ -0,0 +1,198 @@
<template>
<div class="cid-field-container">
<el-card shadow="hover">
<div class="system-user-search mb15">
<el-form :model="tableData.param" ref="queryRef" :inline="true" label-width="68px">
<el-form-item label="关键字" prop="keyword">
<el-input
v-model="tableData.param.keyword"
placeholder="请输入字段名称"
clearable
size="default"
style="width: 240px"
@keyup.enter.native="getList"
/>
</el-form-item>
<el-form-item>
<el-button size="default" type="primary" @click="getList">
<el-icon><ele-Search /></el-icon>
查询
</el-button>
<el-button size="default" @click="resetQuery(queryRef)">
<el-icon><ele-Refresh /></el-icon>
重置
</el-button>
<el-button size="default" type="success" @click="onOpenAdd">
<el-icon><ele-FolderAdd /></el-icon>
新增映射
</el-button>
<el-button size="default" type="danger" @click="onRowDel(null)">
<el-icon><ele-Delete /></el-icon>
删除
</el-button>
</el-form-item>
</el-form>
</div>
<el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="sourceField" label="源字段" show-overflow-tooltip />
<el-table-column prop="targetField" label="目标字段" show-overflow-tooltip />
<el-table-column prop="datasourceName" label="所属平台" show-overflow-tooltip />
<el-table-column prop="apiName" label="所属接口" show-overflow-tooltip />
<el-table-column prop="description" label="描述" show-overflow-tooltip />
<el-table-column prop="createdAt" label="创建时间" show-overflow-tooltip />
<el-table-column label="操作" width="180">
<template #default="scope">
<el-button size="small" text type="primary" @click="onOpenEdit(scope.row)">修改</el-button>
<el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="tableData.total > 0"
:total="tableData.total"
v-model:page="tableData.param.pageNum"
v-model:limit="tableData.param.pageSize"
@pagination="getList"
/>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" :close-on-click-modal="false">
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
<el-form-item label="源字段" prop="sourceField">
<el-input v-model="form.sourceField" placeholder="请输入源字段名" />
</el-form-item>
<el-form-item label="目标字段" prop="targetField">
<el-input v-model="form.targetField" placeholder="请输入目标字段名" />
</el-form-item>
<el-form-item label="所属平台" prop="datasourceId">
<el-input v-model="form.datasourceId" placeholder="请输入所属平台ID" />
</el-form-item>
<el-form-item label="所属接口" prop="apiId">
<el-input v-model="form.apiId" placeholder="请输入所属接口ID" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialog.visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :loading="dialog.saving">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
export default { name: 'cidField' };
</script>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
const queryRef = ref<FormInstance>();
const formRef = ref<FormInstance>();
const ids = ref<string[]>([]);
const tableData = reactive({
data: [] as any[],
total: 0,
param: {
pageNum: 1,
pageSize: 10,
keyword: '',
},
});
const dialog = reactive({
visible: false,
title: '',
saving: false,
});
const form = reactive({
id: '',
sourceField: '',
targetField: '',
datasourceId: '',
apiId: '',
description: '',
});
const rules = {
sourceField: [{ required: true, message: '请输入源字段名', trigger: 'blur' }],
targetField: [{ required: true, message: '请输入目标字段名', trigger: 'blur' }],
};
const getList = () => {
// TODO: 调用列表接口
};
const resetQuery = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
getList();
};
const handleSelectionChange = (selection: any[]) => {
ids.value = selection.map((item) => item.id);
};
const onOpenAdd = () => {
dialog.title = '新增字典映射';
dialog.visible = true;
form.id = '';
form.sourceField = '';
form.targetField = '';
form.datasourceId = '';
form.apiId = '';
form.description = '';
};
const onOpenEdit = (row: any) => {
dialog.title = '修改字典映射';
dialog.visible = true;
form.id = row.id;
form.sourceField = row.sourceField;
form.targetField = row.targetField;
form.datasourceId = row.datasourceId;
form.apiId = row.apiId;
form.description = row.description;
};
const onSubmit = () => {
if (!formRef.value) return;
formRef.value.validate((valid) => {
if (!valid) return;
dialog.saving = true;
// TODO: 调用新增/编辑接口
dialog.saving = false;
dialog.visible = false;
getList();
});
};
const onRowDel = (row: any) => {
const delIds = row ? [row.id] : ids.value;
if (delIds.length === 0) {
ElMessage.error('请选择要删除的数据');
return;
}
ElMessageBox.confirm(`确定要删除选中的字典映射吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
// TODO: 调用删除接口
ElMessage.success('删除成功');
getList();
}).catch(() => {});
};
onMounted(() => {
getList();
});
</script>

View File

@@ -13,9 +13,7 @@
<div class="document-content">
<div class="content-header">
<h2>{{ documentInfo.name }}</h2>
<div class="content-meta">
Size{{ formatFileSize(documentInfo.fileSize) }} Uploaded Time{{ documentInfo.createdAt }}
</div>
<div class="content-meta">Size{{ formatFileSize(documentInfo.fileSize) }} Uploaded Time{{ documentInfo.createdAt }}</div>
</div>
<div class="content-body" v-loading="contentLoading">
<pre class="document-text">{{ documentContent }}</pre>
@@ -37,13 +35,7 @@
</el-radio-group>
</div>
<div class="toolbar-actions">
<el-input
v-model="chunkSearch"
placeholder="搜索"
clearable
size="small"
style="width: 120px"
>
<el-input v-model="chunkSearch" placeholder="搜索" clearable size="small" style="width: 120px">
<template #prefix>
<el-icon><ele-Search /></el-icon>
</template>
@@ -59,11 +51,7 @@
<el-checkbox v-model="selectAll" @change="onSelectAllChange">选择所有</el-checkbox>
</div>
<div
class="chunk-item"
v-for="chunk in filteredChunks"
:key="chunk.id"
>
<div class="chunk-item" v-for="chunk in filteredChunks" :key="chunk.id">
<div class="chunk-checkbox">
<el-checkbox v-model="chunk.selected" />
</div>
@@ -112,8 +100,8 @@ import { ElMessage } from 'element-plus';
const props = defineProps<{
modelValue: boolean;
datasetId: string;
datasetName: string;
knowledgeId: string;
knowledgeName: string;
document: any;
}>();
@@ -150,9 +138,7 @@ const selectAll = ref(false);
// 过滤后的切片列表
const filteredChunks = computed(() => {
if (!chunkSearch.value) return chunkList.value;
return chunkList.value.filter((chunk) =>
(chunk.content || '').toLowerCase().includes(chunkSearch.value.toLowerCase())
);
return chunkList.value.filter((chunk) => (chunk.content || '').toLowerCase().includes(chunkSearch.value.toLowerCase()));
});
// 格式化文件大小
@@ -215,7 +201,7 @@ const getChunkList = async () => {
// 全选变化
const onSelectAllChange = (val: boolean) => {
chunkList.value.forEach(chunk => {
chunkList.value.forEach((chunk) => {
chunk.selected = val;
});
};
@@ -231,12 +217,15 @@ const onAddChunk = () => {
};
// 监听弹窗打开
watch(() => props.modelValue, (val) => {
watch(
() => props.modelValue,
(val) => {
if (val && props.document) {
getDocumentDetail();
getChunkList();
}
});
}
);
</script>
<style scoped lang="scss">

View File

@@ -1,11 +1,5 @@
<template>
<el-dialog
:title="isEdit ? '编辑数据集' : '新增数据集'"
v-model="isShowDialog"
width="600px"
:close-on-click-modal="false"
@close="onCancel"
>
<el-dialog :title="isEdit ? '编辑数据集' : '新增数据集'" v-model="isShowDialog" width="600px" :close-on-click-modal="false" @close="onCancel">
<el-form ref="formRef" :model="ruleForm" :rules="rules" label-width="100px">
<el-row :gutter="20">
<el-col :span="24">
@@ -67,7 +61,7 @@
<script lang="ts">
export default {
name: 'editDataset',
name: 'editknowledge',
};
</script>
@@ -75,10 +69,10 @@ export default {
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import { createDataset, updateDataset, getDataset } from '/@/api/knowledge/dataset';
import { createknowledge, updateknowledge, getknowledge } from '/@/api/knowledge/knowledge';
// 定义事件
const emit = defineEmits(['getDatasetList']);
const emit = defineEmits(['getknowledgeList']);
// 表单ref
const formRef = ref<FormInstance>();
@@ -124,7 +118,7 @@ const openDialog = async (row?: any) => {
if (row) {
try {
const res: any = await getDataset(row.id);
const res: any = await getknowledge(row.id);
const data = res.data || row;
ruleForm.id = data.id || '';
ruleForm.name = data.name || '';
@@ -174,15 +168,15 @@ const onSubmit = async () => {
};
if (isEdit.value) {
await updateDataset(data);
await updateknowledge(data);
ElMessage.success('保存成功');
} else {
await createDataset(data);
await createknowledge(data);
ElMessage.success('创建成功');
}
isShowDialog.value = false;
emit('getDatasetList');
emit('getknowledgeList');
} catch (_error) {
ElMessage.error(isEdit.value ? '保存失败,请重试' : '创建失败,请重试');
} finally {
@@ -198,5 +192,4 @@ defineExpose({
});
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@@ -1,8 +1,8 @@
<template>
<div class="knowledge-dataset-page">
<div class="knowledge-dataset-container">
<div class="knowledge-knowledge-page">
<div class="knowledge-knowledge-container">
<el-card shadow="hover">
<div class="knowledge-dataset-search mb15">
<div class="knowledge-knowledge-search mb15">
<el-form :inline="true">
<el-form-item label="数据集名称">
<el-input size="default" v-model="tableData.param.keyword" placeholder="请输入数据集名称" clearable style="width: 200px" />
@@ -21,7 +21,7 @@
</el-select>
</el-form-item>
<el-form-item>
<el-button size="default" type="primary" @click="getDatasetList">
<el-button size="default" type="primary" @click="getknowledgeList">
<el-icon><ele-Search /></el-icon>
查询
</el-button>
@@ -29,7 +29,7 @@
<el-icon><ele-Refresh /></el-icon>
重置
</el-button>
<el-button size="default" type="success" @click="onOpenAdd" v-auth="'api/v1/knowledge/dataset/create'">
<el-button size="default" type="success" @click="onOpenAdd" v-auth="'api/v1/knowledge/knowledge/create'">
<el-icon><ele-Plus /></el-icon>
新增
</el-button>
@@ -63,7 +63,7 @@
active-text=""
inactive-text=""
@change="onStatusChange(scope.row)"
v-auth="'api/v1/knowledge/dataset/updateStatus'"
v-auth="'api/v1/knowledge/knowledge/updateStatus'"
/>
</template>
</el-table-column>
@@ -71,9 +71,9 @@
<el-table-column prop="updatedAt" label="更新时间" width="170" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right" align="center">
<template #default="scope">
<el-button size="small" text type="primary" @click="onEdit(scope.row)" v-auth="'api/v1/knowledge/dataset/update'">编辑</el-button>
<el-button size="small" text type="primary" @click="onEdit(scope.row)" v-auth="'api/v1/knowledge/knowledge/update'">编辑</el-button>
<el-button size="small" text type="success" @click="onManageDocuments(scope.row)">文档</el-button>
<el-button size="small" text type="danger" @click="onRowDel(scope.row)" v-auth="'api/v1/knowledge/dataset/delete'">删除</el-button>
<el-button size="small" text type="danger" @click="onRowDel(scope.row)" v-auth="'api/v1/knowledge/knowledge/delete'">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -91,13 +91,13 @@
</div>
</el-card>
</div>
<EditDataset ref="editDatasetRef" @getDatasetList="getDatasetList" />
<Editknowledge ref="editknowledgeRef" @getknowledgeList="getknowledgeList" />
</div>
</template>
<script lang="ts">
export default {
name: 'knowledgeDataset',
name: 'knowledgeknowledge',
};
</script>
@@ -105,8 +105,8 @@ export default {
import { ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { listDatasets, deleteDataset, updateDatasetStatus } from '/@/api/knowledge/dataset';
import EditDataset from './component/editDataset.vue';
import { listknowledges, deleteknowledge, updateknowledgeStatus } from '/@/api/knowledge/knowledge';
import Editknowledge from './component/editknowledge.vue';
const router = useRouter();
@@ -125,13 +125,13 @@ const tableData = reactive({
});
// 编辑弹窗ref
const editDatasetRef = ref();
const editknowledgeRef = ref();
// 获取数据集列表
const getDatasetList = async () => {
const getknowledgeList = async () => {
tableData.loading = true;
try {
const res: any = await listDatasets(tableData.param);
const res: any = await listknowledges(tableData.param);
const list = res.data?.list || [];
tableData.data = list.map((item: any) => ({
...item,
@@ -190,27 +190,27 @@ const onResetQuery = () => {
tableData.param.type = '';
tableData.param.status = undefined;
tableData.param.pageNum = 1;
getDatasetList();
getknowledgeList();
};
// 打开新增弹窗
const onOpenAdd = () => {
editDatasetRef.value.openDialog();
editknowledgeRef.value.openDialog();
};
// 打开编辑弹窗
const onEdit = (row: any) => {
editDatasetRef.value.openDialog(row);
editknowledgeRef.value.openDialog(row);
};
// 查看详情
const onViewDetail = (row: any) => {
router.push({ path: '/knowledge/document', query: { datasetId: row.id, datasetName: row.name } });
router.push({ path: '/knowledge/document', query: { knowledgeId: row.id, knowledgeName: row.name } });
};
// 管理文档
const onManageDocuments = (row: any) => {
router.push({ path: '/knowledge/document', query: { datasetId: row.id, datasetName: row.name } });
router.push({ path: '/knowledge/document', query: { knowledgeId: row.id, knowledgeName: row.name } });
};
// 状态切换
@@ -218,7 +218,7 @@ const onStatusChange = async (row: any) => {
const newStatus = row.statusEnabled ? 'enable' : 'disable';
const statusText = row.statusEnabled ? '启用' : '禁用';
try {
await updateDatasetStatus({ id: row.id, status: newStatus });
await updateknowledgeStatus({ id: row.id, status: newStatus });
ElMessage.success(`${statusText}成功`);
} catch (_error) {
row.statusEnabled = !row.statusEnabled;
@@ -232,40 +232,42 @@ const onRowDel = (row: any) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
})
.then(async () => {
try {
await deleteDataset(row.id);
await deleteknowledge(row.id);
ElMessage.success('删除成功');
getDatasetList();
getknowledgeList();
} catch (_error) {
ElMessage.error('删除失败');
}
}).catch(() => {});
})
.catch(() => {});
};
// 分页大小改变
const onSizeChange = (size: number) => {
tableData.param.pageSize = size;
getDatasetList();
getknowledgeList();
};
// 当前页改变
const onCurrentChange = (page: number) => {
tableData.param.pageNum = page;
getDatasetList();
getknowledgeList();
};
// 页面加载时获取数据
onMounted(() => {
getDatasetList();
getknowledgeList();
});
</script>
<style scoped lang="scss">
.knowledge-dataset-page {
.knowledge-knowledge-page {
padding: 15px;
.knowledge-dataset-container {
.knowledge-dataset-search {
.knowledge-knowledge-container {
.knowledge-knowledge-search {
.el-form-item {
margin-bottom: 0;
}

View File

@@ -1,10 +1,5 @@
<template>
<el-drawer
v-model="isShowDrawer"
title="文档分段管理"
size="70%"
:close-on-click-modal="true"
>
<el-drawer v-model="isShowDrawer" title="文档分段管理" size="70%" :close-on-click-modal="true">
<div class="chunks-container" v-loading="loading">
<div class="chunks-header">
<div class="doc-info">
@@ -12,13 +7,7 @@
<el-tag size="small" type="info"> {{ tableData.total }} 个分段</el-tag>
</div>
<div class="actions">
<el-input
v-model="searchKeyword"
placeholder="搜索分段内容"
clearable
style="width: 200px"
@keyup.enter="onSearch"
>
<el-input v-model="searchKeyword" placeholder="搜索分段内容" clearable style="width: 200px" @keyup.enter="onSearch">
<template #append>
<el-button @click="onSearch">
<el-icon><ele-Search /></el-icon>
@@ -29,11 +18,7 @@
</div>
<div class="chunks-list">
<div
class="chunk-item"
v-for="chunk in tableData.data"
:key="chunk.id"
>
<div class="chunk-item" v-for="chunk in tableData.data" :key="chunk.id">
<div class="chunk-header">
<div class="chunk-index">
<el-tag size="small"># {{ chunk.chunkIndex + 1 }}</el-tag>
@@ -55,12 +40,7 @@
{{ chunk.content }}
</div>
<div class="chunk-edit" v-else>
<el-input
v-model="chunk.editContent"
type="textarea"
:rows="6"
placeholder="请输入分段内容"
/>
<el-input v-model="chunk.editContent" type="textarea" :rows="6" placeholder="请输入分段内容" />
<div class="edit-actions">
<el-button size="small" @click="onCancelEdit(chunk)">取消</el-button>
<el-button size="small" type="primary" @click="onSaveChunk(chunk)" :loading="chunk.saving">保存</el-button>
@@ -186,7 +166,8 @@ const onDeleteChunk = (chunk: any) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
})
.then(async () => {
try {
await deleteDocumentChunk(chunk.id);
ElMessage.success('删除成功');
@@ -194,7 +175,8 @@ const onDeleteChunk = (chunk: any) => {
} catch (_error) {
ElMessage.error('删除失败');
}
}).catch(() => {});
})
.catch(() => {});
};
// 分页大小改变
@@ -277,7 +259,8 @@ defineExpose({
align-items: center;
gap: 12px;
.char-count, .token-count {
.char-count,
.token-count {
font-size: 12px;
color: #909399;
}

View File

@@ -1,10 +1,5 @@
<template>
<el-drawer
v-model="isShowDrawer"
title="文档预览"
size="60%"
:close-on-click-modal="true"
>
<el-drawer v-model="isShowDrawer" title="文档预览" size="60%" :close-on-click-modal="true">
<div class="preview-container" v-loading="loading">
<div class="preview-header">
<div class="file-info">
@@ -37,7 +32,7 @@
<el-tab-pane label="文档信息" name="info">
<el-descriptions :column="2" border>
<el-descriptions-item label="文档名称">{{ documentInfo.name }}</el-descriptions-item>
<el-descriptions-item label="所属数据集">{{ documentInfo.datasetName }}</el-descriptions-item>
<el-descriptions-item label="所属数据集">{{ documentInfo.knowledgeName }}</el-descriptions-item>
<el-descriptions-item label="文件类型">{{ documentInfo.fileType?.toUpperCase() }}</el-descriptions-item>
<el-descriptions-item label="文件大小">{{ formatFileSize(documentInfo.fileSize) }}</el-descriptions-item>
<el-descriptions-item label="字符数">{{ documentInfo.charCount }}</el-descriptions-item>
@@ -81,8 +76,8 @@ const activeTab = ref('content');
const documentInfo = reactive({
id: '',
name: '',
datasetId: '',
datasetName: '',
knowledgeId: '',
knowledgeName: '',
fileType: '',
fileSize: 0,
filePath: '',
@@ -211,10 +206,7 @@ const openDialog = async (row: any) => {
// 获取文档详情和内容
loading.value = true;
try {
const [detailRes, contentRes] = await Promise.all([
getDocument(row.id),
previewDocument(row.id),
]);
const [detailRes, contentRes] = await Promise.all([getDocument(row.id), previewDocument(row.id)]);
if (detailRes.data) {
Object.assign(documentInfo, detailRes.data);

View File

@@ -1,15 +1,9 @@
<template>
<el-dialog
title="上传文档"
v-model="isShowDialog"
width="600px"
:close-on-click-modal="false"
@close="onCancel"
>
<el-dialog title="上传文档" v-model="isShowDialog" width="600px" :close-on-click-modal="false" @close="onCancel">
<el-form ref="formRef" :model="ruleForm" :rules="rules" label-width="100px">
<el-form-item label="所属数据集" prop="datasetId">
<el-select v-model="ruleForm.datasetId" placeholder="请选择数据集" clearable style="width: 100%" :disabled="!!currentDatasetId">
<el-option v-for="item in datasetOptions" :key="item.id" :label="item.name" :value="item.id" />
<el-form-item label="所属数据集" prop="knowledgeId">
<el-select v-model="ruleForm.datasetId" placeholder="请选择数据集" clearable style="width: 100%" :disabled="!!currentknowledgeId">
<el-option v-for="item in knowledgeOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="上传文件" prop="files">
@@ -25,13 +19,9 @@
accept=".pdf,.docx,.doc,.txt,.md,.html"
>
<el-icon class="el-icon--upload"><ele-UploadFilled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">
支持 PDFWordTXTMarkdownHTML 格式单个文件不超过 20MB
</div>
<div class="el-upload__tip">支持 PDFWordTXTMarkdownHTML 格式单个文件不超过 20MB</div>
</template>
</el-upload>
</el-form-item>
@@ -81,8 +71,8 @@ import { uploadDocument } from '/@/api/knowledge/document';
// 定义props
const props = defineProps<{
datasetOptions: any[];
currentDatasetId?: string;
knowledgeOptions: any[];
currentknowledgeId?: string;
}>();
// 定义事件
@@ -115,7 +105,7 @@ const rules = reactive<FormRules>({
// 监听当前数据集ID
watch(
() => props.currentDatasetId,
() => props.currentknowledgeId,
(val) => {
if (val) {
ruleForm.datasetId = val;
@@ -126,7 +116,7 @@ watch(
// 重置表单
const resetForm = () => {
if (!props.currentDatasetId) {
if (!props.currentknowledgeId) {
ruleForm.datasetId = '';
}
ruleForm.chunkSize = 500;

View File

@@ -4,10 +4,10 @@
<div class="page-header">
<el-breadcrumb separator=">">
<el-breadcrumb-item>
<span class="back-link" @click="onBackToKnowledge">知识库</span>
<span class="back-link" @click="onBackToknowledge">知识库</span>
</el-breadcrumb-item>
<el-breadcrumb-item>
<span class="back-link" @click="onBackToDataset">{{ datasetName }}</span>
<span class="back-link" @click="onBackToknowledge">{{ knowledgeName }}</span>
</el-breadcrumb-item>
<el-breadcrumb-item>{{ documentInfo.name }}</el-breadcrumb-item>
</el-breadcrumb>
@@ -18,9 +18,7 @@
<div class="document-content">
<div class="content-header">
<h2>{{ documentInfo.name }}</h2>
<div class="content-meta">
Size{{ formatFileSize(documentInfo.fileSize) }} Uploaded Time{{ documentInfo.createdAt }}
</div>
<div class="content-meta">Size{{ formatFileSize(documentInfo.fileSize) }} Uploaded Time{{ documentInfo.createdAt }}</div>
</div>
<div class="content-body" v-loading="contentLoading">
<pre class="document-text">{{ documentContent }}</pre>
@@ -42,13 +40,7 @@
</el-radio-group>
</div>
<div class="toolbar-actions">
<el-input
v-model="chunkSearch"
placeholder="搜索"
clearable
size="small"
style="width: 120px"
>
<el-input v-model="chunkSearch" placeholder="搜索" clearable size="small" style="width: 120px">
<template #prefix>
<el-icon><ele-Search /></el-icon>
</template>
@@ -64,11 +56,7 @@
<el-checkbox v-model="selectAll" @change="onSelectAllChange">选择所有</el-checkbox>
</div>
<div
class="chunk-item"
v-for="chunk in filteredChunks"
:key="chunk.id"
>
<div class="chunk-item" v-for="chunk in filteredChunks" :key="chunk.id">
<div class="chunk-checkbox">
<el-checkbox v-model="chunk.selected" />
</div>
@@ -121,8 +109,8 @@ const route = useRoute();
const router = useRouter();
// 路由参数
const datasetId = ref('');
const datasetName = ref('');
const knowledgeId = ref('');
const knowledgeName = ref('');
const documentId = ref('');
// 文档信息
@@ -151,9 +139,7 @@ const selectAll = ref(false);
// 过滤后的切片列表
const filteredChunks = computed(() => {
if (!chunkSearch.value) return chunkList.value;
return chunkList.value.filter((chunk) =>
(chunk.content || '').toLowerCase().includes(chunkSearch.value.toLowerCase())
);
return chunkList.value.filter((chunk) => (chunk.content || '').toLowerCase().includes(chunkSearch.value.toLowerCase()));
});
// 格式化文件大小
@@ -171,15 +157,15 @@ const truncateText = (text: string, maxLength: number) => {
};
// 返回知识库列表
const onBackToKnowledge = () => {
router.push('/knowledge/dataset');
const onBackToknowledge = () => {
router.push('/knowledge/knowledge');
};
// 返回数据集详情
const onBackToDataset = () => {
const onBackToknowledge = () => {
router.push({
path: '/knowledge/document',
query: { datasetId: datasetId.value, datasetName: datasetName.value },
query: { knowledgeId: knowledgeId.value, knowledgeName: knowledgeName.value },
});
};
@@ -195,8 +181,7 @@ const getDocumentDetail = async () => {
documentInfo.fileSize = detail.fileSize || 0;
documentInfo.createdAt = detail.createdAt || '';
const contentData = contentRes.data;
documentContent.value =
typeof contentData === 'string' ? contentData : contentData?.content || contentData?.text || '';
documentContent.value = typeof contentData === 'string' ? contentData : contentData?.content || contentData?.text || '';
} catch (_error) {
ElMessage.error('获取文档详情失败');
documentContent.value = '';
@@ -248,12 +233,12 @@ const onAddChunk = () => {
// 初始化
onMounted(() => {
datasetId.value = (route.query.datasetId as string) || '';
datasetName.value = (route.query.datasetName as string) || '';
knowledgeId.value = (route.query.knowledgeId as string) || '';
knowledgeName.value = (route.query.knowledgeName as string) || '';
documentId.value = (route.query.docId as string) || '';
if (!documentId.value) {
ElMessage.warning('缺少文档ID无法查看详情');
onBackToDataset();
onBackToknowledge();
return;
}
getDocumentDetail();

View File

@@ -3,18 +3,18 @@
<div class="knowledge-document-container">
<el-card shadow="hover">
<!-- 面包屑导航 -->
<div class="breadcrumb-nav mb15" v-if="currentDataset.id">
<div class="breadcrumb-nav mb15" v-if="currentknowledge.id">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/knowledge/dataset' }">数据集管理</el-breadcrumb-item>
<el-breadcrumb-item>{{ currentDataset.name }}</el-breadcrumb-item>
<el-breadcrumb-item :to="{ path: '/knowledge/knowledge' }">数据集管理</el-breadcrumb-item>
<el-breadcrumb-item>{{ currentknowledge.name }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="knowledge-document-search mb15">
<el-form :inline="true">
<el-form-item label="所属数据集" v-if="!currentDataset.id">
<el-select size="default" v-model="tableData.param.datasetId" placeholder="请选择数据集" clearable style="width: 180px">
<el-option v-for="item in datasetOptions" :key="item.id" :label="item.name" :value="item.id" />
<el-form-item label="所属数据集" v-if="!currentknowledge.id">
<el-select size="default" v-model="tableData.param.knowledgeId" placeholder="请选择数据集" clearable style="width: 180px">
<el-option v-for="item in knowledgeOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="文档名称">
@@ -50,20 +50,20 @@
<el-icon><ele-Upload /></el-icon>
上传文档
</el-button>
<el-button size="default" type="danger" @click="onBatchDelete" :disabled="selectedIds.length === 0" v-auth="'api/v1/knowledge/document/batchDelete'">
<el-button
size="default"
type="danger"
@click="onBatchDelete"
:disabled="selectedIds.length === 0"
v-auth="'api/v1/knowledge/document/batchDelete'"
>
<el-icon><ele-Delete /></el-icon>
批量删除
</el-button>
</el-form-item>
</el-form>
</div>
<el-table
:data="tableData.data"
style="width: 100%"
v-loading="tableData.loading"
border
@selection-change="onSelectionChange"
>
<el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading" border @selection-change="onSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="name" label="文档名称" min-width="200" show-overflow-tooltip>
@@ -76,7 +76,7 @@
</div>
</template>
</el-table-column>
<el-table-column prop="datasetName" label="所属数据集" width="150" show-overflow-tooltip v-if="!currentDataset.id" />
<el-table-column prop="knowledgeName" label="所属数据集" width="150" show-overflow-tooltip v-if="!currentknowledge.id" />
<el-table-column prop="fileType" label="文件类型" width="100" align="center">
<template #default="scope">
<el-tag size="small">{{ scope.row.fileType?.toUpperCase() }}</el-tag>
@@ -103,8 +103,18 @@
<el-table-column label="操作" width="200" fixed="right" align="center">
<template #default="scope">
<el-button size="small" text type="primary" @click="onPreview(scope.row)">预览</el-button>
<el-button size="small" text type="success" @click="onViewChunks(scope.row)" v-auth="'api/v1/knowledge/document/chunks'">分段</el-button>
<el-button size="small" text type="warning" @click="onReprocess(scope.row)" v-if="scope.row.status === 'failed'" v-auth="'api/v1/knowledge/document/reprocess'">重试</el-button>
<el-button size="small" text type="success" @click="onViewChunks(scope.row)" v-auth="'api/v1/knowledge/document/chunks'"
>分段</el-button
>
<el-button
size="small"
text
type="warning"
@click="onReprocess(scope.row)"
v-if="scope.row.status === 'failed'"
v-auth="'api/v1/knowledge/document/reprocess'"
>重试</el-button
>
<el-button size="small" text type="danger" @click="onRowDel(scope.row)" v-auth="'api/v1/knowledge/document/delete'">删除</el-button>
</template>
</el-table-column>
@@ -125,7 +135,12 @@
</div>
<!-- 上传文档弹窗 -->
<UploadDocument ref="uploadDocumentRef" :datasetOptions="datasetOptions" :currentDatasetId="currentDataset.id" @getDocumentList="getDocumentList" />
<UploadDocument
ref="uploadDocumentRef"
:knowledgeOptions="knowledgeOptions"
:currentknowledgeId="currentknowledge.id"
@getDocumentList="getDocumentList"
/>
<!-- 文档预览弹窗 -->
<PreviewDocument ref="previewDocumentRef" />
@@ -146,7 +161,7 @@ import { ref, reactive, onMounted, watch } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { listDocuments, deleteDocument, batchDeleteDocuments, reprocessDocument } from '/@/api/knowledge/document';
import { listDatasets } from '/@/api/knowledge/dataset';
import { listknowledges } from '/@/api/knowledge/knowledge';
import UploadDocument from './component/uploadDocument.vue';
import PreviewDocument from './component/previewDocument.vue';
import DocumentChunks from './component/documentChunks.vue';
@@ -154,13 +169,13 @@ import DocumentChunks from './component/documentChunks.vue';
const route = useRoute();
// 当前数据集
const currentDataset = reactive({
const currentknowledge = reactive({
id: '',
name: '',
});
// 数据集选项
const datasetOptions = ref<any[]>([]);
const knowledgeOptions = ref<any[]>([]);
// 选中的文档ID
const selectedIds = ref<string[]>([]);
@@ -172,7 +187,7 @@ const tableData = reactive({
loading: false,
param: {
keyword: '',
datasetId: '',
knowledgeId: '',
fileType: '',
status: undefined as string | undefined,
pageNum: 1,
@@ -186,12 +201,12 @@ const previewDocumentRef = ref();
const documentChunksRef = ref();
// 获取数据集列表
const getDatasetOptions = async () => {
const getknowledgeOptions = async () => {
try {
const res: any = await listDatasets({ pageNum: 1, pageSize: 1000 });
datasetOptions.value = res.data?.list || [];
const res: any = await listknowledges({ pageNum: 1, pageSize: 1000 });
knowledgeOptions.value = res.data?.list || [];
} catch (_error) {
datasetOptions.value = [];
knowledgeOptions.value = [];
ElMessage.error('获取数据集列表失败');
}
};
@@ -201,8 +216,8 @@ const getDocumentList = async () => {
tableData.loading = true;
try {
const params = { ...tableData.param };
if (currentDataset.id) {
params.datasetId = currentDataset.id;
if (currentknowledge.id) {
params.knowledgeId = currentknowledge.id;
}
const res: any = await listDocuments(params);
tableData.data = res.data?.list || [];
@@ -309,8 +324,8 @@ const getIndexStatusText = (status: string) => {
// 重置查询
const onResetQuery = () => {
tableData.param.keyword = '';
if (!currentDataset.id) {
tableData.param.datasetId = '';
if (!currentknowledge.id) {
tableData.param.knowledgeId = '';
}
tableData.param.fileType = '';
tableData.param.status = undefined;
@@ -346,7 +361,7 @@ const onReprocess = async (row: any) => {
// 选择变化
const onSelectionChange = (selection: any[]) => {
selectedIds.value = selection.map(item => item.id);
selectedIds.value = selection.map((item) => item.id);
};
// 批量删除
@@ -355,7 +370,8 @@ const onBatchDelete = () => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
})
.then(async () => {
try {
await batchDeleteDocuments(selectedIds.value);
ElMessage.success('删除成功');
@@ -363,7 +379,8 @@ const onBatchDelete = () => {
} catch (_error) {
ElMessage.error('批量删除失败');
}
}).catch(() => {});
})
.catch(() => {});
};
// 删除文档
@@ -372,7 +389,8 @@ const onRowDel = (row: any) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
})
.then(async () => {
try {
await deleteDocument(row.id);
ElMessage.success('删除成功');
@@ -380,7 +398,8 @@ const onRowDel = (row: any) => {
} catch (_error) {
ElMessage.error('删除失败');
}
}).catch(() => {});
})
.catch(() => {});
};
// 分页大小改变
@@ -399,13 +418,13 @@ const onCurrentChange = (page: number) => {
watch(
() => route.query,
(query) => {
if (query.datasetId) {
currentDataset.id = query.datasetId as string;
currentDataset.name = query.datasetName as string || '';
tableData.param.datasetId = currentDataset.id;
if (query.knowledgeId) {
currentknowledge.id = query.knowledgeId as string;
currentknowledge.name = (query.knowledgeName as string) || '';
tableData.param.knowledgeId = currentknowledge.id;
} else {
currentDataset.id = '';
currentDataset.name = '';
currentknowledge.id = '';
currentknowledge.name = '';
}
getDocumentList();
},
@@ -414,7 +433,7 @@ watch(
// 页面加载时获取数据
onMounted(() => {
getDatasetOptions();
getknowledgeOptions();
});
</script>

View File

@@ -1,25 +1,25 @@
<template>
<div class="knowledge-page">
<!-- 数据集列表页 -->
<div class="dataset-list-view" v-if="!currentDataset">
<div class="knowledge-list-view" v-if="!currentknowledge">
<div class="page-header">
<div class="header-left">
<el-icon class="header-icon"><ele-Folder /></el-icon>
<span class="header-title">知识库</span>
</div>
<el-button type="primary" @click="onAddDataset">
<el-button type="primary" @click="onAddknowledge">
<el-icon><ele-Plus /></el-icon>
新建知识库
</el-button>
</div>
<div class="dataset-cards" v-loading="datasetLoading">
<div class="knowledge-cards" v-loading="knowledgeLoading">
<!-- 数据集卡片 -->
<div
class="dataset-card"
v-for="item in datasetList"
class="knowledge-card"
v-for="item in knowledgeList"
:key="item.id"
@click="onSelectDataset(item)"
@click="onSelectknowledge(item)"
@contextmenu.prevent="onCardContextMenu($event, item)"
>
<div class="card-icon">
@@ -33,12 +33,12 @@
<!-- 悬停操作按钮 -->
<div class="card-actions" @click.stop>
<el-tooltip content="重命名" placement="top">
<el-button text size="small" @click="onRenameDataset(item)">
<el-button text size="small" @click="onRenameknowledge(item)">
<el-icon><ele-Edit /></el-icon>
</el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button text size="small" type="danger" @click="onDeleteDataset(item)">
<el-button text size="small" type="danger" @click="onDeleteknowledge(item)">
<el-icon><ele-Delete /></el-icon>
</el-button>
</el-tooltip>
@@ -46,17 +46,17 @@
</div>
<!-- 查看全部卡片 -->
<div class="see-all-card" v-if="datasetList.length > 0">
<div class="see-all-card" v-if="knowledgeList.length > 0">
<span>See All</span>
<el-icon><ele-ArrowRight /></el-icon>
</div>
<el-empty v-if="datasetList.length === 0 && !datasetLoading" description="暂无知识库,点击上方按钮创建" :image-size="100" />
<el-empty v-if="knowledgeList.length === 0 && !knowledgeLoading" description="暂无知识库,点击上方按钮创建" :image-size="100" />
</div>
</div>
<!-- 数据集详情页 -->
<div class="dataset-detail-view" v-else>
<div class="knowledge-detail-view" v-else>
<!-- 顶部导航 -->
<div class="detail-header">
<div class="header-left">
@@ -64,7 +64,7 @@
<el-breadcrumb-item>
<span class="back-link" @click="onBackToList">知识库</span>
</el-breadcrumb-item>
<el-breadcrumb-item>{{ currentDataset.name }}</el-breadcrumb-item>
<el-breadcrumb-item>{{ currentknowledge.name }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
</div>
@@ -72,48 +72,32 @@
<div class="detail-body">
<!-- 左侧信息面板 -->
<div class="info-sidebar">
<div class="dataset-profile">
<div class="knowledge-profile">
<div class="profile-icon">
<span class="icon-text">{{ currentDataset.name?.charAt(0)?.toUpperCase() || 'D' }}</span>
<span class="icon-text">{{ currentknowledge.name?.charAt(0)?.toUpperCase() || 'D' }}</span>
</div>
<div class="profile-info">
<div class="profile-name">{{ currentDataset.name }}</div>
<div class="profile-meta">{{ currentDataset.fileCount || 0 }} 个文件 · {{ formatFileSize(currentDataset.totalSize || 0) }}</div>
<div class="profile-time">创建于 {{ currentDataset.createdAt }}</div>
<div class="profile-name">{{ currentknowledge.name }}</div>
<div class="profile-meta">{{ currentknowledge.fileCount || 0 }} 个文件 · {{ formatFileSize(currentknowledge.totalSize || 0) }}</div>
<div class="profile-time">创建于 {{ currentknowledge.createdAt }}</div>
</div>
</div>
<!-- 功能菜单 -->
<div class="func-menu">
<div
class="menu-item"
:class="{ active: activeMenu === 'files' }"
@click="activeMenu = 'files'"
>
<div class="menu-item" :class="{ active: activeMenu === 'files' }" @click="activeMenu = 'files'">
<el-icon><ele-Document /></el-icon>
<span>文件列表</span>
</div>
<div
class="menu-item"
:class="{ active: activeMenu === 'search' }"
@click="activeMenu = 'search'"
>
<div class="menu-item" :class="{ active: activeMenu === 'search' }" @click="activeMenu = 'search'">
<el-icon><ele-Search /></el-icon>
<span>检索测试</span>
</div>
<div
class="menu-item"
:class="{ active: activeMenu === 'logs' }"
@click="activeMenu = 'logs'"
>
<div class="menu-item" :class="{ active: activeMenu === 'logs' }" @click="activeMenu = 'logs'">
<el-icon><ele-List /></el-icon>
<span>日志</span>
</div>
<div
class="menu-item"
:class="{ active: activeMenu === 'settings' }"
@click="activeMenu = 'settings'"
>
<div class="menu-item" :class="{ active: activeMenu === 'settings' }" @click="activeMenu = 'settings'">
<el-icon><ele-Setting /></el-icon>
<span>配置</span>
</div>
@@ -130,13 +114,7 @@
<span class="subtitle">解析成功后才能问答哦</span>
</div>
<div class="header-actions">
<el-input
v-model="searchKeyword"
placeholder="搜索文件"
clearable
style="width: 200px"
@keyup.enter="getFileList"
>
<el-input v-model="searchKeyword" placeholder="搜索文件" clearable style="width: 200px" @keyup.enter="getFileList">
<template #prefix>
<el-icon><ele-Search /></el-icon>
</template>
@@ -149,51 +127,33 @@
</div>
<div class="file-table" v-loading="fileLoading">
<el-table :data="fileList" style="width: 100%" row-key="id" border>
<el-table-column type="selection" width="50" align="center" />
<el-table-column prop="name" label="名称" min-width="200" sortable>
<el-table-column prop="Title" label="名称" min-width="200">
<template #default="scope">
<div class="file-name" @click="onViewDocumentDetail(scope.row)">
<el-icon class="file-icon" :style="{ color: getFileIconColor(scope.row.fileType) }">
<ele-Document />
</el-icon>
<span class="file-link">{{ scope.row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="上传日期" width="180" sortable />
<el-table-column prop="source" label="来源" width="80" align="center">
<template #default>
<el-icon><ele-Monitor /></el-icon>
</template>
</el-table-column>
<el-table-column prop="enabled" label="启用" width="80" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.enabled"
size="small"
@change="onFileStatusChange(scope.row)"
/>
<span class="file-link" @click="onViewDocumentDetail(scope.row)" style="cursor: pointer; color: #409eff">{{
scope.row.Title
}}</span>
</template>
</el-table-column>
<el-table-column prop="chunkCount" label="分块数" width="80" align="center" />
<el-table-column prop="parseStatus" label="解析" width="100" align="center">
<el-table-column prop="status" label="状态" width="90" align="center">
<template #default="scope">
<el-tag :type="getParseStatusType(scope.row.parseStatus)" size="small">
{{ scope.row.parseStatus }}
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'" size="small">
{{ scope.row.status === 1 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="动作" width="140" align="center">
<el-table-column prop="vectorStatus" label="向量化" width="100" align="center">
<template #default="scope">
<el-button text size="small" @click="onPreviewFile(scope.row)">
<el-icon><ele-View /></el-icon>
</el-button>
<el-button text size="small" @click="onDownloadFile(scope.row)">
<el-icon><ele-Download /></el-icon>
</el-button>
<el-button text size="small" type="danger" @click="onDeleteFile(scope.row)">
<el-icon><ele-Delete /></el-icon>
</el-button>
<el-tag :type="scope.row.vectorStatus === 'done' ? 'success' : 'warning'" size="small">
{{ scope.row.vectorStatus || '未处理' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="上传日期" width="180" />
<el-table-column label="动作" width="120" align="center">
<template #default="scope">
<el-button text size="small" @click="onPreviewFile(scope.row)">预览</el-button>
<el-button text size="small" type="danger" @click="onDeleteFile(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -205,12 +165,7 @@
<template v-if="activeMenu === 'search'">
<div class="panel-card">
<h3>检索测试</h3>
<el-input
v-model="searchQuery"
type="textarea"
:rows="3"
placeholder="输入问题进行检索测试..."
/>
<el-input v-model="searchQuery" type="textarea" :rows="3" placeholder="输入问题进行检索测试..." />
<el-button type="primary" class="mt15" @click="onSearchTest">测试检索</el-button>
<div class="search-results mt15" v-if="searchResults.length > 0">
<h4>检索结果</h4>
@@ -227,12 +182,7 @@
<div class="panel-card">
<h3>操作日志</h3>
<el-timeline>
<el-timeline-item
v-for="(log, index) in logList"
:key="index"
:timestamp="log.time"
placement="top"
>
<el-timeline-item v-for="(log, index) in logList" :key="index" :timestamp="log.time" placement="top">
<span>{{ log.content }}</span>
</el-timeline-item>
</el-timeline>
@@ -246,7 +196,7 @@
<h3>数据集配置</h3>
<el-form label-width="120px" style="max-width: 500px">
<el-form-item label="数据集名称">
<el-input v-model="currentDataset.name" disabled />
<el-input v-model="currentknowledge.name" disabled />
</el-form-item>
<el-form-item label="向量模型">
<el-select v-model="settingsForm.embeddingModel" style="width: 100%">
@@ -272,33 +222,23 @@
</div>
<!-- 新增/编辑数据集弹窗 -->
<el-dialog
:title="datasetForm.id ? '编辑知识库' : '新建知识库'"
v-model="showDatasetDialog"
width="500px"
:close-on-click-modal="false"
>
<el-form ref="datasetFormRef" :model="datasetForm" :rules="datasetRules" label-width="100px">
<el-dialog :title="knowledgeForm.id ? '编辑知识库' : '新建知识库'" v-model="showknowledgeDialog" width="500px" :close-on-click-modal="false">
<el-form ref="knowledgeFormRef" :model="knowledgeForm" :rules="knowledgeRules" label-width="100px">
<el-form-item label="名称" prop="name">
<el-input v-model="datasetForm.name" placeholder="请输入知识库名称" />
<el-input v-model="knowledgeForm.name" placeholder="请输入知识库名称" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="datasetForm.description" type="textarea" :rows="3" placeholder="请输入描述" />
<el-input v-model="knowledgeForm.description" type="textarea" :rows="3" placeholder="请输入描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showDatasetDialog = false">取消</el-button>
<el-button type="primary" @click="onSaveDataset" :loading="datasetSaving">确定</el-button>
<el-button @click="showknowledgeDialog = false">取消</el-button>
<el-button type="primary" @click="onSaveknowledge" :loading="knowledgeSaving">确定</el-button>
</template>
</el-dialog>
<!-- 上传文件弹窗 -->
<el-dialog
title="上传文件"
v-model="showUploadDialog"
width="600px"
:close-on-click-modal="false"
>
<el-dialog title="上传文件" v-model="showUploadDialog" width="600px" :close-on-click-modal="false">
<el-upload
ref="uploadRef"
class="upload-area"
@@ -327,8 +267,8 @@
<!-- 文档详情弹窗 -->
<DocumentDetailDialog
v-model="showDocumentDetailDialog"
:datasetId="currentDataset?.id || ''"
:datasetName="currentDataset?.name || ''"
:knowledgeId="currentknowledge?.id || ''"
:knowledgeName="currentknowledge?.name || ''"
:document="currentDocument"
/>
</div>
@@ -345,24 +285,37 @@ import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { FormInstance, FormRules, UploadFile } from 'element-plus';
import DocumentDetailDialog from './component/documentDetailDialog.vue';
import { listDatasets, createKnowledgeBase, updateKnowledgeBase, deleteDataset } from '/@/api/knowledge/dataset';
import { listknowledges, createknowledge, updateknowledge, deleteknowledge } from '/@/api/knowledge/dataset';
import { listDocuments, uploadFile, createDocument, deleteDocument } from '/@/api/knowledge/document';
// 数据集相关
const datasetLoading = ref(false);
const datasetList = ref<any[]>([]);
const currentDataset = ref<any>(null);
const showDatasetDialog = ref(false);
const datasetSaving = ref(false);
const datasetFormRef = ref<FormInstance>();
const datasetForm = reactive({
const knowledgeLoading = ref(false);
const knowledgeList = ref<any[]>([]);
const currentknowledge = ref<any>(null);
const showknowledgeDialog = ref(false);
const knowledgeSaving = ref(false);
const knowledgeFormRef = ref<FormInstance>();
const knowledgeForm = reactive({
id: '',
name: '',
description: '',
});
const datasetRules = reactive<FormRules>({
const knowledgeRules = reactive<FormRules>({
name: [{ required: true, message: '请输入知识库名称', trigger: 'blur' }],
});
// 文件列表含OSS上传结果
interface UploadFileItem {
file: UploadFile;
filePath: string;
fileSize: number;
fileFormat: string;
fileName: string;
uploading: boolean;
error: boolean;
}
const uploadFileItems = ref<UploadFileItem[]>([]);
// 文件相关
const fileLoading = ref(false);
const fileList = ref<any[]>([]);
@@ -401,7 +354,7 @@ const formatFileSize = (size: number) => {
};
// 获取文件图标颜色
const getFileIconColor = (fileType: string) => {
const _getFileIconColor = (fileType: string) => {
const colors: Record<string, string> = {
pdf: '#f56c6c',
docx: '#409eff',
@@ -415,7 +368,7 @@ const getFileIconColor = (fileType: string) => {
};
// 获取解析状态类型
const getParseStatusType = (status: string) => {
const _getParseStatusType = (status: string) => {
const types: Record<string, string> = {
general: 'success',
pending: 'warning',
@@ -425,40 +378,40 @@ const getParseStatusType = (status: string) => {
};
// 获取数据集列表
const getDatasetList = async () => {
datasetLoading.value = true;
const getknowledgeList = async () => {
knowledgeLoading.value = true;
try {
const response = await listDatasets({
const response = await listknowledges({
pageNum: 1,
pageSize: 100
pageSize: 100,
});
datasetList.value = response.data.list || [];
knowledgeList.value = response.data.list || [];
} catch (_error) {
ElMessage.error('获取知识库列表失败');
} finally {
datasetLoading.value = false;
knowledgeLoading.value = false;
}
};
// 选择数据集
const onSelectDataset = (item: any) => {
currentDataset.value = item;
const onSelectknowledge = (item: any) => {
currentknowledge.value = item;
activeMenu.value = 'files';
getFileList();
getLogList();
};
// 新增数据集
const onAddDataset = () => {
datasetForm.id = '';
datasetForm.name = '';
datasetForm.description = '';
showDatasetDialog.value = true;
const onAddknowledge = () => {
knowledgeForm.id = '';
knowledgeForm.name = '';
knowledgeForm.description = '';
showknowledgeDialog.value = true;
};
// 返回列表
const onBackToList = () => {
currentDataset.value = null;
currentknowledge.value = null;
};
// 右键菜单
@@ -467,64 +420,66 @@ const onCardContextMenu = (event: MouseEvent, item: any) => {
};
// 重命名数据集
const onRenameDataset = (item: any) => {
datasetForm.id = item.id;
datasetForm.name = item.name;
datasetForm.description = item.description || '';
showDatasetDialog.value = true;
const onRenameknowledge = (item: any) => {
knowledgeForm.id = item.id;
knowledgeForm.name = item.name;
knowledgeForm.description = item.description || '';
showknowledgeDialog.value = true;
};
// 删除数据集
const onDeleteDataset = (item: any) => {
const onDeleteknowledge = (item: any) => {
ElMessageBox.confirm(`确定要删除知识库【${item.name}】吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
})
.then(async () => {
try {
await deleteDataset(item.id);
await deleteknowledge(item.id);
ElMessage.success('删除成功');
if (currentDataset.value?.id === item.id) {
currentDataset.value = null;
if (currentknowledge.value?.id === item.id) {
currentknowledge.value = null;
}
getDatasetList();
getknowledgeList();
} catch (_error) {
ElMessage.error('删除失败,请重试');
}
}).catch(() => {});
})
.catch(() => {});
};
// 保存数据集
const onSaveDataset = async () => {
const form = datasetFormRef.value;
const onSaveknowledge = async () => {
const form = knowledgeFormRef.value;
if (!form) return;
form.validate(async (valid: boolean) => {
if (valid) {
datasetSaving.value = true;
knowledgeSaving.value = true;
try {
if (datasetForm.id) {
if (knowledgeForm.id) {
// 更新知识库
await updateKnowledgeBase({
id: datasetForm.id,
name: datasetForm.name,
description: datasetForm.description
await updateknowledge({
id: knowledgeForm.id,
name: knowledgeForm.name,
description: knowledgeForm.description,
});
} else {
// 创建知识库
await createKnowledgeBase({
name: datasetForm.name,
description: datasetForm.description
await createknowledge({
name: knowledgeForm.name,
description: knowledgeForm.description,
});
}
ElMessage.success(datasetForm.id ? '保存成功' : '创建成功');
showDatasetDialog.value = false;
getDatasetList();
ElMessage.success(knowledgeForm.id ? '保存成功' : '创建成功');
showknowledgeDialog.value = false;
getknowledgeList();
} catch (_error) {
ElMessage.error('保存失败,请重试');
} finally {
datasetSaving.value = false;
knowledgeSaving.value = false;
}
}
});
@@ -532,40 +487,17 @@ const onSaveDataset = async () => {
// 获取文件列表
const getFileList = async () => {
if (!currentDataset.value) return;
if (!currentknowledge.value) return;
fileLoading.value = true;
try {
// 模拟数据
fileList.value = [
{
id: '1',
name: '456_product(1).txt',
fileType: 'txt',
createdAt: '21/01/2026 16:53:32',
enabled: true,
chunkCount: 1,
parseStatus: 'general',
},
{
id: '2',
name: '123_speech(1).txt',
fileType: 'txt',
createdAt: '21/01/2026 16:53:26',
enabled: true,
chunkCount: 1,
parseStatus: 'general',
},
{
id: '3',
name: '123_product.txt',
fileType: 'txt',
createdAt: '21/01/2026 14:39:41',
enabled: true,
chunkCount: 1,
parseStatus: 'general',
},
];
const response = await listDocuments({
datasetId: currentknowledge.value.id,
...(searchKeyword.value ? { keyword: searchKeyword.value } : {}),
pageNum: 1,
pageSize: 100,
});
fileList.value = response.data?.list || [];
} catch (_error) {
ElMessage.error('获取文件列表失败');
} finally {
@@ -575,36 +507,78 @@ const getFileList = async () => {
// 上传文件
const onUploadFile = () => {
uploadFileItems.value = [];
uploadFileList.value = [];
showUploadDialog.value = true;
};
// 上传文件变化
const onUploadChange = (file: UploadFile, files: UploadFile[]) => {
// 选择文件时立即上传到OSS
const onUploadChange = async (file: UploadFile, files: UploadFile[]) => {
uploadFileList.value = files;
// 找出新增的文件(还没有对应的 item
const exists = uploadFileItems.value.some((i) => i.file.uid === file.uid);
if (exists || !file.raw) return;
const item: UploadFileItem = {
file,
filePath: '',
fileSize: 0,
fileFormat: '',
fileName: file.name,
uploading: true,
error: false,
};
uploadFileItems.value.push(item);
try {
const ossRes = await uploadFile(file.raw as File);
item.filePath = ossRes.data?.fileURL || '';
item.fileSize = ossRes.data?.fileSize || file.size || 0;
item.fileFormat = ossRes.data?.fileFormat || file.name.split('.').pop() || '';
item.fileName = ossRes.data?.fileName || file.name;
item.uploading = false;
} catch (_e) {
item.uploading = false;
item.error = true;
ElMessage.error(`${file.name} 上传失败`);
}
};
// 移除上传文件
const onUploadRemove = (file: UploadFile, files: UploadFile[]) => {
uploadFileList.value = files;
uploadFileItems.value = uploadFileItems.value.filter((i) => i.file.uid !== file.uid);
};
// 确认上传
// 确认上传所有文件已上传OSS直接创建文档
const onConfirmUpload = async () => {
const readyItems = uploadFileItems.value.filter((i) => !i.uploading && !i.error && i.filePath);
if (readyItems.length === 0) {
ElMessage.warning('请等待文件上传完成或移除上传失败的文件');
return;
}
uploading.value = true;
try {
ElMessage.success(`成功上传 ${uploadFileList.value.length} 个文件`);
for (const item of readyItems) {
const ext = item.file.name.split('.').pop() || '';
await createDocument({
datasetId: currentknowledge.value.id,
filePath: item.filePath,
fileSize: item.fileSize || item.file.size || 0,
format: item.fileFormat || ext,
title: item.fileName || item.file.name,
});
}
ElMessage.success(`成功创建 ${readyItems.length} 个文件`);
showUploadDialog.value = false;
getFileList();
} catch (_error) {
ElMessage.error('上传失败,请重试');
ElMessage.error('创建文档失败,请重试');
} finally {
uploading.value = false;
}
};
// 文件状态变化
const onFileStatusChange = (row: any) => {
const _onFileStatusChange = (row: any) => {
ElMessage.success(row.enabled ? '已启用' : '已禁用');
};
@@ -620,7 +594,7 @@ const onPreviewFile = (row: any) => {
};
// 下载文件
const onDownloadFile = (row: any) => {
const _onDownloadFile = (row: any) => {
ElMessage.info(`下载文件: ${row.name}`);
};
@@ -630,10 +604,17 @@ const onDeleteFile = (row: any) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
})
.then(async () => {
try {
await deleteDocument(row.id);
ElMessage.success('删除成功');
getFileList();
}).catch(() => {});
} catch (_error) {
ElMessage.error('删除失败,请重试');
}
})
.catch(() => {});
};
// 检索测试
@@ -656,7 +637,7 @@ const getLogList = () => {
{ time: '2026-01-21 16:53:32', content: '上传文件 456_product(1).txt' },
{ time: '2026-01-21 16:53:26', content: '上传文件 123_speech(1).txt' },
{ time: '2026-01-21 14:39:41', content: '上传文件 123_product.txt' },
{ time: '2026-01-17 10:00:00', content: '创建知识库 dataset_tenant_1' },
{ time: '2026-01-17 10:00:00', content: '创建知识库 knowledge_tenant_1' },
];
};
@@ -667,7 +648,7 @@ const onSaveSettings = () => {
// 页面加载
onMounted(() => {
getDatasetList();
getknowledgeList();
});
</script>
@@ -678,7 +659,7 @@ onMounted(() => {
box-sizing: border-box;
// 数据集列表页
.dataset-list-view {
.knowledge-list-view {
.page-header {
display: flex;
justify-content: space-between;
@@ -703,12 +684,12 @@ onMounted(() => {
}
}
.dataset-cards {
.knowledge-cards {
display: flex;
flex-wrap: wrap;
gap: 16px;
.dataset-card {
.knowledge-card {
width: 200px;
padding: 16px;
background: #fff;
@@ -804,7 +785,7 @@ onMounted(() => {
}
// 数据集详情页
.dataset-detail-view {
.knowledge-detail-view {
height: 100%;
display: flex;
flex-direction: column;
@@ -840,7 +821,7 @@ onMounted(() => {
flex-direction: column;
background: #fafafa;
.dataset-profile {
.knowledge-profile {
padding: 20px 16px;
border-bottom: 1px solid #ebeef5;

View File

@@ -212,7 +212,7 @@ export default defineComponent({
draggable: '.workflow-left-item',
forceFallback: true,
onEnd: function (evt: any) {
const { name, icon, id } = evt.clone.dataset;
const { name, icon, id } = evt.clone.knowledge;
const { layerX, layerY, clientX, clientY } = evt.originalEvent;
const el = state.workflowRightRef!;
const { x, y, width, height } = el.getBoundingClientRect();
@@ -617,8 +617,8 @@ export default defineComponent({
position: relative;
overflow: hidden;
height: 100%;
background-image: linear-gradient(90deg, rgb(156 214 255 / 15%) 10%, rgba(0, 0, 0, 0) 10%),
linear-gradient(rgb(156 214 255 / 15%) 10%, rgba(0, 0, 0, 0) 10%);
background-image:
linear-gradient(90deg, rgb(156 214 255 / 15%) 10%, rgba(0, 0, 0, 0) 10%), linear-gradient(rgb(156 214 255 / 15%) 10%, rgba(0, 0, 0, 0) 10%);
background-size: 10px 10px;
.workflow-right-clone {
position: absolute;

View File

@@ -707,7 +707,7 @@ export default defineComponent({
},
},
],
dataset: {
knowledge: {
source: [
{ status: '已签收', value1: 33, value2: 93 },
{ status: '配送中', value1: 53, value2: 32 },