修复前端:按真实门禁结果展示下发注销与对账状态

This commit is contained in:
wangzhibiao committed 2026-09-21 16:55:57 +08:00
1 parent 7d10968b50
commit 9922ae3aa4
2 files changed
+216 -38

No files matched your search

@@ -0,0 +1,188 @@
/**
* 门禁凭据与对账交互端点前端结果语义解析器 (Package A.3 - MAJ-05)
*
* 核心原则:
* 1. 杜绝依赖 AjaxResult 顶层 res.msg (防止“操作成功”假成功);
* 2. 严格按后端 DTO 真实契约解析 (ReconciliationResult, IssueResultVO);
* 3. 严禁对排队、未执行、部分失败或人工对账展示“成功”。
*/
/**
* 解析门禁注销操作结果反馈 (POST /train/access/auth/{authId}/revoke)
* @param {Object} data - res.data (ReconciliationResult)
* @returns {{ type: 'success'|'info'|'warning'|'error', message: string }}
*/
export function resolveRevokeFeedback(data) {
const d = data || {}
const decision = d.decision || ''
const success = d.success === true
const externalInvoked = d.externalInvoked === true
const reason = d.reason || ''
// 1. decision == NO_ACTION && success == true -> 无需操作或已处于安全终态 (info/success)
if (decision === 'NO_ACTION' && success) {
return {
type: 'info',
message: reason || '无需操作或已处于安全终态'
}
}
// 2. decision == REVOKE_REQUIRED && success == true && externalInvoked == true
if (decision === 'REVOKE_REQUIRED' && success && externalInvoked) {
return {
type: 'info',
message: '注销请求已提交,等待设备确认。'
}
}
// 3. decision == REVOKE_REQUIRED && success == true && externalInvoked == false
if (decision === 'REVOKE_REQUIRED' && success && !externalInvoked) {
return {
type: 'warning',
message: reason || '当前未执行外部注销,等待DMS恢复或后续处理。'
}
}
// 4. decision == FAILED || success == false -> error 直接展示 reason
if (decision === 'FAILED' || !success) {
return {
type: 'error',
message: reason || '门禁注销失败'
}
}
// 安全兜底
return {
type: 'info',
message: reason || '注销请求已提交'
}
}
/**
* 解析门禁凭据显式下发结果反馈 (POST /train/access/auth/{authId}/issue)
* @param {Object} data - res.data (IssueResultVO)
* @returns {{ type: 'success'|'warning'|'error', message: string, openCredentialDialog: boolean }}
*/
export function resolveIssueFeedback(data) {
const d = data || {}
const status = d.status || ''
const message = d.message || ''
const hasCredential = Boolean(d.oneTimePlaintextCredential && String(d.oneTimePlaintextCredential).trim().length > 0)
// 1. ACCEPTED: 仅在有 credential 时才允许弹窗;提示“凭据已提交下发,设备确认后生效。”
if (status === 'ACCEPTED') {
return {
type: 'success',
message: '凭据已提交下发,设备确认后生效。',
openCredentialDialog: hasCredential
}
}
// 2. WAITING_FOR_PREREQUISITE (包含 MISSING_STUDENT_PHONE, INVALID_VALIDITY_WINDOW, DMS_DISABLED 等真实 reason)
if (status === 'WAITING_FOR_PREREQUISITE') {
return {
type: 'warning',
message: message || '前置条件未满足,暂无法下发门禁凭据',
openCredentialDialog: false
}
}
// 3. AMBIGUOUS_EXTERNAL_MUTATION: 不得允许直接重发
if (status === 'AMBIGUOUS_EXTERNAL_MUTATION' || status === 'AMBIGUOUS') {
return {
type: 'error',
message: message || '上一次下发结果无法确认,禁止重复下发,请先核对厂商侧凭据。(AMBIGUOUS_EXTERNAL_MUTATION)',
openCredentialDialog: false
}
}
// 4. FAILED 或其他错误
return {
type: 'error',
message: message || '门禁凭据下发失败',
openCredentialDialog: false
}
}
/**
* 解析门禁手动对账结果反馈 (POST /train/access/reconcile/{enrollmentId})
* @param {Object} data - res.data (ReconciliationResult)
* @returns {{ type: 'success'|'info'|'warning'|'error', message: string, openCredentialDialog: boolean }}
*/
export function resolveReconcileFeedback(data) {
const d = data || {}
const batchResult = d.batchResult || null
const outcome = (batchResult && batchResult.outcome) || d.outcome || (d.decision === 'NO_ACTION' ? 'ALL_SETTLED' : '')
const reason = d.reason || ''
const hasCredential = Boolean(d.oneTimePlaintextCredential && String(d.oneTimePlaintextCredential).trim().length > 0)
// 1. ALL_SETTLED -> success
if (outcome === 'ALL_SETTLED') {
return {
type: 'success',
message: reason || '对账完成:所有授权均已处于安全终态或无需操作',
openCredentialDialog: hasCredential
}
}
// 2. IN_PROGRESS -> info
if (outcome === 'IN_PROGRESS') {
return {
type: 'info',
message: reason || '对账处理中:指令已派发至外部设备,等待设备确认',
openCredentialDialog: hasCredential
}
}
// 3. WAITING_FOR_DMS -> warning
if (outcome === 'WAITING_FOR_DMS') {
return {
type: 'warning',
message: reason || '门禁系统当前未启用,操作已排队等待DMS恢复后处理',
openCredentialDialog: hasCredential
}
}
// 4. PARTIAL_FAILURE -> error (明确“部分处理失败”,严禁报对账成功)
if (outcome === 'PARTIAL_FAILURE') {
return {
type: 'error',
message: reason ? ('部分处理失败:' + reason) : '对账完成:部分授权处理失败,需关注处理',
openCredentialDialog: false
}
}
// 5. ALL_FAILED -> error
if (outcome === 'ALL_FAILED') {
return {
type: 'error',
message: reason ? ('对账处理失败:' + reason) : '对账执行失败:所有授权操作均失败',
openCredentialDialog: false
}
}
// 6. REQUIRES_MANUAL_SETTLEMENT -> error (明确“需要人工核对厂商侧状态”)
if (outcome === 'REQUIRES_MANUAL_SETTLEMENT') {
return {
type: 'error',
message: reason ? ('需要人工核对厂商侧状态:' + reason) : '需要人工核对厂商侧状态:外部凭据状态不确定,阻断自动处理',
openCredentialDialog: false
}
}
// 7. 兜底逻辑:若明确 success == false
if (d.success === false) {
return {
type: 'error',
message: reason || '对账执行未成功',
openCredentialDialog: false
}
}
// 8. 默认中性提示
return {
type: 'info',
message: reason || '对账执行完成',
openCredentialDialog: hasCredential
}
}
+28 -38
View File
@@ -301,6 +301,7 @@
<script>
import { listAuth, getOperationHistory, manualReconcile, issueAuth, revokeAuth, getCapabilities } from '@/api/train/access'
import { resolveRevokeFeedback, resolveIssueFeedback, resolveReconcileFeedback } from './authFeedback'
export default {
name: 'Auth',
@@ -386,9 +387,18 @@ export default {
).then(() => {
return manualReconcile(row.enrollmentId)
}).then(res => {
this.$modal.msgSuccess(res.msg || '对账执行成功')
const feedback = resolveReconcileFeedback(res.data)
if (feedback.type === 'success') {
this.$modal.msgSuccess(feedback.message)
} else if (feedback.type === 'warning') {
this.$modal.msgWarning(feedback.message)
} else if (feedback.type === 'error') {
this.$modal.msgError(feedback.message)
} else {
this.$modal.msgInfo(feedback.message)
}
this.getList()
if (res.data && res.data.oneTimePlaintextCredential) {
if (feedback.openCredentialDialog && res.data && res.data.oneTimePlaintextCredential) {
this.oneTimeCredential = res.data.oneTimePlaintextCredential
this.maskedMode = false
this.oneTimeDialogOpen = true
@@ -403,29 +413,17 @@ export default {
this.$modal.confirm(`确认向学员【${row.studentName || row.studentId}】下发门禁凭据?`).then(() => {
return issueAuth(row.authId)
}).then(res => {
const data = res.data || {}
const decision = data.decision || data.status || ''
const msg = res.msg || data.message || ''
if (decision === 'DISPATCHED' || data.status === 'ACTIVE' || data.status === 'GRANTING') {
this.$modal.msgSuccess(msg || '凭据已提交下发,设备确认后生效')
} else if (decision === 'WAITING_FOR_DMS' || msg.includes('DMS当前未启用')) {
this.$modal.msgWarning(msg || 'DMS当前未启用,未执行门禁下发')
} else if (decision === 'MISSING_STUDENT_PHONE') {
this.$modal.msgWarning(msg || '缺少学员手机号,未下发门禁')
} else if (decision === 'INVALID_VALIDITY_WINDOW') {
this.$modal.msgWarning(msg || '授权起止时间无效,未下发门禁')
} else if (decision === 'AMBIGUOUS_EXTERNAL_MUTATION' || decision === 'AMBIGUOUS') {
this.$modal.msgError(msg || '上一次下发结果无法确认,禁止重复下发,请先核对厂商侧凭据')
} else if (decision === 'FAILED' || !res.data) {
this.$modal.msgError(msg || '凭据下发失败')
const feedback = resolveIssueFeedback(res.data)
if (feedback.type === 'success') {
this.$modal.msgSuccess(feedback.message)
} else if (feedback.type === 'warning') {
this.$modal.msgWarning(feedback.message)
} else {
this.$modal.msgInfo(msg || '凭据处理完成')
this.$modal.msgError(feedback.message)
}
this.getList()
if (data.oneTimePlaintextCredential) {
this.oneTimeCredential = data.oneTimePlaintextCredential
if (feedback.openCredentialDialog && res.data && res.data.oneTimePlaintextCredential) {
this.oneTimeCredential = res.data.oneTimePlaintextCredential
this.maskedMode = false
this.oneTimeDialogOpen = true
}
@@ -442,23 +440,15 @@ export default {
this.$modal.confirm(`确定要注销学员【${studentDesc}】在宿舍【${dormDesc}】的门禁授权(授权ID: ${row.authId})吗?注销后该学员将无法通行该门禁。`).then(() => {
return revokeAuth(row.authId)
}).then(res => {
const data = res.data || {}
const status = data.authStatus || data.status || ''
const decision = data.decision || ''
const msg = res.msg || data.message || ''
if (status === 'REVOKED') {
this.$modal.msgSuccess(msg || '门禁注销成功')
} else if (status === 'PENDING_REVOKE' || decision === 'WAITING_FOR_DMS') {
this.$modal.msgWarning(msg || '已建立注销队列,等待DMS处理')
} else if (status === 'REVOKING' || decision === 'DISPATCHED') {
this.$modal.msgInfo(msg || '注销指令已提交,设备确认后生效')
} else if (status === 'AMBIGUOUS') {
this.$modal.msgError(msg || '外部注销结果不确定,请人工核对')
} else if (status === 'ERROR') {
this.$modal.msgError(msg || '注销操作失败')
const feedback = resolveRevokeFeedback(res.data)
if (feedback.type === 'success') {
this.$modal.msgSuccess(feedback.message)
} else if (feedback.type === 'warning') {
this.$modal.msgWarning(feedback.message)
} else if (feedback.type === 'error') {
this.$modal.msgError(feedback.message)
} else {
this.$modal.msgInfo(msg || '注销请求已提交')
this.$modal.msgInfo(feedback.message)
}
this.getList()
}).catch(() => {})