feat(admin): Add user management and upgrade to module permission system
Features - User Management (Phase 4.1): - Database: Add user_modules table for fine-grained module permissions - Database: Add 4 user permissions (view/create/edit/delete) to role_permissions - Backend: UserService (780 lines) - CRUD with tenant isolation - Backend: UserController + UserRoutes (648 lines) - 13 API endpoints - Backend: Batch import users from Excel - Frontend: UserListPage (412 lines) - list/filter/search/pagination - Frontend: UserFormPage (341 lines) - create/edit with module config - Frontend: UserDetailPage (393 lines) - details/tenant/module management - Frontend: 3 modal components (592 lines) - import/assign/configure - API: GET/POST/PUT/DELETE /api/admin/users/* endpoints Architecture Upgrade - Module Permission System: - Backend: Add getUserModules() method in auth.service - Backend: Login API returns modules array in user object - Frontend: AuthContext adds hasModule() method - Frontend: Navigation filters modules based on user.modules - Frontend: RouteGuard checks requiredModule instead of requiredVersion - Frontend: Remove deprecated version-based permission system - UX: Only show accessible modules in navigation (clean UI) - UX: Smart redirect after login (avoid 403 for regular users) Fixes: - Fix UTF-8 encoding corruption in ~100 docs files - Fix pageSize type conversion in userService (String to Number) - Fix authUser undefined error in TopNavigation - Fix login redirect logic with role-based access check - Update Git commit guidelines v1.2 with UTF-8 safety rules Database Changes: - CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled) - ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code) - INSERT 4 permissions + role assignments - UPDATE PUBLIC tenant with 8 module subscriptions Technical: - Backend: 5 new files (~2400 lines) - Frontend: 10 new files (~2500 lines) - Docs: 1 development record + 2 status updates + 1 guideline update - Total: ~4900 lines of code Status: User management 100% complete, module permission system operational
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 模块权限配置弹窗
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
Checkbox,
|
||||
Row,
|
||||
Col,
|
||||
message,
|
||||
Typography,
|
||||
Spin,
|
||||
Alert,
|
||||
} from 'antd';
|
||||
import * as userApi from '../api/userApi';
|
||||
import type { TenantMembership, ModuleOption } from '../types/user';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModulePermissionModalProps {
|
||||
visible: boolean;
|
||||
userId: string;
|
||||
membership: TenantMembership;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
const ModulePermissionModal: React.FC<ModulePermissionModalProps> = ({
|
||||
visible,
|
||||
userId,
|
||||
membership,
|
||||
onClose,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [moduleOptions, setModuleOptions] = useState<ModuleOption[]>([]);
|
||||
const [selectedModules, setSelectedModules] = useState<string[]>([]);
|
||||
|
||||
// 加载模块选项
|
||||
useEffect(() => {
|
||||
if (visible && membership) {
|
||||
setLoading(true);
|
||||
userApi.getModuleOptions(membership.tenantId)
|
||||
.then((options) => {
|
||||
setModuleOptions(options);
|
||||
// 设置当前已启用的模块
|
||||
const enabled = membership.allowedModules
|
||||
.filter((m) => m.isEnabled)
|
||||
.map((m) => m.code);
|
||||
setSelectedModules(enabled);
|
||||
})
|
||||
.catch(console.error)
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
}, [visible, membership]);
|
||||
|
||||
// 提交
|
||||
const handleSubmit = async () => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await userApi.updateUserModules(userId, {
|
||||
tenantId: membership.tenantId,
|
||||
modules: selectedModules,
|
||||
});
|
||||
message.success('模块权限更新成功');
|
||||
onSuccess();
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.message || '更新失败');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 可用模块(租户已订阅的)
|
||||
const subscribedModules = moduleOptions.filter((m) => m.isSubscribed);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`配置模块权限 - ${membership.tenantName}`}
|
||||
open={visible}
|
||||
onCancel={onClose}
|
||||
onOk={handleSubmit}
|
||||
confirmLoading={submitting}
|
||||
destroyOnClose
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<Alert
|
||||
message="模块权限说明"
|
||||
description="选择用户在该租户内可以访问的模块。取消所有选择将默认继承租户的全部模块权限。"
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
|
||||
{subscribedModules.length > 0 ? (
|
||||
<Checkbox.Group
|
||||
value={selectedModules}
|
||||
onChange={(values) => setSelectedModules(values as string[])}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Row gutter={[16, 16]}>
|
||||
{subscribedModules.map((module) => (
|
||||
<Col span={12} key={module.code}>
|
||||
<Checkbox value={module.code}>{module.name}</Checkbox>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
) : (
|
||||
<Text type="secondary">该租户暂无可用模块</Text>
|
||||
)}
|
||||
</Spin>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModulePermissionModal;
|
||||
|
||||
Reference in New Issue
Block a user