|
|
|
@ -59,8 +59,7 @@ |
|
|
|
<t-button variant="outline" size="small" @click="batchReprocess">批量重新处理</t-button> |
|
|
|
<t-button variant="outline" size="small" @click="batchEnable">批量启用</t-button> |
|
|
|
<t-button variant="outline" size="small" @click="batchDisable">批量禁用</t-button> |
|
|
|
<t-select v-model="moveCategoryId" :options="moveCategoryOptions" placeholder="移动到分类..." size="small" style="width:140px;" /> |
|
|
|
<t-button variant="outline" size="small" @click="batchMove" :disabled="moveCategoryId === ''">移动</t-button> |
|
|
|
<t-button variant="outline" size="small" @click="openMove(Array.from(selectedIds))">移动</t-button> |
|
|
|
<t-button variant="text" size="small" @click="clearSelection">取消选择</t-button> |
|
|
|
</template> |
|
|
|
</div> |
|
|
|
@ -96,6 +95,7 @@ |
|
|
|
<t-space :size="2"> |
|
|
|
<t-button size="small" variant="text" @click="viewDetail(row.id)">查看</t-button> |
|
|
|
<t-button v-show="row.filePath" size="small" variant="text" @click="download(row.id)">下载</t-button> |
|
|
|
<t-button size="small" variant="text" @click="openMove([row.id])">移动</t-button> |
|
|
|
<t-button size="small" variant="text" @click="reprocess(row.id)">重新处理</t-button> |
|
|
|
<t-button size="small" variant="text" theme="danger" @click="remove(row.id)">删除</t-button> |
|
|
|
</t-space> |
|
|
|
@ -116,6 +116,27 @@ |
|
|
|
<t-button theme="primary" @click="submitFolderDialog">保存</t-button> |
|
|
|
</div> |
|
|
|
</t-dialog> |
|
|
|
|
|
|
|
<!-- 移动文档弹窗:选择目标位置(分类根或目录) --> |
|
|
|
<t-dialog v-model:visible="moveDialog.visible" header="移动文档" width="440px" :footer="false"> |
|
|
|
<div class="move-tree-tip">选择目标位置(分类根或目录):</div> |
|
|
|
<t-tree |
|
|
|
v-if="moveDialog.visible" |
|
|
|
:data="moveTreeData" |
|
|
|
:keys="{ value: 'value', label: 'label', children: 'children' }" |
|
|
|
:activable="true" |
|
|
|
:active-multiple="false" |
|
|
|
hover |
|
|
|
expand-all |
|
|
|
v-model:actived="moveActiveKeys" |
|
|
|
@active="onMoveTreeChange" |
|
|
|
style="max-height:360px;overflow:auto;" |
|
|
|
/> |
|
|
|
<div class="dialog-footer"> |
|
|
|
<t-button variant="outline" @click="moveDialog.visible = false">取消</t-button> |
|
|
|
<t-button theme="primary" :disabled="!moveDialog.target" @click="confirmMove">移动</t-button> |
|
|
|
</div> |
|
|
|
</t-dialog> |
|
|
|
</t-card> |
|
|
|
</template> |
|
|
|
|
|
|
|
@ -150,9 +171,12 @@ const filterStatus = ref('') |
|
|
|
const keyword = ref('') |
|
|
|
const filterTag = ref('') |
|
|
|
const selectedIds = ref(new Set<string>()) |
|
|
|
const moveCategoryId = ref('') |
|
|
|
const hasProcessing = ref(false) |
|
|
|
|
|
|
|
// 移动文档弹窗状态 |
|
|
|
const moveDialog = ref({ visible: false, ids: [] as string[], target: null as any }) |
|
|
|
const moveActiveKeys = ref<string[]>([]) |
|
|
|
|
|
|
|
// 目录树相关状态 |
|
|
|
const treeData = ref<any[]>([]) |
|
|
|
const activedKeys = ref<string[]>([]) |
|
|
|
@ -206,14 +230,19 @@ const columns = [ |
|
|
|
{ colKey: 'enabled', title: '启用', width: 60 }, |
|
|
|
{ colKey: 'chunkCount', title: '分块', width: 60, sorter: true }, |
|
|
|
{ colKey: 'createTime', title: '创建时间', width: 160, sorter: true, cell: (_:any,{row}:any) => formatDate(row.createTime) }, |
|
|
|
{ colKey: 'op', title: '操作', width: 200 }, |
|
|
|
{ colKey: 'op', title: '操作', width: 240 }, |
|
|
|
] |
|
|
|
|
|
|
|
// ==================== 选项 ==================== |
|
|
|
const categoryOptions = computed(() => [{ label: '全部分类', value: '' }, ...categoryStore.categories.map(c => ({ label: c.name, value: String(c.id) }))]) |
|
|
|
const statusOptions = [{ label: '全部状态', value: '' }, { label: '已完成', value: 'READY' }, { label: '处理中', value: 'PROCESSING' }, { label: '失败', value: 'FAILED' }] |
|
|
|
const tagOptions = computed(() => [{ label: '全部标签', value: '' }, ...(documentStore.tags || []).map((t: any) => ({ label: `${t.tag} (${t.count})`, value: t.tag }))]) |
|
|
|
const moveCategoryOptions = computed(() => [{ label: '移动到分类...', value: '' }, ...categoryStore.categories.map(c => ({ label: c.name, value: String(c.id) })), { label: '未分类', value: '0' }]) |
|
|
|
// 移动弹窗目录树:确保有「未分类」节点,保留移出到未分类的能力 |
|
|
|
const moveTreeData = computed(() => { |
|
|
|
const hasUnclassified = treeData.value.some(n => n.value === 'cat:0') |
|
|
|
if (hasUnclassified) return treeData.value |
|
|
|
return [{ value: 'cat:0', label: '未分类', nodeType: 'category', categoryId: '0', children: [] }, ...treeData.value] |
|
|
|
}) |
|
|
|
|
|
|
|
// ==================== 工具函数 ==================== |
|
|
|
function getDocTags(doc: any): string[] { |
|
|
|
@ -502,18 +531,37 @@ async function batchDisable() { |
|
|
|
} catch (e: any) { toast('批量禁用失败:' + e.message, 'error') } |
|
|
|
} |
|
|
|
|
|
|
|
async function batchMove() { |
|
|
|
const ids = Array.from(selectedIds.value) |
|
|
|
const catId = moveCategoryId.value |
|
|
|
if (!catId && catId !== '0') { toast('请选择目标分类', 'error'); return } |
|
|
|
const catName = catId === '0' ? '未分类' : categoryStore.getCategoryName(catId) |
|
|
|
if (!await confirm(`确定将选中的 ${ids.length} 个文档移动到「${catName}」?`)) return |
|
|
|
function openMove(ids: string[]) { |
|
|
|
moveDialog.value = { visible: true, ids, target: null } |
|
|
|
moveActiveKeys.value = [] |
|
|
|
} |
|
|
|
|
|
|
|
function onMoveTreeChange(_value: any[], ctx: any) { |
|
|
|
moveDialog.value.target = ctx?.node?.data ?? null |
|
|
|
} |
|
|
|
|
|
|
|
async function confirmMove() { |
|
|
|
const t = moveDialog.value.target |
|
|
|
if (!t) { toast('请选择目标位置', 'error'); return } |
|
|
|
// 目标为目录 → folderId=目录ID、categoryId=目录所属分类;目标为分类根 → folderId=0、categoryId=分类 |
|
|
|
const folderId = t.nodeType === 'folder' ? String(t.folderId ?? '0') : '0' |
|
|
|
const categoryId = String(t.categoryId ?? '0') |
|
|
|
const ids = moveDialog.value.ids |
|
|
|
try { |
|
|
|
// 雪花 ID 精度:0 保持为字符串 0,其余保持字符串 |
|
|
|
const json = await batchMoveDocuments(ids, catId) |
|
|
|
if (json.success) { toast(json.message, 'success'); selectedIds.value = new Set(); moveCategoryId.value = ''; loadData(); documentStore.loadStats() } |
|
|
|
else toast(json.message || '批量移动失败', 'error') |
|
|
|
} catch (e: any) { toast('批量移动失败:' + e.message, 'error') } |
|
|
|
const json = await batchMoveDocuments(ids, categoryId, folderId) |
|
|
|
if (json.success) { |
|
|
|
toast(json.message, 'success') |
|
|
|
moveDialog.value.visible = false |
|
|
|
moveActiveKeys.value = [] |
|
|
|
selectedIds.value = new Set() |
|
|
|
loadData() |
|
|
|
documentStore.loadStats() |
|
|
|
} else { |
|
|
|
toast(json.message || '移动失败', 'error') |
|
|
|
} |
|
|
|
} catch (e: any) { |
|
|
|
toast('移动失败:' + e.message, 'error') |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化 |
|
|
|
@ -553,4 +601,5 @@ loadData() |
|
|
|
.tree-node:hover .tree-ops { display: inline-flex; } |
|
|
|
|
|
|
|
.dialog-footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; } |
|
|
|
.move-tree-tip { font-size: 12px; color: var(--td-text-color-placeholder); margin-bottom: 8px; } |
|
|
|
</style> |