精华内容
下载资源
问答
  • 这一篇将说明如何使用C#和WPF浏览器控件来完成淘宝自动搜索,翻页自动点击的操作

    这一篇将说明如何使用C#和WPF浏览器控件来完成淘宝自动搜索,翻页,自动点击的操作


    首先说明下,我在实现过程中使用的控件,一个是浏览器WebBrowser,一个是按钮Button,还有单选按钮RadioButton,以及多选CheckBox,下文代码中的reload函数是重新加载页面的函数。

     

    目录

    1.自动填写搜索栏

    2.自动点击搜索按钮的实现

    3.翻页的实现

    4.匹配名称和ID并点击商品


    这个软件设计的手动操作部分如下

     

    1.自动填写搜索栏

    先是实例化HTMLDocument,HTMLDocument类会获取目前浏览器控件加载好的网页文档,提供之后IHTMLElementCollection进行元素查找和操作。

     

    尽管这部分函数我设计为按钮触发,但为了防止没有加载好的时候就获取,需要先等待网页加载完毕

    if (webbrowser.IsLoaded != true)
                {
                    InfoAdd("搜索前请先等待页面完成加载");
                    return;
                }

    其中webbrowser是我们WPF的浏览器控件名称,如果加载完成,那么可以实例化HTMLDocument

    HTMLDocument doc = webbrowser.Document as HTMLDocument;

    接下来我们可以看看淘宝的主页,顶部的搜索栏的HTML文本结构,这里可以使用浏览器调试模式查看,点击F12即可

    可以看到,标签是input,因此,接下来我们要获取网页中所有的input标签,并且遍历搜索,找到搜索文本的输入框,并且将我需要的内容输入进去

    //实例化元素搜集用途的类,然后弄一个页面上所有input标签的集合
    IHTMLElementCollection els = doc.getElementsByTagName("input");
    
    //遍历查找我们需要的那个搜索输入框
                foreach (IHTMLElement em in els)
                {
                    try
                    {
                        i++;
                        if (em.id != null)
                        {
                            if (em.id.Contains("q"))
                            {
                                //tbSearchWord.Text是一个控件,填写我需要填入淘宝搜索栏的文本
                                em.setAttribute("value", tbSearchWord.Text);
    
                            }
                        }
                    }
                    catch
                    {
    
                    }
    
                }

    获取淘宝页面中的搜索栏,并且自动填写文本就实现了


     

    2.自动点击搜索按钮的实现

    和上一步一样的步骤,打开网页调试窗口,可以看到搜索按钮是button标签,所以我们一样获取下button元素

    IHTMLElementCollection els2 = doc.getElementsByTagName("button");

     然后一样是遍历获取到的按钮,因为按钮中有文本所以我们通过比对innerText即可获取到

    foreach (IHTMLElement em in els2)
                {
                    try
                    {
                        if (em.innerText != null)
                        {
                            if (em.innerText.Contains("搜索"))
                            {
                                em.click();
                            }
                        }
    
                    }
                    catch
                    {
    
                    }
                    finally
                    {
    
                    }
    
                }

     

    搜索+点击搜索按钮的函数(无注释版本)

    void function2() //auto input 
            {
                int i = 0;
                if (webbrowser.IsLoaded != true)
                {
                    InfoAdd("搜索前请先等待页面完成加载");
                    return;
                }
                HTMLDocument doc = webbrowser.Document as HTMLDocument;
                IHTMLElementCollection els = doc.getElementsByTagName("input");
                foreach (IHTMLElement em in els)
                {
                    try
                    {
                        i++;
                        if (em.id != null)
                        {
                            if (em.id.Contains("q"))
                            {
                                em.setAttribute("value", tbSearchWord.Text);
                            }
                        }
                    }
                    catch
                    {
    
                    }
    
                }
                Thread.Sleep(200);
                IHTMLElementCollection els2 = doc.getElementsByTagName("button");
                foreach (IHTMLElement em in els2)
                {
                    try
                    {
                        if (em.innerText != null)
                        {
                            if (em.innerText.Contains("搜索"))
                            {
                                em.click();
                            }
                        }
    
                    }
                    catch
                    {
    
                    }
                    finally
                    {
    
                    }
    
                }
                //   System.Windows.MessageBox.Show("Current:"+i);
            }

     

    3.翻页的实现

    翻页是对于整个网站结构的推测,例如,我先随便搜索什么东西,跳转页面结果后,网址是

    https://s.taobao.com/search?q=ai&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306

    这里面并没有包含页面的信息,滑动到网页末尾,点击第二页

    https://s.taobao.com/search?q=ai&imgfile=&commend=all&ssid=s5-e&search_type=item&sourceId=tb.index&spm=a21bo.2017.201856-taobao-item.1&ie=utf8&initiative_id=tbindexz_20170306&bcoffset=3&ntoffset=3&p4ppushleft=1%2C48&s=44

    可以看到,末尾有一个s=44

     

    通过修改,可以了解,这部分是商品显示的序号,一页显示43个商品,那么s=44就是说,从第44个开始显示

     

    可以试试,s=0 搜索页面末尾的结果

     

    当s=41时

     

    这里让我有点奇怪的是,一页显示是43个,那么最后一个应该是42(因为是从0开始),但这边却是41

     

    不过,依据以上的方法也就基本可以搞懂搜索结果页面翻页的原理了

    先获取网页的连接,判断现在的页面是翻页后的页面(有s=)还是没有翻页的页面如果说当前页面不是翻页后的页面,那么就在末尾添加标识,请求下一页的44个商品

    tbAddress.Text = webbrowser.Source.AbsoluteUri;
                string currentUri = tbAddress.Text;
    if (currentUri.Contains("ie=utf8") && !currentUri.Contains("&s="))
                {
                    InfoAdd("当前是搜索结果页面首页");
                    InfoAdd("尝试跳转到第2页");
                    reload(currentUri + "&s=44");
                    return 2;
                }

     

    如果是翻页的页面,就获取s=的值,以判断当前第几页,算法只是操作字符串,所以就不做详述了。函数会返回当前的页面是搜索结果的第几页

    else if (currentUri.Contains("&s="))
                {
    
                    InfoAdd("尝试跳转到下一页");
                    string rebuilds = "";
                    string[] s = currentUri.Split('=');
                    s[s.Length - 1] = (Convert.ToInt32(s[s.Length - 1]) + 44).ToString();
                    for (int i = 0; i < s.Length; i++)
                    {
                        if (i != 0) rebuilds += "=";
                        rebuilds += s[i];
                    }
                    int currentpage = (Convert.ToInt32(s[s.Length - 1])) / 44 + 1;
                    InfoAdd("当前页为第" + currentpage.ToString() + "页");
                    reload(rebuilds);
                    // System.Windows.MessageBox.Show("RebuildUrl:" + rebuilds);
                    return currentpage;
    
                }

     

     

    翻页部分的整个函数

    由按钮触发,自定义的控件有tbNextPage, tbAddress, webbrowser

    private void tbNextPage_Click(object sender, RoutedEventArgs e)
            {
                GoToNextPage();
            }
            /// <summary>
            /// 寻找下一页按钮并跳转
            /// </summary>
            /// <returns>跳转后的页面是第几页</returns>
            private int GoToNextPage()
            {
                tbAddress.Text = webbrowser.Source.AbsoluteUri;
                string currentUri = tbAddress.Text;
    
                if (currentUri.Contains("ie=utf8") && !currentUri.Contains("&s="))
                {
                    InfoAdd("当前是搜索结果页面首页");
                    InfoAdd("尝试跳转到第2页");
                    reload(currentUri + "&s=44");
                    return 2;
                }
                else if (currentUri.Contains("&s="))
                {
    
                    InfoAdd("尝试跳转到下一页");
                    string rebuilds = "";
                    string[] s = currentUri.Split('=');
                    s[s.Length - 1] = (Convert.ToInt32(s[s.Length - 1]) + 44).ToString();
                    for (int i = 0; i < s.Length; i++)
                    {
                        if (i != 0) rebuilds += "=";
                        rebuilds += s[i];
                    }
                    int currentpage = (Convert.ToInt32(s[s.Length - 1])) / 44 + 1;
                    InfoAdd("当前页为第" + currentpage.ToString() + "页");
                    reload(rebuilds);
                    // System.Windows.MessageBox.Show("RebuildUrl:" + rebuilds);
                    return currentpage;
    
                }
                return 0; //当前页面不可跳转
    
            }

     

    4.匹配名称和ID并点击商品

    商品连接就是普通的连接,在淘宝的页面中是以a标签标注的,所以,依照先前的部分一样,我们需要实例化并且获取到所有的a标签元素

     HTMLDocument doc = webbrowser.Document as HTMLDocument;
     IHTMLElementCollection allurl = doc.getElementsByTagName("a");

    然后就是对获取到的a标签进行筛选,获取到目标,有一点是,天猫,推荐和普通商品这三种,连接的服务器名部分是不一样的

    foreach (IHTMLElement em in allurl)
                    {
                        string url = em.getAttribute("href");
                        if (url == null) continue;
                        string[] sarr = url.Split(new char[] { '/' });
                        if (sarr.Length < 3) continue;
                        string s = url.Split(new char[] { '/' })[2].Split(new char[] { '.' })[0];
                        string name = em.innerText;
    
                        //普通被勾选
                        if (cbItem.IsChecked == true && cbDetail.IsChecked == false && cbClick.IsChecked == false)
                        {
                            if (s.Equals("detail") || s.Equals("item"))
                            {
                                if (name != null && name.Contains("销量") == false && name.Contains("¥") == false && name.Contains(".00") == false)
                                {
                                    tbaddressinfo.Text += url + "\r\n";
    
                                    if (name.Length > 2)
                                    {
                                        if (name.Contains(targetname) || name.Contains(targetname.Substring(0, targetname.Length - 1)) )
                                        {
                                            if(url.Contains(tbTargetID.Text))
                                            {
                                                reload(url);
                                                finded = true;
                                                break;
                                            }
                                            
                                        }
                                    }
                                }
                            }
                        }

    通过上面的算法就可以选出我们需要的物品的名称以及连接,最后一步的判断有一个匹配TargetID的部分

                                          if(url.Contains(tbTargetID.Text))
                                            {
                                                reload(url);
                                                finded = true;
                                                break;
                                            }

    其中这里的tbTargetID是指的一件商品独有的ID,因为只搜索商品名称,有可能会有重复的问题,所以建议加上该部分判断

     

     

    如果还有不了解的请在评论中留言
     

    展开全文
  • 电商小程序中,用到瀑布流的地方非常多,每次都写一个瀑布流,重复一次逻辑,作为程序员,肯定是非常愿意的。瀑布流的形式都是大同小异,不同的是瀑布流中每个模块的内容,随业务而变化。所以,我们把瀑布流框架...

    电商小程序中,用到瀑布流的地方非常多,每次都写一个瀑布流,重复一次逻辑,作为程序员,肯定是非常不愿意的。
    瀑布流的形式都是大同小异,不同的是瀑布流中每个模块的内容,随业务而变化。
    所以,我们把瀑布流框架抽象成组件,瀑布流的内容由业务确定。这样即可实现组件化和自定义的最大平衡,微信小程序组件源码。 首先,我们来看一下瀑布流组件在实际项目中的实际效果。

    1 实际效果

    瀑布流组件实际效果如下图所示,左侧为用户交互效果,右侧为图片懒加载实际效果。 瀑布流组件效果图

    2 什么是瀑布流?

    瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,waterfall-item宽度固定,高度不定,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。如下图所示: 瀑布流示意图

    3 实现功能

    该瀑布流组件实现了以下几个功能:

    • 支持图片懒加载
    • 支持上拉数据翻页
    • 支持自定义样式
    • 支持瀑布流Item间隔底层自动计算
    • 原生组件模式:即类swiperswiper-item 组件用法
    • 组件与数据完全解耦

    4 实现原理

    4.1 waterfallwaterfall-item实现原理

    第一步:在 waterfall-layout 目录下创建 waterfallwaterfall-item 组件,目录结构如下:

    .
    ├── query-node.js
    ├── waterfall-item.js
    ├── waterfall-item.json
    ├── waterfall-item.wxml
    ├── waterfall-item.wxss
    ├── waterfall.js
    ├── waterfall.json
    ├── waterfall.wxml
    └── waterfall.wxss

    第二步:分别在waterfall.jswaterfall-item.jsrelations选项中指定组件父、子级关系:

    // waterfall.js
    Component({
        // ... other code
        relations: {
        './waterfall-item': {
          type: 'child',
        },
        // ... other code
      }
    })
    // waterfall-item.js
    Component({
        // ... other code
        relations: {
        '././waterfall': {
          type: 'parent',
        },
        // ... other code
      }
    })

    指定彼此的父、子组件的关系后,即可通过 this.getRelationNodes 原生 API,就能访问彼此实例对象及其属性和方法。

    第三步:实现waterfall.wxmlwaterfall-item.wxml代码:
    waterfall.wxml代码实现非常简单,只有5行代码:

    <view class="waterfall custom-class">
      <view class="waterfall-inner">
        <slot ></slot>
      </view>
    </view>

    同样,waterfall-item.wxml代码实现也非常简单,只有5行代码:

    <view
      class="waterfall-item custom-class"
      style="{{position}}:0;top:{{(top >= 0 ? top + 'px' : 0 + 'rpx')}};"
    >
      <slot ></slot>
    </view>

    不知道slot用法的童鞋,请参考微信小程序自定义组件模板和样式文档。

    4.2 瀑布流原理

    其实,不管是微信小程序、web、还是原生APP,瀑布流的实现原理都是一样的。都可以绝对定位位置计算来实现。
    瀑布流的大体过程如下图所示:
    第一步:数据通过this.setData从逻辑层传输到视图层,进行第一渲染,由于每个waterfall-itemtop:0;position:left;,所以都重叠了在一起。
    第二步:通过节点查询API获取每个waterfall-item元素信息,并且计算出正确的topposition值。
    第三步setData每个waterfall-itemtopposition,实现重排。

    具体逻辑实现如下:

    首先,我们来实现一个节点查询API querySelector,之后会用到:

    // query-node.js
    /**
     * 获取当前页面中,选择器为 selector 的第一个node节点
     * @param {String} selector 符合微信小程序规范的选择器
     * @param {Object} context 调用环境,普通页面中为wx,自定义组件中为this;默认值为wx.
     * @return {Array} 返回一个数组,第一个元素为 node 节点
     */
    export const querySelector = function (selector, context = wx) {
      return new Promise((resolve, reject) => {
        context.createSelectorQuery()
        .select(selector)
        .boundingClientRect((res) => {
          if (res) {
            resolve(res);
          } else {
            reject(`不存在选择器为 ${selector} 的节点`);
          }
        })
        .exec();
      })
    };

    接着,看一下组件waterfallwaterfall-item在实际项目中的用法:

        <waterfall
          loading="{{loadMorePending}}"
          isAllLoaded="{{isAllLoaded}}"
        >
          <block wx:for="{{data.sections}}" wx:key="id" wx:for-item="product">
            <waterfall-item
              index="{{index}}"
              custom-class="flow-item-wrapper"
            >
              <view class="product-item">
                业务代码
              </view>
            </waterfall-item>
          </block>
        </waterfall>

    当第一个waterfall-item组件,在视图层布局完成后会执行ready生命周期钩子。
    ready 生命周期钩子中,我们需要做两件事:

    • 获取父组件waterfall的实例对象,并挂载在waterfall-item组件的 this实例对象上。因为之后我们需要在waterfall-item组件中修改waterfall上的数据。
    • 获取waterfall-item组件的高度,计算waterfall-item组件的位置信息topposition
    // waterfall-item.js
    import { querySelector } from './query-node';
    Component({
      // ... other code
      lifetimes: {
        ready() {
          const [waterfall] = this.getRelationNodes('./waterfall');
          this.parent = waterfall;
          this.setWaterfallItemPosition();
        },
      }
      methods:{
        async setWaterfallItemPosition() {
          querySelector('.waterfall-item', this)
            .then(async (node) => {
              const { top, position } = await this.parent.getWaterfallItemPostionInfo(node);
              this.setData({
                top,
                position
              })
            })
        },  
      }
      // ... other code
    })

    setWaterfallItemPosition方法中,我们调用了父组件上的方法this.parent.getWaterfallItemPostionInfo,获取当前waterfall-item组件的topposition信息。并把已经渲染好的waterfall-item组件的累计高度缓存在waterfallleftHeightsrightHeights属性上,用于计算下一个waterfall-item组件位置,主要逻辑如下:

    // waterfall.js
    const POSITION_LEFT = 'left';
    const POSITION_RIGHT = 'right';
    
    Component({
      // ... other code
      /**
       * 组件的方法列表
       */
      methods: {
        lifetimes: {
          ready() {
            this.initParams();
          }
         },
        initParams() {
          this.leftHeights = 0;
          this.rightHeights = 0;
        },
        /**
         * 设置 waterfall-item 的高度值
         * @param {Object} node waterfall-item 组件位置尺寸数据
         */
        async getWaterfallItemPostionInfo(node) {
          let top = 0;
          let position = POSITION_LEFT;
          const { height } = node;
          const { itemGap } = this;
          if (this.leftHeights <= this.rightHeights) {
            top = this.leftHeights;
            if(this.leftHeights === 0) {
              this.leftHeights += height;
            } else {
              top += itemGap;
              this.leftHeights += (height + itemGap);
            }
          } else {
            position = POSITION_RIGHT;
            top = this.rightHeights;
            if(this.rightHeights === 0) {
              this.rightHeights += height;
            } else {
              top += itemGap;
              this.rightHeights += (height + itemGap);
            }
          }
          return {
            top,
            position,
          }
        }
        // ... other code
      }
    })

    当所有的waterfall-item重排结束后,瀑布流渲染完成。

    4.3 图片懒加载原理

    微信小程序中,<image>标签本身是支持懒加载的,当lazy-load={{true}},且在即将进入一定范围(上下三屏)时才开始加载。
    也就是说,当lazy-load={{true}}<image>标签初次渲染在视口上下三屏之外时,是不会请求图片资源的,当<image>即将进入三屏之内时,才会加载。
    在4.2小节的图3中,<waterfall-item>的初始化位置设置成了top:0;position:left;,所以,都在视口中。如果将top的值成三屏之外的数值,例如,400vh或者更大,则<waterfall-item>重排之后,任然在三屏之外的图片即会自动懒加载。

    <view
      class="waterfall-item custom-class"
      style="{{position}}:0;top:{{(top >= 0 ? top + 'px' : itemCount * 100 + 'vh')}};"
    >
      <slot ></slot>
    </view>
    Component({
      // waterfall-item.js
      // ... other code
      lifetimes: {
        ready() {
          const { itemCount } = this.data;
          const [waterfall] = this.getRelationNodes('./waterfall');
          waterfall.childCount += 1;
          this.parent = waterfall;
          this.setData({
            itemCount: itemCount + waterfall.childCount,
          })
        },
      },
      // ... other code
    })

    4.4 数据翻页

    因为实现了wx:for <waterfall-item>功能,和<swiper-item>组件一样,因此翻页逻辑完全由用户自己定制,<waterfall><waterfall-item>只给你提供翻页的功能,组件就可以和瀑布流数据结构完全解耦。

    4.5 瀑布流Item间隔底层自动计算

    将列和行中,两个<waterfall-item>组件之间的距离定义为itemGap,则:

    itemGap = waterfall宽度 - (waterfall-item宽度 * 2)

    <waterfall>ready钩子中,可以获取到<waterfall>组件的宽度;同理,在<waterfall-item>ready钩子中,可以获取到<waterfall-item>组件的宽度。
    在调用getWaterfallItemPostionInfo之前,获取到itemGap的值即可。这样,在计算<waterfall-item>top值时,除了第一行的<waterfall-item>top值等于0之外,其他所有<waterfall-item>top值等于:

    // this.leftHeights += height + itemGap;
    // or 
    // this.rightHeights += height + itemGap;

    具体代码实现请查看源码

    5 总结

    通过瀑布流框架抽象,使<waterfall><waterfall-item>接近原生组件使用体验,同时使组件与数据完全解耦。通过巧妙的初始化位置top设置,使瀑布流具图片有懒加载的功能。 关注我,一周3篇干货文章与你分享。

    展开全文
  • 设计模式之16 - 迭代器模式Itertor

    千次阅读 2017-01-13 20:19:34
    1. 迭代器模式(Iterator Pattern)的定义 (1)定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又需要暴露该对象的内部表示。  ①迭代器迭代的是具体的聚合对象(如数组和链表等),它围绕的是...

    1迭代器模式(Iterator Pattern)的定义

    (1)定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示

      ①迭代器迭代的是具体的聚合对象(如数组和链表等),它围绕的是“访问”做文章。

      ②可用不同的遍历策略来遍历聚合,比如是否需要过滤

      ③为不同聚合结构提供统一的迭代接口,也就是说通过一个迭代接口可以访问不同的聚合结构,这叫做多态迭代。标准的迭代模式实现基本上都是支持多态迭代的。如用同一个迭代接口来实现对数组和链表的遍历

    (2)迭代器模式的结构和说明

     

      ①Iterator:迭代器接口。定义访问和遍历元素的接口。

      ②ConcreteInterator:具体的迭代器实现对象,会持有被迭代的具体的聚合对象的引用,并对聚合对象的遍历及跟踪遍历时的当前位置。

      ③Aggregate:聚合对象。提供创建相应迭代器对象的函数(如createIterator())。

      ④ConcreteAggregate:具体聚合对象。实现创建相应迭代器对象。

    【编程实验】以统一的方式对数组和链表进行遍历(多态迭代)

     View Code

    2. 思考迭代器模式

    (1)迭代器的本质控制访问聚合对象中的元素。迭代器能实现“无须暴露聚合对象的内部实现,就能够访问到聚合对象的各个元素的功能”,做到“透明”的访问聚合对象中的元素。注意迭代器能够即“透明”访问,又可以“控制访问”聚合对象,认识这点,对于识别和变形使用迭代器模式很有帮助。

    (2)迭代器的关键思想:把对聚合对象的遍历访问从聚合对象中分离出来,放入单独的迭代器中,这样聚合对象会变得简单,而且迭代器和聚合对象可以独立地变化和发展,会大大加强系统的灵活性。

    (3)迭代器的动机

      在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。将遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方法

    (4)内部迭代器与外部迭代器

      ①内部迭代器:指的是由迭代器自己来控制下一个元素的步骤,即当客户端利用迭代器读取当前元素后,迭代器的当前元素自动移到一下个元素,而客户端无法干预。

      ②外部迭代器:则客户端控制迭代下一个元素的步骤,即客户端必须显示的next来迭代下一个元素。从总体来说外部迭代器比内部迭代器要灵活一些。这也是常见的实现方式。

    【编程实验】简单模仿STL库容器类的遍历方式(变式迭代器)

    复制代码
    //行为模式——迭代器模式
    //场景:简单仿STL容器类的Iterator
    //说明:该模式主要用于将列表的访问和遍历分离出来并放进一个迭代器中,
    //本例是模仿STL库的容量遍历方式
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class ArrayList
    {
    private:
        int cnt;
        int arr[100];
    
    public:
        typedef int* Iterator; //内部类(型)
    
        ArrayList(){cnt = 0;}
    
        //这里可以看出来Iterator声明为一个Int*类型的指针
        Iterator begin()
        {
            return &arr[0];
        }
    
        Iterator end()
        {
            return &arr[cnt];
        }
    
        void insert(int v)
        {
            if(cnt>=100)
            {
                cout << "列表容量己满,不可再插入" << endl;
                return;
            }
            arr[cnt++] = v;
        }
    };
    int main()
    {
        ArrayList al;
    
        for(int i = 0; i< 110; i++)
        {
            al.insert(i);
        }
    
        ArrayList::Iterator iter = al.begin();
        while( iter != al.end())
        {
            cout << *iter << endl;
            ++iter;
        }
    
        return 0;
    }
    复制代码

    3. 迭代器模式高级应用

    (1)带迭代策略的迭代器

      ①由于迭代器模式把聚合对象和访问聚合的机制实现了分离,因此可以在迭代器上实现不同的迭代策略,如实现过滤功能的迭代器

      ②在实现过滤功能的迭代器中,有两种常见的过滤情况,一是对数据整条过滤,如只能查看自己部门的数据;另一种情况是对数据进行部分过滤,如某些人不能查看工资数据。

      ③带迭代策略的迭代器实现的一个基本思路就是把聚合对象的聚合数据获取到并存储在迭代器中,这样迭代器就可以按照不同的策略来迭代数据了。

    (2)双向迭代器:可以向前和向后遍历数据的迭代器

     【编程实验】带过滤和双向遍历功能的工资查看系统

    复制代码
    //行为模式——迭代器模式
    //场景:工资表数据遍历(带过滤和双向遍历功能)
    
    #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std;
    //***************************辅助类*************************
    //前向声明
    class Iterator;
    class SalaryModel;
    typedef SalaryModel Object;
    
    //工资描述模型对象
    class SalaryModel
    {
    private:
        string userName; //职工姓名
        double salary;      //工资数额
    public:
        string& getUserName(){return userName;}
        void setUserName(string name)
        {
            this->userName = name;
        }
    
        double getSalary(){return salary;}
        void setSalary(double salary)
        {
            this->salary = salary;
        }
    
        void toString()
        {
            cout << "userName = " << userName
                 <<", Salary = " << salary << endl;
        }
    };
    
    //**********************迭代器接口***************************
    //迭代器接口,定义访问和遍历元素的操作(双向遍历)
    class Iterator
    {
    public:
        //移动到聚合对象的第一个位置
        virtual void first() = 0;
        //移动到最后一个位置
        virtual void last() = 0;
        //移动到聚合对象的下一个位置
        virtual void next() = 0;
        //移动到聚合对象的上一个位置
        virtual void previous() = 0;
    
        //判断是否到了尾部位置
        virtual bool isEof() = 0;
        //判断是否到了头部位置
        virtual bool isBof() = 0;
    
        //获取当前元素
        virtual Object* currentItem() = 0;
        virtual ~Iterator(){};
    };
    
    //************************************抽象聚合类**************************
    //聚合对象的接口
    class Aggregate
    {
    public:
        //创建相应迭代器对象的接口
        virtual Iterator* createIterator() = 0;
        //获取数量大小
        virtual int size() = 0;
        //获取指定位置的元素
        virtual Object* get(int index) = 0;
        //加入元素
        virtual void add(Object* o) = 0;
        virtual Aggregate* newInstance() {return NULL;}
        virtual ~Aggregate(){}
    };
    
    //***********************具体迭代器*****************************
    //具体的迭代器对象
    //用来实现访问数组的迭代接口,加入了迭代策略
    //其主要思路就是在ArrayInterator中保存一份过滤后的Aggregate聚合对象数据。
    class ArrayIterator : public Iterator
    {
    private:
        Aggregate* agg;
    
        //记录当前迭代到的位置索引
        int index;
    public:
        ArrayIterator(Aggregate* agg)
        {
            index = 0;
    
            //在这里先对聚合对象的数据进行过滤,比如必须在3000以上
            this ->agg = agg->newInstance();//原型模式,根据agg的实际类型创建对象
    
            //下面用一般遍历方法,而不agg的迭代器,否则那样会造成死循环。
            for(int i = 0;i < agg->size(); i++)
            {
                Object* obj = agg->get(i);
                if (obj->getSalary()> 3000)
                {
                    this->agg->add(obj);
                }
            }
        }
    
        void first()//移动到聚合对象的第1个位置
        {
            index = 0;
        }
    
        void last()//移动到聚合对象的最后一个位置
        {
            index = agg->size() -1;
        }
    
        //移动到聚合对象的下一个位置
        void next()
        {
            if(index < agg->size())
                ++index;
        }
    
        //移动到前一个位置
        void previous()
        {
            if(index>=0)
            {
                --index;
            }
        }
    
        //判断是否到了尾部位置
        bool isEof()
        {
            return (index >= agg->size());
        }
    
        //判断是否到了头部位置
        bool isBof()
        {
            return (index < 0);
        }
    
        //获取当前元素
        Object* currentItem()
        {
            //在这里对返回的数据进行过滤,比如不让查看具体的工资数据
            Object* obj = agg->get(index);
            obj->setSalary(0);
            return obj;
        }
    
        ~ArrayIterator()
        {
            delete agg;
        }
    };
    
    //用数组模拟具体的聚合对象
    class SalaryManager : public Aggregate
    {
    private:
        //聚合对象
        vector<Object*> vecSalarys; //数组
    
        //提供创建具体迭代器的接口
        Iterator* mIter;
    public:
    
        SalaryManager()
        {
            mIter = NULL;
        }
    
        Iterator* createIterator()
        {
            if (mIter == NULL)
            {
                mIter = new ArrayIterator(this);
            }
            return mIter;
        }
    
        //获取数量大小
        int size()
        {
            return vecSalarys.size();
        }
    
        //获取指定位置的元素
        Object* get(int index)
        {
            return vecSalarys[index];
        }
    
        //加入元素
        void add(Object* o)
        {
            vecSalarys.push_back(o);
        }
    
        Aggregate* newInstance()
        {
            Aggregate* ret = new SalaryManager();
            return ret;
        }
        ~SalaryManager()
        {
            delete mIter;
            vecSalarys.clear();
        }
    };
    
    int main()
    {
        Aggregate* agg = new SalaryManager();
    
        //为了测试,输入些数据进去
        SalaryModel* sm = new SalaryModel();
        sm->setSalary(3800);
        sm->setUserName("张三");
        agg->add(sm);
    
        sm = new SalaryModel();
        sm->setSalary(5800);
        sm->setUserName("李四");
        agg->add(sm);
    
        sm = new SalaryModel();
        sm->setSalary(2200);
        sm->setUserName("王五");
        agg->add(sm);
    
        sm = new SalaryModel();
        sm->setSalary(3500);
        sm->setUserName("赵六");
        agg->add(sm);
    
        Iterator* it = agg->createIterator();
    
        //正向遍历
        it->first();
        while(!it->isEof())
        {
            it->currentItem()->toString();
            it->next();
        }
    
        //返向遍历、
        it->last();
        while(!it->isBof())
        {
            it->currentItem()->toString();
            it->previous();
        }
    
        return 0;
    }
    复制代码

    (3)翻页迭代器

      ①在数据库访问中,如果每页显示10条记录,通常用户很少翻到10页以后,那么在第一次访问时,可以从从数据库中获取前10页的数据,即100条记录放在内存里。

      ②当用户在前10页进行翻页操作时,就可以不再访问数据库而是直接从内存中获取数据,速度就快了。

      ③当想获取第11页的数据时,才会再次访问数据库。而翻页迭代的意思是一次迭代要求取出一页的数据,而不是一条数据。所以其实现主要把原来一次迭代一条数据的接口,都修改成一次迭代一页的数据就可以了。

     【编程实验】带随机翻页功能的工资查看系统

    复制代码
    //行为模式——迭代器模式
    //场景:工资表数据遍历(带随机翻页功能)
    //说明:翻页即每页读取若干条记录,
    //      随机指可以指定页数和每页的记录数的访问方式,
    //      而不是按顺序访问页面.
    
    #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std;
    //***************************辅助类*************************
    //前向声明
    class Iterator;
    class SalaryModel;
    typedef SalaryModel Object;
    
    //工资描述模型对象
    class SalaryModel
    {
    private:
        string userName; //职工姓名
        double salary;      //工资数额
    public:
        string& getUserName(){return userName;}
        void setUserName(string name)
        {
            this->userName = name;
        }
    
        double getSalary(){return salary;}
        void setSalary(double salary)
        {
            this->salary = salary;
        }
    
        void toString()
        {
            cout << "userName = " << userName
                 <<", Salary = " << salary << endl;
        }
    };
    
    //**********************迭代器接口***************************
    //迭代器接口,定义访问和遍历元素的操作(双向遍历)
    class Iterator
    {
    public:
    
        //判断是否还有下一个元素,无所谓是否够一页数据
        //因为最后哪怕只有一条数据,也是要算一页。
        virtual bool hasNext() = 0;
        //判断是否还有上一个元素,无所谓是否够一页数据
        //因为最后哪怕只有一条数据,也是要算一页。
        virtual bool hasPrevious() = 0;
    
        //获取指定页号和每页显示的数量
        virtual  void getPage(int pageNum, int pageCount) = 0;
    
        virtual ~Iterator(){};
    };
    
    //************************************抽象聚合类**************************
    //聚合对象的接口
    class Aggregate
    {
    public:
        //创建相应迭代器对象的接口
        virtual Iterator* createIterator() = 0;
        //获取数量大小
        virtual int size() = 0;
        //获取指定位置的元素
        virtual Object* get(int index) = 0;
        //加入元素
        virtual void add(Object* o) = 0;
        virtual Aggregate* newInstance() {return NULL;}
        virtual ~Aggregate(){}
    };
    
    //***********************具体迭代器*****************************
    //具体的迭代器对象
    //用来实现访问数组的迭代接口,加入了迭代策略
    //其主要思路就是在ArrayInterator中保存一份过滤后的Aggregate聚合对象数据。
    class ArrayIterator : public Iterator
    {
    private:
        Aggregate* agg;
    
        //记录当前迭代到的位置索引
        int index;
    public:
        ArrayIterator(Aggregate* agg)
        {
            index = 0;
            this ->agg = agg;
        }
    
        //判断是否还有下一个元素,无所谓是否够一页数据
        //因为最后哪怕只有一条数据,也是要算一页。
        bool hasNext()
        {
            return (index <agg->size());
        }
        //判断是否还有上一个元素,无所谓是否够一页数据
        //因为最后哪怕只有一条数据,也是要算一页。
        bool hasPrevious()
        {
            return (index >0);
        }
    
        //获取指定页号和每页显示的数量
        void getPage(int pageNum, int pageCount)
        {
            //这里可读取到的数据通过数组或链表返回给客户
            //但为了简便,我们在这里
    
            //计算需要获取的数据的开始条数和结束条数
            int start = (pageNum - 1)* pageCount;
            int end = start + pageCount - 1;
    
            //控制start的边界,最小是0
            if(start < 0)
                start = 0;
    
            //控制end的边界
            if(end > agg->size()-1)
                end = agg->size() -1;
    
            //每次取值都是从头开始循环
            index = 0;
    
            while(hasNext() && index <= end)
            {
                if(index>=start)
                {
                    agg->get(index)->toString();
                }
                index++;
            }
        }
    };
    
    //用数组模拟具体的聚合对象
    class SalaryManager : public Aggregate
    {
    private:
        //聚合对象
        vector<Object*> vecSalarys; //数组
    
        //提供创建具体迭代器的接口
        Iterator* mIter;
    public:
    
        SalaryManager()
        {
            mIter = NULL;
        }
    
        Iterator* createIterator()
        {
            if (mIter == NULL)
            {
                mIter = new ArrayIterator(this);
            }
            return mIter;
        }
    
        //获取数量大小
        int size()
        {
            return vecSalarys.size();
        }
    
        //获取指定位置的元素
        Object* get(int index)
        {
            return vecSalarys[index];
        }
    
        //加入元素
        void add(Object* o)
        {
            vecSalarys.push_back(o);
        }
    
        Aggregate* newInstance()
        {
            Aggregate* ret = new SalaryManager();
            return ret;
        }
        ~SalaryManager()
        {
            delete mIter;
            vecSalarys.clear();
        }
    };
    
    int main()
    {
        Aggregate* agg = new SalaryManager();
    
        //为了测试,输入些数据进去
        SalaryModel* sm = new SalaryModel();
        sm->setSalary(3800);
        sm->setUserName("张三");
        agg->add(sm);
    
        sm = new SalaryModel();
        sm->setSalary(5800);
        sm->setUserName("李四");
        agg->add(sm);
    
        sm = new SalaryModel();
        sm->setSalary(2200);
        sm->setUserName("王五");
        agg->add(sm);
    
        sm = new SalaryModel();
        sm->setSalary(3500);
        sm->setUserName("赵六");
        agg->add(sm);
    
        sm = new SalaryModel();
        sm->setSalary(2900);
        sm->setUserName("钱七");
        agg->add(sm);
    
        //得到翻页迭代器
        Iterator* it = agg->createIterator();
    
        //获取第一页,每页显示2条
        cout <<"第1页数据:" <<endl;
        it->getPage(1, 2);
    
        //获取第2页,每页显示2条
        cout <<"第2页数据:" <<endl;
        it->getPage(2, 2);
    
        //再获取第1页,每页显示2条
        cout <<"第1页数据:" <<endl;
        it->getPage(1, 2);
    
        //再获取第3页,每页显示2条
        cout <<"第3页数据:" <<endl;
        it->getPage(3, 2);
    
        return 0;
    }
    复制代码

    4. 迭代器的优缺点

    (1)优点

      ①更好的封装性,可对一个聚合对象的访问,而无须暴露聚合对象的内部实现。

      ②可以不同的遍历方式来遍历一个聚合对象(如正向和反向遍历)

      ③将聚合对象的内容和具体的迭代算法分离开,这样就可以通过使用不同的迭代器实例、不同的遍历方式来遍历一个聚合对象。

      ④简化客户端调用:迭代器为遍历不同的聚合对象提供了一个统一的接口,使用客户端遍历聚合对象的内容变得更加简单。

      ⑤同一个聚合上可以有多个遍历。每个迭代器保持它自己的遍历状态(如索引位置),因此可以对同一个聚合对象同时进行多个遍历。只要为其设置设计不同的迭代器。

    (2)缺点:

      ①由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐。

      ②迭代器模式在遍历的同时更改迭代器所在的集合结构会导致出现异常。所以使用foreach语句只能在对集合进行遍历,不能在遍历的同时更改集合中的元素。

    5. 应用场景

    (1)如果希望提供访问一个聚合对象的内容,但又不想暴露它的内部表示的时候可使用迭代器模式

    (2)如果希望有多种遍历方式可以访问聚合对象。

    (3)如果希望为遍历不同的聚合对象提供一个统一的接口,可以使用迭代器模式(多态迭代)。

    6. 相关模式

    (1)迭代器模式和组合模式

      组合模式是一种递归的对象结构,在枚举某个组合对象的子对象时,通常会使用迭代器模式。

    (2)迭代器模式和工厂方法模式

      在聚合对象创建迭代器的时候,通常会采用工厂方法模式来实例化相应的迭代器对象。

    http://www.cnblogs.com/5iedu/p/5596939.html

    展开全文
  • H5滑动实现翻页(vue)

    千次阅读 2019-05-04 17:04:07
    H5 滑动翻页 框架:Vue + element + JQ + vue-awesome-swiper 项目:H5绣红旗 转载:vue-awesome-swiper - 基于vue实现h5滑动翻页效果 已经打好vue-cli项目,安装vue-awesome-swiper npm i --s vue-awesome-swiper...

    个人blog,欢迎关注加收藏

    H5 滑动翻页

    框架:Vue + element + JQ + vue-awesome-swiper

    项目:H5绣红旗

    转载:vue-awesome-swiper - 基于vue实现h5滑动翻页效果

    1. 已经打好vue-cli项目,安装vue-awesome-swiper
    npm i --s vue-awesome-swiper
    
    1. main.js
    import VueAwesomeSwiper from 'vue-awesome-swiper'
    import 'swiper/dist/css/swiper.css'
    Vue.use(VueAwesomeSwiper)
    
    1. 组件中
      3.1 写上swiper结构
    <transition name="fade">
                <swiper id="swiperBox" v-bind:options="swiperOption" ref="mySwiper">
                    <!-- 第一页 -->
                    <swiper-slide class="swiper-slide1">
                        <div class="page">
                            <home></home>
                        </div>
                    </swiper-slide>
                    <!-- 第二页 -->
                    <swiper-slide class="swiper-slide2">
                        <div class="page">
                            <statistics></statistics>
                        </div>
                    </swiper-slide>
                    <!-- 第三页 -->
                    <swiper-slide class="swiper-slide3">
                        <div class="page">
                            <enterprise></enterprise>
                        </div>
                    </swiper-slide>
                    <!-- 第四页 -->
                    <swiper-slide class="swiper-slide4">
                        <div class="page">
                            <congratulation></congratulation>
                        </div>
                    </swiper-slide>
                </swiper>
            </transition>
    

    3.2 引入swiper相关组件

    import { swiper, swiperSlide } from 'vue-awesome-swiper'
    

    3.3 注册组件

    components:{
          home,//第1页内容组件
          statistics,//第2页内容组件
          enterprise,//第3页内容组件
          congratulation,//第4页内容组件
          swiper,
          swiperSlide,
    
      },
    

    3.4 swiper配置,详细参数请参照官网swiper中文官网

    data () {
        return {
            swiperOption: {
                // swiper configs 所有的配置同swiper官方api配置
                
                notNextTick: true,//notNextTick是一个组件自有属性,如果notNextTick设置为true,组件则不会通过NextTick来实例化swiper,也就意味着你可以在第一时间获取到swiper对象,假如你需要刚加载遍使用获取swiper对象来做什么事,那么这个属性一定要是true
                direction: 'vertical',//垂直方向移动
                grabCursor: true,//鼠标覆盖Swiper时指针会变成手掌形状,拖动时指针会变成抓手形状
                setWrapperSize: true,//Swiper使用flexbox布局(display: flex),开启这个设定会在Wrapper上添加等于slides相加的宽或高,在对flexbox布局的支持不是很好的浏览器中可能需要用到。
                autoHeight: true,//自动高度。设置为true时,wrapper和container会随着当前slide的高度而发生变化
                slidesPerView: 1,//设置slider容器能够同时显示的slides数量(carousel模式)。可以设置为数字(可为小数,小数不可loop),或者 'auto'则自动根据slides的宽度来设定数量。loop模式下如果设置为'auto'还需要设置另外一个参数loopedSlides。
                mousewheel: false,//开启鼠标滚轮控制Swiper切换。可设置鼠标选项,默认值false
                mousewheelControl: false,//同上
                height: window.innerHeight, // 高度设置,占满设备高度
                resistanceRatio: 0,//抵抗率。边缘抵抗力的大小比例。值越小抵抗越大越难将slide拖离边缘,0时完全无法拖离。本业务需要
                observeParents: true,//将observe应用于Swiper的父元素。当Swiper的父元素变化时,例如window.resize,Swiper更新
        
                // 如果自行设计了插件,那么插件的一些配置相关参数,也应该出现在这个对象中,如下debugger
                debugger: true,
        
                // swiper的各种回调函数也可以出现在这个对象中,和swiper官方一样
                onTransitionStart(swiper){
                    console.log(swiper)
                }
            }
        }
      },
    

    vue组件中整体代码:

    <template>
      <div class="index">
            <transition name="fade">
                <swiper id="swiperBox" v-bind:options="swiperOption" ref="mySwiper">
                    <!-- 第一页 -->
                    <swiper-slide class="swiper-slide1">
                        <div class="page">
                            <home></home>
                        </div>
                    </swiper-slide>
                    <!-- 第二页 -->
                    <swiper-slide class="swiper-slide2">
                        <div class="page">
                            <statistics></statistics>
                        </div>
                    </swiper-slide>
                    <!-- 第三页 -->
                    <swiper-slide class="swiper-slide3">
                        <div class="page">
                            <enterprise></enterprise>
                        </div>
                    </swiper-slide>
                    <!-- 第四页 -->
                    <swiper-slide class="swiper-slide4">
                        <div class="page">
                            <congratulation></congratulation>
                        </div>
                    </swiper-slide>
                </swiper>
            </transition>
      </div>
    </template>
    
    <script>
    import home from './home.vue'//第1页的内容,组件化
    import statistics from './statistics.vue'//第2页的内容,组件化
    import enterprise from './enterprise.vue'//第3页的内容,组件化
    import congratulation from './congratulation.vue'//第4页的内容,组件化
    
    import { swiper, swiperSlide } from 'vue-awesome-swiper'
    export default {
      name: 'index',
      data () {
        return {
            swiperOption: {
                // swiper configs 所有的配置同swiper官方api配置
                
                notNextTick: true,//notNextTick是一个组件自有属性,如果notNextTick设置为true,组件则不会通过NextTick来实例化swiper,也就意味着你可以在第一时间获取到swiper对象,假如你需要刚加载遍使用获取swiper对象来做什么事,那么这个属性一定要是true
                direction: 'vertical',//垂直方向移动,水平方向是horizontal
                grabCursor: true,//鼠标覆盖Swiper时指针会变成手掌形状,拖动时指针会变成抓手形状
                setWrapperSize: true,//Swiper使用flexbox布局(display: flex),开启这个设定会在Wrapper上添加等于slides相加的宽或高,在对flexbox布局的支持不是很好的浏览器中可能需要用到。
                autoHeight: true,//自动高度。设置为true时,wrapper和container会随着当前slide的高度而发生变化
                slidesPerView: 1,//设置slider容器能够同时显示的slides数量(carousel模式)。可以设置为数字(可为小数,小数不可loop),或者 'auto'则自动根据slides的宽度来设定数量。loop模式下如果设置为'auto'还需要设置另外一个参数loopedSlides。
                mousewheel: false,//开启鼠标滚轮控制Swiper切换。可设置鼠标选项,默认值false
                mousewheelControl: false,//同上
                height: window.innerHeight, // 高度设置,占满设备高度
                resistanceRatio: 0,//抵抗率。边缘抵抗力的大小比例。值越小抵抗越大越难将slide拖离边缘,0时完全无法拖离。本业务需要
                observeParents: true,//将observe应用于Swiper的父元素。当Swiper的父元素变化时,例如window.resize,Swiper更新
        
                // 如果自行设计了插件,那么插件的一些配置相关参数,也应该出现在这个对象中,如下debugger
                debugger: true,
        
                // swiper的各种回调函数也可以出现在这个对象中,和swiper官方一样
                onTransitionStart(swiper){
                    console.log(swiper)
                }
            }
        }
      },
      computed: {
        swiper() {
          return this.$refs.mySwiper.swiper
        }
      },
      methods:{
          
      },
      components:{
          home,
          statistics,
          enterprise,
          congratulation,
          swiper,
          swiperSlide
    
      },
     
     mounted(){
            let _this = this;
            console.log('进入首页');
      }
    }
    </script>
    
    <style scoped>
      .fade-enter-active, .fade-leave-active {
        transition: opacity .5s
      }
      .fade-enter, .fade-leave-to{
        opacity: 0
      }
      .swiper-wrapper{
        /* height: 100px; */
      }
    </style>
    
    
    展开全文
  • 米读小说怎么设置翻页,不少的小伙伴平常也是在使用这款软件在阅读小说,自己对此也是比较的关心这个怎么设置翻页,自己也是太喜欢这个翻页效果,下面就给大家带来详细介绍。 米读小说怎么设置翻页 1、首先我们...
  • cocos2d - JS 实现翻页效果

    千次阅读 2017-01-17 15:38:11
    cocos2d - JS 实现翻页效果 :显示效果 :说明 :Demo实现了常见的翻页效果 , 还有点击选关模式的放大效果 . 翻页效果主层 JavaScript 代码 :var SelectPageLayer = cc.Layer.extend({ deltaCount : null, ...
  • PHP翻页

    千次阅读 2011-11-01 14:06:09
    //是否支持AJAX分页模式 /** * private * */ var $pagebarnum=10;//控制记录条的个数。 var $totalpage=0;//总页数 var $ajax_action_name='';//AJAX动作名 var $nowindex=1;//当前页 var $url="";//url...
  • android 上下平滑翻页

    万次阅读 热门讨论 2013-09-18 09:04:19
    主要实现上下滑动翻页的自定义控件代码:   package com.example.flingpage; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; im
  • 纵观交互设计发展史,就是创新的交互模式被广为接受后成为标准,旧的交互模式不断被淘汰的历史。所以交互模式发展也是一个“物竞天择,优胜劣汰”的过程。本文是09年在碳酸饮料会上做过的一次分享,当时创新的东西...
  • 设计模式

    千次阅读 2017-05-01 21:56:04
    更新ingSOLID原则 缩写 英文 中文 描述 SRP The Single Responsibility Principle 单一责任原则 让一个类只做一种类型责任,当这个类需要承当其他... 软件实体应该是可扩展,而可修改的。也就是说,对扩展是
  • 由于在敏捷开发过程中每个迭代都会增加功能、修复缺陷或重构代码,所以在完成当前迭代新增特性测试工作的同时,还要通过回归测试来保证历史功能受影响。为此我们期望: 测试范围足够广: 测试用例要覆盖...
  • 切换模式键退出命令文本修改键光标移动指令剪切和粘贴键翻页键 vi编辑器 vi是UNIX和类UNIX环境下的可用于创建文件的屏幕编辑器。vi有两种工作模式:命令模式和文本输入模式。启动vi需要输入vi,按...
  • 一个翻页的JS代码

    千次阅读 2010-03-09 10:31:00
    支持 ie 和 mf ,大家可以自行添加喜欢的样式,也可修改默认样式表就为了方便,呵呵 说明:自动监测当前页面地址,并分析url参数(?后的参数),获取page变量(如有多个以最后一个为准)pageCount: 定义总页数(必要)...
  • FlipIt 是一款 Windows 下免费开源的翻页时钟,开发者灵感来自老牌翻页时钟应用 Fliqlo,无网络权限,需要 Flash,单 .scr 屏保程序,堪称最佳 Fliqlo 替代品。 Fliqlo是最经典的翻页时钟屏保。但它基于 Flash ...
  • vim是vi的升级版,支持显示高亮颜色语法 三种模式: 命令模式 输入模式 末行模式 光标移动:上下左右键或HJKL 命令模式 行间跳转: G:跳转光标到末行 gg:跳转光标到顶端 行内跳转: $/end:光标跳转到光标...
  • xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_h
  • 三大模式切换(命令模式,(插入)编辑模式,(末行)底行模式) i 切换到输入模式,以输入字符 : 切换到底线命令模式,以在最底一行输入命令。 命令模式 复制一行:yy 粘贴 : p 光标所在行下一行 P 光标所在行上一...
  • vim编辑器的工作模式

    千次阅读 2018-08-22 16:06:01
    vim编辑器的三种工作模式:命令模式、输入模式、末行模式 打开文件:将准备打开的文件指定为命令行上的一个参数。# vim /etc/hosts 编辑文本 ... 切换到插入模式,并在当前光标位置之前开始插...
  • 前端JS设计模式

    千次阅读 2020-03-22 00:46:26
    什么是设计模式 软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、...
  •  支持翻页、上一页、下一页 6. 支持语音自动阅读,发声接近正常真人发声,非常强大。 语音方案可以选择两种: (1). 宇音SYN6658 (2). 科大讯飞SYN5152。 这两款芯片都是通过串口通信,编程十分简单。 内部编程...
  • 这篇文章整理自一本书《移动设计模式大观》,下载地址:http://download.csdn.net/detail/shanxing2/5103470 4.搜索 4.1触发搜索 有“搜索”按钮,点击后触发搜索。 最佳实践:搜索框比较醒目,提供取消搜索操作...
  • 移动端自动化测试实战(一)

    万次阅读 2019-09-20 09:39:22
    说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家! 目录 一丶移动端测试知识概览 ...三丶第二阶段移动端自动化工具简介以及安装 1.主流的移动端自动化工具 2.Appium环...
  • 意图 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。 场景 这次说游戏了,假设我们需要在一个Web页面上分页显示数据。首先需要一个分页控制器和一...
  • 我自己的资源网站需要做分页,无奈discuz官方一直都搞分页的,唯有自己DIY一波了。这几天我也百度了很多资料,才最终研究出来,现在把经验分享给大家! 下面大家按我的节奏来,我会先添加一个新的独立页面,...
  • 1.“俄罗斯存储过程”的改良版 CREATE procedure pagination1 (@pagesize int, --页面大小,如每页存储20条记录 @pageindex int --当前页码) as set nocount on begin declare @indextable table(id int identity(1,...
  • Airtest+Poco游戏自动化测试

    千次阅读 2019-10-18 16:09:15
    Airtest是一个跨平台的、基于图像识别的UI自动化测试框架,适用于游戏和App,支持平台有Windows、Android和iOS。 Poco是一款基于UI控件识别的自动化测试框架,目前支持Unity3D/cocos2dx-*/Android原生app/iOS原生app...
  • 在vim中针对c++自动补全

    千次阅读 2011-11-14 10:18:04
    首先确定vim编辑.cc或者.cpp文件时当前自动补全函数,在命令模式下输入 :set omnifunc? 如果得到的结果为:omnifunc=ccomplete#Complete,说明有必要进行以下的操作以实现针对c++的自动补全 1
  • 11-selenium浏览器自动

    千次阅读 2020-08-26 23:17:50
    自动化:通过它,我们可以写出自动化程序,像人一样在浏览器里操作web界面。 比如点击界面按钮,在文本框中输入文字 等操作,还能从web界面获取信息。 比如获取12306票务信息,招聘网站职位信息,财经网站股票价格...
  • * 分批加载,网上拉的时候数据会自动加载 * startIndex 开始的位置 * maxCount 每页展示的最大的条目 */ public List<BlackNumberInfo> findPar2(int startIndex, int maxCount) { SQLiteDatabase db = ...
  • 敏捷自动化测试

    2015-09-29 16:07:51
    由于在敏捷开发过程中每个迭代都会增加功能、修复缺陷或重构代码,所以在完成当前迭代新增特性测试工作的同时,还要通过回归测试来保证历史功能受影响。为此我们期望: 测试范围足够广: 1、测试用例要覆盖所有...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 16,802
精华内容 6,720
关键字:

当前模式不支持自动翻页