fix: 审批流程编辑器按步骤类型过滤可选用户

- 审核阶段: 只显示 admin/superadmin/dba/auditor 角色用户
- 执行阶段: 只显示 admin/superadmin/dba 角色用户
- 全选按钮也基于过滤后的用户列表
- UserDropdown 改用 fixed 定位避免 overflow 裁剪
This commit is contained in:
logan committed 2026-05-31 23:22:08 +08:00
1 parent aaa7666d66
commit 1f9d373c08
1 file changed
+33 -7
+33 -7
View File
@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import { import {
Plus, Edit2, Eye, ToggleLeft, ToggleRight, GitBranch, Users, CheckCircle, Plus, Edit2, Eye, ToggleLeft, ToggleRight, GitBranch, Users, CheckCircle,
ArrowRight, Shield, Trash2, X, ChevronDown, ChevronUp, ArrowRight, Shield, Trash2, X, ChevronDown, ChevronUp,
@@ -99,15 +99,30 @@ interface UserDropdownProps {
function UserDropdown({ users, selectedIds, label, onToggle, onToggleAll, onClear, getUserName }: UserDropdownProps) { function UserDropdown({ users, selectedIds, label, onToggle, onToggleAll, onClear, getUserName }: UserDropdownProps) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const triggerRef = useRef<HTMLButtonElement>(null);
const ids = Array.isArray(selectedIds) ? selectedIds : []; const ids = Array.isArray(selectedIds) ? selectedIds : [];
const allSelected = ids.length === users.length && users.length > 0; const allSelected = ids.length === users.length && users.length > 0;
const [dropdownPos, setDropdownPos] = useState({ top: 0, left: 0, width: 0 });
const updatePos = () => {
if (triggerRef.current) {
const rect = triggerRef.current.getBoundingClientRect();
setDropdownPos({ top: rect.bottom + 4, left: rect.left, width: rect.width });
}
};
const handleToggle = () => {
if (!open) updatePos();
setOpen(!open);
};
return ( return (
<div className="relative"> <div className="relative">
{/* Trigger */} {/* Trigger */}
<button <button
ref={triggerRef}
type="button" type="button"
onClick={() => setOpen(!open)} onClick={handleToggle}
className={`w-full flex items-center justify-between px-3 py-2 rounded-lg text-sm transition-colors ${ className={`w-full flex items-center justify-between px-3 py-2 rounded-lg text-sm transition-colors ${
selectedIds.length === 0 selectedIds.length === 0
? 'bg-[var(--dg-bg-tertiary)] border border-red-500/40 text-[var(--dg-text-muted)]' ? 'bg-[var(--dg-bg-tertiary)] border border-red-500/40 text-[var(--dg-text-muted)]'
@@ -142,11 +157,14 @@ function UserDropdown({ users, selectedIds, label, onToggle, onToggleAll, onClea
</div> </div>
)} )}
{/* Dropdown */} {/* Dropdown — fixed positioning to escape overflow clipping */}
{open && ( {open && (
<> <>
<div className="fixed inset-0 z-40" onClick={() => setOpen(false)} /> <div className="fixed inset-0 z-40" onClick={() => setOpen(false)} />
<div className="absolute left-0 right-0 top-full mt-1 z-50 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-lg shadow-xl max-h-52 overflow-y-auto"> <div
className="fixed z-50 bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-lg shadow-xl max-h-52 overflow-y-auto"
style={{ top: dropdownPos.top, left: dropdownPos.left, width: dropdownPos.width }}
>
{/* Select all / Clear */} {/* Select all / Clear */}
<div className="flex items-center justify-between px-3 py-2 border-b border-[var(--dg-border)]"> <div className="flex items-center justify-between px-3 py-2 border-b border-[var(--dg-border)]">
<button <button
@@ -262,12 +280,20 @@ function WorkflowEditorModal({ workflow, users, onClose, onSaved }: EditorProps)
}); });
}; };
const getEligibleUsers = (stepType: string) => {
if (stepType === 'audit') return users.filter(u => ['admin', 'superadmin', 'dba', 'auditor'].includes(u.role));
if (stepType === 'execute') return users.filter(u => ['admin', 'superadmin', 'dba'].includes(u.role));
return users;
};
const toggleAllUsers = (idx: number) => { const toggleAllUsers = (idx: number) => {
const step = steps[idx]; const step = steps[idx];
if (step.approver_ids.length === users.length) { const eligible = getEligibleUsers(step.step_type);
const allSelected = step.approver_ids.length === eligible.length && eligible.length > 0;
if (allSelected) {
updateStep(idx, { approver_ids: [] }); updateStep(idx, { approver_ids: [] });
} else { } else {
updateStep(idx, { approver_ids: users.map((u) => u.id) }); updateStep(idx, { approver_ids: eligible.map((u) => u.id) });
} }
}; };
@@ -417,7 +443,7 @@ function WorkflowEditorModal({ workflow, users, onClose, onSaved }: EditorProps)
{step.step_type !== 'submit' && ( {step.step_type !== 'submit' && (
<div className="pl-9 mt-2"> <div className="pl-9 mt-2">
<UserDropdown <UserDropdown
users={users} users={getEligibleUsers(step.step_type)}
selectedIds={step.approver_ids} selectedIds={step.approver_ids}
label={step.step_type === 'audit' ? '审核人员' : '执行人员'} label={step.step_type === 'audit' ? '审核人员' : '执行人员'}
onToggle={(uid) => toggleUser(idx, uid)} onToggle={(uid) => toggleUser(idx, uid)}