主要改动: 1. 创建独立的 planCard.ts 组件 - 导出 getPlanCardStyles() 和 getPlanCardScript() - 包含 renderPlanCardInSegment 和 renderPlanCardStatic 两个渲染函数 2. 优化 messageArea.ts - 移除原有的 plan-card 内联样式和脚本(约 80 行) - 引入 planCard 组件并调用其函数 - 添加 addRule 和 updateNode 工具映射 3. 优化计划卡片样式 - 调整外边距:segment-plan 从 8px 增加到 12px - 优化内边距:header 12px 16px,body 16px,actions 14px 16px - 改进按钮布局:使用垂直布局,按钮和输入框分行显示 - 增加元素间距:步骤间距 6px,按钮间距 10px - 添加行高 1.5 提升可读性 4. 添加 plannerIconSvg 图标
274 lines
8.5 KiB
TypeScript
274 lines
8.5 KiB
TypeScript
/**
|
|
* 计划卡片组件
|
|
*
|
|
* 功能说明:
|
|
* - 显示执行计划的卡片界面
|
|
* - 包含计划标题、摘要和步骤列表
|
|
* - 提供确认执行、修改计划、取消等操作按钮
|
|
*/
|
|
|
|
import { plannerIconSvg } from "../constants/toolIcons";
|
|
|
|
/**
|
|
* 获取计划卡片的样式
|
|
*/
|
|
export function getPlanCardStyles(): string {
|
|
return `
|
|
/* 计划卡片样式 */
|
|
.segment-plan {
|
|
margin: 12px 0;
|
|
}
|
|
.plan-card {
|
|
border: 1px solid var(--vscode-input-border);
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
background: var(--vscode-editor-background);
|
|
}
|
|
.plan-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 12px 16px;
|
|
background: var(--vscode-sideBar-background);
|
|
border-bottom: 1px solid var(--vscode-input-border);
|
|
}
|
|
.plan-icon {
|
|
font-size: 18px;
|
|
}
|
|
.plan-title {
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
}
|
|
.plan-body {
|
|
padding: 16px;
|
|
}
|
|
.plan-summary {
|
|
color: var(--vscode-descriptionForeground);
|
|
margin-bottom: 12px;
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
}
|
|
.plan-steps {
|
|
font-size: 13px;
|
|
}
|
|
.plan-step {
|
|
padding: 8px 12px;
|
|
margin-bottom: 6px;
|
|
background: var(--vscode-list-hoverBackground);
|
|
border-radius: 4px;
|
|
line-height: 1.5;
|
|
}
|
|
.plan-step:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
.step-num {
|
|
color: var(--vscode-textLink-foreground);
|
|
font-weight: 500;
|
|
margin-right: 6px;
|
|
}
|
|
.plan-actions {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
padding: 14px 16px;
|
|
border-top: 1px solid var(--vscode-input-border);
|
|
background: var(--vscode-sideBar-background);
|
|
}
|
|
.plan-actions .question-options {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
.plan-btn {
|
|
padding: 8px 18px;
|
|
border-radius: 4px;
|
|
border: none;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
.plan-btn-confirm {
|
|
background: var(--vscode-button-background);
|
|
color: var(--vscode-button-foreground);
|
|
}
|
|
.plan-btn-confirm:hover {
|
|
background: var(--vscode-button-hoverBackground);
|
|
}
|
|
.plan-btn-modify {
|
|
background: var(--vscode-input-background);
|
|
color: var(--vscode-foreground);
|
|
border: 1px solid var(--vscode-input-border);
|
|
}
|
|
.plan-btn-cancel {
|
|
background: transparent;
|
|
color: var(--vscode-descriptionForeground);
|
|
}
|
|
.plan-actions .custom-input-container {
|
|
display: flex;
|
|
gap: 8px;
|
|
width: 100%;
|
|
}
|
|
.plan-actions .custom-input {
|
|
flex: 1;
|
|
padding: 8px 12px;
|
|
background: var(--vscode-input-background);
|
|
color: var(--vscode-input-foreground);
|
|
border: 1px solid var(--vscode-input-border);
|
|
border-radius: 4px;
|
|
font-size: 13px;
|
|
}
|
|
.plan-actions .custom-submit {
|
|
padding: 8px 18px;
|
|
background: var(--vscode-button-background);
|
|
color: var(--vscode-button-foreground);
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
.plan-actions .custom-submit:hover {
|
|
background: var(--vscode-button-hoverBackground);
|
|
}
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* 获取计划卡片的脚本
|
|
*/
|
|
export function getPlanCardScript(): string {
|
|
return `
|
|
// 渲染计划卡片(在 updateSegmentsRealtime 中使用)
|
|
function renderPlanCardInSegment(segment, segmentDiv, answeredQuestions) {
|
|
segmentDiv.className += ' segment-plan';
|
|
|
|
// 检查是否已回答
|
|
const isAnswered = answeredQuestions.has(segment.askId);
|
|
const selectedAnswer = answeredQuestions.get(segment.askId);
|
|
|
|
if (isAnswered) {
|
|
segmentDiv.classList.add('answered');
|
|
}
|
|
|
|
const stepsHtml = (segment.planSteps || []).map((step, i) =>
|
|
\`<div class="plan-step"><span class="step-num">\${i + 1}.</span> \${step}</div>\`
|
|
).join('');
|
|
|
|
// 选项按钮
|
|
const options = ['确认执行', '修改计划', '取消'];
|
|
const optionsHtml = options.map(opt => {
|
|
const isSelected = isAnswered && opt === selectedAnswer;
|
|
return \`<button class="question-option\${isSelected ? ' selected' : ''}" data-option="\${opt}">\${opt}</button>\`;
|
|
}).join('');
|
|
|
|
segmentDiv.innerHTML = \`
|
|
<div class="plan-card">
|
|
<div class="plan-header">
|
|
<span class="plan-icon">${plannerIconSvg}</span>
|
|
<span class="plan-title">\${segment.planTitle || '执行计划'}</span>
|
|
</div>
|
|
<div class="plan-body">
|
|
<div class="plan-summary">\${segment.planSummary || ''}</div>
|
|
<div class="plan-steps">\${stepsHtml}</div>
|
|
</div>
|
|
<div class="plan-actions">
|
|
<div class="question-options" data-ask-id="\${segment.askId}">\${optionsHtml}</div>
|
|
<div class="custom-input-container" style="display: \${isAnswered ? 'none' : 'flex'};">
|
|
<input type="text" class="custom-input" placeholder="输入修改建议..." />
|
|
<button class="custom-submit">提交</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
\`;
|
|
|
|
// 只在未回答时添加事件监听
|
|
if (!isAnswered) {
|
|
setTimeout(() => {
|
|
const optionButtons = segmentDiv.querySelectorAll('.question-option');
|
|
optionButtons.forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const option = this.getAttribute('data-option');
|
|
// 发送答案到后端
|
|
handleQuestionAnswerInSegment(segment.askId, option, segmentDiv);
|
|
// 同时发送 planAction 用于模式切换
|
|
const actionMap = {
|
|
'确认执行': 'confirm',
|
|
'修改计划': 'modify',
|
|
'取消': 'cancel'
|
|
};
|
|
vscode.postMessage({
|
|
command: 'planAction',
|
|
action: actionMap[option] || option,
|
|
planTitle: segment.planTitle
|
|
});
|
|
});
|
|
});
|
|
|
|
const submitBtn = segmentDiv.querySelector('.custom-submit');
|
|
const customInput = segmentDiv.querySelector('.custom-input');
|
|
if (submitBtn && customInput) {
|
|
submitBtn.addEventListener('click', function() {
|
|
const customValue = customInput.value.trim();
|
|
if (customValue) {
|
|
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
|
|
}
|
|
});
|
|
|
|
customInput.addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
const customValue = customInput.value.trim();
|
|
if (customValue) {
|
|
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}, 0);
|
|
}
|
|
}
|
|
|
|
// 渲染计划卡片(在 renderSegments 中使用)
|
|
function renderPlanCardStatic(segment, segmentDiv) {
|
|
segmentDiv.className += ' segment-plan';
|
|
const stepsHtml = (segment.planSteps || []).map((step, i) =>
|
|
\`<div class="plan-step"><span class="step-num">\${i + 1}.</span> \${step}</div>\`
|
|
).join('');
|
|
|
|
segmentDiv.innerHTML = \`
|
|
<div class="plan-card">
|
|
<div class="plan-header">
|
|
<span class="plan-icon">📋</span>
|
|
<span class="plan-title">\${segment.planTitle || '执行计划'}</span>
|
|
</div>
|
|
<div class="plan-body">
|
|
<div class="plan-summary">\${segment.planSummary || ''}</div>
|
|
<div class="plan-steps">\${stepsHtml}</div>
|
|
</div>
|
|
<div class="plan-actions">
|
|
<button class="plan-btn plan-btn-confirm" data-action="confirm">确认执行</button>
|
|
<button class="plan-btn plan-btn-modify" data-action="modify">修改计划</button>
|
|
<button class="plan-btn plan-btn-cancel" data-action="cancel">取消</button>
|
|
</div>
|
|
</div>
|
|
\`;
|
|
|
|
// 绑定按钮事件
|
|
setTimeout(() => {
|
|
const planCard = segmentDiv.querySelector('.plan-card');
|
|
if (planCard) {
|
|
planCard.querySelectorAll('.plan-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
const action = e.currentTarget?.dataset?.action;
|
|
vscode.postMessage({
|
|
command: 'planAction',
|
|
action: action,
|
|
planTitle: segment.planTitle
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}, 0);
|
|
}
|
|
`;
|
|
}
|