feat: 1. 本地化思源黑体的字体包并优先使用。
2. 添加权限映射表和全局查看权限的hook,便于路由控制不同权限按钮显示/隐藏。 3. 删除评查点分组的部分旧api方法。 4. 对接评查点分组接口,文档类型接口, 提示词管理接口, 入口模块管理的接口。 5. 优化角色权限管理的接口,完善不用地区的访问权限认证。 6. 优化主页交叉评查和设置的入口样式和布局。 7. 优化评查点分组,评查规则的功能权限校验。
This commit is contained in:
@@ -529,9 +529,11 @@ interface AssignUserModalProps {
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
role: RoleInfo | null;
|
||||
isCityAdmin?: boolean;
|
||||
currentUserArea?: string;
|
||||
}
|
||||
|
||||
function AssignUserModal({ isOpen, onClose, onSuccess, role }: AssignUserModalProps) {
|
||||
function AssignUserModal({ isOpen, onClose, onSuccess, role, isCityAdmin, currentUserArea }: AssignUserModalProps) {
|
||||
const [allUsers, setAllUsers] = useState<UserInfo[]>([]);
|
||||
const [selectedUserIds, setSelectedUserIds] = useState<number[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
@@ -552,12 +554,24 @@ function AssignUserModal({ isOpen, onClose, onSuccess, role }: AssignUserModalPr
|
||||
setLoadingUsers(true);
|
||||
try {
|
||||
const users = await getAllUsers();
|
||||
setAllUsers(users);
|
||||
|
||||
// v3.3: 市级管理员只能看到同地区的用户(使用 area 字段)
|
||||
let filteredUsers = users;
|
||||
if (isCityAdmin && currentUserArea) {
|
||||
filteredUsers = users.filter(user => user.area === currentUserArea);
|
||||
console.log('🔒 [AssignUserModal v3.3] 市级管理员用户过滤:', {
|
||||
当前地区: currentUserArea,
|
||||
原始用户数: users.length,
|
||||
过滤后用户数: filteredUsers.length
|
||||
});
|
||||
}
|
||||
|
||||
setAllUsers(filteredUsers);
|
||||
|
||||
// 批量获取每个用户的角色
|
||||
const rolesMap = new Map<number, RoleInfo[]>();
|
||||
await Promise.all(
|
||||
users.map(async (user) => {
|
||||
filteredUsers.map(async (user) => {
|
||||
const roles = await getUserRoles(user.id);
|
||||
rolesMap.set(user.id, roles);
|
||||
})
|
||||
@@ -678,6 +692,14 @@ function AssignUserModal({ isOpen, onClose, onSuccess, role }: AssignUserModalPr
|
||||
}
|
||||
>
|
||||
<div className="assign-user-modal">
|
||||
{/* v3.3: 市级管理员地区过滤提示 */}
|
||||
{isCityAdmin && currentUserArea && (
|
||||
<div className="form-notice info" style={{ marginBottom: '12px' }}>
|
||||
<i className="ri-information-line"></i>
|
||||
<span>市级管理员权限:仅显示 {currentUserArea} 地区的用户</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 搜索框 */}
|
||||
<div className="search-box">
|
||||
<i className="ri-search-line"></i>
|
||||
@@ -784,6 +806,12 @@ export default function RolePermissions() {
|
||||
const [activeTab, setActiveTab] = useState<'permissions' | 'users'>('permissions');
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// v3.3: 检查当前用户角色和地区
|
||||
const [currentUserRole, setCurrentUserRole] = useState('');
|
||||
const [currentUserArea, setCurrentUserArea] = useState('');
|
||||
const [isProvincialAdmin, setIsProvincialAdmin] = useState(false);
|
||||
const [isCityAdmin, setIsCityAdmin] = useState(false);
|
||||
|
||||
// 模态框状态
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
@@ -800,6 +828,13 @@ export default function RolePermissions() {
|
||||
} | null>(null);
|
||||
const [deleteCountdown, setDeleteCountdown] = useState(3);
|
||||
|
||||
// 权限警告Modal状态
|
||||
const [showPermissionWarning, setShowPermissionWarning] = useState(false);
|
||||
const [pendingRouteChange, setPendingRouteChange] = useState<{
|
||||
routeId: number;
|
||||
checked: boolean;
|
||||
} | null>(null);
|
||||
|
||||
// 路由权限相关状态
|
||||
const [selectedRouteIds, setSelectedRouteIds] = useState<number[]>([]);
|
||||
const [roleUsers, setRoleUsers] = useState<UserInfo[]>([]);
|
||||
@@ -829,19 +864,62 @@ export default function RolePermissions() {
|
||||
const loadData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// v3.3: 检查当前用户角色和地区
|
||||
if (typeof window !== 'undefined') {
|
||||
const userInfoStr = localStorage.getItem('user_info');
|
||||
if (userInfoStr) {
|
||||
try {
|
||||
const userInfo = JSON.parse(userInfoStr);
|
||||
const userRole = userInfo.user_role || '';
|
||||
const userArea = userInfo.area || ''; // v3.3: 使用 area 字段进行地区隔离
|
||||
|
||||
setCurrentUserRole(userRole);
|
||||
setCurrentUserArea(userArea);
|
||||
setIsProvincialAdmin(userRole === 'provincial_admin');
|
||||
setIsCityAdmin(userRole === 'admin');
|
||||
|
||||
console.log('🔑 [RolePermissions v3.3] 当前用户信息:', {
|
||||
role: userRole,
|
||||
area: userArea,
|
||||
isProvincialAdmin: userRole === 'provincial_admin',
|
||||
isCityAdmin: userRole === 'admin'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('❌ [RolePermissions] 解析用户信息失败:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [rolesData, routesData, usersData] = await Promise.all([
|
||||
getRoles(),
|
||||
getRoutes(),
|
||||
getAllUsers()
|
||||
]);
|
||||
|
||||
setRoles(rolesData);
|
||||
setRoutes(routesData);
|
||||
setUsers(usersData);
|
||||
// v3.3: 角色列表对所有人可见(不过滤)
|
||||
const filteredRoles = rolesData;
|
||||
|
||||
// 默认选中第一个角色
|
||||
if (rolesData.length > 0) {
|
||||
handleSelectRole(rolesData[0]);
|
||||
// v3.3: 根据用户地区过滤可见的用户列表
|
||||
let filteredUsers = usersData;
|
||||
if (isCityAdmin && currentUserArea) {
|
||||
// 市级管理员只能看到同地区的用户(使用 area 字段)
|
||||
filteredUsers = usersData.filter(user =>
|
||||
user.area === currentUserArea
|
||||
);
|
||||
console.log('🔒 [RolePermissions v3.3] 市级管理员用户过滤:', {
|
||||
当前地区: currentUserArea,
|
||||
原始用户数: usersData.length,
|
||||
过滤后用户数: filteredUsers.length
|
||||
});
|
||||
}
|
||||
|
||||
setRoles(filteredRoles);
|
||||
setRoutes(routesData);
|
||||
setUsers(filteredUsers);
|
||||
|
||||
// 默认选中第一个角色(使用过滤后的列表)
|
||||
if (filteredRoles.length > 0) {
|
||||
handleSelectRole(filteredRoles[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载数据失败:", error);
|
||||
@@ -888,6 +966,20 @@ export default function RolePermissions() {
|
||||
setRoleUsers(users);
|
||||
};
|
||||
|
||||
// 递归查找路由
|
||||
const findRouteById = (routes: RouteInfo[], routeId: number): RouteInfo | null => {
|
||||
for (const route of routes) {
|
||||
if (route.id === routeId) {
|
||||
return route;
|
||||
}
|
||||
if (route.children && route.children.length > 0) {
|
||||
const found = findRouteById(route.children, routeId);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 递归获取所有路由ID(包括子路由)
|
||||
const getAllRouteIds = (routes: RouteInfo[]): number[] => {
|
||||
let ids: number[] = [];
|
||||
@@ -900,8 +992,34 @@ export default function RolePermissions() {
|
||||
return ids;
|
||||
};
|
||||
|
||||
// 递归检查路由树中是否包含指定路径的路由
|
||||
const containsRoutePath = (routes: RouteInfo[], targetPath: string): boolean => {
|
||||
for (const route of routes) {
|
||||
if (route.route_path === targetPath) {
|
||||
return true;
|
||||
}
|
||||
if (route.children && route.children.length > 0) {
|
||||
if (containsRoutePath(route.children, targetPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 切换路由权限
|
||||
const handleToggleRoute = (routeId: number, checked: boolean) => {
|
||||
// 检查是否正在取消勾选 /role-permissions 路由
|
||||
if (!checked) {
|
||||
const route = findRouteById(routes, routeId);
|
||||
if (route && route.route_path === '/role-permissions') {
|
||||
// 显示警告模态框
|
||||
setPendingRouteChange({ routeId, checked });
|
||||
setShowPermissionWarning(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
setSelectedRouteIds([...selectedRouteIds, routeId]);
|
||||
} else {
|
||||
@@ -911,6 +1029,20 @@ export default function RolePermissions() {
|
||||
|
||||
// 切换父路由(包括所有子路由)
|
||||
const handleToggleParentRoute = (route: RouteInfo, checked: boolean) => {
|
||||
// 检查是否正在取消勾选包含 /role-permissions 的父路由
|
||||
if (!checked) {
|
||||
const allRoutes = route.children ? [route, ...route.children] : [route];
|
||||
const hasRolePermissionsRoute = allRoutes.some(r => r.route_path === '/role-permissions') ||
|
||||
(route.children && containsRoutePath(route.children, '/role-permissions'));
|
||||
|
||||
if (route.route_path === '/role-permissions' || hasRolePermissionsRoute) {
|
||||
// 显示警告模态框,传递 route 对象表示是父路由操作
|
||||
setPendingRouteChange({ routeId: route.id, checked });
|
||||
setShowPermissionWarning(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const childIds = route.children ? getAllRouteIds(route.children) : [];
|
||||
const allIds = [route.id, ...childIds];
|
||||
|
||||
@@ -926,6 +1058,32 @@ export default function RolePermissions() {
|
||||
}
|
||||
};
|
||||
|
||||
// 确认取消角色权限管理路由
|
||||
const confirmRemovePermissionRoute = () => {
|
||||
if (!pendingRouteChange) return;
|
||||
|
||||
const { routeId, checked } = pendingRouteChange;
|
||||
const route = findRouteById(routes, routeId);
|
||||
|
||||
if (route) {
|
||||
// 如果是父路由,取消所有子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
const childIds = getAllRouteIds(route.children);
|
||||
const allIds = [route.id, ...childIds];
|
||||
setSelectedRouteIds(selectedRouteIds.filter(id => !allIds.includes(id)));
|
||||
} else {
|
||||
// 单个路由
|
||||
setSelectedRouteIds(selectedRouteIds.filter(id => id !== routeId));
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭模态框并重置状态
|
||||
setShowPermissionWarning(false);
|
||||
setPendingRouteChange(null);
|
||||
|
||||
toastService.warning('已取消角色权限管理路由,请谨慎保存权限配置');
|
||||
};
|
||||
|
||||
// v3.0: 切换路由展开状态(显示/隐藏权限列表)
|
||||
const handleToggleRouteExpand = (routeId: number) => {
|
||||
setExpandedRouteIds(prev =>
|
||||
@@ -1070,16 +1228,27 @@ export default function RolePermissions() {
|
||||
}
|
||||
};
|
||||
|
||||
// 保存权限 - v3.0: 同时保存路由权限和API权限
|
||||
// 保存权限 - v3.3: 同时保存路由权限和API权限,仅省级管理员可操作
|
||||
const handleSavePermissions = async () => {
|
||||
if (!selectedRole) return;
|
||||
|
||||
// v3.3: 前置权限检查(仅省级管理员)
|
||||
if (!isProvincialAdmin) {
|
||||
toastService.error('权限不足:仅省级管理员可以修改角色路由权限');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 保存路由权限
|
||||
const routeResult = await updateRoleRoutePermissions(selectedRole.id, selectedRouteIds);
|
||||
|
||||
// v3.3: 处理权限不足错误
|
||||
if (!routeResult.success) {
|
||||
toastService.error(routeResult.message);
|
||||
if (routeResult.code === 4003) {
|
||||
toastService.error('权限不足:仅省级管理员可以修改角色路由权限');
|
||||
} else {
|
||||
toastService.error(routeResult.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1146,6 +1315,7 @@ export default function RolePermissions() {
|
||||
}
|
||||
}}
|
||||
className="route-checkbox"
|
||||
disabled={!isProvincialAdmin}
|
||||
/>
|
||||
<label htmlFor={`route-${route.id}`} className="route-label">
|
||||
{route.icon && <i className={`${route.icon} route-icon`}></i>}
|
||||
@@ -1212,6 +1382,7 @@ export default function RolePermissions() {
|
||||
checked={selectedPermissionIds.includes(permission.id)}
|
||||
onChange={(e) => handleTogglePermission(permission.id, e.target.checked)}
|
||||
style={{ margin: 0 }}
|
||||
disabled={!isProvincialAdmin}
|
||||
/>
|
||||
<span
|
||||
style={{
|
||||
@@ -1398,12 +1569,20 @@ export default function RolePermissions() {
|
||||
{/* 路由权限Tab */}
|
||||
{activeTab === 'permissions' && (
|
||||
<div className="permissions-tab">
|
||||
{/* v3.3: 权限提示(仅省级管理员可修改) */}
|
||||
{!isProvincialAdmin && (
|
||||
<div className="form-notice warning" style={{ marginBottom: '16px' }}>
|
||||
<i className="ri-information-line"></i>
|
||||
<span>您当前为只读模式,仅省级管理员可以修改角色路由权限</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="permissions-header">
|
||||
<h3>为角色 "{selectedRole.role_name}" 分配路由权限</h3>
|
||||
<Button
|
||||
type="primary"
|
||||
icon="ri-save-line"
|
||||
onClick={handleSavePermissions}
|
||||
disabled={!isProvincialAdmin}
|
||||
>
|
||||
保存权限
|
||||
</Button>
|
||||
@@ -1536,6 +1715,8 @@ export default function RolePermissions() {
|
||||
}
|
||||
}}
|
||||
role={selectedRole}
|
||||
isCityAdmin={isCityAdmin}
|
||||
currentUserArea={currentUserArea}
|
||||
/>
|
||||
|
||||
{/* 确认删除模态框 */}
|
||||
@@ -1614,6 +1795,47 @@ export default function RolePermissions() {
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* 权限警告模态框 */}
|
||||
<Modal
|
||||
isOpen={showPermissionWarning}
|
||||
onClose={() => {
|
||||
setShowPermissionWarning(false);
|
||||
setPendingRouteChange(null);
|
||||
}}
|
||||
title="⚠️ 警告:取消角色权限管理路由"
|
||||
size="medium"
|
||||
>
|
||||
<div style={{ padding: '20px 0' }}>
|
||||
<p style={{ marginBottom: '16px', fontSize: '15px', lineHeight: '1.6', color: '#ff6b00' }}>
|
||||
您正在尝试取消勾选 <strong>"/role-permissions"</strong> 路由权限。
|
||||
</p>
|
||||
<p style={{ marginBottom: '16px', fontSize: '14px', lineHeight: '1.6' }}>
|
||||
<strong>请注意:</strong>如果取消此路由权限,该角色的用户将无法访问角色权限管理页面,这可能导致无法管理系统权限。
|
||||
</p>
|
||||
<p style={{ marginBottom: '16px', fontSize: '14px', color: '#666' }}>
|
||||
请谨慎操作,确认后需要点击"保存权限"才会生效。
|
||||
</p>
|
||||
|
||||
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end', marginTop: '24px' }}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setShowPermissionWarning(false);
|
||||
setPendingRouteChange(null);
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={confirmRemovePermissionRoute}
|
||||
>
|
||||
确认取消勾选
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user