fix: close permission review gaps
fix: harden permission-gated UI — minimum-org endpoint, modal/Popconfirm fail-closed on revocation
This commit is contained in:
@@ -312,8 +312,11 @@ interface MatchModalProps {
|
||||
}
|
||||
|
||||
const JinshujuMatchModal: React.FC<MatchModalProps> = ({ open, onClose, onApplied }) => {
|
||||
const { hasPermission } = usePermission();
|
||||
const { hasPermission, hasAllPermissions, permissionsReady } = usePermission();
|
||||
const canReadSync = hasPermission('sync:read');
|
||||
const canTriggerSync = hasPermission('sync:trigger');
|
||||
const canEnterModal = permissionsReady && hasAllPermissions('sync:read', 'sync:trigger');
|
||||
const canWriteRules = permissionsReady && canTriggerSync;
|
||||
const [step, setStep] = useState<'connection' | 'rule' | 'match' | 'applying'>('connection');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedRuleId, setSelectedRuleId] = useState<number | undefined>();
|
||||
@@ -334,8 +337,22 @@ const JinshujuMatchModal: React.FC<MatchModalProps> = ({ open, onClose, onApplie
|
||||
|
||||
// Load rules on open
|
||||
useEffect(() => {
|
||||
if (open) loadRules();
|
||||
}, [open]);
|
||||
if (open && canEnterModal) loadRules();
|
||||
}, [open, canEnterModal]);
|
||||
|
||||
// Close and reset when permission is lost
|
||||
const enteredRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (canEnterModal) {
|
||||
enteredRef.current = true;
|
||||
return;
|
||||
}
|
||||
if (enteredRef.current) {
|
||||
enteredRef.current = false;
|
||||
reset();
|
||||
onClose();
|
||||
}
|
||||
}, [canEnterModal, onClose]);
|
||||
|
||||
const loadRules = async () => {
|
||||
try {
|
||||
@@ -347,6 +364,7 @@ const JinshujuMatchModal: React.FC<MatchModalProps> = ({ open, onClose, onApplie
|
||||
};
|
||||
|
||||
const handleConnectionNext = async () => {
|
||||
if (!canTriggerSync) return;
|
||||
try {
|
||||
const values = await credForm.validateFields();
|
||||
setLoading(true);
|
||||
@@ -366,6 +384,7 @@ const JinshujuMatchModal: React.FC<MatchModalProps> = ({ open, onClose, onApplie
|
||||
};
|
||||
|
||||
const handlePreview = async () => {
|
||||
if (!canTriggerSync) return;
|
||||
try {
|
||||
const values = await credForm.validateFields();
|
||||
setLoading(true);
|
||||
@@ -689,7 +708,7 @@ const JinshujuMatchModal: React.FC<MatchModalProps> = ({ open, onClose, onApplie
|
||||
return (
|
||||
<Modal
|
||||
title="同步金数据"
|
||||
open={open}
|
||||
open={open && canEnterModal}
|
||||
onCancel={handleClose}
|
||||
width={step === 'match' || step === 'applying' ? 900 : 640}
|
||||
maskClosable={false}
|
||||
|
||||
@@ -1401,9 +1401,12 @@ const StudentProfileContent: React.FC<StudentProfileContentProps> = ({
|
||||
onClose,
|
||||
}) => {
|
||||
const { hasPermission, hasAnyPermission } = usePermission();
|
||||
const canViewOrganizations = hasPermission('organization:view');
|
||||
const canChooseOrganization =
|
||||
canViewOrganizations && hasAnyPermission('student:create', 'student:edit');
|
||||
const canLoadOrganizations = hasAnyPermission(
|
||||
'organization:view',
|
||||
'student:create',
|
||||
'student:edit',
|
||||
);
|
||||
const canChooseOrganization = hasAnyPermission('student:create', 'student:edit');
|
||||
const [aggregateData, setAggregateData] = useState<StudentProfileAggregate | null>(null);
|
||||
const [organizations, setOrganizations] = useState<Array<{ id: number; name: string }>>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -1426,17 +1429,17 @@ const StudentProfileContent: React.FC<StudentProfileContentProps> = ({
|
||||
}, [fetchData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canViewOrganizations) {
|
||||
if (!canLoadOrganizations) {
|
||||
setOrganizations([]);
|
||||
return;
|
||||
}
|
||||
api
|
||||
.get('/organizations', { params: { includeArchived: 'false' } })
|
||||
.get('/organizations/options')
|
||||
.then((res: unknown) => {
|
||||
setOrganizations(res as Array<{ id: number; name: string }>);
|
||||
setOrganizations(res as Array<{ id: number; name: string; isHost?: boolean }>);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [canViewOrganizations]);
|
||||
}, [canLoadOrganizations]);
|
||||
|
||||
const handlePreviewReport = useCallback(async () => {
|
||||
try {
|
||||
@@ -1451,7 +1454,11 @@ const StudentProfileContent: React.FC<StudentProfileContentProps> = ({
|
||||
}
|
||||
}, [studentId]);
|
||||
|
||||
const handleViewSensitive = useViewSensitive(studentId, '学生档案');
|
||||
const handleViewSensitive = useViewSensitive(
|
||||
studentId,
|
||||
'学生档案',
|
||||
hasPermission('log:create'),
|
||||
);
|
||||
|
||||
const tabItems = useMemo(() => {
|
||||
if (!aggregateData) return [];
|
||||
|
||||
Reference in New Issue
Block a user