|
|
@@ -37,12 +37,7 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 搜索栏下方工具条 -->
|
|
|
- <div class="toolbar-section">
|
|
|
- <el-button type="primary" @click="handleAdd">
|
|
|
- <el-icon><Plus /></el-icon> 新建片段
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
+
|
|
|
|
|
|
<div v-if="!queryParams.kb" class="empty-state">
|
|
|
<el-empty description="请先选择一个知识库以查看内容" />
|
|
|
@@ -61,17 +56,25 @@
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
- <el-table-column prop="code" label="片段编号" width="160" />
|
|
|
+ <el-table-column prop="code" label="片段编号" width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ row.document_id || row.code || '-' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
|
|
|
<el-table-column label="片段内容" min-width="300">
|
|
|
<template #default="{ row }">
|
|
|
<el-tooltip
|
|
|
effect="dark"
|
|
|
- :content="row.content"
|
|
|
placement="top"
|
|
|
:show-after="500"
|
|
|
:disabled="!row.content || row.content.length <= 200"
|
|
|
>
|
|
|
+ <template #content>
|
|
|
+ <div style="max-width: 400px; max-height: 300px; overflow-y: auto; white-space: pre-wrap;">
|
|
|
+ {{ row.content }}
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
<div class="content-cell">
|
|
|
{{ row.content && row.content.length > 200 ? row.content.substring(0, 200) + '...' : row.content }}
|
|
|
</div>
|
|
|
@@ -83,7 +86,32 @@
|
|
|
|
|
|
<el-table-column label="元数据信息" min-width="200">
|
|
|
<template #default="{ row }">
|
|
|
- <span class="meta-info">{{ formatMetaInfo(row) }}</span>
|
|
|
+ <el-tooltip
|
|
|
+ v-if="formatMetaInfo(row) && formatMetaInfo(row).length > 50"
|
|
|
+ effect="dark"
|
|
|
+ :content="formatMetaInfo(row)"
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <span class="meta-info truncate">{{ formatMetaInfo(row) }}</span>
|
|
|
+ </el-tooltip>
|
|
|
+ <span v-else class="meta-info">{{ formatMetaInfo(row) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="标签" min-width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="tags-container">
|
|
|
+ <el-tag
|
|
|
+ v-for="(tag, index) in parseTags(row.tag_list)"
|
|
|
+ :key="index"
|
|
|
+ size="small"
|
|
|
+ class="snippet-tag"
|
|
|
+ effect="plain"
|
|
|
+ >
|
|
|
+ {{ tag }}
|
|
|
+ </el-tag>
|
|
|
+ <span v-if="!row.tag_list" class="no-tags">-</span>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
@@ -177,19 +205,20 @@
|
|
|
<el-form-item label="文档名称" required>
|
|
|
<el-select
|
|
|
v-model="formData.doc_name"
|
|
|
- placeholder="请选择或输入文档名称"
|
|
|
+ placeholder="请输入文档名称进行搜索"
|
|
|
filterable
|
|
|
- allow-create
|
|
|
- default-first-option
|
|
|
+ remote
|
|
|
+ :remote-method="loadDocOptions"
|
|
|
:loading="docLoading"
|
|
|
style="width: 100%"
|
|
|
- @focus="loadDocOptions"
|
|
|
+ @focus="() => loadDocOptions('')"
|
|
|
+ @change="handleDocChange"
|
|
|
>
|
|
|
<el-option
|
|
|
v-for="item in docOptions"
|
|
|
- :key="item"
|
|
|
- :label="item"
|
|
|
- :value="item"
|
|
|
+ :key="item.value"
|
|
|
+ :label="item.label"
|
|
|
+ :value="item.value"
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
@@ -200,6 +229,48 @@
|
|
|
<el-input v-model="formData.parent_id" placeholder="可选: 输入父段ID" />
|
|
|
</el-form-item>
|
|
|
|
|
|
+ <el-form-item label="片段标签">
|
|
|
+ <el-tree-select
|
|
|
+ v-model="formData.tag_ids"
|
|
|
+ :data="tagTreeData"
|
|
|
+ multiple
|
|
|
+ :render-after-expand="false"
|
|
|
+ show-checkbox
|
|
|
+ check-strictly
|
|
|
+ node-key="id"
|
|
|
+ :props="{ label: 'name', children: 'children' }"
|
|
|
+ placeholder="请选择标签"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- 动态渲染元数据字段 -->
|
|
|
+ <template v-if="currentKbSchema.length > 0">
|
|
|
+ <el-divider content-position="left">元数据信息</el-divider>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12" v-for="field in currentKbSchema" :key="field.field_en_name">
|
|
|
+ <el-form-item :label="field.field_zh_name || field.field_en_name">
|
|
|
+ <el-input
|
|
|
+ v-if="field.field_type === 'text'"
|
|
|
+ v-model="formData.custom_fields[field.field_en_name]"
|
|
|
+ :placeholder="'请输入' + (field.field_zh_name || field.field_en_name)"
|
|
|
+ />
|
|
|
+ <el-input-number
|
|
|
+ v-else-if="field.field_type === 'num'"
|
|
|
+ v-model="formData.custom_fields[field.field_en_name]"
|
|
|
+ :placeholder="'请输入' + (field.field_zh_name || field.field_en_name)"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ <el-input
|
|
|
+ v-else
|
|
|
+ v-model="formData.custom_fields[field.field_en_name]"
|
|
|
+ :placeholder="'请输入' + (field.field_zh_name || field.field_en_name)"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </template>
|
|
|
+
|
|
|
<el-form-item label="片段内容" required>
|
|
|
<el-input
|
|
|
v-model="formData.content"
|
|
|
@@ -237,6 +308,18 @@
|
|
|
{{ viewData.status === 'normal' ? '启用' : '禁用' }}
|
|
|
</el-tag>
|
|
|
</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="标签">
|
|
|
+ <div class="tags-container">
|
|
|
+ <el-tag
|
|
|
+ v-for="(tag, index) in parseTags(viewData.tag_list)"
|
|
|
+ :key="index"
|
|
|
+ size="small"
|
|
|
+ class="snippet-tag"
|
|
|
+ >
|
|
|
+ {{ tag }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </el-descriptions-item>
|
|
|
<el-descriptions-item label="元数据信息">{{ formatMetaInfo(viewData) }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="创建时间">{{ viewData.created_at || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="修改时间">{{ viewData.updated_at || '-' }}</el-descriptions-item>
|
|
|
@@ -264,6 +347,7 @@ import {
|
|
|
type Snippet
|
|
|
} from '@/api/snippet'
|
|
|
import { getKnowledgeBases, getKnowledgeBaseMetadata, type KnowledgeBase } from '@/api/knowledge-base'
|
|
|
+import { tagApi } from '@/api/tag'
|
|
|
|
|
|
// Table Data
|
|
|
const tableData = ref<Snippet[]>([])
|
|
|
@@ -291,35 +375,86 @@ const formData = reactive({
|
|
|
id: '',
|
|
|
collection_name: '',
|
|
|
doc_name: '',
|
|
|
+ selected_doc_id: '',
|
|
|
+ selected_doc_name: '',
|
|
|
parent_id: '',
|
|
|
content: '',
|
|
|
- custom_fields: {} as Record<string, any>
|
|
|
+ custom_fields: {} as Record<string, any>,
|
|
|
+ tag_ids: [] as number[]
|
|
|
})
|
|
|
|
|
|
-const docOptions = ref<string[]>([])
|
|
|
+// 解析标签字符串
|
|
|
+const parseTags = (tagListStr: string | undefined) => {
|
|
|
+ if (!tagListStr) return []
|
|
|
+ if (tagListStr.includes(',')) {
|
|
|
+ return tagListStr.split(',').filter(t => t)
|
|
|
+ }
|
|
|
+ return [tagListStr]
|
|
|
+}
|
|
|
+
|
|
|
+const getTagNamesByIds = (ids: number[]) => {
|
|
|
+ const names: string[] = []
|
|
|
+ const findName = (nodes: any[]) => {
|
|
|
+ for (const node of nodes) {
|
|
|
+ if (ids.includes(node.id)) {
|
|
|
+ names.push(node.name)
|
|
|
+ }
|
|
|
+ if (node.children) {
|
|
|
+ findName(node.children)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ findName(tagTreeData.value)
|
|
|
+ return names.join(',')
|
|
|
+}
|
|
|
+
|
|
|
+const getTagIdsByNames = (tagStr: string) => {
|
|
|
+ if (!tagStr) return []
|
|
|
+ const names = tagStr.split(',').filter(t => t)
|
|
|
+ const ids: number[] = []
|
|
|
+
|
|
|
+ const findId = (nodes: any[]) => {
|
|
|
+ for (const node of nodes) {
|
|
|
+ if (names.includes(node.name)) {
|
|
|
+ ids.push(node.id)
|
|
|
+ }
|
|
|
+ if (node.children) {
|
|
|
+ findId(node.children)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ findId(tagTreeData.value)
|
|
|
+ return ids
|
|
|
+}
|
|
|
+
|
|
|
+const docOptions = ref<{label: string, value: string}[]>([])
|
|
|
const docLoading = ref(false)
|
|
|
|
|
|
-const loadDocOptions = async () => {
|
|
|
- if (!formData.collection_name) return
|
|
|
+import { documentApi, type DocumentItem } from '@/api/document'
|
|
|
+
|
|
|
+// ...
|
|
|
+
|
|
|
+const loadDocOptions = async (query: string) => {
|
|
|
+ // 只有当有输入或者初始化(空字符串)时才搜索
|
|
|
+ // 这里允许空字符串查询,即显示最近的一些文档
|
|
|
docLoading.value = true
|
|
|
try {
|
|
|
- // 使用一个特殊的查询来获取该知识库下的所有文档名(去重)
|
|
|
- // 这里暂时使用 getSnippets 模拟,实际上应该有一个 getDocList 接口
|
|
|
- // 为了简单,我们查询最近的 100 条记录并提取文档名
|
|
|
- const res = await getSnippets({
|
|
|
+ const res = await documentApi.getList({
|
|
|
page: 1,
|
|
|
- page_size: 100,
|
|
|
- kb: formData.collection_name
|
|
|
- })
|
|
|
+ size: 50, // 限制返回数量,作为搜索结果
|
|
|
+ keyword: query, // 将输入作为关键字
|
|
|
+ // whether_to_enter: 1 // 移除此过滤,搜索所有文档
|
|
|
+ }, true)
|
|
|
+
|
|
|
if (res.code === 0) {
|
|
|
- const names = new Set<string>()
|
|
|
- res.data.forEach(item => {
|
|
|
- if (item.doc_name) names.add(item.doc_name)
|
|
|
- })
|
|
|
- docOptions.value = Array.from(names)
|
|
|
+ // 映射为 label (title) 和 value (id)
|
|
|
+ docOptions.value = res.data.items.map(item => ({
|
|
|
+ label: item.title,
|
|
|
+ value: item.id
|
|
|
+ }))
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- console.error(error)
|
|
|
+ console.error("加载文档列表失败", error)
|
|
|
} finally {
|
|
|
docLoading.value = false
|
|
|
}
|
|
|
@@ -335,10 +470,12 @@ const currentKbSchema = ref<any[]>([]) // 当前选中知识库的自定义Schem
|
|
|
const handleKbChange = async (collection_name: string) => {
|
|
|
// 清空文档选择
|
|
|
formData.doc_name = ''
|
|
|
+ formData.selected_doc_id = ''
|
|
|
+ formData.selected_doc_name = ''
|
|
|
docOptions.value = []
|
|
|
|
|
|
- // 自动加载文档列表
|
|
|
- loadDocOptions()
|
|
|
+ // 自动加载文档列表 (初始加载最近文档)
|
|
|
+ loadDocOptions('')
|
|
|
|
|
|
// 找到对应的 KB ID
|
|
|
const kb = kbOptions.value.find(k => k.value === collection_name)
|
|
|
@@ -347,10 +484,24 @@ const handleKbChange = async (collection_name: string) => {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 由于后端 Schema 已固定,不再动态加载自定义字段
|
|
|
- currentKbSchema.value = []
|
|
|
+ // 加载知识库的元数据定义
|
|
|
+ try {
|
|
|
+ const res = await getKnowledgeBaseMetadata(kb.id)
|
|
|
+ if (res.code === 0) {
|
|
|
+ if (Array.isArray(res.data)) {
|
|
|
+ currentKbSchema.value = res.data
|
|
|
+ } else if (res.data && Array.isArray(res.data.metadata_fields)) {
|
|
|
+ currentKbSchema.value = res.data.metadata_fields
|
|
|
+ } else {
|
|
|
+ currentKbSchema.value = []
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("加载元数据定义失败", error)
|
|
|
+ currentKbSchema.value = []
|
|
|
+ }
|
|
|
|
|
|
- // 初始化 custom_fields (如果需要的话,但现在没有动态字段了)
|
|
|
+ // 初始化 custom_fields
|
|
|
formData.custom_fields = {}
|
|
|
}
|
|
|
|
|
|
@@ -418,8 +569,39 @@ const loadKbOptions = async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 标签树数据
|
|
|
+const tagTreeData = ref<any[]>([])
|
|
|
+
|
|
|
+// 递归处理树数据,禁用非 label 节点
|
|
|
+const processTagTree = (nodes: any[]) => {
|
|
|
+ return nodes.map(node => {
|
|
|
+ // 如果不是 label 类型,禁用选择
|
|
|
+ if (node.type !== 'label') {
|
|
|
+ node.disabled = true
|
|
|
+ }
|
|
|
+
|
|
|
+ if (node.children && node.children.length > 0) {
|
|
|
+ node.children = processTagTree(node.children)
|
|
|
+ }
|
|
|
+ return node
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 加载标签树
|
|
|
+const loadTagTree = async () => {
|
|
|
+ try {
|
|
|
+ const res = await tagApi.getCategoryTree(false) // 不包含禁用
|
|
|
+ if (res.code === 200) {
|
|
|
+ tagTreeData.value = processTagTree(res.data)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("加载标签树失败", error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
loadKbOptions()
|
|
|
+ loadTagTree()
|
|
|
})
|
|
|
|
|
|
// Methods
|
|
|
@@ -428,62 +610,166 @@ const handleSearch = () => {
|
|
|
loadData()
|
|
|
}
|
|
|
|
|
|
-const handleAdd = () => {
|
|
|
+const handleAdd = async () => {
|
|
|
dialogType.value = 'add'
|
|
|
resetForm()
|
|
|
- // 如果筛选栏选中了知识库,默认填入
|
|
|
+ // 如果筛选栏选中了知识库,默认填入并加载元数据
|
|
|
if (queryParams.kb) {
|
|
|
formData.collection_name = queryParams.kb
|
|
|
+ await handleKbChange(queryParams.kb)
|
|
|
}
|
|
|
dialogVisible.value = true
|
|
|
}
|
|
|
|
|
|
-const handleEdit = (row: Snippet) => {
|
|
|
+const handleEdit = async (row: Snippet) => {
|
|
|
dialogType.value = 'edit'
|
|
|
formData.id = row.id
|
|
|
formData.collection_name = row.collection_name
|
|
|
+
|
|
|
+ // 触发加载 Schema (会重置 doc_name 和 custom_fields)
|
|
|
+ await handleKbChange(row.collection_name)
|
|
|
+
|
|
|
+ // 恢复数据
|
|
|
formData.doc_name = row.doc_name
|
|
|
formData.content = row.content
|
|
|
- formData.parent_id = '' // 列表中没有返回 parent_id,暂时留空或需要额外请求获取
|
|
|
- formData.custom_fields = {}
|
|
|
|
|
|
- // 触发加载文档列表
|
|
|
- loadDocOptions()
|
|
|
+ // 恢复 parent_id
|
|
|
+ formData.parent_id = row.parent_id || (row.metadata && (row.metadata as any).parent_id) || ''
|
|
|
|
|
|
- // 触发加载 Schema
|
|
|
- handleKbChange(row.collection_name)
|
|
|
+ // 恢复自定义字段
|
|
|
+ if (row.metadata && typeof row.metadata === 'object') {
|
|
|
+ formData.custom_fields = { ...row.metadata }
|
|
|
+ } else {
|
|
|
+ formData.custom_fields = {}
|
|
|
+ }
|
|
|
+
|
|
|
+ // 回显标签
|
|
|
+ formData.tag_ids = getTagIdsByNames(row.tag_list || (row as any).tags)
|
|
|
|
|
|
dialogVisible.value = true
|
|
|
}
|
|
|
|
|
|
+const handleDocChange = async (val: string) => {
|
|
|
+ // val 是选中的文档ID
|
|
|
+ // 找到对应的文档名称用于显示(如果需要的话,但 formData.doc_name 已经绑定了 val 即 ID)
|
|
|
+ // 这里我们需要注意:formData.doc_name 存储的是 ID 还是 Name?
|
|
|
+ // 根据用户需求:"新建时选择的文档的id就是对应知识库的schema中的document_id的值"
|
|
|
+ // 同时也需要显示文档名称
|
|
|
+
|
|
|
+ // 实际上,Snippet 结构中 doc_name 应该存名称,document_id 存 ID
|
|
|
+ // 但前端 el-select v-model 绑定的是 value (ID)
|
|
|
+
|
|
|
+ // 我们需要调整 formData 结构或者在提交时处理
|
|
|
+ // 让我们查找选中的项
|
|
|
+ const selected = docOptions.value.find(item => item.value === val)
|
|
|
+ if (selected) {
|
|
|
+ // 将选中的文档ID保存到 custom_fields.document_id (如果 Schema 中有) 或者单独保存
|
|
|
+ // 实际上 Snippet Create 接口需要 doc_name 和 document_id
|
|
|
+ // 我们可以暂时将 ID 存入 formData 的一个临时字段,或者直接修改提交逻辑
|
|
|
+ formData.selected_doc_id = val
|
|
|
+ formData.selected_doc_name = selected.label
|
|
|
+
|
|
|
+ // 自动填充元数据 (如果当前 KB 有定义的 Schema)
|
|
|
+ if (currentKbSchema.value.length > 0) {
|
|
|
+ try {
|
|
|
+ // 获取文档详情
|
|
|
+ const res = await documentApi.getDetail(val)
|
|
|
+ if (res.code === 0 && res.data) {
|
|
|
+ const doc = res.data
|
|
|
+
|
|
|
+ // 定义元数据字段名与文档属性的映射关系
|
|
|
+ const mapping: Record<string, keyof DocumentItem> = {
|
|
|
+ 'file_name': 'title',
|
|
|
+ 'title': 'title',
|
|
|
+ 'standard_number': 'standard_no',
|
|
|
+ 'standard_no': 'standard_no',
|
|
|
+ 'issuing_authority': 'issuing_authority',
|
|
|
+ 'document_type': 'document_type',
|
|
|
+ 'professional_field': 'professional_field',
|
|
|
+ 'validity': 'validity',
|
|
|
+ 'file_url': 'file_url',
|
|
|
+ 'plan_type_list': 'plan_category',
|
|
|
+ 'plan_category': 'plan_category'
|
|
|
+ }
|
|
|
+
|
|
|
+ // 遍历当前 KB 定义的所有字段
|
|
|
+ currentKbSchema.value.forEach(field => {
|
|
|
+ const fieldName = field.field_en_name
|
|
|
+ let docValue: any = null
|
|
|
+
|
|
|
+ // 1. 尝试映射匹配
|
|
|
+ if (mapping[fieldName] && doc[mapping[fieldName]] !== undefined) {
|
|
|
+ docValue = doc[mapping[fieldName]]
|
|
|
+ }
|
|
|
+ // 2. 尝试直接属性名匹配
|
|
|
+ else if (fieldName in doc) {
|
|
|
+ docValue = doc[fieldName as keyof DocumentItem]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 特殊处理:层级信息 (hierarchy)
|
|
|
+ if (fieldName === 'hierarchy') {
|
|
|
+ const levels = [
|
|
|
+ doc.level_1_classification,
|
|
|
+ doc.level_2_classification,
|
|
|
+ doc.level_3_classification,
|
|
|
+ doc.level_4_classification
|
|
|
+ ].filter(l => l) // 过滤掉空值
|
|
|
+
|
|
|
+ if (levels.length > 0) {
|
|
|
+ docValue = levels.join('/')
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果找到了对应的值,且不为空,则填充
|
|
|
+ if (docValue !== undefined && docValue !== null && docValue !== '') {
|
|
|
+ formData.custom_fields[fieldName] = docValue
|
|
|
+ }
|
|
|
+ })
|
|
|
+ ElMessage.success('已自动填充文档元数据')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("自动填充元数据失败", error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
const handleSubmit = async () => {
|
|
|
if (!formData.collection_name || !formData.content) {
|
|
|
ElMessage.warning('请填写完整信息')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
+ const tagListStr = getTagNamesByIds(formData.tag_ids)
|
|
|
+
|
|
|
submitLoading.value = true
|
|
|
try {
|
|
|
if (dialogType.value === 'add') {
|
|
|
await createSnippet({
|
|
|
collection_name: formData.collection_name,
|
|
|
- doc_name: formData.doc_name || '手动添加',
|
|
|
+ // 如果用户选择了文档,使用选择的名称;否则(兼容旧逻辑)使用输入值
|
|
|
+ doc_name: formData.selected_doc_name || formData.doc_name || '手动添加',
|
|
|
content: formData.content,
|
|
|
meta_info: '',
|
|
|
custom_fields: {
|
|
|
...formData.custom_fields,
|
|
|
- parent_id: formData.parent_id // 传递父段ID
|
|
|
+ parent_id: formData.parent_id,
|
|
|
+ // 传递 document_id
|
|
|
+ document_id: formData.selected_doc_id,
|
|
|
+ tag_list: tagListStr
|
|
|
}
|
|
|
})
|
|
|
ElMessage.success('创建成功')
|
|
|
} else {
|
|
|
+ // 编辑模式下的处理...
|
|
|
await updateSnippet(formData.id, {
|
|
|
collection_name: formData.collection_name,
|
|
|
- doc_name: formData.doc_name,
|
|
|
+ doc_name: formData.doc_name, // 编辑时通常不改文档归属,或者需要同样的逻辑
|
|
|
content: formData.content,
|
|
|
custom_fields: {
|
|
|
...formData.custom_fields,
|
|
|
- parent_id: formData.parent_id
|
|
|
+ parent_id: formData.parent_id,
|
|
|
+ tag_list: tagListStr
|
|
|
}
|
|
|
})
|
|
|
ElMessage.success('更新成功')
|
|
|
@@ -501,9 +787,12 @@ const resetForm = () => {
|
|
|
formData.id = ''
|
|
|
formData.collection_name = ''
|
|
|
formData.doc_name = ''
|
|
|
+ formData.selected_doc_id = ''
|
|
|
+ formData.selected_doc_name = ''
|
|
|
formData.parent_id = ''
|
|
|
formData.content = ''
|
|
|
formData.custom_fields = {}
|
|
|
+ formData.tag_ids = []
|
|
|
docOptions.value = []
|
|
|
currentKbSchema.value = []
|
|
|
}
|
|
|
@@ -546,12 +835,28 @@ const formatMetaInfo = (row: Snippet) => {
|
|
|
const displayParts = []
|
|
|
for (const [key, value] of Object.entries(row.metadata)) {
|
|
|
if (!['doc_name', 'file_name', 'title'].includes(key)) {
|
|
|
- displayParts.push(`${key}: ${value}`)
|
|
|
+ // 简单格式化:Key: Value
|
|
|
+ // 如果 value 也是对象,转字符串
|
|
|
+ const valStr = typeof value === 'object' ? JSON.stringify(value) : String(value)
|
|
|
+ displayParts.push(`${key}: ${valStr}`)
|
|
|
}
|
|
|
}
|
|
|
if (displayParts.length > 0) return displayParts.join(' | ')
|
|
|
- } else {
|
|
|
- return String(row.metadata)
|
|
|
+ } else if (typeof row.metadata === 'string') {
|
|
|
+ try {
|
|
|
+ // 尝试解析 JSON 字符串
|
|
|
+ const metaObj = JSON.parse(row.metadata)
|
|
|
+ const displayParts = []
|
|
|
+ for (const [key, value] of Object.entries(metaObj)) {
|
|
|
+ if (!['doc_name', 'file_name', 'title'].includes(key)) {
|
|
|
+ const valStr = typeof value === 'object' ? JSON.stringify(value) : String(value)
|
|
|
+ displayParts.push(`${key}: ${valStr}`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (displayParts.length > 0) return displayParts.join(' | ')
|
|
|
+ } catch (e) {
|
|
|
+ return String(row.metadata)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -608,12 +913,7 @@ const handleCurrentChange = (val: number) => {
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
-.toolbar-section {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 16px;
|
|
|
- gap: 12px;
|
|
|
-}
|
|
|
+
|
|
|
|
|
|
.table-card {
|
|
|
border-radius: 8px;
|
|
|
@@ -644,6 +944,14 @@ const handleCurrentChange = (val: number) => {
|
|
|
.meta-info {
|
|
|
color: #909399;
|
|
|
font-size: 13px;
|
|
|
+ display: inline-block;
|
|
|
+ max-width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.meta-info.truncate {
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
.status-tag {
|
|
|
@@ -697,4 +1005,17 @@ const handleCurrentChange = (val: number) => {
|
|
|
.snippet-form {
|
|
|
padding-right: 20px;
|
|
|
}
|
|
|
+
|
|
|
+.tags-container {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+.snippet-tag {
|
|
|
+ margin-right: 0;
|
|
|
+}
|
|
|
+.no-tags {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
</style>
|