python学生管理系统(pyqt5 含界面)

news2024/9/24 9:27:58

学生管理系统项目流程

项目模块

账号登陆

人脸识别

增添学生信息

删除学生信息

改动学生信息

查询学生信息

项目主体框架

在这里插入图片描述

  • app.py为主代码,负责用于界面打开展示。
  • img文件夹负责放置项目qrc的图像
  • page文件夹为单独页面的类
  • plugin文件夹为功能模块的类
  • ui文件夹为存放页面的ui文件和pyuic5导出的py文件

项目设计知识:

  1. PyQt5
  2. Mysql
  3. 线程与进程
  4. docker

Mysql数据库的搭建(docker快速部署方案)

安装docker

1、使用daocloud脚本安装docker。

curl -sSL https://get.daocloud.io/docker | sh

2、检查安装状态

sudo service docker status(q退出)

或者docker -v

安装Mysql

1、拉取mysql。

docker pull mysql

2、创建一个目录存放mysql数据文件。

mkdir mysql

3、 进入mysql目录。

cd mysql

4、启动mysql。(scc110186为密码,自己设置,开放安全组3306端口)

docker run --name mysqlserver -v $PWD/conf:/etc/mysql/conf.d -v $PWD/logs:/logs -v $PWD/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=scc110186 -d -i -p 3306:3306 mysql:latest

5、查看mysql在容器的名称。

docker ps -a

6、进入docker mysql容器。

docker exec -it mysqlserver bash

7、连接mysql。

mysql -uroot -pscc110186

8、执行命令。

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'scc110186';

9、打开SQLyog等数据库连接软件,输入主机地址,用户名,密码,进行连接,即可连接成功。

项目功能插件部分

Mysql数据库操作部分

连接数据库

首先,因为我们的代码中,经常出现增删查改的部分,那么我们可以认为这个部分就是高频调用部分,那么一般我们再写项目的时候,我们可以将高频调用的部分,直接放入我们的函数中,再用一个类对这些函数统一进行封装,来封装出类的方法。
那么首先我们的类的初始化部分,一定要对数据库进行连接,然后创建游标。用self来修饰变量,使他们可以在整个类中被访问到。
下面的代码涉及到数据库参数的部分,被我清除了,请大家自行写入。

class db_handle:
    def __init__(self):
        # 初始化自动连接Mysql数据库
        self.db = pymysql.connect(host="",
                                  user="",
                                  password="",
                                  database="")
        self.cur = self.db.cursor()

上面的代码中self.db和self.cur为整个db_handle类中公用的部分。所以我们可以在类的方法中进行调用、访问。

账号密码登陆验证

其次就是账号密码登录部分,我们可以封装一个方法,接受两个形参,一个是账号,一个是密码。因为账号为数据库的主键,所以账号是唯一的,所以我们可以通过搜索账号,获取数据库中存储的密码,来进行验证。代码如下:

def login_check(self, id, pwd):
    """
    登录时账号密码的检查函数
    Author:SCC
    :param id: 账号
    :param pwd: 密码
    :return: True / False
    """
    sql_message = "SELECT pwd FROM login_db WHERE id='{}'".format(id)  # 因为 id 为主键
    self.cur.execute(sql_message)
    data = self.cur.fetchall()
    logger.debug(data)
    right_pwd = data[0][0]
    if pwd == right_pwd:
        return True
    else:
        return False

那么我们可以看到一个搜索数据库的一个方法。SELECT ? FROM ? WHERE ?。搜索到的结果一般是以元组来装载的,在元组中又是一个又一个小的元组。我们可以通过下标进行访问。用for或while进行遍历输出。函数的返回值为True或者False。这样我们的界面在调用函数时,就可以清楚的知道账号密码是否正确。同时可以通过if判断来对函数返回值的True和False做判断,实现登陆界面是否跳转主界面的逻辑。

搜索表内所有学生数据

在界面打开后我们需要搜索所有学生的信息,并显示在tableWidget控件上。那么我们可以继续使用搜索语句,搜索数据库中所有的学生信息。

def info_init(self):
    sql_message = "SELECT * FROM student_info"  # 搜索所有学生信息数据
    self.cur.execute(sql_message)  # 执行语句
    datas = self.cur.fetchall()  # 获取所有信息结果
    logger.debug(datas)
    data_list = [data for data in datas]
    return data_list

上面就是对所有学生信息进行查询的方法,在查询之后,将每一组学生数据通过列表进行存储。上述存储方式使用的为列表推导式。如果为初学阶段,可以改为如下代码。

for data in datas:
    data_list.append(data)

上面为简易版本,但是速度上存在差异,不适用于高数据IO时进行使用。如果可以的话,甚至可以封装一个queue队列,进行先入先出显示。存储在线程中。会让程序更加高效。

删除学生信息

现在就是我们进入增删改查部分的编写了,我们要先对学生的信息删除进行编写。代码如下:

def remove(self, name, id, class_text, phone, addr):
    sql_message = "DELETE FROM student_info WHERE name='{}'" \
                  " and id='{}'" \
                  " and class='{}'" \
                  " and phone='{}'" \
                  " and addr='{}'".format(name, id, class_text, phone, addr)
    self.cur.execute(sql_message)
    self.db.commit()

上述代码中包含了删除的书写方式,同时可以将删除部分写在一行中,但是因为Python书写规范中明确表明:一行字符最多不能超过120个,所以使用这种方式更加规范,易读。

增加、更改、查询学生信息

剩下的代码就是增删改查中增、改、查部分了。主题思路与删除一致。不过多赘述。

def search(self, name):
    sql_message = "SELECT * FROM student_info WHERE name='{}'".format(name)
    self.cur.execute(sql_message)
    datas = self.cur.fetchall()
    data_list = [data for data in datas]
    return data_list

def insert(self, name, id, class_text, phone, addr):
    sql_message = "INSERT INTO student_info(name, id, class, phone, addr)" \
                  " values('{}', '{}', '{}', '{}', '{}')".format(name, id, class_text, phone, addr)
    self.cur.execute(sql_message)
    self.db.commit()

def change(self, name, id, class_text, phone, addr, oname, oid, oclass_text, ophone, oaddr):
    sql_message = "UPDATE student_info set name='{}',id='{}',class='{}',phone = '{}',addr='{}'" \
                  "where name = '{}' and id='{}' and class='{}' and phone = '{}' and addr='{}'".format(
        name, id, class_text, phone, addr, oname, oid, oclass_text, ophone, oaddr
    )
    self.cur.execute(sql_message)
    self.db.commit()

UI界面设计

因为初版为Demo版本所以界面,没有过多美化。大家可以自行添加QSS进行修改。先给大家放一下我们两个界面的设计图。
在这里插入图片描述

上面这个就是用控件图绘制的界面。

登陆界面

下面我们来讲一下登陆界面的逻辑,首先登录界面有账号密码登录,人脸识别登录。因为人脸识别涉及到了一些dll动态链接库和一些不同操作系统下的检测,并且算法属于实验室二次开发出来的,就不在这里进行展示。只展示账号密码部分的逻辑。
账号密码的逻辑就是将我们两个输入框的文本进行获取,在获取之后,进行一个数据库的比对,正确时进行界面跳转,错误是弹窗警报。

退出与最小化

我们的界面需要退出或者最小化,而这个又是最好实现的功能。那么我们就看一下这个应该如何去写。

self.close_btn.clicked.connect(lambda: sys.exit())
self.mini_btn.clicked.connect(lambda: self.showMinimized())

我们直接定义两个按钮的信号与槽,然后绑定一个匿名函数,一个用于关闭系统,另一个用于最小化界面。匿名函数看不懂的话,可以写成如下效果。

self.close_btn.clicked.connect(self.close_ui)
self.mini_btn.clicked.connect(self.minisize_ui)

def close_ui(self):
    self.close()
    
def minisize_ui(self):
    self.showMinimized()

数据库匹配登录

我们可以像如下代码一样编写一个登录验证的函数,并且绑定信号与槽,这里我就不写绑定部分了,可以从上一部分学习一下。

    def login(self):
        """
        进行登陆的函数
        Author:SCC
        :return: None
        """
        try:
            id_text = self.id_edit.text()
            pwd_text = self.pwd_edit.text()
        except BaseException as e:
            QMessageBox.about(self, 'Error', '请正确输入账号或密码!')
            return
        if id_text.replace(" ", "") == "" or pwd_text.replace(" ", "") == "":
            QMessageBox.about(self, 'Error', '请正确输入账号或密码!')
        else:
            logger.debug(id_text)
            logger.debug(pwd_text)
            if self.db_handle.login_check(id_text, pwd_text):
                logger.debug("ok")
                self.cap.release()
                self.info_ui = info_ui()
                self.info_ui.show()
                self.close()
            else:
                QMessageBox.about(self, 'Error', '账号或密码错误!')

在这里我们为了防止出现Bug,接入了一个try except。因为无论何时,我们都不能保证用户是一个正常的思维使用我们的项目,他可能不输入,也可能乱输入。所以要进行报错处理。在获取输入之后,我们将数据放入Mysql中进行匹配查找。当查找函数返回为True,代表查找结果成功,存在对应的数据。相对应的登陆成功,出现登录成功的弹窗。但是若返回参数为False,那么代表数据库中未找到匹配的人员信息,那么登录失败,出现帐号或密码错误的弹窗。

界面跳转

界面跳转讲几个步骤。

  1. 首先实例化第一个类
  2. 其次显示这个界面
  3. 最后关闭当前界面
self.info_ui = info_ui()  # 实例化新的界面类
self.info_ui.show()  # 显示新界面
self.close()  # 关闭当前界面

信息管理界面

数据准备

我们在界面跳转后,进入我们的增删改查界面,首先我们要将学生的信息自动显示在tableWidget中,那么对于大量的数据IO,我们不能将查找的算法直接写在进程中,因为这样在大量数据出现时,容易导致进程阻塞,最后界面进入假死状态。所以我们要写到对应的线程中。
线程是一个在后期经常使用的写法,因为一个程序中多个功能同步或异步进行的情况非常多,如果将所有代码放在一个进程中,一定会出现一些卡顿,甚至是异常状态。那么这个时候我们一般将线程放置到我们的代码中,来解决这样的问题。代码如下:

class db_init(QThread):
    def __init__(self, info_ui):
        super(db_init, self).__init__()
        self.info_ui = info_ui
        self.data_list = None
        self.db_handle = db_handle()

    def run(self):
        self.info_ui.tableWidget.clearContents()
        self.data_list = self.db_handle.info_init()
        logger.debug(self.data_list)
        self.info_ui.tableWidget.setRowCount(len(self.data_list))
        for i in range(len(self.data_list)):
            for y in range(5):
                nameitem = QTableWidgetItem(QtWidgets.QTableWidgetItem(self.data_list[i][y]))
                nameitem.setTextAlignment(Qt.AlignCenter)  # 中对齐
                self.info_ui.tableWidget.setItem(i, y, nameitem)

我们的线程会将数据放入到tablewidget中,进行显示。
那么我们来看一下代码,首先我们声明一个类,名为db_init,他绑定了一个QThread,并继承于info_ui界面。那么这个线程在初始化时,只是将我们提前准备的数据库操作的类进行了初始化。而他的run方法中,是一个完整的搜索数据库并显示在界面中的算法。这样我们的线程就创建好了。我们只需要在需要他的时候,进行实例化,并且将它开启。当线程执行结束后,将自动停止,但是也可以手动调用stop方法,停止线程。

主界面逻辑

接下来,我们要写的是主界面的一些逻辑,我们要绑定所有按钮的信号与槽,让他们在被点击后可以有自己的处理事件。首先是增加学生。也就是数据库的增添函数。

def add_stu(self):
    add_name = self.info_name.text()
    add_id = self.info_id.text()
    add_class = self.info_class.text()
    add_phone = self.info_phone.text()
    add_addr = self.info_addr.text()
    try:
        if add_name == "" or add_id == "" or add_class == "" or add_phone == "" or add_addr == "":
            QMessageBox.about(self, 'Error', '请将数据输入完整!')
        else:
            self.db_handle.insert(add_name, add_id, add_class, add_phone, add_addr)
            self.tableWidget.clearContents()
            self.data_list = self.db_handle.info_init()
            self.tableWidget.setRowCount(len(self.data_list))
            for i in range(len(self.data_list)):
                for y in range(5):
                    nameitem = QTableWidgetItem(QtWidgets.QTableWidgetItem(self.data_list[i][y]))
                    nameitem.setTextAlignment(Qt.AlignCenter)  # 中对齐
                    self.tableWidget.setItem(i, y, nameitem)
    except:
        QMessageBox.about(self, 'Error', '数据重复!')

我们要获取学生的姓名班级学号这些输入框,同时大家可以加上一些判断,例如是否输入的是数字,学号是否符合规则等。这样会让程序更加的稳定。我们将几个数据传入数据库中,进行添加,然后加一个刷新数据库和界面的功能,让我们的程序显得更加智能化。
这里我们依然是调用的我们写好的数据库操作类进行的添加。所以我们可以看出,这些函数是非常常用的函数,所以在封装之后,我们可以很轻松的调用他们。
当数据出现重复时,我们可以直接进行弹窗提醒。那么如何监测的重复,我们可以理解为学生的姓名也许可以重复,学生的地址可以重复,但是学号和电话号不会重复,那么在Mysql中我们可以将这两列作为我们区分的条件。
其次就是删除学生,首先我们要获取我们的tablewidget点击事件。在触发点击事件后我们可以获取点击事件的行和列。那么根据行和列的参数,就可以获取到我们想删除的学生信息。

def salegoodsselect(self, row):
    self.name = self.tableWidget.item(row, 0).text()
    self.id = self.tableWidget.item(row, 1).text()
    self.class_text = self.tableWidget.item(row, 2).text()
    self.phone = self.tableWidget.item(row, 3).text()
    self.addr = self.tableWidget.item(row, 4).text()

在获取到数据后,我们将当前学生的各个信息存储至变量中,随后我们就可以执行删除函数。同时我们将参数进行传入,让数据库操作删除指定的一行数据。
但是,同时我们要注意一些小的细节。如果我不进行点击,我就单独进行删除,那么现在我们的存储信息的变量的值就是None。那么如果将None传入Mysql,肯定会导致报错。所以在执行前我们要进行一个判断,在发现结果为None是,我们就显示一个请选择删除人员的弹窗。

def remove_stu(self):
    if self.name is None or self.id is None or self.class_text is None or self.phone is None or self.addr is None:
        QMessageBox.about(self, 'Error', '请先选择删除人员!')
        return
    else:
        self.db_handle.remove(self.name, self.id, self.class_text, self.phone, self.addr)
        self.tableWidget.clearContents()
        self.data_list = self.db_handle.info_init()
        self.tableWidget.setRowCount(len(self.data_list))
        for i in range(len(self.data_list)):
            for y in range(5):
                nameitem = QTableWidgetItem(QtWidgets.QTableWidgetItem(self.data_list[i][y]))
                nameitem.setTextAlignment(Qt.AlignCenter)  # 中对齐
                self.tableWidget.setItem(i, y, nameitem)

删除后,同样自动进行更新函数。
然后是改动的函数,这个函数同样需要依赖获取我们点击tablewidget的事件,同时获取我们的输入框信息,将信息存储至变量中,进行判断,确保已经获取到点击人员的信息。同时我们判断一下输入框中是否有数据。同时可以加上一些输入信息是否合理的判断。在判断全都通过后,进行数据的上传,同时要确定数据没有重复的情况。

def change_stu(self):
    add_name = self.info_name.text()
    add_id = self.info_id.text()
    add_class = self.info_class.text()
    add_phone = self.info_phone.text()
    add_addr = self.info_addr.text()
    if self.name is None or self.id is None or self.class_text is None or self.phone is None or self.addr is None:
        QMessageBox.about(self, 'Error', '请先选择修改人员!')
        return
    try:
        if add_name == "" or add_id == "" or add_class == "" or add_phone == "" or add_addr == "":
            QMessageBox.about(self, 'Error', '请将数据输入完整!')
        else:
            self.db_handle.change(add_name, add_id, add_class, add_phone, add_addr, self.name, self.id, self.class_text, self.phone, self.addr)
            self.tableWidget.clearContents()
            self.data_list = self.db_handle.info_init()
            self.tableWidget.setRowCount(len(self.data_list))
            for i in range(len(self.data_list)):
                for y in range(5):
                    nameitem = QTableWidgetItem(QtWidgets.QTableWidgetItem(self.data_list[i][y]))
                    nameitem.setTextAlignment(Qt.AlignCenter)  # 中对齐
                    self.tableWidget.setItem(i, y, nameitem)
    except BaseException as e:
        logger.debug(e)
        QMessageBox.about(self, 'Error', '数据重复!')

最后是查询函数,我们需要先获取输入框中的文本,确定我们要找到的学生姓名,然后传输到数据库中进行查找,获取他在数据库中的数据,最后将数据进行整合,显示在tablewidget中。

def search_stu(self):
    if self.name_edit.text() != "":
        data_list = self.db_handle.search(self.name_edit.text())
        self.tableWidget.clearContents()
        self.tableWidget.setRowCount(len(data_list))
        for i in range(len(data_list)):
            for y in range(5):
                nameitem = QTableWidgetItem(QtWidgets.QTableWidgetItem(data_list[i][y]))
                nameitem.setTextAlignment(Qt.AlignCenter)  # 中对齐
                self.tableWidget.setItem(i, y, nameitem)
    else:
        self.tableWidget.clearContents()
        self.data_list = self.db_handle.info_init()
        self.tableWidget.setRowCount(len(self.data_list))
        for i in range(len(self.data_list)):
            for y in range(5):
                nameitem = QTableWidgetItem(QtWidgets.QTableWidgetItem(self.data_list[i][y]))
                nameitem.setTextAlignment(Qt.AlignCenter)  # 中对齐
                self.tableWidget.setItem(i, y, nameitem)

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

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

相关文章

机器人中的数值优化|【一】数值优化基础

数值优化基础 凸集 Convex Sets 凸集的定义 令X是线性空间。如果对于X的子集S中的所有x和y,并且在区间 [0,1]中的所有t,点 (1−t)xty(1-t)x ty(1−t)xty也属于S,则S称为凸集。 不失一般性,对于所有的凸集,其线性组…

Zookeeper详解(二)——API 事件监听

Java API znode是zooKeeper集合的核心组件,zookeeper API提供了一小组方法使用zookeeper集合来操纵znode的所有细节。 客户端应该遵循以下步骤,与zookeeper服务器进行清晰和干净的交互。 连接到zookeeper服务器。zookeeper服务器为客户端分配会话ID。…

uniapp 之 接入小程序客服

目录 前言 小程序客服 代码只需要一步 配置也需要一步​​​​​​​ 前言 小程序客服 因老大 看到别人家有在线客服这个功能&#xff0c;就让我也做一个&#xff0c;这个功能很简单 效果图1 代码只需要一步 <button type"default" open-type"con…

MATLAB | 绘图复刻(六) | 分组环形热图

有粉丝问我Ecology Letters, (2021) 24: 1018–1028 Soil carbon persistence governed by plant input and mineral protection at regional and global scales 这篇文章中的Figure 2咋画&#xff0c;原图长这样&#xff1a; 复刻效果&#xff1a; 完整步骤 0 数据定义 按…

node.js创建网站实例1

1.node.js安装 我的电脑环境&#xff1a;win10 网址&#xff1a;https://nodejs.org/en/ 我下载了18.12.1版本 一路next默认安装&#xff0c;安装完成后&#xff0c;运行cmd&#xff0c;查看版本号 会同时安装npm&#xff0c;也可以同时查看版本号 2.创建第一个网站实例hell…

内卷对于2022是一种无奈,也是一种修行

其实我们谁也不知道2023年对于我们普通的开发人员来说会有什么样的试炼&#xff0c;因为2022年身边有太多的人&#xff0c;为了工作&#xff0c;为了生活&#xff0c;为了家庭&#xff0c;为了理想&#xff0c;不得不选择走向别人看似很卷的那条路。 对于我们周围的人来说&…

【Vim】基本操作及命令集详解

概述 Vim 是从 vi 发展出来的一个文本编辑器。vi 内置在Linux系统中&#xff0c;是vim的简化版编辑器&#xff0c;vim则需要进行安装使用。Vim代码补全、编译及错误跳转等方便编程的功能特别丰富&#xff0c;可以实现高效率移动和高效的输入&#xff0c;在程序员中被广泛使用。…

CPT203-Software Engineering(3)

文章目录9. Software Design9.1 Architecture Design9.1.1 Architectural patterns9.2 Component-level Design9.2.1 Component9.2.2 Views of component9.2.3 Component-level design process9.3 User Interface Design9.3.1 Interface Design Process9.3.2 Interface Design …

蓝桥杯Python练习题16-最大最小公倍数

资源限制   内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述   已知一个正整数N&#xff0c;问从1~N中任选出三个数&#xff0c;他们的最小公倍数最大可以为多少。 输入格式   输入一…

三维数学(一)

视频教程&#xff1a;https://www.bilibili.com/video/BV12s411g7gU?p155 向量 一个数字列表&#xff0c;表示各个维度上的有向位移&#xff1b;同时也是一个有大小有方向的物理量&#xff0c;大小及向量的模长&#xff0c;而方向即空间中向量的指向&#xff0c;可以表示物体…

TikTok Shop 越南站点收入已达Lazada 的 80%

让我们一起来看看今日都有哪些新鲜事吧&#xff01;01 TikTok Shop 越南站点收入已达Lazada 的 80% 据越南电商平台数据分析软件Metric.vn 统计&#xff0c;Shopee、Lazada、Tiki 和 Sendo 仍然主导着越南电子商务市场&#xff0c;1-11 月&#xff0c;共销售了 13 亿件产品。其…

简化开发小技巧-Mybatis-Plus的使用和常用操作

目录 简介 快速使用 pom 代码 mapper service 使用 常用操作 简单或操作查询 多条件或查询 更新字段为null 方法一&#xff0c;如果要更新的字段是String类型&#xff0c; 方法二&#xff0c; 使用mybatis-plus的字段注入。 方法三&#xff0c;使用UpdateWrapper…

基于R的Bilibili视频数据建模及分析——预处理篇

基于R的Bilibili视频数据建模及分析——预处理篇 文章目录基于R的Bilibili视频数据建模及分析——预处理篇0、写在前面1、项目介绍1.1 项目背景1.2 数据来源1.3 数据集展示2、数据预处理2.1 删除空数据2.2 增加id字段2.3 处理数值字段3、参考资料0、写在前面 实验环境 Python版…

Stable Diffusion背后原理(Latent Diffusion Models)

前言 2023年第一篇博客&#xff0c;大家新年好呀~ 这次来关注一下Stable Diffusion背后的原理&#xff0c;即 High-Resolution Image Synthesis with Latent Diffusion Models 这篇论文。 之前关注的那些工作只能工作到 256256256 \times 256256256 像素(resize成这个后才输…

设计模式简介

一、设计模式简介 编写软件过程中&#xff0c;程序员面临着来自耦合性&#xff0c;内聚性 以及可维护性&#xff0c;可扩展性&#xff0c;重用性&#xff0c;灵活性等多方面的挑战&#xff0c;设计模式是为了让程序&#xff08;软件&#xff09;&#xff0c;具有更好的&#xf…

04SpringCloudAlibaba服务注册中心—Consul

目录 Consul简介 Consul是什么What is Consul? | Consul by HashiCorp Consul能做什么 Consul下载&#xff1a;Downloads | Consul by HashiCorp Consul使用&#xff1a;Spring Cloud Consul 中文文档 参考手册 中文版 安装并运行Consul 1、官网安装说明&#xff1a;In…

开发板测试手册——系统启动、文件传送操作步骤详解(1)

目 录 前 言 4 1 评估板快速测试 5 1.1 系统启动测试 5 1.2 文件传送测试 11 1.2.1 通过 Linux 系统启动卡 11 1.2.2 通过 OpenSSH 12 1.3 LED 测试 15 1.4 KEY 测试 15 1.5 DDR 读写测试 16 1.6 SD 卡读写测试 17 1.7 eMMC 读写测试 18 前 言 本指导文档适用开发…

2022 CSDN年度报告已出炉

2022年已过&#xff0c;我们迎来了2023年&#xff0c;那么在2022年&#xff0c;你在CSDN平台都做了些什么&#xff0c;收获了什么呢&#xff1f;2022 CSDN年度报告已出炉&#xff0c;来看看你的2022年度报告吧。 点此查看2022年度报告 或扫码查看你的2022 CSDN年度报告哦&…

23种设计模式(三)——策略模式【组件协作】

文章目录意图什么时候使用策略真实世界类比策略模式的实现策略模式的优缺点亦称&#xff1a;Strategy 意图 定义了一组策略&#xff0c;分别在不同类中封装起来&#xff0c;每种策略都可以根据当前场景相互替换&#xff0c;从而使策略的变化可以独立于操作者。比如我们要去某个…

Type-challenges: tuple to object / union

type-challenges/README.zh-CN.md at main type-challenges/type-challenges GitHub https://github.com/TIMPICKLE/type-challenges/blob/main/questions/00010-medium-tuple-to-union/README.zh-CN.md lets do it , mate! 首先概念澄清&#xff1a; JS&#xff1a; con…