Web前端自动化测试Cypress实践总结
本文主要首先主要介绍了什么是自动化测试,接着对常用的自动化测试框架进行了对比分析,最后,介绍了如果将自动化测试框架Cypress运用在项目中。
一、自动化测试概述
为了保障软件质量,并减少重复性的测试工作,自动化测试已经被广泛运用。在开始学习自动化测试之前,我们很有必要先搞清楚这几个问题,**什么是自动化测试?为什么要做自动化测试?哪些项目适合做自动化测试?**
1、什么是自动化测试
****自动化测试是一种测试方法,是指使用特定的软件,去控制测试流程,并比较实际结果与预期结果之间的差异。通过将测试自动化,可以把人对软件的测试行为转化为由机器自动执行测试的行为,从而替代大量的手工测试操作,使得测试可以快速,反复的进行。
关于自动化测试,有一个测试金字塔模型,该模型把测试从下到上分为了单元测试、集成测试和端到端测试(E2E测试/UI界面测试)。越往金字塔底层,测试成本越低,效率也越高,而越往金字塔的顶层,测试成本会逐渐增高,收益也会越低。

-
单元测试
单元测试又称为模块测试,主要针对程序中最小可测试单元(一般指方法,类)的测试,具备投入小、收益产出高的特征,可以较早期地发现代码缺陷,适用于公共函数库的测试。
-
集成测试
集成测试主要包括模块接口测试,子功能模块集成起来的功能模块测试等,目的是为了验证在单元测试的基础上,所有模块集成起来的子系统、子功能是否仍然满足质量目标。
-
端到端测试
端到端测试的主要目的是从软件使用者角度来检验软件的质量,如打开浏览器,进行一系列的操作,验证界面或功能是否符合预期。
不同类型的项目,具有不同的测试场景,因此也需要采用不同的测试类型。对于开发人员来说,单元测试专注于代码底层,可能是一种比较友好的选择。但是站在产品的角度上,也许端到端测试(E2E)是更好的选择,更能保障产品功能符合预期。
讲完了自动化测试类型,我们再来看看测试中常用的测试模式,一般常用的测试模式包括TDD和BDD两种。
-
TDD
TDD(测试驱动开发,Test Driven Development),TDD是指先写测试用例代码,再写功能代码,并且不断的重复上述步骤直到完成开发工作。TDD一般结合单元测试使用,是白盒测试。
-
BDD
BDD(行为驱动开发,Behavior Driven Development),BDD是指先写功能代码,再写测试用例代码,BDD一般结合集成测试或端到端测试使用,是黑盒测试。
当然,是选择TDD还是BDD,也是需要从项目的实际角度出发考虑,再做选择。
2、为什么要做自动化测试
接下来,我们再来聊聊为什么要做自动化测试?在实际的项目开发中,我们常常会遇到以下问题:
-
产品迭代频繁
迭代过程中不可避免的需要新增功能或修改功能,怎么保障新功能的发布不会影响原有功能呢?
-
多人共同参与开发,代码维护难
项目开发过程中多人参与开发,人员变动频繁,开发过程中可能出现误删或误改他人代码逻辑的问题,如何保障代码的质量和可靠性?
-
测试人力不足,回归测试耗时耗力
为了解决上面提到的两个问题,其实方法很简单,就是每次新功能发布后,都对原有功能再进行回归测试。但是又可能遇到测试人力不足的情况,自己手动进行回归测试又耗时耗力,如何才能减少重复性工作,提高效率呢?说到这里,自动化测试就派上用场啦~
那项目引入自动化测试有什么好处呢?自动化测试的好处主要包括了以下几点。
-
验证代码正确性,保障产品质量
可以验证代码或产品功能的正确性,确保每次产品迭代,新功能和原有功能能够正确集成,保证产品质量。
-
提高测试效率
编写的测试用例具有一次编写,多次运行的特点,通过执行测试脚本,可以实现使得测试快速,反复的进行,可以替代大量的重复性手动测试工作,提高效率。
-
起到文档作用
编写的测试用例可以起到文档的作用,有利于项目后续的维护。
3、哪些项目适合引入自动化测试
既然自动化测试有这么多好处,那是不是所有项目都适合引入自动化测试呢?当然不是!自动化测试需要进行测试用例的编写,需要一定的开发成本,我们需要立足于项目本身,再来决定是否适合引入自动化测试。
-
适合引入自动化测试的项目
1)产品周期较长,需要不断进行迭代/重构的项目。2)公共库类的开发维护。
-
不适合引入自动化测试的项目
1)产品周期过短的项目。2)需求变动过于频繁的项目。
二、前端自动化测试框架选择
在明确了我们的项目有必要引入自动化测试之后,就需要选择一款自动化测试框架或工具来帮助我们完成自动化测试工作啦~
1、测试框架对比
下面主要对比了现在常用的Web前端自动化测试框架,如果需要了解更多的框架,可以参考[测试框架选型](https://www.yuque.com/susieyao1314/softwaretesting/ixft8g)
| Jest | Mocha | Selenium/WebDriver | Nightwatch | TestCafe | Cypress | |
|---|---|---|---|---|---|---|
| 支持测试类型 | 单元测试 | 单元测试 | E2E测试 | E2E测试、Node.js单元和集成测试 | E2E测试 | 单元测试、集成测试、E2E测试 |
| 支持语言 | JS、Node | JS、Node | Java、Python、C#、JS | JS、Node | JS、TypeScript | JS |
| 是否支持可视化测试 | 否 | 否 | 否 | 否 | 否 | 是 |
| 是否自带断言库 | 是 | 否 | 否 | 是 | 否 | 是 |
| 是否开箱即用 | 是 | 否 | 否 | 是(安装配置繁琐) | 是 | 是 |
| 录制生成脚本 | / | / | 支持 | 不支持 | 不支持 | 不支持 |
| Github Star | 35.2k | 20.5k | 20.8k | 10.7k | 8.9k | 31.3k |
| 文档 | Jest文档 | Mocha文档 | Selenium文档 | Nightwatch文档 | Testcafe文档 | Cypress文档 |
在上述框架中,由于**Cypress能够同时支持单元测试、集成测试和E2E测试,提供了一套完成的测试解决方案,** 能够满足我们的需求。此外,**Cypress支持JS编写测试用例,支持Jquery元素定位选择器,支持Headless和CI持续集成,运行速度快,上手成本低**,并且**具有可视化调试界面,方便定位问题**。因此决定尝试将Cypress运用到项目中。
三、Cypress实践
接下来,主要介绍如何将Cypress运用在项目中。
1、Cypress安装
在安装Cypress时,可以直接在原有的项目上进行安装,也可以另起一个项目安装。
**
npm install cypress --save-dev
2、Cypress启动
Cypress主要包含以下两种启动方式:1)命令行执行npx cypress open:会在浏览器打开测试用例集的界面,需要手动运行。2)命令行执行npx cypress run:会以无头浏览器模式运行指定的所有测试用例,没有可视化界面,但运行过程中会录制整个测试过程的视频,可在cypress/videos目录下查看。当然,除了直接在命令行运行上述命令,也可以通过配置package.json的scripts字段来定义启动方式。
**
"scripts": {"serve": "vue-cli-service serve","build": "vue-cli-service build","lint": "vue-cli-service lint","cypress:open": "npx cypress open","cypress:run": "npx cypress run"
}
-
可视化界面运行
如果我们需要在可视化界面进行测试,在配置好package.json后,只需要执行npm run cypress:open,就可以启动Cypress,实现可视化调试,如果在启动的过程中遇到以下错误,可以先执行npx cypress install -force,再重新启动Cypress。

如果成功启动Cypress,将会看到以下界面,examples目录下是cypress自带的测试用例演示代码(如果后面不需要,我们可以将这些测试用例删除),点击其中的某个测试用例,将会自动打开浏览器运行测试用例。

如果我们是第一次启动Cypress,会发现在项目根目录下也自动生成了cypress.json配置文件和cypress目录。其中,integration文件夹就是我们用来存放测试用例的目录,可以在cypress.json中自定义这些默认目录的命名。

-
无头浏览器模式运行
如果我们想以无头浏览器模式运行,在配置好package.json后,需要执行npm run cypress:run,Cypress就会以无头浏览器的模式运行指定的所有测试用例。

3、编写测试用例
接下来以验证百度页面的搜索功能为例,演示如何编写测试用例,测试用例可以以.spec.js或.js结尾命名,并放入cypress/integration中。项目目录如下所示,在cypress/integration中创建test.js或test.spec.js测试用例文件。

接着,可以在test.js中开始编写测试用例,**Cypress支持Jquery元素选择器及汉字选择器,并且也支持链式操作,此外,由于Cypress拥有自动等待机制,我们无须在测试中添加wait或sleep,Cypress会自动等待元素至可操作状态时才执行命令或断言**。
**
/// <reference types="cypress" />context('百度页面测试', () => {it('访问百度页面,验证搜索功能', () => {cy.visit('https://www.baidu.com').then(() => {// 1. 输入搜索内容cy.get(".s_ipt").should("exist").type("Cypress自动化测试");// 2. 点击百度一下按钮cy.get(".s_btn").contains("百度一下").should("exist").click();// 3. 验证搜索内容不为空cy.get("#content_left").find("div").then(ele => {expect(ele.length).gt(0);});});});
});
编写完上述代码,我们就可以直接启动Cypress运行啦,当然,我们也可以根据实际需要在cypress.json进行一些配置,下面给出了一些常用的配置,可以在[Cypress文档](https://docs.cypress.io/guides/references/configuration#Timeouts)查看更多配置。

最后,执行npm run cypress:open启动Cypress,启动成功后,我们就可以看到以下界面,点击test.js,就会在浏览器中运行该测试用例。

在测试用例执行的过程中,每一步操作都会被记录下来,可以点击左边的界面对每一步的操作进行回看,可以帮助我们快速定位问题。


4、Cypess文件上传/下载
在实际的使用过程中,我们通常也需要验证文件上传或下载功能,而Cypress也能够满足这些需求。
1)文件上传
首先需要安装cypress-upload-file插件。
**
npm install cypress-upload-file --save-dev
将要上传的文件放到cypress/fixtures中。

编写测试用例代码。
**
/// <reference types="cypress" />context('文件上传', () => {it('验证文件上传功能', () => {// 访问页面,此处步骤省略let file = "file/cover.jpg";cy.get("input[type='file']").attachFile(file);// 执行断言,此处步骤省略});
});
** 2)文件下载**
若我们在运行测试用例的过程中存在文件下载操作,Cypress会自动在cypress目录下创建一个downloads目录,所下载的文件会自动保存在该目录中。

可以在测试用例中读取并解析下载的文件。
**
/// <reference types="cypress" />const path = require("path");context('文件下载', () => {it('验证文件下载功能', () => {// 访问页面,执行下载操作,此处步骤省略const downloadsFolder = Cypress.config("downloadsFolder");const downloadedFilename = path.join(downloadsFolder, "下载文件.xls");// 读取文件cy.readFile(downloadedFilename).then(data => {// 执行断言,此处步骤省略});});
});
5、Cypress测试报告
在执行完自动化测试后,我们通常都希望能够得到一份详细的测试报告,而Cypress也能够提供这个功能。Cypress除了内置的测试报告,也支持用户自定义报告格式。
1)内置的测试报告
Cypress内置的测试报告主要包括了spec格式报告(在控制台窗口输出嵌套分级视图),json格式报告(在控制台窗口输出一个大的json对象)和junit格式报告(输出一个xml文件)。以spec格式报告为例,在启动cypress时加上以下参数即可。

** 2)自定义的测试报告**
常用的自定义测试报告有Mochawesome报告,Mochawesome是与Mocha一起使用的自定义报告程序,并与mochawesome-report-generator结合使用以生成独立的HTML/CSS报告。安装mocha和mochawesome。
**
npm install mocha --save-dev
npm install mochawesome --save-dev
修改启动参数。

运行npm run cypress:run,执行结束后,会在项目根目录下生成mochawesome-report目录。

在浏览器中打开mochawesome.html,就可以查看可视化测试报告。

四、编写可维护的测试脚本
在实际编写测试用例的过程中,随着页面的增多,我们常常会遇到以下这些问题,而这个时候,如何编写可维护的测试脚本,方便后期维护,也显得非常重要,这里也总结了实际开发中的一些经验。

1、测试用例代码结构组织
在编写测试用例时,我们可以一个页面对应一个测试文件,也可以同个功能模块的页面一起对应一个测试文件,并且和平时开发中所采用的代码组织结构类似,将不同的测试文件划分到对应的目录下进行管理,方便后期的维护。

2、页面选择器统一管理
在E2E测试中,我们通常需要获取页面元素,才能够进行点击等操作。而Cypress支持Jquery选择器,我们可以通过元素的class或id定位元素。但是一旦页面的类名或id发生变化,我们不得不修改对应页面的所有测试用例。

在编写测试用例的过程中,我们可以**将页面选择器进行统一管理,实现类名或id选择器和逻辑代码的分离**。对于每个页面或者每个测试文件,可以创建一个对应的xxxControl.js文件,在该文件中,将会**定义一个json对象并且export出来,其中,key为我们自己定义的选择器名称,而value值对应页面中实际的class或id**。

由于目前项目中使用到了iview组件库,因此也提取出了commonControl.js,对iview的选择器进行统一管理。

由于每个页面都采用到了iview组件,因此每个页面或每个测试文件对应的control.js都需要将上面的commonControl.js引入进来。

最后,每个测试文件只需要引入对应的control.js,就可以通过自己定义的key值获取页面真正的class或id。

上面的方法看起来虽然麻烦了点,但是有两个好处,首先,采用自己定义的key值,更容易方便我们记忆,可以减少编写测试用例过程中反复查看页面元素对应的class或id名。其次,当页面的类名或id发生变化时,我们只需要修改页面对应的control.js文件就可以,而不用修改所有的测试用例文件,有利于后续的维护。
3、路径名及接口统一管理
在编写测试用例的过程中,我们通常需要使用cy.visit()去访问某个页面,或者使用cy.request()去调用后台接口以请求数据或创建测试数据,对于页面url或后台接口api,我们也可以放入某个文件中进行统一管理。


4、代码复用
在测试的过程中,我们可能会注意到,不同的页面可能会存在一些相同的功能。比如像目前的项目中,不同的页面都需要对一些操作进行弹框确认或表单输入,而在验证弹框功能是否正确的过程中,我们都需要对弹框执行点击确定按钮、点击取消按钮、点击关闭按钮等操作,而这个时候就可以**采用面向对象编程的方法实现代码的封装和复用**。

定义一个弹框类,并且定义属性和方法。

在测试文件中,需要实例化对象,并调用相关的方法完成某个操作。

当然,有些方法并不是所有的页面都共有的,但是在某个页面或功能中会反复使用到,因此也可以为每个页面或每个测试文件单独封装相应的方法。比如为课程管理页面封装相应的通用方法。


以上就是关于将Cypress运用在项目中的一些总结,而如何将Cypress和CI/CD结合,并且实现自动化测试的定时执行,也是接下来需要继续完成的内容~
相关文章:
Web前端自动化测试Cypress实践总结
本文主要首先主要介绍了什么是自动化测试,接着对常用的自动化测试框架进行了对比分析,最后,介绍了如果将自动化测试框架Cypress运用在项目中。 一、自动化测试概述 为了保障软件质量,并减少重复性的测试工作,自动化测…...
Nacos本地修改编译源码2.2.3
下载Nacos源码 由于github访问速度慢,所以在gitee上下载 git clone https://gitee.com/mirrors/Nacos.git切换2.2.3版本 git checkout 2.2.3或者直接下载2.2.3的源码 本地编译 源码导入idea,然后编译 mvn -Dmaven.test.skiptrue -Drat.skiptrue c…...
邦芒攻略:提升职场核心竞争力的7点建议
如何提高职场的核心竞争力,职场是由职场定位,以及竞争,合作,等各种关系构建起来的一个无形的圈子,职场一直以来竞争激烈,那么,该如何提高职场的核心竞争力? 1、首先要保持学习…...
Android 如何在Android studio中快速创建raw和assets文件夹
一 方案 1. 创建raw文件夹 切成project浏览模式——>找到res文件粘贴要放入raw文件夹下的文件。 当然此时raw文件还没有,直接在右侧输入框中出现的路径~\res后面加上\raw即可。 2. 创建assets文件夹 同理在main文件夹下粘贴要放入assets文件夹的文件࿰…...
功率放大器功能及用途介绍
功率放大器是一种用于将输入信号的电压放大到更高水平的电子设备。它在各个领域中发挥着重要作用,包括音频、通信、测量和控制等应用。下面Aigtek安泰电子将详细介绍功率放大器的功能及其主要用途。 图:ATA-3000系列功率放大器 功率放大器的功能介绍&…...
11.Linux系统:定时任务备份mysql数据库为文件并传输到其他服务器
1. 创建脚本 mysql_dumps.sh内容如下: #!/bin/bash # 查找名称为“mysql_mysql”的容器id CONTAINER_IDdocker ps -a | grep "mysql_mysql" | awk {print $1} | head -n 1 MYSQL_USERNAME"root" MYSQL_PASSWORD"root" FILENAME_SU…...
基于Python的豆瓣电影排行榜,可视化系统
1 简介 基于Python flask 的豆瓣电影数据获取,数据可视化系统,本系统朱亚奥包括了影视系统的爬虫与分析。影视是人们娱乐、放松心情的重要方式之一,因此对影视的分析具有重要的现实意义。通过采用Python编程语言,使用flask框架搭…...
Flink日志采集-ELK可视化实现
一、各组件版本 组件版本Flink1.16.1kafka2.0.0Logstash6.5.4Elasticseach6.3.1Kibana6.3.1 针对按照⽇志⽂件⼤⼩滚动⽣成⽂件的⽅式,可能因为某个错误的问题,需要看好多个⽇志⽂件,还有Flink on Yarn模式提交Flink任务,在任务执…...
iOS NSKeyedUnarchiver归档和读取
使用NSKeyedUnarchiver归档数据到本地,很多时候保存的并不是基础数据类型,更多是自己定义的Model。有时会碰到归档或者读取的内容跟自己保存的数据类型不匹配。 现在按照思路一步一步解决: 1.先保存文件 保存的数据的类型 #import <Fou…...
算法通关村第五关|青铜|基于链表实现队列
基于链表实现队列 public class LinkQueue {// front的next指向首部结点private Node front;// rear记录尾部结点private Node rear;private int size;public LinkQueue() {this.front new Node(0);this.rear new Node(0);}// 入队public void push(int value) {Node newNod…...
【Vue】使用v-model实现控制子组件显隐
v-model 可以实现双向绑定的效果,允许父组件控制子组件的显示/隐藏,同时允许子组件自己控制自身的显示/隐藏。以下是如何使用 v-model 实现这个需求: 在父组件中,你可以使用 v-model 来双向绑定一个变量,这个变量用于…...
一篇博客读懂顺序表 —— Sequence-List
目录 一、顺序表的初始定义 1.1新建头文件和源文件 1.2 SeqList.h 中的准备工作 二、顺序表的初始化与销毁 三、首尾插入元素 四、首尾删除元素 五、中间插入元素 六、中间删除元素 七、查找指定元素下标 八、源代码 一、顺序表的初始定义 1.1新建头文件和源文件 当我…...
OceanBase:02-单机部署(生产环境)
目录 一、部署规划 二、配置要求 三、部署前配置 1.配置 limits.conf 2.配置 sysctl.conf 3.关闭防火墙 4.关闭 SELinux 5.创建数据目录,修改文件所有者信息 6.设置无密码 SSH 登录 7.安装jdk 四、解压执行安装 五、OBD命令行部署 1.修改配置文件(all-c…...
【嵌入式 C 常用算法 2 -- 变量值交换函数异或方式实现】
文章目录 变量值交换函数异或方式实现 变量值交换函数异或方式实现 在C语言中,可以使用异或运算符(^)来进行两个数的交换,而不需要使用额外的临时变量。这种交换方式的基础是异或运算的以下性质: 任何数和 0 做异或运…...
Hadoop HDFS(分布式文件系统)
一、Hadoop HDFS(分布式文件系统) 为什么要分布式存储数据 假设一个文件有100tb,我们就把文件划分为多个部分,放入到多个服务器 靠数量取胜,多台服务器组合,才能Hold住 数据量太大,单机存储能力有上限,需要…...
力扣1.两数之和
原题链接:1.两数之和 根据题意可以得出 需要找出数组nums内 有两个元素相加等于target的两个整数,并且返回这两个证书的下标。并且数组内有重复元素,但是返回的答案不能有重复元素出现 要记住的就是,需要判断元素是否出现过&…...
JTA分布式事务管理器
XA协议:是一种标准协议,允许事务管理器协调多个资源管理器,确保在分布式事务中的一致性和原子性。 JTA:是JavaEE规范中的一种,用于管理分布式事务的 API,提供了事务的控制和协调机制 Atomikos理解成JTA的实现 XA是JTA的基础(JT…...
晨控CK-GW08系列网关控制器与CODESYS软件MODBUSTCP通讯手册
晨控CK-GW08系列是一款支持标准工业通讯协议ModbusTCP的网关控制器,方便用户集成到PLC等控制系统中。系统还集成了8路读写接口,用户可通过通信接口使用Modbus TCP协议对8路读写接口所连接的读卡器进行相对独立的读写操作。 晨控CK-GW08系列网关控制器适用于本公司多…...
读书笔记——labuladong算法笔记
读书笔记——labuladong算法笔记 序言计算机算法世界观计算机算法方法论二叉树遍历广度遍历BFS二叉树的前中后序遍历回溯算法动态规划算法二分搜索算法 其他算法滑动窗口双指针Union-Find算法 序言 labuladong算法笔记是一本讲解算法题求解技巧的书。本次读书笔记为2023年8月第…...
Linux中阶教程:bash shell基础
文章目录 输入输出赋值和计算条件判断函数for 循环数组及其遍历其他控制语句 输入输出 echo表示打印字符串;read表示获取用户输入;$用于引用变量。 # test1.sh bash中用#进行单行注释 echo "input your name:" read user_name echo "h…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
