Springboot——restful实践前端 (ajax)

Springboot——restful实践前端 (ajax)

前言

项目截图

在这里插入图片描述

参考程序

  • controller类
package com.antrain.restful.controller;

import com.antrain.restful.entity.User;
import com.antrain.restful.service.UserService;
import com.antrain.restful.util.PageResult;
import com.antrain.restful.util.PageUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Map;

@Controller
@RequestMapping("/adminAjax")
public class UserController2 {

    @Resource
    private UserService userService2;

    @GetMapping("/userPage")
    public String list(){
        return "admin/userManager";
    }
	//@ResponseBody 作用是将结果以json形式传送给前端
    @ResponseBody
    @GetMapping("/users")
    public PageResult userList(@RequestParam Map<String, Object> params){
        PageResult pageResult = userService2.queryUsers(new PageUtil(params));
        return pageResult;
    }

    public boolean valid(User user) {
        return user==null||user.getId()==null||user.getUsername() == null ||
                user.getPassword() == null || user.getBirth() == null || user.getSex() == null;

    }

    @ResponseBody
    @PostMapping("/user")
    public String add(@RequestBody User user){
    	// @RequestBody 从请求体拿数据,不加出错
       return String.valueOf(userService2.insert(user));
    }

    @ResponseBody
    @GetMapping("/user/{id}")
    public User toEdit(@PathVariable("id") Integer id){
        return userService2.queryById(id);
    }

    @ResponseBody
    @PutMapping("/user")
    public int update(@RequestBody User user){
        //System.out.println(user);
       return userService2.update(user);
    }

    @ResponseBody
    @DeleteMapping("/user/{id}")
    public int del(@PathVariable("id") Integer id){
        return userService2.del(id);
    }
}
  • 片段 bar.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="header-fragment">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Restful测试</title>
    <link th:href="@{/static/css/bootstrap.min.css}" rel="stylesheet">
    <link th:href="@{/static/css/dashboard.css}" rel="stylesheet">
    <link th:href="@{/static/css/bootstrap-table.min.css}" rel="stylesheet">
    <link th:href="@{/static/css/font-awesome.min.css}" rel="stylesheet" th:if="${select!=null} and ${select=='Y'}">
    <link th:href="@{/static/css/animate.min.css}" rel="stylesheet" th:if="${select!=null} and ${select=='Y'}">
    <link th:href="@{/static/css/style.css}" rel="stylesheet" th:if="${select!=null} and ${select=='Y'}">
    <base target="_blank" th:if="${select!=null} and ${select=='Y'}">
</head>
<body>
<!--topbar-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
    <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
    <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
</nav>

<!--sidebar-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
    <div class="sidebar-sticky">
        <ul class="nav flex-column">
            <li class="nav-item">
                <a class="nav-link active"
                   th:class="${activeUri=='index.html'?'nav-link active':'nav-link'}" th:href="@{/index.html}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
                        <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
                        <polyline points="9 22 9 12 15 12 15 22"></polyline>
                    </svg>
                    Dashboard <span class="sr-only">(current)</span>
                </a>
            </li>


            <li class="nav-item">
                <a class="nav-link active" href="#" th:href="@{/admin/users}"
                   th:class="${activeUri=='users'?'nav-link active':'nav-link'}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
                        <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
                        <circle cx="9" cy="7" r="4"></circle>
                        <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
                        <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
                    </svg>
                    用户管理
                </a>
            </li>


            <li class="nav-item">
                <a class="nav-link active"  th:href="@{/adminAjax/userPage}"
                   th:class="${activeUri=='userss'?'nav-link active':'nav-link'}">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
                        <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
                        <circle cx="9" cy="7" r="4"></circle>
                        <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
                        <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
                    </svg>
                    用户管理2
                </a>
            </li>

        </ul>

    </div>
</nav>
<div th:fragment="footer-js">
    <script type="text/javascript" th:src="@{/static/js/jquery.min.js}"></script>
    <script type="text/javascript" th:src="@{/static/js/popper.min.js}" th:if="${select!=null} and ${select=='Y'}"></script>
   <script type="text/javascript" th:src="@{/static/js/bootstrap.min.js}"></script>

    <script type="text/javascript" th:src="@{/static/js/bootstrap-table.min.js}" th:if="${select!=null} and ${select=='Y'}"></script>
    <script type="text/javascript" th:src="@{/static/js/bootstrap-table-zh-CN.min.js}" th:if="${select!=null} and ${select=='Y'}"></script>
    <script type="text/javascript" th:src="@{/static/js/userManager.js}"  th:if="${select!=null} and ${select=='Y'}"></script>
</div>
</body>
</html>
  • html界面 userManager.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="commons/bar::header-fragment(select='Y')"></head>
<body>
<div th:replace="commons/bar::topbar"></div>

<div class="container-fluid">
    <div class="row">
        <!--引入侧边栏-->
        <div th:replace="commons/bar::#sidebar(activeUri='userss')"></div>
        <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
            <div class="wrapper wrapper-content animated fadeInRight">
                <div class="ibox float-e-margins">
                    <div class="ibox-content">
                        <p>专项管理</p>
                        <p>
                            <button class="btn btn-primary btn-add" type="button">
                                <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>&nbsp;新增
                            </button>
                        </p>
                        <div class="bootstrap-table">
                            <table id="tb_users" data-mobile-responsive="true"></table>
                        </div>

                    </div>
                </div>
            </div>
        </main>
    </div>
</div>
<div class="content">
    <!-- 模态框(Modal) -->
    <div class="modal fade" id="modalEdit" tabindex="-1" role="dialog" aria-labelledby="modalAddLabel">
        <div class="modal-dialog modal-dialog-centered" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h3 class="modal-title" id="modalTitle">新用户添加</h3>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    <form>
                        <div class="form-group" id="idDiv">
                            <label for="userName" class="control-label">用户ID:</label>
                            <input type="text" class="form-control" id="userOfId" autocomplete="off" name="id" >
                        </div>
                        <div class="form-group">
                            <label for="userName" class="control-label">用户名:</label>
                            <input type="text" class="form-control" id="username" autocomplete="off" name="username" value="antRain">
                        </div>
                        <div class="form-group">
                            <label for="password" class="control-label">密码:</label>
                            <input type="text" class="form-control" id="password" name="password" value="123456">
                        </div>
                        <div class="form-group">
                            <label>性别</label><br/>
                            <div class="form-check form-check-inline">
                                <input class="form-check-input" type="radio" name="sex" value="M" id="sexM">
                                <label for="sexM" class="form-check-label"></label>
                            </div>
                            <div class="form-check form-check-inline">
                                <input class="form-check-input" type="radio" name="sex" value="F" id="sexF">
                                <label for="sexF" class="form-check-label"></label>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="birth" class="form-check-label">出生日期</label>
                            <input name="birth" type="date" class="form-control" id="birth">
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
                    <button type="button" class="btn btn-primary" id="saveButton">确认</button>
                </div>
            </div>
        </div>
    </div>
    <!-- /.modal -->
</div>


<div th:replace="commons/bar::footer-js(select='Y')"></div>
</body>

</html>
  • js: userManager.js
$('#tb_users').bootstrapTable({
    url: "http://localhost:8080/adminAjax/users", // 请求的后台URL(*)
    method: 'get', // 请求方式:get/post(*)
    toolbar: '#toolbar', // 工具按钮用哪个容器
    cache: false, // 是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)
    showRefresh: false, // 是否显示刷新按钮
    sortable: false, // 是否启用排序
    sortOrder: "asc", // 排序方式
    minimumCountColumns: 2, // 最少允许的列数
    showToggle: false, // 是否显示详细视图和列表视图的切换按钮
    showColumns: false, // 是否显示列操作按钮
    detailView: false, // 是否显示详细视图
    striped: true, // 设置为true会有隔行变色效果
    dataType: "json", // 服务器返回的数据类型
    pagination: true, // 设置为true会在底部显示分页条
    // queryParamsType : "limit",
    // 设置为limit则会发送符合RESTFull格式的参数
    singleSelect: true, // 设置为true将禁止多选
    clickToSelect: true, // 是否启用点击选中行
    cardView: false, // 是否显示详细视图
    paginationLoop: true,
    maintainSelected: true,
    // contentType : "application/x-www-form-urlencoded",
    // 发送到服务器的数据编码类型
    pageSize: 2, // 如果设置了分页,每页数据条数
    pageList: [2, 5, 10], // 可供选择的每页的行数(*)

    pageNumber: 1, // 如果设置了分布,首页页码
    search: false, // 是否显示搜索框
    uniqueId: "id", // 每一行的唯一标识,一般为主键列
    sidePagination: "server", // 设置在哪里进行分页,可选值为"client" 或者 "server"

    queryParams: function(params) {
        return {
            // 说明:传入后台的参数包括offset开始索引,limit步长,sort排序列,order:desc或者,以及所有列的键值对
            limit: params.limit,
            pageSize: params.pageSize,
            offset: params.offset,
        };
    },
    responseHandler: function(res) {
        //console.log(res);
        return { //return bootstrap-table能处理的数据格式
            "total": res.totalCount,
            "rows": res.list
        }
    },
    // 请求服务器数据时,你可以通过重写参数的方式添加一些额外的参数,例如 toolbar 中的参数 如果
    // queryParamsType = 'limit' ,返回参数必须包含
    // limit, offset, search, sort, order 否则, 需要包含:
    // pageSize, pageNumber, searchText, sortName,
    // sortOrder.
    // 返回false将会终止请求
    columns: [{
        title: '序号',
        field: 'id',
        align: 'left',
        valign: 'center',
        width: '10%',
        formatter: function(value, row, index) {
            return index + 1;
        }

    }, {
        title: '用户ID',
        field: 'id',
        align: 'left',
        valign: 'center',

    }, {
        title: '用户姓名',
        field: 'username',
        align: 'center',
        valign: 'center',
        cellStyle: function(value, row, index) {
            return {
                css: {
                    "word-wrap": "break-word",
                    "word-break": "normal"
                }
            };
        }

    },{
        title: '性别',
        field: 'sex',
        align: 'center',
        valign: 'center',
        formatter: function (value, row, index) {
            return value=='M'?'男':'女';
        } //格式化日期
    },  {
        title: '出生日期',
        field: 'birth',
        align: 'center',
        valign: 'center',
        // formatter: function (value, row, index) {
        //  return ;
        // } //格式化日期
    },{
        title: 'Item Operate',
        align: 'center',
        field: 'id',
        clickToSelect: false,
        formatter:  function operateFormatter(value, row, index) {
            var btn = '<button type="button" id="delUser" class="btn btn-outline-danger" userId='
                + value
                + ' οnclick="delUser(this)">删除</button><button type="button" id="dupUser" class="btn btn-outline-info" ' +
                ' οnclick="updUser('+value + ')">修改</button>'
            return btn;
        }
    }]
});

// 删除用户
function delUser(dom) {
    var message = confirm("确认删除嘛?");
    if (message == true) {
        $.ajax({
            url : '/adminAjax/user/' + $(dom).attr("userId"),
            type : 'delete',
            success : function(data) {
                alert("删除成功");
                $('#tb_users').bootstrapTable('refresh');//刷新表格
            },
            error : function(data){
                alert("服务器繁忙")
            }
        });
    }
}

// 编辑用户
function updUser(id) {
    $("#modalTitle").text("编辑");
    $.ajax({
        url : '/adminAjax/user/' +id,
        type : 'get',
        success : function(data) {
            $('#userOfId').show().attr('readonly',"readonly").val(data.id);
            $("#username").val(data.username);
            $('#password').attr('hidden','hidden').val(data.password);
            if(data.sex=='M'){
                $('#sexM').attr("checked","checked");
            } else {
                $('#sexF').attr("checked","checked");
            }
            $('#birth').val(data.birth);
            $("#modalEdit").modal("show");
        },
        error : function(data){
            alert("服务器繁忙")
        }
    });
    //console.log("测试");
}

//"新增"按钮
$(".btn-add").click(function() {
    $("#modalTitle").text("添加新用户");
    $('#idDiv').hide();
    $("#username").val("antRain");
    $('#password').val("123456").removeAttr('hidden');
    $('#sexM').attr("checked","checked");
    $('#birth').val('1999-02-23');
    $("#modalEdit").modal("show"); //利用bootstrap 的model 
});


$("#saveButton").click(function () {
    var modalTitle = $("#modalTitle").html();
    var method ="post";
    var username = $('#username').val();
    var password = $('#password').val();
    var id = $('#userOfId').val();
    var sex =$('input[name="sex"]:checked').val();
    var birth = $("#birth").val();
    var user ={
        "username":username,
        "password":password,
        "sex":sex,
        "birth":birth
    };
    if(modalTitle=="编辑"){
        method = 'put';
        user.id = id;
    }
    $.ajax({
        url : '/adminAjax/user',
        type : method,
        contentType: "application/json; charset=utf-8",
        data: JSON.stringify(user),//把 JavaScript 对象转换为字符串。
        success : function(data) {
            $('#tb_users').bootstrapTable('refresh');//刷新表格
        },
        error : function(data){
            alert("服务器繁忙")
        }
    });
    $("#modalEdit").modal("hide");
});


问题日志

  • Malformed markup: Attribute “class” appears more than once in element
<input id="zxName" name="zxName" class="form-control " type="text"
 aria-required="true" aria-invalid="false" class="valid">
在thymeleaf 标签不能有两个class
  • 片段选取
<head th:replace="commons/bar::#header-fragment(select='Y')"></head>
<head th:fragment="header-fragment">
:: 后面可以通过css选择器选取
  • a.ajax is not a function
    版本不对

  • bootstraptable json数据加载不出来
    json格式

{
    "totalCount": 13,
    "pageSize": 10,
    "totalPage": 2,
    "currPage": 0,
    "list": [{
        "id": 1,
        "username": "cogles0",
        "password": "g9saZ4BJ9Xn",
        "sex": "M",
        "birth": "2009-06-11"
    }, {
        "id": 2,
        "username": "kboor1",
        "password": "QxTltEzxz",
        "sex": "F",
        "birth": "2005-06-23"
    }]
}

解决:添加方法

 responseHandler: function(res) {
                    console.log(res);
                    return { //return bootstrap-table能处理的数据格式
                        "total": res.totalCount,
                        "rows": res.list
                    }
                },

或者最好在服务端传送数据格式

return Json("{\"total\":数据条数,\"rows\":显示的数据}")
  • bootstrap.min.js:6 Uncaught TypeError: n is not a constructor
    at a.l.toggle (bootstrap.min.js:6)
    解决参考博客

学习过程

  • input:radio 利用jQuery选中
<body>
    <input type="radio" name="sex" id="sexM">
    <input type="radio" name="sex" id="sexF">
    <script src="plugins/jquery/jquery.min.js"></script>
    <script>
        console.log("before");
        $("#sexM").attr("checked", "checked");
        //$("#sexM").attr("checked");不行
        //<input type="radio" name="sex" id="sexM" checked> 这个是可以,有默认值
        console.log("after");
    </script>
</body>
  • bootstraptable 学习调试代码
<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width" />
    <title>BootstrapTable分页</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
    <link rel="stylesheet" href="https://unpkg.com/@fortawesome/fontawesome-free@5.12.1/css/all.min.css">
    <link href="https://unpkg.com/bootstrap-table@1.16.0/dist/bootstrap-table.min.css" rel="stylesheet">
</head>

<body>

    <style>
        .like {
            margin-right: 10px;
        }
    </style>



    <div id="toolbar">
        <button id="remove" class="btn btn-danger" disabled>
          <i class="glyphicon glyphicon-remove"></i> Delete
        </button>
    </div>
    <table id="table" data-toolbar="#toolbar" data-search="true" data-show-refresh="true" data-show-toggle="true" data-show-fullscreen="true" data-show-columns="true" data-show-columns-toggle-all="true" data-detail-view="true" data-show-export="true" data-click-to-select="true"
        data-detail-formatter="detailFormatter" data-minimum-count-columns="2" data-show-pagination-switch="true" data-pagination="true" data-id-field="id" data-page-list="[10, 25, 50, 100, all]" data-show-footer="true" data-side-pagination="server" data-url="https://examples.wenzhixin.net.cn/examples/bootstrap_table/data"
        data-response-handler="responseHandler">
    </table>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
    <script src="https://unpkg.com/tableexport.jquery.plugin/tableExport.min.js"></script>
    <script src="https://unpkg.com/bootstrap-table@1.16.0/dist/bootstrap-table.min.js"></script>
    <script src="https://unpkg.com/bootstrap-table@1.16.0/dist/locale/bootstrap-table-zh-CN.min.js"></script>
    <script src="https://unpkg.com/bootstrap-table@1.16.0/dist/extensions/export/bootstrap-table-export.min.js"></script>
    <script>
        var $table = $('#table')
        var $remove = $('#remove')
        var selections = []

        function getIdSelections() {
            return $.map($table.bootstrapTable('getSelections'), function(row) {
                return row.id
            })
        }

        function responseHandler(res) {
            $.each(res.rows, function(i, row) {
                row.state = $.inArray(row.id, selections) !== -1
            })
            return res
        }

        function detailFormatter(index, row) {
            var html = []
            $.each(row, function(key, value) {
                html.push('<p><b>' + key + ':</b> ' + value + '</p>')
            })
            return html.join('')
        }

        function operateFormatter(value, row, index) {
            return [
                '<a class="like" href="javascript:void(0)" title="Like">',
                '<i class="fa fa-heart"></i>',
                '</a>  ',
                '<a class="remove" href="javascript:void(0)" title="Remove">',
                '<i class="fa fa-trash"></i>',
                '</a>'
            ].join('')
        }

        window.operateEvents = {
            'click .like': function(e, value, row, index) {
                alert('You click like action, row: ' + JSON.stringify(row))
            },
            'click .remove': function(e, value, row, index) {
                $table.bootstrapTable('remove', {
                    field: 'id',
                    values: [row.id]
                })
            }
        }

        function totalTextFormatter(data) {
            return 'Total'
        }

        function totalNameFormatter(data) {
            return data.length
        }

        function totalPriceFormatter(data) {
            var field = this.field
            return '$' + data.map(function(row) {
                return +row[field].substring(1)
            }).reduce(function(sum, i) {
                return sum + i
            }, 0)
        }

        function initTable() {
            $table.bootstrapTable('destroy').bootstrapTable({
                height: 550,
                locale: $('#locale').val(),
                columns: [
                    [{
                        field: 'state',
                        checkbox: true,
                        rowspan: 2,
                        align: 'center',
                        valign: 'middle'
                    }, {
                        title: 'Item ID',
                        field: 'id',
                        rowspan: 2,
                        align: 'center',
                        valign: 'middle',
                        sortable: true,
                        footerFormatter: totalTextFormatter
                    }, {
                        title: 'Item Detail',
                        colspan: 3,
                        align: 'center'
                    }],
                    [{
                        field: 'name',
                        title: 'Item Name',
                        sortable: true,
                        footerFormatter: totalNameFormatter,
                        align: 'center'
                    }, {
                        field: 'price',
                        title: 'Item Price',
                        sortable: true,
                        align: 'center',
                        footerFormatter: totalPriceFormatter
                    }, {
                        field: 'operate',
                        title: 'Item Operate',
                        align: 'center',
                        clickToSelect: false,
                        events: window.operateEvents,
                        formatter: operateFormatter
                    }]
                ]
            })
            $table.on('check.bs.table uncheck.bs.table ' +
                'check-all.bs.table uncheck-all.bs.table',
                function() {
                    $remove.prop('disabled', !$table.bootstrapTable('getSelections').length)

                    // save your data, here just save the current page
                    selections = getIdSelections()
                        // push or splice the selections if you want to save all data selections
                })
            $table.on('all.bs.table', function(e, name, args) {
                console.log(name, args)
            })
            $remove.click(function() {
                var ids = getIdSelections()
                $table.bootstrapTable('remove', {
                    field: 'id',
                    values: ids
                })
                $remove.prop('disabled', true)
            })
        }

        $(function() {
            initTable()
        })
    </script>


</body>

</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值