C++零基础项目:俄罗斯方块!详细思路+源码分享

news2025/2/5 0:42:59

游戏介绍

这是使用 C++ 和 EasyX 写的一个俄罗斯方块小游戏,里面用到的 C++ 特性并不多。

游戏主要分成了两个类来实现:Game 和 Block 类,分别用来实现游戏逻辑和单独的俄罗斯方块,里面顶多就用到了静态成员函数和变量的特性,但主要的游戏逻辑还是放到了 main 函数中实现。这样,每个类分工明确,就实现了俄罗斯方块游戏。

代码非常简单,里面的每个游戏逻辑都值得初学者分析和学习。

游戏截图

代码分析

1. Block 类

这个类主要封装了俄罗斯方块中的独立小方块,提供了各种函数来操作方块,比如 move 移动方块、clear 清除方块、draw 绘制方块等等。但其中的重点是方块碰撞的判断和旋转方块的算法:方块碰撞的判断可以先移动方块,接着遍历方块在地图上每一个块的位置,如果这里有块,就说明发生碰撞了;而旋转方块的算法,因为除了长条占四格以外,其它方块只占三格,所以我们采用最简单的方法,只旋转三格以内的块,具体做法是中心块不变,分别旋转十字中心和四个角,长条方块另外处理。这样简单的实现方法 “小学生” 也能看懂,于是就实现了 Block 类。

2. Game 类

这个类的主要功能是完成游戏界面的绘制和方块满行的判断。游戏界面的绘制很简单,而对于方块满行的判断,可以先遍历一行,如果满行,就将上面的每一行都下移一格位置。这样,Game 类的实现也就大功告成了!

视频教程

C++俄罗斯方块https://nxv.xet.tech/s/3uAEYJ

源码

#include <easyx.h>
#include <ctime>


#define MAP_WIDTH 10	// 地图宽度
#define MAP_HEIGHT 20	// 地图高度
#define BLOCK_WIDTH 20	// 方块宽度


// 游戏类
class Game
{
public:
	void drawMap() const;						// 绘制地图
	void drawPrompt() const;					// 绘制提示界面
	bool checkLine(const unsigned& line) const;	// 判断满行
	void clearLine();							// 清除满行

// 内联函数
public:
	// 设置地图
	void setMap(const int& x, const int& y, const bool& value)
	{
		map[x][y] = value;
	}

	// 获取地图
	const bool& getMap(const int& x, const int& y) const
	{
		return map[x][y];
	}

	// 添加游戏时间
	void addGameTime()
	{
		gameTime += 500;
	}

private:
	bool map[MAP_WIDTH][MAP_HEIGHT] = { 0 };	// 地图数组
	unsigned score = 0;							// 游戏分数
	unsigned gameTime = 0;						// 游戏时间
};


void Game::drawMap() const
{
	// 绘制边框
	roundrect(10, 10, 340, 430, 10, 10);
	rectangle(20, 20, 220, 420);

	// 绘制地图
	setfillcolor(LIGHTCYAN);
	for (int i = 0; i < MAP_WIDTH; i++)
	{
		for (int j = 0; j < MAP_HEIGHT; j++)
		{
			if (map[i][j])
			{
				int left = 20 + 20 * i, top = 20 + 20 * j;
				fillrectangle(left, top, left + 20, top + 20);
			}
		}
	}
}

void Game::drawPrompt() const
{
	// 设置字体
	LOGFONT f;
	gettextstyle(&f);
	f.lfHeight = 30;
	wcscpy_s(f.lfFaceName, L"微软雅黑");
	f.lfQuality = DEFAULT_QUALITY;
	settextstyle(&f);
	settextcolor(BLACK);

	// 绘制 “下一方块” 提示
	outtextxy(235, 20, L"下一方块");
	rectangle(240, 60, 320, 140);

	// 绘制 “分数” 和 “时间”
	f.lfHeight = 25;
	settextstyle(&f);

	wchar_t str[10];
	wsprintf(str, L"分数:%u", score);
	outtextxy(230, 150, str);

	wsprintf(str, L"时间:%u秒", gameTime / 1000);
	outtextxy(230, 175, str);

	// 绘制 “俄罗斯方块”
	f.lfHeight = 40;
	settextstyle(&f);
	settextcolor(RED);
	outtextxy(234, 250, L"俄罗斯");
	outtextxy(250, 290, L"方块");
	outtextxy(240, 330, L"Tetris");

	// 绘制 “作者”
	f.lfHeight = 25;
	settextstyle(&f);
	outtextxy(234, 395, L"C++俄罗斯方块");
}

bool Game::checkLine(const unsigned& line) const
{
	for (int i = 0; i < MAP_WIDTH; i++)
	{
		if (map[i][line] == 0)
		{
			return false;
		}
	}
	return true;
}

void Game::clearLine()
{
	int line = -1;
	// 判断哪一行满行
	for (int j = 0; j < MAP_HEIGHT; j++)
	{
		if (checkLine(j))
		{
			line = j;
			break;
		}
	}

	if (line != -1)
	{
		// 将上一行移至满行
		for (int j = line; j > 0; j--)
		{
			for (int i = 0; i < MAP_WIDTH; i++)
			{
				map[i][j] = map[i][j - 1];
			}
		}
		score += 10;	// 将游戏分数加 10
	}
	drawPrompt();
}


// 方块类
class Block
{
public:
	Block(Game& game, const int& x = (MAP_WIDTH - 4) / 2, const int& y = 0);	// 初始化函数
	bool move(const unsigned& direction = 0);									// 移动函数,0 表示下移一格,1 表示左移一格,2 表示右移一格,当下移检测到碰撞时返回 true
	void draw() const;															// 绘制函数
	void clear() const;															// 清除函数
	void addMap() const;														// 添加方块到地图
	void rotate();																// 旋转方块,false 向左,true 向右
	bool checkCollision() const;												// 碰撞检查
	static void generateBlockData();											// 初始化方块数据
	void setPos(const int& x = (MAP_WIDTH - 4) / 2, const int& y = 0);			// 设置方块坐标
	void randType();															// 随机方块类型
	void operator=(const Block& block);											// 为 Block 赋值

private:
	int x;							// 方块 X 坐标
	int y;							// 方块 Y 坐标
	unsigned char type;				// 方块类型
	bool direction;					// 旋转方向,false 代表顺时针,true 代表逆时针
	bool block[4][4];				// 方块数组,true 表示有方块,false 表示没方块
	COLORREF color;					// 方块填充颜色
	static bool blockData[7][4][4];	// 方块数据,对象共享的资源
	Game& game;						// Game 引用对象
};


bool Block::blockData[7][4][4] = { 0 };


Block::Block(Game& game, const int& x, const int& y) : game(game), x(x), y(y)
{
	type = rand() % 7;	// 随机方块类型
	direction = 1;
	
	// 设置方块填充颜色
	switch (type)
	{
	case 0:
		color = LIGHTBLUE;
		break;

	case 1:
		color = GREEN;
		break;

	case 2:
		color = LIGHTGREEN;
		break;

	case 3:
		color = YELLOW;
		break;

	case 4:
		color = RED;
		break;

	case 5:
		color = CYAN;
		break;

	case 6:
		color = MAGENTA;
	}

	// 复制方块数据
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			block[i][j] = blockData[type][i][j];
		}
	}
}

bool Block::move(const unsigned& direction)
{
	switch (direction)
	{
	case 0:
		y++;
		if (checkCollision())
		{
			y--;
			return true;
		}
		break;

	case 1:
		x--;
		if (checkCollision())
		{
			x++;
		}
		break;

	case 2:
		x++;
		if (checkCollision())
		{
			x--;
		}
		break;
	}
	return false;
}

void Block::draw() const
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (block[i][j])
			{
				// 绘制方块,Y 坐标为负时不绘制
				if (j + y >= 0)
				{
					setfillcolor(color);
					int left = 20 + BLOCK_WIDTH * (x + i), top = 20 + BLOCK_WIDTH * (j + y);
					fillrectangle(left, top, left + BLOCK_WIDTH, top + BLOCK_WIDTH);
				}
			}
		}
	}
}

void Block::clear() const
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (block[i][j])
			{
				// 清除方块
				int left = 20 + BLOCK_WIDTH * (x + i), top = 20 + BLOCK_WIDTH * (j + y);
				clearrectangle(left, top, left + BLOCK_WIDTH, top + BLOCK_WIDTH);
			}
		}
	}
}

void Block::addMap() const
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (block[i][j])
			{
				// 添加方块
				game.setMap(x + i, y + j, block[i][j]);
			}
		}
	}
}

void Block::rotate()
{
	// 田字方块不旋转
	if (type != 4)
	{
		// 只旋转左上角 3 * 3 的区域
		bool temp;
		if (direction == 0)	// 顺时针旋转
		{
			// 角旋转
			temp = block[0][0];
			block[0][0] = block[0][2];
			block[0][2] = block[2][2];
			block[2][2] = block[2][0];
			block[2][0] = temp;

			// 十字中心旋转
			temp = block[1][0];
			block[1][0] = block[0][1];
			block[0][1] = block[1][2];
			block[1][2] = block[2][1];
			block[2][1] = temp;
		}
		else	// 逆时针旋转
		{
			// 角旋转
			temp = block[0][0];
			block[0][0] = block[2][0];
			block[2][0] = block[2][2];
			block[2][2] = block[0][2];
			block[0][2] = temp;

			// 十字中心旋转
			temp = block[1][0];
			block[1][0] = block[2][1];
			block[2][1] = block[1][2];
			block[1][2] = block[0][1];
			block[0][1] = temp;
		}

		// 处理其它方块的特殊情况
		switch (type)
		{
			// 长条方块
		case 0:
			if (block[1][3])
			{
				block[1][3] = 0;
				block[3][1] = 1;
			}
			else
			{
				block[1][3] = 1;
				block[3][1] = 0;
			}
			break;

			// Z 字方块(正)和 Z 字方块(反)
		case 5:
		case 6:
			// 没有发生碰撞才执行此操作
			if (!checkCollision())
			{
				direction = !direction;
			}
		}

		// 碰撞检查,如果发生碰撞则往相反方向旋转一次
		if (checkCollision())
		{
			// 相反方向旋转
			if (direction == 1)	// 顺时针旋转
			{
				// 角旋转
				temp = block[0][0];
				block[0][0] = block[0][2];
				block[0][2] = block[2][2];
				block[2][2] = block[2][0];
				block[2][0] = temp;

				// 十字中心旋转
				temp = block[1][0];
				block[1][0] = block[0][1];
				block[0][1] = block[1][2];
				block[1][2] = block[2][1];
				block[2][1] = temp;
			}
			else	// 逆时针旋转
			{
				// 角旋转
				temp = block[0][0];
				block[0][0] = block[2][0];
				block[2][0] = block[2][2];
				block[2][2] = block[0][2];
				block[0][2] = temp;

				// 十字中心旋转
				temp = block[1][0];
				block[1][0] = block[2][1];
				block[2][1] = block[1][2];
				block[1][2] = block[0][1];
				block[0][1] = temp;
			}
			if (type == 0)
			{
				if (block[1][3])
				{
					block[1][3] = 0;
					block[3][1] = 1;
				}
				else
				{
					block[1][3] = 1;
					block[3][1] = 0;
				}
			}
		}
	}
}

bool Block::checkCollision() const
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			// 判断方块是否与地图发生碰撞,顶部不判断
			if ((game.getMap(x + i, y + j) || 20 + BLOCK_WIDTH * (x + i) < 20 || 20 + BLOCK_WIDTH * (x + i) + BLOCK_WIDTH > 220 || 20 + BLOCK_WIDTH * (j + y) + BLOCK_WIDTH > 420) && block[i][j])
			{
				return true;
			}
		}
	}
	return false;
}

void Block::generateBlockData()
{
	// 长条方块
	blockData[0][1][0] = blockData[0][1][1] = blockData[0][1][2] = blockData[0][1][3] = true;
	// 七字方块(正)
	blockData[1][0][0] = blockData[1][1][0] = blockData[1][1][1] = blockData[1][1][2] = true;
	// 七字方块(反)
	blockData[2][2][0] = blockData[2][1][0] = blockData[2][1][1] = blockData[2][1][2] = true;
	// 凸字方块
	blockData[3][1][0] = blockData[3][0][1] = blockData[3][1][1] = blockData[3][2][1] = true;
	// 田字方块
	blockData[4][0][0] = blockData[4][1][0] = blockData[4][0][1] = blockData[4][1][1] = true;
	// Z 字方块(正)
	blockData[5][0][0] = blockData[5][1][0] = blockData[5][1][1] = blockData[5][2][1] = true;
	// Z 字方块(反)
	blockData[6][2][0] = blockData[6][1][0] = blockData[6][1][1] = blockData[6][0][1] = true;
}

void Block::setPos(const int& x, const int& y)
{
	this->x = x;
	this->y = y;
}

void Block::randType()
{
	type = rand() % 7;	// 随机方块类型
	direction = 1;

	// 设置方块填充颜色
	switch (type)
	{
	case 0:
		color = LIGHTBLUE;
		break;

	case 1:
		color = GREEN;
		break;

	case 2:
		color = LIGHTGREEN;
		break;

	case 3:
		color = YELLOW;
		break;

	case 4:
		color = RED;
		break;

	case 5:
		color = CYAN;
		break;

	case 6:
		color = MAGENTA;
	}

	// 复制方块数据
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			block[i][j] = blockData[type][i][j];
		}
	}
}

void Block::operator=(const Block& block)
{
	x = block.x;
	y = block.y;
	type = block.type;
	direction = block.direction;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			this->block[i][j] = block.block[i][j];
		}
	}
	color = block.color;
}


int main()
{
	SetWindowText(initgraph(350, 440), L"俄罗斯方块");

	// 设置绘图颜色
	setbkcolor(WHITE);
	cleardevice();
	setlinecolor(BLACK);

	// 生成游戏界面和数据
	srand(time(NULL));
	Block::generateBlockData();

	Game game;
	game.drawMap();
	game.drawPrompt();

	Block b(game);
	Block nextBlock(game, 11, 2);	// 下一方块
	clock_t start = 0;				// 时钟开始时间
	clock_t end;					// 时钟结束时间

	ExMessage msg;

	nextBlock.draw();

	// 游戏循环
	BeginBatchDraw();
	while (true)
	{
		b.clear();
		clearrectangle(20, 20, 220, 420);
		game.drawMap();

		// 按键按下
		while (peekmessage(&msg, EM_KEY) && msg.message == WM_KEYDOWN)
		{
			switch (msg.vkcode)
			{
				// 上键旋转
			case 'W':
			case VK_UP:
				b.rotate();
				break;

				// 下键加速下落
			case 'S':
			case VK_DOWN:
				if (b.move())
				{
					// 重新设置方块
					b.addMap();
					b = nextBlock;
					b.setPos();
					nextBlock.clear();
					nextBlock.setPos(11, 2);
					nextBlock.randType();
					nextBlock.draw();

					// 如果方块刚刚生成却发生了碰撞,说明游戏结束
					if (b.checkCollision())
					{
						MessageBox(GetHWnd(), L"游戏结束!", L"提示", MB_ICONWARNING);
						goto exit;
					}
				}
				break;

				// 左键移动
			case 'A':
			case VK_LEFT:
				b.move(1);
				break;

				// 右键移动
			case 'D':
			case VK_RIGHT:
				b.move(2);
				break;

				// Esc 键退出程序
			case VK_ESCAPE:
				goto exit;
				break;
			}
		}

		// 判断每过了 500 毫秒方块下移一格
		end = clock();
		if (end - start >= 500)
		{
			if (b.move())
			{
				// 重新设置方块
				b.addMap();
				b = nextBlock;
				b.setPos();
				nextBlock.clear();
				nextBlock.setPos(11, 2);
				nextBlock.randType();
				nextBlock.draw();

				// 如果方块刚刚生成却发生了碰撞,说明游戏结束
				if (b.checkCollision())
				{
					MessageBox(GetHWnd(), L"游戏结束!", L"提示", MB_ICONWARNING);
					goto exit;
				}
			}
			start = clock();
			game.addGameTime();
		}
		b.draw();
		game.clearLine();
		FlushBatchDraw();	// 刷新缓冲区
		Sleep(50);			// 每 50 毫秒接收一次按键
	}

exit:
	EndBatchDraw();

	closegraph();
	return 0;
}

大家赶紧去动手试试吧!

此外,我也给大家分享我收集的其他资源,从最零基础开始的教程到C语言C++项目案例,帮助大家在学习C语言的道路上披荆斩棘!

 

 整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!

欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!(↓↓↓↓↓↓)
传送门icon-default.png?t=M85Bhttps://jq.qq.com/?_wv=1027&k=xrXx1Yqt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/103298.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nexus上传自定义starter

nexus上传自定义starter1、starter上传简介2、上传方法2.1、setting.xml文件2.2、项目中的pom文件3、具体部署1、starter上传简介 在我们自定义了springboot的starter后&#xff0c;starter一般有是一个父子级maven工程&#xff0c;如下图所示&#xff0c;对于 autoconfigure 来…

H5 导航栏示例

本文是通过:hover更新元素样式&#xff0c;通过递归树形菜单渲染到页面。 效果 源码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"&…

基于ssm框架的汽车故障维修管理系统源码+开题报告+论文+远程安装部署+视频讲解

博主介绍&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 项目名称 基于ssm框架的汽车故障维修管理系统源码开题报告论文远程安装部署视频讲解 演示视频 视频去哪了呢&#xff1f;_哔哩哔哩_bilibili 系统介绍 项目介绍…

dependencies与dependencyManagement的区别

最近再看项目的时候&#xff0c;无意间注意到项目中的pom文件既有dependencyManagement&#xff0c;也有dependencies&#xff0c;有点疑惑为什么要同时有这两个标签&#xff0c;可能之前没太注意过吧。 dependencies与dependencyManagement的区别&#xff1a; a…

js基础面试题

js基础 h5新特性 1. 新增选择器&#xff1a;querySelector、querySelectorAll 2. 拖拽功能&#xff1a;drag和drop 3. 媒体播放&#xff1a;video和audio 4. 本地存储&#xff1a;localStorage和sessionStorage 5. 语义化标签&#xff1a;article、footer、header、nav、sect…

D. Maximum Sum of Products(二维数组记录改变区间)

Problem - 1519D - Codeforces 给你两个长度为n的整数数组a和b。 你最多可以扭转数组a的一个子数组&#xff08;连续子段&#xff09;。 你的任务是反转这样一个子数组&#xff0c;使其总和∑i1nai⋅bi达到最大。 输入 第一行包含一个整数n&#xff08;1≤n≤5000&#xff0…

第三十四章 linux-模块的加载过程四

第三十四章 linux-模块的加载过程四 文章目录第三十四章 linux-模块的加载过程四调用模块的初始化函数释放INT section所占用的空间呼叫模块通知链模块的卸载find_module检查依赖关系free_modulesys_init_module第二部分由load_module返回的do_init_module实现 static noinlin…

LCHub:到2023年,全球低代码市场预计达到269亿美元

12月13日,Gartner发布全球低代码市场规模报告。数据显示,到2023年,全球低代码市场规模预计达到269亿美元,同比增长19.6%。 业务技术专家认为,到2026年,超级自动化和业务可组合性将成为加速低代码技术应用的关键驱动力。 Gartner还发布了一项调查数据,到2023年全球超级自…

计算机毕设Python+Vue学习类视频网站(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

C++ STL 之堆栈(后进先出) stack 详解

文章目录Part.I AttentionPart.II FuncitonPart.III CodePart.I Attention stack<T>容器适配器的数据是以 LIFO (Last in First Out, 后进先出) 的方式组织的&#xff0c;可以将它想象成放在餐桌上的一摞盘子。必须要包含头文件#include <stack> Part.II Funcito…

2023年天津农学院专升本专业课考试具体安排及准考证打印时间

天津农学院2023年高职升本科专业课考试相关事宜的通知 一、考试时间及考试地点 1.考试时间&#xff1a;2022年12月31日9:00-11:00 2.考试地点&#xff1a;天津农学院东、西校区&#xff0c;每位考生具体的考试地点、考场号等信息以准考证上标注的为准。 天津农学院东校…

人工智能时代,你应该花4个月时间去学习编程,并找到一份好工作

把现在作为你冒险的开始&#xff0c;你会学到一种由高需求的技能&#xff0c;你会有一段新的经历&#xff0c;你会得到新的机会。试一试吧。 编者按&#xff1a;新的一年&#xff0c;很多人都会想要有一个新的开始。在Andrei Neagoie看来&#xff0c;人们应该选择一个非常有前…

maven—分模块开发与设计

项目与模块下载 任务&#xff1a; 将springmvc_ssm这个ssm整合后的项目拆分成四个模块&#xff0c;如下图&#xff1a; ssm_pojo拆分 新建模块 从原始项目中拷贝实体类&#xff08;User&#xff09;到该模块 ssm_dao拆分 新建模块 从原始项目中拷贝相关内容到该模块 数据…

DEJA_VU3D - Cesium功能集 之 089-台风范围几何绘制

前言 编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小130个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(尽可能把代码简洁一些)。博文内容…

学习IB带给了我什么?

在某些层面&#xff0c;IB课程类似于大家更加熟知的AP课程——它是一种让高中学生学习高级、严格课程的体系。然而&#xff0c;有两个主要区别。首先&#xff0c;IB项目在美国高中或者美国大学申请中的受欢迎程度远远低于AP课程&#xff08;个人感觉是这样子滴哈&#xff09;&a…

【复习笔记】嵌入式系统及其原理复习重点

嵌入式系统及其原理复习重点笔记 计算机的发展 第一代——电子管计算机&#xff08;1946—1954年&#xff09; 内 存 延迟线或磁芯外 存 纸带、卡片或磁带工作速度 几千&#xff5e;一万次&#xff0f;秒软 件 机器语言或汇编语言应 用 科学计算代表机型 ENIAC特 点 体积庞大…

【vue】简易封装echarts

将echarts封装成组件&#xff0c;达到只要调用方法&#xff0c;传入数据和相应的参数就能生成图表的效果&#xff0c;避免在项目中编写大量重复和累赘的echarts的配置代码&#xff0c;实现的思路如下&#xff1a; 接口返回的一般是json数据&#xff0c;所以首先要将json数据进…

【LVGL学习笔记】(四)PlatformIO + LVGL8.3配置

LVGL全程LittleVGL&#xff0c;是一个轻量化的&#xff0c;开源的&#xff0c;用于嵌入式GUI设计的图形库。并且配合LVGL模拟器&#xff0c;可以在电脑对界面进行编辑显示&#xff0c;测试通过后再移植进嵌入式设备中&#xff0c;实现高效的项目开发。 LVGL中文教程手册&#…

PicGo+GitHub搭建个人图床用于Markdown、HTML等图片引用

方便程度&#xff1a;★★★★☆ 配置难度&#xff1a;★★☆☆☆ 稳定性&#xff1a;★★★★★ 适用环境&#xff1a;Windows、Mac、Linux 需要工具&#xff1a;GitHub 账号、PicGo 客户端 隐私性&#xff1a;别人可以访问你的图片仓库 GitHub仓库设置 流程&#xff1…

vue后台系统管理项目-商城轮播图管理功能

商城轮播图管理功能 功能介绍&#xff1a; 1.轮播图列表分页功能&#xff1b; 2.轮播图添加功能&#xff1b; 3.轮播图编辑功能&#xff1b; 4.轮播图删除功能&#xff1b; 5.轮播图启用禁用功能&#xff1b; 6.轮播图获取排序号功能&#xff1b; 7.轮播图查看详情功能&#xf…