feat(web): AI 文档查询卡片重构与单号判定统一
- documentClassification 抽出 isApplicationDocumentNo,统一兼容 AP-/APP- 旧格式与 A+8 新格式,aiDocumentQueryModel 复用 - aiDocumentQueryModel 文档卡片改为结构化字段布局(单据类型/金额/申请人/编号/操作),新增查询范围摘要区,渲染走 HTML 信任块 - AppShellRouteView/useAppShell/useRequests/detailAlerts/riskVisibility 等差旅详情模型适配单号判定 - 同步更新 ai-document-query-model/workbench-ai-mode-switch 测试,新增 document-classification 测试
This commit is contained in:
@@ -1094,30 +1094,84 @@
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-query-summary) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 12px;
|
||||
margin-top: 0;
|
||||
padding: 2px 0 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-query-summary__label) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: auto;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
font-weight: 760;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-query-summary__scope) {
|
||||
min-width: 0;
|
||||
color: #0f172a;
|
||||
font-size: 15px;
|
||||
font-weight: 860;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-query-summary__count) {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-query-summary__count strong) {
|
||||
color: #1d4ed8;
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card-list) {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
gap: 16px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card) {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid rgba(226, 232, 240, 0.9);
|
||||
border-left: 3px solid #cbd5e1;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
border: 1px solid rgba(203, 213, 225, 0.76);
|
||||
border-left: 0;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 4px 12px rgba(15, 23, 42, 0.04);
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow:
|
||||
0 1px 2px rgba(15, 23, 42, 0.035),
|
||||
0 10px 26px rgba(15, 23, 42, 0.045);
|
||||
color: #334155;
|
||||
animation: workbenchDocumentCardReveal 360ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
|
||||
transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card:hover) {
|
||||
border-color: rgba(148, 163, 184, 0.7);
|
||||
box-shadow: 0 2px 4px rgba(15, 23, 42, 0.05), 0 8px 20px rgba(15, 23, 42, 0.07);
|
||||
border-color: rgba(148, 163, 184, 0.72);
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.065);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@@ -1133,186 +1187,161 @@
|
||||
animation-delay: 120ms;
|
||||
}
|
||||
|
||||
/* 状态语义色:左侧边条颜色随状态变化,一眼判断当前阶段 */
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-pending) {
|
||||
border-left-color: #2563eb;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-success) {
|
||||
border-left-color: #16a34a;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-warning) {
|
||||
border-left-color: #d97706;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-danger) {
|
||||
border-left-color: #dc2626;
|
||||
}
|
||||
|
||||
/* 卡片头部:状态 + 类型(左) · 单据编号(右) */
|
||||
/* 状态语义色:头部浅底色和状态文字随单据状态变化 */
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__head) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__head-left) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
padding: 13px 18px;
|
||||
background: rgba(37, 99, 235, 0.11);
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__status) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 22px;
|
||||
padding: 0 9px;
|
||||
border-radius: 6px;
|
||||
background: rgba(148, 163, 184, 0.16);
|
||||
color: #475569;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
min-height: 24px;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: #1d4ed8;
|
||||
font-size: 15px;
|
||||
font-weight: 860;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-success .ai-document-card__head) {
|
||||
background: rgba(22, 163, 74, 0.1);
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-warning .ai-document-card__head) {
|
||||
background: rgba(217, 119, 6, 0.12);
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-danger .ai-document-card__head) {
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-pending .ai-document-card__status) {
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-success .ai-document-card__status) {
|
||||
background: rgba(22, 163, 74, 0.1);
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-warning .ai-document-card__status) {
|
||||
background: rgba(217, 119, 6, 0.1);
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-danger .ai-document-card__status) {
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__type) {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__number) {
|
||||
flex: 0 0 auto;
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* 卡片主体:事由(主焦点) + 申请人/部门(次焦点) */
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__body) {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
padding: 16px 18px 18px;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__reason) {
|
||||
display: -webkit-box;
|
||||
color: #0f172a;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
min-width: 0;
|
||||
color: #1e40af;
|
||||
font-size: 15px;
|
||||
font-weight: 760;
|
||||
line-height: 1.45;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__owner-line) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-success .ai-document-card__reason) {
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__owner) {
|
||||
color: #1e293b;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-warning .ai-document-card__reason) {
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__dept) {
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card.is-danger .ai-document-card__reason) {
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__dot) {
|
||||
color: #cbd5e1;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 卡片底部:辅助元信息(左) · 金额(右) · 操作 */
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__foot) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid rgba(226, 232, 240, 0.9);
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__meta) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__meta-item) {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__amount-block) {
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__details) {
|
||||
display: grid;
|
||||
justify-items: end;
|
||||
gap: 1px;
|
||||
flex: 0 0 auto;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 28px;
|
||||
padding-top: 2px;
|
||||
border-top: 1px solid rgba(203, 213, 225, 0.76);
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__amount-label) {
|
||||
color: #94a3b8;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__field) {
|
||||
display: grid;
|
||||
grid-template-columns: 86px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__field--action) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__label) {
|
||||
color: #8a94a6;
|
||||
font-size: 13px;
|
||||
font-weight: 640;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__value) {
|
||||
min-width: 0;
|
||||
color: #334155;
|
||||
font-size: 14px;
|
||||
font-weight: 720;
|
||||
line-height: 1.45;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__amount) {
|
||||
color: #0f172a;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__number) {
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
font-weight: 740;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__action) {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
min-height: 26px;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: #1d4ed8;
|
||||
font-size: 14px;
|
||||
font-weight: 820;
|
||||
box-shadow: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__action:hover) {
|
||||
background: transparent;
|
||||
color: #1e40af;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.markdown-table-wrap),
|
||||
@@ -1547,31 +1576,37 @@
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card) {
|
||||
padding: 14px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__head) {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__body) {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__details) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__field) {
|
||||
grid-template-columns: 76px minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__field--action) {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__number) {
|
||||
flex-basis: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__foot) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__amount-block) {
|
||||
justify-items: start;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.workbench-ai-answer-markdown :deep(.ai-document-card__action) {
|
||||
order: 3;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.workbench-ai-application-preview {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { fetchOntologyParse } from '../services/ontology.js'
|
||||
import { fetchLatestConversation } from '../services/orchestrator.js'
|
||||
import { markAiWorkbenchConversationDraftDeleted } from '../utils/aiWorkbenchConversationStore.js'
|
||||
import { clearAssistantSessionSnapshotForDraftClaim } from '../utils/assistantSessionSnapshot.js'
|
||||
import { isApplicationDocumentNo } from '../utils/documentClassification.js'
|
||||
import {
|
||||
ASSISTANT_SCOPE_SESSION_STEWARD,
|
||||
buildUnsupportedBusinessScopeConversation,
|
||||
@@ -428,8 +429,7 @@ export function useAppShell() {
|
||||
return (
|
||||
documentType === 'application'
|
||||
|| documentType === 'expense_application'
|
||||
|| normalizedClaimNo.startsWith('AP-')
|
||||
|| normalizedClaimNo.startsWith('APP-')
|
||||
|| isApplicationDocumentNo(normalizedClaimNo)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
|
||||
import { fetchAllExpenseClaims } from '../services/reimbursements.js'
|
||||
import { isApplicationDocumentNo } from '../utils/documentClassification.js'
|
||||
import { filterActionableRiskFlags, normalizeRiskFlagTone } from '../utils/riskFlags.js'
|
||||
|
||||
const EXPENSE_TYPE_LABELS = {
|
||||
@@ -212,8 +213,7 @@ function resolveDocumentTypeMeta(claim, typeCode) {
|
||||
const isApplication =
|
||||
explicitType === DOCUMENT_TYPE_APPLICATION
|
||||
|| explicitType === 'expense_application'
|
||||
|| claimNo.startsWith('AP-')
|
||||
|| claimNo.startsWith('APP-')
|
||||
|| isApplicationDocumentNo(claimNo)
|
||||
|| normalizedType === 'application'
|
||||
|| normalizedType.endsWith('_application')
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { extractExpenseClaimItems } from '../services/reimbursements.js'
|
||||
import { isApplicationDocumentNo } from './documentClassification.js'
|
||||
|
||||
const DOCUMENT_QUERY_LIMIT = 8
|
||||
|
||||
@@ -336,8 +337,7 @@ function resolveDocumentTypeCode(claim = {}) {
|
||||
|| explicitType === 'expense_application'
|
||||
|| expenseType === 'application'
|
||||
|| expenseType.endsWith('_application')
|
||||
|| documentNo.startsWith('AP-')
|
||||
|| documentNo.startsWith('APP-')
|
||||
|| isApplicationDocumentNo(documentNo)
|
||||
) {
|
||||
return 'application'
|
||||
}
|
||||
@@ -679,49 +679,66 @@ function buildDocumentCardHtml(record = {}) {
|
||||
const typeClass = record.documentType === 'application' ? 'application' : 'reimbursement'
|
||||
const statusTone = record.statusTone || 'is-pending'
|
||||
const amountLabel = record.documentType === 'application' ? '预计金额' : '报销金额'
|
||||
|
||||
// footer 左侧辅助元信息:业务地点(可选)+ 时间
|
||||
const metaParts = []
|
||||
if (record.locationLabel) {
|
||||
metaParts.push(`<span class="ai-document-card__meta-item">${escapeHtml(record.locationLabel)}</span>`)
|
||||
}
|
||||
metaParts.push(`<span class="ai-document-card__meta-item">${escapeHtml(record.time || '待补充')}</span>`)
|
||||
const metaHtml = `<div class="ai-document-card__meta">${metaParts.join('<span class="ai-document-card__dot">·</span>')}</div>`
|
||||
const ownerText = [record.ownerLabel, record.departmentLabel]
|
||||
.filter((item) => item && item !== '未显示')
|
||||
.join(' · ') || '未显示'
|
||||
|
||||
return [
|
||||
`<article class="ai-document-card ai-document-card--${typeClass} ${statusTone}" aria-label="单据详情">`,
|
||||
'<header class="ai-document-card__head">',
|
||||
'<div class="ai-document-card__head-left">',
|
||||
`<strong class="ai-document-card__reason">${escapeHtml(record.reason)}</strong>`,
|
||||
`<span class="ai-document-card__status">${escapeHtml(record.statusLabel)}</span>`,
|
||||
`<span class="ai-document-card__type">${escapeHtml(record.documentTypeLabel)} · ${escapeHtml(record.typeLabel)}</span>`,
|
||||
'</div>',
|
||||
`<span class="ai-document-card__number">${escapeHtml(record.documentNo || '未编号单据')}</span>`,
|
||||
'</header>',
|
||||
'<div class="ai-document-card__body">',
|
||||
`<strong class="ai-document-card__reason">${escapeHtml(record.reason)}</strong>`,
|
||||
'<div class="ai-document-card__owner-line">',
|
||||
`<span class="ai-document-card__owner">${escapeHtml(record.ownerLabel)}</span>`,
|
||||
'<span class="ai-document-card__dot">·</span>',
|
||||
`<span class="ai-document-card__dept">${escapeHtml(record.departmentLabel)}</span>`,
|
||||
'<div class="ai-document-card__details">',
|
||||
'<div class="ai-document-card__field">',
|
||||
'<span class="ai-document-card__label">单据类型</span>',
|
||||
`<strong class="ai-document-card__value">${escapeHtml(record.documentTypeLabel)} · ${escapeHtml(record.typeLabel)}</strong>`,
|
||||
'</div>',
|
||||
'</div>',
|
||||
'<footer class="ai-document-card__foot">',
|
||||
metaHtml,
|
||||
'<div class="ai-document-card__amount-block">',
|
||||
`<span class="ai-document-card__amount-label">${escapeHtml(amountLabel)}</span>`,
|
||||
'<div class="ai-document-card__field">',
|
||||
`<span class="ai-document-card__label">${escapeHtml(amountLabel)}</span>`,
|
||||
`<strong class="ai-document-card__amount">${escapeHtml(record.amountLabel)}</strong>`,
|
||||
'</div>',
|
||||
'<div class="ai-document-card__field">',
|
||||
'<span class="ai-document-card__label">申请人</span>',
|
||||
`<strong class="ai-document-card__value">${escapeHtml(ownerText)}</strong>`,
|
||||
'</div>',
|
||||
'<div class="ai-document-card__field">',
|
||||
'<span class="ai-document-card__label">单据编号</span>',
|
||||
`<strong class="ai-document-card__value ai-document-card__number">${escapeHtml(record.documentNo || '未编号单据')}</strong>`,
|
||||
'</div>',
|
||||
'<div class="ai-document-card__field ai-document-card__field--action">',
|
||||
'<span class="ai-document-card__label">操作</span>',
|
||||
href
|
||||
? `<a class="ai-html-action-link ai-html-action-link-document ai-document-card__action" data-ai-action="open-document-detail" href="${escapeHtml(href)}">查看详情</a>`
|
||||
: '',
|
||||
'</footer>',
|
||||
: '<span class="ai-document-card__value">暂无详情</span>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
'</article>'
|
||||
].join('')
|
||||
}
|
||||
|
||||
function buildDocumentCardsHtml(records = []) {
|
||||
function buildDocumentQuerySummaryHtml(scopeText = '', totalCount = 0, visibleCount = 0) {
|
||||
return [
|
||||
'<section class="ai-document-query-summary" aria-label="单据查询范围">',
|
||||
'<span class="ai-document-query-summary__label">查询范围</span>',
|
||||
`<strong class="ai-document-query-summary__scope">${escapeHtml(scopeText || '相关单据')}</strong>`,
|
||||
'<span class="ai-document-query-summary__count">',
|
||||
`共 <strong>${escapeHtml(String(totalCount))}</strong> 张`,
|
||||
'</span>',
|
||||
'<span class="ai-document-query-summary__count">',
|
||||
`展示最近 <strong>${escapeHtml(String(visibleCount))}</strong> 张`,
|
||||
'</span>',
|
||||
'</section>'
|
||||
].join('')
|
||||
}
|
||||
|
||||
function buildDocumentCardsHtml(records = [], options = {}) {
|
||||
const querySummaryHtml = options.querySummaryHtml || ''
|
||||
return [
|
||||
'<!-- ai-trusted-html:start -->',
|
||||
querySummaryHtml,
|
||||
'<section class="ai-document-card-list" aria-label="单据查询结果">',
|
||||
...records.map((record) => buildDocumentCardHtml(record)),
|
||||
'</section>',
|
||||
@@ -772,9 +789,9 @@ export function buildAiDocumentQueryMessage(intent = {}, claimsPayload = []) {
|
||||
const lines = [
|
||||
'### 已查询到相关单据',
|
||||
'',
|
||||
`**查询范围**:${scopeText || '相关单据'};共找到 **${records.length}** 张,先展示最近 **${visibleRecords.length}** 张。`,
|
||||
'',
|
||||
buildDocumentCardsHtml(visibleRecords)
|
||||
buildDocumentCardsHtml(visibleRecords, {
|
||||
querySummaryHtml: buildDocumentQuerySummaryHtml(scopeText, records.length, visibleRecords.length)
|
||||
})
|
||||
]
|
||||
|
||||
if (records.length > visibleRecords.length) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { filterActionableRiskFlags, normalizeRiskFlagTone } from './riskFlags.js'
|
||||
import { canViewRiskForContext } from './riskVisibility.js'
|
||||
import { isApplicationDocumentNo } from './documentClassification.js'
|
||||
|
||||
const SYSTEM_GENERATED_EXPENSE_TYPES = new Set(['travel_allowance'])
|
||||
const LOCATION_REQUIRED_EXPENSE_TYPES = new Set(['travel', 'meeting', 'entertainment'])
|
||||
@@ -37,8 +38,7 @@ function isApplicationDocumentRequest(request) {
|
||||
return (
|
||||
documentType === 'application'
|
||||
|| documentType === 'expense_application'
|
||||
|| claimNo.startsWith('AP-')
|
||||
|| claimNo.startsWith('APP-')
|
||||
|| isApplicationDocumentNo(claimNo)
|
||||
|| typeCode === 'application'
|
||||
|| typeCode.endsWith('_application')
|
||||
)
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
const APPLICATION_DOCUMENT_NO_PATTERN = /^A[A-HJ-NP-Z2-9]{8}$/i
|
||||
|
||||
export function isApplicationDocumentNo(value) {
|
||||
const claimNo = String(value || '').trim().toUpperCase()
|
||||
return (
|
||||
APPLICATION_DOCUMENT_NO_PATTERN.test(claimNo)
|
||||
|| claimNo.startsWith('AP-')
|
||||
|| claimNo.startsWith('APP-')
|
||||
)
|
||||
}
|
||||
|
||||
export function isApplicationRequestLike(value) {
|
||||
const explicitType = String(
|
||||
value?.documentTypeCode
|
||||
@@ -14,8 +25,7 @@ export function isApplicationRequestLike(value) {
|
||||
return (
|
||||
explicitType === 'application'
|
||||
|| explicitType === 'expense_application'
|
||||
|| claimNo.startsWith('AP-')
|
||||
|| claimNo.startsWith('APP-')
|
||||
|| isApplicationDocumentNo(claimNo)
|
||||
|| typeCode === 'application'
|
||||
|| typeCode.endsWith('_application')
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
isFinanceUser,
|
||||
isPlatformAdminUser
|
||||
} from './accessControl.js'
|
||||
import { isApplicationDocumentNo } from './documentClassification.js'
|
||||
|
||||
const APPLICATION_STAGE_ALIASES = new Set([
|
||||
'expense_application',
|
||||
@@ -159,8 +160,7 @@ export function isApplicationRiskStageRequest(request = {}) {
|
||||
return (
|
||||
documentType === 'application' ||
|
||||
documentType === 'expense_application' ||
|
||||
claimNo.startsWith('AP-') ||
|
||||
claimNo.startsWith('APP-') ||
|
||||
isApplicationDocumentNo(claimNo) ||
|
||||
typeCode === 'application' ||
|
||||
typeCode.endsWith('_application')
|
||||
)
|
||||
|
||||
@@ -273,7 +273,27 @@ const sidebarCollapsed = ref(false)
|
||||
const sidebarCollapsedBeforeAiMode = ref(false)
|
||||
const mobileSidebarOpen = ref(false)
|
||||
const overviewDashboard = ref('finance')
|
||||
const workbenchMode = ref('traditional')
|
||||
const { companyProfile, currentUser, logout } = useSystemState()
|
||||
|
||||
function resolveDefaultWorkbenchMode(user) {
|
||||
return isPlatformAdminUser(user) ? 'traditional' : 'ai'
|
||||
}
|
||||
|
||||
function resolveWorkbenchUserKey(user = {}) {
|
||||
const roleCodes = Array.isArray(user?.roleCodes) ? user.roleCodes.join(',') : ''
|
||||
return [
|
||||
user?.id,
|
||||
user?.userId,
|
||||
user?.username,
|
||||
user?.account,
|
||||
user?.name,
|
||||
user?.role,
|
||||
roleCodes,
|
||||
user?.isAdmin ? 'admin' : 'user'
|
||||
].map((item) => String(item || '').trim()).join('|')
|
||||
}
|
||||
|
||||
const workbenchMode = ref(resolveDefaultWorkbenchMode(currentUser.value))
|
||||
const aiSidebarCommandSeq = ref(0)
|
||||
const aiSidebarCommand = ref({ seq: 0, type: '', payload: null })
|
||||
const aiActiveConversationId = ref('')
|
||||
@@ -343,7 +363,6 @@ const {
|
||||
topBarView
|
||||
} = useAppShell()
|
||||
|
||||
const { companyProfile, currentUser, logout } = useSystemState()
|
||||
const PRODUCT_DISPLAY_NAME = '易财费控'
|
||||
const ENTERPRISE_DISPLAY_NAME = '远光软件股份有限公司'
|
||||
const filteredNavItems = computed(() => filterNavItemsByAccess(navItems, currentUser.value))
|
||||
@@ -496,7 +515,14 @@ function handleLogout() {
|
||||
|
||||
watch(
|
||||
() => currentUser.value,
|
||||
(user) => {
|
||||
(user, previousUser) => {
|
||||
if (resolveWorkbenchUserKey(user) !== resolveWorkbenchUserKey(previousUser)) {
|
||||
const nextMode = resolveDefaultWorkbenchMode(user)
|
||||
workbenchMode.value = nextMode
|
||||
if (nextMode === 'ai') {
|
||||
sidebarCollapsed.value = false
|
||||
}
|
||||
}
|
||||
aiConversationHistory.value = loadAiWorkbenchConversationHistory(user || {})
|
||||
},
|
||||
{ immediate: true }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isApplicationDocumentNo } from '../../utils/documentClassification.js'
|
||||
|
||||
const REQUIRED_APPLICATION_EXPENSE_TYPES = new Set(['travel', 'meal'])
|
||||
|
||||
const APPLICATION_TYPE_ALIASES = {
|
||||
@@ -302,8 +304,7 @@ export function isExpenseApplicationClaim(claim) {
|
||||
|
||||
return documentType === 'application'
|
||||
|| documentType === 'expense_application'
|
||||
|| claimNo.startsWith('AP-')
|
||||
|| claimNo.startsWith('APP-')
|
||||
|| isApplicationDocumentNo(claimNo)
|
||||
|| expenseType === 'application'
|
||||
|| expenseType.endsWith('_application')
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isApplicationDocumentNo } from '../../utils/documentClassification.js'
|
||||
|
||||
function normalizeText(value) {
|
||||
return String(value || '').trim()
|
||||
}
|
||||
@@ -22,7 +24,7 @@ function isApplicationDocumentRequest(requestModel) {
|
||||
|| requestModel?.document_type
|
||||
).toLowerCase()
|
||||
const claimNo = normalizeText(requestModel?.claimNo || requestModel?.claim_no || requestModel?.documentNo).toUpperCase()
|
||||
return documentType === 'application' || claimNo.startsWith('AP-') || claimNo.startsWith('APP-')
|
||||
return documentType === 'application' || isApplicationDocumentNo(claimNo)
|
||||
}
|
||||
|
||||
function isHotelExpenseItem(item) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isApplicationDocumentNo } from '../../utils/documentClassification.js'
|
||||
|
||||
function normalizeText(value) {
|
||||
return String(value || '').trim()
|
||||
}
|
||||
@@ -112,7 +114,7 @@ export function resolveRequestBusinessStage(request = {}) {
|
||||
|| request?.document_no
|
||||
|| request?.id
|
||||
).toUpperCase()
|
||||
if (claimNo.startsWith('AP-') || claimNo.startsWith('APP-')) {
|
||||
if (isApplicationDocumentNo(claimNo)) {
|
||||
return 'expense_application'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isApplicationDocumentNo } from '../../utils/documentClassification.js'
|
||||
|
||||
export const EXPENSE_TYPE_OPTIONS = [
|
||||
{ value: 'travel', label: '差旅费' },
|
||||
{ value: 'train_ticket', label: '火车票' },
|
||||
@@ -83,8 +85,7 @@ export function isApplicationDocumentRequest(request) {
|
||||
return (
|
||||
documentType === 'application'
|
||||
|| documentType === 'expense_application'
|
||||
|| claimNo.startsWith('AP-')
|
||||
|| claimNo.startsWith('APP-')
|
||||
|| isApplicationDocumentNo(claimNo)
|
||||
|| typeCode === 'application'
|
||||
|| typeCode.endsWith('_application')
|
||||
)
|
||||
|
||||
@@ -718,7 +718,7 @@ export function useTravelReimbursementFlow({
|
||||
function buildApplicationDuplicateDetail(payload) {
|
||||
const result = payload?.result && typeof payload.result === 'object' ? payload.result : {}
|
||||
const answer = String(result.answer || result.message || '').trim()
|
||||
const claimNo = answer.match(/AP-\d{14}-[A-HJ-NP-Z2-9]{8}/)?.[0] || ''
|
||||
const claimNo = answer.match(/A[A-HJ-NP-Z2-9]{8}|AP-\d{14}-[A-HJ-NP-Z2-9]{8}|APP-\d{8}-[A-Z0-9]{6}/)?.[0] || ''
|
||||
return claimNo
|
||||
? `已拦截重复申请,已有申请单:${claimNo}`
|
||||
: '已拦截重复申请,未创建新申请单'
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
collectReceiptFiles
|
||||
} from './travelReimbursementAttachmentModel.js'
|
||||
import { resolveAssistantScopeGuard } from '../../utils/assistantSessionScope.js'
|
||||
import { isApplicationDocumentNo } from '../../utils/documentClassification.js'
|
||||
import {
|
||||
APPLICATION_TRANSPORT_MODE_OPTIONS,
|
||||
applyApplicationBusinessTimeContext,
|
||||
@@ -148,8 +149,7 @@ function isApplicationClaimRecord(claim) {
|
||||
expenseType === 'application' ||
|
||||
expenseType === 'expense_application' ||
|
||||
expenseType.endsWith('_application') ||
|
||||
claimNo.startsWith('AP-') ||
|
||||
claimNo.startsWith('APP-') ||
|
||||
isApplicationDocumentNo(claimNo) ||
|
||||
Boolean(extractApplicationDetailFromClaim(claim))
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user