feat(gerp): 档案页身份证号二次密码解锁查看

- 档案管理页新增「查看身份证」按钮:输入当前登录账号密码解锁
- 解锁成功后刷新列表,10 分钟内切换他人档案同样可查看(解锁态后端按登录用户维护)
- api/form.ts 新增 ehrmIdCardApi(unlock/status)
This commit is contained in:
TermiNexus committed 2026-10-02 14:13:30 +08:00
1 parent 1c5b239f2f
commit 25ee6ab0bd
2 files changed
+109 -2

No files matched your search

+17
View File
@@ -91,3 +91,20 @@ export const formApi = {
return post<T>('/API/FormAPI/BatchPositions', data)
},
}
/**
* eHRM 员工档案身份证:二次密码验证
*
* 身份证号为敏感字段,未解锁时后端返回空值(完全隐藏)。
* 输入当前登录用户密码后开启 10 分钟查看窗口,解锁态按登录用户维度共享,
* 因此窗口内切换到其他员工档案也能查看。
*/
export const ehrmIdCardApi = {
/** 校验当前登录用户密码,开启 10 分钟身份证查看窗口 */
unlock: (password: string): Promise<{ unlocked: boolean; expiresIn: number }> =>
post<{ unlocked: boolean; expiresIn: number }>('/API/Ehrm/UnlockIdCard', { Password: password }),
/** 查询当前登录用户是否处于解锁窗口内 */
status: (): Promise<{ unlocked: boolean }> =>
get<{ unlocked: boolean }>('/API/Ehrm/IdCardStatus'),
}
+92 -2
View File
@@ -3,23 +3,76 @@
* 档案管理(eHRM 人力资源管理扩展)
*
* 员工档案表:工号 / 部门 / 入职日期 / 职务 / 学历 / 紧急联系人 / 地址等。
*
* 身份证号(IdCard)为敏感列:未二次验证时后端返回空值(完全隐藏),
* 需在此页点「查看身份证」输入**当前登录账号**密码解锁;解锁后 10 分钟内在
* 任意员工档案(含切换到别人)都能查看——解锁态由后端按登录用户维度维护。
*/
import { computed } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import CrudTable from '@/components/CrudTable.vue'
import AdminBadge from '@/components/AdminBadge.vue'
import { useUserStore } from '@/stores/user'
import { setPageTitle } from '@/utils/serverScope'
import { ehrmIdCardApi } from '@/api/form'
const userStore = useUserStore()
setPageTitle('档案管理')
const isAdmin = computed(() => userStore.isAdmin)
// ===== 身份证二次密码验证 =====
// 解锁窗口(10 分钟)由后端记录,前端只负责触发解锁并在成功后刷新列表取明文
const tableRef = ref()
const idCardUnlocked = ref(false)
const unlockDialogVisible = ref(false)
const unlockPassword = ref('')
const unlocking = ref(false)
/** 查询当前登录用户是否已处于解锁窗口内(页面刷新后恢复按钮状态) */
async function loadIdCardStatus(): Promise<void> {
try {
const res = await ehrmIdCardApi.status()
idCardUnlocked.value = !!res?.unlocked
} catch {
idCardUnlocked.value = false
}
}
function openUnlockDialog(): void {
unlockPassword.value = ''
unlockDialogVisible.value = true
}
/** 提交密码:成功后刷新列表(此时后端才会在 EmployeeProfile 记录里返回身份证明文) */
async function submitUnlock(): Promise<void> {
if (!unlockPassword.value) {
ElMessage.warning('请输入密码')
return
}
unlocking.value = true
try {
await ehrmIdCardApi.unlock(unlockPassword.value)
idCardUnlocked.value = true
unlockDialogVisible.value = false
unlockPassword.value = ''
ElMessage.success('验证成功,10 分钟内可查看身份证号')
await tableRef.value?.refresh?.()
} catch {
// 失败提示由响应拦截器统一弹出(如「密码错误」),弹窗保留供重试
} finally {
unlocking.value = false
}
}
onMounted(loadIdCardStatus)
</script>
<template>
<div class="ehrm-page">
<AdminBadge />
<CrudTable
ref="tableRef"
table="EmployeeProfile"
title="员工档案"
pk-field="EmployeeProfileUUID"
@@ -27,7 +80,37 @@ const isAdmin = computed(() => userStore.isAdmin)
pageable
stretch
:selectable="isAdmin"
/>
>
<template #toolbar>
<el-tag v-if="idCardUnlocked" type="success" effect="plain">身份证已解锁</el-tag>
<el-button v-else type="warning" plain @click="openUnlockDialog">
<el-icon><Lock /></el-icon>查看身份证
</el-button>
</template>
</CrudTable>
<el-dialog
v-model="unlockDialogVisible"
title="查看身份证号"
width="440px"
append-to-body
>
<p class="idcard-tip">
身份证号为敏感信息,请输入当前登录账号的密码完成二次验证。
验证通过后 10 分钟内,切换到其他员工档案也可查看。
</p>
<el-input
v-model="unlockPassword"
type="password"
placeholder="请输入当前登录账号密码"
show-password
@keyup.enter="submitUnlock"
/>
<template #footer>
<el-button @click="unlockDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="unlocking" @click="submitUnlock">验证</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -39,4 +122,11 @@ const isAdmin = computed(() => userStore.isAdmin)
flex-direction: column;
box-sizing: border-box;
}
.idcard-tip {
margin: 0 0 12px;
font-size: 13px;
line-height: 1.6;
color: var(--el-text-color-secondary);
}
</style>