·天新网首页·加入收藏·设为首页·网站导航
数码笔记本手机摄像机相机MP3MP4GPS
硬件台式机网络服务器主板CPU硬盘显卡
办公投影打印传真
家电电视影院空调
游戏网游单机动漫
汽车新车购车试驾
下载驱动源码
学院开发设计
考试公务员高考考研
业界互联网通信探索
您现在的位置:天新网 > 软件开发 > .Net开发 > C#
使用 C# 开发智能手机软件:推箱子(十一) 介绍 Common/Env.cs 源程序文件
http://www.21tx.com 2012年08月30日 博客园 银河

这是“使用 C# 开发智能手机软件:推箱子”系列文章的第十一篇。在这篇文章中,介绍 Common/Env.cs 源程序文件。这个源程序文件中包含表示“工作环境”的密封类 Env 。也就是说,主程序中重要的变量都封装在这个类中,作为整个程序的“工作环境”。她还起着桥梁作用,其中两个字段:

   DataFile db;       // 数据文件
   ConfigFile cfg;    // 配置文件

   正是我们以前介绍过的管理数据文件的密封类 DataFile 和管理配置文件的密封类 ConfigFile 的实例。密封类 Env 中的不少属性和方法是通过这两个字段调用其各自的属性和方法。
   下面对密封类 Env 中的一些方法作点说明:


   GetClientSize 方法用来计算当使用标准箱子尺寸时主窗体客户区的尺寸。该方法仅当程序运行在计算机上时才会被调用,使主窗体的尺寸根据当前关的尺寸自动调整。程序运行在智能手机时是不会被调用的,因为在智能手机上本程序并不改变主窗体的尺寸。
   SetBoxInfo 方法的作用是根据客户区尺寸计算箱子的尺寸,并相应设定要显示的图形单元。图形单元共有 24x24、20x20、16x16 和 12x12 四种尺寸,如下所示:

使用 C# 开发智能手机软件:推箱子(十一) 介绍 Common/Env.cs 源程序文件

使用 C# 开发智能手机软件:推箱子(十一) 介绍 Common/Env.cs 源程序文件

使用 C# 开发智能手机软件:推箱子(十一) 介绍 Common/Env.cs 源程序文件  

使用 C# 开发智能手机软件:推箱子(十一) 介绍 Common/Env.cs 源程序文件

   如果使用 12x12 的图形单元还不能在客户区完整显示地图的话,可能这一关的游戏就无法玩了。
   Draw 方法用来更新主窗体客户区,也就是在主窗体客户区画出本关的地图,并根据玩家的动作随时更新地图。
   Design 方法实现在设计模式下,当鼠标点击时要采取的动作。
   StepIt 方法实现工人往指定方向前进一步(可能推着箱子)。
   Back 方法实现工人后退一步(可能连带箱子一起后退)。
   GetMoveInfo 方法寻找一条将工人移动到鼠标点击的位置的路线。她调用我们以前介绍过的静态类 FindPath 的 Seek 方法来寻找最短路线。
   GetPushInfo 方法给出将箱子推动到鼠标点击的位置所需的信息。
   到此为止,Common 目录下所有源程序文件都介绍完了,这些源程序文件中包含的类是实现整个程序功能的基础。在随后的文章中将介绍 Windows 目录下的源程序文件,她们包含的类实现整个程序的用户界面。

下面就是密封类 Env 的源程序代码,虽然稍微长了一点,但是里面的注释比较详细,应该不难理解。
 1using System;
 2using System.Drawing;
 3using System.Collections.Generic;
 4
 5namespace Skyiv.Ben.PushBox.Common
 6{
 7  /// <summary>
 8  /// 工作环境
 9  /// </summary>
10  sealed class Env : IDisposable
11  {
12    DataFile db;       // 数据文件
13    ConfigFile cfg;    // 配置文件
14    string errorMsg;   // 错误信息
15    string debugMsg;   // 调试信息
16    bool isReplay;     // 是否正在回放
17    Action active;     // 模式: 正常 新建 编辑 删除
18    byte pen;          // 设计时的笔
19    Bitmap img;        // 图形单元, 横向被均匀分为八份
20    Stack<Step> stack; // 历史路线, 用于后退功能
21    Size clientSize;   // 工作区域尺寸(以像素为单位)
22    Size boxSize;      // 图形元素尺寸(以像素为单位)
23    Point toPixel;     // 将要到达的位置(以像素为单位)
24    Point worker;      // 当前工人位置(以单元格为单位)
25    int pushSteps;     // 推动着箱子走的步数
26    int levelOem;      // 原来的关数,仅用于“菜单 -> 数据 -> 设计 -> 新建”放弃后恢复现场
27
28    public string ErrorMsg { get { return errorMsg; } }
29    public string DebugMsg { get { return debugMsg; } }
30    public string[] Groups { get { return cfg.Groups; } }
31    public int Group { get { return cfg.Group; } set { cfg.Group = value; } }
32    public int Level { get { return cfg.Levels[Group]; } set { cfg.Levels[Group] = value; } }
33    public int LeveLOem { get { return levelOem; } }
34    public int MaxLevel { get { return db.MaxLevel; } }
35    public string Steps { get { return cfg.Steps; } }
36    public Size LevelSize { get { return db.LevelSize; } }
37    public Size ClientSize { set { clientSize = value; } }
38    public Point ToPixel { set { toPixel = value; } }
39    public int MaxLevelSize { get { return cfg.MaxLevelSize; } set { cfg.MaxLevelSize = value; } }
40    public int StepDelay { get { return cfg.StepDelay; } set { cfg.StepDelay = value; } }
41    public int ReplayDelay { get { return cfg.ReplayDelay; } set { cfg.ReplayDelay = value; } }
42    public Action Active { get { return active; } set { active = value; } }
43    public byte Pen { get { return pen; } set { pen = value; } }
44    public bool HasError { get { return !string.IsNullOrEmpty(errorMsg); } }
45    public bool HasWorker { get { return db.HasWorker; } }
46    public bool CanUndo { get { return stack.Count != 0; } }
47    public bool CanReplay { get { return db.IsFinished && !CanUndo; } }
48    public bool IsSave { get { return cfg.IsSave; } set { cfg.IsSave = value; } }
49    public bool IsFinish { get { return db.Tasks == db.Boths; } }
50    public bool IsReplay { get { return isReplay; } set { isReplay = value; } }
51    public bool IsDesign { get { return active != Action.None; } }
52
53    public Env()
54    {
55      stack = new Stack<Step>();
56      cfg = new ConfigFile();
57      db = new DataFile();
58      Init();
59    }
60
61    /// <summary>
62    /// 状态栏信息
63    /// </summary>
64    public string StatusMessage
65    {
66      get
67      {
68        return HasError ? "请点击“菜单 -> 帮助 -> 错误信息”" : string.Format(
69          "{0} {1}/{2} {3} {4} {5} [{6}] {7}",
70          (active == Action.Create) ? '+' : (active == Action.Edit) ? '=' : isReplay ? "|/-\"[stack.Count % 4] : '>',
71          Level + 1, MaxLevel, Pub.ToString(LevelSize),
72          IsDesign ? string.Format("{0}={1}", db.Boxs, db.Slots) : string.Format("{0}/{1}", db.Boths, db.Tasks),
73          IsDesign ? Block.GetPenName(pen) : string.Format("{0}({1})", stack.Count, pushSteps),
74          IsDesign ? (active == Action.Create ? "新建" : "编辑") : db.IsFinished ?
75          string.Format("{0}({1})", db.MovedSteps, db.PushedSteps) : string.Empty,
76          db.GroupName);
77      }
78    }
79
80    public void Dispose()
81    {
82      db.Dispose();
83    }
84
85    public void Init()
86    {
87      active = Action.None;
88      pen = Block.Land;
89      stack.Clear();
90      SetExceptionMessage(null);
91    }
92
93    void SetExceptionMessage(Exception ex)
94    {
95      errorMsg = Pub.GetMessage(ex, false);
96      debugMsg = Pub.GetMessage(ex, true);
97    }
98
99    /// <summary>
100    /// 计算当使用标准箱子尺寸时主窗体客户区的尺寸
101    /// </summary>
102    /// <param name="statusBarHeight">状态条的高度</param>
103    /// <returns>客户区的尺寸</returns>
104    public Size GetClientSize(int statusBarHeight)
105    {
106      int width = (Properties.Resources.PushBox24.Width / 8) * LevelSize.Width;
107      int height = Properties.Resources.PushBox24.Height * LevelSize.Height + statusBarHeight;
108      if (width < 240) width = 240;
109      if (height < 48) height = 48;
110      if (width > 1008) width = 1008;
111      if (height > 672) height = 672;
112      return new Size(width, height);
113    }
114
115    /// <summary>
116    /// 根据客户区尺寸,计算箱子的尺寸,并相应设定要显示的图形单元
117    /// </summary>
118    public void SetBoxInfo()
119    {
120      if (HasError) return;
121      if (LevelSize.IsEmpty) return;
122      int rX = clientSize.Width / LevelSize.Width;
123      int rY = clientSize.Height / LevelSize.Height;
124      int r = Math.Min(rX, rY);
125      if (r >= 24) img = Properties.Resources.PushBox24;
126      else if (r >= 20) img = Properties.Resources.PushBox20;
127      else if (r >= 16) img = Properties.Resources.PushBox16;
128      else img = Properties.Resources.PushBox12;
129      boxSize = new Size(img.Height, img.Width / 8);
130    }
131
132    /// <summary>
133    /// 装入配置文件
134    /// </summary>
135    public void LoadConfig()
136    {
137      if (HasError) return;
138      try
139      {
140        cfg.LoadConfig();
141      }
142      catch (Exception ex)
143      {
144        SetExceptionMessage(ex);
145      }
146    }
147
148    /// <summary>
149    /// 保存组信息到配置文件
150    /// </summary>
151    /// <param name="groups">组信息</param>
152    public void SaveConfig(string[] groups)
153    {
154      if (HasError) return;
155      try
156      {
157        cfg.SaveConfig(groups);
158      }
159      catch (Exception ex)
160      {
161        SetExceptionMessage(ex);
162      }
163    }
164
165    /// <summary>
166    /// 保存当前选项及当前走法到配置文件
167    /// </summary>
168    public void SaveConfig()
169    {
170      if (HasError) return;
171      try
172      {
173        cfg.SaveConfig(stack.ToArray());
174      }
175      catch (Exception ex)
176      {
177        SetExceptionMessage(ex);
178      }
179    }
180
181    /// <summary>
182    /// 装入当前组信息
183    /// </summary>
184    public void LoadGroup()
185    {
186      if (HasError) return;
187      try
188      {
189        db.LoadGroup(Groups[Group]);
190      }
191      catch (Exception ex)
192      {
193        SetExceptionMessage(ex);
194      }
195    }
196
197    /// <summary>
198    /// 装入当前关信息
199    /// </summary>
200    public void LoadLevel()
201    {
202      active = Action.None;
203      if (HasError) return;
204      try
205      {
206        db.LoadLevel(Level);
207        worker = db.Worker;
208        stack.Clear();
209        pushSteps = 0;
210        isReplay = false;
211      }
212      catch (Exception ex)
213      {
214        SetExceptionMessage(ex);
215      }
216    }
217
218    /// <summary>
219    /// 新建一关
220    /// </summary>
221    /// <param name="isCopy">是否复制当前关</param>
222    /// <param name="size">新建关的尺寸</param>
223    public void NewLevel(bool isCopy, Size size)
224    {
225      if (HasError) return;
226      try
227      {
228        levelOem = Level;
229        Level = MaxLevel;
230        db.NewLevel(isCopy, size);
231      }
232      catch (Exception ex)
233      {
234        SetExceptionMessage(ex);
235      }
236    }
237
238    /// <summary>
239    /// 给出通关步骤
240    /// </summary>
241    /// <returns>通关步骤</returns>
242    public string GetSteps()
243    {
244      string steps = "";
245      if (!HasError)
246      {
247        try
248        {
249          steps = db.GetSteps(Level);
250        }
251        catch (Exception ex)
252        {
253          SetExceptionMessage(ex);
254        }
255      }
256      return steps;
257    }
258
259    /// <summary>
260    /// 记录通关步骤
261    /// </summary>
262    public void Record()
263    {
264      if (HasError) return;
265      try
266      {
267        db.SaveLevel(Level, stack.ToArray(), pushSteps);
268      }
269      catch (Exception ex)
270      {
271        SetExceptionMessage(ex);
272      }
273    }
274
275    /// <summary>
276    /// 保存设计数据
277    /// </summary>
278    public void SaveDesign()
279    {
280      if (HasError) return;
281      try
282      {
283        db.SaveDesign(active == Action.Create, Level);
284      }
285      catch (Exception ex)
286      {
287        SetExceptionMessage(ex);
288      }
289    }
290
291    /// <summary>
292    /// 删除最后一关
293    /// </summary>
294    public void DeleteLastLevel()
295    {
296      if (HasError) return;
297      try
298      {
299        db.DeleteLastLevel(Level);
300      }
301      catch (Exception ex)
302      {
303        SetExceptionMessage(ex);
304      }
305    }
306
307    /// <summary>
308    /// 更新主窗体客户区
309    /// </summary>
310    /// <param name="dc">画布</param>
311    /// <param name="rectangle">要在其中绘画的矩形</param>
312    public void Draw(Graphics dc, Rectangle rectangle)
313    {
314      if (HasError) return;
315      Rectangle box = PixelToBox(rectangle);
316      Rectangle box2 = new Rectangle(box.Left, box.Top, box.Width + 1, box.Height + 1);
317      for (int i = 1; i <= LevelSize.Height; i++)
318      {
319        for (int j = 1; j <= LevelSize.Width; j++)
320        {
321          if (!box2.Contains(j, i)) continue;
322          DrawBox(dc, j, i);
323        }
324      }
325    }
326
327    /// <summary>
328    /// 绘制一个单元格
329    /// </summary>
330    /// <param name="dc">画布</param>
331    /// <param name="x">单元格的横坐标</param>
332    /// <param name="y">单元格的纵坐标</param>
333    void DrawBox(Graphics dc, int x, int y)
334    {
335      DrawBox(dc, db.Map[y, x], (x - 1) * boxSize.Width, (y - 1) * boxSize.Height);
336    }
337
338    /// <summary>
339    /// 绘制一个单元格
340    /// </summary>
341    /// <param name="dc">画布</param>
342    /// <param name="idx">单元格的类型: 地 槽 墙 砖 箱子 工人</param>
343    /// <param name="x">单元格的横坐标</param>
344    /// <param name="y">单元格的纵坐标</param>
345    void DrawBox(Graphics dc, int idx, int x, int y)
346    {
347      dc.DrawImage(img, x, y, new Rectangle(idx * boxSize.Width, 0, boxSize.Width, boxSize.Height), GraphicsUnit.Pixel);
348    }
349
350    /// <summary>
351    /// 将单元格换算为像素
352    /// </summary>
353    /// <param name="box">单元格矩形</param>
354    /// <returns>像素矩形</returns>
355    Rectangle BoxToPixel(Rectangle box)
356    {
357      return new Rectangle((box.Left - 1) * boxSize.Width, (box.Top - 1) * boxSize.Height,
358        (box.Width + 1) * boxSize.Width, (box.Height + 1) * boxSize.Height);
359    }
360
361    /// <summary>
362    /// 将像素换算为单元格
363    /// </summary>
364    /// <param name="pixel">像素矩形</param>
365    /// <returns>单元格矩形</returns>
366    Rectangle PixelToBox(Rectangle pixel)
367    {
368      int x0 = pixel.Left / boxSize.Width + 1;
369      int y0 = pixel.Top / boxSize.Height + 1;
370      int x1 = (pixel.Right - 1) / boxSize.Width + 1;
371      int y1 = (pixel.Bottom - 1) / boxSize.Height + 1;
372      return new Rectangle(x0, y0, x1 - x0, y1 - y0);
373    }
374
375    /// <summary>
376    /// 根据指定的对角顶点创建矩形
377    /// </summary>
378    /// <param name="a">顶点</param>
379    /// <param name="b">对角的顶点</param>
380    /// <returns>所需要的矩形</returns>
381    Rectangle GetRectangle(Point a, Point b)
382    {
383      return Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y));
384    }
385
386    /// <summary>
387    /// 设计模式下,当鼠标点击时要采取的动作
388    /// </summary>
389    /// <param name="invalid">输出:要重绘的区域</param>
390    /// <returns>是否发生动作</returns>
391    public bool Design(out Rectangle invalid)
392    {
393      invalid = Rectangle.Empty;
394      Point to;
395      if (!ValidClick(out to)) return false;
396      db.UpdateCounts(to.X, to.Y, false);
397      Block.Update(ref db.Map[to.Y, to.X], pen);
398      db.UpdateCounts(to.X, to.Y, true);
399      if (pen == Block.Man0 && HasWorker) pen = Block.Box0;
400      invalid = BoxToPixel(GetRectangle(to, to));
401      return true;
402    }
403
404    /// <summary>
405    /// 工人往指定方向前进一步(可能推着箱子)
406    /// </summary>
407    /// <param name="dir">前进的方向</param>
408    /// <param name="isStop">“撤销”时是否停留</param>
409    /// <param name="invalid">输出:要重绘的区域</param>
410    /// <returns>是否成功</returns>
411    public bool StepIt(Direction dir, bool isStop, out Rectangle invalid)
412    {
413      invalid = Rectangle.Empty;
414      if (HasError) return false;
415      if (Direction.None == dir) return false;
416      Point p1 = worker; // 工人前进方向一步的位置
417      Point p2 = worker; // 箱子前进方向一步的位置
418      switch (dir)
419      {
420        case Direction.East: p1.X++; p2.X += 2; break;
421        case Direction.South: p1.Y++; p2.Y += 2; break;
422        case Direction.West: p1.X--; p2.X -= 2; break;
423        case Direction.North: p1.Y--; p2.Y -= 2; break;
424      }
425      byte b1 = db.Map[p1.Y, p1.X]; // 工人前进方向一步位置上的东西
426      bool isBox = Block.IsBox(b1); // 是否推着箱子前进
427      if (!isBox && !Block.IsBlank(b1)) return false; // 如果没有推着箱子且前方不是空地则失败
428      if (isBox && !Block.IsBlank(db.Map[p2.Y, p2.X])) return false; // 如果推着箱子且箱子前方不是空地则失败
429      invalid = BoxToPixel(GetRectangle(worker, isBox ? p2 : p1)); // 要重绘的区域
430      stack.Push(new Step(dir, isBox, isStop)); // 记录走法步骤
431      Block.ManOut(ref db.Map[worker.Y, worker.X]); // 工人离开当前位置
432      Block.ManIn(ref db.Map[p1.Y, p1.X]); // 工人进入前方位置
433      if (isBox)
434      {
435        pushSteps++; // 更新推箱子步数
436        db.Boths += (db.Map[p2.Y, p2.X] - Block.Land) - (b1 - Block.Box0); // 更新已完成任务数
437        Block.BoxOut(ref db.Map[p1.Y, p1.X]); // 箱子离开当前位置
438        Block.BoxIn(ref db.Map[p2.Y, p2.X]); // 箱子进入前方位置
439      }
440      worker = p1; // 更新工人位置
441      return true; // 工人成功前进一步(可能推着条子)
442    }
443
444    /// <summary>
445    /// 工人后退一步(可能连带箱子一起后退)
446    /// </summary>
447    /// <param name="invalid">输出:要重绘的区域</param>
448    /// <returns>是否完成“撤消”</returns>
449    public bool Back(out Rectangle invalid)
450    {
451      invalid = Rectangle.Empty;
452      if (HasError) return true;
453      if (stack.Count == 0) return true;
454      Step step = stack.Pop(); // 当前步骤
455      Point p1 = worker; // 工人后退方向一步的位置
456      Point p2 = worker; // 箱子的当前位置
457      switch (step.Direct)
458      {
459        case Direction.East: p1.X--; p2.X++; break;
460        case Direction.South: p1.Y--; p2.Y++; break;
461        case Direction.West: p1.X++; p2.X--; break;
462        case Direction.North: p1.Y++; p2.Y--; break;
463      }
464      invalid = BoxToPixel(GetRectangle(p1, step.IsBox ? p2 : worker)); // 要重绘的区域
465      Block.ManOut(ref db.Map[worker.Y, worker.X]); // 工人离开当前位置
466      Block.ManIn(ref db.Map[p1.Y, p1.X]); // 工人进入后退方向一步的位置
467      if (step.IsBox)
468      {
469        Block.BoxOut(ref db.Map[p2.Y, p2.X]); // 箱子离开当前位置
470        Block.BoxIn(ref db.Map[worker.Y, worker.X]); // 箱子进入工人原来的位置
471        db.Boths += (db.Map[worker.Y, worker.X] - Block.Box0) - (db.Map[p2.Y, p2.X] - Block.Land); // 更新已完成任务数
472        pushSteps--; // 更新推箱子步数
473      }
474      worker = p1; // 更新工人位置
475      return step.IsStop; // 是否完成“撤消”
476    }
477
478    /// <summary>
479    /// 寻找一条将工人移动到鼠标点击的位置的路线
480    /// </summary>
481    /// <returns>移动的路线</returns>
482    public Queue<Direction> GetMoveInfo()
483    {
484      Point to;
485      if (!CanTo(out to)) return null;
486      return FindPath.Seek(db.Map, worker, to);
487    }
488
489    /// <summary>
490    /// 给出将箱子推动到鼠标点击的位置所需的信息
491    /// </summary>
492    /// <param name="dir">输出:工人移动的方向</param>
493    /// <returns>工人移动的步数</returns>
494    public int GetPushInfo(out Direction dir)
495    {
496      dir = Direction.None;
497      if (HasError) return 0;
498      Point to; // 目的地
499      if (!CanTo(out to)) return 0; // 无效的目的地
500      if (to.Y != worker.Y && to.X != worker.X) return 0; // 目的地和工人不在同一条直线上
501      int z0 = (to.Y == worker.Y) ? worker.X : worker.Y;
502      int z9 = (to.Y == worker.Y) ? to.X : to.Y;
503      if (to.Y == worker.Y) dir = (z9 > z0) ? Direction.East : Direction.West;
504      else dir = (z9 > z0) ? Direction.South : Direction.North;
505      int i0 = Math.Min(z9, z0);
506      int i9 = Math.Max(z9, z0);
507      int steps = i9 - i0; // 目的地和工人之间的距离
508      int boxs = 0;
509      for (int i = i0 + 1; i < i9; i++)
510      {
511        byte bi = (to.Y == worker.Y) ? db.Map[worker.Y, i] : db.Map[i, worker.X];
512        if (Block.IsBox(bi)) boxs++; // 计算工人和目的地之间的箱子的个数
513        else if (!Block.IsBlank(bi)) boxs += 2; // “墙”和“砖”折算为两个箱子
514      }
515      if (boxs > 1) return 0; // 最多只能推着一个箱子前进
516      return steps - boxs; // 工人移动的步数
517    }
518
519    /// <summary>
520    /// 检查鼠标点击位置是否可达, 并将像素换算为单元格
521    /// </summary>
522    /// <param name="to">输出:换算后的位置</param>
523    /// <returns>是否可达</returns>
524    bool CanTo(out Point to)
525    {
526      if (!ValidClick(out to)) return false;
527      if (!Block.IsMan(db.Map[worker.Y, worker.X])) throw new Exception("内部错误:工人的位置上不是工人");
528      if (!Block.IsBlank(db.Map[to.Y, to.X])) return false; // 目的地必须是“地”或“槽”
529      if (to.Y == worker.Y && to.X == worker.X) return false; // 目的地不能是工人当前的位置
530      return true; // 目的地可达
531    }
532
533    /// <summary>
534    /// 检查鼠标点击位置是否有效, 并将像素换算为单元格
535    /// </summary>
536    /// <param name="to">输出:换算后的位置</param>
537    /// <returns>是否有效位置</returns>
538    bool ValidClick(out Point to)
539    {
540      to = Point.Empty;
541      if (HasError) return false;
542      to.Y = toPixel.Y / boxSize.Height + 1;
543      to.X = toPixel.X / boxSize.Width + 1;
544      if (toPixel.X >= boxSize.Width * LevelSize.Width || toPixel.Y >= boxSize.Height * LevelSize.Height)
545        return false; // 目的地超出当前关的有效范围
546      return true; // 目的地有效
547    }
548  }
549}
550

上一篇: 使用 C# 开发智能手机软件:推箱子(九)介绍 Common/ConfigFile.cs 源程序文件
下一篇: 使用 C# 开发智能手机软件:推箱子(十)介绍 Common/DataFile.cs 源程序文件

关于我们 | 联系我们 | 加入我们 | 广告服务 | 投诉意见 | 网站导航
Copyright © 2000-2011 21tx.com, All Rights Reserved.
晨新科技 版权所有 Created by TXSite.net