user-select.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. <template>
  2. <el-dialog
  3. :title="title"
  4. :visible.sync="visible"
  5. :before-close="handleClose"
  6. :close-on-click-modal="false"
  7. top="5vh"
  8. :close-on-press-escape="false"
  9. append-to-body
  10. width="70%"
  11. >
  12. <el-card shadow="never">
  13. <ele-split-layout
  14. width="244px"
  15. allow-collapse
  16. :right-style="{ overflow: 'hidden' }"
  17. >
  18. <div class="ele-border-lighter split-layout-right-content">
  19. <DepartmentTree
  20. @handleNodeClick="handleNodeClick"
  21. :isFirstRefreshTable="false"
  22. ref="treeList"
  23. />
  24. </div>
  25. <!-- 表格 -->
  26. <template v-slot:content>
  27. <user-select-search
  28. ref="search"
  29. @search="reload"
  30. ></user-select-search>
  31. <ele-pro-table
  32. ref="table"
  33. :columns="currentColumns"
  34. :datasource="datasource"
  35. row-key="id"
  36. :height="tableHeight"
  37. class="dict-table"
  38. @cell-click="cellClick"
  39. @selection-change="handleSelectionChange"
  40. @done="handleTableDone"
  41. >
  42. <!-- 表头工具栏 -->
  43. <template v-slot:action="{ row }" v-if="!isMultiple">
  44. <el-radio class="radio" v-model="radio" :label="row.id"
  45. ><i></i
  46. ></el-radio>
  47. </template>
  48. </ele-pro-table>
  49. </template>
  50. </ele-split-layout>
  51. </el-card>
  52. <div slot="footer" class="dialog-footer">
  53. <span v-if="isMultiple" class="selected-count"
  54. >已选 {{ selectedCount }} 人</span
  55. >
  56. <el-button type="primary" size="small" @click="selected">选择</el-button>
  57. <el-button size="small" @click="handleClose">关闭</el-button>
  58. </div>
  59. </el-dialog>
  60. </template>
  61. <script>
  62. import { getUserPage } from '@/api/system/organization';
  63. import DepartmentTree from '@/components/DepartmentTree/index.vue';
  64. import userSelectSearch from './user-select-search.vue';
  65. export default {
  66. components: {
  67. DepartmentTree,
  68. userSelectSearch
  69. },
  70. props: {
  71. disabledList: {
  72. default: () => {
  73. return [];
  74. }
  75. },
  76. // 已选人员(打开时回显,确定后与本次勾选合并返回)
  77. selectedList: {
  78. type: Array,
  79. default: () => {
  80. return [];
  81. }
  82. }
  83. },
  84. data() {
  85. return {
  86. currentIndex: 0,
  87. title: '选择',
  88. visible: false,
  89. isMultiple: false,
  90. selectedRows: [],
  91. // 本次打开时已选人员(在表格中选择框中勾选回显)
  92. preselected: [],
  93. // 本次打开后被取消勾选的已选人员 id
  94. uncheckedIds: [],
  95. // 当前选中的部门节点
  96. curNodeData: null,
  97. current: null,
  98. statusOptions: [
  99. { value: 1, label: '全职' },
  100. { value: 2, label: '兼职' },
  101. { value: 3, label: '实习' },
  102. { value: 4, label: '正式' },
  103. { value: 5, label: '试用' },
  104. { value: 6, label: '离职' }
  105. ],
  106. columns: [
  107. {
  108. action: 'action',
  109. slot: 'action',
  110. align: 'center',
  111. label: '选择'
  112. },
  113. {
  114. columnKey: 'index',
  115. label: '序号',
  116. type: 'index',
  117. width: 55,
  118. align: 'center',
  119. showOverflowTooltip: true,
  120. fixed: 'left'
  121. },
  122. {
  123. prop: 'name',
  124. label: '姓名',
  125. sortable: 'custom',
  126. showOverflowTooltip: true,
  127. minWidth: 110
  128. },
  129. {
  130. prop: 'jobNumber',
  131. label: '工号',
  132. sortable: 'custom',
  133. showOverflowTooltip: true,
  134. minWidth: 110
  135. },
  136. {
  137. prop: 'loginName',
  138. label: '用户账号',
  139. sortable: 'custom',
  140. showOverflowTooltip: true,
  141. minWidth: 110
  142. },
  143. {
  144. prop: 'sex',
  145. label: '性别',
  146. sortable: 'custom',
  147. showOverflowTooltip: true,
  148. minWidth: 80,
  149. formatter: (_row, _column, cellValue) => {
  150. return cellValue == 1 ? '男' : cellValue == 2 ? '女' : '';
  151. }
  152. },
  153. {
  154. prop: 'status',
  155. label: '状态',
  156. align: 'center',
  157. sortable: 'custom',
  158. width: 80,
  159. formatter: (_row, _column, cellValue) => {
  160. const dom = this.statusOptions.find((item) => {
  161. return item.value == cellValue;
  162. });
  163. return dom ? dom.label : '';
  164. }
  165. }
  166. ],
  167. radio: null
  168. };
  169. },
  170. computed: {
  171. // 表格高度
  172. tableHeight() {
  173. return 'calc(100vh - 430px)';
  174. },
  175. // 已选人数
  176. selectedCount() {
  177. return this.mergeSelected().length;
  178. },
  179. currentColumns() {
  180. if (this.isMultiple) {
  181. return [
  182. {
  183. type: 'selection',
  184. // 跨页/重新加载后保留勾选状态,配合 row-key 使用
  185. reserveSelection: true,
  186. width: 55,
  187. align: 'center',
  188. selectable: (row, index) => {
  189. return !this.disabledList
  190. .map((item) => item.id)
  191. .includes(row.id);
  192. }
  193. },
  194. ...this.columns
  195. ];
  196. }
  197. return this.columns;
  198. }
  199. },
  200. watch: {},
  201. methods: {
  202. open(item, index = 0, multiple = false) {
  203. this.isMultiple = multiple;
  204. this.selectedRows = [];
  205. this.uncheckedIds = [];
  206. this.current = null;
  207. this.radio = null;
  208. // 已选人员回显(打开时在表格的选择框中勾选)
  209. this.preselected = [...(this.selectedList || [])];
  210. if (item) {
  211. this.radio = item.id;
  212. // 单选时保持原有选中,未重新选择则沿用
  213. this.current = item;
  214. } else if (!multiple && this.preselected.length) {
  215. const first = this.preselected[0];
  216. this.radio = first.id;
  217. this.current = first;
  218. }
  219. this.currentIndex = index;
  220. // 先亮起弹窗再排 nextTick:首次打开时对话框内容尚未挂载,
  221. // 否则 clearSelection 执行到 $refs.table 时还是 undefined
  222. this.visible = true;
  223. this.$nextTick(() => {
  224. if (this.$refs.table) {
  225. this.$refs.table.clearSelection();
  226. // 已选人员勾选到表格的选择框中
  227. this.syncPreselectedSelection();
  228. }
  229. // 每次打开重置搜索条件与部门筛选
  230. this.resetFilter();
  231. });
  232. },
  233. /* 重置搜索条件与部门树选中 */
  234. resetFilter() {
  235. this.curNodeData = null;
  236. const tree = this.$refs.treeList && this.$refs.treeList.getInstance();
  237. if (tree) {
  238. tree.setCurrentKey(null);
  239. }
  240. // 每页条数回初始值
  241. const table = this.$refs.table;
  242. if (table) {
  243. const pageSize = table.pageSize || 10;
  244. if (table.tableLimit !== pageSize) {
  245. table.tableLimit = pageSize;
  246. }
  247. }
  248. if (this.$refs.search) {
  249. // 清空搜索条件并重新加载表格
  250. this.$refs.search.reset();
  251. } else {
  252. this.reload();
  253. }
  254. },
  255. /* 表格数据加载完成:保证已选人员仍处于勾选状态 */
  256. handleTableDone() {
  257. this.syncPreselectedSelection();
  258. },
  259. /* 将已选人员同步为表格中的勾选状态 */
  260. syncPreselectedSelection() {
  261. const table = this.$refs.table;
  262. if (!table || !this.isMultiple) {
  263. return;
  264. }
  265. const dataMap = new Map();
  266. this.getTableData().forEach((row) => dataMap.set(row.id, row));
  267. const selectedIds = new Set(
  268. (this.selectedRows || []).map((row) => row.id)
  269. );
  270. this.preselected.forEach((item) => {
  271. if (
  272. !item ||
  273. item.id == null ||
  274. selectedIds.has(item.id) ||
  275. // 本次打开后被取消勾选的,不再重新勾选
  276. this.uncheckedIds.includes(item.id)
  277. ) {
  278. return;
  279. }
  280. // 优先使用表格中的行对象,保证复选框能正常回显
  281. const row = dataMap.get(item.id) || item;
  282. table.toggleRowSelection(row, true);
  283. selectedIds.add(item.id);
  284. });
  285. },
  286. /* 表格当前数据 */
  287. getTableData() {
  288. const table = this.$refs.table;
  289. if (table && typeof table.getData === 'function') {
  290. return table.getData() || [];
  291. }
  292. return [];
  293. },
  294. /* 合并已选人员与本次勾选的人员 */
  295. mergeSelected() {
  296. const map = new Map();
  297. const dataMap = new Map(
  298. this.getTableData().map((row) => [row.id, row])
  299. );
  300. // 已选人员(未被取消勾选的)
  301. this.preselected.forEach((item) => {
  302. if (!item || item.id == null || this.uncheckedIds.includes(item.id)) {
  303. return;
  304. }
  305. map.set(item.id, dataMap.get(item.id) || item);
  306. });
  307. // 本次勾选的人员
  308. (this.selectedRows || []).forEach((row) => {
  309. if (!row || row.id == null) {
  310. return;
  311. }
  312. map.set(row.id, dataMap.get(row.id) || row);
  313. });
  314. return Array.from(map.values());
  315. },
  316. /* 表格数据源 */
  317. datasource({ page, limit, where }) {
  318. const params = {
  319. pageNum: page,
  320. size: limit
  321. };
  322. Object.keys(where || {}).forEach((key) => {
  323. const value = where[key];
  324. if (value !== undefined && value !== null && value !== '') {
  325. params[key] = value;
  326. }
  327. });
  328. return getUserPage(params);
  329. },
  330. /* 刷新表格 */
  331. reload(where) {
  332. this.$refs.table.reload({ page: 1, where: where });
  333. },
  334. handleNodeClick(data, node) {
  335. this.curNodeData = data;
  336. this.reload({ groupId: data.id });
  337. },
  338. // 单击获取id
  339. cellClick(row) {
  340. if (this.isMultiple) return;
  341. this.current = row;
  342. this.radio = row.id;
  343. },
  344. handleSelectionChange(rows) {
  345. const list = rows || [];
  346. const prevIds = new Set((this.selectedRows || []).map((row) => row.id));
  347. const curIds = new Set(list.map((row) => row.id));
  348. // 被取消勾选的人员
  349. prevIds.forEach((id) => {
  350. if (!curIds.has(id) && !this.uncheckedIds.includes(id)) {
  351. this.uncheckedIds.push(id);
  352. }
  353. });
  354. // 被重新勾选的人员
  355. this.uncheckedIds = this.uncheckedIds.filter((id) => !curIds.has(id));
  356. this.selectedRows = list;
  357. },
  358. handleClose() {
  359. this.visible = false;
  360. this.current = null;
  361. this.radio = null;
  362. this.selectedRows = [];
  363. this.uncheckedIds = [];
  364. this.preselected = [];
  365. },
  366. selected() {
  367. if (this.isMultiple) {
  368. const rows = this.mergeSelected();
  369. if (!rows.length) {
  370. return this.$message.warning('请至少选择一项');
  371. }
  372. this.$emit('changeParent', rows, this.currentIndex);
  373. } else {
  374. if (!this.current) {
  375. return this.$message.warning('请至少选择一项');
  376. }
  377. this.$emit('changeParent', this.current, this.currentIndex);
  378. }
  379. this.handleClose();
  380. }
  381. }
  382. };
  383. </script>
  384. <style lang="scss" scoped>
  385. .selected-count {
  386. float: left;
  387. line-height: 32px;
  388. color: #606266;
  389. font-size: 13px;
  390. }
  391. .tree_col {
  392. border: 1px solid #eee;
  393. padding: 10px 0;
  394. box-sizing: border-box;
  395. height: 500px;
  396. overflow: auto;
  397. }
  398. .table_col {
  399. padding-left: 10px;
  400. ::v-deep .el-table th.el-table__cell {
  401. background: #f2f2f2;
  402. }
  403. }
  404. .pagination {
  405. text-align: right;
  406. padding: 10px 0;
  407. }
  408. .btns {
  409. text-align: center;
  410. padding: 10px 0;
  411. }
  412. .topsearch {
  413. margin-bottom: 15px;
  414. }
  415. </style>