feat: 风险可见性控制与差旅详情页交互优化

- 新增风险可见性工具函数与风险日趋势图表组件
- 优化差旅请求详情页费用模型与视图交互
- 完善顶部导航栏样式与应用壳路由逻辑
- 补充风险可见性、风险看板与差旅详情测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-03 22:15:45 +08:00
parent 75d5c178e1
commit 87da5df91b
17 changed files with 809 additions and 168 deletions

View File

@@ -17,6 +17,7 @@ import {
import {
buildExpenseItemViewModel,
buildDraftBlockingIssues,
rebuildExpenseItems,
buildStandardAdjustmentMap,
isApplicationDocumentRequest
} from '../src/views/scripts/travelRequestDetailExpenseModel.js'
@@ -543,9 +544,13 @@ test('AI advice risk section uses compact card styling hooks', () => {
test('expense rows show a major-risk warning icon before time', () => {
assert.match(detailViewTemplate, /'has-major-risk': hasExpenseRiskIndicator\(item\)/)
assert.match(detailViewTemplate, /class="expense-time-content"/)
assert.match(detailViewTemplate, /class="expense-risk-indicator"/)
assert.match(detailViewTemplate, /class="expense-risk-indicator-placeholder"/)
assert.match(detailViewTemplate, /@click="focusExpenseRisk\(item\)"/)
assert.match(detailViewStyle, /\.expense-time-content \{/)
assert.match(detailViewStyle, /\.expense-risk-indicator \{/)
assert.match(detailViewStyle, /\.expense-risk-indicator,\s*\.expense-risk-indicator-placeholder \{/)
assert.match(detailViewScript, /function hasExpenseRiskIndicator\(item\)/)
assert.match(detailViewScript, /buildItemClaimRiskState\(item, resolveClaimRiskFlags\(\)\)/)
})
@@ -556,7 +561,7 @@ test('expense risk indicator can focus and flash related risk card', () => {
assert.match(detailViewTemplate, /'is-highlighted': isHighlightedRiskCard\(card\)/)
assert.match(detailViewScript, /async function focusExpenseRisk\(item\)/)
assert.match(detailViewScript, /document\.getElementById\(resolveRiskCardDomId\(card\)\)/)
assert.match(detailViewScript, /scrollIntoView\(\{ behavior: 'smooth', block: 'center' \}\)/)
assert.match(detailViewScript, /scrollIntoView\(\{ behavior: 'smooth', block: 'nearest', inline: 'nearest' \}\)/)
assert.match(detailViewStyle, /\.validation-section--risk \.risk-advice-card\.is-highlighted/)
assert.match(detailViewStyle, /@keyframes risk-card-flash/)
})
@@ -748,6 +753,50 @@ test('expense detail shows standard-adjusted reimbursable amount separately from
assert.equal(item.hasStandardAdjustment, true)
})
test('plain reimbursable amount does not mark an item as standard-adjusted during detail rebuild', () => {
const item = buildExpenseItemViewModel(
{
id: 'hotel-risk-item',
itemType: 'hotel_ticket',
itemReason: '上海住宿',
itemAmount: 1086,
reimbursableAmount: 1086,
originalItemAmount: 1086,
invoiceId: 'hotel-risk.jpg',
standardAdjustmentAccepted: false,
hasStandardAdjustment: false
},
0,
{ riskFlags: [] }
)
assert.equal(item.standardAdjustmentAccepted, false)
assert.equal(item.hasStandardAdjustment, false)
assert.equal(item.reimbursableAmount, 1086)
const rebuiltItems = rebuildExpenseItems([item], { riskFlags: [] })
assert.equal(rebuiltItems[0].standardAdjustmentAccepted, false)
assert.equal(rebuiltItems[0].hasStandardAdjustment, false)
const riskCards = [
{
id: 'hotel-risk',
source: 'attachment_analysis',
itemId: 'hotel-risk-item',
tone: 'high',
risk: '住宿标准超标。'
}
]
const visibleCards = filterSubmitterResolvedRiskCards({
cards: riskCards,
businessStage: 'reimbursement',
isCurrentApplicant: true,
expenseItems: rebuiltItems,
standardAdjustmentMap: new Map()
})
assert.deepEqual(visibleCards.map((card) => card.id), ['hotel-risk'])
})
test('standard adjustment resolves submitter risk prompt only after accepted while reviewer still sees notice', () => {
const originalRiskCard = {
id: 'risk-hotel-1',