390 lines
8.5 KiB
Vue
390 lines
8.5 KiB
Vue
<template>
|
||
<el-dialog
|
||
v-model="visible"
|
||
:title="documentInfo.name || '文档详情'"
|
||
width="90%"
|
||
top="5vh"
|
||
:close-on-click-modal="false"
|
||
destroy-on-close
|
||
class="document-detail-dialog"
|
||
>
|
||
<div class="dialog-content">
|
||
<!-- 左侧:文档原文 -->
|
||
<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>
|
||
<div class="content-body" v-loading="contentLoading">
|
||
<pre class="document-text">{{ documentContent }}</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:切片结果 -->
|
||
<div class="chunk-panel">
|
||
<div class="panel-header">
|
||
<h3>切片结果</h3>
|
||
<div class="panel-subtitle">查看用于嵌入和召回的切片段落。</div>
|
||
</div>
|
||
|
||
<div class="panel-toolbar">
|
||
<div class="toolbar-tabs">
|
||
<el-radio-group v-model="viewMode" size="small">
|
||
<el-radio-button label="full">全文</el-radio-button>
|
||
<el-radio-button label="chunk">省略</el-radio-button>
|
||
</el-radio-group>
|
||
</div>
|
||
<div class="toolbar-actions">
|
||
<el-input
|
||
v-model="chunkSearch"
|
||
placeholder="搜索"
|
||
clearable
|
||
size="small"
|
||
style="width: 120px"
|
||
>
|
||
<template #prefix>
|
||
<el-icon><ele-Search /></el-icon>
|
||
</template>
|
||
</el-input>
|
||
<el-button size="small" @click="onAddChunk">
|
||
<el-icon><ele-Plus /></el-icon>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chunk-list" v-loading="chunkLoading">
|
||
<div class="select-all">
|
||
<el-checkbox v-model="selectAll" @change="onSelectAllChange">选择所有</el-checkbox>
|
||
</div>
|
||
|
||
<div
|
||
class="chunk-item"
|
||
v-for="(chunk, index) in filteredChunks"
|
||
:key="chunk.id"
|
||
>
|
||
<div class="chunk-checkbox">
|
||
<el-checkbox v-model="chunk.selected" />
|
||
</div>
|
||
<div class="chunk-content">
|
||
<span class="chunk-text">{{ viewMode === 'full' ? chunk.content : truncateText(chunk.content, 100) }}</span>
|
||
</div>
|
||
<div class="chunk-actions">
|
||
<el-switch v-model="chunk.enabled" size="small" @change="onChunkStatusChange(chunk)" />
|
||
</div>
|
||
</div>
|
||
|
||
<el-empty v-if="filteredChunks.length === 0 && !chunkLoading" description="暂无切片" :image-size="60" />
|
||
</div>
|
||
|
||
<!-- 分页 -->
|
||
<div class="panel-footer">
|
||
<span class="total-info">总共 {{ chunkTotal }} 条</span>
|
||
<el-pagination
|
||
v-model:current-page="chunkPage"
|
||
:page-size="chunkPageSize"
|
||
:total="chunkTotal"
|
||
layout="prev, pager, next"
|
||
small
|
||
@current-change="getChunkList"
|
||
/>
|
||
<el-select v-model="chunkPageSize" size="small" style="width: 80px" @change="getChunkList">
|
||
<el-option :value="10" label="10条/页" />
|
||
<el-option :value="20" label="20条/页" />
|
||
<el-option :value="50" label="50条/页" />
|
||
</el-select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
export default {
|
||
name: 'documentDetailDialog',
|
||
};
|
||
</script>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, computed, watch } from 'vue';
|
||
import { ElMessage } from 'element-plus';
|
||
|
||
const props = defineProps<{
|
||
modelValue: boolean;
|
||
datasetId: string;
|
||
datasetName: string;
|
||
document: any;
|
||
}>();
|
||
|
||
const emit = defineEmits(['update:modelValue']);
|
||
|
||
const visible = computed({
|
||
get: () => props.modelValue,
|
||
set: (val) => emit('update:modelValue', val),
|
||
});
|
||
|
||
// 文档信息
|
||
const documentInfo = reactive({
|
||
id: '',
|
||
name: '',
|
||
fileType: '',
|
||
fileSize: 0,
|
||
createdAt: '',
|
||
});
|
||
|
||
// 文档内容
|
||
const documentContent = ref('');
|
||
const contentLoading = ref(false);
|
||
|
||
// 切片相关
|
||
const chunkLoading = ref(false);
|
||
const chunkList = ref<any[]>([]);
|
||
const chunkTotal = ref(0);
|
||
const chunkPage = ref(1);
|
||
const chunkPageSize = ref(50);
|
||
const chunkSearch = ref('');
|
||
const viewMode = ref('full');
|
||
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())
|
||
);
|
||
});
|
||
|
||
// 格式化文件大小
|
||
const formatFileSize = (size: number) => {
|
||
if (!size) return '0 Bytes';
|
||
if (size < 1024) return size + ' Bytes';
|
||
if (size < 1024 * 1024) return (size / 1024).toFixed(0) + ' KB';
|
||
return (size / 1024 / 1024).toFixed(1) + ' MB';
|
||
};
|
||
|
||
// 截断文本
|
||
const truncateText = (text: string, maxLength: number) => {
|
||
if (!text || text.length <= maxLength) return text;
|
||
return text.substring(0, maxLength) + '...';
|
||
};
|
||
|
||
// 获取文档详情
|
||
const getDocumentDetail = async () => {
|
||
contentLoading.value = true;
|
||
try {
|
||
// 模拟数据
|
||
documentInfo.id = props.document?.id || '';
|
||
documentInfo.name = props.document?.name || '456_product(1).txt';
|
||
documentInfo.fileType = props.document?.fileType || 'txt';
|
||
documentInfo.fileSize = props.document?.fileSize || 10;
|
||
documentInfo.createdAt = props.document?.createdAt || '22/01/2026 00:53:32';
|
||
|
||
// 模拟文档内容
|
||
documentContent.value = '<p>123</p>';
|
||
} catch (error) {
|
||
console.error('获取文档详情失败:', error);
|
||
} finally {
|
||
contentLoading.value = false;
|
||
}
|
||
};
|
||
|
||
// 获取切片列表
|
||
const getChunkList = async () => {
|
||
chunkLoading.value = true;
|
||
try {
|
||
// 模拟数据
|
||
chunkList.value = [
|
||
{
|
||
id: '1',
|
||
content: '123',
|
||
enabled: true,
|
||
selected: false,
|
||
},
|
||
];
|
||
chunkTotal.value = 1;
|
||
} catch (error) {
|
||
console.error('获取切片列表失败:', error);
|
||
} finally {
|
||
chunkLoading.value = false;
|
||
}
|
||
};
|
||
|
||
// 全选变化
|
||
const onSelectAllChange = (val: boolean) => {
|
||
chunkList.value.forEach(chunk => {
|
||
chunk.selected = val;
|
||
});
|
||
};
|
||
|
||
// 切片状态变化
|
||
const onChunkStatusChange = (chunk: any) => {
|
||
ElMessage.success(chunk.enabled ? '已启用' : '已禁用');
|
||
};
|
||
|
||
// 添加切片
|
||
const onAddChunk = () => {
|
||
ElMessage.info('添加切片功能开发中');
|
||
};
|
||
|
||
// 监听弹窗打开
|
||
watch(() => props.modelValue, (val) => {
|
||
if (val && props.document) {
|
||
getDocumentDetail();
|
||
getChunkList();
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.document-detail-dialog {
|
||
:deep(.el-dialog__body) {
|
||
padding: 0;
|
||
max-height: calc(90vh - 100px);
|
||
overflow: hidden;
|
||
}
|
||
}
|
||
|
||
.dialog-content {
|
||
display: flex;
|
||
height: 70vh;
|
||
|
||
// 左侧文档内容
|
||
.document-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
border-right: 1px solid #ebeef5;
|
||
overflow: hidden;
|
||
|
||
.content-header {
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
|
||
h2 {
|
||
margin: 0 0 8px 0;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.content-meta {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
|
||
.content-body {
|
||
flex: 1;
|
||
padding: 16px 20px;
|
||
overflow: auto;
|
||
background: #fafafa;
|
||
|
||
.document-text {
|
||
margin: 0;
|
||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||
font-size: 14px;
|
||
line-height: 1.6;
|
||
color: #303133;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 右侧切片面板
|
||
.chunk-panel {
|
||
width: 420px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #fff;
|
||
|
||
.panel-header {
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
|
||
h3 {
|
||
margin: 0 0 4px 0;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.panel-subtitle {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
|
||
.panel-toolbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12px 20px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
|
||
.toolbar-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
}
|
||
|
||
.chunk-list {
|
||
flex: 1;
|
||
overflow: auto;
|
||
padding: 12px 20px;
|
||
|
||
.select-all {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.chunk-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
padding: 12px;
|
||
background: #f5f7fa;
|
||
border-radius: 6px;
|
||
margin-bottom: 8px;
|
||
border: 1px solid #ebeef5;
|
||
|
||
.chunk-checkbox {
|
||
margin-right: 12px;
|
||
padding-top: 2px;
|
||
}
|
||
|
||
.chunk-content {
|
||
flex: 1;
|
||
min-width: 0;
|
||
|
||
.chunk-text {
|
||
font-size: 14px;
|
||
color: #303133;
|
||
line-height: 1.5;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
|
||
.chunk-actions {
|
||
margin-left: 12px;
|
||
flex-shrink: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.panel-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
padding: 12px 20px;
|
||
border-top: 1px solid #ebeef5;
|
||
|
||
.total-info {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|