zoukankan      html  css  js  c++  java
  • knockout 和mvc4结合使用

    Knockout (或者Knockout.js
    ,KnockoutJS)是一个开源的JavaScript库,网址为www.knockoutjs.com。Knockout语法简洁、可读性好,能轻松实现与DOM元素的关联。一旦数据模型的状态发生改变,则立即自动刷新UI。Knockout采用Model-View-View-Model
    (MVVM)的设计模式来简化动态JavaScript UI。Knockout有效实现了JavaScript与UI
    HTML呈现的分离。有了Knockout,在写JavaScript时,就不需要在页面中引用UI元素或DOM。

      Knockout设计目标是把任何JavaScript对象当成View Model来使用。只要View
    Model的属性具有可监听性,就可以使用Knockout将其与UI绑定。一旦属性值发生变化时,UI会被自动刷新。


      Order Entry Header – 编辑模式与显示模式


      Order Header页面的关键功能是,在不重复提交整个页面的前提下,自由切换编辑模式与显示模式。ASP.NET
    post-back模式通常表现为:用户点击Edit按钮,post提交至服务器,返回后,整个页面被重新刷新。使用Knockout与MVVM数据绑定技术,则可以避免页面重新刷新。这里,我们需要做的仅仅是将Order
    Header页面去绑定JavaScript创建的View Model。


      数据绑定标签


      为创建一个MVC View来回切换只读与编辑模式,我们为页面的每一个元素都创建单独的DIV与SPAN标签。一个(编辑模式)包含INPUT
    HTML控件,另一个(只读)只显示文本。添加Knockout数据绑定标签可以灵活控制HTML元素何时被显示,何时被隐藏。下例中,ShipName
    包含一个两个数据绑定标签,前者关联Ship Name的值,后者是一个布尔标签,控制只读或编辑模式。


      





    <div
    style
    ="float:left;
    150px; height:25px; text-align:right;
    "
    class
    ="field-label">Ship To Name:
    </div>


    <div style="float:left; 300px; height:25px;">
    <span data-bind="visible:EditFields">
    @Html.TextBox(
    "ShipName",
    @Model.Order.ShipName,
    new Dictionary<string, object> {
    {
    "data-bind", "value:
    ShipName
    " }, { "style", "300px" } })

    </span>
    <span data-bind="visible: ReadOnlyMode, text: OriginalShipName"></span>
    </div>


      Order Entry显示模式


      当第一次选择一个Order编辑时,此时页面处于只读模式。要创建Knockout与HTML对象的自动绑定,我们必须创建一个JavaScript View
    Model对象,与Knockout绑定,这样Knockout可以监听View Model对象属性的变化,并自动更新UI。





    // Overall viewmodel
    for this
    screen, along
    with initial state

      var viewModel
    =
    {

      EditFields: ko.observable(
    false),

      ReadOnlyMode: ko.observable(
    false),

      DisplayCreateOrderButton:
    ko.observable(
    false),

      DisplayEditOrderButton:
    ko.observable(
    false),

      DisplayUpdateOrderButton:
    ko.observable(
    false),

      DisplayOrderDetailsButton:
    ko.observable(
    false),

      DisplayCancelChangesButton:
    ko.observable(
    true),

      SelectedShipVia: ko.observable($(
    "#OriginalShipVia").val()),

      Shippers:
    ko.observableArray(shippers),

      OrderID: ko.observable($(
    "#OrderID").val()),

      ShipperName:
    ko.observable($(
    "#ShipperName").val()),

      CustomerID:
    ko.observable($(
    "#CustomerID").val()),

      OriginalShipName:
    ko.observable($(
    "#OriginalShipName").val()),

      OriginalShipAddress:
    ko.observable($(
    "#OriginalShipAddress").val()),

      OriginalShipCity:
    ko.observable($(
    "#OriginalShipCity").val()),

      OriginalShipRegion:
    ko.observable($(
    "#OriginalShipRegion").val()),

      OriginalShipPostalCode:
    ko.observable($(
    "#OriginalShipPostalCode").val()),

      OriginalShipCountry:
    ko.observable($(
    "#OriginalShipCountry").val()),

      OriginalRequiredDate:
    ko.observable($(
    "#OriginalRequiredDate").val()),

      OriginalShipVia:
    ko.observable($(
    "#OriginalShipVia").val()),

      ShipName: ko.observable($(
    "#OriginalShipName").val()),

      ShipAddress:
    ko.observable($(
    "#OriginalShipAddress").val()),

      ShipCity: ko.observable($(
    "#OriginalShipCity").val()),

      ShipRegion:
    ko.observable($(
    "#OriginalShipRegion").val()),

      ShipPostalCode:
    ko.observable($(
    "#OriginalShipPostalCode").val()),

      ShipCountry:
    ko.observable($(
    "#OriginalShipCountry").val()),

      RequiredDate:
    ko.observable($(
    "#OriginalRequiredDate").val()),

      MessageBox: ko.observable(
    "")

      }

      ko.applyBindings(viewModel);



      我们创建一个Edit Order点击事件函数,当用户点击Edit Order按钮,页面处于编辑模式。代码如下:





    $("#btnEditOrder").click(function ()
    {

      viewModel.DisplayEditOrderButton(
    false);

      viewModel.DisplayUpdateOrderButton(
    true);

      viewModel.DisplayOrderDetailsButton(
    false);

      viewModel.DisplayCancelChangesButton(
    true);

      viewModel.EditFields(
    true);

      viewModel.ReadOnlyMode(
    false);

      });

      上例中,我们使用Unobtrusive
    JavaScript这种方式来触发Edit按钮点击事件,实现两种显示与编辑模式的切换。Knockout会监听View
    Model,实现自动切换。Unobtrusive JavaScript是一项用于页面内容结构与页面呈现分离的新技术。


      用户点击Update Oder 按钮,则调用UpdateOrder 函数。UpdateOrder 函数的功能是抓取View
    Model的值,并创建一个表示物流信息的JavaScript对象。通过JQuery
    AJAX调用,该对象将提交给UpdateOrderController函数。


      





    function
    UpdateOrder() {

      var shippingInformation
    = new
    ShippingInformation();

      shippingInformation.OrderID
    =
    viewModel.OrderID();

      shippingInformation.CustomerID
    =
    viewModel.CustomerID();

      shippingInformation.ShipName
    =
    viewModel.ShipName();

      shippingInformation.ShipAddress
    =
    viewModel.ShipAddress();

      shippingInformation.ShipCity
    =
    viewModel.ShipCity();

      shippingInformation.ShipRegion
    =
    viewModel.ShipRegion();

      shippingInformation.ShipPostalCode
    =
    viewModel.ShipPostalCode();

      shippingInformation.ShipCountry
    =
    viewModel.ShipCountry();

      shippingInformation.RequiredDate
    =
    viewModel.RequiredDate();

      shippingInformation.Shipper
    =
    viewModel.SelectedShipVia();

      var url
    = "/Orders/UpdateOrder";

      $(
    ':input').removeClass('validation-error');

      $.post(url,
    shippingInformation,
    function (data, textStatus)
    {

      UpdateOrderComplete(data);

      });

      }

      
    function
    UpdateOrderComplete(result) {

      
    if
    (result.ReturnStatus
    == true)
    {

      viewModel.MessageBox(result.MessageBoxView);

      viewModel.OrderID(result.ViewModel.Order.OrderID);

      viewModel.ShipperName(result.ViewModel.Order.ShipperName);

      viewModel.DisplayEditOrderButton(
    true);

      viewModel.DisplayUpdateOrderButton(
    false);

      viewModel.DisplayOrderDetailsButton(
    true);

      viewModel.DisplayCancelChangesButton(
    false);

      viewModel.DisplayCreateOrderButton(
    false);

      viewModel.EditFields(
    false);

      viewModel.ReadOnlyMode(
    true);

      viewModel.OriginalShipName(result.ViewModel.Order.ShipName);

      viewModel.OriginalShipAddress(result.ViewModel.Order.ShipAddress);

      viewModel.OriginalShipCity(result.ViewModel.Order.ShipCity);

      viewModel.OriginalShipRegion(result.ViewModel.Order.ShipRegion);

      viewModel.OriginalShipPostalCode(result.ViewModel.Order.ShipPostalCode);

      viewModel.OriginalShipCountry(result.ViewModel.Order.ShipCountry);

      viewModel.OriginalRequiredDate(result.ViewModel.Order.RequiredDateFormatted);

      viewModel.OriginalShipVia(viewModel.SelectedShipVia());

      }

      
    else

      {

      viewModel.MessageBox(result.MessageBoxView);

      }

      
    for (var val in
    result.ValidationErrors) {

      var element
    = "#" +
    val;

      $(element).addClass(
    'validation-error');

      }

      }


      验证错误


      我们可通过一个CSS类以显示验证错误信息。CSS会循环遍历JSON返回的INPUT控件对象,验证其输入值是否合法如有错误则用红色标记高亮。代码如下:





    for (var val in
    result.ValidationErrors) {

      var element
    = "#" +
    val;

      $(element).addClass(
    'validation-error');

      }



      Oder Entry Details视图 – Knockout 模版


      在完成Order Shipping Information的编辑之后,用户可查看订单详细列表,并可向订单中添加产品。下面的Order Details
    View使用Knockout模版功能,实现了无需post–back的前提下,逐行编辑每一个line item。


      Knockout 模版可轻松实现复杂的UI,例如不断重复与嵌套的Block。Knockout模版将模版渲染之结果填充至关联的DOM元素。


      预渲染与格式化数据


      通常情况下,数据在前后端的结构与模式所有不同,特别是对于日期,货币等字段,此时就免不了数据的重新格式化。在传统的ASP.NET
    Web表单中,多数控件是通过预渲染或数据绑定事件,来实现数据到达给用户之前的重新格式化。在MVC中,我们可以抓取View Model数据,调用服务器端代码,实现在View开始阶段做预渲染操作。下例中,拿到重新格式化的数据后,我们生成了一个订单明细列表。





    @model
    NorthwindViewModel.OrderViewModel

      @{

      ViewBag.Title
    =
    "Order
    Entry Detail
    ";

      ArrayList orderDetails
    = new
    ArrayList();

      foreach (var item in
    Model.OrderDetailsProducts)

      {

      var orderDetail
    = new

      {

      ProductID
    =
    item.OrderDetails.ProductIDFormatted,

      ProductName
    =
    item.Products.ProductName,

      Quantity
    =
    item.OrderDetails.Quantity,

      UnitPrice
    =
    item.OrderDetails.UnitPriceFormatted,

      QuantityPerUnit
    =
    item.Products.QuantityPerUnit,

      Discount
    =
    item.OrderDetails.DiscountFormatted

      };

      orderDetails.Add(orderDetail);

      }

      }



      待数据完成格式化后,我们使用DIV标签加载编码后的JSON对象。稍后,JavaScript将访问该JSON对象,将数据绑定至knockout模版。


      





      我们创建一个Knockout模版,如下。Script标签的类型为text/html,包含各种内容与数据绑定标签。





    <!--====== Template
    ======-->

    <script
    type
    ="text/html" id="OrderDetailTemplate">
    <tr data-bind="style: { background:
    viewModel.SetBackgroundColor($data) }
    ">
    <td style="height:25px"><div data-bind="text:ProductID"></div></td>
    <td><div
    data
    -bind="text:
    ProductName
    "></div></td>
    <td>
    <div data-bind="text: Quantity,
    visible:DisplayMode
    "></div>
    <div data-bind="visible:
    EditMode
    " >
    <input type="text" data-bind="value:
    Quantity
    " style="
    50px
    "
    />

    </div>
    </td>
    <td><div
    data
    -bind="text:UnitPrice"></div></td>
    <td><div
    data
    -bind="text:
    QuantityPerUnit
    "></div></td>
    <td><div
    data
    -bind="text: Discount,
    visible:DisplayMode
    "></div>
    <div data-bind="visible:
    EditMode
    " >
    <input type="text" data-bind="value:Discount" style="50px" />
    </div>
    </td>
    <td>
    <div
    data
    -bind="visible:DisplayDeleteEditButtons">
    <div style="25px;float:left"><img alt="delete" data-bind="click:function()
    {
    viewModel.DeleteLineItem($data) }
    "
    title="Delete item" src="@Url.Content("~/Content/Images/icon-delete.gif")"/>
    </div>
    <div style="25px;float:left"><img alt="edit" data-bind="click:function()
    {
    viewModel.EditLineItem($data) }
    " title="Edit item"
    src="@Url.Content("~/Content/Images/icon-pencil.gif")"/>
    </div>
    </div>


    <div data-bind="visible:DisplayCancelSaveButtons">
    <div style="25px;float:left"><img alt="save" data-bind="click: function()
    {viewModel.UpdateLineItem($data) }" title="Save
    item
    "

    src="@Url.Content("~/Content/Images/icon-floppy.gif")"/>
    </div>
    <div style="25px;float:left"><img alt="cancel
    edit
    "

    data
    -bind="click:function() {
    viewModel.CancelLineItem($data) }
    "

    title
    ="Cancel
    Edit
    "
    src
    ="@Url.Content("~/Content/Images/icon-pencil-x.gif")"/>
    </div>
    </div>

    </td>
    </tr>
    </script>





      要想将Knockout模版添加至HTML中,只需要使用data-bind模版标签与一个foreach语句即可。





    <!--====== Container
    ======-->
    <table border="0" cellpadding="0" cellspacing="0" style="100%">
    <tr class="DataGridHeader">
    <td style="10%;
    height:25px
    ">Product
    ID
    </td>
    <td style="30%">Product Description</td>
    <td
    style
    ="10%">Quantity</td>
    <td
    style
    ="10%">Unit Price</td>
    <td
    style
    ="15%">UOM</td>
    <td style="10%">Discount</td>
    <td
    style
    ="15%">Edit Options</td>
    </tr>
    <tbody
    data
    -bind='template: {name:
    "OrderDetailTemplate", foreach:LineItems}'> </tbody>

    </table>


      


      JavaScript eval函数可作JSON对象的解析。不过,由于JavaScript
    eval可编译并运行任何JavaScript程序,会导致安全性问题。因此,较安全的做法是使用JSON解析器。JSON解析器只识别JSON文本,而不会执行任何潜在风险的脚本。json.org网站中提供了许多JavaScript编写的JSON解析器。


      使用JSON解析器,我们可以解析初始加载的订单明细数据,这些数据会与Knockout View Model实现绑定。当创建多个details line
    items时,我们需要创建一个数组,供Knockout监听。


      



      


      Knockout映射插件


      上例中,我们采取的是自定义创建View
    Model的方式。另一种方式是采用Knockout映射插件,选择合适的映射规则,直截了将JavaScript对象与View Model绑定。


      编辑,更新与删除Template Items


      完整的页面Knockout View Model包含有line item的编辑,更新,删除。





    <script
    language
    ="javascript" type="text/javascript">

    var viewModel
    = {

    LineItems:
    ko.observableArray(),
    MessageBox: ko.observable(),
    AddNewLineItem:
    ko.observable(
    false),

    SetBackgroundColor:
    function
    (currentLineItemData) {
    var rowIndex
    =
    this.LineItems.indexOf(currentLineItemData);
    var colorCode
    = rowIndex %
    2
    ==
    0 ?
    "White" : "WhiteSmoke";
    return
    colorCode;
    },

    EditLineItem:
    function
    (currentLineItemData) {
    var currentLineItem
    =
    this.LineItems.indexOf(currentLineItemData);
    this.LineItems()[currentLineItem].DisplayMode(
    false);

    this.LineItems()[currentLineItem].EditMode(
    true);

    this.LineItems()[currentLineItem].DisplayDeleteEditButtons(
    false);
    this.LineItems()[currentLineItem].DisplayCancelSaveButtons(
    true);
    },


    DeleteLineItem:
    function (currentLineItemData) {
    var currentLineItem
    =
    this.LineItems.indexOf(currentLineItemData);
    var productName
    =
    this.LineItems()[currentLineItem].ProductName();
    var productID
    =
    this.LineItems()[currentLineItem].ProductID();


    ConfirmDeleteLineItem(productID, productName, currentLineItem);

    },

    DeleteLineItemConfirmed:
    function
    (currentLineItem) {
    var row
    = this.LineItems()[currentLineItem];

    this.LineItems.remove(row);
    },

    CancelLineItem:
    function
    (currentLineItemData) {

    currentLineItem
    =
    this.LineItems.indexOf(currentLineItemData);
    this.LineItems()[currentLineItem].DisplayMode(
    true);
    this.LineItems()[currentLineItem].EditMode(
    false);

    this.LineItems()[currentLineItem].DisplayDeleteEditButtons(
    true);
    this.LineItems()[currentLineItem].DisplayCancelSaveButtons(
    false);

    this.LineItems()[currentLineItem].Quantity(this.LineItems()
    [currentLineItem].OriginalQuantity());
    this.LineItems()[currentLineItem].Discount(this.LineItems()
    [currentLineItem].OriginalDiscount());
    },


    UpdateLineItem:
    function (currentLineItemData) {

    currentLineItem
    =
    this.LineItems.indexOf(currentLineItemData);
    var lineItem
    =
    this.LineItems()[currentLineItem];
    UpdateOrderDetail(lineItem,
    currentLineItem);
    },

    UpdateOrderDetailComplete:
    function
    (currentLineItem, discount)
    {

    this.LineItems()[currentLineItem].DisplayMode(
    true);

    this.LineItems()[currentLineItem].EditMode(
    false);

    this.LineItems()[currentLineItem].DisplayDeleteEditButtons(
    true);
    this.LineItems()[currentLineItem].DisplayCancelSaveButtons(
    false);
    this.LineItems()[currentLineItem].OriginalQuantity(this.LineItems()
    [currentLineItem].Quantity());
    this.LineItems()[currentLineItem].OriginalDiscount(discount);
    this.LineItems()[currentLineItem].Discount(discount);

    }
    }



    选择一个line item,点击铅笔编辑图标,EditLineItem函数会触发onclick事件,line item处于编辑模式。如下:






    EditLineItem: function
    (currentLineItemData) {

    var currentLineItem
    =
    this.LineItems.indexOf(currentLineItemData);


    this.LineItems()[currentLineItem].DisplayMode(
    false);
    this.LineItems()[currentLineItem].EditMode(
    true);

    this.LineItems()[currentLineItem].DisplayDeleteEditButtons(
    false);
    this.LineItems()[currentLineItem].DisplayCancelSaveButtons(
    true);


    },

       
    借助Knockout模版与Knockout绑定技术,我们可以创建类似ASP.NET Web Forms
    DataGrid控件的完整in-line编辑grid。


           点击Add Line Item按钮,打开一个line
    item,可将一个item添加至order中。


    使用modal popup窗口,可搜索一个Product Item。在一个新的line item上点击Search按钮,弹出product
    search 窗口。


           The Modal Popup Product Search 窗口


          Modal 弹出窗口是AJAX调用与Partial View的结合。AJAX 请求调用Product Inquiry
    partial view,返回product search的内容,最后填充至DIV标签。





    <div
    id
    ="dialog-modal" title="Product Inquiry">
    <div id="ProductInquiryModalDiv"> </div>
    </div>


           Modal 弹出窗口是一个具有dialog功能的JQuery插件。







    function
    ShowProductInquiryModal() {

    var url
    = "/Products/BeginProductInquiry";

    $.post(url,
    null,
    function
    (html, textStatus) {
    ShowProductInquiryModalComplete(html);
    });

    }


    function ShowProductInquiryModalComplete(productInquiryHtml)
    {

    $(
    "#ProductInquiryModalDiv").html(productInquiryHtml);
    $(
    "#dialog-modal").dialog({

    height:
    500,
    900,
    modal:
    true

    });
    //
    // execute Product Inquiry query after the initial page content has
    been loaded
    //
    setTimeout(
    "ProductInquiryInitializeGrid()", 1000);

    }




            Product Inquiry Search窗口 – UID生成机制


            Product Inquiry Search窗口本身是一个Partial View。由于该窗口与Order
    Order页面加载的DOM一样,因此所有的HTML控件与动态创建的JavaScript函数及变量均要求名字独一无二。在渲染页面内容之前,该Partial
    View实例化自定义的PageIDGeneration类,调用GenerateID方法,生成独一无二的控件ID,JavaScript函数名,以及变量名。PageIDGeneration类通过设置unique
    Guid数目,保证生成ID的唯一性。
     





    @model NorthwindViewModel.ProductViewModel
    @using
    NorthwindWebApplication.Helpers;
    @{


    NorthwindWebControls.PageIDGeneration webControls
    =
    new
    NorthwindWebControls.PageIDGeneration();

    string txtProductID
    =
    webControls.GenerateID(
    "ProductID");
    string
    txtProductDescription
    = webControls.GenerateID("ProductName");
    string btnSearch
    =
    webControls.GenerateID(
    "BtnSearch");
    string btnReset
    =
    webControls.GenerateID(
    "BtnReset");
    string messageBox
    =
    webControls.GenerateID(
    "MessageBox");
    string productResults
    =
    webControls.GenerateID(
    "ProductResults");

    }


    <div class="SearchBar">
    <div style="float:left; 200px">
    Product ID
    </div>
    <div
    style
    ="float:left;
    200px
    ">
    Product
    Description
    </div>
    <div style="clear:both;"></div>
    <div style="float:left; 200px">
    <input id="@txtProductID" type="text" value="" style = "150px" />

    </div>
    <div style="float:left; 200px ">
    <input id="@txtProductDescription" type="text" value="" style = "150px" />

    </div>
    <input id="@btnSearch" type="button" value="Search" />

    <input id="@btnReset" type="button" value="Reset"/>
    </div>
    <div
    style
    ="clear:both;"></div>
    <div id="@productResults"></div>
    <div id="@messageBox"></div>


    @Html.RenderJavascript(webControls.RenderJavascriptVariables(
    "ProductInquiry_"))

    <script
    language
    ="javascript" type="text/javascript">

    $(ProductInquiry_BtnSearch).click(
    function() {

    ProductInquiryInitializeGrid();
    });


    $(ProductInquiry_BtnReset).click(
    function()
    {
    $(ProductInquiry_ProductID).val(
    "");
    $(ProductInquiry_ProductName).val(
    "");
    ProductInquiryInitializeGrid();
    });


    function ProductInquiryRequest() {
    this.CurrentPageNumber;

    this.PageSize;
    this.ProductID;

    this.ProductName;
    this.SortDirection;
    this.SortExpression;
    this.PageID;

    };

    function ProductInquiry(currentPageNumber, sortExpression,
    sortDirection) {

    var url
    = "/Products/ProductInquiry";

    var
    productInquiryRequest
    = new ProductInquiryRequest();


    productInquiryRequest.ProductID
    =
    $(ProductInquiry_ProductID).val();
    productInquiryRequest.ProductName
    =
    $(ProductInquiry_ProductName).val();
    productInquiryRequest.CurrentPageNumber
    =
    currentPageNumber;
    productInquiryRequest.SortDirection
    = sortDirection;

    productInquiryRequest.SortExpression
    = sortExpression;

    productInquiryRequest.PageSize
    = 10;

    productInquiryRequest.PageID
    =
    $(ProductInquiry_PageID).val();

    $.post(url, productInquiryRequest,
    function
    (data, textStatus) {
    ProductInquiryComplete(data);
    });
    };


    function ProductInquiryComplete(result) {

    if
    (result.ReturnStatus
    == true) {

    $(ProductInquiry_ProductResults).html(
    "");

    $(ProductInquiry_ProductResults).html(result.ProductInquiryView);

    $(ProductInquiry_MessageBox).html(
    "");

    }
    else
    {
    $(ProductInquiry_MessageBox).html(result.MessageBoxView);
    }


    }

    function ProductInquiryInitializeGrid()
    {
    ProductInquiry(
    1, "ProductName", "ASC");
    }


    function ProductSelected(productID)
    {
    GetProductInformation(productID);
    }

    </script>




     

     总结


      ASP.NET
    MVC是一个适用于大型Web应用开发的日益成熟的Web框架。MVC的架构思想是注重分离,对于具有Trial、Error、Discovery的Web应用开发而言,MVC的学习曲线就显得与众不同。MVC与我们过去一直使用的ASP.NET
    Web Forms技术与Web Form post-back
    model技术完全不同。在未来,MVC开发者需要更加注重新兴框架与开源库,增强型MVC的开发。


      本文重点关注的是开源JavaScript库Knockout与JQuery,以及用于交换视图与控制器数据的JSON。建议MVC开发者也多多关注其它的开发工具与框架,特别是Backbone与JavaScriptMVC。作为比较,后续的文章将会在示例程序Northwind中引入Backbone与JavaScriptMVC。

  • 相关阅读:
    window XP下 php5.5+mysql+apache2+phpmyadmin安装
    poj2478Farey Sequence
    poj2723Get Luffy Out
    niop2015day2
    P2473 [SCOI2008]奖励关
    P4284 [SHOI2014]概率充电器
    P2486 [SDOI2011]染色
    noip2015day1
    hdu 2795 Billboard
    exgcd
  • 原文地址:https://www.cnblogs.com/dullbaby/p/3186391.html
Copyright © 2011-2022 走看看