You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Assessment模型以树形结构渲染并绑定至表单?

树形评估节点的UI渲染与表单绑定实现方案

问题背景

我有一个可包含n个子评估模型的Assessment模型,需要把AssessmentNode模型以树形结构渲染成表单元素(比如HTML文本框),一个表单里可以包含多个评估节点元素。目前已经用LIFO栈类在UI里渲染模型,现在要把弹出的节点绑定到表单上。

相关模型代码

public class Assessment
{
    public Guid ID { get; set; }
    public string Name { get; set; }
    public List<AssessmentNode> Nodes { get; set; }
    public List<Connector> Connectors { get; set; }
}
public class AssessmentNode
{
    public Guid NodeID { get; set; }
    public string Question { get; set; }
    public string Answer { get; set; }
    public bool HasChildNode { get; set; }
}

public class Connector
{
    public Guid ConnectorID { get; set; }
    public string Name { get; set; }
    public string SourceNodeId { get; set; }
    public string TargetNodeId { get; set; }
}

期望树形结构示例

  1. Q1
  2. Q2
    a) 2Qa
    b) 2Qb
  3. Q3

实现步骤

1. 构建树形数据结构

现有模型的Nodes是平级列表,Connectors用于关联父子节点,首先需要将平级节点转换为树形结构:

// 先给AssessmentNode添加Children属性,用于存储子节点
// 建议修改模型:public List<AssessmentNode> Children { get; set; } = new List<AssessmentNode>();

// 构建节点字典,快速查找节点
var nodeDict = assessment.Nodes.ToDictionary(n => n.NodeID.ToString());
var rootNodes = new List<AssessmentNode>();

foreach (var node in assessment.Nodes)
{
    // 通过Connector查找当前节点的父节点
    var parentConnector = assessment.Connectors.FirstOrDefault(c => c.TargetNodeId == node.NodeID.ToString());
    if (parentConnector == null)
    {
        // 无父节点的为根节点
        rootNodes.Add(node);
    }
    else
    {
        if (nodeDict.TryGetValue(parentConnector.SourceNodeId, out var parentNode))
        {
            parentNode.Children.Add(node);
            parentNode.HasChildNode = true; // 自动维护HasChildNode状态
        }
    }
}

2. 基于LIFO栈的树形UI渲染

利用栈的后进先出特性,迭代渲染树形结构,匹配预期的层级样式:

<!-- ASP.NET Razor视图示例,前端框架逻辑类似 -->
<ol>
@{
    var stack = new Stack<(AssessmentNode Node, int Level)>();
    // 根节点按正序压入栈,层级设为1
    foreach (var root in rootNodes)
    {
        stack.Push((root, 1));
    }

    while (stack.Count > 0)
    {
        var (currentNode, level) = stack.Pop();
        if (level == 1)
        {
            <li>
                @currentNode.Question
                <input type="text" asp-for="@currentNode.Answer" placeholder="输入答案" />
                @if (currentNode.Children.Any())
                {
                    <ul style="list-style-type: lower-alpha;">
                        <!-- 子节点倒序压入栈,保证正序渲染 -->
                        @foreach (var child in currentNode.Children.Reverse())
                        {
                            stack.Push((child, 2));
                        }
                    </ul>
                }
            </li>
        }
        else
        {
            <li>
                @currentNode.Question
                <input type="text" asp-for="@currentNode.Answer" placeholder="输入答案" />
            </li>
        }
    }
}
</ol>

3. 表单绑定实现

后端渲染场景(如ASP.NET)

直接使用框架自带的绑定语法(如asp-for),输入框会自动与AssessmentNode的Answer属性绑定,表单提交时后端可直接接收完整的Assessment对象。

纯前端场景(如Vue/React)

将树形结构数据转为前端可绑定的对象数组,用循环渲染并绑定字段:

// Vue示例
<template>
  <ol>
    <li v-for="root in rootNodes" :key="root.nodeID">
      {{ root.question }}
      <input v-model="root.answer" placeholder="输入答案" />
      <ul v-if="root.children.length" style="list-style-type: lower-alpha;">
        <li v-for="child in root.children" :key="child.nodeID">
          {{ child.question }}
          <input v-model="child.answer" placeholder="输入答案" />
        </li>
      </ul>
    </li>
    <button @click="submitForm">提交</button>
  </ol>
</template>

<script>
export default {
  data() {
    return {
      rootNodes: [] // 后端传入的树形结构数据
    }
  },
  methods: {
    submitForm() {
      // 将树形结构转回平级节点列表,匹配后端模型
      const flatNodes = [];
      const stack = [...this.rootNodes];
      while (stack.length) {
        const node = stack.pop();
        flatNodes.push({
          nodeID: node.nodeID,
          question: node.question,
          answer: node.answer,
          hasChildNode: node.children.length > 0
        });
        stack.push(...node.children.reverse());
      }
      // 提交数据到后端
      fetch('/api/assessment', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ nodes: flatNodes })
      });
    }
  }
}
</script>

4. 关键注意事项

  • 建议删除原模型的HasChildNode字段,直接通过Children.Count判断是否有子节点,避免数据不一致。
  • 栈渲染子节点时需倒序压入,保证最终渲染顺序符合预期。
  • 前端提交时需将树形结构转回平级节点列表,匹配后端Assessment.Nodes的格式。

内容的提问来源于stack exchange,提问作者Binay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 18:35:07