feat: 状态渲染修复+中文姓名映射+SUPERVISOR/SUPER_ADMIN权限控制(前端+后端)
This commit is contained in:
@ -92,15 +92,23 @@ const FlowCard = memo(function FlowCard({
|
||||
task,
|
||||
isActive,
|
||||
onAction,
|
||||
currentUser,
|
||||
assigneeName,
|
||||
}: {
|
||||
task: TaskResponse;
|
||||
isActive: boolean;
|
||||
onAction: (target: ModalTarget) => void;
|
||||
currentUser?: { username?: string; role?: string } | null;
|
||||
assigneeName?: string;
|
||||
}) {
|
||||
const cfg = getStatusConfig(task.status);
|
||||
const dwell = calcDwell(task.received_at, task.completed_at, task.status);
|
||||
const isCompleted = task.status?.toUpperCase() === TASK_STATUS.COMPLETED;
|
||||
const isArchived = task.status?.toUpperCase() === TASK_STATUS.ARCHIVED;
|
||||
// 权限:本人或管理员/主管可操作
|
||||
const isOwner = !!(currentUser?.username && task.assignee_id === currentUser.username);
|
||||
const isManager = currentUser?.role === "SUPER_ADMIN" || currentUser?.role === "SUPERVISOR";
|
||||
const canOperate = isOwner || isManager;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -137,7 +145,7 @@ const FlowCard = memo(function FlowCard({
|
||||
</span>
|
||||
{task.assignee_id && (
|
||||
<span className="text-[10px] text-gray-400 truncate max-w-[80px]">
|
||||
{task.assignee_id}
|
||||
{assigneeName || task.assignee_id}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@ -160,49 +168,75 @@ const FlowCard = memo(function FlowCard({
|
||||
{task.received_at && `接收: ${new Date(task.received_at).toLocaleDateString("zh-CN")}`}
|
||||
</p>
|
||||
|
||||
{/* 操作按钮 — 仅激活态显示 */}
|
||||
{isActive && (
|
||||
{/* 操作按钮 — 仅激活态 + 有权限时显示 */}
|
||||
{isActive && canOperate && (
|
||||
<div className="mt-2 flex gap-1.5 border-t border-gray-100 pt-2">
|
||||
{task.status?.toUpperCase() === TASK_STATUS.PENDING && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => onAction({ task, action: "receive" })}
|
||||
className="flex-1 rounded border border-blue-200 bg-blue-50 py-1 text-[10px] font-medium text-blue-600 hover:bg-blue-100"
|
||||
>
|
||||
<button onClick={() => onAction({ task, action: "receive" })}
|
||||
className="flex-1 rounded border border-blue-200 bg-blue-50 py-1 text-[10px] font-medium text-blue-600 hover:bg-blue-100">
|
||||
接收
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onAction({ task, action: "reject" })}
|
||||
className="rounded border border-red-200 bg-red-50 px-2 py-1 text-[10px] font-medium text-red-500 hover:bg-red-100"
|
||||
>
|
||||
<button onClick={() => onAction({ task, action: "reject" })}
|
||||
className="rounded border border-red-200 bg-red-50 px-2 py-1 text-[10px] font-medium text-red-500 hover:bg-red-100">
|
||||
驳回
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onAction({ task, action: "transfer" })}
|
||||
className="rounded border border-green-200 bg-green-50 px-2 py-1 text-[10px] font-medium text-green-600 hover:bg-green-100"
|
||||
>
|
||||
<button onClick={() => onAction({ task, action: "transfer" })}
|
||||
className="rounded border border-green-200 bg-green-50 px-2 py-1 text-[10px] font-medium text-green-600 hover:bg-green-100">
|
||||
转交
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{task.status?.toUpperCase() === TASK_STATUS.WIP && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => onAction({ task, action: "reject" })}
|
||||
className="flex-1 rounded border border-red-200 bg-red-50 py-1 text-[10px] font-medium text-red-500 hover:bg-red-100"
|
||||
>
|
||||
<button onClick={() => onAction({ task, action: "reject" })}
|
||||
className="flex-1 rounded border border-red-200 bg-red-50 py-1 text-[10px] font-medium text-red-500 hover:bg-red-100">
|
||||
驳回
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onAction({ task, action: "transfer" })}
|
||||
className="flex-1 rounded border border-green-200 bg-green-50 py-1 text-[10px] font-medium text-green-600 hover:bg-green-100"
|
||||
>
|
||||
<button onClick={() => onAction({ task, action: "transfer" })}
|
||||
className="flex-1 rounded border border-green-200 bg-green-50 py-1 text-[10px] font-medium text-green-600 hover:bg-green-100">
|
||||
转交
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* 非本人但管理员可见:干预提示 */}
|
||||
{isActive && !isOwner && isManager && (
|
||||
<div className="mt-2 border-t border-orange-100 pt-2">
|
||||
<p className="text-[9px] text-orange-500 mb-1">⚠ 运维干预模式</p>
|
||||
{task.status?.toUpperCase() === TASK_STATUS.PENDING && (
|
||||
<div className="flex gap-1.5">
|
||||
<button onClick={() => onAction({ task, action: "reject" })}
|
||||
className="flex-1 rounded border border-orange-200 bg-orange-50 py-1 text-[10px] font-medium text-orange-600 hover:bg-orange-100">
|
||||
强制驳回
|
||||
</button>
|
||||
<button onClick={() => onAction({ task, action: "transfer" })}
|
||||
className="flex-1 rounded border border-orange-200 bg-orange-50 py-1 text-[10px] font-medium text-orange-600 hover:bg-orange-100">
|
||||
强制转交
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{task.status?.toUpperCase() === TASK_STATUS.WIP && (
|
||||
<div className="flex gap-1.5">
|
||||
<button onClick={() => onAction({ task, action: "reject" })}
|
||||
className="flex-1 rounded border border-orange-200 bg-orange-50 py-1 text-[10px] font-medium text-orange-600 hover:bg-orange-100">
|
||||
强制驳回
|
||||
</button>
|
||||
<button onClick={() => onAction({ task, action: "transfer" })}
|
||||
className="flex-1 rounded border border-orange-200 bg-orange-50 py-1 text-[10px] font-medium text-orange-600 hover:bg-orange-100">
|
||||
强制转交
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* 非本人非管理员:只读提示 */}
|
||||
{isActive && !canOperate && (
|
||||
<div className="mt-2 border-t border-gray-100 pt-2">
|
||||
<p className="text-[9px] text-gray-400 text-center">非当前任务责任人,无法操作</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@ -212,9 +246,11 @@ const FlowCard = memo(function FlowCard({
|
||||
interface TaskFlowViewProps {
|
||||
tasks: TaskResponse[];
|
||||
onAction: (target: ModalTarget) => void;
|
||||
currentUser?: { username?: string; role?: string } | null;
|
||||
assigneeNames?: Record<string, string>;
|
||||
}
|
||||
|
||||
export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction }: TaskFlowViewProps) {
|
||||
export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction, currentUser, assigneeNames }: TaskFlowViewProps) {
|
||||
// 按 depth 分组的层级数据
|
||||
const depthMap = useMemo(() => {
|
||||
if (!tasks || tasks.length === 0) return new Map<number, TaskResponse[]>();
|
||||
@ -266,6 +302,8 @@ export const TaskFlowView = memo(function TaskFlowView({ tasks, onAction }: Task
|
||||
task={task}
|
||||
isActive={isActive}
|
||||
onAction={onAction}
|
||||
currentUser={currentUser}
|
||||
assigneeName={assigneeNames?.[task.assignee_id || ""]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user