SpringBoot + Vue 后台管理系统(四):管理页面

管理页面

主要就是mybatis-plus使用,以及前端表单验证。

MyBatis是一款非常热门的数据操作层(持久层)框架。

优点:

  • 自定义SQL ,满足所有的复杂查询,方便SQL优化。
  • 相对(JPA)来说上手简单一些。

mybatis-plus ?

Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。参考mybatis-plus官网。其实就是它已经封装好了一些crud方法,我们不需要再写xml了,直接调用这些方法就行,就类似于JPA。

效果图:

10879157-7bb6ca0ea09a317d.png
列表

10879157-03907090f2435bad.png
新增/修改

主要依赖:

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>2.3.1</version>
    </dependency>

直接在配置文件添加配置参数即可:

spring: 
   ...... 
  mybatis-plus: 
    global-config:
      #主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
      id-type: 0
      #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
      field-strategy: 2
      #驼峰下划线转换
      db-column-underline: true
      #刷新mapper 调试神器
      refresh-mapper: true
      #数据库大写下划线转换
      #capital-mode: true
      #序列接口实现类配置
      #key-generator: com.baomidou.springboot.xxx
      #逻辑删除配置
      #logic-delete-value: 0
      #logic-not-delete-value: 1
      #自定义填充策略接口实现
      #meta-object-handler: com.umeox.waas.domain.handler.MyMetaObjectHandler
      #自定义SQL注入器
      #sql-injector: com.baomidou.springboot.xxx
    configuration:
      map-underscore-to-camel-case: true  #entity类字段名映射表字段名
      cache-enabled: false

MyBatis-Plus使用:

public interface IService<T> {

    /**
     * <p>
     * 插入一条记录(选择字段,策略插入)
     * </p>
     *
     * @param entity 实体对象
     * @return boolean
     */
    boolean insert(T entity);

    /**
     * <p>
     * 插入一条记录(全部字段)
     * </p>
     *
     * @param entity 实体对象
     * @return boolean
     */
    boolean insertAllColumn(T entity);

    /**
     * <p>
     * 插入(批量),该方法不适合 Oracle
     * </p>
     *
     * @param entityList 实体对象列表
     * @return boolean
     */
    boolean insertBatch(List<T> entityList);

    /**
     * <p>
     * 插入(批量)
     * </p>
     *
     * @param entityList 实体对象列表
     * @param batchSize  插入批次数量
     * @return boolean
     */
    boolean insertBatch(List<T> entityList, int batchSize);

    /**
     * <p>
     * 批量修改插入
     * </p>
     *
     * @param entityList 实体对象列表
     * @return boolean
     */
    boolean insertOrUpdateBatch(List<T> entityList);

    /**
     * <p>
     * 批量修改插入
     * </p>
     *
     * @param entityList 实体对象列表
     * @param batchSize
     * @return boolean
     */
    boolean insertOrUpdateBatch(List<T> entityList, int batchSize);

    /**
     * <p>
     * 批量修改或插入全部字段
     * </p>
     *
     * @param entityList 实体对象列表
     * @return boolean
     */
    boolean insertOrUpdateAllColumnBatch(List<T> entityList);

    /**
     * 批量修改或插入全部字段
     *
     * @param entityList 实体对象列表
     * @param batchSize
     * @return boolean
     */
    boolean insertOrUpdateAllColumnBatch(List<T> entityList, int batchSize);

    /**
     * <p>
     * 根据 ID 删除
     * </p>
     *
     * @param id 主键ID
     * @return boolean
     */
    boolean deleteById(Serializable id);

    /**
     * <p>
     * 根据 columnMap 条件,删除记录
     * </p>
     *
     * @param columnMap 表字段 map 对象
     * @return boolean
     */
    boolean deleteByMap(Map<String, Object> columnMap);

    /**
     * <p>
     * 根据 entity 条件,删除记录
     * </p>
     *
     * @param wrapper 实体包装类 {@link Wrapper}
     * @return boolean
     */
    boolean delete(Wrapper<T> wrapper);

    /**
     * <p>
     * 删除(根据ID 批量删除)
     * </p>
     *
     * @param idList 主键ID列表
     * @return boolean
     */
    boolean deleteBatchIds(Collection<? extends Serializable> idList);

    /**
     * <p>
     * 根据 ID 选择修改
     * </p>
     *
     * @param entity 实体对象
     * @return boolean
     */
    boolean updateById(T entity);

    /**
     * <p>
     * 根据 ID 修改全部字段
     * </p>
     *
     * @param entity 实体对象
     * @return boolean
     */
    boolean updateAllColumnById(T entity);

    /**
     * <p>
     * 根据 whereEntity 条件,更新记录
     * </p>
     *
     * @param entity  实体对象
     * @param wrapper 实体包装类 {@link Wrapper}
     * @return boolean
     */
    boolean update(T entity, Wrapper<T> wrapper);

    /**
     * <p>
     * 根据 whereEntity 条件,自定义set值更新记录
     * </p>
     *
     * @param setStr  set值字符串
     * @param wrapper 实体包装类 {@link Wrapper}
     * @return boolean
     */
    boolean updateForSet(String setStr, Wrapper<T> wrapper);

    /**
     * <p>
     * 根据ID 批量更新
     * </p>
     *
     * @param entityList 实体对象列表
     * @return boolean
     */
    boolean updateBatchById(List<T> entityList);

    /**
     * <p>
     * 根据ID 批量更新
     * </p>
     *
     * @param entityList 实体对象列表
     * @param batchSize  更新批次数量
     * @return boolean
     */
    boolean updateBatchById(List<T> entityList, int batchSize);

    /**
     * <p>
     * 根据ID 批量更新全部字段
     * </p>
     *
     * @param entityList 实体对象列表
     * @return boolean
     */
    boolean updateAllColumnBatchById(List<T> entityList);

    /**
     * <p>
     * 根据ID 批量更新全部字段
     * </p>
     *
     * @param entityList 实体对象列表
     * @param batchSize  更新批次数量
     * @return boolean
     */
    boolean updateAllColumnBatchById(List<T> entityList, int batchSize);

    /**
     * <p>
     * TableId 注解存在更新记录,否插入一条记录
     * </p>
     *
     * @param entity 实体对象
     * @return boolean
     */
    boolean insertOrUpdate(T entity);

    /**
     * 插入或修改一条记录的全部字段
     *
     * @param entity 实体对象
     * @return boolean
     */
    boolean insertOrUpdateAllColumn(T entity);

    /**
     * <p>
     * 根据 ID 查询
     * </p>
     *
     * @param id 主键ID
     * @return T
     */
    T selectById(Serializable id);

    /**
     * <p>
     * 查询(根据ID 批量查询)
     * </p>
     *
     * @param idList 主键ID列表
     * @return List<T>
     */
    List<T> selectBatchIds(Collection<? extends Serializable> idList);

    /**
     * <p>
     * 查询(根据 columnMap 条件)
     * </p>
     *
     * @param columnMap 表字段 map 对象
     * @return List<T>
     */
    List<T> selectByMap(Map<String, Object> columnMap);

    /**
     * <p>
     * 根据 Wrapper,查询一条记录
     * </p>
     *
     * @param wrapper 实体对象
     * @return T
     */
    T selectOne(Wrapper<T> wrapper);

    /**
     * <p>
     * 根据 Wrapper,查询一条记录
     * </p>
     *
     * @param wrapper {@link Wrapper}
     * @return Map<String,Object>
     */
    Map<String, Object> selectMap(Wrapper<T> wrapper);

    /**
     * <p>
     * 根据 Wrapper,查询一条记录
     * </p>
     *
     * @param wrapper {@link Wrapper}
     * @return Object
     */
    Object selectObj(Wrapper<T> wrapper);

    /**
     * <p>
     * 根据 Wrapper 条件,查询总记录数
     * </p>
     *
     * @param wrapper 实体对象
     * @return int
     */
    int selectCount(Wrapper<T> wrapper);

    /**
     * <p>
     * 查询列表
     * </p>
     *
     * @param wrapper 实体包装类 {@link Wrapper}
     * @return
     */
    List<T> selectList(Wrapper<T> wrapper);

    /**
     * <p>
     * 翻页查询
     * </p>
     *
     * @param page 翻页对象
     * @return
     */
    Page<T> selectPage(Page<T> page);

    /**
     * <p>
     * 查询列表
     * </p>
     *
     * @param wrapper {@link Wrapper}
     * @return
     */
    List<Map<String, Object>> selectMaps(Wrapper<T> wrapper);

    /**
     * <p>
     * 根据 Wrapper 条件,查询全部记录
     * </p>
     *
     * @param wrapper 实体对象封装操作类(可以为 null)
     * @return List<Object>
     */
    List<Object> selectObjs(Wrapper<T> wrapper);

    /**
     * <p>
     * 翻页查询
     * </p>
     *
     * @param page    翻页对象
     * @param wrapper {@link Wrapper}
     * @return
     */
    @SuppressWarnings("rawtypes")
    Page<Map<String, Object>> selectMapsPage(Page page, Wrapper<T> wrapper);

    /**
     * <p>
     * 翻页查询
     * </p>
     *
     * @param page    翻页对象
     * @param wrapper 实体包装类 {@link Wrapper}
     * @return
     */
    Page<T> selectPage(Page<T> page, Wrapper<T> wrapper);

}

需要继承的基础接口,有默认的实现。(以菜单为例)

  • 新增操作单个或者数组批量新增。直接调用相应的方法即可

  • 修改, 根据ID修改。
    只修改一个字段,或者某几个字段。

    # 不为null的属性都会被修改。 反之如果不想修改的属性直接设置为null即可。
    Menu menu = new Menu();
    menu.setId(1);
    menu.setName("新的名称")
    menuRepository.updateById(menu);
    # 相当于SQL语句。update menu set name = "新的名称" where id = 1;
    

    不是根据ID修改的怎么处理呢?把所有的按钮设置为无效的。

    Menu menu = new Menu(); 
    menu.setStatus(false)
    udpate(menu, new EntityWrapper<Menu>().eq("type", 1));
    # 相当于SQL语句。update menu set status = false where type = 1;
    
  • 查询操作

    根据ID查询一个:
    Menu menu = this.selectById(1);
    根据条件查询一个
    Menu menu = selectOne(new EntityWrapper<Menu>()
                     .eq("type", 1).last("limit 1"));
    # last会造成SQL注入的风险。所以这个参数不能是外部传入。
    # lt("column",value)  ==>  column< value;
    # le("column",value)  ==>  column<= value;
    # gt("column",value)  ==>  column> value;
    # ge("column",value)  ==>  column>= value;
    # between(column, val1, val2) ==>  columnbetween val1 and val2;
    # like(column,value)  ==> like "%value%"; 
    # orderBy(column, ture) ==> order By column asc ;false:表示倒序
    # selectPage(page) ==> 分页查询
    # selectPage(page, new Enw...) ==> 分页查询在家查询条件。
    

当然也可以自己写SQL语句。多表关联查询时,需要自己定义SQL。
和mybatis使用方式一致。

前端表单校验

    <template>
      <div class="backdrop" v-loading="loading">
        <div v-show="isList">
          <el-row>
            <el-col :span="10" :offset="1">
              <el-input placeholder="请输入内容" clearable v-model="queryObj.value" class="input-with-select">
                <el-select v-model="queryObj.type" slot="prepend" placeholder="请选择">
                  <el-option label="名称" value="name"></el-option>
                  <el-option label="路径" value="path"></el-option>
                </el-select>
                <el-button slot="append" type="primary" icon="el-icon-search" @click="loadList"></el-button>
              </el-input>
            </el-col>
            <el-col :span="6">
              <el-button v-show="button.save" type="primary" @click="add" icon="el-icon-circle-plus-outline"
                         class="padding-button"></el-button>
              <el-popover v-show="button.delete" placement="top" width="200" v-model="visible2">
                <p>确定要删除这些菜单吗?</p>
                <br/>
                <div style="text-align: right; margin: 0">
                  <el-button size="mini" type="primary" class="padding-button" @click="visible2 = false">取消
                  </el-button>
                  <el-button size="mini" type="danger" class="padding-button" @click="remove">确定</el-button>
                </div>
                <el-button slot="reference" type="danger" @click="visible2 = true" icon="el-icon-delete"
                           class="padding-button"></el-button>
              </el-popover>
            </el-col>
          </el-row>
          <div class="margin-bottom-10"></div>
          <el-table :data="menuses" style="width: 100%" height="590" border
                    @sort-change="solrLoadMenusess"
                    @selection-change="electRow">
            <!--多选框-->
            <el-table-column type="selection" width="55"></el-table-column>
            <el-table-column sortable="custom" prop="id" label="ID" ></el-table-column>
            <el-table-column sortable="custom" prop="name" label="名称" ></el-table-column>
            <el-table-column sortable="custom" prop="url" label="链接" ></el-table-column>
            <el-table-column sortable="custom" prop="type" label="类型" >
              <template slot-scope="scope">
                <el-tag type="success" size="medium" v-if="scope.row.type == 1">菜单</el-tag>
                <el-tag type="info" size="medium" v-if="scope.row.type == 2">按钮</el-tag>
              </template>
            </el-table-column>
            <el-table-column sortable="custom" prop="permission" label="权限" ></el-table-column>
            <el-table-column label="操作" fixed="right" width="180">
              <template slot-scope="scope">
                <el-tooltip v-show="button.update" class="item" effect="dark" content="修改菜单" placement="top">
                  <el-button size="mini" type="primary" @click="update(scope.$index, scope.row)"
                             icon="el-icon-edit" circle></el-button>
                </el-tooltip>
                <el-tooltip v-show="button.delete" class="item" effect="dark" content="删除菜单" placement="top">
                  <el-button size="mini" type="danger" @click="closeMenu(scope.$index, scope.row)"
                             icon="el-icon-delete" circle></el-button>
                </el-tooltip>
              </template>
            </el-table-column>
          </el-table>
          <el-pagination
              @size-change="pageSizeChange"
              @current-change="currentPageChange"
              :current-page="queryObj.currentPage"
              :page-sizes="[10, 20, 30, 40]"
              :page-size="queryObj.pageSize"
              layout="total, sizes, prev, pager, next, jumper"
              :total="queryObj.total">
          </el-pagination>
        </div>

        <div v-show="!isList">
          <el-form ref="menu" :model="menu" :rules="rules" label-width="80px" label-position="right"
                   class="demo-ruleForm" size="mini">
            <el-form-item prop="name" label="菜单名称">
              <el-col :span="10">
                <el-input v-model="menu.name"></el-input>
              </el-col>
            </el-form-item>
            <el-form-item prop="type" label="类型">
              <el-col :span="3">
                <el-select v-model="menu.type" placeholder="请选择类型">
                  <el-option label="目录" value="0"></el-option>
                  <el-option label="菜单" value="1"></el-option>
                  <el-option label="按钮" value="2"></el-option>
                </el-select>
              </el-col>
            </el-form-item>
            <el-form-item prop="path" label="菜单路由" v-if="menu.type == 1">
              <el-col :span="10">
                <el-input v-model="menu.path"></el-input>
              </el-col>
            </el-form-item>
            <el-form-item label="父级菜单">
              <el-col :span="10">
                <el-input v-model="menu.parentName" readonly @focus="dialogFormVisible = true"></el-input>
                <el-dialog title="选择父级菜单" width="30%"
                           :close-on-click-modal="false"
                           :close-on-press-escape="false"
                           :show-close="false"
                           :visible.sync="dialogFormVisible">
                  <el-tree
                      :data="this.buildMenus()"
                      @node-click="setParentId"
                      node-key="id"
                      :accordion=true
                      :highlight-current=true
                      :default-expanded-keys=[menu.parentId]
                      :current-node-key=menu.parentId
                      :props="{children: 'childs', label: 'name'}">
                  </el-tree>
                  <div slot="footer" class="dialog-footer">
                    <el-button @click="menu.parentId=0;menu.parentName='';dialogFormVisible = false;">取消
                    </el-button>
                    <el-button type="primary" @click="dialogFormVisible = false">确定</el-button>
                  </div>
                </el-dialog>
              </el-col>
            </el-form-item>
            <el-form-item prop="permission" label="菜单权限">
              <el-col :span="10">
                <el-input v-model="menu.permission"></el-input>
              </el-col>
            </el-form-item>
            <el-form-item label="图标">
              <el-col :span="10">
                <el-input v-model="menu.icon"></el-input>
              </el-col>
            </el-form-item>
            <el-form-item>
              <el-col :span="10">
                <el-button type="primary" @click="submitMenu('menu')">提交</el-button>
                <el-button @click="notSubmitMenu('menu')">取消</el-button>
              </el-col>
            </el-form-item>
          </el-form>
        </div>
      </div>
    </template>

    <script lang="ts">
      import {Component, Prop, Vue} from 'vue-property-decorator';
      import {Menu} from "@/entity/Menu";
      import {Query} from "@/utils/Query";
      import {StringUtils} from  "@/utils/StringUtils"
      @Component({})
      export default class MenuMana extends Vue {
        # 表单校验规则
        rules: object = {
            name: [ {required: true, message: '请输入菜单名称', trigger: 'blur'},
                {min:2, max:5, message:"限制2~5个字符", trigger: 'blur'}
            ],
            type: [{ required: true, message: '请选择类型', trigger: 'change' }],
            permission: [{ validator: (rule: any, value: any, callback: any)=> {
                let _this:any = this.$refs["menu"];
                if(_this.model.type == "2"){
                  if (value === "") {
                    callback(new Error("请输入权限"));
                  }else {
                    callback();
                  }
                }else {
                  callback();
                }
              }, trigger: 'change' }],
            url: [{ required: true, message: '请输入菜单路由', trigger: 'blur' }]
        }

        loading: boolean = false;
        isList: boolean = true;
        visible2: boolean = false;
        dialogFormVisible: boolean = false;
        menuses: Array<Menu> = [];
        electMenus: Array<Menu> = [];
        allMenus: Array<Menu> = [];
        // 初始化菜单信息
        menu: Menu = new Menu("", "1", "", "", "0", "");
        button: object = {
          save: StringUtils.isPermisson('sys:menu:save,sys:menu:select'),
          update: StringUtils.isPermisson('sys:menu:update,sys:menu:select'),
          delete: StringUtils.isPermisson('sys:menu:delete')
        }
        queryObj: Query = new Query();

        mounted():void{
          this.loadList();
          this.allMenu().then(resp=>{
            console.log("111111111")
          });
        }
        // 加载列表数据
        loadList():void {
          const _this = this;
          _this.loading = true;
          _this.axios.get("/menu/list",{params: _this.queryObj})
                  .then(resp=> {
                    _this.loading = false;
                    _this.menuses = resp.data.records;
                    _this.queryObj.total = resp.data.total;
                  })
        }

        // 切换搜索条件
        solrLoadMenusess(val: any):void{
          this.queryObj.isAsc = val.order == "ascending";
          this.queryObj.orderFields = val.prop;
          this.loadList();
        }
        // 切换每页数量
        pageSizeChange(val: any):void{
          this.queryObj.pageSize = val;
          this.loadList();
        }
        // 切换页面数
        currentPageChange(val:any):void{
          this.queryObj.currentPage = val;
          this.loadList();
        }
        // 选中的值
        electRow(val:any):void{
          this.electMenus = val;
        }
        // ——

        // 新增菜单
        add():void{
          this.isList = false;
          this.menu = new Menu("", "1", "", "", "0", "");
        }
        // 修改菜单
        update(index:number, row: Menu):void{
          this.isList = false;
          this.menu = new Menu("", "1", "", "", "0", "");
          this.menu.id = row.id;
          this.menu.name = row.name;
          this.menu.parentId = row.parentId + "";
          this.menu.url = row.url;
          this.menu.permission = row.permission;
          this.menu.type = row.type + "";
          this.menu.icon = row.icon;
          this.setMenuParentName();
        }

        // 设置当前菜单的父级菜单名字
        setMenuParentName():void{
          let parentName = "";
          let id = this.menu.parentId;
          this.allMenus.forEach(ele => {
            if (ele.id.toString() == id) {
              parentName = ele.parentName;
            }else if (ele.childs != null) {
              ele.childs.forEach(child=>{
                if (child.id.toString() == id) {
                  parentName = child.parentName;
                }
              })
            }
          })
          this.menu.parentName = parentName;
        }

        // 批量删除
        remove():void{
          let _this = this;
          _this.visible2 = false;
          if (this.electMenus.length == 0) {
            _this.$message.error("请选择需要删除的菜单");
            return;
          }
          let arr = new Array<number>();
          _this.axios.delete("/menu/delete",{data:arr})
                  .then(resp=>{
                    localStorage.removeItem("allMenus");
                    _this.$message.success("删除成功了!");
                    if(_this.electMenus.length >= _this.menuses.length && _this.queryObj.currentPage > 1){
                      _this.queryObj.currentPage = _this.queryObj.currentPage - 1;
                    }
                    _this.loadList();
                  })
        }
        // 删除单个
        closeMenu(idnex:number, row: any):void{
          let _this = this;
          _this.$alert("确认删除这个菜单吗?", "友情提示", {
            confirmButtonText: "确认",
            callback: action => {
              _this.axios.delete("/menu/delete",{data:[row.id]})
                      .then(resp=>{
                        localStorage.removeItem("allMenus");
                        _this.$message.success("删除成功了!");
                        if(_this.menuses.length <= 1 && _this.queryObj.currentPage > 1){
                          _this.queryObj.currentPage = _this.queryObj.currentPage - 1;
                        }
                        _this.loadList();
                      })
            }
          })
        }
        // 提交
        submitMenu(menu: string):void{
          let _this = this;
          let el: any = this.$refs[menu];
          el.validate((valid: any) => {
            if (valid) {
              _this.loading = true;
              if (_this.menu.id) {
                _this.axios.put("/menu/update",_this.menu)
                        .then((resp: any) => {
                          _this.loading = false;
                          if (resp.code == 0){
                            _this.loading = false;
                            localStorage.removeItem("allMenus");
                            _this.$message.success("修改成功了!");
                            _this.loadList();
                          }
                        })
              }else{
                _this.axios.post("/menu/save",_this.menu)
                        .then((resp: any)=>{
                          _this.loading = false;
                          if(resp.code == 0) {
                            _this.isList = true;
                            localStorage.removeItem("allMenus");
                            _this.$message.success("添加成功了!")
                            _this.loadList();
                          };
                        })
              }
            }else{
              this.$message.error("错误的提交");
              return;
            }
          })
        }
        // 不提交菜单
        notSubmitMenu(menu:string):void{
          this.isList = true;
          this.menu = new Menu("", "1", "", "", "0", "");
        }
        // 全部菜单
        allMenu():Promise<any>{
          let _this = this;
          return new Promise(function (resolve, reject) {
            let allMenus = localStorage.getItem("allMenus");
            if (allMenus!=null && StringUtils.isNotBlank(allMenus)) {
              _this.allMenus = JSON.parse(allMenus.toString());
              if (_this.allMenus.length != 0) {
                resolve(_this.allMenus);
              }
            }else{
              _this.axios.get("/menu/select")
                      .then((resp: any)=>{
                        _this.allMenus = resp.data;
                        localStorage.setItem("allMenus", JSON.stringify(resp.data));
                        resolve(_this.allMenus);
                      })
            }
          });
        }
        // 处理菜单
        buildMenus(): Array<Menu> {
          let _this = this;
          let arr = new Array<Menu>();
          arr = _this.allMenus;
          /*arr.forEach(elv=>{
            if(elv.childs){
              elv.childs.forEach((child:Menu)=>{
                child.childs = new Array<Menu>();
              })
            }
          })*/
          return arr;
        }
        // 选择父级菜单时,赋值
        setParentId(data: Menu,node: any, eml:any): void{
          this.menu.parentId = data.id.toString();
          this.menu.parentName = data.name;
        }
      }
    </script>

    <style scoped>
      .el-col{
        margin-bottom: 10px;
        margin-top: 10px;
      }
    </style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值