feat(workflow): Phase 6 前端项目多工作流切换器、前置依赖横幅与文档血缘失效提醒

This commit is contained in:
WeiCHEN
2026-09-13 02:50:21 +00:00
committed by weichen
parent abeab90388
commit 716aa6f49a
9 changed files with 658 additions and 2 deletions
+50
View File
@@ -0,0 +1,50 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
Project,
ProjectWorkflow,
WorkflowDefinition,
DocumentLineage,
} from '../types/projectWorkflow';
test('Project workflow types validation', () => {
const p: Project = {
id: 1001,
code: 'electro-cutter-01',
name: '智能高频电刀',
status: 'active',
creator_user_id: 2,
active_workflow_key: 'main',
workflow_selection_version: 1,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
assert.equal(p.id, 1001);
assert.equal(p.creator_user_id, 2);
assert.equal(p.active_workflow_key, 'main');
const pw: ProjectWorkflow = {
id: 1,
project_id: 1001,
workflow_key: 'product_manager',
is_active: true,
display_order: 2,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
assert.equal(pw.workflow_key, 'product_manager');
const lineage: DocumentLineage = {
id: 1,
project_id: 1001,
downstream_doc_id: 'doc_proj1001_test_manager',
downstream_version: 1,
upstream_doc_id: 'doc_proj1001_system_reqs',
upstream_version: 1,
upstream_sha256: 'sha_upstream',
is_stale: false,
created_at: new Date().toISOString(),
};
assert.equal(lineage.is_stale, false);
assert.equal(lineage.upstream_doc_id, 'doc_proj1001_system_reqs');
});
+151
View File
@@ -0,0 +1,151 @@
import {
Project,
ProjectWorkflow,
WorkflowDefinition,
DocumentLineage,
CreateProjectInput,
ActivateWorkflowInput,
ActivateWorkflowResult,
} from '../types/projectWorkflow';
const API_BASE = '';
function getAuthHeaders(): HeadersInit {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (typeof window !== 'undefined' && window.localStorage) {
try {
const raw = window.localStorage.getItem('smdf_auth_session');
if (raw) {
const session = JSON.parse(raw);
if (session.token) {
headers['Authorization'] = `Bearer ${session.token}`;
}
if (session.user?.id) {
headers['X-User-ID'] = String(session.user.id);
}
}
} catch {
// ignore
}
}
return headers;
}
export async function fetchProjects(): Promise<Project[]> {
const res = await fetch(`${API_BASE}/api/v1/projects`, {
headers: getAuthHeaders(),
});
if (!res.ok) {
throw new Error(`获取项目列表失败: ${res.status}`);
}
return res.json();
}
export async function createProject(data: CreateProjectInput): Promise<Project> {
const res = await fetch(`${API_BASE}/api/v1/projects`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(data),
});
if (!res.ok) {
const text = await res.text();
throw new Error(text || `创建项目失败: ${res.status}`);
}
return res.json();
}
export async function getProject(id: number): Promise<Project> {
const res = await fetch(`${API_BASE}/api/v1/projects/${id}`, {
headers: getAuthHeaders(),
});
if (!res.ok) {
throw new Error(`获取项目详情失败: ${res.status}`);
}
return res.json();
}
export async function fetchProjectWorkflows(projectId: number): Promise<ProjectWorkflow[]> {
const res = await fetch(`${API_BASE}/api/v1/projects/${projectId}/workflows`, {
headers: getAuthHeaders(),
});
if (!res.ok) {
throw new Error(`获取项目工作流清单失败: ${res.status}`);
}
return res.json();
}
export async function activateWorkflow(
projectId: number,
input: ActivateWorkflowInput
): Promise<ActivateWorkflowResult> {
const res = await fetch(`${API_BASE}/api/v1/projects/${projectId}/active-workflow`, {
method: 'PUT',
headers: getAuthHeaders(),
body: JSON.stringify(input),
});
if (!res.ok) {
const text = await res.text();
let msg = text;
try {
const parsed = JSON.parse(text);
if (parsed.message) msg = parsed.message;
} catch {
// ignore
}
throw new Error(msg || `切换激活工作流失败: ${res.status}`);
}
return res.json();
}
export async function fetchWorkflowCatalog(): Promise<WorkflowDefinition[]> {
const res = await fetch(`${API_BASE}/api/v1/workflows`, {
headers: getAuthHeaders(),
});
if (!res.ok) {
throw new Error(`获取工作流规范目录失败: ${res.status}`);
}
return res.json();
}
export async function fetchProjectLineage(
projectId: number,
docId?: string
): Promise<DocumentLineage[]> {
const url = docId
? `${API_BASE}/api/v1/projects/${projectId}/lineage?doc_id=${encodeURIComponent(docId)}`
: `${API_BASE}/api/v1/projects/${projectId}/lineage`;
const res = await fetch(url, {
headers: getAuthHeaders(),
});
if (!res.ok) {
throw new Error(`获取文档血缘失败: ${res.status}`);
}
return res.json();
}
export async function runProjectWorkflow(
projectId: number,
workflowKey: string,
payload: Record<string, any>
): Promise<any> {
const res = await fetch(`${API_BASE}/api/v1/projects/${projectId}/workflows/${workflowKey}/run`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify(payload),
});
if (!res.ok) {
const text = await res.text();
let msg = text;
try {
const parsed = JSON.parse(text);
if (parsed.message) msg = parsed.message;
else if (parsed.error_reason) msg = parsed.error_reason;
} catch {
// ignore
}
throw new Error(msg || `启动工作流运行失败: ${res.status}`);
}
return res.json();
}