OrgPicker.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. <template>
  2. <w-dialog :border="false" closeFree width="600px" @ok="selectOk" :title="title" v-model="visible">
  3. <div class="picker">
  4. <div class="candidate" v-loading="loading">
  5. <div v-if="type !== 'role'">
  6. <el-input v-model="search" @input="searchUser" style="width: 95%;" size="small"
  7. clearable placeholder="搜索人员,支持拼音、姓名" prefix-icon="el-icon-search"/>
  8. <div v-show="!showUsers">
  9. <ellipsis hoverTip style="height: 18px; color: #8c8c8c; padding: 5px 0 0" :row="1" :content="deptStackStr">
  10. <i slot="pre" class="el-icon-office-building"></i>
  11. </ellipsis>
  12. <div style="margin-top: 5px">
  13. <el-checkbox v-model="checkAll" @change="handleCheckAllChange" :disabled="!multiple">全选</el-checkbox>
  14. <span v-show="deptStack.length > 0" class="top-dept" @click="beforeNode">上一级</span>
  15. </div>
  16. </div>
  17. </div>
  18. <div class="role-header" v-else>
  19. <div>系统角色</div>
  20. </div>
  21. <div class="org-items" :style="type === 'role' ? 'height: 350px':''">
  22. <el-empty :image-size="100" description="似乎没有数据" v-show="orgs.length === 0"/>
  23. <div v-for="(org, index) in orgs" :key="index" :class="orgItemClass(org)" @click="selectChange(org)">
  24. <el-checkbox :value="org.selected" @change="selectChange(org)" :disabled="disableDept(org)"></el-checkbox>
  25. <div v-if="org.type === 'dept'">
  26. <i class="el-icon-folder-opened"></i>
  27. <span class="name">{{ org.name }}</span>
  28. <span @click.stop="nextNode(org)" :class="`next-dept${org.selected ? '-disable':''}`">
  29. <i class="iconfont icon-map-site"></i>下级
  30. </span>
  31. </div>
  32. <div v-else-if="org.type === 'user'" style="display: flex; align-items: center">
  33. <el-avatar :size="35" :src="org.avatar" v-if="$isNotEmpty(org.avatar)"/>
  34. <span v-else class="avatar">{{getShortName(org.name)}}</span>
  35. <span class="name">{{ org.name }}</span>
  36. </div>
  37. <div style="display: inline-block" v-else>
  38. <i class="iconfont icon-bumen"></i>
  39. <span class="name">{{ org.name }}</span>
  40. </div>
  41. </div>
  42. </div>
  43. </div>
  44. <div class="selected">
  45. <div class="count">
  46. <span>已选 {{ select.length }} 项</span>
  47. <span @click="clearSelected">清空</span>
  48. </div>
  49. <div class="org-items" style="height: 350px;">
  50. <el-empty :image-size="100" description="请点击左侧列表选择数据" v-show="select.length === 0"/>
  51. <div v-for="(org, index) in select" :key="index" :class="orgItemClass(org)" >
  52. <div v-if="org.type === 'dept'">
  53. <i class="el-icon-folder-opened"></i>
  54. <span style="position: static" class="name">{{ org.name }}</span>
  55. </div>
  56. <div v-else-if="org.type === 'user'" style="display: flex; align-items: center">
  57. <el-avatar :size="35" :src="org.avatar" v-if="$isNotEmpty(org.avatar)"/>
  58. <span v-else class="avatar">{{getShortName(org.name)}}</span>
  59. <span class="name">{{ org.name }}</span>
  60. </div>
  61. <div v-else>
  62. <i class="iconfont icon-bumen"></i>
  63. <span class="name">{{ org.name }}</span>
  64. </div>
  65. <i class="el-icon-close" @click="noSelected(index)"></i>
  66. </div>
  67. </div>
  68. </div>
  69. </div>
  70. </w-dialog>
  71. </template>
  72. <script>
  73. import {getOrgTree, getUserByName} from '@/utils/api/org'
  74. export default {
  75. name: "OrgPicker",
  76. components: {},
  77. props: {
  78. title: {
  79. default: '请选择',
  80. type: String
  81. },
  82. type: {
  83. default: 'org', //org选择部门/人员 user-选人 dept-选部门 role-选角色
  84. type: String
  85. },
  86. multiple: { //是否多选
  87. default: false,
  88. type: Boolean
  89. },
  90. selected: {
  91. default: () => {
  92. return []
  93. },
  94. type: Array
  95. },
  96. },
  97. data() {
  98. return {
  99. visible: false,
  100. loading: false,
  101. checkAll: false,
  102. nowDeptId: null,
  103. isIndeterminate: false,
  104. searchUsers: [],
  105. nodes: [],
  106. select: [],
  107. search: '',
  108. deptStack: []
  109. }
  110. },
  111. computed: {
  112. deptStackStr() {
  113. return String(this.deptStack.map(v => v.name)).replaceAll(',', ' > ')
  114. },
  115. orgs() {
  116. return !this.search || this.search.trim() === '' ? this.nodes : this.searchUsers
  117. },
  118. showUsers(){
  119. return this.search || this.search.trim() !== ''
  120. }
  121. },
  122. methods: {
  123. show() {
  124. this.visible = true
  125. this.init()
  126. this.getOrgList()
  127. },
  128. orgItemClass(org){
  129. return {
  130. 'org-item': true,
  131. 'org-dept-item': org.type === 'dept',
  132. 'org-user-item': org.type === 'user',
  133. 'org-role-item': org.type === 'role'
  134. }
  135. },
  136. disableDept(node) {
  137. return this.type === 'user' && 'dept' === node.type
  138. },
  139. getOrgList() {
  140. this.loading = true
  141. getOrgTree({deptId: this.nowDeptId, type: this.type}).then(rsp => {
  142. this.loading = false
  143. this.nodes = rsp.data
  144. this.selectToLeft()
  145. }).catch(err => {
  146. this.loading = false
  147. this.$message.error(err.response.data)
  148. })
  149. },
  150. getShortName(name) {
  151. if (name) {
  152. return name.length > 2 ? name.substring(1, 3) : name;
  153. }
  154. return '**'
  155. },
  156. searchUser() {
  157. let userName = this.search.trim()
  158. this.searchUsers = []
  159. this.loading = true
  160. getUserByName({userName: userName}).then(rsp => {
  161. this.loading = false
  162. this.searchUsers = rsp.data
  163. this.selectToLeft()
  164. }).catch(err => {
  165. this.loading = false
  166. this.$message.error("接口异常")
  167. })
  168. },
  169. selectToLeft() {
  170. let nodes = this.search.trim() === '' ? this.nodes : this.searchUsers;
  171. nodes.forEach(node => {
  172. for (let i = 0; i < this.select.length; i++) {
  173. if (this.select[i].id === node.id) {
  174. node.selected = true;
  175. break;
  176. } else {
  177. node.selected = false;
  178. }
  179. }
  180. })
  181. },
  182. selectChange(node) {
  183. if (node.selected) {
  184. this.checkAll = false;
  185. for (let i = 0; i < this.select.length; i++) {
  186. if (this.select[i].id === node.id) {
  187. this.select.splice(i, 1);
  188. break;
  189. }
  190. }
  191. node.selected = false;
  192. } else if (!this.disableDept(node)) {
  193. node.selected = true
  194. let nodes = this.search.trim() === '' ? this.nodes : this.searchUsers;
  195. if (!this.multiple) {
  196. nodes.forEach(nd => {
  197. if (node.id !== nd.id) {
  198. nd.selected = false
  199. }
  200. })
  201. }
  202. if (node.type === 'dept') {
  203. if (!this.multiple) {
  204. this.select = [node]
  205. } else {
  206. this.select.unshift(node);
  207. }
  208. } else {
  209. if (!this.multiple) {
  210. this.select = [node]
  211. } else {
  212. this.select.push(node);
  213. }
  214. }
  215. }
  216. },
  217. noSelected(index) {
  218. let nodes = this.nodes;
  219. for (let f = 0; f < 2; f++) {
  220. for (let i = 0; i < nodes.length; i++) {
  221. if (nodes[i].id === this.select[index].id) {
  222. nodes[i].selected = false;
  223. this.checkAll = false;
  224. break;
  225. }
  226. }
  227. nodes = this.searchUsers;
  228. }
  229. this.select.splice(index, 1)
  230. },
  231. handleCheckAllChange() {
  232. this.nodes.forEach(node => {
  233. if (this.checkAll) {
  234. if (!node.selected && !this.disableDept(node)) {
  235. node.selected = true
  236. this.select.push(node)
  237. }
  238. } else {
  239. node.selected = false;
  240. for (let i = 0; i < this.select.length; i++) {
  241. if (this.select[i].id === node.id) {
  242. this.select.splice(i, 1);
  243. break;
  244. }
  245. }
  246. }
  247. })
  248. },
  249. nextNode(node) {
  250. this.nowDeptId = node.id
  251. this.deptStack.push(node)
  252. this.getOrgList()
  253. },
  254. beforeNode() {
  255. if (this.deptStack.length === 0) {
  256. return;
  257. }
  258. if (this.deptStack.length < 2) {
  259. this.nowDeptId = null
  260. } else {
  261. this.nowDeptId = this.deptStack[this.deptStack.length - 2].id
  262. }
  263. this.deptStack.splice(this.deptStack.length - 1, 1);
  264. this.getOrgList()
  265. },
  266. recover() {
  267. this.select = []
  268. this.nodes.forEach(nd => nd.selected = false)
  269. },
  270. selectOk() {
  271. this.$emit('ok', Object.assign([], this.select.map(v => {
  272. v.avatar = undefined
  273. return v
  274. })))
  275. this.visible = false
  276. this.recover()
  277. },
  278. clearSelected(){
  279. this.$confirm('您确定要清空已选中的项?', '提示', {
  280. confirmButtonText: '确定',
  281. cancelButtonText: '取消',
  282. type: 'warning'
  283. }).then(() => {
  284. this.recover()
  285. })
  286. },
  287. close() {
  288. this.$emit('close')
  289. this.recover()
  290. },
  291. init() {
  292. this.checkAll = false;
  293. this.nowDeptId = null;
  294. this.deptStack = [];
  295. this.nodes = []
  296. this.select = Object.assign([], this.selected)
  297. this.selectToLeft()
  298. }
  299. }
  300. }
  301. </script>
  302. <style lang="less" scoped>
  303. @containWidth: 278px;
  304. .candidate, .selected {
  305. position: absolute;
  306. display: inline-block;
  307. width: @containWidth;
  308. height: 400px;
  309. border: 1px solid #e8e8e8;
  310. }
  311. .picker {
  312. height: 402px;
  313. position: relative;
  314. text-align: left;
  315. .candidate {
  316. left: 0;
  317. top: 0;
  318. .role-header{
  319. padding: 10px !important;
  320. margin-bottom: 5px;
  321. border-bottom: 1px solid #e8e8e8;
  322. }
  323. .top-dept{
  324. margin-left: 20px;
  325. cursor: pointer;
  326. color:#38adff;
  327. }
  328. .next-dept {
  329. float: right;
  330. color: #1890FF;
  331. cursor: pointer;
  332. }
  333. .next-dept-disable {
  334. float: right;
  335. color: #8c8c8c;
  336. cursor: not-allowed;
  337. }
  338. & > div:first-child {
  339. padding: 5px 10px;
  340. }
  341. }
  342. .selected {
  343. right: 0;
  344. top: 0;
  345. }
  346. .org-items {
  347. overflow-y: auto;
  348. height: 310px;
  349. .el-icon-close {
  350. position: absolute;
  351. right: 5px;
  352. cursor: pointer;
  353. font-size: larger;
  354. }
  355. .org-dept-item {
  356. padding: 10px 5px;
  357. & > div {
  358. display: inline-block;
  359. & > span:last-child {
  360. position: absolute;
  361. right: 5px;
  362. }
  363. }
  364. }
  365. .org-role-item {
  366. display: flex;
  367. align-items: center;
  368. padding: 10px 5px;
  369. }
  370. /deep/ .org-user-item {
  371. display: flex;
  372. align-items: center;
  373. padding: 5px;
  374. & > div {
  375. display: inline-block;
  376. }
  377. .avatar {
  378. width: 35px;
  379. text-align: center;
  380. line-height: 35px;
  381. background: #1890FF;
  382. color: white;
  383. border-radius: 50%;
  384. }
  385. }
  386. /deep/ .org-item {
  387. margin: 0 5px;
  388. border-radius: 5px;
  389. position: relative;
  390. .el-checkbox {
  391. margin-right: 10px;
  392. }
  393. .name {
  394. margin-left: 5px;
  395. }
  396. &:hover {
  397. background: #f1f1f1;
  398. }
  399. }
  400. }
  401. }
  402. .selected {
  403. border-left: none;
  404. .count {
  405. width: calc(@containWidth - 20px);
  406. padding: 10px;
  407. display: inline-block;
  408. border-bottom: 1px solid #e8e8e8;
  409. margin-bottom: 5px;
  410. & > span:nth-child(2) {
  411. float: right;
  412. color: #c75450;
  413. cursor: pointer;
  414. }
  415. }
  416. }
  417. /deep/ .el-dialog__body {
  418. padding: 10px 20px;
  419. }
  420. .disabled{
  421. cursor: not-allowed !important;
  422. color: #8c8c8c !important;
  423. }
  424. ::-webkit-scrollbar {
  425. float: right;
  426. width: 4px;
  427. height: 4px;
  428. background-color: white;
  429. }
  430. ::-webkit-scrollbar-thumb {
  431. border-radius: 16px;
  432. background-color: #efefef;
  433. }
  434. </style>