feat(web): 设置中心缓存管理与文件预览资产工具
- 新增 documentPreviewAssets 工具,统一从 URL/Blob/File 推断预览类型(image/pdf/file/unsupported) - SettingsView/SettingsView.js/settingsModelHelper 新增系统缓存管理区块,调用 /settings/cache/clear 并展示清理结果;useSettings/services 适配 - WorkbenchAiFilePreviewDialog/useWorkbenchAiFilePreview 接入预览资产工具,workbenchAiComposerModel 调整文件处理 - ReceiptFolder/LogDetailView/DigitalEmployeeWorkRecords/travelReimbursementAttachmentModel 配套适配 - 新增 settings-cache-management-section 测试,更新 settings-llm/rendering/receipt-folder-view/composer-components/attachment-association 测试
This commit is contained in:
@@ -131,7 +131,7 @@ test('attachment upload association uses conversation selection instead of legac
|
||||
assert.doesNotMatch(submitComposerSource, /查询可关联草稿失败,已继续按新单据识别/)
|
||||
assert.match(
|
||||
submitDraftPreflightSource,
|
||||
/const claims = await fetchExpenseClaims\(\)[\s\S]*const queryPayload = buildDraftAssociationQueryPayload\(claims\)[\s\S]*meta: \['等待选择关联单据'\][\s\S]*queryPayload/
|
||||
/const claims = await fetchExpenseClaims\([^)]*\)[\s\S]*const queryPayload = buildDraftAssociationQueryPayload\(claims\)[\s\S]*meta: \['等待选择关联单据'\][\s\S]*queryPayload/
|
||||
)
|
||||
assert.match(submitDraftPreflightSource, /meta: \['单据查询失败'\][\s\S]*return \{ handled: true, value: null \}/)
|
||||
assert.match(
|
||||
@@ -186,6 +186,26 @@ test('OCR preview builders keep hotel receipt image previews when preview kind i
|
||||
assert.deepEqual(reviewPreviews, [{ filename: 'hotel.png', kind: 'image', url: dataUrl }])
|
||||
})
|
||||
|
||||
test('OCR preview builders reuse receipt folder image preview endpoints', () => {
|
||||
const ocrPreviews = buildOcrFilePreviews({
|
||||
documents: [
|
||||
{
|
||||
filename: '2月23 上海-武汉.pdf',
|
||||
preview_kind: 'image',
|
||||
receipt_preview_url: '/receipt-folder/receipt-train-1/preview'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
assert.deepEqual(ocrPreviews, [
|
||||
{
|
||||
filename: '2月23 上海-武汉.pdf',
|
||||
kind: 'image',
|
||||
url: '/receipt-folder/receipt-train-1/preview'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
test('OCR receipt folder ids are kept for final draft attachment association', () => {
|
||||
const files = [
|
||||
{ name: 'invoice.png' },
|
||||
|
||||
@@ -62,6 +62,15 @@ function testReceiptFolderViewSurface() {
|
||||
assert.doesNotMatch(view, /const claims = await fetchExpenseClaims\(\)/)
|
||||
}
|
||||
|
||||
function testReceiptPreviewKindFollowsReturnedBlobType() {
|
||||
const view = readProjectFile('web/src/views/ReceiptFolderView.vue')
|
||||
|
||||
assert.match(view, /import \{ inferPreviewKindFromBlob \} from '\.\.\/utils\/documentPreviewAssets\.js'/)
|
||||
assert.doesNotMatch(view, /function inferPreviewKindFromBlob\(blob\)/)
|
||||
assert.match(view, /const resolvedPreviewKind = inferPreviewKindFromBlob\(blob\)/)
|
||||
assert.match(view, /preview_kind: resolvedPreviewKind/)
|
||||
}
|
||||
|
||||
function testReceiptFolderServiceContract() {
|
||||
const service = readProjectFile('web/src/services/receiptFolder.js')
|
||||
const ocrService = readProjectFile('web/src/services/ocr.js')
|
||||
@@ -180,6 +189,7 @@ function testAssistantUnlinkedReceiptPrompt() {
|
||||
|
||||
function run() {
|
||||
testReceiptFolderViewSurface()
|
||||
testReceiptPreviewKindFollowsReturnedBlobType()
|
||||
testReceiptFolderServiceContract()
|
||||
testAppShellWiresReceiptFolder()
|
||||
testSharedDocumentListStyleReuse()
|
||||
|
||||
39
web/tests/settings-cache-management-section.test.mjs
Normal file
39
web/tests/settings-cache-management-section.test.mjs
Normal file
@@ -0,0 +1,39 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
const settingsModel = readFileSync(new URL('../src/utils/settingsModelHelper.js', import.meta.url), 'utf8')
|
||||
const settingsService = readFileSync(new URL('../src/services/settings.js', import.meta.url), 'utf8')
|
||||
const settingsScript = readFileSync(new URL('../src/views/scripts/SettingsView.js', import.meta.url), 'utf8')
|
||||
const settingsView = readFileSync(new URL('../src/views/SettingsView.vue', import.meta.url), 'utf8')
|
||||
const useSettings = readFileSync(new URL('../src/composables/useSettings.js', import.meta.url), 'utf8')
|
||||
|
||||
test('system settings replace Agent Trace with cache management', () => {
|
||||
assert.match(settingsModel, /id:\s*'cacheManagement'[\s\S]*label:\s*'缓存管理'/)
|
||||
assert.doesNotMatch(settingsModel, /id:\s*'agentTraces'/)
|
||||
assert.doesNotMatch(settingsScript, /AgentTraceCenterView/)
|
||||
assert.match(settingsView, /activeSection === 'cacheManagement'/)
|
||||
assert.match(settingsView, /一键清理缓存/)
|
||||
})
|
||||
|
||||
test('cache management action calls the settings clear-cache endpoint', () => {
|
||||
assert.match(settingsService, /export function clearSystemCaches/)
|
||||
assert.match(settingsService, /\/settings\/cache\/clear/)
|
||||
assert.match(useSettings, /clearSystemCaches/)
|
||||
assert.match(settingsView, /@click="clearAllCaches"/)
|
||||
})
|
||||
|
||||
test('cache management section uses an enterprise maintenance layout', () => {
|
||||
assert.match(settingsView, /class="settings-card cache-management-card"/)
|
||||
assert.match(settingsView, /class="card-title-with-icon"/)
|
||||
assert.match(settingsView, /class="cache-management-hero-metrics"/)
|
||||
assert.match(settingsView, /class="cache-scope-grid"/)
|
||||
assert.match(settingsView, /OCR 识别结果/)
|
||||
assert.match(settingsView, /模型失败冷却/)
|
||||
assert.match(settingsView, /知识库本地索引/)
|
||||
assert.match(settingsView, /不会删除源文件、业务单据和系统日志/)
|
||||
assert.match(settingsView, /:class="\{ 'is-error': cacheClearFailed \}"/)
|
||||
assert.match(useSettings, /const cacheClearFailed = ref\(false\)/)
|
||||
assert.match(useSettings, /normalizeCacheClearErrorMessage/)
|
||||
assert.doesNotMatch(settingsView, />\\s*Not Found\\s*</)
|
||||
})
|
||||
@@ -1,17 +1,18 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
const settingsScript = readFileSync(new URL('../src/views/scripts/SettingsView.js', import.meta.url), 'utf8')
|
||||
const settingsModel = readFileSync(new URL('../src/utils/settingsModelHelper.js', import.meta.url), 'utf8')
|
||||
const settingsView = readFileSync(new URL('../src/views/SettingsView.vue', import.meta.url), 'utf8')
|
||||
const llmSettingsPanel = readFileSync(new URL('../src/views/LlmSettingsPanel.vue', import.meta.url), 'utf8')
|
||||
|
||||
function testLlmSectionReplacesVlmWithReranker() {
|
||||
assert.doesNotMatch(settingsView, /VLM 模型/)
|
||||
assert.match(settingsView, /Reranker 模型配置/)
|
||||
assert.match(settingsScript, /rerankerProvider/)
|
||||
assert.match(llmSettingsPanel, /Reranker 模型配置/)
|
||||
assert.match(settingsModel, /rerankerProvider/)
|
||||
}
|
||||
|
||||
function testRerankerCardRendersAfterEmbeddingCard() {
|
||||
assert.match(settingsView, /Embedding 模型配置[\s\S]*Reranker 模型配置/)
|
||||
assert.match(llmSettingsPanel, /Embedding 模型配置[\s\S]*Reranker 模型配置/)
|
||||
}
|
||||
|
||||
function run() {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
const settingsScript = readFileSync(new URL('../src/views/scripts/SettingsView.js', import.meta.url), 'utf8')
|
||||
const settingsModel = readFileSync(new URL('../src/utils/settingsModelHelper.js', import.meta.url), 'utf8')
|
||||
const settingsView = readFileSync(new URL('../src/views/SettingsView.vue', import.meta.url), 'utf8')
|
||||
const settingsStyles = readFileSync(new URL('../src/assets/styles/views/settings-view.css', import.meta.url), 'utf8')
|
||||
|
||||
function testRenderingSectionUsesConciseToolbarTitle() {
|
||||
assert.match(settingsScript, /title:\s*'文件渲染'/)
|
||||
assert.doesNotMatch(settingsScript, /title:\s*'ONLYOFFICE 文件渲染配置'/)
|
||||
assert.match(settingsModel, /title:\s*'文件渲染'/)
|
||||
assert.doesNotMatch(settingsModel, /title:\s*'ONLYOFFICE 文件渲染配置'/)
|
||||
}
|
||||
|
||||
function testRenderingCardRemovesDuplicatedDescription() {
|
||||
|
||||
@@ -3,6 +3,8 @@ import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { buildSelectedFileCards } from '../src/composables/workbenchAiMode/workbenchAiComposerModel.js'
|
||||
|
||||
function readSource(path) {
|
||||
return readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
||||
}
|
||||
@@ -49,6 +51,31 @@ test('shared workbench file strip preserves OCR status badges', () => {
|
||||
assert.match(fileStripComponent, /:title="file\.ocrState\.title \|\| file\.ocrState\.label"/)
|
||||
})
|
||||
|
||||
test('shared workbench file strip uses recognized image preview metadata over raw PDF type', () => {
|
||||
const [card] = buildSelectedFileCards([
|
||||
{
|
||||
name: '2月23 上海-武汉.pdf',
|
||||
type: 'application/pdf',
|
||||
size: 24940,
|
||||
lastModified: 1760000000000
|
||||
}
|
||||
], () => ({
|
||||
status: 'recognized',
|
||||
document: {
|
||||
preview_kind: 'image',
|
||||
receipt_preview_url: '/receipt-folder/receipt-train-1/preview'
|
||||
}
|
||||
}))
|
||||
|
||||
assert.equal(card.tone, 'image')
|
||||
assert.equal(card.icon, 'mdi mdi-file-image-outline')
|
||||
assert.deepEqual(card.previewAsset, {
|
||||
kind: 'image',
|
||||
url: '/receipt-folder/receipt-train-1/preview',
|
||||
source: 'receipt'
|
||||
})
|
||||
})
|
||||
|
||||
test('AI mode primes attachment OCR synchronously after file selection', () => {
|
||||
assert.match(
|
||||
filePreviewRuntime,
|
||||
|
||||
Reference in New Issue
Block a user