Angular cdk 学习之 drag-drop

       Angualr drag-drop里面的功能能让我们非常方便的处理页面上视图的拖拽(自由拖拽、列表排序拖拽、列表之间拖拽)问题。推荐大伙儿直接看官网,因为下面的内容绝大部分内容包括例子都来源于官网 https://material.angular.io/cdk/drag-drop/overview。一直认为官网才是最好的文档。

       同样和我们前面讲的cdk里面其他模块的功能一样,drag-drop使用之前也需要导入DragDropModule模块。

一 CdkDrag

       CdkDrag是一个指令。添加了CdkDrag指令的视图,表明该视图是可以拖拽的。

       Selector: [cdkDrag]

       Exported as: cdkDrag

1.1 CdkDrag属性

属性 类型 描述
data: T @Input(‘cdkDragData’) CdkDrag指令对应组件元素实例上的数据
disabled: boolean @Input(‘cdkDragDisabled’) 是否禁用从此容器启动拖动序列(我试了下这个属性好像没效果)
lockAxis: ‘x’ | ‘y’ @Input(‘cdkDragLockAxis’) 拖曳方向
rootElementSelector: string @Input(‘cdkDragRootElement’) 传递可拖动元素替代的根元素(换句话说现在是拖动根元素了)
dropped: EventEmitter<CdkDragDrop> @Output(‘cdkDragDropped’) 当一个拖拽的元素松开的时候,并且是在一个container(容器)里面的时候会回调
ended: EventEmitter @Output(‘cdkDragEnded’) 拖拽结束的时候调用
entered: EventEmitter<CdkDragEnter> @Output(‘cdkDragEntered’) 拖拽的时候进入到一个新的容器的时候回调(两个list之间拖拽的时候用到CdkDropList)
exited: EventEmitter<CdkDragExit> @Output(‘cdkDragExited’) 拖拽的时候退出当前容器的时候回调
moved: Observable<CdkDragMove> @Output(‘cdkDragMoved’) 在拖拽的过程中会回调
started: EventEmitter @Output(‘cdkDragStarted’) 在开始拖拽的时候调用
dropContainer: CdkDropListContainer
element: ElementRef

上面表格中大量的提到了容器,我们可以把下文中提到的CdkDropList指令对应的视图元素当做是一个视图。

1.2 CdkDrag使用

       关于CdkDrag的使用,我们就直接贴代码了,很简单就是在我们想要拖动的视图元素上添加CdkDrag指令就行。

import {Component} from '@angular/core';

@Component({
    selector: 'app-drag-drop-drop',
    template: `
        <div class="dragParent" style="width: 500px; height: 500px; background-color: #f1f1f1">
            <div class="example-box" cdkDrag cdkDragRootElement=".dragParent">
                可以到处拖拽
            </div>
        </div>

    `,
    styles: [`
        .example-box {
            width: 100px;
            height: 100px;
            border: solid 1px #ccc;
            color: rgba(0, 0, 0, 0.87);
            cursor: move;
            display: flex;
            justify-content: center;
            align-items: center;
            text-align: center;
            background: #fff;
            border-radius: 4px;
            position: relative;
            z-index: 1;
            transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
            box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2),
            0 2px 2px 0 rgba(0, 0, 0, 0.14),
            0 1px 5px 0 rgba(0, 0, 0, 0.12);
        }

        .example-box:active {
            box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
            0 8px 10px 1px rgba(0, 0, 0, 0.14),
            0 3px 14px 2px rgba(0, 0, 0, 0.12);
        }
    `]
})
export class DragDropDropComponent {

}

视图元素的拖动

上图中因为我设置CdkDrag指令的cdkDragRootElement属性为灰色的div,所以拖动的是整个灰色的div。

二 CdkDropList

       Container that wraps a set of draggable items(可以拖拽item的一个集合,说白了就是把一些可拖元素始放到一起来。用CdkDropList指令来表示)。熟悉android的人可以把他看着是一个ListView。

       Selector: [cdkDropList] cdk-drop-list

       Exported as: cdkDropList

2.1 CdkDropList属性

属性名字 类型 描述
connectedTo: (CdkDropList string)[] CdkDropList
data: T @Input(‘cdkDropListData’) 拖拽容器上的数据(通常是一个数组或者list)
disabled: boolean @Input(‘cdkDropListDisabled’) 是否禁用从此容器启动拖动序列
enterPredicate: (drag: CdkDrag, drop: CdkDropList) => boolean @Input(‘cdkDropListEnterPredicate’) 指定哪些item是可以拖拽到当前容器
id: string @Input() 唯一的id,cdkDropListConnectedTo属性的时候可以使用
lockAxis: ‘x’ | ‘y’ @Input(‘cdkDropListLockAxis’) 指定容器内所有的item的拖拽放下(垂直,水平)
orientation: ‘horizontal’ | ‘vertical’ @Input(‘cdkDropListOrientation’) 容器里面item的摆放方向(垂直,水平)
dropped: EventEmitter<CdkDragDrop<T, any>> @Output(‘cdkDropListDropped’) 拖拽结束的时候,当有item落到当前容器的时候回调
entered: EventEmitter<CdkDragEnter> @Output(‘cdkDropListEntered’) 当item进入当前容器的时候回调
exited: EventEmitter<CdkDragExit> @Output(‘cdkDropListExited’) 当item离开当前容器的时候回调
sorted: EventEmitter<CdkDragSortEvent> @Output(‘cdkDropListSorted’) 拖拽过程中交换item的时候回调

2.2 CdkDropList使用

2.2.1 水平方向的CdkDropList

       cdkDropListOrientation属性的使用

import {Component} from '@angular/core';
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-drag-drop-orientation-drag',
  template: `
      <div cdkDropList cdkDropListOrientation="horizontal" class="example-list" (cdkDropListDropped)="drop($event)">
          <div class="example-box" *ngFor="let timePeriod of timePeriods" cdkDrag>{{timePeriod}}</div>
      </div>
  `,
  styles: [`
      .example-list {
          width: 1000px;
          max-width: 100%;
          border: solid 1px #ccc;
          min-height: 60px;
          display: flex;
          flex-direction: row;
          background: white;
          border-radius: 4px;
          overflow: hidden;
      }

      .example-box {
          padding: 20px 10px;
          border-right: solid 1px #ccc;
          color: rgba(0, 0, 0, 0.87);
          display: flex;
          flex-direction: row;
          align-items: center;
          justify-content: space-between;
          box-sizing: border-box;
          cursor: move;
          background: white;
          font-size: 14px;
          flex-grow: 1;
          flex-basis: 0;
      }

      .cdk-drag-preview {
          box-sizing: border-box;
          border-radius: 4px;
          box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
          0 8px 10px 1px rgba(0, 0, 0, 0.14),
          0 3px 14px 2px rgba(0, 0, 0, 0.12);
      }

      .cdk-drag-placeholder {
          opacity: 0;
      }

      .cdk-drag-animating {
          transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
      }

      .example-box:last-child {
          border: none;
      }

      .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
          transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
      }

  `]
})
export class DragDropOrientationDragComponent {

    timePeriods = [
        'Bronze age',
        'Iron age',
        'Middle ages',
        'Early modern period',
        'Long nineteenth century'
    ];

    /**
     * 移动item的时候,item之间的位置变换
     * @param event
     */
    drop(event: CdkDragDrop<string[]>) {
        moveItemInArray(this.timePeriods, event.previousIndex, event.currentIndex);
    }

}

水平方向的拖动

2.2.2 两个list之间数据交换

       CdkDropList指令cdkDropListConnectedTo属性的使用。同时为方便大家的使用cdk drag-drop也给提供了transferArrayItem()、moveItemInArray()用于两个list之间交互item和单个list之间item位置变换。

import { Component, OnInit } from '@angular/core';
import {CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-drag-drop-transferring-item',
  template: `
      <div class="example-container">
          <h2>左边list</h2>
          <div
                  cdkDropList
                  #todoList="cdkDropList"
                  [cdkDropListData]="leftSource"
                  [cdkDropListConnectedTo]="[doneList]"
                  class="example-list"
                  (cdkDropListDropped)="drop($event)">
              <div class="example-box" *ngFor="let item of leftSource" cdkDrag>{{item}}</div>
          </div>
      </div>

      <div class="example-container">
          <h2>右边list</h2>
          <div
                  cdkDropList
                  #doneList="cdkDropList"
                  [cdkDropListData]="rightSource"
                  [cdkDropListConnectedTo]="[todoList]"
                  class="example-list"
                  (cdkDropListDropped)="drop($event)">
              <div class="example-box" *ngFor="let item of rightSource" cdkDrag>{{item}}</div>
          </div>
      </div>
  `,
  styles: [`
      .example-container {
          width: 400px;
          max-width: 100%;
          margin: 0 25px 25px 0;
          display: inline-block;
          vertical-align: top;
      }

      .example-list {
          border: solid 1px #ccc;
          min-height: 60px;
          background: white;
          border-radius: 4px;
          overflow: hidden;
          display: block;
      }

      .example-box {
          padding: 20px 10px;
          border-bottom: solid 1px #ccc;
          color: rgba(0, 0, 0, 0.87);
          display: flex;
          flex-direction: row;
          align-items: center;
          justify-content: space-between;
          box-sizing: border-box;
          cursor: move;
          background: white;
          font-size: 14px;
      }

      .cdk-drag-preview {
          box-sizing: border-box;
          border-radius: 4px;
          box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
          0 8px 10px 1px rgba(0, 0, 0, 0.14),
          0 3px 14px 2px rgba(0, 0, 0, 0.12);
      }

      .cdk-drag-placeholder {
          opacity: 0;
      }

      .cdk-drag-animating {
          transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
      }

      .example-box:last-child {
          border: none;
      }

      .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
          transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
      }

  `]
})
export class DragDropTransferringItemComponent {

    /**
     * 左边类别数据源
     */
    leftSource = [
        'Get to work',
        'Pick up groceries',
        'Go home',
        'Fall asleep'
    ];

    /**
     * 右边列表数据源
     */
    rightSource = [
        'Get up',
        'Brush teeth',
        'Take a shower',
        'Check e-mail',
        'Walk dog'
    ];

    /**
     * 拖动的时候,list交换item或者单个list里面item位置的变换
     * @param event
     */
    drop(event: CdkDragDrop<string[]>) {
        if (event.previousContainer === event.container) {
            moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
        } else {
            transferArrayItem(event.previousContainer.data,
                event.container.data,
                event.previousIndex,
                event.currentIndex);
        }
    }

}

两个list之间拖动交互数据

2.2.3 可以自己去控制哪些item可以拖进来

       我们有一个这样的例子,左边的list里面不接受其他list的数据拖进来,右边的list只接受其他list里面的偶数。代码如下

import {Component} from '@angular/core';
import {CdkDrag, CdkDragDrop, moveItemInArray, transferArrayItem} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-drag-drop-controlling-with-item',
  template: `
      <div class="example-container">
          <h2>不接受其他list的数据</h2>
          <div
                  id="all"
                  cdkDropList
                  [cdkDropListData]="all"
                  cdkDropListConnectedTo="even"
                  class="example-list"
                  (cdkDropListDropped)="drop($event)"
                  [cdkDropListEnterPredicate]="noReturnPredicate">
              <div
                      class="example-box"
                      *ngFor="let number of all"
                      [cdkDragData]="number"
                      cdkDrag>
                  {{number}}
              </div>
          </div>
      </div>

      <div class="example-container">
          <h2>只接受其他list里面的偶数</h2>
          <div
                  id="even"
                  cdkDropList
                  [cdkDropListData]="even"
                  cdkDropListConnectedTo="all"
                  class="example-list"
                  (cdkDropListDropped)="drop($event)"
                  [cdkDropListEnterPredicate]="evenPredicate">
              <div
                      class="example-box"
                      *ngFor="let number of even"
                      cdkDrag
                      [cdkDragData]="number">{{number}}
              </div>
          </div>
      </div>
  `,
  styles: [`
      .example-container {
          width: 400px;
          max-width: 100%;
          margin: 0 25px 25px 0;
          display: inline-block;
          vertical-align: top;
      }

      .example-list {
          border: solid 1px #ccc;
          min-height: 60px;
          background: white;
          border-radius: 4px;
          overflow: hidden;
          display: block;
      }

      .example-box {
          padding: 20px 10px;
          border-bottom: solid 1px #ccc;
          color: rgba(0, 0, 0, 0.87);
          display: flex;
          flex-direction: row;
          align-items: center;
          justify-content: space-between;
          box-sizing: border-box;
          cursor: move;
          background: white;
          font-size: 14px;
      }

      .cdk-drag-preview {
          box-sizing: border-box;
          border-radius: 4px;
          box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
          0 8px 10px 1px rgba(0, 0, 0, 0.14),
          0 3px 14px 2px rgba(0, 0, 0, 0.12);
      }

      .cdk-drag-placeholder {
          opacity: 0;
      }

      .cdk-drag-animating {
          transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
      }

      .example-box:last-child {
          border: none;
      }

      .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
          transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
      }

  `]
})
export class DragDropControllingWithItemComponent {

    all = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    even = [10];

    drop(event: CdkDragDrop<string[]>) {
        if (event.previousContainer === event.container) {
            moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
        } else {
            transferArrayItem(event.previousContainer.data,
                event.container.data,
                event.previousIndex,
                event.currentIndex);
        }
    }

    /** 只接受其他list里面的偶数 */
    evenPredicate(item: CdkDrag<number>) {
        return item.data % 2 === 0;
    }

    /** 不让其他list里面的数据移入到这个里面来 */
    noReturnPredicate() {
        return false;
    }

}


控制元素是否可以拖动

三 CdkDropListGroup

       CdkDropListGroup指令的范围比CdkDropList要大一级。CdkDropListGroup可以包含多个CdkDropList。而且当CdkDropListGroup包含多个CdkDropList的时候,这些CdkDropList直接是相互connect的(CdkDropList就不用去写cdkDropListConnectedTo属性了)。

       Selector: [cdkDropListGroup]

       Exported as: cdkDropListGroup

CdkDropListGroup使用场景:当我们在多个list之间的item需要相互拖动的时候派上用场。

四 CdkDragHandle

       Handle that can be used to drag and CdkDrag instance。拖动CdkDrag实例的句柄,简单来说就是指定一个元素。当我们拖动这个元素的时候整个CdkDrag实例都会动。

4.1 CdkDragHandle的使用

import {Component, OnInit} from '@angular/core';

@Component({
    selector: 'app-drag-drop-handle-drag-area',
    template: `
        <div class="example-box" cdkDrag>
            I can only be dragged using the handle
            <div class="example-handle" cdkDragHandle>
                <svg width="24px" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z">

                    </path>
                    <path d="M0 0h24v24H0z" fill="none"></path>
                </svg>
            </div>
        </div>
    `,
    styles: [`
        .example-box {
            width: 200px;
            height: 200px;
            padding: 10px;
            box-sizing: border-box;
            border: solid 1px #ccc;
            color: rgba(0, 0, 0, 0.87);
            display: flex;
            justify-content: center;
            align-items: center;
            text-align: center;
            background: #fff;
            border-radius: 4px;
            position: relative;
            z-index: 1;
            transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
            box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2),
            0 2px 2px 0 rgba(0, 0, 0, 0.14),
            0 1px 5px 0 rgba(0, 0, 0, 0.12);
        }

        .example-box:active {
            box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
            0 8px 10px 1px rgba(0, 0, 0, 0.14),
            0 3px 14px 2px rgba(0, 0, 0, 0.12);
        }

        .example-handle {
            position: absolute;
            top: 10px;
            right: 10px;
            color: #ccc;
            cursor: move;
            width: 24px;
            height: 24px;
        }

    `]
})
export class DragDropHandleDragAreaComponent {


}

drap handler

五 CdkDragPreview

       Element that will be used as a template for the preview of a CdkDrag when it is being dragged。简单点就是drag拖动过程中拖动的item的展示形式。

5.1 CdkDragPreview的使用

       比如一个这里的例子,展示影片名字列表,在拖动的时候展示影片对应的图片。

import {Component, OnInit} from '@angular/core';
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';

@Component({
    selector: 'app-drag-drop-customizing-drag-preview',
    template: `
        <div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
            <div class="example-box" *ngFor="let movie of movies" cdkDrag>
                {{movie.title}}
                <img *cdkDragPreview [src]="movie.poster" [alt]="movie.title">
            </div>
        </div>
    `,
    styles: [`
        .example-list {
            width: 500px;
            max-width: 100%;
            border: solid 1px #ccc;
            min-height: 60px;
            display: block;
            background: white;
            border-radius: 4px;
            overflow: hidden;
        }

        .example-box {
            padding: 20px 10px;
            border-bottom: solid 1px #ccc;
            color: rgba(0, 0, 0, 0.87);
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: space-between;
            box-sizing: border-box;
            cursor: move;
            background: white;
            font-size: 14px;
        }

        .cdk-drag-preview {
            box-sizing: border-box;
            border-radius: 4px;
            box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
            0 8px 10px 1px rgba(0, 0, 0, 0.14),
            0 3px 14px 2px rgba(0, 0, 0, 0.12);
        }

        .cdk-drag-placeholder {
            opacity: 0;
        }

        .cdk-drag-animating {
            transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
        }

        .example-box:last-child {
            border: none;
        }

        .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
            transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
        }

    `]
})
export class DragDropCustomizingDragPreviewComponent {

    movies = [
        {
            title: 'Episode I - The Phantom Menace',
            poster: 'https://upload.wikimedia.org/wikipedia/en/4/40/Star_Wars_Phantom_Menace_poster.jpg'
        },
        {
            title: 'Episode II - Attack of the Clones',
            poster: 'https://upload.wikimedia.org/wikipedia/en/3/32/Star_Wars_-_Episode_II_Attack_of_the_Clones_%28movie_poster%29.jpg'
        },
        {
            title: 'Episode III - Revenge of the Sith',
            poster: 'https://upload.wikimedia.org/wikipedia/en/9/93/Star_Wars_Episode_III_Revenge_of_the_Sith_poster.jpg'
        },
        {
            title: 'Episode IV - A New Hope',
            poster: 'https://upload.wikimedia.org/wikipedia/en/8/87/StarWarsMoviePoster1977.jpg'
        },
        {
            title: 'Episode V - The Empire Strikes Back',
            poster: 'https://upload.wikimedia.org/wikipedia/en/3/3c/SW_-_Empire_Strikes_Back.jpg'
        },
        {
            title: 'Episode VI - Return of the Jedi',
            poster: 'https://upload.wikimedia.org/wikipedia/en/b/b2/ReturnOfTheJediPoster1983.jpg'
        },
        {
            title: 'Episode VII - The Force Awakens',
            poster: 'https://upload.wikimedia.org/wikipedia/en/a/a2/Star_Wars_The_Force_Awakens_Theatrical_Poster.jpg'
        },
        {
            title: 'Episode VIII - The Last Jedi',
            poster: 'https://upload.wikimedia.org/wikipedia/en/7/7f/Star_Wars_The_Last_Jedi.jpg'
        }
    ];

    drop(event: CdkDragDrop<{ title: string, poster: string }[]>) {
        moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
    }

}

拖动 preview

六 CdkDragPlaceholder

       Element that will be used as a template for the placeholder of a CdkDrag when it is being dragged. The placeholder is displayed in place of the element being dragged。简单来说就是drag过程中,drag item对应的container里面item的占位符。

6.1 CdkDragPlaceholder使用

       如下的例子,拖动的时候,CdkDragPlaceholder对应加个灰色的背景

import {Component} from '@angular/core';
import {CdkDragDrop, moveItemInArray} from "@angular/cdk/drag-drop";

@Component({
    selector: 'app-drag-drop-customizing-drag-place-holder',
    template: `
        <div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
            <div class="example-box" *ngFor="let movie of movies" cdkDrag>
                <div class="example-custom-placeholder" *cdkDragPlaceholder></div>
                {{movie}}
            </div>
        </div>
    `,
    styles: [`
        .example-list {
            width: 500px;
            max-width: 100%;
            border: solid 1px #ccc;
            min-height: 60px;
            display: block;
            background: white;
            border-radius: 4px;
            overflow: hidden;
        }

        .example-box {
            padding: 20px 10px;
            border-bottom: solid 1px #ccc;
            color: rgba(0, 0, 0, 0.87);
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: space-between;
            box-sizing: border-box;
            cursor: move;
            background: white;
            font-size: 14px;
        }

        .cdk-drag-preview {
            box-sizing: border-box;
            border-radius: 4px;
            box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
            0 8px 10px 1px rgba(0, 0, 0, 0.14),
            0 3px 14px 2px rgba(0, 0, 0, 0.12);
        }

        .cdk-drag-animating {
            transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
        }

        .example-box:last-child {
            border: none;
        }

        .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
            transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
        }

        .example-custom-placeholder {
            background: #ccc;
            border: dotted 3px #999;
            min-height: 60px;
            transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
        }

    `]
})
export class DragDropCustomizingDragPlaceHolderComponent {

    movies = [
        'Episode I - The Phantom Menace',
        'Episode II - Attack of the Clones',
        'Episode III - Revenge of the Sith',
        'Episode IV - A New Hope',
        'Episode V - The Empire Strikes Back',
        'Episode VI - Return of the Jedi',
        'Episode VII - The Force Awakens',
        'Episode VIII - The Last Jedi'
    ];

    drop(event: CdkDragDrop<string[]>) {
        moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
    }
}

拖动 holder


       关于cdk里面drag-drop的内容咱们就先将这么多,主要就是各个指令的使用。文章中涉及的代码例子 https://github.com/tuacy/angular-cdk-study

猜你喜欢

转载自blog.csdn.net/wuyuxing24/article/details/85063083