Commit 50692090 by zhangdaihao

Jeecg-Boot 2.1.0 版本发布,Online表单开发&在线代码生成器(迟到的版本)

parent 83935778
......@@ -6,7 +6,7 @@
Jeecg-Boot 快速开发平台(前后端分离版本)
===============
当前最新版本: 2.0.2(发布日期:20190708
当前最新版本: 2.1.0(发布日期:20190826
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-JEECG团队-orange.svg)](http://www.jeecg.com)
......@@ -239,7 +239,7 @@ Jeecg-Boot快速开发平台,可以应用在任何J2EE项目的开发中,尤
│ └─异常页面
│ └─个人页面
├─Online在线开发(暂未开源)
│ ├─Online在线表单
│ ├─Online在线表单 - 已开放功能
│ ├─Online在线图表
│ ├─Online图表模板配置
│ ├─Online在线报表
......
Ant Design Jeecg Vue
====
当前最新版本: 2.0.2(发布日期:20190708
当前最新版本: 2.1.0(发布日期:20190826
Overview
----
......
{
"name": "ant-design-vue-jeecg",
"version": "2.0.2",
"private": true,
"name": "vue-antd-jeecg",
"version": "2.1.0",
"private": false,
"scripts": {
"serve": "vue-cli-service serve --open",
"pre": "cnpm install || yarn --registry https://registry.npm.taobao.org || npm install --registry https://registry.npm.taobao.org ",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e"
"lint": "vue-cli-service lint"
},
"dependencies": {
"@antv/data-set": "^0.10.2",
"@jeecg/antd-onine": "^1.0.1",
"@tinymce/tinymce-vue": "^2.0.0",
"ant-design-vue": "^1.3.9",
"apexcharts": "^3.6.5",
......
......@@ -242,9 +242,10 @@
<!-- 全局配置 -->
<script>
window._CONFIG = {};
window._CONFIG['domianURL'] = 'http://localhost:8080/jeecg-boot';
window._CONFIG['imgDomainURL'] = 'http://localhost:8080/jeecg-boot/sys/common/view';
window._CONFIG['pdfDomainURL'] = 'http://localhost:8080/jeecg-boot/sys/common/pdf/pdfPreviewIframe';
window._CONFIG['domianURL'] = 'http://127.0.0.1:8080/jeecg-boot';
window._CONFIG['casPrefixUrl'] = 'http://cas.example.org:8443/cas';
window._CONFIG['imgDomainURL'] = window._CONFIG['domianURL'] + '/sys/common/view';
window._CONFIG['pdfDomainURL'] = window._CONFIG['domianURL'] + '/sys/common/pdf/pdfPreviewIframe';
</script>
</head>
......@@ -261,15 +262,6 @@
</div>
</div>
<!-- update_begin author:sunjianlei date:20190524 for: 去指定页面的加载动画 -->
<script>
// 去指定页面的加载动画
if (location.href.indexOf('online/desform/pureview') !== -1) {
document.getElementById('loader-wrapper').style.display = 'none'
}
</script>
<!-- update_end author:sunjianlei date:20190524 for: 去指定页面的加载动画 -->
</body>
</html>
\ No newline at end of file
......@@ -45,11 +45,11 @@ const getPermissionRuleList = (params)=>getAction("/sys/permission/getPermRuleLi
const queryPermissionRule = (params)=>getAction("/sys/permission/queryPermissionRule",params);
// 部门管理
const queryDepartTreeList = (params)=>getAction("/sysdepart/sysDepart/queryTreeList",params);
const queryIdTree = (params)=>getAction("/sysdepart/sysDepart/queryIdTree",params);
const queryParentName = (params)=>getAction("/sysdepart/sysDepart/queryParentName",params);
const searchByKeywords = (params)=>getAction("/sysdepart/sysDepart/searchBy",params);
const deleteByDepartId = (params)=>deleteAction("/sysdepart/sysDepart/delete",params);
const queryDepartTreeList = (params)=>getAction("/sys/sysDepart/queryTreeList",params);
const queryIdTree = (params)=>getAction("/sys/sysDepart/queryIdTree",params);
const queryParentName = (params)=>getAction("/sys/sysDepart/queryParentName",params);
const searchByKeywords = (params)=>getAction("/sys/sysDepart/searchBy",params);
const deleteByDepartId = (params)=>deleteAction("/sys/sysDepart/delete",params);
//日志管理
//const getLogList = (params)=>getAction("/sys/log/list",params);
......
import Vue from 'vue'
import { ACCESS_TOKEN } from "@/store/mutation-types"
import store from '@/store'
/**
* 单点登录
*/
const init = (callback) => {
console.log("-------单点登录开始-------");
let token = Vue.ls.get(ACCESS_TOKEN);
let st = getUrlParam("ticket");
var sevice = "http://"+window.location.host+"/";
if(token){
loginSuccess(callback);
}else{
if(st){
validateSt(st,sevice,callback);
}else{
var serviceUrl = encodeURIComponent(sevice);
window.location.href = window._CONFIG['casPrefixUrl']+"/login?service="+serviceUrl;
}
}
console.log("-------单点登录结束-------");
};
const SSO = {
init: init
};
function getUrlParam(paraName) {
var url = document.location.toString();
var arrObj = url.split("?");
if (arrObj.length > 1) {
var arrPara = arrObj[1].split("&");
var arr;
for (var i = 0; i < arrPara.length; i++) {
arr = arrPara[i].split("=");
if (arr != null && arr[0] == paraName) {
return arr[1];
}
}
return "";
}
else {
return "";
}
}
function validateSt(ticket,service,callback){
let params = {
ticket: ticket,
service:service
};
store.dispatch('ValidateLogin',params).then(res => {
//this.departConfirm(res)
if(res.success){
loginSuccess(callback);
}else{
var sevice = "http://"+window.location.host+"/";
var serviceUrl = encodeURIComponent(sevice);
window.location.href = window._CONFIG['casPrefixUrl']+"/login?service="+serviceUrl;
}
}).catch((err) => {
console.log(err);
//that.requestFailed(err);
});
}
function loginSuccess (callback) {
callback();
}
export default SSO;
\ No newline at end of file
......@@ -39,3 +39,5 @@ UserMenu.vue:首页右上侧的内容
![输入图片说明](https://static.oschina.net/uploads/img/201904/12201226_laQK.png "在这里输入图片标题")
####16.trend包 趋势显示组件(如下图)
![输入图片说明](https://static.oschina.net/uploads/img/201904/12201600_Wo8K.png "在这里输入图片标题")
![corn表达式](https://oscimg.oschina.net/oscnet/661f9ac09016395f9f49286143af3241623.jpg)
![corn控件添加清除按钮](https://oscimg.oschina.net/oscnet/15096e49f2e29bd829e304d56770025d03c.jpg)
\ No newline at end of file
......@@ -32,7 +32,7 @@
type="arc"
:zIndex="1"
:start="arcGuide2Start"
:end="getArcGuide2End"
:end="getArcGuide2End()"
:vStyle="arcGuide2Style"
></v-guide>
<v-guide
......@@ -88,7 +88,7 @@
}];
const data = [
{ value: 7.0 },
{ value: 0},
];
export default {
......@@ -96,7 +96,7 @@
props:{
datasource:{
type: Number,
default:7
default:0
},
title: {
type: String,
......
<template>
<div class="components-input-demo-presuffix">
<a-input @click="openModal" placeholder="corn表达式" v-model="cron" @change="handleOK">
<a-icon slot="prefix" type="schedule" title="corn控件"/>
<a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
</a-input>
<JCronModal ref="innerVueCron" :data="cron" @ok="handleOK"></JCronModal>
</div>
</template>
<script>
import JCronModal from "./modal/JCronModal";
export default {
name: 'JCron',
components: {
JCronModal
},
props: {
value: {
required: false,
type: String,
default:()=>{
return '* * * * * ? *'
}
}
},
data(){
return {
cron: this.value,
}
},
watch:{
value(val){
this.cron = val
}
},
methods:{
openModal(){
this.$refs.innerVueCron.show();
},
handleOK(val){
this.cron = val;
this.$emit("change", this.cron);
//this.$emit("change", Object.assign({}, this.cron));
},
handleEmpty(){
this.handleOK('')
}
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
<style scoped>
.components-input-demo-presuffix .anticon-close-circle {
cursor: pointer;
color: #ccc;
transition: color 0.3s;
font-size: 12px;
}
.components-input-demo-presuffix .anticon-close-circle:hover {
color: #f5222d;
}
.components-input-demo-presuffix .anticon-close-circle:active {
color: #666;
}
</style>
<!-- JEditableTable -->
<!-- @version 1.4.2 -->
<!-- @version 1.4.4 -->
<!-- @author sjlei -->
<template>
<a-spin :spinning="loading">
......@@ -26,6 +26,9 @@
<div class="thead" ref="thead">
<div class="tr" :style="{width: this.realTrWidth}">
<!-- 左侧固定td -->
<div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
<span></span>
</div>
<div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
<!--:indeterminate="true"-->
<a-checkbox
......@@ -62,6 +65,8 @@
<div v-if="rows.length===0" class="tr-nodata">
<span>暂无数据</span>
</div>
<draggable v-model="rows" handle=".td-ds-icons" @end="handleDragMoveEnd">
<!-- 动态生成tr -->
<template v-for="(row,rowIndex) in rows">
<!-- tr 只加载可见的和预加载的总共十条数据 -->
......@@ -77,6 +82,14 @@
:style="buildTrStyle(rowIndex)"
:key="row.id">
<!-- 左侧固定td -->
<div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
<div class="td-ds-icons">
<a-icon type="align-left"/>
<a-icon type="align-right"/>
</div>
</div>
<div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
<!-- 此 v-for 只是为了拼接 id 字符串 -->
<template v-for="(id,i) in [`${row.id}`]">
......@@ -252,6 +265,7 @@
<div v-else-if="col.type === formTypes.slot" :key="i">
<slot
:name="(col.slot || col.slotName) || col.key"
:index="rowIndex"
:text="inputValues[rowIndex][col.key]"
:column="col"
:rowId="removeCaseId(row.id)"
......@@ -268,7 +282,7 @@
<!-- -- tr end -- -->
</template>
</draggable>
</div>
</div>
......@@ -278,6 +292,7 @@
<script>
import Vue from 'vue'
import Draggable from 'vuedraggable'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { FormTypes, VALIDATE_NO_PASSED } from '@/utils/JEditableTableUtil'
import { cloneObject, randomString } from '@/utils/util'
......@@ -289,7 +304,7 @@
export default {
name: 'JEditableTable',
components: { JDate },
components: { JDate, Draggable },
props: {
// 列信息
columns: {
......@@ -338,7 +353,16 @@
disabled: {
type: Boolean,
default: false
}
},
// 是否可拖拽排序
dragSort: {
type: Boolean,
default: false
},
dragSortKey: {
type: String,
default: 'orderNum'
},
},
data() {
return {
......@@ -354,7 +378,8 @@
// 'max-height': '400px'
tbody: { left: '0px' },
// 左侧固定td的style
tdLeft: { 'min-width': '4%', 'max-width': '45px' }
tdLeft: { 'min-width': '4%', 'max-width': '45px' },
tdLeftDs: { 'min-width': '30px', 'max-width': '35px' },
},
// 表单的类型
formTypes: FormTypes,
......@@ -455,7 +480,9 @@
},
// 侦听器
watch: {
dataSource: function (newValue) {
dataSource: {
immediate: true,
handler: function (newValue) {
this.initialize()
let rows = []
......@@ -538,6 +565,7 @@
this.updateFormValues()
})
}
},
columns: {
immediate: true,
......@@ -722,6 +750,12 @@
this.selectValues = selectValues
this.jdateValues = jdateValues
if (this.dragSort) {
this.inputValues.forEach((item, index) => {
item[this.dragSortKey] = (index + 1)
})
}
if (update) {
this.rows = rows
this.$nextTick(() => {
......@@ -751,6 +785,7 @@
},
/** 添加一行 */
add(num = 1, forceScrollToBottom = false) {
if (num < 1) return
// let timestamp = new Date().getTime()
let rows = this.rows
let row
......@@ -1230,6 +1265,31 @@
}
},
/** 拖动结束,交换inputValue中的值 */
handleDragMoveEnd(event) {
let { oldIndex, newIndex } = event
let values = this.inputValues
// 存储旧数据,并删除旧项目
let temp = values[oldIndex]
values.splice(oldIndex, 1)
// 向新项目里添加旧数据
values.splice(newIndex, 0, temp)
values.forEach((item, index) => {
item[this.dragSortKey] = (index + 1)
})
this.forceUpdateFormValues()
// 触发已拖动事件
this.$emit('dragged', {
oldIndex,
newIndex,
target: this
})
},
/* --- common function begin --- */
/** 鼠标移入 */
......@@ -1355,7 +1415,12 @@
_loadDictConcatToOptions(column) {
initDictOptions(column.dictCode).then((res) => {
if (res.success) {
column.options = (column.options || []).concat(res.result)
let newOptions = (column.options || [])// .concat(res.result)
res.result.forEach(item => {
for (let option of newOptions) if (option.value === item.value) return
newOptions.push(item)
})
column.options = newOptions
} else {
console.group(`JEditableTable 查询字典(${column.dictCode})发生异常`)
console.log(res.message)
......@@ -1377,6 +1442,8 @@
/** 辅助方法:指定a-select 和 j-data 的父容器 */
getParentContainer(node) {
let element = (() => {
// nodeType 8 : Comment : 注释
if (this.$el && this.$el.nodeType !== 8) {
return this.$el
}
......@@ -1385,6 +1452,36 @@
return doc
}
return node.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
})()
// 递归判断是否带有 overflow: hidden;的父元素
const ifParent = (child) => {
let currentOverflow = null
if (child['currentStyle']) {
currentOverflow = child['currentStyle']['overflow']
} else if (window.getComputedStyle) {
currentOverflow = window.getComputedStyle(child)['overflow']
}
if (currentOverflow != null) {
if (currentOverflow === 'hidden') {
// 找到了带有 hidden 的标签,判断它的父级是否还有 hidden,直到遇到完全没有 hidden 或 body 的时候才停止递归
let temp = ifParent(child.parentNode)
return temp != null ? temp : child.parentNode
} else
// 当前标签没有 hidden ,如果有父级并且父级不是 body 的话就继续递归判断父级
if (child.parentNode && child.parentNode.tagName.toLocaleLowerCase() !== 'body') {
return ifParent(child.parentNode)
} else {
// 直到 body 都没有遇到有 hidden 的标签
return null
}
} else {
return child
}
}
let temp = ifParent(element)
return (temp != null) ? temp : element
},
/** 辅助方法:替换${...}变量 */
......@@ -1440,6 +1537,9 @@
props['showSearch'] = true
}
// 判断是否是禁用的列
props['disabled'] = !!col['disabled']
// 判断是否为禁用的行
if (props['disabled'] !== true) {
props['disabled'] = ((this.disabledRowIds || []).indexOf(row.id) !== -1)
......@@ -1515,6 +1615,38 @@
align-items: center;
}
&.td-ds {
margin-right: 0;
padding-left: 0;
padding-right: 0;
justify-content: center;
align-items: center;
.td-ds-icons {
position: relative;
cursor: move;
width: 100%;
/*padding: 25% 0;*/
height: 100%;
.anticon-align-left,
.anticon-align-right {
position: absolute;
top: 30%;
}
.anticon-align-left {
left: 25%;
}
.anticon-align-right {
right: 25%;
}
}
}
}
}
......
......@@ -84,7 +84,7 @@
},
watch: {
value(newValue) {
this.myValue = newValue
this.myValue = (newValue == null ? '' : newValue)
},
myValue(newValue) {
console.log(newValue)
......
......@@ -3,13 +3,15 @@
## 参数配置
| 参数 | 类型 | 必填 | 说明 |
|--------------|---------|------|----------------------------------------------------------------|
|--------------|---------|------|---------------------------------------------------------------------------------|
| columns | array | ✔️ | 表格列的配置描述,具体项见下表 |
| dataSource | array | ✔️ | 表格数据 |
| loading | boolean | | 是否正在加载,加载中不会显示任何行,默认false |
| actionButton | boolean | | 是否显示操作按钮,包括"新增"、"删除",默认false |
| rowNumber | boolean | | 是否显示行号,默认false |
| rowSelection | boolean | | 是否可选择行,默认false |
| dragSort | boolean | | 是否可拖动排序,默认false |
| dragSortKey | string | | 拖动排序存储的Key,无需定义在columns内也能在getValues()时获取到值,默认orderNum |
| maxHeight | number | | 设定最大高度(px),默认400 |
| disabledRows | object | | 设定禁用的行,被禁用的行无法被选择和编辑,配置方法可以查看示例 |
| disabled | boolean | | 是否禁用所有行,默认false |
......@@ -17,7 +19,7 @@
### columns 参数详解
| 参数 | 类型 | 必填 | 说明 |
|---------------|--------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
|---------------|---------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| title | string | ✔️ | 表格列头显示的问题 |
| key | string | ✔️ | 列数据在数据项中对应的 key,必须是唯一的 |
| type | string | ✔️ | 表单的类型,可以通过`JEditableTableUtil.FormTypes`赋值 |
......@@ -26,6 +28,7 @@
| defaultValue | string | | 默认值,在新增一行时生效 |
| validateRules | array | | 表单验证规则,配置方式见[validateRules 配置规则](#validaterules-配置规则) |
| props | object | | 设置添加给表单元素的自定义属性,例如:`props:{title: 'show title'}` |
| disabled | boolean | | 是否禁用当前列,默认false |
#### 当 type=checkbox 时所需的参数
......@@ -40,7 +43,7 @@
|------------|---------|------|----------------------------------------------------|
| options | array | ✔️ | 下拉选项列表,详见下表 |
| allowInput | boolean | | 是否允许用户输入内容,并创建新的内容 |
| dictCode | String | | 数据字典Code,若options也有值,则拼接options后面 |
| dictCode | String | | 数据字典Code,若options也有值,则拼接options后面 |
##### options 所需参数
......@@ -76,10 +79,11 @@
## 事件
| 事件名 | 触发时机 | 参数 |
|-----------------|----------------------------------------------------|-------------------------------|
|-----------------|----------------------------------------------------|--------------------------------------------------|
| added | 当添加行操作完成后触发 | |
| deleted | 当删除行操作完成后触发(批量删除操作只会触发一次) | `deleteIds` 被逻辑删除的id |
| selectRowChange | 当行被选中或取消选中时触发 | `selectedRowIds` 被选中行的id |
| valueChange | 当数据发生改变的时候触发的事件 | `{ type, row, column, value, target }` Event对象 |
## 方法
......@@ -490,6 +494,7 @@ this.$refs.editableTable.getValues((error, values) => {
/* a 标签的点击事件,删除当前选中的行 */
handleDelete(props) {
// 参数解释
// props.index :当前行的下标
// props.text :当前值,可能是defaultValue定义的值,也可能是从dataSource中取出的值
// props.rowId :当前选中行的id,如果是新增行则是临时id
// props.column :当前操作的列
......
......@@ -58,6 +58,16 @@
watch:{
departId(){
this.initDepartComponent()
},
visible: {
handler() {
if (this.departId) {
this.checkedKeys = this.departId.split(",");
console.log('this.departId', this.departId)
} else {
this.checkedKeys = [];
}
}
}
},
methods:{
......
......@@ -148,11 +148,28 @@
<style lang="scss">
.ant-menu.ant-menu-root {
& > .ant-menu-item:first-child {
background-color: white;
background-color: transparent;
& > a, & > a:hover {
color: rgba(0, 0, 0, 0.65);
}
&.ant-menu-item-selected {
& > a, & > a:hover {
color: #1890ff;
}
}
}
&.ant-menu-dark > .ant-menu-item:first-child {
& > a, & > a:hover {
color: rgba(255, 255, 255, 0.65);
}
&.ant-menu-item-selected {
& > a, & > a:hover {
color: rgba(255, 255, 255, 1);
}
}
}
}
......
......@@ -153,9 +153,10 @@
</a-alert>
</div>
</div>
<div class="setting-drawer-index-handle" @click="toggle">
<a-icon type="setting" v-if="!visible"/>
<a-icon type="close" v-else/>
<div class="setting-drawer-index-handle" @click="toggle" v-if="visible">
<!-- <a-icon type="setting" v-if="!visible"/>-->
<!-- <a-icon type="close" v-else/>-->
<a-icon type="close" />
</div>
</a-drawer>
</div>
......
<template>
<a-modal
:title="title"
:title="currTitle"
:width="450"
:visible="visible"
:closable="false"
......@@ -41,6 +41,9 @@
<script>
import { getAction,putAction } from '@/api/manage'
import Vue from 'vue'
import store from '@/store/'
import { USER_INFO } from "@/store/mutation-types"
export default {
name: 'DepartSelect',
......@@ -70,6 +73,7 @@
},
data(){
return {
currTitle:this.title,
visible:false,
departList:[],
departSelected:"",
......@@ -100,7 +104,7 @@
this.departSelected = orgCode
this.departList = departs
if(this.currDepartName){
this.title ="部门切换(当前部门 : "+this.currDepartName+")"
this.currTitle ="部门切换(当前部门 : "+this.currDepartName+")"
}
}
......@@ -122,6 +126,10 @@
}
putAction("/sys/selectDepart",obj).then(res=>{
if(res.success){
const userInfo = res.result.userInfo;
Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000);
store.commit('SET_INFO', userInfo);
//console.log("---切换组织机构---userInfo-------",store.getters.userInfo.orgCode);
this.departClear()
}
})
......
......@@ -78,6 +78,7 @@
<script>
import { getAction,putAction } from '@/api/manage'
import ShowAnnouncement from './ShowAnnouncement'
import store from '@/store/'
export default {
name: "HeaderNotice",
......@@ -89,7 +90,8 @@
loadding: false,
url:{
listCementByUser:"/sys/annountCement/listByUser",
editCementSend:"/system/sysAnnouncementSend/editByAnntIdAndUserId",
editCementSend:"/sys/sysAnnouncementSend/editByAnntIdAndUserId",
queryById:"/sys/annountCement/queryById",
},
hovered: false,
announcement1:[],
......@@ -98,6 +100,7 @@
msg2Count:"0",
msg1Title:"通知(3)",
msg2Title:"",
stopTimer:false,
}
},
computed:{
......@@ -105,15 +108,25 @@
return parseInt(this.msg1Count)+parseInt(this.msg2Count);
}
},
created() {
mounted() {
this.loadData();
this.timer();
//this.timerFun();
this.initWebSocket();
},
destroyed: function () { // 离开页面生命周期函数
this.websocketclose();
},
methods: {
timer() {
return setInterval(()=>{
timerFun() {
this.stopTimer = false;
let myTimer = setInterval(()=>{
// 停止定时器
if (this.stopTimer == true) {
clearInterval(myTimer);
return;
}
this.loadData()
},60000)
},6000)
},
loadData (){
try {
......@@ -127,8 +140,14 @@
this.msg2Count = res.result.sysMsgTotal;
this.msg2Title = "系统消息(" + res.result.sysMsgTotal + ")";
}
}).catch(error => {
console.log("系统消息通知异常",error);//这行打印permissionName is undefined
this.stopTimer = true;
console.log("清理timer");
});
} catch (err) {
this.stopTimer = true;
console.log("通知异常",err);
}
},
fetchNotice () {
......@@ -163,6 +182,74 @@
this.hovered = visible;
},
initWebSocket: function () {
// WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
var userId = store.getters.userInfo.id;
var url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://")+"/websocket/"+userId;
//console.log(url);
this.websock = new WebSocket(url);
this.websock.onopen = this.websocketonopen;
this.websock.onerror = this.websocketonerror;
this.websock.onmessage = this.websocketonmessage;
this.websock.onclose = this.websocketclose;
},
websocketonopen: function () {
console.log("WebSocket连接成功");
},
websocketonerror: function (e) {
console.log("WebSocket连接发生错误");
},
websocketonmessage: function (e) {
console.log("-----接收消息-------",e.data);
var data = eval("(" + e.data + ")"); //解析对象
this.loadData();
//if(data.cmd == "topic"){
//系统通知
this.openNotification(data);
//}else if(data.cmd == "user"){
//用户消息
// this.openNotification(data);
//}
},
websocketclose: function (e) {
console.log("connection closed (" + e.code + ")");
},
openNotification (data) {
var text = data.msgTxt;
const key = `open${Date.now()}`;
this.$notification.open({
message: '消息提醒',
placement:'bottomRight',
description: text,
key,
btn: (h)=>{
return h('a-button', {
props: {
type: 'primary',
size: 'small',
},
on: {
click: () => this.showDetail(key,data)
}
}, '查看详情')
},
});
},
showDetail(key,data){
this.$notification.close(key);
var id = data.msgId;
getAction(this.url.queryById,{id:id}).then((res) => {
if (res.success) {
var record = res.result;
this.showAnnouncement(record);
}
})
},
}
}
</script>
......
<template>
<div class="logo">
<router-link :to="{name:'dashboard'}">
<img src="~@/assets/logo.svg" alt="logo">
<!-- update-begin- author:sunjianlei --- date:20190814 --- for: logo颜色根据主题颜色变化 -->
<img v-if="navTheme === 'dark'" src="~@/assets/logo-white.png" alt="logo">
<img v-else src="~@/assets/logo.svg" alt="logo">
<!-- update-begin- author:sunjianlei --- date:20190814 --- for: logo颜色根据主题颜色变化 -->
<h1 v-if="showTitle">{{ title }}</h1>
</router-link>
</div>
</template>
<script>
import { mixin } from '@/utils/mixin.js'
export default {
name: 'Logo',
mixins: [mixin],
props: {
title: {
type: String,
......
<template>
<a-modal
width="60%"
class="announcementCustomModal"
:width="modelStyle.width"
:visible="visible"
:bodyStyle ="bodyStyle"
@cancel="handleCancel"
destroyOnClose
:footer="null">
<a-card style="width: 100%;height: 100%" class="daily-article" :loading="loading">
<template slot="title">
<a-button icon="fullscreen" class="custom-btn" @click="handleClickToggleFullScreen"/>
</template>
<a-card class="daily-article" :loading="loading">
<a-card-meta
:title="record.titile"
:description="'发布人:'+record.sender + ' 发布时间: ' + record.sendTime"/>
:description="'发布人:'+record.sender + ' 发布时间: ' + record.sendTime">
</a-card-meta>
<a-divider />
<span v-html="record.msgContent" class="article-content"></span>
</a-card>
</a-modal>
</template>
......@@ -40,8 +43,14 @@
bodyStyle:{
padding: "0",
height:(window.innerHeight*0.8)+"px",
"overflow-y":"auto"
"overflow-y":"auto",
},
modelStyle:{
width: '60%',
style: { top: '20px' },
fullScreen: false
}
}
},
created () {
......@@ -54,10 +63,45 @@
handleCancel () {
this.visible = false;
},
/** 切换全屏显示 */
handleClickToggleFullScreen() {
let mode = !this.modelStyle.fullScreen
if (mode) {
this.modelStyle.width = '100%'
this.modelStyle.style.top = '20px'
} else {
this.modelStyle.width = '60%'
this.modelStyle.style.top = '50px'
}
this.modelStyle.fullScreen = mode
}
}
}
</script>
<style lang="less">
.announcementCustomModal{
.ant-modal-header {
border: none;
display: inline-block;
position: absolute;
z-index: 1;
right: 56px;
padding: 0;
.ant-modal-title{
.custom-btn{
width: 56px;
height: 56px;
border: none;
box-shadow: none;
}
}
}
.daily-article{
border-bottom: 0;
}
}
</style>
<style scoped lang="less">
.daily-article {
.article-button {
......
......@@ -24,11 +24,15 @@
<span>账户设置</span>
</router-link>
</a-menu-item>
<a-menu-item key="2" @click="updatePassword">
<a-menu-item key="3" @click="systemSetting">
<a-icon type="tool"/>
<span>系统设置</span>
</a-menu-item>
<a-menu-item key="4" @click="updatePassword">
<a-icon type="setting"/>
<span>密码修改</span>
</a-menu-item>
<a-menu-item key="3" @click="updateCurrentDepart">
<a-menu-item key="5" @click="updateCurrentDepart">
<a-icon type="cluster"/>
<span>切换部门</span>
</a-menu-item>
......@@ -53,12 +57,14 @@
</span>
<user-password ref="userPassword"></user-password>
<depart-select ref="departSelect" :closable="true" title="部门切换"></depart-select>
<setting-drawer ref="settingDrawer" :closable="true" title="系统设置"></setting-drawer>
</div>
</template>
<script>
import HeaderNotice from './HeaderNotice'
import UserPassword from './UserPassword'
import SettingDrawer from "@/components/setting/SettingDrawer";
import DepartSelect from './DepartSelect'
import { mapActions, mapGetters } from 'vuex'
import { mixinDevice } from '@/utils/mixin.js'
......@@ -69,7 +75,8 @@
components: {
HeaderNotice,
UserPassword,
DepartSelect
DepartSelect,
SettingDrawer
},
props: {
theme: {
......@@ -112,6 +119,9 @@
},
updateCurrentDepart(){
this.$refs.departSelect.show()
},
systemSetting(){
this.$refs.settingDrawer.showDrawer()
}
}
}
......
......@@ -323,30 +323,6 @@ export const constantRouterMap = [
]
},
// {
// path: '/',
// name: 'index',
// component: TabLayout,
// meta: {title: '首页'},
// redirect: '/dashboard/workplace',
// children: [
// {
// path: '/online',
// name: 'online',
// redirect: '/online',
// component: RouteView,
// meta: {title: '在线开发', icon: 'dashboard', permission: ['dashboard']},
// children: [
// {
// path: '/online/auto/:code',
// name: 'report',
// component: () => import('@/views/modules/online/cgreport/OnlCgreportAutoList')
// },
// ]
// },
// ]
// },
{
path: '/test',
component: BlankLayout,
......
......@@ -18,6 +18,9 @@ import VueApexCharts from 'vue-apexcharts'
import preview from 'vue-photo-preview'
import 'vue-photo-preview/dist/skin.css'
import "@jeecg/antd-onine"
import '@jeecg/antd-onine/dist/OnlineForm.css'
import {
ACCESS_TOKEN,
......
......@@ -3,6 +3,7 @@ import { login, logout, phoneLogin } from "@/api/login"
import { ACCESS_TOKEN, USER_NAME,USER_INFO,USER_AUTH,SYS_BUTTON_AUTH } from "@/store/mutation-types"
import { welcome } from "@/utils/util"
import { queryPermissionsByUser } from '@/api/api'
import { getAction } from '@/api/manage'
const user = {
state: {
......@@ -36,6 +37,30 @@ const user = {
},
actions: {
// CAS验证登录
ValidateLogin({ commit }, userInfo) {
return new Promise((resolve, reject) => {
getAction("/cas/client/validateLogin",userInfo).then(response => {
console.log("----cas 登录--------",response);
if(response.success){
const result = response.result
const userInfo = result.userInfo
Vue.ls.set(ACCESS_TOKEN, result.token, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_NAME, userInfo.username, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000)
commit('SET_TOKEN', result.token)
commit('SET_INFO', userInfo)
commit('SET_NAME', { username: userInfo.username,realname: userInfo.realname, welcome: welcome() })
commit('SET_AVATAR', userInfo.avatar)
resolve(response)
}else{
resolve(response)
}
}).catch(error => {
reject(error)
})
})
},
// 登录
Login({ commit }, userInfo) {
return new Promise((resolve, reject) => {
......@@ -115,6 +140,9 @@ const user = {
Vue.ls.remove(ACCESS_TOKEN)
//console.log('logoutToken: '+ logoutToken)
logout(logoutToken).then(() => {
//var sevice = "http://"+window.location.host+"/";
//var serviceUrl = encodeURIComponent(sevice);
//window.location.href = window._CONFIG['casPrefixUrl']+"/logout?service="+serviceUrl;
resolve()
}).catch(() => {
resolve()
......
......@@ -78,11 +78,13 @@ service.interceptors.request.use(config => {
config.headers[ 'X-Access-Token' ] = token // 让每个请求携带自定义 token 请根据实际情况自行修改
}
if(config.method=='get'){
if(config.url.indexOf("sys/dict/getDictItems")<0){
config.params = {
_t: Date.parse(new Date())/1000,
...config.params
}
}
}
return config
},(error) => {
return Promise.reject(error)
......
......@@ -233,3 +233,22 @@ export function showDealBtn(bpmStatus){
}
return false;
}
/**
* 增强CSS,可以在页面上输出全局css
* @param css 要增强的css
* @param id style标签的id,可以用来清除旧样式
*/
export function cssExpand(css, id) {
let style = document.createElement('style')
style.type = "text/css"
style.innerHTML = `@charset "UTF-8"; ${css}`
// 清除旧样式
if (id) {
let $style = document.getElementById(id)
if ($style != null) $style.outerHTML = ''
style.id = id
}
// 应用新样式
document.head.appendChild(style)
}
\ No newline at end of file
......@@ -49,7 +49,7 @@
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="选择部门">
<j-select-depart v-model="departId"></j-select-depart>
<j-select-depart v-model="departId" :multi="true"></j-select-depart>
</a-form-item>
</a-col>
<a-col :span="12">选中的部门ID(v-model):{{ departId }}</a-col>
......@@ -217,10 +217,8 @@
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="cron表达式">
<a-input @click="openModal" placeholder="corn表达式" v-model="cron.label" readOnly >
<a-icon slot="prefix" type="schedule" title="corn控件"/>
</a-input>
<VueCron ref="innerVueCron" :data="cron" @change="changeCron" ></VueCron>
<j-cron ref="innerVueCron" v-decorator="['cronExpression', {'initialValue':'0/1 * * * * ?'}]" @change="setCorn"></j-cron>
<!-- <j-cron ref="innerVueCron" v-model="cron" @change="setCorn"></j-cron>-->
</a-form-item>
</a-col>
</a-row>
......@@ -245,7 +243,7 @@
import JSlider from '@/components/jeecg/JSlider'
import JSelectMultiple from '@/components/jeecg/JSelectMultiple'
import JTreeDict from "../../components/jeecg/JTreeDict.vue";
import VueCron from "./modules/VueCronModal.vue";
import JCron from "@/components/jeecg/JCron.vue";
export default {
name: 'SelectDemo',
components: {
......@@ -257,7 +255,7 @@
JCheckbox,
JCodeEditor,
JDate, JEditor, JEllipsis, JGraphicCode, JSlider, JSelectMultiple,
VueCron
JCron
},
data() {
return {
......@@ -314,10 +312,7 @@ sayHi('hello, world!')`
style: { top: '20px' },
fullScreen: true
},
cron: {
label: '',
value: {}
}
cron: '',
}
},
computed: {
......@@ -372,14 +367,12 @@ sayHi('hello, world!')`
}
this.modal.fullScreen = mode
},
openModal(){
this.$refs.innerVueCron.show()
},
changeCron(val){
this.cron=val;
console.log(val);
}
setCorn(data){
this.$nextTick(() => {
this.form.cronExpression = data;
})
}
}
}
</script>
......
<template>
<a-card :bordered="false">
<a-table
rowKey="id"
bordered
:columns="columns"
:dataSource="dataSource"
:pagination="false"
>
</a-table>
</a-card>
</template>
<script>
export default {
name: 'TableTotal',
data() {
return {
columns: [
{
title: '#',
width: '180px',
align: 'center',
dataIndex: 'rowIndex',
customRender: function (text, r, index) {
return (text !== '合计') ? (parseInt(index) + 1) : text
}
},
{
title: '姓名',
dataIndex: 'name',
},
{
title: '贡献点',
dataIndex: 'point',
},
{
title: '等级',
dataIndex: 'level',
},
{
title: '更新时间',
dataIndex: 'updateTime',
},
],
dataSource: [
{ name: '张三', point: 23, level: 3, updateTime: '2019-8-14' },
{ name: '小王', point: 6, level: 1, updateTime: '2019-8-13' },
{ name: '李四', point: 53, level: 8, updateTime: '2019-8-12' },
{ name: '小红', point: 44, level: 5, updateTime: '2019-8-11' },
{ name: '王五', point: 97, level: 10, updateTime: '2019-8-10' },
{ name: '小明', point: 33, level: 2, updateTime: '2019-8-10' },
]
}
},
mounted() {
this.tableAddTotalRow(this.columns, this.dataSource)
},
methods: {
/** 表格增加合计行 */
tableAddTotalRow(columns, dataSource) {
let numKey = 'rowIndex'
let totalRow = { [numKey]: '合计' }
columns.forEach(column => {
let { key, dataIndex } = column
if (![key, dataIndex].includes(numKey)) {
let total = 0
dataSource.forEach(data => {
total += /^\d+\.?\d?$/.test(data[dataIndex]) ? Number.parseInt(data[dataIndex]) : Number.NaN
console.log(data[dataIndex], ':', (/^\d+\.?\d?$/.test(data[dataIndex]) ? Number.parseInt(data[dataIndex]) : Number.NaN))
})
if (Number.isNaN(total)) {
total = '-'
}
totalRow[dataIndex] = total
}
})
dataSource.push(totalRow)
}
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<div>
<a-button @click="handleTableCheck" type="primary">表单验证</a-button>
<span style="padding-left:8px;"></span>
<a-tooltip placement="top" title="获取值,忽略表单验证" :autoAdjustOverflow="true">
<a-button @click="handleTableGet" type="primary">获取值</a-button>
</a-tooltip>
<span style="padding-left:8px;"></span>
<a-tooltip placement="top" title="模拟加载1000条数据" :autoAdjustOverflow="true">
<a-button @click="handleTableSet" type="primary">设置值</a-button>
</a-tooltip>
<j-editable-table
ref="editableTable"
:loading="loading"
:columns="columns"
:dataSource="dataSource"
:rowNumber="true"
:rowSelection="true"
:actionButton="true"
:dragSort="true"
style="margin-top: 8px;"
@selectRowChange="handleSelectRowChange">
<template v-slot:action="props">
<a @click="handleDelete(props)">{{ props.text }}</a>
</template>
</j-editable-table>
</div>
</template>
<script>
import moment from 'moment'
import { FormTypes } from '@/utils/JEditableTableUtil'
import { randomUUID, randomNumber } from '@/utils/util'
import JEditableTable from '@/components/jeecg/JEditableTable'
export default {
name: 'DefaultTable',
components: { JEditableTable },
data() {
return {
loading: false,
columns: [
{
title: '字段名称',
key: 'dbFieldName',
// width: '19%',
width: '300px',
type: FormTypes.input,
defaultValue: '',
placeholder: '请输入${title}',
validateRules: [
{
required: true, // 必填
message: '请输入${title}' // 显示的文本
},
{
pattern: /^[a-z|A-Z][a-z|A-Z\d_-]{0,}$/, // 正则
message: '${title}必须以字母开头,可包含数字、下划线、横杠'
}
]
},
{
title: '文件域',
key: 'upload',
type: FormTypes.upload,
// width: '19%',
width: '300px',
placeholder: '点击上传',
token: true,
responseName: 'message',
action: window._CONFIG['domianURL'] + '/sys/common/upload'
},
{
title: '字段类型',
key: 'dbFieldType',
// width: '18%',
width: '300px',
type: FormTypes.select,
options: [ // 下拉选项
{ title: 'String', value: 'string' },
{ title: 'Integer', value: 'int' },
{ title: 'Double', value: 'double' },
{ title: 'Boolean', value: 'boolean' }
],
allowInput: true,
defaultValue: '',
placeholder: '请选择${title}',
validateRules: [{ required: true, message: '请选择${title}' }]
},
{
title: '性别(字典)',
key: 'sex_dict',
width: '300px',
type: FormTypes.select,
options: [],
dictCode: 'sex',
placeholder: '请选择${title}',
validateRules: [{ required: true, message: '请选择${title}' }]
},
{
title: '多选测试',
key: 'multipleSelect',
// width: '18%',
width: '300px',
type: FormTypes.select,
props: { 'mode': 'multiple' }, // 支持多选
options: [
{ title: 'String', value: 'string' },
{ title: 'Integer', value: 'int' },
{ title: 'Double', value: 'double' },
{ title: 'Boolean', value: 'boolean' }
],
defaultValue: ['int', 'boolean'], // 多个默认项
// defaultValue: 'string,double,int', // 也可使用这种方式
placeholder: '这里可以多选',
validateRules: [{ required: true, message: '请选择${title}' }]
},
{
title: '字段长度',
key: 'dbLength',
// width: '8%',
width: '100px',
type: FormTypes.inputNumber,
defaultValue: 32,
placeholder: '${title}',
validateRules: [{ required: true, message: '请输入${title}' }]
},
{
title: '日期',
key: 'datetime',
// width: '22%',
width: '320px',
type: FormTypes.datetime,
defaultValue: '2019-4-30 14:52:22',
placeholder: '请选择${title}',
validateRules: [{ required: true, message: '请选择${title}' }]
},
{
title: '可以为空',
key: 'isNull',
// width: '8%',
width: '100px',
type: FormTypes.checkbox,
customValue: ['Y', 'N'], // true ,false
defaultChecked: false
},
{
title: '操作',
key: 'action',
// width: '8%',
width: '100px',
type: FormTypes.slot,
slotName: 'action',
defaultValue: '删除'
}
],
dataSource: [],
selectedRowIds: []
}
},
mounted() {
this.randomData(23, false)
},
methods: {
/** 表单验证 */
handleTableCheck() {
this.$refs.editableTable.getValues((error) => {
if (error === 0) {
this.$message.success('验证通过')
} else {
this.$message.error('验证未通过')
}
})
},
/** 获取值,忽略表单验证 */
handleTableGet() {
this.$refs.editableTable.getValues((error, values) => {
console.log('values:', values)
}, false)
console.log('deleteIds:', this.$refs.editableTable.getDeleteIds())
this.$message.info('获取值成功,请看控制台输出')
},
/** 模拟加载1000条数据 */
handleTableSet() {
this.randomData(1000, true)
},
handleSelectRowChange(selectedRowIds) {
this.selectedRowIds = selectedRowIds
},
/* 随机生成数据 */
randomData(size, loading = false) {
if (loading) {
this.loading = true
}
let randomDatetime = () => {
let time = parseInt(randomNumber(1000, 9999999999999))
return moment(new Date(time)).format('YYYY-MM-DD HH:mm:ss')
}
let begin = Date.now()
let values = []
for (let i = 0; i < size; i++) {
values.push({
id: randomUUID(),
dbFieldName: `name_${i + 1}`,
// dbFieldTxt: randomString(10),
multipleSelect: ['string', ['int', 'double', 'boolean'][randomNumber(0, 2)]],
dbFieldType: ['string', 'int', 'double', 'boolean'][randomNumber(0, 3)],
dbLength: randomNumber(0, 233),
datetime: randomDatetime(),
isNull: ['Y', 'N'][randomNumber(0, 1)]
})
}
this.dataSource = values
let end = Date.now()
let diff = end - begin
if (loading && diff < size) {
setTimeout(() => {
this.loading = false
}, size - diff)
}
},
handleDelete(props) {
let { rowId, target } = props
target.removeRows(rowId)
}
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<j-editable-table
:columns="columns"
:dataSource="dataSource"
:rowNumber="true"
:rowSelection="true"
:maxHeight="400"
:disabled="true"
/>
</template>
<script>
import { FormTypes } from '@/utils/JEditableTableUtil'
import JEditableTable from '@/components/jeecg/JEditableTable'
export default {
name: 'ReadOnlyTable',
components: { JEditableTable },
data() {
return {
columns: [
{
title: '输入框',
key: 'input',
type: FormTypes.input,
placeholder: '清输入'
},
{
title: '下拉框',
key: 'select',
type: FormTypes.select,
options: [
{ title: 'String', value: 'string' },
{ title: 'Integer', value: 'int' },
{ title: 'Double', value: 'double' },
{ title: 'Boolean', value: 'boolean' }
],
placeholder: '请选择'
},
{
title: '多选框',
key: 'checkbox',
type: FormTypes.checkbox,
customValue: [true, false]
},
{
title: '日期',
key: 'datetime',
type: FormTypes.datetime
}
],
dataSource: [
{ input: 'hello', select: 'int', checkbox: true, datetime: '2019-6-17 14:50:48' },
{ input: 'world', select: 'string', checkbox: false, datetime: '2019-6-16 14:50:48' },
{ input: 'one', select: 'double', checkbox: true, datetime: '2019-6-17 15:50:48' },
{ input: 'two', select: 'boolean', checkbox: false, datetime: '2019-6-14 14:50:48' },
{ input: 'three', select: '', checkbox: false, datetime: '2019-6-13 14:50:48' }
]
}
},
mounted() {
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
<template>
<j-editable-table
:columns="columns"
:dataSource="dataSource"
:rowNumber="true"
:actionButton="true"
:rowSelection="true"
:maxHeight="400"
@valueChange="handleValueChange"
/>
</template>
<script>
import { FormTypes } from '@/utils/JEditableTableUtil'
import JEditableTable from '@/components/jeecg/JEditableTable'
export default {
name: 'ThreeLinkage',
components: { JEditableTable },
data() {
return {
columns: [
{
title: '省/直辖市/自治区',
key: 's1',
type: FormTypes.select,
width: '240px',
options: [],
placeholder: '请选择${title}'
},
{
title: '市',
key: 's2',
type: FormTypes.select,
width: '240px',
options: [],
placeholder: '请选择${title}'
},
{
title: '县/区',
key: 's3',
type: FormTypes.select,
width: '240px',
options: [],
placeholder: '请选择${title}'
}
],
dataSource: [],
mockData: [
{ label: '北京市', value: '110000', parent: null },
{ label: '天津市', value: '120000', parent: null },
{ label: '河北省', value: '130000', parent: null },
{ label: '上海市', value: '310000', parent: null },
{ label: '北京市', value: '110100', parent: '110000' },
{ label: '天津市市', value: '120100', parent: '120000' },
{ label: '石家庄市', value: '130100', parent: '130000' },
{ label: '唐山市', value: '130200', parent: '130000' },
{ label: '秦皇岛市', value: '130300', parent: '130000' },
{ label: '上海市', value: '310100', parent: '310000' },
{ label: '东城区', value: '110101', parent: '110100' },
{ label: '西城区', value: '110102', parent: '110100' },
{ label: '朝阳区', value: '110105', parent: '110100' },
{ label: '和平区', value: '120101', parent: '120000' },
{ label: '河东区', value: '120102', parent: '120000' },
{ label: '河西区', value: '120103', parent: '120000' },
{ label: '黄浦区', value: '310101', parent: '310100' },
{ label: '徐汇区', value: '310104', parent: '310100' },
{ label: '长宁区', value: '310105', parent: '310100' },
{ label: '长安区', value: '130102', parent: '130100' },
{ label: '桥西区', value: '130104', parent: '130100' },
{ label: '新华区', value: '130105', parent: '130100' },
{ label: '路南区', value: '130202', parent: '130200' },
{ label: '路北区', value: '130203', parent: '130200' },
{ label: '古冶区', value: '130204', parent: '130200' },
{ label: '海港区', value: '130302', parent: '130300' },
{ label: '山海关区', value: '130303', parent: '130300' },
{ label: '北戴河区', value: '130304', parent: '130300' },
]
}
},
mounted() {
// 初始化数据
this.columns[0].options = this.request(null)
},
methods: {
request(parentId) {
return this.mockData.filter(i => i.parent === parentId)
},
/** 当选项被改变时,联动其他组件 */
handleValueChange(event) {
const { type, row, column, value, target } = event
if (type === FormTypes.select) {
// 第一列
if (column.key === 's1') {
// 设置第二列的 options
this.columns[1].options = this.request(value)
// 清空后两列的数据
target.setValues([{
rowKey: row.id,
values: { s2: '', s3: '' }
}])
this.columns[2].options = []
} else
// 第二列
if (column.key === 's2') {
this.columns[2].options = this.request(value)
target.setValues([{
rowKey: row.id,
values: { s3: '' }
}])
}
}
}
}
}
</script>
<style scoped>
</style>
\ No newline at end of file
......@@ -486,7 +486,7 @@
days = this.result.day.cronLastSpecificDomDay + 'L';
break;
case '9':
days = 'L-' + this.day.cronDaysBeforeEomMinus;
days = 'L-' + this.result.day.cronDaysBeforeEomMinus;
break;
case '10':
days = this.result.day.cronDaysNearestWeekday+"W";
......
/**
* 同步列表,可以同步新增、修改、删除
* @author sunjianlei
* */
export function syncAllTable(vm, table1) {
vm.$refs.editableTable.resetScrollTop()
let deleteIds = table1.$refs.editableTable.getDeleteIds()
let table1Value
table1.$refs.editableTable.getValuesPromise(false).then((values) => {
table1Value = values
return vm.$refs.editableTable.getValuesPromise(false)
}).then((values) => {
table1Value.forEach(value => {
let flag = false
values.forEach((thisValue) => {
if (value.id === thisValue.id) {
// 判断是否修改了值
let dbFieldName = thisValue['dbFieldName']
let dbFieldTxt = thisValue['dbFieldTxt']
// return
if (value.dbFieldName !== dbFieldName
|| value.dbFieldTxt !== dbFieldTxt) {
// 修改了
vm.$refs.editableTable.setValues([{
rowKey: thisValue.id,
values: {
dbFieldName: value.dbFieldName,
dbFieldTxt: value.dbFieldTxt
}
}])
}
flag = true
} else {
// id不匹配则有可能是新增也有可能是删除了的
// 遍历传进来的 deleteIds 进行对比
deleteIds.forEach(delId => {
// 对比成功,则删除该条数据
if (delId === thisValue.id) {
vm.$refs.editableTable.removeRows(vm.$refs.editableTable.caseId + delId)
flag = true
}
})
}
})
// return
// 判断是否操作了该条数据,若没有操作则代表要执行新增操作
if (!flag) {
let record = Object.assign({}, value)
vm.columns.forEach(column => {
if (
column.dataIndex !== 'dbFieldName' &&
column.dataIndex !== 'dbFieldTxt'
) {
record[column.dataIndex] = column.defaultValue
}
})
vm.$refs.editableTable.push(record)
}
})
})
}
/**
* 将数据分类并Set进dataSource
* @author sunjianlei
**/
export function setDataSource(vm, queryData) {
let dataSource = []
// 遍历查询出来的数据
queryData.forEach(value => {
let data = { id: value['id'] }
vm.columns.forEach(column => {
let key = column.key
if (key) {
data[key] = value[key]
// 由于多选下拉框返回的是一个数组,所以需要改成 [1,2,3] 数组的形式,否则组件不识别
// if (key === 'indexField') {
// data[key] = value[key].split(',')
// }
}
})
dataSource.push(data)
})
vm.dataSource = dataSource
}
/** 获取主表的初始化数据 */
export function getMasterTableInitialData() {
return [
{
dbFieldName: 'id',
dbFieldTxt: '主键',
dbLength: 36,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '1',
dbIsNull: '0',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'text',
fieldLength: '120',
queryMode: 'single',
orderNum: 1
},
{
dbFieldName: 'create_by',
dbFieldTxt: '创建人',
dbLength: 50,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'text',
fieldLength: '120',
queryMode: 'single',
orderNum: 2
},
{
dbFieldName: 'create_time',
dbFieldTxt: '创建日期',
dbLength: 20,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'Date',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'datetime',
fieldLength: '120',
queryMode: 'single',
orderNum: 3
},
{
dbFieldName: 'update_by',
dbFieldTxt: '更新人',
dbLength: 50,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'text',
fieldLength: '120',
queryMode: 'single',
orderNum: 4
},
{
dbFieldName: 'update_time',
dbFieldTxt: '更新日期',
dbLength: 20,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'Date',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'datetime',
fieldLength: '120',
queryMode: 'single',
orderNum: 5
},{
dbFieldName: 'sys_org_code',
dbFieldTxt: '所属部门',
dbLength: 64,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'text',
fieldLength: '120',
queryMode: 'single',
orderNum: 6
}
// {
// dbFieldName: 'sys_org_code',
// dbFieldTxt: '所属部门',
// dbLength: 50,
// dbPointLength: 0,
// dbDefaultVal: '',
// dbType: 'string',
// dbIsKey: false,
// dbIsNull: true
// }, {
// dbFieldName: 'sys_company_code',
// dbFieldTxt: '所属公司',
// dbLength: 50,
// dbPointLength: 0,
// dbDefaultVal: '',
// dbType: 'string',
// dbIsKey: false,
// dbIsNull: true
// }, {
// dbFieldName: 'bpm_status',
// dbFieldTxt: '流程状态',
// dbLength: 32,
// dbPointLength: 0,
// dbDefaultVal: '',
// dbType: 'string',
// dbIsKey: false,
// dbIsNull: true
// }
]
}
/** 获取树的初始化数据 */
export function getTreeNeedFields() {
return [{
dbFieldName: 'pid',
dbFieldTxt: '父级节点',
dbLength: 32,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '1',
isShowList: '0',
fieldShowType: 'text',
fieldLength: '120',
queryMode: 'single',
orderNum: 7
},{
dbFieldName: 'has_child',
dbFieldTxt: '是否有子节点',
dbLength: 3,
dbPointLength: 0,
dbDefaultVal: '',
dbType: 'string',
dbIsKey: '0',
dbIsNull: '1',
// table2
isShowForm: '0',
isShowList: '0',
fieldShowType: 'list',
fieldLength: '120',
queryMode: 'single',
orderNum: 8,
// table3
dictField:"yn"
}]
}
\ No newline at end of file
......@@ -205,11 +205,11 @@
mobile: {rules: [{validator: this.validateMobile}]}
},
url: {
delete: '/sysdepart/sysDepart/delete',
edit: '/sysdepart/sysDepart/edit',
deleteBatch: '/sysdepart/sysDepart/deleteBatch',
exportXlsUrl: "sysdepart/sysDepart/exportXls",
importExcelUrl: "sysdepart/sysDepart/importExcel",
delete: '/sys/sysDepart/delete',
edit: '/sys/sysDepart/edit',
deleteBatch: '/sys/sysDepart/deleteBatch',
exportXlsUrl: "sys/sysDepart/exportXls",
importExcelUrl: "sys/sysDepart/importExcel",
},
}
},
......@@ -292,7 +292,7 @@
var that = this
this.$confirm({
title: '确认删除',
content: '确定要删除所选中的 ' + this.checkedKeys.length + ' 条数据?',
content: '确定要删除所选中的 ' + this.checkedKeys.length + ' 条数据,以及子节点数据吗?',
onOk: function () {
deleteAction(that.url.deleteBatch, {ids: ids}).then((res) => {
if (res.success) {
......
......@@ -148,9 +148,9 @@
selectedRowKeys: [],
selectedRows: [],
url: {
list: "/sysdepart/sysDepart/list",
delete: "/sysdepart/sysDepart/delete",
deleteBatch: "/sysdepart/sysDepart/deleteBatch",
list: "/sys/sysDepart/list",
delete: "/sys/sysDepart/delete",
deleteBatch: "/sys/sysDepart/deleteBatch",
},
}
......
......@@ -19,7 +19,7 @@
</a-form-item>
</a-col>
<a-col :md="8" :sm="10">
<a-col :md="6" :sm="10">
<a-form-item label="创建时间" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-range-picker
style="width: 210px"
......@@ -31,13 +31,18 @@
/>
</a-form-item>
</a-col>
<a-col :md="5" :sm="8" v-if="tabKey === '2'">
<a-form-item label="操作类型" style="left: 10px">
<j-dict-select-tag v-model="queryParam.operateType" placeholder="请选择操作类型" dictCode="operate_type"/>
</a-form-item>
</a-col>
<a-col :md="8" :sm="10" >
<span style="float: right;" class="table-page-search-submitButtons">
<a-button type="primary" style="left: -35px" @click="searchQuery" icon="search">查询</a-button>
<a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px;left: -35px">重置</a-button>
</span>
<span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
<a-col :md="6" :sm="24" >
<a-button type="primary" style="left: 10px" @click="searchQuery" icon="search">查询</a-button>
<a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px;left: 10px">重置</a-button>
</a-col>
</span>
</a-row>
</a-form>
......@@ -58,7 +63,10 @@
<div style="margin-bottom: 5px"><a-badge status="success" style="vertical-align: middle;"/><span style="vertical-align: middle;">请求方法:{{ record.method }}</span></div>
<div><a-badge status="processing" style="vertical-align: middle;"/><span style="vertical-align: middle;">请求参数:{{ record.requestParam }}</span></div>
</div>
<!-- 字符串超长截取省略号显示-->
<span slot="logContent" slot-scope="text, record">
<j-ellipsis :value="text" :length="40"/>
</span>
</a-table>
<!-- table区域-end -->
</a-card>
......@@ -67,10 +75,14 @@
<script>
import { filterObj } from '@/utils/util';
import { JeecgListMixin } from '@/mixins/JeecgListMixin'
import JEllipsis from '@/components/jeecg/JEllipsis'
export default {
name: "LogList",
mixins:[JeecgListMixin],
components: {
JEllipsis
},
data () {
return {
description: '这是日志管理页面',
......@@ -81,6 +93,7 @@
logType:'1',
keyWord:'',
},
tabKey: "1",
// 表头
columns: [
{
......@@ -96,6 +109,7 @@
title: '日志内容',
align:"left",
dataIndex: 'logContent',
scopedSlots: { customRender: 'logContent' },
sorter: true
},
{
......@@ -143,6 +157,12 @@
sorter: true
}
],
operateColumn:
{
title: '操作类型',
dataIndex: 'operateType_dictText',
align:"center",
},
labelCol: {
xs: { span: 1 },
sm: { span: 2 },
......@@ -177,6 +197,17 @@
},
// 日志类型
callback(key){
// 动态添加操作类型列
if (key == 2) {
this.tabKey = '2';
this.columns.splice(7, 0, this.operateColumn);
}else if(this.columns.length == 9)
{
this.tabKey = '1';
this.columns.splice(7,1);
}
let that=this;
that.queryParam.logType=key;
that.loadData();
......
......@@ -98,7 +98,7 @@
} else if (text == 1) {
return '菜单'
} else if (text == 2) {
return '按钮'
return '按钮/权限'
} else {
return text
}
......
......@@ -68,13 +68,13 @@
@change="handleTableChange">
<span slot="action" slot-scope="text, record">
<a @click="handleEdit(record)">编辑</a>
<a v-if="record.sendStatus == 0" @click="handleEdit(record)">编辑</a>
<a-divider type="vertical"/>
<a-divider type="vertical" v-if="record.sendStatus == 0"/>
<a-dropdown>
<a class="ant-dropdown-link">更多 <a-icon type="down"/></a>
<a-menu slot="overlay">
<a-menu-item>
<a-menu-item v-if="record.sendStatus != 1">
<a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">
<a>删除</a>
</a-popconfirm>
......
......@@ -125,9 +125,9 @@
scopedSlots: { customRender: 'action' },
}],
url: {
list: "/system/sysAnnouncementSend/getMyAnnouncementSend",
editCementSend:"system/sysAnnouncementSend/editByAnntIdAndUserId",
readAllMsg:"system/sysAnnouncementSend/readAll",
list: "/sys/sysAnnouncementSend/getMyAnnouncementSend",
editCementSend:"sys/sysAnnouncementSend/editByAnntIdAndUserId",
readAllMsg:"sys/sysAnnouncementSend/readAll",
},
loading:false,
}
......
......@@ -3,7 +3,7 @@
<!-- 查询区域 -->
<div class="table-page-search-wrapper">
<a-form layout="inline">
<a-form layout="inline" @submit.prevent="searchQuery">
<a-row :gutter="24">
<a-col :md="6" :sm="12">
......@@ -117,8 +117,9 @@
</template>
<span slot="action" slot-scope="text, record">
<a @click="handleEdit(record)">编辑</a>
<a-divider type="vertical"/>
<a @click="handleEdit(record)" v-has="'user:edit'">编辑</a>
<a-divider type="vertical" v-has="'user:edit'"/>
<a-dropdown>
<a class="ant-dropdown-link">
......@@ -140,13 +141,13 @@
</a-menu-item>
<a-menu-item v-if="record.status==1">
<a-popconfirm title="确定冻结吗?" @confirm="() => handleFrozen(record.id,2)">
<a-popconfirm title="确定冻结吗?" @confirm="() => handleFrozen(record.id,2,record.username)">
<a>冻结</a>
</a-popconfirm>
</a-menu-item>
<a-menu-item v-if="record.status==2">
<a-popconfirm title="确定解冻吗?" @confirm="() => handleFrozen(record.id,1)">
<a-popconfirm title="确定解冻吗?" @confirm="() => handleFrozen(record.id,1,record.username)">
<a>解冻</a>
</a-popconfirm>
</a-menu-item>
......@@ -297,6 +298,16 @@
} else {
let ids = "";
let that = this;
let isAdmin = false;
that.selectionRows.forEach(function (row) {
if (row.username == 'admin') {
isAdmin = true;
}
});
if (isAdmin) {
that.$message.warning('管理员账号不允许此操作,请重新选择!');
return;
}
that.selectedRowKeys.forEach(function (val) {
ids += val + ",";
});
......@@ -326,8 +337,13 @@
this.batchFrozen(1);
}
},
handleFrozen: function (id, status) {
handleFrozen: function (id, status, username) {
let that = this;
//TODO 后台校验管理员角色
if ('admin' == username) {
that.$message.warning('管理员账号不允许此操作!');
return;
}
frozenBatch({ids: id, status: status}).then((res) => {
if (res.success) {
that.$message.success(res.message);
......
......@@ -105,7 +105,7 @@
mobile:{rules: [{validator:this.validateMobile}]}
},
url: {
add: "/sysdepart/sysDepart/add",
add: "/sys/sysDepart/add",
},
}
},
......
......@@ -213,7 +213,7 @@
component:{rules: [{ required: this.show, message: '请输入前端组件!' }]},
url:{rules: [{ required: this.show, message: '请输入菜单路径!' }]},
permsType:{rules: [{ required: true, message: '请输入授权策略!' }]},
sortNo:{rules: [{initialValue:1.0,validator: this.validateNumber}]},
sortNo:{initialValue:1.0,rules: [{validator: this.validateNumber}]},
}
}
},
......
......@@ -23,11 +23,12 @@
:labelCol="labelCol"
:wrapperCol="wrapperCol"
label="cron表达式">
<a-input placeholder="请输入cron表达式" v-decorator="['cronExpression', {'initialValue':'0/1 * * * * ?',rules: [{ required: true, message: '请输入任务类名!' }]}]" />
<a target="_blank" href="http://cron.qqe2.com/">
<a-icon type="share-alt" />
在线cron表达式生成
</a>
<!-- <a-input placeholder="请输入cron表达式" v-decorator="['cronExpression', {'initialValue':'0/1 * * * * ?',rules: [{ required: true, message: '请输入任务类名!' }]}]" />-->
<!-- <a target="_blank" href="http://cron.qqe2.com/">-->
<!-- <a-icon type="share-alt" />-->
<!-- 在线cron表达式生成-->
<!-- </a>-->
<j-cron ref="innerVueCron" v-decorator="['cronExpression', {'initialValue':'0/1 * * * * ?',rules: [{ required: true, message: '请输入cron表达式!' }]}]" @change="setCorn"></j-cron>
</a-form-item>
<a-form-item
:labelCol="labelCol"
......@@ -60,11 +61,15 @@
<script>
import { httpAction } from '@/api/manage'
import JCron from "@/components/jeecg/JCron.vue";
import pick from 'lodash.pick'
import moment from "moment"
export default {
name: "QuartzJobModal",
components: {
JCron
},
data () {
return {
title:"操作",
......@@ -78,10 +83,18 @@
xs: { span: 24 },
sm: { span: 16 },
},
cron: {
label: '',
value: ''
},
confirmLoading: false,
form: this.$form.createForm(this),
validatorRules:{
validatorRules: {
cron: {
rules: [{
required: true, message: '请输入cron表达式!'
}]
}
},
url: {
add: "/sys/quartzJob/add",
......@@ -96,8 +109,7 @@
this.edit({});
},
edit (record) {
this.form.resetFields();
this.model = Object.assign({}, record);
this.model = Object.assign({},record);
console.log(this.model)
this.visible = true;
this.$nextTick(() => {
......@@ -113,7 +125,13 @@
const that = this;
// 触发表单验证
this.form.validateFields((err, values) => {
console.log('values',values)
if (!err) {
// if (typeof values.cronExpression == "undefined" || Object.keys(values.cronExpression).length==0 ) {
// this.$message.warning('请输入cron表达式!');
// return false;
// }
that.confirmLoading = true;
let httpurl = '';
let method = '';
......@@ -127,7 +145,7 @@
let formData = Object.assign(this.model, values);
//时间格式化
console.log(formData)
console.log('提交参数',formData)
httpAction(httpurl,formData,method).then((res)=>{
if(res.success){
that.$message.success(res.message);
......@@ -146,7 +164,24 @@
handleCancel () {
this.close()
},
setCorn(data){
console.log('data)',data);
this.$nextTick(() => {
this.model.cronExpression = data;
})
// console.log(Object.keys(data).length==0);
// if (Object.keys(data).length==0) {
// this.$message.warning('请输入cron表达式!');
// }
},
validateCron(rule, value, callback){
if(!value){
callback()
}else if (Object.keys(value).length==0) {
callback("请输入cron表达式!");
}
},
}
}
......
......@@ -196,8 +196,12 @@
getQueryParams(){
let param = Object.assign({}, this.queryParam,this.isorter);
param.field = this.getQueryField();
param.current = this.ipagination.current;
//--update-begin----author:scott---date:20190818------for:新建公告时指定特定用户翻页错误SelectUserListModal #379----
// param.current = this.ipagination.current;
// param.pageSize = this.ipagination.pageSize;
param.pageNo = this.ipagination.current;
param.pageSize = this.ipagination.pageSize;
//--update-end----author:scott---date:20190818------for:新建公告时指定特定用户翻页错误SelectUserListModal #379---
return filterObj(param);
},
getQueryField(){
......
......@@ -99,9 +99,9 @@
endTime:{rules: [{ required: true, message: '请输入代理结束时间!' }]},
},
url: {
add: "/system/sysUserAgent/add",
edit: "/system/sysUserAgent/edit",
queryByUserName:"/system/sysUserAgent/queryByUserName",
add: "/sys/sysUserAgent/add",
edit: "/sys/sysUserAgent/edit",
queryByUserName:"/sys/sysUserAgent/queryByUserName",
},
}
},
......
......@@ -45,6 +45,7 @@
mode="multiple"
style="width: 100%"
placeholder="请选择用户角色"
optionFilterProp = "children"
v-model="selectedRole">
<a-select-option v-for="(role,roleindex) in roleList" :key="roleindex.toString()" :value="role.id">
{{ role.roleName }}
......@@ -411,7 +412,7 @@
if(!value){
callback()
}else{
if(new RegExp(/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/).test(value)){
if(new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/).test(value)){
var params = {
tableName: 'sys_user',
fieldName: 'email',
......
<template>
<a-card :bordered="false">
<a-card :bordered="false" style="width: 160%;text-align: center;margin-left:-25%">
<a-steps class="steps" :current="currentTab">
<a-step title="账户信息" />
<a-step title="身份验证" />
......
......@@ -85,12 +85,12 @@
<a-form-item>
<a-checkbox v-model="formLogin.rememberMe">自动登陆</a-checkbox>
<router-link :to="{ name: 'alteration'}" class="forge-password" style="float: right;">
<!-- <router-link :to="{ name: 'alteration'}" class="forge-password" style="float: right;">
忘记密码
</router-link>
<router-link :to="{ name: 'register'}" class="forge-password" style="float: right;margin-right: 10px" >
注册账户
</router-link>
</router-link>-->
</a-form-item>
<a-form-item style="margin-top:24px">
......@@ -176,6 +176,8 @@
import { putAction } from '@/api/manage'
import { postAction } from '@/api/manage'
import { encryption , getEncryptedString } from '@/utils/encryption/aesEncrypt'
import store from '@/store/'
import { USER_INFO } from "@/store/mutation-types"
export default {
components: {
......@@ -342,7 +344,9 @@
})
},
loginSuccess () {
this.loginBtn = false
// update-begin- author:sunjianlei --- date:20190812 --- for: 登录成功后不解除禁用按钮,防止多次点击
// this.loginBtn = false
// update-end- author:sunjianlei --- date:20190812 --- for: 登录成功后不解除禁用按钮,防止多次点击
this.$router.push({ name: "dashboard" })
this.$notification.success({
message: '欢迎',
......@@ -425,6 +429,10 @@
}
putAction("/sys/selectDepart",obj).then(res=>{
if(res.success){
const userInfo = res.result.userInfo;
Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000);
store.commit('SET_INFO', userInfo);
//console.log("---切换组织机构---userInfo-------",store.getters.userInfo.orgCode);
this.departClear()
this.loginSuccess()
}else{
......
......@@ -41,7 +41,7 @@
<script>
import JGraphicCode from '@/components/jeecg/JGraphicCode'
import { getAction } from '@/api/manage'
import {duplicateCheck } from '@/api/api'
import {checkOnlyUser } from '@/api/api'
export default {
name: "Step1",
components: {
......@@ -116,14 +116,12 @@
callback("请输入用户名和手机号!");
}
//判断用户输入账号还是手机号码
if(reg.test(value)){
var params = {
tableName: 'sys_user',
fieldName: 'phone',
fieldVal: value,
dataId: null
phone : value,
};
duplicateCheck(params).then((res) => {
checkOnlyUser(params).then((res) => {
if (res.success) {
callback("用户名不存在!")
} else {
......@@ -132,12 +130,9 @@
})
}else{
var params = {
tableName: 'sys_user',
fieldName: 'username',
fieldVal: value,
dataId: null
username: value,
};
duplicateCheck(params).then((res) => {
checkOnlyUser(params).then((res) => {
if (res.success) {
callback("用户名不存在!")
} else {
......
......@@ -30,22 +30,22 @@
</a-input>
</a-form-item>
<a-form-item
label="验证码"
:labelCol="{span: 5}"
:wrapperCol="{span: 19}"
style="margin-left:106px"
v-if="show">
<a-row :gutter="16" >
<a-col class="gutter-row" :span="16">
<a-row :gutter="16" style="margin-left: 35px">
<a-col class="gutter-row" :span="10">
<a-input
v-decorator="['captcha',validatorRules.captcha]"
type="text"
placeholder="请输入验证码" >
placeholder="手机短信验证码" >
</a-input>
</a-col>
<a-col class="gutter-row" :span="8" >
<a-button
class="getCaptcha"
tabindex="-1"
size="default"
:disabled="state.smsSendBtn"
@click.stop.prevent="getCaptcha"
v-text="!state.smsSendBtn && '获取验证码' || (state.time+' s')"></a-button>
......
......@@ -21,8 +21,12 @@ module.exports = {
}
},
*/
configureWebpack: {},
configureWebpack: config => {
//生产环境取消 console.log
if (process.env.NODE_ENV === 'production') {
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
}
},
chainWebpack: (config) => {
config.resolve.alias
.set('@$', resolve('src'))
......
......@@ -841,6 +841,43 @@
cssnano-preset-default "^4.0.0"
postcss "^7.0.0"
"@jeecg/antd-onine@^1.0.1":
version "1.0.1"
resolved "https://registry.npmjs.org/@jeecg/antd-onine/-/antd-onine-1.0.1.tgz#bc9e54e75e8e0eff3ee361a1c022cb8672357a8d"
integrity sha512-l9HuoxX8sQ/XQZQfpTDnWuuqqfd2RY4EwOvf0SG45eOk6mDYzaR4P0BRT4QEC59lT3cRu5w5rKH2dn3RHEFzUw==
dependencies:
"@antv/data-set" "^0.10.2"
"@tinymce/tinymce-vue" "^2.0.0"
ant-design-vue "^1.3.9"
apexcharts "^3.6.5"
axios "^0.18.0"
clipboard "^2.0.4"
codemirror "^5.46.0"
dayjs "^1.8.0"
enquire.js "^2.1.6"
js-cookie "^2.2.0"
lodash.get "^4.4.2"
lodash.pick "^4.4.0"
md5 "^2.2.1"
nprogress "^0.2.0"
tinymce "^5.0.2"
viser-vue "^2.4.4"
vue "^2.6.10"
vue-apexcharts "^1.3.2"
vue-class-component "^6.0.0"
vue-cropper "^0.4.8"
vue-i18n "^8.7.0"
vue-loader "^15.7.0"
vue-ls "^3.2.0"
vue-photo-preview "^1.1.3"
vue-print-nb-jeecg "^1.0.8"
vue-property-decorator "^7.3.0"
vue-router "^3.0.1"
vue-splitpane "^1.0.4"
vuedraggable "^2.20.0"
vuex "^3.0.1"
vuex-class "^0.3.1"
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
......
Jeecg-Boot 快速开发平台
===============
当前最新版本: 2.0.2(发布日期:20190708
当前最新版本: 2.1.0(发布日期:20190826
## 后端技术架构
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
-- 创建mysql库
create database `jeecg-boot-os` default character set utf8mb4 collate utf8mb4_general_ci;
\ No newline at end of file
-- author:scott -- date:20190522-- for: 菜单根请求URL配置错误 ---------
-- author:scott -- date:20190522-- for: 菜单根请求URL配置错误 ---------
UPDATE `sys_permission` SET `id`='190c2b43bec6a5f7a4194a85db67d96a', `parent_id`='d7d6e2e4e2934f2c9385a623fd98c6f3', `name`='角色维护', `url`='/isystem/roleUserList', `component`='system/RoleUserList', `component_name`=NULL, `redirect`=NULL, `menu_type`='1', `perms`=NULL, `perms_type`=NULL, `sort_no`='1', `always_show`='0', `icon`=NULL, `is_route`='1', `is_leaf`='1', `hidden`='0', `description`=NULL, `create_by`='admin', `create_time`='2019-04-17 15:13:56', `update_by`=NULL, `update_time`=NULL, `del_flag`='0', `rule_flag`='0', `status`=NULL WHERE (`id`='190c2b43bec6a5f7a4194a85db67d96a');
UPDATE `sys_permission` SET `id`='5c2f42277948043026b7a14692456828', `parent_id`='d7d6e2e4e2934f2c9385a623fd98c6f3', `name`='我的部门', `url`='/isystem/departUserList', `component`='system/DepartUserList', `component_name`=NULL, `redirect`=NULL, `menu_type`='1', `perms`=NULL, `perms_type`=NULL, `sort_no`='1', `always_show`='0', `icon`=NULL, `is_route`='1', `is_leaf`='1', `hidden`='0', `description`=NULL, `create_by`='admin', `create_time`='2019-04-17 15:12:24', `update_by`=NULL, `update_time`=NULL, `del_flag`='0', `rule_flag`='0', `status`=NULL WHERE (`id`='5c2f42277948043026b7a14692456828');
-- author:scott -- date:20190522-- for: 菜单根请求URL配置错误 ----------
-- author:scott -- date:20190524-- for: 字段长度不够 ----------
ALTER TABLE `sys_log`
MODIFY COLUMN `request_param` varchar(1000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求参数' AFTER `request_url`;
-- author:scott -- date:20190524-- for: 字段长度不够 ----------
-- author:taoyan -- date:20190529-- for: 分类字典
INSERT INTO `sys_permission` (`id`, `parent_id`, `name`, `url`, `component`, `component_name`, `redirect`, `menu_type`, `perms`, `perms_type`, `sort_no`, `always_show`, `icon`, `is_route`, `is_leaf`, `hidden`, `description`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `rule_flag`, `status`) VALUES ('ebb9d82ea16ad864071158e0c449d186', 'd7d6e2e4e2934f2c9385a623fd98c6f3', '分类字典', '/isys/category', 'system/SysCategoryList', NULL, NULL, '1', NULL, '1', '5', '0', NULL, '1', '1', '0', NULL, 'admin', '2019-05-29 18:48:07', 'admin', '2019-05-29 18:48:27', '0', '0', '1');
CREATE TABLE `sys_category` (
`id` varchar(36) NOT NULL,
`pid` varchar(36) DEFAULT NULL COMMENT '父级节点',
`name` varchar(100) DEFAULT NULL COMMENT '类型名称',
`code` varchar(100) DEFAULT NULL COMMENT '类型编码',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建日期',
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新日期',
`sys_org_code` varchar(64) DEFAULT NULL COMMENT '所属部门',
`has_child` varchar(3) DEFAULT NULL COMMENT '是否有子节点',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- author:taoyan -- date:20190529-- for: 分类字典
-- author:scott---date:20190531 -----for:字典表del_flag代码不规范问题
update sys_dict set del_flag = 0 where del_flag=1;
update sys_dict set del_flag = 1 where del_flag=2;
delete from sys_dict_item where dict_id not in (select id from sys_dict);
-- author:scott---date:20190531 -----for:字典表del_flag代码不规范问题
-- author:taoyan -- date:20190611 -- for: 树形列表菜单新增字典
INSERT INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `type`) VALUES ('a7adbcd86c37f7dbc9b66945c82ef9e6', '1是0否', 'yn', '', '1', 'admin', '2019-05-22 19:29:29', NULL, NULL, '0');
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES ('51222413e5906cdaf160bb5c86fb827c', 'a7adbcd86c37f7dbc9b66945c82ef9e6', '是', '1', '', '1.00', '1', 'admin', '2019-05-22 19:29:45', NULL, NULL);
INSERT INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`, `update_by`, `update_time`) VALUES ('c5700a71ad08994d18ad1dacc37a71a9', 'a7adbcd86c37f7dbc9b66945c82ef9e6', '否', '0', '', '1.00', '1', 'admin', '2019-05-22 19:29:55', NULL, NULL);
-- author:taoyan -- date:20190611 -- for: 树形列表菜单新增字典
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -3,12 +3,12 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-boot-base-common</artifactId>
<version>2.0.2</version>
<version>2.1.0</version>
<parent>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-parent</artifactId>
<version>2.0.2</version>
<version>2.1.0</version>
</parent>
<repositories>
......
......@@ -33,4 +33,11 @@ public @interface AutoLog {
* @return 0:操作日志;1:登录日志;2:定时任务;
*/
int logType() default CommonConstant.LOG_TYPE_2;
/**
* 操作日志类型
*
* @return (1查询,2添加,3修改,4删除)
*/
int operateType() default 0;
}
......@@ -22,5 +22,15 @@ public interface CacheConstant {
*/
public static final String LOGIN_USER_RULES_CACHE = "loginUser_cacheRules";
/**
* 部门信息缓存
*/
public static final String DEPART_INFO_CACHE = "departCache_info";
/**
* 部门id信息缓存
*/
public static final String DEPART_IDMODEL_CACHE = "departCache_idmodel";
}
......@@ -32,6 +32,36 @@ public interface CommonConstant {
*/
public static final int LOG_TYPE_2 = 2;
/**
* 操作日志类型: 查询
*/
public static final int OPERATE_TYPE_1 = 1;
/**
* 操作日志类型: 添加
*/
public static final int OPERATE_TYPE_2 = 2;
/**
* 操作日志类型: 更新
*/
public static final int OPERATE_TYPE_3 = 3;
/**
* 操作日志类型: 删除
*/
public static final int OPERATE_TYPE_4 = 4;
/**
* 操作日志类型: 倒入
*/
public static final int OPERATE_TYPE_5 = 5;
/**
* 操作日志类型: 导出
*/
public static final int OPERATE_TYPE_6 = 6;
/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
......
......@@ -12,12 +12,15 @@ public interface DataBaseConstant {
/**
* 数据-所属机构编码
*/
public static final String SYS_ORG_CODE_TABLE = "sys_org_code";
/**
* 数据-所属机构编码
*/
public static final String SYS_MULTI_ORG_CODE = "sysMultiOrgCode";
/**
* 数据-所属机构编码
*/
public static final String SYS_ORG_CODE_TABLE = "sys_org_code";
public static final String SYS_MULTI_ORG_CODE_TABLE = "sys_multi_org_code";
/**
* 数据-系统用户编码(对应登录用户账号)
*/
......
......@@ -7,6 +7,7 @@ import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;
import lombok.extern.slf4j.Slf4j;
......@@ -65,4 +66,13 @@ public class JeecgBootExceptionHandler {
return Result.error("没有权限,请联系管理员授权");
}
/**
* spring默认上传大小100MB 超出大小捕获异常MaxUploadSizeExceededException
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
public Result<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
log.error(e.getMessage(), e);
return Result.error("文件大小超出10MB限制, 请压缩或降低文件质量! ");
}
}
......@@ -55,7 +55,7 @@ public class QueryGenerator {
private static SimpleDateFormat getTime(){
SimpleDateFormat time = local.get();
if(time == null){
time = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
local.set(time);
}
return time;
......
......@@ -155,7 +155,7 @@ public class JwtUtil {
}
}
//替换为系统登录用户真实名字
if (key.equals(DataBaseConstant.SYS_USER_NAME)|| key.equals(DataBaseConstant.SYS_USER_NAME_TABLE)) {
else if (key.equals(DataBaseConstant.SYS_USER_NAME)|| key.equals(DataBaseConstant.SYS_USER_NAME_TABLE)) {
if(user==null) {
returnValue = sysUser.getRealname();
}else {
......@@ -164,7 +164,7 @@ public class JwtUtil {
}
//替换为系统用户登录所使用的机构编码
if (key.equals(DataBaseConstant.SYS_ORG_CODE)|| key.equals(DataBaseConstant.SYS_ORG_CODE_TABLE)) {
else if (key.equals(DataBaseConstant.SYS_ORG_CODE)|| key.equals(DataBaseConstant.SYS_ORG_CODE_TABLE)) {
if(user==null) {
returnValue = sysUser.getOrgCode();
}else {
......@@ -172,7 +172,7 @@ public class JwtUtil {
}
}
//替换为系统用户所拥有的所有机构编码
if (key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE)|| key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE)) {
else if (key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE)|| key.equals(DataBaseConstant.SYS_MULTI_ORG_CODE_TABLE)) {
if(user.isOneDepart()) {
returnValue = user.getSysMultiOrgCode().get(0);
}else {
......@@ -180,18 +180,23 @@ public class JwtUtil {
}
}
//替换为当前系统时间(年月日)
if (key.equals(DataBaseConstant.SYS_DATE)|| key.equals(DataBaseConstant.SYS_DATE_TABLE)) {
else if (key.equals(DataBaseConstant.SYS_DATE)|| key.equals(DataBaseConstant.SYS_DATE_TABLE)) {
returnValue = user.getSysDate();
}
//替换为当前系统时间(年月日时分秒)
if (key.equals(DataBaseConstant.SYS_TIME)|| key.equals(DataBaseConstant.SYS_TIME_TABLE)) {
else if (key.equals(DataBaseConstant.SYS_TIME)|| key.equals(DataBaseConstant.SYS_TIME_TABLE)) {
returnValue = user.getSysTime();
}
//流程状态默认值(默认未发起)
if (key.equals(DataBaseConstant.BPM_STATUS_TABLE)|| key.equals(DataBaseConstant.BPM_STATUS_TABLE)) {
else if (key.equals(DataBaseConstant.BPM_STATUS)|| key.equals(DataBaseConstant.BPM_STATUS_TABLE)) {
returnValue = "1";
}
if(returnValue!=null){returnValue = returnValue + moshi;}
return returnValue;
}
public static void main(String[] args) {
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NjUzMzY1MTMsInVzZXJuYW1lIjoiYWRtaW4ifQ.xjhud_tWCNYBOg_aRlMgOdlZoWFFKB_givNElHNw3X0";
System.out.println(JwtUtil.getUsername(token));
}
}
......@@ -2,6 +2,8 @@ package org.jeecg.common.system.vo;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
......@@ -9,6 +11,7 @@ import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class DictModel implements Serializable{
private static final long serialVersionUID = 1L;
......
......@@ -39,6 +39,11 @@ public class LoginUser {
private String realname;
/**
* 登录人密码
*/
private String password;
/**
* 当前登录部门code
*/
private String orgCode;
......
package org.jeecg.common.util;
import org.apache.commons.lang.StringUtils;
public enum DySmsEnum {
LOGIN_TEMPLATE_CODE("SMS_167040816","JEECG","code"),
FORGET_PASSWORD_TEMPLATE_CODE("SMS_167040816","JEECG","code"),
REGISTER_TEMPLATE_CODE("SMS_144146309","JEECG","code");
/**
* 短信模板编码
*/
private String templateCode;
/**
* 签名
*/
private String signName;
/**
* 短信模板必需的数据名称,多个key以逗号分隔,此处配置作为校验
*/
private String keys;
private DySmsEnum(String templateCode,String signName,String keys) {
this.templateCode = templateCode;
this.signName = signName;
this.keys = keys;
}
public String getTemplateCode() {
return templateCode;
}
public void setTemplateCode(String templateCode) {
this.templateCode = templateCode;
}
public String getSignName() {
return signName;
}
public void setSignName(String signName) {
this.signName = signName;
}
public String getKeys() {
return keys;
}
public void setKeys(String keys) {
this.keys = keys;
}
public static DySmsEnum toEnum(String templateCode) {
if(StringUtils.isEmpty(templateCode)){
return null;
}
for(DySmsEnum item : DySmsEnum.values()) {
if(item.getTemplateCode().equals(templateCode)) {
return item;
}
}
return null;
}
}
......@@ -2,6 +2,8 @@ package org.jeecg.common.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
......@@ -31,32 +33,27 @@ public class DySmsHelper {
static final String domain = "dysmsapi.aliyuncs.com";
// TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
static final String accessKeyId = "?";
static final String accessKeySecret = "?";
/**
* 登陆时采用的短信发送模板编码
*/
public static final String LOGIN_TEMPLATE_CODE="SMS_167040816";
static String accessKeyId;
static String accessKeySecret;
/**
* 忘记密码时采用的短信发送模板编码
*/
public static final String FORGET_PASSWORD_TEMPLATE_CODE="SMS_167040816";
public static void setAccessKeyId(String accessKeyId) {
DySmsHelper.accessKeyId = accessKeyId;
}
public static void setAccessKeySecret(String accessKeySecret) {
DySmsHelper.accessKeySecret = accessKeySecret;
}
/**
* 注册时采用的短信发送模板编码
*/
public static final String REGISTER_TEMPLATE_CODE="SMS_144146309";
public static String getAccessKeyId() {
return accessKeyId;
}
/**
* 必填:短信签名-可在短信控制台中找到
*/
public static final String signName="JEECG";
public static String getAccessKeySecret() {
return accessKeySecret;
}
public static boolean sendSms(String phone,String code,String templateCode) throws ClientException {
public static boolean sendSms(String phone,JSONObject templateParamJson,DySmsEnum dySmsEnum) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
......@@ -66,16 +63,19 @@ public class DySmsHelper {
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//验证json参数
validateParam(templateParamJson,dySmsEnum);
//组装请求对象-具体描述见控制台-文档部分内容
SendSmsRequest request = new SendSmsRequest();
//必填:待发送手机号
request.setPhoneNumbers(phone);
//必填:短信签名-可在短信控制台中找到
request.setSignName(signName);
request.setSignName(dySmsEnum.getSignName());
//必填:短信模板-可在短信控制台中找到
request.setTemplateCode("SMS_167040816");
request.setTemplateCode(dySmsEnum.getTemplateCode());
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
request.setTemplateParam("{\"code\":\""+code+"\"}");
request.setTemplateParam(templateParamJson.toJSONString());
//选填-上行短信扩展码(无特殊需求用户请忽略此字段)
//request.setSmsUpExtendCode("90997");
......@@ -96,10 +96,21 @@ public class DySmsHelper {
}
private static void validateParam(JSONObject templateParamJson,DySmsEnum dySmsEnum) {
String keys = dySmsEnum.getKeys();
String [] keyArr = keys.split(",");
for(String item :keyArr) {
if(!templateParamJson.containsKey(item)) {
throw new RuntimeException("模板缺少参数:"+item);
}
}
}
public static void main(String[] args) throws ClientException, InterruptedException {
sendSms("13800138000", "123456", FORGET_PASSWORD_TEMPLATE_CODE);
JSONObject obj = new JSONObject();
obj.put("code", "1234");
sendSms("13800138000", obj, DySmsEnum.FORGET_PASSWORD_TEMPLATE_CODE);
}
}
......@@ -88,8 +88,9 @@ public class PasswordUtil {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
encipheredData = cipher.doFinal(plaintext.getBytes());
//update-begin-author:sccott date:20180815 for:中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7
encipheredData = cipher.doFinal(plaintext.getBytes("utf-8"));
//update-end-author:sccott date:20180815 for:中文作为用户名时,加密的密码windows和linux会得到不同的结果 gitee/issues/IZUD7
} catch (Exception e) {
}
return bytesToHexString(encipheredData);
......
......@@ -62,7 +62,30 @@ public class SqlInjectionUtil {
*/
@Deprecated
public static void specialFilterContent(String value) {
String specialXssStr = "exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|,";
String specialXssStr = "exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|";
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) {
return;
}
value = value.toLowerCase();// 统一转为小写
for (int i = 0; i < xssArr.length; i++) {
if (value.indexOf(xssArr[i]) > -1) {
log.error("请注意,值可能存在SQL注入风险!---> {}", value);
throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
}
}
return;
}
/**
* @特殊方法(不通用) 仅用于Online报表SQL解析,注入过滤
* @param value
* @return
*/
@Deprecated
public static void specialFilterContentForOnlineReport(String value) {
String specialXssStr = "exec |insert |delete |update |drop |chr |mid |master |truncate |char |declare |";
String[] xssArr = specialXssStr.split("\\|");
if (value == null || "".equals(value)) {
return;
......
package org.jeecg.common.util.security;
import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONObject;
import org.jeecg.common.util.security.entity.*;
import javax.crypto.SecretKey;
import java.security.KeyPair;
public class SecurityTools {
public static final String ALGORITHM = "AES/ECB/PKCS5Padding";
public static SecurityResp valid(SecurityReq req) {
SecurityResp resp=new SecurityResp();
String pubKey=req.getPubKey();
String aesKey=req.getAesKey();
String data=req.getData();
String signData=req.getSignData();
RSA rsa=new RSA(null, Base64Decoder.decode(pubKey));
Sign sign= new Sign(SignAlgorithm.SHA1withRSA,null,pubKey);
byte[] decryptAes = rsa.decrypt(aesKey, KeyType.PublicKey);
//log.info("rsa解密后的秘钥"+ Base64Encoder.encode(decryptAes));
AES aes = SecureUtil.aes(decryptAes);
String dencrptValue =aes.decryptStr(data);
//log.info("解密后报文"+dencrptValue);
resp.setData(new JSONObject(dencrptValue));
boolean verify = sign.verify(dencrptValue.getBytes(), Base64Decoder.decode(signData));
resp.setSuccess(verify);
return resp;
}
public static SecuritySignResp sign(SecuritySignReq req) {
SecretKey secretKey = SecureUtil.generateKey(ALGORITHM);
byte[] key= secretKey.getEncoded();
String prikey=req.getPrikey();
String data=req.getData();
AES aes = SecureUtil.aes(key);
aes.getSecretKey().getEncoded();
String encrptData =aes.encryptBase64(data);
RSA rsa=new RSA(prikey,null);
byte[] encryptAesKey = rsa.encrypt(secretKey.getEncoded(), KeyType.PrivateKey);
//log.info(("rsa加密过的秘钥=="+Base64Encoder.encode(encryptAesKey));
Sign sign= new Sign(SignAlgorithm.SHA1withRSA,prikey,null);
byte[] signed = sign.sign(data.getBytes());
//log.info(("签名数据===》》"+Base64Encoder.encode(signed));
SecuritySignResp resp=new SecuritySignResp();
resp.setAesKey(Base64Encoder.encode(encryptAesKey));
resp.setData(encrptData);
resp.setSignData(Base64Encoder.encode(signed));
return resp;
}
public static MyKeyPair generateKeyPair(){
KeyPair keyPair= SecureUtil.generateKeyPair(SignAlgorithm.SHA1withRSA.getValue(),2048);
String priKey= Base64Encoder.encode(keyPair.getPrivate().getEncoded());
String pubkey= Base64Encoder.encode(keyPair.getPublic().getEncoded());
MyKeyPair resp=new MyKeyPair();
resp.setPriKey(priKey);
resp.setPubKey(pubkey);
return resp;
}
}
package org.jeecg.common.util.security.entity;
import lombok.Data;
@Data
public class MyKeyPair {
private String priKey;
private String pubKey;
}
package org.jeecg.common.util.security.entity;
import lombok.Data;
@Data
public class SecurityReq {
private String data;
private String pubKey;
private String signData;
private String aesKey;
}
package org.jeecg.common.util.security.entity;
import cn.hutool.json.JSONObject;
import lombok.Data;
@Data
public class SecurityResp {
private Boolean success;
private JSONObject data;
}
package org.jeecg.common.util.security.entity;
import lombok.Data;
@Data
public class SecuritySignReq {
private String data;
private String prikey;
}
package org.jeecg.common.util.security.entity;
import lombok.Data;
@Data
public class SecuritySignResp {
private String data;
private String signData;
private String aesKey;
}
*.js linguist-language=Java
*.css linguist-language=Java
*.html linguist-language=Java
*.vue linguist-language=Java
......@@ -3,12 +3,12 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-boot-module-system</artifactId>
<version>2.0.2</version>
<version>2.1.0</version>
<parent>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-parent</artifactId>
<version>2.0.2</version>
<version>2.1.0</version>
</parent>
<repositories>
......@@ -35,6 +35,24 @@
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-base-common</artifactId>
</dependency>
<!-- online form-->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>online-form</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- online form -->
</dependencies>
<build>
......
package org.jeecg;
import java.net.InetAddress;
import java.net.UnknownHostException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import lombok.extern.slf4j.Slf4j;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Slf4j
@EnableSwagger2
@SpringBootApplication
......@@ -31,7 +30,5 @@ public class JeecgApplication {
"swagger-ui: \thttp://" + ip + ":" + port + path + "/swagger-ui.html\n\t" +
"Doc: \t\thttp://" + ip + ":" + port + path + "/doc.html\n" +
"----------------------------------------------------------");
}
}
\ No newline at end of file
......@@ -17,7 +17,9 @@ import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
......@@ -82,13 +84,18 @@ public class RedisConfig extends CachingConfigurerSupport {
*/
@Bean
public CacheManager cacheManager(LettuceConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 以锁写入的方式创建RedisCacheWriter对象
RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
//RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
// 创建默认缓存配置对象
/* 默认配置,设置缓存有效期 1小时*/
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
//RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));
/* 配置test的超时时间为120s*/
RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter(lettuceConnectionFactory)).cacheDefaults(defaultCacheConfig)
RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter(factory)).cacheDefaults(redisCacheConfiguration)
.withInitialCacheConfigurations(singletonMap("test", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(120)).disableCachingNullValues()))
.transactionAware().build();
return cacheManager;
......
......@@ -42,8 +42,11 @@ public class ShiroConfig {
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//cas验证登录
filterChainDefinitionMap.put("/cas/client/validateLogin", "anon");
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
filterChainDefinitionMap.put("/sys/getEncryptedString", "anon"); //获取加密串
filterChainDefinitionMap.put("/sys/sms", "anon");//短信验证码
filterChainDefinitionMap.put("/sys/phoneLogin", "anon");//手机登录
......@@ -63,9 +66,16 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/**/*.css", "anon");
filterChainDefinitionMap.put("/**/*.html", "anon");
filterChainDefinitionMap.put("/**/*.svg", "anon");
filterChainDefinitionMap.put("/**/*.pdf", "anon");
filterChainDefinitionMap.put("/**/*.jpg", "anon");
filterChainDefinitionMap.put("/**/*.png", "anon");
filterChainDefinitionMap.put("/**/*.ico", "anon");
// update-begin--Author:sunjianlei Date:20190813 for:排除字体格式的后缀
filterChainDefinitionMap.put("/**/*.ttf", "anon");
filterChainDefinitionMap.put("/**/*.woff", "anon");
// update-begin--Author:sunjianlei Date:20190813 for:排除字体格式的后缀
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger**/**", "anon");
......@@ -77,6 +87,19 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/actuator/httptrace/**", "anon");
filterChainDefinitionMap.put("/actuator/redis/**", "anon");
filterChainDefinitionMap.put("/test/jeecgDemo/demo3", "anon"); //模板测试
filterChainDefinitionMap.put("/test/jeecgDemo/redisDemo/**", "anon"); //redis测试
//排除Online请求
filterChainDefinitionMap.put("/auto/cgform/**", "anon");
//websocket排除
filterChainDefinitionMap.put("/websocket/**", "anon");
// 添加自己的过滤器并且取名为jwt
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
filterMap.put("jwt", new JwtFilter());
......
package org.jeecg.config;
import org.jeecg.common.util.DySmsHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 设置静态参数初始化
*/
@Configuration
public class StaticConfig {
@Value("${jeecg.sms.accessKeyId}")
private String accessKeyId;
@Value("${jeecg.sms.accessKeySecret}")
private String accessKeySecret;
@Bean
public void initStatic() {
DySmsHelper.setAccessKeyId(accessKeyId);
DySmsHelper.setAccessKeySecret(accessKeySecret);
}
}
package org.jeecg.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* 注入ServerEndpointExporter,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
......@@ -106,11 +106,13 @@ public class MybatisInterceptor implements Interceptor {
Field[] fields = null;
if (parameter instanceof ParamMap) {
ParamMap<?> p = (ParamMap<?>) parameter;
//update-begin-author:scott date:20190729 for:批量更新报错issues/IZA3Q--
if (p.containsKey("et")) {
parameter = p.get("et");
} else {
parameter = p.get("param1");
}
//update-end-author:scott date:20190729 for:批量更新报错issues/IZA3Q-
fields = oConvertUtils.getAllFields(parameter);
} else {
fields = oConvertUtils.getAllFields(parameter);
......
package org.jeecg.modules.cas.controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.cas.util.CASServiceUtil;
import org.jeecg.modules.cas.util.XmlUtils;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysDepartService;
import org.jeecg.modules.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* CAS单点登录客户端登录认证
* </p>
*
* @Author zhoujf
* @since 2018-12-20
*/
@Slf4j
@RestController
@RequestMapping("/cas/client")
public class CasClientController {
@Autowired
private ISysUserService sysUserService;
@Autowired
private ISysDepartService sysDepartService;
@Autowired
private RedisUtil redisUtil;
@Value("${cas.prefixUrl}")
private String prefixUrl;
@GetMapping("/validateLogin")
public Object validateLogin(@RequestParam(name="ticket") String ticket,
@RequestParam(name="service") String service,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
Result<JSONObject> result = new Result<JSONObject>();
log.info("Rest api login.");
try {
String validateUrl = prefixUrl+"/p3/serviceValidate";
String res = CASServiceUtil.getSTValidate(validateUrl, ticket, service);
log.info("res."+res);
final String error = XmlUtils.getTextForElement(res, "authenticationFailure");
if(StringUtils.isNotEmpty(error)) {
throw new Exception(error);
}
final String principal = XmlUtils.getTextForElement(res, "user");
if (StringUtils.isEmpty(principal)) {
throw new Exception("No principal was found in the response from the CAS server.");
}
log.info("-------token----username---"+principal);
//1. 校验用户是否有效
SysUser sysUser = sysUserService.getUserByName(principal);
result = sysUserService.checkUserIsEffective(sysUser);
if(!result.isSuccess()) {
return result;
}
String token = JwtUtil.sign(sysUser.getUsername(), sysUser.getPassword());
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
// 设置超时时间
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
// 获取用户部门信息
JSONObject obj = new JSONObject();
List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
obj.put("departs", departs);
if (departs == null || departs.size() == 0) {
obj.put("multi_depart", 0);
} else if (departs.size() == 1) {
sysUserService.updateUserDepart(principal, departs.get(0).getOrgCode());
obj.put("multi_depart", 1);
} else {
obj.put("multi_depart", 2);
}
obj.put("token", token);
obj.put("userInfo", sysUser);
result.setResult(obj);
result.success("登录成功");
} catch (Exception e) {
//e.printStackTrace();
result.error500(e.getMessage());
}
return new HttpEntity<>(result);
}
}
package org.jeecg.modules.cas.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
public class CASServiceUtil {
public static void main(String[] args) {
String serviceUrl = "https://cas.8f8.com.cn:8443/cas/p3/serviceValidate";
String service = "http://localhost:3003/user/login";
String ticket = "ST-5-1g-9cNES6KXNRwq-GuRET103sm0-DESKTOP-VKLS8B3";
String res = getSTValidate(serviceUrl,ticket, service);
System.out.println("---------res-----"+res);
}
/**
* 验证ST
*/
public static String getSTValidate(String url,String st, String service){
try {
url = url+"?service="+service+"&ticket="+st;
CloseableHttpClient httpclient = createHttpClientWithNoSsl();
HttpGet httpget = new HttpGet(url);
HttpResponse response = httpclient.execute(httpget);
String res = readResponse(response);
return res == null ? null : (res == "" ? null : res);
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* 读取 response body 内容为字符串
*
* @param response
* @return
* @throws IOException
*/
private static String readResponse(HttpResponse response) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String result = new String();
String line;
while ((line = in.readLine()) != null) {
result += line;
}
return result;
}
/**
* 创建模拟客户端(针对 https 客户端禁用 SSL 验证)
*
* @param cookieStore 缓存的 Cookies 信息
* @return
* @throws Exception
*/
private static CloseableHttpClient createHttpClientWithNoSsl() throws Exception {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
// don't check
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
// don't check
}
}
};
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, trustAllCerts, null);
LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(ctx);
return HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.build();
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -11,6 +11,7 @@ import javax.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.aspect.annotation.PermissionData;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.DateUtils;
......@@ -143,7 +144,8 @@ public class JeecgDemoController extends JeecgController<JeecgDemo,IJeecgDemoSer
*/
@PutMapping(value = "/edit")
@ApiOperation(value = "编辑DEMO", notes = "编辑DEMO")
public Result<JeecgDemo> eidt(@RequestBody JeecgDemo jeecgDemo) {
@AutoLog(value = "编辑DEMO",operateType= CommonConstant.OPERATE_TYPE_3)
public Result<JeecgDemo> edit(@RequestBody JeecgDemo jeecgDemo) {
Result<JeecgDemo> result = new Result<JeecgDemo>();
JeecgDemo jeecgDemoEntity = jeecgDemoService.getById(jeecgDemo.getId());
if (jeecgDemoEntity == null) {
......
......@@ -25,7 +25,7 @@ import lombok.experimental.Accessors;
public class SysMessage extends JeecgEntity {
/**推送内容*/
@Excel(name = "推送内容", width = 15)
private java.lang.Object esContent;
private java.lang.String esContent;
/**推送所需参数Json格式*/
@Excel(name = "推送所需参数Json格式", width = 15)
private java.lang.String esParam;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment