Three.js地球可视化实战:华为云同款特效的核心技术解析

2025-04-29 0 303

在浏览网页时,笔者发现华为云一个有趣的效果,就是将地球上布局的城市标注出来,当城市出现在地球正面视线范围内时,就显示出来,而在靠近边缘时,就慢慢地隐藏直至消失不见;那么这种效果是如何实现的呢?里面又包含了哪些的逻辑呢?本文我们就来看下这个效果的实现过程。

我们先来看下效果示例:

Three.js地球可视化实战:华为云同款特效的核心技术解析
全球坐标分布效果

❝由于gif文件太大了,这里就不截取动图效果了,感兴趣的小伙伴可以直接滑到文末查看实现的效果。

环境准备

首先还是准备我们画布的基础环境,初始化场景、相机、渲染器、控制器四件套:

export defaultclass Index {
constructor() {
    // 初始化场景
    this.scene = initScene();
    this.scene.background = new Color(0xf7f7f7);

    // 初始化相机
    this.camera = initCamera(new Vector3(10, 0, 0), 55, 0.001, 20000);

    // 初始化渲染器
    this.renderer = initRenderer();

    // 初始化控制器哦
    this.controls = initOrbitControls(this.camera, this.renderer);
    // 禁止缩放
    this.controls.enableZoom = false;
    // 阻尼
    this.controls.enableDamping = true;
    // 自动旋转
    this.controls.autoRotate = true;
  }
}

我们再向场景中添加一个地球,这里我们直接用一个地球的纹理贴图即可:

// 球体的半径大小
const SPHERE_RADIUX = 3;

initMesh() {
    this.loader = new TextureLoader();
    const mat = new MeshBasicMaterial({
        map: this.loader.load("ditu.jpg"),
    });
    const geo = new SphereGeometry(SPHERE_RADIUX);
    const sphere = new Mesh(geo, mat);
    sphere.position.set(0, 0, 0);
    this.scene.add(sphere);
}

下面就需要在地球🌍上贴上一个个的城市定位了,这里用到一个新的渲染器:CSS2DRenderer,这个渲染器用于将Html元素嵌入到3D场景中去,用于在场景中展示一些额外的信息;比如VR看房时候的标签,使用html标签有更好的操控,比如使用CSS还可以实现点击、悬浮、激活等等效果。

我们在初始化场景的时候添加一个CSS2DRenderer渲染器:

import {
  CSS2DRenderer,
  CSS2DObject,
} from"three/examples/jsm/renderers/CSS2DRenderer.js";

exportdefaultclass Index {
constructor() {
    // 其他场景代码
    const labelRenderer = new CSS2DRenderer();
    labelRenderer.setSize(window.innerWidth, window.innerHeight);
    labelRenderer.domElement.style.position = "absolute"; // 设置渲染器样式
    labelRenderer.domElement.style.top = "0";
    labelRenderer.domElement.style.left = "0";
    labelRenderer.domElement.style.zIndex = "1";
    this.labelRenderer = labelRenderer;
    document.body.appendChild(labelRenderer.domElement);

    // 这里修改
    this.controls = initOrbitControls(this.camera, this.labelRenderer, false);
  }
}

❝这里要注意的是,我们在初始化controls控制器的时候,需要修改将CSS2DRenderer传入控制器的构造函数中,否则就会出现画布无法转动的情况。

下面我们就需要将Html标签添加到CSS2DRenderer中去,

const list = [
  { id: 0, name: "北京", lng: 116.39, lat: 39.9 },
// 省略其他城市
];

const tagsList = [];
for (let i = 0; i < list.length; i++) {
const { name, lng, lat } = list[i];

const pos = this.latLongToVector3(lng, lat, SPHERE_RADIUX);

const box = document.createElement("div");
  box.className = "global_position-box";
  box.innerHtml = name;

const tag = new CSS2DObject(box);
  tag.position.copy(pos);
this.scene.add(tag);
  tagsList.push(tag);
}

this.tagsList = tagsList;

这里在循环列表的时候,需要将数据的经纬度坐标转换成在三维空间里的x、y、z坐标,我们用到一个latLongToVector3函数进行转换处理,我们下面会介绍到这个函数。

通过CSS2DObject实例化标签后,设置标签的左边;但是将标签添加到页面上去后,我们会看到,如果是在我们视线背面的标签,同时也显现出来了。

Three.js地球可视化实战:华为云同款特效的核心技术解析
CSS2DObject效果

这样的效果肯定不是我们想要的,因此,我们需要在每次render的时候,就需要不断的去控制每个html标签的透明度,当标签在我们视线后面的时候就设置为0,这才是我们想要的效果;那么标签的透明怎么来计算呢?我们先来学习三个工具函数的使用。

经纬度vs球坐标系

首先我们要学习的一个就是经纬度和三维球体坐标转换的一个关系函数,它也是Three.js中做三维地图经常会遇到的一个问题,下面我们就来看下它的原理和实现逻辑。

我们知道在三维坐标系中,我们用xyz能很快确定一个点在空间上的坐标;但是在球体坐标系中,我们需要另外三个参数来确定一个点的位置,我们看下数学中是如何来表示的:

  • 径向距离:也就是我们常说的球体半径,是球面坐标点到球心的距离,用r表示。
  • 极角:是z轴与r的交角,一般用θ表示。
  • 方位角:是赤道面(由 x 轴与 y 轴确定的平面)上起始于 x 轴,沿逆时针方向量出的角度,通常用φ表示。

❝我们假设地球是一个完美的球体。

Three.js地球可视化实战:华为云同款特效的核心技术解析
球体坐标

❝需要注意的是,Three.js中,y轴和z轴与数学描述中的位置是相反的,即y轴是纵向的,z轴是从后往前延伸的;这也导致了下面代码中y和z的计算方式与公式的计算方式互换。

如果用公式来表示,直角坐标和球坐标的对应关系如下:

Three.js地球可视化实战:华为云同款特效的核心技术解析
转换公式

公式有了,我们下面就要来看公式中的极角θ和方位角φ分别如何来得到;我们知道,纬度是点相对于赤道平面的角度,从-90°的南极到90°的北极,而极角是Three.js中的Y轴和点之间的夹角,因此北极是0,赤道是90,而南极是180;因此我们需要用90减去纬度,再通过度数和弧度的转换即可得到如下代码:

const phi = (90 - latitude) * (Math.PI / 180);

其次是方位角,由于是沿逆时针方向量出的角度,我们对其取反:

const theta = (longitude + 180) * (Math.PI / 180); 

❝但是我们实际开发中拿到的地图不一定是标准的地图,需要对方位角进行处理,不一定是加180。

极角和方位角得到了,我们通过公式得到一个标准的经纬度转换三维空间坐标的函数如下:

/**
 * 将经纬度转换为三维空间坐标
 * @param {number} longitude - 经度(-180到180)
 * @param {number} latitude - 纬度(-90到90)
 * @param {number} radius - 球体半径
 * @returns {THREE.Vector3} 返回Three.js的三维向量坐标
 */
function latLongToVector3(longitude, latitude, radius): Vector3 {
// 极角(从北极开始)
    const phi = (90 - latitude) * (Math.PI / 180); 
    // 方位角(从本初子午线开始)
    const theta = (longitude + 180) * (Math.PI / 180); 

    // 计算球体上的点坐标(Y轴向上)
    const x = -radius * Math.sin(phi) * Math.cos(theta);
    const y = radius * Math.cos(phi);
    const z = radius * Math.sin(phi) * Math.sin(theta);

    returnnew THREE.Vector3(x, y, z);
}

角度计算函数

下面我们再来看一个数学问题:

❝在三维空间中,已知ABC三个点的坐标,求每个点的角度?

经常学高数的朋友都知道,不要把它想象成三维,而是一个平面上的三角形;根据下面三角形的余弦定理

Three.js地球可视化实战:华为云同款特效的核心技术解析
余弦定理

我们看上面的公式,根据任意三条边的长度,我们都可以计算出角度的余弦值;因此我们需要一个函数来计算三维空间下两个点之间的距离:

// 计算空间上的两个点之间的距离
export function calc3DPointDist(x1, y1, z1, x2, y2, z2) {
  const distX = x2 - x1;
  const distY = y2 - y1;
  const distZ = z2 - z1;
  return Math.sqrt(distX * distX + distY * distY + distZ * distZ);
}

有了这个函数,我们就可以计算每个角对应边的长度了:

/**
 * 已经ABC三维坐标,求各个点的角度
 * @param {*} A
 * @param {*} B
 * @param {*} C
 * @param {String} pos
 * @returns
 */
export function calc3DAngle(A, B, C, pos = "A") {
  // 三角形每条边的长度
  const a = calc3DPointDist(B.x, B.y, B.z, C.x, C.y, C.z);
  const b = calc3DPointDist(A.x, A.y, A.z, C.x, C.y, C.z);
  const c = calc3DPointDist(A.x, A.y, A.z, B.x, B.y, B.z);
  // ...
}

将上面的余弦定理继续推导一下,我们可以得到每个角度的cos计算公式:

Three.js地球可视化实战:华为云同款特效的核心技术解析
余弦定理推导

再利用Math.acos,我们就得到了ABC三个角的角度;再通过传入的参数pos,直接得到我们想要角的角度:

export function calc3DAngle(A, B, C, pos = "A") {
    // 省略上面a、b、c的计算
    let cosA = Math.acos((b * b + c * c - a * a) / (b * c * 2));
    let cosB = Math.acos((a * a + c * c - b * b) / (a * c * 2));
    let cosC = Math.acos((a * a + b * b - c * c) / (a * b * 2));
  
    return {
      A: (cosA * 180) / Math.PI,
      B: (cosB * 180) / Math.PI,
      C: (cosC * 180) / Math.PI,
    }[pos];
}

clamp函数

clamp函数很多同学可能都没用过,一般在c++或者python中用的比较多,它的作用是;它需要传入三个值:

function clamp(num, min, max) {
  // ...
}

三个参数分别代表如下含义:

  • num:需要判断的数值。
  • min:范围的最小值。
  • max:范围的最大值。

为了方便大家理解,我们还是举几个例子🌰来简单看下:

// 返回10,小于最小值,返回最小值
clamp(5, 10, 20)


// 返回16,在返回内,返回原值
clamp(16, 10, 20)


// 返回20,超出最大值,返回最大值
clamp(36, 10, 20)

通过三个demo,相信大家就能理解这个函数的作用了,函数的实现其实也非常简单:

function clamp(num, min, max) {
  return Math.min(Math.max(num, min), max);
}

显示还是隐藏标签

对上面两个函数理解后,我们就可以回到地球上坐标的处理了;我们在threejs每次render的时候循环tagsList:

{
  render() {
    // 当前摄像头的位置
    const cameraPos = this.camera.position;

    if (this.tagsList && this.tagsList.length) {
      this.tagsList.map((tag) => {
        const { position, element } = tag;
      });
    }
  }
}

首先我们将tag打印出来看下,里面有两个属性position和element,是我们所需要的;position属性是一个Vector3类型,表示tag当前的位置信息,element属性是一个dom节点,表示标签对应的dom元素。

我们想象一下,在三维空间中,我们的相机位置Camera一直在旋转的,因为设置了autoRotate自动旋转;而标签的位置position是固定的;因此这两个点和原点之间就形成了一个特殊的三角形:

Three.js地球可视化实战:华为云同款特效的核心技术解析
三维视角的位置

临界情况就是以原点为顶点的角正好是90度,此时我们刚刚能看到标签;当相机位置不断旋转时,如果小于90度,我们还是可以看到标签的;但是如果大于90度,标签已经到了球体的后面了。

const originPoint = new Vector3(0, 0, 0)
this.tagsList.map((tag) => {
  const { position } = tag;
  // 以原点为顶点的角度
  const ang = calc3DAngle(cameraPos, originPoint, position, "B");
  // 省略其他
});

❝这里传入的顺序无所谓,我们只要计算以原点为顶点的角度即可。

利用上面的calc3DAngle函数,我们将三个位置传入,就可以很轻松的得到角度ang;有了这个角度,我们就可以计算标签的透明度了;我们上面提到了,透明度的临界值就是90度,但是实际上由于视角和球体的缘故,这个角度不是很准确,笔者测试之后大概是在85度左右。

同时,我们的标签也并不是一下子透明度就从1变到0的,我们需要给它一个缓冲范围,让它也缓缓,在这个范围内会进行变化;这个范围大致就是从80度到85度之间,透明度会从1到0逐渐的变化。

Three.js地球可视化实战:华为云同款特效的核心技术解析
透明度映射

看到这样的映射关系图,相信大家已经猜到了,没错,这里就要用到我们上面介绍的clamp函数了,我们将角度ang夹到80到85之间,然后使用scale函数进行映射后就得到了我们的透明度opacity,给element元素的样式赋值即可:

/**
 * 映射范围
 * @param {Number} number 需要映射的数值
 * @param {Number} inMin 映射入口的最小
 * @param {Number} inMax 映射入口的最大
 * @param {Number} outMin 映射出口的最小
 * @param {Number} outMax 映射出口的最大
 */
exportfunction scale(number, inMin, inMax, outMin, outMax) {
return ((number - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
}

const ANG1 = 80;
const ANG2 = 85;

this.tagsList.map((tag) => {
// 省略其他代码
const opacity = scale(clamp(ang, ANG1, ANG2), ANG1, ANG2, 1, 0);
  tag.element.style.opacity = opacity;
});

这样我们的标签就实现了一个过渡变化;最后,在vue页面卸载之后,别忘记还需要将CSS2DRenderer渲染器的dom节点删除,否则会导致页面会有问题:

{
  beforeDestroy() {
    if (this.labelRenderer) {
      document.body.removeChild(this.labelRenderer.domElement);
    }
  }
}

我们来看下实现的效果https://gallery.xieyufei.com/case/three/global,跟原页面的效果已经十分接近了。❞

总结

我们发现很多3D场景下的问题,其本质都是一个个数学问题;本文我们研究了球体和经纬度转换函数、三个点之间的角度计算函数,这两个问题无不考验着我们的数学推理能力;笔者甚至发现,在完成这个案例的过程中,学习的数学知识,甚至比写代码的时间更长、更费时间。

源码
<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>3D Globe with Labeled Points</title>  <style>    body { margin0overflow: hidden; }    canvas { display: block; }    .label-box {      backgroundrgba(0000.7);      color: white;      padding5px 10px;      border-radius5px;      font-family: Arial, sans-serif;      font-size12px;      text-align: center;      min-width100px;    }    .label-title { margin0font-weight: bold; }    .label-desc { margin2px 0 0 0font-size10px; }    .label-position {      width6px;      height6px;      background: red;      border-radius50%;      margin5px auto;    }    .global_position-box {      display: flex;      justify-content: center;    }    .cont { display: flex; flex-direction: column; align-items: center; }  </style></head><body>  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>  <script src="https://cdn.jsdelivr.net/npm/three@0.134.0/examples/js/controls/OrbitControls.js"></script>  <script src="https://cdn.jsdelivr.net/npm/three@0.134.0/examples/js/renderers/CSS2DRenderer.js"></script>  <script>    const { CSS2DRendererCSS2DObject } = THREE;    class Globe {      constructor() {        this.scene = new THREE.Scene();        this.scene.background = new THREE.Color(0xf7f7f7);        this.camera = new THREE.PerspectiveCamera(          55,          window.innerWidth / window.innerHeight,          0.001,          20000        );        this.camera.position.set(1000);        this.renderer = new THREE.WebGLRenderer({ antialiastrue });        this.renderer.setSize(window.innerWidthwindow.innerHeight);        this.renderer.setPixelRatio(window.devicePixelRatio);        this.renderer.setClearColor(0xf7f7f7);        this.renderer.autoClear = false;        document.body.appendChild(this.renderer.domElement);        this.labelRenderer = new CSS2DRenderer();        this.labelRenderer.setSize(window.innerWidthwindow.innerHeight);        this.labelRenderer.domElement.style.position = "absolute";        this.labelRenderer.domElement.style.top = "0";        this.labelRenderer.domElement.style.left = "0";        this.labelRenderer.domElement.style.zIndex = "9";        document.body.appendChild(this.labelRenderer.domElement);        this.controls = new THREE.OrbitControls(this.camerathis.labelRenderer.domElement);        this.controls.enableZoom = false;        this.controls.enableDamping = true;        this.controls.dampingFactor = 0.05;        this._resizeFn = this.resizeFn.bind(this);        window.addEventListener("resize"this._resizeFn);        this.loader = new THREE.TextureLoader();        this.loadAssets();      }      loadAssets() {
      // 也可以下载作者的纹理      // https://gallery.xieyufei.com/images/global/ditu.jpg      this.loader.load(          "https://threejs.org/examples/textures/planets/earth_atmos_2048.jpg",          (texture) => {            console.log("纹理加载成功");            this.map = texture;            this.initMesh();            this.render();          },          (progress) => {            console.log(`纹理加载进度: ${(progress.loaded / progress.total * 100).toFixed(2)}%`);          },          (error) => {            console.error("纹理加载失败:", error.message || error);            this.map = null;            this.initMesh();            this.render();          }        );      }      initMesh() {        const SPHERE_RADIUS = 3;        const material = new THREE.MeshBasicMaterial({          mapthis.map || null,          colorthis.map ? 0xffffff : 0xaaaaaa,        });        const geometry = new THREE.SphereGeometry(SPHERE_RADIUS);        const sphere = new THREE.Mesh(geometry, material);        sphere.position.set(000);        this.scene.add(sphere);        const list = [          { id0name"北京"lng116.39lat39.9 },          { id1name"上海"lng121.39lat31.25 },          { id2name"广东"lng113.25lat23.13 },          { id3name"新加坡"lng104.51lat1.18 },          { id4name"曼谷"lng100.49lat13.75 },          { id5name"伊斯坦布尔"lng28.58lat41.02 },          { id6name"哈萨克斯坦"lng68lat48 },          { id7name"瑞典"lng20lat65 },          { id8name"墨西哥"lng: -105lat24 },          { id9name"加拿大"lng: -105lat64 },          { id10name"巴西"lng: -56lat: -18 },          { id11name"俄罗斯"lng100lat70 },          { id12name"澳大利亚"lng133lat: -25 },          { id13name"印尼"lng122lat: -2.5 },          { id14name"南非"lng27lat: -27 },          { id15name"沙特阿拉伯"lng45lat25 },          { id16name"阿尔及利亚"lng6lat25 },        ];        this.tagsList = [];
        // 标签样式可以根据 class 自定义        list.forEach(({ name, lng, lat }) => {          const p1 = document.createElement("p");          p1.className = "label-title";          p1.innerHTML = name;          const p2 = document.createElement("p");          p2.className = "label-desc";          p2.innerHTML = `业务数量:${Math.floor(Math.random() * 25) + 5}`;          const labelBox = document.createElement("div");          labelBox.className = "label-box";          labelBox.append(p1, p2);          const posTag = document.createElement("div");          posTag.className = "label-position";          const pos = this.latLongToVector3(lng, lat, SPHERE_RADIUS);          const box = document.createElement("div");          box.className = "global_position-box";          const cont = document.createElement("div");          cont.className = "cont";          cont.append(labelBox, posTag);          box.append(cont);          const tag = new CSS2DObject(box);          tag.position.copy(pos);          this.scene.add(tag);          this.tagsList.push(tag);        });      }      latLongToVector3(longitude, latitude, radius) {        const phi = (90 - latitude) * (Math.PI / 180);        const theta = (longitude + 30) * (Math.PI / 180);        const x = -radius * Math.sin(phi) * Math.cos(theta);        const y = radius * Math.cos(phi);        const z = radius * Math.sin(phi) * Math.sin(theta);        return new THREE.Vector3(x, y, z);      }      resizeFn() {        this.camera.aspect = window.innerWidth / window.innerHeight;        this.camera.updateProjectionMatrix();        this.renderer.setSize(window.innerWidthwindow.innerHeight);        this.labelRenderer.setSize(window.innerWidthwindow.innerHeight);      }      render() {        this.renderer.render(this.scenethis.camera);        this.labelRenderer.render(this.scenethis.camera);        this.controls.update();        const ANG1 = 80;        const ANG2 = 85;        const originPoint = new THREE.Vector3(000);        if (this.tagsList) {          this.tagsList.forEach((tag) => {            const angle = this.calc3DAngle(this.camera.position, originPoint, tag.position);            const opacity = this.scale(this.clamp(angle, ANG1ANG2), ANG1ANG210);            tag.element.style.opacity = opacity;          });        }        this.timer = requestAnimationFrame(this.render.bind(this));      }      calc3DAngle(A, B, C) {        const AB = A.clone().sub(B).normalize();        const BC = C.clone().sub(B).normalize();        const angle = Math.acos(AB.dot(BC)) * (180 / Math.PI);        return angle;      }      clamp(value, min, max) {        return Math.max(min, Math.min(max, value));      }      scale(value, inMin, inMax, outMin, outMax) {        return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;      }      beforeDestroy() {        if (this.timercancelAnimationFrame(this.timer);        window.removeEventListener("resize"this._resizeFn);        if (this.labelRenderer.domElement) {          document.body.removeChild(this.labelRenderer.domElement);        }      }    }    const globe = new Globe();    window.addEventListener("beforeunload"() => globe.beforeDestroy());  </script></body></html>
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

免责声明 1、百创网作为第三方中介平台,依据交易合同(商品描述、交易前商定的内容)来保障交易的安全及买卖双方的权益; 2、非平台线上交易的项目,出现任何后果均与百创网无关;无论卖家以何理由要求线下交易的,请联系管理举报。 3. 百创网网站的资源均由店家上传出售,本站无法判断和识别资源的版权等合法性属性。如果您对本网站上传的信息资源的版权存有异议,请您及时联系 我们。如果需要删除链接,请下载下面的附件,正确填写信息后并发给我们,本站核实信息真实性后,在24小时内对商品进行删除处理。 联系邮箱:baicxx@baicxx.com (相关事务请发函至该邮箱)

百创网-源码交易平台_网站源码_商城源码_小程序源码 行业资讯 Three.js地球可视化实战:华为云同款特效的核心技术解析 https://www.baicxx.com/30505.html

下一篇:

已经没有下一篇了!

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、百创会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、百创无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在百创上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于百创介入快速处理。
查看详情
  • 1、百创作为第三方中介平台,依据交易合同(商品描述、交易前商定的内容)来保障交易的安全及买卖双方的权益; 2、非平台线上交易的项目,出现任何后果均与百创无关;无论卖家以何理由要求线下交易的,请联系管理举报。
查看详情
  • 免责声明 1、百创网作为第三方中介平台,依据交易合同(商品描述、交易前商定的内容)来保障交易的安全及买卖双方的权益; 2、非平台线上交易的项目,出现任何后果均与百创网无关;无论卖家以何理由要求线下交易的,请联系管理举报。 3. 百创网网站的资源均由店家上传出售,本站无法判断和识别资源的版权等合法性属性。如果您对本网站上传的信息资源的版权存有异议,请您及时联系 我们。如果需要删除链接,请下载下面的附件,正确填写信息后并发给我们,本站核实信息真实性后,在24小时内对商品进行删除处理。 联系邮箱:baicxx@baicxx.com (相关事务请发函至该邮箱)
查看详情

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务

  • 0 +

    访问总数

  • 0 +

    会员总数

  • 0 +

    文章总数

  • 0 +

    今日发布

  • 0 +

    本周发布

  • 0 +

    运行天数

你的前景,远超我们想象