fix(需求池): 优化待评审操作列展示
This commit is contained in:
@@ -2,7 +2,22 @@
|
|||||||
|
|
||||||
import { RouteGuard } from '@/components/auth/Guard';
|
import { RouteGuard } from '@/components/auth/Guard';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { Search, Plus, Lightbulb, ArrowUp, ArrowDown, ChevronDown, ChevronRight, FolderOpen, Layers } from 'lucide-react';
|
import {
|
||||||
|
Search,
|
||||||
|
Plus,
|
||||||
|
Lightbulb,
|
||||||
|
ArrowUp,
|
||||||
|
ArrowDown,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronRight,
|
||||||
|
FolderOpen,
|
||||||
|
Layers,
|
||||||
|
Pencil,
|
||||||
|
Check,
|
||||||
|
XCircle,
|
||||||
|
Archive,
|
||||||
|
Trash2,
|
||||||
|
} from 'lucide-react';
|
||||||
import { useRequirementStore } from '@/stores/useRequirementStore';
|
import { useRequirementStore } from '@/stores/useRequirementStore';
|
||||||
import { useProductStore } from '@/stores/useProductStore';
|
import { useProductStore } from '@/stores/useProductStore';
|
||||||
import { useDevTaskStore } from '@/stores/useDevTaskStore';
|
import { useDevTaskStore } from '@/stores/useDevTaskStore';
|
||||||
@@ -40,6 +55,16 @@ const PRIORITY_COLORS: Record<string, string> = {
|
|||||||
P4: 'bg-zinc-100 text-zinc-500',
|
P4: 'bg-zinc-100 text-zinc-500',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const REQUIREMENT_ACTION_BUTTON_BASE_CLASS =
|
||||||
|
'inline-flex h-6 w-6 shrink-0 items-center justify-center rounded text-[var(--ink-muted)] transition-colors';
|
||||||
|
|
||||||
|
const REQUIREMENT_ACTION_BUTTON_TONE_CLASS = {
|
||||||
|
neutral: 'hover:bg-[var(--bg-subtle)] hover:text-[var(--ink)]',
|
||||||
|
success: 'text-emerald-600 hover:bg-emerald-50',
|
||||||
|
danger: 'text-red-600 hover:bg-red-50',
|
||||||
|
warning: 'text-orange-600 hover:bg-orange-50',
|
||||||
|
} as const;
|
||||||
|
|
||||||
const STATUS_TABS: { key: string; label: string }[] = [
|
const STATUS_TABS: { key: string; label: string }[] = [
|
||||||
{ key: 'all', label: '全部' },
|
{ key: 'all', label: '全部' },
|
||||||
{ key: 'pending_review', label: '待评审' },
|
{ key: 'pending_review', label: '待评审' },
|
||||||
@@ -753,9 +778,11 @@ function RequirementsPageContent() {
|
|||||||
{canMutateRequirement(req) && (['pending_review', 'adopted', 'planned'] as const).includes(req.status as any) && canEditRequirement(req.id, devTasksByRequirement.get(req.id) ?? []) && (
|
{canMutateRequirement(req) && (['pending_review', 'adopted', 'planned'] as const).includes(req.status as any) && canEditRequirement(req.id, devTasksByRequirement.get(req.id) ?? []) && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEdit(req)}
|
onClick={() => handleEdit(req)}
|
||||||
className="h-6 rounded px-1.5 text-[11px] font-medium text-[var(--ink-soft)] transition-colors hover:bg-[var(--bg-subtle)]"
|
className={`${REQUIREMENT_ACTION_BUTTON_BASE_CLASS} ${REQUIREMENT_ACTION_BUTTON_TONE_CLASS.neutral}`}
|
||||||
|
title="编辑"
|
||||||
|
aria-label="编辑需求"
|
||||||
>
|
>
|
||||||
编辑
|
<Pencil className="h-3.5 w-3.5" strokeWidth={1.8} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{/* 待评审:采纳/拒绝 */}
|
{/* 待评审:采纳/拒绝 */}
|
||||||
@@ -763,15 +790,19 @@ function RequirementsPageContent() {
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => updateRequirement(req.id, { status: 'adopted' })}
|
onClick={() => updateRequirement(req.id, { status: 'adopted' })}
|
||||||
className="h-6 rounded px-1.5 text-[11px] font-medium text-emerald-600 transition-colors hover:bg-emerald-50"
|
className={`${REQUIREMENT_ACTION_BUTTON_BASE_CLASS} ${REQUIREMENT_ACTION_BUTTON_TONE_CLASS.success}`}
|
||||||
|
title="采纳"
|
||||||
|
aria-label="采纳需求"
|
||||||
>
|
>
|
||||||
采纳
|
<Check className="h-3.5 w-3.5" strokeWidth={2} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => { setRejectingReq(req); setRejectReason(''); }}
|
onClick={() => { setRejectingReq(req); setRejectReason(''); }}
|
||||||
className="h-6 rounded px-1.5 text-[11px] font-medium text-red-600 transition-colors hover:bg-red-50"
|
className={`${REQUIREMENT_ACTION_BUTTON_BASE_CLASS} ${REQUIREMENT_ACTION_BUTTON_TONE_CLASS.danger}`}
|
||||||
|
title="拒绝"
|
||||||
|
aria-label="拒绝需求"
|
||||||
>
|
>
|
||||||
拒绝
|
<XCircle className="h-3.5 w-3.5" strokeWidth={1.8} />
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -779,18 +810,22 @@ function RequirementsPageContent() {
|
|||||||
{canMutateRequirement(req) && (req.status === 'adopted' || req.status === 'planned') && canCloseRequirement(req.id, devTasksByRequirement.get(req.id) ?? []) && (
|
{canMutateRequirement(req) && (req.status === 'adopted' || req.status === 'planned') && canCloseRequirement(req.id, devTasksByRequirement.get(req.id) ?? []) && (
|
||||||
<button
|
<button
|
||||||
onClick={() => updateRequirement(req.id, { status: 'closed' })}
|
onClick={() => updateRequirement(req.id, { status: 'closed' })}
|
||||||
className="h-6 rounded px-1.5 text-[11px] font-medium text-orange-600 transition-colors hover:bg-orange-50"
|
className={`${REQUIREMENT_ACTION_BUTTON_BASE_CLASS} ${REQUIREMENT_ACTION_BUTTON_TONE_CLASS.warning}`}
|
||||||
|
title="关闭"
|
||||||
|
aria-label="关闭需求"
|
||||||
>
|
>
|
||||||
关闭
|
<Archive className="h-3.5 w-3.5" strokeWidth={1.8} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{/* 删除:待评审/已拒绝/已关闭(已采纳后不能直接删,需走拒绝) */}
|
{/* 删除:待评审/已拒绝/已关闭(已采纳后不能直接删,需走拒绝) */}
|
||||||
{canMutateRequirement(req) && (['pending_review', 'rejected', 'closed'] as const).includes(req.status as any) && (
|
{canMutateRequirement(req) && (['pending_review', 'rejected', 'closed'] as const).includes(req.status as any) && (
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteRequirement(req.id)}
|
onClick={() => deleteRequirement(req.id)}
|
||||||
className="h-6 rounded px-1.5 text-[11px] font-medium text-red-500 transition-colors hover:bg-red-50"
|
className={`${REQUIREMENT_ACTION_BUTTON_BASE_CLASS} ${REQUIREMENT_ACTION_BUTTON_TONE_CLASS.danger}`}
|
||||||
|
title="删除"
|
||||||
|
aria-label="删除需求"
|
||||||
>
|
>
|
||||||
删除
|
<Trash2 className="h-3.5 w-3.5" strokeWidth={1.8} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,6 +19,16 @@ test('uses fixed table layout without horizontal scrolling for requirement list'
|
|||||||
assert.equal(totalWidth, 100);
|
assert.equal(totalWidth, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('reserves enough width for pending-review row actions without horizontal scrolling', () => {
|
||||||
|
const sourceColumnWidth = REQUIREMENT_TABLE_COLUMN_WIDTHS[2];
|
||||||
|
const creatorColumnWidth = REQUIREMENT_TABLE_COLUMN_WIDTHS[9];
|
||||||
|
const actionColumnWidth = REQUIREMENT_TABLE_COLUMN_WIDTHS[11];
|
||||||
|
|
||||||
|
assert.ok(sourceColumnWidth <= 10);
|
||||||
|
assert.ok(creatorColumnWidth <= 4);
|
||||||
|
assert.ok(actionColumnWidth >= 20);
|
||||||
|
});
|
||||||
|
|
||||||
test('keeps requirement table headers on one line', () => {
|
test('keeps requirement table headers on one line', () => {
|
||||||
assert.match(REQUIREMENT_TABLE_HEADER_CELL_CLASS, /\bwhitespace-nowrap\b/);
|
assert.match(REQUIREMENT_TABLE_HEADER_CELL_CLASS, /\bwhitespace-nowrap\b/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
export type RequirementTableTextColumn = 'source' | 'project' | 'type' | 'version' | 'creator';
|
export type RequirementTableTextColumn = 'source' | 'project' | 'type' | 'version' | 'creator';
|
||||||
|
|
||||||
const TEXT_COLUMN_MAX_WIDTH: Record<RequirementTableTextColumn, string> = {
|
const TEXT_COLUMN_MAX_WIDTH: Record<RequirementTableTextColumn, string> = {
|
||||||
source: 'max-w-[170px]',
|
source: 'max-w-[120px]',
|
||||||
project: 'max-w-[120px]',
|
project: 'max-w-[120px]',
|
||||||
type: 'max-w-[96px]',
|
type: 'max-w-[96px]',
|
||||||
version: 'max-w-[92px]',
|
version: 'max-w-[92px]',
|
||||||
creator: 'max-w-[96px]',
|
creator: 'max-w-[64px]',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const REQUIREMENT_TABLE_CONTAINER_CLASS =
|
export const REQUIREMENT_TABLE_CONTAINER_CLASS =
|
||||||
@@ -13,7 +13,7 @@ export const REQUIREMENT_TABLE_CONTAINER_CLASS =
|
|||||||
|
|
||||||
export const REQUIREMENT_TABLE_CLASS = 'w-full table-fixed text-left text-[13px]';
|
export const REQUIREMENT_TABLE_CLASS = 'w-full table-fixed text-left text-[13px]';
|
||||||
|
|
||||||
export const REQUIREMENT_TABLE_COLUMN_WIDTHS = [6, 15, 14, 7, 6, 6, 5, 6, 7, 6, 8, 14] as const;
|
export const REQUIREMENT_TABLE_COLUMN_WIDTHS = [6, 15, 10, 7, 6, 6, 5, 6, 7, 4, 8, 20] as const;
|
||||||
|
|
||||||
export const REQUIREMENT_TABLE_HEADER_CELL_CLASS =
|
export const REQUIREMENT_TABLE_HEADER_CELL_CLASS =
|
||||||
'px-3 py-2.5 whitespace-nowrap text-[11px] font-medium uppercase tracking-wide text-[var(--ink-muted)]';
|
'px-3 py-2.5 whitespace-nowrap text-[11px] font-medium uppercase tracking-wide text-[var(--ink-muted)]';
|
||||||
|
|||||||
Reference in New Issue
Block a user