当前位置: 首页 > news >正文

使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)

概述

最近,有客户向我们请求开发一个前端下拉控件,需求是显示了一个列表,其中包含可由用户单独选择的项目控件,该控件将在下拉列表中显示多选TreeView(树形图)。

如今WijmoJS已经实现了该控件——DropDownTree,本文将主要介绍如何创建自定义DropDownTree控件以及其源代码。

DropDownTree 控件源代码

HTML

<div class="container">
    <h1>
        DropDownTree Control
    </h1>
  <p>
    The <b>DropDownTree</b> control is similar to a 
        <b>MultiSelect</b>, but it hosts a <b>TreeView</b> 
        in the drop-down instead of a <b>ListBox</b>.</p>
  <p>
    The <b>DropDownTree</b>'s object model is also 
        similar to the <b>MultiSelect</b>'s: you can listen
        to the <b>checkedItemsChanged</b> event and get/set
        the selection using the <b>checkedItems</b> property:</p>

  <h3>
        Drop-Down-Tree
    </h3>
    <input id="ddTree" placeholder="multi tree">

    <h3>
        Multi-Select
    </h3>
    <input id="multiSelect" placeholder="multi select">
</div>

JavaScript

onload = function() {
  // create the DropDownTree
    var ddTree = new wijmo.input.DropDownTree('#ddTree', {
      displayMemberPath: 'header',
    childItemsPath: 'items',
    showCheckboxes: true,
    itemsSource: getTreeData(),
    checkedItemsChanged: function (s, e) {
        console.log('dropDownTree.checkedItemsChanged:');
      s.checkedItems.forEach(function (item, index) {
          console.log(index, item[s.displayMemberPath])
            })
        }
    });

  // create the MultiSelect
    var multiSelect = new wijmo.input.MultiSelect('#multiSelect', {
        itemsSource: 'Austria,Belgium,Chile,Denmark'.split(','),
        checkedItemsChanged: function (s, e) {
            console.log('multiSelect.checkedItemsChanged:');
            s.checkedItems.forEach(function (item, index) {
                console.log(index, item)
            })
        }
    });

  // get the tree data
    function getTreeData() {
      return [
        { header: 'Electronics', img: 'resources/electronics.png', items: [
              { header: 'Trimmers/Shavers' },
          { header: 'Tablets' },
          { header: 'Phones', img: 'resources/phones.png', items: [
              { header: 'Apple' },
            { header: 'Motorola', newItem: true },
            { header: 'Nokia' },
            { header: 'Samsung' }
                    ]},
          { header: 'Speakers', newItem: true },
          { header: 'Monitors' }
            ]},
      { header: 'Toys', img: 'resources/toys.png', items: [
          { header: 'Shopkins' },
        { header: 'Train Sets' },
        { header: 'Science Kit', newItem: true },
        { header: 'Play-Doh' },
        { header: 'Crayola' }
            ]},
      { header: 'Home', img: 'resources/home.png', items: [
          { header: 'Coffeee Maker' },
        { header: 'Breadmaker', newItem: true },
        { header: 'Solar Panel', newItem: true },
        { header: 'Work Table' },
        { header: 'Propane Grill' }
            ]}
        ];
    }
}

// DropDownTree: transpiled TypeScript
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var wijmo;
(function (wijmo) {
    var input;
    (function (input) {
        var DropDownTree = /** @class */ (function (_super) {
            __extends(DropDownTree, _super);
            /**
             * Initializes a new instance of the @see:DropDownTree class.
             *
             * @param element The DOM element that hosts the control, or a CSS selector for the host element (e.g. '#theCtrl').
             * @param options The JavaScript object containing initialization data for the control.
             */
            function DropDownTree(element, options) {
                var _this = _super.call(this, element) || this;
                _this._maxHdrItems = 2;
                _this._hdrFmt = wijmo.culture.MultiSelect.itemsSelected;
                /**
                 * Occurs when the value of the @see:checkedItems property changes.
                 */
                _this.checkedItemsChanged = new wijmo.Event();
                wijmo.addClass(_this.hostElement, 'wj-dropdowntree');
                // make header element read-only
                _this._tbx.readOnly = true;
                // toggle drop-down when clicking on the header element
                // (and not on a containing label element)
                _this.addEventListener(_this.inputElement, 'click', function (e) {
                    if (document.elementFromPoint(e.clientX, e.clientY) == _this.inputElement) {
                        _this.isDroppedDown = !_this.isDroppedDown;
                    }
                });
                // update header now, when the itemsSource changes, and when items are selected
                _this._updateHeader();
                var tree = _this._tree;
                tree.checkedItemsChanged.addHandler(function () {
                    _this._updateHeader();
                    _this.onCheckedItemsChanged();
                });
                tree.selectedItemChanged.addHandler(function () {
                    if (!tree.showCheckboxes) {
                        _this._updateHeader();
                        _this.onCheckedItemsChanged();
                    }
                });
                tree.loadedItems.addHandler(function () {
                    _this._updateHeader();
                });
                // close tree on enter/escape
                tree.addEventListener(tree.hostElement, 'keydown', function (e) {
                    switch (e.keyCode) {
                        case wijmo.Key.Enter:
                        case wijmo.Key.Escape:
                            _this.isDroppedDown = false;
                            break;
                    }
                });
                // initialize control options
                _this.initialize(options);
                return _this;
            }
            Object.defineProperty(DropDownTree.prototype, "treeView", {
                /**
                 * Gets the @see:TreeView control shown in the drop-down.
                 */
                get: function () {
                    return this._tree;
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "itemsSource", {
                /**
                 * Gets or sets the array that contains the @see:TreeView items.
                 *
                 * @see:TreeView #see:itemsSource arrays usually have a hierarchical
                 * structure with items that contain child items. There is no fixed
                 * limit to the depth of the items.
                 *
                 * For details, see the @see:TreeView.itemsSource property.
                 */
                get: function () {
                    return this._tree.itemsSource;
                },
                set: function (value) {
                    this._tree.itemsSource = value;
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "displayMemberPath", {
                /**
                 * Gets or sets the name of the property (or properties) to use as
                 * the visual representation of the nodes.
                 *
                 * The default value for this property is the string 'header'.
                 *
                 * For details, see the @see:TreeView.displayMemberPath property.
                 */
                get: function () {
                    return this._tree.displayMemberPath;
                },
                set: function (value) {
                    this._tree.displayMemberPath = value;
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "childItemsPath", {
                /**
                 * Gets or sets the name of the property (or properties) that contains
                 * the child items for each node.
                 *
                 * The default value for this property is the string 'items'.
                 *
                 * For details, see the @see:TreeView.childItemsPath property.
                 */
                get: function () {
                    return this._tree.childItemsPath;
                },
                set: function (value) {
                    this._tree.childItemsPath = value;
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "showCheckboxes", {
                /**
                 * Gets or sets a value that determines whether the @see:TreeView should
                 * add checkboxes to nodes and manage their state.
                 *
                 * For details, see the @see:TreeView.showCheckboxes property.
                 */
                get: function () {
                    return this._tree.showCheckboxes;
                },
                set: function (value) {
                    this._tree.showCheckboxes = value;
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "checkedItems", {
                /**
                 * Gets or sets an array containing the items that are currently checked.
                 */
                get: function () {
                    var tree = this._tree;
                    if (tree.showCheckboxes) {
                        return tree.checkedItems;
                    }
                    else {
                        return tree.selectedItem ? [tree.selectedItem] : [];
                    }
                },
                set: function (value) {
                    var tree = this._tree;
                    if (tree.showCheckboxes) {
                        tree.checkedItems = wijmo.asArray(value);
                    }
                    else {
                        tree.selectedItem = wijmo.isArray(value) ? value[0] : value;
                    }
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "maxHeaderItems", {
                /**
                 * Gets or sets the maximum number of items to display on the control header.
                 *
                 * If no items are selected, the header displays the text specified by the
                 * @see:placeholder property.
                 *
                 * If the number of selected items is smaller than or equal to the value of the
                 * @see:maxHeaderItems property, the selected items are shown in the header.
                 *
                 * If the number of selected items is greater than @see:maxHeaderItems, the
                 * header displays the selected item count instead.
                 */
                get: function () {
                    return this._maxHdrItems;
                },
                set: function (value) {
                    if (this._maxHdrItems != value) {
                        this._maxHdrItems = wijmo.asNumber(value);
                        this._updateHeader();
                    }
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "headerFormat", {
                /**
                 * Gets or sets the format string used to create the header content
                 * when the control has more than @see:maxHeaderItems items checked.
                 *
                 * The format string may contain the '{count}' replacement string
                 * which gets replaced with the number of items currently checked.
                 * The default value for this property in the English culture is
                 * '{count:n0} items selected'.
                 */
                get: function () {
                    return this._hdrFmt;
                },
                set: function (value) {
                    if (value != this._hdrFmt) {
                        this._hdrFmt = wijmo.asString(value);
                        this._updateHeader();
                    }
                },
                enumerable: true,
                configurable: true
            });
            Object.defineProperty(DropDownTree.prototype, "headerFormatter", {
                /**
                 * Gets or sets a function that gets the HTML in the control header.
                 *
                 * By default, the control header content is determined based on the
                 * @see:placeholder, @see:maxHeaderItems, and on the current selection.
                 *
                 * You may customize the header content by specifying a function that
                 * returns a custom string based on whatever criteria your application
                 * requires.
                 */
                get: function () {
                    return this._hdrFormatter;
                },
                set: function (value) {
                    if (value != this._hdrFormatter) {
                        this._hdrFormatter = wijmo.asFunction(value);
                        this._updateHeader();
                    }
                },
                enumerable: true,
                configurable: true
            });
            /**
             * Raises the @see:checkedItemsChanged event.
             */
            DropDownTree.prototype.onCheckedItemsChanged = function (e) {
                this.checkedItemsChanged.raise(this, e);
            };
            //** overrides
            // switch focus to the tree when the drop-down opens
            DropDownTree.prototype.onIsDroppedDownChanged = function (e) {
                if (this.containsFocus() && this.isDroppedDown) {
                    this._tree.focus();
                }
                _super.prototype.onIsDroppedDownChanged.call(this, e);
            };
            // create the drop-down element
            DropDownTree.prototype._createDropDown = function () {
                // create child TreeView control
                var lbHost = wijmo.createElement('<div style="width:100%;border:none"></div>', this._dropDown);
                this._tree = new wijmo.nav.TreeView(lbHost, {
                    showCheckboxes: true,
                });
                // let base class do its thing
                _super.prototype._createDropDown.call(this);
            };
            Object.defineProperty(DropDownTree.prototype, "isReadOnly", {
                // override since our input is always read-only
                get: function () {
                    return this._readOnly;
                },
                set: function (value) {
                    this._readOnly = wijmo.asBoolean(value);
                    wijmo.toggleClass(this.hostElement, 'wj-state-readonly', this.isReadOnly);
                },
                enumerable: true,
                configurable: true
            });
            // update header when refreshing
            DropDownTree.prototype.refresh = function (fullUpdate) {
                if (fullUpdate === void 0) { fullUpdate = true; }
                _super.prototype.refresh.call(this, fullUpdate);
                this._updateHeader();
            };
            //** implementation
            // update the value of the control header
            DropDownTree.prototype._updateHeader = function () {
                // get selected items
                var items = this.checkedItems;
                // update the header
                if (wijmo.isFunction(this._hdrFormatter)) {
                    this.inputElement.value = this._hdrFormatter();
                }
                else {
                    var hdr = '';
                    if (items.length > 0) {
                        if (items.length <= this._maxHdrItems) {
                            if (this.displayMemberPath) {
                                var binding_1 = new wijmo.Binding(this.displayMemberPath);
                                items = items.map(function (item) {
                                    return binding_1.getValue(item);
                                });
                            }
                            hdr = items.join(', ');
                        }
                        else {
                            hdr = wijmo.format(this.headerFormat, {
                                count: items.length
                            });
                        }
                    }
                    this.inputElement.value = hdr;
                }
                // update wj-state attributes
                this._updateState();
            };
            return DropDownTree;
        }(input.DropDown));
        input.DropDownTree = DropDownTree;
    })(input = wijmo.input || (wijmo.input = {}));
})(wijmo || (wijmo = {}));
//# sourceMappingURL=DropDownTree.js.map

CSS

body {

margin-bottom: 24pt;

}

控件准备就绪后,它将如下所示:

DropDownTree Control

Drop-Down-Tree

本控件使用两个独立的WijmoJS模块:输入和导航。所需的步骤与开发MultiSelect控件时所采用的步骤相同:

选择基类

在这种场景下,我们可以将DropDown控件进行扩展,该控件包含使用下拉按钮实现输入元素所需的所有逻辑以及可用于托管任何控件的通用下拉列表。 DropDown控件用作ComboBox,InputColor和InputDate控件的基类。

定义对象模型

因为DropDownTree控件在其下拉列表中托管TreeView,所以我们决定直接从DropDownTree公开TreeView控件的主要属性:

  • TreeView获取对下拉列表中显示的TreeView控件的引用。
  • ItemsSource获取或设置对用于填充TreeView的对象数组的引用。
  • DisplayMemberPath获取或设置用作项目可视化表示的属性名称(默认为“header”)。
  • ChildItemsPath获取或设置包含数据源中每个项的子项的属性的名称(默认为“items”)。
  • ShowCheckboxes获取或设置一个值,该值确定控件是否应为每个项添加复选框,以便用户可以选择多个项(默认为true)。

我们还添加了一些额外的属性和事件来定义当前选择以及它在控制头中的表示方式。这些属性镜像MultiSelect控件中的相应属性:

  • CheckedItems获取或设置包含当前所选项目的数组。
  • CheckedItemsChanged是CheckedItems属性值更改时发生的事件。
  • MaxHeaderItems是控件头中显示的最大选定项数。
  • 当控件具有超过*maxHeaderItems项目选项时,headerFormat获取或设置用于创建标题内容的格式字符串。
  • HeaderFormatter获取或设置一个函数,该函数获取控件头中显示的文本。 这将覆盖maxHeaderItems和headerFormat属性的设置。

实现控件

我们首先将控件声明为基类的扩展:

namespace wijmo.input {
    export class DropDownTree extends DropDown {
    }
}

“extendsDropDown”语句确保我们的控件继承基本DropDown类的所有功能,包括属性,事件,方法和所有内部/私有成员。

创建树视图

接下来,我们覆盖DropDown类中的_createDropDown方法,以创建将在下拉列表中显示的TreeView控件。

除了创建TreeView之外,我们还会覆盖onIsDroppedDownChanged方法,以便在下拉列表打开且控件具有焦点时将焦点转移到树。 这允许用户使用键盘导航树。 他们可以通过键入内容来搜索项目,通过按空格键来检查项目,或使用光标键导航树。

namespace wijmo.input {
    export class DropDownTree extends DropDown {
        private _tree: wijmo.nav.TreeView;

        // create the drop-down element
        protected _createDropDown() {

            // create child TreeView control
            let lbHost = document.createElement('div');
            setCss(lbHost, {
                width: '100%',
                border: 'none'
            });
            this._tree = new wijmo.nav.TreeView(lbHost, {
                showCheckboxes: true
            });
        }

        // switch focus to the tree when the drop-down opens
        onIsDroppedDownChanged(e?: EventArgs) {
            if (this.containsFocus() && this.isDroppedDown) {
                this._tree.focus();
            }
            super.onIsDroppedDownChanged(e);
        }
    }
}

公开TreeView及其属性

下一步是添加公开TreeView及其主要属性:

namespace wijmo.input {
    export class DropDownTree extends DropDown {
        private _tree: wijmo.nav.TreeView;

        get treeView(): wijmo.nav.TreeView {
            return this._tree;
        }
        get itemsSource(): any[] {
            return this._tree.itemsSource;
        }
        set itemsSource(value: any[]) {
            this._tree.itemsSource = value;
        }
        // same for displayMemberPath, childItemsPath, 
        // and showCheckboxes

        // create the drop-down element
        protected _createDropDown() {…}
    }
}

这些属性只是获取或设置包含的TreeView上的相应属性的快捷方式。 因此,它们非常简单,我们甚至不启用类型检查,因为TreeView将为我们处理。

CheckedItems属性

控件的主要属性是CheckedItems,它用来表示用户当前已获取和自定义的数组。 我们可以用它实现上面那样的传递属性,也可以实现多选和单选功能。比如想实现其单选功能时,我们需要检查ShowCheckboxes属性的值并使用树的checkedItems或selectedItem属性。

除了CheckedItems属性,我们还实现了checkedItemsChanged事件及其伴随方法onCheckedItemsChanged。 这是WijmoJS事件的标准模式。 每个事件X都有一个相应的onX方法,负责触发事件。

namespace wijmo.input {
    export class DropDownTree extends DropDown {
        private _tree: wijmo.nav.TreeView;

        // TreeView pass-through properties…

        get checkedItems(): any[] {
            let tree = this._tree;
            if (tree.showCheckboxes) {
                return tree.checkedItems;
            } else {
                return tree.selectedItem
                   ? [tree.selectedItem] : [];
            }
        }
        set checkedItems(value: any[]) {
            let tree = this._tree;
            if (tree.showCheckboxes) {
                tree.checkedItems = asArray(value);
            } else {
                tree.selectedItem = isArray(value)
                    ? value[0] : value;
            }
        }

        readonly checkedItemsChanged = new Event();
        onCheckedItemsChanged(e?: EventArgs) {
            this.checkedItemsChanged.raise(this, e);
        }

        // create the drop-down element
        protected _createDropDown() {…}
}

请注意,即使在单个选择的情况下,checkedItems属性也会返回一个数组(该数组为空或包含单个元素)。

更新控件头

这里不会重点讨论maxHeaderItems,headerFormat或headerFormatter属性的实现方式,因为它们很简单。我们需要将目光聚焦在_updateHeader函数的逻辑中,该函数使用这些属性,并在其值或选择更改时自动调用以更新控件头:

namespace wijmo.input {
    export class DropDownTree extends DropDown {
        private _tree: wijmo.nav.TreeView;

        // TreeView pass-through properties…
        // checketItems property…

        private _updateHeader() {
            let items = this.checkedItems;
            if (isFunction(this._hdrFormatter)) {
                this.inputElement.value = this._hdrFormatter();
            } else {
                let hdr = '';
                if (items.length > 0) {
                    if (items.length <= this._maxHdrItems) {
                        if (this.displayMemberPath) {
                            let dmp = this.displayMemberPath,
                                binding = new Binding(dmp);
                            items = items.map((item) => {
                                return binding.getValue(item);
                            });
                        }
                        hdr = items.join(', ');
                    } else {
                        hdr = format(this.headerFormat, {
                            count: items.length
                        });
                    }
                }
                this.inputElement.value = hdr;
            }
        }

        // create the drop-down element
        protected _createDropDown() {…}
    }
}

构造函数

到此为止,我们几乎已经完成了控件架构。最后一步是实现构造函数,该构造函数将部件与事件侦听器连接,并调用initialize方法以使用options参数中的用户提供的值初始化属性和事件处理程序:

namespace wijmo.input {
    export class DropDownTree extends DropDown {
        private _tree: wijmo.nav.TreeView;
        private _readOnly: boolean;
        private _maxHdrItems = 2;
        private _hdrFmt = wijmo.culture.MultiSelect.itemsSelected;
        private _hdrFormatter: Function;

        constructor(element: HTMLElement, options?: any) {
            super(element);
            addClass(this.hostElement, 'wj-dropdowntree');

            // make header element read-only
            this._tbx.readOnly = true;

            // update header now, when the itemsSource changes, 
            // and when items are selected
            this._updateHeader();
            let tree = this._tree;
            tree.checkedItemsChanged.addHandler(() => {
                this._updateHeader();
                this.onCheckedItemsChanged();
            });
            tree.selectedItemChanged.addHandler(() => {
                if (!tree.showCheckboxes) {
                    this._updateHeader();
                    this.onCheckedItemsChanged();
                }
            });
            tree.loadedItems.addHandler(() => {
                this._updateHeader();
            });

            // initialize control options
            this.initialize(options);
        }

        // TreeView pass-through properties…
        // checketItems property…
        // _updateHeader implementation…
        // _createDropDown implementation…
    }
}

测试控件

现在控件已准备好,我们可以测试它,并检查它是否按照我们想要的方式运行。

运行DropDownTree 控件源代码,单击下拉按钮以打开TreeView。 打开后,单击几个项目以选择它们,并注意控件头的更新方式:

Drop-Down-Tree

我们由衷希望DropDownTree控件对您产生帮助。更重要的是,我们希望您现在可以放心地将DropDown控件扩展为托管其他类型的元素,同时创建自己的自定义控件。


WijmoJS:灵活高效的前端开发工具包,可快速搭建企业 Web 应用程序
图片描述

相关文章:

  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • java 运算符,流程控制语句,键盘录入
  • 【转】在Win7的IIS上搭建FTP服务及用户授权
  • layui-学习02-全局样式
  • Mac OS 系统占用储存空间太大怎么办?
  • 生产管理软件改进生产流程
  • 双十一移动端页面总结
  • PYTHON——多进程:概念
  • Android开发 - 掌握ConstraintLayout(四)创建基本约束
  • Python爬取新浪微博用户信息及微博内容
  • IIS7应用程序池集成和经典的区别
  • Es学习第二课, ES安装和客户端使用
  • EOS源码解析 eosio账号默认合约
  • 项目总结11:Centos部署JDK+Tomcat+MySQL文档(阿里云-网易云-华为云)
  • Oracle Procedure模板
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • 230. Kth Smallest Element in a BST
  • bootstrap创建登录注册页面
  • ES6核心特性
  • gf框架之分页模块(五) - 自定义分页
  • JavaScript类型识别
  • JWT究竟是什么呢?
  • linux学习笔记
  • overflow: hidden IE7无效
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • Android开发者必备:推荐一款助力开发的开源APP
  • #Z2294. 打印树的直径
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (windows2012共享文件夹和防火墙设置
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (九)One-Wire总线-DS18B20
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (十) 初识 Docker file
  • (十六)一篇文章学会Java的常用API
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .gitignore文件设置了忽略但不生效
  • .Net CF下精确的计时器
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .NET 发展历程
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .Net8 Blazor 尝鲜
  • .net快速开发框架源码分享
  • .NET企业级应用架构设计系列之结尾篇
  • [ Linux 长征路第二篇] 基本指令head,tail,date,cal,find,grep,zip,tar,bc,unname
  • [ MSF使用实例 ] 利用永恒之蓝(MS17-010)漏洞导致windows靶机蓝屏并获取靶机权限
  • [ 蓝桥杯Web真题 ]-Markdown 文档解析
  • [20180129]bash显示path环境变量.txt