Skip to content

Commit 5c49fae

Browse files
authored
Merge pull request #4767 from VisActor/develop
release
2 parents d112448 + 271c239 commit 5c49fae

25 files changed

Lines changed: 2273 additions & 194 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "feat: copy formula to paste cell\n\n",
5+
"type": "none",
6+
"packageName": "@visactor/vtable"
7+
}
8+
],
9+
"packageName": "@visactor/vtable",
10+
"email": "892739385@qq.com"
11+
}

docs/assets/guide/en/sheet/formula.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,23 @@ Cell reference input and range selection:
115115
<img src="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/guide/formula-drag-cellRange.gif" />
116116
</div>
117117

118-
### Formula Copying (TODO)
118+
### Formula Copying
119119

120120
When copying cells containing formulas, references are automatically adjusted:
121121
- Relative references adjust according to position offset
122122
- Absolute references remain unchanged
123123

124+
<div style="width: 30%; text-align: center;">
125+
<img src="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/guide/formula-copy.gif" />
126+
</div>
127+
128+
### Formula Auto Filling
129+
130+
When a region is selected and contains formulas, the formula is automatically filled using the fill handle.
131+
<div style="width: 30%; text-align: center;">
132+
<img src="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/guide/formula-autoFill.gif" />
133+
</div>
134+
124135
## 6. Advanced Features
125136

126137
### Multi-Sheet Support (TODO)

docs/assets/guide/zh/sheet/formula.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,21 @@ VTableSheet 自身开发了 FormulaEngine 模块 作为核心的计算引擎:
114114
<img src="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/guide/formula-drag-cellRange.gif" />
115115
</div>
116116

117-
### 公式复制(TODO)
117+
### 公式复制
118118

119119
当复制包含公式的单元格时,引用会自动调整:
120120
- 相对引用会根据位置偏移调整
121121
- 绝对引用保持不变
122+
<div style="width: 30%; text-align: center;">
123+
<img src="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/guide/formula-copy.gif" />
124+
</div>
125+
126+
### 填充柄自动填充
122127

128+
当选中区域存在公式的时候,利用填充柄拖拽自动填充单元格的公式。
129+
<div style="width:30%; text-align: center;">
130+
<img src="https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/guide/formula-autoFill.gif" />
131+
</div>
123132
## 6. 高级功能
124133

125134
### 多工作表支持(TODO)

packages/vtable-plugins/src/excel-edit-cell-keyboard.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class ExcelEditCellKeyboardPlugin implements pluginsDefinition.IVTablePlu
7070
}
7171
handleKeyDown(event: KeyboardEvent) {
7272
// this.pluginOptions?.keyDown_before?.(event);
73-
if (this.table.editorManager && this.isExcelShortcutKey(event)) {
73+
if (this.table?.editorManager && this.isExcelShortcutKey(event)) {
7474
const eventKey = event.key.toLowerCase() as ExcelEditCellKeyboardResponse;
7575
//判断是键盘触发编辑单元格的情况下,那么在编辑状态中切换方向需要选中下一个继续编辑
7676
if (this.table.editorManager.editingEditor && this.table.editorManager.beginTriggerEditCellMode === 'keydown') {
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* 复制源位置记录测试
3+
* 验证复制时记录的源位置是否正确用于粘贴时的公式调整
4+
*/
5+
6+
describe('复制源位置记录测试', () => {
7+
it('应该正确记录复制时的源位置', () => {
8+
// 模拟EventManager的行为
9+
const mockEventManager = {
10+
copySourceRange: null as { startCol: number; startRow: number } | null,
11+
12+
// 模拟handleCopy中记录源位置的逻辑
13+
recordCopySourceRange(ranges: any[]) {
14+
if (ranges && ranges.length > 0) {
15+
const sourceRange = ranges[0];
16+
this.copySourceRange = {
17+
startCol: Math.min(sourceRange.start.col, sourceRange.end.col),
18+
startRow: Math.min(sourceRange.start.row, sourceRange.end.row)
19+
};
20+
}
21+
},
22+
23+
// 模拟粘贴时获取源位置的逻辑
24+
getCopySourcePosition() {
25+
return this.copySourceRange;
26+
},
27+
28+
// 模拟清除复制源位置的逻辑
29+
clearCopySourceRange() {
30+
this.copySourceRange = null;
31+
}
32+
};
33+
34+
// 测试1:复制C5时的位置记录
35+
const copyRanges1 = [
36+
{
37+
start: { col: 2, row: 4 }, // C5
38+
end: { col: 2, row: 4 }
39+
}
40+
];
41+
42+
mockEventManager.recordCopySourceRange(copyRanges1);
43+
44+
console.log('复制C5后的源位置:', mockEventManager.getCopySourcePosition());
45+
expect(mockEventManager.getCopySourcePosition()).toEqual({
46+
startCol: 2, // C列
47+
startRow: 4 // 第5行
48+
});
49+
50+
// 测试2:复制A1:B2区域时的位置记录
51+
const copyRanges2 = [
52+
{
53+
start: { col: 0, row: 0 }, // A1
54+
end: { col: 1, row: 1 } // B2
55+
}
56+
];
57+
58+
mockEventManager.recordCopySourceRange(copyRanges2);
59+
60+
console.log('复制A1:B2后的源位置:', mockEventManager.getCopySourcePosition());
61+
expect(mockEventManager.getCopySourcePosition()).toEqual({
62+
startCol: 0, // A列
63+
startRow: 0 // 第1行
64+
});
65+
66+
// 测试3:清除源位置
67+
mockEventManager.clearCopySourceRange();
68+
console.log('清除后的源位置:', mockEventManager.getCopySourcePosition());
69+
expect(mockEventManager.getCopySourcePosition()).toBeNull();
70+
});
71+
72+
it('应该正确处理相对位置计算', () => {
73+
// 模拟processFormulaPaste的逻辑
74+
const calculateRelativeOffset = (
75+
sourceStartCol: number,
76+
sourceStartRow: number,
77+
targetStartCol: number,
78+
targetStartRow: number
79+
) => {
80+
return {
81+
colOffset: targetStartCol - sourceStartCol,
82+
rowOffset: targetStartRow - sourceStartRow
83+
};
84+
};
85+
86+
// 测试场景:C5复制到D5
87+
const sourcePos = { startCol: 2, startRow: 4 }; // C5
88+
const targetPos = { startCol: 3, startRow: 4 }; // D5
89+
90+
const offset = calculateRelativeOffset(
91+
sourcePos.startCol,
92+
sourcePos.startRow,
93+
targetPos.startCol,
94+
targetPos.startRow
95+
);
96+
97+
console.log('C5->D5的相对偏移:', offset);
98+
expect(offset).toEqual({
99+
colOffset: 1, // 右移1列
100+
rowOffset: 0 // 行不变
101+
});
102+
103+
// 测试场景:A1复制到C3
104+
const offset2 = calculateRelativeOffset(0, 0, 2, 2);
105+
106+
console.log('A1->C3的相对偏移:', offset2);
107+
expect(offset2).toEqual({
108+
colOffset: 2, // 右移2列
109+
rowOffset: 2 // 下移2行
110+
});
111+
});
112+
113+
it('应该验证完整的复制粘贴流程', () => {
114+
// 完整的流程测试
115+
const mockWorkSheet = {
116+
copySourceRange: null as { startCol: number; startRow: number } | null,
117+
118+
// 复制时记录源位置
119+
handleCopy(ranges: any[]) {
120+
if (ranges && ranges.length > 0) {
121+
const sourceRange = ranges[0];
122+
this.copySourceRange = {
123+
startCol: Math.min(sourceRange.start.col, sourceRange.end.col),
124+
startRow: Math.min(sourceRange.start.row, sourceRange.end.row)
125+
};
126+
}
127+
},
128+
129+
// 粘贴时使用记录的源位置
130+
processFormulaPaste(formulas: string[][], targetStartCol: number, targetStartRow: number): string[][] {
131+
if (!this.copySourceRange) {
132+
return formulas; // 没有源位置,不处理
133+
}
134+
135+
const colOffset = targetStartCol - this.copySourceRange.startCol;
136+
const rowOffset = targetStartRow - this.copySourceRange.startRow;
137+
138+
return formulas.map(row =>
139+
row.map(cell => {
140+
if (cell.startsWith('=')) {
141+
return cell.replace(/([A-Z]+)(\d+)/g, (match, col, row) => {
142+
const colNum = col.charCodeAt(0) - 'A'.charCodeAt(0) + colOffset;
143+
const rowNum = parseInt(row, 10) + rowOffset;
144+
return String.fromCharCode('A'.charCodeAt(0) + colNum) + rowNum;
145+
});
146+
}
147+
return cell;
148+
})
149+
);
150+
}
151+
};
152+
153+
// 步骤1:复制C5(公式=A2)
154+
mockWorkSheet.handleCopy([
155+
{
156+
start: { col: 2, row: 4 }, // C5
157+
end: { col: 2, row: 4 }
158+
}
159+
]);
160+
161+
// 步骤2:粘贴到D5
162+
const sourceFormula = [['=A2']];
163+
const result = mockWorkSheet.processFormulaPaste(sourceFormula, 3, 4); // D5
164+
165+
console.log('完整流程结果:', result);
166+
expect(result).toEqual([['=B2']]); // C5的=A2粘贴到D5应该变成=B2
167+
});
168+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { FormulaPasteProcessor } from '../src/formula/formula-paste-processor';
2+
import { FormulaReferenceAdjustor } from '../src/formula/formula-reference-adjustor';
3+
4+
describe('Debug Formula Paste', () => {
5+
it('should debug formula adjustment', () => {
6+
const originalFormula = '=A1+B1';
7+
const colOffset = 1; // 右移1列
8+
const rowOffset = 1; // 下移1行
9+
10+
console.log('Original formula:', originalFormula);
11+
console.log('Column offset:', colOffset);
12+
console.log('Row offset:', rowOffset);
13+
14+
const adjustedFormula = FormulaReferenceAdjustor.adjustFormulaReferences(originalFormula, colOffset, rowOffset);
15+
16+
console.log('Adjusted formula:', adjustedFormula);
17+
expect(adjustedFormula).toBe('=B2+C2');
18+
});
19+
20+
it('should debug batch formula adjustment', () => {
21+
const formulas = [
22+
['=A1', '=B1'],
23+
['=A2', '=B2']
24+
];
25+
const colOffset = 2;
26+
const rowOffset = 1;
27+
28+
console.log('Original formulas:', formulas);
29+
30+
const adjustedFormulas = FormulaPasteProcessor.adjustFormulasForPasteWithOffset(formulas, colOffset, rowOffset);
31+
32+
console.log('Adjusted formulas:', adjustedFormulas);
33+
expect(adjustedFormulas).toEqual([
34+
['=C2', '=D2'],
35+
['=C3', '=D3']
36+
]);
37+
});
38+
});

0 commit comments

Comments
 (0)