工作中代码收录 key code
- 关注点+开发步骤
- 高级应召开发思想》直呼内行
- 开发环境配置
- 服务器操作
- tomcat7
- java
- CollectionUtil
- mysql
- mybatis
- sqlite
- Socket
- layui
- vue
- vue-cli [脚手架 安装](https://www.cnbloegs.com/loveyaxin/p/7094089.html)
- vue项目引入自定义svg矢量图片
- vue+自定义bus.js兄弟传值
- vue + [eventBus 跨级通信](https://blog.csdn.net/ZYS10000/article/details/123295982)
- WebSocket+SockJS+Stomp 消息功能(简化使用websocket)
- Sockjs HTML页面引入效果
- Springb[oot整合websocket](https://blog.csdn.net/qq_31960623/article/details/114131424) 后台原生效果
- 弹窗悬浮,可点击父级页面(覆盖拖拽效果)
- vue整合typescript
- Hutool
- js 工具类
- 特殊需求
- MyBatis Plus 自动类型转换之TypeHandler
- ResponseBody返回值包裹ResultData
- Vue+Webpack项目,游览器断点进入错误文件;vue 组件命名index.vue重名调试不进断点;(Webpack输出路径问题?热替换)
- 后台时间数据正确,但传到前端显示与后台相差8个小时
- 前台vue 后台springboot打包jar运行
- mvn install cmd安装包
- freemarker模板(生成doc)
- 页面前进、历史操作
- ResultData包装类 返回string报错 ResultData cant cast to string
- 登录cookie问题(springboot+vue,shiro设置)
- null空值判断
- 缓存处理(项目加上版本号)
- springboot配置socket io
- 深度覆盖框架css【深度选择器】
- 前端vue项目ip请求失败
- cmd 关闭exe
关注点+开发步骤
内容关注点为高效编码,省出时间去happy or 自我提升。
开发顺序》最优路线
1.搞懂需求》脑海里有页面及交互(如果自己想的跟项目经理或甲方不同,早晚需要填坑,这个可根据时间要求进行权衡),可以在纸上画,用电脑画图工具画,用原型设计工具如墨刀,募客,axure之类的,个人喜欢募客经典版
2.设计数据库结构
3.代码生成简单逻辑代码
4.手动编写逻辑代码
5.测试(一般自己开发时就顺手搞了,有用户更佳)
6.打包上线测试/使用
文档准备(接口文档,使用说明书,测试报告,会议纪要,技术方案,开发计划书,数据库文档)
高级应召开发思想》直呼内行
- 复杂表实现?动态表头,动态层级设计》【表头】和【指标】分库保存,指标处理多层级,表头保存数据库字段名称+jsform初始化col用字段+排序,然后用高级mybatis组合每次查询的表头list,获取到动态结果值jsonObject。相关页面操作,导出word时可能比较痛苦,代码沉重。
- struct2+hibernate ssh框架开发》偶然见到老哥页面刷新方式:html body下全是一个form,form一提交就自动替换所有,妙哉(整半天找不到url)
- 原公司甲方突然有想法,在原来的公司列表下突出总公司》地区公司》分销点等多级结构,如何解决?直接扩展公司ID长度,用公司ID-公司ID-公司ID来体现层级,瞬间解决了问题。(正确做法是加一个parentId字段来记录,个人喜欢再加一个level记录层级,这样后期记录查询很方便)
开发环境配置
【工作要求:Java8,mysql5.7 】
---- java ----
1.安装java, java安装包安装顺序为 jdk》jre ,两个都安装吗
(win10版本配置环境变量)win7配置查看帖子
2.新建JAVA_HOME ,地址如 【C:\Program Files\Java\jdk1.8.0_231】
3.编辑Path变量,添加 【%JAVA_HOME%\bin】 ,【%JAVA_HOME%\jre\bin】,两个变量最好上移至顶部
4.新建CLASSPATH,添加【.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tool.jar】
5.cmd查看java版本 java -version
---- mysql---- 安装包
path下添加mysql路径 【D:\Program Files\MySQL\MySQL Server 5.7\bin】
cmd查看mysql 服务【mysql -uroot -proot】
跳过登录密码【my.ini】文件设置【skip-grant-tables】
#mysql登录问题
use mysql;
select user,host,from user;
GRANT ALL PRIVILEGES ON *.* TO 'root'@'10.251.226.172' IDENTIFIED BY 'czp' WITH GRANT OPTION;
flush privileges;
maven 安装包 下载bin.zip 解压即可使用
新建【MAVEN_HOME】变量,添加【D:\apache-maven-3.6.3\bin】
编辑Path变量,新增【%MAVEN_HOME%\bin】
cm查看maven版本【mvn -v】
/* maven添加本地包命令 */
mvn install:install-file -Dfile=jar包的位置(参数一) -DgroupId=groupId(参数二) -DartifactId=artifactId(参数三) -Dversion=version(参数四) -Dpackaging=jar
mvn install:install-file -Dmaven.repo.local=D:\apache-maven-3.6.3\repository -Dfile=D:\openhtmltopdf-pdfbox-1.0.8.jar -DgroupId=com.openhtmltopdf -DartifactId=openhtmltopdf-pdfbox -Dversion=1.0.8 -Dpackaging=jar
---- config文件配置 【conf/settings.xml】内关键标签 ----
<localRepository>D:\tools\repository</localRepository>
----修改maven默认JDK版本----
<profile>
<id>JDK-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
----添加国内镜像源----
<!-- 阿里云仓库 -->
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>
<!-- 中央仓库1 -->
<mirror>
<id>repo1</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://repo1.maven.org/maven2/</url>
</mirror>
<!-- 中央仓库2 -->
<mirror>
<id>repo2</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://repo2.maven.org/maven2/</url>
</mirror>
服务器操作
1.创建文件夹》复制jar+启动bat+yml+dev.yml文件
2.检查并修改bat文件,从jar中复制出来pro.yml文件
3.配置文件修改active,修改端口号+tcp硬件接口号》(可能要同步修改系统信息展板以及字段信息表)
4.确认配置信息无误》端口号/路径/数据库/密码等后启动服务,如果启动有问题》》》检查log的文件日志
5.系统启动后记得清除历史记录》修改一些关键展示信息》版权所有之类的东西。
conf>server.xml
tomcat7
Connection reset by peer: socket write error tomcat连接池最大并发数连接修改
conf>server.xml
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" acceptCount="500" maxThreads="400" />
<Connector port="8009" protocol="AJP/1.3"
redirectPort="8443"
acceptCount="500"
maxThreads="400" />
java
Stream / Collector操作
//1.过滤数据
DictInfoList.stream().filter(dict ->"wenti".equals(dict.getItemCode())).collect(Collectors.toList());
//2.查找指定数据
mubanEditUserList.stream().filter(user -> user.getUserId().equals(userDTO.getId())).findFirst().orElse(null);
//3.数据分组
Map<Long, List<KaopingJihuaDTO>> collect = jihuaList.stream().collect(Collectors.groupingBy(KaopingJihua::getFenzhuId));
jihuaList.stream().collect(Collectors.groupingBy(jihua -> jihua.getFenzhiId()+"-"+ jihua.getRenwuId()))
//4.lamda查询
duixiangFenzhuServiceImpl.lambdaQuery().eq(DuixiangFenzhu::getYanlianId,condition.getYanlianId())
.eq(DuixiangFenzhu::getFenzuDengji,condition.getFenzuDengji())
.eq(DuixiangFenzhu::getParentId,condition.getParentId()).list();
//5.数据排序
btmap.getValue().sort(Comparator.comparing(biaotou -> biaotou.getLevel()+biaotou.getFieldSort()));
//时间排序
paperUserList.stream().sorted(((o1, o2) -> {
return Long.compare(o1.getCreateDate().getTime(),o2.getCreateDate().getTime());
})).collect(Collectors.toList());
//6.数值合计
jsonObjectList.stream().map(obj -> obj.getStr("fenzhi" + maxLevel)).mapToDouble(Double::parseDouble).sum();
//status状态判断 tag展示
<div class='tag' ><div class='layui-badge layui-bg-green' >编辑中</div> </div>
普通递归操作
一次性请求,前台拼接数据
ajaxPOST("/admin/sys/region/query",{level:1}, (res) => {
let currentParentCode = $("input[name='parentCode']").val();
let parentName = $("input[name='parentName']").val();
let dataArr =[];
res.data.forEach(item =>{
item.value = item.code;
if(!item.parentCode){
item.title= item.name;
dataArr.push(item);
rootAl(res.data,item,item.code,currentParentCode);
}
})
}
function rootAl(arr,item,parentCode,currentParentCode) {
item.children = item.children ? item.children:[];
arr.forEach(Item =>{
if(Item.parentCode == parentCode){
Item.label = Item.name;
Item.value = Item.code;
// Item.isLeaf = true;
Item.selected = Item.code == currentParentCode ? true: false;
item.children.push(Item);
rootAl(arr, Item, Item.code);}
})
}
记录保存验证
List<Student> studentList = studentServiceImpl.lambdaQuery().eq(Student::getNo,entity.getNo()).list();
if(null == entity.getId()){
if(studentList .size() > 0){
return ResultData.errorMsg("已存在相同学号!");
}
}else if(studentList .size() > 0 && !studentList .get(0).getId().equals(entity.getId())){
return ResultData.errorMsg("已存在相同学号!");
}
CollectionUtil
1.Arrays.asList(userIds).removeAll(collect) 删除报错,因为asList返回list有固定长度
//java.lang.UnsupportedOperationException: null
注意:在后台操作列表或者使用lambda查询,批量处理,最好统一将array转成list处理,这样查询,删除等操作会减少逻辑误判,只关心业务!
//Array
ArrayList<ClassUserInfo> addUserList = new ArrayList<>();
String[] userIds = examUserIds.split(",");
List<String> collect = existUserList.stream().map(item -> item.getUserId()+"").collect(Collectors.toList());
Arrays.asList(userIds).removeAll(collect);// userIds:['4','5','6'] , collect:['4']
解决方法:使用hutool工具的CollectionUtil.newArrayList
ArrayList<String> stringList = CollectionUtil.newArrayList(userIds);
//array转list
userIds = stringList.toArray(new String[stringList.size()]);
mysql
属性对照
BigDecimal | float(5,2) | 分数 | 30.00 |
---|---|---|---|
float | decimal(5,2) | 分数 | 30.00 |
String | varchar(100) | 描述 | fasdfafdfasd |
常用sql
1.更新数据,日期增加时间间隔(12小时)
update inspect_task_user set C_ACCEPT_DATE = DATE_ADD(C_ACCEPT_DATE,INTERVAL 12 hour) where C_ACCEPT_DATE is not null;
2.查询根据时间区间大小
select id from inspect_task_user where unix_timestamp(C_ACCEPT_DATE) > UNIX_TIMESTAMP(C_FINISH_DATE)
3.mysql 列表查询函数,类似于in查询
FIND_IN_SET(str,strlist)
select FIND_IN_SET('2', '1,2'); 返回2
select FIND_IN_SET('6', '1'); 返回0 strlist中不存在str,所以返回0。
4.添加字段
ALTER TABLE y_task ADD COLUMN C_START_DATE datetime DEFAULT NULL COMMENT '开始时间' AFTER C_ITEM_NAME;
5.修改字段
alter table y_task modify COLUMN C_DESCR VARCHAR(500) DEFAULT NULL COMMENT '描述';
6.插入记录
INSERT INTO `demo-yanlian-test1`.`sys_dict_type`( `C_CREATE_BY`, `C_CREATE_DATE`, `C_UPDATE_BY`, `C_UPDATE_DATE`, `C_DELETE_FLAG`, `C_CODE`, `C_NAME`, `C_SYSTEM`, `C_SORT`, `C_DESCR`) VALUES ( '管理员', '2021-10-08 11:47:03', NULL, NULL, 0, 'file_type', '文件类型', b'0', NULL, '');
7.时间 限制24小时之内
WHERE lcs.C_CREATE_DATE >= (NOW() - interval 24 hour)
8.mysqlif判断
IF(condition,result1,result2) IF(state=1,'正常','不正常') '用户状态'
9.mysql[复杂判断](https://blog.csdn.net/yaofangshou/article/details/79488023)
SELECT
`id` '用户ID',
`name` '用户名称',
( CASE `state`
WHEN 1 THEN '正常'
WHEN 2 THEN '不正常'
ELSE NULL
END
) '用户状态'
FROM
`user`
10.elt函数(需要固定顺序)
condition的值必须是顺序1、2、3、...才会对应结果result1、result2、result3、...
SELECT
`id` '用户ID',
`name` '用户名称',
`type`,
ELT(type,'普通员工','中层员工','高层员工') '用户类型'
FROM
`user`;
11.空值null默认 IFNULL> sum(1) 结果为null就返回0,否则返回sum(1)
select IFNULL( sum(1),0) from class_user_info where C_CLASS_ID = t.ID and C_STATUS = 1
12.union用法 将两张业务表的公共字段 合并成一个虚拟表(这样能正常使用分页插件)
select DISTINCT <include refid="Base_Column_List"/>
,t.USER_NAME
,t.USER_ACCOUNT
from
( SELECT t1.*
,u.C_NAME as USER_NAME
,u.C_ACCOUNT as USER_ACCOUNT
FROM y_baobiao t1 left join sys_user u on u.ID = t1.C_CREATE_BY_ID
where t1.C_CREATE_BY_ID = '111'
union
SELECT t2.*
,u.C_NAME as USER_NAME
,u.C_ACCOUNT as USER_ACCOUNT
FROM y_baobiao t2 left join sys_user u on u.ID = t2.C_CREATE_BY_ID
where t2.C_FENZHU_ID in (22,33)
) t
where t.C_YANLIAN_ID = 11
and AND t.C_LEIXING = 0
and name like CONCAT('%','test','%')
13.GROUP BY 报错》优化代码,数据库有默认only_full_group_By,
设计sql语句时尽量将 结果集字段跟group by的字段对上。 (数据库设计看情况优化)
修改sql模式
SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
SELECT @@GLOBAL.sql_mode;
SELECT @@SESSION.sql_mode;
set @@GLOBAL.sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
set @@SESSION.sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATENO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
优化sql
如果想实现 select A.column1, A.column2 from table as A group by column1
在group by后面追加上column2就可以了,意义相当于按照 由column1和column2拼接成的联合字段进行分组
select A.column1, A.column2 from table as A group by column1 , column2
或者在内查询里只查询分组的字段, 各位可以参考如下形式
SELECT A.column1, A.column2 FROM TABLE A JOIN ( SELECT max(id) id, column1 FROM TABLE b GROUP BY column_1 ) C ON A.column1 = C.column1 and C.id = A.id
补充
MySQL5.7之后,sql_mode中ONLY_FULL_GROUP_BY模式默认设置为打开状态。
ONLY_FULL_GROUP_BY的语义就是确定select target list中的所有列的值都是明确语义,简单的说来,在此模式下,target list中的值要么是来自于聚合函数(sum、avg、max等)的结果,要么是来自于group by list中的表达式的值
MySQL提供了any_value()函数来抑制ONLY_FULL_GROUP_BY值被拒绝
any_value()会选择被分到同一组的数据里第一条数据的指定列值作为返回数据
14.创建索引
create unique index ID on y_plan(ID); //唯一索引
create index C_ROOM_ID on y_plan(C_ROOM_ID); //普通索引
15.数据库模式更改
SELECT @@GLOBAL.sql_mode;
SELECT @@SESSION.sql_mode;
set @@GLOBAL.sql_mode='STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
set @@SESSION.sql_mode='STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
项目启动后mapper方法查询失败
Cause: java.sql.SQLException: The user specified as a definer ('root'@'%') does not exist
此种报错主要是针对访问视图文件引起的(没有权限)
权限问题,授权 给 root 所有sql 权限
mysql -uroot -proot
mysql> grant all privileges on *.* to root@"%" identified by ".";
Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
mybatis
integer类型前台传空后台接收为null,update时不会自动更新
//updateStrategy更新策略注解:【忽略为空不更新的策略】
@TableField(value = "C_PARENT_ID",updateStrategy = FieldStrategy.IGNORED)
sqlite
----字段类型----
sqlite时动态数据类型,根据存入值自动判断
1.integer:带符号的整形,具体取决于存入数字范围大小
2.real:浮点数字,存储8-byte IEEE浮点数
3.TEXT:字符串文本
4.BLOB:二进制对象
----转移数据----
1.sqlite环境预备
sqlite-tools-win32 / sqlite-dll-win64 在本地解压后放到指定文件夹里
2.指定文件夹路径添加到系统环境变量PATH下面
例:D:\Program Files\sqlite
3.创建本地数据库robot.db
cmd切换路径: cd d:\program files\sqlite
创建db数据库文件 sqlite3 robot.db
查看本地数据库文件 .databases
4.使用navicat连接到该数据库文件,然后创建对应表结构+记录
<!-- pom配置 -->
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.36.0.3</version>
</dependency>
# springboot配置sqlite数据库 (默认不需要配置用户 / 密码)
# 可能存在sqlite无法保存Date类型问题
spring:
datasource:
url: jdbc:sqlite:D:\Workspace\ylt\demo-ylt.db
username:
password:
driver-class-name: org.sqlite.JDBC
application:
name: "云里听"
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
//代码generator配置数据库路径
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:sqlite:D:/Workspace/ylt/demo-ylt.db");
dataSourceConfig.setDriverName("org.sqlite.JDBC");
dataSourceConfig.setDbType(DbType.SQLITE);
mpg.setDataSource(dataSourceConfig);
Socket
socket server + client网上有代码
经测试:局域网连接传输文件很快
但是在zip文件生成之后传输会报错
1.java.net.SocketException: Software caused connection abort: socket write error
2.java.net.SocketException: Connection reset by peer: socket write error
layui
img图片预览(缩略图)
//table展示预览图片时最好加上单元格高度
.layui-table-cell { /*cell内容高度自动,不然内容不显示*/
height: auto;
max-height: 50px;
}
<div class="layui-form-item ">
<label class="layui-form-label ">文件缩略图</label>
<div class="layui-input-block">
<input type="text" name="imageThumb" th:value="${entity?.imageThumb}" placeholder="请输入" autocomplete="off" class="layui-input layui-disabled"
readonly="readonly" style="width: 70%;display: inline-block">
<button type="button" class="layui-btn " id="imageThumb"><i class="layui-icon"></i>上传</button>
</div>
</div>
//缩略图上传
upload.render({
elem: '#imageThumb'
,url:ctxPath + '/admin/course/file/uploadImg'
// ,auto:false
,accept:'images'
// ,acceptMine:'application/msexcel,application/msword,application/pdf,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
,exts: 'png|jpg'//限制上传文件的后缀名,用|分隔
//,multiple: true
// ,data:{id:id}
,before: function(obj){ //obj参数包含的信息,跟 choose回调完全一致,可参见上文。
layer.load(); //上传loading
}
,done: function(res){
layer.closeAll('loading'); //关闭loading
console.log(res);
$("input[name='imageThumb']").val(res.data);
}
});
@PostMapping({"/uploadImg"})
@RequiresPermissions({"sys_upload_file:add"})
public ResultData uploadImg(@RequestParam("file") MultipartFile multipartFile) throws IOException {
String originalFilename = multipartFile.getOriginalFilename();
VerifyUtil.isNull(originalFilename, "文件名不能为空");
assert originalFilename != null;
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String path = this.getUploadPath(UUID.randomUUID() + suffix);
File file = new File(path);
multipartFile.transferTo(file);
String absolutePath = file.getAbsolutePath();
String url =this.convertPathToUrl(absolutePath);
return ResultData.success(url);
}
js col行内模板
templet:function(d){
if(!d.imageThumb){return "";}
// return '<img src="'+ctxPath+d.imageThumb +'" />';
return '<div id="layer-photos-'+d.id+'" class="layer-photos-demo" >\n' +
' <img layer-src="'+ ctxPath + d.imageThumb +'" src="'+ ctxPath + d.imageThumb +'" alt="图片名">\n' +
' </div>'
}
js 点击事件
//图片点击效果
$(document).on('click','.layer-photos-demo', function(){
let id = $(this).attr('id');
layer.photos({photos: '#'+id},true);
});
input输入框宽度调整
.layui-form-label{
width: 90px;
}
.layui-input-block{
margin-left: 120px;
}
layui时间控件闪退
//日期时间选择器
laydate.render({
elem: 'input[name=signIn]'
,type: 'datetime'
,trigger: 'click'
});
//多个时间控件闪退只需要在控件中添加 trigger: 'click'
<input name="startTime" th:value="${#dates.format(entity?.startDate, 'yyyy-MM-dd HH:mm:ss')}" />
/**
* 开始时间 import java.util.Date;
*/
@TableField("C_START_DATE")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private Date startDate;
xmselect初始化 《树,单选,过滤掉己方层级下数据》
<div id="demo1" class="xm-select-demo"></div>
//过滤己方ID下数据
let chatperId = $("input[name='id']").val();
//初始化上级树parentId
var demo1 = xmSelect.render({
el: '#demo1',
autoRow: true,
name:"parentId",
// filterable: true,
radio: true,
tree: {
show: true,//是否显示树状结构
showFolderIcon: true,//是否展示三角图标
showLine: true,//是否显示虚线
//间距
indent: 15,
// expandedKeys: [ -3 ],//默认展开节点的数组, 为 true 时, 展开所有节点
strict: false,//是否严格遵守父子模式
},
// toolbar: {
// show: true,
// list: ['ALL', 'REVERSE', 'CLEAR']
// },
height: 'auto',
data: []
})
ajaxPOST("/admin/course/chapter/query", {}, (res) => {
if (!res.data) {
return false;
}
res.data.forEach(item => {
item.value = item.id;
item.children = []
});
//数据拼接 [为了避免循环选择出现,数据拼接需要用记录id过滤一下]
let parentList = res.data.filter(item => !item.parentId && item.id != chatperId);
parentList.forEach(item =>{
if(item.id == chatperId){
return false;
}
appendChildList(item,res.data);
})
demo1.update({
data: parentList
})
//初始化赋值
let parentId = $("#parentId").val();
if (parentId) {
let find = res.data.find(item => item.id == parentId);
if (find) {
demo1.setValue([
{name: find.name, value: find.id}
])
}
}
})
function appendChildList(parent,list){
//[为了避免循环选择出现,数据拼接需要用记录id过滤一下]
let childrenList = list.filter(item => item.parentId == parent.id && item.id != chatperId);
if(childrenList.length > 0){
parent.children = childrenList;
parent.children.forEach(item =>{
if(item.id == chatperId){
return false;
}
appendChildList(item,list);
})
}
}
layui 获取弹窗内table的选中值
var iframeWindow = window['layui-layer-iframe' + index] //获取弹框页面
var checkStatus = iframeWindow.layui.table.checkStatus('userTable');//获取table的elem:"#test"
thymeleaf 初始化赋值
//赋值
th:value="${entity?.captcha}"
//附加class
th:classappend="${entity?.captcha != null? 'layui-disabled' : ''}"
//禁用
th:disabled="${entity?.captcha != null}"
//格式化时间
th:value="${#dates.format(entity?.startDate, 'yyyy-MM-dd HH:mm:ss')}"
//默认选中 (bit格式字段返回到前台会自动转true/false)
th:checked="${entity?.sex} == null ? true : ${entity?.sex} == 1"
th:checked="${entity?.sex} == 2"
//select
th:each="course : ${courseList}" th:text="${course.name}" th:value="${course.id}" th:selected="${course.id} eq ${entity?.courseId}"
//select option 下选中值文字
let userName = $("select[name='examUserId'] option:selected").text();
thymeleaf 初始化select
<ul th:each="t,it: ${ts}">
<span th:text="${it.count}"></span>
<span th:text="${t.name}"></span>
</ul>
it.index 当前的迭代对象的index(从1开始计算)
it.count 当前迭代对象的index(从1开始计算)
it.size 被迭代对象的大小
it.even/odd 布尔值,循环是否是偶数/奇数
it.first 布尔值,循环是否是第一个
it.last 布尔值,循环是否最后一个
普通select遍历
<select name="groupId" lay-verify="required">
<option value="">请选择</option>
<option th:each="group : ${groupList}" th:text="${group.name}" th:value="${group.id}" th:selected="${group.id} eq ${entity?.groupId}" ></option>
</select>
templet:function (d) { return d.publish === null ? '<span>'+ '' +'</span>' : d.publish === true ? '<span>'+ '是' +'</span>' : '<span>'+ '否' +'</span>'
}
//取值
let sysUserName = layero.find("iframe").contents().find("select[name='sysUserId'] option:selected").text();
//如果要扩展参数 + 联动方法
//option标签内扩展attr
th:data-text="${group.name}"
//select添加layui标识
lay-filter="groupId"
//select联动方法
form.on('select(groupId)', function(data){
let groupName = $("select[name='groupId'] option:selected").attr("data-text");
$("input[name='groupName']").val(groupName);
});
thymeleaf 初始化radio
//radio默认选中
<input type="radio" name="publish" value="0" title="未发布" th:checked="${entity?.publish} == null ? true : ${entity?.publish} == false" >
<div class="layui-input-block">
<input type="radio" name="publish" value="0" title="未发布" th:checked="${entity?.publish} == false">
<input type="radio" name="publish" value="1" title="已发布" th:checked="${entity?.publish} == true">
</div>
//layui 列表初始化option【实体类的boolean类型,0/1自动转true/false】
templet:function (d) { return d.publish === null ? '<span>'+ '' +'</span>' : d.publish === true ? '<span>'+ '是' +'</span>' : '<span>'+ '否' +'</span>'
}
layui tree初始化 (栅栏分页面)
//页面 参考代码
<div style="padding: 10px">
<div class="layui-row layui-col-space10">
<div class="layui-col-md2">
<div class="layui-card">
<div class="layui-card-body">
<div id="userGroupTree" style="height: calc(100vh - 70px);overflow: scroll"></div>
<button class="layui-btn layui-btn-fluid layui-btn-sm" onclick="openUserGroupAddFn()">新增课堂学员分组</button>
</div>
</div>
</div>
<div class="layui-col-md10">
<div class="layui-card">
<div class="layui-card-body">
// ----- 主页 ------
</div>
</div>
</div>
</div>
</div>
//js代码
tree.render({
elem: '#userGroupTree'
,id: 'userGroupTree' //定义索引
, data: []
,edit: ['update', 'del']
, customOperate: true
, click: function (obj) {
//节点高亮
// $(".layui-tree-set").removeClass("layui-tree-set-active");
// obj.elem.addClass("layui-tree-set-active");
}
,operate: function(obj){
var type = obj.type; //得到操作类型:add、edit、del
var data = obj.data; //得到当前节点的数据
var elem = obj.elem; //得到当前节点元素
//Ajax 操作
var id = data.id; //得到节点索引
if(type === 'add'){ //增加节点
openGroupAddFn();
} else if(type === 'update'){ //修改节点
openGroupEditFn(id);
} else if(type === 'del'){ //删除节点
groupDelFn(id);
}
}
});
reloadUserInfoGroupTree();
function reloadUserInfoGroupTree() {
ajaxPOST("/admin/class/user_group/query",{},(res)=>{
if(!res.data){
return false;
}
res.data.forEach(item =>{
item.title = item.name;
})
layui.tree.reload('userGroupTree', {data : res.data});
})
}
layui tree控制按钮显隐
<style>
.treeHide{
display: none;
}
</style>
if(newsIndex != -1){
//$(".layui-icon.layui-icon-add-1");
$(".layui-icon.layui-icon-edit")[newsIndex].className="treeHide";
$(".layui-icon.layui-icon-delete")[newsIndex].className="treeHide";
}
文件下载
直接下载文件
window.location.href="/unauth/fzxx/downloadFzFile";
@GetMapping("/downloadFzFile")
public void downloadFzFile(HttpServletResponse response) throws IOException {
response.setContentType("application/force-download");
String fileName = URLEncoder.encode("仿真引擎.zip", "UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
//文件名称乱码(空格变加号)
response.setHeader("Content-Disposition", "attachment;filename="+
new String(classFile.getFileName().getBytes("UTF-8"),"ISO8859-1"));
InputStream is = this.getClass().getResourceAsStream("/templates/仿真引擎.zip");
OutputStream outputStream = response.getOutputStream();
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) > 0) {
outputStream.write(buffer, 0, len);
}
outputStream.close();
}
后台生成文件
window.location.href = ctxPath + '/admin/sys/user/exportMuban';
@GetMapping("/exportMuban")
@RequiresPermissions("sys_user:list")
public void exportKaopingyuanMuban(HttpServletResponse response,
HttpServletRequest request){
ExcelWriter writer = ExcelUtil.getWriter();
List<String> headerRow1 = new ArrayList<>();
headerRow1.add("系统帐号");
headerRow1.add("登录密码");
headerRow1.add("姓名");
headerRow1.add("性别");
headerRow1.add("要素");
headerRow1.add("角色");
writer.writeHeadRow(headerRow1);
writer.setColumnWidth(0,25);
writer.setColumnWidth(1,25);
writer.setColumnWidth(2,25);
writer.setColumnWidth(3,25);
writer.setColumnWidth(4,25);
writer.setColumnWidth(5,25);
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setContentType("application/vnd.ms-excel;charset=utf-8");
ServletOutputStream out= null;
try {
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("系统用户模板.xls", "UTF-8"));
out = response.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
writer.flush(out,true);
writer.close();
IoUtil.close(out);
}
文件上传前后台
//支持文件类型
//文件类型:0-图片,1-文件,2-音频,3-视频,4-Flash,5-rich_text,字典:document_type
//String [] suffixArr = new String[]{".doc",".docx",".pdf",".xls",".xlsx",".mp3",".mp4",".png",".jpg"};
//单行按钮初始化
<div class="layui-input-block">
<input type="text" name="imageThumb" th:value="${entity?.imageThumb}" readonly="readonly" placeholder="请输入" autocomplete="off" class="layui-input layui-disabled" style="width: 70%;display: inline-block">
<button type="button" class="layui-btn layui-btn-danger" id="imageThumb"><i class="layui-icon"></i>上传</button>
</div>
//拖拽上传初始化
<div class="layui-upload-drag" id="fileSelect">
<i class="layui-icon"></i>
<p>点击上传,或将文件拖拽到此处</p>
<div class="layui-hide" id="uploadDemoView">
<hr>
<img src="" alt="上传成功后渲染" style="max-width: 196px">
</div>
</div>
//upload初始化
//拖拽上传
upload.render({
elem: '#fileSelect'
,url:ctxPath + '/admin/document/info/upload'
// ,auto:false
,accept:'file'
// ,acceptMine:'application/msexcel,application/msword,application/pdf,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
,exts: 'doc|docx|pdf|xls|xlsx|mp3|mp4|png|jpg'//限制上传文件的后缀名,用|分隔
//,multiple: true
,data:{id:id}
,done: function(res){
}
});
//接口
@PostMapping({"/uploadFile"})
@RequiresPermissions({"sys_upload_file:add"})
public ResultData<SysUploadFile> uploadFile(@RequestParam("file") MultipartFile multipartFile, HttpSession session) throws IOException {
String originalFilename = multipartFile.getOriginalFilename();
VerifyUtil.isNull(originalFilename, "文件名不能为空");
assert originalFilename != null;
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String path = this.getUploadPath(UUID.randomUUID() + suffix);
File file = new File(path);
multipartFile.transferTo(file);
String absolutePath = file.getAbsolutePath();
String url = this.convertPathToUrl(absolutePath);
SysUploadFile sysUploadFile = new SysUploadFile();
sysUploadFile.setPath(absolutePath);
sysUploadFile.setFileName(originalFilename);
sysUploadFile.setUrl(url);
sysUploadFile.setExtend(suffix.replace(".", ""));
ResultData<SysUploadFile> resultData = ResultData.successMsg("上传成功");
resultData.setData(sysUploadFile);
return resultData;
}
启用按钮切换
//html
<script type="text/html" id="disabledColumn">
<input type="checkbox" name="disabled" lay-filter="disabled"
shiro:hasPermission="iot_device:update"
lay-skin="switch" lay-text="是|否" data-id="{{d.id}}" {{!d.disabled || 'checked'}} >
<div shiro:lacksPermission="iot_device:update">
{{!d.disabled?'否':'是' }}
</div>
</script>
//js
,{field:'state', title:'禁用', align: 'center',width:80,toolbar:'#disabledColumn'}
//切换事件
form.on('switch(disabled)', function (obj) {
let id = this.getAttribute("data-id");
let checked = !!this.checked;
ajaxPOST('/admin/iot/device/update', {id: id, disabled: checked}, (res) => {
layer.msg(res.message, {time: 1000});
})
}
读取excel
ExcelReader reader = ExcelUtil.getReader(multipartFile.getInputStream());
reader.setSheet(0);
List<Map<String, Object>> maps = reader.readAll();
for (Map<String, Object> map : maps) {
System.out.println(map);
}
//excel读取中如果出现找不到方法,workbook,celltype之类的报错,很可能是poi版本异常。
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.0.0</version>
</dependency>
# xm-select
插件官网》 https://maplemei.gitee.io/xm-select/#/plugin/tree
可自行配置树+懒加载树,(树全量显示如果记录超过4000页面会有明显卡顿)
```javascript
<script th:src="@{/static/lib/xm-select/xm-select.js}"></script>
vue
vue-cli 脚手架 安装
vue init webpack my-project #创建新vue项目
#可用的模板有:webpack / webpack-simple / browserify / browserify-simple / pwa / simple
#其中simple模板仅仅只会下载一个html,非常精简。
vue项目初始化配置
vue项目引入自定义svg矢量图片
svg官网链接
svgicon
svg-sprite-loader
vue-cli配置svg (实际项目测试配置完毕后记得重启vue再看页面,不然不显示)
//模式一 svgicon
npm install vue-svgicon -D
//模式二
npm install svg-sprite-loader
vue+自定义bus.js兄弟传值
待补充
vue + eventBus 跨级通信
eventBus 适合小项目、数据被更少组件使用的项目,对于中大型项目数据在很多组件之间使用的情况 eventBus 就不太适用了
1.npm install vue-bus --save
2.main.js配置
import Vue from 'vue';
import VueBus from 'vue-bus';
Vue.use(VueBus);
3.组件使用
// ---------------A 组件-------------------------
<template>
<div class="wrap">
<button @click="sendMsg">触发</button>
</div>
</template>
<script>
export default {
data(){
return {
Amsg:'我是来自A组件的信息',
}
},
methods:{
sendMsg(){
this.$bus.emit('changeMsg', this.Amsg );
this.$bus.emit('doOnce','我只会触发一次');
}
},
}
</script>
// ---------------B 组件------------------------------------
<template>
<div>
<h3>{{Bmsg}}</h3>
</div>
</template>
<script>
export default {
data(){
return {
Bmsg:'我是B组件',
}
},
methods:{
getMsg(msg){
this.Bmsg = msg;
console.log(msg);
}
},
created(){
/*
* 接收事件
* 这种写法也体现了:A 组件调用 B 组件中的方法。如果只是传递数据,可参考如下简化写法:
* this.$bus.on('changeMsg', (msg) => { this.Bmsg = msg });
*/
this.$bus.on('changeMsg', this.getMsg);
// 此侦听器只会触发一次
this.$bus.once('doOnce', (txt) => { console.log(txt) });
},
// 组件销毁时,移除EventBus事件监听
beforeDestroy() {
this.$bus.off('changeMsg', this.getMsg);
},
}
</script>
WebSocket+SockJS+Stomp 消息功能(简化使用websocket)
先说个坑:springboot版本问题
WebSocketConfig设置跨域: 如果方法名不对,vue一请求就会停vue服务
[springboot 2.2.0]
registry.addEndpoint(“/ws”).setAllowedOrigins(““) .withSockJS();
[springboot2.6.3 ]
registry.addEndpoint(”/ws").setAllowedOriginPatterns("”).withSockJS();
1.pom文件新增websocket
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2.后台socket类
WebSocketConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
//开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样。
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//设置WebSocket请求的路径后缀,withSockJS(),代表支持sockjs 初始化接口
registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 客户端订阅消息的前缀 主题
config.enableSimpleBroker("/topic","alone","/mass","/user","/all");
// 用户级别的消息订阅的前缀 点对点
config.setUserDestinationPrefix("/user");
//客户端与服务端交互的前缀
//config.setApplicationDestinationPrefixes("/app");
}
/**
* 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
ImController
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.demo.base.parent.BaseController;
import com.example.demo.base.result.ResultData;
import com.yckx.yljl.entity.JlModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@EnableScheduling
public class ImController extends BaseController {
@Autowired
private SimpMessagingTemplate template;
/**
* 消息群发
* SendTo 发送至 Broker 下的指定订阅路径 @SendTo("/mass/getResponse"):作用跟convertAndSend类似,广播发给与该通道相连的客户端
* @param imMsgMap
* @return
*/
@MessageMapping("/massRequest")
@SendTo("/mass/getResponse")
public ResultData mass(Map imMsgMap) {
//方法用于群发测试
System.out.println("msg = " + imMsgMap.get("接收人").toString());
String[] array = imMsgMap.get("接收人").toString().split(",");
return ResultData.success(imMsgMap);
}
/**
* 单独聊天
* @param imMsgMap
* @return
*/
@MessageMapping("/sendToUser")
public ResultData alone(Map imMsgMap) {
this.template.convertAndSendToUser("", "/alone/getResponse", imMsgMap);
this.template.convertAndSend("/all/client", imMsgMap);
return ResultData.success();
}
/**
* 群发
* @param map
* @return
*/
@MessageMapping("/all/client")
@SendTo("/all/client")
public ResultData sendAllClient(Map map) {
this.template.convertAndSend("/all/client", map);
return ResultData.success(map);
}
@Scheduled(fixedRate = 5000)
public void sendTopicMessage(){
System.out.println("后台广播推送!");
TestModel testModel = new TestModel();
testModel.setName_1("111");
testModel.setName_2("222");
testModel.setName_3("333");
String str = JSONUtil.toJsonStr(jlModel);
/**
* 如果对象属性是大写字符
* 返回值的json可按照Hutool发送大写字符给前台
* 可按照jackson的包装发送大写+自动小写给前台
**/
this.template.convertAndSend("/topic/find/new/noise/alarm",str);
}
}
vue安装
npm install sockjs-client
npm install stompjs
测试代码,开启连接+订阅+打印返回值
config 新增转发配置
'/ws': {
target: 'http://localhost:8080',//这里后台的地址模拟的;应该填写你们真实的后台接口
ws: true,
changeOrigin: true,//允许跨域
pathRewrite: {
'^/ws': '/yljl/ws'//请求的时候使用这个api就可以
}
},
initWebSocket() {
let socket = new SockJS(this.$baseUrl+'/ws');// 连接服务端
this.stompClient = Stomp.over(socket);
this.stompClient.connect({}, (frame) => {
// 链接成功 订阅主题 接收服务端推送数据
this.stompClient.subscribe('/topic/find/new/noise/alarm', this.onWebSocketMessageReceived);
}, (e) => {
console.error("链接失败", e);
// 失败重连
setTimeout(() => {
this.initWebSocket();
}, 5000);
});
//this.stompClient.disconnect();
},
// 接收到消息并对消息做处理
onWebSocketMessageReceived(payload) {
let data = JSON.parse(payload.body);
console.log("data",data);
},
Sockjs HTML页面引入效果
Springboot整合websocket 后台原生效果
pom引入spring-boot-starter-websocket
-------------------------------html+js----------------
<button class="layui-btn layui-btn-sm" onclick="send()">send</button>//点击后有消息弹窗
var ws;
$(function () {
ws = new WebSocket("ws://localhost:8080/demo/api/pushMessage");
ws.onopen = function()
{
alert("Message is sending");
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
alert("Message is received" + received_msg);
};
})
function send() {
let test = {state:"success",msg:"3333333ffdaff发射点发",toUserId:12};
ws.send(JSON.stringify(test));
}
-------------------------------java socket server 服务器-------------------
//接口调用websocket
JSONObject jsonObject = new JSONObject();
// {state:"success",msg:"3333333ffdaff发射点发",toUserId:12}
jsonObject.set("state","success");
jsonObject.set("msg","3333333ffdaff发射点发");
jsonObject.set("toUserId","12");
WebSocketServer.sendInfo(jsonObject.toString(),"12");
//socket服务器
@Component
@ServerEndpoint("/api/pushMessage") ///{userId}
public class WebSocketServer {
/**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
private static int onlineCount = 0;
/**concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。*/
private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
private Session session;
/**接收userId*/
// private String userId = "";
//连接建立成成功
@OnOpen
public void onOpen(Session session) {//@PathParam("userId") String userId
this.session = session;
webSocketMap.put("12",this);//map控制 socket进出以及统计实时在线人数
// log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount());
sendMessage("连接成功");
}
//连接关闭
@OnClose
public void onClose() {
webSocketMap.remove(userId);
// log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
}
//收到客户端消息后调用的方法
@OnMessage
public void onMessage(String message, Session session) {
// log.info("用户消息:"+userId+",报文:"+message);
//可以群发消息
//消息保存到数据库、redis
if(StringUtils.isNotBlank(message)){
try {
//解析发送的报文
JSONObject jsonObject = new JSONObject(message);
//追加发送人(防止串改)
// jsonObject.put("fromUserId",this.userId);
String toUserId=jsonObject.getStr("toUserId");
//传送给对应toUserId用户的websocket
if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){
webSocketMap.get(toUserId).sendMessage(message);
}else{
//否则不在这个服务器上,发送到mysql或者redis
// log.error("请求的userId:"+toUserId+"不在该服务器上");
}
}catch (Exception e){
e.printStackTrace();
}
}
}
@OnError
public void onError(Session session, Throwable error) {
// log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
error.printStackTrace();
}
//实现服务器主动推送
public void sendMessage(String message) {
try {
this.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
//发送自定义消息
public static void sendInfo(String message, String userId) {
// log.info("发送消息到:"+userId+",报文:"+message);
if(StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)){
webSocketMap.get(userId).sendMessage(message);
}else{
// log.error("用户"+userId+",不在线!");
}
}
}
弹窗悬浮,可点击父级页面(覆盖拖拽效果)
1.原始dialog配置
<el-dialog class="dialogClass" :title="dialogAdd.title" :visible.sync="dialogAdd.show" width="800px"
:modal="false" :close-on-click-modal="false"
:modal-append-to-body="false" v-dialogDrag
append-to-body="true"
>
2.配置css
<style scoped>
.dialogClass{
top: 150px;
bottom: auto;
right: auto;
left: 10px;
box-shadow: 0 0 5px #73859f;
}
.dialogClass::v-deep .el-dialog {
margin: 0 !important;
}
::v-deep .el-dialog__header {
position: relative;
z-index: 9999;
}
::v-deep .el-dialog__body {
position: relative;
z-index: 9900;
margin-top: -20px;
}
.el-dialog {
border-radius: 10px;
/* &__header {
padding: 30px 20px 0;
}
&__body {
padding: 0 20px 30px;
}*/
}
.el-dialog_header {
padding: 30px 20px 0;
}
.el-dialog_body {
padding: 0 20px 30px;
}
</style>
3.引入拖拽效果
drag.js引入asset/js路径下
main.js引入import './assets/js/drag'
[v-dialogDrag]生效,之前写会报错。
vue整合typescript
1.安装插件
npm install typescript ts-loader --save-de
npm install vue-property-decorator --save-dev
2.修改vue.config.js
configureWebpack: {
resolve: { extensions: [".ts", ".tsx", ".js", ".json"] },
}
3.新建tsconfig.json在根目录
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"strictNullChecks": true,
"esModuleInterop": true,
"experimentalDecorators": true
}
}
4.新建shims-vue.d.ts文件
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
Hutool
json 保留null值
// false表示不跳过空值
JSONObject json = JSONUtil.parseObj(userA, false);
Console.log(json.toStringPretty());
//json内部保留字段顺序
// 第二个参数表示保持有序
JSONObject json = JSONUtil.parseObj(userA, false, true);
js 工具类
jquery日常操作
$("#account").css("border-color","");
$("#account").css({"display":"none","border-color":""});
$("#nameList").next().find("input").css({"border-color":""});//找下一个同级节点的input标签
three.js
官网案例查看
使用参考手册参考(工作中用到该例子引入GLTF文件,可惜模型跟官网差距蛮大,效果差强人意。)
1.选择模型类型,obj.gltf(glb)之类的,之前项目里用的是glb格式,公司做模型的说是用maya做的,有一些问题需要梳理下:模型最好是一块块的对象组成一个大模型对象,因为getObjectByName可以直接获取出这个对象,然后做一些位移或旋转自定义操作。
模型编码真的很坑(拙计难为之)》比如展示手机模型,我想让它以音量键位置进行左右旋转,找到音量键按钮对象让它旋转:期待以音量键为中轴线旋转但它还是以手机模型的原点进行旋转(坐标系x,y,z的0位置),左右腾挪,百转难回,惆怅万千。
//html引入canvas
<canvas id="c" ></canvas>
//js渲染画布
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
//摄像头参数
const fov = 45;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
//控制
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = false; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.5;
controls["控制切换"] = false;
controls.screenSpacePanning = false;
controls.minDistance = 0.5;
controls.maxDistance = 1000;
controls.maxPolarAngle = Math.PI / 2;
controls.update();
//环境
const scene = new THREE.Scene();
//加入背景
var background = createBackground({colors: ["#ffffff","#353535"]})
scene.add(background)
//进度提示面板
var steps = document.createElement("div");
steps.setAttribute("id", "step");
steps.setAttribute("lay-filter", "step");
var h1 = document.createElement("h2");
h1.innerText = "第一步";
var h1Div = document.createElement("section");
var h1D = document.createElement("p");
h1D.innerText = "打开电源";
h1Div.appendChild(h1D);
steps.appendChild(h1);
steps.appendChild(h1Div);
//模型展示效果跟maya不同:可参考以下代码
// 伽马输出 //电脑显示屏的gammaFactor为2.2
renderer.gammaOutput = true;
renderer.gammaFactor = 2.2;
renderer.toneMappingExposure = 1; //曝光
javascript
----tinyMCE----
1、如果当前页面只有一个编辑器:
tinyMCE.activeEditor.getContent() / tinyMCE.activeEditor.setContent(“需要设置的编辑器内容”)
2.如果当前页面有多个编辑器
tinyMCE.editors[0].getContent() / tinyMCE.editors[0].setContent(“需要设置的编辑器内容”)
3.获取不带HTML标记的纯文本内容:
var activeEditor = tinymce.activeEditor;
var editBody = activeEditor.getBody();
activeEditor.selection.select(editBody);
var text = activeEditor.selection.getContent( { ‘format’ : ‘text’ } );
4.设置禁用
tinymce.activeEditor.getBody().setAttribute('contenteditable', false);
gojs (图表展示)
js版本如下:
go.js (生产推荐使用)
go-debug.js (开发推荐使用)
go-module.js & go-debug-module.js
go.mjs & go-debug.mjs
GoJS v2.2.2 去除水印方法
实操有效:gojs 2.2版本
搜索:7ca11abfd022028846 找到代码 【c=Va(“7ca11abfd022028846”)】,删除后面的代码(一行之内,大概在800行)
gojs 2.2版本代码展示:
【;b[c]=Qa(“398c3597c01238”);for(var d=[“5da73c80a36455d6038e4972187c3cae51fd22”,sa.Dx+“4ae6247590da4bb21c324ba3a84e385776”,od.xF+“fb236cdfda5de14c134ba1a95a2d4c7cc6f93c1387”,M.za],e=1;5>e;e++)bQa(“7ca11abfd7330390”);b[c]=Qa(“39f046ebb36e4b”);for(c=1;5>c;c++)bQa(“7ca11abfd7330390”);if(4!==d.length||“5”!==d[0][0]||“7”!==d[3][0])od.prototype.Hd=od.prototype.Cx;】
刷新界面,gojs的水印就消失了
在这里插入代码片
js判断数据类型
typeof "test" //string
数组去重
let parentIds = res.data.filter(item => item.parentId).map(item => item.parentId);//[10,10]
parentIds = parentIds.filter((item,index,array)=>{
return array.indexOf(item,0) === index;
});//[10]
dayjs
let startDay = dayjs(row.batchStartTime);
startDay.format('MM-DD HH:mm') //07-25 12:00
页面关闭,触发事件
mounted(){
window.addEventListener("beforeunload", (e) => this.beforeunloadHandler(e));
}
,method(){
// 网页关闭
beforeunloadHandler(e) {
this.offLine();
},
}
destroyed() {
window.removeEventListener("beforeunload", (e) => this.beforeunloadHandler(e));
},
特殊需求
MyBatis Plus 自动类型转换之TypeHandler
问题出现原因在于使用sqlite数据库,无法自动保存date类型数据,需要自定义类型处理器
有帮助的帖子
mybatis-plus官网自动填充功能
1.数据保存时没有createDate,
操作:将demo.base的handler里复制出来MybatisCommonMetaHandler,在insertFill和updateFill中自定义了数据格式(1635151351***什么的时间millis转成时间字符串【这个操作其实最后没用上】)
2.将handler引入项目扫描出了问题,一直无效。测试生效配置如下:
生效配置为:
//@ServletComponentScan
@MapperScan({"com.example.dddd.**","com.example.demo.**"})
@SpringBootApplication
@ComponentScan({"com.example.dddd.**","com.example.demo.**"})//新加入
public class Application extends SpringBootServletInitializer {
@Override//新加入
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.Date时间保存到了数据库,变成了163513之类的timemillis.
尝试上面的MybatisCommonMetaHandler修改插入时的时间字符串,结果发现取出数据展示会无法自动解析。所以最后又依靠DateTypeHandler*完成了日期格式转换,需要ovride方法,测试可行
操作:注释掉字段数据类型解析 【//@DateTimeFormat(pattern = “yyyy-MM-dd HH:mm:ss”)】
2.配置TypeHandler数据类型转换处理器(来源:Mybatis的TypeHandler)
测试可用代码如下:Date数据类型处理
import cn.hutool.core.date.DateUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
@MappedTypes(Date.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class DateTypeHandler extends BaseTypeHandler<Date> {
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
// ps.setDate(i, new java.sql.Date(parameter.getTime()));
String format = DateUtil.format(parameter, "yyyy-MM-dd HH:mm:ss");
ps.setString(i,format);
}
public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
String string = rs.getString(columnName);
return DateUtil.parse(string, "yyyy-MM-dd HH:mm:ss");
// java.sql.Date sqlDate = rs.getDate(columnName);
// return sqlDate != null ? new Date(sqlDate.getTime()) : null;
}
public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String string = rs.getString(columnIndex);
return DateUtil.parse(string, "yyyy-MM-dd HH:mm:ss");
// java.sql.Date sqlDate = rs.getDate(columnIndex);
// return sqlDate != null ? new Date(sqlDate.getTime()) : null;
}
public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String string = cs.getString(columnIndex);
return DateUtil.parse(string, "yyyy-MM-dd HH:mm:ss");
// java.sql.Date sqlDate = cs.getDate(columnIndex);
// return sqlDate != null ? new Date(sqlDate.getTime()) : null;
}
}
ResponseBody返回值包裹ResultData
springbooot框架
有些后台接口会返回boolean,前台接收后无法直接使用res.message,或者res.data.所以需要通过ResponseBodyDataHandler 重新包装下返回值。
import com.example.demo.base.result.ResultData;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice
public class ResponseBodyDataHandler implements ResponseBodyAdvice<Object> {
private final Log logger = LogFactory.getLog(getClass());
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return returnType.getParameterType() != ResultData.class;
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (body instanceof ResultData) {
// 防止 ResponseEntity
return body;
} else if (body instanceof String) {
//处理返回值是String的情况
return ResultData.success(body);
}
return ResultData.success(body);
}
}
Vue+Webpack项目,游览器断点进入错误文件;vue 组件命名index.vue重名调试不进断点;(Webpack输出路径问题?热替换)
配置操作较复杂,留了些帖子防止以后乱找.
webpack之输出路径处理
webpack打包静态文件处理
vue + webpack调试(热替换)
浅谈vue+webpack项目调试方法
vue+webpack项目,chrome调试断点打不准的解决办法
vue 组件命名index.vue重名调试不进断点
vue开发中出现:
有index.vue同名页面6个(不同文件路径下)。
初始化页面就会自动断点到index6号文件的handleQuery方法(其实是断点行生效了,但是游览器没更新文件,导致无法正常调试)
调试了很久:(尝试修改webpack 输出路径,清除缓存,重装npm install等无效》》无耐)
生效代码如下
1.dev方式启动:
vue-cli2版本
config下index.js配置
/*关键代码节选*/
module.exports = {
dev: {
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
//devtool: 'cheap-module-eval-source-map',
devtool: 'cheap-module-source-map',
}
}
2.serve方式启动:
配置vue.config.js文件
process.env.NODE_ENV的名字可能是dev或者是devlopment,不确定可以在main.js里打印一遍
alert(process.env.NODE_ENV)
module.exports = {
publicPath:'/demo',
productionSourceMap: process.env.NODE_ENV === 'development',
configureWebpack: {
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : undefined
}
后台时间数据正确,但传到前端显示与后台相差8个小时
SpringBoot中对于@RestController或者@Controller+@ResponseBody注解的接口方法的返回值默认是Json格式,所以对于data类型的数据,在返回浏览器段被SpringBoot默认的JackJson框架转换,而JackSon框架默认的时区是GMT,相对于中国少了8个小时。
需要在application.properties添加配置
spring.jackson.date-format: yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
前台vue 后台springboot打包jar运行
vue打包npm build 可能存在问题
1.请求后台路径无响应(后台mapping找不到,vue配置路径代理表格)
需要根据实际运行效果进行调配路径
proxyTable:{
'/demo/admin': {
target: 'http://localhost:8089/demo/admin',//这里后台的地址模拟的;应该填写你们真实的后台接口
ws: true,
changeOrigin: true,//允许跨域
pathRewrite: {
'^/demo/admin': ''//请求的时候使用这个api就可以
}
},
2.vue请求后台路径异常,需要给axios请求前面加上后台项目路径【return axiosIns(“/demo”+url, 】
3.运行后请求静态文件图片或者elementui的组件文件找不到404。
vue webpack打包后图片、js、css路径错误的完美解决方法
实际项目中对应修改为
1.config/index.js 添加配置【build> assetsPublicPath: ‘./’,】
2.build/utils.js 添加配置【ExtractTextPlugin.extract > publicPath:‘…/…/’,】
4.jar运行
jar文件运行 java -jar ylt-0.0.1-SNAPSHOT.jar
路径请求 【http://localhost:8089/demo/dist/index.html#/index/alarmpage】
mvn install cmd安装包
mvn install:install-file -Dfile=jar包的位置(参数一) -DgroupId=groupId(参数二) -DartifactId=artifactId(参数三) -Dversion=version(参数四) -Dpackaging=jar
mvn install:install-file -Dmaven.repo.local=D:\apache-maven-3.6.3\repository -Dfile=D:\openhtmltopdf-pdfbox-1.0.8.jar -DgroupId=com.openhtmltopdf -DartifactId=openhtmltopdf-pdfbox -Dversion=1.0.8 -Dpackaging=jar
freemarker模板(生成doc)
分页符
<w:p ><w:r><w:br w:type="page" /></w:r></w:p>
HUtool 图片转Base64
String encodeA = Base64Encoder.encode(FileUtil.readBytes("D:\\1.png"));
System.out.println(encodeA);
页面前进、历史操作
history.back(-1):直接返回当前页的上一页,数据全部消息,是个新页面
history.go(-1):也是返回当前页的上一页,不过表单里的数据全部还在
history.back(1) 前进
history.back(-1) 后退
window.location.reload(); //刷新
window.history.go(1); //前进
window.history.go(-1); //返回+刷新
window.history.forward(); //前进
window.history.back(); //返回
<a href="javascript:void(0)">单击此处什么也不会发生</a>
ResultData包装类 返回string报错 ResultData cant cast to string
接口偶然返回string,导致异常,后来一点点断点发现异常在ResponseBodyDataHandler
StringHttpMessageConverter
包装类默认不会返回string,因为@Controller + return "index"就会导致跳转页面。
@Override
protected void addDefaultHeaders(HttpHeaders headers, String s, @Nullable MediaType type) throws IOException {
if (headers.getContentType() == null ) {
if (type != null && type.isConcrete() &&
(type.isCompatibleWith(MediaType.APPLICATION_JSON) ||
type.isCompatibleWith(APPLICATION_PLUS_JSON))) {
// Prevent charset parameter for JSON..
headers.setContentType(type);
}
}
super.addDefaultHeaders(headers, s, type);
}
登录cookie问题(springboot+vue,shiro设置)
问题出现的核心问题是vue在路径请求时没有以斜杠/开始,导请求错误404,302重定向
后台如果没有shiro,则不用配置
1.在application.yml中加入配置测试无效
server:
port: 8082
servlet:
context-path: /zhengzhi
session:
timeout: 7200 #会话超时。如果未指定持续时间后缀,则使用秒。
cookie:
path: /
2.在启动类上加上bean,启动过程证实会执行到代码,依旧无效
/**
* 配置保存sessionId的cookie
* 注意:这里的cookie 不是上面的记住我 cookie
* 记住我需要一个cookie session管理 也需要自己的cookie
* @return
*/
@Bean("sessionIdCookie")
public SimpleCookie sessionIdCookie(){
//这个参数是cookie的名称
SimpleCookie simpleCookie = new SimpleCookie("JSESSIONID");
//setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。
//它有以下特点:
//setcookie()的第七个参数
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setSecure(true);
simpleCookie.setPath("/");
//maxAge=-1表示浏览器关闭时失效此Cookie
simpleCookie.setMaxAge(-1);
return simpleCookie;
}
3.文件夹路径:shiro/ShiroConfig 重写文件,生效,
@Configuration
public class ShiroConfig {
//------------省略代码N行------------------
//自定义sessionManager
@Bean
public SessionManager sessionManager() {
ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
shiroSessionManager.setSessionIdUrlRewritingEnabled(false);
SimpleCookie simpleCookie = new SimpleCookie("JSESSIONID");
//setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。
//它有以下特点:
//setcookie()的第七个参数
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setSecure(false);
simpleCookie.setPath("/");
//maxAge=-1表示浏览器关闭时失效此Cookie
simpleCookie.setMaxAge(-1);
shiroSessionManager.setSessionIdCookie(simpleCookie);
return shiroSessionManager;
}
null空值判断
Optional.ofNullable(list).orElse(Lists.newArrayList());//jdk1.8 优雅判断null
缓存处理(项目加上版本号)
spring.resources.chain.strategy.fixed.enabled = true//是否开启内容版本策略,默认为false
spring.resources.chain.strategy.fixed.version = v20220608001 //指定版本策略使用的版本号
其余springboot配置
springboot配置socket io
深度覆盖框架css【深度选择器】
<style scoped>
/deep/ .el-son{
/* 要覆盖的样式 */
}
</style>
<style lang="scss" scoped>
>>>.el-badge__content.is-fixed.is-dot{
top:12px;
}
</style>
前端vue项目ip请求失败
有次项目在远程部署,http总是默认会转换为https
切换了nginx版本,修改vue项目路由模式,最后发现是index.html里有meta标签问题
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
服务器收到请求后会返回 “Content-Security-Policy: upgrade-insecure-requests” 头,告诉浏览器,可以把所属本站的所有
http 连接升级为 https 连接。
cmd 关闭exe
cmd查找 nginx
tasklist /fi "imagename eq nginx.exe"
cmd关闭 pid
taskkill -f -pid 22988