NewLife/NewLife.Remoting

优化ZeroServer节点管理,完善页面查询
大石头 authored at 2024-06-30 10:44:39
2926328
Tree
1 Parent(s) d6adf29
Summary: 17 changed files with 347 additions and 56 deletions.
Added +13 -0
Added +27 -0
Modified +29 -27
Modified +10 -12
Modified +8 -12
Modified +34 -0
Added +9 -0
Added +22 -0
Added +18 -0
Added +12 -0
Added +21 -0
Added +12 -0
Modified +2 -1
Added +12 -0
Added +58 -0
Added +60 -0
Modified +0 -4
Added +13 -0
diff --git a/Samples/IoTZero/Areas/IoT/Views/NodeOnline/_List_Search.cshtml b/Samples/IoTZero/Areas/IoT/Views/NodeOnline/_List_Search.cshtml
new file mode 100644
index 0000000..a82123a
--- /dev/null
+++ b/Samples/IoTZero/Areas/IoT/Views/NodeOnline/_List_Search.cshtml
@@ -0,0 +1,13 @@
+@using Stardust.Data.Nodes;
+@using NewLife;
+@using NewLife.Web;
+@using XCode;
+@{
+    var fact = ViewBag.Factory as IEntityFactory;
+    var page = ViewBag.Page as Pager;
+}
+<div class="form-group">
+    <label for="category" class="control-label">类别:</label>
+    @Html.ForDropDownList("category", Node.FindAllCategory(), page["category"], "全部", true)
+</div>
+@await Html.PartialAsync("_Area2", "")
Added +27 -0
diff --git a/Samples/IoTZero/Areas/IoT/Views/NodeOnline/_List_Toolbar_Batch.cshtml b/Samples/IoTZero/Areas/IoT/Views/NodeOnline/_List_Toolbar_Batch.cshtml
new file mode 100644
index 0000000..91bbf2c
--- /dev/null
+++ b/Samples/IoTZero/Areas/IoT/Views/NodeOnline/_List_Toolbar_Batch.cshtml
@@ -0,0 +1,27 @@
+@using NewLife.Common;
+@using System.Collections.Generic;
+@{
+    var set = ViewBag.PageSetting as PageSetting;
+    var page = ViewBag.Page as Pager;
+}
+@if (set.EnableSelect)
+{
+    <button type="button" class="btn btn-success btn-sm" data-action="action" data-url="@Url.Action("CheckUpgrade")" data-fields="keys" disabled>
+        检查更新
+    </button>
+    <button type="button" class="btn btn-success btn-sm" data-action="action" data-url="@Url.Action("Restart")" data-fields="keys" disabled>
+        重启服务
+    </button>
+    <button type="button" class="btn btn-success btn-sm" data-action="action" data-url="@Url.Action("Reboot")" data-fields="keys" disabled>
+        重启系统
+    </button>
+    <div class="form-group">
+        <label for="command" class="control-label">命令:</label>
+        @Html.TextBox("command", page["command"])
+        <label for="argument" class="control-label">参数:</label>
+        @Html.TextBox("argument", page["argument"])
+    </div>
+    <button type="button" class="btn btn-purple btn-sm" data-action="action" data-url="@Url.Action("Execute")" data-fields="keys,command,argument">
+        执行命令
+    </button>
+}
\ No newline at end of file
Modified +29 -27
diff --git a/Samples/ZeroServer/Areas/Nodes/Controllers/NodeController.cs b/Samples/ZeroServer/Areas/Nodes/Controllers/NodeController.cs
index f2c23b0..7b8d47e 100644
--- a/Samples/ZeroServer/Areas/Nodes/Controllers/NodeController.cs
+++ b/Samples/ZeroServer/Areas/Nodes/Controllers/NodeController.cs
@@ -14,50 +14,52 @@ namespace ZeroServer.Areas.Nodes.Controllers;
 /// <summary>节点</summary>
 [Menu(30, true, Icon = "fa-table")]
 [NodesArea]
-public class NodeController : EntityController<Node>
+public class NodeController : NodeEntityController<Node>
 {
     static NodeController()
     {
-        //LogOnChange = true;
+        LogOnChange = true;
 
         //ListFields.RemoveField("Id", "Creator");
         ListFields.RemoveCreateField().RemoveRemarkField();
 
-        //{
-        //    var df = ListFields.GetField("Code") as ListField;
-        //    df.Url = "?code={Code}";
-        //}
-        //{
-        //    var df = ListFields.AddListField("devices", null, "Onlines");
-        //    df.DisplayName = "查看设备";
-        //    df.Url = "Device?groupId={Id}";
-        //    df.DataVisible = e => (e as Node).Devices > 0;
-        //}
-        //{
-        //    var df = ListFields.GetField("Kind") as ListField;
-        //    df.GetValue = e => ((Int32)(e as Node).Kind).ToString("X4");
-        //}
-        //ListFields.TraceUrl("TraceId");
+        {
+            var df = ListFields.GetField("Name") as ListField;
+            df.Url = "/Nodes/Node/Detail?id={Id}";
+            df.Target = "_blank";
+        }
+        {
+            var df = ListFields.AddListField("Log", "UpdateTime");
+            df.DisplayName = "日志";
+            df.Url = "/Admin/Log?category=节点&linkId={Id}";
+            df.Target = "_frame";
+        }
     }
 
-    //private readonly ITracer _tracer;
-
-    //public NodeController(ITracer tracer)
-    //{
-    //    _tracer = tracer;
-    //}
-
     /// <summary>高级搜索。列表页查询、导出Excel、导出Json、分享页等使用</summary>
     /// <param name="p">分页器。包含分页排序参数,以及Http请求参数</param>
     /// <returns></returns>
     protected override IEnumerable<Node> Search(Pager p)
     {
-        //var deviceId = p["deviceId"].ToInt(-1);
-        //var enable = p["enable"]?.Boolean();
+        var nodeId = p["Id"].ToInt(-1);
+        if (nodeId > 0)
+        {
+            var node = Node.FindByKey(nodeId);
+            if (node != null) return [node];
+        }
+
+        var rids = p["areaId"].SplitAsInt("/");
+        var provinceId = rids.Length > 0 ? rids[0] : -1;
+        var cityId = rids.Length > 1 ? rids[1] : -1;
+
+        var category = p["category"];
+        var product = p["product"];
+        var version = p["version"];
+        var enable = p["enable"]?.ToBoolean();
 
         var start = p["dtStart"].ToDateTime();
         var end = p["dtEnd"].ToDateTime();
 
-        return Node.Search(start, end, p["Q"], p);
+        return Node.Search(provinceId, cityId, category, product, version, enable, start, end, p["Q"], p);
     }
 }
\ No newline at end of file
Modified +10 -12
diff --git a/Samples/ZeroServer/Areas/Nodes/Controllers/NodeHistoryController.cs b/Samples/ZeroServer/Areas/Nodes/Controllers/NodeHistoryController.cs
index af41563..a3d4316 100644
--- a/Samples/ZeroServer/Areas/Nodes/Controllers/NodeHistoryController.cs
+++ b/Samples/ZeroServer/Areas/Nodes/Controllers/NodeHistoryController.cs
@@ -14,13 +14,13 @@ namespace ZeroServer.Areas.Nodes.Controllers;
 /// <summary>节点历史</summary>
 [Menu(10, true, Icon = "fa-table")]
 [NodesArea]
-public class NodeHistoryController : EntityController<NodeHistory>
+public class NodeHistoryController : NodeEntityController<NodeHistory>
 {
     static NodeHistoryController()
     {
         //LogOnChange = true;
 
-        //ListFields.RemoveField("Id", "Creator");
+        ListFields.RemoveField("ProvinceName");
         ListFields.RemoveCreateField().RemoveRemarkField();
 
         //{
@@ -40,24 +40,22 @@ public class NodeHistoryController : EntityController<NodeHistory>
         ListFields.TraceUrl("TraceId");
     }
 
-    //private readonly ITracer _tracer;
-
-    //public NodeHistoryController(ITracer tracer)
-    //{
-    //    _tracer = tracer;
-    //}
-
     /// <summary>高级搜索。列表页查询、导出Excel、导出Json、分享页等使用</summary>
     /// <param name="p">分页器。包含分页排序参数,以及Http请求参数</param>
     /// <returns></returns>
     protected override IEnumerable<NodeHistory> Search(Pager p)
     {
-        //var deviceId = p["deviceId"].ToInt(-1);
-        //var enable = p["enable"]?.Boolean();
+        var rids = p["areaId"].SplitAsInt("/");
+        var provinceId = rids.Length > 0 ? rids[0] : -1;
+        var cityId = rids.Length > 1 ? rids[1] : -1;
+
+        var nodeId = p["nodeId"].ToInt(-1);
+        var action = p["action"];
+        var success = p["success"]?.ToBoolean();
 
         var start = p["dtStart"].ToDateTime();
         var end = p["dtEnd"].ToDateTime();
 
-        return NodeHistory.Search(start, end, p["Q"], p);
+        return NodeHistory.Search(nodeId, provinceId, cityId, action, success, start, end, p["Q"], p);
     }
 }
\ No newline at end of file
Modified +8 -12
diff --git a/Samples/ZeroServer/Areas/Nodes/Controllers/NodeOnlineController.cs b/Samples/ZeroServer/Areas/Nodes/Controllers/NodeOnlineController.cs
index 6196cbe..ce41276 100644
--- a/Samples/ZeroServer/Areas/Nodes/Controllers/NodeOnlineController.cs
+++ b/Samples/ZeroServer/Areas/Nodes/Controllers/NodeOnlineController.cs
@@ -14,13 +14,13 @@ namespace ZeroServer.Areas.Nodes.Controllers;
 /// <summary>节点在线</summary>
 [Menu(20, true, Icon = "fa-table")]
 [NodesArea]
-public class NodeOnlineController : EntityController<NodeOnline>
+public class NodeOnlineController : NodeEntityController<NodeOnline>
 {
     static NodeOnlineController()
     {
         //LogOnChange = true;
 
-        //ListFields.RemoveField("Id", "Creator");
+        ListFields.RemoveField("ProvinceName", "Token");
         ListFields.RemoveCreateField().RemoveRemarkField();
 
         //{
@@ -40,24 +40,20 @@ public class NodeOnlineController : EntityController<NodeOnline>
         //ListFields.TraceUrl("TraceId");
     }
 
-    //private readonly ITracer _tracer;
-
-    //public NodeOnlineController(ITracer tracer)
-    //{
-    //    _tracer = tracer;
-    //}
-
     /// <summary>高级搜索。列表页查询、导出Excel、导出Json、分享页等使用</summary>
     /// <param name="p">分页器。包含分页排序参数,以及Http请求参数</param>
     /// <returns></returns>
     protected override IEnumerable<NodeOnline> Search(Pager p)
     {
-        //var deviceId = p["deviceId"].ToInt(-1);
-        //var enable = p["enable"]?.Boolean();
+        var nodeId = p["nodeId"].ToInt(-1);
+        var rids = p["areaId"].SplitAsInt("/");
+        var provinceId = rids.Length > 0 ? rids[0] : -1;
+        var cityId = rids.Length > 1 ? rids[1] : -1;
+        var category = p["category"];
 
         var start = p["dtStart"].ToDateTime();
         var end = p["dtEnd"].ToDateTime();
 
-        return NodeOnline.Search(start, end, p["Q"], p);
+        return NodeOnline.Search(nodeId, provinceId, cityId, category, start, end, p["Q"], p);
     }
 }
\ No newline at end of file
Modified +34 -0
diff --git a/Samples/ZeroServer/Areas/Nodes/NodesArea.cs b/Samples/ZeroServer/Areas/Nodes/NodesArea.cs
index 8b9a063..496011e 100644
--- a/Samples/ZeroServer/Areas/Nodes/NodesArea.cs
+++ b/Samples/ZeroServer/Areas/Nodes/NodesArea.cs
@@ -1,6 +1,9 @@
 using System.ComponentModel;
+using Microsoft.AspNetCore.Mvc.Filters;
 using NewLife;
 using NewLife.Cube;
+using NewLife.Cube.ViewModels;
+using XCode;
 
 namespace ZeroServer.Areas.Nodes;
 
@@ -8,4 +11,35 @@ namespace ZeroServer.Areas.Nodes;
 public class NodesArea : AreaBase
 {
     public NodesArea() : base(nameof(NodesArea).TrimEnd("Area")) { }
+}
+
+/// <summary>节点管理控制器基类。抽象共性能力</summary>
+/// <typeparam name="TEntity"></typeparam>
+public abstract class NodeEntityController<TEntity> : EntityController<TEntity> where TEntity : Entity<TEntity>, new()
+{
+    public override void OnActionExecuting(ActionExecutingContext filterContext)
+    {
+        base.OnActionExecuting(filterContext);
+
+        var nodeId = GetRequest("Id").ToInt(-1);
+        if (nodeId <= 0) nodeId = GetRequest("nodeId").ToInt(-1);
+        if (nodeId > 0)
+        {
+            PageSetting.NavView = "_Node_Nav";
+            PageSetting.EnableNavbar = false;
+        }
+    }
+
+    protected override FieldCollection OnGetFields(ViewKinds kind, Object model)
+    {
+        var fields = base.OnGetFields(kind, model);
+
+        if (kind == ViewKinds.List)
+        {
+            var nodeId = GetRequest("nodeId").ToInt(-1);
+            if (nodeId > 0) fields.RemoveField("NodeName");
+        }
+
+        return fields;
+    }
 }
\ No newline at end of file
Added +9 -0
diff --git a/Samples/ZeroServer/Areas/Nodes/Views/_ViewImports.cshtml b/Samples/ZeroServer/Areas/Nodes/Views/_ViewImports.cshtml
new file mode 100644
index 0000000..8278207
--- /dev/null
+++ b/Samples/ZeroServer/Areas/Nodes/Views/_ViewImports.cshtml
@@ -0,0 +1,9 @@
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+@using NewLife
+@using NewLife.Cube
+@using NewLife.Reflection
+@using NewLife.Web
+@using XCode
+@using XCode.Membership
+@using Zero.Data.Nodes
+@using ZeroServer.Models
Added +22 -0
diff --git a/Samples/ZeroServer/Areas/Nodes/Views/Node/_List_Search.cshtml b/Samples/ZeroServer/Areas/Nodes/Views/Node/_List_Search.cshtml
new file mode 100644
index 0000000..cb3fbc7
--- /dev/null
+++ b/Samples/ZeroServer/Areas/Nodes/Views/Node/_List_Search.cshtml
@@ -0,0 +1,22 @@
+@using NewLife;
+@using NewLife.Web;
+@using NewLife.Cube;
+@using XCode;
+@{
+    var fact = ViewBag.Factory as IEntityFactory;
+    var page = ViewBag.Page as Pager;
+}
+<div class="form-group">
+    <label for="category" class="control-label">类别:</label>
+    @Html.ForDropDownList("category", Node.FindAllCategory(), page["category"], "全部", true)
+</div>
+<div class="form-group">
+    <label for="product" class="control-label">产品:</label>
+    @Html.ForDropDownList("product", Node.FindAllProduct(), page["product"], "全部", true)
+</div>
+<div class="form-group">
+    <label for="version" class="control-label">版本:</label>
+    @Html.ForDropDownList("version", Node.FindAllVersion(), page["version"], "全部", true)
+</div>
+@await Html.PartialAsync("_Area2", "")
+@await Html.PartialAsync("_DateRange")
\ No newline at end of file
Added +18 -0
diff --git a/Samples/ZeroServer/Areas/Nodes/Views/NodeHistory/_List_Search.cshtml b/Samples/ZeroServer/Areas/Nodes/Views/NodeHistory/_List_Search.cshtml
new file mode 100644
index 0000000..c3a8249
--- /dev/null
+++ b/Samples/ZeroServer/Areas/Nodes/Views/NodeHistory/_List_Search.cshtml
@@ -0,0 +1,18 @@
+@using NewLife;
+@using NewLife.Web;
+@using NewLife.Cube;
+@using XCode;
+@{
+    var fact = ViewBag.Factory as IEntityFactory;
+    var page = ViewBag.Page as Pager;
+
+    var dic = new Dictionary<Int32, String>();
+    dic.Add(1, "成功");
+    dic.Add(0, "失败");
+}
+<div class="form-group">
+    <label for="success" class="control-label">状态:</label>
+    @Html.ForDropDownList("success", dic, page["success"], "全部", true)
+</div>
+@await Html.PartialAsync("_SelectNode", new SelectNodeModel { Id = "nodeId" })
+@await Html.PartialAsync("_DateRange")
\ No newline at end of file
Added +12 -0
diff --git a/Samples/ZeroServer/Areas/Nodes/Views/NodeOnline/_List_Search.cshtml b/Samples/ZeroServer/Areas/Nodes/Views/NodeOnline/_List_Search.cshtml
new file mode 100644
index 0000000..02f156a
--- /dev/null
+++ b/Samples/ZeroServer/Areas/Nodes/Views/NodeOnline/_List_Search.cshtml
@@ -0,0 +1,12 @@
+@using NewLife;
+@using NewLife.Web;
+@using XCode;
+@{
+    var fact = ViewBag.Factory as IEntityFactory;
+    var page = ViewBag.Page as Pager;
+}
+<div class="form-group">
+    <label for="category" class="control-label">类别:</label>
+    @Html.ForDropDownList("category", Node.FindAllCategory(), page["category"], "全部", true)
+</div>
+@await Html.PartialAsync("_Area2", "")
Added +21 -0
diff --git a/Samples/ZeroServer/Areas/Nodes/Views/NodeOnline/_List_Toolbar_Batch.cshtml b/Samples/ZeroServer/Areas/Nodes/Views/NodeOnline/_List_Toolbar_Batch.cshtml
new file mode 100644
index 0000000..efd35df
--- /dev/null
+++ b/Samples/ZeroServer/Areas/Nodes/Views/NodeOnline/_List_Toolbar_Batch.cshtml
@@ -0,0 +1,21 @@
+@using NewLife.Common;
+@using System.Collections.Generic;
+@{
+    var set = ViewBag.PageSetting as PageSetting;
+    var page = ViewBag.Page as Pager;
+}
+@if (set.EnableSelect)
+{
+    <button type="button" class="btn btn-success btn-sm" data-action="action" data-url="@Url.Action("CheckUpgrade")" data-fields="keys" disabled>
+        检查更新
+    </button>
+    <div class="form-group">
+        <label for="command" class="control-label">命令:</label>
+        @Html.TextBox("command", page["command"])
+        <label for="argument" class="control-label">参数:</label>
+        @Html.TextBox("argument", page["argument"])
+    </div>
+    <button type="button" class="btn btn-purple btn-sm" data-action="action" data-url="@Url.Action("Execute")" data-fields="keys,command,argument">
+        执行命令
+    </button>
+}
\ No newline at end of file
Added +12 -0
diff --git a/Samples/ZeroServer/Models/SelectNodeModel.cs b/Samples/ZeroServer/Models/SelectNodeModel.cs
new file mode 100644
index 0000000..8a44d8a
--- /dev/null
+++ b/Samples/ZeroServer/Models/SelectNodeModel.cs
@@ -0,0 +1,12 @@
+namespace ZeroServer.Models;
+
+public class SelectNodeModel
+{
+    public String Id { get; set; }
+
+    public String Category { get; set; }
+
+    public String Product { get; set; }
+
+    public Int32 NodeId { get; set; }
+}
Modified +2 -1
diff --git a/Samples/ZeroServer/Services/NodeService.cs b/Samples/ZeroServer/Services/NodeService.cs
index 3ad2ab4..71cc5e1 100644
--- a/Samples/ZeroServer/Services/NodeService.cs
+++ b/Samples/ZeroServer/Services/NodeService.cs
@@ -343,7 +343,8 @@ public class NodeService : IDeviceService
     /// <param name="ip"></param>
     public void WriteHistory(IDeviceModel model, String action, Boolean success, String remark, String ip)
     {
-        NodeHistory.Create(model as Node, action, success, remark, Environment.MachineName, ip);
+        var history = NodeHistory.Create(model as Node, action, success, remark, Environment.MachineName, ip);
+        history.SaveAsync();
     }
     #endregion
 }
\ No newline at end of file
Added +12 -0
diff --git a/Samples/ZeroServer/Views/_ViewImports.cshtml b/Samples/ZeroServer/Views/_ViewImports.cshtml
new file mode 100644
index 0000000..615a51e
--- /dev/null
+++ b/Samples/ZeroServer/Views/_ViewImports.cshtml
@@ -0,0 +1,12 @@
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+@using NewLife
+@using NewLife.Cube
+@using NewLife.Reflection
+@using NewLife.Web
+@using XCode
+@using XCode.Membership
+@using NewLife.Cube.Areas.Admin.Models 
+@using NewLife.Cube.Extensions 
+@using NewLife.Cube.ViewModels 
+@using Zero.Data.Nodes
+@using ZeroServer.Models
Added +58 -0
diff --git a/Samples/ZeroServer/Views/Shared/_Node_Nav.cshtml b/Samples/ZeroServer/Views/Shared/_Node_Nav.cshtml
new file mode 100644
index 0000000..a22df27
--- /dev/null
+++ b/Samples/ZeroServer/Views/Shared/_Node_Nav.cshtml
@@ -0,0 +1,58 @@
+@{
+    var obj = Model as Object;
+    var path = Context.Request.Path + "";
+    var nodeId = Context.Request.Query["nodeId"].ToInt(0);
+    if (nodeId == 0 && path.EqualIgnoreCase("/Nodes/Node", "/Nodes/Node/Detail", "/Nodes/Node/Edit"))
+    {
+        nodeId = Context.Request.Query["Id"].ToInt(0);
+    }
+
+    var start = Context.Request.Query["dtStart"].ToDateTime();
+    var end = Context.Request.Query["dtEnd"].ToDateTime();
+    if (end.Year < 2000) end = Context.Request.Query["dtEnd2"].ToDateTime().AddSeconds(1);
+
+    var node = Node.FindByID(nodeId) ?? new Node();
+
+    var dic = new Dictionary<String, Object>();
+    dic[node.Name + ""] = "/Nodes/Node/Edit?Id=" + nodeId;
+    dic["在线"] = "/Nodes/NodeOnline?nodeId=" + nodeId;
+    dic["历史"] = "/Nodes/NodeHistory?nodeId=" + nodeId;
+}
+@if (nodeId > 0)
+{
+    <div class="navbar-collapse text-center">
+        <ul class="nav nav-pills" style="margin-bottom: 10px; display: inline-block;float: none;">
+            @foreach (var item in dic)
+            {
+                if (item.Value is IDictionary<String, Object> childs)
+                {
+                    <li role="presentation" class="dropdown">
+                        <a class="dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">@item.Key <span class="caret"></span></a>
+                        <ul class="dropdown-menu">
+                            @foreach (var elm in childs)
+                            {
+                                var url = elm.Value + "";
+                                var v = url.Substring(null, "?");
+                                if (start.Year > 2000) url += "&dtStart=" + start.ToFullString();
+                                if (end.Year > 2000) url += "&dtEnd=" + end.ToFullString();
+                                <li role="presentation" class="@(path.EqualIgnoreCase(v)?"active":"")">
+                                    <a href="@url">@elm.Key</a>
+                                </li>
+                            }
+                        </ul>
+                    </li>
+                }
+                else
+                {
+                    var url = item.Value + "";
+                    var v = url.Substring(null, "?");
+                    if (start.Year > 2000) url += "&dtStart=" + start.ToFullString();
+                    if (end.Year > 2000) url += "&dtEnd=" + end.ToFullString();
+                    <li role="presentation" class="@(path.EqualIgnoreCase(v)?"active":"")">
+                        <a href="@url">@item.Key</a>
+                    </li>
+                }
+            }
+        </ul>
+    </div>
+}
\ No newline at end of file
Added +60 -0
diff --git a/Samples/ZeroServer/Views/Shared/_SelectNode.cshtml b/Samples/ZeroServer/Views/Shared/_SelectNode.cshtml
new file mode 100644
index 0000000..f6b73b4
--- /dev/null
+++ b/Samples/ZeroServer/Views/Shared/_SelectNode.cshtml
@@ -0,0 +1,60 @@
+@model SelectNodeModel
+@using NewLife
+@using NewLife.Web
+@using XCode
+@using System.Linq
+@using ZeroServer.Models
+@{
+    var id = Model.Id;
+    var page = ViewBag.Page as Pager;
+
+    if (id.IsNullOrEmpty()) { id = "nodeId"; }
+    var nodeId = Model.NodeId;
+    if (nodeId <= 0) nodeId = page[id].ToInt();
+    var showValue = Node.FindByID(nodeId)?.Name;
+}
+
+<div class="form-group" style="position:relative">
+    <div class="col-lg-12">
+        <div class="input-group">
+            <input type="hidden" name="@id" id="@id" value="@nodeId" />
+            <input type="text" class="form-control" id="@(id)_select" placeholder="搜索节点" value="@showValue">
+            <div class="input-group-btn">
+                <ul class="dropdown-menu dropdown-menu-right" role="menu"></ul>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="~/Content/bootstrap-suggest.js"></script>
+<script type="text/javascript">
+    $(function () {
+        $("#@(id)_select").bsSuggest({
+            url: "/Nodes/Node/NodeSearch?category=@Model.Category&product=@Model.Product&key=",
+            getDataMethod:"url",
+            effectiveFieldsAlias: { id: "编号", code: "代码", name: "名称", ip: "地址" },
+            ignorecase: true,
+            showHeader: true,
+            delayUntilKeyup: true, //获取数据的方式为 firstByUrl 时,延迟到有输入/获取到焦点时才请求数据
+            hideOnSelect: true,
+            idField: "id",
+            showBtn: false,
+            keyField: "name",
+            clearable: true,
+            searchingTip: '搜索中...',
+            allowNoKeyword: true,
+            delay: 500,
+            emptyTip: '无数据',
+            containerValueId: '@id',
+            fnProcessData: function (result) {
+                dt = {};
+                dt.value = result.data;
+                return dt;
+            }
+        }).on('onSetSelectValue', function (e, keyword, data) {
+            $('#@id').val(keyword.id);
+        }).on('onUnsetSelectValue', function () {
+            $('#@id').val('');
+        });
+    });
+</script>
\ No newline at end of file
Modified +0 -4
diff --git a/Samples/ZeroServer/ZeroServer.csproj b/Samples/ZeroServer/ZeroServer.csproj
index 52af78d..8fc599f 100644
--- a/Samples/ZeroServer/ZeroServer.csproj
+++ b/Samples/ZeroServer/ZeroServer.csproj
@@ -28,8 +28,4 @@
     <ProjectReference Include="..\..\NewLife.Remoting.Extensions\NewLife.Remoting.Extensions.csproj" />
   </ItemGroup>
 
-  <ItemGroup>
-    <Folder Include="Areas\" />
-  </ItemGroup>
-
 </Project>