OxyPlot组件的基本使用

OxyPlot组件的基本使用,第1张

OxyPlot组件的基本使用

  在制作上位机的时候,很多时候需要使用到监控绘图界面,使用来绘制曲线的组件有很多,GDI+、char、OxyPlot等等,这篇文章用来介绍OxyPlot组件的基本应用,在本文中主要是利用随心数生成函数结合拖杆控件来模拟出绘图所需要的数据。


  首先需要先创建一个Winfrom窗体应用的工程,创建工程过程可参考以下链接https://www.cnblogs.com/xionglaichuangyichuang/p/13734179.html

  OxyPlot组件是一个开源的组件,需要使用它就需要在工程中安装好给组件,便可以进行后续 *** 作,该组件的安装过程可以参考以下链接完成安装,https://www.cnblogs.com/xionglaichuangyichuang/p/13734265.html

  组件安装好之后,打开工具箱,我们可以发现工具箱上多了一组控件,如下图所示,将该控件往右边拖拽既可以完成初步的控件布局。


 

  上位机的界面的控件命名如下图:

  老套路,先抛出完整的工程代码,再对关键部分代码进行解析,完整的工程代码如下图所示:

  1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Threading;
9 using System.Threading.Tasks;
10 using System.Windows.Forms;
11 using OxyPlot;
12 using OxyPlot.Annotations;
13 using OxyPlot.Axes;
14 using OxyPlot.Series;
15
16 namespace WindowsFormsApp2
17 {
18 public partial class Form1 : Form
19 {
20 private DateTimeAxis _dateAxis;//创建一个X轴类
21 private LinearAxis _valueAxis;//创建一个Y轴类
22
23 private PlotModel _myPlotModel;
24 private Random rand = new Random();//用来生成随机数
25
26 public int speed = 0;
27 public bool flag = false; //绘图线程开启标志位,true表示开始,false表示停止
28
29 public Form1()
30 {
31 InitializeComponent();
32 }
33 #region 数据模拟
34 //随机数模拟通道数据
35 public double getSendCount()
36 {
37 double iTotal = 0;
38 //iTotal = rand.Next(100, 300) / 10.0;
39 //iTotal = 50.0;
40 iTotal = speed;
41
42 return iTotal;
43 }
44
45 public double getChannel1()
46 {
47 double iTotal = 0;
48 iTotal = rand.Next(100, 900) / 10.0;
49
50 return iTotal;
51 }
52
53 public double getChannel2()
54 {
55 double iTotal = 0;
56 iTotal = rand.Next(300, 800) / 10.0;
57
58 return iTotal;
59 }
60
61 public double getChannel3()
62 {
63 double iTotal;
64 iTotal = rand.Next(300, 700) / 10.0;
65
66 return iTotal;
67 }
68
69 public double getChannel4()
70 {
71 double iTotal;
72 iTotal = rand.Next(200, 600) / 10.0;
73
74 return iTotal;
75 }
76 #endregion
77
78 private void Form1_Load(object sender, EventArgs e)
79 {
80 // 创建画布坐标轴
81 coordinate();
82 }
83
84 #region 绘制坐标轴
85 private void coordinate()
86 {
87 //定义model
88 _myPlotModel = new PlotModel()
89 {
90 Title = "速度检测",
91 //LegendTitle = "通道值",
92 LegendOrientation = LegendOrientation.Horizontal,
93 LegendPlacement = LegendPlacement.Outside,
94 LegendPosition = LegendPosition.TopCenter,
95 LegendBackground = OxyColor.FromAColor(0, OxyColors.Beige),
96 //设置文本框边框颜色
97 LegendBorder = OxyColors.Transparent
98 };
99
100
101
102 //X轴
103 _dateAxis = new DateTimeAxis()
104 {
105 Position = AxisPosition.Bottom,
106 Minimum = DateTimeAxis.ToDouble(DateTime.Now),
107 Maximum = DateTimeAxis.ToDouble(DateTime.Now.AddMinutes(1)),
108 MajorGridlineStyle = LineStyle.Solid,
109 MinorGridlineStyle = LineStyle.Dot,
110 IntervalLength = 80,
111 Title = "time",//显示标题内容
112 TitlePosition = 0.95,//显示标题位置
113 IsZoomEnabled = true,
114 IsPanEnabled = true
115 };
116 _myPlotModel.Axes.Add(_dateAxis);
117
118 //Y轴
119 _valueAxis = new LinearAxis()
120 {
121 //Y轴刻度间距设置
122 MajorStep = 10,
123 Position = AxisPosition.Left,
124 MajorGridlineStyle = LineStyle.Solid,
125 MinorGridlineStyle = LineStyle.Dot,
126 Title = "Speed",//显示标题内容
127 TitlePosition = 1,//显示标题位置
128 IntervalLength = 80,
129 //设置坐标轴上的刻度标签的方向
130 Angle = 0,
131 IsZoomEnabled = false,
132 IsPanEnabled = false,
133 Maximum = 100,
134 Minimum = 0
135 };
136 _myPlotModel.Axes.Add(_valueAxis);
137
138
139 //添加标注线,速度上下限
140 var lineTempMaxAnnotation = new OxyPlot.Annotations.LineAnnotation()
141 {
142 Type = LineAnnotationType.Horizontal,
143 Color = OxyColors.Red,
144 LineStyle = LineStyle.Solid,
145 Y = 10,
146 Text = "Speed Min:10"
147
148 };
149 _myPlotModel.Annotations.Add(lineTempMaxAnnotation);
150
151 var lineTempMinAnnotation = new LineAnnotation()
152 {
153 Type = LineAnnotationType.Horizontal,
154 Y = 90,
155 Text = "Speed Max:90",
156 Color = OxyColors.Red,
157 LineStyle = LineStyle.Solid
158 };
159 _myPlotModel.Annotations.Add(lineTempMinAnnotation);
160
161 //添加一条标准曲线,四个通道速度
162 var series = new LineSeries()
163 {
164 Color = OxyColors.Green,
165 //曲线的宽度设置
166 StrokeThickness = 2,
167 //标记大小的设置
168 MarkerSize = 3,
169 //标记的颜色设置
170 MarkerStroke = OxyColors.DarkGreen,
171 //标记的类型设置
172 MarkerType = MarkerType.Diamond,
173 Title = "standard",
174 };
175 _myPlotModel.Series.Add(series);
176
177 series = new LineSeries()
178 {
179 Color = OxyColors.Blue,
180 StrokeThickness = 2,
181 MarkerSize = 3,
182 MarkerStroke = OxyColors.BlueViolet,
183 MarkerType = MarkerType.Star,
184 Title = "channel1",
185 };
186 _myPlotModel.Series.Add(series);
187
188 series = new LineSeries()
189 {
190 Color = OxyColors.Yellow,
191 StrokeThickness = 2,
192 MarkerSize = 3,
193 MarkerStroke = OxyColors.Yellow,
194 MarkerType = MarkerType.Star,
195 Title = "channel2",
196 };
197 _myPlotModel.Series.Add(series);
198
199 series = new LineSeries()
200 {
201 Color = OxyColors.SaddleBrown,
202 StrokeThickness = 2,
203 MarkerSize = 3,
204 MarkerStroke = OxyColors.SaddleBrown,
205 MarkerType = MarkerType.Star,
206 Title = "channel3",
207 };
208 _myPlotModel.Series.Add(series);
209
210 series = new LineSeries()
211 {
212 Color = OxyColors.HotPink,
213 StrokeThickness = 2,
214 MarkerSize = 3,
215 MarkerStroke = OxyColors.HotPink,
216 MarkerType = MarkerType.Star,
217 Title = "channel4",
218 };
219 _myPlotModel.Series.Add(series);
220
221 plotView1.Model = _myPlotModel;
222
223 }
224 #endregion
225
226 #region 清除波形
227 private void clearDrawing()
228 {
229 //遍历,清除所有之前绘制的曲线
230 foreach (var lineSer in plotView1.Model.Series)
231 {
232 ((LineSeries)lineSer).Points.Clear();
233 }
234 //清除完曲线之后,重新刷新坐标轴
235 _dateAxis.Minimum = DateTimeAxis.ToDouble(DateTime.Now.AddSeconds(0));
236 _dateAxis.Maximum = DateTimeAxis.ToDouble(DateTime.Now.AddSeconds(1));
237 _myPlotModel.InvalidatePlot(true);
238 Thread.Sleep(10);
239 }
240 #endregion
241
242 #region 曲线绘制
243 //数据曲线绘制
244 private void drawing()
245 {
246 //开启绘图时刷新坐标轴时间
247 _dateAxis.Minimum = DateTimeAxis.ToDouble(DateTime.Now);
248 _dateAxis.Maximum = DateTimeAxis.ToDouble(DateTime.Now.AddMinutes(1));
249
250 bool bToMove = false;
251
252 Task.Factory.StartNew(() =>
253 {
254 while (flag)
255 {
256
257 var date = DateTime.Now;
258 _myPlotModel.Axes[0].Maximum = DateTimeAxis.ToDouble(date.AddSeconds(1));
259
260 var lineSer = plotView1.Model.Series[0] as LineSeries;
261 lineSer.Points.Add(new DataPoint(DateTimeAxis.ToDouble(date), getSendCount()));
262 if (lineSer.Points.Count > 200)
263 {
264 lineSer.Points.RemoveAt(0);
265 }
266 plotView1.Model.Series[0].Title = string.Format("standard : {0:00.00}km/h", getSendCount());
267
268 lineSer = plotView1.Model.Series[1] as LineSeries;
269 lineSer.Points.Add(new DataPoint(DateTimeAxis.ToDouble(date), getChannel1()));
270 if (lineSer.Points.Count > 200)
271 {
272 lineSer.Points.RemoveAt(0);
273 }
274 //Current channel speed refreshes
275 plotView1.Model.Series[1].Title = string.Format("channel1 : {0:00.00}km/h", getChannel1());
276
277 lineSer = plotView1.Model.Series[2] as LineSeries;
278 lineSer.Points.Add(new DataPoint(DateTimeAxis.ToDouble(date), getChannel2()));
279 if (lineSer.Points.Count > 200)
280 {
281 lineSer.Points.RemoveAt(0);
282 }
283 plotView1.Model.Series[2].Title = string.Format("channel2 : {0:00.00}km/h", getChannel2());
284
285 lineSer = plotView1.Model.Series[3] as LineSeries;
286 lineSer.Points.Add(new DataPoint(DateTimeAxis.ToDouble(date), getChannel3()));
287 if (lineSer.Points.Count > 200)
288 {
289 lineSer.Points.RemoveAt(0);
290 }
291 plotView1.Model.Series[3].Title = string.Format("channel3 : {0:00.00}km/h", getChannel3());
292
293 lineSer = plotView1.Model.Series[4] as LineSeries;
294 lineSer.Points.Add(new DataPoint(DateTimeAxis.ToDouble(date), getChannel4()));
295 if (lineSer.Points.Count > 200)
296 {
297 lineSer.Points.RemoveAt(0);
298 }
299 plotView1.Model.Series[4].Title = string.Format("channel4 : {0:00.00}km/h", getChannel4());
300
301
302
303 if (!bToMove)
304 {
305 TimeSpan timeSpan = DateTime.Now - DateTimeAxis.ToDateTime(_dateAxis.Minimum);
306 if (timeSpan.TotalSeconds >= 58)
307 {
308 bToMove = true;
309 }
310 }
311 else
312 {
313 _dateAxis.Minimum = DateTimeAxis.ToDouble(DateTime.Now.AddSeconds(-58));
314 _dateAxis.Maximum = DateTimeAxis.ToDouble(DateTime.Now.AddSeconds(2));
315 }
316
317
318 _myPlotModel.InvalidatePlot(true);
319 Thread.Sleep(1000);
320 }
321 });
322 }
323 #endregion
324
325 private void plotView1_Click(object sender, EventArgs e)
326 {
327
328 }
329
330 //滑轨控件拖拉事件
331 private void trackBar1_Scroll(object sender, EventArgs e)
332 {
333 speed = trackBar1.Value;
334 }
335
336 //start/stop按键单击事件
337 private void Start_Click(object sender, EventArgs e)
338 {
339 if (flag == false)
340 {
341 Start.Text = "stop";
342 flag = true;
343 //开启绘图线程
344 drawing();
345 }
346 else
347 {
348 Start.Text = "start";
349 flag = false;
350 }
351 }
352
353 //波形清除按键单击事件
354 private void clearButton_Click(object sender, EventArgs e)
355 {
356 Start.Enabled = false;
357 clearDrawing();
358 Thread.Sleep(10);
359 flag = false;
360 Thread.Sleep(1100);
361 flag = true;
362 drawing();
363 Start.Enabled = true;
364 }
365
366 }
367 }

  工程代码的基本设计框架如下图所示:

  使用过GDI+绘图的朋友都知道,在进行图像绘制之前都需要先创建一块画布,对一些基本的界面元素需要先完成布局。


使用OxyOplot控件也一样,绘制曲线需要在坐标轴里面进行绘制,那么我们就需要先将该部分坐标轴中的坐标轴标题、X/Y轴、刻度值、四个曲线通道等等进行绘制,该部分的设计代码如下:

  1 #region 绘制坐标轴
2 private void coordinate()
3 {
4 //定义model
5 _myPlotModel = new PlotModel()
6 {
7 Title = "速度检测",
8 //LegendTitle = "通道值",
9 LegendOrientation = LegendOrientation.Horizontal,
10 LegendPlacement = LegendPlacement.Outside,
11 LegendPosition = LegendPosition.TopCenter,
12 LegendBackground = OxyColor.FromAColor(0, OxyColors.Beige),
13 //设置文本框边框颜色
14 LegendBorder = OxyColors.Transparent
15 };
16
17
18
19 //X轴
20 _dateAxis = new DateTimeAxis()
21 {
22 Position = AxisPosition.Bottom,
23 Minimum = DateTimeAxis.ToDouble(DateTime.Now),
24 Maximum = DateTimeAxis.ToDouble(DateTime.Now.AddMinutes(1)),
25 MajorGridlineStyle = LineStyle.Solid,
26 MinorGridlineStyle = LineStyle.Dot,
27 IntervalLength = 80,
28 Title = "time",//显示标题内容
29 TitlePosition = 0.95,//显示标题位置
30 IsZoomEnabled = true,
31 IsPanEnabled = true
32 };
33 _myPlotModel.Axes.Add(_dateAxis);
34
35 //Y轴
36 _valueAxis = new LinearAxis()
37 {
38 //Y轴刻度间距设置
39 MajorStep = 10,
40 Position = AxisPosition.Left,
41 MajorGridlineStyle = LineStyle.Solid,
42 MinorGridlineStyle = LineStyle.Dot,
43 Title = "Speed",//显示标题内容
44 TitlePosition = 1,//显示标题位置
45 IntervalLength = 80,
46 //设置坐标轴上的刻度标签的方向
47 Angle = 0,
48 IsZoomEnabled = false,
49 IsPanEnabled = false,
50 Maximum = 100,
51 Minimum = 0
52 };
53 _myPlotModel.Axes.Add(_valueAxis);
54
55
56 //添加标注线,速度上下限
57 var lineTempMaxAnnotation = new OxyPlot.Annotations.LineAnnotation()
58 {
59 Type = LineAnnotationType.Horizontal,
60 Color = OxyColors.Red,
61 LineStyle = LineStyle.Solid,
62 Y = 10,
63 Text = "Speed Min:10"
64
65 };
66 _myPlotModel.Annotations.Add(lineTempMaxAnnotation);
67
68 var lineTempMinAnnotation = new LineAnnotation()
69 {
70 Type = LineAnnotationType.Horizontal,
71 Y = 90,
72 Text = "Speed Max:90",
73 Color = OxyColors.Red,
74 LineStyle = LineStyle.Solid
75 };
76 _myPlotModel.Annotations.Add(lineTempMinAnnotation);
77
78 //添加一条标准曲线,四个通道速度
79 var series = new LineSeries()
80 {
81 Color = OxyColors.Green,
82 //曲线的宽度设置
83 StrokeThickness = 2,
84 //标记大小的设置
85 MarkerSize = 3,
86 //标记的颜色设置
87 MarkerStroke = OxyColors.DarkGreen,
88 //标记的类型设置
89 MarkerType = MarkerType.Diamond,
90 Title = "standard",
91 };
92 _myPlotModel.Series.Add(series);
93
94 series = new LineSeries()
95 {
96 Color = OxyColors.Blue,
97 StrokeThickness = 2,
98 MarkerSize = 3,
99 MarkerStroke = OxyColors.BlueViolet,
100 MarkerType = MarkerType.Star,
101 Title = "channel1",
102 };
103 _myPlotModel.Series.Add(series);
104
105 series = new LineSeries()
106 {
107 Color = OxyColors.Yellow,
108 StrokeThickness = 2,
109 MarkerSize = 3,
110 MarkerStroke = OxyColors.Yellow,
111 MarkerType = MarkerType.Star,
112 Title = "channel2",
113 };
114 _myPlotModel.Series.Add(series);
115
116 series = new LineSeries()
117 {
118 Color = OxyColors.SaddleBrown,
119 StrokeThickness = 2,
120 MarkerSize = 3,
121 MarkerStroke = OxyColors.SaddleBrown,
122 MarkerType = MarkerType.Star,
123 Title = "channel3",
124 };
125 _myPlotModel.Series.Add(series);
126
127 series = new LineSeries()
128 {
129 Color = OxyColors.HotPink,
130 StrokeThickness = 2,
131 MarkerSize = 3,
132 MarkerStroke = OxyColors.HotPink,
133 MarkerType = MarkerType.Star,
134 Title = "channel4",
135 };
136 _myPlotModel.Series.Add(series);
137
138 plotView1.Model = _myPlotModel;
139
140 }
141 #endregion

  绘制曲线需要外部提供数据,在这里为了方便显示效果,使用了Random()随机数方法生成了三组数据,另外一组数据通过和滑杆控件相关联,总共完成四组通道数据的模拟。


#region 数据模拟
//随机数模拟通道数据
public double getSendCount()
{
double iTotal = 0;
//iTotal = rand.Next(100, 300) / 10.0;
//iTotal = 50.0;
iTotal = speed; return iTotal;
} public double getChannel1()
{
double iTotal = 0;
iTotal = rand.Next(100, 900) / 10.0; return iTotal;
} public double getChannel2()
{
double iTotal = 0;
iTotal = rand.Next(300, 800) / 10.0; return iTotal;
} public double getChannel3()
{
double iTotal;
iTotal = rand.Next(300, 700) / 10.0; return iTotal;
} public double getChannel4()
{
double iTotal;
iTotal = rand.Next(200, 600) / 10.0; return iTotal;
}
#endregion //滑轨控件拖拉事件
private void trackBar1_Scroll(object sender, EventArgs e)
{
speed = trackBar1.Value;
}

  绘制曲线的时候,为了不引起和主线程的冲突,因此单独创建了一个独立的子线程完成绘图的 *** 作,当界面中积累的曲线点数达到60个点时,坐标轴就会往左边平移,曲线绘制函数设计如下:

 1 #region 曲线绘制
2 //数据曲线绘制
3 private void drawing()
4 {
5 //开启绘图时刷新坐标轴时间
6 _dateAxis.Minimum = DateTimeAxis.ToDouble(DateTime.Now);
7 _dateAxis.Maximum = DateTimeAxis.ToDouble(DateTime.Now.AddMinutes(1));
8
9 bool bToMove = false;
10
11 Task.Factory.StartNew(() =>
12 {
13 while (flag)
14 {
15
16 var date = DateTime.Now;
17 _myPlotModel.Axes[0].Maximum = DateTimeAxis.ToDouble(date.AddSeconds(1));
18
19 var lineSer = plotView1.Model.Series[0] as LineSeries;
20 lineSer.Points.Add(new DataPoint(DateTimeAxis.ToDouble(date), getSendCount()));
21 if (lineSer.Points.Count > 200)
22 {
23 lineSer.Points.RemoveAt(0);
24 }
25 plotView1.Model.Series[0].Title = string.Format("standard : {0:00.00}km/h", getSendCount());
26
27 lineSer = plotView1.Model.Series[1] as LineSeries;
28 lineSer.Points.Add(new DataPoint(DateTimeAxis.ToDouble(date), getChannel1()));
29 if (lineSer.Points.Count > 200)
30 {
31 lineSer.Points.RemoveAt(0);
32 }
33 //Current channel speed refreshes
34 plotView1.Model.Series[1].Title = string.Format("channel1 : {0:00.00}km/h", getChannel1());
35
36 lineSer = plotView1.Model.Series[2] as LineSeries;
37 lineSer.Points.Add(new DataPoint(DateTimeAxis.ToDouble(date), getChannel2()));
38 if (lineSer.Points.Count > 200)
39 {
40 lineSer.Points.RemoveAt(0);
41 }
42 plotView1.Model.Series[2].Title = string.Format("channel2 : {0:00.00}km/h", getChannel2());
43
44 lineSer = plotView1.Model.Series[3] as LineSeries;
45 lineSer.Points.Add(new DataPoint(DateTimeAxis.ToDouble(date), getChannel3()));
46 if (lineSer.Points.Count > 200)
47 {
48 lineSer.Points.RemoveAt(0);
49 }
50 plotView1.Model.Series[3].Title = string.Format("channel3 : {0:00.00}km/h", getChannel3());
51
52 lineSer = plotView1.Model.Series[4] as LineSeries;
53 lineSer.Points.Add(new DataPoint(DateTimeAxis.ToDouble(date), getChannel4()));
54 if (lineSer.Points.Count > 200)
55 {
56 lineSer.Points.RemoveAt(0);
57 }
58 plotView1.Model.Series[4].Title = string.Format("channel4 : {0:00.00}km/h", getChannel4());
59
60
61
62 if (!bToMove)
63 {
64 TimeSpan timeSpan = DateTime.Now - DateTimeAxis.ToDateTime(_dateAxis.Minimum);
65 if (timeSpan.TotalSeconds >= 58)
66 {
67 bToMove = true;
68 }
69 }
70 else
71 {
72 _dateAxis.Minimum = DateTimeAxis.ToDouble(DateTime.Now.AddSeconds(-58));
73 _dateAxis.Maximum = DateTimeAxis.ToDouble(DateTime.Now.AddSeconds(2));
74 }
75
76
77 _myPlotModel.InvalidatePlot(true);
78 Thread.Sleep(1000);
79 }
80 });
81 }
82 #endregion

  开始绘制曲线和和停止整合成一个按键,实现原理如下:

 1 //start/stop按键单击事件
2 private void Start_Click(object sender, EventArgs e)
3 {
4 if (flag == false)
5 {
6 Start.Text = "stop";
7 flag = true;
8 //开启绘图线程
9 drawing();
10 }
11 else
12 {
13 Start.Text = "start";
14 flag = false;
15 }
16 }

  在实际的监控界面中,为了满足清除界面的曲线的需求,增加了清除显示曲线的工程,防止出现线程冲突和线程多开,需要先失能开始的按键,因为绘制曲线是没1000ms绘制一次,所以线程在完成一次关闭和开启的时候需要添加一个大于1000ms的延时,界面清除完毕之后再使能开始按钮,设计原理如下:

 1 //波形清除按键单击事件
2 private void clearButton_Click(object sender, EventArgs e)
3 {
4 Start.Enabled = false;
5 clearDrawing();
6 Thread.Sleep(10);
7 flag = false;
8 Thread.Sleep(1100);
9 flag = true;
10 drawing();
11 Start.Enabled = true;
12 }
13
14 #region 清除波形
15 private void clearDrawing()
16 {
17 //遍历,清除所有之前绘制的曲线
18 foreach (var lineSer in plotView1.Model.Series)
19 {
20 ((LineSeries)lineSer).Points.Clear();
21 }
22 //清除完曲线之后,重新刷新坐标轴
23 _dateAxis.Minimum = DateTimeAxis.ToDouble(DateTime.Now.AddSeconds(0));
24 _dateAxis.Maximum = DateTimeAxis.ToDouble(DateTime.Now.AddSeconds(1));
25 _myPlotModel.InvalidatePlot(true);
26 Thread.Sleep(10);
27 }
28 #endregion

  最终效果图如下:

  OxyPlot组件自带一些工程,在图像显示区域可以滚动鼠标的滚轮对曲线进行缩小放大,通过双击鼠标滚轮可以恢复正常曲线显示。


  如有各位大佬们有更好的完善建议和想法,欢迎在留言区一起讨论,一起学习一起进步!

  需要完整工程朋友,可以通过以下链接下载:

  链接:https://pan.baidu.com/s/1Vu1hFEO03_gx5yeCDbgDGA

  提取码:r8na

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/589391.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-12
下一篇 2022-04-12

发表评论

登录后才能评论

评论列表(0条)

保存