React实现后台管理系统(二)

一、页面tag实现

1、tag显示和兄弟组件传值

①tag显示效果实现

使用antd中的Tag组件实现,在components文件夹下新建tag/index.js以及index.css文件
tag/index.js:

import React, { useState } from 'react'
import { Tag, Space } from 'antd'
import './index.css'

const MyTag = props => {
  return (
    <Space className='tag-box' size={[0, 8]} wrap>
      <Tag>首页</Tag>
      <Tag closeIcon onClose={handleClose} color='#55acee'>
        Prevent Default
      </Tag>
    </Space>
  )
}
export default MyTag

tag/index.css:

.tag-box {
  padding-top: 24px;
  padding-left: 24px;
}

在layout中引入tag组件:

... ...
import MyTag from '../components/tag'
... ...

const MyLayout = () => {
  ... ...
  return (
    <Layout className='main-container'>
      ... ...
        <MyHeader collapsed={collapsed} Collapsed={Collapsed} setCollapsed={setCollapsed} />
        <MyTag></MyTag>
        <Content
          ... ...
        >
          <Outlet />
        </Content>
      ... ...
    </Layout>
  )
}

export default MyLayout

②使用redux实现兄弟组件传值

修改src/store/reducers/tab.js文件:

import { createSlice } from '@reduxjs/toolkit'

const tabSlice = createSlice({
  name: 'tab',
  initialState: {
    ... ...
    tagList: [
      {
        path: '/',
        name: 'home',
        label: '首页'
      }
    ]
  },
  reducers: {
    ... ...
    selectTagList: (state, { payload: val }) => {
      if (val.name !== 'home') {
        //如果已经存在的tag就不添加
        const res = state.tagList.findIndex(item => item.name === val.name)
        if (res === -1) {
          state.tagList.push(val)
        }
      }
    }
  }
})

export const { selectTagList } = tabSlice.actions
export default tabSlice.reducer

修改conponents/aside/index.js文件:

... ...
import { useDispatch } from 'react-redux'
import { selectTagList } from '../../store/reducers/tab'

... ...
const Aside = props => {
  ... ...
  const dispatch = useDispatch()

  //添加数据到store
  const setTagList = val => {
    dispatch(selectTagList(val))
  }

  ... ...

  const clickMenu = e => {
    let data
    menuList.forEach(item => {
      if (item.path === e.keyPath[e.keyPath.length - 1]) {
        data = item
        //如果有二级菜单
        if (e.keyPath.length > 1) {
          data = item.children.find(child => {
            return child.path === e.key
          })
        }
      }
    })
    setTagList({
      path: data.path,
      name: data.name,
      label: data.label
    })
    navigate(e.key)
  }
   ... ...
}
export default Aside

修改components/tag/index.js文件:

... ...
import { useNavigate } from 'react-router-dom'
import { useSelector } from 'react-redux'

const MyTag = props => {
  const tagList = useSelector(state => state.tab.tagList)
  const handleClose = () => {
    console.log(123, tagList)
  }
  ... ...
}
export default MyTag

2、tag选中和删除

①当前菜单存储

同样使用redux进行存储,修改src/store/reducers/tab.js文件:

... ...

const tabSlice = createSlice({
  name: 'tab',
  initialState: {
    ... ...
    currentMenu: {}
  },
  reducers: {
   ... ...
    selectTagList: (state, { payload: val }) => {
      if (val.name !== 'home') {
        state.currentMenu = val
        ... ...
      } else if(val.name !== 'home' && state.tagList.length === 1){
        state.currentMenu = {}
      }
    },
    setCurrentMenu: (state, { payload: val }) => {
      if (val.name === 'home') {
        state.currentMenu = {}
      } else {
        state.currentMenu = val
      }
    }
  }
})
... ...

②tag遍历显示

修改components/tag/index.js文件:

... ...
const MyTag = props => {
  const tagList = useSelector(state => state.tab.tagList)
  const currentMenu = useSelector(state => state.tab.currentMenu)
  const handleCloseTag = () => {
    console.log(123, tagList)
  }

  const handleClickTag = tag => {}

  //tag的显示,flag为是否选中标识
  const setTag = (flag, item, index) => {
    return flag ? (
      <Tag closeIcon onClose={() => handleCloseTag(item, index)} color='#55acee' key={item.name}>
        {item.label}
      </Tag>
    ) : (
      <Tag key={item.name} onClick={() => handleClickTag(item)}>
        {item.label}
      </Tag>
    )
  }
  return (
    <Space className='tag-box' size={[0, 8]} wrap>
      {currentMenu.name &&
        tagList.map((item, index) => setTag(item.path === currentMenu.path, item, index))}
    </Space>
  )
}
export default MyTag

③删除tag

修改src/store/reducers/tab.js文件:

import { createSlice } from '@reduxjs/toolkit'

const tabSlice = createSlice({
  ... ...
  reducers: {
    ... ...
    closeTag: (state, { payload: val }) => {
      let res = state.tagList.findIndex(item => item.name === val.name)
      state.tagList.splice(res, 1)
    },
    setCurrentMenu: (state, { payload: val }) => {
      if (val.name === 'home') {
        state.currentMenu = {}
      } else {
        state.currentMenu = val
      }
    }
  }
})

export const { changeCollapse, selectTagList, closeTag, setCurrentMenu } = tabSlice.actions
export default tabSlice.reducer

修改components/tag/index.js文件:

... ...
import { useNavigate } from 'react-router-dom'
import { useSelector, useDispatch } from 'react-redux'
import { useLocation } from 'react-router-dom'
import { closeTag, setCurrentMenu } from '../../store/reducers/tab'
... ...

const MyTag = props => {
  const dispatch = useDispatch()
  const navigate = useNavigate()
  const tagList = useSelector(state => state.tab.tagList)
  const currentMenu = useSelector(state => state.tab.currentMenu)
  const location = useLocation()

  const handleCloseTag = (tag, index) => {
    let length = tagList.length - 1
    dispatch(closeTag(tag))
    if (index === length) {
      //设置当前数据
      const curData = tagList[index - 1]
      dispatch(setCurrentMenu(curData))
      navigate(curData.path)
    } else {
      //关闭中间tag
      if (tagList.length > 1) {
        //如果tag至少存在一个数据,则选择后一个
        const curData = tagList[index + 1]
        dispatch(setCurrentMenu(curData))
        navigate(curData.path)
      }
    }
  }

  const handleClickTag = tag => {
    dispatch(setCurrentMenu(tag))
    navigate(tag.path)
  }
  ... ...

④菜单选中效果与tag、路由不一致问题

修改components/aside/index.js文件:

... ...
import { useNavigate, useLocation } from 'react-router-dom'
... ...

const Aside = props => {
  ... ...
  const location = useLocation()

  ... ...
  return (
    ... ...
      <Menu
        ... ...
        selectedKeys={[location.pathname]}
        ... ...
      />
    ... ...
  )
}

效果:
请添加图片描述

二、用户管理界面实现

1、用户筛选框显示

使用antd的Button、Form组件实现,修改page/user/index.js文件:

import React, { useEffect, useState } from 'react'
import { Button, Form, Input } from 'antd'
import { PlusOutlined } from '@ant-design/icons'
import './index.css'

const User = () => {
  const handleModal = () => {}
  const onFinish = () => {}
  return (
    <div className='user-list'>
      <div className='filter-box'>
        <Button type='primary' onClick={() => handleModal()} icon={<PlusOutlined />}>
          新建
        </Button>
        <Form name='basic' layout='inline' onFinish={onFinish} autoComplete='off'>
          <Form.Item name='keyword'>
            <Input placeholder='请输入用户名' />
          </Form.Item>

          <Form.Item>
            <Button type='primary' htmlType='submit'>
              搜索
            </Button>
          </Form.Item>
        </Form>
      </div>
    </div>
  )
}
export default User

page/user/index.css文件:

.user-list .filter-box {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}

2、用户列表table显示

①table数据获取

在src/mock文件夹下新建user.js文件:

import Mock from 'mockjs'

function param2Obj(url) {
  const search = url.split('?')[1]
  if (!search) {
    return {}
  }
  return JSON.parse(
    '{"' +
      decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') +
      '"}'
  )
}

let List = []
const count = 200

for (let i = 0; i < count; i++) {
  List.push(
    Mock.mock({
      id: Mock.Random.guid(),
      name: Mock.Random.cname(),
      addr: Mock.mock('@county(true)'),
      'age|18-60': 1,
      birth: Mock.Random.date(),
      sex: Mock.Random.integer(0, 1)
    })
  )
}

const userApi = {
  /**
   * 获取列表
   * 参数 name,page,limit;name可以不填,page,limit有默认值。
   * @param name
   * @param page
   * @param limit
   * @return {{code:number,count:number,data:*[]}}
   */
  getUserList: config => {
    const { name, page = 1, limit = 20 } = param2Obj(config.url)
    const mockList = List.filter(user => {
      if (name && user.name.indexOf(name) === -1 && user.addr.indexOf(name) === -1) return false
      return true
    })
    const pageList = mockList.filter(
      (item, index) => index < limit * page && index >= limit * (page - 1)
    )
    return {
      code: 200,
      count: mockList.length,
      list: pageList
    }
  }
}
export default userApi

在utils/mock.js文件中新增查询mock接口:

... ...
import userApi from '../mock/user'

//拦截接口
... ...
Mock.mock(/user\/getUser/, userApi.getUserList)

在src/page/user文件夹下新建services.js文件:

import { get, post } from '../../utils/request'

export const getUser = async params => {
  return get('/user/getUser', params)
}

在src/page/user/index.js文件中调用接口:

... ...
import { getUser } from './services'

const User = () => {
  const [filterParam, setFilterParam] = useState({ //查询条件
    name: ''
  })
  const getTableList = () => {
    getUser(filterParam).then(res => {
      console.log(res)
    })
  }
  useEffect(() => {
    getTableList()
  }, [])
  const handleModal = () => {}
  const onFinish = e => {
    setFilterParam({
      name: e.keyword
    })
  }
  
... ...
export default User

控制台打印:
在这里插入图片描述

②table表格数据渲染

修改src/page/user/index.js文件内容:

import React, { useEffect, useState } from 'react'
import { Button, Form, Input, Table, Space, Popconfirm } from 'antd'
... ...

const User = () => {
  ... ...
  const [tableData, setTableData] = useState([])

  const columns = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name'
    },
    {
      title: '年龄',
      dataIndex: 'age',
      key: 'age'
    },
    {
      title: '性别',
      dataIndex: 'sex',
      key: 'sex',
      render: val => {
        return val ? '女' : '男'
      }
    },
    {
      title: '出生日期',
      dataIndex: 'birth',
      key: 'birth'
    },
    {
      title: '地址',
      dataIndex: 'addr',
      key: 'addr'
    },
    {
      title: '操作',
      key: 'x',
      render: (text, record) => {
        return (
          <Space>
            <Button onClick={() => handleModal('edit', text)}>编辑</Button>
            <Popconfirm
              title='提示'
              description='此操作将删除该用户,是否继续?'
              onConfirm={() => handleDelete(text)}
              onCancel={handleCancel}
              okText='确认'
              cancelText='取消'
            >
              <Button type='primary' danger>
                删除
              </Button>
            </Popconfirm>
          </Space>
        )
      }
    }
  ]

  const handleCancel = () => {}
  const handleDelete = () => {}
  ... ...
  return (
    <div className='user-list'>
      ... ...
      <Table columns={columns} dataSource={tableData} rowKey={'id'}></Table>
    </div>
  )
}
export default User

效果:
在这里插入图片描述

3、用户新增编辑弹窗

①弹窗显示

使用antd的Modal组件搭配Form表单实现,修改src/page/user/index.js文件内容:

... ...
import {
  Button,
  Form,
  Input,
  Table,
  Space,
  Popconfirm,
  Modal,
  InputNumber,
  Select,
  DatePicker
} from 'antd'

const User = () => {
  ... ...
  const [isModalOpen, setIsModalOpen] = useState(false)
  const [method, setMethod] = useState('add')
  const [form] = Form.useForm()

  ... ...
  
  const handleCancel = () => {
    setIsModalOpen(false)
  }
  
  const handleModal = method => {
    setMethod(method)
    setIsModalOpen(true)
  }

  const handleSubmit = () => {}

  return (
    <div className='user-list'>
      ... ...
      <Table columns={columns} dataSource={tableData} rowKey={'id'}></Table>
      <Modal
        title={method === 'edit' ? '编辑用户' : '新增用户'}
        open={isModalOpen}
        onOk={handleSubmit}
        onCancel={handleCancel}
        okText='确定'
        cancelText='取消'
      >
        <Form
          form={form}
          name='basic'
          labelCol={{
            span: 6
          }}
          wrapperCol={{
            span: 18
          }}
          labelAlign='left'
        >
          <Form.Item
            label='姓名'
            name='name'
            rules={[
              {
                required: true,
                message: '请输入姓名!'
              }
            ]}
          >
            <Input placeholder='请输入姓名' />
          </Form.Item>

          <Form.Item
            label='年龄'
            name='age'
            rules={[
              {
                required: true,
                message: '请输入年龄!'
              }
            ]}
          >
            <InputNumber placeholder='请输入年龄' />
          </Form.Item>

          <Form.Item
            label='性别'
            name='sex'
            rules={[
              {
                required: true,
                message: '请选择性别!'
              }
            ]}
          >
            <Select
              placeholder='请选择性别'
              options={[
                { value: 0, label: '男' },
                { value: 1, label: '女' }
              ]}
            />
          </Form.Item>

          <Form.Item
            label='出生日期'
            name='birth'
            rules={[
              {
                required: true,
                message: '请选择出生日期!'
              }
            ]}
          >
            <DatePicker placeholder='请选择' format={'YYYY/MM/DD'} />
          </Form.Item>

          <Form.Item
            label='地址'
            name='addr'
            rules={[
              {
                required: true,
                message: '请填写地址!'
              }
            ]}
          >
            <Input placeholder='请填写地址' />
          </Form.Item>
        </Form>
      </Modal>
    </div>
  )
}
export default User

效果:
在这里插入图片描述

②弹窗功能实现

添加/编辑时提交的出生日期参数需要做日期转换,需要使用到的是dayjs,所以需要下载dayjs依赖:

npm install dayjs --save

在这里插入图片描述

Ⅰ、用户添加功能

在src/mock/user.js文件中新建添加用户mock接口:

 ... ...
const userApi = {
  ... ...
  /**
   * 增加用户
   * @param name
   * @param addr
   * @param age
   * @param birth
   * @param sex
   * @return {{code:number,data:{message:string}}}
   */
  createUser: config => {
    const { name, addr, age, birth, sex } = JSON.parse(config.body)
    List.unshift({
      id: Mock.Random.guid(),
      name: name,
      age: age,
      addr: addr,
      birth: birth,
      sex: sex
    })
    return {
      code: 200,
      data: {
        message: '添加成功'
      }
    }
  }
export default userApi

在utils/mock.js文件中拦截接口:

... ...

//拦截接口
... ...
Mock.mock(/user\/createUser/, 'post', userApi.createUser)

在src/pages/user/services.js文件新建前端接口:

... ...
export const createUser = async data => {
  return post('/user/createUser', data)
}

在src/pages/user/index.js文件调用接口:

... ...
import { getUser, createUser} from './services'
import dayjs from 'dayjs'

const User = () => {
  ... ...
  const handleCancel = () => {
    setIsModalOpen(false)
    form.resetFields()
  }
  
  const handleSubmit = () => {
    form
      .validateFields()
      .then(value => {
        value.birth = dayjs(value.birth).format('YYYY-MM-DD')
        if (method === 'edit') {
        } else {
          createUser(value).then(res => {
            handleCancel()
            getTableList()
            message.success(res.data.data.message)
          })
        }
      })
      .catch(e => {
        console.log(e)
      })
  }

  ... ...
}
export default User

效果:
请添加图片描述

Ⅱ、用户编辑功能

在src/mock/user.js文件中新建编辑用户mock接口:

 ... ...
const userApi = {
  ... ...
 /**
   * 编辑用户
   * @param name
   * @param addr
   * @param age
   * @param birth
   * @param sex
   * @return {{code:number,data:{message:string}}}
   */
  updateUser: config => {
    const { id, name, addr, age, birth, sex } = JSON.parse(config.body)
    const sex_num = parseInt(sex)
    List.some(item => {
      if (item.id === id) {
        item.name = name
        item.age = age
        item.addr = addr
        item.birth = birth
        item.sex = sex_num
        return true
      }
    })
    return {
      code: 200,
      data: {
        message: '编辑成功'
      }
    }
  }
}
export default userApi

在utils/mock.js文件中拦截接口:

... ...

//拦截接口
... ...
Mock.mock(/user\/updateUser/, 'post', userApi.updateUser)

在src/pages/user/services.js文件新建前端接口:

... ...
export const updateUser = async data => {
  return post('/user/updateUser', data)
}

在src/pages/user/index.js文件增加数据回填逻辑并调用接口:

... ...
import { getUser, createUser, updateUser } from './services'

const User = () => {
  ... ...
  const handleModal = (method, data) => {
    if (method === 'edit') {
      const cloneData = JSON.parse(JSON.stringify(data))
      cloneData.birth = dayjs(cloneData.birth)
      form.setFieldsValue(cloneData)
    }
    ... ...
  }

  const handleSubmit = () => {
    form
      .validateFields()
      .then(value => {
        value.birth = dayjs(value.birth).format('YYYY-MM-DD')
        if (method === 'edit') {
          updateUser(value).then(res => {
            handleCancel()
            getTableList()
            message.success(res.data.data.message)
          })
        } else {
          ... ...
        }
      })
      .catch(e => {
        console.log(e)
      })
  }

  ... ...
  return (
    <div className='user-list'>
      ... ...
      <Modal
        ... ...
      >
        <Form
          ... ...
        >
          {method === 'edit' && (
            <Form.Item name='id' hidden>
              <Input />
            </Form.Item>
          )}
          ... ...
        </Form>
      </Modal>
    </div>
  )
}
export default User

效果:
请添加图片描述

③删除功能实现

在src/mock/user.js文件中新建删除用户mock接口:

 ... ...
const userApi = {
  ... ...
 /**
   * 删除用户
   * @param id
   * @return {*}
   */
  deleteUser: config => {
    const { id } = JSON.parse(config.body)
    if (!id) {
      return {
        code: 400,
        message: '删除失败'
      }
    } else {
      List = List.filter(item => item.id !== id)
      return {
        code: 200,
        message: '删除成功'
      }
    }
  }
}
export default userApi

在utils/mock.js文件中拦截接口:

... ...
Mock.mock(/user\/deleteUser/, 'post', userApi.deleteUser)

在src/pages/user/services.js文件新建前端接口:

... ...
export const deleteUser = async data => {
  return post('/user/deleteUser', data)
}

在src/pages/user/index.js调用接口:

... ...
import { getUser, createUser, updateUser, deleteUser } from './services'

const User = () => {
... ...

  const handleDelete = data => {
    const { id } = data
    deleteUser({ id }).then(res => {
      console.log(res.data)
      if (res.data.code === 200) {
        getTableList()
        message.success(res.data.message)
      } else {
        message.error(res.data.message)
      }
    })
  }
  ... ...
}
export default User

效果:
请添加图片描述

④搜索功能实现

通过监听filterParam来实现搜索,点击搜索时修改filterParam的值,如果filterParam发生改变则调用查询接口,修改src/pages/user/index.js文件:

... ...
const User = () => {
... ...

  useEffect(() => {
    console.log(123123, filterParam)
    getTableList()
  }, [filterParam])

... ...

  const onFinish = e => {
    setFilterParam({
      name: e.keyword
    })
  }
... ...
}
export default User

效果:
请添加图片描述

二、登录页和用户鉴权实现

1、登录页显示

在pages文件夹下新建login/index.js、login/index.css文件:
在这里插入图片描述
增加路由,修改src/router/index.js文件:

... ...
import Login from '../pages/login'

const routes = [
 ... ...
  {
    path: 'login',
    Component: Login
  }
]
... ...

login/index.js文件内容:

import React from 'react'
import { Form, Input, Button } from 'antd'
import './index.css'

const Login = () => {
  const handleSubmit = () => {}
  return (
    <Form className='login-box' onFinish={handleSubmit}>
      <div className='title'>系统登录</div>
      <Form.Item
        label='账号'
        name='username'
        rules={[{ required: true, message: 'Please input your username!' }]}
      >
        <Input placeholder='请输入账号' />
      </Form.Item>

      <Form.Item
        label='密码'
        name='password'
        rules={[{ required: true, message: 'Please input your password!' }]}
      >
        <Input.Password placeholder='请输入密码' />
      </Form.Item>
      <Form.Item className='login-button'>
        <Button type='primary' htmlType='submit'>
          登录
        </Button>
      </Form.Item>
    </Form>
  )
}
export default Login

login/index.css文件内容:

.login-box {
  width: 350px;
  border: 1px solid #eaeaea;
  margin: 180px auto;
  padding: 35px 35px 15px 35px;
  background-color: #fff;
  border-radius: 15px;
  box-shadow: 0 0 25px #cac6c6;
  box-sizing: border-box;
}

.login-box .title {
  text-align: center;
  margin-bottom: 40px;
  color: #505458;
  font-size: 20px;
}

.login-box .login-button {
  text-align: center;
}

效果:
在这里插入图片描述

2、用户鉴权实现

使用mock模拟登录接口返回token,存入localStorage来判断用户是否登录

①使用mock模拟登录接口

在src/mock文件夹下新建permission.js文件:

import { message } from 'antd'
import Mock from 'mockjs'

const PermissionApi = {
  LoginApi: config => {
    const { username, password } = JSON.parse(config.body)
    //先判断用户是否存在
    //判断账号和密码是否对应
    if (username === 'admin' && password === 'admin') {
      return {
        code: 200,
        data: {
          token: Mock.Random.guid(),
          message: '登录成功'
        }
      }
    } else if (username === 'account' && password === 'account') {
      return {
        code: 200,
        data: {
          token: Mock.Random.guid(),
          message: '登录成功'
        }
      }
    } else {
      return {
        code: 400,
        message: '登录失败'
      }
    }
  }
}

export default PermissionApi

在src/utils/mock.js文件新增以下代码:

... ...
import PermissionApi from '../mock/permission'

... ...
Mock.mock(/permission\/LoginApi/, 'post', PermissionApi.LoginApi)

在src/pages/login文件夹下新建services.js文件:

import { post } from '../../utils/request'

export const LoginApi = async data => {
  return post('/permission/LoginApi', data)
}

②界面调用接口

在src/pages/login/index.js文件中增加以下代码:

... ...
import { LoginApi } from './services'
import { useNavigate, Navigate } from 'react-router-dom'

const Login = () => {
  const navigator = useNavigate()
  //在登陆状态下,需要跳转到home页面
  if (localStorage.getItem('token')) {
    return <Navigate to='/home' />
  }
  const handleSubmit = val => {
    if (!val.password || !val.username) {
      message.warning('请输入用户名和密码')
    }
    LoginApi(val).then(res => {
      const { data } = res
      localStorage.setItem('token', data.data.token)
      navigator('/home')
    })
  }
 ... ...
}
export default Login

③退出登录

在src/components/header/index.js文件中增加登出逻辑:

... ...
import { useNavigate } from 'react-router-dom'

... ...

const MyHeader = props => {
  ... ...
  const navigat = useNavigate()
  //登出
  const logout = () => {
    //清除token
    localStorage.removeItem('token')
    navigat('/login')
  }
}
export default MyHeader

④没有token情况下不让访问除login以外路由

使用高阶组件进行鉴权,在src/router文件下新建routerAuth.js文件:

import { Navigate } from 'react-router-dom'

export const RouterAuth = ({ children }) => {
  const token = localStorage.getItem('token')
  if (!token) {
    return <Navigate to='/login' replace />
  }
  return children
}

修改src/pages/layout.js文件内容:

... ...
import { RouterAuth } from '../router/routerAuth'

... ...

const MyLayout = () => {
  ... ...
  return (
    <RouterAuth>
      <Layout className='main-container'>
        .... ....
      </Layout>
    </RouterAuth>
  )
}
export default MyLayout

项目到这,整体框架就完成噜,可以根据自身需求修改样式、内容哦。
。:.゚ヽ(。◕‿◕。)ノ゚.:。+゚

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值