当前位置:   article > 正文

Avalonia基础学习_avalonia groupbox

avalonia groupbox

Avalonia学习

基本知识点

代码项目结构

这是典型的MVVM模式结构,主要代码文件及目录用途如下:
Program.cs:系统入口
App.axaml:应用样式及初始化等
ViewLocator:View层与ViewModel层的映射

1.模型-视图-视图模型(MVVM)模式在Avalonia中创建一个简单的TODO应用程序
2.MVVM是用于编写GUl应用程序的常见模式,也是编写Avalonia应用程序时推荐使用的模式。这里我们将假设一个CRUD应用程序,但这些概念中的大多数都可以应用于所有类型的应用程序。在本指南中,我们将使用ReactiveUl,这是一个基于。net Reactive的MVVM框架扩展。本指南将解释如何使用MVVM和ReactiveUl与Avalonia,但你也可以看到ReactiveUl文档获取更详细的信息

3.创建一个mvvm的项目
你可以看到MVVM模式中的每个概念(模型、视图和视图模型)以及其他一些文件和目录
Assets目录保存应用程序的二进制资产,如图标和位图。放置在此目录中的文件将自动作为资源包含在应用程序中。
Models目录目前是空的,但顾名思义,这是我们将模型放置的地方。
ViewModels目录预先填充了视图模型的基类和应用程序主窗口的视图模型。
Views目录目前只包含应用程序主窗口。
App.axaml文件是放置应用于整个应用程序的XAML样式和模板的地方。
Program.cs文件是执行应用程序的入口点。在这里,如果需要,您可以为Avalonia配置平台选项。
ViewLocator.cs文件用于查找视图模型的视图。
4.在views文件夹中创建一个用户控件
这一切意味着什么?让我们逐行查看刚刚输入的代码。

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
             x:Class="ToDoList.Views.UcToDoList">
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

XAML中的根元素是UserControl。后面是一堆xnlns声明。每一个都声明一个XML名称空间
但最重要的是第一个:xmlns="https://github.com/avaloniaui "。没有这个条目,什么都不会起作用。
下一个XML名称空间是xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml "。这用于导入不是Avalonia特有的XAML特性。我们将在后面看到它的使用。
x:Class=“ToDoList.Views.UcToDoList”。这一行告诉XAML引擎在哪里可以找到伴随XAML的类。它是类的完全限定名。注意,这个属性的前缀是x: -这与我们之前看到的xnins:x条目有关。好了,这就是样板文件!

首先我们添加一个DockPanel作为UserControl的子控件。一个UserControl只能包含一个孩子,所以孩子通常是Avalonia的面板控件之一。面板控件的特殊之处在于它们可以有多个子控件。DockPanel是一种面板,它将控件布局在顶部、底部、左侧和右侧,由单个控件填充中间的剩余空间。

xmlns:views=“clr-namespace:Todo.Views”
views:TodoListView/
我们希望显示刚刚在Todo中创建的控件。需要加一下命名空间。任何不是核心Avalonia控件的控件通常都需要这种类型的映射,以便XAML引擎找到该控件。
5.创建模型,它将表示我们的数据,因为它将存储在数据库中。我们的模型将非常简单:每个TODO项将由一个文本描述和一个表示是否选中该项的布尔值组成。将以下类放在项目的Models目录中:Models/Todoltem.cs
6.创建视图模型。这个类将为我们的视图提供数据。我们已经创建了视图并将其命名为TodoListView因此相关的视图模型将称为TodoListViewlodel。把这个类放在你项目的Viewllodels目录中:ViewModels / TodoListViewModel cs
7.连接视图,现在我们已经设置了视图模型,我们需要让视图使用这些视图模型。我们通过使用Avalonia的数据绑定特性来做到这一点
主要的变化是,我们不再使用views:TodoListView/控件作为窗口的内容,而是将窗口的内容绑定到MainWindowViewModel。Content=“{Binding List}”

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="Todo.Views.MainWindow"
        Icon="/Assets/avalonia-logo.ico"
        Width="200" Height="300"
        Title="Avalonia Todo"
        Content="{Binding List}">
</Window>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

8.从视图中获取数据
首先要注意的是,我们已经将控件更改为ItemsControl:

<ItemsControl Items="{Binding Items}">
  • 1
<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="300"
             x:Class="Todo.Views.TodoListView">
  <DockPanel>
    <Button DockPanel.Dock="Bottom"
            HorizontalAlignment="Center">
      Add an item
    </Button>
    <ItemsControl Items="{Binding Items}">
      <ItemsControl.ItemTemplate>
        <DataTemplate>
          <CheckBox Margin="4"
                    IsChecked="{Binding IsChecked}"
                    Content="{Binding Description}"/>
        </DataTemplate>
      </ItemsControl.ItemTemplate>
    </ItemsControl>
  </DockPanel>
</UserControl>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

ItemsControl是一个非常简单的控件,用于显示分配给Items属性的集合中的每一项。这里每个项目都是TodoItem模型的一个实例,就像TodoListViewModel一样。每个项的显示方式由ItemTemplate控制。ItemTemplate接受一个DataTemplate,其内容对于每个项都是重复的。在本例中,我们将每个项目显示为CheckBox,检查状态绑定到TodoItenViewModel的IsChecked属性,内容绑定到Description

<ItemsControl.ItemTemplate>
  <DataTemplate>
    <CheckBox Margin="4"
              IsChecked="{Binding IsChecked}"
              Content="{Binding Description}"/>
  </DataTemplate>
</ItemsControl.ItemTemplate>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

9.ViewLocator定义了一个数据模板,用于将视图模型转换为视图。它定义了两个方法:Match(对象数据)查看数据,如果数据继承自ViewModelBase,则返回true,表示应该调用BuildBuild(对象数据)采用数据类型的完全限定名,并将字符串“ViewModel”替换为字符串“View”。然后,它尝试获取与该名称匹配的类型。如果找到匹配的类型,则创建该类型的实例并返回该实例
应用程序中有一个ViewLocator的实例。Application.DataTemplates

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Todo"
             x:Class="Todo.App">
    <Application.DataTemplates>
        <local:ViewLocator/>
    </Application.DataTemplates>
​
    <Application.Styles>
        <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
        <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
    </Application.Styles>
</Application>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

当ContentControl的实例(如Window)将其Content属性设置为非控件时,它会在控件树中搜索与内容数据匹配的DataTemplate。如果没有其他DataTemplate与数据匹配,它将最终到达应用程序数据模板中的ViewLocator, ViewLocator将执行其业务并返回相应视图的实例。

10.调用方法
Command属性描述单击按钮时要调用的命令,我们将它绑定到$parent[Window].DataContext.AddItem
$parent [Window]表示查找Window类型的祖先控件并获取它的DataContext
(即在本例中是MainWindowViewModel)并绑定到该视图模型上的AddItem方法

<Button DockPanel.Dock="Bottom"
            Command="{Binding $parent[Window].DataContext.AddItem}">
      Add an item
    </Button>
  • 1
  • 2
  • 3
  • 4

11.添加数据以及取消的命令

public AddItemViewModel()
        {
            var okEnabled = this.WhenAnyValue(
                x => x.Description,
                x => !string.IsNullOrWhiteSpace(x));
​
            Ok = ReactiveCommand.Create(
                () => new TodoItem { Description = Description }, 
                okEnabled);
            Cancel = ReactiveCommand.Create(() => { });
        }
        public ReactiveCommand<Unit, TodoItem> Ok { get; }
        public ReactiveCommand<Unit, Unit> Cancel { get; }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

首先,我们修改Description属性以引发更改通知。我们以前在主窗口视图模型中看到过这种模式。在这种情况下,我们为ReactiveUI实现变更通知,而不是专门为Avalonia实现:

    x => x.Description,
    x => !string.IsNullOrWhiteSpace(x));
  • 1
  • 2

现在Description已经启用了更改通知,我们可以使用WhenAnyValue将属性转换为IObservable形式的值流。以上代码可以理解为:对于Description的初始值,以及后续的更改选择使用该值调用string.IsNullOrWhiteSpace()结果的倒数这意味着okEnabled表示bool值流,当Description为非空字符串时将生成true,当Description为空字符串时将生成false。这正是我们想要的OK按钮启用。
然后我们创建一个ReactiveCommand并将其分配给Ok属性:

    () => new TodoItem { Description = Description }, 
    okEnabled);
  • 1
  • 2

ReactiveCommand的第二个参数。Create控制命令的启用状态,因此我们刚刚创建的可观察对象被传递到那里。第一个参数是在执行命令时运行的lambda。在这里,我们简单地用用户输入的描述创建模型TodoItem的实例。我们还为“Cancel”按钮创建了一个命令:

    取消命令总是启用的,所以我们不传递一个可观察对象来控制它的状态,我们只是传递一个“execute”lambda,在这种情况下,它什么都不做。

    12.绑定OK和Cancel按钮现在我们可以将视图中的OK和Cancel按钮绑定到我们刚才只在视图模型中创建的OK和Cancel命令:

                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="300"
                 x:Class="Todo.Views.AddItemView">
      <DockPanel>
        <Button DockPanel.Dock="Bottom" Command="{Binding Cancel}">Cancel</Button>
        <Button DockPanel.Dock="Bottom" Command="{Binding Ok}">OK</Button>
        <TextBox AcceptsReturn="False"
                 Text="{Binding Description}"
                 Watermark="Enter your TODO"/>
      </DockPanel>
    </UserControl>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

      14.这段代码利用了ReactiveCommand本身是一个可观察对象的事实,它在每次执行命令时都会产生一个值。你会注意到,当我们定义这些命令时,它们的声明略有不同

      public ReactiveCommand<Unit, TodoItem> Ok { get; }
      public ReactiveCommand<Unit, Unit> Cancel { get; }
      
      • 1
      • 2

      reactivecommand第二个类型参数指定执行命令时产生的结果类型。Ok生成TodoItem,而Cancel生成Unit。单位是虚空的反应版本。这意味着该命令不会产生任何值。可观察到的。Merge将任意数量的可观测数据的输出组合在一起,并将它们合并为单个可观察数据流。因为它们被合并到一个流中,所以它们需要具有相同的类型。因此,我们调用vm.Cancel.Select (_ => (TodoItem)null):这样做的效果是,每当Cancel可观察对象产生一个值时,我们都会选择一个空TodoItem。
      .Take(1)
      我们只对第一次点击OK或Cancel按钮感兴趣;一旦点击了其中一个按钮,我们就不需要再听任何点击了。Take(1)表示“只取可观察序列产生的第一个值”。
      15.最后,我们订阅可观察序列的结果。如果该命令导致生成了一个模型(即单击OK),那么将该模型添加到列表中。然后我们将Content设置回List,以便在窗口中显示列表并隐藏“AddltemView”。

      {
          if (model != null)
          {
              List.Items.Add(model);
          }
      ​
          Content = List;
      });
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      1.基本的使用方法

      axaml中定义 后台找到并设置对象
      在WPF中,当你在xaml文件中定义完UI并设置x:Name就可以在后台中直接使用对象名称进行操作.那是因为vs在你设计时自动生成了.g.i.cs文件(你可以在/obj中看到)

      而ava中不同,你需要在后台中自己Get到这个UI对象(与Android类似): 例如:

      axaml中定义一个名称为 TB_Title的TextBlock文本标签:

      <TextBlock x:Name="TB_Title" HorizontalAlignment="Center" Foreground="White" FontSize="14" VerticalAlignment="Center" Margin="10,0,0,10" Text="My Avalonia Desktop App"/>
      
      • 1

      在cs中定义并更改标签内容:

      TextBlock TB_Title = this.Get<TextBlock>("TB_Title");
      TB_Title.Text = "嘻嘻";
      
      • 1
      • 2

      这里就用到了this.Get(string Name)方法

      T:表示对象类型 Name:为x:Name中定义的名称

      注意:在后台查找UI对象 若不是局部变量 应需考虑 时序问题 否则在使用时对象可能是null

      建议将所有的控件优先查找出来(如果你控制得比较好可以不用…)

      事件
      详细的介绍可以看官方文档:http://avaloniaui.net/docs/input/events

      有几点比较坑的地方:

      1.直接在axaml中定义事件有时候不会成功,可以在后台中定义,例如:

      <Button x:Name="btn" Click="Btn_Click">Click Me</Button>
      void Btn_Click(object sender, RoutedEventArgs args)
      {
         //...
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      若不成功可以:

      this.Get<Button>("btn").Click+=Btn_Click;
      
      • 1

      2.样式设置

      标签中增加一下内容,可以为界面元素设置样式

      <Window.Styles>
          <Style Selector="TextBox.tb1">
            <Setter Property="Margin" Value="0,-40,0,0"/>
            <Setter Property="Height" Value="26"/>
            <Setter Property="Width" Value="250"/>
            <Setter Property="Watermark" Value="账号"/>
            <Setter Property="BorderBrush" Value="#80c0ff"/>
          </Style>
      </Window.Styles>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      2.1.窗口标题栏消除掉

          ExtendClientAreaToDecorationsHint="True"
          ExtendClientAreaChromeHints="NoChrome"
          ExtendClientAreaTitleBarHeightHint="-1"
      
      • 1
      • 2
      • 3

      2.2visualbrush画分割线

      DestinationRect=“10,10,10,10” SourceRect=“0,0,0,0”
      我感觉是矩形坐标,我画的分割线宽度是10

              <Border.Background>
                  <VisualBrush
                      AlignmentX="Left"
                      AlignmentY="Top"
                      DestinationRect="10,10,10,10"
                      SourceRect="0,0,0,0"
                      Stretch="None"
                      TileMode="Tile">
                      <VisualBrush.Visual>
                          <Grid Width="10" Height="10">
                              <Line
                                  Stroke="DarkGray"
                                  StrokeThickness="2"
                                  StartPoint="0,10"
                                  EndPoint="10,0" />
                              <Line
                                  Stroke="DarkGray"
                                  StrokeThickness="2"
                                  StartPoint="10,10"
                                  EndPoint="0,0" />
                          </Grid>
                      </VisualBrush.Visual>
                  </VisualBrush>
              </Border.Background>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

      2.3文本框

      <TextBox Text="{Binding Greeting, Mode=OneWay}"
      						 IsReadOnly="True"//只读
      						 FontWeight="Bold"//加粗
      						 Watermark="请填写..."//没有输入时显示的内容
      						 TextWrapping=“Wrap”//换行:不换行使用省略号表示:TextWrapping=“NoWrap”                      
      						  TextTrimming=“CharacterEllipsis”
      						  默认TextWrapping=“NoWrap”
                                 PointerPressed=“”// 点击事件
      						  />
      						  
      Avalonia.Controls.TextBox没有TextChanged事件,所以使用属性改变事件监视Text属性
      txtBox.PropertyChanged += TxtBox_PropertyChanged;
      private void TxtBox_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
      {
      var property = e.Property.Name;
      if (property == “Text”)
      {
      //处理
      }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

      2.4容器

      设置填充时,只有在dockPanel中才有效果
      <StackPanel Margin="10" Orientation="Horizontal" Margin="32,0,32,4"//左,上,右,下 >//布局 </StackPanel>

      2.5边框

      <Border BorderBrush="Red" BorderThickness="1">//设置边框宽度和颜色,必须要一起有,不然没效果
            
          </Border>
      
      • 1
      • 2
      • 3

      2.6滚动条

      <ScrollViewer 
      HorizontalScrollBarVisibility="Auto"//水平滚动条可见度:不设置时只显示上下的,设置成Auto时会左右,上下滚动
      
      >
      
      </ScrollViewer>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      2.7Grid设置行列

      
      Grid.SetRow(panel1 , 3);
      Grid.SetColumn(panel1, 1);
      Grid.SetColumnSpan(panel1, 2);
      //或者
      panel1.SetValue(Grid.RowProperty, 3);
      panel1.SetValue(Grid.ColumnProperty, 1);
      panel1.SetValue(Grid.ColumnSpanProperty, 2);
      
      //设置所属父控件
      grid1.Children.Add(panel1 );
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      2.8Calendar 日历

      用法:

      IsTodayHighlighted="true"//当前选中日期是否高亮,默认高亮
       SelectionMode="SingleDate">
              </Calendar>
      
      • 1
      • 2
      • 3

      2.9image控件

      添加到项目中的图片设置为avalonia的资源文件后

      											   VerticalAlignment="Center"
      											   HorizontalAlignment="Center"
      											   Width="16" Height="16"
      											   Source="avares://CreateCtrlByType/Images/down.png">
      
      • 1
      • 2
      • 3
      • 4

      Image.Source=图片路径

      Image image = new Image
      {
        Source = new DrawingImage
        {
         Drawing = new ImageDrawing
         {
           ImageSource = new Bitmap(BitmapPath),
           Rect = new Rect(0, 0, 200, 200),
         }
       }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      Name=“{Binding DataItemId, StringFormat=‘itemsContainer{0}’}”

      2.10下拉框

      SelectionChanged:当控件的选择发生更改时发生。继承自SelectingltemsControl

                          {
                              [!ComboBox.SelectedItemProperty] = new Binding("DataItemValue", BindingMode.TwoWay),//双向绑定数据
                              [!ToolTip.TipProperty] = new Binding("DataItemValue")//悬浮显示内容
                          };
      
      • 1
      • 2
      • 3
      • 4

      2.11ListBox

      样式

                <Style Selector="ListBox">
                  <Setter Property="Background" Value="White"/>
                  <Setter Property="Foreground" Value="Black" />
                  <Setter Property="Margin" Value="0" />
                  <Setter Property="Padding" Value="0" />
                </Style>
                <Style Selector="ListBoxItem">
                  <Setter Property="Background" Value="Transparent"/>
                  <Setter Property="Margin" Value="0"/>
                  <Setter Property="Padding" Value="0"/>
                  <Setter Property="Height" Value="22"/>
                </Style>
                <Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
                  <!--鼠标移入-->
                  <Setter Property="Background" Value="#007CCC"/>
                </Style>
                <Style Selector="ListBoxItem:pointerover > DockPanel > TextBlock">
                  <!--鼠标移入-->
                  <Setter Property="Foreground" Value="White" />
                </Style>
                <Style Selector="ListBoxItem:selected /template/ ContentPresenter">
                  <!--选中时的背景-->
                  <Setter Property="Background" Value="#007CCC"/>
                </Style>
                <Style Selector="ListBoxItem:selected > DockPanel > TextBlock">
                  <Setter Property="Foreground" Value="White" />
                </Style>
                <Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter">
                  <!--选中时,鼠标移入-->
                  <Setter Property="Background" Value="#007CCC"/>
                </Style>
              </ListBox.Styles>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32

      2.12button

                    <Style Selector="Button:pointerover ContentPresenter">//鼠标悬浮的背景
                      <Setter Property="Background" Value="Red"/>
                    </Style>
                  <Style Selector="Button:pointerover AccessText">//鼠标悬浮字体颜色
                      <Setter Property="Foreground" Value="Yellow"/>
                    </Style>
                  </Button.Styles>
                  
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      3.注意事项

      3.1双向绑定list集合

      如以下代码,绑定了 ListUserInfoList
      在删除某一项时,界面可能不会变化,且可能出现异常

                             SelectedIndex="0"
                            Cursor="Hand"
                     Name="listBox"
                                >
              <ListBox.ItemTemplate>
                <DataTemplate>
                  <DockPanel>
                    <TextBlock Margin="8,0,0,0" Text="{Binding UserName}"/>
                    <TextBlock Margin="0,0,8,0" HorizontalAlignment="Right" Tag="{Binding UserName}" Foreground="White" Text="X" PointerPressed="delUserName_PointerPressed" />
                  </DockPanel>
                </DataTemplate>
              </ListBox.ItemTemplate>
            </ListBox>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      解决方案,使用ObservableCollection代替List

      3.2神奇的属性Bounds

      例如元素的真实高宽:Bounds.Height/Width
      元素相对于父控件的位置:Bounds.Position.X/Y

      MaxWidth=“800” MaxHeight=“450” MinWidth=“800” MinHeight=“450”

      3.3 tab标题样式

      声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/90631
      推荐阅读
      相关标签
        

      闽ICP备14008679号