From d2e38891df43155a5a7a7b4ece927429c9c3c7b8 Mon Sep 17 00:00:00 2001 From: "huangxin.leben" Date: Wed, 8 Apr 2026 12:10:54 +0000 Subject: [PATCH] Add `+dashboard-arrange` command for auto-arranging dashboard blocks layout and introduce `text` block type with Markdown support for dashboard visualization. - Add `+dashboard-arrange` command that triggers server-side smart layout optimization via POST /open-apis/base/v3/bases/{token}/dashboards/{id}/arrange - Add `text` block type support for dashboard blocks with Markdown syntax (headers, bold, italic, strikethrough, lists) - Update `validateBlockDataConfig()` to handle text-specific validation rules - Update documentation (SKILL.md, lark-base-dashboard.md, dashboard-block-data-config.md, lark-base-dashboard-arrange.md) - Add comprehensive unit tests for new commands and block type - [x] Unit tests pass (`go test ./shortcuts/base/...`) - [x] All dashboard-related tests pass including new `TestBaseDashboardExecuteArrange` - [x] Text block type validation tests pass - None --- shortcuts/base/base_dashboard_execute_test.go | 206 ++++++++++++++++++ shortcuts/base/base_shortcuts_test.go | 2 +- shortcuts/base/dashboard_arrange.go | 29 +++ shortcuts/base/dashboard_block_create.go | 9 +- shortcuts/base/dashboard_block_update.go | 6 +- shortcuts/base/dashboard_ops.go | 56 +++++ shortcuts/base/helpers.go | 15 ++ shortcuts/base/shortcuts.go | 1 + skills/lark-base/SKILL.md | 2 +- .../references/dashboard-block-data-config.md | 43 +++- .../references/lark-base-dashboard-arrange.md | 83 +++++++ .../lark-base-dashboard-block-create.md | 13 +- .../references/lark-base-dashboard.md | 30 ++- 13 files changed, 481 insertions(+), 14 deletions(-) create mode 100644 shortcuts/base/dashboard_arrange.go create mode 100644 skills/lark-base/references/lark-base-dashboard-arrange.md diff --git a/shortcuts/base/base_dashboard_execute_test.go b/shortcuts/base/base_dashboard_execute_test.go index d69ecf261..845446779 100644 --- a/shortcuts/base/base_dashboard_execute_test.go +++ b/shortcuts/base/base_dashboard_execute_test.go @@ -12,6 +12,7 @@ import ( // ── Dashboard CRUD ────────────────────────────────────────────────── +// TestBaseDashboardExecuteList tests the +dashboard-list command. func TestBaseDashboardExecuteList(t *testing.T) { t.Run("single page", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) @@ -41,6 +42,7 @@ func TestBaseDashboardExecuteList(t *testing.T) { } +// TestBaseDashboardExecuteGet tests the +dashboard-get command. func TestBaseDashboardExecuteGet(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) reg.Register(&httpmock.Stub{ @@ -67,6 +69,7 @@ func TestBaseDashboardExecuteGet(t *testing.T) { } } +// TestBaseDashboardExecuteCreate tests the +dashboard-create command. func TestBaseDashboardExecuteCreate(t *testing.T) { t.Run("name only", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) @@ -114,6 +117,7 @@ func TestBaseDashboardExecuteCreate(t *testing.T) { }) } +// TestBaseDashboardExecuteUpdate tests the +dashboard-update command. func TestBaseDashboardExecuteUpdate(t *testing.T) { t.Run("update name", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) @@ -161,6 +165,7 @@ func TestBaseDashboardExecuteUpdate(t *testing.T) { }) } +// TestBaseDashboardExecuteDelete tests the +dashboard-delete command. func TestBaseDashboardExecuteDelete(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) reg.Register(&httpmock.Stub{ @@ -179,6 +184,7 @@ func TestBaseDashboardExecuteDelete(t *testing.T) { // ── Dashboard Block CRUD ──────────────────────────────────────────── +// TestBaseDashboardBlockExecuteList tests the +dashboard-block-list command. func TestBaseDashboardBlockExecuteList(t *testing.T) { t.Run("single page", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) @@ -208,6 +214,7 @@ func TestBaseDashboardBlockExecuteList(t *testing.T) { } +// TestBaseDashboardBlockExecuteGet tests the +dashboard-block-get command. func TestBaseDashboardBlockExecuteGet(t *testing.T) { t.Run("basic", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) @@ -261,6 +268,7 @@ func TestBaseDashboardBlockExecuteGet(t *testing.T) { }) } +// TestBaseDashboardBlockExecuteCreate tests the +dashboard-block-create command. func TestBaseDashboardBlockExecuteCreate(t *testing.T) { t.Run("with data-config", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) @@ -354,6 +362,7 @@ func TestBaseDashboardBlockExecuteCreate(t *testing.T) { }) } +// TestBaseDashboardBlockExecuteUpdate tests the +dashboard-block-update command. func TestBaseDashboardBlockExecuteUpdate(t *testing.T) { t.Run("update name and data-config", func(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) @@ -420,6 +429,7 @@ func TestBaseDashboardBlockExecuteUpdate(t *testing.T) { }) } +// TestBaseDashboardBlockExecuteDelete tests the +dashboard-block-delete command. func TestBaseDashboardBlockExecuteDelete(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) reg.Register(&httpmock.Stub{ @@ -438,6 +448,7 @@ func TestBaseDashboardBlockExecuteDelete(t *testing.T) { // ── Dry Run: Dashboard & Blocks ────────────────────────────────────── +// TestBaseDashboardDryRun_List tests the +dashboard-list --dry-run flag. func TestBaseDashboardDryRun_List(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) if err := runShortcut(t, BaseDashboardList, []string{"+dashboard-list", "--base-token", "app_x", "--page-size", "50", "--dry-run", "--format", "pretty"}, factory, stdout); err != nil { @@ -449,6 +460,7 @@ func TestBaseDashboardDryRun_List(t *testing.T) { } } +// TestBaseDashboardDryRun_Get tests the +dashboard-get --dry-run flag. func TestBaseDashboardDryRun_Get(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) if err := runShortcut(t, BaseDashboardGet, []string{"+dashboard-get", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--dry-run", "--format", "pretty"}, factory, stdout); err != nil { @@ -460,6 +472,7 @@ func TestBaseDashboardDryRun_Get(t *testing.T) { } } +// TestBaseDashboardDryRun_Create tests the +dashboard-create --dry-run flag. func TestBaseDashboardDryRun_Create(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) args := []string{"+dashboard-create", "--base-token", "app_x", "--name", "新报表", "--theme-style", "default", "--dry-run", "--format", "pretty"} @@ -472,6 +485,7 @@ func TestBaseDashboardDryRun_Create(t *testing.T) { } } +// TestBaseDashboardDryRun_Update tests the +dashboard-update --dry-run flag. func TestBaseDashboardDryRun_Update(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) args := []string{"+dashboard-update", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--name", "更新名", "--dry-run", "--format", "pretty"} @@ -484,6 +498,7 @@ func TestBaseDashboardDryRun_Update(t *testing.T) { } } +// TestBaseDashboardDryRun_Delete tests the +dashboard-delete --dry-run flag. func TestBaseDashboardDryRun_Delete(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) args := []string{"+dashboard-delete", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--dry-run", "--format", "pretty"} @@ -496,6 +511,7 @@ func TestBaseDashboardDryRun_Delete(t *testing.T) { } } +// TestBaseDashboardBlockDryRun_List tests the +dashboard-block-list --dry-run flag. func TestBaseDashboardBlockDryRun_List(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) args := []string{"+dashboard-block-list", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--page-size", "10", "--dry-run", "--format", "pretty"} @@ -508,6 +524,7 @@ func TestBaseDashboardBlockDryRun_List(t *testing.T) { } } +// TestBaseDashboardBlockDryRun_Get tests the +dashboard-block-get --dry-run flag. func TestBaseDashboardBlockDryRun_Get(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) args := []string{"+dashboard-block-get", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--block-id", "blk_a", "--user-id-type", "union_id", "--dry-run", "--format", "pretty"} @@ -520,6 +537,7 @@ func TestBaseDashboardBlockDryRun_Get(t *testing.T) { } } +// TestBaseDashboardBlockDryRun_Create tests the +dashboard-block-create --dry-run flag. func TestBaseDashboardBlockDryRun_Create(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) args := []string{"+dashboard-block-create", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--name", "订单趋势", "--type", "column", "--data-config", `{"table_name":"订单表","count_all":true}`, "--user-id-type", "open_id", "--dry-run", "--format", "pretty"} @@ -532,6 +550,7 @@ func TestBaseDashboardBlockDryRun_Create(t *testing.T) { } } +// TestBaseDashboardBlockDryRun_Update tests the +dashboard-block-update --dry-run flag. func TestBaseDashboardBlockDryRun_Update(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) args := []string{"+dashboard-block-update", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--block-id", "blk_a", "--name", "订单趋势v2", "--data-config", `{"table_name":"订单表2","count_all":true}`, "--dry-run", "--format", "pretty"} @@ -544,6 +563,7 @@ func TestBaseDashboardBlockDryRun_Update(t *testing.T) { } } +// TestBaseDashboardBlockDryRun_Delete tests the +dashboard-block-delete --dry-run flag. func TestBaseDashboardBlockDryRun_Delete(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) args := []string{"+dashboard-block-delete", "--base-token", "app_x", "--dashboard-id", "dsh_1", "--block-id", "blk_a", "--dry-run", "--format", "pretty"} @@ -558,6 +578,7 @@ func TestBaseDashboardBlockDryRun_Delete(t *testing.T) { // ── Validator: data_config ─────────────────────────────────────────── +// TestBaseDashboardBlockCreate_ValidateFails tests that data_config validation catches missing table_name. func TestBaseDashboardBlockCreate_ValidateFails(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) // 缺 table_name 且 series 与 count_all 同时存在 @@ -574,6 +595,7 @@ func TestBaseDashboardBlockCreate_ValidateFails(t *testing.T) { } } +// TestBaseDashboardBlockCreate_NoValidateFlagAllocs tests that --no-validate flag skips client-side validation. func TestBaseDashboardBlockCreate_NoValidateFlagAllocs(t *testing.T) { factory, stdout, reg := newExecuteFactory(t) reg.Register(&httpmock.Stub{Method: "POST", URL: "/open-apis/base/v3/bases/app_x/dashboards/dsh_1/blocks", @@ -591,6 +613,7 @@ func TestBaseDashboardBlockCreate_NoValidateFlagAllocs(t *testing.T) { } } +// TestBaseDashboardBlockCreate_InvalidRollup tests that invalid rollup values are rejected during validation. func TestBaseDashboardBlockCreate_InvalidRollup(t *testing.T) { factory, stdout, _ := newExecuteFactory(t) // 合法 JSON,但 rollup=COUNTA(不支持) @@ -606,3 +629,186 @@ func TestBaseDashboardBlockCreate_InvalidRollup(t *testing.T) { t.Fatalf("unexpected error: %v", err) } } + +// ── Text Block Tests ──────────────────────────────────────────────── + +// TestBaseDashboardBlockExecuteCreate_TextType tests creating text blocks with markdown content. +func TestBaseDashboardBlockExecuteCreate_TextType(t *testing.T) { + t.Run("valid text block", func(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/base/v3/bases/app_x/dashboards/dsh_001/blocks", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "block_id": "blk_text", + "name": "说明文字", + "type": "text", + "data_config": map[string]interface{}{ + "text": "# 标题\n**加粗**", + }, + }, + }, + }) + args := []string{"+dashboard-block-create", "--base-token", "app_x", "--dashboard-id", "dsh_001", + "--name", "说明文字", "--type", "text", + "--data-config", `{"text":"# 标题\n**加粗**"}`, + } + if err := runShortcut(t, BaseDashboardBlockCreate, args, factory, stdout); err != nil { + t.Fatalf("err=%v", err) + } + got := stdout.String() + if !strings.Contains(got, `"blk_text"`) || !strings.Contains(got, `"created": true`) { + t.Fatalf("stdout=%s", got) + } + }) + + t.Run("text block missing text field", func(t *testing.T) { + factory, stdout, _ := newExecuteFactory(t) + args := []string{"+dashboard-block-create", "--base-token", "app_x", "--dashboard-id", "dsh_001", + "--name", "Bad", "--type", "text", + "--data-config", `{}`, + } + err := runShortcut(t, BaseDashboardBlockCreate, args, factory, stdout) + if err == nil { + t.Fatalf("expected validation error for missing text field") + } + if got := err.Error(); !strings.Contains(got, "text") || !strings.Contains(got, "data_config 校验失败") { + t.Fatalf("unexpected error: %v", err) + } + }) +} + +// TestBaseDashboardBlockExecuteUpdate_TextType tests updating text block content and name. +func TestBaseDashboardBlockExecuteUpdate_TextType(t *testing.T) { + t.Run("update text content", func(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + reg.Register(&httpmock.Stub{ + Method: "PATCH", + URL: "/open-apis/base/v3/bases/app_x/dashboards/dsh_001/blocks/blk_text", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "block_id": "blk_text", + "name": "更新后的标题", + "type": "text", + "data_config": map[string]interface{}{ + "text": "# 新内容", + }, + }, + }, + }) + args := []string{"+dashboard-block-update", "--base-token", "app_x", "--dashboard-id", "dsh_001", "--block-id", "blk_text", + "--name", "更新后的标题", + "--data-config", `{"text":"# 新内容"}`, + } + if err := runShortcut(t, BaseDashboardBlockUpdate, args, factory, stdout); err != nil { + t.Fatalf("err=%v", err) + } + got := stdout.String() + if !strings.Contains(got, `"updated": true`) || !strings.Contains(got, "新内容") { + t.Fatalf("stdout=%s", got) + } + }) + + t.Run("update without type skips strict validation", func(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + // update 不传 type,不做强类型校验,直接透传给后端 + reg.Register(&httpmock.Stub{ + Method: "PATCH", + URL: "/open-apis/base/v3/bases/app_x/dashboards/dsh_001/blocks/blk_text", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "block_id": "blk_text", + "type": "text", + }, + }, + }) + args := []string{"+dashboard-block-update", "--base-token", "app_x", "--dashboard-id", "dsh_001", "--block-id", "blk_text", + "--data-config", `{"content":"xxx"}`, + } + // 不传 type,本地不做强校验,让后端处理 + err := runShortcut(t, BaseDashboardBlockUpdate, args, factory, stdout) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := stdout.String(); !strings.Contains(got, `"updated": true`) { + t.Fatalf("stdout=%s", got) + } + }) +} + +// ── Dashboard Arrange ──────────────────────────────────────────────── + +// TestBaseDashboardExecuteArrange tests the +dashboard-arrange command for auto-arranging dashboard blocks. +func TestBaseDashboardExecuteArrange(t *testing.T) { + t.Run("arrange dashboard blocks", func(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/base/v3/bases/app_x/dashboards/dsh_001/arrange", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "dashboard_id": "dsh_001", + "name": "测试仪表盘", + "blocks": []interface{}{ + map[string]interface{}{ + "block_id": "cht_xxx", + "block_name": "组件1", + "block_type": "column", + "layout": map[string]interface{}{ + "x": 0, "y": 0, "w": 500, "h": 400, + }, + }, + }, + }, + }, + }) + args := []string{"+dashboard-arrange", "--base-token", "app_x", "--dashboard-id", "dsh_001"} + if err := runShortcut(t, BaseDashboardArrange, args, factory, stdout); err != nil { + t.Fatalf("err=%v", err) + } + got := stdout.String() + if !strings.Contains(got, `"arranged": true`) || !strings.Contains(got, `"dashboard_id"`) { + t.Fatalf("stdout=%s", got) + } + }) + + t.Run("arrange with user-id-type", func(t *testing.T) { + factory, stdout, reg := newExecuteFactory(t) + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "user_id_type=union_id", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "dashboard_id": "dsh_001", + "blocks": []interface{}{}, + }, + }, + }) + args := []string{"+dashboard-arrange", "--base-token", "app_x", "--dashboard-id", "dsh_001", "--user-id-type", "union_id"} + if err := runShortcut(t, BaseDashboardArrange, args, factory, stdout); err != nil { + t.Fatalf("err=%v", err) + } + if got := stdout.String(); !strings.Contains(got, `"arranged": true`) || !strings.Contains(got, `"dashboard_id"`) { + t.Fatalf("stdout=%s", got) + } + }) +} + +// TestBaseDashboardDryRun_Arrange tests the +dashboard-arrange --dry-run flag includes empty body. +func TestBaseDashboardDryRun_Arrange(t *testing.T) { + factory, stdout, _ := newExecuteFactory(t) + args := []string{"+dashboard-arrange", "--base-token", "app_x", "--dashboard-id", "dsh_001", "--user-id-type", "union_id", "--dry-run", "--format", "pretty"} + if err := runShortcut(t, BaseDashboardArrange, args, factory, stdout); err != nil { + t.Fatalf("err=%v", err) + } + got := stdout.String() + if !strings.Contains(got, "POST /open-apis/base/v3/bases/app_x/dashboards/dsh_001/arrange") || !strings.Contains(got, "union_id") || !strings.Contains(got, "{}") { + t.Fatalf("stdout=%s", got) + } +} diff --git a/shortcuts/base/base_shortcuts_test.go b/shortcuts/base/base_shortcuts_test.go index a6f1c61d0..60aeb4ae3 100644 --- a/shortcuts/base/base_shortcuts_test.go +++ b/shortcuts/base/base_shortcuts_test.go @@ -122,7 +122,7 @@ func TestShortcutsCatalog(t *testing.T) { "+data-query", "+form-create", "+form-delete", "+form-list", "+form-update", "+form-get", "+form-questions-create", "+form-questions-delete", "+form-questions-update", "+form-questions-list", - "+dashboard-list", "+dashboard-get", "+dashboard-create", "+dashboard-update", "+dashboard-delete", + "+dashboard-list", "+dashboard-get", "+dashboard-create", "+dashboard-update", "+dashboard-delete", "+dashboard-arrange", "+dashboard-block-list", "+dashboard-block-get", "+dashboard-block-create", "+dashboard-block-update", "+dashboard-block-delete", } if len(shortcuts) != len(want) { diff --git a/shortcuts/base/dashboard_arrange.go b/shortcuts/base/dashboard_arrange.go new file mode 100644 index 000000000..320b704a3 --- /dev/null +++ b/shortcuts/base/dashboard_arrange.go @@ -0,0 +1,29 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package base + +import ( + "context" + + "github.com/larksuite/cli/shortcuts/common" +) + +var BaseDashboardArrange = common.Shortcut{ + Service: "base", + Command: "+dashboard-arrange", + Description: "Auto-arrange dashboard blocks layout (server-side smart layout)", + Risk: "write", + Scopes: []string{"base:dashboard:update"}, + AuthTypes: authTypes(), + HasFormat: true, + Flags: []common.Flag{ + baseTokenFlag(true), + dashboardIDFlag(true), + {Name: "user-id-type", Desc: "user ID type: open_id / union_id / user_id"}, + }, + DryRun: dryRunDashboardArrange, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + return executeDashboardArrange(runtime) + }, +} diff --git a/shortcuts/base/dashboard_block_create.go b/shortcuts/base/dashboard_block_create.go index 9b663d33b..d10a7a6b6 100644 --- a/shortcuts/base/dashboard_block_create.go +++ b/shortcuts/base/dashboard_block_create.go @@ -6,6 +6,7 @@ package base import ( "context" "encoding/json" + "fmt" "strings" "github.com/larksuite/cli/shortcuts/common" @@ -23,7 +24,7 @@ var BaseDashboardBlockCreate = common.Shortcut{ baseTokenFlag(true), dashboardIDFlag(true), {Name: "name", Desc: "block name", Required: true}, - {Name: "type", Desc: "block type: column(柱状图)|bar(条形图)|line(折线图)|pie(饼图)|ring(环形图)|area(面积图)|combo(组合图)|scatter(散点图)|funnel(漏斗图)|wordCloud(词云)|radar(雷达图)|statistics(指标卡). Read dashboard-block-data-config.md before creating.", Required: true}, + {Name: "type", Desc: "block type: column(柱状图)|bar(条形图)|line(折线图)|pie(饼图)|ring(环形图)|area(面积图)|combo(组合图)|scatter(散点图)|funnel(漏斗图)|wordCloud(词云)|radar(雷达图)|statistics(指标卡)|text(文本). Read dashboard-block-data-config.md before creating.", Required: true}, {Name: "data-config", Desc: "data config JSON object (table_name, series, count_all, group_by, filter, etc.)"}, {Name: "user-id-type", Desc: "user ID type: open_id / union_id / user_id"}, {Name: "no-validate", Type: "bool", Desc: "skip local data_config validation"}, @@ -34,7 +35,11 @@ var BaseDashboardBlockCreate = common.Shortcut{ } raw := runtime.Str("data-config") if strings.TrimSpace(raw) == "" { - return nil // 允许无 data_config 的创建(某些类型可先创建后配置) + // text 类型必须提供 data-config(含 text 内容) + if strings.ToLower(runtime.Str("type")) == "text" { + return fmt.Errorf("text 类型组件必须提供 data-config,包含必填字段 text") + } + return nil } cfg, err := parseJSONObject(raw, "data-config") if err != nil { diff --git a/shortcuts/base/dashboard_block_update.go b/shortcuts/base/dashboard_block_update.go index 3a6cc59e0..a9df8de73 100644 --- a/shortcuts/base/dashboard_block_update.go +++ b/shortcuts/base/dashboard_block_update.go @@ -24,7 +24,7 @@ var BaseDashboardBlockUpdate = common.Shortcut{ dashboardIDFlag(true), blockIDFlag(true), {Name: "name", Desc: "new block name"}, - {Name: "data-config", Desc: "data config JSON: table_name, series|count_all (mutually exclusive), group_by, filter. See dashboard-block-data-config.md for details."}, + {Name: "data-config", Desc: "data config JSON. For chart types: table_name, series|count_all, group_by, filter. For text type: text (markdown supported). See dashboard-block-data-config.md for details."}, {Name: "user-id-type", Desc: "user ID type: open_id / union_id / user_id"}, {Name: "no-validate", Type: "bool", Desc: "skip local data_config validation"}, }, @@ -41,9 +41,7 @@ var BaseDashboardBlockUpdate = common.Shortcut{ return err } norm := normalizeDataConfig(cfg) - if errs := validateBlockDataConfig("", norm); len(errs) > 0 { // update 时不强校验类型特性 - return formatDataConfigErrors(errs) - } + // update 时不做强类型校验(不传 type),让后端验证具体字段 b, _ := json.Marshal(norm) _ = runtime.Cmd.Flags().Set("data-config", string(b)) return nil diff --git a/shortcuts/base/dashboard_ops.go b/shortcuts/base/dashboard_ops.go index c319fde59..e39fdc668 100644 --- a/shortcuts/base/dashboard_ops.go +++ b/shortcuts/base/dashboard_ops.go @@ -10,14 +10,17 @@ import ( "github.com/larksuite/cli/shortcuts/common" ) +// dashboardIDFlag returns a Flag for dashboard ID. func dashboardIDFlag(required bool) common.Flag { return common.Flag{Name: "dashboard-id", Desc: "dashboard ID", Required: required} } +// blockIDFlag returns a Flag for dashboard block ID. func blockIDFlag(required bool) common.Flag { return common.Flag{Name: "block-id", Desc: "dashboard block ID", Required: required} } +// dryRunDashboardBase returns a base DryRunAPI with common dashboard parameters set. func dryRunDashboardBase(runtime *common.RuntimeContext) *common.DryRunAPI { return common.NewDryRunAPI(). Set("base_token", runtime.Str("base-token")). @@ -25,6 +28,7 @@ func dryRunDashboardBase(runtime *common.RuntimeContext) *common.DryRunAPI { Set("block_id", runtime.Str("block-id")) } +// dryRunDashboardList returns a DryRunAPI for listing dashboards. func dryRunDashboardList(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { params := map[string]interface{}{} if pageSize := strings.TrimSpace(runtime.Str("page-size")); pageSize != "" { @@ -38,11 +42,13 @@ func dryRunDashboardList(_ context.Context, runtime *common.RuntimeContext) *com Params(params) } +// dryRunDashboardGet returns a DryRunAPI for getting a dashboard. func dryRunDashboardGet(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { return dryRunDashboardBase(runtime). GET("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id") } +// dryRunDashboardCreate returns a DryRunAPI for creating a dashboard. func dryRunDashboardCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body := map[string]interface{}{"name": runtime.Str("name")} if themeStyle := strings.TrimSpace(runtime.Str("theme-style")); themeStyle != "" { @@ -53,6 +59,7 @@ func dryRunDashboardCreate(_ context.Context, runtime *common.RuntimeContext) *c Body(body) } +// dryRunDashboardUpdate returns a DryRunAPI for updating a dashboard. func dryRunDashboardUpdate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body := map[string]interface{}{} if name := strings.TrimSpace(runtime.Str("name")); name != "" { @@ -66,11 +73,13 @@ func dryRunDashboardUpdate(_ context.Context, runtime *common.RuntimeContext) *c Body(body) } +// dryRunDashboardDelete returns a DryRunAPI for deleting a dashboard. func dryRunDashboardDelete(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { return dryRunDashboardBase(runtime). DELETE("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id") } +// dryRunDashboardBlockList returns a DryRunAPI for listing dashboard blocks. func dryRunDashboardBlockList(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { params := map[string]interface{}{} if pageSize := strings.TrimSpace(runtime.Str("page-size")); pageSize != "" { @@ -84,6 +93,7 @@ func dryRunDashboardBlockList(_ context.Context, runtime *common.RuntimeContext) Params(params) } +// dryRunDashboardBlockGet returns a DryRunAPI for getting a dashboard block. func dryRunDashboardBlockGet(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { params := map[string]interface{}{} if userIDType := strings.TrimSpace(runtime.Str("user-id-type")); userIDType != "" { @@ -94,6 +104,7 @@ func dryRunDashboardBlockGet(_ context.Context, runtime *common.RuntimeContext) Params(params) } +// dryRunDashboardBlockCreate returns a DryRunAPI for creating a dashboard block. func dryRunDashboardBlockCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body := map[string]interface{}{} if name := strings.TrimSpace(runtime.Str("name")); name != "" { @@ -118,6 +129,7 @@ func dryRunDashboardBlockCreate(_ context.Context, runtime *common.RuntimeContex Body(body) } +// dryRunDashboardBlockUpdate returns a DryRunAPI for updating a dashboard block. func dryRunDashboardBlockUpdate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body := map[string]interface{}{} if name := strings.TrimSpace(runtime.Str("name")); name != "" { @@ -138,6 +150,7 @@ func dryRunDashboardBlockUpdate(_ context.Context, runtime *common.RuntimeContex Body(body) } +// dryRunDashboardBlockDelete returns a DryRunAPI for deleting a dashboard block. func dryRunDashboardBlockDelete(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { return dryRunDashboardBase(runtime). DELETE("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id/blocks/:block_id") @@ -145,6 +158,7 @@ func dryRunDashboardBlockDelete(_ context.Context, runtime *common.RuntimeContex // ── Dashboard CRUD ────────────────────────────────────────────────── +// executeDashboardList lists all dashboards in a base. func executeDashboardList(runtime *common.RuntimeContext) error { params := map[string]interface{}{} if pageSize := strings.TrimSpace(runtime.Str("page-size")); pageSize != "" { @@ -161,6 +175,7 @@ func executeDashboardList(runtime *common.RuntimeContext) error { return nil } +// executeDashboardGet retrieves a dashboard by ID. func executeDashboardGet(runtime *common.RuntimeContext) error { data, err := baseV3Call(runtime, "GET", baseV3Path("bases", runtime.Str("base-token"), "dashboards", runtime.Str("dashboard-id")), nil, nil) if err != nil { @@ -170,6 +185,7 @@ func executeDashboardGet(runtime *common.RuntimeContext) error { return nil } +// executeDashboardCreate creates a new dashboard. func executeDashboardCreate(runtime *common.RuntimeContext) error { body := map[string]interface{}{"name": runtime.Str("name")} if themeStyle := strings.TrimSpace(runtime.Str("theme-style")); themeStyle != "" { @@ -183,6 +199,7 @@ func executeDashboardCreate(runtime *common.RuntimeContext) error { return nil } +// executeDashboardUpdate updates an existing dashboard. func executeDashboardUpdate(runtime *common.RuntimeContext) error { body := map[string]interface{}{} if name := strings.TrimSpace(runtime.Str("name")); name != "" { @@ -199,6 +216,7 @@ func executeDashboardUpdate(runtime *common.RuntimeContext) error { return nil } +// executeDashboardDelete deletes a dashboard by ID. func executeDashboardDelete(runtime *common.RuntimeContext) error { _, err := baseV3Call(runtime, "DELETE", baseV3Path("bases", runtime.Str("base-token"), "dashboards", runtime.Str("dashboard-id")), nil, nil) if err != nil { @@ -210,6 +228,7 @@ func executeDashboardDelete(runtime *common.RuntimeContext) error { // ── Dashboard Block CRUD ──────────────────────────────────────────── +// executeDashboardBlockList lists all blocks in a dashboard. func executeDashboardBlockList(runtime *common.RuntimeContext) error { params := map[string]interface{}{} if pageSize := strings.TrimSpace(runtime.Str("page-size")); pageSize != "" { @@ -226,6 +245,7 @@ func executeDashboardBlockList(runtime *common.RuntimeContext) error { return nil } +// executeDashboardBlockGet retrieves a dashboard block by ID. func executeDashboardBlockGet(runtime *common.RuntimeContext) error { params := map[string]interface{}{} if userIDType := strings.TrimSpace(runtime.Str("user-id-type")); userIDType != "" { @@ -239,6 +259,7 @@ func executeDashboardBlockGet(runtime *common.RuntimeContext) error { return nil } +// executeDashboardBlockCreate creates a new dashboard block. func executeDashboardBlockCreate(runtime *common.RuntimeContext) error { body := map[string]interface{}{} if name := strings.TrimSpace(runtime.Str("name")); name != "" { @@ -268,6 +289,7 @@ func executeDashboardBlockCreate(runtime *common.RuntimeContext) error { return nil } +// executeDashboardBlockUpdate updates an existing dashboard block. func executeDashboardBlockUpdate(runtime *common.RuntimeContext) error { body := map[string]interface{}{} if name := strings.TrimSpace(runtime.Str("name")); name != "" { @@ -293,6 +315,7 @@ func executeDashboardBlockUpdate(runtime *common.RuntimeContext) error { return nil } +// executeDashboardBlockDelete deletes a dashboard block by ID. func executeDashboardBlockDelete(runtime *common.RuntimeContext) error { _, err := baseV3Call(runtime, "DELETE", baseV3Path("bases", runtime.Str("base-token"), "dashboards", runtime.Str("dashboard-id"), "blocks", runtime.Str("block-id")), nil, nil) if err != nil { @@ -301,3 +324,36 @@ func executeDashboardBlockDelete(runtime *common.RuntimeContext) error { runtime.Out(map[string]interface{}{"deleted": true, "block_id": runtime.Str("block-id")}, nil) return nil } + +// ── Dashboard Arrange ──────────────────────────────────────────────── + +// dryRunDashboardArrange returns a DryRunAPI for the dashboard arrange endpoint. +func dryRunDashboardArrange(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + params := map[string]interface{}{} + if userIDType := strings.TrimSpace(runtime.Str("user-id-type")); userIDType != "" { + params["user_id_type"] = userIDType + } + return dryRunDashboardBase(runtime). + POST("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id/arrange"). + Params(params). + Body(map[string]interface{}{}) +} + +// executeDashboardArrange sends a POST request to auto-arrange dashboard blocks layout. +func executeDashboardArrange(runtime *common.RuntimeContext) error { + params := map[string]interface{}{} + if userIDType := strings.TrimSpace(runtime.Str("user-id-type")); userIDType != "" { + params["user_id_type"] = userIDType + } + // 请求体为空对象,由服务端智能重排 + data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "dashboards", runtime.Str("dashboard-id"), "arrange"), params, map[string]interface{}{}) + if err != nil { + return err + } + if data == nil { + data = map[string]interface{}{} + } + data["arranged"] = true + runtime.Out(data, nil) + return nil +} diff --git a/shortcuts/base/helpers.go b/shortcuts/base/helpers.go index 9032ab5a2..f6b3ccd14 100644 --- a/shortcuts/base/helpers.go +++ b/shortcuts/base/helpers.go @@ -973,6 +973,8 @@ func sleepBetweenBatches(index int, total int) { // ── Dashboard Block data_config normalization & validation ─────────── +// normalizeDataConfig normalizes data_config fields for dashboard blocks. +// It converts series[].rollup to uppercase and group_by[].sort fields to lowercase. func normalizeDataConfig(cfg map[string]interface{}) map[string]interface{} { if cfg == nil { return nil @@ -1014,8 +1016,21 @@ func normalizeDataConfig(cfg map[string]interface{}) map[string]interface{} { return out } +// validateBlockDataConfig validates data_config based on block type. +// For text type, it checks for the presence of text field. +// For chart types, it validates table_name, series/count_all, group_by, and filter fields. func validateBlockDataConfig(blockType string, cfg map[string]interface{}) []string { var errs []string + + // text 类型特殊校验:只需要有 text 字段即可 + if strings.ToLower(blockType) == "text" { + if txt, _ := cfg["text"].(string); strings.TrimSpace(txt) == "" { + errs = append(errs, "text 类型组件缺少必填字段 text") + } + return errs + } + + // 图表类型通用校验 // table_name 必填 if tn, _ := cfg["table_name"].(string); strings.TrimSpace(tn) == "" { errs = append(errs, "缺少必填字段 table_name") diff --git a/shortcuts/base/shortcuts.go b/shortcuts/base/shortcuts.go index fea4ebd7e..a0270a551 100644 --- a/shortcuts/base/shortcuts.go +++ b/shortcuts/base/shortcuts.go @@ -71,6 +71,7 @@ func Shortcuts() []common.Shortcut { BaseDashboardCreate, BaseDashboardUpdate, BaseDashboardDelete, + BaseDashboardArrange, BaseDashboardBlockList, BaseDashboardBlockGet, BaseDashboardBlockCreate, diff --git a/skills/lark-base/SKILL.md b/skills/lark-base/SKILL.md index 23f4abc87..47897e1ab 100644 --- a/skills/lark-base/SKILL.md +++ b/skills/lark-base/SKILL.md @@ -303,4 +303,4 @@ https://{domain}/base/{base-token}?table={table-id}&view={view-id} | [`form commands`](references/lark-base-form-create.md) | `+form-list / +form-get / +form-create / +form-update / +form-delete` | | [`form questions commands`](references/lark-base-form-questions-create.md) | `+form-questions-list / +form-questions-create / +form-questions-update / +form-questions-delete` | | [`workflow commands`](references/lark-base-workflow.md) | `+workflow-list / +workflow-get / +workflow-create / +workflow-update / +workflow-enable / +workflow-disable` | -| [`dashboard / dashboard-block commands`](references/lark-base-dashboard.md) | `+dashboard-list / +dashboard-get / +dashboard-create / +dashboard-update / +dashboard-delete / +dashboard-block-list / +dashboard-block-get / +dashboard-block-create / +dashboard-block-update / +dashboard-block-delete` | +| [`dashboard / dashboard-block commands`](references/lark-base-dashboard.md) | `+dashboard-list / +dashboard-get / +dashboard-create / +dashboard-update / +dashboard-delete / +dashboard-arrange / +dashboard-block-list / +dashboard-block-get / +dashboard-block-create / +dashboard-block-update / +dashboard-block-delete` | diff --git a/skills/lark-base/references/dashboard-block-data-config.md b/skills/lark-base/references/dashboard-block-data-config.md index bf85fd51c..996bea0fa 100644 --- a/skills/lark-base/references/dashboard-block-data-config.md +++ b/skills/lark-base/references/dashboard-block-data-config.md @@ -18,6 +18,7 @@ Block 的 `data_config` 字段因 `type` 不同而变化。本文档描述所有 | `wordCloud` | 词云 | | `radar` | 雷达图 | | `statistics` | 指标卡 | +| `text` | 文本(支持 Markdown) | ## 字段类型与操作符速查(AI 决策用) @@ -45,6 +46,29 @@ Block 的 `data_config` 字段因 `type` 不同而变化。本文档描述所有 | `filter.conjunction` | `"and"` / `"or"` | 筛选逻辑 | | `filter.conditions` | `[{ "field_name", "operator", "value" }]` | 筛选条件数组,value 类型因字段类型而异(见下方 filter 格式规则) | +### text 类型特殊结构 + +`text` 类型组件用于展示富文本内容,**不需要数据源配置**(无 `table_name`、`series`、`group_by`、`filter`)。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `text` | string | **必填**。支持 Markdown 语法,详见下方说明 | + +**支持的 Markdown 语法:** + +| 语法 | 示例 | 效果 | +|------|------|------| +| 一级标题 | `# 标题` | 大标题 | +| 二级标题 | `## 标题` | 中标题 | +| 三级标题 | `### 标题` | 小标题 | +| 加粗 | `**文字**` | **文字** | +| 斜体 | `*文字*` | *文字* | +| 删除线 | `~~文字~~` | ~~文字~~ | +| 有序列表 | `1. 项目` | 1. 项目 | +| 无序列表 | `- 项目` | - 项目 | + +> **注意**:以上未提及的 Markdown 语法(如链接、图片、代码块、表格等)均不支持。 + ## group_by 详细说明 ### mode 枚举 @@ -138,8 +162,10 @@ Block 的 `data_config` 字段因 `type` 不同而变化。本文档描述所有 ## 约束与本地校验 - 必填与互斥 - - 必填:`table_name` - - 互斥:`series` 与 `count_all` 二选一,且至少提供其一 + - 图表类型必填:`table_name` + - text 类型必填:`text` + - 互斥:`series` 与 `count_all` 二选一,且至少提供其一(仅图表类型) + - text 类型**不支持**:`series`、`count_all`、`group_by`、`filter` - 长度/结构 - `group_by` 最多 2 个;每项 `field_name` 必填 - `group_by[].sort.type` 取值 `group|value|view`;`order` 取值 `asc|desc` @@ -147,7 +173,8 @@ Block 的 `data_config` 字段因 `type` 不同而变化。本文档描述所有 - `series[].rollup` 自动转成大写(如 `sum` → `SUM`) - `group_by[].sort.type/order` 自动转成小写 - 本地校验(可通过 `--no-validate` 跳过) - - `+dashboard-block-create/update` 默认对 `data_config` 做轻量校验;失败会聚合错误并给出修复建议 + - `+dashboard-block-create` 默认对 `data_config` 做轻量校验;失败会聚合错误并给出修复建议 + - `+dashboard-block-update` 不做强类型校验,由后端验证具体字段 - 仅需传入合法 JSON;CLI 不会擅自改写你的业务含义 ## 可复制模板 @@ -287,6 +314,16 @@ Block 的 `data_config` 字段因 `type` 不同而变化。本文档描述所有 } ``` +文本组件(Markdown 富文本): + +```json +{ + "text": "# 🚀 一级标题\n这是一个 **加粗** *斜体* ~~删除线~~ 的示例。\n\n## 📌 二级标题\n1. 有序列表项 1\n2. 有序列表项 2\n\n### 📌 三级标题\n- 无序列表项 1\n- 无序列表项 2" +} +``` + +> **注意**:text 类型组件不需要 `table_name`、`series`、`group_by`、`filter` 等数据源相关字段。 + ## 常见错误与修复 - 同时存在 `series` 与 `count_all` diff --git a/skills/lark-base/references/lark-base-dashboard-arrange.md b/skills/lark-base/references/lark-base-dashboard-arrange.md new file mode 100644 index 000000000..7a793137d --- /dev/null +++ b/skills/lark-base/references/lark-base-dashboard-arrange.md @@ -0,0 +1,83 @@ +# base +dashboard-arrange + +> **前置条件:** 先阅读 [lark-base-dashboard.md](lark-base-dashboard.md) 了解整体工作流 + +自动重新排列仪表盘组件布局。服务端根据组件数量和类型进行智能布局优化。 + +## 使用场景 + +| 场景 | 说明 | +|------|------| +| **从 0 到 1 搭建后** | 使用 `+dashboard-create` 和 `+dashboard-block-create` 创建仪表盘后,默认布局可能不够工整美观,推荐使用本命令做一次整体重排 | +| **用户明确要求** | 用户主动要求对已有仪表盘进行布局重排或美化时 | + +> [!CAUTION] +> - **不建议**在已有仪表盘上自动调用此命令,除非用户明确要求 +> - 排列结果是**服务端智能推荐**,不一定完全符合用户预期 +> - 无法指定具体位置(如"第一排放 A,第二排放 B"),排列逻辑是**自适应**的 + +## 推荐命令 + +```bash +# 基础用法 +lark-cli base +dashboard-arrange \ + --base-token xxx \ + --dashboard-id blk_xxx +``` + +## 参数 + +| 参数 | 必填 | 说明 | +|------|------|------| +| `--base-token ` | 是 | Base Token | +| `--dashboard-id ` | 是 | 仪表盘 ID | +| `--user-id-type ` | 否 | 用户 ID 类型:open_id / union_id / user_id | +| `--dry-run` | 否 | 预览 API 调用,不执行 | + +## 返回示例 + +```json +{ + "dashboard_id": "blk_xxx", + "name": "数据分析", + "blocks": [ + { + "block_id": "chtbxxxx", + "block_name": "总销售额", + "block_type": "statistics", + "layout": { + "x": 0, + "y": 0, + "w": 6, + "h": 6 + } + }, + { + "block_id": "chtbcrxxxx", + "block_name": "月度趋势", + "block_type": "column", + "layout": { + "x": 6, + "y": 0, + "w": 6, + "h": 6 + } + } + ], + "arranged": true +} +``` + +## 返回重点 + +| 字段 | 说明 | +|------|------| +| `blocks[].layout` | 重排后的布局信息,包含 x/y/w/h | +| `arranged` | 是否重排成功 | + +> [!CAUTION] +> 这是**写入操作** — 执行前必须向用户确认。 + +## 参考 + +- [lark-base-dashboard.md](lark-base-dashboard.md) — dashboard 模块指引 diff --git a/skills/lark-base/references/lark-base-dashboard-block-create.md b/skills/lark-base/references/lark-base-dashboard-block-create.md index 4db25cd39..0e8aa1d2a 100644 --- a/skills/lark-base/references/lark-base-dashboard-block-create.md +++ b/skills/lark-base/references/lark-base-dashboard-block-create.md @@ -22,6 +22,14 @@ lark-cli base +dashboard-block-create \ --type statistics \ --data-config '{"table_name":"订单表","count_all":true}' +# 文本组件示例(Markdown 富文本) +lark-cli base +dashboard-block-create \ + --base-token xxx \ + --dashboard-id blk_xxx \ + --name "说明文字" \ + --type text \ + --data-config '{"text":"# 标题\n## 副标题\n**加粗** *斜体* ~~删除~~\n1. 列表1\n2. 列表2"}' + # 复杂配置用文件传入 lark-cli base +dashboard-block-create \ --base-token xxx \ @@ -40,8 +48,8 @@ lark-cli base +dashboard-block-create \ | `--base-token ` | 是 | Base Token | | `--dashboard-id ` | 是 | 仪表盘 ID(从 `+dashboard-list/get` 获取) | | `--name ` | **是** | 组件名称(允许重名) | -| `--type ` | **是** | 组件类型,见下方枚举值。**不同 type 对应不同的 data_config 结构**,常用:`column`(柱状图)、`line`(折线图)、`pie`(饼图)、`statistics`(指标卡) | -| `--data-config ` | 否 | 数据配置 JSON,**结构随 type 变化**。**⚠️ 必须阅读 [dashboard-block-data-config.md](dashboard-block-data-config.md) 了解如何构造** | +| `--type ` | **是** | 组件类型,见下方枚举值。**不同 type 对应不同的 data_config 结构**,常用:`column`(柱状图)、`line`(折线图)、`pie`(饼图)、`statistics`(指标卡)、`text`(文本) | +| `--data-config ` | 否 | 数据配置 JSON,**结构随 type 变化**。**⚠️ 必须阅读 [dashboard-block-data-config.md](dashboard-block-data-config.md) 了解如何构造**。创建时会做本地校验,更新时由后端校验 | | `--user-id-type ` | 否 | 用户 ID 类型,filter 涉及人员字段时使用 | | `--dry-run` | 否 | 预览 API 调用,不执行 | @@ -61,6 +69,7 @@ lark-cli base +dashboard-block-create \ | `wordCloud` | 词云 | | `radar` | 雷达图 | | `statistics` | 指标卡 | +| `text` | 文本(支持 Markdown) | ## 返回示例 diff --git a/skills/lark-base/references/lark-base-dashboard.md b/skills/lark-base/references/lark-base-dashboard.md index c5db1653a..f988ba0e2 100644 --- a/skills/lark-base/references/lark-base-dashboard.md +++ b/skills/lark-base/references/lark-base-dashboard.md @@ -18,6 +18,7 @@ Dashboard 是 Base 中的数据可视化看板,可以把表格数据变成** | 在仪表盘里添加组件 | `+dashboard-block-create` | 先读 [lark-base-dashboard-block-create.md](lark-base-dashboard-block-create.md),再读 [dashboard-block-data-config.md](dashboard-block-data-config.md) | | 修改组件 | `+dashboard-block-update` | 先读 [lark-base-dashboard-block-update.md](lark-base-dashboard-block-update.md),再读 [dashboard-block-data-config.md](dashboard-block-data-config.md) | | 查看仪表盘有哪些组件 | `+dashboard-get` 或 `+dashboard-block-list` | 本页下方「查看仪表盘」 | +| 智能重排组件布局 | `+dashboard-arrange` | [lark-base-dashboard-arrange.md](lark-base-dashboard-arrange.md) | ## 典型场景工作流 @@ -58,6 +59,12 @@ lark-cli base +dashboard-block-create \ --data-config '{"table_name":"订单表","series":[{"field_name":"金额","rollup":"SUM"}],"group_by":[{"field_name":"月份","mode":"integrated"}]}' # 继续创建其他组件... + +# 第 5 步:组件创建完成后,使用 arrange 命令智能重排布局(可选但推荐) +# 默认布局可能不够美观,arrange 会根据组件数量和类型自动优化布局 +lark-cli base +dashboard-arrange \ + --base-token xxx \ + --dashboard-id blk_xxx ``` ### 场景 2:在已有仪表盘上添加新组件 @@ -119,7 +126,26 @@ lark-cli base +dashboard-block-update \ --data-config '{...}' ``` -### 场景 4:读取仪表盘或组件现状 +### 场景 4:重排仪表盘布局 + +当用户明确要求对已有仪表盘进行布局重排或美化时使用。 + +> [!CAUTION] +> - 排列结果是**服务端智能推荐**,不一定完全符合用户预期 +> - 无法指定具体位置(如"第一排放 A,第二排放 B"),排列逻辑是**自适应**的 +> - **不建议**在已有仪表盘上自动调用,除非用户明确要求 + +```bash +# 第 1 步:列出仪表盘,定位到目标仪表盘 +lark-cli base +dashboard-list --base-token xxx + +# 第 2 步:执行智能重排 +lark-cli base +dashboard-arrange \ + --base-token xxx \ + --dashboard-id blk_xxx +``` + +### 场景 5:读取仪表盘或组件现状 **选择查询方式:** - 想看仪表盘整体结构(含主题、所有组件名称和类型)→ 用 **方式 A** @@ -154,6 +180,7 @@ lark-cli base +dashboard-block-get --base-token xxx --dashboard-id blk_xxx --blo | 类别比较(谁高谁低) | column | 柱状图组件 | | 占比分布(各部分比例) | pie | 饼图组件 | | 单个关键指标 | statistics | 指标卡组件 | +| 富文本说明/标题/注释 | text | 文本组件(支持 Markdown) | 详细组件类型和 data_config 完整规则:[dashboard-block-data-config.md](dashboard-block-data-config.md) @@ -205,6 +232,7 @@ A: 在「添加新组件」或「编辑组件」前查看已有组件可以: | `+dashboard-create` | 创建仪表盘 | [lark-base-dashboard-create.md](lark-base-dashboard-create.md) | | `+dashboard-update` | 修改仪表盘 | [lark-base-dashboard-update.md](lark-base-dashboard-update.md) | | `+dashboard-delete` | 删除仪表盘 | [lark-base-dashboard-delete.md](lark-base-dashboard-delete.md) | +| `+dashboard-arrange` | 智能重排布局 | [lark-base-dashboard-arrange.md](lark-base-dashboard-arrange.md) | | `+dashboard-block-list` | 列出组件 | [lark-base-dashboard-block-list.md](lark-base-dashboard-block-list.md) | | `+dashboard-block-get` | 获取单个组件详情 | [lark-base-dashboard-block-get.md](lark-base-dashboard-block-get.md) | | `+dashboard-block-create` | 创建组件 | [lark-base-dashboard-block-create.md](lark-base-dashboard-block-create.md) |