feat(ehrm): 用户详情员工档案改为单条档案面板(去表格)

去掉员工档案子表,改为一人一份的档案面板:无档案且有 eHRM 权限(菜单可见)时居中显示「点击填写」(抽屉动态表单),无权限显示「无权修改」;已有档案时用 el-descriptions 键值只读展示,卡片右上角提供编辑/删除。表单与列表共用 /API/FormAPI/Columns/EmployeeProfile 列定义,UserUUID 由本页上下文注入。撤回上一版 CrudTable 的 singleRow 属性(该方案已废弃)。
This commit is contained in:
TermiNexus committed 2026-09-30 23:45:46 +08:00
1 parent 1cdc44c1d0
commit cf621cb9ec
1 file changed
+244 -14
+244 -14
View File
@@ -25,17 +25,18 @@
*/
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import DetailPage from '@/components/DetailPage.vue'
import InfoGrid from '@/components/InfoGrid.vue'
import CrudTable from '@/components/CrudTable.vue'
import CrudFormPanel from '@/components/CrudFormPanel.vue'
import EntityPicker from '@/components/EntityPicker.vue'
import { formApi } from '@/api/form'
import { get } from '@/api/request'
import { useUserStore } from '@/stores/user'
import { getServerScopeParams, setPageTitle } from '@/utils/serverScope'
import { formatUtcTime as formatTime } from '@/utils/dateTime'
import type { ColumnSchema } from '@/types/api'
import type { ColumnSchema, MenuItem } from '@/types/api'
const route = useRoute()
const router = useRouter()
@@ -199,8 +200,171 @@ async function handleSubmit(): Promise<void> {
}
}
// === 子表跳转 ===
// ===== 员工档案(eHRM)=====
// 一人一份:无档案时给「点击填写」入口,已有档案时键值只读展示 + 右上角编辑/删除。
// 是否能写按 eHRM 权限判定(后端按应用安装 + 职务绑定过滤菜单,菜单可见即视为有权限)。
// 后端 form_service 已对「同一员工重复建档 / 改绑到已有档案的员工」做拦截。
/** eHRM 档案管理菜单路由(清单 MenuLink /ehrm/profile 规范化后的 Path) */
const EHRM_PROFILE_PATH = '/ehrm/profile'
/** 档案只读展示时排除的列:主键与纯 UUID 引用列(员工=本页用户,部门已有 DeptName 展示) */
const PROFILE_HIDDEN_FIELDS = ['EmployeeProfileUUID', 'UserUUID', 'DeptUUID', 'TenantUUID', 'CompanyUUID']
const profileLoading = ref(false)
/** 该员工的档案记录(一人一份,最多一条) */
const profileRecord = ref<Record<string, any> | null>(null)
/** 档案列定义(只读展示与填写表单共用) */
const profileColumns = ref<ColumnSchema[]>([])
const profileDialogVisible = ref(false)
const profileSubmitting = ref(false)
/** 表单是否为编辑态(false=首次填写) */
const profileIsEdit = ref(false)
const profileForm = reactive<Record<string, any>>({})
/** 菜单树中是否存在指定路由(判断 eHRM 档案管理菜单是否可见) */
function menuTreeHasPath(items: MenuItem[], path: string): boolean {
return items.some(
(m) => m.Path === path || (m.Children?.length ? menuTreeHasPath(m.Children, path) : false),
)
}
/** 是否具备 eHRM 档案修改权限(菜单可见即视为有权限) */
const canEditProfile = computed(() => menuTreeHasPath(userStore.menus, EHRM_PROFILE_PATH))
/** 只读展示列:在表格可见列基础上排除主键与纯 UUID 引用列 */
const profileDisplayColumns = computed(() =>
profileColumns.value.filter(
(c) =>
c.visible !== false &&
c.type !== 'hidden' &&
c.type !== 'divider' &&
c.type !== 'html' &&
!PROFILE_HIDDEN_FIELDS.includes(c.field),
),
)
/** 填写/编辑表单列:排除隐藏/主键/非表单类型,以及由本页上下文注入的 UserUUID */
const profileFormColumns = computed(() =>
profileColumns.value.filter(
(c) =>
c.editable !== false &&
!c.hideInForm &&
c.type !== 'hidden' &&
c.type !== 'divider' &&
c.type !== 'html' &&
c.field !== 'EmployeeProfileUUID' &&
c.field !== 'UserUUID',
),
)
/** 只读展示值:实体标签(*_label)优先 → 选项映射 → 日期格式化 → 原值 */
function profileValue(col: ColumnSchema): string {
const row = profileRecord.value
if (!row) return '-'
const label = row[`${col.field}_label`]
if (label) return String(label)
const raw = row[col.field]
if (raw === null || raw === undefined || raw === '') return '-'
if (col.options?.length) {
const hit = col.options.find((o) => String(o.value) === String(raw))
if (hit) return hit.label
}
if (col.type === 'date' || col.type === 'datetime') return formatTime(String(raw))
return String(raw)
}
/** 加载该员工的档案(列表按 UserUUID 过滤,取首条) */
async function loadProfile(): Promise<void> {
if (!userUUID.value) {
profileRecord.value = null
return
}
profileLoading.value = true
try {
if (!profileColumns.value.length) {
profileColumns.value = await formApi.getColumns('EmployeeProfile')
}
} catch {
// 列定义加载失败不阻塞只读展示
}
try {
const res = await formApi.list<Record<string, any>>('EmployeeProfile', {
UserUUID: userUUID.value,
pageSize: 1,
})
const list = Array.isArray(res) ? res : ((res as { list?: Record<string, any>[] })?.list ?? [])
profileRecord.value = list[0] ?? null
} catch {
// 错误提示已由响应拦截器统一弹出
profileRecord.value = null
} finally {
profileLoading.value = false
}
}
/** 「点击填写」:新建档案(UserUUID 由本页上下文注入,表单内不出现该字段) */
function openProfileCreate(): void {
if (!canEditProfile.value) return
Object.keys(profileForm).forEach((k) => delete profileForm[k])
profileForm.UserUUID = userUUID.value
profileIsEdit.value = false
profileDialogVisible.value = true
}
/** 「编辑」:回填现有档案 */
function openProfileEdit(): void {
if (!canEditProfile.value || !profileRecord.value) return
Object.keys(profileForm).forEach((k) => delete profileForm[k])
Object.assign(profileForm, profileRecord.value)
profileForm.UserUUID = userUUID.value
profileIsEdit.value = true
profileDialogVisible.value = true
}
/** 提交档案表单(新增 Add / 编辑 Modify) */
async function submitProfile(): Promise<void> {
profileSubmitting.value = true
try {
if (profileIsEdit.value && profileRecord.value) {
await formApi.update('EmployeeProfile', profileRecord.value.EmployeeProfileUUID as string, {
...profileForm,
})
} else {
await formApi.create('EmployeeProfile', { ...profileForm })
}
ElMessage.success(profileIsEdit.value ? '保存成功' : '填写成功')
profileDialogVisible.value = false
await loadProfile()
} catch {
// 错误提示已由响应拦截器统一弹出(如"该员工已存在档案")
} finally {
profileSubmitting.value = false
}
}
/** 「删除」:删除后可重新填写 */
async function deleteProfile(): Promise<void> {
if (!canEditProfile.value || !profileRecord.value) return
try {
await ElMessageBox.confirm('确认删除该员工的档案?删除后可重新填写。', '删除档案', {
type: 'warning',
confirmButtonText: '删除',
cancelButtonText: '取消',
})
} catch {
return // 用户取消
}
try {
await formApi.remove('EmployeeProfile', profileRecord.value.EmployeeProfileUUID as string)
ElMessage.success('已删除')
await loadProfile()
} catch {
// 错误提示已由响应拦截器统一弹出
}
}
// === 子表跳转 ===
/** UserRole 子表查看 → 角色详情 */
function goRoleDetail(row: Record<string, any>): void {
router.push({ path: '/system/role-detail', query: { RoleUUID: row.RoleUUID || '' } })
@@ -216,6 +380,7 @@ onMounted(async () => {
// 空值场景静默显示空态(loadData 内处理),不再弹警告
watch(userUUID, () => {
void loadData()
void loadProfile()
}, { immediate: true })
</script>
@@ -337,24 +502,47 @@ watch(userUUID, () => {
/>
</el-card>
<!-- eHRM 联动子表:员工档案 / 考核 / 绩效(query 中的 UserUUID 自动注入新增/编辑表单) -->
<!-- 员工档案(eHRM,一人一份):无档案给填写入口,有档案键值只读展示 + 右上角编辑/删除 -->
<el-card shadow="never" class="detail-card">
<template #header>
<div class="detail-card-header">
<el-icon><Document /></el-icon>
<span>员工档案(eHRM)</span>
<template v-if="profileRecord && canEditProfile">
<span class="profile-header-actions">
<el-button link type="primary" size="small" @click="openProfileEdit">
<el-icon><Edit /></el-icon>编辑
</el-button>
<el-button link type="danger" size="small" @click="deleteProfile">
<el-icon><Delete /></el-icon>删除
</el-button>
</span>
</template>
</div>
</template>
<CrudTable
v-if="userUUID"
table="EmployeeProfile"
title="员工档案"
:query="{ UserUUID: userUUID }"
pk-field="EmployeeProfileUUID"
selectable
pageable
:max-height="400"
/>
<div v-loading="profileLoading" class="profile-panel">
<!-- 已有档案:键值只读展示(不可更改) -->
<el-descriptions v-if="profileRecord" :column="3" border class="profile-desc">
<el-descriptions-item
v-for="col in profileDisplayColumns"
:key="col.field"
:label="col.title"
>
{{ profileValue(col) }}
</el-descriptions-item>
</el-descriptions>
<!-- 无档案 + 有 eHRM 修改权限:居中「点击填写」 -->
<div v-else-if="canEditProfile" class="profile-empty">
<el-button type="primary" size="large" @click="openProfileCreate">
<el-icon><Plus /></el-icon>点击填写
</el-button>
</div>
<!-- 无档案 + 无 eHRM 修改权限 -->
<el-empty v-else description="无权修改" :image-size="70" />
</div>
</el-card>
<el-card shadow="never" class="detail-card">
@@ -481,6 +669,17 @@ watch(userUUID, () => {
<el-button type="primary" :loading="editSubmitting" @click="handleSubmit">保存</el-button>
</template>
</el-drawer>
<!-- 员工档案 填写/编辑表单(动态列渲染,UserUUID 由本页上下文注入) -->
<CrudFormPanel
v-model:visible="profileDialogVisible"
:title="profileIsEdit ? '编辑员工档案' : '填写员工档案'"
:submitting="profileSubmitting"
:columns="profileFormColumns"
:form-model="profileForm"
mode="drawer"
@submit="submitProfile"
/>
</DetailPage>
</template>
@@ -504,4 +703,35 @@ watch(userUUID, () => {
flex-shrink: 0;
background: var(--el-fill-color-light);
}
// ===== 员工档案面板(eHRM)=====
// 卡片头右侧操作按钮(编辑/删除)推到最右
.profile-header-actions {
margin-left: auto;
display: flex;
align-items: center;
gap: 4px;
}
// 面板主体:加载态用 v-loading,故给最小高度避免抖动
.profile-panel {
min-height: 72px;
}
// 只读键值表:标签区紧凑 + 内容换行
.profile-desc {
:deep(.el-descriptions__label) {
width: 110px;
font-weight: 500;
color: var(--el-text-color-regular);
}
}
// 无档案空态:居中「点击填写」
.profile-empty {
display: flex;
align-items: center;
justify-content: center;
padding: 24px 0;
}
</style>