feat: stabilize document type and upload flows
This commit is contained in:
@@ -4,13 +4,13 @@ import { Card } from "~/components/ui/Card";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { Modal } from "~/components/ui/Modal";
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
import { usePermission } from "~/hooks/usePermission";
|
||||
import {
|
||||
getRoles,
|
||||
getAllRoutes,
|
||||
getRoleRoutePermissions,
|
||||
updateRoleRoutePermissions,
|
||||
getRoleRoutesWithPermissions,
|
||||
saveRoleApiPermissions,
|
||||
saveRoleAccess,
|
||||
getRolePermissions,
|
||||
getRoleUsers,
|
||||
getUsersWithRoles,
|
||||
@@ -938,6 +938,7 @@ function AssignUserModal({ isOpen, onClose, onSuccess, role, isCityAdmin, curren
|
||||
|
||||
// 主组件
|
||||
export default function RolePermissions() {
|
||||
const { hasPermission, userRole, userArea } = usePermission();
|
||||
const [roles, setRoles] = useState<RoleInfo[]>([]);
|
||||
const [routes, setRoutes] = useState<RouteInfo[]>([]);
|
||||
const [users, setUsers] = useState<UserInfo[]>([]);
|
||||
@@ -946,9 +947,7 @@ export default function RolePermissions() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// v3.3: 检查当前用户角色和地区
|
||||
const [currentUserRole, setCurrentUserRole] = useState('');
|
||||
const [currentUserArea, setCurrentUserArea] = useState('');
|
||||
const [canEdit, setCanEdit] = useState(false);
|
||||
const [isCityAdmin, setIsCityAdmin] = useState(false);
|
||||
|
||||
// 模态框状态
|
||||
@@ -999,10 +998,23 @@ export default function RolePermissions() {
|
||||
// v3.8: 路由ID到路由信息的映射(用于显示通用权限关联的路由名称)
|
||||
const [routeIdToInfoMap, setRouteIdToInfoMap] = useState<Map<number, { title: string; path: string }>>(new Map());
|
||||
|
||||
const canCreateRole = hasPermission('rbac:roles:create');
|
||||
const canUpdateRole = hasPermission('rbac:roles:update');
|
||||
const canDeleteRole = hasPermission('rbac:roles:delete');
|
||||
const canAssignUsers = hasPermission('rbac:user_roles:write');
|
||||
const canSaveRoutePermissions = hasPermission('rbac:role_routes:write');
|
||||
const canSaveApiPermissions = hasPermission('rbac:role_permissions:write');
|
||||
const canSavePermissions = canSaveRoutePermissions && canSaveApiPermissions;
|
||||
|
||||
// 加载初始数据
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, []);
|
||||
}, [canAssignUsers, canCreateRole, canDeleteRole, canSaveApiPermissions, canSaveRoutePermissions, canUpdateRole, userArea, userRole]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentUserArea(userArea || '');
|
||||
setIsCityAdmin(userRole === 'admin');
|
||||
}, [userArea, userRole]);
|
||||
|
||||
// 删除确认倒计时
|
||||
useEffect(() => {
|
||||
@@ -1018,31 +1030,16 @@ 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);
|
||||
setCanEdit((userRole === 'provincial_admin' || userRole === 'admin'));
|
||||
setIsCityAdmin(userRole === 'admin');
|
||||
|
||||
console.log('🔑 [RolePermissions v3.3] 当前用户信息:', {
|
||||
role: userRole,
|
||||
area: userArea,
|
||||
canEdit: (userRole === 'provincial_admin' || userRole === 'admin'),
|
||||
isCityAdmin: userRole === 'admin'
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('❌ [RolePermissions] 解析用户信息失败:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('🔑 [RolePermissions] 当前用户权限:', {
|
||||
role: userRole,
|
||||
area: userArea,
|
||||
canCreateRole,
|
||||
canUpdateRole,
|
||||
canDeleteRole,
|
||||
canAssignUsers,
|
||||
canSaveRoutePermissions,
|
||||
canSaveApiPermissions
|
||||
});
|
||||
|
||||
const [rolesData, routesData, usersData] = await Promise.all([
|
||||
getRoles(),
|
||||
@@ -1472,12 +1469,21 @@ export default function RolePermissions() {
|
||||
|
||||
// 编辑角色
|
||||
const handleEditRole = (role: RoleInfo) => {
|
||||
if (!canUpdateRole) {
|
||||
toastService.error('权限不足:当前账号不能编辑角色');
|
||||
return;
|
||||
}
|
||||
setRoleToEdit(role);
|
||||
setShowEditModal(true);
|
||||
};
|
||||
|
||||
// 删除角色 - 显示确认Modal
|
||||
const handleDeleteRole = async (role: RoleInfo) => {
|
||||
if (!canDeleteRole) {
|
||||
toastService.error('权限不足:当前账号不能删除角色');
|
||||
return;
|
||||
}
|
||||
|
||||
// 系统角色禁止删除
|
||||
if (role.is_system_role) {
|
||||
toastService.error('系统角色不能删除');
|
||||
@@ -1538,6 +1544,10 @@ export default function RolePermissions() {
|
||||
// 移除用户角色 - 显示确认Modal
|
||||
const handleRemoveUserRole = (user: UserInfo) => {
|
||||
if (!selectedRole) return;
|
||||
if (!canAssignUsers) {
|
||||
toastService.error('权限不足:当前账号不能移除用户角色');
|
||||
return;
|
||||
}
|
||||
|
||||
// 打开确认删除Modal
|
||||
setDeleteTarget({ type: 'userRole', role: selectedRole, user });
|
||||
@@ -1570,66 +1580,41 @@ export default function RolePermissions() {
|
||||
}
|
||||
};
|
||||
|
||||
// 保存权限 - 省级管理员和地区管理员可操作
|
||||
// 保存权限:路由与 API 权限联合提交
|
||||
// v3.5: 增加事务性操作和回滚机制
|
||||
const handleSavePermissions = async () => {
|
||||
if (!selectedRole) return;
|
||||
|
||||
// 前置权限检查(省级管理员和地区管理员)
|
||||
if (!canEdit) {
|
||||
toastService.error('权限不足:仅省级管理员和地区管理员可以修改角色路由权限');
|
||||
// 菜单和 API 权限会联合保存,必须同时具备两个写权限
|
||||
if (!canSavePermissions) {
|
||||
toastService.error('权限不足:当前账号缺少菜单保存或接口权限保存能力');
|
||||
return;
|
||||
}
|
||||
|
||||
setSavingPermissions(true);
|
||||
|
||||
// v3.5: 开始事务性操作,保存原始状态以便回滚
|
||||
const originalRouteIds = [...selectedRouteIds];
|
||||
const originalPermissionIds = [...selectedPermissionIds];
|
||||
|
||||
try {
|
||||
// 1. 保存路由权限
|
||||
const routeResult = await updateRoleRoutePermissions(selectedRole.id, selectedRouteIds);
|
||||
const scopedPermissionIds = originalAllPermissions.map(permission => permission.id);
|
||||
const result = await saveRoleAccess(
|
||||
selectedRole.id,
|
||||
selectedRouteIds,
|
||||
selectedPermissionIds,
|
||||
scopedPermissionIds
|
||||
);
|
||||
|
||||
// v3.3: 处理权限不足错误
|
||||
if (!routeResult.success) {
|
||||
if (routeResult.code === 4003) {
|
||||
toastService.error('权限不足:仅省级管理员和地区管理员可以修改角色路由权限');
|
||||
if (!result.success) {
|
||||
if (result.code === 4003) {
|
||||
toastService.error('权限不足:当前账号缺少菜单保存或接口权限保存能力');
|
||||
} else {
|
||||
toastService.error(routeResult.message);
|
||||
toastService.error(result.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// v3.5: 只有在路由权限保存成功后才保存API权限
|
||||
// 2. 保存API权限(如果有选中的权限)
|
||||
let permResult;
|
||||
if (selectedPermissionIds.length > 0) {
|
||||
permResult = await saveRoleApiPermissions(selectedRole.id, selectedPermissionIds);
|
||||
} else {
|
||||
// 没有选中API权限时,清空该角色的所有API权限
|
||||
permResult = await saveRoleApiPermissions(selectedRole.id, []);
|
||||
}
|
||||
|
||||
// v3.5: 处理API权限保存失败的情况
|
||||
if (!permResult.success) {
|
||||
console.error('API权限保存失败,正在回滚路由权限...');
|
||||
// 回滚路由权限到原始状态
|
||||
await updateRoleRoutePermissions(selectedRole.id, originalRouteIds);
|
||||
toastService.error('权限保存失败,已自动回滚到原始状态');
|
||||
// 恢复前端状态
|
||||
setSelectedRouteIds(originalRouteIds);
|
||||
setSelectedPermissionIds(originalPermissionIds);
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.success(`路由权限:${routeResult.message} | API权限:${permResult.message}`);
|
||||
toastService.success(result.message);
|
||||
} catch (error) {
|
||||
console.error("保存权限失败:", error);
|
||||
toastService.error("保存权限失败,已自动回滚到原始状态");
|
||||
// 发生异常时回滚到原始状态
|
||||
setSelectedRouteIds(originalRouteIds);
|
||||
setSelectedPermissionIds(originalPermissionIds);
|
||||
toastService.error("保存权限失败");
|
||||
} finally {
|
||||
setSavingPermissions(false);
|
||||
}
|
||||
@@ -1742,7 +1727,7 @@ export default function RolePermissions() {
|
||||
checked={selectedPermissionIds.includes(permission.id)}
|
||||
onChange={(e) => handleTogglePermission(permission, e.target.checked)}
|
||||
style={{ margin: '3px 0 0 0', flexShrink: 0 }}
|
||||
disabled={!canEdit}
|
||||
disabled={!canSavePermissions}
|
||||
/>
|
||||
{isShared && (
|
||||
<span
|
||||
@@ -1832,7 +1817,7 @@ export default function RolePermissions() {
|
||||
}
|
||||
}}
|
||||
className="route-checkbox"
|
||||
disabled={!canEdit}
|
||||
disabled={!canSavePermissions}
|
||||
/>
|
||||
<label htmlFor={`route-${route.id}`} className="route-label">
|
||||
{route.icon && <i className={`${route.icon} route-icon`}></i>}
|
||||
@@ -1878,7 +1863,7 @@ export default function RolePermissions() {
|
||||
}
|
||||
}}
|
||||
className="route-checkbox"
|
||||
disabled={!canEdit}
|
||||
disabled={!canSavePermissions}
|
||||
/>
|
||||
<label htmlFor={`route-${route.id}`} className="route-label">
|
||||
{route.icon && <i className={`${route.icon} route-icon`}></i>}
|
||||
@@ -1964,7 +1949,7 @@ export default function RolePermissions() {
|
||||
type="primary"
|
||||
icon="ri-add-line"
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
disabled={!canEdit}
|
||||
disabled={!canCreateRole}
|
||||
>
|
||||
新建角色
|
||||
</Button>
|
||||
@@ -2010,6 +1995,7 @@ export default function RolePermissions() {
|
||||
handleEditRole(role);
|
||||
}}
|
||||
title="编辑"
|
||||
disabled={!canUpdateRole}
|
||||
>
|
||||
<i className="ri-edit-line"></i>
|
||||
</button>
|
||||
@@ -2020,6 +2006,7 @@ export default function RolePermissions() {
|
||||
handleDeleteRole(role);
|
||||
}}
|
||||
title="删除"
|
||||
disabled={!canDeleteRole}
|
||||
>
|
||||
<i className="ri-delete-bin-line"></i>
|
||||
</button>
|
||||
@@ -2059,11 +2046,11 @@ export default function RolePermissions() {
|
||||
<div className="permissions-tab">
|
||||
{/* v3.8: 固定头部区域 */}
|
||||
<div className="permissions-tab-header">
|
||||
{/* 权限提示(省级管理员和地区管理员可修改) */}
|
||||
{!canEdit && (
|
||||
{/* 权限提示:当前账号缺少联合保存所需写权限时仅可查看 */}
|
||||
{!canSavePermissions && (
|
||||
<div className="form-notice warning" style={{ marginBottom: '12px' }}>
|
||||
<i className="ri-information-line"></i>
|
||||
<span>您当前为只读模式,仅省级管理员和地区管理员可以修改角色路由权限</span>
|
||||
<span>您当前为只读模式,缺少菜单保存或接口权限保存能力,无法提交本页改动</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="permissions-header">
|
||||
@@ -2098,7 +2085,7 @@ export default function RolePermissions() {
|
||||
type="primary"
|
||||
icon={savingPermissions ? "ri-loader-4-line spin" : "ri-save-line"}
|
||||
onClick={handleSavePermissions}
|
||||
disabled={!canEdit || savingPermissions}
|
||||
disabled={!canSavePermissions || savingPermissions}
|
||||
>
|
||||
{savingPermissions ? '保存中...' : '保存菜单与接口权限'}
|
||||
</Button>
|
||||
@@ -2162,6 +2149,7 @@ export default function RolePermissions() {
|
||||
type="primary"
|
||||
icon="ri-user-add-line"
|
||||
onClick={() => setShowAssignUserModal(true)}
|
||||
disabled={!canAssignUsers}
|
||||
>
|
||||
分配用户
|
||||
</Button>
|
||||
@@ -2214,6 +2202,7 @@ export default function RolePermissions() {
|
||||
className="btn-icon text-error"
|
||||
onClick={() => handleRemoveUserRole(user)}
|
||||
title="移除角色"
|
||||
disabled={!canAssignUsers}
|
||||
>
|
||||
<i className="ri-user-unfollow-line"></i>
|
||||
</button>
|
||||
@@ -2330,7 +2319,7 @@ export default function RolePermissions() {
|
||||
marginTop: '24px'
|
||||
}}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="default"
|
||||
onClick={() => {
|
||||
setShowDeleteConfirm(false);
|
||||
setDeleteTarget(null);
|
||||
@@ -2339,7 +2328,7 @@ export default function RolePermissions() {
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
type="danger"
|
||||
onClick={() => {
|
||||
if (deleteTarget?.type === 'role') {
|
||||
confirmDeleteRole();
|
||||
@@ -2378,7 +2367,7 @@ export default function RolePermissions() {
|
||||
|
||||
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end', marginTop: '24px' }}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="default"
|
||||
onClick={() => {
|
||||
setShowPermissionWarning(false);
|
||||
setPendingRouteChange(null);
|
||||
@@ -2387,7 +2376,7 @@ export default function RolePermissions() {
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
type="danger"
|
||||
onClick={confirmRemovePermissionRoute}
|
||||
>
|
||||
确认取消勾选
|
||||
|
||||
Reference in New Issue
Block a user