Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
mes-ui
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
周远喜
mes-ui
Commits
be458b9d
Commit
be458b9d
authored
Jul 15, 2020
by
周远喜
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
部门管理优化
parent
3c1c9738
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
689 additions
and
21 deletions
+689
-21
org-view.vue
components/department/org-view.vue
+172
-0
zoom-controller.vue
components/department/zoom-controller.vue
+81
-0
tools.js
libs/tools.js
+215
-0
index.vue
pages/basicData/department/index.vue
+39
-9
org.vue
pages/basicData/department/org.vue
+182
-12
No files found.
components/department/org-view.vue
0 → 100644
View file @
be458b9d
<
template
>
<div
ref=
"dragWrapper"
class=
"org-tree-drag-wrapper"
@
mousedown=
"mousedownView"
@
contextmenu=
"handleDocumentContextmenu"
>
<div
class=
"org-tree-wrapper"
:style=
"orgTreeStyle"
>
<v-org-tree
v-if=
"data"
:data=
"data"
:node-render=
"nodeRender"
:expand-all=
"true"
@
on-node-click=
"handleNodeClick"
collapsable
></v-org-tree>
</div>
</div>
</
template
>
<
script
>
import
{
on
,
off
}
from
'@/libs/tools'
const
menuList
=
[
{
key
:
'edit'
,
label
:
'编辑部门'
},
// {
// key: 'detail',
// label: '查看部门'
// },
{
key
:
'add'
,
label
:
'新增子部门'
},
{
key
:
'delete'
,
label
:
'删除部门'
}
]
export
default
{
name
:
'OrgView'
,
props
:
{
zoomHandled
:
{
type
:
Number
,
default
:
1
},
data
:
Object
},
data
()
{
return
{
currentContextMenuId
:
''
,
orgTreeOffsetLeft
:
0
,
orgTreeOffsetTop
:
0
,
initPageX
:
0
,
initPageY
:
0
,
oldMarginLeft
:
0
,
oldMarginTop
:
0
,
canMove
:
false
}
},
computed
:
{
orgTreeStyle
()
{
return
{
transform
:
`translate(-50%, -50%) scale(
${
this
.
zoomHandled
}
,
${
this
.
zoomHandled
}
)`
,
marginLeft
:
`
${
this
.
orgTreeOffsetLeft
}
px`
,
marginTop
:
`
${
this
.
orgTreeOffsetTop
}
px`
}
}
},
methods
:
{
handleNodeClick
(
e
,
data
,
expand
)
{
expand
()
},
closeMenu
()
{
this
.
currentContextMenuId
=
''
},
getBgColor
(
data
)
{
return
this
.
currentContextMenuId
===
data
.
id
?
data
.
isRoot
?
'#0d7fe8'
:
'#5d6c7b'
:
''
},
nodeRender
(
h
,
data
)
{
return
(
<
div
class
=
{[
'custom-org-node'
,
data
.
children
&&
data
.
children
.
length
?
'has-children-label'
:
''
]}
on
-
mousedown
=
{
event
=>
event
.
stopPropagation
()}
on
-
contextmenu
=
{
this
.
contextmenu
.
bind
(
this
,
data
)}
>
{
data
.
label
}
<
dropdown
trigger
=
"custom"
class
=
"context-menu"
visible
=
{
this
.
currentContextMenuId
===
data
.
id
}
nativeOn
-
click
=
{
this
.
handleDropdownClick
}
on
-
on
-
click
=
{
this
.
handleContextMenuClick
.
bind
(
this
,
data
)}
style
=
{{
transform
:
`scale(
${
1
/
this
.
zoomHandled
}
,
${
1
/
this
.
zoomHandled
}
)`
}}
on
-
on
-
clickoutside
=
{
this
.
closeMenu
}
>
<
dropdown
-
menu
slot
=
"list"
>
{
menuList
.
map
(
item
=>
{
return
(
<
dropdown
-
item
name
=
{
item
.
key
}
>
{
item
.
label
}
<
/dropdown-item
>
)
})}
<
/dropdown-menu
>
<
/dropdown
>
<
/div
>
)
},
contextmenu
(
data
,
$event
)
{
let
event
=
$event
||
window
.
event
event
.
preventDefault
?
event
.
preventDefault
()
:
(
event
.
returnValue
=
false
)
this
.
currentContextMenuId
=
data
.
id
},
setDepartmentData
(
data
)
{
data
.
isRoot
=
true
this
.
departmentData
=
data
},
mousedownView
(
event
)
{
this
.
canMove
=
true
this
.
initPageX
=
event
.
pageX
this
.
initPageY
=
event
.
pageY
this
.
oldMarginLeft
=
this
.
orgTreeOffsetLeft
this
.
oldMarginTop
=
this
.
orgTreeOffsetTop
on
(
document
,
'mousemove'
,
this
.
mousemoveView
)
on
(
document
,
'mouseup'
,
this
.
mouseupView
)
},
mousemoveView
(
event
)
{
if
(
!
this
.
canMove
)
return
const
{
pageX
,
pageY
}
=
event
this
.
orgTreeOffsetLeft
=
this
.
oldMarginLeft
+
pageX
-
this
.
initPageX
this
.
orgTreeOffsetTop
=
this
.
oldMarginTop
+
pageY
-
this
.
initPageY
},
mouseupView
()
{
this
.
canMove
=
false
off
(
document
,
'mousemove'
,
this
.
mousemoveView
)
off
(
document
,
'mouseup'
,
this
.
mouseupView
)
},
handleDropdownClick
(
event
)
{
event
.
stopPropagation
()
},
handleDocumentContextmenu
()
{
this
.
canMove
=
false
},
handleContextMenuClick
(
data
,
key
)
{
this
.
$emit
(
'on-menu-click'
,
{
data
,
key
})
}
},
mounted
()
{
on
(
document
,
'contextmenu'
,
this
.
handleDocumentContextmenu
)
},
beforeDestroy
()
{
off
(
document
,
'contextmenu'
,
this
.
handleDocumentContextmenu
)
}
}
</
script
>
<
style
>
</
style
>
components/department/zoom-controller.vue
0 → 100644
View file @
be458b9d
<
template
>
<div
class=
"zoom-wrapper"
>
<button
class=
"zoom-button"
@
click=
"scale('down')"
>
<Icon
type=
"md-remove"
:size=
"14"
color=
"#fff"
/>
</button>
<span
class=
"zoom-number"
>
{{
value
}}
%
</span>
<button
class=
"zoom-button"
@
click=
"scale('up')"
>
<Icon
type=
"md-add"
:size=
"14"
color=
"#fff"
/>
</button>
</div>
</
template
>
<
script
>
export
default
{
name
:
'ZoomController'
,
props
:
{
value
:
{
type
:
Number
,
default
:
100
},
step
:
{
type
:
Number
,
default
:
20
},
min
:
{
type
:
Number
,
default
:
10
},
max
:
{
type
:
Number
,
default
:
200
}
},
methods
:
{
scale
(
type
)
{
const
zoom
=
this
.
value
+
(
type
===
'down'
?
-
this
.
step
:
this
.
step
)
if
(
(
zoom
<
this
.
min
&&
type
===
'down'
)
||
(
zoom
>
this
.
max
&&
type
===
'up'
)
)
{
return
}
this
.
$emit
(
'input'
,
zoom
)
}
}
}
</
script
>
<
style
lang=
"less"
>
.trans(@duration) {
transition: ~"all @{duration} ease-in";
}
.zoom-wrapper {
.zoom-button {
width: 20px;
height: 20px;
line-height: 10px;
border-radius: 50%;
background: rgba(157, 162, 172, 1);
box-shadow: 0px 2px 8px 0px rgba(218, 220, 223, 0.7);
border: none;
cursor: pointer;
outline: none;
&:active {
box-shadow: 0px 0px 2px 2px rgba(218, 220, 223, 0.2) inset;
}
.trans(0.1s);
&:hover {
background: #1890ff;
.trans(0.1s);
}
}
.zoom-number {
color: #657180;
padding: 0 8px;
display: inline-block;
width: 46px;
text-align: center;
}
}
</
style
>
libs/tools.js
0 → 100644
View file @
be458b9d
export
const
forEach
=
(
arr
,
fn
)
=>
{
if
(
!
arr
.
length
||
!
fn
)
return
let
i
=
-
1
let
len
=
arr
.
length
while
(
++
i
<
len
)
{
let
item
=
arr
[
i
]
fn
(
item
,
i
,
arr
)
}
}
/**
* @param {Array} arr1
* @param {Array} arr2
* @description 得到两个数组的交集, 两个数组的元素为数值或字符串
*/
export
const
getIntersection
=
(
arr1
,
arr2
)
=>
{
let
len
=
Math
.
min
(
arr1
.
length
,
arr2
.
length
)
let
i
=
-
1
let
res
=
[]
while
(
++
i
<
len
)
{
const
item
=
arr2
[
i
]
if
(
arr1
.
indexOf
(
item
)
>
-
1
)
res
.
push
(
item
)
}
return
res
}
/**
* @param {Array} arr1
* @param {Array} arr2
* @description 得到两个数组的并集, 两个数组的元素为数值或字符串
*/
export
const
getUnion
=
(
arr1
,
arr2
)
=>
{
return
Array
.
from
(
new
Set
([...
arr1
,
...
arr2
]))
}
/**
* @param {Array} target 目标数组
* @param {Array} arr 需要查询的数组
* @description 判断要查询的数组是否至少有一个元素包含在目标数组中
*/
export
const
hasOneOf
=
(
targetarr
,
arr
)
=>
{
return
targetarr
.
some
(
_
=>
arr
.
indexOf
(
_
)
>
-
1
)
}
/**
* @param {String|Number} value 要验证的字符串或数值
* @param {*} validList 用来验证的列表
*/
export
function
oneOf
(
value
,
validList
)
{
for
(
let
i
=
0
;
i
<
validList
.
length
;
i
++
)
{
if
(
value
===
validList
[
i
])
{
return
true
}
}
return
false
}
/**
* @param {Number} timeStamp 判断时间戳格式是否是毫秒
* @returns {Boolean}
*/
const
isMillisecond
=
timeStamp
=>
{
const
timeStr
=
String
(
timeStamp
)
return
timeStr
.
length
>
10
}
/**
* @param {Number} timeStamp 传入的时间戳
* @param {Number} currentTime 当前时间时间戳
* @returns {Boolean} 传入的时间戳是否早于当前时间戳
*/
const
isEarly
=
(
timeStamp
,
currentTime
)
=>
{
return
timeStamp
<
currentTime
}
/**
* @param {Number} num 数值
* @returns {String} 处理后的字符串
* @description 如果传入的数值小于10,即位数只有1位,则在前面补充0
*/
const
getHandledValue
=
num
=>
{
return
num
<
10
?
'0'
+
num
:
num
}
/**
* @param {Number} timeStamp 传入的时间戳
* @param {Number} startType 要返回的时间字符串的格式类型,传入'year'则返回年开头的完整时间
*/
const
getDate
=
(
timeStamp
,
startType
)
=>
{
const
d
=
new
Date
(
timeStamp
*
1000
)
const
year
=
d
.
getFullYear
()
const
month
=
getHandledValue
(
d
.
getMonth
()
+
1
)
const
date
=
getHandledValue
(
d
.
getDate
())
const
hours
=
getHandledValue
(
d
.
getHours
())
const
minutes
=
getHandledValue
(
d
.
getMinutes
())
const
second
=
getHandledValue
(
d
.
getSeconds
())
let
resStr
=
''
if
(
startType
===
'year'
)
resStr
=
year
+
'-'
+
month
+
'-'
+
date
+
' '
+
hours
+
':'
+
minutes
+
':'
+
second
else
resStr
=
month
+
'-'
+
date
+
' '
+
hours
+
':'
+
minutes
return
resStr
}
/**
* @param {String|Number} timeStamp 时间戳
* @returns {String} 相对时间字符串
*/
export
const
getRelativeTime
=
timeStamp
=>
{
// 判断当前传入的时间戳是秒格式还是毫秒
const
IS_MILLISECOND
=
isMillisecond
(
timeStamp
)
// 如果是毫秒格式则转为秒格式
if
(
IS_MILLISECOND
)
Math
.
floor
(
timeStamp
/=
1000
)
// 传入的时间戳可以是数值或字符串类型,这里统一转为数值类型
timeStamp
=
Number
(
timeStamp
)
// 获取当前时间时间戳
const
currentTime
=
Math
.
floor
(
Date
.
parse
(
new
Date
())
/
1000
)
// 判断传入时间戳是否早于当前时间戳
const
IS_EARLY
=
isEarly
(
timeStamp
,
currentTime
)
// 获取两个时间戳差值
let
diff
=
currentTime
-
timeStamp
// 如果IS_EARLY为false则差值取反
if
(
!
IS_EARLY
)
diff
=
-
diff
let
resStr
=
''
const
dirStr
=
IS_EARLY
?
'前'
:
'后'
// 少于等于59秒
if
(
diff
<=
59
)
resStr
=
diff
+
'秒'
+
dirStr
// 多于59秒,少于等于59分钟59秒
else
if
(
diff
>
59
&&
diff
<=
3599
)
resStr
=
Math
.
floor
(
diff
/
60
)
+
'分钟'
+
dirStr
// 多于59分钟59秒,少于等于23小时59分钟59秒
else
if
(
diff
>
3599
&&
diff
<=
86399
)
resStr
=
Math
.
floor
(
diff
/
3600
)
+
'小时'
+
dirStr
// 多于23小时59分钟59秒,少于等于29天59分钟59秒
else
if
(
diff
>
86399
&&
diff
<=
2623859
)
resStr
=
Math
.
floor
(
diff
/
86400
)
+
'天'
+
dirStr
// 多于29天59分钟59秒,少于364天23小时59分钟59秒,且传入的时间戳早于当前
else
if
(
diff
>
2623859
&&
diff
<=
31567859
&&
IS_EARLY
)
resStr
=
getDate
(
timeStamp
)
else
resStr
=
getDate
(
timeStamp
,
'year'
)
return
resStr
}
/**
* @returns {String} 当前浏览器名称
*/
export
const
getExplorer
=
()
=>
{
const
ua
=
window
.
navigator
.
userAgent
const
isExplorer
=
(
exp
)
=>
{
return
ua
.
indexOf
(
exp
)
>
-
1
}
if
(
isExplorer
(
'MSIE'
))
return
'IE'
else
if
(
isExplorer
(
'Firefox'
))
return
'Firefox'
else
if
(
isExplorer
(
'Chrome'
))
return
'Chrome'
else
if
(
isExplorer
(
'Opera'
))
return
'Opera'
else
if
(
isExplorer
(
'Safari'
))
return
'Safari'
}
/**
* @description 绑定事件 on(element, event, handler)
*/
export
const
on
=
(
function
()
{
if
(
document
.
addEventListener
)
{
return
function
(
element
,
event
,
handler
)
{
if
(
element
&&
event
&&
handler
)
{
element
.
addEventListener
(
event
,
handler
,
false
)
}
}
}
else
{
return
function
(
element
,
event
,
handler
)
{
if
(
element
&&
event
&&
handler
)
{
element
.
attachEvent
(
'on'
+
event
,
handler
)
}
}
}
})()
/**
* @description 解绑事件 off(element, event, handler)
*/
export
const
off
=
(
function
()
{
if
(
document
.
removeEventListener
)
{
return
function
(
element
,
event
,
handler
)
{
if
(
element
&&
event
)
{
element
.
removeEventListener
(
event
,
handler
,
false
)
}
}
}
else
{
return
function
(
element
,
event
,
handler
)
{
if
(
element
&&
event
)
{
element
.
detachEvent
(
'on'
+
event
,
handler
)
}
}
}
})()
/**
* 判断一个对象是否存在key,如果传入第二个参数key,则是判断这个obj对象是否存在key这个属性
* 如果没有传入key这个参数,则判断obj对象是否有键值对
*/
export
const
hasKey
=
(
obj
,
key
)
=>
{
if
(
key
)
return
key
in
obj
else
{
let
keysArr
=
Object
.
keys
(
obj
)
return
keysArr
.
length
}
}
/**
* @param {*} obj1 对象
* @param {*} obj2 对象
* @description 判断两个对象是否相等,这两个对象的值只能是数字或字符串
*/
export
const
objEqual
=
(
obj1
,
obj2
)
=>
{
const
keysArr1
=
Object
.
keys
(
obj1
)
const
keysArr2
=
Object
.
keys
(
obj2
)
if
(
keysArr1
.
length
!==
keysArr2
.
length
)
return
false
else
if
(
keysArr1
.
length
===
0
&&
keysArr2
.
length
===
0
)
return
true
/* eslint-disable-next-line */
else
return
!
keysArr1
.
some
(
key
=>
obj1
[
key
]
!=
obj2
[
key
])
}
pages/basicData/department/index.vue
View file @
be458b9d
<
template
>
<div>
<div>
<div
class=
"fr"
>
<div
id=
"department"
>
<div
class=
"tr"
>
<Button
type=
"primary"
@
click=
"add()"
class=
"mb10"
>
新增部门
</Button>
</div>
<RadioGroup
v-model=
"img"
type=
"button"
size=
"small"
>
<Radio
:label=
"1"
><Icon
type=
"ios-apps"
/></Radio>
<Radio
:label=
"0"
><Icon
type=
"md-git-merge"
/></Radio>
</RadioGroup>
</div>
<div>
<TreeGrid
:columns=
"columns"
:items=
"treeData"
></TreeGrid>
<div
class=
"main"
>
<TreeGrid
v-if=
"img==1"
:columns=
"columns"
:items=
"treeData"
></TreeGrid>
<Org
v-else
:data=
"treeData"
@
nodeChange=
"nodeChange"
/>
</div>
<Modal
v-model=
"addModal"
title=
"新增部门"
width=
"800"
footer-hide
>
<Add
@
on-close=
"cancel"
@
on-ok=
"addOk"
/>
...
...
@@ -24,16 +27,19 @@ import Api from "./api";
import
Add
from
"./add"
;
import
AddRow
from
"./Addrow"
;
import
Edit
from
"./edit"
;
import
Org
from
"./org"
;
export
default
{
name
:
"list"
,
components
:
{
Add
,
Edit
,
AddRow
AddRow
,
Org
},
data
()
{
return
{
searchValue
:
""
,
//搜索库位名称
img
:
0
,
modal
:
{
title
:
""
,
width
:
1000
,
...
...
@@ -133,7 +139,9 @@ export default {
this
.
treeData
=
this
.
$u
.
toTree
(
r
.
result
.
items
,
0
,
undefined
,
(
u
)
=>
{
u
.
label
=
u
.
name
},
"parent_Id"
);
});
...
...
@@ -157,7 +165,19 @@ export default {
this
.
editModal
=
true
;
this
.
rowData
=
row
;
},
nodeChange
(
key
,
node
){
if
(
key
==
"edit"
){
this
.
edit
(
node
);
}
else
if
(
this
.
key
==
"delete"
){
this
.
remove
(
node
.
id
)
}
else
if
(
this
.
key
==
"add"
){
if
(
node
.
id
>
0
){
this
.
addrow
(
node
)
}
else
{
this
.
add
()
}
}
},
remove
(
id
)
{
this
.
editModal
=
false
;
this
.
deletelModal
=
true
;
...
...
@@ -191,4 +211,14 @@ export default {
};
</
script
>
<
style
lang=
"less"
>
#department{
height: calc(100vh - 150px);
overflow: auto;
.tr{
line-height: 50px;
}
.main{
height: 100%;
}
}
</
style
>
\ No newline at end of file
pages/basicData/department/org.vue
View file @
be458b9d
<
template
>
<div>
<Card
shadow
style=
"height:100%;width:100%;overflow:hidden; position: relative;"
>
<div
class=
"department-outer"
>
<div
class=
"zoom-box"
>
<zoom-controller
v-model=
"zoom"
:min=
"20"
:max=
"200"
></zoom-controller>
</div>
<div
class=
"view-box"
>
<org-view
:data=
"rootData"
:zoom-handled=
"zoomHandled"
@
on-menu-click=
"handleMenuClick"
></org-view>
</div>
</div>
</Card>
</
template
>
<
script
>
export
default
{
name
:
''
,
data
()
{
return
{
}
}
import
OrgView
from
"@/components/department/org-view.vue"
;
import
ZoomController
from
"@/components/department/zoom-controller.vue"
;
const
menuDic
=
{
edit
:
"编辑部门"
,
detail
:
"查看部门"
,
new
:
"新增子部门"
,
delete
:
"删除部门"
};
export
default
{
name
:
"org_tree_page"
,
components
:
{
OrgView
,
ZoomController
},
props
:
{
data
:
{
type
:
Array
,
default
:
()
=>
{
return
[];
}
}
},
data
()
{
return
{
modal
:
false
,
modal1
:
false
,
modalTitle
:
"新增部门"
,
information
:
{},
//部门信息
model
:
{
deptId
:
0
,
name
:
""
,
parentId
:
0
,
parentName
:
""
,
orderNum
:
1
},
root
:
{
deptId
:
0
,
name
:
"顶层"
,
label
:
"顶层"
,
parentId
:
0
,
parentName
:
""
,
orderNum
:
1
},
data
:
null
,
zoom
:
100
};
},
computed
:
{
zoomHandled
()
{
return
this
.
zoom
/
100
;
},
rootData
()
{
console
.
warn
(
this
.
data
);
var
root
=
{
id
:
0
,
label
:
"根节点"
,
children
:
this
.
data
};
return
root
;
}
},
methods
:
{
setDepartmentData
(
data
)
{
data
.
isRoot
=
true
;
return
data
;
},
handleMenuClick
({
data
,
key
})
{
// if (key == "edit") {
// this.model = {
// deptId: data.deptId,
// name: data.name,
// parentId: data.parentId,
// parentName: data.parentName,
// orderNum: data.orderNum
// };
// this.edit(data.id);
// }
this
.
$emit
(
"nodeChange"
,
key
,
data
)
},
},
mounted
()
{
console
.
warn
(
this
.
data
);
}
};
</
script
>
<
style
lang=
"less"
>
</
style
>
\ No newline at end of file
.tools {
position: absolute;
z-index: 66;
right: 10px;
}
.percent-100 {
width: 100%;
height: 100%;
}
.department-outer {
.percent-100;
overflow: hidden;
.tip-box {
position: absolute;
left: 20px;
top: 20px;
z-index: 12;
}
.zoom-box {
position: absolute;
right: 30px;
bottom: 30px;
z-index: 2;
}
.view-box {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
cursor: move;
.org-tree-drag-wrapper {
width: 100%;
height: 100%;
}
.org-tree-wrapper {
display: inline-block;
position: absolute;
left: 50%;
top: 50%;
transition: transform 0.2s ease-out;
.org-tree-node-label {
box-shadow: 0px 2px 12px 0px rgba(5, 123, 241, 0.4);
border-radius: 4px;
.org-tree-node-label-inner {
padding: 0;
.custom-org-node {
padding: 14px 41px;
background: #0672dd;
user-select: none;
word-wrap: none;
white-space: nowrap;
border-radius: 4px;
color: #ffffff;
font-size: 14px;
font-weight: 500;
line-height: 20px;
transition: background 0.1s ease-in;
cursor: default;
&:hover {
background: #4194e7;
transition: background 0.1s ease-in;
}
&.has-children-label {
cursor: pointer;
}
.context-menu {
position: absolute;
right: -10px;
bottom: 20px;
z-index: 10;
}
}
}
}
}
}
}
.info-p {
height: 40px;
line-height: 40px;
margin-left: 20px;
span {
font-size: 13px;
}
}
</
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment