feat: 优化代码变更面板样式和交互
- 优化变更面板和 diff 视图样式 - 新增全部采纳和全部拒绝按钮 - 修复删除文件的变更追踪和采纳逻辑 - 整个标题栏可点击展开/收起 - 增强视觉效果和用户体验
This commit is contained in:
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user