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

@@ -205,26 +205,31 @@
<span>条款填写时间</span>
</td>
<td :class="['expense-time col-time', { 'has-major-risk': hasExpenseRiskIndicator(item) }]">
<button
v-if="hasExpenseRiskIndicator(item)"
class="expense-risk-indicator"
type="button"
:title="resolveExpenseRiskIndicatorTitle(item)"
:aria-label="resolveExpenseRiskIndicatorTitle(item)"
@click="focusExpenseRisk(item)"
>
<i class="mdi mdi-alert"></i>
</button>
<template v-if="editingExpenseId === item.id">
<div class="cell-editor">
<input v-model="expenseEditor.itemDate" class="editor-input" type="date" />
<span>{{ item.dayLabel }}</span>
<div class="expense-time-content">
<button
v-if="hasExpenseRiskIndicator(item)"
class="expense-risk-indicator"
type="button"
:title="resolveExpenseRiskIndicatorTitle(item)"
:aria-label="resolveExpenseRiskIndicatorTitle(item)"
@click="focusExpenseRisk(item)"
>
<i class="mdi mdi-alert"></i>
</button>
<span v-else class="expense-risk-indicator-placeholder" aria-hidden="true"></span>
<div class="expense-time-value">
<template v-if="editingExpenseId === item.id">
<div class="cell-editor">
<input v-model="expenseEditor.itemDate" class="editor-input" type="date" />
<span>{{ item.dayLabel }}</span>
</div>
</template>
<template v-else>
<strong>{{ item.time }}</strong>
<span>{{ item.dayLabel }}</span>
</template>
</div>
</template>
<template v-else>
<strong>{{ item.time }}</strong>
<span>{{ item.dayLabel }}</span>
</template>
</div>
</td>
<td class="expense-type col-type">
<template v-if="editingExpenseId === item.id">

View File

@@ -1682,7 +1682,7 @@ export default {
const target = card
? document.getElementById(resolveRiskCardDomId(card))
: riskSection
target?.scrollIntoView({ behavior: 'smooth', block: 'center' })
target?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' })
if (highlightedRiskCardTimer) {
window.clearTimeout(highlightedRiskCardTimer)

View File

@@ -454,10 +454,33 @@ function resolveSourceStandardAdjustment(source, id, requestModel) {
if (reimbursableAmount === null) {
return null
}
const originalAmount = parseOptionalCurrency(
source?.originalItemAmount
?? source?.original_item_amount
?? source?.originalAmount
?? source?.original_amount
)
const employeeAbsorbedAmount = parseOptionalCurrency(
source?.employeeAbsorbedAmount
?? source?.employee_absorbed_amount
) || 0
const hasExplicitAdjustmentMarker = Boolean(
source?.standardAdjustmentAccepted
|| source?.standard_adjustment_accepted
|| source?.hasStandardAdjustment
|| source?.has_standard_adjustment
|| String(source?.standardAdjustmentMessage || source?.standard_adjustment_message || '').trim()
|| employeeAbsorbedAmount > 0
|| (originalAmount !== null && reimbursableAmount < originalAmount)
)
if (!hasExplicitAdjustmentMarker) {
return null
}
return {
originalAmount: parseOptionalCurrency(source?.originalItemAmount ?? source?.original_item_amount ?? source?.originalAmount ?? source?.original_amount),
originalAmount,
reimbursableAmount,
employeeAbsorbedAmount: parseOptionalCurrency(source?.employeeAbsorbedAmount ?? source?.employee_absorbed_amount) || 0,
employeeAbsorbedAmount,
message: String(source?.standardAdjustmentMessage || source?.standard_adjustment_message || '').trim()
}
}