Element-plus虚拟表格 el-table-v2自定义排序功能,添加边框,选择框

该文章已生成可运行项目,

由于数据量过大,且需求是不允许分页,需要大量操作数据,需要使用el-table-v2虚拟表格来完成,但是他的样式以及功能与el-table有些差异,查找很多文档,并没有与我的需求相吻合的,需要自己来完成,记录一下,方便大家参考

成果截图

代码放在下面了

基本结构

<div v-loading="loading" style="height: calc(100vh - 84px - 140px)">
      <el-auto-resizer>
        <template #default="{ height, width }">
          <el-table-v2
            :cache="8"
            :columns="columns"
            :data="sysInvoiceList"
            :width="width"
            :height="height"
            :row-height="58"
            fixed
            @scroll="tableScroll"
          />
        </template>
      </el-auto-resizer>
    </div>

ElDicTag是我的内部组件,请注意不要引入、使用 

import { TableV2FixedDir, Column } from 'element-plus';
import ElDicTag from '@/components/DictTag/index.vue';
import { CaretTop, CaretBottom } from '@element-plus/icons-vue';

 自定义选择列单元格组件

function SelectionCell({ value, indeterminate = false, onChange, disabled = false }) {
  return h(ElCheckbox, {
    onChange: onChange,
    modelValue: value,
    preventDefault: true,
    indeterminate,
    disabled,
    onClick: (e) => e.stopPropagation() //阻止冒泡
  });
}

 自定义单元格排序组件

function SortIconCell(title: string, key) {
  let { isAsc, orderByColumn } = queryParams.value;
  return h(
    'div',
    {
      class: 'customize_header',
      onClick: ($event) => {
        customizeSortChange({ $event, prop: key });
      }
    },
    [
      h('div', {}, title),
      h(
        'div',
        {
          class: 'sort_view'
        },
        [
          h('i', {
            class: ['sort-caret ascending', { 'is_active_sort_button': isAsc == 'ascending' && orderByColumn == key }],
            onClick: ($event) => {
              customizeSortChange({ $event, order: 'ascending', prop: key });
            }
          }),
          h('i', {
            class: ['sort-caret descending', { 'is_active_sort_button': isAsc == 'descending' && orderByColumn == key }],
            onClick: ($event) => {
              customizeSortChange({ $event, order: 'descending', prop: key });
            }
          })
        ]
      )
    ]
  );
}

自定义单元格排序方法

const toggleOrder = ({ order, prop }) => {
  let { orderByColumn } = queryParams.value;
  if (orderByColumn != prop) return 'descending';
  let sortOrders = ['descending', 'ascending', undefined];
  const index = sortOrders.indexOf(order);
  if (prop == 'djrq') sortOrders.pop();
  return sortOrders[index == sortOrders.length - 1 ? 0 : index + 1];
};
const customizeSortChange = ({ $event, order, prop }) => {
  event.stopPropagation();
  let { isAsc, orderByColumn } = queryParams.value;
  let isActive = isAsc == order && orderByColumn == prop;
  queryParams.value.isAsc = isActive ? undefined : order || toggleOrder({ order: isAsc, prop });
  queryParams.value.orderByColumn = isActive ? undefined : prop;
  queryParams.value.pageNum = 1;
  getList();
};

colums配置

const columns: Column[] = [
  {
    key: 'selection',
    width: 55,
    fixed: TableV2FixedDir.LEFT,
    align: 'center',
    cellRenderer: ({ rowData }) => {
      //必须添加这一句不然出现渲染错误,页面展示的就是复选框选中跟着滚动条一起动
      if (rowData.checked == undefined) rowData.checked = false;
      const onChange = (value: boolean) => {
        rowData.checked = value;
        value ? ids.value.push(rowData.id) : (ids.value = ids.value.filter((e) => e != rowData.id));
        single.value = ids.value.length != 1;
        multiple.value = !ids.value.length;
      };
      return h(SelectionCell, { value: rowData.checked, onChange });
    },
    headerCellRenderer: () => {
      const _data = sysInvoiceList.value;
      const onChange = (value) => {
        sysInvoiceList.value = _data.map((row) => {
          row.checked = value;
          return row;
        });
        ids.value = sysInvoiceList.value.filter((item) => item.checked).map((item) => item.id);
        single.value = ids.value.length != 1;
        multiple.value = !ids.value.length;
      };
      const allSelected = _data.length > 0 ? _data.every((row) => row.checked) : false;
      const containsChecked = _data.some((row) => row.checked);
      return h(SelectionCell, {
        value: allSelected,
        intermediate: containsChecked && !allSelected,
        onChange,
        disabled: _data.length > 0 ? false : true
      });
    }
  },
  {
    dataKey: 'djbh',
    key: 'djbh',
    width: 150,
    align: 'center',
    class: 'table_djbh',
    fixed: TableV2FixedDir.LEFT,
    headerCellRenderer: ({ column }) => SortIconCell('单据编号', column.dataKey)
  },
  {
    dataKey: 'khmc',
    key: 'khmc',
    width: 200,
    align: 'center',
    headerCellRenderer: ({ column }) => SortIconCell('购方名称', column.dataKey)
  },
  { dataKey: 'khsh', key: 'khsh', width: 200, align: 'center', headerCellRenderer: ({ column }) => SortIconCell('购方税号', column.dataKey) },
  {
    dataKey: 'fplxdm',
    key: 'fplxdm',
    title: '发票类型',
    width: 150,
    align: 'center',
    cellRenderer: ({ rowData }) => {
      return h(ElDicTag, {
        options: sys_invoice_type.value,
        value: rowData.fplxdm
      });
    }
  },
  {
    dataKey: 'status',
    key: 'status',
    title: '开票状态',
    width: 100,
    align: 'center',
    cellRenderer: ({ rowData }) => {
      return h(ElDicTag, {
        options: sys_invoice_kpzt.value,
        value: rowData.status
      });
    }
  },
  {
    dataKey: 'kz1',
    key: 'kz1',
    title: '渠道',
    width: 100,
    align: 'center',
    cellRenderer: ({ rowData }) => {
      return h(ElDicTag, {
        options: sys_invoice_vtweg.value,
        value: rowData.kz1
      });
    }
  },
  {
    dataKey: 'kz2',
    key: 'kz2',
    title: '工厂',
    width: 100,
    align: 'center',
    cellRenderer: ({ rowData }) => {
      return h(ElDicTag, {
        options: sys_invoice_werks.value,
        value: rowData.kz2
      });
    }
  },
  { dataKey: 'khlxfs', key: 'khlxfs', title: '购方联系方式', width: 140, align: 'center' },
  { dataKey: 'xfsh', key: 'xfsh', title: '销方税号', width: 200, align: 'center' },
  {
    dataKey: 'djrq',
    key: 'djrq',
    title: '单据日期',
    width: 170,
    align: 'center',
    headerCellRenderer: ({ column }) => SortIconCell('单据日期', column.dataKey)
  },
  { dataKey: 'je', key: 'je', title: '净值', width: 100, align: 'center', headerCellRenderer: ({ column }) => SortIconCell('净值', column.dataKey) },
  {
    dataKey: 'jshj',
    key: 'jshj',
    title: '价税合计',
    width: 100,
    align: 'center',
    headerCellRenderer: ({ column }) => SortIconCell('价税合计', column.dataKey)
  },
  { dataKey: 'se', key: 'se', title: '税额', width: 100, align: 'center', headerCellRenderer: ({ column }) => SortIconCell('税额', column.dataKey) },
  {
    dataKey: 'tax',
    key: 'tax',
    title: '税率',
    width: 100,
    align: 'center',
    cellRenderer: ({ rowData }) => {
      return h(ElDicTag, {
        options: sys_invoice_sl.value,
        value: rowData.tax
      });
    }
  },
  {
    dataKey: 'yxj',
    key: 'yxj',
    title: '操作',
    width: 146,
    align: 'center',
    style: { border: 'none' },
    fixed: TableV2FixedDir.RIGHT,
    headerCellRenderer: ({ column }) => {
      return h('div', { class: 'small-padding' }, ['操作', h('div', { class: 'icon_view', onClick: () => (drawer.value = true) }, h(Filter))]);
    },
    cellRenderer: ({ rowData }) => {
      return h('div', {}, [
        h(ElButton, { type: 'primary', size: 'small', onClick: () => handleLook(rowData) }, () => '查看'),
        h(ElButton, { type: 'primary', size: 'small', onClick: () => handleUpdate(false, rowData) }, () => '编辑')
      ]);
    }
  }
];

计算滚动条位置来判断是否展示固定列的阴影样式

let tableV2Style = {
  none: null,
  left: '2px 0 4px 0 rgba(0, 0, 0, 0.06)',
  right: '-2px 0 4px 0 rgba(0, 0, 0, 0.06)',
  clientWidth: 0,
  scrollWidth: 0
};
const tableScroll = ({ scrollLeft }) => {
  if (!scrollLeft) {
    let domLeft = document.getElementsByClassName('el-table-v2__left')[0];
    domLeft.style.boxShadow = tableV2Style.none = 'none';
  } else if (scrollLeft + tableV2Style.scrollWidth >= tableV2Style.clientWidth) {
    let domRight = document.getElementsByClassName('el-table-v2__right')[0];
    domRight.style.boxShadow = tableV2Style.none = 'none';
  } else {
    if (!tableV2Style.none) return;
    let domLeft = document.getElementsByClassName('el-table-v2__left')[0];
    let domRight = document.getElementsByClassName('el-table-v2__right')[0];
    tableV2Style.none = null;
    domLeft.style.boxShadow = tableV2Style.left;
    domRight.style.boxShadow = tableV2Style.right;
  }
};
onMounted(async () => {
  await getList();
  // 通过异步获取dom元素,防止获取为空
  setTimeout((v) => {
    let dom = document.getElementsByClassName('el-table-v2__header-wrapper')[0];
    tableV2Style.clientWidth = dom.children[0].clientWidth;
    tableV2Style.scrollWidth = dom.clientWidth;
  }, 100);
});

设置样式

::v-deep {
  .el-auto-resizer {
    border: 1px solid #ebeef5;
  }
  .el-table-v2__row-cell.is-align-center {
    border-right: 1px solid #ebeef5;
  }
  .el-table-v2__header-wrapper {
    position: relative;
    background-color: #f8f8f9;
    .icon_view {
      position: absolute;
      background-color: #fff;
      width: 27px;
      height: 27px;
      border-bottom-left-radius: 20px;
      top: 0;
      right: 0;
      display: flex;
      justify-content: center;
      align-items: center;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      cursor: pointer;
      :first-child {
        height: 15px;
        width: 15px;
        margin-left: 3px;
      }
    }
  }
  .el-table-v2__header-cell {
    border-right: 1px solid #ebeef5;
    background-color: #f8f8f9;
    color: #515a6e !important;
  }
  .el-table-v2__cell-text {
    color: #606266 !important;
  }
  .el-table-v2__left {
  }
  .el-table-v2__right {
    box-shadow: -2px 0 4px 0 rgba(0, 0, 0, 0.06);
  }
  .customize_header {
    width: 100%;
    height: 100%;
    display: flex;
    cursor: pointer;
    justify-content: center;
    align-items: center;
  }
  .sort_view {
    align-items: center;
    display: flex;
    flex-direction: column;
    height: 14px;
    overflow: initial;
    position: relative;
    vertical-align: middle;
    width: 24px;
    top: 1px;
  }
  .sort-caret {
    border: 5px solid transparent;
    height: 0;
    left: 7px;
    position: absolute;
    width: 0;
  }
  .sort-caret.ascending {
    border-bottom-color: #a8abb2;
    top: -5px;
  }
  .sort-caret.descending {
    border-top-color: #a8abb2;
    bottom: -3px;
  }
  .is_active_sort_button.ascending {
    border-bottom-color: #409eff;
  }
  .is_active_sort_button.descending {
    border-top-color: #409eff;
  }
}

目前功能全部实现,Element-plus内部有bug我已经向官方提出,正在修改中

 

本文章已经生成可运行项目
<template> <el-dialog class="infinite-list add-user-modal" v-model="visible" width="98%" top="5vh" center destroy-on-close append-to-body :close-on-click-modal="false" @close="onClose()"> <div class="el-dialog_ti"> <span>Benefit Details View</span> </div> <el-form ref="dataFormRef" :model="form" label-width="132px" > <el-card > <el-row :gutter="20"> <el-col :span="8"> <el-form-item label="Class Code"> {{props.classCode}} </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="Class Description"> {{props.className}} </el-form-item> </el-col> </el-row> <el-row></el-row> <el-card> <div slot="header" class="clearfix card-header"> <span><b>Benefit Details View</b></span> </div> <el-divider /> <template v-if="props.productPlan==='2'"> <div v-for="item in state.data"> <el-table :data="item.dataList" :span-method="objectSpanMethod" border style="width: 100%"> <el-table-column prop="benefitType" :label="props.productPlan==='2'?'Benefit Flag & Level':'Benefit Type'"> </el-table-column> <el-table-column prop="benefitItem" label="Benefit Item"> </el-table-column> <el-table-column prop="factorName" label="Factor Name"> </el-table-column> <el-table-column prop="applyTo" label="Apply To" width="180"> <template #default="{ row }"> {{getVal(row.applyTo)}} </template> </el-table-column> <el-table-column prop="factorValue" label="Factor Value" width="180"> </el-table-column> <el-table-column prop="description" label="Description"> </el-table-column> <el-table-column prop="dependantCovered" label="Dependant Covered"> </el-table-column> </el-table> <el-row></el-row> <br> </div> </template> <template v-else> <div v-for="item in paginatedData"> <el-row> <el-col :span="24"> <el-form-item label="Apply To"> <el-radio disabled aria-checked="true" style="width: auto"></el-radio> {{getVal(item.applyTo)}} </el-form-item> </el-col> </el-row> <el-table :data="item.tableData" border style="width: 100%" :span-method="(params) => spanMethod(item.tableData)(params)" :preserve-expanded-content="preserveExpanded"> <el-table-column prop="benefitType" :label="props.productPlan==='2'?'Benefit Flag & Level':'Benefit Type'"> </el-table-column> <el-table-column prop="benefitItemName" label="Benefit Item"> </el-table-column> <el-table-column type="expand"> <el-table-column prop="factorName" label="Factor Name"> </el-table-column> <el-table-column prop="factorValue" label="Non-Network" width="180"> <template #default="{ row }"> {{row.factorValue&&item.applyTo!=='1'?row.factorValue:'N/A'}} </template> </el-table-column> <el-table-column prop="factorValueNetwork" label="Network" width="180"> <template #default="{ row }"> {{row.factorValueNetwork&&(item.applyTo==='1'||item.applyTo==='3')?row.factorValueNetwork:(row.factorValue&&item.applyTo==='4'?row.factorValue:'N/A')}} </template> </el-table-column> <el-table-column prop="combined" label="Combined" width="180"> <template #default="{ row }"> {{row.combined&&(item.applyTo==='4'||item.applyTo==='3')?row.combined:'N/A'}} </template> </el-table-column> <el-table-column prop="description" label="Description"> </el-table-column> <el-table-column label="Dependant Covered"> <template #default="scope"> {{ scope.row.dependentCovered === '1' ? 'Yes' : scope.row.dependentCovered === '0' ? 'No' : '' }} </template> </el-table-column> </el-table-column> </el-table> <el-row></el-row> <br> </div> <el-row></el-row> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10, 20, 30, 50]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="state.data.length" /> <el-row></el-row> <el-row></el-row> <el-card v-if="!shareHide"> <div slot="header" class="clearfix card-header"> <span><b>Shared Factor</b></span> </div> <el-divider /> <el-table :data="props.groupSchemeShareBenefitFactors" border style="width: 100%"> <el-table-column prop="factorName" label="Factor Name"> <template #default="{ row }"> {{getVal2(row.factorCode,'1')}} </template> </el-table-column> <el-table-column prop="factorValue" label="Shared Benefit Type & Benefit Item"> <template #default="{ row }"> {{getVal2(row.benefitItems,row.itemEnumList)}} <!-- <el-select v-model="row.benefitItems" multiple>--> <!-- <el-option--> <!-- v-for="item in row.itemEnumList"--> <!-- :key="item.detailCode"--> <!-- :label="item.detailEnglishName"--> <!-- :value="item.detailCode"--> <!-- />--> <!-- </el-select>--> </template> </el-table-column> <el-table-column prop="applyTo" label="Apply to"> <template #default="{ row }"> {{getVal(row.applyTo)}} </template> </el-table-column> <el-table-column prop="factorValue" label="Non-Network"> <template #default="{ row }"> {{row.factorValue}} </template> </el-table-column> <el-table-column prop="factorValueNonNetwork" label="Network"> <template #default="{ row }"> {{row.factorValueNetwork}} </template> </el-table-column> <el-table-column prop="combined" label="Combined"> <template #default="{ row }"> {{row.combined}} </template> </el-table-column> <el-table-column prop="description" label="Description"> <template #default="{ row }"> {{row.description}} </template> </el-table-column> </el-table> </el-card> </template> </el-card> <el-row></el-row> <el-row> <el-col class="align_center"> <el-button @click="HandleCancel()" type="info" >Exit </el-button> </el-col> </el-row> </el-card> </el-form> </el-dialog> </template> <script setup> import {computed, reactive, ref, toRefs, watch} from "vue"; import {reqSearchBenefitAndItemFactor, reqSearchBenefitFactorInfo} from "@/api/interface"; import utils from "@/tools/utils"; import {useEnumStore} from "@/store/commons"; const visible = ref(false); const dataFormRef = ref(); const preserveExpanded = ref(false) const state = reactive({ data:[] }) const props = defineProps({ data: { type: Object, default: {} }, groupSchemeShareBenefitFactors:{ type: Array, default: [] }, classCode:{ type:String, default:'' }, className:{ type:String, default:'' }, productPlan:{ type:String, default:'' }, planCode:{ type:String, default:'' }, productCode:{ type:String, default:'' }, mainVersionNo:{ type:String, default:'' }, }) const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => { // 合并 'benefitType' 列:首行显示,后续行隐藏 if (column.property === 'benefitType') { return rowIndex === 0 ? { rowspan: row.span, colspan: 1 } : { rowspan: 1, colspan: 0 }; } // 合并 'benefitItem' 列:仅当 row.span > 0 时合并 if (column.property === 'benefitItem') { if (row.line > 0&&utils.isEmpty(row.span)) { return { rowspan: row.line, colspan: 1 }; // 修改为固定 colspan: 1 }else if(utils.isEmpty(row.span)&&utils.isNotEmpty(row.benefitItem)){ return { rowspan: 1, colspan: 0 }; } } // 默认返回,防止 undefined 导致渲染异常 return { rowspan: 1, colspan: 1 }; } const onClose = () => { visible.value = false } const HandleCancel = () => { onClose() } const shareHide = ref(false) const init = async (shareIsHide = false) => { shareHide.value = shareIsHide visible.value = true if(props.productPlan==='2'){ const param = { planCode:props.planCode,//'AXC001' props.planCode, productCode:props.productCode,// 'AXC', mainVersionNo:props.mainVersionNo,// 'Z202505093046' props.mainVersionNo, modeType:'0', } const { data} = await reqSearchBenefitAndItemFactor(param) state.data = JSON.parse(JSON.stringify(data??[])) state.data.map((item,index)=>{ state.data[index].dataList = [] if(utils.isNotEmpty(item?.planFactorDefines)){ item?.planFactorDefines.map((v,i)=>{ const obj = {} if(i===0){ obj.benefitType = item.coverGroupName obj.span = (item.planFactorDefines?.length || 0) + (item.dutyDetailList?.reduce((sum, ditem) => sum + (ditem.planFactorDefines?.length || 0), 0) || 0); } obj.factorName = v.factorName obj.factorValue = v.factorValue obj.applyTo = v.applyTo obj.factorValueNetwork = v.factorValueNetwork obj.factorValueNonNetwork = v.factorValueNonNetwork obj.description = v.description if(!obj.dependantCovered){ obj.dependantCovered = 'Yes' } state.data[index].dataList.push(obj) }) } if(utils.isNotEmpty(item?.dutyDetailList)){ item?.dutyDetailList.map((v,i)=>{ v?.planFactorDefines?.map((v2,i2)=>{ const obj = {} if(i2===0){ obj.line = v.planFactorDefines.length } obj.benefitItem = v.detailEnglishName obj.factorName = v2.factorName obj.factorValue = v2.factorValue obj.applyTo = v2.applyTo obj.factorValueNetwork = v2.factorValueNetwork obj.factorValueNonNetwork = v2.factorValueNonNetwork obj.description = v2.description if(!obj.dependantCovered){ obj.dependantCovered = 'Yes' } if(state.data[index].dataList.length===0){ obj.benefitType = item.dutyEngLishName obj.span = (item.planFactorDefines?.length || 0) + (item.dutyDetailList?.reduce((sum, ditem) => sum + (ditem.planFactorDefines?.length || 0), 0) || 0); } state.data[index].dataList.push(obj) }) }) } }) return } const { code , data } = await reqSearchBenefitFactorInfo({ planCode:props.planCode, mainVersionNo:props.mainVersionNo, dutyDetailCode:'', modeType:'1' }) if(code === 200){ state.itemEnumList = [...data] } state.data = JSON.parse(JSON.stringify(props.data??[])) // console.log(props.data) state.data.forEach((info) => { let result1 = info?.groupSchemeBenefitFactors; result1?.forEach(v => { v.dependentCovered = info?.dependentCovered; v.benefitType = info?.benefitType; }); info?.groupSchemeBenefitItems?.forEach(item => { item.groupSchemeBenefitItemFactors.map((factor) => { //factor.description = item?.description; factor.benefitItemName = item?.benefitItemName; factor.dependentCovered = item?.dependentCovered; return factor; }); result1 = [...(result1 || []), ...(item?.groupSchemeBenefitItemFactors || [])]; result1?.forEach(v => { v.benefitType = info?.benefitType; //v.description = item?.description; //v.dependentCovered = item?.dependentCovered; }); }); info.tableData = result1; }); } const spanMethod = (tableData) => ({ row, column, rowIndex, columnIndex }) => { // 合并第一列:benefitType if (columnIndex === 0) { let count = 1; for (let i = rowIndex + 1; i < tableData.length; i++) { if (tableData[i].benefitType === row.benefitType) { count++; } else { break; } } if (rowIndex > 0 && tableData[rowIndex - 1]?.benefitType === row.benefitType) { return { rowspan: 0, colspan: 0 }; } return { rowspan: count, colspan: 1 }; } // 合并第二列:benefitItemName if (columnIndex === 1) { let count = 1; for (let i = rowIndex + 1; i < tableData.length; i++) { if (tableData[i].benefitItemName === row.benefitItemName) { count++; } else { break; } } if (rowIndex > 0 && tableData[rowIndex - 1]?.benefitItemName === row.benefitItemName) { return { rowspan: 0, colspan: 0 }; } return { rowspan: count, colspan: 1 }; } // 其他列不合并 return { rowspan: 1, colspan: 1 }; }; let enumStore; let enumList = reactive({}); enumStore = useEnumStore(); enumStore.setEnumList().then(()=>{ Object.assign(enumList, enumStore.enumList) }) const getVal = (val,type='E00141') =>{ if (!enumList || !enumList[type]) return ''; //state.itemEnumList const item = enumList[type].find(item => item.valueCode === val); return item ? item.valueEnglishName : 'N/A'; } const getVal2 = (val,type) =>{ if(type==='1'){ if (!state.itemEnumList) return ''; const item = state.itemEnumList.find(item => item.factorCode === val); return item ? item.factorName : ''; } else { if (!type) return ''; const itemType = type && Array.isArray(type) ? type.filter(item => { const valSet = new Set(val); return valSet.has(item.detailCode); }) : undefined; const item = itemType && itemType.length > 0 ? itemType.map(it=>it.detailEnglishName) : undefined; return item ? item.toString() : ''; } } // 分页相关状态 const currentPage = ref(1); const pageSize = ref(20); // 计算当前页显示的数据 const paginatedData = computed(() => { const start = (currentPage.value - 1) * pageSize.value; const end = start + pageSize.value; return state.data.slice(start, end); }); // 分页事件处理 const handleSizeChange = (val) => { pageSize.value = val; }; const handleCurrentChange = (val) => { currentPage.value = val; }; defineExpose({ init }); </script> <style scoped lang="less"> :deep(.el-form-item),:deep(.el-form-item__label){ font-size: 12px; justify-content:flex-start; } .lg-label :deep(.el-form-item__label){ line-height: 16px; } </style>
最新发布
11-21
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值