Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $YECBGYFECGEAFWHA as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2

Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $BBWFDDBHHYHDXXAB as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2
在Angular中集成Three.js并管理画布布局_创想鸟

在Angular中集成Three.js并管理画布布局

在Angular中集成Three.js并管理画布布局

本教程详细介绍了如何在angular应用中集成three.js,并精确控制其渲染画布的大小和位置,避免默认全屏显示。通过html结构、css样式和angular的`@viewchild`装饰器,您可以将three.js场景嵌入到特定的dom元素中,实现灵活的布局管理和响应式渲染,从而在应用中创建多个独立的3d视图。

引言

在Angular等现代前端框架中集成Three.js时,一个常见的问题是Three.js默认创建的画布(canvas)会占据整个屏幕,这使得我们难以将其作为组件的一部分嵌入到页面布局中。本教程旨在提供一个结构化的方法,通过结合HTML、CSS和Angular的组件生命周期管理,精确控制Three.js渲染画布的尺寸和位置,从而实现更灵活、更可控的3D场景集成。

问题背景:默认全屏显示

当Three.js渲染器被初始化并直接添加到document.body时,它通常会创建一个与浏览器视口同等大小的画布。虽然可以通过直接修改画布元素的style属性来尝试调整其尺寸和位置,但这并非一个健壮且符合Angular开发范式的方法,尤其是在需要将3D场景作为页面特定区域的组件时。

// 常见但非最佳的初始化方式,可能导致全屏显示export class AppComponent implements OnInit {  ngOnInit(): void {    // ... Three.js 场景、相机等初始化代码 ...    // 创建一个div并添加到body,然后将渲染器domElement添加到div    // 这种方式难以精确控制布局    const container = document.createElement('div');    document.body.appendChild(container);    container.appendChild(renderer.domElement);    // ... animate() ...  }}

解决方案核心:HTML/CSS布局与Angular集成

解决此问题的关键在于:

在Angular组件的模板中明确定义一个canvas元素,并将其包裹在一个div容器中。利用CSS精确控制这个div容器的尺寸和位置。在Angular组件的TypeScript代码中,通过@ViewChild装饰器获取到这个特定的canvas元素及其容器的引用。初始化Three.js渲染器时,将获取到的canvas元素作为目标,并根据容器的实际尺寸设置渲染器的大小。

步骤一:定义HTML画布容器

首先,在你的Angular组件模板(例如app.component.html)中,定义一个用于承载Three.js场景的div容器,并在其中放置一个canvas元素。为它们添加类名,以便于CSS选择和Angular组件中引用。

步骤二:应用CSS样式控制布局

接下来,在你的组件样式文件(例如app.component.css)中,为上述HTML元素添加样式。通过控制.canvas-container的width、height、position和top/left等属性,你可以精确地定位和调整3D场景的显示区域。同时,将.webgl-canvas的尺寸设置为100%,使其填充整个父容器。

/* app.component.css */.canvas-container {  width: 600px; /* 控制容器宽度 */  height: 400px; /* 控制容器高度 */  position: absolute; /* 允许自由定位 */  top: 50px; /* 距离页面顶部50px */  left: 50px; /* 距离页面左侧50px */  border: 1px solid #ccc; /* 可选:方便调试时看到容器边界 */  box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.2);}.webgl-canvas {  width: 100%; /* 使画布填充父容器的宽度 */  height: 100%; /* 使画布填充父容器的高度 */  display: block; /* 移除可能的内联元素空白 */}

步骤三:Angular组件中集成Three.js

在Angular组件的TypeScript文件中,你需要执行以下操作:

1. 获取DOM引用

使用@ViewChild装饰器来获取canvas元素及其父容器的引用。由于DOM元素在视图初始化之后才可用,所以Three.js的初始化逻辑应放在ngAfterViewInit生命周期钩子中。

// app.component.tsimport { Component, OnInit, AfterViewInit, ViewChild, ElementRef } from '@angular/core';import * as THREE from 'three'; // 导入Three.js库@Component({  selector: 'app-root',  templateUrl: './app.component.html',  styleUrls: ['./app.component.css']})export class AppComponent implements AfterViewInit {  @ViewChild('canvasContainer', { static: true }) canvasContainerRef!: ElementRef;  @ViewChild('webglCanvas', { static: true }) webglCanvasRef!: ElementRef;  private scene!: THREE.Scene;  private camera!: THREE.PerspectiveCamera;  private renderer!: THREE.WebGLRenderer;  private cube!: THREE.Mesh;  ngAfterViewInit(): void {    // 确保DOM元素已加载    if (this.canvasContainerRef && this.webglCanvasRef) {      this.initThreeJs();      this.animate();    }  }  private initThreeJs(): void {    const container = this.canvasContainerRef.nativeElement;    const canvas = this.webglCanvasRef.nativeElement;    // 场景    this.scene = new THREE.Scene();    // 相机    const sizes = {      width: container.clientWidth,      height: container.clientHeight    };    this.camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 1000);    this.camera.position.z = 5;    this.scene.add(this.camera);    // 渲染器    this.renderer = new THREE.WebGLRenderer({      canvas: canvas, // 将渲染器绑定到特定的canvas元素      antialias: true // 开启抗锯齿    });    this.renderer.setSize(sizes.width, sizes.height); // 设置渲染器尺寸与容器一致    this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // 优化高清屏显示    // 添加一个立方体作为示例    const geometry = new THREE.BoxGeometry(1, 1, 1);    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });    this.cube = new THREE.Mesh(geometry, material);    this.scene.add(this.cube);  }  private animate(): void {    // 动画循环    requestAnimationFrame(() => this.animate());    // 旋转立方体    if (this.cube) {      this.cube.rotation.x += 0.01;      this.cube.rotation.y += 0.01;    }    // 渲染场景    this.renderer.render(this.scene, this.camera);  }}

注意: 在@ViewChild装饰器中,我们使用了{ static: true }。这意味着在ngOnInit生命周期钩子中就可以访问到元素。然而,为了确保元素在渲染时机正确,更推荐在ngAfterViewInit中使用,此时可以省略static: true或设置为false。这里为了简化代码,暂时保持static: true,但实际项目中,如果元素是通过*ngIf等条件渲染的,则必须使用{ static: false }并在ngAfterViewInit中访问。

2. 初始化渲染器并设置尺寸

在initThreeJs方法中,我们通过this.webglCanvasRef.nativeElement获取到实际的HTMLCanvasElement。然后,将这个元素传递给THREE.WebGLRenderer的构造函数。最关键的是,通过container.clientWidth和container.clientHeight获取父容器的实际尺寸,并使用renderer.setSize()方法将渲染器调整到与容器相同的尺寸。同时,相机(THREE.PerspectiveCamera)的aspect属性也需要根据容器的宽高比进行设置。

完整代码示例

为了使示例更完整,以下是app.component.html和app.component.ts的修改,以及一个简单的app.component.css。

app.component.html

app.component.ts

import { Component, AfterViewInit, ViewChild, ElementRef, OnDestroy, HostListener } from '@angular/core';import * as THREE from 'three';@Component({  selector: 'app-root',  templateUrl: './app.component.html',  styleUrls: ['./app.component.css']})export class AppComponent implements AfterViewInit, OnDestroy {  // 使用模板引用变量 #canvasContainer 和 #webglCanvas  @ViewChild('canvasContainer', { static: false }) canvasContainerRef!: ElementRef;  @ViewChild('webglCanvas', { static: false }) webglCanvasRef!: ElementRef;  private scene!: THREE.Scene;  private camera!: THREE.PerspectiveCamera;  private renderer!: THREE.WebGLRenderer;  private cube!: THREE.Mesh;  private animationFrameId: number | null = null; // 用于存储 requestAnimationFrame 的ID  ngAfterViewInit(): void {    // ngAfterViewInit 确保了模板中的元素已经渲染并可用    if (this.canvasContainerRef && this.webglCanvasRef) {      this.initThreeJs();      this.animate();    } else {      console.error('Canvas or container not found.');    }  }  private initThreeJs(): void {    const container = this.canvasContainerRef.nativeElement;    const canvas = this.webglCanvasRef.nativeElement;    // 1. 场景    this.scene = new THREE.Scene();    this.scene.background = new THREE.Color(0xdddddd); // 设置背景色    // 2. 相机    const sizes = {      width: container.clientWidth,      height: container.clientHeight    };    this.camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 1000);    this.camera.position.z = 3;    this.scene.add(this.camera);    // 3. 渲染器    this.renderer = new THREE.WebGLRenderer({      canvas: canvas,      antialias: true // 开启抗锯齿    });    this.renderer.setSize(sizes.width, sizes.height);    this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));    // 4. 添加示例物体    const geometry = new THREE.BoxGeometry(1, 1, 1);    const material = new THREE.MeshStandardMaterial({ color: 0x0077ff }); // 使用StandardMaterial以便能看到光照效果    this.cube = new THREE.Mesh(geometry, material);    this.scene.add(this.cube);    // 5. 添加光源    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 环境光    this.scene.add(ambientLight);    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); // 定向光    directionalLight.position.set(1, 1, 1);    this.scene.add(directionalLight);  }  private animate(): void {    this.animationFrameId = requestAnimationFrame(() => this.animate());    // 动画逻辑    if (this.cube) {      this.cube.rotation.x += 0.005;      this.cube.rotation.y += 0.005;    }    // 渲染场景    this.renderer.render(this.scene, this.camera);  }  // 响应窗口大小变化  @HostListener('window:resize', ['$event'])  onResize(event: Event): void {    if (this.canvasContainerRef && this.camera && this.renderer) {      const container = this.canvasContainerRef.nativeElement;      const newWidth = container.clientWidth;      const newHeight = container.clientHeight;      this.camera.aspect = newWidth / newHeight;      this.camera.updateProjectionMatrix();      this.renderer.setSize(newWidth, newHeight);      this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));    }  }  ngOnDestroy(): void {    // 组件销毁时取消动画帧,防止内存泄漏    if (this.animationFrameId) {      cancelAnimationFrame(this.animationFrameId);    }    // 清理Three.js资源(可选,但推荐在复杂场景中进行)    if (this.renderer) {      this.renderer.dispose();    }    if (this.scene) {      this.scene.traverse((object) => {        if ((object as THREE.Mesh).geometry) {          (object as THREE.Mesh).geometry.dispose();        }        if ((object as THREE.Mesh).material) {          if (Array.isArray((object as THREE.Mesh).material)) {            ((object as THREE.Mesh).material as THREE.Material[]).forEach(material => material.dispose());          } else {            ((object as THREE.Mesh).material as THREE.Material).dispose();          }        }      });    }  }}

app.component.css (与之前相同)

.canvas-container {  width: 600px; /* 控制容器宽度 */  height: 400px; /* 控制容器高度 */  position: absolute; /* 允许自由定位 */  top: 50px; /* 距离页面顶部50px */  left: 50px; /* 距离页面左侧50px */  border: 1px solid #ccc; /* 可选:方便调试时看到容器边界 */  box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.2);  background-color: #f0f0f0; /* 容器背景色 */}.webgl-canvas {  width: 100%; /* 使画布填充父容器的宽度 */  height: 100%; /* 使画布填充父容器的高度 */  display: block; /* 移除可能的内联元素空白 */}

注意事项与最佳实践

生命周期钩子: 始终在ngAfterViewInit中初始化Three.js,因为此时组件的视图(包括模板中的canvas元素)已经完全渲染并可用。响应式布局: 通过监听window:resize事件,并在事件触发时重新计算容器尺寸,然后更新相机宽高比和渲染器大小,可以使3D场景具备响应式能力。多画布场景: 如果你需要显示多个独立的Three.js场景,只需重复上述步骤:为每个场景在HTML中定义独立的div和canvas,并在TypeScript中为每个场景创建独立的Three.js实例(场景、相机、渲染器)。你可以通过不同的模板引用变量(#canvasContainer2, #webglCanvas2)来获取它们的引用。资源清理: 在ngOnDestroy生命周期钩子中,务必取消requestAnimationFrame的动画循环,并调用renderer.dispose()来释放WebGL上下文和相关资源,以防止内存泄漏。对于更复杂的场景,可能还需要手动释放几何体、材质和纹理等资源。性能优化: setPixelRatio可以帮助在高DPI屏幕上获得更清晰的渲染效果,但过高的值可能会影响性能。Math.min(window.devicePixelRatio, 2)是一个常用的折衷方案。

总结

通过上述方法,我们可以在Angular应用中实现对Three.js渲染画布的精确控制。这种方式不仅解决了画布默认全屏显示的问题,更重要的是,它提供了一种符合Angular组件化思想的集成方案,使得Three.js场景能够作为可控的UI元素融入到复杂的应用布局中,为构建丰富的3D交互体验奠定了基础。

以上就是在Angular中集成Three.js并管理画布布局的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1593808.html

赞 (0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
解决Django模板中Bootstrap Modal动态ID失效的策略
上一篇 2025年12月23日 08:54:13
DOM遍历与文本节点换行符添加:HTML元素内容换行处理教程
下一篇 2025年12月23日 08:54:19

相关推荐

  • Linux平台下的Eclipse配置

    在linux平台上配置eclipse时,可能会遇到一些常见的问题和优化需求。本文将详细介绍如何解决这些问题,并提供优化eclipse的建议。 启动Eclipse报错 启动Eclipse时,如果遇到以下错误: A Java Runtime Environment (JRE) or Java Devel…

    2026年9月22日
    000
  • 怎样在iPhone情侣模式中分享视频?快速上传和同步的实用方法

    怎样在iPhone情侣模式中分享视频?快速上传和同步的实用方法怎样在iPhone情侣模式中分享视频?快速上传和同步的实用方法怎样在iPhone情侣模式中分享视频?快速上传和同步的实用方法怎样在iPhone情侣模式中分享视频?快速上传和同步的实用方法

    最实用的方法是使用iCloud共享相簿,它支持情侣间视频快速上传与实时同步。首先双方需开启iCloud照片中的共享相簿功能,然后创建专属相簿并邀请对方加入。此后,任一方添加的视频会自动同步至对方设备,且不占用个人iCloud空间。相比AirDrop(限近距离)、即时通讯工具(压缩画质)或云盘(占存储…

    2026年9月22日 • 用户投稿
    000
  • 神马搜索App用户反馈处理教程_神马搜索App问题解决技巧

    首先通过App内“帮助与反馈”提交问题,其次针对负面信息访问官网进行侵权投诉,最后定期清理缓存以解决卡顿或显示异常问题。 如果您在使用神马搜索App时遇到问题或希望反馈体验,可以通过官方渠道提交信息以获得处理。以下是针对不同问题的解决步骤: 本文运行环境:iPhone 15 Pro,iOS 18 一…

    2026年9月22日
    000
  • 如何判断网络延迟(Ping值)过高是由运营商问题还是自身设备引起的?

    先重启设备并检查本地连接,若有线网络延迟降低则问题在无线信号;通过ping网关和外部DNS判断故障范围,结合tracert追踪路径,前几跳异常指向运营商问题,最后用官方测速工具验证并联系客服解决。 判断网络延迟高是运营商还是自身设备的问题,关键在于逐步隔离排查。从最接近你的设备开始测试,向外层层推进…

    2026年9月22日
    100
  • 深入理解PHP数组中JSON字符串的解析与数据提取

    本文将详细讲解如何在PHP中处理包含JSON格式字符串的数组。通过使用json_decode函数,我们可以将这些JSON字符串转换为可操作的PHP数组,进而轻松提取所需的shortname和fullname等键值对。教程将提供清晰的示例代码,演示循环遍历和直接访问两种数据提取方式,帮助开发者高效地解…

    2026年9月22日
    000
  • AdobeIllustrator如何导出AI生成的图片?矢量图像保存的步骤

    首先明确输出需求,再选择“文件>导出>导出为…”或“文件>存储为…”,根据用途选取合适格式如SVG、PDF或AI,确保矢量信息保留,参数设置需匹配目标平台要求。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ Adob…

    2026年9月22日
    000
  • PHP面向对象开发:解决父类构造器参数在嵌套子对象方法中丢失的问题

    在PHP面向对象编程中,我们经常会遇到类继承和对象组合的场景。一个常见的问题是,当父类构造函数接收参数并用于初始化内部的子对象时,该子对象的方法在后续调用中可能无法正确访问到这些参数,甚至显示为null。本文将深入探讨这一问题,并提供两种有效的解决方案。 引言与问题阐述 考虑一个典型的web应用架构…

    2026年9月22日
    000
  • 悟空浏览器下载速度慢怎么解决_悟空浏览器下载速度慢提升技巧

    下载慢可优化缓存、网络、后台、多线程及版本。1、清理缓存与数据;2、检查Wi-Fi并优化信号;3、关闭后台应用释放资源;4、开启多线程加速下载;5、更新浏览器至最新版本。 如果您在使用悟空浏览器下载文件时遇到速度缓慢的情况,这可能是由于网络连接不稳定、浏览器缓存堆积或后台资源被大量占用所致。优化相关…

    2026年9月22日
    600
  • 使用MockWebServer对FeignClient进行单元测试

    本文详细阐述了如何利用Spring Cloud LoadBalancer和MockWebServer对FeignClient进行高效单元测试。通过在测试配置中动态注册MockWebServer实例,并将其作为FeignClient的服务发现目标,开发者可以精确模拟后端API的行为,包括各种HTTP响…

    2026年9月22日
    000
  • Sublime快速定位MySQL死锁与阻塞问题_提高系统稳定性与并发处理能力

    Sublime快速定位MySQL死锁与阻塞问题_提高系统稳定性与并发处理能力Sublime快速定位MySQL死锁与阻塞问题_提高系统稳定性与并发处理能力Sublime快速定位MySQL死锁与阻塞问题_提高系统稳定性与并发处理能力Sublime快速定位MySQL死锁与阻塞问题_提高系统稳定性与并发处理能力

    mysql死锁和阻塞问题可通过sublime分析日志排查。1.使用关键词搜索及正则匹配快速定位死锁信息块;2.通过查找waiting状态事务并折叠无关内容聚焦阻塞链;3.结合general log与事务id查找sql执行顺序;4.利用语法高亮、列选择等功能提升日志可读性。这些方法能有效提高排查效率。…

    2026年9月22日 • 用户投稿
    100
  • win11平板模式无法自动切换怎么办_win11平板模式自动切换异常修复方法

    首先检查设备形态和设置,确保键盘分离或屏幕翻转到位,并在“平板电脑”设置中启用自动切换;若无效,重启Windows资源管理器、更新传感器驱动或重装感应式旋转锁驱动;最后通过sfc /scannow命令修复系统文件,恢复平板模式自动切换功能。 如果您发现Windows 11的平板模式无法根据设备形态自…

    2026年9月22日
    100
  • 豫事办的密码怎么改

    首先通过个人中心修改密码或使用忘记密码功能重置。记得密码时,进入豫事办APP我的→设置→修改密码,输入原密码和新密码即可;忘记密码则在登录页点击“忘记密码”,输入手机号获取验证码后设置新密码完成重置。 如果您需要更新您的豫事办账户密码以确保账户安全,可以通过应用内设置直接修改,或在忘记密码时通过身份…

    2026年9月22日
    400
  • Canva中AI生成图片如何导出?教你快速保存设计作品的方法

    答案:Canva中导出AI生成图片的操作与普通图片相同,点击右上角“分享”按钮,选择“下载”,可选PNG、JPG、PDF、SVG、MP4或GIF等格式;为保证画质,建议优先选用PNG格式,避免有损压缩,同时选择高分辨率和合适尺寸,Pro用户可进一步调整质量与透明背景设置;除下载外,还可通过分享链接、…

    2026年9月22日
    800
  • windows怎么用命令行删除顽固文件_命令行删除顽固文件的方法

    使用命令行可强制删除顽固文件,先以管理员身份运行cmd或PowerShell,通过del /f /q或Remove-Item -Force命令删除文件,若被占用则用taskkill终止进程,或进入安全模式删除,也可用rd /s /q删除整个目录。 如果您尝试删除某个文件时,发现资源管理器无法完成操作…

    2026年9月22日
    200
  • VSCode安全更新机制解析

    VSCode通过自动检查、数字签名验证和用户可控策略确保更新安全。启动时后台定期HTTPS请求检查新版本,每日一次;安装包经平台特定签名(Windows Authenticode、macOS代码签名、Linux GPG)验证完整性;用户可选自动更新、提示或关闭,企业可集中管控;微软通过安全入口响应漏…

    2026年9月22日
    100
  • Linux系统中文件属性和权限实战操作

    Linux系统中文件属性和权限实战操作Linux系统中文件属性和权限实战操作Linux系统中文件属性和权限实战操作Linux系统中文件属性和权限实战操作

    —–原本今天的文章是昨天晚上就要更新的,但是由于昨天晚上下班回到住的地方,发现停电了,所以就没写成。今天是在上一篇文章–linux系统中文件类型的基础上,继续进行深入的学习。好了,直接开干。 一、文件的操作权限: 1、在这之前我想还是很有必要介绍对文件的操作权限(…

    2026年9月22日 • 用户投稿
    000
  • PHP中为数组元素设置默认值的最佳实践:使用Null合并运算符

    本教程将介绍如何在PHP中为数组元素设置默认值,尤其当源数据可能为空或缺失时。通过利用PHP 7+提供的Null合并运算符(??),可以简洁高效地实现这一需求,避免冗长的条件判断,提高代码可读性和健壮性。 引言:处理缺失或空值时的数组赋值 在Web开发中,我们经常需要从用户请求、数据库查询或其他外部…

    2026年9月22日
    000
  • Hazelcast缓存数据未显示:排查与解决指南

    本文旨在解决在使用Spring Cache结合Hazelcast时,通过@CachePut等注解成功将数据放入缓存,但无法通过HazelcastInstance获取缓存数据的问题。文章将深入探讨可能的原因,并提供详细的配置步骤和代码示例,帮助开发者正确配置和使用Hazelcast缓存。 在使用Spr…

    2026年9月22日
    000
  • VSCode快速配置Dart:Flutter开发、中文提示、热加载

    安装vscode并下载flutter sdk,解压至无中文或特殊字符的路径;2. 将flutter sdk的bin目录添加到系统环境变量path中;3. 打开新终端执行flutter doctor,根据提示安装缺失的依赖;4. 在vscode扩展商店安装dart和flutter扩展;5. 确保在调试…

    2026年9月22日
    200
  • Inkscape如何导出AI生成的矢量图片?教你快速保存图像的步骤

    答案:在Inkscape中导出矢量图需根据用途选择格式,网页用优化SVG并转文本为路径,印刷则导出为PDF/EPS、转文字为路径、确保高分辨率位图,同时注意颜色模式与出血设置。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 在Inkscap…

    2026年9月22日
    700

发表回复

登录后才能评论
关注微信