【问题标题】:d3.js Tree - Paging with searchd3.js 树 - 带搜索的分页
【发布时间】:2021-10-04 19:15:47
【问题描述】:

我有一个previous question,它有一个分页子节点的解决方案。

这很好用,但在查找 d.type == 'learning_event' 的节点时导致 Select2 列表出现问题。

Select2 显示节点,但在树中分页的节点不显示在 Select2 中。

显示分页节点的名称“...”而不是分页节点的名称。

fiddle

小提琴中的代码:

    //===============================================
    function learningEventCollection(d) {
        if (d.children)
            d.children.forEach(learningEventCollection);
        else if (d._children)
            d._children.forEach(learningEventCollection);
        if (!d.children && d.type == 'learning_event') learningEventData.push(d.name);
    }

    //===============================================
    function searchTree(d) {
        if (d.children)
            d.children.forEach(searchTree);
        else if (d._children)
            d._children.forEach(searchTree);
        var searchFieldValue = eval(searchField);
        if (searchFieldValue && searchFieldValue.toLowerCase().match(searchText.toLowerCase())) {
            // Walk parent chain
            var ancestors = [];
            var parent = d;
            while (typeof(parent) !== "undefined") {
                ancestors.push(parent);
                //console.log(parent);
                parent.class = "found";
                parent = parent.parent;
            }
            //console.log(ancestors);
        }
    }

    //===============================================
    function clearAll(d) {
        d.class = "";
        if (d.children)
            d.children.forEach(clearAll);
        else if (d._children)
            d._children.forEach(clearAll);
    }
    //===============================================
    function collapse(d) {

        if (d.children) {
            d._children = d.children;
            //set the parent object in all the children
            d._children.forEach(function(d1) {
                d1.parent = d;
                collapse(d1);
            });
            d.children = null;
        }
    }
    //===============================================
    function collapseAllNotFound(d) {
        if (d.children) {
            if (d.class !== "found") {
                d._children = d.children;
                d._children.forEach(collapseAllNotFound);
                d.children = null;
            } else
                d.children.forEach(collapseAllNotFound);
        }
    }
    //===============================================
    function expandAll(d) {
        if (d._children) {
            d.children = d._children;
            d.children.forEach(expandAll);
            d._children = null;
        } else if (d.children)
            d.children.forEach(expandAll);
    }

    //===============================================
    // Toggle children on click.
    function toggle(d) {
        if (d.children) {
            d._children = d.children;
            d.children = null;
        } else {
            d.children = d._children;
            d._children = null;
        }
        clearAll(root);
        update(d);
        $("#search").select2("val", "");
    }

    //===============================================
    $("#search").on("select2-selecting", function(e) {
        clearAll(root);
        expandAll(root);
        update(root);
        searchField = "d.name";
        searchText = e.object.text;
        searchTree(root);
        root.children.forEach(collapseAllNotFound);
        update(root);
    })
    
        var colourScale = d3.scale.ordinal()
        .domain(["Program", "ProgramGroup1", "ProgramGroup2"])
        .range(["#abacab", "#b67a4e", "#5a6fbb"])
    

var margin = {
    top: 20,
    right: 120,
    bottom: 20,
    left: 120
  },
  width = 2000 - margin.right - margin.left,
  height = 500 - margin.top - margin.bottom;

var i = 0,
  duration = 750,
  root;

var tree = d3.layout.tree()
  .size([height, width]);

var diagonal = d3.svg.diagonal()
  .projection(function(d) {
    return [d.y, d.x];
  });

var svg = d3.select("body").append("svg")
  .attr("width", width + margin.right + margin.left)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

root = treeData;
root.x0 = height / 2;
root.y0 = 0;

function pageNodes(d, options) {
  if (d.children) {
    d.children.forEach(c => pageNodes(c, options));
    if (options.page && options.page(d) && d.children.length > options.maxNode) {
      d.pages = {}
      const count = options.maxNode - 2;
      const l = Math.ceil(d.children.length / count);
      for (let i = 0; i < l; i++) {
        const startRange = i * count;
        const endRange = i * count + count;
        let pageNumber = i == 0 ? l - 1 : i - 1;
        d.pages[i] = d.children.slice(startRange, endRange);
        d.pages[i].unshift({
          ...d.pages[i][0],
          data: {
            name: options.getLabel ? options.getLabel(pageNumber) : "..."
          },
          pageNumber,
          name: "..."
        })

        // console.log(i, d.pages[i]);
        pageNumber = i != (l - 1) ? i + 1 : 0;
        d.pages[i].push({
          ...d.pages[i][0],
          data: {
            name: options.getLabel ? options.getLabel(pageNumber) : "..."
          },
          pageNumber,
          name: "..."
        });
      }
      d.children = d.pages[0];
      console.log(d.pages)
    }
  }
}
root.children.forEach(c => pageNodes(c, {
  maxNode: 8,
  page: function(d) {
    return d.type == "unit_group";
  }
}));


 learningEventData = [];
        learningEventCollection(root);
        learningEventDataObject = [];
        learningEventData.sort(function(a, b) {
                if (a > b) return 1; // sort
                if (a < b) return -1;
                return 0;
            })
            .filter(function(item, i, ar) {
                return ar.indexOf(item) === i;
            }) // remove duplicate items
            .filter(function(item, i, ar) {
                learningEventDataObject.push({
                    "id": i,
                    "text": item
                });
            });
        
        $("#search").select2({
            placeholder: "Select a Learning Event...",
            data: learningEventDataObject,
            containerCssClass: "search"
        });
        
        

function collapse(d) {
  if (d.children) {
    d._children = d.children;
    d._children.forEach(collapse);
    d.children = null;
  }
}

root.children.forEach(collapse);
update(root);

//svg.style("height", "500px");

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
    links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) {
    d.y = d.depth * 180;
  });

  // Update the nodes…
  var node = svg.selectAll("g.node")
    .data(nodes, function(d) {
      return d.id || (d.id = ++i);
    });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter().append("g")
    .attr("class", "node")
    .attr("transform", function(d) {
      return "translate(" + source.y0 + "," + source.x0 + ")";
    })
    .on("click", click);

  nodeEnter.append("circle")
    .attr("r", 1e-6)
    .attr('stroke', function(d) {
      return d.color ? d.color : 'blue';
    })
    .style("fill", function(d) {
      return d._children ? "#ccc" : "#fff";
    });

  nodeEnter.append("text")
    .attr("x", function(d) {
      return d.children || d._children ? -13 : 13;
    })
    .attr("dy", ".35em")
    .attr("text-anchor", function(d) {
      return d.children || d._children ? "end" : "start";
    })
    .text(function(d) {
      return d.name;
    })
    .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    });

   nodeUpdate.select("circle")
            .attr("r", 6.5)
            .attr("fill-opacity", "0.7")
            .attr("stroke-opacity", "1")
            .style("fill", function(d) {
            if (d.class !== "found") {
                if(d.type == "learning_event") return "#6eb7e3";
                if(d.type == "assessment") return "#4ecc44";
            }
                if (d.class === "found") {
                    return "#ff4136"; //red
                } else {
                    return (typeof d._children !== 'undefined') ? (colourScale(findParent(d))) : '#FFF';

                }
            })
            .style("stroke", function(d) {
             if (d.class !== "found") {
                if(d.type == "learning_event") return "#6eb7e3";
                if(d.type == "assessment") return "#4ecc44";
                if(d.name.match(/Learning Events/)) return "#6eb7e3";
                if(d.name.match(/Assessments/)) return "#4ecc44";
             }
                if (d.class === "found") {
                    return "#ff4136"; //red
                } else {
                    return colourScale(findParent(d));
                }
            });
    
    

  nodeUpdate.select("text")
    .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + source.y + "," + source.x + ")";
    })
    .remove();

  nodeExit.select("circle")
    .attr("r", 1e-6);

  nodeExit.select("text")
    .style("fill-opacity", 1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
    .data(links, function(d) {
      return d.target.id;
    });

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
    .attr("class", "link")
    .attr("d", function(d) {
      var o = {
        x: source.x0,
        y: source.y0
      };
      return diagonal({
        source: o,
        target: o
      });
    });

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d", diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d", function(d) {
      var o = {
        x: source.x,
        y: source.y
      };
      return diagonal({
        source: o,
        target: o
      });
    })
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

    function findParent(datum) {
        if (datum.depth < 2) {
            return datum.name
        } else {
            return findParent(datum.parent)
        }
    }

    function findParentLinks(datum) {
        if (datum.target.depth < 2) {
            return datum.target.name
        } else {
            return findParent(datum.target.parent)
        }
    }

// Toggle children on click.
function click(d) {
  if (d.hasOwnProperty('pageNumber')) {
    d.parent.children = d.parent.pages[d.pageNumber];
  } else if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}

【问题讨论】:

    标签: javascript jquery d3.js jquery-select2


    【解决方案1】:
    • 问题是type 被分配为learning_event 作为同一级别的其他节点,所以我将type 更改为page-toggle-node
    • 对于Select2,我已在所有节点的选项组中对其进行了修改,并相应地调整了搜索功能
    • 您使用match 比较文本时出现问题,当LE1LE15 进行比较时,它会返回true,因为LE15 包含LE1
    var treeData = {"name":"Program","column_to_sort_by":null,"type":"program","children":[{"name":"ProgramGroup1","column_to_sort_by":null,"type":"program_group","children":[{"name":"POGroup1","column_to_sort_by":null,"type":"1program_outcome_group","children":[{"name":"PO1","column_to_sort_by":null,"type":"program_outcome","children":[{"name":"Unit1","column_to_sort_by":"Unit1","children":[{"name":"UG1-LE","column_to_sort_by":null,"type":"unit_group","children":[{"name":"LE1","column_to_sort_by":"LE1","type":"learning_event"},{"name":"LE10","column_to_sort_by":"LE10","type":"learning_event"},{"name":"LE11","column_to_sort_by":"LE11","type":"learning_event"},{"name":"LE12","column_to_sort_by":"LE12","type":"learning_event"},{"name":"LE13","column_to_sort_by":"LE13","type":"learning_event"},{"name":"LE14","column_to_sort_by":"LE14","type":"learning_event"},{"name":"LE15","column_to_sort_by":"LE15","type":"learning_event"},{"name":"LE2","column_to_sort_by":"LE2","type":"learning_event"},{"name":"LE4","column_to_sort_by":"LE4","type":"learning_event"},{"name":"LE5","column_to_sort_by":"LE5","type":"learning_event"},{"name":"LE6","column_to_sort_by":"LE6","type":"learning_event"},{"name":"LE7","column_to_sort_by":"LE7","type":"learning_event"},{"name":"LE8","column_to_sort_by":"LE8","type":"learning_event"},{"name":"LE9","column_to_sort_by":"LE9","type":"learning_event"}]},{"name":"UG1-Assessments","column_to_sort_by":null,"type":"unit_group","children":[{"name":"ASST1","column_to_sort_by":"ASST1","type":"assessment"},{"name":"ASST10","column_to_sort_by":"ASST10","type":"assessment"},{"name":"ASST11","column_to_sort_by":"ASST11","type":"assessment"},{"name":"ASST13","column_to_sort_by":"ASST13","type":"assessment"},{"name":"ASST14","column_to_sort_by":"ASST14","type":"assessment"},{"name":"ASST15","column_to_sort_by":"ASST15","type":"assessment"},{"name":"ASST2","column_to_sort_by":"ASST2","type":"assessment"},{"name":"ASST3","column_to_sort_by":"ASST3","type":"assessment"},{"name":"ASST4","column_to_sort_by":"ASST4","type":"assessment"},{"name":"ASST5","column_to_sort_by":"ASST5","type":"assessment"},{"name":"ASST6","column_to_sort_by":"ASST6","type":"assessment"},{"name":"ASST7","column_to_sort_by":"ASST7","type":"assessment"},{"name":"ASST8","column_to_sort_by":"ASST8","type":"assessment"},{"name":"ASST9","column_to_sort_by":"ASST9","type":"assessment"}]}],"type":"unit"}]},{"name":"PO2","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO3","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO4","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO5","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO6","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO7","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO8","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO9","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO10","column_to_sort_by":null,"type":"program_outcome"},{"name":"PO11","column_to_sort_by":null,"type":"program_outcome"}]},{"name":"POGroup2","column_to_sort_by":null,"type":"1program_outcome_group"}]},{"name":"ProgramGroup2","column_to_sort_by":null,"type":"program_group"}]};
    
    //===============================================
    function searchTree(d) {
      if (d.pages) {
        const pageKeys = Object.keys(d.pages)
        Object.keys(d.pages).forEach(page => {
          let currentNodes = d.pages[page];
          currentNodes.forEach((node, idx) => {
            if (node.name.toLowerCase() == searchText.toLowerCase()) {
              currentNodes[idx]['parent'] = d.children[0].parent;
              d.children = currentNodes;
            }
          })
        });
      }
    
      if (d.children)
        d.children.forEach(searchTree);
      else if (d._children)
        d._children.forEach(searchTree);
      var searchFieldValue = eval(searchField);
      if (searchFieldValue && searchFieldValue.toLowerCase().match(searchText.toLowerCase())) {
        // Walk parent chain
        var ancestors = [];
        var parent = d;
        while (typeof(parent) !== "undefined") {
          ancestors.push(parent);
          //console.https://jsfiddle.net/qyn5fd0t/#runlog(parent);
          parent.class = "found";
          parent = parent.parent;
        }
        // console.log(ancestors);
      }
    }
    
    //===============================================
    function clearAll(d) {
      d.class = "";
      if (d.children)
        d.children.forEach(clearAll);
      else if (d._children)
        d._children.forEach(clearAll);
    }
    //===============================================
    function collapse(d) {
    
      if (d.children) {
        d._children = d.children;
        //set the parent object in all the children
        d._children.forEach(function(d1) {
          d1.parent = d;
          collapse(d1);
        });
        d.children = null;
      }
    }
    //===============================================
    function collapseAllNotFound(d) {
      if (d.children) {
        if (d.class !== "found") {
          d._children = d.children;
          d._children.forEach(collapseAllNotFound);
          d.children = null;
        } else
          d.children.forEach(collapseAllNotFound);
      }
    }
    //===============================================
    function expandAll(d) {
      if (d._children) {
        d.children = d._children;
        d.children.forEach(expandAll);
        d._children = null;
      } else if (d.children)
        d.children.forEach(expandAll);
    }
    
    //===============================================
    // Toggle children on click.
    function toggle(d) {
      if (d.children) {
        d._children = d.children;
        d.children = null;
      } else {
        d.children = d._children;
        d._children = null;
      }
      clearAll(root);
      update(d);
      $("#search").select2("val", "");
    }
    
    //===============================================
    $("#search").on("select2-selecting", function(e) {
      clearAll(root);
      expandAll(root);
      update(root);
      searchField = "d.name";
      searchText = e.object.text;
      searchTree(root);
      root.children.forEach(collapseAllNotFound);
      update(root);
    })
    
    var colourScale = d3.scale.ordinal()
      .domain(["Program", "ProgramGroup1", "ProgramGroup2"])
      .range(["#abacab", "#b67a4e", "#5a6fbb"])
    
    var margin = {
        top: 20,
        right: 120,
        bottom: 20,
        left: 120
      },
      width = 2000 - margin.right - margin.left,
      height = 500 - margin.top - margin.bottom;
    
    var i = 0,
      duration = 750,
      root;
    
    var tree = d3.layout.tree()
      .size([height, width]);
    
    var diagonal = d3.svg.diagonal()
      .projection(function(d) {
        return [d.y, d.x];
      });
    
    var svg = d3.select("body").append("svg")
      .attr("width", width + margin.right + margin.left)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    
    root = treeData;
    root.x0 = height / 2;
    root.y0 = 0;
    
    function pageNodes(d, options) {
      if (d.children) {
        d.children.forEach(c => pageNodes(c, options));
        if (options.isPageable && options.isPageable(d) && d.children.length > options.maxNode) {
          d.pages = {}
          const count = options.maxNode - 2;
          const l = Math.ceil(d.children.length / count);
          for (let i = 0; i < l; i++) {
            const startRange = i * count;
            const endRange = i * count + count;
            let pageNumber = i == 0 ? l - 1 : i - 1;
            d.pages[i] = d.children.slice(startRange, endRange);
            options.page && options.page(d.pages[i]);
            d.pages[i].unshift({
              ...d.pages[i][0],
              data: {
                name: options.getLabel ? options.getLabel(pageNumber) : "..."
              },
              pageNumber,
              name: "...",
              type: "page-toggle-node"
            })
    
            // console.log(i, d.pages[i]);
            pageNumber = i != (l - 1) ? i + 1 : 0;
            d.pages[i].push({
              ...d.pages[i][0],
              data: {
                name: options.getLabel ? options.getLabel(pageNumber) : "..."
              },
              pageNumber,
              name: "...",
              type: "page-toggle-node"
            });
          }
          d.children = d.pages[0];
        }
      }
    }
    
    //===============================================
    const groupLearningOptions = [];
    let groupLearningOptionsCounter = 0;
    root.children.forEach(c => pageNodes(c, {
      maxNode: 8,
      isPageable: function(d) {
        return d.type == "unit_group";
      },
      page: function(nodes) {
        if (nodes[0].type == "learning_event") {
          groupLearningOptions.push({
            text: `Group ${groupLearningOptions.length + 1}`,
            children: nodes.map(node => ({
              id: groupLearningOptionsCounter++,
              text: node.name
            }))
            .sort((a, b) => (a.text > b.text) ? 1 : -1)
          })
        }
      }
    }));
    // console.log(groupLearningOptions);
    
    $("#search").select2({
      placeholder: "Select a Learning Event...",
      data: {
        results: groupLearningOptions
      },
      containerCssClass: "search"
    });
    
    
    function collapse(d) {
      if (d.children) {
        d._children = d.children;
        d._children.forEach(collapse);
        d.children = null;
      }
    }
    
    root.children.forEach(collapse);
    update(root);
    
    //svg.style("height", "500px");
    
    function update(source) {
    
      // Compute the new tree layout.
      var nodes = tree.nodes(root).reverse(),
        links = tree.links(nodes);
    
      // Normalize for fixed-depth.
      nodes.forEach(function(d) {
        d.y = d.depth * 180;
      });
    
      // Update the nodes…
      var node = svg.selectAll("g.node")
        .data(nodes, function(d) {
          return d.id || (d.id = ++i);
        });
    
      // Enter any new nodes at the parent's previous position.
      var nodeEnter = node.enter().append("g")
        .attr("class", "node")
        .attr("transform", function(d) {
          return "translate(" + source.y0 + "," + source.x0 + ")";
        })
        .on("click", click);
    
      nodeEnter.append("circle")
        .attr("r", 1e-6)
        .attr('stroke', function(d) {
          return d.color ? d.color : 'blue';
        })
        .style("fill", function(d) {
          return d._children ? "#ccc" : "#fff";
        });
    
      nodeEnter.append("text")
        .attr("x", function(d) {
          return d.children || d._children ? -13 : 13;
        })
        .attr("dy", ".35em")
        .attr("text-anchor", function(d) {
          return d.children || d._children ? "end" : "start";
        })
        .text(function(d) {
          return d.name;
        })
        .style("fill-opacity", 1e-6);
    
      // Transition nodes to their new position.
      var nodeUpdate = node.transition()
        .duration(duration)
        .attr("transform", function(d) {
          return "translate(" + d.y + "," + d.x + ")";
        });
    
      nodeUpdate.select("circle")
        .attr("r", 6.5)
        .attr("fill-opacity", "0.7")
        .attr("stroke-opacity", "1")
        .style("fill", function(d) {
          if (d.class !== "found") {
            if (d.type == "learning_event") return "#6eb7e3";
            if (d.type == "assessment") return "#4ecc44";
          }
          if (d.class === "found") {
            return "#ff4136"; //red
          } else {
            return (typeof d._children !== 'undefined') ? (colourScale(findParent(d))) : '#FFF';
    
          }
        })
        .style("stroke", function(d) {
          if (d.class !== "found") {
            if (d.type == "learning_event") return "#6eb7e3";
            if (d.type == "assessment") return "#4ecc44";
            if (d.name.match(/Learning Events/)) return "#6eb7e3";
            if (d.name.match(/Assessments/)) return "#4ecc44";
          }
          if (d.class === "found") {
            return "#ff4136"; //red
          } else {
            return colourScale(findParent(d));
          }
        });
    
    
    
      nodeUpdate.select("text")
        .style("fill-opacity", 1);
    
      // Transition exiting nodes to the parent's new position.
      var nodeExit = node.exit().transition()
        .duration(duration)
        .attr("transform", function(d) {
          return "translate(" + source.y + "," + source.x + ")";
        })
        .remove();
    
      nodeExit.select("circle")
        .attr("r", 1e-6);
    
      nodeExit.select("text")
        .style("fill-opacity", 1e-6);
    
      // Update the links…
      var link = svg.selectAll("path.link")
        .data(links, function(d) {
          return d.target.id;
        });
    
      // Enter any new links at the parent's previous position.
      link.enter().insert("path", "g")
        .attr("class", "link")
        .attr("d", function(d) {
          var o = {
            x: source.x0,
            y: source.y0
          };
          return diagonal({
            source: o,
            target: o
          });
        });
    
      // Transition links to their new position.
      link.transition()
        .duration(duration)
        .attr("d", diagonal);
    
      // Transition exiting nodes to the parent's new position.
      link.exit().transition()
        .duration(duration)
        .attr("d", function(d) {
          var o = {
            x: source.x,
            y: source.y
          };
          return diagonal({
            source: o,
            target: o
          });
        })
        .remove();
    
      // Stash the old positions for transition.
      nodes.forEach(function(d) {
        d.x0 = d.x;
        d.y0 = d.y;
      });
    }
    
    function findParent(datum) {
      if (datum.depth < 2) {
        return datum.name
      } else {
        return findParent(datum.parent)
      }
    }
    
    function findParentLinks(datum) {
      if (datum.target.depth < 2) {
        return datum.target.name
      } else {
        return findParent(datum.target.parent)
      }
    }
    
    // Toggle children on click.
    function click(d) {
      if (d.hasOwnProperty('pageNumber')) {
        d.parent.children = d.parent.pages[d.pageNumber];
      } else if (d.children) {
        d._children = d.children;
        d.children = null;
      } else {
        d.children = d._children;
        d._children = null;
      }
      update(d);
    }
    

    在这里工作example

    【讨论】:

    • 再次感谢钱丹!这行得通。搜索 Select2 是否可以列出没有 Groups 的学习事件?
    • @IlludiumPu36 您只需要在 pageNodes 调用之前创建 Select2 数据,这将在单个列表中创建它。
    • 不确定我是否理解,抱歉。我已经将 groupLearningOptions 放在 pageNode 函数之前,它没有区别jsfiddle.net/hbpv4nrf
    • @IlludiumPu36 您必须输入您为创建learning_event 列表而创建的learningEventCollection 函数,参考您可以查看here
    猜你喜欢
    • 2021-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-14
    • 2019-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多