Compare commits
1 Commits
feat/slide
...
docs/wiki-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
006452dcd6 |
3
.github/CODEOWNERS
vendored
@@ -1,7 +1,4 @@
|
||||
/go.mod @liangshuo-1
|
||||
/go.sum @liangshuo-1
|
||||
/internal/ @liangshuo-1
|
||||
/shortcuts/common/ @liangshuo-1
|
||||
|
||||
# Last match wins: existing domains below are exempt, only new skills/ entries need review.
|
||||
/skills/ @liangshuo-1
|
||||
|
||||
22
CHANGELOG.md
@@ -2,27 +2,6 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [v1.0.80] - 2026-07-29
|
||||
|
||||
### Features
|
||||
|
||||
- **drive**: add +member-list shortcut (#1795)
|
||||
- **drive**: add +permission-get-setting shortcut (#1738)
|
||||
- propagate invocation metadata (#2097)
|
||||
|
||||
### Documentation
|
||||
|
||||
- **slides**: 补齐 shortcut 参数说明,修正 +xml-get --output 必填标注 (#2088)
|
||||
- **slides**: +create 的参数下沉到 create.md,主 skill 只留路由 (#2096)
|
||||
|
||||
### Tests
|
||||
|
||||
- **e2e**: wait for base role update visibility (#2087)
|
||||
|
||||
### Misc
|
||||
|
||||
- Feat/detect line text overlap (#2069)
|
||||
|
||||
## [v1.0.79] - 2026-07-28
|
||||
|
||||
### Features
|
||||
@@ -1722,7 +1701,6 @@ Bundled AI agent skills for intelligent assistance:
|
||||
- Bilingual documentation (English & Chinese).
|
||||
- CI/CD pipelines: linting, testing, coverage reporting, and automated releases.
|
||||
|
||||
[v1.0.80]: https://github.com/larksuite/cli/releases/tag/v1.0.80
|
||||
[v1.0.79]: https://github.com/larksuite/cli/releases/tag/v1.0.79
|
||||
[v1.0.78]: https://github.com/larksuite/cli/releases/tag/v1.0.78
|
||||
[v1.0.77]: https://github.com/larksuite/cli/releases/tag/v1.0.77
|
||||
|
||||
@@ -66,7 +66,6 @@ func PrepareDomainHelp(cmd *cobra.Command, skillFS fs.FS) bool {
|
||||
fmt.Fprintf(&b, "\n\nDomain guide (concepts, command choice, conventions): lark-cli skills read %s", skill)
|
||||
}
|
||||
}
|
||||
appendSlidesXMLQuickReference(&b, cmd, skillFS)
|
||||
cmd.Long = b.String()
|
||||
return true
|
||||
}
|
||||
@@ -117,90 +116,6 @@ const (
|
||||
shortcutBaseAnnotation = "affordance-shortcut-base"
|
||||
)
|
||||
|
||||
const slidesXMLQuickReferencePath = "lark-slides/references/xml-schema-quick-ref.md"
|
||||
|
||||
// slidesShortcutReferencePaths maps each Slides shortcut to its primary
|
||||
// command guide. XML-consuming shortcuts also include the shared schema
|
||||
// reference because their command guide accepts XML but does not repeat the
|
||||
// complete element grammar.
|
||||
var slidesShortcutReferencePaths = map[string][]string{
|
||||
"+create": {
|
||||
"lark-slides/references/lark-slides-create.md",
|
||||
slidesXMLQuickReferencePath,
|
||||
},
|
||||
"+xml-get": {
|
||||
"lark-slides/references/lark-slides-xml-presentations-get.md",
|
||||
},
|
||||
"+screenshot": {
|
||||
"lark-slides/references/lark-slides-screenshot.md",
|
||||
},
|
||||
"+media-upload": {
|
||||
"lark-slides/references/lark-slides-media-upload.md",
|
||||
},
|
||||
"+replace-slide": {
|
||||
"lark-slides/references/lark-slides-replace-slide.md",
|
||||
"lark-slides/references/lark-slides-edit-workflows.md",
|
||||
slidesXMLQuickReferencePath,
|
||||
},
|
||||
"+replace-pages": {
|
||||
"lark-slides/references/lark-slides-replace-pages.md",
|
||||
"lark-slides/references/lark-slides-edit-workflows.md",
|
||||
slidesXMLQuickReferencePath,
|
||||
},
|
||||
"+history-list": {
|
||||
"lark-slides/references/lark-slides-history.md",
|
||||
},
|
||||
"+history-revert": {
|
||||
"lark-slides/references/lark-slides-history.md",
|
||||
},
|
||||
"+history-revert-status": {
|
||||
"lark-slides/references/lark-slides-history.md",
|
||||
},
|
||||
}
|
||||
|
||||
// appendSlidesXMLQuickReference adds the embedded XML schema summary to the
|
||||
// slides domain help. The reference file is already shipped in the skill
|
||||
// content tree, so help and the standalone skill reader share one source of
|
||||
// truth instead of maintaining a second, drifting copy in Go.
|
||||
func appendSlidesXMLQuickReference(b *strings.Builder, cmd *cobra.Command, skillFS fs.FS) {
|
||||
if cmd.Name() != "slides" || skillFS == nil {
|
||||
return
|
||||
}
|
||||
content, err := fs.ReadFile(skillFS, slidesXMLQuickReferencePath)
|
||||
if err != nil || len(content) == 0 {
|
||||
return
|
||||
}
|
||||
b.WriteString("\n\nEmbedded XML syntax quick reference:\n")
|
||||
b.Write(content)
|
||||
}
|
||||
|
||||
func readSlidesShortcutReferences(cmd *cobra.Command, skillFS fs.FS) ([]string, bool) {
|
||||
if cmdmeta.Domain(cmd) != "slides" || skillFS == nil {
|
||||
return nil, false
|
||||
}
|
||||
paths, ok := slidesShortcutReferencePaths[cmd.Name()]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var contents []string
|
||||
for _, path := range paths {
|
||||
content, err := fs.ReadFile(skillFS, path)
|
||||
if err != nil || len(content) == 0 {
|
||||
continue
|
||||
}
|
||||
contents = append(contents, fmt.Sprintf("Embedded command reference: %s\n%s", path, content))
|
||||
}
|
||||
return contents, len(contents) > 0
|
||||
}
|
||||
|
||||
func appendSlidesShortcutReferences(b *strings.Builder, contents []string) {
|
||||
for _, content := range contents {
|
||||
b.WriteString("\n\n")
|
||||
b.WriteString(content)
|
||||
}
|
||||
}
|
||||
|
||||
// setMethodHelpData records the coordinates PrepareMethodHelp needs (storing a
|
||||
// few strings is the only build-time cost; the overlay stays untouched).
|
||||
func setMethodHelpData(cmd *cobra.Command, service, methodID, schemaPath, paramsOnly string) {
|
||||
@@ -256,11 +171,11 @@ func PrepareMethodHelp(cmd *cobra.Command, skillFS fs.FS) bool {
|
||||
}
|
||||
|
||||
// PrepareShortcutHelp composes a +-prefixed shortcut's Long from its affordance
|
||||
// overlay and any embedded command references — the same top layout as method
|
||||
// help (description, Risk, guidance block, related skills) minus the schema
|
||||
// pointer, which shortcuts have none of. Returns false when the command is not
|
||||
// a shortcut, or when it has neither an overlay nor an embedded reference, so
|
||||
// ordinary shortcuts keep the default help plus the bottom risk/tips append.
|
||||
// overlay — the same top layout as method help (description, Risk, guidance
|
||||
// block, related skills) minus the schema pointer, which shortcuts have none
|
||||
// of. Returns false when the command is not a shortcut or carries no overlay
|
||||
// entry, so shortcuts without guidance keep the default help plus the bottom
|
||||
// risk/tips append.
|
||||
//
|
||||
// The lead is the command's pristine base (captureHelpBase): a shortcut that
|
||||
// set a hand-authored Long in PostMount (e.g. the docs shortcuts' "agents MUST
|
||||
@@ -276,17 +191,12 @@ func PrepareShortcutHelp(cmd *cobra.Command, skillFS fs.FS) bool {
|
||||
if src, _ := cmdmeta.SourceOf(cmd); src != cmdmeta.SourceShortcut {
|
||||
return false
|
||||
}
|
||||
references, hasReferences := readSlidesShortcutReferences(cmd, skillFS)
|
||||
|
||||
var a meta.Affordance
|
||||
hasAffordance := false
|
||||
if raw, ok := affordanceRaw(cmd); ok {
|
||||
if parsed, parsedOK := (meta.Method{Affordance: raw}).ParsedAffordance(); parsedOK {
|
||||
a = parsed
|
||||
hasAffordance = true
|
||||
}
|
||||
raw, ok := affordanceRaw(cmd)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if !hasAffordance && !hasReferences {
|
||||
a, ok := (meta.Method{Affordance: raw}).ParsedAffordance()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if len(a.Tips) == 0 {
|
||||
@@ -301,7 +211,6 @@ func PrepareShortcutHelp(cmd *cobra.Command, skillFS fs.FS) bool {
|
||||
b.WriteString(block)
|
||||
}
|
||||
writeRelatedSkills(&b, a.Skills, skillFS)
|
||||
appendSlidesShortcutReferences(&b, references)
|
||||
|
||||
cmd.Long = b.String()
|
||||
return true
|
||||
|
||||
@@ -264,94 +264,6 @@ func TestPrepareShortcutHelp_PreservesPostMountLong(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareShortcutHelp_SlidesReferenceWithoutAffordance(t *testing.T) {
|
||||
sc := &cobra.Command{Use: "+xml-get", Short: "Fetch presentation XML"}
|
||||
cmdmeta.SetSource(sc, cmdmeta.SourceShortcut, false)
|
||||
cmdmeta.SetDomain(sc, "slides")
|
||||
cmdmeta.SetAffordanceRef(sc, "slides", "+xml-get")
|
||||
cmdutil.SetRisk(sc, "read")
|
||||
|
||||
skillFS := fstest.MapFS{
|
||||
"lark-slides/references/lark-slides-xml-presentations-get.md": {
|
||||
Data: []byte("# slides +xml-get\n\nRead the presentation XML."),
|
||||
},
|
||||
}
|
||||
if !PrepareShortcutHelp(sc, skillFS) {
|
||||
t.Fatal("PrepareShortcutHelp returned false for a Slides shortcut with an embedded reference")
|
||||
}
|
||||
for _, want := range []string{
|
||||
"Fetch presentation XML",
|
||||
"Risk: read",
|
||||
"Embedded command reference: lark-slides/references/lark-slides-xml-presentations-get.md",
|
||||
"Read the presentation XML.",
|
||||
} {
|
||||
if !strings.Contains(sc.Long, want) {
|
||||
t.Errorf("Slides shortcut help missing %q:\n%s", want, sc.Long)
|
||||
}
|
||||
}
|
||||
PrepareShortcutHelp(sc, skillFS)
|
||||
if got := strings.Count(sc.Long, "Embedded command reference:"); got != 1 {
|
||||
t.Fatalf("embedded reference appended %d times after re-render, want 1:\n%s", got, sc.Long)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlidesShortcutReferenceMapping(t *testing.T) {
|
||||
want := map[string]string{
|
||||
"+create": "lark-slides/references/lark-slides-create.md",
|
||||
"+xml-get": "lark-slides/references/lark-slides-xml-presentations-get.md",
|
||||
"+screenshot": "lark-slides/references/lark-slides-screenshot.md",
|
||||
"+media-upload": "lark-slides/references/lark-slides-media-upload.md",
|
||||
"+replace-slide": "lark-slides/references/lark-slides-replace-slide.md",
|
||||
"+replace-pages": "lark-slides/references/lark-slides-replace-pages.md",
|
||||
"+history-list": "lark-slides/references/lark-slides-history.md",
|
||||
"+history-revert": "lark-slides/references/lark-slides-history.md",
|
||||
"+history-revert-status": "lark-slides/references/lark-slides-history.md",
|
||||
}
|
||||
|
||||
for command, path := range want {
|
||||
t.Run(command, func(t *testing.T) {
|
||||
sc := &cobra.Command{Use: command, Short: command}
|
||||
cmdmeta.SetSource(sc, cmdmeta.SourceShortcut, false)
|
||||
cmdmeta.SetDomain(sc, "slides")
|
||||
skillFS := fstest.MapFS{
|
||||
path: {Data: []byte("reference content")},
|
||||
}
|
||||
contents, ok := readSlidesShortcutReferences(sc, skillFS)
|
||||
if !ok || len(contents) == 0 {
|
||||
t.Fatalf("shortcut %q has no mapped reference", command)
|
||||
}
|
||||
if !strings.Contains(contents[0], "Embedded command reference: "+path) {
|
||||
t.Fatalf("shortcut %q mapped content does not include %q:\n%s", command, path, contents[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlidesScreenshotHelpDoesNotIncludeXMLQuickReference(t *testing.T) {
|
||||
sc := &cobra.Command{Use: "+screenshot", Short: "Save screenshots"}
|
||||
cmdmeta.SetSource(sc, cmdmeta.SourceShortcut, false)
|
||||
cmdmeta.SetDomain(sc, "slides")
|
||||
skillFS := fstest.MapFS{
|
||||
"lark-slides/references/lark-slides-screenshot.md": {
|
||||
Data: []byte("# slides +screenshot\n\nSave screenshots."),
|
||||
},
|
||||
slidesXMLQuickReferencePath: {
|
||||
Data: []byte("# XML Schema Quick Reference"),
|
||||
},
|
||||
}
|
||||
|
||||
contents, ok := readSlidesShortcutReferences(sc, skillFS)
|
||||
if !ok {
|
||||
t.Fatal("screenshot shortcut should have a primary reference")
|
||||
}
|
||||
if len(contents) != 1 {
|
||||
t.Fatalf("screenshot reference count = %d, want 1: %#v", len(contents), contents)
|
||||
}
|
||||
if strings.Contains(contents[0], "XML Schema Quick Reference") {
|
||||
t.Fatalf("screenshot help must not include the XML quick reference:\n%s", contents[0])
|
||||
}
|
||||
}
|
||||
|
||||
// domainCmd wires a domain-tagged command with a subcommand under a root, the
|
||||
// shape PrepareDomainHelp expects.
|
||||
func domainCmd(short, long string) *cobra.Command {
|
||||
@@ -394,51 +306,3 @@ func TestPrepareDomainHelp_FallsBackToShort(t *testing.T) {
|
||||
t.Errorf("Short should seed Long when no hand-authored Long exists; got:\n%s", dom.Long)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareDomainHelp_SlidesIncludesEmbeddedXMLReference(t *testing.T) {
|
||||
root := &cobra.Command{Use: "root"}
|
||||
dom := &cobra.Command{Use: "slides", Short: "Slides"}
|
||||
cmdmeta.SetDomain(dom, "slides")
|
||||
dom.AddCommand(&cobra.Command{Use: "+create", Short: "Create", Run: func(*cobra.Command, []string) {}})
|
||||
root.AddCommand(dom)
|
||||
|
||||
const quickReference = `# XML Schema Quick Reference
|
||||
|
||||
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
||||
<slide>
|
||||
<data>
|
||||
<shape type="text" topLeftX="80" topLeftY="80" width="800" height="120">
|
||||
<content textType="title"><p>Title</p></content>
|
||||
</shape>
|
||||
</data>
|
||||
</slide>
|
||||
</presentation>
|
||||
|
||||
<table><colgroup><col/></colgroup><tr><td><content><p>A</p></content></td></tr></table>
|
||||
<chart><chartPlotArea/><chartData/></chart>`
|
||||
skillFS := fstest.MapFS{
|
||||
"lark-slides/SKILL.md": {Data: []byte("# slides")},
|
||||
"lark-slides/references/xml-schema-quick-ref.md": {Data: []byte(quickReference)},
|
||||
}
|
||||
|
||||
if !PrepareDomainHelp(dom, skillFS) {
|
||||
t.Fatal("PrepareDomainHelp returned false for slides domain")
|
||||
}
|
||||
for _, want := range []string{
|
||||
"Embedded XML syntax quick reference:",
|
||||
`<presentation xmlns="http://www.larkoffice.com/sml/2.0"`,
|
||||
"<shape type=\"text\"",
|
||||
"<content",
|
||||
"topLeftX",
|
||||
"<table>",
|
||||
"<chart>",
|
||||
} {
|
||||
if !strings.Contains(dom.Long, want) {
|
||||
t.Errorf("slides help missing XML reference marker %q:\n%s", want, dom.Long)
|
||||
}
|
||||
}
|
||||
PrepareDomainHelp(dom, skillFS)
|
||||
if got := strings.Count(dom.Long, "Embedded XML syntax quick reference:"); got != 1 {
|
||||
t.Fatalf("slides XML reference appended %d times after re-render, want 1:\n%s", got, dom.Long)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ const (
|
||||
HeaderShortcut = "X-Cli-Shortcut"
|
||||
HeaderExecutionId = "X-Cli-Execution-Id"
|
||||
HeaderAgentTrace = "X-Agent-Trace"
|
||||
HeaderAgentName = "X-Agent-Name"
|
||||
|
||||
SourceValue = "lark-cli"
|
||||
|
||||
@@ -56,9 +55,6 @@ func BaseSecurityHeaders() http.Header {
|
||||
if v := envvars.AgentTrace(); v != "" {
|
||||
h.Set(HeaderAgentTrace, v)
|
||||
}
|
||||
if v := envvars.AgentName(); v != "" {
|
||||
h.Set(HeaderAgentName, v)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
|
||||
@@ -263,34 +263,9 @@ func TestBaseSecurityHeaders_AllRequiredHeaders(t *testing.T) {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Agent headers injected via BaseSecurityHeaders
|
||||
// HeaderAgentTrace injection (via BaseSecurityHeaders)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestBaseSecurityHeaders_NoAgentNameHeaderWhenEnvUnset(t *testing.T) {
|
||||
t.Setenv(envvars.CliAgentName, "")
|
||||
h := BaseSecurityHeaders()
|
||||
if v := h.Get(HeaderAgentName); v != "" {
|
||||
t.Fatalf("BaseSecurityHeaders() included %s = %q, want absent when env unset", HeaderAgentName, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseSecurityHeaders_IncludesAgentNameHeaderWhenEnvSet(t *testing.T) {
|
||||
const agentName = "sample-agent"
|
||||
t.Setenv(envvars.CliAgentName, agentName)
|
||||
h := BaseSecurityHeaders()
|
||||
if v := h.Get(HeaderAgentName); v != agentName {
|
||||
t.Fatalf("BaseSecurityHeaders()[%s] = %q, want %q", HeaderAgentName, v, agentName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseSecurityHeaders_NoAgentNameHeaderWhenEnvInvalid(t *testing.T) {
|
||||
t.Setenv(envvars.CliAgentName, "agent\r\nX-Evil: attack")
|
||||
h := BaseSecurityHeaders()
|
||||
if v := h.Get(HeaderAgentName); v != "" {
|
||||
t.Fatalf("BaseSecurityHeaders() included %s = %q, want absent for invalid input", HeaderAgentName, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseSecurityHeaders_NoAgentTraceHeaderWhenEnvUnset(t *testing.T) {
|
||||
t.Setenv(envvars.CliAgentTrace, "")
|
||||
h := BaseSecurityHeaders()
|
||||
|
||||
@@ -16,18 +16,16 @@ func TestAgentName_EmptyWhenEnvUnset(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAgentName_ReturnsCleanValue(t *testing.T) {
|
||||
const agentName = "sample-agent"
|
||||
t.Setenv(CliAgentName, agentName)
|
||||
if got := AgentName(); got != agentName {
|
||||
t.Fatalf("AgentName() = %q, want %q", got, agentName)
|
||||
t.Setenv(CliAgentName, "claude-code")
|
||||
if got := AgentName(); got != "claude-code" {
|
||||
t.Fatalf("AgentName() = %q, want %q", got, "claude-code")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentName_TrimsWhitespace(t *testing.T) {
|
||||
const agentName = "sample-agent"
|
||||
t.Setenv(CliAgentName, " "+agentName+" ")
|
||||
if got := AgentName(); got != agentName {
|
||||
t.Fatalf("AgentName() = %q, want %q (whitespace trimmed)", got, agentName)
|
||||
t.Setenv(CliAgentName, " cursor ")
|
||||
if got := AgentName(); got != "cursor" {
|
||||
t.Fatalf("AgentName() = %q, want %q (whitespace trimmed)", got, "cursor")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@larksuite/cli",
|
||||
"version": "1.0.80",
|
||||
"version": "1.0.79",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@larksuite/cli",
|
||||
"version": "1.0.80",
|
||||
"version": "1.0.79",
|
||||
"cpu": [
|
||||
"x64",
|
||||
"arm64",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@larksuite/cli",
|
||||
"version": "1.0.80",
|
||||
"version": "1.0.79",
|
||||
"description": "The official CLI for Lark/Feishu open platform",
|
||||
"bin": {
|
||||
"lark-cli": "scripts/run.js"
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -251,8 +250,7 @@ func TestBaseFormQuestionsExecuteList(t *testing.T) {
|
||||
"total": 2,
|
||||
"questions": []interface{}{
|
||||
map[string]interface{}{"id": "q_001", "title": "您的姓名", "required": true, "description": nil},
|
||||
map[string]interface{}{"id": "q_002", "title": "发票抬头", "required": false, "description": nil,
|
||||
"visible_rule": map[string]interface{}{"logic": "and", "conditions": []interface{}{[]interface{}{"q_001", "==", "是"}}}},
|
||||
map[string]interface{}{"id": "q_002", "title": "您的年龄", "required": false, "description": nil},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -260,14 +258,9 @@ func TestBaseFormQuestionsExecuteList(t *testing.T) {
|
||||
if err := runShortcut(t, BaseFormQuestionsList, []string{"+form-questions-list", "--base-token", "app_x", "--table-id", "tbl_x", "--form-id", "vew_form1"}, factory, stdout); err != nil {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
got := stdout.String()
|
||||
if !strings.Contains(got, `"q_001"`) || !strings.Contains(got, `"total": 2`) {
|
||||
if got := stdout.String(); !strings.Contains(got, `"q_001"`) || !strings.Contains(got, `"total": 2`) {
|
||||
t.Fatalf("stdout=%s", got)
|
||||
}
|
||||
// The list output must forward visible_rule verbatim so agents can read existing display conditions.
|
||||
if !strings.Contains(got, `"visible_rule"`) {
|
||||
t.Fatalf("visible_rule missing from list output: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseFormQuestionsExecuteCreate(t *testing.T) {
|
||||
@@ -303,49 +296,11 @@ func TestBaseFormQuestionsExecuteCreate(t *testing.T) {
|
||||
t.Fatalf("expected error for invalid questions JSON")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("visible_rule passthrough", func(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
stub := &httpmock.Stub{
|
||||
Method: "POST",
|
||||
URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/forms/vew_form1/questions",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"questions": []interface{}{
|
||||
map[string]interface{}{"id": "q_new1", "title": "发票抬头"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
reg.Register(stub)
|
||||
args := []string{"+form-questions-create", "--base-token", "app_x", "--table-id", "tbl_x", "--form-id", "vew_form1",
|
||||
"--questions", `[{"type":"text","title":"发票抬头","visible_rule":{"logic":"and","conditions":[["是否需要发票","==","是"]]}}]`}
|
||||
if err := runShortcut(t, BaseFormQuestionsCreate, args, factory, stdout); err != nil {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
var body struct {
|
||||
Questions []map[string]interface{} `json:"questions"`
|
||||
}
|
||||
if err := json.Unmarshal(stub.CapturedBody, &body); err != nil {
|
||||
t.Fatalf("captured body json err=%v body=%s", err, string(stub.CapturedBody))
|
||||
}
|
||||
if len(body.Questions) != 1 {
|
||||
t.Fatalf("questions=%#v", body.Questions)
|
||||
}
|
||||
rule, ok := body.Questions[0]["visible_rule"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("visible_rule not forwarded verbatim: body=%s", string(stub.CapturedBody))
|
||||
}
|
||||
if rule["logic"] != "and" {
|
||||
t.Fatalf("visible_rule logic not preserved: %#v", rule)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBaseFormQuestionsExecuteUpdate(t *testing.T) {
|
||||
factory, stdout, reg := newExecuteFactory(t)
|
||||
stub := &httpmock.Stub{
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "PATCH",
|
||||
URL: "/open-apis/base/v3/bases/app_x/tables/tbl_x/forms/vew_form1/questions",
|
||||
Body: map[string]interface{}{
|
||||
@@ -356,29 +311,15 @@ func TestBaseFormQuestionsExecuteUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
reg.Register(stub)
|
||||
})
|
||||
args := []string{"+form-questions-update", "--base-token", "app_x", "--table-id", "tbl_x", "--form-id", "vew_form1",
|
||||
"--questions", `[{"id":"q_001","title":"更新后的问题","required":true,"visible_rule":{"logic":"and","conditions":[["q_002","==","是"]]}}]`}
|
||||
"--questions", `[{"id":"q_001","title":"更新后的问题","required":true}]`}
|
||||
if err := runShortcut(t, BaseFormQuestionsUpdate, args, factory, stdout); err != nil {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
if got := stdout.String(); !strings.Contains(got, `"questions"`) || !strings.Contains(got, `"q_001"`) {
|
||||
t.Fatalf("stdout=%s", got)
|
||||
}
|
||||
// visible_rule must be forwarded verbatim to the API (transcribe faithfully).
|
||||
var body struct {
|
||||
Questions []map[string]interface{} `json:"questions"`
|
||||
}
|
||||
if err := json.Unmarshal(stub.CapturedBody, &body); err != nil {
|
||||
t.Fatalf("captured body json err=%v body=%s", err, string(stub.CapturedBody))
|
||||
}
|
||||
if len(body.Questions) != 1 {
|
||||
t.Fatalf("questions=%#v", body.Questions)
|
||||
}
|
||||
if _, ok := body.Questions[0]["visible_rule"].(map[string]interface{}); !ok {
|
||||
t.Fatalf("visible_rule not forwarded verbatim: body=%s", string(stub.CapturedBody))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseFormQuestionsExecuteDelete(t *testing.T) {
|
||||
|
||||
@@ -25,21 +25,14 @@ var BaseFormQuestionsCreate = common.Shortcut{
|
||||
{Name: "base-token", Desc: "Base token (base_token)", Required: true},
|
||||
{Name: "table-id", Desc: "table ID", Required: true},
|
||||
{Name: "form-id", Desc: "form ID", Required: true},
|
||||
{Name: "questions", Desc: `questions JSON array, max 10 items. Each item requires "title"(field title) and "type"(text/number/select/datetime/user/attachment/location). Optional fields: "description"(plain text or markdown link like [text](https://example.com)),"required","option_display_mode"(0=dropdown/1=vertical/2=horizontal,select only),"multiple"(bool,select/user),"options"([{"name":"opt","hue":"Blue"}],select only),"style"({"type":"plain/phone/url/email/barcode/rating","precision":2,"format":"yyyy/MM/dd","icon":"star","min":1,"max":5}),"visible_rule"(display condition; same shape as view filter {"logic":"and","conditions":[["前序题目","==","是"]]}, field references another question's title/id, empty/absent = always shown). E.g. '[{"type":"text","title":"Your name","required":true}]'`, Required: true},
|
||||
{Name: "questions", Desc: `questions JSON array, max 10 items. Each item requires "title"(field title) and "type"(text/number/select/datetime/user/attachment/location). Optional fields: "description"(plain text or markdown link like [text](https://example.com)),"required","option_display_mode"(0=dropdown/1=vertical/2=horizontal,select only),"multiple"(bool,select/user),"options"([{"name":"opt","hue":"Blue"}],select only),"style"({"type":"plain/phone/url/email/barcode/rating","precision":2,"format":"yyyy/MM/dd","icon":"star","min":1,"max":5}). E.g. '[{"type":"text","title":"Your name","required":true}]'`, Required: true},
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
api := common.NewDryRunAPI().
|
||||
return common.NewDryRunAPI().
|
||||
POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms/:form_id/questions").
|
||||
Set("base_token", runtime.Str("base-token")).
|
||||
Set("table_id", runtime.Str("table-id")).
|
||||
Set("form_id", runtime.Str("form-id"))
|
||||
// Transcribe the questions body verbatim so the preview shows exactly
|
||||
// what would be sent (including optional fields like visible_rule).
|
||||
var questions []interface{}
|
||||
if err := json.Unmarshal([]byte(runtime.Str("questions")), &questions); err == nil {
|
||||
api.Body(map[string]interface{}{"questions": questions})
|
||||
}
|
||||
return api
|
||||
},
|
||||
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
baseToken := runtime.Str("base-token")
|
||||
|
||||
@@ -25,26 +25,14 @@ var BaseFormQuestionsUpdate = common.Shortcut{
|
||||
{Name: "base-token", Desc: "Base token (base_token)", Required: true},
|
||||
{Name: "table-id", Desc: "table ID", Required: true},
|
||||
{Name: "form-id", Desc: "form ID", Required: true},
|
||||
{Name: "questions", Desc: `questions JSON array, max 10 items, each item must include "id". Update uses full question overwrite semantics: omitted/empty fields are written as defaults/empty, so run +form-questions-list first and include existing values you want to keep. Supported fields: "id"(required),"title","description"(plain text or markdown link like [text](https://example.com)),"required","option_display_mode"(0=dropdown,1=vertical,2=horizontal,select only),"visible_rule"(display condition; same shape as view filter {"logic":"and","conditions":[["前序题目","==","是"]]}, field references another question's title/id; pass null or omit to clear). E.g. '[{"id":"q_001","title":"Updated?","required":true}]'`, Required: true},
|
||||
},
|
||||
Tips: []string{
|
||||
"Update uses full question overwrite semantics, not a patch.",
|
||||
"Run +form-questions-list first and include existing title/description/required/option_display_mode/visible_rule values you want to keep.",
|
||||
"Omitted fields reset to defaults; empty strings, null, and empty arrays are written as empty/clear when accepted by the API.",
|
||||
{Name: "questions", Desc: `questions JSON array, max 10 items, each item must include "id". Supported fields: "id"(required),"title","description"(plain text or markdown link like [text](https://example.com)),"required","option_display_mode"(0=dropdown,1=vertical,2=horizontal,select only). E.g. '[{"id":"q_001","title":"Updated?","required":true}]'`, Required: true},
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
api := common.NewDryRunAPI().
|
||||
return common.NewDryRunAPI().
|
||||
PATCH("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms/:form_id/questions").
|
||||
Set("base_token", runtime.Str("base-token")).
|
||||
Set("table_id", runtime.Str("table-id")).
|
||||
Set("form_id", runtime.Str("form-id"))
|
||||
// Transcribe the questions body verbatim so the preview shows exactly
|
||||
// what would be sent (including optional fields like visible_rule).
|
||||
var questions []interface{}
|
||||
if err := json.Unmarshal([]byte(runtime.Str("questions")), &questions); err == nil {
|
||||
api.Body(map[string]interface{}{"questions": questions})
|
||||
}
|
||||
return api
|
||||
},
|
||||
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
baseToken := runtime.Str("base-token")
|
||||
|
||||
@@ -783,20 +783,6 @@ func TestBaseJSONExamplesLiveInFlagDescriptions(t *testing.T) {
|
||||
`JSON array of question IDs to delete, max 10 items, e.g. '["q_001","q_002"]'`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "form question create visible_rule",
|
||||
shortcut: BaseFormQuestionsCreate,
|
||||
wantHelp: []string{
|
||||
`"visible_rule"(display condition; same shape as view filter`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "form question update visible_rule",
|
||||
shortcut: BaseFormQuestionsUpdate,
|
||||
wantHelp: []string{
|
||||
`"visible_rule"(display condition; same shape as view filter`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "record search json",
|
||||
shortcut: BaseRecordSearch,
|
||||
@@ -1042,39 +1028,6 @@ func TestBaseFieldUpdateHelpGuidesAgents(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseFormQuestionsUpdateHelpGuidesFullOverwrite(t *testing.T) {
|
||||
parent := &cobra.Command{Use: "base"}
|
||||
BaseFormQuestionsUpdate.Mount(parent, &cmdutil.Factory{})
|
||||
cmd := parent.Commands()[0]
|
||||
|
||||
help := cmd.Flags().FlagUsages()
|
||||
wantHelp := []string{
|
||||
"Update uses full question overwrite semantics",
|
||||
"run +form-questions-list first",
|
||||
"include existing values you want to keep",
|
||||
"pass null or omit to clear",
|
||||
}
|
||||
for _, want := range wantHelp {
|
||||
if !strings.Contains(help, want) {
|
||||
t.Fatalf("flag help missing %q:\n%s", want, help)
|
||||
}
|
||||
}
|
||||
|
||||
tips := strings.Join(cmdutil.GetTips(cmd), "\n")
|
||||
wantTips := []string{
|
||||
"full question overwrite semantics, not a patch",
|
||||
"Run +form-questions-list first",
|
||||
"title/description/required/option_display_mode/visible_rule",
|
||||
"Omitted fields reset to defaults",
|
||||
"empty strings, null, and empty arrays are written as empty/clear",
|
||||
}
|
||||
for _, want := range wantTips {
|
||||
if !strings.Contains(tips, want) {
|
||||
t.Fatalf("tips missing %q:\n%s", want, tips)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseAttachmentHelpGuidesAgents(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package drive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
)
|
||||
|
||||
type driveMemberListSpec struct {
|
||||
Token string
|
||||
Type string
|
||||
Fields string
|
||||
PermType string
|
||||
}
|
||||
|
||||
var driveMemberListTypes = []string{
|
||||
"doc", "sheet", "file", "wiki", "bitable", "docx",
|
||||
"mindnote", "minutes", "slides", "folder",
|
||||
}
|
||||
|
||||
var driveMemberListFields = []string{"name", "type", "avatar", "external_label"}
|
||||
var driveMemberListPermTypes = []string{"container", "single_page"}
|
||||
|
||||
var driveMemberListURLPathToType = []struct {
|
||||
Prefix string
|
||||
Type string
|
||||
}{
|
||||
{"/drive/folder/", "folder"},
|
||||
{"/docx/", "docx"},
|
||||
{"/doc/", "doc"},
|
||||
{"/sheets/", "sheet"},
|
||||
{"/base/", "bitable"},
|
||||
{"/bitable/", "bitable"},
|
||||
{"/wiki/", "wiki"},
|
||||
{"/file/", "file"},
|
||||
{"/mindnotes/", "mindnote"},
|
||||
{"/slides/", "slides"},
|
||||
{"/minutes/", "minutes"},
|
||||
}
|
||||
|
||||
func readDriveMemberListSpec(runtime *common.RuntimeContext) (driveMemberListSpec, error) {
|
||||
token, resourceType, err := resolveDriveMemberListTarget(runtime.Str("token"), runtime.Str("type"))
|
||||
if err != nil {
|
||||
return driveMemberListSpec{}, err
|
||||
}
|
||||
fields, err := normalizeDriveMemberListFields(runtime.Str("fields"), runtime.Changed("fields"))
|
||||
if err != nil {
|
||||
return driveMemberListSpec{}, err
|
||||
}
|
||||
permType, err := normalizeDriveMemberListPermType(runtime.Str("perm-type"), resourceType, runtime.Changed("perm-type"))
|
||||
if err != nil {
|
||||
return driveMemberListSpec{}, err
|
||||
}
|
||||
return driveMemberListSpec{
|
||||
Token: token,
|
||||
Type: resourceType,
|
||||
Fields: fields,
|
||||
PermType: permType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func resolveDriveMemberListTarget(raw, explicitType string) (token, resourceType string, err error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--token is required").WithParam("--token")
|
||||
}
|
||||
|
||||
explicitType, err = normalizeDriveMemberListEnumValue(explicitType, driveMemberListTypes, "--type")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if strings.Contains(raw, "://") {
|
||||
parsed, parseErr := url.Parse(raw)
|
||||
if parseErr != nil || parsed.Hostname() == "" {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--token URL is malformed: %q", raw).WithParam("--token")
|
||||
}
|
||||
ref, ok := parseDriveMemberListResourceURLPath(parsed.Path)
|
||||
if !ok {
|
||||
return "", "", errs.NewValidationError(
|
||||
errs.SubtypeInvalidArgument,
|
||||
"unsupported --token URL %q: pass a recognized Lark Drive document/folder URL or a bare token with --type",
|
||||
raw,
|
||||
).WithParam("--token")
|
||||
}
|
||||
if explicitType != "" && explicitType != ref.Type {
|
||||
return "", "", errs.NewValidationError(
|
||||
errs.SubtypeInvalidArgument,
|
||||
"--type %q conflicts with URL path type %q; remove --type or use a matching value",
|
||||
explicitType,
|
||||
ref.Type,
|
||||
).WithParam("--type")
|
||||
}
|
||||
if err := validate.ResourceName(ref.Token, "--token"); err != nil {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "%s", err).WithParam("--token")
|
||||
}
|
||||
return ref.Token, ref.Type, nil
|
||||
}
|
||||
|
||||
if explicitType == "" {
|
||||
return "", "", errs.NewValidationError(
|
||||
errs.SubtypeInvalidArgument,
|
||||
"--type is required when --token is a bare token; accepted values: %s",
|
||||
strings.Join(driveMemberListTypes, ", "),
|
||||
).WithParam("--type")
|
||||
}
|
||||
if err := validate.ResourceName(raw, "--token"); err != nil {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "%s", err).WithParam("--token")
|
||||
}
|
||||
return raw, explicitType, nil
|
||||
}
|
||||
|
||||
func parseDriveMemberListResourceURLPath(path string) (common.ResourceRef, bool) {
|
||||
for _, mapping := range driveMemberListURLPathToType {
|
||||
if !strings.HasPrefix(path, mapping.Prefix) {
|
||||
continue
|
||||
}
|
||||
token := path[len(mapping.Prefix):]
|
||||
token = strings.TrimRight(token, "/")
|
||||
if idx := strings.IndexByte(token, '/'); idx >= 0 {
|
||||
token = token[:idx]
|
||||
}
|
||||
token = strings.TrimSpace(token)
|
||||
if token == "" {
|
||||
return common.ResourceRef{}, false
|
||||
}
|
||||
return common.ResourceRef{Type: mapping.Type, Token: token}, true
|
||||
}
|
||||
return common.ResourceRef{}, false
|
||||
}
|
||||
|
||||
func normalizeDriveMemberListFields(raw string, changed bool) (string, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
if changed {
|
||||
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--fields cannot be blank; allowed: %s, *", strings.Join(driveMemberListFields, ", ")).WithParam("--fields")
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
parts := strings.Split(raw, ",")
|
||||
fields := make([]string, 0, len(parts))
|
||||
seen := make(map[string]bool, len(parts))
|
||||
for _, part := range parts {
|
||||
field := strings.ToLower(strings.TrimSpace(part))
|
||||
if field == "" {
|
||||
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--fields contains an empty field; allowed: %s, *", strings.Join(driveMemberListFields, ", ")).WithParam("--fields")
|
||||
}
|
||||
if field == "*" {
|
||||
if len(parts) != 1 {
|
||||
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--fields=* cannot be combined with other fields").WithParam("--fields")
|
||||
}
|
||||
return "*", nil
|
||||
}
|
||||
if !driveMemberListFieldAllowed(field) {
|
||||
return "", errs.NewValidationError(
|
||||
errs.SubtypeInvalidArgument,
|
||||
"invalid value %q for --fields, allowed: %s, *",
|
||||
strings.TrimSpace(part),
|
||||
strings.Join(driveMemberListFields, ", "),
|
||||
).WithParam("--fields")
|
||||
}
|
||||
if !seen[field] {
|
||||
fields = append(fields, field)
|
||||
seen[field] = true
|
||||
}
|
||||
}
|
||||
return strings.Join(fields, ","), nil
|
||||
}
|
||||
|
||||
func driveMemberListFieldAllowed(field string) bool {
|
||||
for _, allowed := range driveMemberListFields {
|
||||
if field == allowed {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func normalizeDriveMemberListPermType(raw, resourceType string, changed bool) (string, error) {
|
||||
permType, err := normalizeDriveMemberListEnumValue(raw, driveMemberListPermTypes, "--perm-type")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resourceType != "wiki" && changed {
|
||||
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--perm-type only applies when resource type is wiki; got %q", resourceType).WithParam("--perm-type")
|
||||
}
|
||||
return permType, nil
|
||||
}
|
||||
|
||||
func normalizeDriveMemberListEnumValue(raw string, allowed []string, flagName string) (string, error) {
|
||||
value := strings.TrimSpace(raw)
|
||||
if value == "" {
|
||||
return "", nil
|
||||
}
|
||||
for _, candidate := range allowed {
|
||||
if strings.EqualFold(value, candidate) {
|
||||
return candidate, nil
|
||||
}
|
||||
}
|
||||
return "", errs.NewValidationError(
|
||||
errs.SubtypeInvalidArgument,
|
||||
"invalid value %q for %s, allowed: %s",
|
||||
value,
|
||||
flagName,
|
||||
strings.Join(allowed, ", "),
|
||||
).WithParam(flagName)
|
||||
}
|
||||
|
||||
func (s driveMemberListSpec) apiPath() string {
|
||||
return fmt.Sprintf("/open-apis/drive/v1/permissions/%s/members", validate.EncodePathSegment(s.Token))
|
||||
}
|
||||
|
||||
func (s driveMemberListSpec) params() map[string]interface{} {
|
||||
params := map[string]interface{}{"type": s.Type}
|
||||
if s.Fields != "" {
|
||||
params["fields"] = s.Fields
|
||||
}
|
||||
if s.PermType != "" {
|
||||
params["perm_type"] = s.PermType
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
// DriveMemberList lists collaborator/member permissions on a Drive resource.
|
||||
var DriveMemberList = common.Shortcut{
|
||||
Service: "drive",
|
||||
Command: "+member-list",
|
||||
Description: "List collaborator/member permissions on a Drive document, file, folder, or wiki node",
|
||||
Risk: "read",
|
||||
Scopes: []string{"docs:permission.member:retrieve"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
HasFormat: true,
|
||||
Flags: []common.Flag{
|
||||
{Name: "token", Desc: "target URL or bare token (doc/sheet/file/wiki/bitable/docx/mindnote/minutes/slides/folder)", Required: true},
|
||||
{Name: "type", Desc: "target type; auto-inferred from URL, required for bare tokens"},
|
||||
{Name: "fields", Desc: "optional collaborator fields to return: name,type,avatar,external_label or *"},
|
||||
{Name: "perm-type", Desc: "wiki permission scope filter; one of container|single_page"},
|
||||
},
|
||||
Tips: []string{
|
||||
"--token accepts a Lark URL or bare token; pass --type when using a bare token.",
|
||||
"Use --type folder for Drive folders.",
|
||||
"--fields is omitted by default; pass --fields '*' or a comma-separated subset when extra collaborator fields are needed.",
|
||||
"--perm-type only applies to wiki nodes.",
|
||||
},
|
||||
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
_, err := readDriveMemberListSpec(runtime)
|
||||
return err
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
spec, err := readDriveMemberListSpec(runtime)
|
||||
if err != nil {
|
||||
return common.NewDryRunAPI().Set("error", err.Error())
|
||||
}
|
||||
return common.NewDryRunAPI().
|
||||
Desc("List Drive collaborator/member permissions").
|
||||
GET(spec.apiPath()).
|
||||
Params(spec.params())
|
||||
},
|
||||
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
spec, err := readDriveMemberListSpec(runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(runtime.IO().ErrOut, "Listing Drive members for %s %s...\n", spec.Type, common.MaskToken(spec.Token))
|
||||
data, err := runtime.CallAPITyped("GET", spec.apiPath(), spec.params(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if items, ok := data["items"].([]interface{}); ok {
|
||||
fmt.Fprintf(runtime.IO().ErrOut, "Found %d Drive member(s)\n", len(items))
|
||||
}
|
||||
runtime.OutFormat(data, nil, func(w io.Writer) {
|
||||
renderDriveMemberListPretty(w, data)
|
||||
})
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func renderDriveMemberListPretty(w io.Writer, data map[string]interface{}) {
|
||||
items, _ := data["items"].([]interface{})
|
||||
if len(items) == 0 {
|
||||
fmt.Fprintln(w, "No Drive members found.")
|
||||
return
|
||||
}
|
||||
for i, raw := range items {
|
||||
member, _ := raw.(map[string]interface{})
|
||||
fmt.Fprintf(w, "[%d] %s\n", i+1, driveMemberListValue(member["member_id"]))
|
||||
fmt.Fprintf(w, " member_type: %s\n", driveMemberListValue(member["member_type"]))
|
||||
fmt.Fprintf(w, " perm: %s\n", driveMemberListValue(member["perm"]))
|
||||
if permType := driveMemberListValue(member["perm_type"]); permType != "-" {
|
||||
fmt.Fprintf(w, " perm_type: %s\n", permType)
|
||||
}
|
||||
if memberType := driveMemberListValue(member["type"]); memberType != "-" {
|
||||
fmt.Fprintf(w, " type: %s\n", memberType)
|
||||
}
|
||||
if name := driveMemberListValue(member["name"]); name != "-" {
|
||||
fmt.Fprintf(w, " name: %s\n", name)
|
||||
}
|
||||
if avatar := driveMemberListValue(member["avatar"]); avatar != "-" {
|
||||
fmt.Fprintf(w, " avatar: %s\n", avatar)
|
||||
}
|
||||
if label, ok := member["external_label"]; ok {
|
||||
fmt.Fprintf(w, " external_label: %v\n", label)
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
}
|
||||
|
||||
func driveMemberListValue(v interface{}) string {
|
||||
if s, ok := v.(string); ok && s != "" {
|
||||
return s
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
@@ -1,426 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package drive
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/httpmock"
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func newDriveMemberListRuntime(t *testing.T, token, docType, fields, permType string) *common.RuntimeContext {
|
||||
t.Helper()
|
||||
|
||||
cmd := &cobra.Command{Use: "drive +member-list"}
|
||||
cmd.Flags().String("token", "", "")
|
||||
cmd.Flags().String("type", "", "")
|
||||
cmd.Flags().String("fields", "", "")
|
||||
cmd.Flags().String("perm-type", "", "")
|
||||
for name, value := range map[string]string{
|
||||
"token": token,
|
||||
"type": docType,
|
||||
"fields": fields,
|
||||
"perm-type": permType,
|
||||
} {
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if err := cmd.Flags().Set(name, value); err != nil {
|
||||
t.Fatalf("set --%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
return common.TestNewRuntimeContext(cmd, driveTestConfig())
|
||||
}
|
||||
|
||||
func TestDriveMemberListSpecResolvesTargets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
docType string
|
||||
wantTok string
|
||||
wantType string
|
||||
}{
|
||||
{
|
||||
name: "folder URL",
|
||||
token: "https://example.feishu.cn/drive/folder/fldTok?from=share",
|
||||
wantTok: "fldTok",
|
||||
wantType: "folder",
|
||||
},
|
||||
{
|
||||
name: "docx URL",
|
||||
token: "https://example.feishu.cn/docx/doxTok",
|
||||
wantTok: "doxTok",
|
||||
wantType: "docx",
|
||||
},
|
||||
{
|
||||
name: "bare folder token",
|
||||
token: " fldTok ",
|
||||
docType: " folder ",
|
||||
wantTok: "fldTok",
|
||||
wantType: "folder",
|
||||
},
|
||||
{
|
||||
name: "mindnotes URL",
|
||||
token: "https://example.feishu.cn/mindnotes/mndTok",
|
||||
wantTok: "mndTok",
|
||||
wantType: "mindnote",
|
||||
},
|
||||
{
|
||||
name: "minutes URL",
|
||||
token: "https://example.feishu.cn/minutes/obTok",
|
||||
wantTok: "obTok",
|
||||
wantType: "minutes",
|
||||
},
|
||||
}
|
||||
|
||||
for _, temp := range tests {
|
||||
tt := temp
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runtime := newDriveMemberListRuntime(t, tt.token, tt.docType, "", "")
|
||||
spec, err := readDriveMemberListSpec(runtime)
|
||||
if err != nil {
|
||||
t.Fatalf("read spec: %v", err)
|
||||
}
|
||||
if spec.Token != tt.wantTok || spec.Type != tt.wantType {
|
||||
t.Fatalf("spec token/type = %q/%q, want %q/%q", spec.Token, spec.Type, tt.wantTok, tt.wantType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriveMemberListSpecValidationErrorsAreTyped(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
docType string
|
||||
fields string
|
||||
permType string
|
||||
wantParam string
|
||||
wantMessage string
|
||||
}{
|
||||
{
|
||||
name: "missing token",
|
||||
wantParam: "--token",
|
||||
wantMessage: "--token is required",
|
||||
},
|
||||
{
|
||||
name: "bare token without type",
|
||||
token: "doxTok",
|
||||
wantParam: "--type",
|
||||
wantMessage: "--type is required",
|
||||
},
|
||||
{
|
||||
name: "unsupported URL",
|
||||
token: "https://example.feishu.cn/calendar/calTok",
|
||||
wantParam: "--token",
|
||||
wantMessage: "unsupported --token URL",
|
||||
},
|
||||
{
|
||||
name: "URL type conflict",
|
||||
token: "https://example.feishu.cn/docx/doxTok",
|
||||
docType: "folder",
|
||||
wantParam: "--type",
|
||||
wantMessage: "conflicts with URL path type",
|
||||
},
|
||||
{
|
||||
name: "invalid bare token",
|
||||
token: "../bad",
|
||||
docType: "folder",
|
||||
wantParam: "--token",
|
||||
wantMessage: "--token",
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
token: "doxTok",
|
||||
docType: "comment",
|
||||
wantParam: "--type",
|
||||
wantMessage: "invalid value",
|
||||
},
|
||||
{
|
||||
name: "invalid fields",
|
||||
token: "doxTok",
|
||||
docType: "docx",
|
||||
fields: "name,unknown",
|
||||
wantParam: "--fields",
|
||||
wantMessage: "invalid value",
|
||||
},
|
||||
{
|
||||
name: "star mixed with fields",
|
||||
token: "doxTok",
|
||||
docType: "docx",
|
||||
fields: "*,name",
|
||||
wantParam: "--fields",
|
||||
wantMessage: "cannot be combined",
|
||||
},
|
||||
{
|
||||
name: "perm type rejected for non-wiki",
|
||||
token: "doxTok",
|
||||
docType: "docx",
|
||||
permType: "single_page",
|
||||
wantParam: "--perm-type",
|
||||
wantMessage: "only applies when resource type is wiki",
|
||||
},
|
||||
}
|
||||
|
||||
for _, temp := range tests {
|
||||
tt := temp
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runtime := newDriveMemberListRuntime(t, tt.token, tt.docType, tt.fields, tt.permType)
|
||||
_, err := readDriveMemberListSpec(runtime)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error, got nil")
|
||||
}
|
||||
problem, ok := errs.ProblemOf(err)
|
||||
if !ok {
|
||||
t.Fatalf("error is not typed: %T %v", err, err)
|
||||
}
|
||||
if problem.Category != errs.CategoryValidation || problem.Subtype != errs.SubtypeInvalidArgument {
|
||||
t.Fatalf("problem = %s/%s, want validation/invalid_argument", problem.Category, problem.Subtype)
|
||||
}
|
||||
validationErr, ok := err.(*errs.ValidationError)
|
||||
if !ok {
|
||||
t.Fatalf("error type = %T, want *errs.ValidationError", err)
|
||||
}
|
||||
if validationErr.Param != tt.wantParam {
|
||||
t.Fatalf("param = %q, want %q", validationErr.Param, tt.wantParam)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.wantMessage) {
|
||||
t.Fatalf("error = %q, want substring %q", err.Error(), tt.wantMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriveMemberListSpecParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
docType string
|
||||
fields string
|
||||
permType string
|
||||
want map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "default omits optional params",
|
||||
token: "doxTok",
|
||||
docType: "docx",
|
||||
want: map[string]interface{}{"type": "docx"},
|
||||
},
|
||||
{
|
||||
name: "fields canonicalized and deduplicated",
|
||||
token: "doxTok",
|
||||
docType: "docx",
|
||||
fields: "Name,avatar,name",
|
||||
want: map[string]interface{}{"type": "docx", "fields": "name,avatar"},
|
||||
},
|
||||
{
|
||||
name: "wiki accepts perm type",
|
||||
token: "wikTok",
|
||||
docType: "WIKI",
|
||||
fields: "*",
|
||||
permType: "SINGLE_PAGE",
|
||||
want: map[string]interface{}{"type": "wiki", "fields": "*", "perm_type": "single_page"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, temp := range tests {
|
||||
tt := temp
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runtime := newDriveMemberListRuntime(t, tt.token, tt.docType, tt.fields, tt.permType)
|
||||
spec, err := readDriveMemberListSpec(runtime)
|
||||
if err != nil {
|
||||
t.Fatalf("read spec: %v", err)
|
||||
}
|
||||
if got := spec.params(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Fatalf("params = %#v, want %#v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriveMemberListDryRunIncludesGETRequest(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
f, stdout, _, _ := cmdutil.TestFactory(t, driveTestConfig())
|
||||
err := mountAndRunDrive(t, DriveMemberList, []string{
|
||||
"+member-list",
|
||||
"--token", "https://example.feishu.cn/drive/folder/fldTok",
|
||||
"--fields", "*",
|
||||
"--dry-run",
|
||||
"--as", "bot",
|
||||
}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var got struct {
|
||||
Data struct {
|
||||
API []struct {
|
||||
Method string `json:"method"`
|
||||
URL string `json:"url"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
} `json:"api"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(stdout.Bytes(), &got); err != nil {
|
||||
t.Fatalf("decode dry-run output: %v\n%s", err, stdout.String())
|
||||
}
|
||||
if len(got.Data.API) != 1 {
|
||||
t.Fatalf("api count = %d, want 1", len(got.Data.API))
|
||||
}
|
||||
api := got.Data.API[0]
|
||||
if api.Method != "GET" || api.URL != "/open-apis/drive/v1/permissions/fldTok/members" {
|
||||
t.Fatalf("api = %#v", api)
|
||||
}
|
||||
if api.Params["type"] != "folder" || api.Params["fields"] != "*" {
|
||||
t.Fatalf("params = %#v", api.Params)
|
||||
}
|
||||
if _, ok := api.Params["perm_type"]; ok {
|
||||
t.Fatalf("perm_type should be omitted for folder: %#v", api.Params)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriveMemberListExecutePreservesRawData(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
f, stdout, stderr, reg := cmdutil.TestFactory(t, driveTestConfig())
|
||||
|
||||
var capturedQuery string
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/drive/v1/permissions/doxTok/members",
|
||||
OnMatch: func(req *http.Request) {
|
||||
capturedQuery = req.URL.RawQuery
|
||||
},
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{
|
||||
"items": []interface{}{
|
||||
map[string]interface{}{
|
||||
"member_id": "ou_x",
|
||||
"member_type": "openid",
|
||||
"perm": "view",
|
||||
"type": "user",
|
||||
"name": "zhangsan",
|
||||
"server_future": "preserved",
|
||||
"external_label": true,
|
||||
},
|
||||
},
|
||||
"server_top_level": "preserved",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
err := mountAndRunDrive(t, DriveMemberList, []string{
|
||||
"+member-list",
|
||||
"--token", "doxTok",
|
||||
"--type", "docx",
|
||||
"--fields", "name,type,external_label",
|
||||
"--as", "bot",
|
||||
}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(capturedQuery, "type=docx") ||
|
||||
!strings.Contains(capturedQuery, "fields=name%2Ctype%2Cexternal_label") {
|
||||
t.Fatalf("captured query = %q", capturedQuery)
|
||||
}
|
||||
data := decodeDriveEnvelope(t, stdout)
|
||||
if data["server_top_level"] != "preserved" {
|
||||
t.Fatalf("server_top_level = %#v", data["server_top_level"])
|
||||
}
|
||||
for _, key := range []string{"token", "type", "count"} {
|
||||
if _, ok := data[key]; ok {
|
||||
t.Fatalf("data[%s] = %#v, want omitted", key, data[key])
|
||||
}
|
||||
}
|
||||
items, _ := data["items"].([]interface{})
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("items = %#v, want one item", data["items"])
|
||||
}
|
||||
item, _ := items[0].(map[string]interface{})
|
||||
if item["server_future"] != "preserved" || item["external_label"] != true {
|
||||
t.Fatalf("item future fields not preserved: %#v", item)
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "Found 1 Drive member") {
|
||||
t.Fatalf("stderr = %q, want count log", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriveMemberListDeclaresScopeAndIdentities(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if !reflect.DeepEqual(DriveMemberList.Scopes, []string{"docs:permission.member:retrieve"}) {
|
||||
t.Fatalf("Scopes = %v, want docs:permission.member:retrieve", DriveMemberList.Scopes)
|
||||
}
|
||||
if !reflect.DeepEqual(DriveMemberList.AuthTypes, []string{"user", "bot"}) {
|
||||
t.Fatalf("AuthTypes = %v, want [user bot]", DriveMemberList.AuthTypes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriveMemberListPrettyOutput(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig())
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/drive/v1/permissions/wikTok/members",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{
|
||||
"items": []interface{}{
|
||||
map[string]interface{}{
|
||||
"member_id": "ou_x",
|
||||
"member_type": "openid",
|
||||
"perm": "view",
|
||||
"perm_type": "single_page",
|
||||
"type": "user",
|
||||
"name": "zhangsan",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
err := mountAndRunDrive(t, DriveMemberList, []string{
|
||||
"+member-list",
|
||||
"--token", "wikTok",
|
||||
"--type", "wiki",
|
||||
"--perm-type", "single_page",
|
||||
"--format", "pretty",
|
||||
"--as", "bot",
|
||||
}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
out := stdout.String()
|
||||
for _, want := range []string{"[1] ou_x", "member_type: openid", "perm_type: single_page", "name: zhangsan"} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Fatalf("pretty output missing %q:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package drive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
)
|
||||
|
||||
type drivePermissionGetSettingSpec struct {
|
||||
Token string
|
||||
Type string
|
||||
}
|
||||
|
||||
var drivePermissionGetSettingTypes = []string{
|
||||
"doc", "sheet", "file", "wiki", "bitable", "docx",
|
||||
"mindnote", "minutes", "slides", "folder",
|
||||
}
|
||||
|
||||
var drivePermissionGetSettingURLPathToType = []struct {
|
||||
Prefix string
|
||||
Type string
|
||||
}{
|
||||
{"/drive/folder/", "folder"},
|
||||
{"/docx/", "docx"},
|
||||
{"/doc/", "doc"},
|
||||
{"/sheets/", "sheet"},
|
||||
{"/base/", "bitable"},
|
||||
{"/bitable/", "bitable"},
|
||||
{"/wiki/", "wiki"},
|
||||
{"/file/", "file"},
|
||||
{"/mindnotes/", "mindnote"},
|
||||
{"/slides/", "slides"},
|
||||
{"/minutes/", "minutes"},
|
||||
}
|
||||
|
||||
func readDrivePermissionGetSettingSpec(runtime *common.RuntimeContext) (drivePermissionGetSettingSpec, error) {
|
||||
rawToken := strings.TrimSpace(runtime.Str("token"))
|
||||
explicitType := strings.ToLower(strings.TrimSpace(runtime.Str("type")))
|
||||
|
||||
if rawToken == "" {
|
||||
return drivePermissionGetSettingSpec{}, errs.NewValidationError(
|
||||
errs.SubtypeInvalidArgument,
|
||||
"--token is required",
|
||||
).WithParam("--token")
|
||||
}
|
||||
|
||||
if explicitType != "" && !drivePermissionGetSettingTypeAllowed(explicitType) {
|
||||
return drivePermissionGetSettingSpec{}, errs.NewValidationError(
|
||||
errs.SubtypeInvalidArgument,
|
||||
"invalid --type %q: allowed values are %s",
|
||||
explicitType,
|
||||
strings.Join(drivePermissionGetSettingTypes, ", "),
|
||||
).WithParam("--type")
|
||||
}
|
||||
|
||||
if strings.Contains(rawToken, "://") {
|
||||
ref, ok := parseDrivePermissionGetSettingResourceURL(rawToken)
|
||||
if !ok {
|
||||
return drivePermissionGetSettingSpec{}, errs.NewValidationError(
|
||||
errs.SubtypeInvalidArgument,
|
||||
"unsupported --token URL %q: pass a recognized Lark Drive document/folder URL or a bare token with --type",
|
||||
rawToken,
|
||||
).WithParam("--token")
|
||||
}
|
||||
if explicitType != "" && explicitType != ref.Type {
|
||||
return drivePermissionGetSettingSpec{}, errs.NewValidationError(
|
||||
errs.SubtypeInvalidArgument,
|
||||
"--type %q conflicts with URL path type %q; remove --type or use a matching value",
|
||||
explicitType,
|
||||
ref.Type,
|
||||
).WithParam("--type")
|
||||
}
|
||||
if err := validate.ResourceName(ref.Token, "--token"); err != nil {
|
||||
return drivePermissionGetSettingSpec{}, errs.NewValidationError(errs.SubtypeInvalidArgument, "%s", err).WithParam("--token")
|
||||
}
|
||||
return drivePermissionGetSettingSpec{Token: ref.Token, Type: ref.Type}, nil
|
||||
}
|
||||
|
||||
if explicitType == "" {
|
||||
return drivePermissionGetSettingSpec{}, errs.NewValidationError(
|
||||
errs.SubtypeInvalidArgument,
|
||||
"--type is required when --token is a bare token (allowed: %s)",
|
||||
strings.Join(drivePermissionGetSettingTypes, ", "),
|
||||
).WithParam("--type")
|
||||
}
|
||||
|
||||
if err := validate.ResourceName(rawToken, "--token"); err != nil {
|
||||
return drivePermissionGetSettingSpec{}, errs.NewValidationError(errs.SubtypeInvalidArgument, "%s", err).WithParam("--token")
|
||||
}
|
||||
return drivePermissionGetSettingSpec{Token: rawToken, Type: explicitType}, nil
|
||||
}
|
||||
|
||||
func parseDrivePermissionGetSettingResourceURL(rawURL string) (common.ResourceRef, bool) {
|
||||
parsed, err := url.Parse(strings.TrimSpace(rawURL))
|
||||
if err != nil || parsed.Hostname() == "" {
|
||||
return common.ResourceRef{}, false
|
||||
}
|
||||
|
||||
for _, mapping := range drivePermissionGetSettingURLPathToType {
|
||||
if !strings.HasPrefix(parsed.Path, mapping.Prefix) {
|
||||
continue
|
||||
}
|
||||
token := parsed.Path[len(mapping.Prefix):]
|
||||
token = strings.TrimRight(token, "/")
|
||||
if idx := strings.IndexByte(token, '/'); idx >= 0 {
|
||||
token = token[:idx]
|
||||
}
|
||||
token = strings.TrimSpace(token)
|
||||
if token == "" {
|
||||
return common.ResourceRef{}, false
|
||||
}
|
||||
return common.ResourceRef{Type: mapping.Type, Token: token}, true
|
||||
}
|
||||
|
||||
return common.ResourceRef{}, false
|
||||
}
|
||||
|
||||
func drivePermissionGetSettingTypeAllowed(docType string) bool {
|
||||
for _, allowed := range drivePermissionGetSettingTypes {
|
||||
if docType == allowed {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s drivePermissionGetSettingSpec) url(runtime *common.RuntimeContext) string {
|
||||
if runtime != nil && runtime.Config != nil {
|
||||
if u := common.BuildResourceURL(runtime.Config.Brand, s.Type, s.Token); u != "" {
|
||||
return u
|
||||
}
|
||||
}
|
||||
return common.BuildResourceURL("", s.Type, s.Token)
|
||||
}
|
||||
|
||||
func (s drivePermissionGetSettingSpec) params() map[string]interface{} {
|
||||
return map[string]interface{}{"type": s.Type}
|
||||
}
|
||||
|
||||
func (s drivePermissionGetSettingSpec) apiPath() string {
|
||||
return drivePermissionPublicV2Path(s.Token)
|
||||
}
|
||||
|
||||
func drivePermissionPublicV2Path(token string) string {
|
||||
return fmt.Sprintf("/open-apis/drive/v2/permissions/%s/public", validate.EncodePathSegment(token))
|
||||
}
|
||||
|
||||
func drivePermissionGetSettingPermissionPublic(data map[string]interface{}) (map[string]interface{}, error) {
|
||||
permissionPublic := common.GetMap(data, "permission_public")
|
||||
if permissionPublic == nil {
|
||||
return nil, errs.NewInternalError(
|
||||
errs.SubtypeInvalidResponse,
|
||||
"drive permission get response missing data.permission_public",
|
||||
)
|
||||
}
|
||||
return permissionPublic, nil
|
||||
}
|
||||
|
||||
// DrivePermissionGetSetting queries permission_public settings for a Drive
|
||||
// document, file, wiki node, or folder.
|
||||
var DrivePermissionGetSetting = common.Shortcut{
|
||||
Service: "drive",
|
||||
Command: "+permission-get-setting",
|
||||
Description: "Get public access, sharing, collaborator management, security, and comment permission settings",
|
||||
Risk: "read",
|
||||
Scopes: []string{"docs:permission.setting:read"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
HasFormat: true,
|
||||
Flags: []common.Flag{
|
||||
{Name: "token", Desc: "target URL or bare token (doc/sheet/file/wiki/bitable/docx/mindnote/minutes/slides/folder)", Required: true},
|
||||
{Name: "type", Desc: "target type; auto-inferred from URL, required for bare tokens", Enum: drivePermissionGetSettingTypes},
|
||||
},
|
||||
Tips: []string{
|
||||
"--token accepts a Lark URL or bare token; pass --type when using a bare token.",
|
||||
"Use --type folder for Drive folders. This shortcut reads the target's own permission settings; it does not recurse into child documents.",
|
||||
},
|
||||
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
_, err := readDrivePermissionGetSettingSpec(runtime)
|
||||
return err
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
spec, err := readDrivePermissionGetSettingSpec(runtime)
|
||||
if err != nil {
|
||||
return common.NewDryRunAPI().Set("error", err.Error())
|
||||
}
|
||||
return common.NewDryRunAPI().
|
||||
Desc("Get Drive permission settings").
|
||||
GET(spec.apiPath()).
|
||||
Params(spec.params())
|
||||
},
|
||||
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
spec, err := readDrivePermissionGetSettingSpec(runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(runtime.IO().ErrOut, "Getting permission settings for %s %s...\n", spec.Type, common.MaskToken(spec.Token))
|
||||
data, err := runtime.CallAPITyped(
|
||||
"GET",
|
||||
spec.apiPath(),
|
||||
spec.params(),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
permissionPublic, err := drivePermissionGetSettingPermissionPublic(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
permissionPublicPretty, err := json.MarshalIndent(permissionPublic, "", " ")
|
||||
if err != nil {
|
||||
return errs.NewInternalError(
|
||||
errs.SubtypeInvalidResponse,
|
||||
"encode drive permission settings for pretty output",
|
||||
).WithCause(err)
|
||||
}
|
||||
|
||||
out := map[string]interface{}{"permission_public": permissionPublic}
|
||||
runtime.OutFormat(out, nil, func(w io.Writer) {
|
||||
fmt.Fprintf(w, "Type: %s\n", spec.Type)
|
||||
fmt.Fprintf(w, "Token: %s\n", spec.Token)
|
||||
if url := spec.url(runtime); url != "" {
|
||||
fmt.Fprintf(w, "URL: %s\n", url)
|
||||
}
|
||||
fmt.Fprintf(w, "Permission settings:\n%s\n", permissionPublicPretty)
|
||||
})
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -1,438 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package drive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/httpmock"
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func newDrivePermissionGetSettingRuntime(t *testing.T, token, docType string) *common.RuntimeContext {
|
||||
t.Helper()
|
||||
|
||||
cmd := &cobra.Command{Use: "drive +permission-get-setting"}
|
||||
cmd.Flags().String("token", "", "")
|
||||
cmd.Flags().String("type", "", "")
|
||||
if token != "" {
|
||||
if err := cmd.Flags().Set("token", token); err != nil {
|
||||
t.Fatalf("set --token: %v", err)
|
||||
}
|
||||
}
|
||||
if docType != "" {
|
||||
if err := cmd.Flags().Set("type", docType); err != nil {
|
||||
t.Fatalf("set --type: %v", err)
|
||||
}
|
||||
}
|
||||
return common.TestNewRuntimeContext(cmd, driveTestConfig())
|
||||
}
|
||||
|
||||
func TestDrivePermissionGetSettingSpecResolvesTargets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
docType string
|
||||
wantTok string
|
||||
wantType string
|
||||
}{
|
||||
{
|
||||
name: "folder URL",
|
||||
token: "https://example.feishu.cn/drive/folder/fldTok?from=share",
|
||||
wantTok: "fldTok",
|
||||
wantType: "folder",
|
||||
},
|
||||
{
|
||||
name: "docx URL",
|
||||
token: "https://example.feishu.cn/docx/doxTok",
|
||||
wantTok: "doxTok",
|
||||
wantType: "docx",
|
||||
},
|
||||
{
|
||||
name: "file URL",
|
||||
token: "https://example.feishu.cn/file/boxTok",
|
||||
wantTok: "boxTok",
|
||||
wantType: "file",
|
||||
},
|
||||
{
|
||||
name: "wiki URL",
|
||||
token: "https://example.feishu.cn/wiki/wikTok",
|
||||
wantTok: "wikTok",
|
||||
wantType: "wiki",
|
||||
},
|
||||
{
|
||||
name: "minutes URL",
|
||||
token: "https://example.feishu.cn/minutes/obTok",
|
||||
wantTok: "obTok",
|
||||
wantType: "minutes",
|
||||
},
|
||||
{
|
||||
name: "mindnotes URL",
|
||||
token: "https://example.feishu.cn/mindnotes/mndTok",
|
||||
wantTok: "mndTok",
|
||||
wantType: "mindnote",
|
||||
},
|
||||
{
|
||||
name: "bare folder token",
|
||||
token: " fldTok ",
|
||||
docType: " folder ",
|
||||
wantTok: "fldTok",
|
||||
wantType: "folder",
|
||||
},
|
||||
{
|
||||
name: "bare file token",
|
||||
token: "boxTok",
|
||||
docType: "file",
|
||||
wantTok: "boxTok",
|
||||
wantType: "file",
|
||||
},
|
||||
{
|
||||
name: "bare wiki token",
|
||||
token: "wikTok",
|
||||
docType: "wiki",
|
||||
wantTok: "wikTok",
|
||||
wantType: "wiki",
|
||||
},
|
||||
}
|
||||
|
||||
for _, temp := range tests {
|
||||
tt := temp
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runtime := newDrivePermissionGetSettingRuntime(t, tt.token, tt.docType)
|
||||
spec, err := readDrivePermissionGetSettingSpec(runtime)
|
||||
if err != nil {
|
||||
t.Fatalf("read spec: %v", err)
|
||||
}
|
||||
if spec.Token != tt.wantTok {
|
||||
t.Fatalf("Token = %q, want %q", spec.Token, tt.wantTok)
|
||||
}
|
||||
if spec.Type != tt.wantType {
|
||||
t.Fatalf("Type = %q, want %q", spec.Type, tt.wantType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrivePermissionGetSettingSpecValidationErrorsAreTyped(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
docType string
|
||||
wantParam string
|
||||
wantMessage string
|
||||
}{
|
||||
{
|
||||
name: "missing token",
|
||||
wantParam: "--token",
|
||||
wantMessage: "--token is required",
|
||||
},
|
||||
{
|
||||
name: "bare token without type",
|
||||
token: "doxTok",
|
||||
wantParam: "--type",
|
||||
wantMessage: "--type is required",
|
||||
},
|
||||
{
|
||||
name: "unsupported URL",
|
||||
token: "https://example.feishu.cn/calendar/calTok",
|
||||
wantParam: "--token",
|
||||
wantMessage: "unsupported --token URL",
|
||||
},
|
||||
{
|
||||
name: "URL type conflict",
|
||||
token: "https://example.feishu.cn/docx/doxTok",
|
||||
docType: "sheet",
|
||||
wantParam: "--type",
|
||||
wantMessage: "conflicts with URL path type",
|
||||
},
|
||||
{
|
||||
name: "invalid bare token",
|
||||
token: "../bad",
|
||||
docType: "folder",
|
||||
wantParam: "--token",
|
||||
wantMessage: "--token",
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
token: "doxTok",
|
||||
docType: "comment",
|
||||
wantParam: "--type",
|
||||
wantMessage: "invalid --type",
|
||||
},
|
||||
}
|
||||
|
||||
for _, temp := range tests {
|
||||
tt := temp
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runtime := newDrivePermissionGetSettingRuntime(t, tt.token, tt.docType)
|
||||
_, err := readDrivePermissionGetSettingSpec(runtime)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error, got nil")
|
||||
}
|
||||
problem, ok := errs.ProblemOf(err)
|
||||
if !ok {
|
||||
t.Fatalf("error is not typed: %T %v", err, err)
|
||||
}
|
||||
if problem.Category != errs.CategoryValidation || problem.Subtype != errs.SubtypeInvalidArgument {
|
||||
t.Fatalf("problem = %s/%s, want validation/invalid_argument", problem.Category, problem.Subtype)
|
||||
}
|
||||
if validationErr, ok := err.(*errs.ValidationError); ok {
|
||||
if validationErr.Param != tt.wantParam {
|
||||
t.Fatalf("param = %q, want %q", validationErr.Param, tt.wantParam)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("error type = %T, want *errs.ValidationError", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.wantMessage) {
|
||||
t.Fatalf("error = %q, want substring %q", err.Error(), tt.wantMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrivePermissionGetSettingDryRunIncludesGETRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
docType string
|
||||
wantURL string
|
||||
wantType string
|
||||
}{
|
||||
{
|
||||
name: "folder URL",
|
||||
token: "https://example.feishu.cn/drive/folder/fldTok",
|
||||
wantURL: "/open-apis/drive/v2/permissions/fldTok/public",
|
||||
wantType: "folder",
|
||||
},
|
||||
{
|
||||
name: "bare folder token",
|
||||
token: "fldTok",
|
||||
docType: "folder",
|
||||
wantURL: "/open-apis/drive/v2/permissions/fldTok/public",
|
||||
wantType: "folder",
|
||||
},
|
||||
{
|
||||
name: "docx URL",
|
||||
token: "https://example.feishu.cn/docx/doxTok",
|
||||
wantURL: "/open-apis/drive/v2/permissions/doxTok/public",
|
||||
wantType: "docx",
|
||||
},
|
||||
{
|
||||
name: "bare wiki token",
|
||||
token: "wikTok",
|
||||
docType: "wiki",
|
||||
wantURL: "/open-apis/drive/v2/permissions/wikTok/public",
|
||||
wantType: "wiki",
|
||||
},
|
||||
{
|
||||
name: "file URL",
|
||||
token: "https://example.feishu.cn/file/boxTok",
|
||||
wantURL: "/open-apis/drive/v2/permissions/boxTok/public",
|
||||
wantType: "file",
|
||||
},
|
||||
{
|
||||
name: "minutes URL",
|
||||
token: "https://example.feishu.cn/minutes/obTok",
|
||||
wantURL: "/open-apis/drive/v2/permissions/obTok/public",
|
||||
wantType: "minutes",
|
||||
},
|
||||
{
|
||||
name: "mindnotes URL",
|
||||
token: "https://example.feishu.cn/mindnotes/mndTok",
|
||||
wantURL: "/open-apis/drive/v2/permissions/mndTok/public",
|
||||
wantType: "mindnote",
|
||||
},
|
||||
}
|
||||
|
||||
for _, temp := range tests {
|
||||
tt := temp
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runtime := newDrivePermissionGetSettingRuntime(t, tt.token, tt.docType)
|
||||
dry := DrivePermissionGetSetting.DryRun(context.Background(), runtime)
|
||||
if dry == nil {
|
||||
t.Fatal("DryRun returned nil")
|
||||
}
|
||||
data, err := json.Marshal(dry)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal dry-run: %v", err)
|
||||
}
|
||||
out := string(data)
|
||||
for _, want := range []string{
|
||||
`"` + tt.wantURL + `"`,
|
||||
`"GET"`,
|
||||
`"type":"` + tt.wantType + `"`,
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Fatalf("dry-run output missing %q:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
if strings.Contains(out, `"folder_token"`) {
|
||||
t.Fatalf("dry-run output contains folder_token, want omitted:\n%s", out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrivePermissionGetSettingExecutePreservesPermissionPublic(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig())
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/drive/v2/permissions/doxTok/public?type=docx",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"msg": "ok",
|
||||
"data": map[string]interface{}{
|
||||
"permission_public": map[string]interface{}{
|
||||
"link_share_entity": "closed",
|
||||
"external_access_entity": "closed",
|
||||
"security_entity": "anyone_can_view",
|
||||
"comment_entity": "anyone_can_view",
|
||||
"share_entity": "anyone",
|
||||
"manage_collaborator_entity": "collaborator_can_view",
|
||||
"lock_switch": false,
|
||||
"server_future_field": "preserved",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
err := mountAndRunDrive(t, DrivePermissionGetSetting, []string{
|
||||
"+permission-get-setting",
|
||||
"--token", "doxTok",
|
||||
"--type", "docx",
|
||||
"--as", "bot",
|
||||
}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
data := decodeDriveEnvelope(t, stdout)
|
||||
for _, key := range []string{"type", "token", "url"} {
|
||||
if _, ok := data[key]; ok {
|
||||
t.Fatalf("data[%s] = %#v, want field omitted", key, data[key])
|
||||
}
|
||||
}
|
||||
permissionPublic, _ := data["permission_public"].(map[string]interface{})
|
||||
if permissionPublic == nil {
|
||||
t.Fatalf("permission_public missing in output: %#v", data)
|
||||
}
|
||||
for key, want := range map[string]interface{}{
|
||||
"link_share_entity": "closed",
|
||||
"external_access_entity": "closed",
|
||||
"security_entity": "anyone_can_view",
|
||||
"comment_entity": "anyone_can_view",
|
||||
"share_entity": "anyone",
|
||||
"manage_collaborator_entity": "collaborator_can_view",
|
||||
"lock_switch": false,
|
||||
"server_future_field": "preserved",
|
||||
} {
|
||||
if permissionPublic[key] != want {
|
||||
t.Fatalf("permission_public[%s] = %#v, want %#v", key, permissionPublic[key], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrivePermissionGetSettingExecuteRejectsMissingPermissionPublic(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig())
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/drive/v2/permissions/doxTok/public?type=docx",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"msg": "ok",
|
||||
"data": map[string]interface{}{"unexpected": "response"},
|
||||
},
|
||||
})
|
||||
|
||||
err := mountAndRunDrive(t, DrivePermissionGetSetting, []string{
|
||||
"+permission-get-setting",
|
||||
"--token", "doxTok",
|
||||
"--type", "docx",
|
||||
"--as", "bot",
|
||||
}, f, stdout)
|
||||
if err == nil {
|
||||
t.Fatal("expected invalid response error, got nil")
|
||||
}
|
||||
problem, ok := errs.ProblemOf(err)
|
||||
if !ok || problem.Category != errs.CategoryInternal || problem.Subtype != errs.SubtypeInvalidResponse {
|
||||
t.Fatalf("problem = %#v, want internal/invalid_response", problem)
|
||||
}
|
||||
if stdout.Len() != 0 {
|
||||
t.Fatalf("stdout should be empty on invalid response, got %s", stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrivePermissionGetSettingExecutePrettyFormatIncludesPermissionPublic(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, driveTestConfig())
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/drive/v2/permissions/doxTok/public?type=docx",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"msg": "ok",
|
||||
"data": map[string]interface{}{
|
||||
"permission_public": map[string]interface{}{
|
||||
"link_share_entity": "closed",
|
||||
"server_future_field": "preserved",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
err := mountAndRunDrive(t, DrivePermissionGetSetting, []string{
|
||||
"+permission-get-setting",
|
||||
"--token", "doxTok",
|
||||
"--type", "docx",
|
||||
"--format", "pretty",
|
||||
"--as", "bot",
|
||||
}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
for _, want := range []string{
|
||||
"Permission settings:",
|
||||
`"link_share_entity": "closed"`,
|
||||
`"server_future_field": "preserved"`,
|
||||
} {
|
||||
if !strings.Contains(stdout.String(), want) {
|
||||
t.Fatalf("pretty output missing %q:\n%s", want, stdout.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrivePermissionGetSettingDeclaresScopeAndIdentities(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if !reflect.DeepEqual(DrivePermissionGetSetting.Scopes, []string{"docs:permission.setting:read"}) {
|
||||
t.Fatalf("Scopes = %v, want docs:permission.setting:read", DrivePermissionGetSetting.Scopes)
|
||||
}
|
||||
if !reflect.DeepEqual(DrivePermissionGetSetting.AuthTypes, []string{"user", "bot"}) {
|
||||
t.Fatalf("AuthTypes = %v, want [user bot]", DrivePermissionGetSetting.AuthTypes)
|
||||
}
|
||||
for _, flag := range DrivePermissionGetSetting.Flags {
|
||||
if flag.Name == "token" && !flag.Required {
|
||||
t.Fatal("--token must be declared required")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,6 @@ func Shortcuts() []common.Shortcut {
|
||||
DriveTaskResult,
|
||||
DriveApplyPermission,
|
||||
DriveMemberAdd,
|
||||
DriveMemberList,
|
||||
DrivePermissionGetSetting,
|
||||
DriveSecureLabelList,
|
||||
DriveSecureLabelUpdate,
|
||||
DriveSearch,
|
||||
|
||||
@@ -39,8 +39,6 @@ func TestShortcutsIncludesExpectedCommands(t *testing.T) {
|
||||
"+task_result",
|
||||
"+apply-permission",
|
||||
"+member-add",
|
||||
"+member-list",
|
||||
"+permission-get-setting",
|
||||
"+secure-label-list",
|
||||
"+secure-label-update",
|
||||
"+search",
|
||||
|
||||
@@ -57,12 +57,12 @@ metadata:
|
||||
| 写记录 | `+record-upsert` / `+record-batch-create` / `+record-batch-update` | 必读 [lark-base-record-upsert.md](references/lark-base-record-upsert.md) / [lark-base-record-batch-create.md](references/lark-base-record-batch-create.md) / [lark-base-record-batch-update.md](references/lark-base-record-batch-update.md) 和 [lark-base-cell-value.md](references/lark-base-cell-value.md) |
|
||||
| 附件字段 | `+record-upload-attachment` / `+record-download-attachment` / `+record-remove-attachment` | 附件不要伪造成普通 CellValue;上传走本地文件,下载/删除按 file token 或字段定位 |
|
||||
| 删除记录 / 分享记录链接 / 历史 | `+record-delete` / `+record-share-link-create` / `+record-history-list` | 删除前确认 record;分享链接最多 100 条;历史读 [lark-base-record-history-list.md](references/lark-base-record-history-list.md),只查单条记录,不做整表审计 |
|
||||
| 管理视图 | `+view-*` | `+view-set-filter` 读 [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md)(filter 条件结构见公共协议 [lark-base-filter-condition.md](references/lark-base-filter-condition.md));其余配置先 get 现状,再按返回结构更新 |
|
||||
| 管理视图 | `+view-*` | `+view-set-filter` 读 [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md);其余配置先 get 现状,再按返回结构更新 |
|
||||
| 一次性聚合统计 | `+data-query` | 必读 [lark-base-data-analysis-sop.md](references/lark-base-data-analysis-sop.md) 和入口 [lark-base-data-query-guide.md](references/lark-base-data-query-guide.md);完整 DSL 再读 [lark-base-data-query.md](references/lark-base-data-query.md) |
|
||||
| 公式字段 | `+field-create/update --json '{"type":"formula",...}'` | 必读 [formula-field-guide.md](references/formula-field-guide.md),读后再加隐藏确认 flag `--i-have-read-guide` |
|
||||
| Lookup 字段 | `+field-create/update --json '{"type":"lookup",...}'` | 必读 [lookup-field-guide.md](references/lookup-field-guide.md),读后再加隐藏确认 flag `--i-have-read-guide` |
|
||||
| 表单提交 | `+form-submit` | 先读 [lark-base-form-detail.md](references/lark-base-form-detail.md) 获取题目、filter 和附件所需 `base_token`;提交 JSON 读 [lark-base-form-submit.md](references/lark-base-form-submit.md) |
|
||||
| 表单题目创建/更新 | `+form-questions-create` / `+form-questions-update` | 读 [lark-base-form-questions-create.md](references/lark-base-form-questions-create.md) / [lark-base-form-questions-update.md](references/lark-base-form-questions-update.md);题目显隐条件 `visible_rule` 结构见公共协议 [lark-base-filter-condition.md](references/lark-base-filter-condition.md) |
|
||||
| 表单题目创建/更新 | `+form-questions-create` / `+form-questions-update` | 读 [lark-base-form-questions-create.md](references/lark-base-form-questions-create.md) / [lark-base-form-questions-update.md](references/lark-base-form-questions-update.md) |
|
||||
| 其他表单管理 | `+form-list/get/detail/create/update/delete` / `+form-questions-list/delete` | `+form-detail` 读 [lark-base-form-detail.md](references/lark-base-form-detail.md);删除前确认目标表单 |
|
||||
| 仪表盘与组件 | `+dashboard-*` / `+dashboard-block-*` | 提到图表/看板/block 时先读 [lark-base-dashboard.md](references/lark-base-dashboard.md);组件 `data_config` 读 [dashboard-block-data-config.md](references/dashboard-block-data-config.md);读取图表计算结果用 `+dashboard-block-get-data` |
|
||||
| Workflow | `+workflow-*` | 创建/更新或理解 steps 时读入口 [lark-base-workflow-guide.md](references/lark-base-workflow-guide.md) 和 steps JSON SSOT [lark-base-workflow-schema.md](references/lark-base-workflow-schema.md);list/get/enable/disable 只处理 workflow ID 与启停状态 |
|
||||
@@ -116,7 +116,6 @@ metadata:
|
||||
## 表单与视图细节
|
||||
|
||||
- `+form-submit` 是高风险写操作,必须带 `--yes` 确认;调用前必须先跑 `+form-detail`,读取 `questions[].type`、`required`、`filter` 和附件场景需要的 `base_token`;不要填写被 filter 隐藏的问题。
|
||||
- `+form-questions-update` 是题目配置全量覆盖,不是 patch;未传字段会回落默认值,传空字符串 / `null` / 空数组会直接写入空或清空。更新前先 `+form-questions-list` 读取当前题目,把要保留的 `title` / `description` / `required` / `option_display_mode` / `visible_rule` 等字段带回请求。
|
||||
- 表单附件不要写进 `fields`,放在 `--json.attachments`;提交附件时必须同时传表单所属 Base 的 `--base-token`。
|
||||
- `+view-set-filter` 是唯一保留的 view reference;sort/group/card/timebar/visible-fields 这类配置先用对应 get 命令读现状,保留未修改字段,只替换用户要求变更的配置。
|
||||
- 视图适合持久化、共享和 UI 复用;一次性筛选/排序可先用 `+record-list` / `+record-search` 的 filter/sort 验证结果,再按需要沉淀为持久视图。
|
||||
@@ -147,14 +146,13 @@ metadata:
|
||||
## 保留 Reference
|
||||
|
||||
- [lark-base-data-analysis-sop.md](references/lark-base-data-analysis-sop.md):查询/统计/全局结论的选路 SOP
|
||||
- [lark-base-data-query-guide.md](references/lark-base-data-query-guide.md) / [lark-base-data-query.md](references/lark-base-data-query.md):聚合查询入口 fewshot 与 DSL SSOT;`+data-query` 的 `filters` 结构是独立对象 DSL,不使用公共 tuple filter 协议
|
||||
- [lark-base-data-query-guide.md](references/lark-base-data-query-guide.md) / [lark-base-data-query.md](references/lark-base-data-query.md):聚合查询入口 fewshot 与 DSL SSOT
|
||||
- [lark-base-cell-value.md](references/lark-base-cell-value.md):记录 CellValue 构造
|
||||
- [lark-base-field-json.md](references/lark-base-field-json.md):字段 JSON 构造
|
||||
- [formula-field-guide.md](references/formula-field-guide.md) / [lookup-field-guide.md](references/lookup-field-guide.md):公式与 lookup 字段
|
||||
- [lark-base-field-create.md](references/lark-base-field-create.md) / [lark-base-field-update.md](references/lark-base-field-update.md):字段创建/更新命令级补充
|
||||
- [lark-base-record-upsert.md](references/lark-base-record-upsert.md) / [lark-base-record-batch-create.md](references/lark-base-record-batch-create.md) / [lark-base-record-batch-update.md](references/lark-base-record-batch-update.md) / [lark-base-record-history-list.md](references/lark-base-record-history-list.md):记录写入 JSON 与历史返回解释
|
||||
- [lark-base-view-set-filter.md](references/lark-base-view-set-filter.md):视图筛选 JSON
|
||||
- [lark-base-filter-condition.md](references/lark-base-filter-condition.md):视图 filter、记录 `--filter-json`、表单 `visible_rule` 的 tuple 条件结构公共协议 SSOT;不适用于 `+data-query`
|
||||
- [lark-base-form-detail.md](references/lark-base-form-detail.md) / [lark-base-form-submit.md](references/lark-base-form-submit.md) / [lark-base-form-questions-create.md](references/lark-base-form-questions-create.md) / [lark-base-form-questions-update.md](references/lark-base-form-questions-update.md):表单详情、提交和复杂 JSON
|
||||
- [lark-base-dashboard.md](references/lark-base-dashboard.md) / [dashboard-block-data-config.md](references/dashboard-block-data-config.md) / [lark-base-dashboard-block-get-data.md](references/lark-base-dashboard-block-get-data.md):仪表盘、组件配置与图表结果协议
|
||||
- [lark-base-workflow-guide.md](references/lark-base-workflow-guide.md) / [lark-base-workflow-schema.md](references/lark-base-workflow-schema.md):workflow 入口与 steps JSON SSOT
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
# Base Filter 条件结构(公共协议)
|
||||
|
||||
Filter 是一组「字段/操作符/值」条件的组合,用 `logic`(`and` / `or`)把多条 `conditions` 连接起来,用于描述「满足什么条件」。视图筛选 `filter`、记录读取/搜索的 `--filter-json`、表单题目显隐条件 `visible_rule` 复用同一套 tuple 结构,本文件是其公共协议(SSOT)。
|
||||
|
||||
## 0. 适用范围
|
||||
|
||||
本协议只适用于以下场景:
|
||||
|
||||
- `+view-set-filter` / `+view-get-filter` 的视图筛选配置。
|
||||
- `+record-list --filter-json` / `+record-search --filter-json` 的结构化记录筛选。
|
||||
- `+form-questions-create` / `+form-questions-update` 中的 `visible_rule` 显隐条件。
|
||||
|
||||
本协议**不适用于 `+data-query`**。`+data-query` 支持过滤,但使用的是 LiteQuery DSL 的 `filters` 对象结构:`{"type":1,"conjunction":"and","conditions":[{"field_name":"状态","operator":"is","value":["有效"]}]}`,不是这里的 tuple 条件 `["状态","==","有效"]`。构造 `+data-query --dsl` 时请阅读 [lark-base-data-query.md](lark-base-data-query.md) 的 FilterGroup / Condition 章节。
|
||||
|
||||
## 1. 顶层结构
|
||||
|
||||
- 必须是 JSON 对象。
|
||||
- 顶层结构是 `{logic?, conditions?}`。
|
||||
- `logic` 默认 `and`;推荐只用 canonical 值 `and` / `or`。
|
||||
- `conditions` 默认空数组。
|
||||
- 每条条件写成 tuple:`[field, operator, value?]`。
|
||||
- `empty` / `non_empty` 可写成 2 项:`[field, "empty"]`、`[field, "non_empty"]`。
|
||||
|
||||
```json
|
||||
{
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["状态", "intersects", ["Doing"]],
|
||||
["负责人", "intersects", [{ "id": "ou_xxx" }]],
|
||||
["截止时间", "empty"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
清空写法:
|
||||
|
||||
```json
|
||||
{
|
||||
"conditions": []
|
||||
}
|
||||
```
|
||||
|
||||
## 2. operator
|
||||
|
||||
可用 operator:
|
||||
- `==`
|
||||
- `!=`
|
||||
- `>`
|
||||
- `>=`
|
||||
- `<`
|
||||
- `<=`
|
||||
- `intersects`
|
||||
- `disjoint`
|
||||
- `empty`
|
||||
- `non_empty`
|
||||
|
||||
## 3. value 写法
|
||||
|
||||
value 类型取决于条件引用对象(字段 / 题目)的类型。
|
||||
|
||||
### `text`
|
||||
|
||||
用字符串:
|
||||
|
||||
```json
|
||||
["标题", "intersects", "发布"]
|
||||
```
|
||||
|
||||
### `location`
|
||||
|
||||
location 筛选只按 `full_address` 字符串匹配,不能直接按经纬度筛选;优先使用 `intersects` 做包含匹配,例如查深圳:
|
||||
|
||||
```json
|
||||
["位置", "intersects", "深圳"]
|
||||
```
|
||||
|
||||
不推荐写 `["位置", "==", "深圳"]` 这类精确匹配,除非确保筛选值与完整 `full_address` 完全一致。
|
||||
|
||||
### `number` / `auto_number`
|
||||
|
||||
用数字:
|
||||
|
||||
```json
|
||||
["工时", ">=", 3.5]
|
||||
```
|
||||
|
||||
### `select`
|
||||
|
||||
用选项名数组:
|
||||
|
||||
```json
|
||||
["状态", "intersects", ["Doing", "Blocked"]]
|
||||
```
|
||||
|
||||
### `user` / `created_by` / `updated_by`
|
||||
|
||||
用对象数组:
|
||||
|
||||
> **人员筛选:不要猜 ID。** 不知道 `open_id` 时,先用 `lark-contact` 查 id:`lark-cli contact +search-user --query "<姓名/邮箱/手机号>" --as user`。
|
||||
|
||||
```json
|
||||
["负责人", "intersects", [{ "id": "ou_xxx" }]]
|
||||
```
|
||||
|
||||
### `group_chat`
|
||||
|
||||
用对象数组:
|
||||
|
||||
> **群组筛选:不要猜 ID。** 不知道 `chat_id` 时,先用 `lark-im` 搜群:`lark-cli im +chat-search --query "<群名关键词>" --as user`;取结果里的 `oc_xxx`。
|
||||
|
||||
```json
|
||||
["负责群", "intersects", [{ "id": "oc_xxx" }]]
|
||||
```
|
||||
|
||||
### `link`
|
||||
|
||||
用记录 id 对象数组:
|
||||
|
||||
```json
|
||||
["关联任务", "intersects", [{ "id": "rec_xxx" }]]
|
||||
```
|
||||
|
||||
### `checkbox`
|
||||
|
||||
用布尔值:
|
||||
|
||||
```json
|
||||
["完成", "==", true]
|
||||
```
|
||||
|
||||
### `datetime` / `created_at` / `updated_at`
|
||||
|
||||
用相对时间关键字或 `ExactDate(...)`:
|
||||
|
||||
```json
|
||||
["截止时间", "==", "ExactDate(2026-01-01)"]
|
||||
```
|
||||
|
||||
```json
|
||||
["截止时间", "==", "ExactDate(2026-01-01 11:30)"]
|
||||
```
|
||||
|
||||
```json
|
||||
["截止时间", "==", "Today"]
|
||||
```
|
||||
|
||||
可用关键字:
|
||||
- `Today`
|
||||
- `Yesterday`
|
||||
- `Tomorrow`
|
||||
|
||||
### `formula` / `lookup`
|
||||
|
||||
- 筛选值类型由字段计算结果类型动态决定。
|
||||
- 拿不准时,先把 `value` 当作单个字符串填入做一次尝试。
|
||||
- 如果报错,再按错误提示把 `value` 改成对应类型。
|
||||
|
||||
字符串示例:
|
||||
|
||||
```json
|
||||
["风险说明", "intersects", "高风险"]
|
||||
```
|
||||
|
||||
数字示例:
|
||||
|
||||
```json
|
||||
["汇总分", ">=", 80]
|
||||
```
|
||||
|
||||
## 4. 易错点
|
||||
|
||||
- 不要再写旧对象风格:`{"field_name":...,"operator":...}`。
|
||||
- `user` / `group_chat` / `link` 不要写成单个标量。
|
||||
- `empty` / `non_empty` 不要硬塞无意义的 value。
|
||||
- 日期条件稳定写法用 `ExactDate(...)` 或 `Today` / `Yesterday` / `Tomorrow`。
|
||||
- `formula` / `lookup` 的 value 形状不固定;拿不准时先读当前配置或字段定义,或根据错误提示修正类型。
|
||||
|
||||
## 5. 参考
|
||||
- [lookup-field-guide.md](lookup-field-guide.md)
|
||||
@@ -19,7 +19,10 @@ lark-cli base +form-questions-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"type":"text","title":"您的姓名是?","required":true},{"type":"text","title":"您的联系方式是?","required":false}]'
|
||||
--questions '[
|
||||
{"type":"text","title":"您的姓名是?","required":true},
|
||||
{"type":"text","title":"您的联系方式是?","required":false}
|
||||
]'
|
||||
|
||||
# 添加单选题(带选项)
|
||||
lark-cli base +form-questions-create \
|
||||
@@ -47,13 +50,6 @@ lark-cli base +form-questions-create \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"type":"text","title":"反馈建议","description":"更多详情请查看[帮助文档](https://example.com/help)"}]'
|
||||
|
||||
# 添加带显隐条件(visible_rule)的问题:当「是否需要发票」选择「是」时才显示「发票抬头」
|
||||
lark-cli base +form-questions-create \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"type":"select","title":"是否需要发票","required":true,"options":[{"name":"是","hue":"Blue"},{"name":"否","hue":"Gray"}]},{"type":"text","title":"发票抬头","visible_rule":{"logic":"and","conditions":[["是否需要发票","==","是"]]}}]'
|
||||
```
|
||||
|
||||
## 参数
|
||||
@@ -82,7 +78,6 @@ lark-cli base +form-questions-create \
|
||||
| `multiple` | 否 | 是否多选(`select`/`user` 类型有效,bool) |
|
||||
| `options` | 否 | 选项列表(仅 `select` 有效):`[{"name":"选项1","hue":"Blue"}]`,hue 可选:`Red`/`Orange`/`Yellow`/`Green`/`Blue`/`Purple`/`Gray` |
|
||||
| `style` | 否 | 字段样式配置(见下方说明) |
|
||||
| `visible_rule` | 否 | 题目显隐条件(见下方「`visible_rule` 显隐条件」) |
|
||||
|
||||
### `style` 字段说明
|
||||
|
||||
@@ -93,30 +88,6 @@ lark-cli base +form-questions-create \
|
||||
| `number`(评分) | `{"type":"rating","icon":"star","min":1,"max":5}` | icon 可选:`star`/`heart`/`thumbsup`/`fire`/`smile`/`lightning`/`flower`/`number` |
|
||||
| `datetime` | `{"format":"yyyy/MM/dd"}` | format 可选:`yyyy/MM/dd`、`yyyy/MM/dd HH:mm`、`MM-dd`、`MM/dd/yyyy`、`dd/MM/yyyy` |
|
||||
|
||||
### `visible_rule` 显隐条件
|
||||
|
||||
> **仅当用户明确要求为题目设置显隐条件(显示/隐藏逻辑)时,才需要读下面的结构说明;否则忽略本节。**
|
||||
|
||||
`visible_rule` 控制题目在表单中的显示/隐藏:当条件满足时题目显示,不满足时隐藏;不传或 `conditions` 为空数组则题目始终显示。
|
||||
|
||||
- **结构与视图筛选 `filter` 完全一致**,即 `{logic?, conditions?}`,共用同一套公共协议。
|
||||
- 与视图 `filter` 唯一的区别:`conditions` 中的 `field` 引用的是**同一表单内其他题目的题目名称或题目 ID**(推荐用题目 ID 以避免重名歧义),而不是数据表字段。
|
||||
- **只能引用前序题目**:条件只能引用排在当前题目之前的题目——创建时按 `questions` 数组顺序判定(可引用同批次更靠前的新题目或表单中已有题目),不支持循环引用。
|
||||
- 引用的题目必须真实存在,否则会报错。
|
||||
- 列出题目(`+form-questions-list`)会在每个题目对象中**原样返回** `visible_rule`;未设置显隐条件的题目返回 `null` 或 `conditions` 为空数组。
|
||||
|
||||
```json
|
||||
{
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["是否需要发票", "==", "是"],
|
||||
["报销金额", ">=", 1000]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
详细的 `visible_rule` 结构(顶层规则、operator 列表、各题目类型的 value 写法)请阅读 [lark-base-filter-condition.md](lark-base-filter-condition.md)。
|
||||
|
||||
## 输出格式
|
||||
|
||||
返回创建成功的问题列表:
|
||||
@@ -144,5 +115,4 @@ lark-cli base +form-questions-create \
|
||||
## 参考
|
||||
|
||||
- [lark-base](../SKILL.md) — 多维表格全部命令
|
||||
- [lark-base-filter-condition.md](lark-base-filter-condition.md) — `visible_rule` / `filter` 条件结构公共协议
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
|
||||
@@ -2,60 +2,40 @@
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
批量更新多维表格表单/问卷中的问题配置(标题、描述、是否必填、显隐条件等)。
|
||||
|
||||
> [!CAUTION]
|
||||
> `+form-questions-update` 是**题目配置全量覆盖**,不是 patch。对每个传入的题目,未携带的属性会回落为默认值,显式传空字符串 / `null` / 空数组会直接写入空或清空;如果要保留现有属性,必须先用 `+form-questions-list` 查出现状,再把要保留的字段一起带回 `--questions`。
|
||||
批量更新多维表格表单/问卷中的问题(标题、描述、是否必填)。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 先读取现有题目配置,作为 read-modify-write 的基线
|
||||
lark-cli base +form-questions-list \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id>
|
||||
|
||||
# 更新一个问题的标题,同时带回要保留的 required / description / visible_rule 等字段
|
||||
# 更新一个问题的标题
|
||||
lark-cli base +form-questions-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"id":"q_001","title":"您的真实姓名是?","description":"请填写真实姓名","required":true,"visible_rule":null}]'
|
||||
--questions '[{"id":"q_001","title":"您的真实姓名是?"}]'
|
||||
|
||||
# 同时更新多个问题;每个对象都应是该题目的目标完整配置
|
||||
# 同时更新多个问题
|
||||
lark-cli base +form-questions-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"id":"q_001","title":"姓名(必填)","required":true},{"id":"q_002","title":"联系方式","required":false}]'
|
||||
--questions '[
|
||||
{"id":"q_001","title":"姓名(必填)","required":true},
|
||||
{"id":"q_002","title":"联系方式","required":false}
|
||||
]'
|
||||
|
||||
# 更新问题描述(纯文本),同时带回要保留的 title / required / visible_rule
|
||||
# 更新问题描述(纯文本)
|
||||
lark-cli base +form-questions-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"id":"q_001","title":"您的姓名","description":"请填写您的真实姓名","required":true,"visible_rule":null}]'
|
||||
# 更新问题描述(含链接),同时带回要保留的 title / required / visible_rule
|
||||
--questions '[{"id":"q_001","description":"请填写您的真实姓名"}]'
|
||||
# 更新问题描述(含链接)
|
||||
lark-cli base +form-questions-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"id":"q_001","title":"反馈建议","description":"更多说明请参考[帮助文档](https://example.com/help)","required":false,"visible_rule":null}]'
|
||||
|
||||
# 更新题目显隐条件(visible_rule),同时带回要保留的 title / description / required
|
||||
lark-cli base +form-questions-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"id":"q_002","title":"发票抬头","description":"","required":false,"visible_rule":{"logic":"and","conditions":[["q_001","==","是"]]}}]'
|
||||
|
||||
# 清空题目显隐条件(使题目始终显示),同时带回要保留的 title / description / required
|
||||
lark-cli base +form-questions-update \
|
||||
--base-token <base_token> \
|
||||
--table-id <table_id> \
|
||||
--form-id <form_id> \
|
||||
--questions '[{"id":"q_002","title":"发票抬头","description":"","required":false,"visible_rule":null}]'
|
||||
--questions '[{"id":"q_001","description":"更多说明请参考[帮助文档](https://example.com/help)"}]'
|
||||
```
|
||||
|
||||
## 参数
|
||||
@@ -72,46 +52,15 @@ lark-cli base +form-questions-update \
|
||||
|
||||
## `--questions` 格式
|
||||
|
||||
每个问题对象必须包含 `id`。注意:对象不是增量 patch,而是该题目的目标完整配置;未携带字段会按服务端默认值重建。
|
||||
每个问题对象必须包含 `id`,其余字段按需传入:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | **是** | 问题 ID(field_id),不可修改 |
|
||||
| `title` | 否 | 目标问题标题;省略会回落为字段名,传空字符串会写入空标题(若服务端允许) |
|
||||
| `description` | 否 | 目标问题描述(纯文本或 Markdown 链接,如 `[文本](https://example.com)`);省略或传空字符串都会清空描述 |
|
||||
| `required` | 否 | 目标是否必填;省略会回落为 `false` |
|
||||
| `option_display_mode` | 否 | 目标选项展示方式(仅 `select` 有效):`0`=下拉,`1`=纵向(默认),`2`=横向;省略会回落默认展示方式 |
|
||||
| `visible_rule` | 否 | 目标题目显隐条件;传完整 `{logic, conditions}` 对象覆盖,传 `null` 或省略都会清空(见下方说明) |
|
||||
|
||||
## 全量覆盖语义
|
||||
|
||||
- 先执行 `+form-questions-list`,读取被更新题目的当前 `id`、`title`、`description`、`required`、`option_display_mode`、`visible_rule`。
|
||||
- 构造 `--questions` 时,只改用户明确要求变化的字段;所有仍要保留的字段必须按当前值一并传回。
|
||||
- 不要用“只传要改的字段”的方式更新题目。比如只传 `{"id":"q_002","title":"新标题"}` 会让 `description` 清空、`required` 回落为 `false`、`visible_rule` 清空。
|
||||
- 用户明确要求清空时才传空值:`description:""` 清空描述,`visible_rule:null` 清空显隐条件,`conditions:[]` 也表示无条件显示。
|
||||
|
||||
### `visible_rule` 显隐条件
|
||||
|
||||
> **仅当用户明确要求为题目设置或修改显隐条件(显示/隐藏逻辑)时,才需要读下面的结构说明;否则忽略本节。**
|
||||
|
||||
`visible_rule` 控制题目显示/隐藏,**结构与视图筛选 `filter` 完全一致**(`{logic?, conditions?}`),共用同一套公共协议。
|
||||
|
||||
- `conditions` 中的 `field` 引用**同一表单内其他题目的题目名称或题目 ID**(推荐用题目 ID)。
|
||||
- 更新时按表单中题目的**实际顺序**判定,只能引用排在当前题目之前的题目;不支持循环引用。
|
||||
- 更新 `visible_rule` 需传**完整**的 `{logic, conditions}` 对象(整体覆盖);要保留现有显隐条件就必须把当前 `visible_rule` 原样带回;传 `null`、省略 `visible_rule` 或传空 `conditions` 都会使题目始终显示。
|
||||
- 列出题目(`+form-questions-list`)会在每个题目对象中**原样返回** `visible_rule`;未设置显隐条件的题目返回 `null` 或 `conditions` 为空数组。
|
||||
|
||||
```json
|
||||
{
|
||||
"logic": "and",
|
||||
"conditions": [
|
||||
["q_001", "==", "是"],
|
||||
["q_003", ">=", 1000]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
详细的 `visible_rule` 结构(顶层规则、operator 列表、各题目类型的 value 写法)请阅读 [lark-base-filter-condition.md](lark-base-filter-condition.md)。
|
||||
| `title` | 否 | 新的问题标题 |
|
||||
| `description` | 否 | 新的问题描述(纯文本或 Markdown 链接,如 `[文本](https://example.com)`) |
|
||||
| `required` | 否 | 是否必填 |
|
||||
| `option_display_mode` | 否 | 选项展示方式(仅 `select` 有效):`0`=下拉,`1`=纵向(默认),`2`=横向 |
|
||||
|
||||
## 输出格式
|
||||
|
||||
@@ -133,13 +82,11 @@ lark-cli base +form-questions-update \
|
||||
> [!CAUTION]
|
||||
> 这是**写入操作** — 执行前必须向用户确认。
|
||||
|
||||
1. 先用 `+form-questions-list` 获取现有问题及其 `id` 和完整配置。
|
||||
2. 以现有配置为基线,只修改用户明确要求变化的字段;要保留的字段必须原样带回。
|
||||
3. 构造包含 `id` 和目标完整配置的更新数组。
|
||||
4. 执行命令并报告更新结果。
|
||||
1. 先用 `+form-questions-list` 获取现有问题及其 `id`
|
||||
2. 构造包含 `id` 的更新数组
|
||||
3. 执行命令并报告更新结果
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-base](../SKILL.md) — 多维表格全部命令
|
||||
- [lark-base-filter-condition.md](lark-base-filter-condition.md) — `visible_rule` / `filter` 条件结构公共协议
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
|
||||
@@ -4,13 +4,142 @@
|
||||
|
||||
更新视图筛选配置。
|
||||
|
||||
## 1. filter 结构
|
||||
|
||||
`--json` 就是一个 filter 条件对象,结构见公共协议 SSOT [lark-base-filter-condition.md](lark-base-filter-condition.md),即 `{logic?, conditions?}`。此处 `conditions` 中的 `field` 引用**数据表字段名或字段 id**。
|
||||
## 1. 顶层规则
|
||||
|
||||
- `--json` 必须是 JSON 对象。
|
||||
- 顶层结构是 `{logic?, conditions?}`。
|
||||
- `logic` 默认 `and`;推荐只用 canonical 值 `and` / `or`。
|
||||
- `conditions` 默认空数组。
|
||||
- 每条条件写成 tuple:`[field, operator, value?]`。
|
||||
- `empty` / `non_empty` 可写成 2 项:`[field, "empty"]`、`[field, "non_empty"]`。
|
||||
- 支持 `filter` 的视图类型:`grid`、`kanban`、`gallery`、`calendar`、`gantt`。
|
||||
|
||||
## 2. 推荐命令
|
||||
## 2. operator
|
||||
|
||||
可用 operator:
|
||||
- `==`
|
||||
- `!=`
|
||||
- `>`
|
||||
- `>=`
|
||||
- `<`
|
||||
- `<=`
|
||||
- `intersects`
|
||||
- `disjoint`
|
||||
- `empty`
|
||||
- `non_empty`
|
||||
|
||||
## 3. value 写法
|
||||
|
||||
### `text`
|
||||
|
||||
用字符串:
|
||||
|
||||
```json
|
||||
["标题", "intersects", "发布"]
|
||||
```
|
||||
|
||||
### `location`
|
||||
|
||||
location 筛选只按 `full_address` 字符串匹配,不能直接按经纬度筛选;优先使用 `intersects` 做包含匹配,例如查深圳:
|
||||
|
||||
```json
|
||||
["位置", "intersects", "深圳"]
|
||||
```
|
||||
|
||||
不推荐写 `["位置", "==", "深圳"]` 这类精确匹配,除非确保筛选值与完整 `full_address` 完全一致。
|
||||
|
||||
### `number` / `auto_number`
|
||||
|
||||
用数字:
|
||||
|
||||
```json
|
||||
["工时", ">=", 3.5]
|
||||
```
|
||||
|
||||
### `select`
|
||||
|
||||
用选项名数组:
|
||||
|
||||
```json
|
||||
["状态", "intersects", ["Doing", "Blocked"]]
|
||||
```
|
||||
|
||||
### `user` / `created_by` / `updated_by`
|
||||
|
||||
用对象数组:
|
||||
|
||||
> **人员筛选:不要猜 ID。** 不知道 `open_id` 时,先用 `lark-contact` 查 id:`lark-cli contact +search-user --query "<姓名/邮箱/手机号>" --as user`。
|
||||
|
||||
```json
|
||||
["负责人", "intersects", [{ "id": "ou_xxx" }]]
|
||||
```
|
||||
|
||||
### `group_chat`
|
||||
|
||||
用对象数组:
|
||||
|
||||
> **群组筛选:不要猜 ID。** 不知道 `chat_id` 时,先用 `lark-im` 搜群:`lark-cli im +chat-search --query "<群名关键词>" --as user`;取结果里的 `oc_xxx`。
|
||||
|
||||
```json
|
||||
["负责群", "intersects", [{ "id": "oc_xxx" }]]
|
||||
```
|
||||
|
||||
### `link`
|
||||
|
||||
用记录 id 对象数组:
|
||||
|
||||
```json
|
||||
["关联任务", "intersects", [{ "id": "rec_xxx" }]]
|
||||
```
|
||||
|
||||
### `checkbox`
|
||||
|
||||
用布尔值:
|
||||
|
||||
```json
|
||||
["完成", "==", true]
|
||||
```
|
||||
|
||||
### `datetime` / `created_at` / `updated_at`
|
||||
|
||||
用相对时间关键字或 `ExactDate(...)`:
|
||||
|
||||
```json
|
||||
["截止时间", "==", "ExactDate(2026-01-01)"]
|
||||
```
|
||||
|
||||
```json
|
||||
["截止时间", "==", "ExactDate(2026-01-01 11:30)"]
|
||||
```
|
||||
|
||||
```json
|
||||
["截止时间", "==", "Today"]
|
||||
```
|
||||
|
||||
可用关键字:
|
||||
- `Today`
|
||||
- `Yesterday`
|
||||
- `Tomorrow`
|
||||
|
||||
### `formula` / `lookup`
|
||||
|
||||
- 筛选值类型由字段计算结果类型动态决定。
|
||||
- 拿不准时,先把 `value` 当作单个字符串填入做一次尝试。
|
||||
- 如果报错,再按错误提示把 `value` 改成对应类型。
|
||||
|
||||
字符串示例:
|
||||
|
||||
```json
|
||||
["风险说明", "intersects", "高风险"]
|
||||
```
|
||||
|
||||
数字示例:
|
||||
|
||||
```json
|
||||
["汇总分", ">=", 80]
|
||||
```
|
||||
|
||||
## 4. 推荐命令
|
||||
|
||||
```bash
|
||||
lark-cli base +view-set-filter \
|
||||
@@ -20,7 +149,7 @@ lark-cli base +view-set-filter \
|
||||
--json '{"logic":"and","conditions":[["状态","intersects",["Doing"]],["负责人","intersects",[{"id":"ou_xxx"}]],["截止时间","empty"]]}'
|
||||
```
|
||||
|
||||
## 3. JSON 写法
|
||||
## 5. JSON 写法
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -41,16 +170,14 @@ lark-cli base +view-set-filter \
|
||||
}
|
||||
```
|
||||
|
||||
完整的 operator 列表与各字段类型的 value 写法(`text` / `number` / `select` / `user` / `datetime` / `formula` / `lookup` 等),见 [lark-base-filter-condition.md](lark-base-filter-condition.md)。
|
||||
|
||||
## 4. 使用建议
|
||||
## 6. 使用建议
|
||||
|
||||
- 先读取当前筛选配置,理解现有 `logic` 和 `conditions` 的组合关系;只替换用户要求变更的条件,未提到的条件默认保留。
|
||||
- 优先传字段 id,不要依赖字段名。
|
||||
- 拿不准字段 type 或真实取值时,先用 `+field-list` / `+record-list` 确认,再按对应字段类型的 value 写法构造条件;别按字段名猜 type、凭印象猜枚举取值。
|
||||
- 需要清空全部筛选时,直接传 `{"conditions":[]}`。
|
||||
|
||||
## 5. 易错点
|
||||
## 7. 易错点
|
||||
|
||||
- 本 tuple DSL 由 `+view-set-filter` 与 `+record-list` / `+record-search` 的 `--filter-json` 共用;不要写成 `+data-query` 的对象风格 `{"field_name":...,"operator":...}`(会报校验失败)。
|
||||
- 标量类字段(`text` / `number` / `datetime` 等)的 value 用标量、别包成数组(各类型详见 value 写法一节)。
|
||||
@@ -59,7 +186,6 @@ lark-cli base +view-set-filter \
|
||||
- 日期条件稳定写法用 `ExactDate(...)` 或 `Today` / `Yesterday` / `Tomorrow`。
|
||||
- `formula` / `lookup` 的 value 形状不固定;拿不准时先读当前 filter 或字段定义,或根据错误提示修正类型。
|
||||
|
||||
## 6. 参考
|
||||
## 8. 参考
|
||||
|
||||
- [lark-base-filter-condition.md](lark-base-filter-condition.md):filter/visible_rule 条件结构公共协议 SSOT
|
||||
- [lookup-field-guide.md](lookup-field-guide.md)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: lark-doc
|
||||
version: 2.0.0
|
||||
description: "飞书云文档(Docx / Wiki 文档):读取和编辑飞书文档内容。当用户给出文档 URL 或 token,或需要查看、创建、编辑文档、插入或下载文档图片附件时使用。文档中嵌入的电子表格、多维表格、画板,先用本 skill 提取 token 再切到对应 skill。当用户给出 doubao.com 的 /docx/ 或 /wiki/ URL/token 时,也应直接使用本 skill;路由依据是 URL 路径模式和 token,而不是域名。不负责文档评论管理,也不负责表格或 Base 的数据操作。当用户明确要操作飞书思维笔记时,也使用本 skill。"
|
||||
description: "飞书云文档(Docx):仅用于明确 Docx 资源:/docx/ URL、Docx token。读取、创建和编辑 Docx 文档内容,插入或下载文档图片附件。文档中嵌入电子表格、多维表格或画板时,提取 token 后切换到对应 Skill。当用户给出 doubao.com 的 /docx/ URL/token 时,也应直接使用本 Skill;路由依据是 URL 路径模式和 token,而不是域名。不负责文档评论管理,也不负责表格或 Base 的数据操作。当用户明确要操作飞书思维笔记时,也使用本 Skill。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["lark-cli"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: lark-drive
|
||||
version: 1.0.0
|
||||
description: "飞书云空间(云盘/云存储):管理 Drive 文件和文件夹,包含上传/下载、创建文件夹、复制/移动/删除、查看元数据、查询权限设置、评论/权限/订阅、标题、版本、飞书文档密级标签(secure labels)和本地文件导入。用户需要整理云盘目录、处理云空间资源 URL/token、判断链接类型/真实 token/标题,或导入 Word/Markdown/Excel/CSV/PPTX/.base 为 docx/sheet/bitable/slides 时使用;doubao.com 云空间 URL/token 也按资源路径和 token 路由,不回退 WebFetch。不负责:文档内容编辑(走 lark-doc)、表格/Base 表内数据操作(走 lark-sheets/lark-base)、知识空间节点/成员管理(走 lark-wiki)、原生 Markdown 文件读写/patch/diff(走 lark-markdown)。"
|
||||
description: "飞书云空间(云盘/云存储):管理 Drive 文件和文件夹,包含上传/下载、创建文件夹、复制/移动/删除、查看元数据、评论/权限/订阅、标题、版本、飞书文档密级标签(secure labels)和本地文件导入。用户需要整理云盘目录、处理云空间资源 URL/token、判断链接类型/真实 token/标题,或导入 Word/Markdown/Excel/CSV/PPTX/.base 为 docx/sheet/bitable/slides 时使用;doubao.com 云空间 URL/token 也按资源路径和 token 路由,不回退 WebFetch。不负责:文档内容编辑(走 lark-doc)、表格/Base 表内数据操作(走 lark-sheets/lark-base)、知识空间节点/成员管理(走 lark-wiki)、原生 Markdown 文件读写/patch/diff(走 lark-markdown)。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["lark-cli"]
|
||||
@@ -27,7 +27,6 @@ metadata:
|
||||
- 用户要**检查 / 治理文档权限、公开范围、链接分享、外部访问、复制下载权限、密级标签、owner 转移**,或要”权限风险报告、收紧权限、申请查看 / 编辑权限、转移 / 批量转移 owner”,必须先阅读 [`references/lark-drive-workflow.md`](references/lark-drive-workflow.md),再按其中 `Workflow Registry` 进入 [`permission_governance`](references/lark-drive-workflow-permission-governance.md) workflow。
|
||||
- 用户要为指定飞书文档**设置 / 修改密级标签(secure label)**,或查询当前用户可用的密级标签,直接读取 [`references/lark-drive-secure-label.md`](references/lark-drive-secure-label.md);这是 Drive 文件治理能力。
|
||||
- 用户要**检查 / 治理文档权限、公开范围、链接分享、外部访问、复制下载权限、密级标签、owner 转移**,或要“权限风险报告、收紧权限、申请查看 / 编辑权限、转移 / 批量转移 owner”,必须先阅读 [`references/lark-drive-workflow.md`](references/lark-drive-workflow.md),再按其中 `Workflow Registry` 进入 [`permission_governance`](references/lark-drive-workflow-permission-governance.md) workflow。
|
||||
- 用户要**查询文件、文件夹或云文档自身的公开访问、分享、协作者管理、安全与评论权限设置**,优先使用 `lark-cli drive +permission-get-setting`;它只读取目标自身设置,不递归审计文件夹子文档权限。裸 token 必须显式传 `--type`。
|
||||
- 用户要**按特定主题、关键词或内容线索跨容器查找资料,并统一收集到 Drive 文件夹或 Wiki 节点**,必须先阅读 [`references/lark-drive-workflow.md`](references/lark-drive-workflow.md),再按其中 `Workflow Registry` 进入 [`topic_move_collector`](references/lark-drive-workflow-topic-move-collector.md) workflow。该 workflow 负责搜索召回、内容验证、相关性分类、移动计划、写前确认和结果验证;禁止直接从 `drive +search` 或 `drive +move` 开始。
|
||||
- 用户要**整理云盘 / 文件夹 / 文档库 / 知识库 / 个人文档库**,或要“盘点目录结构、找出未归档/临时/重复/空目录、生成整理方案”,必须先阅读 [`references/lark-drive-workflow.md`](references/lark-drive-workflow.md),再按其中 `Workflow Registry` 进入 [`knowledge_organize`](references/lark-drive-workflow-knowledge-organize.md) workflow。默认只生成方案;创建目录、移动资源、申请权限都必须单独确认。
|
||||
- 按主题跨范围查找并集中归档,进入 `topic_move_collector`;对已知文件夹、文档库或知识库做目录盘点和结构重组,进入 `knowledge_organize`;只移动一个已明确资源时仍使用原子移动命令。
|
||||
@@ -121,7 +120,6 @@ lark-cli drive +inspect --url 'https://xxx.feishu.cn/wiki/wikcnXXX'
|
||||
### 权限能力入口
|
||||
|
||||
- 用户要管理 Drive 文档/文件协作者、公开权限、授权当前应用访问文档,或处理 `permission.public.patch` 的 `91009` / `91010` / `91011` / `91012` 错误时,先读 [`lark-drive-permission-guide.md`](references/lark-drive-permission-guide.md)。
|
||||
- 用户要查询文件、文件夹或云文档自身的公开访问、分享、协作者管理、安全与评论权限设置,使用 [`+permission-get-setting`](references/lark-drive-permission-get-setting.md);如果要递归审计文件夹下子文档权限,再进入 [`permission_governance`](references/lark-drive-workflow-permission-governance.md) workflow。
|
||||
- 用户只是没有访问权限并希望向 owner 申请访问,优先使用 [`+apply-permission`](references/lark-drive-apply-permission.md)。
|
||||
- 普通 scope、身份或登录问题仍按 [`lark-shared`](../lark-shared/SKILL.md) 处理;不要把租户安全策略、对外分享、密级拦截简单归类为缺 scope。
|
||||
|
||||
@@ -165,8 +163,6 @@ Shortcut 是对常用操作的高级封装(`lark-cli drive +<verb> [flags]`)
|
||||
| [`+inspect`](references/lark-drive-inspect.md) | 检视 URL 的类型、标题和 canonical token;wiki URL 会自动解包到底层文档。 |
|
||||
| [`+apply-permission`](references/lark-drive-apply-permission.md) | 以 user 身份向文档 owner 申请访问权限。 |
|
||||
| [`+member-add`](references/lark-drive-member-add.md) | 添加一个或最多 10 个 Drive 文档、文件、文件夹或 wiki 节点协作者/授权成员;封装 Drive permission member create/batch_create,真实写入需要 `--yes`。 |
|
||||
| [`+member-list`](references/lark-drive-member-list.md) | 查询 Drive 文档、文件、文件夹或 wiki 节点的协作者/授权成员列表。 |
|
||||
| [`+permission-get-setting`](references/lark-drive-permission-get-setting.md) | 查询文件、文件夹或云文档自身的公开访问、分享、协作者管理、安全与评论权限设置;支持 URL 或裸 token + `--type`;不递归读取文件夹子文档权限。 |
|
||||
| [`+secure-label-list`](references/lark-drive-secure-label.md) | 列出当前用户可用的密级标签。 |
|
||||
| [`+secure-label-update`](references/lark-drive-secure-label.md) | 更新 Drive 文件或文档的密级标签。 |
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# drive +member-list(查询协作者/授权成员列表)
|
||||
|
||||
本 skill 对应 shortcut:`lark-cli drive +member-list`。它读取 Drive 文档、文件、文件夹或 wiki 节点的协作者/授权成员列表。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# URL 自动推断 type
|
||||
lark-cli drive +member-list \
|
||||
--token 'https://example.feishu.cn/drive/folder/<folder_token>' \
|
||||
--as user --format json
|
||||
|
||||
# 查询附加字段
|
||||
lark-cli drive +member-list \
|
||||
--token '<token>' \
|
||||
--type docx \
|
||||
--fields 'name,type,external_label' \
|
||||
--as user --format json
|
||||
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--token` | 是 | 裸 token 或完整 URL。URL 路径支持 `/folder/`、`/docx/`、`/doc/`、`/sheets/`、`/base/`、`/bitable/`、`/wiki/`、`/file/`、`/mindnotes/`、`/slides/`、`/minutes/`。 |
|
||||
| `--type` | 裸 token 必填 | 目标类型:`doc` / `sheet` / `file` / `wiki` / `bitable` / `docx` / `mindnote` / `minutes` / `slides` / `folder`。URL 可自动推断;如果同时传 URL 和冲突的 `--type`,CLI 会拒绝。 |
|
||||
| `--fields` | 否 | 默认不传。可取 `name` / `type` / `avatar` / `external_label`,支持逗号分隔;也可传 `*` 请求当前支持的所有附加字段。该参数只声明期望返回的字段,不授予字段级权限。 |
|
||||
| `--perm-type` | 否 | 仅 `--type wiki` 有效;取值 `container` / `single_page`。 |
|
||||
| `--dry-run` | 否 | 只打印请求,不调用 API。 |
|
||||
|
||||
## 输出
|
||||
|
||||
JSON 输出原样透传 API 的 `data` :
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"identity": "user",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"member_type": "openid",
|
||||
"member_id": "ou_xxx",
|
||||
"perm": "view",
|
||||
"perm_type": "container",
|
||||
"type": "user",
|
||||
"name": "zhangsan",
|
||||
"external_label": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`--format pretty` 会轻量展示成员 ID、成员类型、权限、wiki `perm_type` 和已返回的附加字段。机器读取优先使用 `--format json`。
|
||||
|
||||
## 行为说明
|
||||
|
||||
- **身份支持**:`--as user` 和 `--as bot` 均可用;缺 scope 或目标权限时按统一 permission 错误路径处理。
|
||||
- **接口 scope**:查询成员列表需要 `docs:permission.member:retrieve`。
|
||||
- **fields 默认**:不传 `--fields` 时按官方 API 默认,不请求姓名、头像、外部标签等附加字段;需要时显式指定。
|
||||
- **字段级权限**:`--fields` 只控制请求哪些附加字段,不保证服务端一定返回。请求用户的 `name` / `avatar` 时,应用还需开通 `contact:user.base:readonly`(“获取用户基本信息”;已具备官方兼容的历史通讯录权限也可满足要求)。
|
||||
- **缺字段语义**:字段级权限或数据可见性不足时,接口仍可能成功,但会省略相应敏感字段。响应中缺少已请求字段表示“服务端未返回”,不能解释为字段值为空,也不能据此认定成员信息完整。
|
||||
- **folder 支持**:CLI 支持 `--type folder` 并会按需求发送 `type=folder`;部分环境的后端如果尚未放开 folder 枚举,可能返回 `99992402 field validation failed`。
|
||||
@@ -1,48 +0,0 @@
|
||||
# drive +permission-get-setting(查询权限设置)
|
||||
|
||||
本 skill 对应 shortcut:`lark-cli drive +permission-get-setting`。它读取单个 Drive 资源自身的公开访问、分享、协作者管理、安全与评论权限设置,不递归读取文件夹中的子资源。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 通过 URL 自动推断 type
|
||||
lark-cli drive +permission-get-setting \
|
||||
--token 'https://example.feishu.cn/drive/folder/<folder_token>' \
|
||||
--as user --format json
|
||||
|
||||
# 通过 bare token 显式指定 type
|
||||
lark-cli drive +permission-get-setting \
|
||||
--token '<folder_token>' \
|
||||
--type folder \
|
||||
--as user --format json
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--token` | 是 | bare token 或完整 URL。URL 路径支持 `/folder/`、`/docx/`、`/doc/`、`/sheets/`、`/base/`、`/bitable/`、`/wiki/`、`/file/`、`/mindnotes/`、`/slides/`、`/minutes/`。 |
|
||||
| `--type` | bare token 必填 | 目标类型:`doc` / `sheet` / `file` / `wiki` / `bitable` / `docx` / `mindnote` / `minutes` / `slides` / `folder`。URL 可自动推断;如果同时传 URL 和冲突的 `--type`,CLI 会拒绝。 |
|
||||
| `--dry-run` | 否 | 只打印请求,不调用 API。 |
|
||||
|
||||
## 输出
|
||||
|
||||
JSON 输出中的 `data.permission_public` 是目标当前的权限设置;服务端未返回该字段时,命令会报响应结构错误,而不会把其他字段伪装成权限设置。
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"identity": "user",
|
||||
"data": {
|
||||
"permission_public": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`--format pretty` 会展示完整的 `permission_public` 对象,包括服务端将来新增的字段。
|
||||
|
||||
## 行为说明
|
||||
|
||||
- **身份支持**:`--as user` 和 `--as bot` 均可用。
|
||||
- **所需 scope**:`docs:permission.setting:read`。
|
||||
- **单目标读取**:命令只读取 `--token` 指向资源自身的权限设置;`--type folder` 不会递归读取子资源。
|
||||
@@ -28,7 +28,7 @@ lark-cli drive +secure-label-list --page-size 10 --lang zh
|
||||
```bash
|
||||
lark-cli drive +secure-label-update \
|
||||
--token "https://example.feishu.cn/docx/doxcnxxxx" \
|
||||
--label-id '<label-id>' # replace $LABEL_ID before running
|
||||
--label-id "7217780879644737539"
|
||||
```
|
||||
|
||||
参数:
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
lark-cli drive +inspect --url '<url>' --as user --format json
|
||||
```
|
||||
|
||||
`drive +inspect` 支持 Drive folder,并且是受支持 Drive URL 的统一解析入口。对文件夹自身权限设置,先通过 `+inspect` 解析 URL,或直接使用 `drive +permission-get-setting --token '<folder_url>'`;传 bare folder token 时必须显式传 `--type folder`。
|
||||
|
||||
`/wiki/space/<space_id>` URL 是 Wiki space 范围,不要用 `drive +inspect` 当作单文档解析;直接提取 `space_id` 后进入 `DISCOVER_TARGETS`。
|
||||
|
||||
## 目标发现
|
||||
@@ -27,16 +25,16 @@ lark-cli drive +inspect --url '<url>' --as user --format json
|
||||
lark-cli wiki +node-list \
|
||||
--space-id '<space_id>' --page-size 50 \
|
||||
--page-all --page-limit 0 \
|
||||
--as user --format json # replace $SPACE_ID before running
|
||||
--as user --format json
|
||||
|
||||
lark-cli wiki +node-list \
|
||||
--space-id '<space_id>' --parent-node-token '<node_token>' --page-size 50 \
|
||||
--page-all --page-limit 0 \
|
||||
--as user --format json # replace $SPACE_ID before running
|
||||
--as user --format json
|
||||
|
||||
lark-cli wiki +node-list \
|
||||
--space-id '<space_id>' --page-token '<PAGE_TOKEN>' --page-size 50 \
|
||||
--as user --format json # replace $SPACE_ID before running
|
||||
--as user --format json
|
||||
```
|
||||
|
||||
解析返回时使用 `data.nodes`,不要读取顶层 `items`。`--page-limit 0` 表示当前层分页不设页数上限;`--page-all` 只覆盖当前 `space-id` / `parent-node-token` 范围内的分页,不会递归子节点。节点 `has_child=true` 时,必须继续以该节点的 `node_token` 作为 `--parent-node-token` 递归读取。
|
||||
@@ -63,42 +61,14 @@ lark-cli drive metas batch_query \
|
||||
--as user --format json
|
||||
```
|
||||
|
||||
读取权限设置:
|
||||
读取 public permission:
|
||||
|
||||
```bash
|
||||
lark-cli drive +permission-get-setting \
|
||||
--token '<url-or-token>' --type '<type>' \
|
||||
lark-cli drive permission.public get \
|
||||
--params '{"token":"<token>","type":"<type>"}' \
|
||||
--as user --format json
|
||||
```
|
||||
|
||||
裸 folder token 必须显式传 `--type folder`:
|
||||
|
||||
```bash
|
||||
lark-cli drive +permission-get-setting \
|
||||
--token '<folder_token>' --type folder \
|
||||
--as user --format json
|
||||
```
|
||||
|
||||
通过 URL 读取权限设置时可以省略 `--type`:
|
||||
|
||||
```bash
|
||||
lark-cli drive +permission-get-setting \
|
||||
--token '<url>' \
|
||||
--as user --format json # replace $LARK_DRIVE_URL before running
|
||||
```
|
||||
|
||||
按需读取直接协作者/授权成员列表:
|
||||
|
||||
```bash
|
||||
lark-cli drive +member-list \
|
||||
--token '<token_or_url>' \
|
||||
--type '<type>' \
|
||||
--fields 'name,type,external_label' \
|
||||
--as user --format json
|
||||
```
|
||||
|
||||
`--fields` 默认不传;只有需要名称、协作者类型、头像或外部标签时才显式传。它只声明期望返回的字段,不授予字段级权限:请求用户的 `name` / `avatar` 时还需 `contact:user.base:readonly`(“获取用户基本信息”)。字段权限或数据可见性不足时,接口仍可能成功但省略相应字段;缺字段不能解释为空值。
|
||||
|
||||
按需读取访问统计:
|
||||
|
||||
```bash
|
||||
@@ -190,9 +160,9 @@ lark-cli drive +secure-label-list \
|
||||
```bash
|
||||
lark-cli drive +secure-label-update \
|
||||
--token '<url>' \
|
||||
--label-id '<label-id>' --as user --format json # replace $LABEL_ID before running
|
||||
--label-id '<label-id>' --as user --format json
|
||||
|
||||
lark-cli drive +secure-label-update \
|
||||
--token '<bare-token>' --type '<type>' \
|
||||
--label-id '<label-id>' --as user --format json # replace $LABEL_ID before running
|
||||
--label-id '<label-id>' --as user --format json
|
||||
```
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
- 多目标明确列表默认输出逐目标诊断摘要;不要因为目标数大于 1 就套用容器递归发现报告。
|
||||
- 用户可见结论默认跟随用户当前语言。用户用中文提问时输出中文,用户用英文提问时输出英文;混合语言时跟随主要语言。
|
||||
- 单目标公开性判断默认输出业务表达,不直接展示 `link_share_entity`、`external_access_entity`、`external_access` 等底层字段名;只有用户要求 raw evidence、排障,或完整清单 / artifact 场景才展示底层字段。
|
||||
- 中文用户可见输出中,`permission_public` / `public permission` 默认译为“目标公共访问和协作权限设置”;可在摘要里简称“公共访问与协作设置”。优先按实际返回字段解释公开访问、分享、协作者管理、安全与评论设置;复制内容、创建副本、打印、下载等字段只有在当前 CLI schema 和实际响应返回时才可判断。只有命令名、schema 字段、raw evidence、排障信息和完整 artifact 字段名保留英文原文。
|
||||
- 中文用户可见输出中,`permission_public` / `public permission` 默认译为“文档公共访问和协作权限设置”;可在摘要里简称“公共访问与协作设置”。它在官方语义中包含链接分享、对外分享、协作者管理、复制内容、创建副本、打印、下载和评论;具体可判断字段以当前 CLI schema 和实际响应为准。只有命令名、schema 字段、raw evidence、排障信息和完整 artifact 字段名保留英文原文。
|
||||
- 容器目标默认输出安全诊断报告摘要:一句话结论、覆盖情况、风险分级、优先处理对象、建议下一步和剩余限制。
|
||||
- 容器目标不要把风险按数量机械排序;外部公开、允许对外分享、缺失密级标签优先于复制 / 下载 / 评论这类依赖策略的候选项。
|
||||
- 用户没有提供明确 policy 时,使用“候选风险 / 待复核 / 待策略确认”,不要写“违规 / 已泄露 / 已外部访问”。
|
||||
@@ -36,7 +36,7 @@
|
||||
- 当摘要未展示全部风险对象时,必须明确“完整清单包含 <count> 条”,并提供生成 Markdown / CSV / 飞书文档风险清单或整改 dry-run 的下一步。
|
||||
- 只要发现需要处理的对象,最终回复必须给出可执行下一步 CTA。不能因为默认只读,就只报告风险后结束。
|
||||
- 完整风险清单是后续治理选择的输入;Markdown / CSV / 飞书文档报告必须使用同一套字段和稳定 `risk_id`。
|
||||
- 写入前必须使用确认模板;权限申请、目标公共访问和协作权限设置修改、owner 转移、密级标签更新分别确认。
|
||||
- 写入前必须使用确认模板;权限申请、文档公共访问和协作权限设置修改、owner 转移、密级标签更新分别确认。
|
||||
- 最终回复必须包含已完成事项、验证结果和剩余限制;异步权限申请审批不能表述为已完成授权。
|
||||
|
||||
## Semantic Rendering
|
||||
@@ -75,7 +75,7 @@
|
||||
| `lock_switch=true` | `lock_state=locked_not_inheriting` | 已限制权限,不再继承父级页面权限 | The node is locked and no longer inherits parent-page permissions |
|
||||
| `lock_switch=false` | `lock_state=not_locked_or_inheriting` | 未限制权限,可能继承父级页面权限 | The node is not locked and may inherit parent-page permissions |
|
||||
| field absent / unsupported | `<state>=unknown` | 当前 schema 未返回,无法判断 | The current schema did not return this field, so it is unknown |
|
||||
| `check_scope=current_public_permission_only` | `check_scope=current_public_permission_only` | 本次判断的是当前目标公共访问和协作权限设置,不是协作者名单或历史权限变更审计 | This check covers the target's current public access and collaboration settings, not collaborator-list or historical permission-change auditing |
|
||||
| `check_scope=current_public_permission_only` | `check_scope=current_public_permission_only` | 本次判断的是当前文档公共访问和协作权限设置,不是协作者名单或历史权限变更审计 | This check covers current public access and collaboration settings, not collaborator-list or historical permission-change auditing |
|
||||
| `sec_label_name` missing | `sec_label=missing` | 缺少密级标签 | Security label is missing |
|
||||
|
||||
## 定位与治理动作
|
||||
@@ -165,7 +165,7 @@ Evidence fields:
|
||||
|
||||
覆盖情况:
|
||||
- 用户提供目标:<input_target_count>;成功解析:<resolved_count>
|
||||
- 成功读取目标公共访问和协作权限设置:<permission_checked_count>;读取失败 / 不支持 / 无权限:<failed_or_unsupported_count>
|
||||
- 成功读取文档公共访问和协作权限设置:<permission_checked_count>;读取失败 / 不支持 / 无权限:<failed_or_unsupported_count>
|
||||
|
||||
逐目标结果(1-10 个目标默认全部展示;超过 10 个时按 `摘要清单展开规则` 展示,并提示生成完整风险清单):
|
||||
|
||||
@@ -233,7 +233,7 @@ URL:<url-or-token-if-url-unavailable>
|
||||
|
||||
覆盖情况:
|
||||
- 当前身份可见目标:<visible_count>
|
||||
- 已成功检查目标公共访问和协作权限设置:<permission_checked_count>
|
||||
- 已成功检查文档公共访问和协作权限设置:<permission_checked_count>
|
||||
- 读取失败 / 已删除 / 无权限:<failed_count>
|
||||
- 未覆盖能力:<collaborator_list / inheritance / audit_log / view_records / none>
|
||||
|
||||
@@ -355,8 +355,8 @@ Agent 必须回复:
|
||||
- 字段变更:
|
||||
- <risk_id> <path> (<url-or-token>): <field> <old> -> <new>
|
||||
- 跳过项:<unsupported / no manage_public / unsupported type / missing policy>
|
||||
- 验证方式:执行后重新读取 <元数据 / 目标公共访问和协作权限设置>
|
||||
- 有限回滚范围:<目标公共访问和协作权限设置快照字段 / 不适用>
|
||||
- 验证方式:执行后重新读取 <元数据 / 文档公共访问和协作权限设置>
|
||||
- 有限回滚范围:<文档公共访问和协作权限设置快照字段 / 不适用>
|
||||
|
||||
请确认是否进入写入确认。
|
||||
```
|
||||
@@ -407,8 +407,8 @@ Agent 必须回复:
|
||||
- 风险:<risk_level>
|
||||
- 字段变更:
|
||||
- <field>: <old> -> <new>
|
||||
- 验证方式:执行后重新读取 <元数据 / 目标公共访问和协作权限设置>
|
||||
- 有限回滚材料:<目标公共访问和协作权限设置快照 / 不适用>
|
||||
- 验证方式:执行后重新读取 <元数据 / 文档公共访问和协作权限设置>
|
||||
- 有限回滚材料:<文档公共访问和协作权限设置快照 / 不适用>
|
||||
|
||||
请确认是否执行。
|
||||
```
|
||||
@@ -419,6 +419,6 @@ Agent 必须回复:
|
||||
已完成:<read checks / writes>
|
||||
验证:<fresh read result or async permission-request approval note>
|
||||
清单状态:<risk_id status updates / not applicable>
|
||||
回滚材料:<目标公共访问和协作权限设置快照 / 不适用>
|
||||
回滚材料:<文档公共访问和协作权限设置快照 / 不适用>
|
||||
剩余限制:<unsupported_checks / partial facts / approvals>
|
||||
```
|
||||
|
||||
@@ -38,11 +38,11 @@ Risk / Structure: `R2` / `S2`
|
||||
- 目录组织、迁移、归档或清理;这类需求应使用知识整理 workflow。
|
||||
- 内容审查、过期内容判断或知识质量评分。
|
||||
- backup owner 补充、部门 / 项目负责人绑定、协作者创建 / 撤销、成员列表审计;本 workflow 只支持把 owner 转移给每个目标明确指定的新 owner,不建模 backup owner 或负责人绑定关系。
|
||||
- 文件夹自身公开权限审计或修复。文件夹自身权限设置可以用 `drive +permission-get-setting` 读取;写入是否支持必须以运行时 schema 和明确需求为准,不能猜测执行 `patch type=folder`。
|
||||
- 文件夹自身公开权限审计或修复。`drive permission.public get` / `patch` 不支持 `type=folder`;必须记录到 `unsupported_checks`,然后继续读取文件夹下其他支持的文档事实。
|
||||
- 当前身份无法枚举到的不可见文档的完整发现;只能处理已发现目标,或用户显式提供的 URL / token。
|
||||
- 未按范围确认的批量写入。
|
||||
|
||||
协作者列表读取只覆盖当前目标的直接协作者/授权成员:可使用 `drive +member-list` 。
|
||||
不要声称已完成协作者列表验证:当前 CLI surface 没有 `permission.members list` shortcut。
|
||||
|
||||
## Progressive Load Map
|
||||
|
||||
@@ -53,7 +53,7 @@ Risk / Structure: `R2` / `S2`
|
||||
| `PARSE_INTENT` | 本文件、[`lark-drive-workflow.md`](lark-drive-workflow.md)、[`../../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) |
|
||||
| `TARGET_INSPECT` | [`lark-drive-inspect.md`](lark-drive-inspect.md) |
|
||||
| `DISCOVER_TARGETS` | 容器范围时读取 [`../../lark-wiki/references/lark-wiki-node-list.md`](../../lark-wiki/references/lark-wiki-node-list.md) 或 [`lark-drive-files-list.md`](lark-drive-files-list.md) |
|
||||
| `FACT_READ` | `lark-cli schema drive.metas.batch_query`;涉及权限设置读取时使用 `drive +permission-get-setting`;涉及活跃度、访问复核或生命周期判断时再读取 `lark-cli schema drive.file.statistics.get` 和 `lark-cli schema drive.file.view_records.list` |
|
||||
| `FACT_READ` | `lark-cli schema drive.metas.batch_query`;涉及公开权限时再读取 `lark-cli schema drive.permission.public.get`;涉及活跃度、访问复核或生命周期判断时再读取 `lark-cli schema drive.file.statistics.get` 和 `lark-cli schema drive.file.view_records.list` |
|
||||
| `RISK_ASSESS` | 本文件的 `Risk Classification` |
|
||||
| `EXEC_CONFIRM` | 只为用户选择的动作读取 [`lark-drive-apply-permission.md`](lark-drive-apply-permission.md)、[`lark-drive-secure-label.md`](lark-drive-secure-label.md),或 `lark-cli schema drive.permission.public.patch` / `lark-cli schema drive.permission.members.transfer_owner`;需要确认模板时读取 [`lark-drive-workflow-permission-governance-outputs.md`](lark-drive-workflow-permission-governance-outputs.md) |
|
||||
| `EXECUTE` | 复用 `EXEC_CONFIRM` 已加载且已确认的写命令上下文 |
|
||||
@@ -76,9 +76,9 @@ Risk / Structure: `R2` / `S2`
|
||||
| State | Protocol Step | Agent MUST Do | User-Facing Output | wait_for_user | Next State |
|
||||
|-------|---------------|---------------|--------------------|---------------|------------|
|
||||
| `PARSE_INTENT` | `route` / `scope` | 解析 intent、target scope、desired policy,以及只读审计、单目标公开性判断、权限申请、owner 转移还是修复模式;单目标公开性判断设置 `intent=public_exposure_check`、`target_scope=single_resource` | 范围确认;如果缺少目标、新 owner 或期望动作,只问一个澄清问题 | 缺少 target / new owner / action,或容器范围需要用户确认时为 `true` | `TARGET_INSPECT` |
|
||||
| `TARGET_INSPECT` | `scope` | 解析单资源、明确列表、Wiki space / node、Drive folder;Drive folder 直接从 URL 路径或显式 `type=folder` 解析,不调用 `drive +inspect`;保留原始 URL、scope type、canonical token/type | 目标范围表,包含 scope、title/type/token status | 除非解析失败,否则为 `false` | `DISCOVER_TARGETS` or `FACT_READ` |
|
||||
| `TARGET_INSPECT` | `scope` | 解析单资源、明确列表、Wiki space / node、Drive folder;保留原始 URL、scope type、canonical token/type | 目标范围表,包含 scope、title/type/token status | 除非解析失败,否则为 `false` | `DISCOVER_TARGETS` or `FACT_READ` |
|
||||
| `DISCOVER_TARGETS` | `scope` / `read` | 对 Wiki space / node 或 Drive folder 递归只读枚举,归一化为 `discovered_targets`;记录 `discovery_blockers` | 发现进度和覆盖摘要;不展示内部 cursor/token,除非用户要求 | 除非发现范围无法确认或全部被阻断,否则为 `false` | `FACT_READ` |
|
||||
| `FACT_READ` | `read` | 对直接目标或 `discovered_targets` 执行 `drive metas batch_query`;对支持的文件、文件夹或云文档目标执行 `drive +permission-get-setting` 读取自身权限设置;当 `intent=public_exposure_check` 且 `target_scope=single_resource` 时,可复用 `drive +inspect` 返回的 title / URL / type,只补读目标公共访问和协作权限设置;在用户要求活跃度 / 访问复核 / 生命周期判断时读取访问统计和访问记录 | 权限事实摘要、coverage summary、activity facts 和 unsupported checks | 除非所有目标都被 auth 阻断,否则为 `false` | `RISK_ASSESS` |
|
||||
| `FACT_READ` | `read` | 对直接目标或 `discovered_targets` 执行 `drive metas batch_query`;对支持的非 folder 目标执行 `drive permission.public get`;当 `intent=public_exposure_check` 且 `target_scope=single_resource` 时,可复用 `drive +inspect` 返回的 title / URL / type,只补读文档公共访问和协作权限设置;在用户要求活跃度 / 访问复核 / 生命周期判断时读取访问统计和访问记录 | 权限事实摘要、coverage summary、activity facts 和 unsupported checks | 除非所有目标都被 auth 阻断,否则为 `false` | `RISK_ASSESS` |
|
||||
| `RISK_ASSESS` | `assess/plan` | 对每个可审计目标生成 `per_target_permission_assessment` 并分类证据;如用户提供 policy,则对照 policy;`public_exposure_check + single_resource` 只渲染单目标结论,不生成 `risk_id`;owner 转移路径生成 `owner_transfer_candidates` / `owner_transfer_plan`;治理路径构建可定位风险清单、访问复核清单、dry-run 整改计划或候选修复计划,完整清单必须生成稳定 `risk_id` | 带 priority、URL、risk_id、owner、sec_label 的 findings、confidence、review items、建议动作和下一步 CTA;单目标公开性判断只输出结论和关键字段 | 治理路径为 `true`,单目标公开性判断为 `false` | `EXEC_CONFIRM` or `DONE` |
|
||||
| `EXEC_CONFIRM` | `confirm` | 展示准确写入范围、command family、target count、risk、verification method | 确认请求 | `true` | `EXECUTE` or `DONE` |
|
||||
| `EXECUTE` | `execute` | 只执行 `Command Map` 中已确认的写入 | 进度 / 结果摘要 | 除非被阻断,否则为 `false` | `VERIFY` |
|
||||
@@ -91,23 +91,21 @@ Risk / Structure: `R2` / `S2`
|
||||
|
||||
| State | Allowed Command Families | Purpose |
|
||||
|-------|--------------------------|---------|
|
||||
| `TARGET_INSPECT` | `drive +inspect` | 解析非 folder URL、type、canonical token、title 和 wiki unwrap data;Drive folder 不支持 `+inspect`,必须从 URL 路径或显式 `type=folder` 直接解析 |
|
||||
| `TARGET_INSPECT` | `drive +inspect` | 解析 URL、type、canonical token、title 和 wiki unwrap data |
|
||||
| `DISCOVER_TARGETS` | `wiki +node-list` | 递归发现 Wiki space / node 下当前身份可见的节点 |
|
||||
| `DISCOVER_TARGETS` | `drive files list` | 递归发现 Drive folder 下当前身份可见的文件和子文件夹 |
|
||||
| `FACT_READ` | `drive metas batch_query` | 读取 title、URL、owner 和 secure-label metadata |
|
||||
| `FACT_READ` | `drive permission.public get` | 读取支持类型的文档公共访问和协作权限设置,包括链接分享、对外分享、协作者管理、复制内容、创建副本、打印、下载和评论 |
|
||||
| `FACT_READ` | `drive +member-list` | 读取用户显式要求的单目标直接协作者/授权成员列表;不代表完整继承链或历史权限审计 |
|
||||
| `FACT_READ` | `drive +permission-get-setting` | 读取支持类型的文件、文件夹或云文档自身权限设置,包括公开访问、分享、协作者管理、安全与评论 |
|
||||
| `FACT_READ` | `drive file.statistics get` | 在用户要求活跃度、闲置暴露、生命周期或访问复核时读取文件访问统计 |
|
||||
| `FACT_READ` | `drive file.view_records list` | 在用户要求最近访问人、访问复核或低活跃证据时读取访问记录 |
|
||||
| `EXEC_CONFIRM` | `drive +secure-label-list` | 提议 label update 前解析可用 secure-label IDs |
|
||||
| `EXEC_CONFIRM` | `drive permission.members auth` | 目标公共访问和协作权限设置修改前检查 `action=manage_public` |
|
||||
| `EXEC_CONFIRM` | `drive permission.members auth` | 文档公共访问和协作权限设置修改前检查 `action=manage_public` |
|
||||
| `EXEC_CONFIRM` | `lark-cli schema drive.permission.members.transfer_owner` | owner 转移前读取当前字段、支持类型和高风险写入门禁 |
|
||||
| `EXECUTE` | `drive +apply-permission` | 向 owner 提交 view/edit access request;只允许单目标、小列表或已明确确认的候选列表逐个执行 |
|
||||
| `EXECUTE` | `drive permission.public patch` | 修改已确认的 public/link settings;必须传 `--yes` |
|
||||
| `EXECUTE` | `drive permission.members transfer_owner` | 转移已确认目标的 owner;必须传 `--yes` |
|
||||
| `EXECUTE` | `drive +secure-label-update` | 设置已确认的 secure-label ID |
|
||||
| `VERIFY` | `drive metas batch_query`, `drive +permission-get-setting` | 验证支持的 metadata,包括 owner、secure-label 和目标公共访问与协作权限设置变更;权限申请只能表述为已发起 |
|
||||
| `VERIFY` | `drive metas batch_query`, `drive permission.public get` | 验证支持的 metadata,包括 owner、secure-label 和文档公共访问与协作权限设置变更;权限申请只能表述为已发起 |
|
||||
|
||||
## Command Patterns
|
||||
|
||||
@@ -121,9 +119,9 @@ Risk / Structure: `R2` / `S2`
|
||||
|
||||
1. "所有文档"只表示当前身份在确认范围内可枚举到的文档。不可见、无权限、API 不返回或工具预算不足的部分必须进入 `discovery_blockers` 或 `unsupported_checks`。
|
||||
2. 发现阶段必须生成稳定 `path`。不要只保存 title;同名文档必须能通过 path 或 token 区分。
|
||||
3. 权限设置读取使用 `drive +permission-get-setting`,目标类型包括 `doc`、`sheet`、`file`、`wiki`、`bitable`、`docx`、`mindnote`、`minutes`、`slides`、`folder`;未来新增类型以 shortcut 和 OpenAPI 元数据为准。
|
||||
3. 只把 `drive.permission.public.get` 当前 schema 支持的类型加入公开权限可审计目标。已知支持包括 `doc`、`sheet`、`file`、`wiki`、`bitable`、`docx`、`mindnote`、`minutes`、`slides`;未来新增类型以运行时 schema 为准。
|
||||
4. `minutes` 只能作为 `partial_public_permission` 目标:可读取 / 修改公开权限和 owner 转移能力以运行时 schema 为准,但 `drive metas batch_query` 当前不支持 `minutes`,URL、owner、密级等 metadata 可能进入 `unsupported_checks`。
|
||||
5. `folder` 作为递归容器时先枚举子资源;如用户明确要查询文件夹自身权限设置,可对该文件夹单独执行 `drive +permission-get-setting --token <folder_token> --type folder`。不要执行 raw `permission.public patch type=folder`,除非 schema 和需求都明确支持。`shortcut`、`catalog` 或缺少 stable token/type 的条目必须记录为 unsupported,除非后续 API 明确解析出支持目标。
|
||||
5. `folder` 只作为递归容器,不执行 `permission.public get` / `patch`。如果用户明确要求 owner 转移且 schema 支持 `folder`,必须按 owner-transfer 写入规则单独确认。`shortcut`、`catalog` 或缺少 stable token/type 的条目必须记录为 unsupported,除非后续 API 明确解析出支持目标。
|
||||
6. 对大范围目标输出进度时,只展示已扫描容器数、已发现目标数、已审计目标数、剩余队列或 blocker;不要默认展示内部 page token / cursor。
|
||||
|
||||
Wiki space / node 发现:
|
||||
@@ -135,7 +133,7 @@ Wiki space / node 发现:
|
||||
|
||||
Drive folder 发现:
|
||||
|
||||
1. `/drive/folder/<folder_token>` 解析为 `target_scope=drive_folder`。默认继续枚举其子文档;只有用户明确要求文件夹自身权限设置时,才额外调用 `drive +permission-get-setting --token <folder_token> --type folder` 读取该文件夹自身设置。
|
||||
1. `/drive/folder/<folder_token>` 解析为 `target_scope=drive_folder`。文件夹自身公开权限不支持;继续枚举其子文档。
|
||||
2. 按 [`lark-drive-files-list.md`](lark-drive-files-list.md) 递归处理 `data.files`、`has_more` 和 `next_page_token`。不要把第一页数量当作完整范围。
|
||||
3. 只对返回项中的 `folder` 继续递归;对子文档按 `type + token` 归一化为 `discovered_targets`。
|
||||
4. 如果某个目录分页失败、无 continuation token、权限不足或 API 报错,只阻断该目录分支,并在 `discovery_blockers` 中记录;继续处理其他可枚举分支。
|
||||
@@ -143,11 +141,11 @@ Drive folder 发现:
|
||||
## Fact Read Rules
|
||||
|
||||
1. `drive metas batch_query` 单次最多 200 个 `request_docs`;当 `targets` 或 `discovered_targets` 超过 200 个时,必须分批读取并合并结果。
|
||||
2. `drive +permission-get-setting` 没有批量读取接口;对支持目标逐个读取。单个目标失败时记录 `unsupported_checks` 或 `partial`,不要阻断其他目标。
|
||||
2. `drive permission.public get` 没有批量读取接口;对支持目标逐个读取。单个目标失败时记录 `unsupported_checks` 或 `partial`,不要阻断其他目标。
|
||||
3. 对 Wiki 发现目标,公开权限读取优先使用 `type=wiki` + `node_token`;metadata 可使用 `obj_type` + `obj_token` 补充 title、owner、URL 和 `sec_label_name`。
|
||||
4. 当 intent 是 `list_permission_settings` 时,只输出权限设置清单和覆盖限制,不主动生成修复计划。
|
||||
5. 单目标、多目标明确列表和容器发现目标都必须复用同一套逐目标事实读取与语义归一逻辑;差异只体现在目标来源、coverage summary 和输出聚合。
|
||||
6. `permission_public` 用户可见含义是“目标公共访问和协作权限设置”,语义以官方 OpenAPI 字段说明为准,同时兼容当前 CLI schema 返回的字段:优先使用 `external_access_entity`,缺失时才用 `external_access` boolean 映射为 `open` / `closed`;`manage_collaborator_entity`、`copy_entity`、`lock_switch` 等字段缺失时标记为 unknown,不要伪造;未识别字段保留在 raw evidence / partial note 中。
|
||||
6. `permission_public` 用户可见含义是“文档公共访问和协作权限设置”,语义以官方 OpenAPI 字段说明为准,同时兼容当前 CLI schema 返回的字段:优先使用 `external_access_entity`,缺失时才用 `external_access` boolean 映射为 `open` / `closed`;`manage_collaborator_entity`、`copy_entity`、`lock_switch` 等字段缺失时标记为 unknown,不要伪造;未识别字段保留在 raw evidence / partial note 中。
|
||||
7. `drive file.statistics get` 和 `drive file.view_records list` 只在用户要求最近访问、活跃度、闲置暴露、访问复核,或用户提供的 policy 明确依赖活跃度时执行;不要为普通权限审计默认读取访问记录。
|
||||
8. 访问统计 / 访问记录当前只对 `doc`、`docx`、`sheet`、`bitable`、`mindnote`、`wiki`、`file` 作为支持类型处理。其他类型必须进入 `unsupported_checks`,不能推断活跃度。
|
||||
9. `view_records` 是访问证据,不是权限列表。没有返回访问记录只能表述为“未获得最近访问证据”或“低活跃候选”,不能表述为“无人有权限”。
|
||||
@@ -164,17 +162,17 @@ Drive folder 发现:
|
||||
- `PolicyReview`:复制、创建副本、打印、下载、评论等依赖 policy 的设置;没有明确 policy 时不要称为高风险。
|
||||
- `Unknown`:读取失败、已删除、无权限、API 不支持、协作者名单 / 继承链 / DLP / AI 索引 / 审计日志未覆盖。
|
||||
|
||||
每个可审计目标都必须先归一化为 `per_target_permission_assessment`,再按 [`lark-drive-workflow-permission-governance-outputs.md`](lark-drive-workflow-permission-governance-outputs.md) 的 `Semantic Rendering` 渲染。`public_exposure_check` 只是 `target_count=1` 的轻量渲染模式;它和多目标、容器诊断复用同一套语义字段与风险分类。该判断只覆盖当前目标公共访问和协作权限设置,不审计协作者名单、历史权限变更、完整继承链或审计日志。
|
||||
每个可审计目标都必须先归一化为 `per_target_permission_assessment`,再按 [`lark-drive-workflow-permission-governance-outputs.md`](lark-drive-workflow-permission-governance-outputs.md) 的 `Semantic Rendering` 渲染。`public_exposure_check` 只是 `target_count=1` 的轻量渲染模式;它和多目标、容器诊断复用同一套语义字段与风险分类。该判断只覆盖当前文档公共访问和协作权限设置,不审计协作者名单、历史权限变更、完整继承链或审计日志。
|
||||
|
||||
`AI 检索暴露候选风险` 只是基于权限和标签的代理标签。除非另有工具明确返回索引状态,否则不要声称某个文档已经被 Agent、Copilot 或 RAG 索引。
|
||||
|
||||
## 写入规则
|
||||
|
||||
- 目标公共访问和协作权限设置修改(`drive permission.public patch`)属于高风险写入。请求确认前,必须展示 target title、token、current setting、desired setting 和准确 field changes。
|
||||
- 文档公共访问和协作权限设置修改(`drive permission.public patch`)属于高风险写入。请求确认前,必须展示 target title、token、current setting、desired setting 和准确 field changes。
|
||||
- 如果 `manage_public_auth.auth_result=false`,禁止 patch。告诉用户需要具备 manage-public 权限的用户,或由 owner 操作。
|
||||
- 权限设置读取使用 `drive +permission-get-setting`;裸 token 必须传 `--type`,URL 可以自动推断。写入仍使用 `drive permission.public patch`,只 patch 已解析且 schema 明确支持的类型和字段,不要把读取支持的 `folder` 自动外推为可写入。
|
||||
- `drive permission.public get` 只用于 `drive +inspect` 或 `DISCOVER_TARGETS` 可解析且运行时 schema 支持的目标类型;类型集合不要硬编码,执行时以 `lark-cli schema drive.permission.public.get` 为准。
|
||||
- 不要 patch 已解析类型不支持的字段。对于 wiki 目标,必须省略 schema 明确标注为 wiki 不支持的字段。
|
||||
- 不要在同一个写入确认中合并密级标签更新和目标公共访问与协作权限设置修改;必须分别确认。
|
||||
- 不要在同一个写入确认中合并密级标签更新和文档公共访问与协作权限设置修改;必须分别确认。
|
||||
- `drive +apply-permission` 默认不批量执行;每次调用都会向 owner 发送通知。
|
||||
- `permission_request_candidates` 可以来自用户直接提供的目标、明确列表或容器发现目标;只要能构造 token、type、权限类型和申请理由,就可以进入候选。不要因为目标不在 `discovered_targets` 中而拒绝单目标 / 小列表权限申请。
|
||||
- 容器范围内的"统一申请权限"必须先产出 `permission_request_candidates`。未展示候选目标、数量、权限类型和 owner 通知影响前,禁止调用 `drive +apply-permission`。
|
||||
@@ -184,8 +182,8 @@ Drive folder 发现:
|
||||
- 批量 owner 转移必须逐个顺序执行;失败项进入结果清单,不要重复执行已成功目标。`remove_old_owner=true` 或 `old_owner_perm` 降权必须单独在确认中高亮。
|
||||
- 用户要求“生成整改方案 / dry-run / 先看看会改什么”时,只生成 `remediation_plan`,不执行任何写命令。dry-run 必须包含 target count、field changes、跳过原因、验证方式和有限回滚范围。
|
||||
- 用户基于完整风险清单选择对象时,必须先解析 `risk_id`、风险分组、URL 或 artifact 中 `selected=true` 的行,生成 `selected_risk_items`。无法匹配到当前 `risk_manifest` 的选择必须要求用户重新确认或重新读取清单。
|
||||
- 针对 `selected_risk_items` 生成 dry-run 前,必须重新读取所选目标的 `drive +permission-get-setting`;如果当前设置和清单快照不同,标记为 `changed_since_report` 并跳过或要求用户确认更新后的计划。
|
||||
- 执行 `drive permission.public patch` 前,必须把当前 `public_permission_facts` 中会被改动的字段保存为 `public_permission_snapshots`。该快照只用于目标公共访问和协作权限设置字段的有限回滚说明,不覆盖协作者、owner、继承权限或密级标签。
|
||||
- 针对 `selected_risk_items` 生成 dry-run 前,必须重新读取所选目标的 `drive permission.public get`;如果当前设置和清单快照不同,标记为 `changed_since_report` 并跳过或要求用户确认更新后的计划。
|
||||
- 执行 `drive permission.public patch` 前,必须把当前 `public_permission_facts` 中会被改动的字段保存为 `public_permission_snapshots`。该快照只用于文档公共访问和协作权限设置字段的有限回滚说明,不覆盖协作者、owner、继承权限或密级标签。
|
||||
- 如果用户要求批量收紧权限,必须按风险分层和目标顺序逐个执行;失败项进入结果清单,不要因为单个失败而重复执行已成功目标。
|
||||
- 遇到 secure-label downgrade error `1063013` 时,停止重试,并告诉用户需要在文档 UI 中完成审批。
|
||||
|
||||
@@ -196,7 +194,7 @@ Drive folder 发现:
|
||||
- `drive permission.members create` 可创建协作者权限,但当前 workflow 不做协作者 grant / update / revoke;未来需要单独定义授权对象解析、最小权限、确认模板和验证方式。
|
||||
- backup owner、部门 / 项目负责人绑定没有当前 workflow 可执行写入面;如用户要落地为 owner 转移,必须先给出明确目标和新 owner,并走本 workflow 的 owner-transfer 确认。
|
||||
- `wiki +member-list` 可作为 Wiki space 成员治理的读侧事实来源;当前 workflow 只治理文档 / 节点 / 文件夹下可发现文档的权限,不做 space member governance。
|
||||
- `drive +member-list` 可读取单目标直接协作者/授权成员;当前 CLI 仍没有完整继承链、DLP 扫描、AI 索引状态、审计日志和跨平台权限事实。遇到这些需求必须记录为 `unsupported_checks` 或建议新增独立 workflow。
|
||||
- 当前 CLI 没有 `permission.members list`、完整继承链、DLP 扫描、AI 索引状态、审计日志和跨平台权限事实。遇到这些需求必须记录为 `unsupported_checks` 或建议新增独立 workflow。
|
||||
|
||||
## 输出策略
|
||||
|
||||
|
||||
@@ -75,17 +75,15 @@ metadata:
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**本表只定位「场景 → 用哪条命令、读哪份文档」。参数以「执行前必做」里对应的文档和 `lark-cli slides +<verb> --help` 为准,不要凭记忆或按别的命令类比补参数。**
|
||||
|
||||
| 用户需求 | 优先动作 | 关键文档 / 命令 |
|
||||
|----------|----------|-----------------|
|
||||
| 新建 PPT | 先规划 `slide_plan.json`,再按复杂度选择一步或两步创建 | `planning-layer.md`、`visual-planning.md`、`asset-planning.md`、`lark-slides-create.md`、`slides +create` |
|
||||
| 用户要求使用模板,或提供 PPTX 文件要求修改、美化 | 将模板导入为 Slides 再编辑 | `lark-slides-pptx-template-workflows.md` |
|
||||
| 新建 PPT | 先规划 `slide_plan.json`,再按复杂度选择一步或两步创建 | `planning-layer.md`、`visual-planning.md`、`asset-planning.md`、`slides +create` |
|
||||
| 用户要求使用模板 | 将模板导入为 Slides 再编辑 | `lark-slides-pptx-template-workflows.md` |
|
||||
| 编辑单个标题、文本块、图片或局部元素 | 优先块级替换/插入,不改页序 | `slides +replace-slide`、`lark-slides-replace-slide.md` |
|
||||
| 读取或分析已有 PPT | 解析 slides/wiki token,用 shortcut 回读全文 XML 或读取单页 XML,保存 `xml_presentation_id`、`slide_id`、`revision_id` | `slides +xml-get`、`xml_presentation.slide.get`、`lark-slides-xml-presentations-get.md` |
|
||||
| 查看或回滚历史版本 | 先用 `+history-list` 找 `history_version_id`,再 `+history-revert`,必要时 `+history-revert-status` 轮询 | [`lark-slides-history.md`](references/lark-slides-history.md) |
|
||||
| 获取幻灯片页面截图 | 用 `slide_id` 或页号指定页面,一次不超过 10 页 | `slides +screenshot`、`lark-slides-screenshot.md` |
|
||||
| 上传或使用图片 | 先上传为 `file_token`,禁止直接写 http(s) 外链 | `slides +media-upload`、`lark-slides-media-upload.md`,或 `+create --slides` 的 XML 里写 `<img src="@./path">` 占位符 |
|
||||
| 上传或使用图片 | 先上传为 `file_token`,禁止直接写 http(s) 外链 | `slides +media-upload`、`lark-slides-media-upload.md`,或 `+create --slides` 的 `@./path` 占位符 |
|
||||
| 绘制图表 | 原生图表(柱状、条形、折线、面积、饼(环)、雷达、组合图)用 `<chart>`,其他(漏斗图、金字塔图、象限图、矩阵图等)用 `<shape>` + `<line>` 模拟 | `xml-schema-quick-ref.md`、`slides_chart_demo.xml` |
|
||||
| 绘制表格 | 优先用 `rect` 和 `text` 模拟,其他用 `<table>` | `xml-schema-quick-ref.md` |
|
||||
| 使用图标 | 禁止盲猜 iconType,必须先检索 IconPark,再写 `<icon iconType="...">`,图标必须填充颜色并和背景有足够对比,禁止使用 emoji 图标 | `iconpark_tool.py search → resolve`、`iconpark.md` |
|
||||
@@ -145,7 +143,7 @@ lark-cli auth login --domain slides
|
||||
|
||||
调用相关命令前必须读取相关的文档以了解命令的使用方式:
|
||||
|
||||
- 创建:[`lark-slides-create.md`](references/lark-slides-create.md)、[`lark-slides-xml-presentation-slide-create.md`](references/lark-slides-xml-presentation-slide-create.md)(逐页添加)
|
||||
- 创建:[`lark-slides-create.md`](references/lark-slides-create.md)
|
||||
- 阅读:[`lark-slides-xml-presentations-get.md`](references/lark-slides-xml-presentations-get.md)
|
||||
- 编辑:[`lark-slides-edit-workflows.md`](references/lark-slides-edit-workflows.md)、[`lark-slides-replace-slide.md`](references/lark-slides-replace-slide.md)、[`lark-slides-replace-pages.md`](references/lark-slides-replace-pages.md)
|
||||
- 历史版本:[`lark-slides-history.md`](references/lark-slides-history.md)
|
||||
@@ -191,6 +189,20 @@ lark-cli auth login --domain slides
|
||||
- 不要在任何位置使用 emoji 图标。
|
||||
|
||||
|
||||
### 创建方式选择
|
||||
|
||||
| 场景 | 推荐方式 |
|
||||
|------|----------|
|
||||
| 简单 XML(1-3 页、结构简单、几乎无复杂中文和特殊字符) | `slides +create --slides '[...]'` 一步创建 |
|
||||
| 复杂 XML(多页、含中文、大段文本、复杂布局、嵌套引号、特殊字符较多) | **两步创建**:先 `slides +create` 创建空白 PPT,再用 `xml_presentation.slide create` 逐页添加 |
|
||||
| 已有 PPT 继续追加或插入页面 | 使用 `xml_presentation.slide create`,必要时配合 `before_slide_id` |
|
||||
|
||||
> [!WARNING]
|
||||
> `--slides '[...]'` 的风险点主要在 shell 参数传递,而不是单纯页数。即使只有 1 页,只要 XML 足够复杂,也建议使用两步创建法。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `slides +create --slides` 底层会逐页创建,不是原子操作。中途失败时先记录 `xml_presentation_id`,回读确认当前状态,再继续修复或追加。
|
||||
|
||||
### 生成流程
|
||||
|
||||
```text
|
||||
@@ -208,8 +220,7 @@ Step 2: 生成大纲 → 写入 slide_plan.json
|
||||
Step 3: 按 slide_plan.json 生成 XML → 创建
|
||||
- 逐页消费 plan:key_message 定主结论,layout_type 定几何,visual_focus 定主视觉,text_density 定文本量
|
||||
- 缺少真实素材时必须用 `fallback_if_missing` 生成替代图片,不要留空
|
||||
- 读 lark-slides-create.md 定一步创建还是两步创建,并据此构造 `slides +create`;两步创建再读 lark-slides-xml-presentation-slide-create.md 逐页添加
|
||||
- 图片按 lark-slides-media-upload.md 处理;复杂 XML、转义和 3350001 排查按 troubleshooting.md 执行
|
||||
- 创建方式按“创建方式选择”判断;图片、复杂 XML、转义和 3350001 排查按 lark-slides-create.md、media-upload.md、troubleshooting.md 执行
|
||||
|
||||
Step 4: 审查 & 交付
|
||||
- 创建完成后,必须用 `slides +xml-get --presentation <xml_presentation_id>` 读取全文 XML,并按 validation-checklist.md 做显式验证记录,包括 XML 文本重叠检查
|
||||
@@ -219,7 +230,7 @@ Step 4: 审查 & 交付
|
||||
|
||||
### jq 命令模板(编辑已有 PPT 时使用)
|
||||
|
||||
以下 jq 模板适用于向已有演示文稿追加页面的场景,可以避免手动转义双引号:
|
||||
新建 PPT 推荐用 `+create --slides`。以下 jq 模板适用于向已有演示文稿追加页面的场景,可以避免手动转义双引号:
|
||||
|
||||
```bash
|
||||
# 追加到末尾
|
||||
@@ -302,7 +313,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli slides +<verb> [flags]`
|
||||
|
||||
| Shortcut | 说明 |
|
||||
|----------|------|
|
||||
| [`+create`](references/lark-slides-create.md) | 创建 PPT,可选一步添加页面 |
|
||||
| [`+create`](references/lark-slides-create.md) | 创建 PPT(可选 `--slides` 一步添加页面,支持 `<img src="@./local.png">` 占位符自动上传) |
|
||||
| [`+xml-get`](references/lark-slides-xml-presentations-get.md) | 读取全文 XML,用 `--presentation` 指定演示文稿的 `xml_presentation_id`,用 `--output` 把 XML 存到本地文件(必须是 CWD 内的相对路径,如 `.lark-slides/plan/<deck>/readback.xml`) |
|
||||
| [`+screenshot`](references/lark-slides-screenshot.md) | 把幻灯片页面截图保存为本地图片,用 `--slide-number` 指定页号(从 1 开始,多页重复传入,一次最多 10 页),用 `--output-dir` 指定保存目录(必须是 CWD 内的相对路径,默认 `.lark-slides/screenshots`),失败时降级到 XML 回读等非截图检查 |
|
||||
| [`+media-upload`](references/lark-slides-media-upload.md) | 上传本地图片到指定演示文稿,返回 `file_token`(用作 `<img src="...">`),最大 20 MB |
|
||||
@@ -321,12 +332,12 @@ lark-cli slides <resource> <method> [flags] # 调用 API
|
||||
## 核心规则
|
||||
|
||||
1. **先规划再写 XML**:新建演示文稿或大幅改写页面时,必须先写入 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`;模板、风格和大纲只能作为规划输入,不能绕过规划层
|
||||
2. **创建流程**:新建演示文稿用 `slides +create`,一步创建还是两步创建按 [`lark-slides-create.md`](references/lark-slides-create.md) 判断
|
||||
2. **创建流程**:简单短 XML(1-3 页、结构简单、特殊字符少)可用 `slides +create --slides '[...]'` 一步创建;复杂内容、含图片/中文大段文本/嵌套引号/较多特殊字符,或超过 10 页时,默认先 `slides +create` 创建空白 PPT,再用 `xml_presentation.slide.create` 逐页添加
|
||||
3. **`<slide>` 直接子元素只有 `<style>`、`<data>`、`<note>`**:文本和图形必须放在 `<data>` 内
|
||||
4. **文本通过 `<content>` 表达**:必须用 `<content><p>...</p></content>`,不能把文字直接写在 shape 内
|
||||
5. **保存关键 ID**:后续操作需要 `xml_presentation_id`、`slide_id`、`revision_id`
|
||||
6. **删除谨慎**:删除操作不可逆,且至少保留一页幻灯片
|
||||
7. **编辑已有页面优先原链接更新**:修改单个 shape/img 用 `+replace-slide`(`block_replace` / `block_insert`),不要整页重建;已有 Slides 的多页整页重建用 `+replace-pages`,不要用 `slides +create` 新建整份 PPT;只有没有 shortcut 覆盖的特殊单页整页操作才手动 `slide.create` + `slide.delete`
|
||||
8. **`<img src>` 只能用上传到飞书 drive 的 `file_token`,禁止使用 http(s) 外链 URL**:飞书 slides 渲染端不会代理外链图片,外链 src 在 PPT 里通常不显示或显示破图。流程必须是「先把图存到本地 → 用 `slides +media-upload` 上传,或在 `+create --slides` 的 XML 里写 `<img src="@./path">` 占位符自动上传 → 拿 `file_token` 写进 `<img src>`」。如果用户给了网图链接,先 `curl`/下载到 CWD 内再走上传流程,不要直接把外链 URL 塞进 `src`。**图片最大 20 MB**(slides upload API 不支持分片上传)。
|
||||
8. **`<img src>` 只能用上传到飞书 drive 的 `file_token`,禁止使用 http(s) 外链 URL**:飞书 slides 渲染端不会代理外链图片,外链 src 在 PPT 里通常不显示或显示破图。流程必须是「先把图存到本地 → 用 `slides +media-upload` 上传或 `+create --slides` 的 `@./path` 占位符自动上传 → 拿 `file_token` 写进 `<img src>`」。如果用户给了网图链接,先 `curl`/下载到 CWD 内再走上传流程,不要直接把外链 URL 塞进 `src`。**图片最大 20 MB**(slides upload API 不支持分片上传)。
|
||||
|
||||
> **注意**:如果 md 内容与 `slides_xml_schema_definition.xml` 或 `lark-cli schema slides.<resource>.<method>` 输出不一致,以后两者为准。
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
||||
<title>资产处置类型分布</title>
|
||||
<theme>
|
||||
<textStyles>
|
||||
<headline fontColor="rgba(31, 35, 41, 1)"/>
|
||||
<body fontColor="rgba(31, 35, 41, 1)"/>
|
||||
<caption fontColor="rgba(155, 158, 162, 1)" fontSize="14"/>
|
||||
</textStyles>
|
||||
</theme>
|
||||
<slide>
|
||||
<style>
|
||||
<fill>
|
||||
<fillColor color="rgba(250, 248, 242, 1)"/>
|
||||
</fill>
|
||||
</style>
|
||||
<data>
|
||||
<shape width="800" height="40" topLeftX="80" topLeftY="40" type="text">
|
||||
<content textType="headline" fontSize="24" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)" bold="true">
|
||||
<p>资产处置类型分布</p>
|
||||
</content>
|
||||
</shape>
|
||||
<chart width="700" height="440" topLeftX="130" topLeftY="80">
|
||||
<chartPlotArea>
|
||||
<chartPlot type="pie" yAxisPosition="right">
|
||||
<chartExtra/>
|
||||
<chartLabels position="outside" category="false" value="true" percentage="true" fontSize="14" color="rgba(60, 60, 60, 1)"/>
|
||||
<chartSeriesList>
|
||||
<chartSeries index="1">
|
||||
<chartSectors innerRadius="0.5" offsetRadius="0" startAngle="90"/>
|
||||
</chartSeries>
|
||||
</chartSeriesList>
|
||||
</chartPlot>
|
||||
</chartPlotArea>
|
||||
<chartLegend position="right" fontSize="16" color="rgba(60, 60, 60, 1)"/>
|
||||
<chartData>
|
||||
<dim1>
|
||||
<chartField name="类型">股权处置,土地经营权,停车泊位,供水污水,房产处置,保障房,林水经营</chartField>
|
||||
</dim1>
|
||||
<dim2>
|
||||
<chartField name="规模">24,19,8.5,7.5,5,4.15,2.7</chartField>
|
||||
</dim2>
|
||||
</chartData>
|
||||
<chartStyle>
|
||||
<chartBackground color="rgba(0, 0, 0, 0)"/>
|
||||
<chartBorder color="rgb(222, 224, 227)" width="0"/>
|
||||
<chartColorTheme>
|
||||
<color value="rgb(178, 34, 34)"/>
|
||||
<color value="rgb(205, 50, 50)"/>
|
||||
<color value="rgb(224, 80, 80)"/>
|
||||
<color value="rgb(238, 110, 110)"/>
|
||||
<color value="rgb(218, 165, 32)"/>
|
||||
<color value="rgb(238, 195, 55)"/>
|
||||
<color value="rgb(248, 222, 100)"/>
|
||||
</chartColorTheme>
|
||||
</chartStyle>
|
||||
</chart>
|
||||
</data>
|
||||
<note>
|
||||
<content/>
|
||||
</note>
|
||||
</slide>
|
||||
</presentation>
|
||||
@@ -1,52 +0,0 @@
|
||||
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
||||
<style>
|
||||
<fill>
|
||||
<fillColor color="rgba(250, 248, 242, 1)"/>
|
||||
</fill>
|
||||
</style>
|
||||
<data>
|
||||
<shape width="800" height="40" topLeftX="80" topLeftY="40" type="text">
|
||||
<content textType="headline" fontSize="24" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)" bold="true">
|
||||
<p>资产处置类型分布</p>
|
||||
</content>
|
||||
</shape>
|
||||
<chart width="700" height="440" topLeftX="130" topLeftY="80">
|
||||
<chartPlotArea>
|
||||
<chartPlot type="pie" yAxisPosition="right">
|
||||
<chartExtra/>
|
||||
<chartLabels position="outside" category="false" value="true" percentage="true" fontSize="14" color="rgba(60, 60, 60, 1)"/>
|
||||
<chartSeriesList>
|
||||
<chartSeries index="1">
|
||||
<chartSectors innerRadius="0.5" offsetRadius="0" startAngle="90"/>
|
||||
</chartSeries>
|
||||
</chartSeriesList>
|
||||
</chartPlot>
|
||||
</chartPlotArea>
|
||||
<chartLegend position="right" fontSize="16" color="rgba(60, 60, 60, 1)"/>
|
||||
<chartData>
|
||||
<dim1>
|
||||
<chartField name="类型">股权处置,土地经营权,停车泊位,供水污水,房产处置,保障房,林水经营</chartField>
|
||||
</dim1>
|
||||
<dim2>
|
||||
<chartField name="规模">24,19,8.5,7.5,5,4.15,2.7</chartField>
|
||||
</dim2>
|
||||
</chartData>
|
||||
<chartStyle>
|
||||
<chartBackground color="rgba(0, 0, 0, 0)"/>
|
||||
<chartBorder color="rgb(222, 224, 227)" width="0"/>
|
||||
<chartColorTheme>
|
||||
<color value="rgb(178, 34, 34)"/>
|
||||
<color value="rgb(205, 50, 50)"/>
|
||||
<color value="rgb(224, 80, 80)"/>
|
||||
<color value="rgb(238, 110, 110)"/>
|
||||
<color value="rgb(218, 165, 32)"/>
|
||||
<color value="rgb(238, 195, 55)"/>
|
||||
<color value="rgb(248, 222, 100)"/>
|
||||
</chartColorTheme>
|
||||
</chartStyle>
|
||||
</chart>
|
||||
</data>
|
||||
<note>
|
||||
<content/>
|
||||
</note>
|
||||
</slide>
|
||||
@@ -3,22 +3,13 @@
|
||||
|
||||
创建一个新的飞书幻灯片演示文稿,可选一步添加页面内容。
|
||||
|
||||
提交源必须是直接生成的单页 `<slide>` XML。禁止从完整 `<presentation>` XML 解析、拆分、重序列化出 slide 数组再提交。
|
||||
- 禁止:从完整 <presentation> XML 解析/拆分/重序列化生成提交 payload。
|
||||
- 推荐:提交源直接就是单页 <slide> XML;+create --slides 只接受已经人工/程序直接生成的 slide 数组,不接受由
|
||||
presentation 动态拆出来的数组。
|
||||
|
||||
本命令只从零创建演示文稿,没有导入本地 PPT 文件的参数。要把已有 PPTX 变成 Slides,用 `drive +import --file <x.pptx> --type slides`,再在导入结果上编辑,流程见 [lark-slides-pptx-template-workflows.md](lark-slides-pptx-template-workflows.md)。
|
||||
- 最稳:复杂 deck 默认空 deck + 单页 slide create,每次只提交一个 <slide>。
|
||||
|
||||
## 创建方式选择
|
||||
|
||||
| 场景 | 推荐方式 |
|
||||
|------|----------|
|
||||
| 简单 XML(1-3 页、结构简单、几乎无复杂中文和特殊字符) | `slides +create --slides '[...]'` 一步创建 |
|
||||
| 复杂 XML(多页、含中文、大段文本、复杂布局、嵌套引号、特殊字符较多) | **两步创建**:先 `slides +create` 创建空白 PPT,再用 [`xml_presentation.slide create`](lark-slides-xml-presentation-slide-create.md) 逐页添加 |
|
||||
| 已有 PPT 继续追加或插入页面 | 使用 [`xml_presentation.slide create`](lark-slides-xml-presentation-slide-create.md),必要时配合 `before_slide_id` |
|
||||
|
||||
> [!WARNING]
|
||||
> `--slides '[...]'` 的风险点主要在 shell 参数传递,而不是单纯页数。即使只有 1 页,只要 XML 足够复杂,也建议使用两步创建法。
|
||||
> [!IMPORTANT]
|
||||
> `slides +create --slides` 底层会逐页创建,不是原子操作。中途失败时先记录 `xml_presentation_id`,回读确认当前状态,再继续修复或追加。
|
||||
- 注意:复杂 XML 不适合直接塞命令行,中文、引号、特殊字符较多时,直接拼接 --slides 容易发生 shell 转义或截断。建议将每页 XML 保存为独立文件,使用 `jq --rawfile` 组装 JSON 数组,避免手动处理 XML 引号和换行。
|
||||
|
||||
## 命令
|
||||
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Black-box harness for the `chart_external_overlay` lint rule.
|
||||
|
||||
Runs xml_text_overlap_lint over every fixture named in a manifest and compares
|
||||
the observed `chart_external_overlay` issues against each page's expectation.
|
||||
|
||||
Usage:
|
||||
python3 chart_overlay_harness.py --plan <plan-dir> [--input-xml <file>] [--out <dir>]
|
||||
|
||||
--plan directory holding manifest.json and slides/ (the fixtures).
|
||||
--input-xml optional single presentation XML to lint instead of per-fixture
|
||||
files (used to lint a server readback deck); pages are matched to
|
||||
the manifest by slide order.
|
||||
--out output directory for results.json and report.md (default: --plan).
|
||||
|
||||
Exit code is 0 when false-negatives == 0 and false-positives == 0, else 1.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPTS_DIR = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(SCRIPTS_DIR))
|
||||
import xml_text_overlap_lint as lint # noqa: E402
|
||||
|
||||
CODE = "chart_external_overlay"
|
||||
|
||||
|
||||
def chart_issues(slide_result: dict) -> list[dict]:
|
||||
return [i for i in slide_result.get("issues", []) if i.get("code") == CODE]
|
||||
|
||||
|
||||
def other_error_codes(slide_result: dict) -> list[str]:
|
||||
return sorted(
|
||||
{
|
||||
i.get("code")
|
||||
for i in slide_result.get("issues", [])
|
||||
if i.get("level") == "error" and i.get("code") != CODE
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def slides_from_presentation(xml: str) -> list[str]:
|
||||
return re.findall(r"<slide\b[\s\S]*?</slide>", xml)
|
||||
|
||||
|
||||
def lint_page(slide_xml: str) -> dict:
|
||||
wrapped = (
|
||||
'<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">'
|
||||
+ slide_xml
|
||||
+ "</presentation>"
|
||||
)
|
||||
result = lint.lint_xml(wrapped)
|
||||
return result["slides"][0] if result.get("slides") else {"issues": []}
|
||||
|
||||
|
||||
def evaluate(manifest: dict, plan_dir: Path, readback_xml: str | None) -> dict:
|
||||
pages = manifest["pages"]
|
||||
readback_slides = slides_from_presentation(readback_xml) if readback_xml else None
|
||||
rows = []
|
||||
fn = fp = unrelated = 0
|
||||
for idx, page in enumerate(pages):
|
||||
if readback_slides is not None:
|
||||
if idx >= len(readback_slides):
|
||||
rows.append({**_row_base(page), "status": "MISSING_IN_READBACK",
|
||||
"observed_issue_count": None})
|
||||
fn += 1 if page["expected_issue_count"] else 0
|
||||
continue
|
||||
slide_result = lint_page(readback_slides[idx])
|
||||
else:
|
||||
slide_xml = (plan_dir / page["fixture"]).read_text(encoding="utf-8")
|
||||
slide_result = lint_page(slide_xml)
|
||||
|
||||
issues = chart_issues(slide_result)
|
||||
observed = len(issues)
|
||||
expected = page["expected_issue_count"]
|
||||
others = other_error_codes(slide_result)
|
||||
if others:
|
||||
unrelated += 1
|
||||
|
||||
# A page is "correct" when observed presence matches expectation.
|
||||
expected_present = expected > 0
|
||||
observed_present = observed > 0
|
||||
if expected_present and not observed_present:
|
||||
status = "FALSE_NEGATIVE"
|
||||
fn += 1
|
||||
elif not expected_present and observed_present:
|
||||
status = "FALSE_POSITIVE"
|
||||
fp += 1
|
||||
elif expected_present and observed != expected:
|
||||
status = "COUNT_MISMATCH" # right presence, wrong number of chart issues
|
||||
else:
|
||||
status = "OK"
|
||||
|
||||
rows.append({
|
||||
**_row_base(page),
|
||||
"observed_issue_count": observed,
|
||||
"observed_elements": [i.get("elements") for i in issues],
|
||||
"unrelated_error_codes": others,
|
||||
"status": status,
|
||||
})
|
||||
|
||||
return {
|
||||
"mode": "readback" if readback_xml else "fixture",
|
||||
"summary": {
|
||||
"page_count": len(pages),
|
||||
"false_negatives": fn,
|
||||
"false_positives": fp,
|
||||
"pages_with_unrelated_errors": unrelated,
|
||||
"pass": fn == 0 and fp == 0,
|
||||
},
|
||||
"pages": rows,
|
||||
}
|
||||
|
||||
|
||||
def _row_base(page: dict) -> dict:
|
||||
return {
|
||||
"case_id": page["case_id"],
|
||||
"slide_number": page["slide_number"],
|
||||
"chart_type": page.get("chart_type"),
|
||||
"rendered_occlusion": page.get("rendered_occlusion"),
|
||||
"rendered_occlusion_source": page.get("rendered_occlusion_source"),
|
||||
"expected_issue_count": page["expected_issue_count"],
|
||||
}
|
||||
|
||||
|
||||
def render_markdown(results: dict) -> str:
|
||||
s = results["summary"]
|
||||
lines = [
|
||||
f"# chart_external_overlay black-box results ({results['mode']} mode)",
|
||||
"",
|
||||
f"- pages: **{s['page_count']}**",
|
||||
f"- false negatives: **{s['false_negatives']}**",
|
||||
f"- false positives: **{s['false_positives']}**",
|
||||
f"- pages with unrelated errors: **{s['pages_with_unrelated_errors']}**",
|
||||
f"- suite pass: **{s['pass']}**",
|
||||
"",
|
||||
"| # | case | type | occ | occ_src | exp | obs | unrelated | status |",
|
||||
"|--:|------|------|:---:|:-------:|:---:|:---:|-----------|--------|",
|
||||
]
|
||||
for r in results["pages"]:
|
||||
lines.append(
|
||||
f"| {r['slide_number']} | {r['case_id']} | {r.get('chart_type','')} | "
|
||||
f"{r.get('rendered_occlusion')} | {r.get('rendered_occlusion_source','')} | "
|
||||
f"{r['expected_issue_count']} | {r.get('observed_issue_count')} | "
|
||||
f"{','.join(r.get('unrelated_error_codes') or []) or '-'} | {r['status']} |"
|
||||
)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--plan", required=True)
|
||||
ap.add_argument("--input-xml")
|
||||
ap.add_argument("--out")
|
||||
args = ap.parse_args()
|
||||
|
||||
plan_dir = Path(args.plan).resolve()
|
||||
manifest = json.loads((plan_dir / "manifest.json").read_text(encoding="utf-8"))
|
||||
readback_xml = Path(args.input_xml).read_text(encoding="utf-8") if args.input_xml else None
|
||||
|
||||
results = evaluate(manifest, plan_dir, readback_xml)
|
||||
|
||||
out_dir = Path(args.out).resolve() if args.out else plan_dir
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
suffix = "_readback" if readback_xml else ""
|
||||
(out_dir / f"results{suffix}.json").write_text(
|
||||
json.dumps(results, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
(out_dir / f"report{suffix}.md").write_text(render_markdown(results), encoding="utf-8")
|
||||
|
||||
s = results["summary"]
|
||||
print(f"[{results['mode']}] pages={s['page_count']} FN={s['false_negatives']} "
|
||||
f"FP={s['false_positives']} unrelated={s['pages_with_unrelated_errors']} "
|
||||
f"pass={s['pass']}")
|
||||
return 0 if s["pass"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,642 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Collect text width measurement data from slides presentation for character width estimation optimization."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import unicodedata
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
PRESENTATION_ID = "BUFBsLX2ZlzyMTdprLicd7rpneg"
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
CSV_OUTPUT = SCRIPT_DIR / "width_measurement_data.csv"
|
||||
MD_OUTPUT = SCRIPT_DIR / "width_measurement_report.md"
|
||||
|
||||
|
||||
def estimate_character_width(character: str, font_size: int | float) -> int | float:
|
||||
if character.isspace():
|
||||
return font_size * 0.33
|
||||
if unicodedata.east_asian_width(character) in {"F", "W"}:
|
||||
return font_size
|
||||
return font_size * 0.55
|
||||
|
||||
|
||||
def estimate_text_width(text: str, font_size: int | float) -> int | float:
|
||||
return sum(estimate_character_width(character, font_size) for character in text)
|
||||
|
||||
|
||||
def extract_attribute(tag_source: str, name: str) -> str | None:
|
||||
match = re.search(
|
||||
fr"(?:^|\s){re.escape(name)}\s*=\s*(?:\"([^\"]+)\"|'([^']+)')", tag_source
|
||||
)
|
||||
if not match:
|
||||
return None
|
||||
return match.group(1) if match.group(1) is not None else match.group(2)
|
||||
|
||||
|
||||
def extract_numeric_attribute(tag_source: str, name: str) -> int | float | None:
|
||||
raw = extract_attribute(tag_source, name)
|
||||
if raw is None:
|
||||
return None
|
||||
try:
|
||||
value = float(raw)
|
||||
except ValueError:
|
||||
return None
|
||||
return int(value) if value.is_integer() else value
|
||||
|
||||
|
||||
def extract_bool_attribute(tag_source: str, name: str) -> bool:
|
||||
value = extract_attribute(tag_source, name)
|
||||
return value in {"true", "1", "yes"}
|
||||
|
||||
|
||||
def strip_xml(value: str, preserve_line_breaks: bool = False) -> str:
|
||||
stripped = re.sub(r"<!\[CDATA\[([\s\S]*?)\]\]>", r"\1", value)
|
||||
if preserve_line_breaks:
|
||||
stripped = re.sub(r"<br\b[^>]*>", "\n", stripped)
|
||||
stripped = re.sub(r"<[^>]+>", " ", stripped)
|
||||
stripped = stripped.replace(" ", " ")
|
||||
stripped = stripped.replace("&", "&")
|
||||
stripped = stripped.replace("<", "<")
|
||||
stripped = stripped.replace(">", ">")
|
||||
stripped = stripped.replace(""", '"')
|
||||
stripped = stripped.replace("'", "'")
|
||||
if preserve_line_breaks:
|
||||
return "\n".join(re.sub(r"\s+", " ", line).strip() for line in stripped.split("\n"))
|
||||
return re.sub(r"\s+", " ", stripped).strip()
|
||||
|
||||
|
||||
def strip_xml_paragraphs(value: str) -> str:
|
||||
paragraphs = re.findall(r"<p\b[^>]*>([\s\S]*?)</p\s*>", value)
|
||||
if paragraphs:
|
||||
return "\n".join(strip_xml(paragraph, preserve_line_breaks=True) for paragraph in paragraphs)
|
||||
return strip_xml(value, preserve_line_breaks=True)
|
||||
|
||||
|
||||
def normalize_text_no_whitespace(text: str) -> str:
|
||||
return re.sub(r"\s+", "", text)
|
||||
|
||||
|
||||
def classify_text_type(text: str) -> str:
|
||||
has_chinese = bool(re.search(r"[\u4e00-\u9fff]", text))
|
||||
has_english = bool(re.search(r"[A-Za-z]", text))
|
||||
has_digit = bool(re.search(r"[0-9]", text))
|
||||
has_punctuation = bool(re.search(r"[^\u4e00-\u9fffA-Za-z0-9\s]", text))
|
||||
|
||||
categories = []
|
||||
if has_chinese:
|
||||
categories.append("中文")
|
||||
if has_english:
|
||||
categories.append("英文")
|
||||
if has_digit:
|
||||
categories.append("数字")
|
||||
if has_punctuation and not categories:
|
||||
categories.append("标点")
|
||||
if not categories:
|
||||
return "其他"
|
||||
return "+".join(categories)
|
||||
|
||||
|
||||
def extract_text_spans(content: str, default_font_size: int | float, default_font_family: str | None,
|
||||
default_bold: bool, default_italic: bool) -> list[dict[str, Any]]:
|
||||
spans = []
|
||||
|
||||
p_matches = list(re.finditer(r"<p\b([^>]*)>([\s\S]*?)</p\s*>", content))
|
||||
if not p_matches:
|
||||
p_matches = [re.match(r"()([\s\S]*)", content)]
|
||||
|
||||
for p_match in p_matches:
|
||||
p_attrs, p_body = p_match.groups()
|
||||
p_text = strip_xml(p_body, preserve_line_breaks=True)
|
||||
if not p_text:
|
||||
continue
|
||||
|
||||
span_matches = list(re.finditer(r"<span\b([^>]*)>([\s\S]*?)</span\s*>", p_body))
|
||||
if not span_matches:
|
||||
span_text = strip_xml(p_body, preserve_line_breaks=True)
|
||||
if span_text:
|
||||
font_size = default_font_size
|
||||
if extract_numeric_attribute(p_attrs, "fontSize") is not None:
|
||||
font_size = extract_numeric_attribute(p_attrs, "fontSize")
|
||||
font_family = extract_attribute(p_attrs, "fontFamily") or default_font_family
|
||||
latin_font = extract_attribute(p_attrs, "latinFont")
|
||||
ea_font = extract_attribute(p_attrs, "eaFont")
|
||||
cs_font = extract_attribute(p_attrs, "csFont")
|
||||
bold = extract_bool_attribute(p_attrs, "bold") or default_bold
|
||||
italic = extract_bool_attribute(p_attrs, "italic") or default_italic
|
||||
letter_spacing = extract_numeric_attribute(p_attrs, "letterSpacing")
|
||||
|
||||
spans.append({
|
||||
"text": span_text,
|
||||
"font_size": font_size,
|
||||
"font_family": font_family,
|
||||
"latin_font": latin_font,
|
||||
"ea_font": ea_font,
|
||||
"cs_font": cs_font,
|
||||
"bold": bold,
|
||||
"italic": italic,
|
||||
"letter_spacing": letter_spacing,
|
||||
})
|
||||
else:
|
||||
last_end = 0
|
||||
for span_match in span_matches:
|
||||
span_attrs, span_body = span_match.groups()
|
||||
span_text = strip_xml(span_body, preserve_line_breaks=True)
|
||||
if not span_text:
|
||||
last_end = span_match.end()
|
||||
continue
|
||||
|
||||
font_size = default_font_size
|
||||
if extract_numeric_attribute(p_attrs, "fontSize") is not None:
|
||||
font_size = extract_numeric_attribute(p_attrs, "fontSize")
|
||||
if extract_numeric_attribute(span_attrs, "fontSize") is not None:
|
||||
font_size = extract_numeric_attribute(span_attrs, "fontSize")
|
||||
|
||||
font_family = extract_attribute(span_attrs, "fontFamily") or \
|
||||
extract_attribute(p_attrs, "fontFamily") or default_font_family
|
||||
latin_font = extract_attribute(span_attrs, "latinFont") or extract_attribute(p_attrs, "latinFont")
|
||||
ea_font = extract_attribute(span_attrs, "eaFont") or extract_attribute(p_attrs, "eaFont")
|
||||
cs_font = extract_attribute(span_attrs, "csFont") or extract_attribute(p_attrs, "csFont")
|
||||
bold = extract_bool_attribute(span_attrs, "bold") or \
|
||||
extract_bool_attribute(p_attrs, "bold") or default_bold
|
||||
italic = extract_bool_attribute(span_attrs, "italic") or \
|
||||
extract_bool_attribute(p_attrs, "italic") or default_italic
|
||||
letter_spacing = extract_numeric_attribute(span_attrs, "letterSpacing") or \
|
||||
extract_numeric_attribute(p_attrs, "letterSpacing")
|
||||
|
||||
spans.append({
|
||||
"text": span_text,
|
||||
"font_size": font_size,
|
||||
"font_family": font_family,
|
||||
"latin_font": latin_font,
|
||||
"ea_font": ea_font,
|
||||
"cs_font": cs_font,
|
||||
"bold": bold,
|
||||
"italic": italic,
|
||||
"letter_spacing": letter_spacing,
|
||||
})
|
||||
last_end = span_match.end()
|
||||
|
||||
tail_text = strip_xml(p_body[last_end:], preserve_line_breaks=True)
|
||||
if tail_text:
|
||||
font_size = default_font_size
|
||||
if extract_numeric_attribute(p_attrs, "fontSize") is not None:
|
||||
font_size = extract_numeric_attribute(p_attrs, "fontSize")
|
||||
font_family = extract_attribute(p_attrs, "fontFamily") or default_font_family
|
||||
latin_font = extract_attribute(p_attrs, "latinFont")
|
||||
ea_font = extract_attribute(p_attrs, "eaFont")
|
||||
cs_font = extract_attribute(p_attrs, "csFont")
|
||||
bold = extract_bool_attribute(p_attrs, "bold") or default_bold
|
||||
italic = extract_bool_attribute(p_attrs, "italic") or default_italic
|
||||
letter_spacing = extract_numeric_attribute(p_attrs, "letterSpacing")
|
||||
spans.append({
|
||||
"text": tail_text,
|
||||
"font_size": font_size,
|
||||
"font_family": font_family,
|
||||
"latin_font": latin_font,
|
||||
"ea_font": ea_font,
|
||||
"cs_font": cs_font,
|
||||
"bold": bold,
|
||||
"italic": italic,
|
||||
"letter_spacing": letter_spacing,
|
||||
})
|
||||
return spans
|
||||
|
||||
|
||||
def extract_text_elements(slide_xml: str) -> list[dict[str, Any]]:
|
||||
elements = []
|
||||
|
||||
for match in re.finditer(r"<(shape)\b([^>]*)>", slide_xml):
|
||||
kind, attrs = match.group(1), match.group(2)
|
||||
is_self_closing = attrs.rstrip().endswith("/")
|
||||
content = ""
|
||||
if kind in {"shape"} and not is_self_closing:
|
||||
close_index = slide_xml.find(f"</{kind}>", match.end())
|
||||
if close_index != -1:
|
||||
content = slide_xml[match.end() : close_index]
|
||||
|
||||
element_type = extract_attribute(attrs, "type")
|
||||
if element_type != "text":
|
||||
continue
|
||||
|
||||
element_id = extract_attribute(attrs, "id") or f"{kind}-{len(elements) + 1}"
|
||||
x = extract_numeric_attribute(attrs, "topLeftX")
|
||||
y = extract_numeric_attribute(attrs, "topLeftY")
|
||||
width = extract_numeric_attribute(attrs, "width")
|
||||
height = extract_numeric_attribute(attrs, "height")
|
||||
if any(v is None for v in [x, y, width, height]):
|
||||
continue
|
||||
|
||||
content_attrs_match = re.search(r"<content\b([^>]*)>", content)
|
||||
content_attrs = content_attrs_match.group(1) if content_attrs_match else ""
|
||||
|
||||
font_size = extract_numeric_attribute(content_attrs, "fontSize")
|
||||
if font_size is None:
|
||||
font_size = extract_numeric_attribute(attrs, "fontSize")
|
||||
if font_size is None:
|
||||
font_size = 16
|
||||
|
||||
font_family = extract_attribute(content_attrs, "fontFamily") or extract_attribute(attrs, "fontFamily")
|
||||
latin_font = extract_attribute(content_attrs, "latinFont")
|
||||
ea_font = extract_attribute(content_attrs, "eaFont")
|
||||
cs_font = extract_attribute(content_attrs, "csFont")
|
||||
bold = extract_bool_attribute(content_attrs, "bold") or extract_bool_attribute(attrs, "bold")
|
||||
italic = extract_bool_attribute(content_attrs, "italic") or extract_bool_attribute(attrs, "italic")
|
||||
letter_spacing = extract_numeric_attribute(content_attrs, "letterSpacing")
|
||||
|
||||
wrap = extract_attribute(content_attrs, "wrap")
|
||||
if wrap is None:
|
||||
wrap = "true"
|
||||
auto_fit = extract_attribute(content_attrs, "autoFit")
|
||||
if auto_fit is None:
|
||||
auto_fit = "none"
|
||||
text_align = extract_attribute(content_attrs, "textAlign")
|
||||
vertical_align = extract_attribute(content_attrs, "verticalAlign") or "middle"
|
||||
|
||||
padding_left = extract_numeric_attribute(content_attrs, "paddingLeft") or 0
|
||||
padding_right = extract_numeric_attribute(content_attrs, "paddingRight") or 0
|
||||
padding_top = extract_numeric_attribute(content_attrs, "paddingTop") or 0
|
||||
padding_bottom = extract_numeric_attribute(content_attrs, "paddingBottom") or 0
|
||||
|
||||
full_text = strip_xml_paragraphs(content)
|
||||
clean_text = normalize_text_no_whitespace(full_text)
|
||||
if not clean_text:
|
||||
continue
|
||||
|
||||
content_inner = ""
|
||||
if content_attrs_match:
|
||||
content_start = content_attrs_match.end()
|
||||
content_end = content.find("</content>", content_start)
|
||||
if content_end != -1:
|
||||
content_inner = content[content_start:content_end]
|
||||
|
||||
spans = extract_text_spans(content_inner, font_size, font_family, bold, italic)
|
||||
|
||||
hard_lines = full_text.split("\n")
|
||||
max_line_estimated_width = 0
|
||||
max_line_text = ""
|
||||
for line in hard_lines:
|
||||
line_clean = normalize_text_no_whitespace(line)
|
||||
if not line_clean:
|
||||
continue
|
||||
line_width = 0
|
||||
for span in spans:
|
||||
if span["text"] in line or line in span["text"]:
|
||||
line_width += estimate_text_width(normalize_text_no_whitespace(span["text"]), span["font_size"])
|
||||
if line_width == 0:
|
||||
line_width = estimate_text_width(line_clean, font_size)
|
||||
if line_width > max_line_estimated_width:
|
||||
max_line_estimated_width = line_width
|
||||
max_line_text = line_clean
|
||||
|
||||
available_width = width - padding_left - padding_right
|
||||
|
||||
elements.append({
|
||||
"id": element_id,
|
||||
"x": x,
|
||||
"y": y,
|
||||
"width": width,
|
||||
"height": height,
|
||||
"padding_left": padding_left,
|
||||
"padding_right": padding_right,
|
||||
"padding_top": padding_top,
|
||||
"padding_bottom": padding_bottom,
|
||||
"available_width": available_width,
|
||||
"font_size": font_size,
|
||||
"font_family": font_family,
|
||||
"latin_font": latin_font,
|
||||
"ea_font": ea_font,
|
||||
"cs_font": cs_font,
|
||||
"bold": bold,
|
||||
"italic": italic,
|
||||
"letter_spacing": letter_spacing,
|
||||
"wrap": wrap,
|
||||
"auto_fit": auto_fit,
|
||||
"text_align": text_align,
|
||||
"vertical_align": vertical_align,
|
||||
"text_raw": full_text,
|
||||
"text_clean": clean_text,
|
||||
"max_line_text": max_line_text,
|
||||
"estimated_width": max_line_estimated_width,
|
||||
"width_ratio": max_line_estimated_width / available_width if available_width > 0 else None,
|
||||
"spans": spans,
|
||||
})
|
||||
|
||||
return elements
|
||||
|
||||
|
||||
def run_lark_cli(args: list[str], env: dict[str, str]) -> subprocess.CompletedProcess:
|
||||
cmd = ["lark-cli"] + args
|
||||
return subprocess.run(
|
||||
cmd,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(SCRIPT_DIR),
|
||||
timeout=120,
|
||||
)
|
||||
|
||||
|
||||
def get_presentation_xml(env: dict[str, str]) -> str:
|
||||
result = run_lark_cli(
|
||||
["slides", "+xml-get", "--presentation", PRESENTATION_ID, "--raw"],
|
||||
env,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"Error getting presentation XML: {result.stderr}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return result.stdout
|
||||
|
||||
|
||||
def get_slide_ids(xml: str) -> list[tuple[int, str]]:
|
||||
slides = []
|
||||
for index, match in enumerate(re.finditer(r"<slide\b([^>]*)>", xml)):
|
||||
attrs = match.group(1)
|
||||
slide_id = extract_attribute(attrs, "id")
|
||||
if slide_id:
|
||||
slides.append((index + 1, slide_id))
|
||||
return slides
|
||||
|
||||
|
||||
def get_slide_xml(slide_id: str, env: dict[str, str]) -> str:
|
||||
result = run_lark_cli(
|
||||
["slides", "+xml-get", "--presentation", PRESENTATION_ID, "--slide-id", slide_id, "--raw"],
|
||||
env,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(f"Error getting slide {slide_id} XML: {result.stderr}", file=sys.stderr)
|
||||
return ""
|
||||
return result.stdout
|
||||
|
||||
|
||||
def take_screenshots(slide_ids: list[str], output_dir: Path, env: dict[str, str]) -> None:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
batch_size = 10
|
||||
for i in range(0, len(slide_ids), batch_size):
|
||||
batch = slide_ids[i:i + batch_size]
|
||||
args = [
|
||||
"slides", "+screenshot",
|
||||
"--presentation", PRESENTATION_ID,
|
||||
"--output-dir", str(output_dir.relative_to(SCRIPT_DIR)),
|
||||
]
|
||||
for sid in batch:
|
||||
args.extend(["--slide-id", sid])
|
||||
result = run_lark_cli(args, env)
|
||||
if result.returncode != 0:
|
||||
print(f"Warning: screenshot failed for batch {i//batch_size + 1}: {result.stderr}", file=sys.stderr)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
env = os.environ.copy()
|
||||
env["LARKSUITE_CLI_NO_UPDATE_NOTIFIER"] = "1"
|
||||
env["LARKSUITE_CLI_NO_SKILLS_NOTIFIER"] = "1"
|
||||
|
||||
print(f"Fetching presentation {PRESENTATION_ID}...")
|
||||
full_xml = get_presentation_xml(env)
|
||||
slides_info = get_slide_ids(full_xml)
|
||||
print(f"Found {len(slides_info)} slides")
|
||||
|
||||
screenshot_dir = SCRIPT_DIR / ".lark-slides" / "screenshots"
|
||||
print(f"Taking screenshots (this may take a while)...")
|
||||
take_screenshots([sid for _, sid in slides_info], screenshot_dir, env)
|
||||
|
||||
all_samples = []
|
||||
|
||||
for slide_num, slide_id in slides_info:
|
||||
print(f"Processing slide {slide_num} ({slide_id})...")
|
||||
slide_xml = get_slide_xml(slide_id, env)
|
||||
if not slide_xml:
|
||||
continue
|
||||
|
||||
elements = extract_text_elements(slide_xml)
|
||||
for elem in elements:
|
||||
text_type = classify_text_type(elem["text_clean"])
|
||||
is_single_line_hard = "\n" not in elem["text_raw"]
|
||||
wrap_enabled = elem["wrap"] not in {"false", "0"}
|
||||
has_auto_fit = elem["auto_fit"] in {"normal-auto-fit", "shape-auto-fit"}
|
||||
|
||||
sample = {
|
||||
"slide_number": slide_num,
|
||||
"slide_id": slide_id,
|
||||
"element_id": elem["id"],
|
||||
"x": round(elem["x"], 2),
|
||||
"y": round(elem["y"], 2),
|
||||
"shape_width": round(elem["width"], 2),
|
||||
"shape_height": round(elem["height"], 2),
|
||||
"padding_left": elem["padding_left"],
|
||||
"padding_right": elem["padding_right"],
|
||||
"available_width": round(elem["available_width"], 2),
|
||||
"font_size": elem["font_size"],
|
||||
"font_family": elem["font_family"] or "",
|
||||
"bold": str(elem["bold"]).lower(),
|
||||
"italic": str(elem["italic"]).lower(),
|
||||
"wrap": elem["wrap"] or "",
|
||||
"auto_fit": elem["auto_fit"] or "",
|
||||
"text_align": elem["text_align"] or "",
|
||||
"text_clean": elem["text_clean"],
|
||||
"text_length": len(elem["text_clean"]),
|
||||
"text_type": text_type,
|
||||
"is_single_line_hard": str(is_single_line_hard).lower(),
|
||||
"estimated_width": round(elem["estimated_width"], 2),
|
||||
"width_ratio": round(elem["width_ratio"], 4) if elem["width_ratio"] is not None else "",
|
||||
"likely_wraps_actual": "",
|
||||
"notes": "",
|
||||
}
|
||||
if elem["width_ratio"] is not None and not has_auto_fit:
|
||||
ratio = elem["width_ratio"]
|
||||
if not wrap_enabled:
|
||||
sample["likely_wraps_actual"] = "no_wrap"
|
||||
sample["notes"] = "wrap=false,不自动换行"
|
||||
elif ratio < 0.7:
|
||||
sample["likely_wraps_actual"] = "no_slack"
|
||||
sample["notes"] = f"估算宽度 < 70% 可用宽度({ratio:.2f}),明显有留白"
|
||||
elif ratio < 0.85:
|
||||
sample["likely_wraps_actual"] = "no"
|
||||
sample["notes"] = f"估算宽度 {ratio:.2f}x 可用宽度,大概率单行"
|
||||
elif ratio <= 1.0:
|
||||
sample["likely_wraps_actual"] = "tight_single"
|
||||
sample["notes"] = f"估算宽度 {ratio:.2f}x 可用宽度,接近填满,需确认是否单行"
|
||||
elif ratio <= 1.2:
|
||||
sample["likely_wraps_actual"] = "borderline"
|
||||
sample["notes"] = f"估算宽度 {ratio:.2f}x 可用宽度,边界情况,需截图确认是否换行"
|
||||
elif ratio <= 1.5:
|
||||
sample["likely_wraps_actual"] = "likely_wrap"
|
||||
sample["notes"] = f"估算宽度 {ratio:.2f}x 可用宽度,大概率换2行"
|
||||
else:
|
||||
sample["likely_wraps_actual"] = "yes"
|
||||
sample["notes"] = f"估算宽度 {ratio:.2f}x 可用宽度,肯定换行(多行)"
|
||||
elif has_auto_fit:
|
||||
sample["likely_wraps_actual"] = "autofit"
|
||||
sample["notes"] = "autoFit开启,字体会自动缩放"
|
||||
else:
|
||||
sample["likely_wraps_actual"] = "unknown"
|
||||
sample["notes"] = "无法判断"
|
||||
|
||||
all_samples.append(sample)
|
||||
|
||||
print(f"\nCollected {len(all_samples)} text shape samples")
|
||||
|
||||
csv_fields = [
|
||||
"slide_number", "slide_id", "element_id", "x", "y",
|
||||
"shape_width", "shape_height", "padding_left", "padding_right", "available_width",
|
||||
"font_size", "font_family", "bold", "italic", "wrap", "auto_fit", "text_align",
|
||||
"text_clean", "text_length", "text_type", "is_single_line_hard",
|
||||
"estimated_width", "width_ratio", "likely_wraps_actual", "notes",
|
||||
]
|
||||
|
||||
with open(CSV_OUTPUT, "w", encoding="utf-8", newline="") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=csv_fields)
|
||||
writer.writeheader()
|
||||
writer.writerows(all_samples)
|
||||
print(f"CSV saved to: {CSV_OUTPUT}")
|
||||
|
||||
key_categories = {"tight_single", "borderline", "likely_wrap", "yes"}
|
||||
tight_samples = [s for s in all_samples if s["likely_wraps_actual"] == "tight_single"]
|
||||
borderline_samples = [s for s in all_samples if s["likely_wraps_actual"] == "borderline"]
|
||||
likely_wrap_samples = [s for s in all_samples if s["likely_wraps_actual"] == "likely_wrap"]
|
||||
yes_wrap_samples = [s for s in all_samples if s["likely_wraps_actual"] == "yes"]
|
||||
|
||||
type_stats = defaultdict(list)
|
||||
for s in all_samples:
|
||||
if s["width_ratio"] != "":
|
||||
type_stats[s["text_type"]].append(s)
|
||||
|
||||
md_lines = [
|
||||
"# 字符宽度测量数据报告",
|
||||
"",
|
||||
f"**Presentation**: {PRESENTATION_ID}",
|
||||
f"**总样本数**: {len(all_samples)}",
|
||||
f"**关键测量样本(比值≥0.85)**: {len(tight_samples) + len(borderline_samples) + len(likely_wrap_samples) + len(yes_wrap_samples)}",
|
||||
f" - 接近填满(0.85-1.0): {len(tight_samples)}",
|
||||
f" - 边界情况(1.0-1.2): {len(borderline_samples)}",
|
||||
f" - 大概率换行(1.2-1.5): {len(likely_wrap_samples)}",
|
||||
f" - 肯定换行(>1.5): {len(yes_wrap_samples)}",
|
||||
"",
|
||||
"## 按文本类型统计(所有样本)",
|
||||
"",
|
||||
"| 文本类型 | 样本数 | 平均估算/可用比 | 最小比值 | 最大比值 |",
|
||||
"|----------|--------|------------------|----------|----------|",
|
||||
]
|
||||
|
||||
for text_type in sorted(type_stats.keys()):
|
||||
samples = type_stats[text_type]
|
||||
ratios = [float(s["width_ratio"]) for s in samples if s["width_ratio"] != ""]
|
||||
if not ratios:
|
||||
continue
|
||||
avg_ratio = sum(ratios) / len(ratios)
|
||||
min_ratio = min(ratios)
|
||||
max_ratio = max(ratios)
|
||||
md_lines.append(
|
||||
f"| {text_type} | {len(samples)} | {avg_ratio:.4f} | {min_ratio:.4f} | {max_ratio:.4f} |"
|
||||
)
|
||||
|
||||
def add_sample_table(title: str, samples: list[dict], max_rows: int = 50):
|
||||
md_lines.extend([
|
||||
"",
|
||||
f"## {title}",
|
||||
"",
|
||||
"| 页码 | 元素ID | 字体 | 字号 | Bold | 文本类型 | 硬换行 | 文本 | 估算宽度 | 可用宽度 | 比值 |",
|
||||
"|------|--------|------|------|------|----------|--------|------|----------|----------|------|",
|
||||
])
|
||||
for idx, s in enumerate(samples[:max_rows]):
|
||||
text_preview = s["text_clean"][:50] + ("..." if len(s["text_clean"]) > 50 else "")
|
||||
md_lines.append(
|
||||
f"| {s['slide_number']} | {s['element_id']} | {s['font_family']} | {s['font_size']} | "
|
||||
f"{s['bold']} | {s['text_type']} | {s['is_single_line_hard']} | {text_preview} | {s['estimated_width']} | "
|
||||
f"{s['available_width']} | {s['width_ratio']} |"
|
||||
)
|
||||
if len(samples) > max_rows:
|
||||
md_lines.append(f"| ... | ... | ... | ... | ... | ... | ... | (共 {len(samples)} 个样本) | ... | ... | ... |")
|
||||
|
||||
add_sample_table("接近填满样本(估算比值 0.85-1.0,最适合校准单行宽度)", tight_samples)
|
||||
add_sample_table("边界换行样本(估算比值 1.0-1.2,需截图确认是否换行)", borderline_samples)
|
||||
add_sample_table("大概率换行样本(估算比值 1.2-1.5)", likely_wrap_samples)
|
||||
add_sample_table("肯定换行样本(估算比值 > 1.5)", yes_wrap_samples, max_rows=20)
|
||||
|
||||
md_lines.extend([
|
||||
"",
|
||||
"## 字体统计",
|
||||
"",
|
||||
"| 字体 | 样本数 |",
|
||||
"|------|--------|",
|
||||
])
|
||||
|
||||
font_stats = defaultdict(int)
|
||||
for s in all_samples:
|
||||
font = s["font_family"] or "(default)"
|
||||
font_stats[font] += 1
|
||||
for font, count in sorted(font_stats.items(), key=lambda x: -x[1]):
|
||||
md_lines.append(f"| {font} | {count} |")
|
||||
|
||||
md_lines.extend([
|
||||
"",
|
||||
"## 字号+粗体统计",
|
||||
"",
|
||||
"| 字号 | Bold | 样本数 | 平均比值 |",
|
||||
"|------|------|--------|----------|",
|
||||
])
|
||||
|
||||
size_bold_stats = defaultdict(list)
|
||||
for s in all_samples:
|
||||
if s["width_ratio"] != "":
|
||||
key = (s["font_size"], s["bold"])
|
||||
size_bold_stats[key].append(float(s["width_ratio"]))
|
||||
for (font_size, bold), ratios in sorted(size_bold_stats.items()):
|
||||
avg = sum(ratios) / len(ratios)
|
||||
md_lines.append(f"| {font_size} | {bold} | {len(ratios)} | {avg:.4f} |")
|
||||
|
||||
md_lines.extend([
|
||||
"",
|
||||
"## 说明",
|
||||
"",
|
||||
"- **估算宽度**: 使用当前 `estimate_character_width` 函数计算(中文=1em,西文=0.55em,空格=0.33em)",
|
||||
"- **可用宽度**: shape.width - paddingLeft - paddingRight",
|
||||
"- **width_ratio**: 估算宽度 / 可用宽度(针对最长硬换行段落计算)",
|
||||
"- **硬换行**: 文本中是否包含显式 \\n 分段",
|
||||
f"- 截图保存在: {screenshot_dir}",
|
||||
"",
|
||||
"### 比值解读建议",
|
||||
"- ratio < 0.7: 明显留白,估算宽度可能偏宽,或文本确实很短",
|
||||
"- 0.7-0.85: 大概率单行,有少量留白",
|
||||
"- 0.85-1.0: 接近填满,是校准西文/中文字符宽度系数的最佳样本",
|
||||
"- 1.0-1.2: 边界情况,需要看截图确认:是刚好填满单行还是换行了",
|
||||
"- 1.2-1.5: 大概率换2行",
|
||||
"- >1.5: 肯定换行(多行文本)",
|
||||
"",
|
||||
"### 校准建议",
|
||||
"1. 先看 ratio 0.85-1.0 的样本:如果截图中这些文本**确实单行且接近填满**,说明当前估算大致准确;如果有较多留白,说明估算偏宽,需要减小西文字符系数",
|
||||
"2. 再看 ratio 1.0-1.2 的样本:结合截图判断实际是单行还是换行,反推合理系数",
|
||||
"3. 重点关注纯英文、纯数字、纯中文、中英混合这几类分别统计",
|
||||
"4. Bold 字体通常比常规字体稍宽,Italic 稍窄,需要分别考虑",
|
||||
"",
|
||||
"请人工核对截图确认边界样本的实际换行情况,用于校准字符宽度系数。",
|
||||
])
|
||||
|
||||
with open(MD_OUTPUT, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(md_lines))
|
||||
print(f"Markdown report saved to: {MD_OUTPUT}")
|
||||
|
||||
print("\n=== Summary ===")
|
||||
print(f"Total samples: {len(all_samples)}")
|
||||
print(f" Tight single (0.85-1.0): {len(tight_samples)}")
|
||||
print(f" Borderline (1.0-1.2): {len(borderline_samples)}")
|
||||
print(f" Likely wrap (1.2-1.5): {len(likely_wrap_samples)}")
|
||||
print(f" Definite wrap (>1.5): {len(yes_wrap_samples)}")
|
||||
print()
|
||||
for text_type in sorted(type_stats.keys()):
|
||||
samples = type_stats[text_type]
|
||||
ratios = [float(s["width_ratio"]) for s in samples if s["width_ratio"] != ""]
|
||||
if ratios:
|
||||
avg_ratio = sum(ratios) / len(ratios)
|
||||
print(f" {text_type}: {len(samples)} samples, avg ratio = {avg_ratio:.4f}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,265 +0,0 @@
|
||||
#!/usr/bin/env -S npx tsx
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// CLI: measure single-line text width via CanvasKit, reusing ee/slide's core
|
||||
// measure function so widths match the on-line renderer.
|
||||
//
|
||||
// Input is a Slide XML string (or an explicit --text). XML paragraph/style
|
||||
// extraction mirrors xml_text_overlap_lint.py (extract_text_paragraphs /
|
||||
// extract_max_span_font_size / style attribute names).
|
||||
//
|
||||
// Usage:
|
||||
// tsx cli.ts --font "Noto Sans SC=/abs/NotoSansSC.ttf" [--font ...] --file slide.xml
|
||||
// tsx cli.ts --font "Arial=/abs/Arial.ttf" --text "Hello" --font-size 16 [--bold] [--italic] [--letter-spacing 0]
|
||||
// cat slide.xml | tsx cli.ts --font "Arial=/abs/Arial.ttf"
|
||||
//
|
||||
// Output: JSON to stdout. Progress/warnings to stderr.
|
||||
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
import { StandaloneMeasureRuntime, type FontSpec } from './runtime.ts';
|
||||
import { measureSingleLineText, type SingleLineStyle } from './measure.ts';
|
||||
|
||||
interface CliArgs {
|
||||
fonts: FontSpec[];
|
||||
file?: string;
|
||||
xml?: string;
|
||||
text?: string;
|
||||
fontFamily: string;
|
||||
fontSize: string;
|
||||
letterSpacing: string;
|
||||
bold: boolean;
|
||||
italic: boolean;
|
||||
}
|
||||
|
||||
interface Segment {
|
||||
shapeId: string | null;
|
||||
text: string;
|
||||
style: SingleLineStyle;
|
||||
}
|
||||
|
||||
function parseArgs(argv: string[]): CliArgs {
|
||||
const args: CliArgs = {
|
||||
fonts: [],
|
||||
fontFamily: '',
|
||||
fontSize: '16px',
|
||||
letterSpacing: '0px',
|
||||
bold: false,
|
||||
italic: false,
|
||||
};
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
const next = () => {
|
||||
const value = argv[++i];
|
||||
if (value === undefined) {
|
||||
throw new Error(`Missing value for ${arg}`);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
switch (arg) {
|
||||
case '--font': {
|
||||
// "Family Name=/abs/path.ttf" — split on the FIRST '=' so family names
|
||||
// may not contain '=', which is fine for font-family strings.
|
||||
const spec = next();
|
||||
const eq = spec.indexOf('=');
|
||||
if (eq < 0) {
|
||||
throw new Error(`--font expects "family=path", got: ${spec}`);
|
||||
}
|
||||
args.fonts.push({ family: spec.slice(0, eq).trim(), path: spec.slice(eq + 1).trim() });
|
||||
break;
|
||||
}
|
||||
case '--file':
|
||||
args.file = next();
|
||||
break;
|
||||
case '--xml':
|
||||
args.xml = next();
|
||||
break;
|
||||
case '--text':
|
||||
args.text = next();
|
||||
break;
|
||||
case '--font-family':
|
||||
args.fontFamily = next();
|
||||
break;
|
||||
case '--font-size':
|
||||
args.fontSize = next();
|
||||
break;
|
||||
case '--letter-spacing':
|
||||
args.letterSpacing = next();
|
||||
break;
|
||||
case '--bold':
|
||||
args.bold = true;
|
||||
break;
|
||||
case '--italic':
|
||||
args.italic = true;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
async function readStdin(): Promise<string> {
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of process.stdin) {
|
||||
chunks.push(chunk as Buffer);
|
||||
}
|
||||
return Buffer.concat(chunks).toString('utf8');
|
||||
}
|
||||
|
||||
// --- XML extraction (mirrors xml_text_overlap_lint.py) ---
|
||||
|
||||
function stripXml(value: string, preserveLineBreaks: boolean): string {
|
||||
let stripped = value.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '');
|
||||
stripped = stripped
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, "'");
|
||||
if (preserveLineBreaks) {
|
||||
return stripped
|
||||
.split('\n')
|
||||
.map(line => line.replace(/\s+/g, ' ').trim())
|
||||
.join('\n');
|
||||
}
|
||||
return stripped.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function extractAttribute(attrs: string, name: string): string | null {
|
||||
const match = attrs.match(new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`, 'i'));
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function extractNumericAttribute(attrs: string, name: string): number | null {
|
||||
const raw = extractAttribute(attrs, name);
|
||||
if (raw === null) {
|
||||
return null;
|
||||
}
|
||||
const value = parseFloat(raw);
|
||||
return Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function extractBoolAttribute(attrs: string, name: string): boolean {
|
||||
return extractAttribute(attrs, name) === 'true';
|
||||
}
|
||||
|
||||
// Largest span fontSize wins, falling back to the content default (mirrors
|
||||
// extract_max_span_font_size).
|
||||
function extractMaxSpanFontSize(body: string, defaultFontSize: number): number {
|
||||
const sizes = [defaultFontSize];
|
||||
for (const [, spanAttrs] of body.matchAll(/<span\b([^>]*)>/gi)) {
|
||||
const size = extractNumericAttribute(spanAttrs, 'fontSize');
|
||||
if (size !== null) {
|
||||
sizes.push(size);
|
||||
}
|
||||
}
|
||||
return Math.max(...sizes);
|
||||
}
|
||||
|
||||
function detectAnySpanBool(body: string, name: string): boolean {
|
||||
for (const [, spanAttrs] of body.matchAll(/<span\b([^>]*)>/gi)) {
|
||||
if (extractBoolAttribute(spanAttrs, name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract one segment per <p> inside each <shape ...><content ...>. Each hard
|
||||
// paragraph is measured independently (single-line scope).
|
||||
function extractSegments(xml: string): Segment[] {
|
||||
const segments: Segment[] = [];
|
||||
for (const [, shapeAttrs, shapeBody] of xml.matchAll(/<shape\b([^>]*)>([\s\S]*?)<\/shape\s*>/gi)) {
|
||||
const shapeId = extractAttribute(shapeAttrs, 'id');
|
||||
const contentMatch = shapeBody.match(/<content\b([^>]*)>([\s\S]*?)<\/content\s*>/i);
|
||||
if (!contentMatch) {
|
||||
continue;
|
||||
}
|
||||
const [, contentAttrs, contentBody] = contentMatch;
|
||||
const contentFontSize = extractNumericAttribute(contentAttrs, 'fontSize') ?? 16;
|
||||
const contentFontFamily = extractAttribute(contentAttrs, 'fontFamily') ?? '';
|
||||
const contentLetterSpacing = extractNumericAttribute(contentAttrs, 'letterSpacing');
|
||||
const contentBold = extractBoolAttribute(contentAttrs, 'bold');
|
||||
const contentItalic = extractBoolAttribute(contentAttrs, 'italic');
|
||||
|
||||
const paragraphs = [...contentBody.matchAll(/<p\b([^>]*)>([\s\S]*?)<\/p\s*>/gi)];
|
||||
const iter = paragraphs.length > 0 ? paragraphs : [['', '', contentBody] as unknown as RegExpMatchArray];
|
||||
|
||||
for (const [, , body] of iter) {
|
||||
const text = stripXml(body, true);
|
||||
if (!text) {
|
||||
continue;
|
||||
}
|
||||
const fontSize = extractMaxSpanFontSize(body, contentFontSize);
|
||||
const bold = contentBold || detectAnySpanBool(body, 'bold');
|
||||
const italic = contentItalic || detectAnySpanBool(body, 'italic');
|
||||
const letterSpacing = contentLetterSpacing ?? 0;
|
||||
segments.push({
|
||||
shapeId,
|
||||
text,
|
||||
style: {
|
||||
fontSize: `${fontSize}px`,
|
||||
fontFamily: contentFontFamily,
|
||||
letterSpacing: `${letterSpacing}px`,
|
||||
bold,
|
||||
italic,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
|
||||
const runtime = new StandaloneMeasureRuntime();
|
||||
await runtime.ensureReady(args.fonts);
|
||||
|
||||
// Each segment may itself contain hard line breaks; measure each visual line
|
||||
// and report the max width (single-line scope: no wrapping).
|
||||
const measureSegment = (text: string, style: SingleLineStyle) => {
|
||||
const lines = text.split('\n');
|
||||
const widths = lines.map(line => measureSingleLineText(line, style, runtime));
|
||||
return { widthPx: Math.max(...widths, 0), lineWidths: widths };
|
||||
};
|
||||
|
||||
let output: unknown;
|
||||
|
||||
if (args.text !== undefined) {
|
||||
const style: SingleLineStyle = {
|
||||
fontSize: args.fontSize,
|
||||
fontFamily: args.fontFamily,
|
||||
letterSpacing: args.letterSpacing,
|
||||
bold: args.bold,
|
||||
italic: args.italic,
|
||||
};
|
||||
const { widthPx, lineWidths } = measureSegment(args.text, style);
|
||||
output = { mode: 'text', text: args.text, style, widthPx, lineWidths };
|
||||
} else {
|
||||
const xml = args.xml ?? (args.file ? await readFile(args.file, 'utf8') : await readStdin());
|
||||
if (!xml.trim()) {
|
||||
throw new Error('No XML input provided (use --file, --xml, --text, or pipe via stdin).');
|
||||
}
|
||||
const segments = extractSegments(xml);
|
||||
output = {
|
||||
mode: 'xml',
|
||||
segmentCount: segments.length,
|
||||
segments: segments.map(seg => {
|
||||
const { widthPx, lineWidths } = measureSegment(seg.text, seg.style);
|
||||
return { shapeId: seg.shapeId, text: seg.text, style: seg.style, widthPx, lineWidths };
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
runtime.dispose();
|
||||
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,95 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Core single-line text measurement, transcribed verbatim from ee/slide
|
||||
// modules/text-measure-module/src/util/canvaskit/text-layout.ts
|
||||
// (function measureSingleLineText, lines 1717-1776) so that the width produced
|
||||
// here is identical to what the Slides renderer computes on-line.
|
||||
//
|
||||
// Only the single-line path is reproduced (per the agreed scope). The multi-run
|
||||
// measureTextWithCanvasKit path — which needs an editor-kit ZoneDelta — is not
|
||||
// included here.
|
||||
|
||||
import type { CanvasKit, FontCollection, TypefaceFontProvider } from 'canvaskit-wasm';
|
||||
|
||||
/**
|
||||
* Read-only runtime view the measure function depends on. Mirrors
|
||||
* CanvasKitMeasureRuntime in text-measure-module/src/type/canvaskit.type.ts.
|
||||
* Initialization (WASM load, font registration) is the runtime's job, not the
|
||||
* measure function's.
|
||||
*/
|
||||
export interface CanvasKitMeasureRuntime {
|
||||
readonly getCanvasKit: () => CanvasKit | null;
|
||||
readonly getFontProvider: () => TypefaceFontProvider | null;
|
||||
readonly getFontCollection: () => FontCollection | null;
|
||||
readonly getDefaultFontFamily: () => string;
|
||||
readonly isFontRegistered: (fontFamily: string) => boolean;
|
||||
readonly hasWghtAxis: (fontFamily: string) => boolean;
|
||||
}
|
||||
|
||||
export interface SingleLineStyle {
|
||||
fontSize: string;
|
||||
fontFamily: string;
|
||||
letterSpacing: string;
|
||||
bold: boolean;
|
||||
italic: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verbatim transcription of measureSingleLineText from text-layout.ts:1717.
|
||||
* Returns the natural (unwrapped) width in px via Skia's getMaxIntrinsicWidth.
|
||||
*/
|
||||
export function measureSingleLineText(
|
||||
text: string,
|
||||
style: SingleLineStyle,
|
||||
runtime: CanvasKitMeasureRuntime,
|
||||
): number {
|
||||
const ck = runtime.getCanvasKit();
|
||||
const fontProvider = runtime.getFontProvider();
|
||||
if (!ck || !fontProvider) {
|
||||
throw new Error('CanvasKit runtime not ready, call ensureReady() before measuring');
|
||||
}
|
||||
|
||||
const fontSize = parseInt(style.fontSize, 10) || 16;
|
||||
const letterSpacing = parseFloat(style.letterSpacing) || 0;
|
||||
const rawFontFamily = style.fontFamily.split(',')[0].trim().replace(/['"]/g, '');
|
||||
const defaultFont = runtime.getDefaultFontFamily();
|
||||
const fontFamilies = runtime.isFontRegistered(rawFontFamily) ? [rawFontFamily, defaultFont] : [defaultFont];
|
||||
|
||||
const textStyle = new ck.TextStyle({
|
||||
fontSize,
|
||||
fontFamilies,
|
||||
letterSpacing,
|
||||
fontStyle: {
|
||||
weight: style.bold ? ck.FontWeight.Bold : ck.FontWeight.Normal,
|
||||
slant: style.italic ? ck.FontSlant.Italic : ck.FontSlant.Upright,
|
||||
},
|
||||
});
|
||||
|
||||
const paraStyle = new ck.ParagraphStyle({
|
||||
textStyle: {
|
||||
fontSize,
|
||||
fontFamilies,
|
||||
},
|
||||
});
|
||||
|
||||
const builder = ck.ParagraphBuilder.MakeFromFontProvider(paraStyle, fontProvider);
|
||||
|
||||
try {
|
||||
builder.pushStyle(textStyle);
|
||||
builder.addText(text);
|
||||
builder.pop();
|
||||
|
||||
const paragraph = builder.build();
|
||||
|
||||
try {
|
||||
// Unbounded width: measure the full natural width of a single line.
|
||||
paragraph.layout(Infinity);
|
||||
return paragraph.getMaxIntrinsicWidth();
|
||||
} finally {
|
||||
paragraph.delete();
|
||||
}
|
||||
} finally {
|
||||
builder.delete();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "lark-slides-text-measure",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Standalone CanvasKit single-line text measurement, reusing ee/slide text-measure-module's core measure function.",
|
||||
"bin": {
|
||||
"measure-text": "./cli.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"measure": "tsx cli.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"canvaskit-wasm": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "*",
|
||||
"tsx": "*",
|
||||
"typescript": "*"
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Standalone CanvasKit runtime that satisfies CanvasKitMeasureRuntime without
|
||||
// the @slide/modular DI container. The CanvasKit init and font-provider setup
|
||||
// are distilled from ee/slide text-measure-module:
|
||||
// - createCanvasKit() <- provide/canvaskit/canvaskit.service.implement.ts:64
|
||||
// - TypefaceFontProvider <- provide/canvaskit-font/canvaskit-font.service.base.ts:67
|
||||
// - registerFont() <- canvaskit-font.service.base.ts:108
|
||||
// - Node wasm path resolve <- provide/canvaskit/canvaskit.node.util.ts:6
|
||||
|
||||
import { createRequire } from 'node:module';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
import type { CanvasKit, FontCollection, TypefaceFontProvider } from 'canvaskit-wasm';
|
||||
import type { CanvasKitMeasureRuntime } from './measure.ts';
|
||||
|
||||
const nodeRequire = createRequire(import.meta.url);
|
||||
|
||||
function resolveNodeCanvasKitWasmPath(): string {
|
||||
return nodeRequire.resolve('canvaskit-wasm/bin/canvaskit.wasm');
|
||||
}
|
||||
|
||||
export interface FontSpec {
|
||||
/** font-family name the XML references (matched by measureSingleLineText). */
|
||||
family: string;
|
||||
/** absolute path to the font file (.ttf/.otf/.ttc). */
|
||||
path: string;
|
||||
/** true for variable fonts exposing a `wght` axis. */
|
||||
hasWghtAxis?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal runtime: loads CanvasKit WASM, creates one TypefaceFontProvider, and
|
||||
* registers caller-supplied font files. The first registered font becomes the
|
||||
* default family (mirrors registerDefaultFont in the base service).
|
||||
*/
|
||||
export class StandaloneMeasureRuntime implements CanvasKitMeasureRuntime {
|
||||
private canvasKit: CanvasKit | null = null;
|
||||
private fontProvider: TypefaceFontProvider | null = null;
|
||||
private fontCollection: FontCollection | null = null;
|
||||
private readonly registeredFonts = new Set<string>();
|
||||
private readonly wghtAxisFonts = new Set<string>();
|
||||
private defaultFontFamily = 'sans-serif';
|
||||
|
||||
async ensureReady(fonts: readonly FontSpec[]): Promise<void> {
|
||||
if (!this.canvasKit) {
|
||||
const canvasKitModule = await import('canvaskit-wasm');
|
||||
const wasmUrl = resolveNodeCanvasKitWasmPath();
|
||||
this.canvasKit = await canvasKitModule.default({
|
||||
locateFile: (file: string) => (file.endsWith('.wasm') ? wasmUrl : file),
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.fontProvider) {
|
||||
this.fontProvider = this.canvasKit.TypefaceFontProvider.Make();
|
||||
}
|
||||
|
||||
if (!this.fontCollection) {
|
||||
this.fontCollection = this.canvasKit.FontCollection.Make();
|
||||
this.fontCollection.setDefaultFontManager(this.fontProvider);
|
||||
this.fontCollection.enableFontFallback();
|
||||
}
|
||||
|
||||
for (const [index, font] of fonts.entries()) {
|
||||
await this.registerFont(font, index === 0);
|
||||
}
|
||||
|
||||
if (!this.hasAnyRegisteredFont()) {
|
||||
// CanvasKit embeds no fonts; with none registered Skia finds no glyphs and
|
||||
// measures 0 width while resolving successfully — worse than failing.
|
||||
throw new Error('No font registered; measurement would be unreliable. Pass at least one --font family=path.');
|
||||
}
|
||||
}
|
||||
|
||||
private async registerFont(font: FontSpec, isDefault: boolean): Promise<void> {
|
||||
if (!this.fontProvider) {
|
||||
throw new Error('Font provider not initialized');
|
||||
}
|
||||
const data = await readFile(font.path);
|
||||
const buffer = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
||||
const result = this.fontProvider.registerFont(buffer, font.family) as null | undefined;
|
||||
if (result === null) {
|
||||
console.warn(`[CanvasKit] registerFont failed for ${font.family} (${font.path})`);
|
||||
return;
|
||||
}
|
||||
this.registeredFonts.add(font.family);
|
||||
if (font.hasWghtAxis) {
|
||||
this.wghtAxisFonts.add(font.family);
|
||||
}
|
||||
if (isDefault) {
|
||||
this.defaultFontFamily = font.family;
|
||||
}
|
||||
}
|
||||
|
||||
hasAnyRegisteredFont(): boolean {
|
||||
return this.registeredFonts.size > 0;
|
||||
}
|
||||
|
||||
getCanvasKit(): CanvasKit | null {
|
||||
return this.canvasKit;
|
||||
}
|
||||
|
||||
getFontProvider(): TypefaceFontProvider | null {
|
||||
return this.fontProvider;
|
||||
}
|
||||
|
||||
getFontCollection(): FontCollection | null {
|
||||
return this.fontCollection;
|
||||
}
|
||||
|
||||
getDefaultFontFamily(): string {
|
||||
return this.defaultFontFamily;
|
||||
}
|
||||
|
||||
isFontRegistered(fontFamily: string): boolean {
|
||||
return this.registeredFonts.has(fontFamily);
|
||||
}
|
||||
|
||||
hasWghtAxis(fontFamily: string): boolean {
|
||||
return this.wghtAxisFonts.has(fontFamily);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.fontCollection?.delete();
|
||||
this.fontProvider?.delete();
|
||||
this.fontCollection = null;
|
||||
this.fontProvider = null;
|
||||
this.canvasKit = null;
|
||||
this.registeredFonts.clear();
|
||||
this.wghtAxisFonts.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["ES2020"],
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["*.ts"]
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify character width calibration by comparing new estimates against visual evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import sys
|
||||
import unicodedata
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(SCRIPT_DIR))
|
||||
|
||||
import xml_text_overlap_lint as lint
|
||||
|
||||
|
||||
def old_estimate_character_width(character: str, font_size: float) -> float:
|
||||
if character.isspace():
|
||||
return font_size * 0.33
|
||||
if unicodedata.east_asian_width(character) in {"F", "W"}:
|
||||
return font_size
|
||||
return font_size * 0.55
|
||||
|
||||
|
||||
def old_estimate_text_width(text: str, font_size: float) -> float:
|
||||
return sum(old_estimate_character_width(c, font_size) for c in text)
|
||||
|
||||
|
||||
def make_element(
|
||||
width: float,
|
||||
height: float,
|
||||
text: str,
|
||||
font_size: float,
|
||||
*,
|
||||
padding_left: float = 0,
|
||||
padding_right: float = 0,
|
||||
padding_top: float = 0,
|
||||
padding_bottom: float = 0,
|
||||
bold: bool = False,
|
||||
wrap: str = "true",
|
||||
auto_fit: str | None = None,
|
||||
font_family: str = "",
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"id": "test",
|
||||
"kind": "shape",
|
||||
"type": "text",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": width,
|
||||
"height": height,
|
||||
"paddingLeft": padding_left,
|
||||
"paddingRight": padding_right,
|
||||
"paddingTop": padding_top,
|
||||
"paddingBottom": padding_bottom,
|
||||
"fontSize": font_size,
|
||||
"fontFamily": font_family,
|
||||
"bold": bold,
|
||||
"italic": False,
|
||||
"text": text,
|
||||
"wrap": wrap,
|
||||
"autoFit": auto_fit,
|
||||
"letterSpacing": 0,
|
||||
"paragraphs": [
|
||||
{
|
||||
"text": p,
|
||||
"fontSize": font_size,
|
||||
"lineSpacing": None,
|
||||
"beforeLineSpacing": None,
|
||||
"afterLineSpacing": None,
|
||||
"letterSpacing": None,
|
||||
}
|
||||
for p in text.split("\n")
|
||||
if p
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def count_chars(text: str) -> dict[str, int]:
|
||||
counts = {"cjk": 0, "upper": 0, "lower": 0, "digit": 0, "punct": 0, "space": 0}
|
||||
for c in text:
|
||||
if c.isspace():
|
||||
counts["space"] += 1
|
||||
elif unicodedata.east_asian_width(c) in {"F", "W"}:
|
||||
counts["cjk"] += 1
|
||||
elif c.isupper():
|
||||
counts["upper"] += 1
|
||||
elif c.islower():
|
||||
counts["lower"] += 1
|
||||
elif c.isdigit():
|
||||
counts["digit"] += 1
|
||||
else:
|
||||
counts["punct"] += 1
|
||||
return counts
|
||||
|
||||
|
||||
def test_case(
|
||||
name: str,
|
||||
text: str,
|
||||
available_width: float,
|
||||
font_size: float,
|
||||
*,
|
||||
expected_single_line: bool | None = None,
|
||||
note: str = "",
|
||||
) -> dict[str, Any]:
|
||||
elem = make_element(available_width, 1000, text, font_size)
|
||||
new_width = lint.estimate_text_max_line_width(elem)
|
||||
new_ratio = new_width / available_width
|
||||
new_lines = lint.estimate_text_line_count_for_text(elem, text)
|
||||
|
||||
old_width = max(old_estimate_text_width(p, font_size) for p in text.split("\n") if p)
|
||||
old_ratio = old_width / available_width
|
||||
|
||||
counts = count_chars(text)
|
||||
|
||||
result = {
|
||||
"name": name,
|
||||
"text_preview": text[:60] + ("..." if len(text) > 60 else ""),
|
||||
"font_size": font_size,
|
||||
"available_width": available_width,
|
||||
"char_counts": counts,
|
||||
"new_estimated_width": round(new_width, 2),
|
||||
"new_ratio": round(new_ratio, 4),
|
||||
"new_line_count": new_lines,
|
||||
"old_estimated_width": round(old_width, 2),
|
||||
"old_ratio": round(old_ratio, 4),
|
||||
"expected_single_line": expected_single_line,
|
||||
"note": note,
|
||||
}
|
||||
|
||||
if expected_single_line is True:
|
||||
result["new_correct"] = new_ratio <= 1.02
|
||||
result["old_correct"] = old_ratio <= 1.02
|
||||
elif expected_single_line is False:
|
||||
result["new_correct"] = new_ratio > 1.0
|
||||
result["old_correct"] = old_ratio > 1.0
|
||||
else:
|
||||
result["new_correct"] = None
|
||||
result["old_correct"] = None
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def print_result(r: dict[str, Any]) -> None:
|
||||
status_new = ""
|
||||
status_old = ""
|
||||
if r["expected_single_line"] is not None:
|
||||
status_new = "✓" if r["new_correct"] else "✗"
|
||||
status_old = "✓" if r["old_correct"] else "✗"
|
||||
|
||||
print(f"--- {r['name']} {status_new} ---")
|
||||
print(f" Text: {r['text_preview']}")
|
||||
print(f" Font: {r['font_size']}pt, Available: {r['available_width']}px")
|
||||
cc = r["char_counts"]
|
||||
print(f" Chars: CJK={cc['cjk']} Upper={cc['upper']} Lower={cc['lower']} "
|
||||
f"Digit={cc['digit']} Punct={cc['punct']} Space={cc['space']}")
|
||||
print(f" NEW: width={r['new_estimated_width']}px ratio={r['new_ratio']:.4f} lines={r['new_line_count']} {status_new}")
|
||||
print(f" OLD: width={r['old_estimated_width']}px ratio={r['old_ratio']:.4f} {status_old}")
|
||||
if r["note"]:
|
||||
print(f" Note: {r['note']}")
|
||||
print()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 70)
|
||||
print("Character Width Calibration Verification")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
results = []
|
||||
|
||||
# Test cases based on visual evidence from screenshots
|
||||
# Page 17 line 2: "在 Natural Language Processing 领域,大语言模型 LLM 的出现彻底改变了人机交互方式。"
|
||||
# Visually: single line, fills ~93-95% of 800px
|
||||
results.append(test_case(
|
||||
"p17 bbR line2 (mixed CJK+English, 18pt, 800px, expected single)",
|
||||
"在 Natural Language Processing 领域,大语言模型 LLM 的出现彻底改变了人机交互方式。",
|
||||
available_width=800,
|
||||
font_size=18,
|
||||
expected_single_line=True,
|
||||
note="Visually single line, ~93% fill"
|
||||
))
|
||||
|
||||
# Page 17 line 3: "从 ChatGPT 到 文心一言,从 GPT-4 到 Claude 3,AI 助手正在成为人们工作生活的标配。"
|
||||
# Visually: single line
|
||||
results.append(test_case(
|
||||
"p17 bbR line3 (mixed CJK+English, 18pt, 800px, expected single)",
|
||||
"从 ChatGPT 到 文心一言,从 GPT-4 到 Claude 3,AI 助手正在成为人们工作生活的标配。",
|
||||
available_width=800,
|
||||
font_size=18,
|
||||
expected_single_line=True,
|
||||
note="Visually single line"
|
||||
))
|
||||
|
||||
# Page 6 paragraph 3: "中文与英文混排测试:The quick brown fox jumps over the lazy dog. 敏捷的棕色狐狸跳过了懒狗。"
|
||||
# Visually: single line, very tight (was borderline with old model)
|
||||
results.append(test_case(
|
||||
"p6 bNf para3 (mixed tight line, 18pt, 800px, expected single)",
|
||||
"中文与英文混排测试:The quick brown fox jumps over the lazy dog. 敏捷的棕色狐狸跳过了懒狗。",
|
||||
available_width=800,
|
||||
font_size=18,
|
||||
expected_single_line=True,
|
||||
note="Borderline case - visually fits on one line"
|
||||
))
|
||||
|
||||
# Page 6 paragraph 2 (English): "Typography is the art and technique of arranging type to make written language legible, readable, and appealing when displayed."
|
||||
# Visually: wraps to 2 lines
|
||||
results.append(test_case(
|
||||
"p6 bNf para2 (English paragraph, 18pt, 800px, wraps)",
|
||||
"Typography is the art and technique of arranging type to make written language legible, readable, and appealing when displayed.",
|
||||
available_width=800,
|
||||
font_size=18,
|
||||
expected_single_line=False,
|
||||
note="Visually wraps to 2 lines"
|
||||
))
|
||||
|
||||
# Page 28 right column line 1: "This is the right column body text in 15pt size."
|
||||
# Visually: single line, ~92% fill of 400px
|
||||
results.append(test_case(
|
||||
"p28 bZQ line1 (English, 15pt, 400px, expected single)",
|
||||
"This is the right column body text in 15pt size.",
|
||||
available_width=400,
|
||||
font_size=15,
|
||||
expected_single_line=True,
|
||||
note="Visually single line, ~92% fill"
|
||||
))
|
||||
|
||||
# Page 28 right column paragraph: "Multi-column layout optimizes space and information density."
|
||||
# Visually: wraps to 2 lines ("density." on its own line)
|
||||
results.append(test_case(
|
||||
"p28 bZQ para (English, 15pt, 400px, wraps)",
|
||||
"Multi-column layout optimizes space and information density.",
|
||||
available_width=400,
|
||||
font_size=15,
|
||||
expected_single_line=False,
|
||||
note="Visually wraps - 'density.' on next line"
|
||||
))
|
||||
|
||||
# Page 28 left column line 1: "这是左栏的正文内容,使用 15pt 字号,1.7 倍行间距。"
|
||||
# Visually: single line in 420px column
|
||||
results.append(test_case(
|
||||
"p28 bZm line1 (CJK+digits, 15pt, 420px, expected single)",
|
||||
"这是左栏的正文内容,使用 15pt 字号,1.7 倍行间距。",
|
||||
available_width=420,
|
||||
font_size=15,
|
||||
expected_single_line=True,
|
||||
note="Visually single line"
|
||||
))
|
||||
|
||||
# Page 23: 2.0 line spacing body (14pt, 270px) - wraps to multiple lines
|
||||
# "这是一段测试文字,用于展示2.0倍行间距的效果。行间距较大时,文字显得疏朗透气,阅读体验轻松。适合需要留白感的设计。"
|
||||
results.append(test_case(
|
||||
"p23 bZc body (CJK+digits, 14pt, 270px, wraps)",
|
||||
"这是一段测试文字,用于展示2.0倍行间距的效果。行间距较大时,文字显得疏朗透气,阅读体验轻松。适合需要留白感的设计。",
|
||||
available_width=270,
|
||||
font_size=14,
|
||||
expected_single_line=False,
|
||||
note="Visually wraps to ~4 lines"
|
||||
))
|
||||
|
||||
# Page 21 English line: "Underline is used for links and key annotations, strikethrough for deleted content."
|
||||
# Visually: single line at 18pt in 800px, fills ~92%
|
||||
results.append(test_case(
|
||||
"p21 bbT English line (18pt, 800px, expected single)",
|
||||
"Underline is used for links and key annotations, strikethrough for deleted content.",
|
||||
available_width=800,
|
||||
font_size=18,
|
||||
expected_single_line=True,
|
||||
note="Visually single line, ~92% fill"
|
||||
))
|
||||
|
||||
# Page 12 Helvetica paragraph: "Sans-serif fonts are clean, modern, and highly legible. Widely used in UI design, branding, and digital media."
|
||||
# 16pt, 800px, wraps to 2 lines
|
||||
results.append(test_case(
|
||||
"p12 bak paragraph (English, 16pt, 800px, wraps)",
|
||||
"Sans-serif fonts are clean, modern, and highly legible. Widely used in UI design, branding, and digital media.",
|
||||
available_width=800,
|
||||
font_size=16,
|
||||
expected_single_line=False,
|
||||
note="Visually wraps to 2 lines"
|
||||
))
|
||||
|
||||
# Pure uppercase test: "ABCDEFGHIJKLMNOPQRSTUVWXYZ" at 28pt bold, should be ~50% of 800px
|
||||
results.append(test_case(
|
||||
"p12 uppercase alphabet (28pt bold, 800px, single)",
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
available_width=800,
|
||||
font_size=28,
|
||||
expected_single_line=True,
|
||||
note="Centered test string, ~50% width"
|
||||
))
|
||||
|
||||
# Pure lowercase test: "abcdefghijklmnopqrstuvwxyz" at 28pt
|
||||
results.append(test_case(
|
||||
"p12 lowercase alphabet (28pt bold, 800px, single)",
|
||||
"abcdefghijklmnopqrstuvwxyz",
|
||||
available_width=800,
|
||||
font_size=28,
|
||||
expected_single_line=True,
|
||||
note="Centered test string, similar width to uppercase"
|
||||
))
|
||||
|
||||
# Digits+symbols: "0123456789!@#$%^&*()" at 28pt, shorter than alphabets
|
||||
results.append(test_case(
|
||||
"p12 digits+symbols (28pt bold, 800px, single)",
|
||||
"0123456789!@#$%^&*()",
|
||||
available_width=800,
|
||||
font_size=28,
|
||||
expected_single_line=True,
|
||||
note="Shorter than alphabet lines"
|
||||
))
|
||||
|
||||
for r in results:
|
||||
print_result(r)
|
||||
|
||||
# Summary statistics
|
||||
print("=" * 70)
|
||||
print("SUMMARY")
|
||||
print("=" * 70)
|
||||
new_correct = sum(1 for r in results if r["new_correct"] is True)
|
||||
old_correct = sum(1 for r in results if r["old_correct"] is True)
|
||||
total_evaluated = sum(1 for r in results if r["expected_single_line"] is not None)
|
||||
print(f"Test cases with known expectation: {total_evaluated}")
|
||||
print(f"New model correct: {new_correct}/{total_evaluated}")
|
||||
print(f"Old model correct: {old_correct}/{total_evaluated}")
|
||||
print()
|
||||
|
||||
# Show ratio distribution for single-line cases
|
||||
print("Single-line cases (ratio should be 0.85-1.02):")
|
||||
for r in results:
|
||||
if r["expected_single_line"] is True:
|
||||
print(f" {r['name'][:50]:50s} new_ratio={r['new_ratio']:.4f} old_ratio={r['old_ratio']:.4f}")
|
||||
print()
|
||||
|
||||
print("Wrapping cases (ratio should be >1.0):")
|
||||
for r in results:
|
||||
if r["expected_single_line"] is False:
|
||||
print(f" {r['name'][:50]:50s} new_ratio={r['new_ratio']:.4f} old_ratio={r['old_ratio']:.4f}")
|
||||
print()
|
||||
|
||||
# Check for tight_single samples (0.85-1.0) ratio target
|
||||
tight_single = [r for r in results if r["expected_single_line"] is True and r["new_ratio"] >= 0.80]
|
||||
if tight_single:
|
||||
avg_new_ratio = sum(r["new_ratio"] for r in tight_single) / len(tight_single)
|
||||
avg_old_ratio = sum(r["old_ratio"] for r in tight_single) / len(tight_single)
|
||||
print(f"Average ratio for near-full single lines:")
|
||||
print(f" New model: {avg_new_ratio:.4f}")
|
||||
print(f" Old model: {avg_old_ratio:.4f}")
|
||||
print(f" Target: ~0.90-0.98 (some margin but mostly filled)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,159 +0,0 @@
|
||||
slide_number,slide_id,element_id,x,y,shape_width,shape_height,padding_left,padding_right,available_width,font_size,font_family,bold,italic,wrap,auto_fit,text_align,text_clean,text_length,text_type,is_single_line_hard,estimated_width,width_ratio,likely_wraps_actual,notes
|
||||
1,pmm,bNA,80,160,800,120,0,0,800,64,思源黑体,true,false,true,none,center,字体排版测试样张,8,中文,true,512,0.64,no_slack,估算宽度 < 70% 可用宽度(0.64),明显有留白
|
||||
1,pmm,bNj,80,300,800,60,0,0,800,24,思源黑体,true,false,true,none,center,TypographyTestSample·30Pages,28,英文+数字,true,369.6,0.462,no_slack,估算宽度 < 70% 可用宽度(0.46),明显有留白
|
||||
1,pmm,bNV,80,420,800,40,0,0,800,16,思源黑体,false,false,true,none,center,包含字号/字体/中英文/数字/样式/间距/颜色等全面测试,28,中文,true,404.8,0.506,no_slack,估算宽度 < 70% 可用宽度(0.51),明显有留白
|
||||
2,pmZ,bNt,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,02·超大字号测试72pt,13,中文+英文+数字,true,275.8,0.3283,no_slack,估算宽度 < 70% 可用宽度(0.33),明显有留白
|
||||
2,pmZ,bNh,60,140,840,110,0,0,840,72,思源黑体,true,false,true,none,center,汉字测试ABC123,10,中文+英文+数字,true,525.6,0.6257,no_slack,估算宽度 < 70% 可用宽度(0.63),明显有留白
|
||||
2,pmZ,bNn,60,270,840,100,0,0,840,72,思源黑体,true,false,true,none,center,排版设计Typography,14,中文+英文,true,684.0,0.8143,no,估算宽度 0.81x 可用宽度,大概率单行
|
||||
2,pmZ,bNk,60,400,840,60,0,0,840,14,思源黑体,false,false,true,none,center,72pt超大字号·用于标题展示·测试字重与字间距,24,中文+英文+数字,true,298.2,0.355,no_slack,估算宽度 < 70% 可用宽度(0.35),明显有留白
|
||||
3,pmY,bNw,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,03·大字号测试48pt,12,中文+英文+数字,true,247.8,0.295,no_slack,估算宽度 < 70% 可用宽度(0.30),明显有留白
|
||||
3,pmY,bNv,60,130,840,80,0,0,840,48,思源黑体,true,false,true,none,center,中华人民共和国2024,11,中文+数字,true,441.6,0.5257,no_slack,估算宽度 < 70% 可用宽度(0.53),明显有留白
|
||||
3,pmY,bNe,60,230,840,80,0,0,840,48,思源黑体,true,false,true,none,center,TheQuickBrownFox,16,英文,true,422.4,0.5029,no_slack,估算宽度 < 70% 可用宽度(0.50),明显有留白
|
||||
3,pmY,bNz,60,330,840,80,0,0,840,48,思源黑体,true,false,true,none,center,0123456789数字测试,14,中文+数字,true,456.0,0.5429,no_slack,估算宽度 < 70% 可用宽度(0.54),明显有留白
|
||||
3,pmY,bNp,60,440,840,50,0,0,840,14,思源黑体,false,false,true,none,center,48pt大字号·常用于主标题·中英文数字混排测试,24,中文+英文+数字,true,298.2,0.355,no_slack,估算宽度 < 70% 可用宽度(0.35),明显有留白
|
||||
4,pma,bNc,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,04·中大号字号测试36pt,14,中文+英文+数字,true,303.8,0.3617,no_slack,估算宽度 < 70% 可用宽度(0.36),明显有留白
|
||||
4,pma,bNZ,80,130,800,60,0,0,800,36,思源黑体,true,false,true,none,,科技创新驱动未来发展Innovation,20,中文+英文,true,558.0,0.6975,no_slack,估算宽度 < 70% 可用宽度(0.70),明显有留白
|
||||
4,pma,bNd,80,210,800,60,0,0,800,36,思源黑体,true,false,true,none,,人工智能改变生活方式AI2024,16,中文+英文+数字,true,478.8,0.5985,no_slack,估算宽度 < 70% 可用宽度(0.60),明显有留白
|
||||
4,pma,bNB,80,290,800,60,0,0,800,36,思源黑体,true,false,true,none,,数据可视化DataVisualization,22,中文+英文,true,516.6,0.6458,no_slack,估算宽度 < 70% 可用宽度(0.65),明显有留白
|
||||
4,pma,bNN,80,370,800,60,0,0,800,36,思源黑体,true,false,true,none,,云计算CloudComputing99%,20,中文+英文+数字,true,444.6,0.5558,no_slack,估算宽度 < 70% 可用宽度(0.56),明显有留白
|
||||
4,pma,bNb,80,450,800,40,0,0,800,14,思源黑体,false,false,true,none,center,36pt中大号·副标题级·多行对比测试,19,中文+英文+数字,true,228.2,0.2853,no_slack,估算宽度 < 70% 可用宽度(0.29),明显有留白
|
||||
5,pmW,bNs,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,05·中号字号测试28pt,13,中文+英文+数字,true,275.8,0.3283,no_slack,估算宽度 < 70% 可用宽度(0.33),明显有留白
|
||||
5,pmW,bNT,80,120,800,50,0,0,800,28,思源黑体,true,false,true,none,,一、项目背景与目标Background,19,中文+英文,true,406.0,0.5075,no_slack,估算宽度 < 70% 可用宽度(0.51),明显有留白
|
||||
5,pmW,bNI,80,185,800,50,0,0,800,28,思源黑体,true,false,true,none,,二、市场分析与调研Market2024,19,中文+英文+数字,true,406.0,0.5075,no_slack,估算宽度 < 70% 可用宽度(0.51),明显有留白
|
||||
5,pmW,bNY,80,250,800,50,0,0,800,28,思源黑体,true,false,true,none,,三、技术方案与架构Technology,19,中文+英文,true,406.0,0.5075,no_slack,估算宽度 < 70% 可用宽度(0.51),明显有留白
|
||||
5,pmW,bNq,80,315,800,50,0,0,800,28,思源黑体,true,false,true,none,,四、实施计划与时间表Plan12月,17,中文+英文+数字,true,400.4,0.5005,no_slack,估算宽度 < 70% 可用宽度(0.50),明显有留白
|
||||
5,pmW,bNr,80,380,800,50,0,0,800,28,思源黑体,true,false,true,none,,五、预期效果与收益BenefitROI,19,中文+英文,true,406.0,0.5075,no_slack,估算宽度 < 70% 可用宽度(0.51),明显有留白
|
||||
5,pmW,bNM,80,450,800,40,0,0,800,14,思源黑体,false,false,true,none,center,28pt中号·章节标题级·目录式排列测试,20,中文+英文+数字,true,242.2,0.3027,no_slack,估算宽度 < 70% 可用宽度(0.30),明显有留白
|
||||
6,pmL,bNO,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,06·正文字号测试18pt,13,中文+英文+数字,true,275.8,0.3283,no_slack,估算宽度 < 70% 可用宽度(0.33),明显有留白
|
||||
6,pmL,bNf,80,120,800,320,0,0,800,18,思源黑体,false,false,true,none,,"这是一段正文测试文字,使用18pt字号,是幻灯片中最常用的正文字号。Typographyistheartandtechniqueofarrangingtypetomakewrittenlanguagelegible,readable,andappealingwhendisplayed.中文与英文混排测试:Thequickbrownfoxjumpsoverthelazydog.敏捷的棕色狐狸跳过了懒狗。数字测试:2024年12月25日,增长率12.5%,用户数1,234,567人。",242,中文+英文+数字,false,1079.1,1.3489,likely_wrap,估算宽度 1.35x 可用宽度,大概率换2行
|
||||
6,pmL,bNE,80,460,800,30,0,0,800,14,思源黑体,false,false,true,none,center,18pt正文·行间距1.8倍·中英文数字混排,22,中文+英文+数字,true,251.3,0.3141,no_slack,估算宽度 < 70% 可用宽度(0.31),明显有留白
|
||||
7,pmy,bNK,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,07·小号字号测试14pt,13,中文+英文+数字,true,275.8,0.3283,no_slack,估算宽度 < 70% 可用宽度(0.33),明显有留白
|
||||
7,pmy,bNU,80,120,800,340,0,0,800,14,思源黑体,false,false,true,none,,"小号正文测试14pt这是一段使用14pt字号的正文文字,适合用于内容较多的页面或说明性文字。在信息密度较高的幻灯片中,14pt是一个兼顾可读性与信息量的选择。Thisisaparagraphofbodytextin14ptsize.Itiscommonlyusedfordetaileddescriptions,footnotes,orcontent-heavyslideswhereinformationdensitymatters.中英文数字混排:2024年度报告显示,公司营收达到1,234.56万元,同比增长23.45%,用户满意度98.6%。常用标点符号测试:逗号,句号。感叹号!问号?冒号:分号;引号""""括号()省略号……破折号——",322,中文+英文+数字,false,1070.3,1.3379,likely_wrap,估算宽度 1.34x 可用宽度,大概率换2行
|
||||
7,pmy,bNF,80,475,800,30,0,0,800,12,思源黑体,false,false,true,none,center,14pt小号正文·行间距1.6倍·高密度信息展示,24,中文+英文+数字,true,239.4,0.2992,no_slack,估算宽度 < 70% 可用宽度(0.30),明显有留白
|
||||
8,pmX,bNX,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,08·极小字号测试10pt,13,中文+英文+数字,true,275.8,0.3283,no_slack,估算宽度 < 70% 可用宽度(0.33),明显有留白
|
||||
8,pmX,bNm,80,120,800,360,0,0,800,10,思源黑体,false,false,true,none,,"极小字号测试10pt—用于脚注、注释、数据来源说明等本页测试10pt极小字号的可读性。在正式演示中,10pt通常仅用于数据来源标注、脚注说明、版权信息等非核心内容,不建议用于正文。Datasource:NationalBureauofStatistics,2024AnnualReport.AllfiguresareinRMB10,000unlessotherwisenoted.Growthratesarecalculatedyear-over-year.数据来源:国家统计局2024年度报告。所有金额单位为万元,另有说明除外。增长率按同比计算。样本量n=10,234,置信区间95%。©2024TypographyTestLab.Allrightsreserved.版权所有,翻印必究。",345,中文+英文+数字,false,764.5,0.9556,tight_single,估算宽度 0.96x 可用宽度,接近填满,需确认是否单行
|
||||
8,pmX,bNl,80,490,800,30,0,0,800,10,思源黑体,false,false,true,none,center,10pt极小字号·脚注/注释级·测试极限可读性,23,中文+英文+数字,true,198.5,0.2481,no_slack,估算宽度 < 70% 可用宽度(0.25),明显有留白
|
||||
9,pmE,baa,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,09·字号阶梯对比测试,11,中文+数字,true,270.2,0.3217,no_slack,估算宽度 < 70% 可用宽度(0.32),明显有留白
|
||||
9,pmE,bao,100,115,760,70,0,0,760,60,思源黑体,true,false,true,none,,60pt标题字号Title,13,中文+英文+数字,true,537.0,0.7066,no,估算宽度 0.71x 可用宽度,大概率单行
|
||||
9,pmE,bac,100,185,760,55,0,0,760,44,思源黑体,true,false,true,none,,44pt大标题Headline,15,中文+英文+数字,true,422.4,0.5558,no_slack,估算宽度 < 70% 可用宽度(0.56),明显有留白
|
||||
9,pmE,bNg,100,245,760,45,0,0,760,32,思源黑体,true,false,true,none,,32pt副标题Sub-headline,19,中文+英文+数字,true,377.6,0.4968,no_slack,估算宽度 < 70% 可用宽度(0.50),明显有留白
|
||||
9,pmE,bNL,100,295,760,38,0,0,760,24,思源黑体,false,false,true,none,,24pt小标题Section,14,中文+英文+数字,true,217.2,0.2858,no_slack,估算宽度 < 70% 可用宽度(0.29),明显有留白
|
||||
9,pmE,baN,100,340,760,32,0,0,760,18,思源黑体,false,false,true,none,,18pt正文字号BodyText,16,中文+英文+数字,true,190.8,0.2511,no_slack,估算宽度 < 70% 可用宽度(0.25),明显有留白
|
||||
9,pmE,bab,100,380,760,28,0,0,760,14,思源黑体,false,false,true,none,,14pt小号正文SmallBody,17,中文+英文+数字,true,156.1,0.2054,no_slack,估算宽度 < 70% 可用宽度(0.21),明显有留白
|
||||
9,pmE,bad,100,415,760,24,0,0,760,11,思源黑体,false,false,true,none,,11pt注释字号Caption/Footnote,24,中文+英文+数字,true,165.0,0.2171,no_slack,估算宽度 < 70% 可用宽度(0.22),明显有留白
|
||||
9,pmE,baB,100,455,760,30,0,0,760,13,思源黑体,false,false,true,none,center,从60pt到11pt·七级字号阶梯对比·一目了然,24,中文+英文+数字,true,253.5,0.3336,no_slack,估算宽度 < 70% 可用宽度(0.33),明显有留白
|
||||
10,pms,baw,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,10·中文字体测试·黑体,12,中文+数字,true,285.6,0.34,no_slack,估算宽度 < 70% 可用宽度(0.34),明显有留白
|
||||
10,pms,bav,80,120,800,80,0,0,800,48,思源黑体,true,false,true,none,center,思源黑体SourceHanSans,17,中文+英文,true,535.2,0.669,no_slack,估算宽度 < 70% 可用宽度(0.67),明显有留白
|
||||
10,pms,bae,80,220,800,60,0,0,800,32,思源黑体,true,false,true,none,center,现代简洁清晰易读专业稳重,12,中文,true,384,0.48,no_slack,估算宽度 < 70% 可用宽度(0.48),明显有留白
|
||||
10,pms,baz,80,300,800,150,0,0,800,18,思源黑体,false,false,true,none,center,黑体字笔画均匀、结构方正,具有现代感和力量感。广泛应用于标题、标语、UI界面等场景。Thequickbrownfoxjumpsoverthelazydog.0123456789,88,中文+英文+数字,false,455.4,0.5692,no_slack,估算宽度 < 70% 可用宽度(0.57),明显有留白
|
||||
10,pms,bap,80,470,800,30,0,0,800,12,思源黑体,false,false,true,none,center,思源黑体·无衬线中文字体·现代商务风格首选,21,中文,true,241.2,0.3015,no_slack,估算宽度 < 70% 可用宽度(0.30),明显有留白
|
||||
11,pmV,baA,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,11·中文字体测试·宋体,12,中文+数字,true,285.6,0.34,no_slack,估算宽度 < 70% 可用宽度(0.34),明显有留白
|
||||
11,pmV,baj,80,120,800,80,0,0,800,48,思源宋体,true,false,true,none,center,思源宋体SourceHanSerif,18,中文+英文,true,561.6,0.702,no,估算宽度 0.70x 可用宽度,大概率单行
|
||||
11,pmV,bau,80,220,800,60,0,0,800,32,思源宋体,true,false,true,none,center,典雅端庄文化底蕴传统韵味,12,中文,true,384,0.48,no_slack,估算宽度 < 70% 可用宽度(0.48),明显有留白
|
||||
11,pmV,baW,80,300,800,150,0,0,800,18,思源宋体,false,false,true,none,center,宋体字横细竖粗,笔画末端有装饰性衬线,具有传统文化气息,适合正式、庄重的场合。Thequickbrownfoxjumpsoverthelazydog.0123456789,85,中文+英文+数字,false,455.4,0.5692,no_slack,估算宽度 < 70% 可用宽度(0.57),明显有留白
|
||||
11,pmV,baJ,80,470,800,30,0,0,800,12,思源黑体,false,false,true,none,center,思源宋体·衬线中文字体·学术文化风格首选,20,中文,true,229.2,0.2865,no_slack,估算宽度 < 70% 可用宽度(0.29),明显有留白
|
||||
12,pmD,ban,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,12·英文字体测试·无衬线体,14,中文+数字,true,341.6,0.4067,no_slack,估算宽度 < 70% 可用宽度(0.41),明显有留白
|
||||
12,pmD,bay,80,120,800,80,0,0,800,52,Helvetica,true,false,true,none,center,HelveticaNeue,13,英文,true,371.8,0.4648,no_slack,估算宽度 < 70% 可用宽度(0.46),明显有留白
|
||||
12,pmD,baf,80,210,800,50,0,0,800,28,Helvetica,true,false,true,none,center,ABCDEFGHIJKLMNOPQRSTUVWXYZ,26,英文,true,400.4,0.5005,no_slack,估算宽度 < 70% 可用宽度(0.50),明显有留白
|
||||
12,pmD,baU,80,265,800,50,0,0,800,28,Helvetica,true,false,true,none,center,abcdefghijklmnopqrstuvwxyz,26,英文,true,400.4,0.5005,no_slack,估算宽度 < 70% 可用宽度(0.50),明显有留白
|
||||
12,pmD,bah,80,320,800,50,0,0,800,28,Helvetica,true,false,true,none,center,0123456789!@#$%^&*(),20,数字,true,308.0,0.385,no_slack,估算宽度 < 70% 可用宽度(0.39),明显有留白
|
||||
12,pmD,bak,80,390,800,60,0,0,800,16,Helvetica,false,false,true,none,center,"Sans-seriffontsareclean,modern,andhighlylegible.WidelyusedinUIdesign,branding,anddigitalmedia.",94,英文,false,422.4,0.528,no_slack,估算宽度 < 70% 可用宽度(0.53),明显有留白
|
||||
12,pmD,baO,80,475,800,30,0,0,800,12,思源黑体,false,false,true,none,center,无衬线体Sans-serif·现代简洁·数字界面首选,26,中文+英文,true,247.2,0.309,no_slack,估算宽度 < 70% 可用宽度(0.31),明显有留白
|
||||
13,pmA,bai,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,13·英文字体测试·衬线体,13,中文+数字,true,313.6,0.3733,no_slack,估算宽度 < 70% 可用宽度(0.37),明显有留白
|
||||
13,pmA,bam,80,120,800,80,0,0,800,52,Georgia,true,false,true,none,center,GeorgiaSerif,12,英文,true,343.2,0.429,no_slack,估算宽度 < 70% 可用宽度(0.43),明显有留白
|
||||
13,pmA,bal,80,210,800,50,0,0,800,28,Georgia,true,false,true,none,center,ABCDEFGHIJKLMNOPQRSTUVWXYZ,26,英文,true,400.4,0.5005,no_slack,估算宽度 < 70% 可用宽度(0.50),明显有留白
|
||||
13,pmA,baq,80,265,800,50,0,0,800,28,Georgia,true,false,true,none,center,abcdefghijklmnopqrstuvwxyz,26,英文,true,400.4,0.5005,no_slack,估算宽度 < 70% 可用宽度(0.50),明显有留白
|
||||
13,pmA,bar,80,320,800,50,0,0,800,28,Georgia,true,false,true,none,center,0123456789!@#$%^&*(),20,数字,true,308.0,0.385,no_slack,估算宽度 < 70% 可用宽度(0.39),明显有留白
|
||||
13,pmA,baM,80,390,800,60,0,0,800,16,Georgia,false,false,true,none,center,"Seriffontshavesmalllinesattheendsofcharacters,conveyingtradition,elegance,andauthority.",87,英文,false,404.8,0.506,no_slack,估算宽度 < 70% 可用宽度(0.51),明显有留白
|
||||
13,pmA,baC,80,475,800,30,0,0,800,12,思源黑体,false,false,true,none,center,衬线体Serif·优雅传统·印刷出版首选,20,中文+英文,true,202.2,0.2527,no_slack,估算宽度 < 70% 可用宽度(0.25),明显有留白
|
||||
14,pmi,baY,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,14·数字测试·阿拉伯数字,13,中文+数字,true,313.6,0.3733,no_slack,估算宽度 < 70% 可用宽度(0.37),明显有留白
|
||||
14,pmi,baK,80,120,800,100,0,0,800,72,思源黑体,true,false,true,none,center,0123456789,10,数字,true,396.0,0.495,no_slack,估算宽度 < 70% 可用宽度(0.50),明显有留白
|
||||
14,pmi,baP,80,240,800,60,0,0,800,36,思源黑体,true,false,true,none,center,0123456789零壹贰叁肆伍陆柒捌玖,20,中文+数字,true,558.0,0.6975,no_slack,估算宽度 < 70% 可用宽度(0.70),明显有留白
|
||||
14,pmi,bax,80,320,800,120,0,0,800,20,思源黑体,false,false,true,none,center,"金额:¥12,345,678.90元百分比:99.99%增长率:+23.45%日期:2024-12-25时间:14:30:00",63,中文+数字,false,318.0,0.3975,no_slack,估算宽度 < 70% 可用宽度(0.40),明显有留白
|
||||
14,pmi,baD,80,460,800,30,0,0,800,12,思源黑体,false,false,true,none,center,阿拉伯数字·等宽比例·金额/百分比/日期格式测试,24,中文,true,266.4,0.333,no_slack,估算宽度 < 70% 可用宽度(0.33),明显有留白
|
||||
15,pmK,bag,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,15·数字测试·中文数字,12,中文+数字,true,285.6,0.34,no_slack,估算宽度 < 70% 可用宽度(0.34),明显有留白
|
||||
15,pmK,bba,80,120,800,80,0,0,800,48,思源黑体,true,false,true,none,center,小写:〇一二三四五六七八九十,14,中文,true,672,0.84,no,估算宽度 0.84x 可用宽度,大概率单行
|
||||
15,pmK,baX,80,210,800,80,0,0,800,48,思源黑体,true,false,true,none,center,大写:零壹贰叁肆伍陆柒捌玖拾,14,中文,true,672,0.84,no,估算宽度 0.84x 可用宽度,大概率单行
|
||||
15,pmK,baG,80,310,800,130,0,0,800,20,思源黑体,false,false,true,none,center,人民币壹佰贰拾叁万肆仟伍佰陆拾柒元捌角玖分二千零二十四年十二月二十五日第一百二十届第三季度百分之八十五,51,中文,false,420,0.525,no_slack,估算宽度 < 70% 可用宽度(0.53),明显有留白
|
||||
15,pmK,bbN,80,465,800,30,0,0,800,12,思源黑体,false,false,true,none,center,中文数字·大小写·财务/正式文书场景,18,中文,true,199.8,0.2497,no_slack,估算宽度 < 70% 可用宽度(0.25),明显有留白
|
||||
16,pmQ,bbc,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,16·数字测试·罗马数字,12,中文+数字,true,285.6,0.34,no_slack,估算宽度 < 70% 可用宽度(0.34),明显有留白
|
||||
16,pmQ,bbd,80,130,800,80,0,0,800,48,思源黑体,true,false,true,none,center,IIIIIIIVVVIVIIVIIIIXX,21,英文,true,554.4,0.693,no_slack,估算宽度 < 70% 可用宽度(0.69),明显有留白
|
||||
16,pmQ,bbB,80,225,800,60,0,0,800,36,思源黑体,true,false,true,none,center,XLLXCCCDDCMM,12,英文,true,237.6,0.297,no_slack,估算宽度 < 70% 可用宽度(0.30),明显有留白
|
||||
16,pmQ,bbv,80,300,800,140,0,0,800,20,思源黑体,false,false,true,none,center,ChapterXXIV·VolumeIII·EditionIXKingHenryVIII·PopeJohnPaulII第XXI届冬季奥林匹克运动会·第III季度报告MMXXIV年·MCMLXXI年,98,中文+英文,false,397.0,0.4963,no_slack,估算宽度 < 70% 可用宽度(0.50),明显有留白
|
||||
16,pmQ,bbb,80,465,800,30,0,0,800,12,思源黑体,false,false,true,none,center,罗马数字·古典风格·章节编号/正式命名场景,21,中文,true,235.8,0.2947,no_slack,估算宽度 < 70% 可用宽度(0.29),明显有留白
|
||||
17,pme,bbe,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,17·中英文混排测试,10,中文+数字,true,242.2,0.2883,no_slack,估算宽度 < 70% 可用宽度(0.29),明显有留白
|
||||
17,pme,bbz,80,120,800,60,0,0,800,32,思源黑体,true,false,true,none,center,人工智能ArtificialIntelligence技术,28,中文+英文,true,579.2,0.724,no,估算宽度 0.72x 可用宽度,大概率单行
|
||||
17,pme,bbR,80,200,800,200,0,0,800,18,思源黑体,false,false,true,none,,随着AI技术的快速发展,MachineLearning与DeepLearning已经渗透到各行各业。在NaturalLanguageProcessing领域,大语言模型LLM的出现彻底改变了人机交互方式。从ChatGPT到文心一言,从GPT-4到Claude3,AI助手正在成为人们工作生活的标配。2024年被称为AI应用元年,GenerativeAI创造了无限可能。,184,中文+英文+数字,false,709.2,0.8865,tight_single,估算宽度 0.89x 可用宽度,接近填满,需确认是否单行
|
||||
17,pme,bbp,80,420,800,60,0,0,800,16,思源黑体,false,false,true,none,center,"测试中英文之间的间距、基线对齐、字号协调等混排效果Testingspacing,baselinealignment,andsizeharmonybetweenCJKandLatinscripts",97,中文+英文,false,633.6,0.792,no,估算宽度 0.79x 可用宽度,大概率单行
|
||||
18,pmF,bbj,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,18·中英文数字混排测试,12,中文+数字,true,298.2,0.355,no_slack,估算宽度 < 70% 可用宽度(0.35),明显有留白
|
||||
18,pmF,bbu,80,115,800,55,0,0,800,28,思源黑体,true,false,true,none,,2024Q4季度业绩报告QuarterlyReport,27,中文+英文+数字,true,491.4,0.6142,no_slack,估算宽度 < 70% 可用宽度(0.61),明显有留白
|
||||
18,pmF,bbA,80,180,800,250,0,0,800,17,思源黑体,false,false,true,none,,"核心数据KeyMetrics:总营收Revenue:¥1,234.56万元,同比增长+23.45%用户数Users:567,890人,月活MAU达89%净利润NetProfit:¥234.56万元,利润率19.0%客户满意度CSAT:4.8/5.0分,NPS达72产品迭代Version:v3.2.1,发布于2024-12-15",164,中文+英文+数字,false,402.05,0.5026,no_slack,估算宽度 < 70% 可用宽度(0.50),明显有留白
|
||||
18,pmF,bbW,80,450,800,40,0,0,800,14,思源黑体,false,false,true,none,center,中文+英文+数字+符号+列表·综合混排测试,21,中文,true,262.5,0.3281,no_slack,估算宽度 < 70% 可用宽度(0.33),明显有留白
|
||||
19,pmz,bbD,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,19·字重测试·粗体Bold,14,中文+英文+数字,true,291.2,0.3467,no_slack,估算宽度 < 70% 可用宽度(0.35),明显有留白
|
||||
19,pmz,bbs,80,120,380,60,0,0,380,32,思源黑体,true,false,true,none,,常规Regular,9,中文+英文,true,187.2,0.4926,no_slack,估算宽度 < 70% 可用宽度(0.49),明显有留白
|
||||
19,pmz,bbF,500,120,380,60,0,0,380,32,思源黑体,true,false,true,none,,粗体Bold,6,中文+英文,true,134.4,0.3537,no_slack,估算宽度 < 70% 可用宽度(0.35),明显有留白
|
||||
19,pmz,bbK,80,200,380,60,0,0,380,32,思源黑体,true,false,true,none,,正常字重400,7,中文+数字,true,180.8,0.4758,no_slack,估算宽度 < 70% 可用宽度(0.48),明显有留白
|
||||
19,pmz,bbI,500,200,380,60,0,0,380,32,思源黑体,true,false,true,none,,加粗字重700,7,中文+数字,true,180.8,0.4758,no_slack,估算宽度 < 70% 可用宽度(0.48),明显有留白
|
||||
19,pmz,bbY,80,290,800,150,0,0,800,18,思源黑体,false,false,true,none,,"在正文中,粗体文字用于强调关键信息,引导读者视线。Inbodytext,boldtexthighlightskeyinformationandguidesthereader.数字加粗:1234567890对比常规:1234567890",117,中文+英文+数字,false,613.8,0.7672,no,估算宽度 0.77x 可用宽度,大概率单行
|
||||
19,pmz,bbU,80,460,800,30,0,0,800,12,思源黑体,false,false,true,none,center,粗体vs常规·字重对比·强调效果测试,18,中文+英文,true,194.4,0.243,no_slack,估算宽度 < 70% 可用宽度(0.24),明显有留白
|
||||
20,pmd,bbE,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,20·字体样式测试·斜体Italic,18,中文+英文+数字,true,378.0,0.45,no_slack,估算宽度 < 70% 可用宽度(0.45),明显有留白
|
||||
20,pmd,bbS,80,120,380,60,0,0,380,32,思源黑体,true,false,true,none,,正体Upright,9,中文+英文,true,187.2,0.4926,no_slack,估算宽度 < 70% 可用宽度(0.49),明显有留白
|
||||
20,pmd,bbk,500,120,380,60,0,0,380,32,思源黑体,true,true,true,none,,斜体Italic,8,中文+英文,true,169.6,0.4463,no_slack,估算宽度 < 70% 可用宽度(0.45),明显有留白
|
||||
20,pmd,bbO,80,200,380,60,0,0,380,32,思源黑体,true,false,true,none,,NormalText,10,英文,true,176.0,0.4632,no_slack,估算宽度 < 70% 可用宽度(0.46),明显有留白
|
||||
20,pmd,bbf,500,200,380,60,0,0,380,32,思源黑体,true,true,true,none,,ItalicText,10,英文,true,176.0,0.4632,no_slack,估算宽度 < 70% 可用宽度(0.46),明显有留白
|
||||
20,pmd,bbJ,80,290,800,150,0,0,800,18,思源黑体,false,false,true,none,,"斜体常用于引用、书名、外来词等场景,增添文字的韵律感。Italictextisoftenusedforquotes,booktitles,andforeignwords.数字斜体:1234567890对比正体:1234567890",115,中文+英文+数字,false,574.2,0.7177,no,估算宽度 0.72x 可用宽度,大概率单行
|
||||
20,pmd,bbn,80,460,800,30,0,0,800,12,思源黑体,false,false,true,none,center,斜体vs正体·引用/书名/强调场景,17,中文+英文,true,177.0,0.2213,no_slack,估算宽度 < 70% 可用宽度(0.22),明显有留白
|
||||
21,pmG,bbM,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,21·文字装饰测试·下划线/删除线,17,中文+数字,true,413.0,0.4917,no_slack,估算宽度 < 70% 可用宽度(0.49),明显有留白
|
||||
21,pmG,bbq,80,120,800,60,0,0,800,32,思源黑体,true,false,true,none,center,下划线文字UnderlineText,18,中文+英文,true,388.8,0.486,no_slack,估算宽度 < 70% 可用宽度(0.49),明显有留白
|
||||
21,pmG,bbi,80,200,800,60,0,0,800,32,思源黑体,true,false,true,none,center,删除线文字StrikethroughText,22,中文+英文,true,459.2,0.574,no_slack,估算宽度 < 70% 可用宽度(0.57),明显有留白
|
||||
21,pmG,bbT,80,290,800,150,0,0,800,18,思源黑体,false,false,true,none,,"下划线常用于链接、重点标注等场景,删除线用于表示已删除或作废的内容。Underlineisusedforlinksandkeyannotations,strikethroughfordeletedcontent.组合效果:加粗加下划线·斜体加下划线",124,中文+英文,false,712.8,0.891,tight_single,估算宽度 0.89x 可用宽度,接近填满,需确认是否单行
|
||||
21,pmG,bbC,80,460,800,30,0,0,800,12,思源黑体,false,false,true,none,center,下划线·删除线·组合样式测试,14,中文,true,157.2,0.1965,no_slack,估算宽度 < 70% 可用宽度(0.20),明显有留白
|
||||
22,pmq,bZa,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,22·字间距测试LetterSpacing,21,中文+英文+数字,true,386.4,0.46,no_slack,估算宽度 < 70% 可用宽度(0.46),明显有留白
|
||||
22,pmq,bbg,80,120,800,50,0,0,800,28,思源黑体,true,false,true,none,center,紧凑字间距-2pxTight,14,中文+英文+数字,true,278.6,0.3483,no_slack,估算宽度 < 70% 可用宽度(0.35),明显有留白
|
||||
22,pmq,bbG,80,185,800,50,0,0,800,28,思源黑体,true,false,true,none,center,正常字间距0pxNormal,14,中文+英文+数字,true,278.6,0.3483,no_slack,估算宽度 < 70% 可用宽度(0.35),明显有留白
|
||||
22,pmq,bZN,80,250,800,50,0,0,800,28,思源黑体,true,false,true,none,center,宽松字间距3pxLoose,13,中文+英文+数字,true,263.2,0.329,no_slack,估算宽度 < 70% 可用宽度(0.33),明显有留白
|
||||
22,pmq,bZb,80,315,800,50,0,0,800,28,思源黑体,true,false,true,none,center,超宽字间距8pxWide,12,中文+英文+数字,true,247.8,0.3098,no_slack,估算宽度 < 70% 可用宽度(0.31),明显有留白
|
||||
22,pmq,bbl,80,390,800,80,0,0,800,16,思源黑体,false,false,true,none,center,字间距影响文字的呼吸感和阅读节奏标题常用宽松字间距营造高级感,正文用正常字间距保证可读性,44,中文,false,448,0.56,no_slack,估算宽度 < 70% 可用宽度(0.56),明显有留白
|
||||
22,pmq,bbX,80,485,800,25,0,0,800,11,思源黑体,false,false,true,none,center,从-2px到8px·四级字间距对比,17,中文+英文+数字,true,147.4,0.1842,no_slack,估算宽度 < 70% 可用宽度(0.18),明显有留白
|
||||
23,pmS,bZB,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,23·行间距测试LineSpacing,19,中文+英文+数字,true,355.6,0.4233,no_slack,估算宽度 < 70% 可用宽度(0.42),明显有留白
|
||||
23,pmS,bZZ,60,115,270,340,0,0,270,14,思源黑体,false,false,true,none,,1.0倍行距这是一段测试文字,用于展示1.0倍行间距的效果。行间距较小时,文字显得紧凑,但可能影响可读性。紧凑排版适合空间有限的场景。,67,中文+数字,false,639.1,2.367,yes,估算宽度 2.37x 可用宽度,肯定换行(多行)
|
||||
23,pmS,bZo,345,115,270,340,0,0,270,14,思源黑体,false,false,true,none,,1.5倍行距这是一段测试文字,用于展示1.5倍行间距的效果。这是最常用的行间距设置,兼顾可读性和信息密度。适合大多数正文排版场景。,65,中文+数字,false,639.1,2.367,yes,估算宽度 2.37x 可用宽度,肯定换行(多行)
|
||||
23,pmS,bZc,630,115,270,340,0,0,270,14,思源黑体,false,false,true,none,,2.0倍行距这是一段测试文字,用于展示2.0倍行间距的效果。行间距较大时,文字显得疏朗透气,阅读体验轻松。适合需要留白感的设计。,64,中文+数字,false,322,1.1926,borderline,估算宽度 1.19x 可用宽度,边界情况,需截图确认是否换行
|
||||
23,pmS,bZH,60,475,840,30,0,0,840,12,思源黑体,false,false,true,none,center,1.0/1.5/2.0倍行距对比·三列并排展示,23,中文+数字,true,211.2,0.2514,no_slack,估算宽度 < 70% 可用宽度(0.25),明显有留白
|
||||
24,pmM,bZR,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,24·对齐方式测试Alignment,18,中文+英文+数字,true,352.8,0.42,no_slack,估算宽度 < 70% 可用宽度(0.42),明显有留白
|
||||
24,pmM,bZu,80,115,800,50,0,0,800,24,思源黑体,true,false,true,none,left,←左对齐LeftAlign,13,中文+英文,true,204.0,0.255,no_slack,估算宽度 < 70% 可用宽度(0.25),明显有留白
|
||||
24,pmM,bZe,80,180,800,50,0,0,800,24,思源黑体,true,false,true,none,center,居中对齐CenterAlign,15,中文+英文,true,241.2,0.3015,no_slack,估算宽度 < 70% 可用宽度(0.30),明显有留白
|
||||
24,pmM,bZz,80,245,800,50,0,0,800,24,思源黑体,true,false,true,none,right,右对齐RightAlign→,14,中文+英文,true,217.2,0.2715,no_slack,估算宽度 < 70% 可用宽度(0.27),明显有留白
|
||||
24,pmM,bZp,80,310,800,120,0,0,800,16,思源黑体,false,false,true,none,justify,两端对齐Justify:这是一段用于测试两端对齐效果的较长文字,文字的左右两边都会对齐,形成整齐的文字块边缘,适合报纸、杂志等正式排版。Thequickbrownfoxjumpsoverthelazydog.Typographyistheartandtechniqueofarrangingtype.,150,中文+英文,true,1759.2,2.199,yes,估算宽度 2.20x 可用宽度,肯定换行(多行)
|
||||
24,pmM,bZV,80,455,800,30,0,0,800,12,思源黑体,false,false,true,none,center,左对齐·居中·右对齐·两端对齐·四种对齐方式,22,中文,true,242.4,0.303,no_slack,估算宽度 < 70% 可用宽度(0.30),明显有留白
|
||||
25,pmP,bZt,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,25·文字颜色测试Colors,15,中文+英文+数字,true,306.6,0.365,no_slack,估算宽度 < 70% 可用宽度(0.36),明显有留白
|
||||
25,pmP,bZk,80,115,800,45,0,0,800,26,思源黑体,true,false,true,none,center,深蓝DarkBlue·主色Primary,20,中文+英文,true,332.8,0.416,no_slack,估算宽度 < 70% 可用宽度(0.42),明显有留白
|
||||
25,pmP,bZO,80,170,800,45,0,0,800,26,思源黑体,true,false,true,none,center,亮蓝BrightBlue·辅色Secondary,24,中文+英文,true,390.0,0.4875,no_slack,估算宽度 < 70% 可用宽度(0.49),明显有留白
|
||||
25,pmP,bZA,80,225,800,45,0,0,800,26,思源黑体,true,false,true,none,center,青绿Teal·成功色Success,17,中文+英文,true,301.6,0.377,no_slack,估算宽度 < 70% 可用宽度(0.38),明显有留白
|
||||
25,pmP,bZJ,80,280,800,45,0,0,800,26,思源黑体,true,false,true,none,center,琥珀Amber·警告色Warning,18,中文+英文,true,315.9,0.3949,no_slack,估算宽度 < 70% 可用宽度(0.39),明显有留白
|
||||
25,pmP,bZn,80,335,800,45,0,0,800,26,思源黑体,true,false,true,none,center,红色Red·危险色Danger,15,中文+英文,true,273.0,0.3413,no_slack,估算宽度 < 70% 可用宽度(0.34),明显有留白
|
||||
25,pmP,bZS,80,390,800,45,0,0,800,26,思源黑体,true,false,true,none,center,灰色Gray·辅助色Neutral,17,中文+英文,true,301.6,0.377,no_slack,估算宽度 < 70% 可用宽度(0.38),明显有留白
|
||||
25,pmP,bZy,80,455,800,30,0,0,800,12,思源黑体,false,false,true,none,center,六色系统·主/辅/成功/警告/危险/中性·色彩对比测试,27,中文,true,286.2,0.3577,no_slack,估算宽度 < 70% 可用宽度(0.36),明显有留白
|
||||
26,pmh,bZU,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,26·特殊符号测试Symbols,16,中文+英文+数字,true,322.0,0.3833,no_slack,估算宽度 < 70% 可用宽度(0.38),明显有留白
|
||||
26,pmh,bZD,80,115,800,50,0,0,800,28,思源黑体,true,false,true,none,center,"标点符号:,。!?:;""""''()【】",29,中文+英文,true,635.6,0.7945,no,估算宽度 0.79x 可用宽度,大概率单行
|
||||
26,pmh,bZE,80,180,800,50,0,0,800,28,思源黑体,true,false,true,none,center,数学符号:+-×÷=≈≠≤≥±%,16,中文,true,347.2,0.434,no_slack,估算宽度 < 70% 可用宽度(0.43),明显有留白
|
||||
26,pmh,bZF,80,245,800,50,0,0,800,28,思源黑体,true,false,true,none,center,货币符号:¥$€£₩₹¢,12,中文,true,247.8,0.3098,no_slack,估算宽度 < 70% 可用宽度(0.31),明显有留白
|
||||
26,pmh,bZK,80,310,800,50,0,0,800,28,思源黑体,true,false,true,none,center,单位符号:℃℉°‰㎡kgms,14,中文+英文,true,291.2,0.364,no_slack,估算宽度 < 70% 可用宽度(0.36),明显有留白
|
||||
26,pmh,bZI,80,375,800,50,0,0,800,28,思源黑体,true,false,true,none,center,其他符号:@#&*§¶©®™,14,中文,true,278.6,0.3483,no_slack,估算宽度 < 70% 可用宽度(0.35),明显有留白
|
||||
26,pmh,bZP,80,450,800,30,0,0,800,12,思源黑体,false,false,true,none,center,标点·数学·货币·单位·特殊符号·全面测试,21,中文,true,225.0,0.2812,no_slack,估算宽度 < 70% 可用宽度(0.28),明显有留白
|
||||
27,pml,bZY,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,27·段落与列表测试Lists,15,中文+英文+数字,true,319.2,0.38,no_slack,估算宽度 < 70% 可用宽度(0.38),明显有留白
|
||||
27,pml,bZq,80,115,380,340,0,0,380,16,思源黑体,false,false,true,none,,无序列表UnorderedList第一项ItemOne第二项ItemTwo第三项ItemThree第四项ItemFour第五项ItemFive,71,中文+英文,false,178.4,0.4695,no_slack,估算宽度 < 70% 可用宽度(0.47),明显有留白
|
||||
27,pml,bZi,500,115,380,340,0,0,380,16,思源黑体,false,false,true,none,,有序列表OrderedList第一步StepOne第二步StepTwo第三步StepThree第四步StepFour第五步StepFive,69,中文+英文,false,160.8,0.4232,no_slack,估算宽度 < 70% 可用宽度(0.42),明显有留白
|
||||
27,pml,bZr,80,470,800,30,0,0,800,12,思源黑体,false,false,true,none,center,无序列表·有序列表·中英文列表项对比,18,中文,true,205.2,0.2565,no_slack,估算宽度 < 70% 可用宽度(0.26),明显有留白
|
||||
28,pmB,bZl,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,28·综合排版测试Layout,15,中文+英文+数字,true,306.6,0.365,no_slack,估算宽度 < 70% 可用宽度(0.36),明显有留白
|
||||
28,pmB,bZG,60,110,420,50,0,0,420,26,思源黑体,true,false,true,none,,左栏标题LeftColumn,14,中文+英文,true,247.0,0.5881,no_slack,估算宽度 < 70% 可用宽度(0.59),明显有留白
|
||||
28,pmB,bZM,500,110,400,50,0,0,400,26,思源黑体,true,false,true,none,,右栏标题RightColumn,15,中文+英文,true,261.3,0.6533,no_slack,估算宽度 < 70% 可用宽度(0.65),明显有留白
|
||||
28,pmB,bZm,60,170,420,280,0,0,420,15,思源黑体,false,false,true,none,,"这是左栏的正文内容,使用15pt字号,1.7倍行间距。多栏排版可以有效利用页面空间,提升信息密度。要点一:重点信息加粗显示要点二:引用内容斜体标注要点三:关键数据下划线强调数字测试:123,456,789·百分比:99.9%",112,中文+英文+数字,false,357.75,0.8518,tight_single,估算宽度 0.85x 可用宽度,接近填满,需确认是否单行
|
||||
28,pmB,bZQ,500,170,400,280,0,0,400,15,思源黑体,false,false,true,none,,"Thisistherightcolumnbodytextin15ptsize.Multi-columnlayoutoptimizesspaceandinformationdensity.Point1:BoldforemphasisPoint2:ItalicforquotesPoint3:UnderlineforkeydataNumbers:123,456,789·Percent:99.9%",196,英文+数字,false,445.5,1.1138,borderline,估算宽度 1.11x 可用宽度,边界情况,需截图确认是否换行
|
||||
28,pmB,bZg,60,470,840,30,0,0,840,12,思源黑体,false,false,true,none,center,双栏布局·中英对照·列表+强调+数字·综合测试,23,中文,true,249.0,0.2964,no_slack,估算宽度 < 70% 可用宽度(0.30),明显有留白
|
||||
29,pmU,bdZ,60,40,840,50,0,0,840,28,思源黑体,true,false,true,none,,29·字符密度测试Density,16,中文+英文+数字,true,322.0,0.3833,no_slack,估算宽度 < 70% 可用宽度(0.38),明显有留白
|
||||
29,pmU,bdN,60,110,840,360,0,0,840,13,思源黑体,false,false,true,none,,"高密度文字排版测试·小字号大信息量场景排版设计(Typography)是一门关于文字排列和视觉呈现的艺术与技术。它涉及字体选择、字号大小、行间距、字间距、对齐方式、颜色搭配等多个方面。好的排版设计不仅能让文字更易读,还能传递情感和品牌调性。Typographyistheartandtechniqueofarrangingtypetomakewrittenlanguagelegible,readable,andappealingwhendisplayed.Thearrangementoftypeinvolvesselectingtypefaces,pointsizes,linelengths,line-spacing,andletter-spacing,andadjustingthespacebetweenpairsofletters.中文排版与英文排版有许多不同之处:中文是方块字,每个字符宽度基本一致;英文是比例字体,字符宽度各不相同。中文排版需要考虑标点符号的位置、中英文混排的间距等问题。数字0123456789在中英文混排时也需要特别注意基线对齐和宽度协调。数据:2024年全球字体市场规模达$12.34亿美元,年增长率8.5%。Top5字体:Helvetica,Arial,TimesNewRoman,Calibri,Garamond。",578,中文+英文+数字,false,1801.8,2.145,yes,估算宽度 2.15x 可用宽度,肯定换行(多行)
|
||||
29,pmU,bda,60,485,840,25,0,0,840,11,思源黑体,false,false,true,none,center,13pt高密度·多段落·中英文数字混排·信息密度测试,26,中文+英文+数字,true,251.35,0.2992,no_slack,估算宽度 < 70% 可用宽度(0.30),明显有留白
|
||||
30,pmv,bdB,80,180,800,100,0,0,800,56,思源黑体,true,false,true,none,center,测试样张·完,6,中文,true,310.8,0.3885,no_slack,估算宽度 < 70% 可用宽度(0.39),明显有留白
|
||||
30,pmv,bdc,80,300,800,50,0,0,800,22,思源黑体,true,false,true,none,center,EndofTypographyTestSample,25,英文,true,302.5,0.3781,no_slack,估算宽度 < 70% 可用宽度(0.38),明显有留白
|
||||
30,pmv,bdd,80,380,800,80,0,0,800,14,思源黑体,false,false,true,none,center,共30页·涵盖字号/字体/中英文/数字/样式/间距/颜色/排版30Pages·Size/Font/CJK&Latin/Numbers/Style/Spacing/Color/Layout,93,中文+英文+数字,false,477.4,0.5967,no_slack,估算宽度 < 70% 可用宽度(0.60),明显有留白
|
||||
|
@@ -1,115 +0,0 @@
|
||||
# 字符宽度测量数据报告
|
||||
|
||||
**Presentation**: BUFBsLX2ZlzyMTdprLicd7rpneg
|
||||
**总样本数**: 158
|
||||
**关键测量样本(比值≥0.85)**: 12
|
||||
- 接近填满(0.85-1.0): 4
|
||||
- 边界情况(1.0-1.2): 2
|
||||
- 大概率换行(1.2-1.5): 2
|
||||
- 肯定换行(>1.5): 4
|
||||
|
||||
## 按文本类型统计(所有样本)
|
||||
|
||||
| 文本类型 | 样本数 | 平均估算/可用比 | 最小比值 | 最大比值 |
|
||||
|----------|--------|------------------|----------|----------|
|
||||
| 中文 | 24 | 0.4099 | 0.1965 | 0.8400 |
|
||||
| 中文+数字 | 21 | 0.6316 | 0.2514 | 2.3670 |
|
||||
| 中文+英文 | 38 | 0.5405 | 0.2213 | 2.1990 |
|
||||
| 中文+英文+数字 | 56 | 0.4992 | 0.1842 | 2.1450 |
|
||||
| 数字 | 3 | 0.4217 | 0.3850 | 0.4950 |
|
||||
| 英文 | 14 | 0.4805 | 0.2970 | 0.6930 |
|
||||
| 英文+数字 | 2 | 0.7879 | 0.4620 | 1.1138 |
|
||||
|
||||
## 接近填满样本(估算比值 0.85-1.0,最适合校准单行宽度)
|
||||
|
||||
| 页码 | 元素ID | 字体 | 字号 | Bold | 文本类型 | 硬换行 | 文本 | 估算宽度 | 可用宽度 | 比值 |
|
||||
|------|--------|------|------|------|----------|--------|------|----------|----------|------|
|
||||
| 8 | bNm | 思源黑体 | 10 | false | 中文+英文+数字 | false | 极小字号测试10pt—用于脚注、注释、数据来源说明等本页测试10pt极小字号的可读性。在正式演示中,... | 764.5 | 800 | 0.9556 |
|
||||
| 17 | bbR | 思源黑体 | 18 | false | 中文+英文+数字 | false | 随着AI技术的快速发展,MachineLearning与DeepLearning已经渗透到各行各业。... | 709.2 | 800 | 0.8865 |
|
||||
| 21 | bbT | 思源黑体 | 18 | false | 中文+英文 | false | 下划线常用于链接、重点标注等场景,删除线用于表示已删除或作废的内容。Underlineisusedf... | 712.8 | 800 | 0.891 |
|
||||
| 28 | bZm | 思源黑体 | 15 | false | 中文+英文+数字 | false | 这是左栏的正文内容,使用15pt字号,1.7倍行间距。多栏排版可以有效利用页面空间,提升信息密度。要... | 357.75 | 420 | 0.8518 |
|
||||
|
||||
## 边界换行样本(估算比值 1.0-1.2,需截图确认是否换行)
|
||||
|
||||
| 页码 | 元素ID | 字体 | 字号 | Bold | 文本类型 | 硬换行 | 文本 | 估算宽度 | 可用宽度 | 比值 |
|
||||
|------|--------|------|------|------|----------|--------|------|----------|----------|------|
|
||||
| 23 | bZc | 思源黑体 | 14 | false | 中文+数字 | false | 2.0倍行距这是一段测试文字,用于展示2.0倍行间距的效果。行间距较大时,文字显得疏朗透气,阅读体验... | 322 | 270 | 1.1926 |
|
||||
| 28 | bZQ | 思源黑体 | 15 | false | 英文+数字 | false | Thisistherightcolumnbodytextin15ptsize.Multi-colum... | 445.5 | 400 | 1.1138 |
|
||||
|
||||
## 大概率换行样本(估算比值 1.2-1.5)
|
||||
|
||||
| 页码 | 元素ID | 字体 | 字号 | Bold | 文本类型 | 硬换行 | 文本 | 估算宽度 | 可用宽度 | 比值 |
|
||||
|------|--------|------|------|------|----------|--------|------|----------|----------|------|
|
||||
| 6 | bNf | 思源黑体 | 18 | false | 中文+英文+数字 | false | 这是一段正文测试文字,使用18pt字号,是幻灯片中最常用的正文字号。Typographyisthea... | 1079.1 | 800 | 1.3489 |
|
||||
| 7 | bNU | 思源黑体 | 14 | false | 中文+英文+数字 | false | 小号正文测试14pt这是一段使用14pt字号的正文文字,适合用于内容较多的页面或说明性文字。在信息密... | 1070.3 | 800 | 1.3379 |
|
||||
|
||||
## 肯定换行样本(估算比值 > 1.5)
|
||||
|
||||
| 页码 | 元素ID | 字体 | 字号 | Bold | 文本类型 | 硬换行 | 文本 | 估算宽度 | 可用宽度 | 比值 |
|
||||
|------|--------|------|------|------|----------|--------|------|----------|----------|------|
|
||||
| 23 | bZZ | 思源黑体 | 14 | false | 中文+数字 | false | 1.0倍行距这是一段测试文字,用于展示1.0倍行间距的效果。行间距较小时,文字显得紧凑,但可能影响可... | 639.1 | 270 | 2.367 |
|
||||
| 23 | bZo | 思源黑体 | 14 | false | 中文+数字 | false | 1.5倍行距这是一段测试文字,用于展示1.5倍行间距的效果。这是最常用的行间距设置,兼顾可读性和信息... | 639.1 | 270 | 2.367 |
|
||||
| 24 | bZp | 思源黑体 | 16 | false | 中文+英文 | true | 两端对齐Justify:这是一段用于测试两端对齐效果的较长文字,文字的左右两边都会对齐,形成整齐的文... | 1759.2 | 800 | 2.199 |
|
||||
| 29 | bdN | 思源黑体 | 13 | false | 中文+英文+数字 | false | 高密度文字排版测试·小字号大信息量场景排版设计(Typography)是一门关于文字排列和视觉呈现的... | 1801.8 | 840 | 2.145 |
|
||||
|
||||
## 字体统计
|
||||
|
||||
| 字体 | 样本数 |
|
||||
|------|--------|
|
||||
| 思源黑体 | 145 |
|
||||
| Helvetica | 5 |
|
||||
| Georgia | 5 |
|
||||
| 思源宋体 | 3 |
|
||||
|
||||
## 字号+粗体统计
|
||||
|
||||
| 字号 | Bold | 样本数 | 平均比值 |
|
||||
|------|------|--------|----------|
|
||||
| 10 | false | 2 | 0.6018 |
|
||||
| 11 | false | 3 | 0.2335 |
|
||||
| 12 | false | 17 | 0.2784 |
|
||||
| 13 | false | 2 | 1.2393 |
|
||||
| 14 | false | 12 | 0.8339 |
|
||||
| 15 | false | 2 | 0.9828 |
|
||||
| 16 | false | 8 | 0.7480 |
|
||||
| 17 | false | 1 | 0.5026 |
|
||||
| 18 | false | 8 | 0.7501 |
|
||||
| 20 | false | 3 | 0.4729 |
|
||||
| 22 | true | 1 | 0.3781 |
|
||||
| 24 | false | 1 | 0.2858 |
|
||||
| 24 | true | 4 | 0.3225 |
|
||||
| 26 | true | 8 | 0.4544 |
|
||||
| 28 | true | 49 | 0.4030 |
|
||||
| 32 | true | 14 | 0.4931 |
|
||||
| 36 | true | 6 | 0.5820 |
|
||||
| 44 | true | 1 | 0.5558 |
|
||||
| 48 | true | 8 | 0.6644 |
|
||||
| 52 | true | 2 | 0.4469 |
|
||||
| 56 | true | 1 | 0.3885 |
|
||||
| 60 | true | 1 | 0.7066 |
|
||||
| 64 | true | 1 | 0.6400 |
|
||||
| 72 | true | 3 | 0.6450 |
|
||||
|
||||
## 说明
|
||||
|
||||
- **估算宽度**: 使用当前 `estimate_character_width` 函数计算(中文=1em,西文=0.55em,空格=0.33em)
|
||||
- **可用宽度**: shape.width - paddingLeft - paddingRight
|
||||
- **width_ratio**: 估算宽度 / 可用宽度(针对最长硬换行段落计算)
|
||||
- **硬换行**: 文本中是否包含显式 \n 分段
|
||||
- 截图保存在: /Users/bytedance/go/src/github.com/larksuite/cli/skills/lark-slides/scripts/.lark-slides/screenshots
|
||||
|
||||
### 比值解读建议
|
||||
- ratio < 0.7: 明显留白,估算宽度可能偏宽,或文本确实很短
|
||||
- 0.7-0.85: 大概率单行,有少量留白
|
||||
- 0.85-1.0: 接近填满,是校准西文/中文字符宽度系数的最佳样本
|
||||
- 1.0-1.2: 边界情况,需要看截图确认:是刚好填满单行还是换行了
|
||||
- 1.2-1.5: 大概率换2行
|
||||
- >1.5: 肯定换行(多行文本)
|
||||
|
||||
### 校准建议
|
||||
1. 先看 ratio 0.85-1.0 的样本:如果截图中这些文本**确实单行且接近填满**,说明当前估算大致准确;如果有较多留白,说明估算偏宽,需要减小西文字符系数
|
||||
2. 再看 ratio 1.0-1.2 的样本:结合截图判断实际是单行还是换行,反推合理系数
|
||||
3. 重点关注纯英文、纯数字、纯中文、中英混合这几类分别统计
|
||||
4. Bold 字体通常比常规字体稍宽,Italic 稍窄,需要分别考虑
|
||||
|
||||
请人工核对截图确认边界样本的实际换行情况,用于校准字符宽度系数。
|
||||
@@ -935,96 +935,6 @@ def estimate_text_width(
|
||||
return base + max(len(text) - 1, 0) * letter_spacing
|
||||
|
||||
|
||||
def is_cjk_char(character: str) -> bool:
|
||||
"""CJK-like characters may wrap between any two adjacent glyphs.
|
||||
|
||||
Mirrors the isCJKLike ranges used by ee/slide text-measure-module so that
|
||||
line-count estimation matches DOM/Skia wrapping: CJK breaks per glyph while
|
||||
latin words stay atomic.
|
||||
"""
|
||||
code = ord(character)
|
||||
return (
|
||||
0x2E80 <= code <= 0x9FFF
|
||||
or 0x3000 <= code <= 0xD7AF
|
||||
or 0xF900 <= code <= 0xFAFF
|
||||
or 0xFE30 <= code <= 0xFE4F
|
||||
or 0xFF01 <= code <= 0xFF60
|
||||
or 0xFFE0 <= code <= 0xFFE6
|
||||
)
|
||||
|
||||
|
||||
def tokenize_for_wrap(text: str) -> list[tuple[str, str]]:
|
||||
"""Split a hard line into wrap tokens: latin words are atomic, CJK glyphs
|
||||
are individually breakable, whitespace runs are collapse points."""
|
||||
tokens: list[tuple[str, str]] = []
|
||||
index = 0
|
||||
length = len(text)
|
||||
while index < length:
|
||||
character = text[index]
|
||||
if character.isspace():
|
||||
start = index
|
||||
while index < length and text[index].isspace():
|
||||
index += 1
|
||||
tokens.append(("space", text[start:index]))
|
||||
elif is_cjk_char(character):
|
||||
tokens.append(("cjk", character))
|
||||
index += 1
|
||||
else:
|
||||
start = index
|
||||
while index < length and not text[index].isspace() and not is_cjk_char(text[index]):
|
||||
index += 1
|
||||
tokens.append(("word", text[start:index]))
|
||||
return tokens
|
||||
|
||||
|
||||
def count_wrapped_lines(
|
||||
text: str,
|
||||
font_size: int | float,
|
||||
letter_spacing: int | float,
|
||||
bold: bool,
|
||||
font_family: str | None,
|
||||
available_width: int | float,
|
||||
) -> int:
|
||||
"""Greedy word-aware wrapped line count.
|
||||
|
||||
Unlike ceil(width / available), latin words are never split mid-word (unless
|
||||
a single word is wider than the line, in which case it breaks like DOM
|
||||
overflow-wrap:break-word). This avoids under-counting lines for word-heavy
|
||||
text and matches how ee/slide reconciles Skia wrapping with the DOM.
|
||||
"""
|
||||
tokens = tokenize_for_wrap(text)
|
||||
if not tokens:
|
||||
return 1
|
||||
lines = 1
|
||||
current = 0.0
|
||||
|
||||
def token_width(token: str) -> int | float:
|
||||
return estimate_text_width(token, font_size, letter_spacing, bold, font_family)
|
||||
|
||||
for kind, token in tokens:
|
||||
width = token_width(token)
|
||||
# letter-spacing applies at every glyph boundary, including the seam
|
||||
# between two tokens on the same line; add it back so a packed line
|
||||
# matches estimate_text_width of the concatenated run.
|
||||
junction = letter_spacing if current > 0 else 0
|
||||
if kind == "space":
|
||||
if current > 0:
|
||||
current += junction + width
|
||||
continue
|
||||
if current > 0 and current + junction + width <= available_width:
|
||||
current += junction + width
|
||||
continue
|
||||
if current > 0:
|
||||
lines += 1
|
||||
if kind == "word" and width > available_width:
|
||||
extra = math.ceil(width / available_width) - 1
|
||||
lines += extra
|
||||
current = width - extra * available_width
|
||||
else:
|
||||
current = width
|
||||
return lines
|
||||
|
||||
|
||||
def resolve_letter_spacing(element: dict[str, Any], paragraph: dict[str, Any] | None = None) -> int | float:
|
||||
if paragraph is not None:
|
||||
value = paragraph.get("letterSpacing")
|
||||
@@ -1057,11 +967,6 @@ def is_short_metric_text(text: str) -> bool:
|
||||
return re.fullmatch(r"[+\-–—]?[0-9A-Za-z,.,/%%\-–—\u4e00-\u9fff]+", compact) is not None
|
||||
|
||||
|
||||
def is_labeled_short_metric_text(text: str) -> bool:
|
||||
"""Return whether a short metric contains a separate label before its value."""
|
||||
return is_short_metric_text(text) and re.search(r"[A-Za-z]+\s+[+\-–—]?\d", text) is not None
|
||||
|
||||
|
||||
def is_single_line_visual_candidate(
|
||||
element: dict[str, Any],
|
||||
paragraph: dict[str, Any] | None,
|
||||
@@ -1071,7 +976,7 @@ def is_single_line_visual_candidate(
|
||||
) -> bool:
|
||||
if "\n" in text or logical_width <= effective_width:
|
||||
return False
|
||||
if is_short_metric_text(text) and not is_labeled_short_metric_text(text):
|
||||
if is_short_metric_text(text):
|
||||
return logical_width <= effective_width * SINGLE_LINE_METRIC_WIDTH_RATIO
|
||||
|
||||
text_align = (paragraph or {}).get("textAlign") or element.get("textAlign")
|
||||
@@ -1090,12 +995,7 @@ def estimate_text_max_line_width(element: dict[str, Any]) -> int | float:
|
||||
bold = element.get("bold", False)
|
||||
font_family = element.get("fontFamily", "")
|
||||
letter_spacing = resolve_letter_spacing(element)
|
||||
# Visual width ignores trailing whitespace: like Skia (which trims line-end
|
||||
# spaces), a run's rightmost visible glyph bounds the box. Counting trailing
|
||||
# spaces inflates the right edge and manufactures overlap false positives.
|
||||
paragraphs = [
|
||||
stripped for paragraph in re.split(r"\n+", element["text"]) if (stripped := paragraph.rstrip())
|
||||
]
|
||||
paragraphs = [paragraph for paragraph in re.split(r"\n+", element["text"]) if paragraph]
|
||||
return max(
|
||||
[estimate_text_width(paragraph, font_size, letter_spacing, bold, font_family) for paragraph in paragraphs]
|
||||
or [1]
|
||||
@@ -1133,9 +1033,7 @@ def estimate_text_line_count_for_text(
|
||||
if is_single_line_visual_candidate(element, paragraph, hard_line, logical_width, effective_width):
|
||||
line_count += 1
|
||||
continue
|
||||
line_count += count_wrapped_lines(
|
||||
hard_line, font_size, letter_spacing, bold, font_family, effective_width
|
||||
)
|
||||
line_count += max(1, math.ceil(logical_width / effective_width))
|
||||
return line_count
|
||||
|
||||
|
||||
|
||||
@@ -814,28 +814,6 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
|
||||
]
|
||||
self.assertEqual(overflow_issues, [])
|
||||
|
||||
def test_lint_xml_reports_labeled_short_metric_when_it_wraps(self) -> None:
|
||||
result = xml_text_overlap_lint.lint_xml(
|
||||
"""
|
||||
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
||||
<data>
|
||||
<shape id="sheet-success" type="text" topLeftX="520" topLeftY="385" width="180" height="50">
|
||||
<content textType="headline" fontSize="32" bold="true" autoFit="no-auto-fit">
|
||||
<p>Sheet 98.5%</p>
|
||||
</content>
|
||||
</shape>
|
||||
</data>
|
||||
</slide>
|
||||
"""
|
||||
)
|
||||
overflow_issues = [
|
||||
issue
|
||||
for issue in result["slides"][0]["issues"]
|
||||
if issue["code"] == "text_may_overflow_shape"
|
||||
]
|
||||
self.assertEqual(len(overflow_issues), 1)
|
||||
self.assertEqual(overflow_issues[0]["elements"], ["sheet-success"])
|
||||
|
||||
def test_lint_xml_reports_plain_short_metric_when_it_wraps(self) -> None:
|
||||
result = xml_text_overlap_lint.lint_xml(
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: lark-wiki
|
||||
version: 1.0.3
|
||||
description: "飞书知识库:管理知识空间、空间成员和文档节点。创建和查询知识空间、查看和管理空间成员、管理节点层级结构、在知识库中组织文档和快捷方式。当用户需要在知识库中查找或创建文档、浏览知识空间结构、查看或管理空间成员、移动或复制节点时使用。当用户给出 doubao.com 的 /wiki/ URL/token 时,也应直接使用本 skill,不要因为域名不是飞书而回退到 WebFetch;路由依据是 URL 路径模式和 token,而不是域名。不负责:上传文件到知识库节点下(走 lark-drive)、编辑文档/表格/Base 内容(走 lark-doc / lark-sheets / lark-base)。"
|
||||
description: "飞书知识库:/wiki/ URL 或 Wiki token 的唯一初始路由入口;读取、总结或编辑其内容时,先用本 Skill 解析真实资源类型和 token,再路由到对应业务 Skill。创建和查询知识空间、查看和管理空间成员、管理节点层级结构、在知识库中组织文档和快捷方式,当用户需要在知识库中查找或创建文档、浏览知识空间结构、移动或复制节点时使用。doubao.com 的 /wiki/ URL/token 同样适用。不负责:上传文件到知识库节点下(走 lark-drive)、编辑文档/表格/Base 内容(走 lark-doc / lark-sheets / lark-base)。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["lark-cli"]
|
||||
@@ -24,6 +24,7 @@ metadata:
|
||||
|
||||
## 快速决策
|
||||
|
||||
- 用户给出 `/wiki/` URL/token 并要求总结、读取、编辑等内容操作时,先按「身份选择」确定 `--as`(默认 user;用户明确要求 bot 时用 bot),再使用 `lark-cli wiki +node-get --node-token "<wiki_url_or_token>" --as "<selected_identity>" --format json` 识别真实 `obj_type` / `obj_token`,再按真实类型切到对应业务 skill;移动、复制、删除、成员、层级等 Wiki 节点操作继续使用 Wiki node_token 和 `lark-wiki` 命令。
|
||||
- 用户要**按特定主题 / 关键词 / 内容线索查找资料并收集到知识库节点或新建知识库节点下**,必须先阅读 [`../lark-drive/references/lark-drive-workflow.md`](../lark-drive/references/lark-drive-workflow.md),再按其中 `Workflow Registry` 进入 [`topic_move_collector`](../lark-drive/references/lark-drive-workflow-topic-move-collector.md) workflow。该 workflow 使用 Drive 全量搜索召回,再按 Wiki 目标解析、确认和移动;不要只用 Wiki 节点列表做局部遍历。
|
||||
- 用户要**整理 / 盘点 / 归类 / 重构知识库、个人文档库、文档库目录或 Wiki 节点结构**,或要生成整理方案、目标目录树、移动计划时,不要只使用 Wiki 节点 API。必须先阅读 [`../lark-drive/references/lark-drive-workflow.md`](../lark-drive/references/lark-drive-workflow.md),再按其中 `Workflow Registry` 进入 [`knowledge_organize`](../lark-drive/references/lark-drive-workflow-knowledge-organize.md) workflow;该 workflow 负责 Drive / Wiki / 个人文档库的统一入口解析、资源盘点、分类计划、写前确认和结果验证。
|
||||
- 用户要把**已有 Wiki 节点移出知识库,放到 Drive 文件夹或“我的空间”根目录**:使用 `wiki +move-to-drive`,不要使用 `wiki +move` 或 `drive +move`。这是会改变节点归属和权限继承的写操作,执行前确认源节点与目标位置。
|
||||
@@ -58,7 +59,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli wiki +<verb> [flags]`)
|
||||
| [`+space-create`](references/lark-wiki-space-create.md) | Create a wiki space (user identity only) |
|
||||
| [`+node-list`](references/lark-wiki-node-list.md) | List wiki nodes in a space or under a parent node (supports pagination) |
|
||||
| [`+node-copy`](references/lark-wiki-node-copy.md) | Copy a wiki node to a target space or parent node |
|
||||
| [`+node-get`](references/lark-wiki-node-get.md) | Get a wiki node's details by node_token / obj_token / Lark URL |
|
||||
| [`+node-get`](references/lark-wiki-node-get.md) | Get a wiki node's details by node_token / obj_token / Lark URL, including true obj_type / obj_token |
|
||||
| [`+node-delete`](references/lark-wiki-node-delete.md) | Delete a wiki node, polling the async delete task when needed |
|
||||
| [`+member-add`](references/lark-wiki-member-add.md) | Add a member to a wiki space |
|
||||
| [`+member-remove`](references/lark-wiki-member-remove.md) | Remove a member from a wiki space |
|
||||
|
||||
189
switch_env.sh
@@ -1,189 +0,0 @@
|
||||
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TYPES_FILE="${SCRIPT_DIR}/internal/core/types.go"
|
||||
SECHEADER_FILE="${SCRIPT_DIR}/internal/cmdutil/secheader.go"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 --env <boe|online|pre> --x-tt-env <value> [--build]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --env Set environment: 'boe' for feishu-boe.cn, 'online' for feishu.cn, 'pre' for feishu-pre.cn"
|
||||
echo " --x-tt-env Set x-tt-env header value. Empty string to remove the header."
|
||||
echo " --build Build and install lark-cli after switching environment"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 --env boe --x-tt-env boe_sun_ai"
|
||||
echo " $0 --env online --x-tt-env ''"
|
||||
echo " $0 --env pre --x-tt-env pre_env --build"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ENV=""
|
||||
XTTENV=""
|
||||
BUILD=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--env)
|
||||
ENV="$2"
|
||||
shift 2
|
||||
;;
|
||||
--x-tt-env)
|
||||
XTTENV="$2"
|
||||
shift 2
|
||||
;;
|
||||
--build)
|
||||
BUILD=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$ENV" ]]; then
|
||||
echo "Error: --env parameter is required"
|
||||
usage
|
||||
fi
|
||||
|
||||
if [[ "$ENV" != "boe" && "$ENV" != "online" && "$ENV" != "pre" ]]; then
|
||||
echo "Error: --env must be 'boe', 'online' or 'pre'"
|
||||
usage
|
||||
fi
|
||||
|
||||
update_endpoints() {
|
||||
local env="$1"
|
||||
local domain
|
||||
local mcp_domain
|
||||
|
||||
if [[ "$env" == "boe" ]]; then
|
||||
domain="feishu-boe.cn"
|
||||
mcp_domain="feishu-boe.cn"
|
||||
elif [[ "$env" == "pre" ]]; then
|
||||
domain="feishu-pre.cn"
|
||||
mcp_domain="feishu.cn"
|
||||
else
|
||||
domain="feishu.cn"
|
||||
mcp_domain="feishu.cn"
|
||||
fi
|
||||
|
||||
echo "Updating endpoints to use domain: ${domain} (MCP: ${mcp_domain})"
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' "s|feishu-boe\.cn|TEMP_DOMAIN_PLACEHOLDER|g" "$TYPES_FILE"
|
||||
sed -i '' "s|feishu-pre\.cn|TEMP_DOMAIN_PLACEHOLDER|g" "$TYPES_FILE"
|
||||
sed -i '' "s|feishu\.cn|TEMP_DOMAIN_PLACEHOLDER|g" "$TYPES_FILE"
|
||||
sed -i '' "s|TEMP_DOMAIN_PLACEHOLDER|${domain}|g" "$TYPES_FILE"
|
||||
sed -i '' "s|mcp\.${domain}|mcp.${mcp_domain}|g" "$TYPES_FILE"
|
||||
else
|
||||
sed -i "s|feishu-boe\.cn|TEMP_DOMAIN_PLACEHOLDER|g" "$TYPES_FILE"
|
||||
sed -i "s|feishu-pre\.cn|TEMP_DOMAIN_PLACEHOLDER|g" "$TYPES_FILE"
|
||||
sed -i "s|feishu\.cn|TEMP_DOMAIN_PLACEHOLDER|g" "$TYPES_FILE"
|
||||
sed -i "s|TEMP_DOMAIN_PLACEHOLDER|${domain}|g" "$TYPES_FILE"
|
||||
sed -i "s|mcp\.${domain}|mcp.${mcp_domain}|g" "$TYPES_FILE"
|
||||
fi
|
||||
|
||||
echo "Endpoints updated successfully"
|
||||
}
|
||||
|
||||
update_xtt_env_header() {
|
||||
local xtt_env="$1"
|
||||
|
||||
if [[ -z "$xtt_env" ]]; then
|
||||
echo "Removing x-tt-env header..."
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' '/h\.Set("x-tt-env",/d' "$SECHEADER_FILE"
|
||||
else
|
||||
sed -i '/h\.Set("x-tt-env",/d' "$SECHEADER_FILE"
|
||||
fi
|
||||
echo "x-tt-env header removed"
|
||||
else
|
||||
echo "Setting x-tt-env header to: ${xtt_env}"
|
||||
if grep -q 'h\.Set("x-tt-env",' "$SECHEADER_FILE"; then
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' "s/h\.Set(\"x-tt-env\", \"[^\"]*\")/h.Set(\"x-tt-env\", \"${xtt_env}\")/g" "$SECHEADER_FILE"
|
||||
else
|
||||
sed -i "s/h\.Set(\"x-tt-env\", \"[^\"]*\")/h.Set(\"x-tt-env\", \"${xtt_env}\")/g" "$SECHEADER_FILE"
|
||||
fi
|
||||
else
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' "s/h\.Set(HeaderUserAgent, UserAgentValue())/h.Set(HeaderUserAgent, UserAgentValue())\n\th.Set(\"x-tt-env\", \"${xtt_env}\")/g" "$SECHEADER_FILE"
|
||||
else
|
||||
sed -i "s/h\.Set(HeaderUserAgent, UserAgentValue())/h.Set(HeaderUserAgent, UserAgentValue())\n\th.Set(\"x-tt-env\", \"${xtt_env}\")/g" "$SECHEADER_FILE"
|
||||
fi
|
||||
fi
|
||||
echo "x-tt-env header updated"
|
||||
fi
|
||||
|
||||
if [[ "$xtt_env" == ppe* ]]; then
|
||||
echo "Adding x-use-ppe header (x-tt-env starts with 'ppe')..."
|
||||
if ! grep -q 'h\.Set("x-use-ppe",' "$SECHEADER_FILE"; then
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' 's/h\.Set("x-tt-env", "\(.*\)")/h.Set("x-tt-env", "\1")\'$'\n''\th.Set("x-use-ppe", "1")/g' "$SECHEADER_FILE"
|
||||
else
|
||||
sed -i 's/h\.Set("x-tt-env", "\(.*\)")/h.Set("x-tt-env", "\1")\n\th.Set("x-use-ppe", "1")/g' "$SECHEADER_FILE"
|
||||
fi
|
||||
fi
|
||||
echo "x-use-ppe header added"
|
||||
else
|
||||
if grep -q 'h\.Set("x-use-ppe",' "$SECHEADER_FILE"; then
|
||||
echo "Removing x-use-ppe header..."
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' '/h\.Set("x-use-ppe",/d' "$SECHEADER_FILE"
|
||||
else
|
||||
sed -i '/h\.Set("x-use-ppe",/d' "$SECHEADER_FILE"
|
||||
fi
|
||||
echo "x-use-ppe header removed"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
build_and_install() {
|
||||
echo "Building and installing lark-cli..."
|
||||
|
||||
rm -rf ~/.lark-cli/cache
|
||||
rm -f "${SCRIPT_DIR}/lark-cli"
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
./build.sh
|
||||
|
||||
local lark_cli_path
|
||||
lark_cli_path=$(which lark-cli 2>/dev/null || echo "")
|
||||
|
||||
if [[ -n "$lark_cli_path" ]]; then
|
||||
cp "${SCRIPT_DIR}/lark-cli" "$(dirname "$lark_cli_path")/"
|
||||
echo "lark-cli installed to $(dirname "$lark_cli_path")/"
|
||||
else
|
||||
echo "Warning: lark-cli not found in PATH, binary is at ${SCRIPT_DIR}/lark-cli"
|
||||
fi
|
||||
|
||||
echo "Build and install completed"
|
||||
}
|
||||
|
||||
echo "=== Environment Switch Script ==="
|
||||
echo "Environment: ${ENV}"
|
||||
echo "x-tt-env: ${XTTENV:-<empty - will remove header>}"
|
||||
echo "Build: ${BUILD}"
|
||||
echo ""
|
||||
|
||||
update_endpoints "$ENV"
|
||||
update_xtt_env_header "$XTTENV"
|
||||
|
||||
if [[ "$BUILD" == true ]]; then
|
||||
echo ""
|
||||
build_and_install
|
||||
fi
|
||||
echo ""
|
||||
echo "=== Done ==="
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
clie2e "github.com/larksuite/cli/tests/cli_e2e"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBaseFormQuestionsCreateVisibleRuleDryRun(t *testing.T) {
|
||||
setBaseDryRunConfigEnv(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
||||
Args: []string{
|
||||
"base", "+form-questions-create",
|
||||
"--base-token", "bascnXXXX",
|
||||
"--table-id", "tblXXXX",
|
||||
"--form-id", "vewXXXX",
|
||||
"--questions", `[{"type":"text","title":"发票抬头","visible_rule":{"logic":"and","conditions":[["是否需要发票","==","是"]]}}]`,
|
||||
"--dry-run",
|
||||
},
|
||||
DefaultAs: "bot",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
result.AssertExitCode(t, 0)
|
||||
|
||||
output := strings.TrimSpace(result.Stdout)
|
||||
assert.Contains(t, output, "/open-apis/base/v3/bases/bascnXXXX/tables/tblXXXX/forms/vewXXXX/questions")
|
||||
assert.Contains(t, output, `"method": "POST"`)
|
||||
// visible_rule must be transcribed verbatim into the request body.
|
||||
assert.Contains(t, output, "visible_rule")
|
||||
assert.Contains(t, output, "是否需要发票")
|
||||
}
|
||||
|
||||
func TestBaseFormQuestionsUpdateVisibleRuleDryRun(t *testing.T) {
|
||||
setBaseDryRunConfigEnv(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
||||
Args: []string{
|
||||
"base", "+form-questions-update",
|
||||
"--base-token", "bascnXXXX",
|
||||
"--table-id", "tblXXXX",
|
||||
"--form-id", "vewXXXX",
|
||||
"--questions", `[{"id":"q_002","visible_rule":{"logic":"and","conditions":[["q_001","==","是"]]}}]`,
|
||||
"--dry-run",
|
||||
},
|
||||
DefaultAs: "bot",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
result.AssertExitCode(t, 0)
|
||||
|
||||
output := strings.TrimSpace(result.Stdout)
|
||||
assert.Contains(t, output, "/open-apis/base/v3/bases/bascnXXXX/tables/tblXXXX/forms/vewXXXX/questions")
|
||||
assert.Contains(t, output, `"method": "PATCH"`)
|
||||
assert.Contains(t, output, "visible_rule")
|
||||
}
|
||||
@@ -12,7 +12,6 @@
|
||||
- TestBaseRecordBatchUpdatePerRecordDryRun: proves `+record-batch-update` preserves the per-record `update_records` request shape.
|
||||
- TestBaseRecordBatchUpdatePerRecordWorkflow: creates two records, updates different field types in one request, asserts the minimal response contract, reads both records back, verifies a missing record ID is not prevalidated, and cleans up the temporary Base.
|
||||
- TestBase_RoleWorkflow: proves `+advperm-enable`, `+role-create`, `+role-list`, `+role-get`, and `+role-update`; key `t.Run(...)` proof points are `list as bot`, `get as bot`, and `update as bot`.
|
||||
- TestBaseFormQuestionsCreateVisibleRuleDryRun / TestBaseFormQuestionsUpdateVisibleRuleDryRun: prove `+form-questions-create` / `+form-questions-update` dry-run request shape and that the optional `visible_rule` display condition is transcribed verbatim into the request body.
|
||||
- Cleanup note: `+table-delete` and `+role-delete` only run in cleanup and are intentionally left uncovered.
|
||||
- Blocked area: dashboard, field, most record operations, form, view, and workflow operations still lack deterministic create/read/update workflows in this suite.
|
||||
|
||||
@@ -52,10 +51,10 @@
|
||||
| ✕ | base +form-delete | shortcut | | none | form workflows not covered |
|
||||
| ✕ | base +form-get | shortcut | | none | form workflows not covered |
|
||||
| ✕ | base +form-list | shortcut | | none | form workflows not covered |
|
||||
| ✓ | base +form-questions-create | shortcut | TestBaseFormQuestionsCreateVisibleRuleDryRun | questions[].visible_rule | dry-run: request shape + visible_rule body passthrough |
|
||||
| ✕ | base +form-questions-create | shortcut | | none | form workflows not covered |
|
||||
| ✕ | base +form-questions-delete | shortcut | | none | form workflows not covered |
|
||||
| ✕ | base +form-questions-list | shortcut | | none | form workflows not covered |
|
||||
| ✓ | base +form-questions-update | shortcut | TestBaseFormQuestionsUpdateVisibleRuleDryRun | questions[].visible_rule | dry-run: request shape + visible_rule body passthrough |
|
||||
| ✕ | base +form-questions-update | shortcut | | none | form workflows not covered |
|
||||
| ✕ | base +form-update | shortcut | | none | form workflows not covered |
|
||||
| ✓ | base +record-batch-create | shortcut | base_record_batch_update_workflow_test.go::TestBaseRecordBatchUpdatePerRecordWorkflow | `--base-token`; `--table-id`; `--json.create_records` | seeds heterogeneous live workflow records |
|
||||
| ✓ | base +record-batch-update | shortcut | base_record_batch_update_dryrun_test.go::TestBaseRecordBatchUpdatePerRecordDryRun; base_record_batch_update_workflow_test.go::TestBaseRecordBatchUpdatePerRecordWorkflow | `--base-token`; `--table-id`; `--json.update_records`; dry-run + live | heterogeneous select/number update with write-back verification |
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package drive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
clie2e "github.com/larksuite/cli/tests/cli_e2e"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestDrive_MemberListDryRun(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
t.Setenv("LARKSUITE_CLI_APP_ID", "app")
|
||||
t.Setenv("LARKSUITE_CLI_APP_SECRET", "secret")
|
||||
t.Setenv("LARKSUITE_CLI_BRAND", "feishu")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantURL string
|
||||
wantType string
|
||||
wantFields string
|
||||
wantPermType string
|
||||
}{
|
||||
{
|
||||
name: "bare folder token",
|
||||
args: []string{
|
||||
"drive", "+member-list",
|
||||
"--token", "fldE2E001",
|
||||
"--type", "folder",
|
||||
"--dry-run",
|
||||
},
|
||||
wantURL: "/open-apis/drive/v1/permissions/fldE2E001/members",
|
||||
wantType: "folder",
|
||||
},
|
||||
{
|
||||
name: "folder URL infers folder type",
|
||||
args: []string{
|
||||
"drive", "+member-list",
|
||||
"--token", "https://example.feishu.cn/drive/folder/fldE2E002?from=share",
|
||||
"--dry-run",
|
||||
},
|
||||
wantURL: "/open-apis/drive/v1/permissions/fldE2E002/members",
|
||||
wantType: "folder",
|
||||
},
|
||||
{
|
||||
name: "fields star is passed only when explicit",
|
||||
args: []string{
|
||||
"drive", "+member-list",
|
||||
"--token", "doxE2E003",
|
||||
"--type", "docx",
|
||||
"--fields", "*",
|
||||
"--dry-run",
|
||||
},
|
||||
wantURL: "/open-apis/drive/v1/permissions/doxE2E003/members",
|
||||
wantType: "docx",
|
||||
wantFields: "*",
|
||||
},
|
||||
{
|
||||
name: "wiki perm type",
|
||||
args: []string{
|
||||
"drive", "+member-list",
|
||||
"--token", "wikE2E004",
|
||||
"--type", "wiki",
|
||||
"--fields", "name,type",
|
||||
"--perm-type", "single_page",
|
||||
"--dry-run",
|
||||
},
|
||||
wantURL: "/open-apis/drive/v1/permissions/wikE2E004/members",
|
||||
wantType: "wiki",
|
||||
wantFields: "name,type",
|
||||
wantPermType: "single_page",
|
||||
},
|
||||
}
|
||||
|
||||
for _, temp := range tests {
|
||||
tt := temp
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
||||
Args: tt.args,
|
||||
DefaultAs: "bot",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
result.AssertExitCode(t, 0)
|
||||
|
||||
out := result.Stdout
|
||||
if got := gjson.Get(out, "data.api.0.method").String(); got != "GET" {
|
||||
t.Fatalf("method = %q, want GET\nstdout:\n%s", got, out)
|
||||
}
|
||||
if got := gjson.Get(out, "data.api.0.url").String(); got != tt.wantURL {
|
||||
t.Fatalf("url = %q, want %q\nstdout:\n%s", got, tt.wantURL, out)
|
||||
}
|
||||
if got := gjson.Get(out, "data.api.0.params.type").String(); got != tt.wantType {
|
||||
t.Fatalf("params.type = %q, want %q\nstdout:\n%s", got, tt.wantType, out)
|
||||
}
|
||||
if tt.wantFields == "" {
|
||||
if gjson.Get(out, "data.api.0.params.fields").Exists() {
|
||||
t.Fatalf("params.fields should be omitted\nstdout:\n%s", out)
|
||||
}
|
||||
} else if got := gjson.Get(out, "data.api.0.params.fields").String(); got != tt.wantFields {
|
||||
t.Fatalf("params.fields = %q, want %q\nstdout:\n%s", got, tt.wantFields, out)
|
||||
}
|
||||
if tt.wantPermType == "" {
|
||||
if gjson.Get(out, "data.api.0.params.perm_type").Exists() {
|
||||
t.Fatalf("params.perm_type should be omitted\nstdout:\n%s", out)
|
||||
}
|
||||
} else if got := gjson.Get(out, "data.api.0.params.perm_type").String(); got != tt.wantPermType {
|
||||
t.Fatalf("params.perm_type = %q, want %q\nstdout:\n%s", got, tt.wantPermType, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrive_MemberListWorkflow(t *testing.T) {
|
||||
parentT := t
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
folderName := "lark-cli-e2e-drive-member-list-" + clie2e.GenerateSuffix()
|
||||
folderToken := createDriveFolderOrSkipPermission(t, parentT, ctx, folderName)
|
||||
|
||||
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
||||
Args: []string{
|
||||
"drive", "+member-list",
|
||||
"--token", folderToken,
|
||||
"--type", "folder",
|
||||
"--format", "json",
|
||||
},
|
||||
DefaultAs: "bot",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if result.ExitCode != 0 {
|
||||
combinedOutput := strings.ToLower(result.Stdout + "\n" + result.Stderr)
|
||||
if strings.Contains(combinedOutput, "docs:permission.member:retrieve") ||
|
||||
strings.Contains(combinedOutput, "app scope not enabled") ||
|
||||
strings.Contains(combinedOutput, "missing required scope") ||
|
||||
strings.Contains(combinedOutput, "missing_scope") ||
|
||||
strings.Contains(combinedOutput, "99991672") ||
|
||||
strings.Contains(combinedOutput, "1063002") ||
|
||||
strings.Contains(combinedOutput, "1063004") ||
|
||||
strings.Contains(combinedOutput, "permission denied") ||
|
||||
strings.Contains(combinedOutput, "no share permission") {
|
||||
t.Skipf("skip drive member list workflow due to missing bot scope or folder permission: %s", strings.TrimSpace(result.Stdout+"\n"+result.Stderr))
|
||||
}
|
||||
if strings.Contains(combinedOutput, "99992402") &&
|
||||
strings.Contains(combinedOutput, "field validation failed") {
|
||||
t.Skipf("skip drive member list workflow because this environment does not yet accept type=folder on the member list API: %s", strings.TrimSpace(result.Stdout+"\n"+result.Stderr))
|
||||
}
|
||||
t.Fatalf("drive member list workflow failed: exit=%d\nstdout:\n%s\nstderr:\n%s", result.ExitCode, result.Stdout, result.Stderr)
|
||||
}
|
||||
result.AssertStdoutStatus(t, true)
|
||||
|
||||
if items := gjson.Get(result.Stdout, "data.items"); !items.Exists() || !items.IsArray() {
|
||||
t.Fatalf("data.items must be present as an array\nstdout:\n%s", result.Stdout)
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package drive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
clie2e "github.com/larksuite/cli/tests/cli_e2e"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestDrive_PermissionGetSettingDryRun(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
t.Setenv("LARKSUITE_CLI_APP_ID", "app")
|
||||
t.Setenv("LARKSUITE_CLI_APP_SECRET", "secret")
|
||||
t.Setenv("LARKSUITE_CLI_BRAND", "feishu")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantURL string
|
||||
wantType string
|
||||
}{
|
||||
{
|
||||
name: "bare folder token",
|
||||
args: []string{
|
||||
"drive", "+permission-get-setting",
|
||||
"--token", "fldE2E001",
|
||||
"--type", "folder",
|
||||
"--dry-run",
|
||||
},
|
||||
wantURL: "/open-apis/drive/v2/permissions/fldE2E001/public",
|
||||
wantType: "folder",
|
||||
},
|
||||
{
|
||||
name: "folder URL",
|
||||
args: []string{
|
||||
"drive", "+permission-get-setting",
|
||||
"--token", "https://example.feishu.cn/drive/folder/fldE2E001?from=share",
|
||||
"--dry-run",
|
||||
},
|
||||
wantURL: "/open-apis/drive/v2/permissions/fldE2E001/public",
|
||||
wantType: "folder",
|
||||
},
|
||||
{
|
||||
name: "docx URL",
|
||||
args: []string{
|
||||
"drive", "+permission-get-setting",
|
||||
"--token", "https://example.feishu.cn/docx/doxE2E001",
|
||||
"--dry-run",
|
||||
},
|
||||
wantURL: "/open-apis/drive/v2/permissions/doxE2E001/public",
|
||||
wantType: "docx",
|
||||
},
|
||||
}
|
||||
|
||||
for _, temp := range tests {
|
||||
tt := temp
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
||||
Args: tt.args,
|
||||
DefaultAs: "bot",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
result.AssertExitCode(t, 0)
|
||||
|
||||
out := result.Stdout
|
||||
if got := gjson.Get(out, "data.api.0.method").String(); got != "GET" {
|
||||
t.Fatalf("method = %q, want GET\nstdout:\n%s", got, out)
|
||||
}
|
||||
if got := gjson.Get(out, "data.api.0.url").String(); got != tt.wantURL {
|
||||
t.Fatalf("url = %q, want %q\nstdout:\n%s", got, tt.wantURL, out)
|
||||
}
|
||||
if got := gjson.Get(out, "data.api.0.params.type").String(); got != tt.wantType {
|
||||
t.Fatalf("params.type = %q, want %q\nstdout:\n%s", got, tt.wantType, out)
|
||||
}
|
||||
if gjson.Get(out, "data.folder_token").Exists() {
|
||||
t.Fatalf("folder_token exists in dry-run output, want omitted\nstdout:\n%s", out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDrive_PermissionGetSettingWorkflow(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
folderToken := CreateDriveFolder(
|
||||
t,
|
||||
t,
|
||||
ctx,
|
||||
"lark-cli-e2e-drive-permission-get-setting-"+clie2e.GenerateSuffix(),
|
||||
"bot",
|
||||
"",
|
||||
)
|
||||
|
||||
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
||||
Args: []string{
|
||||
"drive", "+permission-get-setting",
|
||||
"--token", folderToken,
|
||||
"--type", "folder",
|
||||
"--format", "json",
|
||||
},
|
||||
DefaultAs: "bot",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if result.ExitCode != 0 {
|
||||
combinedOutput := strings.ToLower(result.Stdout + "\n" + result.Stderr)
|
||||
if strings.Contains(combinedOutput, "docs:permission.setting:read") ||
|
||||
strings.Contains(combinedOutput, "app scope not enabled") ||
|
||||
strings.Contains(combinedOutput, "missing required scope") ||
|
||||
strings.Contains(combinedOutput, "99991672") {
|
||||
t.Skipf("skip drive permission setting workflow due to missing bot scope docs:permission.setting:read: %s", strings.TrimSpace(result.Stdout+"\n"+result.Stderr))
|
||||
}
|
||||
}
|
||||
result.AssertExitCode(t, 0)
|
||||
result.AssertStdoutStatus(t, true)
|
||||
|
||||
if !gjson.Get(result.Stdout, "data.permission_public").Exists() {
|
||||
t.Fatalf("permission_public missing in output\nstdout:\n%s", result.Stdout)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<slide id="pWs">
|
||||
<style>
|
||||
<fill id="fWs">
|
||||
<fillColor color="rgba(255, 85, 0, 1)"/>
|
||||
</fill>
|
||||
</style>
|
||||
<data>
|
||||
<shape width="920" height="350" topLeftX="20" topLeftY="20" type="text" id="bdp">
|
||||
<content textType="title" fontSize="240" fontFamily="思源黑体" color="rgba(255, 255, 255, 0.102)" bold="true" letterSpacing="-5" textAlign="left">
|
||||
<p>PULSE</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="800" height="120" topLeftX="80" topLeftY="160" type="text" id="bdX">
|
||||
<content textType="title" fontSize="72" fontFamily="思源黑体" color="rgba(255, 255, 255, 1)" bold="true" letterSpacing="2" textAlign="left">
|
||||
<p>MOVE BEYOND.</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="600" height="80" topLeftX="80" topLeftY="280" type="text" id="bdy">
|
||||
<content textType="headline" fontSize="36" fontFamily="思源黑体" color="rgba(255, 255, 255, 1)" bold="true" textAlign="left">
|
||||
<p>超越边界,脉动不止</p>
|
||||
</content>
|
||||
</shape>
|
||||
<line id="bdd" startX="80" startY="380" endX="200.00416659433122" endY="380">
|
||||
<border color="rgba(255, 255, 255, 1)" width="3"/>
|
||||
</line>
|
||||
<shape width="500" height="80" topLeftX="80" topLeftY="400" type="text" id="bdZ">
|
||||
<content fontSize="14" fontFamily="思源黑体" color="rgba(255, 255, 255, 0.851)" lineSpacing="multiple:1.8" textAlign="left">
|
||||
<p>我们相信运动不只是竞技,更是一种生活态度。</p>
|
||||
<p>PULSE 为街头而生,为每一个不甘平凡的灵魂。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="40" height="380" topLeftX="750" topLeftY="80" rotation="90" type="text" id="bdm">
|
||||
<content textType="caption" fontSize="11" fontFamily="思源黑体" color="rgba(255, 255, 255, 0.6)" letterSpacing="6" textAlign="left">
|
||||
<p>BRAND MANIFESTO / 品牌宣言</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="200" height="20" topLeftX="60" topLeftY="490" type="text" id="bdc">
|
||||
<content textType="caption" fontSize="10" fontFamily="思源黑体" color="rgba(255, 255, 255, 0.6)" letterSpacing="2" textAlign="left">
|
||||
<p>02 / 12</p>
|
||||
</content>
|
||||
</shape>
|
||||
</data>
|
||||
<note id="bdH">
|
||||
<content/>
|
||||
</note>
|
||||
</slide>
|
||||
@@ -1,89 +0,0 @@
|
||||
<slide id="pBU">
|
||||
<style>
|
||||
<fill id="fBU">
|
||||
<fillColor color="rgba(55, 42, 30, 1)"/>
|
||||
</fill>
|
||||
</style>
|
||||
<data>
|
||||
<shape width="360" height="200" topLeftX="600" topLeftY="20" type="text" id="bqp">
|
||||
<content textType="title" fontSize="180" fontFamily="思源宋体" color="rgba(180, 130, 80, 0.078)" bold="true" textAlign="right">
|
||||
<p>土</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="150" height="30" topLeftX="80" topLeftY="60" type="text" id="bqR">
|
||||
<content textType="caption" fontSize="12" fontFamily="思源宋体" color="rgba(200, 150, 100, 1)" letterSpacing="2">
|
||||
<p>06 / 品牌价值</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="600" height="80" topLeftX="80" topLeftY="100" type="text" id="bqg">
|
||||
<content textType="title" fontSize="40" fontFamily="思源宋体" color="rgba(240, 230, 215, 1)" bold="true">
|
||||
<p>我们坚持的三件事</p>
|
||||
</content>
|
||||
</shape>
|
||||
<line id="bqx" startX="80" startY="185" endX="120.01249804748511" endY="185">
|
||||
<border color="rgba(200, 150, 100, 1)"/>
|
||||
</line>
|
||||
<shape width="60" height="60" topLeftX="80" topLeftY="230" type="text" id="bqF">
|
||||
<content textType="title" fontSize="48" fontFamily="思源宋体" color="rgba(200, 150, 100, 1)" bold="true">
|
||||
<p>壹</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="200" height="30" topLeftX="150" topLeftY="235" type="text" id="bqu">
|
||||
<content fontSize="18" fontFamily="思源宋体" color="rgba(240, 220, 190, 1)" bold="true">
|
||||
<p>全手工制作</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="200" height="30" topLeftX="150" topLeftY="268" type="text" id="bqc">
|
||||
<content fontSize="13" fontFamily="思源宋体" color="rgba(220, 205, 185, 1)">
|
||||
<p>拒绝模具量产,每一件都是孤品</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="60" height="60" topLeftX="380" topLeftY="230" type="text" id="bqj">
|
||||
<content textType="title" fontSize="48" fontFamily="思源宋体" color="rgba(200, 150, 100, 1)" bold="true">
|
||||
<p>贰</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="200" height="30" topLeftX="450" topLeftY="235" type="text" id="bqk">
|
||||
<content fontSize="18" fontFamily="思源宋体" color="rgba(240, 220, 190, 1)" bold="true">
|
||||
<p>天然原矿釉</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="200" height="30" topLeftX="450" topLeftY="268" type="text" id="bqh">
|
||||
<content fontSize="13" fontFamily="思源宋体" color="rgba(220, 205, 185, 1)">
|
||||
<p>安全无毒,可食用级釉料</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="60" height="60" topLeftX="680" topLeftY="230" type="text" id="bqC">
|
||||
<content textType="title" fontSize="48" fontFamily="思源宋体" color="rgba(200, 150, 100, 1)" bold="true">
|
||||
<p>叁</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="180" height="30" topLeftX="750" topLeftY="235" type="text" id="bqB">
|
||||
<content fontSize="18" fontFamily="思源宋体" color="rgba(240, 220, 190, 1)" bold="true">
|
||||
<p>高温烧制</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="180" height="30" topLeftX="750" topLeftY="268" type="text" id="bqV">
|
||||
<content fontSize="13" fontFamily="思源宋体" color="rgba(220, 205, 185, 1)">
|
||||
<p>1280°C高温,坚固耐用</p>
|
||||
</content>
|
||||
</shape>
|
||||
<line id="bql" startX="80" startY="350" endX="880.0006249997559" endY="350">
|
||||
<border color="rgba(200, 150, 100, 0.3)" width="1"/>
|
||||
</line>
|
||||
<shape width="800" height="80" topLeftX="80" topLeftY="380" type="text" id="bqe">
|
||||
<content fontSize="14" fontFamily="思源宋体" color="rgba(180, 165, 145, 1)" lineSpacing="multiple:2" textAlign="center">
|
||||
<p>我们相信,好的器物应该陪伴人很久很久</p>
|
||||
<p>在日复一日的使用中,包浆、温润,成为生活的一部分</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="60" height="30" topLeftX="860" topLeftY="490" type="text" id="bqW">
|
||||
<content textType="caption" fontSize="10" fontFamily="思源黑体" color="rgba(140, 125, 110, 1)" textAlign="right">
|
||||
<p>07 / 10</p>
|
||||
</content>
|
||||
</shape>
|
||||
</data>
|
||||
<note id="bqG">
|
||||
<content/>
|
||||
</note>
|
||||
</slide>
|
||||
@@ -1,261 +0,0 @@
|
||||
<presentation id="A4fSsAeq4lEE6Gd7JbVcZZQ2n0g" xmlns="/sml/2.0" width="960" height="540">
|
||||
<title>幽灵字样张 Ghost Type Specimen</title>
|
||||
<theme>
|
||||
<textStyles>
|
||||
<headline fontColor="rgba(31, 35, 41, 1)"/>
|
||||
<sub-headline fontColor="rgba(31, 35, 41, 1)"/>
|
||||
<body fontColor="rgba(31, 35, 41, 1)"/>
|
||||
<caption fontColor="rgba(155, 158, 162, 1)" fontSize="14"/>
|
||||
</textStyles>
|
||||
</theme>
|
||||
<slide id="pqq">
|
||||
<style>
|
||||
<fill id="fqq">
|
||||
<fillColor color="rgba(6, 9, 18, 1)"/>
|
||||
</fill>
|
||||
</style>
|
||||
<data>
|
||||
<img id="bJv" src="ZaCqbgcWZo0ByFxBg39c1IZ1nSg" width="960" height="540" topLeftX="0" topLeftY="0">
|
||||
<crop leftOffset="0" rightOffset="0" topOffset="0" bottomOffset="0"/>
|
||||
</img>
|
||||
<shape width="960" height="540" topLeftX="0" topLeftY="0" presetHandlers="0" type="rect" id="bJN">
|
||||
<fill>
|
||||
<fillColor color="rgba(3, 7, 16, 0.48)"/>
|
||||
</fill>
|
||||
<border color="rgba(3, 7, 16, 0)" width="0"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="892" height="154" topLeftX="34" topLeftY="296" alpha="0.24" type="text" id="bJo">
|
||||
<content textType="title" fontSize="124" fontFamily="Source Han Sans" color="rgba(235, 248, 255, 1)" bold="true" lineSpacing="multiple:0.9" textAlign="center" autoFit="normal-auto-fit">
|
||||
<p>GHOST</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="260" height="32" topLeftX="72" topLeftY="62" type="text" id="bJy">
|
||||
<content textType="caption" fontSize="12" fontFamily="Source Han Sans" color="rgba(115, 255, 235, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>TYPE SPECIMEN / 01</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="560" height="102" topLeftX="70" topLeftY="126" type="text" id="bJY">
|
||||
<content textType="title" fontSize="42" fontFamily="Source Han Sans" color="rgba(247, 251, 255, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>幽灵字样张</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="520" height="56" topLeftX="74" topLeftY="222" type="text" id="bJF">
|
||||
<content verticalAlign="top" fontSize="15" fontFamily="Source Han Sans" color="rgba(207, 224, 232, 1)" lineSpacing="multiple:1.4" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>把文字从“标题”改造成雾、残影和空间层。重叠不是事故,而是建立气氛的材料。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="214" height="86" topLeftX="672" topLeftY="64" presetHandlers="0" type="rect" id="bJZ">
|
||||
<fill>
|
||||
<fillColor color="rgba(5, 13, 25, 0.54)"/>
|
||||
</fill>
|
||||
<border color="rgba(126, 255, 238, 0.52)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="174" height="48" topLeftX="690" topLeftY="82" type="text" id="bJz">
|
||||
<content fontSize="13" fontFamily="Source Han Sans" color="rgba(229, 245, 250, 1)" lineSpacing="multiple:1.2" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>低透明度 / 大字号 / 贴边裁切 / 图文压叠</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="460" height="22" topLeftX="72" topLeftY="476" type="text" id="bJb">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(157, 179, 190, 1)" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Sample deck generated with Lark Slides XML</p>
|
||||
</content>
|
||||
</shape>
|
||||
</data>
|
||||
<note id="bJJ">
|
||||
<content/>
|
||||
</note>
|
||||
</slide>
|
||||
<slide id="pqb">
|
||||
<style>
|
||||
<fill id="fqb">
|
||||
<fillColor color="rgba(8, 11, 20, 1)"/>
|
||||
</fill>
|
||||
</style>
|
||||
<data>
|
||||
<shape width="960" height="540" topLeftX="0" topLeftY="0" presetHandlers="0" type="rect" id="bJL">
|
||||
<fill>
|
||||
<fillColor color="rgba(8, 11, 20, 1)"/>
|
||||
</fill>
|
||||
<border color="rgba(8, 11, 20, 0)" width="0"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<img id="bJC" src="QsRqbf41WoqOsDxkBFHcQWVAnmd" width="291.99999999999994" height="328" topLeftX="612" topLeftY="112">
|
||||
<crop type="rect" leftOffset="145.55555555555554" rightOffset="145.5555555555556" topOffset="0" bottomOffset="0"/>
|
||||
</img>
|
||||
<shape width="292" height="328" topLeftX="612" topLeftY="112" presetHandlers="0" type="rect" id="bJx">
|
||||
<fill>
|
||||
<fillColor color="rgba(1, 5, 13, 0.32)"/>
|
||||
</fill>
|
||||
<border color="rgba(120, 255, 238, 0.42)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="872" height="96" topLeftX="43.99999999999992" topLeftY="381.99999999999994" rotation="2" alpha="0.16" type="text" id="bJd">
|
||||
<content textType="headline" fontSize="78" fontFamily="Source Han Sans" color="rgba(224, 247, 255, 1)" bold="true" lineSpacing="multiple:0.9" textAlign="center" autoFit="normal-auto-fit">
|
||||
<p>LAYER</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="420" height="48" topLeftX="56" topLeftY="35" type="text" id="bJK">
|
||||
<content textType="headline" fontSize="30" fontFamily="Source Han Sans" color="rgba(244, 250, 255, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Layer Grammar</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="360" height="34" topLeftX="542" topLeftY="42" type="text" id="bJQ">
|
||||
<content textType="caption" fontSize="11" fontFamily="Source Han Sans" color="rgba(156, 178, 189, 1)" lineSpacing="multiple:1.0" textAlign="right" autoFit="normal-auto-fit">
|
||||
<p>幽灵字不是压住信息,而是给信息制造深度。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="166" height="242" topLeftX="56" topLeftY="112" presetHandlers="0" type="rect" id="bJH">
|
||||
<fill>
|
||||
<fillColor color="rgba(238, 248, 255, 0.08)"/>
|
||||
</fill>
|
||||
<border color="rgba(238, 248, 255, 0.16)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="130" height="28" topLeftX="74" topLeftY="132" type="text" id="bJk">
|
||||
<content textType="caption" fontSize="12" fontFamily="Source Han Sans" color="rgba(115, 255, 235, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>01 / SAFE TEXT</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="130" height="116" topLeftX="74" topLeftY="174" type="text" id="bJf">
|
||||
<content verticalAlign="top" fontSize="13" fontFamily="Source Han Sans" color="rgba(231, 241, 245, 1)" lineSpacing="multiple:1.3" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>主信息必须留在安全区。它可以靠近幽灵字,但不依赖幽灵字才能被读懂。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="130" height="28" topLeftX="74" topLeftY="304" type="text" id="bJW">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(158, 180, 190, 1)" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Readability first</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="166" height="242" topLeftX="246" topLeftY="112" presetHandlers="0" type="rect" id="bJr">
|
||||
<fill>
|
||||
<fillColor color="rgba(238, 248, 255, 0.08)"/>
|
||||
</fill>
|
||||
<border color="rgba(238, 248, 255, 0.16)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="130" height="28" topLeftX="264" topLeftY="132" type="text" id="bJP">
|
||||
<content textType="caption" fontSize="12" fontFamily="Source Han Sans" color="rgba(115, 255, 235, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>02 / OVERLAP</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="130" height="116" topLeftX="264" topLeftY="174" type="text" id="bJg">
|
||||
<content verticalAlign="top" fontSize="13" fontFamily="Source Han Sans" color="rgba(231, 241, 245, 1)" lineSpacing="multiple:1.3" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>允许图像、暗色块和大字交叠;让重叠发生在低对比背景层,而不是正文层。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="130" height="28" topLeftX="264" topLeftY="304" type="text" id="bJM">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(158, 180, 190, 1)" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Depth, not noise</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="146" height="242" topLeftX="436" topLeftY="112" presetHandlers="0" type="rect" id="bJE">
|
||||
<fill>
|
||||
<fillColor color="rgba(238, 248, 255, 0.08)"/>
|
||||
</fill>
|
||||
<border color="rgba(238, 248, 255, 0.16)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="110" height="28" topLeftX="454" topLeftY="132" type="text" id="bJA">
|
||||
<content textType="caption" fontSize="12" fontFamily="Source Han Sans" color="rgba(115, 255, 235, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>03 / BLEED</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="110" height="116" topLeftX="454" topLeftY="174" type="text" id="bJu">
|
||||
<content verticalAlign="top" fontSize="13" fontFamily="Source Han Sans" color="rgba(231, 241, 245, 1)" lineSpacing="multiple:1.3" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>大字可靠近边界,被画布切出张力;核心内容仍保留完整轮廓。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="110" height="28" topLeftX="454" topLeftY="304" type="text" id="bJR">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(158, 180, 190, 1)" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Intentional crop</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="248" height="42" topLeftX="632" topLeftY="364" type="text" id="bJS">
|
||||
<content fontSize="14" fontFamily="Source Han Sans" color="rgba(238, 247, 250, 1)" bold="true" lineSpacing="multiple:1.1" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Three layers: image, ghost word, readable claim.</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="500" height="24" topLeftX="56" topLeftY="468" type="text" id="bJO">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(148, 170, 181, 1)" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Rule sheet: keep the readable layer calm, then let the atmosphere get aggressive.</p>
|
||||
</content>
|
||||
</shape>
|
||||
</data>
|
||||
<note id="bJm">
|
||||
<content/>
|
||||
</note>
|
||||
</slide>
|
||||
<slide id="pqE">
|
||||
<style>
|
||||
<fill id="fqE">
|
||||
<fillColor color="rgba(5, 7, 14, 1)"/>
|
||||
</fill>
|
||||
</style>
|
||||
<data>
|
||||
<img id="bJl" src="WYcVbbQfgo5qitxHrsBcJqBJnKh" width="960" height="540" topLeftX="0" topLeftY="0">
|
||||
<crop leftOffset="0" rightOffset="0" topOffset="0" bottomOffset="0"/>
|
||||
</img>
|
||||
<shape width="960" height="540" topLeftX="0" topLeftY="0" presetHandlers="0" type="rect" id="bJI">
|
||||
<fill>
|
||||
<fillColor color="rgba(2, 5, 12, 0.5)"/>
|
||||
</fill>
|
||||
<border color="rgba(2, 5, 12, 0)" width="0"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="892" height="168" topLeftX="34.000000000000014" topLeftY="78.00000000000001" rotation="1" alpha="0.22" type="text" id="bJt">
|
||||
<content textType="title" fontSize="138" fontFamily="Source Han Sans" color="rgba(238, 250, 255, 1)" bold="true" lineSpacing="multiple:0.9" textAlign="center" autoFit="normal-auto-fit">
|
||||
<p>BLEED</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="270" height="72" topLeftX="630" topLeftY="248" alpha="0.18" type="text" id="bJn">
|
||||
<content textType="headline" fontSize="54" fontFamily="Source Han Sans" color="rgba(142, 255, 239, 1)" bold="true" lineSpacing="multiple:0.9" textAlign="center" autoFit="normal-auto-fit">
|
||||
<p>OFF EDGE</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="516" height="126" topLeftX="62" topLeftY="316" presetHandlers="0" type="rect" id="bJX">
|
||||
<fill>
|
||||
<fillColor color="rgba(5, 12, 24, 0.7)"/>
|
||||
</fill>
|
||||
<border color="rgba(128, 255, 240, 0.46)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="468" height="52" topLeftX="86" topLeftY="336" type="text" id="bJV">
|
||||
<content textType="headline" fontSize="28" fontFamily="Source Han Sans" color="rgba(248, 252, 255, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>让字先离开屏幕</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="454" height="34" topLeftX="88" topLeftY="390" type="text" id="bJi">
|
||||
<content fontSize="14" fontFamily="Source Han Sans" color="rgba(204, 222, 232, 1)" lineSpacing="multiple:1.3" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>越界感留给幽灵字;行动语句、页码、结论仍留在可读安全区。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="232" height="70" topLeftX="646" topLeftY="362" presetHandlers="0" type="rect" id="bJs">
|
||||
<fill>
|
||||
<fillColor color="rgba(122, 255, 238, 0.12)"/>
|
||||
</fill>
|
||||
<border color="rgba(122, 255, 238, 0.38)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="196" height="38" topLeftX="664" topLeftY="378" type="text" id="bJD">
|
||||
<content fontSize="13" fontFamily="Source Han Sans" color="rgba(226, 245, 249, 1)" lineSpacing="multiple:1.2" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>大胆设计可以失控,但信息层不能失联。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="270" height="28" topLeftX="62" topLeftY="60" type="text" id="bJq">
|
||||
<content textType="caption" fontSize="12" fontFamily="Source Han Sans" color="rgba(115, 255, 235, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>TYPE SPECIMEN / 03</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="154" height="22" topLeftX="744" topLeftY="474" type="text" id="bJe">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(154, 176, 187, 1)" lineSpacing="multiple:1.0" textAlign="right" autoFit="normal-auto-fit">
|
||||
<p>Ghost typography sample</p>
|
||||
</content>
|
||||
</shape>
|
||||
</data>
|
||||
<note id="bJp">
|
||||
<content/>
|
||||
</note>
|
||||
</slide>
|
||||
</presentation>
|
||||
@@ -1,261 +0,0 @@
|
||||
<presentation id="A4fSsAeq4lEE6Gd7JbVcZZQ2n0g" xmlns="/sml/2.0" width="960" height="540">
|
||||
<title>幽灵字样张 Ghost Type Specimen</title>
|
||||
<theme>
|
||||
<textStyles>
|
||||
<headline fontColor="rgba(31, 35, 41, 1)"/>
|
||||
<sub-headline fontColor="rgba(31, 35, 41, 1)"/>
|
||||
<body fontColor="rgba(31, 35, 41, 1)"/>
|
||||
<caption fontColor="rgba(155, 158, 162, 1)" fontSize="14"/>
|
||||
</textStyles>
|
||||
</theme>
|
||||
<slide id="pqq">
|
||||
<style>
|
||||
<fill id="fqq">
|
||||
<fillColor color="rgba(6, 9, 18, 1)"/>
|
||||
</fill>
|
||||
</style>
|
||||
<data>
|
||||
<img id="bJv" src="ZaCqbgcWZo0ByFxBg39c1IZ1nSg" width="960" height="540" topLeftX="0" topLeftY="0">
|
||||
<crop leftOffset="0" rightOffset="0" topOffset="0" bottomOffset="0"/>
|
||||
</img>
|
||||
<shape width="960" height="540" topLeftX="0" topLeftY="0" presetHandlers="0" type="rect" id="bJN">
|
||||
<fill>
|
||||
<fillColor color="rgba(3, 7, 16, 0.48)"/>
|
||||
</fill>
|
||||
<border color="rgba(3, 7, 16, 0)" width="0"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="892" height="154" topLeftX="380.7158671586716" topLeftY="344" alpha="0.24" type="text" id="bJo">
|
||||
<content textType="title" fontSize="124" fontFamily="Source Han Sans" color="rgba(235, 248, 255, 1)" bold="true" lineSpacing="multiple:0.9" textAlign="center" autoFit="normal-auto-fit">
|
||||
<p>GHOST</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="260" height="32" topLeftX="72" topLeftY="62" type="text" id="bJy">
|
||||
<content textType="caption" fontSize="12" fontFamily="Source Han Sans" color="rgba(115, 255, 235, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>TYPE SPECIMEN / 01</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="560" height="102" topLeftX="70" topLeftY="126" type="text" id="bJY">
|
||||
<content textType="title" fontSize="42" fontFamily="Source Han Sans" color="rgba(247, 251, 255, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>幽灵字样张</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="520" height="56" topLeftX="74" topLeftY="222" type="text" id="bJF">
|
||||
<content verticalAlign="top" fontSize="15" fontFamily="Source Han Sans" color="rgba(207, 224, 232, 1)" lineSpacing="multiple:1.4" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>把文字从“标题”改造成雾、残影和空间层。重叠不是事故,而是建立气氛的材料。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="214" height="86" topLeftX="672" topLeftY="64" presetHandlers="0" type="rect" id="bJZ">
|
||||
<fill>
|
||||
<fillColor color="rgba(5, 13, 25, 0.54)"/>
|
||||
</fill>
|
||||
<border color="rgba(126, 255, 238, 0.52)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="174" height="48" topLeftX="690" topLeftY="82" type="text" id="bJz">
|
||||
<content fontSize="13" fontFamily="Source Han Sans" color="rgba(229, 245, 250, 1)" lineSpacing="multiple:1.2" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>低透明度 / 大字号 / 贴边裁切 / 图文压叠</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="460" height="22" topLeftX="72" topLeftY="476" type="text" id="bJb">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(157, 179, 190, 1)" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Sample deck generated with Lark Slides XML</p>
|
||||
</content>
|
||||
</shape>
|
||||
</data>
|
||||
<note id="bJJ">
|
||||
<content/>
|
||||
</note>
|
||||
</slide>
|
||||
<slide id="pqb">
|
||||
<style>
|
||||
<fill id="fqb">
|
||||
<fillColor color="rgba(8, 11, 20, 1)"/>
|
||||
</fill>
|
||||
</style>
|
||||
<data>
|
||||
<shape width="960" height="540" topLeftX="0" topLeftY="0" presetHandlers="0" type="rect" id="bJL">
|
||||
<fill>
|
||||
<fillColor color="rgba(8, 11, 20, 1)"/>
|
||||
</fill>
|
||||
<border color="rgba(8, 11, 20, 0)" width="0"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<img id="bJC" src="QsRqbf41WoqOsDxkBFHcQWVAnmd" width="291.99999999999994" height="328" topLeftX="612" topLeftY="112">
|
||||
<crop type="rect" leftOffset="145.55555555555554" rightOffset="145.5555555555556" topOffset="0" bottomOffset="0"/>
|
||||
</img>
|
||||
<shape width="872" height="96" topLeftX="175.99999999994805" topLeftY="369.18694025920706" rotation="2" alpha="0.16" type="text" id="bJd">
|
||||
<content textType="headline" fontSize="78" fontFamily="Source Han Sans" color="rgba(224, 247, 255, 1)" bold="true" lineSpacing="multiple:0.9" textAlign="center" autoFit="normal-auto-fit">
|
||||
<p>LAYER</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="420" height="48" topLeftX="56" topLeftY="35" type="text" id="bJK">
|
||||
<content textType="headline" fontSize="30" fontFamily="Source Han Sans" color="rgba(244, 250, 255, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Layer Grammar</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="360" height="34" topLeftX="542" topLeftY="42" type="text" id="bJQ">
|
||||
<content textType="caption" fontSize="11" fontFamily="Source Han Sans" color="rgba(156, 178, 189, 1)" lineSpacing="multiple:1.0" textAlign="right" autoFit="normal-auto-fit">
|
||||
<p>幽灵字不是压住信息,而是给信息制造深度。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="166" height="242" topLeftX="56" topLeftY="112" presetHandlers="0" type="rect" id="bJH">
|
||||
<fill>
|
||||
<fillColor color="rgba(238, 248, 255, 0.08)"/>
|
||||
</fill>
|
||||
<border color="rgba(238, 248, 255, 0.16)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="130" height="28" topLeftX="74" topLeftY="132" type="text" id="bJk">
|
||||
<content textType="caption" fontSize="12" fontFamily="Source Han Sans" color="rgba(115, 255, 235, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>01 / SAFE TEXT</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="130" height="116" topLeftX="74" topLeftY="174" type="text" id="bJf">
|
||||
<content verticalAlign="top" fontSize="13" fontFamily="Source Han Sans" color="rgba(231, 241, 245, 1)" lineSpacing="multiple:1.3" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>主信息必须留在安全区。它可以靠近幽灵字,但不依赖幽灵字才能被读懂。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="130" height="28" topLeftX="74" topLeftY="304" type="text" id="bJW">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(158, 180, 190, 1)" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Readability first</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="166" height="242" topLeftX="246" topLeftY="112" presetHandlers="0" type="rect" id="bJr">
|
||||
<fill>
|
||||
<fillColor color="rgba(238, 248, 255, 0.08)"/>
|
||||
</fill>
|
||||
<border color="rgba(238, 248, 255, 0.16)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="130" height="28" topLeftX="264" topLeftY="132" type="text" id="bJP">
|
||||
<content textType="caption" fontSize="12" fontFamily="Source Han Sans" color="rgba(115, 255, 235, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>02 / OVERLAP</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="130" height="116" topLeftX="264" topLeftY="174" type="text" id="bJg">
|
||||
<content verticalAlign="top" fontSize="13" fontFamily="Source Han Sans" color="rgba(231, 241, 245, 1)" lineSpacing="multiple:1.3" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>允许图像、暗色块和大字交叠;让重叠发生在低对比背景层,而不是正文层。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="130" height="28" topLeftX="264" topLeftY="304" type="text" id="bJM">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(158, 180, 190, 1)" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Depth, not noise</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="146" height="242" topLeftX="436" topLeftY="112" presetHandlers="0" type="rect" id="bJE">
|
||||
<fill>
|
||||
<fillColor color="rgba(238, 248, 255, 0.08)"/>
|
||||
</fill>
|
||||
<border color="rgba(238, 248, 255, 0.16)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="110" height="28" topLeftX="454" topLeftY="132" type="text" id="bJA">
|
||||
<content textType="caption" fontSize="12" fontFamily="Source Han Sans" color="rgba(115, 255, 235, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>03 / BLEED</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="110" height="116" topLeftX="454" topLeftY="174" type="text" id="bJu">
|
||||
<content verticalAlign="top" fontSize="13" fontFamily="Source Han Sans" color="rgba(231, 241, 245, 1)" lineSpacing="multiple:1.3" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>大字可靠近边界,被画布切出张力;核心内容仍保留完整轮廓。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="110" height="28" topLeftX="454" topLeftY="304" type="text" id="bJR">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(158, 180, 190, 1)" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Intentional crop</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="248" height="42" topLeftX="632" topLeftY="364" type="text" id="bJS">
|
||||
<content fontSize="14" fontFamily="Source Han Sans" color="rgba(238, 247, 250, 1)" bold="true" lineSpacing="multiple:1.1" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Three layers: image, ghost word, readable claim.</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="500" height="24" topLeftX="56" topLeftY="468" type="text" id="bJO">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(148, 170, 181, 1)" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>Rule sheet: keep the readable layer calm, then let the atmosphere get aggressive.</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="292" height="328" topLeftX="612" topLeftY="112" presetHandlers="0" type="rect" id="bJx">
|
||||
<fill>
|
||||
<fillColor color="rgba(1, 5, 13, 0.32)"/>
|
||||
</fill>
|
||||
<border color="rgba(120, 255, 238, 0.42)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
</data>
|
||||
<note id="bJm">
|
||||
<content/>
|
||||
</note>
|
||||
</slide>
|
||||
<slide id="pqE">
|
||||
<style>
|
||||
<fill id="fqE">
|
||||
<fillColor color="rgba(5, 7, 14, 1)"/>
|
||||
</fill>
|
||||
</style>
|
||||
<data>
|
||||
<img id="bJl" src="WYcVbbQfgo5qitxHrsBcJqBJnKh" width="960" height="540" topLeftX="0" topLeftY="0">
|
||||
<crop leftOffset="0" rightOffset="0" topOffset="0" bottomOffset="0"/>
|
||||
</img>
|
||||
<shape width="960" height="540" topLeftX="0" topLeftY="0" presetHandlers="0" type="rect" id="bJI">
|
||||
<fill>
|
||||
<fillColor color="rgba(2, 5, 12, 0.5)"/>
|
||||
</fill>
|
||||
<border color="rgba(2, 5, 12, 0)" width="0"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="892" height="168" topLeftX="-339.39807418048235" topLeftY="203.22902033583466" rotation="1" alpha="0.22" type="text" id="bJt">
|
||||
<content textType="title" fontSize="138" fontFamily="Source Han Sans" color="rgba(238, 250, 255, 1)" bold="true" lineSpacing="multiple:0.9" textAlign="center" autoFit="normal-auto-fit">
|
||||
<p>BLEED</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="270" height="72" topLeftX="793.4022140221402" topLeftY="307" alpha="0.18" type="text" id="bJn">
|
||||
<content textType="headline" fontSize="54" fontFamily="Source Han Sans" color="rgba(142, 255, 239, 1)" bold="true" lineSpacing="multiple:0.9" textAlign="center" autoFit="normal-auto-fit">
|
||||
<p>OFF EDGE</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="516" height="126" topLeftX="62" topLeftY="316" presetHandlers="0" type="rect" id="bJX">
|
||||
<fill>
|
||||
<fillColor color="rgba(5, 12, 24, 0.7)"/>
|
||||
</fill>
|
||||
<border color="rgba(128, 255, 240, 0.46)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="468" height="52" topLeftX="86" topLeftY="336" type="text" id="bJV">
|
||||
<content textType="headline" fontSize="28" fontFamily="Source Han Sans" color="rgba(248, 252, 255, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>让字先离开屏幕</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="454" height="34" topLeftX="88" topLeftY="390" type="text" id="bJi">
|
||||
<content fontSize="14" fontFamily="Source Han Sans" color="rgba(204, 222, 232, 1)" lineSpacing="multiple:1.3" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>越界感留给幽灵字;行动语句、页码、结论仍留在可读安全区。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="232" height="70" topLeftX="646" topLeftY="362" presetHandlers="0" type="rect" id="bJs">
|
||||
<fill>
|
||||
<fillColor color="rgba(122, 255, 238, 0.12)"/>
|
||||
</fill>
|
||||
<border color="rgba(122, 255, 238, 0.38)" width="1"/>
|
||||
<content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
|
||||
</shape>
|
||||
<shape width="196" height="38" topLeftX="664" topLeftY="378" type="text" id="bJD">
|
||||
<content fontSize="13" fontFamily="Source Han Sans" color="rgba(226, 245, 249, 1)" lineSpacing="multiple:1.2" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>大胆设计可以失控,但信息层不能失联。</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="270" height="28" topLeftX="62" topLeftY="60" type="text" id="bJq">
|
||||
<content textType="caption" fontSize="12" fontFamily="Source Han Sans" color="rgba(115, 255, 235, 1)" bold="true" lineSpacing="multiple:1.0" textAlign="left" autoFit="normal-auto-fit">
|
||||
<p>TYPE SPECIMEN / 03</p>
|
||||
</content>
|
||||
</shape>
|
||||
<shape width="154" height="22" topLeftX="744" topLeftY="474" type="text" id="bJe">
|
||||
<content textType="caption" fontSize="10" fontFamily="Source Han Sans" color="rgba(154, 176, 187, 1)" lineSpacing="multiple:1.0" textAlign="right" autoFit="normal-auto-fit">
|
||||
<p>Ghost typography sample</p>
|
||||
</content>
|
||||
</shape>
|
||||
</data>
|
||||
<note id="bJp">
|
||||
<content/>
|
||||
</note>
|
||||
</slide>
|
||||
</presentation>
|
||||
@@ -1,104 +0,0 @@
|
||||
---
|
||||
name: kimi-slides
|
||||
description: 创建和编辑 PPTX 格式的演示文稿。此技能定义了 .pptd 中间格式来简化 OOXML 操作。任何涉及生成或编辑 PPTX 文件的任务都必须使用此技能,而不是其他方法。该技能还可以用于读取上传的PPTX文件并将PPTX文档转换为图像。当用户请求信息图或海报而不指定图像或 HTML 格式时,该技能同样可以用于将其创建为 PPTX 文件。
|
||||
---
|
||||
|
||||
# 定义
|
||||
kimi-slides 是 Moonshot AI 作为第一方技能构建的 ppt 生成技能。它定义了 YAML 格式的中间 DSL (.pptd),进一步抽象了 OOXML,使演示文稿生成变得毫不费力。 DSL 可用于生成和渲染 PPT,现有的 pptx 文件也可以转换为此 DSL 进行编辑。
|
||||
|
||||
## pptd 格式
|
||||
.pptd 格式是 OOXML 上的简化抽象层,遵循基本 YAML 语法。这种抽象保留了 OOXML 的核心内容(主题、页面布局、元素位置和定义等),同时删除了复杂的嵌套逻辑,例如 Masters;每个页面都是独立的——所见即所得。请阅读 reference/pptd.md 以了解此 DSL 的完整定义。
|
||||
|
||||
## 配套 CLI
|
||||
该技能还附带一个配套的 CLI 工具(预安装在环境中),用于 pptd 和 pptx 之间的双向转换、pptd 验证、pptd/pptx 屏幕截图渲染等。
|
||||
请阅读 reference/cli.md 以获取完整的 CLI 使用说明。
|
||||
|
||||
## PPT制作流程
|
||||
|
||||
### 步骤1。仔细阅读上下文
|
||||
阅读**用户上传的所有文件**、提供的URL以及pptd格式指南“reference/pptd.md”,以充分了解用户的需求。
|
||||
|
||||
### 步骤2。了解用户的需求
|
||||
根据上下文了解用户的需求:
|
||||
1.首先确定请求的目的
|
||||
- 创建 PPT:创建一个新的演示文稿(从头开始,或从现有的 pptx 模板)
|
||||
- 编辑PPT:编辑用户上传的PPT(本地修改、单页美化等)
|
||||
- 复制 PPT:将演示文稿从非 pptx 格式(图像、PDF 等)复制为 pptx 格式
|
||||
|
||||
2.然后确定设计方向
|
||||
- 自主设计:没有偏好,或者只给出简单的风格约束;您需要填写或创建设计
|
||||
- 设计系统:指定技能预设的设计系统,或者用户提供涵盖所有颜色、字体、布局和组件规格的完整详细的设计方案
|
||||
- 使用模板:提供了模板并且必须使用
|
||||
- 风格迁移:提供风格参考源(图片、网页等)
|
||||
|
||||
3.然后确定输入类型
|
||||
- 仅主题:仅给出PPT主题方向或演示的内容要求,没有具体内容
|
||||
- 完整文档:用户提供完整的文档(论文、研究报告、新闻稿等)
|
||||
- 大纲:用户提供逐页大纲、演讲稿或类似内容
|
||||
* 当“用户输入类型”为【完整文档】或【大纲】且未指定是否允许扩展时:由于逐页大纲、演讲稿或用户文档很难支持演示的全部内容,优先使用搜索来扩展更相关的材料、案例等,除非用户明确表示不扩展
|
||||
|
||||
4. 页数
|
||||
- 如果用户请求特定页数,则用户的要求优先
|
||||
- 提供逐页大纲/脚本:与大纲/脚本中的页数匹配
|
||||
- 当提供完整且相对结构化的文档时:使用ask工具与用户确认一页应涵盖多少文档内容,并给出预计的总页数;当仅提供主题时:使用询问工具建议建议的页数并与用户确认#### 澄清和后续问题
|
||||
当出现以下情况时,通过提问工具解决
|
||||
1. 要求不明确
|
||||
- 用户的意图不明确或难以理解
|
||||
- 用户提供的文件/URL 无法访问
|
||||
2. 意图冲突
|
||||
- 用户的意图相互矛盾。例如:
|
||||
* 选择设计系统的同时还要求与该设计系统完全不一致的样式(例如,使用麦肯锡样式,同时要求页面上有大面积的空白)/使用模板/引用图像样式
|
||||
* 请求“制作 10 页”和“交付 30 多页输出”
|
||||
3、无法自行确定用户的需求
|
||||
- 当目的、设计方向、输入类型、页数等难以自行确定时
|
||||
|
||||
### 步骤3。根据用户需求生成演示文稿
|
||||
|
||||
在生成之前,首先阅读“reference/pptd.md”以了解 pptd 格式定义和约束,并阅读“reference/cli.md”以了解如何使用配套的 CLI
|
||||
|
||||
#### 复制 PPT
|
||||
- 分析图像以估计元素位置、字体和大小等,并**尽可能地按 1:1 复制**。
|
||||
- 当图像包含难以直接复制且无法用图标/形状近似的元素(例如照片、头像)时,您可以使用bash或python等工具对原始图像进行裁剪和截图
|
||||
|
||||
#### 编辑 PPT
|
||||
- 将用户上传的pptx文件转换为.pptd格式
|
||||
- 截取转换后的文件的屏幕截图,然后拼接并压缩屏幕截图以获得概览。之后单独阅读几个关键页面。
|
||||
- 找到要编辑的页面,并注意不要影响预期范围之外的部分。
|
||||
> `kimi-slides Convert` 命令并不是完美的无损转换。如果用户后期报告格式错误、乱码内容等,与原pptx进行比对,并参考比对修复pptd
|
||||
|
||||
#### 生成 PPT
|
||||
制作PPT时,针对不同的用户采取不同的制作方式【设计方向】
|
||||
#####自主设计
|
||||
1.阅读设计指南`reference/slides_categories.md`,并阅读用户查询对应的场景文档
|
||||
2. 根据以上内容制作演示文稿
|
||||
|
||||
#### 生成其他格式的内容
|
||||
- 当用户明确要求信息图、海报或高度视觉化的单页设计时,请阅读“reference/general-poster.md”并将其实现为单页或几页可编辑的 PPTD;当用户只要求一张图片时,仍然先用PPTD构建,然后通过截图或渲染的方式输出图片。对于普通 PPT 请求,请勿加载此参考文件。
|
||||
|
||||
#####设计系统
|
||||
1.阅读《reference/slides_categories.md》指南的一般约束部分,并阅读用户查询对应的场景文档作为设计基础
|
||||
2. 阅读正在使用的设计系统文档作为演示风格。严禁引用或混用其他设计风格
|
||||
3. 参考上述内容制作演示文稿
|
||||
|
||||
##### 使用模板
|
||||
1.使用`kimi-slides Convert`将用户上传的pptx文件转换为pptd形式
|
||||
2. 对转换后的文件进行截图,然后拼接并压缩截图以概览,了解模板的视觉风格(配色方案、字体风格、元素特征、布局特征、内容密度等)
|
||||
3. 识别页面类型;重点阅读特殊页面,例如封面、摘要页面和章节分隔符(单页屏幕截图、.page 文件),提取其页面布局、内容结构、可重用组件(图标、形状、smartart、可重用正文布局方案等)和元素样式(例如空格/线条/卡片分隔符、方角/圆角等)
|
||||
4. 使用模板制作演示文稿##### 风格转移
|
||||
1. 分析参考文件的视觉风格(配色方案、字体风格、元素特征、布局特征、内容密度等)、页面布局、内容结构、可重用组件(图标、形状、smartart、可重用正文布局方案等)和元素样式(例如,空白/线条/卡片分隔符、方角/圆角等)。
|
||||
- 如果用户提供样式参考URL,不要只阅读文本内容;多参考和学习页面的视觉效果,有助于理解风格
|
||||
2. 使用参考文件的风格特征制作演示文稿。我们鼓励您重复使用原始 pdf/url 中的插图、字体、字体大小层次结构、元素等
|
||||
|
||||
### 步骤4。 PPT验证
|
||||
1. 使用 kimi-slides check 命令对生成的文件进行多轮验证和修复
|
||||
2.使用`kimi-slides snapshot`进行截图,
|
||||
- 截图后,先拼接并压缩单页截图,以便快速概览
|
||||
- 细化有问题的页面并运行多轮验证和修复
|
||||
|
||||
注意:当前的屏幕截图 CLI 可能存在错误,导致结果与实际渲染不同。已知问题:
|
||||
1.图标退化成圆圈
|
||||
2. 渐变文本可能会降级为纯色
|
||||
这些问题仅存在于截图CLI中,不影响用户交付;它们可以被忽略。
|
||||
|
||||
###第5步。 PPT交付
|
||||
使用 kimi_ref 将 .pptd 文件传送给用户。该路径直接指向.pptd文件; **严格禁止转换为 pptx 格式进行交付**。用户使用 .pptd 文件的唯一方法是单击下面的卡片进入编辑器,在其中可以预览、编辑、演示和导出为 pptx 格式。
|
||||
@@ -1,113 +0,0 @@
|
||||
# kimi-slides cli
|
||||
|
||||
本节列出了 kimi-slides CLI 支持的所有命令。这篇文档是权威参考! `kimi-slides --help` 返回的指南不正确 - 无需运行该命令。
|
||||
|
||||
## 转换
|
||||
|
||||
```bash
|
||||
kimi-slides convert path/deck.pptx
|
||||
kimi-slides convert path/deck.pptx -o path/output/
|
||||
```
|
||||
|
||||
`-o, --output`:指定输出目录;选修的。如果省略,则会在输入旁边创建一个同名(不带扩展名)的目录:
|
||||
|
||||
```text
|
||||
path/deck.pptx -> path/deck/
|
||||
```
|
||||
|
||||
输出目录结构:
|
||||
|
||||
```text
|
||||
deck/
|
||||
deck.pptd
|
||||
pages/
|
||||
page-1.page
|
||||
media/
|
||||
<hash>.png
|
||||
```
|
||||
|
||||
> 转换过程中,PPTX 中嵌入的图像将被提取到 `media/` 目录中,并且页面文件中的图像路径将被重写为相对于输出目录的路径:
|
||||
|
||||
```yaml
|
||||
src: ./media/<hash>.png
|
||||
```
|
||||
|
||||
## 检查
|
||||
|
||||
```bash
|
||||
kimi-slides check path/deck
|
||||
kimi-slides check path/deck -p 1,3 -s all
|
||||
kimi-slides check path/deck -p 2-10 --level keep
|
||||
kimi-slides check path/deck --level auto
|
||||
```
|
||||
|
||||
`check` 的输入必须是包含 `.pptd` 文件的目录。默认情况下,它检查主 `.pptd` 文件和所有页面文件。
|
||||
|
||||
参数:
|
||||
|
||||
- `-p, --page <spec>`:指定页码,从1开始;默认为所有页面。支持`3`、`1,2`、`2-10`。
|
||||
- `-s, --severity <spec>`:指定要输出的问题。支持 `all`、`error`、`warning` 以及特定问题类型,例如`MissingField,SrcNotFound`。
|
||||
- `--level <level>`:处理级别。 `keep` 只检查不修改; `auto` 尝试安全修复。
|
||||
|
||||
目前已检查以下问题:
|
||||
- `YamlParseError`:每个检查的文件必须使用 YAML 解析器正确解析。
|
||||
- `FileReadError`:对输入目录、`.pptd` 文件、页面文件或资源路径的访问异常。
|
||||
- `MissingField`:缺少必填字段,例如`.pptd.pages`、`.pptd.size` 或必需的元素字段。
|
||||
- `InvalidType`:字段类型与其定义不匹配,例如字符串、数字、布尔值、元组、数组、填充、边框等。
|
||||
- `OutOfRange`:字段值超出可接受范围,例如非正页面大小、非正边界、非法枚举值。
|
||||
- `InvalidTheme`:引用的主题令牌不存在,例如`$primary` 不存在于 `theme.colors` 中。
|
||||
- `PageNotFound`:`.pptd.pages` 引用的 `.page` 文件不存在,或者指定的页码超出范围。
|
||||
- `UnknownField`:发现当前模式无法识别的字段。
|
||||
- `BoundsOutside`:元素的边界超出页面尺寸。
|
||||
- `SrcNotFound`:`src`引用的本地资源(图像、图像填充、自定义字体等)不存在。
|
||||
- `TextOverflow`:文本可能会溢出其文本框。
|
||||
- `TextUnderFill`:文本可能占据文本框高度的 50% 以下。
|
||||
- `TextOcclusion`:文本可能会被稍后绘制的元素遮挡。
|
||||
- `TextDrift`:文本框可以跨越其下方元素的边界。
|
||||
|
||||
> 文本相关检查当前使用具有模拟文本尺寸的启发式方法,因此可能存在一些偏差。
|
||||
|
||||
`--level auto` 仅执行确定性修复:
|
||||
|
||||
- 简单的类型转换,例如`"12"` 为数字,`12` 为字符串,`"true"` 为布尔值。
|
||||
- 删除无效的可选字段。
|
||||
- 删除无法安全修复的无效元素。
|
||||
- 重写修复文件的 YAML 格式。
|
||||
|
||||
`check` 以以下格式输出问题:
|
||||
|
||||
```text
|
||||
[whether deterministically fixed][issue type:issue name] file path id="element id" issue details
|
||||
[fixed: false][Error:InvalidType] pages/page-1.page id="title" Expected 4 numbers for elements[0].bounds
|
||||
[fixed: false][Warning:TextOverflow] pages/page-2.page id="body" Text may overflow its bounds
|
||||
```
|
||||
|
||||
## 截图
|
||||
|
||||
```bash
|
||||
kimi-slides screenshot path/deck -o path/screenshots/
|
||||
kimi-slides screenshot path/deck -p 1,3,5 -o path/screenshots/
|
||||
kimi-slides screenshot path/deck -p 2-6 -o path/screenshots/
|
||||
```
|
||||
|
||||
`screenshot` 的输入必须是包含 `.pptd` 文件的目录。此命令将 `.pptd` 演示文稿渲染为图像以检查页面的视觉结果。
|
||||
|
||||
`-o, --output` 是可选的。如果省略,则会在输入旁边创建一个屏幕截图目录:
|
||||
|
||||
```text
|
||||
path/deck/ -> path/deck-screenshots/
|
||||
```
|
||||
|
||||
输出目录结构通常为:
|
||||
|
||||
```text
|
||||
pages/
|
||||
page-1.png
|
||||
page-2.png
|
||||
page-3.png
|
||||
```
|
||||
|
||||
参数:
|
||||
|
||||
- `-p, --page <spec>`:指定页码,从1开始;默认为所有页面。支持`3`、`1,2`、`2-10`。
|
||||
- `-o, --output <path>`:指定截图输出目录。
|
||||
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -1,165 +0,0 @@
|
||||
# 组织名称 — 生产力回报风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
面向高管、战略团队和研究读者的咨询思想领导报告式 PPT。支持可读的长文本、定量图表、版块导航,具有品牌化、前瞻性的视觉感受;它不是一个低字数的销售推介页面或视觉海报式的单页页面。
|
||||
|
||||
核心页面语法:
|
||||
|
||||
1. **浅蓝色报告页**:柔和的淡蓝色-浅紫色渐变背景,带有黑色长文本、多栏解释和紫色关键数字。
|
||||
2. **黑色背景证据页**:图表位于大型黑色画布上,白色文本和紫色/粉色/橙色条或线形成强烈对比。
|
||||
3. **深色摄影部分分隔线**:封面和部分过渡使用“前进路径”图像,例如光迹、山脊线/道路/人物。
|
||||
4. **固定报告骨架**:底部有白色部分导航条,左下角有紫色“>”身份标记,右边缘有垂直的报告名称,右下角有页码。
|
||||
|
||||
基本原则:
|
||||
|
||||
1. **证据优先于视觉戏剧**:图表、来源、数字和结论必须可读;黑色背景、紫色和摄影光效果不得模糊数据或削弱对比度。
|
||||
2. **高密度必须有稳定的骨架**:允许多个文本段落、多列模块和复杂的图表,但每个页面必须有标题区域、正文区域、页脚导航和足够的填充——避免自由浮动堆叠。
|
||||
3. **页面变化服务章节节奏**:摄影页面用于开场和章节过渡,黑色背景页面用于模型/数据证据,浅蓝色页面用于解释和建议;不要仅仅为了多样性而在每个页面上发明一个新模板。
|
||||
|
||||
## 2. 页面骨架- **画布比例**:宽屏 16:9;使用完整的矩形画布——没有肖像或社交媒体比例。
|
||||
- **默认边距**:内容页保留相对较宽的外边距;机身从左上向右中延伸,底部预留固定导航带。黑色图表块和摄影块可以几乎全出血,但仍必须避免页脚和页码。
|
||||
- **后台系统**:内容页面和目录采用淡蓝色、冰蓝色、浅紫色渐变;图表页面在浅蓝色页面上覆盖一个黑色矩形证据区域,或者让单个黑色块占据页面的大部分;部分/封面使用全出血深色摄影。
|
||||
- **标题区域**:通常位于左上角。浅蓝色页面上的黑色或紫色;黑色背景图表页面上黑色区域内的白色;放置在摄影页面上的黑暗或平静区域。
|
||||
- **正文区域**:分为文字说明区域、图表证据区域、图像区域和数字强调区域。内容页面大多为2-3栏;图表页面通常是一个大的黑色背景图表+说明性侧边栏;案例页面通常是文本+右侧或上方的照片。
|
||||
- **标题**:没有强大的标题栏;报告名称在右边缘显示为垂直文本,而不是水平顶部标题。
|
||||
- **页脚导航**:大多数非封面页面底部都有一个白色导航带。左下角有一个紫色的“>”符号;部分名称水平布置在中间,并用细垂直线分隔;当前部分更暗或更粗;右下角的页码。保持其高度低、线条细且位置稳定。
|
||||
- **右侧垂直报告名称**:内容页面、目录和某些图表页面将报告标题/副标题放置在右侧边缘。不要让它成为显眼的装饰——它应该小而不引人注目。
|
||||
- **徽标**:在封面右下角,使用完整的品牌文字标记(呈现为纯小文本,写新主题名称;不要绘制图形徽标);在内容页面上,紫色“>”身份标记主要取代了完整的文字标记;封底或封页可以恢复完整的品牌文字标记。
|
||||
- **目录页**:浅蓝色渐变背景,标题位于左上角,章节条目布置在多个水平列中;页码范围放置在章节标题上方或附近;关键部分可能带有小缩略图网格;列由细垂直线分隔。
|
||||
- **作者页**:浅蓝色渐变背景,左上角紫色标题;作者头像排列在网格中——头像、姓名、标题和一个小的专业社交平台图标形成了标准的作者卡。
|
||||
- **默认阅读路径**:左上角标题/引入→正文左栏文本→中心图表或数字→右栏补充/照片→确认该部分的底部导航。
|
||||
|
||||
## 3. 内容组织- **标题风格**:使用报告风格的陈述——判断语句、章节名称或“图X”图表标题;重要的部分分隔页可以使用大标题来提出问题或方向。不要追逐简短而有力的口号。
|
||||
- **关键信息句子**:在标题下方或图表上方放置 1-2 个解释性句子,说明发现、定义或如何阅读图表。除非是剖面摄影页面,否则不要将整个页面变成一个大的陈述句。
|
||||
- **正文组织**:短段落,左对齐;单个段落的长度为中短;每栏 2-4 个段落。避免长而不间断的文字墙。
|
||||
- **分组逻辑**:包括“问题—证据—含义”、“模型要素”、“行为/推荐列表”、“案例解释”、“数字比较”;使用列、浅色卡片、黑色背景区域或细分隔线来分隔它们,而不是使用大量图标。
|
||||
- **每页点数**:一个内容页包含1个主要主题加2-4个支撑点;高密度页面可以包含多个子模块,但必须有一个统一的整体标题,布局清晰。
|
||||
- **项目符号风格**:更喜欢小标题+段落解释;谨慎使用传统的项目符号列表;列表项应该简短,缩进要克制。
|
||||
- **关键字强调**:使用粗体、紫色数字或紫色副标题;避免浓重的彩色突出显示。每个模块最多突出显示 1-2 个关键字或数字。
|
||||
- **数字/单位/时间范围**:大量数字可能单独作为紫色或黑色信息块,并附有说明;百分比、倍数和时间范围必须与图表标签保持一致。数字不得用装饰性图形代替。
|
||||
- **来源脚注**:将小的来源/注释放置在图表和研究页面的底部或图表边缘,对比度低但可读。来源不得进入主标题层次结构,也不得用装饰颜色来吸引注意力。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A.浅蓝色多栏报告页面
|
||||
|
||||
用于概念解释、背景、建议、案例文本和少量关键数据。淡蓝色/浅紫色渐变背景,标题位于左上角,正文分为 2-3 列:左列包含引言或主要论点,中间列包含补充段落/项目符号,右列包含大量数字、图像或简短结论。使用紫色数字、粗体副标题和清晰的列宽来建立焦点并保持段落间距。如果需要复杂的数据比较,不要强迫它进入这种布局 - 切换到黑色背景图表页面。
|
||||
|
||||
### B. 黑色背景图表证据页+解释性侧边栏
|
||||
|
||||
适用于复杂的条形图、折线图、分组比较、模型推导和“图”型证据。一侧或中心是一个大的黑色矩形图表区域,内部有白色标题和说明文字;另一侧保留浅蓝色文本栏,以黑色正文文本解释调查结果。图表区域是视觉重心;使用紫色/粉色/橙色数据系列、白键注释和局部箭头/连接线。如果图表仅包含一个简单的数字,请不要使用大的黑色背景 - 它会感觉太重。
|
||||
|
||||
### C. 摄影部分/问题页面
|
||||
|
||||
用于封面、章节过渡、重要问题或临时结论。使用全出血暗摄影——山脊线、道路、剪影、科技设备、紫色光迹很常见;一个大的白色标题位于一个黑暗的区域上,还有一个较小的副标题。除了必要的标题、简短说明和品牌文字标记(纯小文本;不要绘制图形徽标)之外,不要覆盖长正文或复杂的图表。
|
||||
|
||||
### D.文字+图片案例页面用于行业案例、场景描述或具体证据。真实照片或部分设备/场景位于右侧或上方的大矩形区域中;文本位于左侧或旁边的列中。该图像不作为整页背景,也不使用任何插图。如果照片背景很杂乱,请勿将文本直接放在其上 - 将文本放在浅色区域。
|
||||
|
||||
### E.高密度模块/框架页面
|
||||
|
||||
适用于 3-5 个行为、元素、阶段或模型组件。使用水平卡片、垂直列、紫色底部标签或黑色背景公式区域进行组织;每个模块包含一个小标题、一个简短的解释段落以及必要的数字或关键字。当模块较多时,使用等宽的列和一致的padding;不要用复杂的 SmartArt 装饰代替文本逻辑。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- **浅蓝色背景**:大约#D6ECFE / #E4F4FF / #DFF3FF。用于内容页、目录、作者和案例页的大背景区域;可能会逐渐渐变为浅紫色,但不得变得过饱和。可替代性低——这是该风格的核心。
|
||||
- **浅紫色背景过渡**:大约#DAE3F9 / #D8E3FA / #C2A1BC。用于背景渐变边缘、卡底或局部氛围;中低区域。同一色系内可调整;不建议改用大的暖色底色。
|
||||
- **黑色/近黑色证据基础**:大约#08151C / #201F20 / #000000。用于图表画布、公式模型、暗部页面的文本承载区域;区域范围可以从半页到大部分页;不得破碎成碎片与浅蓝色正文文本混合。
|
||||
- **主要文本黑色**:大约#111111 / #201F20。用于淡蓝色背景上的正文、副标题、页脚导航和表格文本;高对比度是强制性的。不得以浅灰色代替作为主体颜色。
|
||||
- **反转白色文本**:#FFFFFF 或接近白色。用于黑色背景图表、摄影页面标题和封面标题;仅在深色背景上。
|
||||
- **品牌紫色/强调紫色**:大约#A100FF / #8A00E6 / #7F35FF。用于“">”身份标记、关键数字、关键图表系列、标签和一些副标题;中小面积。可以向品牌的原色进行微调,但不得用多种不相关的鲜艳颜色代替。
|
||||
- **粉紫色/洋红色数据颜色**:大约#D000FF / #C000C8。用于图表或黑色背景卡片中的第二个强调系列;仅提供数据分组服务,不提供大块正文文本。
|
||||
- **橙色/珊瑚色数据颜色**:大约#D38778 / #D7AA9F。用于封面灯光效果、图表中的对比系列或暖色调;面积小;不得成为主体颜色。
|
||||
- **灰色支持**:大约#8C95A4 / #A8ABBC / #C7CAD6。用于轴、分隔符、注释、非键标签和导航分隔符;保持不显眼。
|
||||
- **桌子底座/灯卡底座**:大约#E8F3FC / #DCEFF8。用于高密度表格或信息框,与背景略有区别。
|
||||
|
||||
保留蓝紫色系列 + 黑/白/灰 + 1 种暖色数据中的单个页面。状态颜色不突出;如果需要正/负状态,则更喜欢文本标签和紫色/灰色深度变化,并谨慎引入红色/绿色。
|
||||
|
||||
## 6. 字体和文本层次结构- **字体字符**:正文主要使用Arial系列无衬线字体;标题、章节或目录条目可以使用 Times New Roman 类衬线;少量粗体投石机用于特殊标题或品牌文本。总体上避免使用手写体、手写体和圆形卡通字体。
|
||||
- **正文**:1x,用于段落、解释、作者标题和页脚导航。
|
||||
- **脚注/来源/页码**:大约0.6–0.8x,低调但可读;页码不应放大为装饰数字。
|
||||
- **副标题/模块标题**:大约 1.1–1.4 倍,可能是粗体或紫色。
|
||||
- **图表标签/轴**:约 0.7–0.9x,黑色背景上白色/灰色,浅色背景上黑色/灰色;保持清晰,不与头衔竞争。
|
||||
- **关键信息句子/重点强调**:约 1.2–1.6x,位于标题和正文之间。
|
||||
- **页面标题**:大约 2–3x,在内容页面上左上角对齐;目录/作者页标题可能会稍大一些。
|
||||
- **封面/部分大标题**:约 3–5 倍,白色或黑色,具体取决于背景;整个页面的第一个视觉层次。
|
||||
- **大数字**:约 2.5–4x,通常是紫色,搭配 0.9–1x 说明文字;数字必须带有单位或上下文。
|
||||
|
||||
文本主要是左对齐的。居中的副本可能仅出现在封面或摄影部分的页面上;避免内容页面上出现大的居中块。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
主要图表类型有条形图、分组条形图、折线/趋势图、公式/模型类型图以及少量矩阵比较。
|
||||
|
||||
- **图表画布**:更喜欢黑色或接近黑色的矩形,边缘干净,没有粗边框或阴影;浅蓝色文本区域的清晰边界。
|
||||
- **标题和编号**:将“图 X”加上解释性标题放在图表区域的左上角,为白色;标题应说明阅读图表的目的,而不仅仅是图表类型。
|
||||
- **轴和网格线**:使用低亮度的细灰色线,数量较少。不要用密集的网格产生噪音。
|
||||
- **数据系列**:主要是紫色、粉紫色和橙色/珊瑚色;非重点系列采用灰色或深紫色;系列的数量应该受到限制——避免彩虹色。
|
||||
- **标签**:关键数据可以直接标记在条形顶部或线附近;白色或浅灰色的小标签。避免所有标签堆放得难以辨认。
|
||||
- **图例**:小而清晰,放置在图表右侧或上方;色样/线段必须与系列相匹配。
|
||||
- **关键注释**:白色箭头、弯曲连接器、括号或简短注释可能会强调趋势/差距;注释必须服务于结论,而不是装饰。
|
||||
- **预测/目标/基线**:使用虚线、细线或带有文字说明的单一强调色;不要用大的半透明带覆盖数据。
|
||||
- **边界**:在图表很少的页面上不强行插入图表;当没有数据时,使用文本模块或数字卡代替。
|
||||
|
||||
## 8. 表格和高密度信息- **标题**:使用黑底白字、浅紫色/浅蓝色黑字或粗体文字;限制高度,左对齐文本。
|
||||
- **行/列结构**:用细灰线或浅色基块分隔;避免重型网格;按重要性分配列宽——第一列可能稍宽。
|
||||
- **行高**:容纳 1-3 行文本;高密度页面可以压缩,但文本绝不能接触线条或重叠。
|
||||
- **基色**:普通细胞为浅蓝色/浅紫色,高对比度证据表为黑色背景;如果使用斑马条纹,它应该非常微妙。
|
||||
- **关键行/列**:用紫色文本、紫色条、粗体或深色底色上的反白突出显示;不要同时使用多种状态颜色进行标记。
|
||||
- **数字对齐**:数字、百分比、金额和年份按列对齐,并使用统一单位;解释性文字左对齐。
|
||||
- **符号/评级点**:可以使用简单的点、短划线、勾号或紫色标记;它们必须是低装饰且明确的。避免使用彩色图标评级系统。
|
||||
- **摘要行**:使用粗体、顶部分隔线或深色底座处理 - 无凸起阴影。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
- **底部导航**:细白带+紫色“>”+水平部分名称+细垂直线+页码;不得被正文内容覆盖。
|
||||
- **右侧垂直报告名称**:小,靠近边缘,对比度低。不要把它变成一个大的侧边栏。
|
||||
- **紫色“>”品牌符号**:用于左下角并用于本地品牌提示;面积小——不能成为到处使用的大装饰箭头。
|
||||
- **标题栏/图表标题块**:位于黑色图表区域的左上方,由“图X+结论标题”组成,白色文字分层,紧凑,左对齐。
|
||||
- **数字强调块**:大的紫色百分比/值+短的黑色说明文字;通常每页 1-3 个;避免过度使用。
|
||||
- **浅色信息框**:用于“工作中的知识”式补充、定义、提示或行动建议;底色比背景稍深,填充稳定,文字密度中等。
|
||||
- **紫色标签/卡座**:用于模型或行为页面上的简短推荐或关键句子;适合作为黑色区域内的重点强调,但保持较低的计数。
|
||||
- **图片框**:矩形真实照片,边缘干净,无粗边框;可以占据页面的一半或三分之一,与文本分开。
|
||||
- **流程箭头/模型符号**:使用简单的箭头、乘号、加号、连接线来表达公式或关系;线条细,颜色主要有白色、灰色、紫色。
|
||||
- **图标**:仅使用一些功能性小图标,例如专业社交平台标记。不要扩展到彩色图标系统。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
一种扁平、内敛、类似报告的技术咨询风格,通过深色摄影、黑白对比、紫色品牌口音和数据图表来构建视觉焦点——而不是复杂的装饰。
|
||||
|
||||
**禁止**:
|
||||
|
||||
- 表情符号、卡通贴纸、社交媒体风格的图标、大型彩色插图。
|
||||
- 3D 字体、立体按钮、发光轮廓、浓重阴影、玻璃形态卡片。
|
||||
- 复杂的纹理背景、华丽的边框、彩虹渐变、无意义的装饰线条。
|
||||
- 大块居中的正文、放置在繁忙照片上的文本、低对比度的浅灰色正文。
|
||||
- 没有数据源的假图表,纯粹为了美观而添加的仪表板组件。
|
||||
|
||||
**谨慎使用**:- 渐变:仅浅蓝色-浅紫色背景或摄影光轨氛围;没有强烈的多色渐变。
|
||||
- 阴影:源文件几乎不依赖阴影;生成时避免它们;如果用于图像分层,请保持它们非常微妙。
|
||||
- 圆角:不是主要特征;卡片和图像应该有方形或略圆的角——不要成为圆角的 SaaS 风格。
|
||||
- 彩色图标:仅在功能标记中可见,例如作者页面上的专业社交平台图标;不应在正文页面中大量使用。
|
||||
- 照片:可用于内容中的案例或场景,但必须分区;不要将每一页都设置为摄影背景。
|
||||
|
||||
**可在特殊页面上使用**:
|
||||
|
||||
- 全出血深色照片、强光轨迹、大白色标题:用于封面和部分过渡。
|
||||
- 大黑色背景:用于图表证据、模型推导和深色结束/附录样式页面。
|
||||
- 完整的品牌字标(纯小文字,写新主题名称;不绘制图形标志):更适合封面和封底;内容页面主要使用紫色“>”。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 是 16:9 的报告页而不是海报/社交卡吗?
|
||||
- 页面类型是否明显属于以下之一:浅蓝色内容页、黑色背景证据、摄影部分、案例文本+图像、作者/目录?
|
||||
- 是否保留底部白色导航带,左下角有紫色“>”、章节名称和页码;内容页面右侧是否有垂直报告名称?
|
||||
- 标题是否左对齐且采用报告格式;正文中的段落是否过长?
|
||||
- 调色板是否以蓝紫黑白灰为主,暖色仅补充少量数据/光迹?
|
||||
- 紫色是否用于品牌、数字和关键点,而不是大面积泛滥?
|
||||
- 图表是否有标题、标签、图例/来源以及黑色背景的足够对比度?
|
||||
- 表格是否使用细线、统一对齐、基色克制、数字单位一致?
|
||||
- 图像是否是真正的摄影,并且与文本分开,没有将长文本压在繁忙的背景上?
|
||||
- 是否避免使用表情符号、3D、重阴影、复杂装饰、彩虹色和无意义的图标?
|
||||
- 您是否检查过文本溢出、截断、乱码、重叠、低对比度、遮挡、空内容、覆盖页脚和错误比例?
|
||||
- 每一页是否都有一个主要主题和清晰的阅读路径,且不会因模板重复或元素堆叠而削弱信息?
|
||||
- 默认情况下没有卡片:除非用户明确请求,否则严格禁止使用圆角矩形或矩形卡片来构建层次结构或对齐方式:线段、空白和字体大小差异是更好的解决方案
|
||||
- 无均分组合:除非没有其他可用的布局,否则不要默认三向分割、四向分割或 2×2 矩阵。这包括三部分结论,例如三栏+标题+结论
|
||||
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -1,198 +0,0 @@
|
||||
# PJ Mountain — 业务 DD 报告(组织名称)风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
适用于商业尽职调查、市场研究、战略诊断、管理决策报告等高信息密度场景。它使用白色风景画布、黑色结论标题、薄灰色分隔线、深海军蓝结构块、蓝色数据强调以及密集的图表和表格。标题区写明“本页判断/分析主题”;条形图、堆积条形图、折线图、热图、矩阵和多列文本提供了证据;页脚固定地包含来源、公司名称和页码。大图像、插图和装饰图形很少出现;品牌颜色只服务于结构和强调,而不服务于氛围。
|
||||
|
||||
**基本原则**
|
||||
|
||||
1. **证据优先于装饰。** 必须首先保留图表、表格、脚注、来源和测量基础注释;如果页面拥挤,请压缩装饰和空白,而不是删除数据标签或源。
|
||||
2. **一致性优先于每页变化。** 允许使用右侧解释栏、左侧深色意见栏、目录突出显示或基于灰色的解释区域等变体,但标题区域、页脚、分隔符、字体层次结构和品牌颜色语法必须保持稳定。
|
||||
3. **品牌颜色具有可读性。** 结构和关键类别采用深海军蓝;亮蓝色表示数据强调和选定状态;当品牌颜色降低对比度或混淆数据时,首选黑色、灰色、白色和清晰的标签。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
**画布比例:** 宽屏 16:9 横向。默认白色背景;目录页和说明栏可以使用浅灰色;部分/意见页面可以使用深海军蓝垂直块。内容页面不使用全屏图像背景。
|
||||
|
||||
**边距和网格:**保持左右外边距稳定;标题区域位于顶部,正文的矩形工作区位于标题规则下方,固定的页脚区域位于底部。正文通常使用 2 列、3 列、图像-左-文本-右、表格-左-注释-右或图表-上-上-脚注-下。
|
||||
|
||||
**标题区:**左上角的主标题通常为1-2行,采用大号黑色衬线粗体或粗体标题样式;下面是无衬线副标题,说明主题、范围、单位或测量基础。标题下的一条细灰色/黑色水平线分隔了证据区域。
|
||||
|
||||
**正文区域:**包含图表、表格、流程图/结构图、并行模块和解释性文本。通常主要证据位于左侧;右侧的解释、背景、结论或补充材料;多模块页面从上到下、从左到右阅读。
|
||||
|
||||
**页脚:**固定包含来源/脚注、公司名称和页码。来源位于左下方或图表下方,其尺寸明显小于正文; “组织名称”和页码位于右下角。页脚上方通常有一条细线将正文分开。
|
||||
|
||||
**徽标和品牌:** 封面的左上角带有品牌文字标记,以纯小文本形式显示新主题名称或留空 - 未绘制图形徽标;内容页面通过页脚公司名称来传达品牌,而不是通过在每个模块中重复放大的徽标。
|
||||
|
||||
**页面类型:**- **封面:**左侧包含品牌字标、项目名称、报告类型、日期和保密声明;右侧采用蓝色线性曲线图形作为唯一强烈的视觉元素。封面不使用内容页页脚和页码结构。
|
||||
- **目录页:**大尺寸内容位于左侧白色区域,章节结构位于右侧浅灰色区域;当前部分可能会用深海军蓝水平条突出显示。 TOC页面强调导航,没有数据图表。
|
||||
- **内容页:**以白色背景为主,顶部结论标题+规则+正文证据+底部来源/页码。
|
||||
- **部分/进度页面:**重用内容结构,并用深海军栏标记当前位置;它们可以作为各部分之间的过渡,但不得推广到普通内容页面。
|
||||
- **高密度附录/分析页面:**允许更小的文本、更密集的表格和更强的栏目,但保留标题、脚注和页码。
|
||||
|
||||
**默认阅读路径:**标题判断→副标题/单位→图表或表格中最大差异→右侧/底部说明→来源和脚注。
|
||||
|
||||
## 3. 内容组织
|
||||
|
||||
**标题风格:**使用分析性主题或结论句,而不是营销口号。常见的结构是“主题+趋势/判断+关键数字/范围/原因”,控制在1-2行;当标题已包含结论时,副标题仅添加测量依据。
|
||||
|
||||
**关键信息句子:**放置在图表上方或右侧,说明数据范围、单位、复合年增长率、市场定义、样本或情景;他们直接为图表提供服务——没有抽象的促销路线。
|
||||
|
||||
**正文组织:** 使用短段落、项目符号、编号或副标题组,每个项目符号表达一个事实或原因。避免长居中的副本。使用粗体关键字、缩进、规则和模块标题组织密集的文本。
|
||||
|
||||
**每页点数:** 一个标准的内容页围绕1个主要判断,最多包含2-4个支持论点。高密度页面可能包含多个证据块,但它们必须共享相同的分析问题;不要将不相关的图表拼贴到一页上。
|
||||
|
||||
**分组逻辑:**按市场/客户/产品/竞争/组织,或按时间、地区、细分、增长动力或影响程度分组。并行模块必须是同构的:标题位置、度量定义和基础、颜色含义一致。
|
||||
|
||||
**关键字强调:** 使用粗体、深海军蓝副标题、亮蓝色数字和浅灰色底框。避免使用彩色荧光笔、表情符号、夸张的感叹号或装饰性下划线。
|
||||
|
||||
**数字和单位:**数字必须带有单位、时间范围和必要的计量基础;增长率、复合年增长率、份额和金额在图表标题、轴标签或脚注中指定。预测、目标和实际情况通过颜色、破折号或标签来区分,而不仅仅是通过文本解释来区分。
|
||||
|
||||
**来源脚注:**所有数据图表和表格必须有来源/注释/测量基础注释。使用最小的文本级别,放置在图表底部或页脚左侧;从来不在视觉中心。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
**A。结论标题+单一主图+右侧说明栏**
|
||||
|
||||
用于核心趋势或规模判断。左边 65%–75% 持有条形图、堆积条形图或折线图;右边的 20%–30% 拥有浅灰色背景说明栏或白色标题栏,其中包含背景、增长动力和管理含义。使用亮蓝色系列、粗体数字、箭头或一些椭圆形标签来强调。不适合多主题并排布局或需要逐项扩展的复杂分类页面。
|
||||
|
||||
**B。结论标题+多栏文字分析页**用于文本论证,例如趋势、市场特征、驱动因素和风险点。正文分为 2-3 栏,每栏都有一个编号的副标题和一小段解释;列之间的间距较窄或线条较细,并保留粗体关键字。这种布局信息密集,但视觉证据较弱——不要在太多连续页面上使用它;如果有关键数字,请切换到图表页面。
|
||||
|
||||
**C。图表+机制/流程说明组合页**
|
||||
|
||||
用于从传统状态到新状态、价值链、业务流程或组织转型的迁移。趋势图位于左侧或中间;垂直流块、箭头或舞台音符位于右侧;深海军蓝/亮蓝色块标记目标状态、关键步骤或强调的链接。流程组件有助于解释——它们不能取代数据证据。
|
||||
|
||||
**D。高密度表格/矩阵页**
|
||||
|
||||
用于对客户、竞争对手、产品、功能、KBF 或机会点进行多维度比较。正文是一个大表格或矩阵,具有清晰的标题和精细的行/列分隔;关键单元格使用蓝色、灰色底座、评级点或符号标记。右侧可能有一个深度海军意见专栏,总结 2-4 个关键发现。不适合讲述单一趋势,也不适合缺乏可比维度的内容。
|
||||
|
||||
**E。深色侧边栏意见页面**
|
||||
|
||||
用于章节开头、强烈意见摘要或将“解释”与“证据”分开。左侧或右侧的深海军蓝垂直列大约占宽度的四分之一到三分之一,内部有白色文本和亮蓝色副标题;另一侧持有白色背景图表/表格。深色柱不得被滥用作为装饰背景——仅在需要指导、结论压力或部分识别时才使用它。
|
||||
|
||||
**F。目录/进度导航页面**
|
||||
|
||||
用于部分过渡。左侧为内容,右侧为浅灰色内容区域,当前部分以深海军蓝突出显示。它只负责导航——不进行身体分析。生成新主题时,保留章节编号、层次结构、页面范围和突出显示语法。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
**背景白色#FFFFFF**:内容页的主背景,面积最大。不能用彩色背景代替;除 TOC/解释列外,不建议使用大色基。
|
||||
|
||||
**浅灰色背景#E6E6E6 / #E9E9E9**:用于目录的右侧区域、侧面说明栏、表格基础和本地背景。用于结构区分;明度可能会略有调整,但不建议更换为高饱和度的品牌颜色。
|
||||
|
||||
**主要文本黑色 #000000 / 深灰色 #222222**:用于标题、正文、图表标签和表格文本。低对比度灰色不得取代正文。
|
||||
|
||||
**品牌深海军蓝#051C2C**:用于深色侧边栏、关键结构块、一些栏系列、标题栏和流程块。可以用同等深度的公司原色代替,但白色文本必须保持可读。
|
||||
|
||||
**亮蓝色#2150FE / #1F5BFF**:用于强调数据系列、当前状态、关键标签、所选项目和关键流程块。不得大面积分布;如果用品牌颜色代替,它必须保持高饱和度、高对比度,并且与深海军蓝不同。
|
||||
|
||||
**浅蓝色#BBD5EE / #DAE7F3 / #E6EEF6**:用于辅助数据系列、浅色面积图、覆盖线性图形和弱表格强调。不能携带白色正文;避免太轻而失去边缘。
|
||||
|
||||
**中性灰色#747474 / #8E9294 / #A4A4A4 / #D0D0D0**:用于辅助条形图、轴、网格线、表格线、标题和不强调的类别。灰色步骤可以被替换,但层次结构必须保持。**状态颜色边界:**没有建立红绿黄状态系统。当需要正/负状态时,更喜欢深度变化、标签和箭头;红色/绿色只能谨慎且谨慎地用于明确的正面/负面效果——它们不得改变套牌平静的蓝灰色气质。
|
||||
|
||||
**每页原色限制:**内容页使用黑/白/灰+深海军蓝+亮/浅蓝色。除非必须区分数据,否则不要引入第三种高饱和度颜色。
|
||||
|
||||
## 6. 字体和文本层次结构
|
||||
|
||||
**字体字符:**正文和大多数标签使用 Arial 级无衬线字体;封面主标题和内容页大标题可以使用Times New Roman Bold 级衬线粗体。斜体 Arial 很少用于注释、脚注或源代码行中的解释性文本。
|
||||
|
||||
**相对层次:**
|
||||
|
||||
- **封面项目名称:** 大约 3.5–4.5 倍正文大小,衬线粗体,甲板上最大的文本之一。
|
||||
- **内容页主标题:** 大约 2-3 倍正文大小,黑色,允许 1-2 行,结论在前。
|
||||
- **章节/TOC 标题:** 大约是正文大小的 2-3 倍,通常与大量空白或浅灰色内容区域配对。
|
||||
- **副标题/图表标题:** 大约 1.1–1.4 倍正文大小,可以是粗体;说明主题、单位和测量基础。
|
||||
- **正文和项目符号:** 定义为 1x,行间距紧凑但从不重叠。
|
||||
- **表格文本/图表标签:** 大约 0.75–1x 主体大小;数字标签可能稍小,但必须保持可读。
|
||||
- **注释、来源、脚注、页码:** 大约 0.55–0.75 倍正文大小,信息完整,但视觉上凹进。
|
||||
|
||||
**粗细规则:**粗体用于标题、副标题、表格标题、关键词和关键数字;不要加粗大段的长正文段落。斜体仅用于音符音调或来源归属,而不是主要强调手段。
|
||||
|
||||
**对齐方式:**标题和正文以左对齐为主;表格中的数字右对齐或小数点后对齐,类别文本左对齐。避免大的中心块。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
商业分析图表包括条形图、堆积条形图、折线/趋势线、组合图、市场规模预测、份额/排名图以及一些散点/气泡型标记。图表必须证明标题——它们不是装饰。
|
||||
|
||||
**坐标轴和网格:**坐标轴使用细灰色或黑色;网格线非常细,不需要时可以省略。轴标签较短;单位位于图表标题中或轴旁边。横轴通常是年份、细分市场或类别;纵轴包含金额、份额、指数等。
|
||||
|
||||
**系列颜色:**重点系列采用深海军蓝或亮蓝色;次要/历史/其他类别使用灰色或浅蓝色。堆叠栏中相同类别的相同颜色必须在页面之间保持一致 - 不要随机更改页面之间的颜色。
|
||||
|
||||
**数据标签:**直接标记关键柱、最近一年、CAGR、预测端点和比较差异。让标签靠近数据点;避免让读者依赖复杂的传说。
|
||||
|
||||
**预测和基线:**预测年份、复合年增长率、目标或基线可以用虚线、箭头、括号、椭圆标签或不同的灰蓝色调来区分。必须说明实际/预测间隔——仅靠颜色是不够的。
|
||||
|
||||
**图例:**放置在图表上方或图表内部的空白区域;系列较少,更喜欢直接标签。色样必须与图形相匹配;小无衬线文本。
|
||||
|
||||
**注释:** 避免使用短箭头、括号、CAGR 标签、圆形/椭圆形小标记。没有发光、阴影、3D 条形或透视效果。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
表格用于比较竞争对手、客户、产品、功能、市场属性和机会点——作为分析工具,而不是装饰性列表。**标题:**使用粗体、深色文本或深海军蓝/浅灰色底色。列标题可以有多行,但必须使用可比较的定义和测量基础。如果标题很长,请将其拆分为主标题 + 副标题 - 不要将其挤得难以辨认。
|
||||
|
||||
**行/列和分隔:**主要是细灰色线,水平方向多于垂直方向;浅灰色底色可以区分标题、组行或说明列。避免厚重的边框和复杂的网格。
|
||||
|
||||
**行高和密度:**允许高密度,但每行需要稳定的基线和足够的间距。长文本应该分成几行或作为片段来表述——切勿溢出单元格。
|
||||
|
||||
**关键标记:**关键行/列可以使用深海军蓝条、浅蓝色底色、亮蓝色文本、粗体、评级点、检查/符号或小标签。翼形符号可以谨慎地用于状态/标记,但不得成为彩色图标系统。
|
||||
|
||||
**对齐方式:** 文本左对齐;右对齐数字或对齐数字位置;以统一的格式保存百分比、金额和年份。排名、分数和状态点居中对齐。
|
||||
|
||||
**斑马条纹和底色:**浅灰色斑马条纹或局部浅灰色底色可以提高可扫描性,但不能太暗。表格基色必须保持黑色文本的高对比度。
|
||||
|
||||
**摘要和注释:**摘要行、平均值或关键结论位于表格底部或右侧注释栏中;来源和测量基础注释位于表格正下方。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
**细水平线:**分隔标题区域、正文区域和页脚。线条是细的、灰色的或黑色的;不要用粗彩色线代替它们。
|
||||
|
||||
**深海军蓝侧边栏:**用于高密度表格旁边的章节意见、结论摘要、解释栏或指南。白色文字,亮蓝色副标题,里面有短项目符号;它是一个信息容器,而不是装饰色块。
|
||||
|
||||
**浅灰色说明框:**用于背景、增长动力、假设和补充说明。放置在页面右侧或表格旁边;必须有副标题。
|
||||
|
||||
**目录突出显示栏:** 深海军蓝水平栏用反白文本标记当前部分。仅用于目录/进度页面。
|
||||
|
||||
**数据标签和徽章:** CAGR、最新值、预测值和关键类别可以使用小标签、省略号或胶囊标记;颜色保持深海军蓝、亮蓝色或灰色;将它们放在相应数据附近。
|
||||
|
||||
**流程块和箭头:**用于迁移路径、阶段和机制说明。形状为矩形、圆角矩形或深蓝色/亮蓝色/灰色的简单箭头。没有复杂的 SmartArt 样式、尺寸箭头或渐变箭头。
|
||||
|
||||
**图像框架:**如果使用屏幕截图/系统界面,请将它们仅作为证据材料嵌入,保留矩形边界和标题 - 没有大的情感图像。
|
||||
|
||||
**图标:**彩色图标并不是这种风格的稳定语言。如果必须显示状态,请使用单一颜色或蓝灰色系列的简单符号、点、检查、箭头或文本标签。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
总体而言,扁平、克制、数据驱动、咨询报告的质感,通过排版、线条、表格、图表和有限的色块构建专业精神。
|
||||
|
||||
**禁止:**
|
||||
|
||||
- 大照片背景、视频静态背景或情感全屏图像;
|
||||
- 表情符号、卡通插图、复杂的彩色图标、贴纸式装饰;
|
||||
- 3D 图表、透视条、发光、强烈阴影、玻璃形态、纹理背景;
|
||||
- 大渐变色块作为身体背景;
|
||||
- 大型居中文案、营销口号标题、无源数据;
|
||||
- 随机多调色板或多个高饱和度颜色在一页上竞争。
|
||||
|
||||
**谨慎使用:**- 圆角:可用于小标签、流块、椭圆标记;一定不能概括为卡片式的 UI 风格;
|
||||
- 阴影:源文件几乎不依赖它们;如果用于嵌入式屏幕截图,请使其保持极轻;
|
||||
- 红/绿状态颜色:仅用于明确的积极/消极/风险/机会,谨慎使用;
|
||||
- 屏幕截图:仅作为事实证据——它们需要标题、边界、来源或说明。
|
||||
|
||||
**可用于特殊页面:**封面可使用蓝色直线曲线图形; TOC可以使用大的浅灰色内容区域和深海军蓝高亮条;部分进度页面可以加强空白和导航,但不得引入类似海报的视觉效果。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 是 16:9 横向,以白色背景为主,有稳定的标题区、正文区和页脚区吗?
|
||||
- 标题是分析性主题还是结论句,而不是空洞的口号?是否控制在1-2行?
|
||||
- 页面是否只有一个主要判断,并有图表/表格直接支持该判断?
|
||||
- 是否保留来源、脚注、单位、时间范围以及实际与预测的基础?
|
||||
- 调色板是否仅限于黑/白/灰+深海军蓝+亮/浅蓝色,仅在关键点上使用亮蓝色?
|
||||
- 正文是否为 Arial 级无衬线字体,且标题允许采用正式衬线粗体?是否避免了对特定点大小的依赖?
|
||||
- 图表是否使用平条、堆叠条、线条、标签和细灰轴,避免 3D、渐变和过多的图例?
|
||||
- 表格是否有清晰的标题、细小的分隔符、统一的对齐方式、可读的行高和必要的关键标记?
|
||||
- 右侧的说明栏或深色侧边栏是否具有结论/背景功能而不是纯粹的装饰?
|
||||
- 是否避免使用照片背景、表情符号、复杂的图标、阴影、发光、纹理和随机颜色?
|
||||
- 您是否检查过文本溢出、截断、乱码、重叠、低对比度、遮挡、空内容、错误比例以及缺失页码/来源?
|
||||
- 生成多个页面时,目录突出显示、页脚、颜色语法、图表编码和标题层次结构是否一致 - 避免每个页面看起来像不同的模板?
|
||||
- 默认情况下没有卡片:除非用户明确请求,否则严格禁止使用圆角矩形或矩形卡片来构建层次结构或对齐方式:线段、空白和字体大小差异是更好的解决方案
|
||||
- 无均分组合:除非没有其他可用的布局,否则不要默认三向分割、四向分割或 2×2 矩阵。这包括三部分结论,例如三栏+标题+结论
|
||||
|
Before Width: | Height: | Size: 1.0 MiB |
@@ -1,141 +0,0 @@
|
||||
# 组织名称 · 2023.05 报告名称 CEO 生成式 AI 风格设计系统路线图
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
咨询公司向首席执行官、高管和商业领袖进行的演讲,内容涵盖趋势洞察、战略路线图、风险识别和组织行动建议。它使用高密度的商业论证:每一页首先给出结论,然后用图表、表格、比较框、案例摘录或行动列表来支持它。
|
||||
|
||||
核心设计:带有标题的深色科技照片横幅;白色/极浅灰色的机身让阅读变得轻松;薄荷绿和品牌绿组织层次结构;带有小脚注的高密度网格保持了报告的感觉。阅读路径是“论点优先+证据展开+来源作为支撑”。
|
||||
|
||||
基本原则:
|
||||
- **论证优先于装饰**:每页的顶部必须是一个独立的判断或面向行动的标题;图表、表格和组件仅服务于该判断。
|
||||
- **绿色仅用于结构和强调**:浅绿色用于背景分区和标注块,深绿色用于关键数字、标签、路径、条形和积极状态;绿色不得扩展为任意整版装饰色。
|
||||
- **网格必须驯服高密度**:多个证据点必须位于列、行、卡片、矩阵、流或表中;避免自由浮动的分散、未对齐的拼贴和任意放大的局部元素。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
- **画布比例**:水平 16:9 宽屏,专为报告阅读而不是大型舞台演示而设计。
|
||||
- **默认背景**:内容页面使用白色或极浅灰色;浅灰色用于表格底座、模块底座或底部摘要带。封面、封底和一些章节/过渡页使用深色技术摄影。
|
||||
- **标题区域**:内容页面顶部有一个固定的深色窄横幅,嵌入深色照片或近乎黑色的基色;标题左对齐,白色,粗体,一个完整的判断语句。小节编号或进度标签(例如“1a / 1b / 2b”)可能会出现在标题的下边缘或正文的左上角。
|
||||
- **身体区域**:位于横幅下方。常见的排列方式有左右分栏、三栏卡片、象限/矩阵、图表+注释、表格+旁注、中心模型+周围说明等;边距紧凑且严格对齐,由细线、浅灰色底色或空白分隔。
|
||||
- **页脚**:底部的一个小灰色脚注/来源/版权区域,左对齐;右下角或底部边缘的小页码。脚注通过空格或细线与正文分开,并且永远不会混合到正文中。
|
||||
- **徽标和品牌**:在封面的左上角,组织名称和报告名称的纯小文本组合(写下新主题名称)或留空;封底上,品牌文字标记位于深色照片上;两者都没有绘制图形标志。内容页面没有大规模重复徽标——黑色横幅、绿色系统和脚注风格带有品牌识别度。
|
||||
- **封面骨架**:全出血暗化照片;左上角有一个浅绿色的品牌块;页面中部向左/中心的水平浅绿色半透明标题块;标题为黑色粗体;小衬线/斜体级别的日期。绿色块的下边缘可能带有细垂直条纹。
|
||||
- **章节/过渡页面**:左侧浅绿色块或深色照片块承载章节主题,右侧有项目符号列表;增加空白并保持左对齐。
|
||||
- **关闭/免责声明页面**:免责声明页面使用白色底色、浅绿色副标题和灰色正文块;后盖恢复深色摄影和白色品牌字标。
|
||||
- **默认阅读路径**:顶部横幅结论→左上角数字/模块标题→主图表或中心模型→右侧/下方解释→脚注来源。## 3. 内容组织
|
||||
|
||||
- **标题风格**:使用结论句、判断句或动作句——而不仅仅是名词;表达诸如“正在发生什么”、“为什么重要”、“首席执行官应该如何行动”、“风险在哪里”等含义。标题可以更长,但必须在横幅内的一到两行内保持可读性。
|
||||
- **关键信息句**:标题下方或正文左上角,可能会出现“数字+短语”阶段标签;数字使用绿色或黑色小标签。
|
||||
- **正文组织**:以短段落和项目符号组织的微点。避免连续全出血的长段落;将长解释分成卡片、左右列或表格行。
|
||||
- **分组逻辑**:偏好时间/阶段递进、左右对比、矩阵比较、“三并列”、“二类对比”、“核心/非核心”、“传统/新类型”、“机会/风险/行动”。
|
||||
- **每页点数**:每页一个主要判断,内有3-6个证据点、项目符号或比较维度。如果需要6个以上的并行点,请转换为表格或拆分为两页。
|
||||
- **关键字强调**:使用粗体、绿色文本、绿色标签、浅绿色基块、环或边框;风险或警告可以使用小面积的洋红色/玫瑰色标记。不要使用荧光多色、高饱和度红蓝混合或大彩虹强调。
|
||||
- **数字和单位**:数字直接位于条形图、标签、表格单元格或关键结论旁边;单位和时间范围位于图表标题、轴标题或脚注中。增长率、年份、市场规模和股票类型数据应该比解释性文字更加突出。
|
||||
- **引述和案例**:外部引述使用引号、浅绿色引述块或新闻摘录框;引用是提供证据的,不能取代页面的主要结论。
|
||||
- **来源脚注**:图表、市场规模、案例截图和报价均保留其来源;使用极小的灰色文本,最好是左对齐,远离主要阅读流程。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 顶部横幅 + 三列/多列意见卡
|
||||
|
||||
针对概念差异、能力特征、风险类别和组织要求。正文分为三栏或四栏,每栏都有一个小图标/副标题/简短标题/一些项目符号;列标题为绿色,列由浅灰色垂直线或空格分隔。关键列可以添加浅绿色底座或深绿色标签;每栏包含一个小要点和 2-4 个支持注释。当必须显示连续数据或复杂因果链时,请勿使用此布局。
|
||||
|
||||
### B. 主图+右侧证据/说明卡
|
||||
|
||||
了解增长趋势、市场规模、采用率和预算变化。该图表大约占一半到三分之二;解释框、新闻摘录或要点位于右侧或上方。图表采用绿色初级系列和灰色次级系列,直接标注关键数据;卡片使用浅灰色或白色底色,带有细边框和一些绿色标题。每页只有一个主要图表结论 - 避免堆叠多个不相关的图表。
|
||||
|
||||
### C. 左侧大意见块+右侧行动说明
|
||||
|
||||
针对 CEO 行动建议、路线图提示和核心问题定义。左边一个大的浅绿色基块承载着核心判断、问题或分类模型;右侧用项目符号、短段落或案例解释了行动的含义。环、双圆、箭头或分隔线可以将它们连接起来。如果页面的重点是数据证明,请减少大意见块的区域以避免挤压图表。
|
||||
|
||||
### D. 比较矩阵/表格页用于比较技术阶段、业务用例、风险维度、组织能力和部门职责。使用列标题+行尺寸;浅绿色或深绿色的标题,包含短语或项目符号的单元格。关键列/行使用绿色底色、粗体或图形标记。如果维度少于 2×2,请使用卡片而不是表格。
|
||||
|
||||
### E.中心模型+周边讲解
|
||||
|
||||
用于框架、能力系统、治理模型或价值链。圆、环或矩阵核心位于中心,周围有 4-8 个解释节点,通过细线、箭头或空间关系连接。中心通常为绿色,外围为浅灰色/浅绿色。这种布局强调结构关系,不适合长文本段落或精确值。
|
||||
|
||||
### F. 流程/路线图布局
|
||||
|
||||
对于诸如“发现——选择——构建——治理——规模”之类的行动步骤。使用水平或垂直步进条,绿色标记当前/关键阶段,灰色标记辅助阶段;每个步骤下方都有一个简短的标题。流向箭头应该是轻量且扁平的——没有复杂的尺寸箭头。如果步骤不连续,请转换为矩阵卡或并行卡。
|
||||
|
||||
### G. 新闻/案例拼贴页面
|
||||
|
||||
对于外部事件、公众情绪、风险实例或现实世界的证据。新闻卡片、截图或摘录必须遵守网格边界和统一标题横幅;屏幕截图不应填满整个页面,并应带有解释性标签。此布局是证据页面,不得成为常规内容模板。
|
||||
|
||||
### H. 联系人/附录列表页面
|
||||
|
||||
对于团队、作者、联系人和免责声明。使用头像或文本卡网格;姓名/头衔以粗体显示,角色、电子邮件和链接位于较小的级别。减少图表并强调重点。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- **近黑色/深木炭 #0B0C0D、#0D0F0F、#191E1F**:用于标题横幅、深色照片叠加层、封底背景和主要正文文本。不能用鲜艳的品牌色来代替——那会破坏咨询报告的权威性。
|
||||
- **白色/极浅灰色#F1F1F1、#F2F2F0、#ECEDED**:用于主页背景、内容库、表格空白区域和卡片库。可以在白色和浅灰色之间切换,但不得成为高饱和度的彩色背景。
|
||||
- **薄荷浅绿色,大约#9CDDAA、#92C9A0、#8FC49B、#BFEFD0**:用于封面标题块、章节块、标注框、大左意见库和弱表格强调。文本必须保持黑色/深色;替换颜色也必须是低饱和度、浅色背景。
|
||||
- **品牌原色绿色#23B871、#35BA7A、#408A5A**:用于关键栏、标签、关键数字、图标、当前流程步骤和核心环。不要同时引入几种相似的果岭,以免造成层次混乱。
|
||||
- **深绿色#26633B、#2E6F47、#5F877B**:用于小标题标签、深色图表系列、强调边框和结构线;不适合作为长正文文本的基色。
|
||||
- **中灰色#748687、#A6AB9E、#B7B7B7、#D2D2D2**:用于分隔线、表格线、轴、辅助文本、非键系列和禁用/基线状态;不得替换主要文本颜色。
|
||||
- **洋红色/玫瑰色,大约#C2185B、#B0185A**:仅适用于风险、反例、警告和负面类别的小区域标记 - 环、标签、关键字。不得大面积使用作为第二品牌颜色。
|
||||
- **图表系列边界**:主色调为绿色;只有灰色、浅绿色和一点洋红色作为辅助色。如果数据系列超过 3-4,最好用绿色深度步长和灰色来区分 - 不要引入彩虹色。
|
||||
|
||||
## 6. 字体和文本层次结构- **字体字符**:正文使用 Arial 系列,标题和副标题为粗体,引文或注释为斜体; Trebuchet MS 很少用于脚注/版权;封面“报告名称”可以使用衬线字体。
|
||||
- **页面标题**:1.8–2.5x正文大小,粗体,白色,放置在深色横幅中;长标题可能会换行,保持左对齐且行间距较小。
|
||||
- **封面主标题**:2.5–4倍主体大小,黑色粗体,放置在浅绿色半透明块上;日期/系列名称较小,可以使用斜体或衬线。
|
||||
- **关键信息句子/引导**:1.2–1.6x 正文大小,可以是粗体或绿色,放置在正文的左上方或浅绿色块上。
|
||||
- **正文**:定义为 1x、常规 Arial、紧行距、左对齐;避免居中的长段落。
|
||||
- **小标题/卡片标题**:1.1–1.4x 正文大小,粗体、绿色或深色;文字简短而明确。
|
||||
- **表格和图表标签**:0.75–1x 主体尺寸;数字可以加粗或放大至 1.1–1.4 倍;轴标签和图例较小,保持灰色。
|
||||
- **注释/脚注/来源/页码**:0.45–0.7x 正文大小,灰色。脚注不得用绿色来吸引注意力。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
- **实际存在的类型**:主要是条形图、堆积条形图、折线/趋势图、圆环/气泡式关系图和简化坐标图。
|
||||
- **轴和网格线**:轴线和网格线极细,浅灰色;背景网格可以被削弱或省略,但必要的坐标信息被保留。
|
||||
- **系列颜色**:主系列使用品牌绿色或绿色深度的组合;辅助系列采用灰色、浅黄绿色、或浅灰色;预测、目标或关键年份使用更深的绿色和直接数字注释。
|
||||
- **数据标签**:直接注释关键条、端点、峰值或增长率;黑色或绿色的数字。避免让读者仅仅依赖图例来获取关键点。
|
||||
- **图例**:小尺寸,放置在图表上方或下方。如果只有 1-2 个系列,请直接在图形旁边进行标记。
|
||||
- **注释和解释**:使用细线、箭头、浅绿色框或小文字来解释关键转折点;新闻摘录可以作为支持证据卡。注释不得覆盖数据主体。
|
||||
- **边界**:没有 3D 条、渐变填充、重阴影、彩虹热调色板或复杂的动画图表效果;图表的美观必须服从于数据的清晰度。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
- **表格作用**:用于多维度比较、风险列表、行动职责、能力评估和用例筛选;通常是页面的主要证据结构,而不是装饰性布局。
|
||||
- **标题**:深绿色、浅绿色或深色文本,背景主要为浅绿色/白色/浅灰色;文本简短,强调维度名称而不是长句子。
|
||||
- **行/列结构**:允许多列,但需要严格对齐;使用细灰色线或浅灰色底线来分隔行 - 不要依赖粗黑色边框。
|
||||
- **底色和斑马条纹**:浅灰色或浅绿色底色区域;斑马线对比度不宜太强。
|
||||
- **关键行/列**:用浅绿色底色、绿色文本、粗体、点等级或边框进行强调;风险/负面项目使用小的洋红色标记。没有全行亮红色或高饱和度警告色。
|
||||
- **数字和文本对齐**:数字右对齐或居中,文本左对齐;短评级、符号和点可以居中;单位统一在列标题或脚注中注明。
|
||||
- **行高和密度**:紧凑的行高;每个单元格仅限于一个短语、短句或 1-2 个项目符号。如果单元格需要很长的解释,请转换为卡片页。
|
||||
- **符号标记**:可以使用最少的符号,例如点、格子、短划线和水平色块;需要图例或语义必须保持一致。
|
||||
|
||||
## 9. 组件和图形元素- **深色标题横幅**:核心识别组件,横跨顶部,包含深色技术照片或近乎黑色的底座,带有白色结论标题;所有内容页面的首选。
|
||||
- **浅绿色标题/意见块**:用于封面主标题、章节提示、关键问题和行动原则;矩形或大色块,没有复杂的效果。
|
||||
- **数字标签**:如“1a / 1b / 2b”,表示路线图进度;绿色/深色,位于主体的左上方。
|
||||
- **卡片**:白色或浅灰色底座,细边框或无边框,包含副标题、短标题、图标/数字;通过对齐和空白来区分,没有沉重的阴影。
|
||||
- **分隔线**:细灰色线、浅绿色线或列之间、表行之间的空白间隙,并隔离脚注区域;线条应该很轻,不会产生笼状网格的感觉。
|
||||
- **环形/双圆/矩阵标记**:用于核心/非核心、正/负、键/非键;绿色表示核心或积极,洋红色表示非核心/风险/警告。环内的文字必须短。
|
||||
- **图标**:少量单色绿色/灰色线性图标或简化符号,仅用于帮助模块识别。禁止使用彩色拟物图标。
|
||||
- **箭头和流线**:表达过程、因果关系或转换关系;扁平细线,绿色或灰色。避免使用粗的尺寸箭头。
|
||||
- **屏幕截图/新闻卡**:仅在案例证据页面上,放置在带有短标题的白色卡片或浅灰色区域;必须有边界和对齐——切勿随意倾斜或堆叠。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
扁平、内敛、咨询报告式的科技感;品牌感觉来自品牌绿色、深色照片横幅、Arial字体系统和严格的网格——而不是复杂的装饰。
|
||||
|
||||
- **禁止**:3D图表、发光效果、重阴影、复杂渐变、彩虹调色板、表情符号、卡通插图、彩色拟物图标、无意义的装饰线、大块居中副本、整页无网格自由格式布局、低对比度浅色文本以及跨内容页面滥用的封面式照片背景。
|
||||
- **谨慎使用**:照片、屏幕截图、洋红色警告色、环模型、流动箭头、大浅绿色基块。它们必须具有证据、分类、行动或强调功能;如果它们只填补空白,请将其删除。
|
||||
- **可用于特殊页面**:封面、封底和章节过渡页可以使用大的深色照片和浅绿色块;案例证据页面可以使用新闻截图拼贴;联系页面可以使用头像网格。常规内容页面应保留白色底色、横幅和图表/表格/卡片。
|
||||
- **圆角和阴影**:卡片应该只有轻微的圆角或接近正方形;阴影基本没用过。模块由基色、线条和空白分隔。
|
||||
- **图像依赖性**:内容页面不依赖大图像来营造气氛;图像仅出现在标题横幅或证据卡中。每张图像都需要变暗、裁剪和对齐,并且不得破坏文本的可读性。
|
||||
|
||||
## 11. 一代清单- 骨架:是16:9吗?内容页面是否有深色顶部横幅;身体是否落入清晰的网格中?页脚来源/版权/页码是否位于小底部?
|
||||
- 文案:标题是结论还是行动句;是否每一页只有一个主判断;段落是否被分成短句、项目符号、卡片或表格?
|
||||
- 层次结构:标题、关键信息、正文、副标题、图表标签和脚注是否形成相对大小差异;是否避免了特定点大小依赖性和无层次堆叠?
|
||||
- 颜色:基础为白色/浅灰色,文字为黑色,强调绿色;洋红色仅用于小风险区域;每个页面是否保持在绿色+灰色+少量警告色范围内?
|
||||
- 布局:图表、表格、卡片和流程各司其职;是否避免了多个不相关的主视觉效果之间的竞争;左右/多列排列是否对齐?
|
||||
- 图表:使用的是扁平的绿灰色系列;轴线和网格线是否亮;关键数据直接标注; 3D、渐变和过多的系列颜色是否被禁止?
|
||||
- 表格:标题、行/列、分隔符、关键行/列以及数字/文本对齐是否一致;单元格是否可以避免长段落溢出?
|
||||
- 组件:标题横幅、数字标签、浅绿色意见块、卡片、分隔线和环形/流线是否具有明确的语义;图标是单色且简单的吗?
|
||||
- 禁止物品:是否没有表情符号、卡通插图、彩色拟物化图标、发光、浓重阴影、复杂装饰、大型居中身体块或滥用的照片背景?
|
||||
- 质量:检查文本是否溢出、截断、乱码、重叠、对比度低、遮挡、内容空洞、比例错误、压在正文上的脚注以及与信息结构不匹配的模板重复。
|
||||
- 默认情况下没有卡片:除非用户明确请求,否则严格禁止使用圆角矩形或矩形卡片来构建层次结构或对齐方式:线段、空白和字体大小差异是更好的解决方案
|
||||
- 无均分组合:除非没有其他可用的布局,否则不要默认三向分割、四向分割或 2×2 矩阵。这包括三部分结论,例如三栏+标题+结论
|
||||
|
Before Width: | Height: | Size: 1.5 MiB |
@@ -1,176 +0,0 @@
|
||||
#F030·顶级管理咨询与策略全套PPT模板风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
用于针对管理层、项目所有者或决策委员会提供有关战略咨询、成本优化、运营诊断、效率改进、路线图规划、机会评估和结果审查的管理报告。采用“白色咨询报告骨架+高饱和度红色焦点+灰度数据组件”:封面近黑/深红色,标题超大;内容页面使用白色或极浅灰色的底色,标题位于左上角或左上角,基于图表的校样,基于卡片的摘要和路线图进展。照片、插图和装饰图标不构成主要视觉效果。
|
||||
|
||||
基本原则:
|
||||
- **每页一个红色焦点**:它可能适用于主卡、关键栏、关键字、最高强度的热图单元或 CTA - 不要将其均匀分布在同一页面的多个区域。
|
||||
- **装饰前的结构**:通过左右分栏、卡片矩阵、图表组合、时间线、热力图来组织页面;不要依赖纹理、阴影、复杂图形或大图像。
|
||||
- **数据清晰胜过图表表演**:条形图、圆环图、热度矩阵、简单表格和数字卡直接得出结论;弱化轴、图例和网格线以突出比较关系和关键值。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
画布为水平 16:9 商业演示比例。内容页面默认为白色或#F6F6F6级别的极浅灰色背景,具有相对较宽的安全边距。基本结构为“左上标题区+中央正文区+小标题/脚注区”,包含阅读路径运行标题、红色焦点、图表/卡片/表格详细信息。
|
||||
|
||||
标题区域:通常位于左上角,在右上角或左上中的几个图表页面上,包含主题、问题或结论 - 没有装饰性标题栏。标题下方的 1-2 行可以说明分析目的、范围或行动含义;身体在其下方或右侧展开。
|
||||
|
||||
车身区域:分柱式、模块化;常见内容包括大类型定义、饼图/圆环图、条形图、热矩阵、路线图卡、胶囊标签流、数字度量卡和表格式比较。卡片保持均匀对齐和间隔;浅灰色块划分页面;红色块标记优先级。
|
||||
|
||||
标题:非强制。小品牌文字标记或小标记可能位于左上角;日期、简短说明或小标题可以位于右上角;它必须保持较低的姿态,并且决不能形成沉重的单杠。
|
||||
|
||||
页脚:没有很强的导航功能;可能包含脚注、来源、联系信息或小型 CTA。如果添加页码,请使用塞在角落里的极小的黑灰色文本——它不能从正文中窃取。
|
||||
|
||||
分隔线:使用浅灰色细线、卡片边界或背景块。避免粗黑线和复杂的边框。轴和参考线也使用浅灰色低对比度。
|
||||
|
||||
封面:近黑/深红色背景,超大白色粗体标题,红色按钮式标签,深红色大字水印,右侧少量说明文字;这种处理不得迁移到普通内容页面。
|
||||
|
||||
结束页:带有一些联系方式/CTA 的大标题,白色或浅灰色底色;红色可以强调关键动作词,但不应添加照片墙或复杂的装饰。
|
||||
|
||||
## 3. 内容组织
|
||||
|
||||
标题使用问题型、主题型或行动型短语,例如“为什么……重要”、“目标……”、“路线图概述”、“识别……机会”。更喜欢名词短语或动宾结构;避免长句子。
|
||||
|
||||
在标题附近放置 1-2 行关键信息,说明结论、分析依据或下一步行动——它们不能取代身体图。解释性内容分为简短的标题、项目符号卡或脚注。正文内容遵循“一个要点+三到五个支撑点”。支持点可以是卡片、流程阶段、度量列、图表标签或表格行;每张卡片通常包含一个小标题和一个压缩说明,以空白、浅灰色底色和编号来区分。
|
||||
|
||||
关键词可以使用红色文本、红色文本上的白色文本、黑色胶囊标签、粗体副标题、放大的数字或局部红色图表元素。红色仅对应“关键点、当前、推荐、高价值、风险或行动”——绝不对应普通装饰。
|
||||
|
||||
百分比、金额、改进幅度、节省和阶段编号等数字作为独立的块,单位和解释紧邻。时间、阶段、序号保持一致;路线图使用清晰的编号,例如 01/02/03/04,并且不会混合复杂的层次结构。
|
||||
|
||||
来源和注释使用极小级别的浅灰色或深灰色文本,放置在底部、图表旁边或标题右侧;它们仅陈述数据源、假设或测量基础——它们不包含关键结论。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 大字定义/意见页面
|
||||
用于介绍概念、定义问题或陈述战略立场。一个小的引入线位于顶部或左上角;正文以超大文本陈述观点,关键字标记为红色;下面可能跟随一行指标、类别或关键字模块。信息密度低到中等。如果文本超出几行或需要多个数据组,请勿使用此布局。
|
||||
|
||||
### B. 左标题+右/中图表分析页
|
||||
用于现状、分布、趋势、比较和原因分析。标题和说明文字位于左侧或右上角;主体持有圆环图、条形图、组合条形线图或小公制卡;关键部分、关键条或当前值使用红色,其他所有内容使用灰色。适合1个主图+2-4个注释点;不适合塞入多个同等权重的大型图表中。
|
||||
|
||||
### C. 路线图/阶段卡页面
|
||||
用于显示 3-5 个阶段、目标、任务或执行路径。一张大红牌标志着第一阶段、当前阶段或核心举措;其余为浅灰色卡片;放大的编号位于顶部或左侧。强调阶段名称、行动和预期结果。超过五个阶段,分页或转换为时间线;不适合无序的杂项列表。
|
||||
|
||||
### D. 热度矩阵/机会识别页面
|
||||
用于影响、复杂性、优先级或节省空间的二维评估。主体是红-粉-浅粉红色网格,深红色标记高强度或优先级,旁边有一个小图例、轴注释和红色结论卡。矩阵服务于相对定位和聚类,而不是精确的值读取;当需要精确值时,请切换到表格或条形图。
|
||||
|
||||
### E.胶囊标签流程+推荐卡页面
|
||||
用于流程节点、操作步骤、类别分组或优化机会列表。黑色胶囊标记一级类别或路径节点,红色胶囊标记关键节点,浅灰色胶囊标记普通项; 2-3 张推荐卡可能位于底部或右侧。标签必须短且避免缠绕;当标签太多时,将它们分组——没有复杂的连接线。
|
||||
|
||||
### F. 多指标仪表板/案例结果页面
|
||||
用于结果、案例、预测、节省或绩效变化。由大数字卡片、条形图、迷你圆环图、标题列表组成;红色数字卡或红色图表元素是结果焦点,灰色卡提供比较。每个大数字都必须有一个解释——不要堆叠孤立的指标。### G.简单表格/比较矩阵页面
|
||||
用于比较计划、部门、成本项目、时间段或机会类别。该表占据主体的中下部分,具有简单的标题和红色或浅红色底座的关键列/行。标题和结论句是强制性的——表格不能成为无法解释的数据堆。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
背景颜色:
|
||||
- **#FFFFFF**:主要内容基础、卡片内部和大空白区域 - 最大的区域。不能用高饱和度的颜色代替。
|
||||
- **#F6F6F6 / #F7F7F7 / #F8F8F8**:整页背景、浅色分区、表格底座或图表容器底座;必须保持白卡清晰可见。
|
||||
|
||||
主要文本颜色:
|
||||
- **#070302 / #0F0605 / #1C0B0B**:标题、重要正文、封面主标题和关键图表文本。
|
||||
- **#2F2929 / #454545 / #484545**:正文标题、副标题、图例文本和表格文本。
|
||||
|
||||
主要强调红色:
|
||||
- **#FF4D4D / #FF5454 / #FF5C5B / #FE6464**:用于红卡、关键数字、关键图表栏、当前阶段、重要标签和 CTA。每页最多一个大面积红块;其他红色仅显示为点状回声。
|
||||
- 可以更换为品牌原色,但必须保留“单一高饱和度强调+灰度系统”,并保持白字/黑字对比。
|
||||
|
||||
辅助粉红色:
|
||||
- **#FED8D8 / #FFC5C4 / #FFDFE0 / #FFF1F1 / #F1CCCC**:热图渐变、弱强调背景、面积图填充和红卡的亮度级别。不得用于小尺寸的主要正文文本 - 避免低对比度。
|
||||
|
||||
辅助灰度和分离:
|
||||
- **#BDBDBD / #C1C1C1 / #D9D6D6 / #DDDBDB / #E7E6E6**:轴、网格线、卡片边界、普通条形、表格线、禁用或次要状态;不得与正文相冲突。
|
||||
|
||||
覆盖深色:
|
||||
- **#070302 / #120706 / #210E0D / #2F0505**:仅适用于封面或极少数暗调部分页面的背景和暗红色水印。内容页面不使用大型深色底座。
|
||||
|
||||
单个页面仅使用红色+灰度的一种色调。图表系列以红色、粉色、灰色为主;避免引入蓝色/绿色/紫色的多色业务图表,除非必须区分源数据状态并保持低饱和度。
|
||||
|
||||
## 6. 字体和文本层次结构
|
||||
|
||||
现代无衬线商业字体。正文主要使用ArialUnicodeMS / LiberationSans-class sans-serif;封面的超大标题采用了较重的Arial-Black风格。整体干净、紧凑、理性——没有手写字体、装饰性衬线字体或卡通字体。
|
||||
|
||||
相对层次结构:
|
||||
- 封面主标题:约5-6倍正文大小,超粗体,白色,行距紧凑。
|
||||
- 内容页面主标题:大约 2.5–3 倍正文大小,常规或中等粗细,黑色,位于左上方或顶部。
|
||||
- 大字体意见/定义:大约是正文大小的 3-4 倍,可以跨多行,关键字为红色。
|
||||
- 副标题/卡片标题:大约是正文大小的 1-1.2 倍,通常为粗体或更深的黑色。
|
||||
- 正文标题:body=1x,短线条,低装饰。
|
||||
- 表格文本/图表标签:大约是正文尺寸的 0.75–0.9 倍。
|
||||
- 脚注、来源、标题小文本、页码:大约 0.55–0.7 倍正文大小,灰色或黑灰色。
|
||||
- 数量指标:大约是身体尺寸的 2-4 倍;单位和解释下降到正文或注释级别。
|
||||
|
||||
文本主要是左对齐;大的中心通道不是系统的一部分;封面可以局部使用左对齐的超大标题。避免段落过长,并避免卡片内出现多级项目符号。在同一页面中使用一种一致的大写样式。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
实际出现的图表主要是圆环图/饼图、条形图、组合条线图、面积/趋势图、热矩阵、小仪表和公制卡——服务于咨询分析,而不是追求复杂的可视化。
|
||||
|
||||
圆环图/饼图:使用红色、深红色、灰色和黑灰色扇形,中间有一个短标签或核心值;简短的外部注释,低存在的图例。红色部分标记关键成本项目或关键对象。条形图:红色条标记关键点、当前值、目标或高值;灰色条是比较。轴极小,网格线浅灰色,条形间距清晰;短值或标签可以位于条形顶部——避免密集的注释。组合图表中的线条使用细笔划和小节点作为趋势或目标参考。
|
||||
|
||||
热矩阵:浅粉色到红色表示强度;轴文本保持简单;网格边界被软化。太多的暗块会破坏优先判断。需要解释颜色强度的图例或注释。
|
||||
|
||||
公制卡/迷你图表:嵌入红卡、灰卡或白色容器中。大量数字引人注目,并有简短的标题解释了它们发生变化的原因。迷你条形图和圆环图只是补充——它们不能成为页面的第二个主要焦点。
|
||||
|
||||
图例和注释:图例保持较小且靠近图表,颜色与系列相匹配。关键点可以使用红色胶囊、小点、箭头或短标签,但不能使用复杂的标注。预测线、目标线或基线线使用浅灰色线或带有简短注释的细红色线。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
表格用于“证明、比较、分解和优先排序”;它们通常位于正文的中下或右侧,与标题和结论句配对。
|
||||
|
||||
表头:浅灰色底,或白色底细线;文本加粗或加暗。关键列可以使用红色标题或红色顶栏,但整个表格不得高饱和度。
|
||||
|
||||
行和列:行高均匀,表述为短句。保持较低的列数 - 优先考虑名称、指标、影响、操作和状态字段。较长的解释应分解为卡片或脚注;避免细胞内多线拥挤。
|
||||
|
||||
分隔符和基色:使用浅灰色细线,例如#E7E6E6、#DDDBDB;浅灰色块可以对内容进行分组;斑马条纹不是一个强大的系统——如果使用,请保持非常微妙。关键行/列可以使用浅红色底座或红色条标记。
|
||||
|
||||
对齐方式:文本左对齐,数字右对齐或在小数/单位上视觉对齐。百分比、金额和变化值保持一致的单位。状态使用红点、黑点、浅灰色标签或短徽章 - 没有复杂的图标集。
|
||||
|
||||
摘要和要点:摘要行使用深色文本、浅灰色底色或细红色线。不要同时用多种颜色标记级别;喜欢用红/粉/灰三个级别来表示高/中/低或重点/普通/弱。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
标题组件:左上角有一个大的黑色标题+下面或旁边的标题——没有沉重的标题栏。封面标题是一种特殊的超大白色文本例外。
|
||||
|
||||
红色钥匙卡:当前阶段的关键结论、核心指标、建议行动或 CTA;可以是红色底上的白色文本,也可以是红色到浅色底色上的黑色文本。每页仅保留一张主红卡;其他红色元素降级为标签或图表点。
|
||||
|
||||
浅灰/白卡:用于并行信息、阶段说明、度量注释和比较内容。光边界,内敛圆角,无阴影依赖;均匀的间距和对齐方式形成网格。
|
||||
|
||||
胶囊标签:黑色、红色、浅灰色三种。黑色标记类别或主路径节点,红色标记关键节点,浅灰色标记普通节点。标签文本很短,不包含长句子,并且不会包装成多行胶囊。
|
||||
|
||||
编号部分:使用01/02/03/04或大数字。红底白字标记当前或初级阶段;其余部分用灰黑标记。数字必须与卡片标题绑定在一起,而不是孤立的装饰。
|
||||
|
||||
分隔符和轴线:浅灰色细线组织图表、表格和模块;它们不得变厚或变成装饰性边框。
|
||||
|
||||
徽章/按钮:封面和 CTA 可以使用红色按钮式标签;内容页面可以使用小的红色/黑色标签。按钮不具有真正的交互性——没有浓重的阴影或高光。图标:彩色图标不是系统的核心。如果使用的话,它们只能是极小的单色功能标记——点、箭头、加号、位置点——用于指导或注释;禁止使用彩色插图图标集。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
整体扁平、克制、类似咨询报告,通过强烈的对齐、空白、红色焦点和数据结构构建视觉吸引力。
|
||||
|
||||
禁止:
|
||||
- 大面积照片背景、大人物图片或场景摄影作为内容页面的主视觉。
|
||||
- 表情符号、卡通插图、3D 图形、发光、光泽玻璃纹理、复杂纹理。
|
||||
- 彩色图标集,混合多种品牌颜色,如蓝色/绿色/紫色,彩虹式图表。
|
||||
- 浓重的阴影、强烈的渐变背景、复杂的边框、装饰曲线和华丽。
|
||||
- 大型居中副本、无层次结构的长段落、整页项目符号列表。
|
||||
|
||||
谨慎使用:
|
||||
- 渐变:仅适用于红色到浅红色的卡基、热图或面积图,不适用于整页耀斑背景。
|
||||
- 阴影:如果必须区分卡片,请使用极其微妙的阴影或简单的浅灰色底色;不要形成浮卡风格。
|
||||
- 圆角:可用于胶囊标签和按钮;卡片的角应仅有轻微的倒圆角;不得变成互联网UI风格。
|
||||
- 图标/箭头:仅用于流向、关键点注释或状态点——数量少,单色。
|
||||
|
||||
适用于特殊页面:封面可使用深色背景、深红色水印、超大标题、按钮式红色标签;结束页面可能会使用更大的 CTA 消息以及联系方式。章节分隔页可以少量采用封面的深色氛围或白底大字,但不能取代内容页系统。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 骨架:是否是16:9的横向商业页面;标题区域、正文区域和说明/脚注区域是否清晰;内容是否远离边缘?
|
||||
- 文案:每一页是否只有一个要点;标题是否简短且具体;标题是否控制在 1-2 行;卡片上的文字是短句吗?
|
||||
- 层次结构:标题、关键信息、正文、图表标签和脚注是否显示出明显的相对大小差异;是否避免了无层次结构的文本堆叠?
|
||||
- 颜色:是否保留白色/浅灰色背景+黑灰色文本+单一红色强调;红色是否只对应关键点?是否避免使用多色图表?
|
||||
- 布局:在大字体意见、图表分析、路线图、热矩阵、标签流、指标案例或简单表格中选择合适的方案;不相关的信息是否会被强制放到同一页面上?
|
||||
- 图表:红色仅标记关键系列;轴和网格线是否软化;标签是否可读;图例是否简单且接近图表?
|
||||
- 表格:标题、分隔符、关键行/列、数字对齐是否一致;是否避免了长单元格段落和过于密集的线框?
|
||||
- 部件:红钥匙卡、浅灰卡、胶囊标签、编号、分隔符是否功能清晰;没有添加任何组件只是为了装饰吗?
|
||||
- 禁止物品:照片背景、表情符号、3D、发光、浓阴影、复杂纹理、彩色图标和彩虹调色板是否不存在?
|
||||
- 质量:检查文本溢出、截断、乱码、重叠、对比度低、图表遮挡、空卡、错误比例、模板过多相同或红色焦点过多。
|
||||
|
Before Width: | Height: | Size: 456 KiB |
@@ -1,151 +0,0 @@
|
||||
# 组织名称·2025.08 通信功能的GenAI变革STYLE DESIGN SYSTEM
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
用于咨询公司数据包、功能转型评估、人工智能/技术影响量化、方法论解释和执行层报告。针对企业主、沟通/职能领导者和咨询项目团队;每一页都必须快速得出结论,同时图表定义和测量基础、方法假设和来源仍然可追溯。
|
||||
|
||||
一言以蔽之:**一种深色的咨询数据包风格,深灰色的画布和深绿色的科技氛围承载着密集的分析,青绿色的数据编码凸显了 GenAI 的转型机会和方法论水平。**
|
||||
|
||||
稳定的功能:深灰色/深绿色全底色、TrebuchetMS 字体系统、左上角的结论式标题、大正文图表/矩阵、右侧的短段落解释、底部的微小来源/版权线、细线框架、荧光绿/青绿色口音以及封面/结束页上的 AI 肖像视觉效果。
|
||||
|
||||
- **结论为先,证据为正文**:标题直接陈述判断;图表、表格、矩阵或流程具有说服力;装饰不能代替证据。
|
||||
- **深色统一,绿色层次感**:深灰色和深绿色定义了整个甲板;亮绿色仅标记关键数据、标签、图表系列和状态——没有无意义的颜色填充。
|
||||
- **高密度但可扫描**:使用栏、分隔符、标题、标签、脚注和对齐来构建阅读路径;避免无层次结构的文本堆叠。
|
||||
|
||||
## 2. 页面骨架- **画布比例**:宽屏横向 16:9。所有页面都为脚注、版权和页码保留底部空间。
|
||||
- **默认背景**:内容页面位于深灰色#2C2C2C上,覆盖深绿色#0A3021–#0D4430区域或右上角/右侧的绿色光芒;部分分隔符和免责声明页面可以使用近乎纯的深灰色。
|
||||
- **边际**:狭窄的安全边际;标签和标题位于顶部,来源和页码位于底部,正文位于中间。
|
||||
- **标题区域**:左上角的一个小选项卡/层次结构标签,后面是主标题和一行副标题或关键消息。左对齐,在页面顶部跨越大约一半到全宽;从未居中。
|
||||
- **正文区**:内容页主要是左侧或中心的图表/表格+右侧解读;方法论页面使用水平流或矩阵;概念页面使用多列/节点关系。标题区域和正文之间的间距很小。
|
||||
- **标题/导航**:左上角有一个绿色小标签,例如功能级别、任务级别、方法论;它仅表示部分和分析级别——没有大的导航栏。
|
||||
- **页脚**:固定在底部,包含来源、假设注释和版权,其大小明显小于正文;右下角的页码。脚注可以水平跨页或聚集在图表下方。
|
||||
- **Logo**:品牌标志主要出现在封面和封页上;内容页不放大Logo。
|
||||
- **分隔线**:细线、表格线、轴线、矩形轮廓;颜色为深灰色、灰绿色或低饱和度绿色——仅用于组织功能。
|
||||
- **背景图形**:封面和尾页可能使用人工智能肖像/生成视觉效果;内容页面仅使用抽象的绿色发光或渐变 - 没有大的人物图像。
|
||||
- **页面类型骨架**:
|
||||
- 封面:左侧为深灰色品牌和标题栏,右侧为带有深绿色光效的AI人像;标题位于细线框架或半框架区域内。
|
||||
- 内容数据页:标题位于左上角,大中央图表,解释位于右侧,来源位于底部。
|
||||
- 方法论部分分隔线:近乎纯深灰色,左下/中左的大标题,一些小方块作为视觉锚点。
|
||||
- 方法论内容页面:标签+标题位于顶部,流程、矩阵或步骤卡位于下方,补充测量基础注释位于底部。
|
||||
- 免责声明页面:左侧大标题,右侧密集法律文本,深灰色背景。
|
||||
- 结束页:品牌标识结合AI人像/绿光效果,信息量极少。
|
||||
- **默认阅读路径**:左上标签→主标题/副标题→主图表或矩阵核心数据→右侧解释→底部来源/测量基础注释→页码。
|
||||
|
||||
## 3. 内容组织- **标题风格**:使用咨询式的结论句,直接陈述排名、影响、机会、方法范围或比较判断;避免使用“概述”或“背景”等模糊标题。可能包括百分比、排名、主题和结果。
|
||||
- **副标题/关键信息**:在主标题下方,一小行说明量化范围、阶段、指标定义或基础,或“为什么重要”;颜色为浅绿色或浅灰色。
|
||||
- **正文结构**:短段落、短句子和项目符号;右侧解释通常有 2-4 段,每段支持一个发现。不要使用大块的中心叙述。
|
||||
- **分组逻辑**:按功能、任务类别、影响类型、AI 类型、自动化阶段、方法步骤、成本池或工作流程分组;标题更喜欢简短的名词短语。
|
||||
- **每页点数**:每页 1 个要点,搭配 2-4 个子发现;高密度的表格页可以添加更多条目,但一个标题结论必须引导所有条目。
|
||||
- **正文密度**:允许更多标签、脚注和数据,按列宽、标题、行组和色块分层。当文本超过 4-5 个短段落时,拆分页面或切换到表格/矩阵。
|
||||
- **项目符号样式**:使用短项目符号、短破折号、分号或换行符;以动词或名词短语开头;避免完整的长段落。
|
||||
- **关键字强调**:使用亮绿色、青绿色、粗体或在深色基础上局部突出显示百分比、排名、目标对象和舞台名称;没有下划线、发光轮廓或多色强调。
|
||||
- **数字/单位/时间范围**:突出显示百分比、范围、近似值、排名和阶段差异。数字和单位保持相邻;尽可能直接在图表内进行注释,而不是依赖图例。
|
||||
- **来源脚注**:底部必须保留来源、估计假设、样本范围、版权等;多个源用分号或短语水平压缩。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A.左图+右解释
|
||||
|
||||
用于定量结论,例如排名、横截面比较、成本影响、生产力提升和功能基准测试。左侧或中间 60-70% 包含水平条形图、堆叠条形图或范围条形图;右侧 20-30% 包含解释性文字;顶部是结论标题和测量依据,底部是来源。目标物体采用亮绿色/青绿色,其他物体采用灰白色或深绿色;细垂直线可以标记平均值、目标或阶段边界。不适合维度太多且标签无法保持可读性的场景,也不适合需要长解释的叙事页面。
|
||||
|
||||
### B.全角高密度表/矩阵
|
||||
|
||||
用于任务列表、工作流程、AI 类型比较、自动化潜力、成本池和方法输入/输出。身体几乎填满了宽度;列标题位于深绿色或青绿色底座上,行组位于深灰色侧边栏或色带上。文本列左对齐,数字列右对齐或居中,状态列带有点/检查十字/色块;通过青绿色底座、亮绿色数字、红/绿色状态点或粗体行来强调。不要在数据少于 3×3 的情况下使用它——它看起来会是空的;如果比较很复杂但数字较少,请切换到卡片或流程。
|
||||
|
||||
### C. 堆叠条形/瀑布式分解
|
||||
|
||||
用于分解总机会。左侧列出类别,右侧水平堆叠条显示阶段、类型或潜在范围,底部有一个小样本图例解释该系列。关键行标签或条形使用高亮绿色;非关键部分保持低饱和度。不要使用太多系列颜色——避免将深色页面变成彩色噪音。
|
||||
|
||||
### D. 方法流程用于诊断模型、评估步骤、量化逻辑和输入到输出路径。使用 4-5 个水平步骤卡或由箭头/线条连接的节点;每个步骤都包含一个简短的标题、一个类似图标的圆圈或小卡片以及 2-3 个说明点。使用绿色标题、圆形节点、细线箭头和阶段标签来表达层次结构。不适合携带大量精确数字。
|
||||
|
||||
### E. 多列概念/比较
|
||||
|
||||
对于影响类型、传统 AI/GenAI/Agent AI 差异、评估维度或能力分类。使用 3-4 栏,每栏一个概念,里面有短句、状态标记或小矩阵;列标题使用绿条、白卡或圆形色块。适合中等信息密度,不适用于复杂的财务建模。
|
||||
|
||||
### F. 部分分隔符/分隔页
|
||||
|
||||
用于方法论和免责声明等结构转变。使用空的深灰色字段,大标题左对齐或位于左下角,仅保留最少的装饰方块/线条。不要添加图表、卡片组或解释性段落——这会破坏部分分隔符的节奏。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- **主背景深灰色#2C2C2C**:内容页面、分区分隔线和免责声明页面的基色;不能用浅色背景代替——风格会被篡改。
|
||||
- **深绿色背景#0A3021 / #0B3928 / #0D4430**:用于内容页面、图表底板以及封面/结束页面的右侧氛围上的渐变区域。可大面积低饱和度使用;可以在同一系列内进行微调 - 不要切换到蓝紫色或橙红色。
|
||||
- **中绿色#125E42 / #176F4E / #197352**:用于主要图表系列、标题标签、模块基础和分组栏。可以用类似品牌的绿色代替,但必须与深绿色背景形成足够的对比。
|
||||
- **明亮的青绿色#1A835B / #35AA7E / #74D1A9**:用于关键数据、目标对象、强调栏、状态点、图例突出显示和一些强调文本。中小区域——避免填满整个页面;不适用于普通正文。
|
||||
- **白色/近白色#F2F4F2**:用于主标题、正文、表格文本和图表标签。不要将所有正文替换为纯灰色 - 可读性会下降。
|
||||
- **浅灰色 #B8B9B9 / #D7D7D7**:用于辅助标签、非关键栏、轴标签和脚注。
|
||||
- **深灰绿色#35594C / #5B7067 / #61746C**:用于网格线、表格分隔、弱化模块和边界线 - 仅结构颜色。
|
||||
- **红色/粉色状态,#C83A5A 周围**:仅在非常小的区域内标记低能力、限制、风险或负面状态的矩阵中。禁止作为主要装饰色。
|
||||
- **图表系列边界**:单页停留在绿色系列中,以灰色/白色为参考;红色状态仅作为例外。不要一次引入多个蓝色/黄色/紫色数据系列;如果不可避免地需要更多系列,请选择相同绿色的亮度阶梯。
|
||||
|
||||
## 6. 字体和文本层次结构- **字体字符**:整个牌组使用TrebuchetMS,具有TrebuchetMS-粗体和斜体建筑层次结构。不要混合使用衬线或手写字体。
|
||||
- **层次结构比率**:主体 = 1x;主标题约 1.7–2.4 倍;封面标题约 2.5–3.2 倍;部分分隔符大标题约 3-4 倍;字幕/关键信息约 1.2–1.5 倍;图表轴标签和表格主体约 0.8–1x;脚注、版权、页码约为 0.45–0.65x。
|
||||
- **标题**:左对齐,行距较紧,可以部分加粗或换行;不超过三行。
|
||||
- **关键信息/副标题**:在标题下方,以浅绿色或浅灰色说明测量基础 - 不要重复标题。
|
||||
- **正文**:以常规权重为主;关键短语可以是粗体或亮绿色;段落短,线宽适中,右侧解释左对齐。
|
||||
- **副标题/标签**:较小尺寸,绿色底座或绿色文本;保持文字简短。
|
||||
- **表格文本**:列标题可以是粗体,正文是常规的;文本列左对齐,数字/百分比列居中或右对齐。表格内没有频繁出现的斜体。
|
||||
- **图表标签**:轴标签和数据标签小于正文,但必须保持清晰;关键数据可能会放大到接近身体大小,并以亮绿色强调。
|
||||
- **注释/脚注/页码**:极小的级别,位于底部或右边缘垂直页码区域;必须清晰可辨,并且绝不能混入正文中。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
图表主要是水平条形图、堆积条形图、矩阵比较和一些流程图/节点图——没有复杂的多图表系统。
|
||||
|
||||
- **条形图**:深色基底,水平布局;左侧为类别标签,右侧为条形图和值。使用绿色梯子或浅灰色参考,关键栏采用明亮的青绿色;轴线和网格线细且对比度低。
|
||||
- **范围/阶段表达**:在同一行内,平行条或范围条可以表达当前与未来阶段、部署与重新发明。用不同的绿色亮度台阶或灰绿色基数进行区分,并直接在图例或条形上进行注释。
|
||||
- **堆叠条**:用于任务组合或工作流程分解。片段来自相同的绿色序列;标签嵌入或靠近条形,底部有一个简单的图例。
|
||||
- **矩阵图**:清晰的行/列标题,列标题可以使用绿条;单元格使用点、勾/叉或短文本短语来表示状态。绿色为正极,红色为负极/受限,灰白色为普通参考。
|
||||
- **流程/节点图**:圆形节点、白卡和细线箭头;节点可以包含简化的图标或首字母视觉效果,但它们必须服务于步骤的含义。
|
||||
- **图例**:位于图表的上方、下方或底部,绝不位于视觉中心;样本小而整齐,文字短。
|
||||
- **注释**:使用右侧解释文本而不是复杂的气泡标注;图表内仅保留必要的数字、基线和阶段注释。禁止大量浮动音符覆盖小节。
|
||||
- **图表边界**:无 3D 图表、饼图、圆环图、透视图或装饰曲线图;没有证据支持复杂的动画图表或插图图表。
|
||||
|
||||
## 8. 表格和高密度信息- **表格作用**:承载方法、任务、类型、评分、多维度对比——核心证据载体。
|
||||
- **标题**:深绿色或中绿色底座,白色/近白色文本,短列名称;多层标题可以使用不同的绿色步骤或细线分区。
|
||||
- **行/列结构**:行标签左对齐,指标/状态列居中,值列右对齐或居中。复杂的任务描述需要更宽的栏目;评级/潜力栏变得狭窄。
|
||||
- **行高和密度**:紧凑行高但保持间距;将长文本压缩为短语——单元格中没有完整的段落。
|
||||
- **分隔符**:细灰线、灰绿线或深色边界来分隔行组、列组和标题 - 没有厚重的外框。
|
||||
- **底色**:深灰色为底色;组行或关键列可以使用深绿色/青绿色底座;一般避免使用大的灯座以保护暗系统。
|
||||
- **关键行/列**:目标行、汇总列和关键百分比可以使用亮绿色、粗体、基色块或边框;仅选择 1-2 个焦点——切勿突出整个表格。
|
||||
- **斑马条纹**:允许极其微妙的深灰/深绿交替,绝不明显艳丽;当细线分隔已经存在时省略它。
|
||||
- **符号标记**:点、勾/叉、红绿状态点和评级色块可以表达能力/风险/自动化水平;需要图例或者必须可以从上下文推断出含义。
|
||||
- **摘要/脚注**:注释、来源和假设可能位于表格底部;摘要行或结果列必须与普通行区分开来。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
- **左上选项卡**:一个小的绿色矩形或文本标签,显示功能级别、任务级别、方法等。宽度跟随文本 - 无长横幅。
|
||||
- **标题框/封面细线框**:封面标题位于绿色/灰绿色细线框或半框内;内容页面很少使用大标题框。
|
||||
- **右侧解读栏**:在图表旁边,包含短段落、关键词突出显示和一些数字;它解释了“那又怎样”,并且不重复所有数据。
|
||||
- **卡片/步骤块**:方法页面使用深色卡片、小白色卡片或包含步骤名称和项目符号的绿色标题栏;边界清晰,四舍五入非常克制(如果有的话)。
|
||||
- **圆形节点**:对于影响类型或方法步骤,颜色为绿色、黄绿色、蓝绿色或小红色节点。数量很少——不是大量的装饰性气泡。
|
||||
- **分隔符和轮廓**:用于图表轴、表格边界和模块分区;薄、低对比度、功能至上。
|
||||
- **图例样本**:小矩形/点+解释阶段、状态、系列的短标签;保持靠近图表的底部或顶部。
|
||||
- **页码/版权垂直侧边栏**:页码位于右下角或沿右边缘,版权/来源沿底部。
|
||||
- **图像框架**:仅在封面和封页上放置AI肖像;如果图像出现在内容页面上,它必须保持深绿色覆盖层 - 没有屏幕截图拼贴。
|
||||
- **图标**:方法页面可能使用一些线性/简化的图标或圆形图标。禁止使用彩色拟物图标库;图标必须与步骤/状态直接相关。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
整体质感**黑暗、扁平、内敛、数据密集、技术咨询**;视觉焦点来自绿光、数据亮点和结构化信息。- **禁止**:大浅色背景、彩虹调色板、3D图表、发光轮廓文本、复杂纹理、表情符号、卡通插图、社交媒体风格贴纸、无意义的大图标、长居中副本、装饰波浪线、过度圆形卡片、强阴影浮动层。
|
||||
- **谨慎使用**:渐变、阴影、圆形节点、图标、红色状态。渐变仅适用于深绿色背景氛围;阴影,如果有的话,也极其微弱;红色仅针对负/限制状态;仅用于方法步骤的图标。
|
||||
- **可在特殊页面上使用**:人工智能肖像/生成图像仅在封面和结束页上,作为主题视觉效果;部分分隔线可以使用最小的几何正方形;免责声明页面可能会使用密集的法律文本。
|
||||
- **图像依赖边界**:内容页面不依赖照片、屏幕截图或插图来讲述故事,除非图像本身就是数据证据;否则转换为图表、表格或流程。
|
||||
- **品牌感觉边界**:品牌标志不应在每页上放大和重复;品牌感觉来自于深色基底、绿色系统、版式和脚注结构。
|
||||
- **密度控制**:永远不要牺牲对齐、对比度和脚注可读性。如果文本重叠、表格挤压或图表标签变得难以辨认,请先剪切内容,而不是将其缩小到难以阅读。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 这是一个 16:9 的深色咨询报告页面,而不是海报、信息图表或社交卡吗?
|
||||
- 左上角是否有清晰的标签/标题区域,标题是否是结论句而不是模糊的主题词?
|
||||
- 单页是否仅包含 1 个要点,并通过图表、表格、矩阵或流程证明?
|
||||
- 背景是否以深灰色和深绿色为主,亮绿色仅用于关键数据/标签?
|
||||
- 正文是否组织为以下之一:左图 + 右解释、全角矩阵、流程步骤或多列比较?
|
||||
- 右侧解释是带有关键词突出显示的短段落,而不是长篇正文吗?
|
||||
- 图表是否使用水平条形图、堆叠条形图、矩阵或流节点;是否避免使用 3D、饼图和艳丽的系列颜色?
|
||||
- 表格是否有清晰的标题、分组、对齐、细分隔符和关键行/列?
|
||||
- 页脚是否保留来源、假设、版权和页码而不与正文重叠?
|
||||
- 该类型是否保留 TrebuchetMS 特征,并通过相对大小、重量和颜色表示层次结构?
|
||||
- 是否避免了照片/屏幕截图/插图滥用,人工智能肖像仅出现在封面或结束页上?
|
||||
- 是否出现溢出、截断、乱码、重叠、对比度低、遮挡、内容空、比例错误、机械模板重复等情况?
|
||||
- 如果更换品牌颜色,深色底色、高对比度白色文字和单一基色的原则是否仍然有效?
|
||||
|
Before Width: | Height: | Size: 404 KiB |
@@ -1,121 +0,0 @@
|
||||
# 制图策略·地图策略风格设计系统
|
||||
|
||||
> 预期主页标签:战略规划/战略
|
||||
> 视觉隐喻:当代图集+策略室
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
国际化、区域扩张、业务组合、生态系统布局、资源配置和长期路线图。它不是传统的咨询蓝白模板,而是“将策略放入空间”的地图式叙事:温暖的白色纸张、深邃的海军蓝、内敛的钴蓝色、朱红色的坐标,通过路线、区域、比例、编号和图例构建方向感。
|
||||
|
||||
基本原则:
|
||||
|
||||
1. 每一页首先回答一个决策问题,然后显示位置、路径或优先级证据。
|
||||
2. 地图语言服务于比较和顺序——等高线和经纬网格不是装饰壁纸。
|
||||
3、一张主图引领页面;标题、数字和标签都是围绕它组织的。
|
||||
4. 当地理信息不足时,使用抽象的位置图、路线图或合成地图——切勿捏造精确的边界。
|
||||
5. 标题读起来就像管理层实际上会说的判断——避免使用“战略蓝图”或“共同走向未来”等模糊口号。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
画布为 16:9 横向。内容页面以暖白色为主;部分分隔页可以使用深海军蓝。外部利润保持稳定;微小的坐标刻度可能会出现在四个角,但内容永远不会接触边缘。
|
||||
|
||||
顶部是标题和页码坐标区,约占页面的14%~18%;中间是主地图工作区,65%–72%;底部为来源、假设和图例保留了空间。标题下没有全角粗线,而是用短刻度线、页码和细线代替。
|
||||
|
||||
封面采用“大标题+抽象区域图+一条主要路线”。标题位于左侧或左下角;地图占据右侧的 45%–60%。内容页面在地图、矩阵、路线和时间线之间切换,但始终保持相同的坐标和图例语法。
|
||||
|
||||
## 3. 内容组织
|
||||
|
||||
标题使用结论句或明确的问题,例如“中东提供最快的首次进入验证窗口”。副标题添加范围、年份、评估维度或数据定义和测量基础。
|
||||
|
||||
一页只有一个战略判断,最多有3个支持证据。正文使用短句、编号和直接标签——没有长的解释性段落。
|
||||
|
||||
空间分组优先考虑区域、市场、阶段、进入模式、能力节点和风险水平。每条路线都必须有起点、目的地、顺序和意义;每种颜色都必须在图例中进行解释。
|
||||
|
||||
数字紧邻其对象,带有单位、时间和测量基础。来源和假设位于页脚中,而不是隐藏在视觉中心。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 战略地图页面
|
||||
|
||||
左边25%~32%持有结论和2~3个关键解释;右边有一张抽象的区域地图。点标记市场或能力节点,线宽表示优先级,实线/虚线区分已经到位的内容和必须构建的内容。适合市场选择、网络布局、资源迁移。
|
||||
|
||||
### B. 三个市场比较区间
|
||||
|
||||
页面水平划分为三个平行区域,每个区域包含一个市场名称、一个主号码、一张小位置图和2-3个评价维度。三列必须同构;推荐的选项可以用钴色边缘线或朱红色坐标点来强调。
|
||||
|
||||
### C. 机会-难度矩阵
|
||||
|
||||
矩阵占据了页面的60%~70%,推荐结论在右侧。点标签直接命名市场,而不依赖于复杂的图例。象限名称是可操作的判断,例如“首先进入”、“建立伙伴关系”、“持续关注”——而不是抽象的明星/摇钱树陈词滥调。
|
||||
|
||||
### D. 路线和阶段页面一条从左下到右上的主干道连接着3-5个阶段。每个阶段都有其时间安排、关键操作、阈值和退出条件。路线可能会转弯;不使用等宽堆叠工艺卡。
|
||||
|
||||
### E. 能力网页
|
||||
|
||||
中央功能锚定页面,连接渠道、交付、合规性、产品和人才节点。连接线表达依赖性;节点大小表示优先级。仅显示真正影响实施的 5-8 个节点。
|
||||
|
||||
### F. 决策页面
|
||||
|
||||
左边有一个大结论;右侧是“立即决定/进入条件/90 天行动”的三部分列表。缩小的路线图可以回顾整个旅程——这不是一个普通的感谢页面。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- 暖纸白色#F4F0E8:主要内容背景。
|
||||
- 深海军#0B1F33:标题、章节基础、主要路线和核心文本。
|
||||
- 钴蓝色#2F5BFF:推荐选项、关键路径和关键数字。
|
||||
- Vermilion #E4572E:风险、关键坐标和必须决定的节点;它的面积严格小于钴。
|
||||
- 雾青#9BD5D8:次要区域、历史阶段和去强调的数据。
|
||||
- 石墨灰色 #5B6570:标题、轴、来源和未选择的选项。
|
||||
|
||||
常规页面使用纸白+海军蓝+钴色;朱红色只出现一次。禁止使用彩虹图、荧光渐变和大型红绿对立。
|
||||
|
||||
## 6. 字体和文本层次结构
|
||||
|
||||
中文使用思源黑体或苹果方;英语使用 Arial/Helvetica 级无衬线字体;部分分隔符大标题可以与一行乔治亚级衬线英文搭配以获得地图集的感觉,但切勿混合使用两种以上的字体。
|
||||
|
||||
- 封面标题:4–5 倍正文大小,粗体,最多两行。
|
||||
- 页面标题:正文大小的 2–2.6 倍,左对齐。
|
||||
- 关键数字:主体尺寸的 2.5–3.5 倍,优选等宽或压缩数字。
|
||||
- 正文:1x,短句。
|
||||
- 坐标、图例、来源:0.55–0.75x,保持可读。
|
||||
|
||||
数字、年份和区域代码可以使用等宽字体。没有全大写的长句子、斜体长段落或多层阴影。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
喜欢点图、气泡图、斜率图、路径图、二维矩阵、区域小倍数地图和带有直接注释的条形图。
|
||||
|
||||
图表背景保持透明或纸白色;网格线采用极细的雾灰色。重点系列使用钴;历史/基线使用雾青色或灰色;风险阈值使用单一朱红色线。
|
||||
|
||||
地图必须带有图例、方向或比例提示。抽象地图必须清楚地标记为“示意图”——不允许出现虚假的管理精度。关键差异直接在点旁边注释,减少了独立的图例。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
表格提供市场评分、进入条件和风险列表。标题使用深海军蓝文本或浅雾青色底色;行与行之间有细线,没有粗边框。
|
||||
|
||||
推荐的行通过左侧的钴坐标条进行强调;风险单元格使用小朱红色点或文本标签——切勿将整个单元格涂成红色。数字右对齐,文本左对齐,评分标准和依据在表底部注明。
|
||||
|
||||
高密度内容最好分成“地图/矩阵+右侧解释”——不要将其压缩成十列宽的表格。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
稳定的组件包括坐标点、路线、区域轮廓、短刻度、页码代码、图例、比例式分隔符和细线注释。
|
||||
|
||||
节点采用实心圆或同心圆;线路分为三种类型——主线路、备用线路和依赖线路。仅当方向很重要时才会出现箭头。
|
||||
|
||||
照片不是默认元素。如果使用城市或场景照片,则仅允许一张经过去饱和处理的大图像 - 不允许使用库存照片拼贴。
|
||||
|
||||
## 10. 质地和违禁物品总体而言就像一本当代战略地图集:精确、安静、方向明确。纸基可能具有极轻的质感;深色页面保持纯净;无玻璃态。
|
||||
|
||||
禁止:默认咨询蓝卡墙、随机世界地图水印、假国家边界、3D地球仪、发光路线、彩虹热图、圆形UI面板、表情符号、装饰指南针和没有结论的整页地图。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 每个页面都可以用一句话说明它要求做出什么决定吗?
|
||||
- 地图、矩阵或路线是否真正具有比较关系?
|
||||
- 所有颜色、线条样式和节点大小是否都有明确的含义?
|
||||
- 区域和路径是否有明确标记,没有捏造的精确地理信息?
|
||||
- 推荐的选项是否清晰突出,而替代方案仍然可见?
|
||||
- 页面是否保持纸白色、海军蓝、钴蓝色和朱红色的稳定语法?
|
||||
- 标题、图例、来源、单位和假设是否完整?
|
||||
- 是否避免使用卡片墙、3D 地球仪、随机装饰线和类似模板的标语?
|
||||
- 您是否检查过重叠、裁剪、乱码、低对比度和文本换行?
|
||||
|
Before Width: | Height: | Size: 1.3 MiB |
@@ -1,166 +0,0 @@
|
||||
# 项目报告名称 — 组织名称联合备忘录风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
私募股权/投资银行备忘录类型,高密度报告风格,适用于银团备忘录、投资委员会材料、尽职调查摘要、交易批准、融资方案和管理业务计划材料。针对金融和行业分析读者,支持快速审查交易条款、投资逻辑、客户和市场证据、财务预测、估值、风险和执行计划;目标是“可审查、可接受后续质疑、可追溯”。
|
||||
|
||||
固定样式:16:9风景,高利用率画布;浅奶油白色背景;左侧有一条细橙色垂直线;顶部灰色/深海军蓝分段导航;右上角的小品牌文字标记(显示新主题名称的纯小文本;不要绘制图形徽标);一个大的橙色标题;标题下有一个灰色的信息句。正文使用表格、条形图、桥图、并排模块、评级点、流程框和文本证据块。字体绝对以Arial家族为主;粗体用于标题、表格标题、字段名称和关键数字;斜体仅用于注释或特别强调。
|
||||
|
||||
基本原则:
|
||||
|
||||
1. 信息完整性是第一位的,但必须将其结构化为表格、列、编号、色块标题、脚注和细分隔线系统;长段文字不得自由浮动。
|
||||
2、橙色仅用于页面标题、当前章节、关键数据、关键栏、左侧竖线和少量标签;它不用作大面积的装饰填充,并且不会牺牲正文的可读性。
|
||||
3、身体面积可能有所不同;导航、标题区域、身份标记、左边界和脚注/页码保持稳定。图表美观必须服从于数据的清晰度、可比性和可追溯性。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
画布为16:9横向宽屏,页边距较窄,正文区域占据绝大多数。默认背景是浅乳白色/近白色,大约`#FCF8F5`、`#FEFBF7`、`#F4F0EC`;允许使用非常微弱的品牌几何水印或浅灰色图形,但不透明度较低,因此不会影响数字的阅读。
|
||||
|
||||
封面采用全出血的深蓝色城市夜景照片,叠加半透明的深蓝色品牌几何图形。标题组位于右中区域:主标题为白色粗体,副标题为白色常规字体,日期位于一个橙色小标签内;垂直保密/时间/电子邮件信息位于左侧,橙色品牌字标位于右上角(纯小文本呈现新主题名称;请勿绘制图形徽标)。封面不使用内容页顶部导航。
|
||||
|
||||
部分分隔线使用浅乳白色背景、左侧橙色线、右上角的身份标记(表示新主题名称的纯小文本;不要绘制图形徽标)和大量空白。章节编号是一个大的橙色数字,章节标题是大的深海军字体;它们只是切换部分,并不进行复杂的论证。示例包括“行业和公司概况”、“商业计划”、“估值和退出”以及“融资、尽职调查和关键 IC 发现”。
|
||||
|
||||
标准内容页面骨架:- 顶部导航:页面最顶部的水平分段箭头/带;当前部分具有深海军蓝背景和白色文本,其他部分具有浅灰色背景或低对比度灰色文本。它的高度很低,仅用于定向。
|
||||
- 右上角身份标记:一个橙色的小品牌文字标记(表示新主题名称的纯小文本;不要绘制图形徽标),固定在右上角,周围保留清晰的空间,不重叠图表标题或导航。
|
||||
- 左侧橙色线:大多数内容页面从上到下保留一条细橙色垂直线;内容从右侧开始。
|
||||
- 标题区域:导航下方左对齐;主标题通常是橙色粗体,后面是单个灰色/深灰色信息句子。
|
||||
- 正文区域:紧接标题下方,包含表格、图表、模块、流程、并排证据或长文本,底部保留狭窄的脚注带。
|
||||
- 页脚:底部的来源、注释、保密措辞和页码。脚注明显小于正文,呈浅灰色;页码通常位于右下角或底部边缘。
|
||||
|
||||
阅读路径:顶部导航→橙色标题→信息句→正文区域从左到右、从上到下→底部脚注。
|
||||
|
||||
## 3. 内容组织
|
||||
|
||||
标题使用判断型或主题型短语,例如“交易摘要”、“执行摘要”、“为什么我们喜欢组织名称”、“客户概述”、“商业计划:并购”;可以直接陈述强有力的结论,例如“强大的游戏发布渠道推动游戏开发支出”——没有营销口号。
|
||||
|
||||
标题下的信息句通常为一行,用于陈述结论、数据依据或页面的意义;它的颜色比正文浅,语气专业克制,没有感叹号、修辞或大段居中的段落。
|
||||
|
||||
身体规则:
|
||||
|
||||
- 长文本使用粗体副标题+正文解释+项目符号/编号点;每个段落都必须有明确的副标题或左侧标签。
|
||||
- 摘要页面可以使用左侧垂直导航卡或右侧带有密集项目符号的编号框;每个项目符号首先给出一个粗体关键字,然后给出解释。
|
||||
- 按交易条款、投资亮点、行业/客户、财务、估值、风险、融资和尽职调查等专业维度进行分组。
|
||||
- 将单个页面压缩为3-6个模块或4-8个表行;不鼓励只包含一个大语句的页面。
|
||||
- 使用粗体、橙色数字、深蓝色表格标题、浅色方框或点来强调;没有荧光突出、手绘圆圈或夸张的贴纸。
|
||||
- 保持数据旁边的数字、单位和时基,例如 FY、CY、24E、25E、CAGR、EV、EBITDA、bps、%;表格列标题解释了基础。
|
||||
- 来源脚注以小号浅灰色字体位于底部;多个碱基通过上标或括号连接——不要在正文中堆放长音符。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 交易摘要/条款清单类型
|
||||
|
||||
用于交易概述、资本结构、估值、来源和用途以及关键术语。主体是一个大表:左列包含深色或灰色字段名称,右侧包含解释和数据;中间可能会嵌入一个带有橙色标题的小型评估表或来源和使用表。阅读顺序为字段名→值→注释。适合多基础事实;不适合复杂的因果关系或多阶段过程。
|
||||
|
||||
### B. 执行摘要/多结论长文本类型
|
||||
|
||||
用于投资亮点、主要风险和论点摘要。垂直部分的卡片/编号模块位于左侧,密集的子弹位于右侧;深海军色卡片标记主要类别,橙色卡片标记当前或关键项目。每个项目符号都以粗体关键字开头。适合专业读者扫描;不适合非专业观众或谈话式演示。### C. 投资理由/逐项论证类型
|
||||
|
||||
典型的页面是“为什么我们喜欢组织名称”。正文用圆形 A/B/C/D 编号或标签组织水平参数带;每个波段都包含一个深海军蓝字幕栏、一个橙色数字、正文项目符号和关键人物。适合3-5个并行原因;不适合超过 6 个项目或每个项目都需要复杂图表的内容。
|
||||
|
||||
### D. 水平比较表/对等矩阵类型
|
||||
|
||||
用于比较公司、客户、产品、竞争对手或同行指标。左列包含公司名称或类别标签,顶部包含指标,单元格包含数字、文本、评级点或短标签。标头可以使用浅橙色/深蓝色;关键行和列可以使用浅蓝色或浅橙色填充。适合横向比较;不适合长时间的定性解释。
|
||||
|
||||
### E.图表+说明混合类型
|
||||
|
||||
用于收入趋势、市场规模、增长率、客户渗透率、财务预测、桥图和运营 KPI。主图表占据了大部分宽度,上面有结论栏/标题,下面或侧面有说明框。橙色突出核心系列,深蓝色/青色用于对比系列,灰色用于背景或历史项目。适合数据论证;没有明确的数据基础,不适合概念图。
|
||||
|
||||
### F. 流程/业务计划类型
|
||||
|
||||
用于并购渠道、运营计划和价值创造计划。由水平流程框、阶段箭头、小表格和底部图表组成;深蓝色条作为阶段标题,橙色节点标记关键操作或关键结果,灯箱带有说明。适用于阶段、路径、漏斗、执行杆;它不应该被滥用于普通列表。
|
||||
|
||||
### G. 高密度并排模块类型
|
||||
|
||||
用于客户概述、管理/顾问观点、风险缓解和尽职调查结果。页面分为多个相同大小的卡片或水平模块,每个模块包含副标题、短项目符号、图标式标记或微型图表;模块间距小,用细线和浅色填充进行分隔。不适合单个大图像或情感故事讲述。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- 背景颜色:浅乳白色/近白色`#FCF8F5`、`#FEFBF7`、`#F4F0EC`。用于内容页面的大面积基础;可以用相同亮度的暖白色代替——不要切换到高饱和度背景。
|
||||
- 正文:深灰色`#4D4E53`、`#3E3E42`。用于正文、表格文本和注释文本;必须保持高对比度,不得用浅灰色代替。
|
||||
- 标题/品牌重音橙色:`#C4794A`、`#D8752A`、`#F07A00`。用于主标题、左侧垂直线、日期标签、当前项目、关键数字、重要栏和表格标题;中小面积。可以用品牌原色代替,但暖色调的作用必须保留。
|
||||
- 结构深海军蓝:`#12234E`、`#263660`、`#293B60`。用于当前导航部分、深色侧边栏、表格标题、流程阶段栏和封面覆盖;通常与白色文本配对。可以用深品牌蓝色代替,但必须保持严肃和高对比度。
|
||||
- 辅助灰度:`#BEB6B3`、`#D9D5D5`、`#E8E3E1`、`#C6C6C9`。用于分隔线、表格边框、非当前导航、填充、轴和网格线。
|
||||
- 辅助浅蓝色/青色:`#5E9BB9`、`#9BA8AF`、`#C6CED6`。用于辅助图表系列、客户/竞争对手比较、关键行块或状态区域;它不得与橙色竞争主要口音角色。
|
||||
- 正/负/状态颜色:主要是橙色、深蓝色、青色和灰色,少量绿色表示成长或正数。应谨慎使用绿色以实现积极的改变;红色不属于稳定的原色系统。将单页的主色调控制为橙色、深蓝色、灰色以及少量的水青色。图表系列不应扩展到彩虹色;状态颜色仅用于判断,而不是装饰。
|
||||
|
||||
## 6. 字体和文本层次结构
|
||||
|
||||
字体以 Arial / Arial Bold 为主。 Helvetica Bold 仅用于封面或特殊保密覆盖文本; Calibri、Segoe UI 等不是主系统的一部分。更喜欢 Arial 或等效的无衬线字体。
|
||||
|
||||
- 封面主标题:3–4x 正文,白色粗体。
|
||||
- 内容页面主标题:2.5–3x 正文,橙色粗体,左对齐。
|
||||
- 部分分隔标题:3–4x 正文,深海军蓝;部分编号可能更大,呈橙色。
|
||||
- 信息句/副标题:1–1.2x 正文,灰色常规或半粗体,位于标题下方。
|
||||
- 正文:1x,深灰色;行距要紧,但不能重叠。
|
||||
- 副标题/字段名称/表标题:1–1.15x 正文文本,粗体;可能是深蓝色背景上的白色文本或浅色背景上的深灰色文本。
|
||||
- 表格和图表标签:0.75–0.95x 正文;必要时压缩但保持清晰。
|
||||
- 注释、来源、页码:0.55–0.7x 正文,浅灰色。
|
||||
|
||||
文本主要是左对齐的;表数字右对齐或按小数点视觉对齐;根据数据类型,列标题居中或左对齐。禁止大型中心身体通道。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
使用堆积柱形图、分组柱形图、水平条形图、桥图、时间序列柱线组合、小评级/点阵以及矩阵表内的迷你图;图表嵌入在内容页面的正文区域中,而不是全屏的单个图表。
|
||||
|
||||
轴线和网格线使用浅灰色;图例位于图表顶部附近或标题旁边,使用小色样+短标签。数据标签直接放置在列或条的末端或表格单元格内。
|
||||
|
||||
橙色标志着核心系列、当前项目或关键增长;深蓝色标记基线/历史/主要比较;青色-蓝色标记第二个比较;灰色标记背景、其他项目或非关键数据。预测、目标和估计年份通过 E、FY、CY、虚线、浅色阴影区域或列标题中的明确标签和定义来表达,而不是通过复杂的视觉效果。
|
||||
|
||||
关键数据可以使用橙色数字、粗体、注释框或深色标签。请勿使用 3D 图表、渐变柱、维度饼图、重阴影或没有数据标签的装饰图形。如果图表无法比表格更清楚地表达比较,则更喜欢表格。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
表格包含条款、财务、同行、客户、尽职调查结论和模型假设。标题使用深海军蓝背景搭配白色文本、橙色背景搭配白色文本或浅橙色背景搭配深色文本;左字段列使用深灰色/深蓝色背景和白色文本,或浅灰色背景和粗体文本。
|
||||
|
||||
行列紧凑,行高低;边框是浅灰色的细线。背景在白色、浅乳白色、浅灰色、浅橙色和浅蓝色之间交替形成区域;宽桌子可以使用斑马条纹,但不宜太浓。关键行和列使用橙色标题、浅橙色填充、深蓝色侧边栏或粗体数字;摘要行使用较暗的填充或顶部边框线。
|
||||
|
||||
金额、百分比、倍数和年份尽可能在其列内右对齐或居中;文本左对齐;单位写在列标题或第一行中,而不是在每个单元格中重复。评级点、圆圈、复选标记或短标签在视觉上位于同一列的中心。
|
||||
|
||||
脚注和上标向下移动到底部,不会挤压数据区域。避免将多行长句子塞入单元格中;当需要解释时,请使用表格下方的注释或右侧的说明框。## 9. 组件和图形元素
|
||||
|
||||
- 顶部导航栏:当前部分为深蓝色,其他部分为灰色;它不能变成一个大的菜单或图标导航。
|
||||
- 左侧橙色竖线:不承载任何内容的品牌边界;保留在大多数内容页面上。
|
||||
- 橙色主标题:页面主题的入口点;必须左对齐,与正文保持清晰的间距。
|
||||
- 信息句:标题下的一行判断语句。
|
||||
- 深蓝色字幕栏:用于分组、参数带、流程阶段和表格模块标题,带有简短的白色文本标题。
|
||||
- 橙色编号/圆形徽章:用于 A/B/C/D、步骤、要点或强调的项目。
|
||||
- 光填充卡/模块盒:用于并排证据、客户模块、风险和缓解措施;浅灰色边框,填充浅奶油色/浅灰色/浅橙色。
|
||||
- 流程箭头和连接器:用于并购、管道和价值创造步骤;线条平坦,没有阴影。
|
||||
- 公司标识符:用于客户、同行和竞争对手页面上表格的左列;以统一大小的文本名称或中性占位符块的形式呈现,避免掩盖内容 - 不要绘制图形徽标。
|
||||
- 脚注/来源带:位于底部,采用小浅灰色类型。
|
||||
|
||||
彩色图标并不是一种稳定的语言;如果必须使用,请使用很少的、单一颜色或品牌颜色的,以服务于餐桌识别。可以使用矩阵标记、点等级和复选标记,但需要统一的样式。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
总体而言,保持扁平、克制、专业和轻度品牌化。除了封面照片外,内容页面几乎不依赖大图像背景;照片、屏幕截图和插图不是主要语言。圆角仅用在少数标签或卡片上,半径较小;阴影、渐变、发光、3D 和复杂纹理不属于此风格。
|
||||
|
||||
禁止:
|
||||
|
||||
- 内容页面上的大照片背景或图像上压有低对比度文本。
|
||||
- 3D 图表、渐变柱、发光边框、浓重阴影、拟物化按钮。
|
||||
- 表情符号、卡通插图、社交媒体风格的贴纸、手绘涂鸦、高饱和度彩虹图标。
|
||||
- 大段居中的文案、无结构的长段落、纯粹的口号页面。
|
||||
- 一页上的原色过多,或与橙色/深海军蓝系统冲突的强烈颜色。
|
||||
|
||||
谨慎使用:
|
||||
|
||||
- 公司标识符和客户标识符:仅在比较表和客户页面上,以统一大小的文本名称或中性占位符块形式呈现 - 请勿绘制图形徽标。
|
||||
- 浅色水印/品牌几何形状:可以用作低不透明度背景,但不得穿过文本或表格。
|
||||
- 绿色/红色状态颜色:仅用于明确的语义,例如增长、下降或风险。
|
||||
- 圆形标签和小图标:它们可能仅有助于识别;它们不能取代文本证据。
|
||||
|
||||
特殊页面:封面可以使用全出血的深蓝色照片,并叠加半透明的几何图形;部分分隔符可以使用大量的空白和大数字;内容页面必须返回到表格化、基于证据、导航一致的系统。
|
||||
|
||||
## 11. 一代清单- 骨架:是16:9吗?内容页面是否保留顶部导航、右上角品牌文字标记、左侧橙色线、标题区域和底部脚注/页码?
|
||||
- 文案:标题简短且专业;信息句是否在一行中陈述了判断;正文是由字段、项目符号、表格还是模块组成的?
|
||||
- 层次结构:橙色标题、深蓝色分组、正文和注释是否具有明显的相对大小差异;是否避免了对特定字体大小的依赖?
|
||||
- 颜色:背景为浅乳白色;原色仅限于橙色、深蓝色、灰色和少量的青色;橙色只是为了强调吗?
|
||||
- 布局:正文是否可从左到右、从上到下扫描;高密度内容是否有列、标题、编号和分隔线?
|
||||
- 图表:是否使用扁平柱/条/桥/矩阵;轴和网格是浅灰色的;关键数据是否直接标注;是否避免使用 3D 和彩虹色?
|
||||
- 表格:标题、字段列、边框、填充、关键行和列、数字对齐和单位基数是否一致?
|
||||
- 组件:导航栏、深蓝色标题栏、橙色编号、浅色卡片和脚注来源是否统一,而不是逐页任意重塑?
|
||||
- 禁止项目:内容页面上是否没有表情符号、插画风格的图标、浓重的阴影、渐变、发光、复杂的纹理或大面积的照片?
|
||||
- 技术质量:检查是否有溢出、截断、乱码、重叠、低对比度、遮挡、空内容或比例错误;当连续使用相同的模板时,身体结构应该发生变化,而骨架则不会。
|
||||
- 没有默认使用卡片:除非用户明确要求,否则严格禁止使用圆角矩形或矩形卡片来构建层次结构或对齐方式 - 线段、空白以及字体和字体大小差异是更好的解决方案。
|
||||
- 无等分组合:除非没有其他布局可用,否则不要默认三向分割、四向分割或2×2矩阵——包括三向分割+标题+结论等三部分结构。
|
||||
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -1,116 +0,0 @@
|
||||
# PJ Mountain — 投资备忘录(组织名称风格)风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
适用于私募股权投资委员会、交易审批、尽职调查报告和投资备忘录的高密度专业 PPT 风格,适合具有财务、战略、运营和会计背景的读者。每一页构建了一条可追溯的“结论-证据-假设-来源”链,强调数据密度、比较维度和可审查性。
|
||||
|
||||
核心是白色背景的全画布、左上角的标题、标题下的斜体结论线、紫色结构条、蓝色/青色/灰色分类颜色、细网格表格、紧凑的 Calibri 系列字体以及底部脚注和页码。正文主要由图表、表格、流程框、并排模块和少量证据图像组成;装饰极少,颜色仅起到剖面、分类、数据系列和风险提示的作用。
|
||||
|
||||
基本原则:
|
||||
|
||||
1. 清晰胜过空白:优先保留关键结论、数据、测量基础、来源和比较维度,通过分隔线、标题栏、浅灰色填充和层次结构维持顺序。
|
||||
2、颜色是信息词典,而不是装饰:紫色用于结构和标题;蓝色/青色/灰色表示业务类别或图表系列;红色仅表示风险、差异、敏感假设或黑框强调。
|
||||
3. 页面可能很复杂,但骨架必须保持稳定:正文可以混合图表、表格、流程、地图和图像,但标题区域、结论行、底部来源、页码和颜色语法在整个甲板上保持一致。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
画布为 16:9 横向,默认白色背景;内容页面使用窄边距和高内容利用率。顶部是标题区域,底部是脚注和页码区域,中间是主要证据区域。阅读路径:左上部分标签/标题→标题下斜体结论线→主要区域从左到右,从上到下→底部脚注→右下页码。
|
||||
|
||||
封面页:左侧有纯文本小字体身份标记(写新主题名称;不要绘制图形标志)、项目名称、文档类型和日期;右边是一个大的蓝色线条艺术抽象图形;左下角有红色斜体的保密说明。它使用大量的空白和品牌线条艺术,并且不采用内容页脚注系统;这种抽象线条仅适合封面,不应扩散到内容页面背景中。
|
||||
|
||||
免责声明页面:左上角有一个大标题;正文是一列密集的小字体段落,几乎没有颜色或图形,只保留页码。充分披露优先;不得将其强制放入显示型模块中。
|
||||
|
||||
内容页:左上角可能有一个细边框矩形或一个小编号部分标签;主标题位于其下方或右侧,呈紫色或深灰色;标题下方有一条斜体灰色结论线。正文分为2-4个模块,模块标题采用紫色横条或细边框标题栏。底部保留了来源、注释和假设陈述,右下角有一个很小的页码。
|
||||
|
||||
部分/分隔页保留白色背景和左上角标题逻辑,没有大图像背景。没有持久的顶部标题导航,没有整页水印,也没有大照片背景。来源和脚注是固定的底部信息层,不得被图表或表格覆盖。
|
||||
|
||||
## 3. 内容组织
|
||||
|
||||
标题采用陈述性、分析性的“主题+判断维度”措辞,如公司概况、市场展望、对标分析、结构性解决方案等;避免广告式的短标题。在标题下,一两个斜体信息行表达最重要的判断、发现或问题;它们不具备正文的全部功能。主体被组织成证据块。每个模块必须有一个清晰的副标题,说明比较对象、指标或结论的方向;在里面,使用项目符号、短句、数字、表格或图表。一页可以包含多个要点,但它们必须属于一个主要问题;当平行点超过 4 组时,使用编号、色带或列来建立路径。
|
||||
|
||||
段落应该简短,最好使用项目符号和缩写表达;每一个项目符号包含一个行动/判断+一个事实依据;不要写连续的散文。关键词可以用粗体、紫色、红色框或背景颜色强调,但每页不得超过两个强调级别。
|
||||
|
||||
数字、单位和时基与数据相邻,并在图表标题、表标题或脚注中说明。财务和运营数据保持列对齐;百分比、倍数、年份和货币单位不得混合。来源脚注以小灰色字体位于底部;多个外部数据源按图表或模块排序。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
1. **基于文本的摘要页面**:适合执行摘要、投资论文和主要发现。结构:大标题+斜体概述+多组紫色字幕+项目符号;每组仅包含一个判断类别,并通过字幕、粗体关键词和稀疏编号来强调;不要插入破坏摘要阅读的复杂图表。
|
||||
|
||||
2. **左侧编号导航 + 右侧结论列表**:适合并排展示 4 个尽职调查主题、投资论文或风险应对。左侧放置垂直色块或编号标签;右侧有相应的结论和项目符号。色块代表部分或问题类别,不能纯粹是装饰性的;如果主题没有明确的分组关系,请勿使用此布局。
|
||||
|
||||
3. **多图表仪表板页面**:适合市场规模、业务组合、成本基准和增长动力等多指标比较。正文采用2×2网格或一大二小垂直排列,每个区块有一个紫色标题栏;旁边的简短注释框可以解释异常值。避免拼贴不相关的图表;所有图表必须共同回答标题下的信息线。
|
||||
|
||||
4. **图表+右侧说明/注释页**:适合趋势线、柱形图、箱线图、瀑布图。将主图表放在左侧或中间;在右侧,使用亮色框、红色框或短项目符号来标记结论;红色笔划、箭头、标签或对比数据点可用于强调。如果图表本身已经很拥挤,请将说明文字移至底部或拆分页面。
|
||||
|
||||
5. **表格/矩阵页面**:适合财务模型、可比公司、交易结构、尽职调查清单和收益桥梁。主体是一张大桌子或几张小桌子;紫色标题建立主要分组,浅灰色填充用于辅助标题或摘要行。仅用于自然具有行列维度的信息;不要将叙述性文本强行放入表格中。
|
||||
|
||||
6. **流程/结构图页面**:适合交易结构、融资路径、组织关系或价值创造路径。使用矩形框、细线箭头和少量颜色来区分阶段或聚会。箭头代表真实流程或资本/信息流,不得用作装饰性运动线。复杂的结构图保留图例和脚注解释。
|
||||
|
||||
7. **证据图像/姓名文字混合页面**:可放置少量地图、办公地点、人物、客户姓名文字列表或业务截图。图像必须受边框、标题和说明文字的约束,并且通常面积小于图表/表格主体;它们不是情感背景或整页视觉主角。
|
||||
|
||||
## 5. 颜色系统- **背景:#FFFFFF**。所有内容页、摘要页、表格页的绝对主背景。无法更换为深色背景;如果模块需要区分,请使用浅灰色填充,而不是重新着色整个页面。
|
||||
- **正文:#333333–#4A4A4A**。用于正文、表格内容和图表标签;每个输出设备可能会稍微变暗,但不应该使用纯黑色,因为这样感觉太压抑。
|
||||
- **标题/结构紫色:#470D59**。用于中小型区域的内容页标题、部分标签、模块标题栏、表格标题和关键分隔线;不应覆盖整个页面。可以用深色品牌颜色代替,但必须保持高对比度和金融机构的严肃性。
|
||||
- **封面/商业深蓝色:#0020AC / #0126AE**。用于封面的抽象线条、部分分类颜色、柱形图系列、强调块;可以用作部分颜色或主图表系列颜色,但其在内容页面上的区域应保持在白色和灰色背景下方。
|
||||
- **辅助蓝色:#3E58B5 / #7B8DC7**。用于辅助图表系列、堆叠柱、标签和浅色调类别;不得与紫色争夺冠军角色。
|
||||
- **青色/蓝绿色:大约#00A6B2–#20B8C6**。用于业务类别、流程阶段、图表系列以及部分模块彩条;可以用品牌辅助色代替,但必须与紫色和深蓝色区分开来。
|
||||
- **浅灰色背景/表格填充:#F2F2F3、#F6F7F7、#E8EAEB**。用于表格着色、模块背景、图表绘图区域和灯光分区;可能会占据大面积。不能用高饱和度的颜色代替。
|
||||
- **分隔线/网格灰色:#C8CACE、#D0D4D7、#BBBBBB**。用于表格线、轴、模块边框和分隔线;应该是细的、轻的、低存在感的——避免粗黑线。
|
||||
- **强调/风险红色:大约#B00020–#C00000**。用于封面保密说明、负面警报、敏感假设和红框注释;仅小面积,不是常规系列颜色。
|
||||
|
||||
将单个页面的主色调保持为紫色+蓝色/青色+灰色,红色仅用于警告。图表系列颜色在同一页面上保持一致的字典;避免使用相同的颜色来表示不同的企业或州。
|
||||
|
||||
## 6. 字体和文本层次结构
|
||||
|
||||
字体以 Calibri Light、Calibri 和 Calibri Italic/Bold 为主。浅色用于封面和大标题,常规用于正文和表格,粗体用于关键词和表格标题,斜体用于结论行和免责声明。不要引入衬线字体、手写字体或个性很强的中文字体。
|
||||
|
||||
正文 = 1x:覆盖主标题约。 3–4 倍;内容页面主标题大约。 2–2.8 倍;页面信息行约1.1–1.3x,大部分是斜体;模块标题大约1–1.2x,通常设置在颜色条内; 1x 身体子弹;表格内容和图表标签大约。 0.75–0.9x;注释、来源和页码大约。 0.45–0.65 倍。免责声明页面正文文本可能低于正常正文大小,但必须保持可读。
|
||||
|
||||
文本主要是左对齐的。表中的数字右对齐或按小数点对齐;年份、类别和指标名称可以居中或左对齐。大段居中的文案不属于这种风格;除盖部分外,不使用它。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
真实图表主要有堆积柱形图、分组柱形图、折线图、箱线图/分布图、散点图/气泡图、桥图、面积/成分图和基准比较图。图表嵌入模块框架中,上方有短标题或紫色标题栏,底部注明数据源和测量基础。轴线和网格线使用浅灰色细线;避免使用粗黑轴。图例位于图表的上方、下方或右侧,使用带有系列色样的小字体文本。柱形图系列以深蓝色、浅蓝色、青色、灰色、紫色为主;堆叠顺序保持稳定。折线图可以并行放置多条线,但颜色、线型和结束标签应该会降低阅读成本。
|
||||
|
||||
关键数据使用红色笔划框、红色注释框、箭头、小标签或深色数据标签。预测、目标和基线使用虚线、浅色线或单独的标签,不得与实际数据混淆。图表美化不能牺牲数字清晰度:避免3D、渐变柱、阴影、夸张的圆角和无意义的图标。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
标题使用深紫色背景搭配白色文字或浅灰色背景搭配深色文字;第一级分组使用紫色水平条,第二级分组使用浅灰色填充。行列分隔板又薄又轻;表格行高紧凑,但文本不得重叠。
|
||||
|
||||
数字列右对齐或按小数位对齐;文本列左对齐;年份、阶段和评级符号可以居中。重要的列、摘要行、关键假设或输出结果可以用浅灰色填充、紫色文本、粗体或边框来强调。负值、风险项或需要注意的差异可以使用红色,但不能全表大面积变红。
|
||||
|
||||
斑马条纹不是强制性的;对于许多行,可以使用极浅灰色的交替背景。矩阵式页面可以使用点、复选标记、评级符号、色块和短标签,但每个符号必须有图例或直观易懂。表格担负着“证据和模型”的作用,切不可作为纯粹的布局装饰。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
- **标题栏/章节标签**:左上角的小矩形标签,通常带有数字、章节名称和紫色/蓝色/青色边框或填充。其功能是定位甲板的结构;不要在页面之间随意移动它。
|
||||
- **模块标题栏**:深紫色或剖面色水平栏,带有白色或深色文本;紧凑的条形高度、宽度与模块匹配。
|
||||
- **卡片/盒装模块**:白色或浅灰色背景,带有细灰色边框,用于图表、说明、地图和流程节点。四舍五入难以察觉或不存在;没有阴影来营造卡片的感觉。
|
||||
- **注释/标志框**:红色或紫色细边框,带有解释异常、假设或风险的短句。只针对本地高价值信息;不要框定每一个结论。
|
||||
- **流向箭头**:连接矩形节点的细线箭头,表示真实的顺序、因果关系、资金流向或组织关系;颜色遵循部分颜色或灰色。避免使用粗的装饰箭头。
|
||||
- **图像框架和名称网格**:图像需要边界、标题和说明文字;客户、合作伙伴或类似公司的名称被排列为纯文本小型网格(或用中性占位符块替换)作为证据 - 没有绘制图形徽标。禁止使用彩色图标作为装饰性卖点。
|
||||
- **分隔线**:表格的浅灰色细线、脚注的上边缘和模块分隔。紫色粗线仅用于表标题或模块标题。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
整体扁平、内敛、制度化、高密度;几乎不依赖照片、插图、纹理、灯光效果或尺寸效果。品牌感觉来自紫色结构条、蓝色封面线条艺术、字体和脚注系统。禁止:大面积图像背景、3D图表、发光、复杂渐变、重阴影、表情符号、卡通插图、装饰性彩色图标、大片居中副本、无来源的数据图表、低对比度浅色文本以及与信息无关的背景纹理。
|
||||
|
||||
谨慎使用:照片、屏幕截图、地图、肖像、客户姓名文本、红色注释、圆形卡片。它们必须作为证据或解释工具出现,并控制面积和数量。
|
||||
|
||||
允许特殊页面:封面可以使用大面积的蓝色抽象线条和更多的留白;免责声明可能使用极高密度的单栏法律文本;附录/模型页面可以使用更密集的表格,但仍必须保留标题、来源和页码。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 是否为 16:9、白色背景、窄边距、高内容利用率、保留顶部标题区域和底部脚注/页码区域?
|
||||
- 标题是一个分析性主题,下面有一个清晰的斜体结论行或一页论文吗?
|
||||
- 紫色是否仅用于标题、部分、标题和结构重点,而蓝色/青色/灰色则按类别或数据系列稳定使用?
|
||||
- 页面的要点是否围绕一个主要问题,模块之间有清晰的分组逻辑吗?
|
||||
- 图表是否有标题、图例、轴/标签、来源和测量基础,没有 3D、渐变、阴影或其他装饰?
|
||||
- 表格的行列是否对齐,标题是否清晰,分隔线是否清晰,并且对关键行和列的强调有所限制?
|
||||
- 组件是否具有信息功能:用于定位的标签、用于分组的彩条、用于聚合的框架、用于流向的箭头、用于风险警报的红框?
|
||||
- 是否避免使用照片背景、表情符号、说明性图标、复杂纹理、眩光和无意义的大色块?
|
||||
- 您是否检查过溢出、截断、乱码、重叠、低对比度、遮挡、空模块、页码缺失、比例错误和来源缺失?
|
||||
- 模板变体是否服务于信息结构而不是仅仅使页面看起来不同?
|
||||
- 没有默认使用卡片:除非用户明确要求,否则严格禁止使用圆角矩形或矩形卡片来构建层次结构或对齐方式 - 线段、空白以及字体和字体大小差异是更好的解决方案。
|
||||
- 无等分组合:除非没有其他布局可用,否则不要默认三向分割、四向分割或2×2矩阵——包括三向分割+标题+结论等三部分结构。
|
||||
|
Before Width: | Height: | Size: 1.2 MiB |
@@ -1,171 +0,0 @@
|
||||
# 组织名称 — 报告名称 2025 风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
社论年度研究出版物PPT风格:温暖的米白色纸张、超重的黑色观点标题、部分彩色荧光突出、密集的数据图表和稳定的页脚。适合行业年度报告、投资研究、技术趋势、市场格局和政策/生态系统分析。每一页都先给出判断,然后给出证据。
|
||||
|
||||
固定特性:16:9风景画布;机身多采用类似#F2EFE6的暖白色背景;左上角有一个小部分标签;顶部有强烈观点的标题;由部分颜色矩形支持的关键字;正文使用图表、表格、时间线、卡片或案例网格;底部带有固定的品牌文字标记、来源和版权。部分分隔线使用带有像素块的深色/高饱和度背景,而内容页面则保持克制。
|
||||
|
||||
1. **观点在前,证据在后。** 每一页都必须有一个独立的判断标题;图表/表格/模块的存在只是为了证明判断;没有证据就没有口号页。
|
||||
2. **高密度来自结构,而不是堆积的文字。** 使用栏、表格、卡片、图例、注释和脚注来分层信息,清晰地分隔标题区域、正文区域和页脚区域。
|
||||
3. **颜色承载信息作用。** 暖白、黑、灰处理阅读;部分颜色处理识别和强调;图表系列颜色区分数据。品牌颜色不得牺牲轴、标签和正文的可读性。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
**画布和边距**:标准宽屏 16:9。内容页使用稳定、相当窄的左、右、上安全边距,底部保留页脚带;正文水平展开,避免居中的窄栏长文本。
|
||||
|
||||
**默认内容页面骨架**:
|
||||
- 左上角的小部分标签:小色块/短条或小标题+大写部分名称。
|
||||
- 左上角标题区域约占页面高度的15%–25%;超重的黑色判断语句可以换行,关键词可以用半透明截面颜色矩形突出显示。
|
||||
- 标题下方是正文区域,包含图表、表格、矩阵、左右栏或案例模块。
|
||||
- 页脚横跨底部:品牌文字标记位于左下角,来源/来源位于中左下角,版权位于右下角。
|
||||
- 阅读路径:左上章节标签→大标题判断→高亮关键词→正文证据→图例/注释→来源和版权。
|
||||
|
||||
**封面**:暖白色背景,中间有水平绿色像素块;超大的黑色标题堆叠并居中,带有斜体衬线“of”;年份在下方居中。可以覆盖一些浅蓝色/紫色半透明方块。没有照片,没有纹理,没有复杂的背景。
|
||||
|
||||
**披露/声明页**:全版高饱和度绿色背景;左上角有一个小期刊名称和一个超大的黑色标题;多个小字长文本段落覆盖中下区域。仅用于合法披露——请勿迁移到普通内容页面。
|
||||
|
||||
**目录页**:暖白色背景;左侧有一个大的内容标题和绿色像素块;右侧包含由数字、部分名称、细规则和末尾的小部分颜色块组成的部分列表。没有缩略图样式的目录。
|
||||
|
||||
**部分分隔线**:深蓝色、紫色、绿色等纯色背景;左边或中间有章节编号和大标题;像素块可能位于右上角或角落;没有复杂的数据。
|
||||
|
||||
**水印/背景图形**:除了封面、目录和部分分隔符上的像素块外,内容页面不使用水印或大背景图形。
|
||||
|
||||
## 3. 内容组织**标题写作**:使用完整的观点句子,而不是名词标签,例如“某种趋势正在增长/趋同/占主导地位”。保持两到三行,仅突出显示一个关键短语或单词,并避免给整个句子着色。
|
||||
|
||||
**信息行和正文**:使用短段落或小注释,放置在图表或内部卡片的旁边或下方;它们说明了测量基础、样本范围、例外情况或结论的含义。避免大范围的居中副本。
|
||||
|
||||
**分组逻辑**:趋势图解释随时间的变化,堆积图解释组成,表格解释比较,卡片解释案例,时间线解释阶段。多模块页面使用等宽列、等高卡片或统一表格框架。
|
||||
|
||||
**每页点数**:每页1个主要判断点,2-4个从属支撑点;不要同时提出多个不相关的结论。
|
||||
|
||||
**正文密度**:允许高密度,但文本块必须短,并且必须保留图表标签和来源。除了一张主图表之外,更喜欢左右栏或两个堆叠的证据区域。
|
||||
|
||||
**项目符号书写**:谨慎使用传统的点列表;喜欢图表旁注、卡片标题、表格列标题、图例和标签。使用粗体、色块背景或局部彩色文本来强调关键字——没有下划线或花哨的标记。
|
||||
|
||||
**数字/单位/时间基准**:数字接近图表或卡片;单位、时间范围和估计基础都包含在副标题、轴或脚注中。估计范围可以使用高可见度的彩色文本或引导线。
|
||||
|
||||
**来源脚注**:每个数据页底部必须有来源/来源;本地图表注释无法替代页脚源。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
**A。单张大图+右侧说明栏**
|
||||
左边/中间有一个大的折线图、面积图、堆积面积图或柱形图;右侧窄栏包含大胆的结论、关键数字或图例解释。使用标题突出显示、图表内注释点、结束标签或右侧的彩色说明框进行强调。不适合比较具有不同测量基础的多个图表。
|
||||
|
||||
**B。热门观点+全角图表**
|
||||
标题位于顶部,图表横跨整个正文宽度,具有清晰的轴和网格线。适合时间序列、市场份额和长期变化;不要在图表上覆盖太多卡片。
|
||||
|
||||
**C。左文本右图表/左图表右文本两列**
|
||||
用于“判断+证据+解释”。文本栏包含短段落、关键数字和源补充;图表栏是主要证据,比率可能有利于图表。正文栏不能变成一篇长文章——最多是几个要点。
|
||||
|
||||
**D。高密度并排模块/卡网格**
|
||||
用于案例、公司、流程链或链接、产品或指标比较。卡片大小统一,用浅色填充或细线分隔;标题为粗体黑色,内部包含小图像、数字或简短注释;部分颜色小标签或卡片背景颜色可以提供强调。如果每张卡片都需要很长的解释,请拆分页面。
|
||||
|
||||
**E。表格/矩阵式页面**
|
||||
用于多实体、多指标比较。清晰的标题、细线和浅色填充区分行和列;关键列可以使用部分颜色、深色填充或图标点。表格是证据的主角,不会与大图表竞争同一页。
|
||||
|
||||
**F。半圆/仪表式估算图**
|
||||
用于表达范围估计、层次关系以及从大量群体到较小活跃群体的嵌套。必须与左侧的数字标签和引导线配对;不适合精确的趋势比较。**G。图表 + 案例备注卡**
|
||||
将特定项目/案例放在趋势图旁边。案例卡具有浅色或白色背景,带有略圆/近矩形的角,包含项目文本名称/产品图像、一个短段落和 2-3 个关键指标。案例卡是辅助证据,不得掩盖主图表。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
**背景 — 温暖的米白色#F2EFE6 / #F5F2E9**:用于页面基础、浅表填充和图表背景。除非源图像/嵌入图表需要,否则不能用纯白色或冷灰色替换。
|
||||
|
||||
**主要文本 — 近乎黑色 #111111 / #1A1A1A**:用于标题、正文、关键轴文本和页脚品牌字标。主标题不得替换为浅灰色。
|
||||
|
||||
**辅助文本/线条 — 灰色 #83817D / #9B9D9D / #C5C0BA**:用于脚注、来源、网格线、表格分隔符和辅助标签;不包含主要结论。
|
||||
|
||||
**品牌/部分绿色 — #52DA92 / #79D497**:用于封面像素块、披露页面背景、目录像素块、部分颜色和标题关键字突出显示。大面积使用仅限于封面、目录、披露页或章节页;在内容页面上,它仅用作小区域突出显示、标签和图表系列。
|
||||
|
||||
**深蓝色/部分暗色调 - 大约#342B56 / #39374F / #0B2D3A**:用于部分分隔符背景、深色图表系列和强调块。深色背景使用白色或亮色文本;在内容页面上谨慎使用大面积区域。
|
||||
|
||||
**蓝色系列 — #2351EC / #4168C5 / #A4C9FE**:用于图表系列、范围填充、部分颜色和强调标签。浅蓝色可以填充较大的图形区域;深蓝色用于线条和关键系列。
|
||||
|
||||
**橙色系列 — #F6A031 / #F6931C / #F9961F**:用于重要的图表系列、标签、正增长或一个资产/类别标识符。不应在一页上与太多其他高饱和度颜色一起出现。
|
||||
|
||||
**紫色/洋红色系列 — 大约 #DB76ED / #9D8C7F / #7B3FF2**:用于分区分隔线、图表系列和标签卡;与橙色/蓝色形成鲜明对比,在内容页面上控制区域。
|
||||
|
||||
**绿色图表系列 — 大约 #70AF87 / #2FBF72**:用于面积图、增长趋势和状态强调。品牌绿色用于部分/突出显示;绿色数据代表系列。
|
||||
|
||||
**每页颜色边界**:内容页面仅限于 1 个部分颜色 + 2-4 个数据系列颜色。高饱和度的颜色只是为了区分数据,而不是随意的装饰。没有强烈的正/负状态颜色系统;在表达涨跌时,更喜欢图例文字解释,避免采用红绿金融惯例。
|
||||
|
||||
## 6. 字体和文本层次结构
|
||||
|
||||
强大的无衬线出版系统:标题极其粗体、紧凑、黑色;正文和脚注采用清晰的无衬线字体;封面上的“of”为斜体衬线。
|
||||
|
||||
- **主标题**:大约。 2.5–4x 正文,超重,紧行距,主要左对齐;盖子可能会更大。
|
||||
- **部分分隔符标题/编号**:可能超过正文文本的 4 倍,粗体黑色或白色。
|
||||
- **小部分标签**:约。 0.7–0.9x 正文,全部大写或短语,通常带有小色块。
|
||||
- **正文解释**:正文 = 1x,黑色或深灰色短段落,行间距宽松;避免长而密集的列。
|
||||
- **图表标题/副标题**:大约。 0.9–1.2x 正文文本,粗体或半粗体,放置在图表上方。
|
||||
- **表格文本/图例标签**:大约。 0.75–0.95x 正文;数字可以是半粗体。
|
||||
- **注释/来源/页脚**:大约。 0.55–0.7x 正文,灰色或低粗黑色;必须保持可读性。
|
||||
- **页码**:不是显眼的导航设备;如果添加,它们应该被抑制并折叠到页脚中——它们不能成为视觉焦点。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
主要图表类型:堆积面积图、折线图、柱形图、组合柱线图、半圆形嵌套/仪表式估计图、小时间序列、比较图。**轴和网格**:细的灰色轴;浅色、规则的网格线;暖白色或浅色背景。轴标签小而清晰;时间轴通常水平延伸。
|
||||
|
||||
**图例和标签**:图例位于图表的内边缘或图表旁边,使用色块/线条样本+类别名称。关键数据直接标注在图表上或右侧说明栏中。
|
||||
|
||||
**系列颜色**:采用橙色、蓝色、紫色、绿色、浅蓝色等高辨识度颜色。堆积面积图必须保持边界清晰;折线图中的强调线可能更粗或更饱和。
|
||||
|
||||
**强调注释**:使用部分颜色突出显示、水平基线、解释框、关键数字标签或本地彩色卡;注释靠近相关数据点,不会作为装饰而浮动。
|
||||
|
||||
**预测/目标/基线**:新预测使用虚线/浅色阴影范围,并在图例中进行解释;不要将它们与坚实的历史线索混淆。
|
||||
|
||||
**边界**:没有 3D 图表、透视图、发光图表或过度拟物化的仪表。轴和源不得省略。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
表格用于专业证据和多实体比较。标题使用粗体文本、浅色填充或顶部分隔线;行和列由细灰色线划分;填充物有暖白色、浅米色或浅灰色;关键单元格可以使用部分颜色或深色块。
|
||||
|
||||
**行列规则**:行高紧凑,不拥挤;长文本左对齐,数字右对齐或按小数点/单位视觉对齐;栏目标题保持简短;多列指标保持统一的单位和时间基础。
|
||||
|
||||
**强调处理**:使用突出显示的列、彩色标签、粗体数字、深色摘要行或小点标记。突出显示仅用于关键比较——不要对整个表格进行模式化。
|
||||
|
||||
**斑马条纹和分隔**:更喜欢细线和浅色填充分区;如果使用斑马条纹,它必须很轻。避免厚重的边框。
|
||||
|
||||
**高密度模块**:并排卡片、公制块、格子和表格必须具有统一的尺寸、标题级别和间距;每个模块仅包含必要的数字、注释和图例。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
**像素块**:由阶梯式矩形块组成,通常用于封面、目录和部分分隔符,很少用于导航。大面积使用仅限于结构页面;在内容页面上,不要重复铺上像素块作为背景装饰。
|
||||
|
||||
**章节标签**:一个小色块+大写的章节名称,固定在左上角,用于章节定位。
|
||||
|
||||
**标题高亮条**:位于关键字后面的半透明或实心矩形,具有像荧光笔一样的直边。仅突出显示关键字或短语;切勿突出显示整个正文段落。
|
||||
|
||||
**卡片/标注框**:用于案例、图例解释、关键数字和类别注释。矩形或略圆角;填充可以是橙色、紫色、深色或浅色,并带有高对比度文本;放置在相应图表附近。
|
||||
|
||||
**分隔线**:用于目录、表格、图表网格和模块边界的灰色细线。线条内敛、笔直;没有装饰曲线。
|
||||
|
||||
**图标/身份标记**:仅少量用作小类别图标;品牌/项目标识符以纯小文本形式呈现(写下新主题名称)——不绘制图形徽标。彩色图标不是一种全球语言;它们只能用于案例卡、图例或项目文本标识符。
|
||||
|
||||
**流程箭头/矩阵标记**:用于流程或阶段说明;保持它们平坦、细线、颜色少;不要构建复杂的流程图。
|
||||
|
||||
**图像框架**:放置在卡片内或图表旁边的少量产品/设备图像,仅辅助 - 绝不是整页背景。
|
||||
|
||||
## 10. 质地和违禁物品整体扁平、出版级、内敛、高密度、带有强烈的品牌色彩;识别来自于字体重量、温暖的米白色纸张、像素块和部分颜色,而不是来自照片跨页或维度纹理。
|
||||
|
||||
**禁止**:大面积摄影背景、复杂插图背景、表情符号、3D图表、发光效果、玻璃形态、强烈阴影、复杂纹理、花边装饰、随意渐变、大片居中副本、无源数据图表、低对比度浅色文本以及无标题判断的纯图像页面。
|
||||
|
||||
**谨慎使用**:彩色图标、产品屏幕截图、圆形卡片、整页深色背景、渐变填充、高度饱和的多色图表。它们必须提供信息功能并仅限于案例/图表/章节页面范围。
|
||||
|
||||
**允许特殊页面**:整版高饱和度绿色用于披露;用于部分分隔符的整页深蓝色/紫色/绿色背景;封面和目录上的大面积像素块;案例页面上有少量物理/产品图像。普通内容页面仍然以暖白色背景和数据证据为中心。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 骨架:是16:9吗?它是否保留左上角部分标签、顶部意见标题、正文证据区域和底部品牌字标/来源/版权?
|
||||
- 文案:标题是否为完整的判断语句;每页有一个要点吗?关键词高亮是否受到限制?
|
||||
- 层次结构:标题、图表标题、正文、注释和页脚是否有明显的相对大小差异;页码不用作主要导航。
|
||||
- 颜色:背景是暖米白色;部分颜色仅用于识别/强调;一页上是否有太多高饱和度颜色;文字对比度是否足够?
|
||||
- 布局:图表、表格和卡片是否对齐;利润率是否稳定;页脚没有被身体挤压吗?
|
||||
- 图表:是否有轴、图例、单位、时间范围和来源;重点注释是否接近数据;没有使用 3D/发光/无意义的装饰?
|
||||
- 表格:标题、分隔符、数字对齐和单位基数是否清晰;突出显示仅用于关键行和列吗?
|
||||
- 组件:是仅在封面/目录/部分分隔符上使用或用于稀疏导航的像素块;卡片是否起到解释作用?图标有实际意义吗?
|
||||
- 禁止使用的物品:禁止使用大图像背景、表情符号、复杂插图、重阴影、玻璃态、过度渐变或大段居中副本。
|
||||
- 质量:检查溢出、截断、乱码、重叠、低对比度、遮挡、空内容、错误比例和机械模板重复。
|
||||
- 没有默认使用卡片:除非用户明确要求,否则严格禁止使用圆角矩形或矩形卡片来构建层次结构或对齐方式 - 线段、空白以及字体和字体大小差异是更好的解决方案。
|
||||
- 无等分组合:除非没有其他布局可用,否则不要默认三向分割、四向分割或2×2矩阵——包括三向分割+标题+结论等三部分结构。
|
||||
|
Before Width: | Height: | Size: 780 KiB |
@@ -1,192 +0,0 @@
|
||||
#设计系统:消费趋势监测·景观呈现版
|
||||
|
||||
> 逆向工程来自:36页肖像(美国信件612×792)投资监测季刊的真实视觉证据(封面+所有35页的逐页图像)。
|
||||
> 该系统仅保留其**视觉方法** - 报纸风格的网格、与数据图表混合的衬线标题、青色/琥珀色/深红色图表语言和纸米色页脚 -
|
||||
> 不复制任何第三方徽标、品牌名称、商标或源文本;根据任务要求,将纵向报告语法转换为16:9横向呈现系统。
|
||||
> 任何无法从像素证据中精确确认的值都被标记为“近似值”。
|
||||
|
||||
---
|
||||
|
||||
## 1.视觉DNA(一句话)
|
||||
|
||||
**财经报纸式的数据监测报告**:内敛的黑白版式+大衬线标题+密集的柱线组合图表+深海蓝色封面米色页脚;整体气质是“值得信赖的行业仪表板”,而不是营销海报。
|
||||
|
||||
四项可执行原则:
|
||||
1. **结论用斜体衬线表达,证据由图表给出**——每个信息块都是“斜体衬线结论句+图表/正文”的配对。
|
||||
2. **图表是主角**:页面区域先到图表;文本是图表的注释。
|
||||
3. **颜色仅编码,从不装饰**:青色=主要系列,深青色=次要系列,琥珀色/黄色=比较线,深红色=警告或第二主题,米色仅出现在页脚和分隔带中。
|
||||
4. **密集但不拥挤**:小字体,多列,紧密的前导 - 但模块之间有清晰的空白。
|
||||
|
||||
---
|
||||
|
||||
## 2.画布和网格(16:9横向转换)
|
||||
|
||||
|项目 |价值|注意|
|
||||
|---|---|---|
|
||||
|帆布| 960 × 540 像素 | 16:9 演示标准 |
|
||||
|左/右页边距 | 44 像素 |源纵向边距 ≈ 页面宽度的 7%,引入横向(大约)|
|
||||
|内容区 | y = 20 ~ 508 | y = 20 ~ 508 |米色页脚带上方|
|
||||
|页脚带| y = 512 ~ 540 (28 高) |米色水平带,参见§9|
|
||||
|立柱系统| 12 列心理网格,通常分为 2 / 3 列;装订线 24 px |源码正文采用固定3窄列布局|
|
||||
|基线节律| 4 px 的倍数 |所有间距均为 4 的倍数(近似值)|
|
||||
|
||||
纵向→横向转换规则:
|
||||
- 来源“全页大图像 + 下面三栏正文” → 横向变为“左侧 60% 图表 + 右侧 40% 文本栏”或“图表在顶部,文本在下方,图表跨越整个宽度”。
|
||||
- 源 2×2 图表矩阵页面(垂直排列)→ 景观变成自然的 2×2 网格 — 天生适合。
|
||||
- 源码唯一的横向页面(区域地图+两侧堆叠条)证明本系统支持横向;将密度与该页面对齐。
|
||||
|
||||
---
|
||||
|
||||
## 3. 字体层次结构
|
||||
|
||||
中文字体受运行时字体库约束;映射如下(字体名称必须与库完全匹配):
|
||||
|
||||
|角色 |字体|尺寸/款式 |用途 |证据|
|
||||
|---|---|---|---|---|
|
||||
|显示衬线 | `{latin: Oranienbaum, ea: 思源宋体}`,粗体 |封面 40~48 / 页标题 26~28 |封面主标题、各页大标题|源标题是乔治亚型粗体衬线(近似映射)|
|
||||
|结论线/图表标题 |与上面相同,斜体+粗体| 14~15 |每页的结论线、每个图表的斜体标题 |源图表标题为斜体衬线(近似值)|
|
||||
|无衬线体 (Sans) | `MiSans` | 10~10.5,行高1.45~1.5 |多列正文、图例、轴标签 |源正文 ≈ 9px 无衬线三列(近似值)|
|
||||
|眉毛| `MiSans`,字母间距2~3 | 9~10,全部大写感觉被粗体取代了 |页面标题上方的列标识符 |来源“DEALS BY SECTOR”式小型眉毛|
|
||||
|数据标签| `MiSans` 粗体 | 9~10 |列内/行内的值 |来源列内白色粗体值 |
|
||||
|大数字(统计)|显示衬线粗体或 MiSans 粗体 | 28~36 |关键指标卡|源码封底和内页大数字处理(近似值)|
|
||||
|页脚/来源注释| `MiSans` | 8~9,灰色 |页脚带文本,图表下源注释 |来源页脚的小号大写灰色类型 |
|
||||
|
||||
规则:
|
||||
- 一页内,衬线仅用于“标题+结论线+图表标题”;正文和图表注释始终为无衬线。
|
||||
- 没有超出上述两个系列的字体;没有书法/手写的面孔。
|
||||
|
||||
---
|
||||
|
||||
## 4. 颜色标记(十六进制;都是像素采样近似值)
|
||||
|
||||
|代币|十六进制 |角色 |抽样依据|
|
||||
|---|---|---|---|
|
||||
| `ink` 深海蓝| `#06223F` |封面/章节页底、表格标题、最暗图表系列、强调文本 |封面底座#051C38,标题#2C4057,最暗的地图状态#001830 |
|
||||
| `ink-soft` | `#14324F` |深蓝色辅助(卡片、辅助标题)|近似|
|
||||
| `teal` | `#2FBFC4` |主系列列颜色、主线、强调图标 |主图表栏#30C0C0~#3CC0C0 |
|
||||
| `teal-dark` 深青色 | `#1B6E72` |二级系列立柱,第二堆叠段 |深青色柱#186C6C~#246C78 |
|
||||
| `teal-pale` 浅青色 | `#BFE5E6` |最轻堆叠段、浅面积图表层、图表背景块 |浅青色段#C0E4E4 |
|
||||
| `amber` | `#D9A62E` |比较线(柱线组合中的“线”)、增长率注释 |金线#CCA80C~#E4C09C |
|
||||
| `yellow`亮黄色| `#DCC83C` |单系列黄柱(行业页原色),堆叠段|黄柱#D6D470~#CCA80C |
|
||||
| `orange` | `#E5862E` |第三主题栏颜色、堆叠段|橙色柱#E48430 |
|
||||
| `crimson` | `#76101A` |警告/第二主题列颜色,深色堆叠段|深红柱#6C0000~#780C0C |
|
||||
| `salmon` | `#F0A89A` |中间堆叠部分(深红色和浅青色之间)|粉红色段样本#FEF1EE 增亮估计(近似值)|
|
||||
| `slate` 石板蓝| `#5E84A0` |地图/图表中间色调、辅助线 |中色调地图状态#60849C |
|
||||
| `paper`纸白| `#FFFFFF` |内容页库 | — |
|
||||
| `beige` | `#EDE7D6` |页脚带、水平分隔带、注释框填充 |页脚带#EFEBDE |
|
||||
| `beige-line` | `#D8D2C2` |米色带顶边,细桌线|带边缘#D2D1C9 |
|
||||
| `text` 机身墨水 | `#262626` |正文 |近似|
|
||||
| `muted` 灰色 | `#8A8A8A` |轴标签、图例、源注释、页脚文本 |近似|
|
||||
| `grid` 格灰| `#E7E7E7` |桌子外框,薄隔板 |近似|
|
||||
|
||||
对比地板:在 `ink` 上,仅白色/浅青色文字;深 `teal`/`crimson`/`orange` 色谱柱上的数据标签为白色或以深灰色放置在色谱柱上方;在米色带上,仅显示 `#6B6B6B` 深度或更暗的文本。
|
||||
|
||||
---
|
||||
|
||||
## 5. 页面骨架(内容页面通用)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ ▪ wordmark "Specialty Coffee Consumption Monitor" │ ← header row y≈20~44
|
||||
│ version at right: 2026 Annual Insights · sample setting │
|
||||
│ │
|
||||
│ Eyebrow (9px, wide letter spacing, gray) │ ← y≈56
|
||||
│ Serif display headline 26~28px │ ← y≈74~104
|
||||
│ Italic serif conclusion line 14px (the page's only conclusion) │ ← directly under the title
|
||||
│ │
|
||||
│ ┌────────────────── content area y≈130~486 ────────────────────────┐ │
|
||||
│ │ chart / multi-column body / table / cards (choose layout per §6) │ │
|
||||
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||
│ page number (centered, 9px serif gray) │ ← y≈496
|
||||
│ ▔▔▔▔▔▔▔▔▔▔ beige footer band: report name centered 9px ▔▔▔▔▔▔▔▔▔▔ │ ← y≈512~540
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- 标题左侧文字标记:实心青色小方块 (10×10) + 粗体无衬线报告名称;右侧是一个小的灰色版本标识符。 **禁止任何真实的品牌标志。**
|
||||
- 页码位于米色带上方的中心(源样式);页脚区域以小字体显示居中的完整报告名称。
|
||||
|
||||
---
|
||||
|
||||
## 6. 可重用布局 (8)
|
||||
|
||||
1. **封面(深海蓝色)**:全出血 `ink` 底座 + 右侧同心细弧/细线信号图案(低不透明度蓝灰色椭圆笔划,完全在画布内)+ 大衬线标题(可以混合斜体和罗马字)+ 一行版本信息 + 底部关键数字带(3 个统计数据,青色/白色文本)。
|
||||
2. **结论页(封面变体)**:与封面相同的骨架,但标题是页面的结论句,下面有3~4个数据统计带作为证据。
|
||||
3. **大图表+文字栏**:左边55~60%持有一张柱线组合图表(斜体图表标题+图例+来源注释);右 40% 拥有一栏正文 + 一张统计卡或小图表。
|
||||
4. **全角图表+三栏正文**:顶部为全角组合图表(占高度的45~50%),下方三栏10px正文文本,每栏可选择以粗体开头句开头。
|
||||
5. **2×2图表矩阵**:每个象限一个小图表,每个图表标题为“斜体衬线图表标题+小字体副标题+图表+源注释”,象限之间有24px空白。
|
||||
6. **横条+右侧统计轨迹**:左侧60%为横条排名图;右 40% 垂直堆叠 2~3 张统计卡(米色或浅青色填充、大衬线数字 + 小字体注释)。
|
||||
7. **比较配对栏+解释栏**:中间偏左的比较图,有2~4个大栏(在栏上方和栏外标记值),右侧1个解释文本栏+1个“x倍差”箭头注释。
|
||||
8. **表格页/卡片页**:一个深青色标题白色文本表格(参见§7)或2~3张等宽操作卡(卡片=浅青色顶栏+衬线字幕+无衬线正文+底部数据锚芯片)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 图表和表格语言
|
||||
|
||||
### 一般图表
|
||||
- 图表标题:斜体衬线粗体 14px `ink`,左对齐;下面的行是一个 9 像素的灰色字幕(公制)。
|
||||
- 轴:没有 y 轴网格线 (`gridLine: false`)、没有轴线或只有一条细 `#E7E7E7` 基线;轴标签 9px `muted`;货币轴带有单位缩写。
|
||||
- 图例:图表下方水平,小色样+ 9px 灰色文本。
|
||||
- 来源说明:图表右下角8px灰色文字——“中国精品咖啡消费趋势监测·样本数据(内部估算)”。
|
||||
- 值标签:9~10px;列系列标签最好在列内使用白色粗体,或者在不适合时在列上方使用深灰色(如果渲染器不支持位置控制,请将它们均匀地放置在上方和外部,视为源的近似值)。
|
||||
- 线系列标签位于端点上方,呈 `muted` 灰色。
|
||||
|
||||
### 图表型食谱(按证据频率排序)
|
||||
1. **柱线组合(本系统的标志图)**:实心柱(`teal` / `crimson` / `orange` / `yellow`之一)+对比细线(`amber`或`teal`),双轴;标有金额的列,标有计数/比率的行。
|
||||
2. **多线**:4~5条1.5~2px的细线,颜色顺序`ink`→`teal`→`amber`→`crimson`→`slate`,端点值直接标记在右端。
|
||||
3. **100% 堆叠条**:从 `teal`/`teal-pale`/`salmon`/`crimson` 或 `teal`/`teal-dark`/`ink` 的每个主题分段颜色,分段内带有白色 % 文本。
|
||||
4. **水平条排名**:单色系列(`teal` 或 `ink`),值标记在条末端,类别轴位于左侧。
|
||||
5. **分层区域**:`ink`→`teal-dark`→`teal`→`yellow`自下而上,仅用于“库存积累”语义。
|
||||
6. **比较配对列**:深色和浅色的两个同族列,它们之间有“x 倍差异”箭头和文本。
|
||||
|
||||
### 表格
|
||||
- 标题:`ink` 背景,白色粗体 9~10px 文本,左对齐(数字列右对齐)。
|
||||
- 数据行:白色背景,行间只有 `#E7E7E7` 0.75~1px 基线,没有垂直线,没有斑马条纹(源表没有斑马条纹)。
|
||||
- 行高 22~26px,单元格内边距左右各 8px。
|
||||
- 表格下面还有源注释。
|
||||
|
||||
---
|
||||
|
||||
## 8. 图像裁剪和图形组件
|
||||
|
||||
- **图像**:只有封面/章节页面可以使用全帧图像,始终为 `fit: cover` 全出血裁剪;风景中的人像图像仅取中间波段(`crop: {top, bottom}` 各修剪 15~25%);内容页面不使用照片,而是使用图表和图形组件。源肖像为右栏小方块(约120×150)+灰文简介;仅在真正需要时才使用相同规格的占位符。
|
||||
- **信号图案(封面装饰)**:3~4个同心椭圆细笔画(`slate`/`teal`,不透明度0.15~0.3),所有边界在画布内;或一条 2px 青色“脉冲”折线。
|
||||
- **统计卡**:`teal-pale` 或 `beige` 填充(无描边,无阴影),大衬线 `ink` 数字 + 9px 灰色注释;在深蓝色页面上切换为透明填充+青色大数字。
|
||||
- **动作卡**:白色底座 + 4px `teal` 实心顶栏 + 衬线字幕 + 10px 正文 + 底部数据芯片(`beige` 填充圆形小字体)。
|
||||
- **图标**:仅限 Font Awesome 免费实心 (`fas:`),单色 `teal` 或 `ink`,16~20px,无混合样式。
|
||||
- **分隔**:始终为 1 像素 `#E7E7E7` 细线或空白;无阴影,无渐变块(覆盖信号图案除外)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 密度、间距和页脚
|
||||
|
||||
- 信息密度与来源一致:1个结论+1~2个图表+每页不超过3栏正文;正文10px,行高1.45,段落间距6~8px。
|
||||
- 模块间距:标题区域→内容区域20~24px;图表矩阵象限相距 24px;卡片间隔 16~20px。
|
||||
- 页脚:米色带(`beige`,28px高)横跨整个宽度,居中9px `muted`报告名称“中国精品咖啡消费趋势监测·2026”;页码为 9 像素衬线灰色,中心位于条带上方 6 像素处。
|
||||
- 每页右下角的图表注释均使用统一的 §7 源注释措辞。
|
||||
|
||||
---
|
||||
|
||||
## 10. 禁止事项
|
||||
|
||||
1. 不含第三方徽标、品牌名称、赞助商标志、商标或源手稿原文。
|
||||
2. 份额排名没有饼图/圆环图(来源根本没有饼图);股票始终使用 100% 堆叠条形图或水平条形图。
|
||||
3. 没有阴影、渐变填充块、磨砂玻璃、3D 图表或动画风格的图表装饰。
|
||||
4. 没有圆角半径大于8px的卡片;图表列保持直角。
|
||||
5. 文本不得压在图像上,图表元素不得渗出画布(所有边界必须保持在 960×540 以内)。
|
||||
6、米色页脚带以外的正文区域不得大面积米色填充;米色只是一个“带”,而不是一个“块”。
|
||||
7. 禁止捏造数字:所有值必须来自任务查询;定性判断必须标注“内部判断”。
|
||||
---
|
||||
|
||||
## 11. 交付前检查表
|
||||
|
||||
- [ ] 每一页都有一个且仅有一个明确的结论,以斜体衬线句子的形式直接位于标题下方。
|
||||
- [ ] 6页构成一个完整的叙述:结论→市场结构→消费者细分→渠道和会员经济学→机会图→行动建议。
|
||||
- [ ] 每个号码都可以一一追踪到query.md;没有捏造的东西,没有外部数据。
|
||||
- [ ] 所有颜色均来自 §4 标记;图表着色遵循 §7 配方;没有渐变/阴影。
|
||||
- [ ] 只有两种字体系列:serif `{Oranienbaum, 思源宋体}` 和 sans-serif `MiSans`;尺寸遵循§3。
|
||||
- [ ] 页眉文字标记、米色页脚带 + 页码和图表下源注释出现在每个页面上的一致位置。
|
||||
- [ ] `kimi-ppt check` 报告没有错误;逐页屏幕截图显示没有重叠、没有裁剪、没有溢出、没有意外换行;深色背景白色文本对比通过。
|
||||
- [ ] 最终目录:`deck.pptx`(唯一的PPTX)+完整的PPTD捆绑包+`final-screenshots/pages/1.jpg~6.jpg`。
|
||||
|
||||
- 没有默认使用卡片:除非用户明确要求,否则严格禁止使用圆角矩形或矩形卡片来构建层次结构或对齐方式 - 线段、空白以及字体和字体大小差异是更好的解决方案。
|
||||
- 无等分组合:除非没有其他布局可用,否则不要默认三向分割、四向分割或2×2矩阵——包括三向分割+标题+结论等三部分结构。
|
||||
|
Before Width: | Height: | Size: 1.0 MiB |
@@ -1,153 +0,0 @@
|
||||
# 组织名称 — 2025 年第 2 季度加密货币行业报告风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
一个黑暗的、数据驱动的季度加密行业研究报告PPT设计系统,面向精通跨市场、资产、交易量、ETF、DeFi、NFT和交易所指标的专业读者,支持高密度数据、较长的解释文本和多系列图表。车身采用深色研究型仪表板,搭配组织名称的绿色视觉标识;封面、分区和产品/结束页使用高饱和度的绿色或紫色框架、大圆形卡片和吉祥物插图。
|
||||
|
||||
整个甲板是16:9的风景画布;内容页面使用近乎黑色的背景,带有白色现代无衬线粗体标题、正文和脚注,采用 Geist/Inter-type 无衬线;标题带有固定的小品牌文字标记;页脚包含报告名称、版权、来源和页码。主要内容包括折线图、柱状图、堆积柱状图、面积图、时间线、表格、文本列表/头像矩阵;强调色有荧光绿、紫色、粉色、黄色等高饱和度颜色。
|
||||
|
||||
基本原则:
|
||||
|
||||
1. **数据重于装饰。** 页面优先考虑图表、表格、数字和解释性文本;插图、边框、亮色和身份标记仅用于品牌识别、分类、强调或部分过渡。
|
||||
2. **身体骨架一致,胜过布局炫耀。** 普通数据页保留黑色背景,左上角品牌标记,白色标题,右侧或上方洞察线,底部出处和页码;大面积绿色/紫色品牌页面仅出现在封面、部分分隔线、产品或结束位置。
|
||||
3. **强调色必须带有语义。** 荧光绿用于品牌、积极/增长价值观、关键系列或按钮状组件;紫色/粉色表示洞察线、部分过渡和特殊注释;红色仅适用于负面变化或风险类型数据。如果明亮的颜色妨碍读取值,请优先考虑轴、标签、单位和图例的清晰度。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
画布为 16:9 横向宽屏。内容页面背景为纯黑色或接近黑色;封面/章节/结束页使用亮绿色的外部背景,或带有深色圆形内卡的紫色外框。保持稳定的边距,外缘无关键数据;高密度页面可能接近全出血,但页眉、标题和页脚必须保留其安全区域。
|
||||
|
||||
**封面**:亮绿色外底,中央有一张大的深绿色圆形卡片,顶部有缺口式分体或双卡结构。左上角是身份标记——带有新主题名称的纯小文本,或者留空;没有绘制图形标志。主标题巨大,年份或关键词可能有深绿色/浅绿色渐变的感觉,报告名称为白色/浅绿色;中下部分为组织名称吉祥物插图、细线趋势线和半色调点;赞助商文字列表位于右侧或右下,面积小于主标题和吉祥物。
|
||||
|
||||
**目录页**:黑色背景,左上角有一个大的“内容”样式标题;正文是一个多列内容列表,一级部分标题较大且白色,条目采用较小的白色/灰色文本,页码右对齐;作者头像、姓名和头衔可能位于下方。内容页面没有使用大图表和很少的装饰。
|
||||
|
||||
**部分分隔线**:亮绿色或紫色的外框/背景,内部有一张大的深绿色或深蓝黑色圆形卡片。左边有部分名称和一个小标签;右侧或底部有大型组织名称吉祥物和主题图形。章节标题占主导地位,信息很少。**内容数据页**:左上角有一个固定的小品牌文字标记,其下方或右侧有白色页面标题;标题区域在顶部占据了一个紧凑的水平带。洞察线位于标题右侧、页面右上角或说明栏顶部,通常以紫色开头,并以灰白色文本继续。正文可以使用单个大图表、左右栏、图表+说明栏、时间线、表格或矩阵。左下角包含报告名称/版权,来源位于图表下方或左下角,页码位于右下角。阅读路径:品牌和标题→紫色洞察线/关键信息线→主图表或表格→解释性文字/图例→来源和页码。
|
||||
|
||||
分隔线仅用于图表轴、表格线、卡片边框和时间线规则 - 不设置繁重的标题规则。内容页面不使用水印或大背景图形;背景保持干净。
|
||||
|
||||
## 3. 内容组织
|
||||
|
||||
标题为对象型或指标型,例如“某种资产的价格和交易量”、“某种市场份额”、“值得注意的事件”或“交易所交易量”,负责主题定位;结论由洞察线表达。标题应为 1 行,必要时为 2 行;避免超过两条线,这会压缩图表区域。
|
||||
|
||||
每页优先显示一条信息行,位于标题右侧或解释栏顶部,结构为“关键变化/结论+数据定义和测量依据/时间范围+原因或影响”。使用紫色突出显示开头短语或核心编号,其余部分为灰白色。不要将多个结论堆放在一个段落中;每一页都优先考虑一个主要判断。
|
||||
|
||||
正文使用短段落和项目符号,通常位于图表左侧或右侧的窄栏中;每一段都解释一个原因、数据发现或影响。保持段落断断续续,行长适中;左右布局中的文本栏是为了解释图表而存在的,它本身不会成为一篇长文章。
|
||||
|
||||
按市场类别、资产类别、时间、平台、链、交易所或指标定义和基础进行分组。高密度页面使用字幕、标签、图例、彩色系列和细线分隔。一页包含一个核心问题;它可以包含多个数据系列,但不得同时讨论多个不相关的主题。
|
||||
|
||||
数字和单位靠近数据标签或轴,保留相关格式和单位——百分比、美元、倍数、日期和季度。时间轴保持连续且可读;大数字可能会在洞察线或图表注释中突出显示。源脚注位于图表下方或页脚中,采用小灰白色文本,不与主要数据竞争。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
**A。左边说明栏+右边大图表**
|
||||
适合价格、交易量、指数、资金流向等单一主题时间序列。左栏包含标题、见解、2-4 个解释段落和来源;右侧的大图表占据了正文的大部分宽度,包含轴、图例和数据注释。使用亮线、峰值标签、垂直注释线或关键点文本进行强调。不适合需要广泛分类比较的表格类型内容。
|
||||
|
||||
**B。顶部标题和见解 + 下面的全角图表**
|
||||
适合水平时间轴图表,例如多系列线、堆积柱形图和市场份额变化。一个紧凑的标题和一条洞察线位于顶部;主图表横跨整个页面;图例位于图表顶部或右侧附近,系列颜色保持高对比度。如果解释太多,请切换到左右栏,以避免压缩图表高度。**C。大图表+右侧注释/赞助商/备注栏**
|
||||
适合需要补充事件背景的页面,如IPO、ETF、交易量等。主图表位于左侧或中间;右边的窄栏包含简短的注释、文字标记或辅助图例;右栏的宽度不得超过主图表。此布局不适合多个长段落或多个不相关的文字标记。
|
||||
|
||||
**D。时间线/事件带布局**
|
||||
适合“重大事件”类型的内容。使用多行水平日期栏;日期块用高饱和度的绿色、粉色、紫色等区分时期或类别,下方有小尺寸的事件注释;标题区域很小。不要添加复杂的图表;事件文案一定要短——长的解释会打破时间线的节奏。
|
||||
|
||||
**E。表格/排名/矩阵页面**
|
||||
适合资产回报、稳定币、交易所、链生态系统以及团队或平台比较。正文使用暗表、排名栏、文本列表或多列卡片;注释栏可能位于左侧,彩色条、点、图标或文本颜色突出显示表格内的正/负变化。这种布局适合高密度分类,而不是连续趋势。
|
||||
|
||||
**F。品牌/产品/截面风格车身变型**
|
||||
使用绿色或紫色卡片、按钮、屏幕截图式界面以及吉祥物进行产品宣传、章节介绍或结束页面;信息内容比数据页少。不得滥用作为普通研究页面的模板。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
**背景黑色/近黑色#000000–#080808**:内容页面的主要背景,面积最大。不能用浅色代替;即使新主题使用品牌颜色,也要保持主体背景为深色。
|
||||
|
||||
**深绿色卡片#183F2D / #1A412E**:封面、分区分隔线和品牌卡片的主要基色,通常搭配亮绿色外框。品牌页面可能会大面积使用它;普通内容页面仅使用少量标签或边框 - 它不应该替换大面积的黑色背景。
|
||||
|
||||
**荧光绿#A6F05F / #9EE45A / #4BCC00**:用于封面外底座、剖面框架、身份标记、正值、关键数据线、按钮状标签和一些图表系列。品牌页面上的区域可能较大,而内容页面上的区域可能较小到中等。它可能会被替换为新的品牌原色,但必须保持暗背景高对比度和一致的“积极/关键”语义。
|
||||
|
||||
**白色#FFFFFF /浅灰白色#DDE5E0**:用于标题、关键标签、主轴文本和高优先级数字。不能用低对比度的颜色代替。
|
||||
|
||||
**灰蓝色/灰绿色文本#8A9492 / #B9BCA6 / #808963**:用于正文、注释、辅助图例文本、轴标签、来源和页脚;亮度低,但绝不会低到难以辨认。
|
||||
|
||||
**紫色 #7A54FF / #6B4CFF**:用于中小型区域的分区分隔线、洞察线开口、关键线、标签和品牌变体;默认不代表正/负状态。
|
||||
|
||||
**粉色#F19EE2 / #F4A0E5**:用于小区域的事件时间线、图表系列和强调块,表达类别。避免大量与紫色同时使用,会显得花哨。
|
||||
|
||||
**黄色#FAEB6A / #E7D84A**:用于柱形图、键值、事件或返回类型系列;不适用于长文本段落。
|
||||
|
||||
**红色/珊瑚色#FF5A6A / #BE827F**:用于负回报、下降、风险或负值。仅用于状态/数据,绝不作为装饰。
|
||||
|
||||
内容页面的主要色调为黑-白-灰 + 1 个品牌强调色 + 2-4 个数据系列颜色。多系列图表可以添加颜色,但图例必须保持清晰;颜色仅用于分类/状态——没有无意义的装饰。
|
||||
|
||||
## 6. 字体和文本层次结构
|
||||
|
||||
字体为现代无衬线字体,以 Geist-Regular、Geist-Medium、Geist-SemiBold、Geist-Bold 和 Inter 系列为主。不要混用衬线字体或手写字体;特殊赞助商/徽标字体仅作为图像或标记存在。正文注释文本 = 1x:
|
||||
|
||||
- **封面主标题/年份**:大约。 4–6x、粗体或半粗体、允许紧密的字母间距。
|
||||
- **章节标题**:大约。 3–5x,白色或浅绿色,通常为 1–2 条线。
|
||||
- **内容页面标题**:大约。 2–3x,白色,中粗体到粗体。
|
||||
- **洞察线/信息线**:约。 1.4–2x,通常是中粗体,紫色关键词+灰白色延续。
|
||||
- **字幕/组标题**:大约。 1.1–1.4x,中粗体或半粗体,白色/浅灰色。
|
||||
- **正文段落和项目符号**:1x,常规重量,灰白色或灰蓝色。
|
||||
- **表格文本/图表标签**:大约。 0.7–1x,按空间压缩,保持数字可读。
|
||||
- **脚注、来源、页脚、页码**:大约。 0.6–0.8x,灰色,低级别,但从未消失。
|
||||
|
||||
数字标签使用与正文相同的字体,但粗细增加或颜色不同;没有装饰性的数字形式。标题和正文通过大小、粗细和颜色构建层次结构,而不依赖于阴影或笔画。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
核心图表包括折线图、柱形图、堆积柱形图、面积图、双轴价格/交易量图表、水平条排名、时间线、份额/市场份额图表、甜甜圈/饼图迷你小部件和表格排行榜。
|
||||
|
||||
图表使用深色底色;轴线和网格线是低亮度的灰色细线;没有白色背景的图表框。主数据线采用荧光绿、黄色、紫色、粉色、青色等高对比度颜色;次要系列较低的饱和度或线宽。柱形图使用明亮的柱形与暗网格相对应,并且成交量柱形图可以与价格线结合。多系列堆积柱需要清晰的图例,放置在图表上方、右侧或空白图表区域中。
|
||||
|
||||
关键数据使用峰/谷标签、垂直事件线、端点值、本地文本注释、颜色增亮以及负值的红色。注释紧扣要点,不应形成长段落。预测、目标或基线使用虚线/细线和明确的标签,与历史数据区分开来;永远不要让预测线模糊成实际线。
|
||||
|
||||
轴保留单位和时间基准——尤其是美元、百分比、季度、日期和交易量。网格线必须细、暗且少;没有繁重的边界。页面标题说明图表的主题;图表本身只保留必要的副标题和图例,避免重复。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
表格用于排名、资产比较、交易所/链/协议指标、回报、市场份额和团队/作者信息。它们采用深色底色、高密度样式:标题为白色或浅灰色中粗体文本,行列分隔线为低亮度细灰色,单元格通常没有明显的填充;必要时,使用深灰色/深绿色进行光分区。
|
||||
|
||||
数字列右对齐或在视觉上按小数点对齐;文本列左对齐;名称列可以添加小图标或圆形标记占位符块。正/负值以绿/红区分;排名或强度可以使用水平条、色块长度、热色或小点。摘要行或关键行使用更亮的文本、细边框或浅色填充,但没有浓重的突出显示填充。
|
||||
|
||||
高密度并排模块使用卡片、列组、文本列表或小型图表集合。每个模块都有一个标题、一个键值和一个最小的解释;模块通过对齐、间距和细线分隔开——没有大的阴影。斑马条纹不是主要语言;可以谨慎使用对比度极低的深灰色,并且在分隔线足够的情况下,不添加斑马条纹。
|
||||
|
||||
如果表格超出了一个屏幕可以扫描的范围,请将其拆分为多个页面或重点关注前 N 个页面 - 不要将其压缩至难以辨认。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
**品牌字标标题**:固定在内容页左上角,尺寸较小,白色品牌字+绿色“研究”。将其保留在新页面上;不要将其放大为装饰。**洞察线组件**:紫色关键词/开头+灰白色解释,位于标题右侧或正文栏上方;每页至多一个主要见解——避免多个紫色句子相互竞争。
|
||||
|
||||
**圆形品牌卡片**:封面、章节页和产品页面使用大圆形深绿色/深蓝色卡片,外框为亮绿色或紫色。默认情况下,内容数据页面不会将所有内容框入大卡片中。
|
||||
|
||||
**标签/徽章**:用于部分代码、资产符号、按钮式 CTA 或图表类别;轮廓圆角矩形或带有短文本的明亮填充。不能带长句子。
|
||||
|
||||
**细分隔线和轴线**:表格、图表和时间线的低亮度灰线;细而内敛——没有装饰性的粗线条。
|
||||
|
||||
**事件时间线块**:水平日期颜色块+下方的事件注释,按日期期间或类别着色。适合事件列表,而不是数字趋势。
|
||||
|
||||
**文本列表/头像矩阵**:用于内容页面作者、赞助商、交易所、生态系统项目等;机构名称以文本列表或中性占位符块的形式呈现,头像可能会保留;统一尺寸、对齐方式和标签文本。请勿混入彩色图标装饰;没有绘制任何图形标志。
|
||||
|
||||
**吉祥物插图**:组织名称绿色/紫色字符用于封面、章节分隔符和结束页。在普通数据页面上,它只能出现在特殊品牌/产品页面上,并且不得覆盖图表。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
整体扁平、黑暗、数据驱动、品牌内敛;内容页面依赖于颜色对比度、字体大小层次结构、图表密度和对齐方式,而不是摄影、复杂纹理、阴影或 3D。
|
||||
|
||||
**禁止**:普通内容页面、大面积照片背景、复杂渐变背景、3D图形、发光笔画、表情符号、花哨的插图、装饰性彩色图标、大片居中副本、浓重阴影、低对比度灰色文本、无源图表、无单位数字或覆盖轴的装饰。
|
||||
|
||||
**谨慎使用**:圆形卡片、大面积亮绿色底座、大紫色块、文本列表、头像、截图式界面、甜甜圈迷你图表、斑马条纹表格。它们必须提供信息功能或品牌节点功能——绝不是为了填充空白而添加的。
|
||||
|
||||
**允许特殊页面**:封面、分区、产品页面和结束页面可以使用吉祥物、大圆形卡片、亮绿色/紫色外框、半色调点、趋势线背景和赞助商文本列表。即使在特殊页面上,也要保持标题的可读性,并且永远不要让品牌元素掩盖主要信息。
|
||||
|
||||
图片/截图不是身体的核心语言;如果必须使用它们,请将它们放置在带有标题和来源的固定边界内 - 不要全屏摄影讲故事。如果出现渐变,也仅限于对封面标题或品牌氛围的微妙处理,而不是主体背景系统。
|
||||
|
||||
## 11. 一代清单- 骨架:是16:9吗?内容页面是否以黑人为主;左上角有一个小品牌字标吗?页脚是否包含报告名称/版权、来源和页码?
|
||||
- 文案:标题是否是一个明确的对象/指标;每页是否只有一个主要见解;正文是短段落或项目符号;数字是否带有单位、时间范围和测量基础?
|
||||
- 层次结构:标题、洞察线、正文、图表标签和脚注按相对大小和颜色分隔;是否避免了对特定字体大小的依赖?
|
||||
- 颜色:是否保持黑白灰+荧光绿/紫色+必要的数据颜色;正/负状态明显为绿色/红色;一页上是否有太多鲜艳的颜色?
|
||||
- 布局:主图表/表格是否占据最大面积;解释栏对图表有帮助吗?是否存在无意义的空白或过度拥挤?
|
||||
- 图表:轴、单位、图例、网格线、数据标签和重点注释是否完整;多系列颜色是否可区分;是否标记了预测/基线?
|
||||
- 表格:标题、对齐方式、分隔线、关键行/列以及正/负颜色是否一致;文本可读;是否避免了过度压缩?
|
||||
- 组件:圆形卡片、标签、时间线、文字标记和吉祥物是否仅出现在适当的页面上;它们提供信息而不是装饰吗?
|
||||
- 禁止使用的物品:禁止使用照片背景、表情符号、3D、发光、浓重阴影、复杂纹理、无意义的图标或大段居中副本。
|
||||
- 质量:检查溢出、截断、乱码、重叠、低对比度、遮挡、空内容、图表比例错误、页码缺失、来源缺失和机械模板重复。
|
||||
- 没有默认使用卡片:除非用户明确要求,否则严格禁止使用圆角矩形或矩形卡片来构建层次结构或对齐方式 - 线段、空白以及字体和字体大小差异是更好的解决方案。
|
||||
- 无等分组合:除非没有其他布局可用,否则不要默认三向分割、四向分割或2×2矩阵——包括三向分割+标题+结论等三部分结构。
|
||||
|
Before Width: | Height: | Size: 1.3 MiB |
@@ -1,162 +0,0 @@
|
||||
#F001·科技风格橙色年度报告&商业洞察计划提案PPT模板风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
适合正式的业务报告,例如年度绩效评估、业务洞察、业务回顾、战略计划建议和投资者/管理层更新。它针对具有商业和数据背景的读者,通过大标题、关键数字、图表、表格和简短的解释段落来呈现结论。
|
||||
|
||||
信息密度适中、克制:白底内容页保留大量留白,通常只放一张主图表或结构化模块,旁边附有短卡片;表格页面和路线图页面可能是局部高密度的,但要避免全出血包装。
|
||||
|
||||
- 白色/浅灰色内容页面用于数据分析;
|
||||
- 橙色是唯一的高饱和度主色调,用于图表、关键数字、圆形关系图、节点以及黑色背景上的灯光效果;
|
||||
- 黑色背景页面使用橙色渐变光带、同心圆/弧线框和最小文本,用于封面、过渡、引文线和结束页面。
|
||||
|
||||
基本原则:
|
||||
- **一页仅设置一个视觉锚点。** 大标题、核心百分比、循环关系图、柱形图、甜甜圈、表格或引文占据主导地位 - 其余的是解释层。
|
||||
- **品牌橙色服务于信息层次结构,而不是装饰性堆叠。**它首先用于数据系列、关键行、节点、关键字和重要数字;大面积背景使用白色、近黑色或浅灰色。
|
||||
- **结构清晰,信息详尽。** 保留结论、关键指标和一些支持点;复杂的细节被分成单独的页面或降级为注释——不要用正文段落和小标签填充图片。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
画布为 16:9 横向宽屏。它使用宽边距、低噪音背景和左右列;常见的阅读路径是“左上标题→中/左主视觉→右侧说明卡/数字→底部脚注”。主体位于画面中央,四周留白清晰,避免了抱边排列。
|
||||
|
||||
### 封面/结束页面骨架
|
||||
- 近乎黑色的全版背景,底部或上下边缘有橙色渐变光带;局部覆盖深色矩形框、细线同心圆或弧形纹理。
|
||||
- 左上角有一个小品牌标志;主标题居中或接近居中,可能会分成多行,并且可能包含轻微的字体变化;标语和签名位于底部或右下角,远小于主标题。
|
||||
- 中央深色透明框架可以带有标题,但不得形成粗边框。
|
||||
|
||||
### 黑色背景部分/引用行页面骨架
|
||||
- 近乎黑色的背景,橙色的光芒,文本居中或接近居中,内容量极低。
|
||||
- 低对比度同心圆线框可以形成焦点,而不与文本的层次竞争。
|
||||
- 不携带复杂的图表、表格或密集的小字体。
|
||||
|
||||
### 常规内容页面骨架
|
||||
- 白色或非常浅的灰白色背景;左上角是标题区,标题下允许有简短的注释。
|
||||
- 图表或结构图位于左侧/中间;解释卡、关键数字或短段落位于右侧。
|
||||
- 浅灰色细线、表格线和时间线仅用于组织 - 它们不是装饰主角。
|
||||
- 页码没有稳定、突出的风格,不宜强行生成;来源/脚注在底部或图表附近使用微小的灰色文本。### 目录/导航和水印
|
||||
源文件没有显示强大的目录页面,也没有持久的标题导航;默认情况下不要添加进度条、部分导航、页码栏或大水印。部分提示使用黑色背景过渡页面,而不是每个页面上的顶部导航。
|
||||
|
||||
## 3. 内容组织
|
||||
|
||||
标题倾向于简洁的商业短语,通常是 2-3 个单词的名词组或简短的主题名称,例如概述、财务、收入细分、时间表、产品性能、后续步骤等。语气平静——没有夸张的问题或营销口号;常规页面标题位于左上角,而章节/引用行页面可能居中。
|
||||
|
||||
信息分为三层:
|
||||
- **宣言/引文行:**用于封面后的开头、可引文行和结束页;明显大于正文,可以分成 2-4 行,允许使用局部斜体。
|
||||
- **简短的解释性段落:** 在标题下方或图表旁边,解释页面的目的、结论的背景或指标含义。
|
||||
- **卡片要点:**在右侧说明区,一般为2-4项,每项一个副标题/关键词+一句说明。
|
||||
|
||||
一页 = 1 个主要结论 + 2-4 个支持点。清单不应超过 4 项;时间线在 Q1-Q4 或 3-4 阶段最为稳定;圆形关系图以 3 个圆或 3 个节点为中心。正文保持短段落,避免居中布局的长段。可以使用橙色文本、黑色小胶囊标签、粗体无衬线、橙色数字或浅橙色行填充来强调关键字。
|
||||
|
||||
百分比、货币、季度、年份和增长率应保持在相应的数字或结论附近;主要数字大于正文,单位和注释较小且相邻。来源、注释和测量基础在页面底部或图表附近使用低对比度灰色文本,而不会破坏主要阅读路径。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 左侧标题+中心图表+右侧说明卡
|
||||
适合财务概况、收入构成、成本和利润以及市场表现。左上方放置标题和简短的引言,中间放置柱形图、圆环图、面积图或水平条,右侧放置 2-4 张浅灰色说明卡;图表提供证据,卡片解释原因、行动或影响。使用主橙色图形、黑色小标签和橙色数字来强调。如果数据维度超过5组或说明超过4条,则拆分页面。
|
||||
|
||||
### B. 中心循环关系/能力模型
|
||||
适合非精确比较的内容,如公司概况、能力组合、团队构成、战略关系等。主体使用3个相交的半透明橙色圆圈,或单个发光圆形节点,文字放置在圆心或交叉点附近;右派可能持有很大的比例和一个简短的说明。这种布局强调关系和组合,不适合精确的数字排名或复杂的流程依赖性。
|
||||
|
||||
### C. 时间表/里程碑页面
|
||||
适合季度业绩、阶段进展和关键事件审查。左边是标题和背景说明;身体用浅灰色的细线划分阶段; Q1–Q4 节点使用浅橙色四分之一标记和浅灰色事件卡。保持单行叙述,从左到右或从上到下。不要将其扩展为多项目甘特图,也不要在每个节点放置多个段落。
|
||||
|
||||
### D. 大图表+旁注说明页
|
||||
适合利润曲线、趋势变化、排名比较。大图表占据左半部分或中央主要区域;旁注解释了峰值、拐点、增长率和管理影响。面积图使用从深到浅的橙色层,条形图使用按长度排序的橙色条。旁注保持简短,并且不能超过图表。### E.表格比较页面
|
||||
适用于产品性能、指标比较和功能检查表。标题位于左上角;表格向左或居中展开;简短的结论可能位于右侧或上方。只保留关键列;关键行或关键单元格使用浅橙色填充。如果细节太多,可以用汇总表+注释代替缩字来填满页面。
|
||||
|
||||
### F. 高密度并排模块页面
|
||||
适合团队架构、成本分配、资金部署等多维度汇总。多个浅灰色卡片、短列表或小数字块并排放置,具有统一的宽度、行高和对齐基线;每个模块承载一个指标或一组短文本。不要混合过多的图标、照片或复杂的边框。
|
||||
|
||||
### G. 黑色背景情绪/过渡页面
|
||||
适合封面、分节符、战略引文和结束致谢。核心是近乎黑色的背景+橙色发光+少量居中的副本,通常是一条声明或感谢语加上一个小签名。不适合常规数据页、表格页或小型注释页。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- **近黑色背景#1D1D1D / #191919**:用于封面、过渡、引用行和结束页的整页背景。白色内容页面不得在大面积上使用接近黑色的颜色——仅适用于小标签、图例点或高对比度文本。
|
||||
- **主体文本黑色#111111–#222222**:用于内容页标题、关键注释、表格正文文本和主标签;对于长正文文本,不得将其替换为橙色。
|
||||
- **中性灰色#5B5B5B / #837571 / #A09C9A**:用于解释性文本、图表辅助标签、脚注和辅助信息。必须与白色底色保持对比;不适用于关键数字。
|
||||
- **浅灰色背景#F7F7F7 / #F1EFEF / #E9E9EA**:用于卡片填充、表格区域、浅色页面分区和信息容器;面积可以为中等,但不得超过空白区域。可以更换为相同亮度的品牌浅灰色。
|
||||
- **分隔线灰色#D0D0D1 / #E1DEDC**:用于表格线、时间线、卡片边框和辅助轴线。薄且低对比度——没有粗黑框。
|
||||
- **初级橙色#FC7C3D / #F16736 / #FA8555**:用于柱形图、甜甜圈初级系列、关键数字、节点、关键词和按钮状小标签。在白页上,它作为本地主要视觉效果,不得覆盖大面积的背景区域。可以替换为品牌原色,前提是保持高辨识度和足够的明度差。
|
||||
- **深橙色/红橙色#DC4A21 / #C22B0C / #BF5732**:用于图表高值区域、深区域图表图层、圆环段和黑底光效的黑暗部分;不适用于长文本。
|
||||
- **浅橙色#FECEAA / #FDAA74 / #FCC8B3 / #FBE6DC**:用于半透明圆圈、辅助图表系列、表格关键行、软背景块以及预测或弱化系列。区域可能比主要橙色稍大,但必须保持浅色 - 切勿让页面变成橙色为主。
|
||||
- **白色#FFFFFF /近白色#F8F8F9**:用于内容页面主背景和黑色页面上的反白文本。
|
||||
|
||||
在一页内,仅使用橙色 + 中性色 - 不要引入额外的主色调,例如蓝色、绿色或紫色。数据系列通过橙色的深浅和色调来区分;正/负状态没有独立的红绿系统——如果必须表达正/负,最好使用标签文本和箭头/符号,不要自行添加鲜艳的绿色或警告红色。
|
||||
|
||||
## 6. 字体和文本层次结构
|
||||
|
||||
现代无衬线商业风格,以LiberationSans和ArialUnicodeMS为主体,少量Arial-Black用于字幕/强调标签,少量浅色和斜体用于封面或引文。对于中文,选择几何或中性无衬线字体;避免书法、手写和装饰性较强的字体。- **封面主标题:** 大约。 5-7x 正文、居中、多行措辞、允许局部浅色或斜体部分。
|
||||
- **部分/引用行大句子:**大约。 3-4 倍正文、宽松的行间距、低字数。
|
||||
- **内容页面标题:** 大约。正文文本的 2.5–3.5 倍,左上对齐,中等至较轻的粗细,无粗笔画。
|
||||
- **关键信息数字:** 大约。 2-3x 正文,可能比标题稍小或同等,带有小单元/注释文本。
|
||||
- **字幕/卡片标题:** 大约。 1–1.2x 正文,可以加粗或带有 Arial-Black 气质;保持简短。
|
||||
- **正文注释:** 1x,比例基线;控制行长并避免全角长段落。
|
||||
- **图表标签/表格文本:** 大约。 0.8–1x 正文;保持清晰——不要过度缩小以塞满内容。
|
||||
- **注释/来源/页脚:** 大约。 0.55–0.7x 正文,低对比度灰色。
|
||||
|
||||
文本主要是左对齐的;黑色背景情绪页面和封面可能居中。标题和正文之间保持清晰的间距;不要在一页上混合太多粗细——通常常规、粗体和斜体就足够了。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
主要图表类型有柱形图、水平条形图、圆环图、面积/瀑布感趋势图、圆形关系图和一些小指标图表。图表被简化,网格和轴装饰很少,突出显示橙色区域和直接标签。
|
||||
|
||||
- **柱形图:**大多是 2-4 个带有方角或略圆角的柱形图;主柱为主橙色,次柱为浅橙色。值标签位于列的顶部或旁边;小黑色胶囊可能标记关键数量/值。轴和网格线非常弱——直接标记优先。
|
||||
- **甜甜圈:**用于成分比例,中心留空,部分为原橙色、深橙色和浅橙色。图例位于底部或旁边,使用小色点+短标签。不要引入太多细分——超过 4-5 个类别,识别度就会下降。
|
||||
- **面积/趋势图:** 使用深橙色、原色橙色和浅橙色进行分层或褪色效果,注释增长率、拐点或关键阶段;网格线几乎看不见,水平轴标签很短。适合趋势和利润结构,而不是精确读数。
|
||||
- **水平条:** 橙色条按长度排序,旁边有短标签或值;保持在 3-5 个小节。
|
||||
- **圆形关系图:**半透明橙色圆圈重叠以表达交叉点、组合和能力关系。它们不是严格的数据图表——不附加精确的轴或复杂的比例。
|
||||
|
||||
图表强调是通过色调、标签位置和局部大数字来实现的;不要使用 3D 图表、重阴影、维度饼图、彩虹系列、密集网格线或复杂图例。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
表格服务于“管理比较”和“指标总结”——它们不是详细的数据库。标题为浅灰色或白色,带有粗体黑色文本和短列名称;正文使用黑色/深灰色文本。文本列左对齐,数字和百分比整齐对齐,数字列可以居中或右对齐。
|
||||
|
||||
- 分隔线使用浅灰色细线;避免粗黑边框和全封闭的重型框架。
|
||||
- 行高宽松,桌子周围有明显的空白——不是边到边。
|
||||
- 关键行、关键单元格或当前项目使用浅橙色填充,并限制强调区域。
|
||||
- 浅灰色底座可以占据桌子区域,但不要用高饱和度填充覆盖整个桌子。
|
||||
- 斑马条纹不是很强的特征,可以省略;如果使用的话,只有极浅的灰色交替。
|
||||
- 摘要/结论行可以加粗或用浅橙色填充,但不能使用复杂的多级标题。高密度并排信息使用浅灰色卡、短列表、四分之一节点和公制块。块保持统一的大小和对齐方式,在一句话中包含简短的标题和解释。符号使用小黑点、小彩点、细线和短标签——没有大量的彩色图标或表情符号。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
- **黑色小胶囊标签:** 标记关键金额、图表高点或小类别;反白文字,小面积。它们不应该在页面上成组堆积。
|
||||
- **浅灰色说明卡:**用于右侧说明列表、时间线事件和补充说明;浅灰色填充,无明显阴影,包含一个副标题和一句话。将计数保持在 2-4。
|
||||
- **橙色圆圈/半透明圆圈:**用于关系模型、节点和关键视觉效果;它们可能会重叠成交叉点,但文本必须保持可读。不要将圆圈变成复杂的气泡图。
|
||||
- **细分隔线/时间线:**用于阶段分隔、表格行和列以及布局分区;浅色,细线。
|
||||
- **环形/同心圆线框:**用于黑色背景过渡、科技感页面或一些居中视觉页面;低对比度,绝不是信息图的主体。
|
||||
- **橙色光带/发光:**仅适用于黑色背景封面、章节页面、结尾和其他情绪页面。白色内容页面不使用大面积光效。
|
||||
- **标题栏/导航:** 尚未建立持久标题导航,并且不得强制到每个页面上。标题本身就是主要导航。
|
||||
- **图像框架:** 仅看到占位符矩形/灰色块的轻微倾向 - 这不是照片驱动的风格。如果使用图像,请将它们限制为矩形辅助工具,永远不要占据图表和文本的水平。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
整体扁平、内敛、务实;黑色背景页面可能会使用微妙的科技灯光效果,而内容页面则依赖于排版、空白、图表和橙色强调。
|
||||
|
||||
禁止:
|
||||
- 3D 图表、维度饼图、透视柱、发光描边文本、强烈阴影、金属纹理、大型玻璃形态组件;
|
||||
- 彩虹调色板、混合多品牌颜色、表情符号、卡通插图、手绘箭头、夸张的图标堆叠;
|
||||
- 大段居中的正文、整页密集的小字体、复杂的多层边框、粗重的表格线;
|
||||
- 常规数据页上的大面积图像背景或黑色背景小型表格。
|
||||
|
||||
谨慎使用:
|
||||
- 渐变:仅适用于黑底橙色发光、柔和的面积图分层和浅橙色过渡;从来没有整页彩色渐变背景。
|
||||
- Shadows:源文件不依赖阴影;如果必须区分层次结构,请仅使用极其微妙的阴影或改用浅灰色填充。
|
||||
- 圆角:可能会轻微软化卡片或列,但这不是重圆角卡片样式。
|
||||
- 图标:如果必须使用,则只有最少的线条图标或小符号,黑色/灰色/橙色 - 没有多色图标。
|
||||
- 照片/截图:可作为辅助证据或占位符,但不是核心语言;他们需要矩形裁剪、低装饰并且没有全出血背景。
|
||||
|
||||
特殊页面可以使用黑底灯光效果、同心圆、低对比度技术线框、居中引用线、最小斜体和反白文本 - 仅适用于封面、章节、引文和结束页。
|
||||
|
||||
## 11. 一代清单- 骨架:是16:9吗?内容页面是白色/浅灰色的,覆盖/过渡接近黑色+橙色的灯光效果;利润充足吗?
|
||||
- 文案:标题是短语;每页是否只有 1 个主要结论;解释是否控制在短段落或 2-4 张卡片内?
|
||||
- 层次结构:标题、正文、脚注是否有明确的相对比例;是否避免了特定的字体大小依赖性和层次结构混乱?
|
||||
- 颜色:仅使用橙色+中性色;橙色用于强调而不是淹没页面;低对比度灰色文本仍然可读吗?
|
||||
- 布局:是否有清晰的阅读路径;主视觉、解释区域和脚注各司其职;是否避免了多个竞争锚点?
|
||||
- 图表:使用简化的柱形图/甜甜圈图/面积图/水平条/圆形关系图;是否避免了 3D、彩虹色、密集网格和复杂图例?
|
||||
- 表格:表头、行、列、关键行是否清晰;线条是否轻薄;数字是否对齐;是否避免了细节过多?
|
||||
- 组成部分:浅灰色卡片、黑色胶囊、橙色节点和细线是否提供信息?页码、导航和水印从未被强制添加过吗?
|
||||
- 违禁物品:禁止表情符号、卡通插图、浓重阴影、复杂装饰、大图像背景、粗边框或发光文字。
|
||||
- 质量:检查文本溢出、截断、乱码、重叠、低对比度、遮挡、空内容、错误比例以及过度重复的模板感觉。
|
||||
|
Before Width: | Height: | Size: 346 KiB |
@@ -1,124 +0,0 @@
|
||||
# 公民简报·公共服务简报风格设计系统
|
||||
|
||||
> 预期主页标签:工作报告
|
||||
> 视觉隐喻:市政导视系统+公共服务运营简述
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
适用于季度回顾、公益运营、项目进展更新、跨部门汇报、基层工作总结等。页面应该感觉像一个清晰、值得信赖的公民寻路系统:暖白色底座、深靛蓝文本、天蓝色路线和安全黄色提示,使用车站、路线、数字和状态来明确“做了什么、有效了以及下一步要改变什么”。
|
||||
|
||||
基本原则:
|
||||
|
||||
1. 工作报告以运营结论为开头,然后给出覆盖范围、效率、问题和行动的证据。
|
||||
2. 寻路语言必须带有位置、顺序、责任或状态;它不能退化为装饰性图标。
|
||||
3. 一页仅包含一个管理问题,最多突出显示 3 个数字或操作。
|
||||
4. 成就、问题和待决决策使用独特但稳定的视觉语义。
|
||||
5、不得模仿政府公文、标志、红金礼仪风格;不要制造虚假的权威感。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
画布为 16:9 横向。内容页面以暖白色为主;部分分隔符或结论页可以使用深靛蓝。页面保持较宽的安全边距;狭窄的“路线编号条”可能会沿着左侧延伸,标题、季度和页码位于顶部,数据定义、测量基础和责任信息位于底部。
|
||||
|
||||
标题区域占 15%–20%,主要工作区域 65%–72%,页脚 6%–8%。标题下不要使用粗横线;使用短蓝线段或站号来定位它。
|
||||
|
||||
封面采用“一个大标题+一条服务线路+3个重点站点”。内容页面在覆盖图、需求带、运行路线、问题诊断、行动列表之间切换;避免在每一页上重复三列卡片布局。
|
||||
|
||||
## 3. 内容组织
|
||||
|
||||
标题使用管理结论,例如“现在覆盖了整个地区的需求,但周一早上仍然是最明显的拥堵点。”字幕说明了地区、时期、受众以及数据定义或测量基础。
|
||||
|
||||
每个页面的组织方式为“当前状态 - 证据 - 含义 - 下一步”。成就必须表明它们对所服务的人民意味着什么;问题必须与原因或负责的流程步骤或所有者相关;行动必须带有截止日期、所有者或接受标准。
|
||||
|
||||
数字位于相应的站、进程或人口旁边,并且必须保留单位、周期和分母。不要在同一范围内比较访问次数、比率和百分比。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 操作概述页面
|
||||
|
||||
用一根横向业务线连接覆盖、处理、解析、反馈4个节点。每个节点只保存一个主数和一个判断;该行的末尾给出了该季度的总体评估。适合开口;不要建造 KPI 卡墙。
|
||||
|
||||
### B. 覆盖范围和客流量页面
|
||||
|
||||
将简化的地区地图或服务点网络放在左侧,将人流结构和高峰时间放在右侧。地图仅传达枢纽、移动站、压力点;不要制造精确的地理边界。
|
||||
|
||||
### C. 需求构成带
|
||||
|
||||
使用宽度随份额变化的水平带来显示主要服务类型;在其下方,使用2-3个直接注释来解释需求集中的地方。将次要类别合并到“其他”以保留主要-次要层次结构。
|
||||
|
||||
### D. 服务流程页面
|
||||
|
||||
将居民到达、取票、办案、移交、办结视为一条连续路径。时间或通过率显示在节点上方,障碍物显示在下方。在节点前画连接线;线条不得与文本交叉。
|
||||
|
||||
### E. 问题-责任页面左边列出了他们的证据的三个问题;右侧将每个问题与材料、系统或协调责任联系起来。注意事项采用安全黄色;仅针对真正阻碍服务的问题保留朱红色。
|
||||
|
||||
### F. 下一季度行动页面
|
||||
|
||||
使用3条行动路线来呈现“行动/所有者/截止日期/接受标准”;在右下角单独列出需要领导签字的项目。以明确的工作决定结束,而不是通用的感谢页面。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- 暖白色#F5F2EA:内容页面的主要背景。
|
||||
- 深靛蓝 #17324D:标题、正文、部分分隔符和主要路线。
|
||||
- 天蓝色#3D8CC9:正常运行、主要路线、关键数据。
|
||||
- 安全黄色#F2C14E:注意点、等待状态和待协调事项。
|
||||
- 绿色服务#4D8B72:已完成的项目、有效的试点和稳定的状态。
|
||||
- 朱红色#D95D39:堵塞、投诉和必须升级的问题;区域受到严格限制。
|
||||
- 道路灰色#A8AFB5:次要线、基线和来源。
|
||||
|
||||
单个页面通常使用靛蓝加一种状态颜色。红色和绿色不能成为唯一的区别;它们必须与文本或符号配对。禁止使用大红色背景、蓝紫色渐变和五色状态灯。
|
||||
|
||||
## 6. 版式和文本层次结构
|
||||
|
||||
中文使用思源黑体或苹果方,英文使用 Arial/Helvetica 型无衬线字体;数字、代码和时间可以使用等宽字体。整个平台上不超过两个字体系列角色。
|
||||
|
||||
- 封面标题:4-5 倍正文大小,最多两行。
|
||||
- 页面标题:正文大小的 2–2.6 倍,左对齐。
|
||||
- 主要数字:3-4x 身体尺寸,单位紧邻。
|
||||
- 动作标题:1.2–1.5 倍身体尺寸。
|
||||
- 正文:1x,短句。
|
||||
- 页脚、数据定义和测量基础注释以及路线代码:0.6–0.75x,但必须保持清晰。
|
||||
|
||||
层次结构主要依赖于字体大小、粗细和位置,而不是彩色圆形背景框。切勿将标题缩小为小文本以适应长句子;首先修剪副本。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
更喜欢服务路线图、直接标记的条形图、时间热带、点图、斜率图、简化的覆盖网络和 100% 堆叠条形图。
|
||||
|
||||
正常运行时使用天蓝色或服务绿色,压力点使用安全黄色,严重堵塞时仅使用朱红色。网格线是极细的灰色线;图表标题必须说明发现结果。
|
||||
|
||||
时间和比率不得共用同一轴。将百分比变化明确表述为“百分点”;不要将它们写成百分比增长。每个图表必须标明其周期、样本定义和抽样基础。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
表格仅用于服务中心比较、问题列表和行动分类帐。标题使用深靛蓝文本,底部有细线;在行与行之间留出喘息空间;不要使用封闭的重边框。
|
||||
|
||||
状态由小方块、短标签、文字共同表达;有问题的行可能会得到一个安全黄色的左标记,而朱红色的橙色则保留给逾期或阻塞的行。所有者、截止日期和验收标准必须位于同一阅读线上。
|
||||
|
||||
如果表格超过6列,最好将其拆分为“主要结论图表+精简分类账”;不要将完整的后端表塞进一页。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
稳定的组件包括路线、车站点、服务号码、方向箭头、状态方块、短勾、区域轮廓和责任标签。
|
||||
|
||||
站位大小表明层次,而非任意审美;线路样式区分正常、试点和尚未连接的路线。保持图标数量较少;仅使用清晰的公共服务线路符号。照片不是默认元素。如果使用服务场景照片,请放置单个纪实照片并标记其是说明性的还是真实的来源;不要使用舞台照片拼贴画。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
整体感觉是现代城市服务运营的简述:克制、清晰、对公众负责。视觉重心是路线和动作,而不是装饰。
|
||||
|
||||
禁止:红金政府模板、假徽章印章、领导讲话式封面、KPI卡墙、圆形SaaS面板、卡通人物、彩虹图标、3D地图、千分表、整版标语、无问题报告成绩。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 每一页是否首先陈述一个运营结论或管理问题?
|
||||
- 覆盖范围、效率、问题和行动是否构成完整的审核链?
|
||||
- 所有路线、站点和状态都具有商业意义吗?
|
||||
- 数字是否带有句点、单位、分母和正确的百分比措辞?
|
||||
- 问题是否与负责任的流程步骤或责任人相关,行动是否包含责任人和验收标准?
|
||||
- 颜色是否补充有文字/符号以避免单独依赖红色与绿色?
|
||||
- 暖白色、靛蓝色和单一状态颜色的稳定语法是否保持不变?
|
||||
- 是否避免模仿官方文件、卡片墙、仪表板和空洞的成就口号?
|
||||
- 是否检查了重叠、裁剪、换行、低对比度、乱码和过小的页脚?
|
||||
|
||||
- 默认情况下没有卡片:除非用户明确请求,否则切勿使用圆角矩形或矩形卡片来构建层次结构或对齐方式:线段、空白和字体大小差异是更好的解决方案
|
||||
- 没有均匀划分的构图:除非没有其他可用的布局,否则不要默认为三分之一、四分之一或二乘二矩阵。这包括三部分结构,例如三栏+标题+结论
|
||||
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -1,171 +0,0 @@
|
||||
# 报告名称 风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
社论式研究报告PPT,面向旅游、酒店、目的地和行业决策领域的读者。以杂志阅读体验来组织“观点——证据——解释”:旅行/酒店场景摄影设置背景,浅蓝色和深海军蓝色形成报告界面,段落、专家引述、调查数据和章节叙述交替推进。它既不是面向销售的演示,也不是纯粹的数据仪表板。
|
||||
|
||||
它采用风景宽屏画布、品牌定制的低装饰无衬线字体、深蓝色/亮蓝色/浅报告蓝色的品牌颜色、大矩形照片和色块、低调的页脚、超大零件号(部分分隔板上有半屏摄影)、带有跳转箭头的列表项内容页、蓝紫色图形和细线分隔的数据页。允许相对较高的信息密度,但通过矩形分区、宽边距、标题层次结构和摄影空白来保持清晰度。
|
||||
|
||||
基本原则:
|
||||
|
||||
1. **场景优先,信息嵌入**:使用真实的旅行/酒店照片构建场景;将文本、引言和数据放在单独的色块或白卡上,避免将它们直接放在复杂的照片上。
|
||||
2. **品牌蓝为阅读服务,绝不抢夺阅读**:深蓝色用于最高强调和反转区域,浅蓝色承载长文本和章节底板,亮蓝色仅用于导航、标签和小区域强调;正文的可读性不得降低。
|
||||
3. **出版一致性胜过页面表演**:照片、两栏布局、表格和图表可能会有所不同,但标题位置、页脚、矩形分区、排版层次结构和冷蓝色调色板必须在整个平台上保持一致。
|
||||
|
||||
## 2. 页面骨架- **画布比例**:统一的横向宽屏报告页面,接近PDF/杂志排版;不要使用大字体、宽空白的演示模板。保持可见的外边距;内容永远不会触及页面边缘。
|
||||
- **基本边距**:内容页面在所有四个边上保持稳定的白色或浅蓝色边框;左、右和上边距保留了杂志的感觉,下边距为页脚、报告名称或页码保留了空间。大图像可以接近内容区域的边缘,但不得突破安全边界。
|
||||
- **标题区**:正文标题位于左上角或顶部,使用短问句、章节名称或结论性短句;分区标题可能会占据屏幕的左半部分或一个大的浅蓝色区域;封面标题设置在覆盖在照片上的深蓝色信息卡中。分层只能通过字体大小、位置、背景颜色和水平线来实现。
|
||||
- **正文区域**:组织在矩形网格上 - 通常为左/右两列,上/下两个带,照片+文本卡,深蓝色侧边栏,完整的浅蓝色底板以及多列文本/图表组合。用空格或细线分隔块;不要堆叠浮动元素。
|
||||
- **标题**:常规内容页面没有复杂的标题导航;章节信息以小文本形式融入标题或页脚。不要添加持久的顶部导航栏。
|
||||
- **页脚和页码**:内容页面底部稳定地带有品牌洞察标记、报告名称和页码。字体明显小于正文,为深蓝色/灰黑色;页码位于底部边缘附近,不会与主图表竞争注意力。
|
||||
- **Logo**:封面左上角出现身份标记,呈现为小纯文本(写入新主题名称),不绘制图形徽标;内部页面通过小的页脚文本或品牌信息来弱化它;封底/封页可以强化品牌文字标记。不要在每个页面上放置大徽标。
|
||||
- **内容导航**:左侧窄蓝色渐变/抽象曲面图形+右侧内容列表。条目由章节名称、开头附近或左侧栏中的数字页码以及右侧/尾部的“↗”组成;条目之间用细线分隔。
|
||||
- **分隔线**:细灰色/浅蓝色线用于内容、列之间、表格中、引用属性和数据列表中;它们的粗细和对比度低,而不是装饰性的粗边框。
|
||||
- **水印和背景图形**:浅蓝色部分分隔线可能使用非常微弱的弧线/曲线阴影;深蓝色页面可能使用低对比度纹理或线性背景。这些仅用作背景,不得损害正文和照片的易读性。
|
||||
- **默认阅读路径**:左上角标题或零件号→主照片/深色强调区→正文或图表→脚注和页脚。两列布局从左到右、从上到下阅读;在照片 + 侧边栏布局中,首先是照片,然后是关键数据或引言。
|
||||
|
||||
## 3. 内容组织- **标题的书写方式**:封面采用主标题+副标题+系列/品牌标签;目录页列出了章节;章节分隔符使用“第十部分”+一句话章节主题;正文页使用疑问句或明确的主题句。具体且可读;没有夸张的营销口号。
|
||||
- **信息句子**:观点和引用页面可以使用完整的报告式段落,而不是被切成许多简短的口号。突出显示的句子使用超大引号、浅蓝色引号框或深蓝色反转区域。
|
||||
- **正文组织**:段落短、行长窄、段落间距清晰;前言/方法页可以密集地设置在 2-3 列中。不要将长段落拉伸到整个页面宽度。
|
||||
- **分组逻辑**:套牌按部分分为章节;每个页面的组织方式为“主题标题 - 解释段落 - 照片/引用 - 数据或列表”。每一页都围绕一个主要问题;不掺杂任何不相关的主题。
|
||||
- **每页观点数量**:常规内容页面包含 1 个主要观点,可选附有 1 条专家引述或 1 组数据;一个数据复合页面可以并列 2-4 个指标/图表,但它们必须共享相同的问题、定义和测量基础。
|
||||
- **如何编写项目符号**:更喜欢短标题、百分比、细线分隔符、图标/小标记或并排卡片 - 而不是传统的大项目符号点;保持项目的长度相似,并避免任何单个项目变成长段落。
|
||||
- **关键词强调**:使用粗体、色块、字体大小、位置、放大数据;谨慎使用文本内颜色突出显示。斜体仅用于报告名称、引用来源或语气强调,切勿大面积使用。
|
||||
- **数量/单位/时间基础**:百分比、年份、样本量及其解释保持相邻;大数字必须附有对其定义和测量基础的简短解释。单位设置得小于数字,但必须保持清晰。
|
||||
- **来源脚注**:图表来源、调查样本定义和方法说明位于图表下方、页面底部或侧边栏底部;低对比度但清晰,不要与页脚混淆。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 大摄影+深蓝色信息侧栏
|
||||
|
||||
用于专家观点、主要发现、排名列表和百分比数据。一面放旅游/酒店场景照片;另一侧是深海军蓝矩形侧边栏,设置有白色文本、浅蓝色细线、小图标或百分比列表。关键点在顶部;报价可能会以超大引号开头,出处位于底部。
|
||||
|
||||
它携带 1 个报价或 4-6 个短数据项;照片和侧边栏不得相互重叠。如果文本需要长篇解释或复杂表格,请勿使用此布局。
|
||||
|
||||
### B.浅蓝色底板+照片/文字两栏
|
||||
|
||||
用于章节开头、个人引言、较长的解释性段落和方法论注释。浅报告蓝色作为整页底板或半页载体层,一侧为照片,另一侧为标题、段落和出处;文本为深蓝色/近乎黑色,并用分隔线将属性与正文分开。
|
||||
|
||||
强调来自大标题、左对齐和浅蓝色空间。如果页面包含多色数据图表,请减少浅蓝色区域或切换为白色背景,以避免混色。
|
||||
|
||||
### C.白色报表页+多栏正文
|
||||
|
||||
用于前言、引言、方法论和解释性章节。大标题位于顶部或左上角;正文分为 2-3 列,两侧可选择肖像照片、浅蓝色作者信息框或小记事卡;列由空格或细线分隔。控制列宽;避免全角长行。这种布局不适合情感章节的过渡,也不适合呈现需要强烈视觉冲击力的大量数字。
|
||||
|
||||
### D. 部分分隔符:超大零件号 + 半屏摄影
|
||||
|
||||
用于分隔章节。左侧或上层的浅蓝色区域上有一个超大的“Part X”,下面是章节主题句;屏幕的右侧或一半带有肖像/旅行场景照片。浅蓝色区域可能包含非常微弱的曲线。
|
||||
|
||||
它仅提供导航和章节氛围——没有复杂的正文、图表、脚注或多个观点。零件编号明显大于章节主题。
|
||||
|
||||
### E. 数据综合页面:主题解释 + 多个图表/指标
|
||||
|
||||
用于调查结果、投资方向和偏好比较。顶部包含主题标题和简要说明;正文分为图表区域、数字度量区域和解释性文本区域。常见元素包括圆环图、条形列表、百分比卡和小图标列表,颜色主要为蓝紫色和深蓝色。
|
||||
|
||||
强调来自于大百分比、深蓝色背景上的白色文本以及亮蓝色/紫色系列颜色。如果数据定义或测量基础复杂或注释较多,请拆分页面或将其转换为表格/方法页面。
|
||||
|
||||
### F. 高密度并行模块页面
|
||||
|
||||
用于多个发现、多个市场/细分市场或多个战略维度的横向比较。使用 2-4 张等宽的卡片或列,每张卡片或列包含一个副标题、一个短段落、一个数字或一个小图表;严格对齐,用浅色分隔线组织,并在白色和浅蓝色之间交替背景颜色。
|
||||
|
||||
不适合大的情感照片;照片可能仅显示为小缩略图以帮助分类。所有模块必须共享相同的语法,否则报告的一致性就会中断。
|
||||
|
||||
## 5. 颜色系统- **深海军蓝#000735 / #020D3E / #051954**:用于封面信息卡、深蓝色侧边栏、强调区域、封底、深色图表背景和标题文本;区域范围可以从小标签到半页/全页暗块。它可以替换为其他品牌的深色,但白色文本必须保持高对比度,并且颜色不得转变为灰色或黑色,以致失去其蓝色身份。
|
||||
- **浅报告蓝色#C6D4F9 / #C7D5FA**:用于分区分隔板底板、报价页背景、方法/注释框、浅表填充和大型载体层 - 最多半页甚至整页。可以换成低饱和度的品牌浅色,但一定要保持冷色调,明度足够。
|
||||
- **明亮的品牌蓝色#1E77F8 / #3781F9 / #5D8DF4**:用于内容页面左侧的抽象图形、标签、小强调、链接样式箭头和图表系列颜色;它仅用作小区域指导。不能大面积铺展来代替浅蓝色,否则结果会变得过于科技味,失去杂志感。
|
||||
- **白色#FFFFFF /近白色#F1F3FA**:用于内容页面背景、卡片、图表背景和空白区域;它们上面的文字是深蓝色或接近黑色的。
|
||||
- **主要文本接近黑蓝色 #030208 / #07090F / #2B3A5C**:用于正文、标题、表格文本和图表标签。浅灰色不得用作主体颜色。
|
||||
- **中性灰色#8D92A1 / #9FA0A9 / #B3B6C3**:用于分隔线、辅助标签、轴、脚注和非必要信息;面积小,对比度低但可辨别。它不得携带关键信息。
|
||||
- **照片暖色调#A87C39 / #C06712 / #CB9742 /棕褐色系列**:这些主要来自旅行/酒店摄影,不是系统原色;它们可能只会少量地呼应图表或当地口音——不要将它们提升为主要品牌颜色。
|
||||
- **紫色/蓝紫色辅助色**:用于少量图表系列、关键指标或比较组;它们的区域受到控制,不得同时与亮蓝色和深蓝色竞争。
|
||||
|
||||
每一页都以蓝色家族为中心;除照片的自然色外,系统颜色的数量不应超过2-3种色调。首先通过明暗对比、标签文本、箭头或图例来解释正/负状态;谨慎引入红色和绿色,切勿使用高饱和度的红绿组合,破坏报告的冷色调。
|
||||
|
||||
## 6. 版式和文本层次结构
|
||||
|
||||
使用现代无衬线字体。 Regular中品牌定制字体占绝大多数;粗体很少用于标题/强调,常规斜体用于报告名称或引用强调,中/浅仅很少使用。正常体重占主导地位;避免过度加粗。- **封面主标题**:主体尺寸的 3.5–4.5 倍,设置在深蓝色信息卡内或照片上方明确定义的区域;行距紧但不拥挤。
|
||||
- **封面字幕/系列标签**:字幕为正文尺寸的 1.5–2 倍;系列/标签是主体尺寸的 0.7–0.9 倍,通常设置在亮蓝色或浅蓝色条内。
|
||||
- **部分分隔件零件号**:4-6 倍主体尺寸,轻量或常规重量 — 无需加粗。
|
||||
- **章节主题句**:1.6–2.5x 正文大小,左对齐,自然断开多行。
|
||||
- **内容页标题**:1.5–2.2x 身体尺寸,通常为常规重量;短标题可以放大。不要使用过于沉重的标题,以免产生商业海报的感觉。
|
||||
- **正文**:1x;宽大的行距和清晰的段落间距。
|
||||
- **副标题/卡片标题**:1–1.2x 正文大小,可以是粗体或中号。
|
||||
- **图表标签/表格文本**:0.75–0.95x 主体尺寸;键数可放大至 1.5–3 倍。
|
||||
- **引号文本**:长引号是正文大小的 1–1.2 倍;引号可以放大到 2-3 倍并设置为深蓝色。
|
||||
- **注释、来源、页脚、页码**:0.55–0.75 倍正文大小,低调但清晰。脚注不能太小而难以阅读。
|
||||
|
||||
默认情况下,文本左对齐。除封面信息卡的局部区域外,请勿使用大块居中副本。标题可以使用标题大小写或句子大小写,但同一级别必须统一。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
图表是嵌入在报告叙述中的证据组件,而不是主要的仪表板。样式包括圆环/环百分比、条形/排名列表、多指标卡、小图标列表、表格比较和少量坐标图表。
|
||||
|
||||
- **图表类型**:更喜欢百分比环、水平条、排名条、指标卡、简单的柱/线和比较表。避免复杂的交互式仪表板、雷达图、3D 饼图和包含太多系列的折线图。
|
||||
- **轴和网格线**:使用浅灰色/浅蓝色低对比度线;网格线仅有助于阅读。轴标签比正文小,呈中性灰色或深蓝色。
|
||||
- **标签和图例**:尽可能在图形附近直接标记值,并尽量减少远处的图例;将百分比保持在其指标名称附近;传说样本小而整洁。
|
||||
- **系列颜色**:使用深蓝色、亮蓝色、浅蓝色和蓝紫色,并控制每页的系列数量。照片暖色调不会进入常规数据系列,除非它们与本地内容相呼应。
|
||||
- **关键数据**:使用大数字、深蓝色背景上的白色文本、粗线或高饱和度蓝紫色。不要同时堆叠多个强调点,例如阴影、分解饼图和荧光色。
|
||||
- **预测/目标/基线**:使用带有短标签的细虚线或细实线。不要添加复杂的渐变填充。
|
||||
- **注释**:使用白色卡片或带有细线的浅蓝色方框,并谨慎使用箭头;它们不得掩盖图表的主要读数。
|
||||
|
||||
保持图表的简洁和精致;如果不能快速掌握一页上的金额,请拆分页面或将其移动到附录表中。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
表格用于方法论、样本描述、市场/指标比较以及浓缩高密度信息。它们使用浅色边框、微妙的背景色调和报告式排版——而不是办公软件默认的厚重网格。- **标题**:使用浅蓝色、近白色或深蓝色底白色;文本可能会稍微加粗,大小接近正文文本或稍小;保持足够的高度和空白。
|
||||
- **行/列结构**:按内容函数分配列宽;左侧通常包含类别/维度,右侧通常包含值或解释;用细线或空格分隔。
|
||||
- **分隔线**:使用浅灰色/浅蓝色细线,避免使用粗的全黑边框。水平规则比垂直规则更常见;仅当需要进行强比较时才保留垂直分隔符。
|
||||
- **背景填充**:使用白色、浅蓝色或微妙的斑马条纹;斑马条纹必须是低对比度的并且仅有助于扫描。
|
||||
- **关键行/列**:用深蓝色文本、浅蓝色填充、粗体或小标签突出显示。不要用高度饱和的颜色填充大面积区域。
|
||||
- **摘要行**:用稍深的浅蓝色填充或上方的细线进行区分 - 而不是粗边框。
|
||||
- **数字对齐**:右对齐或小数点对齐百分比、金额、年份等;左对齐文本;单位紧跟在数字后面或移动到列标题中。
|
||||
- **符号标记/评级点**:可以使用小点、复选标记、箭头或短标签,保持在单色蓝色/灰色系统内。彩色图标不是默认的表格标记。
|
||||
|
||||
保留标题、描述和脚注;不要让表格填满整个页面。表格压缩证据;它们不能取代叙事。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
- **深蓝色信息卡/侧边栏**:用于关键结论、引述和数据列表;白色文字和内部浅蓝色细线,无阴影;非常适合与照片并排放置。
|
||||
- **浅蓝色载体盒**:用于长引用、章节主题、解释性段落和方法论信息;文本为深蓝色/接近黑色;它可能没有轮廓,区域由色块和空白定义。
|
||||
- **封面标题卡**:覆盖在照片上的深蓝色矩形卡,顶部可选择亮蓝色/浅蓝色标签条;直角,位于中心右侧或中心右侧,避免遮盖人脸。
|
||||
- **目录条目**:由页码、章节名称、细线分隔符和“↗”组成;可以少量用于导航,但不应淹没正文中的每个列表。
|
||||
- **超大引号组件**:用于专家引述或观点摘录;引号是深蓝色或白色,并且明显大于正文;接下来是该段落,出处位于底部。
|
||||
- **百分比/指标列表**:每个项目包含一个小图标或短标签、百分比和说明文字;项目之间用细线或空格分隔;适合深蓝色侧边栏或白色卡片。
|
||||
- **图像框**:矩形、直角裁剪,通常占据半页或较大面积;主题包括人物、酒店、自然、交通/旅行场景。通过直边与文本分开,没有相框阴影。
|
||||
- **抽象蓝色曲面/曲线**:内容和分区页面可以使用蓝色渐变曲面或淡线条;这些仅用于章节/导航氛围,而不是每个页面上的装饰。
|
||||
- **细线分隔线**:低对比度,长度跟随内容块的宽度;绝不整版重装饰线。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
整体质感扁平、内敛,像社论杂志,页面通过摄影、矩形色块、排版层次和蓝色系统进行组织。**禁止**:
|
||||
- 3D 图表、透视文本、发光轮廓、霓虹灯效果、复杂纹理、浓重阴影和玻璃形态。
|
||||
- 表情符号、卡通贴纸、大量彩色图标和社交媒体风格的表情符号。
|
||||
- 大块居中文案、海报式口号堆叠以及整页非结构化超大文本。
|
||||
- 将长正文文本直接放置在复杂的照片上,或用低对比度文本覆盖人物/场景主题。
|
||||
- 使用高饱和度的红色、绿色或黄色作为大面积状态颜色,打破了蓝色报告系统。
|
||||
|
||||
**谨慎使用**:
|
||||
- 渐变:仅限于内容页抽象图形、蓝色曲面或极其微妙的背景;不适用于按钮或大面积的身体接地。
|
||||
- 阴影:源样式几乎不依赖于阴影;如果必须区分白色卡片,请仅使用非常浅的阴影或切换到空白/背景颜色。
|
||||
- 圆角:源文件以直角矩形为主;圆角不得成为系统功能。
|
||||
- 图标:数据列表中可以使用小的单色线图标;大的彩色图标不合适。
|
||||
- 插图:不被视为核心语言;它不能取代真实的摄影。
|
||||
|
||||
**允许在特殊页面上**:
|
||||
- 大面积的摄影背景可用于封面、分区、结束页面或一些情感过渡;长文本正文页面不应使用全图像背景。
|
||||
- 抽象的蓝色渐变形状可用于内容或导航页面;普通内容的页面应该受到限制。
|
||||
- 整页或半页深蓝色可用于强调结论/引文/封底,但内容量必须小,并且反转的文本必须保持清晰。
|
||||
|
||||
## 11. 一代清单- 骷髅:是统一的风景宽屏吗?外边距和底部页脚是否保留?标题区、正文区、脚注区是否划分清楚?
|
||||
- 复制语法:标题是具体的主题/问题/章节句子吗?每一页是否只包含一个主要观点?段落是否太长或太宽?
|
||||
- 层次结构:封面、部分、内容页标题、正文、图表标签和脚注的相对大小是否明确定义?粗体是否被过度使用?
|
||||
- 颜色:核心调色板是深蓝色、浅蓝色、亮蓝色和白色/近黑色吗?一页上的系统颜色是否过多?深色背景上的文字对比度是否足够?
|
||||
- 布局:照片、文本和图表是否与矩形网格对齐?是否存在随机浮动、被覆盖的面或不一致的边距?
|
||||
- 图表:图表简单易读吗?百分比及其定义和测量基础是否相邻?图例、轴和网格线是否被低估了?是否有3D或过多的装饰?
|
||||
- 表格:标题、行/列、分隔线、数字对齐和脚注是否标准化?是否使用了厚重的黑色网格或高度饱和的填充?
|
||||
- 组件:深蓝色侧边栏、浅蓝色载体盒、内容箭头、引号和指标列表是否携带信息而不仅仅是装饰?
|
||||
- 禁止物品:是否避免使用表情符号、复杂插图、大型彩色图标、强烈阴影、发光、3D、复杂纹理和大块居中副本?
|
||||
- 可读性:是否存在溢出、截断、乱码、重叠、对比度低、遮挡、内容空、图像长宽比错误、页脚压入正文等问题?
|
||||
- 一致性:页面可能有所不同,但它们是否仍然保留蓝色系统、页脚、直角矩形、左对齐和报告杂志字符?避免使每个页面成为不相关的模板。
|
||||
- 默认情况下没有卡片:除非用户明确请求,否则切勿使用圆角矩形或矩形卡片来构建层次结构或对齐方式:线段、空白和字体大小差异是更好的解决方案
|
||||
- 没有均匀划分的构图:除非没有其他可用的布局,否则不要默认为三分之一、四分之一或二乘二矩阵。这包括三部分结构,例如三栏+标题+结论
|
||||
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -1,158 +0,0 @@
|
||||
# 机构名称 AR 23 24 · 2023年年度报告 24 STYLE DESIGN SYSTEM
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
上市公司年报、投资者关系、ESG和治理披露、财务附注和审计报表的风景报告式PPT。它为投资者、董事会、监管机构和内部管理层服务,强调长篇阅读、快速导航、可审查性和可追溯性。正文、表格、脚注和图表采用温暖的灰白色纸张背景、稳定的网格、小尺寸密集排版、Tahoma/Caladea、黑灰色文字、细灰色线条和少量金棕色进行组织。摄影仅用于封面、内容/章节页面或少数品牌页面;正文页面以多栏文本、数据指标、圆环图/柱形图、宽表和治理矩阵为主。
|
||||
|
||||
基本原则:
|
||||
- **披露阅读胜过视觉表现**:标题、正文、数据、脚注和来源必须沿着网格连续阅读;装饰、图像和色块不得破坏审查路径。
|
||||
- **低饱和度一致性战胜强刺激**:卡组以灰白色、黑灰色、浅灰色、金棕色为主;只有黑色封底、摄影页和必要的图表可能会表现出更强的对比度。
|
||||
- **信息通过位置、尺寸比例、线条和空白进行分层**:使用很少的大面积颜色模块,没有强烈的阴影或复杂的图形;高密度页面通过列宽、细线分隔线、副标题和表格标题来组织信息。
|
||||
|
||||
## 2. 页面骨架- **画布比例**:横向宽屏报告页面,接近16:9;将其视为“可打印的年度报告页”,而不是舞台演示页。
|
||||
- **默认背景**:整个正文页面是暖白色,大约为`#FBF9ED`;纹理、渐变或图案通常不会分层到背景上。
|
||||
- **边距**:所有四个边都有稳定的空白;上边距有一个小章节/报告标记,下边距有页码和报告名称;主体作为多列网格插入。
|
||||
- **标题**:左上角通常显示三个短条/章节名称迷你导航; “年度报告 2023/24”样式的报告标记位于顶部中心或右上角;非常小且对比度低。
|
||||
- **页脚和页码**:小页码、页面名称或报告信息位于底角,呈黑灰色或浅灰色。页脚一定要轻,不能与正文争夺注意力。
|
||||
- **标题区**:标题位于左上角或左上角;正文页使用简短的报告式标题,而章节/目录页可能会覆盖照片或与其并排放置。数据或文字直接跟在标题后面,没有任何夸张的装饰。
|
||||
- **身体区域**:2-4 列网格。文本页面采用等宽多栏;数据页面将关键指标放在左侧,将解释/图表/表格放在右侧;宽表格占据页面的中下部分或右侧的较大区域。
|
||||
- **分隔线**:细线用于列之间、表格、脚注、度量区域、注释框和图表基线中;浅灰色/中灰色,重量有限。
|
||||
- **徽标**:身份标记(拼写报告名称的小纯文本;没有绘制图形徽标)主要出现在封面/封底或少数品牌位置。正文页不得在每一页上放大徽标——最多显示为极小的报告信息。
|
||||
- **导航**:目录页由章节标题、条目、页码组成;部分页面可以使用大图像+内容样式列表。正文页面仅保留浅色标题 - 没有彩色进度条。
|
||||
- **水印/背景图形**:正文页没有稳定的水印;圆形产品/品牌缩略图或照片仅作为元素出现在几页上。
|
||||
- **默认阅读路径**:左上角标题/章节和标题→左侧或第一正文列的关键指标→从左到右、从上到下→图表、表格注释和页脚来源。
|
||||
|
||||
页面类型骨架:
|
||||
- **封面**:整页或大面积的内部/产品照片,超大标题覆盖在中心稍偏左的位置,白色文本高对比度,底部有身份标记(小纯文本拼写报告名称;未绘制图形徽标)。
|
||||
- **内容页面**:左侧有一个垂直的摄影栏,带有重叠的标题,右侧的灰白色区域包含多栏内容列表;章节标题为粗体,条目较小,页码右对齐。
|
||||
- **身体报告页面**:灰白色背景,浅色页眉和页脚,左上角标题,多列文本/图表/表格。
|
||||
- **章节/注释页**:左侧是摄影或色调统一的图像,右侧是内容风格的条目列表。
|
||||
- **封底/特别结束页**:可以使用黑色背景和白色身份标记(小纯文本;不绘制图形徽标);这是一个特殊页面,不得应用于正文页面。
|
||||
|
||||
## 3. 内容组织- **标题如何书写**:简短、客观、报告风格——例如概述、展望、地区名称、治理主题、审计/注释标题。避免营销口号、长问句和过于情绪化的措辞。
|
||||
- **信息句子**:标题下可有 1-3 行描述或摘要;不要将大块的居中副本作为内容页的主体。
|
||||
- **正文组织**:使用短段落、副标题和必要的项目符号。较长的财务、治理和审计文本分为 2-4 栏;避免单列长行。
|
||||
- **分组逻辑**:按年度报告章节、管理回顾、战略展望、治理、财务、附注和审计进行组织;同一级别的信息采用统一的标题样式、栏宽、间距。
|
||||
- **每页观点数量**:常规商业页面包含一个主题 + 3-5 个小节/指标;纯粹的披露页面可能会添加更多文本,但每列都必须有副标题或段落分隔符。大字体的观点页面不是主流,切不可过度使用。
|
||||
- **如何书写要点**:使用破折号、点或不超过两层的缩进;使用空白和细线进行分组,并谨慎使用彩色图标。
|
||||
- **关键字强调**:使用粗体、位置、放大数字、金棕色或表格背景填充。不要使用荧光突出显示、堆叠下划线或多色强调。
|
||||
- **数字/单位/时间**:关键数字形成独立的块,并且大于其解释文字;单位紧贴数字或作为小标签位于其上方/下方。财务范围、百分比、丹麦克朗、年份等必须保留其定义和计量基础注释。
|
||||
- **来源脚注**:脚注、星号、括号注释和监管引文放置在底部或表格下方,设置小于正文文本,对比度低但可读。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 多栏报告正文页面
|
||||
|
||||
适用于审计意见、治理声明、政策说明、ESG 披露、财务说明和其他长文本。左上角有一个简短的标题;正文由 3-4 列等宽的文本组成,每列使用副标题+密集段落;列由空格或细线分隔,而不是颜色块。重点取决于粗体副标题、第一段位置和段落间距。如果内容需要强烈的视觉叙事、单个大视点或许多图像,请不要使用此布局。
|
||||
|
||||
### B. 公司/主题概述页面
|
||||
|
||||
适合“一目了然”式的摘要、业务构成、产品类别和渠道/市场概况。中间是圆环图或核心解释;关键数字、简短描述、类别标签或最小图形位于左侧和右侧。圆环图表达成分关系,不负责精确比较;当需要精确的趋势、排名或多维度比较时,可以使用柱形图/表格。
|
||||
|
||||
### C. 展望和假设页面
|
||||
|
||||
适合年度展望、目标范围、假设、敏感性和风险说明。左侧的垂直关键指标区域包含 2-4 个大数字/范围;右侧分为“假设”、“注释”、“敏感性”或小表格。金棕色用于目标范围或关键值;灰色填充的盒子带有附加条件。当没有明确的量化结论时,不要强行将内容强行放入 Outlook 页面中。
|
||||
|
||||
### D. 地区/业务评论页面
|
||||
|
||||
适合对地区、业务部门或业务线进行审核。区域名称和关键数据位于左侧,小柱形图/指标比较位于中间,右侧 2-3 列解释原因、行动和结果。切入点是地区名称和号码,而不是摄影。如果只有叙述而没有指标,请切换到多列正文页面。
|
||||
|
||||
### E. 宽表披露页面适用于财务报表、治理矩阵、董事会信息和薪酬/审计详细信息。简短的标题和描述位于顶部;横向宽工作台占用面积最大;必要时,左侧说明,右侧表格。键列、总行或标题使用灰色填充、粗体或深灰色规则。如果表被压缩到无法读取,则必须将其拆分为多个页面。
|
||||
|
||||
### F. 人员/治理卡页面
|
||||
|
||||
适用于董事、管理层和委员会的信息。使用等宽垂直卡片:顶部是黑白或低饱和度肖像,下面是姓名、角色、任期、职责和委员会信息,卡片之间有细垂直线。照片经过统一裁剪和灰度/低饱和度;没有彩色头像墙社交媒体风格。
|
||||
|
||||
### G. 摄影部分页面
|
||||
|
||||
适合封面、内容、注释和章节开头。内部/产品摄影占据左侧或整个页面,章节标题和内容列表位于右侧或覆盖区域。照片必须有低噪音区域来承载文字,其色调必须与灰白色/金棕色一致。正文披露页面不得依赖大图像背景。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- **纸背景灰白色 `#FBF9ED` / `#FEFCF0`**:正文页面的主要背景。不得用纯白色或高饱和度的浅色代替;迁移品牌时,可以用同样低饱和度的暖白色代替。
|
||||
- **主要文本黑灰色`#171717`–`#2B2B2B`**:标题、正文和表格的主要文本。浅灰色不得取代正文,以避免对比度低。
|
||||
- **辅助文本灰色`#5A5E6B`/`#7D7A72`**:页眉、页脚、注释、图表标签和辅助注释。用于降低层级;不应该用于长段落的主体。
|
||||
- **浅灰色分隔线 `#D9D7CB` / `#C6C4BB`**:列规则、表格网格线、图表轴和注释框边界。避免粗的纯黑线。
|
||||
- **表格/注释框灰色填充`#A09C93`,浅灰色填充`#E5E3D8`**:用于标题、摘要区域、敏感框或需要强调的表格;较深的灰色填充与浅色文本配对,浅灰色填充提供二次区分。不要大规模覆盖整个页面。
|
||||
- **品牌金棕色 `#B59D71` / `#AB8F62` / `#97754C`**:关键数字、圆环图强调、一些图表系列以及摄影色调的呼应;面积小到中等。它可能会迁移到品牌的主色,但必须保持低饱和度、暖色调,并与灰白色和谐。
|
||||
- **`#6F5435`**周围的深金色/棕黑色:仅用于金棕色系统内的更深层次或用于图表对比;谨慎使用。
|
||||
- **黑色特殊颜色`#000000`/深黑灰色**:封底或极少数封页的背景,搭配白色身份标记(小纯文本;未绘制图形徽标)/文本。正文页面禁止使用大的黑色背景。
|
||||
- **图表系列颜色**:使用金棕色、深灰色、浅灰色、黑灰色;通常每页不超过 2 个主要色调,根据亮度和透明度而变化。如果出现正负状态,首先用文字/符号和明暗对比来区分;除非数据合规性需要,否则不要引入红绿强对比度系统。
|
||||
|
||||
## 6. 版式和文本层次结构- **类型字符**:Caladea-Regular 和 Tahoma 是主流,Tahoma-Bold 用于副标题/部分强调;该组合可以用类似的衬线字体 + 人文主义者 sans 来复制。该字体不追求科技或手写的感觉。
|
||||
- **主体 = 1x**:甲板宽度基线;高密度、紧凑但可读;长文本使用常规粗细。
|
||||
- **脚注/标题/页码 = 0.75–0.9x**:报告名称、页码、注释、来源和监管引文,颜色较浅。
|
||||
- **副标题/表标题 = 1.2–1.6x**:章节内的小节、表标题、卡片名称或内容章节名称;可能会加粗。
|
||||
- **正文页标题 = 2–3x**:左上对齐,黑灰色;不需要夸张的装饰。
|
||||
- **章节/封面大标题 = 4–6x**:仅用于封面、内容摄影区和注释/章节开头页;可能是图像上覆盖的白色文本或灰白色区域中的黑色文本。
|
||||
- **关键数字 = 2.5–4x**:用于展望、概述和区域页面;单位和注释为 0.8-1x,与数字紧密相关。
|
||||
- **图表标签 = 0.8–1x**:轴、图例和数据标签小而清晰;关键的可以加粗或使用金棕色。
|
||||
- **表格文本= 0.8–1x**:可能比正文稍小,但绝不能太小而难以阅读;标题和摘要行可以加粗。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
主要是圆环图、小柱形图、表格数据和少量的度量区域——没有复杂的可视化系统。
|
||||
|
||||
- **圆环图**:用于成分份额或类别比例。置于页面中部或下部;金棕色标记关键类别,灰色/黑灰色标记其他类别。标签环绕在环上,线条很少;避免爆炸饼图、3D 饼图和过多的切片。
|
||||
- **柱形图**:用于区域或年度比较。条形狭窄而干净,层次分明,有金棕色、黑灰色和浅灰色;轴和网格线非常微弱。小柱形图可以证实指标,但不包含整个页面的视觉效果。
|
||||
- **公制区域**:大数字+小注释+单位/时间基础,通常在左侧;关键值可以使用金棕色。解释性文字必须与数字紧密结合——避免让数字孤立。
|
||||
- **轴和网格**:轴线、刻度线和网格线是低对比度的;图表框架尽可能保持明亮。
|
||||
- **图例**:放置在图表附近,带有小文本和简单的样本。传奇绝不能成为一条独立的彩色装饰条。
|
||||
- **注释**:预测、目标和基线使用细线、浅灰色框或小注释;不要使用大箭头、发光标记或夸张的标注。
|
||||
- **边界**:不要引入彩色信息图表、复杂地图、动画图形或拟物化仪表板。图表美观遵循数据清晰度和完整的定义、范围和计算基础。
|
||||
|
||||
## 8. 表格和高密度信息- **表角色**:作为财务、治理、董事会、薪酬、审计、ESG 指标等的精确披露和证据工具;它们可以承载页面的主体。
|
||||
- **标题**:使用浅灰色或中灰色填充和粗体文本;深灰色标题与浅色文本配对。高度紧凑,与数据行明显区分。
|
||||
- **行/列结构**:浅灰色细网格线;窄数字列和宽文本列。避免粗边框和过多的斑马条纹。
|
||||
- **行高**:高密度但不拥挤;长文本可能会换行,并且不得强制压缩为截断。
|
||||
- **关键行/列**:总计、关键年份、委员会隶属关系或状态列可以使用粗体、灰色填充或深色分隔线。不要用多种明亮的背景颜色来标记它们。
|
||||
- **数字对齐**:数字右对齐或按小数位/单位对齐,文本左对齐;百分比、货币和年份使用一致的格式和测量基础。
|
||||
- **符号标记**:可以使用低调的符号,例如小点、星号和复选标记/评级点;翅膀/符号字符只是辅助性的,不是彩色图标系统。
|
||||
- **斑马条纹**:如果使用,只有极浅的灰色/灰白色亮度差异。大的黑行填充保留用于标题或摘要区域。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
- **轻标题组件**:左上角迷你导航/三个短栏+章节名称,报告名称位于右上角或顶部;仅用于定向,不得加粗或放大。
|
||||
- **内容组件**:粗体章节标题、小条目、右对齐页码、排列在多列中;适合内容和备注页,并可与左侧摄影栏相结合。
|
||||
- **键数组件**:大数、单位、短注垂直组合;用于概述、展望和区域页面。使用黑灰色和金棕色,以空白作为入口点。
|
||||
- **细线分隔组件**:水平线、垂直线、表格网格和列规则建立顺序 - 它们不是装饰性的细工。
|
||||
- **灰色填充注释框**:用于假设、敏感性、脚注强化或本地表格注释;低饱和度填充,文字小,边界清晰。多个颜色鲜艳的警报箱不得堆叠。
|
||||
- **卡片/人物列**:顶部为肖像,下方为文字,列之间有细线;没有可见的阴影——边界由网格和空白定义。
|
||||
- **图像框架**:摄影使用矩形裁剪,边缘干净,没有圆角或阴影;用于封面、内容、章节页面和一些品牌/人物页面。
|
||||
- **图标**:只有少量的最小符号/社交标记/状态点。彩色图标、表情符号和插图不属于该系统。
|
||||
- **流程箭头/SmartArt**:不是稳定的主要组件。如果必须表达一个过程,请仅使用细线、编号和文本框;避免使用尺寸箭头和模板化 SmartArt。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
整体感觉保持平淡、内敛、纸质、饱和度低;圆角、阴影、渐变、发光、3D 和复杂的装饰并不是主要风格的证据。
|
||||
|
||||
**禁止:**
|
||||
- 正文页面上的大面积渐变、发光、3D、投影卡、玻璃形态或复杂纹理。
|
||||
- 彩虹图、荧光突出显示、表情符号、卡通插图和社交媒体风格的彩色图标。
|
||||
- 居中排版长段落,或用演示海报大小的巨型文本替换报告内容。
|
||||
- 反复放大正文页上的徽标,使用全版黑色背景,或用强烈的色块覆盖灰白色的纸系统。
|
||||
- 表格中的文本被截断、重叠或低对比度,或者为了修饰而牺牲数据定义或测量基础。**谨慎使用:**
|
||||
- 摄影:仅在封面、内容、章节、人物或品牌相关页面上;正文披露页面应谨慎使用它。
|
||||
- 金棕色强调:可用于关键数字和图表系列,但不要将其撒满整个页面。
|
||||
- 灰色填充框:用于注释/标题/摘要;不要将它们变成密集的卡墙。
|
||||
- 图标和符号:仅用于方向或状态支持;保持它们单色且较小。
|
||||
|
||||
**允许在特殊页面上使用:**
|
||||
- 封面可以使用带有大白色标题的全图像背景。
|
||||
- 封底可以使用黑色背景,带有白色身份标记(小纯文本;不绘制图形徽标)。
|
||||
- 章节开头/注释可以使用左侧的大照片+右侧的内容列表。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 骨架:是否为景观报告页面;灰白色背景占主导地位;页眉、页脚和页码是否轻量且对齐?
|
||||
- 文案:标题是否简短且类似于报告;正文是按副标题、段落和列表组织的;是否避免使用营销口号和大段居中文案?
|
||||
- 层次结构:body=1x 清晰;标题、关键数字、小标题和脚注是否按相对比例构建层次结构,而没有超出大小逻辑的夸张变化?
|
||||
- 颜色:每页控制在灰白色、黑灰色、浅灰色、金棕色左右;是否避免使用高饱和度的多色和纯装饰色?
|
||||
- 布局:使用 2-4 列网格;关键指标、文本、图表和表格的功能是否有明确定义;阅读路径从左上角到右下角是否流畅?
|
||||
- 图表:仅使用源样式的甜甜圈、小柱形图、度量区域和表格数据;轴、图例和标签是否低调且可读?
|
||||
- 表格:标题、网格、数字对齐、摘要行和脚注是否清晰;是否有截断、乱码、重叠、过密、对比度低等情况?
|
||||
- 组成部分:细线、灰色填充框、内容、关键号码和人员卡片是否提供信息;没有模板化的 SmartArt 或堆叠的彩色图标吗?
|
||||
- 禁止项目:禁止阴影、渐变、发光、3D、表情符号、复杂插图、社交媒体卡片样式或大型黑色背景正文页?
|
||||
- 完整性:检查是否存在空内容、遮挡、比例错误、低质量拉伸图像、页码错位、标题溢出或重复模板导致的无意义页面。
|
||||
- 默认情况下没有卡片:除非用户明确请求,否则切勿使用圆角矩形或矩形卡片来构建层次结构或对齐方式:线段、空白和字体大小差异是更好的解决方案
|
||||
- 没有均匀划分的构图:除非没有其他可用的布局,否则不要默认为三分之一、四分之一或二乘二矩阵。这包括三部分结构,例如三栏+标题+结论
|
||||
|
Before Width: | Height: | Size: 1023 KiB |
@@ -1,159 +0,0 @@
|
||||
#T035·2023年影响总结风格设计体系
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
适合面向政策制定者、投资者、合作伙伴、媒体和员工的长篇外部交流 PPT,例如上市公司影响摘要、责任报告、ESG/AI 治理、社会价值、技能推广和环境行动。它通过“可信的报告+人类故事+数据证据”来建立机构问责制,而不是销售路演式的视觉冲击。
|
||||
|
||||
内容页面允许 3-5 列文本、脚注链接、引文和小数据卡共存。核心视觉效果:“#FFF9F3”灰白色背景、Arial 无衬线正文、偶尔的 Times New Roman 衬线显示标题、彩色章节条/胶囊导航、真人或场景照片以及平面矩形数据卡。
|
||||
|
||||
基本原则:
|
||||
- **可信度胜过视觉表演**:用文字、来源、归属、照片和数据来说服;装饰遵循阅读——没有复杂的背景或运动感。
|
||||
- **章节颜色用于导航,而不是彩色显示**:每个主题都有一种主要颜色用于顶部条、当前导航项、数据卡或报价背景;避免多种高度饱和的颜色在同一页面上竞争。
|
||||
- **必须结构化**:用栏、细线、卡片、副标题和脚注分割内容;不要通过缩小文本或压缩行间距来堆积内容。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
- **画布比例**:横向宽屏 16:9。
|
||||
- **全局背景**:主页使用暖灰白色 `#FFF9F3` / `#FFFAF4`;不要将其更改为纯白色、深色或渐变背景。分区分隔页和某些视点页面可能会使用占据顶部 20%–30% 高度的纯色条。
|
||||
- **边距**:左上角起始边距相对较宽,外边距宽松,列间隙清晰;没有边到边排版;右上角保留用于页码或小符号的小空间。
|
||||
- **标题区**:
|
||||
- 章节/方法页面:顶部彩色条带有章节标题、简短描述和胶囊导航,左对齐。
|
||||
- 案例/观点页面:左侧或左上角有大标题,右侧或右半部分有照片;标题可以设置为衬线字体。
|
||||
- 常规内容页面:左上角标题,后跟多栏正文或卡片区域,间距清晰。
|
||||
- **正文区**:多栏正文文本、左/右分割、图文并置、嵌入数据卡;从左到右、从上到下阅读。
|
||||
- **标题导航**:顶部的圆形胶囊标签;当前章节使用填充色或高亮色,其他章节使用带有细轮廓的白色/灰白色底色。高度较低,因此不会窃取头衔等级。
|
||||
- **页脚和页码**:页码位于右上角或角落,处于极低的位置;封面/封底可以带有纯文本身份标记(写新主题名称;不绘制图形标志)和日期。内容页面不需要大徽标,也没有沉重的页脚栏。
|
||||
- **身份标记**:身份标记(拼写组织名称的小纯文本;未绘制图形徽标)主要出现在封面、封底或报告身份页上;在内容页面上,它应该小而安静——避免品牌水印式的重复。
|
||||
- **来源/链接**:放置在正文的末尾、卡片的底部或页面的下部,位于较小的位置,带有蓝色/紫色下划线。
|
||||
- **背景图形**:无持久的几何纹理、水印或装饰图案;依赖纯色、照片和矩形色块。封面可以使用方形瓷砖的彩色照片墙。
|
||||
|
||||
默认路径:顶部章节颜色和导航→左上角标题/问题句→分栏正文→右侧的照片、引言、数据卡和脚注链接。
|
||||
|
||||
## 3. 内容组织- **标题如何书写**:短句、问句或行动陈述,例如“我们如何工作”、“我们对负责任的人工智能的方法”、“我们如何保护基本权利?”;不要堆叠营销字幕。
|
||||
- **信息句**:一行上下文、一个人的名字或一个角色注释可以位于标题下方 - 补充上下文而不重复标题。
|
||||
- **正文组织**:以几个项目符号为主的报告段落;左对齐,列窄,段落之间间隙小。
|
||||
- **分组逻辑**:按承诺、行动、进展、案例和展望进行组织;并行模块通常有 3-5 个,每个模块包含一个副标题、正文、链接/源或一小段数据。
|
||||
- **每页观点数量**:高密度内容页可能包含3-5个平行主题;案例页面集中于一个问题、一张照片、一条引言或一项数据证据;大字体的观点页面只保留一个核心问题/立场。
|
||||
- **如何写要点**:副标题很短,正文使用完整的句子;不要把整页变成支离破碎的口号。项目符号应该短——避免多级缩进。
|
||||
- **关键字强调**:使用 Arial Bold 进行局部加粗;链接使用蓝色/紫色下划线。避免荧光笔效果、轮廓文本和彩虹式强调。
|
||||
- **数字定义及计量依据**:大数字卡片代表重要数字;该数字与其单位紧密结合——百万、十亿、货币符号、百分比——下面有一个解释性句子。请勿将不同定义或计量基准的数字混放在同一张号码卡中。
|
||||
- **时间和来源**:年份、报告期和来源链接使用小级别;脚注必须可读。报价需要姓名/标题/归属或支持照片。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 彩章条+多栏承诺页
|
||||
|
||||
用于方法、工作原则、年度优先事项和责任框架。顶部 20%–30% 是章节彩色条带,包含胶囊导航、标题和简短描述;下面是 4-5 个正文栏,每个栏都有副标题、段落、链接或脚注。重点来自当前导航项、突出显示的链接和本地粗体。请勿将其用于单故事页面或需要大图像情感影响的内容。
|
||||
|
||||
### B.灰白色多柱体+嵌入式数据卡
|
||||
|
||||
用于“行动+结果+投资”证据页面。正文为3-4栏文字,右下或中下嵌有1-2张彩色数据卡;每张卡片上都有一个超大的数字、一个简短的说明和一个小图标/标记。如果需要横向比较的数据点超过 3 个,请切换到表格或图表 - 不要在页面上塞满卡片。
|
||||
|
||||
### C. 左文本/右图像案例页面
|
||||
|
||||
用于有关人、社区、教育、环境和慈善事业的案例。左侧灰白色区域包含大标题/问题句、短段落、链接按钮或引用;右侧 40%–55% 包含一张真实照片,可以是全高的,也可以是大矩形的。照片必须与案件直接相关——不得使用不相关的库存图像。
|
||||
|
||||
### D. 观点问题页面/大衬线页面
|
||||
|
||||
用于章节过渡、核心问题和价值立场。衬线标题远大于正文且左对齐;它可能与短的彩色下划线或色块按钮配对,右侧有照片,只有少量说明。不要在此处放置 4 列正文或复杂的图表。
|
||||
|
||||
### E. 签名信/领导留言页
|
||||
|
||||
用于打开职位和来自领导者的消息。两列或三列长文本,搭配圆形肖像、姓名和头衔以及手写签名;大标题可能是衬线体,正文是 Arial。当没有签名、肖像或明确标识的发言者时,请勿应用此布局。
|
||||
|
||||
### F. 封面和封底身份页封面左侧有纯文本身份标记(写有组织名称;没有绘制图形徽标)、报告标题和日期,右侧是方形瓷砖的彩色照片墙;封底或关闭页以狭窄的照片墙和链接/报告名称关闭。仅用于打开和关闭页面 - 不在正文内重复复制。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- **背景纸颜色`#FFF9F3`/`#FFFAF4`**:整个甲板的主要背景。不得用纯黑色或浓灰色代替;在印刷中,它可能会接近白色,但必须保持其暖色调。
|
||||
- **主要文本黑色`#131323`/`#1E171F`**:正文、标题和表格文本的主要颜色。浅灰色不得取代正文。
|
||||
- **中性灰色`#9E9E9F`、`#C5BFBB`、`#EDE8E4`**:细线分隔线、表格规则、次要注释、照片边界和弱背景;它们不携带主要信息。
|
||||
- **紫色章节`#6F2572`**:AI/责任章节的顶条、当前导航、引言或强调卡;在正文中,它仅显示为链接、标签或数据强调。更换时,保持足够的深度和白字对比度。
|
||||
- **黄色章`#FFB901`**:顶部条带、数据卡和技能/学习章节的右侧强调块;与黑色文本配对——避免在黄色上使用低对比度的白色文本。
|
||||
- **浅紫色 `#C6B3E4` / `#C5B3E3`**:报价框、辅助数据卡和软强调区;它带有黑色或深紫色文本,不作为主体文本的基础而大规模传播。
|
||||
- **蓝色`#0978DE`**:链接、少量图表系列、强调标记或封面照片中的品牌颜色;面积小。
|
||||
- **绿色/青色`#6D7A62`、`#B8DCD2`、`#152C27`**:环境或可持续发展章节、数据卡和照片回声;深青色卡底与浅色文字搭配,浅青色作为辅助底色。
|
||||
- **橙红色系列(靠近`#FF5A3D`)**:章节条、标签或强调线;每页使用一种原色,不要让它同时与黄色和紫色大规模竞争。
|
||||
|
||||
一页只能有1章原色+灰白+黑灰;数据卡可添加1种辅助颜色。不要凭空发明红绿上/下系统。品牌颜色可能会取代章节颜色,但“顶部条/导航/卡片”角色保持不变。
|
||||
|
||||
## 6. 版式和文本层次结构
|
||||
|
||||
- **类型字符**:正文以Arial字体为主;标题和观点页面有时会使用 Times New Roman。请勿将装饰字体或手写字体混合到正文中。
|
||||
- **正文**:1x — 多列段落、表格注释和案例文本的基线。
|
||||
- **副标题/栏标题**:大约 1.1–1.3x,通常为 Arial 粗体。
|
||||
- **页面标题**:常规内容页面约 2–3x;章节标题可以达到 3-4x,左对齐。
|
||||
- **衬线显示标题**:Times New Roman 标题可能达到 3.5–5 倍,行间距紧凑但不重叠。
|
||||
- **数据数量**:约 4–6x;单位按数字分组; 0.8–1x 的解释性文字。
|
||||
- **信息句/属性**:约 0.9–1x。
|
||||
- **脚注/链接/页码**:约 0.55–0.75x;页码极其薄弱,脚注可读。
|
||||
- **强调方法**:更喜欢粗体、色块背景和链接颜色;不要使用轮廓文本、阴影文本、渐变文本或大块居中排版。
|
||||
|
||||
## 7. 图表语言图表保持简单,以数据卡、小表格和少量简化图形为主。
|
||||
- **类型边界**:适合小型柱形图/条形图、进度条、简单比较和数据标签卡;不适合复杂的仪表板、雷达图、3D 饼图或多轴财务图表。
|
||||
- **轴和网格**:网格线极其微弱,呈中性灰色;轴不再被强调——避免粗的黑色轴线。当可以直接标记值时,图例不是必需的。
|
||||
- **系列颜色**:当前章节主色+浅灰/浅辅色;单个图表不应超过 3-4 种颜色。
|
||||
- **数据标签**:关键值位于图形附近或卡片上;关键数据使用章节颜色或超大数字。
|
||||
- **基线/目标线**:使用带有短标注的细线 - 避免红色警报式的粗线。
|
||||
- **图例**:小,接近图表,带有简单的样本;不要构建大型独立图例区域。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
- **标题**:副标题级和粗体;背景为灰白色、浅灰色或浅色章色调。避免深色标题主导视觉效果。
|
||||
- **行/列结构**:浅灰色细线,以空白和细线分隔;行高仍然可读,而不是压缩成密集数据库的感觉。
|
||||
- **背景填充**:大面积的灰白色;关键单元可以用浅紫色、黄色、浅绿色或蓝色卡片化。
|
||||
- **数字对齐**:数字右对齐或按小数点/单位统一,文本左对齐;大量的卡可能会成为独立的卡。
|
||||
- **关键行/列**:使用小章节颜色标签、浅色填充或粗体;避免用高度饱和的颜色填充整行。
|
||||
- **斑马条纹**:不是一个强有力的规则;如果使用,只有极浅的灰色/灰白色差异。
|
||||
- **符号和评级**:可以使用小点、破折号、复选标记或短颜色条,但不得发展成复杂的图标系统。
|
||||
- **溢出控制**:宁愿分成多列或两页,也不要让文本接触边缘、脚注重叠或表格覆盖页码。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
- **胶囊导航**:顶部有小圆形标签;当前章节用章节颜色填充或突出显示,其他章节有白色/灰白色底色和细轮廓。文本非常短——它不能成为次要内容列表。
|
||||
- **章节色条**:顶部约页面高度的五分之一到三分之一,带有标题、描述和导航。请勿在同一页面上堆叠多个大色条。
|
||||
- **数据卡**:长方形或略呈圆形,填充有黄色、浅紫色、绿色、蓝色等;包含大量数字、简短说明和小标记。适合 1-2 个核心数 - 不适合堆积成九个方格。
|
||||
- **引号框**:浅紫色、灰白色或章节色块,包含引号、短句和姓名/肖像/签名;他们必须有一个来源。
|
||||
- **相框**:长方形裁剪的真实照片,往往占据右侧大片区域,可能会用色块拼接。照片没有厚边框、阴影或复杂的蒙版。
|
||||
- **链接按钮/源栏**:小的彩色圆角栏或带下划线的文本,靠近相关正文文本 - 从不浮动。
|
||||
- **分隔线**:细灰色线或短彩色线,用于列之间、用于强调标题以及表格边界;线条应该很轻,并且不能形成笼状网格。
|
||||
- **肖像和签名**:用于领导信息和引言页面;不得在普通数据页上过度使用。
|
||||
- **图标**:只有一些小标记/简单符号;它们不得扩展为大型彩色图标集合;它们必须支持标签,并且不能单独表达复杂的概念。
|
||||
|
||||
## 10. 质地和违禁物品整体感觉扁平、内敛、类似报告——排版、色块、照片和数据——不依赖视觉效果。
|
||||
|
||||
**禁止:**
|
||||
- 大面积渐变背景、发光、3D、拟物按钮、厚阴影、玻璃形态和复杂纹理。
|
||||
- 表情符号、卡通插图、无源装饰图标和社交媒体海报风格的大型居中标语。
|
||||
- 将大量正文直接压到全图像背景上,或使用低对比度浅灰色正文。
|
||||
- 多个高度饱和的章节颜色并列在同一页面上,争夺主视觉。
|
||||
|
||||
**谨慎使用:**
|
||||
- 圆角:导航胶囊、按钮、数据卡可能会略微呈圆角;大型容器不应全部变成圆形卡片。
|
||||
- 照片:应该是真实人物/场景的证据;没有与内容无关的上演库存镜头。
|
||||
- 彩色图标:仅作为极小的辅助标记,无法成为主要视觉系统。
|
||||
- 表格/图表:仅在真正需要进行数据比较时使用,保留细线和直接标签。
|
||||
|
||||
**允许在特殊页面上使用:**
|
||||
- 封面/封底可以使用方形瓷砖的彩色照片墙或大图像拼贴画。
|
||||
- 观点页面可以使用超大衬线标题、短彩色下划线和半页照片。
|
||||
- 领导留言页可以使用肖像、签名和长文本排版。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 是否是16:9的暖灰白色报告地面,没有错误地使用纯白色/深色/渐变背景?
|
||||
- 页面是否有清晰的左上角标题、正文阅读路径以及足够的外边距和栏间距?
|
||||
- 部分分隔页是否使用带有胶囊导航的单个章节颜色条,并且当前项目是否突出显示?
|
||||
- 文案风格是短标题+报告段落/平行小标题,而不是堆叠的营销口号?
|
||||
- 正文文本是否基于 Arial 1x,标题、编号和脚注级别相对清晰且不受绝对字体大小依赖性?
|
||||
- 单页原色是否控制为 1 章颜色 + 中性底色,并限制链接/数据强调?
|
||||
- 多栏体是否有碰边、重叠、截断、乱码、行距过紧、对比度低等现象?
|
||||
- 照片是否真实、相关且经过矩形裁剪,没有厚重的阴影或复杂的遮罩?
|
||||
- 数据卡是否仅突出显示几个关键数字,并且数字/单位/解释完整?
|
||||
- 图表是否是浅网格、弱轴、直接标记、没有 3D、多轴或太多系列颜色?
|
||||
- 表格是否细线且填充较少,文本左对齐/数字均匀对齐,没有过多填充?
|
||||
- 组件是否来自系统——胶囊导航、章节条、数据卡、报价框、相框、细线分隔线——而不是新添加的装饰?
|
||||
- 是否避免使用表情符号、复杂的插图、发光、渐变、厚阴影、大块居中正文文本和空内容占位符?
|
||||
- 封面、正文、章节、案例和结束页是否有所不同,但仍保持相同的报告框架?
|
||||
- 默认情况下没有卡片:除非用户明确请求,否则切勿使用圆角矩形或矩形卡片来构建层次结构或对齐方式:线段、空白和字体大小差异是更好的解决方案
|
||||
- 没有均匀划分的构图:除非没有其他可用的布局,否则不要默认为三分之一、四分之一或二乘二矩阵。这包括三部分结构,例如三栏+标题+结论
|
||||
|
Before Width: | Height: | Size: 897 KiB |
@@ -1,147 +0,0 @@
|
||||
#C007·2023年组织名称影响报告重点介绍风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
适用于企业影响报告、ESG/可持续发展摘要以及技术产品和环境数据叙述,面向投资者、媒体、政策/行业观察者和内部管理层。信息密度中等至稍高,在“可读报告页面”和“沉浸式摄影页面”之间交替。核心视觉效果:近乎白色的背景,黑色/灰色的无衬线文本,极细的浅灰色线条,低饱和度的绿色点缀,真实的汽车/工厂/自然/人群摄影,极简的装饰,宽敞的空白。
|
||||
|
||||
基本原则:
|
||||
- **可信度高于装饰**:页面应该感觉像是一份正式的报告,而不是营销海报;要素必须服务于论证、数据定义和测量基础,或者真实场景的证据。
|
||||
- **空白过度填充**:当信息太多时,分页、使用列或使用表格 - 不要缩小字体大小或塞入组件。
|
||||
- **绿色仅用于积极/环境/数据强调**:它不得扩展为大的装饰背景颜色或无意义的突出显示。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
- **画布比例**:宽屏 16:9 横向。内容页面大多是近白色/白色背景;封面、章节过渡和结束页可以使用全出血深色摄影或黑色背景。
|
||||
- **边距系统**:所有四个边都有宽边距 - 内容永远不会触及边缘;标题、正文、图表和照片都有清晰的外部边界。照片可能接近页面宽度的一半,但必须保留喘息空间。
|
||||
- **标题区**:位于左上或左上中,使用黑色/深灰色的简短短语或完整的判断句;接下来是 1-2 段浅灰色的解释。标题区域不使用彩色条、粗体框架或大背景面板。
|
||||
- **正文区域**:承载照片、图表、指标组、产品关系图或多栏文本。常见结构:左侧文本/右侧图像、左侧图像/右侧文本、顶部标题/下方图表、中心关系图或水平度量卡。
|
||||
- **标题**:在最顶部,一个非常小的浅灰色报告名称/部分名称/辅助导航左对齐或居中 - 没有突出的导航栏。
|
||||
- **页脚和页码**:页码通常位于右上角或边缘,呈现极小且浅灰色;脚注/来源位于图表底部或附近。
|
||||
- **徽标**:品牌徽标不会在每个页面上以大徽标形式重复出现。
|
||||
- **封面/章节页**:全出血或大比例摄影,文本覆盖在左下/左侧。封面可以将大标题和年份分开;部分页面可以使用覆盖有白色文本的深色照片。
|
||||
- **分隔线**:仅在图表轴、表格、度量卡或文本列之间使用,使用极细的浅灰色线条。禁止使用粗线、彩色水平条和装饰性边框。
|
||||
- **水印/背景图形**:默认情况下,不使用抽象水印、纹理或几何背景;黑暗的气氛来自于摄影或纯黑色的背景。
|
||||
- **默认阅读路径**:左上角标题→潜台词/度量解释→右侧或下方证据区域→脚注来源。
|
||||
|
||||
## 3. 内容组织- **标题风格**:使用独立的结论句、动作句或论文句,例如“产品产生了一定的影响”或“一项行动减少了一定的足迹”。避免模糊的口号和文字堆叠。
|
||||
- **关键信息句子**:在标题下方,使用比标题浅的颜色对背景、测量基础或因果关系进行简短的报告式解释。
|
||||
- **正文组织**:使用短段落、2-3 个文本栏或少量项目符号点;较长的政策/解释性内容可以分成具有较大行距和列间隙的列。
|
||||
- **每页点数**:每页一个核心点,最多辅以一张照片、一张图表或一组指标。多个子点使用水平卡片、列或表格,而不是分散放置。
|
||||
- **项目符号样式**:谨慎使用传统的项目符号列表;喜欢短段落、小标题、公制卡和表格单元格;当必须使用子弹时,标志物应小、轻、克制。
|
||||
- **关键字强调**:使用大数字、暗/亮对比度、低饱和度绿色和空白。请勿在正文中使用霓虹灯突出显示、下划线或多色强调。
|
||||
- **数字/单位/时间范围**:关键数字可以放大并单独显示;单位直接跟在数字后面或显示为小标签。百分比、吨位、里程、年数等必须保留其定义和计量基准注释;时间表或年份应与数据图表一致。
|
||||
- **来源脚注**:数据页必须包含一个非常小的浅灰色脚注或来源注释,放置在图表底部或下方;它只是补充测量基础,并不承载主要叙述。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. Text-left/image-right 或 image-left/text-right 类型
|
||||
适用于产品场景、制造设施、能源设施以及涉及人员或地点的证据。文本侧包含标题、1-2 个解释段落、脚注或小状态点;图片一侧包含一张真实照片,约占页面视觉宽度的 40%–55%。图像是矩形或略呈圆形,裁剪干净。文字给出结论;照片提供了现场证据。不适合多组数据比较、流程复杂或正文较长。
|
||||
|
||||
### B. 大图像沉浸式
|
||||
适用于封面、章节页面、主题过渡、宣言式结尾和情绪页面。照片填满整个页面或大部分画布,文字覆盖在左下/左侧;必要时将照片调暗以确保白色文本仍然可读。可以使用更大的标题和年份/章节信息。不适合大量数字、表格或细粒度图表。
|
||||
|
||||
### C.标题+水平度量组类型
|
||||
用于气候影响、排放份额、累积结果和效率指标等摘要。将标题和简要说明放在顶部或左上角,下面水平排列 4-6 个指标;每个都包含一个大数字和一个小单元/解释,由极浅的背景或细线分隔。用薄荷绿突出显示关键数字,用深灰色或浅灰色突出显示其他数字。当度量定义或测量基础差异太大或解释性文本太长时不应使用。
|
||||
|
||||
### D. 图表参数类型
|
||||
对于单一定量参数,例如趋势变化、累积影响、避免量或效率下降。标题位于左上角或顶部,图表占据下半部分或右侧的大部分区域。使用浅灰色网格、极细的轴线、很少的系列颜色、直接标注;绿色趋势线/基线表达了核心结论。不要使用多色商业仪表板式图表;当系列超过 2-3 个时,拆分页面或简化。### E. 中心关系/产品替代图类型
|
||||
用于产品生态系统、替代关系和能源系统组成。围绕中心节点、小型产品图像或简化的对象构建,通过极细的浅灰色线条连接;文字标签很小,背景是白色的。依靠中心位置、空白和连接关系来强调——不要使用彩色图标。当节点过多、交叉复杂或流程步骤较多时不宜使用。
|
||||
|
||||
### F. 多列报告文本类型
|
||||
适用于政策解释、供应链、道德、劳动力、材料等面向阅读的内容。标题位于左上角,正文分为 2-3 列,列宽和列距保持平衡;可以在侧面或底部添加一张小照片。使用副标题或绿色数字来提取关键点——不要使用大的背景色块。不适合演示场景或快速扫描页面。
|
||||
|
||||
### G.表格/高密度比较型
|
||||
用于结构化比较,例如车辆型号、指标、区域或项目状态。标题位于顶部,表格居中或占据下方较大区域;用浅灰色线条和浅色背景进行组织,谨慎使用粗边框。仅突出显示关键列、摘要行或状态点。如果说明性文本超出了表格本身,请切换到多列文本页。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- **主要背景白色/近白色**:#FAF9F7、#F8F8F8、#F5F6F6、#F1F2F2。用于大部分内容页面,占用面积最大。不得用彩色背景代替;品牌只能稍微调整白色的暖色/冷色。
|
||||
- **主要文本黑色/灰色**:#111111、#111215、#23272E、#262F35。用于标题、关键数字、轴标签和正文强调;标题接近黑色,正文可能是深灰色。不得用彩色标题替换。
|
||||
- **辅助文本灰色**:#64726D、#93A299、#ABB3B1、#BCC8C5。用于字幕、段落、脚注、弱标签、轴文本;一定不能轻到消失。
|
||||
- **分隔线/网格灰色**:#EDEFEE、#ECEDED、#D6DAD9、#C6C9C9。用于图表网格、表格线、卡片分隔线和浅色背景;对比度必须较低,以避免沉重的、类似桌子的感觉。
|
||||
- **环境强调绿色/薄荷绿**:#95BFA9、#BBE6D1、#65A889、#2E9B73。用于关键百分比、环境指标、趋势线、积极状态点和小标签。中小面积;不用作整页背景色或普通装饰。
|
||||
- **深色照片/黑色背景页面**:#060608、#0D1708、#121C25、#202611、#404720。用于夜景、森林、工厂、暗化照片、沉浸式页面和宣言页面;文本使用白色/浅灰色。
|
||||
- **自然照片颜色**:森林绿、道路黑、天蓝灰色、工厂灰和车辆白/黑/红来自真实摄影,不会提升为成分颜色。照片颜色不得任意提取为图表系列颜色。
|
||||
- **图表系列颜色边界**:单页主色调保持在黑/灰+绿色范围内;对于多个系列,请使用不同的灰色亮度级别加上一种绿色强调色。避免使用多颜色图例,例如红色/橙色/紫色/蓝色,除非原始数据状态确实需要区分。
|
||||
- **积极/消极状态**:积极/改善使用绿色;负面/风险默认为深灰色文本或注释——谨慎引入红色。
|
||||
|
||||
## 6. 字体和文本层次结构
|
||||
|
||||
使用现代的无衬线字体,给人干净、科技、报告般的感觉,与组织或工业技术品牌的视觉基调一致。不得使用衬线字体、手写字体、卡通字体或装饰字体。- **正文 = 1x**:段落说明和表格正文,深灰色或中灰色,行距相对宽松。
|
||||
- **标题 = 2.5–4x**:页面的要点,黑色/深灰色,中等至轻微粗体;封面/章节页可能会更大。
|
||||
- **关键信息/副标题 = 1.1–1.4x**:位于标题下方,颜色比标题浅。
|
||||
- **副标题/组标题 = 1.1–1.6x**:用在表格、图表标题和列引言上方,权重稍强。
|
||||
- **关键数字 = 3–6x**:与单位和小标签配对;不需要额外的粗体框架。
|
||||
- **图表标签/表格文本 = 0.8–1x**:清晰但弱于正文标题;图表轴标签可能更轻。
|
||||
- **注释/脚注/页码 = 0.5–0.7x**:极小,浅灰色,靠近边缘。
|
||||
- **文本布局**:文本较多或数字的页面大多左对齐;避免大的中心块。标题换行符遵循语义划分。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
使用报告式的简洁折线图、条形图/小指标图、份额指标和少量关系图 - 无业务仪表板。
|
||||
|
||||
- **图表类型**:线趋势、简单的水平/垂直条、百分比度量卡、单个基线或比较线。关系图使用小产品图像+细连接线。
|
||||
- **轴**:极细的轴线,浅灰色或深灰色,不透明度低;很少的刻度线,避免密集的鳞片。
|
||||
- **网格线**:极浅的细灰色线,仅用于帮助读取值,不形成网格背景。
|
||||
- **标签**:更喜欢直接标记关键值或在数据点附近放置小标签,减少图例;使用灰色、深灰色和绿色来强调。
|
||||
- **系列颜色**:核心系列使用薄荷/绿线,对比系列使用浅灰色或深灰色。不要在同一图表中使用大量高饱和度的颜色。
|
||||
- **关键数据**:通过绿线、放大的数字、局部粗体或旁注进行强调。不要使用爆炸形状、发光或粗箭头。
|
||||
- **预测/目标/基线**:使用细虚线或浅灰色线,并在线条附近添加小的说明文字。不要添加梯度预测带。
|
||||
- **图表边界**:保留充足的空白和清晰的标题。当需要长图例、多层过滤器或复杂的交互解释时,这种风格不适合。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
- **标题**:使用浅灰色文本或稍重的字体,背景接近白色/极浅灰色。标题不使用带有反白文本的深色背景。
|
||||
- **行/列结构**:用浅灰色细线分隔,削弱外边框,并在列之间留出充足的空白以避免拥挤。
|
||||
- **行高**:比较宽松;即使是高密度的页面也不应该被压缩成密集的财务报表式网格。
|
||||
- **基色**:极浅灰色的块可以区分标题、摘要或卡片区域;如果使用斑马条纹,则必须非常微妙。
|
||||
- **关键行/列**:用绿色数字、小状态点、浅色背景或粗体标记。避免用浓烈的颜色填充整行。
|
||||
- **数字和文本对齐**:文本左对齐;数字按列右对齐或按小数位对齐;百分比、吨位、数量等的单位、定义和计量基础是一致的。
|
||||
- **符号标记**:可以使用小点、短划线或极简状态符号;不要使用色彩丰富、复杂的图标。
|
||||
- **页面角色**:表格上方必须有清晰的观点标题,下方可能有脚注解释测量基础。
|
||||
|
||||
## 9. 组件和图形元素- **标题栏/标题微导航**:顶部边缘的极小的浅灰色报告名称、部分名称或页码,仅用于定向。
|
||||
- **公制卡**:由大数字、小标签、浅色背景或薄分隔线组成;适用于3-6个并行指标;绿色仅用于关键数字或正值。
|
||||
- **小状态点/徽章**:小绿点或简化标记,与脚注、解释或积极状态配对;尺寸小——一定不能发展成一个大的图标系统。
|
||||
- **相框**:真正的摄影使用矩形或略圆形的框架,剪裁干净;圆角应该小而现代。照片没有添加粗边框或阴影。
|
||||
- **提示/说明块**:与浅灰色背景或空白分组,通过使用浅灰色来减少文本的视觉重量。不得使用彩色警告框或气泡对话框。
|
||||
- **分隔线**:用于表格、图表、列分隔和关系连接的极细浅灰色线。所有线条都保持轻量级——没有粗箭头或粗笔画。
|
||||
- **流程/关系箭头**:使用细线、淡箭头或节点连接来表达替代关系或流程;优先减少节点和线路交叉。
|
||||
- **产品图像/小对象**:使用真实的产品渲染或裁剪的照片对象作为节点,优先于通用图标。彩色插图风格的图标不应取代它们。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
总体而言,保持平面、克制、类似报告,并由真实摄影驱动,通过布局和照片创建页面变化。
|
||||
|
||||
**禁止:**
|
||||
- 大渐变背景、发光、3D 装饰、玻璃形态、复杂纹理、霓虹灯线。
|
||||
- 卡通插图、表情符号、社交媒体贴纸、夸张的彩色图标、手绘箭头。
|
||||
- 厚重的边框、强烈的阴影、厚重的彩色标题栏、全页品牌色背景。
|
||||
- 大量居中的副本、没有来源的大量数字、没有明确定义或测量基础的修饰数据。
|
||||
- 多色图例堆叠、仪表板式复杂图表、覆盖在照片明亮区域上的低对比度文本。
|
||||
|
||||
**谨慎使用:**
|
||||
- 圆角:仅适用于相框或灯卡,半径较小——不得变得卡通化。
|
||||
- 阴影:源文件显示没有稳定的阴影系统;默认情况下不使用;如果需要图像分层,请保持极其微妙。
|
||||
- 彩色车辆/照片颜色:可能来自真实摄影,但不得扩展到页面的颜色系统。
|
||||
- 深色页面:仅用于封面、章节、结束语或强烈的情感过渡 - 不应广泛用于内容数据页面。
|
||||
|
||||
**可在特殊页面上使用:**
|
||||
- 全出血照片背景,覆盖白色文本,用于封面/章节/结束页。
|
||||
- 黑色背景宣言页,用于最终摘要或强烈的观点陈述。
|
||||
- 高密度多栏文本页面,适合报告阅读场景,但必须保持清晰的栏间距和脚注。
|
||||
|
||||
## 11. 一代清单- 是 16:9 横向,内容页面默认为接近白色的背景和宽边距吗?
|
||||
- 标题是否是一个清晰的观点句子,位于左上/左阅读起点?
|
||||
- 每一页是否仅包含一个核心点,证据区域仅选择照片、图表、指标组或表格之一作为主要元素?
|
||||
- 正文是否以短段落/列的形式组织,避免大的居中块和密集的列表?
|
||||
- 颜色是否保持在黑/灰/白+低饱和度绿色范围内,而不引入毫无根据的多色系统?
|
||||
- 绿色是否仅用于强调环境、积极或关键数据,并有限制区域?
|
||||
- 图表是否使用细轴线、光网格、少量系列和直接标签,以避免仪表板的感觉?
|
||||
- 表格是否使用细线、浅色背景和宽大的行高,重点仅限于绿色数字或浅色背景标记?
|
||||
- 照片是否真实、裁剪清晰、没有粗边框或阴影、文本覆盖的地方有足够的对比度吗?
|
||||
- 页眉、页码和脚注是否小而轻,不会成为突出的装饰?
|
||||
- 是否避免使用渐变、发光、3D、复杂的插图、表情符号、强烈的阴影和粗边框?
|
||||
- 是否检查过溢出、截断、乱码、重叠、低对比度、遮挡、内容空、比例错误、机械模板重复等情况?
|
||||
- 默认情况下没有卡片:除非用户明确请求,否则严格禁止使用圆角矩形或矩形卡片来构建层次结构或对齐方式:线条、空白和字体大小差异是更好的解决方案
|
||||
- 无均分组合:除非没有其他可用的布局,否则不要默认三向分割、四向分割或 2×2 矩阵。这包括三部分结论,例如三栏+标题+结论
|
||||
|
Before Width: | Height: | Size: 2.1 MiB |
@@ -1,176 +0,0 @@
|
||||
# 组织名称·业务报告风格设计系统
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
一份旨在咨询公司雇主品牌、人才吸引、企业文化和组织能力信息传递的景观 PPT。受众是外部候选人、员工或一般商业人群;它通过少量的事实、奖项、地图、照片和强有力的口号传达了一个可信的、全球性的、以人为本的品牌形象。
|
||||
|
||||
- **每页一篇论文,证据为论文服务**:每一页仅设置一个明确的标题或结论;照片、地图、奖项、标志、图表或三联画组件都围绕着这个结论——不要让多个点相互竞争。
|
||||
- **红色用于品牌基调设置和结论着陆点**:它可以用于封面、过渡、标题带、底部结论栏或图标核心,而不会分裂成许多小装饰点。
|
||||
- **可读性优于装饰**:黑白/灰度视觉效果、白色背景信息区域和 Verdana 粗体大标题形成高对比度;照片、手写文本、徽章和图表不得压倒文本结论。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
- **画布比例**:横向宽屏呈现比例;以全出血风景构图为主,白底内容页、红底标语页、黑白照片页和混合拼贴页之间交替。
|
||||
- **边距系统**:红条、照片和封面对角切割色块可能会接近画布边缘;正文、徽标墙、地图和徽章组保持清晰的外边距。底部的红色条通常跨越大部分宽度,包含结论。
|
||||
- **标题区域**:
|
||||
- 顶部红色水平栏,带有白色、全部大写、粗体标题,用于部分/主题。
|
||||
- 白色背景左上角的大红色标题,用于意见或愿景页面。
|
||||
- 特殊标语页面使用居中或底部的大白色文本,而不依赖于传统的标题。
|
||||
- **身体区域**:使用黑白照片、地图、奖章、徽标墙、圆形三联画、简单的折线/面积图或大量数字;单个页面仅保留一个主要视觉逻辑。
|
||||
- **页眉/页脚**:少数咨询内容页面可能会在底部边缘放置极小的保密声明、文档说明或页脚信息;通信页面通常没有复杂的标题导航。
|
||||
- **页码**:没有明确的页码系统;新页面可能会省略它们,或者仅将它们放在较弱的脚注级别。
|
||||
- **徽标**:品牌徽标用于封面和闭页等身份页面。结束页是红色背景,居中的白色徽标和小手写风格的标语;徽标不会在每页上重复和放大。
|
||||
- **导航/水印**:没有稳定的目录导航、章节进度条或水印系统 - 不应强制添加任何内容。
|
||||
- **分隔线和背景图形**:使用实心红色矩形、对角切割红色块、黑色水平带和圆形裁剪;不要使用复杂的线框背景、渐变纹理或抽象装饰网络。
|
||||
- **默认阅读路径**:首先阅读红色标题带或大标语,然后查看主要视觉证据,然后阅读小注释/脚注。在图像左/文本右页面上,首先查看照片,然后阅读右侧的项目符号和粗体关键字。
|
||||
|
||||
## 3. 内容组织- **标题风格**:主要是简洁、面向动作的动词或名词短语,通常在适当的时候全部大写。一般为1行,最多2行;避免长句子、问答式标题和堆叠的多层字幕。
|
||||
- **关键信息句子**:手写风格的文本可能带有简短的标签、引用风格的口号或小组名称,例如“顶尖商业人才的最佳职业”或“结果故事”,而不是正式的长正文。
|
||||
- **正文组织**:左对齐项目符号,每页 3-5 项,每项跨 1-3 行;关键字使用 Verdana-Bold 进行局部粗体显示,而不依赖于彩色突出显示。
|
||||
- **分组逻辑**:三向分组用于平行证据、奖项或结果类别;双向分组用于照片+文字、红块+照片或标题+证明;标志墙是按视觉重量排列的,而不是严格的网格。
|
||||
- **每页点数**:默认1个要点+1个证据类别;复杂的页面可能有 2-4 个证据点,但它们必须全部围绕相同的结论。不要同时放置愿景、指标、流程、案例和多个图表。
|
||||
- **数字和单位**:关键数字采用超大粗体文字,与结论短语组合成“数量+对象+范围”;可以放置在底部红色栏或页面中心。
|
||||
- **来源脚注**:保密声明、来源和注释放置在图表底部或附近,使用极小的文本,仅解释来源和限制,而不会扩展到业务叙述。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 左图像/右文本简介页面
|
||||
|
||||
了解机构事实、能力范围或背景信息。左侧放置一张黑白或低饱和度照片;右侧为白色背景,包含手写风格的小标题和项目符号列表,正文为黑色,关键词加粗。适合中等量的文字;不适合复杂的表格、多个图表或太多的小图标。
|
||||
|
||||
### B. 地图+底部红色结论栏
|
||||
|
||||
适用于全球覆盖、办事处或区域分布。正文是浅灰色点阵/线画地图,位置用红点和细线标记,城市标签用黑色/灰色小文字表示;底部的红色条带有大量白色文本结论。不适合精确的地理分析或密集的路线解释。
|
||||
|
||||
### C. 红底大标语页
|
||||
|
||||
用于章节转换、主题重点或价值主张。大型纯红色背景,大型白色 Verdana-Bold 文本居中或偏心,可能与大符号或短语配对。仅表达一篇论文;项目符号、长段落或复杂的图表不得分层。
|
||||
|
||||
### D.白底意见+红块/照片拼贴页
|
||||
|
||||
人才愿景、文化表述和情感表达。顶部白底红色大标题;下面,红色矩形文本块和黑白肖像照片并排放置。红色块带有手写风格的白色文本或短语;不适合细粒度的数据解释。
|
||||
|
||||
### E. 奖章/标志证据墙页
|
||||
|
||||
寻求外部认可、合作伙伴或认可。主要是白色背景,顶部或底部有一个红色条表明主题;徽章/徽标排列成 2-3 行,尺寸根据品牌视觉重量进行微调。文字解释很少,保留空白。不适合冗长的解释或强制徽标大小相同,导致拥挤。
|
||||
|
||||
### F.三圈平行证据页
|
||||
|
||||
对于三类结果、三种挑战或三种类型的认可。三个圆形视觉对象水平均匀划分,顶部有手写式标签;圆圈可能包含黑白照片、红色图形图标或灰度肖像。最多可容纳三件物品;超过三个应切换到列表或分组墙。
|
||||
|
||||
### G.图表+水平强调带页面对于少量的趋势数据。顶部有简短的标题式解释,以简洁的折线/面积图作为正文,覆盖黑色水平带或手写式白色文字结论。图表作为证据,波段作为解释;当数据系列过多或需要精确读数时请勿使用。
|
||||
|
||||
### H.黑白照片标语页
|
||||
|
||||
用于文化陈述或情感终结。照片填充了主体或大部分框架,大的粗体白色全大写文本放置在黑暗区域上,可能与底部红色条配对。必须保证照片足够暗,文字对比度足够;白色文本不得直接位于明亮区域上方。
|
||||
|
||||
### 一、文字+底部红色结论页
|
||||
|
||||
用于支持系统、原则列表或行动承诺。顶部或左侧有红色粗体标题,旁边有项目符号;底部的红条承载着最终的承诺。用于将支持点汇聚成一个口号;不适合列出超过 5-6 个子项目。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- **背景白色:#FFFFFF**
|
||||
- 用作内容页面、证据墙、地图页面、徽章页面、图表页面和大多数信息页面的主要背景。
|
||||
- 不要添加暖色纸张纹理、浅色渐变或复杂的背景图案;切不可随意更换品牌浅色。
|
||||
|
||||
- **品牌原色红色:#CC0001 / #C00000 / #CE2222**
|
||||
- 用于封面覆盖、全页红色背景、标题栏、底部结论栏、关键图形和红色文本标题。
|
||||
- 可用作小强调或整个页面;过渡/封底可能会以它为主,而内容页面通常会在 10%–30% 的区域中使用它。
|
||||
- 不与其他高饱和度的原色竞争;不要用于长正文。
|
||||
- 迁移品牌时,可能会被新品牌的单一基色取代,但必须保持高饱和度、强对比度和一致的使用。
|
||||
|
||||
- **黑色主要文本:#111111 / #1B1B1B / #292929**
|
||||
- 用于照片页面上的正文、项目符号、标签、图表文本和深色文本。
|
||||
- 避免在正文中使用浅灰色;黑色文本仅放置在白色或浅灰色背景上;不建议使用彩色正文文本。
|
||||
|
||||
- **中性灰色:#4B4B4B / #747474 / #B6B5B5 / #D7D7D7**
|
||||
- 用于黑白照片、地图背景纹理、图表坐标、城市标签和分隔线辅助元素。
|
||||
- 可在照片和地图页面上大面积使用;灰度应该只是起辅助作用——它不应该使整个页面对比度低,也不应该变成彩色插图背景。
|
||||
|
||||
- **黑色口音带:#222222 / #2A2A2A**
|
||||
- 用于图表页面上的水平解释带和一些照片文本叠加层,带有白色手写风格/结论文本。
|
||||
- 不要将其用作与红条并列的同等级别的标题,以避免竞争焦点。
|
||||
|
||||
- **证据绿色:#53951A / #306937**
|
||||
- 仅用作奖章的真正来源颜色——它是外部认证的原始颜色。
|
||||
- 不是全局强调色;不得扩展为图表的主要颜色或标题颜色。
|
||||
|
||||
- **图表/状态颜色边界**:单个页面的主色调通常只有红色加黑/白/灰;徽章/徽标可以保留其原始颜色,但不得进一步扩展。正面/负面状态更喜欢文本、箭头和红色强调,而不是多色交通灯系统。
|
||||
|
||||
## 6. 字体和文本层次结构- **字体字符**:主要是 Verdana 和 Verdana-Bold;少量的手写风格字体用于情感标签,而不是作为正文字体。
|
||||
- **标题层次结构**:主要标题使用 Verdana-Bold,全部大写,大约是正文大小的 2.5–4 倍;红色背景使用白色文字,白色背景通常使用红色文字;行距很紧。
|
||||
- **标语/大量层次结构**:封面、过渡、地图结论和照片标语可能达到正文大小的 3-5 倍,并且必须简短且粗体。
|
||||
- **关键信息/副标题**:大约是正文大小的 1.2–1.8 倍,可以是常规或粗体,不得压倒主标题。
|
||||
- **正文项目符号**:正文 = 1x,主要是 Verdana 常规字体,关键短语以相同大小的 Verdana-Bold 粗体显示;项目符号标记很简单,压痕很浅。
|
||||
- **副标题/组标签**:手写风格字体或 Verdana-Bold,大约是正文大小的 0.9–1.3 倍;手写式标签必须简短。
|
||||
- **表格/图表标签**:大约是正文大小的 0.55–0.8 倍,黑色/灰色,不得与主标题竞争注意力。
|
||||
- **注释、脚注、保密声明**:约正文大小的0.25-0.35倍,置于底部,灰黑色,视觉重量极低。
|
||||
- **页码**:如果存在,则与脚注处于同一级别,不构成视觉元素。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
- **图表类型**:使用单一趋势图,或叠加折线图/面积图,来证明“结果随时间变化”;不要构建复杂的多图表仪表板。
|
||||
- **轴和网格线**:轴很细,灰黑色;网格线变弱或极少;轴标签很小。
|
||||
- **系列颜色**:数据主体使用红色区域/线条,必要时搭配黑色/灰色基线;不要引入多个系列颜色。
|
||||
- **关键数据**:用红色数据区域、峰值标记或水平黑色解释带进行强调;避免出现大量标注气泡。
|
||||
- **图例**:尽可能省略或保持极少;当只有一两个数据类别时,直接在图表中进行标记。
|
||||
- **预测/目标/基线**:没有稳定的预测线系统;如果需要,请使用细灰色或黑色线 - 不要使用华丽的虚线。
|
||||
- **注释语言**:手写字体或白字横带可以表达简短的结论;图表仅作为证据,而不是完整的叙述。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
没有传统的大表系统;高密度信息主要通过项目符号列表、标志墙、城市标记地图和徽章收藏来承载。
|
||||
|
||||
- **表格使用边界**:当必须使用表格时,保留白色背景、黑色/灰色文本和细灰色分隔线;红色仅用于标题或摘要结论,没有粗边框或多色背景。
|
||||
- **标题**:可以使用红色背景和白色文本,或黑色/灰色粗体文本;保持紧凑,避免装饰性渐变。
|
||||
- **行和列**:行高优先考虑可读性,列数不宜过多;文本左对齐,数字右对齐或按小数点对齐。
|
||||
- **分隔线**:使用浅灰色细线;不要使用厚重的黑色网格或许多垂直线。
|
||||
- **基色**:默认白色背景;极浅的灰色可以区分群体;不建议使用过于强烈的斑马条纹。
|
||||
- **关键行/列**:使用红色条、红色标题文本或粗体突出显示摘要 - 不要用红色填充表格的许多部分。
|
||||
- **符号/评级点**:没有稳定的评级系统;如果需要,限制为黑色、灰色和红色,并搭配文字说明。
|
||||
- **页面作用**:表格只能作为证据模块——它不应该取代一页一篇论文的结构。
|
||||
|
||||
## 9. 组件和图形元素- **红色标题带**:一个水平矩形,带有白色、全大写、粗体标题,放置在顶部或底部;用于陈述主题或结论,不用于普通的章节装饰。
|
||||
- **底部红色结论栏**:用于地图、奖励和支持系统页面,持有大量或一个承诺;作为阅读终点,保留为简短、高对比度的短语。
|
||||
- **对角切割红色盖块**:覆盖左侧的红色块,沿对角边缘切入照片;适合封面/身份页,而不是在每个页面上重复。
|
||||
- **黑白相框**:灰度肖像或场景照片,可以是全出血矩形、半页或圆形裁剪,服务于红黑白品牌对比。
|
||||
- **圆形裁剪组件**:用于三向平行证据,可以容纳照片、头像或红色图形符号;间距均匀,没有添加粗轮廓。
|
||||
- **手写式标签**:用于群组名称、情感短语和图表水平带标题;仅用于简短短语——不能代替正式标题和正文。
|
||||
- **徽章/徽标墙**:保留外部徽标的原始形式,排列在红色主题栏周围;标志尺寸根据视觉重量进行调整。
|
||||
- **简单的图形图标**:包括红色箭头、网格和扁平、简化的符号,例如人物/地球仪;图标必须大、少且概念清晰;避免彩色图标集。
|
||||
- **地图标注**:灰图底+红点+细线+城市标签;不适合复杂的地理数据编码。
|
||||
- **分隔线和叠加**:使用黑色水平带、红色矩形和照片的深色区域;不要使用阴影卡或玻璃形态。
|
||||
|
||||
## 10. 质地和违禁物品
|
||||
|
||||
保持平面、高对比度和咨询风格的克制;装饰仅来自色块、照片裁剪、圆形构图和字体层次结构。
|
||||
|
||||
- **禁止**:
|
||||
- 大型彩色插图、表情符号、3D 图形、发光、霓虹灯、复杂纹理、玻璃形态、拟物化按钮。
|
||||
- 多色图标集、彩虹式图表、精美的渐变背景。
|
||||
- 大的居中文本块、填充红色背景的长段落、低对比度的灰色正文。
|
||||
- 每个页面上重复出现的大徽标、复杂的导航栏、社交媒体卡片式贴纸装饰。
|
||||
|
||||
- **谨慎使用**:
|
||||
- 阴影:源文件几乎不依赖阴影;除了外部徽章已经固有的效果之外,不要主动添加它们。
|
||||
- 圆角:可以使用圆形裁剪,但圆角卡角不是主要样式。
|
||||
- 彩色标志/徽章:仅以其原始形式保留作为外部证据;未扩展到页面的配色方案中。
|
||||
- 手写式字体:仅用于简短标签和情感标语;过度使用会削弱咨询感。
|
||||
- 大面积照片:必须是黑白或低饱和度,保证文字可读性。
|
||||
|
||||
- **可在特殊页面上使用**:
|
||||
- 全版红色背景:用于过渡、封底和强烈的口号,不用于承载信息密集的正文。
|
||||
- 全出血黑白照片:用于文化陈述或情感终结,搭配大白色文本或红色结论栏。
|
||||
- 对角切割的红色盖块:用于在开口处建立身份;不应成为所有内容页面的模板。
|
||||
|
||||
## 11. 一代清单- 骨架:是横向宽屏吗?是否保留白色背景/红条/黑白照片结构;是否没有强制目录导航或水印。
|
||||
- 副本:每页是否有一篇论文;标题是否简短、直接且倾向于全部大写;正文是项目符号,关键字是粗体而不是随机颜色。
|
||||
- 层次结构:主标题、口号/编号、正文、标签、脚注是否形成清晰的相对比例;是避免使用小而难以阅读的文本的关键信息。
|
||||
- 颜色:红色+黑色/白色/灰色为主调色板;外部标志/徽章颜色是否仅限于证据区域;多种原色之间不存在竞争吗?
|
||||
- 布局:是根据内容选择的真实布局——图左/文字右、地图+结论栏、红底标语、三圈平行、标志墙、图表+条带等;避免混合多个模板。
|
||||
- 图表:是否简洁、系列少、网格弱、红色强调;促销页面避免成为复杂的仪表板。
|
||||
- 表格:如果使用表格,是否为白色背景,带有细灰色线,红色仅突出显示标题/摘要;没有厚重的网格或多色背景吗?
|
||||
- 组成部分:红色标题带、底部结论栏、圆形裁剪、手写标签和黑白照片是否具有信息功能,而不是堆积起来作为装饰。
|
||||
- 禁止物品:是否没有表情符号、3D、发光、复杂渐变、彩色图标集、玻璃形态或大型居中副本。
|
||||
- 质量:检查文本溢出、截断、乱码、重叠、低对比度、照片遮挡文本、空占位符、拉伸比例、扭曲徽标以及模板重复造成的单调节奏。
|
||||
|
Before Width: | Height: | Size: 298 KiB |
@@ -1,123 +0,0 @@
|
||||
#文化拼贴·文化拼贴风格设计系统
|
||||
|
||||
> 预期主页标签:促销
|
||||
> 视觉隐喻:当代文化海报+触觉社论拼贴
|
||||
|
||||
## 1.风格定位
|
||||
|
||||
适用于文化节庆、城市活动、品牌合作、展会招募、社区推广、合作伙伴邀请等。这些页面感觉就像一系列可以不断翻阅的当代文化海报:大胆的排版、裁剪的图像、纸片色块、胶印编号和手工制作的纹理共同构建了气氛,同时仍然可以快速找到信息。
|
||||
|
||||
基本原则:
|
||||
|
||||
1. 每个页面首先营造出清晰的感觉,然后让日期、地点、节目和行动自然地就位。
|
||||
2. 拼贴画必须围绕主题、路线或节奏进行组织——不能是一堆随机的贴纸。
|
||||
3、字体、色块、图像比例必须形成清晰的层次;关键事实不能隐藏在艺术效果中。
|
||||
4、单个页面最多使用一种原色和一种对比色——避免将“青春”等同于全彩。
|
||||
5. 当事件是虚构概念时,仅使用原始抽象图像、许可资产或诚实的占位符 - 不要盗用现有的品牌视觉效果。
|
||||
|
||||
## 2. 页面骨架
|
||||
|
||||
16:9 风景。元素可能已控制出血和偏移,但所有关键信息必须保留在安全区域内。背景在乳白色、墨黑色和单一高饱和度颜色之间切换。
|
||||
|
||||
页面不固定传统的标题栏。标题可以垂直、跨栏或靠近主图像,但每一页仍然需要一个清晰的第一阅读点。日期、页码和路线编号作为稳定的导航,固定在边缘或角落。
|
||||
|
||||
封面就像主海报一样:活动名称占据画面的 35%–55%,搭配一张原创主视觉图和一张具体邀请函。身体在节目拼贴、路线海报、时间表条、票务信息和合作伙伴动作之间形成快慢节奏。
|
||||
|
||||
## 3. 内容组织
|
||||
|
||||
标题使用观众可以大声说出的邀请或声明,例如“重新聆听这座城市的七个夜晚”。字幕添加日期、地点或参与方式。
|
||||
|
||||
每页最多包含一个主要叙述和 3-5 个基本事实。使用短句、日期、动词和直接标签——不要使用长的品牌宣言。
|
||||
|
||||
内容的组织方式是“被吸引——了解体验——选择路线——计划参与——检查参与要求或资格——立即行动”。组织者角度的制作说明、促销策略和设计评论不得出现在面向观众的页面上。
|
||||
|
||||
## 4.内容页面布局系统
|
||||
|
||||
### A. 海报主页面
|
||||
|
||||
活动名称、日期和一句邀请句构成了大字体,主视觉占据了一半以上。信息稀疏而清晰——不要在封面上列出每个项目和合作伙伴。
|
||||
|
||||
### B. 程序拼贴页面
|
||||
|
||||
形成一组 3-5 个不同比例的图像或色块,并将节目计数和类别以大数字形式嵌入到构图中。每个类别仅保留其名称、计数和一个代表性操作。
|
||||
|
||||
### C. 主题路线页面
|
||||
|
||||
将3条主题路线呈现为3条不同方向的视觉轨迹,每一条都有一个体验描述、主要节目以及适合的受众。三个路线不必是等宽的卡片,但它们必须具有可比性。
|
||||
|
||||
### D. 城市时间表页面
|
||||
|
||||
使用水平的 7 天节奏带或四区域地图来组织日期和场地。通过放大字体大小或使用裁剪色块来突出显示关键会议 - 不要使用密集的日历表。
|
||||
|
||||
### E. 参与信息页面
|
||||
|
||||
门票价格、免费入场比例、容量以及如何登记都围绕一张主图排列。数字必须清晰——旋转、纹理或遮挡不得牺牲其可读性。
|
||||
|
||||
### F. 合作伙伴和行动页面左侧说明合作伙伴可以参加哪些活动;右侧给出了促销资源、截止日期和明确的联系行动。结尾必须是具体的动词,例如“注册/提交/联系/参加”——没有含糊的感谢语。
|
||||
|
||||
## 5. 颜色系统
|
||||
|
||||
- 奶油色纸#F3E8D4:浅色主背景。
|
||||
- 墨水黑色 #171717:标题、正文和深色页面。
|
||||
- 橙色海报#F05A38:主要邀请函、日期和能量焦点。
|
||||
- 群青#2557D6:路线、夜间内容和对比信息。
|
||||
- 酸性石灰黄#D9F04A:极少量的高光、贴纸和操作提示。
|
||||
- 粉色雾#E8A7B3:辅助纸片和柔和的背景。
|
||||
- 纸灰色#B8B0A4:标题、页码和纹理。
|
||||
|
||||
页面以奶油色纸/黑色为底色,仅选择橙色或群青色作为主色;酸性石灰黄占页面面积不足 5%。彩虹渐变色、紫粉色霓虹色以及多种均匀分布的高饱和度颜色都是被禁止的。
|
||||
|
||||
## 6. 字体和文本层次结构
|
||||
|
||||
中文将粗体 sans 与中性 sans 配对;英语可以使用紧凑的 grotesk sans-serif;必要时,一个衬线字体可以作为非常短的文化脚注。最多三个字体角色。
|
||||
|
||||
- 活动名称:6–9x 正文;可以跨行,但不能分割主词。
|
||||
- 页面标题:2.5–4x 正文。
|
||||
- 日期和关键数字:3–6x 正文。
|
||||
- 程序标签:1–1.3x 正文。
|
||||
- 描述和票务:1x;必须保持舒适的可读性。
|
||||
- 页码和脚注:0.6–0.75x。
|
||||
|
||||
少量文本可以旋转 90 度作为导航,但正文、价格、日期和操作信息绝不能旋转。不要使用手写风格的字体来承载关键信息。
|
||||
|
||||
## 7. 图表语言
|
||||
|
||||
促销页面默认不使用商业图表。定量关系优选通过大数字、比例条、程序矩阵、时间节奏和区域分布来表达。
|
||||
|
||||
当需要比较时,使用直接标记的单系列条形图、堆叠条形图或气泡图,没有多余的轴。数据图形应融入海报布局,但值必须保持精确。
|
||||
|
||||
不要使用 3D 饼图、仪表板、复杂的雷达图或不包含任何信息的波形装饰。
|
||||
|
||||
## 8. 表格和高密度信息
|
||||
|
||||
时间表和合作伙伴信息使用编辑风格的列表:宽大的行间距、清晰的日期、地点和项目名称 - 没有 Excel 风格的完整边框。
|
||||
|
||||
关键会议可以用橙色海报块或粗黑色下划线来强调;其他项目保持黑白。超过 10 个条目的完整时间表不应缩小到塞满一页 - 而是显示可选择的主线程和注册条目。
|
||||
|
||||
门票价格和容量必须以大字号的正常方向显示。合作伙伴的利益并未纳入定价卡墙。
|
||||
|
||||
## 9. 组件和图形元素
|
||||
|
||||
稳定的组件包括裁剪的相框、撕边的纸片、套印不准、路线箭头、日期戳、超大数字、短下划线和小星芒。
|
||||
|
||||
每页最多使用两种纹理技术。撕裂的边缘、纹理和套准不准只是轻微的触觉触摸,不得降低文本或照片的清晰度。
|
||||
|
||||
主要视觉效果可能包括原创抽象摄影、物体特写、纸雕或几何结构。如果有人出现,他们必须来自可用的资产,而不是捏造的身份、引言或故事。
|
||||
|
||||
## 10. 材质和违禁物品
|
||||
|
||||
整体应该感觉像是来自一个独立文化机构的完整视觉形象:有触觉、有节奏、有明确的邀请。反模板的感觉来自构图和规模,而不是故意的混乱。禁止:随机贴纸包、整页撕纸、堆叠复古滤镜、紫蓝色霓虹灯、表情符号阵列、社交媒体 UI 模型、圆角卡片墙、不相关的潮流人物、被盗的品牌海报、遮挡关键信息、无法参与的心情。
|
||||
|
||||
## 11. 一代清单
|
||||
|
||||
- 封面是否在 3 秒内传达了活动名称、日期和诉求?
|
||||
- 这六页是否形成了从吸引到实际参与的连续路径?
|
||||
- 拼贴画中的每个图像、色块和数字是否都服务于主题或导航?
|
||||
- 日期、地点、价格、容量和行动是否完整且可读?
|
||||
- 每页是否都保留单一原色,而不会变成彩虹海报?
|
||||
- 是否使用原创/可用的视觉效果,而不盗用现有品牌或编造人们的故事?
|
||||
- 是否避免使用卡片墙、社交用户界面、随机贴纸和模糊的品牌宣言?
|
||||
- 是否检查了出血安全、文本遮挡、旋转误用、重叠、裁剪和乱码文本?
|
||||
|
||||
- 默认情况下没有卡片:除非用户明确请求,否则严格禁止使用圆角矩形或矩形卡片来构建层次结构或对齐方式:线段、空白和字体大小差异是更好的解决方案
|
||||
- 没有均匀划分的构图:除非没有其他可用的布局,否则不要默认为三分之一、四分之一或二乘二矩阵。这包括三部分结论,例如三向分割加标题加结论
|
||||