第四章
优学题库项目开发2
优就业.JAVA教研室
学习目标
- 了解启动开源管理前端框架
- 掌握开发配置题目分类服务及前端维护管理功能
- 掌握开发配置题目维护后端服务及前端维护管理功能
- 掌握完成题目导入、导出功能开发
- 掌握Echarts图表入门
- 掌握统计分析功能开发
1、启动人人开源管理前端
1.1、确认本地已经安装好Node.js
输入指令,确认:
node -v
node.js安装教程
https://www.runoob.com/nodejs/nodejs-install-setup.html
1.2、安装前端开发工具VSCode
下载前端代码:
renren-fast-vue 管理后台前端 https://gitee.com/hk109/renren-fast-vue.git
1.2.1 打开人人开源管理前端代码所在目录
1.2.2 选中人人开源前端代码的目录,
1.2.3打开目录效果。
1.2.4保存工作区:
1.2.5选中工作区保存位置
1.2.6 最终保存为工作区
1.2.7、禁用eslint插件
1.3、启动前端项目
1.3.1 进入VSCODE终端
终端界面
npm -v
1.3.2 配置cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
1.3.3 安装node_modules依赖包
cnpm install
注意安装的饿时候,需要修改package.json,里面的node-sass的依赖版本号:^4.14.1
1.3.4 打包运行前端项目
npm run dev
1.3.5 浏览器访问
注意:renren-fast项目要先启动
浏览后台:http://localhost:8001
登录后台
账号:admin
密码:admin
1.4、配置前端使用API网关-请求后端API接口调用地址
修改配置文件对接后端api地址:
文件:renren-fast-vue\static\config\index.js
api接口请求地址替换为题目微服务的地址
window.SITE_CONFIG['baseUrl'] = 'http://localhost:8080/renren-fast';
替换为
window.SITE_CONFIG['baseUrl'] = 'http://localhost:8888/api'; // 网关地址
配置cloud-gateway转发到renren-fast的路由:
spring:
cloud:
gateway:
routes:
- id: renrenfast-route # 人人后台服务路由
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
刷新登录页面,可以正常获取验证码,请求路径为网关地址 + /api/captcha.jpg?uuid=xxxxx
http://localhost:8888/api/captcha.jpg?uuid=84d36089-07ae-4201-85c0-8217b032f23e
1.5、登录跨域问题
点击登录,控制台报错,登录失败:
Access to XMLHttpRequest at 'http://localhost:8888/api/sys/login' from origin 'http://localhost:8001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' head is present on the requested resource
登录页面url:http://localhost:8001,点击登录访问的请求url:http://localhost:8888/api/sys/login,两个url的端口号不一样,产生了跨域问题。
什么是跨域?
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
比如,站点 http://domain-a.com 的某 HTML 页面通过 的 src 请求 http://domain-b.com/image.jpg。网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源。
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。
1.6、解决跨域问题
添加响应头,配置当次请求允许跨域
Access-Control-Allow-Origin:支持哪些来源的请求跨域 Access-Control-Allow-Methods:支持哪些方法跨域 Access-Control-Allow-Credentials:跨域请求默认不包含cookie,设置为true可以包含cookie Access-Control-Expose-Headers:跨域请求暴露的字段CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。 Access-Control-Max-Age:表明该响应的有效时间为多少秒。在有效时间内,浏览器无 须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果 该首部字段的值超过了最大有效时间,将不会生效。
1.6.1 添加跨域配置
在cloud-gateway模块添加解决跨域的配置文件GatewayCorsConfiguration
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class GatewayCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 配置跨域
corsConfiguration.addAllowedHeader("*"); // 允许所有请求头跨域
corsConfiguration.addAllowedMethod("*"); // 允许所有请求方法跨域
corsConfiguration.addAllowedOrigin("*"); // 允许所有请求来源跨域
corsConfiguration.setAllowCredentials(true); //允许携带cookie跨域,否则跨域请求会丢失cookie信息
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}
也可以采用配置方式,修改application.yml:
spring:
cloud:
gateway:
globalcors: #跨域配置
cors-configurations:
'[/**]':
allowCredentials: true #允许跨域读取cookie
allowedOrigins: "*" #允许发出跨域请求主机域名 * 任意主机
allowedMethods: "*" #请求方法
allowedHeaders: "*" #允许发出跨域的请求头
1.6.2、注释renren-fast跨域配置
注释renren-fast里面的跨域配置
文件路径:src/main/java/io/renren/config/CorsConfig.java
重启各个服务及网关,测试即可成功登录。
1.6.3 前后端联调登录
登录成功
查看后端服务日志
说明前端登录请求发送到了后端服务,并验证了用户名和密码是否正确。
2、开发配置题目分类服务前端维护管理功能
2.1、添加题目管理目录(一级菜单)
选择管理界面–系统管理–菜单管理,点击 新增按钮
刷新页面,就可以看到题目中心菜单
2.2、添加题目类型维护菜单(二级菜单)
选择管理界面–系统管理–菜单管理,点击 新增按钮
选择类型:菜单
刷新,可以看到菜单
可以看到数据库uxeu_admin的表sys_menu中新增了两条记录,分别对应两个菜单
点击类型维护菜单,打开了链接:http://localhost:8001/#/question-type,页面显示空白页面.
2.3、自动生成类型维护前端页面
用renren-generator自动生成前端代码
注意配置数据库连接文件application.yml,指向数据库 uxue_qms
修改模板生成属性配置文件generator.properties
配置模块名和包名:
重启服务,打开生成页面选择表,点击生成
2.4、拷贝前端代码到 前端工程renren-fast-vue
拷贝\main\resources\src\views\modules\question目录到前端目录 renren-fast-vue\src\views\modules
2.5、测试类型维护
点击类型维护菜单,可以看到请求报404
http://localhost:8001/#/question-type
因为页面的请求都访问到renren-fast服务了,所以要修改为访问题目微服务。
修改colud-gateway网关配置,配置转发题目微服务:
(注意:qms-route的路由配置一定要配置在renrenfast-route之前,拥有加载顺序。)
spring:
cloud:
nacos:
discovery:
server-addr: http://localhost:8848
gateway:
routes:
- id: qms-route # 题目微服务路由
uri: lb://u-question
predicates:
- Path=/api/question/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
- id: renrenfast-route # 人人后台服务路由
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
重启网关服务,再次刷新访问地址:http://localhost:8001/#/question-type
即可正常访问题目类型列表页:
- 数据库插入3条测试数据
- 测试查询列表,可以看到有三条记录查询出来了
点击 修改:
点击 确定 即可完成修改。
点击删除即可删除记录
弹出删除确认提示:
确定即可删除对应记录
2.6、开启类型维护新增、批量删除权限
打开前端工程renren-fast-vue找到配置文件src\utils\index.js
/**
* 是否有权限
* @param {*} key
*/
export function isAuth (key) {
// return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
return true
}
暂时不判断权限,全部返回true
再次访问类型维护列表页,即可看到新增按钮
去除新增创建时间、更新时间必须输入限制:
修改文件src/views/modules/question/type-add-or-update.vue
dataRule: {
type: [
{ required: true, message: '类型名称不能为空', trigger: 'blur' }
],
comments: [
{ required: true, message: '备注不能为空', trigger: 'blur' }
],
logoUrl: [
{ required: true, message: '类型logo路径不能为空', trigger: 'blur' }
],
delFlag: [
{ required: true, message: '删除标记(0-正常,1-删除)不能为空', trigger: 'blur' }
],
createTime: [
{ required: false, message: '创建时间不能为空', trigger: 'blur' }
],
updateTime: [
{ required: false, message: '更新时间不能为空', trigger: 'blur' }
]
}
把createTime、updateTime required: true改成 required: false
点击新增按钮,即可弹出新增界面
批量删除测试
2.7、配置逻辑删除
前面进行的删除都是物理删除,页可以进行逻辑删除,就是不直接把数据记录从数据库删除,而是采用设置删除标记来标识数据记录被删除。
2.7.1、所有表字段添加del_flag字段
字段属性
del_flag tinyint(1) DEFAULT 0 COMMENT '删除标记(0-正常,1-删除)',
2.7.2、修改MyBatisPlus配置逻辑删除
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
logic-delete-field: delFlag #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
2.7.3、配置springboot打印mybatis执行sql
修改配置文件application.yml
logging:
level:
com.offcn.question: debug
再次测试访问列表和执行删除,可以看到控制台打印sql指令
查询指令:
SELECT id,type,comments,logo_url,del_flag,create_time,update_time FROM qms_type WHERE del_flag=0
删除指令:
UPDATE qms_type SET del_flag=1 WHERE id IN ( 1 ) AND del_flag=0
查看数据库记录:
2.8、模糊查询类型功能
修改实现类TypeServiceImpl的方法queryPage
@Override
public PageUtils queryPage(Map<String, Object> params) {
//1、获取查询关键字
String key= (String) params.get("key");
//2、创建查询条件对象
QueryWrapper<TypeEntity> queryWrapper = new QueryWrapper<>();
//3、设置查询条件
if(!StringUtils.isEmpty(key)){
queryWrapper.eq("id",key).or().like("type",key);
}
IPage<TypeEntity> page = this.page(
new Query<TypeEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
设置id或者类型名称模糊查询的条件
前端访问测试:
2.9、配置分页
修改u-question配置分页插件配置类:
@Configuration
public class MyBatisConfig {
//引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}
添加分页插件后的显示:
3、开发配置题目前端维护管理功能
3.1、添加题目维护菜单(二级菜单)
刷新后台管理页面,就可以看到题目维护菜单
3.2、拷贝代码生成器生成的题目维护的前端源码
拷贝题目维护源码到前端工程renren-fast-vue
3.3、测试题目维护
访问题目维护菜单,可以正常浏览到题目数据。
3.4、新增题目–处理题目类型下拉菜单
打开新增题目,发现里面涉及到题目类型,需要下拉选择已经存在的题目类型。
3.4.1.获取根据分类获取题库列表数据接口
3.4.1.1、编写获取题库分类接口及实现类
接口:TypeService 增加获取全部分类方法
public interface TypeService extends IService<TypeEntity> {
//获取全部分类
List<TypeEntity> findAll();
}
实现类:TypeServiceImpl
@Service("typeService")
public class TypeServiceImpl extends ServiceImpl<TypeDao, TypeEntity> implements TypeService {
@Override
public List<TypeEntity> findAll() {
return this.list();
}
}
3.4.1.2、编写获取题库分类控制器
修改TypeController,增加获取全部分类接口
@RestController
@RequestMapping("question/type")
public class TypeController {
@Autowired
private TypeService typeService;
//获取全部分类
@GetMapping("findall")
public R findAll(){
List<TypeEntity> all = typeService.findAll();
return R.ok().put("data",all);
}
}
3.4.2、前端代码编写
修改question-add-or-update.vue
修改数据结构,定义题目类型下拉菜单数据
export default {
data () {
return {
options: [],
修改方法,当用户点击打开新增、或者修改页面的时候,调用后台接口获取全部题目类型数据
methods: {
init (id) {
this.dataForm.id = id || 0
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
//向服务器端发出一个请求,获取全部题目分类数据
this.$http({
url: this.$http.adornUrl(`/question/type/findall`),
method: 'get'
}).then(({data}) => {
this.options = data.data
})
<el-form-item label="题目类型" prop="type">
<el-select v-model="dataForm.type" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.id"
:label="item.type"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
点击新增、或者修改,可以看到题目类型下拉菜单成功显示
3.5、新增题目–处理是否显示、是否删除
修改页面question-add-or-update.vue
<el-form-item label="是否显示" prop="enable">
<el-radio v-model="dataForm.enable" label="0">不显示</el-radio>
<el-radio v-model="dataForm.enable" label="1">显示</el-radio>
</el-form-item>
<el-form-item label="删除标记" prop="delFlag">
<el-radio v-model="dataForm.delFlag" label="0">不删除</el-radio>
<el-radio v-model="dataForm.delFlag" label="1" >删除</el-radio>
</el-form-item>
修改数据结构,定义是否显示初始化值
修改从后台接口获取数据赋值方法,把后台响应数据转换为字符串
关键点:data.question.enable + ''
data.question.delFlag + ''
点击新增或者修改,可以看到是否显示、是否删除成功已单选框显示
3.6、模糊查询题目
修改实现类QuestionServiceImpl的方法queryPage
@Override
public PageUtils queryPage(Map<String, Object> params) {
//1、获取查询关键字
String key= (String) params.get("key");
//2、创建查询条件对象
QueryWrapper<QuestionEntity> queryWrapper = new QueryWrapper<>();
//3、设置查询条件
if(!StringUtils.isEmpty(key)){
queryWrapper.eq("id",key).or().like("title",key);
}
IPage<QuestionEntity> page = this.page(
new Query<QuestionEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
测试输入id或者标题包含的关键字查询
3.7、快速设置是否显示
想要将是否显示改为快速开关
修改页面question.vue添加更新状态方法
methods: {
// 更新题目是否显示
updateQuestionStatus (data) {
console.log(data)
let {id, enable} = data
this.$http({
url: this.$http.adornUrl('/question/question/update'),
method: 'post',
data: this.$http.adornData({id, enable}, false)
}).then(({ data }) => {
this.$message({
type:'success',
message: '状态更新成功'
})
});
},
修改页面question.vue
<el-table-column
prop="enable"
header-align="center"
align="center"
label="是否显示">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enable"
:active-value=1
:inactive-value=0
active-color="#13ce66"
inactive-color="#ff4949"
@change="updateQuestionStatus(scope.row)">
</el-switch>
</template>
</el-table-column>
测试页面效果
3.8、校验排序不能是小数或者小于0的数字
修改前端页面question-add-or-update.vue
var validateOrder = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入排序'));
} else {
if (!(value%1 === 0)||(value<0)) {
callback(new Error('排序应该是整数且大于0'));
}
callback();
}
};
return {
…………略
dataRule: {
…………
displayOrder: [
{ validator: validateOrder, trigger: 'blur' }
],
…………
},
}
测试
只有输入整数才能验证通过。
3.9、修改题目答案为多行文本框
<el-form-item label="题目解答" prop="answer">
<el-input type="textarea" :rows="2" v-model="dataForm.answer" placeholder="题目解答"></el-input>
</el-form-item>
效果:
4、完成题目导入、导出功能
4.1、JAVA操作excel类库POI
12.1.1 什么是POI?
Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。
· HSSF - 提供读写Microsoft Excel格式档案的功能。(.xls)
· XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。(.xlsx)
· HWPF - 提供读写Microsoft Word格式档案的功能。
· HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
· HDGF - 提供读写Microsoft Visio格式档案的功能。
12.1.2 POI官网
官网可以找到文档和每个版本的下载地址
4.2、POI入门demo
4.2.1.创建一个普通的maven项目
项目名:excel-poi
4.2.2.pom中引入xml相关依赖
<dependencies>
<!--xls-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<!--xlsx-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
<!--test-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
4.2.3.java写入excel
2003版写文件
package com.excel.poi;
public class ExcelTest {
@Test
public void testWrite03() throws IOException {
// 创建新的Excel 工作簿
Workbook workbook = new HSSFWorkbook();
// 在Excel工作簿中建一工作表,其名为缺省值 Sheet0
//Sheet sheet = workbook.createSheet();
// 如要新建一名为"会员登录统计"的工作表,其语句为:
Sheet sheet = workbook.createSheet("会员登录统计");
// 创建行(row 1)
Row row1 = sheet.createRow(0);
// 创建单元格(col 1-1)
Cell cell11 = row1.createCell(0);
cell11.setCellValue("今日人数");
// 创建单元格(col 1-2)
Cell cell12 = row1.createCell(1);
cell12.setCellValue(666);
// 创建行(row 2)
Row row2 = sheet.createRow(1);
// 创建单元格(col 2-1)
Cell cell21 = row2.createCell(0);
cell21.setCellValue("统计时间");
//创建单元格(第三列)
Cell cell22 = row2.createCell(1);
String dateTime = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
cell22.setCellValue(dateTime);
// 新建一输出文件流(注意:要先创建文件夹)
FileOutputStream out = new FileOutputStream("d:/excel-poi/test-write03.xls");
// 把相应的Excel 工作簿存盘
workbook.write(out);
// 操作结束,关闭文件
out.close();
System.out.println("文件生成成功");
}
}
2007版写文件
@Test
public void testWrite07() throws IOException {
// 创建新的Excel 工作簿
Workbook workbook = new XSSFWorkbook();
// 在Excel工作簿中建一工作表,其名为缺省值 Sheet0
//Sheet sheet = workbook.createSheet();
// 如要新建一名为"会员登录统计"的工作表,其语句为:
Sheet sheet = workbook.createSheet("会员登录统计");
// 创建行(row 1)
Row row1 = sheet.createRow(0);
// 创建单元格(col 1-1)
Cell cell11 = row1.createCell(0);
cell11.setCellValue("今日人数");
// 创建单元格(col 1-2)
Cell cell12 = row1.createCell(1);
cell12.setCellValue(666);
// 创建行(row 2)
Row row2 = sheet.createRow(1);
// 创建单元格(col 2-1)
Cell cell21 = row2.createCell(0);
cell21.setCellValue("统计时间");
//创建单元格(第三列)
Cell cell22 = row2.createCell(1);
String dateTime = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
cell22.setCellValue(dateTime);
// 新建一输出文件流(注意:要先创建文件夹)
FileOutputStream out = new FileOutputStream("d:/excel-poi/test-write07.xlsx");
// 把相应的Excel 工作簿存盘
workbook.write(out);
// 操作结束,关闭文件
out.close();
System.out.println("文件生成成功");
}
注意:如果针对不同版本的Excel使用了不适合的类库,则会报告异常 org.apache.poi.POIXMLException: org.apache.poi.openxml4j.exceptions.InvalidFormatException: Package should contain a content type part [M1.13]
4.2.4.Java读取excel
2003版读文件
package com.excel.poi;
public class ExcelReadTest {
@Test
public void testRead03() throws Exception{
InputStream is = new FileInputStream("d:/excel-poi/test-write03.xls");
Workbook workbook = new HSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);
// 读取第一行第一列
Row row = sheet.getRow(0);
Cell cell = row.getCell(0);
// 输出单元内容
System.out.println(cell.getStringCellValue());
// 操作结束,关闭文件
is.close();
}
}
2007版读文件
@Test
public void testRead07() throws Exception{
InputStream is = new FileInputStream("d:/excel-poi/test-write07.xlsx");
Workbook workbook = new XSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);
// 读取第一行第一列
Row row = sheet.getRow(0);
Cell cell = row.getCell(0);
// 输出单元内容
System.out.println(cell.getStringCellValue());
// 操作结束,关闭文件
is.close();
}
4.3、JAVA后端代码编写
4.3.1修改项目u-question的接口QuestionService增加导入、导出接口定义
public interface QuestionService extends IService<QuestionEntity> {
PageUtils queryPage(Map<String, Object> params);
//导入
public Map importExcel(MultipartFile file);
//导出
public Workbook exportExcel();
}
4.3.2 修改实现类QuestionServiceImpl实现导入、导出方法
@Override
public Map importExcel(MultipartFile file) {
Map result=new HashMap();
//获取上传文件名称
String filename = file.getOriginalFilename();
String extName = filename.substring(filename.lastIndexOf(".")+1);
//2、判断文件扩展名是否等于xlsx,xls
if(!extName.equals("xls") &&!extName.equals("xlsx") ){
result.put("result",false);
result.put("msg","文件扩展名不正确,导入失败");
result.put("num",0);
}
try {
InputStream inputStream = file.getInputStream();
//初始化创建工作簿对象
Workbook workbook = WorkbookFactory.create(inputStream);
//获取第一张工作表
Sheet sheet = workbook.getSheetAt(0);
//获取工作表里面有数据行数
int rows = sheet.getPhysicalNumberOfRows();
//循环表格行
for (int i = 1; i <rows ; i++) {
QuestionEntity questionEntity = new QuestionEntity();
//获取当前行
Row row = sheet.getRow(i);
//获取第一个单元格的内容 标题
String title= row.getCell(0).getStringCellValue();
//设置到题目对象标题属性
questionEntity.setTitle(title);
System.out.println("title:"+title);
//获取第二个单元格的内容 答案
String answer = row.getCell(1).getStringCellValue();
questionEntity.setAnswer(answer);
System.out.println("answer:"+answer);
//获取第三个单元格的内容 题目难度等级
double level = row.getCell(2).getNumericCellValue();
questionEntity.setLevel((int)level);
//获取第四个单元格的内容 paix
questionEntity.setDisplayOrder((int)row.getCell(3).getNumericCellValue());
//获取副标题
questionEntity.setSubTitle(row.getCell(4).getStringCellValue());
//题目类型
questionEntity.setType((long)row.getCell(5).getNumericCellValue());
//是否显示
questionEntity.setEnable((int)row.getCell(6).getNumericCellValue());
//调用数据库保存方法,保存题目数据到书籍
this.save(questionEntity);
}
result.put("result",true);
result.put("msg","导入成功");
System.out.println("导入成功总条数:"+list.size());
} catch (Exception e) {
e.printStackTrace();
result.put("result",false);
result.put("msg","文件导入失败,"+e.getMessage());
result.put("num",0);
}
return result;
}
@Override
public Workbook exportExcel() {
// 创建新的Excel 工作簿
Workbook workbook = new HSSFWorkbook();
// 在Excel工作簿中建一工作表,其名为缺省值 Sheet0
//Sheet sheet = workbook.createSheet();
// 创建工作表
Sheet sheet = workbook.createSheet("题库");
Row row = sheet.createRow(0);
row.createCell(0).setCellValue("题目标题");
row.createCell(1).setCellValue("题目解答");
row.createCell(2).setCellValue("题目难度等级");
row.createCell(3).setCellValue("排序");
row.createCell(4).setCellValue("副标题");
row.createCell(5).setCellValue("题目类型");
row.createCell(6).setCellValue("是否显示");
List<QuestionEntity> list = this.list();
for (int i = 0; i < list.size(); i++) {
Row row2 = sheet.createRow(i + 1);
//创建表格
row2.createCell(0).setCellValue(list.get(i).getTitle());
row2.createCell(1).setCellValue(list.get(i).getAnswer());
row2.createCell(2).setCellValue(list.get(i).getLevel());
row2.createCell(3).setCellValue(list.get(i).getDisplayOrder());
row2.createCell(4).setCellValue(list.get(i).getSubTitle());
row2.createCell(5).setCellValue(list.get(i).getType());
row2.createCell(6).setCellValue(list.get(i).getEnable());
}
return workbook;
}
4.3.3 修改控制器QuestionController实现导入、导出方法
//题目上传导入
@PostMapping("/upload")
public R upload(MultipartFile file){
Map result = questionService.importExcel(file);
return R.ok().put("result",result.get("result")).put("msg",result.get("msg")).put("num",result.get("num"));
}
//导出excel
@GetMapping("exportExcel")
public void export(String tableName, HttpServletResponse response){
System.out.println("导出excele");
Workbook workbook = questionService.exportExcel();
if (workbook != null) {
String fileName = "uxue_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".xlsx";
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType("application/octet-stream;charset=UTF-8");
OutputStream outputStream;
try {
outputStream = response.getOutputStream();
workbook.write(outputStream);
outputStream.flush();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
response.getWriter().print("error");
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.4、前端代码编写
4.4.1 创建导入弹窗口视图页面
在renren-fast-vue前端工程/src/views/modules/question/ 目录下新增视图文件 question-import.vue
<template>
<el-dialog
:title="'导入题库'"
:close-on-click-modal="false"
:visible.sync="visible" >
<el-row>
<div class="el-form-item__content">
<el-upload ref="upload"
accept=".xls,.xlsx"
action="#"
:auto-upload="false"
:multiple="false"
:file-list="fileList"
:http-request="uploadHttpRequest"
>
<el-button size="small" type="primary">选择文件</el-button>
<div slot="tip" class="el-upload__tip">一次只能上传一个xls/xlsx文件,且不超过10M</div>
</el-upload>
<el-button type="primary" size="small" @click="submitUpload">上 传</el-button>
<a type="primary" href="/test.xls" size="small" >下载导入模板</a>
</div>
</el-row>
</el-dialog>
</template>
<script>
export default {
data () {
return {
visible: false,
fileList: [],
}
},
methods: {
init () {
this.visible = true
},
// param是默认参数,可以取得file文件信息,具体信息如下图
uploadHttpRequest(param) {
const formData = new FormData() //FormData对象,添加参数只能通过append('key', value)的形式添加
formData.append('file', param.file) //添加文件对象
formData.append('uploadType', this.rulesType)
this.$http({
url: this.$http.adornUrl(`/question/question/upload`),
method: 'post',
data: formData,
contentType: 'form'
}).then( res => {
const { data: { code, mark, num, msg } } = res
if(code === 0) {
this.fileList = []
this.visible = false
// param.onSuccess() // 上传成功的文件显示绿色的对勾
// this.uploadMark = mark
//this.$message.info('上传文件成功,导入条数:'+num)
this.$message({
message: '上传文件成功,导入条数:'+num,
type: 'success',
duration: 1000,
onClose: () => {
this.visible = false
this.$emit('refreshDataList')
}
})
}else {
this.$message.error(msg)
}
})
.catch( err => {
console.log('失败', err)
param.onError() //上传失败的文件会从文件列表中删除
})
},
// 点击上传:手动上传到服务器,此时会触发组件的http-request
submitUpload() {
this.$refs.upload.submit()
}
}
}
</script>
4.4.2 修改题目维护页面question.vue
引入导入页面:
import ImportExcel from './question-import'
注册导入窗口:
<!-- 导入窗口-->
<ImportExcel v-if="importVisible" ref="importExcel" @refreshDataList="getDataList"></ImportExcel>
声明允许显示导入弹窗变量
data () {
return {
//是否允许显示导入窗口
importVisible: false
}
},
把导入页面注册到当前页面组件:
components: {
AddOrUpdate,
ImportExcel
},
添加导入导出点击触发方法:
//导入
importExcelHandle () {
this.importVisible = true
this.$nextTick(() => {
this.$refs.importExcel.init()
})
},
//导出
exportExcelHandle () {
var dom=document.getElementById('ifile')
dom.src= this.$http.adornUrl('/question/question/exportExcel')
},
在页面模板区域,添加 导入、导出按钮:
<el-button type="primary" @click="importExcelHandle()">导入</el-button>
<el-button type="success" @click="exportExcelHandle()">导出</el-button>
<iframe id="ifile" style="display:none"></iframe>
导入、导出视图显示效果:
点击 导入 按钮,弹出导入弹窗:
下载导入模板,按照模板,填写数据,选中文件,点击上传,即可导入
导入模板样式:
导入成功,提示导入条数,关闭导入弹窗。
5、Echarts图表入门
5.1.Echarts简介
ECahrs是百度的一个项目,用于图表展示,提供了常规的折线图、柱状图、散点图、饼图、K线图,用于统计的盒形图,用于地理数据可视化的地图、热力图、线图,用于关系数据可视化的关系图、treemap、旭日图,多维数据可视化的平行坐标,还有用于 BI 的漏斗图,仪表盘,并且支持图与图之间的混搭。 官方网站:https://echarts.apache.org/zh/index.html
5.2.基本使用
入门参考:官网->文档->教程->5分钟上手ECharts
5.2.1、柱状图创建
(1)创建html页面:柱图.html
(2)引入ECharts
<!-- 引入 ECharts 文件 -->
<script src="echarts.min.js"></script>
(3)定义图表区域
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
(4)渲染图表
<script>
var myChart = echarts.init(document.getElementById('main'));
var option = {
title: {
text: 'Echarts 柱状图入门demo'
},
tootip: {},
legend: {
data: ['销量']
},
xAxis: {
data: ["衬衫","羊毛衫","羽绒服","裤子","高跟鞋"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [1,6,7,4,15]
}]
};
myChart.setOption(option);
</script>
测试效果:
5.2.2、折线图创建
<script>
var myChart = echarts.init(document.getElementById('main'));
var option = {
title: {
text: 'Echarts 折线图入门demo'
},
tootip: {},
legend: {
data: ['销量']
},
xAxis: {
type: 'category',
data: ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]
},
yAxis: {
type: 'value'
},
series: [{
name: '销量',
type: 'line',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}]
};
myChart.setOption(option);
</script>
6、统计分析
6.1.题库分类数量统计分析
6.1.1 编写java后端代码
打开java模块u-question ,编辑接口QuestionService新增统计分类题目数量方法
public interface QuestionService extends IService<QuestionEntity> {
public List<Map<String, Object>> countTypeQuestion();
}
编辑接口实现类QuestionServiceImpl,编写统计分类题目数量实现方法
@Service("questionService")
public class QuestionServiceImpl extends ServiceImpl<QuestionDao, QuestionEntity> implements QuestionService {
@Autowired
private QuestionDao questionDao;
@Override
public List<Map<String, Object>> countTypeQuestion() {
QueryWrapper<QuestionEntity> queryWrapper = new QueryWrapper<QuestionEntity>().select("TYPE,COUNT(TYPE) AS num").groupBy("type");
List<Map<String, Object>> mapList = questionDao.selectMaps(queryWrapper);
return mapList;
}
}
编辑QuestionController,新增统计分类题目数量方法
@RestController
@RequestMapping("question/question")
public class QuestionController {
@Autowired
private QuestionService questionService;
@Autowired
private TypeService typeService;
//获取按照题库分类的统计数据
@RequestMapping("countTypeQuestion")
public R countTypeQuestion(){
List<Map<String, Object>> mapList = questionService.countTypeQuestion();
for (Map<String, Object> map : mapList) {
//根据分类id,读取对应的分类数据
Long typeId = (Long) map.get("TYPE");
TypeEntity typeEntity = typeService.getById(typeId);
//重新封装分类名称到map
map.put("name",typeEntity.getType());
}
return R.ok().put("mapList",mapList);
}
}
6.1.2 编写前端代码
前端项目renren-fast-vue/src/views/modules/question目录 新增统计注册用户数量页面 echarts.vue
<template>
<div class="mod-demo-echarts">
<el-row :gutter="20">
<el-col :span="24">
<el-card>
<div id="J_chartBarBox" class="chart-box"></div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import echarts from 'echarts'
export default {
data () {
return {
chartBar: null,
beginTime: '',
endTime: '',
mapList: [],
xData: [],
yData: [],
btnDisabled: false
}
},
mounted () {
this.create()
},
activated () {
// 由于给echart添加了resize事件, 在组件激活时需要重新resize绘画一次, 否则出现空白bug
if (this.chartBar) {
this.chartBar.resize()
}
},
methods: {
create () {
this. getDataList ()
this.initChartBar()
},
// 获取数据列表
getDataList () {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/question/question/countTypeQuestion'),
method: 'get'
}).then(({data}) => {
if (data && data.code === 0) {
this.mapList = data.mapList
this.xData = []
this.yData = []
for(let j in this.mapList){
this.xData.push(this.mapList[j].name)
this.yData.push(this.mapList[j].num)
}
console.log(this.xData)
console.log(this.yData)
this.chartBar.resize()
// this.xData= this.mapList.map(obj => n.NAME);
// this.yData= this.mapList.map(obj => n.VALUE);
this.chartBar.setOption({
xAxis: [
{
type: 'category',
data: this.xData
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '题目数量',
type: 'bar',
data: this.yData
}
]
})
} else {
this.mapList = []
}
})
},
// 柱状图
initChartBar () {
var option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['题目数量']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: this.xData
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '每日注册用户数',
type: 'bar',
data: this.yData
}
]
}
this.chartBar = echarts.init(document.getElementById('J_chartBarBox'))
// this.chartBar.setOption(option)
window.addEventListener('resize', () => {
this.chartBar.resize()
})
}
}
}
</script>
<style lang="scss">
.mod-demo-echarts {
> .el-alert {
margin-bottom: 10px;
}
> .el-row {
margin-top: -10px;
margin-bottom: -10px;
.el-col {
padding-top: 10px;
padding-bottom: 10px;
}
}
.chart-box {
min-height: 400px;
}
}
</style>
6.1.3 配置用户注册统计菜单
新增菜单后,刷新,即可查看菜单
点击题目数量统计可以查看按照题目分类统计的数量柱状图