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站视频效果

引用\[1\]中提到了在使用STM32F103标准库进行多级菜单显示时遇到的问题。根据描述,问题出现在页面切换后功能没有执行。根据其他博主的代码,可以使用结构体和枚举来操作界面切换和函数功能的调用。作者通过不断调试和根据自己的想法实现了所需的功能。同时,作者也表示会学习其他大佬的代码风格。 引用\[2\]中提到了在创建STM32F103工程时,需要添加对应的分组。为了让工程结构清晰,可以让MDK的工程分组和工程文件夹对应起来。由于MDK分组不支持多级目录,可以将路径也带入分组命名中,以便区分。 引用\[3\]中提到了STM32F103系列的启动文件存放在STM32CubeF1软件包的特定文件夹下。对于STM32F103ZET6开发板,对应的启动文件为startup_stm32f103xe.s。为了节省空间,可以对启动文件进行修改,如将Heap_Size设置为0,去掉不需要调用的SystemInit函数等。 综上所述,如果你想在STM32F103标准库中实现多级菜单功能,可以参考其他博主的代码,使用结构体和枚举来操作界面切换和函数功能的调用。同时,为了让工程结构清晰,可以将MDK的工程分组和工程文件夹对应起来。在创建工程时,需要添加对应的分组。此外,还可以根据需要对启动文件进行修改,以满足项目的需求。 #### 引用[.reference_title] - *1* [2021校赛基于stm32f103多功能台灯](https://blog.csdn.net/boybs/article/details/121339111)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [【正点原子STM32连载】 第六章 新建寄存器本MDK工程 摘自【正点原子】STM32F103 战舰开发指南V1.2](https://blog.csdn.net/weixin_55796564/article/details/130576791)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值