Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
V
vue-quasar-admin-dev
概览
Overview
Details
Activity
Cycle Analytics
版本库
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
问题
0
Issues
0
列表
Board
标记
里程碑
合并请求
0
Merge Requests
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
Snippets
成员
Members
Collapse sidebar
Close sidebar
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
吴超
vue-quasar-admin-dev
Commits
3de9a072
Commit
3de9a072
authored
Apr 27, 2026
by
周海峰
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
优化
parent
9f535b90
隐藏空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
1785 行增加
和
23 行删除
+1785
-23
README.md
+1
-1
src/components/organization-ant_bak.vue
+769
-0
src/components/organization-dept-panel.vue
+572
-0
src/pages/permission/roleuser.vue
+122
-22
src/pages/permission/roleuser_bak.vue
+286
-0
src/service/user/user.js
+35
-0
没有找到文件。
README.md
View file @
3de9a072
部署地址:10.12.9.9 /home/docker-nginx/nginx_auth/html-new/platform-auth
## 功能与特点
## 功能与特点
...
...
src/components/organization-ant_bak.vue
0 → 100644
View file @
3de9a072
<
template
>
<div
style=
"position: relative;"
>
<!-- Loading遮罩层 - 覆盖树的半透明效果 -->
<div
class=
"tree-loading-overlay"
:class=
"
{ 'is-loading': checkLoading }"
>
<div
class=
"tree-loading-content"
>
<q-spinner-orbit
color=
"primary"
size=
"40px"
/>
<span
class=
"tree-loading-text"
>
处理中...
</span>
</div>
</div>
<div
class=
"tree-wrapper"
:class=
"
{ 'is-dimmed': checkLoading }">
<div
class=
"search-container"
>
<div
class=
"search-wrapper"
>
<q-input
v-model=
"searchText"
clearable
class=
"search-input"
label=
"搜索(支持两字以上的名字搜索)"
placeholder=
"输入搜索并选择"
@
input=
"onSearchInput"
@
focus=
"showSearchMenu = true"
@
blur=
"hideSearchMenu"
>
<template
v-slot:prepend
>
<q-icon
name=
"search"
class=
"search-icon"
/>
</
template
>
<
template
v-slot:append
>
<q-btn
flat
dense
round
icon=
"search"
class=
"search-btn"
@
click=
"manualSearch"
/>
</
template
>
</q-input>
<!-- 搜索菜单 -->
<div
v-show=
"showSearchMenu"
class=
"search-menu"
>
<div
class=
"search-results"
v-if=
"searchOptions.length > 0"
>
<div
v-for=
"option in searchOptions"
:key=
"option.key"
class=
"search-result-item"
@
click=
"selectSearchOption(option)"
>
<div
class=
"search-result-avatar"
>
<q-icon
:name=
"option.type === 'department' ? 'business' : 'person'"
:color=
"option.type === 'department' ? 'primary' : 'positive'"
size=
"sm"
/>
</div>
<div
class=
"search-result-content"
>
<div
class=
"search-result-label"
>
{{ option.label }}
</div>
<div
class=
"search-result-caption"
>
{{ option.type === 'department' ? '部门' : '人员' }}
<span
v-if=
"option.key < 1000000 && option.key"
class=
"search-result-id"
>
ID: {{ option.key }}
</span>
</div>
</div>
<div
class=
"search-result-side"
>
<q-icon
name=
"chevron_right"
size=
"sm"
color=
"grey-5"
/>
</div>
</div>
</div>
<!-- 无搜索结果 -->
<div
v-else-if=
"searchText && searchText.length > 0"
class=
"no-results"
>
<div
class=
"no-result-item"
>
<div
class=
"no-result-avatar"
>
<q-icon
name=
"info"
color=
"grey-5"
size=
"sm"
/>
</div>
<div
class=
"no-result-content"
>
<div
class=
"no-results-text"
>
没有找到匹配的人员或部门
</div>
<div
class=
"no-results-hint"
>
请尝试其他关键词
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
style=
"padding-top: 6vh;"
>
<Tree
v-if=
"treeData.length > 0"
ref=
"tree"
checkable
:defaultExpandedKeys=
"defaultExpandedKeys"
:checkedKeys=
"checkedKeys"
:treeData=
"treeData"
@
check=
"onCheck"
>
<
template
slot=
"title"
slot-scope=
"node"
>
<span
style=
"color: rgb(33, 186, 69);"
v-if=
"checkedKeys.indexOf(node.key)>-1"
>
√
</span>
<span
v-if=
"node.title.indexOf(searchValue) > -1"
>
{{
node
.
title
.
substr
(
0
,
node
.
title
.
indexOf
(
searchValue
))
}}
<span
style=
"color: #f50"
>
{{
searchValue
}}
</span>
{{
node
.
title
.
substr
(
node
.
title
.
indexOf
(
searchValue
)
+
searchValue
.
length
)
}}
<span
style=
"color: #64b5fe"
>
{{
node
.
no
?
node
.
no
:
''
}}
</span>
</span>
<span
v-else
>
{{
node
.
title
}}
<span
style=
"color: #64b5fe;"
>
{{
node
.
no
?
node
.
no
:
''
}}
</span>
</span>
</
template
>
</Tree>
</div>
</div>
<div
v-show=
"!treeData.length > 0"
>
正在加载组织机构组件...
</div>
</div>
</template>
<
script
>
import
Tree
from
'ant-design-vue/lib/tree'
;
import
'ant-design-vue/dist/antd.css'
;
import
AInputSearch
from
"ant-design-vue/es/input/Search"
;
import
{
deptall
}
from
"@/service/user/user"
;
const
dataList
=
[]
const
generateList
=
(
data
)
=>
{
for
(
let
i
=
0
;
i
<
data
.
length
;
i
++
)
{
//高首骊
const
node
=
data
[
i
]
// const key = node.key
dataList
.
push
({
//key,
title
:
node
.
title
,
key
:
node
.
key
,
no
:
node
.
no
,
avatar
:
node
.
avatar
,
id
:
node
.
id
,
label
:
node
.
label
,
pid
:
node
.
pid
,
scopedSlots
:
{
title
:
'title'
}
})
if
(
node
.
children
)
{
generateList
(
node
.
children
,
node
.
key
)
}
node
.
scopedSlots
=
{
title
:
'title'
}
}
}
const
getParentKey
=
(
key
,
tree
)
=>
{
let
parentKey
for
(
let
i
=
0
;
i
<
tree
.
length
;
i
++
)
{
const
node
=
tree
[
i
]
if
(
node
.
children
)
{
if
(
node
.
children
.
some
(
item
=>
item
.
key
===
key
))
{
parentKey
=
node
.
key
}
else
if
(
getParentKey
(
key
,
node
.
children
))
{
parentKey
=
getParentKey
(
key
,
node
.
children
)
}
}
}
return
parentKey
}
export
default
{
components
:
{
AInputSearch
,
Tree
},
name
:
"organization-ant"
,
data
()
{
return
{
treeData
:
[],
searchValue
:
''
,
searchText
:
''
,
// 自定义
expandedKeys
:
[
1000001
],
defaultExpandedKeys
:
[
1000001
],
autoExpandParent
:
true
,
// 复选模式下的选中key(包含部门和人员)
checkedKeys
:
[],
// 准备提交的选中的部门
checkedDepartment
:
[],
// 准备提交的选中的人类对象
checkedPeople
:
[],
// 节点缓存,用于快速查找
nodeCache
:
null
,
// 搜索相关
searchOptions
:
[],
showSearchMenu
:
false
,
// 勾选操作loading状态
checkLoading
:
false
,
// 保存body原始样式
originalBodyStyle
:
null
};
},
props
:
{
value
:
{
type
:
Array
}
},
computed
:
{
getTreeData
()
{
// this.initTree();
// generateList(this.$store.state.orgTreeData)
// console.log(this.treeData)
if
(
this
.
treeData
.
length
>
0
)
{
console
.
log
(
'计算属性,组织机构,ok'
);
this
.
$emit
(
'ok'
,
'ok'
)
}
return
this
.
treeData
;
},
getselectKeysStr
(){
return
this
.
checkedKeys
.
map
(
item
=>
item
).
join
(
","
)
}
},
created
()
{
this
.
initTree
();
},
mounted
()
{
},
methods
:
{
// 初始化树
async
initTree
(){
let
Res
=
await
deptall
();
let
resdata
=
Res
.
data
.
data
;
this
.
treeData
.
push
(
resdata
)
generateList
(
this
.
treeData
);
// 构建节点缓存
this
.
nodeCache
=
new
Map
();
this
.
buildNodeCache
(
this
.
treeData
);
},
onExpand
(
expandedKeys
)
{
// console.log('onExpand', expandedKeys)
// if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
this
.
expandedKeys
=
expandedKeys
this
.
autoExpandParent
=
false
},
onCheck
(
checkedKeys
,
e
)
{
console
.
log
(
'onCheck'
,
checkedKeys
,
e
);
// 显示loading
this
.
checkLoading
=
true
;
console
.
log
(
'Loading set to true:'
,
this
.
checkLoading
);
// 防止页面滚动
this
.
preventBodyScroll
();
// 强制Vue重新渲染
this
.
$forceUpdate
();
// 使用setTimeout强制分离到下一个事件循环,确保loading先显示
setTimeout
(()
=>
{
console
.
log
(
'Processing check operation...'
);
this
.
processCheckOperation
(
checkedKeys
,
e
);
},
0
);
},
// 分离的处理逻辑
processCheckOperation
(
checkedKeys
,
e
)
{
try
{
// 更新本地状态
this
.
checkedKeys
=
checkedKeys
;
// 更新选中的人员列表
this
.
updateSelectedStaff
();
// 双向绑定会通过watch自动处理
// 处理完成后,延迟一点时间再隐藏loading,确保用户能看到
setTimeout
(()
=>
{
this
.
checkLoading
=
false
;
// 恢复页面滚动
this
.
restoreBodyScroll
();
},
500
);
// 最少显示200ms,让用户看到反馈
}
catch
(
error
)
{
console
.
error
(
'勾选处理出错:'
,
error
);
this
.
checkLoading
=
false
;
// 出错时也要隐藏loading
this
.
restoreBodyScroll
();
// 恢复页面滚动
}
},
// 防止body滚动
preventBodyScroll
()
{
// 保存原始样式
if
(
!
this
.
originalBodyStyle
)
{
this
.
originalBodyStyle
=
{
overflow
:
document
.
body
.
style
.
overflow
,
position
:
document
.
body
.
style
.
position
};
}
// 防止滚动
document
.
body
.
style
.
overflow
=
'hidden'
;
document
.
body
.
style
.
position
=
'fixed'
;
document
.
body
.
style
.
width
=
'100%'
;
},
// 恢复body滚动
restoreBodyScroll
()
{
// 恢复原始样式
if
(
this
.
originalBodyStyle
)
{
document
.
body
.
style
.
overflow
=
this
.
originalBodyStyle
.
overflow
||
''
;
document
.
body
.
style
.
position
=
this
.
originalBodyStyle
.
position
||
''
;
document
.
body
.
style
.
width
=
''
;
this
.
originalBodyStyle
=
null
;
}
},
onSelect
(
selectedKeys
,
info
)
{
// 在复选模式下,onSelect 可以用来处理一些辅助功能,比如展开/折叠
// console.log('onSelect', selectedKeys, info);
},
// 搜索输入处理
onSearchInput
(
value
)
{
this
.
searchValue
=
value
||
''
;
if
(
value
&&
value
.
length
>
0
)
{
// 过滤搜索选项
this
.
searchOptions
=
dataList
.
filter
(
item
=>
item
.
title
==
value
||
item
.
no
==
value
)
.
map
(
item
=>
({
key
:
item
.
key
,
label
:
item
.
title
,
displayLabel
:
`
${
item
.
title
}${
item
.
no
?
' ('
+
item
.
no
+
')'
:
''
}${
item
.
key
>=
1000000
?
' [部门]'
:
' [人员]'
}
`
,
type
:
item
.
key
>=
1000000
?
'department'
:
'person'
}))
.
slice
(
0
,
10
);
// 限制最多显示10个结果
this
.
showSearchMenu
=
true
;
}
else
{
this
.
searchOptions
=
[];
this
.
showSearchMenu
=
false
;
}
},
// 手动搜索
manualSearch
()
{
if
(
this
.
searchText
&&
this
.
searchText
.
length
>
0
)
{
this
.
onSearchInput
(
this
.
searchText
);
}
},
// 隐藏搜索菜单
hideSearchMenu
()
{
// 延迟隐藏,以便点击选项时能正常触发
setTimeout
(()
=>
{
this
.
showSearchMenu
=
false
;
},
500
);
},
// 选择搜索选项
selectSearchOption
(
option
)
{
this
.
checkLoading
=
true
;
// 强制Vue重新渲染
this
.
$forceUpdate
();
console
.
log
(
option
,
'selectSearchOption======='
)
// 清空搜索状态
this
.
searchText
=
''
;
this
.
searchOptions
=
[];
this
.
showSearchMenu
=
false
;
setTimeout
(()
=>
{
// 勾选选中的节点
if
(
!
this
.
checkedKeys
.
includes
(
option
.
key
))
{
this
.
checkedKeys
=
[
option
.
key
,
...
this
.
checkedKeys
];
}
else
{
//根据 key 移除
this
.
checkedKeys
=
this
.
checkedKeys
.
filter
(
item
=>
item
!==
option
.
key
);
}
this
.
updateSelectedStaff
();
this
.
checkLoading
=
false
;
},
0
);
},
// 获取节点到根节点的路径
getParentKeys
(
key
,
treeData
,
parents
=
[])
{
for
(
let
node
of
treeData
)
{
if
(
node
.
key
===
key
)
{
return
parents
;
}
if
(
node
.
children
)
{
const
found
=
this
.
getParentKeys
(
key
,
node
.
children
,
[...
parents
,
node
.
key
]);
if
(
found
.
length
>
parents
.
length
)
{
return
found
;
}
}
}
return
parents
;
},
// 原有的搜索方法,现在主要用于高亮显示
onChange
(
value
)
{
if
(
!
value
)
{
this
.
searchValue
=
''
;
return
;
}
this
.
searchValue
=
value
;
// 展开包含搜索结果的节点
// const expandedKeys = dataList.map((item) => {
// if (item.title.indexOf(value) > -1) {
// return getParentKey(item.key, this.getTreeData)
// }
// return null
// }).filter((item, i, self) => item && self.indexOf(item) === i)
// if (expandedKeys.length > 0) {
// this.expandedKeys = [...new Set([...this.expandedKeys, ...expandedKeys])];
// this.autoExpandParent = true;
// }
},
// 获取节点下的所有叶子节点key
getAllLeafKeys
(
nodes
)
{
let
leafKeys
=
[];
nodes
.
forEach
(
node
=>
{
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
// 如果有子节点,递归获取
leafKeys
=
leafKeys
.
concat
(
this
.
getAllLeafKeys
(
node
.
children
));
}
else
{
// 如果是叶子节点(人员),添加到结果中
if
(
node
.
key
<
1000000
)
{
// 小于1000000的是人员
leafKeys
.
push
(
node
.
key
);
}
}
});
return
leafKeys
;
},
// 更新选中的人员列表
updateSelectedStaff
()
{
this
.
checkedPeople
=
dataList
.
filter
(
node
=>
node
.
key
<
1000000
&&
this
.
checkedKeys
.
includes
(
node
.
key
))
// 向父组件发送选中的人员列表
this
.
$emit
(
'staffNode'
,
this
.
checkedPeople
);
},
// 构建节点缓存,避免每次查找都遍历树
buildNodeCache
(
nodes
)
{
nodes
.
forEach
(
node
=>
{
this
.
nodeCache
.
set
(
node
.
key
,
node
);
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
this
.
buildNodeCache
(
node
.
children
);
}
});
},
orderStaffNode
(){
//对选择的人根据选择顺序排序
let
staffNode
=
[]
for
(
var
i
=
0
;
i
<
this
.
checkedKeys
.
length
;
i
++
){
let
key
=
this
.
checkedKeys
[
i
]
if
(
key
<
1000000
){
//小于百万的为人员。大于为机构
let
node
=
this
.
checkedPeople
.
filter
(
item
=>
item
.
key
==
key
)[
0
]
if
(
node
){
staffNode
.
push
(
node
);
}
}
}
return
staffNode
}
},
watch
:
{
checkedKeys
:
{
handler
(
val
)
{
// 当 checkedKeys 改变时,同步更新 value,但避免循环
const
filteredKeys
=
val
.
filter
(
key
=>
key
<
1000000
);
// 只传递人员key给父组件
// 只有当过滤后的key与当前value不同时才emit,避免循环
if
(
JSON
.
stringify
(
filteredKeys
)
!==
JSON
.
stringify
(
this
.
value
||
[]))
{
this
.
$emit
(
'input'
,
filteredKeys
);
}
},
deep
:
true
},
value
:
{
handler
(
newValue
)
{
// 只有当树已经加载完成且新值与当前checkedKeys中的人员key不同时才更新,避免循环
if
(
this
.
treeData
.
length
>
0
)
{
const
currentStaffKeys
=
this
.
checkedKeys
.
filter
(
key
=>
key
<
1000000
);
if
(
JSON
.
stringify
(
newValue
||
[])
!==
JSON
.
stringify
(
currentStaffKeys
))
{
this
.
checkedKeys
=
newValue
||
[];
if
(
this
.
checkedKeys
.
length
>
0
)
{
this
.
updateSelectedStaff
();
}
}
}
},
immediate
:
true
}
},
}
// 筛选方法 结束
</
script
>
<
style
scoped
>
.search-container
{
position
:
fixed
;
width
:
17vw
;
z-index
:
9999
;
padding
:
0
1vh
;
}
.search-wrapper
{
position
:
relative
;
}
.search-input
{
padding-top
:
10px
;
padding-left
:
5px
;
border-radius
:
4px
!important
;
background
:
linear-gradient
(
135deg
,
#f8f9fa
0%
,
#ffffff
100%
);
/* box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); */
transition
:
all
0.3s
cubic-bezier
(
0.4
,
0
,
0.2
,
1
);
}
.search-input
:hover
{
box-shadow
:
0
6px
20px
rgba
(
0
,
0
,
0
,
0.12
);
transform
:
translateY
(
-1px
);
}
.search-input
:focus-within
{
box-shadow
:
0
8px
25px
rgba
(
33
,
150
,
243
,
0.15
);
transform
:
translateY
(
-2px
);
}
.search-input
>>>
.q-field__control
{
border-radius
:
12px
;
padding
:
8px
16px
;
}
.search-input
>>>
.q-field__label
{
font-weight
:
500
;
color
:
#666
;
font-size
:
12px
;
}
.search-input
>>>
.q-field__native
{
font-size
:
14px
;
font-weight
:
400
;
padding-left
:
12px
!important
;
padding-top
:
2px
!important
;
padding-bottom
:
2px
!important
;
}
.search-input
>>>
.q-field__native
::placeholder
{
font-size
:
14px
;
color
:
#999
;
position
:
relative
;
left
:
0px
;
top
:
0px
;
transform
:
none
;
}
.search-icon
{
color
:
#999
;
transition
:
color
0.3s
ease
;
}
.search-input
:hover
.search-icon
{
color
:
#1976d2
;
}
.search-btn
{
margin-right
:
4px
;
transition
:
all
0.3s
ease
;
}
.search-btn
:hover
{
background-color
:
rgba
(
25
,
118
,
210
,
0.1
);
transform
:
scale
(
1.05
);
}
.search-menu
{
border-radius
:
12px
;
box-shadow
:
0
8px
32px
rgba
(
0
,
0
,
0
,
0.12
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.2
);
backdrop-filter
:
blur
(
8px
);
background
:
rgba
(
255
,
255
,
255
,
0.95
);
min-width
:
16vw
;
max-width
:
16vw
;
max-height
:
300px
;
overflow-y
:
auto
;
position
:
absolute
;
top
:
100%
;
left
:
0
;
right
:
0
;
z-index
:
1000
;
margin-top
:
8px
;
}
.search-results
{
padding
:
8px
0
;
}
.search-result-item
{
display
:
flex
;
align-items
:
center
;
padding
:
12px
16px
;
border-radius
:
8px
;
margin
:
2px
8px
;
cursor
:
pointer
;
transition
:
all
0.2s
ease
;
border
:
1px
solid
transparent
;
}
.search-result-item
:hover
{
background-color
:
rgba
(
25
,
118
,
210
,
0.08
);
transform
:
translateX
(
2px
);
border-color
:
rgba
(
25
,
118
,
210
,
0.1
);
}
.search-result-avatar
{
flex-shrink
:
0
;
width
:
40px
;
height
:
40px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border-radius
:
50%
;
background-color
:
rgba
(
0
,
0
,
0
,
0.04
);
margin-right
:
12px
;
}
.search-result-content
{
flex
:
1
;
min-width
:
0
;
}
.search-result-side
{
flex-shrink
:
0
;
margin-left
:
8px
;
opacity
:
0.6
;
}
.search-result-label
{
font-weight
:
500
;
color
:
#333
;
font-size
:
14px
;
}
.search-result-caption
{
font-size
:
12px
;
color
:
#666
;
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
}
.search-result-id
{
background-color
:
rgba
(
102
,
102
,
102
,
0.1
);
padding
:
2px
6px
;
border-radius
:
4px
;
font-size
:
11px
;
font-weight
:
500
;
}
.no-results
{
padding
:
16px
;
text-align
:
center
;
}
.no-result-item
{
display
:
flex
;
align-items
:
center
;
padding
:
12px
16px
;
justify-content
:
center
;
}
.no-result-avatar
{
flex-shrink
:
0
;
width
:
40px
;
height
:
40px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border-radius
:
50%
;
background-color
:
rgba
(
0
,
0
,
0
,
0.04
);
margin-right
:
12px
;
}
.no-result-content
{
flex
:
1
;
text-align
:
left
;
}
.no-results-text
{
color
:
#666
;
font-weight
:
500
;
font-size
:
14px
;
}
.no-results-hint
{
color
:
#999
;
font-size
:
12px
;
margin-top
:
4px
;
}
/* 滚动条美化 */
.search-menu
::-webkit-scrollbar
{
width
:
6px
;
}
.search-menu
::-webkit-scrollbar-track
{
background
:
rgba
(
0
,
0
,
0
,
0.05
);
border-radius
:
3px
;
}
.search-menu
::-webkit-scrollbar-thumb
{
background
:
rgba
(
0
,
0
,
0
,
0.2
);
border-radius
:
3px
;
}
.search-menu
::-webkit-scrollbar-thumb:hover
{
background
:
rgba
(
0
,
0
,
0
,
0.3
);
}
/* 树组件的半透明Loading遮罩层 */
.tree-loading-overlay
{
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
bottom
:
0
;
background-color
:
rgba
(
255
,
255
,
255
,
0.75
);
backdrop-filter
:
blur
(
2px
);
z-index
:
100
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border-radius
:
8px
;
opacity
:
0
;
visibility
:
hidden
;
transition
:
opacity
0.2s
ease
,
visibility
0.2s
ease
;
}
.tree-loading-overlay.is-loading
{
opacity
:
1
;
visibility
:
visible
;
}
.tree-wrapper
{
transition
:
opacity
0.2s
ease
;
}
.tree-wrapper.is-dimmed
{
opacity
:
0.3
;
}
.tree-loading-text
{
color
:
#666
;
font-size
:
14px
;
font-weight
:
500
;
}
/* 响应式设计 */
@media
(
max-width
:
768px
)
{
.search-container
{
width
:
90vw
;
}
.search-menu
{
min-width
:
90vw
;
max-width
:
90vw
;
}
}
</
style
>
src/components/organization-dept-panel.vue
0 → 100644
View file @
3de9a072
<
template
>
<div
style=
"position: relative;"
>
<div
class=
"tree-wrapper"
>
<div
class=
"search-container"
>
<div
class=
"search-wrapper"
>
<q-input
v-model=
"searchText"
clearable
class=
"search-input"
label=
"搜索部门"
placeholder=
"输入部门名称搜索"
@
input=
"onSearchInput"
@
focus=
"showSearchMenu = true"
@
blur=
"hideSearchMenu"
>
<template
v-slot:prepend
>
<q-icon
name=
"search"
class=
"search-icon"
/>
</
template
>
</q-input>
<!-- 搜索结果菜单 -->
<div
v-show=
"showSearchMenu"
class=
"search-menu"
>
<div
class=
"search-results"
v-if=
"searchResults.length > 0"
>
<div
v-for=
"item in searchResults"
:key=
"item.key"
class=
"search-result-item"
@
click=
"onSearchResultClick(item)"
>
<div
class=
"search-result-avatar"
>
<q-icon
name=
"business"
color=
"primary"
size=
"sm"
/>
</div>
<div
class=
"search-result-content"
>
<div
class=
"search-result-label"
>
{{ item.title }}
</div>
</div>
<div
class=
"search-result-side"
>
<q-icon
name=
"keyboard_arrow_right"
size=
"sm"
color=
"grey-5"
/>
</div>
</div>
</div>
<div
v-else-if=
"searchText && searchText.length > 0"
class=
"no-results"
>
没有找到匹配的部门
</div>
</div>
</div>
</div>
<div
style=
"padding-top: 6vh;"
>
<Tree
v-if=
"treeData.length > 0"
:expandedKeys=
"expandedKeys"
:autoExpandParent=
"autoExpandParent"
:treeData=
"treeData"
@
expand=
"onExpand"
>
<
template
slot=
"title"
slot-scope=
"node"
>
<span
style=
"display: inline-flex; align-items: center; width: 100%;"
>
<span
style=
"flex: 1;"
>
{{
node
.
title
}}
</span>
<!-- 编辑按钮 -->
<q-btn
flat
round
dense
icon=
"edit"
size=
"sm"
class=
"dept-edit-btn"
@
click
.
stop=
"onDeptBtnClick(node)"
/>
</span>
</
template
>
</Tree>
</div>
</div>
<div
v-show=
"!treeData.length"
>
正在加载组织机构...
</div>
<!-- 部门用户选择弹窗 -->
<q-modal
v-model=
"deptModal"
maximized
>
<q-modal-layout>
<q-toolbar
slot=
"header"
color=
"primary"
>
<q-btn
flat
round
dense
@
click=
"deptModal = false"
icon=
"reply"
/>
<q-toolbar-title>
{{ currentDept ? currentDept.title : '' }} — 部门用户
</q-toolbar-title>
</q-toolbar>
<q-toolbar
slot=
"footer"
>
<q-toolbar-title></q-toolbar-title>
<q-btn
round
color=
"red"
@
click=
"confirmDeptSelection"
>
确认
</q-btn>
<q-btn
round
@
click=
"deptModal = false"
>
取消
</q-btn>
</q-toolbar>
<div
class=
"q-pa-md"
>
<q-input
v-model=
"deptUserSearchText"
clearable
outlined
dense
placeholder=
"搜索部门用户..."
class=
"q-mb-md"
>
<
template
v-slot:prepend
>
<q-icon
name=
"search"
/>
</
template
>
</q-input>
<div
class=
"select-all-row"
>
<q-checkbox
:value=
"filteredDeptUsers.length > 0 && filteredDeptUsers.every(u => getCurrentCheckedSet().has(u.key))"
:indeterminate=
"filteredDeptUsers.some(u => getCurrentCheckedSet().has(u.key)) && !filteredDeptUsers.every(u => getCurrentCheckedSet().has(u.key))"
@
input=
"toggleSelectAll"
/>
<span
class=
"select-all-label"
>
全选
</span>
</div>
<div
v-if=
"deptUserLoading"
class=
"text-center q-pa-md text-grey-6"
>
<q-spinner-dots
size=
"32px"
/>
加载中...
</div>
<div
v-else-if=
"filteredDeptUsers.length === 0"
class=
"text-center q-pa-md text-grey-5"
>
该部门下暂无用户
</div>
<div
v-else
class=
"dept-user-list"
>
<div
v-for=
"user in filteredDeptUsers"
:key=
"user.key"
class=
"dept-user-item"
@
click=
"toggleTempUser(user.key)"
>
<q-checkbox
:value=
"deptUserSearchText ? searchCheckedUsers.has(user.key) : tempCheckedUsers.has(user.key)"
@
input=
"toggleTempUser(user.key)"
/>
<div
class=
"dept-user-info"
>
<div
class=
"dept-user-name"
>
{{ user.title }}
</div>
<div
class=
"dept-user-no"
v-if=
"user.no"
>
{{ user.no }}
</div>
</div>
</div>
</div>
<div
class=
"text-grey-6 text-center q-mt-md"
>
已选 {{ tempCheckedUsers.size }} 人
</div>
</div>
</q-modal-layout>
</q-modal>
</div>
</template>
<
script
>
import
Tree
from
'ant-design-vue/lib/tree'
;
import
'ant-design-vue/dist/antd.css'
;
import
{
deptTree
,
getDeptUsers
}
from
"@/service/user/user"
;
let
cachedTreeData
=
null
;
export
default
{
name
:
"organization-dept-panel"
,
components
:
{
Tree
},
props
:
{
value
:
{
type
:
Array
,
default
:
()
=>
[]
}
},
data
()
{
return
{
treeData
:
[],
expandedKeys
:
[],
autoExpandParent
:
true
,
searchText
:
''
,
showSearchMenu
:
false
,
searchResults
:
[],
// 部门弹窗
deptModal
:
false
,
currentDept
:
null
,
deptUsers
:
[],
// 部门下的所有用户
deptUserSearchText
:
''
,
deptUserLoading
:
false
,
tempCheckedUsers
:
new
Set
(),
// 弹窗内临时勾选状态
searchCheckedUsers
:
new
Set
(),
// 搜索框内临时勾选状态
// 已选用户集合(主数据源,同步 orgList)
selectedUserIds
:
new
Set
(),
};
},
computed
:
{
filteredDeptUsers
()
{
if
(
!
this
.
deptUserSearchText
)
return
this
.
deptUsers
;
const
kw
=
this
.
deptUserSearchText
.
toLowerCase
();
return
this
.
deptUsers
.
filter
(
u
=>
(
u
.
title
||
''
).
toLowerCase
().
includes
(
kw
)
||
(
u
.
no
||
''
).
toLowerCase
().
includes
(
kw
)
);
}
},
watch
:
{
value
:
{
handler
(
newVal
)
{
this
.
selectedUserIds
=
new
Set
(
newVal
||
[]);
},
immediate
:
true
}
},
created
()
{
this
.
initTree
();
},
methods
:
{
async
initTree
()
{
if
(
cachedTreeData
)
{
this
.
treeData
=
cachedTreeData
;
this
.
expandedKeys
=
this
.
treeData
.
length
>
0
?
[
this
.
treeData
[
0
].
key
]
:
[];
return
;
}
let
Res
=
await
deptTree
();
cachedTreeData
=
[
Res
.
data
.
data
];
this
.
treeData
=
cachedTreeData
;
this
.
expandedKeys
=
this
.
treeData
.
length
>
0
?
[
this
.
treeData
[
0
].
key
]
:
[];
},
onExpand
(
expandedKeys
)
{
this
.
expandedKeys
=
expandedKeys
;
this
.
autoExpandParent
=
false
;
},
// 搜索部门
onSearchInput
(
value
)
{
if
(
!
value
||
value
.
length
<
1
)
{
this
.
searchResults
=
[];
return
;
}
const
kw
=
value
.
toLowerCase
();
const
results
=
[];
const
searchTree
=
(
nodes
)
=>
{
for
(
const
node
of
nodes
)
{
if
((
node
.
title
||
''
).
toLowerCase
().
includes
(
kw
))
{
results
.
push
({
key
:
node
.
key
,
title
:
node
.
title
});
}
if
(
node
.
children
)
searchTree
(
node
.
children
);
}
};
searchTree
(
this
.
treeData
);
this
.
searchResults
=
results
.
slice
(
0
,
20
);
},
hideSearchMenu
()
{
setTimeout
(()
=>
{
this
.
showSearchMenu
=
false
;
},
200
);
},
onSearchResultClick
(
item
)
{
this
.
searchText
=
item
.
title
;
this
.
showSearchMenu
=
false
;
// 展开到该节点
if
(
!
this
.
expandedKeys
.
includes
(
item
.
key
))
{
this
.
expandedKeys
.
push
(
item
.
key
);
}
this
.
onDeptBtnClick
(
item
);
},
// 点击部门编辑按钮
async
onDeptBtnClick
(
dept
)
{
this
.
currentDept
=
dept
;
this
.
deptUserSearchText
=
''
;
this
.
deptModal
=
true
;
// 懒加载部门用户
if
(
dept
.
key
>=
1000000
)
{
const
realDeptId
=
dept
.
key
-
1000000
;
this
.
deptUserLoading
=
true
;
this
.
deptUsers
=
[];
try
{
let
res
=
await
getDeptUsers
(
realDeptId
,
1
,
100
);
let
users
=
(
res
.
data
.
data
.
list
||
res
.
data
.
data
)
||
[];
this
.
deptUsers
=
users
.
map
(
u
=>
({
key
:
u
.
id
,
title
:
u
.
title
||
u
.
label
||
''
,
no
:
u
.
no
||
''
}));
}
catch
(
e
)
{
console
.
error
(
'loadDeptUsers error:'
,
e
);
this
.
deptUsers
=
[];
}
finally
{
this
.
deptUserLoading
=
false
;
// deptUsers 填充后再初始化勾选状态
this
.
tempCheckedUsers
=
new
Set
(
[...
this
.
selectedUserIds
].
filter
(
id
=>
this
.
deptUsers
.
some
(
u
=>
u
.
key
===
id
))
);
}
}
else
{
// 部门key
<
1000000
,直接从树节点
children
取
this
.
deptUsers
=
(
dept
.
children
||
[]).
map
(
u
=>
({
key
:
u
.
key
,
title
:
u
.
title
||
u
.
label
||
''
,
no
:
u
.
no
||
''
}));
// deptUsers 填充后再初始化勾选状态
this
.
tempCheckedUsers
=
new
Set
(
[...
this
.
selectedUserIds
].
filter
(
id
=>
this
.
deptUsers
.
some
(
u
=>
u
.
key
===
id
))
);
}
},
toggleTempUser
(
userKey
)
{
const
currentSet
=
this
.
deptUserSearchText
?
this
.
searchCheckedUsers
:
this
.
tempCheckedUsers
;
const
newSet
=
new
Set
(
currentSet
);
if
(
newSet
.
has
(
userKey
))
{
newSet
.
delete
(
userKey
);
}
else
{
newSet
.
add
(
userKey
);
}
if
(
this
.
deptUserSearchText
)
{
this
.
searchCheckedUsers
=
newSet
;
}
else
{
this
.
tempCheckedUsers
=
newSet
;
}
},
getCurrentCheckedSet
()
{
return
this
.
deptUserSearchText
?
this
.
searchCheckedUsers
:
this
.
tempCheckedUsers
;
},
toggleSelectAll
(
checked
)
{
const
currentSet
=
this
.
deptUserSearchText
?
this
.
searchCheckedUsers
:
this
.
tempCheckedUsers
;
if
(
checked
)
{
this
.
filteredDeptUsers
.
forEach
(
u
=>
currentSet
.
add
(
u
.
key
));
}
else
{
this
.
filteredDeptUsers
.
forEach
(
u
=>
currentSet
.
delete
(
u
.
key
));
}
if
(
this
.
deptUserSearchText
)
{
this
.
searchCheckedUsers
=
new
Set
(
currentSet
);
}
else
{
this
.
tempCheckedUsers
=
new
Set
(
currentSet
);
}
},
confirmDeptSelection
()
{
const
prevInDept
=
new
Set
([...
this
.
selectedUserIds
].
filter
(
id
=>
this
.
deptUsers
.
some
(
u
=>
u
.
key
===
id
)));
// 添加本次新增的
const
newSelected
=
new
Set
([...
this
.
selectedUserIds
]);
this
.
tempCheckedUsers
.
forEach
(
id
=>
newSelected
.
add
(
id
));
this
.
searchCheckedUsers
.
forEach
(
id
=>
newSelected
.
add
(
id
));
// 移除本次取消的
prevInDept
.
forEach
(
id
=>
{
if
(
!
this
.
tempCheckedUsers
.
has
(
id
)
&&
!
this
.
searchCheckedUsers
.
has
(
id
))
{
newSelected
.
delete
(
id
);
}
});
this
.
selectedUserIds
=
newSelected
;
this
.
$emit
(
'change'
,
[...
this
.
selectedUserIds
]);
this
.
deptModal
=
false
;
}
}
};
</
script
>
<
style
scoped
>
.search-container
{
position
:
fixed
;
width
:
17vw
;
z-index
:
9999
;
padding
:
0
1vh
;
}
.search-wrapper
{
position
:
relative
;
}
.search-input
{
padding-top
:
10px
;
padding-left
:
5px
;
border-radius
:
4px
!important
;
background
:
linear-gradient
(
135deg
,
#f8f9fa
0%
,
#ffffff
100%
);
transition
:
all
0.3s
cubic-bezier
(
0.4
,
0
,
0.2
,
1
);
}
.search-input
:hover
{
box-shadow
:
0
6px
20px
rgba
(
0
,
0
,
0
,
0.12
);
transform
:
translateY
(
-1px
);
}
.search-input
:focus-within
{
box-shadow
:
0
8px
25px
rgba
(
33
,
150
,
243
,
0.15
);
transform
:
translateY
(
-2px
);
}
.search-input
>>>
.q-field__control
{
border-radius
:
12px
;
padding
:
8px
16px
;
}
.search-input
>>>
.q-field__label
{
font-weight
:
500
;
color
:
#666
;
font-size
:
12px
;
}
.search-input
>>>
.q-field__native
{
font-size
:
14px
;
font-weight
:
400
;
padding-left
:
12px
!important
;
padding-top
:
2px
!important
;
padding-bottom
:
2px
!important
;
}
.search-input
>>>
.q-field__native
::placeholder
{
font-size
:
14px
;
color
:
#999
;
}
.search-icon
{
color
:
#999
;
transition
:
color
0.3s
ease
;
}
.search-input
:hover
.search-icon
{
color
:
#1976d2
;
}
.search-menu
{
border-radius
:
12px
;
box-shadow
:
0
8px
32px
rgba
(
0
,
0
,
0
,
0.12
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.2
);
backdrop-filter
:
blur
(
8px
);
background
:
rgba
(
255
,
255
,
255
,
0.95
);
min-width
:
16vw
;
max-width
:
16vw
;
max-height
:
300px
;
overflow-y
:
auto
;
position
:
absolute
;
top
:
100%
;
left
:
0
;
right
:
0
;
z-index
:
1000
;
margin-top
:
8px
;
}
.search-results
{
padding
:
8px
0
;
}
.search-result-item
{
display
:
flex
;
align-items
:
center
;
padding
:
12px
16px
;
border-radius
:
8px
;
margin
:
2px
8px
;
cursor
:
pointer
;
transition
:
all
0.2s
ease
;
border
:
1px
solid
transparent
;
}
.search-result-item
:hover
{
background-color
:
rgba
(
25
,
118
,
210
,
0.08
);
transform
:
translateX
(
2px
);
border-color
:
rgba
(
25
,
118
,
210
,
0.1
);
}
.search-result-avatar
{
flex-shrink
:
0
;
width
:
40px
;
height
:
40px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border-radius
:
50%
;
background-color
:
rgba
(
0
,
0
,
0
,
0.04
);
margin-right
:
12px
;
}
.search-result-content
{
flex
:
1
;
min-width
:
0
;
}
.search-result-side
{
flex-shrink
:
0
;
width
:
24px
;
height
:
24px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
opacity
:
0.5
;
}
.search-result-label
{
font-weight
:
500
;
color
:
#333
;
font-size
:
14px
;
}
.no-results
{
padding
:
16px
;
text-align
:
center
;
color
:
#999
;
font-size
:
14px
;
}
.dept-edit-btn
{
transition
:
opacity
0.2s
;
}
.dept-user-list
{
max-height
:
60vh
;
overflow-y
:
auto
;
border
:
1px
solid
#e0e0e0
;
border-radius
:
8px
;
}
.select-all-row
{
display
:
flex
;
align-items
:
center
;
padding
:
6px
0
;
margin-bottom
:
4px
;
}
.select-all-label
{
font-size
:
14px
;
color
:
#333
;
user-select
:
none
;
}
.dept-user-item
{
display
:
flex
;
align-items
:
center
;
padding
:
12px
16px
;
cursor
:
pointer
;
border-bottom
:
1px
solid
#f0f0f0
;
transition
:
background-color
0.15s
;
}
.dept-user-item
:last-child
{
border-bottom
:
none
;
}
.dept-user-item
:hover
{
background-color
:
#f5f5f5
;
}
.dept-user-info
{
margin-left
:
12px
;
flex
:
1
;
}
.dept-user-name
{
font-size
:
14px
;
color
:
#333
;
}
.dept-user-no
{
font-size
:
12px
;
color
:
#999
;
margin-top
:
2px
;
}
.dept-edit-btn
{
transition
:
opacity
0.2s
;
}
:deep
(
.ant-tree-title
:hover
)
.dept-edit-btn
{
opacity
:
1
;
}
.tree-wrapper
{
transition
:
opacity
0.2s
ease
;
}
</
style
>
src/pages/permission/roleuser.vue
View file @
3de9a072
...
@@ -40,28 +40,51 @@
...
@@ -40,28 +40,51 @@
<div
class=
"col-2 q-ml-lg"
style=
"height:80vh;overflow:auto;"
>
<div
class=
"col-2 q-ml-lg"
style=
"height:80vh;overflow:auto;"
>
</div>
</div>
<div
class=
"col-4 q-mt-lg"
style=
"height:80vh;overflow:auto;"
>
<div
class=
"col-4 q-mt-lg"
style=
"height:80vh;overflow:auto;"
>
<organization
Ant
v-model=
"orgList"
@
staffNode=
"staffNod
e"
/>
<organization
DeptPanel
:value=
"orgList"
@
change=
"onOrgListChang
e"
/>
</div>
</div>
<div
class=
"col-4 q-mt-lg"
style=
"height:80vh;overflow:auto;"
>
<div
class=
"col-4 q-mt-lg"
style=
"height:80vh;overflow:auto;"
ref=
"staffListEl"
@
scroll=
"onStaffScroll"
>
<div
class=
"q-mb-lg q-mt-lg"
>
<div
class=
"q-mb-md"
>
显示已选择的成员,共{{selectStaff.length}}人
<q-input
</div>
v-model=
"selectedSearchText"
<div
v-if=
"!selectStaff || selectStaff.length ===0"
class=
"text-grey-5"
>
clearable
没有选择任何成员
outlined
dense
placeholder=
"搜索已选成员..."
>
<
template
v-slot:prepend
>
<q-icon
name=
"search"
/>
</
template
>
</q-input>
<div
class=
"q-mt-xs text-grey-6"
style=
"font-size:12px;"
>
共 {{ filteredStaff.length }} 人{{ selectedSearchText ? '(已过滤)' : '' }}
</div>
</div>
</div>
<div
v-else
>
<div
class=
"row shadow-1"
v-for=
"node in selectStaff"
:key=
"node.id"
>
<div
v-if=
"visibleStaff.length > 0"
>
<div
class=
"col"
style=
"display:flex;align-items: center;"
>
<div
class=
"row shadow-1 q-mb-xs"
v-for=
"node in visibleStaff"
:key=
"node.key"
>
<q-icon
name=
"person"
size=
"16px"
/>
<div
class=
"col"
style=
"display:flex;align-items:center;"
>
<q-icon
name=
"person"
size=
"16px"
/>
<div>
<div>
{{' '+node.title}}
<span
style=
"color: #64b5fe"
>
{{ node.no ? node.no:'' }}
</span>
{{ ' ' + node.title }}
<span
style=
"color:#64b5fe"
>
{{ node.no ? ' ' + node.no : '' }}
</span>
</div>
</div>
</div>
</div>
<div
class=
"col"
>
<div
class=
"col"
style=
"display:flex;align-items:center;justify-content:flex-end;"
>
<q-btn
class=
" float-right"
flat
color=
"primary"
icon=
"clear"
@
click=
"deleteNode(node)"
/>
<q-btn
flat
color=
"primary"
icon=
"clear"
@
click=
"deleteNode(node)"
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div
v-if=
"staffLoading"
class=
"text-center q-pa-sm text-grey-6"
>
加载中...
</div>
<div
v-else-if=
"!selectedSearchText && filteredStaff.length === 0"
class=
"text-grey-5 q-mt-md"
>
没有选择任何成员
</div>
<div
v-else-if=
"selectedSearchText && filteredStaff.length === 0"
class=
"text-grey-5 q-mt-md"
>
没有匹配的成员
</div>
</div>
</div>
</div>
</div>
</q-modal-layout>
</q-modal-layout>
...
@@ -70,15 +93,21 @@
...
@@ -70,15 +93,21 @@
</template>
</template>
<
script
>
<
script
>
import
{
getRolePagedList
}
from
"@/service/permission/role"
;
import
{
getRolePagedList
}
from
"@/service/permission/role"
;
import
{
getUserRoleList
,
editRoleUser
,
editUserRoleList
}
from
"@/service/user/user"
;
import
{
getUserRoleList
,
editRoleUser
,
editUserRoleList
,
listByIds
}
from
"@/service/user/user"
;
import
organization
Ant
from
'@/components/organization-ant
'
import
organization
DeptPanel
from
'@/components/organization-dept-panel
'
export
default
{
export
default
{
components
:
{
organizationAnt
},
components
:
{
organizationDeptPanel
},
// name: "roleuser",
// name: "roleuser",
data
()
{
data
()
{
return
{
return
{
orgList
:[],
//选中的树节点
orgList
:[],
//选中的树节点
selectStaff
:[],
//选中的人
selectStaff
:[],
//选中的人
selectedSearchText
:
''
,
// 右侧已选列表搜索
visibleStaff
:
[],
// 当前可见的已选成员(分页)
staffPageSize
:
50
,
// 每页加载条数
staffPage
:
1
,
// 当前页
staffLoading
:
false
,
// 加载中
staffListEl
:
null
,
// 滚动容器 ref
treeKey
:[],
treeKey
:[],
resdata
:[],
resdata
:[],
expandedKeys
:
[],
expandedKeys
:
[],
...
@@ -190,9 +219,41 @@ export default {
...
@@ -190,9 +219,41 @@ export default {
}
}
};
};
},
},
computed
:
{
filteredStaff
()
{
if
(
!
this
.
selectedSearchText
)
return
this
.
selectStaff
;
const
kw
=
this
.
selectedSearchText
.
toLowerCase
();
return
this
.
selectStaff
.
filter
(
s
=>
(
s
.
title
||
''
).
toLowerCase
().
includes
(
kw
)
||
(
s
.
no
||
''
).
toLowerCase
().
includes
(
kw
)
);
}
},
watch
:
{
filteredStaff
()
{
this
.
staffPage
=
1
;
this
.
visibleStaff
=
this
.
filteredStaff
.
slice
(
0
,
this
.
staffPageSize
);
}
},
methods
:
{
methods
:
{
staffNode
(
nodeList
)
{
//组织机构树人员选择后组件的回调方法
onOrgListChange
(
selectedIds
)
{
//新组件选中后回调
this
.
selectStaff
=
nodeList
this
.
orgList
=
selectedIds
;
if
(
selectedIds
.
length
>
0
)
{
listByIds
(
selectedIds
).
then
(
res
=>
{
let
users
=
res
.
data
.
data
||
[];
this
.
selectStaff
=
users
.
map
(
u
=>
({
key
:
u
.
id
,
title
:
u
.
title
||
u
.
label
||
''
,
no
:
u
.
no
||
''
}));
this
.
visibleStaff
=
this
.
selectStaff
.
slice
(
0
,
this
.
staffPageSize
);
this
.
staffPage
=
1
;
});
}
else
{
this
.
selectStaff
=
[];
this
.
visibleStaff
=
[];
this
.
staffPage
=
1
;
}
},
},
// checkStaffNode(nodeList){//组织机构树人员选择后组件的回调方法-返回选中的node
// checkStaffNode(nodeList){//组织机构树人员选择后组件的回调方法-返回选中的node
// this.selectStaff = nodeList
// this.selectStaff = nodeList
...
@@ -207,7 +268,30 @@ export default {
...
@@ -207,7 +268,30 @@ export default {
}
}
var
staff
=
this
.
selectStaff
.
filter
(
staff
=>
staff
.
key
!==
node
.
key
);
var
staff
=
this
.
selectStaff
.
filter
(
staff
=>
staff
.
key
!==
node
.
key
);
this
.
selectStaff
=
staff
;
this
.
selectStaff
=
staff
;
this
.
orgList
=
this
.
selectStaff
.
map
(
item
=>
item
.
key
)
this
.
orgList
=
this
.
selectStaff
.
map
(
item
=>
item
.
key
);
this
.
visibleStaff
=
this
.
filteredStaff
.
slice
(
0
,
this
.
staffPage
*
this
.
staffPageSize
);
},
onStaffScroll
(
e
)
{
const
el
=
e
.
target
;
if
(
el
.
scrollHeight
-
el
.
scrollTop
-
el
.
clientHeight
<
50
)
{
this
.
loadMoreStaff
();
}
},
loadMoreStaff
()
{
if
(
this
.
staffLoading
)
return
;
const
total
=
this
.
filteredStaff
.
length
;
const
next
=
(
this
.
staffPage
+
1
)
*
this
.
staffPageSize
;
if
(
next
>=
total
)
{
this
.
visibleStaff
=
this
.
filteredStaff
;
this
.
staffLoading
=
false
;
}
else
{
this
.
staffLoading
=
true
;
setTimeout
(()
=>
{
this
.
staffPage
++
;
this
.
visibleStaff
=
this
.
filteredStaff
.
slice
(
0
,
this
.
staffPage
*
this
.
staffPageSize
);
this
.
staffLoading
=
false
;
},
100
);
}
},
},
async
request
(
props
)
{
async
request
(
props
)
{
this
.
loading
=
true
;
this
.
loading
=
true
;
...
@@ -234,15 +318,31 @@ export default {
...
@@ -234,15 +318,31 @@ export default {
this
.
roleId
=
props
.
value
;
this
.
roleId
=
props
.
value
;
this
.
roleName
=
props
.
row
.
namezh
;
this
.
roleName
=
props
.
row
.
namezh
;
this
.
editModal
=
true
;
this
.
editModal
=
true
;
let
query
=
{
role
:
this
.
roleId
};
let
query
=
{
role
:
this
.
roleId
};
let
dataRes
=
await
getUserRoleList
(
query
);
let
dataRes
=
await
getUserRoleList
(
query
);
let
data
=
dataRes
.
data
.
data
;
let
data
=
dataRes
.
data
.
data
;
this
.
orgList
=
data
.
map
(
item
=>
item
.
userId
);
this
.
orgList
=
data
||
[];
if
(
this
.
orgList
.
length
>
0
)
{
let
idsRes
=
await
listByIds
(
this
.
orgList
);
let
users
=
idsRes
.
data
.
data
||
[];
this
.
selectStaff
=
users
.
map
(
u
=>
({
key
:
u
.
id
,
title
:
u
.
title
||
u
.
label
||
''
,
no
:
u
.
no
||
''
}));
this
.
visibleStaff
=
this
.
selectStaff
.
slice
(
0
,
this
.
staffPageSize
);
this
.
staffPage
=
1
;
}
else
{
this
.
selectStaff
=
[];
}
},
},
closeEditModal
(){
//关闭弹层,清空树
closeEditModal
(){
//关闭弹层,清空树
this
.
editModal
=
false
this
.
editModal
=
false
this
.
orgList
=
[]
this
.
orgList
=
[]
this
.
selectStaff
=
[]
this
.
selectStaff
=
[]
this
.
visibleStaff
=
[]
this
.
staffPage
=
1
this
.
selectedSearchText
=
''
},
},
search
()
{
search
()
{
this
.
request
({
this
.
request
({
...
...
src/pages/permission/roleuser_bak.vue
0 → 100644
View file @
3de9a072
<
template
>
<div
style=
"padding:10px"
>
<q-card
inline
class=
"fit shadow-6"
>
<div>
</div>
<q-card-main
style=
"height:80%"
>
<q-table
ref=
"table"
color=
"primary"
:data=
"serverData"
:columns=
"columns"
separator=
"cell"
selection=
"none"
row-key=
"id"
:rows-per-page-options=
"[5,10,20,30,40,50,60,200,500]"
:pagination
.
sync=
"serverPagination"
@
request=
"request"
:loading=
"loading"
:rows-per-page-label=
"$t('Rows per page')"
:no-data-label=
"$t('No data')"
>
<template
slot=
"top-left"
slot-scope=
"props"
>
<q-input
v-model=
"filter.namezh"
type=
"text"
:prefix=
"$t('Role name') + ':'"
/>
<q-input
v-model=
"filter.name"
type=
"text"
:prefix=
"$t('Role code') + ':'"
/>
<q-btn
push
dense
color=
"primary"
icon=
"search"
@
click=
"search"
>
{{
$t
(
'Search'
)
}}
</q-btn>
</
template
>
<
template
slot=
"top-right"
slot-scope=
"props"
>
<q-btn
flat
round
dense
:icon=
"props.inFullscreen ? 'fullscreen_exit' : 'fullscreen'"
@
click=
"props.toggleFullscreen"
/>
</
template
>
<q-td
slot=
"body-cell-id"
slot-scope=
"props"
:props=
"props"
style=
"width:150px"
>
<q-btn
glossy
color=
"secondary"
:label=
"$t('User list')"
@
click=
"editRoleUser(props)"
></q-btn>
</q-td>
</q-table>
</q-card-main>
</q-card>
<q-modal
v-model=
"editModal"
maximized
>
<q-modal-layout>
<q-toolbar
slot=
"header"
>
<q-btn
flat
round
dense
@
click=
"closeEditModal"
icon=
"reply"
/>
<q-toolbar-title>
{{$t('Editing role')}}
<q-chip
small
>
{{roleName}}
</q-chip>
{{$t('User under')}}
</q-toolbar-title>
</q-toolbar>
<q-toolbar
slot=
"footer"
>
<q-toolbar-title>
</q-toolbar-title>
<q-btn
round
color=
"red"
@
click=
"modifyRoleUser"
>
{{$t('Save')}}
</q-btn>
<q-btn
round
@
click=
"closeEditModal"
>
{{$t('Cancel')}}
</q-btn>
</q-toolbar>
<!-- 选择个人 -->
<div
class=
" row q-mb-lg q-ml-lg q-mr-lg"
>
<div
class=
"col-2 q-ml-lg"
style=
"height:80vh;overflow:auto;"
>
</div>
<div
class=
"col-4 q-mt-lg"
style=
"height:80vh;overflow:auto;"
>
<organizationAnt
v-model=
"orgList"
@
staffNode=
"staffNode"
/>
</div>
<div
class=
"col-4 q-mt-lg"
style=
"height:80vh;overflow:auto;"
>
<div
class=
"q-mb-lg q-mt-lg"
>
显示已选择的成员,共{{selectStaff.length}}人
</div>
<div
v-if=
"!selectStaff || selectStaff.length ===0"
class=
"text-grey-5"
>
没有选择任何成员
</div>
<div
v-else
>
<div
class=
"row shadow-1"
v-for=
"node in selectStaff"
:key=
"node.id"
>
<div
class=
"col"
style=
"display:flex;align-items: center;"
>
<q-icon
name=
"person"
size=
"16px"
/>
<div>
{{' '+node.title}}
<span
style=
"color: #64b5fe"
>
{{ node.no ? node.no:'' }}
</span>
</div>
</div>
<div
class=
"col"
>
<q-btn
class=
" float-right"
flat
color=
"primary"
icon=
"clear"
@
click=
"deleteNode(node)"
/>
</div>
</div>
</div>
</div>
</div>
</q-modal-layout>
</q-modal>
</div>
</template>
<
script
>
import
{
getRolePagedList
}
from
"@/service/permission/role"
;
import
{
getUserRoleList
,
editRoleUser
,
editUserRoleList
}
from
"@/service/user/user"
;
import
organizationAnt
from
'@/components/organization-ant'
export
default
{
components
:
{
organizationAnt
},
// name: "roleuser",
data
()
{
return
{
orgList
:[],
//选中的树节点
selectStaff
:[],
//选中的人
treeKey
:[],
resdata
:[],
expandedKeys
:
[],
autoExpandParent
:
true
,
checkedKeys
:
[],
selectedKeys
:
[],
namefilter
:
""
,
leaderSelected
:
[],
leaderList
:
[],
tempFunction
:{
contactList
:[{}],
},
serverData
:
[],
serverPagination
:
{
page
:
1
,
rowsNumber
:
0
,
// specifying this determines pagination is server-side
rowsPerPage
:
10
// current rows per page being displayed
},
columns
:
[
{
name
:
"namezh"
,
required
:
true
,
label
:
this
.
$t
(
"Role name"
),
align
:
"left"
,
field
:
"namezh"
,
sortable
:
true
},
{
name
:
"name"
,
label
:
this
.
$t
(
"Role code"
),
field
:
"name"
,
sortable
:
true
,
align
:
"left"
,
},
{
name
:
"id"
,
required
:
true
,
label
:
this
.
$t
(
"ID"
),
align
:
"left"
,
field
:
"id"
}
],
filter
:
{
name
:
""
,
namezh
:
""
},
loading
:
false
,
editModal
:
false
,
roleId
:
0
,
roleName
:
""
,
roleUser
:
{
serverData
:
[],
serverPagination
:
{
page
:
1
,
rowsNumber
:
0
,
// specifying this determines pagination is server-side
rowsPerPage
:
10
// current rows per page being displayed
},
columns
:
[
{
name
:
"username"
,
required
:
true
,
label
:
this
.
$t
(
"Name"
),
align
:
"left"
,
field
:
"username"
,
sortable
:
true
},
{
name
:
"account"
,
label
:
this
.
$t
(
"Account"
),
field
:
"account"
,
sortable
:
true
,
align
:
"left"
},
{
name
:
"email"
,
label
:
this
.
$t
(
"Email"
),
field
:
"email"
,
sortable
:
true
,
align
:
"left"
},
{
name
:
"phoneNumber"
,
label
:
this
.
$t
(
"Phone"
),
field
:
"phoneNumber"
,
sortable
:
true
,
align
:
"left"
},
{
name
:
"position"
,
required
:
true
,
label
:
this
.
$t
(
"Position"
),
align
:
"left"
,
field
:
"position"
},
{
name
:
"id"
,
required
:
true
,
label
:
this
.
$t
(
"ID"
),
align
:
"left"
,
field
:
"id"
}
],
filter
:
{
account
:
""
,
username
:
""
,
roleId
:
""
},
loading
:
false
}
};
},
methods
:
{
staffNode
(
nodeList
)
{
//组织机构树人员选择后组件的回调方法
this
.
selectStaff
=
nodeList
},
// checkStaffNode(nodeList){//组织机构树人员选择后组件的回调方法-返回选中的node
// this.selectStaff = nodeList
// },
// checkStaffKey(keyList){//组织机构树人员选择后组件的回调方法-返回key
// this.selectStaff = keyList
// },
// 删除节点事件
deleteNode
(
node
)
{
if
(
!
node
)
{
return
false
;
}
var
staff
=
this
.
selectStaff
.
filter
(
staff
=>
staff
.
key
!==
node
.
key
);
this
.
selectStaff
=
staff
;
this
.
orgList
=
this
.
selectStaff
.
map
(
item
=>
item
.
key
)
},
async
request
(
props
)
{
this
.
loading
=
true
;
this
.
serverPagination
=
props
.
pagination
;
let
table
=
this
.
$refs
.
table
,
{
page
,
rowsPerPage
,
sortBy
,
descending
}
=
props
.
pagination
;
let
query
=
{
pageNum
:
page
,
pageSize
:
rowsPerPage
,
sortBy
:
sortBy
,
descending
:
descending
,
name
:
this
.
filter
.
name
,
namezh
:
this
.
filter
.
namezh
};
let
dataRes
=
await
getRolePagedList
(
query
);
let
data
=
dataRes
.
data
.
data
;
this
.
serverPagination
.
rowsNumber
=
data
.
total
;
this
.
serverData
=
data
.
list
;
setTimeout
(()
=>
{
this
.
loading
=
false
;
},
500
);
},
async
editRoleUser
(
props
)
{
this
.
roleId
=
props
.
value
;
this
.
roleName
=
props
.
row
.
namezh
;
this
.
editModal
=
true
;
let
query
=
{
role
:
this
.
roleId
};
let
dataRes
=
await
getUserRoleList
(
query
);
let
data
=
dataRes
.
data
.
data
;
this
.
orgList
=
data
.
map
(
item
=>
item
.
userId
);
},
closeEditModal
(){
//关闭弹层,清空树
this
.
editModal
=
false
this
.
orgList
=
[]
this
.
selectStaff
=
[]
},
search
()
{
this
.
request
({
pagination
:
this
.
serverPagination
,
filter
:
this
.
filter
});
},
searchRoleUser
()
{
this
.
roleUserRequest
({
pagination
:
this
.
roleUser
.
serverPagination
,
filter
:
this
.
roleUser
.
filter
});
},
async
modifyRoleUser
()
{
//保存用户角色信息,并刷新角色用户列表
let
userIds
=
this
.
selectStaff
.
map
(
item
=>
item
.
key
).
join
(
","
)
let
res
=
await
editUserRoleList
({
roleId
:
this
.
roleId
,
userIds
:
userIds
});
this
.
closeEditModal
()
this
.
$q
.
notify
({
type
:
"positive"
,
message
:
this
.
$t
(
"Added successfully"
),
position
:
"bottom-right"
});
this
.
request
({
pagination
:
this
.
serverPagination
,
filter
:
this
.
filter
});
}
},
mounted
()
{
this
.
request
({
pagination
:
this
.
serverPagination
,
filter
:
this
.
filter
});
}
};
</
script
>
src/service/user/user.js
View file @
3de9a072
...
@@ -189,6 +189,41 @@ export function getweixinisleaderindeptByaccount(){
...
@@ -189,6 +189,41 @@ export function getweixinisleaderindeptByaccount(){
})
})
}
}
export
function
deptTree
()
{
return
request
({
url
:
'/user/dept_tree'
,
method
:
'get'
,
loading
:
'gears'
});
}
export
function
searchUsers
(
keyword
,
pageNum
,
pageSize
)
{
return
request
({
url
:
'/user/search'
,
method
:
'get'
,
params
:
{
keyword
,
pageNum
,
pageSize
},
loading
:
'gears'
});
}
export
function
getDeptUsers
(
deptId
,
pageNum
,
pageSize
)
{
return
request
({
url
:
'/user/dept_users/'
+
deptId
,
method
:
'get'
,
params
:
{
pageNum
,
pageSize
},
loading
:
'gears'
});
}
export
function
listByIds
(
ids
)
{
return
request
({
url
:
'/user/list_by_ids'
,
method
:
'post'
,
data
:
ids
,
loading
:
'gears'
});
}
export
function
editUserRoleList
(
params
)
{
export
function
editUserRoleList
(
params
)
{
return
request
({
return
request
({
url
:
'/roleuser/editUserRoleList'
,
url
:
'/roleuser/editUserRoleList'
,
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论