<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Loading

      ASP.NET Core Blazor 初探之 Blazor WebAssembly

      最近Blazor熱度很高,傳說馬上就要發布正式版了,做為微軟腦殘粉,趕緊也來湊個熱鬧,學習一下。

      Blazor

      Blazor是微軟在ASP.NET Core框架下開發的一種全新的Web開發框架。Blazor利用WebAssembly使得開發者可以拋開JavaScript而使用優雅的C#來開發web單頁應用。微軟利用WebAssembly在瀏覽器里實現了一個.NET Runtime,任何.NET STANDARD 2.1的代碼都可以在瀏覽器上運行,真的是屌炸了。Blazor強化了Razor模板引擎,并且借鑒了當前熱門前端框架的優點,比如雙向綁定技術,組件化,使前端開發敏捷高效。如果你對NG,VUE等框架熟悉那么很容易找到其中的共通點。

      Blazor WebAssembly

      Blazor 技術又分兩種:

      • Blazor WebAssembly
      • Blazor Server

      Blazor WebAssembly 是真正的SPA,頁面的渲染在前端實現,可以實現真正的前后端分離設計。而Blazor Server可以認為是前者的服務端渲染版本,它使用SignalR實現了客戶端的實時通訊,它的計算跟渲染都在服務端處理。本次咱先研究WebAssembly技術,因為我覺得它的應用前景可能更適合一般項目。廢話不多說,直接開干吧,我們的目標還是完成一個標準的對學員進行CRUD的并且前后端分離的小項目。

      安裝Blazor WebAssembly模板

      dotnet new -i Microsoft.AspNetCore.Components.WebAssembly.Templates::3.2.0-preview5.20216.8
      

      因為Blzor WebAssembly還在預覽階段所以要手工安裝模板,在控制臺運行以上命令來安裝最新的模板。

      新建Blazor WebAssembly項目

      打開vs找到Blazor的項目模板,就是那個特別像火影標志的那個圖標。新建一個項目名叫BlazorWebAssemblyApp。點下一步,這里會讓選是Blazor Server還是Blazor WebAssembly,不要選錯了。
      YmBx8P.md.png
      先看一下項目結構:
      YmrQQf.png
      Blazor Webassembly的項目結構比較簡單,跟Razor Page的項目結構比較類似。

      新建ASP.NET CORE WebApi項目

      我們的目標是打造一個前后端分離的項目,那么自然還要建一個Api項目。并且這個項目對外提供一個Student的Restful API。在vs里新建ASP.NET CORE WebApi項目,名為BlazorWebassemblyApisite。
      為了演示方便,使用靜態變量實現一個StudentRepository。

          public class Student
          {
              public int Id { get; set; }
              public string Name { get; set; }
      
              public string Class { get; set; }
      
              public int Age { get; set; }
      
              public string Sex { get; set; }
          }
      
          public interface IStudentRepository
          {
              List<Student> List();
      
              Student Get(int id);
      
              bool Add(Student student);
      
              bool Update(Student student);
      
              bool Delete(int id);
          }
      
       public class StudentRepository : IStudentRepository
          {
              private static List<Student> Students = new List<Student> {
                      new Student{ Id=1, Name="小紅", Age=10, Class="1班", Sex="女"},
                      new Student{ Id=2, Name="小明", Age=11, Class="2班", Sex="男"},
                      new Student{ Id=3, Name="小強", Age=12, Class="3班", Sex="男"}
              };
      
              public bool Add(Student student)
              {
                  Students.Add(student);
      
                  return true;
              }
      
              public bool Delete(int id)
              {
                  var stu = Students.FirstOrDefault(s => s.Id == id);
                  if (stu != null)
                  {
                      Students.Remove(stu);
                  }
      
                  return true;
              }
      
              public Student Get(int id)
              {
                  return Students.FirstOrDefault(s => s.Id == id);
              }
      
              public List<Student> List()
              {
                  return Students;
              }
      
              public bool Update(Student student)
              {
                  var stu = Students.FirstOrDefault(s => s.Id == student.Id);
                  if (stu != null)
                  {
                      Students.Remove(stu);
                  }
      
                  Students.Add(student);
                  return true;
              }
          }
      

      在Startup里注冊這個Repository:

      services.AddScoped<IStudentRepository, StudentRepository>();
      

      實現StudentController用來暴露API:

      [ApiController]
          [Route("[controller]")]
          public class StudentController : ControllerBase
          {
              private IStudentRepository _studentRepository;
              public StudentController(IStudentRepository studentRepository)
              {
                  _studentRepository = studentRepository;
              }
      
              [HttpGet]
              public List<Student> Get()
              {
                  return _studentRepository.List();
              }
      
              [HttpGet("{id}")]
              public Student Get(int id)
              {
                  return _studentRepository.Get(id);
              }
      
              [HttpPost]
              public Student Post(Student model)
              {
                  _studentRepository.Add(model);
      
                  return model;
              }
      
              [HttpPut]
              public Student Put(Student model)
              {
                  _studentRepository.Update(model);
      
                  return model;
              }
      
              [HttpDelete("{id}")]
              public void Delete(int id)
              {
                  _studentRepository.Delete(id);
              }
          }
      

      因為我們的前后端項目會分兩個網址部署,所以肯定需要配置CORS的問題:

       app.UseCors(config =>
                  {
                      config.AllowAnyOrigin();
                      config.AllowAnyMethod();
                      config.AllowAnyHeader();
                  });
      

      這樣我們的后端API網站就完成了,接來下就是真正的Blazor環節了。

      配置HttpClient與注入

      讓我們切換回BlazorWebAssemblyApp項目。我們的Blazor項目需要通過Http與API站點進行通信,所以肯定需要一個訪問Http的類庫。如果是JavaScript我們平時使用如axios等庫,但是Blazor可以使用C#實現的HttpClient,在前端由C#發起Http請求,Cool!當然最后HttpClient發出的請求會還是會轉換為瀏覽器的Fetch請求。Blazor項目支持依賴注入,這個用法跟ASP.NET Core項目的體驗是一致的,通過IServiceCollection配置注入的生命周期:

      builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri("https://localhost:6001") });
      

      Blazor的注入同樣分Transient、Scope、Singleton等生命周期。這里我們注冊HttpClient為Transient,并且配置baseAddress為https://localhost:6001,這是ApiSite的地址。

      實現學生列表(student/list)

      因為新建成的項目會自動生成一些頁面,為了減少干擾,先刪掉點內容。
      簡化MainLayout.razor,刪除一些不必要的東西:

      @inherits LayoutComponentBase
      
      <div class="main">
          <div class="content px-4">
              @Body
          </div>
      </div>
      

      刪除Index.razor的內容,就留一個page指令:

      @page "/"
      

      新建Model文件夾,用來存放Student模型,這里其實可以把Api網站的Student模型提取出來,作為公共的定義模塊,為了簡單就直接定義一個一模一樣的吧:

          public class Student
          {
              public int Id { get; set; }
              public string Name { get; set; }
      
              public string Class { get; set; }
      
              public int Age { get; set; }
      
              public string Sex { get; set; }
          }
      

      新建一個student文件夾,在這個文件夾內新建一個List.razor文件:

      @page "/student/list"
      
      @using BlazorWebAssemblyApp.Model
      
      @inject HttpClient Http
      
      <h1>List</h1>
      
      <p class="text-right">
          <a class="btn btn-primary" href="/student/add">Add</a>
      </p>
      
      <table class="table">
          <tr>
              <th>Id</th>
              <th>Name</th>
              <th>Age</th>
              <th>Sex</th>
              <th>Class</th>
              <th></th>
          </tr>
          @if (_stutdents != null)
          {
              foreach (var item in _stutdents)
              {
                  <tr>
                      <td>@item.Id</td>
                      <td>@item.Name</td>
                      <td>@item.Age</td>
                      <td>@item.Sex</td>
                      <td>@item.Class</td>
                      <td>
                          <a class="btn btn-primary" href="/student/modify/@item.Id">修改</a>
                          <a class="btn btn-danger" href="/student/delete/@item.Id">刪除</a>
                      </td>
                  </tr>
              }
          }
      
      </table>
      
      @code {
          private List<Student> _stutdents;
      
          protected override async Task OnInitializedAsync()
          {
              var students = await Http.GetFromJsonAsync<List<Student>>("/student");
              this._stutdents = students;
          }
      }
      

      這個文件大體上看跟RazorPages的頁面差不多,Html主體使用razor語法渲染。但是還是有很大的不同,讓我們從頭開始一個個的解釋:

      @page "/student/list"
      

      @page指令指示這個頁面的路由,當用戶訪問/student/list時就會路由到這個頁面

      @using BlazorWebAssemblyApp.Model
      

      @using指令不多說了,引用namespace,這個跟Razor Pages是一樣的

      @inject HttpClient Http
      

      @inject指令從字面看就很容易理解,注入。上面的意思就是注入HttpClient對象,并且命名為Http。后面就可以使用這個Http對象了,當然前提是在Program里注冊好。

      @code {
          private List<Student> _stutdents;
      
          protected override async Task OnInitializedAsync()
          {
              var students = await Http.GetFromJsonAsync<List<Student>>("/student");
              this._stutdents = students;
          }
      }
      

      @code指令指示這個scope里的內容為C#代碼。雖然沒有明確定義為class,但是顯然這個代碼塊最后會被編譯成一個類。這個類里的變量可以作為razor模板的數據源,可以進行綁定或者for循環。OnInitializedAsync方法為初始化方法,可以在這里處理一些初始化工作,比如我們這里就是通過一次Http請求獲取學生的列表數據。如果是同步方法請使用OnInitialized。這個文件的結構看起是不是很像VUE的單文件組件,笑哭。
      讓我們運行一下吧:
      YmIGvQ.md.png

      實現新增學生頁面(/student/add)

      當點擊列表頁面的Add按鈕的時候,需要導航至新增頁面,導航直接使用a標簽沒有任何問題。

         <a class="btn btn-primary" href="/student/add">Add</a>
      

      考慮到后面還有編輯頁面,新增跟編輯頁面整體是一樣的,只是后臺處理的邏輯不一樣。既然Blazor支持組件化,那么這種重復的東西既然是封裝為一個組件為好了。

      封裝Edit組件

      我們把對學生信息編輯的功能抽象成一個組件叫做Edit。在student文件夾下新建一個component文件夾,在文件夾內新建Edit.razor文件:

      @using BlazorWebAssemblyApp.Model
      
      <div>
          <div class="form-group">
              <label>Id</label>
              <input @bind="Student.Id" class="form-control" />
          </div>
          <div class="form-group">
              <label>Name</label>
              <input @bind="Student.Name" class="form-control"/>
          </div>
          <div class="form-group">
              <label>Age</label>
              <input @bind="Student.Age" class="form-control" />
          </div>
          <div class="form-group">
              <label>Class</label>
              <input @bind="Student.Class" class="form-control" />
          </div>
          <div class="form-group">
              <label>Sex</label>
              <input @bind="Student.Sex" class="form-control" />
          </div>
      
           <button class="btn btn-primary" @onclick="TrySave">
               保存
           </button>
      
      </div>
      
      @code{
      
          [Parameter]
          public Student Student { get; set; }
          [Parameter]
          public EventCallback<Student> OnSaveCallback { get; set; }
      
          protected override Task OnInitializedAsync()
          {
              if (Student == null)
              {
                  Student = new Student();
              }
      
              return Task.CompletedTask;
          }
      
          private void TrySave()
          {
              OnSaveCallback.InvokeAsync(Student);
          }
      }
      

      繼續解釋下這個文件:

      數據綁定

      <input @bind="Student.Id" class="form-control" />
      

      使用@bind指令可以跟某個對象實現的屬性實現雙向綁定。@bind指令本質上是通過對value跟onchange這個屬性的綁定配合來實現雙向綁定,這個套路怎么那么熟悉?對了VUE也是這么干的,笑哭。@bind="Student.Id"翻譯過來等效于:

      <input value="@Student.Id"
          @onchange="@((ChangeEventArgs __e) => Student.Id = 
              __e.Value.ToString())" />
      

      事件綁定

      除了對數據的綁定,Blazor還支持對事件的綁定:

           <button class="btn btn-primary" @onclick="TrySave">
               保存
           </button>
      

      @onclick="TrySave" 表示這個button的click事件指向TrySave這個方法。

      組件屬性

      我們封裝組件經常對外暴露屬性,以便接受外部傳入的數據,比如我們這個Edit組件就需要外部傳入一個Student對象才能正常工作。

          [Parameter]
          public Student Student { get; set; }
      

      我們在@code代碼里的屬性上打上[Parameter]標簽。這里叫做Parameter,估計是為了跟C#里的屬性(property)進行區分。這樣的話,這個屬性就可以接受父組件的傳參,注意這個屬性是單項數據流,組件內對Student修改并不會修改外部組件的數據源,這個也很VUE啊,笑哭。

      組件事件

      我們除了需要對外暴露屬性,常常還需要對外暴露事件,用來通知外部組件。當外部組件接受到事件的時候可以進行相應的處理。比如這個Edit組件點擊保存的時候并沒有進行真正的保存操作,而是對外拋一個事件,當外部組件接受這個事件的時候進行真正的處理,比如是調用新增API還是更新API。

      [Parameter]
          public EventCallback<Student> OnSaveCallback { get; set; }
      

      我們在@code代碼里的EventCallback事件上打上[Parameter]標簽。這樣外部組件就可以注冊這個事件了。當我們在這個組件上點擊保存的時候激發這個事件,并且把修改過的Student對象傳遞出去。

       OnSaveCallback.InvokeAsync(Student);
      

      使用Edit組件

      Edit組件封裝完成了,讓我們開始使用它。新建一個Add.razor文件,并且在這里使用Edit組件。組件的使用跟VUE等一樣,使用一個自定義的Tag插入到html的里。

      @page "/student/add"
      
      @using BlazorWebAssemblyApp.Model
      
      @inject HttpClient Http
      @inject NavigationManager NavManager
      
      <h1>Add</h1>
      
      <Edit OnSaveCallback="OnSaveAsync"></Edit>
      
      <div class="text-danger">
          @_errmsg
      </div>
      
      @code {
      
          private Student Student { get; set; }
      
          private string _errmsg;
      
          protected override Task OnInitializedAsync()
          {
              return base.OnInitializedAsync();
          }
      
          private async Task OnSaveAsync(Student student)
          {
              Student = student;
      
              var result = await Http.PostAsJsonAsync("/student", Student);
      
              if (result.IsSuccessStatusCode)
              {
                  NavManager.NavigateTo("/student/list");
              }
              else
              {
                  _errmsg = "保存失敗";
              }
          }
      
      }
      

      Add.razor的邏輯很簡單,接受Edit組件的保存事件,然后把Student通過Http提交到后臺。

      <Edit OnSaveCallback="OnSaveAsync"></Edit>
      

      通過OnSaveCallback="OnSaveAsync"設置Edit組件的OnSaveCallback事件回調為OnSaveAsync方法。
      當我們保存功能的時候,需要跳轉到列表頁面。Blazor提供了一個簡單的導航框架:NavigationManager。NavigationManager是默認注冊到IoC容器的,所以可以直接使用@inject注入到需要的地方:

      @inject NavigationManager NavManager
      

      調用NavigateTo方法進行頁面跳轉。

      NavManager.NavigateTo("/student/list");
      

      讓我們運行一下看看吧:
      YnDgXT.md.png

      實現修改學生信息頁面(/student/modify)

      修改界面相對新增頁面會多涉及一個知識點,url傳參。當我們需要修改學生信息的時候,需要傳遞一個id參數過去,告訴頁面需要修改哪一個學生。

      @page "/student/modify/{Id:int}"
      
      @using BlazorWebAssemblyApp.Model
      @using BlazorWebAssemblyApp.Data
      
      @inject HttpClient Http
      @inject NavigationManager NavManager
      @inject Store Store
      
      <h1>Modify</h1>
      
      <Edit Student="Student" OnSaveCallback="OnSaveAsync"></Edit>
      
      <div class="text-danger">
          @_errmsg
      </div>
      
      @code {
          [Parameter]
          public int Id { get; set; }
      
          private Student Student { get; set; }
      
          private string _errmsg;
      
          protected override void OnInitialized()
          {
              Student = Store.GetStudentById(Id);
          }
      
          private async Task OnSaveAsync(Student student)
          {
              Student = student;
      
              var result = await Http.PutAsJsonAsync("/student", Student);
      
              if (result.IsSuccessStatusCode)
              {
                  NavManager.NavigateTo("/student/list");
              }
              else
              {
                  _errmsg = "保存失敗";
              }
          }
      }
      

      @page指令配置的路由模板可以支持參數匹配

      @page "/student/modify/{Id:int}"
      

      我們在列表頁面使用a標簽進行跳轉,url組合成/student/modify/1樣式,其中1會匹配給屬性Id,并且這里限制了Id的類型為int。

      <Edit Student="Student" OnSaveCallback="OnSaveAsync"></Edit>
      

      對Edit組件的使用,修改頁面跟新增頁面不同的是,修改頁面需要傳遞一個Student對象到Edit組件內部,以便顯示學員信息。

      實現一個Store

      修改頁面顯然需要顯示學生當前的信息。我們通過url傳遞過來的參數只有id,那么需要一次Http請求去后臺獲取學生信息,這沒什么問題。但是如果是SPA應用,其實學生的信息本身已經在列表頁面了,對于那些不是高頻更新的數據,我們沒有必要每次都去數據庫里獲取最新的數據,況且即使你從數據庫里獲取到了最新的數據,也可能在你修改的過程中被別人修改。因為SPA跟傳統的Web項目不同,它可以完整的維護狀態,所以如果我們把列表的數據存起來,那么其他地方可以很方便直接在內存里查詢到,高效又便捷。通常使用Angularjs的時候這種場景會使用一個單例的Service來完成。這里我也簡單使用C#來實現一個Service來存儲頁面的數據,名稱就借鑒一下VUE的Vuex吧,叫Store。

         public class Store
          {
              private  List<Student> _students;
      
              public  void SetStudents(List<Student> list)
              {
                  _students = list;
              }
      
              public List<Student> GetStudents()
              {
                  return _students;
              }
      
              public  Student GetStudentById(int id)
              {
                  var stu = _students?.FirstOrDefault(s => s.Id == id);
      
                  return stu;
              }
          }
      
      builder.Services.AddSingleton<Store>();
      

      這個service很簡單,就是一個簡單的class。使用List來存儲學生列表信息,對外提供幾個Set,Get方法來存儲數據跟獲取數據。這里我并沒有手工實現為單例,直接在框架的容器上注冊為單例生命周期。

      改造列表頁面

      現在我們有了Store,所以當列表獲取到數據后需要存儲到Store里,這樣我們在修改頁面或者其他地方就能根據id直接獲取數據了。

      @inject Store Store
      
      @code {
          private List<Student> _stutdents => Store.GetStudents();
      
          protected override async Task OnInitializedAsync()
          {
              var students = await Http.GetFromJsonAsync<List<Student>>("/student");
              Store.SetStudents(students);
          }
      }
      

      我們的改造完成了,運行一下看看吧:
      YnTaRK.md.png

      實現刪除頁面(/student/delete)

      刪除頁面比較簡單,使用前面的知識點輕松可以搞定。同樣通過Url傳遞一個Id到刪除頁面,頁面上獲取學生數據后進行顯示,并且提示用戶是否確定刪除這個學生信息。如果點擊確定就調用刪除API進行刪除操作,如果點擊取消則回退到前一頁。為了增加樂趣,這里會增加C#跟JavaScript交互的內容。

      @page "/student/delete/{id:int}"
      
      @using BlazorWebAssemblyApp.Model
      @using BlazorWebAssemblyApp.Data
      
      @inject HttpClient Http
      @inject Store Store
      @inject NavigationManager NavManager
      @inject IJSRuntime JSRuntime
      
      <h1>Delete</h1>
      
      <h3>
          確定刪除(@Student.Id)@Student.Name ?
      </h3>
      
      <button class="btn btn-danger" @onclick="OnDeleteAsync">
          刪除
      </button>
      
      <button class="btn btn-info" @onclick="OnCancel">取消</button>
      
      @code {
          [Parameter]
          public int Id { get; set; }
      
          private Student Student { get; set; }
      
          protected override void OnInitialized()
          {
              Student = Store.GetStudentById(Id);
          }
      
          private async Task OnDeleteAsync()
          {
              var result = await Http.DeleteAsync("/student/" + Id);
              if (result.IsSuccessStatusCode)
              {
                  NavManager.NavigateTo("/student/list");
              }
          }
      
          private void OnCancel()
          {
              JSRuntime.InvokeVoidAsync("history.back");
          }
      }
      

      IJSRuntime

      當用戶點擊取消的時候我們需要回退到前一個頁面,但是Blazor的NavigationManager并沒有提供GoBack這種操作。這個我實在是想不明白,不管是WPF的導航框架、還是VUE的路由服務都有這種機制,以至于我還得通過JavaScript的能力去調用瀏覽器的原生后退功能來實現。Blazor中想要跟JavaScript交互需要注入JSRuntime對象:

      JSRuntime.InvokeVoidAsync("history.back");
      

      我們在取消按鈕的事件代碼里調用以上代碼,這樣就能順利后退了。
      YuZE5R.md.png

      總結

      通過以上,我們使用Blazor實現了一個簡單的前后端分離的SPA。總體涉及了Blazor的幾個重要知識點,比如:數據綁定,事件處理,封裝組件,JavaScript交互等。其中每個知識點都可以再深入展開來寫一篇。我們使用Blazor,在幾乎沒用JavaScript的情況下順利的完成了一個SPA,總體感覺還是比較良好的。雖然不用JavaScript,但是顯然它借鑒了熱門JavaScript框架的一些特點,如果你有一點前端基礎跟.NET基礎很容易就能上手。但是,我不想在這神吹Blazor,畢竟它也沒有到讓人驚艷的地步,比如我熟悉Angular,熟悉VUE,說真的,目前來說,我沒有什么動力切換到Blazor上來。如果Blazor早出現那么幾年,或許一切都不一樣了。但是,又要但是。。。但是我還是會學習Blazor,就像我當年學習Silverlight一樣。沒錯,我就是那個被微軟傷害兩次(Silverlight,Windows Phone)依然待他如初戀的男人,笑哭。微軟的東西雖然不流行,但是不代表它不先進,有的時候或許是過于先進。比如MVVM、雙向綁定、前后端分離,這些概念都是當年Silverlight RIA應用早就有的。雖然Silverlight后來黃了,但是它里面的一些設計理念,開發模式并不落后,甚至是超前的。這些經驗對后來我學習Angularjs,VUE來說有非常大的幫助,學起來得心應手,因為套路都是那個套路。所以哪天說不定WebAssembly大行其道,Blazor又成了開山鼻祖,學習它的經驗一定是有用的。

      最后demo的源碼:BlazorWebAssemblyAppDemo

      關注我的公眾號一起玩轉技術

      posted @ 2020-05-09 00:18  Agile.Zhou  閱讀(10902)  評論(17)    收藏  舉報
      主站蜘蛛池模板: 国产中年熟女大集合| 无码伊人久久大杳蕉中文无码| 国产国拍亚洲精品永久软件| 国产精品视频亚洲二区| 株洲市| 亚洲av影院一区二区三区| 99久久激情国产精品| 丰顺县| 99久久国产福利自产拍| 欧美特级午夜一区二区三区| 国产睡熟迷奷系列网站| 国产天美传媒性色av高清| 婷婷六月天在线| 国产做a爱片久久毛片a片| 情欲少妇人妻100篇| 在线看片免费不卡人成视频| 国产一级av在线播放| 成人av天堂男人资源站| 久久久无码精品国产一区| 99国精品午夜福利视频不卡99| 久久久国产乱子伦精品作者| 丁香五月天综合缴情网| 国产对白老熟女正在播放| 成年女人午夜毛片免费视频| 精品91在线| 亚洲精品国产福利一区二区| 91久久国产成人免费观看| 亚洲国产精品综合久久2007| 久久久国产成人一区二区| 婷婷成人丁香五月综合激情| 亚洲午夜激情久久加勒比| 成人精品区| 亚洲精品天堂成人片AV在线播放| 久久99精品久久久大学生| 色综合一本到久久亚洲91| 亚洲欧美中文日韩在线v日本| 久久99精品久久久久久9| 欧美成本人视频免费播放| 国产熟睡乱子伦午夜视频| 色吊丝一区二区中文字幕| 亚洲乱码一区二区三区视色 |