30天入坑React ---------------day05 Data-Driven

这篇文章是30天React系列的一部分 。

在本系列中,我们将从非常基础开始,逐步了解您需要了解的所有内容,以便开始使用React。如果您曾经想学习React,那么这里就是您的最佳选择!

下载免费的PDF

30天的React Mini-Ebook

数据驱动

在Github上编辑此页面

我们的应用程序中的硬编码数据并不完全理想。今天,我们将我们的组件设置为由数据驱动,以便访问外部数据。

通过这一点,我们编写了第一个组件并将它们设置为子/父关系。但是,我们还没有将任何数据绑定到我们的React组件。虽然在React中编写网站是一种更愉快的体验(我们认为),但我们还没有利用React的强大功能来显示任何动态数据。

让我们今天改变。

走向数据驱动

回想一下,昨天我们构建了包含标题和活动列表的时间轴组件的开头:

我们将演示分解为组件,最后使用静态JSX模板构建了三个独立的组件。每当我们对网站数据进行更改时,必须更新我们组件的模板并不是很方便。

相反,让我们给出用于显示的组件数据。让我们从<Header />组件开始。就目前而言,该<Header />组件仅显示元素的标题Timeline。这是一个很好的元素,能够在页面的其他部分重用它会很好,但标题Timeline对于每次使用都没有意义。

让我们告诉React我们希望能够将标题设置为其他内容。

介绍道具

React允许我们使用与组件相同的属性或属性,以与HTML相同的语法将数据发送到组件。这类似于将src属性传递给图像标记。我们可以考虑<img />标签的属性,因为prop我们正在设置一个名为的组件img

我们可以在组件中访问这些属性this.props。让我们看看props行动吧。

回想一下,我们将<Header />组件定义为:

class Header extends React.Component {
  render() {
    return (
      <div className="header">
        <div className="fa fa-more"></div>

        <span className="title">Timeline</span>

        <input
          type="text"
          className="searchInput"
          placeholder="Search ..." />

        <div className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

当我们使用<Header />组件时,我们将它放在我们的<App />组件中,如下所示:

<Header />

 

我们可以通过更新组件的使用来传递我们title作为prop的属性,<Header />将属性调用设置title为某个字符串,如下所示:

<Header title="Timeline" />

时间在我们的组件内部,我们可以title从类中的this.props属性访问此prop HeaderTimeline我们可以用传入的属性替换它,而不是像在模板中那样静态地设置标题。

 

class Header extends React.Component {
  render() {
    return (
      <div className="header">
        <div className="menuIcon">
          <div className="dashTop"></div>
          <div className="dashBottom"></div>
          <div className="circle"></div>
        </div>

        <span className="title">
          {this.props.title}
        </span>

        <input
          type="text"
          className="searchInput"
          placeholder="Search ..." />

        <div className="fa fa-search searchIcon"></div>
      </div>
    )
  }
}

我们还稍微更新了代码,以便更接近我们最终<Header />代码的样子,包括添加searchIcon一些元素来设置样式menuIcon

现在我们的<Header />组件将显示我们在title调用组件时传入的字符串。例如,<Header />像我们这样调用我们的组件四次:

<Header title="Timeline" />
<Header title="Profile" />
<Header title="Settings" />
<Header title="Chat" />

四个<Header />组件的结果如下所示:

时间线

轮廓

设置

很漂亮,是吗?现在我们可以使用<Header />动态title属性重用该组件。

我们可以传递的不仅仅是组件中的字符串。我们可以传递数字,字符串,各种对象,甚至函数!我们将更多地讨论如何定义这些不同的属性,以便我们稍后可以构建组件api。

而不是静态设置内容和日期让我们采取Content组件并通过数据变量而不是文本设置时间轴内容。就像我们可以使用HTML组件一样,我们可以将多个props组件传递给组件。

回想一下,昨天我们定义了这样的Content容器:

class Content extends React.Component {
  render() {
    return (
      <div className="content">
        <div className="line"></div>

      {/* Timeline item */}
        <div className="item">
          <div className="avatar">
            <img src="http://www.croop.cl/UI/twitter/images/doug.jpg" />
            Doug
          </div>

          <span className="time">
            An hour ago
          </span>
          <p>Ate lunch</p>
          <div className="commentCount">
            2
          </div>
        </div>

        {/* ... */}

      </div>
    )
  }
}

正如我们所做的那样title,让我们来看看props我们的Content组件需要什么:

  • 用户的头像图像
  • 活动的时间戳
  • 活动项目的文本
  • 评论数量

假设我们有一个表示活动项的JavaScript对象。我们将有一些字段,例如字符串字段(文本)和日期对象。我们可能有一些嵌套对象,比如a usercomments。例如:

const moment1 = {
  timestamp: new Date().getTime(),
  text: "Ate lunch",
  user: {
    id: 1,
    name: 'Nate',
    avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
  },
  comments: [
    { from: 'Ari', text: 'Me too!' }
  ]
}

就像我们将字符串标题传递给<Header />组件一样,我们可以获取此活动对象并将其直接传递给Content组件。让我们转换我们的组件,在其模板中显示此活动的详细信息。

为了将动态变量的值传递给模板,我们必须使用模板语法在模板中呈现它。例如:

class Content extends React.Component {
  render() {
    const {activity} = this.props; // ES6 destructuring

    return (
      <div className="content">
        <div className="line"></div>

        {/* Timeline item */}
        <div className="item">
          <div className="avatar">
            <img
              alt={activity.text}
              src={activity.user.avatar} />
            {activity.user.name}
          </div>

          <span className="time">
            {activity.timestamp}
          </span>
          <p>{activity.text}</p>
          <div className="commentCount">
            {activity.comments.length}
          </div>
        </div>
      </div>
    )
  }
}

我们在render()名为destructuring的函数的第一行中的类定义中使用了一些ES6 。以下两行在功能上是等效的:

// these lines do the same thing
const activity = this.props.activity;
const {activity} = this.props;

解构允许我们以更短,更紧凑的方式节省打字和定义变量。

然后,我们可以通过将对象作为prop而不是硬编码字符串传递来使用此新内容。例如:

<Content activity={moment1} />

太棒了,现在我们的活动项目由一个对象驱动。但是,您可能已经注意到我们必须使用不同的注释多次实现此操作。相反,我们可以将一组对象传递给一个组件。

假设我们有一个包含多个活动项的对象:

 

const activities = [
  {
    timestamp: new Date().getTime(),
    text: "Ate lunch",
    user: {
      id: 1, name: 'Nate',
      avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
    },
    comments: [{ from: 'Ari', text: 'Me too!' }]
  },
  {
    timestamp: new Date().getTime(),
    text: "Woke up early for a beautiful run",
    user: {
      id: 2, name: 'Ari',
      avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
    },
    comments: [{ from: 'Nate', text: 'I am so jealous' }]
  },
]

我们可以<Content />通过传递多个活动而不是仅仅一个来重新表达我们的用法:

<Content activities={activities} />

但是,如果我们刷新视图,则不会显示任何内容!我们需要先更新我们的Content组件以接受多个活动。正如我们之前所了解的,JSX实际上只是由浏览器执行的JavaScript。我们可以在JSX内容中执行JavaScript函数,因为它将像我们的其他JavaScript一样由浏览器运行。

让我们将活动项目JSX移动map到我们将为每个项目运行的函数的函数内部。

class Content extends React.Component {
  render() {
    const {activities} = this.props; // ES6 destructuring

    return (
      <div className="content">
        <div className="line"></div>

        {/* Timeline item */}
        {activities.map((activity) => {
          return (
            <div className="item">
              <div className="avatar">
                <img
                  alt={activity.text}
                  src={activity.user.avatar} />
                {activity.user.name}
              </div>

              <span className="time">
                {activity.timestamp}
              </span>
              <p>{activity.text}</p>
              <div className="commentCount">
                {activity.comments.length}
              </div>
            </div>
          );
        })}

      </div>
    )
  }
}

现在我们可以将任意数量的活动传递给我们的数组,Content组件将处理它,但是如果我们现在离开组件,那么我们将有一个相对复杂的组件处理包含和显示活动列表。像这样离开它真的不是React方式。

ActivityItem

在这里,编写一个组件以包含显示单个活动项目的意义,然后Content我们可以转移责任,而不是构建一个复杂的组件。这也将使测试,添加功能等更容易。

让我们更新我们的Content组件以显示组件列表ActivityItem(我们将在下一步创建)。

class Content extends React.Component {
  render() {
    const {activities} = this.props; // ES6 destructuring

    return (
      <div className="content">
        <div className="line"></div>

        {/* Timeline item */}
        {activities.map((activity) => (
          <ActivityItem
            activity={activity} />
        ))}

      </div>
    )
  }
}

这不仅更简单,更容易理解,而且可以更轻松地测试这两个组件。

使用我们新鲜的Content组件,让我们创建ActivityItem组件。由于我们已经为此创建了视图,因此ActivityItem我们需要做的就是从Content组件的模板中复制它作为自己的模块。

class ActivityItem extends React.Component {
  render() {
    const {activity} = this.props; // ES6 destructuring

    return (
      <div className="item">
        <div className="avatar">
          <img
            alt={activity.text}
            src={activity.user.avatar} />
          {activity.user.name}
        </div>

        <span className="time">
          {activity.timestamp}
        </span>
        <p>{activity.text}</p>
        <div className="commentCount">
          {activity.comments.length}
        </div>
      </div>
    )
  }
}

最后是我的实践代码:

文档目录

创建文件

  1. Timeline.css
  2. NewTimeline.html
  3. UpdateTimeline.html

最后在浏览器打开以上的html代码即可运行

作者本人的代码封装的很好,值得学习

PS: 当然,你也要去配置react环境才行   :)


Timeline.css


.notificationsFrame {
  z-index: 2;
  width: 100%;
  top: 20px;
  background: #fff;
  border-radius: 3px;
  overflow: hidden;
  font-family: 'Open Sans', Helvetica, sans-serif;
  margin-bottom: 40px;
}
 .notificationsFrame.show-menu {
  transform: translate3d(150px, 0, 0);
}

 .notificationsFrame .searchInput {
  border: 10px solid red;
  box-sizing: border-box;
  position: absolute;
  top: 13px;
  right: 55px;
  width: 200px;
  height: 34px;
  border-radius: 17px;
  border: none;
  background: #fff;
  padding: 0 17px;
  font-size: 13px;
  color: #666;
  transition: all 0.3s ease-in-out;
  transform: translateX(15px);
  opacity: 0;
  pointer-events: none;
}

 .notificationsFrame .searchInput:focus {
  outline: none;
}

 .notificationsFrame .searchInput.active {
  -webkit-transform: translateX(0);
  transform: translateX(0);
  opacity: 1;
  pointer-events: all;
}

 .notificationsFrame .header {
  position: relative;
  height: 60px;
  background: #5f98cd;
  padding-top: 0;
}

 .notificationsFrame .header .menuIcon {
  position: absolute;
  width: 29px;
  height: 15px;
  top: 23px;
  left: 20px;
  cursor: pointer;
}

 .notificationsFrame .header .menuIcon:hover .dashTop,
 .notificationsFrame .header .menuIcon:hover .dashBottom,
 .notificationsFrame .header .menuIcon:hover .circle {
  background: #fff;
}

 .notificationsFrame .header .menuIcon .dashTop {
  position: absolute;
  width: 20px;
  height: 3px;
  top: 0;
  left: 0;
  background: #b2daff;
  border-radius: 3px;
  -webkit-transition: all 0.2s ease-in-out;
  transition: all 0.2s ease-in-out;
}

 .notificationsFrame .header .menuIcon .dashBottom {
  position: absolute;
  width: 20px;
  height: 3px;
  top: 0;
  left: 0;
  background: #b2daff;
  border-radius: 3px;
  -webkit-transition: all 0.2s ease-in-out;
  transition: all 0.2s ease-in-out;
  width: 29px;
  top: auto;
  bottom: 0;
}

 .notificationsFrame .header .menuIcon .circle {
  position: absolute;
  height: 7px;
  width: 7px;
  border-radius: 4px;
  top: -2px;
  right: 0;
  background: #b2daff;
  -webkit-transition: all 0.2s ease-in-out;
  transition: all 0.2s ease-in-out;
}

 .notificationsFrame .header .title {
  display: block;
  text-align: center;
  color: #fff;
  font-weight: 600;
  font-size: 15px;
  padding-top: 20px;
}

 .notificationsFrame .header .searchIcon {
  position: absolute;
  z-index: 3;
  font-size: 21px;
  color: #fff;
  top: 18px;
  right: 20px;
  -webkit-transition: all 0.3s ease;
  transition: all 0.3s ease;
  cursor: pointer;
}

 .notificationsFrame .header .searchIcon:hover {
  color: #fff;
}

 .notificationsFrame .content {
  position: relative;
  height: 100%;
  overflow: hidden;
}

 .notificationsFrame .content .line {
  position: absolute;
  top: 0;
  left: 40px;
  bottom: 0;
  width: 3px;
  background: #ebebeb;
}

 .notificationsFrame .content .item {
  position: relative;
  z-index: 2;
  margin: 20px 30px 30px 70px;
  display: block;
  /*border-radius: 50%;
   border: 5px solid #ecf0f1;
   box-sizing: border-box;
   position: absolute;
   height: 20px;
   width: 20px;
   background: #fff;
   border: 2px solid #5F98CD;
   box-shadow: 0 0 0 3px #fff;*/
}

 .notificationsFrame .content .item:hover {
  color: #5f98cd;
  cursor: pointer;
}

 .notificationsFrame .content .item .circle {
  box-sizing: border-box;
  position: absolute;
  height: 11px;
  width: 11px;
  background: #fff;
  border: 2px solid #5f98cd;
  box-shadow: 0 0 0 3px #fff;
  border-radius: 6px;
  top: 0;
  left: -20px;
}

 .notificationsFrame .content .item .avatar {
  position: absolute;
  height: 40px;
  width: 40px;
  display: inline-block;
  vertical-align: top;
  overflow: hidden;
  left: -49px;
}

 .notificationsFrame .content .item .avatar img {
  width: 100%;
  -webkit-border-radius: 50%;
  -moz-border-radius: 50%;
  -ms-border-radius: 50%;
  -o-border-radius: 50%;
  border-radius: 50%;
  position: absolute;
  left: 0;
  top: 0;
}

 .notificationsFrame .content .item .time {
  display: block;
  font-size: 11px;
  line-height: 11px;
  margin-bottom: 2px;
}

 .notificationsFrame .content .item p {
  font-size: 15px;
  line-height: 20px;
  margin: 0px 40px 0px 0px;
  font-family: 'Open Sans', Lora, Times, no-serif;
}

 .notificationsFrame .content .item p b {
  font-weight: 600;
}

 .notificationsFrame .content .item .right {
  position: absolute;
  right: 5px;
  font-size: 11px;
  top: 11px;
}

 .notificationsFrame .content .item .commentCount {
  position: absolute;
  right: 15px;
  font-size: 12px;
  top: 11px;
}

 .notificationsFrame .content .item .commentCount:after {
  content: "\f075";
  font-family: FontAwesome;
  position: absolute;
  font-size: 20px;
  color: #ebebeb;
  top: -50%;
  left: 100%;
  margin-left: 10px;
  z-index: 3;
}

 .notificationsFrame .content .item .commentCount:hover:after {
  color: lightblue;
}

 .notificationsFrame .footer {
  position: relative;
  background: #fff;
  margin: auto;
  height: 30px;
  border-top: 1px solid #eee;
  width: 100%;
  border-radius: 10px;
}

 .notificationsFrame .footer button {
  background: #eee;
  position: absolute;
  width: 100%;
  right: 0px;
  left: 0px;
  top: 0px;
  bottom: 0px;
  border: 0;
}

 .notificationsFrame .footer button i {
  margin: 0 10px;
}

2.NewTimeline.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Hello world</title>
    <link href='Timeline.css' rel="stylesheet" type="text/css" />
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
    <!-- Script tags including React -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.min.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>

<body>
    <div id="app"></div>
    <script type="text/babel">
    class Header extends React.Component {
        render() {
            return (
                <div className="header">
                    <div className="menuIcon">
                        <div className="dashTop"></div>
                        <div className="dashBottom"></div>
                        <div className="circle"></div>
                    </div>


                    <span className="title">
                    {/* Timeline */}
                        {this.props.title}
                    
                    </span>

                    <input
                        type="text"
                        className="searchInput"
                        placeholder="Search ..." />

                    <div className="fa fa-search searchIcon"></div>
                </div>
            )
        }
    }
    const moment1 = {
        timestamp:new Date().getTime(),
        text:"Ate lunch",
        user:{
            id: 1,
            name: 'Nate',
            avatar:"http://www.croop.cl/UI/twitter/images/doug.jpg"
        },
        comments: [
            {from:'Ari', text:'Me too!'}
        ]
    }
    class Content extends React.Component{
        render(){
            // these lines do the same thing
            // const activity = this.props.activity;
            //const {activity}= this.props; 

            //{}对象作为 activities
            const {activities}= this.props; // ES6 destructuring
            return (
                <div className="content">
                    <div className="line"></div>
                {/* Timeline item */}
                {/* 使用map是将数组转化为map 
                *使用地图重新格式化阵列中的对象(activity) 叫obj也可以
                *对每一个对象进行操作
                */}
                {activities.map((activity) =>{
                    return(
                        <div className="item">
                        <div className = "avatar">
                            <img
                            alt={activity.text}
                            src={activity.user.avatar} />
                            {activity.user.name}
                        </div>

                        <span className = "time">
                        {activity.timestamp}
                        </span>

                        <p>{activity.text}</p>

                        <div className="commentCount">
                        {activity.comments.length}
                        </div>
                     </div>
                    );
                })}
                    {/* ... */}
            </div>

            )
        }
    } 
    //使用一个包含多个活动项的对象
    const activities = [
        {
            timestamp:new Date().getTime(),
            text:"Ate lunch",
            user:{
            id: 1,name: 'Nate',
            avatar:"http://www.croop.cl/UI/twitter/images/doug.jpg"
            },
        comments: [{from:'Ari', text:'Me too!'}]
        },
        {
            timestamp:new Date().getTime(),
            text:"Woke up early for a beautiful run",
            user:{
            id: 2,name: 'Ari',
            avatar:"http://www.croop.cl/UI/twitter/images/doug.jpg"
            },
        comments: [{ from: 'Nate', text: 'I am so jealous'}]
        },
    ]
      class App extends React.Component {
        render() {
             return (
                 <div className="notificationsFrame">
                    <div className="panel">
                     {/* content goes here */}
                        <Header  title="Timeline1"/>
                        <Header  title="Profile"/>
                        <Header  title="Settings"/>
                        <Header  title="Timeline1"/>
                        <Content activities = {activities}/>
                    </div>
                 </div>
             )
            }
        }
      var mount  = document.querySelector('#app');
      ReactDOM.render(<App />, mount);
  </script>
</body>

</html>

3.UpdateTimeline.html

<!-- 让我们更新我们的Content组件以显示组件列表ActivityItem-->

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Hello world</title>
    <link href='Timeline.css' rel="stylesheet" type="text/css" />
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
    <!-- Script tags including React -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.1/react-dom.min.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>

<body>
    <div id="app"></div>
    <script type="text/babel">
    class Header extends React.Component {
        render() {
            return (
                <div className="header">
                    <div className="menuIcon">
                        <div className="dashTop"></div>
                        <div className="dashBottom"></div>
                        <div className="circle"></div>
                    </div>


                    <span className="title">
                    {/* Timeline */}
                        {this.props.title}
                    
                    </span>

                    <input
                        type="text"
                        className="searchInput"
                        placeholder="Search ..." />

                    <div className="fa fa-search searchIcon"></div>
                </div>
            )
        }
    }
    const moment1 = {
        timestamp:new Date().getTime(),
        text:"Ate lunch",
        user:{
            id: 1,
            name: 'Nate',
            avatar:"http://www.croop.cl/UI/twitter/images/doug.jpg"
        },
        comments: [
            {from:'Ari', text:'Me too!'}
        ]
    }
    class Content extends React.Component{
        render(){
            const {activities}= this.props; // ES6 destructuring
            return (
                <div className="content">
                    <div className="line"></div>
                {/* Timeline item */}
                {activities.map((activity) =>
                    return(
                        <ActivityItem 
                            activity = {activity}
                        />
                    ))}
                    {/* ... */}
            </div>

            )
        }
    } 
    class ActivityItem extends React.Component{
        const {activity} = this.props;//ES6 destructuring
        render(){
            return (
                <div className="item">
                        <div className = "avatar">
                            <img
                            alt={activity.text}
                            src={activity.user.avatar} />
                            {activity.user.name}
                        </div>

                        <span className = "time">
                        {activity.timestamp}
                        </span>

                        <p>{activity.text}</p>

                        <div className="commentCount">
                        {activity.comments.length}
                        </div>
                     </div>
            )
        }
    }
    //使用一个包含多个活动项的对象
    const activities = [
        {
            timestamp:new Date().getTime(),
            text:"Ate lunch",
            user:{
            id: 1,name: 'Nate',
            avatar:"http://www.croop.cl/UI/twitter/images/doug.jpg"
            },
        comments: [{from:'Ari', text:'Me too!'}]
        },
        {
            timestamp:new Date().getTime(),
            text:"Woke up early for a beautiful run",
            user:{
            id: 2,name: 'Ari',
            avatar:"http://www.croop.cl/UI/twitter/images/doug.jpg"
            },
        comments: [{ from: 'Nate', text: 'I am so jealous'}]
        },
    ]
      class App extends React.Component {
        render() {
             return (
                 <div className="notificationsFrame">
                    <div className="panel">
                     {/* content goes here */}
                        <Header  title="Timeline1"/>
                        <Header  title="Profile"/>
                        <Header  title="Settings"/>
                        <Header  title="Timeline1"/>
                        <Content activities = {activities}/>
                    </div>
                 </div>
             )
            }
        }
      var mount  = document.querySelector('#app');
      ReactDOM.render(<App />, mount);
  </script>
</body>

</html>

本周,我们使用React props概念更新了由数据驱动的组件。在下一节中,我们将深入研究有状态组件。

学习REACT正确的方法

React和朋友的最新,深入,完整的指南。

下载第一章

下一章:

状态(State)

https://blog.csdn.net/qq_35812380/article/details/83011062

本教程系列的完整源代码可以在GitHub repo找到,其中包括所有样式和代码示例。

如果您在任何时候感到困难,还有其他问题,请随时通过以下方式与我们联系:


 

学习构建真正的React应用程序

想了解如何在生产应用程序中使用React?点击下面的电子邮件,我们将通过电子邮件向您发送PDF文件,引导您在React中逐步构建应用程序 - 从空文件夹到工作应用程序。

名字

 

电子邮件地址

  发送我的PDF

没有垃圾邮件,很容易取消订阅。

 

资源

其他产品

保持联系

  • Morgan Pierson • 2 years ago

    I agree with Floyd. It seems like you kind of skip the webpack portion of setting up this react app, which results in a lack of any sort of visual confirmation that my code is correct.

    Reply

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值