Cesium 100K数据加载 支持弹窗 动态更改位置

18 篇文章 4 订阅
16 篇文章 20 订阅
  • 前言:今天总结关于point、label、billboard海量数据加载。后续会研究下大量model加载以及大bim(几百G上T)模型记载

海量点加载

在这里插入图片描述

  • 弹窗

加载点位时,不加载弹窗。点击点位时在加载弹窗,及有效的减少加载量,优化性能。

const handler = new Cesium.ScreenSpaceEventHandler();
handler.setInputAction(function (movement: any) {
    const pickedLabel = viewer.scene.pick(movement.position);
    if (Cesium.defined(pickedLabel)) {
      console.log(pickedLabel)
      staticPoins.some(i => {
        if (i.id === pickedLabel.id.id || i.id === pickedLabel.id) {
          changePopWins(i)
          return true
        }
      })
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  function changePopWins(item: Point) {
  let flag = popWins.value.some(i => {
    if (i.id === item.id) {
      i.visible = !i.visible
      return true
    }
  })
  if (!flag) {
    popWins.value.push({ ...item, visible: true })
  }
}
  • dom-tag组件
<template>
  <div class="cesium-domPoint" ref="element">
    <slot :data="props.data">
    </slot>
  </div>
</template>
<script lang="ts" setup>
import * as Cesium from "cesium";
import { onMounted, onUnmounted, ref, PropType } from "vue";
import type { EalignX, EalignY } from "../typing";
const props = defineProps({
  eventClick: Function,
  data: Object,
  trackPos: {
    type: Object as PropType<Cesium.Cartesian3>,
  },
  trackEntity: {
    type: Object as PropType<Cesium.Entity>,
  },
  alignX: {
    type: String as PropType<EalignX>,
  },
  alignY: {
    type: String as PropType<EalignY>,
  },
  trackCursor: Boolean,
});
const { Viewer } = window;
let element = ref<HTMLDivElement | null>(null),
  mousePos: Cesium.Cartesian2,
  trackCursor = props.trackCursor;
const handler = new Cesium.ScreenSpaceEventHandler();
const onUpdate = () => {
  if (element == null) return;
  let screenPos;
  if (trackCursor) {
    screenPos = mousePos;
  } else if (props.trackEntity) {
    let pos =
      (props.trackEntity.position &&
        props.trackEntity.position.getValue(Cesium.JulianDate.now())) ||
      Cesium.Cartesian3.ZERO;
    screenPos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
      Viewer.scene,
      pos
    );
  } else if (props.trackPos) {
    screenPos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
      Viewer.scene,
      props.trackPos
    );
  }
  if (screenPos) {
    if (element.value) {
      switch (props.alignX) {
        case "left":
          element.value.style.left = screenPos.x + "px";
          break;
        case "center":
          element.value.style.left =
            screenPos.x + element.value.clientWidth * 0.5 + "px";
          break;
        case "right":
        default:
          element.value.style.left =
            screenPos.x - element.value.clientWidth * 0.5 + "px";
      }

      switch (props.alignY) {
        case "top":
          element.value.style.top =
            screenPos.y - element.value.clientHeight + "px";
          break;
        case "bottom":
          element.value.style.top = screenPos.y + "px";
          break;
        case "center":
        default:
          element.value.style.top =
            screenPos.y - element.value.clientHeight * 0.5 + "px";
      }
      if (parseFloat(element.value.style.top) < (-element.value.clientWidth) || parseFloat(element.value.style.left) < (-element.value.clientHeight) || parseFloat(element.value.style.left) > (document.body.clientWidth + element.value.clientWidth) || parseFloat(element.value.style.top) > (document.body.clientHeight + element.value.clientHeight)) {
        element.value.style.display = "none";
      }
      else {
        element.value.style.display = "";
      }
    }
  } else {
    if (element.value) element.value.style.display = "none";
  }
};
onMounted(() => {
  if (trackCursor) {
    handler.setInputAction((event: any) => {
      let offsetToLeftTop = Viewer.container.getBoundingClientRect();
      mousePos = Cesium.Cartesian2.subtract(
        event.endPosition,
        new Cesium.Cartesian2(offsetToLeftTop.left, offsetToLeftTop.top),
        new Cesium.Cartesian2()
      );
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  }
  Viewer.scene.preUpdate.addEventListener(onUpdate);
});
onUnmounted(() => {
  if (Viewer && !Viewer.isDestroyed()) {
    Viewer.scene.preUpdate.removeEventListener(onUpdate);
  }
  handler && handler.destroy()
});
</script>
<style lang="less" scoped>
.cesium-domPoint {
  position: absolute;
  z-index: 0;
}
</style>
 <template v-for="item in popWins" :key="item.id">
    <DomTag :trackPos="item.pos" v-if="item.visible">
      <PopWin :point="item" @tooglePopWin="tooglePopWin1" />
    </DomTag>
  </template>
  • 最常用的就是entity

不加载图片以及文字的时候20W的点都没问题,虽然帧数就10左右,但是流畅度还行

for (let i = 0; i < 500; i++) {
     for (let j = 0; j < 400; j++) {
       viewer.entities.add({
         point: {
           pixelSize: 5,
           color: Cesium.Color.BEIGE
         },
         position: Cesium.Cartesian3.fromDegrees(103 + i * 0.1, 30 + j * 0.1)
       })  
     }
   }

在这里插入图片描述

添加文字后(20K),虽然也是10侦左右但是会感到明显的卡顿

for (let i = 0; i < 50; i++) {
  for (let j = 0; j < 400; j++) {
    viewer.entities.add({
      point: {
        pixelSize: 5,
        color: Cesium.Color.BEIGE
      },
      label: {
        text: i+""
      },
      position: Cesium.Cartesian3.fromDegrees(103 + i * 0.1, 30 + j * 0.1)
    })  
  }
}

在这里插入图片描述

  • 添加图片

图片较小只有179个字节。20k的数据流畅度可以,图片越大流畅度越低

 viewer.entities.add({
  point: {
      pixelSize: 5,
      color: Cesium.Color.BEIGE
    },
    billboard: {
      image: 'icons/facility.gif'
    },
    // label: {
    //   text: i+""
    // },
    position: Cesium.Cartesian3.fromDegrees(103 + i * 0.1, 30 + j * 0.1)
  })  

在这里插入图片描述

  • 总结:entity加载,图片越大流畅度越低,点越密集流畅度越低。层级越高流畅度越高(密度下降)。

entity聚合

添加(10k)聚合功能,首次加载时间延长,但是加载成功后流畅度显著提高,并且entity可以同时添加文字和图片
/*
本质上还是利用的是entity加载,还是慢加载时间长
加载完成后性能提升
*/
在这里插入图片描述

  let staticPoins: Point[] = []
  for (let i = 0; i < 500; i++) {
    for (let j = 0; j < 200; j++) {
      let obj = {
        visible: false,
        id: i + '_' + j,
        onlinetime: "2023-02-21 11:32:22",
        accountname: `${i}_${j}`,
        differentialstate: 7,
        curH: 474.59999999999997,
        curB: 30 + i * 0.1,
        curL: 104 + 0.1 * j,
        satellitenumber: 50,
        dopvalue: 0.6000000238418579,
        delay: 0,
        reserver1: "153403.172851563",
        reserver2: "NTRIP GNSSInternetRadio/1.4.5",
        pos: Cesium.Cartesian3.fromDegrees(104.06657490833334, 30.63132543),
      }
      obj.pos = Cesium.Cartesian3.fromDegrees(obj.curL, obj.curB)
      staticPoins.push(obj)
    }
  }
  staticPoins.forEach((i, j) => {
    if (j < 10000)
      dataSource.entities.add({
        id: i.id,
        point: {
          pixelSize: 0
        },
        label: {
          text: i.accountname,
          font: '20px sans-serif',
          showBackground: true,
          // verticalOrigin: Cesium.VerticalOrigin.TOP,
          pixelOffset: new Cesium.Cartesian2(0, -65),
          distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 10e5),//根据Label与相机的距离来获取或设置Label的近和远像素偏移缩放比例属性
          // eyeOffset: new Cesium.Cartesian3(0, 7.2, 0)
          heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND
        },
        billboard: {
          image: 'icons/facility.gif',
          scaleByDistance: new Cesium.NearFarScalar(1.5e2, 1, 1.5e7, 0),//根据相机距离缩放(下限、下限的值、上限、上限的值)
          width: 16,
          height: 16,
          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // //相对于对象的原点(注意是原点的位置)的水平位置
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        },
        position: i.pos
      })
  })
  const dataSourcePromise = viewer.dataSources.add(dataSource);
  dataSourcePromise.then(function (dataSource) {
    const pixelRange = 15;
    const minimumClusterSize = 3;
    const enabled = true;
    dataSource.clustering.enabled = enabled; //是否聚合
    dataSource.clustering.pixelRange = pixelRange;
    dataSource.clustering.minimumClusterSize = minimumClusterSize;
    const pinBuilder = new Cesium.PinBuilder();
    const pin1000 = pinBuilder
      .fromText("1000+", Cesium.Color.RED, 48)
      .toDataURL();
    const pin500 = pinBuilder
      .fromText("100+", Cesium.Color.RED, 48)
      .toDataURL();
    const pin100 = pinBuilder
      .fromText("100+", Cesium.Color.RED, 48)
      .toDataURL();
    const pin50 = pinBuilder
      .fromText("50+", Cesium.Color.RED, 48)
      .toDataURL();
    const pin40 = pinBuilder
      .fromText("40+", Cesium.Color.ORANGE, 48)
      .toDataURL();
    const pin30 = pinBuilder
      .fromText("30+", Cesium.Color.YELLOW, 48)
      .toDataURL();
    const pin20 = pinBuilder
      .fromText("20+", Cesium.Color.GREEN, 48)
      .toDataURL();
    const pin10 = pinBuilder
      .fromText("10+", Cesium.Color.BLUE, 48)
      .toDataURL();
    const singleDigitPins = new Array(8);
    for (let i = 0; i < singleDigitPins.length; ++i) {
      singleDigitPins[i] = pinBuilder
        .fromText(`${i + 2}`, Cesium.Color.VIOLET, 48)
        .toDataURL();
    }
    function customStyle() {
      if (Cesium.defined(removeListener)) {
        removeListener && removeListener();
        removeListener = undefined;
      } else {
        removeListener = dataSource.clustering.clusterEvent.addEventListener(
          function (clusteredEntities, cluster) {
            cluster.label.show = false;
            cluster.billboard.show = true;
            cluster.billboard.id = cluster.label.id;
            cluster.billboard.verticalOrigin =
              Cesium.VerticalOrigin.BOTTOM;

            if (clusteredEntities.length >= 1000) {
              cluster.billboard.image = pin1000;
            } else if (clusteredEntities.length >= 500) {
              cluster.billboard.image = pin500;
            } else if (clusteredEntities.length >= 100) {
              cluster.billboard.image = pin100;
            } else if (clusteredEntities.length >= 50) {
              cluster.billboard.image = pin50;
            } else if (clusteredEntities.length >= 40) {
              cluster.billboard.image = pin40;
            } else if (clusteredEntities.length >= 30) {
              cluster.billboard.image = pin30;
            } else if (clusteredEntities.length >= 20) {
              cluster.billboard.image = pin20;
            } else if (clusteredEntities.length >= 10) {
              cluster.billboard.image = pin10;
            } else {
              cluster.billboard.image =
                singleDigitPins[clusteredEntities.length - 2];
            }
          }
        );
      }
      const pixelRange = dataSource.clustering.pixelRange;
      dataSource.clustering.pixelRange = 0;
      dataSource.clustering.pixelRange = pixelRange;
    }
    customStyle();
  })

dom加载

利用坐标点的变化时候改变dom元素的位置
优点:自由度高
缺点:适合几百以内的数据。

<template v-for="item in points" :key="item.id">
  <DomTag :trackPos="item.pos">
    <Dev :point="item" @tooglePopWin="tooglePopWin" />
  </DomTag>
</template>
  • PointPrimitiveCollection

加载PointPrimitiveCollection点集合,速度快且流畅,也是推荐的方式。
缺点只能加载点

在这里插入图片描述

let pointPrimitives: Cesium.PointPrimitiveCollection;
pointPrimitives = scene.primitives.add(
 new Cesium.PointPrimitiveCollection()
);
for(let i = 0; i < 100000; i++) {
pointPrimitives.add({
 id: i.id,
 pixelSize: 10,
 color: color,
 outlineColor: outlineColor,
 outlineWidth: 0,
 label: {
   text: 222
 },
 distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
   5.5e3
 ),
 position: i.pos,
});
}
  • 点位移动

这个方法,移动十万个点位,很流畅

viewer.scene.preUpdate.addEventListener(animateBillboards);
function animateBillboards() {
  const moveAmount = new Cesium.Cartesian3(100, 0.0, 0.0);
  const positionScratch = new Cesium.Cartesian3();
  // @ts-ignore
  const billboards = pointPrimitives._pointPrimitives;
  const length = billboards.length;
  for (let i = 0; i < length; ++i) {
    const billboard = billboards[i];
    Cesium.Cartesian3.clone(billboard.position, positionScratch);
    Cesium.Cartesian3.add(positionScratch, moveAmount, positionScratch);
    billboard.position = positionScratch;
  }
}
  • billoardPrimitives

同上,只能加载图片。

let billoardPrimitives: Cesium.BillboardCollection
billoardPrimitives = viewer.scene.primitives.add(
    new Cesium.BillboardCollection({
      scene: scene,
    })
  );
for(let i = 0; i < 100000; i++) {
billoardPrimitives.add({
 id: i.id,
 image: 'icons/facility.gif',
 width: 10,
 height: 10,
 position: i.pos
});
}
  • Primitive和聚合搭配着使用

cesium官方未能提供primitive的聚合方法,但是可以用entity的聚合搭配着使用。

  • CommomSiteTookit
import * as Cesium from "cesium";
import { defaultValue } from 'cesium'


type Option = {
    delay?:number;
    enabled?: boolean;
    pixelRange?:number;
    minimumClusterSize?:number;
}
/**
 * @_v 引入外部创建的Viewer实例(new Cesium.Viewer(...))
 * @myPrimitives 原语集合,可以包含页面显示的pointPrimitiveCollection、billboardCollection、labelCollection、primitiveCollection、primitiveCluster
 * @myPrimitiveCluster 自定义原语集群
 * @myBillboardCollection 广告牌集合(站点显示的内容数据)
 *
 * @desc 使用primitiveCollection原语集合与primitiveCluster原语集群,处理地图界面显示广告牌billboard数量 > 10w 级时,界面卡顿,浏览器崩溃等问题
 */
class CommomSiteTookit {
  static _v: Cesium.Viewer;
  myPrimitives: Cesium.PrimitiveCollection | null = null
  myPrimitiveCluster:any = null;
  myBillboardCollection:Cesium.BillboardCollection|null = null;
  constructor() {}

  /**
   * @desc 使用commomSiteTookit实例前,必须先初始化该实例的_v对象
   */
  init(viewer: Cesium.Viewer) {
    CommomSiteTookit._v = viewer;
  }

  /**
   * @param [options] 具有以下属性的对象
   * @param [options.delay=800] 防抖处理定时器的time
   * @param [options.enabled=true] 是否启用集群
   * @param [options.pixelRange=15] 用于扩展屏幕空间包围框的像素范围
   * @param [options.minimumClusterSize=2] 可集群的屏幕空间对象的最小数量
   *
   * @desc 处理原语集合,并实现聚合集群功能方法
   * @return billboardCollection集合,可直接往集合里添加广告牌billboard,呈现在页面上
   */
  load(options:Option = {}) {
    let billboardCollection = new Cesium.BillboardCollection();
    let labelboardCollection = new Cesium.LabelCollection();
    if (Cesium.defined(this.myPrimitives)) {
        CommomSiteTookit._v.scene.primitives.remove(this.myPrimitives);
    }
    this.myPrimitives = CommomSiteTookit._v.scene.primitives.add(
      new Cesium.PrimitiveCollection()
    );
    //@ts-ignore
    const primitiveCluster = new Cesium.PrimitiveCluster();
    this.myPrimitives && this.myPrimitives.add(primitiveCluster);
    primitiveCluster.delay = defaultValue(options.delay, 800);
    primitiveCluster.enabled = defaultValue(options.enabled, true);
    primitiveCluster.pixelRange = defaultValue(options.pixelRange, 15);
    primitiveCluster.minimumClusterSize = defaultValue(
      options.minimumClusterSize,
      2
    );
    primitiveCluster._billboardCollection = billboardCollection;
    primitiveCluster._initialize(CommomSiteTookit._v.scene);

    let removeListener:any;
    let pinBuilder = new Cesium.PinBuilder();
    /* 定义广告牌 fromText(显示文字,颜色,大小) */
    const pin1000 = pinBuilder
      .fromText("1000+", Cesium.Color.RED, 48)
      .toDataURL();
    const pin500 = pinBuilder
      .fromText("500+", Cesium.Color.RED, 48)
      .toDataURL();
    const pin100 = pinBuilder
      .fromText("100+", Cesium.Color.RED, 48)
      .toDataURL();
    const pin50 = pinBuilder
      .fromText("50+", Cesium.Color.RED, 48)
      .toDataURL();
    const pin40 = pinBuilder
      .fromText("40+", Cesium.Color.ORANGE, 48)
      .toDataURL();
    const pin30 = pinBuilder
      .fromText("30+", Cesium.Color.YELLOW, 48)
      .toDataURL();
    const pin20 = pinBuilder
      .fromText("20+", Cesium.Color.GREEN, 48)
      .toDataURL();
    const pin10 = pinBuilder
      .fromText("10+", Cesium.Color.BLUE, 48)
      .toDataURL();
    /* 数量小于十个的聚合广告牌 */
    let singleDigitPins = new Array(8);
    for (let i = 0; i < singleDigitPins.length; ++i) {
      singleDigitPins[i] = pinBuilder
        .fromText("" + (i + 2), Cesium.Color.VIOLET, 40)
        .toDataURL();
    }

    const _ = this;
    function customStyle() {
      if (Cesium.defined(removeListener)) {
        removeListener();
        removeListener = undefined;
      } else {
        removeListener = primitiveCluster.clusterEvent.addEventListener(
          function(clusteredEntities:any, cluster:any) {
            cluster.label.show = false;
            cluster.billboard.show = true;
            cluster.billboard.id = cluster.label.id;
            cluster.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
            /* 根据站点(参数)的数量给予对应的广告牌  */
            if (clusteredEntities.length >= 1000) {
                cluster.billboard.image = pin1000;
              } else if (clusteredEntities.length >= 500) {
                cluster.billboard.image = pin500;
              } else if (clusteredEntities.length >= 100) {
                cluster.billboard.image = pin100;
              } else if (clusteredEntities.length >= 50) {
                cluster.billboard.image = pin50;
              } else if (clusteredEntities.length >= 40) {
                cluster.billboard.image = pin40;
              } else if (clusteredEntities.length >= 30) {
                cluster.billboard.image = pin30;
              } else if (clusteredEntities.length >= 20) {
                cluster.billboard.image = pin20;
              } else if (clusteredEntities.length >= 10) {
                cluster.billboard.image = pin10;
              } else {
                cluster.billboard.image =
                  singleDigitPins[clusteredEntities.length - 2];
              }
          }
        );
      }
      // force a re-cluster with the new styling
      let pixelRange = primitiveCluster.pixelRange;
      primitiveCluster.pixelRange = 0;
      primitiveCluster.pixelRange = pixelRange;
      _.myPrimitiveCluster = primitiveCluster;
    }
    this.myBillboardCollection = billboardCollection;
    // start with custom style
    customStyle();
    return billboardCollection;
  }

  /**
   * @params enable bool值控制开启或关闭集群
   * @desc 控制集群生效与否
   */
  enableCluster(enable:boolean) {
    if (Cesium.defined(this.myPrimitiveCluster)) {
      this.myPrimitiveCluster!.enabled = enable;
    }
  }

  /**
   * @params id 站点ID
   * @return 返回可操作的广告牌[siteBillboard.image = 'xxxx']
   * @desc 根据id在集合中获取指定站点广告牌
   */
  getSiteBillboardById(id: string) {
    if (!Cesium.defined(this.myBillboardCollection)) return undefined;
    const _b = this.myBillboardCollection!;
    const l = _b.length;
    let siteBillboard = undefined;
    for (let i = 0; i < l; i++) {
      if (id == _b.get(i).id) {
        siteBillboard = _b.get(i);
        break;
      }
    }
    return siteBillboard;
  }

  /**
   * @desc 删除所有站点广告牌
   */
  removeAll() {
    if (Cesium.defined(this.myPrimitives)) {
        CommomSiteTookit._v.scene.primitives.remove(this.myPrimitives);
    }
  }

  /**
   * @params show bool值 控制显示或隐藏
   * @desc 隐藏或显示所有站点广告牌
   */
  showStatus(show = true) {
    this.myPrimitives!.show = show;
  }

  /**
   * @desc 根据id删除指定站点广告牌
   */
  remove(id:string) {
    const billboard = this.getSiteBillboardById(id);
    billboard && this.myBillboardCollection!.remove(billboard);
  }

  /**
   * @desc 销毁(目前退出页面时直接viewer销毁)
   */
  destroy() {
    this.myPrimitives = null;
    this.myPrimitiveCluster = null;
    this.myBillboardCollection = null;
    // this._v.scene.primitives.destroy()
  }
}

export default new CommomSiteTookit();

 CommomSiteTookit.init(viewer)
  const data = CommomSiteTookit.load({
    enabled: true,
    delay: 1200,
    pixelRange: 20
  });
 for(let i = 0; i  < 100000; i++) {
	data.add({
        image: 'icons/facility.gif',
        scaleByDistance: new Cesium.NearFarScalar(1.5e2, 1, 1.5e7, 0.2),
        width: 16,
        height: 16,
        position: i.pos,
        id: i.id
      });
}
    ```


  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是基于Cesium实现自定义弹窗效果的代码示例: ```javascript // 创建弹窗 function createPopup(position, content) { var popup = document.createElement("div"); popup.className = "cesium-popup"; popup.innerHTML = content; document.body.appendChild(popup); // 将弹窗位置转换为屏幕坐标 var positionOnScreen = Cesium.SceneTransforms.wgs84ToWindowCoordinates( viewer.scene, position ); // 设置弹窗位置 popup.style.left = positionOnScreen.x + "px"; popup.style.top = positionOnScreen.y + "px"; // 监听场景变化,更新弹窗位置 viewer.scene.postRender.addEventListener(function () { var positionOnScreen = Cesium.SceneTransforms.wgs84ToWindowCoordinates( viewer.scene, position ); popup.style.left = positionOnScreen.x + "px"; popup.style.top = positionOnScreen.y + "px"; }); return popup; } // 使用示例 var position = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883); var content = "<h1>Hello, World!</h1>"; var popup = createPopup(position, content); ``` 上述代码中,`createPopup`函数用于创建弹窗,接受两个参数:弹窗位置弹窗内容。在函数内部,首先创建一个`div`元素作为弹窗容器,并将其添加到`body`元素中。然后,使用`Cesium.SceneTransforms.wgs84ToWindowCoordinates`方法将弹窗位置从世界坐标系转换为屏幕坐标系,并设置弹窗位置。最后,监听场景变化事件,更新弹窗位置。 使用示例中,我们创建了一个位于经度`-75.59777`、纬度`40.03883`的弹窗,并设置弹窗内容为`<h1>Hello, World!</h1>`。 --相关问题--: 1. 如何在Cesium中添加自定义图层? 2. 如何在Cesium中添加3D模型? 3. 如何在

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值