关于vxe-table的使用心得及扩展2【vxe-table表格二次封装】(非插件)


前言

根据官网文档预设些自定义参数,二次封装了vxe-table。注意这不是vxe-grid哦!

1.添加了官方不支持的表格合计计算参数

2.使表格 全选复选框勾选变化时,能直接触发复选框的变化事件。即使联动逻辑和el-table保持相同

3.补充了表格高度动态计算参数,dynamicHeight,如果实在不会用 就留言要例子把,请先看我写的参数注释!


一、在components中创建vxe-table\index.vue文件

<template>
  <div class="table-box">
    <vxe-table
      ref="vxetableRef"
      :scroll-x="{enabled: true, gt: 0}"
      :scroll-y="{enabled: true, gt: 0}"
      v-on="$listeners"
      v-bind="$attrs"
      :loading="loading"
      :loading-config="loadingOpts"
      :auto-resize="autoResize"
      :stripe="stripe"
      :align="align"
      :header-align="headerAlign"
      :merge-cells="mergeCells"
      :tree-config="treeOpts"
      :radio-config="radioOpts"
      :checkbox-config="checkboxOpts"
      :show-overflow="showOverflow"
      :show-header-overflow="showHeaderOverflow"
      :show-footer-overflow="showFooterOverflow"
      :keep-source="keepSource"
      :border="border"
      :height="height"
      :min-height="minHeight"
      :max-height="maxHeight"
      :show-footer="showFooter"
      :column-config="columnOpts"
      :resize-config="resizeOpts"
      :row-config="rowOpts"
      :menu-config="ctxMenuOpts"
      :edit-config="editOpts"
      :expand-config="expandConfig"
      :tooltip-config="tooltipOpts"
      :span-method="spanMethod"
      :row-class-name="rowClassName"
      :cell-class-name="cellClassName"
      :header-row-class-name="headerRowClassName"
      :header-cell-class-name="headerCellClassName"
      :footer-row-class-name="footerRowClassName"
      :footer-cell-class-name="footerCellClassName"
      :footer-data="footerData"
      :footer-align="footerAlign"
      :footer-span-method="footerSpanMethod"
      :footer-method="footerMethod"
      :cell-style="cellStyle"
      @header-cell-click="$emit('headerCellClick')"
      @header-cell-dblclick="$emit('headerCellDblclick')"
      @header-cell-menu="$emit('headerCellMenu')"
      @cell-click="$emit('cellClick')"
      @cell-dblclick="$emit('cellDBLClick')"
      @cell-mouseenter="$emit('cellMouseenter')"
      @cell-mouseleave="$emit('cellMouseleave')"
      @cell-menu="$emit('cellMenu')"
      @radio-change="$emit('radioChange')"
      @checkbox-change="checkboxChange"
      @checkbox-all="checkboxAll"
      @scroll="$emit('scroll')">
      <slot></slot>
    </vxe-table>
  </div>
</template>

<script>
/* global XEUtils:false */
/* ==================== 说明开始 ==================== */

// 1. API详见 https://vxetable.cn/v3.8/#/table/api
// 2. 该表格属性若干,该版只重定义了部分常用属性,其它方法可直接在组件上按官网案例调用,其方法和属性穿透实现方式为:$listeners、$attrs
// 3. 公用属性已提取到同级conf.js中,补充属性请按此规则配置并补充对应参数说明
;`表格底部求和及平局值调用示例:
<custom-vxe-table
  showFooter
  :summaryColKeyList="[['classify','allSum'],['allSum','agreementMoney']]"
  :sumConfig="[{columnIndex: 1, text: '合计(元)' },{columnIndex: 1, text: '平均值(元)', ruleName: 'avg' }]"
  :table-data="data"
  @selection-change="handleSelectionChange">
    <vxe-column type="seq" width="50"></vxe-column>
    <vxe-column field="classify" title="classify"></vxe-column>
    <vxe-column field="allSum" title="allSum"></vxe-column>
    <vxe-column field="agreementMoney" title="agreementMoney"></vxe-column>
</custom-vxe-table>`

/* ==================== 说明结束 ==================== */

import GlobalConfig from './conf'
import { accAdd } from '@/util/util'
import { getPageIndexHeight } from '@/util/tools'
import { validatenull } from '@/util/validate'

export default {
  name: 'vxe-table-m',
  props: {
    tableData: {
      type: Array,
      default: () => [],
    },
    // tableHeight、minHeight、maxHeight 三者之间的关系见 https://www.cnblogs.com/xldbk/p/12114686.html
    tableHeight: {
      type: [Number, String],
    },
    minHeight: { type: [Number, String], default: () => GlobalConfig.table.minHeight },
    maxHeight: {
      type: [Number, String],
      default: () => GlobalConfig.table.maxHeight,
    },
    // 最终高度需要再减去的高度,常用来减去已知的自定义元素高度,此数值需要是1920*1080分辨率下的像素值(只有不传入其它任何高度限制时生效)
    dynamicHeight: {
      type: [Number, String],
      default: 0,
    },
    // 保持原始值的状态,被某些功能所依赖,比如编辑状态、还原数据等(开启后影响性能,具体取决于数据量)
    keepSource: { type: Boolean, default: () => GlobalConfig.table.keepSource },
    // 是否自动监听父容器变化去更新响应式表格宽高
    autoResize: {
      type: Boolean,
      default: () => {
        return false
      },
    },
    // 响应式布局配置项
    resizeConfig: {
      type: Object,
    },
    // 是否带有斑马纹
    stripe: { type: Boolean, default: () => GlobalConfig.table.stripe },
    border: { type: [Boolean, String], default: () => GlobalConfig.table.border },
    loading: {
      type: Boolean,
      default: () => {
        return false
      },
    },
    loadingConfig: {
      type: Object,
    },
    // 所有的列对其方式
    align: { type: String, default: () => GlobalConfig.table.align },
    // 所有的表头列的对齐方式
    headerAlign: { type: String, default: () => GlobalConfig.table.headerAlign },
    // 所有的表尾列的对齐方式
    footerAlign: { type: String, default: () => GlobalConfig.table.footerAlign },
    // 给行附加 className
    rowClassName: {
      type: [String, Function],
      default: () => {
        return ''
      },
    },
    // 给单元格附加 className
    cellClassName: [String, Function],
    // 给表头的行附加 className
    headerRowClassName: [String, Function],
    // 给表头的单元格附加 className
    headerCellClassName: [String, Function],
    rowConfig: {
      type: Object,
    },
    // 列默认配置项
    columnConfig: {
      type: Object,
      default: () => GlobalConfig.table.columnConfig,
    },
    // 快捷菜单配置项(右键)
    menuConfig: {
      type: Object,
    },
    // 展开行配置项
    expandConfig: {
      type: Object,
    },
    // 编辑配置项
    editConfig: {
      type: Object,
    },
    // 合并指定单元格
    mergeCells: {
      type: Array,
    },
    tooltipConfig: {
      type: Object,
    },
    // 树形结构配置项
    treeConfig: [Boolean, Object],
    radioConfig: {
      type: Object,
    },
    checkboxConfig: {
      type: Object,
    },
    // 设置所有内容过长时显示为省略号
    showOverflow: { type: [Boolean, String], default: () => GlobalConfig.table.showOverflow },
    // 设置表头所有内容过长时显示为省略号; 要开启横向虚拟滚动 此项必须为true
    showHeaderOverflow: {
      type: [Boolean, String],
      default: () => GlobalConfig.table.showHeaderOverflow,
    },
    // 设置表尾所有内容过长时显示为省略号; 要开启横向虚拟滚动 此项必须为true
    showFooterOverflow: {
      type: [Boolean, String],
      default: () => GlobalConfig.table.showFooterOverflow,
    },
    // 是否显示表尾合计
    showFooter: {
      type: Boolean,
    },
    // 表尾数据
    footerData: {
      type: Array,
    },
    // 自定义合并行或列的方法
    spanMethod: Function,
    // 表尾合并行或列
    footerSpanMethod: Function,
    // 给表尾的行附加 className
    footerRowClassName: [String, Function],
    // 给表尾的单元格附加 className
    footerCellClassName: [String, Function],
    // 给单元格附加样式
    cellStyle: [Object, Function],
    // 需要计算合计的列的key, 二维数组, key可以是无序的 [['第一行key1', '第一行key2'],['第二行key1','第二行key2']], 如果只传一个数组,后续计算则复用
    summaryColKeyList: Array,
    /**
     * 合计描述及计算规则, 对应footerMethod方法的返回值(多行合计数组),即:
     * [{columnIndex: '第一行合计行描述列下标', text: '描述', ruleName: '计算规则(求和)'},{columnIndex: '第二行合计行描述列下标',text: '描述', ruleName: '计算规则(平均值)'}]
     * 计算规则参数(ruleName): add 求和, avg 平均值 , 默认值:add。结果保留2位小数
     */
    sumConfig: {
      type: Array,
      required: true,
      default: () => {
        return [{ columnIndex: 1, text: '合计(元)', ruleName: 'add' }]
      },
    },
    /**
     * 如果不想自动求和,比如直接使用后端请求回来的数据,则需要手动设置summaryData。此时将不启用sumConfig中的(ruleName)计算规则进行计算
     * 对象数组(对应summaryColKeyList中的key) [{第一行key1:'第一行key1的值', 第一行key2:'第一行key2的值'},...]
     */
    summaryData: Array,
  },
  data() {
    return {
      height: '', // 表格高度
    }
  },
  watch: {
    tableData: {
      handler(newValue) {
        let _self = this
          if (!validatenull(newValue)) {
            _self.autoHeight()
            _self.$nextTick(()=>{
              _self.$refs["vxetableRef"].reloadData(_self.tableData)
              // 官方的展开 只有再数据初始化时才生效且只执行一次,而在表格嵌套在tab中且懒加载数据时是不会再触发展开功能的。
              // 所以这里手动触发一下,但也只触发一次。expandInit: false 表示只触发一次
              _self.$nextTick(()=>{
                setTimeout(()=>{
                  if(!_self.treeOpts?.expandInit &&_self.treeConfig?.expandAll){
                    _self.treeOpts.expandInit = true
                    _self.$refs["vxetableRef"].setAllTreeExpand(true)
                  }
                },0)
              })
            })
          }else{
            _self.autoHeight()
            _self.$nextTick(()=>{
              _self.$refs["vxetableRef"].reloadData([])
            })
          }
      },
      immediate: true,
    },
  },
  computed: {
    // tData() {
    //   if (!validatenull(this.tableData)) {
    //     this.autoHeight()
    //   }
    //   return this.tableData
    // },
    columnOpts() {
      return Object.assign({}, GlobalConfig.table.columnConfig, this.columnConfig)
    },
    rowOpts() {
      return Object.assign({}, GlobalConfig.table.rowConfig, this.rowConfig)
    },
    resizeOpts() {
      return Object.assign({}, GlobalConfig.table.resizeConfig, this.resizeConfig)
    },
    radioOpts() {
      return Object.assign({}, GlobalConfig.table.radioConfig, this.radioConfig)
    },
    checkboxOpts() {
      return Object.assign({}, GlobalConfig.table.checkboxConfig, this.checkboxConfig)
    },
    tooltipOpts() {
      return Object.assign({}, GlobalConfig.table.tooltipConfig, this.tooltipConfig)
    },
    editOpts() {
      return Object.assign({}, GlobalConfig.table.editConfig, this.editConfig)
    },
    treeOpts() {
      return Object.assign({}, GlobalConfig.table.treeConfig, this.treeConfig)
    },
    ctxMenuOpts() {
      return Object.assign({}, GlobalConfig.table.menuConfig, this.menuConfig)
    },
    loadingOpts() {
      return Object.assign({}, GlobalConfig.table.loadingConfig, this.loadingConfig)
    },
  },
  mounted() {
    let _self = this
    _self.exportMethodForVxeTable()
    // 动态计算高度
    _self.autoHeight()    
    // 监听页面缩放, 重新计算表格高度
    window.onresize = XEUtils.debounce(() => {
      return (() => {
        // _self.autoHeight()
      })()
    }, 200)
  },
  methods: {
    // 表尾合计,
    footerMethod({ columns, data }) {
      let _self = this
      if(!_self.showFooter) return
      let sumRows = []
      if (!validatenull(_self.sumConfig)) {
        _self.sumConfig.forEach((conf, index) => {
          sumRows.push(
            columns.map((column, columnIndex) => {
              // 设置表尾合计的描述
              if (columnIndex == conf.columnIndex) {
                return conf.text
              }
              // 对符合要求的列进行计算
              if (!validatenull(_self.summaryColKeyList)) {
                let keyList = _self.summaryColKeyList[index]
                  ? _self.summaryColKeyList[index]
                  : _self.summaryColKeyList[0]
                if (!validatenull(keyList) && keyList?.includes(column.field)) {
                  if (!validatenull(_self.summaryData?.[index])) {
                    return _self.summaryData[index][column.field] || null
                  }
                  return _self.sumNum(data, column.field, conf.ruleName || 'add')
                }
              }
              return null
            })
          )
        })
      }
      return sumRows
    },
    // 根据sumConfig中配置的计算规则进行计算
    sumNum(list, field, rule) {
      let count = 0
      if (rule == 'add') {
        list.forEach((item) => {
          count = accAdd(count, Number(item[field]))
        })
        return Math.round(count * 100) / 100
      }
      if (rule == 'avg') {
        // 求平均...
        list.forEach((item) => {
          count += Number(item[field])
        })
        return Math.round((count / list.length) * 100) / 100
      }
    },
    // checkbox选中状态变换. 兼容el-table
    checkboxChange(params) {
      let selections = params.records || []
      let selectionIds = selections.map((item) => {
        return item.id
      })
      this.$emit('selection-change', selections, selectionIds, params)
    },
    // 计算table页面高度: 如果传入自定义高度,则使用自定义高度,否则动态计算高度
    autoHeight() {
      this.$nextTick(() => {
        if (this.maxHeight == null) {
          if (this.tableHeight) {
            this.height = this.tableHeight
          } else {
            this.height = getPageIndexHeight(this.dynamicHeight, this.tableData.length > 0)
          }
        }
      })
    },
    // checkbox全选状态变换. 兼容el-table
    checkboxAll(params) {
      let selections = params.records || []
      this.$emit('select-all', selections, params)
      // 兼容el-table 触发逻辑,select-all会联动selection-change
      let selectionIds = selections.map((item) => {
        return item.id
      })
      this.$emit('selection-change', selections, selectionIds, params)
    },
    // 抛出vxe-*组件自带的指定方法,以便提供给refs.xxx方式的调用 如果你无需重写官方自带方法,都不建议再写emit或者prop。请保持方法名的唯一性。
    // ps: 通过ref获取vxetable的方法进行循环注册会造成页面假死。可能是vxe自带的方法是在太多了,挂再this上会很影响性能。
    exportMethodForVxeTable(){
      const names = ['loadData','reloadData','getRowNode','getTableData','getData','remove','setAllTreeExpand','insertNextAt','getScroll','scrollToRow','scrollTo','setCurrentRow','isCheckedByRadioRow','cell-click']
      for (let i = 0; i < names.length; i++) {
        const key = names[i];
        if(this.$refs.vxetableRef[key]){
          this[key] = this.$refs.vxetableRef[key]
        }
      }
    }
  },
}
</script>

二、同级创建配置文件conf.js

export default {
  table: {
    fit: true,
    align: 'left',
    headerAlign: 'left',
    footerAlign: 'left',
    showHeader: true,
    animat: true,
    delayHover: 250,
    autoResize: true,
    minHeight: null,
    maxHeight: null,
    keepSource: false,
    showOverflow: true,
    showHeaderOverflow: true,
    showFooterOverflow: true,
    // size: null,
    // zIndex: null,
    stripe: true,
    border: true,
    // round: false,
    // emptyText: '暂无数据',
    // emptyRender: {
    //   name: ''
    // },
    loadingConfig: { icon: 'el-icon-loading', text: '加载中...' },
    rowConfig: {
      // keyField: '_X_ROW_KEY', // 行数据的唯一主键字段名
      // 是否需要为每一行的 VNode 设置 key 属性
      boolean: false,
      // 自定义行数据唯一主键的字段名(默认自动生成)
      // 当鼠标点击行时,是否要高亮当前行
      isCurrent: false,
      // 当鼠标移到行时,是否要高亮当前行
      isHover: false,
    },
    columnConfig: {
      resizable: true,
    },
    resizeConfig: {
      refreshDelay: 250,
    },
    radioConfig: {
      // trigger: 'default'
      strict: true,
    },
    checkboxConfig: {
      // trigger: 'default',
      strict: true,
    },
    tooltipConfig: {
      // 所有单元格开启工具提示 boolean
      showAll: false,
      // tooltip 的主题颜色string  dark,light
      theme: 'dark',
      // 鼠标是否可进入到工具提示中boolean
      enterable: false,
      // 鼠标移入后延时多少才显示工具提示number
      enterDelay: 500,
      // 鼠标移出后延时多少才隐藏工具提示number
      leaveDelay: 300,
    },
    validConfig: {
      showMessage: true,
      autoClear: true,
      autoPos: true,
      message: 'inline',
      msgMode: 'single',
    },
    menuConfig: {
      enabled: false, // 是否启用
    },
    customConfig: {
      allowVisible: true,
      allowResizable: true,
      allowFixed: true,
      allowSort: true,
      showFooter: true,
      placement: 'topRight',
      //  storage: false,
      //  checkMethod () {},
      modalOptions: {
        showZoom: true,
        mask: true,
        lockView: true,
        resize: true,
        escClosable: true,
      },
    },
    sortConfig: {
      // remote: false,
      // trigger: 'default',
      // orders: ['asc', 'desc', null],
      // sortMethod: null,
      showIcon: true,
      iconLayout: 'vertical',
    },
    filterConfig: {
      // remote: false,
      // filterMethod: null,
      showIcon: true,
    },
    treeConfig: {
      rowField: 'id',
      parentField: 'parentId',
      childrenField: 'children',
      hasChildField: 'hasChild',
      indent: 20,
      showIcon: true,
      expandInit: false
    },
    expandConfig: {
      // trigger: 'default',
      showIcon: true,
    },
    editConfig: {
      // mode: 'cell',
      showIcon: true,
      showAsterisk: true,
    },
    importConfig: {
      _typeMaps: {
        csv: 1,
        html: 1,
        xml: 1,
        txt: 1,
      },
      modes: ['insert', 'covering'],
    },
    exportConfig: {
      _typeMaps: {
        csv: 1,
        html: 1,
        xml: 1,
        txt: 1,
      },
      modes: ['current', 'selected'],
    },
    printConfig: {
      modes: ['current', 'selected'],
    },
    mouseConfig: {
      extension: true,
    },
    keyboardConfig: {
      isEsc: true,
    },
    scrollX: {
      // enabled: false,
      gt: 60,
      // oSize: 0
    },
    scrollY: {
      // enabled: false,
      gt: 100,
      // oSize: 0
    },
  },
  export: {
    types: {},
  },
  pager: {
    pageSizePlacement: 'top',
    // size: null,
    // autoHidden: false,
    // perfect: true,
    // pageSize: 10,
    // pagerCount: 7,
    // pageSizes: [10, 15, 20, 50, 100],
    // layouts: ['PrevJump', 'PrevPage', 'Jump', 'PageCount', 'NextPage', 'NextJump', 'Sizes', 'Total']
  },
}

三. 目录结构

仅供参考(示例):
在这里插入图片描述


四. 注册调用, 可使用其它方式注册

注册(示例):

// components/components.js
import Vue from 'vue'
import CustomVxeTable from '@/components/vxe-table'
Vue.component('CustomVxeTable', CustomVxeTable)
// main.js
import './components/components'

调用(示例):

<custom-vxe-table
      border
      :max-height="475"
      show-footer
      :merge-footer-items="[{ row: 0, col: 1, rowspan: 1, colspan: 3 }]"
      :summaryColKeyList="[['num1', 'age'], ['rate']]"
      :sumConfig="[{columnIndex: 1, text: '合计(元)' },{columnIndex: 1, text: '平均值(元)', ruleName: 'avg' }]"
      :table-data="tableData"
      @selection-change="handleSelectionChange">
      <template v-slot:column>
        <vxe-column
          type="seq"
          title="序号"
          width="60"></vxe-column>
        <vxe-colgroup title="统计信息">
          <vxe-column
            field="name"
            title="Name"
            :edit-render="{}">
            <template #edit="{ row }">
              <vxe-input
                v-model="row.name"
                type="text"></vxe-input>
            </template>
          </vxe-column>
          <vxe-column
            field="age"
            title="Age"
            :edit-render="{ autofocus: '.vxe-input--inner' }">
            <template #edit="{ row }">
              <vxe-input
                v-model="row.age"
                type="text"
                :min="1"
                :max="120"
                @change="updateFooterEvent"></vxe-input>
            </template>
          </vxe-column>
          <vxe-column
            field="num1"
            title="Num"
            :edit-render="{ autofocus: '.vxe-input--inner' }">
            <template #edit="{ row }">
              <vxe-input
                v-model="row.num1"
                type="text"
                @input="updateFooterEvent"></vxe-input>
            </template>
          </vxe-column>
          <vxe-column
            field="rate"
            title="Rate"
            :edit-render="{}">
            <template #edit="{ row }">
              <vxe-input
                v-model="row.rate"
                type="text"
                @input="updateFooterEvent"></vxe-input>
            </template>
          </vxe-column>
        </vxe-colgroup>
      </template>
    </custom-vxe-table>

表格测试数据

tableData: [
        {
          id: 10001,
          name: 'Test1',
          nickname: 'T1',
          role: 'Develop',
          sex: '0',
          sex2: ['0'],
          num1: 40,
          age: 28.2,
          rate: 22,
        },
        {
          id: 10002,
          name: 'Test2',
          nickname: 'T2',
          role: 'Designer',
          sex: '1',
          sex2: ['0', '1'],
          num1: 23,
          age: 22.1,
          rate: 34,
        },
        {
          id: 10003,
          name: 'Test3',
          nickname: 'T3',
          role: 'Test',
          sex: '0',
          sex2: ['1'],
          num1: 200,
          age: 32.1,
          rate: 18,
        },
        {
          id: 10004,
          name: 'Test4',
          nickname: 'T4',
          role: 'Designer',
          sex: '1',
          sex2: ['1'],
          num1: 30,
          age: 23.3,
          rate: 13,
        },
        {
          id: 10005,
          name: 'Test5',
          nickname: 'T5',
          role: 'Develop',
          sex: '0',
          sex2: ['1', '0'],
          num1: 20,
          age: 30.01,
          rate: 6,
        },
        {
          id: 10006,
          name: 'Test6',
          nickname: 'T6',
          role: 'Designer',
          sex: '1',
          sex2: ['0'],
          num1: 10,
          age: 21.03,
          rate: 33,
        },
        {
          id: 10007,
          name: 'Test7',
          nickname: 'T7',
          role: 'Develop',
          sex: '0',
          sex2: ['0'],
          num1: 5,
          age: 29,
          rate: 4,
        },
        {
          id: 10008,
          name: 'Test8',
          nickname: 'T8',
          role: 'PM',
          sex: '1',
          sex2: ['0'],
          num1: 2,
          age: 35,
          rate: 55,
        },
      ],

五. 链接指引

💡 关于vxe-table的使用心得及扩展【表格虚拟滚动】(非插件)
💡💡 关于vxe-table的使用心得及扩展3【vxe-table二次封装组件应用】(非插件)

vxe-table是一个基于Vue.js的强大的表格组件库,它提供了丰富的功能和灵活的配置选项,可以用于展示和处理大量的数据。而vxe-table二次封装是指在vxe-table的基础上进行进一步的封装和定制,以满足特定项目或业务需求。 在进行vxe-table二次封装时,可以根据具体需求进行以下操作: 1. 封装常用的表格组件:根据项目需求,可以将常用的表格组件进行封装,以便在不同页面或模块中复用。例如,可以封装一个通用的表格组件,包含常用的列配置、分页、排序等功能。 2. 定制样式和主题:通过修改vxe-table的样式文件或者使用自定义主题,可以使表格组件与项目整体风格保持一致。这样可以提升用户体验,并增加项目的专属感。 3. 扩展功能和事件:根据项目需求,可以通过扩展vxe-table的功能和事件来满足特定的业务逻辑。例如,可以添加自定义的筛选、导出、编辑等功能,并监听相应的事件进行处理。 4. 封装数据请求和处理逻辑:在二次封装中,可以将数据请求和处理逻辑进行封装,以便在使用表格组件时更加方便地进行数据的获取和展示。可以通过封装API接口、数据转换等方式来实现。 5. 提供文档和示例:在进行vxe-table二次封装后,可以编写相应的文档和示例,以便其他开发人员能够更好地理解和使用封装后的表格组件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值