feat(ai-analysis): 添加详情页智能分析入口
Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled

This commit is contained in:
2026-07-09 09:42:58 +08:00
parent 6d2999085e
commit 1e6fb0c7aa
6 changed files with 231 additions and 4 deletions

View File

@@ -10,6 +10,10 @@ import { useOvertimeStore } from '@/stores/useOvertimeStore';
import { RequirementTable } from '@/components/product/RequirementTable';
import { RequirementForm } from '@/components/product/RequirementForm';
import { ProductForm } from '@/components/product/ProductForm';
import { AnalysisContextDrawer } from '@/components/analysis/AnalysisContextDrawer';
import { AnalysisEntryButton } from '@/components/analysis/AnalysisEntryButton';
import { useAuthStore } from '@/stores/useAuthStore';
import { useMemberStore } from '@/stores/useMemberStore';
const TABS = [
{ key: 'requirements' as const, label: '需求池' },
@@ -29,10 +33,13 @@ export default function ProductDetailPage() {
deleteRequirement,
} = useRequirementStore();
const { records, fetchRecords } = useOvertimeStore();
const user = useAuthStore((state) => state.user);
const roles = useMemberStore((state) => state.roles);
const [showReqForm, setShowReqForm] = useState(false);
const [editingReq, setEditingReq] = useState<any>(null);
const [editingProduct, setEditingProduct] = useState(false);
const [showAnalysisDrawer, setShowAnalysisDrawer] = useState(false);
const [activeTab, setActiveTab] = useState<'requirements' | 'projects' | 'versions'>('requirements');
const [reqStatusFilter, setReqStatusFilter] = useState<RequirementStatus | null>(null);
@@ -47,6 +54,10 @@ export default function ProductDetailPage() {
if (reqStatusFilter) list = list.filter((r) => r.status === reqStatusFilter);
return list;
}, [requirements, productId, reqStatusFilter]);
const currentPermissions = useMemo(
() => roles.find((role) => role.id === user?.roleId)?.permissions ?? [],
[roles, user?.roleId],
);
const handleFilterChange = (status: RequirementStatus | null) => {
setReqStatusFilter(status);
@@ -93,7 +104,8 @@ export default function ProductDetailPage() {
{currentProduct.name}
</h1>
</div>
<div className="flex gap-1.5">
<div className="flex items-center gap-1.5">
<AnalysisEntryButton onClick={() => setShowAnalysisDrawer(true)} />
<button
onClick={() => setEditingProduct(true)}
className="flex h-8 items-center gap-1 rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-2.5 text-[12px] text-[var(--ink-soft)] hover:bg-[var(--bg-subtle)] hover:text-[var(--ink)]"
@@ -215,6 +227,14 @@ export default function ProductDetailPage() {
)}
</div>
</div>
<AnalysisContextDrawer
open={showAnalysisDrawer}
title={`产品智能分析 · ${currentProduct.name}`}
context={{ surface: 'product_detail', productId: currentProduct.id }}
permissions={currentPermissions}
onClose={() => setShowAnalysisDrawer(false)}
/>
</div>
);
}

View File

@@ -19,6 +19,8 @@ import { calcGroupProgress as calcDevTaskProgress, aggregateDevTaskHours } from
import { CapsuleStages } from '@/components/version/CapsuleStages';
import { MemberChips } from '@/components/version/MemberChips';
import { ProjectMemberPanel } from '@/components/project/ProjectMemberPanel';
import { AnalysisContextDrawer } from '@/components/analysis/AnalysisContextDrawer';
import { AnalysisEntryButton } from '@/components/analysis/AnalysisEntryButton';
import { getRequirementCoverageSummary, type VersionPlan } from '@/lib/version-plan';
import { buildVersionTimelineSummary, calcStageEffortMetrics, formatVersionOverviewDateTime, getVersionCardDefaultExpanded, mergeStageProgressWithEffort } from '@/lib/version-overview';
import { calcScopedVersionProgress } from '@/lib/version-progress';
@@ -327,6 +329,7 @@ export default function ProjectDetailPage() {
const { testCases, fetchTestCases } = useTestCaseStore();
const { bugs, fetchBugs } = useBugStore();
const [statusFilter, setStatusFilter] = useState<string>('all');
const [showAnalysisDrawer, setShowAnalysisDrawer] = useState(false);
useEffect(() => { fetchOverview(); }, [fetchOverview]);
useEffect(() => { fetchRequirements(); }, [fetchRequirements]);
@@ -340,6 +343,10 @@ export default function ProjectDetailPage() {
const user = useAuthStore((s) => s.user);
const currentUserName = user?.name || '';
const { roles } = useMemberStore();
const currentPermissions = useMemo(
() => roles.find((role) => role.id === user?.roleId)?.permissions ?? [],
[roles, user?.roleId],
);
const isSuperAdmin = useMemo(() => {
const r = roles.find((x) => x.id === user?.roleId);
return !!r && r.permissions.includes('*');
@@ -435,8 +442,11 @@ export default function ProjectDetailPage() {
<span className="ml-2 text-[var(--ink-muted)]">/</span>
<span className="ml-2 text-[15px] font-semibold text-[var(--ink)]">{project.name}</span>
</div>
<div className="flex items-center gap-1.5 rounded-full bg-[var(--bg-subtle)] px-2.5 py-1 text-xs text-[var(--ink-soft)]">
<Package className="h-3 w-3" /><span>{project.productName}</span>
<div className="flex items-center gap-2">
<AnalysisEntryButton onClick={() => setShowAnalysisDrawer(true)} />
<div className="flex items-center gap-1.5 rounded-full bg-[var(--bg-subtle)] px-2.5 py-1 text-xs text-[var(--ink-soft)]">
<Package className="h-3 w-3" /><span>{project.productName}</span>
</div>
</div>
</header>
@@ -482,6 +492,13 @@ export default function ProjectDetailPage() {
</section>
</div>
</div>
<AnalysisContextDrawer
open={showAnalysisDrawer}
title={`项目智能分析 · ${project.name}`}
context={{ surface: 'project_detail', projectId: project.id }}
permissions={currentPermissions}
onClose={() => setShowAnalysisDrawer(false)}
/>
</div>
);
}

View File

@@ -17,6 +17,8 @@ import { PlanTab } from '@/components/version/PlanTab';
import { DevTaskTab } from '@/components/dev-task/DevTaskTab';
import { TestCaseTab } from '@/components/test-case/TestCaseTab';
import { BugTab } from '@/components/bug/BugTab';
import { AnalysisContextDrawer } from '@/components/analysis/AnalysisContextDrawer';
import { AnalysisEntryButton } from '@/components/analysis/AnalysisEntryButton';
import { useVersionPlanStore } from '@/stores/useVersionPlanStore';
import { useDevTaskStore } from '@/stores/useDevTaskStore';
import { useTestCaseStore } from '@/stores/useTestCaseStore';
@@ -135,6 +137,7 @@ export default function VersionDetailPage() {
const { departments, members: allMembers, roles, fetchMembers } = useMemberStore();
const { categories: taskCategories, fetchCategories } = useTaskCategoryStore();
const currentRole = useMemo(() => roles.find((r) => r.id === user?.roleId), [roles, user?.roleId]);
const currentPermissions = currentRole?.permissions ?? [];
const memberCandidates = useMemo(
() => allMembers.map((member) => ({
id: member.id,
@@ -157,6 +160,7 @@ export default function VersionDetailPage() {
const [showRecommendModal, setShowRecommendModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [showReleaseModal, setShowReleaseModal] = useState(false);
const [showAnalysisDrawer, setShowAnalysisDrawer] = useState(false);
const [recommendationDataReady, setRecommendationDataReady] = useState(false);
const [v22Scope, setV22Scope] = useState<VersionDataScope | null>(null);
@@ -432,7 +436,10 @@ export default function VersionDetailPage() {
<span className="ml-1.5 text-[var(--ink-muted)]">/</span>
<span className="ml-1.5 text-[15px] font-semibold text-[var(--ink)]">{version.name}</span>
</div>
<div className="flex items-center gap-2">{renderActions()}</div>
<div className="flex items-center gap-2">
<AnalysisEntryButton onClick={() => setShowAnalysisDrawer(true)} />
{renderActions()}
</div>
</header>
{/* Tab bar */}
@@ -1184,6 +1191,13 @@ export default function VersionDetailPage() {
onClose={() => setShowMemberModal(false)}
/>
)}
<AnalysisContextDrawer
open={showAnalysisDrawer}
title={`版本智能分析 · ${version.name}`}
context={{ surface: 'version_detail', versionId: version.id }}
permissions={currentPermissions}
onClose={() => setShowAnalysisDrawer(false)}
/>
</div>
);
}