RuleGo-Editor RuleGo-Editor
🏠首页
🧭使用
  • 简介
  • 安装
  • 配置
  • 组件
  • 架构设计
  • 国际化
  • 事件系统
  • 调试功能
  • AI功能
  • 自定义节点
  • 分组节点
  • RuleGo-Editor (opens new window)
  • RuleGo-Server (opens new window)
  • 后端API文档 (opens new window)
RuleGo (opens new window)
  • English
  • 简体中文
🏠首页
🧭使用
  • 简介
  • 安装
  • 配置
  • 组件
  • 架构设计
  • 国际化
  • 事件系统
  • 调试功能
  • AI功能
  • 自定义节点
  • 分组节点
  • RuleGo-Editor (opens new window)
  • RuleGo-Server (opens new window)
  • 后端API文档 (opens new window)
RuleGo (opens new window)
  • English
  • 简体中文
  • 简介
  • 安装
  • 配置
  • 节点组件
  • 架构设计
  • 国际化
  • 事件系统
  • 调试功能
  • AI 功能
  • 自定义节点开发
    • 节点类型说明
      • BaseNode
      • SimpleNode
      • EndpointNode
      • StartNode / EndNode
      • GroupNode
      • ChainNode
      • CommentNode
      • AgentNode
      • AgentOrchestrationNode
      • FlowLink
    • 自定义节点开发步骤
      • 步骤 1:创建节点文件
      • 步骤 2:注册节点
      • 步骤 3:配置国际化
      • 步骤 4:添加组件到列表
      • 步骤 5:添加图标
    • 表单字段类型
      • string
      • int / float
      • boolean
      • select
      • textarea
      • codeEditor
      • table
      • struct
      • map
      • expr
      • slider
      • input-number
      • datePicker
      • timePicker
      • ruleChainSelector
    • 高级用法
      • 自定义锚点形状
      • 自定义节点形状
      • 添加连线校验规则
      • 动态调整节点尺寸
    • 完整示例
    • 参考资料
  • 分组节点
目录

自定义节点开发

RuleGo-Editor 基于 LogicFlow (opens new window) 实现节点可视化编辑。本文档介绍如何开发自定义节点,包括节点类型说明、开发步骤、代码示例和表单配置。

# 节点类型说明

RuleGo-Editor 内置了以下节点类型,所有节点均位于 src/components/lf-node/nodes/ 目录下。

# BaseNode

基础节点类,其他节点继承自它。定义在 BaseNode.js 中。

主要特性:

  • 默认尺寸:宽度 120,高度 30
  • 支持左右锚点(左侧输入,右侧输出)
  • 支持状态高亮(processing/success/error)
  • 支持图标显示
  • 内置连线校验规则

核心属性:

属性 类型 默认值 说明
width number 120 节点宽度
height number 30 节点高度
radius number 5 圆角半径
iconPosition string '' 图标位置,'left' 或 'right'
defaultFill string '#a6bbcf' 默认填充颜色

状态高亮:

节点支持三种状态高亮,通过设置 properties.status 属性实现:

// 设置节点状态
lf.getNodeModelById(nodeId).setProperties({
  status: 'processing' // 或 'success'、'error'
})
1
2
3
4
状态 边框颜色 说明
processing #409EFF (蓝色) 处理中
success #67c23a (绿色) 成功
error #f56c6c (红色) 错误

# SimpleNode

简单节点,继承自 BaseNode。适用于大多数普通组件。

// 文件位置:src/components/lf-node/nodes/SimpleNode.js
export default {
  type: 'simple-node',
  model: SimpleNodeModel,
  view: SimpleNode
}
1
2
3
4
5
6

特性:

  • 继承 BaseNode 的所有特性
  • 支持左右锚点
  • 图标默认使用 /images/fetch.svg

# EndpointNode

输入端节点,继承自 BaseNode。用于规则链的起始输入。

// 文件位置:src/components/lf-node/nodes/EndpointNode.js
export default {
  type: 'endpoint-node',
  model: EndpointNodeModel,
  view: EndpointNode
}
1
2
3
4
5
6

特性:

  • 只有右锚点(只能输出,不能输入)
  • 图标默认使用 /images/start.svg
  • 用于规则链的起始节点

# StartNode / EndNode

开始和结束节点,分别用于规则链的起点和终点。

// StartNode - 文件位置:src/components/lf-node/nodes/StartNode.js
export default {
  type: 'start-node',
  model: StartNodeModel,
  view: StartNode
}

// EndNode - 文件位置:src/components/lf-node/nodes/EndNode.js
export default {
  type: 'end-node',
  model: EndNodeModel,
  view: EndNode
}
1
2
3
4
5
6
7
8
9
10
11
12
13

StartNode 特性:

  • 只有右锚点
  • 默认填充颜色:rgb(166, 187, 207)
  • 点击图标触发 rulego-editor:start 事件

EndNode 特性:

  • 只有左锚点
  • 默认填充颜色:rgb(166, 187, 207)

# GroupNode

分组节点,继承自 LogicFlow 的 dynamicGroup。支持折叠/展开功能。

// 文件位置:src/components/lf-node/nodes/GroupNode.js
export default {
  type: 'group-node',
  model: GroupNodeModel,
  view: GroupNode
}
1
2
3
4
5
6

特性:

  • 默认尺寸:宽度 300,高度 200
  • 支持折叠/展开(折叠后尺寸:120x30)
  • 支持调整大小
  • 支持拖入其他节点(endpoint-node 和 start-node 除外)
  • 动态锚点位置(根据折叠状态自动调整)

配置选项:

属性 默认值 说明
resizable true 是否可调整大小
collapsible true 是否可折叠
isRestrict true 是否限制节点在组内
autoResize true 是否自动调整大小
foldable true 是否可折叠
autoToFront true 选中时是否自动置顶

# ChainNode

子规则链节点,继承自 BaseNode。用于引用其他规则链。

// 文件位置:src/components/lf-node/nodes/ChainNode.js
export default {
  type: 'chain-node',
  model: ChainNodeModel,
  view: ChainNode
}
1
2
3
4
5
6

特性:

  • 只有左锚点(只能输入)
  • 用于调用子规则链

# CommentNode

注释节点,继承自 BaseNode。用于在画布上添加注释。

// 文件位置:src/components/lf-node/nodes/CommentNode.js
export default {
  type: 'comment-node',
  model: CommentNodeModel,
  view: CommentNode
}
1
2
3
4
5
6

特性:

  • 没有锚点(不能连线)
  • 文本以斜体显示
  • 宽度根据文本长度自动调整

# AgentNode

智能体节点,继承自 RectNode。用于 AI 智能体编排。

// 文件位置:src/components/lf-node/nodes/AgentNode.js
export default {
  type: 'agent-node',
  model: AgentNodeModel,
  view: AgentNode
}
1
2
3
4
5
6

特性:

  • 默认尺寸:宽度 160,高度 80
  • 支持标准左右锚点
  • 底部四个特殊锚点:大模型(llm)、工具(tool)、记忆(memory)、知识库(knowledge)
  • 锚点使用不同颜色区分

# AgentOrchestrationNode

智能体编排节点,继承自 RectNode。用于可视化的多智能体编排。

// 文件位置:src/components/lf-node/nodes/AgentOrchestrationNode.js
export default {
  type: 'agent-orchestration-node',
  model: AgentOrchestrationNodeModel,
  view: AgentOrchestrationNodeView
}
1
2
3
4
5
6

特性:

  • 默认尺寸:宽度 180,高度 120
  • 圆角 8px,蓝色主题(填充 #E8F4FD,边框 #1890FF)
  • 只能作为连接的起点(无输入锚点)
  • 支持右侧和底部锚点连出

# FlowLink

连线类型,使用贝塞尔曲线。定义在 FlowLink.js 中。

// 文件位置:src/components/lf-node/FlowLink.js
export default {
  type: 'flow-link',
  view: FlowLink,
  model: FlowLinkModel
}
1
2
3
4
5
6

特性:

  • 使用贝塞尔曲线
  • 支持调试高亮(debugHighlight)
  • 选中/悬停时边框变色
  • 文本自动换行

# 自定义节点开发步骤

# 步骤 1:创建节点文件

在 src/components/lf-node/nodes/ 目录下创建新的节点文件,例如 MyCustomNode.js。

// src/components/lf-node/nodes/MyCustomNode.js
import { h } from '@logicflow/core'
import BaseNode from './BaseNode'
import { resolveIconPath } from '../../utils/common'

// 节点视图类
class MyCustomNodeView extends BaseNode.view {
  /**
   * 重写图标渲染方法
   */
  getIcon() {
    const { width, height, icon } = this.props.model
    const _icon = resolveIconPath(icon || '/images/my-custom.svg')
    return h('image', {
      width: 30,
      height: 30,
      x: -width / 2,
      y: -height / 2,
      href: _icon
    })
  }
}

// 节点模型类
class MyCustomNodeModel extends BaseNode.model {
  /**
   * 初始化节点数据
   */
  initNodeData(data) {
    super.initNodeData(data)
    // 设置默认尺寸
    this.width = 120
    this.height = 30
    // 设置默认填充颜色
    this.defaultFill = '#E6E0F8'
  }

  /**
   * 重写节点样式
   */
  getNodeStyle() {
    const style = super.getNodeStyle()
    const dataStyle = this.properties.view || {}

    // 自定义样式
    style.fill = dataStyle.background || this.defaultFill
    style.stroke = dataStyle.borderColor || '#999'

    return style
  }

  /**
   * 重写锚点定义
   */
  getDefaultAnchor() {
    const { x, y, id, width, height } = this
    const anchors = [
      {
        x: x + width / 2,
        y: y,
        id: `${id}_right`,
        type: 'right'
      },
      {
        x: x - width / 2,
        y: y,
        id: `${id}_left`,
        type: 'left'
      }
    ]
    return anchors
  }

  /**
   * 获取节点数据(用于保存)
   */
  getData() {
    const data = super.getData()
    data.properties.ui = 'rulego-editor'
    return data
  }
}

// 导出节点定义
export default {
  type: 'my-custom-node',
  model: MyCustomNodeModel,
  view: MyCustomNodeView
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

# 步骤 2:注册节点

在 src/components/lf-node/index.js 中注册新节点。

// src/components/lf-node/index.js
import MyCustomNode from './nodes/MyCustomNode'

class NodeRedExtension {
  constructor({ lf }) {
    // ... 其他节点注册

    // 注册自定义节点
    lf.register(MyCustomNode)

    // ... 其他代码
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 步骤 3:配置国际化

在 src/components/i18n/zh/index.js 中添加节点的国际化配置。

// src/components/i18n/zh/index.js
export const locales = {
  category: {
    // ... 其他分类

    // 添加自定义分类(可选)
    custom: {
      label: '自定义',
      background: '#C6EFCB',
      nodeType: 'simple-node'
    }
  },
  component: {
    nodes: {
      // ... 其他节点

      // 添加自定义节点配置
      'my-custom': {
        label: '我的自定义组件',
        icon: '/images/my-custom.svg',
        desc: '这是一个自定义组件的描述',
        // 字段配置
        fieldName: {
          label: '字段名称',
          desc: '字段的详细说明',
          rules: [
            { required: true, message: '该项是必须的' }
          ]
        },
        // 可选:指定节点类型
        nodeType: 'my-custom-node'
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 步骤 4:添加组件到列表

在 src/components/utils/default.js 中添加组件定义,使其出现在左侧工具栏。

// src/components/utils/default.js
export const defaultComponents = {
  endpoints: [
    // ... 其他端点
  ],
  nodes: [
    // ... 其他节点

    // 添加自定义组件
    {
      type: 'my-custom',
      category: 'custom',
      label: '我的自定义组件',
      desc: '这是一个自定义组件',
      icon: '/images/my-custom.svg',
      fields: [
        {
          name: 'fieldName',
          type: 'string',
          defaultValue: '',
          label: '字段名称',
          desc: '字段的详细说明',
          rules: [
            { required: true, message: '该项是必须的' }
          ]
        }
      ]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 步骤 5:添加图标

在 public/images/ 目录下添加节点图标文件(SVG 格式推荐)。

# 表单字段类型

RuleGo-Editor 支持多种表单字段类型,用于配置节点属性。

# string

文本输入框。

{
  name: 'fieldName',
  type: 'string',
  label: '字段名称',
  desc: '字段说明',
  defaultValue: '',
  rules: [
    { required: true, message: '该项是必须的' }
  ]
}
1
2
3
4
5
6
7
8
9
10

# int / float

数字输入框。

{
  name: 'port',
  type: 'int',
  label: '端口号',
  desc: '服务端口',
  defaultValue: 8080
}
1
2
3
4
5
6
7

# boolean

布尔值,使用开关(Switch)组件。

{
  name: 'enableLog',
  type: 'boolean',
  label: '启用日志',
  desc: '是否启用日志记录',
  defaultValue: false
}
1
2
3
4
5
6
7

# select

下拉选择框。

{
  name: 'method',
  type: 'string',
  label: '请求方法',
  component: {
    type: 'select',
    filterable: true,
    allowCreate: false,
    multiple: false,
    options: [
      { label: 'GET', value: 'GET' },
      { label: 'POST', value: 'POST' },
      { label: 'PUT', value: 'PUT' },
      { label: 'DELETE', value: 'DELETE' }
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

select 组件属性:

属性 类型 说明
filterable boolean 是否可搜索
allowCreate boolean 是否允许创建新选项
multiple boolean 是否多选
options array 选项列表,格式:[{label, value}]
loadData function 动态加载选项的函数

# textarea

多行文本输入框。

{
  name: 'sql',
  type: 'string',
  label: 'SQL语句',
  component: {
    type: 'textarea',
    rows: 4
  }
}
1
2
3
4
5
6
7
8
9

# codeEditor

代码编辑器,支持语法高亮。

{
  name: 'jsScript',
  type: 'string',
  label: 'JavaScript 脚本',
  component: {
    type: 'codeEditor'
  }
}
1
2
3
4
5
6
7
8

# table

表格编辑器,用于编辑数组类型数据。

{
  name: 'items',
  type: 'string',
  label: '缓存项',
  component: {
    type: 'table',
    options: [
      {
        name: 'key',
        label: '键',
        type: 'string',
        rules: [
          { required: true, message: '该项是必须的' }
        ]
      },
      {
        name: 'value',
        label: '值',
        type: 'string'
      }
    ]
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# struct

结构体编辑器,支持嵌套字段。

{
  name: 'config',
  type: 'string',
  label: '配置',
  fields: [
    {
      name: 'host',
      type: 'string',
      label: '主机地址'
    },
    {
      name: 'port',
      type: 'int',
      label: '端口'
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# map

键值对编辑器。

{
  name: 'headers',
  type: 'string',
  label: '请求头',
  component: {
    type: 'map'
  }
}
1
2
3
4
5
6
7
8

# expr

表达式编辑器。

{
  name: 'condition',
  type: 'string',
  label: '过滤表达式',
  desc: '例如: msg.temperature > 50',
  component: {
    type: 'expr'
  }
}
1
2
3
4
5
6
7
8
9

# slider

滑块组件。

{
  name: 'timeout',
  type: 'int',
  label: '超时时间',
  component: {
    type: 'slider',
    min: 0,
    max: 100,
    step: 1
  }
}
1
2
3
4
5
6
7
8
9
10
11

# input-number

数字输入框,支持步进。

{
  name: 'count',
  type: 'int',
  label: '数量',
  component: {
    type: 'input-number',
    min: 0,
    max: 100,
    step: 1
  }
}
1
2
3
4
5
6
7
8
9
10
11

# datePicker

日期选择器。

{
  name: 'startDate',
  type: 'string',
  label: '开始日期',
  component: {
    type: 'datePicker'
  }
}
1
2
3
4
5
6
7
8

# timePicker

时间选择器。

{
  name: 'startTime',
  type: 'string',
  label: '开始时间',
  component: {
    type: 'timePicker'
  }
}
1
2
3
4
5
6
7
8

# ruleChainSelector

规则链选择器,用于选择子规则链。

{
  name: 'targetId',
  type: 'string',
  label: '规则链',
  component: {
    type: 'ruleChainSelector'
  }
}
1
2
3
4
5
6
7
8

# 高级用法

# 自定义锚点形状

重写 getAnchorShape 方法来自定义锚点形状。

class MyNode extends BaseNode.view {
  getAnchorShape(anchorData) {
    const { x, y, type } = anchorData

    // 根据锚点类型返回不同形状
    if (type === 'right') {
      return h('circle', {
        cx: x,
        cy: y,
        r: 5,
        fill: '#1890ff',
        stroke: '#fff',
        strokeWidth: 2
      })
    }

    // 默认使用矩形
    return h('rect', {
      x: x - 5,
      y: y - 5,
      width: 10,
      height: 10,
      className: 'custom-anchor'
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 自定义节点形状

重写 getShape 方法来自定义节点外观。

class MyNode extends BaseNode.view {
  getShape() {
    const { x, y, width, height, radius } = this.props.model
    const style = this.props.model.getNodeStyle()

    return h('g', { className: 'my-custom-node' }, [
      // 主体矩形
      h('rect', {
        ...style,
        x: x - width / 2,
        y: y - height / 2,
        width,
        height,
        rx: radius,
        ry: radius
      }),
      // 图标区域
      h('rect', {
        x: x - width / 2,
        y: y - height / 2,
        width: 30,
        height: 30,
        fill: '#000',
        fillOpacity: 0.05
      }),
      // 图标
      this.getIcon(),
      // 分隔线
      h('line', {
        x1: x - width / 2 + 30,
        y1: y - height / 2,
        x2: x - width / 2 + 30,
        y2: y + height / 2,
        stroke: '#000',
        strokeOpacity: 0.1
      })
    ])
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 添加连线校验规则

在 initNodeData 中添加自定义连线校验规则。

class MyNodeModel extends BaseNode.model {
  initNodeData(data) {
    super.initNodeData(data)

    // 连出校验规则
    const sourceRule = {
      message: '只允许连接到特定类型的节点',
      validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => {
        // 只允许连接到 filter 类型的节点
        return targetNode.type === 'filter-node'
      }
    }
    this.sourceRules.push(sourceRule)

    // 连入校验规则
    const targetRule = {
      message: '只允许从左边锚点连入',
      validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => {
        return targetAnchor.type === 'left'
      }
    }
    this.targetRules.push(targetRule)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 动态调整节点尺寸

根据文本内容动态调整节点宽度。

class MyNodeModel extends BaseNode.model {
  setAttributes() {
    if (this.text.value) {
      // 根据文本长度计算宽度
      let width = 30 + getBytesLength(this.text.value) * 9
      // 向上取整到 20 的倍数
      width = Math.ceil(width / 20) * 20
      // 设置最小宽度
      this.width = Math.max(width, 120)

      // 截断过长的文本
      const cutNum = Math.ceil((width - this.width) / 20)
      if (cutNum > 0) {
        const endIndex = this.text.value.length - cutNum
        if (endIndex > 0) {
          this.text.value = this.text.value.substring(0, endIndex) + '...'
        }
      }
    }
  }

  updateText(val) {
    super.updateText(val)
    this.setAttributes()
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 完整示例

以下是一个完整的自定义节点示例,实现一个带状态指示器的过滤器节点。

// src/components/lf-node/nodes/FilterNode.js
import { h } from '@logicflow/core'
import BaseNode from './BaseNode'
import { resolveIconPath } from '../../utils/common'

class FilterNodeView extends BaseNode.view {
  getIcon() {
    const { width, height } = this.props.model
    return h('image', {
      width: 30,
      height: 30,
      x: -width / 2,
      y: -height / 2,
      href: resolveIconPath('/images/filter.svg')
    })
  }

  getShape() {
    const { x, y, width, height, radius } = this.props.model
    const style = this.props.model.getNodeStyle()
    const status = this.props.model.properties.status

    return h('g', { className: 'lf-filter-node' }, [
      // 主体矩形
      h('rect', {
        ...style,
        x: x - width / 2,
        y: y - height / 2,
        width,
        height,
        rx: radius,
        ry: radius,
        className: status === 'processing' ? 'lf-node-processing' :
                   status === 'success' ? 'lf-node-success' :
                   status === 'error' ? 'lf-node-error' : ''
      }),
      // 图标和分隔线容器
      h('g', {
        style: 'pointer-events: none;',
        transform: `translate(${x}, ${y})`
      }, [
        // 图标背景
        h('rect', {
          x: -width / 2,
          y: -height / 2,
          width: 30,
          height: 30,
          fill: '#000',
          fillOpacity: 0.05,
          stroke: 'none'
        }),
        // 图标
        this.getIcon(),
        // 分隔线
        h('path', {
          d: `M ${30 - width / 2} ${1 - height / 2} l 0 28`,
          stroke: '#000',
          strokeOpacity: 0.1,
          strokeWidth: 1
        })
      ])
    ])
  }
}

class FilterNodeModel extends BaseNode.model {
  initNodeData(data) {
    super.initNodeData(data)
    this.width = 120
    this.height = 30
    this.defaultFill = '#f1e861'
  }

  getNodeStyle() {
    const style = super.getNodeStyle()
    const dataStyle = this.properties.view || {}

    style.fill = dataStyle.background || this.defaultFill
    this.icon = resolveIconPath(dataStyle.icon || '/images/filter.svg')

    return style
  }

  getData() {
    const data = super.getData()
    data.properties.ui = 'rulego-editor'
    return data
  }
}

export default {
  type: 'filter-node',
  model: FilterNodeModel,
  view: FilterNodeView
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

对应的国际化配置:

// src/components/i18n/zh/index.js
{
  category: {
    filter: {
      label: '过滤器',
      background: '#f1e861',
      nodeType: 'filter-node'
    }
  },
  component: {
    nodes: {
      'my-filter': {
        label: '自定义过滤器',
        icon: '/images/filter.svg',
        desc: '自定义过滤器组件',
        expression: {
          label: '过滤表达式',
          desc: '例如: msg.temperature > 50',
          rules: [
            { required: true, message: '过滤表达式是必须的' }
          ],
          component: {
            type: 'expr'
          }
        }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 参考资料

  • LogicFlow 官方文档 (opens new window)
  • LogicFlow 节点开发 (opens new window)
  • Element Plus 组件库 (opens new window)
上次更新: 2026/05/29, 04:08:46
AI 功能
分组节点

← AI 功能 分组节点→

Theme by Vdoing | Copyright © 2023-2026 RuleGo Team | Apache 2.0 License

  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式