React后台管理前端系统(基于开源框架开发)起步式

前端技术 同时被 2 个专栏收录
89 篇文章 1 订阅
224 篇文章 1 订阅

写博客不容易,如果对你有帮助请点个赞吧。有时间的可以评论一下。

       这个系统的搭建背景是这样的,有一个朋友想看到现有系统中的一些,用户数据,新闻数据,只需要看到,短期不需要增删改功能,让我搭建一个简单的后台系统给他看.接到任务作为一个有四年开发经验的人来说这也太简单了吧,开始干吧,最简单的办法是直接使用开源项目https://github.com/PanJiaChen/vue-element-admin 下载下来,把不要的路由去掉,新建几个表格路由,页面直接复制过来就用了.

      这样算下来,整个系统搭建时间不会超过半天,而且这个开源框架我已经玩的很溜了,后期开发完全不费事.但是,这个方案被我拒绝了。为什么那?因为我想跳出舒适区.接受更大的挑战,目前和vue并驾齐驱的React项目也很流程,很多公司也在使用.于是乎,技术栈就是用React了。但是要从零搭建,时间上划不来,因为需要集成很多东西,基础建设就要耗费很长时间.于是乎在GitHub上一顿搜,Ok 找到了二个比较可靠的项目使用,一个是 ant-design-pro 另一个是antd-admin 二个项目大同小异, 使用的技术栈reactant-designdvaMock  基于 Antd UI 设计语言  虽然我对dva和ant-design 了解不深,但我还是准备挑战一下.不入虎穴焉得虎子.

  一开始我选择的是antd-admin 因为代码比较规范,UI也比较符合我的审美.开始做吧

 首先找到一个列表页,仔细阅读代码,弄清楚依赖关系,然后新建一个目录,把列表页的相关文件都拷贝进去, 配路由,设置权限.

但是在调取接口的时候,接口虽然调取了,但是页面没有刷新出来,怎么调试都不出来,急死了.最后只好需求同事的帮助,但是他们也很少有人用React.就这样过了一天,我决定先把问题放一下, 问题的答案肯定就在代码里,先休息一下,别被这个问题让自己的眼光太局限,导致看不到问题的本质.在这个问题还没有解决的情况下,我又开始了另一个项目 ant-design-pro的尝试.因为我知道,那个页面没有显示数据的问题,我肯定会解决的,只是时间问题,当我解决了,肯定会觉得自己当时太笨了,后来的进展证明确实是这样.

  在数据不显示的问题上短暂停留下,我开始探索ant-design-pro

  这里我来说一下,当一个菜鸟接手一个新项目时遇到的迷茫和问题。

 首先项目开启我们会看到页面的URL,URL与我们的页面是一一对应的,意思就是我们根据URL可以再项目中准确地找到对应的页面,或页面入口.当然页面可能会一个主页面,多个子页面.这里忘记说一点,URL对应页面 这种对应关系的配置,叫做路由.

看到URL,就能找到路由,就能找到页面, 找到页面就完成了项目熟悉的第一步, 到了这一步,你就能自己写个静态页面,自己配个路由就能在浏览器看到了. 是不是很简单.这是大致的思路.不过有的框架有权限拦截,新的页面路由可能需要给登录的人赋权限才能出现. 等下我会以具体页面案例来做个演示.

第一步已经学会了,接下来就是进入到页面内,看看页面的数据是怎么流转的,事件,参数是怎么配置的.这一块是比较难的,也是熟悉前端项目的核心知识点.敲重点,重点,重点 一般一个页面,都会有初始化函数, 比如一个vue组件会在mounted状态下调取获取数据的接口,来渲染页面.React会在componentDidMount生命周期调取获取数据的接口. 每个页面或组件都有可能是两个或更多个组件,组合而来的,而组件的参数也是错综复杂,组件的表现,事件都是有这些参数控制的.除了简单理解这些参数,还需要理解这些组件是怎么组合起来的.

说也说了够多的 下面我就用一个列表页来给你看一下 我是怎么了解一个前端项目和开发的

我以ant-design-pro的查询表格页面为例子

我们拿着/list/table-list这个路由去项目中搜索 

打开这个文件 

很明显这个文件是路由管理  这里清楚的写着 /list/table-list路由指向文件../routes/List/TableList

其中代码中的

dynamicWrapper(app, ['rule'], ()

我们暂时不需要去理解他的意思,因为我们的第一步是根据路由找到对应的页面. 而不是去理解每一行代码.

打开../routes/List/TableList我们看到

果然是我们要寻找的页面, 改一二个文字 刷新一下页面,果然改变了.好开心.第一步就这样完了.

必须要提的全局搜索是一个非常很好的技能,大家一定要学会.另外 搜素路由的时候,有的路由是分开写的,比如路由/list/table-list 是有/list 和/table-list 直接搜/list/table-list搜不到,这个时候你就要去尝试单个搜索了,交叉比对,或者,找像路由配合的目录 这是一个简单实用的方法.走到这一步,你就可以自己去创建一个页面,配一个路由看看了.

接下来说一下进入页面内部改如何快速理解页面大致结构

import React, { PureComponent, Fragment } from 'react';
import { connect } from 'dva';
import moment from 'moment';
import {
  Row,
  Col,
  Card,
  Form,
  Input,
  Select,
  Icon,
  Button,
  Dropdown,
  Menu,
  InputNumber,
  DatePicker,
  Modal,
  message,
  Badge,
  Divider,
} from 'antd';
import StandardTable from 'components/StandardTable';
import PageHeaderLayout from '../../layouts/PageHeaderLayout';

import styles from './TableList.less';

const FormItem = Form.Item;
const { Option } = Select;
const getValue = obj =>
  Object.keys(obj)
    .map(key => obj[key])
    .join(',');
const statusMap = ['default', 'processing', 'success', 'error'];
const status = ['关闭', '运行中', '已上线', '异常'];

const CreateForm = Form.create()(props => {
  const { modalVisible, form, handleAdd, handleModalVisible } = props;
  const okHandle = () => {
    form.validateFields((err, fieldsValue) => {
      if (err) return;
      form.resetFields();
      handleAdd(fieldsValue);
    });
  };
  return (
    <Modal
      title="新建规则"
      visible={modalVisible}
      onOk={okHandle}
      onCancel={() => handleModalVisible()}
    >
      <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="描述">
        {form.getFieldDecorator('desc', {
          rules: [{ required: true, message: 'Please input some description...' }],
        })(<Input placeholder="请输入" />)}
      </FormItem>
    </Modal>
  );
});

@connect(({ rule, loading }) => ({
  rule,
  loading: loading.models.rule,
}))
@Form.create()
export default class TableList extends PureComponent {
  state = {
    modalVisible: false,
    expandForm: false,
    selectedRows: [],
    formValues: {},
  };

  componentDidMount() {
    const { dispatch } = this.props;
    dispatch({
      type: 'rule/fetch',
    });
  }

  handleStandardTableChange = (pagination, filtersArg, sorter) => {
    const { dispatch } = this.props;
    const { formValues } = this.state;

    const filters = Object.keys(filtersArg).reduce((obj, key) => {
      const newObj = { ...obj };
      newObj[key] = getValue(filtersArg[key]);
      return newObj;
    }, {});

    const params = {
      currentPage: pagination.current,
      pageSize: pagination.pageSize,
      ...formValues,
      ...filters,
    };
    if (sorter.field) {
      params.sorter = `${sorter.field}_${sorter.order}`;
    }

    dispatch({
      type: 'rule/fetch',
      payload: params,
    });
  };

  handleFormReset = () => {
    const { form, dispatch } = this.props;
    form.resetFields();
    this.setState({
      formValues: {},
    });
    dispatch({
      type: 'rule/fetch',
      payload: {},
    });
  };

  toggleForm = () => {
    const { expandForm } = this.state;
    this.setState({
      expandForm: !expandForm,
    });
  };

  handleMenuClick = e => {
    const { dispatch } = this.props;
    const { selectedRows } = this.state;

    if (!selectedRows) return;

    switch (e.key) {
      case 'remove':
        dispatch({
          type: 'rule/remove',
          payload: {
            no: selectedRows.map(row => row.no).join(','),
          },
          callback: () => {
            this.setState({
              selectedRows: [],
            });
          },
        });
        break;
      default:
        break;
    }
  };

  handleSelectRows = rows => {
    this.setState({
      selectedRows: rows,
    });
  };

  handleSearch = e => {
    e.preventDefault();

    const { dispatch, form } = this.props;

    form.validateFields((err, fieldsValue) => {
      if (err) return;

      const values = {
        ...fieldsValue,
        updatedAt: fieldsValue.updatedAt && fieldsValue.updatedAt.valueOf(),
      };

      this.setState({
        formValues: values,
      });

      dispatch({
        type: 'rule/fetch',
        payload: values,
      });
    });
  };

  handleModalVisible = flag => {
    this.setState({
      modalVisible: !!flag,
    });
  };

  handleAdd = fields => {
    const { dispatch } = this.props;
    dispatch({
      type: 'rule/add',
      payload: {
        description: fields.desc,
      },
    });

    message.success('添加成功');
    this.setState({
      modalVisible: false,
    });
  };

  renderSimpleForm() {
    const { form } = this.props;
    const { getFieldDecorator } = form;
    return (
      <Form onSubmit={this.handleSearch} layout="inline">
        <Row gutter={{ md: 8, lg: 24, xl: 48 }}>
          <Col md={8} sm={24}>
            <FormItem label="规则编号">
              {getFieldDecorator('no')(<Input placeholder="请输入" />)}
            </FormItem>
          </Col>
          <Col md={8} sm={24}>
            <FormItem label="使用状态">
              {getFieldDecorator('status')(
                <Select placeholder="请选择" style={{ width: '100%' }}>
                  <Option value="0">关闭</Option>
                  <Option value="1">运行中</Option>
                </Select>
              )}
            </FormItem>
          </Col>
          <Col md={8} sm={24}>
            <span className={styles.submitButtons}>
              <Button type="primary" htmlType="submit">
                查询
              </Button>
              <Button style={{ marginLeft: 8 }} onClick={this.handleFormReset}>
                重置
              </Button>
              <a style={{ marginLeft: 8 }} onClick={this.toggleForm}>
                展开 <Icon type="down" />
              </a>
            </span>
          </Col>
        </Row>
      </Form>
    );
  }

  renderAdvancedForm() {
    const { form } = this.props;
    const { getFieldDecorator } = form;
    return (
      <Form onSubmit={this.handleSearch} layout="inline">
        <Row gutter={{ md: 8, lg: 24, xl: 48 }}>
          <Col md={8} sm={24}>
            <FormItem label="规则编号">
              {getFieldDecorator('no')(<Input placeholder="请输入" />)}
            </FormItem>
          </Col>
          <Col md={8} sm={24}>
            <FormItem label="使用状态">
              {getFieldDecorator('status')(
                <Select placeholder="请选择" style={{ width: '100%' }}>
                  <Option value="0">关闭</Option>
                  <Option value="1">运行中</Option>
                </Select>
              )}
            </FormItem>
          </Col>
          <Col md={8} sm={24}>
            <FormItem label="调用次数">
              {getFieldDecorator('number')(<InputNumber style={{ width: '100%' }} />)}
            </FormItem>
          </Col>
        </Row>
        <Row gutter={{ md: 8, lg: 24, xl: 48 }}>
          <Col md={8} sm={24}>
            <FormItem label="更新日期">
              {getFieldDecorator('date')(
                <DatePicker style={{ width: '100%' }} placeholder="请输入更新日期" />
              )}
            </FormItem>
          </Col>
          <Col md={8} sm={24}>
            <FormItem label="使用状态">
              {getFieldDecorator('status3')(
                <Select placeholder="请选择" style={{ width: '100%' }}>
                  <Option value="0">关闭</Option>
                  <Option value="1">运行中</Option>
                </Select>
              )}
            </FormItem>
          </Col>
          <Col md={8} sm={24}>
            <FormItem label="使用状态">
              {getFieldDecorator('status4')(
                <Select placeholder="请选择" style={{ width: '100%' }}>
                  <Option value="0">关闭</Option>
                  <Option value="1">运行中</Option>
                </Select>
              )}
            </FormItem>
          </Col>
        </Row>
        <div style={{ overflow: 'hidden' }}>
          <div style={{ float: 'right', marginBottom: 24 }}>
            <Button type="primary" htmlType="submit">
              查询
            </Button>
            <Button style={{ marginLeft: 8 }} onClick={this.handleFormReset}>
              重置
            </Button>
            <a style={{ marginLeft: 8 }} onClick={this.toggleForm}>
              收起 <Icon type="up" />
            </a>
          </div>
        </div>
      </Form>
    );
  }

  renderForm() {
    const { expandForm } = this.state;
    return expandForm ? this.renderAdvancedForm() : this.renderSimpleForm();
  }

  render() {
    const {
      rule: { data },
      loading,
    } = this.props;
    const { selectedRows, modalVisible } = this.state;

    const columns = [
      {
        title: '规则编号',
        dataIndex: 'no',
      },
      {
        title: '描述',
        dataIndex: 'description',
      },
      {
        title: '服务调用次数',
        dataIndex: 'callNo',
        sorter: true,
        align: 'right',
        render: val => `${val} 万`,
        // mark to display a total number
        needTotal: true,
      },
      {
        title: '状态',
        dataIndex: 'status',
        filters: [
          {
            text: status[0],
            value: 0,
          },
          {
            text: status[1],
            value: 1,
          },
          {
            text: status[2],
            value: 2,
          },
          {
            text: status[3],
            value: 3,
          },
        ],
        onFilter: (value, record) => record.status.toString() === value,
        render(val) {
          return <Badge status={statusMap[val]} text={status[val]} />;
        },
      },
      {
        title: '更新时间',
        dataIndex: 'updatedAt',
        sorter: true,
        render: val => <span>{moment(val).format('YYYY-MM-DD HH:mm:ss')}</span>,
      },
      {
        title: '操作',
        render: () => (
          <Fragment>
            <a href="">配置</a>
            <Divider type="vertical" />
            <a href="">订阅警报</a>
          </Fragment>
        ),
      },
    ];

    const menu = (
      <Menu onClick={this.handleMenuClick} selectedKeys={[]}>
        <Menu.Item key="remove">删除</Menu.Item>
        <Menu.Item key="approval">批量审批</Menu.Item>
      </Menu>
    );

    const parentMethods = {
      handleAdd: this.handleAdd,
      handleModalVisible: this.handleModalVisible,
    };

    return (
      <PageHeaderLayout title="查询表格">
        <Card bordered={false}>
          <div className={styles.tableList}>
            <div className={styles.tableListForm}>{this.renderForm()}</div>
            <div className={styles.tableListOperator}>
              <Button icon="plus" type="primary" onClick={() => this.handleModalVisible(true)}>
                新建
              </Button>
              {selectedRows.length > 0 && (
                <span>
                  <Button>批量操作</Button>
                  <Dropdown overlay={menu}>
                    <Button>
                      更多操作 <Icon type="down" />
                    </Button>
                  </Dropdown>
                </span>
              )}
            </div>
            <StandardTable
              selectedRows={selectedRows}
              loading={loading}
              data={data}
              columns={columns}
              onSelectRow={this.handleSelectRows}
              onChange={this.handleStandardTableChange}
            />
          </div>
        </Card>
        <CreateForm {...parentMethods} modalVisible={modalVisible} />
      </PageHeaderLayout>
    );
  }
}

 

以这个页面为例

第一次阅读代码我们一定要按照从上到下的顺序阅读,记住 是第一次.第一次第一次

首先这个文件引入了几个组件 React dva moment antd  .....

我们要对这个库或组件有个大致印象,比如我们要在页面修改antd 的Dropdown组件,就要去antd官文看看有那些参数可以调整.

其他组件和工具库都是这样.假如你把页面头部的import 的东西有了大致了解,就继续往下看.

页面有const 定义了一个方法,或一系列对象,这个对象有的是作为组件的参数 有的本身就是小子组件

 但我们看到这段代码时就要特别注意了

componentDidMount() {
  const { dispatch } = this.props;
  dispatch({
    type: 'rule/fetch',
  });
}

在React组件的componentDidMount生命周期时 执行了

dispatch({
  type: 'rule/fetch',
});

获取你不知道dva 或许你根本看不懂这段代码,但是你可以想象他到底是什么意思.有什么作用. 或者你直接删掉这段代码,看看页面有什么变化. 了解代码效果最简单最明了的方法是 直接删掉,看看报什么错,或有什么变化...O(∩_∩)O哈哈~虽然不是什么好方法,但是简单易用.到这里 你大概知道 这段代码大概就是去调取接口获取数据渲染到页面的.这种写法可能与我们平时写的不一样

我们平时调取接口大概是这样的

copyrightTypeList().then(res => {
  if (res.data.code === 0) {
    this.caseTypeList = res.data.data
  }
})

少年不要迷茫了,要接受改变.学习新的知识吧,跳出你的舒适区.

dispatch({ type: 'rule/fetch', }); 这段代码的关键字是rule/fetch 要先在页面仔细搜索关于rule和fetch相关的代码.

在搜素了很久我终于好到 rule是在那里

......文章到这里就吿一段落了 剩余的就好理解了

属性一个项目还有一个方法就是打开项目的package.json 将里面的包 挨个查一遍,了解每个包的作用,用法.这也不失为一种好的快速上手的方法

谢谢阅读.如果觉得对你有帮助请记得点赞或收藏.欢迎留言讨论.谢谢.

 

  • 34
    点赞
  • 8
    评论
  • 45
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

<p> <span style="color:#333333;"><strong></strong></span> </p> <span style="font-size:24px;">一、简介</span> <p> <span style="font-size:12px;"></span> </p> <p> <span style="font-size:16px;">通过这个课程带大家从零开发一款功能全面的后台管理系统,包括项目搭建、功能实现到最后的Linux系统部署全过程。本课程使用SpringMVC + Spring + Mybatis作为主体框架,使用AdminLTE作为前端框架,使用主流关系型数据库Mysql作为存储数据库,使用非关系型数据库Redis作为缓存数据库,并集成SpringSecuriy安全框架做权限的动态管理,集成Swagger2自动生成接口文档,集成Druid连接池进行SQL性能监控,集成ActiveMQ消息中间件进行异步解耦,提高性能。最后使用linux系统进行服务部署,并搭建nginx反向代理服务器提高网站性能。</span> </p> <p> <br /> </p> <p> <span style="font-size:24px;"><strong>二、学习目标</strong></span> </p> <p> <span style="font-size:16px;">通过本课程的学习带大家掌握SSM框架开发流程,并熟练使用SpringSecurity做为安全框架进行权限管理,整合相关优秀的开源框架进行功能开发。还在项目中带大家学习前端相关的Jquery、Bootstrap等知识。课程结束之后希望大家能做到独立进行开发项目的目的,增强解决问题的能力,具备功能落地实现的能力。</span> </p> <p> <span style="font-size:16px;"><span style="font-size:24px;"><strong>三、课程涉及知识点</strong></span></span> </p> <p> <span style="font-size:16px;"></span> </p> <ul> <li> SpringMVC源码分析 </li> <li> Mybatis源码分析 </li> <li> 通用Mapper </li> <li> Mysql数据库 </li> <li> Redis缓存实现 </li> <li> ActiveMQ消息中间件 </li> <li> SpringSecurity鉴权 </li> <li> Swagger2接口文档生成 </li> <li> 自定义注解 </li> <li> AOP切面编程 </li> <li> 自定义过滤器 </li> <li> Logback日志整合 </li> <li> Druid性能监控 </li> <li> Linux系统 </li> <li> Nginx反向代理 </li> <li> Ajax异步请求技术 </li> <li> Jquery基本使用 </li> <li> AdminLTE前端框架 </li> <li> Chart图表-线状图和饼状图 </li> <li> 百度地图定位城市 </li> <li> BootStrap前端框架 </li> <li> BootStrap-Table插件 </li> <li> BootStrap-Treeview插件 </li> <li> Markdown编辑器 </li> <li> 403、404、500错误页面配置 </li> <li> 数据库事务 </li> <li> 消息提示插件toastr.js </li> <li> 图片上传插件bootstrap fileinput </li> <li> 数字滚动效果 </li> <li> pv/uv流量统计 </li> <li> ... </li> </ul> <p> <br /> </p> <p> <span style="font-size:16px;"><span style="font-size:24px;"><strong>四、</strong></span><span style="font-size:24px;"><strong>课程部分内容截图如下</strong></span><span style="font-size:24px;"><strong></strong></span></span> </p> <p> <span style="font-size:18px;"><span style="font-size:18px;"><strong>1、首页</strong></span></span> </p> <p> <span style="font-size:16px;"><span style="font-size:24px;"><strong><span></span><span><img alt="" src="https://imgconvert.csdnimg.cn/aHR0cDovL3d3dy5kcmVhbWxhbmQud2FuZy9pbWFnZXMvaW1hZ2UvMjAxOTA5MTkvMjAxOTA5MTkxMTE1MTJfMTM5LnBuZw?x-oss-process=image/format,png" /></span></strong></span></span> </p> <p> <span style="font-size:24px;"><span style="font-size:24px;"><strong><span style="font-size:24px;">2、菜单管理</span></strong></span></span> </p> <p> <span style="font-size:24px;"><span style="font-size:24px;"><strong><span style="font-size:24px;"><span></span><span><img alt="" src="https://imgconvert.csdnimg.cn/aHR0cDovL3d3dy5kcmVhbWxhbmQud2FuZy9pbWFnZXMvaW1hZ2UvMjAxOTA5MTkvMjAxOTA5MTkxMTE3MjBfNzE5LnBuZw?x-oss-process=image/format,png" /></span><br /> </span></strong></span></span> </p> <p> <span></span> </p> <p> <span><span style="font-size:24px;"><strong>3、图床管理</strong></span><span style="font-size:24px;"><strong></strong></span><br /> </span> </p> <p> <span><span style="font-size:24px;"><strong><span></span><span><img alt="" src="https://imgconvert.csdnimg.cn/aHR0cDovL3d3dy5kcmVhbWxhbmQud2FuZy9pbWFnZXMvaW1hZ2UvMjAxOTA5MTkvMjAxOTA5MTkxMTIwMTJfNjYwLnBuZw?x-oss-process=image/format,png" /><span></span></span><span></span><br /> </strong></span></span> </p> <p> <span><span style="font-size:24px;"><strong>4、图标管理<br /> </strong></span></span> </p> <p> <span><span style="font-size:24px;"><strong><span></span><span><img alt="" src="https://imgconvert.csdnimg.cn/aHR0cDovL3d3dy5kcmVhbWxhbmQud2FuZy9pbWFnZXMvaW1hZ2UvMjAxOTA5MTkvMjAxOTA5MTkxMTIxMDBfMTcucG5n?x-oss-process=image/format,png" /><span></span></span><span></span></strong></span></span> </p> <p> <span><span style="font-size:24px;"><strong>5、留言反馈管理<br /> </strong></span></span> </p> <p> <span><span style="font-size:24px;"><strong><span></span><span><img alt="" src="https://imgconvert.csdnimg.cn/aHR0cDovL3d3dy5kcmVhbWxhbmQud2FuZy9pbWFnZXMvaW1hZ2UvMjAxOTA5MjEvMjAxOTA5MjExMDEwMTJfMzQ2LnBuZw?x-oss-process=image/format,png" /><span></span></span><span></span></strong></span></span> </p> <p> <span><span style="font-size:24px;"><strong>6、druid监控<br /> </strong></span></span> </p> <p> <span></span><span><img alt="" src="https://imgconvert.csdnimg.cn/aHR0cDovL3d3dy5kcmVhbWxhbmQud2FuZy9pbWFnZXMvaW1hZ2UvMjAxOTA5MTkvMjAxOTA5MTkxMTIzMzVfNTgwLnBuZw?x-oss-process=image/format,png" /></span> </p> <p> <br /> </p> <p> <span style="font-size:24px;"><strong></strong></span><span style="font-size:24px;"><strong>7、登录日志</strong></span><span style="font-size:24px;"><strong></strong></span> </p> <p> <span style="font-size:24px;"><strong><span></span><span><img alt="" src="https://imgconvert.csdnimg.cn/aHR0cDovL3d3dy5kcmVhbWxhbmQud2FuZy9pbWFnZXMvaW1hZ2UvMjAxOTA5MTkvMjAxOTA5MTkxMTI0MzJfOTQyLnBuZw?x-oss-process=image/format,png" /><span></span></span><span></span><br /> </strong></span> </p> <p> <br /> </p>
表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值