Files
2026-07-10 15:25:05 +08:00

661 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using Zhaizj.Framework.Web;
namespace Zhaizj.Framework {
/// <summary>
/// 树状节点接口
/// </summary>
public interface INode {
/// <summary>
/// 节点的 Id
/// </summary>
int Id { get; set; }
/// <summary>
/// 节点的名称
/// </summary>
String Name { get; set; }
/// <summary>
/// 上级节点的 Id
/// </summary>
int ParentId { get; set; }
}
/// <summary>
/// 节点绑定器
/// </summary>
public interface INodeBinder {
String Bind( INode node );
}
/// <summary>
/// 树状节点(将 T 做了封装,便于操作)
/// </summary>
/// <typeparam name="T">节点必须实现了 INode 接口</typeparam>
public class Node<T> where T : INode {
public Node() {
}
public Node( T node ) {
_rawNode = node;
}
private T _rawNode;
/// <summary>
/// 获取原始节点数据
/// </summary>
/// <returns></returns>
public T getNode() {
return _rawNode;
}
//-----------------------------------------------------
private Tree<T> _tree;
public void setTree( Tree<T> tree ) {
_tree = tree;
}
private Node<T> _parent;
/// <summary>
/// 获取上级节点
/// </summary>
/// <returns></returns>
public Node<T> getParent() {
if (this.getNode().ParentId == 0) return null;
if (_parent == null) {
_parent = _tree.FindById( this.getNode().ParentId );
}
return _parent;
}
private int _depth = -1;
/// <summary>
/// 获取节点的深度
/// </summary>
/// <returns></returns>
public int getDepth() {
if (_depth < 0) {
if (this.getNode().ParentId == 0) {
_depth = 0;
}
else {
if (this.getParent() == null) {
_depth = 0;
}
else {
_depth = this.getParent().getDepth() + 1;
}
}
}
return _depth;
}
private List<Node<T>> _children = new List<Node<T>>();
/// <summary>
/// 获取所有子节点
/// </summary>
/// <returns></returns>
public List<Node<T>> getChildren() {
return _children;
}
internal void addChildren( Node<T> node ) {
_children.Add( node );
}
//-------------------------------------------------------------
private Node<T> _prevNode;
private Node<T> _nextNode;
/// <summary>
/// 获取前一个节点
/// </summary>
/// <returns></returns>
public Node<T> getPrev() {
return _prevNode;
}
/// <summary>
/// 获取后一个节点
/// </summary>
/// <returns></returns>
public Node<T> getNext() {
return _nextNode;
}
internal void setPrev( Node<T> node ) {
_prevNode = node;
}
internal void setNext( Node<T> node ) {
_nextNode = node;
}
internal Boolean indent() {
if (getPrev() == null) return true;
return getDepth() > getPrev().getDepth();
}
internal Boolean outdent() {
if (getPrev() == null) return false;
return getDepth() < getPrev().getDepth();
}
internal int getOutdentCount() {
return getPrev().getDepth() - getDepth();
}
}
/// <summary>
/// 树状结构
/// </summary>
/// <typeparam name="T">节点必须实现了 INode 接口</typeparam>
public class Tree<T> where T : INode {
private static readonly ILog logger = LogManager.GetLogger( "Zhaizj.Framework.Tree" );
private List<T> _rawList;
public Tree( List<T> nodeList ) {
_rawList = nodeList;
initProxyList();
}
//------------------------------------------------------------
/// <summary>
/// 根据 Id 检索节点
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Node<T> FindById( int id ) {
return getById( id );
}
/// <summary>
/// 根据 Id 获取它的上级节点
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Node<T> FindParent( int id ) {
Node<T> proxy = getById( id );
if (proxy == null) return default( Node<T> );
return proxy.getParent();
}
/// <summary>
/// 根据 Id,获取它的节点路径(从根级开始到当前节点)
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public List<Node<T>> FindPath( int id ) {
List<Node<T>> nodePath = new List<Node<T>>();
Node<T> proxy = getById( id );
if (proxy == null) return nodePath;
nodePath.Add( proxy );
Node<T> currentNode = proxy;
while (true) {
Node<T> parent = currentNode.getParent();
if (parent == null) break;
nodePath.Add( parent );
currentNode = parent;
}
nodePath.Reverse();
return nodePath;
}
/// <summary>
/// 获取所有根节点
/// </summary>
/// <returns></returns>
public List<Node<T>> FindRoots() {
return getRoots();
}
/// <summary>
/// 根据 Id,获取所有子节点
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public List<Node<T>> FindChildren( int id ) {
Node<T> proxy = getById( id );
if (proxy == null) return new List<Node<T>>();
return proxy.getChildren();
}
private List<Node<T>> _allOrdered;
/// <summary>
/// 获取所有节点(经过排序)
/// </summary>
/// <returns></returns>
public List<Node<T>> FindAllOrdered() {
if (_allOrdered == null) {
List<Node<T>> results = new List<Node<T>>();
List<Node<T>> roots = this.FindRoots();
foreach (Node<T> node in roots) {
addSubProxyNodes( results, node );
}
_allOrdered = results;
}
return _allOrdered;
}
private void addSubProxyNodes( List<Node<T>> results, Node<T> parentnode ) {
results.Add( parentnode );
List<Node<T>> subnodes = parentnode.getChildren();
foreach (Node<T> node in subnodes) {
addSubProxyNodes( results, node );
}
}
//------------------------------------------------------------
/// <summary>
/// 根据 Id 获取节点
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public T GetById( int id ) {
Node<T> proxy = getById( id );
if (proxy != null) return proxy.getNode();
return default( T );
}
/// <summary>
/// 根据 Id 获取节点的深度
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public int GetDepth( int id ) {
Node<T> proxy = getById( id );
if (proxy != null) return proxy.getDepth();
return 0;
}
/// <summary>
/// 根据 Id 获取上级节点
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public T GetParent( int id ) {
Node<T> proxy = getById( id );
if (proxy == null) return default( T );
Node<T> parentProxy = proxy.getParent();
if (parentProxy != null) return parentProxy.getNode();
return default( T );
}
/// <summary>
/// 根据 Id,获取节点的路径(从根级开始到当前节点)
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public List<T> GetPath( int id ) {
List<T> nodePath = new List<T>();
Node<T> proxy = getById( id );
if (proxy == null) return nodePath;
nodePath.Add( proxy.getNode() );
Node<T> tempNode = proxy;
while (true) {
Node<T> parent = tempNode.getParent();
if (parent == null) break;
nodePath.Add( parent.getNode() );
tempNode = parent;
}
nodePath.Reverse();
return nodePath;
}
/// <summary>
/// 根据 Id,获取所有下级节点
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public List<T> GetChildren( int id ) {
Node<T> proxy = getById( id );
if (proxy == null) return new List<T>();
List<Node<T>> children = proxy.getChildren();
List<T> results = new List<T>();
foreach (Node<T> px in children) results.Add( px.getNode() );
return results;
}
/// <summary>
/// 获取所有根节点
/// </summary>
/// <returns></returns>
public List<T> GetRoots() {
List<Node<T>> nodes = this.FindRoots();
List<T> results = new List<T>();
foreach (Node<T> px in nodes) {
results.Add( px.getNode() );
}
return results;
}
private List<T> _allOrderedNode;
/// <summary>
/// 获取所有排序过的节点
/// </summary>
/// <returns></returns>
public List<T> GetAllOrdered() {
if (_allOrderedNode == null) {
List<Node<T>> nodes = this.FindAllOrdered();
List<T> results = new List<T>();
foreach (Node<T> px in nodes) {
results.Add( px.getNode() );
}
_allOrderedNode = results;
}
return _allOrderedNode;
}
//----------------------------------------------------------------
/// <summary>
/// 树状下拉列表控件
/// </summary>
/// <param name="dropName">下拉列表name</param>
/// <param name="selectValue">当前选定的值</param>
/// <returns></returns>
public String DropList( String dropName, int selectValue ) {
return DropList( dropName, selectValue, 0, null );
}
/// <summary>
/// 获取下拉列表select
/// </summary>
/// <param name="dropName">下拉列表的name和id</param>
/// <param name="selectValue">当前选中项的值</param>
/// <param name="nodeId">当select用于设置父节点之时,此参数表示将节点自己排除在下拉列表之外,防止将自己作为自己的父节点</param>
/// <param name="rootSelectName">根节点(并不存在,但你可以给它取个名称)</param>
/// <returns></returns>
public String DropList( String dropName, int selectValue, int nodeId, String rootSelectName ) {
List<Node<T>> list = this.FindAllOrdered();
StringBuilder builder = new StringBuilder();
builder.AppendFormat( "<select name=\"{0}\" id=\"{0}\">", dropName );
int selval = getSelectedValue( dropName, selectValue );
if (strUtil.HasText( rootSelectName )) {
String strSel = selectValue == 0 ? "selected" : "";
builder.AppendFormat( "<option value=\"{0}\" {1}>{2}</option>", 0, strSel, rootSelectName );
builder.AppendLine();
}
for (int i = 0; i < list.Count; i++) {
if (list[i].getNode().Id == nodeId) continue;
int depth = list[i].getDepth();
String name = getBlank( depth ) + list[i].getNode().Name;
String strSel = (selval == list[i].getNode().Id ? "selected" : "");
builder.AppendFormat( "<option value=\"{0}\" {1}>{2}</option>", list[i].getNode().Id, strSel, name );
builder.AppendLine();
}
builder.Append( "</select>" );
return builder.ToString();
}
private static String getBlank( int depth ) {
StringBuilder builder = new StringBuilder();
int length = depth * 4;
for (int i = 0; i <= length; i++) {
builder.Append( "&nbsp;" );
}
return builder.ToString();
}
private static int getSelectedValue( String dropName, int val ) {
if (CurrentRequest.getHttpMethod().Equals( "POST" )) {
return cvt.ToInt( CurrentRequest.getForm( dropName ) );
}
return val;
}
/// <summary>
/// 获取树状结构的 html
/// </summary>
/// <param name="treeId"></param>
/// <returns></returns>
public String RenderList( String treeId ) {
return RenderList( treeId, true, null, 0 );
}
/// <summary>
/// 获取树状结构的 html
/// </summary>
/// <param name="treeId"></param>
/// <param name="showChildren"></param>
/// <param name="binder"></param>
/// <param name="currentNodeId"></param>
/// <returns></returns>
public String RenderList( String treeId, Boolean showChildren, INodeBinder binder, int currentNodeId ) {
List<Node<T>> list = this.FindAllOrdered();
StringBuilder builder = new StringBuilder();
int indent = 0;
for (int i = 0; i < list.Count; i++) {
Node<T> node = list[i];
// 是否添加 ul 以及是否隐藏
if (i == 0) {
builder.Append( "<ul class=\"wTree\" id=\"" );
builder.Append( treeId );
builder.Append( "\">" );
indent++;
}
else if (node.indent()) {
String displayCls = getDisplayCls( showChildren, currentNodeId, node.getNode().Id );
builder.Append( "<ul" );
builder.Append( displayCls );
builder.Append( ">" );
indent++;
}
else if (node.outdent()) {
int outdentCount = node.getOutdentCount();
for (int x = 0; x < outdentCount; x++) {
builder.Append( "</ul>" );
indent--;
}
}
// li 的 class
builder.Append( "<li" );
String cls = getListClass( showChildren, currentNodeId, node );
if (strUtil.HasText( cls )) {
builder.Append( " class=\"" );
builder.Append( cls );
builder.Append( "\"" );
}
builder.Append( ">" );
String item = (binder == null ? node.getNode().Name : binder.Bind( node.getNode() ));
builder.Append( item );
builder.Append( "</li>" );
}
for (int y = 0; y < indent; y++) {
builder.Append( "</ul>" );
}
return builder.ToString();
}
private String getListClass( Boolean showChildren, int currentNodeId, Node<T> node ) {
String cls = "";
if (node.getChildren().Count > 0) {
cls += "parentNode";
if (isOpenedNode( currentNodeId, node.getNode().Id, showChildren ))
cls += " collapseNode";
else
cls += " expandNode";
}
if (node.getNode().Id == currentNodeId) cls += " currentNode";
return cls.Trim();
}
private String getDisplayCls( Boolean showChildren, int currentNodeId, int nodeId ) {
if (showChildren) return "";
List<Node<T>> path = FindPath( currentNodeId );
foreach (Node<T> parent in path) {
if (parent.getNext() == null) continue;
if (nodeId == parent.getNext().getNode().Id) return "";
}
return " class=\"hide\"";
}
private Boolean isOpenedNode( int currentNodeId, int nodeId, Boolean showChildren ) {
if (showChildren) return true;
List<Node<T>> path = FindPath( currentNodeId );
foreach (Node<T> parent in path) {
if (nodeId == parent.getNode().Id) return true;
}
return false;
}
//----------------------------------------------------------------
private Node<T> getById( int id ) {
if (getIdCache().ContainsKey( id ) == false) return default( Node<T> );
return getIdCache()[id];
}
//----------------------------------------------------------------
private List<Node<T>> _proxyList;
private Dictionary<int, Node<T>> _idcache;
private List<Node<T>> _roots;
private List<Node<T>> getNodeList() {
return _proxyList;
}
private Dictionary<int, Node<T>> getIdCache() {
return _idcache;
}
private List<Node<T>> getRoots() {
return _roots;
}
private void initProxyList() {
_proxyList = new List<Node<T>>();
_idcache = new Dictionary<int, Node<T>>();
_roots = new List<Node<T>>();
// 缓存id
foreach (T node in _rawList) {
Node<T> proxy = new Node<T>( node );
proxy.setTree( this );
_proxyList.Add( proxy );
_idcache.Add( node.Id, proxy );
}
// 缓存children
foreach (Node<T> node in _proxyList) {
if (node.getNode().ParentId == 0) {
_roots.Add( node );
}
else {
Node<T> p = node.getParent();
if (p == null) {
logger.Error( string.Format( "parent does not exist: id={0}, name={1}, parentId={2}", node.getNode().Id, node.getNode().Name, node.getNode().ParentId ) );
}
else {
p.addChildren( node );
}
}
}
// 排序并缓存pre和next
List<Node<T>> orderedList = FindAllOrdered();
for (int i = 0; i < orderedList.Count; i++) {
if (i == 0) continue;
Node<T> node = orderedList[i];
Node<T> pre = orderedList[i - 1];
node.setPrev( pre );
pre.setNext( node );
}
}
}
}