拖拽-实现变换元素在元素堆中的顺序,mouseDown\mousemove\mouseUp

 一、draggable+jquery实现

1、给所有的元素添加draggable=“true”属性

2、drop事件不能触发:在dragover事件中添加e.preventDefault()

目标效果:拖拽元素使之位于目标元素后面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>拖拽元素使之位于目标元素之后</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .container{
            width: 100%;
            height: 100%;
            position: relative;
            margin: auto;
            font-size: 0;
        }
        .drag{
            width: 100px;
            height: 100px;
            margin-left: 10px;
            margin-bottom: 10px;
            display: inline-block;
            background-color: green;
            opacity: 0.6;
            vertical-align: top;
        }
        .example{
            background-color: yellow;
        }
        .mask{
            display: none;
            background: white;
            border: 1px dashed red;
            position: absolute;
            top: 0;
            left: 0;
            box-sizing: border-box;
        }
        .mousehover{
            display: inline-block;
            width: 10px;
            height: 100px;
            background-color: skyblue;
            border-radius: 5px;
            vertical-align: top;
        }
    </style>
    <script>
        // 记录当前被拖拽元素的大小和位置,以确定mask大小和位置
        var width, height
        var x = 0
        var y = 0
        // 位置提示条
        var mousehover = '<div class="mousehover"></div>'
        // 设置数组记录位置顺序
        var sortArr = []
        // 目标元素的索引
        var currentIndex
        // 目标元素的data-order
        var currentDataOrder

        $(function(){
            drag.init('container')
        })
        var drag = {
            class_name: null, // 允许放置的容器
            _x: 0, // 节点x坐标
            _y: 0, // 节点y坐标
            _left: 0, // 光标和节点坐标的距离
            _top: 0, // 光标和节点坐标的距离
            old_elm: null, // 拖拽原节点
            new_elm: null, // 拖拽完成后添加的新节点
            // 初始化
            init: function(className){
                // 允许拖拽节点的父容器的className
                drag.class_name = className
                // 1.监听拖拽开始事件,动态绑定要拖拽的节点
                $(document).on('dragstart', '.drag', function(e){
                    // 获取到拖拽的原节点对象
                    drag.old_elm = $(this)
                    // 执行拖拽开始事件的操作
                    drag.dragstartHandle(e)
                    // 专门定义一个函数可以在下面的on事件中获取到drag.old_elm
                    drag.getOldElm()
                })
                // 2.位置提示条的出现和消失
                $(document).on('dragenter', '.drag', function(e){
                    $(e.target).after(mousehover)
                    $('.drag').off('dragenter')
                })
                $(document).on('dragover', '.drag', function(e){
                    e.preventDefault()
                })
                $(document).on('dragleave', '.drag', function(e){
                    $(e.target).nextAll('.mousehover').remove()
                    $('.drag').off('dragleave')
                })
                // 3.监听元素拖拽中事件
                $(document).on('drop', '.drag', function(e){
                    // 删除位置提示条
                    $(e.target).nextAll('.mousehover').remove()
                    // 删除蒙版
                    $('.mask').remove()

                    // 获取到被拖拽元素
                    var old_elm = drag.getOldElm()

                    //删除被拖拽元素
                    $(old_elm).remove()
                    
                    // 在目标元素后面添加被拖拽元素
                    $(e.target).after($(old_elm))
                    //  记录拖拽之后的顺序
                    if(sortArr.length !== 0){
                        sortArr.length = 0
                    }
                    $('.drag').each(function(index, item){
                        sortArr.push($(item).attr('data-order'))
                    })
                })
            },
            dragstartHandle(e){
                // 1. 设置蒙版的样式和出现位置
                width = drag.old_elm.get(0).offsetWidth
                height = drag.old_elm.get(0).offsetHeight
                x = drag.old_elm.get(0).offsetLeft
                y =  drag.old_elm.get(0).offsetTop
                $('.mask').css({
                    'width': width + 'px',
                    'height': height + 'px',
                    "left": x + 'px',
                    "top": y + 'px'
                })
                $('.mask').stop().show()
            },
            getOldElm(){
                return drag.old_elm
            }
        }
    </script>
</head>
<body>
    <div class="container">
        <div class="mask"></div>
        <div class="drag" draggable="true" data-order="0">0</div>
        <div class="drag" draggable="true" data-order="1">1</div>
        <div class="drag" draggable="true" data-order="2">2</div>
        <div class="drag example" draggable="true" data-order="3">3</div>
        <div class="drag" draggable="true" data-order="4">4</div>
        <div class="drag" draggable="true" data-order="5">5</div>
        <div class="drag" draggable="true" data-order="6">6</div>
        <div class="drag" draggable="true" data-order="7">7</div>
        <div class="drag" draggable="true" data-order="8">8</div>
        <div class="drag" draggable="true" data-order="9">9</div>        
        <div class="drag" draggable="true" data-order="10">10</div>
    </div>
</body>
</html>

二、react通过mouseDown 、mousemove、mouseUp实现拖拽

当实现类似Excel选中区域的功能时,经常出现 mouseup 事件丢失的情况,由于缺少了 mouseup 事件,导致一个完整的操作无法进行。

目前发现两个原因:

  1. 触发了浏览器的 drag 操作,导致mouseup丢失。
  2. 由于鼠标离开了操作的区域,触发了mouseleave导致mouseup丢失。
  3. 最后的解决办法:1)mouseUp方法放到父元素上防止失去焦点。2)在mouseUp事件一开始就取消mousemove事件   document.mousemove = null。 3)取消冒泡和捕获事件。
    // 阻止事件冒泡, 不仅仅要stopPropagation,还要preventDefault
      pauseEvent(e){ 
        e=e || window.event;
        if(e.stopPropagation) e.stopPropagation();
        if(e.preventDefault) e.preventDefault();
        e.cancelBubble=true;
        e.returnValue=false;
        return false;
      }

解决办法

第一种情况

通过执行下面的代码阻止系统默认的操作来防止触发 drag 操作:

//在事件中
e=e || window.event;
pauseEvent(e);

//阻止事件冒泡
//不仅仅要stopPropagation,还要preventDefault
function pauseEvent(e){
if(e.stopPropagation) e.stopPropagation();
if(e.preventDefault) e.preventDefault();
e.cancelBubble=true;
e.returnValue=false;
return false;
}

通过对事件调用pauseEvent方法可以防止出现drag操作,因此在区域内可以避免mouseup丢失。即使你想实现的本来就是 drag 操作,也可以通过创建跟随鼠标移动的dom元素实现效果。

参考地址:

http://stackoverflow.com/questions/5429827/how-can-i-prevent-text-element-selection-with-cursor-drag

第二种情况

由于鼠标移到了区域外,触发了 mouseleave 操作,因此在这种情况下要监听 mouseleave 操作,当触发该操作时可以停止或者还原状态。

特别注意的地方

当处理鼠标事件时,可以还要考虑是否要控制按下那个键时才允许操作。

Mouse事件中有一个 buttons 属性,该值标示鼠标按下了一个或者多个按键,如果按下的键为多个,值则为多个:

  • 0 : 没有按键或者是没有初始化
  • 1 : 鼠标左键
  • 2 : 鼠标右键
  • 4 : 鼠标滚轮或者是中键
  • 8 : 第四按键 (通常是“浏览器后退”按键)
  • 16 : 第五按键 (通常是“浏览器前进”)

多个值的时候,相当于进行|操作,即鼠标左右键同时按下时1|2=3。判断是否按下左键可以用value&1!=0进行,例如左右键同时按下时3&1!=0true,说明按下了左键。

子组件:

/* 
 @params title:移动块的标题
 @params pendingNum:待处理数据的数量
 @params clickPendingNum:点击待处理数量触发的函数
*/

import React, { Component } from 'react';
import './style.less'

export default class Drag extends Component {
  constructor(props){
      super(props)
      this.state = {
        // dragSize: {...props.options}.dragSize, // 移动块的大小类型,小:small、中:middle、大:great、宽:relax、巨:giant
        sizeOptions: {...props.options}.sizeOptions, // 父组件传递过来的大小型号的选项,默认第一个是初始型号
        allow_drag: false, // 是否允许拖拽
        currentOrder: 0, // 被拖拽元素的data-order
        targetOrder: -1, // 目标元素的data-order
        moveOrder: 0, // 移动过程中途径元素的order,用于控制位置提示条的显隐
        origin_left: 0, // 被拖拽元素原始相对于视图的X坐标
        origin_top: 0, // 被拖拽元素原始相对于视图的Y坐标
        mouse_left: 0, // 鼠标相对于视图的x坐标
        mouse_top: 0, // 鼠标相对于视图的y坐标
        fixed_left: 0, // 鼠标相对于元素的X坐标
        fixed_top: 0, // 鼠标相对于元素的Y坐标
        move_x: 0, // 元素相对于父元素的left
        move_y: 0, // 元素相对于父元素的top
        aPos: [], // 各个移动块的位置
      }
      this.mouseDown = this.mouseDown.bind(this)
      this.mouseMove = this.mouseMove.bind(this)
      this.mouseUp = this.mouseUp.bind(this)
      this.findNearest = this.findNearest.bind(this)
      this.getPosition = this.getPosition.bind(this)
      this.pauseEvent = this.pauseEvent.bind(this)
  }

  // 开始拖拽事件
  mouseDown(e){
    console.log('鼠标按下事件')
    this.pauseEvent(e);
    // 鼠标按下时元素放大部分的宽度
    var scaleWidth = e.target.closest('.move_block').offsetWidth * 0.05
    if(!this.state.allow_drag){
      this.setState({
        currentOrder: parseInt(e.target.closest('.blockAndBg').dataset.order),
        allow_drag: true,
        origin_left: this.getPosition(e.target.closest('.blockAndBg')).left,
        origin_top: this.getPosition(e.target.closest('.blockAndBg')).top,
        fixed_left: e.pageX+document.getElementsByClassName('right-container')[0].scrollLeft - this.getPosition(e.target.closest('.blockAndBg')).left + scaleWidth / 2,
        fixed_top: e.pageY+document.getElementsByClassName('right-container')[0].scrollTop - this.getPosition(e.target.closest('.blockAndBg')).top
      },() => {
        // 获取页面中所有移动块的位置
        var arrPos = []
        // 不能在遍历中改变aPos的状态,因为setState是异步更新的,改变的状态全都是最后的值
        document.querySelectorAll('.blockAndBg').forEach((item, index) => {
          arrPos.push(
            {
              left: item.offsetLeft, 
              right: item.offsetLeft+ item.offsetWidth,
              top: item.offsetTop + document.getElementsByClassName('top')[0].offsetHeight, 
              bottom: item.offsetTop + document.getElementsByClassName('top')[0].offsetHeight + item.offsetHeight
            }
          )
        })
        this.setState({
          aPos: arrPos
        }, () => {
          // 监听移动事件
          document.onmousemove = (event) => {
            this.mouseMove(event)
          }
        })
      })
    }
  }
  // 鼠标移动事件
  mouseMove(e){
    this.pauseEvent(e);
    if(this.state.allow_drag){
      // 获取鼠标移动的x,y坐标
      this.setState({
        mouse_left: e.pageX+document.getElementsByClassName('right-container')[0].scrollLeft,
        mouse_top: e.pageY+document.getElementsByClassName('right-container')[0].scrollTop,
        // 鼠标移动的x - (鼠标相对于元素在x轴上的距离 + blockAndBg的left值 ) = 元素absolute的left值
        move_x: e.pageX+document.getElementsByClassName('right-container')[0].scrollLeft - this.state.fixed_left - this.state.origin_left, 
        move_y: e.pageY+document.getElementsByClassName('right-container')[0].scrollTop - this.state.fixed_top - this.state.origin_top
      },()=>{
        this.findNearest()
      })
    }
  }
  // 结束拖拽事件
  mouseUp(e){
    // 必须先删除mousemove事件,否则可能会无法实现鼠标抬起事件
    document.onmousemove = null
    this.pauseEvent()
    this.setState({
      allow_drag: false,
      move_x: 0,
      move_y: 0
    },() => {
      // 调用父元素的getTargetOrder方法传递给父元素拖拽对象的order和目标对象的order
      this.props.options.getTargetOrder(this.state.currentOrder, this.state.targetOrder)
      // 将位置提示条隐藏
      this.props.options.changeMoveHover(-1)
    })      
  }
  findNearest(){
    this.state.aPos.forEach((item, index) => {
      if(this.state.mouse_left > item.left && this.state.mouse_left < item.right && this.state.mouse_top < item.bottom && this.state.mouse_top > item.top){
        // 目标元素
        var targetMove = document.querySelectorAll('.blockAndBg')[index]
        var orderBy = parseInt(targetMove.dataset.order)
        this.setState({
          targetOrder: orderBy,
          moveOrder: orderBy
        },() => {
          this.props.options.changeMoveHover(this.state.moveOrder)
        })
      }
    })
  }
  // 获取元素到文档区域的坐标
  getPosition(element){
    if(element != null){
      var rec = element.getBoundingClientRect(),
      _x = rec.left, // 获取元素相对浏览器视窗window的左、上坐标
      _y = rec.top;
      // 与html或body元素的滚动距离相加就是元素相对于文档区域document的坐标位置
      _x += document.getElementsByClassName('right-container')[0].scrollLeft;
      _y += document.getElementsByClassName('right-container')[0].scrollTop;
      return {
        left: _x,
        top: _y
      };
    }
  }
  // 阻止事件冒泡, 不仅仅要stopPropagation,还要preventDefault
  pauseEvent(e){ 
    e=e || window.event;
    if(e.stopPropagation) e.stopPropagation();
    if(e.preventDefault) e.preventDefault();
    e.cancelBubble=true;
    e.returnValue=false;
    return false;
  }
  render() {
    return (
      <div className={`blockAndBg ${this.props.dragSize}`} data-order={this.props.options['data-order']}>
        <div className={`bg ${!this.state.allow_drag ? "hide": ''}`}></div>
        <div className="move_block list-divs-big" style={this.state.allow_drag ? {"zIndex": 999, transform:'scaleX(1.05)', opacity: 0.5, left: this.state.move_x, top: this.state.move_y} : null}>
          <div className="list-div-top">
              <span className="textcor" onClick={this.props.options.clickPendingNum}>
                {this.props.options.title} 
                {this.props.options.pendingNum ? "(" : null}<i className='clientNum'>{this.props.options.pendingNum}</i>{this.props.options.pendingNum ? ")" : null}
              </span>
              <div className="right_bet" onMouseUp={e => this.mouseUp(e)}>
                  <span className="moveBtn" title="移动" onMouseDown={this.mouseDown}></span>
                  {this.props.options.moreClick ? <span className="moreClient" onClick={this.props.options.moreClick}>更多</span> : null}
                  <div className="install_border">
                      <div className="install_size"></div>
                      <div className="border_size visitors_size">
                          <div className={`small-line ${this.state.sizeOptions.indexOf("small") !== -1 ? 'show-flex' : 'hide'}`} onClick={() => {
                              this.props.options.getSizeFromOrder('small', this.props.options['data-order'])
                          }}>
                              <i className="iocsmall"></i>
                              <span>小</span>
                          </div>
                          <div className={`middle-line ${this.state.sizeOptions.indexOf("middle") !== -1 ? 'show-flex' : 'hide'}`} onClick={() => {
                              this.props.options.getSizeFromOrder('middle', this.props.options['data-order'])
                          }}>
                              <i className="iocmiddle"></i>
                              <span>中</span>
                          </div>
                          <div className={`great-line ${this.state.sizeOptions.indexOf("great") !== -1 ? 'show-flex' : 'hide'}`} onClick={() => {
                              this.props.options.getSizeFromOrder('great', this.props.options['data-order'])
                          }}>
                              <i className="iocgreat"></i>
                              <span>大</span>
                          </div>
                          <div className={`relax-line ${this.state.sizeOptions.indexOf("relax") !== -1 ? 'show-flex' : 'hide'}`} onClick={() => {
                              this.props.options.getSizeFromOrder('relax', this.props.options['data-order'])
                          }}>
                              <i className="iocrelax"></i>
                              <span>宽</span>
                          </div>
                          <div className={`giant-line ${this.state.sizeOptions.indexOf("giant") !== -1 ? 'show-flex' : 'hide'}`} onClick={() => {
                              this.props.options.getSizeFromOrder('giant', this.props.options['data-order'])
                          }}>
                              <i className="iocgiant"></i>
                              <span>巨</span>
                          </div>
                      </div>
                  </div>
              </div>
          </div>
          <div>{this.props.children}</div>
        </div>
        {
          this.props.showMoveHover === this.props.options['data-order']  && <div className="mousehover"></div>
        }
      </div>
    );
  }
}

父组件

import React from 'react';
import './style.less'
import Drag from '../../components/Drag'
import { useState, useEffect } from 'react';

export default function Overview() {

  // 点击更多触发
  const moreClick = () => {
    console.log('tell me which num')
  }
  // 点击待处理触发
  const clickPendingNum = () => {
    console.log('overview 点击待处理数量')
  }
  // 改变位置提示条的显隐
  const [moveHoverOrder, setMoveHoverOrder] = useState(-1)
  const changeMoveHover = (value) => {
    console.log(value)
    setMoveHoverOrder(value)
  }
  
  // currentOrder: 被拖拽元素的data-order, targetOrder: 目标元素的data-order
  const [sortArr, setSortArr] = useState([0,1,2,3])
  var sort1 = sortArr.slice() // 浅复制数组
  const getTargetOrder = (currentOrder, targetOrder) => {
    if(targetOrder !== -1){
      // 拖拽元素在sort1数组中的位置
      var currentIndex = sort1.indexOf(currentOrder)
      // 目标元素在sort1中的位置
      var targetIndex = sort1.indexOf(targetOrder)
      if(currentIndex < targetIndex){
        // 拖拽元素在前,先增后减
        sort1.splice(targetIndex+1, 0, currentOrder)
        sort1.splice(currentIndex,1)
      }else if(currentIndex > targetIndex){ // 目标元素在前,先减后增
        sort1.splice(currentIndex,1)
        sort1.splice(targetIndex+1, 0, currentOrder)
      }
      // 至关重要的一步,不能使用setSortArr(sort1),否则sortArr指向的内存地址不变,不会立即更新视图
      setSortArr([...sort1])
    }
  }
 
  // 获取到order对应的类型大小
  const getSizeFromOrder = (size, order) => {
    // console.log('size', size, 'order', order)
    var optionsArr = [optionOne, optionTwo, optionThree, optionFour]
    optionsArr.forEach((item) => { 
      if(item['data-order'] === order) { 
        // var changeItem = Object.assign({}, item,  { dragSize: size })
        var changeItem = {...item, dragSize: size}
        if(item['data-order'] === 0){
          setOptionOne(changeItem)
        }else if(item['data-order'] === 1){
          setOptionTwo(changeItem)
        }else if(item['data-order'] === 2){
          setOptionThree(changeItem)
        }else if(item['data-order'] === 3){
          setOptionFour(changeItem)
        }
      }
    })  
  }

  var [optionOne, setOptionOne] = useState({
    sizeOptions: ['small', 'middle', 'great', 'relax', 'giant'],
    dragSize: "small",
    title:'第0个dragDiv',
    "data-order": 0,
    getTargetOrder: getTargetOrder,
    changeMoveHover: changeMoveHover,
    getSizeFromOrder: getSizeFromOrder,
    childrenNode: () => {
      return (
        <>
          <div style={{height: '100px',width: '100px', backgroundColor:'red'}}></div>
        </>
      )
    }
  })
  var [optionTwo, setOptionTwo] = useState({
    sizeOptions: ['small', 'middle', 'great', 'relax', 'giant'],
    dragSize: "small",
    title:'第1个dragDiv',
    pendingNum: 2,
    "data-order": 1,
    clickPendingNum:clickPendingNum,
    moreClick: moreClick,
    getTargetOrder: getTargetOrder,
    changeMoveHover: changeMoveHover,
    getSizeFromOrder: getSizeFromOrder,
    childrenNode: () => {
      return (
        <>
          <div style={{height: '100px',width: '100px', backgroundColor:'orange'}}></div>
        </>
      )
    }
  })
  var [optionThree, setOptionThree] = useState({
    sizeOptions: ['small', 'middle', 'great', 'relax', 'giant'],
    dragSize: "small",
    title:'第2个dragDiv',
    pendingNum: 3,
    "data-order": 2,
    clickPendingNum:clickPendingNum,
    getTargetOrder: getTargetOrder,
    changeMoveHover: changeMoveHover,
    getSizeFromOrder: getSizeFromOrder,
    childrenNode: () => {
      return (
        <>
          <div style={{height: '100px',width: '100px', backgroundColor:'blue'}}></div>
        </>
      )
    }
  })
  var [optionFour, setOptionFour] = useState({
    sizeOptions: ['small', 'middle', 'great', 'relax', 'giant'],
    dragSize: "small",
    title:'第3个dragDiv',
    pendingNum: 4,
    "data-order": 3,
    clickPendingNum:clickPendingNum,
    getTargetOrder: getTargetOrder,
    changeMoveHover: changeMoveHover,
    getSizeFromOrder: getSizeFromOrder,
    childrenNode: () => {
      return (
        <>
          <div style={{height: '100px',width: '100px', backgroundColor:'green'}}></div>
        </>
      )
    }
  })

  const [componentsArr, setComponentsArr] = useState([]) 
  useEffect(() => {
    var oComponentArr = []
    sortArr.forEach((item1) => {
      [optionOne, optionTwo, optionThree, optionFour].forEach((item2) => {
        item2['data-order'] === item1 && oComponentArr.push(item2)
      })
    })
    setComponentsArr(oComponentArr)
  }, [sortArr, optionOne, optionTwo, optionThree, optionFour, moveHoverOrder]) // eslint-disable-line react-hooks/exhaustive-deps

  return (
    <div className="overview">
      {
        componentsArr.map((item) => {
            return <Drag key={item["data-order"]} options={item} dragSize={item.dragSize} showMoveHover={moveHoverOrder}>{item.childrenNode()}</Drag>
        })
      }
    </div>
  );
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现 Canvas 中元素的选中、拖动和旋转功能,需要以下步骤: 1. 绘制元素 首先,我们需要在 Canvas 中绘制元素,如矩形、圆形等。可以使用 Canvas 提供的绘制 API,例如 `ctx.fillRect()` 绘制矩形,`ctx.arc()` 绘制圆形等。 2. 给元素添加 ID 为了实现元素的选中,需要给每个元素添加一个 ID 属性。可以使用 JavaScript 对象来表示每个元素,并给对象添加一个 ID 属性,例如: ```js const rect = { id: 'rect1', x: 100, y: 100, width: 50, height: 50, color: '#f00' }; ``` 3. 监听鼠标事件 为了实现元素的拖动和旋转,需要监听 Canvas 的鼠标事件,例如 `mousedown`、`mouseup`、`mousemove` 等。可以在监听到 `mousedown` 事件时,判断当前鼠标位置是否在某个元素内部,如果在元素内部,则将该元素设置为选中状态,否则清空选中状态。 4. 移动元素元素被选中时,在 `mousemove` 事件中,根据鼠标位置的变化,计算出元素应该移动的距离,并将元素位置更新。可以使用 `ctx.translate()` 函数实现元素的移动。 5. 旋转元素元素被选中时,在 `mousemove` 事件中,根据鼠标位置的变化,计算出元素应该旋转的角度,并将元素的角度更新。可以使用 `ctx.rotate()` 函数实现元素的旋转。 6. 重新绘制 Canvas 当元素位置或角度发生变化时,需要重新绘制 Canvas。可以使用 `ctx.clearRect()` 函数清空 Canvas,然后重新绘制所有元素。 以上就是实现 Canvas 中元素的选中、拖动和旋转功能的步骤。具体实现需要根据具体情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值