feat(management): 接入文档用户权限与报表

This commit is contained in:
yelan
2026-09-01 10:13:11 +08:00
parent beeb98d18f
commit e467f03f23
12 changed files with 2361 additions and 41 deletions
+271 -11
View File
@@ -2,21 +2,64 @@
import { computed, onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { getCampuses, getDepartments } from '@/api/management/organization'
import { getRoles } from '@/api/management/roles'
import { getUserDetail, getUsers, isUserMockEnabled } from '@/api/management/users'
import { createUser, updateUser } from '@/api/management/users'
import type {
CampusRecord,
DepartmentRecord,
RoleRecord,
UserRecord as ApiUserRecord,
} from '@/api/management/types'
import { useAppStore } from '@/stores/app'
import RoleSummaryCards from './components/RoleSummaryCards.vue'
import UserTable from './components/UserTable.vue'
import UserEditorDialog from './components/UserEditorDialog.vue'
import { roleColors, roleSummaries, users } from './mock'
import type { RoleSummary, UserTableRow } from './types'
import type { RoleSummary, UserEditorForm, UserTableRow } from './types'
const appStore = useAppStore()
const loading = ref(true)
const keyword = ref('')
const roleRows = ref<RoleSummary[]>([])
const userRows = ref<UserTableRow[]>([])
const roleColorMap = ref<Record<string, string>>(roleColors)
const campuses = ref<CampusRecord[]>([])
const departments = ref<DepartmentRecord[]>([])
const editorVisible = ref(false)
const editorInitial = ref<UserEditorForm | null>(null)
const editorSaving = ref(false)
const mockCampuses: CampusRecord[] = [
{ id: 'mock-main', code: 'MAIN', name: '本部院区', status: 'ENABLED', sortNo: 1 },
{ id: 'mock-east', code: 'EAST', name: '东院区', status: 'ENABLED', sortNo: 2 },
{ id: 'mock-west', code: 'WEST', name: '西院区', status: 'ENABLED', sortNo: 3 },
]
const mockDepartments: DepartmentRecord[] = [
'医务处',
'放射科',
'心内科',
'骨科',
'健康管理中心',
'信息科',
'纪检监察室',
].map((name, index) => ({
id: `mock-department-${index + 1}`,
campusId: index === 3 || index === 4 ? 'mock-east' : 'mock-main',
code: `MOCK-${index + 1}`,
name,
status: 'ENABLED',
sortNo: index + 1,
}))
const visibleUsers = computed(() => {
const normalizedKeyword = keyword.value.trim().toLowerCase()
return userRows.value.filter((user) => {
const matchesCampus = user.campus === '本部院区'
const matchesCampus = user.campus === appStore.selectedCampus
const matchesKeyword =
!normalizedKeyword ||
[user.name, user.employeeNo].join(' ').toLowerCase().includes(normalizedKeyword)
@@ -27,26 +70,233 @@ const visibleUsers = computed(() => {
async function loadUsers() {
loading.value = true
await Promise.resolve()
roleRows.value = roleSummaries.map((role) => ({ ...role }))
userRows.value = users.map((user) => ({ ...user }))
loading.value = false
try {
if (isUserMockEnabled) {
campuses.value = mockCampuses.map((campus) => ({ ...campus }))
departments.value = mockDepartments.map((department) => ({ ...department }))
roleRows.value = roleSummaries.map((role) => ({ ...role }))
userRows.value = users.map((user) => ({ ...user }))
roleColorMap.value = roleColors
return
}
const [usersResult, rolesResult, campusesResult, departmentsResult] = await Promise.allSettled([
getUsers({ page: 1, pageSize: 200, status: 'all' }),
getRoles(),
getCampuses(),
getDepartments(),
])
if (usersResult.status === 'rejected') {
throw usersResult.reason
}
const campusNames = new Map(
campusesResult.status === 'fulfilled'
? campusesResult.value.map((campus) => [campus.id, campus.name])
: [],
)
const departmentNames = new Map(
departmentsResult.status === 'fulfilled'
? departmentsResult.value.map((department) => [department.id, department.name])
: [],
)
campuses.value = campusesResult.status === 'fulfilled' ? campusesResult.value : []
departments.value = departmentsResult.status === 'fulfilled' ? departmentsResult.value : []
const apiUsers = usersResult.value.records
userRows.value = apiUsers.map((user) => mapUserRow(user, campusNames, departmentNames))
const roles = rolesResult.status === 'fulfilled' ? rolesResult.value : []
roleRows.value = roles.map((role) => mapRoleSummary(role, apiUsers))
roleColorMap.value = Object.fromEntries(
roles.map((role) => [role.name, role.color ?? '#8a9aa3']),
)
} catch {
roleRows.value = []
userRows.value = []
ElMessage.error('用户与角色加载失败,请稍后重试')
} finally {
loading.value = false
}
}
function mapUserRow(
user: ApiUserRecord,
campusNames: Map<string, string>,
departmentNames: Map<string, string>,
): UserTableRow {
const dataScopeLabels: Record<string, string> = {
ALL: '全院',
CAMPUS: '院区',
DEPARTMENT: '本科室',
READ_ONLY_ALL: '全院只读',
}
return {
id: user.id,
name: user.name,
account: user.account ?? '',
employeeNo: user.employeeNo ?? '—',
phone: user.phone ?? '',
email: user.email ?? '',
campus: campusNames.get(user.campusId ?? '') ?? '未指定院区',
campusId: user.campusId,
department: departmentNames.get(user.departmentId ?? '') ?? user.department ?? '未指定科室',
departmentId: user.departmentId,
role: user.role,
dataScope: dataScopeLabels[user.dataScope ?? ''] ?? user.dataScope ?? '—',
status: user.status,
}
}
function mapRoleSummary(role: RoleRecord, users: ApiUserRecord[]): RoleSummary {
const userCount =
role.userCount ??
users.filter((user) => user.roleNames?.some((roleName) => roleName === role.name)).length
return {
id: role.id,
name: role.name,
color: role.color ?? '#8a9aa3',
description: role.description,
userCount,
}
}
function showAddUserMessage() {
ElMessage.info('原型演示:新增用户')
editorInitial.value = null
editorVisible.value = true
}
function showImportMessage() {
ElMessage.info('原型演示:批量导入')
}
function editUser(user: UserTableRow) {
ElMessage.info(`原型演示:编辑用户《${user.name}》`)
async function editUser(row: UserTableRow) {
try {
const user = isUserMockEnabled
? userRows.value.find((item) => item.id === row.id)
: await getUserDetail(row.id)
if (!user) {
ElMessage.error('用户不存在或已被删除')
return
}
editorInitial.value = {
id: user.id,
username: user.account ?? '',
password: '',
displayName: user.name,
employeeNo: user.employeeNo ?? '',
phone: user.phone ?? '',
email: user.email ?? '',
campusId:
user.campusId ?? campuses.value.find((campus) => campus.name === row.campus)?.id ?? '',
departmentId:
user.departmentId ??
departments.value.find((department) => department.name === row.department)?.id ??
'',
status: user.status,
dataScope: toBackendDataScope(user.dataScope),
}
editorVisible.value = true
} catch {
ElMessage.error('用户详情加载失败,请稍后重试')
}
}
function resetPassword(user: UserTableRow) {
ElMessage.success(`已重置${user.name}的密码`)
ElMessage.info(
isUserMockEnabled
? `原型演示:重置${user.name}的密码`
: '当前接口未提供重置密码能力,请由认证系统或管理员流程处理',
)
}
function toBackendDataScope(value: string | undefined) {
const map: Record<string, string> = {
全院: 'ALL',
院区: 'CAMPUS',
本科室: 'DEPARTMENT',
全院只读: 'READ_ONLY_ALL',
}
return map[value ?? ''] ?? value ?? 'DEPARTMENT'
}
function getCampusName(id: string) {
return campuses.value.find((campus) => campus.id === id)?.name ?? '未指定院区'
}
function getDepartmentName(id: string) {
return departments.value.find((department) => department.id === id)?.name ?? '未指定科室'
}
async function saveUser(form: UserEditorForm) {
editorSaving.value = true
try {
if (isUserMockEnabled) {
const row: UserTableRow = {
id: form.id ?? `mock-user-${Date.now()}`,
name: form.displayName,
account: form.username,
employeeNo: form.employeeNo || '—',
phone: form.phone,
email: form.email,
campus: getCampusName(form.campusId),
campusId: form.campusId,
department: getDepartmentName(form.departmentId),
departmentId: form.departmentId || null,
role: form.id
? (userRows.value.find((item) => item.id === form.id)?.role ?? '未分配角色')
: '未分配角色',
dataScope: form.dataScope,
status: form.status,
}
const index = userRows.value.findIndex((item) => item.id === row.id)
if (index === -1) {
userRows.value.unshift(row)
} else {
userRows.value[index] = row
}
} else if (form.id) {
await updateUser(form.id, {
displayName: form.displayName,
employeeNo: form.employeeNo || undefined,
phone: form.phone || undefined,
email: form.email || undefined,
campusId: form.campusId,
departmentId: form.departmentId || null,
status: form.status.toUpperCase(),
dataScope: form.dataScope,
})
await loadUsers()
} else {
await createUser({
username: form.username,
password: form.password,
displayName: form.displayName,
employeeNo: form.employeeNo || undefined,
phone: form.phone || undefined,
email: form.email || undefined,
campusId: form.campusId,
departmentId: form.departmentId || null,
status: form.status.toUpperCase(),
dataScope: form.dataScope,
})
await loadUsers()
}
editorVisible.value = false
ElMessage.success(form.id ? '用户已更新' : '用户已创建')
} catch {
ElMessage.error(form.id ? '用户更新失败,请稍后重试' : '用户创建失败,请稍后重试')
} finally {
editorSaving.value = false
}
}
onMounted(loadUsers)
@@ -77,12 +327,22 @@ onMounted(loadUsers)
</div>
<UserTable
:rows="visibleUsers"
:role-colors="roleColors"
:role-colors="roleColorMap"
@edit="editUser"
@reset-password="resetPassword"
/>
</section>
</template>
<UserEditorDialog
:visible="editorVisible"
:initial="editorInitial"
:campuses="campuses"
:departments="departments"
:saving="editorSaving"
@close="editorVisible = false"
@save="saveUser"
/>
</section>
</template>