仿真Windows_XP画图板的java实现
( 一 )
——蓝杰实训·1016项目组·吴少聪
一、项目开发与相关的技术点掌握:
Question 1:高仿Windows_XP画图板需要一些什么技术点?
需要的技术点包括:窗体的创建、菜单栏创建、数组的使用、颜色选择器的创建、鼠标动作监听器的创建与添加、绘制图形相应的方法的实现、重绘的实现,文件输出流与输入流的结合实现图画的存储读取、基本存储格式的运用(伪BMP图片格式的使用)。
Question 2:高仿Windows_XP画图板的开发需要通过的步骤?
一个简单的高仿Windows_XP画图板其实通过一下的几步便可实现。
①、创建窗体、创建画布(画图区域)、创建菜单栏、创建工具栏、创建颜色选择栏;
②、创建鼠标监听器对象并添加到画图区域(画布)上,实现相应坐标点数据的传入;
③、调用相应的方法,根据传入的坐标数据,在画图区域(画布)上实现相应图形的绘制;
④、根据“缓存—内存”的关系,定义“重绘”的方法,在窗体最小化(缓存释放)之后再次打开窗体依旧能看到所画图形的实现;
⑤、文件输出流和文件输入流的创建,及相应的包装,实现最后的保存与读取;
⑥、保存文件格式的自定义,伪BMP图片格式的自定义读取存储;
⑦、网络图片资源的获取,使图形按钮更相似。
二、项目开发过程与技术点掌握过程:
Part 1:窗体(JFrame)、容器(JPanel)、按钮(JButton)与布局;
该部分的掌握,最初源于对于Windows_XP计算器的模仿:
窗体(JFrame),即一个基本的外围界面,可理解为最大的外围容器,可将JPanel之类的小型容器放于其上,一个最简单的窗体包括:窗体开头(窗体名)、默认使用的边框(表现为设置好的大小格式)、窗体右上角自带“最小化”、“最大化”、“关闭窗体”三个按钮、以及其余属性。
如图所示:
其实现的代码如下:
import javax.swing.JFrame;
/**
* 定义窗体示范类,该类继承自JFrame
*
* @author 吴少聪
*/
public class Calcolator extends JFrame {
/**
* 主函数部分
*/
public static void main(String[] args) {
// 实例化一个对象:cal,并且用该对象调用init方法
Calcolator cal = new Calcolator();
cal.init();
}
/**
* 定义初始化窗体界面的方法:init
*/
public void init() {
// 设置窗体的属性
this.setTitle("这是一个窗体,可以随意设置窗体名");// 窗体名的设定
this.setSize(320, 250);// 窗体大小的设定,其大小单位为像素点数量,可理解为分辨率为320x250
this.setDefaultCloseOperation(3);// 设置窗体关闭时退出程序
this.setLocationRelativeTo(null);// 设置窗体创建之后在屏幕上居中显示
this.setResizable(false);// 设置窗体大小固定,无法实现最大化
this.setVisible(true);// 设置窗体对外可见(可显示出来)
}
}
……………………………………分割线……………………………………………………
窗体的创建这就像是搭建了一个大体的框架,如一个衣柜或是一个壁橱钉好了最外围的框架,现在我们要往上添加放置具体器物的格子,即放置最基本的JPanel小容器。
如图所示:
窗体要变成这样,我们需要做出如下的源码添加:
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* 定义窗体示范类,该类继承自JFrame
*
* @author 吴少聪
*/
public class Calcolator extends JFrame {
/**
* 主函数部分
*/
public static void main(String[] args) {
// 实例化对象cal,并且用该对象调用init方法
Calcolator cal = new Calcolator();
cal.init();
}
/**
* 定义初始化窗体界面的方法:init
*/
public void init() {
// 设置窗体的属性
this.setTitle("这是一个窗体,可以随意设置窗体名");// 窗体名的设定
this.setSize(420, 250);// 窗体大小的设定,其大小单位为像素点数量,可理解为分辨率为420x250
this.setLocationRelativeTo(null);// 设置窗体创建之后在屏幕上居中显示
this.setResizable(false);// 设置窗体大小固定,无法实现最大化
// <<<<<<<<<<<<<<JPanel容器面板的创建与放置>>>>>>>>>>>>>>>>//
JPanel panel = new JPanel();// 创建(实例化)一个JPanel面板对象:panel
panel.setPreferredSize(new Dimension(65, 65));// 同样的,面板自己也有大小,设置面板的大小
panel.setBackground(Color.blue);// 设置面板自身的背景颜色:蓝色(可自定义)
this.add(panel);// 将创建并定义好的面板放置在窗体上
this.setDefaultCloseOperation(3);// 设置窗体关闭时退出程序
this.setVisible(true);// 设置窗体对外可见(可显示出来)
}
}
……………………………………分割线……………………………………………………
很显然,出错了……错误在哪?我们往窗体上放置面板,窗体的大小(计算边框)为420*250分辨率,而在我们自己的设置当中,面板的大小仅为65*65分辨率,按理来说面板与窗体大小相差甚远,但是从运行情况上来看,窗体明显被蓝色背景颜色的面板占满,违背了我们设置大小差异的初衷。那么,问题在哪?答案很简单:布局!所谓布局,通俗来讲便是放置具体器物的空间规划,而窗体本身默认一个布局:边框布局BorderLayout,由于我们未加定义具体的参数,所以布局将面板拉伸扩充至将窗体界面占满为止。
BorderLayout.布局的形式如下:
<!--EndFragment-->
因此我们只需要把源码中的部分语句增添说明便可:
this.add(panel);// 将创建并定义好的面板放置在窗体上
改为:
this.add(panel, BorderLayout.SOUTH);// 将创建并定义好的面板放置在窗体上
便可实现:
<!--EndFragment-->
或是:
虽然能够做到对布局做一个大体的改变,但是仍然发现无论是什么样的方位安排,JPanel面板都会填充这一方位,我们定义分辨率的两个数据中也只有一个能起到作用,这也必然达不到我们预期的希望:按照我们的计划自由安排JPanel面板在窗体上的摆放,且大小完全由我们自定义,而不是方位上的拉伸扩充。
于是我们可以采取使用别的布局方法:
Java语言三大布局管理方法:FlowLayout(流式布局)、BorderLayout(边框布局)、GridLayout(网格布局管理器)
这里采用FlowLayout(流式布局)进行改造示范:
添加如下源码语句:
<!--EndFragment-->
this.setLayout(new FlowLayout(FlowLayout.CENTER));// 定义窗体为流式布局,并将JPanel面板放于正中间
并将之前做的布局更改取消,得到如下效果:
<!--EndFragment-->
现在达到了一个比较令人满意的效果,JPanel面板的方位和大小都能够按照安排摆放了,但是要实现一开始的仿Windows_XP计算器面板效果,还有两部分未完成:文本框与按钮。
由于文本框并不是高仿Windows_XP画图板开发所需的技术点,这里简要叙述,且该计算器只是单纯的面板画面模仿,并未实现任何计算和输入功能。
文本框(JTextField)的代码实现如下:
<!--EndFragment-->
//<<<<<<<<<<<<<<<<JTextField文本框的创建与添加>>>>>>>>>>>>>//
JTextField txt = new JTextField(27);//创建(实例化)一个文本框对象txt
txt.setText("这是一个文本框");//设置文本框显示的字符串
txt.setHorizontalAlignment(JTextField.LEFT);//设置显示文本框从左边开始显示字符串
txt.setEditable(false);
panel.setLayout(new FlowLayout(FlowLayout.CENTER));//面板本身也可以设置布局
panel.add(txt);//将文本框添加到JPanel面板上
由于需要效果明显一些,所以在添加时,取消了窗体的流式布局,实现效果如下:
<!--EndFragment-->
…………………………………………分割线………………………………………………
文本框的简单实现如上,还有绝大部分的功能未有实现,有待后期学习的发掘,现在进入重要性较高的一个部分:按钮(JButton)的创建和添加。
按钮创建与添加的源码实现如下:
<!--EndFragment-->
//<<<<<<<<<<<<<<<<JButton按钮的创建与添加>>>>>>>>>>>>>>>>//
String buttonName=new String("这是一个按钮");//定义一个字符串常量
JButton jbn = new JButton(buttonName);//创建(实例化)一个按钮对象:jbn
jbn.setFont(new Font("Arial Narrow",Font.BOLD,18));
panel.add(jbn);//添加此按钮到面板上
实现过后的效果如下:
(可看见按钮上自动设置了“焦点”)
…………………………………………分割线………………………………………………
到了现在,基本的技术点已经演示完一遍,在实例中,多个按钮的创建可以通过循环数组,且可以采取整体的网格布局,但由于高仿Windows_XP画图板的开发重点并不仅仅是一个画面风格的构建,所以在这里不做仿Windows_XP计算器界面逐步完善过程的进一步阐述,现将完善后的全部源码分享如下,大家可以多多批评指正:
<!--EndFragment-->
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* 定义计算器类,该类继承自JFrame
*
* @author 吴少聪
*/
public class Calcolator extends JFrame {
/**
* 主函数部分
*/
public static void main(String[] args) {
// 实例化对象cal,并且用该对象调用init方法
Calcolator cal = new Calcolator();
cal.init();
}
/**
* 定义初始化窗体界面的方法:init
*/
public void init() {
// 设置窗体的属性
this.setTitle("仿Windows_XP计算器界面");// 窗体名的设定
this.setSize(420, 250);// 窗体大小的设定,其大小单位为像素点数量,可理解为分辨率为420x250
this.setLocationRelativeTo(null);// 设置窗体创建之后在屏幕上居中显示
this.setResizable(false);// 设置窗体大小固定,无法实现最大化
// <<<<<<<<<<<<<<JPanel容器面板的创建与放置>>>>>>>>>>>>>>>>//
JPanel panel01 = new JPanel();// 创建(实例化)一个JPanel面板对象:panel01
panel01.setPreferredSize(new Dimension(350, 65));// 同样的,面板自己也有大小,设置面板的大小
panel01.setBackground(Color.white);// 设置面板自身的背景颜色:白色(可自定义)
JPanel panel02 = new JPanel();// 创建(实例化)第二个JPanel面板对象:panel02
panel02.setLayout(new GridLayout(4, 6, 5, 5));// 面板对象:panel02采用网格布局法
this.add(panel01, BorderLayout.NORTH);// 将创建并定义好的面板放置在窗体上
this.add(panel02, BorderLayout.CENTER);
// <<<<<<<<<<<<<<<<JTextField文本框的创建与添加>>>>>>>>>>>>>//
JTextField txt = new JTextField(27);// 创建(实例化)一个文本框对象txt
txt.setText("0.");// 设置文本框显示的字符串
txt.setHorizontalAlignment(JTextField.RIGHT);// 设置显示文本框从右边开始显示字符串
txt.setEditable(false);
panel01.setLayout(new FlowLayout(FlowLayout.RIGHT));// 面板本身也可以设置布局
panel01.add(txt);// 将文本框添加到JPanel01面板上
// <<<<<<<<<<<<<<<<JButton按钮的创建与添加>>>>>>>>>>>>>>>>//
JButton btn1 = new JButton("Backspace");// 实例化一个按钮对象btn1,按钮上显示Backspace
JButton btn2 = new JButton(" CE ");// 同上
JButton btn3 = new JButton(" C ");// 同上
panel01.add(btn1);// 将按钮添加到面板panel01上
panel01.add(btn2);
panel01.add(btn3);
// 定义一个array数组,指定数组的每一个元素值(即计算器按钮上符号的集群)
String array[] = { "MC", "7", "8", "9", "/", "sqrt", "MR", "4", "5",
"6", "*", "%", "MS", "1", "2", "3", "-", "1/x", "M+", "0",
"+/-", ".", "+", "=" };
// 使用循环array数组,并在循环中创建按钮对象
for (int i = 0; i < array.length; i++) {
// 创建对象,按钮上显示的文字从数组中获取
JButton jbn = new JButton(array[i]);
jbn.setFont(new Font("Arial Narrow", Font.BOLD, 12));
panel02.add(jbn);// 将创建的按钮放置到面板panel02上
}
this.setDefaultCloseOperation(3);// 设置窗体关闭时退出程序
this.setVisible(true);// 设置窗体对外可见(可显示出来)
}
}
实现的效果如图:
<!--EndFragment-->
写报告期间重新写的,跟之前写的(开篇演示的第一幅图)效果存在一些参数上的差距,无论像不像,也请各位一笑而过,技术点在这就行了。
…………………………………………分割线………………………………………………
Part 2:颜色选择器的创建、鼠标动作监听器的创建与添加、绘制图形相应的方法的实现。
该部分的掌握源于简单画板的实现:
<!--EndFragment-->
Color按钮点击后所弹出的颜色选择界面:
<!--EndFragment-->
可实现选择颜色的使用和记忆存储……
(该颜色选择界面无需个人定义,为系统自带默认使用,功能已经很完善)
界面的绘制与之前的仿Windows_XP画图板类似,但更加简单(按钮数量少,布局单一,未加入文本框)简单画图板的实现只是为了实现监听、一些基本的传值和相应的图形绘制。
实现上述简单画板的代码如下:
<<<<<<<<<<<<<<<<<<<<<<<<<画板界面创建类>>>>>>>>>>>>>>>>>>>>>>>>>
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* 简单画图板程序类,该类继承自JFrame类
*
* @author 吴少聪
*/
public class Draw extends JFrame {
// 将一些常用量定义成属性
private String item = "Line";// 默认画直线
private Color color = Color.BLACK;// 默认使用的颜色为:黑色(可自定义)
/**
* 主函数部分
*/
public static void main(String[] args) {
// 创建对象:draw,并调用初始化界面的方法
Draw draw = new Draw();
draw.init();
}
/**
* 初始化界面的方法
*/
public void init() {
// 设置窗体的属性
this.setTitle("这是一个简单的画图板");
this.setSize(600, 500);
this.setDefaultCloseOperation(3);
this.setResizable(false);
this.setLocationRelativeTo(null);
// 创建(实例化)画图面板对象
JPanel panel = new JPanel();
panel.setBackground(Color.white);
this.add(panel, BorderLayout.CENTER);
// 创建简单的工具面板的对象
ToolPanel tp = new ToolPanel();
// 添加到窗体上,窗体默认的布局是边框布局BorderLayout
this.add(tp, BorderLayout.WEST);// 将工具面板置于布局的west方位(之前已经介绍过BorderLayout的布局方式)
this.setVisible(true);// 窗体可视化
// 面板上获取获取Graphics对象,用于相应图形的绘制
// 技巧:要获取Graphics对象,必须在窗体可见之后,才可以获取到,否则获取的是空值null
Graphics gh = panel.getGraphics();
// 创建画图监听事件处理类对象:dl
DrawListener dl = new DrawListener(gh, this);
// 将创建好的鼠标监听器添加到面板上
panel.addMouseListener(dl);
// 将创建好的接收组件上的鼠标移动事件的监听器添加到面板上
panel.addMouseMotionListener((MouseMotionListener) dl);
}
/**
* 定义一个内部类,此类用于创建一个简单的工具面板,继承自JPanel类
*/
class ToolPanel extends JPanel {
/**
* 构造函数
*/
public ToolPanel() {
JPanel panel = new JPanel();// 实例化一个JPanel对象panel
panel.setPreferredSize(new Dimension(100, 200));// 设置相应的大小
// panel.setBackground(Color.BLUE);
/**
* 定义一个匿名内部类,此类用于创建一个动作监听器
*/
// 实例化一个动作监听器对象:al
ActionListener al = new ActionListener() {
// 事件处理方法
public void actionPerformed(ActionEvent e) {
// 判断点击的是否是color按钮,如果是,则弹出一个颜色选择界面,如果不是直接获取按钮上的文本值
if (e.getActionCommand().equals("Color")) {
// 弹出界面,用户选择颜色,如果没有选择,返回null.
color = JColorChooser.showDialog(null, "选择颜色",
Color.BLACK);
System.out.println(color);
} else {
// 获取选择的图形
item = e.getActionCommand();
}
}
};
// 创建按钮
JButton btnLine = new JButton("Line");
JButton btnRect = new JButton("Rect");
JButton btnOval = new JButton("Oval");
JButton btnCurve = new JButton("Curve");
JButton btnColor = new JButton("Color");
// 添加按钮
panel.add(btnLine);
panel.add(btnRect);
panel.add(btnOval);
panel.add(btnColor);
panel.add(btnCurve);
// 往按钮上添加监听器
btnLine.addActionListener(al);
btnRect.addActionListener(al);
btnOval.addActionListener(al);
btnColor.addActionListener(al);
btnCurve.addActionListener(al);
// 将面板添加到窗体上
this.add(panel);
}
}
// 根据动作得到要画图形的指令
public String getItem() {
return item;
}
// 根据颜色选择界面的选择情况确定接下来绘制的颜色
public Color getColor() {
return color;
}
}
>>>>>>>>>>>>>>>>>>>>>鼠标监听器与绘图类<<<<<<<<<<<<<<<<<<<
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.event.MouseInputListener;
public class DrawListener implements MouseListener, MouseInputListener {
// 定义存储坐标值的变量
private int x1, y1, x2, y2;
// 定义一个画图形的对象
private Graphics g;
// 获取图形和颜色的对象
private Draw draw;
/**
* 定义带参数的构造函数
*/
public DrawListener(Graphics g, Draw draw) {
this.g = g;
this.draw = draw;
}
/**
* 鼠标在事件源上按下时执行的方法
*/
public void mousePressed(MouseEvent e) {
// 获取第一个点的坐标值
x1 = e.getX();
y1 = e.getY();
}
/**
* 鼠标在事件源上释放时执行的方法
*/
public void mouseReleased(MouseEvent e) {
// 获取第二个点的坐标值
x2 = e.getX();
y2 = e.getY();
// 设置图形的颜色
g.setColor(draw.getColor());
// 判断选择的图形
if (draw.getItem().equals("Line")) {
// 画直线
g.drawLine(x1, y1, x2, y2);
} else if (draw.getItem().equals("Rect")) {
// 画矩形
if (x2 > x1 && y2 > x1) {
g.drawRect(x1, y1, Math.abs(x2 - x1), Math.abs(y2 - y1));
} else if (x2 < x1 && y2 < y1) {
g.drawRect(x2, y2, Math.abs(x2 - x1), Math.abs(y2 - y1));
} else if (x2 > x1 && y2 < y1) {
g.drawRect(x1, y2, Math.abs(x2 - x1), Math.abs(y2 - y1));
} else if (x2 < x1 && y2 > y1) {
g.drawRect(x2, y1, Math.abs(x2 - x1), Math.abs(y2 - y1));
}
} else if (draw.getItem().equals("Oval")) {
// 画圆
if (x2 > x1 && y2 > x1) {
g.drawOval(x1, y1, Math.abs(x2 - x1), Math.abs(y2 - y1));
} else if (x2 < x1 && y2 < y1) {
g.drawOval(x2, y2, Math.abs(x2 - x1), Math.abs(y2 - y1));
} else if (x2 > x1 && y2 < y1) {
g.drawOval(x1, y2, Math.abs(x2 - x1), Math.abs(y2 - y1));
} else if (x2 < x1 && y2 > y1) {
g.drawOval(x2, y1, Math.abs(x2 - x1), Math.abs(y2 - y1));
}
} else if (draw.getItem().equals("Curve")) {
// 画曲线
g.drawOval(x1, y1, Math.abs(x1 - x2), Math.abs(y1 - y2));
}
}
/**
* 鼠标在事件源上单击时执行的方法
*/
public void mouseClicked(MouseEvent e) {
}
/**
* 鼠标进入事件源时执行的方法
*/
public void mouseEntered(MouseEvent e) {
}
/**
* 鼠标退出事件源时执行的方法
*/
public void mouseExited(MouseEvent e) {
}
@Override
// 重写父类的方法
public void mouseDragged(MouseEvent e) {
// 获取第二个点的坐标值
x2 = e.getX();
y2 = e.getY();
// 设置图形的颜色
g.setColor(draw.getColor());
// 判断选择的图形
if (draw.getItem().equals("Curve")) {
// 画曲线
g.drawLine(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
}
}
public void mouseMoved(MouseEvent e) {
}
}
……………………………………分割线…………………………………………
刚开始绘制图形时,碰到了以下的两个问题:
第一:从左上角往右下角绘制矩形和椭圆总是正常的,即:主对角线向下方向正常,但其余的方向上绘制时皆发生明显的偏移,为什么?!
第二:计算机不认识曲线是什么,也没有绘制曲线的已经定义好的方法,那么如何绘制这个最基础的曲线问题?!
问题一的解决:JDK API.1.60说明文件中,对于drawRect方法的介绍如下:
不难看出,画一个矩形所需要的四个数据:矩形左上角坐标点的x,y坐标值,以及矩形的长宽。若是使用一般的方法绘制矩形,开始时传入的坐标值为我们在panel画图面板上点击鼠标左键时获取的坐标点,该坐标点被方法默认为所需绘制矩形的左上角开始点(该方法也只会从左上角开始绘制矩形),由于长宽是由点击与释放鼠标左键所获取的两点坐标之间的绝对值(使用math.abs()取得),故不会收到任何的影响。无论我们往任何方向画矩形,其都会将鼠标点击瞬间取得的坐标点作为矩形的左上角,并由此开始画,所以主次对角线四个方向只有一个会正常绘制,其余的都只会发生绘图位置偏移而大小不变的问题,原因就是左上角坐标等于鼠标点击点坐标导致的,应该考虑到不同的情况,对起止坐标大小进行比较后,再定义左上角坐标点位置。相同的道理,画椭圆其实是画一个看不见的矩形内接圆,也是相同的道理,上述代码中已经用if语句判断解决了这种问题,比较的繁琐,其实可以用三目运算符“…:…?…:…”或是使用Math.min()实现,可减少一半的代码量。
问题二的解决:归根到底,若是直线足够短,且所绘制的短直线首尾相接,那么宏观来看,便是实现了曲线的绘制,鼠标点击时获取曲线端点的坐标,使用特殊的鼠标监听器,监听鼠标在事件源(panel画图面板)上的拖动。不断地获取拖动轨迹上的坐标值,并且不断地进行首尾坐标的交替,再利用简单的绘制直线的办法便可实现。、
……………………………………分割线…………………………………………
Part 3:窗体重绘(repaint)的实现
<!--EndFragment-->![](http://dl.iteye.com/upload/attachment/0064/1459/130506ea-7e32-3ec6-9177-da7a658494aa.bmp)
最小化与弹出后重绘实现的验证:
最小化:
再次弹出窗体:
很明显地看到,重绘已经成功实现。
接下来便是重要的:文件的保存与读取:
简单测试:之前技术老师写了一个用相同方法存取的画板,所以其创建的文件是相同的格式,都是伪BMP格式,现在用老师的画板作画,在我写的面板读取:
老师的画图板绘画:
<!--EndFragment-->
老师画图文件的存储地址:
用自己写的画图板打开:
<!--EndFragment-->
自己画图板的读取地址:
那么读取的测试完成,现在测试保存,使用自身验证的方法,先进行一定的绘图,然后保存,接着做更改,然后打开保存的文件,要是成功的话,画布会回到保存的时候的模样,而保存动作之后的添加则消失。(这为画板“撤销”功能的实现提供了思路,尚待开发)
测试如下:
第一步:设置相同的存储与读取地址
<!--EndFragment-->
第二步:开启程序绘图:四个实心矩形,四个空心矩形,两个空心椭圆
<!--EndFragment-->
第三步:保存之后进行修改:
<!--EndFragment-->
第四步:打开文件,验证是否覆盖当前画面:
<!--EndFragment-->
很显然,程序测试成功!
………………………………………分割线………………………………………………
阶段总结:虽然因为时间原因未能真正的进行仿真制作,但是基本的技术点已经如上总结,通过对该阶段的java技术掌握,对于后面的进一步学习奠定了基础。
代码和叙述有不当之处,请各位谅解,新手上路,还请多加指教。
待真正完成之后,将会写下一阶段的总结报告,敬请期待。
<!--EndFragment-->