661 lines
19 KiB
C#
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( " " );
|
|
}
|
|
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 );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
}
|