ASP.NET MVC with Entity Framework and CSS一書翻譯系列文章之第三章:搜索、高級過濾和視圖模型


  在這一章中,我們首先添加一個搜索產品的模塊以增強站點的功能,然后使用視圖模型而不是ViewBag向視圖傳遞復雜數據。

注意:如果你想按照本章的代碼編寫示例,你必須完成第二章或者直接從www.apress.com下載第二章的源代碼。

3.1 添加產品搜索

  為了執行產品搜索,我們將添加一些功能使其能夠按照產品名稱、描述和分類進行搜索,從而讓用戶有一個更好的選擇來查找相關結果。

  之所以將分類也包含在內,是因為如果用戶輸入的是“clothes”,而不是一件特定的衣服,那么所有的衣服都會被搜索到。如果我們不將分類作為搜索條件,用戶可能不會搜索到任何結果,這是因為產品的名稱或描述字段中可能沒有包含“clothes”這個單詞。

  我們將使用LINQ to Entities中的方法來搜索相關產品信息。

3.1.1 修改控制器以進行產品搜索

  修改Controllers\ProductsController.cs文件中的Index方法以添加產品搜索功能,代碼如下所示:

 1 public ActionResult Index(string category, string search)
 2 {
 3     var products = db.Products.Include(p => p.Category);
 4 
 5     if (!string.IsNullOrEmpty(category))
 6     {
 7         products = products.Where(p => p.Category.Name == category);
 8     }
 9 
10     if (!string.IsNullOrEmpty(search))
11     {
12         products = products.Where(p => p.Name.Contains(search) || p.Description.Contains(search) || p.Category.Name.Contains(search));
13     }
14 
15     return View(products.ToList());
16 }

  首先,在Index方法中添加了一個名為search的參數,然后,判斷該參數是否為null或空,如果不為null或空,則我們使用下面的代碼來進行產品的搜索:

1 if (!string.IsNullOrEmpty(search))
2 {
3     products = products.Where(p => p.Name.Contains(search) || p.Description.Contains(search) || p.Category.Name.Contains(search));
4 }

  按照直白的說法,上面的代碼表示的含義是:如果產品的名稱、產品的描述以及產品的分類名稱中含有search,則這些產品都會被作為結果返回。這段代碼也使用了lambda表達式,但是這個表達式更加復雜,並且使用了邏輯或(||)操作符。注意,在lambda操作符(=>)左邊依然只有一個參數,雖然在其右邊的代碼中有多條語句。當這條語句提交到數據庫時,Contains方法被轉換為SQL的LIKE操作,並且它不是大小寫敏感的。

3.1.2 測試產品搜索

  點擊【開始調試(不執行)】菜單項啟動應用程序以測試新的搜索功能,在首頁中點擊產品鏈接,打開產品索引(Index)頁面,在URL后面手動添加?search=red,然后回車,我們將會看到如圖3-1所示的結果。

圖3-1:通過URL手動搜索含有“red”的產品

  為了測試按照分類名稱搜索產品是否能夠正確工作,我們修改URL為/Products?search=clothes。這個查詢現在將匹配在產品名稱或產品描述或分類名稱中含有clothes這個單詞的所有產品,結果如圖3-2所示。

圖3-2:通過修改URL搜索屬於clothes分類的產品

3.1.3 在站點主導航欄中添加搜索框

  我們不應該期望用戶能夠手動輸入URL以搜索產品,因此,我們有必要在站點中添加一個搜索框。我們將在站點的主導航欄中添加這個搜索框,以便用戶可以一直看到它,並可以在任何頁面中使用它。

  就像在第一章講述的那樣,主導航欄是站點布局頁的一部分,因此它包含在Views\Shared\_Layout.cshtml文件中。

  首先,刪除站點中的關於和聯系方式連接。在這個例子中不會使用到它們,並且它們還占用空間。為了完成此工作,從Views\Shared\_Layout.cshtml文件中刪除以下代碼:

1 <li>@Html.ActionLink("關於", "About", "Home")</li>
2 <li>@Html.ActionLink("聯系方式", "Contact", "Home")</li>

  為了添加一個搜索框,編輯Views\Shared\_Layout.cshtml文件中class屬性為“navbar-collapse collapse”的div,將其代碼修改為如下高亮顯示的代碼:

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 5     <meta charset="utf-8" />
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7     <title>@ViewBag.Title - 我的 ASP.NET 應用程序</title>
 8     @Styles.Render("~/Content/css")
 9     @Scripts.Render("~/bundles/modernizr")
10 
11 </head>
12 <body>
13     <div class="navbar navbar-inverse navbar-fixed-top">
14         <div class="container">
15             <div class="navbar-header">
16                 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
17                     <span class="icon-bar"></span>
18                     <span class="icon-bar"></span>
19                     <span class="icon-bar"></span>
20                 </button>
21                 @Html.ActionLink("Baby Store", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
22             </div>
23             <div class="navbar-collapse collapse">
24                 <ul class="nav navbar-nav">
25                     <li>@Html.ActionLink("主頁", "Index", "Home")</li>
26                     <li>@Html.ActionLink("分類", "Index", "Categories")</li>
27                     <li>@Html.ActionLink("產品", "Index", "Products")</li>
28                 </ul>
29  @using (Html.BeginForm("Index", "Products", FormMethod.Get, new { @class = "navbar-form navbar-left" })) 30  { 31                     <div class="form-group">
32  @Html.TextBox("Search", null, new { @class = "form-control", @placeholder = "Search Products" }) 33                     </div>
34                     <button type="submit" class="btn btn-default">Submit</button>
35  } 36                 @Html.Partial("_LoginPartial")
37             </div>
38         </div>
39     </div>
40     <div class="container body-content">
41         @RenderBody()
42         <hr />
43         <footer>
44             <p>&copy; @DateTime.Now.Year - 我的 ASP.NET 應用程序</p>
45         </footer>
46     </div>
47 
48     @Scripts.Render("~/bundles/jquery")
49     @Scripts.Render("~/bundles/bootstrap")
50     @RenderSection("scripts", required: false)
51 </body>
52 </html>

  表單使用GET而不是POST請求以便搜索條件可以在URL中看到。因此,用戶可以復制它然后使用其他方式分享它,比如e-mail或社交媒體。按照慣例,GET請求用於查詢數據庫中的數據,而不是修改數據庫中的數據。

  在這段代碼中還涉及了前面我們沒有使用過的HTML5的placeholder特性,它主要用於在頁面首次加載時,在搜索框架中顯示“Search Products”文本,以向用戶提示該搜索框的用途。這主要通過在代碼@Html.TextBox("Search", null, new { @class = "form-control", @placeholder = "Search Products"})中向htmlAttributes參數對象傳遞額外的條目完成的。文本框的name屬性被指定為Search,MVC框架將其與搜索參數Search匹配。圖3-3顯示了導航欄的最終效果。

圖3-3:完成搜索框之后的導航欄

  使用搜索框執行一些搜索,結果應該和手動在URL中輸入搜索條件的效果一樣。

3.1.4 如何使用Bootstrap添加樣式

  Bootstrap是一個HTML、CSS和JavaScript的框架,起初由Twitter創建。使用基架的ASP.NET項目默認使用它來樣式化站點的外觀。在網上有一大堆關於Bootstrap的可用信息,因此,我們不會討論關於它的任何細節,但是,當我們引入新的知識時,我們會解釋為什么我們會那樣樣式化它。

  那么,我們如何知道搜索框的樣式?答案在於Thomas Park創建的站點http://www.bootswatch.com。這個站點提供了幾個免費的Bootstrap主題,還提供了許多元素的HTML預覽。如果我們點擊Themes按鈕中的某個主題,然后往下拉動頁面,並且將鼠標懸停在某個元素上,這個時候<>符號將會出現在該元素的右上角,如圖3-4所示。如果點擊這個符號,HTML預覽就會出現,如圖3-5所示。

圖3-4:在bootswatch.com站點上出現在某個元素右上角的預覽符號<>

圖3-5:來自於bootswatch.com的HTML預覽。高亮部分顯示了如何樣式化一個搜索框以及如何在導航中加入表單

3.2 使用ViewBag按照分類過濾搜索結果

  下面,我們添加一個新功能以便用戶可以按照分類過濾搜索結果。在這個例子中,我們將在搜索結果頁面中使用ViewBag來顯示一個含有分類信息的下拉框。我們將這個下拉框與用戶輸入的搜索條件相關聯,以便在下拉框中只顯示與搜索結果相關的分類,同時不允許用戶選擇一個空的分類。

3.2.1 修改ProductsController控制器的Index方法以實現按照分類進行過濾

  按下面的代碼修改Controllers\ProductsController.cs文件中的Index方法,以便在ViewBag中存儲當前搜索的條件,並且生成一個去除重復值的分類列表,並將其作為SelectList對象存儲在ViewBag中。

 1 public ActionResult Index(string category, string search)
 2 {
 3     var products = db.Products.Include(p => p.Category);
 4 
 5     if (!string.IsNullOrEmpty(category))
 6     {
 7         products = products.Where(p => p.Category.Name == category);
 8     }
 9 
10     if (!string.IsNullOrEmpty(search))
11     {
12         products = products.Where(p => p.Name.Contains(search) || p.Description.Contains(search) || p.Category.Name.Contains(search));
13         ViewBag.Search = search; 14     }
15 
16     var categories = products.OrderBy(p => p.Category.Name).Select(p => p.Category.Name).Distinct(); 17 
18     ViewBag.Category = new SelectList(categories); 19 
20     return View(products.ToList());
21 }

  將搜索條件保存在VeiwBag中,以便當用戶點擊分類過濾時可以重用該搜索條件。如果我們不保存該搜索條件,那么這個搜索條件將會被丟棄,產品信息將不會被正確地被過濾。

  代碼var categories = products.OrderBy(p => p.Category.Name).Select(p => p.Category.Name).Distinct();生成了一個按字母排序並且去除重復值的分類列表。這個分類列表是不全面的,它只包含與搜索條件相關的產品所包含的分類信息。

  最后,我們根據categories變量生成了一個SelectList對象,並將其保存在ViewBag中,以便在視圖中使用。

3.2.2 向產品的索引(Index)頁面添加過濾功能

  為了使\Products\Index.cshtml文件生成的HTML頁面具有按分類過濾的功能,我們需要添加一個新的HTML表單以提交過濾請求。我們在Views\Products\Index.cshtml文件的Create New鏈接后面添加一個帶有下列列表的新表單,代碼如下所示:

1 <p>
2     @Html.ActionLink("Create New", "Create")
3  @using(Html.BeginForm("Index", "Products", FormMethod.Get)) 4  { 5         <label>Filter by category:</label> @Html.DropDownList("Category", "All") 6         <input type="submit" value="Filter"/>
7         <input type="hidden" name="Search" id="Search" value="@ViewBag.Search"/>
8  } 9 </p>

  這段代碼添加了一個表單,該表單使用GET方法向ProductsController控制器的Index動作方法提交請求,因此,查詢字符串所包含的值一並被提交到服務端。使用代碼@Html.DropDownList("Category", "All")生成一個下拉列表,該下拉列表使用了ViewBag.Category屬性所提供的值,參數“All”對下拉列表指定了一個默認值。添加的提交按鈕Filter可以讓用戶提交表單並執行過濾。一個隱藏的HTML元素被用來存儲當前的搜索條件,當按分類過濾產品信息並提交表單時,可以保證用戶最初輸入的搜索條件被保存下來。

  不調試啟動站點,圖3-6顯示了搜索“Red”單詞后的產品的索引(Index)頁面的樣子,注意All是過濾下拉框的默認值。

圖3-6:帶有過濾功能的產品索引(Index)頁面

  看起來代碼都像預期的一樣被正確執行,然而,代碼存在一個小問題。圖3-7顯示了搜索“Red”之后,又按照Clothes分類過濾之后的效果。

圖3-7:搜索“Red”后按照Clothes過濾后的頁面

  這個問題是在在按照Clothes分類過濾后,在分類列表中的Toys分類消息了。修正這個問題很簡單,但是它強調了需要按正確的順序來創建我們的查詢。為了修正這個問題,向如下所示的代碼一樣修改\Controllers\ProductsController.cs文件中的Index方法,以使按照分類過濾產品的代碼放在categories變量已被賦值的后面:

 1 public ActionResult Index(string category, string search)
 2 {
 3     var products = db.Products.Include(p => p.Category);
 4 
 5     if (!string.IsNullOrEmpty(search))
 6     {
 7         products = products.Where(p => p.Name.Contains(search) || p.Description.Contains(search) || p.Category.Name.Contains(search));
 8         ViewBag.Search = search;
 9     }
10 
11     var categories = products.OrderBy(p => p.Category.Name).Select(p => p.Category.Name).Distinct();
12 
13     if (!string.IsNullOrEmpty(category)) 14  { 15         products = products.Where(p => p.Category.Name == category); 16  } 17 
18     ViewBag.Category = new SelectList(categories);
19 
20     return View(products.ToList());
21 }

  不調試啟動站點,如果我們現在執行一個搜索,然后按照分類進行過濾,我們會發現在分類下拉列表中的Toys分類依然存在,如圖3-8所示。

圖3-8:搜索“Red”,然后按Clothes分類過濾之后,Toys分類依然可以讓用戶進行選擇

3.3 使用視圖模型實現更加復雜的過濾

  一旦我們使用ViewBag將多個數據由控制器傳遞給視圖時,我們的代碼會變得難以維護,這主要歸咎於ViewBag的動態特性,Visual Studio沒有對ViewBag可用的屬性提供智能提示功能,這很容易造成ViewBag屬性的拼寫錯誤。

  由控制器向視圖傳遞信息更好的一種方法是使用視圖模型,而不是ViewBag。然后視圖基於這種模型,而不是基於域模型(到目前為止,我們的視圖都是基於域模型的)。一些開發人員將這種概念進一步細分,將他們所有的視圖都僅僅基於視圖模型。在本書中,我們混用視圖模型和域模型。

  在這個例子中,我們將學習對於每一個分類,如何實現在下拉列表中同時顯示該分類所對的產品數量。為了實現這個功能,我們需要一個視圖模型保存我們傳遞給視圖的所有信息。

3.3.1 創建視圖模型

  在BabyStore項目下創建一個名為“ViewModels”的新文件夾,然后在其添加一個名為ProductIndexViewModel的類,然后在該類中編寫如下代碼:

 1 using System.Collections.Generic;
 2 using System.Linq;
 3 using System.Web.Mvc;
 4 using BabyStore.Models;
 5 
 6 namespace BabyStore.ViewModels
 7 {
 8     public class CategoryWithCount
 9     {
10         public int ProductCount { get; set; }
11         public string CategoryName { get; set; }
12         public string CatNameWithCount
13         {
14             get
15             {
16                 return CategoryName + " (" + ProductCount.ToString() + ")";
17             }
18         }
19     }
20 
21     public class ProductIndexViewModel
22     {
23         public IQueryable<Product> Products { get; set; }
24         public string Search { get; set; }
25         public IEnumerable<CategoryWithCount> CatsWithCount { get; set; }
26         public string Category { get; set; }
27 
28         public IEnumerable<SelectListItem> CatFilterItems
29         {
30             get
31             {
32                 var allCats = CatsWithCount.Select(cc => new SelectListItem
33                 {
34                     Value = cc.CategoryName,
35                     Text = cc.CatNameWithCount
36                 });
37 
38                 return allCats;
39             }
40         }
41     }
42 }

  這個類文件看起來比我們之前使用的代碼都要復雜,因此,我們將一步一步地解釋每個屬性的用途。

  首先,這個文件包含兩個類,分別為CategoryWithCount和ProductIndexViewModel。

  CategoryWithCount類比較簡單,主要用於保存分類的名字和該分類所包含該的產品數量。

  第一個屬性ProductCount用於保存該分類中所包含的產品數量。

  第二個屬性CategoryName用於保存分類的名稱。

  第三個屬性CatNameWithCount返回ProductCount和CategoryName合並之后的格式化字符串,例如:Clothes (2)。

  ProductIndexViewModel類需要保存先前使用ViewBag傳遞給視圖的信息組合以及模型IEnumerable<BabyStore.Models.Product>(該模型出現在/Views/Products/Index.cshtml文件的頂部)。

  在這個類中的第一個屬性public IQueryable<Product> Products { get; set; },將會用於替換在當前視圖中使用的模型。

  第二個屬性是public string Search { get; set; },將會用於替換在當前ProductsController類中使用的ViewBag.Search。

  第三個屬性是public IEnumerable<CategoryWithCount> CatsWithCount { get; set; },將會用於存儲CategoryWithCount類型的集合,該集合用於視圖中的下拉列表中。

  第四個屬性Category將會用作視圖中的下拉列表的名稱。

  最后一個屬性public IEnumerable<SelectListItem> CatFilterItems用於返回一個SelectListItem類型的列表,該屬性將會利用第三個屬性CatsWithCount生成SelectListItem對象,該對象的Value對應CategoryWithCount的CategoryName,Text對應CategoryWithCount的CatNameWithCount;Value用於視圖中下拉列表的值,Text用於視圖中下拉列表的顯示文本。

3.3.2 修改ProductsController控制器的Index方法以使用視圖模型

  更新\Controllers\ProductsController.cs文件的Index方法以便它匹配下列代碼。改動的代碼使用粗體高亮顯示:使用視圖模型而不是ViewBag返回項目的計數。首先,在該文件的上方添加一個using語句,以使該類可以訪問我們剛剛創建的ProductIndexViewModel類。

 1 using BabyStore.ViewModels;  2 
 3 public ActionResult Index(string category, string search)
 4 {
 5     // instantiate a new view model
 6     ProductIndexViewModel viewModel = new ProductIndexViewModel();  7 
 8     var products = db.Products.Include(p => p.Category);
 9 
10     if (!string.IsNullOrEmpty(search))
11     {
12         products = products.Where(p => p.Name.Contains(search) || p.Description.Contains(search) || p.Category.Name.Contains(search));
13         viewModel.Search = search; 14     }
15 
16     // group search results into categories and count how many item in each category
17     viewModel.CatsWithCount = from matchinngProducts in products 18                               where matchinngProducts.CategoryID != null
19  group matchinngProducts by matchinngProducts.Category.Name into catGroup 20                               select new CategoryWithCount() 21  { 22                                   CategoryName = catGroup.Key, 23                                   ProductCount = catGroup.Count() 24  }; 25 
26     if (!string.IsNullOrEmpty(category))
27     {
28         products = products.Where(p => p.Category.Name == category);
29     }
30 
31     viewModel.Products = products; 32 
33     return View(viewModel);
34 }

  在這個方法內的第一處改動的代碼是創建一個視圖模型:ProductIndexViewModel viewModel = new ProductIndexViewModel();

  代碼viewModel.Search = search;將變量search賦值給viewModel而不是ViewBag。

  第三處代碼改動是LINQ語句,該語句將一個CategoryWithCount對象的列表賦值給viewModel的CatsWithCount屬性。在這個例子中,因為這個查詢比較復雜,我們使用了另外一種形式的LINQ,該形成稱之為查詢語法(query syntax),該語法使得查詢語句更容易閱讀。

  該語句的下列代碼,首先使用where子句過濾掉分類ID為null的產品,然后按照分類名稱進行分組。

from matchinngProducts in products
where matchinngProducts.CategoryID != null
group matchinngProducts by matchinngProducts.Category.Name into catGroup

  對於每一個分組,分類的名稱和該分類所對應的產品數量被賦值給一個CategoryWithCount對象。

select new CategoryWithCount()
{
  CategoryName = catGroup.Key,
  ProductCount = catGroup.Count()
};

  最后一處改動的代碼將products變量賦值給viewModel的Products屬性,而不是將它傳遞給視圖。緊接着,我們將viewModel傳遞給視圖,代碼如下:

viewModel.Products = products;

return View(viewModel);

  注意,不屬於任何分類的的產品將不會顯示在分類的下拉列表中,但是依然能夠被搜索到。

3.3.3 修改視圖以便使用視圖模型完成新過濾功能

   更新\Views\Products\Index.cshtml文件,以便使用視圖模型生成過濾下拉列表、檢索搜索字符串、顯示表格標題以及顯示產品列表。進行一下更改:

 1 @model BabyStore.ViewModels.ProductIndexViewModel  2 
 3 @{
 4     ViewBag.Title = "Index";
 5 }
 6 
 7 <h2>Index</h2>
 8 
 9 <p>
10     @Html.ActionLink("Create New", "Create")
11     @using(Html.BeginForm("Index", "Products", FormMethod.Get))
12     {
13         <label>Filter by category:</label>@Html.DropDownListFor(vm => vm.Category, Model.CatFilterItems, "All"); 14         <input type="submit" value="Filter"/>
15         <input type="hidden" name="Search" id="Search" value="@Model.Search"/>
16     }
17 </p>
18 <table class="table">
19     <tr>
20         <th>
21             @Html.DisplayNameFor(model => model.Category)
22         </th>
23         <th>
24             @Html.DisplayNameFor(model => model.Products.First().Name)
25         </th>
26         <th>
27             @Html.DisplayNameFor(model => model.Products.First().Description)
28         </th>
29         <th>
30             @Html.DisplayNameFor(model => model.Products.First().Price)
31         </th>
32         <th></th>
33     </tr>
34 
35 @foreach (var item in Model.Products) {
36     <tr>
37         <td>
38             @Html.DisplayFor(modelItem => item.Category.Name)
39         </td>
40         <td>
41             @Html.DisplayFor(modelItem => item.Name)
42         </td>
43         <td>
44             @Html.DisplayFor(modelItem => item.Description)
45         </td>
46         <td>
47             @Html.DisplayFor(modelItem => item.Price)
48         </td>
49         <td>
50             @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
51             @Html.ActionLink("Details", "Details", new { id=item.ID }) |
52             @Html.ActionLink("Delete", "Delete", new { id=item.ID })
53         </td>
54     </tr>
55 }
56 
57 </table>

  對此文件所做的代碼修改十分簡單,但意義重大。第一處改動@model BabyStore.ViewModels.ProductIndexViewModel只是簡單地告訴視圖使用ProductIndexViewModel作為該視圖基於的模型。注意該模型現在是一個單一的類,而不是一個可枚舉的集合。

  第二處改動@Html.DropDownListFor(vm => vm.Category, Model.CatFilterItems, "All");基於視圖模型的CatFilterItems屬性生成一個下拉列表,第二個參數用於生成下拉列表中的顯示數據以及值。第一個參數vm => vm.Category指定該下拉列表的名稱,當提交該表單時,該下拉列表的名稱將出現在URL的查詢字符串部分。因為該控件的名稱是Category,因此,我們先前在URL中查找Category參數的代碼將會繼續正確地工作。

  第三次改動保確保隱藏域現在引用的是視圖模型而不是ViewBag:

<input type="hidden" name="Search" id="Search" value="@Model.Search" />

  當我們需要生成表格標題時,不像看起來那么簡單,因為HTML輔助器的DisplayNameFor方法不適用於集合,因此,我們基於視圖模型的Products屬性來顯示標題。分類標題十分簡單,因為這是視圖模型的一個屬性。但是,顯示Product類的Description屬性的名字不能使用類似這樣的代碼@Html.DisplayNameFor(model => model.Products.Description),而是要求我們使用First()方法,強制輔助器使用自於products集合中的,一個實際的Product實體,代碼如下:

@Html.DisplayNameFor(model => model.Products.First().Description)

  這段代碼將會生成一個基於Product類的Description屬性的表格標題。如果在數據庫中的Products表中沒有數據,該代碼也會正確工作。

  提示:當HTML輔助器的DisplayNameFor方法用於集合而不是單一對象時,使用First()方法以便訪問我們要想顯示的屬性的名稱。

  最后一處改動是@foreach (var item in Model.Products) {,確保我們現在使用視圖模型的Products屬性來顯示產品信息。

  不調試啟動站點,然后點擊產品鏈接,我們會發現在分類下拉列表中的每個分類都帶有一個數量,這反映出每個分類所包含的產品的數量。圖3-9顯示了我們在搜索框中輸入“Red”后,下拉列表所顯示的條目。

圖3-9:在下拉列表中的每一個分類都包含所匹配的產品數量

3.4 小節

  在這一章中,我們學習了如何添加搜索功能,包括如何使用Bootstrap添加一個搜索框,然后學習了怎樣發現更多關於使用Boootstrap如何樣式化的站點的信息。我們還演示了如何使用ViewBag添加一個對於搜索結果的過濾,以及如何使用視圖模型添加一個更加復雜的過濾。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM