feat(workbench): keep progress detail return context
This commit is contained in:
75
document/development/工作台费用进度详情返回与类型列/CONCEPT.md
Normal file
75
document/development/工作台费用进度详情返回与类型列/CONCEPT.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# 工作台费用进度详情返回与类型列概念文档
|
||||||
|
|
||||||
|
## 功能一句话
|
||||||
|
|
||||||
|
让用户从首页费用进度进入单据详情后能返回首页,并在费用进度列表中直接看到每笔单据的费用类型。
|
||||||
|
|
||||||
|
## 背景与问题
|
||||||
|
|
||||||
|
当前首页费用进度点击单据后进入详情页,但详情页返回按钮默认回到单据中心,破坏了用户从首页查看进度的上下文。同时费用进度行只展示单号、标题、流程、状态和金额,用户需要点进详情才知道单据属于差旅、招待、办公等哪类费用。
|
||||||
|
|
||||||
|
## 目标与非目标
|
||||||
|
|
||||||
|
目标:
|
||||||
|
|
||||||
|
- 从首页费用进度进入详情时,详情页返回按钮回到个人工作台。
|
||||||
|
- 从单据中心进入详情时,原有返回单据中心逻辑不变。
|
||||||
|
- 在首页费用进度行新增“费用类型”列。
|
||||||
|
- 费用类型优先使用单据已有类型字段,缺失时按标题、场景和备注轻量归类。
|
||||||
|
|
||||||
|
非目标:
|
||||||
|
|
||||||
|
- 不修改详情页主体内容。
|
||||||
|
- 不新增后端接口。
|
||||||
|
- 不改变单据中心列表、审批详情和其他来源的返回逻辑。
|
||||||
|
|
||||||
|
## 用户与场景
|
||||||
|
|
||||||
|
个人员工在首页查看最近费用进度,点击某笔单据进入详情核对处理情况。查看完后点击返回,应回到刚才的首页工作台继续看其他进度项,而不是跳到单据中心列表。
|
||||||
|
|
||||||
|
## 功能能力
|
||||||
|
|
||||||
|
- 首页打开详情时带入 `returnTo=workbench` 来源标记。
|
||||||
|
- 详情页根据来源标记动态显示返回按钮文案并执行返回首页。
|
||||||
|
- 费用进度数据新增 `expenseTypeLabel`。
|
||||||
|
- 费用类型列在桌面端作为独立列展示,窄屏下按移动端布局折行展示。
|
||||||
|
|
||||||
|
## 方案设计
|
||||||
|
|
||||||
|
前端实现:
|
||||||
|
|
||||||
|
- `PersonalWorkbench.vue` 在 `open-document` 事件 payload 中补 `source: 'workbench'` 和 `returnTo: 'workbench'`。
|
||||||
|
- `AppShellRouteView.vue` 接收工作台来源并传给 `openRequestDetail`。
|
||||||
|
- `useAppShell.js` 在打开详情时写入查询参数,在关闭详情时根据查询参数返回工作台或单据中心。
|
||||||
|
- `workbenchSummary.js` 在 `progressItems` 中补费用类型字段。
|
||||||
|
- `personal-workbench.css` 与响应式样式新增费用类型列。
|
||||||
|
|
||||||
|
## 算法与公式
|
||||||
|
|
||||||
|
当前功能不涉及复杂数学公式。
|
||||||
|
|
||||||
|
费用类型归类优先级:
|
||||||
|
|
||||||
|
1. 单据显式字段:`expenseCategory`、`expense_type`、`category` 等。
|
||||||
|
2. 文本规则:从场景、标题、备注和描述中匹配差旅、招待、办公、培训、市场等关键词。
|
||||||
|
3. 兜底为“其他费用”。
|
||||||
|
|
||||||
|
## 测试方案
|
||||||
|
|
||||||
|
- 源码测试确认首页费用进度打开详情时带 `returnTo=workbench`。
|
||||||
|
- 源码测试确认详情返回文案和关闭逻辑支持工作台来源。
|
||||||
|
- 单元测试确认 `progressItems` 输出费用类型字段。
|
||||||
|
- 源码测试确认费用进度模板和样式包含费用类型列。
|
||||||
|
- 运行前端定向测试与构建。
|
||||||
|
|
||||||
|
## 指标与验收
|
||||||
|
|
||||||
|
- 从首页费用进度进入详情后,返回按钮回到个人工作台。
|
||||||
|
- 从单据中心进入详情后,返回按钮仍回到单据中心。
|
||||||
|
- 首页费用进度行可直接看到费用类型。
|
||||||
|
- 相关定向测试与前端构建通过。
|
||||||
|
|
||||||
|
## 风险与开放问题
|
||||||
|
|
||||||
|
- 当前费用类型归类仍是前端轻量归类。后续若后端已有稳定 ontology 类型字段,应优先接入 canonical 字段。
|
||||||
|
- 路由查询参数只用于详情返回来源,不应影响单据筛选和详情数据加载。
|
||||||
25
document/development/工作台费用进度详情返回与类型列/TODO.md
Normal file
25
document/development/工作台费用进度详情返回与类型列/TODO.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# 工作台费用进度详情返回与类型列 TODO
|
||||||
|
|
||||||
|
## 调研与契约
|
||||||
|
|
||||||
|
- [x] 核对首页费用进度点击链路、详情页返回逻辑和当前进度行样式。[CONCEPT: 方案设计] 证据:已确认 `PersonalWorkbench.vue` 发出 `open-document`,`AppShellRouteView.vue` 转入详情,`useAppShell.js` 默认返回单据中心。
|
||||||
|
- [x] 明确来源标记与费用类型字段的前端契约。[CONCEPT: 功能能力] 证据:采用 `returnTo: 'workbench'` 与 `expenseTypeLabel`。
|
||||||
|
|
||||||
|
## 前端实现
|
||||||
|
|
||||||
|
- [x] 首页费用进度打开详情时带入工作台返回来源。[CONCEPT: 方案设计] 证据:`PersonalWorkbench.vue` 的 `open-document` payload 已包含 `source` 与 `returnTo`。
|
||||||
|
- [x] 详情页关闭逻辑按来源返回工作台或单据中心。[CONCEPT: 功能能力] 证据:`useAppShell.js` 根据 `route.query.returnTo` 选择 `app-workbench` 或 `app-documents`。
|
||||||
|
- [x] 工作台进度汇总新增费用类型字段。[CONCEPT: 功能能力] 证据:`workbenchSummary.js` 的 `progressItems` 输出 `expenseTypeLabel`。
|
||||||
|
- [x] 首页费用进度行新增费用类型列及响应式样式。[CONCEPT: 指标与验收] 证据:`PersonalWorkbench.vue` 新增 `progress-type`,样式和响应式布局已更新。
|
||||||
|
|
||||||
|
## 测试与验证
|
||||||
|
|
||||||
|
- [x] 补充详情返回来源源码测试。[CONCEPT: 测试方案] 证据:`node web/tests/workbench-detail-return.test.mjs` 通过。
|
||||||
|
- [x] 补充费用进度类型列源码测试。[CONCEPT: 测试方案] 证据:`node web/tests/personal-workbench-assistant.test.mjs` 通过。
|
||||||
|
- [x] 补充工作台汇总单元测试,覆盖费用类型字段。[CONCEPT: 测试方案] 证据:`node web/tests/workbench-summary.test.mjs` 通过。
|
||||||
|
- [x] 运行定向测试和前端构建。[CONCEPT: 指标与验收] 证据:以上定向测试和 `npm.cmd --prefix web run build` 均通过。
|
||||||
|
|
||||||
|
## 交付
|
||||||
|
|
||||||
|
- [x] 复查暂存范围,避免纳入无关工作区改动。[CONCEPT: 风险与开放问题] 证据:`git diff --cached --name-only` 仅包含本次工作台进度、返回来源、测试和开发文档。
|
||||||
|
- [ ] 提交并 push 本次功能分支。[CONCEPT: 指标与验收]
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress-row {
|
.progress-row {
|
||||||
grid-template-columns: minmax(72px, 0.42fr) minmax(126px, 0.86fr) minmax(270px, 1.32fr) minmax(86px, auto);
|
grid-template-columns: minmax(72px, 0.4fr) minmax(122px, 0.72fr) minmax(78px, 0.42fr) minmax(238px, 1.18fr) minmax(86px, auto);
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,6 +328,7 @@
|
|||||||
grid-template-columns: minmax(70px, auto) 1fr minmax(74px, auto);
|
grid-template-columns: minmax(70px, auto) 1fr minmax(74px, auto);
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"time identity result"
|
"time identity result"
|
||||||
|
"type type type"
|
||||||
"steps steps steps";
|
"steps steps steps";
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
@@ -356,6 +357,11 @@
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-type {
|
||||||
|
grid-area: type;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.progress-result {
|
.progress-result {
|
||||||
grid-area: result;
|
grid-area: result;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -525,6 +525,38 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-type {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
justify-items: start;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-type small {
|
||||||
|
color: var(--workbench-muted);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 750;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-type strong {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 22px;
|
||||||
|
padding: 0 8px;
|
||||||
|
border: 1px solid rgba(var(--theme-primary-rgb, 58, 124, 165), 0.18);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(var(--theme-primary-rgb, 58, 124, 165), 0.08);
|
||||||
|
color: var(--workbench-primary-active);
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 850;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.progress-status {
|
.progress-status {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -555,7 +587,7 @@
|
|||||||
.progress-row {
|
.progress-row {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(78px, 0.44fr) minmax(138px, 0.86fr) minmax(300px, 1.46fr) minmax(92px, auto);
|
grid-template-columns: minmax(78px, 0.42fr) minmax(132px, 0.74fr) minmax(84px, 0.42fr) minmax(260px, 1.28fr) minmax(92px, auto);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -605,6 +637,7 @@
|
|||||||
|
|
||||||
.progress-time,
|
.progress-time,
|
||||||
.progress-identity,
|
.progress-identity,
|
||||||
|
.progress-type,
|
||||||
.progress-result {
|
.progress-result {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -215,6 +215,11 @@
|
|||||||
<small>{{ item.title }}</small>
|
<small>{{ item.title }}</small>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span class="progress-type" :title="item.expenseTypeLabel || '其他费用'">
|
||||||
|
<small>费用类型</small>
|
||||||
|
<strong>{{ item.expenseTypeLabel || '其他费用' }}</strong>
|
||||||
|
</span>
|
||||||
|
|
||||||
<span class="progress-steps" aria-hidden="true">
|
<span class="progress-steps" aria-hidden="true">
|
||||||
<span
|
<span
|
||||||
v-for="step in item.steps"
|
v-for="step in item.steps"
|
||||||
@@ -665,6 +670,8 @@ function openWorkbenchTarget(item) {
|
|||||||
const target = item?.target || {}
|
const target = item?.target || {}
|
||||||
if (target.type === 'document' && (target.id || target.claimNo)) {
|
if (target.type === 'document' && (target.id || target.claimNo)) {
|
||||||
emit('open-document', {
|
emit('open-document', {
|
||||||
|
source: 'workbench',
|
||||||
|
returnTo: 'workbench',
|
||||||
claimId: target.id,
|
claimId: target.id,
|
||||||
id: target.id || target.claimNo,
|
id: target.id || target.claimNo,
|
||||||
claimNo: target.claimNo
|
claimNo: target.claimNo
|
||||||
|
|||||||
@@ -95,6 +95,10 @@ export function useAppShell() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const detailMode = computed(() => route.name === 'app-document-detail')
|
const detailMode = computed(() => route.name === 'app-document-detail')
|
||||||
|
const detailReturnTarget = computed(() => String(route.query.returnTo || '').trim())
|
||||||
|
const detailBackLabel = computed(() => (
|
||||||
|
detailReturnTarget.value === 'workbench' ? '返回首页' : '返回单据中心'
|
||||||
|
))
|
||||||
const detailAlerts = computed(() => (
|
const detailAlerts = computed(() => (
|
||||||
detailMode.value
|
detailMode.value
|
||||||
? buildDetailAlerts(selectedRequest.value, { currentUser: currentUser.value })
|
? buildDetailAlerts(selectedRequest.value, { currentUser: currentUser.value })
|
||||||
@@ -398,16 +402,38 @@ export function useAppShell() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function openRequestDetail(request) {
|
function buildDocumentDetailQuery(options = {}) {
|
||||||
|
const nextQuery = { ...route.query }
|
||||||
|
const returnTo = String(options.returnTo || '').trim()
|
||||||
|
if (returnTo === 'workbench') {
|
||||||
|
nextQuery.returnTo = 'workbench'
|
||||||
|
} else {
|
||||||
|
delete nextQuery.returnTo
|
||||||
|
}
|
||||||
|
return nextQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDocumentReturnQuery() {
|
||||||
|
const { returnTo, ...nextQuery } = route.query
|
||||||
|
return nextQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRequestDetail(request, options = {}) {
|
||||||
selectedRequestSnapshot.value = request || null
|
selectedRequestSnapshot.value = request || null
|
||||||
router.push({
|
router.push({
|
||||||
name: 'app-document-detail',
|
name: 'app-document-detail',
|
||||||
params: { requestId: request.claimId || request.id }
|
params: { requestId: request.claimId || request.id },
|
||||||
|
query: buildDocumentDetailQuery(options)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeRequestDetail() {
|
function closeRequestDetail() {
|
||||||
router.push({ name: 'app-documents' })
|
if (detailReturnTarget.value === 'workbench') {
|
||||||
|
router.push({ name: 'app-workbench' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push({ name: 'app-documents', query: buildDocumentReturnQuery() })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleRequestUpdated() {
|
async function handleRequestUpdated() {
|
||||||
@@ -423,7 +449,7 @@ export function useAppShell() {
|
|||||||
|
|
||||||
await reloadRequests()
|
await reloadRequests()
|
||||||
selectedRequestSnapshot.value = null
|
selectedRequestSnapshot.value = null
|
||||||
router.push({ name: 'app-documents' })
|
router.push({ name: 'app-documents', query: buildDocumentReturnQuery() })
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -465,6 +491,7 @@ export function useAppShell() {
|
|||||||
smartEntryRevealToken,
|
smartEntryRevealToken,
|
||||||
smartEntrySessionId,
|
smartEntrySessionId,
|
||||||
detailAlerts,
|
detailAlerts,
|
||||||
|
detailBackLabel,
|
||||||
toast,
|
toast,
|
||||||
topBarView
|
topBarView
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ function buildProgressItems(ownedRequests) {
|
|||||||
id: requestId,
|
id: requestId,
|
||||||
requestId,
|
requestId,
|
||||||
title,
|
title,
|
||||||
|
expenseTypeLabel: resolveExpenseCategory(request),
|
||||||
amount: formatCurrency(request?.amount),
|
amount: formatCurrency(request?.amount),
|
||||||
status: normalizeText(request?.approvalStatus || currentStep?.label) || '处理中',
|
status: normalizeText(request?.approvalStatus || currentStep?.label) || '处理中',
|
||||||
statusTone: resolveProgressStatusTone(normalizeText(request?.approvalKey)),
|
statusTone: resolveProgressStatusTone(normalizeText(request?.approvalKey)),
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
<TravelRequestDetailView
|
<TravelRequestDetailView
|
||||||
v-else-if="activeView === 'documents' && detailMode && selectedRequest"
|
v-else-if="activeView === 'documents' && detailMode && selectedRequest"
|
||||||
:request="selectedRequest"
|
:request="selectedRequest"
|
||||||
back-label="返回单据中心"
|
:back-label="detailBackLabel"
|
||||||
@back-to-requests="closeRequestDetail"
|
@back-to-requests="closeRequestDetail"
|
||||||
@open-assistant="openSmartEntry"
|
@open-assistant="openSmartEntry"
|
||||||
@request-updated="handleRequestUpdated"
|
@request-updated="handleRequestUpdated"
|
||||||
@@ -279,6 +279,7 @@ const {
|
|||||||
closeSmartEntry,
|
closeSmartEntry,
|
||||||
customRange,
|
customRange,
|
||||||
detailAlerts,
|
detailAlerts,
|
||||||
|
detailBackLabel,
|
||||||
detailMode,
|
detailMode,
|
||||||
documentCenterRefreshToken,
|
documentCenterRefreshToken,
|
||||||
filteredRequests,
|
filteredRequests,
|
||||||
@@ -370,7 +371,14 @@ function openWorkbenchDocument(payload = {}) {
|
|||||||
|| String(item.id || '').trim() === requestId
|
|| String(item.id || '').trim() === requestId
|
||||||
|| String(item.claimNo || '').trim() === requestId
|
|| String(item.claimNo || '').trim() === requestId
|
||||||
))
|
))
|
||||||
openRequestDetail(request || payload)
|
const returnTo = (
|
||||||
|
String(payload.returnTo || '').trim() === 'workbench'
|
||||||
|
|| String(payload.source || '').trim() === 'workbench'
|
||||||
|
|| activeView.value === 'workbench'
|
||||||
|
)
|
||||||
|
? 'workbench'
|
||||||
|
: ''
|
||||||
|
openRequestDetail(request || payload, { returnTo })
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLogout() {
|
function handleLogout() {
|
||||||
|
|||||||
@@ -145,6 +145,11 @@ test('workbench submit shows intent recognition feedback before assistant opens'
|
|||||||
|
|
||||||
test('workbench progress rows show update time first', () => {
|
test('workbench progress rows show update time first', () => {
|
||||||
assert.match(workbench, /class="progress-time"/)
|
assert.match(workbench, /class="progress-time"/)
|
||||||
|
assert.match(workbench, /class="progress-type"/)
|
||||||
|
assert.match(workbench, /费用类型/)
|
||||||
|
assert.match(workbench, /\{\{ item\.expenseTypeLabel \|\| '其他费用' \}\}/)
|
||||||
|
assert.match(workbench, /source:\s*'workbench'/)
|
||||||
|
assert.match(workbench, /returnTo:\s*'workbench'/)
|
||||||
assert.match(workbench, /has-long-duration-divider/)
|
assert.match(workbench, /has-long-duration-divider/)
|
||||||
assert.match(workbench, /hasLongDurationDivider/)
|
assert.match(workbench, /hasLongDurationDivider/)
|
||||||
assert.match(workbench, /const LONG_DURATION_DAYS = 10/)
|
assert.match(workbench, /const LONG_DURATION_DAYS = 10/)
|
||||||
@@ -153,13 +158,15 @@ test('workbench progress rows show update time first', () => {
|
|||||||
assert.match(workbench, /displayTime: formatProgressTime\(item\?\.updatedAt\)/)
|
assert.match(workbench, /displayTime: formatProgressTime\(item\?\.updatedAt\)/)
|
||||||
assert.match(workbench, /function formatProgressTime\(value\)/)
|
assert.match(workbench, /function formatProgressTime\(value\)/)
|
||||||
assert.doesNotMatch(workbench, />全部进度/)
|
assert.doesNotMatch(workbench, />全部进度/)
|
||||||
assert.match(workbenchStyles, /\.progress-row\s*\{[\s\S]*grid-template-columns:\s*minmax\(78px,\s*0\.44fr\)/)
|
assert.match(workbenchStyles, /\.progress-row\s*\{[\s\S]*grid-template-columns:\s*minmax\(78px,\s*0\.42fr\)[\s\S]*minmax\(84px,\s*0\.42fr\)/)
|
||||||
|
assert.match(workbenchStyles, /\.progress-type strong\s*\{[\s\S]*var\(--workbench-primary-active\)/)
|
||||||
assert.match(workbenchStyles, /\.progress-row\.has-long-duration-divider::before\s*\{[\s\S]*content:\s*"10日以上"/)
|
assert.match(workbenchStyles, /\.progress-row\.has-long-duration-divider::before\s*\{[\s\S]*content:\s*"10日以上"/)
|
||||||
assert.match(workbenchStyles, /\.progress-row\.has-long-duration-divider::before\s*\{[\s\S]*left:\s*50%;[\s\S]*transform:\s*translateX\(-50%\);/)
|
assert.match(workbenchStyles, /\.progress-row\.has-long-duration-divider::before\s*\{[\s\S]*left:\s*50%;[\s\S]*transform:\s*translateX\(-50%\);/)
|
||||||
assert.match(workbenchStyles, /\.progress-row\.has-long-duration-divider::before\s*\{[\s\S]*color:\s*var\(--theme-primary-active\);/)
|
assert.match(workbenchStyles, /\.progress-row\.has-long-duration-divider::before\s*\{[\s\S]*color:\s*var\(--theme-primary-active\);/)
|
||||||
assert.match(workbenchStyles, /\.progress-row\.has-long-duration-divider::after\s*\{[\s\S]*rgba\(var\(--theme-primary-rgb/)
|
assert.match(workbenchStyles, /\.progress-row\.has-long-duration-divider::after\s*\{[\s\S]*rgba\(var\(--theme-primary-rgb/)
|
||||||
assert.match(workbenchStyles, /\.progress-time\s*\{[\s\S]*color:\s*var\(--workbench-muted\);/)
|
assert.match(workbenchStyles, /\.progress-time\s*\{[\s\S]*color:\s*var\(--workbench-muted\);/)
|
||||||
assert.match(workbenchResponsiveStyles, /grid-template-areas:[\s\S]*"time identity result"[\s\S]*"steps steps steps"/)
|
assert.match(workbenchResponsiveStyles, /grid-template-areas:[\s\S]*"time identity result"[\s\S]*"type type type"[\s\S]*"steps steps steps"/)
|
||||||
|
assert.match(workbenchResponsiveStyles, /\.progress-type\s*\{[\s\S]*grid-area:\s*type;/)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('workbench expense stats detail opens a local modal instead of the assistant', () => {
|
test('workbench expense stats detail opens a local modal instead of the assistant', () => {
|
||||||
|
|||||||
31
web/tests/workbench-detail-return.test.mjs
Normal file
31
web/tests/workbench-detail-return.test.mjs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import test from 'node:test'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const appShell = readFileSync(
|
||||||
|
fileURLToPath(new URL('../src/views/AppShellRouteView.vue', import.meta.url)),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
const appShellComposable = readFileSync(
|
||||||
|
fileURLToPath(new URL('../src/composables/useAppShell.js', import.meta.url)),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
const workbench = readFileSync(
|
||||||
|
fileURLToPath(new URL('../src/components/business/PersonalWorkbench.vue', import.meta.url)),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
test('workbench document detail keeps workbench as the return target', () => {
|
||||||
|
assert.match(workbench, /source:\s*'workbench'/)
|
||||||
|
assert.match(workbench, /returnTo:\s*'workbench'/)
|
||||||
|
assert.match(appShell, /:back-label="detailBackLabel"/)
|
||||||
|
assert.match(appShell, /String\(payload\.returnTo \|\| ''\)\.trim\(\) === 'workbench'/)
|
||||||
|
assert.match(appShell, /String\(payload\.source \|\| ''\)\.trim\(\) === 'workbench'/)
|
||||||
|
assert.match(appShell, /openRequestDetail\(request \|\| payload,\s*\{ returnTo \}\)/)
|
||||||
|
assert.match(appShellComposable, /const detailReturnTarget = computed/)
|
||||||
|
assert.match(appShellComposable, /detailReturnTarget\.value === 'workbench' \? '返回首页' : '返回单据中心'/)
|
||||||
|
assert.match(appShellComposable, /nextQuery\.returnTo = 'workbench'/)
|
||||||
|
assert.match(appShellComposable, /router\.push\(\{ name: 'app-workbench' \}\)/)
|
||||||
|
assert.match(appShellComposable, /router\.push\(\{ name: 'app-documents', query: buildDocumentReturnQuery\(\) \}\)/)
|
||||||
|
})
|
||||||
@@ -75,6 +75,7 @@ test('workbench summary builds real user notifications and progress from request
|
|||||||
summary.progressItems[0].steps.map((step) => step.label),
|
summary.progressItems[0].steps.map((step) => step.label),
|
||||||
['创建单据', '待提交', '直属领导审批', '财务审批']
|
['创建单据', '待提交', '直属领导审批', '财务审批']
|
||||||
)
|
)
|
||||||
|
assert.equal(summary.progressItems[0].expenseTypeLabel, '差旅交通')
|
||||||
assert.equal(summary.notifications.length, 1)
|
assert.equal(summary.notifications.length, 1)
|
||||||
assert.equal(summary.unreadNotificationCount, 1)
|
assert.equal(summary.unreadNotificationCount, 1)
|
||||||
assert.equal(summary.expenseStatsDetail.distributionRows[0].label, '差旅交通')
|
assert.equal(summary.expenseStatsDetail.distributionRows[0].label, '差旅交通')
|
||||||
|
|||||||
Reference in New Issue
Block a user