fix(knowledge): harden batch tag dialog submit and list refresh

Keep the batch tag dialog open until the API succeeds, reset pagination
after batch updates, and add structural tests for BatchTagDialog.
This commit is contained in:
wizardchen
2026-07-30 12:04:38 +08:00
committed by lyingbug
parent de262ba6dd
commit 57fb1de45d
4 changed files with 54 additions and 11 deletions
@@ -1922,6 +1922,7 @@ const handleBatchTag = () => {
};
const onBatchTagConfirm = async (tagIds: string[]) => {
if (batchTagging.value || selectedIds.value.size === 0) return;
const ids = Array.from(selectedIds.value);
const updateMap: Record<string, string[]> = {};
for (const id of ids) {
@@ -1931,8 +1932,10 @@ const onBatchTagConfirm = async (tagIds: string[]) => {
try {
await updateKnowledgeTagBatch({ updates: updateMap });
MessagePlugin.success(t('knowledgeBase.batchTagSuccess', { count: ids.length }));
batchTagDialogVisible.value = false;
clearSelection();
batchMode.value = false;
resetPage();
loadKnowledgeFiles(kbId.value);
loadTags(kbId.value, true);
} catch (e: any) {
@@ -2427,6 +2430,7 @@ async function createNewSession(value: string): Promise<void> {
<BatchTagDialog :visible="batchTagDialogVisible"
:count="selectedIds.size" :kb-id="kbId" :tag-list="tagList"
:pre-selected-tag-ids="batchTagPreSelectedIds" :can-manage="canEdit"
:confirm-loading="batchTagging"
@update:visible="batchTagDialogVisible = $event" @confirm="onBatchTagConfirm"
@tag-created="loadTags(kbId, true)" @open-manage="openTagManageFromBatchDialog" />
@@ -0,0 +1,42 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { readFileSync } from 'node:fs'
const component = readFileSync(new URL('./BatchTagDialog.vue', import.meta.url), 'utf8')
const zhCN = readFileSync(new URL('../../../i18n/locales/zh-CN.ts', import.meta.url), 'utf8')
const enUS = readFileSync(new URL('../../../i18n/locales/en-US.ts', import.meta.url), 'utf8')
const koKR = readFileSync(new URL('../../../i18n/locales/ko-KR.ts', import.meta.url), 'utf8')
const ruRU = readFileSync(new URL('../../../i18n/locales/ru-RU.ts', import.meta.url), 'utf8')
test('uses a compact flat dialog with selected and available sections', () => {
assert.match(component, /dialog-class-name="batch-tag-dialog"/)
assert.match(component, /width="420px"/)
assert.match(component, /<template #header>/)
assert.match(component, /class="batch-tag-heading-icon"/)
assert.match(component, /name="discount"/)
assert.match(component, /class="setting-drawer__section"/)
assert.match(component, /class="setting-drawer__section-title"/)
assert.match(component, /batchTagSelectedSection/)
assert.match(component, /batchTagAvailableSection/)
assert.match(component, /preSelectedTagIds/)
assert.match(component, /confirmLoading/)
assert.match(component, /canManage/)
assert.match(component, /tagManageLink/)
assert.match(component, /open-manage/)
assert.match(component, /selectedTagsList/)
assert.match(component, /availableTagsList/)
assert.match(component, /class="batch-tag-chip"/)
assert.match(component, /class="batch-tag-create-row"/)
assert.match(component, /class="batch-tag-footer"/)
assert.match(component, /function handleConfirm\(\) {\s*if \(props\.confirmLoading\) return;\s*emit\('confirm', Array.from\(selectedSet\.value\)\);\s*}/)
})
test('defines batch tag dialog strings in every supported locale', () => {
for (const locale of [zhCN, enUS, koKR, ruRU]) {
assert.match(locale, /batchTagDialogHeading:/)
assert.match(locale, /batchTagSelectedSection:/)
assert.match(locale, /batchTagAvailableSection:/)
assert.match(locale, /batchTagSuccess:/)
assert.match(locale, /batchTagFailed:/)
}
})
@@ -75,10 +75,10 @@
{{ $t('knowledgeBase.tagSelectedCount', { count: selectedSet.size }) }}
</span>
<div class="batch-tag-footer-right">
<t-button variant="outline" size="small" @click="handleClose">
<t-button variant="outline" size="small" :disabled="confirmLoading" @click="handleClose">
{{ $t('common.cancel') }}
</t-button>
<t-button theme="primary" size="small" :loading="saving" @click="handleConfirm">
<t-button theme="primary" size="small" :loading="confirmLoading" @click="handleConfirm">
{{ $t('common.confirm') }}
</t-button>
</div>
@@ -106,6 +106,7 @@ const props = defineProps<{
tagList: Tag[];
preSelectedTagIds?: string[];
canManage?: boolean;
confirmLoading?: boolean;
}>();
const emit = defineEmits<{
@@ -120,7 +121,6 @@ const { t } = useI18n();
const searchQuery = ref('');
const selectedSet = ref<Set<string>>(new Set());
const creatingTag = ref(false);
const saving = ref(false);
const newTagName = ref('');
watch(
@@ -213,14 +213,9 @@ async function handleAddNewTag() {
}
}
async function handleConfirm() {
saving.value = true;
try {
emit('confirm', Array.from(selectedSet.value));
emit('update:visible', false);
} finally {
saving.value = false;
}
function handleConfirm() {
if (props.confirmLoading) return;
emit('confirm', Array.from(selectedSet.value));
}
function handleClose() {
@@ -118,7 +118,9 @@ const { t } = useI18n();
.batch-bar-actions {
flex-shrink: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 8px;
}