dept-select.vue 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <!-- 部门树形选择下拉框 -->
  2. <template>
  3. <ele-tree-select
  4. clearable
  5. :value="value || ''"
  6. :data="treeData"
  7. label-key="name"
  8. value-key="id"
  9. default-expand-all
  10. :placeholder="placeholder"
  11. :filterable="filterable"
  12. :filter-method="deptFilterMethod"
  13. @input="updateValue"
  14. @change="changeChoose"
  15. @visible-change="visibleChange"
  16. ref="treeSelect"
  17. v-bind="$attrs"
  18. />
  19. </template>
  20. <script>
  21. import { listOrganizations } from '@/api/system/organization';
  22. import { getCurrentUser } from '@/utils/token-util';
  23. export default {
  24. props: {
  25. // 选中的数据(v-model)
  26. value: [Number, String],
  27. // 提示信息
  28. placeholder: {
  29. type: String,
  30. default: '请选择'
  31. },
  32. // 是否在下拉框内显示搜索框
  33. filterable: {
  34. type: Boolean,
  35. default: false
  36. },
  37. // 是否只显示当前登录用户所属公司的机构
  38. companyScope: {
  39. type: Boolean,
  40. default: false
  41. },
  42. // 自定义搜索过滤方法(关键字, 节点数据),不传则默认按部门名称/部门编码匹配
  43. filterMethod: {
  44. type: Function,
  45. default: null
  46. }
  47. },
  48. data() {
  49. return {
  50. treeData: []
  51. };
  52. },
  53. computed: {
  54. // 传给 ele-tree-select 的过滤方法
  55. deptFilterMethod() {
  56. return this.filterMethod || this.defaultFilterMethod;
  57. }
  58. },
  59. created() {
  60. this.getData();
  61. },
  62. methods: {
  63. // 默认过滤:按部门名称或部门编码模糊匹配(不区分大小写)
  64. defaultFilterMethod(keyword, data) {
  65. const key = (keyword || '').trim().toLowerCase();
  66. if (!key) {
  67. return true;
  68. }
  69. return (
  70. (data.name || '').toLowerCase().includes(key) ||
  71. (data.groupCode || '').toLowerCase().includes(key)
  72. );
  73. },
  74. async getData(parmas = {}) {
  75. const data = await listOrganizations(parmas);
  76. const list = this.companyScope
  77. ? this.filterByCurrentCompany(data || [])
  78. : data || [];
  79. this.treeData = this.$util.toTreeData({
  80. data: list,
  81. idField: 'id',
  82. parentIdField: 'parentId'
  83. });
  84. },
  85. /* 当前登录用户所属机构id(优先取顶栏切换后的当前机构) */
  86. getCurrentGroupId() {
  87. let currentGroupId = null;
  88. try {
  89. currentGroupId = getCurrentUser()?.currentGroupId;
  90. } catch (e) {
  91. currentGroupId = null;
  92. }
  93. const userInfo = this.$store?.state?.user?.info || {};
  94. return currentGroupId || userInfo.groupId || null;
  95. },
  96. /* 只保留当前登录用户所属公司(顶级机构)下的机构 */
  97. filterByCurrentCompany(list) {
  98. const groupId = this.getCurrentGroupId();
  99. if (groupId == null || groupId === '') {
  100. return list;
  101. }
  102. const nodeMap = new Map();
  103. list.forEach((item) => nodeMap.set(String(item.id), item));
  104. if (!nodeMap.has(String(groupId))) {
  105. // 找不到当前用户所属机构时不做过滤, 避免下拉框被清空
  106. return list;
  107. }
  108. const companyCache = new Map();
  109. /* 沿 parentId 上溯, 取所属顶级机构(公司) */
  110. const getCompanyId = (id) => {
  111. const key = String(id);
  112. if (companyCache.has(key)) {
  113. return companyCache.get(key);
  114. }
  115. let node = nodeMap.get(key);
  116. let companyId = key;
  117. const visited = new Set();
  118. while (node) {
  119. companyId = String(node.id);
  120. const parentKey =
  121. node.parentId == null || node.parentId === ''
  122. ? ''
  123. : String(node.parentId);
  124. if (
  125. !parentKey ||
  126. !nodeMap.has(parentKey) ||
  127. visited.has(companyId)
  128. ) {
  129. break;
  130. }
  131. visited.add(companyId);
  132. node = nodeMap.get(parentKey);
  133. }
  134. companyCache.set(key, companyId);
  135. return companyId;
  136. };
  137. const companyId = getCompanyId(groupId);
  138. const selectedKey =
  139. this.value == null || this.value === '' ? '' : String(this.value);
  140. const selectedCompanyId =
  141. selectedKey && nodeMap.has(selectedKey)
  142. ? getCompanyId(selectedKey)
  143. : '';
  144. return list.filter((item) => {
  145. const itemCompanyId = getCompanyId(item.id);
  146. if (itemCompanyId === companyId) {
  147. return true;
  148. }
  149. // 已选机构属于其他公司时一并保留, 保证编辑/详情能正常回显名称
  150. return !!selectedCompanyId && itemCompanyId === selectedCompanyId;
  151. });
  152. },
  153. /* 更新选中数据 */
  154. updateValue(value) {
  155. this.$emit('input', value);
  156. },
  157. changeChoose(val) {
  158. this.$emit(
  159. 'changeGroup',
  160. val,
  161. this.$refs.treeSelect.getNodeByValue(val)
  162. );
  163. },
  164. // 下拉框展开/收起时清空上一次的搜索关键字,保证每次打开都是空的
  165. visibleChange() {
  166. this.$nextTick(() => {
  167. const treeSelect = this.$refs.treeSelect;
  168. if (treeSelect && treeSelect.keywords) {
  169. treeSelect.keywords = '';
  170. treeSelect.filterNode();
  171. }
  172. });
  173. },
  174. async locateByName(code) {
  175. await this.getData();
  176. const res = this.findNodesByNameWithPath(this.treeData, code);
  177. console.log(res);
  178. if (!res.length) return;
  179. const { node, path } = res[0];
  180. this.$nextTick(() => {
  181. // 设置选中
  182. this.updateValue(node.id);
  183. this.changeChoose(node.id);
  184. // 展开树路径
  185. const tree = this.$refs.treeSelect?.$refs?.tree;
  186. if (!tree) return;
  187. path.forEach((p) => {
  188. const treeNode = tree.store.nodesMap[p.id];
  189. treeNode && treeNode.expand();
  190. });
  191. });
  192. },
  193. findNodesByNameWithPath(tree, code) {
  194. const result = [];
  195. function dfs(list, path = []) {
  196. list.forEach((node) => {
  197. const currentPath = [...path, node];
  198. if (node.groupCode == code) {
  199. result.push({
  200. node,
  201. path: currentPath
  202. });
  203. }
  204. if (node.children?.length) {
  205. dfs(node.children, currentPath);
  206. }
  207. });
  208. }
  209. dfs(tree);
  210. return result;
  211. }
  212. }
  213. };
  214. </script>