|
|
@@ -73,32 +73,12 @@ const getRoleName = (role) => {
|
|
|
return UserRole[role] || role
|
|
|
}
|
|
|
|
|
|
-// 用户角色选项 - 根据当前用户角色限制
|
|
|
+// 用户角色选项 - 只有 ADMIN 可以看到,所以只返回所有角色
|
|
|
const roleOptions = computed(() => {
|
|
|
- // ADMIN 可以创建所有角色
|
|
|
- if (isAdmin.value) {
|
|
|
- return ['admin', 'manager', 'user'].map((role) => ({
|
|
|
- value: role,
|
|
|
- label: UserRole[role]
|
|
|
- }))
|
|
|
- }
|
|
|
-
|
|
|
- // MANAGER 在创建模式下只能创建 user 角色
|
|
|
- // 在编辑模式下,显示所有角色(但禁用),以便查看当前角色
|
|
|
- if (isEditMode.value) {
|
|
|
- return ['admin', 'manager', 'user'].map((role) => ({
|
|
|
- value: role,
|
|
|
- label: UserRole[role]
|
|
|
- }))
|
|
|
- }
|
|
|
-
|
|
|
- // MANAGER 创建新用户时只能选择 user
|
|
|
- return [
|
|
|
- {
|
|
|
- value: 'user',
|
|
|
- label: UserRole.user
|
|
|
- }
|
|
|
- ]
|
|
|
+ return ['admin', 'manager', 'user'].map((role) => ({
|
|
|
+ value: role,
|
|
|
+ label: UserRole[role]
|
|
|
+ }))
|
|
|
})
|
|
|
|
|
|
// 用户表单相关
|
|
|
@@ -128,8 +108,8 @@ const userFormResolver = computed(() => {
|
|
|
.refine((val) => !userForm.value.password || val === userForm.value.password, { message: '密码不一致' })
|
|
|
}
|
|
|
|
|
|
- // ADMIN 可以修改角色,MANAGER 不能修改角色
|
|
|
- if (isAdmin.value || !isEditMode.value) {
|
|
|
+ // 只有 ADMIN 可以设置角色
|
|
|
+ if (isAdmin.value) {
|
|
|
baseSchema.role = z.string().min(1, { message: '请选择角色' })
|
|
|
}
|
|
|
|
|
|
@@ -170,11 +150,11 @@ const saveUser = async ({ valid, values }) => {
|
|
|
name: values.name
|
|
|
}
|
|
|
|
|
|
- // 只有 ADMIN 可以设置/修改角色,或者创建新用户时可以设置角色
|
|
|
- if (isAdmin.value || !isEditMode.value) {
|
|
|
+ // 只有 ADMIN 可以设置/修改角色
|
|
|
+ if (isAdmin.value) {
|
|
|
submitData.role = values.role
|
|
|
}
|
|
|
- // MANAGER 在编辑模式下不能修改角色,不传 role 字段
|
|
|
+ // 非 ADMIN 用户不传 role 字段,后端会根据权限自动设置(MANAGER 创建的用户默认为 user)
|
|
|
|
|
|
if (values.password) {
|
|
|
submitData.password = values.password
|
|
|
@@ -241,27 +221,15 @@ onMounted(() => {
|
|
|
<InputIcon>
|
|
|
<i class="pi pi-hashtag" />
|
|
|
</InputIcon>
|
|
|
- <InputNumber
|
|
|
- v-model="searchId"
|
|
|
- placeholder="搜索ID"
|
|
|
- :useGrouping="false"
|
|
|
- fluid
|
|
|
- size="small"
|
|
|
- @keyup.enter="fetchData"
|
|
|
- />
|
|
|
+ <InputNumber v-model="searchId" placeholder="搜索ID" :useGrouping="false" fluid size="small"
|
|
|
+ @keyup.enter="fetchData" />
|
|
|
</IconField>
|
|
|
</div>
|
|
|
|
|
|
<!-- 搜索按钮 -->
|
|
|
<Button icon="pi pi-search" @click="fetchData" label="搜索" size="small" />
|
|
|
- <Button
|
|
|
- v-if="search || searchId"
|
|
|
- icon="pi pi-times"
|
|
|
- @click="clearSearch"
|
|
|
- label="清除"
|
|
|
- severity="secondary"
|
|
|
- size="small"
|
|
|
- />
|
|
|
+ <Button v-if="search || searchId" icon="pi pi-times" @click="clearSearch" label="清除" severity="secondary"
|
|
|
+ size="small" />
|
|
|
|
|
|
<!-- 左侧按钮组 -->
|
|
|
<div class="flex items-center gap-2 flex-nowrap">
|
|
|
@@ -271,31 +239,18 @@ onMounted(() => {
|
|
|
|
|
|
<!-- 右侧按钮:新增用户 -->
|
|
|
<div class="ml-auto">
|
|
|
- <Button
|
|
|
- icon="pi pi-plus"
|
|
|
- @click="openNewUserDialog"
|
|
|
- label="新增用户"
|
|
|
- severity="success"
|
|
|
- size="small"
|
|
|
- />
|
|
|
+ <Button icon="pi pi-plus" @click="openNewUserDialog" label="新增用户" severity="success" size="small" />
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 数据表格 -->
|
|
|
- <DataTable
|
|
|
- :value="tableData.content"
|
|
|
- :paginator="true"
|
|
|
+ <DataTable :value="tableData.content" :paginator="true"
|
|
|
paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown JumpToPageInput"
|
|
|
- currentPageReportTemplate="{totalRecords} 条记录 "
|
|
|
- :rows="tableData.metadata.size"
|
|
|
- :rowsPerPageOptions="[10, 20, 50, 100]"
|
|
|
- :totalRecords="tableData.metadata.total"
|
|
|
- @page="handlePageChange"
|
|
|
- lazy
|
|
|
- scrollable
|
|
|
- >
|
|
|
- <Column field="id" header="ID"></Column>
|
|
|
+ currentPageReportTemplate="{totalRecords} 条记录 " :rows="tableData.metadata.size"
|
|
|
+ :rowsPerPageOptions="[10, 20, 50, 100]" :totalRecords="tableData.metadata.total" @page="handlePageChange" lazy
|
|
|
+ scrollable>
|
|
|
+ <Column v-if="isAdmin" field="id" header="ID"></Column>
|
|
|
<Column field="name" header="用户名"></Column>
|
|
|
<Column field="role" header="角色">
|
|
|
<template #body="slotProps">
|
|
|
@@ -311,27 +266,15 @@ onMounted(() => {
|
|
|
</Column>
|
|
|
<Column header="操作" style="min-width: 150px">
|
|
|
<template #body="slotProps">
|
|
|
- <Button
|
|
|
- icon="pi pi-pencil"
|
|
|
- severity="info"
|
|
|
- size="small"
|
|
|
- text
|
|
|
- rounded
|
|
|
- aria-label="编辑"
|
|
|
- @click="openEditUserDialog(slotProps.data)"
|
|
|
- />
|
|
|
+ <Button icon="pi pi-pencil" severity="info" size="small" text rounded aria-label="编辑"
|
|
|
+ @click="openEditUserDialog(slotProps.data)" />
|
|
|
</template>
|
|
|
</Column>
|
|
|
</DataTable>
|
|
|
|
|
|
<!-- 用户表单对话框 -->
|
|
|
- <Dialog
|
|
|
- v-model:visible="userDialog"
|
|
|
- :modal="true"
|
|
|
- :header="isEditMode ? '编辑用户' : '创建用户'"
|
|
|
- :style="{ width: '450px' }"
|
|
|
- position="center"
|
|
|
- >
|
|
|
+ <Dialog v-model:visible="userDialog" :modal="true" :header="isEditMode ? '编辑用户' : '创建用户'"
|
|
|
+ :style="{ width: '450px' }" position="center">
|
|
|
<Form v-slot="$form" :resolver="userFormResolver" :initialValues="userForm" @submit="saveUser" class="p-fluid">
|
|
|
<div class="field mt-4">
|
|
|
<FloatLabel variant="on">
|
|
|
@@ -350,15 +293,8 @@ onMounted(() => {
|
|
|
<FloatLabel variant="on">
|
|
|
<IconField>
|
|
|
<InputIcon class="pi pi-lock" />
|
|
|
- <Password
|
|
|
- id="password"
|
|
|
- name="password"
|
|
|
- v-model="userForm.password"
|
|
|
- toggleMask
|
|
|
- :feedback="false"
|
|
|
- autocomplete="off"
|
|
|
- fluid
|
|
|
- />
|
|
|
+ <Password id="password" name="password" v-model="userForm.password" toggleMask :feedback="false"
|
|
|
+ autocomplete="off" fluid />
|
|
|
</IconField>
|
|
|
<label for="password">{{ isEditMode ? '密码 (可选)' : '密码' }}</label>
|
|
|
</FloatLabel>
|
|
|
@@ -372,15 +308,8 @@ onMounted(() => {
|
|
|
<FloatLabel variant="on">
|
|
|
<IconField>
|
|
|
<InputIcon class="pi pi-lock" />
|
|
|
- <Password
|
|
|
- id="confirmPassword"
|
|
|
- name="confirmPassword"
|
|
|
- v-model="userForm.confirmPassword"
|
|
|
- toggleMask
|
|
|
- :feedback="false"
|
|
|
- fluid
|
|
|
- autocomplete="off"
|
|
|
- />
|
|
|
+ <Password id="confirmPassword" name="confirmPassword" v-model="userForm.confirmPassword" toggleMask
|
|
|
+ :feedback="false" fluid autocomplete="off" />
|
|
|
</IconField>
|
|
|
<label for="confirmPassword">确认密码</label>
|
|
|
</FloatLabel>
|
|
|
@@ -390,36 +319,20 @@ onMounted(() => {
|
|
|
<div v-if="isEditMode" class="text-sm text-gray-500 mt-1 ml-1">* 留空则不修改</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="field mt-4">
|
|
|
+ <div v-if="isAdmin" class="field mt-4">
|
|
|
<FloatLabel variant="on">
|
|
|
- <Select
|
|
|
- id="role"
|
|
|
- name="role"
|
|
|
- v-model="userForm.role"
|
|
|
- :options="roleOptions"
|
|
|
- optionLabel="label"
|
|
|
- optionValue="value"
|
|
|
- :disabled="isEditMode && !isAdmin"
|
|
|
- fluid
|
|
|
- />
|
|
|
+ <Select id="role" name="role" v-model="userForm.role" :options="roleOptions" optionLabel="label"
|
|
|
+ optionValue="value" fluid />
|
|
|
<label for="role">角色</label>
|
|
|
</FloatLabel>
|
|
|
<Message v-if="$form.role?.invalid" severity="error" size="small" variant="simple">
|
|
|
{{ $form.role.error?.message }}
|
|
|
</Message>
|
|
|
- <div v-if="isEditMode && !isAdmin" class="text-sm text-gray-500 mt-1 ml-1">
|
|
|
- * 您无权修改用户角色
|
|
|
- </div>
|
|
|
</div>
|
|
|
|
|
|
<div class="flex justify-end gap-2 mt-4">
|
|
|
- <Button
|
|
|
- label="取消"
|
|
|
- severity="secondary"
|
|
|
- type="button"
|
|
|
- @click="userDialog = false"
|
|
|
- :disabled="userFormLoading"
|
|
|
- />
|
|
|
+ <Button label="取消" severity="secondary" type="button" @click="userDialog = false"
|
|
|
+ :disabled="userFormLoading" />
|
|
|
<Button label="保存" type="submit" :loading="userFormLoading" />
|
|
|
</div>
|
|
|
</Form>
|