当前位置:首页 » 《随便一记》 » 正文

Web互动涂鸦:基于HTML5 Canvas的简易实现

1 人参与  2024年11月15日 08:01  分类 : 《随便一记》  评论

点击全文阅读


本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:简单涂鸦DOM利用HTML5 Canvas元素实现网页上的自由绘画功能。用户通过鼠标操作进行绘画,该项目通过JavaScript控制Canvas进行像素级绘图,提供了基本的绘画、擦除、保存和分享功能。开发者可以在此基础上扩展更多绘图工具和功能。 简单涂鸦dom

1. Canvas绘图基础

1.1 HTML5 Canvas元素简介

Canvas是HTML5中提供的一个用于绘制图形的API,通过简单的标签,开发者可以在网页上绘制图形、动画等。Canvas的核心是一张位图,允许JavaScript脚本进行像素级操作,提供了2D绘图的无限可能性。

<canvas id="myCanvas" width="200" height="100"></canvas>

1.2 Canvas与SVG的比较

尽管SVG也提供了图形绘制的能力,但它基于XML,更擅长于静态图形的展示。而Canvas是基于JavaScript的,适合用于动态图形和交互式内容的创建,其渲染过程完全由JavaScript控制,因此更加灵活和强大。

1.3 Canvas绘图上下文

Canvas绘图依赖于一个叫做“绘图上下文”(context)的对象。通过获取Canvas元素的绘图上下文,可以执行绘图命令。通常使用的是2D上下文,通过 getContext('2d') 方法获得。

var canvas = document.getElementById('myCanvas');var ctx = canvas.getContext('2d');

本章节主要介绍了Canvas的定义、特点以及如何在HTML中使用Canvas元素。同时,区分了Canvas与SVG的不同之处,帮助读者理解各自的应用场景。最后,介绍了Canvas的绘图上下文概念,为后续章节中复杂的绘图操作打下了基础。

2. 鼠标事件监听实现绘图

2.1 Canvas事件监听基础

2.1.1 鼠标事件类型详解

Canvas提供了多种鼠标事件,这些事件使得我们能够根据用户的动作来执行相应的绘图操作。常见的鼠标事件包括:

click : 当鼠标点击画布时触发。 mousedown mouseup : 当鼠标按钮被按下或释放时触发。 mousemove : 当鼠标在画布上移动时不断触发。 mouseover mouseout : 当鼠标指针移动到画布上或移出画布时触发。 mouseenter mouseleave : 类似于 mouseover mouseout ,但不冒泡,即不会在子元素触发。

2.1.2 事件监听器的绑定与响应

为了响应上述事件,我们需要在JavaScript中为Canvas元素绑定事件监听器。通常,我们使用 addEventListener 方法来添加监听器。例如:

// 获取Canvas元素const canvas = document.getElementById('myCanvas');const ctx = canvas.getContext('2d');// 绑定点击事件监听器canvas.addEventListener('click', function(event) {  const rect = canvas.getBoundingClientRect();  const x = event.clientX - rect.left;  const y = ***;  // 根据点击位置进行绘图操作  // 例如:绘制一个点  ctx.beginPath();  ctx.arc(x, y, 5, 0, Math.PI * 2);  ctx.fill();});

在上述代码中,我们首先获取了Canvas元素及其绘图上下文,然后为它绑定了一个点击事件监听器。当用户点击Canvas时,事件对象 event 会被传递给监听函数,其中 event.clientX event.clientY 表示点击位置的坐标。通过计算,我们可以得到相对于Canvas左上角的坐标,并在该位置绘制一个圆点。

2.2 通过鼠标绘制图形

2.2.1 绘制线条与曲线

利用鼠标事件,我们可以让用户自由地绘制线条和曲线。以下是使用 mousedown , mousemove , 和 mouseup 事件绘制线条的示例代码:

let isDrawing = false;let startX, startY;canvas.addEventListener('mousedown', function(e) {  startX = e.clientX - canvas.offsetLeft;  startY = e.clientY - canvas.offsetTop;  isDrawing = true;});canvas.addEventListener('mousemove', function(e) {  if (isDrawing) {    const endX = e.clientX - canvas.offsetLeft;    const endY = e.clientY - canvas.offsetTop;    // 开始绘制线条    ctx.beginPath();    ctx.moveTo(startX, startY);    ctx.lineTo(endX, endY);    ctx.stroke();    startX = endX;    startY = endY;  }});canvas.addEventListener('mouseup', function(e) {  if (isDrawing) {    // 绘制结束    isDrawing = false;  }});

在这段代码中, mousedown 事件负责记录起始点, mousemove 事件负责绘制线条,而 mouseup 事件则负责标记绘制过程的结束。用户在画布上按下鼠标并移动时,就可以绘制连续的线条。

2.2.2 绘制多边形和矩形

通过监听不同的鼠标事件,我们还可以让用户绘制多边形和矩形。以下是如何根据用户点击来绘制矩形的代码示例:

let rectX, rectY, rectWidth, rectHeight;canvas.addEventListener('mousedown', function(e) {  rectX = e.clientX - canvas.offsetLeft;  rectY = e.clientY - canvas.offsetTop;});canvas.addEventListener('mousemove', function(e) {  const mouseX = e.clientX - canvas.offsetLeft;  const mouseY = e.clientY - canvas.offsetTop;  // 计算矩形的位置和大小  rectWidth = Math.abs(mouseX - rectX);  rectHeight = Math.abs(mouseY - rectY);  // 重绘画布  ctx.clearRect(0, 0, canvas.width, canvas.height);  ctx.strokeRect(rectX, rectY, rectWidth, rectHeight);});canvas.addEventListener('mouseup', function(e) {  // 此处可以执行矩形绘制结束的逻辑});

在这个例子中,当用户按下鼠标时记录起始点,而 mousemove 事件用来更新矩形的位置和大小。当用户释放鼠标时,可以执行一些结束绘制的逻辑,比如在画布上填充矩形。

2.3 鼠标控制下的交互绘图

2.3.1 实现图形的选择与移动

在交互式绘图应用中,用户往往需要对已经绘制的图形进行选择和移动。这通常需要记录每个图形的位置信息,并在鼠标事件触发时进行更新。

// 假设有一个圆形对象的数组let circles = [];function createCircle(x, y) {  const circle = {    x: x,    y: y,    radius: 10,  };  circles.push(circle);}// 鼠标点击事件监听,用于选择并移动图形canvas.addEventListener('mousedown', function(e) {  const x = e.clientX - canvas.offsetLeft;  const y = e.clientY - canvas.offsetTop;  circles.forEach((circle, index) => {    const dx = x - circle.x;    const dy = y - circle.y;    const distance = Math.sqrt(dx * dx + dy * dy);    if (distance < circle.radius) {      // 选中圆形,设置isMoving状态      selectedCircleIndex = index;      isMoving = true;    }  });});// 鼠标移动事件监听,用于移动图形canvas.addEventListener('mousemove', function(e) {  if (isMoving) {    const x = e.clientX - canvas.offsetLeft;    const y = e.clientY - canvas.offsetTop;    circles[selectedCircleIndex].x = x;    circles[selectedCircleIndex].y = y;    drawAllCircles();  }});// 绘制所有圆形function drawAllCircles() {  ctx.clearRect(0, 0, canvas.width, canvas.height);  circles.forEach(circle => {    ctx.beginPath();    ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);    ctx.stroke();  });}

在上述示例代码中,我们定义了一个 createCircle 函数来创建圆形对象,并将它们保存在数组 circles 中。通过监听 mousedown mousemove 事件,我们可以实现选择圆形并随着鼠标移动来拖动圆形的功能。

2.3.2 鼠标悬停效果与动态绘制

动态绘制指的是当鼠标悬停在画布上时,根据鼠标的移动轨迹绘制图形。常见的动态绘制效果包括颜色变化、线条粗细变化等。

let prevX, prevY;canvas.addEventListener('mousemove', function(e) {  const x = e.clientX - canvas.offsetLeft;  const y = e.clientY - canvas.offsetTop;  if (prevX !== undefined && prevY !== undefined) {    // 绘制线条,实现动态效果    ctx.beginPath();    ctx.moveTo(prevX, prevY);    ctx.lineTo(x, y);    // 随着鼠标的移动改变线条颜色    ctx.strokeStyle = `hsl(${Math.random() * 360}, 50%, 50%)`;    ctx.lineWidth = Math.random() * 10 + 1;    ctx.stroke();  }  // 更新上一个坐标点  prevX = x;  prevY = y;});

这段代码使用 mousemove 事件监听器来不断绘制线条,并通过随机函数生成不同的颜色和线条宽度,从而实现动态绘制效果。每次移动鼠标时,都会根据当前鼠标的坐标和之前记录的坐标来绘制线条。

下一章节将介绍如何在Canvas中实现颜色与线条宽度的调整。

3. 颜色与线条宽度调整

3.1 颜色选择与填充

颜色是任何视觉作品的基础元素之一,它能够极大地影响最终的视觉效果。在Canvas中,颜色的处理和应用非常灵活,开发者可以根据需求进行多种颜色相关的操作。

3.1.1 颜色模型与选择技巧

在Canvas中,颜色通常使用RGB模型来定义。RGB是红、绿、蓝三种颜色的首字母缩写,通过调整这三种颜色的亮度值(0-255)可以混合出几乎所有的颜色。

// 设置颜色为红色ctx.fillStyle = 'rgb(255, 0, 0)';

开发者还可以使用十六进制颜色代码来定义颜色,如下所示:

// 设置颜色为蓝色ctx.fillStyle = '#0000FF';

此外,Canvas还支持使用RGBA和HSLA模型,其中A表示透明度,可以用来制作半透明的视觉效果。

在选择颜色时,可以利用颜色选择器工具来帮助确定具体的颜色代码,或者使用在线颜色库来寻找灵感。考虑到颜色对比和视觉舒适度也非常重要,例如WCAG(Web Content Accessibility Guidelines)为web内容提供了颜色对比度的指导标准,以确保色盲用户也能正常使用。

3.1.2 设置图形与画布的背景色

在Canvas中设置颜色不仅可以用于填充图形,还可以用于设置画布的背景色。

// 设置画布背景色为白色document.getElementById('myCanvas').getContext('2d').fillnaStyle = '#FFFFFF';

为图形填充颜色的基本方法是使用 fillStyle 属性来设置所需的颜色,然后调用 fill() 方法:

// 设置圆形的颜色为黄色,并填充到Canvas中ctx.beginPath();ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // 创建圆形ctx.fillStyle = 'yellow';ctx.fill();

此外,可以通过线性渐变或径向渐变来创建更为复杂和动态的背景效果。这将在3.3节进行详细讨论。

3.2 线条样式定制

Canvas不仅提供了填充颜色的功能,还允许开发者定制线条的样式。线条样式对于创建轮廓和边框非常重要,可以显著提升图形的可读性和美观度。

3.2.1 线条宽度与端点样式的设置

在Canvas中,线条的宽度可以通过 lineWidth 属性进行设置:

// 设置线条宽度为5像素ctx.lineWidth = 5;

线条的端点样式定义了线条的结束端点外观,有三种端点样式可供选择:

butt :端点是平的,与线条的最后一个像素形成垂直线。 round :端点是圆的,其半径等于线条宽度的一半。 square :端点是方的,其外延等于线条宽度的一半。
// 设置线条端点样式为圆形ctx.lineCap = 'round';

3.2.2 虚线样式的应用与自定义

虚线的样式在很多设计中都有应用,可以用来表示不连续的线条。在Canvas中,可以通过设置 lineDash 属性来定义虚线样式:

// 设置虚线样式为[10, 5]ctx.setLineDash([10, 5]);

在上面的代码中,数字数组定义了交替的虚线段和间隔长度,单位是像素。

此外,如果需要实现自定义的虚线样式,可以利用Canvas的绘图循环和条件判断来手动绘制特定的线条模式。这种方式可以创造出无限可能的虚线效果,比如虚线、点线等。

3.3 高级绘图特性

Canvas绘图库不仅包含基础的填充和线条绘制,还有更多高级特性,如渐变和图案的填充、文字与图像的绘制等。

3.3.1 渐变与图案的填充技术

渐变填充可以使图形从一个颜色平滑过渡到另一个颜色,适用于创建阴影效果、高光效果等。

在Canvas中,创建渐变可以通过以下步骤:

使用 createLinearGradient() createRadialGradient() 创建渐变对象。 使用 addColorStop() 方法指定渐变的起始和结束颜色以及它们在渐变路径上的位置。 将渐变对象赋值给图形的 fillStyle strokeStyle 属性。 使用 fill() stroke() 方法绘制图形。
// 创建线性渐变var gradient = ctx.createLinearGradient(0, 0, 0, 170);gradient.addColorStop(0, '#00F');gradient.addColorStop(1, '#0F0');// 应用渐变ctx.fillStyle = gradient;ctx.fillRect(0, 0, 170, 170);

图案的填充使用的是 createPattern() 方法,它接受一个图像(Image)、视频(Video)、Canvas或另一个 <img> 元素作为参数,并且可以设置图像重复的模式,例如 repeat repeat-x repeat-y no-repeat

3.3.2 文字与图像在Canvas上的绘制

Canvas提供了 fillText() strokeText() 方法来在Canvas上绘制文字,支持设置字体样式、大小、颜色等。

// 设置文字样式并绘制到Canvas上ctx.font = 'bold 48px arial';ctx.fillText("Hello Canvas", 10, 50);

绘制图像时,可以使用 drawImage() 方法将图片绘制到Canvas中。这个方法非常强大,它允许开发者对图像进行缩放、裁剪等操作:

// 绘制图像到Canvas中var img = new Image();img.onload = function() {    ctx.drawImage(img, 0, 0, 300, 200);};img.src = 'path_to_image.jpg';

通过这些高级特性,开发者可以创作出视觉效果丰富的Canvas应用,从而满足各种创意需求和业务场景。

4. 橡皮擦和画布清空功能

在前几章中,我们已经探讨了Canvas绘图的基础知识,学习了如何利用鼠标事件实现绘图功能,并深入到颜色和线条样式的调整。现在,我们将注意力转向如何在Canvas上实现橡皮擦和清空画布的功能。这些功能对于提供一个完整的绘图体验至关重要,允许用户修正错误或从头开始。

4.1 实现橡皮擦功能

橡皮擦工具是数字绘图软件中不可或缺的一部分。与传统橡皮擦一样,它能够移除画布上的内容,为用户提供重新绘制或修正错误的机会。

4.1.1 橡皮擦工具的原理与实现

橡皮擦功能的基本原理是将画布上的像素恢复到初始状态,或者根据背景色覆盖掉相应的部分。在Canvas中,这通常意味着通过清除特定区域的像素来实现。

代码示例1:基本橡皮擦功能实现

function erase(ctx, x, y, size) {    ctx.globalCompositeOperation = 'destination-out'; // 设置合成模式为destination-out,实现橡皮擦效果    ctx.fillStyle = 'rgba(0,0,0,1)'; // 设置橡皮擦颜色,这里为黑色    ctx.fillRect(x - size/2, y - size/2, size, size); // 绘制一个覆盖目标区域的矩形    ctx.globalCompositeOperation = 'source-over'; // 恢复合成模式为默认值}

4.1.2 橡皮擦的大小和形状定制

橡皮擦并不总是圆形的,用户可能希望根据需要来定制橡皮擦的大小和形状。下面的代码展示了如何实现一个可调整大小的圆形橡皮擦,并留出定制形状的可能性。

代码示例2:可定制大小和形状的橡皮擦

let eraserSize = 20; // 初始橡皮擦大小let eraserShape = 'round'; // 初始橡皮擦形状canvas.addEventListener('mousedown', startErase, false);canvas.addEventListener('mousemove', erase, false);canvas.addEventListener('mouseup', stopErase, false);canvas.addEventListener('mouseleave', stopErase, false);function startErase(e) {    if (e.which === 1) { // 左键开始绘制        isDrawing = true;    }}function erase(e) {    if (!isDrawing) return;    ctx.globalCompositeOperation = 'destination-out';    ctx.lineCap = 'round';    ctx.strokeStyle = 'rgba(0,0,0,1)';    ctx.lineWidth = eraserSize;    let pos = getMousePos(canvas, e);    if (eraserShape === 'round') {        ctx.beginPath();        ctx.arc(pos.x, pos.y, eraserSize / 2, 0, Math.PI * 2);        ctx.fill();    } else if (eraserShape === 'square') {        // 实现方形橡皮擦的逻辑代码    }    ctx.globalCompositeOperation = 'source-over';}function stopErase(e) {    if (e.which === 1) { // 左键停止绘制        isDrawing = false;    }}function getMousePos(canvas, evt) {    // 获取鼠标在Canvas元素上的位置}

在上述代码中,我们使用了 mousedown , mousemove , mouseup , 和 mouseleave 事件来追踪鼠标动作,并应用了Canvas的 globalCompositeOperation 属性来实现橡皮擦效果。通过改变 lineCap lineWidth 属性,我们可以模拟不同的笔刷形状和大小。

4.2 清空画布技巧

在用户希望去除画布上所有内容,或者准备以全新的画布开始时,清空画布是一个必要的功能。

4.2.1 清空整个画布的操作方法

清空画布是一个简单但重要的操作。可以使用 .clearRect 方法来实现。

代码示例3:清除整个画布

function clearCanvas() {    ctx.clearRect(0, 0, canvas.width, canvas.height);}

4.2.2 局部清除与重绘技巧

有时,用户可能只希望清除画布的一部分。利用 globalCompositeOperation drawImage 方法可以实现非破坏性的局部清除和重绘。

代码示例4:局部清除与重绘

function clearPartOfCanvas(x, y, width, height) {    // 保存当前画布内容    ctx.save();    // 使用destination-out模式来擦除指定区域    ctx.globalCompositeOperation = 'destination-out';    ctx.fillRect(x, y, width, height);    // 恢复画布内容    ctx.restore();}

4.3 高级擦除功能实现

橡皮擦功能的高级实现不仅包括基本的擦除操作,还可以扩展到更复杂的交互,例如擦除效果和撤销操作的集成。

4.3.1 可配置的擦除区域选择

让用户能够选择擦除的区域可以大大增强橡皮擦的灵活性。我们可以通过绘制一个自定义的形状或选择框来实现这一功能。

代码示例5:可配置擦除区域的橡皮擦

let selectionStartX, selectionStartY;let isSelecting = false;canvas.addEventListener('mousedown', function(e) {    isSelecting = true;    selectionStartX = e.offsetX;    selectionStartY = e.offsetY;});canvas.addEventListener('mouseup', function(e) {    if (isSelecting) {        let selectionEndX = e.offsetX;        let selectionEndY = e.offsetY;        clearPartOfCanvas(selectionStartX, selectionStartY, selectionEndX - selectionStartX, selectionEndY - selectionStartY);    }    isSelecting = false;});

4.3.2 擦除效果与撤销操作的集成

一个成熟的绘图应用还应该提供撤销操作的能力,允许用户撤销最近的擦除或绘图动作。这通常涉及到维护一个命令的历史堆栈。

代码示例6:集成擦除效果与撤销操作

const commandStack = [];function executeCommand(command) {    command.execute();    commandStack.push(command);}function undo() {    if (commandStack.length === 0) return;    const command = commandStack.pop();    command.undo();}// 命令模式class Command {    constructor(ctx) {        this.ctx = ctx;        this.properties = { ... };    }    execute() {        // 执行命令的逻辑代码    }    undo() {        // 执行撤销命令的逻辑代码    }}

在上述示例中,我们使用了命令模式,这是实现撤销功能的一种常见方法。 Command 类定义了基本操作, execute 方法用于执行当前命令,而 undo 方法用于撤销该命令。我们通过一个数组 commandStack 来保存所有操作,以支持撤销功能。

这一章,我们深入了解了Canvas上的橡皮擦功能和画布清空技术。我们从基础的橡皮擦实现到高级的擦除效果和撤销操作的集成,每一步都细致地分析了相关的技术细节和实现方法。这些功能对于提供一个流畅且无限制的绘图体验是必不可少的。

5. 画作保存与分享实现

在本章节中,我们将探讨如何实现Canvas中完成的画作的保存和分享。这不仅包括将画布内容导出为数据格式,并保存到本地存储,还会涉及到分享给其他用户或通过社交媒体进行分享的过程。这一章节将分为三个主要部分:

5.1 画作的数据保存方法 5.2 实现画作的本地保存 5.3 推广与分享机制

5.1 画作的数据保存方法

Canvas绘图的保存通常涉及到将画布状态导出为某种数据格式。最常用的数据格式是PNG或JPEG,但也可以导出为PDF,SVG,甚至是二进制的图片数据。

5.1.1 将画布状态导出为数据格式

要将Canvas的状态导出为图片数据,我们可以使用 toDataURL() 方法。这个方法会返回一个包含图片表示的data URL,可被用作 <img> 标签的 src 属性。这个过程不涉及服务器端的操作,所有的处理都在客户端完成。

// 获取当前canvas元素const canvas = document.getElementById('myCanvas');// 将画布内容导出为PNG格式的数据URLconst pngData = canvas.toDataURL('image/png');// 可以将pngData用于创建下载链接或显示图片document.getElementById('downloadLink').href = pngData;

5.1.2 压缩画布数据与存储优化

导出的数据可能很大,特别是当画布尺寸较大或细节丰富时。为了优化存储和传输,我们可以在导出前对数据进行压缩。Web Workers可以用来进行后台压缩,从而不会影响到主线程的响应性。

// 使用Worker来压缩画布数据const worker = new Worker('compressWorker.js');// 发送画布数据到Worker进行压缩worker.postMessage({ canvasData: pngData });// 在Worker中处理压缩// compressWorker.jsself.addEventListener('message', (e) => {  const compressedData = compress(e.data.canvasData);  self.postMessage(compressedData);});

5.2 实现画作的本地保存

除了数据导出,用户还希望能够直接将他们的作品保存到本地文件系统。HTML5提供了File APIs,它允许我们通过用户触发的事件保存文件到本地。

5.2.1 画作的下载与本地存储

为了提供画作的下载和存储,我们可以创建一个 <a> 元素,并设置其 href 属性为Canvas导出的数据URL,并使用 download 属性直接触发下载。

<!-- HTML示例 --><a id="downloadLink" href="" download="myDrawing.png">下载画作</a>
// JavaScript代码示例document.getElementById('downloadLink').href = pngData;

5.2.2 文件格式支持与兼容性处理

并非所有浏览器都支持 toDataURL() 方法返回的所有图像格式。为了解决这个问题,我们可能需要检测浏览器是否支持特定格式,并且可能需要回退到一种更兼容的格式,例如JPEG。

5.3 推广与分享机制

为了增加用户互动和画作的曝光率,实现分享功能至关重要。用户应能够生成分享链接或二维码,这些链接和二维码将指向他们创建的画作。

5.3.1 创建分享链接与二维码

分享链接可以是一个指向Canvas画作的URL,而二维码则可以生成为方便用户扫描的图片格式。

// 生成分享链接const shareLink = '***' + encodeURIComponent(pngData);// 使用第三方库来生成分享用的二维码const qrCode = qrcode(0, 'M');qrCode.makeImage({    text: shareLink,    width: 256,    height: 256,    color: {        dark: '#000000ff',        light: '#ffffffff'    }}, function (img) {    document.getElementById('qrImage').appendChild(img);});

5.3.2 社交媒体集成与第三方平台分享

为了使分享更加容易,可以集成社交媒体分享按钮,允许用户直接将链接或内容分享到Facebook、Twitter等平台。同时,也可以集成第三方平台API,例如Email、WhatsApp等,让用户选择分享方式。

<!-- 社交媒体分享按钮示例 --><!-- Facebook --><a href="***{{shareLink}}" target="_blank">分享到Facebook</a><!-- Twitter --><a href="***{{shareLink}}" target="_blank">分享到Twitter</a>

以上就是关于如何实现画作的保存与分享的详细介绍。通过这些方法,用户不仅能够将他们的Canvas艺术作品保存为数据文件,还能通过各种方式与他人分享他们的创作。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:简单涂鸦DOM利用HTML5 Canvas元素实现网页上的自由绘画功能。用户通过鼠标操作进行绘画,该项目通过JavaScript控制Canvas进行像素级绘图,提供了基本的绘画、擦除、保存和分享功能。开发者可以在此基础上扩展更多绘图工具和功能。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/186332.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1