Files
pmp-tool/src/lib/decision-cards.ts
xiaohei 20d510d857
Some checks failed
CI / lint-and-typecheck (push) Failing after 31s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
feat: MVP v0.5 完成 - 全部14个P0功能
- P0-8: 决策交互卡片(飞书卡片+回调+4种模板)
- P0-10: 执行记录REST API(Hono框架+统计接口)
- P0-11: 创建流程串联(向导→章程→任务→看板→通知)
- P0-12: GitHub Actions CI/CD
- P0-14: Dockerfile + docker-compose部署
- 前端入口+Vite配置+项目结构完善
- CHANGELOG + PROGRESS更新
2026-04-11 19:01:11 +08:00

173 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 决策交互卡片
* 飞书消息卡片格式 + 回调处理
*/
export type DecisionType = 'approve_reject' | 'multi_choice' | 'confirm';
export interface DecisionCard {
title: string;
description: string;
type: DecisionType;
options: DecisionOption[];
metadata?: Record<string, unknown>;
}
export interface DecisionOption {
key: string;
label: string;
style?: 'primary' | 'danger' | 'default';
value: string;
}
/**
* 生成飞书消息卡片JSON
*/
export function buildDecisionCard(card: DecisionCard): Record<string, unknown> {
const elements: Record<string, unknown>[] = [
{
tag: 'div',
text: { tag: 'plain_text', content: card.description },
},
];
// Action buttons
const actions = card.options.map((opt) => ({
tag: 'button',
text: { tag: 'plain_text', content: opt.label },
type: opt.style === 'danger' ? 'danger' : opt.style === 'primary' ? 'primary' : 'default',
value: { action: opt.key, ...card.metadata },
}));
elements.push({ tag: 'action', actions });
return {
msg_type: 'interactive',
card: {
header: {
title: { tag: 'plain_text', content: card.title },
template: 'blue',
},
elements,
},
};
}
/**
* 决策记录
*/
export interface DecisionRecord {
id: string;
cardTitle: string;
type: DecisionType;
options: string[];
chosenOption: string | null;
status: 'pending' | 'responded' | 'expired';
createdAt: string;
respondedAt?: string;
}
const pendingDecisions: Map<string, DecisionRecord> = new Map();
/**
* 创建决策请求
*/
export function createDecision(card: DecisionCard): DecisionRecord {
const id = `decision-${Date.now()}`;
const record: DecisionRecord = {
id,
cardTitle: card.title,
type: card.type,
options: card.options.map((o) => o.key),
chosenOption: null,
status: 'pending',
createdAt: new Date().toISOString(),
};
pendingDecisions.set(id, record);
return record;
}
/**
* 处理决策回调
*/
export function handleDecisionCallback(
decisionId: string,
actionKey: string
): { ok: boolean; record?: DecisionRecord; error?: string } {
const record = pendingDecisions.get(decisionId);
if (!record) {
return { ok: false, error: 'Decision not found' };
}
if (record.status !== 'pending') {
return { ok: false, error: 'Already responded' };
}
record.chosenOption = actionKey;
record.status = 'responded';
record.respondedAt = new Date().toISOString();
return { ok: true, record };
}
/**
* 获取待决策列表
*/
export function getPendingDecisions(): DecisionRecord[] {
return Array.from(pendingDecisions.values()).filter((d) => d.status === 'pending');
}
/**
* 预定义的决策卡片模板
*/
export const DECISION_TEMPLATES = {
/** 章程审批 */
charterApproval: (projectName: string): DecisionCard => ({
title: '📋 项目章程审批',
description: `项目「${projectName}」的章程已生成,请审批。`,
type: 'approve_reject',
options: [
{ key: 'approve', label: '✅ 批准', style: 'primary', value: 'approved' },
{ key: 'reject', label: '❌ 驳回', style: 'danger', value: 'rejected' },
{ key: 'revise', label: '✏️ 需修改', value: 'needs_revision' },
],
metadata: { type: 'charter', project: projectName },
}),
/** 风险应对 */
riskResponse: (riskDesc: string, priority: number): DecisionCard => ({
title: `⚠️ 风险应对决策(优先级${priority}`,
description: riskDesc,
type: 'multi_choice',
options: [
{ key: 'avoid', label: '🛡 规避', value: 'avoid' },
{ key: 'transfer', label: '🔄 转移', value: 'transfer' },
{ key: 'mitigate', label: '🔧 减轻', value: 'mitigate' },
{ key: 'accept', label: '✅ 接受', value: 'accept' },
],
metadata: { type: 'risk', priority },
}),
/** 变更审批 */
changeApproval: (changeDesc: string): DecisionCard => ({
title: '🔀 变更请求审批',
description: changeDesc,
type: 'approve_reject',
options: [
{ key: 'approve', label: '✅ 批准', style: 'primary', value: 'approved' },
{ key: 'reject', label: '❌ 拒绝', style: 'danger', value: 'rejected' },
{ key: 'defer', label: '⏳ 延后', value: 'deferred' },
],
metadata: { type: 'change' },
}),
/** 里程碑确认 */
milestoneConfirm: (name: string, date: string): DecisionCard => ({
title: `🏁 里程碑达成确认`,
description: `里程碑「${name}」(目标:${date})是否已达成?`,
type: 'confirm',
options: [
{ key: 'done', label: '✅ 已达成', style: 'primary', value: 'reached' },
{ key: 'missed', label: '❌ 未达成', style: 'danger', value: 'missed' },
],
metadata: { type: 'milestone', name, date },
}),
};