本文由
Mermaid中文文档
整理而来,并且它同时提供了一个
Mermaid在线编辑器
,支持在线编辑与生成流程图。
在文章的末尾我们将介绍如何使用AI来自动生成 Mermaid 流程图。

Mermaid 流程图 - 基本语法

流程图由
节点
(几何形状)和

(箭头或线)组成。Mermaid 代码定义了如何创建节点和边,并适应不同的箭头类型、多方向箭头,以及与子图的链接。

如果在流程图节点中使用“end”这个词,整个词或其中的字母必须大写(例如“End”或“END”)。在小写字母中输入“end”将会导致流程图出现问题。
如果在连接的流程图节点中使用“o”或“x”作为首字母,请在前面加一个空格或将该字母大写(例如“dev--- ops”、“dev---Ops”)。

输入“A---oB”将创建一个[circle edge](#circle-edge-example)。

输入“A---xB”将创建一个[cross edge](#cross-edge-example)。

节点

---
title: 节点
---
flowchart LR
    id

id是显示在框中的内容。
除了`flowchart`,也可以使用`graph`。

带文本的节点

也可以设置与id不同的文本。 如果多次进行此操作,最后找到的文本将用于该节点。如果稍后为该节点定义边时,您可以省略文本定义。先前定义的文本将在渲染框时使用。

---
title: 带文本的节点
---
flowchart LR
    id1[这是框中的文本]

Unicode文本

使用
"
将unicode文本括起来。

flowchart LR
    id["这是 ❤ Unicode"]

Markdown格式化

使用双引号和反引号 "` text `" 将Markdown文本括起来。

%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
    markdown["`这一行 **是** _Markdown_`"]
    newLines["`Line 1
    Line 2
    Line 3`"]
    markdown --> newLines

方向

此语句声明了流程图的方向。

这声明了流程图从上到下定向(
TD

TB
)。

flowchart TD
    Start --> Stop

这声明了流程图从左到右定向(
LR
)。

flowchart LR
    Start --> Stop

可能的流程图方向:

  • TB - 从上到下
  • TD - 从上到下/与从上到下相同
  • BT - 从下到上
  • RL - 从右到左
  • LR - 从左到右

节点形状

带圆边的节点

flowchart LR
    id1(这是框中的文本)

体育馆形状的节点

flowchart LR
    id1([这是框中的文本])

子程序形状的节点

flowchart LR
    id1[[这是框中的文本]]

圆柱形状的节点

flowchart LR
    id1[(数据库)]

圆形的节点

flowchart LR
    id1((这是圆形中的文本))

不对称形状的节点

flowchart LR
    id1>这是框中的文本]

目前只有上面的形状可能,而不是其镜像形状。
这可能会在未来的版本中改变。

菱形节点

flowchart LR
    id1{这是框中的文本}

六边形节点

flowchart LR
    id1{{这是框中的文本}}

平行四边形

flowchart TD
    id1[/这是框中的文本/]

反平行四边形

flowchart TD
    id1[\这是框中的文本\]

梯形

flowchart TD
    A[/圣诞节\]

反梯形

flowchart TD
    B[\去购物/]

双圈

flowchart TD
    id1(((这是圈中的文本)))

Mermaid流程图中的扩展节点形状(v11.3.0+)

Mermaid引入30种新形状,以增强流程图创建的灵活性和精确性。这些新形状提供了更多选项,用于在流程图中可视化地表示过程、决策、事件、数据存储以及其他元素,提高了清晰度和语义意义。

新的形状定义语法

Mermaid现在支持一种通用语法来定义形状类型,以适应不断增长的形状数量。此语法允许您使用清晰和灵活的格式将特定形状分配给节点:

A@{ shape: rect }

此语法创建节点A为矩形。其渲染方式与
A["A"]

A
相同。

新形状的示例流程图

以下是利用一些新引入的形状的示例流程图:

flowchart RL
    A@{ shape: manual-file, label: "文件处理"}
    B@{ shape: manual-input, label: "用户输入"}
    C@{ shape: docs, label: "多个文档"}
    D@{ shape: procs, label: "过程自动化"}
    E@{ shape: paper-tape, label: "纸质记录"}

过程

flowchart TD
    A@{ shape: rect, label: "这是一个过程" }

事件

flowchart TD
    A@{ shape: rounded, label: "这是一个事件" }

终点(体育馆)

flowchart TD
    A@{ shape: stadium, label: "终点" }

子过程

flowchart TD
    A@{ shape: subproc, label: "这是一个子过程" }

数据库(圆柱)

flowchart TD
    A@{ shape: cyl, label: "数据库" }

开始(圆形)

flowchart TD
    A@{ shape: circle, label: "开始" }

不对称形状

flowchart TD
    A@{ shape: odd, label: "不对称形状" }

决策(菱形)

flowchart TD
    A@{ shape: diamond, label: "决策" }

准备条件(六边形)

flowchart TD
    A@{ shape: hex, label: "准备条件" }

数据输入/输出(倾斜右)

flowchart TD
    A@{ shape: lean-r, label: "输入/输出" }

数据输入/输出(倾斜左)

flowchart TD
    A@{ shape: lean-l, label: "输出/输入" }

优先操作(底部梯形)

flowchart TD
    A@{ shape: trap-b, label: "优先操作" }

手动操作(顶部梯形)

flowchart TD
    A@{ shape: trap-t, label: "手动操作" }

停止(双圈)

flowchart TD
    A@{ shape: dbl-circ, label: "停止" }

文本块

flowchart TD
    A@{ shape: text, label: "这是一个文本块" }

这是一个文本块

卡片(缺口矩形)

flowchart TD
    A@{ shape: notch-rect, label: "卡片" }

有线/阴影过程

flowchart TD
    A@{ shape: lin-rect, label: "有线过程" }

开始(小圆形)

flowchart TD
    A@{ shape: sm-circ, label: "小开始" }

停止(框架圆形)

flowchart TD
    A@{ shape: framed-circle, label: "停止" }

分叉/合并(长矩形)

flowchart TD
    A@{ shape: fork, label: "分叉或合并" }

整理(沙漏)

flowchart TD
    A@{ shape: hourglass, label: "整理" }

注释(大括号)

flowchart TD
    A@{ shape: comment, label: "注释" }

右侧注释(右大括号)

flowchart TD
    A@{ shape: brace-r, label: "注释" }

两侧都带大括号的注释

flowchart TD
    A@{ shape: braces, label: "注释" }

通信链接(闪电)

flowchart TD
    A@{ shape: bolt, label: "通信链接" }

文档

flowchart TD
    A@{ shape: doc, label: "文档" }

延迟(半圆角矩形)

flowchart TD
    A@{ shape: delay, label: "延迟" }

直接存储(横向圆柱)

flowchart TD
    A@{ shape: das, label: "直接访问存储" }

磁盘存储(有线圆柱)

flowchart TD
    A@{ shape: lin-cyl, label: "磁盘存储" }

显示(曲线梯形)

flowchart TD
    A@{ shape: curv-trap, label: "显示" }

分割过程(分割矩形)

flowchart TD
    A@{ shape: div-rect, label: "分割过程" }

提取(小三角形)

flowchart TD
    A@{ shape: tri, label: "提取" }

内部存储(窗格)

flowchart TD
    A@{ shape: win-pane, label: "内部存储" }

交点(填充圆形)

flowchart TD
    A@{ shape: f-circ, label: "交点" }

有线文档

flowchart TD
    A@{ shape: lin-doc, label: "有线文档" }

循环限制(缺口五边形)

flowchart TD
    A@{ shape: notch-pent, label: "循环限制" }

手动文件(翻转三角形)

flowchart TD
    A@{ shape: flip-tri, label: "手动文件" }

手动输入(倾斜矩形)

flowchart TD
    A@{ shape: sl-rect, label: "手动输入" }

多文档(堆叠文档)

flowchart TD
    A@{ shape: docs, label: "多个文档" }

多过程(堆叠矩形)

flowchart TD
    A@{ shape: processes, label: "多个过程" }

纸带(标志)

flowchart TD
    A@{ shape: flag, label: "纸带" }

存储数据(领结矩形)

flowchart TD
    A@{ shape: bow-rect, label: "存储数据" }

总结(交叉圆形)

flowchart TD
    A@{ shape: cross-circ, label: "总结" }

标记文档

flowchart TD
    A@{ shape: tag-doc, label: "标记文档" }

标记过程(标记矩形)

flowchart TD
    A@{ shape: tag-rect, label: "标记过程" }

Mermaid流程图中的特殊形状(v11.3.0+)

Mermaid还引入了2种特殊形状,以增强您的流程图:
图标

图像
。这些形状允许您直接在流程图中包含图标和图像,提供更多视觉上下文和清晰度。

图标形状

您可以使用
icon
形状在流程图中包含一个图标。要使用图标,您需要先注册图标包。请按照
此处
提供的说明进行操作。定义图标形状的语法如下:

flowchart TD
    A@{ icon: "fa:user", form: "square", label: "用户图标", pos: "t", h: 60 }

参数

  • icon
    : 注册图标包中图标的名称。
  • form
    : 指定图标的背景形状。如果未定义,则图标没有背景。可选项包括:
    • square
    • circle
    • rounded
  • label
    : 与图标关联的文本标签。这可以是任何字符串。如果未定义,则不会显示标签。
  • pos
    : 标签的位置。如果未定义,标签默认为在图标的底部。可选值包括:
    • t
    • b
  • h
    : 图标的高度。如果未定义,默认为48,这是最小值。

图像形状

您可以使用
image
形状在流程图中包含图像。定义图像形状的语法如下:

flowchart TD
    A@{ img: "https://example.com/image.png", label: "图像标签", pos: "t", w: 60, h: 60, constraint: "off" }

参数

  • img
    : 要显示的图像的URL。
  • label
    : 与图像关联的文本标签。这可以是任何字符串。如果未定义,则不会显示标签。
  • pos
    : 标签的位置。如果未定义,标签默认为图像的底部。可选值包括:
    • t
    • b
  • w
    : 图像的宽度。如果未定义,默认为图像的自然宽度。
  • h
    : 图像的高度。如果未定义,默认为图像的自然高度。
  • constraint
    : 决定图像是否应该限制节点大小。此设置还确保图像保持其原始纵横比,相应地调整高度(
    h
    )以适应宽度(
    w
    )。如果未定义,默认为
    off
    ,可选值为:
    • on
    • off

这些新形状为您的流程图提供额外的灵活性和视觉吸引力,使其更具信息性和吸引力。

节点之间的链接

节点可以用链接/边连接。可以使用不同类型的链接或附加文本字符串到链接。

带箭头的链接

flowchart LR
    A-->B

开放链接

flowchart LR
    A --- B

链接上的文本

flowchart LR
    A-- 这是文本! ---B

flowchart LR
    A---|这是文本|B

带箭头和文本的链接

flowchart LR
    A-->|文本|B


flowchart LR
    A-- 文本 -->B

虚线链接

flowchart LR
   A-.->B;

带文本的虚线链接

flowchart LR
   A-. 文本 .-> B

粗线链接

flowchart LR
   A ==> B

带文本的粗线链接

flowchart LR
   A == 文本 ==> B

无形链接

这在某些情况下是一个有用的工具,当你想更改节点的默认位置时。

flowchart LR
    A ~~~ B

链式连接

可以在同一行中声明多个链接,如下所示:

flowchart LR
   A -- 文本 --> B -- 文本2 --> C

也可以在同一行中声明多个节点链接,如下所示:

flowchart LR
   a --> b & c--> d


您可以以非常清晰的方式描述依赖关系。就像下面的一行代码:

flowchart TB
    A & B--> C & D

如果您使用基本语法描述相同的图形,将需要四行代码。值得警告的是,人们可能在Markdown表单中过度使用此功能,使流程图更难阅读。瑞典词“lagom”浮现在我的脑海中。它的意思是,不太多也不少。对于表达式语法也是如此。

flowchart TB
    A --> C
    A --> D
    B --> C
    B --> D

新箭头类型

现在支持新的箭头类型:

  • 圆形边
  • 交叉边

圆形边示例

flowchart LR
    A --o B

交叉边示例

flowchart LR
    A --x B

多方向箭头

可以使用多方向箭头。

flowchart LR
    A o--o B
    B <--> C
    C x--x D

链接的最小长度

流程图中的每个节点最终在呈现图中分配到一个等级,即垂直或横向水平(根据流程图的定向),基于与其链接的节点。默认情况下,链接可以跨越任意数量的等级,但您可以要求任何链接比其他链接更长,通过在链接定义中添加额外的破折号。

在下面的例子中,链接从节点_B_到节点_E_添加了两个额外的破折号,因此它跨度比常规链接多两个等级:

flowchart TD
    A[开始] --> B{是否是?}
    B -->|是| C[好的]
    C --> D[重新考虑]
    D --> B
    B ---->|否| E[结束]

注意
链接可能仍然超出所请求数量的等级,由渲染引擎调整以适应其他请求。

当链接标签写在链接的中间时,额外的破折号必须添加在链接的右侧。以下示例等同于前一个示例:

flowchart TD
    A[开始] --> B{是否是?}
    B -- 是 --> C[好的]
    C --> D[重新考虑]
    D --> B
    B -- 否 ----> E[结束]

对于虚线或粗线链接,需要添加的字符是等号或点,如下表所示:

长度 1 2 3
正常 --- ---- -----
带箭头正常 --> ---> ---->
粗线 === ==== =====
带箭头的粗线 ==> ===> ====>
虚线 -.- -..- -...-
带箭头的虚线 -.-> -..-> -...->

破坏语法的特殊字符

可以将文本放在引号内,以呈现更麻烦的字符。如下例所示:

flowchart LR
    id1["这是一段(文本)"]

转义字符的实体代码

可以使用以下语法转义字符。

    flowchart LR
        A["一个双引号:#quot;"] --> B["十进制字符:#9829;"]

给定的数字是十进制,因此
#
可以被编码为
#35;
。同样也支持使用HTML字符名称。

子图

subgraph title
    graph definition
end

以下是示例:

flowchart TB
    c1-->a2
    subgraph one
    a1-->a2
    end
    subgraph two
    b1-->b2
    end
    subgraph three
    c1-->c2
    end

您还可以为子图设置显式id。

flowchart TB
    c1-->a2
    subgraph ide1 [one]
    a1-->a2
    end

流程图

使用图形类型流程图,还可以将边设置到子图及其返回,如下所示:

flowchart TB
    c1-->a2
    subgraph one
    a1-->a2
    end
    subgraph two
    b1-->b2
    end
    subgraph three
    c1-->c2
    end
    one --> two
    three --> two
    two --> c2

子图中的方向

使用图类型流程图,可以使用方向语句设置子图的渲染方向,如下所示。

flowchart LR
  subgraph TOP
    direction TB
    subgraph B1
        direction RL
        i1 -->f1
    end
    subgraph B2
        direction BT
        i2 -->f2
    end
  end
  A --> TOP --> B
  B1 --> B2

限制

如果子图的任何节点与外部链接,子图方向将被忽略。相反,子图将继承父图的方向:

flowchart LR
    subgraph subgraph1
        direction TB
        top1[top] --> bottom1[bottom]
    end
    subgraph subgraph2
        direction TB
        top2[top] --> bottom2[bottom]
    end
    %% ^ 这些子图是相同的,只是在于他们的链接:

    %% 链接到subgraph1: subgraph1的方向得以保持
    outside --> subgraph1
    %% 链接在subgraph2内:
    %% subgraph2继承了顶层图的方向(LR)
    outside ---> top2

Markdown字符串

“Markdown Strings”功能通过提供更灵活的字符串类型,增强了流程图和思维导图,支持文本格式化选项,如粗体和斜体,并自动在标签内换行文本。

%%{init: {"flowchart": {"htmlLabels": false}} }%%
flowchart LR
subgraph "One"
  a("`这只 **猫** 在帽子里`") -- "边标签" --> b{{"`这只 **狗** 在母猪里`"}}
end
subgraph "`**Two**`"
  c("`这只 **猫** 在帽子里`") -- "`粗体 **边标签**`" --> d("这只狗在母猪里")
end

格式化:

  • 粗体文本使用双星号(
    **
    )在文本前后。
  • 斜体使用单星号(
    *
    )在文本前后。
  • 使用传统字符串时,您需要添加
    <br>
    标签以使文本在节点中换行。然而,Markdown字符串会在文本过长时自动换行,并允许您通过简单使用换行符开始新行,而无需
    <br>
    标签。

该功能适用于节点标签、边标签和子图标签。

自动换行可以通过使用

---
config:
  markdownAutoWrap: false
---
graph LR

禁用。

交互

可以将点击事件绑定到节点,点击可以引导至javascript回调或将在新浏览器标签中打开的链接。

使用`securityLevel='strict'`时,此功能被禁用,而使用`securityLevel='loose'`时启用。
click nodeId callback
click nodeId call callback()
  • nodeId是节点的id
  • callback是显示图形的页面上定义的javascript函数的名称,函数将以nodeId作为参数调用。

以下是tooltip使用的示例:

<script>
  window.callback = function () {
    alert('回调被触发');
  };
</script>

tooltip文本用双引号括起来。tooltip的样式由类
.mermaidTooltip
设置。

flowchart LR
    A-->B
    B-->C
    C-->D
    click A callback "回调的tooltip"
    click B "https://www.github.com" "这是一个链接的tooltip"
    click C call callback() "回调的tooltip"
    click D href "https://www.github.com" "这是一个链接的tooltip"

成功
tooltip功能和链接URL的能力从版本0.5.2开始可用。

?> 由于Docsify处理JavaScript回调函数的限制,上述代码的替代工作演示可以在
this jsfiddle
中查看。

链接默认在同一浏览器标签/窗口中打开。可以通过在点击定义中添加链接目标(支持
_self

_blank

_parent

_top
)来更改这一点:

flowchart LR
    A-->B
    B-->C
    C-->D
    D-->E
    click A "https://www.github.com" _blank
    click B "https://www.github.com" "在新标签中打开" _blank
    click C href "https://www.github.com" _blank
    click D href "https://www.github.com" "在新标签中打开" _blank

初学者提示 - 用于HTML上下文中使用交互链接的完整示例:

<body>
  <pre class="mermaid">
    flowchart LR
        A-->B
        B-->C
        C-->D
        click A callback "tooltip"
        click B "https://www.github.com" "这是一个链接"
        click C call callback() "tooltip"
        click D href "https://www.github.com" "这是一个链接"
  </pre>

  <script>
    window.callback = function () {
      alert('回调被触发');
    };
    const config = {
      startOnLoad: true,
      flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' },
      securityLevel: 'loose',
    };
    mermaid.initialize(config);
  </script>
</body>

注释

可以在流程图中输入注释,解析器将忽略这些注释。注释需要单独占一行,并且必须以
%%
(双百分号)开头。注释内容从注释开始到下一个换行符之间的任何文本都将被视为注释,包括任何流程语法。

flowchart LR
%% 这是一个注释 A -- 文本 --> B{节点}
   A -- 文本 --> B -- 文本2 --> C

样式和类

样式链接

可以给链接添加样式。例如,您可能想样式化流程中的反向链接。由于链接没有与节点相同的id,因此需要另一种方式来决定应该附加什么样的样式。链接样式将根据链接在图中的定义顺序编号,或使用default来应用于所有链接。在以下示例中,链接样式声明中定义的样式将属于图中的第四个链接:

linkStyle 3 stroke:#ff3,stroke-width:4px,color:red;

也可以通过逗号分隔来将样式添加到多个链接一次声明中:

linkStyle 1,2,7 color:blue;

样式化线条曲线

可以根据需要样式化项之间的线条曲线类型,如果默认方法不符合您的需求。可用的曲线样式包括
basis

bumpX

bumpY

cardinal

catmullRom

linear

monotoneX

monotoneY

natural

step

stepAfter

stepBefore

在此示例中,从左到右的图形使用
stepBefore
曲线样式:

%%{ init: { 'flowchart': { 'curve': 'stepBefore' } } }%%
graph LR

有关可用曲线的完整列表以及自定义曲线的解释,请查阅
d3-shape
项目中的
Shapes
文档。

样式化节点

可以为节点应用特定样式,例如更厚的边框或不同的背景颜色。

flowchart LR
    id1(开始)-->id2(停止)
    style id1 fill:#f9f,stroke:#333,stroke-width:4px
    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5

比每次定义样式更方便的是定义样式类并将该类附加到应该有不同外观的节点。类定义看起来如下面的示例:

    classDef className fill:#f9f,stroke:#333,stroke-width:4px;

也可以在一条声明中定义多个类样式:

    classDef firstClassName,secondClassName font-size:12pt;

将类附加到节点的方式如下:

    class nodeId1 className;

还可以在一条声明中将类附加到节点列表:

    class nodeId1,nodeId2 className;

使用
:::
运算符将类附加到节点的简化形式,如下所示:

flowchart LR
    A:::someclass --> B
    classDef someclass fill:#f96

此形式可用于在节点之间声明多个链接:

flowchart LR
    A:::foo & B:::bar --> C:::foobar
    classDef foo stroke:#f00
    classDef bar stroke:#0f0
    classDef foobar stroke:#00f

CSS类

还可以在CSS样式中预定义类,可以从图表定义中应用,如下所示:

示例样式

<style>
  .cssClass > rect {
    fill: #ff0000;
    stroke: #ffff00;
    stroke-width: 4px;
  }
</style>

示例定义

flowchart LR
    A-->B[AAA<span>BBB</span>]
    B-->D
    class A cssClass

默认类

如果类名为default,则会分配给所有没有特定类定义的类。

    classDef default fill:#f9f,stroke:#333,stroke-width:4px;

Fontawesome的基本支持

可以从fontawesome添加图标。

图标通过语法fa:#图标名称#访问。

flowchart TD
    B["fa:fa-twitter 以和平为主题"]
    B-->C[fa:fa-ban 禁止]
    B-->D(fa:fa-spinner)
    B-->E(A fa:fa-camera-retro 也许?)

Mermaid支持Font Awesome,只要CSS包含在网站中。Mermaid对可以使用的Font Awesome版本没有任何限制。

请查阅
官方Font Awesome文档
了解如何在您的网站上包含它。


<head>
中添加此代码片段将添加对Font Awesome v6.5.1的支持

<link
  href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
  rel="stylesheet"
/>

自定义图标

只要网站导入相应的工具包,就可以使用从Font Awesome平台提供的自定义图标。

请注意,这目前是Font Awesome的一项付费功能。

对于自定义图标,您需要使用前缀
fak

示例

flowchart TD
    B[fa:fa-twitter] %% 标准图标
    B-->E(fak:fa-custom-icon-name) %% 自定义图标

尝试渲染它

flowchart TD
    B["fa:fa-twitter 以和平为主题"]
    B-->C["fab:fa-truck-bold 自定义图标"]

图表声明之间的空格和无需分号

  • 在图表声明中,语句现在也可以在没有分号的情况下结束。在0.2.16版本发布后,以分号结束图表声明已成为可选,因此以下图表声明也是有效的,同时旧的图表声明也保持有效。

  • 节点和链接之间允许单个空格。但是,节点与其文本之间以及链接与其文本之间不得存在空格。图表边缘的新声明如下所示,该声明也与旧的图表边缘声明有效。

flowchart LR
    A[硬边] -->|链接文本| B(圆边)
    B --> C{决策}
    C -->|一种| D[结果一]
    C -->|二| E[结果二]

配置

渲染器

图的布局由渲染器完成。默认渲染器是dagre。

从Mermaid版本9.4开始,可以使用一个名为elk的替代渲染器。elk渲染器更适合大型和/或更复杂的图。

elk
渲染器是一个实验性特性。

您可以通过添加这个指令将渲染器更改为elk:

%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
请注意,网站需要使用mermaid版本9.4+才能正常工作,并在延迟加载配置中启用此功能。

宽度

可以调整渲染流程图的宽度。

这可以通过定义
mermaid.flowchartConfig
或通过CLI使用JSON文件与配置来完成。关于如何使用CLI的信息可以在mermaidCLI页面上找到。

mermaid.flowchartConfig = {
    width: 100%
}

最后是使用AI来自动生成 Mermaid 流程图的教程

  1. 首先打
    中文Mermaid官方网站
    ,如下图:

  2. 然后在【文本内容】区域输入你要整理的内容,比如:网站用户登陆的流程

  3. 点击【整理成图】按钮之后,稍等片刻即可得到以下 Mermaid 代码,如下图:

    下方的【图表代码】即是自动生成得到的对应代码,右侧图表即是代码渲染的结果。

总体只需3步就可以用AI完成工作,方便高效。

大家好,我是汤师爷~

这篇聊聊 Bolt.new 和 Cursor 的对比。

Bolt.new 是一款基于 SaaS 的 AI 编码平台。它由 LLM 驱动的智能体作为底层,并结合 WebContainers 技术,让用户可以直接在浏览器中进行编码和运行。其主要优势包括:

  • 支持前后端同时开发;
  • 项目文件夹结构可视化;
  • 环境自托管,自动安装依赖(如 Vite、Next.js 等);
  • 运行 Node.js 服务器,从部署到生产。

Bolt.new 的目标是让 Web 应用开发变得更加普及,使得即便是编程新手,也能通过简单的自然语言实现创意。

Bolt 虽然很牛,但绝不是网传的什么“Cursor 杀手”。如果让我选,我会毫不犹豫地选择 Cursor。

1、编码操控感

作为开发者,最关心的是对工具的操控感。

Cursor 在这方面表现优异,可以自由定制模型、选择要参考的文件和文档,甚至能指定文档的版本。让开发者可以清楚地掌握 AI 的工作方式。

反观 Bolt.new,更多时候感觉像是“暗箱操作”。设置里几乎没有关于模型的选项,也没有明显的配置能力。对于开发者来说,这种粗粒度会让人感到限制太多。如果你是产品经理或者交互设计师,Bolt.new 可能够用,但开发者绝对会觉得不够。

2. 信息透明度

Cursor 在信息透明度上做得相当好,它会显示代码修改的差异、明确告诉你修改了哪些文件,甚至会展示引用了哪些文档和搜索了什么内容。这种透明度不仅提升了信任感,还让开发者可以精准调整,避免不必要的出错。

而 Bolt.new 则藏着掖着,不确定它的代码差异在哪里,也不清楚它具体引用了哪些文档和逻辑。这种缺乏透明度的体验,会让开发者不踏实,尤其是在需要理解复杂逻辑时。

3. 功能缺失

Bolt.new 在一些关键功能上显得不足,比如:

  • 在线搜索
    :我不知道它是否支持,或者它是如何实现的,它没有提供清晰的反馈。
  • 代码仓库索引
    :它没有解释索引的逻辑,开发者无法清楚了解背后的工作原理。
  • 实时文档支持
    :Bolt 可能用了一些热门框架的文档(比如 Next.js),但我无法切换不同版本,而这些在 Cursor 里都能轻松实现。

4. 灵活性不足

Bolt.new 的灵活性也显得不足,整个系统不能灵活配置,让开发者失去了对工具的操控感。而 Cursor 则提供了充分的灵活性,允许用户根据需求调整各种配置。

从操控感、透明度、功能、灵活性,Bolt.new 目前还不足以取代 Cursor,更别说成为“Cursor 杀手”。如果要对代码进行精细化的调整,无脑选择 Cursor。

本文已收录于,我的技术网站:
tangshiye.cn
里面有,AI 编程、算法 Leetcode 详解、面试八股文、BAT面试真题、简历模版、架构设计,等经验分享。

现在的时间是2025年1月1日,也就是元旦节。傍晚6点整,此时此刻,我正安坐在小书房的电脑前,整理这一年的得失。

昨天晚上,我回看了去年的“编程之路”总结,再加上回顾年初立下的Flag们。依然逐个实现了,除了这篇文章在年尾跳票了一天,现在马上补全,唯恐耽搁太久。废话就不多说了,我们一起进入正题。

今年年初的时候,收到了来自掘金社区的新春礼物,里面有传统的对联、福字,还有一个叫做“抖音文创云漫春木礼盒套装”,里面是塔香、香薰,还有搭配使用的香炉和香盘。虽然和“抖音”这种快节奏的短视频有些联系,但无论是看上去和用上去都突出一个“慢生活”的状态。确实忙了一年,好不容易有个过年长假,该休息的时候还是需要给自己放个假的。

熟悉我的朋友都知道,从上年年尾到年初,家里经历了一场变故,给我造成的打击和影响是巨大的,而且余波一直蔓延到24年夏秋之交。其中的经历,有些可以在当时向身边的人求助,大部分则只能自己慢慢吞咽下去。之前说:2024年是“重启之年”,没想到这重启的过程充满荆棘。好在到现在这个时间,该过去的,都已经挺过去了。所以不怕讲给那些关心我的人听,至少现在讲,他们不至于为我担忧了。

先说个题外话,自从大学毕业之后,不管是生活还是工作,仿佛一切都顺风顺水。这种情境其实总让我感觉自己像是身处温室中的植物,一旦换到真实的环境,便会不适应甚至被摧毁。老实讲,在出事之前,我一直觉得在未来的某个时间点,会有一个挑战在那等着我。就好像“增强回路”和“调节回路”是一对,一直处在“增强回路”状态下,一定不是正常现象。所以我的预感其实并不能说是完全的玄学。

想多了,便会觉得很恐怖。一方面不知道具体会是什么,也不知道挑战有多大;另一方面,又能清楚地感觉到它的存在,就仿佛有一个躲在阴影里的怪物,我在明它在暗,只要时机合适,它便会展开行动。用比较流行的词语,这就不是个“黑天鹅”,而是个“灰犀牛”。你知道它在那,但是眼下也没有什么办法,拿它根本没招。当然,这种预感的内容也不全是坏事,其实大多数是不好不坏的事或者就是纯粹的喜讯。以现在的认知视角来看,其实根本就没有什么“好“事或者“坏”事,这一好一坏只是在当时的情形之下,人对事的定义。多年之后再回头看,也许就会反转。所以不管命运给了什么,都应该开心地接着。

当事情发生的时候,现在回想起来,有几个瞬间是没有任何情绪的。不是悲伤,也不是欢喜,而是大脑里一片空白,一种无感。日常的工作和生活依靠惯性仍然机械式地进行着,在公司,每天的工作都能顺利交差,和同事的相处也有说有笑,很融洽。只是回到家里之后,会难受痛苦一些。偶尔会找朋友聊聊天,有的人表现出来的态度,似乎比我自己还要觉得事情很严重,我就觉得一定有地方不对,可能是我把这事儿想简单了。

真的让我有切身觉察的事,是从感觉到恶心开始的,从早上刷牙开始,会干呕。出去遛狗,跑两步也会干呕。我记得特别清楚的就是有一次我给我妈打电话,一句话当中我干呕了三次,一句话说了中断了三次才说完。但是又不能让她老人家听出来有问题,就假装咽唾沫,喘大气,假装自己在遛狗,在奔跑。回到家里测了一下体重,竟然掉到了90多斤,这是我万万没想到的。

没过多久,楼上开始装修。我有一个特别引以为傲的本领,就是听力非常好,同一首熟悉的曲子,用我熟悉的器材,基本上分辨320K MP3和无损就是一耳朵的事儿,结果这个本领现在开始帮倒忙了。我家住在4层,装修的这家好像住在是6层还是8层的。有一天晚上我就嫌他们家实在太吵了,几乎敲击的每一下,都会让我心跳加速,浑身冒汗。我实在受不了,便去楼上找他们,后来才知道他们并不住在相邻的5层,而是更高。而且5层的邻居并没有觉得很大声,我就又觉得哪里不对劲。后来竟然发展到很容易受到声音的惊吓,都会心跳加速,身体冒汗。

再又没过多久,我就觉得自己的情绪变得很奇怪。要么就很忧郁,要么就很亢奋。于是,作为一个理性大于感性的人,我展开了对自己的分析。但是实在是琢磨不明白,如果是抑郁症的话,为什么会亢奋呢?于是,我就去医院了。诊断结果叫做“躁狂抑郁症”还有“惊恐症”,还开了些药。好消息是医生说我这种情况不严重,最多也就半年也就差不多了。事实证明果然是术业有专攻,大概也就1个月,我就没什么事儿了。甚至连干呕的情况也没有了。

原以为这样折腾一通,也就该结束了。结果在夏天的时候,突然有一天,半边耳朵就像堵住了一样,几乎听不见了。刚开始也是没当事儿,觉得过几天就会好,事实结果和想象的完全不一样。这次我没犹豫,直接又去医院了。但没有上次那么幸运,医生看了半天,也没发现耳朵有什么问题,说是“突发性耳聋”。只是让我别熬夜,多休息,别有太多心理压力。但是那个时候,公司安排值夜班,通宵那种,一周基本上都会熬一次夜。其实这还不是最难受的,由于一直是半边听不清,时间久了就会头晕,又会恶心,这简直是太讨厌了。

好在,这种状态没持续多久,基本上快到秋天的时候,也就康复了。现在说起来感觉很轻松,其实在过程中有很多小事需要忍耐和跨越,这些小事累积到一起,有时候就会让人觉得多少有些委屈。比如,去父母家,听到他们家楼上也叮咣响,特别难受的时候,最多只能抱怨一句。并且要装作很轻松,无事发生,还要表现出很享受呆在那的状态。现在回想起那个感觉,真是爽极了。

看到这,估计有人会觉得我在卖惨,其实并不是这样。我想表达一种态度,这种态度可能已经深藏在我心里多年,但之前我没有看见过它。就是只要我在困难面前没举白旗,尽管它可以一直向我攻击,但凡我没被困难打服,等我喘过来这口气,就该轮到我还手了,而且我一般不打没准备的仗。但凡我开始动了,那对手就已经是死棋了。这有点像回合制游戏,也很像尼采说过的那句话:“那些杀不死你的,终将使你更强大”。事实上确实如此。

眼看这一年都过去大半了,面对年初立下的flag,我只能说:今年要翻车了。但是凭借一身的反骨,我觉得我应该还有机会扳回这一局。因为我看到了生活抛给我的问题,看清了那只“灰犀牛”,也不过这点三脚猫的功夫。

运气来了,下半年我接到一个Web开发项目,需求其实很简单。但问题在于前端我完全不熟,最多也就是能在局部改一改别人写过的东西,而且团队里也没有经验可以参考。再加上生活上发生了种种事情,其实我在内心里是没有什么信心做成的。

于是,我就利用国庆假期的时间,恶补了前端开发的技能。对,我就是那个暗地里使劲儿的做事风格,就算最后没有惊艳所有人,至少咱得把这事儿干成了。然后积蓄力量,等待机会,再玩一次。我想,总有一回能干成个大的吧。

项目在推进中,当然会遇到挑战,每个软件项目几乎都如此,不过最终还是顺利交付了,最终的结果皆大欢喜。看到这样的结果,那个熟悉的我终于又回来了。在落地实现的开始,我就没想用PC端现成的UI。尽管可以这么弄,但给用户的体验一定不如专门为移动端设计的那么像样。我知道这样做其实有风险,特别是我在前端这二把刀的能力。但是我又一想,我身边那么多前端同事,我可以求助呀。上司当然也知道我不擅长,退一步说,如果我预感要做砸,可以申请更多的时间来弥补啊。再退一万步说,就算公司没有前端同事,上司也不给任何同情分数。以我的编程经验来说,上手一门编程语言,应该不会是难事,况且现在还有AI可以辅助。困难有数,对抗困难的方法却不止一种,我就知道我可以。

另一方面,在编写Git提交的Commit信息时,尽管团队当中没有格式、内容的规定,但我仍然会坚持写得清楚一些。用固定的格式认真地编写。这一做法在当时看起来没有什么用,但是万万没想到,在年尾要做工作汇报。做汇报,总得列数字呀。我的工作量,统计起来就特别轻松而且省时间,还准确,可追溯。这就应了那句话:“但行好事,莫问前程”。有些事情,当下看来没什么用,但只要事情本身是“好事”,不妨就把它做了,谁能说得好再未来的某个时刻,它就让人受益了呢,哪怕这个“人”不是自己。

24年的下半年,是真的忙碌,也真的充实。

好险,年初的目标都能挑上勾了。

写到这,差不多有3000多字了。作为读者,看到这,也差不多得花上个几分钟了。在这个快节奏,信息爆炸的时代,还能耐心地看到这,是真朋友。

走过了布满荆棘的2024,来到2025。凭借我那种“半玄学”的预感能力,我感觉2025年可能会像白开水似的平淡,而好运将会在2026年光顾。那么,接下来的一年,就是积蓄力量,“暗地里使劲儿”,待机会来临时,伸手抓住它。

最后,想对清华社编辑王老师郑重地道个歉,年初的书稿没有如期交付。也感谢您的仗义相助,让我在充满波折的上半年有机会喘口气。感谢公司的各位同仁,正是你们的包容、鼓励和帮助,让我有勇气重树信心。感谢我的老友们,在我需要你们的时候,总是没有丝毫犹豫。

衷心感谢可爱、可敬、靠谱的你们。

本文分享自华为云社区
《ENS网络连接,面向多云多池网络的高效互联》
,作者:华为云Stack ENS研发团队。

1、ENS网络连接服务场景详细介绍

ENS网络连接通过统一建模和全局管控实现跨云跨池的网络互通、网络安全服务化,ENS网络连接给客户提供三大核心场景的支持。

场景一:不同数据中心、不同资源池网络一键高性能互通

图1:不同数据中心、不同资源池网络通过ENS实现一键高性能互通

政企客户数据中心经过多年演进,通常会形成多个资源分区,如传统分区、云分区,甚至包括硬件SDN分区和虚拟化分区。这些分区上部署的业务有互通诉求,举个典型的例子,客户的办公业务会同时部署在传统分区、云分区的内网VPC、外联网VPC、互联网VPC,办公业务之间需要互联互通。

通过ENS,客户只需要创建一个网络分段,把各个分区的子网加入到网络分段中,既可以实现自动化互通,并且还可以配置一些自定义路由和策略,进一步优化互通方案,配置效率从“月/天级”提升到“分钟级”。ENS引入连接网关实现不同数据中心自动互通,连接网关采用硬件交换机overlay转发,性能和稳定性高,并且与物理网络解耦,为复杂网络环境提供稳定支持。

场景二:跨云跨池大二层互通

图2:通过ENS实现跨云跨池大二层互通

受限于数据中心的物理空间和电力系统,许多客户面临老数据中心计算资源耗尽但无法扩容服务器的困境。此时,新建数据中心成为唯一选择,而老数据中心与新数据中心的业务往往需要在二层实现无缝连接。

ENS独创的二层扩展功能,可以实现不同数据中心、不同资源分区的网络大二层互通,即一个子网下部分IP在老数据中心分区,部分IP在新数据中心分区,并且二层扩展可以叠加网络分段一起使用,大二层的子网还可以通过网络分段实现三层访问。

场景三:跨云跨池资源迁移,保持IP不变

图3:通过ENS实现跨云跨池迁移保持IP不变

当客户遇到机房老旧不能使用、平台跨代际演进迁移,平台更换迁移等场景,需要进行业务搬迁,而在业务迁移过程中,希望保持IP地址不变,且通过分批迁移降低风险。

ENS的二层扩展功能,可以自动感知IP的迁移变化,结合华为云Stack的CMS(Cloud Migration Station)迁移服务,实现IP粒度的细化迁移,支持单台虚拟机级别的迁移操作
。迁移到新资源池后,业务还可以跟老资源池的IP保持二层和三层互通,完全免除修改应用和网络配置的繁琐操作,提供无缝、低风险的迁移体验。

2、ENS网络连接服务架构设计

ENS网络连接要解决的是政企客户不同类型/平台/厂商的IT基础设施之间网络互通和网络安全问题,实现复杂度比单一的IT基础设施要高很多,因此在架构设计上有3个方面需要额外重点考虑:

1)统一的通用网络模型

与资源池网络实现和技术解耦,简单易用。ENS站在全局网络视角,定义了一套全新的服务模型,用户不用感知资源池内是传统物理网络、虚拟化网络还是云SDN网络,也不用感知是哪个厂商的方案,即可以快速实现不同资源池的网络/安全统一管理自动打通。下面是一些核心模块和说明:

图4:ENS通用网络模型,与资源池网络实现无关

•         全局网络:表示面向全局的网络连接器拓扑,里面可以存放各种支持跨池的网络服务实例,方便管理,当前支持存放网络分段实例和二层扩展实例。

•         网络分段:表示全局路由互通的虚拟核心路由器,可以配置路由和端点规则,基于这些配置来实现全局一张网来连接各个资源池。

•         端点:表示连接器,一端连接网络分段,另一端连接各个资源池的网络,通过端点来实现各个资源池的快速自动接入网络分段。

•         端点规则:用于控制网络分段下各个端点是否可以默认互通,便于强管控网络通断。

•         路由策略:用于灵活控制网段分段的转发行为,满足用户自定义灵活组网的诉求。

•         安全策略:用于基于五元组(源IP、目的IP、协议、源端口、目的端口)的统一安全管理,提供域间访问控制安全和双向安全能力。

•         二层扩展:用于实现跨池的大二层互通,在跨资源池容灾、业务搬迁等场景可以保持IP不变,业务无感知迁移。

2)可灵活扩展的软件架构

通过封装标准接口和插件机制,扩展不同类型的资源池仅需要开发对接插件即可,并且可独立测试。

图5:ENS可灵活扩展的软件架构

ENS采用全局管控和本地管控分离的软件架构。全局管控统一管理逻辑,实现ENS主要功能。本地管控部署在资源池内,对接全局管控,可以屏蔽资源池差异,提供插件机制,扩展资源池类型仅需本地管控开发插件。

3)高性能的域间连接网关和安全网关

ENS网络连接设计了两种关键网关,一种是跨资源池的网络互通的连接网关,一种是跨资源池安全访问控制的安全网关。对于客户自建数据中心的资源池,包括传统物理资源池、虚拟化资源池和云资源池,支持利旧传统的硬件交换机作为连接网关,利旧传统的物理防火墙做安全网关。对于三方云资源池,支持采用NFV网元作为连接网关和安全网关,满足客户多样性的诉求。

3、ENS网络连接服务功能展示

ENS网络连接服务在华为云Stack已经商用,客户可以在8.3.1以及最新的8.5.0版本体验。下面是软件功能在界面上具体实现的展示。

•         跨资源池网络高速三层自动互通

客户只需要在ENS console上操作,即可快速组件一张覆盖多种资源池的全互联网络。下图是HCS不同版本资源池之间的四个VPC和一个传统数据中心组建一个全互通的网络,图中的连线表示是否连通。

图6:ENS全局拓扑

•         跨资源池网络高速二层自动互通

ENS是业界首个为客户提供跨资源池二层功能的云服务,ENS可以动态感知资源池内IP的申请和释放,能够感知跨资源池大二层广播域里的IP和MAC冲突,并且自动做抑制和恢复。图7是创建一个二层扩展连接两个资源池的同一个子网的拓扑图,图上还呈现了资源池的IP分布情况。

图7:ENS二层扩展拓扑

图8是呈现IP的详情以及冲突的IP/MAC,冲突的IP/MAC恢复后,ENS服务可以自动感知恢复。

图8:ENS二层扩展中IP详情

•         运维监控可视化

ENS为用户提供实例级的黄金监控指标,并可视化呈现,帮助客户运维复杂全局网络。我们在租户侧实时监控了端点实例的关键指标以及管理侧连接网关的关键指标,并在console上呈现,图9是监控的端口的指标样例。

图9:ENS监控指标可视化

4、总结

政企客户数字化转型并非一蹴而就,多平台类型、多地理位置、多计算类型、多应用架构形态必然持续长期共存。ENS很好的解决了混合云架构下网络连接难、网络安全管理难、运维难等痛点问题,国内金融行业客户和海外客户已经在陆续上线使用,ENS会持续演进,为政企客户提供更多更丰富的新功能。

点击关注,第一时间了解华为云新鲜技术~

该实际场景比较常见于,当存在多个用户控件页面拼成一个窗体,因为实际控件对应窗体的宽度并不能确定,也不是那种能指定的宽度或者高度,比如窗体分导航区域和内容区域,左侧导航区域可以直接指定宽度,而右侧内容区域则是使用Auto或者*的宽度。
在WPF中,尝试将一个控件的宽度绑定到其父级用户控件的实际宽度(ActualWidth)时,会遇到一些挑战。因为 ActualWidth和ActualHeight 是只读属性,并且它们是在布局过程之后计算出来的,这可能导致绑定延迟或不更新的问题。为了确保子控件能够正确地响应父控件大小的变化,根据实际情况使用如下方式。
方法1:使用相对宽度和星号单位
最简单的方法是让子控件自动填充可用空间,而不是显式地绑定到父控件的 ActualWidth。可以通过设置子控件的 HorizontalAlignment 属性为 Stretch 或使用在布局Grid的宽度用 * 星号单位来实现这一点。
如下:
<rubyer:Card x:Name="cardNotice" Grid.Row="1"Height="120"Padding="5"HorizontalAlignment="Stretch"  <!-- 设置为 Stretch -->HorizontalContentAlignment="Center">
    <!-- Card 内容 -->
</rubyer:Card>
Card内容里部分,可用StackPanel容器包装,StackPanel容器自动适应内部空间的宽度和高度,在结合HorizontalAlignment="Stretch"就可以实现,将rubyer:Card这个控件自动适配宽度和用户控件的宽度一样,当然也需要该rubyer:Card占据用户控件全部的Column。
或:
<Gridx:Name="homeGrid"Margin="10">
    <Grid.ColumnDefinitions>
        <ColumnDefinitionWidth="350" />
        <ColumnDefinitionWidth="*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinitionHeight="*" />
        <RowDefinitionHeight="5" />
    </Grid.RowDefinitions>
 </Grid>
方法2:使用 RelativeSource 绑定
如果确实需要基于父控件的实际宽度进行绑定,可以尝试使用 RelativeSource 绑定来引用父控件的 ActualWidth。
如下:
<UserControlx:Class="YourNamespace.PlanMoudelView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"SizeChanged="PlanMoudelView_SizeChanged">
    <Grid>
        <rubyer:Cardx:Name="cardNotice"Grid.Row="1"Height="120"Width="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ActualWidth, Mode=OneWay}"Padding="5"HorizontalAlignment="Stretch"HorizontalContentAlignment="Center">
            <!--Card 内容-->
        </rubyer:Card>
    </Grid>
</UserControl>
主要为:Width="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ActualWidth, Mode=OneWay}"

方法3:使用 SizeChanged 事件处理程序
另一种方法是在父控件的 SizeChanged 事件中手动调整子控件的宽度。这种方法适用于更复杂的情况,但通常不是首选,因为 WPF 布局系统应该能够处理大多数场景。
private void PlanMoudelView_SizeChanged(objectsender, SizeChangedEventArgs e)
{
if (cardNotice != null)
{
cardNotice.Width
= this.ActualWidth; //'this' 指向 PlanMoudelView }
}
方法4:使用 MultiBinding 和转换器
如果需要更复杂的逻辑,比如保留一定的边距或比例,可以使用 MultiBinding 结合 IMultiValueConverter 来计算子控件的宽度。
<rubyer:Cardx:Name="cardNotice"Grid.Row="1"Height="120"Padding="5"HorizontalAlignment="Stretch"HorizontalContentAlignment="Center">
    <rubyer:Card.Width>
        <MultiBindingConverter="{StaticResource WidthConverter}">
            <BindingRelativeSource="{RelativeSource AncestorType=UserControl}"Path="ActualWidth"/>
            <BindingSource="{x:Static sys:Double.NaN}"/> <!--如果需要额外参数-->
        </MultiBinding>
    </rubyer:Card.Width>
    <!--Card 内容-->
</rubyer:Card>
注意:
确保父容器允许子控件扩展
确保包含 Card 控件的父容器(例如 Grid)没有限制子控件的尺寸。检查是否有固定的高度或宽度、MaxWidth 或 MaxHeight 等可能影响布局的属性。
总结
通常情况下,使用相对宽度(如 * 星号单位)和适当的 HorizontalAlignment 是最简单有效的方法,可以确保子控件随着父控件的大小变化而自动调整。如果需要更精确的控制,可以考虑使用 RelativeSource 绑定或其他高级技术。确保父容器也支持子控件的动态尺寸调整非常重要。
最后附上,绑定后宽度减数的转换器,因为通常不能直接用子控件跟父控件完全等宽或等高,肯定需要有偏差:

/// <summary>
///控件宽度减法转换器///可用于子控件绑定父控件宽度做减法/// </summary>
public classSubtractValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, objectparameter, System.Globalization.CultureInfo culture)
{
double parentWidth = (double)value;double subtractValue = double.Parse(parameter.ToString());return parentWidth -subtractValue;
}
public object ConvertBack(object value, Type targetType, objectparameter, System.Globalization.CultureInfo culture)
{
throw newNotImplementedException();
}
}

在用户控件页面增加绑定资源:
<UserControl.Resources> <converter:SubtractValueConverter x:Key="subtractValueConverter" /> </UserControl.Resources>然后,在绑定时增加转换器的使用:
Width
="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ActualWidth, Mode=OneWay, Converter={StaticResource subtractValueConverter}, ConverterParameter=20}"

转换器代码

实际下图中右下角的列表Card使用实例:

<UserControlx:Name="VehicleQueueView"x:Class="WpfAppMom.Views.Plan.VehicleQueue"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:controls="clr-namespace:WpfAppMom.Controls"xmlns:converter="clr-namespace:WpfAppMom.Converter"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:i="http://schemas.microsoft.com/xaml/behaviors"xmlns:local="clr-namespace:WpfAppMom.Views.Plan"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:rubyer="http://rubyer.io/winfx/xaml/toolkit"xmlns:viewModels="clr-namespace:WpfAppMom.ViewModels.Plan"d:DataContext="{d:DesignInstance Type=viewModels:VehicleQueueViewModel}"d:DesignHeight="450"d:DesignWidth="800"mc:Ignorable="d">
    <UserControl.Resources>
        <converter:SubtractValueConverterx:Key="subtractValueConverter" />
    </UserControl.Resources>
    <i:Interaction.Triggers>
        <i:EventTriggerEventName="Loaded">
            <i:InvokeCommandActionCommand="{Binding LoadedCommand}" />
        </i:EventTrigger>
        <i:EventTriggerEventName="Closed">
            <i:InvokeCommandActionCommand="{Binding CancelCommand}"CommandParameter="{Binding ElementName=VehicleQueueView}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <controls:ControlDisplayx:Name="QueueControl"Title="车辆队列"HorizontalAlignment="Stretch"rubyer:PanelHelper.Spacing="10">
        <GridWidth="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ActualWidth, Mode=OneWay, Converter={StaticResource subtractValueConverter}, ConverterParameter=20}"Height="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ActualHeight, Mode=OneWay, Converter={StaticResource subtractValueConverter}, ConverterParameter=55}"HorizontalAlignment="Left"rubyer:GridHelper.RowDefinitions="45, *, 50">
            <Grid.ColumnDefinitions>
                <ColumnDefinitionWidth="*" />
                <ColumnDefinitionWidth="2" />
            </Grid.ColumnDefinitions>
            <StackPanelGrid.Row="0"Margin="5"HorizontalAlignment="Left"rubyer:PanelHelper.Spacing="5"Orientation="Horizontal">
                <ButtonWidth="110"rubyer:ButtonHelper.IconType="PhoneLine"Content="呼叫AGV" />
                <ButtonWidth="110"rubyer:ButtonHelper.IconType="FileShredLine"BorderBrush="{StaticResource ButtonBorderForegroundBrush}"Content="打印表单"FontStyle="Normal"Foreground="{StaticResource ButtonFontForegroundBrush}"Style="{StaticResource OutlineButton}" />
                <ButtonWidth="110"rubyer:ButtonHelper.IconType="GitRepositoryCommitsLine"BorderBrush="{StaticResource ButtonBorderForegroundBrush}"Content="报工"Foreground="{StaticResource ButtonFontForegroundBrush}"Style="{StaticResource OutlineButton}" />
            </StackPanel>
            <DataGridx:Name="DataGrid"Grid.Row="1"HorizontalContentAlignment="Center"rubyer:ControlHelper.CornerRadius="{DynamicResource AllContainerCornerRadius}"rubyer:ControlHelper.FocusedBrush="{DynamicResource Primary}"rubyer:ControlHelper.FocusedForegroundBrush="{DynamicResource WhiteForeground}"rubyer:ControlHelper.MaskOpacity="1"rubyer:DataGridHelper.ClickToEdit="False"rubyer:DataGridHelper.Loading="{Binding IsLoading}"rubyer:HeaderHelper.Background="{StaticResource DataGridTitleBrackgroudBrush}"rubyer:HeaderHelper.FontFamily="宋体"rubyer:HeaderHelper.FontSize="15"rubyer:HeaderHelper.FontWeight="DemiBold"rubyer:HeaderHelper.Foreground="{StaticResource DataGridTitleFontBrush}"rubyer:HeaderHelper.HorizontalAlignment="Center"AutoGenerateColumns="True"CanUserAddRows="False"GridLinesVisibility="Horizontal"IsReadOnly="False"ItemsSource="{Binding Datas}"RowHeight="40">
                <DataGrid.Columns>
                    <rubyer:DataGridSelectCheckBoxColumnWidth="85"Binding="{Binding IsSelected}"Header="全选" />
                </DataGrid.Columns>
            </DataGrid>
            <rubyer:PageBarGrid.Row="2"Margin="0 10 20 0"IsShowPageSize="True"IsShowTotal="True"ItemsDock="Left"PageIndexChanged="PageBar_PageIndexChanged"PageSizeChanged="PageBar_PageSizeChanged"PageSizeCollection="10, 20, 30, 50"Style="{StaticResource TextPageBar}"Total="1000" />
        </Grid>

    </controls:ControlDisplay>
</UserControl>

用户控件代码