tree.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. <template>
  2. <div class="cl-dept-tree">
  3. <div class="cl-dept-tree__header">
  4. <div>组织架构</div>
  5. <ul class="cl-dept-tree__op">
  6. <li>
  7. <el-tooltip content="刷新">
  8. <i class="el-icon-refresh" @click="refresh()"></i>
  9. </el-tooltip>
  10. </li>
  11. <li v-if="drag && isPc">
  12. <el-tooltip content="拖动排序">
  13. <i class="el-icon-s-operation" @click="isDrag = true"></i>
  14. </el-tooltip>
  15. </li>
  16. <li class="no" v-show="isDrag">
  17. <el-button type="text" size="mini" @click="treeOrder(true)">保存</el-button>
  18. <el-button type="text" size="mini" @click="treeOrder(false)">取消</el-button>
  19. </li>
  20. </ul>
  21. </div>
  22. <div class="cl-dept-tree__container" @contextmenu.prevent="openCM">
  23. <el-tree
  24. node-key="id"
  25. highlight-current
  26. default-expand-all
  27. :data="list"
  28. :props="{
  29. label: 'name'
  30. }"
  31. :draggable="isDrag"
  32. :allow-drag="allowDrag"
  33. :allow-drop="allowDrop"
  34. :expand-on-click-node="false"
  35. v-loading="loading"
  36. @node-contextmenu="openCM"
  37. >
  38. <template slot-scope="{ node, data }">
  39. <div class="cl-dept-tree__node">
  40. <span class="cl-dept-tree__node-label" @click="rowClick(data)">{{
  41. node.label
  42. }}</span>
  43. <span
  44. class="cl-dept-tree__node-icon"
  45. v-if="!isPc"
  46. @click="openCM($event, data, node)"
  47. >
  48. <i class="el-icon-more"></i>
  49. </span>
  50. </div>
  51. </template>
  52. </el-tree>
  53. </div>
  54. </div>
  55. </template>
  56. <script>
  57. import { deepTree, isArray, revDeepTree, isPc } from "cl-admin/utils";
  58. import { ContextMenu, Form } from "cl-admin-crud";
  59. export default {
  60. name: "cl-dept-tree",
  61. props: {
  62. drag: {
  63. type: Boolean,
  64. default: true
  65. },
  66. level: {
  67. type: Number,
  68. default: 99
  69. }
  70. },
  71. data() {
  72. return {
  73. list: [],
  74. loading: false,
  75. isDrag: false,
  76. isPc: isPc()
  77. };
  78. },
  79. created() {
  80. this.refresh();
  81. },
  82. methods: {
  83. openCM(e, d, n) {
  84. let list = [
  85. {
  86. label: "新增",
  87. "suffix-icon": "el-icon-plus",
  88. hidden: n && n.level >= this.level,
  89. callback: (_, done) => {
  90. this.rowEdit({
  91. name: "",
  92. parentName: d.name,
  93. parentId: d.id
  94. });
  95. done();
  96. }
  97. },
  98. {
  99. label: "编辑",
  100. "suffix-icon": "el-icon-edit",
  101. callback: (_, done) => {
  102. this.rowEdit(d);
  103. done();
  104. }
  105. }
  106. ];
  107. if (!d) {
  108. d = this.list[0];
  109. }
  110. if (d.parentId) {
  111. list.push({
  112. label: "删除",
  113. "suffix-icon": "el-icon-delete",
  114. callback: (_, done) => {
  115. this.rowDel(d);
  116. done();
  117. }
  118. });
  119. }
  120. list.push({
  121. label: "新增成员",
  122. "suffix-icon": "el-icon-user",
  123. callback: (_, done) => {
  124. this.$emit("user-add", d);
  125. done();
  126. }
  127. });
  128. ContextMenu.open(e, {
  129. list
  130. });
  131. },
  132. allowDrag({ data }) {
  133. return data.parentId;
  134. },
  135. allowDrop(draggingNode, dropNode) {
  136. return dropNode.data.parentId;
  137. },
  138. refresh() {
  139. this.isDrag = false;
  140. this.loading = true;
  141. this.$service.system.dept
  142. .list()
  143. .then((res) => {
  144. this.list = deepTree(res);
  145. this.$emit("list-change", this.list);
  146. })
  147. .done(() => {
  148. this.loading = false;
  149. });
  150. },
  151. rowClick(e) {
  152. ContextMenu.close();
  153. let ids = e.children ? revDeepTree(e.children).map((e) => e.id) : [];
  154. ids.unshift(e.id);
  155. this.$emit("row-click", { item: e, ids });
  156. },
  157. rowEdit(e) {
  158. const method = e.id ? "update" : "add";
  159. Form.open({
  160. title: "编辑部门",
  161. width: "550px",
  162. props: {
  163. "label-width": "100px"
  164. },
  165. items: [
  166. {
  167. label: "部门名称",
  168. prop: "name",
  169. value: e.name,
  170. component: {
  171. name: "el-input",
  172. attrs: {
  173. placeholder: "请填写部门名称"
  174. }
  175. },
  176. rules: {
  177. required: true,
  178. message: "部门名称不能为空"
  179. }
  180. },
  181. {
  182. label: "上级部门",
  183. prop: "parentId",
  184. value: e.parentName || "...",
  185. component: {
  186. name: "el-input",
  187. attrs: {
  188. disabled: true
  189. }
  190. }
  191. },
  192. {
  193. label: "排序",
  194. prop: "orderNum",
  195. value: e.orderNum || 0,
  196. component: {
  197. name: "el-input-number",
  198. props: {
  199. "controls-position": "right",
  200. min: 0,
  201. max: 100
  202. }
  203. }
  204. }
  205. ],
  206. on: {
  207. submit: (data, { done, close }) => {
  208. this.$service.system.dept[method]({
  209. id: e.id,
  210. parentId: e.parentId,
  211. name: data.name,
  212. orderNum: data.orderNum
  213. })
  214. .then(() => {
  215. this.$message.success(`新增部门${data.name}成功`);
  216. close();
  217. this.refresh();
  218. })
  219. .catch((err) => {
  220. this.$message.error(err);
  221. done();
  222. });
  223. }
  224. }
  225. });
  226. },
  227. rowDel(e) {
  228. const del = (f) => {
  229. this.$service.system.dept
  230. .delete({
  231. ids: e.id,
  232. deleteUser: f
  233. })
  234. .then(() => {
  235. if (f) {
  236. this.$message.success("删除成功");
  237. } else {
  238. this.$confirm("该部门用户已移动到部门顶级", "删除成功");
  239. }
  240. })
  241. .done(() => {
  242. this.refresh();
  243. });
  244. };
  245. this.$confirm("该操作会删除部门下的所有用户,是否确认?", "提示", {
  246. type: "warning",
  247. confirmButtonText: "直接删除",
  248. cancelButtonText: "保留用户",
  249. distinguishCancelAndClose: true
  250. })
  251. .then(() => {
  252. del(true);
  253. })
  254. .catch((action) => {
  255. if (action == "cancel") {
  256. del(false);
  257. }
  258. });
  259. },
  260. treeOrder(f) {
  261. if (f) {
  262. this.$confirm("部门架构已发生改变,是否保存?", "提示", {
  263. type: "warning"
  264. })
  265. .then(() => {
  266. const deep = (list, pid) => {
  267. list.forEach((e) => {
  268. e.parentId = pid;
  269. ids.push(e);
  270. if (e.children && isArray(e.children)) {
  271. deep(e.children, e.id);
  272. }
  273. });
  274. };
  275. let ids = [];
  276. deep(this.list, null);
  277. this.$service.system.dept
  278. .order(
  279. ids.map((e, i) => {
  280. return {
  281. id: e.id,
  282. parentId: e.parentId,
  283. orderNum: i
  284. };
  285. })
  286. )
  287. .then(() => {
  288. this.$message.success("更新排序成功");
  289. })
  290. .catch((err) => {
  291. this.$message.error(err);
  292. })
  293. .done(() => {
  294. this.refresh();
  295. this.isDrag = false;
  296. });
  297. })
  298. .catch(() => {});
  299. } else {
  300. this.refresh();
  301. }
  302. }
  303. }
  304. };
  305. </script>
  306. <style lang="scss" scoped>
  307. .cl-dept-tree {
  308. height: 100%;
  309. width: 100%;
  310. &__header {
  311. display: flex;
  312. align-items: center;
  313. height: 40px;
  314. padding: 0 10px;
  315. background-color: #fff;
  316. letter-spacing: 1px;
  317. position: relative;
  318. div {
  319. font-size: 14px;
  320. color: $color-main;
  321. flex: 1;
  322. white-space: nowrap;
  323. }
  324. i {
  325. font-size: 18px;
  326. color: $color-main;
  327. cursor: pointer;
  328. }
  329. }
  330. /deep/.el-tree-node__content {
  331. height: 36px;
  332. }
  333. &__op {
  334. display: flex;
  335. li {
  336. display: flex;
  337. justify-content: center;
  338. align-items: center;
  339. list-style: none;
  340. margin-left: 5px;
  341. padding: 5px;
  342. cursor: pointer;
  343. &:not(.no):hover {
  344. background-color: #eee;
  345. }
  346. }
  347. }
  348. &__container {
  349. height: calc(100% - 40px);
  350. overflow-y: auto;
  351. overflow-x: hidden;
  352. /deep/.el-tree-node__content {
  353. margin: 0 5px;
  354. }
  355. }
  356. &__node {
  357. display: flex;
  358. align-items: center;
  359. height: 100%;
  360. width: 100%;
  361. box-sizing: border-box;
  362. &-label {
  363. display: flex;
  364. align-items: center;
  365. flex: 1;
  366. height: 100%;
  367. font-size: 14px;
  368. overflow: hidden;
  369. text-overflow: ellipsis;
  370. white-space: nowrap;
  371. }
  372. &-icon {
  373. height: 28px;
  374. width: 28px;
  375. line-height: 28px;
  376. text-align: center;
  377. margin-right: 5px;
  378. }
  379. }
  380. }
  381. </style>