feat: 优化代码变更面板样式和交互

- 优化变更面板和 diff 视图样式
   - 新增全部采纳和全部拒绝按钮
   - 修复删除文件的变更追踪和采纳逻辑
   - 整个标题栏可点击展开/收起
   - 增强视觉效果和用户体验
This commit is contained in:
Roe-xin
2026-03-02 10:37:45 +08:00
parent 4c7ec65577
commit 5f88c7ceac
4 changed files with 201 additions and 43 deletions

View File

@ -143,7 +143,16 @@ class ChangeTrackerService {
} }
const absolutePath = path.join(workspaceFolder.uri.fsPath, change.filePath); const absolutePath = path.join(workspaceFolder.uri.fsPath, change.filePath);
await fs.promises.writeFile(absolutePath, change.newContent, 'utf-8');
// 如果是删除操作,删除文件
if (change.changeType === 'delete') {
if (fs.existsSync(absolutePath)) {
await fs.promises.unlink(absolutePath);
}
} else {
// 创建或修改文件
await fs.promises.writeFile(absolutePath, change.newContent, 'utf-8');
}
this.removeChange(changeId); this.removeChange(changeId);
return true; return true;

View File

@ -180,6 +180,13 @@ async function executeFileDelete(args: FileDeleteArgs): Promise<string> {
throw new Error(`不能删除目录,请指定文件路径: ${filePath}`); throw new Error(`不能删除目录,请指定文件路径: ${filePath}`);
} }
// 读取文件内容用于变更追踪
const oldContent = fs.readFileSync(absolutePath, 'utf-8');
// 记录删除变更
const relativePath = path.relative(workspacePath, absolutePath);
changeTracker.trackChange(relativePath, oldContent, '');
// 删除文件 // 删除文件
fs.unlinkSync(absolutePath); fs.unlinkSync(absolutePath);

View File

@ -121,49 +121,68 @@ export function getDiffStyles(): string {
return ` return `
.diff-viewer { .diff-viewer {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px; font-size: 13px;
line-height: 1.5; line-height: 1.6;
background: var(--vscode-editor-background); background: var(--vscode-editor-background);
border: 1px solid var(--vscode-panel-border); border: 1px solid var(--vscode-panel-border);
border-radius: 4px; border-radius: 6px;
overflow-x: auto; overflow: hidden;
} }
.diff-line { .diff-line {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 2px 0; padding: 4px 0;
white-space: pre; white-space: pre;
transition: background 0.15s;
}
.diff-line:hover {
background: var(--vscode-list-hoverBackground);
} }
.diff-line-add { .diff-line-add {
background: rgba(40, 167, 69, 0.15); background: rgba(40, 167, 69, 0.2);
border-left: 3px solid #28a745;
}
.diff-line-add:hover {
background: rgba(40, 167, 69, 0.25);
} }
.diff-line-remove { .diff-line-remove {
background: rgba(220, 53, 69, 0.15); background: rgba(220, 53, 69, 0.2);
border-left: 3px solid #dc3545;
}
.diff-line-remove:hover {
background: rgba(220, 53, 69, 0.25);
} }
.diff-line-context { .diff-line-context {
background: transparent; background: transparent;
border-left: 3px solid transparent;
} }
.line-num { .line-num {
display: inline-block; display: inline-block;
width: 40px; width: 45px;
text-align: right; text-align: right;
padding: 0 8px; padding: 0 10px;
color: var(--vscode-editorLineNumber-foreground); color: var(--vscode-editorLineNumber-foreground);
user-select: none; user-select: none;
flex-shrink: 0; flex-shrink: 0;
font-size: 11px;
opacity: 0.7;
} }
.line-prefix { .line-prefix {
display: inline-block; display: inline-block;
width: 20px; width: 24px;
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
flex-shrink: 0; flex-shrink: 0;
font-size: 14px;
} }
.diff-line-add .line-prefix { .diff-line-add .line-prefix {
@ -176,7 +195,7 @@ export function getDiffStyles(): string {
.line-content { .line-content {
flex: 1; flex: 1;
padding-right: 8px; padding: 0 12px 0 8px;
overflow-x: auto; overflow-x: auto;
} }
`; `;

View File

@ -5,7 +5,7 @@
* 使用场景:对话结束后展示代码变更供用户审查 * 使用场景:对话结束后展示代码变更供用户审查
*/ */
import { getDiffStyles } from '../utils/diffRenderer'; import { getDiffStyles } from "../utils/diffRenderer";
/** /**
* 获取变更面板的 HTML 内容 * 获取变更面板的 HTML 内容
@ -13,15 +13,22 @@ import { getDiffStyles } from '../utils/diffRenderer';
export function getChangePanelContent(): string { export function getChangePanelContent(): string {
return ` return `
<div class="change-panel" id="changePanel" style="display: none;"> <div class="change-panel" id="changePanel" style="display: none;">
<div class="change-panel-header"> <div class="change-panel-header" onclick="toggleChangePanel()">
<div class="change-panel-title"> <div class="change-panel-title">
<span class="change-icon">📝</span>
<span>代码变更</span> <span>代码变更</span>
<span class="change-count" id="changeCount">0</span> <span class="change-count" id="changeCount">0</span>
</div> </div>
<button class="change-toggle-btn" id="changePanelToggle" onclick="toggleChangePanel()"> <div class="change-panel-actions" onclick="event.stopPropagation()">
<span class="toggle-icon">▼</span> <button class="batch-action-btn accept-all-btn" onclick="acceptAllChanges()" title="采纳全部">
</button> <span>✓ 全部采纳</span>
</button>
<button class="batch-action-btn reject-all-btn" onclick="rejectAllChanges()" title="拒绝全部">
<span>✕ 全部拒绝</span>
</button>
<button class="change-toggle-btn" id="changePanelToggle">
<span class="toggle-icon">▼</span>
</button>
</div>
</div> </div>
<div class="change-panel-body" id="changePanelBody" style="display: none;"> <div class="change-panel-body" id="changePanelBody" style="display: none;">
<div class="change-list" id="changeList"> <div class="change-list" id="changeList">
@ -49,10 +56,11 @@ export function getChangePanelStyles(): string {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 12px; padding: 6px 16px;
background: var(--vscode-sideBar-background); background: var(--vscode-sideBar-background);
cursor: pointer;
user-select: none; user-select: none;
border-bottom: 2px solid var(--vscode-panel-border);
cursor: pointer;
} }
.change-panel-header:hover { .change-panel-header:hover {
@ -62,22 +70,66 @@ export function getChangePanelStyles(): string {
.change-panel-title { .change-panel-title {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 10px;
font-size: 13px; font-size: 14px;
font-weight: 500; font-weight: 600;
} }
.change-icon { .change-icon {
font-size: 16px; font-size: 18px;
} }
.change-count { .change-count {
background: var(--vscode-badge-background); background: var(--vscode-badge-background);
color: var(--vscode-badge-foreground); color: var(--vscode-badge-foreground);
padding: 2px 8px; padding: 3px 10px;
border-radius: 10px; border-radius: 12px;
font-size: 11px; font-size: 12px;
font-weight: 600; font-weight: 700;
min-width: 24px;
text-align: center;
}
.change-panel-actions {
display: flex;
align-items: center;
gap: 8px;
}
.batch-action-btn {
padding: 6px 12px;
border: none;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 4px;
}
.batch-action-btn:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.accept-all-btn {
background: #28a745;
color: white;
}
.accept-all-btn:hover {
background: #218838;
}
.reject-all-btn {
background: #dc3545;
color: white;
}
.reject-all-btn:hover {
background: #c82333;
} }
.change-toggle-btn { .change-toggle-btn {
@ -110,24 +162,31 @@ export function getChangePanelStyles(): string {
} }
.change-list { .change-list {
padding: 8px; padding: 0px;
} }
.change-item { .change-item {
border: 1px solid var(--vscode-panel-border); border: 1px solid var(--vscode-panel-border);
border-radius: 4px; border-radius: 6px;
margin-bottom: 8px; margin-bottom: 10px;
overflow: hidden; overflow: hidden;
background: var(--vscode-editor-background); background: var(--vscode-editor-background);
transition: all 0.2s;
}
.change-item:hover {
border-color: var(--vscode-focusBorder);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
} }
.change-item-header { .change-item-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 8px 12px; padding: 6px 16px;
background: var(--vscode-sideBar-background); background: var(--vscode-sideBar-background);
cursor: pointer; cursor: pointer;
transition: background 0.2s;
} }
.change-item-header:hover { .change-item-header:hover {
@ -143,11 +202,12 @@ export function getChangePanelStyles(): string {
} }
.change-type-badge { .change-type-badge {
padding: 2px 6px; padding: 4px 10px;
border-radius: 3px; border-radius: 4px;
font-size: 10px; font-size: 11px;
font-weight: 600; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px;
} }
.change-type-create { .change-type-create {
@ -157,7 +217,7 @@ export function getChangePanelStyles(): string {
.change-type-modify { .change-type-modify {
background: #ffc107; background: #ffc107;
color: black; color: #000;
} }
.change-type-delete { .change-type-delete {
@ -166,7 +226,8 @@ export function getChangePanelStyles(): string {
} }
.change-file-path { .change-file-path {
font-size: 12px; font-size: 13px;
font-weight: 500;
color: var(--vscode-foreground); color: var(--vscode-foreground);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -179,16 +240,18 @@ export function getChangePanelStyles(): string {
} }
.change-action-btn { .change-action-btn {
padding: 4px 10px; padding: 6px 14px;
border: none; border: none;
border-radius: 3px; border-radius: 4px;
font-size: 11px; font-size: 12px;
font-weight: 500;
cursor: pointer; cursor: pointer;
transition: opacity 0.2s; transition: all 0.2s;
} }
.change-action-btn:hover { .change-action-btn:hover {
opacity: 0.8; transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
} }
.accept-btn { .accept-btn {
@ -196,11 +259,19 @@ export function getChangePanelStyles(): string {
color: white; color: white;
} }
.accept-btn:hover {
background: #218838;
}
.reject-btn { .reject-btn {
background: #dc3545; background: #dc3545;
color: white; color: white;
} }
.reject-btn:hover {
background: #c82333;
}
.change-item-diff { .change-item-diff {
padding: 12px; padding: 12px;
background: var(--vscode-editor-background); background: var(--vscode-editor-background);
@ -237,6 +308,58 @@ export function getChangePanelScript(): string {
} }
} }
// 全部采纳
window.acceptAllChanges = function() {
const changeList = document.getElementById('changeList');
if (!changeList) {
console.error('changeList not found');
return;
}
const items = Array.from(changeList.querySelectorAll('.change-item'));
console.log('Found items:', items.length);
if (items.length === 0) {
alert('没有待处理的变更');
return;
}
items.forEach(item => {
const changeId = item.id.replace('change-item-', '');
console.log('Accepting change:', changeId);
vscode.postMessage({
command: 'acceptChange',
changeId: changeId
});
});
};
// 全部拒绝
window.rejectAllChanges = function() {
const changeList = document.getElementById('changeList');
if (!changeList) {
console.error('changeList not found');
return;
}
const items = Array.from(changeList.querySelectorAll('.change-item'));
console.log('Found items:', items.length);
if (items.length === 0) {
alert('没有待处理的变更');
return;
}
items.forEach(item => {
const changeId = item.id.replace('change-item-', '');
console.log('Rejecting change:', changeId);
vscode.postMessage({
command: 'rejectChange',
changeId: changeId
});
});
};
// 打开文件 diff在 VS Code 中打开) // 打开文件 diff在 VS Code 中打开)
function openFileDiff(changeId) { function openFileDiff(changeId) {
vscode.postMessage({ vscode.postMessage({