feat(workflow): Phase 6 前端项目多工作流切换器、前置依赖横幅与文档血缘失效提醒
This commit is contained in:
@@ -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');
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user