|
|
@@ -1,6 +1,7 @@
|
|
|
<script setup>
|
|
|
import { UserRole } from '@/enums'
|
|
|
import { createUserApi, listUsersApi, updateUserApi } from '@/services/api'
|
|
|
+import { useUserStore } from '@/stores/user'
|
|
|
import { Form } from '@primevue/forms'
|
|
|
import { zodResolver } from '@primevue/forms/resolvers/zod'
|
|
|
import { useDateFormat } from '@vueuse/core'
|
|
|
@@ -13,6 +14,7 @@ import FloatLabel from 'primevue/floatlabel'
|
|
|
import IconField from 'primevue/iconfield'
|
|
|
import InputIcon from 'primevue/inputicon'
|
|
|
import InputText from 'primevue/inputtext'
|
|
|
+import InputNumber from 'primevue/inputnumber'
|
|
|
import Message from 'primevue/message'
|
|
|
import Password from 'primevue/password'
|
|
|
import { useToast } from 'primevue/usetoast'
|
|
|
@@ -20,6 +22,9 @@ import { computed, onMounted, ref } from 'vue'
|
|
|
import { z } from 'zod'
|
|
|
|
|
|
const toast = useToast()
|
|
|
+const userStore = useUserStore()
|
|
|
+const currentUserRole = computed(() => userStore.userInfo?.role || '')
|
|
|
+
|
|
|
const tableData = ref({
|
|
|
content: [],
|
|
|
metadata: {
|
|
|
@@ -29,11 +34,29 @@ const tableData = ref({
|
|
|
}
|
|
|
})
|
|
|
const search = ref('')
|
|
|
+const searchId = ref(null)
|
|
|
+const isAdmin = computed(() => currentUserRole.value === 'admin')
|
|
|
+
|
|
|
const fetchData = async () => {
|
|
|
- const response = await listUsersApi(tableData.value.metadata.page, tableData.value.metadata.size)
|
|
|
+ const id = isAdmin.value && searchId.value ? Number(searchId.value) : undefined
|
|
|
+ const name = search.value.trim() || undefined
|
|
|
+ const response = await listUsersApi(
|
|
|
+ tableData.value.metadata.page,
|
|
|
+ tableData.value.metadata.size,
|
|
|
+ id,
|
|
|
+ name
|
|
|
+ )
|
|
|
tableData.value = response
|
|
|
}
|
|
|
|
|
|
+// 清除搜索条件
|
|
|
+const clearSearch = () => {
|
|
|
+ search.value = ''
|
|
|
+ searchId.value = null
|
|
|
+ tableData.value.metadata.page = 0
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
const handlePageChange = (event) => {
|
|
|
console.log('handlePageChange', event)
|
|
|
tableData.value.metadata.page = event.page
|
|
|
@@ -50,13 +73,32 @@ const getRoleName = (role) => {
|
|
|
return UserRole[role] || role
|
|
|
}
|
|
|
|
|
|
-// 用户角色选项
|
|
|
+// 用户角色选项 - 根据当前用户角色限制
|
|
|
const roleOptions = computed(() => {
|
|
|
- const allowedRoles = ['user', 'admin', 'channel', 'operator']
|
|
|
- return allowedRoles.map((role) => ({
|
|
|
- value: role,
|
|
|
- label: UserRole[role]
|
|
|
- }))
|
|
|
+ // 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
|
|
|
+ }
|
|
|
+ ]
|
|
|
})
|
|
|
|
|
|
// 用户表单相关
|
|
|
@@ -77,16 +119,21 @@ const userFormResolver = computed(() => {
|
|
|
? z.string().optional() // 更新时密码可选
|
|
|
: z.string().min(8, { message: '密码至少8位' })
|
|
|
|
|
|
- return zodResolver(
|
|
|
- z.object({
|
|
|
- name: z.string().min(1, { message: '用户名不能为空' }),
|
|
|
- password: passwordRules,
|
|
|
- confirmPassword: z
|
|
|
- .string()
|
|
|
- .refine((val) => !userForm.value.password || val === userForm.value.password, { message: '密码不一致' }),
|
|
|
- role: z.string().min(1, { message: '请选择角色' })
|
|
|
- })
|
|
|
- )
|
|
|
+ // 构建基础验证对象
|
|
|
+ const baseSchema = {
|
|
|
+ name: z.string().min(1, { message: '用户名不能为空' }),
|
|
|
+ password: passwordRules,
|
|
|
+ confirmPassword: z
|
|
|
+ .string()
|
|
|
+ .refine((val) => !userForm.value.password || val === userForm.value.password, { message: '密码不一致' })
|
|
|
+ }
|
|
|
+
|
|
|
+ // ADMIN 可以修改角色,MANAGER 不能修改角色
|
|
|
+ if (isAdmin.value || !isEditMode.value) {
|
|
|
+ baseSchema.role = z.string().min(1, { message: '请选择角色' })
|
|
|
+ }
|
|
|
+
|
|
|
+ return zodResolver(z.object(baseSchema))
|
|
|
})
|
|
|
|
|
|
const openNewUserDialog = () => {
|
|
|
@@ -120,9 +167,14 @@ const saveUser = async ({ valid, values }) => {
|
|
|
try {
|
|
|
// 构建提交数据,过滤掉不需要的confirmPassword
|
|
|
const submitData = {
|
|
|
- name: values.name,
|
|
|
- role: values.role
|
|
|
+ name: values.name
|
|
|
+ }
|
|
|
+
|
|
|
+ // 只有 ADMIN 可以设置/修改角色,或者创建新用户时可以设置角色
|
|
|
+ if (isAdmin.value || !isEditMode.value) {
|
|
|
+ submitData.role = values.role
|
|
|
}
|
|
|
+ // MANAGER 在编辑模式下不能修改角色,不传 role 字段
|
|
|
|
|
|
if (values.password) {
|
|
|
submitData.password = values.password
|
|
|
@@ -173,15 +225,44 @@ onMounted(() => {
|
|
|
<!-- 操作栏 -->
|
|
|
<div class="mb-4 overflow-x-auto py-2">
|
|
|
<div class="flex items-center gap-2 flex-nowrap min-w-[760px]">
|
|
|
+ <!-- 搜索框:用户名 -->
|
|
|
<div class="field w-36">
|
|
|
<IconField>
|
|
|
<InputIcon>
|
|
|
<i class="pi pi-search" />
|
|
|
</InputIcon>
|
|
|
- <InputText v-model="search" placeholder="搜索" fluid />
|
|
|
+ <InputText v-model="search" placeholder="搜索用户名" fluid size="small" @keyup.enter="fetchData" />
|
|
|
</IconField>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- 搜索框:ID(仅 ADMIN 显示) -->
|
|
|
+ <div v-if="isAdmin" class="field w-36">
|
|
|
+ <IconField>
|
|
|
+ <InputIcon>
|
|
|
+ <i class="pi pi-hashtag" />
|
|
|
+ </InputIcon>
|
|
|
+ <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"
|
|
|
+ />
|
|
|
+
|
|
|
<!-- 左侧按钮组 -->
|
|
|
<div class="flex items-center gap-2 flex-nowrap">
|
|
|
<span class="w-px h-6 bg-[var(--p-content-border-color)] mx-1"></span>
|
|
|
@@ -318,6 +399,7 @@ onMounted(() => {
|
|
|
:options="roleOptions"
|
|
|
optionLabel="label"
|
|
|
optionValue="value"
|
|
|
+ :disabled="isEditMode && !isAdmin"
|
|
|
fluid
|
|
|
/>
|
|
|
<label for="role">角色</label>
|
|
|
@@ -325,6 +407,9 @@ onMounted(() => {
|
|
|
<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">
|