mirror of
https://github.com/larksuite/cli.git
synced 2026-08-03 08:32:46 +08:00
feat(sheets): +dim-freeze --rows/--cols for both axes in one call
Freeze is full-state replacement server-side, so --dimension/--count unfreezes the axis it does not name. Two calls cannot hold both axes, and inside +batch-update there was no way at all: sub-ops are a static array that cannot read the current state, and +styles-put is not batchable. --rows/--cols state the complete freeze state in one operation, so the standalone command and the batch sub-op keep producing identical bodies. The legacy pair still works and prints the exact --rows/--cols equivalent on use, but is hidden from --help and from the skill docs.
This commit is contained in:
@@ -93,6 +93,15 @@ func TestBatchOp_BodyMatchesStandalone(t *testing.T) {
|
||||
args: []string{"--sheet-id", "sh1", "--dimension", "row", "--count", "2"},
|
||||
subInput: `{"sheet-id":"sh1","dimension":"row","count":2}`,
|
||||
},
|
||||
{
|
||||
// The both-axes form has to hold inside a batch too: it is the only
|
||||
// way to freeze rows AND columns there, since +styles-put (the other
|
||||
// carrier of a combined freeze) is not a batchable sub-op.
|
||||
shortcut: "+dim-freeze",
|
||||
sc: DimFreeze,
|
||||
args: []string{"--sheet-id", "sh1", "--rows", "1", "--cols", "2"},
|
||||
subInput: `{"sheet-id":"sh1","rows":1,"cols":2}`,
|
||||
},
|
||||
{
|
||||
shortcut: "+dim-group",
|
||||
sc: DimGroup,
|
||||
|
||||
@@ -1044,8 +1044,9 @@
|
||||
"name": "dimension",
|
||||
"kind": "own",
|
||||
"type": "string",
|
||||
"required": "required",
|
||||
"desc": "Dimension (row or column)",
|
||||
"required": "optional",
|
||||
"desc": "[legacy] Dimension (row or column), paired with --count; sets one axis only and unfreezes the other. Prefer --rows / --cols",
|
||||
"hidden": true,
|
||||
"enum": [
|
||||
"row",
|
||||
"column"
|
||||
@@ -1055,8 +1056,23 @@
|
||||
"name": "count",
|
||||
"kind": "own",
|
||||
"type": "int",
|
||||
"required": "required",
|
||||
"desc": "Freeze the first N rows/columns; pass 0 to unfreeze"
|
||||
"required": "optional",
|
||||
"desc": "[legacy] Freeze the first N rows/columns (paired with --dimension); 0 clears all freezing. Equivalent to --rows N / --cols N, and only --rows/--cols can hold both axes at once",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rows",
|
||||
"kind": "own",
|
||||
"type": "int",
|
||||
"required": "optional",
|
||||
"desc": "Freeze the first N rows; together with --cols this states the COMPLETE freeze state — an omitted axis is left unfrozen (0 means no frozen rows)"
|
||||
},
|
||||
{
|
||||
"name": "cols",
|
||||
"kind": "own",
|
||||
"type": "int",
|
||||
"required": "optional",
|
||||
"desc": "Freeze the first N columns; together with --rows this states the COMPLETE freeze state — an omitted axis is left unfrozen (0 means no frozen columns)"
|
||||
},
|
||||
{
|
||||
"name": "dry-run",
|
||||
|
||||
@@ -361,8 +361,10 @@ var flagDefs = map[string]commandDef{
|
||||
{Name: "spreadsheet-token", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet token (XOR with `--url`)"},
|
||||
{Name: "sheet-id", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet reference_id — required: pass this or `--sheet-name` (exactly one of the two)"},
|
||||
{Name: "sheet-name", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet name — required: pass this or `--sheet-id` (exactly one of the two)"},
|
||||
{Name: "dimension", Kind: "own", Type: "string", Required: "required", Desc: "Dimension (row or column)", Enum: []string{"row", "column"}},
|
||||
{Name: "count", Kind: "own", Type: "int", Required: "required", Desc: "Freeze the first N rows/columns; pass 0 to unfreeze"},
|
||||
{Name: "dimension", Kind: "own", Type: "string", Required: "optional", Desc: "[legacy] Dimension (row or column), paired with --count; sets one axis only and unfreezes the other. Prefer --rows / --cols", Hidden: true, Enum: []string{"row", "column"}},
|
||||
{Name: "count", Kind: "own", Type: "int", Required: "optional", Desc: "[legacy] Freeze the first N rows/columns (paired with --dimension); 0 clears all freezing. Equivalent to --rows N / --cols N, and only --rows/--cols can hold both axes at once", Hidden: true},
|
||||
{Name: "rows", Kind: "own", Type: "int", Required: "optional", Desc: "Freeze the first N rows; together with --cols this states the COMPLETE freeze state — an omitted axis is left unfrozen (0 means no frozen rows)"},
|
||||
{Name: "cols", Kind: "own", Type: "int", Required: "optional", Desc: "Freeze the first N columns; together with --rows this states the COMPLETE freeze state — an omitted axis is left unfrozen (0 means no frozen columns)"},
|
||||
{Name: "dry-run", Kind: "system", Type: "bool", Required: "optional"},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -438,19 +438,23 @@ var DimUngroup = newDimGroupShortcut(
|
||||
"+dim-ungroup", "Remove a row/column outline group.", "ungroup",
|
||||
)
|
||||
|
||||
// DimFreeze freezes the first N rows or columns; --count 0 unfreezes that
|
||||
// dimension.
|
||||
// DimFreeze sets the sheet's freeze state. Freeze is full-state replacement
|
||||
// server-side (verified 07-31 live), so every call states the WHOLE state:
|
||||
// --rows/--cols name both axes at once, while the older --dimension/--count
|
||||
// pair can only name one and therefore unfreezes the other.
|
||||
var DimFreeze = common.Shortcut{
|
||||
Service: "sheets",
|
||||
Command: "+dim-freeze",
|
||||
Description: "Freeze the first N rows or columns; --count 0 unfreezes the chosen dimension.",
|
||||
Description: "Freeze the first N rows and/or columns; this sets the whole freeze state, so an axis you do not name ends up unfrozen.",
|
||||
Risk: "write",
|
||||
Scopes: []string{"sheets:spreadsheet:write_only"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
HasFormat: true,
|
||||
Flags: flagsFor("+dim-freeze"),
|
||||
Tips: []string{
|
||||
"Example: lark-cli sheets +dim-freeze --url <URL> --sheet-name Sheet1 --dimension row --count 2 (freezes the first 2 rows; --count 0 unfreezes)",
|
||||
"Example: lark-cli sheets +dim-freeze --url <URL> --sheet-name Sheet1 --rows 1 --cols 2 (holds the header row and the first 2 columns in one call)",
|
||||
"Freezing is not additive: --dimension row --count 1 followed by --dimension column --count 2 leaves ONLY the columns frozen. Pass --rows/--cols together instead of calling twice",
|
||||
"To unfreeze one axis but keep the other, state the survivor: --rows 0 --cols 2. Bare --count 0 clears both",
|
||||
},
|
||||
Validate: validateViaInput(dimFreezeInput),
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
@@ -472,6 +476,23 @@ var DimFreeze = common.Shortcut{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// DEPRECATED(phase-2): +dim-freeze --dimension / --count — replaced by
|
||||
// --rows / --cols. Phase 1 (here): the flags keep working, are retired
|
||||
// from the skill docs via bundle.json doc_hidden_flags in
|
||||
// sheet-skill-spec, and every use prints the exact replacement below.
|
||||
// Phase 2 removal: drop both rows from spec-tables/flags.json + their
|
||||
// doc_hidden_flags entry, then this block, dimFreezeEquivalent, and the
|
||||
// legacy branch in dimFreezeInput.
|
||||
//
|
||||
// The pair is a strict subset of --rows/--cols — every
|
||||
// --dimension/--count call has a byte-identical --rows/--cols spelling
|
||||
// (TestDimFreezeEquivalent pins this) — and it is the form that reads as
|
||||
// if it scoped to one axis when the backend replaces the whole state.
|
||||
if runtime.Changed("dimension") || runtime.Changed("count") {
|
||||
fmt.Fprintf(runtime.IO().ErrOut,
|
||||
"note: --dimension/--count is superseded by --rows/--cols, which state both axes at once; this call is equivalent to %s\n",
|
||||
dimFreezeEquivalent(runtime))
|
||||
}
|
||||
out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_sheet_structure", input)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -481,33 +502,88 @@ var DimFreeze = common.Shortcut{
|
||||
},
|
||||
}
|
||||
|
||||
// dimFreezeEquivalent renders the --rows/--cols spelling of a legacy
|
||||
// --dimension/--count call, so the deprecation note carries the exact
|
||||
// replacement instead of a generic pointer.
|
||||
func dimFreezeEquivalent(runtime flagView) string {
|
||||
count := runtime.Int("count")
|
||||
if count == 0 {
|
||||
return "--rows 0 --cols 0"
|
||||
}
|
||||
if runtime.Str("dimension") == "row" {
|
||||
return fmt.Sprintf("--rows %d", count)
|
||||
}
|
||||
return fmt.Sprintf("--cols %d", count)
|
||||
}
|
||||
|
||||
// dimFreezeInput builds the freeze body for both the standalone shortcut and
|
||||
// the +batch-update sub-op, so the two stay byte-identical (see
|
||||
// TestBatchOp_BodyMatchesStandalone).
|
||||
//
|
||||
// Two request forms, deliberately not mixable:
|
||||
//
|
||||
// - --rows / --cols state the complete target state in ONE operation. This
|
||||
// is the only form that can hold both axes, because freeze is full-state
|
||||
// replacement server-side (verified 07-31 live: freeze rows=1 then
|
||||
// columns=2 in two calls ends at 0 rows / 2 columns — the second call
|
||||
// drops the first axis). It is also the only form usable inside
|
||||
// +batch-update, whose sub-ops are a static array that cannot read the
|
||||
// current state to preserve an axis.
|
||||
// - --dimension + --count is the original single-axis form, kept for
|
||||
// compatibility. It necessarily unfreezes the axis it does not name.
|
||||
func dimFreezeInput(runtime flagView, token, sheetID, sheetName string) (map[string]interface{}, error) {
|
||||
if err := requireSheetSelector(sheetID, sheetName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !runtime.Changed("dimension") {
|
||||
return nil, sheetsValidationForFlag("dimension", "--dimension is required")
|
||||
pairForm := runtime.Changed("dimension") || runtime.Changed("count")
|
||||
axisForm := runtime.Changed("rows") || runtime.Changed("cols")
|
||||
switch {
|
||||
case pairForm && axisForm:
|
||||
return nil, sheetsValidationForFlag("rows",
|
||||
"give either --rows/--cols or --dimension/--count, not both — they are two ways to say the same thing; --rows/--cols is the one that can hold both axes at once")
|
||||
case !pairForm && !axisForm:
|
||||
return nil, sheetsValidationForFlag("rows",
|
||||
"nothing to freeze: pass --rows N and/or --cols N (e.g. --rows 1 --cols 2 holds the header row and the first 2 columns), or the single-axis --dimension row --count 1")
|
||||
}
|
||||
if !runtime.Changed("count") {
|
||||
return nil, sheetsValidationForFlag("count", "--count is required (0 unfreezes)")
|
||||
}
|
||||
if runtime.Int("count") < 0 {
|
||||
return nil, sheetsValidationForFlag("count", "--count must be >= 0")
|
||||
}
|
||||
dim := runtime.Str("dimension")
|
||||
count := runtime.Int("count")
|
||||
op := "freeze"
|
||||
if count == 0 {
|
||||
op = "unfreeze"
|
||||
}
|
||||
input := map[string]interface{}{"excel_id": token, "operation": op}
|
||||
sheetSelectorForToolInput(input, sheetID, sheetName)
|
||||
if op == "freeze" {
|
||||
if dim == "row" {
|
||||
input["freeze_rows"] = count
|
||||
} else {
|
||||
input["freeze_columns"] = count
|
||||
|
||||
rows, cols := 0, 0
|
||||
if axisForm {
|
||||
for _, name := range []string{"rows", "cols"} {
|
||||
if runtime.Changed(name) && runtime.Int(name) < 0 {
|
||||
return nil, sheetsValidationForFlag(name, "--%s must be >= 0 (0 leaves that axis unfrozen)", name)
|
||||
}
|
||||
}
|
||||
rows, cols = runtime.Int("rows"), runtime.Int("cols")
|
||||
} else {
|
||||
if !runtime.Changed("dimension") {
|
||||
return nil, sheetsValidationForFlag("dimension", "--dimension is required alongside --count (or use --rows/--cols to set both axes at once)")
|
||||
}
|
||||
if !runtime.Changed("count") {
|
||||
return nil, sheetsValidationForFlag("count", "--count is required alongside --dimension (0 unfreezes)")
|
||||
}
|
||||
if runtime.Int("count") < 0 {
|
||||
return nil, sheetsValidationForFlag("count", "--count must be >= 0")
|
||||
}
|
||||
if runtime.Str("dimension") == "row" {
|
||||
rows = runtime.Int("count")
|
||||
} else {
|
||||
cols = runtime.Int("count")
|
||||
}
|
||||
}
|
||||
|
||||
// An all-zero target is the bare "unfreeze" operation, which carries no
|
||||
// dimension and clears everything — the same request the old --count 0
|
||||
// always sent.
|
||||
input := map[string]interface{}{"excel_id": token, "operation": "unfreeze"}
|
||||
if rows > 0 || cols > 0 {
|
||||
input["operation"] = "freeze"
|
||||
}
|
||||
sheetSelectorForToolInput(input, sheetID, sheetName)
|
||||
if rows > 0 {
|
||||
input["freeze_rows"] = rows
|
||||
}
|
||||
if cols > 0 {
|
||||
input["freeze_columns"] = cols
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package sheets
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -135,6 +136,47 @@ func TestSheetStructureShortcuts_DryRun(t *testing.T) {
|
||||
"sheet_id": testSheetID,
|
||||
},
|
||||
},
|
||||
{
|
||||
// The whole point of --rows/--cols: both axes in ONE operation.
|
||||
// Two single-axis calls would leave only the last axis frozen,
|
||||
// because freeze is full-state replacement server-side.
|
||||
name: "+dim-freeze --rows 1 --cols 2 → one combined op",
|
||||
sc: DimFreeze,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--rows", "1", "--cols", "2"},
|
||||
toolName: "modify_sheet_structure",
|
||||
wantInput: map[string]interface{}{
|
||||
"excel_id": testToken,
|
||||
"operation": "freeze",
|
||||
"sheet_id": testSheetID,
|
||||
"freeze_rows": float64(1),
|
||||
"freeze_columns": float64(2),
|
||||
},
|
||||
},
|
||||
{
|
||||
// Stating the survivor is how you unfreeze one axis and keep the
|
||||
// other; a zero axis is simply omitted from the body.
|
||||
name: "+dim-freeze --rows 0 --cols 2 → columns only",
|
||||
sc: DimFreeze,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--rows", "0", "--cols", "2"},
|
||||
toolName: "modify_sheet_structure",
|
||||
wantInput: map[string]interface{}{
|
||||
"excel_id": testToken,
|
||||
"operation": "freeze",
|
||||
"sheet_id": testSheetID,
|
||||
"freeze_columns": float64(2),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "+dim-freeze --rows 0 --cols 0 → unfreeze",
|
||||
sc: DimFreeze,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--rows", "0", "--cols", "0"},
|
||||
toolName: "modify_sheet_structure",
|
||||
wantInput: map[string]interface{}{
|
||||
"excel_id": testToken,
|
||||
"operation": "unfreeze",
|
||||
"sheet_id": testSheetID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "+dim-group row 1:5 fold",
|
||||
sc: DimGroup,
|
||||
@@ -293,6 +335,103 @@ func TestDimRange_Validation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestDimFreezeEquivalent pins the replacement spelling printed by the
|
||||
// phase-1 deprecation note: it must be the exact --rows/--cols call the user
|
||||
// should switch to, not a generic pointer. Each pairing is also asserted for
|
||||
// body equality, which is what makes the legacy form strictly redundant.
|
||||
func TestDimFreezeEquivalent(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
dimension string
|
||||
count int
|
||||
want string
|
||||
}{
|
||||
{"row", 2, "--rows 2"},
|
||||
{"column", 3, "--cols 3"},
|
||||
{"row", 0, "--rows 0 --cols 0"},
|
||||
{"column", 0, "--rows 0 --cols 0"},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
legacy := newMapFlagViewForCommand("+dim-freeze", map[string]interface{}{
|
||||
"dimension": tt.dimension, "count": tt.count,
|
||||
})
|
||||
if got := dimFreezeEquivalent(legacy); got != tt.want {
|
||||
t.Fatalf("dimFreezeEquivalent = %q, want %q", got, tt.want)
|
||||
}
|
||||
// The advertised replacement must produce the identical body.
|
||||
modern := map[string]interface{}{}
|
||||
if tt.count > 0 {
|
||||
if tt.dimension == "row" {
|
||||
modern["rows"] = tt.count
|
||||
} else {
|
||||
modern["cols"] = tt.count
|
||||
}
|
||||
} else {
|
||||
modern["rows"], modern["cols"] = 0, 0
|
||||
}
|
||||
legacyInput, err := dimFreezeInput(legacy, testToken, testSheetID, "")
|
||||
if err != nil {
|
||||
t.Fatalf("legacy form: %v", err)
|
||||
}
|
||||
modernInput, err := dimFreezeInput(newMapFlagViewForCommand("+dim-freeze", modern), testToken, testSheetID, "")
|
||||
if err != nil {
|
||||
t.Fatalf("modern form: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(legacyInput, modernInput) {
|
||||
t.Fatalf("bodies diverge:\n legacy = %v\n modern = %v", legacyInput, modernInput)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDimFreeze_FormValidation pins the two request forms as mutually
|
||||
// exclusive, and pins that neither-form is a prescriptive error rather than a
|
||||
// silent no-op.
|
||||
func TestDimFreeze_FormValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "forms cannot be mixed",
|
||||
args: []string{"--rows", "1", "--dimension", "row", "--count", "1"},
|
||||
want: "not both",
|
||||
},
|
||||
{
|
||||
name: "neither form given",
|
||||
args: []string{},
|
||||
want: "nothing to freeze",
|
||||
},
|
||||
{
|
||||
name: "negative rows",
|
||||
args: []string{"--rows", "-1"},
|
||||
want: "--rows must be >= 0",
|
||||
},
|
||||
{
|
||||
name: "count without dimension",
|
||||
args: []string{"--count", "2"},
|
||||
want: "--dimension is required alongside --count",
|
||||
},
|
||||
{
|
||||
name: "dimension without count",
|
||||
args: []string{"--dimension", "row"},
|
||||
want: "--count is required alongside --dimension",
|
||||
},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
args := append([]string{"--url", testURL, "--sheet-id", testSheetID, "--dry-run"}, tt.args...)
|
||||
_, _, err := runShortcutCapturingErr(t, DimFreeze, args)
|
||||
requireValidation(t, err, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDimMove_DryRun verifies the native v3 move_dimension payload shape.
|
||||
// CLI's --source-range "1:3" (1-based inclusive) is parsed into
|
||||
// source.{start_index=0, end_index=2} (0-based inclusive), and sheet_id is
|
||||
|
||||
@@ -111,8 +111,8 @@ _公共四件套 · 系统:`--dry-run`_
|
||||
|
||||
| Flag | Type | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `--dimension` | string | required | 维度方向(行或列)(可选值:`row` / `column`) |
|
||||
| `--count` | int | required | 冻结前 N 行/列;传 0 解除冻结 |
|
||||
| `--rows` | int | optional | 冻结前 N 行;与 --cols 一起描述完整冻结状态,省略的轴即为不冻结(0 表示不冻结行) |
|
||||
| `--cols` | int | optional | 冻结前 N 列;与 --rows 一起描述完整冻结状态,省略的轴即为不冻结(0 表示不冻结列) |
|
||||
|
||||
### `+dim-group`
|
||||
|
||||
@@ -202,9 +202,14 @@ lark-cli sheets +dim-move --url "..." --sheet-id "$SID" --source-range "C:F" --t
|
||||
|
||||
### `+dim-freeze`
|
||||
|
||||
冻结是**整份状态覆盖**、不是按轴叠加:`--rows` / `--cols` 一起描述完整的目标状态,没写的轴即为不冻结。所以要同时冻住行和列必须一次给全,拆成两次调用只会剩下最后一次的那个轴。
|
||||
|
||||
```bash
|
||||
# 冻结前 1 行(--count 传 0 解除冻结)
|
||||
lark-cli sheets +dim-freeze --url "..." --sheet-id "$SID" --dimension row --count 1
|
||||
# 冻结前 1 行 + 前 2 列(一次给全)
|
||||
lark-cli sheets +dim-freeze --url "..." --sheet-id "$SID" --rows 1 --cols 2
|
||||
|
||||
# 解除行冻结但保住列:把要保留的轴一并写出
|
||||
lark-cli sheets +dim-freeze --url "..." --sheet-id "$SID" --rows 0 --cols 2
|
||||
```
|
||||
|
||||
### `+dim-group` / `+dim-ungroup`(大纲)
|
||||
@@ -213,6 +218,6 @@ lark-cli sheets +dim-freeze --url "..." --sheet-id "$SID" --dimension row --coun
|
||||
|
||||
### Validate / DryRun / Execute 约束
|
||||
|
||||
- `Validate`:XOR 公共四件套;`--range` / `--source-range` 必须是合法 A1 闭区间(行用数字、列用字母,不可混用);`+dim-insert` 的 `--count` > 0;`+dim-move` 的 `--target` 必须与 `--source-range` 同维度(行 vs 列);`+dim-delete` 强制 `--yes` 或 `--dry-run`,`--range` 与 `--ranges` 二选一、`--ranges` 各区间同维度且不可重叠(≤100 个);`+rows-resize` / `+cols-resize` 的统一形态(`--range` + `--height`/`--width` 或 `--type`)与 map 形态(`--heights`/`--widths`)二选一、不可混用;详见 `lark-sheets-range-operations.md`。
|
||||
- `Validate`:XOR 公共四件套;`--range` / `--source-range` 必须是合法 A1 闭区间(行用数字、列用字母,不可混用);`+dim-insert` 的 `--count` > 0;`+dim-freeze` 至少给 `--rows` / `--cols` 之一;`+dim-move` 的 `--target` 必须与 `--source-range` 同维度(行 vs 列);`+dim-delete` 强制 `--yes` 或 `--dry-run`,`--range` 与 `--ranges` 二选一、`--ranges` 各区间同维度且不可重叠(≤100 个);`+rows-resize` / `+cols-resize` 的统一形态(`--range` + `--height`/`--width` 或 `--type`)与 map 形态(`--heights`/`--widths`)二选一、不可混用;详见 `lark-sheets-range-operations.md`。
|
||||
- `DryRun`:写操作输出"将要 PATCH 的目标范围 + 目标参数"。
|
||||
- `Execute`:写后不自动回读;如需确认,自行调用 `+sheet-info --include row_heights,col_widths,hidden_rows,hidden_cols,groups,frozen` 查看受影响的范围。
|
||||
|
||||
Reference in New Issue
Block a user