feat(management): 接入文档用户权限与报表
This commit is contained in:
@@ -0,0 +1,380 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, watch } from 'vue'
|
||||
|
||||
import type { CampusRecord, DepartmentRecord } from '@/api/management/types'
|
||||
|
||||
import type { UserEditorForm } from '../types'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
initial: UserEditorForm | null
|
||||
campuses: CampusRecord[]
|
||||
departments: DepartmentRecord[]
|
||||
saving: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
save: [form: UserEditorForm]
|
||||
}>()
|
||||
|
||||
const form = reactive<UserEditorForm>(createEmptyForm())
|
||||
const error = reactive({ message: '' })
|
||||
|
||||
const isEditing = computed(() => Boolean(form.id))
|
||||
const availableDepartments = computed(() =>
|
||||
props.departments.filter((department) => !form.campusId || department.campusId === form.campusId),
|
||||
)
|
||||
|
||||
function createEmptyForm(): UserEditorForm {
|
||||
return {
|
||||
username: '',
|
||||
password: '',
|
||||
displayName: '',
|
||||
employeeNo: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
campusId: props.campuses[0]?.id ?? '',
|
||||
departmentId: '',
|
||||
status: 'enabled',
|
||||
dataScope: 'DEPARTMENT',
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
Object.assign(form, props.initial ? { ...props.initial } : createEmptyForm())
|
||||
error.message = ''
|
||||
}
|
||||
|
||||
function handleCampusChange() {
|
||||
if (!availableDepartments.value.some((department) => department.id === form.departmentId)) {
|
||||
form.departmentId = ''
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
error.message = ''
|
||||
|
||||
if (!form.displayName.trim()) {
|
||||
error.message = '请输入姓名'
|
||||
return
|
||||
}
|
||||
|
||||
if (!isEditing.value && !form.username.trim()) {
|
||||
error.message = '请输入登录账号'
|
||||
return
|
||||
}
|
||||
|
||||
if (!isEditing.value && !form.password) {
|
||||
error.message = '请输入初始密码'
|
||||
return
|
||||
}
|
||||
|
||||
if (!form.campusId) {
|
||||
error.message = '请选择院区'
|
||||
return
|
||||
}
|
||||
|
||||
emit('save', {
|
||||
...form,
|
||||
username: form.username.trim(),
|
||||
displayName: form.displayName.trim(),
|
||||
employeeNo: form.employeeNo.trim(),
|
||||
phone: form.phone.trim(),
|
||||
email: form.email.trim(),
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
resetForm()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.initial,
|
||||
() => {
|
||||
if (props.visible) {
|
||||
resetForm()
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="visible" class="dialog-mask" @click.self="emit('close')">
|
||||
<section
|
||||
class="dialog-card"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="user-editor-title"
|
||||
>
|
||||
<header class="dialog-header">
|
||||
<div>
|
||||
<strong id="user-editor-title">{{ isEditing ? '编辑用户' : '新增用户' }}</strong>
|
||||
<p>{{ isEditing ? '更新用户基础资料与数据范围' : '创建可登录医签通的医护用户' }}</p>
|
||||
</div>
|
||||
<button type="button" class="close-button" aria-label="关闭" @click="emit('close')">
|
||||
×
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<form class="dialog-body" @submit.prevent="submit">
|
||||
<div class="form-grid">
|
||||
<label v-if="!isEditing">
|
||||
<span>登录账号 <em>*</em></span>
|
||||
<input v-model="form.username" autocomplete="username" placeholder="如:zhangsan" />
|
||||
</label>
|
||||
<label v-else>
|
||||
<span>登录账号</span>
|
||||
<input :value="form.username" disabled />
|
||||
</label>
|
||||
<label>
|
||||
<span>姓名 <em>*</em></span>
|
||||
<input v-model="form.displayName" autocomplete="name" placeholder="请输入姓名" />
|
||||
</label>
|
||||
<label v-if="!isEditing">
|
||||
<span>初始密码 <em>*</em></span>
|
||||
<input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
placeholder="请输入初始密码"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>工号</span>
|
||||
<input v-model="form.employeeNo" placeholder="请输入工号" />
|
||||
</label>
|
||||
<label>
|
||||
<span>手机号</span>
|
||||
<input
|
||||
v-model="form.phone"
|
||||
inputmode="tel"
|
||||
autocomplete="tel"
|
||||
placeholder="请输入手机号"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>邮箱</span>
|
||||
<input
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
autocomplete="email"
|
||||
placeholder="请输入邮箱"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>院区 <em>*</em></span>
|
||||
<select v-model="form.campusId" @change="handleCampusChange">
|
||||
<option value="" disabled>请选择院区</option>
|
||||
<option v-for="campus in campuses" :key="campus.id" :value="campus.id">
|
||||
{{ campus.name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>科室</span>
|
||||
<select v-model="form.departmentId">
|
||||
<option value="">未指定科室</option>
|
||||
<option
|
||||
v-for="department in availableDepartments"
|
||||
:key="department.id"
|
||||
:value="department.id"
|
||||
>
|
||||
{{ department.name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>状态</span>
|
||||
<select v-model="form.status">
|
||||
<option value="enabled">启用</option>
|
||||
<option value="disabled">停用</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>数据范围</span>
|
||||
<select v-model="form.dataScope">
|
||||
<option value="ALL">全院</option>
|
||||
<option value="CAMPUS">院区</option>
|
||||
<option value="DEPARTMENT">本科室</option>
|
||||
<option value="READ_ONLY_ALL">全院只读</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<p v-if="error.message" class="form-error">{{ error.message }}</p>
|
||||
<p class="form-tip">当前接口未提供用户角色绑定字段,角色分配暂保留在角色管理流程。</p>
|
||||
</form>
|
||||
|
||||
<footer class="dialog-footer">
|
||||
<span class="footer-spacer" />
|
||||
<button type="button" class="button button--ghost" @click="emit('close')">取消</button>
|
||||
<button type="button" class="button" :disabled="saving" @click="submit">
|
||||
{{ saving ? '保存中…' : '保存' }}
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dialog-mask {
|
||||
position: fixed;
|
||||
z-index: 50;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
background: rgb(9 40 52 / 45%);
|
||||
}
|
||||
|
||||
.dialog-card {
|
||||
width: 680px;
|
||||
max-width: 96vw;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--sh-modal);
|
||||
}
|
||||
|
||||
.dialog-header,
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 14px 18px;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.dialog-header strong {
|
||||
color: var(--ink);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.dialog-header p {
|
||||
margin: 4px 0 0;
|
||||
color: var(--mut);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
padding: 0;
|
||||
margin-left: auto;
|
||||
color: var(--mut);
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px 16px;
|
||||
}
|
||||
|
||||
.form-grid label {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.form-grid label > span {
|
||||
color: var(--ink);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.form-grid em {
|
||||
color: var(--err);
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.form-grid input,
|
||||
.form-grid select {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
color: var(--ink);
|
||||
font: inherit;
|
||||
font-size: 12.5px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.form-grid input:focus,
|
||||
.form-grid select:focus {
|
||||
outline: none;
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
.form-grid input:disabled {
|
||||
color: var(--mut);
|
||||
background: #f5f8f9;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
margin: 12px 0 0;
|
||||
color: var(--err);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
padding: 9px 10px;
|
||||
margin: 14px 0 0;
|
||||
color: var(--mut);
|
||||
font-size: 11.5px;
|
||||
line-height: 1.6;
|
||||
background: #f7fafb;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
background: #f8fbfc;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.footer-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 8px 16px;
|
||||
color: #fff;
|
||||
font-size: 12.5px;
|
||||
font-weight: 700;
|
||||
background: var(--brand);
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.button--ghost {
|
||||
color: var(--brand);
|
||||
background: #fff;
|
||||
border: 1px solid var(--brand);
|
||||
}
|
||||
|
||||
@media (max-width: 620px) {
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -11,10 +11,29 @@ export interface RoleSummary {
|
||||
export interface UserTableRow {
|
||||
id: string
|
||||
name: string
|
||||
account?: string
|
||||
employeeNo: string
|
||||
phone?: string
|
||||
email?: string
|
||||
campus: string
|
||||
campusId?: string | null
|
||||
department: string
|
||||
departmentId?: string | null
|
||||
role: string
|
||||
dataScope: string
|
||||
status: UserStatus
|
||||
}
|
||||
|
||||
export interface UserEditorForm {
|
||||
id?: string
|
||||
username: string
|
||||
password: string
|
||||
displayName: string
|
||||
employeeNo: string
|
||||
phone: string
|
||||
email: string
|
||||
campusId: string
|
||||
departmentId: string
|
||||
status: UserStatus
|
||||
dataScope: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user