zoukankan      html  css  js  c++  java
  • 【Flutter学习】基本组件之基本网格Gradview组件

    一,概述  

    数据量很大的时用矩阵方式排列比较清晰,此时用网格列表组件,即为GridView组件,可实现多行多列的应用场景。 使用GridView创建网格列表有多种方式:

    • GridView.count 通过单行展示个数创建GridView。
    • GridView.extend通过最大宽度创建GridView。

    二,构造函数  

    • GridView

      • 使用场景:使用自定义SliverGridDelegate创建可滚动的2D小部件数组
      • 构造函数
        GridView({Key key, 
        Axis scrollDirection: Axis.vertical,
        bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, @required SliverGridDelegate gridDelegate, bool addAutomaticKeepAlives: true, bool addRepaintBoundaries: true, bool addSemanticIndexes: true, double cacheExtent, List<Widget> children: const [], int semanticChildCount
        })
    • GridView.count

      • 使用场景:创建一个可滚动的2D小部件数组,在横轴上具有固定数量的网格块
      • 构造函数
        GridView.count({Key key, Axis scrollDirection: Axis.vertical, 
          bool reverse: false, ScrollController controller, 
          bool primary, ScrollPhysics physics, bool shrinkWrap: false, 
          EdgeInsetsGeometry padding, @required int crossAxisCount,
          double mainAxisSpacing: 0.0, double crossAxisSpacing: 0.0, 
           double childAspectRatio: 1.0, bool addAutomaticKeepAlives: true,
           bool addRepaintBoundaries: true, 
           bool addSemanticIndexes: true,
           double cacheExtent, List<Widget> children: const [], 
           int semanticChildCount 
        })
      • 分析和使用
        Widget gridViewDefaultCount(List<BaseBean> list) {
            return GridView.count(
        //      padding: EdgeInsets.all(5.0),
              //一行多少个
              crossAxisCount: 5,
              //滚动方向
              scrollDirection: Axis.vertical,
              // 左右间隔
              crossAxisSpacing: 10.0,
              // 上下间隔
              mainAxisSpacing: 10.0,
              //宽高比
              childAspectRatio: 2 / 5,
        
              children: initListWidget(list),
            );
          }
        
        List<Widget> initListWidget(List<BaseBean> list) {
            List<Widget> lists = [];
            for (var item in list) {
              lists.add(new Container(
                height: 50.0,
                 50.0,
                color: Colors.yellow,
                child: new Center(
                    child: new Text(
                  item.age.toString(),
                )),
              ));
            }
            return lists;
          }
    • GridView.extent

      • 使用场景:使用每个都具有最大横轴范围的 网格块 创建可滚动的2D小部件数组。
      • 构造函数
        GridView.extent({Key key, Axis scrollDirection: Axis.vertical,
           bool reverse: false, ScrollController controller,
           bool primary, ScrollPhysics physics, 
           bool shrinkWrap: false, EdgeInsetsGeometry padding,
           @required double maxCrossAxisExtent,
           double mainAxisSpacing: 0.0, double crossAxisSpacing: 0.0,
           double childAspectRatio: 1.0, 
           bool addAutomaticKeepAlives: true,
           bool addRepaintBoundaries: true, 
           bool addSemanticIndexes: true,
           List<Widget> children: const [], 
           int semanticChildCount 
        })
      • 分析和使用  
        ///GridView.extent 允许您指定项的最大像素宽度
          Widget gridViewDefaultExtent(List<BaseBean> list) {
            return GridView.extent(
              ///设置item的最大像素宽度  比如 130
              maxCrossAxisExtent: 130.0,
              ///其他属性和count一样
              children: initListWidget(list),
            );
          }
    • GridView.builder

      • 使用场景:创建按需创建的可滚动的2D小部件数组
      • 构造函数
        GridView.builder({Key key, Axis scrollDirection: Axis.vertical,
           bool reverse: false, ScrollController controller, 
            bool primary, ScrollPhysics physics,
            bool shrinkWrap: false, EdgeInsetsGeometry padding, 
            @required SliverGridDelegate gridDelegate, 
            @required IndexedWidgetBuilder itemBuilder,
            int itemCount, bool addAutomaticKeepAlives: true,
            bool addRepaintBoundaries: true, 
            bool addSemanticIndexes: true, 
            double cacheExtent, int semanticChildCount 
        })
      • 分析和使用
        ///GridView.builder  可以定义gridDelegate的模式
          Widget gridViewDefaultBuilder(List<BaseBean> list) {
            return GridView.builder(
                gridDelegate: MyGridViewDefaultCustom(
                  crossAxisCount: 2,
                  mainAxisSpacing: 10.0,
                  crossAxisSpacing: 10.0,
                  childAspectRatio: 1.0,
                ),
                itemBuilder: (context, i) => new Container(
                      child: new Row(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: <Widget>[
                          new Text(
                            "${list[i].name}",
                            style: new TextStyle(fontSize: 18.0, color: Colors.red),
                          ),
                          new Text(
                            "${list[i].age}",
                            style: new TextStyle(fontSize: 18.0, color: Colors.green),
                          ),
                          new Text(
                            "${list[i].content}",
                            style: new TextStyle(fontSize: 18.0, color: Colors.blue),
                          ),
                        ],
                      ),
                 ));
          }
        ///自定义SliverGridDelegate
        class MyGridViewDefaultCustom extends SliverGridDelegate {
          ///横轴上的子节点数。  一行多少个child
          final int crossAxisCount;
        
          ///沿主轴的每个子节点之间的逻辑像素数。 默认垂直方向的子child间距  这里的是主轴方向 当你改变 scrollDirection: Axis.vertical,就是改变了主轴发方向
          final double mainAxisSpacing;
        
          ///沿横轴的每个子节点之间的逻辑像素数。默认水平方向的子child间距
          final double crossAxisSpacing;
        
          ///每个孩子的横轴与主轴范围的比率。 child的宽高比  常用来处理child的适配
          final double childAspectRatio;
        
          bool _debugAssertIsValid() {
            assert(mainAxisSpacing >= 0.0);
            assert(crossAxisSpacing >= 0.0);
            assert(childAspectRatio > 0.0);
            return true;
          }
        
          const MyGridViewDefaultCustom({
            @required this.crossAxisCount,
            this.mainAxisSpacing = 0.0,
            this.crossAxisSpacing = 0.0,
            this.childAspectRatio = 1.0,
          })  : assert(crossAxisCount != null && crossAxisCount > 0),
                assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
                assert(crossAxisSpacing != null && crossAxisSpacing >= 0),
                assert(childAspectRatio != null && childAspectRatio > 0);
        
          ///  返回值有关网格中图块大小和位置的信息。这里就是处理怎么摆放 我们可以自己定义
          ///   SliverGridLayout是抽象类  SliverGridRegularTileLayout继承于SliverGridLayout是抽象类
          @override
          SliverGridLayout getLayout(SliverConstraints constraints) {
            // TODO: implement getLayout
            assert(_debugAssertIsValid());
        
            ///对参数的修饰 自定义
            final double usableCrossAxisExtent =
                constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
            final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
            final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
            return MySliverGridLayout(
              crossAxisCount: crossAxisCount,
              mainAxisStride: childMainAxisExtent + mainAxisSpacing,
              crossAxisStride: childCrossAxisExtent + crossAxisSpacing,
              childMainAxisExtent: childMainAxisExtent,
              childCrossAxisExtent: childCrossAxisExtent,
              reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
            );
          }
        
          /// 和ListView的 shouldRebuild 作用一样   之前的实例和新进来的实例是相同的就返回true
          @override
          bool shouldRelayout(SliverGridDelegate oldDelegate) {
            // TODO: implement shouldRelayout
            return true;
          }
        }
    • GridView.custom 

      • 使用场景:使用自定义SliverGridDelegate和自定义SliverChildDelegate创建可滚动的2D小部件数组
      • 构造函数
        GridView.custom({Key key, Axis scrollDirection: Axis.vertical,
          bool reverse: false, ScrollController controller, 
          bool primary, ScrollPhysics physics, 
          bool shrinkWrap: false, 
          EdgeInsetsGeometry padding,
          @required SliverGridDelegate gridDelegate,
          @required SliverChildDelegate childrenDelegate, 
          double cacheExtent,
          int semanticChildCount })
      • 分析和使用  
         ///GridView.custom 就是自己定制规则
          /// 这里说一下 GridView.count gridDelegate 其实就是内部实现 SliverGridDelegateWithFixedCrossAxisCount
          /// GridView.extent gridDelegate 其实就是内部实现 SliverGridDelegateWithMaxCrossAxisExtent
          Widget gridViewDefaultCustom(List<BaseBean> list) {
            return GridView.custom(
              gridDelegate: MyGridViewDefaultCustom(
                crossAxisCount: 2,
                mainAxisSpacing: 10.0,
                crossAxisSpacing: 10.0,
                childAspectRatio: 1.0,
              ),
              childrenDelegate: MyGridChildrenDelegate(
                (BuildContext context, int i) {
                  return new Container(
                      child: new Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: <Widget>[
                      new Text(
                        "${list[i].name}",
                        style: new TextStyle(fontSize: 18.0, color: Colors.red),
                      ),
                      new Text(
                        "${list[i].age}",
                        style: new TextStyle(fontSize: 18.0, color: Colors.green),
                      ),
                      new Text(
                        "${list[i].content}",
                        style: new TextStyle(fontSize: 18.0, color: Colors.blue),
                      ),
                    ],
                  ));
                },
                childCount: list.length,
              ),
            );
          }
        /**
         * 继承SliverChildBuilderDelegate  可以对列表的监听
         */
        class MyGridChildrenDelegate extends SliverChildBuilderDelegate {
          MyGridChildrenDelegate(
            Widget Function(BuildContext, int) builder, {
            int childCount,
            bool addAutomaticKeepAlive = true,
            bool addRepaintBoundaries = true,
          }) : super(builder,
                    childCount: childCount,
                    addAutomaticKeepAlives: addAutomaticKeepAlive,
                    addRepaintBoundaries: addRepaintBoundaries);
        
          ///监听 在可见的列表中 显示的第一个位置和最后一个位置
          @override
          void didFinishLayout(int firstIndex, int lastIndex) {
            print('firstIndex: $firstIndex, lastIndex: $lastIndex');
          }
        
          ///可不重写 重写不能为null  默认是true  添加进来的实例与之前的实例是否相同 相同返回true 反之false
          ///listView 暂时没有看到应用场景 源码中使用在 SliverFillViewport 中
          @override
          bool shouldRebuild(SliverChildBuilderDelegate oldDelegate) {
            // TODO: implement shouldRebuild
            print("oldDelegate$oldDelegate");
            return super.shouldRebuild(oldDelegate);
          }
        }

    三,参数详解

    • gridDelegate:

       构造 GridView 的委托者,GridView.count 就相当于指定 gridDelegate 为 SliverGridDelegateWithFixedCrossAxisCount,GridView.extent 就相当于指定 gridDelegate 为 SliverGridDelegateWithMaxCrossAxisExtent,它们相当于对普通构造方法的一种封装。它的值是一个 SliverGridDelegate 对象,参考 2.1 SliverGridDelegate。

    • cacheExtent:同 ListView,预加载的区域。
    • controller:同 ListView,滑动监听,值为一个 ScrollController 对象,这个属性应该可以用来做下拉刷新和上垃加载,后面详细研究。
    • padding:同 ListView,整个 GridView 的内间距。
    • physics:同 ListView,设置 GridView 如何响应用户的滑动行为,值为一个 ScrollPhysics 对象,它的实现类常用的有:
      • AlwaysScrollableScrollPhysics:总是可以滑动。
      • NeverScrollableScrollPhysics:禁止滚动。
      • BouncingScrollPhysics:内容超过一屏,上拉有回弹效果。
      • ClampingScrollPhysics:包裹内容,不会有回弹,感觉跟 AlwaysScrollableScrollPhysics 差不多。
    • reverse:Item 的顺序是否反转,若为 true 则反转,这个翻转只是行翻转,即第一行变成最后一行,但是每一行中的子组件还是从左往右摆放的,用到该属性的开发情景较少。
    • scrollDirection:GirdView 的方向,为 Axis.vertical 表示纵向,为 Axis.horizontal 表示横向,横向的话 CrossAxis 和 MainAxis 表示的轴也会调换,为 Axis.Horizontal 的情况也较少。
    • semanticChildCount:不太清楚。
    • shrinkWrap:不太清楚。
    • children:子组件,不用多说。

    四,关于SliverGridDelegate

    构造 GridView 的委托者,它有两个实现类:

    • SliverGridDelegateWithFixedCrossAxisCount

        该委托者通常用于每一行的子组件个数固定的情况,它可以指定如下几个属性:

      • crossAxisCount:必传参数,Cross 轴(在 GridView 中通常是横轴,即每一行)子组件个数。

      • childAspectRatio:子组件宽高比,如 2 表示宽:高=2:1,如 0.5 表示宽:高=0.5:1=1:2,简单来说就是值大于 1 就会宽大于高,小于 1 就会宽小于高。

      • crossAxisSpacing:Cross 轴子组件的间隔,一行中第一个子组件左边不会添加间隔,最后一个子组件右边不会添加间隔,这一点很棒。

      • mainAxisSpacing:Main 轴(在 GridView 中通常是纵轴,即每一列)子组件间隔,也就是每一行之间的间隔,同样第一行的上边和最后一行的下边不会添加间隔。

    • SliverGridDelegateWithMaxCrossAxisExtent
      • maxCrossAxisExtent:必传参数,
      • Cross 轴(在 GridView 中通常是横轴,即每一行)子组件最大宽度,会根据该值来决定一行摆放几个子组件。

        其余属性 childAspectRatio、crossAxisSpacing、mainAxisSpacing 同 SliverGridDelegateWithFixedCrossAxisCount。

    五,示例demo  

    import 'package:flutter/material.dart';
    import 'package:flutter/src/rendering/sliver.dart';
    import 'package:flutter/src/rendering/sliver_grid.dart';
    import 'package:flutter_vscode/listview_demo.dart';
    
    class GridViewDemo extends StatefulWidget {
      @override
      _GridViewDemoState createState() => new _GridViewDemoState();
    }
    
    class _GridViewDemoState extends State<GridViewDemo> {
      List<BaseBean> gridList;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        gridList = new List<BaseBean>.generate(
            32, (i) => new BaseBean("name$i", i, "content=$i"));
      }
    
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: "",
          home: new Scaffold(
            appBar: new AppBar(
              centerTitle: true,
              title: new Text("GridView"),
            ),
            body: gridViewDefaultCount(gridList),
          ),
        );
      }
    
      List<Widget> initListWidget(List<BaseBean> list) {
        List<Widget> lists = [];
        for (var item in list) {
          lists.add(new Container(
            height: 50.0,
             50.0,
            color: Colors.yellow,
            child: new Center(
                child: new Text(
              item.age.toString(),
            )),
          ));
        }
        return lists;
      }
    
      Widget gridViewDefaultCount(List<BaseBean> list) {
        return GridView.count(
    //      padding: EdgeInsets.all(5.0),
          crossAxisCount: 5,
          //一行多少个
          scrollDirection: Axis.vertical,
          //滚动方向
          crossAxisSpacing: 10.0,
          // 左右间隔
          mainAxisSpacing: 10.0,
          // 上下间隔
          childAspectRatio: 2 / 5,
          //宽高比
          children: initListWidget(list),
        );
      }
    
      ///GridView.extent 允许您指定项的最大像素宽度
      Widget gridViewDefaultExtent(List<BaseBean> list) {
        return GridView.extent(
          ///设置item的最大像素宽度  比如 130
          maxCrossAxisExtent: 130.0,
    
          ///其他属性和count一样
          children: initListWidget(list),
        );
      }
    
      ///GridView.custom 就是自己定制规则
      /// 这里说一下 GridView.count gridDelegate 其实就是内部实现 SliverGridDelegateWithFixedCrossAxisCount
      /// GridView.extent gridDelegate 其实就是内部实现 SliverGridDelegateWithMaxCrossAxisExtent
      Widget gridViewDefaultCustom(List<BaseBean> list) {
        return GridView.custom(
          gridDelegate: MyGridViewDefaultCustom(
            crossAxisCount: 2,
            mainAxisSpacing: 10.0,
            crossAxisSpacing: 10.0,
            childAspectRatio: 1.0,
          ),
          childrenDelegate: MyGridChildrenDelegate(
            (BuildContext context, int i) {
              return new Container(
                  child: new Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  new Text(
                    "${list[i].name}",
                    style: new TextStyle(fontSize: 18.0, color: Colors.red),
                  ),
                  new Text(
                    "${list[i].age}",
                    style: new TextStyle(fontSize: 18.0, color: Colors.green),
                  ),
                  new Text(
                    "${list[i].content}",
                    style: new TextStyle(fontSize: 18.0, color: Colors.blue),
                  ),
                ],
              ));
            },
            childCount: list.length,
          ),
        );
      }
    
      ///GridView.builder  可以定义gridDelegate的模式
      Widget gridViewDefaultBuilder(List<BaseBean> list) {
        return GridView.builder(
            gridDelegate: MyGridViewDefaultCustom(
              crossAxisCount: 2,
              mainAxisSpacing: 10.0,
              crossAxisSpacing: 10.0,
              childAspectRatio: 1.0,
            ),
            itemBuilder: (context, i) => new Container(
                  child: new Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: <Widget>[
                      new Text(
                        "${list[i].name}",
                        style: new TextStyle(fontSize: 18.0, color: Colors.red),
                      ),
                      new Text(
                        "${list[i].age}",
                        style: new TextStyle(fontSize: 18.0, color: Colors.green),
                      ),
                      new Text(
                        "${list[i].content}",
                        style: new TextStyle(fontSize: 18.0, color: Colors.blue),
                      ),
                    ],
                  ),
                ));
      }
    }
    
    ///自定义SliverGridDelegate
    class MyGridViewDefaultCustom extends SliverGridDelegate {
      ///横轴上的子节点数。  一行多少个child
      final int crossAxisCount;
    
      ///沿主轴的每个子节点之间的逻辑像素数。 默认垂直方向的子child间距  这里的是主轴方向 当你改变 scrollDirection: Axis.vertical,就是改变了主轴发方向
      final double mainAxisSpacing;
    
      ///沿横轴的每个子节点之间的逻辑像素数。默认水平方向的子child间距
      final double crossAxisSpacing;
    
      ///每个孩子的横轴与主轴范围的比率。 child的宽高比  常用来处理child的适配
      final double childAspectRatio;
    
      bool _debugAssertIsValid() {
        assert(mainAxisSpacing >= 0.0);
        assert(crossAxisSpacing >= 0.0);
        assert(childAspectRatio > 0.0);
        return true;
      }
    
      const MyGridViewDefaultCustom({
        @required this.crossAxisCount,
        this.mainAxisSpacing = 0.0,
        this.crossAxisSpacing = 0.0,
        this.childAspectRatio = 1.0,
      })  : assert(crossAxisCount != null && crossAxisCount > 0),
            assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
            assert(crossAxisSpacing != null && crossAxisSpacing >= 0),
            assert(childAspectRatio != null && childAspectRatio > 0);
    
      ///  返回值有关网格中图块大小和位置的信息。这里就是处理怎么摆放 我们可以自己定义
      ///   SliverGridLayout是抽象类  SliverGridRegularTileLayout继承于SliverGridLayout是抽象类
      @override
      SliverGridLayout getLayout(SliverConstraints constraints) {
        // TODO: implement getLayout
        assert(_debugAssertIsValid());
    
        ///对参数的修饰 自定义
        final double usableCrossAxisExtent =
            constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
        final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
        final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
        return MySliverGridLayout(
          crossAxisCount: crossAxisCount,
          mainAxisStride: childMainAxisExtent + mainAxisSpacing,
          crossAxisStride: childCrossAxisExtent + crossAxisSpacing,
          childMainAxisExtent: childMainAxisExtent,
          childCrossAxisExtent: childCrossAxisExtent,
          reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
        );
      }
    
      /// 和ListView的 shouldRebuild 作用一样   之前的实例和新进来的实例是相同的就返回true
      @override
      bool shouldRelayout(SliverGridDelegate oldDelegate) {
        // TODO: implement shouldRelayout
        return true;
      }
    }
    
    ///自定义SliverGridLayout
    class MySliverGridLayout extends SliverGridLayout {
      final int crossAxisCount;
    
      final double mainAxisStride;
    
      final double crossAxisStride;
    
      final double childMainAxisExtent;
    
      final double childCrossAxisExtent;
    
      final bool reverseCrossAxis;
    
      const MySliverGridLayout({
        @required this.crossAxisCount,
        @required this.mainAxisStride,
        @required this.crossAxisStride,
        @required this.childMainAxisExtent,
        @required this.childCrossAxisExtent,
        @required this.reverseCrossAxis,
      })  : assert(crossAxisCount != null && crossAxisCount > 0),
            assert(mainAxisStride != null && mainAxisStride >= 0),
            assert(crossAxisStride != null && crossAxisStride >= 0),
            assert(childMainAxisExtent != null && childMainAxisExtent >= 0),
            assert(childCrossAxisExtent != null && childCrossAxisExtent >= 0),
            assert(reverseCrossAxis != null);
    
      ///如果有,则完全显示所有图块所需的滚动范围
      ///“childCount”儿童总数。
      ///
      ///子计数永远不会为空。
      @override
      double computeMaxScrollOffset(int childCount) {
        // TODO: implement computeMaxScrollOffset
        return null;
      }
    
      ///具有给定索引的子项的大小和位置。
      @override
      SliverGridGeometry getGeometryForChildIndex(int index) {
        // TODO: implement getGeometryForChildIndex
        return null;
      }
    
      ///在此滚动偏移处(或之前)可见的最大子索引。
      @override
      int getMaxChildIndexForScrollOffset(double scrollOffset) {
        // TODO: implement getMaxChildIndexForScrollOffset
        return null;
      }
    
      ///在此滚动偏移处(或之后)可见的最小子索引。
      @override
      int getMinChildIndexForScrollOffset(double scrollOffset) {
        // TODO: implement getMinChildIndexForScrollOffset
        return null;
      }
    }
    
    // ignore: slash_for_doc_comments
    /**
     * 继承SliverChildBuilderDelegate  可以对列表的监听
     */
    class MyGridChildrenDelegate extends SliverChildBuilderDelegate {
      MyGridChildrenDelegate(
        Widget Function(BuildContext, int) builder, {
        int childCount,
        bool addAutomaticKeepAlive = true,
        bool addRepaintBoundaries = true,
      }) : super(builder,
                childCount: childCount,
                addAutomaticKeepAlives: addAutomaticKeepAlive,
                addRepaintBoundaries: addRepaintBoundaries);
    
      ///监听 在可见的列表中 显示的第一个位置和最后一个位置
      @override
      void didFinishLayout(int firstIndex, int lastIndex) {
        print('firstIndex: $firstIndex, lastIndex: $lastIndex');
      }
    
      ///可不重写 重写不能为null  默认是true  添加进来的实例与之前的实例是否相同 相同返回true 反之false
      ///listView 暂时没有看到应用场景 源码中使用在 SliverFillViewport 中
      @override
      bool shouldRebuild(SliverChildBuilderDelegate oldDelegate) {
        // TODO: implement shouldRebuild
        print("oldDelegate$oldDelegate");
        return super.shouldRebuild(oldDelegate);
      }
    }

    五,官方文档

      官方文档

  • 相关阅读:
    Redis主从复制
    maven生命周期和插件
    maven私服搭建
    为公司运营人员整理EXCEL数据小程序
    docker之本地镜像上传阿里云
    docker阿里云镜像之Tomcat
    docker环境安装
    网页及第三方连接禅道数据库
    yagmail 发送HTML格式邮件图片不显示问题解决方案
    pyechars模块安装踩得坑
  • 原文地址:https://www.cnblogs.com/lxlx1798/p/11063857.html
Copyright © 2011-2022 走看看