diff --git a/frontend/src/components/UserFormModal.vue b/frontend/src/components/UserFormModal.vue index 96aed38..cea4149 100644 --- a/frontend/src/components/UserFormModal.vue +++ b/frontend/src/components/UserFormModal.vue @@ -14,13 +14,20 @@ const emit = defineEmits<{ success: [] }>() +interface RoleOption { + id: number + name: string +} + const formRef = ref() const submitting = ref(false) +const roles = ref([]) const formState = reactive({ username: '', password: '', email: '', status: 1 as number, + role_id: undefined as number | undefined, }) const rules = computed>(() => { @@ -46,20 +53,33 @@ const rules = computed>(() => { const title = computed(() => (props.mode === 'create' ? '新建用户' : '编辑用户')) +async function fetchRoles() { + try { + const data = await api.get('/api/v1/roles') + roles.value = data + } catch { + roles.value = [] + message.warning('获取角色列表失败,角色选择不可用') + } +} + watch( () => props.open, (val) => { if (val) { + fetchRoles() if (props.mode === 'edit' && props.userData) { formState.username = props.userData.username formState.email = props.userData.email formState.status = props.userData.status + formState.role_id = props.userData.role_id formState.password = '' } else { formState.username = '' formState.password = '' formState.email = '' formState.status = 1 + formState.role_id = undefined } // 清除上一次的校验状态 nextTick(() => formRef.value?.clearValidate()) @@ -76,20 +96,36 @@ async function handleSubmit() { submitting.value = true try { + let userId: number if (props.mode === 'create') { - await api.post('/api/v1/users', { + const created = await api.post<{ id: number }>('/api/v1/users', { username: formState.username, password: formState.password, email: formState.email, status: formState.status, }) + userId = created.id } else { await api.put(`/api/v1/users/${props.userData!.id}`, { username: formState.username, email: formState.email, status: formState.status, }) + userId = props.userData!.id } + + // 角色分配(独立 try-catch,用户信息保存不受影响) + const needsRoleUpdate = props.mode === 'create' + ? !!formState.role_id + : formState.role_id && formState.role_id !== props.userData!.role_id + if (needsRoleUpdate) { + try { + await api.put(`/api/v1/users/${userId}/role`, { role_id: formState.role_id }) + } catch { + message.warning('用户已保存,但角色分配失败,请稍后在用户列表中重试') + } + } + message.success('操作成功') emit('update:open', false) emit('success') @@ -130,6 +166,13 @@ function handleCancel() { 禁用 + + + + {{ role.name }} + + + diff --git a/frontend/src/components/layouts/MainLayout.vue b/frontend/src/components/layouts/MainLayout.vue index 3f269f4..e39014c 100644 --- a/frontend/src/components/layouts/MainLayout.vue +++ b/frontend/src/components/layouts/MainLayout.vue @@ -21,6 +21,7 @@ interface MenuItem { key: string icon: Component label: string + adminOnly?: boolean children?: MenuItem[] } @@ -54,7 +55,7 @@ watch(() => route.path, initOpenKeys) // 导航菜单配置 const menuItems: MenuItem[] = [ { key: '/', icon: DashboardOutlined, label: '首页' }, - { key: '/users', icon: UserOutlined, label: '用户管理' }, + { key: '/users', icon: UserOutlined, label: '用户管理', adminOnly: true }, { key: '/products', icon: ShoppingOutlined, label: '产品管理' }, { key: 'orders-group', @@ -74,14 +75,19 @@ const menuItems: MenuItem[] = [ { key: '/refund-items', icon: UnorderedListOutlined, label: '退款子项' }, ], }, - { key: '/mq-status', icon: MonitorOutlined, label: '队列监控' }, + { key: '/mq-status', icon: MonitorOutlined, label: '队列监控', adminOnly: true }, ] +const filteredMenuItems = computed(() => + menuItems.filter((item) => !item.adminOnly || userStore.isAdmin), +) + const username = computed(() => userStore.username || 'admin') -const handleMenuClick = ({ key }: { key: string }) => { - if (key.startsWith('/')) { - router.push(key) +const handleMenuClick = ({ key }: { key: string | number }) => { + const path = String(key) + if (path.startsWith('/')) { + router.push(path) } } @@ -168,7 +174,7 @@ const breadcrumbItems = computed(() => { theme="dark" @click="handleMenuClick" > -