fix: 审批流程编辑器按步骤类型过滤可选用户
- 审核阶段: 只显示 admin/superadmin/dba/auditor 角色用户 - 执行阶段: 只显示 admin/superadmin/dba 角色用户 - 全选按钮也基于过滤后的用户列表 - UserDropdown 改用 fixed 定位避免 overflow 裁剪
This commit is contained in:
1 parent
aaa7666d66
commit
1f9d373c08
1 file changed
+33
-7
@@ -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)}
|
||||||
|
|||||||
Reference in new issue
Block a user