【Lingo Tom-公子小白 工作中关键代码收录 key code collections from work】

工作中代码收录 key code

关注点+开发步骤

内容关注点为高效编码,省出时间去happy or 自我提升。

开发顺序》最优路线
1.搞懂需求》脑海里有页面及交互(如果自己想的跟项目经理或甲方不同,早晚需要填坑,这个可根据时间要求进行权衡),可以在纸上画,用电脑画图工具画,用原型设计工具如墨刀,募客,axure之类的,个人喜欢募客经典版
2.设计数据库结构
3.代码生成简单逻辑代码
4.手动编写逻辑代码
5.测试(一般自己开发时就顺手搞了,有用户更佳)
6.打包上线测试/使用
文档准备(接口文档,使用说明书,测试报告,会议纪要,技术方案,开发计划书,数据库文档)

高级应召开发思想》直呼内行

  1. 复杂表实现?动态表头,动态层级设计》【表头】和【指标】分库保存,指标处理多层级,表头保存数据库字段名称+jsform初始化col用字段+排序,然后用高级mybatis组合每次查询的表头list,获取到动态结果值jsonObject。相关页面操作,导出word时可能比较痛苦,代码沉重。
  2. struct2+hibernate ssh框架开发》偶然见到老哥页面刷新方式:html body下全是一个form,form一提交就自动替换所有,妙哉(整半天找不到url)
  3. 原公司甲方突然有想法,在原来的公司列表下突出总公司》地区公司》分销点等多级结构,如何解决?直接扩展公司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

属性对照

BigDecimalfloat(5,2)分数30.00
floatdecimal(5,2)分数30.00
Stringvarchar(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,所以返回04.添加字段
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的值必须是顺序123...才会对应结果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图片预览(缩略图)

layer-photos-demo预览

//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页面引入效果

npm官网
socketjs 引入
stomjs引入

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,在insertFillupdateFill中自定义了数据格式(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重名调试不进断点

webpack 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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值