IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32之简易GUI(多级菜单进阶版) -> 正文阅读

[嵌入式]STM32之简易GUI(多级菜单进阶版)

很久之前,写过一个简易的多级菜单,如今回头看,我都不敢承认那玩意是自己写的,于是打算重新写过一个,并且做成一个简易的GUI。原来用的OLED,现在改成了TFT(ST7789驱动)。但是答题的思路还是不变的。

先说说思路,由于选择的是一个没有触摸的屏幕,于是就要加上物理按键。这样才能操作屏幕嘛。
按照面向对象的思想,我将整个GUI结构分为三类:按键,显示,动作。

  1. 按键用来控制屏幕,进行上滑下滑切入切出菜单;
  2. 显示则作为每一级菜单的背景,并且将菜单设置为静态显示(即每次切换只显示一次);
  3. 动作则用来链接各种显示功能。例如一级菜单切入二级菜单这么个动作;
  4. 除此之外就是各种动态效果,这部分暂时还未开工。

接下来看看menu.c文件的内容

#include <stdio.h>
#include "string.h"
#include "lcd_init.h"
#include "lcd.h"
#include "main.h"
#include "menu.h"

//多级菜单的级数
#define		Menu_Select		3

//选项显示缓存区
char buf[100];

//按键标志初始值
uint8_t List_Number = 1;

//主次菜单切换
static uint8_t Menu_Change = 0;
//菜单刷新标志位
static uint8_t Menu_Refresh_Flag = 0;
//up/down控制按键标志位
static uint8_t UD_Action_Flag = 0;


extern unsigned char BMP_1[];
extern unsigned char BMP_2[];
extern unsigned char BMP_3[];
extern unsigned char BMP_4[];
extern unsigned char BMP_5[];
extern unsigned char BMP_6[];
extern unsigned char BMP_7[];
extern unsigned char BMP_8[];
extern unsigned char BMP_9[];



/******************************************************************************
  * @brief  绘制一条垂线或者横线
  * @param  x1,y1 起始坐标
			x2,y2 终点坐标
			color 颜色
  * @retval 无
  ******************************************************************************/
void Draw_Line(u16 x1, u16 y1, u16 x2, u16 y2, u16 color)
{
	u16 i = 0;
	if (x1 == x2)
	{
		for (i = y1; i <= y2; i++)
		{
			LCD_DrawPoint(x1, i, color);
		}
	}
	if (y1 == y2)
	{
		for (i = x1; i <= x2; i++)
		{
			LCD_DrawPoint(i, y1, color);
		}
	}
}

/******************************************************************************
  * @brief  绘制一个方框
  * @param  x1,y1 起始坐标
			x2,y2 终点坐标
			color 颜色
  * @retval 无
  ******************************************************************************/
void Draw_Rectangle(u16 x1, u16 y1, u16 x2, u16 y2, u16 color)
{
	Draw_Line(x1, y1, x2, y1, color);
	Draw_Line(x1, y1, x1, y2, color);
	Draw_Line(x1, y2, x2, y2, color);
	Draw_Line(x2, y1, x2, y2, color);
}

/******************************************************************************
  * @brief  绘制选项框
  * @param  x1,y1 起始坐标
			x2,y2 终点坐标
			color 颜色
			index 选项框的厚度
  * @retval 无
  ******************************************************************************/
void Draw_Box(u16 x1, u16 y1, u16 x2, u16 y2, u16 color, u16 index)
{
	u16 i = 0;
	for (i = 0; i < index; i++)
	{
		Draw_Rectangle(x1 + i, y1 + i, x2 - i, y2 - i, color);
	}
}

/******************************************************************************
  * @brief  绘制窗体
  * @param  x1,y1 起始坐标
			x2,y2 终点坐标
			Box_Height 选项框的高度
			Menu_List	选项框个数
  * @retval 无
 ******************************************************************************/
void Draw_Windows(void)
{
	Draw_Box(0, 0, 240, 240, LBBLUE, 5);
	Draw_Box(0, 36, 240, 41, LBBLUE, 5);
	sprintf(buf, "   WINDOWS1      ");
	LCD_ShowString(5, 5, (uint8_t*)buf, WHITE, LBBLUE, 32, 0);
}

/******************************************************************************
  * @brief  绘制圆角框
  * @param  x0,y0 圆心坐标
			color 颜色
			length 框体长度
  * @retval 无
 ******************************************************************************/
void Draw_Circal_Box(u16 x0, u16 y0, u8 r, u16 color, u8 length)
{
	int a, b;
	a = 0;
	b = r;
	while (a <= b)
	{
		LCD_DrawPoint(x0 - b, y0 - a, color);
		LCD_DrawPoint(x0 - b, y0 + a, color);
		LCD_DrawPoint(x0 - a, y0 + b, color);
		LCD_DrawPoint(x0 - a, y0 - b, color);

		LCD_DrawPoint(x0 + b + length, y0 - a, color);
		LCD_DrawPoint(x0 + b + length, y0 + a, color);
		LCD_DrawPoint(x0 + a + length, y0 - b, color);
		LCD_DrawPoint(x0 + a + length, y0 + b, color);
		a++;
		if ((a * a + b * b) > (r * r))//判断要画的点是否过远
		{
			b--;
		}
	}
	LCD_DrawLine(x0, y0 - r, x0 + length, y0 - r, color);
	LCD_DrawLine(x0, y0 + r, x0 + length, y0 + r, color);
}

/******************************************************************************
  * @brief  绘制实心圆
  * @param  x0,y0 圆心坐标
			color 颜色
  * @retval 无
 ******************************************************************************/
void Draw_Circal_Solid(u16 x0, u16 y0, u8 r, u16 color)
{
	int a, b, c;
	a = 0;
	b = r;
	for (c = r; c > 0; c--)
	{
		Draw_Circle(x0, y0, c - 1, color);
	}
}



/******************************************************************************
      * @brief	显示初始菜单,自己任意配置
      * @param  x,y 显示坐标
				fc 字的颜色
				bc 字的背景色
				sizey 字号
				mode:  0非叠加模式  1叠加模式
				Box_Height 选项框的高度
      * @retval 无
******************************************************************************/
void Display_Initial_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height)
{
		sprintf(buf,"DISPLAY");
		LCD_ShowString(x, y + ( Box_Height * 0 ), (uint8_t *)buf, fc, bc, sizey, mode);
		sprintf(buf,"VOICE");
		LCD_ShowString(x, y + ( Box_Height * 1 ), (uint8_t *)buf, fc, bc, sizey, mode);
		sprintf(buf,"NOTICE");
		LCD_ShowString(x, y + ( Box_Height * 2 ), (uint8_t *)buf, fc, bc, sizey, mode);
		sprintf(buf,"BATTERY");
		LCD_ShowString(x, y + ( Box_Height * 3 ), (uint8_t *)buf, fc, bc, sizey, mode);
		sprintf(buf,"MEMORY");
		LCD_ShowString(x, y + ( Box_Height * 4 ), (uint8_t *)buf, fc, bc, sizey, mode);
		sprintf(buf,"ABOUT");
		LCD_ShowString(x, y + ( Box_Height * 5 ), (uint8_t *)buf, fc, bc, sizey, mode);
}



/******************************************************************************
	  * @brief	显示二级菜单,自己任意配置
	  * @param  x,y 显示坐标
				fc 字的颜色
				bc 字的背景色
				sizey 字号
				mode:  0非叠加模式  1叠加模式
				Box_Height 选项框的高度
	  * @retval 无
******************************************************************************/
void Display_Second_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height)
{
	sprintf(buf,"DISPLAY");
	LCD_ShowString(x, y + ( Box_Height * 0 ), (uint8_t *)buf, fc, bc, sizey, mode);
	sprintf(buf,"VOICE");
	LCD_ShowString(x, y + ( Box_Height * 1 ), (uint8_t *)buf, fc, bc, sizey, mode);
	sprintf(buf,"NOTICE");
	LCD_ShowString(x, y + ( Box_Height * 2 ), (uint8_t *)buf, fc, bc, sizey, mode);
	sprintf(buf,"BATTERY");
	LCD_ShowString(x, y + ( Box_Height * 3 ), (uint8_t *)buf, fc, bc, sizey, mode);
}



/******************************************************************************
	  * @brief	显示三级菜单,自己任意配置
	  * @param  x,y 显示坐标
				fc 字的颜色
				bc 字的背景色
				sizey 字号
				mode:  0非叠加模式  1叠加模式
				Box_Height 选项框的高度
	  * @retval 无
******************************************************************************/
void Display_Third_Menu(void)
{
	LCD_ShowPicture( 25, 15, 50, 50, BMP_1);	//1
	LCD_ShowPicture( 95, 15, 50, 50, BMP_2);	//2
	LCD_ShowPicture(175, 15, 50, 50, BMP_3);	//3

	LCD_ShowPicture( 15, 95, 50, 50, BMP_4);	//4
	LCD_ShowPicture( 95, 95, 50, 50, BMP_5);	//5
	LCD_ShowPicture(175, 95, 50, 50, BMP_6);	//6

	LCD_ShowPicture( 15, 175, 50, 50, BMP_7);	//7
	LCD_ShowPicture( 95, 175, 50, 50, BMP_8);	//8
	LCD_ShowPicture(175, 175, 50, 50, BMP_9);	//9
}



/******************************************************************************
  * @brief  注册up控制按键
  * @param  Menu_List:控制的列表项数目
  * @retval 无
  * eg:一个页面有6个选项,就让 Menu_List = 6
 ******************************************************************************/
void Button_Up_Click(uint8_t Menu_List)
{
	if (Button_Click_Event_1)
	{
		if (List_Number > 1)
			List_Number = List_Number - 1;
		else if (List_Number == 1)
			List_Number = Menu_List;
		else
			List_Number = List_Number;

		UD_Action_Flag = 2;
		//显示列表项标号
		LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);
	}
}



/******************************************************************************
  * @brief  注册down控制按键
  * @param  控制的列表项数目
  * @retval 无
 ******************************************************************************/
void Button_Down_Click(uint8_t Menu_List)
{
	if (Button_Click_Event_2)
	{
		if (List_Number < Menu_List)
			List_Number = List_Number + 1;
		else if (List_Number == Menu_List)
			List_Number = 1;
		else
			List_Number = List_Number;

		UD_Action_Flag = 1;
		//显示列表项标号
		LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);
	}
}



/******************************************************************************
  * @brief  注册菜单切入控制按键
  * @param  Desk_Num 菜单级数
  * @retval 无
  ****************************************************************************/
void Button_Menu_Enter(u8 Desk_Num)
{
	LCD_ShowIntNum(200,220,Menu_Change,1,WHITE,BLACK,16);
	//切入菜单,即进入下一级菜单
	if(Button_Click_Event_3)
	{
		Action_Menu_Change(0);		//载入下一级菜单动作
		Menu_Refresh_Flag = 1;

		if (Menu_Change < Desk_Num - 1)
			Menu_Change = Menu_Change + 1;
		else if (Menu_Change == Desk_Num - 1)
			Menu_Change = Desk_Num - 1;

		//显示菜单级标号
		LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);
	}
}



/******************************************************************************
  * @brief  注册菜单切出控制按键
  * @param  Desk_Num 菜单级数
  * @retval 无
  ****************************************************************************/
void Button_Menu_Exit(u8 Desk_Num)
{
	LCD_ShowIntNum(200, 220, Menu_Change, 1, WHITE, BLACK, 16);
	//切出菜单,即返回上一级菜单
	if (Button_Click_Event_4)
	{
		Action_Menu_Change(1);		//载入上一级菜单的动作
		Menu_Refresh_Flag = 1;
		
		if (Menu_Change > 0)
			Menu_Change = Menu_Change - 1;
		if (Menu_Change < Desk_Num - 2)
			Menu_Change = 0;

		//显示菜单级标号
		LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);
	}
}



/******************************************************************************
  * @brief  注册菜单切换控制按键搭配的动作
  * @param  select 事件标志
  * @retval 无
  ****************************************************************************/
u8 List = 0,List2 = 0;
void Action_Menu_Change(u8 select)
{
	switch (select)
	{
	case 0:
		switch (Menu_Change)
		{
		case 0:
			List = List_Number;
			List_Number = 1;
			LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);
			break;
		case 1:
			List2 = List_Number;
			List_Number = 1;
			LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);
			break;
		//case ...3级、4级菜单
		}
		break;

	case 1:
		switch (Menu_Change)
		{
		case 1:
			List_Number = List;
			LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);
			break;
		case 2:
			List_Number = List2;	
			LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);
			break;
		//case ...3级、4级菜单
		}
	default:
		break;
	}
	
}



/******************************************************************************
  * @brief  注册选项框上滑动作
  * @param  x1,y1 起始坐标
			x2,y2 终点坐标
			Box_Height 选项框的高度
			Menu_List	选项框个数
  * @retval 无
 ******************************************************************************/
void Action_Box_Up(u16 x1,u16 y1,u16 x2,u16 y2,uint8_t Menu_List, uint8_t Box_Height)
{
	if(UD_Action_Flag == 2)
	{
		UD_Action_Flag = 0;
		if(List_Number >= 1 && List_Number < Menu_List)
		{
			LCD_DrawLine(x1, y2+Box_Height*(List_Number), x2, y2+Box_Height*(List_Number), BLACK);
			LCD_DrawLine(x1, y1+Box_Height*(List_Number), x1, y2+Box_Height*(List_Number), BLACK);
			LCD_DrawLine(x2, y1+Box_Height*(List_Number), x2, y2+Box_Height*(List_Number), BLACK);
			printf("%d",List_Number);
		}
		if(List_Number == Menu_List)
		{
			LCD_DrawRectangle(x1, y1 + Box_Height*(0), x2, y2+Box_Height*(0), BLACK);
		}
	}
	
}



/******************************************************************************
  * @brief  注册选项框下滑动作
  * @param  x1,y1 起始坐标
			x2,y2 终点坐标
			Box_Height 选项框的高度
			Menu_List	选项框个数
  * @retval 无
 ******************************************************************************/
void Action_Box_Down(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height)
{
	if (UD_Action_Flag == 1)
	{
		UD_Action_Flag = 0;
		if (List_Number > 1 && List_Number <= Menu_List)
		{
			LCD_DrawLine(x1, y1 + Box_Height * (List_Number - 2), x2, y1 + Box_Height * (List_Number - 2), BLACK);
			LCD_DrawLine(x1, y1 + Box_Height * (List_Number - 2), x1, y2 + Box_Height * (List_Number - 2), BLACK);
			LCD_DrawLine(x2, y1 + Box_Height * (List_Number - 2), x2, y2 + Box_Height * (List_Number - 2), BLACK);
			printf("%d", List_Number);
		}
		if (List_Number == 1)
		{
			LCD_DrawRectangle(x1, y1 + Box_Height * (Menu_List - 1), x2, y2 + Box_Height * (Menu_List - 1), BLACK);
		}
	}
}


/******************************************************************************
  * @brief  绘制选项框
  * @param  x1,y1 起始坐标
			x2,y2 终点坐标
			Box_Height 选项框的高度
			Menu_List	选项框个数
  * @retval 无
 ******************************************************************************/
void Draw_Option_Box(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height)
{
	LCD_DrawRectangle(x1, y1 + Box_Height * (List_Number - 1), x2, y2 + Box_Height * (List_Number - 1), BLUE);
}



/******************************************************************************
  * @brief  注册菜单
  * @param  无
  * @retval 无
 ******************************************************************************/
void Menu_Display_Control(void)
{
	Button_Menu_Enter(Menu_Select);												//菜单等级控制
	Button_Menu_Exit(Menu_Select);

	switch (Menu_Change)
	{
		case 0:
			if (Menu_Refresh_Flag == 1)
			{
				Display_Initial_Menu(10, 3, WHITE, BLACK, 24, 0, 30);		//注册一个0级菜单
				Menu_Refresh_Flag = 0;
			}

			Draw_Option_Box(3, 0, 200, 30, 6, 30);							//注册选项框

			Action_Box_Up(3, 0, 200, 30, 6, 30); 					//添加选项框的上滑动作
			Action_Box_Down(3, 0, 200, 30, 6, 30); 					//添加选项框的下滑动作

			Button_Down_Click(6);										//注册down控制按钮
			Button_Up_Click(6);											//注册up控制按钮
			break;

		case 1:
			if (Menu_Refresh_Flag == 1)
			{
				Display_Second_Menu(10, 3, RED, BLACK, 32, 0, 59);			//注册一个1级菜单
				Menu_Refresh_Flag = 0;
			}

			Draw_Option_Box(3, 0, 150, 59, 4, 59);							//注册选项框

			Action_Box_Up(3, 0, 150, 59, 4, 59); 					//添加选项框的上滑动作
			Action_Box_Down(3, 0, 150, 59, 4, 59); 					//添加选项框的下滑动作

			Button_Down_Click(4);										//注册down控制按钮
			Button_Up_Click(4);											//注册up控制按钮
			break;

		case 2:
			if (Menu_Refresh_Flag == 1)
			{
				Display_Third_Menu();			//注册一个2级菜单
				Menu_Refresh_Flag = 0;
			}
			
			Draw_Option_Box(0, 0, 80, 79, 3, 79);							//注册选项框

			Action_Box_Up(0, 0, 80,79, 3, 79); 						//添加选项框的上滑动作
			Action_Box_Down(0, 0, 80, 79, 3, 79); 					//添加选项框的下滑动作

			Button_Down_Click(3);										//注册down控制按钮
			Button_Up_Click(3);											//注册up控制按钮
			break;

		default:
			Menu_Refresh_Flag = 0;
			break;
	}
}

重点:如何创建菜单

如果使用其中自带的选项框,那么只需要修改两处即可
一是 Action_Menu_Change(u8 select) 函数,在其中添加 List_Number 变量的继承;
二是Menu_Display_Control(void) 函数中注册新的菜单。
重点在第二个函数

  1. 首先要确定菜单级数
  2. 然后申请一个或多个新的菜单,并且在Menu_Refresh_Flag == 1的情况下申请,不然会不断刷新屏幕
  3. 然后注册选项框,注册选项框的动作,动作可以自行修改
  4. 最后注册按键

接下来是menu.h文件内容

#ifndef __menu_H_
#define __menu_H_
#include <stdio.h>
#include "string.h"
#include "stdint.h"
#include "main.h"



#define Button_Click_Event_1	HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET	//up控制按键
#define Button_Click_Event_2	HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET	//down控制按键
#define Button_Click_Event_3	HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET	//菜单切入控制按键
#define Button_Click_Event_4	HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET	//菜单切出控制按键


void Display_Initial_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height);
void Display_Second_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height);
void Display_Third_Menu(void);


void Draw_Box(u16 x1, u16 y1, u16 x2, u16 y2, u16 color, u16 index);
void Draw_Line(u16 x1, u16 y1, u16 x2, u16 y2, u16 color);
void Draw_Rectangle(u16 x1, u16 y1, u16 x2, u16 y2, u16 color);
void Draw_Option_Box(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);


void Draw_Windows(void);
void Draw_Circal_Box(u16 x0,u16 y0,u8 r,u16 color, u8 length);
void Draw_Circal_Solid(u16 x0, u16 y0, u8 r, u16 color);


void Button_Down_Click(uint8_t Menu_List);
void Button_Up_Click(uint8_t Menu_List);
void Button_Menu_Enter(u8 Desk_Num);
void Button_Menu_Exit(u8 Desk_Num);


void Action_Box_Up(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);
void Action_Box_Down(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);
void Action_Menu_Change(u8 select);


void Menu_Display_Control(void); 

 
//void DA(void);
#endif

最后是main.c的内容

在while循环里添加 Menu_Display_Control(); 即可,菜单的其他配置都在menu.c中配置完成。

int main(void)
{
  	HAL_Init();
 	SystemClock_Config();
  	MX_GPIO_Init();
  	MX_USART2_UART_Init();

  	LCD_Init();//LCD初始化
	LCD_Fill(0,0,LCD_W,LCD_H,BLACK);

	Display_Initial_Menu(10, 3, WHITE, BLACK, 24, 0, 30);		//注册一个0级菜单,防止刚开机不显示东东西
	LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);
  while (1)
  {
    /* USER CODE END WHILE */
		
    /* USER CODE BEGIN 3 */
		Menu_Display_Control();
			
  }
  /* USER CODE END 3 */
}

显示效果

B站视频效果

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-03-12 17:43:47  更:2022-03-12 17:44:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 6:43:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码