将音频资产管理的文本转语音功能从标签页改为下拉选择模式,在知识库文件管理中将文档详情从路由跳转改为弹窗展示
This commit is contained in:
@@ -6,168 +6,172 @@
|
|||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<el-tabs v-model="activeTab" class="audio-tabs">
|
<!-- 模式切换下拉菜单 -->
|
||||||
<el-tab-pane label="上传音频" name="upload">
|
<div class="mode-switch">
|
||||||
<el-form ref="uploadFormRef" :model="uploadForm" :rules="uploadRules" label-width="100px">
|
<el-select v-model="activeMode" style="width: 160px">
|
||||||
<el-form-item label="音频名称" prop="name">
|
<el-option label="上传音频" value="upload" />
|
||||||
<el-input v-model="uploadForm.name" placeholder="请输入音频名称" maxlength="50" show-word-limit />
|
<el-option label="文本转语音" value="tts" />
|
||||||
</el-form-item>
|
</el-select>
|
||||||
|
</div>
|
||||||
<el-form-item label="音色类型" prop="voiceType">
|
|
||||||
<el-select v-model="uploadForm.voiceType" placeholder="请选择音色类型" style="width: 100%">
|
<!-- 上传音频模式 -->
|
||||||
<el-option label="男声" value="male" />
|
<el-form v-if="activeMode === 'upload'" ref="uploadFormRef" :model="uploadForm" :rules="uploadRules" label-width="100px" class="audio-form">
|
||||||
<el-option label="女声" value="female" />
|
<el-form-item label="音频名称" prop="name">
|
||||||
<el-option label="童声" value="child" />
|
<el-input v-model="uploadForm.name" placeholder="请输入音频名称" maxlength="50" show-word-limit />
|
||||||
</el-select>
|
</el-form-item>
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="上传文件" prop="file">
|
|
||||||
<el-upload
|
|
||||||
ref="uploadRef"
|
|
||||||
class="audio-uploader"
|
|
||||||
drag
|
|
||||||
action="#"
|
|
||||||
:auto-upload="false"
|
|
||||||
:limit="1"
|
|
||||||
accept=".mp3,.wav,.pcm,.flac"
|
|
||||||
:on-change="handleFileChange"
|
|
||||||
:on-exceed="handleExceed"
|
|
||||||
>
|
|
||||||
<el-icon class="el-icon--upload"><ele-UploadFilled /></el-icon>
|
|
||||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
|
||||||
<template #tip>
|
|
||||||
<div class="el-upload__tip">支持 mp3、wav、pcm、flac 格式,文件大小不超过 50MB</div>
|
|
||||||
</template>
|
|
||||||
</el-upload>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<!-- 上传预览 -->
|
|
||||||
<div v-if="uploadPreview.show" class="preview-section">
|
|
||||||
<div class="preview-header">
|
|
||||||
<span class="preview-title">文件信息</span>
|
|
||||||
<el-tag type="success" size="small">已选择</el-tag>
|
|
||||||
</div>
|
|
||||||
<div class="preview-info">
|
|
||||||
<span>文件名: {{ uploadPreview.fileName }}</span>
|
|
||||||
<span>文件大小: {{ formatFileSize(uploadPreview.fileSize) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-form>
|
|
||||||
</el-tab-pane>
|
|
||||||
|
|
||||||
<el-tab-pane label="文本转语音" name="tts">
|
<el-form-item label="音色类型" prop="voiceType">
|
||||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
<el-select v-model="uploadForm.voiceType" placeholder="请选择音色类型" style="width: 100%">
|
||||||
<el-form-item label="音频名称" prop="name">
|
<el-option label="男声" value="male" />
|
||||||
<el-input v-model="form.name" placeholder="请输入音频名称" maxlength="50" show-word-limit />
|
<el-option label="女声" value="female" />
|
||||||
|
<el-option label="童声" value="child" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="上传文件" prop="file">
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
class="audio-uploader"
|
||||||
|
drag
|
||||||
|
action="#"
|
||||||
|
:auto-upload="false"
|
||||||
|
:limit="1"
|
||||||
|
accept=".mp3,.wav,.pcm,.flac"
|
||||||
|
:on-change="handleFileChange"
|
||||||
|
:on-exceed="handleExceed"
|
||||||
|
>
|
||||||
|
<el-icon class="el-icon--upload"><ele-UploadFilled /></el-icon>
|
||||||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip">支持 mp3、wav、pcm、flac 格式,文件大小不超过 50MB</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- 上传预览 -->
|
||||||
|
<div v-if="uploadPreview.show" class="preview-section">
|
||||||
|
<div class="preview-header">
|
||||||
|
<span class="preview-title">文件信息</span>
|
||||||
|
<el-tag type="success" size="small">已选择</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="preview-info">
|
||||||
|
<span>文件名: {{ uploadPreview.fileName }}</span>
|
||||||
|
<span>文件大小: {{ formatFileSize(uploadPreview.fileSize) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 文本转语音模式 -->
|
||||||
|
<el-form v-else ref="formRef" :model="form" :rules="rules" label-width="100px" class="audio-form">
|
||||||
|
<el-form-item label="音频名称" prop="name">
|
||||||
|
<el-input v-model="form.name" placeholder="请输入音频名称" maxlength="50" show-word-limit />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="文本内容" prop="text">
|
||||||
|
<el-input
|
||||||
|
v-model="form.text"
|
||||||
|
type="textarea"
|
||||||
|
:rows="5"
|
||||||
|
placeholder="请输入要转换的文本内容"
|
||||||
|
maxlength="500"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="音色选择" prop="voice">
|
||||||
|
<el-select v-model="form.voice" placeholder="请选择音色" style="width: 100%">
|
||||||
|
<el-option-group label="男声">
|
||||||
|
<el-option label="商务男声" value="male_business" />
|
||||||
|
<el-option label="磁性男声" value="male_magnetic" />
|
||||||
|
<el-option label="新闻男声" value="male_news" />
|
||||||
|
</el-option-group>
|
||||||
|
<el-option-group label="女声">
|
||||||
|
<el-option label="甜美女声" value="female_sweet" />
|
||||||
|
<el-option label="知性女声" value="female_intellectual" />
|
||||||
|
<el-option label="温柔女声" value="female_gentle" />
|
||||||
|
</el-option-group>
|
||||||
|
<el-option-group label="童声">
|
||||||
|
<el-option label="活泼童声" value="child_lively" />
|
||||||
|
<el-option label="可爱童声" value="child_cute" />
|
||||||
|
</el-option-group>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="语速">
|
||||||
|
<el-slider v-model="form.speed" :min="0.5" :max="2" :step="0.1" :format-tooltip="(val: number) => val + 'x'" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-form-item label="文本内容" prop="text">
|
<el-col :span="12">
|
||||||
<el-input
|
<el-form-item label="音量">
|
||||||
v-model="form.text"
|
<el-slider v-model="form.volume" :min="0" :max="100" :format-tooltip="(val: number) => val + '%'" />
|
||||||
type="textarea"
|
|
||||||
:rows="5"
|
|
||||||
placeholder="请输入要转换的文本内容"
|
|
||||||
maxlength="500"
|
|
||||||
show-word-limit
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-form-item label="音色选择" prop="voice">
|
</el-row>
|
||||||
<el-select v-model="form.voice" placeholder="请选择音色" style="width: 100%">
|
|
||||||
<el-option-group label="男声">
|
<el-row :gutter="20">
|
||||||
<el-option label="商务男声" value="male_business" />
|
<el-col :span="12">
|
||||||
<el-option label="磁性男声" value="male_magnetic" />
|
<el-form-item label="音调">
|
||||||
<el-option label="新闻男声" value="male_news" />
|
<el-slider v-model="form.pitch" :min="-12" :max="12" :step="1" :format-tooltip="(val: number) => (val > 0 ? '+' : '') + val" />
|
||||||
</el-option-group>
|
</el-form-item>
|
||||||
<el-option-group label="女声">
|
</el-col>
|
||||||
<el-option label="甜美女声" value="female_sweet" />
|
<el-col :span="12">
|
||||||
<el-option label="知性女声" value="female_intellectual" />
|
<el-form-item label="采样率">
|
||||||
<el-option label="温柔女声" value="female_gentle" />
|
<el-select v-model="form.sampleRate" style="width: 100%">
|
||||||
</el-option-group>
|
<el-option label="16000Hz" :value="16000" />
|
||||||
<el-option-group label="童声">
|
<el-option label="22050Hz" :value="22050" />
|
||||||
<el-option label="活泼童声" value="child_lively" />
|
<el-option label="44100Hz" :value="44100" />
|
||||||
<el-option label="可爱童声" value="child_cute" />
|
<el-option label="48000Hz" :value="48000" />
|
||||||
</el-option-group>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-row :gutter="20">
|
</el-row>
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="语速">
|
<el-form-item label="输出格式">
|
||||||
<el-slider v-model="form.speed" :min="0.5" :max="2" :step="0.1" :format-tooltip="(val: number) => val + 'x'" />
|
<el-radio-group v-model="form.format">
|
||||||
</el-form-item>
|
<el-radio label="mp3">MP3</el-radio>
|
||||||
</el-col>
|
<el-radio label="wav">WAV</el-radio>
|
||||||
<el-col :span="12">
|
<el-radio label="pcm">PCM</el-radio>
|
||||||
<el-form-item label="音量">
|
</el-radio-group>
|
||||||
<el-slider v-model="form.volume" :min="0" :max="100" :format-tooltip="(val: number) => val + '%'" />
|
</el-form-item>
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
<!-- TTS预览区域 -->
|
||||||
</el-row>
|
<div v-if="previewGenerated" class="preview-section">
|
||||||
|
<div class="preview-header">
|
||||||
<el-row :gutter="20">
|
<span class="preview-title">预览</span>
|
||||||
<el-col :span="12">
|
<el-tag type="success" size="small">已生成</el-tag>
|
||||||
<el-form-item label="音调">
|
|
||||||
<el-slider v-model="form.pitch" :min="-12" :max="12" :step="1" :format-tooltip="(val: number) => (val > 0 ? '+' : '') + val" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="采样率">
|
|
||||||
<el-select v-model="form.sampleRate" style="width: 100%">
|
|
||||||
<el-option label="16000Hz" :value="16000" />
|
|
||||||
<el-option label="22050Hz" :value="22050" />
|
|
||||||
<el-option label="44100Hz" :value="44100" />
|
|
||||||
<el-option label="48000Hz" :value="48000" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-form-item label="输出格式">
|
|
||||||
<el-radio-group v-model="form.format">
|
|
||||||
<el-radio label="mp3">MP3</el-radio>
|
|
||||||
<el-radio label="wav">WAV</el-radio>
|
|
||||||
<el-radio label="pcm">PCM</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- TTS预览区域 -->
|
|
||||||
<div v-if="previewGenerated" class="preview-section">
|
|
||||||
<div class="preview-header">
|
|
||||||
<span class="preview-title">预览</span>
|
|
||||||
<el-tag type="success" size="small">已生成</el-tag>
|
|
||||||
</div>
|
|
||||||
<div class="preview-player">
|
|
||||||
<el-button
|
|
||||||
:type="isPlaying ? 'danger' : 'primary'"
|
|
||||||
circle
|
|
||||||
@click="togglePreview"
|
|
||||||
>
|
|
||||||
<el-icon>
|
|
||||||
<ele-VideoPlay v-if="!isPlaying" />
|
|
||||||
<ele-VideoPause v-else />
|
|
||||||
</el-icon>
|
|
||||||
</el-button>
|
|
||||||
<el-progress
|
|
||||||
:percentage="playProgress"
|
|
||||||
:show-text="false"
|
|
||||||
style="flex: 1; margin: 0 15px"
|
|
||||||
/>
|
|
||||||
<span class="duration">{{ formatDuration(previewDuration) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="preview-info">
|
|
||||||
<span>文件大小: {{ formatFileSize(previewFileSize) }}</span>
|
|
||||||
<span>时长: {{ formatDuration(previewDuration) }}</span>
|
|
||||||
<span>采样率: {{ form.sampleRate }}Hz</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
<div class="preview-player">
|
||||||
</el-tabs>
|
<el-button
|
||||||
|
:type="isPlaying ? 'danger' : 'primary'"
|
||||||
|
circle
|
||||||
|
@click="togglePreview"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<ele-VideoPlay v-if="!isPlaying" />
|
||||||
|
<ele-VideoPause v-else />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-progress
|
||||||
|
:percentage="playProgress"
|
||||||
|
:show-text="false"
|
||||||
|
style="flex: 1; margin: 0 15px"
|
||||||
|
/>
|
||||||
|
<span class="duration">{{ formatDuration(previewDuration) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-info">
|
||||||
|
<span>文件大小: {{ formatFileSize(previewFileSize) }}</span>
|
||||||
|
<span>时长: {{ formatDuration(previewDuration) }}</span>
|
||||||
|
<span>采样率: {{ form.sampleRate }}Hz</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="handleClose">取消</el-button>
|
<el-button @click="handleClose">取消</el-button>
|
||||||
<template v-if="activeTab === 'upload'">
|
<template v-if="activeMode === 'upload'">
|
||||||
<el-button type="primary" :loading="uploading" :disabled="!uploadPreview.show" @click="handleUpload">
|
<el-button type="primary" :loading="uploading" :disabled="!uploadPreview.show" @click="handleUpload">
|
||||||
<el-icon><ele-Upload /></el-icon>
|
<el-icon><ele-Upload /></el-icon>
|
||||||
{{ uploading ? '上传中...' : '上传音频' }}
|
{{ uploading ? '上传中...' : '上传音频' }}
|
||||||
@@ -196,7 +200,7 @@ import type { FormInstance, FormRules, UploadInstance, UploadFile, UploadRawFile
|
|||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const activeTab = ref('upload');
|
const activeMode = ref<'upload' | 'tts'>('upload');
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
const uploadFormRef = ref<FormInstance>();
|
const uploadFormRef = ref<FormInstance>();
|
||||||
const uploadRef = ref<UploadInstance>();
|
const uploadRef = ref<UploadInstance>();
|
||||||
@@ -260,7 +264,7 @@ const openDialog = () => {
|
|||||||
|
|
||||||
// 重置表单
|
// 重置表单
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
activeTab.value = 'upload';
|
activeMode.value = 'upload';
|
||||||
// 重置上传表单
|
// 重置上传表单
|
||||||
uploadForm.name = '';
|
uploadForm.name = '';
|
||||||
uploadForm.voiceType = '';
|
uploadForm.voiceType = '';
|
||||||
@@ -462,10 +466,12 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.audio-tabs {
|
.mode-switch {
|
||||||
:deep(.el-tabs__content) {
|
margin-bottom: 20px;
|
||||||
padding: 10px 0;
|
}
|
||||||
}
|
|
||||||
|
.audio-form {
|
||||||
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audio-uploader {
|
.audio-uploader {
|
||||||
|
|||||||
389
src/views/knowledge/component/documentDetailDialog.vue
Normal file
389
src/views/knowledge/component/documentDetailDialog.vue
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
<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>
|
||||||
@@ -323,6 +323,14 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 文档详情弹窗 -->
|
||||||
|
<DocumentDetailDialog
|
||||||
|
v-model="showDocumentDetailDialog"
|
||||||
|
:datasetId="currentDataset?.id || ''"
|
||||||
|
:datasetName="currentDataset?.name || ''"
|
||||||
|
:document="currentDocument"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -337,6 +345,7 @@ import { ref, reactive, onMounted } from 'vue';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import type { FormInstance, FormRules, UploadFile } from 'element-plus';
|
import type { FormInstance, FormRules, UploadFile } from 'element-plus';
|
||||||
|
import DocumentDetailDialog from './component/documentDetailDialog.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -364,6 +373,10 @@ const showUploadDialog = ref(false);
|
|||||||
const uploadFileList = ref<UploadFile[]>([]);
|
const uploadFileList = ref<UploadFile[]>([]);
|
||||||
const uploading = ref(false);
|
const uploading = ref(false);
|
||||||
|
|
||||||
|
// 文档详情弹窗
|
||||||
|
const showDocumentDetailDialog = ref(false);
|
||||||
|
const currentDocument = ref<any>(null);
|
||||||
|
|
||||||
// 菜单相关
|
// 菜单相关
|
||||||
const activeMenu = ref('files');
|
const activeMenu = ref('files');
|
||||||
|
|
||||||
@@ -590,15 +603,8 @@ const onFileStatusChange = (row: any) => {
|
|||||||
|
|
||||||
// 查看文档详情
|
// 查看文档详情
|
||||||
const onViewDocumentDetail = (row: any) => {
|
const onViewDocumentDetail = (row: any) => {
|
||||||
router.push({
|
currentDocument.value = row;
|
||||||
path: '/knowledge/document/detail',
|
showDocumentDetailDialog.value = true;
|
||||||
query: {
|
|
||||||
datasetId: currentDataset.value?.id,
|
|
||||||
datasetName: currentDataset.value?.name,
|
|
||||||
docId: row.id,
|
|
||||||
docName: row.name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 预览文件
|
// 预览文件
|
||||||
|
|||||||
Reference in New Issue
Block a user